@ai-sdk/workflow 2.0.71 → 2.0.73

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.
@@ -191,6 +191,9 @@ export function toUIMessageChunk(
191
191
  approvalId: part.approvalId,
192
192
  toolCallId:
193
193
  'toolCallId' in part ? part.toolCallId : part.toolCall.toolCallId,
194
+ ...('inputSchemaInput' in part && part.inputSchemaInput !== undefined
195
+ ? { inputSchemaInput: part.inputSchemaInput }
196
+ : {}),
194
197
  ...(part.signature != null ? { signature: part.signature } : {}),
195
198
  };
196
199
 
@@ -51,6 +51,8 @@ import {
51
51
  convertToLanguageModelPrompt,
52
52
  mergeAbortSignals,
53
53
  mergeCallbacks,
54
+ mapWithConcurrency,
55
+ prepareToolCallConcurrency,
54
56
  signToolApproval,
55
57
  standardizePrompt,
56
58
  validateApprovedToolApprovals,
@@ -426,6 +428,7 @@ export interface PrepareCallOptions<
426
428
  tools: TTools;
427
429
  instructions?: Instructions;
428
430
  toolChoice?: ToolChoice<TTools>;
431
+ toolCallConcurrency?: number;
429
432
  stopWhen?:
430
433
  | StopCondition<NoInfer<TTools>, TRuntimeContext>
431
434
  | Array<StopCondition<NoInfer<TTools>, TRuntimeContext>>;
@@ -514,6 +517,13 @@ export type WorkflowAgentOptions<
514
517
  */
515
518
  toolChoice?: ToolChoice<TTools>;
516
519
 
520
+ /**
521
+ * Maximum number of local tool calls that may execute concurrently within a step.
522
+ * Set to `1` to execute calls sequentially in model order, including approved
523
+ * calls. By default, all local tool calls execute concurrently.
524
+ */
525
+ toolCallConcurrency?: number;
526
+
517
527
  /**
518
528
  * Optional telemetry configuration.
519
529
  */
@@ -999,6 +1009,13 @@ export type WorkflowAgentCallOptions<
999
1009
  */
1000
1010
  toolChoice?: ToolChoice<TTools>;
1001
1011
 
1012
+ /**
1013
+ * Maximum number of local tool calls that may execute concurrently within a step.
1014
+ * Set to `1` to execute calls sequentially in model order, including approved
1015
+ * calls. By default, all local tool calls execute concurrently.
1016
+ */
1017
+ toolCallConcurrency?: number;
1018
+
1002
1019
  /**
1003
1020
  * Limits the tools that are available for the model to call without
1004
1021
  * changing the tool call and result types in the result.
@@ -1540,6 +1557,7 @@ export class WorkflowAgent<
1540
1557
  private instructions?: Instructions;
1541
1558
  private generationSettings: GenerationSettings;
1542
1559
  private toolChoice?: ToolChoice<TBaseTools>;
1560
+ private toolCallConcurrency?: number;
1543
1561
  private telemetry?: TelemetryOptions<TRuntimeContext, TBaseTools>;
1544
1562
  private runtimeContext?: TRuntimeContext;
1545
1563
  private toolsContext?: InferToolSetContext<TBaseTools>;
@@ -1588,6 +1606,7 @@ export class WorkflowAgent<
1588
1606
  // `instructions` takes precedence over deprecated `system`
1589
1607
  this.instructions = options.instructions ?? options.system;
1590
1608
  this.toolChoice = options.toolChoice;
1609
+ this.toolCallConcurrency = options.toolCallConcurrency;
1591
1610
  this.telemetry = options.telemetry;
1592
1611
  this.runtimeContext = options.runtimeContext;
1593
1612
  this.toolsContext = options.toolsContext;
@@ -1727,6 +1746,8 @@ export class WorkflowAgent<
1727
1746
  Context | undefined
1728
1747
  >;
1729
1748
  let effectiveToolChoiceFromPrepare = options.toolChoice ?? this.toolChoice;
1749
+ let effectiveToolCallConcurrency =
1750
+ options.toolCallConcurrency ?? this.toolCallConcurrency;
1730
1751
  let effectiveStopWhenFromPrepare =
1731
1752
  options.stopWhen ??
1732
1753
  this.stopWhen ??
@@ -1754,6 +1775,7 @@ export class WorkflowAgent<
1754
1775
  tools: this.tools,
1755
1776
  instructions: effectiveInstructions,
1756
1777
  toolChoice: effectiveToolChoiceFromPrepare as ToolChoice<TBaseTools>,
1778
+ toolCallConcurrency: effectiveToolCallConcurrency,
1757
1779
  stopWhen: effectiveStopWhenFromPrepare,
1758
1780
  activeTools: effectiveActiveToolsFromPrepare,
1759
1781
  experimental_download: effectiveDownloadFromPrepare,
@@ -1781,6 +1803,8 @@ export class WorkflowAgent<
1781
1803
  if (prepared.toolChoice !== undefined)
1782
1804
  effectiveToolChoiceFromPrepare =
1783
1805
  prepared.toolChoice as ToolChoice<TBaseTools>;
1806
+ if (prepared.toolCallConcurrency !== undefined)
1807
+ effectiveToolCallConcurrency = prepared.toolCallConcurrency;
1784
1808
  if (prepared.stopWhen !== undefined)
1785
1809
  effectiveStopWhenFromPrepare = prepared.stopWhen;
1786
1810
  if (prepared.activeTools !== undefined)
@@ -1818,6 +1842,9 @@ export class WorkflowAgent<
1818
1842
  effectiveGenerationSettings.providerOptions = prepared.providerOptions;
1819
1843
  }
1820
1844
 
1845
+ const toolCallConcurrency = prepareToolCallConcurrency(
1846
+ options.toolCallConcurrency ?? effectiveToolCallConcurrency,
1847
+ );
1821
1848
  const effectiveTelemetry = effectiveTelemetryFromPrepare;
1822
1849
  const telemetryDispatcher = createRestrictedTelemetryDispatcher<
1823
1850
  any,
@@ -1869,6 +1896,7 @@ export class WorkflowAgent<
1869
1896
  effectiveRuntimeContext,
1870
1897
  effectiveToolsContext,
1871
1898
  effectiveToolChoiceFromPrepare,
1899
+ toolCallConcurrency,
1872
1900
  effectiveStopWhenFromPrepare,
1873
1901
  effectiveActiveToolsFromPrepare,
1874
1902
  effectiveToolApprovalSecret,
@@ -1906,6 +1934,7 @@ export class WorkflowAgent<
1906
1934
  effectiveRuntimeContext,
1907
1935
  effectiveToolsContext,
1908
1936
  effectiveToolChoiceFromPrepare,
1937
+ toolCallConcurrency,
1909
1938
  effectiveStopWhenFromPrepare,
1910
1939
  effectiveActiveToolsFromPrepare,
1911
1940
  effectiveToolApprovalSecret,
@@ -1985,52 +2014,56 @@ export class WorkflowAgent<
1985
2014
  output: unknown;
1986
2015
  }> = [];
1987
2016
 
1988
- // Execute approved tools
1989
- for (const approval of approvedToolApprovals) {
1990
- // Provider-executed approvals are forwarded to the provider via the
1991
- // preserved approval response below, not executed locally.
1992
- if (approval.providerExecuted) {
1993
- continue;
1994
- }
1995
- const tool = (this.tools as ToolSet)[approval.toolName];
1996
- if (tool && typeof tool.execute === 'function') {
1997
- if (!tool.needsApproval) {
1998
- const reason = `Tool "${approval.toolName}" does not require approval`;
1999
- toolResultContent.push({
2000
- type: 'tool-result' as const,
2001
- toolCallId: approval.toolCallId,
2002
- toolName: approval.toolName,
2003
- output: await createLanguageModelToolResultOutput({
2017
+ // Execute approved calls in model order with the same limit as new calls.
2018
+ const approvedResults = await mapWithConcurrency({
2019
+ items: approvedToolApprovals.filter(
2020
+ approval => !approval.providerExecuted,
2021
+ ),
2022
+ concurrency: toolCallConcurrency,
2023
+ abortSignal: effectiveAbortSignal,
2024
+ execute: async approval => {
2025
+ const tool = (this.tools as ToolSet)[approval.toolName];
2026
+ if (tool && typeof tool.execute === 'function') {
2027
+ if (!tool.needsApproval) {
2028
+ const reason = `Tool "${approval.toolName}" does not require approval`;
2029
+ return {
2030
+ modelResult: {
2031
+ type: 'tool-result' as const,
2032
+ toolCallId: approval.toolCallId,
2033
+ toolName: approval.toolName,
2034
+ output: await createLanguageModelToolResultOutput({
2035
+ toolCallId: approval.toolCallId,
2036
+ toolName: approval.toolName,
2037
+ input: approval.input,
2038
+ output: reason,
2039
+ tool,
2040
+ errorMode: 'text',
2041
+ supportedUrls: {},
2042
+ download: workflowDownload,
2043
+ }),
2044
+ },
2045
+ };
2046
+ }
2047
+
2048
+ // Re-validate through the shared core implementation: input schema,
2049
+ // approval signature (when configured), and approval policy.
2050
+ // Convert invalid input, denial, or signature errors to a tool error
2051
+ // result so the agent loop can continue gracefully.
2052
+ let revalidationReason: string | undefined;
2053
+ try {
2054
+ await validateWorkflowToolApprovalSignature({
2055
+ secret: effectiveToolApprovalSecret,
2056
+ approvalId: approval.collected.approvalRequest.approvalId,
2004
2057
  toolCallId: approval.toolCallId,
2005
2058
  toolName: approval.toolName,
2006
2059
  input: approval.input,
2007
- output: reason,
2008
- tool,
2009
- errorMode: 'text',
2010
- supportedUrls: {},
2011
- download: workflowDownload,
2012
- }),
2013
- });
2014
- continue;
2015
- }
2016
-
2017
- // Re-validate through the shared core implementation: input schema,
2018
- // approval signature (when configured), and approval policy.
2019
- // Convert invalid input, denial, or signature errors to a tool error
2020
- // result so the agent loop can continue gracefully.
2021
- let revalidationReason: string | undefined;
2022
- try {
2023
- await validateWorkflowToolApprovalSignature({
2024
- secret: effectiveToolApprovalSecret,
2025
- approvalId: approval.collected.approvalRequest.approvalId,
2026
- toolCallId: approval.toolCallId,
2027
- toolName: approval.toolName,
2028
- input: approval.input,
2029
- signature: approval.collected.approvalRequest.signature,
2030
- });
2060
+ signature: approval.collected.approvalRequest.signature,
2061
+ });
2031
2062
 
2032
- const { deniedToolApprovals: policyDenied, invalidToolApprovals } =
2033
- await validateApprovedToolApprovals({
2063
+ const {
2064
+ deniedToolApprovals: policyDenied,
2065
+ invalidToolApprovals,
2066
+ } = await validateApprovedToolApprovals({
2034
2067
  approvedToolApprovals: [approval.collected],
2035
2068
  tools: this.tools as ToolSet,
2036
2069
  toolApproval: undefined,
@@ -2040,58 +2073,67 @@ export class WorkflowAgent<
2040
2073
  runtimeContext: effectiveRuntimeContext,
2041
2074
  });
2042
2075
 
2043
- if (invalidToolApprovals.length > 0) {
2044
- revalidationReason = getErrorMessage(
2045
- invalidToolApprovals[0].error,
2046
- );
2047
- } else if (policyDenied.length > 0) {
2048
- revalidationReason =
2049
- policyDenied[0].approvalResponse.reason ??
2050
- 'Tool approval denied';
2076
+ if (invalidToolApprovals.length > 0) {
2077
+ revalidationReason = getErrorMessage(
2078
+ invalidToolApprovals[0].error,
2079
+ );
2080
+ } else if (policyDenied.length > 0) {
2081
+ revalidationReason =
2082
+ policyDenied[0].approvalResponse.reason ??
2083
+ 'Tool approval denied';
2084
+ }
2085
+ } catch (error) {
2086
+ revalidationReason = getErrorMessage(error);
2051
2087
  }
2052
- } catch (error) {
2053
- revalidationReason = getErrorMessage(error);
2054
- }
2055
2088
 
2056
- if (revalidationReason != null) {
2057
- toolResultContent.push({
2058
- type: 'tool-result' as const,
2059
- toolCallId: approval.toolCallId,
2060
- toolName: approval.toolName,
2061
- output: await createLanguageModelToolResultOutput({
2089
+ if (revalidationReason != null) {
2090
+ return {
2091
+ modelResult: {
2092
+ type: 'tool-result' as const,
2093
+ toolCallId: approval.toolCallId,
2094
+ toolName: approval.toolName,
2095
+ output: await createLanguageModelToolResultOutput({
2096
+ toolCallId: approval.toolCallId,
2097
+ toolName: approval.toolName,
2098
+ input: approval.input,
2099
+ output: revalidationReason,
2100
+ tool,
2101
+ errorMode: 'text',
2102
+ supportedUrls: {},
2103
+ download: workflowDownload,
2104
+ }),
2105
+ },
2106
+ };
2107
+ }
2108
+
2109
+ const result = await executeToolWithCallbacks(
2110
+ {
2062
2111
  toolCallId: approval.toolCallId,
2063
2112
  toolName: approval.toolName,
2064
2113
  input: approval.input,
2065
- output: revalidationReason,
2066
- tool,
2067
- errorMode: 'text',
2068
- supportedUrls: {},
2069
- download: workflowDownload,
2070
- }),
2071
- });
2072
- continue;
2114
+ },
2115
+ this.tools as ToolSet,
2116
+ prompt.messages as unknown as LanguageModelV4Prompt,
2117
+ effectiveToolsContext,
2118
+ 0,
2119
+ sandbox,
2120
+ );
2121
+ return {
2122
+ modelResult: result.modelResult,
2123
+ rawResult: {
2124
+ toolCallId: approval.toolCallId,
2125
+ toolName: approval.toolName,
2126
+ input: approval.input,
2127
+ output: result.rawOutput,
2128
+ },
2129
+ };
2073
2130
  }
2074
-
2075
- const result = await executeToolWithCallbacks(
2076
- {
2077
- toolCallId: approval.toolCallId,
2078
- toolName: approval.toolName,
2079
- input: approval.input,
2080
- },
2081
- this.tools as ToolSet,
2082
- prompt.messages as unknown as LanguageModelV4Prompt,
2083
- effectiveToolsContext,
2084
- 0,
2085
- sandbox,
2086
- );
2087
- toolResultContent.push(result.modelResult);
2088
- approvedRawResults.push({
2089
- toolCallId: approval.toolCallId,
2090
- toolName: approval.toolName,
2091
- input: approval.input,
2092
- output: result.rawOutput,
2093
- });
2094
- }
2131
+ },
2132
+ });
2133
+ for (const result of approvedResults) {
2134
+ if (result == null) continue;
2135
+ toolResultContent.push(result.modelResult);
2136
+ if (result.rawResult != null) approvedRawResults.push(result.rawResult);
2095
2137
  }
2096
2138
 
2097
2139
  // Create denial results for denied tools
@@ -2711,19 +2753,20 @@ export class WorkflowAgent<
2711
2753
  providerApprovalRequests.length > 0
2712
2754
  ) {
2713
2755
  // Execute any executable tools that were also called in this step
2714
- const executableResults = await Promise.all(
2715
- executableToolCalls.map(
2716
- (toolCall): Promise<WorkflowToolExecutionResult> =>
2717
- executeToolWithCallbacks(
2718
- toolCall,
2719
- stepTools,
2720
- iterMessages,
2721
- toolsContext,
2722
- currentStepNumber,
2723
- toolExecutionSandbox,
2724
- ),
2725
- ),
2726
- );
2756
+ const executableResults = await mapWithConcurrency({
2757
+ items: executableToolCalls,
2758
+ concurrency: toolCallConcurrency,
2759
+ abortSignal: effectiveAbortSignal,
2760
+ execute: toolCall =>
2761
+ executeToolWithCallbacks(
2762
+ toolCall,
2763
+ stepTools,
2764
+ iterMessages,
2765
+ toolsContext,
2766
+ currentStepNumber,
2767
+ toolExecutionSandbox,
2768
+ ),
2769
+ });
2727
2770
 
2728
2771
  // Collect provider tool results
2729
2772
  const providerResultEntries = await Promise.all(
@@ -2800,6 +2843,8 @@ export class WorkflowAgent<
2800
2843
  );
2801
2844
  const approvalRequests = await Promise.all(
2802
2845
  approvalToolCalls.map(async tc => {
2846
+ const { inputSchemaInput, ...toolCallWithoutInputSchemaInput } =
2847
+ tc;
2803
2848
  const approvalId = `approval-${tc.toolCallId}`;
2804
2849
  const signature =
2805
2850
  effectiveToolApprovalSecret == null
@@ -2814,15 +2859,23 @@ export class WorkflowAgent<
2814
2859
  return {
2815
2860
  type: 'tool-approval-request' as const,
2816
2861
  approvalId,
2862
+ ...(inputSchemaInput !== undefined
2863
+ ? { inputSchemaInput }
2864
+ : {}),
2817
2865
  toolCall: {
2818
- ...tc,
2866
+ ...toolCallWithoutInputSchemaInput,
2819
2867
  type: 'tool-call' as const,
2820
2868
  } as StepResult<ToolSet>['toolCalls'][number],
2821
2869
  ...(signature != null ? { signature } : {}),
2822
2870
  };
2823
2871
  }),
2824
2872
  );
2825
- step?.content.push(...approvalRequests);
2873
+ step?.content.push(
2874
+ ...approvalRequests.map(
2875
+ ({ inputSchemaInput: _inputSchemaInput, ...request }) =>
2876
+ request,
2877
+ ),
2878
+ );
2826
2879
  const approvalMessages = approvalRequests.map(
2827
2880
  ({ toolCall, ...request }) => ({
2828
2881
  ...request,
@@ -2926,19 +2979,20 @@ export class WorkflowAgent<
2926
2979
  }
2927
2980
 
2928
2981
  // Execute client tools (all have execute functions at this point)
2929
- const clientToolResults = await Promise.all(
2930
- nonProviderToolCalls.map(
2931
- (toolCall): Promise<WorkflowToolExecutionResult> =>
2932
- executeToolWithCallbacks(
2933
- toolCall,
2934
- stepTools,
2935
- iterMessages,
2936
- toolsContext,
2937
- currentStepNumber,
2938
- toolExecutionSandbox,
2939
- ),
2940
- ),
2941
- );
2982
+ const clientToolResults = await mapWithConcurrency({
2983
+ items: nonProviderToolCalls,
2984
+ concurrency: toolCallConcurrency,
2985
+ abortSignal: effectiveAbortSignal,
2986
+ execute: toolCall =>
2987
+ executeToolWithCallbacks(
2988
+ toolCall,
2989
+ stepTools,
2990
+ iterMessages,
2991
+ toolsContext,
2992
+ currentStepNumber,
2993
+ toolExecutionSandbox,
2994
+ ),
2995
+ });
2942
2996
 
2943
2997
  // For provider-executed tools, use the results from the stream
2944
2998
  const providerToolResultEntries = await Promise.all(
@@ -3269,6 +3323,7 @@ async function writeApprovalRequests(
3269
3323
  approvalId: string;
3270
3324
  toolCallId: string;
3271
3325
  signature?: string;
3326
+ inputSchemaInput?: unknown;
3272
3327
  }>,
3273
3328
  ) {
3274
3329
  'use step';
@@ -3280,6 +3335,9 @@ async function writeApprovalRequests(
3280
3335
  approvalId: request.approvalId,
3281
3336
  toolCallId: request.toolCallId,
3282
3337
  ...(request.signature != null ? { signature: request.signature } : {}),
3338
+ ...(request.inputSchemaInput !== undefined
3339
+ ? { inputSchemaInput: request.inputSchemaInput }
3340
+ : {}),
3283
3341
  });
3284
3342
  }
3285
3343
  } finally {
@@ -3692,6 +3750,7 @@ function addApprovalRequestsToMessages(
3692
3750
  approvalId: string;
3693
3751
  toolCallId: string;
3694
3752
  signature?: string;
3753
+ inputSchemaInput?: unknown;
3695
3754
  }>,
3696
3755
  ): ModelMessage[] {
3697
3756
  if (requests.length === 0) return messages;