| ... | ... | @@ -1,7 +1,10 @@ |
| 1 | 1 | import { |
| 2 | type AssistantDeliveryMode, |
| 2 | 3 | CommandId, |
| 3 | 4 | MessageId, |
| 5 | type OrchestrationEvent, |
| 4 | 6 | ProviderThreadId, |
| 7 | type ThreadId, |
| 5 | 8 | TurnId, |
| 6 | 9 | type OrchestrationThreadActivity, |
| 7 | 10 | type ProviderRuntimeEvent, |
| ... | ... | @@ -19,8 +22,32 @@ import { |
| 19 | 22 | const providerTurnKey = (sessionId: ProviderSessionId, turnId: TurnId) => `${sessionId}:${turnId}`; |
| 20 | 23 | const providerCommandId = (event: ProviderRuntimeEvent, tag: string): CommandId => |
| 21 | 24 | CommandId.makeUnsafe(`provider:${event.eventId}:${tag}:${crypto.randomUUID()}`); |
| 25 | const DEFAULT_ASSISTANT_DELIVERY_MODE: AssistantDeliveryMode = "buffered"; |
| 22 | 26 | const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000; |
| 23 | 27 | const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120); |
| 28 | const TURN_DELIVERY_MODE_BY_TURN_CACHE_CAPACITY = 10_000; |
| 29 | const TURN_DELIVERY_MODE_BY_TURN_TTL = Duration.minutes(120); |
| 30 | const PENDING_DELIVERY_MODE_BY_THREAD_CACHE_CAPACITY = 10_000; |
| 31 | const PENDING_DELIVERY_MODE_BY_THREAD_TTL = Duration.minutes(30); |
| 32 | const MESSAGE_DELIVERY_MODE_BY_MESSAGE_ID_CACHE_CAPACITY = 20_000; |
| 33 | const MESSAGE_DELIVERY_MODE_BY_MESSAGE_ID_TTL = Duration.minutes(120); |
| 34 | const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_CACHE_CAPACITY = 20_000; |
| 35 | const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_TTL = Duration.minutes(120); |
| 36 | |
| 37 | type TurnStartRequestedDomainEvent = Extract< |
| 38 | OrchestrationEvent, |
| 39 | { type: "thread.turn-start-requested" } |
| 40 | >; |
| 41 | |
| 42 | type RuntimeIngestionInput = |
| 43 | | { |
| 44 | source: "runtime"; |
| 45 | event: ProviderRuntimeEvent; |
| 46 | } |
| 47 | | { |
| 48 | source: "domain"; |
| 49 | event: TurnStartRequestedDomainEvent; |
| 50 | }; |
| 24 | 51 | |
| 25 | 52 | function toTurnId(value: string | undefined): TurnId | undefined { |
| 26 | 53 | return value === undefined ? undefined : TurnId.makeUnsafe(value); |
| ... | ... | @@ -135,6 +162,26 @@ const make = Effect.gen(function* () { |
| 135 | 162 | timeToLive: TURN_MESSAGE_IDS_BY_TURN_TTL, |
| 136 | 163 | lookup: () => Effect.succeed(new Set<MessageId>()), |
| 137 | 164 | }); |
| 165 | const assistantDeliveryModeByTurnKey = yield* Cache.make<string, AssistantDeliveryMode>({ |
| 166 | capacity: TURN_DELIVERY_MODE_BY_TURN_CACHE_CAPACITY, |
| 167 | timeToLive: TURN_DELIVERY_MODE_BY_TURN_TTL, |
| 168 | lookup: () => Effect.succeed(DEFAULT_ASSISTANT_DELIVERY_MODE), |
| 169 | }); |
| 170 | const pendingAssistantDeliveryModeByThreadId = yield* Cache.make<ThreadId, AssistantDeliveryMode>({ |
| 171 | capacity: PENDING_DELIVERY_MODE_BY_THREAD_CACHE_CAPACITY, |
| 172 | timeToLive: PENDING_DELIVERY_MODE_BY_THREAD_TTL, |
| 173 | lookup: () => Effect.succeed(DEFAULT_ASSISTANT_DELIVERY_MODE), |
| 174 | }); |
| 175 | const assistantDeliveryModeByMessageId = yield* Cache.make<MessageId, AssistantDeliveryMode>({ |
| 176 | capacity: MESSAGE_DELIVERY_MODE_BY_MESSAGE_ID_CACHE_CAPACITY, |
| 177 | timeToLive: MESSAGE_DELIVERY_MODE_BY_MESSAGE_ID_TTL, |
| 178 | lookup: () => Effect.succeed(DEFAULT_ASSISTANT_DELIVERY_MODE), |
| 179 | }); |
| 180 | const bufferedAssistantTextByMessageId = yield* Cache.make<MessageId, string>({ |
| 181 | capacity: BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_CACHE_CAPACITY, |
| 182 | timeToLive: BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_TTL, |
| 183 | lookup: () => Effect.succeed(""), |
| 184 | }); |
| 138 | 185 | |
| 139 | 186 | const rememberAssistantMessageId = ( |
| 140 | 187 | sessionId: ProviderSessionId, |
| ... | ... | @@ -189,6 +236,144 @@ const make = Effect.gen(function* () { |
| 189 | 236 | const clearAssistantMessageIdsForTurn = (sessionId: ProviderSessionId, turnId: TurnId) => |
| 190 | 237 | Cache.invalidate(turnMessageIdsByTurnKey, providerTurnKey(sessionId, turnId)); |
| 191 | 238 | |
| 239 | const rememberAssistantDeliveryModeForTurn = ( |
| 240 | sessionId: ProviderSessionId, |
| 241 | turnId: TurnId, |
| 242 | assistantDeliveryMode: AssistantDeliveryMode, |
| 243 | ) => |
| 244 | Cache.set( |
| 245 | assistantDeliveryModeByTurnKey, |
| 246 | providerTurnKey(sessionId, turnId), |
| 247 | assistantDeliveryMode, |
| 248 | ); |
| 249 | |
| 250 | const clearAssistantDeliveryModeForTurn = (sessionId: ProviderSessionId, turnId: TurnId) => |
| 251 | Cache.invalidate(assistantDeliveryModeByTurnKey, providerTurnKey(sessionId, turnId)); |
| 252 | |
| 253 | const rememberPendingAssistantDeliveryModeForThread = ( |
| 254 | threadId: ThreadId, |
| 255 | assistantDeliveryMode: AssistantDeliveryMode, |
| 256 | ) => Cache.set(pendingAssistantDeliveryModeByThreadId, threadId, assistantDeliveryMode); |
| 257 | |
| 258 | const takePendingAssistantDeliveryModeForThread = (threadId: ThreadId) => |
| 259 | Cache.getOption(pendingAssistantDeliveryModeByThreadId, threadId).pipe( |
| 260 | Effect.flatMap((assistantDeliveryMode) => |
| 261 | Cache.invalidate(pendingAssistantDeliveryModeByThreadId, threadId).pipe( |
| 262 | Effect.as(assistantDeliveryMode), |
| 263 | ), |
| 264 | ), |
| 265 | ); |
| 266 | |
| 267 | const clearPendingAssistantDeliveryModeForThread = (threadId: ThreadId) => |
| 268 | Cache.invalidate(pendingAssistantDeliveryModeByThreadId, threadId); |
| 269 | |
| 270 | const rememberAssistantDeliveryModeForMessage = ( |
| 271 | messageId: MessageId, |
| 272 | assistantDeliveryMode: AssistantDeliveryMode, |
| 273 | ) => Cache.set(assistantDeliveryModeByMessageId, messageId, assistantDeliveryMode); |
| 274 | |
| 275 | const clearAssistantDeliveryModeForMessage = (messageId: MessageId) => |
| 276 | Cache.invalidate(assistantDeliveryModeByMessageId, messageId); |
| 277 | |
| 278 | const appendBufferedAssistantText = (messageId: MessageId, delta: string) => |
| 279 | Cache.getOption(bufferedAssistantTextByMessageId, messageId).pipe( |
| 280 | Effect.flatMap((existingText) => |
| 281 | Cache.set( |
| 282 | bufferedAssistantTextByMessageId, |
| 283 | messageId, |
| 284 | Option.match(existingText, { |
| 285 | onNone: () => delta, |
| 286 | onSome: (text) => `${text}${delta}`, |
| 287 | }), |
| 288 | ), |
| 289 | ), |
| 290 | ); |
| 291 | |
| 292 | const takeBufferedAssistantText = (messageId: MessageId) => |
| 293 | Cache.getOption(bufferedAssistantTextByMessageId, messageId).pipe( |
| 294 | Effect.flatMap((existingText) => |
| 295 | Cache.invalidate(bufferedAssistantTextByMessageId, messageId).pipe( |
| 296 | Effect.as(Option.getOrElse(existingText, () => "")), |
| 297 | ), |
| 298 | ), |
| 299 | ); |
| 300 | |
| 301 | const clearBufferedAssistantText = (messageId: MessageId) => |
| 302 | Cache.invalidate(bufferedAssistantTextByMessageId, messageId); |
| 303 | |
| 304 | const clearAssistantMessageState = (messageId: MessageId) => |
| 305 | Effect.all( |
| 306 | [ |
| 307 | clearAssistantDeliveryModeForMessage(messageId), |
| 308 | clearBufferedAssistantText(messageId), |
| 309 | ], |
| 310 | { concurrency: 1 }, |
| 311 | ).pipe(Effect.asVoid); |
| 312 | |
| 313 | const resolveAssistantDeliveryModeForMessage = (input: { |
| 314 | threadId: ThreadId; |
| 315 | sessionId: ProviderSessionId; |
| 316 | turnId?: TurnId; |
| 317 | messageId: MessageId; |
| 318 | }) => |
| 319 | Effect.gen(function* () { |
| 320 | const messageMode = yield* Cache.getOption(assistantDeliveryModeByMessageId, input.messageId); |
| 321 | if (Option.isSome(messageMode)) { |
| 322 | return messageMode.value; |
| 323 | } |
| 324 | |
| 325 | if (input.turnId) { |
| 326 | const turnMode = yield* Cache.getOption( |
| 327 | assistantDeliveryModeByTurnKey, |
| 328 | providerTurnKey(input.sessionId, input.turnId), |
| 329 | ); |
| 330 | if (Option.isSome(turnMode)) { |
| 331 | return turnMode.value; |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | const pendingMode = yield* Cache.getOption( |
| 336 | pendingAssistantDeliveryModeByThreadId, |
| 337 | input.threadId, |
| 338 | ); |
| 339 | if (Option.isSome(pendingMode)) { |
| 340 | if (input.turnId) { |
| 341 | yield* rememberAssistantDeliveryModeForTurn(input.sessionId, input.turnId, pendingMode.value); |
| 342 | yield* clearPendingAssistantDeliveryModeForThread(input.threadId); |
| 343 | } |
| 344 | return pendingMode.value; |
| 345 | } |
| 346 | |
| 347 | return DEFAULT_ASSISTANT_DELIVERY_MODE; |
| 348 | }); |
| 349 | |
| 350 | const dispatchAssistantCompletion = (input: { |
| 351 | event: ProviderRuntimeEvent; |
| 352 | threadId: ThreadId; |
| 353 | messageId: MessageId; |
| 354 | turnId?: TurnId; |
| 355 | assistantDeliveryMode: AssistantDeliveryMode; |
| 356 | createdAt: string; |
| 357 | commandTag: string; |
| 358 | }) => |
| 359 | Effect.gen(function* () { |
| 360 | const text = |
| 361 | input.assistantDeliveryMode === "buffered" |
| 362 | ? yield* takeBufferedAssistantText(input.messageId) |
| 363 | : undefined; |
| 364 | |
| 365 | yield* orchestrationEngine.dispatch({ |
| 366 | type: "thread.message.assistant.complete", |
| 367 | commandId: providerCommandId(input.event, input.commandTag), |
| 368 | threadId: input.threadId, |
| 369 | messageId: input.messageId, |
| 370 | ...(input.turnId ? { turnId: input.turnId } : {}), |
| 371 | ...(text !== undefined ? { text } : {}), |
| 372 | createdAt: input.createdAt, |
| 373 | }); |
| 374 | yield* clearAssistantMessageState(input.messageId); |
| 375 | }); |
| 376 | |
| 192 | 377 | const clearTurnStateForSession = (sessionId: ProviderSessionId) => |
| 193 | 378 | Effect.gen(function* () { |
| 194 | 379 | const prefix = `${sessionId}:`; |
| ... | ... | @@ -196,12 +381,35 @@ const make = Effect.gen(function* () { |
| 196 | 381 | yield* Effect.forEach( |
| 197 | 382 | turnKeys, |
| 198 | 383 | (key) => |
| 199 | | key.startsWith(prefix) ? Cache.invalidate(turnMessageIdsByTurnKey, key) : Effect.void, |
| 384 | Effect.gen(function* () { |
| 385 | if (!key.startsWith(prefix)) { |
| 386 | return; |
| 387 | } |
| 388 | |
| 389 | const messageIds = yield* Cache.getOption(turnMessageIdsByTurnKey, key); |
| 390 | if (Option.isSome(messageIds)) { |
| 391 | yield* Effect.forEach(messageIds.value, clearAssistantMessageState, { |
| 392 | concurrency: 1, |
| 393 | }).pipe(Effect.asVoid); |
| 394 | } |
| 395 | |
| 396 | yield* Cache.invalidate(turnMessageIdsByTurnKey, key); |
| 397 | }), |
| 398 | { concurrency: 1 }, |
| 399 | ).pipe(Effect.asVoid); |
| 400 | |
| 401 | const deliveryModeKeys = Array.from(yield* Cache.keys(assistantDeliveryModeByTurnKey)); |
| 402 | yield* Effect.forEach( |
| 403 | deliveryModeKeys, |
| 404 | (key) => |
| 405 | key.startsWith(prefix) |
| 406 | ? Cache.invalidate(assistantDeliveryModeByTurnKey, key) |
| 407 | : Effect.void, |
| 200 | 408 | { concurrency: 1 }, |
| 201 | 409 | ).pipe(Effect.asVoid); |
| 202 | 410 | }); |
| 203 | 411 | |
| 204 | | const processEvent = (event: ProviderRuntimeEvent) => |
| 412 | const processRuntimeEvent = (event: ProviderRuntimeEvent) => |
| 205 | 413 | Effect.gen(function* () { |
| 206 | 414 | const readModel = yield* orchestrationEngine.getReadModel(); |
| 207 | 415 | const thread = readModel.threads.find( |
| ... | ... | @@ -218,8 +426,19 @@ const make = Effect.gen(function* () { |
| 218 | 426 | event.type === "turn.started" || |
| 219 | 427 | event.type === "turn.completed" |
| 220 | 428 | ) { |
| 221 | | const activeTurnId = |
| 222 | | event.type === "turn.started" ? (toTurnId(event.turnId) ?? null) : null; |
| 429 | const activeTurnId = event.type === "turn.started" ? (toTurnId(event.turnId) ?? null) : null; |
| 430 | if (event.type === "turn.started" && activeTurnId) { |
| 431 | const pendingAssistantDeliveryMode = yield* takePendingAssistantDeliveryModeForThread( |
| 432 | thread.id, |
| 433 | ); |
| 434 | if (Option.isSome(pendingAssistantDeliveryMode)) { |
| 435 | yield* rememberAssistantDeliveryModeForTurn( |
| 436 | event.sessionId, |
| 437 | activeTurnId, |
| 438 | pendingAssistantDeliveryMode.value, |
| 439 | ); |
| 440 | } |
| 441 | } |
| 223 | 442 | const providerThreadIdFromEvent = |
| 224 | 443 | event.type === "thread.started" |
| 225 | 444 | ? ProviderThreadId.makeUnsafe(event.threadId) |
| ... | ... | @@ -269,16 +488,27 @@ const make = Effect.gen(function* () { |
| 269 | 488 | if (turnId) { |
| 270 | 489 | yield* rememberAssistantMessageId(event.sessionId, turnId, assistantMessageId); |
| 271 | 490 | } |
| 272 | | |
| 273 | | yield* orchestrationEngine.dispatch({ |
| 274 | | type: "thread.message.assistant.delta", |
| 275 | | commandId: providerCommandId(event, "assistant-delta"), |
| 491 | const assistantDeliveryMode = yield* resolveAssistantDeliveryModeForMessage({ |
| 276 | 492 | threadId: thread.id, |
| 493 | sessionId: event.sessionId, |
| 494 | turnId, |
| 277 | 495 | messageId: assistantMessageId, |
| 278 | | delta: event.delta, |
| 279 | | ...(turnId ? { turnId } : {}), |
| 280 | | createdAt: now, |
| 281 | 496 | }); |
| 497 | yield* rememberAssistantDeliveryModeForMessage(assistantMessageId, assistantDeliveryMode); |
| 498 | |
| 499 | if (assistantDeliveryMode === "buffered") { |
| 500 | yield* appendBufferedAssistantText(assistantMessageId, event.delta); |
| 501 | } else { |
| 502 | yield* orchestrationEngine.dispatch({ |
| 503 | type: "thread.message.assistant.delta", |
| 504 | commandId: providerCommandId(event, "assistant-delta"), |
| 505 | threadId: thread.id, |
| 506 | messageId: assistantMessageId, |
| 507 | delta: event.delta, |
| 508 | ...(turnId ? { turnId } : {}), |
| 509 | createdAt: now, |
| 510 | }); |
| 511 | } |
| 282 | 512 | } |
| 283 | 513 | |
| 284 | 514 | if (event.type === "message.completed") { |
| ... | ... | @@ -287,14 +517,22 @@ const make = Effect.gen(function* () { |
| 287 | 517 | if (turnId) { |
| 288 | 518 | yield* rememberAssistantMessageId(event.sessionId, turnId, assistantMessageId); |
| 289 | 519 | } |
| 520 | const assistantDeliveryMode = yield* resolveAssistantDeliveryModeForMessage({ |
| 521 | threadId: thread.id, |
| 522 | sessionId: event.sessionId, |
| 523 | turnId, |
| 524 | messageId: assistantMessageId, |
| 525 | }); |
| 526 | yield* rememberAssistantDeliveryModeForMessage(assistantMessageId, assistantDeliveryMode); |
| 290 | 527 | |
| 291 | | yield* orchestrationEngine.dispatch({ |
| 292 | | type: "thread.message.assistant.complete", |
| 293 | | commandId: providerCommandId(event, "assistant-complete"), |
| 528 | yield* dispatchAssistantCompletion({ |
| 529 | event, |
| 294 | 530 | threadId: thread.id, |
| 295 | 531 | messageId: assistantMessageId, |
| 296 | 532 | ...(turnId ? { turnId } : {}), |
| 533 | assistantDeliveryMode, |
| 297 | 534 | createdAt: now, |
| 535 | commandTag: "assistant-complete", |
| 298 | 536 | }); |
| 299 | 537 | |
| 300 | 538 | if (turnId) { |
| ... | ... | @@ -306,22 +544,37 @@ const make = Effect.gen(function* () { |
| 306 | 544 | const turnId = toTurnId(event.turnId); |
| 307 | 545 | if (turnId) { |
| 308 | 546 | const assistantMessageIds = yield* getAssistantMessageIdsForTurn(event.sessionId, turnId); |
| 309 | | yield* Effect.forEach(assistantMessageIds, (assistantMessageId) => |
| 310 | | orchestrationEngine.dispatch({ |
| 311 | | type: "thread.message.assistant.complete", |
| 312 | | commandId: providerCommandId(event, "assistant-complete-finalize"), |
| 313 | | threadId: thread.id, |
| 314 | | messageId: assistantMessageId, |
| 315 | | turnId, |
| 316 | | createdAt: now, |
| 317 | | }), |
| 547 | yield* Effect.forEach( |
| 548 | assistantMessageIds, |
| 549 | (assistantMessageId) => |
| 550 | Effect.gen(function* () { |
| 551 | const assistantDeliveryMode = |
| 552 | yield* resolveAssistantDeliveryModeForMessage({ |
| 553 | threadId: thread.id, |
| 554 | sessionId: event.sessionId, |
| 555 | turnId, |
| 556 | messageId: assistantMessageId, |
| 557 | }); |
| 558 | yield* dispatchAssistantCompletion({ |
| 559 | event, |
| 560 | threadId: thread.id, |
| 561 | messageId: assistantMessageId, |
| 562 | turnId, |
| 563 | assistantDeliveryMode, |
| 564 | createdAt: now, |
| 565 | commandTag: "assistant-complete-finalize", |
| 566 | }); |
| 567 | }), |
| 568 | { concurrency: 1 }, |
| 318 | 569 | ).pipe(Effect.asVoid); |
| 319 | 570 | yield* clearAssistantMessageIdsForTurn(event.sessionId, turnId); |
| 571 | yield* clearAssistantDeliveryModeForTurn(event.sessionId, turnId); |
| 320 | 572 | } |
| 321 | 573 | } |
| 322 | 574 | |
| 323 | 575 | if (event.type === "session.exited") { |
| 324 | 576 | yield* clearTurnStateForSession(event.sessionId); |
| 577 | yield* clearPendingAssistantDeliveryModeForThread(thread.id); |
| 325 | 578 | } |
| 326 | 579 | |
| 327 | 580 | if (event.type === "runtime.error") { |
| ... | ... | @@ -360,33 +613,50 @@ const make = Effect.gen(function* () { |
| 360 | 613 | ).pipe(Effect.asVoid); |
| 361 | 614 | }); |
| 362 | 615 | |
| 363 | | const processEventSafely = (event: ProviderRuntimeEvent) => |
| 364 | | processEvent(event).pipe( |
| 616 | const processDomainEvent = (event: TurnStartRequestedDomainEvent) => |
| 617 | rememberPendingAssistantDeliveryModeForThread( |
| 618 | event.payload.threadId, |
| 619 | event.payload.assistantDeliveryMode ?? DEFAULT_ASSISTANT_DELIVERY_MODE, |
| 620 | ); |
| 621 | |
| 622 | const processInput = (input: RuntimeIngestionInput) => |
| 623 | input.source === "runtime" ? processRuntimeEvent(input.event) : processDomainEvent(input.event); |
| 624 | |
| 625 | const processInputSafely = (input: RuntimeIngestionInput) => |
| 626 | processInput(input).pipe( |
| 365 | 627 | Effect.catchCause((cause) => { |
| 366 | 628 | if (Cause.hasInterruptsOnly(cause)) { |
| 367 | 629 | return Effect.failCause(cause); |
| 368 | 630 | } |
| 369 | 631 | return Effect.logWarning("provider runtime ingestion failed to process event", { |
| 370 | | eventId: event.eventId, |
| 371 | | eventType: event.type, |
| 632 | source: input.source, |
| 633 | eventId: input.event.eventId, |
| 634 | eventType: input.event.type, |
| 372 | 635 | cause: Cause.pretty(cause), |
| 373 | 636 | }); |
| 374 | 637 | }), |
| 375 | 638 | ); |
| 376 | 639 | |
| 377 | 640 | const start: ProviderRuntimeIngestionShape["start"] = Effect.gen(function* () { |
| 378 | | const providerEventQueue = yield* Queue.unbounded<ProviderRuntimeEvent>(); |
| 379 | | |
| 380 | | yield* Effect.addFinalizer(() => Queue.shutdown(providerEventQueue).pipe(Effect.asVoid)); |
| 641 | const inputQueue = yield* Queue.unbounded<RuntimeIngestionInput>(); |
| 642 | yield* Effect.addFinalizer(() => Queue.shutdown(inputQueue).pipe(Effect.asVoid)); |
| 381 | 643 | |
| 382 | 644 | yield* Effect.forkScoped( |
| 383 | | Effect.forever(Queue.take(providerEventQueue).pipe(Effect.flatMap(processEventSafely))), |
| 645 | Effect.forever(Queue.take(inputQueue).pipe(Effect.flatMap(processInputSafely))), |
| 384 | 646 | ); |
| 385 | 647 | yield* Effect.forkScoped( |
| 386 | 648 | Stream.runForEach(providerService.streamEvents, (event) => |
| 387 | | Queue.offer(providerEventQueue, event).pipe(Effect.asVoid), |
| 649 | Queue.offer(inputQueue, { source: "runtime", event }).pipe(Effect.asVoid), |
| 388 | 650 | ), |
| 389 | 651 | ); |
| 652 | yield* Effect.forkScoped( |
| 653 | Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) => { |
| 654 | if (event.type !== "thread.turn-start-requested") { |
| 655 | return Effect.void; |
| 656 | } |
| 657 | return Queue.offer(inputQueue, { source: "domain", event }).pipe(Effect.asVoid); |
| 658 | }), |
| 659 | ); |
| 390 | 660 | }); |
| 391 | 661 | |
| 392 | 662 | return { |