@ai-sdk/workflow 2.0.28 → 2.0.30

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.
@@ -50,10 +50,12 @@ import {
50
50
  validateApprovedToolApprovals,
51
51
  verifyToolApprovalSignature,
52
52
  } from 'ai/internal';
53
+ import { addToolResultsToConversation } from './add-tool-results-to-conversation.js';
53
54
  import { createLanguageModelToolResultOutput } from './create-language-model-tool-result-output.js';
54
55
  import type {
55
56
  ModelCallStreamPart,
56
57
  ModelStopCondition,
58
+ ProviderExecutedToolResult,
57
59
  } from './do-stream-step.js';
58
60
  import { resolveToolContext } from './resolve-tool-context.js';
59
61
  import { streamTextIterator } from './stream-text-iterator.js';
@@ -1281,11 +1283,24 @@ function addToolResultsToStep(
1281
1283
  return;
1282
1284
  }
1283
1285
 
1284
- const toolOutputs = executedResults.map(result => {
1286
+ const existingProviderResultIds = new Set(
1287
+ step.content.flatMap(part =>
1288
+ (part.type === 'tool-result' || part.type === 'tool-error') &&
1289
+ part.providerExecuted
1290
+ ? [part.toolCallId]
1291
+ : [],
1292
+ ),
1293
+ );
1294
+
1295
+ const toolOutputs = executedResults.flatMap(result => {
1285
1296
  const toolCall = step.toolCalls.find(
1286
1297
  toolCall => toolCall.toolCallId === result.modelResult.toolCallId,
1287
1298
  );
1288
1299
 
1300
+ if (existingProviderResultIds.has(result.modelResult.toolCallId)) {
1301
+ return [];
1302
+ }
1303
+
1289
1304
  const common = {
1290
1305
  toolCallId: result.modelResult.toolCallId,
1291
1306
  toolName: result.modelResult.toolName,
@@ -1296,17 +1311,19 @@ function addToolResultsToStep(
1296
1311
  : {}),
1297
1312
  };
1298
1313
 
1299
- return result.isError
1300
- ? {
1301
- type: 'tool-error' as const,
1302
- ...common,
1303
- error: result.rawOutput,
1304
- }
1305
- : {
1306
- type: 'tool-result' as const,
1307
- ...common,
1308
- output: result.rawOutput,
1309
- };
1314
+ return [
1315
+ result.isError
1316
+ ? {
1317
+ type: 'tool-error' as const,
1318
+ ...common,
1319
+ error: result.rawOutput,
1320
+ }
1321
+ : {
1322
+ type: 'tool-result' as const,
1323
+ ...common,
1324
+ output: result.rawOutput,
1325
+ },
1326
+ ];
1310
1327
  });
1311
1328
 
1312
1329
  step.content.push(...(toolOutputs as StepResult<ToolSet, any>['content']));
@@ -2335,7 +2352,11 @@ export class WorkflowAgent<
2335
2352
  toolsContext: yieldedToolsContext,
2336
2353
  experimental_sandbox: stepSandbox,
2337
2354
  providerExecutedToolResults,
2355
+ providerExecutedToolResultPositions,
2338
2356
  } = result.value;
2357
+ const capturedProviderToolResults =
2358
+ providerExecutedToolResults ??
2359
+ new Map<string, ProviderExecutedToolResult>();
2339
2360
  const toolExecutionSandbox = stepSandbox ?? sandbox;
2340
2361
  // Capture current step number before pushing (0-based)
2341
2362
  const currentStepNumber = steps.length;
@@ -2349,8 +2370,10 @@ export class WorkflowAgent<
2349
2370
  toolsContext = yieldedToolsContext;
2350
2371
  }
2351
2372
 
