@ai-sdk/workflow 2.0.20 → 2.0.21

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.
@@ -8,7 +8,6 @@ import type {
8
8
  import {
9
9
  getErrorMessage,
10
10
  isAbortError,
11
- validateTypes,
12
11
  withUserAgentSuffix,
13
12
  type Context,
14
13
  type HasRequiredKey,
@@ -53,6 +52,7 @@ import type {
53
52
  ModelCallStreamPart,
54
53
  ModelStopCondition,
55
54
  } from './do-stream-step.js';
55
+ import { resolveToolContext } from './resolve-tool-context.js';
56
56
  import { streamTextIterator } from './stream-text-iterator.js';
57
57
 
58
58
  // Re-export for consumers
@@ -83,7 +83,9 @@ export type WorkflowAgentOnStepFinishCallback<
83
83
  * Infer the type of the tools of a workflow agent.
84
84
  */
85
85
  export type InferWorkflowAgentTools<WORKFLOW_AGENT> =
86
- WORKFLOW_AGENT extends WorkflowAgent<infer TOOLS, any> ? TOOLS : never;
86
+ WORKFLOW_AGENT extends WorkflowAgent<infer TOOLS, any, any, any>
87
+ ? TOOLS
88
+ : never;
87
89
 
88
90
  /**
89
91
  * Infer the UI message type of a workflow agent.
@@ -123,6 +125,8 @@ export interface OutputSpecification<OUTPUT, PARTIAL> {
123
125
  ): Promise<OUTPUT>;
124
126
  }
125
127
 
128
+ type DefaultWorkflowAgentOutput<OUTPUT> = 0 extends 1 & OUTPUT ? never : OUTPUT;
129
+
126
130
  /**
127
131
  * Provider-specific options type. This is equivalent to SharedV4ProviderOptions from @ai-sdk/provider.
128
132
  */
@@ -450,6 +454,8 @@ export type PrepareCallCallback<
450
454
  export type WorkflowAgentOptions<
451
455
  TTools extends ToolSet = ToolSet,
452
456
  TRuntimeContext extends Context = Context,
457
+ OUTPUT = any,
458
+ PARTIAL_OUTPUT = any,
453
459
  > = GenerationSettings &
454
460
  WorkflowAgentToolsContextParameter<TTools> & {
455
461
  /**
@@ -533,7 +539,7 @@ export type WorkflowAgentOptions<
533
539
  *
534
540
  * Per-stream `output` values passed to `stream()` override this default.
535
541
  */
536
- output?: OutputSpecification<any, any>;
542
+ output?: OutputSpecification<OUTPUT, PARTIAL_OUTPUT>;
537
543
 
538
544
  /**
539
545
  * Default function that attempts to repair a tool call that failed to parse.
@@ -1216,6 +1222,64 @@ type WorkflowToolExecutionResult = {
1216
1222
  isError: boolean;
1217
1223
  };
1218
1224
 
1225
+ function addToolResultsToStep(
1226
+ step: StepResult<ToolSet, any> | undefined,
1227
+ executedResults: WorkflowToolExecutionResult[],
1228
+ ) {
1229
+ if (step == null || executedResults.length === 0) {
1230
+ return;
1231
+ }
1232
+
1233
+ const toolOutputs = executedResults.map(result => {
1234
+ const toolCall = step.toolCalls.find(
1235
+ toolCall => toolCall.toolCallId === result.modelResult.toolCallId,
1236
+ );
1237
+
1238
+ const common = {
1239
+ toolCallId: result.modelResult.toolCallId,
1240
+ toolName: result.modelResult.toolName,
1241
+ input: toolCall?.input,
1242
+ ...(toolCall?.dynamic === true ? { dynamic: true as const } : {}),
1243
+ ...(toolCall?.providerExecuted === true
1244
+ ? { providerExecuted: true }
1245
+ : {}),
1246
+ };
1247
+
1248
+ return result.isError
1249
+ ? {
1250
+ type: 'tool-error' as const,
1251
+ ...common,
1252
+ error: result.rawOutput,
1253
+ }
1254
+ : {
1255
+ type: 'tool-result' as const,
1256
+ ...common,
1257
+ output: result.rawOutput,
1258
+ };
1259
+ });
1260
+
1261
+ step.content.push(...(toolOutputs as StepResult<ToolSet, any>['content']));
1262
+
1263
+ const toolResults = toolOutputs.filter(
1264
+ result => result.type === 'tool-result',
1265
+ );
1266
+ step.toolResults.push(
1267
+ ...(toolResults as StepResult<ToolSet, any>['toolResults']),
1268
+ );
1269
+ step.staticToolResults.push(
1270
+ ...(toolResults.filter(result => result.dynamic !== true) as StepResult<
1271
+ ToolSet,
1272
+ any
1273
+ >['staticToolResults']),
1274
+ );
1275
+ step.dynamicToolResults.push(
1276
+ ...(toolResults.filter(result => result.dynamic === true) as StepResult<
1277
+ ToolSet,
1278
+ any
1279
+ >['dynamicToolResults']),
1280
+ );
1281
+ }
1282
+
1219
1283
  /**
1220
1284
  * Result of the WorkflowAgent.stream method.
1221
1285
  */
@@ -1316,6 +1380,8 @@ export interface WorkflowAgentStreamResult<
1316
1380
  export class WorkflowAgent<
1317
1381
  TBaseTools extends ToolSet = ToolSet,
1318
1382
  TRuntimeContext extends Context = Context,
1383
+ OUTPUT = any,
1384
+ PARTIAL_OUTPUT = any,
1319
1385
  > {
1320
1386
  /**
1321
1387
  * The id of the agent.
@@ -1337,7 +1403,7 @@ export class WorkflowAgent<
1337
1403
  | StopCondition<TBaseTools, TRuntimeContext>
1338
1404
  | Array<StopCondition<TBaseTools, TRuntimeContext>>;
1339
1405
  private activeTools?: ActiveTools<TBaseTools>;
1340
- private output?: OutputSpecification<any, any>;
1406
+ private output?: OutputSpecification<OUTPUT, PARTIAL_OUTPUT>;
1341
1407
  private repairToolCall?: ToolCallRepairFunction<TBaseTools>;
1342
1408
  private experimentalDownload?: DownloadFunction;
1343
1409
  private experimentalSandbox?: SandboxSession;
@@ -1364,7 +1430,14 @@ export class WorkflowAgent<
1364
1430
  private constructorOnToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback<TBaseTools>;
1365
1431
  private prepareCall?: PrepareCallCallback<TBaseTools, TRuntimeContext>;
1366
1432
 
1367
- constructor(options: WorkflowAgentOptions<TBaseTools, TRuntimeContext>) {
1433
+ constructor(
1434
+ options: WorkflowAgentOptions<
1435
+ TBaseTools,
1436
+ TRuntimeContext,
1437
+ OUTPUT,
1438
+ PARTIAL_OUTPUT
1439
+ >,
1440
+ ) {
1368
1441
  this.id = options.id;
1369
1442
  this.model = options.model;
1370
1443
  this.tools = (options.tools ?? {}) as TBaseTools;
@@ -1419,16 +1492,16 @@ export class WorkflowAgent<
1419
1492
 
1420
1493
  async stream<
1421
1494
  TTools extends TBaseTools = TBaseTools,
1422
- OUTPUT = never,
1423
- PARTIAL_OUTPUT = never,
1495
+ TOutput = DefaultWorkflowAgentOutput<OUTPUT>,
1496
+ TPartialOutput = DefaultWorkflowAgentOutput<PARTIAL_OUTPUT>,
1424
1497
  >(
1425
1498
  options: WorkflowAgentStreamOptions<
1426
1499
  TTools,
1427
1500
  TRuntimeContext,
1428
- OUTPUT,
1429
- PARTIAL_OUTPUT
1501
+ TOutput,
1502
+ TPartialOutput
1430
1503
  >,
1431
- ): Promise<WorkflowAgentStreamResult<TTools, OUTPUT>> {
1504
+ ): Promise<WorkflowAgentStreamResult<TTools, TOutput>> {
1432
1505
  const { onFinish, onEnd = onFinish } = options;
1433
1506
 
1434
1507
  // Call prepareCall to transform parameters before the agent loop
@@ -1872,7 +1945,7 @@ export class WorkflowAgent<
1872
1945
  );
1873
1946
  const mergedOnEnd = mergeCallbacks(
1874
1947
  this.constructorOnEnd as
1875
- | WorkflowAgentOnEndCallback<TTools, TRuntimeContext, OUTPUT>
1948
+ | WorkflowAgentOnEndCallback<TTools, TRuntimeContext, TOutput>
1876
1949
  | undefined,
1877
1950
  onEnd,
1878
1951
  );
@@ -2152,7 +2225,7 @@ export class WorkflowAgent<
2152
2225
  toolResults: [],
2153
2226
  finishReason: 'other',
2154
2227
  totalUsage: aggregateUsage(steps),
2155
- output: undefined as OUTPUT,
2228
+ output: undefined as TOutput,
2156
2229
  };
2157
2230
  }
2158
2231
 
@@ -2303,27 +2376,34 @@ export class WorkflowAgent<
2303
2376
  );
2304
2377
 
2305
2378
  // Collect provider tool results
2306
- const providerResults: WorkflowToolExecutionResult[] =
2307
- await Promise.all(
2308
- providerToolCalls.map(toolCall =>
2309
- resolveProviderToolResult(
2310
- toolCall,
2311
- providerExecutedToolResults,
2312
- effectiveTools as ToolSet,
2313
- download,
2314
- ),
2315
- ),
2316
- );
2317
- await Promise.all(
2318
- providerToolCalls.map((toolCall, index) =>
2319
- recordProviderExecutedToolTelemetry(
2379
+ const providerResultEntries = await Promise.all(
2380
+ providerToolCalls.map(async toolCall => ({
2381
+ toolCall,
2382
+ result: await resolveProviderToolResult(
2320
2383
  toolCall,
2321
- providerResults[index],
2322
- iterMessages,
2323
- currentStepNumber,
2384
+ providerExecutedToolResults,
2385
+ effectiveTools as ToolSet,
2386
+ download,
2324
2387
  ),
2388
+ })),
2389
+ );
2390
+ await Promise.all(
2391
+ providerResultEntries.flatMap(({ toolCall, result }) =>
2392
+ result == null
2393
+ ? []
2394
+ : [
2395
+ recordProviderExecutedToolTelemetry(
2396
+ toolCall,
2397
+ result,
2398
+ iterMessages,
2399
+ currentStepNumber,
2400
+ ),
2401
+ ],
2325
2402
  ),
2326
2403
  );
2404
+ const providerResults = providerResultEntries.flatMap(
2405
+ ({ result }) => (result == null ? [] : [result]),
2406
+ );
2327
2407
 
2328
2408
  const continuationInvalidResults = invalidToolCalls.map(
2329
2409
  createInvalidToolResult,
@@ -2352,6 +2432,8 @@ export class WorkflowAgent<
2352
2432
  output: r.rawOutput,
2353
2433
  }));
2354
2434
 
2435
+ addToolResultsToStep(step, executedResults);
2436
+
2355
2437
  if (resolvedResults.length > 0) {
2356
2438
  iterMessages.push({
2357
2439
  role: 'tool',
@@ -2375,7 +2457,7 @@ export class WorkflowAgent<
2375
2457
  runtimeContext,
2376
2458
  toolsContext:
2377
2459
  toolsContext as unknown as InferToolSetContext<TTools>,
2378
- output: undefined as OUTPUT,
2460
+ output: undefined as TOutput,
2379
2461
  });
2380
2462
  }
2381
2463
  if (!wasAborted && steps.length > 0) {
@@ -2459,7 +2541,7 @@ export class WorkflowAgent<
2459
2541
  toolResults: allToolResults,
2460
2542
  finishReason,
2461
2543
  totalUsage,
2462
- output: undefined as OUTPUT,
2544
+ output: undefined as TOutput,
2463
2545
  };
2464
2546
  }
2465
2547
 
@@ -2479,27 +2561,34 @@ export class WorkflowAgent<
2479
2561
  );
2480
2562
 
2481
2563
  // For provider-executed tools, use the results from the stream
2482
- const providerToolResults: WorkflowToolExecutionResult[] =
2483
- await Promise.all(
2484
- providerToolCalls.map(toolCall =>
2485
- resolveProviderToolResult(
2486
- toolCall,
2487
- providerExecutedToolResults,
2488
- effectiveTools as ToolSet,
2489
- download,
2490
- ),
2491
- ),
2492
- );
2493
- await Promise.all(
2494
- providerToolCalls.map((toolCall, index) =>
2495
- recordProviderExecutedToolTelemetry(
2564
+ const providerToolResultEntries = await Promise.all(
2565
+ providerToolCalls.map(async toolCall => ({
2566
+ toolCall,
2567
+ result: await resolveProviderToolResult(
2496
2568
  toolCall,
2497
- providerToolResults[index],
2498
- iterMessages,
2499
- currentStepNumber,
2569
+ providerExecutedToolResults,
2570
+ effectiveTools as ToolSet,
2571
+ download,
2500
2572
  ),
2573
+ })),
2574
+ );
2575
+ await Promise.all(
2576
+ providerToolResultEntries.flatMap(({ toolCall, result }) =>
2577
+ result == null
2578
+ ? []
2579
+ : [
2580
+ recordProviderExecutedToolTelemetry(
2581
+ toolCall,
2582
+ result,
2583
+ iterMessages,
2584
+ currentStepNumber,
2585
+ ),
2586
+ ],
2501
2587
  ),
2502
2588
  );
2589
+ const providerToolResults = providerToolResultEntries.flatMap(
2590
+ ({ result }) => (result == null ? [] : [result]),
2591
+ );
2503
2592
  const continuationInvalidToolResults = invalidToolCalls.map(
2504
2593
  createInvalidToolResult,
2505
2594
  );
@@ -2566,6 +2655,8 @@ export class WorkflowAgent<
2566
2655
  output: r.rawOutput,
2567
2656
  }));
2568
2657
 
2658
+ addToolResultsToStep(step, executedToolResults);
2659
+
2569
2660
  result = await iterator.next(continuationToolResults);
2570
2661
  } else {
2571
2662
  // Final step with no tool calls - reset tracking
@@ -2622,8 +2713,10 @@ export class WorkflowAgent<
2622
2713
  prompt.messages) as unknown as ModelMessage[];
2623
2714
 
2624
2715
  // Parse structured output if output is specified (stream-level overrides constructor default)
2625
- const effectiveOutput = options.output ?? this.output;
2626
- let experimentalOutput: OUTPUT = undefined as OUTPUT;
2716
+ const effectiveOutput = (options.output ?? this.output) as
2717
+ | OutputSpecification<TOutput, TPartialOutput>
2718
+ | undefined;
2719
+ let experimentalOutput: TOutput = undefined as TOutput;
2627
2720
  if (effectiveOutput && steps.length > 0) {
2628
2721
  const lastStep = steps[steps.length - 1];
2629
2722
  const text = lastStep.text;
@@ -2980,33 +3073,6 @@ async function writeApprovalToolResults(
2980
3073
  }
2981
3074
  }
2982
3075
 
2983
- /**
2984
- * Resolve the per-tool context that gets passed into a tool's `execute`
2985
- * (and `needsApproval`) function. When the tool declares a `contextSchema`,
2986
- * the entry is validated against it.
2987
- */
2988
- async function resolveToolContext({
2989
- toolName,
2990
- tool,
2991
- toolsContext,
2992
- }: {
2993
- toolName: string;
2994
- tool: ToolSet[string];
2995
- toolsContext: Record<string, Context | undefined> | undefined;
2996
- }): Promise<unknown> {
2997
- const contextSchema = (tool as { contextSchema?: unknown }).contextSchema;
2998
- const entry = toolsContext?.[toolName];
2999
- if (contextSchema == null) {
3000
- return entry;
3001
- }
3002
-
3003
- return await validateTypes({
3004
- value: entry,
3005
- schema: contextSchema as Parameters<typeof validateTypes>[0]['schema'],
3006
- context: { field: 'tool context', entityName: toolName },
3007
- });
3008
- }
3009
-
3010
3076
  function aggregateUsage(steps: StepResult<any, any>[]): LanguageModelUsage {
3011
3077
  let inputTokens = 0;
3012
3078
  let outputTokens = 0;
@@ -3029,9 +3095,18 @@ async function resolveProviderToolResult(
3029
3095
  >,
3030
3096
  tools?: ToolSet,
3031
3097
  download?: DownloadFunction,
3032
- ): Promise<WorkflowToolExecutionResult> {
3098
+ ): Promise<WorkflowToolExecutionResult | undefined> {
3033
3099
  const streamResult = providerExecutedToolResults?.get(toolCall.toolCallId);
3034
3100
  if (!streamResult) {
3101
+ const tool = tools?.[toolCall.toolName];
3102
+ if (
3103
+ tool?.type === 'provider' &&
3104
+ tool.isProviderExecuted &&
3105
+ tool.supportsDeferredResults
3106
+ ) {
3107
+ return undefined;
3108
+ }
3109
+
3035
3110
  console.warn(
3036
3111
  `[WorkflowAgent] Provider-executed tool "${toolCall.toolName}" (${toolCall.toolCallId}) ` +
3037
3112
  `did not receive a result from the stream. This may indicate a provider issue.`,