@ai-sdk/workflow 1.0.0-canary.83 → 1.0.0-canary.85

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.
@@ -1,5 +1,4 @@
1
1
  import type {
2
- JSONValue,
3
2
  LanguageModelV4CallOptions,
4
3
  LanguageModelV4Prompt,
5
4
  LanguageModelV4StreamPart,
@@ -7,6 +6,7 @@ import type {
7
6
  SharedV4ProviderOptions,
8
7
  } from '@ai-sdk/provider';
9
8
  import {
9
+ getErrorMessage,
10
10
  validateTypes,
11
11
  type Context,
12
12
  type HasRequiredKey,
@@ -40,6 +40,7 @@ import {
40
40
  mergeCallbacks,
41
41
  standardizePrompt,
42
42
  } from 'ai/internal';
43
+ import { createLanguageModelToolResultOutput } from './create-language-model-tool-result-output.js';
43
44
  import { streamTextIterator } from './stream-text-iterator.js';
44
45
 
45
46
  // Re-export for consumers
@@ -1053,6 +1054,12 @@ export interface ToolResult {
1053
1054
  output: unknown;
1054
1055
  }
1055
1056
 
1057
+ type WorkflowToolExecutionResult = {
1058
+ modelResult: LanguageModelV4ToolResultPart;
1059
+ rawOutput: unknown;
1060
+ isError: boolean;
1061
+ };
1062
+
1056
1063
  /**
1057
1064
  * Result of the WorkflowAgent.stream method.
1058
1065
  */
@@ -1338,6 +1345,7 @@ export class WorkflowAgent<
1338
1345
  ? { prompt: effectivePrompt }
1339
1346
  : { messages: effectiveMessages! }),
1340
1347
  } as Prompt);
1348
+ const download = options.experimental_download ?? this.experimentalDownload;
1341
1349
 
1342
1350
  // Process tool approval responses before starting the agent loop.
1343
1351
  // This mirrors how stream-text.ts handles tool-approval-response parts:
@@ -1348,14 +1356,12 @@ export class WorkflowAgent<
1348
1356
 
1349
1357
  if (approvedToolApprovals.length > 0 || deniedToolApprovals.length > 0) {
1350
1358
  const _toolResultMessages: ModelMessage[] = [];
1351
- const toolResultContent: Array<{
1352
- type: 'tool-result';
1359
+ const toolResultContent: LanguageModelV4ToolResultPart[] = [];
1360
+ const approvedRawResults: Array<{
1353
1361
  toolCallId: string;
1354
1362
  toolName: string;
1355
- output:
1356
- | { type: 'text'; value: string }
1357
- | { type: 'json'; value: JSONValue }
1358
- | { type: 'execution-denied'; reason: string | undefined };
1363
+ input: unknown;
1364
+ output: unknown;
1359
1365
  }> = [];
1360
1366
 
1361
1367
  // Execute approved tools
@@ -1410,12 +1416,25 @@ export class WorkflowAgent<
1410
1416
  type: 'tool-result' as const,
1411
1417
  toolCallId: approval.toolCallId,
1412
1418
  toolName: approval.toolName,
1413
- output:
1414
- typeof toolResult === 'string'
1415
- ? { type: 'text' as const, value: toolResult }
1416
- : { type: 'json' as const, value: toolResult },
1419
+ output: await createLanguageModelToolResultOutput({
1420
+ toolCallId: approval.toolCallId,
1421
+ toolName: approval.toolName,
1422
+ input: approval.input,
1423
+ output: toolResult,
1424
+ tool,
1425
+ errorMode: 'none',
1426
+ supportedUrls: {},
1427
+ download,
1428
+ }),
1429
+ });
1430
+ approvedRawResults.push({
1431
+ toolCallId: approval.toolCallId,
1432
+ toolName: approval.toolName,
1433
+ input: approval.input,
1434
+ output: toolResult,
1417
1435
  });
