@ai-sdk/workflow 0.0.0-6b196531-20260710185421
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 +1145 -0
- package/LICENSE +13 -0
- package/README.md +62 -0
- package/dist/index.d.ts +1188 -0
- package/dist/index.js +2522 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
- package/src/create-language-model-tool-result-output.ts +85 -0
- package/src/do-stream-step.ts +265 -0
- package/src/index.ts +49 -0
- package/src/normalize-ui-message-stream.ts +164 -0
- package/src/providers/mock-function-wrapper.ts +11 -0
- package/src/providers/mock.ts +110 -0
- package/src/serializable-schema.ts +114 -0
- package/src/stream-text-iterator.ts +661 -0
- package/src/test/agent-e2e-workflows.ts +673 -0
- package/src/test/calculate-workflow.ts +19 -0
- package/src/test/test-sandbox.ts +26 -0
- package/src/to-ui-message-chunk.ts +238 -0
- package/src/types.ts +11 -0
- package/src/workflow-agent.ts +2930 -0
- package/src/workflow-chat-transport.ts +534 -0
|
@@ -0,0 +1,2930 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LanguageModelV4CallOptions,
|
|
3
|
+
LanguageModelV4Prompt,
|
|
4
|
+
LanguageModelV4StreamPart,
|
|
5
|
+
LanguageModelV4ToolResultPart,
|
|
6
|
+
SharedV4ProviderOptions,
|
|
7
|
+
} from '@ai-sdk/provider';
|
|
8
|
+
import {
|
|
9
|
+
getErrorMessage,
|
|
10
|
+
validateTypes,
|
|
11
|
+
withUserAgentSuffix,
|
|
12
|
+
type Context,
|
|
13
|
+
type HasRequiredKey,
|
|
14
|
+
type InferToolSetContext,
|
|
15
|
+
} from '@ai-sdk/provider-utils';
|
|
16
|
+
import {
|
|
17
|
+
Output,
|
|
18
|
+
experimental_filterActiveTools as filterActiveTools,
|
|
19
|
+
type FinishReason,
|
|
20
|
+
type LanguageModelResponseMetadata,
|
|
21
|
+
type LanguageModelUsage,
|
|
22
|
+
type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
|
|
23
|
+
type ModelMessage,
|
|
24
|
+
type StepResult,
|
|
25
|
+
type StopCondition,
|
|
26
|
+
type GenerateTextOnStepEndCallback,
|
|
27
|
+
type ActiveTools,
|
|
28
|
+
type ToolCallRepairFunction,
|
|
29
|
+
type ToolChoice,
|
|
30
|
+
type ToolSet,
|
|
31
|
+
type UIMessage,
|
|
32
|
+
type LanguageModel,
|
|
33
|
+
type Prompt,
|
|
34
|
+
type TelemetryOptions as CoreTelemetryOptions,
|
|
35
|
+
type Instructions,
|
|
36
|
+
type Experimental_SandboxSession as SandboxSession,
|
|
37
|
+
} from 'ai';
|
|
38
|
+
import {
|
|
39
|
+
createRestrictedTelemetryDispatcher,
|
|
40
|
+
collectToolApprovals,
|
|
41
|
+
convertToLanguageModelPrompt,
|
|
42
|
+
mergeAbortSignals,
|
|
43
|
+
mergeCallbacks,
|
|
44
|
+
standardizePrompt,
|
|
45
|
+
validateApprovedToolApprovals,
|
|
46
|
+
} from 'ai/internal';
|
|
47
|
+
import { createLanguageModelToolResultOutput } from './create-language-model-tool-result-output.js';
|
|
48
|
+
import { streamTextIterator } from './stream-text-iterator.js';
|
|
49
|
+
|
|
50
|
+
// Re-export for consumers
|
|
51
|
+
export type { CompatibleLanguageModel } from './types.js';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Callback function to be called after each step completes.
|
|
55
|
+
* Alias for the AI SDK's GenerateTextOnStepEndCallback, using
|
|
56
|
+
* WorkflowAgent-consistent naming.
|
|
57
|
+
*/
|
|
58
|
+
export type WorkflowAgentOnStepEndCallback<
|
|
59
|
+
TTools extends ToolSet = ToolSet,
|
|
60
|
+
TRuntimeContext extends Context = Context,
|
|
61
|
+
> = GenerateTextOnStepEndCallback<TTools, TRuntimeContext>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Callback function to be called after each step completes.
|
|
65
|
+
* Deprecated alias for `WorkflowAgentOnStepEndCallback`.
|
|
66
|
+
*
|
|
67
|
+
* @deprecated Use `WorkflowAgentOnStepEndCallback` instead.
|
|
68
|
+
*/
|
|
69
|
+
export type WorkflowAgentOnStepFinishCallback<
|
|
70
|
+
TTools extends ToolSet = ToolSet,
|
|
71
|
+
TRuntimeContext extends Context = Context,
|
|
72
|
+
> = WorkflowAgentOnStepEndCallback<TTools, TRuntimeContext>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Infer the type of the tools of a workflow agent.
|
|
76
|
+
*/
|
|
77
|
+
export type InferWorkflowAgentTools<WORKFLOW_AGENT> =
|
|
78
|
+
WORKFLOW_AGENT extends WorkflowAgent<infer TOOLS, any> ? TOOLS : never;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Infer the UI message type of a workflow agent.
|
|
82
|
+
*/
|
|
83
|
+
export type InferWorkflowAgentUIMessage<
|
|
84
|
+
_WORKFLOW_AGENT,
|
|
85
|
+
MESSAGE_METADATA = unknown,
|
|
86
|
+
> = UIMessage<MESSAGE_METADATA>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Re-export the Output helper for structured output specifications.
|
|
90
|
+
* Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
|
|
91
|
+
*/
|
|
92
|
+
export { Output };
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Output specification interface for structured outputs.
|
|
96
|
+
* Use `Output.object({ schema })` or `Output.text()` to create an output specification.
|
|
97
|
+
*/
|
|
98
|
+
export interface OutputSpecification<OUTPUT, PARTIAL> {
|
|
99
|
+
readonly name: string;
|
|
100
|
+
responseFormat: PromiseLike<LanguageModelV4CallOptions['responseFormat']>;
|
|
101
|
+
parsePartialOutput(options: {
|
|
102
|
+
text: string;
|
|
103
|
+
}): Promise<{ partial: PARTIAL } | undefined>;
|
|
104
|
+
parseCompleteOutput(
|
|
105
|
+
options: { text: string },
|
|
106
|
+
context: {
|
|
107
|
+
response: LanguageModelResponseMetadata;
|
|
108
|
+
usage: LanguageModelUsage;
|
|
109
|
+
finishReason: FinishReason;
|
|
110
|
+
},
|
|
111
|
+
): Promise<OUTPUT>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Provider-specific options type. This is equivalent to SharedV4ProviderOptions from @ai-sdk/provider.
|
|
116
|
+
*/
|
|
117
|
+
export type ProviderOptions = SharedV4ProviderOptions;
|
|
118
|
+
|
|
119
|
+
type WorkflowAgentToolsContextParameter<TTools extends ToolSet> =
|
|
120
|
+
HasRequiredKey<InferToolSetContext<TTools>> extends true
|
|
121
|
+
? { toolsContext: InferToolSetContext<TTools> }
|
|
122
|
+
: { toolsContext?: never };
|
|
123
|
+
export type TelemetryOptions<
|
|
124
|
+
TRuntimeContext extends Context = Context,
|
|
125
|
+
TTools extends ToolSet = ToolSet,
|
|
126
|
+
> = CoreTelemetryOptions<TRuntimeContext, TTools>;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A transformation that is applied to the stream.
|
|
130
|
+
*/
|
|
131
|
+
export type StreamTextTransform<TTools extends ToolSet> = (options: {
|
|
132
|
+
tools: TTools;
|
|
133
|
+
stopStream: () => void;
|
|
134
|
+
}) => TransformStream<LanguageModelV4StreamPart, LanguageModelV4StreamPart>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Function to repair a tool call that failed to parse.
|
|
138
|
+
* Re-exported from the AI SDK core.
|
|
139
|
+
*/
|
|
140
|
+
export type { ToolCallRepairFunction } from 'ai';
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Custom download function for URLs.
|
|
144
|
+
* The function receives an array of URLs with information about whether
|
|
145
|
+
* the model supports them directly.
|
|
146
|
+
*/
|
|
147
|
+
export type DownloadFunction = (
|
|
148
|
+
options: {
|
|
149
|
+
url: URL;
|
|
150
|
+
isUrlSupportedByModel: boolean;
|
|
151
|
+
}[],
|
|
152
|
+
) => PromiseLike<
|
|
153
|
+
({ data: Uint8Array; mediaType: string | undefined } | null)[]
|
|
154
|
+
>;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generation settings that can be passed to the model.
|
|
158
|
+
* These map directly to LanguageModelV4CallOptions.
|
|
159
|
+
*/
|
|
160
|
+
export interface GenerationSettings {
|
|
161
|
+
/**
|
|
162
|
+
* Maximum number of tokens to generate.
|
|
163
|
+
*/
|
|
164
|
+
maxOutputTokens?: number;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Temperature setting. The range depends on the provider and model.
|
|
168
|
+
* It is recommended to set either `temperature` or `topP`, but not both.
|
|
169
|
+
*/
|
|
170
|
+
temperature?: number;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Nucleus sampling. This is a number between 0 and 1.
|
|
174
|
+
* E.g. 0.1 would mean that only tokens with the top 10% probability mass are considered.
|
|
175
|
+
* It is recommended to set either `temperature` or `topP`, but not both.
|
|
176
|
+
*/
|
|
177
|
+
topP?: number;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Only sample from the top K options for each subsequent token.
|
|
181
|
+
* Used to remove "long tail" low probability responses.
|
|
182
|
+
* Recommended for advanced use cases only. You usually only need to use temperature.
|
|
183
|
+
*/
|
|
184
|
+
topK?: number;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Presence penalty setting. It affects the likelihood of the model to
|
|
188
|
+
* repeat information that is already in the prompt.
|
|
189
|
+
* The presence penalty is a number between -1 (increase repetition)
|
|
190
|
+
* and 1 (maximum penalty, decrease repetition). 0 means no penalty.
|
|
191
|
+
*/
|
|
192
|
+
presencePenalty?: number;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Frequency penalty setting. It affects the likelihood of the model
|
|
196
|
+
* to repeatedly use the same words or phrases.
|
|
197
|
+
* The frequency penalty is a number between -1 (increase repetition)
|
|
198
|
+
* and 1 (maximum penalty, decrease repetition). 0 means no penalty.
|
|
199
|
+
*/
|
|
200
|
+
frequencyPenalty?: number;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Stop sequences. If set, the model will stop generating text when one of the stop sequences is generated.
|
|
204
|
+
* Providers may have limits on the number of stop sequences.
|
|
205
|
+
*/
|
|
206
|
+
stopSequences?: string[];
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The seed (integer) to use for random sampling. If set and supported
|
|
210
|
+
* by the model, calls will generate deterministic results.
|
|
211
|
+
*/
|
|
212
|
+
seed?: number;
|
|
213
|
+
|
|
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.
|
|
217
|
+
* @default 2
|
|
218
|
+
*/
|
|
219
|
+
maxRetries?: number;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Abort signal for cancelling the operation.
|
|
223
|
+
*/
|
|
224
|
+
abortSignal?: AbortSignal;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Additional HTTP headers to be sent with the request.
|
|
228
|
+
* Only applicable for HTTP-based providers.
|
|
229
|
+
*/
|
|
230
|
+
headers?: Record<string, string | undefined>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Reasoning effort level for the model. Controls how much reasoning
|
|
234
|
+
* the model performs before generating a response.
|
|
235
|
+
*/
|
|
236
|
+
reasoning?: LanguageModelV4CallOptions['reasoning'];
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Additional provider-specific options. They are passed through
|
|
240
|
+
* to the provider from the AI SDK and enable provider-specific
|
|
241
|
+
* functionality that can be fully encapsulated in the provider.
|
|
242
|
+
*/
|
|
243
|
+
providerOptions?: ProviderOptions;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Information passed to the prepareStep callback.
|
|
248
|
+
*/
|
|
249
|
+
export interface PrepareStepInfo<
|
|
250
|
+
TTools extends ToolSet = ToolSet,
|
|
251
|
+
TRuntimeContext extends Context = Context,
|
|
252
|
+
> {
|
|
253
|
+
/**
|
|
254
|
+
* The current model configuration (string or function).
|
|
255
|
+
* The function should return a LanguageModelV4 instance.
|
|
256
|
+
*/
|
|
257
|
+
model: LanguageModel;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The current step number (0-indexed).
|
|
261
|
+
*/
|
|
262
|
+
stepNumber: number;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* All previous steps with their results.
|
|
266
|
+
*/
|
|
267
|
+
steps: StepResult<TTools, TRuntimeContext>[];
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The messages that will be sent to the model.
|
|
271
|
+
* This is the LanguageModelV4Prompt format used internally.
|
|
272
|
+
*/
|
|
273
|
+
messages: LanguageModelV4Prompt;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The runtime context that flows through the agent loop.
|
|
277
|
+
* Treat the value as immutable; return a new `runtimeContext` from
|
|
278
|
+
* `prepareStep` to update it for the current and subsequent steps.
|
|
279
|
+
*/
|
|
280
|
+
runtimeContext: TRuntimeContext;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Per-tool context, keyed by tool name. Each tool receives only its own
|
|
284
|
+
* validated entry as `context` during execution.
|
|
285
|
+
* Treat the value as immutable; return a new `toolsContext` from
|
|
286
|
+
* `prepareStep` to update it for the current and subsequent steps.
|
|
287
|
+
*/
|
|
288
|
+
toolsContext: InferToolSetContext<TTools>;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* The sandbox environment that the step is operating in.
|
|
292
|
+
*/
|
|
293
|
+
experimental_sandbox?: SandboxSession;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Return type from the prepareStep callback.
|
|
298
|
+
* All properties are optional - only return the ones you want to override.
|
|
299
|
+
*/
|
|
300
|
+
export interface PrepareStepResult<
|
|
301
|
+
TTools extends ToolSet = ToolSet,
|
|
302
|
+
TRuntimeContext extends Context = Context,
|
|
303
|
+
> extends Partial<GenerationSettings> {
|
|
304
|
+
/**
|
|
305
|
+
* Override the model for this step.
|
|
306
|
+
*/
|
|
307
|
+
model?: LanguageModel;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Override the system message for this step.
|
|
311
|
+
*/
|
|
312
|
+
system?: string;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Override the messages for this step.
|
|
316
|
+
* Use this for context management or message injection.
|
|
317
|
+
*/
|
|
318
|
+
messages?: LanguageModelV4Prompt;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Override the tool choice for this step.
|
|
322
|
+
*/
|
|
323
|
+
toolChoice?: ToolChoice<ToolSet>;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Override the active tools for this step.
|
|
327
|
+
* Limits the tools that are available for the model to call.
|
|
328
|
+
*/
|
|
329
|
+
activeTools?: string[];
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Updated runtime context for the current and subsequent steps.
|
|
333
|
+
* Returning a value replaces the agent's runtime context.
|
|
334
|
+
*/
|
|
335
|
+
runtimeContext?: TRuntimeContext;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Updated per-tool context for the current and subsequent steps.
|
|
339
|
+
* Returning a value replaces the agent's tools context.
|
|
340
|
+
*/
|
|
341
|
+
toolsContext?: InferToolSetContext<TTools>;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Override the sandbox environment for this step.
|
|
345
|
+
*/
|
|
346
|
+
experimental_sandbox?: SandboxSession;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Callback function called before each step in the agent loop.
|
|
351
|
+
* Use this to modify settings, manage context, or implement dynamic behavior.
|
|
352
|
+
*/
|
|
353
|
+
export type PrepareStepCallback<
|
|
354
|
+
TTools extends ToolSet = ToolSet,
|
|
355
|
+
TRuntimeContext extends Context = Context,
|
|
356
|
+
> = (
|
|
357
|
+
info: PrepareStepInfo<TTools, TRuntimeContext>,
|
|
358
|
+
) =>
|
|
359
|
+
| PrepareStepResult<TTools, TRuntimeContext>
|
|
360
|
+
| undefined
|
|
361
|
+
| Promise<PrepareStepResult<TTools, TRuntimeContext> | undefined>;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Options passed to the prepareCall callback.
|
|
365
|
+
*/
|
|
366
|
+
export interface PrepareCallOptions<
|
|
367
|
+
TTools extends ToolSet = ToolSet,
|
|
368
|
+
TRuntimeContext extends Context = Context,
|
|
369
|
+
> extends Partial<GenerationSettings> {
|
|
370
|
+
model: LanguageModel;
|
|
371
|
+
tools: TTools;
|
|
372
|
+
instructions?: Instructions;
|
|
373
|
+
toolChoice?: ToolChoice<TTools>;
|
|
374
|
+
telemetry?: TelemetryOptions<TRuntimeContext, TTools>;
|
|
375
|
+
/**
|
|
376
|
+
* Runtime context that flows through the agent loop.
|
|
377
|
+
* Treat as immutable; return a new `runtimeContext` to update it for the call.
|
|
378
|
+
*/
|
|
379
|
+
runtimeContext?: TRuntimeContext;
|
|
380
|
+
/**
|
|
381
|
+
* Per-tool context, keyed by tool name.
|
|
382
|
+
*/
|
|
383
|
+
toolsContext?: InferToolSetContext<TTools>;
|
|
384
|
+
messages: ModelMessage[];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Result of the prepareCall callback. All fields are optional —
|
|
389
|
+
* only returned fields override the defaults.
|
|
390
|
+
* Note: `tools` cannot be overridden via prepareCall because they are
|
|
391
|
+
* bound at construction time for type safety.
|
|
392
|
+
*/
|
|
393
|
+
export type PrepareCallResult<
|
|
394
|
+
TTools extends ToolSet = ToolSet,
|
|
395
|
+
TRuntimeContext extends Context = Context,
|
|
396
|
+
> = Partial<Omit<PrepareCallOptions<TTools, TRuntimeContext>, 'tools'>>;
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Callback called once before the agent loop starts to transform call parameters.
|
|
400
|
+
*/
|
|
401
|
+
export type PrepareCallCallback<
|
|
402
|
+
TTools extends ToolSet = ToolSet,
|
|
403
|
+
TRuntimeContext extends Context = Context,
|
|
404
|
+
> = (
|
|
405
|
+
options: PrepareCallOptions<TTools, TRuntimeContext>,
|
|
406
|
+
) =>
|
|
407
|
+
| PrepareCallResult<TTools, TRuntimeContext>
|
|
408
|
+
| Promise<PrepareCallResult<TTools, TRuntimeContext>>;
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Configuration options for creating a {@link WorkflowAgent} instance.
|
|
412
|
+
*/
|
|
413
|
+
export type WorkflowAgentOptions<
|
|
414
|
+
TTools extends ToolSet = ToolSet,
|
|
415
|
+
TRuntimeContext extends Context = Context,
|
|
416
|
+
> = GenerationSettings &
|
|
417
|
+
WorkflowAgentToolsContextParameter<TTools> & {
|
|
418
|
+
/**
|
|
419
|
+
* The id of the agent.
|
|
420
|
+
*/
|
|
421
|
+
id?: string;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* The model provider to use for the agent.
|
|
425
|
+
*
|
|
426
|
+
* This should be a string compatible with the Vercel AI Gateway (e.g., 'anthropic/claude-opus'),
|
|
427
|
+
* or a LanguageModelV4 instance from a provider.
|
|
428
|
+
*/
|
|
429
|
+
model: LanguageModel;
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* A set of tools available to the agent.
|
|
433
|
+
* Tools can be implemented as workflow steps for automatic retries and persistence,
|
|
434
|
+
* or as regular workflow-level logic using core library features like sleep() and Hooks.
|
|
435
|
+
*/
|
|
436
|
+
tools?: TTools;
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Agent instructions. Can be a string, a SystemModelMessage, or an array of SystemModelMessages.
|
|
440
|
+
* Supports provider-specific options (e.g., caching) when using the SystemModelMessage form.
|
|
441
|
+
*/
|
|
442
|
+
instructions?: Instructions;
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Optional system prompt to guide the agent's behavior.
|
|
446
|
+
* @deprecated Use `instructions` instead.
|
|
447
|
+
*/
|
|
448
|
+
system?: string;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The tool choice strategy. Default: 'auto'.
|
|
452
|
+
*/
|
|
453
|
+
toolChoice?: ToolChoice<TTools>;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Optional telemetry configuration.
|
|
457
|
+
*/
|
|
458
|
+
telemetry?: TelemetryOptions<TRuntimeContext, TTools>;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Default runtime context for every stream call on this agent.
|
|
462
|
+
*
|
|
463
|
+
* The runtime context flows through `prepareStep`, lifecycle callbacks,
|
|
464
|
+
* and step results.
|
|
465
|
+
* Treat as immutable; return a new `runtimeContext` from `prepareStep`
|
|
466
|
+
* to update it between steps.
|
|
467
|
+
*
|
|
468
|
+
* In workflow context, keep values serializable so they can cross workflow
|
|
469
|
+
* and step boundaries.
|
|
470
|
+
*
|
|
471
|
+
* Per-stream `runtimeContext` values passed to `stream()` override this default.
|
|
472
|
+
*/
|
|
473
|
+
runtimeContext?: TRuntimeContext;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Default stop condition for the agent loop. When the condition is an array,
|
|
477
|
+
* any of the conditions can be met to stop the generation.
|
|
478
|
+
*
|
|
479
|
+
* Per-stream `stopWhen` values passed to `stream()` override this default.
|
|
480
|
+
*/
|
|
481
|
+
stopWhen?:
|
|
482
|
+
| StopCondition<NoInfer<ToolSet>, any>
|
|
483
|
+
| Array<StopCondition<NoInfer<ToolSet>, any>>;
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Default set of active tools that limits which tools the model can call,
|
|
487
|
+
* without changing the tool call and result types in the result.
|
|
488
|
+
*
|
|
489
|
+
* Per-stream `activeTools` values passed to `stream()` override this default.
|
|
490
|
+
*/
|
|
491
|
+
activeTools?: ActiveTools<NoInfer<TTools>>;
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Default output specification for structured outputs.
|
|
495
|
+
* Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
|
|
496
|
+
*
|
|
497
|
+
* Per-stream `output` values passed to `stream()` override this default.
|
|
498
|
+
*/
|
|
499
|
+
output?: OutputSpecification<any, any>;
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Default function that attempts to repair a tool call that failed to parse.
|
|
503
|
+
*
|
|
504
|
+
* Per-stream `repairToolCall` values passed to `stream()` override this default.
|
|
505
|
+
*/
|
|
506
|
+
repairToolCall?: ToolCallRepairFunction<TTools>;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Default function that attempts to repair a tool call that failed to parse.
|
|
510
|
+
*
|
|
511
|
+
* Per-stream `repairToolCall` values passed to `stream()` override this default.
|
|
512
|
+
*
|
|
513
|
+
* @deprecated Use `repairToolCall` instead.
|
|
514
|
+
*/
|
|
515
|
+
experimental_repairToolCall?: ToolCallRepairFunction<TTools>;
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Default custom download function to use for URLs.
|
|
519
|
+
*
|
|
520
|
+
* Per-stream `experimental_download` values passed to `stream()` override this default.
|
|
521
|
+
*/
|
|
522
|
+
experimental_download?: DownloadFunction;
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Default sandbox environment passed through to tool execution as
|
|
526
|
+
* `experimental_sandbox`.
|
|
527
|
+
*
|
|
528
|
+
* Per-stream `experimental_sandbox` values passed to `stream()` override this default.
|
|
529
|
+
*/
|
|
530
|
+
experimental_sandbox?: SandboxSession;
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Default callback function called before each step in the agent loop.
|
|
534
|
+
* Use this to modify settings, manage context, or inject messages dynamically
|
|
535
|
+
* for every stream call on this agent instance.
|
|
536
|
+
*
|
|
537
|
+
* Per-stream `prepareStep` values passed to `stream()` override this default.
|
|
538
|
+
*/
|
|
539
|
+
prepareStep?: PrepareStepCallback<TTools, TRuntimeContext>;
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Callback function to be called after each step completes.
|
|
543
|
+
*/
|
|
544
|
+
onStepEnd?: WorkflowAgentOnStepEndCallback<TTools, TRuntimeContext>;
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Callback function to be called after each step completes.
|
|
548
|
+
*
|
|
549
|
+
* @deprecated Use `onStepEnd` instead.
|
|
550
|
+
*/
|
|
551
|
+
onStepFinish?: WorkflowAgentOnStepFinishCallback<TTools, TRuntimeContext>;
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Callback that is called when the LLM response and all request tool executions are finished.
|
|
555
|
+
*/
|
|
556
|
+
onEnd?: WorkflowAgentOnEndCallback<TTools, TRuntimeContext>;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Callback that is called when the LLM response and all request tool executions are finished.
|
|
560
|
+
*
|
|
561
|
+
* @deprecated Use `onEnd` instead.
|
|
562
|
+
*/
|
|
563
|
+
onFinish?: WorkflowAgentOnFinishCallback<TTools, TRuntimeContext>;
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Callback called when the agent starts streaming, before any LLM calls.
|
|
567
|
+
*/
|
|
568
|
+
experimental_onStart?: WorkflowAgentOnStartCallback<
|
|
569
|
+
TTools,
|
|
570
|
+
TRuntimeContext
|
|
571
|
+
>;
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Callback called before each step (LLM call) begins.
|
|
575
|
+
*/
|
|
576
|
+
experimental_onStepStart?: WorkflowAgentOnStepStartCallback<
|
|
577
|
+
TTools,
|
|
578
|
+
TRuntimeContext
|
|
579
|
+
>;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Callback called before a tool's execute function runs.
|
|
583
|
+
*/
|
|
584
|
+
onToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback<TTools>;
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Callback called after a tool execution completes.
|
|
588
|
+
*/
|
|
589
|
+
onToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback<TTools>;
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Prepare the parameters for the stream call.
|
|
593
|
+
* Called once before the agent loop starts. Use this to transform
|
|
594
|
+
* model, tools, instructions, or other settings based on runtime context.
|
|
595
|
+
*/
|
|
596
|
+
prepareCall?: PrepareCallCallback<TTools, TRuntimeContext>;
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Whether to allow system messages inside the `prompt` or `messages` fields.
|
|
600
|
+
* When `false` (the default), system messages in `prompt` or `messages` are
|
|
601
|
+
* rejected to prevent prompt-injection attacks. Set to `true` only when you
|
|
602
|
+
* intentionally interleave system messages with user messages.
|
|
603
|
+
*
|
|
604
|
+
* @default false
|
|
605
|
+
*/
|
|
606
|
+
allowSystemInMessages?: boolean;
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Callback that is called when the LLM response and all request tool executions are finished.
|
|
611
|
+
*/
|
|
612
|
+
export type WorkflowAgentOnEndCallback<
|
|
613
|
+
TTools extends ToolSet = ToolSet,
|
|
614
|
+
TRuntimeContext extends Context = Context,
|
|
615
|
+
OUTPUT = never,
|
|
616
|
+
> = (event: {
|
|
617
|
+
/**
|
|
618
|
+
* Details for all steps.
|
|
619
|
+
*/
|
|
620
|
+
readonly steps: StepResult<TTools, TRuntimeContext>[];
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* The final messages including all tool calls and results.
|
|
624
|
+
*/
|
|
625
|
+
readonly messages: ModelMessage[];
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* The text output from the last step.
|
|
629
|
+
*/
|
|
630
|
+
readonly text: string;
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* The finish reason from the last step.
|
|
634
|
+
*/
|
|
635
|
+
readonly finishReason: FinishReason;
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* The total token usage across all steps.
|
|
639
|
+
*/
|
|
640
|
+
readonly usage: LanguageModelUsage;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* The total token usage across all steps.
|
|
644
|
+
*/
|
|
645
|
+
readonly totalUsage: LanguageModelUsage;
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* The runtime context at the end of the agent loop.
|
|
649
|
+
*/
|
|
650
|
+
readonly runtimeContext: TRuntimeContext;
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* The per-tool context at the end of the agent loop.
|
|
654
|
+
*/
|
|
655
|
+
readonly toolsContext: InferToolSetContext<TTools>;
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* The generated structured output. It uses the `output` specification.
|
|
659
|
+
* Only available when `output` is specified.
|
|
660
|
+
*/
|
|
661
|
+
readonly output: OUTPUT;
|
|
662
|
+
}) => PromiseLike<void> | void;
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Callback that is called when the LLM response and all request tool executions are finished.
|
|
666
|
+
*
|
|
667
|
+
* @deprecated Use `WorkflowAgentOnEndCallback` instead.
|
|
668
|
+
*/
|
|
669
|
+
export type WorkflowAgentOnFinishCallback<
|
|
670
|
+
TTools extends ToolSet = ToolSet,
|
|
671
|
+
TRuntimeContext extends Context = Context,
|
|
672
|
+
OUTPUT = never,
|
|
673
|
+
> = WorkflowAgentOnEndCallback<TTools, TRuntimeContext, OUTPUT>;
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Callback that is invoked when an error occurs during streaming.
|
|
677
|
+
*/
|
|
678
|
+
export type WorkflowAgentOnErrorCallback = (event: {
|
|
679
|
+
error: unknown;
|
|
680
|
+
}) => PromiseLike<void> | void;
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Callback that is set using the `onAbort` option.
|
|
684
|
+
*/
|
|
685
|
+
export type WorkflowAgentOnAbortCallback<TTools extends ToolSet = ToolSet> =
|
|
686
|
+
(event: {
|
|
687
|
+
/**
|
|
688
|
+
* Details for all previously finished steps.
|
|
689
|
+
*/
|
|
690
|
+
readonly steps: StepResult<TTools, any>[];
|
|
691
|
+
}) => PromiseLike<void> | void;
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Callback that is called when the agent starts streaming, before any LLM calls.
|
|
695
|
+
*/
|
|
696
|
+
export type WorkflowAgentOnStartCallback<
|
|
697
|
+
TTools extends ToolSet = ToolSet,
|
|
698
|
+
TRuntimeContext extends Context = Context,
|
|
699
|
+
> = (event: {
|
|
700
|
+
/** The model being used */
|
|
701
|
+
readonly model: LanguageModel;
|
|
702
|
+
/** The messages being sent */
|
|
703
|
+
readonly messages: ModelMessage[];
|
|
704
|
+
/** Shared runtime context for this agent loop */
|
|
705
|
+
readonly runtimeContext: TRuntimeContext;
|
|
706
|
+
/** Per-tool context map for this agent loop */
|
|
707
|
+
readonly toolsContext: InferToolSetContext<TTools>;
|
|
708
|
+
}) => PromiseLike<void> | void;
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Callback that is called before each step (LLM call) begins.
|
|
712
|
+
*/
|
|
713
|
+
export type WorkflowAgentOnStepStartCallback<
|
|
714
|
+
TTools extends ToolSet = ToolSet,
|
|
715
|
+
TRuntimeContext extends Context = Context,
|
|
716
|
+
> = (event: {
|
|
717
|
+
/** The current step number (0-based) */
|
|
718
|
+
readonly stepNumber: number;
|
|
719
|
+
/** The model being used for this step */
|
|
720
|
+
readonly model: LanguageModel;
|
|
721
|
+
/** The messages being sent for this step */
|
|
722
|
+
readonly messages: ModelMessage[];
|
|
723
|
+
/** Results from all previously finished steps */
|
|
724
|
+
readonly steps: ReadonlyArray<StepResult<TTools, TRuntimeContext>>;
|
|
725
|
+
/** Shared runtime context for this step */
|
|
726
|
+
readonly runtimeContext: TRuntimeContext;
|
|
727
|
+
/** Per-tool context map for this step */
|
|
728
|
+
readonly toolsContext: InferToolSetContext<TTools>;
|
|
729
|
+
}) => PromiseLike<void> | void;
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Callback that is called before a tool's execute function runs.
|
|
733
|
+
*/
|
|
734
|
+
export type WorkflowAgentOnToolExecutionStartCallback<
|
|
735
|
+
TTools extends ToolSet = ToolSet,
|
|
736
|
+
> = (event: {
|
|
737
|
+
/** The tool call being executed */
|
|
738
|
+
readonly toolCall: ToolCall;
|
|
739
|
+
/** The current step number (0-based) */
|
|
740
|
+
readonly stepNumber: number;
|
|
741
|
+
/** Messages sent to the language model for the step that produced the call */
|
|
742
|
+
readonly messages: ModelMessage[];
|
|
743
|
+
/** Tool-specific context passed to the tool */
|
|
744
|
+
readonly toolContext:
|
|
745
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
746
|
+
| undefined;
|
|
747
|
+
}) => PromiseLike<void> | void;
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Callback that is called after a tool execution completes.
|
|
751
|
+
* Uses a discriminated union pattern: check `success` to determine
|
|
752
|
+
* whether `output` or `error` is available.
|
|
753
|
+
*/
|
|
754
|
+
export type WorkflowAgentOnToolExecutionEndCallback<
|
|
755
|
+
TTools extends ToolSet = ToolSet,
|
|
756
|
+
> = (
|
|
757
|
+
event:
|
|
758
|
+
| {
|
|
759
|
+
/** The tool call that was executed */
|
|
760
|
+
readonly toolCall: ToolCall;
|
|
761
|
+
/** The current step number (0-based) */
|
|
762
|
+
readonly stepNumber: number;
|
|
763
|
+
/** Execution time in milliseconds */
|
|
764
|
+
readonly durationMs: number;
|
|
765
|
+
/** Messages sent to the language model for the step that produced the call */
|
|
766
|
+
readonly messages: ModelMessage[];
|
|
767
|
+
/** Tool-specific context passed to the tool */
|
|
768
|
+
readonly toolContext:
|
|
769
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
770
|
+
| undefined;
|
|
771
|
+
/** Whether the tool call succeeded */
|
|
772
|
+
readonly success: true;
|
|
773
|
+
/** The tool result */
|
|
774
|
+
readonly output: unknown;
|
|
775
|
+
readonly error?: never;
|
|
776
|
+
}
|
|
777
|
+
| {
|
|
778
|
+
/** The tool call that was executed */
|
|
779
|
+
readonly toolCall: ToolCall;
|
|
780
|
+
/** The current step number (0-based) */
|
|
781
|
+
readonly stepNumber: number;
|
|
782
|
+
/** Execution time in milliseconds */
|
|
783
|
+
readonly durationMs: number;
|
|
784
|
+
/** Messages sent to the language model for the step that produced the call */
|
|
785
|
+
readonly messages: ModelMessage[];
|
|
786
|
+
/** Tool-specific context passed to the tool */
|
|
787
|
+
readonly toolContext:
|
|
788
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
789
|
+
| undefined;
|
|
790
|
+
/** Whether the tool call succeeded */
|
|
791
|
+
readonly success: false;
|
|
792
|
+
/** The error that occurred */
|
|
793
|
+
readonly error: unknown;
|
|
794
|
+
readonly output?: never;
|
|
795
|
+
},
|
|
796
|
+
) => PromiseLike<void> | void;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Options for the {@link WorkflowAgent.stream} method.
|
|
800
|
+
*/
|
|
801
|
+
export type WorkflowAgentStreamOptions<
|
|
802
|
+
TTools extends ToolSet = ToolSet,
|
|
803
|
+
TRuntimeContext extends Context = Context,
|
|
804
|
+
OUTPUT = never,
|
|
805
|
+
PARTIAL_OUTPUT = never,
|
|
806
|
+
> = Partial<GenerationSettings> &
|
|
807
|
+
(
|
|
808
|
+
| {
|
|
809
|
+
/**
|
|
810
|
+
* A prompt. It can be either a text prompt or a list of messages.
|
|
811
|
+
*
|
|
812
|
+
* You can either use `prompt` or `messages` but not both.
|
|
813
|
+
*/
|
|
814
|
+
prompt: string | Array<ModelMessage>;
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* A list of messages.
|
|
818
|
+
*
|
|
819
|
+
* You can either use `prompt` or `messages` but not both.
|
|
820
|
+
*/
|
|
821
|
+
messages?: never;
|
|
822
|
+
}
|
|
823
|
+
| {
|
|
824
|
+
/**
|
|
825
|
+
* The conversation messages to process. Should follow the AI SDK's ModelMessage format.
|
|
826
|
+
*
|
|
827
|
+
* You can either use `prompt` or `messages` but not both.
|
|
828
|
+
*/
|
|
829
|
+
messages: Array<ModelMessage>;
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* A prompt. It can be either a text prompt or a list of messages.
|
|
833
|
+
*
|
|
834
|
+
* You can either use `prompt` or `messages` but not both.
|
|
835
|
+
*/
|
|
836
|
+
prompt?: never;
|
|
837
|
+
}
|
|
838
|
+
) & {
|
|
839
|
+
/**
|
|
840
|
+
* Optional system prompt override. If provided, overrides the system prompt from the constructor.
|
|
841
|
+
*/
|
|
842
|
+
system?: string;
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* A WritableStream that receives raw LanguageModelV4StreamPart chunks in real-time
|
|
846
|
+
* as the model generates them. This enables streaming to the client without
|
|
847
|
+
* coupling WorkflowAgent to UIMessageChunk format.
|
|
848
|
+
*
|
|
849
|
+
* Convert to UIMessageChunks at the response boundary using
|
|
850
|
+
* `createUIMessageChunkTransform()` from `@ai-sdk/workflow`.
|
|
851
|
+
*
|
|
852
|
+
* @example
|
|
853
|
+
* ```typescript
|
|
854
|
+
* // In the workflow:
|
|
855
|
+
* await agent.stream({
|
|
856
|
+
* messages,
|
|
857
|
+
* writable: getWritable<ModelCallStreamPart>(),
|
|
858
|
+
* });
|
|
859
|
+
*
|
|
860
|
+
* // In the route handler:
|
|
861
|
+
* return createUIMessageStreamResponse({
|
|
862
|
+
* stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
|
|
863
|
+
* });
|
|
864
|
+
* ```
|
|
865
|
+
*/
|
|
866
|
+
writable?: WritableStream<ModelCallStreamPart<ToolSet>>;
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Condition for stopping the generation when there are tool results in the last step.
|
|
870
|
+
* When the condition is an array, any of the conditions can be met to stop the generation.
|
|
871
|
+
*/
|
|
872
|
+
stopWhen?:
|
|
873
|
+
| StopCondition<NoInfer<ToolSet>, any>
|
|
874
|
+
| Array<StopCondition<NoInfer<ToolSet>, any>>;
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* The tool choice strategy. Default: 'auto'.
|
|
878
|
+
* Overrides the toolChoice from the constructor if provided.
|
|
879
|
+
*/
|
|
880
|
+
toolChoice?: ToolChoice<TTools>;
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Limits the tools that are available for the model to call without
|
|
884
|
+
* changing the tool call and result types in the result.
|
|
885
|
+
*/
|
|
886
|
+
activeTools?: ActiveTools<NoInfer<TTools>>;
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Optional telemetry configuration.
|
|
890
|
+
*/
|
|
891
|
+
telemetry?: TelemetryOptions<TRuntimeContext, TTools>;
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Runtime context that flows through the agent loop.
|
|
895
|
+
*
|
|
896
|
+
* Treat as immutable; return a new `runtimeContext` from `prepareStep`
|
|
897
|
+
* to update it between steps.
|
|
898
|
+
*
|
|
899
|
+
* In workflow context, keep values serializable so they can cross workflow
|
|
900
|
+
* and step boundaries.
|
|
901
|
+
*
|
|
902
|
+
* Overrides the constructor-level `runtimeContext` if provided.
|
|
903
|
+
*/
|
|
904
|
+
runtimeContext?: TRuntimeContext;
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Per-tool context, keyed by tool name. Each tool receives only its own
|
|
908
|
+
* validated entry as `context` during execution. Tools that declare a
|
|
909
|
+
* `contextSchema` validate their entry against the schema.
|
|
910
|
+
*
|
|
911
|
+
* In workflow context, keep values serializable so they can cross workflow
|
|
912
|
+
* and step boundaries.
|
|
913
|
+
*
|
|
914
|
+
* Overrides the constructor-level `toolsContext` if provided.
|
|
915
|
+
*/
|
|
916
|
+
toolsContext?: InferToolSetContext<TTools>;
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Optional specification for parsing structured outputs from the LLM response.
|
|
920
|
+
* Use `Output.object({ schema })` for structured output or `Output.text()` for text output.
|
|
921
|
+
*
|
|
922
|
+
* @example
|
|
923
|
+
* ```typescript
|
|
924
|
+
* import { Output } from '@workflow/ai';
|
|
925
|
+
* import { z } from 'zod';
|
|
926
|
+
*
|
|
927
|
+
* const result = await agent.stream({
|
|
928
|
+
* messages: [...],
|
|
929
|
+
* writable: getWritable(),
|
|
930
|
+
* output: Output.object({
|
|
931
|
+
* schema: z.object({
|
|
932
|
+
* sentiment: z.enum(['positive', 'negative', 'neutral']),
|
|
933
|
+
* confidence: z.number(),
|
|
934
|
+
* }),
|
|
935
|
+
* }),
|
|
936
|
+
* });
|
|
937
|
+
*
|
|
938
|
+
* console.log(result.output); // { sentiment: 'positive', confidence: 0.95 }
|
|
939
|
+
* ```
|
|
940
|
+
*/
|
|
941
|
+
output?: OutputSpecification<OUTPUT, PARTIAL_OUTPUT>;
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Whether to include raw chunks from the provider in the stream.
|
|
945
|
+
* When enabled, you will receive raw chunks with type 'raw' that contain the unprocessed data from the provider.
|
|
946
|
+
* This allows access to cutting-edge provider features not yet wrapped by the AI SDK.
|
|
947
|
+
* Defaults to false.
|
|
948
|
+
*/
|
|
949
|
+
includeRawChunks?: boolean;
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* A function that attempts to repair a tool call that failed to parse.
|
|
953
|
+
*/
|
|
954
|
+
repairToolCall?: ToolCallRepairFunction<TTools>;
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* A function that attempts to repair a tool call that failed to parse.
|
|
958
|
+
*
|
|
959
|
+
* @deprecated Use `repairToolCall` instead.
|
|
960
|
+
*/
|
|
961
|
+
experimental_repairToolCall?: ToolCallRepairFunction<TTools>;
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Optional stream transformations.
|
|
965
|
+
* They are applied in the order they are provided.
|
|
966
|
+
* The stream transformations must maintain the stream structure for streamText to work correctly.
|
|
967
|
+
*/
|
|
968
|
+
experimental_transform?:
|
|
969
|
+
| StreamTextTransform<TTools>
|
|
970
|
+
| Array<StreamTextTransform<TTools>>;
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Custom download function to use for URLs.
|
|
974
|
+
* By default, files are downloaded if the model does not support the URL for the given media type.
|
|
975
|
+
*/
|
|
976
|
+
experimental_download?: DownloadFunction;
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Sandbox environment passed through to tool execution as
|
|
980
|
+
* `experimental_sandbox`. Overrides the constructor-level value if provided.
|
|
981
|
+
*/
|
|
982
|
+
experimental_sandbox?: SandboxSession;
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Callback function to be called after each step completes.
|
|
986
|
+
*/
|
|
987
|
+
onStepEnd?: WorkflowAgentOnStepEndCallback<TTools, TRuntimeContext>;
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Callback function to be called after each step completes.
|
|
991
|
+
*
|
|
992
|
+
* @deprecated Use `onStepEnd` instead.
|
|
993
|
+
*/
|
|
994
|
+
onStepFinish?: WorkflowAgentOnStepFinishCallback<TTools, TRuntimeContext>;
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Callback that is invoked when an error occurs during streaming.
|
|
998
|
+
* You can use it to log errors.
|
|
999
|
+
*/
|
|
1000
|
+
onError?: WorkflowAgentOnErrorCallback;
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Callback that is called when the LLM response and all request tool executions
|
|
1004
|
+
* (for tools that have an `execute` function) are finished.
|
|
1005
|
+
*/
|
|
1006
|
+
onEnd?: WorkflowAgentOnEndCallback<TTools, TRuntimeContext, OUTPUT>;
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Callback that is called when the LLM response and all request tool executions
|
|
1010
|
+
* (for tools that have an `execute` function) are finished.
|
|
1011
|
+
*
|
|
1012
|
+
* @deprecated Use `onEnd` instead.
|
|
1013
|
+
*/
|
|
1014
|
+
onFinish?: WorkflowAgentOnFinishCallback<TTools, TRuntimeContext, OUTPUT>;
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Callback that is called when the operation is aborted.
|
|
1018
|
+
*/
|
|
1019
|
+
onAbort?: WorkflowAgentOnAbortCallback<TTools>;
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Callback called when the agent starts streaming, before any LLM calls.
|
|
1023
|
+
*/
|
|
1024
|
+
experimental_onStart?: WorkflowAgentOnStartCallback<
|
|
1025
|
+
TTools,
|
|
1026
|
+
TRuntimeContext
|
|
1027
|
+
>;
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Callback called before each step (LLM call) begins.
|
|
1031
|
+
*/
|
|
1032
|
+
experimental_onStepStart?: WorkflowAgentOnStepStartCallback<
|
|
1033
|
+
TTools,
|
|
1034
|
+
TRuntimeContext
|
|
1035
|
+
>;
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Callback called before a tool's execute function runs.
|
|
1039
|
+
*/
|
|
1040
|
+
onToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback<TTools>;
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Callback called after a tool execution completes.
|
|
1044
|
+
*/
|
|
1045
|
+
onToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback<TTools>;
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Callback function called before each step in the agent loop.
|
|
1049
|
+
* Use this to modify settings, manage context, or inject messages dynamically.
|
|
1050
|
+
*
|
|
1051
|
+
* @example
|
|
1052
|
+
* ```typescript
|
|
1053
|
+
* prepareStep: async ({ messages, stepNumber }) => {
|
|
1054
|
+
* // Inject messages from a queue
|
|
1055
|
+
* const queuedMessages = await getQueuedMessages();
|
|
1056
|
+
* if (queuedMessages.length > 0) {
|
|
1057
|
+
* return {
|
|
1058
|
+
* messages: [...messages, ...queuedMessages],
|
|
1059
|
+
* };
|
|
1060
|
+
* }
|
|
1061
|
+
* return {};
|
|
1062
|
+
* }
|
|
1063
|
+
* ```
|
|
1064
|
+
*/
|
|
1065
|
+
prepareStep?: PrepareStepCallback<TTools, TRuntimeContext>;
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Timeout in milliseconds for the stream operation.
|
|
1069
|
+
* When specified, creates an AbortSignal that will abort the operation after the given time.
|
|
1070
|
+
* If both `timeout` and `abortSignal` are provided, whichever triggers first will abort.
|
|
1071
|
+
*/
|
|
1072
|
+
timeout?: number;
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Whether to send a 'finish' chunk to the writable stream when streaming completes.
|
|
1076
|
+
* @default true
|
|
1077
|
+
*/
|
|
1078
|
+
sendFinish?: boolean;
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Whether to prevent the writable stream from being closed after streaming completes.
|
|
1082
|
+
* @default false
|
|
1083
|
+
*/
|
|
1084
|
+
preventClose?: boolean;
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* A tool call made by the model. Matches the AI SDK's tool call shape.
|
|
1089
|
+
*/
|
|
1090
|
+
export interface ToolCall {
|
|
1091
|
+
/** Discriminator for content part arrays */
|
|
1092
|
+
type: 'tool-call';
|
|
1093
|
+
/** The unique identifier of the tool call */
|
|
1094
|
+
toolCallId: string;
|
|
1095
|
+
/** The name of the tool that was called */
|
|
1096
|
+
toolName: string;
|
|
1097
|
+
/** The parsed input arguments for the tool call */
|
|
1098
|
+
input: unknown;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* A tool result from executing a tool. Matches the AI SDK's tool result shape.
|
|
1103
|
+
*/
|
|
1104
|
+
export interface ToolResult {
|
|
1105
|
+
/** Discriminator for content part arrays */
|
|
1106
|
+
type: 'tool-result';
|
|
1107
|
+
/** The tool call ID this result corresponds to */
|
|
1108
|
+
toolCallId: string;
|
|
1109
|
+
/** The name of the tool that was executed */
|
|
1110
|
+
toolName: string;
|
|
1111
|
+
/** The parsed input arguments that were passed to the tool */
|
|
1112
|
+
input: unknown;
|
|
1113
|
+
/** The output produced by the tool */
|
|
1114
|
+
output: unknown;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
type WorkflowToolExecutionResult = {
|
|
1118
|
+
modelResult: LanguageModelV4ToolResultPart;
|
|
1119
|
+
rawOutput: unknown;
|
|
1120
|
+
isError: boolean;
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Result of the WorkflowAgent.stream method.
|
|
1125
|
+
*/
|
|
1126
|
+
export interface WorkflowAgentStreamResult<
|
|
1127
|
+
TTools extends ToolSet = ToolSet,
|
|
1128
|
+
OUTPUT = never,
|
|
1129
|
+
> {
|
|
1130
|
+
/**
|
|
1131
|
+
* The final messages including all tool calls and results.
|
|
1132
|
+
*/
|
|
1133
|
+
messages: ModelMessage[];
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Details for all steps.
|
|
1137
|
+
*/
|
|
1138
|
+
steps: StepResult<TTools, any>[];
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* The tool calls from the last step.
|
|
1142
|
+
* Includes all tool calls regardless of whether they were executed.
|
|
1143
|
+
*
|
|
1144
|
+
* When the agent stops because a tool without an `execute` function was called,
|
|
1145
|
+
* this array will contain those calls. Compare with `toolResults` to find
|
|
1146
|
+
* unresolved tool calls that need client-side handling:
|
|
1147
|
+
*
|
|
1148
|
+
* ```ts
|
|
1149
|
+
* const unresolved = result.toolCalls.filter(
|
|
1150
|
+
* tc => !result.toolResults.some(tr => tr.toolCallId === tc.toolCallId)
|
|
1151
|
+
* );
|
|
1152
|
+
* ```
|
|
1153
|
+
*
|
|
1154
|
+
* This matches the AI SDK's `GenerateTextResult.toolCalls` behavior.
|
|
1155
|
+
*/
|
|
1156
|
+
toolCalls: ToolCall[];
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* The tool results from the last step.
|
|
1160
|
+
* Only includes results for tools that were actually executed (server-side or provider-executed).
|
|
1161
|
+
* Tools without an `execute` function will NOT have entries here.
|
|
1162
|
+
*
|
|
1163
|
+
* This matches the AI SDK's `GenerateTextResult.toolResults` behavior.
|
|
1164
|
+
*/
|
|
1165
|
+
toolResults: ToolResult[];
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* The finish reason from the last step.
|
|
1169
|
+
*/
|
|
1170
|
+
finishReason: FinishReason;
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* The total token usage across all steps.
|
|
1174
|
+
*/
|
|
1175
|
+
totalUsage: LanguageModelUsage;
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* The generated structured output. It uses the `output` specification.
|
|
1179
|
+
* Only available when `output` is specified.
|
|
1180
|
+
*/
|
|
1181
|
+
output: OUTPUT;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* A class for building durable AI agents within workflows.
|
|
1186
|
+
*
|
|
1187
|
+
* WorkflowAgent enables you to create AI-powered agents that can maintain state
|
|
1188
|
+
* across workflow steps, call tools, and gracefully handle interruptions and resumptions.
|
|
1189
|
+
* It integrates seamlessly with the AI SDK and the Workflow DevKit for
|
|
1190
|
+
* production-grade reliability.
|
|
1191
|
+
*
|
|
1192
|
+
* @example
|
|
1193
|
+
* ```typescript
|
|
1194
|
+
* const agent = new WorkflowAgent({
|
|
1195
|
+
* model: 'anthropic/claude-opus',
|
|
1196
|
+
* tools: {
|
|
1197
|
+
* getWeather: {
|
|
1198
|
+
* description: 'Get weather for a location',
|
|
1199
|
+
* inputSchema: z.object({ location: z.string() }),
|
|
1200
|
+
* execute: getWeatherStep,
|
|
1201
|
+
* },
|
|
1202
|
+
* },
|
|
1203
|
+
* instructions: 'You are a helpful weather assistant.',
|
|
1204
|
+
* });
|
|
1205
|
+
*
|
|
1206
|
+
* const result = await agent.stream({
|
|
1207
|
+
* messages: [{ role: 'user', content: 'What is the weather?' }],
|
|
1208
|
+
* });
|
|
1209
|
+
* ```
|
|
1210
|
+
*/
|
|
1211
|
+
export class WorkflowAgent<
|
|
1212
|
+
TBaseTools extends ToolSet = ToolSet,
|
|
1213
|
+
TRuntimeContext extends Context = Context,
|
|
1214
|
+
> {
|
|
1215
|
+
/**
|
|
1216
|
+
* The id of the agent.
|
|
1217
|
+
*/
|
|
1218
|
+
public readonly id: string | undefined;
|
|
1219
|
+
|
|
1220
|
+
private model: LanguageModel;
|
|
1221
|
+
/**
|
|
1222
|
+
* The tool set configured for this agent.
|
|
1223
|
+
*/
|
|
1224
|
+
public readonly tools: TBaseTools;
|
|
1225
|
+
private instructions?: Instructions;
|
|
1226
|
+
private generationSettings: GenerationSettings;
|
|
1227
|
+
private toolChoice?: ToolChoice<TBaseTools>;
|
|
1228
|
+
private telemetry?: TelemetryOptions<TRuntimeContext, TBaseTools>;
|
|
1229
|
+
private runtimeContext?: TRuntimeContext;
|
|
1230
|
+
private toolsContext?: InferToolSetContext<TBaseTools>;
|
|
1231
|
+
private stopWhen?:
|
|
1232
|
+
| StopCondition<ToolSet, any>
|
|
1233
|
+
| Array<StopCondition<ToolSet, any>>;
|
|
1234
|
+
private activeTools?: ActiveTools<TBaseTools>;
|
|
1235
|
+
private output?: OutputSpecification<any, any>;
|
|
1236
|
+
private repairToolCall?: ToolCallRepairFunction<TBaseTools>;
|
|
1237
|
+
private experimentalDownload?: DownloadFunction;
|
|
1238
|
+
private experimentalSandbox?: SandboxSession;
|
|
1239
|
+
private prepareStep?: PrepareStepCallback<TBaseTools, TRuntimeContext>;
|
|
1240
|
+
private allowSystemInMessages: boolean;
|
|
1241
|
+
private constructorOnStepEnd?: WorkflowAgentOnStepEndCallback<
|
|
1242
|
+
TBaseTools,
|
|
1243
|
+
TRuntimeContext
|
|
1244
|
+
>;
|
|
1245
|
+
private constructorOnEnd?: WorkflowAgentOnEndCallback<
|
|
1246
|
+
TBaseTools,
|
|
1247
|
+
TRuntimeContext
|
|
1248
|
+
>;
|
|
1249
|
+
private constructorOnStart?: WorkflowAgentOnStartCallback<
|
|
1250
|
+
TBaseTools,
|
|
1251
|
+
TRuntimeContext
|
|
1252
|
+
>;
|
|
1253
|
+
private constructorOnStepStart?: WorkflowAgentOnStepStartCallback<
|
|
1254
|
+
TBaseTools,
|
|
1255
|
+
TRuntimeContext
|
|
1256
|
+
>;
|
|
1257
|
+
private constructorOnToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback<TBaseTools>;
|
|
1258
|
+
private constructorOnToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback<TBaseTools>;
|
|
1259
|
+
private prepareCall?: PrepareCallCallback<TBaseTools, TRuntimeContext>;
|
|
1260
|
+
|
|
1261
|
+
constructor(options: WorkflowAgentOptions<TBaseTools, TRuntimeContext>) {
|
|
1262
|
+
this.id = options.id;
|
|
1263
|
+
this.model = options.model;
|
|
1264
|
+
this.tools = (options.tools ?? {}) as TBaseTools;
|
|
1265
|
+
// `instructions` takes precedence over deprecated `system`
|
|
1266
|
+
this.instructions = options.instructions ?? options.system;
|
|
1267
|
+
this.toolChoice = options.toolChoice;
|
|
1268
|
+
this.telemetry = options.telemetry;
|
|
1269
|
+
this.runtimeContext = options.runtimeContext;
|
|
1270
|
+
this.toolsContext = options.toolsContext;
|
|
1271
|
+
this.stopWhen = options.stopWhen;
|
|
1272
|
+
this.activeTools = options.activeTools;
|
|
1273
|
+
this.output = options.output;
|
|
1274
|
+
this.repairToolCall =
|
|
1275
|
+
options.repairToolCall ?? options.experimental_repairToolCall;
|
|
1276
|
+
this.experimentalDownload = options.experimental_download;
|
|
1277
|
+
this.experimentalSandbox = options.experimental_sandbox;
|
|
1278
|
+
this.prepareStep = options.prepareStep;
|
|
1279
|
+
this.constructorOnStepEnd = options.onStepEnd ?? options.onStepFinish;
|
|
1280
|
+
const { onFinish, onEnd = onFinish } = options;
|
|
1281
|
+
this.constructorOnEnd = onEnd;
|
|
1282
|
+
this.constructorOnStart = options.experimental_onStart;
|
|
1283
|
+
this.constructorOnStepStart = options.experimental_onStepStart;
|
|
1284
|
+
this.constructorOnToolExecutionStart = options.onToolExecutionStart;
|
|
1285
|
+
this.constructorOnToolExecutionEnd = options.onToolExecutionEnd;
|
|
1286
|
+
this.prepareCall = options.prepareCall;
|
|
1287
|
+
this.allowSystemInMessages = options.allowSystemInMessages ?? false;
|
|
1288
|
+
|
|
1289
|
+
// Extract generation settings
|
|
1290
|
+
this.generationSettings = {
|
|
1291
|
+
maxOutputTokens: options.maxOutputTokens,
|
|
1292
|
+
temperature: options.temperature,
|
|
1293
|
+
topP: options.topP,
|
|
1294
|
+
topK: options.topK,
|
|
1295
|
+
presencePenalty: options.presencePenalty,
|
|
1296
|
+
frequencyPenalty: options.frequencyPenalty,
|
|
1297
|
+
stopSequences: options.stopSequences,
|
|
1298
|
+
seed: options.seed,
|
|
1299
|
+
maxRetries: options.maxRetries,
|
|
1300
|
+
abortSignal: options.abortSignal,
|
|
1301
|
+
headers: options.headers,
|
|
1302
|
+
reasoning: options.reasoning,
|
|
1303
|
+
providerOptions: options.providerOptions,
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
generate() {
|
|
1308
|
+
throw new Error('Not implemented');
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
async stream<
|
|
1312
|
+
TTools extends TBaseTools = TBaseTools,
|
|
1313
|
+
OUTPUT = never,
|
|
1314
|
+
PARTIAL_OUTPUT = never,
|
|
1315
|
+
>(
|
|
1316
|
+
options: WorkflowAgentStreamOptions<
|
|
1317
|
+
TTools,
|
|
1318
|
+
TRuntimeContext,
|
|
1319
|
+
OUTPUT,
|
|
1320
|
+
PARTIAL_OUTPUT
|
|
1321
|
+
>,
|
|
1322
|
+
): Promise<WorkflowAgentStreamResult<TTools, OUTPUT>> {
|
|
1323
|
+
const { onFinish, onEnd = onFinish } = options;
|
|
1324
|
+
|
|
1325
|
+
// Call prepareCall to transform parameters before the agent loop
|
|
1326
|
+
let effectiveModel: LanguageModel = this.model;
|
|
1327
|
+
let effectiveInstructions = options.system ?? this.instructions;
|
|
1328
|
+
let effectivePrompt: string | Array<ModelMessage> | undefined =
|
|
1329
|
+
options.prompt;
|
|
1330
|
+
let effectiveMessages: Array<ModelMessage> | undefined = options.messages;
|
|
1331
|
+
let effectiveGenerationSettings = { ...this.generationSettings };
|
|
1332
|
+
let effectiveRuntimeContext: TRuntimeContext = (options.runtimeContext ??
|
|
1333
|
+
this.runtimeContext ??
|
|
1334
|
+
{}) as TRuntimeContext;
|
|
1335
|
+
let effectiveToolsContext: Record<string, Context | undefined> =
|
|
1336
|
+
(options.toolsContext ?? this.toolsContext ?? {}) as unknown as Record<
|
|
1337
|
+
string,
|
|
1338
|
+
Context | undefined
|
|
1339
|
+
>;
|
|
1340
|
+
let effectiveToolChoiceFromPrepare = options.toolChoice ?? this.toolChoice;
|
|
1341
|
+
let effectiveTelemetryFromPrepare = options.telemetry ?? this.telemetry;
|
|
1342
|
+
|
|
1343
|
+
// Resolve messages for prepareCall: use messages directly, or convert prompt
|
|
1344
|
+
const resolvedMessagesForPrepareCall: ModelMessage[] =
|
|
1345
|
+
effectiveMessages ??
|
|
1346
|
+
(typeof effectivePrompt === 'string'
|
|
1347
|
+
? [{ role: 'user' as const, content: effectivePrompt }]
|
|
1348
|
+
: (effectivePrompt as ModelMessage[])) ??
|
|
1349
|
+
[];
|
|
1350
|
+
|
|
1351
|
+
if (this.prepareCall) {
|
|
1352
|
+
const prepared = await this.prepareCall({
|
|
1353
|
+
model: effectiveModel,
|
|
1354
|
+
tools: this.tools,
|
|
1355
|
+
instructions: effectiveInstructions,
|
|
1356
|
+
toolChoice: effectiveToolChoiceFromPrepare as ToolChoice<TBaseTools>,
|
|
1357
|
+
telemetry: effectiveTelemetryFromPrepare,
|
|
1358
|
+
runtimeContext: effectiveRuntimeContext,
|
|
1359
|
+
toolsContext: effectiveToolsContext as InferToolSetContext<TBaseTools>,
|
|
1360
|
+
messages: resolvedMessagesForPrepareCall,
|
|
1361
|
+
...effectiveGenerationSettings,
|
|
1362
|
+
} as PrepareCallOptions<TBaseTools, TRuntimeContext>);
|
|
1363
|
+
|
|
1364
|
+
if (prepared.model !== undefined) effectiveModel = prepared.model;
|
|
1365
|
+
if (prepared.instructions !== undefined)
|
|
1366
|
+
effectiveInstructions = prepared.instructions;
|
|
1367
|
+
if (prepared.messages !== undefined) {
|
|
1368
|
+
effectiveMessages = prepared.messages as Array<ModelMessage>;
|
|
1369
|
+
effectivePrompt = undefined; // messages from prepareCall take precedence
|
|
1370
|
+
}
|
|
1371
|
+
if (prepared.runtimeContext !== undefined)
|
|
1372
|
+
effectiveRuntimeContext = prepared.runtimeContext;
|
|
1373
|
+
if (prepared.toolsContext !== undefined)
|
|
1374
|
+
effectiveToolsContext = prepared.toolsContext as Record<
|
|
1375
|
+
string,
|
|
1376
|
+
Context | undefined
|
|
1377
|
+
>;
|
|
1378
|
+
if (prepared.toolChoice !== undefined)
|
|
1379
|
+
effectiveToolChoiceFromPrepare =
|
|
1380
|
+
prepared.toolChoice as ToolChoice<TBaseTools>;
|
|
1381
|
+
if (prepared.telemetry !== undefined)
|
|
1382
|
+
effectiveTelemetryFromPrepare = prepared.telemetry;
|
|
1383
|
+
if (prepared.maxOutputTokens !== undefined)
|
|
1384
|
+
effectiveGenerationSettings.maxOutputTokens = prepared.maxOutputTokens;
|
|
1385
|
+
if (prepared.temperature !== undefined)
|
|
1386
|
+
effectiveGenerationSettings.temperature = prepared.temperature;
|
|
1387
|
+
if (prepared.topP !== undefined)
|
|
1388
|
+
effectiveGenerationSettings.topP = prepared.topP;
|
|
1389
|
+
if (prepared.topK !== undefined)
|
|
1390
|
+
effectiveGenerationSettings.topK = prepared.topK;
|
|
1391
|
+
if (prepared.presencePenalty !== undefined)
|
|
1392
|
+
effectiveGenerationSettings.presencePenalty = prepared.presencePenalty;
|
|
1393
|
+
if (prepared.frequencyPenalty !== undefined)
|
|
1394
|
+
effectiveGenerationSettings.frequencyPenalty =
|
|
1395
|
+
prepared.frequencyPenalty;
|
|
1396
|
+
if (prepared.stopSequences !== undefined)
|
|
1397
|
+
effectiveGenerationSettings.stopSequences = prepared.stopSequences;
|
|
1398
|
+
if (prepared.seed !== undefined)
|
|
1399
|
+
effectiveGenerationSettings.seed = prepared.seed;
|
|
1400
|
+
if (prepared.headers !== undefined)
|
|
1401
|
+
effectiveGenerationSettings.headers = prepared.headers;
|
|
1402
|
+
if (prepared.reasoning !== undefined)
|
|
1403
|
+
effectiveGenerationSettings.reasoning = prepared.reasoning;
|
|
1404
|
+
if (prepared.providerOptions !== undefined)
|
|
1405
|
+
effectiveGenerationSettings.providerOptions = prepared.providerOptions;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
const effectiveTelemetry = effectiveTelemetryFromPrepare;
|
|
1409
|
+
const telemetryDispatcher = createRestrictedTelemetryDispatcher<
|
|
1410
|
+
any,
|
|
1411
|
+
any,
|
|
1412
|
+
any
|
|
1413
|
+
>({
|
|
1414
|
+
telemetry: effectiveTelemetry as any,
|
|
1415
|
+
includeRuntimeContext: effectiveTelemetry?.includeRuntimeContext,
|
|
1416
|
+
includeToolsContext: effectiveTelemetry?.includeToolsContext,
|
|
1417
|
+
}) as any;
|
|
1418
|
+
|
|
1419
|
+
const prompt = await standardizePrompt({
|
|
1420
|
+
system: effectiveInstructions,
|
|
1421
|
+
allowSystemInMessages: this.allowSystemInMessages,
|
|
1422
|
+
...(effectivePrompt != null
|
|
1423
|
+
? { prompt: effectivePrompt }
|
|
1424
|
+
: { messages: effectiveMessages! }),
|
|
1425
|
+
} as Prompt);
|
|
1426
|
+
const download = options.experimental_download ?? this.experimentalDownload;
|
|
1427
|
+
const sandbox = options.experimental_sandbox ?? this.experimentalSandbox;
|
|
1428
|
+
|
|
1429
|
+
// Process tool approval responses before starting the agent loop.
|
|
1430
|
+
// This mirrors how stream-text.ts handles tool-approval-response parts:
|
|
1431
|
+
// approved tools are executed, denied tools get denial results, and
|
|
1432
|
+
// approval parts are stripped from the messages.
|
|
1433
|
+
// Use the AI SDK core collector so this path cannot drift from the
|
|
1434
|
+
// hardened generateText/streamText implementation. The collected approvals
|
|
1435
|
+
// are mapped to the flat shape used below; the original (nested) approval
|
|
1436
|
+
// is carried on `collected` for re-validation.
|
|
1437
|
+
const collectedApprovals = collectToolApprovals<ToolSet>({
|
|
1438
|
+
messages: prompt.messages,
|
|
1439
|
+
});
|
|
1440
|
+
const approvedToolApprovals = collectedApprovals.approvedToolApprovals.map(
|
|
1441
|
+
collected => ({
|
|
1442
|
+
toolCallId: collected.toolCall.toolCallId,
|
|
1443
|
+
toolName: collected.toolCall.toolName,
|
|
1444
|
+
input: collected.toolCall.input,
|
|
1445
|
+
reason: collected.approvalResponse.reason,
|
|
1446
|
+
providerExecuted: collected.toolCall.providerExecuted === true,
|
|
1447
|
+
collected,
|
|
1448
|
+
}),
|
|
1449
|
+
);
|
|
1450
|
+
const deniedToolApprovals = collectedApprovals.deniedToolApprovals.map(
|
|
1451
|
+
collected => ({
|
|
1452
|
+
toolCallId: collected.toolCall.toolCallId,
|
|
1453
|
+
toolName: collected.toolCall.toolName,
|
|
1454
|
+
input: collected.toolCall.input,
|
|
1455
|
+
reason: collected.approvalResponse.reason,
|
|
1456
|
+
providerExecuted: collected.toolCall.providerExecuted === true,
|
|
1457
|
+
}),
|
|
1458
|
+
);
|
|
1459
|
+
|
|
1460
|
+
// Approval ids of provider-executed tool calls. Provider-executed tools
|
|
1461
|
+
// (e.g. MCP via the Responses API) cannot be resolved locally — the
|
|
1462
|
+
// provider owns execution. We therefore skip them from local execution
|
|
1463
|
+
// and preserve their approval responses in the messages so the provider
|
|
1464
|
+
// receives the approval on the next call. The discriminator is sourced
|
|
1465
|
+
// from the original `tool-call` part (matching how core's stream-text.ts
|
|
1466
|
+
// decides), not from the response part which may be missing the flag.
|
|
1467
|
+
const providerExecutedApprovalIds = new Set<string>(
|
|
1468
|
+
[
|
|
1469
|
+
...collectedApprovals.approvedToolApprovals,
|
|
1470
|
+
...collectedApprovals.deniedToolApprovals,
|
|
1471
|
+
]
|
|
1472
|
+
.filter(collected => collected.toolCall.providerExecuted === true)
|
|
1473
|
+
.map(collected => collected.approvalResponse.approvalId),
|
|
1474
|
+
);
|
|
1475
|
+
|
|
1476
|
+
if (approvedToolApprovals.length > 0 || deniedToolApprovals.length > 0) {
|
|
1477
|
+
const _toolResultMessages: ModelMessage[] = [];
|
|
1478
|
+
const toolResultContent: LanguageModelV4ToolResultPart[] = [];
|
|
1479
|
+
const approvedRawResults: Array<{
|
|
1480
|
+
toolCallId: string;
|
|
1481
|
+
toolName: string;
|
|
1482
|
+
input: unknown;
|
|
1483
|
+
output: unknown;
|
|
1484
|
+
}> = [];
|
|
1485
|
+
|
|
1486
|
+
// Execute approved tools
|
|
1487
|
+
for (const approval of approvedToolApprovals) {
|
|
1488
|
+
// Provider-executed approvals are forwarded to the provider via the
|
|
1489
|
+
// preserved approval response below, not executed locally.
|
|
1490
|
+
if (approval.providerExecuted) {
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
const tool = (this.tools as ToolSet)[approval.toolName];
|
|
1494
|
+
if (tool && typeof tool.execute === 'function') {
|
|
1495
|
+
if (!tool.needsApproval) {
|
|
1496
|
+
const reason = `Tool "${approval.toolName}" does not require approval`;
|
|
1497
|
+
toolResultContent.push({
|
|
1498
|
+
type: 'tool-result' as const,
|
|
1499
|
+
toolCallId: approval.toolCallId,
|
|
1500
|
+
toolName: approval.toolName,
|
|
1501
|
+
output: await createLanguageModelToolResultOutput({
|
|
1502
|
+
toolCallId: approval.toolCallId,
|
|
1503
|
+
toolName: approval.toolName,
|
|
1504
|
+
input: approval.input,
|
|
1505
|
+
output: reason,
|
|
1506
|
+
tool,
|
|
1507
|
+
errorMode: 'text',
|
|
1508
|
+
supportedUrls: {},
|
|
1509
|
+
download,
|
|
1510
|
+
}),
|
|
1511
|
+
});
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// Re-validate through the shared core implementation: input schema,
|
|
1516
|
+
// HMAC signature (when configured), and approval policy. It throws on
|
|
1517
|
+
// invalid input/signature; convert that to a denial result so the
|
|
1518
|
+
// agent loop can continue gracefully.
|
|
1519
|
+
let revalidationReason: string | undefined;
|
|
1520
|
+
try {
|
|
1521
|
+
const { deniedToolApprovals: policyDenied } =
|
|
1522
|
+
await validateApprovedToolApprovals({
|
|
1523
|
+
approvedToolApprovals: [approval.collected],
|
|
1524
|
+
tools: this.tools as ToolSet,
|
|
1525
|
+
toolApproval: undefined,
|
|
1526
|
+
messages: prompt.messages,
|
|
1527
|
+
toolsContext:
|
|
1528
|
+
effectiveToolsContext as InferToolSetContext<ToolSet>,
|
|
1529
|
+
runtimeContext: effectiveRuntimeContext,
|
|
1530
|
+
});
|
|
1531
|
+
if (policyDenied.length > 0) {
|
|
1532
|
+
revalidationReason =
|
|
1533
|
+
policyDenied[0].approvalResponse.reason ??
|
|
1534
|
+
'Tool approval denied';
|
|
1535
|
+
}
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
revalidationReason = getErrorMessage(error);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
if (revalidationReason != null) {
|
|
1541
|
+
toolResultContent.push({
|
|
1542
|
+
type: 'tool-result' as const,
|
|
1543
|
+
toolCallId: approval.toolCallId,
|
|
1544
|
+
toolName: approval.toolName,
|
|
1545
|
+
output: await createLanguageModelToolResultOutput({
|
|
1546
|
+
toolCallId: approval.toolCallId,
|
|
1547
|
+
toolName: approval.toolName,
|
|
1548
|
+
input: approval.input,
|
|
1549
|
+
output: revalidationReason,
|
|
1550
|
+
tool,
|
|
1551
|
+
errorMode: 'text',
|
|
1552
|
+
supportedUrls: {},
|
|
1553
|
+
download,
|
|
1554
|
+
}),
|
|
1555
|
+
});
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
try {
|
|
1560
|
+
const { execute } = tool;
|
|
1561
|
+
const resolvedContext = await resolveToolContext({
|
|
1562
|
+
toolName: approval.toolName,
|
|
1563
|
+
tool,
|
|
1564
|
+
toolsContext: effectiveToolsContext,
|
|
1565
|
+
});
|
|
1566
|
+
const toolCallEvent: ToolCall = {
|
|
1567
|
+
type: 'tool-call',
|
|
1568
|
+
toolCallId: approval.toolCallId,
|
|
1569
|
+
toolName: approval.toolName,
|
|
1570
|
+
input: approval.input,
|
|
1571
|
+
};
|
|
1572
|
+
const messages = prompt.messages as unknown as ModelMessage[];
|
|
1573
|
+
await telemetryDispatcher.onToolExecutionStart?.({
|
|
1574
|
+
toolCall: toolCallEvent,
|
|
1575
|
+
stepNumber: 0,
|
|
1576
|
+
messages,
|
|
1577
|
+
toolContext: resolvedContext,
|
|
1578
|
+
});
|
|
1579
|
+
const startTime = Date.now();
|
|
1580
|
+
const executeApprovedTool = () =>
|
|
1581
|
+
execute(approval.input, {
|
|
1582
|
+
toolCallId: approval.toolCallId,
|
|
1583
|
+
messages: [],
|
|
1584
|
+
context: resolvedContext,
|
|
1585
|
+
experimental_sandbox: sandbox,
|
|
1586
|
+
});
|
|
1587
|
+
const toolResult =
|
|
1588
|
+
telemetryDispatcher.executeTool != null
|
|
1589
|
+
? await telemetryDispatcher.executeTool({
|
|
1590
|
+
callId: 'workflow-agent',
|
|
1591
|
+
toolCallId: approval.toolCallId,
|
|
1592
|
+
execute: executeApprovedTool,
|
|
1593
|
+
})
|
|
1594
|
+
: await executeApprovedTool();
|
|
1595
|
+
await telemetryDispatcher.onToolExecutionEnd?.({
|
|
1596
|
+
toolCall: toolCallEvent,
|
|
1597
|
+
stepNumber: 0,
|
|
1598
|
+
durationMs: Date.now() - startTime,
|
|
1599
|
+
messages,
|
|
1600
|
+
toolContext: resolvedContext,
|
|
1601
|
+
success: true,
|
|
1602
|
+
output: toolResult,
|
|
1603
|
+
});
|
|
1604
|
+
toolResultContent.push({
|
|
1605
|
+
type: 'tool-result' as const,
|
|
1606
|
+
toolCallId: approval.toolCallId,
|
|
1607
|
+
toolName: approval.toolName,
|
|
1608
|
+
output: await createLanguageModelToolResultOutput({
|
|
1609
|
+
toolCallId: approval.toolCallId,
|
|
1610
|
+
toolName: approval.toolName,
|
|
1611
|
+
input: approval.input,
|
|
1612
|
+
output: toolResult,
|
|
1613
|
+
tool,
|
|
1614
|
+
errorMode: 'none',
|
|
1615
|
+
supportedUrls: {},
|
|
1616
|
+
download,
|
|
1617
|
+
}),
|
|
1618
|
+
});
|
|
1619
|
+
approvedRawResults.push({
|
|
1620
|
+
toolCallId: approval.toolCallId,
|
|
1621
|
+
toolName: approval.toolName,
|
|
1622
|
+
input: approval.input,
|
|
1623
|
+
output: toolResult,
|
|
1624
|
+
});
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
const errorMessage = getErrorMessage(error);
|
|
1627
|
+
await telemetryDispatcher.onToolExecutionEnd?.({
|
|
1628
|
+
toolCall: {
|
|
1629
|
+
type: 'tool-call',
|
|
1630
|
+
toolCallId: approval.toolCallId,
|
|
1631
|
+
toolName: approval.toolName,
|
|
1632
|
+
input: approval.input,
|
|
1633
|
+
},
|
|
1634
|
+
stepNumber: 0,
|
|
1635
|
+
durationMs: 0,
|
|
1636
|
+
messages: prompt.messages as unknown as ModelMessage[],
|
|
1637
|
+
toolContext: undefined,
|
|
1638
|
+
success: false,
|
|
1639
|
+
error,
|
|
1640
|
+
});
|
|
1641
|
+
toolResultContent.push({
|
|
1642
|
+
type: 'tool-result' as const,
|
|
1643
|
+
toolCallId: approval.toolCallId,
|
|
1644
|
+
toolName: approval.toolName,
|
|
1645
|
+
output: await createLanguageModelToolResultOutput({
|
|
1646
|
+
toolCallId: approval.toolCallId,
|
|
1647
|
+
toolName: approval.toolName,
|
|
1648
|
+
input: approval.input,
|
|
1649
|
+
output: errorMessage,
|
|
1650
|
+
tool,
|
|
1651
|
+
errorMode: 'text',
|
|
1652
|
+
supportedUrls: {},
|
|
1653
|
+
download,
|
|
1654
|
+
}),
|
|
1655
|
+
});
|
|
1656
|
+
approvedRawResults.push({
|
|
1657
|
+
toolCallId: approval.toolCallId,
|
|
1658
|
+
toolName: approval.toolName,
|
|
1659
|
+
input: approval.input,
|
|
1660
|
+
output: errorMessage,
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// Create denial results for denied tools
|
|
1667
|
+
for (const denial of deniedToolApprovals) {
|
|
1668
|
+
// Provider-executed denials are forwarded to the provider via the
|
|
1669
|
+
// preserved approval response below, not turned into a local result.
|
|
1670
|
+
if (denial.providerExecuted) {
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
toolResultContent.push({
|
|
1674
|
+
type: 'tool-result' as const,
|
|
1675
|
+
toolCallId: denial.toolCallId,
|
|
1676
|
+
toolName: denial.toolName,
|
|
1677
|
+
output: {
|
|
1678
|
+
type: 'execution-denied' as const,
|
|
1679
|
+
reason: denial.reason,
|
|
1680
|
+
},
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// Strip approval parts that we resolved locally and inject tool results.
|
|
1685
|
+
// Provider-executed approval parts are preserved so the next call to
|
|
1686
|
+
// `convertToLanguageModelPrompt` forwards the approval response to the
|
|
1687
|
+
// provider (it only forwards responses flagged `providerExecuted`).
|
|
1688
|
+
const cleanedMessages: ModelMessage[] = [];
|
|
1689
|
+
for (const msg of prompt.messages) {
|
|
1690
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1691
|
+
const filtered = (msg.content as any[]).filter(
|
|
1692
|
+
(p: any) =>
|
|
1693
|
+
p.type !== 'tool-approval-request' ||
|
|
1694
|
+
providerExecutedApprovalIds.has(p.approvalId),
|
|
1695
|
+
);
|
|
1696
|
+
if (filtered.length > 0) {
|
|
1697
|
+
cleanedMessages.push({ ...msg, content: filtered });
|
|
1698
|
+
}
|
|
1699
|
+
} else if (msg.role === 'tool') {
|
|
1700
|
+
const filtered = (msg.content as any[]).flatMap((p: any) => {
|
|
1701
|
+
if (p.type !== 'tool-approval-response') {
|
|
1702
|
+
return [p];
|
|
1703
|
+
}
|
|
1704
|
+
if (!providerExecutedApprovalIds.has(p.approvalId)) {
|
|
1705
|
+
return [];
|
|
1706
|
+
}
|
|
1707
|
+
// Re-stamp `providerExecuted` so the conversion layer forwards the
|
|
1708
|
+
// response even if the client omitted the flag on the response part.
|
|
1709
|
+
return [{ ...p, providerExecuted: true }];
|
|
1710
|
+
});
|
|
1711
|
+
if (filtered.length > 0) {
|
|
1712
|
+
cleanedMessages.push({ ...msg, content: filtered });
|
|
1713
|
+
}
|
|
1714
|
+
} else {
|
|
1715
|
+
cleanedMessages.push(msg);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// Add tool results as a new tool message
|
|
1720
|
+
if (toolResultContent.length > 0) {
|
|
1721
|
+
cleanedMessages.push({
|
|
1722
|
+
role: 'tool',
|
|
1723
|
+
content: toolResultContent,
|
|
1724
|
+
} as ModelMessage);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
prompt.messages = cleanedMessages;
|
|
1728
|
+
|
|
1729
|
+
// Write tool results and step boundaries to the stream so the UI
|
|
1730
|
+
// can transition approved/denied tool parts to the correct state
|
|
1731
|
+
// and properly separate them from the subsequent model step.
|
|
1732
|
+
if (options.writable && toolResultContent.length > 0) {
|
|
1733
|
+
const deniedResults = toolResultContent
|
|
1734
|
+
.filter(r => r.output.type === 'execution-denied')
|
|
1735
|
+
.map(r => ({ toolCallId: r.toolCallId }));
|
|
1736
|
+
await writeApprovalToolResults(
|
|
1737
|
+
options.writable,
|
|
1738
|
+
approvedRawResults,
|
|
1739
|
+
deniedResults,
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
const modelPrompt = await convertToLanguageModelPrompt({
|
|
1745
|
+
prompt,
|
|
1746
|
+
supportedUrls: {},
|
|
1747
|
+
download,
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
const effectiveAbortSignal = mergeAbortSignals(
|
|
1751
|
+
options.abortSignal ?? effectiveGenerationSettings.abortSignal,
|
|
1752
|
+
options.timeout,
|
|
1753
|
+
);
|
|
1754
|
+
|
|
1755
|
+
// Merge generation settings: constructor defaults < prepareCall < stream options
|
|
1756
|
+
const mergedGenerationSettings: GenerationSettings = {
|
|
1757
|
+
...effectiveGenerationSettings,
|
|
1758
|
+
...(options.maxOutputTokens !== undefined && {
|
|
1759
|
+
maxOutputTokens: options.maxOutputTokens,
|
|
1760
|
+
}),
|
|
1761
|
+
...(options.temperature !== undefined && {
|
|
1762
|
+
temperature: options.temperature,
|
|
1763
|
+
}),
|
|
1764
|
+
...(options.topP !== undefined && { topP: options.topP }),
|
|
1765
|
+
...(options.topK !== undefined && { topK: options.topK }),
|
|
1766
|
+
...(options.presencePenalty !== undefined && {
|
|
1767
|
+
presencePenalty: options.presencePenalty,
|
|
1768
|
+
}),
|
|
1769
|
+
...(options.frequencyPenalty !== undefined && {
|
|
1770
|
+
frequencyPenalty: options.frequencyPenalty,
|
|
1771
|
+
}),
|
|
1772
|
+
...(options.stopSequences !== undefined && {
|
|
1773
|
+
stopSequences: options.stopSequences,
|
|
1774
|
+
}),
|
|
1775
|
+
...(options.seed !== undefined && { seed: options.seed }),
|
|
1776
|
+
...(options.maxRetries !== undefined && {
|
|
1777
|
+
maxRetries: options.maxRetries,
|
|
1778
|
+
}),
|
|
1779
|
+
...(effectiveAbortSignal !== undefined && {
|
|
1780
|
+
abortSignal: effectiveAbortSignal,
|
|
1781
|
+
}),
|
|
1782
|
+
...(options.headers !== undefined && { headers: options.headers }),
|
|
1783
|
+
...(options.reasoning !== undefined && { reasoning: options.reasoning }),
|
|
1784
|
+
...(options.providerOptions !== undefined && {
|
|
1785
|
+
providerOptions: options.providerOptions,
|
|
1786
|
+
}),
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
// tag the outgoing request so usage can be attributed to WorkflowAgent.
|
|
1790
|
+
// chains with the `ai/<version>` and `ai-sdk/<provider>/<version>` suffixes
|
|
1791
|
+
// added downstream by the model run and the provider.
|
|
1792
|
+
mergedGenerationSettings.headers = withUserAgentSuffix(
|
|
1793
|
+
mergedGenerationSettings.headers ?? {},
|
|
1794
|
+
'ai-sdk-agent/workflow',
|
|
1795
|
+
);
|
|
1796
|
+
|
|
1797
|
+
// Merge constructor + stream callbacks (constructor first, then stream)
|
|
1798
|
+
const mergedOnStepEnd = mergeCallbacks(
|
|
1799
|
+
this.constructorOnStepEnd as
|
|
1800
|
+
| WorkflowAgentOnStepEndCallback<TTools, TRuntimeContext>
|
|
1801
|
+
| undefined,
|
|
1802
|
+
options.onStepEnd ?? options.onStepFinish,
|
|
1803
|
+
);
|
|
1804
|
+
const mergedOnEnd = mergeCallbacks(
|
|
1805
|
+
this.constructorOnEnd as
|
|
1806
|
+
| WorkflowAgentOnEndCallback<TTools, TRuntimeContext, OUTPUT>
|
|
1807
|
+
| undefined,
|
|
1808
|
+
onEnd,
|
|
1809
|
+
);
|
|
1810
|
+
const mergedOnStart = mergeCallbacks(
|
|
1811
|
+
this.constructorOnStart as
|
|
1812
|
+
| WorkflowAgentOnStartCallback<TTools, TRuntimeContext>
|
|
1813
|
+
| undefined,
|
|
1814
|
+
options.experimental_onStart,
|
|
1815
|
+
);
|
|
1816
|
+
const mergedOnStepStart = mergeCallbacks(
|
|
1817
|
+
this.constructorOnStepStart as
|
|
1818
|
+
| WorkflowAgentOnStepStartCallback<TTools, TRuntimeContext>
|
|
1819
|
+
| undefined,
|
|
1820
|
+
options.experimental_onStepStart,
|
|
1821
|
+
);
|
|
1822
|
+
const mergedOnToolExecutionStart = mergeCallbacks(
|
|
1823
|
+
this.constructorOnToolExecutionStart,
|
|
1824
|
+
options.onToolExecutionStart,
|
|
1825
|
+
);
|
|
1826
|
+
const mergedOnToolExecutionEnd = mergeCallbacks(
|
|
1827
|
+
this.constructorOnToolExecutionEnd,
|
|
1828
|
+
options.onToolExecutionEnd,
|
|
1829
|
+
);
|
|
1830
|
+
|
|
1831
|
+
// Determine effective tool choice
|
|
1832
|
+
const effectiveToolChoice = effectiveToolChoiceFromPrepare;
|
|
1833
|
+
|
|
1834
|
+
// Filter tools if activeTools is specified (stream-level overrides constructor default)
|
|
1835
|
+
const effectiveActiveTools = options.activeTools ?? this.activeTools;
|
|
1836
|
+
const effectiveTools =
|
|
1837
|
+
effectiveActiveTools && effectiveActiveTools.length > 0
|
|
1838
|
+
? (filterActiveTools({
|
|
1839
|
+
tools: this.tools,
|
|
1840
|
+
activeTools: effectiveActiveTools,
|
|
1841
|
+
}) ?? this.tools)
|
|
1842
|
+
: this.tools;
|
|
1843
|
+
const effectiveModelInfo = getModelInfo(effectiveModel);
|
|
1844
|
+
|
|
1845
|
+
// Initialize context
|
|
1846
|
+
let runtimeContext: TRuntimeContext = effectiveRuntimeContext;
|
|
1847
|
+
let toolsContext: Record<string, Context | undefined> =
|
|
1848
|
+
effectiveToolsContext;
|
|
1849
|
+
|
|
1850
|
+
const steps: StepResult<TTools, TRuntimeContext>[] = [];
|
|
1851
|
+
|
|
1852
|
+
// Track tool calls and results from the last step for the result
|
|
1853
|
+
let lastStepToolCalls: ToolCall[] = [];
|
|
1854
|
+
let lastStepToolResults: ToolResult[] = [];
|
|
1855
|
+
|
|
1856
|
+
// Call onStart before the agent loop
|
|
1857
|
+
if (mergedOnStart) {
|
|
1858
|
+
await mergedOnStart({
|
|
1859
|
+
model: effectiveModel,
|
|
1860
|
+
messages: prompt.messages,
|
|
1861
|
+
runtimeContext,
|
|
1862
|
+
toolsContext: toolsContext as unknown as InferToolSetContext<TTools>,
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
await telemetryDispatcher.onStart?.({
|
|
1866
|
+
callId: 'workflow-agent',
|
|
1867
|
+
operationId: 'ai.workflowAgent.stream',
|
|
1868
|
+
provider: effectiveModelInfo.provider,
|
|
1869
|
+
modelId: effectiveModelInfo.modelId,
|
|
1870
|
+
system: undefined,
|
|
1871
|
+
messages: prompt.messages,
|
|
1872
|
+
tools: effectiveTools,
|
|
1873
|
+
toolChoice: effectiveToolChoice,
|
|
1874
|
+
activeTools: effectiveActiveTools as never,
|
|
1875
|
+
maxOutputTokens: mergedGenerationSettings.maxOutputTokens,
|
|
1876
|
+
temperature: mergedGenerationSettings.temperature,
|
|
1877
|
+
topP: mergedGenerationSettings.topP,
|
|
1878
|
+
topK: mergedGenerationSettings.topK,
|
|
1879
|
+
presencePenalty: mergedGenerationSettings.presencePenalty,
|
|
1880
|
+
frequencyPenalty: mergedGenerationSettings.frequencyPenalty,
|
|
1881
|
+
stopSequences: mergedGenerationSettings.stopSequences,
|
|
1882
|
+
seed: mergedGenerationSettings.seed,
|
|
1883
|
+
maxRetries: mergedGenerationSettings.maxRetries ?? 2,
|
|
1884
|
+
timeout: undefined,
|
|
1885
|
+
headers: mergedGenerationSettings.headers,
|
|
1886
|
+
reasoning: mergedGenerationSettings.reasoning,
|
|
1887
|
+
providerOptions: mergedGenerationSettings.providerOptions,
|
|
1888
|
+
output: (options.output ?? this.output) as never,
|
|
1889
|
+
runtimeContext,
|
|
1890
|
+
toolsContext: toolsContext as unknown as InferToolSetContext<TTools>,
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1893
|
+
// Helper to wrap executeTool with onToolExecutionStart/onToolExecutionEnd callbacks
|
|
1894
|
+
const executeToolWithCallbacks = async (
|
|
1895
|
+
toolCall: { toolCallId: string; toolName: string; input: unknown },
|
|
1896
|
+
tools: ToolSet,
|
|
1897
|
+
messages: LanguageModelV4Prompt,
|
|
1898
|
+
perToolContexts: Record<string, Context | undefined>,
|
|
1899
|
+
currentStepNumber: number = 0,
|
|
1900
|
+
stepSandbox?: SandboxSession,
|
|
1901
|
+
): Promise<WorkflowToolExecutionResult> => {
|
|
1902
|
+
const toolCallEvent: ToolCall = {
|
|
1903
|
+
type: 'tool-call',
|
|
1904
|
+
toolCallId: toolCall.toolCallId,
|
|
1905
|
+
toolName: toolCall.toolName,
|
|
1906
|
+
input: toolCall.input,
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
const tool = tools[toolCall.toolName];
|
|
1910
|
+
const resolvedContext = tool
|
|
1911
|
+
? await resolveToolContext({
|
|
1912
|
+
toolName: toolCall.toolName,
|
|
1913
|
+
tool,
|
|
1914
|
+
toolsContext: perToolContexts,
|
|
1915
|
+
})
|
|
1916
|
+
: undefined;
|
|
1917
|
+
const modelMessages = getToolCallbackMessages(messages);
|
|
1918
|
+
|
|
1919
|
+
if (mergedOnToolExecutionStart) {
|
|
1920
|
+
await mergedOnToolExecutionStart({
|
|
1921
|
+
toolCall: toolCallEvent,
|
|
1922
|
+
stepNumber: currentStepNumber,
|
|
1923
|
+
messages: modelMessages,
|
|
1924
|
+
toolContext: resolvedContext as
|
|
1925
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
1926
|
+
| undefined,
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
await telemetryDispatcher.onToolExecutionStart?.({
|
|
1930
|
+
toolCall: toolCallEvent,
|
|
1931
|
+
stepNumber: currentStepNumber,
|
|
1932
|
+
messages: modelMessages,
|
|
1933
|
+
toolContext: resolvedContext as
|
|
1934
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
1935
|
+
| undefined,
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
const startTime = Date.now();
|
|
1939
|
+
let result: WorkflowToolExecutionResult;
|
|
1940
|
+
try {
|
|
1941
|
+
const execute = () =>
|
|
1942
|
+
executeTool(
|
|
1943
|
+
toolCall,
|
|
1944
|
+
tools,
|
|
1945
|
+
messages,
|
|
1946
|
+
resolvedContext,
|
|
1947
|
+
download,
|
|
1948
|
+
stepSandbox,
|
|
1949
|
+
);
|
|
1950
|
+
result =
|
|
1951
|
+
telemetryDispatcher.executeTool != null
|
|
1952
|
+
? await telemetryDispatcher.executeTool({
|
|
1953
|
+
callId: 'workflow-agent',
|
|
1954
|
+
toolCallId: toolCall.toolCallId,
|
|
1955
|
+
execute,
|
|
1956
|
+
})
|
|
1957
|
+
: await execute();
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
const durationMs = Date.now() - startTime;
|
|
1960
|
+
if (mergedOnToolExecutionEnd) {
|
|
1961
|
+
await mergedOnToolExecutionEnd({
|
|
1962
|
+
toolCall: toolCallEvent,
|
|
1963
|
+
stepNumber: currentStepNumber,
|
|
1964
|
+
durationMs,
|
|
1965
|
+
messages: modelMessages,
|
|
1966
|
+
toolContext: resolvedContext as
|
|
1967
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
1968
|
+
| undefined,
|
|
1969
|
+
success: false,
|
|
1970
|
+
error: err,
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
await telemetryDispatcher.onToolExecutionEnd?.({
|
|
1974
|
+
toolCall: toolCallEvent,
|
|
1975
|
+
stepNumber: currentStepNumber,
|
|
1976
|
+
durationMs,
|
|
1977
|
+
messages: modelMessages,
|
|
1978
|
+
toolContext: resolvedContext as
|
|
1979
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
1980
|
+
| undefined,
|
|
1981
|
+
success: false,
|
|
1982
|
+
error: err,
|
|
1983
|
+
});
|
|
1984
|
+
throw err;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
const durationMs = Date.now() - startTime;
|
|
1988
|
+
if (mergedOnToolExecutionEnd) {
|
|
1989
|
+
if (result.isError) {
|
|
1990
|
+
await mergedOnToolExecutionEnd({
|
|
1991
|
+
toolCall: toolCallEvent,
|
|
1992
|
+
stepNumber: currentStepNumber,
|
|
1993
|
+
durationMs,
|
|
1994
|
+
messages: modelMessages,
|
|
1995
|
+
toolContext: resolvedContext as
|
|
1996
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
1997
|
+
| undefined,
|
|
1998
|
+
success: false,
|
|
1999
|
+
error: result.rawOutput,
|
|
2000
|
+
});
|
|
2001
|
+
} else {
|
|
2002
|
+
await mergedOnToolExecutionEnd({
|
|
2003
|
+
toolCall: toolCallEvent,
|
|
2004
|
+
stepNumber: currentStepNumber,
|
|
2005
|
+
durationMs,
|
|
2006
|
+
messages: modelMessages,
|
|
2007
|
+
toolContext: resolvedContext as
|
|
2008
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
2009
|
+
| undefined,
|
|
2010
|
+
success: true,
|
|
2011
|
+
output: result.rawOutput,
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
if (result.isError) {
|
|
2016
|
+
await telemetryDispatcher.onToolExecutionEnd?.({
|
|
2017
|
+
toolCall: toolCallEvent,
|
|
2018
|
+
stepNumber: currentStepNumber,
|
|
2019
|
+
durationMs,
|
|
2020
|
+
messages: modelMessages,
|
|
2021
|
+
toolContext: resolvedContext as
|
|
2022
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
2023
|
+
| undefined,
|
|
2024
|
+
success: false,
|
|
2025
|
+
error: result.rawOutput,
|
|
2026
|
+
});
|
|
2027
|
+
} else {
|
|
2028
|
+
await telemetryDispatcher.onToolExecutionEnd?.({
|
|
2029
|
+
toolCall: toolCallEvent,
|
|
2030
|
+
stepNumber: currentStepNumber,
|
|
2031
|
+
durationMs,
|
|
2032
|
+
messages: modelMessages,
|
|
2033
|
+
toolContext: resolvedContext as
|
|
2034
|
+
| InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
|
|
2035
|
+
| undefined,
|
|
2036
|
+
success: true,
|
|
2037
|
+
output: result.rawOutput,
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
return result;
|
|
2041
|
+
};
|
|
2042
|
+
|
|
2043
|
+
const recordProviderExecutedToolTelemetry = async (
|
|
2044
|
+
toolCall: { toolCallId: string; toolName: string; input: unknown },
|
|
2045
|
+
result: WorkflowToolExecutionResult,
|
|
2046
|
+
messages: LanguageModelV4Prompt,
|
|
2047
|
+
currentStepNumber: number,
|
|
2048
|
+
) => {
|
|
2049
|
+
const toolCallEvent: ToolCall = {
|
|
2050
|
+
type: 'tool-call',
|
|
2051
|
+
toolCallId: toolCall.toolCallId,
|
|
2052
|
+
toolName: toolCall.toolName,
|
|
2053
|
+
input: toolCall.input,
|
|
2054
|
+
};
|
|
2055
|
+
const modelMessages = getToolCallbackMessages(messages);
|
|
2056
|
+
|
|
2057
|
+
await telemetryDispatcher.onToolExecutionStart?.({
|
|
2058
|
+
toolCall: toolCallEvent,
|
|
2059
|
+
stepNumber: currentStepNumber,
|
|
2060
|
+
messages: modelMessages,
|
|
2061
|
+
toolContext: undefined,
|
|
2062
|
+
});
|
|
2063
|
+
|
|
2064
|
+
await telemetryDispatcher.onToolExecutionEnd?.({
|
|
2065
|
+
toolCall: toolCallEvent,
|
|
2066
|
+
stepNumber: currentStepNumber,
|
|
2067
|
+
durationMs: 0,
|
|
2068
|
+
messages: modelMessages,
|
|
2069
|
+
toolContext: undefined,
|
|
2070
|
+
...(result.isError
|
|
2071
|
+
? {
|
|
2072
|
+
success: false as const,
|
|
2073
|
+
error: result.rawOutput,
|
|
2074
|
+
}
|
|
2075
|
+
: {
|
|
2076
|
+
success: true as const,
|
|
2077
|
+
output: result.rawOutput,
|
|
2078
|
+
}),
|
|
2079
|
+
});
|
|
2080
|
+
};
|
|
2081
|
+
|
|
2082
|
+
// Check for abort before starting
|
|
2083
|
+
if (mergedGenerationSettings.abortSignal?.aborted) {
|
|
2084
|
+
if (options.onAbort) {
|
|
2085
|
+
await options.onAbort({ steps });
|
|
2086
|
+
}
|
|
2087
|
+
return {
|
|
2088
|
+
messages: prompt.messages,
|
|
2089
|
+
steps,
|
|
2090
|
+
toolCalls: [],
|
|
2091
|
+
toolResults: [],
|
|
2092
|
+
finishReason: 'other',
|
|
2093
|
+
totalUsage: aggregateUsage(steps),
|
|
2094
|
+
output: undefined as OUTPUT,
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
const iterator = streamTextIterator({
|
|
2099
|
+
model: effectiveModel,
|
|
2100
|
+
tools: effectiveTools as ToolSet,
|
|
2101
|
+
writable: options.writable,
|
|
2102
|
+
prompt: modelPrompt,
|
|
2103
|
+
stopConditions: options.stopWhen ?? this.stopWhen,
|
|
2104
|
+
|
|
2105
|
+
onStepEnd: mergedOnStepEnd as any,
|
|
2106
|
+
onStepStart: mergedOnStepStart as any,
|
|
2107
|
+
onError: options.onError,
|
|
2108
|
+
prepareStep: (options.prepareStep ??
|
|
2109
|
+
(this.prepareStep as
|
|
2110
|
+
| PrepareStepCallback<ToolSet, TRuntimeContext>
|
|
2111
|
+
| undefined)) as any,
|
|
2112
|
+
generationSettings: mergedGenerationSettings,
|
|
2113
|
+
toolChoice: effectiveToolChoice as ToolChoice<ToolSet>,
|
|
2114
|
+
runtimeContext,
|
|
2115
|
+
toolsContext,
|
|
2116
|
+
telemetry: effectiveTelemetry,
|
|
2117
|
+
includeRawChunks: options.includeRawChunks ?? false,
|
|
2118
|
+
repairToolCall: (options.repairToolCall ??
|
|
2119
|
+
options.experimental_repairToolCall ??
|
|
2120
|
+
this.repairToolCall) as ToolCallRepairFunction<ToolSet> | undefined,
|
|
2121
|
+
responseFormat: await (options.output ?? this.output)?.responseFormat,
|
|
2122
|
+
experimental_sandbox: sandbox,
|
|
2123
|
+
});
|
|
2124
|
+
|
|
2125
|
+
// Track the final conversation messages from the iterator
|
|
2126
|
+
let finalMessages: LanguageModelV4Prompt | undefined;
|
|
2127
|
+
let encounteredError: unknown;
|
|
2128
|
+
let wasAborted = false;
|
|
2129
|
+
|
|
2130
|
+
try {
|
|
2131
|
+
let result = await iterator.next();
|
|
2132
|
+
while (!result.done) {
|
|
2133
|
+
// Check for abort during iteration
|
|
2134
|
+
if (mergedGenerationSettings.abortSignal?.aborted) {
|
|
2135
|
+
wasAborted = true;
|
|
2136
|
+
if (options.onAbort) {
|
|
2137
|
+
await options.onAbort({ steps });
|
|
2138
|
+
}
|
|
2139
|
+
break;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
const {
|
|
2143
|
+
toolCalls,
|
|
2144
|
+
messages: iterMessages,
|
|
2145
|
+
step,
|
|
2146
|
+
runtimeContext: yieldedRuntimeContext,
|
|
2147
|
+
toolsContext: yieldedToolsContext,
|
|
2148
|
+
experimental_sandbox: stepSandbox,
|
|
2149
|
+
providerExecutedToolResults,
|
|
2150
|
+
} = result.value;
|
|
2151
|
+
const toolExecutionSandbox = stepSandbox ?? sandbox;
|
|
2152
|
+
// Capture current step number before pushing (0-based)
|
|
2153
|
+
const currentStepNumber = steps.length;
|
|
2154
|
+
if (step) {
|
|
2155
|
+
steps.push(step as unknown as StepResult<TTools, TRuntimeContext>);
|
|
2156
|
+
}
|
|
2157
|
+
if (yieldedRuntimeContext !== undefined) {
|
|
2158
|
+
runtimeContext = yieldedRuntimeContext as TRuntimeContext;
|
|
2159
|
+
}
|
|
2160
|
+
if (yieldedToolsContext !== undefined) {
|
|
2161
|
+
toolsContext = yieldedToolsContext;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// Only execute tools if there are tool calls
|
|
2165
|
+
if (toolCalls.length > 0) {
|
|
2166
|
+
const invalidToolCalls = toolCalls.filter(tc => tc.invalid === true);
|
|
2167
|
+
const validToolCalls = toolCalls.filter(tc => tc.invalid !== true);
|
|
2168
|
+
|
|
2169
|
+
// Separate provider-executed tool calls from client-executed ones
|
|
2170
|
+
const nonProviderToolCalls = validToolCalls.filter(
|
|
2171
|
+
tc => !tc.providerExecuted,
|
|
2172
|
+
);
|
|
2173
|
+
const providerToolCalls = validToolCalls.filter(
|
|
2174
|
+
tc => tc.providerExecuted,
|
|
2175
|
+
);
|
|
2176
|
+
|
|
2177
|
+
// Check which tools need approval (can be async)
|
|
2178
|
+
const approvalNeeded = await Promise.all(
|
|
2179
|
+
nonProviderToolCalls.map(async tc => {
|
|
2180
|
+
const tool = (effectiveTools as ToolSet)[tc.toolName];
|
|
2181
|
+
if (!tool) return false;
|
|
2182
|
+
if (tool.needsApproval == null) return false;
|
|
2183
|
+
if (typeof tool.needsApproval === 'boolean')
|
|
2184
|
+
return tool.needsApproval;
|
|
2185
|
+
const resolvedContext = await resolveToolContext({
|
|
2186
|
+
toolName: tc.toolName,
|
|
2187
|
+
tool,
|
|
2188
|
+
toolsContext:
|
|
2189
|
+
toolsContext as unknown as InferToolSetContext<TTools>,
|
|
2190
|
+
});
|
|
2191
|
+
return tool.needsApproval(tc.input, {
|
|
2192
|
+
toolCallId: tc.toolCallId,
|
|
2193
|
+
messages: iterMessages as unknown as ModelMessage[],
|
|
2194
|
+
context: resolvedContext,
|
|
2195
|
+
});
|
|
2196
|
+
}),
|
|
2197
|
+
);
|
|
2198
|
+
|
|
2199
|
+
// Further split non-provider tool calls into:
|
|
2200
|
+
// - executable: has execute function and doesn't need approval
|
|
2201
|
+
// - paused: no execute function (client-side) OR needs approval
|
|
2202
|
+
// Note: missing tools (!tool) are left to executeTool which will throw.
|
|
2203
|
+
const executableToolCalls = nonProviderToolCalls.filter((tc, i) => {
|
|
2204
|
+
const tool = (effectiveTools as ToolSet)[tc.toolName];
|
|
2205
|
+
return (
|
|
2206
|
+
(!tool || typeof tool.execute === 'function') &&
|
|
2207
|
+
!approvalNeeded[i]
|
|
2208
|
+
);
|
|
2209
|
+
});
|
|
2210
|
+
const pausedToolCalls = nonProviderToolCalls.filter((tc, i) => {
|
|
2211
|
+
const tool = (effectiveTools as ToolSet)[tc.toolName];
|
|
2212
|
+
return (
|
|
2213
|
+
(tool && typeof tool.execute !== 'function') || approvalNeeded[i]
|
|
2214
|
+
);
|
|
2215
|
+
});
|
|
2216
|
+
|
|
2217
|
+
// If there are paused tool calls (client-side or needing approval),
|
|
2218
|
+
// stop the loop and return them.
|
|
2219
|
+
// This matches AI SDK behavior: tools without execute or needing
|
|
2220
|
+
// approval pause the agent loop.
|
|
2221
|
+
if (pausedToolCalls.length > 0) {
|
|
2222
|
+
// Execute any executable tools that were also called in this step
|
|
2223
|
+
const executableResults = await Promise.all(
|
|
2224
|
+
executableToolCalls.map(
|
|
2225
|
+
(toolCall): Promise<WorkflowToolExecutionResult> =>
|
|
2226
|
+
executeToolWithCallbacks(
|
|
2227
|
+
toolCall,
|
|
2228
|
+
effectiveTools as ToolSet,
|
|
2229
|
+
iterMessages,
|
|
2230
|
+
toolsContext,
|
|
2231
|
+
currentStepNumber,
|
|
2232
|
+
toolExecutionSandbox,
|
|
2233
|
+
),
|
|
2234
|
+
),
|
|
2235
|
+
);
|
|
2236
|
+
|
|
2237
|
+
// Collect provider tool results
|
|
2238
|
+
const providerResults: WorkflowToolExecutionResult[] =
|
|
2239
|
+
await Promise.all(
|
|
2240
|
+
providerToolCalls.map(toolCall =>
|
|
2241
|
+
resolveProviderToolResult(
|
|
2242
|
+
toolCall,
|
|
2243
|
+
providerExecutedToolResults,
|
|
2244
|
+
effectiveTools as ToolSet,
|
|
2245
|
+
download,
|
|
2246
|
+
),
|
|
2247
|
+
),
|
|
2248
|
+
);
|
|
2249
|
+
await Promise.all(
|
|
2250
|
+
providerToolCalls.map((toolCall, index) =>
|
|
2251
|
+
recordProviderExecutedToolTelemetry(
|
|
2252
|
+
toolCall,
|
|
2253
|
+
providerResults[index],
|
|
2254
|
+
iterMessages,
|
|
2255
|
+
currentStepNumber,
|
|
2256
|
+
),
|
|
2257
|
+
),
|
|
2258
|
+
);
|
|
2259
|
+
|
|
2260
|
+
const continuationInvalidResults = invalidToolCalls.map(
|
|
2261
|
+
createInvalidToolResult,
|
|
2262
|
+
);
|
|
2263
|
+
const resolvedResults: LanguageModelV4ToolResultPart[] = [
|
|
2264
|
+
...executableResults.map(result => result.modelResult),
|
|
2265
|
+
...providerResults.map(result => result.modelResult),
|
|
2266
|
+
...continuationInvalidResults,
|
|
2267
|
+
];
|
|
2268
|
+
const executedResults = [...executableResults, ...providerResults];
|
|
2269
|
+
|
|
2270
|
+
const allToolCalls: ToolCall[] = toolCalls.map(tc => ({
|
|
2271
|
+
type: 'tool-call' as const,
|
|
2272
|
+
toolCallId: tc.toolCallId,
|
|
2273
|
+
toolName: tc.toolName,
|
|
2274
|
+
input: tc.input,
|
|
2275
|
+
}));
|
|
2276
|
+
|
|
2277
|
+
const allToolResults: ToolResult[] = executedResults.map(r => ({
|
|
2278
|
+
type: 'tool-result' as const,
|
|
2279
|
+
toolCallId: r.modelResult.toolCallId,
|
|
2280
|
+
toolName: r.modelResult.toolName,
|
|
2281
|
+
input: toolCalls.find(
|
|
2282
|
+
tc => tc.toolCallId === r.modelResult.toolCallId,
|
|
2283
|
+
)?.input,
|
|
2284
|
+
output: r.rawOutput,
|
|
2285
|
+
}));
|
|
2286
|
+
|
|
2287
|
+
if (resolvedResults.length > 0) {
|
|
2288
|
+
iterMessages.push({
|
|
2289
|
+
role: 'tool',
|
|
2290
|
+
content: resolvedResults,
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
const messages = iterMessages as unknown as ModelMessage[];
|
|
2295
|
+
const lastStep = steps[steps.length - 1];
|
|
2296
|
+
const totalUsage = aggregateUsage(steps);
|
|
2297
|
+
const finishReason = lastStep?.finishReason ?? 'other';
|
|
2298
|
+
|
|
2299
|
+
if (mergedOnEnd && !wasAborted) {
|
|
2300
|
+
await mergedOnEnd({
|
|
2301
|
+
steps,
|
|
2302
|
+
messages,
|
|
2303
|
+
text: lastStep?.text ?? '',
|
|
2304
|
+
finishReason,
|
|
2305
|
+
usage: totalUsage,
|
|
2306
|
+
totalUsage,
|
|
2307
|
+
runtimeContext,
|
|
2308
|
+
toolsContext:
|
|
2309
|
+
toolsContext as unknown as InferToolSetContext<TTools>,
|
|
2310
|
+
output: undefined as OUTPUT,
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
if (!wasAborted && steps.length > 0) {
|
|
2314
|
+
const telemetrySteps = steps.map(normalizeStepForTelemetry);
|
|
2315
|
+
const lastTelemetryStep =
|
|
2316
|
+
telemetrySteps[telemetrySteps.length - 1];
|
|
2317
|
+
await telemetryDispatcher.onEnd?.({
|
|
2318
|
+
...lastTelemetryStep,
|
|
2319
|
+
steps: telemetrySteps,
|
|
2320
|
+
usage: totalUsage,
|
|
2321
|
+
totalUsage,
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
// Emit tool-approval-request chunks for tools that need approval
|
|
2326
|
+
// so useChat can show the approval UI
|
|
2327
|
+
if (options.writable) {
|
|
2328
|
+
const approvalToolCalls = pausedToolCalls.filter((_, i) => {
|
|
2329
|
+
const tcIndex = nonProviderToolCalls.indexOf(
|
|
2330
|
+
pausedToolCalls[i],
|
|
2331
|
+
);
|
|
2332
|
+
return approvalNeeded[tcIndex];
|
|
2333
|
+
});
|
|
2334
|
+
if (approvalToolCalls.length > 0) {
|
|
2335
|
+
await writeApprovalRequests(
|
|
2336
|
+
options.writable,
|
|
2337
|
+
approvalToolCalls.map(tc => ({
|
|
2338
|
+
toolCallId: tc.toolCallId,
|
|
2339
|
+
toolName: tc.toolName,
|
|
2340
|
+
})),
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// Close the stream before returning for paused tools
|
|
2346
|
+
if (options.writable) {
|
|
2347
|
+
const sendFinish = options.sendFinish ?? true;
|
|
2348
|
+
const preventClose = options.preventClose ?? false;
|
|
2349
|
+
if (sendFinish || !preventClose) {
|
|
2350
|
+
await closeStream(options.writable, preventClose, sendFinish);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
return {
|
|
2355
|
+
messages,
|
|
2356
|
+
steps,
|
|
2357
|
+
toolCalls: allToolCalls,
|
|
2358
|
+
toolResults: allToolResults,
|
|
2359
|
+
finishReason,
|
|
2360
|
+
totalUsage,
|
|
2361
|
+
output: undefined as OUTPUT,
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// Execute client tools (all have execute functions at this point)
|
|
2366
|
+
const clientToolResults = await Promise.all(
|
|
2367
|
+
nonProviderToolCalls.map(
|
|
2368
|
+
(toolCall): Promise<WorkflowToolExecutionResult> =>
|
|
2369
|
+
executeToolWithCallbacks(
|
|
2370
|
+
toolCall,
|
|
2371
|
+
effectiveTools as ToolSet,
|
|
2372
|
+
iterMessages,
|
|
2373
|
+
toolsContext,
|
|
2374
|
+
currentStepNumber,
|
|
2375
|
+
toolExecutionSandbox,
|
|
2376
|
+
),
|
|
2377
|
+
),
|
|
2378
|
+
);
|
|
2379
|
+
|
|
2380
|
+
// For provider-executed tools, use the results from the stream
|
|
2381
|
+
const providerToolResults: WorkflowToolExecutionResult[] =
|
|
2382
|
+
await Promise.all(
|
|
2383
|
+
providerToolCalls.map(toolCall =>
|
|
2384
|
+
resolveProviderToolResult(
|
|
2385
|
+
toolCall,
|
|
2386
|
+
providerExecutedToolResults,
|
|
2387
|
+
effectiveTools as ToolSet,
|
|
2388
|
+
download,
|
|
2389
|
+
),
|
|
2390
|
+
),
|
|
2391
|
+
);
|
|
2392
|
+
await Promise.all(
|
|
2393
|
+
providerToolCalls.map((toolCall, index) =>
|
|
2394
|
+
recordProviderExecutedToolTelemetry(
|
|
2395
|
+
toolCall,
|
|
2396
|
+
providerToolResults[index],
|
|
2397
|
+
iterMessages,
|
|
2398
|
+
currentStepNumber,
|
|
2399
|
+
),
|
|
2400
|
+
),
|
|
2401
|
+
);
|
|
2402
|
+
const continuationInvalidToolResults = invalidToolCalls.map(
|
|
2403
|
+
createInvalidToolResult,
|
|
2404
|
+
);
|
|
2405
|
+
|
|
2406
|
+
// Combine executable/provider results in the original order,
|
|
2407
|
+
// while preserving invalid tool calls as error results for the
|
|
2408
|
+
// next model step without emitting them as synthetic UI success.
|
|
2409
|
+
const executedToolResults = toolCalls.flatMap(tc => {
|
|
2410
|
+
const clientResult = clientToolResults.find(
|
|
2411
|
+
r => r.modelResult.toolCallId === tc.toolCallId,
|
|
2412
|
+
);
|
|
2413
|
+
if (clientResult) return [clientResult];
|
|
2414
|
+
const providerResult = providerToolResults.find(
|
|
2415
|
+
r => r.modelResult.toolCallId === tc.toolCallId,
|
|
2416
|
+
);
|
|
2417
|
+
if (providerResult) return [providerResult];
|
|
2418
|
+
return [];
|
|
2419
|
+
});
|
|
2420
|
+
const continuationToolResults = toolCalls.flatMap(tc => {
|
|
2421
|
+
const invalidResult = continuationInvalidToolResults.find(
|
|
2422
|
+
r => r.toolCallId === tc.toolCallId,
|
|
2423
|
+
);
|
|
2424
|
+
if (invalidResult) return [invalidResult];
|
|
2425
|
+
const executedResult = executedToolResults.find(
|
|
2426
|
+
r => r.modelResult.toolCallId === tc.toolCallId,
|
|
2427
|
+
);
|
|
2428
|
+
if (executedResult) return [executedResult.modelResult];
|
|
2429
|
+
return [];
|
|
2430
|
+
});
|
|
2431
|
+
|
|
2432
|
+
// Write tool results and step boundaries to the stream so the
|
|
2433
|
+
// UI can transition tool parts to output-available state and
|
|
2434
|
+
// properly separate multi-step model calls in the message history.
|
|
2435
|
+
if (options.writable) {
|
|
2436
|
+
await writeToolResultsWithStepBoundary(
|
|
2437
|
+
options.writable,
|
|
2438
|
+
executedToolResults.map(r => ({
|
|
2439
|
+
toolCallId: r.modelResult.toolCallId,
|
|
2440
|
+
toolName: r.modelResult.toolName,
|
|
2441
|
+
input: toolCalls.find(
|
|
2442
|
+
tc => tc.toolCallId === r.modelResult.toolCallId,
|
|
2443
|
+
)?.input,
|
|
2444
|
+
output: r.rawOutput,
|
|
2445
|
+
})),
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// Track the tool calls and results for this step
|
|
2450
|
+
lastStepToolCalls = toolCalls.map(tc => ({
|
|
2451
|
+
type: 'tool-call' as const,
|
|
2452
|
+
toolCallId: tc.toolCallId,
|
|
2453
|
+
toolName: tc.toolName,
|
|
2454
|
+
input: tc.input,
|
|
2455
|
+
}));
|
|
2456
|
+
lastStepToolResults = executedToolResults.map(r => ({
|
|
2457
|
+
type: 'tool-result' as const,
|
|
2458
|
+
toolCallId: r.modelResult.toolCallId,
|
|
2459
|
+
toolName: r.modelResult.toolName,
|
|
2460
|
+
input: toolCalls.find(
|
|
2461
|
+
tc => tc.toolCallId === r.modelResult.toolCallId,
|
|
2462
|
+
)?.input,
|
|
2463
|
+
output: r.rawOutput,
|
|
2464
|
+
}));
|
|
2465
|
+
|
|
2466
|
+
result = await iterator.next(continuationToolResults);
|
|
2467
|
+
} else {
|
|
2468
|
+
// Final step with no tool calls - reset tracking
|
|
2469
|
+
lastStepToolCalls = [];
|
|
2470
|
+
lastStepToolResults = [];
|
|
2471
|
+
result = await iterator.next([]);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
// When the iterator completes normally, result.value contains the final conversation prompt
|
|
2476
|
+
if (result.done) {
|
|
2477
|
+
finalMessages = result.value;
|
|
2478
|
+
}
|
|
2479
|
+
} catch (error) {
|
|
2480
|
+
encounteredError = error;
|
|
2481
|
+
// Check if this is an abort error
|
|
2482
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
2483
|
+
wasAborted = true;
|
|
2484
|
+
if (options.onAbort) {
|
|
2485
|
+
await options.onAbort({ steps });
|
|
2486
|
+
}
|
|
2487
|
+
} else if (options.onError) {
|
|
2488
|
+
// Call onError for non-abort errors (including tool execution errors)
|
|
2489
|
+
await options.onError({ error });
|
|
2490
|
+
}
|
|
2491
|
+
await telemetryDispatcher.onError?.(error);
|
|
2492
|
+
// Don't throw yet - we want to call onEnd first
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// Use the final messages from the iterator, or fall back to standardized messages
|
|
2496
|
+
const messages = (finalMessages ??
|
|
2497
|
+
prompt.messages) as unknown as ModelMessage[];
|
|
2498
|
+
|
|
2499
|
+
// Parse structured output if output is specified (stream-level overrides constructor default)
|
|
2500
|
+
const effectiveOutput = options.output ?? this.output;
|
|
2501
|
+
let experimentalOutput: OUTPUT = undefined as OUTPUT;
|
|
2502
|
+
if (effectiveOutput && steps.length > 0) {
|
|
2503
|
+
const lastStep = steps[steps.length - 1];
|
|
2504
|
+
const text = lastStep.text;
|
|
2505
|
+
if (text) {
|
|
2506
|
+
try {
|
|
2507
|
+
experimentalOutput = await effectiveOutput.parseCompleteOutput(
|
|
2508
|
+
{ text },
|
|
2509
|
+
{
|
|
2510
|
+
response: lastStep.response,
|
|
2511
|
+
usage: lastStep.usage,
|
|
2512
|
+
finishReason: lastStep.finishReason,
|
|
2513
|
+
},
|
|
2514
|
+
);
|
|
2515
|
+
} catch (parseError) {
|
|
2516
|
+
// If there's already an error, don't override it
|
|
2517
|
+
// If not, set this as the error
|
|
2518
|
+
if (!encounteredError) {
|
|
2519
|
+
encounteredError = parseError;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
const lastStep = steps[steps.length - 1];
|
|
2526
|
+
const totalUsage = aggregateUsage(steps);
|
|
2527
|
+
const finishReason = lastStep?.finishReason ?? 'other';
|
|
2528
|
+
|
|
2529
|
+
// Call onEnd callback if provided (always call, even on errors, but not on abort)
|
|
2530
|
+
if (mergedOnEnd && !wasAborted) {
|
|
2531
|
+
await mergedOnEnd({
|
|
2532
|
+
steps,
|
|
2533
|
+
messages: messages as ModelMessage[],
|
|
2534
|
+
text: lastStep?.text ?? '',
|
|
2535
|
+
finishReason,
|
|
2536
|
+
usage: totalUsage,
|
|
2537
|
+
totalUsage,
|
|
2538
|
+
runtimeContext,
|
|
2539
|
+
toolsContext: toolsContext as unknown as InferToolSetContext<TTools>,
|
|
2540
|
+
output: experimentalOutput,
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
if (!wasAborted && steps.length > 0) {
|
|
2544
|
+
const telemetrySteps = steps.map(normalizeStepForTelemetry);
|
|
2545
|
+
const lastTelemetryStep = telemetrySteps[telemetrySteps.length - 1];
|
|
2546
|
+
await telemetryDispatcher.onEnd?.({
|
|
2547
|
+
...lastTelemetryStep,
|
|
2548
|
+
steps: telemetrySteps,
|
|
2549
|
+
usage: totalUsage,
|
|
2550
|
+
totalUsage,
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// Re-throw any error that occurred
|
|
2555
|
+
if (encounteredError) {
|
|
2556
|
+
// Close the stream before throwing
|
|
2557
|
+
if (options.writable) {
|
|
2558
|
+
const sendFinish = options.sendFinish ?? true;
|
|
2559
|
+
const preventClose = options.preventClose ?? false;
|
|
2560
|
+
if (sendFinish || !preventClose) {
|
|
2561
|
+
await closeStream(options.writable, preventClose, sendFinish);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
throw encounteredError;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
// Close the writable stream
|
|
2568
|
+
if (options.writable) {
|
|
2569
|
+
const sendFinish = options.sendFinish ?? true;
|
|
2570
|
+
const preventClose = options.preventClose ?? false;
|
|
2571
|
+
if (sendFinish || !preventClose) {
|
|
2572
|
+
await closeStream(options.writable, preventClose, sendFinish);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
return {
|
|
2577
|
+
messages: messages as ModelMessage[],
|
|
2578
|
+
steps,
|
|
2579
|
+
toolCalls: lastStepToolCalls,
|
|
2580
|
+
toolResults: lastStepToolResults,
|
|
2581
|
+
finishReason,
|
|
2582
|
+
totalUsage,
|
|
2583
|
+
output: experimentalOutput,
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
function getModelInfo(model: LanguageModel): {
|
|
2589
|
+
provider: string;
|
|
2590
|
+
modelId: string;
|
|
2591
|
+
} {
|
|
2592
|
+
return typeof model === 'string'
|
|
2593
|
+
? { provider: model.split('/')[0] ?? 'gateway', modelId: model }
|
|
2594
|
+
: { provider: model.provider, modelId: model.modelId };
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
function normalizeStepForTelemetry<
|
|
2598
|
+
TOOLS extends ToolSet,
|
|
2599
|
+
RUNTIME_CONTEXT extends Context,
|
|
2600
|
+
>(step: StepResult<TOOLS, RUNTIME_CONTEXT>) {
|
|
2601
|
+
return {
|
|
2602
|
+
...step,
|
|
2603
|
+
model: step.model ?? { provider: 'unknown', modelId: 'unknown' },
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
/**
|
|
2608
|
+
* Filter tools to only include the specified active tools.
|
|
2609
|
+
*/
|
|
2610
|
+
/**
|
|
2611
|
+
* Aggregate token usage across all steps.
|
|
2612
|
+
*/
|
|
2613
|
+
/**
|
|
2614
|
+
* Close the writable stream, optionally sending a finish chunk first.
|
|
2615
|
+
* This is a step function because writable.getWriter() and writable.close()
|
|
2616
|
+
* cannot be called in workflow context (sandbox limitation).
|
|
2617
|
+
*/
|
|
2618
|
+
async function closeStream(
|
|
2619
|
+
writable: WritableStream<any>,
|
|
2620
|
+
preventClose?: boolean,
|
|
2621
|
+
sendFinish?: boolean,
|
|
2622
|
+
) {
|
|
2623
|
+
'use step';
|
|
2624
|
+
if (sendFinish) {
|
|
2625
|
+
const writer = writable.getWriter();
|
|
2626
|
+
try {
|
|
2627
|
+
await writer.write({ type: 'finish' });
|
|
2628
|
+
} finally {
|
|
2629
|
+
writer.releaseLock();
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
if (!preventClose) {
|
|
2633
|
+
await writable.close();
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
/**
|
|
2638
|
+
* Write tool-approval-request chunks to the writable stream.
|
|
2639
|
+
* These are consumed by useChat to show the approval UI.
|
|
2640
|
+
*/
|
|
2641
|
+
async function writeApprovalRequests(
|
|
2642
|
+
writable: WritableStream<any>,
|
|
2643
|
+
toolCalls: Array<{ toolCallId: string; toolName: string }>,
|
|
2644
|
+
) {
|
|
2645
|
+
'use step';
|
|
2646
|
+
const writer = writable.getWriter();
|
|
2647
|
+
try {
|
|
2648
|
+
for (const tc of toolCalls) {
|
|
2649
|
+
await writer.write({
|
|
2650
|
+
type: 'tool-approval-request',
|
|
2651
|
+
approvalId: `approval-${tc.toolCallId}`,
|
|
2652
|
+
toolCallId: tc.toolCallId,
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
} finally {
|
|
2656
|
+
writer.releaseLock();
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
async function writeToolResultsWithStepBoundary(
|
|
2661
|
+
writable: WritableStream<any>,
|
|
2662
|
+
results: Array<{
|
|
2663
|
+
toolCallId: string;
|
|
2664
|
+
toolName: string;
|
|
2665
|
+
input: unknown;
|
|
2666
|
+
output: unknown;
|
|
2667
|
+
}>,
|
|
2668
|
+
) {
|
|
2669
|
+
'use step';
|
|
2670
|
+
const writer = writable.getWriter();
|
|
2671
|
+
try {
|
|
2672
|
+
for (const r of results) {
|
|
2673
|
+
await writer.write({
|
|
2674
|
+
type: 'tool-result',
|
|
2675
|
+
toolCallId: r.toolCallId,
|
|
2676
|
+
toolName: r.toolName,
|
|
2677
|
+
input: r.input,
|
|
2678
|
+
output: r.output,
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
// Emit step boundaries so the UI message history properly separates
|
|
2682
|
+
// the tool call step from the subsequent text step. This ensures
|
|
2683
|
+
// convertToModelMessages creates separate assistant messages for
|
|
2684
|
+
// tool calls and text responses.
|
|
2685
|
+
await writer.write({ type: 'finish-step' });
|
|
2686
|
+
await writer.write({ type: 'start-step' });
|
|
2687
|
+
} finally {
|
|
2688
|
+
writer.releaseLock();
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
async function writeApprovalToolResults(
|
|
2693
|
+
writable: WritableStream<any>,
|
|
2694
|
+
approvedResults: Array<{
|
|
2695
|
+
toolCallId: string;
|
|
2696
|
+
toolName: string;
|
|
2697
|
+
input: unknown;
|
|
2698
|
+
output: unknown;
|
|
2699
|
+
}>,
|
|
2700
|
+
deniedResults: Array<{ toolCallId: string }>,
|
|
2701
|
+
) {
|
|
2702
|
+
'use step';
|
|
2703
|
+
const writer = writable.getWriter();
|
|
2704
|
+
try {
|
|
2705
|
+
for (const r of approvedResults) {
|
|
2706
|
+
await writer.write({
|
|
2707
|
+
type: 'tool-result',
|
|
2708
|
+
toolCallId: r.toolCallId,
|
|
2709
|
+
toolName: r.toolName,
|
|
2710
|
+
input: r.input,
|
|
2711
|
+
output: r.output,
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
for (const r of deniedResults) {
|
|
2715
|
+
await writer.write({
|
|
2716
|
+
type: 'tool-output-denied',
|
|
2717
|
+
toolCallId: r.toolCallId,
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
await writer.write({ type: 'finish-step' });
|
|
2721
|
+
await writer.write({ type: 'start-step' });
|
|
2722
|
+
} finally {
|
|
2723
|
+
writer.releaseLock();
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
/**
|
|
2728
|
+
* Resolve the per-tool context that gets passed into a tool's `execute`
|
|
2729
|
+
* (and `needsApproval`) function. When the tool declares a `contextSchema`,
|
|
2730
|
+
* the entry is validated against it.
|
|
2731
|
+
*/
|
|
2732
|
+
async function resolveToolContext({
|
|
2733
|
+
toolName,
|
|
2734
|
+
tool,
|
|
2735
|
+
toolsContext,
|
|
2736
|
+
}: {
|
|
2737
|
+
toolName: string;
|
|
2738
|
+
tool: ToolSet[string];
|
|
2739
|
+
toolsContext: Record<string, Context | undefined> | undefined;
|
|
2740
|
+
}): Promise<unknown> {
|
|
2741
|
+
const contextSchema = (tool as { contextSchema?: unknown }).contextSchema;
|
|
2742
|
+
const entry = toolsContext?.[toolName];
|
|
2743
|
+
if (contextSchema == null) {
|
|
2744
|
+
return entry;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
return await validateTypes({
|
|
2748
|
+
value: entry,
|
|
2749
|
+
schema: contextSchema as Parameters<typeof validateTypes>[0]['schema'],
|
|
2750
|
+
context: { field: 'tool context', entityName: toolName },
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
function aggregateUsage(steps: StepResult<any, any>[]): LanguageModelUsage {
|
|
2755
|
+
let inputTokens = 0;
|
|
2756
|
+
let outputTokens = 0;
|
|
2757
|
+
for (const step of steps) {
|
|
2758
|
+
inputTokens += step.usage?.inputTokens ?? 0;
|
|
2759
|
+
outputTokens += step.usage?.outputTokens ?? 0;
|
|
2760
|
+
}
|
|
2761
|
+
return {
|
|
2762
|
+
inputTokens,
|
|
2763
|
+
outputTokens,
|
|
2764
|
+
totalTokens: inputTokens + outputTokens,
|
|
2765
|
+
} as LanguageModelUsage;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
async function resolveProviderToolResult(
|
|
2769
|
+
toolCall: { toolCallId: string; toolName: string; input: unknown },
|
|
2770
|
+
providerExecutedToolResults?: Map<
|
|
2771
|
+
string,
|
|
2772
|
+
{ toolCallId: string; toolName: string; result: unknown; isError?: boolean }
|
|
2773
|
+
>,
|
|
2774
|
+
tools?: ToolSet,
|
|
2775
|
+
download?: DownloadFunction,
|
|
2776
|
+
): Promise<WorkflowToolExecutionResult> {
|
|
2777
|
+
const streamResult = providerExecutedToolResults?.get(toolCall.toolCallId);
|
|
2778
|
+
if (!streamResult) {
|
|
2779
|
+
console.warn(
|
|
2780
|
+
`[WorkflowAgent] Provider-executed tool "${toolCall.toolName}" (${toolCall.toolCallId}) ` +
|
|
2781
|
+
`did not receive a result from the stream. This may indicate a provider issue.`,
|
|
2782
|
+
);
|
|
2783
|
+
return {
|
|
2784
|
+
modelResult: {
|
|
2785
|
+
type: 'tool-result' as const,
|
|
2786
|
+
toolCallId: toolCall.toolCallId,
|
|
2787
|
+
toolName: toolCall.toolName,
|
|
2788
|
+
output: {
|
|
2789
|
+
type: 'text' as const,
|
|
2790
|
+
value: '',
|
|
2791
|
+
},
|
|
2792
|
+
},
|
|
2793
|
+
rawOutput: '',
|
|
2794
|
+
isError: false,
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
const result = streamResult.result;
|
|
2799
|
+
const errorMode = streamResult.isError
|
|
2800
|
+
? typeof result === 'string'
|
|
2801
|
+
? 'text'
|
|
2802
|
+
: 'json'
|
|
2803
|
+
: 'none';
|
|
2804
|
+
|
|
2805
|
+
return {
|
|
2806
|
+
modelResult: {
|
|
2807
|
+
type: 'tool-result' as const,
|
|
2808
|
+
toolCallId: toolCall.toolCallId,
|
|
2809
|
+
toolName: toolCall.toolName,
|
|
2810
|
+
output: await createLanguageModelToolResultOutput({
|
|
2811
|
+
toolCallId: toolCall.toolCallId,
|
|
2812
|
+
toolName: toolCall.toolName,
|
|
2813
|
+
input: toolCall.input,
|
|
2814
|
+
output: result,
|
|
2815
|
+
tool: tools?.[toolCall.toolName],
|
|
2816
|
+
errorMode,
|
|
2817
|
+
supportedUrls: {},
|
|
2818
|
+
download,
|
|
2819
|
+
}),
|
|
2820
|
+
},
|
|
2821
|
+
rawOutput: result,
|
|
2822
|
+
isError: streamResult.isError === true,
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
function createInvalidToolResult(toolCall: {
|
|
2827
|
+
toolCallId: string;
|
|
2828
|
+
toolName: string;
|
|
2829
|
+
error?: unknown;
|
|
2830
|
+
}): LanguageModelV4ToolResultPart {
|
|
2831
|
+
return {
|
|
2832
|
+
type: 'tool-result' as const,
|
|
2833
|
+
toolCallId: toolCall.toolCallId,
|
|
2834
|
+
toolName: toolCall.toolName,
|
|
2835
|
+
output: {
|
|
2836
|
+
type: 'error-text' as const,
|
|
2837
|
+
value: getErrorMessage(toolCall.error),
|
|
2838
|
+
},
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
function getToolCallbackMessages(
|
|
2843
|
+
messages: LanguageModelV4Prompt,
|
|
2844
|
+
): ModelMessage[] {
|
|
2845
|
+
const withoutAssistantToolCall =
|
|
2846
|
+
messages.at(-1)?.role === 'assistant' ? messages.slice(0, -1) : messages;
|
|
2847
|
+
return withoutAssistantToolCall as unknown as ModelMessage[];
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
async function executeTool(
|
|
2851
|
+
toolCall: { toolCallId: string; toolName: string; input: unknown },
|
|
2852
|
+
tools: ToolSet,
|
|
2853
|
+
messages: LanguageModelV4Prompt,
|
|
2854
|
+
context?: unknown,
|
|
2855
|
+
download?: DownloadFunction,
|
|
2856
|
+
sandbox?: SandboxSession,
|
|
2857
|
+
): Promise<WorkflowToolExecutionResult> {
|
|
2858
|
+
const tool = tools[toolCall.toolName];
|
|
2859
|
+
if (!tool) throw new Error(`Tool "${toolCall.toolName}" not found`);
|
|
2860
|
+
if (typeof tool.execute !== 'function') {
|
|
2861
|
+
throw new Error(
|
|
2862
|
+
`Tool "${toolCall.toolName}" does not have an execute function. ` +
|
|
2863
|
+
`Client-side tools should be filtered before calling executeTool.`,
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
// Input is already parsed and validated by streamModelCall's parseToolCall
|
|
2867
|
+
const parsedInput = toolCall.input;
|
|
2868
|
+
let toolResult: unknown;
|
|
2869
|
+
|
|
2870
|
+
try {
|
|
2871
|
+
// Extract execute function to avoid binding `this` to the tool object.
|
|
2872
|
+
// If we called `tool.execute(...)` directly, JavaScript would bind `this`
|
|
2873
|
+
// to `tool`, which contains non-serializable properties like `inputSchema`.
|
|
2874
|
+
// When the execute function is a workflow step (marked with 'use step'),
|
|
2875
|
+
// the step system captures `this` for serialization, causing failures.
|
|
2876
|
+
const { execute } = tool;
|
|
2877
|
+
toolResult = await execute(parsedInput, {
|
|
2878
|
+
toolCallId: toolCall.toolCallId,
|
|
2879
|
+
// Pass the conversation messages to the tool so it has context about the conversation
|
|
2880
|
+
messages,
|
|
2881
|
+
// Pass per-tool context to the tool (resolved from `toolsContext`)
|
|
2882
|
+
context,
|
|
2883
|
+
experimental_sandbox: sandbox,
|
|
2884
|
+
});
|
|
2885
|
+
} catch (error) {
|
|
2886
|
+
// Convert tool errors to error-text results sent back to the model,
|
|
2887
|
+
// allowing the agent to recover rather than killing the entire stream.
|
|
2888
|
+
// This aligns with AI SDK's streamText behavior for individual tool failures.
|
|
2889
|
+
const errorMessage = getErrorMessage(error);
|
|
2890
|
+
return {
|
|
2891
|
+
modelResult: {
|
|
2892
|
+
type: 'tool-result',
|
|
2893
|
+
toolCallId: toolCall.toolCallId,
|
|
2894
|
+
toolName: toolCall.toolName,
|
|
2895
|
+
output: await createLanguageModelToolResultOutput({
|
|
2896
|
+
toolCallId: toolCall.toolCallId,
|
|
2897
|
+
toolName: toolCall.toolName,
|
|
2898
|
+
input: parsedInput,
|
|
2899
|
+
output: errorMessage,
|
|
2900
|
+
tool,
|
|
2901
|
+
errorMode: 'text',
|
|
2902
|
+
supportedUrls: {},
|
|
2903
|
+
download,
|
|
2904
|
+
}),
|
|
2905
|
+
},
|
|
2906
|
+
rawOutput: errorMessage,
|
|
2907
|
+
isError: true,
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
return {
|
|
2912
|
+
modelResult: {
|
|
2913
|
+
type: 'tool-result' as const,
|
|
2914
|
+
toolCallId: toolCall.toolCallId,
|
|
2915
|
+
toolName: toolCall.toolName,
|
|
2916
|
+
output: await createLanguageModelToolResultOutput({
|
|
2917
|
+
toolCallId: toolCall.toolCallId,
|
|
2918
|
+
toolName: toolCall.toolName,
|
|
2919
|
+
input: parsedInput,
|
|
2920
|
+
output: toolResult,
|
|
2921
|
+
tool,
|
|
2922
|
+
errorMode: 'none',
|
|
2923
|
+
supportedUrls: {},
|
|
2924
|
+
download,
|
|
2925
|
+
}),
|
|
2926
|
+
},
|
|
2927
|
+
rawOutput: toolResult,
|
|
2928
|
+
isError: false,
|
|
2929
|
+
};
|
|
2930
|
+
}
|