@ai-sdk/workflow 1.0.0-canary.39 → 1.0.0-canary.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/workflow",
3
- "version": "1.0.0-canary.39",
3
+ "version": "1.0.0-canary.41",
4
4
  "description": "WorkflowAgent for building AI agents with AI SDK",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.js",
@@ -28,15 +28,15 @@
28
28
  "dependencies": {
29
29
  "ajv": "^8.18.0",
30
30
  "@ai-sdk/provider": "4.0.0-canary.16",
31
- "@ai-sdk/provider-utils": "5.0.0-canary.33",
32
- "ai": "7.0.0-canary.124"
31
+ "@ai-sdk/provider-utils": "5.0.0-canary.34",
32
+ "ai": "7.0.0-canary.125"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "20.17.24",
36
36
  "@workflow/vitest": "4.0.1",
37
37
  "tsup": "^8",
38
38
  "typescript": "5.8.3",
39
- "workflow": "4.2.0",
39
+ "workflow": "4.2.4",
40
40
  "zod": "3.25.76",
41
41
  "@vercel/ai-tsconfig": "0.0.0"
42
42
  },
@@ -2,6 +2,7 @@ import type {
2
2
  LanguageModelV4CallOptions,
3
3
  LanguageModelV4Prompt,
4
4
  } from '@ai-sdk/provider';
5
+ import type { Context } from '@ai-sdk/provider-utils';
5
6
  import {
6
7
  experimental_streamLanguageModelCall as streamModelCall,
7
8
  gateway,
@@ -56,6 +57,8 @@ export interface DoStreamStepOptions {
56
57
  telemetry?: TelemetryOptions;
57
58
  repairToolCall?: ToolCallRepairFunction<ToolSet>;
58
59
  responseFormat?: LanguageModelV4CallOptions['responseFormat'];
60
+ runtimeContext?: Context;
61
+ toolsContext?: Record<string, Context | undefined>;
59
62
  }
60
63
 
61
64
  /**
@@ -241,8 +244,8 @@ export async function doStreamStep(
241
244
  },
242
245
  functionId: undefined,
243
246
  metadata: undefined,
244
- runtimeContext: undefined,
245
- toolsContext: {},
247
+ runtimeContext: options?.runtimeContext ?? {},
248
+ toolsContext: options?.toolsContext ?? {},
246
249
  content: [
247
250
  ...(text ? [{ type: 'text' as const, text }] : []),
248
251
  ...toolCalls
@@ -3,6 +3,7 @@ import type {
3
3
  LanguageModelV4Prompt,
4
4
  LanguageModelV4ToolResultPart,
5
5
  } from '@ai-sdk/provider';
6
+ import type { Context } from '@ai-sdk/provider-utils';
6
7
  import {
7
8
  experimental_filterActiveTools as filterActiveTools,
8
9
  type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
@@ -43,8 +44,10 @@ export interface StreamTextIteratorYieldValue {
43
44
  messages: LanguageModelV4Prompt;
44
45
  /** The step result from the current step */
45
46
  step?: StepResult<ToolSet, any>;
46
- /** The current experimental context */
47
- context?: unknown;
47
+ /** The current runtime context shared across the agent loop */
48
+ runtimeContext?: Context;
49
+ /** The current per-tool context, keyed by tool name */
50
+ toolsContext?: Record<string, Context | undefined>;
48
51
  /** Provider-executed tool results (keyed by tool call ID) */
49
52
  providerExecutedToolResults?: Map<string, ProviderExecutedToolResult>;
50
53
  }
