pipeai 0.8.2 → 0.9.0

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.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Tool, ToolExecutionOptions, UIMessageStreamWriter, FlexibleSchema, streamText, generateText, Output, LanguageModel, ModelMessage, ToolChoice, ToolSet, StopCondition, OnStepFinishEvent, OnFinishEvent, GenerateTextResult as GenerateTextResult$1, StreamTextResult as StreamTextResult$1, UIMessage, UIMessageStreamOnFinishCallback, IdGenerator } from 'ai';
1
+ import { Tool, ToolExecutionOptions, UIMessageStreamWriter, FlexibleSchema, streamText, generateText, Output, LanguageModel, ModelMessage, ToolChoice, ToolSet, StopCondition, OnStepFinishEvent, OnFinishEvent, GenerateTextResult as GenerateTextResult$1, StreamTextResult as StreamTextResult$1, UIMessage, UIMessageStreamOnFinishCallback, IdGenerator, InferUIMessageChunk } from 'ai';
2
2
  import { ZodType } from 'zod';
3
3
 
4
4
  declare const TOOL_PROVIDER_BRAND: unique symbol;
@@ -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
329
  }
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
- }
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>;
@@ -487,8 +479,8 @@ type WorkflowResult<TOutput> = {
487
479
  readonly snapshot: GateSnapshot;
488
480
  readonly warnings: readonly WorkflowWarning[];
489
481
  };
