UNPKG

eve

Version:

Filesystem-first framework for durable backend AI agents that run anywhere.

107 lines 5.54 kB
import type { Span } from '#compiled/@opentelemetry/api/index.js'; import { type SerializedData, type WorkflowRun, type World } from '#compiled/@workflow/world/index.js'; import type { StepInvocationQueueItem, WorkflowSuspension } from '../global.js'; import { type MutableEventLog } from './helpers.js'; export interface SuspensionHandlerParams { suspension: WorkflowSuspension; world: World; run: WorkflowRun; span?: Span; requestId?: string; /** * The runtime's loaded event log. Each event creation is sent with this * snapshot's `stateUpdatedAt` and, if the backend rejects it as stale (412), * the log is reloaded in place and the create is retried — see * `withPreconditionRetry`. Guarding per-create (rather than the whole * suspension) ensures a retry never re-issues an already-created event. */ eventLog?: MutableEventLog; /** * Turbo mode only: a promise that resolves once the backgrounded * `run_started` has landed (the run exists). When present, every world write * this suspension performs (`hook_created`, `wait_created`, eager overflow * `step_created`, …) is gated on it so the write never races ahead of the * run's creation. The pure inline hot path defers all of its steps and writes * nothing here, so it never awaits this barrier. `undefined` outside turbo, * where `run_started` was already awaited up front. */ runReadyBarrier?: Promise<unknown>; } /** * Result of handling a suspension. Returns pending step items so the caller * can decide which to execute inline vs queue to background. */ export interface SuspensionHandlerResult { /** Pending step items with events created but NOT queued */ pendingSteps: StepInvocationQueueItem[]; /** * Correlation IDs for which this suspension call actually wrote the * step_created event (as opposed to catching EntityConflictError because * a concurrent handler wrote it first). Only the handler that wrote the * step_created event should queue / inline-execute the step — this * guarantees a single owner per step, even when multiple handlers race * into the same batch boundary. */ createdStepCorrelationIds: Set<string>; /** * The steps whose `step_created` writes were intentionally deferred so the * caller can run them inline via lazy `step_started` events (which create * the step on the fly), saving one world round-trip per inline step. Up to * `getMaxInlineSteps()` steps are deferred; the caller runs them inline in * parallel and queues the rest. Empty when no step was deferred (nothing * pending, or a `hook.getConflict()` awaiter is present so nothing is * executed inline). The caller passes each `dehydratedInput` straight to * `executeStep`, which sends it as the `step_started` payload. The atomic * create-claim inside each `step_started` is the exactly-one-owner gate that * the standalone `step_created` provided before: the loser of the race gets * `EntityConflictError` → `skipped` and does not run the body. */ lazyInlineSteps: Array<{ correlationId: string; stepName: string; dehydratedInput: SerializedData; }>; /** * The soonest pending wait, if any: seconds until it elapses and the * correlationId of the wait that produced that timeout. The * correlationId seeds the idempotency key for the wait-continuation * queue message so that repeated suspension passes over the same * pending wait collapse into a single delayed continuation. */ waitTimeout?: { seconds: number; correlationId: string; }; /** Whether a hook conflict was detected (should re-invoke immediately) */ hasHookConflict: boolean; /** Whether a `hook.getConflict()` awaiter needs the workflow to continue immediately */ hasAwaitedHookCreation: boolean; /** Whether native workflow attribute events were written for replay. */ hasAttributeEvents: boolean; /** * Whether this suspension created any hook (`hook_created`) events. Unlike * `hasHookConflict` / `hasAwaitedHookCreation`, this is true even for a plain * fire-and-forget hook with no conflict and no awaiter. Turbo mode uses it to * detect "a hook was created this suspension" and stop forcing optimistic * inline start (a hook introduces later resume invocations that could race). */ hasHookEvents: boolean; /** * Wall-clock ms spent committing this suspension's `hook_created` events * (0 when it created none). The caller accumulates this across iterations * and subtracts it from the TTFS latency measurement, so time spent * durably creating the user's hooks doesn't count as runtime overhead. */ hookCreationMs: number; } /** * Handles a workflow suspension by processing all pending operations (hooks, steps, waits). * Creates events for all operations but does NOT queue step messages — returns the pending * steps so the caller can decide which to execute inline vs queue to background. * * Processing order: * 1. Hooks are processed first to prevent race conditions with webhook receivers * 2. Step events and wait events are created in parallel */ export declare function handleSuspension({ suspension, world, run, span, requestId, eventLog, runReadyBarrier, }: SuspensionHandlerParams): Promise<SuspensionHandlerResult>; //# sourceMappingURL=suspension-handler.d.ts.map