pipeai 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -26,36 +26,6 @@ declare class ToolProvider<TContext, TInput = unknown, TOutput = unknown> implem
26
26
  declare function defineTool<TContext>(): <TInput, TOutput>(config: ToolProviderConfig<TContext, TInput, TOutput>) => ToolProvider<TContext, TInput, TOutput>;
27
27
  declare function isToolProvider<TContext>(obj: unknown): obj is IToolProvider<TContext>;
28
28
 
29
- /**
30
- * Returns the active `UIMessageStreamWriter` if the current async context is
31
- * running inside a streaming workflow, or `undefined` otherwise.
32
- *
33
- * Use from inside a custom `IToolProvider`'s returned `Tool.execute` callback
34
- * to forward incremental output to the workflow's UI message stream:
35
- *
36
- * ```ts
37
- * import { getActiveWriter, type IToolProvider, TOOL_PROVIDER_BRAND } from "pipeai";
38
- *
39
- * const myProvider: IToolProvider<MyCtx> = {
40
- * [TOOL_PROVIDER_BRAND]: true,
41
- * createTool(ctx) {
42
- * return tool({
43
- * execute: async (input) => {
44
- * const writer = getActiveWriter();
45
- * // ...stream incremental progress to writer if present...
46
- * return result;
47
- * },
48
- * });
49
- * },
50
- * };
51
- * ```
52
- *
53
- * **Important timing note:** call this from *inside* the `Tool.execute`
54
- * callback, not from inside `createTool` itself. `createTool` runs during
55
- * agent setup (before the workflow has set the writer); `Tool.execute` runs
56
- * during tool invocation (when the writer is live).
57
- */
58
- declare function getActiveWriter(): UIMessageStreamWriter | undefined;
59
29
  type MaybePromise<T> = T | Promise<T>;
60
30
  /**
61
31
  * A value that can be static or derived from context and input.
@@ -178,28 +148,7 @@ declare class Agent<TContext, TInput = void, TOutput = void> {
178
148
  private resolveTools;
179
149
  }
180
150
 
181
- declare class WorkflowBranchError extends Error {
182
- readonly branchType: "predicate" | "select";
183
- constructor(branchType: "predicate" | "select", message: string);
184
- }
185
- declare class WorkflowLoopError extends Error {
186
- readonly iterations: number;
187
- readonly maxIterations: number;
188
- constructor(iterations: number, maxIterations: number);
189
- }
190
- declare class NestedGateUnsupportedError extends Error {
191
- readonly gateId: string;
192
- readonly workflowId: string | undefined;
193
- readonly siblingErrors: readonly unknown[];
194
- readonly siblingSuspensions: readonly {
195
- index: number;
196
- gateId: string;
197
- }[];
198
- constructor(gateId: string, workflowId: string | undefined, siblingErrors?: readonly unknown[], siblingSuspensions?: readonly {
199
- index: number;
200
- gateId: string;
201
- }[]);
202
- }
151
+ type SkipSentinel = typeof Workflow.SKIP;
203
152
  /**
204
153
  * v2 gate snapshot. The `kind` discriminant differentiates it from
205
154
  * checkpoint snapshots. The legacy v1 form is still accepted by `loadState`.
@@ -211,6 +160,15 @@ interface GateSnapshot {
211
160
  readonly output: unknown;
212
161
  readonly gateId: string;
213
162
  readonly gatePayload: unknown;
163
+ /**
164
+ * Path of nested-workflow step indices from the ROOT workflow down to the
165
+ * workflow that owns the gate, outermost-first. Absent/empty for a top-level
166
+ * gate. Each `.step(workflow)` the suspension bubbles through prepends its own
167
+ * step index, so {@link SealedWorkflow.loadState} can descend back to the
168
+ * gate on resume. (`resumeFromIndex` is the gate's index within that
169
+ * innermost workflow; `gateId` is the innermost gate's id.)
170
+ */
171
+ readonly nestedPath?: readonly number[];
214
172
  }
215
173
  /**
216
174
  * v2 checkpoint snapshot. Carries a step-shape hash; resume verifies the
@@ -239,7 +197,7 @@ interface LegacyGateSnapshotV1 {
239
197
  }
240
198
  type WorkflowSnapshot = GateSnapshot | CheckpointSnapshot | LegacyGateSnapshotV1;
241
199
  interface WorkflowWarning {
242
- readonly source: "step" | "finally" | "catch" | "onCheckpoint" | "onStepStart" | "onStepFinish" | "onStepError" | "foreach-sibling";
200
+ readonly source: "step" | "gate" | "finally" | "catch" | "onCheckpoint" | "onStepStart" | "onStepFinish" | "onStepError" | "onItemStart" | "onItemFinish" | "onItemError" | "foreach-sibling";
243
201
  readonly stepId: string;
244
202
  readonly error: unknown;
245
203
  }
@@ -255,6 +213,9 @@ type WorkflowStepType = "step" | "nested" | "gate" | "catch" | "finally" | "bran
255
213
  * - step-like / gate (cond-true → suspends): onStepStart always; onStepFinish
256
214
  * when body returns (suspended: true for gate, false otherwise); onStepError
257
215
  * on body throw.
216
+ * - step-like (`when` → false → skip): onStepStart, onStepFinish({ suspended:
217
+ * false }) with the passthrough/`otherwise` value as `output`. A skipped
218
+ * step's body never runs, but it is still bracketed by start/finish.
258
219
  * - gate (cond false → skip): onStepStart, onStepFinish({ suspended: false }).
259
220
  * - catch: onStepStart only when pendingError set; onStepFinish when catchFn
260
221
  * returns; onStepError when catchFn throws.
@@ -267,17 +228,17 @@ type WorkflowStepType = "step" | "nested" | "gate" | "catch" | "finally" | "bran
267
228
  * Skip-checked nodes (`state.suspension || pendingError` already set on entry)
268
229
  * emit nothing — `.finally()` is the exception.
269
230
  */
