@yolk-sdk/vercel-workflows 0.1.0-canary.56 → 0.1.0-canary.58
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 -16
- package/dist/workflow-loop.d.mts +7 -1
- package/dist/workflow-loop.d.mts.map +1 -1
- package/dist/workflow-loop.mjs +41 -8
- package/dist/workflow-loop.mjs.map +1 -1
- package/package.json +1 -1
- package/src/workflow-loop.ts +60 -9
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ pnpm add @yolk-sdk/vercel-workflows@canary effect workflow
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
|
|
12
|
+
Requires Node.js 22+ and a server runtime supported by Vercel Workflow.
|
|
12
13
|
|
|
13
14
|
## Subpaths
|
|
14
15
|
|
|
@@ -34,7 +35,8 @@ import {
|
|
|
34
35
|
|
|
35
36
|
The `./workflow` subpath is also exported for explicit imports.
|
|
36
37
|
|
|
37
|
-
Use `./effect` at host boundaries that start, replay, resume, or cancel Workflow runs
|
|
38
|
+
Use `./effect` at host boundaries that start, replay, resume, or cancel Workflow runs. This is a
|
|
39
|
+
host-wiring sketch; `runAgentWorkflow`, `request`, and `context` are host-owned values:
|
|
38
40
|
|
|
39
41
|
```ts
|
|
40
42
|
import { Effect } from 'effect'
|
|
@@ -58,6 +60,8 @@ queues, hooks, or stream storage.
|
|
|
58
60
|
- model step: produce model events/tool calls
|
|
59
61
|
- tool batch step: execute requested tools
|
|
60
62
|
- tool batch result: return one ordered tool-result message per host call, including failed `isError` results
|
|
63
|
+
- partial tool-step failure: return wire-safe `failure`, cumulative `usage`, and `eventSequence` when
|
|
64
|
+
completed sibling tools must remain observable
|
|
61
65
|
- awaiting-input state: carry pending HITL hook data, wait through `awaitInput`, then rerun the
|
|
62
66
|
same tool batch with accumulated responses
|
|
63
67
|
- close step: flush/close output stream after a successful final model step
|
|
@@ -67,7 +71,8 @@ Continuation state stays plain serializable data for Workflow persistence.
|
|
|
67
71
|
|
|
68
72
|
## Minimal workflow wrapper
|
|
69
73
|
|
|
70
|
-
Host apps keep concrete Workflow directives local and pass them into the package loop
|
|
74
|
+
Host apps keep concrete Workflow directives local and pass them into the package loop. The callback
|
|
75
|
+
names below are host-owned `'use step'` functions:
|
|
71
76
|
|
|
72
77
|
```ts
|
|
73
78
|
import { createHook } from 'workflow'
|
|
@@ -114,25 +119,29 @@ streamed retries can duplicate chunks unless host/client de-dupe is ready.
|
|
|
114
119
|
Finite `maxAttempts` values are floored and clamped to at least one; non-finite values normalize to
|
|
115
120
|
one. Retries have no built-in backoff.
|
|
116
121
|
|
|
122
|
+
`maxTurns` follows the same positive-integer boundary: finite values are floored and clamped to at
|
|
123
|
+
least one. Omitted or non-finite values use `defaultMaxWorkflowTurns`.
|
|
124
|
+
|
|
117
125
|
## Durable stream events
|
|
118
126
|
|
|
119
127
|
Workflow streams can replay old chunks after retries or reconnects. Durable streams should emit an
|
|
120
128
|
`eventId` on every JSON-serializable event, typically `AgentEvent`, including errors. Clients should
|
|
121
129
|
track seen ids.
|
|
122
130
|
|
|
123
|
-
Use `writeDurableAgentEvent` to assign deterministic ids and write NDJSON
|
|
131
|
+
Use `writeDurableAgentEvent` to assign deterministic ids and write NDJSON. The following is an
|
|
132
|
+
`Effect.gen` fragment; `writer`, `event`, `runId`, `turn`, and `eventSequence` are host-owned:
|
|
124
133
|
|
|
125
134
|
```ts
|
|
126
135
|
const state = makeDurableAgentEventSequencerState(eventSequence)
|
|
127
136
|
const sequenced =
|
|
128
137
|
yield *
|
|
129
138
|
writeDurableAgentEvent({
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
})
|
|
139
|
+
writer,
|
|
140
|
+
event,
|
|
141
|
+
streamId: `workflow:${runId}`,
|
|
142
|
+
turn,
|
|
143
|
+
state
|
|
144
|
+
})
|
|
136
145
|
|
|
137
146
|
eventSequence = sequenced.nextEventSequence
|
|
138
147
|
```
|
|
@@ -170,18 +179,19 @@ from durable state. Hosts that emit terminal events before persistence should no
|
|
|
170
179
|
`streamAgentEventStreamUntilTerminal()` as a durable-settled signal.
|
|
171
180
|
|
|
172
181
|
Use `commitThenWriteTerminalEvent` when a step must commit host state before writing its terminal
|
|
173
|
-
event
|
|
182
|
+
event. This is an `Effect.gen` fragment whose terminal, persistence, writer, and stream values come
|
|
183
|
+
from the host:
|
|
174
184
|
|
|
175
185
|
```ts
|
|
176
186
|
const result =
|
|
177
187
|
yield *
|
|
178
188
|
commitThenWriteTerminalEvent({
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
})
|
|
189
|
+
terminal,
|
|
190
|
+
commit: persistState,
|
|
191
|
+
write: event => writeDurableAgentEvent({ writer, event, streamId, turn, state }),
|
|
192
|
+
writeCommitError: error =>
|
|
193
|
+
writeDurableAgentEvent({ writer, event: commitErrorEvent(error), streamId, turn, state })
|
|
194
|
+
})
|
|
185
195
|
```
|
|
186
196
|
|
|
187
197
|
The helper never writes the success terminal before `commit` succeeds. If `commit` fails, it writes
|
package/dist/workflow-loop.d.mts
CHANGED
|
@@ -44,9 +44,15 @@ type VercelAgentWorkflowAwaitingInput = {
|
|
|
44
44
|
};
|
|
45
45
|
type VercelAgentWorkflowToolBatchStepResult = {
|
|
46
46
|
readonly messages: ReadonlyArray<unknown>;
|
|
47
|
-
readonly createdMessages: ReadonlyArray<unknown>;
|
|
47
|
+
readonly createdMessages: ReadonlyArray<unknown>; /** Updated cumulative usage when tools run nested model work such as subagents. */
|
|
48
|
+
readonly usage?: unknown;
|
|
48
49
|
readonly awaitingInput?: VercelAgentWorkflowAwaitingInput;
|
|
49
50
|
readonly eventSequence?: number;
|
|
51
|
+
/**
|
|
52
|
+
* A plain wire-safe failure captured after partial progress. Returning it instead of rejecting
|
|
53
|
+
* lets orchestration retain usage and event sequencing from completed sibling tools.
|
|
54
|
+
*/
|
|
55
|
+
readonly failure?: unknown;
|
|
50
56
|
};
|
|
51
57
|
type WorkflowStepResult<A> = {
|
|
52
58
|
readonly _tag: 'Success';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-loop.d.mts","names":[],"sources":["../src/workflow-loop.ts"],"mappings":";KAAY,wBAAA;EAAA,SACD,OAAA;EAAA,SACA,OAAO;AAAA;AAAA,KAGN,yBAAA;EAAA,SACD,OAAA;EAAA,SACA,QAAA,GAAW,aAAA;EAAA,SACX,eAAA,EAAiB,aAAa;EAAA,SAC9B,KAAA;EAAA,SACA,IAAA;EAAA,SACA,aAAA;AAAA;AAAA,KAGC,iCAAA;EAAA,SACD,OAAA;EAAA,SACA,KAAA,EAAO,yBAAyB;AAAA;AAAA,KAG/B,kCAAA;EAAA,SACD,IAAA;EAAA,SACA,QAAA,EAAU,aAAA;EAAA,SACV,eAAA,EAAiB,aAAA;EAAA,SACjB,SAAA,EAAW,aAAA;EAAA,SACX,KAAA;EAAA,SACA,IAAA;EAAA,SACA,aAAA;AAAA;AAAA,KAGC,qCAAA;EAAA,SACD,OAAA;EAAA,SACA,OAAA;EAAA,SACA,KAAA,EAAO,aAAA;EAAA,SACP,eAAA,EAAiB,aAAA;EAAA,SACjB,aAAA,GAAgB,aAAA;EAAA,SAChB,KAAA;EAAA,SACA,IAAA;EAAA,SACA,aAAA;AAAA;AAAA,KAGC,gCAAA;EAAA,SACD,SAAA;EAAA,SACA,QAAA,EAAU,aAAA;EAAA,SACV,QAAA,EAAU,aAAa;EAAA,SACvB,KAAA;EAAA,SACA,KAAA;EAAA,SACA,aAAA;AAAA;AAAA,KAGC,sCAAA;EAAA,SACD,QAAA,EAAU,aAAA;EAAA,SACV,eAAA,EAAiB,aAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"workflow-loop.d.mts","names":[],"sources":["../src/workflow-loop.ts"],"mappings":"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"}
|
package/dist/workflow-loop.mjs
CHANGED
|
@@ -15,6 +15,10 @@ const maxRetryAttempts = (policy) => {
|
|
|
15
15
|
const configured = policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts;
|
|
16
16
|
return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1;
|
|
17
17
|
};
|
|
18
|
+
const maxWorkflowTurns = (configured) => {
|
|
19
|
+
if (configured === void 0 || !Number.isFinite(configured)) return 500;
|
|
20
|
+
return Math.max(1, Math.floor(configured));
|
|
21
|
+
};
|
|
18
22
|
async function retryWorkflowStep(runStep, policy) {
|
|
19
23
|
const maxAttempts = maxRetryAttempts(policy);
|
|
20
24
|
let attempt = 1;
|
|
@@ -33,7 +37,7 @@ async function runVercelAgentWorkflow(config) {
|
|
|
33
37
|
turn: 1,
|
|
34
38
|
eventSequence: 0
|
|
35
39
|
};
|
|
36
|
-
const maxTurns = configuredMaxTurns
|
|
40
|
+
const maxTurns = maxWorkflowTurns(configuredMaxTurns);
|
|
37
41
|
for (let step = 0; step < maxTurns; step++) {
|
|
38
42
|
const modelResult = await settleWorkflowStep(retryWorkflowStep(() => runModelStep({
|
|
39
43
|
context: input.context,
|
|
@@ -49,6 +53,14 @@ async function runVercelAgentWorkflow(config) {
|
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
if (modelResult.value.done) {
|
|
56
|
+
const terminalState = {
|
|
57
|
+
request: input.request,
|
|
58
|
+
messages: modelResult.value.messages,
|
|
59
|
+
createdMessages: modelResult.value.createdMessages,
|
|
60
|
+
usage: modelResult.value.usage,
|
|
61
|
+
turn: modelResult.value.turn,
|
|
62
|
+
eventSequence: modelResult.value.eventSequence ?? state.eventSequence
|
|
63
|
+
};
|
|
52
64
|
const closeResult = await settleWorkflowStep(retryWorkflowStep(closeStream, closeStreamRetry));
|
|
53
65
|
if (closeResult._tag === "Failure") {
|
|
54
66
|
await writeErrorSafely(writeError, closeResult.error);
|
|
@@ -56,18 +68,27 @@ async function runVercelAgentWorkflow(config) {
|
|
|
56
68
|
_tag: "CloseStreamFailed",
|
|
57
69
|
turns: modelResult.value.turn,
|
|
58
70
|
error: closeResult.error,
|
|
59
|
-
state
|
|
71
|
+
state: terminalState
|
|
60
72
|
};
|
|
61
73
|
}
|
|
62
74
|
return {
|
|
63
75
|
_tag: "Completed",
|
|
64
76
|
turns: modelResult.value.turn,
|
|
65
|
-
state
|
|
77
|
+
state: terminalState
|
|
66
78
|
};
|
|
67
79
|
}
|
|
68
80
|
let completedToolsResult;
|
|
69
81
|
let toolHitlResponses = [];
|
|
82
|
+
let cumulativeUsage = modelResult.value.usage;
|
|
70
83
|
let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence;
|
|
84
|
+
const currentTurnState = (toolResult) => ({
|
|
85
|
+
request: input.request,
|
|
86
|
+
messages: toolResult === void 0 ? modelResult.value.messages : [...modelResult.value.messages, ...toolResult.messages],
|
|
87
|
+
createdMessages: toolResult?.createdMessages ?? modelResult.value.createdMessages,
|
|
88
|
+
usage: cumulativeUsage,
|
|
89
|
+
turn: modelResult.value.turn,
|
|
90
|
+
eventSequence: toolEventSequence
|
|
91
|
+
});
|
|
71
92
|
for (;;) {
|
|
72
93
|
const toolsResult = await settleWorkflowStep(retryWorkflowStep(() => runToolBatchStep({
|
|
73
94
|
context: input.context,
|
|
@@ -75,7 +96,7 @@ async function runVercelAgentWorkflow(config) {
|
|
|
75
96
|
calls: modelResult.value.toolCalls,
|
|
76
97
|
createdMessages: modelResult.value.createdMessages,
|
|
77
98
|
hitlResponses: toolHitlResponses,
|
|
78
|
-
usage:
|
|
99
|
+
usage: cumulativeUsage,
|
|
79
100
|
turn: modelResult.value.turn,
|
|
80
101
|
eventSequence: toolEventSequence
|
|
81
102
|
}), toolBatchStepRetry));
|
|
@@ -85,7 +106,18 @@ async function runVercelAgentWorkflow(config) {
|
|
|
85
106
|
_tag: "ToolBatchStepFailed",
|
|
86
107
|
turn: modelResult.value.turn,
|
|
87
108
|
error: toolsResult.error,
|
|
88
|
-
state
|
|
109
|
+
state: currentTurnState()
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
cumulativeUsage = toolsResult.value.usage ?? cumulativeUsage;
|
|
113
|
+
toolEventSequence = toolsResult.value.eventSequence ?? toolEventSequence;
|
|
114
|
+
if (toolsResult.value.failure !== void 0) {
|
|
115
|
+
await writeErrorSafely(writeError, toolsResult.value.failure);
|
|
116
|
+
return {
|
|
117
|
+
_tag: "ToolBatchStepFailed",
|
|
118
|
+
turn: modelResult.value.turn,
|
|
119
|
+
error: toolsResult.value.failure,
|
|
120
|
+
state: currentTurnState(toolsResult.value)
|
|
89
121
|
};
|
|
90
122
|
}
|
|
91
123
|
if (toolsResult.value.awaitingInput === void 0) {
|
|
@@ -93,7 +125,8 @@ async function runVercelAgentWorkflow(config) {
|
|
|
93
125
|
break;
|
|
94
126
|
}
|
|
95
127
|
const awaitingInput = toolsResult.value.awaitingInput;
|
|
96
|
-
|
|
128
|
+
cumulativeUsage = toolsResult.value.usage ?? awaitingInput.usage ?? cumulativeUsage;
|
|
129
|
+
toolEventSequence = awaitingInput.eventSequence ?? toolEventSequence;
|
|
97
130
|
const hitlResponse = await settleWorkflowStep(retryWorkflowStep(() => {
|
|
98
131
|
if (awaitInput === void 0) return Promise.reject(missingAwaitInputHandlerError());
|
|
99
132
|
return awaitInput(awaitingInput);
|
|
@@ -105,7 +138,7 @@ async function runVercelAgentWorkflow(config) {
|
|
|
105
138
|
turn: modelResult.value.turn,
|
|
106
139
|
error: hitlResponse.error,
|
|
107
140
|
awaitingInput,
|
|
108
|
-
state
|
|
141
|
+
state: currentTurnState(toolsResult.value)
|
|
109
142
|
};
|
|
110
143
|
}
|
|
111
144
|
toolHitlResponses = [...toolHitlResponses, hitlResponse.value];
|
|
@@ -124,7 +157,7 @@ async function runVercelAgentWorkflow(config) {
|
|
|
124
157
|
request: input.request,
|
|
125
158
|
messages: [...modelResult.value.messages, ...completedToolsResult.messages],
|
|
126
159
|
createdMessages: completedToolsResult.createdMessages,
|
|
127
|
-
usage:
|
|
160
|
+
usage: completedToolsResult.usage ?? cumulativeUsage,
|
|
128
161
|
turn: modelResult.value.turn + 1,
|
|
129
162
|
eventSequence: completedToolsResult.eventSequence ?? toolEventSequence
|
|
130
163
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-loop.mjs","names":[],"sources":["../src/workflow-loop.ts"],"sourcesContent":["export type VercelAgentWorkflowInput = {\n readonly request: unknown\n readonly context: unknown\n}\n\nexport type SerializableWorkflowState = {\n readonly request: unknown\n readonly messages?: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly usage?: unknown\n readonly turn: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowModelStepInput = {\n readonly context: unknown\n readonly state: SerializableWorkflowState\n}\n\nexport type VercelAgentWorkflowModelStepResult = {\n readonly done: boolean\n readonly messages: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly toolCalls: ReadonlyArray<unknown>\n readonly usage: unknown\n readonly turn: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowToolBatchStepInput = {\n readonly context: unknown\n readonly request: unknown\n readonly calls: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly hitlResponses?: ReadonlyArray<unknown>\n readonly usage?: unknown\n readonly turn?: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowAwaitingInput = {\n readonly hookToken: string\n readonly requests: ReadonlyArray<unknown>\n readonly messages: ReadonlyArray<unknown>\n readonly usage: unknown\n readonly turns: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowToolBatchStepResult = {\n readonly messages: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly awaitingInput?: VercelAgentWorkflowAwaitingInput\n readonly eventSequence?: number\n}\n\nexport type WorkflowStepResult<A> =\n | {\n readonly _tag: 'Success'\n readonly value: A\n }\n | {\n readonly _tag: 'Failure'\n readonly error: unknown\n }\n\nexport type VercelAgentWorkflowStepRetryPolicy = {\n readonly maxAttempts: number\n}\n\nexport type VercelAgentWorkflowRunResult =\n | {\n readonly _tag: 'Completed'\n readonly turns: number\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'ModelStepFailed'\n readonly turn: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'ToolBatchStepFailed'\n readonly turn: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'AwaitInputFailed'\n readonly turn: number\n readonly error: unknown\n readonly awaitingInput: VercelAgentWorkflowAwaitingInput\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'CloseStreamFailed'\n readonly turns: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'MaxTurnsExceeded'\n readonly maxTurns: number\n readonly error: Error\n readonly state: SerializableWorkflowState\n }\n\nexport type VercelAgentWorkflowLoopConfig = {\n readonly input: VercelAgentWorkflowInput\n readonly maxTurns?: number\n readonly runModelStep: (\n input: VercelAgentWorkflowModelStepInput\n ) => Promise<VercelAgentWorkflowModelStepResult>\n readonly runToolBatchStep: (\n input: VercelAgentWorkflowToolBatchStepInput\n ) => Promise<VercelAgentWorkflowToolBatchStepResult>\n /** Called only after a model step returns `done: true`. */\n readonly closeStream: () => Promise<void>\n /**\n * Best-effort final failure handler. It must write a safe final error and\n * close the failure stream; rejection is suppressed in favor of the\n * original structured run result.\n */\n readonly writeError: (error: unknown) => Promise<void>\n /** A successful await suspends/resumes without invoking either finalizer. */\n readonly awaitInput?: (input: VercelAgentWorkflowAwaitingInput) => Promise<unknown>\n readonly modelStepRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly toolBatchStepRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly awaitInputRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly closeStreamRetry?: VercelAgentWorkflowStepRetryPolicy\n}\n\nexport const defaultMaxWorkflowTurns = 500\nexport const noWorkflowStepRetry: VercelAgentWorkflowStepRetryPolicy = { maxAttempts: 1 }\n\nexport const settleWorkflowStep = <A>(promise: Promise<A>): Promise<WorkflowStepResult<A>> =>\n promise.then(\n value => ({ _tag: 'Success', value }),\n error => ({ _tag: 'Failure', error })\n )\n\nconst workflowMaxTurnsError = (maxTurns: number) =>\n new Error(`Vercel agent workflow exceeded max turns: ${maxTurns}`)\n\nconst writeErrorSafely = (writeError: (error: unknown) => Promise<void>, error: unknown) =>\n writeError(error).catch(() => undefined)\n\nconst missingAwaitInputHandlerError = () =>\n new Error(\n 'Vercel agent workflow awaiting input requested but no awaitInput handler is configured'\n )\n\nconst maxRetryAttempts = (policy: VercelAgentWorkflowStepRetryPolicy | undefined) => {\n const configured = policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts\n\n return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1\n}\n\nexport async function retryWorkflowStep<A>(\n runStep: () => Promise<A>,\n policy?: VercelAgentWorkflowStepRetryPolicy\n): Promise<A> {\n const maxAttempts = maxRetryAttempts(policy)\n\n let attempt = 1\n\n for (;;) {\n try {\n return await runStep()\n } catch (error) {\n if (attempt >= maxAttempts) {\n throw error\n }\n\n attempt += 1\n }\n }\n}\n\nexport async function runVercelAgentWorkflow(\n config: VercelAgentWorkflowLoopConfig\n): Promise<VercelAgentWorkflowRunResult> {\n const {\n input,\n maxTurns: configuredMaxTurns,\n runModelStep,\n runToolBatchStep,\n closeStream,\n writeError,\n awaitInput,\n modelStepRetry,\n toolBatchStepRetry,\n awaitInputRetry,\n closeStreamRetry\n } = config\n let state: SerializableWorkflowState = {\n request: input.request,\n createdMessages: [],\n turn: 1,\n eventSequence: 0\n }\n const maxTurns = configuredMaxTurns ?? defaultMaxWorkflowTurns\n\n for (let step = 0; step < maxTurns; step++) {\n const modelResult = await settleWorkflowStep(\n retryWorkflowStep(() => runModelStep({ context: input.context, state }), modelStepRetry)\n )\n\n if (modelResult._tag === 'Failure') {\n await writeErrorSafely(writeError, modelResult.error)\n return {\n _tag: 'ModelStepFailed',\n turn: state.turn,\n error: modelResult.error,\n state\n }\n }\n\n if (modelResult.value.done) {\n const closeResult = await settleWorkflowStep(retryWorkflowStep(closeStream, closeStreamRetry))\n\n if (closeResult._tag === 'Failure') {\n await writeErrorSafely(writeError, closeResult.error)\n\n return {\n _tag: 'CloseStreamFailed',\n turns: modelResult.value.turn,\n error: closeResult.error,\n state\n }\n }\n\n return {\n _tag: 'Completed',\n turns: modelResult.value.turn,\n state\n }\n }\n\n let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined\n let toolHitlResponses: ReadonlyArray<unknown> = []\n let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence\n\n for (;;) {\n const toolsResult = await settleWorkflowStep(\n retryWorkflowStep(\n () =>\n runToolBatchStep({\n context: input.context,\n request: input.request,\n calls: modelResult.value.toolCalls,\n createdMessages: modelResult.value.createdMessages,\n hitlResponses: toolHitlResponses,\n usage: modelResult.value.usage,\n turn: modelResult.value.turn,\n eventSequence: toolEventSequence\n }),\n toolBatchStepRetry\n )\n )\n\n if (toolsResult._tag === 'Failure') {\n await writeErrorSafely(writeError, toolsResult.error)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error: toolsResult.error,\n state\n }\n }\n\n if (toolsResult.value.awaitingInput === undefined) {\n completedToolsResult = toolsResult.value\n break\n }\n\n const awaitingInput = toolsResult.value.awaitingInput\n toolEventSequence =\n awaitingInput.eventSequence ?? toolsResult.value.eventSequence ?? toolEventSequence\n\n const hitlResponse = await settleWorkflowStep(\n retryWorkflowStep(() => {\n if (awaitInput === undefined) {\n return Promise.reject(missingAwaitInputHandlerError())\n }\n\n return awaitInput(awaitingInput)\n }, awaitInputRetry)\n )\n\n if (hitlResponse._tag === 'Failure') {\n await writeErrorSafely(writeError, hitlResponse.error)\n\n return {\n _tag: 'AwaitInputFailed',\n turn: modelResult.value.turn,\n error: hitlResponse.error,\n awaitingInput,\n state\n }\n }\n\n toolHitlResponses = [...toolHitlResponses, hitlResponse.value]\n }\n\n if (completedToolsResult === undefined) {\n const error = new Error('Vercel agent workflow tool batch did not complete')\n await writeErrorSafely(writeError, error)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error,\n state\n }\n }\n\n state = {\n request: input.request,\n messages: [...modelResult.value.messages, ...completedToolsResult.messages],\n createdMessages: completedToolsResult.createdMessages,\n usage: modelResult.value.usage,\n turn: modelResult.value.turn + 1,\n eventSequence: completedToolsResult.eventSequence ?? toolEventSequence\n }\n }\n\n const error = workflowMaxTurnsError(maxTurns)\n await writeErrorSafely(writeError, error)\n\n return {\n _tag: 'MaxTurnsExceeded',\n maxTurns,\n error,\n state\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"workflow-loop.mjs","names":[],"sources":["../src/workflow-loop.ts"],"sourcesContent":["export type VercelAgentWorkflowInput = {\n readonly request: unknown\n readonly context: unknown\n}\n\nexport type SerializableWorkflowState = {\n readonly request: unknown\n readonly messages?: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly usage?: unknown\n readonly turn: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowModelStepInput = {\n readonly context: unknown\n readonly state: SerializableWorkflowState\n}\n\nexport type VercelAgentWorkflowModelStepResult = {\n readonly done: boolean\n readonly messages: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly toolCalls: ReadonlyArray<unknown>\n readonly usage: unknown\n readonly turn: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowToolBatchStepInput = {\n readonly context: unknown\n readonly request: unknown\n readonly calls: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n readonly hitlResponses?: ReadonlyArray<unknown>\n readonly usage?: unknown\n readonly turn?: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowAwaitingInput = {\n readonly hookToken: string\n readonly requests: ReadonlyArray<unknown>\n readonly messages: ReadonlyArray<unknown>\n readonly usage: unknown\n readonly turns: number\n readonly eventSequence?: number\n}\n\nexport type VercelAgentWorkflowToolBatchStepResult = {\n readonly messages: ReadonlyArray<unknown>\n readonly createdMessages: ReadonlyArray<unknown>\n /** Updated cumulative usage when tools run nested model work such as subagents. */\n readonly usage?: unknown\n readonly awaitingInput?: VercelAgentWorkflowAwaitingInput\n readonly eventSequence?: number\n /**\n * A plain wire-safe failure captured after partial progress. Returning it instead of rejecting\n * lets orchestration retain usage and event sequencing from completed sibling tools.\n */\n readonly failure?: unknown\n}\n\nexport type WorkflowStepResult<A> =\n | {\n readonly _tag: 'Success'\n readonly value: A\n }\n | {\n readonly _tag: 'Failure'\n readonly error: unknown\n }\n\nexport type VercelAgentWorkflowStepRetryPolicy = {\n readonly maxAttempts: number\n}\n\nexport type VercelAgentWorkflowRunResult =\n | {\n readonly _tag: 'Completed'\n readonly turns: number\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'ModelStepFailed'\n readonly turn: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'ToolBatchStepFailed'\n readonly turn: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'AwaitInputFailed'\n readonly turn: number\n readonly error: unknown\n readonly awaitingInput: VercelAgentWorkflowAwaitingInput\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'CloseStreamFailed'\n readonly turns: number\n readonly error: unknown\n readonly state: SerializableWorkflowState\n }\n | {\n readonly _tag: 'MaxTurnsExceeded'\n readonly maxTurns: number\n readonly error: Error\n readonly state: SerializableWorkflowState\n }\n\nexport type VercelAgentWorkflowLoopConfig = {\n readonly input: VercelAgentWorkflowInput\n readonly maxTurns?: number\n readonly runModelStep: (\n input: VercelAgentWorkflowModelStepInput\n ) => Promise<VercelAgentWorkflowModelStepResult>\n readonly runToolBatchStep: (\n input: VercelAgentWorkflowToolBatchStepInput\n ) => Promise<VercelAgentWorkflowToolBatchStepResult>\n /** Called only after a model step returns `done: true`. */\n readonly closeStream: () => Promise<void>\n /**\n * Best-effort final failure handler. It must write a safe final error and\n * close the failure stream; rejection is suppressed in favor of the\n * original structured run result.\n */\n readonly writeError: (error: unknown) => Promise<void>\n /** A successful await suspends/resumes without invoking either finalizer. */\n readonly awaitInput?: (input: VercelAgentWorkflowAwaitingInput) => Promise<unknown>\n readonly modelStepRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly toolBatchStepRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly awaitInputRetry?: VercelAgentWorkflowStepRetryPolicy\n readonly closeStreamRetry?: VercelAgentWorkflowStepRetryPolicy\n}\n\nexport const defaultMaxWorkflowTurns = 500\nexport const noWorkflowStepRetry: VercelAgentWorkflowStepRetryPolicy = { maxAttempts: 1 }\n\nexport const settleWorkflowStep = <A>(promise: Promise<A>): Promise<WorkflowStepResult<A>> =>\n promise.then(\n value => ({ _tag: 'Success', value }),\n error => ({ _tag: 'Failure', error })\n )\n\nconst workflowMaxTurnsError = (maxTurns: number) =>\n new Error(`Vercel agent workflow exceeded max turns: ${maxTurns}`)\n\nconst writeErrorSafely = (writeError: (error: unknown) => Promise<void>, error: unknown) =>\n writeError(error).catch(() => undefined)\n\nconst missingAwaitInputHandlerError = () =>\n new Error(\n 'Vercel agent workflow awaiting input requested but no awaitInput handler is configured'\n )\n\nconst maxRetryAttempts = (policy: VercelAgentWorkflowStepRetryPolicy | undefined) => {\n const configured = policy?.maxAttempts ?? noWorkflowStepRetry.maxAttempts\n\n return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1\n}\n\nconst maxWorkflowTurns = (configured: number | undefined) => {\n if (configured === undefined || !Number.isFinite(configured)) {\n return defaultMaxWorkflowTurns\n }\n\n return Math.max(1, Math.floor(configured))\n}\n\nexport async function retryWorkflowStep<A>(\n runStep: () => Promise<A>,\n policy?: VercelAgentWorkflowStepRetryPolicy\n): Promise<A> {\n const maxAttempts = maxRetryAttempts(policy)\n\n let attempt = 1\n\n for (;;) {\n try {\n return await runStep()\n } catch (error) {\n if (attempt >= maxAttempts) {\n throw error\n }\n\n attempt += 1\n }\n }\n}\n\nexport async function runVercelAgentWorkflow(\n config: VercelAgentWorkflowLoopConfig\n): Promise<VercelAgentWorkflowRunResult> {\n const {\n input,\n maxTurns: configuredMaxTurns,\n runModelStep,\n runToolBatchStep,\n closeStream,\n writeError,\n awaitInput,\n modelStepRetry,\n toolBatchStepRetry,\n awaitInputRetry,\n closeStreamRetry\n } = config\n let state: SerializableWorkflowState = {\n request: input.request,\n createdMessages: [],\n turn: 1,\n eventSequence: 0\n }\n const maxTurns = maxWorkflowTurns(configuredMaxTurns)\n\n for (let step = 0; step < maxTurns; step++) {\n const modelResult = await settleWorkflowStep(\n retryWorkflowStep(() => runModelStep({ context: input.context, state }), modelStepRetry)\n )\n\n if (modelResult._tag === 'Failure') {\n await writeErrorSafely(writeError, modelResult.error)\n return {\n _tag: 'ModelStepFailed',\n turn: state.turn,\n error: modelResult.error,\n state\n }\n }\n\n if (modelResult.value.done) {\n const terminalState: SerializableWorkflowState = {\n request: input.request,\n messages: modelResult.value.messages,\n createdMessages: modelResult.value.createdMessages,\n usage: modelResult.value.usage,\n turn: modelResult.value.turn,\n eventSequence: modelResult.value.eventSequence ?? state.eventSequence\n }\n const closeResult = await settleWorkflowStep(retryWorkflowStep(closeStream, closeStreamRetry))\n\n if (closeResult._tag === 'Failure') {\n await writeErrorSafely(writeError, closeResult.error)\n\n return {\n _tag: 'CloseStreamFailed',\n turns: modelResult.value.turn,\n error: closeResult.error,\n state: terminalState\n }\n }\n\n return {\n _tag: 'Completed',\n turns: modelResult.value.turn,\n state: terminalState\n }\n }\n\n let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined\n let toolHitlResponses: ReadonlyArray<unknown> = []\n let cumulativeUsage = modelResult.value.usage\n let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence\n const currentTurnState = (\n toolResult?: VercelAgentWorkflowToolBatchStepResult\n ): SerializableWorkflowState => ({\n request: input.request,\n messages:\n toolResult === undefined\n ? modelResult.value.messages\n : [...modelResult.value.messages, ...toolResult.messages],\n createdMessages: toolResult?.createdMessages ?? modelResult.value.createdMessages,\n usage: cumulativeUsage,\n turn: modelResult.value.turn,\n eventSequence: toolEventSequence\n })\n\n for (;;) {\n const toolsResult = await settleWorkflowStep(\n retryWorkflowStep(\n () =>\n runToolBatchStep({\n context: input.context,\n request: input.request,\n calls: modelResult.value.toolCalls,\n createdMessages: modelResult.value.createdMessages,\n hitlResponses: toolHitlResponses,\n usage: cumulativeUsage,\n turn: modelResult.value.turn,\n eventSequence: toolEventSequence\n }),\n toolBatchStepRetry\n )\n )\n\n if (toolsResult._tag === 'Failure') {\n await writeErrorSafely(writeError, toolsResult.error)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error: toolsResult.error,\n state: currentTurnState()\n }\n }\n\n cumulativeUsage = toolsResult.value.usage ?? cumulativeUsage\n toolEventSequence = toolsResult.value.eventSequence ?? toolEventSequence\n\n if (toolsResult.value.failure !== undefined) {\n await writeErrorSafely(writeError, toolsResult.value.failure)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error: toolsResult.value.failure,\n state: currentTurnState(toolsResult.value)\n }\n }\n\n if (toolsResult.value.awaitingInput === undefined) {\n completedToolsResult = toolsResult.value\n break\n }\n\n const awaitingInput = toolsResult.value.awaitingInput\n cumulativeUsage = toolsResult.value.usage ?? awaitingInput.usage ?? cumulativeUsage\n toolEventSequence = awaitingInput.eventSequence ?? toolEventSequence\n\n const hitlResponse = await settleWorkflowStep(\n retryWorkflowStep(() => {\n if (awaitInput === undefined) {\n return Promise.reject(missingAwaitInputHandlerError())\n }\n\n return awaitInput(awaitingInput)\n }, awaitInputRetry)\n )\n\n if (hitlResponse._tag === 'Failure') {\n await writeErrorSafely(writeError, hitlResponse.error)\n\n return {\n _tag: 'AwaitInputFailed',\n turn: modelResult.value.turn,\n error: hitlResponse.error,\n awaitingInput,\n state: currentTurnState(toolsResult.value)\n }\n }\n\n toolHitlResponses = [...toolHitlResponses, hitlResponse.value]\n }\n\n if (completedToolsResult === undefined) {\n const error = new Error('Vercel agent workflow tool batch did not complete')\n await writeErrorSafely(writeError, error)\n\n return {\n _tag: 'ToolBatchStepFailed',\n turn: modelResult.value.turn,\n error,\n state\n }\n }\n\n state = {\n request: input.request,\n messages: [...modelResult.value.messages, ...completedToolsResult.messages],\n createdMessages: completedToolsResult.createdMessages,\n usage: completedToolsResult.usage ?? cumulativeUsage,\n turn: modelResult.value.turn + 1,\n eventSequence: completedToolsResult.eventSequence ?? toolEventSequence\n }\n }\n\n const error = workflowMaxTurnsError(maxTurns)\n await writeErrorSafely(writeError, error)\n\n return {\n _tag: 'MaxTurnsExceeded',\n maxTurns,\n error,\n state\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/workflow-loop.ts
CHANGED
|
@@ -50,8 +50,15 @@ export type VercelAgentWorkflowAwaitingInput = {
|
|
|
50
50
|
export type VercelAgentWorkflowToolBatchStepResult = {
|
|
51
51
|
readonly messages: ReadonlyArray<unknown>
|
|
52
52
|
readonly createdMessages: ReadonlyArray<unknown>
|
|
53
|
+
/** Updated cumulative usage when tools run nested model work such as subagents. */
|
|
54
|
+
readonly usage?: unknown
|
|
53
55
|
readonly awaitingInput?: VercelAgentWorkflowAwaitingInput
|
|
54
56
|
readonly eventSequence?: number
|
|
57
|
+
/**
|
|
58
|
+
* A plain wire-safe failure captured after partial progress. Returning it instead of rejecting
|
|
59
|
+
* lets orchestration retain usage and event sequencing from completed sibling tools.
|
|
60
|
+
*/
|
|
61
|
+
readonly failure?: unknown
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
export type WorkflowStepResult<A> =
|
|
@@ -157,6 +164,14 @@ const maxRetryAttempts = (policy: VercelAgentWorkflowStepRetryPolicy | undefined
|
|
|
157
164
|
return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1
|
|
158
165
|
}
|
|
159
166
|
|
|
167
|
+
const maxWorkflowTurns = (configured: number | undefined) => {
|
|
168
|
+
if (configured === undefined || !Number.isFinite(configured)) {
|
|
169
|
+
return defaultMaxWorkflowTurns
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return Math.max(1, Math.floor(configured))
|
|
173
|
+
}
|
|
174
|
+
|
|
160
175
|
export async function retryWorkflowStep<A>(
|
|
161
176
|
runStep: () => Promise<A>,
|
|
162
177
|
policy?: VercelAgentWorkflowStepRetryPolicy
|
|
@@ -200,7 +215,7 @@ export async function runVercelAgentWorkflow(
|
|
|
200
215
|
turn: 1,
|
|
201
216
|
eventSequence: 0
|
|
202
217
|
}
|
|
203
|
-
const maxTurns = configuredMaxTurns
|
|
218
|
+
const maxTurns = maxWorkflowTurns(configuredMaxTurns)
|
|
204
219
|
|
|
205
220
|
for (let step = 0; step < maxTurns; step++) {
|
|
206
221
|
const modelResult = await settleWorkflowStep(
|
|
@@ -218,6 +233,14 @@ export async function runVercelAgentWorkflow(
|
|
|
218
233
|
}
|
|
219
234
|
|
|
220
235
|
if (modelResult.value.done) {
|
|
236
|
+
const terminalState: SerializableWorkflowState = {
|
|
237
|
+
request: input.request,
|
|
238
|
+
messages: modelResult.value.messages,
|
|
239
|
+
createdMessages: modelResult.value.createdMessages,
|
|
240
|
+
usage: modelResult.value.usage,
|
|
241
|
+
turn: modelResult.value.turn,
|
|
242
|
+
eventSequence: modelResult.value.eventSequence ?? state.eventSequence
|
|
243
|
+
}
|
|
221
244
|
const closeResult = await settleWorkflowStep(retryWorkflowStep(closeStream, closeStreamRetry))
|
|
222
245
|
|
|
223
246
|
if (closeResult._tag === 'Failure') {
|
|
@@ -227,20 +250,34 @@ export async function runVercelAgentWorkflow(
|
|
|
227
250
|
_tag: 'CloseStreamFailed',
|
|
228
251
|
turns: modelResult.value.turn,
|
|
229
252
|
error: closeResult.error,
|
|
230
|
-
state
|
|
253
|
+
state: terminalState
|
|
231
254
|
}
|
|
232
255
|
}
|
|
233
256
|
|
|
234
257
|
return {
|
|
235
258
|
_tag: 'Completed',
|
|
236
259
|
turns: modelResult.value.turn,
|
|
237
|
-
state
|
|
260
|
+
state: terminalState
|
|
238
261
|
}
|
|
239
262
|
}
|
|
240
263
|
|
|
241
264
|
let completedToolsResult: VercelAgentWorkflowToolBatchStepResult | undefined
|
|
242
265
|
let toolHitlResponses: ReadonlyArray<unknown> = []
|
|
266
|
+
let cumulativeUsage = modelResult.value.usage
|
|
243
267
|
let toolEventSequence = modelResult.value.eventSequence ?? state.eventSequence
|
|
268
|
+
const currentTurnState = (
|
|
269
|
+
toolResult?: VercelAgentWorkflowToolBatchStepResult
|
|
270
|
+
): SerializableWorkflowState => ({
|
|
271
|
+
request: input.request,
|
|
272
|
+
messages:
|
|
273
|
+
toolResult === undefined
|
|
274
|
+
? modelResult.value.messages
|
|
275
|
+
: [...modelResult.value.messages, ...toolResult.messages],
|
|
276
|
+
createdMessages: toolResult?.createdMessages ?? modelResult.value.createdMessages,
|
|
277
|
+
usage: cumulativeUsage,
|
|
278
|
+
turn: modelResult.value.turn,
|
|
279
|
+
eventSequence: toolEventSequence
|
|
280
|
+
})
|
|
244
281
|
|
|
245
282
|
for (;;) {
|
|
246
283
|
const toolsResult = await settleWorkflowStep(
|
|
@@ -252,7 +289,7 @@ export async function runVercelAgentWorkflow(
|
|
|
252
289
|
calls: modelResult.value.toolCalls,
|
|
253
290
|
createdMessages: modelResult.value.createdMessages,
|
|
254
291
|
hitlResponses: toolHitlResponses,
|
|
255
|
-
usage:
|
|
292
|
+
usage: cumulativeUsage,
|
|
256
293
|
turn: modelResult.value.turn,
|
|
257
294
|
eventSequence: toolEventSequence
|
|
258
295
|
}),
|
|
@@ -267,7 +304,21 @@ export async function runVercelAgentWorkflow(
|
|
|
267
304
|
_tag: 'ToolBatchStepFailed',
|
|
268
305
|
turn: modelResult.value.turn,
|
|
269
306
|
error: toolsResult.error,
|
|
270
|
-
state
|
|
307
|
+
state: currentTurnState()
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
cumulativeUsage = toolsResult.value.usage ?? cumulativeUsage
|
|
312
|
+
toolEventSequence = toolsResult.value.eventSequence ?? toolEventSequence
|
|
313
|
+
|
|
314
|
+
if (toolsResult.value.failure !== undefined) {
|
|
315
|
+
await writeErrorSafely(writeError, toolsResult.value.failure)
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
_tag: 'ToolBatchStepFailed',
|
|
319
|
+
turn: modelResult.value.turn,
|
|
320
|
+
error: toolsResult.value.failure,
|
|
321
|
+
state: currentTurnState(toolsResult.value)
|
|
271
322
|
}
|
|
272
323
|
}
|
|
273
324
|
|
|
@@ -277,8 +328,8 @@ export async function runVercelAgentWorkflow(
|
|
|
277
328
|
}
|
|
278
329
|
|
|
279
330
|
const awaitingInput = toolsResult.value.awaitingInput
|
|
280
|
-
|
|
281
|
-
|
|
331
|
+
cumulativeUsage = toolsResult.value.usage ?? awaitingInput.usage ?? cumulativeUsage
|
|
332
|
+
toolEventSequence = awaitingInput.eventSequence ?? toolEventSequence
|
|
282
333
|
|
|
283
334
|
const hitlResponse = await settleWorkflowStep(
|
|
284
335
|
retryWorkflowStep(() => {
|
|
@@ -298,7 +349,7 @@ export async function runVercelAgentWorkflow(
|
|
|
298
349
|
turn: modelResult.value.turn,
|
|
299
350
|
error: hitlResponse.error,
|
|
300
351
|
awaitingInput,
|
|
301
|
-
state
|
|
352
|
+
state: currentTurnState(toolsResult.value)
|
|
302
353
|
}
|
|
303
354
|
}
|
|
304
355
|
|
|
@@ -321,7 +372,7 @@ export async function runVercelAgentWorkflow(
|
|
|
321
372
|
request: input.request,
|
|
322
373
|
messages: [...modelResult.value.messages, ...completedToolsResult.messages],
|
|
323
374
|
createdMessages: completedToolsResult.createdMessages,
|
|
324
|
-
usage:
|
|
375
|
+
usage: completedToolsResult.usage ?? cumulativeUsage,
|
|
325
376
|
turn: modelResult.value.turn + 1,
|
|
326
377
|
eventSequence: completedToolsResult.eventSequence ?? toolEventSequence
|
|
327
378
|
}
|