@ai-sdk/workflow 1.0.0-beta.8 → 1.0.0-canary.32

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.
@@ -7,21 +7,24 @@ import type {
7
7
  SharedV4ProviderOptions,
8
8
  } from '@ai-sdk/provider';
9
9
  import {
10
- type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
10
+ Output,
11
+ experimental_filterActiveTools as filterActiveTools,
11
12
  type FinishReason,
12
13
  type LanguageModelResponseMetadata,
13
14
  type LanguageModelUsage,
15
+ type Experimental_LanguageModelStreamPart as ModelCallStreamPart,
14
16
  type ModelMessage,
15
- Output,
16
17
  type StepResult,
17
18
  type StopCondition,
18
- type StreamTextOnStepFinishCallback,
19
+ type GenerateTextOnStepFinishCallback,
19
20
  type SystemModelMessage,
21
+ type ActiveTools,
20
22
  type ToolCallRepairFunction,
21
23
  type ToolChoice,
22
24
  type ToolSet,
23
25
  type UIMessage,
24
- LanguageModel,
26
+ type LanguageModel,
27
+ type Prompt,
25
28
  } from 'ai';
26
29
  import {
27
30
  convertToLanguageModelPrompt,
@@ -30,19 +33,18 @@ import {
30
33
  standardizePrompt,
31
34
  } from 'ai/internal';
32
35
  import { streamTextIterator } from './stream-text-iterator.js';
33
- import type { CompatibleLanguageModel } from './types.js';
34
36
 
35
37
  // Re-export for consumers
36
38
  export type { CompatibleLanguageModel } from './types.js';
37
39
 
38
40
  /**
39
41
  * Callback function to be called after each step completes.
40
- * Alias for the AI SDK's StreamTextOnStepFinishCallback, using
42
+ * Alias for the AI SDK's GenerateTextOnStepFinishCallback, using
41
43
  * WorkflowAgent-consistent naming.
42
44
  */
43
45
  export type WorkflowAgentOnStepFinishCallback<
44
46
  TTools extends ToolSet = ToolSet,
45
- > = StreamTextOnStepFinishCallback<TTools, any>;
47
+ > = GenerateTextOnStepFinishCallback<TTools, any>;
46
48
 
47
49
  /**
48
50
  * Infer the type of the tools of a workflow agent.
@@ -92,7 +94,7 @@ export type ProviderOptions = SharedV4ProviderOptions;
92
94
  /**
93
95
  * Telemetry settings for observability.
94
96
  */
95
- export interface TelemetrySettings {
97
+ export interface TelemetryOptions {
96
98
  /**
97
99
  * Enable or disable telemetry. Defaults to true.
98
100
  */
@@ -339,7 +341,11 @@ export interface PrepareCallOptions<
339
341
  tools: TTools;
340
342
  instructions?: string | SystemModelMessage | Array<SystemModelMessage>;
341
343
  toolChoice?: ToolChoice<TTools>;
342
- experimental_telemetry?: TelemetrySettings;
344
+ telemetry?: TelemetryOptions;
345
+ /**
346
+ * @deprecated Use `telemetry` instead. This alias will be removed in a future major release.
347
+ */
348
+ experimental_telemetry?: TelemetryOptions;
343
349
  experimental_context?: unknown;
344
350
  messages: ModelMessage[];
345
351
  }
@@ -405,9 +411,16 @@ export interface WorkflowAgentOptions<
405
411
  toolChoice?: ToolChoice<TTools>;
406
412
 
407
413
  /**
408
- * Optional telemetry configuration (experimental).
414
+ * Optional telemetry configuration.
409
415
  */
410
- experimental_telemetry?: TelemetrySettings;
416
+ telemetry?: TelemetryOptions;
417
+
418
+ /**
419
+ * Optional telemetry configuration.
420
+ *
421
+ * @deprecated Use `telemetry` instead. This alias will be removed in a future major release.
422
+ */
423
+ experimental_telemetry?: TelemetryOptions;
411
424
 
412
425
  /**
413
426
  * Default context that is passed into tool execution for every stream call on this agent.
@@ -434,7 +447,7 @@ export interface WorkflowAgentOptions<
434
447
  *
435
448
  * Per-stream `activeTools` values passed to `stream()` override this default.
436
449
  */
437
- activeTools?: Array<keyof NoInfer<TTools>>;
450
+ activeTools?: ActiveTools<NoInfer<TTools>>;
438
451
 
439
452
  /**
440
453
  * Default output specification for structured outputs.
@@ -490,12 +503,12 @@ export interface WorkflowAgentOptions<
490
503
  /**
491
504
  * Callback called before a tool's execute function runs.
492
505
  */
493
- experimental_onToolCallStart?: WorkflowAgentOnToolCallStartCallback;
506
+ experimental_onToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback;
494
507
 
495
508
  /**
496
509
  * Callback called after a tool execution completes.
497
510
  */
498
- experimental_onToolCallFinish?: WorkflowAgentOnToolCallFinishCallback;
511
+ experimental_onToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback;
499
512
 
500
513
  /**
501
514
  * Prepare the parameters for the stream call.
@@ -595,7 +608,7 @@ export type WorkflowAgentOnStepStartCallback<TTools extends ToolSet = ToolSet> =
595
608
  /**
596
609
  * Callback that is called before a tool's execute function runs.
597
610
  */
598
- export type WorkflowAgentOnToolCallStartCallback = (event: {
611
+ export type WorkflowAgentOnToolExecutionStartCallback = (event: {
599
612
  /** The tool call being executed */
600
613
  readonly toolCall: ToolCall;
601
614
  /** The current step number (0-based) */
@@ -607,7 +620,7 @@ export type WorkflowAgentOnToolCallStartCallback = (event: {
607
620
  * Uses a discriminated union pattern: check `success` to determine
608
621
  * whether `output` or `error` is available.
609
622
  */
610
- export type WorkflowAgentOnToolCallFinishCallback = (
623
+ export type WorkflowAgentOnToolExecutionEndCallback = (
611
624
  event:
612
625
  | {
613
626
  /** The tool call that was executed */
@@ -714,13 +727,6 @@ export type WorkflowAgentStreamOptions<
714
727
  | StopCondition<NoInfer<ToolSet>, any>
715
728
  | Array<StopCondition<NoInfer<ToolSet>, any>>;
716
729
 
717
- /**
718
- * Maximum number of sequential LLM calls (steps), e.g. when you use tool calls.
719
- * A maximum number can be set to prevent infinite loops in the case of misconfigured tools.
720
- * By default, it's unlimited (the agent loops until completion).
721
- */
722
- maxSteps?: number;
723
-
724
730
  /**
725
731
  * The tool choice strategy. Default: 'auto'.
726
732
  * Overrides the toolChoice from the constructor if provided.
@@ -731,12 +737,19 @@ export type WorkflowAgentStreamOptions<
731
737
  * Limits the tools that are available for the model to call without
732
738
  * changing the tool call and result types in the result.
733
739
  */
734
- activeTools?: Array<keyof NoInfer<TTools>>;
740
+ activeTools?: ActiveTools<NoInfer<TTools>>;
741
+
742
+ /**
743
+ * Optional telemetry configuration.
744
+ */
745
+ telemetry?: TelemetryOptions;
735
746
 
736
747
  /**
737
- * Optional telemetry configuration (experimental).
748
+ * Optional telemetry configuration.
749
+ *
750
+ * @deprecated Use `telemetry` instead. This alias will be removed in a future major release.
738
751
  */
739
- experimental_telemetry?: TelemetrySettings;
752
+ experimental_telemetry?: TelemetryOptions;
740
753
 
741
754
  /**
742
755
  * Context that is passed into tool execution.
@@ -833,12 +846,12 @@ export type WorkflowAgentStreamOptions<
833
846
  /**
834
847
  * Callback called before a tool's execute function runs.
835
848
  */
836
- experimental_onToolCallStart?: WorkflowAgentOnToolCallStartCallback;
849
+ experimental_onToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback;
837
850
 
838
851
  /**
839
852
  * Callback called after a tool execution completes.
840
853
  */
841
- experimental_onToolCallFinish?: WorkflowAgentOnToolCallFinishCallback;
854
+ experimental_onToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback;
842
855
 
843
856
  /**
844
857
  * Callback function called before each step in the agent loop.
@@ -1005,12 +1018,12 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1005
1018
  | Array<SystemModelMessage>;
1006
1019
  private generationSettings: GenerationSettings;
1007
1020
  private toolChoice?: ToolChoice<TBaseTools>;
1008
- private telemetry?: TelemetrySettings;
1021
+ private telemetry?: TelemetryOptions;
1009
1022
  private experimentalContext: unknown;
1010
1023
  private stopWhen?:
1011
1024
  | StopCondition<ToolSet, any>
1012
1025
  | Array<StopCondition<ToolSet, any>>;
1013
- private activeTools?: Array<keyof TBaseTools>;
1026
+ private activeTools?: ActiveTools<TBaseTools>;
1014
1027
  private output?: OutputSpecification<any, any>;
1015
1028
  private experimentalRepairToolCall?: ToolCallRepairFunction<TBaseTools>;
1016
1029
  private experimentalDownload?: DownloadFunction;
@@ -1019,8 +1032,8 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1019
1032
  private constructorOnFinish?: WorkflowAgentOnFinishCallback<ToolSet>;
1020
1033
  private constructorOnStart?: WorkflowAgentOnStartCallback;
1021
1034
  private constructorOnStepStart?: WorkflowAgentOnStepStartCallback;
1022
- private constructorOnToolCallStart?: WorkflowAgentOnToolCallStartCallback;
1023
- private constructorOnToolCallFinish?: WorkflowAgentOnToolCallFinishCallback;
1035
+ private constructorOnToolExecutionStart?: WorkflowAgentOnToolExecutionStartCallback;
1036
+ private constructorOnToolExecutionEnd?: WorkflowAgentOnToolExecutionEndCallback;
1024
1037
  private prepareCall?: PrepareCallCallback<TBaseTools>;
1025
1038
 
1026
1039
  constructor(options: WorkflowAgentOptions<TBaseTools>) {
@@ -1030,7 +1043,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1030
1043
  // `instructions` takes precedence over deprecated `system`
1031
1044
  this.instructions = options.instructions ?? options.system;
1032
1045
  this.toolChoice = options.toolChoice;
1033
- this.telemetry = options.experimental_telemetry;
1046
+ this.telemetry = options.telemetry ?? options.experimental_telemetry;
1034
1047
  this.experimentalContext = options.experimental_context;
1035
1048
  this.stopWhen = options.stopWhen;
1036
1049
  this.activeTools = options.activeTools;
@@ -1042,8 +1055,10 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1042
1055
  this.constructorOnFinish = options.onFinish;
1043
1056
  this.constructorOnStart = options.experimental_onStart;
1044
1057
  this.constructorOnStepStart = options.experimental_onStepStart;
1045
- this.constructorOnToolCallStart = options.experimental_onToolCallStart;
1046
- this.constructorOnToolCallFinish = options.experimental_onToolCallFinish;
1058
+ this.constructorOnToolExecutionStart =
1059
+ options.experimental_onToolExecutionStart;
1060
+ this.constructorOnToolExecutionEnd =
1061
+ options.experimental_onToolExecutionEnd;
1047
1062
  this.prepareCall = options.prepareCall;
1048
1063
 
1049
1064
  // Extract generation settings
@@ -1085,7 +1100,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1085
1100
  options.experimental_context ?? this.experimentalContext;
1086
1101
  let effectiveToolChoiceFromPrepare = options.toolChoice ?? this.toolChoice;
1087
1102
  let effectiveTelemetryFromPrepare =
1088
- options.experimental_telemetry ?? this.telemetry;
1103
+ options.telemetry ?? options.experimental_telemetry ?? this.telemetry;
1089
1104
 
1090
1105
  // Resolve messages for prepareCall: use messages directly, or convert prompt
1091
1106
  const resolvedMessagesForPrepareCall: ModelMessage[] =
@@ -1101,6 +1116,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1101
1116
  tools: this.tools,
1102
1117
  instructions: effectiveInstructions,
1103
1118
  toolChoice: effectiveToolChoiceFromPrepare as ToolChoice<TBaseTools>,
1119
+ telemetry: effectiveTelemetryFromPrepare,
1104
1120
  experimental_telemetry: effectiveTelemetryFromPrepare,
1105
1121
  experimental_context: effectiveExperimentalContext,
1106
1122
  messages: resolvedMessagesForPrepareCall,
@@ -1119,7 +1135,9 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1119
1135
  if (prepared.toolChoice !== undefined)
1120
1136
  effectiveToolChoiceFromPrepare =
1121
1137
  prepared.toolChoice as ToolChoice<TBaseTools>;
1122
- if (prepared.experimental_telemetry !== undefined)
1138
+ if (prepared.telemetry !== undefined)
1139
+ effectiveTelemetryFromPrepare = prepared.telemetry;
1140
+ else if (prepared.experimental_telemetry !== undefined)
1123
1141
  effectiveTelemetryFromPrepare = prepared.experimental_telemetry;
1124
1142
  if (prepared.maxOutputTokens !== undefined)
1125
1143
  effectiveGenerationSettings.maxOutputTokens = prepared.maxOutputTokens;
@@ -1146,10 +1164,11 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1146
1164
 
1147
1165
  const prompt = await standardizePrompt({
1148
1166
  system: effectiveInstructions,
1167
+ allowSystemInMessages: true, // TODO: consider exposing this as a parameter
1149
1168
  ...(effectivePrompt != null
1150
1169
  ? { prompt: effectivePrompt }
1151
1170
  : { messages: effectiveMessages! }),
1152
- });
1171
+ } as Prompt);
1153
1172
 
1154
1173
  // Process tool approval responses before starting the agent loop.
1155
1174
  // This mirrors how stream-text.ts handles tool-approval-response parts:
@@ -1282,9 +1301,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1282
1301
 
1283
1302
  const effectiveAbortSignal = mergeAbortSignals(
1284
1303
  options.abortSignal ?? effectiveGenerationSettings.abortSignal,
1285
- options.timeout != null
1286
- ? AbortSignal.timeout(options.timeout)
1287
- : undefined,
1304
+ options.timeout,
1288
1305
  );
1289
1306
 
1290
1307
  // Merge generation settings: constructor defaults < prepareCall < stream options
@@ -1341,13 +1358,13 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1341
1358
  this.constructorOnStepStart,
1342
1359
  options.experimental_onStepStart,
1343
1360
  );
1344
- const mergedOnToolCallStart = mergeCallbacks(
1345
- this.constructorOnToolCallStart,
1346
- options.experimental_onToolCallStart,
1361
+ const mergedOnToolExecutionStart = mergeCallbacks(
1362
+ this.constructorOnToolExecutionStart,
1363
+ options.experimental_onToolExecutionStart,
1347
1364
  );
1348
- const mergedOnToolCallFinish = mergeCallbacks(
1349
- this.constructorOnToolCallFinish,
1350
- options.experimental_onToolCallFinish,
1365
+ const mergedOnToolExecutionEnd = mergeCallbacks(
1366
+ this.constructorOnToolExecutionEnd,
1367
+ options.experimental_onToolExecutionEnd,
1351
1368
  );
1352
1369
 
1353
1370
  // Determine effective tool choice
@@ -1360,7 +1377,10 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1360
1377
  const effectiveActiveTools = options.activeTools ?? this.activeTools;
1361
1378
  const effectiveTools =
1362
1379
  effectiveActiveTools && effectiveActiveTools.length > 0
1363
- ? filterTools(this.tools, effectiveActiveTools as string[])
1380
+ ? (filterActiveTools({
1381
+ tools: this.tools,
1382
+ activeTools: effectiveActiveTools,
1383
+ }) ?? this.tools)
1364
1384
  : this.tools;
1365
1385
 
1366
1386
  // Initialize context
@@ -1380,7 +1400,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1380
1400
  });
1381
1401
  }
1382
1402
 
1383
- // Helper to wrap executeTool with onToolCallStart/onToolCallFinish callbacks
1403
+ // Helper to wrap executeTool with onToolExecutionStart/onToolExecutionEnd callbacks
1384
1404
  const executeToolWithCallbacks = async (
1385
1405
  toolCall: { toolCallId: string; toolName: string; input: unknown },
1386
1406
  tools: ToolSet,
@@ -1395,8 +1415,8 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1395
1415
  input: toolCall.input,
1396
1416
  };
1397
1417
 
1398
- if (mergedOnToolCallStart) {
1399
- await mergedOnToolCallStart({
1418
+ if (mergedOnToolExecutionStart) {
1419
+ await mergedOnToolExecutionStart({
1400
1420
  toolCall: toolCallEvent,
1401
1421
  stepNumber: currentStepNumber,
1402
1422
  });
@@ -1408,8 +1428,8 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1408
1428
  result = await executeTool(toolCall, tools, messages, context);
1409
1429
  } catch (err) {
1410
1430
  const durationMs = Date.now() - startTime;
1411
- if (mergedOnToolCallFinish) {
1412
- await mergedOnToolCallFinish({
1431
+ if (mergedOnToolExecutionEnd) {
1432
+ await mergedOnToolExecutionEnd({
1413
1433
  toolCall: toolCallEvent,
1414
1434
  stepNumber: currentStepNumber,
1415
1435
  durationMs,
@@ -1421,14 +1441,14 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1421
1441
  }
1422
1442
 
1423
1443
  const durationMs = Date.now() - startTime;
1424
- if (mergedOnToolCallFinish) {
1444
+ if (mergedOnToolExecutionEnd) {
1425
1445
  const isError =
1426
1446
  result.output &&
1427
1447
  'type' in result.output &&
1428
1448
  (result.output.type === 'error-text' ||
1429
1449
  result.output.type === 'error-json');
1430
1450
  if (isError) {
1431
- await mergedOnToolCallFinish({
1451
+ await mergedOnToolExecutionEnd({
1432
1452
  toolCall: toolCallEvent,
1433
1453
  stepNumber: currentStepNumber,
1434
1454
  durationMs,
@@ -1436,7 +1456,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1436
1456
  error: 'value' in result.output ? result.output.value : undefined,
1437
1457
  });
1438
1458
  } else {
1439
- await mergedOnToolCallFinish({
1459
+ await mergedOnToolExecutionEnd({
1440
1460
  toolCall: toolCallEvent,
1441
1461
  stepNumber: currentStepNumber,
1442
1462
  durationMs,
@@ -1471,7 +1491,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1471
1491
  writable: options.writable,
1472
1492
  prompt: modelPrompt,
1473
1493
  stopConditions: options.stopWhen ?? this.stopWhen,
1474
- maxSteps: options.maxSteps,
1494
+
1475
1495
  onStepFinish: mergedOnStepFinish,
1476
1496
  onStepStart: mergedOnStepStart,
1477
1497
  onError: options.onError,
@@ -1481,7 +1501,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1481
1501
  generationSettings: mergedGenerationSettings,
1482
1502
  toolChoice: effectiveToolChoice as ToolChoice<ToolSet>,
1483
1503
  experimental_context: experimentalContext,
1484
- experimental_telemetry: effectiveTelemetry,
1504
+ telemetry: effectiveTelemetry,
1485
1505
  includeRawChunks: options.includeRawChunks ?? false,
1486
1506
  repairToolCall: (options.experimental_repairToolCall ??
1487
1507
  this.experimentalRepairToolCall) as
@@ -1525,11 +1545,16 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1525
1545
 
1526
1546
  // Only execute tools if there are tool calls
1527
1547
  if (toolCalls.length > 0) {
1548
+ const invalidToolCalls = toolCalls.filter(tc => tc.invalid === true);
1549
+ const validToolCalls = toolCalls.filter(tc => tc.invalid !== true);
1550
+
1528
1551
  // Separate provider-executed tool calls from client-executed ones
1529
- const nonProviderToolCalls = toolCalls.filter(
1552
+ const nonProviderToolCalls = validToolCalls.filter(
1530
1553
  tc => !tc.providerExecuted,
1531
1554
  );
1532
- const providerToolCalls = toolCalls.filter(tc => tc.providerExecuted);
1555
+ const providerToolCalls = validToolCalls.filter(
1556
+ tc => tc.providerExecuted,
1557
+ );
1533
1558
 
1534
1559
  // Check which tools need approval (can be async)
1535
1560
  const approvalNeeded = await Promise.all(
@@ -1593,7 +1618,15 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1593
1618
  ),
1594
1619
  );
1595
1620
 
1596
- const resolvedResults = [...executableResults, ...providerResults];
1621
+ const continuationInvalidResults = invalidToolCalls.map(
1622
+ createInvalidToolResult,
1623
+ );
1624
+ const resolvedResults = [
1625
+ ...executableResults,
1626
+ ...providerResults,
1627
+ ...continuationInvalidResults,
1628
+ ];
1629
+ const executedResults = [...executableResults, ...providerResults];
1597
1630
 
1598
1631
  const allToolCalls: ToolCall[] = toolCalls.map(tc => ({
1599
1632
  type: 'tool-call' as const,
@@ -1602,7 +1635,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1602
1635
  input: tc.input,
1603
1636
  }));
1604
1637
 
1605
- const allToolResults: ToolResult[] = resolvedResults.map(r => ({
1638
+ const allToolResults: ToolResult[] = executedResults.map(r => ({
1606
1639
  type: 'tool-result' as const,
1607
1640
  toolCallId: r.toolCallId,
1608
1641
  toolName: r.toolName,
@@ -1690,25 +1723,32 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1690
1723
  providerToolCalls.map(toolCall =>
1691
1724
  resolveProviderToolResult(toolCall, providerExecutedToolResults),
1692
1725
  );
1726
+ const continuationInvalidToolResults = invalidToolCalls.map(
1727
+ createInvalidToolResult,
1728
+ );
1693
1729
 
1694
- // Combine results in the original order
1695
- const toolResults = toolCalls.map(tc => {
1730
+ // Combine executable/provider results in the original order,
1731
+ // while preserving invalid tool calls as error results for the
1732
+ // next model step without emitting them as synthetic UI success.
1733
+ const continuationToolResults = toolCalls.flatMap(tc => {
1734
+ const invalidResult = continuationInvalidToolResults.find(
1735
+ r => r.toolCallId === tc.toolCallId,
1736
+ );
1737
+ if (invalidResult) return [invalidResult];
1696
1738
  const clientResult = clientToolResults.find(
1697
1739
  r => r.toolCallId === tc.toolCallId,
1698
1740
  );
1699
- if (clientResult) return clientResult;
1741
+ if (clientResult) return [clientResult];
1700
1742
  const providerResult = providerToolResults.find(
1701
1743
  r => r.toolCallId === tc.toolCallId,
1702
1744
  );
1703
- if (providerResult) return providerResult;
1704
- // This should never happen, but return empty result as fallback
1705
- return {
1706
- type: 'tool-result' as const,
1707
- toolCallId: tc.toolCallId,
1708
- toolName: tc.toolName,
1709
- output: { type: 'text' as const, value: '' },
1710
- };
1745
+ if (providerResult) return [providerResult];
1746
+ return [];
1711
1747
  });
1748
+ const executedToolResults = continuationToolResults.filter(
1749
+ result =>
1750
+ !invalidToolCalls.some(tc => tc.toolCallId === result.toolCallId),
1751
+ );
1712
1752
 
1713
1753
  // Write tool results and step boundaries to the stream so the
1714
1754
  // UI can transition tool parts to output-available state and
@@ -1716,7 +1756,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1716
1756
  if (options.writable) {
1717
1757
  await writeToolResultsWithStepBoundary(
1718
1758
  options.writable,
1719
- toolResults.map(r => ({
1759
+ executedToolResults.map(r => ({
1720
1760
  toolCallId: r.toolCallId,
1721
1761
  toolName: r.toolName,
1722
1762
  input: toolCalls.find(tc => tc.toolCallId === r.toolCallId)
@@ -1733,7 +1773,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1733
1773
  toolName: tc.toolName,
1734
1774
  input: tc.input,
1735
1775
  }));
1736
- lastStepToolResults = toolResults.map(r => ({
1776
+ lastStepToolResults = executedToolResults.map(r => ({
1737
1777
  type: 'tool-result' as const,
1738
1778
  toolCallId: r.toolCallId,
1739
1779
  toolName: r.toolName,
@@ -1741,7 +1781,7 @@ export class WorkflowAgent<TBaseTools extends ToolSet = ToolSet> {
1741
1781
  output: 'value' in r.output ? r.output.value : undefined,
1742
1782
  }));
1743
1783
 
1744
- result = await iterator.next(toolResults);
1784
+ result = await iterator.next(continuationToolResults);
1745
1785
  } else {
1746
1786
  // Final step with no tool calls - reset tracking
1747
1787
  lastStepToolCalls = [];
@@ -1979,19 +2019,6 @@ function aggregateUsage(steps: StepResult<any, any>[]): LanguageModelUsage {
1979
2019
  } as LanguageModelUsage;
1980
2020
  }
1981
2021
 
1982
- function filterTools<TTools extends ToolSet>(
1983
- tools: TTools,
1984
- activeTools: string[],
1985
- ): ToolSet {
1986
- const filtered: ToolSet = {};
1987
- for (const toolName of activeTools) {
1988
- if (toolName in tools) {
1989
- filtered[toolName] = tools[toolName];
1990
- }
1991
- }
1992
- return filtered;
1993
- }
1994
-
1995
2022
  // Matches AI SDK's getErrorMessage from @ai-sdk/provider-utils
1996
2023
  function getErrorMessage(error: unknown): string {
1997
2024
  if (error == null) {
@@ -2056,6 +2083,22 @@ function resolveProviderToolResult(
2056
2083
  };
2057
2084
  }
2058
2085
 
2086
+ function createInvalidToolResult(toolCall: {
2087
+ toolCallId: string;
2088
+ toolName: string;
2089
+ error?: unknown;
2090
+ }): LanguageModelV4ToolResultPart {
2091
+ return {
2092
+ type: 'tool-result' as const,
2093
+ toolCallId: toolCall.toolCallId,
2094
+ toolName: toolCall.toolName,
2095
+ output: {
2096
+ type: 'error-text' as const,
2097
+ value: getErrorMessage(toolCall.error),
2098
+ },
2099
+ };
2100
+ }
2101
+
2059
2102
  async function executeTool(
2060
2103
  toolCall: { toolCallId: string; toolName: string; input: unknown },
2061
2104
  tools: ToolSet,
@@ -1,12 +1,12 @@
1
1
  import {
2
+ parseJsonEventStream,
3
+ uiMessageChunkSchema,
2
4
  type ChatRequestOptions,
3
5
  type ChatTransport,
4
6
  type PrepareReconnectToStreamRequest,
5
7
  type PrepareSendMessagesRequest,
6
- parseJsonEventStream,
7
8
  type UIMessage,
8
9
  type UIMessageChunk,
9
- uiMessageChunkSchema,
10
10
  } from 'ai';
11
11
  import {
12
12
  convertAsyncIteratorToReadableStream,