270
- interface WorkflowObservability {
231
+ interface WorkflowObservability<TContext = unknown> {
271
232
  onStepStart?: (event: {
272
233
  stepId: string;
273
234
  type: WorkflowStepType;
274
- ctx: unknown;
235
+ ctx: TContext;
275
236
  input: unknown;
276
237
  }) => MaybePromise<void>;
277
238
  onStepFinish?: (event: {
278
239
  stepId: string;
279
240
  type: WorkflowStepType;
280
- ctx: unknown;
241
+ ctx: TContext;
281
242
  output: unknown;
282
243
  durationMs: number;
283
244
  suspended: boolean;
@@ -285,7 +246,7 @@ interface WorkflowObservability {
285
246
  onStepError?: (event: {
286
247
  stepId: string;
287
248
  type: WorkflowStepType;
288
- ctx: unknown;
249
+ ctx: TContext;
289
250
  error: unknown;
290
251
  durationMs: number;
291
252
  }) => MaybePromise<void>;
@@ -293,14 +254,14 @@ interface WorkflowObservability {
293
254
  stepId: string;
294
255
  type: "foreach" | "parallel";
295
256
  itemIndex: number | string;
296
- ctx: unknown;
257
+ ctx: TContext;
297
258
  input: unknown;
298
259
  }) => MaybePromise<void>;
299
260
  onItemFinish?: (event: {
300
261
  stepId: string;
301
262
  type: "foreach" | "parallel";
302
263
  itemIndex: number | string;
303
- ctx: unknown;
264
+ ctx: TContext;
304
265
  output: unknown;
305
266
  durationMs: number;
306
267
  }) => MaybePromise<void>;
@@ -308,7 +269,7 @@ interface WorkflowObservability {
308
269
  stepId: string;
309
270
  type: "foreach" | "parallel";
310
271
  itemIndex: number | string;
311
- ctx: unknown;
272
+ ctx: TContext;
312
273
  error: unknown;
313
274
  durationMs: number;
314
275
  }) => MaybePromise<void>;
@@ -317,12 +278,15 @@ interface RunOptions {
317
278
  /**
318
279
  * Step-level checkpoint sink. Called after each successful step body when
319
280
  * `checkpointEvery` cadence or `checkpointWhen` predicate fires. Receives a
320
- * v2 `CheckpointSnapshot` and an `AbortSignal` that aborts on
321
- * `checkpointTimeout` expiration. Throwing here propagates to the caller
281
+ * v2 `CheckpointSnapshot` and the run's `abortSignal` (or `undefined` when
282
+ * the run wasn't given one), so a cancelled run can tear down an in-flight
283
+ * write if the callback honors it. Throwing here propagates to the caller
322
284
  * as an error — workflow `.catch()` is bypassed for checkpoint failures.
285
+ * There is no framework-imposed timeout; bound the write yourself by racing
286
+ * the passed `signal` against your own timer if you need one.
323
287
  */
324
288
  readonly onCheckpoint?: (snapshot: CheckpointSnapshot, opts: {
325
- signal: AbortSignal;
289
+ signal: AbortSignal | undefined;
326
290
  }) => MaybePromise<void>;
327
291
  /**
328
292
  * Fire `onCheckpoint` every N executable steps. Mutually exclusive with
@@ -349,42 +313,20 @@ interface RunOptions {
349
313
  /**
350
314
  * Cooperative cancellation signal. Checked at every step boundary inside
351
315
  * `execute()` and forwarded to agent calls in `executeAgent`, foreach
352
- * workers, and nested workflows. When the signal aborts, the workflow
353
- * tears down to `signal.reason` via the same pending-error path as any
354
- * other step failure, so `.catch()` handlers still get a chance to
355
- * observe (or recover from) the abort. `.finally()` bodies still run
356
- * on the abort path. Unlike `freezeSnapshots`, this option DOES
357
- * propagate into nested workflows, foreach items, and repeat loops
358
- * cancellation should be transitive.
316
+ * workers, parallel branches, nested workflows, and the `onCheckpoint`
317
+ * callback's `signal` (so a cancelled run can tear down an in-flight
318
+ * checkpoint write, if the callback honors it). When the signal aborts,
319
+ * the workflow tears down to `signal.reason` via the same pending-error path
320
+ * as any other step failure, so `.catch()` handlers still get a chance to
321
+ * observe the abort (e.g. for logging/cleanup) but an abort is sticky and
322
+ * non-recoverable: even a terminal `.catch()` that returns a value cannot
323
+ * make the run complete; it still rejects with `signal.reason`. `.finally()`
324
+ * bodies still run on the abort path. Unlike `freezeSnapshots`, this option DOES
325
+ * propagate into nested workflows, foreach items, parallel branches, and
326
+ * repeat loops — cancellation should be transitive.
359
327
  */
360
328
  readonly abortSignal?: AbortSignal;
361
- /**
362
- * Maximum ms `onCheckpoint` is allowed to run before its AbortSignal fires.
363
- * On timeout, a `CheckpointTimeoutError` is raised on the run (catch is
364
- * bypassed; original error reaches the caller). Default: no timeout.
365
- */
366
- readonly checkpointTimeout?: number;
367
- }
368
- /**
369
- * Synthetic step id reported when `onCheckpoint` itself throws. Reserved
370
- * via the construction-time `(type, id)` walk — user step ids may not
371
- * contain the `::pipeai::` namespace.
372
- */
373
- declare const CHECKPOINT_STEP_ID: "::pipeai::onCheckpoint";
374
- /**
375
- * Thrown internally when `onCheckpoint` exceeds `RunOptions.checkpointTimeout`.
376
- * Surfaces to the caller as the rejection error.
377
- */
378
- declare class CheckpointTimeoutError extends Error {
379
- readonly timeoutMs: number;
380
- constructor(timeoutMs: number);
381
329
  }
382
- /**
383
- * Convert a legacy v1 gate snapshot to a v2 gate snapshot. Long-lived
384
- * storage (Redis-without-TTL, S3, Postgres) should re-serialize via this
385
- * helper before v0.8.0+ drops v1 acceptance.
386
- */
387
- declare function migrateSnapshot(legacy: LegacyGateSnapshotV1): GateSnapshot;
388
330
  /**
389
331
  * Discriminated union describing one agent invocation's result.
390
332
  *
@@ -438,20 +380,70 @@ interface AgentStepHooks<TContext, TOutput, TNextOutput> {
438
380
  * Has no generate-mode analog because in generate mode there is no stream
439
381
  * to merge. If both `handleStream` and `mapResult`/`onResult` are
440
382
  * configured, `handleStream` runs first.
383
+ *
384
+ * `itemIndex` identifies the execution when this hook runs inside a
385
+ * multi-execution combinator: the numeric index for `foreach` and tuple
386
+ * `parallel`, the key for record `parallel`, and the matched key / case
387
+ * index for `branch`. It is `undefined` for a plain single `.step(agent)`.
441
388
  */
442
389
  handleStream?: (params: {
443
390
  result: StreamTextResult<ToolSet, OutputType<TNextOutput>>;
444
391
  writer: UIMessageStreamWriter;
445
392
  ctx: Readonly<TContext>;
446
393
  input: TOutput;
394
+ itemIndex?: number | string;
447
395
  }) => MaybePromise<void>;
448
396
  }
449
- type StepOptions<TContext, TOutput, TNextOutput> = AgentStepHooks<TContext, TOutput, TNextOutput> & {
397
+ /**
398
+ * Predicate gating whether a step runs. Receives the step's input. When it
399
+ * returns false the step is skipped — its body (agent / fn / sub-workflow) is
400
+ * never invoked.
401
+ */
402
+ type StepWhen<TContext, TOutput> = (params: {
403
+ ctx: Readonly<TContext>;
404
+ input: TOutput;
405
+ }) => MaybePromise<boolean>;
406
+ /**
407
+ * Produces the step's output when `when` returns false. With it, a skipped
408
+ * step's output is `otherwise(...)` (typed `TNextOutput`, so the step's output
409
+ * type stays `TNextOutput`). Without it, a skipped step passes its input
410
+ * through unchanged (output type widens to `TOutput | TNextOutput`).
411
+ */
412
+ type StepOtherwise<TContext, TOutput, TNextOutput> = (params: {
413
+ ctx: Readonly<TContext>;
414
+ input: TOutput;
415
+ }) => MaybePromise<TNextOutput>;
416
+ /** Conditional-skip options shared by all `step` forms. */
417
+ interface ConditionalStepOptions<TContext, TOutput, TNextOutput> {
418
+ /** Run the step only when this returns true. Omit to always run. */
419
+ when?: StepWhen<TContext, TOutput>;
420
+ /**
421
+ * Skip value when `when` is false. Omit for passthrough (input unchanged).
422
+ * Has no effect without `when` — a lone `otherwise` is never invoked.
423
+ */
424
+ otherwise?: StepOtherwise<TContext, TOutput, TNextOutput>;
425
+ }
426
+ type StepOptions<TContext, TOutput, TNextOutput> = AgentStepHooks<TContext, TOutput, TNextOutput> & ConditionalStepOptions<TContext, TOutput, TNextOutput> & {
450
427
  /** Override the default step id (`agent.id`). Required when reusing the same
451
428
  * agent across multiple steps in one workflow — the construction-time
452
429
  * `(type, id)` walk rejects duplicates. */
453
430
  id?: string;
454
431
  };
432
+ /** Options for the inline `step(id, fn, options?)` form — conditional skip only. */
433
+ type InlineStepOptions<TContext, TOutput, TNextOutput> = ConditionalStepOptions<TContext, TOutput, TNextOutput>;
434
+ /** Options for the nested `step(workflow, options?)` form — conditional skip + id override. */
435
+ type NestedStepOptions<TContext, TOutput, TNextOutput> = ConditionalStepOptions<TContext, TOutput, TNextOutput> & {
436
+ id?: string;
437
+ };
438
+ /**
439
+ * A step that supplies `when` without `otherwise` may skip to passthrough, so
440
+ * its output widens to `TOutput | TNextOutput`. Supplying `otherwise` (which
441
+ * returns `TNextOutput`) — or omitting `when` — keeps the output `TNextOutput`.
442
+ */
443
+ type SkipPassthrough<TContext, TOutput, TNextOutput> = ConditionalStepOptions<TContext, TOutput, TNextOutput> & {
444
+ when: StepWhen<TContext, TOutput>;
445
+ otherwise?: undefined;
446
+ };
455
447
  interface BranchCase<TContext, TOutput, TNextOutput> extends AgentStepHooks<TContext, TOutput, TNextOutput> {
456
448
  when?: (params: {
457
449
  ctx: Readonly<TContext>;
@@ -541,6 +533,14 @@ type LoopPredicate<TContext, TOutput> = (params: {
541
533
  ctx: Readonly<TContext>;
542
534
  iterations: number;
543
535
  }) => MaybePromise<boolean>;
536
+ /**
537
+ * Loop control for `repeat`. Exactly one of `until` / `while` — never both.
538
+ *
539
+ * Both forms are **do-while**: the body always runs at least once, then the
540
+ * predicate is checked. This is intentional — the predicate receives the
541
+ * body's `output`, which doesn't exist until the body has run — but it means
542
+ * `while: () => false` still executes the body once (it is not a pre-check).
543
+ */
544
544
  type RepeatOptions<TContext, TOutput> = {
545
545
  until: LoopPredicate<TContext, TOutput>;
546
546
  while?: never;
@@ -551,6 +551,20 @@ type RepeatOptions<TContext, TOutput> = {
551
551
  maxIterations?: number;
552
552
  };
553
553
  type ElementOf<T> = T extends readonly (infer E)[] ? E : never;
554
+ /**
555
+ * Brand that makes a *gated* workflow unassignable where gates are forbidden —
556
+ * `foreach` / `parallel` / `repeat` targets. A nested gate can't suspend one
557
+ * branch of a concurrent fan-out or one iteration of a loop, so it's rejected
558
+ * at build time. Since `.step(workflow)` folds child gates into `TGates`, this
559
+ * catches gates at ANY nesting depth, not just direct ones.
560
+ */
561
+ type GatesForbidden = {
562
+ readonly __agent_workflow_error__: "a workflow with gate(s) cannot be a foreach / parallel / repeat target";
563
+ };
564
+ /** `unknown` when `TG` has no gate keys (no-op intersection), else the {@link GatesForbidden} brand. */
565
+ type NoGates<TG extends Record<string, unknown>> = [keyof TG] extends [never] ? unknown : GatesForbidden;
566
+ /** A `parallel` branch with its gates checked: gated workflows resolve to the {@link GatesForbidden} brand. */
567
+ type GatelessBranch<T> = T extends SealedWorkflow<any, any, any, infer G> ? ([keyof G] extends [never] ? T : GatesForbidden) : T;
554
568
  /** A target for a `parallel()` branch — agent or sealed workflow. */
555
569
  type ParallelTarget<TContext, TInput> = Agent<TContext, TInput, any> | SealedWorkflow<TContext, TInput, any>;
556
570
  /** Extract the output type of a single parallel branch target. */
@@ -563,25 +577,40 @@ type ParallelOutputRecord<T extends Record<string, unknown>> = {
563
577
  type ParallelOutputTuple<T extends ReadonlyArray<unknown>> = {
564
578
  [K in keyof T]: BranchOutput<T[K]>;
565
579
  };
566
- interface ParallelOptions<TContext> {
580
+ /**
581
+ * Record output when `onError` is supplied: any branch may be `SKIP`ped,
582
+ * leaving its slot `undefined`, so every value is widened to `| undefined`.
583
+ */
584
+ type ParallelOutputRecordPartial<T extends Record<string, unknown>> = {
585
+ [K in keyof T]: BranchOutput<T[K]> | undefined;
586
+ };
587
+ /** Tuple counterpart of `ParallelOutputRecordPartial` — each slot `| undefined`. */
588
+ type ParallelOutputTuplePartial<T extends ReadonlyArray<unknown>> = {
589
+ [K in keyof T]: BranchOutput<T[K]> | undefined;
590
+ };
591
+ interface ParallelOptions<TContext, TOutput = unknown> {
567
592
  /** Override the default step id. Default: `parallel:record` or `parallel:tuple`. */
568
593
  id?: string;
569
594
  /**
570
- * Max branches in flight at any moment. Default: `min(branches.length, 5)`.
571
- * Pass `Infinity` (or `branches.length`) for full fan-out on >5-branch calls
572
- * the default caps at 5 to protect against rate limits and emits a
573
- * one-time warn when the cap kicks in.
595
+ * Max branches in flight at any moment. **Default: unbounded** (`Infinity`
596
+ * all branches run concurrently, clamped only by branch count). Pass an
597
+ * integer to throttle against provider rate limits.
574
598
  */
575
599
  concurrency?: number;
576
600
  /**
577
601
  * Per-branch error handler. On the no-suspension path, called once per
578
602
  * rejected branch in index order after all settle. Return a value to
579
- * substitute, return `Workflow.SKIP` to leave the slot undefined (record
580
- * form only tuple SKIP would shift indices), or rethrow to abort the
581
- * parallel.
603
+ * substitute, return `Workflow.SKIP` to leave the slot `undefined`, or
604
+ * rethrow to abort the parallel. A throw (or rethrow) aborts immediately:
605
+ * rejected branches at indices AFTER the throwing one are neither recovered
606
+ * nor surfaced as warnings. SKIP works in both the record and tuple
607
+ * forms (the slot stays `undefined` in place — it does not shift indices);
608
+ * supplying `onError` widens the output values to `BranchOutput | undefined`
609
+ * to reflect that a slot may be skipped.
582
610
  *
583
611
  * **Bypassed entirely on the suspension path** (any branch hit a nested
584
- * gate). See README's "Suspension under `parallel()`" section.
612
+ * gate) and on the cancellation path (the run was aborted). See README's
613
+ * "Suspension under `parallel()`" section.
585
614
  */
586
615
  onError?: (params: {
587
616
  error: unknown;
@@ -590,11 +619,61 @@ interface ParallelOptions<TContext> {
590
619
  /** Branch index in the tuple form; `undefined` in the record form. */
591
620
  index?: number;
592
621
  ctx: Readonly<TContext>;
593
- }) => unknown | typeof Workflow.SKIP | Promise<unknown | typeof Workflow.SKIP>;
622
+ }) => unknown | SkipSentinel | Promise<unknown | SkipSentinel>;
623
+ /**
624
+ * **Stream-mode + agent-branch only.** When the workflow is run via
625
+ * `.stream(...)`, each agent branch runs in stream mode and this hook decides
626
+ * how its stream surfaces to the writer (`itemIndex` = the record key or the
627
+ * tuple index). Without it, agent branches run in generate mode (no
628
+ * auto-merge). Not invoked for `SealedWorkflow` branches (which stream
629
+ * transitively via their own steps) nor in generate mode.
630
+ */
631
+ handleStream?: (params: {
632
+ result: StreamTextResult<ToolSet, any>;
633
+ writer: UIMessageStreamWriter;
634
+ ctx: Readonly<TContext>;
635
+ input: TOutput;
636
+ itemIndex: number | string;
637
+ }) => MaybePromise<void>;
594
638
  }
595
639
  interface SchemaWithParse<T = unknown> {
596
640
  parse(data: unknown): T;
597
641
  }
642
+
643
+ declare class WorkflowBranchError extends Error {
644
+ readonly branchType: "predicate" | "select";
645
+ constructor(branchType: "predicate" | "select", message: string);
646
+ }
647
+ declare class WorkflowLoopError extends Error {
648
+ readonly iterations: number;
649
+ readonly maxIterations: number;
650
+ constructor(iterations: number, maxIterations: number);
651
+ }
652
+ /**
653
+ * Synthetic step id reported when `onCheckpoint` itself throws. Reserved
654
+ * via the construction-time `(type, id)` walk — user step ids may not
655
+ * contain the `::pipeai::` namespace.
656
+ */
657
+ declare const CHECKPOINT_STEP_ID: "::pipeai::onCheckpoint";
658
+ /**
659
+ * Synthetic step id carried by the pending-error a cancellation promotes
660
+ * (surfaced to `.catch()` / observability). Lives in the reserved
661
+ * `::pipeai::` namespace so it can't be confused with a user step literally
662
+ * named "abort".
663
+ */
664
+ declare const ABORT_STEP_ID: "::pipeai::abort";
665
+ /**
666
+ * Synthetic step id used when a gate-resume's response validation / merge
667
+ * throws before the pipeline re-enters `execute()`. Reserved-namespaced for
668
+ * the same reason as {@link ABORT_STEP_ID}.
669
+ */
670
+ declare const GATE_RESUME_STEP_ID: "::pipeai::gate:resume";
671
+ /**
672
+ * Convert a legacy v1 gate snapshot to a v2 gate snapshot. Long-lived
673
+ * storage (Redis-without-TTL, S3, Postgres) should re-serialize via this
674
+ * helper before v0.8.0+ drops v1 acceptance.
675
+ */
676
+ declare function migrateSnapshot(legacy: LegacyGateSnapshotV1): GateSnapshot;
598
677
  type StepCategory = "step" | "nested" | "branch" | "foreach" | "repeat" | "parallel";
599
678
  type StepNode = {
600
679
  readonly type: "step";
@@ -606,12 +685,7 @@ type StepNode = {
606
685
  } | {
607
686
  readonly type: "catch";
608
687
  readonly id: string;
609
- readonly catchFn: (params: {
610
- error: unknown;
611
- ctx: unknown;
612
- lastOutput: unknown;
613
- stepId: string;
614
- }) => MaybePromise<unknown>;
688
+ readonly execute: (state: RuntimeState) => MaybePromise<void>;
615
689
  } | {
616
690
  readonly type: "finally";
617
691
  readonly id: string;
@@ -619,9 +693,8 @@ type StepNode = {
619
693
  } | {
620
694
  readonly type: "gate";
621
695
  readonly id: string;
622
- readonly payload: (state: RuntimeState) => MaybePromise<unknown>;
696
+ readonly execute: (state: RuntimeState) => MaybePromise<void>;
623
697
  readonly schema?: SchemaWithParse;
624
- readonly condition?: (state: RuntimeState) => MaybePromise<boolean>;
625
698
  readonly merge?: (params: {
626
699
  priorOutput: unknown;
627
700
  response: unknown;
@@ -635,13 +708,39 @@ interface RuntimeState {
635
708
  suspension?: GateSnapshot;
636
709
  warnings?: WorkflowWarning[];
637
710
  checkpointFailed?: boolean;
711
+ pendingError?: PendingError;
638
712
  runOptions?: RunOptions;
639
713
  abortSignal?: AbortSignal;
714
+ stepIndex?: number;
715
+ resumeDescent?: ResumeDescent;
640
716
  }
717
+ /**
718
+ * Drives resume re-entry for a gate that suspended inside nested workflows.
719
+ * `remaining` is the list of child start-indices to descend through, one per
720
+ * nesting level, ending with the innermost gate's `resumeFromIndex + 1`. When a
721
+ * `NestedWorkflowStep` consumes the LAST entry it first seeds `state.output`
722
+ * with `seedOutput` (the merged gate response) before running the innermost
723
+ * child from that index.
724
+ */
725
+ type ResumeDescent = {
726
+ readonly remaining: readonly number[];
727
+ readonly seedOutput: unknown;
728
+ };
641
729
  type PendingError = {
642
730
  error: unknown;
643
731
  stepId: string;
644
- source: "step" | "finally" | "catch" | "onCheckpoint";
732
+ source: "step" | "gate" | "finally" | "catch" | "onCheckpoint";
733
+ };
734
+ /**
735
+ * Seed for a run: the initial pipeline `output` plus an optional pre-execute
736
+ * `initialError`. The resume entry points compute these differently (gate
737
+ * schema-parse + merge vs. plain snapshot output), but every entry point then
738
+ * funnels through the same `runGenerate` / `runStream` machinery.
739
+ */
740
+ type StateSeed = {
741
+ output: unknown;
742
+ initialError: PendingError | null;
743
+ resumeDescent?: ResumeDescent;
645
744
  };
646
745
  declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates extends Record<string, unknown> = {}> {
647
746
  readonly id?: string;
@@ -649,6 +748,7 @@ declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates ext
649
748
  protected readonly observability?: WorkflowObservability;
650
749
  private duplicateCheckPassed;
651
750
  private _cachedExecutableStepCount?;
751
+ private _cachedCheckpointableStepCount?;
652
752
  private _cachedStepShapeHash?;
653
753
  protected constructor(steps: ReadonlyArray<StepNode>, id?: string, observability?: WorkflowObservability);
654
754
  /**
@@ -665,6 +765,16 @@ declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates ext
665
765
  * `type: "step"` internally and count as executable.
666
766
  */
667
767
  protected get cachedExecutableStepCount(): number;
768
+ /**
769
+ * Count of *checkpointable* nodes — `type === "step"` only (this includes
770
+ * `branch`/`foreach`/`repeat`/`parallel`/`nested`, all internally `step`).
771
+ * Drives the checkpoint auto-cadence denominator. Distinct from
772
+ * {@link cachedExecutableStepCount}, which also counts `gate` nodes: gates
773
+ * suspend/skip and never reach the checkpoint block, so the runtime
774
+ * `executableStepsSeen` counter never advances on them. Counting gates in
775
+ * the denominator would dilute the "~4 checkpoints across the run" target.
776
+ */
777
+ protected get cachedCheckpointableStepCount(): number;
668
778
  /** @internal — used by `computeStepShapeHash` to descend nested workflows. */
669
779
  getStepsForShapeHash(): ReadonlyArray<StepNode>;
670
780
  protected get cachedStepShapeHash(): string;
@@ -692,13 +802,44 @@ declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates ext
692
802
  * stays allocation-free.
693
803
  */
694
804
  protected fireHook<K extends keyof WorkflowObservability, E extends Parameters<NonNullable<WorkflowObservability[K]>>[0]>(state: RuntimeState, name: K, event: E): MaybePromise<unknown>;
695
- private fireHookSlow;
805
+ protected hasItemHooks(): boolean;
806
+ /**
807
+ * Fire `onStepError` for a step-body failure and honor the documented
808
+ * cause-attachment contract uniformly across every firing path (step, gate,
809
+ * catch, finally, checkpoint). When the hook itself throws, its error is
810
+ * attached as `cause` on the ORIGINAL error so the original still reaches the
811
+ * caller with the failure trail attached. If the original error is frozen /
812
+ * non-extensible (cause assignment throws) or is not an object, the hook
813
+ * error is recorded as a warning instead — so an `onStepError` throw is never
814
+ * silently lost. (The suspension-wins tail fires `onStepError` separately, on
815
+ * its own demotion path.)
816
+ */
817
+ protected fireStepErrorAndAttachCause(state: RuntimeState, event: {
818
+ stepId: string;
819
+ type: WorkflowStepType;
820
+ ctx: unknown;
821
+ error: unknown;
822
+ durationMs: number;
823
+ }): Promise<void>;
696
824
  generate(ctx: TContext, ...args: TInput extends void ? [input?: TInput, opts?: RunOptions] : [input: TInput, opts?: RunOptions]): Promise<WorkflowResult<TOutput>>;
697
825
  stream<UI_MESSAGE extends UIMessage = UIMessage>(ctx: TContext, ...args: TInput extends void ? [input?: TInput, options?: WorkflowStreamOptions<UI_MESSAGE>, opts?: RunOptions] : [input: TInput, options?: WorkflowStreamOptions<UI_MESSAGE>, opts?: RunOptions]): WorkflowStreamResult<TOutput>;
698
826
  protected buildResult(state: RuntimeState): WorkflowResult<TOutput>;
827
+ protected runGenerate(ctx: unknown, startIndex: number, opts: RunOptions | undefined, seed: () => MaybePromise<StateSeed>): Promise<WorkflowResult<TOutput>>;
828
+ protected runStream<UI_MESSAGE extends UIMessage = UIMessage>(ctx: unknown, startIndex: number, opts: RunOptions | undefined, options: WorkflowStreamOptions<UI_MESSAGE> | undefined, seed: () => MaybePromise<StateSeed>): WorkflowStreamResult<TOutput>;
699
829
  protected execute(state: RuntimeState, startIndex?: number, opts?: RunOptions, initialError?: PendingError | null): Promise<void>;
700
- protected executeNestedWorkflow(state: RuntimeState, workflow: SealedWorkflow<TContext, unknown, unknown, any>): Promise<void>;
701
- protected executeAgent<TAgentInput, TNextOutput>(state: RuntimeState, agent: Agent<TContext, any, TNextOutput>, ctx: TContext, options?: AgentStepHooks<TContext, any, TNextOutput>): Promise<void>;
830
+ /**
831
+ * Run THIS sealed workflow as a nested step on the caller's run `state`.
832
+ * Public (internal; not re-exported from index) so `Step` subclasses —
833
+ * `nested` / `repeat` / `foreach` / `parallel` with `SealedWorkflow` targets
834
+ * — can run a sub-workflow without reaching the protected `execute`.
835
+ *
836
+ * Contract: RunOptions is run-scoped, so the child never inherits the
837
+ * parent's (`state.warnings` IS propagated — telemetry > config). A gate
838
+ * inside the child leaves `state.suspension` set so it propagates up (only a
839
+ * `.step(workflow)` ever does this — concurrent/looped combinators forbid
840
+ * gated targets at build time).
841
+ */
842
+ executeAsNested(state: RuntimeState, startIndex?: number): Promise<void>;
702
843
  loadState<K extends string & keyof TGates>(gateId: K, snapshot: WorkflowSnapshot): ResumedWorkflow<TContext, TGates[K], TOutput>;
703
844
  /**
704
845
  * Resume from a checkpoint snapshot. Validates the step-shape hash unless
@@ -719,7 +860,9 @@ declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates ext
719
860
  /**
720
861
  * Append a `.finally()` body to a sealed workflow, returning another sealed
721
862
  * workflow. Allows multi-finally chains (`.finally().finally()`). A throwing
722
- * `.finally` body does NOT abort subsequent ones they all run.
863
+ * `.finally` body bubbles straight out of the run: it is non-recoverable, does
864
+ * NOT aggregate with a prior error, and subsequent `.finally()` bodies do not
865
+ * run. (See {@link FinallyStep} for the full contract.)
723
866
  */
724
867
  finally(id: string, fn: (params: {
725
868
  ctx: Readonly<TContext>;
@@ -736,15 +879,31 @@ interface ResumedWorkflowConfig {
736
879
  readonly priorOutput?: unknown;
737
880
  readonly snapshot?: WorkflowSnapshot;
738
881
  readonly observability?: WorkflowObservability;
882
+ /**
883
+ * Set only for a NESTED gate resume: the descent (child start-indices per
884
+ * level, innermost-last) the merged gate response rides down to the suspended
885
+ * child. When present, `startIndex` is the ROOT's nested-step index and the
886
+ * seed sets `state.resumeDescent` instead of the root `output`.
887
+ */
888
+ readonly nestedRemaining?: readonly number[];
739
889
  }
740
890
  declare class ResumedWorkflow<TContext, TResponse = unknown, TOutput = void> extends SealedWorkflow<TContext, TResponse, TOutput> {
741
891
  private readonly startIndex;
742
892
  private readonly schema?;
743
893
  private readonly mergeFn?;
744
894
  private readonly priorOutput;
895
+ private readonly nestedRemaining?;
745
896
  /** @internal */
746
897
  constructor(steps: ReadonlyArray<StepNode>, startIndex: number, config: ResumedWorkflowConfig);
747
898
  private validateResponse;
899
+ /**
900
+ * Seed the run by validating the gate response and merging it with the
901
+ * suspended output. Runs schema.parse + mergeFn inside a try so a failure
902
+ * becomes a pre-execute `initialError` (routed through `.catch()`) rather
903
+ * than escaping the run synchronously. On error the output falls back to the
904
+ * prior (pre-gate) output.
905
+ */
906
+ private seedFromResponse;
748
907
  generate(ctx: TContext, ...args: TResponse extends void ? [response?: TResponse, opts?: RunOptions] : [response: TResponse, opts?: RunOptions]): Promise<WorkflowResult<TOutput>>;
749
908
  stream<UI_MESSAGE extends UIMessage = UIMessage>(ctx: TContext, ...args: TResponse extends void ? [response?: TResponse, options?: WorkflowStreamOptions<UI_MESSAGE>, opts?: RunOptions] : [response: TResponse, options?: WorkflowStreamOptions<UI_MESSAGE>, opts?: RunOptions]): WorkflowStreamResult<TOutput>;
750
909
  }
@@ -766,17 +925,25 @@ declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends R
766
925
  private constructor();
767
926
  static create<TContext, TInput = void>(options?: {
768
927
  id?: string;
769
- observability?: WorkflowObservability;
928
+ observability?: WorkflowObservability<TContext>;
770
929
  }): Workflow<TContext, TInput, TInput>;
930
+ static from<TContext, TInput, TOutput>(agent: Agent<TContext, TInput, TOutput>, options: StepOptions<TContext, TInput, TOutput> & SkipPassthrough<TContext, TInput, TOutput>): Workflow<TContext, TInput, TInput | TOutput>;
771
931
  static from<TContext, TInput, TOutput>(agent: Agent<TContext, TInput, TOutput>, options?: StepOptions<TContext, TInput, TOutput>): Workflow<TContext, TInput, TOutput>;
772
932
  private appendStep;
933
+ step<TNextOutput>(agent: Agent<TContext, TOutput, TNextOutput>, options: StepOptions<TContext, TOutput, TNextOutput> & SkipPassthrough<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TOutput | TNextOutput, TGates>;
773
934
  step<TNextOutput>(agent: Agent<TContext, TOutput, TNextOutput>, options?: StepOptions<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
774
- step<TNextOutput>(workflow: SealedWorkflow<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
935
+ step<TNextOutput, TChildGates extends Record<string, unknown> = {}>(workflow: SealedWorkflow<TContext, TOutput, TNextOutput, TChildGates>, options: NestedStepOptions<TContext, TOutput, TNextOutput> & SkipPassthrough<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TOutput | TNextOutput, TGates & TChildGates>;
936
+ step<TNextOutput, TChildGates extends Record<string, unknown> = {}>(workflow: SealedWorkflow<TContext, TOutput, TNextOutput, TChildGates>, options?: NestedStepOptions<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates & TChildGates>;
937
+ step<TNextOutput>(id: string, fn: (params: {
938
+ ctx: Readonly<TContext>;
939
+ input: TOutput;
940
+ writer?: UIMessageStreamWriter;
941
+ }) => MaybePromise<TNextOutput>, options: InlineStepOptions<TContext, TOutput, TNextOutput> & SkipPassthrough<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TOutput | TNextOutput, TGates>;
775
942
  step<TNextOutput>(id: string, fn: (params: {
776
943
  ctx: Readonly<TContext>;
777
944
  input: TOutput;
778
945
  writer?: UIMessageStreamWriter;
779
- }) => MaybePromise<TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
946
+ }) => MaybePromise<TNextOutput>, options?: InlineStepOptions<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
780
947
  gate<TResponse = TOutput, Id extends string = string>(id: Id & (Id extends keyof TGates ? never : Id), options?: {
781
948
  payload?: (params: {
782
949
  ctx: Readonly<TContext>;
@@ -807,16 +974,23 @@ declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends R
807
974
  * @param options.id Override the default step id (`foreach:<agentId>` or
808
975
  * the workflow's id). Required when chaining multiple foreach over the same
809
976
  * target — the construction-time `(type, id)` walk rejects duplicates.
810
- * @param options.concurrency Max items in flight at any moment (default 1).
811
- * Backed by a semaphore: as soon as one item completes, the next launches —
977
+ * @param options.concurrency Max items in flight at any moment. **Default:
978
+ * unbounded** (`Infinity` every item runs concurrently, clamped only by
979
+ * item count). Pass an integer to throttle against provider rate limits.
980
+ * Backed by a worker pool: as soon as one item completes, the next launches —
812
981
  * no lockstep batching.
813
982
  * @param options.onError Per-iteration error handler. **Bypassed entirely on
814
- * the suspension path** (when any item hits a nested gate) see the
983
+ * the suspension path** (when any item hits a nested gate) **and on the
984
+ * cancellation path** (the run was aborted — pre-abort failures become
985
+ * `foreach-sibling` warnings and the abort reason rethrows) — see the
815
986
  * foreach concurrency hazards in the README. Otherwise: return a
816
987
  * `TNextOutput` value to substitute, return `Workflow.SKIP` to omit, throw
817
988
  * to abort. Invoked sequentially in index order after all items settle.
989
+ * A throw (or rethrow) from `onError` aborts the foreach immediately:
990
+ * failures at indices AFTER the throwing one are neither recovered nor
991
+ * surfaced as warnings.
818
992
  */
819
- foreach<TNextOutput>(target: Agent<TContext, ElementOf<TOutput>, TNextOutput> | SealedWorkflow<TContext, ElementOf<TOutput>, TNextOutput>, options?: {
993
+ foreach<TNextOutput, TG extends Record<string, unknown> = {}>(target: Agent<TContext, ElementOf<TOutput>, TNextOutput> | (SealedWorkflow<TContext, ElementOf<TOutput>, TNextOutput, TG> & NoGates<TG>), options?: {
820
994
  id?: string;
821
995
  concurrency?: number;
822
996
  onError?: (params: {
@@ -825,12 +999,44 @@ declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends R
825
999
  index: number;
826
1000
  ctx: Readonly<TContext>;
827
1001
  }) => MaybePromise<TNextOutput | typeof Workflow.SKIP>;
1002
+ /**
1003
+ * **Stream-mode + agent-target only.** When the workflow is run via
1004
+ * `.stream(...)`, each item's agent runs in stream mode and this hook
1005
+ * decides how its stream surfaces to the writer (`itemIndex` = the item
1006
+ * index). Without it, agent items run in generate mode (no auto-merge —
1007
+ * unlike a single `.step(agent)`, foreach never auto-merges N streams).
1008
+ * Not invoked for `SealedWorkflow` targets (which stream transitively via
1009
+ * their own steps) nor in generate mode.
1010
+ */
1011
+ handleStream?: (params: {
1012
+ result: StreamTextResult<ToolSet, OutputType<TNextOutput>>;
1013
+ writer: UIMessageStreamWriter;
1014
+ ctx: Readonly<TContext>;
1015
+ input: ElementOf<TOutput>;
1016
+ itemIndex: number;
1017
+ }) => MaybePromise<void>;
828
1018
  }): Workflow<TContext, TInput, TNextOutput[], TGates>;
1019
+ /** Record-form + `onError`. Values are `BranchOutput | undefined` (SKIP-able). */
1020
+ parallel<TBranches extends Record<string, ParallelTarget<TContext, TOutput>>>(branches: TBranches & {
1021
+ [K in keyof TBranches]: GatelessBranch<TBranches[K]>;
1022
+ }, options: ParallelOptions<TContext, TOutput> & {
1023
+ onError: NonNullable<ParallelOptions<TContext, TOutput>["onError"]>;
1024
+ }): Workflow<TContext, TInput, ParallelOutputRecordPartial<TBranches>, TGates>;
829
1025
  /** Record-form overload. Returns `{ [K]: BranchOutput<T[K]> }`. */
830
- parallel<TBranches extends Record<string, ParallelTarget<TContext, TOutput>>>(branches: TBranches, options?: ParallelOptions<TContext>): Workflow<TContext, TInput, ParallelOutputRecord<TBranches>, TGates>;
1026
+ parallel<TBranches extends Record<string, ParallelTarget<TContext, TOutput>>>(branches: TBranches & {
1027
+ [K in keyof TBranches]: GatelessBranch<TBranches[K]>;
1028
+ }, options?: ParallelOptions<TContext, TOutput>): Workflow<TContext, TInput, ParallelOutputRecord<TBranches>, TGates>;
1029
+ /** Tuple-form + `onError`. Each slot is `BranchOutput | undefined` (SKIP-able). */
1030
+ parallel<TBranches extends ReadonlyArray<ParallelTarget<TContext, TOutput>>>(branches: TBranches & {
1031
+ [K in keyof TBranches]: GatelessBranch<TBranches[K]>;
1032
+ }, options: ParallelOptions<TContext, TOutput> & {
1033
+ onError: NonNullable<ParallelOptions<TContext, TOutput>["onError"]>;
1034
+ }): Workflow<TContext, TInput, ParallelOutputTuplePartial<TBranches>, TGates>;
831
1035
  /** Tuple-form overload. Returns `[O1, O2, ...]`. Use `as const`. */
832
- parallel<TBranches extends ReadonlyArray<ParallelTarget<TContext, TOutput>>>(branches: TBranches, options?: ParallelOptions<TContext>): Workflow<TContext, TInput, ParallelOutputTuple<TBranches>, TGates>;
833
- repeat(target: Agent<TContext, TOutput, TOutput> | SealedWorkflow<TContext, TOutput, TOutput>, options: RepeatOptions<TContext, TOutput> & {
1036
+ parallel<TBranches extends ReadonlyArray<ParallelTarget<TContext, TOutput>>>(branches: TBranches & {
1037
+ [K in keyof TBranches]: GatelessBranch<TBranches[K]>;
1038
+ }, options?: ParallelOptions<TContext, TOutput>): Workflow<TContext, TInput, ParallelOutputTuple<TBranches>, TGates>;
1039
+ repeat<TG extends Record<string, unknown> = {}>(target: Agent<TContext, TOutput, TOutput> | (SealedWorkflow<TContext, TOutput, TOutput, TG> & NoGates<TG>), options: RepeatOptions<TContext, TOutput> & {
834
1040
  id?: string;
835
1041
  }): Workflow<TContext, TInput, TOutput, TGates>;
836
1042
  catch(id: string, fn: (params: {
@@ -843,4 +1049,4 @@ declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends R
843
1049
 
844
1050
  declare const SKIP: symbol;
845
1051
 
846
- export { Agent, type AgentConfig, type AgentResultParams, type AgentStepHooks, type AsToolMapOutput, type BranchCase, type BranchSelect, CHECKPOINT_STEP_ID, CheckpointResumedWorkflow, type CheckpointSnapshot, CheckpointTimeoutError, type GateSnapshot, type GenerateTextResult, type IToolProvider, type LegacyGateSnapshotV1, type MaybePromise, NestedGateUnsupportedError, type OutputType, type ParallelOptions, type ParallelOutputRecord, type ParallelOutputTuple, type ParallelTarget, type RepeatOptions, type Resolvable, ResumedWorkflow, type RunOptions, SKIP, SealedWorkflow, type StepOptions, type StreamTextResult, TOOL_PROVIDER_BRAND, type ToolExecuteOptions, ToolProvider, type ToolProviderConfig, Workflow, WorkflowBranchError, WorkflowLoopError, type WorkflowObservability, type WorkflowResult, type WorkflowSnapshot, type WorkflowStepType, type WorkflowStreamOptions, type WorkflowStreamResult, type WorkflowWarning, defineTool, getActiveWriter, isToolProvider, migrateSnapshot };
1052
+ export { ABORT_STEP_ID, Agent, type AgentConfig, type AgentResultParams, type AgentStepHooks, type AsToolMapOutput, type BranchCase, type BranchSelect, CHECKPOINT_STEP_ID, CheckpointResumedWorkflow, type CheckpointSnapshot, GATE_RESUME_STEP_ID, type GateSnapshot, type GenerateTextResult, type IToolProvider, type LegacyGateSnapshotV1, type MaybePromise, type OutputType, type ParallelOptions, type ParallelOutputRecord, type ParallelOutputTuple, type ParallelTarget, type RepeatOptions, type Resolvable, ResumedWorkflow, type RunOptions, SKIP, SealedWorkflow, type StepOptions, type StreamTextResult, TOOL_PROVIDER_BRAND, type ToolExecuteOptions, ToolProvider, type ToolProviderConfig, Workflow, WorkflowBranchError, WorkflowLoopError, type WorkflowObservability, type WorkflowResult, type WorkflowSnapshot, type WorkflowStepType, type WorkflowStreamOptions, type WorkflowStreamResult, type WorkflowWarning, defineTool, isToolProvider, migrateSnapshot };