@ai-sdk/workflow 1.0.67 → 1.0.68
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/CHANGELOG.md +8 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +90 -44
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/do-stream-step.ts +59 -30
- package/src/providers/mock-function-wrapper.ts +24 -14
- package/src/providers/mock.ts +2 -1
- package/src/stream-text-iterator.ts +25 -2
- package/src/test/agent-e2e-workflows.ts +41 -0
- package/src/test/retrying-model.ts +79 -0
- package/src/workflow-agent.ts +29 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/workflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.68",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WorkflowAgent for building AI agents with AI SDK",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"ajv": "^8.20.0",
|
|
30
|
-
"@ai-sdk/provider": "4.0.7",
|
|
31
30
|
"@ai-sdk/provider-utils": "5.0.27",
|
|
32
|
-
"ai": "
|
|
31
|
+
"@ai-sdk/provider": "4.0.7",
|
|
32
|
+
"ai": "7.0.67"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "22.19.19",
|
package/src/do-stream-step.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type ToolChoice,
|
|
17
17
|
type ToolSet,
|
|
18
18
|
} from 'ai';
|
|
19
|
+
import { prepareRetries } from 'ai/internal';
|
|
19
20
|
import type { ProviderOptions } from './workflow-agent.js';
|
|
20
21
|
import {
|
|
21
22
|
resolveSerializableTools,
|
|
@@ -109,6 +110,8 @@ export type DoStreamStepResult =
|
|
|
109
110
|
finish: StreamFinish | undefined;
|
|
110
111
|
raw: DoStreamStepRawResult;
|
|
111
112
|
providerExecutedToolResults: Map<string, ProviderExecutedToolResult>;
|
|
113
|
+
/** Present when the model stream emitted an error part. */
|
|
114
|
+
terminalError?: unknown;
|
|
112
115
|
};
|
|
113
116
|
|
|
114
117
|
export async function doStreamStep(
|
|
@@ -172,42 +175,52 @@ export async function doStreamStep(
|
|
|
172
175
|
},
|
|
173
176
|
};
|
|
174
177
|
|
|
175
|
-
// streamModelCall handles
|
|
176
|
-
// model.doStream(),
|
|
177
|
-
//
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
// streamModelCall expects Prompt (ModelMessage[]) but we pass the
|
|
181
|
-
// pre-converted LanguageModelV4Prompt. standardizePrompt inside
|
|
182
|
-
// streamModelCall handles both formats.
|
|
183
|
-
messages: conversationPrompt as unknown as ModelMessage[],
|
|
184
|
-
allowSystemInMessages: true,
|
|
185
|
-
tools,
|
|
186
|
-
toolChoice: options?.toolChoice,
|
|
187
|
-
includeRawChunks: options?.includeRawChunks,
|
|
188
|
-
providerOptions: options?.providerOptions,
|
|
178
|
+
// streamModelCall handles prompt standardization, tool preparation,
|
|
179
|
+
// model.doStream(), and stream part transformation. Retries are applied
|
|
180
|
+
// around the model dispatch because streamModelCall itself does not retry.
|
|
181
|
+
const { retry } = prepareRetries({
|
|
182
|
+
maxRetries: options?.maxRetries,
|
|
189
183
|
abortSignal,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
184
|
+
});
|
|
185
|
+
const modelStream = await (async () => {
|
|
186
|
+
try {
|
|
187
|
+
const { stream } = await retry(() =>
|
|
188
|
+
streamModelCall({
|
|
189
|
+
model,
|
|
190
|
+
// streamModelCall expects Prompt (ModelMessage[]) but we pass the
|
|
191
|
+
// pre-converted LanguageModelV4Prompt. standardizePrompt inside
|
|
192
|
+
// streamModelCall handles both formats.
|
|
193
|
+
messages: conversationPrompt as unknown as ModelMessage[],
|
|
194
|
+
allowSystemInMessages: true,
|
|
195
|
+
tools,
|
|
196
|
+
toolChoice: options?.toolChoice,
|
|
197
|
+
includeRawChunks: options?.includeRawChunks,
|
|
198
|
+
providerOptions: options?.providerOptions,
|
|
199
|
+
abortSignal,
|
|
200
|
+
headers: options?.headers,
|
|
201
|
+
reasoning: options?.reasoning,
|
|
202
|
+
output,
|
|
203
|
+
maxOutputTokens: options?.maxOutputTokens,
|
|
204
|
+
temperature: options?.temperature,
|
|
205
|
+
topP: options?.topP,
|
|
206
|
+
topK: options?.topK,
|
|
207
|
+
presencePenalty: options?.presencePenalty,
|
|
208
|
+
frequencyPenalty: options?.frequencyPenalty,
|
|
209
|
+
stopSequences: options?.stopSequences,
|
|
210
|
+
seed: options?.seed,
|
|
211
|
+
repairToolCall: options?.repairToolCall,
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return stream;
|
|
216
|
+
} catch (error) {
|
|
205
217
|
if (abortSignal?.aborted && isAbortError(error)) {
|
|
206
218
|
return undefined;
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
throw error;
|
|
210
|
-
}
|
|
222
|
+
}
|
|
223
|
+
})();
|
|
211
224
|
|
|
212
225
|
if (modelStream == null) {
|
|
213
226
|
return { aborted: true };
|
|
@@ -228,6 +241,8 @@ export async function doStreamStep(
|
|
|
228
241
|
| { id?: string; timestamp?: Date; modelId?: string }
|
|
229
242
|
| undefined;
|
|
230
243
|
let warnings: unknown[] | undefined;
|
|
244
|
+
let terminalError: unknown;
|
|
245
|
+
let hasTerminalError = false;
|
|
231
246
|
|
|
232
247
|
// Acquire writer once before the loop to avoid per-chunk lock overhead
|
|
233
248
|
const writer = writable?.getWriter();
|
|
@@ -305,6 +320,15 @@ export async function doStreamStep(
|
|
|
305
320
|
if (writer) {
|
|
306
321
|
await writer.write(part);
|
|
307
322
|
}
|
|
323
|
+
|
|
324
|
+
if (part.type === 'error' && !hasTerminalError) {
|
|
325
|
+
// Retain the first model error as step data. Throwing here would make
|
|
326
|
+
// the durable workflow runtime retry the model step and normalize the
|
|
327
|
+
// original value before WorkflowAgent can surface it. Continue
|
|
328
|
+
// consuming so the existing finish reason and usage are preserved.
|
|
329
|
+
terminalError = part.error;
|
|
330
|
+
hasTerminalError = true;
|
|
331
|
+
}
|
|
308
332
|
}
|
|
309
333
|
} catch (error) {
|
|
310
334
|
if (abortSignal?.aborted && isAbortError(error)) {
|
|
@@ -333,5 +357,10 @@ export async function doStreamStep(
|
|
|
333
357
|
warnings,
|
|
334
358
|
},
|
|
335
359
|
providerExecutedToolResults,
|
|
360
|
+
...(hasTerminalError ? { terminalError } : {}),
|
|
336
361
|
};
|
|
337
362
|
}
|
|
363
|
+
|
|
364
|
+
// Model-call retries are handled above so the workflow runtime must not add
|
|
365
|
+
// another retry layer around the durable step.
|
|
366
|
+
doStreamStep.maxRetries = 0;
|
|
@@ -66,20 +66,30 @@ class SerializableMockLanguageModel extends MockLanguageModelV4 {
|
|
|
66
66
|
usage,
|
|
67
67
|
},
|
|
68
68
|
]
|
|
69
|
-
:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
: response.type === 'tool-call'
|
|
70
|
+
? [
|
|
71
|
+
...prefix,
|
|
72
|
+
{
|
|
73
|
+
type: 'tool-call',
|
|
74
|
+
toolCallId: `call-${responseIndex + 1}`,
|
|
75
|
+
toolName: response.toolName,
|
|
76
|
+
input: response.input,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
type: 'finish',
|
|
80
|
+
finishReason: { unified: 'tool-calls', raw: undefined },
|
|
81
|
+
usage,
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
: [
|
|
85
|
+
...prefix,
|
|
86
|
+
{ type: 'error', error: response.error },
|
|
87
|
+
{
|
|
88
|
+
type: 'finish',
|
|
89
|
+
finishReason: { unified: 'error', raw: 'error' },
|
|
90
|
+
usage,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
83
93
|
|
|
84
94
|
return { stream: convertArrayToReadableStream(streamParts) };
|
|
85
95
|
},
|
package/src/providers/mock.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { mockProvider } from './mock-function-wrapper.js';
|
|
|
2
2
|
|
|
3
3
|
export type MockResponseDescriptor =
|
|
4
4
|
| { type: 'text'; text: string }
|
|
5
|
-
| { type: 'tool-call'; toolName: string; input: string }
|
|
5
|
+
| { type: 'tool-call'; toolName: string; input: string }
|
|
6
|
+
| { type: 'error'; error: unknown };
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Mock model that returns a fixed text response.
|
|
@@ -97,6 +97,11 @@ export interface StreamTextIteratorAbortedValue {
|
|
|
97
97
|
messages: LanguageModelV4Prompt;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
export interface StreamTextIteratorErrorValue {
|
|
101
|
+
error: unknown;
|
|
102
|
+
messages: LanguageModelV4Prompt;
|
|
103
|
+
}
|
|
104
|
+
|
|
100
105
|
// This runs in the workflow context
|
|
101
106
|
export async function* streamTextIterator({
|
|
102
107
|
prompt,
|
|
@@ -147,7 +152,9 @@ export async function* streamTextIterator({
|
|
|
147
152
|
experimental_sandbox?: SandboxSession;
|
|
148
153
|
}): AsyncGenerator<
|
|
149
154
|
StreamTextIteratorYieldValue,
|
|
150
|
-
|
|
155
|
+
| LanguageModelV4Prompt
|
|
156
|
+
| StreamTextIteratorAbortedValue
|
|
157
|
+
| StreamTextIteratorErrorValue,
|
|
151
158
|
LanguageModelV4ToolResultPart[]
|
|
152
159
|
> {
|
|
153
160
|
let conversationPrompt = [...prompt]; // Create a mutable copy
|
|
@@ -166,6 +173,8 @@ export async function* streamTextIterator({
|
|
|
166
173
|
let lastStep: StepResult<any, any> | undefined;
|
|
167
174
|
let lastStepWasToolCalls = false;
|
|
168
175
|
let wasAborted = false;
|
|
176
|
+
let terminalError: unknown;
|
|
177
|
+
let hasTerminalError = false;
|
|
169
178
|
|
|
170
179
|
// TODO(#12164): replace this AI-core telemetry bridge with a
|
|
171
180
|
// WorkflowAgent-specific typed dispatcher. `streamTextIterator` widens
|
|
@@ -347,6 +356,11 @@ export async function* streamTextIterator({
|
|
|
347
356
|
break;
|
|
348
357
|
}
|
|
349
358
|
|
|
359
|
+
if ('terminalError' in streamStepResult) {
|
|
360
|
+
terminalError = streamStepResult.terminalError;
|
|
361
|
+
hasTerminalError = true;
|
|
362
|
+
}
|
|
363
|
+
|
|
350
364
|
const { toolCalls, finish, raw, providerExecutedToolResults } =
|
|
351
365
|
streamStepResult;
|
|
352
366
|
// Reconstruct the full StepResult outside the step boundary so the
|
|
@@ -379,7 +393,12 @@ export async function* streamTextIterator({
|
|
|
379
393
|
|
|
380
394
|
const finishReason = finish?.finishReason;
|
|
381
395
|
|
|
382
|
-
if (
|
|
396
|
+
if (hasTerminalError) {
|
|
397
|
+
// The error crossed the durable step boundary as data. End the loop
|
|
398
|
+
// without throwing so WorkflowAgent can preserve the existing
|
|
399
|
+
// resolved-result contract and expose the original value.
|
|
400
|
+
done = true;
|
|
401
|
+
} else if (finishReason === 'tool-calls') {
|
|
383
402
|
lastStepWasToolCalls = true;
|
|
384
403
|
|
|
385
404
|
const textContent = step.content.filter(
|
|
@@ -508,6 +527,10 @@ export async function* streamTextIterator({
|
|
|
508
527
|
return { aborted: true, messages: conversationPrompt };
|
|
509
528
|
}
|
|
510
529
|
|
|
530
|
+
if (hasTerminalError) {
|
|
531
|
+
return { error: terminalError, messages: conversationPrompt };
|
|
532
|
+
}
|
|
533
|
+
|
|
511
534
|
return conversationPrompt;
|
|
512
535
|
}
|
|
513
536
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { tool } from 'ai';
|
|
5
5
|
import { WorkflowAgent } from '../workflow-agent.js';
|
|
6
6
|
import { mockTextModel, mockSequenceModel } from '../providers/mock.js';
|
|
7
|
+
import { retryingModel } from './retrying-model.js';
|
|
7
8
|
import { createTestSandbox } from './test-sandbox.js';
|
|
8
9
|
import { FatalError, getWritable } from 'workflow';
|
|
9
10
|
import { z } from 'zod/v4';
|
|
@@ -47,6 +48,46 @@ export async function agentBasicE2e(prompt: string) {
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
export async function agentModelRetriesE2e() {
|
|
52
|
+
'use workflow';
|
|
53
|
+
const agent = new WorkflowAgent({
|
|
54
|
+
model: retryingModel(),
|
|
55
|
+
maxRetries: 2,
|
|
56
|
+
});
|
|
57
|
+
const result = await agent.stream({
|
|
58
|
+
messages: [{ role: 'user', content: 'retry the model call' }],
|
|
59
|
+
writable: getWritable(),
|
|
60
|
+
});
|
|
61
|
+
return result.steps.at(-1)?.text;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function agentStreamErrorE2e() {
|
|
65
|
+
'use workflow';
|
|
66
|
+
const terminal = {
|
|
67
|
+
type: 'credential',
|
|
68
|
+
code: 'safe-terminal-classification',
|
|
69
|
+
};
|
|
70
|
+
const callbackErrors: unknown[] = [];
|
|
71
|
+
const agent = new WorkflowAgent({
|
|
72
|
+
model: mockSequenceModel([{ type: 'error', error: terminal }]),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const result = await agent.stream({
|
|
76
|
+
messages: [{ role: 'user', content: 'trigger the terminal error' }],
|
|
77
|
+
writable: getWritable(),
|
|
78
|
+
onError: async ({ error }) => {
|
|
79
|
+
callbackErrors.push(error);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
error: result.error,
|
|
85
|
+
finishReason: result.finishReason,
|
|
86
|
+
stepCount: result.steps.length,
|
|
87
|
+
callbackErrors,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
50
91
|
export async function agentToolCallE2e(a: number, b: number) {
|
|
51
92
|
'use workflow';
|
|
52
93
|
const agent = new WorkflowAgent({
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { APICallError } from '@ai-sdk/provider';
|
|
2
|
+
import {
|
|
3
|
+
WORKFLOW_DESERIALIZE,
|
|
4
|
+
WORKFLOW_SERIALIZE,
|
|
5
|
+
} from '@ai-sdk/provider-utils';
|
|
6
|
+
import { convertArrayToReadableStream, MockLanguageModelV4 } from 'ai/test';
|
|
7
|
+
import { getStepMetadata } from 'workflow';
|
|
8
|
+
|
|
9
|
+
type MockStreamResult = Awaited<ReturnType<MockLanguageModelV4['doStream']>>;
|
|
10
|
+
type MockStreamPart = MockStreamResult extends {
|
|
11
|
+
stream: ReadableStream<infer PART>;
|
|
12
|
+
}
|
|
13
|
+
? PART
|
|
14
|
+
: never;
|
|
15
|
+
|
|
16
|
+
class SerializableRetryingModel extends MockLanguageModelV4 {
|
|
17
|
+
static [WORKFLOW_SERIALIZE](model: SerializableRetryingModel) {
|
|
18
|
+
return { failuresBeforeSuccess: model.failuresBeforeSuccess };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static [WORKFLOW_DESERIALIZE](options: { failuresBeforeSuccess: number }) {
|
|
22
|
+
return new SerializableRetryingModel(options.failuresBeforeSuccess);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
constructor(readonly failuresBeforeSuccess: number) {
|
|
26
|
+
let modelAttempts = 0;
|
|
27
|
+
|
|
28
|
+
super({
|
|
29
|
+
provider: 'workflow-retry-test',
|
|
30
|
+
modelId: 'workflow-retry-test-model',
|
|
31
|
+
doStream: async () => {
|
|
32
|
+
modelAttempts++;
|
|
33
|
+
|
|
34
|
+
if (modelAttempts <= failuresBeforeSuccess) {
|
|
35
|
+
throw new APICallError({
|
|
36
|
+
message: `model call failed on attempt ${modelAttempts}`,
|
|
37
|
+
url: 'https://example.com/model',
|
|
38
|
+
requestBodyValues: {},
|
|
39
|
+
statusCode: 500,
|
|
40
|
+
responseHeaders: { 'retry-after-ms': '0' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const text = `model-attempts=${modelAttempts};step-attempt=${getStepMetadata().attempt}`;
|
|
45
|
+
const streamParts: MockStreamPart[] = [
|
|
46
|
+
{ type: 'stream-start', warnings: [] },
|
|
47
|
+
{ type: 'text-start', id: '1' },
|
|
48
|
+
{ type: 'text-delta', id: '1', delta: text },
|
|
49
|
+
{ type: 'text-end', id: '1' },
|
|
50
|
+
{
|
|
51
|
+
type: 'finish',
|
|
52
|
+
finishReason: { unified: 'stop', raw: 'stop' },
|
|
53
|
+
usage: {
|
|
54
|
+
inputTokens: {
|
|
55
|
+
total: 1,
|
|
56
|
+
noCache: 1,
|
|
57
|
+
cacheRead: undefined,
|
|
58
|
+
cacheWrite: undefined,
|
|
59
|
+
},
|
|
60
|
+
outputTokens: {
|
|
61
|
+
total: 1,
|
|
62
|
+
text: 1,
|
|
63
|
+
reasoning: undefined,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
return { stream: convertArrayToReadableStream(streamParts) };
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Keep construction outside the workflow module to avoid the SWC closure
|
|
76
|
+
// transformation issue tracked in https://github.com/vercel/workflow/issues/1365.
|
|
77
|
+
export function retryingModel(): MockLanguageModelV4 {
|
|
78
|
+
return new SerializableRetryingModel(2);
|
|
79
|
+
}
|
package/src/workflow-agent.ts
CHANGED
|
@@ -212,8 +212,7 @@ export interface GenerationSettings {
|
|
|
212
212
|
seed?: number;
|
|
213
213
|
|
|
214
214
|
/**
|
|
215
|
-
* Maximum number of retries. Set to 0 to disable retries.
|
|
216
|
-
* Note: In workflow context, retries are typically handled by the workflow step mechanism.
|
|
215
|
+
* Maximum number of retries for retryable model call failures. Set to 0 to disable retries.
|
|
217
216
|
* @default 2
|
|
218
217
|
*/
|
|
219
218
|
maxRetries?: number;
|
|
@@ -1191,6 +1190,15 @@ export interface WorkflowAgentStreamResult<
|
|
|
1191
1190
|
*/
|
|
1192
1191
|
finishReason: FinishReason;
|
|
1193
1192
|
|
|
1193
|
+
/**
|
|
1194
|
+
* The original value from a model stream error part.
|
|
1195
|
+
*
|
|
1196
|
+
* This property is present when the model emitted an error part, including
|
|
1197
|
+
* when the supplied value is `undefined`. Check with `'error' in result` to
|
|
1198
|
+
* distinguish that case from a result without a model stream error.
|
|
1199
|
+
*/
|
|
1200
|
+
error?: unknown;
|
|
1201
|
+
|
|
1194
1202
|
/**
|
|
1195
1203
|
* The total token usage across all steps.
|
|
1196
1204
|
*/
|
|
@@ -2146,7 +2154,6 @@ export class WorkflowAgent<
|
|
|
2146
2154
|
stopConditions: effectiveStopWhenFromPrepare,
|
|
2147
2155
|
onStepEnd: mergedOnStepEnd as any,
|
|
2148
2156
|
onStepStart: mergedOnStepStart as any,
|
|
2149
|
-
onError: options.onError,
|
|
2150
2157
|
prepareStep: (options.prepareStep ??
|
|
2151
2158
|
(this.prepareStep as
|
|
2152
2159
|
| PrepareStepCallback<ToolSet, TRuntimeContext>
|
|
@@ -2168,7 +2175,10 @@ export class WorkflowAgent<
|
|
|
2168
2175
|
// Track the final conversation messages from the iterator
|
|
2169
2176
|
let finalMessages: LanguageModelV4Prompt | undefined;
|
|
2170
2177
|
let encounteredError: unknown;
|
|
2178
|
+
let hasEncounteredError = false;
|
|
2171
2179
|
let wasAborted = false;
|
|
2180
|
+
let terminalError: unknown;
|
|
2181
|
+
let hasTerminalError = false;
|
|
2172
2182
|
|
|
2173
2183
|
try {
|
|
2174
2184
|
let result = await iterator.next();
|
|
@@ -2526,6 +2536,10 @@ export class WorkflowAgent<
|
|
|
2526
2536
|
if (result.done) {
|
|
2527
2537
|
if (Array.isArray(result.value)) {
|
|
2528
2538
|
finalMessages = result.value;
|
|
2539
|
+
} else if ('error' in result.value) {
|
|
2540
|
+
finalMessages = result.value.messages;
|
|
2541
|
+
terminalError = result.value.error;
|
|
2542
|
+
hasTerminalError = true;
|
|
2529
2543
|
} else {
|
|
2530
2544
|
finalMessages = result.value.messages;
|
|
2531
2545
|
wasAborted = true;
|
|
@@ -2536,6 +2550,7 @@ export class WorkflowAgent<
|
|
|
2536
2550
|
}
|
|
2537
2551
|
} catch (error) {
|
|
2538
2552
|
encounteredError = error;
|
|
2553
|
+
hasEncounteredError = true;
|
|
2539
2554
|
// Check if this is an abort error
|
|
2540
2555
|
if (isAbortError(error)) {
|
|
2541
2556
|
wasAborted = true;
|
|
@@ -2550,6 +2565,13 @@ export class WorkflowAgent<
|
|
|
2550
2565
|
// Don't throw yet - we want to call onEnd first
|
|
2551
2566
|
}
|
|
2552
2567
|
|
|
2568
|
+
if (hasTerminalError) {
|
|
2569
|
+
if (options.onError) {
|
|
2570
|
+
await options.onError({ error: terminalError });
|
|
2571
|
+
}
|
|
2572
|
+
await telemetryDispatcher.onError?.(terminalError);
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2553
2575
|
// Use the final messages from the iterator, or fall back to standardized messages
|
|
2554
2576
|
const messages = (finalMessages ??
|
|
2555
2577
|
prompt.messages) as unknown as ModelMessage[];
|
|
@@ -2573,8 +2595,9 @@ export class WorkflowAgent<
|
|
|
2573
2595
|
} catch (parseError) {
|
|
2574
2596
|
// If there's already an error, don't override it
|
|
2575
2597
|
// If not, set this as the error
|
|
2576
|
-
if (!
|
|
2598
|
+
if (!hasEncounteredError) {
|
|
2577
2599
|
encounteredError = parseError;
|
|
2600
|
+
hasEncounteredError = true;
|
|
2578
2601
|
}
|
|
2579
2602
|
}
|
|
2580
2603
|
}
|
|
@@ -2610,7 +2633,7 @@ export class WorkflowAgent<
|
|
|
2610
2633
|
}
|
|
2611
2634
|
|
|
2612
2635
|
// Re-throw any error that occurred
|
|
2613
|
-
if (
|
|
2636
|
+
if (hasEncounteredError) {
|
|
2614
2637
|
// Close the stream before throwing
|
|
2615
2638
|
if (options.writable) {
|
|
2616
2639
|
const sendFinish = options.sendFinish ?? true;
|
|
@@ -2639,6 +2662,7 @@ export class WorkflowAgent<
|
|
|
2639
2662
|
finishReason,
|
|
2640
2663
|
totalUsage,
|
|
2641
2664
|
output: experimentalOutput,
|
|
2665
|
+
...(hasTerminalError ? { error: terminalError } : {}),
|
|
2642
2666
|
};
|
|
2643
2667
|
}
|
|
2644
2668
|
}
|