@yolk-sdk/vercel-workflows 0.1.0-canary.71 → 0.1.0-canary.73
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 +28 -3
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/workflow-children.d.mts +40 -0
- package/dist/workflow-children.d.mts.map +1 -0
- package/dist/workflow-children.mjs +51 -0
- package/dist/workflow-children.mjs.map +1 -0
- package/dist/workflow.d.mts +2 -1
- package/dist/workflow.mjs +2 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/workflow-children.ts +62 -0
- package/src/workflow.ts +1 -0
package/README.md
CHANGED
|
@@ -5,10 +5,11 @@ Vercel Workflow agent-loop contracts, durable stream helpers, and an Effect-nati
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm add @yolk-sdk/vercel-workflows@canary effect workflow@^5.0.0-beta.42
|
|
8
|
+
pnpm add @yolk-sdk/vercel-workflows@canary effect@4.0.0-beta.80 workflow@^5.0.0-beta.42
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
|
|
12
|
+
Use the SDK's matching Effect version (`4.0.0-beta.80`) in host code.
|
|
12
13
|
Requires Node.js 22+ and a server runtime supported by Vercel Workflow.
|
|
13
14
|
|
|
14
15
|
Requires `workflow@^5.0.0-beta.42`. The 5.x line region-pins Vercel runs so durable streams are
|
|
@@ -131,10 +132,34 @@ export async function runAgentWorkflow(input: { request: unknown; context: unkno
|
|
|
131
132
|
}
|
|
132
133
|
```
|
|
133
134
|
|
|
134
|
-
`runModelStep`, `
|
|
135
|
-
`
|
|
135
|
+
`runModelStep`, `closeStream`, and `writeError` are host-owned `'use step'` functions.
|
|
136
|
+
`runToolBatchStep` may instead be Workflow orchestration calling concrete per-call steps. Keep provider calls, tools, persistence, telemetry, and Effect runtimes in
|
|
136
137
|
those steps, not in the `'use workflow'` orchestration body.
|
|
137
138
|
|
|
139
|
+
## Independent children
|
|
140
|
+
|
|
141
|
+
`orchestrateWorkflowToolBatch` runs a full-batch `preflight` before any `execute` callback,
|
|
142
|
+
limits local concurrency, and returns results in original call order. Both callbacks are host
|
|
143
|
+
seams: perform IO in steps, not in the orchestration helper. The host commits exactly one tool
|
|
144
|
+
result per call. Return a pause value from preflight when any HITL request is pending.
|
|
145
|
+
If an execution rejects, the helper stops dispatching new calls, settles already-active siblings,
|
|
146
|
+
and returns their ordered progress plus `failures`. Hosts must propagate those failures while
|
|
147
|
+
retaining completed results and usage; do not treat a partial batch as successful.
|
|
148
|
+
|
|
149
|
+
Start child workflows with Workflow 5 `start(childWorkflow, args)` **from orchestration**, not
|
|
150
|
+
inside the parent tool step. A child wrapper runs its own model/tool steps and returns a typed
|
|
151
|
+
application outcome; platform `completed` alone does not prove application success. Foreground
|
|
152
|
+
hosts await that child outcome; background hosts return an accepted handle and continue.
|
|
153
|
+
|
|
154
|
+
`awaitWorkflowChild({ read, sleep })` polls short durable read steps with a host-provided Workflow
|
|
155
|
+
`sleep` between reads. Do not hold a step open awaiting a long `run.returnValue` poll.
|
|
156
|
+
|
|
157
|
+
Hosts own immutable logical reservations, admission/CAS, ownership checks, storage, bounded
|
|
158
|
+
fanout, safe errors, credentials, and cancellation policy. Parent completion/failure must not
|
|
159
|
+
implicitly cancel independent children. Explicit Stop should persist a durable tombstone before
|
|
160
|
+
sweeping admitted child ids, reject late admission, and expose partial sweep failures for retry.
|
|
161
|
+
Give each independent stream and parallel call its own deterministic event-id namespace.
|
|
162
|
+
|
|
138
163
|
## Awaiting input
|
|
139
164
|
|
|
140
165
|
When a tool batch needs HITL input, return `awaitingInput` from `runToolBatchStep`. The package
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { SerializableWorkflowState, VercelAgentWorkflowAwaitingInput, VercelAgentWorkflowInput, VercelAgentWorkflowLoopConfig, VercelAgentWorkflowModelStepInput, VercelAgentWorkflowModelStepResult, VercelAgentWorkflowRunResult, VercelAgentWorkflowStepRetryPolicy, VercelAgentWorkflowToolBatchStepInput, VercelAgentWorkflowToolBatchStepResult, WorkflowStepResult, defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep } from "./workflow-loop.mjs";
|
|
2
2
|
import { CommitThenWriteTerminalEventInput, CommitThenWriteTerminalEventResult, DurableAgentEvent, DurableAgentEventIdInput, DurableAgentEventSequencerState, SequenceDurableAgentEventInput, SequencedDurableAgentEvent, WriteDurableAgentEventInput, commitThenWriteTerminalEvent, durableAgentEventId, makeDurableAgentEventSequencerState, sequenceDurableAgentEvent, writeDurableAgentEvent } from "./workflow-events.mjs";
|
|
3
|
-
|
|
3
|
+
import { awaitWorkflowChild, orchestrateWorkflowToolBatch } from "./workflow-children.mjs";
|
|
4
|
+
export { type CommitThenWriteTerminalEventInput, type CommitThenWriteTerminalEventResult, type DurableAgentEvent, type DurableAgentEventIdInput, type DurableAgentEventSequencerState, type SequenceDurableAgentEventInput, type SequencedDurableAgentEvent, type SerializableWorkflowState, type VercelAgentWorkflowAwaitingInput, type VercelAgentWorkflowInput, type VercelAgentWorkflowLoopConfig, type VercelAgentWorkflowModelStepInput, type VercelAgentWorkflowModelStepResult, type VercelAgentWorkflowRunResult, type VercelAgentWorkflowStepRetryPolicy, type VercelAgentWorkflowToolBatchStepInput, type VercelAgentWorkflowToolBatchStepResult, type WorkflowStepResult, type WriteDurableAgentEventInput, awaitWorkflowChild, commitThenWriteTerminalEvent, defaultMaxWorkflowTurns, durableAgentEventId, makeDurableAgentEventSequencerState, noWorkflowStepRetry, orchestrateWorkflowToolBatch, retryWorkflowStep, runVercelAgentWorkflow, sequenceDurableAgentEvent, settleWorkflowStep, writeDurableAgentEvent };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep } from "./workflow-loop.mjs";
|
|
2
2
|
import { commitThenWriteTerminalEvent, durableAgentEventId, makeDurableAgentEventSequencerState, sequenceDurableAgentEvent, writeDurableAgentEvent } from "./workflow-events.mjs";
|
|
3
|
-
|
|
3
|
+
import { awaitWorkflowChild, orchestrateWorkflowToolBatch } from "./workflow-children.mjs";
|
|
4
|
+
export { awaitWorkflowChild, commitThenWriteTerminalEvent, defaultMaxWorkflowTurns, durableAgentEventId, makeDurableAgentEventSequencerState, noWorkflowStepRetry, orchestrateWorkflowToolBatch, retryWorkflowStep, runVercelAgentWorkflow, sequenceDurableAgentEvent, settleWorkflowStep, writeDurableAgentEvent };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/workflow-children.d.ts
|
|
2
|
+
/** Workflow orchestration only. Callbacks must be durable steps (sleep must be Workflow sleep).
|
|
3
|
+
* A read is short-lived; never put a long returnValue poll inside a step.
|
|
4
|
+
*/
|
|
5
|
+
declare function awaitWorkflowChild<A>(input: {
|
|
6
|
+
readonly read: () => Promise<{
|
|
7
|
+
readonly done: false;
|
|
8
|
+
} | {
|
|
9
|
+
readonly done: true;
|
|
10
|
+
readonly value: A;
|
|
11
|
+
}>;
|
|
12
|
+
readonly sleep: () => Promise<void>;
|
|
13
|
+
}): Promise<A>;
|
|
14
|
+
/** Preflight fences the entire batch. On rejection, stop dispatching new work, settle already
|
|
15
|
+
* active siblings, and return their ordered progress alongside failures. Hosts must handle failures.
|
|
16
|
+
*/
|
|
17
|
+
declare function orchestrateWorkflowToolBatch<Call, Result, Pause>(input: {
|
|
18
|
+
readonly calls: ReadonlyArray<Call>;
|
|
19
|
+
readonly concurrency: number;
|
|
20
|
+
readonly preflight: () => Promise<{
|
|
21
|
+
readonly ready: true;
|
|
22
|
+
} | {
|
|
23
|
+
readonly ready: false;
|
|
24
|
+
readonly value: Pause;
|
|
25
|
+
}>;
|
|
26
|
+
readonly execute: (call: Call, index: number) => Promise<Result>;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
readonly ready: true;
|
|
29
|
+
readonly results: ReadonlyArray<Result>;
|
|
30
|
+
readonly failures?: ReadonlyArray<{
|
|
31
|
+
readonly index: number;
|
|
32
|
+
readonly error: unknown;
|
|
33
|
+
}>;
|
|
34
|
+
} | {
|
|
35
|
+
readonly ready: false;
|
|
36
|
+
readonly value: Pause;
|
|
37
|
+
}>;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { awaitWorkflowChild, orchestrateWorkflowToolBatch };
|
|
40
|
+
//# sourceMappingURL=workflow-children.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-children.d.mts","names":[],"sources":["../src/workflow-children.ts"],"mappings":";;AAGA;;iBAAsB,kBAAA,GAAA,CAAsB,KAAA;EAAA,SACjC,IAAA,QAAY,OAAA;IAAA,SACR,IAAA;EAAA;IAAA,SAA2B,IAAA;IAAA,SAAqB,KAAA,EAAO,CAAA;EAAA;EAAA,SAE3D,KAAA,QAAa,OAAA;AAAA,IACpB,OAAA,CAAQ,CAAA;;;;iBAYU,4BAAA,qBAAA,CAAkD,KAAA;EAAA,SAC7D,KAAA,EAAO,aAAA,CAAc,IAAA;EAAA,SACrB,WAAA;EAAA,SACA,SAAA,QAAiB,OAAA;IAAA,SACb,KAAA;EAAA;IAAA,SAA2B,KAAA;IAAA,SAAuB,KAAA,EAAO,KAAA;EAAA;EAAA,SAE7D,OAAA,GAAU,IAAA,EAAM,IAAA,EAAM,KAAA,aAAkB,OAAA,CAAQ,MAAA;AAAA,IACvD,OAAA;EAAA,SAEW,KAAA;EAAA,SACA,OAAA,EAAS,aAAA,CAAc,MAAA;EAAA,SACvB,QAAA,GAAW,aAAA;IAAA,SAAyB,KAAA;IAAA,SAAwB,KAAA;EAAA;AAAA;EAAA,SAE5D,KAAA;EAAA,SAAuB,KAAA,EAAO,KAAA;AAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/workflow-children.ts
|
|
2
|
+
/** Workflow orchestration only. Callbacks must be durable steps (sleep must be Workflow sleep).
|
|
3
|
+
* A read is short-lived; never put a long returnValue poll inside a step.
|
|
4
|
+
*/
|
|
5
|
+
async function awaitWorkflowChild(input) {
|
|
6
|
+
const { read, sleep } = input;
|
|
7
|
+
for (;;) {
|
|
8
|
+
const state = await read();
|
|
9
|
+
if (state.done) return state.value;
|
|
10
|
+
await sleep();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Preflight fences the entire batch. On rejection, stop dispatching new work, settle already
|
|
14
|
+
* active siblings, and return their ordered progress alongside failures. Hosts must handle failures.
|
|
15
|
+
*/
|
|
16
|
+
async function orchestrateWorkflowToolBatch(input) {
|
|
17
|
+
const { calls, concurrency, preflight, execute } = input;
|
|
18
|
+
const prepared = await preflight();
|
|
19
|
+
if (!prepared.ready) return prepared;
|
|
20
|
+
const width = Number.isFinite(concurrency) ? Math.max(1, Math.floor(concurrency)) : 1;
|
|
21
|
+
const entries = calls.map((call, index) => ({
|
|
22
|
+
call,
|
|
23
|
+
index
|
|
24
|
+
}));
|
|
25
|
+
let next = 0;
|
|
26
|
+
const results = /* @__PURE__ */ new Map();
|
|
27
|
+
const failures = [];
|
|
28
|
+
await Promise.all(Array.from({ length: Math.min(width, calls.length) }, async () => {
|
|
29
|
+
while (failures.length === 0) {
|
|
30
|
+
const entry = entries[next++];
|
|
31
|
+
if (entry === void 0) return;
|
|
32
|
+
try {
|
|
33
|
+
results.set(entry.index, await execute(entry.call, entry.index));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
failures.push({
|
|
36
|
+
index: entry.index,
|
|
37
|
+
error
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}));
|
|
42
|
+
return {
|
|
43
|
+
ready: true,
|
|
44
|
+
results: [...results.entries()].sort(([a], [b]) => a - b).map(([, result]) => result),
|
|
45
|
+
...failures.length === 0 ? {} : { failures: failures.sort((a, b) => a.index - b.index) }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { awaitWorkflowChild, orchestrateWorkflowToolBatch };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=workflow-children.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-children.mjs","names":[],"sources":["../src/workflow-children.ts"],"sourcesContent":["/** Workflow orchestration only. Callbacks must be durable steps (sleep must be Workflow sleep).\n * A read is short-lived; never put a long returnValue poll inside a step.\n */\nexport async function awaitWorkflowChild<A>(input: {\n readonly read: () => Promise<\n { readonly done: false } | { readonly done: true; readonly value: A }\n >\n readonly sleep: () => Promise<void>\n}): Promise<A> {\n const { read, sleep } = input\n for (;;) {\n const state = await read()\n if (state.done) return state.value\n await sleep()\n }\n}\n\n/** Preflight fences the entire batch. On rejection, stop dispatching new work, settle already\n * active siblings, and return their ordered progress alongside failures. Hosts must handle failures.\n */\nexport async function orchestrateWorkflowToolBatch<Call, Result, Pause>(input: {\n readonly calls: ReadonlyArray<Call>\n readonly concurrency: number\n readonly preflight: () => Promise<\n { readonly ready: true } | { readonly ready: false; readonly value: Pause }\n >\n readonly execute: (call: Call, index: number) => Promise<Result>\n}): Promise<\n | {\n readonly ready: true\n readonly results: ReadonlyArray<Result>\n readonly failures?: ReadonlyArray<{ readonly index: number; readonly error: unknown }>\n }\n | { readonly ready: false; readonly value: Pause }\n> {\n const { calls, concurrency, preflight, execute } = input\n const prepared = await preflight()\n if (!prepared.ready) return prepared\n const width = Number.isFinite(concurrency) ? Math.max(1, Math.floor(concurrency)) : 1\n const entries = calls.map((call, index) => ({ call, index }))\n let next = 0\n const results = new Map<number, Result>()\n const failures: Array<{ readonly index: number; readonly error: unknown }> = []\n await Promise.all(\n Array.from({ length: Math.min(width, calls.length) }, async () => {\n while (failures.length === 0) {\n const entry = entries[next++]\n if (entry === undefined) return\n try {\n results.set(entry.index, await execute(entry.call, entry.index))\n } catch (error) {\n failures.push({ index: entry.index, error })\n }\n }\n })\n )\n return {\n ready: true,\n results: [...results.entries()].sort(([a], [b]) => a - b).map(([, result]) => result),\n ...(failures.length === 0 ? {} : { failures: failures.sort((a, b) => a.index - b.index) })\n }\n}\n"],"mappings":";;;;AAGA,eAAsB,mBAAsB,OAK7B;CACb,MAAM,EAAE,MAAM,UAAU;CACxB,SAAS;EACP,MAAM,QAAQ,MAAM,KAAK;EACzB,IAAI,MAAM,MAAM,OAAO,MAAM;EAC7B,MAAM,MAAM;CACd;AACF;;;;AAKA,eAAsB,6BAAkD,OActE;CACA,MAAM,EAAE,OAAO,aAAa,WAAW,YAAY;CACnD,MAAM,WAAW,MAAM,UAAU;CACjC,IAAI,CAAC,SAAS,OAAO,OAAO;CAC5B,MAAM,QAAQ,OAAO,SAAS,WAAW,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,CAAC,IAAI;CACpF,MAAM,UAAU,MAAM,KAAK,MAAM,WAAW;EAAE;EAAM;CAAM,EAAE;CAC5D,IAAI,OAAO;CACX,MAAM,0BAAU,IAAI,IAAoB;CACxC,MAAM,WAAuE,CAAC;CAC9E,MAAM,QAAQ,IACZ,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,OAAO,MAAM,MAAM,EAAE,GAAG,YAAY;EAChE,OAAO,SAAS,WAAW,GAAG;GAC5B,MAAM,QAAQ,QAAQ;GACtB,IAAI,UAAU,KAAA,GAAW;GACzB,IAAI;IACF,QAAQ,IAAI,MAAM,OAAO,MAAM,QAAQ,MAAM,MAAM,MAAM,KAAK,CAAC;GACjE,SAAS,OAAO;IACd,SAAS,KAAK;KAAE,OAAO,MAAM;KAAO;IAAM,CAAC;GAC7C;EACF;CACF,CAAC,CACH;CACA,OAAO;EACL,OAAO;EACP,SAAS,CAAC,GAAG,QAAQ,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,EAAE,KAAK,GAAG,YAAY,MAAM;EACpF,GAAI,SAAS,WAAW,IAAI,CAAC,IAAI,EAAE,UAAU,SAAS,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;CAC1F;AACF"}
|
package/dist/workflow.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { SerializableWorkflowState, VercelAgentWorkflowAwaitingInput, VercelAgentWorkflowInput, VercelAgentWorkflowLoopConfig, VercelAgentWorkflowModelStepInput, VercelAgentWorkflowModelStepResult, VercelAgentWorkflowRunResult, VercelAgentWorkflowStepRetryPolicy, VercelAgentWorkflowToolBatchStepInput, VercelAgentWorkflowToolBatchStepResult, WorkflowStepResult, defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep } from "./workflow-loop.mjs";
|
|
2
2
|
import { CommitThenWriteTerminalEventInput, CommitThenWriteTerminalEventResult, DurableAgentEvent, DurableAgentEventIdInput, DurableAgentEventSequencerState, SequenceDurableAgentEventInput, SequencedDurableAgentEvent, WriteDurableAgentEventInput, commitThenWriteTerminalEvent, durableAgentEventId, makeDurableAgentEventSequencerState, sequenceDurableAgentEvent, writeDurableAgentEvent } from "./workflow-events.mjs";
|
|
3
|
-
|
|
3
|
+
import { awaitWorkflowChild, orchestrateWorkflowToolBatch } from "./workflow-children.mjs";
|
|
4
|
+
export { type CommitThenWriteTerminalEventInput, type CommitThenWriteTerminalEventResult, type DurableAgentEvent, type DurableAgentEventIdInput, type DurableAgentEventSequencerState, type SequenceDurableAgentEventInput, type SequencedDurableAgentEvent, type SerializableWorkflowState, type VercelAgentWorkflowAwaitingInput, type VercelAgentWorkflowInput, type VercelAgentWorkflowLoopConfig, type VercelAgentWorkflowModelStepInput, type VercelAgentWorkflowModelStepResult, type VercelAgentWorkflowRunResult, type VercelAgentWorkflowStepRetryPolicy, type VercelAgentWorkflowToolBatchStepInput, type VercelAgentWorkflowToolBatchStepResult, type WorkflowStepResult, type WriteDurableAgentEventInput, awaitWorkflowChild, commitThenWriteTerminalEvent, defaultMaxWorkflowTurns, durableAgentEventId, makeDurableAgentEventSequencerState, noWorkflowStepRetry, orchestrateWorkflowToolBatch, retryWorkflowStep, runVercelAgentWorkflow, sequenceDurableAgentEvent, settleWorkflowStep, writeDurableAgentEvent };
|
package/dist/workflow.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { defaultMaxWorkflowTurns, noWorkflowStepRetry, retryWorkflowStep, runVercelAgentWorkflow, settleWorkflowStep } from "./workflow-loop.mjs";
|
|
2
2
|
import { commitThenWriteTerminalEvent, durableAgentEventId, makeDurableAgentEventSequencerState, sequenceDurableAgentEvent, writeDurableAgentEvent } from "./workflow-events.mjs";
|
|
3
|
-
|
|
3
|
+
import { awaitWorkflowChild, orchestrateWorkflowToolBatch } from "./workflow-children.mjs";
|
|
4
|
+
export { awaitWorkflowChild, commitThenWriteTerminalEvent, defaultMaxWorkflowTurns, durableAgentEventId, makeDurableAgentEventSequencerState, noWorkflowStepRetry, orchestrateWorkflowToolBatch, retryWorkflowStep, runVercelAgentWorkflow, sequenceDurableAgentEvent, settleWorkflowStep, writeDurableAgentEvent };
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Workflow orchestration only. Callbacks must be durable steps (sleep must be Workflow sleep).
|
|
2
|
+
* A read is short-lived; never put a long returnValue poll inside a step.
|
|
3
|
+
*/
|
|
4
|
+
export async function awaitWorkflowChild<A>(input: {
|
|
5
|
+
readonly read: () => Promise<
|
|
6
|
+
{ readonly done: false } | { readonly done: true; readonly value: A }
|
|
7
|
+
>
|
|
8
|
+
readonly sleep: () => Promise<void>
|
|
9
|
+
}): Promise<A> {
|
|
10
|
+
const { read, sleep } = input
|
|
11
|
+
for (;;) {
|
|
12
|
+
const state = await read()
|
|
13
|
+
if (state.done) return state.value
|
|
14
|
+
await sleep()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Preflight fences the entire batch. On rejection, stop dispatching new work, settle already
|
|
19
|
+
* active siblings, and return their ordered progress alongside failures. Hosts must handle failures.
|
|
20
|
+
*/
|
|
21
|
+
export async function orchestrateWorkflowToolBatch<Call, Result, Pause>(input: {
|
|
22
|
+
readonly calls: ReadonlyArray<Call>
|
|
23
|
+
readonly concurrency: number
|
|
24
|
+
readonly preflight: () => Promise<
|
|
25
|
+
{ readonly ready: true } | { readonly ready: false; readonly value: Pause }
|
|
26
|
+
>
|
|
27
|
+
readonly execute: (call: Call, index: number) => Promise<Result>
|
|
28
|
+
}): Promise<
|
|
29
|
+
| {
|
|
30
|
+
readonly ready: true
|
|
31
|
+
readonly results: ReadonlyArray<Result>
|
|
32
|
+
readonly failures?: ReadonlyArray<{ readonly index: number; readonly error: unknown }>
|
|
33
|
+
}
|
|
34
|
+
| { readonly ready: false; readonly value: Pause }
|
|
35
|
+
> {
|
|
36
|
+
const { calls, concurrency, preflight, execute } = input
|
|
37
|
+
const prepared = await preflight()
|
|
38
|
+
if (!prepared.ready) return prepared
|
|
39
|
+
const width = Number.isFinite(concurrency) ? Math.max(1, Math.floor(concurrency)) : 1
|
|
40
|
+
const entries = calls.map((call, index) => ({ call, index }))
|
|
41
|
+
let next = 0
|
|
42
|
+
const results = new Map<number, Result>()
|
|
43
|
+
const failures: Array<{ readonly index: number; readonly error: unknown }> = []
|
|
44
|
+
await Promise.all(
|
|
45
|
+
Array.from({ length: Math.min(width, calls.length) }, async () => {
|
|
46
|
+
while (failures.length === 0) {
|
|
47
|
+
const entry = entries[next++]
|
|
48
|
+
if (entry === undefined) return
|
|
49
|
+
try {
|
|
50
|
+
results.set(entry.index, await execute(entry.call, entry.index))
|
|
51
|
+
} catch (error) {
|
|
52
|
+
failures.push({ index: entry.index, error })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
return {
|
|
58
|
+
ready: true,
|
|
59
|
+
results: [...results.entries()].sort(([a], [b]) => a - b).map(([, result]) => result),
|
|
60
|
+
...(failures.length === 0 ? {} : { failures: failures.sort((a, b) => a.index - b.index) })
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/workflow.ts
CHANGED