@ai-sdk/workflow 2.0.70 → 2.0.72

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.70",
3
+ "version": "2.0.72",
4
4
  "type": "module",
5
5
  "description": "WorkflowAgent for building AI agents with AI SDK",
6
6
  "license": "Apache-2.0",
@@ -38,11 +38,11 @@
38
38
  "dependencies": {
39
39
  "@ai-sdk/provider": "4.0.27",
40
40
  "@ai-sdk/provider-utils": "5.0.59",
41
- "ai": "7.0.138",
41
+ "ai": "7.0.140",
42
42
  "ajv": "^8.20.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@ai-sdk/otel": "1.0.138",
45
+ "@ai-sdk/otel": "1.0.140",
46
46
  "@opentelemetry/api": "1.9.1",
47
47
  "@opentelemetry/sdk-trace-base": "2.7.1",
48
48
  "@types/node": "22.19.19",
@@ -7,6 +7,7 @@ import {
7
7
  } from 'ai';
8
8
  import {
9
9
  asLanguageModelUsage,
10
+ convertToLanguageModelPrompt,
10
11
  parseToolCall,
11
12
  prepareLanguageModelCallOptions,
12
13
  prepareRetries,
@@ -14,6 +15,7 @@ import {
14
15
  prepareTools,
15
16
  resolveLanguageModel,
16
17
  } from 'ai/internal';
18
+ import { languageModelPromptToModelMessages } from './language-model-prompt-to-model-messages.js';
17
19
  import type {
18
20
  ModelCallOptions,
19
21
  ModelCallRawContentPart,
@@ -90,6 +92,16 @@ async function generateModelCall(
90
92
  AbortSignal.timeout(remaining),
91
93
  ]);
92
94
  const model = resolveLanguageModel(modelInit);
95
+ const modelPrompt = await convertToLanguageModelPrompt({
96
+ prompt: {
97
+ instructions: undefined,
98
+ messages: languageModelPromptToModelMessages(prompt),
99
+ },
100
+ supportedUrls: await model.supportedUrls,
101
+ download: options.download,
102
+ abortSignal,
103
+ provider: model.provider.split('.')[0],
104
+ });
93
105
  const tools = resolveSerializableTools(serializedTools);
94
106
  const toolChoice = prepareToolChoice({ toolChoice: options.toolChoice });
95
107
  const modelTools = await prepareTools({ tools });
@@ -125,7 +137,7 @@ async function generateModelCall(
125
137
  execute: () =>
126
138
  model.doGenerate({
127
139
  ...settings,
128
- prompt: [...prompt],
140
+ prompt: modelPrompt,
129
141
  tools: modelTools,
130
142
  toolChoice,
131
143
  responseFormat: options.responseFormat,
@@ -159,7 +171,7 @@ async function generateModelCall(
159
171
  tools,
160
172
  repairToolCall: options.repairToolCall,
161
173
  instructions:
162
- prompt
174
+ modelPrompt
163
175
  .filter(message => message.role === 'system')
164
176
  .map(message => message.content)
165
177
  .join('\n') || undefined,
@@ -10,10 +10,10 @@ import {
10
10
  NoOutputGeneratedError,
11
11
  wrapLanguageModel,
12
12
  type LanguageModel,
13
- type ModelMessage,
14
13
  type ToolSet,
15
14
  } from 'ai';
16
15
  import { isOutputChunk, prepareRetries } from 'ai/internal';
16
+ import { languageModelPromptToModelMessages } from './language-model-prompt-to-model-messages.js';
17
17
  import type { StreamTextTransform } from './workflow-agent.js';
18
18
  import {
19
19
  resolveSerializableTools,
@@ -178,21 +178,7 @@ async function streamModelStep(
178
178
  // streamModelCall expects Prompt (ModelMessage[]) but we pass the
179
179
  // pre-converted LanguageModelV4Prompt. standardizePrompt inside
180
180
  // streamModelCall handles both formats.
181
- messages: conversationPrompt.map(message =>
182
- message.role !== 'tool'
183
- ? message
184
- : {
185
- ...message,
186
- // Provider prompt approval responses have already been filtered by
187
- // convertToLanguageModelPrompt. Restore the marker expected by the
188
- // model-call helper when it converts these messages again.
189
- content: message.content.map(part =>
190
- part.type === 'tool-approval-response'
191
- ? { ...part, providerExecuted: true }
192
- : part,
193
- ),
194
- },
195
- ) as unknown as ModelMessage[],
181
+ messages: languageModelPromptToModelMessages(conversationPrompt),
196
182
  allowSystemInMessages: true,
197
183
  tools,
198
184
  toolChoice: options?.toolChoice,
@@ -211,6 +197,7 @@ async function streamModelStep(
211
197
  stopSequences: options?.stopSequences,
212
198
  seed: options?.seed,
213
199
  repairToolCall: options?.repairToolCall,
200
+ download: options?.download,
214
201
  ...languageModelCallTelemetry,
215
202
  }),
216
203
  );
@@ -0,0 +1,28 @@
1
+ import type { LanguageModelV4Prompt } from '@ai-sdk/provider';
2
+ import type { ModelMessage } from 'ai';
3
+
4
+ /**
5
+ * Prepares a workflow-side `LanguageModelV4Prompt` for a second, model-aware
6
+ * `convertToLanguageModelPrompt` pass inside a model step.
7
+ *
8
+ * The workflow-side conversion preserves URLs (it cannot download them) and
9
+ * filters out approval responses for tools that are not provider-executed.
10
+ * The remaining approval responses are restored with the `providerExecuted`
11
+ * marker that the second conversion expects.
12
+ */
13
+ export function languageModelPromptToModelMessages(
14
+ prompt: LanguageModelV4Prompt,
15
+ ): ModelMessage[] {
16
+ return prompt.map(message =>
17
+ message.role !== 'tool'
18
+ ? message
19
+ : {
20
+ ...message,
21
+ content: message.content.map(part =>
22
+ part.type === 'tool-approval-response'
23
+ ? { ...part, providerExecuted: true }
24
+ : part,
25
+ ),
26
+ },
27
+ ) as unknown as ModelMessage[];
28
+ }
@@ -143,6 +143,7 @@ export async function* modelCallIterator({
143
143
  includeRawChunks = false,
144
144
  timeoutAt,
145
145
  repairToolCall,
146
+ download,
146
147
  responseFormat,
147
148
  experimental_transform,
148
149
  experimental_sandbox: sandbox,
@@ -170,6 +171,7 @@ export async function* modelCallIterator({
170
171
  includeRawChunks?: boolean;
171
172
  timeoutAt?: number;
172
173
  repairToolCall?: ToolCallRepairFunction<ToolSet>;
174
+ download?: ModelCallOptions['download'];
173
175
  responseFormat?: LanguageModelV4CallOptions['responseFormat'];
174
176
  experimental_transform?:
175
177
  | StreamTextTransform<ToolSet>
@@ -380,6 +382,7 @@ export async function* modelCallIterator({
380
382
  includeRawChunks,
381
383
  timeoutAt,
382
384
  repairToolCall,
385
+ download,
383
386
  responseFormat,
384
387
  include,
385
388
  experimental_transform,
package/src/model-call.ts CHANGED
@@ -15,7 +15,11 @@ import type {
15
15
  ToolChoice,
16
16
  ToolSet,
17
17
  } from 'ai';
18
- import type { ProviderOptions, StreamTextTransform } from './workflow-agent.js';
18
+ import type {
19
+ DownloadFunction,
20
+ ProviderOptions,
21
+ StreamTextTransform,
22
+ } from './workflow-agent.js';
19
23
 
20
24
  export type ModelCallStreamPart<TTools extends ToolSet = ToolSet> =
21
25
  | Experimental_LanguageModelStreamPart<TTools>
@@ -62,6 +66,7 @@ export interface ModelCallOptions {
62
66
  toolChoice?: ToolChoice<ToolSet>;
63
67
  includeRawChunks?: boolean;
64
68
  repairToolCall?: ToolCallRepairFunction<ToolSet>;
69
+ download?: DownloadFunction;
65
70
  include?: {
66
71
  requestBody?: boolean;
67
72
  responseBody?: boolean;
@@ -0,0 +1,66 @@
1
+ import { tool, type ModelMessage } from 'ai';
2
+ import { createHook } from 'workflow';
3
+ import { z } from 'zod/v4';
4
+ import { mockTextModel } from '../providers/mock.js';
5
+ import { WorkflowAgent } from '../workflow-agent.js';
6
+
7
+ async function completeAction(id: string) {
8
+ 'use step';
9
+ return id;
10
+ }
11
+
12
+ export async function resumeSequentialApprovals(mode: 'generate' | 'stream') {
13
+ 'use workflow';
14
+ const events: string[] = [];
15
+ const agent = new WorkflowAgent({
16
+ model: mockTextModel('Done.'),
17
+ toolCallConcurrency: 1,
18
+ tools: {
19
+ action: tool({
20
+ inputSchema: z.object({ id: z.string() }),
21
+ needsApproval: true,
22
+ execute: async ({ id }) => {
23
+ events.push(`start:${id}`);
24
+ if (id === 'first') {
25
+ using hook = createHook<string>();
26
+ await hook;
27
+ }
28
+ const result = await completeAction(id);
29
+ events.push(`end:${id}`);
30
+ return result;
31
+ },
32
+ }),
33
+ },
34
+ });
35
+ const ids = ['first', 'second'];
36
+ const messages: ModelMessage[] = [
37
+ { role: 'user', content: 'Run the tools.' },
38
+ {
39
+ role: 'assistant',
40
+ content: ids.flatMap(id => [
41
+ {
42
+ type: 'tool-call' as const,
43
+ toolCallId: id,
44
+ toolName: 'action',
45
+ input: { id },
46
+ },
47
+ {
48
+ type: 'tool-approval-request' as const,
49
+ toolCallId: id,
50
+ approvalId: `approval-${id}`,
51
+ },
52
+ ]),
53
+ },
54
+ {
55
+ role: 'tool',
56
+ content: [...ids].reverse().map(id => ({
57
+ type: 'tool-approval-response' as const,
58
+ approvalId: `approval-${id}`,
59
+ approved: true,
60
+ })),
61
+ },
62
+ ];
63
+ if (mode === 'generate') await agent.generate({ messages });
64
+ else await agent.stream({ messages });
65
+ return events;
66
+ }