@ai-sdk/workflow 2.0.57 → 2.0.59

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": "2.0.57",
3
+ "version": "2.0.59",
4
4
  "type": "module",
5
5
  "description": "WorkflowAgent for building AI agents with AI SDK",
6
6
  "license": "Apache-2.0",
@@ -36,9 +36,9 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "@ai-sdk/provider": "4.0.21",
40
- "@ai-sdk/provider-utils": "5.0.53",
41
- "ai": "7.0.126",
39
+ "@ai-sdk/provider": "4.0.22",
40
+ "@ai-sdk/provider-utils": "5.0.54",
41
+ "ai": "7.0.128",
42
42
  "ajv": "^8.20.0"
43
43
  },
44
44
  "devDependencies": {
@@ -0,0 +1,329 @@
1
+ import type { LanguageModelV4Content } from '@ai-sdk/provider';
2
+ import {
3
+ convertLanguageModelContent,
4
+ DefaultStepResult,
5
+ calculateTokensPerSecond,
6
+ } from 'ai/internal';
7
+ import type { Context } from '@ai-sdk/provider-utils';
8
+ import {
9
+ DefaultGeneratedFile,
10
+ type LanguageModelUsage,
11
+ type ModelMessage,
12
+ type StepResult,
13
+ type ToolSet,
14
+ } from 'ai';
15
+ import type {
16
+ ModelCallRawResult,
17
+ ModelCallFinish,
18
+ ParsedToolCall,
19
+ ProviderExecutedToolResult,
20
+ } from './model-call.js';
21
+
22
+ export async function buildModelStepResult(
23
+ raw: ModelCallRawResult,
24
+ toolCalls: ParsedToolCall[],
25
+ finish: ModelCallFinish | undefined,
26
+ providerExecutedToolResults: Map<string, ProviderExecutedToolResult>,
27
+ opts: {
28
+ tools?: ToolSet;
29
+ requestMessages?: ModelMessage[];
30
+ stepNumber: number;
31
+ runtimeContext: Context;
32
+ toolsContext: Record<string, Context | undefined>;
33
+ },
34
+ ): Promise<StepResult<ToolSet, any>> {
35
+ if (raw.generation != null && finish != null) {
36
+ const providerContent: LanguageModelV4Content[] = [];
37
+ for (const part of raw.content) {
38
+ switch (part.type) {
39
+ case 'reasoning':
40
+ if (!('reasoningIndex' in part)) providerContent.push(part);
41
+ break;
42
+ case 'tool-call': {
43
+ const call = toolCalls[part.toolCallIndex];
44
+ providerContent.push({ ...call, input: '', type: 'tool-call' });
45
+ break;
46
+ }
47
+ case 'provider-tool-result':
48
+ providerContent.push({
49
+ ...providerExecutedToolResults.get(part.toolCallId)!,
50
+ type: 'tool-result',
51
+ } as Extract<LanguageModelV4Content, { type: 'tool-result' }>);
52
+ break;
53
+ case 'file':
54
+ providerContent.push({
55
+ ...part,
56
+ data: { type: 'data', data: part.data },
57
+ });
58
+ break;
59
+ default:
60
+ providerContent.push(part);
61
+ }
62
+ }
63
+ const content = await convertLanguageModelContent({
64
+ content: providerContent,
65
+ toolCalls: toolCalls as StepResult<ToolSet>['toolCalls'],
66
+ toolOutputs: [],
67
+ toolApprovalRequests: [],
68
+ toolApprovalResponses: [],
69
+ tools: opts.tools,
70
+ });
71
+ const duration = raw.generation.responseTimeMs;
72
+ const rate = (tokens: number | undefined) =>
73
+ calculateTokensPerSecond({ tokens, durationMs: duration });
74
+ return new DefaultStepResult({
75
+ callId: 'workflow-agent',
76
+ stepNumber: opts.stepNumber,
77
+ provider: raw.generation.provider,
78
+ modelId: raw.generation.modelId,
79
+ runtimeContext: opts.runtimeContext,
80
+ toolsContext: opts.toolsContext,
81
+ content,
82
+ finishReason: finish.finishReason,
83
+ rawFinishReason: finish.rawFinishReason,
84
+ usage: finish.usage,
85
+ warnings: raw.warnings as StepResult<ToolSet>['warnings'],
86
+ request: { ...raw.generation.request, messages: opts.requestMessages },
87
+ response: {
88
+ ...raw.responseMetadata,
89
+ messages: [],
90
+ } as StepResult<ToolSet>['response'],
91
+ providerMetadata:
92
+ finish.providerMetadata as StepResult<ToolSet>['providerMetadata'],
93
+ performance: {
94
+ responseTimeMs: duration,
95
+ stepTimeMs: duration,
96
+ toolExecutionMs: {},
97
+ effectiveOutputTokensPerSecond: rate(finish.usage.outputTokens),
98
+ effectiveTotalTokensPerSecond: rate(finish.usage.totalTokens),
99
+ outputTokensPerSecond: undefined,
100
+ inputTokensPerSecond: undefined,
101
+ timeToFirstOutputMs: undefined,
102
+ },
103
+ });
104
+ }
105
+ const {
106
+ content: rawContent,
107
+ reasoning: reasoningParts,
108
+ responseMetadata,
109
+ warnings,
110
+ } = raw;
111
+ const reasoningText = reasoningParts.map(r => r.text).join('') || undefined;
112
+ const validToolCallsByIndex = new Map(
113
+ toolCalls.flatMap((tc, index) =>
114
+ tc.invalid
115
+ ? []
116
+ : [
117
+ [
118
+ index,
119
+ {
120
+ type: 'tool-call' as const,
121
+ toolCallId: tc.toolCallId,
122
+ toolName: tc.toolName,
123
+ input: tc.input,
124
+ ...(tc.providerExecuted != null
125
+ ? { providerExecuted: tc.providerExecuted }
126
+ : {}),
127
+ ...(tc.title != null ? { title: tc.title } : {}),
128
+ ...(tc.toolMetadata != null
129
+ ? { toolMetadata: tc.toolMetadata }
130
+ : {}),
131
+ ...(tc.dynamic ? { dynamic: true as const } : {}),
132
+ ...(tc.providerExecuted ? { providerExecuted: true } : {}),
133
+ ...(tc.providerMetadata != null
134
+ ? { providerMetadata: tc.providerMetadata }
135
+ : {}),
136
+ },
137
+ ] as const,
138
+ ],
139
+ ),
140
+ );
141
+ const validToolCalls = [...validToolCallsByIndex.values()];
142
+ const content: StepResult<ToolSet, any>['content'] = [];
143
+ const files: StepResult<ToolSet, any>['files'] = [];
144
+ const sources: StepResult<ToolSet, any>['sources'] = [];
145
+ let text = '';
146
+
147
+ for (const part of rawContent) {
148
+ switch (part.type) {
149
+ case 'text':
150
+ text += part.text;
151
+ content.push({
152
+ type: 'text',
153
+ text: part.text,
154
+ ...(part.providerMetadata != null
155
+ ? { providerMetadata: part.providerMetadata }
156
+ : {}),
157
+ });
158
+ break;
159
+ case 'reasoning':
160
+ if ('reasoningIndex' in part) {
161
+ const reasoningPart = reasoningParts[part.reasoningIndex];
162
+ if (reasoningPart != null) {
163
+ content.push({
164
+ type: 'reasoning',
165
+ text: reasoningPart.text,
166
+ ...(reasoningPart.providerMetadata != null
167
+ ? { providerMetadata: reasoningPart.providerMetadata }
168
+ : {}),
169
+ });
170
+ }
171
+ }
172
+ break;
173
+ case 'file': {
174
+ const file = new DefaultGeneratedFile({
175
+ data: part.data,
176
+ mediaType: part.mediaType,
177
+ providerMetadata: part.providerMetadata,
178
+ });
179
+ files.push(file);
180
+ content.push({
181
+ type: 'file',
182
+ file,
183
+ ...(part.providerMetadata != null
184
+ ? { providerMetadata: part.providerMetadata }
185
+ : {}),
186
+ });
187
+ break;
188
+ }
189
+ case 'source':
190
+ sources.push(part);
191
+ content.push(part);
192
+ break;
193
+ case 'tool-approval-request': {
194
+ const toolCall = validToolCalls.find(
195
+ call => call.toolCallId === part.toolCallId,
196
+ );
197
+ if (toolCall != null)
198
+ content.push({
199
+ type: 'tool-approval-request',
200
+ approvalId: part.approvalId,
201
+ toolCall: toolCall as StepResult<ToolSet>['toolCalls'][number],
202
+ });
203
+ break;
204
+ }
205
+ case 'tool-call': {
206
+ const toolCall = validToolCallsByIndex.get(part.toolCallIndex);
207
+ if (toolCall != null) {
208
+ content.push(toolCall);
209
+ }
210
+ break;
211
+ }
212
+ case 'provider-tool-result': {
213
+ const result = providerExecutedToolResults.get(part.toolCallId);
214
+ if (result == null) {
215
+ break;
216
+ }
217
+
218
+ const toolCall = toolCalls.find(
219
+ toolCall => toolCall.toolCallId === result.toolCallId,
220
+ );
221
+ const common = {
222
+ toolCallId: result.toolCallId,
223
+ toolName: result.toolName,
224
+ input: toolCall?.input,
225
+ providerExecuted: true as const,
226
+ ...(result.dynamic === true || toolCall?.dynamic === true
227
+ ? { dynamic: true as const }
228
+ : {}),
229
+ ...(result.providerMetadata != null
230
+ ? { providerMetadata: result.providerMetadata }
231
+ : {}),
232
+ ...(toolCall?.toolMetadata != null
233
+ ? { toolMetadata: toolCall.toolMetadata }
234
+ : {}),
235
+ };
236
+
237
+ content.push(
238
+ result.isError
239
+ ? {
240
+ type: 'tool-error',
241
+ ...common,
242
+ error: result.result,
243
+ }
244
+ : {
245
+ type: 'tool-result',
246
+ ...common,
247
+ output: result.result,
248
+ },
249
+ );
250
+ break;
251
+ }
252
+ }
253
+ }
254
+
255
+ const toolResults = content.filter(
256
+ part => part.type === 'tool-result',
257
+ ) as StepResult<ToolSet, any>['toolResults'];
258
+
259
+ return {
260
+ callId: 'workflow-agent',
261
+ stepNumber: opts.stepNumber,
262
+ model: {
263
+ provider: responseMetadata?.modelId?.split(':')[0] ?? 'unknown',
264
+ modelId: responseMetadata?.modelId ?? 'unknown',
265
+ },
266
+ functionId: undefined,
267
+ metadata: undefined,
268
+ runtimeContext: opts.runtimeContext ?? {},
269
+ toolsContext: opts.toolsContext ?? {},
270
+ content,
271
+ text,
272
+ reasoning: reasoningParts.map(r => ({
273
+ type: 'reasoning' as const,
274
+ text: r.text,
275
+ ...(r.providerMetadata != null
276
+ ? { providerMetadata: r.providerMetadata }
277
+ : {}),
278
+ })),
279
+ reasoningText,
280
+ files,
281
+ sources,
282
+ toolCalls: validToolCalls,
283
+ staticToolCalls: validToolCalls.filter(tc => tc.dynamic !== true),
284
+ dynamicToolCalls: validToolCalls.filter(tc => tc.dynamic),
285
+ toolResults,
286
+ staticToolResults: toolResults.filter(result => result.dynamic !== true),
287
+ dynamicToolResults: toolResults.filter(result => result.dynamic === true),
288
+ finishReason: finish?.finishReason ?? 'other',
289
+ rawFinishReason: finish?.rawFinishReason,
290
+ usage:
291
+ finish?.usage ??
292
+ ({
293
+ inputTokens: 0,
294
+ inputTokenDetails: {
295
+ noCacheTokens: undefined,
296
+ cacheReadTokens: undefined,
297
+ cacheWriteTokens: undefined,
298
+ },
299
+ outputTokens: 0,
300
+ outputTokenDetails: {
301
+ textTokens: undefined,
302
+ reasoningTokens: undefined,
303
+ },
304
+ totalTokens: 0,
305
+ } as LanguageModelUsage),
306
+ performance: {
307
+ effectiveOutputTokensPerSecond: 0,
308
+ outputTokensPerSecond: undefined,
309
+ inputTokensPerSecond: undefined,
310
+ effectiveTotalTokensPerSecond: 0,
311
+ stepTimeMs: 0,
312
+ responseTimeMs: 0,
313
+ toolExecutionMs: {},
314
+ timeToFirstOutputMs: undefined,
315
+ },
316
+ warnings,
317
+ request: {
318
+ body: '',
319
+ messages: [], // TODO implement step request messages
320
+ },
321
+ response: {
322
+ id: responseMetadata?.id ?? 'unknown',
323
+ timestamp: responseMetadata?.timestamp ?? new Date(),
324
+ modelId: responseMetadata?.modelId ?? 'unknown',
325
+ messages: [],
326
+ },
327
+ providerMetadata: finish?.providerMetadata ?? {},
328
+ } as StepResult<ToolSet, any>;
329
+ }
@@ -0,0 +1,212 @@
1
+ import type { LanguageModelV4Prompt } from '@ai-sdk/provider';
2
+ import { generateId, convertUint8ArrayToBase64 } from '@ai-sdk/provider-utils';
3
+ import {
4
+ ToolChoiceViolationError,
5
+ type LanguageModel,
6
+ type ModelMessage,
7
+ } from 'ai';
8
+ import {
9
+ asLanguageModelUsage,
10
+ parseToolCall,
11
+ prepareLanguageModelCallOptions,
12
+ prepareRetries,
13
+ prepareToolChoice,
14
+ prepareTools,
15
+ resolveLanguageModel,
16
+ } from 'ai/internal';
17
+ import type {
18
+ ModelCallOptions,
19
+ ModelCallRawContentPart,
20
+ ModelCallResult,
21
+ ProviderExecutedToolResult,
22
+ ToolInputLifecycleEvent,
23
+ } from './model-call.js';
24
+ import {
25
+ resolveSerializableTools,
26
+ type SerializableToolDef,
27
+ } from './serializable-schema.js';
28
+
29
+ /** A single durable provider call; orchestration and callbacks stay in the workflow. */
30
+ export async function doGenerateStep(
31
+ prompt: LanguageModelV4Prompt,
32
+ modelInit: LanguageModel,
33
+ serializedTools: Record<string, SerializableToolDef>,
34
+ options: ModelCallOptions = {},
35
+ ): Promise<ModelCallResult> {
36
+ 'use step';
37
+
38
+ try {
39
+ return await generateModelCall(prompt, modelInit, serializedTools, options);
40
+ } catch (error) {
41
+ // Carry failures as data so Workflow does not normalize arbitrary thrown
42
+ // values (including undefined) before the agent rejects in workflow code.
43
+ return {
44
+ toolCalls: [],
45
+ finish: undefined,
46
+ raw: { content: [], reasoning: [] },
47
+ providerExecutedToolResults: new Map(),
48
+ terminalError: error,
49
+ };
50
+ }
51
+ }
52
+
53
+ async function generateModelCall(
54
+ prompt: LanguageModelV4Prompt,
55
+ modelInit: LanguageModel,
56
+ serializedTools: Record<string, SerializableToolDef>,
57
+ options: ModelCallOptions,
58
+ ): Promise<ModelCallResult> {
59
+ const remaining =
60
+ options.timeoutAt == null ? undefined : options.timeoutAt - Date.now();
61
+ options.abortSignal?.throwIfAborted();
62
+ if (remaining != null && remaining <= 0) {
63
+ throw new DOMException('The generation deadline expired.', 'TimeoutError');
64
+ }
65
+ const abortSignal =
66
+ remaining == null
67
+ ? options.abortSignal
68
+ : AbortSignal.any([
69
+ ...(options.abortSignal == null ? [] : [options.abortSignal]),
70
+ AbortSignal.timeout(remaining),
71
+ ]);
72
+ const model = resolveLanguageModel(modelInit);
73
+ const tools = resolveSerializableTools(serializedTools);
74
+ const toolChoice = prepareToolChoice({ toolChoice: options.toolChoice });
75
+ const modelTools = await prepareTools({ tools });
76
+ const settings = prepareLanguageModelCallOptions(options);
77
+ const { retry } = prepareRetries({
78
+ maxRetries: options.maxRetries,
79
+ abortSignal,
80
+ });
81
+ const start = Date.now();
82
+ const response = await retry(async () => {
83
+ abortSignal?.throwIfAborted();
84
+ if (options.timeoutAt != null && options.timeoutAt <= Date.now())
85
+ throw new DOMException(
86
+ 'The generation deadline expired.',
87
+ 'TimeoutError',
88
+ );
89
+ return model.doGenerate({
90
+ ...settings,
91
+ prompt: [...prompt],
92
+ tools: modelTools,
93
+ toolChoice,
94
+ responseFormat: options.responseFormat,
95
+ providerOptions: options.providerOptions,
96
+ headers: options.headers,
97
+ abortSignal,
98
+ });
99
+ });
100
+ abortSignal?.throwIfAborted();
101
+ const responseTimeMs = Date.now() - start;
102
+ const toolCalls = await Promise.all(
103
+ response.content
104
+ .filter(part => part.type === 'tool-call')
105
+ .map(toolCall =>
106
+ parseToolCall({
107
+ toolCall,
108
+ tools,
109
+ repairToolCall: options.repairToolCall,
110
+ instructions:
111
+ prompt
112
+ .filter(message => message.role === 'system')
113
+ .map(message => message.content)
114
+ .join('\n') || undefined,
115
+ messages: prompt.filter(
116
+ message => message.role !== 'system',
117
+ ) as unknown as ModelMessage[],
118
+ }),
119
+ ),
120
+ );
121
+ if (
122
+ (toolChoice.type === 'required' || toolChoice.type === 'tool') &&
123
+ !toolCalls.some(
124
+ call =>
125
+ toolChoice.type === 'required' || call.toolName === toolChoice.toolName,
126
+ )
127
+ ) {
128
+ throw new ToolChoiceViolationError({
129
+ toolChoice,
130
+ finishReason: response.finishReason.unified,
131
+ provider: model.provider,
132
+ modelId: model.modelId,
133
+ content: response.content,
134
+ });
135
+ }
136
+ const providerExecutedToolResults = new Map<
137
+ string,
138
+ ProviderExecutedToolResult
139
+ >();
140
+ const content: ModelCallRawContentPart[] = response.content.map(part => {
141
+ switch (part.type) {
142
+ case 'tool-call':
143
+ return {
144
+ type: 'tool-call',
145
+ toolCallIndex: toolCalls.findIndex(
146
+ call => call.toolCallId === part.toolCallId,
147
+ ),
148
+ };
149
+ case 'tool-result':
150
+ providerExecutedToolResults.set(part.toolCallId, part);
151
+ return { type: 'provider-tool-result', toolCallId: part.toolCallId };
152
+ case 'file':
153
+ return {
154
+ type: 'file',
155
+ data:
156
+ part.data.type === 'data'
157
+ ? typeof part.data.data === 'string'
158
+ ? part.data.data
159
+ : convertUint8ArrayToBase64(part.data.data)
160
+ : part.data.url.toString(),
161
+ mediaType: part.mediaType,
162
+ providerMetadata: part.providerMetadata,
163
+ };
164
+ default:
165
+ return part;
166
+ }
167
+ });
168
+ const toolInputLifecycleEvents: ToolInputLifecycleEvent[] = toolCalls.flatMap(
169
+ call => {
170
+ const tool = serializedTools[call.toolName];
171
+ if (call.invalid || tool == null) return [];
172
+ const events: ToolInputLifecycleEvent[] = [];
173
+ if (tool.hasOnInputStart || tool.hasOnInputAvailable)
174
+ events.push(['start', call.toolCallId, call.toolName]);
175
+ if (tool.hasOnInputAvailable) events.push(['available', call.toolCallId]);
176
+ return events;
177
+ },
178
+ );
179
+ return {
180
+ toolCalls,
181
+ finish: {
182
+ finishReason: response.finishReason.unified,
183
+ rawFinishReason: response.finishReason.raw,
184
+ usage: asLanguageModelUsage(response.usage),
185
+ providerMetadata: response.providerMetadata,
186
+ },
187
+ raw: {
188
+ content,
189
+ reasoning: [],
190
+ warnings: response.warnings,
191
+ responseMetadata: {
192
+ id: response.response?.id ?? generateId(),
193
+ timestamp: response.response?.timestamp ?? new Date(),
194
+ modelId: response.response?.modelId ?? model.modelId,
195
+ headers: response.response?.headers,
196
+ ...(options.include?.responseBody
197
+ ? { body: response.response?.body }
198
+ : {}),
199
+ },
200
+ generation: {
201
+ provider: model.provider,
202
+ modelId: model.modelId,
203
+ responseTimeMs,
204
+ request: options.include?.requestBody ? response.request : undefined,
205
+ },
206
+ },
207
+ providerExecutedToolResults,
208
+ toolInputLifecycleEvents,
209
+ };
210
+ }
211
+
212
+ doGenerateStep.maxRetries = 0;