pipeai 0.1.0 → 0.2.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 +223 -24
- package/dist/index.cjs +219 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -16
- package/dist/index.d.ts +77 -16
- package/dist/index.js +218 -37
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
Workflow: () => Workflow,
|
|
25
25
|
WorkflowBranchError: () => WorkflowBranchError,
|
|
26
26
|
WorkflowLoopError: () => WorkflowLoopError,
|
|
27
|
+
WorkflowSuspended: () => WorkflowSuspended,
|
|
27
28
|
defineTool: () => defineTool
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -33,6 +34,31 @@ var import_ai2 = require("ai");
|
|
|
33
34
|
|
|
34
35
|
// src/tool-provider.ts
|
|
35
36
|
var import_ai = require("ai");
|
|
37
|
+
|
|
38
|
+
// src/utils.ts
|
|
39
|
+
var import_node_async_hooks = require("async_hooks");
|
|
40
|
+
var writerStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
41
|
+
function runWithWriter(writer, fn) {
|
|
42
|
+
return writerStorage.run(writer, fn);
|
|
43
|
+
}
|
|
44
|
+
function getActiveWriter() {
|
|
45
|
+
return writerStorage.getStore();
|
|
46
|
+
}
|
|
47
|
+
function resolveValue(value, ctx, input) {
|
|
48
|
+
if (typeof value === "function") {
|
|
49
|
+
return value(ctx, input);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
async function extractOutput(result, hasStructuredOutput) {
|
|
54
|
+
if (hasStructuredOutput) {
|
|
55
|
+
const output = await result.output;
|
|
56
|
+
if (output !== void 0) return output;
|
|
57
|
+
}
|
|
58
|
+
return await result.text;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/tool-provider.ts
|
|
36
62
|
var TOOL_PROVIDER_BRAND = /* @__PURE__ */ Symbol.for("agent-workflow.ToolProvider");
|
|
37
63
|
var ToolProvider = class {
|
|
38
64
|
[TOOL_PROVIDER_BRAND] = true;
|
|
@@ -45,7 +71,7 @@ var ToolProvider = class {
|
|
|
45
71
|
return (0, import_ai.tool)({
|
|
46
72
|
...toolDef,
|
|
47
73
|
parameters: inputSchema,
|
|
48
|
-
execute: (input, options) => execute(input, context, options)
|
|
74
|
+
execute: (input, options) => execute(input, context, { ...options, writer: getActiveWriter() })
|
|
49
75
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
76
|
});
|
|
51
77
|
}
|
|
@@ -57,21 +83,6 @@ function isToolProvider(obj) {
|
|
|
57
83
|
return typeof obj === "object" && obj !== null && TOOL_PROVIDER_BRAND in obj;
|
|
58
84
|
}
|
|
59
85
|
|
|
60
|
-
// src/utils.ts
|
|
61
|
-
function resolveValue(value, ctx, input) {
|
|
62
|
-
if (typeof value === "function") {
|
|
63
|
-
return value(ctx, input);
|
|
64
|
-
}
|
|
65
|
-
return value;
|
|
66
|
-
}
|
|
67
|
-
async function extractOutput(result, hasStructuredOutput) {
|
|
68
|
-
if (hasStructuredOutput) {
|
|
69
|
-
const output = await result.output;
|
|
70
|
-
if (output !== void 0) return output;
|
|
71
|
-
}
|
|
72
|
-
return await result.text;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
86
|
// src/agent.ts
|
|
76
87
|
var Agent = class {
|
|
77
88
|
id;
|
|
@@ -136,7 +147,7 @@ var Agent = class {
|
|
|
136
147
|
return await (0, import_ai2.generateText)(options);
|
|
137
148
|
} catch (error) {
|
|
138
149
|
if (this.config.onError) {
|
|
139
|
-
await this.config.onError({ error, ctx, input });
|
|
150
|
+
await this.config.onError({ error, ctx, input, writer: getActiveWriter() });
|
|
140
151
|
}
|
|
141
152
|
throw error;
|
|
142
153
|
}
|
|
@@ -147,7 +158,7 @@ var Agent = class {
|
|
|
147
158
|
const options = this.buildCallOptions(resolved, ctx, input);
|
|
148
159
|
return (0, import_ai2.streamText)({
|
|
149
160
|
...options,
|
|
150
|
-
onError: this.config.onError ? ({ error }) => this.config.onError({ error, ctx, input }) : void 0
|
|
161
|
+
onError: this.config.onError ? ({ error }) => this.config.onError({ error, ctx, input, writer: getActiveWriter() }) : void 0
|
|
151
162
|
});
|
|
152
163
|
}
|
|
153
164
|
asTool(ctx, options) {
|
|
@@ -170,6 +181,13 @@ var Agent = class {
|
|
|
170
181
|
description: this.description,
|
|
171
182
|
parameters: this.config.input,
|
|
172
183
|
execute: async (toolInput) => {
|
|
184
|
+
const writer = getActiveWriter();
|
|
185
|
+
if (writer) {
|
|
186
|
+
const result2 = await this.stream(ctx, toolInput);
|
|
187
|
+
writer.merge(result2.toUIMessageStream());
|
|
188
|
+
if (options?.mapOutput) return options.mapOutput(result2);
|
|
189
|
+
return extractOutput(result2, this.hasOutput);
|
|
190
|
+
}
|
|
173
191
|
const result = await this.generate(ctx, toolInput);
|
|
174
192
|
if (options?.mapOutput) return options.mapOutput(result);
|
|
175
193
|
return extractOutput(result, this.hasOutput);
|
|
@@ -189,8 +207,8 @@ var Agent = class {
|
|
|
189
207
|
...resolved.messages ? { messages: resolved.messages } : { prompt: resolved.prompt ?? "" },
|
|
190
208
|
...resolved.system ? { system: resolved.system } : {},
|
|
191
209
|
...this.config.output ? { output: this.config.output } : {},
|
|
192
|
-
onStepFinish: this._onStepFinish ? (event) => this._onStepFinish({ result: event, ctx, input }) : void 0,
|
|
193
|
-
onFinish: this._onFinish ? (event) => this._onFinish({ result: event, ctx, input }) : void 0
|
|
210
|
+
onStepFinish: this._onStepFinish ? (event) => this._onStepFinish({ result: event, ctx, input, writer: getActiveWriter() }) : void 0,
|
|
211
|
+
onFinish: this._onFinish ? (event) => this._onFinish({ result: event, ctx, input, writer: getActiveWriter() }) : void 0
|
|
194
212
|
};
|
|
195
213
|
}
|
|
196
214
|
resolveConfig(ctx, input) {
|
|
@@ -259,6 +277,14 @@ var WorkflowLoopError = class extends Error {
|
|
|
259
277
|
this.name = "WorkflowLoopError";
|
|
260
278
|
}
|
|
261
279
|
};
|
|
280
|
+
var WorkflowSuspended = class extends Error {
|
|
281
|
+
snapshot;
|
|
282
|
+
constructor(snapshot) {
|
|
283
|
+
super(`Workflow suspended at gate "${snapshot.gateId}"`);
|
|
284
|
+
this.name = "WorkflowSuspended";
|
|
285
|
+
this.snapshot = snapshot;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
262
288
|
var SealedWorkflow = class {
|
|
263
289
|
id;
|
|
264
290
|
steps;
|
|
@@ -315,12 +341,13 @@ var SealedWorkflow = class {
|
|
|
315
341
|
};
|
|
316
342
|
}
|
|
317
343
|
// ── Internal: execute pipeline ────────────────────────────────
|
|
318
|
-
async execute(state) {
|
|
344
|
+
async execute(state, startIndex = 0) {
|
|
319
345
|
if (this.steps.length === 0) {
|
|
320
346
|
throw new Error("Workflow has no steps. Add at least one step before calling generate() or stream().");
|
|
321
347
|
}
|
|
322
348
|
let pendingError = null;
|
|
323
|
-
for (
|
|
349
|
+
for (let i = startIndex; i < this.steps.length; i++) {
|
|
350
|
+
const node = this.steps[i];
|
|
324
351
|
if (node.type === "finally") {
|
|
325
352
|
await node.execute(state);
|
|
326
353
|
continue;
|
|
@@ -340,10 +367,26 @@ var SealedWorkflow = class {
|
|
|
340
367
|
}
|
|
341
368
|
continue;
|
|
342
369
|
}
|
|
370
|
+
if (node.type === "gate") {
|
|
371
|
+
if (pendingError) continue;
|
|
372
|
+
if (node.condition) {
|
|
373
|
+
const shouldSuspend = await node.condition(state);
|
|
374
|
+
if (!shouldSuspend) continue;
|
|
375
|
+
}
|
|
376
|
+
const gatePayload = await node.payload(state);
|
|
377
|
+
throw new WorkflowSuspended({
|
|
378
|
+
version: 1,
|
|
379
|
+
resumeFromIndex: i,
|
|
380
|
+
output: state.output,
|
|
381
|
+
gateId: node.id,
|
|
382
|
+
gatePayload
|
|
383
|
+
});
|
|
384
|
+
}
|
|
343
385
|
if (pendingError) continue;
|
|
344
386
|
try {
|
|
345
387
|
await node.execute(state);
|
|
346
388
|
} catch (error) {
|
|
389
|
+
if (error instanceof WorkflowSuspended) throw error;
|
|
347
390
|
pendingError = { error, stepId: node.id };
|
|
348
391
|
}
|
|
349
392
|
}
|
|
@@ -353,7 +396,16 @@ var SealedWorkflow = class {
|
|
|
353
396
|
// Defined on SealedWorkflow (not Workflow) because TypeScript's protected
|
|
354
397
|
// access rules only allow calling workflow.execute() from the same class.
|
|
355
398
|
async executeNestedWorkflow(state, workflow) {
|
|
356
|
-
|
|
399
|
+
try {
|
|
400
|
+
await workflow.execute(state);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (error instanceof WorkflowSuspended) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Gates inside nested workflows are not yet supported. Gate "${error.snapshot.gateId}" was hit inside nested workflow "${workflow.id ?? "(anonymous)"}". Consider using a conditional gate with \`condition\` to skip when criteria are met, or restructure the workflow to use gates at the top level only.`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
357
409
|
}
|
|
358
410
|
// ── Internal: execute an agent within a step/branch ───────────
|
|
359
411
|
// In stream mode, output extraction awaits the full stream before returning.
|
|
@@ -363,20 +415,23 @@ var SealedWorkflow = class {
|
|
|
363
415
|
const input = state.output;
|
|
364
416
|
const hasStructuredOutput = agent.hasOutput;
|
|
365
417
|
if (state.mode === "stream" && state.writer) {
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
await
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
418
|
+
const writer = state.writer;
|
|
419
|
+
await runWithWriter(writer, async () => {
|
|
420
|
+
const result = await agent.stream(ctx, state.output);
|
|
421
|
+
if (options?.handleStream) {
|
|
422
|
+
await options.handleStream({ result, writer, ctx });
|
|
423
|
+
} else {
|
|
424
|
+
writer.merge(result.toUIMessageStream());
|
|
425
|
+
}
|
|
426
|
+
if (options?.onStreamResult) {
|
|
427
|
+
await options.onStreamResult({ result, ctx, input });
|
|
428
|
+
}
|
|
429
|
+
if (options?.mapStreamResult) {
|
|
430
|
+
state.output = await options.mapStreamResult({ result, ctx, input });
|
|
431
|
+
} else {
|
|
432
|
+
state.output = await extractOutput(result, hasStructuredOutput);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
380
435
|
} else {
|
|
381
436
|
const result = await agent.generate(ctx, state.output);
|
|
382
437
|
if (options?.onGenerateResult) {
|
|
@@ -389,6 +444,106 @@ var SealedWorkflow = class {
|
|
|
389
444
|
}
|
|
390
445
|
}
|
|
391
446
|
}
|
|
447
|
+
// ── Gate: load persisted state for resumption ──────────────────
|
|
448
|
+
loadState(gateId, snapshot) {
|
|
449
|
+
if (snapshot.gateId !== gateId) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`loadState: gate ID mismatch \u2014 expected "${gateId}" but snapshot has "${snapshot.gateId}".`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const gateIndex = this.findGateIndex(snapshot);
|
|
455
|
+
const gateNode = this.steps[gateIndex];
|
|
456
|
+
return new ResumedWorkflow(
|
|
457
|
+
this.steps,
|
|
458
|
+
gateIndex + 1,
|
|
459
|
+
gateNode.schema,
|
|
460
|
+
gateNode.merge,
|
|
461
|
+
snapshot.output
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
findGateIndex(snapshot) {
|
|
465
|
+
if (snapshot.version !== 1) {
|
|
466
|
+
throw new Error(`Unsupported snapshot version: ${snapshot.version}`);
|
|
467
|
+
}
|
|
468
|
+
const hint = snapshot.resumeFromIndex;
|
|
469
|
+
if (hint >= 0 && hint < this.steps.length) {
|
|
470
|
+
const node = this.steps[hint];
|
|
471
|
+
if (node.type === "gate" && node.id === snapshot.gateId) {
|
|
472
|
+
return hint;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
for (let i = 0; i < this.steps.length; i++) {
|
|
476
|
+
const node = this.steps[i];
|
|
477
|
+
if (node.type === "gate" && node.id === snapshot.gateId) {
|
|
478
|
+
return i;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
throw new Error(
|
|
482
|
+
`Gate "${snapshot.gateId}" not found in workflow. The workflow definition may have changed since the snapshot was created.`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
var ResumedWorkflow = class extends SealedWorkflow {
|
|
487
|
+
startIndex;
|
|
488
|
+
schema;
|
|
489
|
+
mergeFn;
|
|
490
|
+
priorOutput;
|
|
491
|
+
/** @internal */
|
|
492
|
+
constructor(steps, startIndex, schema, mergeFn, priorOutput) {
|
|
493
|
+
super(steps);
|
|
494
|
+
this.startIndex = startIndex;
|
|
495
|
+
this.schema = schema;
|
|
496
|
+
this.mergeFn = mergeFn;
|
|
497
|
+
this.priorOutput = priorOutput;
|
|
498
|
+
}
|
|
499
|
+
validateResponse(response) {
|
|
500
|
+
if (this.schema) {
|
|
501
|
+
return this.schema.parse(response);
|
|
502
|
+
}
|
|
503
|
+
return response;
|
|
504
|
+
}
|
|
505
|
+
async generate(ctx, ...args) {
|
|
506
|
+
const response = this.validateResponse(args[0]);
|
|
507
|
+
const output = this.mergeFn ? await this.mergeFn({ priorOutput: this.priorOutput, response }) : response;
|
|
508
|
+
const state = { ctx, output, mode: "generate" };
|
|
509
|
+
await this.execute(state, this.startIndex);
|
|
510
|
+
return { output: state.output };
|
|
511
|
+
}
|
|
512
|
+
stream(ctx, ...args) {
|
|
513
|
+
const response = this.validateResponse(args[0]);
|
|
514
|
+
const options = args[1];
|
|
515
|
+
let resolveOutput;
|
|
516
|
+
let rejectOutput;
|
|
517
|
+
const outputPromise = new Promise((res, rej) => {
|
|
518
|
+
resolveOutput = res;
|
|
519
|
+
rejectOutput = rej;
|
|
520
|
+
});
|
|
521
|
+
outputPromise.catch(() => {
|
|
522
|
+
});
|
|
523
|
+
const mergeFn = this.mergeFn;
|
|
524
|
+
const priorOutput = this.priorOutput;
|
|
525
|
+
const stream = (0, import_ai3.createUIMessageStream)({
|
|
526
|
+
execute: async ({ writer }) => {
|
|
527
|
+
const output = mergeFn ? await mergeFn({ priorOutput, response }) : response;
|
|
528
|
+
const state = {
|
|
529
|
+
ctx,
|
|
530
|
+
output,
|
|
531
|
+
mode: "stream",
|
|
532
|
+
writer
|
|
533
|
+
};
|
|
534
|
+
try {
|
|
535
|
+
await this.execute(state, this.startIndex);
|
|
536
|
+
resolveOutput(state.output);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
rejectOutput(error);
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
...options?.onError ? { onError: options.onError } : {},
|
|
543
|
+
...options?.onFinish ? { onFinish: options.onFinish } : {}
|
|
544
|
+
});
|
|
545
|
+
return { stream, output: outputPromise };
|
|
546
|
+
}
|
|
392
547
|
};
|
|
393
548
|
var Workflow = class _Workflow extends SealedWorkflow {
|
|
394
549
|
constructor(steps = [], id) {
|
|
@@ -442,6 +597,32 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
442
597
|
};
|
|
443
598
|
return new _Workflow([...this.steps, node], this.id);
|
|
444
599
|
}
|
|
600
|
+
// ── gate: human-in-the-loop suspension point ────────────────
|
|
601
|
+
gate(id, options) {
|
|
602
|
+
if (this.steps.some((s) => s.type === "gate" && s.id === id)) {
|
|
603
|
+
throw new Error(`Workflow: duplicate gate ID "${id}". Each gate must have a unique identifier.`);
|
|
604
|
+
}
|
|
605
|
+
const node = {
|
|
606
|
+
type: "gate",
|
|
607
|
+
id,
|
|
608
|
+
schema: options?.schema,
|
|
609
|
+
condition: options?.condition ? async (state) => options.condition({
|
|
610
|
+
ctx: state.ctx,
|
|
611
|
+
input: state.output
|
|
612
|
+
}) : void 0,
|
|
613
|
+
merge: options?.merge ? (params) => options.merge(params) : void 0,
|
|
614
|
+
payload: async (state) => {
|
|
615
|
+
if (options?.payload) {
|
|
616
|
+
return options.payload({
|
|
617
|
+
ctx: state.ctx,
|
|
618
|
+
input: state.output
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return state.output;
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
return new _Workflow([...this.steps, node], this.id);
|
|
625
|
+
}
|
|
445
626
|
// ── branch: implementation ────────────────────────────────────
|
|
446
627
|
branch(casesOrConfig) {
|
|
447
628
|
if (Array.isArray(casesOrConfig)) {
|
|
@@ -588,6 +769,7 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
588
769
|
Workflow,
|
|
589
770
|
WorkflowBranchError,
|
|
590
771
|
WorkflowLoopError,
|
|
772
|
+
WorkflowSuspended,
|
|
591
773
|
defineTool
|
|
592
774
|
});
|
|
593
775
|
//# sourceMappingURL=index.cjs.map
|