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/README.md +482 -50
- package/dist/index.cjs +1242 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +553 -52
- package/dist/index.d.ts +553 -52
- package/dist/index.js +1232 -167
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { tool } from "ai";
|
|
|
10
10
|
|
|
11
11
|
// src/utils.ts
|
|
12
12
|
import { AsyncLocalStorage } from "async_hooks";
|
|
13
|
+
import { createHash } from "crypto";
|
|
13
14
|
var writerStorage = new AsyncLocalStorage();
|
|
14
15
|
function runWithWriter(writer, fn) {
|
|
15
16
|
return writerStorage.run(writer, fn);
|
|
@@ -23,13 +24,58 @@ function resolveValue(value, ctx, input) {
|
|
|
23
24
|
}
|
|
24
25
|
return value;
|
|
25
26
|
}
|
|
26
|
-
async function extractOutput(result, hasStructuredOutput) {
|
|
27
|
+
async function extractOutput(result, hasStructuredOutput, schema) {
|
|
27
28
|
if (hasStructuredOutput) {
|
|
28
29
|
const output = await result.output;
|
|
29
|
-
if (output
|
|
30
|
+
if (output === void 0) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"Agent: structured output was declared but the model returned none. This usually means the model produced text that did not match the declared schema, or the underlying SDK did not parse the structured output."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (schema) {
|
|
36
|
+
return schema.parse(output);
|
|
37
|
+
}
|
|
38
|
+
return output;
|
|
30
39
|
}
|
|
31
40
|
return await result.text;
|
|
32
41
|
}
|
|
42
|
+
function deepFreeze(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
43
|
+
if (value === null || typeof value !== "object" || seen.has(value)) return value;
|
|
44
|
+
if (Object.isFrozen(value)) return value;
|
|
45
|
+
seen.add(value);
|
|
46
|
+
Object.freeze(value);
|
|
47
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
48
|
+
deepFreeze(value[key], seen);
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function computeStepShapeHash(steps, getNested) {
|
|
53
|
+
return createHash("sha256").update(canonicalDescriptor(steps, getNested, /* @__PURE__ */ new WeakSet())).digest("hex");
|
|
54
|
+
}
|
|
55
|
+
function canonicalDescriptor(steps, getNested, path) {
|
|
56
|
+
return JSON.stringify(steps.map((s, i) => {
|
|
57
|
+
const triple = [i, s.type, s.id];
|
|
58
|
+
for (const inner of getNested(s)) {
|
|
59
|
+
if (path.has(inner)) {
|
|
60
|
+
triple.push(`<cycle:${inner.id ?? "anon"}>`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
path.add(inner);
|
|
64
|
+
try {
|
|
65
|
+
triple.push(canonicalDescriptor(inner.getStepsForShapeHash(), getNested, path));
|
|
66
|
+
} finally {
|
|
67
|
+
path.delete(inner);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return triple;
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
var warnedOnceKeys = /* @__PURE__ */ new Set();
|
|
74
|
+
function warnOnce(key, message) {
|
|
75
|
+
if (warnedOnceKeys.has(key)) return;
|
|
76
|
+
warnedOnceKeys.add(key);
|
|
77
|
+
console.warn(message ?? key);
|
|
78
|
+
}
|
|
33
79
|
|
|
34
80
|
// src/tool-provider.ts
|
|
35
81
|
var TOOL_PROVIDER_BRAND = /* @__PURE__ */ Symbol.for("agent-workflow.ToolProvider");
|
|
@@ -60,10 +106,17 @@ var Agent = class {
|
|
|
60
106
|
id;
|
|
61
107
|
description;
|
|
62
108
|
hasOutput;
|
|
109
|
+
/**
|
|
110
|
+
* Zod schema used to validate the agent's structured `output` after the AI
|
|
111
|
+
* SDK returns. Distinct from `tool.outputSchema` (which validates tool
|
|
112
|
+
* execution output). Exposed (readonly) so external runners — notably the
|
|
113
|
+
* workflow runtime — can pass it through to `extractOutput` without
|
|
114
|
+
* re-plumbing it.
|
|
115
|
+
*/
|
|
116
|
+
validateOutput;
|
|
63
117
|
config;
|
|
64
118
|
_hasDynamicConfig;
|
|
65
119
|
_resolvedStaticTools = null;
|
|
66
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
120
|
_passthrough;
|
|
68
121
|
_onStepFinish;
|
|
69
122
|
_onFinish;
|
|
@@ -71,6 +124,7 @@ var Agent = class {
|
|
|
71
124
|
this.id = config.id;
|
|
72
125
|
this.description = config.description ?? "";
|
|
73
126
|
this.hasOutput = config.output !== void 0;
|
|
127
|
+
this.validateOutput = config.validateOutput;
|
|
74
128
|
this.config = config;
|
|
75
129
|
this._hasDynamicConfig = [
|
|
76
130
|
config.model,
|
|
@@ -79,8 +133,7 @@ var Agent = class {
|
|
|
79
133
|
config.messages,
|
|
80
134
|
config.tools,
|
|
81
135
|
config.activeTools,
|
|
82
|
-
config.toolChoice
|
|
83
|
-
config.stopWhen
|
|
136
|
+
config.toolChoice
|
|
84
137
|
].some((v) => typeof v === "function");
|
|
85
138
|
if (!this._hasDynamicConfig) {
|
|
86
139
|
const rawTools = config.tools ?? {};
|
|
@@ -94,6 +147,7 @@ var Agent = class {
|
|
|
94
147
|
description: _desc,
|
|
95
148
|
input: _inputSchema,
|
|
96
149
|
output: _output,
|
|
150
|
+
validateOutput: _validateOutput,
|
|
97
151
|
model: _m,
|
|
98
152
|
system: _s,
|
|
99
153
|
prompt: _p,
|
|
@@ -113,25 +167,13 @@ var Agent = class {
|
|
|
113
167
|
}
|
|
114
168
|
async generate(ctx, ...args) {
|
|
115
169
|
const input = args[0];
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
return await generateText(options);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
if (this.config.onError) {
|
|
122
|
-
await this.config.onError({ error, ctx, input, writer: getActiveWriter() });
|
|
123
|
-
}
|
|
124
|
-
throw error;
|
|
125
|
-
}
|
|
170
|
+
const callOptions = args[1];
|
|
171
|
+
return this.generateWithOptions(ctx, input, callOptions ?? {});
|
|
126
172
|
}
|
|
127
173
|
async stream(ctx, ...args) {
|
|
128
174
|
const input = args[0];
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
return streamText({
|
|
132
|
-
...options,
|
|
133
|
-
onError: this.config.onError ? ({ error }) => this.config.onError({ error, ctx, input, writer: getActiveWriter() }) : void 0
|
|
134
|
-
});
|
|
175
|
+
const callOptions = args[1];
|
|
176
|
+
return this.streamWithOptions(ctx, input, callOptions ?? {});
|
|
135
177
|
}
|
|
136
178
|
asTool(ctx, options) {
|
|
137
179
|
return this.createToolInstance(ctx, options);
|
|
@@ -152,24 +194,86 @@ var Agent = class {
|
|
|
152
194
|
return tool2({
|
|
153
195
|
description: this.description,
|
|
154
196
|
inputSchema: this.config.input,
|
|
155
|
-
|
|
197
|
+
// The AI SDK passes a `ToolExecutionOptions` argument that carries
|
|
198
|
+
// `abortSignal`, `toolCallId`, `messages`, etc. Forward `abortSignal` so
|
|
199
|
+
// a parent agent's abort cancels in-flight sub-agent calls instead of
|
|
200
|
+
// leaving them running and producing detached output.
|
|
201
|
+
execute: async (toolInput, execOptions) => {
|
|
202
|
+
const abortSignal = execOptions?.abortSignal;
|
|
156
203
|
const writer = getActiveWriter();
|
|
157
204
|
if (writer) {
|
|
158
|
-
const result2 = await this.
|
|
205
|
+
const result2 = await this.streamWithOptions(ctx, toolInput, { abortSignal });
|
|
159
206
|
writer.merge(result2.toUIMessageStream());
|
|
160
|
-
if (options?.mapOutput)
|
|
161
|
-
|
|
207
|
+
if (options?.mapOutput) {
|
|
208
|
+
await result2.text;
|
|
209
|
+
return options.mapOutput(result2);
|
|
210
|
+
}
|
|
211
|
+
return extractOutput(result2, this.hasOutput, this.validateOutput);
|
|
162
212
|
}
|
|
163
|
-
const result = await this.
|
|
213
|
+
const result = await this.generateWithOptions(ctx, toolInput, { abortSignal });
|
|
164
214
|
if (options?.mapOutput) return options.mapOutput(result);
|
|
165
|
-
return extractOutput(result, this.hasOutput);
|
|
215
|
+
return extractOutput(result, this.hasOutput, this.validateOutput);
|
|
166
216
|
}
|
|
167
217
|
// TS cannot simplify the SDK's `NeverOptional<TOutput, ...>` conditional in a
|
|
168
218
|
// generic context, so we cast through `unknown` instead of `any`.
|
|
169
219
|
});
|
|
170
220
|
}
|
|
171
|
-
//
|
|
221
|
+
// ── Internal: shared call helpers ─────────────────────────────
|
|
222
|
+
// `generate()` / `stream()` and the `asTool()` wrapper all funnel through
|
|
223
|
+
// these so the abortSignal-forwarding and onError-wrapping logic stays in
|
|
224
|
+
// one place. `extra` carries per-call overrides (currently `abortSignal`).
|
|
225
|
+
// If the user-supplied onError callback itself throws, attach the original
|
|
226
|
+
// model error as `.cause` on the new error and rethrow the wrapper. Without
|
|
227
|
+
// this, the original error is silently shadowed.
|
|
228
|
+
async invokeOnError(error, ctx, input) {
|
|
229
|
+
if (!this.config.onError) return;
|
|
230
|
+
try {
|
|
231
|
+
await this.config.onError({ error, ctx, input, writer: getActiveWriter() });
|
|
232
|
+
} catch (handlerError) {
|
|
233
|
+
if (handlerError instanceof Error) {
|
|
234
|
+
if (handlerError.cause === void 0) {
|
|
235
|
+
handlerError.cause = error;
|
|
236
|
+
}
|
|
237
|
+
throw handlerError;
|
|
238
|
+
}
|
|
239
|
+
const wrapped = new Error(
|
|
240
|
+
`Agent "${this.id}": onError handler threw a non-Error value (${typeof handlerError}): ${String(handlerError)}`
|
|
241
|
+
);
|
|
242
|
+
wrapped.cause = error;
|
|
243
|
+
throw wrapped;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async generateWithOptions(ctx, input, extra) {
|
|
247
|
+
const resolved = await this.resolveConfig(ctx, input);
|
|
248
|
+
const options = this.buildCallOptions(resolved, ctx, input);
|
|
249
|
+
try {
|
|
250
|
+
return await generateText({ ...options, ...extra });
|
|
251
|
+
} catch (error) {
|
|
252
|
+
await this.invokeOnError(error, ctx, input);
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async streamWithOptions(ctx, input, extra) {
|
|
257
|
+
const resolved = await this.resolveConfig(ctx, input);
|
|
258
|
+
const options = this.buildCallOptions(resolved, ctx, input);
|
|
259
|
+
try {
|
|
260
|
+
const onErrorOption = this.config.onError ? { onError: ({ error }) => this.invokeOnError(error, ctx, input) } : {};
|
|
261
|
+
return streamText({
|
|
262
|
+
...options,
|
|
263
|
+
...extra,
|
|
264
|
+
...onErrorOption
|
|
265
|
+
});
|
|
266
|
+
} catch (error) {
|
|
267
|
+
await this.invokeOnError(error, ctx, input);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
172
271
|
buildCallOptions(resolved, ctx, input) {
|
|
272
|
+
if (resolved.messages === void 0 && resolved.prompt === void 0) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Agent "${this.id}": neither \`prompt\` nor \`messages\` was provided. Configure one of them, or supply a Resolvable that returns one based on input.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
173
277
|
return {
|
|
174
278
|
...this._passthrough,
|
|
175
279
|
model: resolved.model,
|
|
@@ -177,11 +281,16 @@ var Agent = class {
|
|
|
177
281
|
activeTools: resolved.activeTools,
|
|
178
282
|
toolChoice: resolved.toolChoice,
|
|
179
283
|
stopWhen: resolved.stopWhen,
|
|
180
|
-
...resolved.messages ? { messages: resolved.messages } : { prompt: resolved.prompt
|
|
181
|
-
|
|
284
|
+
...resolved.messages !== void 0 ? { messages: resolved.messages } : { prompt: resolved.prompt },
|
|
285
|
+
// Use `!== undefined` rather than truthy so an intentional empty
|
|
286
|
+
// `system: ""` survives instead of being silently dropped.
|
|
287
|
+
...resolved.system !== void 0 ? { system: resolved.system } : {},
|
|
182
288
|
...this.config.output ? { output: this.config.output } : {},
|
|
183
|
-
|
|
184
|
-
|
|
289
|
+
// Only attach the callback when the user supplied one. Passing
|
|
290
|
+
// `undefined` explicitly can suppress default SDK rethrow behavior in
|
|
291
|
+
// some versions.
|
|
292
|
+
...this._onStepFinish ? { onStepFinish: (event) => this._onStepFinish({ result: event, ctx, input, writer: getActiveWriter() }) } : {},
|
|
293
|
+
...this._onFinish ? { onFinish: (event) => this._onFinish({ result: event, ctx, input, writer: getActiveWriter() }) } : {}
|
|
185
294
|
};
|
|
186
295
|
}
|
|
187
296
|
resolveConfig(ctx, input) {
|
|
@@ -203,18 +312,27 @@ var Agent = class {
|
|
|
203
312
|
return this.resolveConfigAsync(ctx, input);
|
|
204
313
|
}
|
|
205
314
|
async resolveConfigAsync(ctx, input) {
|
|
206
|
-
const [model, prompt, system, messages, rawTools, activeTools, toolChoice
|
|
315
|
+
const [model, prompt, system, messages, rawTools, activeTools, toolChoice] = await Promise.all([
|
|
207
316
|
resolveValue(this.config.model, ctx, input),
|
|
208
317
|
resolveValue(this.config.prompt, ctx, input),
|
|
209
318
|
resolveValue(this.config.system, ctx, input),
|
|
210
319
|
resolveValue(this.config.messages, ctx, input),
|
|
211
320
|
resolveValue(this.config.tools, ctx, input),
|
|
212
321
|
resolveValue(this.config.activeTools, ctx, input),
|
|
213
|
-
resolveValue(this.config.toolChoice, ctx, input)
|
|
214
|
-
resolveValue(this.config.stopWhen, ctx, input)
|
|
322
|
+
resolveValue(this.config.toolChoice, ctx, input)
|
|
215
323
|
]);
|
|
216
324
|
const tools = this.resolveTools(rawTools ?? {}, ctx);
|
|
217
|
-
return {
|
|
325
|
+
return {
|
|
326
|
+
model,
|
|
327
|
+
prompt,
|
|
328
|
+
system,
|
|
329
|
+
messages,
|
|
330
|
+
tools,
|
|
331
|
+
activeTools,
|
|
332
|
+
toolChoice,
|
|
333
|
+
// `stopWhen` is always static — see field declaration for why.
|
|
334
|
+
stopWhen: this.config.stopWhen
|
|
335
|
+
};
|
|
218
336
|
}
|
|
219
337
|
resolveTools(tools, ctx) {
|
|
220
338
|
const entries = Object.entries(tools);
|
|
@@ -252,37 +370,284 @@ var WorkflowLoopError = class extends Error {
|
|
|
252
370
|
this.name = "WorkflowLoopError";
|
|
253
371
|
}
|
|
254
372
|
};
|
|
255
|
-
var
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
373
|
+
var NestedGateUnsupportedError = class extends Error {
|
|
374
|
+
gateId;
|
|
375
|
+
workflowId;
|
|
376
|
+
// Always present; non-gate rejections from concurrent foreach.
|
|
377
|
+
siblingErrors;
|
|
378
|
+
// Always present; OTHER suspending items in concurrent foreach.
|
|
379
|
+
siblingSuspensions;
|
|
380
|
+
constructor(gateId, workflowId, siblingErrors = [], siblingSuspensions = []) {
|
|
381
|
+
super(`Gate "${gateId}" hit inside nested workflow "${workflowId ?? "(anonymous)"}". Nested gates are not yet supported.`);
|
|
382
|
+
this.name = "NestedGateUnsupportedError";
|
|
383
|
+
this.gateId = gateId;
|
|
384
|
+
this.workflowId = workflowId;
|
|
385
|
+
this.siblingErrors = siblingErrors;
|
|
386
|
+
this.siblingSuspensions = siblingSuspensions;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
var CHECKPOINT_STEP_ID = "::pipeai::onCheckpoint";
|
|
390
|
+
var CheckpointTimeoutError = class extends Error {
|
|
391
|
+
timeoutMs;
|
|
392
|
+
constructor(timeoutMs) {
|
|
393
|
+
super(`onCheckpoint exceeded ${timeoutMs}ms timeout`);
|
|
394
|
+
this.name = "CheckpointTimeoutError";
|
|
395
|
+
this.timeoutMs = timeoutMs;
|
|
261
396
|
}
|
|
262
397
|
};
|
|
263
|
-
|
|
398
|
+
function resolveFreezeSnapshots(state) {
|
|
399
|
+
return state.runOptions?.freezeSnapshots ? true : false;
|
|
400
|
+
}
|
|
401
|
+
function migrateSnapshot(legacy) {
|
|
402
|
+
if (legacy.version !== 1) {
|
|
403
|
+
throw new Error(`migrateSnapshot: expected v1 snapshot, got version ${legacy.version}`);
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
version: 2,
|
|
407
|
+
kind: "gate",
|
|
408
|
+
resumeFromIndex: legacy.resumeFromIndex,
|
|
409
|
+
output: legacy.output,
|
|
410
|
+
gateId: legacy.gateId,
|
|
411
|
+
gatePayload: legacy.gatePayload
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function getObservabilityType(node) {
|
|
415
|
+
if (node.type !== "step") return node.type;
|
|
416
|
+
return node.category ?? "step";
|
|
417
|
+
}
|
|
418
|
+
function getNestedWorkflows(node) {
|
|
419
|
+
switch (node.type) {
|
|
420
|
+
case "step":
|
|
421
|
+
return node.nestedWorkflow ? [node.nestedWorkflow] : [];
|
|
422
|
+
case "gate":
|
|
423
|
+
case "catch":
|
|
424
|
+
case "finally":
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function pendingErrorSourceToStepType(source) {
|
|
429
|
+
switch (source) {
|
|
430
|
+
case "step":
|
|
431
|
+
return "step";
|
|
432
|
+
case "finally":
|
|
433
|
+
return "finally";
|
|
434
|
+
case "catch":
|
|
435
|
+
return "catch";
|
|
436
|
+
case "onCheckpoint":
|
|
437
|
+
return "step";
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async function emitCheckpoint(state, opts, resumeFromIndex, stepShapeHash) {
|
|
441
|
+
if (!opts.onCheckpoint) return;
|
|
442
|
+
const snap = {
|
|
443
|
+
version: 2,
|
|
444
|
+
kind: "checkpoint",
|
|
445
|
+
resumeFromIndex,
|
|
446
|
+
output: state.output,
|
|
447
|
+
stepShapeHash
|
|
448
|
+
};
|
|
449
|
+
if (resolveFreezeSnapshots(state)) deepFreeze(snap);
|
|
450
|
+
const controller = new AbortController();
|
|
451
|
+
if (opts.checkpointTimeout !== void 0) {
|
|
452
|
+
const timeoutMs = opts.checkpointTimeout;
|
|
453
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
454
|
+
try {
|
|
455
|
+
const callPromise = Promise.resolve(opts.onCheckpoint(snap, { signal: controller.signal }));
|
|
456
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
457
|
+
controller.signal.addEventListener(
|
|
458
|
+
"abort",
|
|
459
|
+
() => reject(new CheckpointTimeoutError(timeoutMs)),
|
|
460
|
+
{ once: true }
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
callPromise.catch(() => {
|
|
464
|
+
});
|
|
465
|
+
timeoutPromise.catch(() => {
|
|
466
|
+
});
|
|
467
|
+
await Promise.race([callPromise, timeoutPromise]);
|
|
468
|
+
} finally {
|
|
469
|
+
clearTimeout(timeoutId);
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
await opts.onCheckpoint(snap, { signal: controller.signal });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
var warnedStreamOnErrorOnSuspend = false;
|
|
476
|
+
function pushWarning(state, source, stepId, error) {
|
|
477
|
+
(state.warnings ??= []).push({ source, stepId, error });
|
|
478
|
+
}
|
|
479
|
+
function demotePendingError(state, pe) {
|
|
480
|
+
pushWarning(state, pe.source, pe.stepId, pe.error);
|
|
481
|
+
}
|
|
482
|
+
function maybeWarnStreamOnErrorOnSuspend(result, options) {
|
|
483
|
+
if (result.status !== "suspended" || !options?.onError || warnedStreamOnErrorOnSuspend) return;
|
|
484
|
+
warnedStreamOnErrorOnSuspend = true;
|
|
485
|
+
console.warn(
|
|
486
|
+
"pipeai: stream() with options.onError suspended at a gate \u2014 onError will NOT be invoked for suspension. Discriminate via the resolved output Promise."
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
var SealedWorkflow = class _SealedWorkflow {
|
|
264
490
|
id;
|
|
265
491
|
steps;
|
|
266
|
-
|
|
492
|
+
observability;
|
|
493
|
+
// Memoized — see ensureDuplicateCheck().
|
|
494
|
+
duplicateCheckPassed = false;
|
|
495
|
+
// Memoized lazily per terminal instance — build pipelines once at module
|
|
496
|
+
// load and re-run via generate() to amortize.
|
|
497
|
+
_cachedExecutableStepCount;
|
|
498
|
+
_cachedStepShapeHash;
|
|
499
|
+
constructor(steps, id, observability) {
|
|
267
500
|
this.steps = steps;
|
|
268
501
|
this.id = id;
|
|
502
|
+
this.observability = observability;
|
|
503
|
+
}
|
|
504
|
+
// ── Construction-time validation (memoized per terminal instance) ────
|
|
505
|
+
/**
|
|
506
|
+
* Walk the step list once per terminal instance. Rejects:
|
|
507
|
+
* - Duplicate `(type, id)` pairs.
|
|
508
|
+
* - User step ids containing the reserved `::pipeai::` namespace
|
|
509
|
+
* (CHECKPOINT_STEP_ID lives there).
|
|
510
|
+
*/
|
|
511
|
+
ensureDuplicateCheck() {
|
|
512
|
+
if (this.duplicateCheckPassed) return;
|
|
513
|
+
const seen = /* @__PURE__ */ new Map();
|
|
514
|
+
for (let i = 0; i < this.steps.length; i++) {
|
|
515
|
+
const node = this.steps[i];
|
|
516
|
+
if (node.id.includes("::pipeai::")) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
`Workflow: step id "${node.id}" uses the reserved "::pipeai::" namespace at index ${i}.`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
const key = `${node.type}:${node.id}`;
|
|
522
|
+
const prior = seen.get(key);
|
|
523
|
+
if (prior !== void 0) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`Workflow: duplicate (${node.type}, "${node.id}") at indices ${prior} and ${i}. Pass an explicit \`{ id }\` (e.g. for back-to-back \`branch(...)\` or \`foreach(agentX).foreach(agentX)\`) to disambiguate.`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
seen.set(key, i);
|
|
529
|
+
}
|
|
530
|
+
this.duplicateCheckPassed = true;
|
|
531
|
+
}
|
|
532
|
+
// ── shape-hash + RunOptions validation ────────────────────────
|
|
533
|
+
/**
|
|
534
|
+
* Count of executable nodes — i.e. NOT `catch` or `finally`. Drives
|
|
535
|
+
* checkpoint auto-cadence so adding cleanup steps doesn't surprise users
|
|
536
|
+
* with extra fires. `branch`/`foreach`/`repeat`/`parallel`/`nested` are all
|
|
537
|
+
* `type: "step"` internally and count as executable.
|
|
538
|
+
*/
|
|
539
|
+
get cachedExecutableStepCount() {
|
|
540
|
+
if (this._cachedExecutableStepCount !== void 0) return this._cachedExecutableStepCount;
|
|
541
|
+
let n = 0;
|
|
542
|
+
for (const s of this.steps) {
|
|
543
|
+
if (s.type !== "catch" && s.type !== "finally") n++;
|
|
544
|
+
}
|
|
545
|
+
this._cachedExecutableStepCount = n;
|
|
546
|
+
return n;
|
|
547
|
+
}
|
|
548
|
+
/** @internal — used by `computeStepShapeHash` to descend nested workflows. */
|
|
549
|
+
getStepsForShapeHash() {
|
|
550
|
+
return this.steps;
|
|
551
|
+
}
|
|
552
|
+
get cachedStepShapeHash() {
|
|
553
|
+
if (this._cachedStepShapeHash !== void 0) return this._cachedStepShapeHash;
|
|
554
|
+
const getNested = (node) => getNestedWorkflows(node);
|
|
555
|
+
this._cachedStepShapeHash = computeStepShapeHash(
|
|
556
|
+
this.steps,
|
|
557
|
+
getNested
|
|
558
|
+
);
|
|
559
|
+
return this._cachedStepShapeHash;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Validate user-provided RunOptions before a run begins. Throws on
|
|
563
|
+
* outright errors and on the loud-disaster combo (`freezeSnapshots: true
|
|
564
|
+
* + checkpointEvery: 1` on a workflow of 8+ steps). Warns once on the
|
|
565
|
+
* merely-suspicious combo (`freezeSnapshots: true + cadence <= 2`).
|
|
566
|
+
* Plan-of-record: catastrophic combo escape via the
|
|
567
|
+
* `"iAcceptThePerformanceCost"` literal.
|
|
568
|
+
*/
|
|
569
|
+
validateRunOptions(opts) {
|
|
570
|
+
if (!opts) return;
|
|
571
|
+
if (!opts.onCheckpoint) return;
|
|
572
|
+
if (opts.checkpointEvery !== void 0 && opts.checkpointWhen !== void 0) {
|
|
573
|
+
throw new Error("RunOptions: checkpointEvery and checkpointWhen are mutually exclusive");
|
|
574
|
+
}
|
|
575
|
+
if (opts.checkpointEvery !== void 0 && (!Number.isInteger(opts.checkpointEvery) || opts.checkpointEvery < 1)) {
|
|
576
|
+
throw new Error(`RunOptions: checkpointEvery must be a positive integer, got ${opts.checkpointEvery}`);
|
|
577
|
+
}
|
|
578
|
+
if (opts.checkpointTimeout !== void 0 && (!Number.isFinite(opts.checkpointTimeout) || opts.checkpointTimeout < 1)) {
|
|
579
|
+
throw new Error(`RunOptions: checkpointTimeout must be a finite positive number (ms), got ${opts.checkpointTimeout}`);
|
|
580
|
+
}
|
|
581
|
+
const length = this.cachedExecutableStepCount;
|
|
582
|
+
const cadence = opts.checkpointEvery ?? Math.max(1, Math.ceil(length / 4));
|
|
583
|
+
if (opts.freezeSnapshots && opts.freezeSnapshots !== "iAcceptThePerformanceCost" && cadence === 1 && length >= 8) {
|
|
584
|
+
throw new Error(
|
|
585
|
+
`freezeSnapshots+checkpointEvery:1 on a ${length}-step workflow is reliably catastrophic. Set checkpointEvery >= 5, freezeSnapshots: false, or pass "iAcceptThePerformanceCost".`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
if (opts.freezeSnapshots && cadence <= 2) {
|
|
589
|
+
warnOnce(
|
|
590
|
+
"pipeai:freezeSnapshots-low-cadence",
|
|
591
|
+
"pipeai: freezeSnapshots+checkpointEvery<=2 compounds graph-walk cost."
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// ── Observability helpers ─────────────────────────────────────
|
|
596
|
+
/**
|
|
597
|
+
* Fire an observability hook safely. Returns `undefined` synchronously when
|
|
598
|
+
* no hook is registered — avoiding the promise wrapper + microtask that an
|
|
599
|
+
* async function would unconditionally allocate on every step boundary.
|
|
600
|
+
*
|
|
601
|
+
* On hook throw:
|
|
602
|
+
* - non-`onStepError` hooks: warning pushed + console.error.
|
|
603
|
+
* - `onStepError`: throw is propagated as a return value; the run loop
|
|
604
|
+
* attaches it as `cause` on the original step error.
|
|
605
|
+
*
|
|
606
|
+
* Returns the hook's thrown error if any; undefined otherwise. Callers
|
|
607
|
+
* `await` the result — `await undefined` is sync, so the no-hook path
|
|
608
|
+
* stays allocation-free.
|
|
609
|
+
*/
|
|
610
|
+
fireHook(state, name, event) {
|
|
611
|
+
const hook = this.observability?.[name];
|
|
612
|
+
if (!hook) return void 0;
|
|
613
|
+
return this.fireHookSlow(state, name, event, hook);
|
|
614
|
+
}
|
|
615
|
+
async fireHookSlow(state, name, event, hook) {
|
|
616
|
+
try {
|
|
617
|
+
await hook(event);
|
|
618
|
+
return void 0;
|
|
619
|
+
} catch (e) {
|
|
620
|
+
if (name !== "onStepError") {
|
|
621
|
+
const stepId = event.stepId;
|
|
622
|
+
pushWarning(state, name, stepId, e);
|
|
623
|
+
console.error(`pipeai: ${name} hook threw for stepId "${stepId}":`, e);
|
|
624
|
+
}
|
|
625
|
+
return e;
|
|
626
|
+
}
|
|
269
627
|
}
|
|
270
628
|
// ── Execution ─────────────────────────────────────────────────
|
|
271
629
|
async generate(ctx, ...args) {
|
|
630
|
+
this.ensureDuplicateCheck();
|
|
272
631
|
const input = args[0];
|
|
632
|
+
const opts = args[1];
|
|
633
|
+
this.validateRunOptions(opts);
|
|
273
634
|
const state = {
|
|
274
635
|
ctx,
|
|
275
636
|
output: input,
|
|
276
|
-
mode: "generate"
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return {
|
|
280
|
-
output: state.output
|
|
637
|
+
mode: "generate",
|
|
638
|
+
runOptions: opts,
|
|
639
|
+
abortSignal: opts?.abortSignal
|
|
281
640
|
};
|
|
641
|
+
await this.execute(state, 0, opts);
|
|
642
|
+
return this.buildResult(state);
|
|
282
643
|
}
|
|
283
644
|
stream(ctx, ...args) {
|
|
645
|
+
this.ensureDuplicateCheck();
|
|
284
646
|
const input = args[0];
|
|
285
647
|
const options = args[1];
|
|
648
|
+
const opts = args[2];
|
|
649
|
+
this.validateRunOptions(opts);
|
|
650
|
+
const abortSignal = opts?.abortSignal;
|
|
286
651
|
let resolveOutput;
|
|
287
652
|
let rejectOutput;
|
|
288
653
|
const outputPromise = new Promise((res, rej) => {
|
|
@@ -297,11 +662,15 @@ var SealedWorkflow = class {
|
|
|
297
662
|
ctx,
|
|
298
663
|
output: input,
|
|
299
664
|
mode: "stream",
|
|
300
|
-
writer
|
|
665
|
+
writer,
|
|
666
|
+
runOptions: opts,
|
|
667
|
+
abortSignal
|
|
301
668
|
};
|
|
302
669
|
try {
|
|
303
|
-
await this.execute(state);
|
|
304
|
-
|
|
670
|
+
await this.execute(state, 0, opts);
|
|
671
|
+
const result = this.buildResult(state);
|
|
672
|
+
maybeWarnStreamOnErrorOnSuspend(result, options);
|
|
673
|
+
resolveOutput(result);
|
|
305
674
|
} catch (error) {
|
|
306
675
|
rejectOutput(error);
|
|
307
676
|
throw error;
|
|
@@ -315,20 +684,78 @@ var SealedWorkflow = class {
|
|
|
315
684
|
output: outputPromise
|
|
316
685
|
};
|
|
317
686
|
}
|
|
687
|
+
// Helper — converts terminal RuntimeState into a WorkflowResult; freezes
|
|
688
|
+
// snapshot + warnings if requested via runOptions.
|
|
689
|
+
buildResult(state) {
|
|
690
|
+
const warnings = state.warnings ?? [];
|
|
691
|
+
if (state.suspension && resolveFreezeSnapshots(state)) {
|
|
692
|
+
deepFreeze(warnings);
|
|
693
|
+
}
|
|
694
|
+
if (state.suspension) {
|
|
695
|
+
return { status: "suspended", snapshot: state.suspension, warnings };
|
|
696
|
+
}
|
|
697
|
+
return { status: "complete", output: state.output, warnings };
|
|
698
|
+
}
|
|
318
699
|
// ── Internal: execute pipeline ────────────────────────────────
|
|
319
|
-
async execute(state, startIndex = 0) {
|
|
700
|
+
async execute(state, startIndex = 0, opts, initialError = null) {
|
|
320
701
|
if (this.steps.length === 0) {
|
|
321
702
|
throw new Error("Workflow has no steps. Add at least one step before calling generate() or stream().");
|
|
322
703
|
}
|
|
323
|
-
|
|
704
|
+
if (opts !== void 0 && state.runOptions === void 0) {
|
|
705
|
+
state.runOptions = opts;
|
|
706
|
+
}
|
|
707
|
+
const ckptCadence = opts?.onCheckpoint && opts.checkpointWhen === void 0 ? opts.checkpointEvery ?? Math.max(1, Math.ceil(this.cachedExecutableStepCount / 4)) : 0;
|
|
708
|
+
let pendingError = initialError;
|
|
709
|
+
let abortPromoted = false;
|
|
710
|
+
const makeAbortError = (signal) => ({
|
|
711
|
+
error: signal.reason ?? new Error("Workflow aborted"),
|
|
712
|
+
stepId: "abort",
|
|
713
|
+
source: "step"
|
|
714
|
+
});
|
|
324
715
|
for (let i = startIndex; i < this.steps.length; i++) {
|
|
716
|
+
if (state.abortSignal?.aborted) {
|
|
717
|
+
if (!abortPromoted) {
|
|
718
|
+
abortPromoted = true;
|
|
719
|
+
state.suspension = void 0;
|
|
720
|
+
if (pendingError) demotePendingError(state, pendingError);
|
|
721
|
+
pendingError = makeAbortError(state.abortSignal);
|
|
722
|
+
} else if (!pendingError) {
|
|
723
|
+
pendingError = makeAbortError(state.abortSignal);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
325
726
|
const node = this.steps[i];
|
|
326
727
|
if (node.type === "finally") {
|
|
327
|
-
|
|
728
|
+
const stepId2 = node.id;
|
|
729
|
+
const finStart = performance.now();
|
|
730
|
+
await this.fireHook(state, "onStepStart", { stepId: stepId2, type: "finally", ctx: state.ctx, input: state.output });
|
|
731
|
+
try {
|
|
732
|
+
await node.execute(state);
|
|
733
|
+
await this.fireHook(state, "onStepFinish", {
|
|
734
|
+
stepId: stepId2,
|
|
735
|
+
type: "finally",
|
|
736
|
+
ctx: state.ctx,
|
|
737
|
+
output: state.output,
|
|
738
|
+
durationMs: performance.now() - finStart,
|
|
739
|
+
suspended: false
|
|
740
|
+
});
|
|
741
|
+
} catch (e) {
|
|
742
|
+
await this.fireHook(state, "onStepError", {
|
|
743
|
+
stepId: stepId2,
|
|
744
|
+
type: "finally",
|
|
745
|
+
ctx: state.ctx,
|
|
746
|
+
error: e,
|
|
747
|
+
durationMs: performance.now() - finStart
|
|
748
|
+
});
|
|
749
|
+
if (pendingError) demotePendingError(state, pendingError);
|
|
750
|
+
pendingError = { error: e, stepId: stepId2, source: "finally" };
|
|
751
|
+
}
|
|
328
752
|
continue;
|
|
329
753
|
}
|
|
330
754
|
if (node.type === "catch") {
|
|
331
|
-
if (!pendingError) continue;
|
|
755
|
+
if (state.suspension || !pendingError || state.checkpointFailed) continue;
|
|
756
|
+
const stepId2 = node.id;
|
|
757
|
+
const cStart = performance.now();
|
|
758
|
+
await this.fireHook(state, "onStepStart", { stepId: stepId2, type: "catch", ctx: state.ctx, input: state.output });
|
|
332
759
|
try {
|
|
333
760
|
state.output = await node.catchFn({
|
|
334
761
|
error: pendingError.error,
|
|
@@ -337,49 +764,183 @@ var SealedWorkflow = class {
|
|
|
337
764
|
stepId: pendingError.stepId
|
|
338
765
|
});
|
|
339
766
|
pendingError = null;
|
|
340
|
-
|
|
341
|
-
|
|
767
|
+
await this.fireHook(state, "onStepFinish", {
|
|
768
|
+
stepId: stepId2,
|
|
769
|
+
type: "catch",
|
|
770
|
+
ctx: state.ctx,
|
|
771
|
+
output: state.output,
|
|
772
|
+
durationMs: performance.now() - cStart,
|
|
773
|
+
suspended: false
|
|
774
|
+
});
|
|
775
|
+
} catch (e) {
|
|
776
|
+
await this.fireHook(state, "onStepError", {
|
|
777
|
+
stepId: stepId2,
|
|
778
|
+
type: "catch",
|
|
779
|
+
ctx: state.ctx,
|
|
780
|
+
error: e,
|
|
781
|
+
durationMs: performance.now() - cStart
|
|
782
|
+
});
|
|
783
|
+
if (pendingError) demotePendingError(state, pendingError);
|
|
784
|
+
pendingError = { error: e, stepId: stepId2, source: "catch" };
|
|
342
785
|
}
|
|
343
786
|
continue;
|
|
344
787
|
}
|
|
788
|
+
if (state.suspension || pendingError) continue;
|
|
345
789
|
if (node.type === "gate") {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
790
|
+
const stepId2 = node.id;
|
|
791
|
+
const gStart = performance.now();
|
|
792
|
+
await this.fireHook(state, "onStepStart", { stepId: stepId2, type: "gate", ctx: state.ctx, input: state.output });
|
|
793
|
+
try {
|
|
794
|
+
if (node.condition && !await node.condition(state)) {
|
|
795
|
+
await this.fireHook(state, "onStepFinish", {
|
|
796
|
+
stepId: stepId2,
|
|
797
|
+
type: "gate",
|
|
798
|
+
ctx: state.ctx,
|
|
799
|
+
output: state.output,
|
|
800
|
+
durationMs: performance.now() - gStart,
|
|
801
|
+
suspended: false
|
|
802
|
+
});
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
const snapshot = {
|
|
806
|
+
version: 2,
|
|
807
|
+
kind: "gate",
|
|
808
|
+
resumeFromIndex: i,
|
|
809
|
+
output: state.output,
|
|
810
|
+
gateId: node.id,
|
|
811
|
+
gatePayload: await node.payload(state)
|
|
812
|
+
};
|
|
813
|
+
state.suspension = snapshot;
|
|
814
|
+
if (resolveFreezeSnapshots(state)) deepFreeze(snapshot);
|
|
815
|
+
await this.fireHook(state, "onStepFinish", {
|
|
816
|
+
stepId: stepId2,
|
|
817
|
+
type: "gate",
|
|
818
|
+
ctx: state.ctx,
|
|
819
|
+
output: state.output,
|
|
820
|
+
durationMs: performance.now() - gStart,
|
|
821
|
+
suspended: true
|
|
822
|
+
});
|
|
823
|
+
} catch (e) {
|
|
824
|
+
pendingError = { error: e, stepId: node.id, source: "step" };
|
|
825
|
+
}
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
const obsType = getObservabilityType(node);
|
|
829
|
+
const stepId = node.id;
|
|
830
|
+
const sStart = performance.now();
|
|
831
|
+
const stepInput = state.output;
|
|
832
|
+
await this.fireHook(state, "onStepStart", { stepId, type: obsType, ctx: state.ctx, input: stepInput });
|
|
833
|
+
try {
|
|
834
|
+
await node.execute(state);
|
|
835
|
+
await this.fireHook(state, "onStepFinish", {
|
|
836
|
+
stepId,
|
|
837
|
+
type: obsType,
|
|
838
|
+
ctx: state.ctx,
|
|
355
839
|
output: state.output,
|
|
356
|
-
|
|
357
|
-
|
|
840
|
+
durationMs: performance.now() - sStart,
|
|
841
|
+
suspended: false
|
|
358
842
|
});
|
|
843
|
+
} catch (e) {
|
|
844
|
+
pendingError = { error: e, stepId: node.id, source: "step" };
|
|
845
|
+
const obsError = await this.fireHook(state, "onStepError", {
|
|
846
|
+
stepId,
|
|
847
|
+
type: obsType,
|
|
848
|
+
ctx: state.ctx,
|
|
849
|
+
error: e,
|
|
850
|
+
durationMs: performance.now() - sStart
|
|
851
|
+
});
|
|
852
|
+
if (obsError !== void 0 && typeof e === "object" && e !== null) {
|
|
853
|
+
try {
|
|
854
|
+
e.cause = obsError;
|
|
855
|
+
} catch {
|
|
856
|
+
}
|
|
857
|
+
}
|
|
359
858
|
}
|
|
360
|
-
|
|
859
|
+
const leaked = state.suspension;
|
|
860
|
+
if (leaked) {
|
|
861
|
+
state.suspension = void 0;
|
|
862
|
+
throw new Error(`internal: suspension bubbled from non-gate step "${node.id}" (gate "${leaked.gateId}").`);
|
|
863
|
+
}
|
|
864
|
+
if (!pendingError && !state.suspension && opts?.onCheckpoint) {
|
|
865
|
+
const shouldCheckpoint = opts.checkpointWhen ? opts.checkpointWhen({ stepIndex: i, stepId: node.id, ctx: state.ctx }) : (i + 1) % ckptCadence === 0;
|
|
866
|
+
if (shouldCheckpoint) {
|
|
867
|
+
const ckptStart = performance.now();
|
|
868
|
+
try {
|
|
869
|
+
await emitCheckpoint(
|
|
870
|
+
state,
|
|
871
|
+
opts,
|
|
872
|
+
i + 1,
|
|
873
|
+
this.cachedStepShapeHash
|
|
874
|
+
);
|
|
875
|
+
} catch (e) {
|
|
876
|
+
pendingError = { error: e, stepId: CHECKPOINT_STEP_ID, source: "onCheckpoint" };
|
|
877
|
+
state.checkpointFailed = true;
|
|
878
|
+
await this.fireHook(state, "onStepError", {
|
|
879
|
+
stepId: CHECKPOINT_STEP_ID,
|
|
880
|
+
type: "step",
|
|
881
|
+
ctx: state.ctx,
|
|
882
|
+
error: e,
|
|
883
|
+
durationMs: performance.now() - ckptStart
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (pendingError && !state.suspension) {
|
|
890
|
+
if (state.checkpointFailed) {
|
|
891
|
+
const warningsArr = state.warnings ?? [];
|
|
892
|
+
const checkpointError = pendingError.source === "onCheckpoint" ? pendingError.error : warningsArr.find((w) => w.source === "onCheckpoint")?.error;
|
|
893
|
+
const finallyErrors = warningsArr.filter((w) => w.source === "finally").map((w) => w.error);
|
|
894
|
+
const all = pendingError.source === "finally" ? [...finallyErrors, pendingError.error] : finallyErrors;
|
|
895
|
+
if (all.length > 0) {
|
|
896
|
+
console.warn(
|
|
897
|
+
`pipeai: ${all.length} .finally() error(s) suppressed by checkpoint-failure precedence:`,
|
|
898
|
+
all
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
throw checkpointError ?? pendingError.error;
|
|
902
|
+
}
|
|
903
|
+
const isFinallyPath = pendingError.source === "finally" || (state.warnings?.some((w) => w.source === "finally") ?? false);
|
|
904
|
+
if (isFinallyPath) {
|
|
905
|
+
const all = [...(state.warnings ?? []).map((w) => w.error), pendingError.error];
|
|
906
|
+
throw new AggregateError(all, `Workflow failed with ${all.length} error(s) from .finally() bodies`);
|
|
907
|
+
}
|
|
908
|
+
throw pendingError.error;
|
|
909
|
+
} else if (pendingError && state.suspension) {
|
|
910
|
+
demotePendingError(state, pendingError);
|
|
361
911
|
try {
|
|
362
|
-
await
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
912
|
+
await this.observability?.onStepError?.({
|
|
913
|
+
stepId: pendingError.stepId,
|
|
914
|
+
type: pendingErrorSourceToStepType(pendingError.source),
|
|
915
|
+
ctx: state.ctx,
|
|
916
|
+
error: pendingError.error,
|
|
917
|
+
durationMs: 0
|
|
918
|
+
});
|
|
919
|
+
} catch (obsError) {
|
|
920
|
+
pushWarning(state, "onStepError", pendingError.stepId, obsError);
|
|
366
921
|
}
|
|
922
|
+
pendingError = null;
|
|
367
923
|
}
|
|
368
|
-
if (pendingError) throw pendingError.error;
|
|
369
924
|
}
|
|
370
925
|
// ── Internal: execute a nested workflow within a step/loop ─────
|
|
371
926
|
// Defined on SealedWorkflow (not Workflow) because TypeScript's protected
|
|
372
927
|
// access rules only allow calling workflow.execute() from the same class.
|
|
928
|
+
//
|
|
929
|
+
// Contract: clears any inner suspension before re-throwing as
|
|
930
|
+
// NestedGateUnsupportedError. The outer execute() therefore never observes
|
|
931
|
+
// a leaked `state.suspension` from non-gate nodes (defensive invariant).
|
|
373
932
|
async executeNestedWorkflow(state, workflow) {
|
|
933
|
+
const savedRunOptions = state.runOptions;
|
|
934
|
+
state.runOptions = void 0;
|
|
374
935
|
try {
|
|
375
936
|
await workflow.execute(state);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
throw
|
|
937
|
+
} finally {
|
|
938
|
+
state.runOptions = savedRunOptions;
|
|
939
|
+
}
|
|
940
|
+
if (state.suspension) {
|
|
941
|
+
const gateId = state.suspension.gateId;
|
|
942
|
+
state.suspension = void 0;
|
|
943
|
+
throw new NestedGateUnsupportedError(gateId, workflow.id);
|
|
383
944
|
}
|
|
384
945
|
}
|
|
385
946
|
// ── Internal: execute an agent within a step/branch ───────────
|
|
@@ -389,59 +950,141 @@ var SealedWorkflow = class {
|
|
|
389
950
|
async executeAgent(state, agent, ctx, options) {
|
|
390
951
|
const input = state.output;
|
|
391
952
|
const hasStructuredOutput = agent.hasOutput;
|
|
953
|
+
const abortSignal = state.abortSignal;
|
|
954
|
+
const agentCallOpts = abortSignal ? { abortSignal } : void 0;
|
|
392
955
|
if (state.mode === "stream" && state.writer) {
|
|
393
956
|
const writer = state.writer;
|
|
394
957
|
await runWithWriter(writer, async () => {
|
|
395
|
-
const result = await agent.stream(ctx, state.output);
|
|
958
|
+
const result = await agent.stream(ctx, state.output, agentCallOpts);
|
|
396
959
|
if (options?.handleStream) {
|
|
397
960
|
await options.handleStream({ result, writer, ctx });
|
|
398
961
|
} else {
|
|
399
962
|
writer.merge(result.toUIMessageStream());
|
|
400
963
|
}
|
|
401
|
-
|
|
402
|
-
|
|
964
|
+
const hookParams = {
|
|
965
|
+
mode: "stream",
|
|
966
|
+
result,
|
|
967
|
+
ctx,
|
|
968
|
+
input
|
|
969
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
970
|
+
};
|
|
971
|
+
if (options?.onResult) {
|
|
972
|
+
await options.onResult(hookParams);
|
|
403
973
|
}
|
|
404
|
-
if (options?.
|
|
405
|
-
state.output = await options.
|
|
974
|
+
if (options?.mapResult) {
|
|
975
|
+
state.output = await options.mapResult(hookParams);
|
|
406
976
|
} else {
|
|
407
|
-
state.output = await extractOutput(result, hasStructuredOutput);
|
|
977
|
+
state.output = await extractOutput(result, hasStructuredOutput, agent.validateOutput);
|
|
408
978
|
}
|
|
409
979
|
});
|
|
410
980
|
} else {
|
|
411
|
-
const result = await agent.generate(ctx, state.output);
|
|
412
|
-
|
|
413
|
-
|
|
981
|
+
const result = await agent.generate(ctx, state.output, agentCallOpts);
|
|
982
|
+
const hookParams = {
|
|
983
|
+
mode: "generate",
|
|
984
|
+
result,
|
|
985
|
+
ctx,
|
|
986
|
+
input
|
|
987
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
988
|
+
};
|
|
989
|
+
if (options?.onResult) {
|
|
990
|
+
await options.onResult(hookParams);
|
|
414
991
|
}
|
|
415
|
-
if (options?.
|
|
416
|
-
state.output = await options.
|
|
992
|
+
if (options?.mapResult) {
|
|
993
|
+
state.output = await options.mapResult(hookParams);
|
|
417
994
|
} else {
|
|
418
|
-
state.output = await extractOutput(result, hasStructuredOutput);
|
|
995
|
+
state.output = await extractOutput(result, hasStructuredOutput, agent.validateOutput);
|
|
419
996
|
}
|
|
420
997
|
}
|
|
421
998
|
}
|
|
422
999
|
// ── Gate: load persisted state for resumption ──────────────────
|
|
423
1000
|
loadState(gateId, snapshot) {
|
|
424
|
-
if (snapshot.
|
|
1001
|
+
if (snapshot.version === 2 && snapshot.kind === "checkpoint") {
|
|
1002
|
+
throw new Error(`loadState: received a checkpoint snapshot. Use resumeFrom() for checkpoint resume; loadState() is for gates.`);
|
|
1003
|
+
}
|
|
1004
|
+
const gateLike = snapshot;
|
|
1005
|
+
if (gateLike.gateId !== gateId) {
|
|
425
1006
|
throw new Error(
|
|
426
|
-
`loadState: gate ID mismatch \u2014 expected "${gateId}" but snapshot has "${
|
|
1007
|
+
`loadState: gate ID mismatch \u2014 expected "${gateId}" but snapshot has "${gateLike.gateId}".`
|
|
427
1008
|
);
|
|
428
1009
|
}
|
|
429
|
-
|
|
1010
|
+
this.ensureDuplicateCheck();
|
|
1011
|
+
const gateIndex = this.findGateIndex(gateLike);
|
|
430
1012
|
const gateNode = this.steps[gateIndex];
|
|
431
|
-
return new ResumedWorkflow(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
gateNode.
|
|
435
|
-
|
|
436
|
-
snapshot
|
|
437
|
-
|
|
1013
|
+
return new ResumedWorkflow(this.steps, gateIndex + 1, {
|
|
1014
|
+
mode: "gate",
|
|
1015
|
+
schema: gateNode.schema,
|
|
1016
|
+
mergeFn: gateNode.merge,
|
|
1017
|
+
priorOutput: gateLike.output,
|
|
1018
|
+
snapshot: gateLike,
|
|
1019
|
+
observability: this.observability
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
// ── Checkpoint resume ──────────────────────────────────────────
|
|
1023
|
+
/**
|
|
1024
|
+
* Resume from a checkpoint snapshot. Validates the step-shape hash unless
|
|
1025
|
+
* `{ skipShapeCheck: true }` is passed. Throws on:
|
|
1026
|
+
* - gate snapshots (use `loadState` instead)
|
|
1027
|
+
* - missing/corrupted `stepShapeHash`
|
|
1028
|
+
* - shape mismatch (unless skipped)
|
|
1029
|
+
* - out-of-bounds `resumeFromIndex`
|
|
1030
|
+
* - 0-step workflow (structural invariant)
|
|
1031
|
+
*
|
|
1032
|
+
* Returns a `CheckpointResumedWorkflow` whose `generate(ctx, opts?)` takes
|
|
1033
|
+
* NO response arg — the state is seeded from the snapshot's output. The
|
|
1034
|
+
* matching gate-resume path (`loadState`) keeps the `response` arg.
|
|
1035
|
+
*/
|
|
1036
|
+
resumeFrom(snapshot, options) {
|
|
1037
|
+
const isGate = snapshot.version === 2 && snapshot.kind === "gate" || snapshot.version === 1 && snapshot.gateId !== void 0;
|
|
1038
|
+
if (isGate) {
|
|
1039
|
+
throw new Error(`resumeFrom: received a gate snapshot. Use loadState() for gate resume; resumeFrom() is for checkpoints.`);
|
|
1040
|
+
}
|
|
1041
|
+
if (this.steps.length === 0) {
|
|
1042
|
+
throw new Error("resumeFrom: workflow has no steps; snapshot is structurally invalid.");
|
|
1043
|
+
}
|
|
1044
|
+
const ckpt = snapshot;
|
|
1045
|
+
const idx = ckpt.resumeFromIndex;
|
|
1046
|
+
if (!Number.isInteger(idx) || idx < 0 || idx > this.steps.length) {
|
|
1047
|
+
throw new Error(`resumeFrom: resumeFromIndex (${idx}) out of bounds for ${this.steps.length}-step workflow.`);
|
|
1048
|
+
}
|
|
1049
|
+
if (!options?.skipShapeCheck) {
|
|
1050
|
+
if (!ckpt.stepShapeHash) {
|
|
1051
|
+
throw new Error("resumeFrom: snapshot missing stepShapeHash; corrupted or hand-crafted.");
|
|
1052
|
+
}
|
|
1053
|
+
this.ensureDuplicateCheck();
|
|
1054
|
+
if (this.cachedStepShapeHash !== ckpt.stepShapeHash) {
|
|
1055
|
+
throw new Error("resumeFrom: workflow shape mismatch; cannot safely resume. Pass { skipShapeCheck: true } to override.");
|
|
1056
|
+
}
|
|
1057
|
+
} else {
|
|
1058
|
+
this.ensureDuplicateCheck();
|
|
1059
|
+
}
|
|
1060
|
+
return new CheckpointResumedWorkflow(this.steps, idx, {
|
|
1061
|
+
mode: "checkpoint",
|
|
1062
|
+
priorOutput: ckpt.output,
|
|
1063
|
+
snapshot: ckpt,
|
|
1064
|
+
observability: this.observability
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Append a `.finally()` body to a sealed workflow, returning another sealed
|
|
1069
|
+
* workflow. Allows multi-finally chains (`.finally().finally()`). A throwing
|
|
1070
|
+
* `.finally` body does NOT abort subsequent ones — they all run.
|
|
1071
|
+
*/
|
|
1072
|
+
finally(id, fn) {
|
|
1073
|
+
const node = {
|
|
1074
|
+
type: "finally",
|
|
1075
|
+
id,
|
|
1076
|
+
execute: async (state) => {
|
|
1077
|
+
await fn({ ctx: state.ctx });
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
return new _SealedWorkflow([...this.steps, node], this.id, this.observability);
|
|
438
1081
|
}
|
|
439
1082
|
findGateIndex(snapshot) {
|
|
440
|
-
if (snapshot.version !== 1) {
|
|
1083
|
+
if (snapshot.version !== 1 && snapshot.version !== 2) {
|
|
441
1084
|
throw new Error(`Unsupported snapshot version: ${snapshot.version}`);
|
|
442
1085
|
}
|
|
443
1086
|
const hint = snapshot.resumeFromIndex;
|
|
444
|
-
if (hint >= 0 && hint < this.steps.length) {
|
|
1087
|
+
if (Number.isInteger(hint) && hint >= 0 && hint < this.steps.length) {
|
|
445
1088
|
const node = this.steps[hint];
|
|
446
1089
|
if (node.type === "gate" && node.id === snapshot.gateId) {
|
|
447
1090
|
return hint;
|
|
@@ -464,12 +1107,12 @@ var ResumedWorkflow = class extends SealedWorkflow {
|
|
|
464
1107
|
mergeFn;
|
|
465
1108
|
priorOutput;
|
|
466
1109
|
/** @internal */
|
|
467
|
-
constructor(steps, startIndex,
|
|
468
|
-
super(steps);
|
|
1110
|
+
constructor(steps, startIndex, config) {
|
|
1111
|
+
super(steps, void 0, config.observability);
|
|
469
1112
|
this.startIndex = startIndex;
|
|
470
|
-
this.schema = schema;
|
|
471
|
-
this.mergeFn = mergeFn;
|
|
472
|
-
this.priorOutput = priorOutput;
|
|
1113
|
+
this.schema = config.schema;
|
|
1114
|
+
this.mergeFn = config.mergeFn;
|
|
1115
|
+
this.priorOutput = config.priorOutput;
|
|
473
1116
|
}
|
|
474
1117
|
validateResponse(response) {
|
|
475
1118
|
if (this.schema) {
|
|
@@ -478,15 +1121,31 @@ var ResumedWorkflow = class extends SealedWorkflow {
|
|
|
478
1121
|
return response;
|
|
479
1122
|
}
|
|
480
1123
|
async generate(ctx, ...args) {
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
1124
|
+
const rawResponse = args[0];
|
|
1125
|
+
const opts = args[1];
|
|
1126
|
+
let output = this.priorOutput;
|
|
1127
|
+
let initialError = null;
|
|
1128
|
+
try {
|
|
1129
|
+
const response = this.validateResponse(rawResponse);
|
|
1130
|
+
output = this.mergeFn ? await this.mergeFn({ priorOutput: this.priorOutput, response }) : response;
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
initialError = { error, stepId: "gate:resume", source: "step" };
|
|
1133
|
+
}
|
|
1134
|
+
const state = {
|
|
1135
|
+
ctx,
|
|
1136
|
+
output,
|
|
1137
|
+
mode: "generate",
|
|
1138
|
+
runOptions: opts,
|
|
1139
|
+
abortSignal: opts?.abortSignal
|
|
1140
|
+
};
|
|
1141
|
+
await this.execute(state, this.startIndex, opts, initialError);
|
|
1142
|
+
return this.buildResult(state);
|
|
486
1143
|
}
|
|
487
1144
|
stream(ctx, ...args) {
|
|
488
|
-
const
|
|
1145
|
+
const rawResponse = args[0];
|
|
489
1146
|
const options = args[1];
|
|
1147
|
+
const opts = args[2];
|
|
1148
|
+
const abortSignal = opts?.abortSignal;
|
|
490
1149
|
let resolveOutput;
|
|
491
1150
|
let rejectOutput;
|
|
492
1151
|
const outputPromise = new Promise((res, rej) => {
|
|
@@ -497,18 +1156,92 @@ var ResumedWorkflow = class extends SealedWorkflow {
|
|
|
497
1156
|
});
|
|
498
1157
|
const mergeFn = this.mergeFn;
|
|
499
1158
|
const priorOutput = this.priorOutput;
|
|
1159
|
+
const startIndex = this.startIndex;
|
|
500
1160
|
const stream = createUIMessageStream({
|
|
501
1161
|
execute: async ({ writer }) => {
|
|
502
|
-
|
|
1162
|
+
let output = priorOutput;
|
|
1163
|
+
let initialError = null;
|
|
1164
|
+
try {
|
|
1165
|
+
const response = this.validateResponse(rawResponse);
|
|
1166
|
+
output = mergeFn ? await mergeFn({ priorOutput, response }) : response;
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
initialError = { error, stepId: "gate:resume", source: "step" };
|
|
1169
|
+
}
|
|
503
1170
|
const state = {
|
|
504
1171
|
ctx,
|
|
505
1172
|
output,
|
|
506
1173
|
mode: "stream",
|
|
507
|
-
writer
|
|
1174
|
+
writer,
|
|
1175
|
+
runOptions: opts,
|
|
1176
|
+
abortSignal
|
|
1177
|
+
};
|
|
1178
|
+
try {
|
|
1179
|
+
await this.execute(state, startIndex, opts, initialError);
|
|
1180
|
+
const result = this.buildResult(state);
|
|
1181
|
+
maybeWarnStreamOnErrorOnSuspend(result, options);
|
|
1182
|
+
resolveOutput(result);
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
rejectOutput(error);
|
|
1185
|
+
throw error;
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1188
|
+
...options?.onError ? { onError: options.onError } : {},
|
|
1189
|
+
...options?.onFinish ? { onFinish: options.onFinish } : {}
|
|
1190
|
+
});
|
|
1191
|
+
return { stream, output: outputPromise };
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
var CheckpointResumedWorkflow = class extends SealedWorkflow {
|
|
1195
|
+
startIndex;
|
|
1196
|
+
priorOutput;
|
|
1197
|
+
/** @internal */
|
|
1198
|
+
constructor(steps, startIndex, config) {
|
|
1199
|
+
super(steps, void 0, config.observability);
|
|
1200
|
+
this.startIndex = startIndex;
|
|
1201
|
+
this.priorOutput = config.priorOutput;
|
|
1202
|
+
}
|
|
1203
|
+
// Override with widened arg list compatible with parent's `[input?, opts?]`.
|
|
1204
|
+
// Inputs are ignored — state is seeded from the snapshot's `output` field.
|
|
1205
|
+
async generate(ctx, ...args) {
|
|
1206
|
+
const opts = args[1];
|
|
1207
|
+
this.validateRunOptions(opts);
|
|
1208
|
+
const state = {
|
|
1209
|
+
ctx,
|
|
1210
|
+
output: this.priorOutput,
|
|
1211
|
+
mode: "generate",
|
|
1212
|
+
runOptions: opts
|
|
1213
|
+
};
|
|
1214
|
+
await this.execute(state, this.startIndex, opts);
|
|
1215
|
+
return this.buildResult(state);
|
|
1216
|
+
}
|
|
1217
|
+
stream(ctx, ...args) {
|
|
1218
|
+
const options = args[1];
|
|
1219
|
+
const opts = args[2];
|
|
1220
|
+
this.validateRunOptions(opts);
|
|
1221
|
+
let resolveOutput;
|
|
1222
|
+
let rejectOutput;
|
|
1223
|
+
const outputPromise = new Promise((res, rej) => {
|
|
1224
|
+
resolveOutput = res;
|
|
1225
|
+
rejectOutput = rej;
|
|
1226
|
+
});
|
|
1227
|
+
outputPromise.catch(() => {
|
|
1228
|
+
});
|
|
1229
|
+
const priorOutput = this.priorOutput;
|
|
1230
|
+
const startIndex = this.startIndex;
|
|
1231
|
+
const stream = createUIMessageStream({
|
|
1232
|
+
execute: async ({ writer }) => {
|
|
1233
|
+
const state = {
|
|
1234
|
+
ctx,
|
|
1235
|
+
output: priorOutput,
|
|
1236
|
+
mode: "stream",
|
|
1237
|
+
writer,
|
|
1238
|
+
runOptions: opts
|
|
508
1239
|
};
|
|
509
1240
|
try {
|
|
510
|
-
await this.execute(state,
|
|
511
|
-
|
|
1241
|
+
await this.execute(state, startIndex, opts);
|
|
1242
|
+
const result = this.buildResult(state);
|
|
1243
|
+
maybeWarnStreamOnErrorOnSuspend(result, options);
|
|
1244
|
+
resolveOutput(result);
|
|
512
1245
|
} catch (error) {
|
|
513
1246
|
rejectOutput(error);
|
|
514
1247
|
throw error;
|
|
@@ -521,15 +1254,27 @@ var ResumedWorkflow = class extends SealedWorkflow {
|
|
|
521
1254
|
}
|
|
522
1255
|
};
|
|
523
1256
|
var Workflow = class _Workflow extends SealedWorkflow {
|
|
524
|
-
|
|
525
|
-
|
|
1257
|
+
/**
|
|
1258
|
+
* Sentinel value for `foreach`'s `onError` handler. Returning `Workflow.SKIP`
|
|
1259
|
+
* from `onError` omits the failed item's index from the output array,
|
|
1260
|
+
* shortening it relative to the input array.
|
|
1261
|
+
*/
|
|
1262
|
+
static SKIP = /* @__PURE__ */ Symbol("pipeai.foreach.skip");
|
|
1263
|
+
constructor(steps = [], id, observability) {
|
|
1264
|
+
super(steps, id, observability);
|
|
526
1265
|
}
|
|
527
1266
|
static create(options) {
|
|
528
|
-
return new _Workflow([], options?.id);
|
|
1267
|
+
return new _Workflow([], options?.id, options?.observability);
|
|
529
1268
|
}
|
|
530
1269
|
static from(agent, options) {
|
|
531
1270
|
return new _Workflow([]).step(agent, options);
|
|
532
1271
|
}
|
|
1272
|
+
// Builder helper — append a step and return a re-typed Workflow.
|
|
1273
|
+
// Centralizes the `[...steps, node] as any` + new Workflow + observability/id
|
|
1274
|
+
// forwarding pattern used by every combinator method.
|
|
1275
|
+
appendStep(node) {
|
|
1276
|
+
return new _Workflow([...this.steps, node], this.id, this.observability);
|
|
1277
|
+
}
|
|
533
1278
|
// ── step: implementation ──────────────────────────────────────
|
|
534
1279
|
step(target, optionsOrFn) {
|
|
535
1280
|
if (target instanceof SealedWorkflow) {
|
|
@@ -537,11 +1282,15 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
537
1282
|
const node2 = {
|
|
538
1283
|
type: "step",
|
|
539
1284
|
id: workflow.id ?? "nested-workflow",
|
|
1285
|
+
nestedWorkflow: workflow,
|
|
1286
|
+
// Feeds the recursive stepShapeHash walk.
|
|
1287
|
+
category: "nested",
|
|
1288
|
+
// Observability event type.
|
|
540
1289
|
execute: async (state) => {
|
|
541
1290
|
await this.executeNestedWorkflow(state, workflow);
|
|
542
1291
|
}
|
|
543
1292
|
};
|
|
544
|
-
return
|
|
1293
|
+
return this.appendStep(node2);
|
|
545
1294
|
}
|
|
546
1295
|
if (typeof target === "string") {
|
|
547
1296
|
if (typeof optionsOrFn !== "function") {
|
|
@@ -558,19 +1307,19 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
558
1307
|
});
|
|
559
1308
|
}
|
|
560
1309
|
};
|
|
561
|
-
return
|
|
1310
|
+
return this.appendStep(node2);
|
|
562
1311
|
}
|
|
563
1312
|
const agent = target;
|
|
564
1313
|
const options = optionsOrFn;
|
|
565
1314
|
const node = {
|
|
566
1315
|
type: "step",
|
|
567
|
-
id: agent.id,
|
|
1316
|
+
id: options?.id ?? agent.id,
|
|
568
1317
|
execute: async (state) => {
|
|
569
1318
|
const ctx = state.ctx;
|
|
570
1319
|
await this.executeAgent(state, agent, ctx, options);
|
|
571
1320
|
}
|
|
572
1321
|
};
|
|
573
|
-
return
|
|
1322
|
+
return this.appendStep(node);
|
|
574
1323
|
}
|
|
575
1324
|
// ── gate: human-in-the-loop suspension point ────────────────
|
|
576
1325
|
gate(id, options) {
|
|
@@ -596,19 +1345,20 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
596
1345
|
return state.output;
|
|
597
1346
|
}
|
|
598
1347
|
};
|
|
599
|
-
return
|
|
1348
|
+
return this.appendStep(node);
|
|
600
1349
|
}
|
|
601
1350
|
// ── branch: implementation ────────────────────────────────────
|
|
602
|
-
branch(casesOrConfig) {
|
|
1351
|
+
branch(casesOrConfig, options) {
|
|
603
1352
|
if (Array.isArray(casesOrConfig)) {
|
|
604
|
-
return this.branchPredicate(casesOrConfig);
|
|
1353
|
+
return this.branchPredicate(casesOrConfig, options?.id);
|
|
605
1354
|
}
|
|
606
|
-
return this.branchSelect(casesOrConfig);
|
|
1355
|
+
return this.branchSelect(casesOrConfig, options?.id);
|
|
607
1356
|
}
|
|
608
|
-
branchPredicate(cases) {
|
|
1357
|
+
branchPredicate(cases, explicitId) {
|
|
609
1358
|
const node = {
|
|
610
1359
|
type: "step",
|
|
611
|
-
id: "branch:predicate",
|
|
1360
|
+
id: explicitId ?? "branch:predicate",
|
|
1361
|
+
category: "branch",
|
|
612
1362
|
execute: async (state) => {
|
|
613
1363
|
const ctx = state.ctx;
|
|
614
1364
|
const input = state.output;
|
|
@@ -620,21 +1370,43 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
620
1370
|
await this.executeAgent(state, branchCase.agent, ctx, branchCase);
|
|
621
1371
|
return;
|
|
622
1372
|
}
|
|
623
|
-
|
|
1373
|
+
let inputRepr;
|
|
1374
|
+
try {
|
|
1375
|
+
inputRepr = JSON.stringify(input);
|
|
1376
|
+
if (inputRepr === void 0) inputRepr = String(input);
|
|
1377
|
+
} catch {
|
|
1378
|
+
inputRepr = `[unserializable ${typeof input}]`;
|
|
1379
|
+
}
|
|
1380
|
+
throw new WorkflowBranchError("predicate", `No branch matched and no default branch (a case without \`when\`) was provided. Input: ${inputRepr}`);
|
|
624
1381
|
}
|
|
625
1382
|
};
|
|
626
|
-
return
|
|
1383
|
+
return this.appendStep(node);
|
|
627
1384
|
}
|
|
628
|
-
branchSelect(config) {
|
|
1385
|
+
branchSelect(config, explicitId) {
|
|
629
1386
|
const node = {
|
|
630
1387
|
type: "step",
|
|
631
|
-
id: "branch:select",
|
|
1388
|
+
id: explicitId ?? "branch:select",
|
|
1389
|
+
category: "branch",
|
|
632
1390
|
execute: async (state) => {
|
|
633
1391
|
const ctx = state.ctx;
|
|
634
1392
|
const input = state.output;
|
|
635
1393
|
const key = await config.select({ ctx, input });
|
|
636
|
-
|
|
1394
|
+
const keyDeclared = Object.prototype.hasOwnProperty.call(config.agents, key);
|
|
1395
|
+
if (keyDeclared && config.agents[key] === void 0) {
|
|
1396
|
+
throw new WorkflowBranchError(
|
|
1397
|
+
"select",
|
|
1398
|
+
`Agent for key "${key}" was declared but the value is undefined. This usually means a conditional spread set the value to undefined. Available keys: ${Object.keys(config.agents).join(", ")}`
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
let agent = keyDeclared ? config.agents[key] : void 0;
|
|
637
1402
|
if (!agent) {
|
|
1403
|
+
if (config.onUnknownKey) {
|
|
1404
|
+
config.onUnknownKey({
|
|
1405
|
+
key,
|
|
1406
|
+
availableKeys: Object.keys(config.agents),
|
|
1407
|
+
ctx
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
638
1410
|
if (config.fallback) {
|
|
639
1411
|
agent = config.fallback;
|
|
640
1412
|
} else {
|
|
@@ -644,16 +1416,37 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
644
1416
|
await this.executeAgent(state, agent, ctx, config);
|
|
645
1417
|
}
|
|
646
1418
|
};
|
|
647
|
-
return
|
|
1419
|
+
return this.appendStep(node);
|
|
648
1420
|
}
|
|
649
1421
|
// ── foreach: array iteration ─────────────────────────────────
|
|
1422
|
+
/**
|
|
1423
|
+
* Map each item of an array through an agent or sub-workflow.
|
|
1424
|
+
*
|
|
1425
|
+
* @param target Agent or `SealedWorkflow` invoked once per item.
|
|
1426
|
+
* @param options.id Override the default step id (`foreach:<agentId>` or
|
|
1427
|
+
* the workflow's id). Required when chaining multiple foreach over the same
|
|
1428
|
+
* target — the construction-time `(type, id)` walk rejects duplicates.
|
|
1429
|
+
* @param options.concurrency Max items in flight at any moment (default 1).
|
|
1430
|
+
* Backed by a semaphore: as soon as one item completes, the next launches —
|
|
1431
|
+
* no lockstep batching.
|
|
1432
|
+
* @param options.onError Per-iteration error handler. **Bypassed entirely on
|
|
1433
|
+
* the suspension path** (when any item hits a nested gate) — see the
|
|
1434
|
+
* foreach concurrency hazards in the README. Otherwise: return a
|
|
1435
|
+
* `TNextOutput` value to substitute, return `Workflow.SKIP` to omit, throw
|
|
1436
|
+
* to abort. Invoked sequentially in index order after all items settle.
|
|
1437
|
+
*/
|
|
650
1438
|
foreach(target, options) {
|
|
651
1439
|
const concurrency = options?.concurrency ?? 1;
|
|
1440
|
+
const onError = options?.onError;
|
|
652
1441
|
const isWorkflow = target instanceof SealedWorkflow;
|
|
653
|
-
const
|
|
1442
|
+
const defaultId = isWorkflow ? target.id ?? "foreach" : `foreach:${target.id}`;
|
|
1443
|
+
const id = options?.id ?? defaultId;
|
|
654
1444
|
const node = {
|
|
655
1445
|
type: "step",
|
|
656
1446
|
id,
|
|
1447
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1448
|
+
nestedWorkflow: isWorkflow ? target : void 0,
|
|
1449
|
+
category: "foreach",
|
|
657
1450
|
execute: async (state) => {
|
|
658
1451
|
const items = state.output;
|
|
659
1452
|
if (!Array.isArray(items)) {
|
|
@@ -661,42 +1454,312 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
661
1454
|
}
|
|
662
1455
|
const ctx = state.ctx;
|
|
663
1456
|
const results = new Array(items.length);
|
|
1457
|
+
const skipped = /* @__PURE__ */ new Set();
|
|
1458
|
+
const itemStates = new Array(items.length);
|
|
664
1459
|
const executeItem = async (item, index) => {
|
|
665
|
-
const itemState = {
|
|
666
|
-
|
|
667
|
-
|
|
1460
|
+
const itemState = {
|
|
1461
|
+
ctx: state.ctx,
|
|
1462
|
+
output: item,
|
|
1463
|
+
mode: "generate",
|
|
1464
|
+
abortSignal: state.abortSignal
|
|
1465
|
+
};
|
|
1466
|
+
itemStates[index] = itemState;
|
|
1467
|
+
const itemStart = performance.now();
|
|
1468
|
+
await this.fireHook(state, "onItemStart", {
|
|
1469
|
+
stepId: id,
|
|
1470
|
+
type: "foreach",
|
|
1471
|
+
itemIndex: index,
|
|
1472
|
+
ctx: state.ctx,
|
|
1473
|
+
input: item
|
|
1474
|
+
});
|
|
1475
|
+
try {
|
|
1476
|
+
if (isWorkflow) {
|
|
1477
|
+
await this.executeNestedWorkflow(itemState, target);
|
|
1478
|
+
} else {
|
|
1479
|
+
await this.executeAgent(itemState, target, ctx);
|
|
1480
|
+
}
|
|
1481
|
+
results[index] = itemState.output;
|
|
1482
|
+
await this.fireHook(state, "onItemFinish", {
|
|
1483
|
+
stepId: id,
|
|
1484
|
+
type: "foreach",
|
|
1485
|
+
itemIndex: index,
|
|
1486
|
+
ctx: state.ctx,
|
|
1487
|
+
output: itemState.output,
|
|
1488
|
+
durationMs: performance.now() - itemStart
|
|
1489
|
+
});
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
await this.fireHook(state, "onItemError", {
|
|
1492
|
+
stepId: id,
|
|
1493
|
+
type: "foreach",
|
|
1494
|
+
itemIndex: index,
|
|
1495
|
+
ctx: state.ctx,
|
|
1496
|
+
error,
|
|
1497
|
+
durationMs: performance.now() - itemStart
|
|
1498
|
+
});
|
|
1499
|
+
throw error;
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1502
|
+
const mergeItemWarnings = () => {
|
|
1503
|
+
for (let idx = 0; idx < items.length; idx++) {
|
|
1504
|
+
const its = itemStates[idx];
|
|
1505
|
+
if (!its?.warnings) continue;
|
|
1506
|
+
for (const w of its.warnings) {
|
|
1507
|
+
pushWarning(state, w.source, `${id}[${idx}]:${w.stepId}`, w.error);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
const handleRejection = async (error, item, index) => {
|
|
1512
|
+
if (!onError) throw error;
|
|
1513
|
+
const recovered = await onError({
|
|
1514
|
+
error,
|
|
1515
|
+
item,
|
|
1516
|
+
index,
|
|
1517
|
+
ctx: state.ctx
|
|
1518
|
+
});
|
|
1519
|
+
if (recovered === _Workflow.SKIP) {
|
|
1520
|
+
skipped.add(index);
|
|
668
1521
|
} else {
|
|
669
|
-
|
|
1522
|
+
results[index] = recovered;
|
|
670
1523
|
}
|
|
671
|
-
results[index] = itemState.output;
|
|
672
1524
|
};
|
|
1525
|
+
const failures = [];
|
|
1526
|
+
const signal = state.abortSignal;
|
|
673
1527
|
if (concurrency <= 1) {
|
|
674
1528
|
for (let i = 0; i < items.length; i++) {
|
|
675
|
-
|
|
1529
|
+
if (signal?.aborted) {
|
|
1530
|
+
failures.push({ index: i, error: signal.reason ?? new Error("Workflow aborted") });
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
try {
|
|
1534
|
+
await executeItem(items[i], i);
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
failures.push({ index: i, error });
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
} else {
|
|
1540
|
+
let nextIndex = 0;
|
|
1541
|
+
const worker = async () => {
|
|
1542
|
+
while (true) {
|
|
1543
|
+
const i = nextIndex++;
|
|
1544
|
+
if (i >= items.length) return;
|
|
1545
|
+
if (signal?.aborted) {
|
|
1546
|
+
failures.push({ index: i, error: signal.reason ?? new Error("Workflow aborted") });
|
|
1547
|
+
continue;
|
|
1548
|
+
}
|
|
1549
|
+
try {
|
|
1550
|
+
await executeItem(items[i], i);
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
failures.push({ index: i, error });
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
const workers = Array.from(
|
|
1557
|
+
{ length: Math.min(concurrency, items.length) },
|
|
1558
|
+
() => worker()
|
|
1559
|
+
);
|
|
1560
|
+
await Promise.all(workers);
|
|
1561
|
+
}
|
|
1562
|
+
failures.sort((a, b) => a.index - b.index);
|
|
1563
|
+
const gateFailures = [];
|
|
1564
|
+
const nonGateFailures = [];
|
|
1565
|
+
for (const f of failures) {
|
|
1566
|
+
if (f.error instanceof NestedGateUnsupportedError) {
|
|
1567
|
+
gateFailures.push({ index: f.index, error: f.error });
|
|
1568
|
+
} else {
|
|
1569
|
+
nonGateFailures.push(f);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
mergeItemWarnings();
|
|
1573
|
+
if (gateFailures.length > 0) {
|
|
1574
|
+
for (const nr of nonGateFailures) {
|
|
1575
|
+
pushWarning(state, "foreach-sibling", `${id}[${nr.index}]`, nr.error);
|
|
1576
|
+
}
|
|
1577
|
+
const lowest = gateFailures[0];
|
|
1578
|
+
const otherSuspensions = gateFailures.slice(1).map((g) => ({
|
|
1579
|
+
index: g.index,
|
|
1580
|
+
gateId: g.error.gateId
|
|
1581
|
+
}));
|
|
1582
|
+
const siblingErrors = nonGateFailures.map((nr) => nr.error);
|
|
1583
|
+
throw new NestedGateUnsupportedError(
|
|
1584
|
+
lowest.error.gateId,
|
|
1585
|
+
lowest.error.workflowId,
|
|
1586
|
+
siblingErrors,
|
|
1587
|
+
otherSuspensions
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
for (const { index, error } of nonGateFailures) {
|
|
1591
|
+
await handleRejection(error, items[index], index);
|
|
1592
|
+
}
|
|
1593
|
+
state.output = skipped.size === 0 ? results : results.filter((_, i) => !skipped.has(i));
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
return this.appendStep(node);
|
|
1597
|
+
}
|
|
1598
|
+
// Implementation
|
|
1599
|
+
parallel(branches, options) {
|
|
1600
|
+
const isTuple = Array.isArray(branches);
|
|
1601
|
+
const entries = isTuple ? branches.map((target, i) => ({ key: i, index: i, target })) : Object.entries(branches).map(([k, t], i) => ({ key: k, index: i, target: t }));
|
|
1602
|
+
const branchCount = entries.length;
|
|
1603
|
+
const requestedConcurrency = options?.concurrency;
|
|
1604
|
+
let effectiveConcurrency;
|
|
1605
|
+
if (requestedConcurrency === void 0) {
|
|
1606
|
+
effectiveConcurrency = Math.min(branchCount, 5);
|
|
1607
|
+
} else {
|
|
1608
|
+
effectiveConcurrency = requestedConcurrency;
|
|
1609
|
+
}
|
|
1610
|
+
if (requestedConcurrency === void 0 && branchCount > 5) {
|
|
1611
|
+
warnOnce(
|
|
1612
|
+
"pipeai:parallel-cap",
|
|
1613
|
+
`pipeai: parallel() with ${branchCount} branches capped at concurrency 5 by default. Pass { concurrency: ${branchCount} } (or Infinity) to opt in, or set { concurrency: N } if you want fewer.`
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
const onError = options?.onError;
|
|
1617
|
+
const id = options?.id ?? (isTuple ? "parallel:tuple" : "parallel:record");
|
|
1618
|
+
const node = {
|
|
1619
|
+
type: "step",
|
|
1620
|
+
id,
|
|
1621
|
+
category: "parallel",
|
|
1622
|
+
execute: async (state) => {
|
|
1623
|
+
const ctx = state.ctx;
|
|
1624
|
+
const input = state.output;
|
|
1625
|
+
const results = isTuple ? new Array(branchCount) : {};
|
|
1626
|
+
const branchStates = new Array(branchCount);
|
|
1627
|
+
const executeBranch = async ({ key, index, target }) => {
|
|
1628
|
+
const branchState = { ctx: state.ctx, output: input, mode: "generate" };
|
|
1629
|
+
branchStates[index] = branchState;
|
|
1630
|
+
const branchStart = performance.now();
|
|
1631
|
+
const itemIndex = isTuple ? index : key;
|
|
1632
|
+
await this.fireHook(state, "onItemStart", {
|
|
1633
|
+
stepId: id,
|
|
1634
|
+
type: "parallel",
|
|
1635
|
+
itemIndex,
|
|
1636
|
+
ctx: state.ctx,
|
|
1637
|
+
input
|
|
1638
|
+
});
|
|
1639
|
+
try {
|
|
1640
|
+
if (target instanceof SealedWorkflow) {
|
|
1641
|
+
await this.executeNestedWorkflow(branchState, target);
|
|
1642
|
+
} else {
|
|
1643
|
+
await this.executeAgent(branchState, target, ctx);
|
|
1644
|
+
}
|
|
1645
|
+
results[key] = branchState.output;
|
|
1646
|
+
await this.fireHook(state, "onItemFinish", {
|
|
1647
|
+
stepId: id,
|
|
1648
|
+
type: "parallel",
|
|
1649
|
+
itemIndex,
|
|
1650
|
+
ctx: state.ctx,
|
|
1651
|
+
output: branchState.output,
|
|
1652
|
+
durationMs: performance.now() - branchStart
|
|
1653
|
+
});
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
await this.fireHook(state, "onItemError", {
|
|
1656
|
+
stepId: id,
|
|
1657
|
+
type: "parallel",
|
|
1658
|
+
itemIndex,
|
|
1659
|
+
ctx: state.ctx,
|
|
1660
|
+
error,
|
|
1661
|
+
durationMs: performance.now() - branchStart
|
|
1662
|
+
});
|
|
1663
|
+
throw error;
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
const failures = [];
|
|
1667
|
+
const eff = Number.isFinite(effectiveConcurrency) ? Math.max(1, effectiveConcurrency) : branchCount;
|
|
1668
|
+
if (eff <= 1) {
|
|
1669
|
+
for (const e of entries) {
|
|
1670
|
+
try {
|
|
1671
|
+
await executeBranch(e);
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
failures.push({ key: e.key, index: e.index, error });
|
|
1674
|
+
}
|
|
676
1675
|
}
|
|
677
1676
|
} else {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1677
|
+
let nextIndex = 0;
|
|
1678
|
+
const worker = async () => {
|
|
1679
|
+
while (true) {
|
|
1680
|
+
const i = nextIndex++;
|
|
1681
|
+
if (i >= branchCount) return;
|
|
1682
|
+
const e = entries[i];
|
|
1683
|
+
try {
|
|
1684
|
+
await executeBranch(e);
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
failures.push({ key: e.key, index: e.index, error });
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
const workers = Array.from(
|
|
1691
|
+
{ length: Math.min(eff, branchCount) },
|
|
1692
|
+
() => worker()
|
|
1693
|
+
);
|
|
1694
|
+
await Promise.all(workers);
|
|
1695
|
+
}
|
|
1696
|
+
for (let idx = 0; idx < branchCount; idx++) {
|
|
1697
|
+
const bs = branchStates[idx];
|
|
1698
|
+
if (!bs?.warnings) continue;
|
|
1699
|
+
for (const w of bs.warnings) {
|
|
1700
|
+
pushWarning(state, w.source, `${id}[${entries[idx].key}]:${w.stepId}`, w.error);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
const gateFailures = [];
|
|
1704
|
+
const nonGateFailures = [];
|
|
1705
|
+
for (const f of failures) {
|
|
1706
|
+
if (f.error instanceof NestedGateUnsupportedError) gateFailures.push({ key: f.key, index: f.index, error: f.error });
|
|
1707
|
+
else nonGateFailures.push(f);
|
|
1708
|
+
}
|
|
1709
|
+
gateFailures.sort((a, b) => a.index - b.index);
|
|
1710
|
+
nonGateFailures.sort((a, b) => a.index - b.index);
|
|
1711
|
+
if (gateFailures.length > 0) {
|
|
1712
|
+
for (const nr of nonGateFailures) {
|
|
1713
|
+
pushWarning(state, "foreach-sibling", `${id}[${nr.key}]`, nr.error);
|
|
1714
|
+
}
|
|
1715
|
+
const lowest = gateFailures[0];
|
|
1716
|
+
const otherSuspensions = gateFailures.slice(1).map((g) => ({ index: g.index, gateId: g.error.gateId }));
|
|
1717
|
+
const siblingErrors = nonGateFailures.map((nr) => nr.error);
|
|
1718
|
+
throw new NestedGateUnsupportedError(
|
|
1719
|
+
lowest.error.gateId,
|
|
1720
|
+
lowest.error.workflowId,
|
|
1721
|
+
siblingErrors,
|
|
1722
|
+
otherSuspensions
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
for (const { key, index, error } of nonGateFailures) {
|
|
1726
|
+
if (!onError) throw error;
|
|
1727
|
+
const recovered = await onError({
|
|
1728
|
+
error,
|
|
1729
|
+
key: isTuple ? void 0 : key,
|
|
1730
|
+
index: isTuple ? index : void 0,
|
|
1731
|
+
ctx: state.ctx
|
|
1732
|
+
});
|
|
1733
|
+
if (recovered === _Workflow.SKIP) {
|
|
1734
|
+
results[key] = void 0;
|
|
1735
|
+
} else {
|
|
1736
|
+
results[key] = recovered;
|
|
681
1737
|
}
|
|
682
1738
|
}
|
|
683
1739
|
state.output = results;
|
|
684
1740
|
}
|
|
685
1741
|
};
|
|
686
|
-
return
|
|
1742
|
+
return this.appendStep(node);
|
|
687
1743
|
}
|
|
688
1744
|
// ── repeat: conditional loop ─────────────────────────────────
|
|
689
1745
|
repeat(target, options) {
|
|
690
1746
|
const maxIterations = options.maxIterations ?? 10;
|
|
691
1747
|
const isWorkflow = target instanceof SealedWorkflow;
|
|
692
|
-
const
|
|
1748
|
+
const defaultId = isWorkflow ? target.id ?? "repeat" : `repeat:${target.id}`;
|
|
1749
|
+
const id = options.id ?? defaultId;
|
|
693
1750
|
const predicate = options.until ?? (async (p) => !await options.while(p));
|
|
694
1751
|
const node = {
|
|
695
1752
|
type: "step",
|
|
696
1753
|
id,
|
|
1754
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1755
|
+
nestedWorkflow: isWorkflow ? target : void 0,
|
|
1756
|
+
category: "repeat",
|
|
697
1757
|
execute: async (state) => {
|
|
698
1758
|
const ctx = state.ctx;
|
|
699
1759
|
for (let i = 1; i <= maxIterations; i++) {
|
|
1760
|
+
if (state.abortSignal?.aborted) {
|
|
1761
|
+
throw state.abortSignal.reason ?? new Error("Workflow aborted");
|
|
1762
|
+
}
|
|
700
1763
|
if (isWorkflow) {
|
|
701
1764
|
await this.executeNestedWorkflow(state, target);
|
|
702
1765
|
} else {
|
|
@@ -712,7 +1775,7 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
712
1775
|
throw new WorkflowLoopError(maxIterations, maxIterations);
|
|
713
1776
|
}
|
|
714
1777
|
};
|
|
715
|
-
return
|
|
1778
|
+
return this.appendStep(node);
|
|
716
1779
|
}
|
|
717
1780
|
// ── catch ─────────────────────────────────────────────────────
|
|
718
1781
|
catch(id, fn) {
|
|
@@ -724,26 +1787,28 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
724
1787
|
id,
|
|
725
1788
|
catchFn: fn
|
|
726
1789
|
};
|
|
727
|
-
return
|
|
728
|
-
}
|
|
729
|
-
// ── finally (terminal — returns sealed workflow) ──────────────
|
|
730
|
-
finally(id, fn) {
|
|
731
|
-
const node = {
|
|
732
|
-
type: "finally",
|
|
733
|
-
id,
|
|
734
|
-
execute: async (state) => {
|
|
735
|
-
await fn({ ctx: state.ctx });
|
|
736
|
-
}
|
|
737
|
-
};
|
|
738
|
-
return new SealedWorkflow([...this.steps, node], this.id);
|
|
1790
|
+
return this.appendStep(node);
|
|
739
1791
|
}
|
|
1792
|
+
// `.finally()` is inherited from SealedWorkflow now (it lives there so
|
|
1793
|
+
// multi-finally chains are possible — `.finally().finally()`).
|
|
740
1794
|
};
|
|
1795
|
+
|
|
1796
|
+
// src/index.ts
|
|
1797
|
+
var SKIP = Workflow.SKIP;
|
|
741
1798
|
export {
|
|
742
1799
|
Agent,
|
|
1800
|
+
CHECKPOINT_STEP_ID,
|
|
1801
|
+
CheckpointTimeoutError,
|
|
1802
|
+
NestedGateUnsupportedError,
|
|
1803
|
+
SKIP,
|
|
1804
|
+
TOOL_PROVIDER_BRAND,
|
|
1805
|
+
ToolProvider,
|
|
743
1806
|
Workflow,
|
|
744
1807
|
WorkflowBranchError,
|
|
745
1808
|
WorkflowLoopError,
|
|
746
|
-
|
|
747
|
-
|
|
1809
|
+
defineTool,
|
|
1810
|
+
getActiveWriter,
|
|
1811
|
+
isToolProvider,
|
|
1812
|
+
migrateSnapshot
|
|
748
1813
|
};
|
|
749
1814
|
//# sourceMappingURL=index.js.map
|