pipeai 0.2.1 → 0.8.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.ts CHANGED
@@ -24,7 +24,38 @@ declare class ToolProvider<TContext, TInput = unknown, TOutput = unknown> implem
24
24
  createTool(context: Readonly<TContext>): Tool;
25
25
  }
26
26
  declare function defineTool<TContext>(): <TInput, TOutput>(config: ToolProviderConfig<TContext, TInput, TOutput>) => ToolProvider<TContext, TInput, TOutput>;
27
+ declare function isToolProvider<TContext>(obj: unknown): obj is IToolProvider<TContext>;
27
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;
28
59
  type MaybePromise<T> = T | Promise<T>;
29
60
  /**
30
61
  * A value that can be static or derived from context and input.
@@ -39,6 +70,16 @@ type OutputType<T = any> = ReturnType<typeof Output.object<T>>;
39
70
  type AgentToolSet<TContext> = Record<string, Tool | IToolProvider<TContext>>;
40
71
  type GenerateTextResult<TOOLS extends ToolSet = ToolSet, OUTPUT extends OutputType = OutputType> = GenerateTextResult$1<TOOLS, OUTPUT>;
41
72
  type StreamTextResult<TOOLS extends ToolSet = ToolSet, OUTPUT extends OutputType = OutputType> = StreamTextResult$1<TOOLS, OUTPUT>;
73
+ /**
74
+ * The result passed to `asTool` / `asToolProvider`'s `mapOutput`.
75
+ *
76
+ * The same agent may be invoked as a tool from a generate-mode parent
77
+ * (returns `GenerateTextResult`, sync `.text`/`.output`) or from a
78
+ * stream-mode parent (returns `StreamTextResult`, async `.text`/`.output`).
79
+ * The callsite cannot statically tell which mode it is in, so `mapOutput`
80
+ * receives the union and must `await` the relevant fields to support both.
81
+ */
82
+ type AsToolMapOutput<TOutput> = (result: GenerateTextResult<ToolSet, OutputType<TOutput>> | StreamTextResult<ToolSet, OutputType<TOutput>>) => MaybePromise<TOutput>;
42
83
  type StreamTextOptions = Parameters<typeof streamText>[0];
43
84
  type GenerateTextOptions = Parameters<typeof generateText>[0];
44
85
  type ManagedKeys = 'model' | 'system' | 'prompt' | 'messages' | 'tools' | 'activeTools' | 'toolChoice' | 'stopWhen' | 'output' | 'onFinish' | 'onStepFinish' | 'onError';
@@ -48,6 +89,13 @@ interface AgentConfig<TContext, TInput = void, TOutput = void> extends AIPassthr
48
89
  description?: string;
49
90
  input?: ZodType<TInput>;
50
91
  output?: OutputType<TOutput>;
92
+ /**
93
+ * Zod schema used to validate `output` after the AI SDK returns. Distinct
94
+ * from `tool.outputSchema` (AI SDK's tool-execution output schema): this
95
+ * runs **after** the SDK has parsed structured output, as a runtime guard
96
+ * against parse drift. If omitted, the parsed output is trusted as-is.
97
+ */
98
+ validateOutput?: ZodType<TOutput>;
51
99
  model: Resolvable<TContext, TInput, LanguageModel>;
52
100
  system?: Resolvable<TContext, TInput, string>;
53
101
  prompt?: Resolvable<TContext, TInput, string>;
@@ -55,7 +103,17 @@ interface AgentConfig<TContext, TInput = void, TOutput = void> extends AIPassthr
55
103
  tools?: Resolvable<TContext, TInput, AgentToolSet<TContext>>;
56
104
  activeTools?: Resolvable<TContext, TInput, string[]>;
57
105
  toolChoice?: Resolvable<TContext, TInput, ToolChoice<ToolSet>>;
