@ai-sdk/workflow 2.0.59 → 2.0.61

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.59",
3
+ "version": "2.0.61",
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.22",
40
- "@ai-sdk/provider-utils": "5.0.54",
41
- "ai": "7.0.128",
39
+ "@ai-sdk/provider": "4.0.23",
40
+ "@ai-sdk/provider-utils": "5.0.55",
41
+ "ai": "7.0.129",
42
42
  "ajv": "^8.20.0"
43
43
  },
44
44
  "devDependencies": {
@@ -46,7 +46,7 @@
46
46
  "@vercel/ai-tsconfig": "0.0.0",
47
47
  "@workflow/vitest": "5.0.0-beta.42",
48
48
  "tsdown": "^0.23.0",
49
- "typescript": "5.8.3",
49
+ "typescript": "6.0.3",
50
50
  "vitest": "4.1.6",
51
51
  "workflow": "5.0.0-beta.42",
52
52
  "zod": "4.4.3"
@@ -105,6 +105,7 @@ export async function buildModelStepResult(
105
105
  const {
106
106
  content: rawContent,
107
107
  reasoning: reasoningParts,
108
+ performance: modelCallPerformance,
108
109
  responseMetadata,
109
110
  warnings,
110
111
  } = raw;
@@ -303,16 +304,23 @@ export async function buildModelStepResult(
303
304
  },
304
305
  totalTokens: 0,
305
306
  } 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
- },
307
+ performance:
308
+ modelCallPerformance == null
309
+ ? {
310
+ effectiveOutputTokensPerSecond: 0,
311
+ outputTokensPerSecond: undefined,
312
+ inputTokensPerSecond: undefined,
313
+ effectiveTotalTokensPerSecond: 0,
314
+ stepTimeMs: 0,
315
+ responseTimeMs: 0,
316
+ toolExecutionMs: {},
317
+ timeToFirstOutputMs: undefined,
318
+ }
319
+ : {
320
+ ...modelCallPerformance,
321
+ stepTimeMs: modelCallPerformance.responseTimeMs,
322
+ toolExecutionMs: {},
323
+ },
316
324
  warnings,