@@ -62,7 +65,8 @@ export async function* streamTextIterator({
62
65
  prepareStep,
63
66
  generationSettings,
64
67
  toolChoice,
65
- experimental_context,
68
+ runtimeContext,
69
+ toolsContext,
66
70
  telemetry,
67
71
  includeRawChunks = false,
68
72
  repairToolCall,
@@ -79,7 +83,8 @@ export async function* streamTextIterator({
79
83
  prepareStep?: PrepareStepCallback<any>;
80
84
  generationSettings?: GenerationSettings;
81
85
  toolChoice?: ToolChoice<ToolSet>;
82
- experimental_context?: unknown;
86
+ runtimeContext?: Context;
87
+ toolsContext?: Record<string, Context | undefined>;
83
88
  telemetry?: TelemetryOptions;
84
89
  includeRawChunks?: boolean;
85
90
  repairToolCall?: ToolCallRepairFunction<ToolSet>;
@@ -93,7 +98,9 @@ export async function* streamTextIterator({
93
98
  let currentModel: LanguageModel = model;
94
99
  let currentGenerationSettings = generationSettings ?? {};
95
100
  let currentToolChoice = toolChoice;
96
- let currentContext = experimental_context;
101
+ let currentRuntimeContext: Context = runtimeContext ?? {};
102
+ let currentToolsContext: Record<string, Context | undefined> =
103
+ toolsContext ?? {};
97
104
  let currentActiveTools: string[] | undefined;
98
105
 
99
106
  const steps: StepResult<any, any>[] = [];
@@ -116,19 +123,20 @@ export async function* streamTextIterator({
116
123
  stepNumber,
117
124
  steps,
118
125
  messages: conversationPrompt,
119
- experimental_context: currentContext,
126
+ runtimeContext: currentRuntimeContext,
127
+ toolsContext: currentToolsContext as never,
120
128
  });
121
129
 
122
130
  // Apply any overrides from prepareStep
123
- if (prepareResult.model !== undefined) {
131
+ if (prepareResult?.model !== undefined) {
124
132
  currentModel = prepareResult.model;
125
133
  }
126
134
  // Apply messages override BEFORE system so the system message
127
135
  // isn't lost when messages replaces the prompt.
128
- if (prepareResult.messages !== undefined) {
136
+ if (prepareResult?.messages !== undefined) {
129
137
  conversationPrompt = [...prepareResult.messages];
130
138
  }
131
- if (prepareResult.system !== undefined) {
139
+ if (prepareResult?.system !== undefined) {
132
140
  // Update or prepend system message in the conversation prompt.
133
141
  // Applied AFTER messages override so the system message isn't
134
142
  // lost when messages replaces the prompt.
@@ -149,80 +157,86 @@ export async function* streamTextIterator({
149
157
  });
150
158
  }
151
159
  }
152
- if (prepareResult.experimental_context !== undefined) {
153
- currentContext = prepareResult.experimental_context;
160
+ if (prepareResult?.runtimeContext !== undefined) {
161
+ currentRuntimeContext = prepareResult.runtimeContext;
154
162
  }
155
- if (prepareResult.activeTools !== undefined) {
163
+ if (prepareResult?.toolsContext !== undefined) {
164
+ currentToolsContext = prepareResult.toolsContext as Record<
165
+ string,
166
+ Context | undefined
167
+ >;
168
+ }
169
+ if (prepareResult?.activeTools !== undefined) {
156
170
  currentActiveTools = prepareResult.activeTools;
157
171
  }
158
172
  // Apply generation settings overrides
159
- if (prepareResult.maxOutputTokens !== undefined) {
173
+ if (prepareResult?.maxOutputTokens !== undefined) {
160
174
  currentGenerationSettings = {
161
175
  ...currentGenerationSettings,
162
176
  maxOutputTokens: prepareResult.maxOutputTokens,
163
177
  };
164
178
  }
165
- if (prepareResult.temperature !== undefined) {
179
+ if (prepareResult?.temperature !== undefined) {
166
180
  currentGenerationSettings = {
167
181
  ...currentGenerationSettings,
168
182
  temperature: prepareResult.temperature,
169
183
  };
170
184
  }
171
- if (prepareResult.topP !== undefined) {
185
+ if (prepareResult?.topP !== undefined) {
172
186
  currentGenerationSettings = {
173
187
  ...currentGenerationSettings,
174
188
  topP: prepareResult.topP,
175
189
  };
176
190
  }
177
- if (prepareResult.topK !== undefined) {
191
+ if (prepareResult?.topK !== undefined) {
178
192
  currentGenerationSettings = {
179
193
  ...currentGenerationSettings,
180
194
  topK: prepareResult.topK,
181
195
  };
182
196
  }
183
- if (prepareResult.presencePenalty !== undefined) {
197
+ if (prepareResult?.presencePenalty !== undefined) {
184
198
  currentGenerationSettings = {
185
199
  ...currentGenerationSettings,
186
200
  presencePenalty: prepareResult.presencePenalty,
187
201
  };
188
202
  }
189
- if (prepareResult.frequencyPenalty !== undefined) {
203
+ if (prepareResult?.frequencyPenalty !== undefined) {
190
204
  currentGenerationSettings = {
191
205
  ...currentGenerationSettings,
192
206
  frequencyPenalty: prepareResult.frequencyPenalty,
193
207
  };
194
208
  }
195
- if (prepareResult.stopSequences !== undefined) {
209
+ if (prepareResult?.stopSequences !== undefined) {
196
210
  currentGenerationSettings = {
197
211
  ...currentGenerationSettings,
198
212
  stopSequences: prepareResult.stopSequences,
199
213
  };
200
214
  }
201
- if (prepareResult.seed !== undefined) {
215
+ if (prepareResult?.seed !== undefined) {
202
216
  currentGenerationSettings = {
203
217
  ...currentGenerationSettings,
204
218
  seed: prepareResult.seed,
205
219
  };
206
220
  }
207
- if (prepareResult.maxRetries !== undefined) {
221
+ if (prepareResult?.maxRetries !== undefined) {
208
222
  currentGenerationSettings = {
209
223
  ...currentGenerationSettings,
210
224
  maxRetries: prepareResult.maxRetries,
211
225
  };
212
226
  }
213
- if (prepareResult.headers !== undefined) {
227
+ if (prepareResult?.headers !== undefined) {
214
228
  currentGenerationSettings = {
215
229
  ...currentGenerationSettings,
216
230
  headers: prepareResult.headers,
217
231
  };
218
232
  }
219
- if (prepareResult.providerOptions !== undefined) {
233
+ if (prepareResult?.providerOptions !== undefined) {
220
234
  currentGenerationSettings = {
221
235
  ...currentGenerationSettings,
222
236
  providerOptions: prepareResult.providerOptions,
223
237
  };
224
238
  }
225
- if (prepareResult.toolChoice !== undefined) {
239
+ if (prepareResult?.toolChoice !== undefined) {
226
240
  currentToolChoice = prepareResult.toolChoice;
227
241
  }
228
242
  }
@@ -233,6 +247,8 @@ export async function* streamTextIterator({
233
247
  model: currentModel,
234
248
  messages: conversationPrompt as unknown as ModelMessage[],
235
249
  steps: [...steps],
250
+ runtimeContext: currentRuntimeContext,
251
+ toolsContext: currentToolsContext as never,
236
252
  });
237
253
  }
238
254
 
@@ -264,6 +280,8 @@ export async function* streamTextIterator({
264
280
  telemetry,
265
281
  repairToolCall,
266
282
  responseFormat,
283
+ runtimeContext: currentRuntimeContext,
284
+ toolsContext: currentToolsContext,
267
285
  },
268
286
  );
269
287
 
@@ -308,7 +326,8 @@ export async function* streamTextIterator({
308
326
  toolCalls,
309
327
  messages: conversationPrompt,
310
328
  step,
311
- context: currentContext,
329
+ runtimeContext: currentRuntimeContext,
330
+ toolsContext: currentToolsContext,
312
331
  providerExecutedToolResults,
313
332
  };
314
333
 
@@ -380,7 +399,8 @@ export async function* streamTextIterator({
380
399
  toolCalls: [],
381
400
  messages: conversationPrompt,
382
401
  step: lastStep,
383
- context: currentContext,
402
+ runtimeContext: currentRuntimeContext,
403
+ toolsContext: currentToolsContext,
384
404
  };
385
405
  }
386
406
 
@@ -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
+ }