@ai-sdk/workflow 1.0.0-beta.1 → 1.0.0-beta.100
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 +839 -0
- package/dist/index.d.mts +301 -127
- package/dist/index.mjs +926 -379
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -11
- package/src/create-language-model-tool-result-output.ts +85 -0
- package/src/do-stream-step.ts +52 -31
- package/src/index.ts +9 -6
- package/src/providers/mock.ts +79 -92
- package/src/serializable-schema.ts +5 -1
- package/src/stream-text-iterator.ts +157 -74
- package/src/test/agent-e2e-workflows.ts +89 -9
- package/src/to-ui-message-chunk.ts +13 -10
- package/src/workflow-agent.ts +1525 -804
- package/src/workflow-chat-transport.ts +17 -15
- package/src/telemetry.ts +0 -199
|
@@ -3,15 +3,18 @@ import type {
|
|
|
3
3
|
LanguageModelV4Prompt,
|
|
4
4
|
LanguageModelV4ToolResultPart,
|
|
5
5
|
} from '@ai-sdk/provider';
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
import type { Context } from '@ai-sdk/provider-utils';
|
|
7
|
+
import {
|
|
8
|
+
experimental_filterActiveTools as filterActiveTools,
|
|
9
|
+
type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
|
|
10
|
+
type LanguageModel,
|
|
11
|
+
type ModelMessage,
|
|
12
|
+
type StepResult,
|
|
13
|
+
type ToolCallRepairFunction,
|
|
14
|
+
type ToolChoice,
|
|
15
|
+
type ToolSet,
|
|
14
16
|
} from 'ai';
|
|
17
|
+
import { createRestrictedTelemetryDispatcher } from 'ai/internal';
|
|
15
18
|
import {
|
|
16
19
|
doStreamStep,
|
|
17
20
|
type ModelStopCondition,
|
|
@@ -22,11 +25,12 @@ import { serializeToolSet } from './serializable-schema.js';
|
|
|
22
25
|
import type {
|
|
23
26
|
GenerationSettings,
|
|
24
27
|
PrepareStepCallback,
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
WorkflowAgentOnErrorCallback,
|
|
29
|
+
WorkflowAgentOnStepEndCallback,
|
|
30
|
+
WorkflowAgentOnStepFinishCallback,
|
|
31
|
+
TelemetryOptions,
|
|
27
32
|
WorkflowAgentOnStepStartCallback,
|
|
28
33
|
} from './workflow-agent.js';
|
|
29
|
-
import type { CompatibleLanguageModel } from './types.js';
|
|
30
34
|
|
|
31
35
|
// Re-export for consumers
|
|
32
36
|
export type { ProviderExecutedToolResult } from './do-stream-step.js';
|
|
@@ -42,8 +46,10 @@ export interface StreamTextIteratorYieldValue {
|
|
|
42
46
|
messages: LanguageModelV4Prompt;
|
|
43
47
|
/** The step result from the current step */
|
|
44
48
|
step?: StepResult<ToolSet, any>;
|
|
45
|
-
/** The current
|
|
46
|
-
|
|
49
|
+
/** The current runtime context shared across the agent loop */
|
|
50
|
+
runtimeContext?: Context;
|
|
51
|
+
/** The current per-tool context, keyed by tool name */
|
|
52
|
+
toolsContext?: Record<string, Context | undefined>;
|
|
47
53
|
/** Provider-executed tool results (keyed by tool call ID) */
|
|
48
54
|
providerExecutedToolResults?: Map<string, ProviderExecutedToolResult>;
|
|
49
55
|
}
|
|
@@ -55,15 +61,16 @@ export async function* streamTextIterator({
|
|
|
55
61
|
writable,
|
|
56
62
|
model,
|
|
57
63
|
stopConditions,
|
|
58
|
-
|
|
64
|
+
onStepEnd,
|
|
59
65
|
onStepFinish,
|
|
60
66
|
onStepStart,
|
|
61
67
|
onError,
|
|
62
68
|
prepareStep,
|
|
63
69
|
generationSettings,
|
|
64
70
|
toolChoice,
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
runtimeContext,
|
|
72
|
+
toolsContext,
|
|
73
|
+
telemetry,
|
|
67
74
|
includeRawChunks = false,
|
|
68
75
|
repairToolCall,
|
|
69
76
|
responseFormat,
|
|
@@ -71,20 +78,19 @@ export async function* streamTextIterator({
|
|
|
71
78
|
prompt: LanguageModelV4Prompt;
|
|
72
79
|
tools: ToolSet;
|
|
73
80
|
writable?: WritableStream<ModelCallStreamPart<ToolSet>>;
|
|
74
|
-
model:
|
|
75
|
-
| string
|
|
76
|
-
| CompatibleLanguageModel
|
|
77
|
-
| (() => Promise<CompatibleLanguageModel>);
|
|
81
|
+
model: LanguageModel;
|
|
78
82
|
stopConditions?: ModelStopCondition[] | ModelStopCondition;
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
onStepEnd?: WorkflowAgentOnStepEndCallback<any>;
|
|
84
|
+
/** @deprecated Use `onStepEnd` instead. */
|
|
85
|
+
onStepFinish?: WorkflowAgentOnStepFinishCallback<any>;
|
|
81
86
|
onStepStart?: WorkflowAgentOnStepStartCallback;
|
|
82
|
-
onError?:
|
|
87
|
+
onError?: WorkflowAgentOnErrorCallback;
|
|
83
88
|
prepareStep?: PrepareStepCallback<any>;
|
|
84
89
|
generationSettings?: GenerationSettings;
|
|
85
90
|
toolChoice?: ToolChoice<ToolSet>;
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
runtimeContext?: Context;
|
|
92
|
+
toolsContext?: Record<string, Context | undefined>;
|
|
93
|
+
telemetry?: TelemetryOptions<Context, ToolSet>;
|
|
88
94
|
includeRawChunks?: boolean;
|
|
89
95
|
repairToolCall?: ToolCallRepairFunction<ToolSet>;
|
|
90
96
|
responseFormat?: LanguageModelV4CallOptions['responseFormat'];
|
|
@@ -94,13 +100,12 @@ export async function* streamTextIterator({
|
|
|
94
100
|
LanguageModelV4ToolResultPart[]
|
|
95
101
|
> {
|
|
96
102
|
let conversationPrompt = [...prompt]; // Create a mutable copy
|
|
97
|
-
let currentModel:
|
|
98
|
-
| string
|
|
99
|
-
| CompatibleLanguageModel
|
|
100
|
-
| (() => Promise<CompatibleLanguageModel>) = model;
|
|
103
|
+
let currentModel: LanguageModel = model;
|
|
101
104
|
let currentGenerationSettings = generationSettings ?? {};
|
|
102
105
|
let currentToolChoice = toolChoice;
|
|
103
|
-
let
|
|
106
|
+
let currentRuntimeContext: Context = runtimeContext ?? {};
|
|
107
|
+
let currentToolsContext: Record<string, Context | undefined> =
|
|
108
|
+
toolsContext ?? {};
|
|
104
109
|
let currentActiveTools: string[] | undefined;
|
|
105
110
|
|
|
106
111
|
const steps: StepResult<any, any>[] = [];
|
|
@@ -110,16 +115,21 @@ export async function* streamTextIterator({
|
|
|
110
115
|
let lastStep: StepResult<any, any> | undefined;
|
|
111
116
|
let lastStepWasToolCalls = false;
|
|
112
117
|
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
118
|
+
// TODO(#12164): replace this AI-core telemetry bridge with a
|
|
119
|
+
// WorkflowAgent-specific typed dispatcher. `streamTextIterator` widens
|
|
120
|
+
// tools/runtime context and emits Workflow-shaped events that are only
|
|
121
|
+
// approximately compatible with generateText telemetry event types.
|
|
122
|
+
const telemetryDispatcher = createRestrictedTelemetryDispatcher<
|
|
123
|
+
any,
|
|
124
|
+
any,
|
|
125
|
+
any
|
|
126
|
+
>({
|
|
127
|
+
telemetry: telemetry as any,
|
|
128
|
+
includeRuntimeContext: telemetry?.includeRuntimeContext,
|
|
129
|
+
includeToolsContext: telemetry?.includeToolsContext,
|
|
130
|
+
}) as any;
|
|
116
131
|
|
|
117
132
|
while (!done) {
|
|
118
|
-
// Check if we've exceeded the maximum number of steps
|
|
119
|
-
if (stepNumber >= effectiveMaxSteps) {
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
133
|
// Check for abort signal
|
|
124
134
|
if (currentGenerationSettings.abortSignal?.aborted) {
|
|
125
135
|
break;
|
|
@@ -132,19 +142,20 @@ export async function* streamTextIterator({
|
|
|
132
142
|
stepNumber,
|
|
133
143
|
steps,
|
|
134
144
|
messages: conversationPrompt,
|
|
135
|
-
|
|
145
|
+
runtimeContext: currentRuntimeContext,
|
|
146
|
+
toolsContext: currentToolsContext as never,
|
|
136
147
|
});
|
|
137
148
|
|
|
138
149
|
// Apply any overrides from prepareStep
|
|
139
|
-
if (prepareResult
|
|
150
|
+
if (prepareResult?.model !== undefined) {
|
|
140
151
|
currentModel = prepareResult.model;
|
|
141
152
|
}
|
|
142
153
|
// Apply messages override BEFORE system so the system message
|
|
143
154
|
// isn't lost when messages replaces the prompt.
|
|
144
|
-
if (prepareResult
|
|
155
|
+
if (prepareResult?.messages !== undefined) {
|
|
145
156
|
conversationPrompt = [...prepareResult.messages];
|
|
146
157
|
}
|
|
147
|
-
if (prepareResult
|
|
158
|
+
if (prepareResult?.system !== undefined) {
|
|
148
159
|
// Update or prepend system message in the conversation prompt.
|
|
149
160
|
// Applied AFTER messages override so the system message isn't
|
|
150
161
|
// lost when messages replaces the prompt.
|
|
@@ -165,80 +176,86 @@ export async function* streamTextIterator({
|
|
|
165
176
|
});
|
|
166
177
|
}
|
|
167
178
|
}
|
|
168
|
-
if (prepareResult
|
|
169
|
-
|
|
179
|
+
if (prepareResult?.runtimeContext !== undefined) {
|
|
180
|
+
currentRuntimeContext = prepareResult.runtimeContext;
|
|
170
181
|
}
|
|
171
|
-
if (prepareResult
|
|
182
|
+
if (prepareResult?.toolsContext !== undefined) {
|
|
183
|
+
currentToolsContext = prepareResult.toolsContext as Record<
|
|
184
|
+
string,
|
|
185
|
+
Context | undefined
|
|
186
|
+
>;
|
|
187
|
+
}
|
|
188
|
+
if (prepareResult?.activeTools !== undefined) {
|
|
172
189
|
currentActiveTools = prepareResult.activeTools;
|
|
173
190
|
}
|
|
174
191
|
// Apply generation settings overrides
|
|
175
|
-
if (prepareResult
|
|
192
|
+
if (prepareResult?.maxOutputTokens !== undefined) {
|
|
176
193
|
currentGenerationSettings = {
|
|
177
194
|
...currentGenerationSettings,
|
|
178
195
|
maxOutputTokens: prepareResult.maxOutputTokens,
|
|
179
196
|
};
|
|
180
197
|
}
|
|
181
|
-
if (prepareResult
|
|
198
|
+
if (prepareResult?.temperature !== undefined) {
|
|
182
199
|
currentGenerationSettings = {
|
|
183
200
|
...currentGenerationSettings,
|
|
184
201
|
temperature: prepareResult.temperature,
|
|
185
202
|
};
|
|
186
203
|
}
|
|
187
|
-
if (prepareResult
|
|
204
|
+
if (prepareResult?.topP !== undefined) {
|
|
188
205
|
currentGenerationSettings = {
|
|
189
206
|
...currentGenerationSettings,
|
|
190
207
|
topP: prepareResult.topP,
|
|
191
208
|
};
|
|
192
209
|
}
|
|
193
|
-
if (prepareResult
|
|
210
|
+
if (prepareResult?.topK !== undefined) {
|
|
194
211
|
currentGenerationSettings = {
|
|
195
212
|
...currentGenerationSettings,
|
|
196
213
|
topK: prepareResult.topK,
|
|
197
214
|
};
|
|
198
215
|
}
|
|
199
|
-
if (prepareResult
|
|
216
|
+
if (prepareResult?.presencePenalty !== undefined) {
|
|
200
217
|
currentGenerationSettings = {
|
|
201
218
|
...currentGenerationSettings,
|
|
202
219
|
presencePenalty: prepareResult.presencePenalty,
|
|
203
220
|
};
|
|
204
221
|
}
|
|
205
|
-
if (prepareResult
|
|
222
|
+
if (prepareResult?.frequencyPenalty !== undefined) {
|
|
206
223
|
currentGenerationSettings = {
|
|
207
224
|
...currentGenerationSettings,
|
|
208
225
|
frequencyPenalty: prepareResult.frequencyPenalty,
|
|
209
226
|
};
|
|
210
227
|
}
|
|
211
|
-
if (prepareResult
|
|
228
|
+
if (prepareResult?.stopSequences !== undefined) {
|
|
212
229
|
currentGenerationSettings = {
|
|
213
230
|
...currentGenerationSettings,
|
|
214
231
|
stopSequences: prepareResult.stopSequences,
|
|
215
232
|
};
|
|
216
233
|
}
|
|
217
|
-
if (prepareResult
|
|
234
|
+
if (prepareResult?.seed !== undefined) {
|
|
218
235
|
currentGenerationSettings = {
|
|
219
236
|
...currentGenerationSettings,
|
|
220
237
|
seed: prepareResult.seed,
|
|
221
238
|
};
|
|
222
239
|
}
|
|
223
|
-
if (prepareResult
|
|
240
|
+
if (prepareResult?.maxRetries !== undefined) {
|
|
224
241
|
currentGenerationSettings = {
|
|
225
242
|
...currentGenerationSettings,
|
|
226
243
|
maxRetries: prepareResult.maxRetries,
|
|
227
244
|
};
|
|
228
245
|
}
|
|
229
|
-
if (prepareResult
|
|
246
|
+
if (prepareResult?.headers !== undefined) {
|
|
230
247
|
currentGenerationSettings = {
|
|
231
248
|
...currentGenerationSettings,
|
|
232
249
|
headers: prepareResult.headers,
|
|
233
250
|
};
|
|
234
251
|
}
|
|
235
|
-
if (prepareResult
|
|
252
|
+
if (prepareResult?.providerOptions !== undefined) {
|
|
236
253
|
currentGenerationSettings = {
|
|
237
254
|
...currentGenerationSettings,
|
|
238
255
|
providerOptions: prepareResult.providerOptions,
|
|
239
256
|
};
|
|
240
257
|
}
|
|
241
|
-
if (prepareResult
|
|
258
|
+
if (prepareResult?.toolChoice !== undefined) {
|
|
242
259
|
currentToolChoice = prepareResult.toolChoice;
|
|
243
260
|
}
|
|
244
261
|
}
|
|
@@ -248,20 +265,67 @@ export async function* streamTextIterator({
|
|
|
248
265
|
stepNumber,
|
|
249
266
|
model: currentModel,
|
|
250
267
|
messages: conversationPrompt as unknown as ModelMessage[],
|
|
268
|
+
steps: [...steps],
|
|
269
|
+
runtimeContext: currentRuntimeContext,
|
|
270
|
+
toolsContext: currentToolsContext as never,
|
|
251
271
|
});
|
|
252
272
|
}
|
|
253
273
|
|
|
274
|
+
const stepStartModelInfo = getModelInfo(currentModel);
|
|
275
|
+
await telemetryDispatcher.onStepStart?.({
|
|
276
|
+
callId: 'workflow-agent',
|
|
277
|
+
provider: stepStartModelInfo.provider,
|
|
278
|
+
modelId: stepStartModelInfo.modelId,
|
|
279
|
+
stepNumber,
|
|
280
|
+
system: undefined,
|
|
281
|
+
messages: conversationPrompt as unknown as ModelMessage[],
|
|
282
|
+
tools,
|
|
283
|
+
toolChoice: currentToolChoice,
|
|
284
|
+
activeTools: currentActiveTools as never,
|
|
285
|
+
steps: steps.map(normalizeStepForTelemetry),
|
|
286
|
+
providerOptions: currentGenerationSettings.providerOptions,
|
|
287
|
+
output: undefined,
|
|
288
|
+
runtimeContext: currentRuntimeContext,
|
|
289
|
+
toolsContext: currentToolsContext as never,
|
|
290
|
+
});
|
|
291
|
+
|
|
254
292
|
try {
|
|
255
293
|
// Filter tools if activeTools is specified
|
|
256
294
|
const effectiveTools =
|
|
257
295
|
currentActiveTools && currentActiveTools.length > 0
|
|
258
|
-
?
|
|
296
|
+
? (filterActiveTools({
|
|
297
|
+
tools,
|
|
298
|
+
activeTools: currentActiveTools,
|
|
299
|
+
}) ?? tools)
|
|
259
300
|
: tools;
|
|
260
301
|
|
|
261
302
|
// Serialize tools before crossing the step boundary — zod schemas
|
|
262
303
|
// contain functions that can't be serialized by the workflow runtime.
|
|
263
304
|
// Tools are reconstructed with Ajv validation inside doStreamStep.
|
|
264
305
|
const serializedTools = serializeToolSet(effectiveTools);
|
|
306
|
+
const modelCallInfo = getModelInfo(currentModel);
|
|
307
|
+
|
|
308
|
+
await telemetryDispatcher.onLanguageModelCallStart?.({
|
|
309
|
+
callId: 'workflow-agent',
|
|
310
|
+
provider: modelCallInfo.provider,
|
|
311
|
+
modelId: modelCallInfo.modelId,
|
|
312
|
+
system: undefined,
|
|
313
|
+
messages: conversationPrompt as unknown as ModelMessage[],
|
|
314
|
+
tools:
|
|
315
|
+
serializedTools == null
|
|
316
|
+
? undefined
|
|
317
|
+
: Object.values(serializedTools).map(tool => ({ ...tool })),
|
|
318
|
+
maxOutputTokens: currentGenerationSettings.maxOutputTokens,
|
|
319
|
+
temperature: currentGenerationSettings.temperature,
|
|
320
|
+
topP: currentGenerationSettings.topP,
|
|
321
|
+
topK: currentGenerationSettings.topK,
|
|
322
|
+
presencePenalty: currentGenerationSettings.presencePenalty,
|
|
323
|
+
frequencyPenalty: currentGenerationSettings.frequencyPenalty,
|
|
324
|
+
stopSequences: currentGenerationSettings.stopSequences,
|
|
325
|
+
seed: currentGenerationSettings.seed,
|
|
326
|
+
providerOptions: currentGenerationSettings.providerOptions,
|
|
327
|
+
headers: currentGenerationSettings.headers,
|
|
328
|
+
} as never);
|
|
265
329
|
|
|
266
330
|
const { toolCalls, finish, step, providerExecutedToolResults } =
|
|
267
331
|
await doStreamStep(
|
|
@@ -273,12 +337,24 @@ export async function* streamTextIterator({
|
|
|
273
337
|
...currentGenerationSettings,
|
|
274
338
|
toolChoice: currentToolChoice,
|
|
275
339
|
includeRawChunks,
|
|
276
|
-
experimental_telemetry,
|
|
277
340
|
repairToolCall,
|
|
278
341
|
responseFormat,
|
|
342
|
+
runtimeContext: currentRuntimeContext,
|
|
343
|
+
toolsContext: currentToolsContext,
|
|
344
|
+
stepNumber,
|
|
279
345
|
},
|
|
280
346
|
);
|
|
281
347
|
|
|
348
|
+
await telemetryDispatcher.onLanguageModelCallEnd?.({
|
|
349
|
+
callId: step.callId,
|
|
350
|
+
provider: step.model?.provider ?? 'unknown',
|
|
351
|
+
modelId: step.model?.modelId ?? 'unknown',
|
|
352
|
+
finishReason: step.finishReason,
|
|
353
|
+
usage: step.usage,
|
|
354
|
+
content: step.content,
|
|
355
|
+
responseId: step.response.id,
|
|
356
|
+
});
|
|
357
|
+
|
|
282
358
|
_isFirstIteration = false;
|
|
283
359
|
stepNumber++;
|
|
284
360
|
steps.push(step);
|
|
@@ -320,7 +396,8 @@ export async function* streamTextIterator({
|
|
|
320
396
|
toolCalls,
|
|
321
397
|
messages: conversationPrompt,
|
|
322
398
|
step,
|
|
323
|
-
|
|
399
|
+
runtimeContext: currentRuntimeContext,
|
|
400
|
+
toolsContext: currentToolsContext,
|
|
324
401
|
providerExecutedToolResults,
|
|
325
402
|
};
|
|
326
403
|
|
|
@@ -375,9 +452,11 @@ export async function* streamTextIterator({
|
|
|
375
452
|
);
|
|
376
453
|
}
|
|
377
454
|
|
|
378
|
-
|
|
379
|
-
|
|
455
|
+
const resolvedOnStepEnd = onStepEnd ?? onStepFinish;
|
|
456
|
+
if (resolvedOnStepEnd) {
|
|
457
|
+
await resolvedOnStepEnd(step);
|
|
380
458
|
}
|
|
459
|
+
await telemetryDispatcher.onStepEnd?.(normalizeStepForTelemetry(step));
|
|
381
460
|
} catch (error) {
|
|
382
461
|
if (onError) {
|
|
383
462
|
await onError({ error });
|
|
@@ -392,24 +471,28 @@ export async function* streamTextIterator({
|
|
|
392
471
|
toolCalls: [],
|
|
393
472
|
messages: conversationPrompt,
|
|
394
473
|
step: lastStep,
|
|
395
|
-
|
|
474
|
+
runtimeContext: currentRuntimeContext,
|
|
475
|
+
toolsContext: currentToolsContext,
|
|
396
476
|
};
|
|
397
477
|
}
|
|
398
478
|
|
|
399
479
|
return conversationPrompt;
|
|
400
480
|
}
|
|
401
481
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
return
|
|
482
|
+
function getModelInfo(model: LanguageModel): {
|
|
483
|
+
provider: string;
|
|
484
|
+
modelId: string;
|
|
485
|
+
} {
|
|
486
|
+
return typeof model === 'string'
|
|
487
|
+
? { provider: model.split('/')[0] ?? 'gateway', modelId: model }
|
|
488
|
+
: { provider: model.provider, modelId: model.modelId };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function normalizeStepForTelemetry(step: StepResult<any, any>) {
|
|
492
|
+
return {
|
|
493
|
+
...step,
|
|
494
|
+
model: step.model ?? { provider: 'unknown', modelId: 'unknown' },
|
|
495
|
+
};
|
|
413
496
|
}
|
|
414
497
|
|
|
415
498
|
/**
|
|
@@ -5,7 +5,7 @@ import { tool } from 'ai';
|
|
|
5
5
|
import { WorkflowAgent } from '../workflow-agent.js';
|
|
6
6
|
import { mockTextModel, mockSequenceModel } from '../providers/mock.js';
|
|
7
7
|
import { FatalError, getWritable } from 'workflow';
|
|
8
|
-
import z from 'zod';
|
|
8
|
+
import { z } from 'zod';
|
|
9
9
|
|
|
10
10
|
// ============================================================================
|
|
11
11
|
// Tool step functions
|
|
@@ -334,10 +334,10 @@ export async function agentOnStepStartE2e() {
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
// ============================================================================
|
|
337
|
-
// GAP tests —
|
|
337
|
+
// GAP tests — onToolExecutionStart
|
|
338
338
|
// ============================================================================
|
|
339
339
|
|
|
340
|
-
export async function
|
|
340
|
+
export async function agentonToolExecutionStartE2e() {
|
|
341
341
|
'use workflow';
|
|
342
342
|
const calls: string[] = [];
|
|
343
343
|
const agent = new WorkflowAgent({
|
|
@@ -356,14 +356,14 @@ export async function agentOnToolCallStartE2e() {
|
|
|
356
356
|
execute: echoStep,
|
|
357
357
|
},
|
|
358
358
|
},
|
|
359
|
-
|
|
359
|
+
onToolExecutionStart: async () => {
|
|
360
360
|
calls.push('constructor');
|
|
361
361
|
},
|
|
362
362
|
} as any);
|
|
363
363
|
await agent.stream({
|
|
364
364
|
messages: [{ role: 'user', content: 'test' }],
|
|
365
365
|
writable: getWritable(),
|
|
366
|
-
|
|
366
|
+
onToolExecutionStart: async () => {
|
|
367
367
|
calls.push('method');
|
|
368
368
|
},
|
|
369
369
|
} as any);
|
|
@@ -371,10 +371,10 @@ export async function agentOnToolCallStartE2e() {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
// ============================================================================
|
|
374
|
-
// GAP tests —
|
|
374
|
+
// GAP tests — onToolExecutionEnd
|
|
375
375
|
// ============================================================================
|
|
376
376
|
|
|
377
|
-
export async function
|
|
377
|
+
export async function agentonToolExecutionEndE2e() {
|
|
378
378
|
'use workflow';
|
|
379
379
|
const calls: string[] = [];
|
|
380
380
|
let capturedEvent: any = null;
|
|
@@ -394,14 +394,14 @@ export async function agentOnToolCallFinishE2e() {
|
|
|
394
394
|
execute: addNumbers,
|
|
395
395
|
},
|
|
396
396
|
},
|
|
397
|
-
|
|
397
|
+
onToolExecutionEnd: async () => {
|
|
398
398
|
calls.push('constructor');
|
|
399
399
|
},
|
|
400
400
|
} as any);
|
|
401
401
|
await agent.stream({
|
|
402
402
|
messages: [{ role: 'user', content: 'test' }],
|
|
403
403
|
writable: getWritable(),
|
|
404
|
-
|
|
404
|
+
onToolExecutionEnd: async (event: any) => {
|
|
405
405
|
calls.push('method');
|
|
406
406
|
capturedEvent = {
|
|
407
407
|
toolName: event?.toolCall?.toolName,
|
|
@@ -505,3 +505,83 @@ export async function agentToolInputSchemaE2e(a: number, b: number) {
|
|
|
505
505
|
lastStepText: result.steps[result.steps.length - 1]?.text,
|
|
506
506
|
};
|
|
507
507
|
}
|
|
508
|
+
|
|
509
|
+
// ============================================================================
|
|
510
|
+
// runtimeContext + toolsContext (end-to-end)
|
|
511
|
+
// ============================================================================
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Demonstrates the full context flow:
|
|
515
|
+
*
|
|
516
|
+
* - `runtimeContext` holds shared agent state (`tenantId`, `requestId`).
|
|
517
|
+
* `prepareStep` reads it and tags it with the current step number;
|
|
518
|
+
* `onFinish` receives the final value.
|
|
519
|
+
* - `toolsContext` holds per-tool, schema-validated context. The
|
|
520
|
+
* `lookupCustomer` tool declares `contextSchema`, so its entry is
|
|
521
|
+
* validated and the tool's `execute` only sees its own context.
|
|
522
|
+
*/
|
|
523
|
+
export async function agentRuntimeAndToolsContextE2e() {
|
|
524
|
+
'use workflow';
|
|
525
|
+
|
|
526
|
+
let onFinishRuntimeContext: Record<string, unknown> | undefined;
|
|
527
|
+
let onFinishToolsContext: Record<string, unknown> | undefined;
|
|
528
|
+
let toolReceivedContext: unknown;
|
|
529
|
+
|
|
530
|
+
const agent = new WorkflowAgent({
|
|
531
|
+
model: mockSequenceModel([
|
|
532
|
+
{
|
|
533
|
+
type: 'tool-call',
|
|
534
|
+
toolName: 'lookupCustomer',
|
|
535
|
+
input: JSON.stringify({ customerId: 'cust_123' }),
|
|
536
|
+
},
|
|
537
|
+
{ type: 'text', text: 'Customer cust_123 is eligible.' },
|
|
538
|
+
]),
|
|
539
|
+
tools: {
|
|
540
|
+
lookupCustomer: tool({
|
|
541
|
+
description: 'Look up customer account details.',
|
|
542
|
+
inputSchema: z.object({ customerId: z.string() }),
|
|
543
|
+
contextSchema: z.object({
|
|
544
|
+
apiKey: z.string(),
|
|
545
|
+
region: z.enum(['us', 'eu']),
|
|
546
|
+
}),
|
|
547
|
+
execute: async (input, { context }) => {
|
|
548
|
+
toolReceivedContext = context;
|
|
549
|
+
return { customerId: input.customerId, eligible: true };
|
|
550
|
+
},
|
|
551
|
+
}),
|
|
552
|
+
},
|
|
553
|
+
instructions: 'You look up customers.',
|
|
554
|
+
runtimeContext: {
|
|
555
|
+
tenantId: 'tenant_123',
|
|
556
|
+
requestId: 'req_abc',
|
|
557
|
+
},
|
|
558
|
+
toolsContext: {
|
|
559
|
+
lookupCustomer: {
|
|
560
|
+
apiKey: 'sk-test-key',
|
|
561
|
+
region: 'us',
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
prepareStep: ({ stepNumber, runtimeContext }) => ({
|
|
565
|
+
runtimeContext: { ...runtimeContext, lastStep: stepNumber },
|
|
566
|
+
}),
|
|
567
|
+
onFinish: ({ runtimeContext, toolsContext }) => {
|
|
568
|
+
onFinishRuntimeContext = runtimeContext;
|
|
569
|
+
onFinishToolsContext = toolsContext;
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
const result = await agent.stream({
|
|
574
|
+
messages: [
|
|
575
|
+
{ role: 'user', content: 'Is customer cust_123 eligible for support?' },
|
|
576
|
+
],
|
|
577
|
+
writable: getWritable(),
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
stepCount: result.steps.length,
|
|
582
|
+
lastStepText: result.steps[result.steps.length - 1]?.text,
|
|
583
|
+
toolReceivedContext,
|
|
584
|
+
onFinishRuntimeContext,
|
|
585
|
+
onFinishToolsContext,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
Experimental_LanguageModelStreamPart as ModelCallStreamPart,
|
|
3
|
+
ToolSet,
|
|
4
|
+
UIMessageChunk,
|
|
5
|
+
} from 'ai';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Convert a single ModelCallStreamPart to a UIMessageChunk.
|
|
@@ -188,20 +191,20 @@ export function toUIMessageChunk(
|
|
|
188
191
|
// standard ModelCallStreamPart types but are written by the
|
|
189
192
|
// WorkflowAgent between tool execution and the next model step
|
|
190
193
|
// to ensure proper message splitting in convertToModelMessages.
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
194
|
+
const passthroughPart = part as any;
|
|
195
|
+
if (passthroughPart.type === 'tool-approval-request') {
|
|
193
196
|
return {
|
|
194
197
|
type: 'tool-approval-request',
|
|
195
|
-
approvalId:
|
|
196
|
-
toolCallId:
|
|
198
|
+
approvalId: passthroughPart.approvalId,
|
|
199
|
+
toolCallId: passthroughPart.toolCallId,
|
|
197
200
|
} as UIMessageChunk;
|
|
198
201
|
}
|
|
199
202
|
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
passthroughPart.type === 'finish-step' ||
|
|
204
|
+
passthroughPart.type === 'start-step' ||
|
|
205
|
+
passthroughPart.type === 'tool-output-denied'
|
|
203
206
|
) {
|
|
204
|
-
return
|
|
207
|
+
return passthroughPart as UIMessageChunk;
|
|
205
208
|
}
|
|
206
209
|
return undefined;
|
|
207
210
|
}
|