317
325
  request: {
318
326
  body: '',
@@ -22,6 +22,7 @@ import {
22
22
  import type {
23
23
  ModelCallFinish as StreamFinish,
24
24
  ModelCallOptions as DoStreamStepOptions,
25
+ ModelCallPerformance,
25
26
  ModelCallRawContentPart as DoStreamStepRawContentPart,
26
27
  ModelCallResult as DoStreamStepResult,
27
28
  ModelCallStreamPart,
@@ -212,6 +213,7 @@ export async function doStreamStep(
212
213
  | { id?: string; timestamp?: Date; modelId?: string }
213
214
  | undefined;
214
215
  let warnings: unknown[] | undefined;
216
+ let performance: ModelCallPerformance | undefined;
215
217
  let terminalError: unknown;
216
218
  let hasTerminalError = false;
217
219
  const ongoingToolCallToolNames = new Map<string, string>();
@@ -400,6 +402,7 @@ export async function doStreamStep(
400
402
  | Record<string, unknown>
401
403
  | undefined,
402
404
  };
405
+ performance = part.performance;
403
406
  break;
404
407
  case 'model-call-start':
405
408
  warnings = part.warnings;
@@ -456,6 +459,7 @@ export async function doStreamStep(
456
459
  content,
457
460
  reasoning: reasoningParts,
458
461
  responseMetadata,
462
+ performance,
459
463
  warnings,
460
464
  },
461
465
  providerExecutedToolResults,
package/src/model-call.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  Experimental_LanguageModelStreamPart,
10
10
  FinishReason,
11
11
  LanguageModelUsage,
12
+ StepResultPerformance,
12
13
  StopCondition,
13
14
  ToolCallRepairFunction,
14
15
  ToolChoice,
@@ -99,6 +100,11 @@ export interface ModelCallFinish {
99
100
  providerMetadata?: Record<string, unknown>;
100
101
  }
101
102
 
103
+ export type ModelCallPerformance = Omit<
104
+ StepResultPerformance,
105
+ 'stepTimeMs' | 'toolExecutionMs'
106
+ >;
107
+
102
108
  export type ModelCallRawContentPart =
103
109
  | Extract<
104
110
  LanguageModelV4Content,
@@ -175,6 +181,8 @@ export interface ModelCallRawResult {
175
181
  request?: { body?: unknown };
176
182
  responseTimeMs: number;
177
183
  };
184
+ /** Present on streamed results produced after performance tracking was added. */
185
+ performance?: ModelCallPerformance;
178
186
  warnings?: unknown[];
179
187
  }
180
188
 
@@ -20,6 +20,7 @@ import {
20
20
  } from '@ai-sdk/provider-utils';
21
21
  import { dynamicTool, tool, type ToolSet } from 'ai';
22
22
  import Ajv from 'ajv';
23
+ import Ajv2020 from 'ajv/dist/2020.js';
23
24
 
24
25
  /**
25
26
  * Serializable tool definition — plain objects only, safe for workflow steps.
@@ -142,7 +143,36 @@ function resolveToolDescription<TOOLS extends ToolSet>({
142
143
  export function resolveSerializableTools(
143
144
  tools: Record<string, SerializableToolDef>,
144
145
  ): ToolSet {
145
- const ajv = new Ajv();
146
+ const ajvOptions = {
147
+ strict: false,
148
+ validateFormats: false,
149
+ } as const;
150
+ const ajv = new Ajv(ajvOptions);
151
+ const ajv2020 = new Ajv2020(ajvOptions);
152
+
153
+ const createValidatedInputSchema = (inputSchema: JSONSchema7) => {
154
+ const schemaDialect =
155
+ typeof inputSchema.$schema === 'string'
156
+ ? inputSchema.$schema.replace(/#$/, '')
157
+ : undefined;
158
+ const schemaAjv =
159
+ schemaDialect === 'https://json-schema.org/draft/2020-12/schema'
160
+ ? ajv2020
161
+ : ajv;
162
+ const validateFn = schemaAjv.compile(inputSchema);
163
+
164
+ return jsonSchema(inputSchema, {
165
+ validate: value => {
166
+ if (validateFn(value)) {
167
+ return { success: true, value: value as any };
168
+ }
169
+ return {
170
+ success: false,
171
+ error: new Error(schemaAjv.errorsText(validateFn.errors)),
172
+ };
173
+ },
174
+ });
175
+ };
146
176
 
147
177
  return Object.fromEntries(
148
178
  Object.entries(tools).map(([name, t]) => {
@@ -175,30 +205,17 @@ export function resolveSerializableTools(
175
205
  }
176
206
 
177
207
  if (t.type === 'dynamic') {
178
- const validateFn = ajv.compile(t.inputSchema);
179
-
180
208
  return [
181
209
  name,
182
210
  dynamicTool({
183
211
  description: t.description,
184
212
  inputExamples: t.inputExamples,
185
213
  providerOptions: t.providerOptions,
186
- inputSchema: jsonSchema(t.inputSchema, {
187
- validate: value => {
188
- if (validateFn(value)) {
189
- return { success: true, value };
190
- }
191
- return {
192
- success: false,
193
- error: new Error(ajv.errorsText(validateFn.errors)),
194
- };
195
- },
196
- }),
214
+ inputSchema: createValidatedInputSchema(t.inputSchema),
197
215
  }),
198
216
  ];
199
217
  }
200
218
 
201
- const validateFn = ajv.compile(t.inputSchema);
202
219
  const functionTool = {
203
220
  title: t.title,
204
221
  metadata: t.metadata,
@@ -206,17 +223,7 @@ export function resolveSerializableTools(
206
223
  strict: t.strict,
207
224
  inputExamples: t.inputExamples,
208
225
  providerOptions: t.providerOptions,
209
- inputSchema: jsonSchema(t.inputSchema, {
210
- validate: value => {
211
- if (validateFn(value)) {
212
- return { success: true, value: value as any };
213
- }
214
- return {
215
- success: false,
216
- error: new Error(ajv.errorsText(validateFn.errors)),
217
- };
218
- },
219
- }),
226
+ inputSchema: createValidatedInputSchema(t.inputSchema),
220
227
  };
221
228
 
222
229
  return [
@@ -1950,6 +1950,7 @@ export class WorkflowAgent<
1950
1950
  input: collected.toolCall.input,
1951
1951
  reason: collected.approvalResponse.reason,
1952
1952
  providerExecuted: collected.toolCall.providerExecuted === true,
1953
+ existingToolResult: collected.existingToolResult,
1953
1954
  }),
1954
1955
  );
1955
1956
 
@@ -2092,7 +2093,10 @@ export class WorkflowAgent<
2092
2093
  for (const denial of deniedToolApprovals) {
2093
2094
  // Provider-executed denials are forwarded to the provider via the
2094
2095
  // preserved approval response below, not turned into a local result.
2095
- if (denial.providerExecuted) {
2096
+ if (
2097
+ denial.providerExecuted ||
2098
+ denial.existingToolResult !== undefined
2099
+ ) {
2096
2100
  continue;
2097
2101
  }
2098
2102
  toolResultContent.push({
@@ -2161,10 +2165,13 @@ export class WorkflowAgent<
2161
2165
  // Write tool results and step boundaries to the stream so the UI
2162
2166
  // can transition approved/denied tool parts to the correct state
2163
2167
  // and properly separate them from the subsequent model step.
2164
- if (options.writable && toolResultContent.length > 0) {
2165
- const deniedResults = toolResultContent
2166
- .filter(r => r.output.type === 'execution-denied')
2167
- .map(r => ({ toolCallId: r.toolCallId }));
2168
+ const deniedResults = deniedToolApprovals
2169
+ .filter(denial => !denial.providerExecuted)
2170
+ .map(denial => ({ toolCallId: denial.toolCallId }));
2171
+ if (
2172
+ options.writable &&
2173
+ (toolResultContent.length > 0 || deniedResults.length > 0)
2174
+ ) {
2168
2175
  await writeApprovalToolResults(
2169
2176
  options.writable,
2170
2177
  approvedRawResults,