eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
98 lines • 3.89 kB
TypeScript
/**
* Per-invocation accounting of the *non-step* portion of a workflow
* handler run: deterministic event-log replay, workflow-VM execution
* between step boundaries, suspension handling, queue round-trips, etc.
* Inline step bodies (`"use step"` functions invoked via `executeStep`)
* are intentionally excluded — they are bounded by the platform's
* function `maxDuration` and the `NO_INLINE_REPLAY_AFTER_MS` early-return
* guard.
*
* Usage:
*
* ```ts
* const budget = new ReplayBudget();
* // …non-step work happens here, accumulates against the budget…
* budget.pause();
* try {
* await executeStep(...); // not charged
* } finally {
* budget.resume();
* }
* // back to charging
* if (budget.isExhausted()) { ... }
* ```
*
* Implementation notes:
*
* - `pause()` and `resume()` are idempotent: calling `pause()` while
* already paused (or `resume()` while already resumed) is a no-op.
* This protects against double-counting in future refactors that nest
* step execution or take an early-return path between a `pause()` and
* the matching `resume()`.
* - `isExhausted()` is checked at loop boundaries by the caller — the
* budget itself does not arm any timers. This means an in-flight
* pathological `runWorkflow` call (e.g. a huge event-log replay) can
* overshoot the budget by up to one iteration's worth of work before
* the next check fires. In practice the 20s headroom built into
* `MAX_REPLAY_TIMEOUT_MS` (and the function `maxDuration` ceiling)
* gives us slack; the old `setTimeout`-based approach also ultimately
* relied on the platform SIGTERM as the hard backstop.
*/
export declare class ReplayBudget {
private readonly limitMs;
private elapsedMs;
private intervalStart;
constructor(limitMs?: number);
/**
* The configured replay-timeout limit, in ms. Useful for log messages.
*/
get configuredLimitMs(): number;
/**
* Total non-step time accumulated so far, in ms. Includes the
* currently-active interval if the budget is not paused.
*/
elapsed(): number;
/**
* Stop counting elapsed time toward the budget. Idempotent — safe to
* call multiple times in a row; subsequent calls are no-ops until
* `resume()` reopens an interval.
*/
pause(): void;
/**
* Resume counting elapsed time toward the budget. Idempotent — safe to
* call multiple times in a row; subsequent calls re-anchor the
* interval start to `now()`, which is fine because no time accrues
* between back-to-back `resume()` calls.
*/
resume(): void;
/**
* True if the budget has been exhausted (`elapsed() >= limitMs`).
* Callers should invoke `handleExhausted(...)` afterward and return
* from the handler.
*/
isExhausted(): boolean;
}
/**
* Fail the run (or retry, on early attempts) when the replay budget is
* exhausted. The handling depends on whether the underlying World
* supports `process.exit(1)` as a queue redelivery signal (see
* `World.processExitTriggersQueueRedelivery`):
*
* - **Managed-platform Worlds** (`world-vercel`): on attempts <=
* `REPLAY_TIMEOUT_MAX_RETRIES` exit the process so the platform fails
* the invocation and the queue redelivers; on the next attempt write
* `run_failed` with `RUN_ERROR_CODES.REPLAY_TIMEOUT` and exit.
*
* - **In-process Worlds** (`world-local`, dev servers): calling
* `process.exit()` would terminate the host (e.g. `pnpm dev`), so
* instead log a warning, write `run_failed` best-effort, and return.
* The framework completes the request normally.
*/
export declare function handleReplayBudgetExhausted(args: {
runId: string;
workflowName: string;
requestId: string | undefined;
attempt: number;
limitMs: number;
}): Promise<void>;
//# sourceMappingURL=replay-budget.d.ts.map