pipeai 0.1.1 → 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 +187 -1
- package/dist/index.cjs +165 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -14
- package/dist/index.d.ts +69 -14
- package/dist/index.js +164 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The library is ~1000 lines across 4 files. It's designed to be read, understood,
|
|
|
11
11
|
| Primitive | Purpose |
|
|
12
12
|
| -------------- | ---------------------------------------------------------------------------------------------------- |
|
|
13
13
|
| `Agent` | A pure AI SDK wrapper. Supports `generate()`, `stream()`, `asTool()`, and `asToolProvider()`. |
|
|
14
|
-
| `Workflow` | A typed pipeline that chains agents with `step()`, `branch()`, `foreach()`, `repeat()`, `catch()`, and `finally()`. |
|
|
14
|
+
| `Workflow` | A typed pipeline that chains agents with `step()`, `branch()`, `foreach()`, `repeat()`, `gate()`, `catch()`, and `finally()`. |
|
|
15
15
|
| `defineTool` | A context-aware tool factory — injects runtime context into tool `execute` calls. |
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
@@ -596,6 +596,7 @@ const { stream, output } = pipeline.stream(ctx, initialInput, {
|
|
|
596
596
|
| `.branch({ select, agents })` | Key routing. `select` returns a key, runs the matching agent. |
|
|
597
597
|
| `.foreach(target, opts?)` | Map each array element through an agent or workflow. `opts.concurrency` controls parallelism (default: 1). |
|
|
598
598
|
| `.repeat(target, opts)` | Loop an agent or workflow. Use `{ until }` or `{ while }` (mutually exclusive). `maxIterations` defaults to 10. |
|
|
599
|
+
| `.gate(id, opts?)` | Human-in-the-loop suspension point. Throws `WorkflowSuspended` with a serializable snapshot. Resume via `loadState(gateId, snapshot)`. |
|
|
599
600
|
| `.catch(id, fn)` | Handle errors. `fn` receives `{ error, ctx, lastOutput, stepId }` and returns a recovery value. |
|
|
600
601
|
| `.finally(id, fn)` | Always runs. `fn` receives `{ ctx }`. |
|
|
601
602
|
|
|
@@ -618,6 +619,191 @@ Auto-extraction priority for `step()` with an agent:
|
|
|
618
619
|
| `foreach()` | Deterministic | Items don't stream | Process each element of an array through an agent or workflow |
|
|
619
620
|
| `repeat()` | Condition function | Each iteration streams | Iterative refinement until a quality threshold is met |
|
|
620
621
|
|
|
622
|
+
## Human-in-the-Loop via `gate()`
|
|
623
|
+
|
|
624
|
+
`gate()` suspends a workflow at a designated point, producing a JSON-serializable snapshot. The consumer persists the snapshot, collects human input out-of-band (HTTP, WebSocket, CLI, queue — any transport), then resumes the workflow from where it left off.
|
|
625
|
+
|
|
626
|
+
### Basic gate
|
|
627
|
+
|
|
628
|
+
```ts
|
|
629
|
+
import { Workflow, WorkflowSuspended } from "pipeai";
|
|
630
|
+
|
|
631
|
+
const pipeline = Workflow.create<Ctx>()
|
|
632
|
+
.step(draftAgent)
|
|
633
|
+
.gate("review", {
|
|
634
|
+
payload: ({ input }) => ({ draft: input, instructions: "Please review this draft" }),
|
|
635
|
+
})
|
|
636
|
+
.step(publishAgent);
|
|
637
|
+
|
|
638
|
+
// Run — suspends at gate
|
|
639
|
+
try {
|
|
640
|
+
await pipeline.generate(ctx, input);
|
|
641
|
+
} catch (e) {
|
|
642
|
+
if (e instanceof WorkflowSuspended) {
|
|
643
|
+
await db.saveSnapshot(e.snapshot);
|
|
644
|
+
return res.status(202).json(e.snapshot.gatePayload);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Resume — load state, pass gate ID + snapshot to generate or stream
|
|
649
|
+
const snapshot = await db.loadSnapshot(id);
|
|
650
|
+
const resumed = pipeline.loadState("review", snapshot);
|
|
651
|
+
const { output } = await resumed.generate(ctx, humanResponse);
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
The `snapshot` is plain JSON — it survives `JSON.parse(JSON.stringify())`, database storage, and process restarts. The workflow definition (code) stays in the process; only the data is serialized.
|
|
655
|
+
|
|
656
|
+
### Resuming with streaming
|
|
657
|
+
|
|
658
|
+
For chat applications where the client reconnects and needs a live stream for the remaining steps:
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
const resumed = pipeline.loadState("review", snapshot);
|
|
662
|
+
const { stream, output } = resumed.stream(ctx, humanResponse);
|
|
663
|
+
return new Response(stream);
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
The previous stream is gone — the library only streams forward from the resume point. Load prior chat history from your database and send it to the client before piping the resume stream.
|
|
667
|
+
|
|
668
|
+
### Streaming suspension
|
|
669
|
+
|
|
670
|
+
When `stream()` hits a gate, the stream closes cleanly (partial content from steps before the gate is delivered). The `output` promise rejects with `WorkflowSuspended`:
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
const { stream, output } = pipeline.stream(ctx, input);
|
|
674
|
+
pipeStreamToResponse(res, stream); // partial content delivered normally
|
|
675
|
+
|
|
676
|
+
try {
|
|
677
|
+
await output;
|
|
678
|
+
} catch (e) {
|
|
679
|
+
if (e instanceof WorkflowSuspended) {
|
|
680
|
+
await db.saveSnapshot(e.snapshot);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Schema validation
|
|
686
|
+
|
|
687
|
+
Add a `schema` to validate the human response at runtime. The schema uses a structural type — any object with a `.parse()` method works (Zod, Valibot, ArkType, etc.):
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
const pipeline = Workflow.create<Ctx>()
|
|
691
|
+
.step(draftAgent)
|
|
692
|
+
.gate("review", {
|
|
693
|
+
schema: z.object({ approved: z.boolean(), notes: z.string() }),
|
|
694
|
+
})
|
|
695
|
+
.step("publish", ({ input }) => {
|
|
696
|
+
if (!input.approved) return "Rejected";
|
|
697
|
+
return `Published with notes: ${input.notes}`;
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// Resume — gate ID enables type inference, schema validates at runtime
|
|
701
|
+
const resumed = pipeline.loadState("review", snapshot);
|
|
702
|
+
await resumed.generate(ctx, { approved: true, notes: "lgtm" }); // passes
|
|
703
|
+
await resumed.generate(ctx, { approved: "yes" }); // throws parse error
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Multiple gates
|
|
707
|
+
|
|
708
|
+
A workflow can have multiple gates. Each `generate()`/`stream()` call advances to the next gate or completes:
|
|
709
|
+
|
|
710
|
+
```ts
|
|
711
|
+
const pipeline = Workflow.create<Ctx>()
|
|
712
|
+
.step(draftAgent)
|
|
713
|
+
.gate("review")
|
|
714
|
+
.step("process", ({ input }) => `reviewed: ${input}`)
|
|
715
|
+
.gate("final-approval")
|
|
716
|
+
.step("publish", ({ input }) => `published: ${input}`);
|
|
717
|
+
|
|
718
|
+
// First gate
|
|
719
|
+
let snapshot: WorkflowSnapshot;
|
|
720
|
+
try { await pipeline.generate(ctx, input); }
|
|
721
|
+
catch (e) { snapshot = (e as WorkflowSuspended).snapshot; }
|
|
722
|
+
|
|
723
|
+
// Second gate
|
|
724
|
+
const resumed1 = pipeline.loadState("review", snapshot);
|
|
725
|
+
try { await resumed1.generate(ctx, "first approval"); }
|
|
726
|
+
catch (e) { snapshot = (e as WorkflowSuspended).snapshot; }
|
|
727
|
+
|
|
728
|
+
// Complete
|
|
729
|
+
const resumed2 = pipeline.loadState("final-approval", snapshot);
|
|
730
|
+
const { output } = await resumed2.generate(ctx, "final approval");
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### Merging pre-gate output with response
|
|
734
|
+
|
|
735
|
+
The `snapshot.output` field contains the pre-gate output. Use it to merge with the human response:
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
// The step after the gate needs both the draft and the approval
|
|
739
|
+
const resumed = pipeline.loadState("review", snapshot);
|
|
740
|
+
await resumed.generate(ctx, {
|
|
741
|
+
draft: snapshot.output, // pre-gate output
|
|
742
|
+
approval: humanResponse, // human's response
|
|
743
|
+
});
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Injecting updated context on resume
|
|
747
|
+
|
|
748
|
+
`ctx` is provided fresh on every `generate()`/`stream()` call — never serialized. Use it to inject updated chat history, refreshed auth tokens, or new database connections:
|
|
749
|
+
|
|
750
|
+
```ts
|
|
751
|
+
const freshCtx = {
|
|
752
|
+
chatHistory: await db.loadChatHistory(userId), // includes messages added during the pause
|
|
753
|
+
db: getDbConnection(),
|
|
754
|
+
userId,
|
|
755
|
+
};
|
|
756
|
+
const resumed = pipeline.loadState("review", snapshot);
|
|
757
|
+
await resumed.stream(freshCtx, humanResponse);
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### Conditional gates
|
|
761
|
+
|
|
762
|
+
Use `condition` to make a gate fire only when a predicate returns `true`. When the condition returns `false`, the gate is skipped and the current output passes through unchanged:
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
const pipeline = Workflow.create<Ctx>()
|
|
766
|
+
.step(draftAgent)
|
|
767
|
+
.gate("review", {
|
|
768
|
+
condition: ({ input }) => input.needsReview,
|
|
769
|
+
})
|
|
770
|
+
.step(publishAgent);
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### Merging pre-gate output with response
|
|
774
|
+
|
|
775
|
+
Use `merge` to combine the pre-gate output with the human response into a single value for the next step. Without `merge`, only the human response is forwarded:
|
|
776
|
+
|
|
777
|
+
```ts
|
|
778
|
+
const pipeline = Workflow.create<Ctx>()
|
|
779
|
+
.step(draftAgent)
|
|
780
|
+
.gate("review", {
|
|
781
|
+
merge: ({ priorOutput, response }) => ({
|
|
782
|
+
draft: priorOutput,
|
|
783
|
+
approval: response,
|
|
784
|
+
}),
|
|
785
|
+
})
|
|
786
|
+
.step("publish", ({ input }) => {
|
|
787
|
+
// input is { draft, approval }
|
|
788
|
+
});
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Snapshot shape
|
|
792
|
+
|
|
793
|
+
```ts
|
|
794
|
+
interface WorkflowSnapshot {
|
|
795
|
+
version: 1;
|
|
796
|
+
resumeFromIndex: number; // step index of the gate
|
|
797
|
+
output: unknown; // pre-gate output
|
|
798
|
+
gateId: string; // gate identifier
|
|
799
|
+
gatePayload: unknown; // data for the human
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### Limitations
|
|
804
|
+
|
|
805
|
+
Gates inside nested workflows, `foreach()`, and `repeat()` are not yet supported — a descriptive error is thrown at runtime. Gates at the top level of a workflow work in all cases.
|
|
806
|
+
|
|
621
807
|
## Full Example
|
|
622
808
|
|
|
623
809
|
```ts
|
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);
|
|
@@ -276,6 +277,14 @@ var WorkflowLoopError = class extends Error {
|
|
|
276
277
|
this.name = "WorkflowLoopError";
|
|
277
278
|
}
|
|
278
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
|
+
};
|
|
279
288
|
var SealedWorkflow = class {
|
|
280
289
|
id;
|
|
281
290
|
steps;
|
|
@@ -332,12 +341,13 @@ var SealedWorkflow = class {
|
|
|
332
341
|
};
|
|
333
342
|
}
|
|
334
343
|
// ── Internal: execute pipeline ────────────────────────────────
|
|
335
|
-
async execute(state) {
|
|
344
|
+
async execute(state, startIndex = 0) {
|
|
336
345
|
if (this.steps.length === 0) {
|
|
337
346
|
throw new Error("Workflow has no steps. Add at least one step before calling generate() or stream().");
|
|
338
347
|
}
|
|
339
348
|
let pendingError = null;
|
|
340
|
-
for (
|
|
349
|
+
for (let i = startIndex; i < this.steps.length; i++) {
|
|
350
|
+
const node = this.steps[i];
|
|
341
351
|
if (node.type === "finally") {
|
|
342
352
|
await node.execute(state);
|
|
343
353
|
continue;
|
|
@@ -357,10 +367,26 @@ var SealedWorkflow = class {
|
|
|
357
367
|
}
|
|
358
368
|
continue;
|
|
359
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
|
+
}
|
|
360
385
|
if (pendingError) continue;
|
|
361
386
|
try {
|
|
362
387
|
await node.execute(state);
|
|
363
388
|
} catch (error) {
|
|
389
|
+
if (error instanceof WorkflowSuspended) throw error;
|
|
364
390
|
pendingError = { error, stepId: node.id };
|
|
365
391
|
}
|
|
366
392
|
}
|
|
@@ -370,7 +396,16 @@ var SealedWorkflow = class {
|
|
|
370
396
|
// Defined on SealedWorkflow (not Workflow) because TypeScript's protected
|
|
371
397
|
// access rules only allow calling workflow.execute() from the same class.
|
|
372
398
|
async executeNestedWorkflow(state, workflow) {
|
|
373
|
-
|
|
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
|
+
}
|
|
374
409
|
}
|
|
375
410
|
// ── Internal: execute an agent within a step/branch ───────────
|
|
376
411
|
// In stream mode, output extraction awaits the full stream before returning.
|
|
@@ -409,6 +444,106 @@ var SealedWorkflow = class {
|
|
|
409
444
|
}
|
|
410
445
|
}
|
|
411
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
|
+
}
|
|
412
547
|
};
|
|
413
548
|
var Workflow = class _Workflow extends SealedWorkflow {
|
|
414
549
|
constructor(steps = [], id) {
|
|
@@ -462,6 +597,32 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
462
597
|
};
|
|
463
598
|
return new _Workflow([...this.steps, node], this.id);
|
|
464
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
|
+
}
|
|
465
626
|
// ── branch: implementation ────────────────────────────────────
|
|
466
627
|
branch(casesOrConfig) {
|
|
467
628
|
if (Array.isArray(casesOrConfig)) {
|
|
@@ -608,6 +769,7 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
608
769
|
Workflow,
|
|
609
770
|
WorkflowBranchError,
|
|
610
771
|
WorkflowLoopError,
|
|
772
|
+
WorkflowSuspended,
|
|
611
773
|
defineTool
|
|
612
774
|
});
|
|
613
775
|
//# sourceMappingURL=index.cjs.map
|