58
- stopWhen?: Resolvable<TContext, TInput, StopCondition<ToolSet> | Array<StopCondition<ToolSet>>>;
106
+ /**
107
+ * Stop condition(s) for the tool loop. Pass either a single AI-SDK
108
+ * `StopCondition` (which is itself a function) or an array of them.
109
+ *
110
+ * **Not a `Resolvable`.** A `StopCondition` and a `(ctx, input) => StopCondition`
111
+ * resolver are both functions and cannot be safely distinguished at
112
+ * runtime, so this field intentionally does NOT accept the resolver
113
+ * form. If you need per-call dynamic stop conditions, build the agent
114
+ * inside your handler instead of using a static instance.
115
+ */
116
+ stopWhen?: StopCondition<ToolSet> | Array<StopCondition<ToolSet>>;
59
117
  onStepFinish?: (params: {
60
118
  result: OnStepFinishEvent;
61
119
  ctx: Readonly<TContext>;
@@ -79,6 +137,14 @@ declare class Agent<TContext, TInput = void, TOutput = void> {
79
137
  readonly id: string;
80
138
  readonly description: string;
81
139
  readonly hasOutput: boolean;
140
+ /**
141
+ * Zod schema used to validate the agent's structured `output` after the AI
142
+ * SDK returns. Distinct from `tool.outputSchema` (which validates tool
143
+ * execution output). Exposed (readonly) so external runners — notably the
144
+ * workflow runtime — can pass it through to `extractOutput` without
145
+ * re-plumbing it.
146
+ */
147
+ readonly validateOutput: ZodType<TOutput> | undefined;
82
148
  private readonly config;
83
149
  private readonly _hasDynamicConfig;
84
150
  private readonly _resolvedStaticTools;
@@ -86,15 +152,26 @@ declare class Agent<TContext, TInput = void, TOutput = void> {
86
152
  private readonly _onStepFinish;
87
153
  private readonly _onFinish;
88
154
  constructor(config: AgentConfig<TContext, TInput, TOutput>);
89
- generate(ctx: TContext, ...args: TInput extends void ? [input?: TInput] : [input: TInput]): Promise<GenerateTextResult<ToolSet, OutputType<TOutput>>>;
90
- stream(ctx: TContext, ...args: TInput extends void ? [input?: TInput] : [input: TInput]): Promise<StreamTextResult<ToolSet, OutputType<TOutput>>>;
155
+ generate(ctx: TContext, ...args: TInput extends void ? [input?: TInput, options?: {
156
+ abortSignal?: AbortSignal;
157
+ }] : [input: TInput, options?: {
158
+ abortSignal?: AbortSignal;
159
+ }]): Promise<GenerateTextResult<ToolSet, OutputType<TOutput>>>;
160
+ stream(ctx: TContext, ...args: TInput extends void ? [input?: TInput, options?: {
161
+ abortSignal?: AbortSignal;
162
+ }] : [input: TInput, options?: {
163
+ abortSignal?: AbortSignal;
164
+ }]): Promise<StreamTextResult<ToolSet, OutputType<TOutput>>>;
91
165
  asTool(ctx: TContext, options?: {
92
- mapOutput?: (result: GenerateTextResult<ToolSet, OutputType<TOutput>>) => MaybePromise<TOutput>;
166
+ mapOutput?: AsToolMapOutput<TOutput>;
93
167
  }): Tool;
94
168
  asToolProvider(options?: {
95
- mapOutput?: (result: GenerateTextResult<ToolSet, OutputType<TOutput>>) => MaybePromise<TOutput>;
169
+ mapOutput?: AsToolMapOutput<TOutput>;
96
170
  }): IToolProvider<TContext>;
97
171
  private createToolInstance;
172
+ private invokeOnError;
173
+ private generateWithOptions;
174
+ private streamWithOptions;
98
175
  private buildCallOptions;
99
176
  private resolveConfig;
100
177
  private resolveConfigAsync;
@@ -110,45 +187,270 @@ declare class WorkflowLoopError extends Error {
110
187
  readonly maxIterations: number;
111
188
  constructor(iterations: number, maxIterations: number);
112
189
  }
113
- interface WorkflowSnapshot {
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
+ }
203
+ /**
204
+ * v2 gate snapshot. The `kind` discriminant differentiates it from
205
+ * checkpoint snapshots. The legacy v1 form is still accepted by `loadState`.
206
+ */
207
+ interface GateSnapshot {
208
+ readonly version: 2;
209
+ readonly kind: "gate";
210
+ readonly resumeFromIndex: number;
211
+ readonly output: unknown;
212
+ readonly gateId: string;
213
+ readonly gatePayload: unknown;
214
+ }
215
+ /**
216
+ * v2 checkpoint snapshot. Carries a step-shape hash; resume verifies the
217
+ * workflow definition hasn't drifted before continuing.
218
+ */
219
+ interface CheckpointSnapshot {
220
+ readonly version: 2;
221
+ readonly kind: "checkpoint";
222
+ readonly resumeFromIndex: number;
223
+ readonly output: unknown;
224
+ readonly stepShapeHash: string;
225
+ }
226
+ /**
227
+ * Legacy v0.4.0 gate-only snapshot. Accepted by `loadState` for one release
228
+ * via the shim path. `kind?: undefined` makes runtime narrowing on
229
+ * `kind === undefined` reachable — JSON round-trips strip the property.
230
+ * Migrate via `migrateSnapshot()` before v0.8.0+.
231
+ */
232
+ interface LegacyGateSnapshotV1 {
114
233
  readonly version: 1;
234
+ readonly kind?: undefined;
115
235
  readonly resumeFromIndex: number;
116
236
  readonly output: unknown;
117
237
  readonly gateId: string;
118
238
  readonly gatePayload: unknown;
119
239
  }
120
- declare class WorkflowSuspended extends Error {
121
- readonly snapshot: WorkflowSnapshot;
122
- constructor(snapshot: WorkflowSnapshot);
240
+ type WorkflowSnapshot = GateSnapshot | CheckpointSnapshot | LegacyGateSnapshotV1;
241
+ interface WorkflowWarning {
242
+ readonly source: "step" | "finally" | "catch" | "onCheckpoint" | "onStepStart" | "onStepFinish" | "onStepError" | "foreach-sibling";
243
+ readonly stepId: string;
244
+ readonly error: unknown;
123
245
  }
124
- interface AgentStepHooks<TContext, TOutput, TNextOutput> {
125
- mapGenerateResult?: (params: {
126
- result: GenerateTextResult<ToolSet, OutputType<TNextOutput>>;
127
- ctx: Readonly<TContext>;
128
- input: TOutput;
129
- }) => MaybePromise<TNextOutput>;
130
- mapStreamResult?: (params: {
131
- result: StreamTextResult<ToolSet, OutputType<TNextOutput>>;
132
- ctx: Readonly<TContext>;
133
- input: TOutput;
134
- }) => MaybePromise<TNextOutput>;
135
- onGenerateResult?: (params: {
136
- result: GenerateTextResult<ToolSet, OutputType<TNextOutput>>;
137
- ctx: Readonly<TContext>;
138
- input: TOutput;
246
+ type WorkflowStepType = "step" | "nested" | "gate" | "catch" | "finally" | "branch" | "foreach" | "repeat" | "parallel";
247
+ /**
248
+ * Workflow observability hooks. All optional. Errors thrown inside hooks
249
+ * are captured into `result.warnings` with a matching `source` tag, except
250
+ * `onStepError`, which causes the run to throw the ORIGINAL step error with
251
+ * `error.cause = obsError` (preserving `instanceof` on the original).
252
+ *
253
+ * Per-node firing rules (where "step-like" = step / nested / branch /
254
+ * foreach / parallel / repeat):
255
+ * - step-like / gate (cond-true → suspends): onStepStart always; onStepFinish
256
+ * when body returns (suspended: true for gate, false otherwise); onStepError
257
+ * on body throw.
258
+ * - gate (cond false → skip): onStepStart, onStepFinish({ suspended: false }).
259
+ * - catch: onStepStart only when pendingError set; onStepFinish when catchFn
260
+ * returns; onStepError when catchFn throws.
261
+ * - finally: onStepStart always (runs even after suspension); onStepFinish
262
+ * always; onStepError when body throws.
263
+ * - foreach / parallel: emit ALSO per-item events (onItemStart/Finish/Error).
264
+ * - repeat: emit ONLY combinator-level events (no per-iteration events —
265
+ * iteration count is data-dependent and per-item would be misleading).
266
+ *
267
+ * Skip-checked nodes (`state.suspension || pendingError` already set on entry)
268
+ * emit nothing — `.finally()` is the exception.
269
+ */
270
+ interface WorkflowObservability {
271
+ onStepStart?: (event: {
272
+ stepId: string;
273
+ type: WorkflowStepType;
274
+ ctx: unknown;
275
+ input: unknown;
139
276
  }) => MaybePromise<void>;
140
- onStreamResult?: (params: {
141
- result: StreamTextResult<ToolSet, OutputType<TNextOutput>>;
142
- ctx: Readonly<TContext>;
143
- input: TOutput;
277
+ onStepFinish?: (event: {
278
+ stepId: string;
279
+ type: WorkflowStepType;
280
+ ctx: unknown;
281
+ output: unknown;
282
+ durationMs: number;
283
+ suspended: boolean;
284
+ }) => MaybePromise<void>;
285
+ onStepError?: (event: {
286
+ stepId: string;
287
+ type: WorkflowStepType;
288
+ ctx: unknown;
289
+ error: unknown;
290
+ durationMs: number;
291
+ }) => MaybePromise<void>;
292
+ onItemStart?: (event: {
293
+ stepId: string;
294
+ type: "foreach" | "parallel";
295
+ itemIndex: number | string;
296
+ ctx: unknown;
297
+ input: unknown;
298
+ }) => MaybePromise<void>;
299
+ onItemFinish?: (event: {
300
+ stepId: string;
301
+ type: "foreach" | "parallel";
302
+ itemIndex: number | string;
303
+ ctx: unknown;
304
+ output: unknown;
305
+ durationMs: number;
306
+ }) => MaybePromise<void>;
307
+ onItemError?: (event: {
308
+ stepId: string;
309
+ type: "foreach" | "parallel";
310
+ itemIndex: number | string;
311
+ ctx: unknown;
312
+ error: unknown;
313
+ durationMs: number;
314
+ }) => MaybePromise<void>;
315
+ }
316
+ interface RunOptions {
317
+ /**
318
+ * Step-level checkpoint sink. Called after each successful step body when
319
+ * `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
322
+ * as an error — workflow `.catch()` is bypassed for checkpoint failures.
323
+ */
324
+ readonly onCheckpoint?: (snapshot: CheckpointSnapshot, opts: {
325
+ signal: AbortSignal;
144
326
  }) => MaybePromise<void>;
327
+ /**
328
+ * Fire `onCheckpoint` every N executable steps. Mutually exclusive with
329
+ * `checkpointWhen`. Default: `max(1, ceil(executableCount / 4))` —
330
+ * 4 checkpoints across the run, with a floor of every step on tiny pipelines.
331
+ */
332
+ readonly checkpointEvery?: number;
333
+ /**
334
+ * Predicate variant — fire `onCheckpoint` exactly when this returns true.
335
+ * Mutually exclusive with `checkpointEvery`.
336
+ */
337
+ readonly checkpointWhen?: (params: {
338
+ stepIndex: number;
339
+ stepId: string;
340
+ ctx: unknown;
341
+ }) => boolean;
342
+ /**
343
+ * When truthy, deeply freeze the gate / checkpoint snapshot and the
344
+ * `result.warnings` array. Default false. Pass `"iAcceptThePerformanceCost"`
345
+ * to bypass `validateRunOptions`' catastrophic-combo guard
346
+ * (freezeSnapshots: true + checkpointEvery: 1 + steps.length >= 8).
347
+ */
348
+ readonly freezeSnapshots?: boolean | "iAcceptThePerformanceCost";
349
+ /**
350
+ * Cooperative cancellation signal. Checked at every step boundary inside
351
+ * `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.
359
+ */
360
+ 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
+ }
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
+ /**
389
+ * Discriminated union describing one agent invocation's result.
390
+ *
391
+ * - `mode: "generate"` — `result` is a `GenerateTextResult`; `.text`, `.output`,
392
+ * `.usage` etc. are synchronous (already-resolved).
393
+ * - `mode: "stream"` — `result` is a `StreamTextResult`; the same fields are
394
+ * `Promise`s that you must `await` before reading.
395
+ *
396
+ * The shared field set (`ctx`, `input`) is identical across both modes;
397
+ * narrowing on `mode` is only necessary when you need to touch a
398
+ * mode-specific shape.
399
+ */
400
+ type AgentResultParams<TContext, TOutput, TNextOutput> = {
401
+ readonly mode: "generate";
402
+ readonly result: GenerateTextResult<ToolSet, OutputType<TNextOutput>>;
403
+ readonly ctx: Readonly<TContext>;
404
+ readonly input: TOutput;
405
+ } | {
406
+ readonly mode: "stream";
407
+ readonly result: StreamTextResult<ToolSet, OutputType<TNextOutput>>;
408
+ readonly ctx: Readonly<TContext>;
409
+ readonly input: TOutput;
410
+ };
411
+ interface AgentStepHooks<TContext, TOutput, TNextOutput> {
412
+ /**
413
+ * Transform the agent's result into the next step's input. Fires once per
414
+ * step regardless of generate-vs-stream mode; discriminate on `mode` if you
415
+ * need a mode-specific field. Returning the agent's `result.text` works
416
+ * for both modes (string vs Promise<string>) because `MaybePromise` accepts
417
+ * either.
418
+ *
419
+ * If omitted, the workflow's default extraction is used:
420
+ * - With `agent.output` declared → `extractOutput(result, agent.validateOutput)`
421
+ * - Without `agent.output` → `result.text` (awaited if stream)
422
+ */
423
+ mapResult?: (params: AgentResultParams<TContext, TOutput, TNextOutput>) => MaybePromise<TNextOutput>;
424
+ /**
425
+ * Observe the agent's result without changing the step's downstream value.
426
+ * Fires once per step regardless of mode. Use for logging, telemetry,
427
+ * usage accounting, side-effects that should not affect pipeline data
428
+ * flow.
429
+ */
430
+ onResult?: (params: AgentResultParams<TContext, TOutput, TNextOutput>) => MaybePromise<void>;
431
+ /**
432
+ * **Stream-mode only.** Override the workflow's default
433
+ * `writer.merge(result.toUIMessageStream())` call so YOU control how the
434
+ * agent's stream reaches the outer workflow's UI message stream. Useful
435
+ * for buffering, transforming, fan-out to multiple writers, or injecting
436
+ * custom UI messages around the agent's output.
437
+ *
438
+ * Has no generate-mode analog because in generate mode there is no stream
439
+ * to merge. If both `handleStream` and `mapResult`/`onResult` are
440
+ * configured, `handleStream` runs first.
441
+ */
145
442
  handleStream?: (params: {
146
443
  result: StreamTextResult<ToolSet, OutputType<TNextOutput>>;
147
444
  writer: UIMessageStreamWriter;
148
445
  ctx: Readonly<TContext>;
149
446
  }) => MaybePromise<void>;
150
447
  }
151
- type StepOptions<TContext, TOutput, TNextOutput> = AgentStepHooks<TContext, TOutput, TNextOutput>;
448
+ type StepOptions<TContext, TOutput, TNextOutput> = AgentStepHooks<TContext, TOutput, TNextOutput> & {
449
+ /** Override the default step id (`agent.id`). Required when reusing the same
450
+ * agent across multiple steps in one workflow — the construction-time
451
+ * `(type, id)` walk rejects duplicates. */
452
+ id?: string;
453
+ };
152
454
  interface BranchCase<TContext, TOutput, TNextOutput> extends AgentStepHooks<TContext, TOutput, TNextOutput> {
153
455
  when?: (params: {
154
456
  ctx: Readonly<TContext>;
@@ -161,15 +463,32 @@ interface BranchSelect<TContext, TOutput, TKeys extends string, TNextOutput> ext
161
463
  ctx: Readonly<TContext>;
162
464
  input: TOutput;
163
465
  }) => MaybePromise<TKeys>;
164
- agents: Record<TKeys, Agent<TContext, any, TNextOutput>>;
165
- fallback?: Agent<TContext, any, TNextOutput>;
166
- }
167
- interface WorkflowResult<TOutput> {
168
- output: TOutput;
466
+ agents: Record<TKeys, Agent<TContext, TOutput, TNextOutput>>;
467
+ fallback?: Agent<TContext, TOutput, TNextOutput>;
468
+ /**
469
+ * Diagnostic hook invoked when `select` returns a key that has no matching
470
+ * entry in `agents`. Fires BEFORE `fallback` is applied or
471
+ * `WorkflowBranchError` is thrown, regardless of whether a `fallback` is
472
+ * configured. Useful for logging typos / unexpected classifier output.
473
+ */
474
+ onUnknownKey?: (params: {
475
+ key: string;
476
+ availableKeys: TKeys[];
477
+ ctx: Readonly<TContext>;
478
+ }) => void;
169
479
  }
480
+ type WorkflowResult<TOutput> = {
481
+ readonly status: "complete";
482
+ readonly output: TOutput;
483
+ readonly warnings: readonly WorkflowWarning[];
484
+ } | {
485
+ readonly status: "suspended";
486
+ readonly snapshot: GateSnapshot;
487
+ readonly warnings: readonly WorkflowWarning[];
488
+ };
170
489
  interface WorkflowStreamResult<TOutput> {
171
490
  stream: ReadableStream;
172
- output: Promise<TOutput>;
491
+ output: Promise<WorkflowResult<TOutput>>;
173
492
  }
174
493
  interface WorkflowStreamOptions {
175
494
  onError?: (error: unknown) => string;
@@ -190,13 +509,58 @@ type RepeatOptions<TContext, TOutput> = {
190
509
  maxIterations?: number;
191
510
  };
192
511
  type ElementOf<T> = T extends readonly (infer E)[] ? E : never;
512
+ /** A target for a `parallel()` branch — agent or sealed workflow. */
513
+ type ParallelTarget<TContext, TInput> = Agent<TContext, TInput, any> | SealedWorkflow<TContext, TInput, any>;
514
+ /** Extract the output type of a single parallel branch target. */
515
+ type BranchOutput<T> = T extends Agent<any, any, infer O> ? O : T extends SealedWorkflow<any, any, infer O> ? O : never;
516
+ /** Output shape for the record form: `{ [K]: BranchOutput<T[K]> }`. */
517
+ type ParallelOutputRecord<T extends Record<string, unknown>> = {
518
+ [K in keyof T]: BranchOutput<T[K]>;
519
+ };
520
+ /** Output shape for the tuple form: `[O1, O2, ...]`. */
521
+ type ParallelOutputTuple<T extends ReadonlyArray<unknown>> = {
522
+ [K in keyof T]: BranchOutput<T[K]>;
523
+ };
524
+ interface ParallelOptions<TContext> {
525
+ /** Override the default step id. Default: `parallel:record` or `parallel:tuple`. */
526
+ id?: string;
527
+ /**
528
+ * Max branches in flight at any moment. Default: `min(branches.length, 5)`.
529
+ * Pass `Infinity` (or `branches.length`) for full fan-out on >5-branch calls
530
+ * — the default caps at 5 to protect against rate limits and emits a
531
+ * one-time warn when the cap kicks in.
532
+ */
533
+ concurrency?: number;
534
+ /**
535
+ * Per-branch error handler. On the no-suspension path, called once per
536
+ * rejected branch in index order after all settle. Return a value to
537
+ * substitute, return `Workflow.SKIP` to leave the slot undefined (record
538
+ * form only — tuple SKIP would shift indices), or rethrow to abort the
539
+ * parallel.
540
+ *
541
+ * **Bypassed entirely on the suspension path** (any branch hit a nested
542
+ * gate). See README's "Suspension under `parallel()`" section.
543
+ */
544
+ onError?: (params: {
545
+ error: unknown;
546
+ /** Branch key in the record form; `undefined` in the tuple form. */
547
+ key?: string;
548
+ /** Branch index in the tuple form; `undefined` in the record form. */
549
+ index?: number;
550
+ ctx: Readonly<TContext>;
551
+ }) => unknown | typeof Workflow.SKIP | Promise<unknown | typeof Workflow.SKIP>;
552
+ }
193
553
  interface SchemaWithParse<T = unknown> {
194
554
  parse(data: unknown): T;
195
555
  }
556
+ type StepCategory = "step" | "nested" | "branch" | "foreach" | "repeat" | "parallel";
196
557
  type StepNode = {
197
558
  readonly type: "step";
198
559
  readonly id: string;
199
560
  readonly execute: (state: RuntimeState) => MaybePromise<void>;
561
+ readonly nestedWorkflow?: SealedWorkflow<any, any, any, any>;
562
+ /** Disambiguates observability events. Default `"step"`. */
563
+ readonly category?: StepCategory;
200
564
  } | {
201
565
  readonly type: "catch";
202
566
  readonly id: string;
@@ -226,39 +590,144 @@ interface RuntimeState {
226
590
  output: unknown;
227
591
  mode: "generate" | "stream";
228
592
  writer?: UIMessageStreamWriter;
593
+ suspension?: GateSnapshot;
594
+ warnings?: WorkflowWarning[];
595
+ checkpointFailed?: boolean;
596
+ runOptions?: RunOptions;
597
+ abortSignal?: AbortSignal;
229
598
  }
599
+ type PendingError = {
600
+ error: unknown;
601
+ stepId: string;
602
+ source: "step" | "finally" | "catch" | "onCheckpoint";
603
+ };
230
604
  declare class SealedWorkflow<TContext, TInput = void, TOutput = void, TGates extends Record<string, unknown> = {}> {
231
605
  readonly id?: string;
232
606
  protected readonly steps: ReadonlyArray<StepNode>;
233
- protected constructor(steps: ReadonlyArray<StepNode>, id?: string);
234
- generate(ctx: TContext, ...args: TInput extends void ? [input?: TInput] : [input: TInput]): Promise<WorkflowResult<TOutput>>;
235
- stream(ctx: TContext, ...args: TInput extends void ? [input?: TInput, options?: WorkflowStreamOptions] : [input: TInput, options?: WorkflowStreamOptions]): WorkflowStreamResult<TOutput>;
236
- protected execute(state: RuntimeState, startIndex?: number): Promise<void>;
607
+ protected readonly observability?: WorkflowObservability;
608
+ private duplicateCheckPassed;
609
+ private _cachedExecutableStepCount?;
610
+ private _cachedStepShapeHash?;
611
+ protected constructor(steps: ReadonlyArray<StepNode>, id?: string, observability?: WorkflowObservability);
612
+ /**
613
+ * Walk the step list once per terminal instance. Rejects:
614
+ * - Duplicate `(type, id)` pairs.
615
+ * - User step ids containing the reserved `::pipeai::` namespace
616
+ * (CHECKPOINT_STEP_ID lives there).
617
+ */
618
+ private ensureDuplicateCheck;
619
+ /**
620
+ * Count of executable nodes — i.e. NOT `catch` or `finally`. Drives
621
+ * checkpoint auto-cadence so adding cleanup steps doesn't surprise users
622
+ * with extra fires. `branch`/`foreach`/`repeat`/`parallel`/`nested` are all
623
+ * `type: "step"` internally and count as executable.
624
+ */
625
+ protected get cachedExecutableStepCount(): number;
626
+ /** @internal — used by `computeStepShapeHash` to descend nested workflows. */
627
+ getStepsForShapeHash(): ReadonlyArray<StepNode>;
628
+ protected get cachedStepShapeHash(): string;
629
+ /**
630
+ * Validate user-provided RunOptions before a run begins. Throws on
631
+ * outright errors and on the loud-disaster combo (`freezeSnapshots: true
632
+ * + checkpointEvery: 1` on a workflow of 8+ steps). Warns once on the
633
+ * merely-suspicious combo (`freezeSnapshots: true + cadence <= 2`).
634
+ * Plan-of-record: catastrophic combo escape via the
635
+ * `"iAcceptThePerformanceCost"` literal.
636
+ */
637
+ protected validateRunOptions(opts: RunOptions | undefined): void;
638
+ /**
639
+ * Fire an observability hook safely. Returns `undefined` synchronously when
640
+ * no hook is registered — avoiding the promise wrapper + microtask that an
641
+ * async function would unconditionally allocate on every step boundary.
642
+ *
643
+ * On hook throw:
644
+ * - non-`onStepError` hooks: warning pushed + console.error.
645
+ * - `onStepError`: throw is propagated as a return value; the run loop
646
+ * attaches it as `cause` on the original step error.
647
+ *
648
+ * Returns the hook's thrown error if any; undefined otherwise. Callers
649
+ * `await` the result — `await undefined` is sync, so the no-hook path
650
+ * stays allocation-free.
651
+ */
652
+ protected fireHook<K extends keyof WorkflowObservability, E extends Parameters<NonNullable<WorkflowObservability[K]>>[0]>(state: RuntimeState, name: K, event: E): MaybePromise<unknown>;
653
+ private fireHookSlow;
654
+ generate(ctx: TContext, ...args: TInput extends void ? [input?: TInput, opts?: RunOptions] : [input: TInput, opts?: RunOptions]): Promise<WorkflowResult<TOutput>>;
655
+ stream(ctx: TContext, ...args: TInput extends void ? [input?: TInput, options?: WorkflowStreamOptions, opts?: RunOptions] : [input: TInput, options?: WorkflowStreamOptions, opts?: RunOptions]): WorkflowStreamResult<TOutput>;
656
+ protected buildResult(state: RuntimeState): WorkflowResult<TOutput>;
657
+ protected execute(state: RuntimeState, startIndex?: number, opts?: RunOptions, initialError?: PendingError | null): Promise<void>;
237
658
  protected executeNestedWorkflow(state: RuntimeState, workflow: SealedWorkflow<TContext, unknown, unknown, any>): Promise<void>;
238
659
  protected executeAgent<TAgentInput, TNextOutput>(state: RuntimeState, agent: Agent<TContext, any, TNextOutput>, ctx: TContext, options?: AgentStepHooks<TContext, any, TNextOutput>): Promise<void>;
239
660
  loadState<K extends string & keyof TGates>(gateId: K, snapshot: WorkflowSnapshot): ResumedWorkflow<TContext, TGates[K], TOutput>;
661
+ /**
662
+ * Resume from a checkpoint snapshot. Validates the step-shape hash unless
663
+ * `{ skipShapeCheck: true }` is passed. Throws on:
664
+ * - gate snapshots (use `loadState` instead)
665
+ * - missing/corrupted `stepShapeHash`
666
+ * - shape mismatch (unless skipped)
667
+ * - out-of-bounds `resumeFromIndex`
668
+ * - 0-step workflow (structural invariant)
669
+ *
670
+ * Returns a `CheckpointResumedWorkflow` whose `generate(ctx, opts?)` takes
671
+ * NO response arg — the state is seeded from the snapshot's output. The
672
+ * matching gate-resume path (`loadState`) keeps the `response` arg.
673
+ */
674
+ resumeFrom(snapshot: WorkflowSnapshot, options?: {
675
+ skipShapeCheck?: boolean;
676
+ }): CheckpointResumedWorkflow<TContext, TOutput>;
677
+ /**
678
+ * Append a `.finally()` body to a sealed workflow, returning another sealed
679
+ * workflow. Allows multi-finally chains (`.finally().finally()`). A throwing
680
+ * `.finally` body does NOT abort subsequent ones — they all run.
681
+ */
682
+ finally(id: string, fn: (params: {
683
+ ctx: Readonly<TContext>;
684
+ }) => MaybePromise<void>): SealedWorkflow<TContext, TInput, TOutput, TGates>;
240
685
  private findGateIndex;
241
686
  }
687
+ interface ResumedWorkflowConfig {
688
+ readonly mode: "gate" | "checkpoint";
689
+ readonly schema?: SchemaWithParse<unknown>;
690
+ readonly mergeFn?: (params: {
691
+ priorOutput: unknown;
692
+ response: unknown;
693
+ }) => MaybePromise<unknown>;
694
+ readonly priorOutput?: unknown;
695
+ readonly snapshot?: WorkflowSnapshot;
696
+ readonly observability?: WorkflowObservability;
697
+ }
242
698
  declare class ResumedWorkflow<TContext, TResponse = unknown, TOutput = void> extends SealedWorkflow<TContext, TResponse, TOutput> {
243
699
  private readonly startIndex;
244
700
  private readonly schema?;
245
701
  private readonly mergeFn?;
246
702
  private readonly priorOutput;
247
703
  /** @internal */
248
- constructor(steps: ReadonlyArray<StepNode>, startIndex: number, schema?: SchemaWithParse<TResponse>, mergeFn?: (params: {
249
- priorOutput: unknown;
250
- response: unknown;
251
- }) => MaybePromise<unknown>, priorOutput?: unknown);
704
+ constructor(steps: ReadonlyArray<StepNode>, startIndex: number, config: ResumedWorkflowConfig);
252
705
  private validateResponse;
253
- generate(ctx: TContext, ...args: TResponse extends void ? [response?: TResponse] : [response: TResponse]): Promise<WorkflowResult<TOutput>>;
254
- stream(ctx: TContext, ...args: TResponse extends void ? [response?: TResponse, options?: WorkflowStreamOptions] : [response: TResponse, options?: WorkflowStreamOptions]): WorkflowStreamResult<TOutput>;
706
+ generate(ctx: TContext, ...args: TResponse extends void ? [response?: TResponse, opts?: RunOptions] : [response: TResponse, opts?: RunOptions]): Promise<WorkflowResult<TOutput>>;
707
+ stream(ctx: TContext, ...args: TResponse extends void ? [response?: TResponse, options?: WorkflowStreamOptions, opts?: RunOptions] : [response: TResponse, options?: WorkflowStreamOptions, opts?: RunOptions]): WorkflowStreamResult<TOutput>;
708
+ }
709
+ declare class CheckpointResumedWorkflow<TContext, TOutput = void> extends SealedWorkflow<TContext, void, TOutput> {
710
+ private readonly startIndex;
711
+ private readonly priorOutput;
712
+ /** @internal */
713
+ constructor(steps: ReadonlyArray<StepNode>, startIndex: number, config: ResumedWorkflowConfig);
714
+ generate(ctx: TContext, ...args: [input?: void, opts?: RunOptions]): Promise<WorkflowResult<TOutput>>;
715
+ stream(ctx: TContext, ...args: [input?: void, options?: WorkflowStreamOptions, opts?: RunOptions]): WorkflowStreamResult<TOutput>;
255
716
  }
256
717
  declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends Record<string, unknown> = {}> extends SealedWorkflow<TContext, TInput, TOutput, TGates> {
718
+ /**
719
+ * Sentinel value for `foreach`'s `onError` handler. Returning `Workflow.SKIP`
720
+ * from `onError` omits the failed item's index from the output array,
721
+ * shortening it relative to the input array.
722
+ */
723
+ static readonly SKIP: unique symbol;
257
724
  private constructor();
258
725
  static create<TContext, TInput = void>(options?: {
259
726
  id?: string;
727
+ observability?: WorkflowObservability;
260
728
  }): Workflow<TContext, TInput, TInput>;
261
729
  static from<TContext, TInput, TOutput>(agent: Agent<TContext, TInput, TOutput>, options?: StepOptions<TContext, TInput, TOutput>): Workflow<TContext, TInput, TOutput>;
730
+ private appendStep;
262
731
  step<TNextOutput>(agent: Agent<TContext, TOutput, TNextOutput>, options?: StepOptions<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
263
732
  step<TNextOutput>(workflow: SealedWorkflow<TContext, TOutput, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
264
733
  step<TNextOutput>(id: string, fn: (params: {
@@ -280,23 +749,55 @@ declare class Workflow<TContext, TInput = void, TOutput = void, TGates extends R
280
749
  response: TResponse;
281
750
  }) => MaybePromise<TResponse>;
282
751
  }): Workflow<TContext, TInput, TResponse, TGates & Record<Id, TResponse>>;
283
- branch<TNextOutput>(cases: BranchCase<TContext, TOutput, TNextOutput>[]): Workflow<TContext, TInput, TNextOutput, TGates>;
284
- branch<TKeys extends string, TNextOutput>(config: BranchSelect<TContext, TOutput, TKeys, TNextOutput>): Workflow<TContext, TInput, TNextOutput, TGates>;
752
+ branch<TNextOutput>(cases: BranchCase<TContext, TOutput, TNextOutput>[], options?: {
753
+ id?: string;
754
+ }): Workflow<TContext, TInput, TNextOutput, TGates>;
755
+ branch<TKeys extends string, TNextOutput>(config: BranchSelect<TContext, TOutput, TKeys, TNextOutput>, options?: {
756
+ id?: string;
757
+ }): Workflow<TContext, TInput, TNextOutput, TGates>;
285
758
  private branchPredicate;
286
759
  private branchSelect;
760
+ /**
761
+ * Map each item of an array through an agent or sub-workflow.
762
+ *
763
+ * @param target Agent or `SealedWorkflow` invoked once per item.
764
+ * @param options.id Override the default step id (`foreach:<agentId>` or
765
+ * the workflow's id). Required when chaining multiple foreach over the same
766
+ * target — the construction-time `(type, id)` walk rejects duplicates.
767
+ * @param options.concurrency Max items in flight at any moment (default 1).
768
+ * Backed by a semaphore: as soon as one item completes, the next launches —
769
+ * no lockstep batching.
770
+ * @param options.onError Per-iteration error handler. **Bypassed entirely on
771
+ * the suspension path** (when any item hits a nested gate) — see the
772
+ * foreach concurrency hazards in the README. Otherwise: return a
773
+ * `TNextOutput` value to substitute, return `Workflow.SKIP` to omit, throw
774
+ * to abort. Invoked sequentially in index order after all items settle.
775
+ */
287
776
  foreach<TNextOutput>(target: Agent<TContext, ElementOf<TOutput>, TNextOutput> | SealedWorkflow<TContext, ElementOf<TOutput>, TNextOutput>, options?: {
777
+ id?: string;
288
778
  concurrency?: number;
779
+ onError?: (params: {
780
+ error: unknown;
781
+ item: ElementOf<TOutput>;
782
+ index: number;
783
+ ctx: Readonly<TContext>;
784
+ }) => MaybePromise<TNextOutput | typeof Workflow.SKIP>;
289
785
  }): Workflow<TContext, TInput, TNextOutput[], TGates>;
290
- repeat(target: Agent<TContext, TOutput, TOutput> | SealedWorkflow<TContext, TOutput, TOutput>, options: RepeatOptions<TContext, TOutput>): Workflow<TContext, TInput, TOutput, TGates>;
786
+ /** Record-form overload. Returns `{ [K]: BranchOutput<T[K]> }`. */
787
+ parallel<TBranches extends Record<string, ParallelTarget<TContext, TOutput>>>(branches: TBranches, options?: ParallelOptions<TContext>): Workflow<TContext, TInput, ParallelOutputRecord<TBranches>, TGates>;
788
+ /** Tuple-form overload. Returns `[O1, O2, ...]`. Use `as const`. */
789
+ parallel<TBranches extends ReadonlyArray<ParallelTarget<TContext, TOutput>>>(branches: TBranches, options?: ParallelOptions<TContext>): Workflow<TContext, TInput, ParallelOutputTuple<TBranches>, TGates>;
790
+ repeat(target: Agent<TContext, TOutput, TOutput> | SealedWorkflow<TContext, TOutput, TOutput>, options: RepeatOptions<TContext, TOutput> & {
791
+ id?: string;
792
+ }): Workflow<TContext, TInput, TOutput, TGates>;
291
793
  catch(id: string, fn: (params: {
292
794
  error: unknown;
293
795
  ctx: Readonly<TContext>;
294
796
  lastOutput: TOutput;
295
797
  stepId: string;
296
798
  }) => MaybePromise<TOutput>): Workflow<TContext, TInput, TOutput, TGates>;
297
- finally(id: string, fn: (params: {
298
- ctx: Readonly<TContext>;
299
- }) => MaybePromise<void>): SealedWorkflow<TContext, TInput, TOutput, TGates>;
300
799
  }
301
800
 
302
- export { Agent, type AgentConfig, type AgentStepHooks, type BranchCase, type BranchSelect, type GenerateTextResult, type IToolProvider, type MaybePromise, type OutputType, type RepeatOptions, type Resolvable, ResumedWorkflow, SealedWorkflow, type StepOptions, type StreamTextResult, type ToolExecuteOptions, type ToolProviderConfig, Workflow, WorkflowBranchError, WorkflowLoopError, type WorkflowResult, type WorkflowSnapshot, type WorkflowStreamOptions, type WorkflowStreamResult, WorkflowSuspended, defineTool };
801
+ declare const SKIP: symbol;
802
+
803
+ 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 };