pipeai 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/index.cjs +412 -514
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +230 -127
- package/dist/index.d.ts +230 -127
- package/dist/index.js +412 -514
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ function runWithWriter(writer, fn) {
|
|
|
18
18
|
function getActiveWriter() {
|
|
19
19
|
return writerStorage.getStore();
|
|
20
20
|
}
|
|
21
|
+
var SKIP = /* @__PURE__ */ Symbol("pipeai.foreach.skip");
|
|
21
22
|
function resolveValue(value, ctx, input) {
|
|
22
23
|
if (typeof value === "function") {
|
|
23
24
|
return value(ctx, input);
|
|
@@ -257,7 +258,15 @@ var Agent = class {
|
|
|
257
258
|
const resolved = await this.resolveConfig(ctx, input);
|
|
258
259
|
const options = this.buildCallOptions(resolved, ctx, input);
|
|
259
260
|
try {
|
|
260
|
-
const onErrorOption = this.config.onError ? {
|
|
261
|
+
const onErrorOption = this.config.onError ? {
|
|
262
|
+
onError: async ({ error }) => {
|
|
263
|
+
try {
|
|
264
|
+
await this.invokeOnError(error, ctx, input);
|
|
265
|
+
} catch (handlerError) {
|
|
266
|
+
console.error(`Agent "${this.id}": onError handler threw on the stream path:`, handlerError);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} : {};
|
|
261
270
|
return streamText({
|
|
262
271
|
...options,
|
|
263
272
|
...extra,
|
|
@@ -358,12 +367,19 @@ import {
|
|
|
358
367
|
|
|
359
368
|
// src/steps/step.ts
|
|
360
369
|
var Step = class {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Observability event subtype for `type: "step"` nodes (agent / transform =
|
|
372
|
+
* `"step"`; nested / branch / foreach / repeat / parallel override).
|
|
373
|
+
* `undefined` on gate / catch / finally nodes, whose `type` IS the event type.
|
|
374
|
+
*/
|
|
375
|
+
category;
|
|
376
|
+
/**
|
|
377
|
+
* The sealed sub-workflow attached to this node, when it has one (`nested`,
|
|
378
|
+
* and workflow-target `foreach` / `repeat`). Consumed by the recursive
|
|
379
|
+
* `stepShapeHash` walk and the resume path-walk in `loadState`.
|
|
380
|
+
*/
|
|
381
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
382
|
+
nestedWorkflow;
|
|
367
383
|
/**
|
|
368
384
|
* Precedence source tag a kind writes to `state.pendingError` when it
|
|
369
385
|
* captures a thrown body error. Defaults to `"step"`; kinds with a distinct
|
|
@@ -371,19 +387,20 @@ var Step = class {
|
|
|
371
387
|
*/
|
|
372
388
|
errorSource = "step";
|
|
373
389
|
/**
|
|
374
|
-
* The step's body
|
|
375
|
-
* it to do its work
|
|
376
|
-
* `state.output` is the input on entry and becomes the output on
|
|
377
|
-
* `state.writer` is present in stream mode. The base implementation is
|
|
378
|
-
* no-op so kinds that carry no body of their own need not override it.
|
|
390
|
+
* The step's body, invoked by the run loop only after {@link shouldSkip}
|
|
391
|
+
* returned `false`. Each kind overrides it to do its work and capture errors
|
|
392
|
+
* onto state. `state.output` is the input on entry and becomes the output on
|
|
393
|
+
* exit; `state.writer` is present in stream mode. The base implementation is
|
|
394
|
+
* a no-op so kinds that carry no body of their own need not override it.
|
|
379
395
|
*/
|
|
380
396
|
async execute(_state) {
|
|
381
397
|
}
|
|
382
398
|
/**
|
|
383
|
-
* Run-policy gate
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
399
|
+
* Run-policy gate, called by the run loop before {@link execute}: return
|
|
400
|
+
* `true` when this step should be skipped silently (no hooks, no output
|
|
401
|
+
* change). The default is the "normal" policy — skip while the flow is
|
|
402
|
+
* suspended or already in error. Overridden by kinds with inverted policies:
|
|
403
|
+
* `catch` runs only when there's an error, `finally` always runs.
|
|
387
404
|
*/
|
|
388
405
|
shouldSkip(state) {
|
|
389
406
|
return !!state.suspension || !!state.pendingError;
|
|
@@ -419,7 +436,6 @@ var TransformStep = class extends Step {
|
|
|
419
436
|
this.options = options;
|
|
420
437
|
}
|
|
421
438
|
async execute(state) {
|
|
422
|
-
if (this.shouldSkip(state)) return;
|
|
423
439
|
try {
|
|
424
440
|
if (await this.applyConditionalSkip(state, this.options)) return;
|
|
425
441
|
state.output = await this.fn({
|
|
@@ -450,7 +466,6 @@ var AgentStep = class _AgentStep extends Step {
|
|
|
450
466
|
this.options = options;
|
|
451
467
|
}
|
|
452
468
|
async execute(state) {
|
|
453
|
-
if (this.shouldSkip(state)) return;
|
|
454
469
|
try {
|
|
455
470
|
if (await this.applyConditionalSkip(state, this.options)) return;
|
|
456
471
|
await _AgentStep.runAgent(state, this.agent, state.ctx, this.options);
|
|
@@ -521,6 +536,26 @@ var AgentStep = class _AgentStep extends Step {
|
|
|
521
536
|
}
|
|
522
537
|
};
|
|
523
538
|
|
|
539
|
+
// src/errors.ts
|
|
540
|
+
var WorkflowBranchError = class extends Error {
|
|
541
|
+
constructor(branchType, message) {
|
|
542
|
+
super(message);
|
|
543
|
+
this.branchType = branchType;
|
|
544
|
+
this.name = "WorkflowBranchError";
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
var WorkflowLoopError = class extends Error {
|
|
548
|
+
constructor(iterations, maxIterations) {
|
|
549
|
+
super(`Loop exceeded maximum iterations (${maxIterations})`);
|
|
550
|
+
this.iterations = iterations;
|
|
551
|
+
this.maxIterations = maxIterations;
|
|
552
|
+
this.name = "WorkflowLoopError";
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
var CHECKPOINT_STEP_ID = "::pipeai::onCheckpoint";
|
|
556
|
+
var ABORT_STEP_ID = "::pipeai::abort";
|
|
557
|
+
var GATE_RESUME_STEP_ID = "::pipeai::gate:resume";
|
|
558
|
+
|
|
524
559
|
// src/steps/branch-step.ts
|
|
525
560
|
var PredicateBranchStep = class extends Step {
|
|
526
561
|
type = "step";
|
|
@@ -535,7 +570,6 @@ var PredicateBranchStep = class extends Step {
|
|
|
535
570
|
this.cases = cases;
|
|
536
571
|
}
|
|
537
572
|
async execute(state) {
|
|
538
|
-
if (this.shouldSkip(state)) return;
|
|
539
573
|
try {
|
|
540
574
|
const ctx = state.ctx;
|
|
541
575
|
const input = state.output;
|
|
@@ -574,7 +608,6 @@ var SelectBranchStep = class extends Step {
|
|
|
574
608
|
this.config = config;
|
|
575
609
|
}
|
|
576
610
|
async execute(state) {
|
|
577
|
-
if (this.shouldSkip(state)) return;
|
|
578
611
|
try {
|
|
579
612
|
const ctx = state.ctx;
|
|
580
613
|
const input = state.output;
|
|
@@ -609,6 +642,95 @@ var SelectBranchStep = class extends Step {
|
|
|
609
642
|
}
|
|
610
643
|
};
|
|
611
644
|
|
|
645
|
+
// src/runtime.ts
|
|
646
|
+
function makeAbortError(signal) {
|
|
647
|
+
return {
|
|
648
|
+
error: signal.reason ?? new Error("Workflow aborted"),
|
|
649
|
+
stepId: ABORT_STEP_ID,
|
|
650
|
+
source: "step"
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function prependNestedPath(snapshot, index, state) {
|
|
654
|
+
const next = { ...snapshot, nestedPath: [index, ...snapshot.nestedPath ?? []] };
|
|
655
|
+
if (resolveFreezeSnapshots(state)) deepFreeze(next);
|
|
656
|
+
return next;
|
|
657
|
+
}
|
|
658
|
+
function resolveFreezeSnapshots(state) {
|
|
659
|
+
return state.runOptions?.freezeSnapshots ? true : false;
|
|
660
|
+
}
|
|
661
|
+
function pendingErrorSourceToStepType(source) {
|
|
662
|
+
switch (source) {
|
|
663
|
+
case "step":
|
|
664
|
+
return "step";
|
|
665
|
+
case "gate":
|
|
666
|
+
return "gate";
|
|
667
|
+
case "finally":
|
|
668
|
+
return "finally";
|
|
669
|
+
case "catch":
|
|
670
|
+
return "catch";
|
|
671
|
+
case "onCheckpoint":
|
|
672
|
+
return "step";
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
async function emitCheckpoint(state, opts, resumeFromIndex, stepShapeHash) {
|
|
676
|
+
if (!opts.onCheckpoint) return;
|
|
677
|
+
const willFreeze = resolveFreezeSnapshots(state);
|
|
678
|
+
const snap = {
|
|
679
|
+
version: 2,
|
|
680
|
+
kind: "checkpoint",
|
|
681
|
+
resumeFromIndex,
|
|
682
|
+
output: willFreeze ? structuredClone(state.output) : state.output,
|
|
683
|
+
stepShapeHash
|
|
684
|
+
};
|
|
685
|
+
if (willFreeze) deepFreeze(snap);
|
|
686
|
+
await opts.onCheckpoint(snap, { signal: state.abortSignal });
|
|
687
|
+
}
|
|
688
|
+
var warnedStreamOnErrorOnSuspend = false;
|
|
689
|
+
function pushWarning(state, source, stepId, error) {
|
|
690
|
+
(state.warnings ??= []).push({ source, stepId, error });
|
|
691
|
+
}
|
|
692
|
+
function fireHook(observability, state, name, event) {
|
|
693
|
+
const hook = observability?.[name];
|
|
694
|
+
if (!hook) return void 0;
|
|
695
|
+
return fireHookSlow(state, name, event, hook);
|
|
696
|
+
}
|
|
697
|
+
async function fireHookSlow(state, name, event, hook) {
|
|
698
|
+
try {
|
|
699
|
+
await hook(event);
|
|
700
|
+
return void 0;
|
|
701
|
+
} catch (e) {
|
|
702
|
+
if (name !== "onStepError") {
|
|
703
|
+
const stepId = event.stepId;
|
|
704
|
+
pushWarning(state, name, stepId, e);
|
|
705
|
+
console.error(`pipeai: ${name} hook threw for stepId "${stepId}":`, e);
|
|
706
|
+
}
|
|
707
|
+
return e;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function hasItemHooks(observability) {
|
|
711
|
+
return !!observability && !!(observability.onItemStart || observability.onItemFinish || observability.onItemError);
|
|
712
|
+
}
|
|
713
|
+
function demotePendingError(state, pe) {
|
|
714
|
+
pushWarning(state, pe.source, pe.stepId, pe.error);
|
|
715
|
+
}
|
|
716
|
+
function maybeWarnStreamOnErrorOnSuspend(result, options) {
|
|
717
|
+
if (result.status !== "suspended" || !options?.onError || warnedStreamOnErrorOnSuspend) return;
|
|
718
|
+
warnedStreamOnErrorOnSuspend = true;
|
|
719
|
+
console.warn(
|
|
720
|
+
"pipeai: stream() with options.onError suspended at a gate \u2014 onError will NOT be invoked for suspension. Discriminate via the resolved output Promise."
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
function makeRuntimeState(ctx, output, mode, opts, writer) {
|
|
724
|
+
return {
|
|
725
|
+
ctx,
|
|
726
|
+
output,
|
|
727
|
+
mode,
|
|
728
|
+
...writer ? { writer } : {},
|
|
729
|
+
runOptions: opts,
|
|
730
|
+
abortSignal: opts?.abortSignal
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
612
734
|
// src/steps/semaphore.ts
|
|
613
735
|
var Semaphore = class {
|
|
614
736
|
available;
|
|
@@ -631,17 +753,103 @@ var Semaphore = class {
|
|
|
631
753
|
this.available++;
|
|
632
754
|
}
|
|
633
755
|
}
|
|
634
|
-
async run(fn) {
|
|
635
|
-
await this.acquire();
|
|
636
|
-
try {
|
|
637
|
-
return await fn();
|
|
638
|
-
} finally {
|
|
639
|
-
this.release();
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
756
|
};
|
|
643
757
|
|
|
644
758
|
// src/steps/concurrent.ts
|
|
759
|
+
function validateConcurrency(kind, value) {
|
|
760
|
+
if (value !== void 0 && !(Number.isInteger(value) && value >= 1 || value === Infinity)) {
|
|
761
|
+
throw new Error(`${kind}: concurrency must be a positive integer or Infinity, got ${value}`);
|
|
762
|
+
}
|
|
763
|
+
return value ?? Infinity;
|
|
764
|
+
}
|
|
765
|
+
async function dispatchUnits(params) {
|
|
766
|
+
const { state, stepId, kind, units, observability, handleStream, onUnitSuccess } = params;
|
|
767
|
+
const unitStates = new Array(units.length);
|
|
768
|
+
const wantItemHooks = hasItemHooks(observability);
|
|
769
|
+
const executeUnit = async (unit, index) => {
|
|
770
|
+
const inheritStreaming = unit.isWorkflow || handleStream !== void 0;
|
|
771
|
+
const unitState = {
|
|
772
|
+
ctx: state.ctx,
|
|
773
|
+
output: unit.input,
|
|
774
|
+
mode: inheritStreaming ? state.mode : "generate",
|
|
775
|
+
writer: inheritStreaming ? state.writer : void 0,
|
|
776
|
+
abortSignal: state.abortSignal
|
|
777
|
+
};
|
|
778
|
+
unitStates[index] = unitState;
|
|
779
|
+
const unitStart = wantItemHooks ? performance.now() : 0;
|
|
780
|
+
if (wantItemHooks) {
|
|
781
|
+
await fireHook(observability, state, "onItemStart", {
|
|
782
|
+
stepId,
|
|
783
|
+
type: kind,
|
|
784
|
+
itemIndex: unit.key,
|
|
785
|
+
ctx: state.ctx,
|
|
786
|
+
input: unit.input
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
if (unit.isWorkflow) {
|
|
791
|
+
await unit.target.executeAsNested(unitState);
|
|
792
|
+
} else {
|
|
793
|
+
await AgentStep.runAgent(
|
|
794
|
+
unitState,
|
|
795
|
+
unit.target,
|
|
796
|
+
state.ctx,
|
|
797
|
+
handleStream ? { handleStream } : void 0,
|
|
798
|
+
unit.key
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
onUnitSuccess(index, unitState.output);
|
|
802
|
+
if (wantItemHooks) {
|
|
803
|
+
await fireHook(observability, state, "onItemFinish", {
|
|
804
|
+
stepId,
|
|
805
|
+
type: kind,
|
|
806
|
+
itemIndex: unit.key,
|
|
807
|
+
ctx: state.ctx,
|
|
808
|
+
output: unitState.output,
|
|
809
|
+
durationMs: performance.now() - unitStart
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
} catch (error) {
|
|
813
|
+
if (wantItemHooks) {
|
|
814
|
+
await fireHook(observability, state, "onItemError", {
|
|
815
|
+
stepId,
|
|
816
|
+
type: kind,
|
|
817
|
+
itemIndex: unit.key,
|
|
818
|
+
ctx: state.ctx,
|
|
819
|
+
error,
|
|
820
|
+
durationMs: performance.now() - unitStart
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
throw error;
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
const sem = new Semaphore(params.concurrency);
|
|
827
|
+
const failures = [];
|
|
828
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
829
|
+
for (let i = 0; i < units.length; i++) {
|
|
830
|
+
if (state.abortSignal?.aborted) break;
|
|
831
|
+
await sem.acquire();
|
|
832
|
+
if (state.abortSignal?.aborted) {
|
|
833
|
+
sem.release();
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
const index = i;
|
|
837
|
+
const unit = (async () => {
|
|
838
|
+
try {
|
|
839
|
+
await executeUnit(units[index], index);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
failures.push({ key: units[index].key, index, error });
|
|
842
|
+
} finally {
|
|
843
|
+
sem.release();
|
|
844
|
+
}
|
|
845
|
+
})();
|
|
846
|
+
inflight.add(unit);
|
|
847
|
+
void unit.finally(() => inflight.delete(unit));
|
|
848
|
+
}
|
|
849
|
+
await Promise.all(inflight);
|
|
850
|
+
failures.sort((a, b) => a.index - b.index);
|
|
851
|
+
return reconcileUnits(state, stepId, failures, units.length, (i) => units[i].key, unitStates, state.abortSignal);
|
|
852
|
+
}
|
|
645
853
|
function reconcileUnits(state, id, failures, count, keyAt, unitStates, signal) {
|
|
646
854
|
for (let i = 0; i < count; i++) {
|
|
647
855
|
const us = unitStates[i];
|
|
@@ -669,128 +877,50 @@ function reconcileUnits(state, id, failures, count, keyAt, unitStates, signal) {
|
|
|
669
877
|
var ForeachStep = class extends Step {
|
|
670
878
|
type = "step";
|
|
671
879
|
category = "foreach";
|
|
672
|
-
id;
|
|
673
880
|
nestedWorkflow;
|
|
881
|
+
id;
|
|
674
882
|
target;
|
|
675
883
|
concurrency;
|
|
676
884
|
onError;
|
|
677
885
|
handleStream;
|
|
678
886
|
isWorkflow;
|
|
679
|
-
inheritStreaming;
|
|
680
887
|
observability;
|
|
681
888
|
constructor(target, options, observability) {
|
|
682
889
|
super();
|
|
683
|
-
if (options?.concurrency !== void 0 && !(Number.isInteger(options.concurrency) && options.concurrency >= 1 || options.concurrency === Infinity)) {
|
|
684
|
-
throw new Error(`foreach: concurrency must be a positive integer or Infinity, got ${options.concurrency}`);
|
|
685
|
-
}
|
|
686
890
|
this.target = target;
|
|
687
|
-
this.concurrency = options?.concurrency
|
|
891
|
+
this.concurrency = validateConcurrency("foreach", options?.concurrency);
|
|
688
892
|
this.onError = options?.onError;
|
|
689
893
|
this.handleStream = options?.handleStream;
|
|
690
894
|
this.observability = observability;
|
|
691
895
|
this.isWorkflow = target instanceof SealedWorkflow;
|
|
692
|
-
this.inheritStreaming = this.isWorkflow || this.handleStream !== void 0;
|
|
693
896
|
const defaultId = this.isWorkflow ? target.id ?? "foreach" : `foreach:${target.id}`;
|
|
694
897
|
this.id = options?.id ?? defaultId;
|
|
695
898
|
this.nestedWorkflow = this.isWorkflow ? target : void 0;
|
|
696
899
|
}
|
|
697
900
|
async execute(state) {
|
|
698
|
-
if (this.shouldSkip(state)) return;
|
|
699
901
|
try {
|
|
700
902
|
const items = state.output;
|
|
701
903
|
if (!Array.isArray(items)) {
|
|
702
904
|
throw new Error(`foreach "${this.id}": expected array input, got ${typeof items}`);
|
|
703
905
|
}
|
|
704
906
|
const results = new Array(items.length);
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
};
|
|
716
|
-
itemStates[index] = itemState;
|
|
717
|
-
const itemStart = wantItemHooks ? performance.now() : 0;
|
|
718
|
-
if (wantItemHooks) {
|
|
719
|
-
await fireHook(this.observability, state, "onItemStart", {
|
|
720
|
-
stepId: this.id,
|
|
721
|
-
type: "foreach",
|
|
722
|
-
itemIndex: index,
|
|
723
|
-
ctx: state.ctx,
|
|
724
|
-
input: item
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
try {
|
|
728
|
-
if (this.isWorkflow) {
|
|
729
|
-
await this.target.executeAsNested(itemState);
|
|
730
|
-
} else {
|
|
731
|
-
await AgentStep.runAgent(
|
|
732
|
-
itemState,
|
|
733
|
-
this.target,
|
|
734
|
-
state.ctx,
|
|
735
|
-
this.handleStream ? { handleStream: this.handleStream } : void 0,
|
|
736
|
-
index
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
results[index] = itemState.output;
|
|
740
|
-
if (wantItemHooks) {
|
|
741
|
-
await fireHook(this.observability, state, "onItemFinish", {
|
|
742
|
-
stepId: this.id,
|
|
743
|
-
type: "foreach",
|
|
744
|
-
itemIndex: index,
|
|
745
|
-
ctx: state.ctx,
|
|
746
|
-
output: itemState.output,
|
|
747
|
-
durationMs: performance.now() - itemStart
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
} catch (error) {
|
|
751
|
-
if (wantItemHooks) {
|
|
752
|
-
await fireHook(this.observability, state, "onItemError", {
|
|
753
|
-
stepId: this.id,
|
|
754
|
-
type: "foreach",
|
|
755
|
-
itemIndex: index,
|
|
756
|
-
ctx: state.ctx,
|
|
757
|
-
error,
|
|
758
|
-
durationMs: performance.now() - itemStart
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
throw error;
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
const sem = new Semaphore(this.concurrency);
|
|
765
|
-
const failures = [];
|
|
766
|
-
const inflight = /* @__PURE__ */ new Set();
|
|
767
|
-
for (let i = 0; i < items.length; i++) {
|
|
768
|
-
if (state.abortSignal?.aborted) break;
|
|
769
|
-
await sem.acquire();
|
|
770
|
-
if (state.abortSignal?.aborted) {
|
|
771
|
-
sem.release();
|
|
772
|
-
break;
|
|
907
|
+
const failures = await dispatchUnits({
|
|
908
|
+
state,
|
|
909
|
+
stepId: this.id,
|
|
910
|
+
kind: "foreach",
|
|
911
|
+
units: items.map((item, i) => ({ key: i, input: item, target: this.target, isWorkflow: this.isWorkflow })),
|
|
912
|
+
concurrency: this.concurrency,
|
|
913
|
+
observability: this.observability,
|
|
914
|
+
handleStream: this.handleStream,
|
|
915
|
+
onUnitSuccess: (index, output) => {
|
|
916
|
+
results[index] = output;
|
|
773
917
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
await executeItem(items[index], index);
|
|
778
|
-
} catch (error) {
|
|
779
|
-
failures.push({ key: index, index, error });
|
|
780
|
-
} finally {
|
|
781
|
-
sem.release();
|
|
782
|
-
}
|
|
783
|
-
})();
|
|
784
|
-
inflight.add(unit);
|
|
785
|
-
void unit.finally(() => inflight.delete(unit));
|
|
786
|
-
}
|
|
787
|
-
await Promise.all(inflight);
|
|
788
|
-
failures.sort((a, b) => a.index - b.index);
|
|
789
|
-
const nonGateFailures = reconcileUnits(state, this.id, failures, items.length, (i) => i, itemStates, state.abortSignal);
|
|
790
|
-
for (const { index, error } of nonGateFailures) {
|
|
918
|
+
});
|
|
919
|
+
const skipped = /* @__PURE__ */ new Set();
|
|
920
|
+
for (const { index, error } of failures) {
|
|
791
921
|
if (!this.onError) throw error;
|
|
792
922
|
const recovered = await this.onError({ error, item: items[index], index, ctx: state.ctx });
|
|
793
|
-
if (recovered ===
|
|
923
|
+
if (recovered === SKIP) {
|
|
794
924
|
skipped.add(index);
|
|
795
925
|
} else {
|
|
796
926
|
results[index] = recovered;
|
|
@@ -810,7 +940,6 @@ var ParallelStep = class extends Step {
|
|
|
810
940
|
id;
|
|
811
941
|
entries;
|
|
812
942
|
isTuple;
|
|
813
|
-
branchCount;
|
|
814
943
|
concurrency;
|
|
815
944
|
onError;
|
|
816
945
|
handleStream;
|
|
@@ -818,112 +947,30 @@ var ParallelStep = class extends Step {
|
|
|
818
947
|
constructor(branches, options, observability) {
|
|
819
948
|
super();
|
|
820
949
|
this.isTuple = Array.isArray(branches);
|
|
821
|
-
this.entries = this.isTuple ? branches.map((target, i) => ({ key: i, index: i, target })) : Object.entries(branches).map(([k, t], i) => ({ key: k, index: i, target: t }));
|
|
822
|
-
this.
|
|
823
|
-
const requestedConcurrency = options?.concurrency;
|
|
824
|
-
if (requestedConcurrency !== void 0 && !(Number.isInteger(requestedConcurrency) && requestedConcurrency >= 1 || requestedConcurrency === Infinity)) {
|
|
825
|
-
throw new Error(`parallel: concurrency must be a positive integer or Infinity, got ${requestedConcurrency}`);
|
|
826
|
-
}
|
|
827
|
-
this.concurrency = requestedConcurrency ?? Infinity;
|
|
950
|
+
this.entries = this.isTuple ? branches.map((target, i) => ({ key: i, index: i, target, isWorkflow: target instanceof SealedWorkflow })) : Object.entries(branches).map(([k, t], i) => ({ key: k, index: i, target: t, isWorkflow: t instanceof SealedWorkflow }));
|
|
951
|
+
this.concurrency = validateConcurrency("parallel", options?.concurrency);
|
|
828
952
|
this.onError = options?.onError;
|
|
829
953
|
this.handleStream = options?.handleStream;
|
|
830
954
|
this.observability = observability;
|
|
831
955
|
this.id = options?.id ?? (this.isTuple ? "parallel:tuple" : "parallel:record");
|
|
832
956
|
}
|
|
833
957
|
async execute(state) {
|
|
834
|
-
if (this.shouldSkip(state)) return;
|
|
835
958
|
try {
|
|
836
959
|
const input = state.output;
|
|
837
|
-
const results = this.isTuple ? new Array(this.
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
abortSignal: state.abortSignal
|
|
849
|
-
};
|
|
850
|
-
branchStates[index] = branchState;
|
|
851
|
-
const branchStart = wantItemHooks ? performance.now() : 0;
|
|
852
|
-
const itemIndex = this.isTuple ? index : key;
|
|
853
|
-
if (wantItemHooks) {
|
|
854
|
-
await fireHook(this.observability, state, "onItemStart", {
|
|
855
|
-
stepId: this.id,
|
|
856
|
-
type: "parallel",
|
|
857
|
-
itemIndex,
|
|
858
|
-
ctx: state.ctx,
|
|
859
|
-
input
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
try {
|
|
863
|
-
if (isWorkflowBranch) {
|
|
864
|
-
await target.executeAsNested(branchState);
|
|
865
|
-
} else {
|
|
866
|
-
await AgentStep.runAgent(
|
|
867
|
-
branchState,
|
|
868
|
-
target,
|
|
869
|
-
state.ctx,
|
|
870
|
-
this.handleStream ? { handleStream: this.handleStream } : void 0,
|
|
871
|
-
itemIndex
|
|
872
|
-
);
|
|
873
|
-
}
|
|
874
|
-
results[key] = branchState.output;
|
|
875
|
-
if (wantItemHooks) {
|
|
876
|
-
await fireHook(this.observability, state, "onItemFinish", {
|
|
877
|
-
stepId: this.id,
|
|
878
|
-
type: "parallel",
|
|
879
|
-
itemIndex,
|
|
880
|
-
ctx: state.ctx,
|
|
881
|
-
output: branchState.output,
|
|
882
|
-
durationMs: performance.now() - branchStart
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
} catch (error) {
|
|
886
|
-
if (wantItemHooks) {
|
|
887
|
-
await fireHook(this.observability, state, "onItemError", {
|
|
888
|
-
stepId: this.id,
|
|
889
|
-
type: "parallel",
|
|
890
|
-
itemIndex,
|
|
891
|
-
ctx: state.ctx,
|
|
892
|
-
error,
|
|
893
|
-
durationMs: performance.now() - branchStart
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
throw error;
|
|
897
|
-
}
|
|
898
|
-
};
|
|
899
|
-
const keyAt = (i) => this.entries[i].key;
|
|
900
|
-
const sem = new Semaphore(this.concurrency);
|
|
901
|
-
const failures = [];
|
|
902
|
-
const inflight = /* @__PURE__ */ new Set();
|
|
903
|
-
for (let i = 0; i < this.branchCount; i++) {
|
|
904
|
-
if (state.abortSignal?.aborted) break;
|
|
905
|
-
await sem.acquire();
|
|
906
|
-
if (state.abortSignal?.aborted) {
|
|
907
|
-
sem.release();
|
|
908
|
-
break;
|
|
960
|
+
const results = this.isTuple ? new Array(this.entries.length) : {};
|
|
961
|
+
const failures = await dispatchUnits({
|
|
962
|
+
state,
|
|
963
|
+
stepId: this.id,
|
|
964
|
+
kind: "parallel",
|
|
965
|
+
units: this.entries.map((e) => ({ key: e.key, input, target: e.target, isWorkflow: e.isWorkflow })),
|
|
966
|
+
concurrency: this.concurrency,
|
|
967
|
+
observability: this.observability,
|
|
968
|
+
handleStream: this.handleStream,
|
|
969
|
+
onUnitSuccess: (index, output) => {
|
|
970
|
+
results[this.entries[index].key] = output;
|
|
909
971
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
try {
|
|
913
|
-
await executeBranch(this.entries[index]);
|
|
914
|
-
} catch (error) {
|
|
915
|
-
failures.push({ key: keyAt(index), index, error });
|
|
916
|
-
} finally {
|
|
917
|
-
sem.release();
|
|
918
|
-
}
|
|
919
|
-
})();
|
|
920
|
-
inflight.add(unit);
|
|
921
|
-
void unit.finally(() => inflight.delete(unit));
|
|
922
|
-
}
|
|
923
|
-
await Promise.all(inflight);
|
|
924
|
-
failures.sort((a, b) => a.index - b.index);
|
|
925
|
-
const nonGateFailures = reconcileUnits(state, this.id, failures, this.branchCount, keyAt, branchStates, state.abortSignal);
|
|
926
|
-
for (const { key, index, error } of nonGateFailures) {
|
|
972
|
+
});
|
|
973
|
+
for (const { key, index, error } of failures) {
|
|
927
974
|
if (!this.onError) throw error;
|
|
928
975
|
const recovered = await this.onError({
|
|
929
976
|
error,
|
|
@@ -931,11 +978,7 @@ var ParallelStep = class extends Step {
|
|
|
931
978
|
index: this.isTuple ? index : void 0,
|
|
932
979
|
ctx: state.ctx
|
|
933
980
|
});
|
|
934
|
-
|
|
935
|
-
results[key] = void 0;
|
|
936
|
-
} else {
|
|
937
|
-
results[key] = recovered;
|
|
938
|
-
}
|
|
981
|
+
results[key] = recovered === SKIP ? void 0 : recovered;
|
|
939
982
|
}
|
|
940
983
|
state.output = results;
|
|
941
984
|
} catch (error) {
|
|
@@ -955,25 +998,25 @@ var GateStep = class extends Step {
|
|
|
955
998
|
merge;
|
|
956
999
|
payload;
|
|
957
1000
|
condition;
|
|
958
|
-
constructor(id,
|
|
1001
|
+
constructor(id, options) {
|
|
959
1002
|
super();
|
|
960
1003
|
this.id = id;
|
|
961
|
-
this.payload = payload;
|
|
962
|
-
this.schema = schema;
|
|
963
|
-
this.condition = condition;
|
|
964
|
-
this.merge = merge;
|
|
1004
|
+
this.payload = options?.payload;
|
|
1005
|
+
this.schema = options?.schema;
|
|
1006
|
+
this.condition = options?.condition;
|
|
1007
|
+
this.merge = options?.merge;
|
|
965
1008
|
}
|
|
966
1009
|
async execute(state) {
|
|
967
|
-
if (this.shouldSkip(state)) return;
|
|
968
1010
|
try {
|
|
969
|
-
|
|
1011
|
+
const params = { ctx: state.ctx, input: state.output };
|
|
1012
|
+
if (this.condition && !await this.condition(params)) return;
|
|
970
1013
|
const snapshot = {
|
|
971
1014
|
version: 2,
|
|
972
1015
|
kind: "gate",
|
|
973
1016
|
resumeFromIndex: state.stepIndex ?? -1,
|
|
974
1017
|
output: state.output,
|
|
975
1018
|
gateId: this.id,
|
|
976
|
-
gatePayload: await this.payload(state
|
|
1019
|
+
gatePayload: this.payload ? await this.payload(params) : state.output
|
|
977
1020
|
};
|
|
978
1021
|
state.suspension = snapshot;
|
|
979
1022
|
if (resolveFreezeSnapshots(state)) deepFreeze(snapshot);
|
|
@@ -999,7 +1042,6 @@ var CatchStep = class extends Step {
|
|
|
999
1042
|
return !!state.suspension || !state.pendingError || !!state.checkpointFailed;
|
|
1000
1043
|
}
|
|
1001
1044
|
async execute(state) {
|
|
1002
|
-
if (this.shouldSkip(state)) return;
|
|
1003
1045
|
const handled = state.pendingError;
|
|
1004
1046
|
state.output = await this.catchFn({
|
|
1005
1047
|
error: handled.error,
|
|
@@ -1027,7 +1069,6 @@ var FinallyStep = class extends Step {
|
|
|
1027
1069
|
return false;
|
|
1028
1070
|
}
|
|
1029
1071
|
async execute(state) {
|
|
1030
|
-
if (this.shouldSkip(state)) return;
|
|
1031
1072
|
await this.fn({ ctx: state.ctx });
|
|
1032
1073
|
}
|
|
1033
1074
|
};
|
|
@@ -1036,8 +1077,8 @@ var FinallyStep = class extends Step {
|
|
|
1036
1077
|
var NestedWorkflowStep = class extends Step {
|
|
1037
1078
|
type = "step";
|
|
1038
1079
|
category = "nested";
|
|
1039
|
-
id;
|
|
1040
1080
|
nestedWorkflow;
|
|
1081
|
+
id;
|
|
1041
1082
|
options;
|
|
1042
1083
|
constructor(id, workflow, options) {
|
|
1043
1084
|
super();
|
|
@@ -1064,7 +1105,6 @@ var NestedWorkflowStep = class extends Step {
|
|
|
1064
1105
|
}
|
|
1065
1106
|
return;
|
|
1066
1107
|
}
|
|
1067
|
-
if (this.shouldSkip(state)) return;
|
|
1068
1108
|
const myIndex = state.stepIndex ?? -1;
|
|
1069
1109
|
try {
|
|
1070
1110
|
if (await this.applyConditionalSkip(state, this.options)) return;
|
|
@@ -1080,8 +1120,8 @@ var NestedWorkflowStep = class extends Step {
|
|
|
1080
1120
|
var RepeatStep = class extends Step {
|
|
1081
1121
|
type = "step";
|
|
1082
1122
|
category = "repeat";
|
|
1083
|
-
id;
|
|
1084
1123
|
nestedWorkflow;
|
|
1124
|
+
id;
|
|
1085
1125
|
target;
|
|
1086
1126
|
predicate;
|
|
1087
1127
|
maxIterations;
|
|
@@ -1096,7 +1136,6 @@ var RepeatStep = class extends Step {
|
|
|
1096
1136
|
this.nestedWorkflow = isWorkflow ? target : void 0;
|
|
1097
1137
|
}
|
|
1098
1138
|
async execute(state) {
|
|
1099
|
-
if (this.shouldSkip(state)) return;
|
|
1100
1139
|
try {
|
|
1101
1140
|
const ctx = state.ctx;
|
|
1102
1141
|
for (let i = 1; i <= this.maxIterations; i++) {
|
|
@@ -1118,107 +1157,7 @@ var RepeatStep = class extends Step {
|
|
|
1118
1157
|
}
|
|
1119
1158
|
};
|
|
1120
1159
|
|
|
1121
|
-
// src/runtime.ts
|
|
1122
|
-
function resolveFreezeSnapshots(state) {
|
|
1123
|
-
return state.runOptions?.freezeSnapshots ? true : false;
|
|
1124
|
-
}
|
|
1125
|
-
function pendingErrorSourceToStepType(source) {
|
|
1126
|
-
switch (source) {
|
|
1127
|
-
case "step":
|
|
1128
|
-
return "step";
|
|
1129
|
-
case "gate":
|
|
1130
|
-
return "gate";
|
|
1131
|
-
case "finally":
|
|
1132
|
-
return "finally";
|
|
1133
|
-
case "catch":
|
|
1134
|
-
return "catch";
|
|
1135
|
-
case "onCheckpoint":
|
|
1136
|
-
return "step";
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
async function emitCheckpoint(state, opts, resumeFromIndex, stepShapeHash) {
|
|
1140
|
-
if (!opts.onCheckpoint) return;
|
|
1141
|
-
const willFreeze = resolveFreezeSnapshots(state);
|
|
1142
|
-
const snap = {
|
|
1143
|
-
version: 2,
|
|
1144
|
-
kind: "checkpoint",
|
|
1145
|
-
resumeFromIndex,
|
|
1146
|
-
output: willFreeze ? structuredClone(state.output) : state.output,
|
|
1147
|
-
stepShapeHash
|
|
1148
|
-
};
|
|
1149
|
-
if (willFreeze) deepFreeze(snap);
|
|
1150
|
-
await opts.onCheckpoint(snap, { signal: state.abortSignal });
|
|
1151
|
-
}
|
|
1152
|
-
var warnedStreamOnErrorOnSuspend = false;
|
|
1153
|
-
function pushWarning(state, source, stepId, error) {
|
|
1154
|
-
(state.warnings ??= []).push({ source, stepId, error });
|
|
1155
|
-
}
|
|
1156
|
-
function fireHook(observability, state, name, event) {
|
|
1157
|
-
const hook = observability?.[name];
|
|
1158
|
-
if (!hook) return void 0;
|
|
1159
|
-
return fireHookSlow(state, name, event, hook);
|
|
1160
|
-
}
|
|
1161
|
-
async function fireHookSlow(state, name, event, hook) {
|
|
1162
|
-
try {
|
|
1163
|
-
await hook(event);
|
|
1164
|
-
return void 0;
|
|
1165
|
-
} catch (e) {
|
|
1166
|
-
if (name !== "onStepError") {
|
|
1167
|
-
const stepId = event.stepId;
|
|
1168
|
-
pushWarning(state, name, stepId, e);
|
|
1169
|
-
console.error(`pipeai: ${name} hook threw for stepId "${stepId}":`, e);
|
|
1170
|
-
}
|
|
1171
|
-
return e;
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
function hasItemHooks(observability) {
|
|
1175
|
-
return !!observability && !!(observability.onItemStart || observability.onItemFinish || observability.onItemError);
|
|
1176
|
-
}
|
|
1177
|
-
function demotePendingError(state, pe) {
|
|
1178
|
-
pushWarning(state, pe.source, pe.stepId, pe.error);
|
|
1179
|
-
}
|
|
1180
|
-
function maybeWarnStreamOnErrorOnSuspend(result, options) {
|
|
1181
|
-
if (result.status !== "suspended" || !options?.onError || warnedStreamOnErrorOnSuspend) return;
|
|
1182
|
-
warnedStreamOnErrorOnSuspend = true;
|
|
1183
|
-
console.warn(
|
|
1184
|
-
"pipeai: stream() with options.onError suspended at a gate \u2014 onError will NOT be invoked for suspension. Discriminate via the resolved output Promise."
|
|
1185
|
-
);
|
|
1186
|
-
}
|
|
1187
|
-
function makeRuntimeState(ctx, output, mode, opts, writer) {
|
|
1188
|
-
return {
|
|
1189
|
-
ctx,
|
|
1190
|
-
output,
|
|
1191
|
-
mode,
|
|
1192
|
-
...writer ? { writer } : {},
|
|
1193
|
-
runOptions: opts,
|
|
1194
|
-
abortSignal: opts?.abortSignal
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
1160
|
// src/workflow.ts
|
|
1199
|
-
var WorkflowBranchError = class extends Error {
|
|
1200
|
-
constructor(branchType, message) {
|
|
1201
|
-
super(message);
|
|
1202
|
-
this.branchType = branchType;
|
|
1203
|
-
this.name = "WorkflowBranchError";
|
|
1204
|
-
}
|
|
1205
|
-
};
|
|
1206
|
-
var WorkflowLoopError = class extends Error {
|
|
1207
|
-
constructor(iterations, maxIterations) {
|
|
1208
|
-
super(`Loop exceeded maximum iterations (${maxIterations})`);
|
|
1209
|
-
this.iterations = iterations;
|
|
1210
|
-
this.maxIterations = maxIterations;
|
|
1211
|
-
this.name = "WorkflowLoopError";
|
|
1212
|
-
}
|
|
1213
|
-
};
|
|
1214
|
-
var CHECKPOINT_STEP_ID = "::pipeai::onCheckpoint";
|
|
1215
|
-
var ABORT_STEP_ID = "::pipeai::abort";
|
|
1216
|
-
var GATE_RESUME_STEP_ID = "::pipeai::gate:resume";
|
|
1217
|
-
function prependNestedPath(snapshot, index, state) {
|
|
1218
|
-
const next = { ...snapshot, nestedPath: [index, ...snapshot.nestedPath ?? []] };
|
|
1219
|
-
if (resolveFreezeSnapshots(state)) deepFreeze(next);
|
|
1220
|
-
return next;
|
|
1221
|
-
}
|
|
1222
1161
|
function migrateSnapshot(legacy) {
|
|
1223
1162
|
if (legacy.version !== 1) {
|
|
1224
1163
|
throw new Error(`migrateSnapshot: expected v1 snapshot, got version ${legacy.version}`);
|
|
@@ -1232,30 +1171,15 @@ function migrateSnapshot(legacy) {
|
|
|
1232
1171
|
gatePayload: legacy.gatePayload
|
|
1233
1172
|
};
|
|
1234
1173
|
}
|
|
1235
|
-
function getObservabilityType(node) {
|
|
1236
|
-
if (node.type !== "step") return node.type;
|
|
1237
|
-
return node.category ?? "step";
|
|
1238
|
-
}
|
|
1239
|
-
function getNestedWorkflows(node) {
|
|
1240
|
-
switch (node.type) {
|
|
1241
|
-
case "step":
|
|
1242
|
-
return node.nestedWorkflow ? [node.nestedWorkflow] : [];
|
|
1243
|
-
case "gate":
|
|
1244
|
-
case "catch":
|
|
1245
|
-
case "finally":
|
|
1246
|
-
return [];
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
1174
|
var SealedWorkflow = class _SealedWorkflow {
|
|
1250
1175
|
id;
|
|
1251
1176
|
steps;
|
|
1252
1177
|
observability;
|
|
1253
1178
|
// Memoized — see ensureDuplicateCheck().
|
|
1254
1179
|
duplicateCheckPassed = false;
|
|
1255
|
-
// Memoized lazily per terminal instance
|
|
1256
|
-
//
|
|
1257
|
-
|
|
1258
|
-
_cachedCheckpointableStepCount;
|
|
1180
|
+
// Memoized lazily per terminal instance: the executable / checkpointable step
|
|
1181
|
+
// counts (one walk) and the recursive shape hash (separate — it's expensive).
|
|
1182
|
+
_stepCounts;
|
|
1259
1183
|
_cachedStepShapeHash;
|
|
1260
1184
|
constructor(steps, id, observability) {
|
|
1261
1185
|
this.steps = steps;
|
|
@@ -1290,39 +1214,26 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1290
1214
|
}
|
|
1291
1215
|
this.duplicateCheckPassed = true;
|
|
1292
1216
|
}
|
|
1293
|
-
// ── shape-hash
|
|
1217
|
+
// ── step counts + shape-hash (memoized) ────────────────────────────
|
|
1294
1218
|
/**
|
|
1295
|
-
*
|
|
1296
|
-
*
|
|
1297
|
-
*
|
|
1298
|
-
* `type
|
|
1219
|
+
* Two cadence inputs from a single walk:
|
|
1220
|
+
* - `executable` — nodes that aren't `catch` / `finally`. A graph-size
|
|
1221
|
+
* proxy for the catastrophe threshold in {@link validateRunOptions}.
|
|
1222
|
+
* - `checkpointable` — `type === "step"` nodes only (this includes
|
|
1223
|
+
* branch / foreach / repeat / parallel / nested). Drives the checkpoint
|
|
1224
|
+
* auto-cadence denominator: gates suspend/skip and never reach the
|
|
1225
|
+
* checkpoint block, so counting them would dilute the "~4 checkpoints
|
|
1226
|
+
* across the run" target.
|
|
1299
1227
|
*/
|
|
1300
|
-
get
|
|
1301
|
-
if (this.
|
|
1302
|
-
let
|
|
1228
|
+
get stepCounts() {
|
|
1229
|
+
if (this._stepCounts) return this._stepCounts;
|
|
1230
|
+
let executable = 0;
|
|
1231
|
+
let checkpointable = 0;
|
|
1303
1232
|
for (const s of this.steps) {
|
|
1304
|
-
if (s.type !== "catch" && s.type !== "finally")
|
|
1233
|
+
if (s.type !== "catch" && s.type !== "finally") executable++;
|
|
1234
|
+
if (s.type === "step") checkpointable++;
|
|
1305
1235
|
}
|
|
1306
|
-
this.
|
|
1307
|
-
return n;
|
|
1308
|
-
}
|
|
1309
|
-
/**
|
|
1310
|
-
* Count of *checkpointable* nodes — `type === "step"` only (this includes
|
|
1311
|
-
* `branch`/`foreach`/`repeat`/`parallel`/`nested`, all internally `step`).
|
|
1312
|
-
* Drives the checkpoint auto-cadence denominator. Distinct from
|
|
1313
|
-
* {@link cachedExecutableStepCount}, which also counts `gate` nodes: gates
|
|
1314
|
-
* suspend/skip and never reach the checkpoint block, so the runtime
|
|
1315
|
-
* `executableStepsSeen` counter never advances on them. Counting gates in
|
|
1316
|
-
* the denominator would dilute the "~4 checkpoints across the run" target.
|
|
1317
|
-
*/
|
|
1318
|
-
get cachedCheckpointableStepCount() {
|
|
1319
|
-
if (this._cachedCheckpointableStepCount !== void 0) return this._cachedCheckpointableStepCount;
|
|
1320
|
-
let n = 0;
|
|
1321
|
-
for (const s of this.steps) {
|
|
1322
|
-
if (s.type === "step") n++;
|
|
1323
|
-
}
|
|
1324
|
-
this._cachedCheckpointableStepCount = n;
|
|
1325
|
-
return n;
|
|
1236
|
+
return this._stepCounts = { executable, checkpointable };
|
|
1326
1237
|
}
|
|
1327
1238
|
/** @internal — used by `computeStepShapeHash` to descend nested workflows. */
|
|
1328
1239
|
getStepsForShapeHash() {
|
|
@@ -1330,7 +1241,7 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1330
1241
|
}
|
|
1331
1242
|
get cachedStepShapeHash() {
|
|
1332
1243
|
if (this._cachedStepShapeHash !== void 0) return this._cachedStepShapeHash;
|
|
1333
|
-
const getNested = (node) =>
|
|
1244
|
+
const getNested = (node) => node.nestedWorkflow ? [node.nestedWorkflow] : [];
|
|
1334
1245
|
this._cachedStepShapeHash = computeStepShapeHash(
|
|
1335
1246
|
this.steps,
|
|
1336
1247
|
getNested
|
|
@@ -1341,21 +1252,29 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1341
1252
|
* Validate user-provided RunOptions before a run begins. Throws on
|
|
1342
1253
|
* outright errors and on the loud-disaster combo (`freezeSnapshots: true
|
|
1343
1254
|
* + checkpointEvery: 1` on a workflow of 8+ steps). Warns once on the
|
|
1344
|
-
* merely-suspicious combo (`freezeSnapshots: true + cadence <= 2`)
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1255
|
+
* merely-suspicious combo (`freezeSnapshots: true + cadence <= 2`), and on
|
|
1256
|
+
* checkpoint-cadence options set without an `onCheckpoint` sink (a no-op
|
|
1257
|
+
* that usually signals a forgotten sink).
|
|
1347
1258
|
*/
|
|
1348
1259
|
validateRunOptions(opts) {
|
|
1349
1260
|
if (!opts) return;
|
|
1350
|
-
if (!opts.onCheckpoint) return;
|
|
1351
1261
|
if (opts.checkpointEvery !== void 0 && opts.checkpointWhen !== void 0) {
|
|
1352
1262
|
throw new Error("RunOptions: checkpointEvery and checkpointWhen are mutually exclusive");
|
|
1353
1263
|
}
|
|
1354
1264
|
if (opts.checkpointEvery !== void 0 && (!Number.isInteger(opts.checkpointEvery) || opts.checkpointEvery < 1)) {
|
|
1355
1265
|
throw new Error(`RunOptions: checkpointEvery must be a positive integer, got ${opts.checkpointEvery}`);
|
|
1356
1266
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1267
|
+
if (!opts.onCheckpoint) {
|
|
1268
|
+
if (opts.checkpointEvery !== void 0 || opts.checkpointWhen !== void 0) {
|
|
1269
|
+
warnOnce(
|
|
1270
|
+
"pipeai:checkpoint-without-sink",
|
|
1271
|
+
"pipeai: checkpointEvery/checkpointWhen set without onCheckpoint \u2014 no checkpoints will fire. Did you forget the onCheckpoint sink?"
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
const length = this.stepCounts.executable;
|
|
1277
|
+
const cadence = opts.checkpointEvery ?? Math.max(1, Math.ceil(this.stepCounts.checkpointable / 4));
|
|
1359
1278
|
if (opts.freezeSnapshots && opts.freezeSnapshots !== "iAcceptThePerformanceCost" && cadence === 1 && length >= 8) {
|
|
1360
1279
|
throw new Error(
|
|
1361
1280
|
`freezeSnapshots+checkpointEvery:1 on a ${length}-step workflow is reliably catastrophic. Set checkpointEvery >= 5, freezeSnapshots: false, or pass "iAcceptThePerformanceCost".`
|
|
@@ -1369,6 +1288,13 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1369
1288
|
}
|
|
1370
1289
|
}
|
|
1371
1290
|
// ── Observability helpers ─────────────────────────────────────
|
|
1291
|
+
/** Observability event `type` for a node: a `type: "step"` node reports its
|
|
1292
|
+
* `category` (agent / transform default to `"step"`); every other node's
|
|
1293
|
+
* `type` IS the event type. */
|
|
1294
|
+
obsEventType(node) {
|
|
1295
|
+
if (node.type !== "step") return node.type;
|
|
1296
|
+
return node.category ?? "step";
|
|
1297
|
+
}
|
|
1372
1298
|
/**
|
|
1373
1299
|
* Fire an observability hook safely. Returns `undefined` synchronously when
|
|
1374
1300
|
* no hook is registered — avoiding the promise wrapper + microtask that an
|
|
@@ -1382,17 +1308,14 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1382
1308
|
* Returns the hook's thrown error if any; undefined otherwise. Callers
|
|
1383
1309
|
* `await` the result — `await undefined` is sync, so the no-hook path
|
|
1384
1310
|
* stays allocation-free.
|
|
1311
|
+
*
|
|
1312
|
+
* Thin delegate to the free `fireHook` (which takes an explicit
|
|
1313
|
+
* observability), kept as a method so the loop's many `this.fireHook` call
|
|
1314
|
+
* sites stay unchanged.
|
|
1385
1315
|
*/
|
|
1386
|
-
// Thin delegates to the free `fireHook` / `hasItemHooks` (which take an
|
|
1387
|
-
// explicit observability). Kept as methods so the loop's many `this.fireHook`
|
|
1388
|
-
// call sites stay unchanged; bare `fireHook` / `hasItemHooks` below resolve to
|
|
1389
|
-
// the module-level functions, not these members.
|
|
1390
1316
|
fireHook(state, name, event) {
|
|
1391
1317
|
return fireHook(this.observability, state, name, event);
|
|
1392
1318
|
}
|
|
1393
|
-
hasItemHooks() {
|
|
1394
|
-
return hasItemHooks(this.observability);
|
|
1395
|
-
}
|
|
1396
1319
|
/**
|
|
1397
1320
|
* Fire `onStepError` for a step-body failure and honor the documented
|
|
1398
1321
|
* cause-attachment contract uniformly across every firing path (step, gate,
|
|
@@ -1497,30 +1420,15 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1497
1420
|
if (opts !== void 0 && state.runOptions === void 0) {
|
|
1498
1421
|
state.runOptions = opts;
|
|
1499
1422
|
}
|
|
1500
|
-
const ckptCadence = opts?.onCheckpoint && opts.checkpointWhen === void 0 ? opts.checkpointEvery ?? Math.max(1, Math.ceil(this.
|
|
1501
|
-
|
|
1423
|
+
const ckptCadence = opts?.onCheckpoint && opts.checkpointWhen === void 0 ? opts.checkpointEvery ?? Math.max(1, Math.ceil(this.stepCounts.checkpointable / 4)) : 0;
|
|
1424
|
+
const ckptCounter = { seen: 0 };
|
|
1502
1425
|
state.pendingError = initialError ?? void 0;
|
|
1503
|
-
|
|
1504
|
-
const makeAbortError = (signal) => ({
|
|
1505
|
-
error: signal.reason ?? new Error("Workflow aborted"),
|
|
1506
|
-
stepId: ABORT_STEP_ID,
|
|
1507
|
-
source: "step"
|
|
1508
|
-
});
|
|
1426
|
+
const abortState = { promoted: false };
|
|
1509
1427
|
for (let i = startIndex; i < this.steps.length; i++) {
|
|
1510
|
-
|
|
1511
|
-
if (!abortPromoted) {
|
|
1512
|
-
abortPromoted = true;
|
|
1513
|
-
state.suspension = void 0;
|
|
1514
|
-
if (state.pendingError) demotePendingError(state, state.pendingError);
|
|
1515
|
-
state.pendingError = makeAbortError(state.abortSignal);
|
|
1516
|
-
} else if (!state.pendingError) {
|
|
1517
|
-
state.pendingError = makeAbortError(state.abortSignal);
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1428
|
+
this.promoteAbort(state, abortState);
|
|
1520
1429
|
const node = this.steps[i];
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
const obsType = getObservabilityType(node);
|
|
1430
|
+
if (node.shouldSkip(state)) continue;
|
|
1431
|
+
const obsType = this.obsEventType(node);
|
|
1524
1432
|
const stepId = node.id;
|
|
1525
1433
|
const sStart = performance.now();
|
|
1526
1434
|
const errBefore = state.pendingError;
|
|
@@ -1540,7 +1448,9 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1540
1448
|
throw e;
|
|
1541
1449
|
}
|
|
1542
1450
|
const newError = state.pendingError && state.pendingError !== errBefore ? state.pendingError : null;
|
|
1543
|
-
|
|
1451
|
+
const isAbort = !!newError && state.abortSignal?.aborted === true && newError.error === state.abortSignal.reason;
|
|
1452
|
+
if (isAbort) {
|
|
1453
|
+
} else if (newError) {
|
|
1544
1454
|
await this.fireStepErrorAndAttachCause(state, {
|
|
1545
1455
|
stepId,
|
|
1546
1456
|
type: obsType,
|
|
@@ -1563,32 +1473,73 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1563
1473
|
state.suspension = void 0;
|
|
1564
1474
|
throw new Error(`internal: suspension bubbled from non-gate step "${node.id}" (gate "${leaked.gateId}").`);
|
|
1565
1475
|
}
|
|
1566
|
-
|
|
1567
|
-
executableStepsSeen++;
|
|
1568
|
-
const shouldCheckpoint = opts.checkpointWhen ? opts.checkpointWhen({ stepIndex: i, stepId: node.id, ctx: state.ctx }) : executableStepsSeen % ckptCadence === 0;
|
|
1569
|
-
if (shouldCheckpoint) {
|
|
1570
|
-
const ckptStart = performance.now();
|
|
1571
|
-
try {
|
|
1572
|
-
await emitCheckpoint(
|
|
1573
|
-
state,
|
|
1574
|
-
opts,
|
|
1575
|
-
i + 1,
|
|
1576
|
-
this.cachedStepShapeHash
|
|
1577
|
-
);
|
|
1578
|
-
} catch (e) {
|
|
1579
|
-
state.pendingError = { error: e, stepId: CHECKPOINT_STEP_ID, source: "onCheckpoint" };
|
|
1580
|
-
state.checkpointFailed = true;
|
|
1581
|
-
await this.fireStepErrorAndAttachCause(state, {
|
|
1582
|
-
stepId: CHECKPOINT_STEP_ID,
|
|
1583
|
-
type: "step",
|
|
1584
|
-
ctx: state.ctx,
|
|
1585
|
-
error: e,
|
|
1586
|
-
durationMs: performance.now() - ckptStart
|
|
1587
|
-
});
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1476
|
+
await this.maybeCheckpoint(state, opts, node, i, ckptCadence, ckptCounter);
|
|
1591
1477
|
}
|
|
1478
|
+
await this.settleRun(state, abortState.promoted);
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Promote a fired abort signal into `state.pendingError` at an iteration
|
|
1482
|
+
* boundary. First observation discards any in-progress suspension (the caller
|
|
1483
|
+
* asked to stop) and preserves a genuinely-different prior step error as a
|
|
1484
|
+
* warning — but NOT one that is itself the abort reason (a nested workflow /
|
|
1485
|
+
* concurrent unit that already rethrew it), which would surface a phantom
|
|
1486
|
+
* step-failure warning. Subsequent iterations only re-promote if a downstream
|
|
1487
|
+
* catch cleared pendingError — `AbortSignal.aborted` is sticky, so the
|
|
1488
|
+
* workflow must not resume mid-pipeline just because a catch swallowed one
|
|
1489
|
+
* observation.
|
|
1490
|
+
*/
|
|
1491
|
+
promoteAbort(state, abortState) {
|
|
1492
|
+
const signal = state.abortSignal;
|
|
1493
|
+
if (!signal?.aborted) return;
|
|
1494
|
+
if (!abortState.promoted) {
|
|
1495
|
+
abortState.promoted = true;
|
|
1496
|
+
state.suspension = void 0;
|
|
1497
|
+
const prior = state.pendingError;
|
|
1498
|
+
if (prior && prior.error !== signal.reason) demotePendingError(state, prior);
|
|
1499
|
+
state.pendingError = makeAbortError(signal);
|
|
1500
|
+
} else if (!state.pendingError) {
|
|
1501
|
+
state.pendingError = makeAbortError(signal);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Emit a checkpoint after a successful `type:"step"` body. Skipped on
|
|
1506
|
+
* pendingError (no clean state to snapshot), on suspension (gate already
|
|
1507
|
+
* won), and for catch/finally/gate nodes (not checkpointable). Numeric
|
|
1508
|
+
* `checkpointEvery` (default: `max(1, ceil(count/4))`) uses the loop-hoisted
|
|
1509
|
+
* `ckptCadence`; the predicate form runs per step. A `when:false`-skipped
|
|
1510
|
+
* `type:"step"` node returns normally (its body never ran) and still reaches
|
|
1511
|
+
* here — it advances the counter and can itself be a checkpoint boundary,
|
|
1512
|
+
* keeping the cadence denominator (`stepCounts.checkpointable`) consistent
|
|
1513
|
+
* with the runtime counter.
|
|
1514
|
+
*/
|
|
1515
|
+
async maybeCheckpoint(state, opts, node, index, ckptCadence, counter) {
|
|
1516
|
+
if (node.type !== "step" || state.pendingError || state.suspension || !opts?.onCheckpoint) return;
|
|
1517
|
+
counter.seen++;
|
|
1518
|
+
const shouldCheckpoint = opts.checkpointWhen ? opts.checkpointWhen({ stepIndex: index, stepId: node.id, ctx: state.ctx }) : counter.seen % ckptCadence === 0;
|
|
1519
|
+
if (!shouldCheckpoint) return;
|
|
1520
|
+
const ckptStart = performance.now();
|
|
1521
|
+
try {
|
|
1522
|
+
await emitCheckpoint(state, opts, index + 1, this.cachedStepShapeHash);
|
|
1523
|
+
} catch (e) {
|
|
1524
|
+
state.pendingError = { error: e, stepId: CHECKPOINT_STEP_ID, source: "onCheckpoint" };
|
|
1525
|
+
state.checkpointFailed = true;
|
|
1526
|
+
await this.fireStepErrorAndAttachCause(state, {
|
|
1527
|
+
stepId: CHECKPOINT_STEP_ID,
|
|
1528
|
+
type: "step",
|
|
1529
|
+
ctx: state.ctx,
|
|
1530
|
+
error: e,
|
|
1531
|
+
durationMs: performance.now() - ckptStart
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Terminal reconciliation after the loop. Re-promotes a swallowed abort
|
|
1537
|
+
* (recoverability must not depend on catch position), then resolves the
|
|
1538
|
+
* mutually-exclusive precedence tail: checkpointFailed > original-step error
|
|
1539
|
+
* > suspension. (A throwing catch/finally never reaches here — it bubbles
|
|
1540
|
+
* straight out of the loop, so there is no finally-aggregation branch.)
|
|
1541
|
+
*/
|
|
1542
|
+
async settleRun(state, abortPromoted) {
|
|
1592
1543
|
if (abortPromoted && !state.pendingError && !state.suspension && state.abortSignal?.aborted) {
|
|
1593
1544
|
state.pendingError = makeAbortError(state.abortSignal);
|
|
1594
1545
|
}
|
|
@@ -1664,15 +1615,14 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1664
1615
|
if (nestedPath && nestedPath.length > 0) {
|
|
1665
1616
|
let steps = this.steps;
|
|
1666
1617
|
for (const idx of nestedPath) {
|
|
1667
|
-
const
|
|
1668
|
-
const child = node?.type === "step" ? node.nestedWorkflow : void 0;
|
|
1618
|
+
const child = steps[idx]?.nestedWorkflow;
|
|
1669
1619
|
if (!child) {
|
|
1670
1620
|
throw new Error(`loadState: nested gate "${gateId}" path is stale \u2014 step ${idx} is not a nested workflow.`);
|
|
1671
1621
|
}
|
|
1672
1622
|
steps = child.getStepsForShapeHash();
|
|
1673
1623
|
}
|
|
1674
1624
|
const innerGate = steps[gateLike.resumeFromIndex];
|
|
1675
|
-
if (innerGate
|
|
1625
|
+
if (!(innerGate instanceof GateStep) || innerGate.id !== gateId) {
|
|
1676
1626
|
throw new Error(`loadState: nested gate "${gateId}" not found at the recorded path.`);
|
|
1677
1627
|
}
|
|
1678
1628
|
const remaining = [...nestedPath.slice(1), gateLike.resumeFromIndex + 1];
|
|
@@ -1736,9 +1686,7 @@ var SealedWorkflow = class _SealedWorkflow {
|
|
|
1736
1686
|
this.ensureDuplicateCheck();
|
|
1737
1687
|
}
|
|
1738
1688
|
return new CheckpointResumedWorkflow(this.steps, idx, {
|
|
1739
|
-
mode: "checkpoint",
|
|
1740
1689
|
priorOutput: ckpt.output,
|
|
1741
|
-
snapshot: ckpt,
|
|
1742
1690
|
observability: this.observability
|
|
1743
1691
|
});
|
|
1744
1692
|
}
|
|
@@ -1854,11 +1802,12 @@ var CheckpointResumedWorkflow = class extends SealedWorkflow {
|
|
|
1854
1802
|
};
|
|
1855
1803
|
var Workflow = class _Workflow extends SealedWorkflow {
|
|
1856
1804
|
/**
|
|
1857
|
-
* Sentinel value for `foreach`'s `onError` handler. Returning
|
|
1858
|
-
*
|
|
1859
|
-
*
|
|
1805
|
+
* Sentinel value for `foreach`/`parallel`'s `onError` handler. Returning
|
|
1806
|
+
* `Workflow.SKIP` omits the failed item (foreach: shortens the output array;
|
|
1807
|
+
* parallel: leaves the slot `undefined`). Aliases the leaf-module `SKIP` so
|
|
1808
|
+
* the step subclasses can compare against it without importing this class.
|
|
1860
1809
|
*/
|
|
1861
|
-
static SKIP =
|
|
1810
|
+
static SKIP = SKIP;
|
|
1862
1811
|
constructor(steps = [], id, observability) {
|
|
1863
1812
|
super(steps, id, observability);
|
|
1864
1813
|
}
|
|
@@ -1869,8 +1818,6 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
1869
1818
|
return new _Workflow([]).step(agent, options);
|
|
1870
1819
|
}
|
|
1871
1820
|
// Builder helper — append a step and return a re-typed Workflow.
|
|
1872
|
-
// Centralizes the `[...steps, node] as any` + new Workflow + observability/id
|
|
1873
|
-
// forwarding pattern used by every combinator method.
|
|
1874
1821
|
appendStep(node) {
|
|
1875
1822
|
return new _Workflow([...this.steps, node], this.id, this.observability);
|
|
1876
1823
|
}
|
|
@@ -1915,67 +1862,18 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
1915
1862
|
if (this.steps.some((s) => s.type === "gate" && s.id === id)) {
|
|
1916
1863
|
throw new Error(`Workflow: duplicate gate ID "${id}". Each gate must have a unique identifier.`);
|
|
1917
1864
|
}
|
|
1918
|
-
const node = new GateStep(
|
|
1919
|
-
id,
|
|
1920
|
-
async (state) => {
|
|
1921
|
-
if (options?.payload) {
|
|
1922
|
-
return options.payload({
|
|
1923
|
-
ctx: state.ctx,
|
|
1924
|
-
input: state.output
|
|
1925
|
-
});
|
|
1926
|
-
}
|
|
1927
|
-
return state.output;
|
|
1928
|
-
},
|
|
1929
|
-
options?.schema,
|
|
1930
|
-
options?.condition ? async (state) => options.condition({
|
|
1931
|
-
ctx: state.ctx,
|
|
1932
|
-
input: state.output
|
|
1933
|
-
}) : void 0,
|
|
1934
|
-
options?.merge ? (params) => options.merge(params) : void 0
|
|
1935
|
-
);
|
|
1865
|
+
const node = new GateStep(id, options);
|
|
1936
1866
|
return this.appendStep(node);
|
|
1937
1867
|
}
|
|
1938
1868
|
// ── branch: implementation ────────────────────────────────────
|
|
1939
1869
|
branch(casesOrConfig, options) {
|
|
1940
|
-
|
|
1941
|
-
return this.branchPredicate(casesOrConfig, options?.id);
|
|
1942
|
-
}
|
|
1943
|
-
return this.branchSelect(casesOrConfig, options?.id);
|
|
1944
|
-
}
|
|
1945
|
-
branchPredicate(cases, explicitId) {
|
|
1946
|
-
const node = new PredicateBranchStep(explicitId ?? "branch:predicate", cases);
|
|
1947
|
-
return this.appendStep(node);
|
|
1948
|
-
}
|
|
1949
|
-
branchSelect(config, explicitId) {
|
|
1950
|
-
const node = new SelectBranchStep(explicitId ?? "branch:select", config);
|
|
1870
|
+
const node = Array.isArray(casesOrConfig) ? new PredicateBranchStep(options?.id ?? "branch:predicate", casesOrConfig) : new SelectBranchStep(options?.id ?? "branch:select", casesOrConfig);
|
|
1951
1871
|
return this.appendStep(node);
|
|
1952
1872
|
}
|
|
1953
|
-
//
|
|
1954
|
-
/**
|
|
1955
|
-
* Map each item of an array through an agent or sub-workflow.
|
|
1956
|
-
*
|
|
1957
|
-
* @param target Agent or `SealedWorkflow` invoked once per item.
|
|
1958
|
-
* @param options.id Override the default step id (`foreach:<agentId>` or
|
|
1959
|
-
* the workflow's id). Required when chaining multiple foreach over the same
|
|
1960
|
-
* target — the construction-time `(type, id)` walk rejects duplicates.
|
|
1961
|
-
* @param options.concurrency Max items in flight at any moment. **Default:
|
|
1962
|
-
* unbounded** (`Infinity` — every item runs concurrently, clamped only by
|
|
1963
|
-
* item count). Pass an integer to throttle against provider rate limits.
|
|
1964
|
-
* Backed by a worker pool: as soon as one item completes, the next launches —
|
|
1965
|
-
* no lockstep batching.
|
|
1966
|
-
* @param options.onError Per-iteration error handler. **Bypassed entirely on
|
|
1967
|
-
* the suspension path** (when any item hits a nested gate) **and on the
|
|
1968
|
-
* cancellation path** (the run was aborted — pre-abort failures become
|
|
1969
|
-
* `foreach-sibling` warnings and the abort reason rethrows) — see the
|
|
1970
|
-
* foreach concurrency hazards in the README. Otherwise: return a
|
|
1971
|
-
* `TNextOutput` value to substitute, return `Workflow.SKIP` to omit, throw
|
|
1972
|
-
* to abort. Invoked sequentially in index order after all items settle.
|
|
1973
|
-
* A throw (or rethrow) from `onError` aborts the foreach immediately:
|
|
1974
|
-
* failures at indices AFTER the throwing one are neither recovered nor
|
|
1975
|
-
* surfaced as warnings.
|
|
1976
|
-
*/
|
|
1873
|
+
// Implementation
|
|
1977
1874
|
foreach(target, options) {
|
|
1978
|
-
const
|
|
1875
|
+
const body = typeof target === "function" ? target(_Workflow.create({ observability: this.observability })) : target;
|
|
1876
|
+
const node = new ForeachStep(body, options, this.observability);
|
|
1979
1877
|
return this.appendStep(node);
|
|
1980
1878
|
}
|
|
1981
1879
|
// Implementation
|
|
@@ -2022,13 +1920,13 @@ var Workflow = class _Workflow extends SealedWorkflow {
|
|
|
2022
1920
|
};
|
|
2023
1921
|
|
|
2024
1922
|
// src/index.ts
|
|
2025
|
-
var
|
|
1923
|
+
var SKIP2 = Workflow.SKIP;
|
|
2026
1924
|
export {
|
|
2027
1925
|
ABORT_STEP_ID,
|
|
2028
1926
|
Agent,
|
|
2029
1927
|
CHECKPOINT_STEP_ID,
|
|
2030
1928
|
GATE_RESUME_STEP_ID,
|
|
2031
|
-
SKIP,
|
|
1929
|
+
SKIP2 as SKIP,
|
|
2032
1930
|
TOOL_PROVIDER_BRAND,
|
|
2033
1931
|
ToolProvider,
|
|
2034
1932
|
Workflow,
|