2352
- // Only execute tools if there are tool calls
2353
- if (toolCalls.length > 0) {
2373
+ // Process client tool calls and any provider results captured in this
2374
+ // response. Deferred provider results may arrive without a matching
2375
+ // tool call in the current step.
2376
+ if (toolCalls.length > 0 || capturedProviderToolResults.size > 0) {
2354
2377
  const invalidToolCalls = toolCalls.filter(tc => tc.invalid === true);
2355
2378
  const validToolCalls = toolCalls.filter(tc => tc.invalid !== true);
2356
2379
 
@@ -2361,6 +2384,29 @@ export class WorkflowAgent<
2361
2384
  const providerToolCalls = validToolCalls.filter(
2362
2385
  tc => tc.providerExecuted,
2363
2386
  );
2387
+ const providerToolCallsForResults = [
2388
+ ...providerToolCalls,
2389
+ ...[...capturedProviderToolResults.values()].flatMap(
2390
+ providerResult =>
2391
+ providerToolCalls.some(
2392
+ toolCall => toolCall.toolCallId === providerResult.toolCallId,
2393
+ )
2394
+ ? []
2395
+ : [
2396
+ {
2397
+ type: 'tool-call' as const,
2398
+ toolCallId: providerResult.toolCallId,
2399
+ toolName: providerResult.toolName,
2400
+ input: toolCalls.find(
2401
+ toolCall =>
2402
+ toolCall.toolCallId === providerResult.toolCallId,
2403
+ )?.input,
2404
+ providerExecuted: true,
2405
+ dynamic: providerResult.dynamic,
2406
+ },
2407
+ ],
2408
+ ),
2409
+ ];
2364
2410
 
2365
2411
  // Check which tools need approval (can be async)
2366
2412
  const approvalNeeded = await Promise.all(
@@ -2424,11 +2470,11 @@ export class WorkflowAgent<
2424
2470
 
2425
2471
  // Collect provider tool results
2426
2472
  const providerResultEntries = await Promise.all(
2427
- providerToolCalls.map(async toolCall => ({
2473
+ providerToolCallsForResults.map(async toolCall => ({
2428
2474
  toolCall,
2429
2475
  result: await resolveProviderToolResult(
2430
2476
  toolCall,
2431
- providerExecutedToolResults,
2477
+ capturedProviderToolResults,
2432
2478
  effectiveTools as ToolSet,
2433
2479
  download,
2434
2480
  ),
@@ -2452,9 +2498,9 @@ export class WorkflowAgent<
2452
2498
  ({ result }) => (result == null ? [] : [result]),
2453
2499
  );
2454
2500
 
2455
- const continuationInvalidResults = invalidToolCalls.map(
2456
- createInvalidToolResult,
2457
- );
2501
+ const continuationInvalidResults = invalidToolCalls
2502
+ .filter(toolCall => !toolCall.providerExecuted)
2503
+ .map(createInvalidToolResult);
2458
2504
  const resolvedResults: LanguageModelV4ToolResultPart[] = [
2459
2505
  ...executableResults.map(result => result.modelResult),
2460
2506
  ...providerResults.map(result => result.modelResult),
@@ -2481,12 +2527,21 @@ export class WorkflowAgent<
2481
2527
 
2482
2528
  addToolResultsToStep(step, executedResults);
2483
2529
 
2484
- if (resolvedResults.length > 0) {
2485
- iterMessages.push({
2486
- role: 'tool',
2487
- content: resolvedResults,
2488
- });
2489
- }
2530
+ const responseMessages = addToolResultsToConversation({
2531
+ messages: iterMessages,
2532
+ toolResults: resolvedResults,
2533
+ providerExecutedToolCallIds: new Set(
2534
+ providerToolCallsForResults.map(
2535
+ toolCall => toolCall.toolCallId,
2536
+ ),
2537
+ ),
2538
+ providerExecutedToolResultPositions,
2539
+ });
2540
+ step?.response.messages.push(
2541
+ ...(responseMessages as unknown as NonNullable<
2542
+ typeof step
2543
+ >['response']['messages']),
2544
+ );
2490
2545
 
2491
2546
  const messages = iterMessages as unknown as ModelMessage[];
2492
2547
  const lastStep = steps[steps.length - 1];
@@ -2609,11 +2664,11 @@ export class WorkflowAgent<
2609
2664
 
2610
2665
  // For provider-executed tools, use the results from the stream
2611
2666
  const providerToolResultEntries = await Promise.all(
2612
- providerToolCalls.map(async toolCall => ({
2667
+ providerToolCallsForResults.map(async toolCall => ({
2613
2668
  toolCall,
2614
2669
  result: await resolveProviderToolResult(
2615
2670
  toolCall,
2616
- providerExecutedToolResults,
2671
+ capturedProviderToolResults,
2617
2672
  effectiveTools as ToolSet,
2618
2673
  download,
2619
2674
  ),
@@ -2636,9 +2691,9 @@ export class WorkflowAgent<
2636
2691
  const providerToolResults = providerToolResultEntries.flatMap(
2637
2692
  ({ result }) => (result == null ? [] : [result]),
2638
2693
  );
2639
- const continuationInvalidToolResults = invalidToolCalls.map(
2640
- createInvalidToolResult,
2641
- );
2694
+ const continuationInvalidToolResults = invalidToolCalls
2695
+ .filter(toolCall => !toolCall.providerExecuted)
2696
+ .map(createInvalidToolResult);
2642
2697
 
2643
2698
  // Combine executable/provider results in the original order,
2644
2699
  // while preserving invalid tool calls as error results for the
@@ -2654,6 +2709,14 @@ export class WorkflowAgent<
2654
2709
  if (providerResult) return [providerResult];
2655
2710
  return [];
2656
2711
  });
2712
+ const currentToolCallIds = new Set(
2713
+ toolCalls.map(toolCall => toolCall.toolCallId),
2714
+ );
2715
+ executedToolResults.push(
2716
+ ...providerToolResults.filter(
2717
+ result => !currentToolCallIds.has(result.modelResult.toolCallId),
2718
+ ),
2719
+ );
2657
2720
  const continuationToolResults = toolCalls.flatMap(tc => {
2658
2721
  const invalidResult = continuationInvalidToolResults.find(
2659
2722
  r => r.toolCallId === tc.toolCallId,
@@ -2665,6 +2728,13 @@ export class WorkflowAgent<
2665
2728
  if (executedResult) return [executedResult.modelResult];
2666
2729
  return [];
2667
2730
  });
2731
+ continuationToolResults.push(
2732
+ ...providerToolResults.flatMap(result =>
2733
+ currentToolCallIds.has(result.modelResult.toolCallId)
2734
+ ? []
2735
+ : [result.modelResult],
2736
+ ),
2737
+ );
2668
2738
 
2669
2739
  // Write tool results and step boundaries to the stream so the UI can
2670
2740
  // transition tool parts to the appropriate output state and properly
@@ -3136,10 +3206,7 @@ function aggregateUsage(steps: StepResult<any, any>[]): LanguageModelUsage {
3136
3206
 
3137
3207
  async function resolveProviderToolResult(
3138
3208
  toolCall: { toolCallId: string; toolName: string; input: unknown },
3139
- providerExecutedToolResults?: Map<
3140
- string,
3141
- { toolCallId: string; toolName: string; result: unknown; isError?: boolean }
3142
- >,
3209
+ providerExecutedToolResults?: Map<string, ProviderExecutedToolResult>,
3143
3210
  tools?: ToolSet,
3144
3211
  download?: DownloadFunction,
3145
3212
  ): Promise<WorkflowToolExecutionResult | undefined> {
@@ -3174,11 +3241,7 @@ async function resolveProviderToolResult(
3174
3241
  }
3175
3242
 
3176
3243
  const result = streamResult.result;
3177
- const errorMode = streamResult.isError
3178
- ? typeof result === 'string'
3179
- ? 'text'
3180
- : 'json'
3181
- : 'none';
3244
+ const errorMode = streamResult.isError ? 'json' : 'none';
3182
3245
 
3183
3246
  return {
3184
3247
  modelResult: {
@@ -3195,6 +3258,9 @@ async function resolveProviderToolResult(
3195
3258
  supportedUrls: {},
3196
3259
  download,
3197
3260
  }),
3261
+ ...(streamResult.providerMetadata != null
3262
+ ? { providerOptions: streamResult.providerMetadata }
3263
+ : {}),
3198
3264
  },
3199
3265
  rawOutput: result,
3200
3266
  isError: streamResult.isError === true,