1418
1436
  } catch (error) {
1437
+ const errorMessage = getErrorMessage(error);
1419
1438
  await telemetryDispatcher.onToolExecutionEnd?.({
1420
1439
  toolCall: {
1421
1440
  type: 'tool-call',
@@ -1434,10 +1453,22 @@ export class WorkflowAgent<
1434
1453
  type: 'tool-result' as const,
1435
1454
  toolCallId: approval.toolCallId,
1436
1455
  toolName: approval.toolName,
1437
- output: {
1438
- type: 'text' as const,
1439
- value: getErrorMessage(error),
1440
- },
1456
+ output: await createLanguageModelToolResultOutput({
1457
+ toolCallId: approval.toolCallId,
1458
+ toolName: approval.toolName,
1459
+ input: approval.input,
1460
+ output: errorMessage,
1461
+ tool,
1462
+ errorMode: 'text',
1463
+ supportedUrls: {},
1464
+ download,
1465
+ }),
1466
+ });
1467
+ approvedRawResults.push({
1468
+ toolCallId: approval.toolCallId,
1469
+ toolName: approval.toolName,
1470
+ input: approval.input,
1471
+ output: errorMessage,
1441
1472
  });
1442
1473
  }
1443
1474
  }
@@ -1492,22 +1523,12 @@ export class WorkflowAgent<
1492
1523
  // can transition approved/denied tool parts to the correct state
1493
1524
  // and properly separate them from the subsequent model step.
1494
1525
  if (options.writable && toolResultContent.length > 0) {
1495
- const approvedResults = toolResultContent
1496
- .filter(r => r.output.type !== 'execution-denied')
1497
- .map(r => ({
1498
- toolCallId: r.toolCallId,
1499
- toolName: r.toolName,
1500
- input: approvedToolApprovals.find(
1501
- a => a.toolCallId === r.toolCallId,
1502
- )?.input,
1503
- output: 'value' in r.output ? r.output.value : undefined,
1504
- }));
1505
1526
  const deniedResults = toolResultContent
1506
1527
  .filter(r => r.output.type === 'execution-denied')
1507
1528
  .map(r => ({ toolCallId: r.toolCallId }));
1508
1529
  await writeApprovalToolResults(
1509
1530
  options.writable,
1510
- approvedResults,
1531
+ approvedRawResults,
1511
1532
  deniedResults,
1512
1533
  );
1513
1534
  }
@@ -1516,7 +1537,7 @@ export class WorkflowAgent<
1516
1537
  const modelPrompt = await convertToLanguageModelPrompt({
1517
1538
  prompt,
1518
1539
  supportedUrls: {},
1519
- download: options.experimental_download ?? this.experimentalDownload,
1540
+ download,
1520
1541
  });
1521
1542
 
1522
1543
  const effectiveAbortSignal = mergeAbortSignals(
@@ -1659,7 +1680,7 @@ export class WorkflowAgent<
1659
1680
  messages: LanguageModelV4Prompt,
1660
1681
  perToolContexts: Record<string, Context | undefined>,
1661
1682
  currentStepNumber: number = 0,
1662
- ): Promise<LanguageModelV4ToolResultPart> => {
1683
+ ): Promise<WorkflowToolExecutionResult> => {
1663
1684
  const toolCallEvent: ToolCall = {
1664
1685
  type: 'tool-call',
1665
1686
  toolCallId: toolCall.toolCallId,
@@ -1697,10 +1718,10 @@ export class WorkflowAgent<
1697
1718
  });
1698
1719
 
1699
1720
  const startTime = Date.now();
