| 446 | }; |
| 447 | |
| 448 | export const createExecutionEngine = <E extends Cause.YieldableError = CodeExecutionError>( |
| 449 | config: ExecutionEngineConfig<E>, |
| 450 | ): ExecutionEngine<E> => { |
| 451 | const { executor, codeExecutor, toolDiscoveryProvider = defaultToolDiscoveryProvider } = config; |
| 452 | const pausedExecutions = new Map<string, InternalPausedExecution<E>>(); |
| 453 | // Outcomes of executions that already settled (resumed to completion, hit a |
| 454 | // new pause, or died while paused). MCP clients retry `resume` when a |
| 455 | // response gets lost in transit; without this cache the retry of an |
| 456 | // already-delivered resume answers "no paused execution" (observed in |
| 457 | // production seconds after a successful resume). Bounded FIFO — pause |
| 458 | // volume is tiny (human approvals), so a small window is plenty. |
| 459 | const settledOutcomes = new Map<string, Exit.Exit<ExecutionResult, E>>(); |
| 460 | const SETTLED_OUTCOME_LIMIT = 64; |
| 461 | const settledExecutionIds = new Set<string>(); |
| 462 | const SETTLED_EXECUTION_ID_LIMIT = 1024; |
| 463 | // Resumes whose outcome is still being computed, so a concurrent duplicate |
| 464 | // awaits the same result instead of missing the (already-consumed) pause. |
| 465 | const pendingResumes = new Map<string, Deferred.Deferred<ExecutionResult, E>>(); |
| 466 | |
| 467 | // Exits (not just successes) so a replayed failure re-fails through the |
| 468 | // typed channel — hosts render engine failures opaquely, and a replay must |
| 469 | // not bypass that by flattening the cause into result text. |
| 470 | const recordSettledOutcome = (executionId: string, exit: Exit.Exit<ExecutionResult, E>): void => { |
| 471 | settledExecutionIds.add(executionId); |
| 472 | while (settledExecutionIds.size > SETTLED_EXECUTION_ID_LIMIT) { |
| 473 | const oldest = settledExecutionIds.keys().next().value; |
| 474 | if (oldest === undefined) break; |
| 475 | settledExecutionIds.delete(oldest); |
| 476 | } |
| 477 | settledOutcomes.set(executionId, exit); |
| 478 | while (settledOutcomes.size > SETTLED_OUTCOME_LIMIT) { |
| 479 | const oldest = settledOutcomes.keys().next().value; |
| 480 | if (oldest === undefined) break; |
| 481 | settledOutcomes.delete(oldest); |
| 482 | } |
| 483 | }; |
| 484 | |
| 485 | /** |
| 486 | * Race a running fiber against the pause queue. Returns when either |
| 487 | * the fiber completes or an elicitation handler fires (whichever |
| 488 | * comes first). Re-used by both executeWithPause and resume. |
| 489 | * |
| 490 | * `Effect.raceFirst` (not `Effect.race`) — `race` has prefer-success |
| 491 | * semantics in Effect v4 ("first successful result"), which means a |
| 492 | * fiber failure waits indefinitely for the pause Deferred to succeed. |
| 493 | * For a fast `codeExecutor.execute` failure (e.g. a syntax error |
| 494 | * inside the dynamic worker) the pause signal never fires, so the |
| 495 | * outer Effect hangs until the upstream client gives up. `raceFirst` |
| 496 | * settles on whichever side completes first, success or failure. |
| 497 | */ |
| 498 | const awaitCompletionOrPause = ( |
| 499 | fiber: Fiber.Fiber<ExecuteResult, E>, |
| 500 | pauseQueue: Queue.Queue<InternalPausedExecution<E>>, |
| 501 | ): Effect.Effect<ExecutionResult, E> => |
| 502 | Effect.raceFirst( |
| 503 | Fiber.join(fiber).pipe( |
| 504 | Effect.map((result): ExecutionResult => ({ status: "completed", result })), |
| 505 | ), |