@ai-sdk/workflow 1.0.0-beta.9 → 1.0.0-beta.94
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 +740 -0
- package/dist/index.d.mts +190 -112
- package/dist/index.mjs +855 -340
- 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 +46 -9
- package/src/index.ts +5 -3
- package/src/providers/mock.ts +8 -8
- package/src/serializable-schema.ts +5 -1
- package/src/stream-text-iterator.ts +150 -62
- package/src/test/agent-e2e-workflows.ts +89 -9
- package/src/to-ui-message-chunk.ts +13 -10
- package/src/workflow-agent.ts +1157 -606
- 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,
|
|
@@ -23,8 +26,9 @@ import type {
|
|
|
23
26
|
GenerationSettings,
|
|
24
27
|
PrepareStepCallback,
|
|
25
28
|
WorkflowAgentOnErrorCallback,
|
|
29
|
+
WorkflowAgentOnStepEndCallback,
|
|
26
30
|
WorkflowAgentOnStepFinishCallback,
|
|
27
|
-
|
|
31
|
+
TelemetryOptions,
|
|
28
32
|
WorkflowAgentOnStepStartCallback,
|
|
29
33
|
} from './workflow-agent.js';
|
|
30
34
|
|
|
@@ -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,
|
|
@@ -73,15 +80,17 @@ export async function* streamTextIterator({
|
|
|
73
80
|
writable?: WritableStream<ModelCallStreamPart<ToolSet>>;
|
|
74
81
|
model: LanguageModel;
|
|
75
82
|
stopConditions?: ModelStopCondition[] | ModelStopCondition;
|
|
76
|
-
|
|
83
|
+
onStepEnd?: WorkflowAgentOnStepEndCallback<any>;
|
|
84
|
+
/** @deprecated Use `onStepEnd` instead. */
|
|
77
85
|
onStepFinish?: WorkflowAgentOnStepFinishCallback<any>;
|
|
78
86
|
onStepStart?: WorkflowAgentOnStepStartCallback;
|
|
79
87
|
onError?: WorkflowAgentOnErrorCallback;
|
|
80
88
|
prepareStep?: PrepareStepCallback<any>;
|
|
81
89
|
generationSettings?: GenerationSettings;
|
|
82
90
|
toolChoice?: ToolChoice<ToolSet>;
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
runtimeContext?: Context;
|
|
92
|
+
toolsContext?: Record<string, Context | undefined>;
|
|
93
|
+
telemetry?: TelemetryOptions<Context, ToolSet>;
|
|
85
94
|
includeRawChunks?: boolean;
|
|
86
95
|
repairToolCall?: ToolCallRepairFunction<ToolSet>;
|
|
87
96
|
responseFormat?: LanguageModelV4CallOptions['responseFormat'];
|
|
@@ -94,7 +103,9 @@ export async function* streamTextIterator({
|
|
|
94
103
|
let currentModel: LanguageModel = model;
|
|
95
104
|
let currentGenerationSettings = generationSettings ?? {};
|
|
96
105
|
let currentToolChoice = toolChoice;
|
|
97
|
-
let
|
|
106
|
+
let currentRuntimeContext: Context = runtimeContext ?? {};
|
|
107
|
+
let currentToolsContext: Record<string, Context | undefined> =
|
|
108
|
+
toolsContext ?? {};
|
|
98
109
|
let currentActiveTools: string[] | undefined;
|
|
99
110
|
|
|
100
111
|
const steps: StepResult<any, any>[] = [];
|
|
@@ -104,16 +115,21 @@ export async function* streamTextIterator({
|
|
|
104
115
|
let lastStep: StepResult<any, any> | undefined;
|
|
105
116
|
let lastStepWasToolCalls = false;
|
|
106
117
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
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;
|
|
110
131
|
|
|
111
132
|
while (!done) {
|
|
112
|
-
// Check if we've exceeded the maximum number of steps
|
|
113
|
-
if (stepNumber >= effectiveMaxSteps) {
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
133
|
// Check for abort signal
|
|
118
134
|
if (currentGenerationSettings.abortSignal?.aborted) {
|
|
119
135
|
break;
|
|
@@ -126,19 +142,20 @@ export async function* streamTextIterator({
|
|
|
126
142
|
stepNumber,
|
|
127
143
|
steps,
|
|
128
144
|
messages: conversationPrompt,
|
|
129
|
-
|
|
145
|
+
runtimeContext: currentRuntimeContext,
|
|
146
|
+
toolsContext: currentToolsContext as never,
|
|
130
147
|
});
|
|
131
148
|
|
|
132
149
|
// Apply any overrides from prepareStep
|
|
133
|
-
if (prepareResult
|
|
150
|
+
if (prepareResult?.model !== undefined) {
|
|
134
151
|
currentModel = prepareResult.model;
|
|
135
152
|
}
|
|
136
153
|
// Apply messages override BEFORE system so the system message
|
|
137
154
|
// isn't lost when messages replaces the prompt.
|
|
138
|
-
if (prepareResult
|
|
155
|
+
if (prepareResult?.messages !== undefined) {
|
|
139
156
|
conversationPrompt = [...prepareResult.messages];
|
|
140
157
|
}
|
|
141
|
-
if (prepareResult
|
|
158
|
+
if (prepareResult?.system !== undefined) {
|
|
142
159
|
// Update or prepend system message in the conversation prompt.
|
|
143
160
|
// Applied AFTER messages override so the system message isn't
|
|
144
161
|
// lost when messages replaces the prompt.
|
|
@@ -159,80 +176,86 @@ export async function* streamTextIterator({
|
|
|
159
176
|
});
|
|
160
177
|
}
|
|
161
178
|
}
|
|
162
|
-
if (prepareResult
|
|
163
|
-
|
|
179
|
+
if (prepareResult?.runtimeContext !== undefined) {
|
|
180
|
+
currentRuntimeContext = prepareResult.runtimeContext;
|
|
164
181
|
}
|
|
165
|
-
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) {
|
|
166
189
|
currentActiveTools = prepareResult.activeTools;
|
|
167
190
|
}
|
|
168
191
|
// Apply generation settings overrides
|
|
169
|
-
if (prepareResult
|
|
192
|
+
if (prepareResult?.maxOutputTokens !== undefined) {
|
|
170
193
|
currentGenerationSettings = {
|
|
171
194
|
...currentGenerationSettings,
|
|
172
195
|
maxOutputTokens: prepareResult.maxOutputTokens,
|
|
173
196
|
};
|
|
174
197
|
}
|
|
175
|
-
if (prepareResult
|
|
198
|
+
if (prepareResult?.temperature !== undefined) {
|
|
176
199
|
currentGenerationSettings = {
|
|
177
200
|
...currentGenerationSettings,
|
|
178
201
|
temperature: prepareResult.temperature,
|
|
179
202
|
};
|
|
180
203
|
}
|
|
181
|
-
if (prepareResult
|
|
204
|
+
if (prepareResult?.topP !== undefined) {
|
|
182
205
|
currentGenerationSettings = {
|
|
183
206
|
...currentGenerationSettings,
|
|
184
207
|
topP: prepareResult.topP,
|
|
185
208
|
};
|
|
186
209
|
}
|
|
187
|
-
if (prepareResult
|
|
210
|
+
if (prepareResult?.topK !== undefined) {
|
|
188
211
|
currentGenerationSettings = {
|
|
189
212
|
...currentGenerationSettings,
|
|
190
213
|
topK: prepareResult.topK,
|
|
191
214
|
};
|
|
192
215
|
}
|
|
193
|
-
if (prepareResult
|
|
216
|
+
if (prepareResult?.presencePenalty !== undefined) {
|
|
194
217
|
currentGenerationSettings = {
|
|
195
218
|
...currentGenerationSettings,
|
|
196
219
|
presencePenalty: prepareResult.presencePenalty,
|
|
197
220
|
};
|
|
198
221
|
}
|
|
199
|
-
if (prepareResult
|
|
222
|
+
if (prepareResult?.frequencyPenalty !== undefined) {
|
|
200
223
|
currentGenerationSettings = {
|
|
201
224
|
...currentGenerationSettings,
|
|
202
225
|
frequencyPenalty: prepareResult.frequencyPenalty,
|
|
203
226
|
};
|
|
204
227
|
}
|
|
205
|
-
if (prepareResult
|
|
228
|
+
if (prepareResult?.stopSequences !== undefined) {
|
|
206
229
|
currentGenerationSettings = {
|
|
207
230
|
...currentGenerationSettings,
|
|
208
231
|
stopSequences: prepareResult.stopSequences,
|
|
209
232
|
};
|
|
210
233
|
}
|
|
211
|
-
if (prepareResult
|
|
234
|
+
if (prepareResult?.seed !== undefined) {
|
|
212
235
|
currentGenerationSettings = {
|
|
213
236
|
...currentGenerationSettings,
|
|
214
237
|
seed: prepareResult.seed,
|
|
215
238
|
};
|
|
216
239
|
}
|
|
217
|
-
if (prepareResult
|
|
240
|
+
if (prepareResult?.maxRetries !== undefined) {
|
|
218
241
|
currentGenerationSettings = {
|
|
219
242
|
...currentGenerationSettings,
|
|
220
243
|
maxRetries: prepareResult.maxRetries,
|
|
221
244
|
};
|
|
222
245
|
}
|
|
223
|
-
if (prepareResult
|
|
246
|
+
if (prepareResult?.headers !== undefined) {
|
|
224
247
|
currentGenerationSettings = {
|
|
225
248
|
...currentGenerationSettings,
|
|
226
249
|
headers: prepareResult.headers,
|
|
227
250
|
};
|
|
228
251
|
}
|
|
229
|
-
if (prepareResult
|
|
252
|
+
if (prepareResult?.providerOptions !== undefined) {
|
|
230
253
|
currentGenerationSettings = {
|
|
231
254
|
...currentGenerationSettings,
|
|
232
255
|
providerOptions: prepareResult.providerOptions,
|
|
233
256
|
};
|
|
234
257
|
}
|
|
235
|
-
if (prepareResult
|
|
258
|
+
if (prepareResult?.toolChoice !== undefined) {
|
|
236
259
|
currentToolChoice = prepareResult.toolChoice;
|
|
237
260
|
}
|
|
238
261
|
}
|
|
@@ -243,20 +266,66 @@ export async function* streamTextIterator({
|
|
|
243
266
|
model: currentModel,
|
|
244
267
|
messages: conversationPrompt as unknown as ModelMessage[],
|
|
245
268
|
steps: [...steps],
|
|
269
|
+
runtimeContext: currentRuntimeContext,
|
|
270
|
+
toolsContext: currentToolsContext as never,
|
|
246
271
|
});
|
|
247
272
|
}
|
|
248
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
|
+
|
|
249
292
|
try {
|
|
250
293
|
// Filter tools if activeTools is specified
|
|
251
294
|
const effectiveTools =
|
|
252
295
|
currentActiveTools && currentActiveTools.length > 0
|
|
253
|
-
?
|
|
296
|
+
? (filterActiveTools({
|
|
297
|
+
tools,
|
|
298
|
+
activeTools: currentActiveTools,
|
|
299
|
+
}) ?? tools)
|
|
254
300
|
: tools;
|
|
255
301
|
|
|
256
302
|
// Serialize tools before crossing the step boundary — zod schemas
|
|
257
303
|
// contain functions that can't be serialized by the workflow runtime.
|
|
258
304
|
// Tools are reconstructed with Ajv validation inside doStreamStep.
|
|
259
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);
|
|
260
329
|
|
|
261
330
|
const { toolCalls, finish, step, providerExecutedToolResults } =
|
|
262
331
|
await doStreamStep(
|
|
@@ -268,12 +337,24 @@ export async function* streamTextIterator({
|
|
|
268
337
|
...currentGenerationSettings,
|
|
269
338
|
toolChoice: currentToolChoice,
|
|
270
339
|
includeRawChunks,
|
|
271
|
-
experimental_telemetry,
|
|
272
340
|
repairToolCall,
|
|
273
341
|
responseFormat,
|
|
342
|
+
runtimeContext: currentRuntimeContext,
|
|
343
|
+
toolsContext: currentToolsContext,
|
|
344
|
+
stepNumber,
|
|
274
345
|
},
|
|
275
346
|
);
|
|
276
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
|
+
|
|
277
358
|
_isFirstIteration = false;
|
|
278
359
|
stepNumber++;
|
|
279
360
|
steps.push(step);
|
|
@@ -315,7 +396,8 @@ export async function* streamTextIterator({
|
|
|
315
396
|
toolCalls,
|
|
316
397
|
messages: conversationPrompt,
|
|
317
398
|
step,
|
|
318
|
-
|
|
399
|
+
runtimeContext: currentRuntimeContext,
|
|
400
|
+
toolsContext: currentToolsContext,
|
|
319
401
|
providerExecutedToolResults,
|
|
320
402
|
};
|
|
321
403
|
|
|
@@ -370,9 +452,11 @@ export async function* streamTextIterator({
|
|
|
370
452
|
);
|
|
371
453
|
}
|
|
372
454
|
|
|
373
|
-
|
|
374
|
-
|
|
455
|
+
const resolvedOnStepEnd = onStepEnd ?? onStepFinish;
|
|
456
|
+
if (resolvedOnStepEnd) {
|
|
457
|
+
await resolvedOnStepEnd(step);
|
|
375
458
|
}
|
|
459
|
+
await telemetryDispatcher.onStepEnd?.(normalizeStepForTelemetry(step));
|
|
376
460
|
} catch (error) {
|
|
377
461
|
if (onError) {
|
|
378
462
|
await onError({ error });
|
|
@@ -387,24 +471,28 @@ export async function* streamTextIterator({
|
|
|
387
471
|
toolCalls: [],
|
|
388
472
|
messages: conversationPrompt,
|
|
389
473
|
step: lastStep,
|
|
390
|
-
|
|
474
|
+
runtimeContext: currentRuntimeContext,
|
|
475
|
+
toolsContext: currentToolsContext,
|
|
391
476
|
};
|
|
392
477
|
}
|
|
393
478
|
|
|
394
479
|
return conversationPrompt;
|
|
395
480
|
}
|
|
396
481
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
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
|
+
};
|
|
408
496
|
}
|
|
409
497
|
|
|
410
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
|
}
|