1700
- let result: LanguageModelV4ToolResultPart;
1721
+ let result: WorkflowToolExecutionResult;
1701
1722
  try {
1702
1723
  const execute = () =>
1703
- executeTool(toolCall, tools, messages, resolvedContext);
1724
+ executeTool(toolCall, tools, messages, resolvedContext, download);
1704
1725
  result =
1705
1726
  telemetryDispatcher.executeTool != null
1706
1727
  ? await telemetryDispatcher.executeTool({
@@ -1740,12 +1761,7 @@ export class WorkflowAgent<
1740
1761
 
1741
1762
  const durationMs = Date.now() - startTime;
1742
1763
  if (mergedOnToolExecutionEnd) {
1743
- const isError =
1744
- result.output &&
1745
- 'type' in result.output &&
1746
- (result.output.type === 'error-text' ||
1747
- result.output.type === 'error-json');
1748
- if (isError) {
1764
+ if (result.isError) {
1749
1765
  await mergedOnToolExecutionEnd({
1750
1766
  toolCall: toolCallEvent,
1751
1767
  stepNumber: currentStepNumber,
@@ -1755,7 +1771,7 @@ export class WorkflowAgent<
1755
1771
  | InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
1756
1772
  | undefined,
1757
1773
  success: false,
1758
- error: 'value' in result.output ? result.output.value : undefined,
1774
+ error: result.rawOutput,
1759
1775
  });
1760
1776
  } else {
1761
1777
  await mergedOnToolExecutionEnd({
@@ -1767,19 +1783,11 @@ export class WorkflowAgent<
1767
1783
  | InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
1768
1784
  | undefined,
1769
1785
  success: true,
1770
- output:
1771
- result.output && 'value' in result.output
1772
- ? result.output.value
1773
- : undefined,
1786
+ output: result.rawOutput,
1774
1787
  });
1775
1788
  }
1776
1789
  }
1777
- if (
1778
- result.output &&
1779
- 'type' in result.output &&
1780
- (result.output.type === 'error-text' ||
1781
- result.output.type === 'error-json')
1782
- ) {
1790
+ if (result.isError) {
1783
1791
  await telemetryDispatcher.onToolExecutionEnd?.({
1784
1792
  toolCall: toolCallEvent,
1785
1793
  stepNumber: currentStepNumber,
@@ -1789,7 +1797,7 @@ export class WorkflowAgent<
1789
1797
  | InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
1790
1798
  | undefined,
1791
1799
  success: false,
1792
- error: 'value' in result.output ? result.output.value : undefined,
1800
+ error: result.rawOutput,
1793
1801
  });
1794
1802
  } else {
1795
1803
  await telemetryDispatcher.onToolExecutionEnd?.({
@@ -1801,10 +1809,7 @@ export class WorkflowAgent<
1801
1809
  | InferToolSetContext<TTools>[keyof InferToolSetContext<TTools>]
1802
1810
  | undefined,
1803
1811
  success: true,
1804
- output:
1805
- result.output && 'value' in result.output
1806
- ? result.output.value
1807
- : undefined,
1812
+ output: result.rawOutput,
1808
1813
  });
1809
1814
  }
1810
1815
  return result;
@@ -1812,7 +1817,7 @@ export class WorkflowAgent<
1812
1817
 
1813
1818
  const recordProviderExecutedToolTelemetry = async (
1814
1819
  toolCall: { toolCallId: string; toolName: string; input: unknown },
1815
- result: LanguageModelV4ToolResultPart,
1820
+ result: WorkflowToolExecutionResult,
1816
1821
  messages: LanguageModelV4Prompt,
1817
1822
  currentStepNumber: number,
1818
1823
  ) => {
@@ -1831,32 +1836,20 @@ export class WorkflowAgent<
1831
1836
  toolContext: undefined,
1832
1837
  });
1833
1838
 
1834
- const isError =
1835
- result.output &&
1836
- 'type' in result.output &&
1837
- (result.output.type === 'error-text' ||
1838
- result.output.type === 'error-json');
1839
-
1840
1839
  await telemetryDispatcher.onToolExecutionEnd?.({
1841
1840
  toolCall: toolCallEvent,
1842
1841
  stepNumber: currentStepNumber,
1843
1842
  durationMs: 0,
1844
1843
  messages: modelMessages,
1845
1844
  toolContext: undefined,
1846
- ...(isError
1845
+ ...(result.isError
1847
1846
  ? {
1848
1847
  success: false as const,
1849
- error:
1850
- result.output && 'value' in result.output
1851
- ? result.output.value
1852
- : undefined,
1848
+ error: result.rawOutput,
1853
1849
  }
1854
1850
  : {
1855
1851
  success: true as const,
1856
- output:
1857
- result.output && 'value' in result.output
1858
- ? result.output.value
1859
- : undefined,
1852
+ output: result.rawOutput,
1860
1853
  }),
1861
1854
  });
1862
1855
  };
@@ -2000,7 +1993,7 @@ export class WorkflowAgent<
2000
1993
  // Execute any executable tools that were also called in this step
2001
1994
  const executableResults = await Promise.all(
2002
1995
  executableToolCalls.map(
2003
- (toolCall): Promise<LanguageModelV4ToolResultPart> =>
1996
+ (toolCall): Promise<WorkflowToolExecutionResult> =>
2004
1997
  executeToolWithCallbacks(
2005
1998
  toolCall,
2006
1999
  effectiveTools as ToolSet,
@@ -2012,11 +2005,15 @@ export class WorkflowAgent<
2012
2005
  );
2013
2006
 
2014
2007
  // Collect provider tool results
2015
- const providerResults: LanguageModelV4ToolResultPart[] =
2016
- providerToolCalls.map(toolCall =>
2017
- resolveProviderToolResult(
2018
- toolCall,
2019
- providerExecutedToolResults,
2008
+ const providerResults: WorkflowToolExecutionResult[] =
2009
+ await Promise.all(
2010
+ providerToolCalls.map(toolCall =>
2011
+ resolveProviderToolResult(
2012
+ toolCall,
2013
+ providerExecutedToolResults,
2014
+ effectiveTools as ToolSet,
2015
+ download,
2016
+ ),
2020
2017
  ),
2021
2018
  );
2022
2019
  await Promise.all(
@@ -2033,9 +2030,9 @@ export class WorkflowAgent<
2033
2030
  const continuationInvalidResults = invalidToolCalls.map(
2034
2031
  createInvalidToolResult,
2035
2032
  );
2036
- const resolvedResults = [
2037
- ...executableResults,
2038
- ...providerResults,
2033
+ const resolvedResults: LanguageModelV4ToolResultPart[] = [
2034
+ ...executableResults.map(result => result.modelResult),
2035
+ ...providerResults.map(result => result.modelResult),
2039
2036
  ...continuationInvalidResults,
2040
2037
  ];
2041
2038
  const executedResults = [...executableResults, ...providerResults];
@@ -2049,11 +2046,12 @@ export class WorkflowAgent<
2049
2046
 
2050
2047
  const allToolResults: ToolResult[] = executedResults.map(r => ({
2051
2048
  type: 'tool-result' as const,
2052
- toolCallId: r.toolCallId,
2053
- toolName: r.toolName,
2054
- input: toolCalls.find(tc => tc.toolCallId === r.toolCallId)
2055
- ?.input,
2056
- output: 'value' in r.output ? r.output.value : undefined,
2049
+ toolCallId: r.modelResult.toolCallId,
2050
+ toolName: r.modelResult.toolName,
2051
+ input: toolCalls.find(
2052
+ tc => tc.toolCallId === r.modelResult.toolCallId,
2053
+ )?.input,
2054
+ output: r.rawOutput,
2057
2055
  }));
2058
2056
 
2059
2057
  if (resolvedResults.length > 0) {
@@ -2134,7 +2132,7 @@ export class WorkflowAgent<
2134
2132
  // Execute client tools (all have execute functions at this point)
2135
2133
  const clientToolResults = await Promise.all(
2136
2134
  nonProviderToolCalls.map(
2137
- (toolCall): Promise<LanguageModelV4ToolResultPart> =>
2135
+ (toolCall): Promise<WorkflowToolExecutionResult> =>
2138
2136
  executeToolWithCallbacks(
2139
2137
  toolCall,
2140
2138
  effectiveTools as ToolSet,
@@ -2146,9 +2144,16 @@ export class WorkflowAgent<
2146
2144
  );
2147
2145
 
2148
2146
  // For provider-executed tools, use the results from the stream
2149
- const providerToolResults: LanguageModelV4ToolResultPart[] =
2150
- providerToolCalls.map(toolCall =>
2151
- resolveProviderToolResult(toolCall, providerExecutedToolResults),
2147
+ const providerToolResults: WorkflowToolExecutionResult[] =
2148
+ await Promise.all(
2149
+ providerToolCalls.map(toolCall =>
2150
+ resolveProviderToolResult(
2151
+ toolCall,
2152
+ providerExecutedToolResults,
2153
+ effectiveTools as ToolSet,
2154
+ download,
2155
+ ),
2156
+ ),
2152
2157
  );
2153
2158
  await Promise.all(
2154
2159
  providerToolCalls.map((toolCall, index) =>
@@ -2167,25 +2172,28 @@ export class WorkflowAgent<
2167
2172
  // Combine executable/provider results in the original order,
2168
2173
  // while preserving invalid tool calls as error results for the
2169
2174
  // next model step without emitting them as synthetic UI success.
2170
- const continuationToolResults = toolCalls.flatMap(tc => {
2171
- const invalidResult = continuationInvalidToolResults.find(
2172
- r => r.toolCallId === tc.toolCallId,
2173
- );
2174
- if (invalidResult) return [invalidResult];
2175
+ const executedToolResults = toolCalls.flatMap(tc => {
2175
2176
  const clientResult = clientToolResults.find(
2176
- r => r.toolCallId === tc.toolCallId,
2177
+ r => r.modelResult.toolCallId === tc.toolCallId,
2177
2178
  );
2178
2179
  if (clientResult) return [clientResult];
2179
2180
  const providerResult = providerToolResults.find(
2180
- r => r.toolCallId === tc.toolCallId,
2181
+ r => r.modelResult.toolCallId === tc.toolCallId,
2181
2182
  );
2182
2183
  if (providerResult) return [providerResult];
2183
2184
  return [];
2184
2185
  });
2185
- const executedToolResults = continuationToolResults.filter(
2186
- result =>
2187
- !invalidToolCalls.some(tc => tc.toolCallId === result.toolCallId),
2188
- );
2186
+ const continuationToolResults = toolCalls.flatMap(tc => {
2187
+ const invalidResult = continuationInvalidToolResults.find(
2188
+ r => r.toolCallId === tc.toolCallId,
2189
+ );
2190
+ if (invalidResult) return [invalidResult];
2191
+ const executedResult = executedToolResults.find(
2192
+ r => r.modelResult.toolCallId === tc.toolCallId,
2193
+ );
2194
+ if (executedResult) return [executedResult.modelResult];
2195
+ return [];
2196
+ });
2189
2197
 
2190
2198
  // Write tool results and step boundaries to the stream so the
2191
2199
  // UI can transition tool parts to output-available state and
@@ -2194,11 +2202,12 @@ export class WorkflowAgent<
2194
2202
  await writeToolResultsWithStepBoundary(
2195
2203
  options.writable,
2196
2204
  executedToolResults.map(r => ({
2197
- toolCallId: r.toolCallId,
2198
- toolName: r.toolName,
2199
- input: toolCalls.find(tc => tc.toolCallId === r.toolCallId)
2200
- ?.input,
2201
- output: 'value' in r.output ? r.output.value : undefined,
2205
+ toolCallId: r.modelResult.toolCallId,
2206
+ toolName: r.modelResult.toolName,
2207
+ input: toolCalls.find(
2208
+ tc => tc.toolCallId === r.modelResult.toolCallId,
2209
+ )?.input,
2210
+ output: r.rawOutput,
2202
2211
  })),
2203
2212
  );
2204
2213
  }
@@ -2212,10 +2221,12 @@ export class WorkflowAgent<
2212
2221
  }));
2213
2222
  lastStepToolResults = executedToolResults.map(r => ({
2214
2223
  type: 'tool-result' as const,
2215
- toolCallId: r.toolCallId,
2216
- toolName: r.toolName,
2217
- input: toolCalls.find(tc => tc.toolCallId === r.toolCallId)?.input,
2218
- output: 'value' in r.output ? r.output.value : undefined,
2224
+ toolCallId: r.modelResult.toolCallId,
2225
+ toolName: r.modelResult.toolName,
2226
+ input: toolCalls.find(
2227
+ tc => tc.toolCallId === r.modelResult.toolCallId,
2228
+ )?.input,
2229
+ output: r.rawOutput,
2219
2230
  }));
2220
2231
 
2221
2232
  result = await iterator.next(continuationToolResults);
@@ -2517,30 +2528,15 @@ function aggregateUsage(steps: StepResult<any, any>[]): LanguageModelUsage {
2517
2528
  } as LanguageModelUsage;
2518
2529
  }
2519
2530
 
2520
- // Matches AI SDK's getErrorMessage from @ai-sdk/provider-utils
2521
- function getErrorMessage(error: unknown): string {
2522
- if (error == null) {
2523
- return 'unknown error';
2524
- }
2525
-
2526
- if (typeof error === 'string') {
2527
- return error;
2528
- }
2529
-
2530
- if (error instanceof Error) {
2531
- return error.message;
2532
- }
2533
-
2534
- return JSON.stringify(error);
2535
- }
2536
-
2537
- function resolveProviderToolResult(
2538
- toolCall: { toolCallId: string; toolName: string },
2531
+ async function resolveProviderToolResult(
2532
+ toolCall: { toolCallId: string; toolName: string; input: unknown },
2539
2533
  providerExecutedToolResults?: Map<
2540
2534
  string,
2541
2535
  { toolCallId: string; toolName: string; result: unknown; isError?: boolean }
2542
2536
  >,
2543
- ): LanguageModelV4ToolResultPart {
2537
+ tools?: ToolSet,
2538
+ download?: DownloadFunction,
2539
+ ): Promise<WorkflowToolExecutionResult> {
2544
2540
  const streamResult = providerExecutedToolResults?.get(toolCall.toolCallId);
2545
2541
  if (!streamResult) {
2546
2542
  console.warn(
@@ -2548,36 +2544,45 @@ function resolveProviderToolResult(
2548
2544
  `did not receive a result from the stream. This may indicate a provider issue.`,
2549
2545
  );
2550
2546
  return {
2551
- type: 'tool-result' as const,
2552
- toolCallId: toolCall.toolCallId,
2553
- toolName: toolCall.toolName,
2554
- output: {
2555
- type: 'text' as const,
2556
- value: '',
2547
+ modelResult: {
2548
+ type: 'tool-result' as const,
2549
+ toolCallId: toolCall.toolCallId,
2550
+ toolName: toolCall.toolName,
2551
+ output: {
2552
+ type: 'text' as const,
2553
+ value: '',
2554
+ },
2557
2555
  },
2556
+ rawOutput: '',
2557
+ isError: false,
2558
2558
  };
2559
2559
  }
2560
2560
 
2561
2561
  const result = streamResult.result;
2562
- const isString = typeof result === 'string';
2562
+ const errorMode = streamResult.isError
2563
+ ? typeof result === 'string'
2564
+ ? 'text'
2565
+ : 'json'
2566
+ : 'none';
2563
2567
 
2564
2568
  return {
2565
- type: 'tool-result' as const,
2566
- toolCallId: toolCall.toolCallId,
2567
- toolName: toolCall.toolName,
2568
- output: isString
2569
- ? streamResult.isError
2570
- ? { type: 'error-text' as const, value: result }
2571
- : { type: 'text' as const, value: result }
2572
- : streamResult.isError
2573
- ? {
2574
- type: 'error-json' as const,
2575
- value: result as JSONValue,
2576
- }
2577
- : {
2578
- type: 'json' as const,
2579
- value: result as JSONValue,
2580
- },
2569
+ modelResult: {
2570
+ type: 'tool-result' as const,
2571
+ toolCallId: toolCall.toolCallId,
2572
+ toolName: toolCall.toolName,
2573
+ output: await createLanguageModelToolResultOutput({
2574
+ toolCallId: toolCall.toolCallId,
2575
+ toolName: toolCall.toolName,
2576
+ input: toolCall.input,
2577
+ output: result,
2578
+ tool: tools?.[toolCall.toolName],
2579
+ errorMode,
2580
+ supportedUrls: {},
2581
+ download,
2582
+ }),
2583
+ },
2584
+ rawOutput: result,
2585
+ isError: streamResult.isError === true,
2581
2586
  };
2582
2587
  }
2583
2588
 
@@ -2610,7 +2615,8 @@ async function executeTool(
2610
2615
  tools: ToolSet,
2611
2616
  messages: LanguageModelV4Prompt,
2612
2617
  context?: unknown,
2613
- ): Promise<LanguageModelV4ToolResultPart> {
2618
+ download?: DownloadFunction,
2619
+ ): Promise<WorkflowToolExecutionResult> {
2614
2620
  const tool = tools[toolCall.toolName];
2615
2621
  if (!tool) throw new Error(`Tool "${toolCall.toolName}" not found`);
2616
2622
  if (typeof tool.execute !== 'function') {
@@ -2621,6 +2627,7 @@ async function executeTool(
2621
2627
  }
2622
2628
  // Input is already parsed and validated by streamModelCall's parseToolCall
2623
2629
  const parsedInput = toolCall.input;
2630
+ let toolResult: unknown;
2624
2631
 
2625
2632
  try {
2626
2633
  // Extract execute function to avoid binding `this` to the tool object.
@@ -2629,41 +2636,58 @@ async function executeTool(
2629
2636
  // When the execute function is a workflow step (marked with 'use step'),
2630
2637
  // the step system captures `this` for serialization, causing failures.
2631
2638
  const { execute } = tool;
2632
- const toolResult = await execute(parsedInput, {
2639
+ toolResult = await execute(parsedInput, {
2633
2640
  toolCallId: toolCall.toolCallId,
2634
2641
  // Pass the conversation messages to the tool so it has context about the conversation
2635
2642
  messages,
2636
2643
  // Pass per-tool context to the tool (resolved from `toolsContext`)
2637
2644
  context,
2638
2645
  });
2639
-
2640
- // Use the appropriate output type based on the result
2641
- // AI SDK supports 'text' for strings and 'json' for objects
2642
- const output =
2643
- typeof toolResult === 'string'
2644
- ? { type: 'text' as const, value: toolResult }
2645
- : { type: 'json' as const, value: toolResult };
2646
-
2647
- return {
2648
- type: 'tool-result' as const,
2649
- toolCallId: toolCall.toolCallId,
2650
- toolName: toolCall.toolName,
2651
- output,
2652
- };
2653
2646
  } catch (error) {
2654
2647
  // Convert tool errors to error-text results sent back to the model,
2655
2648
  // allowing the agent to recover rather than killing the entire stream.
2656
2649
  // This aligns with AI SDK's streamText behavior for individual tool failures.
2650
+ const errorMessage = getErrorMessage(error);
2657
2651
  return {
2658
- type: 'tool-result',
2659
- toolCallId: toolCall.toolCallId,
2660
- toolName: toolCall.toolName,
2661
- output: {
2662
- type: 'error-text',
2663
- value: getErrorMessage(error),
2652
+ modelResult: {
2653
+ type: 'tool-result',
2654
+ toolCallId: toolCall.toolCallId,
2655
+ toolName: toolCall.toolName,
2656
+ output: await createLanguageModelToolResultOutput({
2657
+ toolCallId: toolCall.toolCallId,
2658
+ toolName: toolCall.toolName,
2659
+ input: parsedInput,
2660
+ output: errorMessage,
2661
+ tool,
2662
+ errorMode: 'text',
2663
+ supportedUrls: {},
2664
+ download,
2665
+ }),
2664
2666
  },
2667
+ rawOutput: errorMessage,
2668
+ isError: true,
2665
2669
  };
2666
2670
  }
2671
+
2672
+ return {
2673
+ modelResult: {
2674
+ type: 'tool-result' as const,
2675
+ toolCallId: toolCall.toolCallId,
2676
+ toolName: toolCall.toolName,
2677
+ output: await createLanguageModelToolResultOutput({
2678
+ toolCallId: toolCall.toolCallId,
2679
+ toolName: toolCall.toolName,
2680
+ input: parsedInput,
2681
+ output: toolResult,
2682
+ tool,
2683
+ errorMode: 'none',
2684
+ supportedUrls: {},
2685
+ download,
2686
+ }),
2687
+ },
2688
+ rawOutput: toolResult,
2689
+ isError: false,
2690
+ };
2667
2691
  }
2668
2692
 
2669
2693
  /**