490
- interface WorkflowStreamResult<TOutput> {
491
- stream: ReadableStream;
482
+ interface WorkflowStreamResult<TOutput, UI_MESSAGE extends UIMessage = UIMessage> {
483
+ stream: ReadableStream<InferUIMessageChunk<UI_MESSAGE>>;
492
484
  output: Promise<WorkflowResult<TOutput>>;
493
485
  }
494
486
  /**
@@ -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,60 @@ type RepeatOptions<TContext, TOutput> = {
551
551
  maxIterations?: number;
552
552
  };
553
553
  type ElementOf<T> = T extends readonly (infer E)[] ? E : never;
554
+ /**
555
+ * Options for `Workflow.foreach` — shared by the agent / sub-workflow target
556
+ * form and the per-item path-builder callback form.
557
+ */
558
+ interface ForeachOptions<TContext, TOutput, TNextOutput> {
559
+ /** Override the default step id (`foreach:<agentId>` or the body workflow's id). */
560
+ id?: string;
561
+ /**
562
+ * Max items in flight at any moment. **Default: unbounded** (`Infinity` — every
563
+ * item runs concurrently, clamped only by item count). Pass an integer to
564
+ * throttle against provider rate limits. Backed by a worker pool: as soon as
565
+ * one item completes, the next launches — no lockstep batching.
566
+ */
567
+ concurrency?: number;
568
+ /**
569
+ * Per-iteration error handler. Return a `TNextOutput` to substitute, return
570
+ * `Workflow.SKIP` to omit the item, or throw to abort. Invoked sequentially in
571
+ * index order after all items settle. **Bypassed on the abort path.**
572
+ */
573
+ onError?: (params: {
574
+ error: unknown;
575
+ item: ElementOf<TOutput>;
576
+ index: number;
577
+ ctx: Readonly<TContext>;
578
+ }) => MaybePromise<TNextOutput | SkipSentinel>;
579
+ /**
580
+ * **Stream-mode + agent-target only.** When the workflow is run via
581
+ * `.stream(...)`, each item's agent runs in stream mode and this hook decides
582
+ * how its stream surfaces to the writer (`itemIndex` = the item index). Not
583
+ * invoked for sub-workflow bodies (which stream transitively) nor in generate
584
+ * mode.
585
+ */
586
+ handleStream?: (params: {
587
+ result: StreamTextResult<ToolSet, OutputType<TNextOutput>>;
588
+ writer: UIMessageStreamWriter;
589
+ ctx: Readonly<TContext>;
590
+ input: ElementOf<TOutput>;
591
+ itemIndex: number;
592
+ }) => MaybePromise<void>;
593
+ }
594
+ /**
595
+ * Brand that makes a *gated* workflow unassignable where gates are forbidden —
596
+ * `foreach` / `parallel` / `repeat` targets. A nested gate can't suspend one
597
+ * branch of a concurrent fan-out or one iteration of a loop, so it's rejected
598
+ * at build time. Since `.step(workflow)` folds child gates into `TGates`, this
599
+ * catches gates at ANY nesting depth, not just direct ones.
600
+ */
601
+ type GatesForbidden = {
602
+ readonly __agent_workflow_error__: "a workflow with gate(s) cannot be a foreach / parallel / repeat target";
603
+ };
604
+ /** `unknown` when `TG` has no gate keys (no-op intersection), else the {@link GatesForbidden} brand. */
605
+ type NoGates<TG extends Record<string, unknown>> = [keyof TG] extends [never] ? unknown : GatesForbidden;
606
+ /** A `parallel` branch with its gates checked: gated workflows resolve to the {@link GatesForbidden} brand. */
607
+ type GatelessBranch<T> = T extends SealedWorkflow<any, any, any, infer G> ? ([keyof G] extends [never] ? T : GatesForbidden) : T;
554
608
  /** A target for a `parallel()` branch — agent or sealed workflow. */
555
609
  type ParallelTarget<TContext, TInput> = Agent<TContext, TInput, any> | SealedWorkflow<TContext, TInput, any>;
556
610
  /** Extract the output type of a single parallel branch target. */
@@ -563,25 +617,40 @@ type ParallelOutputRecord<T extends Record<string, unknown>> = {
563
617
  type ParallelOutputTuple<T extends ReadonlyArray<unknown>> = {
564
618
  [K in keyof T]: BranchOutput<T[K]>;
565
619
  };
566
- interface ParallelOptions<TContext> {
620
+ /**
621
+ * Record output when `onError` is supplied: any branch may be `SKIP`ped,
622
+ * leaving its slot `undefined`, so every value is widened to `| undefined`.
623
+ */
624
+ type ParallelOutputRecordPartial<T extends Record<string, unknown>> = {
625
+ [K in keyof T]: BranchOutput<T[K]> | undefined;
626
+ };
627
+ /** Tuple counterpart of `ParallelOutputRecordPartial` — each slot `| undefined`. */
628
+ type ParallelOutputTuplePartial<T extends ReadonlyArray<unknown>> = {
629
+ [K in keyof T]: BranchOutput<T[K]> | undefined;
630
+ };
631
+ interface ParallelOptions<TContext, TOutput = unknown> {
567
632
  /** Override the default step id. Default: `parallel:record` or `parallel:tuple`. */
568
633
  id?: string;
569
634
  /**
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.
635
+ * Max branches in flight at any moment. **Default: unbounded** (`Infinity`
636
+ * all branches run concurrently, clamped only by branch count). Pass an
637
+ * integer to throttle against provider rate limits.
574
638
  */
575
639
  concurrency?: number;
576
640
  /**
577
641
  * Per-branch error handler. On the no-suspension path, called once per
578
642
  * 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.
643
+ * substitute, return `Workflow.SKIP` to leave the slot `undefined`, or
644
+ * rethrow to abort the parallel. A throw (or rethrow) aborts immediately:
645
+ * rejected branches at indices AFTER the throwing one are neither recovered
646
+ * nor surfaced as warnings. SKIP works in both the record and tuple
647
+ * forms (the slot stays `undefined` in place — it does not shift indices);
648
+ * supplying `onError` widens the output values to `BranchOutput | undefined`
649
+ * to reflect that a slot may be skipped.
582
650
  *
583
651
  * **Bypassed entirely on the suspension path** (any branch hit a nested
584
- * gate). See README's "Suspension under `parallel()`" section.
652
+ * gate) and on the cancellation path (the run was aborted). See README's
653
+ * "Suspension under `parallel()`" section.
585
654
  */
586
655
  onError?: (params: {
587
656
  error: unknown;
@@ -590,43 +659,27 @@ interface ParallelOptions<TContext> {
590
659
  /** Branch index in the tuple form; `undefined` in the record form. */
591
660
  index?: number;
592
661
  ctx: Readonly<TContext>;
593
- }) => unknown | typeof Workflow.SKIP | Promise<unknown | typeof Workflow.SKIP>;
662
+ }) => unknown | SkipSentinel | Promise<unknown | SkipSentinel>;
663
+ /**
664
+ * **Stream-mode + agent-branch only.** When the workflow is run via
665
+ * `.stream(...)`, each agent branch runs in stream mode and this hook decides
666
+ * how its stream surfaces to the writer (`itemIndex` = the record key or the
667
+ * tuple index). Without it, agent branches run in generate mode (no
668
+ * auto-merge). Not invoked for `SealedWorkflow` branches (which stream
669
+ * transitively via their own steps) nor in generate mode.
670
+ */
671
+ handleStream?: (params: {
672
+ result: StreamTextResult<ToolSet, any>;
673
+ writer: UIMessageStreamWriter;
674
+ ctx: Readonly<TContext>;
675
+ input: TOutput;
676
+ itemIndex: number | string;
677
+ }) => MaybePromise<void>;
594
678
  }
595
679
  interface SchemaWithParse<T = unknown> {
596
680
  parse(data: unknown): T;
597
681
  }
598
- type StepCategory = "step" | "nested" | "branch" | "foreach" | "repeat" | "parallel";
599
- type StepNode = {
600
- readonly type: "step";
601
- readonly id: string;
602
- readonly execute: (state: RuntimeState) => MaybePromise<void>;
603
- readonly nestedWorkflow?: SealedWorkflow<any, any, any, any>;
604
- /** Disambiguates observability events. Default `"step"`. */
605
- readonly category?: StepCategory;
606
- } | {
607
- readonly type: "catch";
608
- readonly id: string;
609
- readonly catchFn: (params: {
610
- error: unknown;
611
- ctx: unknown;
612
- lastOutput: unknown;
613
- stepId: string;
614
- }) => MaybePromise<unknown>;
615
- } | {
616
- readonly type: "finally";
617
- readonly id: string;
618
- readonly execute: (state: RuntimeState) => MaybePromise<void>;
619
- } | {
620
- readonly type: "gate";
621
- readonly id: string;
622
- readonly payload: (state: RuntimeState) => MaybePromise<unknown>;
623
- readonly schema?: SchemaWithParse;
624
- readonly condition?: (state: RuntimeState) => MaybePromise<boolean>;
625
- readonly merge?: (params: {
626
- priorOutput: unknown;
627
- response: unknown;
628
- }) => MaybePromise<unknown>;
629
- };
682
+
630
683
  interface RuntimeState {
631
684
  ctx: unknown;
632
685
  output: unknown;
@@ -635,22 +688,158 @@ interface RuntimeState {
635
688
  suspension?: GateSnapshot;
636
689
  warnings?: WorkflowWarning[];
637
690
  checkpointFailed?: boolean;
691
+ pendingError?: PendingError;
638
692
  runOptions?: RunOptions;
639
693
  abortSignal?: AbortSignal;
694
+ stepIndex?: number;
695
+ resumeDescent?: ResumeDescent;
640
696
  }
697
+ /**
698
+ * Drives resume re-entry for a gate that suspended inside nested workflows.
699
+ * `remaining` is the list of child start-indices to descend through, one per
700
+ * nesting level, ending with the innermost gate's `resumeFromIndex + 1`. When a
701
+ * `NestedWorkflowStep` consumes the LAST entry it first seeds `state.output`
702
+ * with `seedOutput` (the merged gate response) before running the innermost
703
+ * child from that index.
704
+ */
705
+ type ResumeDescent = {
706
+ readonly remaining: readonly number[];
707
+ readonly seedOutput: unknown;
708
+ };
641
709
  type PendingError = {
642
710
  error: unknown;
643
711
  stepId: string;
644
- source: "step" | "finally" | "catch" | "onCheckpoint";
712
+ source: "step" | "gate" | "finally" | "catch" | "onCheckpoint";
713
+ };
714
+
715
+ /**
716
+ * Disambiguates observability events for `type: "step"` nodes. Keeps a single
717
+ * `type: "step"` node kind rather than splitting branch/foreach/repeat/
718
+ * parallel/nested into their own run-loop variants.
719
+ */
720
+ type StepCategory = "step" | "nested" | "branch" | "foreach" | "repeat" | "parallel";
721
+ /**
722
+ * Base class for a single workflow step node. The run loop in `workflow.ts`
723
+ * consumes `ReadonlyArray<Step>` directly — every combinator on `Workflow`
724
+ * constructs one of the subclasses in this directory.
725
+ *
726
+ * ## Execution model (the "fat step")
727
+ *
728
+ * The run loop does two things: ask {@link shouldSkip} whether the node runs
729
+ * at all (skipped nodes fire no observability hooks), then call
730
+ * {@link execute}. Everything else is the step's own business — a kind's
731
+ * `execute` runs its work (applying the body-level `when` / `otherwise`
732
+ * decision via {@link applyConditionalSkip}) and captures any thrown error
733
+ * onto `state.pendingError`, exactly the way it writes its result to
734
+ * `state.output`. Errors accumulate on the state; they do not escape.
735
+ *
736
+ * The base {@link execute} is a no-op so kinds with no body of their own need
737
+ * not override it. {@link errorSource} tags which precedence bucket a captured
738
+ * error lands in.
739
+ */
740
+ declare abstract class Step {
741
+ /** Run-loop dispatch discriminant. */
742
+ abstract readonly type: "step" | "gate" | "catch" | "finally";
743
+ /** Identifier, unique per `type`; surfaced in observability and snapshots. */
744
+ abstract readonly id: string;
745
+ /**
746
+ * Observability event subtype for `type: "step"` nodes (agent / transform =
747
+ * `"step"`; nested / branch / foreach / repeat / parallel override).
748
+ * `undefined` on gate / catch / finally nodes, whose `type` IS the event type.
749
+ */
750
+ readonly category?: StepCategory;
751
+ /**
752
+ * The sealed sub-workflow attached to this node, when it has one (`nested`,
753
+ * and workflow-target `foreach` / `repeat`). Consumed by the recursive
754
+ * `stepShapeHash` walk and the resume path-walk in `loadState`.
755
+ */
756
+ readonly nestedWorkflow?: SealedWorkflow<any, any, any, any>;
757
+ /**
758
+ * Precedence source tag a kind writes to `state.pendingError` when it
759
+ * captures a thrown body error. Defaults to `"step"`; kinds with a distinct
760
+ * error-precedence bucket (e.g. `finally`, `catch`, `gate`) override it.
761
+ */
762
+ protected readonly errorSource: PendingError["source"];
763
+ /**
764
+ * The step's body, invoked by the run loop only after {@link shouldSkip}
765
+ * returned `false`. Each kind overrides it to do its work and capture errors
766
+ * onto state. `state.output` is the input on entry and becomes the output on
767
+ * exit; `state.writer` is present in stream mode. The base implementation is
768
+ * a no-op so kinds that carry no body of their own need not override it.
769
+ */
770
+ execute(_state: RuntimeState): Promise<void>;
771
+ /**
772
+ * Run-policy gate, called by the run loop before {@link execute}: return
773
+ * `true` when this step should be skipped silently (no hooks, no output
774
+ * change). The default is the "normal" policy — skip while the flow is
775
+ * suspended or already in error. Overridden by kinds with inverted policies:
776
+ * `catch` runs only when there's an error, `finally` always runs.
777
+ */
778
+ shouldSkip(state: RuntimeState): boolean;
779
+ /**
780
+ * Apply `when` / `otherwise` conditional-skip options. Returns `true` when
781
+ * the body should be skipped — i.e. `when` returned false. On skip,
782
+ * `otherwise` (if present) produces the output; without it the input passes
783
+ * through unchanged. Distinct from {@link shouldSkip}: this is the body-level
784
+ * `when` / `otherwise` decision a kind applies after the policy gate passes.
785
+ */
786
+ protected applyConditionalSkip(state: RuntimeState, options: ConditionalStepOptions<unknown, unknown, unknown> | undefined): Promise<boolean>;
787
+ }
788
+
789
+ declare class WorkflowBranchError extends Error {
790
+ readonly branchType: "predicate" | "select";
791
+ constructor(branchType: "predicate" | "select", message: string);
792
+ }
793
+ declare class WorkflowLoopError extends Error {
794
+ readonly iterations: number;
795
+ readonly maxIterations: number;
796
+ constructor(iterations: number, maxIterations: number);
797
+ }
798
+ /**
799
+ * Synthetic step id reported when `onCheckpoint` itself throws. Reserved
800
+ * via the construction-time `(type, id)` walk — user step ids may not
801
+ * contain the `::pipeai::` namespace.
802
+ */
803
+ declare const CHECKPOINT_STEP_ID: "::pipeai::onCheckpoint";
804
+ /**
805
+ * Synthetic step id carried by the pending-error a cancellation promotes
806
+ * (surfaced to `.catch()` / observability). Lives in the reserved
807
+ * `::pipeai::` namespace so it can't be confused with a user step literally
808
+ * named "abort".
809
+ */
810
+ declare const ABORT_STEP_ID: "::pipeai::abort";
811
+ /**
812
+ * Synthetic step id used when a gate-resume's response validation / merge
813
+ * throws before the pipeline re-enters `execute()`. Reserved-namespaced for
814
+ * the same reason as {@link ABORT_STEP_ID}.
815
+ */
816
+ declare const GATE_RESUME_STEP_ID: "::pipeai::gate:resume";
817
+
818
+ /**
819
+ * Convert a legacy v1 gate snapshot to a v2 gate snapshot. Long-lived
820
+ * storage (Redis-without-TTL, S3, Postgres) should re-serialize via this
821
+ * helper before v0.8.0+ drops v1 acceptance.
822
+ */
823
+ declare function migrateSnapshot(legacy: LegacyGateSnapshotV1): GateSnapshot;
824
+ /**
825
+ * Seed for a run: the initial pipeline `output` plus an optional pre-execute
826
+ * `initialError`. The resume entry points compute these differently (gate
827
+ * schema-parse + merge vs. plain snapshot output), but every entry point then
828
+ * funnels through the same `runGenerate` / `runStream` machinery.
829
+ */
830
+ type StateSeed = {
831
+ output: unknown;
832
+ initialError: PendingError | null;
833
+ resumeDescent?: ResumeDescent;
645
834
  };
646
835
  declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates extends Record<string, unknown> = {}> {
647
836
  readonly id?: string;
648
- protected readonly steps: ReadonlyArray<StepNode>;
837
+ protected readonly steps: ReadonlyArray<Step>;
649
838
  protected readonly observability?: WorkflowObservability;
650
839
  private duplicateCheckPassed;
651
- private _cachedExecutableStepCount?;
840
+ private _stepCounts?;
652
841
  private _cachedStepShapeHash?;
653
- protected constructor(steps: ReadonlyArray<StepNode>, id?: string, observability?: WorkflowObservability);
842
+ protected constructor(steps: ReadonlyArray<Step>, id?: string, observability?: WorkflowObservability);
654
843
  /**
655
844
  * Walk the step list once per terminal instance. Rejects:
656
845
  * - Duplicate `(type, id)` pairs.
@@ -659,24 +848,35 @@ declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates ext
659
848
  */
660
849
  private ensureDuplicateCheck;
661
850
  /**
662
- * Count of executable nodes i.e. NOT `catch` or `finally`. Drives
663
- * checkpoint auto-cadence so adding cleanup steps doesn't surprise users
664
- * with extra fires. `branch`/`foreach`/`repeat`/`parallel`/`nested` are all
665
- * `type: "step"` internally and count as executable.
851
+ * Two cadence inputs from a single walk:
852
+ * - `executable` nodes that aren't `catch` / `finally`. A graph-size
853
+ * proxy for the catastrophe threshold in {@link validateRunOptions}.
854
+ * - `checkpointable` — `type === "step"` nodes only (this includes
855
+ * branch / foreach / repeat / parallel / nested). Drives the checkpoint
856
+ * auto-cadence denominator: gates suspend/skip and never reach the
857
+ * checkpoint block, so counting them would dilute the "~4 checkpoints
858
+ * across the run" target.
666
859
  */
667
- protected get cachedExecutableStepCount(): number;
860
+ protected get stepCounts(): {
861
+ executable: number;
862
+ checkpointable: number;
863
+ };
668
864
  /** @internal — used by `computeStepShapeHash` to descend nested workflows. */
669
- getStepsForShapeHash(): ReadonlyArray<StepNode>;
865
+ getStepsForShapeHash(): ReadonlyArray<Step>;
670
866
  protected get cachedStepShapeHash(): string;
671
867
  /**
672
868
  * Validate user-provided RunOptions before a run begins. Throws on
673
869
  * outright errors and on the loud-disaster combo (`freezeSnapshots: true
674
870
  * + checkpointEvery: 1` on a workflow of 8+ steps). Warns once on the
675
- * merely-suspicious combo (`freezeSnapshots: true + cadence <= 2`).
676
- * Plan-of-record: catastrophic combo escape via the
677
- * `"iAcceptThePerformanceCost"` literal.
871
+ * merely-suspicious combo (`freezeSnapshots: true + cadence <= 2`), and on
872
+ * checkpoint-cadence options set without an `onCheckpoint` sink (a no-op
873
+ * that usually signals a forgotten sink).
678
874
  */
679
875
  protected validateRunOptions(opts: RunOptions | undefined): void;
876
+ /** Observability event `type` for a node: a `type: "step"` node reports its
877
+ * `category` (agent / transform default to `"step"`); every other node's
878
+ * `type` IS the event type. */
879
+ private obsEventType;
680
880
  /**
681
881
  * Fire an observability hook safely. Returns `undefined` synchronously when
682
882
  * no hook is registered — avoiding the promise wrapper + microtask that an
@@ -690,15 +890,81 @@ declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates ext
690
890
  * Returns the hook's thrown error if any; undefined otherwise. Callers
691
891
  * `await` the result — `await undefined` is sync, so the no-hook path
692
892
  * stays allocation-free.
893
+ *
894
+ * Thin delegate to the free `fireHook` (which takes an explicit
895
+ * observability), kept as a method so the loop's many `this.fireHook` call
896
+ * sites stay unchanged.
693
897
  */
694
898
  protected fireHook<K extends keyof WorkflowObservability, E extends Parameters<NonNullable<WorkflowObservability[K]>>[0]>(state: RuntimeState, name: K, event: E): MaybePromise<unknown>;
695
- private fireHookSlow;
899
+ /**
900
+ * Fire `onStepError` for a step-body failure and honor the documented
901
+ * cause-attachment contract uniformly across every firing path (step, gate,
902
+ * catch, finally, checkpoint). When the hook itself throws, its error is
903
+ * attached as `cause` on the ORIGINAL error so the original still reaches the
904
+ * caller with the failure trail attached. If the original error is frozen /
905
+ * non-extensible (cause assignment throws) or is not an object, the hook
906
+ * error is recorded as a warning instead — so an `onStepError` throw is never
907
+ * silently lost. (The suspension-wins tail fires `onStepError` separately, on
908
+ * its own demotion path.)
909
+ */
910
+ protected fireStepErrorAndAttachCause(state: RuntimeState, event: {
911
+ stepId: string;
912
+ type: WorkflowStepType;
913
+ ctx: unknown;
914
+ error: unknown;
915
+ durationMs: number;
916
+ }): Promise<void>;
696
917
  generate(ctx: TContext, ...args: TInput extends void ? [input?: TInput, opts?: RunOptions] : [input: TInput, opts?: RunOptions]): Promise<WorkflowResult<TOutput>>;
697
- 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>;
918
+ 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, UI_MESSAGE>;
698
919
  protected buildResult(state: RuntimeState): WorkflowResult<TOutput>;
920
+ protected runGenerate(ctx: unknown, startIndex: number, opts: RunOptions | undefined, seed: () => MaybePromise<StateSeed>): Promise<WorkflowResult<TOutput>>;
921
+ protected runStream<UI_MESSAGE extends UIMessage = UIMessage>(ctx: unknown, startIndex: number, opts: RunOptions | undefined, options: WorkflowStreamOptions<UI_MESSAGE> | undefined, seed: () => MaybePromise<StateSeed>): WorkflowStreamResult<TOutput, UI_MESSAGE>;
699
922
  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>;
923
+ /**
924
+ * Promote a fired abort signal into `state.pendingError` at an iteration
925
+ * boundary. First observation discards any in-progress suspension (the caller
926
+ * asked to stop) and preserves a genuinely-different prior step error as a
927
+ * warning — but NOT one that is itself the abort reason (a nested workflow /
928
+ * concurrent unit that already rethrew it), which would surface a phantom
929
+ * step-failure warning. Subsequent iterations only re-promote if a downstream
930
+ * catch cleared pendingError — `AbortSignal.aborted` is sticky, so the
931
+ * workflow must not resume mid-pipeline just because a catch swallowed one
932
+ * observation.
933
+ */
934
+ private promoteAbort;
935
+ /**
936
+ * Emit a checkpoint after a successful `type:"step"` body. Skipped on
937
+ * pendingError (no clean state to snapshot), on suspension (gate already
938
+ * won), and for catch/finally/gate nodes (not checkpointable). Numeric
939
+ * `checkpointEvery` (default: `max(1, ceil(count/4))`) uses the loop-hoisted
940
+ * `ckptCadence`; the predicate form runs per step. A `when:false`-skipped
941
+ * `type:"step"` node returns normally (its body never ran) and still reaches
942
+ * here — it advances the counter and can itself be a checkpoint boundary,
943
+ * keeping the cadence denominator (`stepCounts.checkpointable`) consistent
944
+ * with the runtime counter.
945
+ */
946
+ private maybeCheckpoint;
947
+ /**
948
+ * Terminal reconciliation after the loop. Re-promotes a swallowed abort
949
+ * (recoverability must not depend on catch position), then resolves the
950
+ * mutually-exclusive precedence tail: checkpointFailed > original-step error
951
+ * > suspension. (A throwing catch/finally never reaches here — it bubbles
952
+ * straight out of the loop, so there is no finally-aggregation branch.)
953
+ */
954
+ private settleRun;
955
+ /**
956
+ * Run THIS sealed workflow as a nested step on the caller's run `state`.
957
+ * Public (internal; not re-exported from index) so `Step` subclasses —
958
+ * `nested` / `repeat` / `foreach` / `parallel` with `SealedWorkflow` targets
959
+ * — can run a sub-workflow without reaching the protected `execute`.
960
+ *
961
+ * Contract: RunOptions is run-scoped, so the child never inherits the
962
+ * parent's (`state.warnings` IS propagated — telemetry > config). A gate
963
+ * inside the child leaves `state.suspension` set so it propagates up (only a
964
+ * `.step(workflow)` ever does this — concurrent/looped combinators forbid
965
+ * gated targets at build time).
966
+ */
967
+ executeAsNested(state: RuntimeState, startIndex?: number): Promise<void>;
702
968
  loadState<K extends string & keyof TGates>(gateId: K, snapshot: WorkflowSnapshot): ResumedWorkflow<TContext, TGates[K], TOutput>;
703
969
  /**
704
970
  * Resume from a checkpoint snapshot. Validates the step-shape hash unless
@@ -719,7 +985,9 @@ declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates ext
719
985
  /**
720
986
  * Append a `.finally()` body to a sealed workflow, returning another sealed
721
987
  * workflow. Allows multi-finally chains (`.finally().finally()`). A throwing
722
- * `.finally` body does NOT abort subsequent ones they all run.
988
+ * `.finally` body bubbles straight out of the run: it is non-recoverable, does
989
+ * NOT aggregate with a prior error, and subsequent `.finally()` bodies do not
990
+ * run. (See {@link FinallyStep} for the full contract.)
723
991
  */
724
992
  finally(id: string, fn: (params: {
725
993
  ctx: Readonly<TContext>;
@@ -727,7 +995,7 @@ declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates ext
727
995
  private findGateIndex;
728
996
  }
729
997
  interface ResumedWorkflowConfig {
730
- readonly mode: "gate" | "checkpoint";
998
+ readonly mode: "gate";
731
999
  readonly schema?: SchemaWithParse<unknown>;
732
1000
  readonly mergeFn?: (params: {
733
1001
  priorOutput: unknown;
@@ -736,48 +1004,76 @@ interface ResumedWorkflowConfig {
736
1004
  readonly priorOutput?: unknown;
737
1005
  readonly snapshot?: WorkflowSnapshot;
738
1006
  readonly observability?: WorkflowObservability;
1007
+ /**
1008
+ * Set only for a NESTED gate resume: the descent (child start-indices per
1009
+ * level, innermost-last) the merged gate response rides down to the suspended
1010
+ * child. When present, `startIndex` is the ROOT's nested-step index and the
1011
+ * seed sets `state.resumeDescent` instead of the root `output`.
1012
+ */
1013
+ readonly nestedRemaining?: readonly number[];
739
1014
  }
740
1015
  declare class ResumedWorkflow<TContext, TResponse = unknown, TOutput = void> extends SealedWorkflow<TContext, TResponse, TOutput> {
741
1016
  private readonly startIndex;
742
1017
  private readonly schema?;
743
1018
  private readonly mergeFn?;
744
1019
  private readonly priorOutput;
1020
+ private readonly nestedRemaining?;
745
1021
  /** @internal */
746
- constructor(steps: ReadonlyArray<StepNode>, startIndex: number, config: ResumedWorkflowConfig);
1022
+ constructor(steps: ReadonlyArray<Step>, startIndex: number, config: ResumedWorkflowConfig);
747
1023
  private validateResponse;
1024
+ /**
1025
+ * Seed the run by validating the gate response and merging it with the
1026
+ * suspended output. Runs schema.parse + mergeFn inside a try so a failure
1027
+ * becomes a pre-execute `initialError` (routed through `.catch()`) rather
1028
+ * than escaping the run synchronously. On error the output falls back to the
1029
+ * prior (pre-gate) output.
1030
+ */
1031
+ private seedFromResponse;
748
1032
  generate(ctx: TContext, ...args: TResponse extends void ? [response?: TResponse, opts?: RunOptions] : [response: TResponse, opts?: RunOptions]): Promise<WorkflowResult<TOutput>>;
749
- 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>;
1033
+ 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, UI_MESSAGE>;
750
1034
  }
751
1035
  declare class CheckpointResumedWorkflow<TContext, TOutput = void> extends SealedWorkflow<TContext, void, TOutput> {
752
1036
  private readonly startIndex;
753
1037
  private readonly priorOutput;
754
1038
  /** @internal */
755
- constructor(steps: ReadonlyArray<StepNode>, startIndex: number, config: ResumedWorkflowConfig);
1039
+ constructor(steps: ReadonlyArray<Step>, startIndex: number, config: {
1040
+ priorOutput?: unknown;
1041
+ observability?: WorkflowObservability;
1042
+ });
756
1043
  generate(ctx: TContext, ...args: [input?: void, opts?: RunOptions]): Promise<WorkflowResult<TOutput>>;
757
- stream<UI_MESSAGE extends UIMessage = UIMessage>(ctx: TContext, ...args: [input?: void, options?: WorkflowStreamOptions<UI_MESSAGE>, opts?: RunOptions]): WorkflowStreamResult<TOutput>;
1044
+ stream<UI_MESSAGE extends UIMessage = UIMessage>(ctx: TContext, ...args: [input?: void, options?: WorkflowStreamOptions<UI_MESSAGE>, opts?: RunOptions]): WorkflowStreamResult<TOutput, UI_MESSAGE>;
758
1045
  }
759
1046
  declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends Record<string, unknown> = {}> extends SealedWorkflow<TContext, TInput, TOutput, TGates> {
760
1047
  /**
761
- * Sentinel value for `foreach`'s `onError` handler. Returning `Workflow.SKIP`
762
- * from `onError` omits the failed item's index from the output array,
763
- * shortening it relative to the input array.
1048
+ * Sentinel value for `foreach`/`parallel`'s `onError` handler. Returning
1049
+ * `Workflow.SKIP` omits the failed item (foreach: shortens the output array;
1050
+ * parallel: leaves the slot `undefined`). Aliases the leaf-module `SKIP` so
1051
+ * the step subclasses can compare against it without importing this class.
764
1052
  */
765
- static readonly SKIP: unique symbol;
1053
+ static readonly SKIP: symbol;
766
1054
  private constructor();
767
1055
  static create<TContext, TInput = void>(options?: {
768
1056
  id?: string;
769
- observability?: WorkflowObservability;
1057
+ observability?: WorkflowObservability<TContext>;
770
1058
  }): Workflow<TContext, TInput, TInput>;
1059
+ 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
1060
  static from<TContext, TInput, TOutput>(agent: Agent<TContext, TInput, TOutput>, options?: StepOptions<TContext, TInput, TOutput>): Workflow<TContext, TInput, TOutput>;
772
1061
  private appendStep;
1062
+ step<TNextOutput>(agent: Agent<TContext, TOutput, TNextOutput>, options: StepOptions<TContext, TOutput, TNextOutput> & SkipPassthrough<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TOutput | TNextOutput, TGates>;
773
1063
  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>;
1064
+ 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>;
1065
+ step<TNextOutput, TChildGates extends Record<string, unknown> = {}>(workflow: SealedWorkflow<TContext, TOutput, TNextOutput, TChildGates>, options?: NestedStepOptions<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates & TChildGates>;
775
1066
  step<TNextOutput>(id: string, fn: (params: {
776
1067
  ctx: Readonly<TContext>;
777
1068
  input: TOutput;
778
1069
  writer?: UIMessageStreamWriter;
779
- }) => MaybePromise<TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
780
- gate<TResponse = TOutput, Id extends string = string>(id: Id & (Id extends keyof TGates ? never : Id), options?: {
1070
+ }) => MaybePromise<TNextOutput>, options: InlineStepOptions<TContext, TOutput, TNextOutput> & SkipPassthrough<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TOutput | TNextOutput, TGates>;
1071
+ step<TNextOutput>(id: string, fn: (params: {
1072
+ ctx: Readonly<TContext>;
1073
+ input: TOutput;
1074
+ writer?: UIMessageStreamWriter;
1075
+ }) => MaybePromise<TNextOutput>, options?: InlineStepOptions<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
1076
+ gate<TResponse = TOutput, TMerged = TResponse, Id extends string = string>(id: Id & (Id extends keyof TGates ? never : Id), options?: {
781
1077
  payload?: (params: {
782
1078
  ctx: Readonly<TContext>;
783
1079
  input: TOutput;
@@ -790,16 +1086,14 @@ declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends R
790
1086
  merge?: (params: {
791
1087
  priorOutput: TOutput;
792
1088
  response: TResponse;
793
- }) => MaybePromise<TResponse>;
794
- }): Workflow<TContext, TInput, TResponse, TGates & Record<Id, TResponse>>;
1089
+ }) => MaybePromise<TMerged>;
1090
+ }): Workflow<TContext, TInput, TMerged, TGates & Record<Id, TResponse>>;
795
1091
  branch<TNextOutput>(cases: BranchCase<TContext, TOutput, TNextOutput>[], options?: {
796
1092
  id?: string;
797
1093
  }): Workflow<TContext, TInput, TNextOutput, TGates>;
798
1094
  branch<TKeys extends string, TNextOutput>(config: BranchSelect<TContext, TOutput, TKeys, TNextOutput>, options?: {
799
1095
  id?: string;
800
1096
  }): Workflow<TContext, TInput, TNextOutput, TGates>;
801
- private branchPredicate;
802
- private branchSelect;
803
1097
  /**
804
1098
  * Map each item of an array through an agent or sub-workflow.
805
1099
  *
@@ -807,30 +1101,45 @@ declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends R
807
1101
  * @param options.id Override the default step id (`foreach:<agentId>` or
808
1102
  * the workflow's id). Required when chaining multiple foreach over the same
809
1103
  * 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 —
1104
+ * @param options.concurrency Max items in flight at any moment. **Default:
1105
+ * unbounded** (`Infinity` every item runs concurrently, clamped only by
1106
+ * item count). Pass an integer to throttle against provider rate limits.
1107
+ * Backed by a worker pool: as soon as one item completes, the next launches —
812
1108
  * no lockstep batching.
813
1109
  * @param options.onError Per-iteration error handler. **Bypassed entirely on
814
- * the suspension path** (when any item hits a nested gate) see the
1110
+ * the suspension path** (when any item hits a nested gate) **and on the
1111
+ * cancellation path** (the run was aborted — pre-abort failures become
1112
+ * `foreach-sibling` warnings and the abort reason rethrows) — see the
815
1113
  * foreach concurrency hazards in the README. Otherwise: return a
816
1114
  * `TNextOutput` value to substitute, return `Workflow.SKIP` to omit, throw
817
1115
  * to abort. Invoked sequentially in index order after all items settle.
1116
+ * A throw (or rethrow) from `onError` aborts the foreach immediately:
1117
+ * failures at indices AFTER the throwing one are neither recovered nor
1118
+ * surfaced as warnings.
818
1119
  */
819
- foreach<TNextOutput>(target: Agent<TContext, ElementOf<TOutput>, TNextOutput> | SealedWorkflow<TContext, ElementOf<TOutput>, TNextOutput>, options?: {
820
- id?: string;
821
- concurrency?: number;
822
- onError?: (params: {
823
- error: unknown;
824
- item: ElementOf<TOutput>;
825
- index: number;
826
- ctx: Readonly<TContext>;
827
- }) => MaybePromise<TNextOutput | typeof Workflow.SKIP>;
828
- }): Workflow<TContext, TInput, TNextOutput[], TGates>;
1120
+ foreach<TNextOutput, TG extends Record<string, unknown> = {}>(target: Agent<TContext, ElementOf<TOutput>, TNextOutput> | (SealedWorkflow<TContext, ElementOf<TOutput>, TNextOutput, TG> & NoGates<TG>), options?: ForeachOptions<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput[], TGates>;
1121
+ foreach<TNextOutput, TG extends Record<string, unknown> = {}>(build: (path: Workflow<TContext, ElementOf<TOutput>, ElementOf<TOutput>>) => (SealedWorkflow<TContext, ElementOf<TOutput>, TNextOutput, TG> & NoGates<TG>), options?: ForeachOptions<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput[], TGates>;
1122
+ /** Record-form + `onError`. Values are `BranchOutput | undefined` (SKIP-able). */
1123
+ parallel<TBranches extends Record<string, ParallelTarget<TContext, TOutput>>>(branches: TBranches & {
1124
+ [K in keyof TBranches]: GatelessBranch<TBranches[K]>;
1125
+ }, options: ParallelOptions<TContext, TOutput> & {
1126
+ onError: NonNullable<ParallelOptions<TContext, TOutput>["onError"]>;
1127
+ }): Workflow<TContext, TInput, ParallelOutputRecordPartial<TBranches>, TGates>;
829
1128
  /** 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>;
1129
+ parallel<TBranches extends Record<string, ParallelTarget<TContext, TOutput>>>(branches: TBranches & {
1130
+ [K in keyof TBranches]: GatelessBranch<TBranches[K]>;
1131
+ }, options?: ParallelOptions<TContext, TOutput>): Workflow<TContext, TInput, ParallelOutputRecord<TBranches>, TGates>;
1132
+ /** Tuple-form + `onError`. Each slot is `BranchOutput | undefined` (SKIP-able). */
1133
+ parallel<TBranches extends ReadonlyArray<ParallelTarget<TContext, TOutput>>>(branches: TBranches & {
1134
+ [K in keyof TBranches]: GatelessBranch<TBranches[K]>;
1135
+ }, options: ParallelOptions<TContext, TOutput> & {
1136
+ onError: NonNullable<ParallelOptions<TContext, TOutput>["onError"]>;
1137
+ }): Workflow<TContext, TInput, ParallelOutputTuplePartial<TBranches>, TGates>;
831
1138
  /** 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> & {
1139
+ parallel<TBranches extends ReadonlyArray<ParallelTarget<TContext, TOutput>>>(branches: TBranches & {
1140
+ [K in keyof TBranches]: GatelessBranch<TBranches[K]>;
1141
+ }, options?: ParallelOptions<TContext, TOutput>): Workflow<TContext, TInput, ParallelOutputTuple<TBranches>, TGates>;
1142
+ repeat<TG extends Record<string, unknown> = {}>(target: Agent<TContext, TOutput, TOutput> | (SealedWorkflow<TContext, TOutput, TOutput, TG> & NoGates<TG>), options: RepeatOptions<TContext, TOutput> & {
834
1143
  id?: string;
835
1144
  }): Workflow<TContext, TInput, TOutput, TGates>;
836
1145
  catch(id: string, fn: (params: {
@@ -843,4 +1152,4 @@ declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends R
843
1152
 
844
1153
  declare const SKIP: symbol;
845
1154
 
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 };
1155
+ export { ABORT_STEP_ID, Agent, type AgentConfig, type AgentResultParams, type AgentStepHooks, type AsToolMapOutput, type BranchCase, type BranchSelect, CHECKPOINT_STEP_ID, CheckpointResumedWorkflow, type CheckpointSnapshot, type ForeachOptions, 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 };