@ai-sdk/workflow 2.0.69 → 2.0.70

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @ai-sdk/workflow
2
2
 
3
+ ## 2.0.70
4
+
5
+ ### Patch Changes
6
+
7
+ - 1506ee1: Fix `WorkflowChatTransport` retrying indefinitely when successful reconnect responses contain no UI message chunks. Empty streams now count toward `maxConsecutiveErrors`, while streams that receive chunks continue to advance the resume cursor and reset the error count.
8
+ - 7400215: fix(workflow): retain streamed terminal assistant responses in conversation messages
9
+ - 9aef724: fix(workflow): report incomplete no-output model streams as errors
10
+ - 507cd9d: fix(workflow): include actionable details in tool input validation errors
11
+ - a155baa: fix(workflow): dispatch WorkflowAgent model call telemetry to globally registered integrations from the model step
12
+ - 7c0410d: fix(workflow): preserve provider-executed tool results and invalid local tool call error results on length completion
13
+ - Updated dependencies [eab278a]
14
+ - Updated dependencies [26d64e9]
15
+ - Updated dependencies [9667bf2]
16
+ - Updated dependencies [75b3ea2]
17
+ - Updated dependencies [69f4b82]
18
+ - Updated dependencies [e57803c]
19
+ - Updated dependencies [4c705d9]
20
+ - Updated dependencies [81663cc]
21
+ - Updated dependencies [5150d50]
22
+ - Updated dependencies [9aef724]
23
+ - Updated dependencies [7d0458b]
24
+ - ai@7.0.138
25
+ - @ai-sdk/provider-utils@5.0.59
26
+ - @ai-sdk/provider@4.0.27
27
+
3
28
  ## 2.0.69
4
29
 
5
30
  ### Patch Changes
package/dist/client.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as WorkflowChatTransportOptions, n as SendMessagesOptions, r as WorkflowChatTransport, t as ReconnectToStreamOptions } from "./workflow-chat-transport-DwOgX7dK.js";
1
+ import { i as WorkflowChatTransportOptions, n as SendMessagesOptions, r as WorkflowChatTransport, t as ReconnectToStreamOptions } from "./workflow-chat-transport-Bw-K44A5.js";
2
2
  export { type ReconnectToStreamOptions, type SendMessagesOptions, WorkflowChatTransport, type WorkflowChatTransportOptions };
package/dist/client.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as WorkflowChatTransport } from "./workflow-chat-transport-Bduf4ZDx.js";
1
+ import { t as WorkflowChatTransport } from "./workflow-chat-transport-Dj1poviu.js";
2
2
  export { WorkflowChatTransport };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as WorkflowChatTransportOptions, n as SendMessagesOptions, r as WorkflowChatTransport, t as ReconnectToStreamOptions } from "./workflow-chat-transport-DwOgX7dK.js";
1
+ import { i as WorkflowChatTransportOptions, n as SendMessagesOptions, r as WorkflowChatTransport, t as ReconnectToStreamOptions } from "./workflow-chat-transport-Bw-K44A5.js";
2
2
  import { Context, HasRequiredKey, InferToolContext, InferToolInput, InferToolOutput, InferToolSetContext } from "@ai-sdk/provider-utils";
3
3
  import { ActiveTools, Experimental_LanguageModelStreamPart, Experimental_SandboxSession, FinishReason, GenerateTextOnStepEndCallback, GenerateTextResult, InferUITools, Instructions, LanguageModel, LanguageModelResponseMetadata, LanguageModelUsage, ModelMessage, Output, StepResult, StopCondition, TelemetryOptions as TelemetryOptions$1, ToolCallRepairFunction, ToolCallRepairFunction as ToolCallRepairFunction$1, ToolChoice, ToolSet, UIMessage, UIMessageChunk } from "ai";
4
4
  import { LanguageModelV4, LanguageModelV4CallOptions, LanguageModelV4Prompt, LanguageModelV4StreamPart, SharedV4ProviderOptions } from "@ai-sdk/provider";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { n as normalizeUIMessageStreamParts, t as WorkflowChatTransport } from "./workflow-chat-transport-Bduf4ZDx.js";
2
- import { asArray, asSchema, convertUint8ArrayToBase64, generateId, getErrorMessage, isAbortError, jsonSchema, validateTypes, withUserAgentSuffix } from "@ai-sdk/provider-utils";
1
+ import { n as normalizeUIMessageStreamParts, t as WorkflowChatTransport } from "./workflow-chat-transport-Dj1poviu.js";
2
+ import { asArray, asSchema, convertUint8ArrayToBase64, createIdGenerator, generateId, getErrorMessage, isAbortError, jsonSchema, validateTypes, withUserAgentSuffix } from "@ai-sdk/provider-utils";
3
3
  import { DefaultGeneratedFile, InvalidToolApprovalSignatureError, NoOutputGeneratedError, Output, ToolChoiceViolationError, dynamicTool, experimental_filterActiveTools, experimental_streamLanguageModelCall, gateway, isStepCount, tool, wrapLanguageModel } from "ai";
4
- import { DefaultGenerateTextResult, DefaultStepResult, addLanguageModelUsage, asLanguageModelUsage, calculateTokensPerSecond, collectToolApprovals, convertLanguageModelContent, convertToLanguageModelPrompt, createDefaultDownloadFunction, createNullLanguageModelUsage, createRestrictedTelemetryDispatcher, createToolModelOutput, createToolSearchState, downloadAssets, mapToolResultOutput, mergeAbortSignals, mergeCallbacks, parseToolCall, prepareLanguageModelCallOptions, prepareRetries, prepareToolChoice, prepareTools, resolveLanguageModel, signToolApproval, standardizePrompt, validateApprovedToolApprovals, verifyToolApprovalSignature } from "ai/internal";
4
+ import { DefaultGenerateTextResult, DefaultStepResult, addLanguageModelUsage, asLanguageModelUsage, calculateTokensPerSecond, collectToolApprovals, convertLanguageModelContent, convertToLanguageModelPrompt, createDefaultDownloadFunction, createNullLanguageModelUsage, createRestrictedTelemetryDispatcher, createTelemetryDispatcher, createToolModelOutput, createToolSearchState, downloadAssets, isOutputChunk, mapToolResultOutput, mergeAbortSignals, mergeCallbacks, parseToolCall, prepareLanguageModelCallOptions, prepareRetries, prepareToolChoice, prepareTools, resolveLanguageModel, signToolApproval, standardizePrompt, validateApprovedToolApprovals, verifyToolApprovalSignature } from "ai/internal";
5
5
  import { getWorkflowMetadata } from "workflow";
6
6
  import Ajv from "ajv";
7
7
  import Ajv2020 from "ajv/dist/2020.js";
@@ -182,7 +182,7 @@ async function buildModelStepResult(raw, toolCalls, finish, providerExecutedTool
182
182
  durationMs: duration
183
183
  });
184
184
  return new DefaultStepResult({
185
- callId: "workflow-agent",
185
+ callId: opts.callId ?? "workflow-agent",
186
186
  stepNumber: opts.stepNumber,
187
187
  provider: raw.generation.provider,
188
188
  modelId: raw.generation.modelId,
@@ -311,7 +311,7 @@ async function buildModelStepResult(raw, toolCalls, finish, providerExecutedTool
311
311
  }
312
312
  const toolResults = content.filter((part) => part.type === "tool-result");
313
313
  return {
314
- callId: "workflow-agent",
314
+ callId: opts.callId ?? "workflow-agent",
315
315
  stepNumber: opts.stepNumber,
316
316
  model: {
317
317
  provider: responseMetadata?.modelId?.split(":")[0] ?? "unknown",
@@ -424,6 +424,16 @@ function resolveToolDescription({ tool, toolName, toolsContext, experimental_san
424
424
  experimental_sandbox: sandbox
425
425
  });
426
426
  }
427
+ function formatAjvError(error) {
428
+ const path = `data${error.instancePath}`;
429
+ switch (error.keyword) {
430
+ case "enum": return `${path} must be one of ${error.params.allowedValues.map((value) => JSON.stringify(value)).join(", ")}`;
431
+ case "additionalProperties": return `${path} has unexpected property ${JSON.stringify(error.params.additionalProperty)}`;
432
+ case "const": return `${path} must equal ${JSON.stringify(error.params.allowedValue)}`;
433
+ case "required": return `${path} is missing required property ${JSON.stringify(error.params.missingProperty)}`;
434
+ default: return `${path} ${error.message ?? "is invalid"}`;
435
+ }
436
+ }
427
437
  /**
428
438
  * Reconstructs tool objects from serializable tool definitions inside a step.
429
439
  *
@@ -433,14 +443,14 @@ function resolveToolDescription({ tool, toolName, toolsContext, experimental_san
433
443
  */
434
444
  function resolveSerializableTools(tools) {
435
445
  const ajvOptions = {
446
+ allErrors: true,
436
447
  strict: false,
437
448
  validateFormats: false
438
449
  };
439
450
  const ajv = new Ajv(ajvOptions);
440
451
  const ajv2020 = new Ajv2020(ajvOptions);
441
452
  const createValidatedInputSchema = (inputSchema) => {
442
- const schemaAjv = (typeof inputSchema.$schema === "string" ? inputSchema.$schema.replace(/#$/, "") : void 0) === "https://json-schema.org/draft/2020-12/schema" ? ajv2020 : ajv;
443
- const validateFn = schemaAjv.compile(inputSchema);
453
+ const validateFn = ((typeof inputSchema.$schema === "string" ? inputSchema.$schema.replace(/#$/, "") : void 0) === "https://json-schema.org/draft/2020-12/schema" ? ajv2020 : ajv).compile(inputSchema);
444
454
  return jsonSchema(inputSchema, { validate: (value) => {
445
455
  if (validateFn(value)) return {
446
456
  success: true,
@@ -448,7 +458,7 @@ function resolveSerializableTools(tools) {
448
458
  };
449
459
  return {
450
460
  success: false,
451
- error: new Error(schemaAjv.errorsText(validateFn.errors))
461
+ error: new Error(validateFn.errors.map(formatAjvError).join(", "))
452
462
  };
453
463
  } });
454
464
  };
@@ -494,26 +504,149 @@ function resolveSerializableTools(tools) {
494
504
  }));
495
505
  }
496
506
  //#endregion
507
+ //#region src/model-call-telemetry.ts
508
+ const generateCallId = createIdGenerator({
509
+ prefix: "call",
510
+ size: 24
511
+ });
512
+ /**
513
+ * Global telemetry integrations are not visible in the workflow VM, so the
514
+ * workflow body sends a serializable descriptor and the model step, which
515
+ * runs in Node, dispatches the model call lifecycle itself.
516
+ *
517
+ * Each model call is reported as its own operation with a unique `callId`.
518
+ */
519
+ function shouldDispatchModelCallTelemetryInStep(telemetry) {
520
+ return telemetry?.isEnabled !== false && telemetry?.integrations == null && globalThis.AI_SDK_TELEMETRY_INTEGRATIONS == null;
521
+ }
522
+ async function runModelCallWithTelemetry({ telemetry, modelInit, prompt, serializedTools, options, execute }) {
523
+ if (telemetry == null) return execute(void 0);
524
+ const dispatcher = createTelemetryDispatcher({ telemetry });
525
+ const callId = generateCallId();
526
+ const { provider, modelId } = typeof modelInit === "string" ? {
527
+ provider: "gateway",
528
+ modelId: modelInit
529
+ } : modelInit;
530
+ const tools = serializedTools == null ? void 0 : resolveSerializableTools(serializedTools);
531
+ const messages = prompt;
532
+ await dispatcher.onStart?.({
533
+ callId,
534
+ operationId: "ai.workflowAgent.stream",
535
+ provider,
536
+ modelId,
537
+ system: void 0,
538
+ messages,
539
+ tools,
540
+ toolChoice: options?.toolChoice,
541
+ activeTools: void 0,
542
+ maxOutputTokens: options?.maxOutputTokens,
543
+ temperature: options?.temperature,
544
+ topP: options?.topP,
545
+ topK: options?.topK,
546
+ presencePenalty: options?.presencePenalty,
547
+ frequencyPenalty: options?.frequencyPenalty,
548
+ stopSequences: options?.stopSequences,
549
+ seed: options?.seed,
550
+ maxRetries: options?.maxRetries ?? 2,
551
+ timeout: void 0,
552
+ headers: options?.headers,
553
+ reasoning: options?.reasoning,
554
+ providerOptions: options?.providerOptions,
555
+ output: void 0,
556
+ runtimeContext: {},
557
+ toolsContext: {}
558
+ });
559
+ await dispatcher.onStepStart?.({
560
+ callId,
561
+ provider,
562
+ modelId,
563
+ stepNumber: telemetry.stepNumber,
564
+ system: void 0,
565
+ messages,
566
+ tools,
567
+ toolChoice: options?.toolChoice,
568
+ activeTools: void 0,
569
+ steps: [],
570
+ providerOptions: options?.providerOptions,
571
+ output: void 0,
572
+ runtimeContext: {},
573
+ toolsContext: {}
574
+ });
575
+ let result;
576
+ try {
577
+ result = await execute({
578
+ callId,
579
+ onLanguageModelCallStart: dispatcher.onLanguageModelCallStart,
580
+ onLanguageModelCallEnd: dispatcher.onLanguageModelCallEnd,
581
+ executeLanguageModelCallInTelemetryContext: dispatcher.executeLanguageModelCall
582
+ });
583
+ } catch (error) {
584
+ await dispatcher.onError?.({
585
+ callId,
586
+ error
587
+ });
588
+ throw error;
589
+ }
590
+ if (result.aborted) {
591
+ await dispatcher.onAbort?.({
592
+ callId,
593
+ steps: []
594
+ });
595
+ return result;
596
+ }
597
+ if ("terminalError" in result) {
598
+ await dispatcher.onError?.({
599
+ callId,
600
+ error: result.terminalError
601
+ });
602
+ return result;
603
+ }
604
+ const step = await buildModelStepResult(result.raw, result.toolCalls, result.finish, result.providerExecutedToolResults, {
605
+ callId,
606
+ tools,
607
+ stepNumber: telemetry.stepNumber,
608
+ runtimeContext: {},
609
+ toolsContext: {}
610
+ });
611
+ await dispatcher.onStepEnd?.(step);
612
+ await dispatcher.onEnd?.(Object.assign(Object.create(Object.getPrototypeOf(step)), step, {
613
+ finalStep: step,
614
+ steps: [step],
615
+ usage: step.usage,
616
+ totalUsage: step.usage
617
+ }));
618
+ return result;
619
+ }
620
+ //#endregion
497
621
  //#region src/do-generate-step.ts
498
622
  /** A single durable provider call; orchestration and callbacks stay in the workflow. */
499
623
  async function doGenerateStep(prompt, modelInit, serializedTools, options = {}) {
500
624
  "use step";
501
- try {
502
- return await generateModelCall(prompt, modelInit, serializedTools, options);
503
- } catch (error) {
504
- return {
505
- toolCalls: [],
506
- finish: void 0,
507
- raw: {
508
- content: [],
509
- reasoning: []
510
- },
511
- providerExecutedToolResults: /* @__PURE__ */ new Map(),
512
- terminalError: error
513
- };
514
- }
625
+ return runModelCallWithTelemetry({
626
+ telemetry: options.telemetry,
627
+ modelInit,
628
+ prompt,
629
+ serializedTools,
630
+ options,
631
+ execute: async (languageModelCallTelemetry) => {
632
+ try {
633
+ return await generateModelCall(prompt, modelInit, serializedTools, options, languageModelCallTelemetry);
634
+ } catch (error) {
635
+ return {
636
+ toolCalls: [],
637
+ finish: void 0,
638
+ raw: {
639
+ content: [],
640
+ reasoning: []
641
+ },
642
+ providerExecutedToolResults: /* @__PURE__ */ new Map(),
643
+ terminalError: error
644
+ };
645
+ }
646
+ }
647
+ });
515
648
  }
516
- async function generateModelCall(prompt, modelInit, serializedTools, options) {
649
+ async function generateModelCall(prompt, modelInit, serializedTools, options, languageModelCallTelemetry) {
517
650
  const remaining = options.timeoutAt == null ? void 0 : options.timeoutAt - Date.now();
518
651
  options.abortSignal?.throwIfAborted();
519
652
  if (remaining != null && remaining <= 0) throw new DOMException("The generation deadline expired.", "TimeoutError");
@@ -527,23 +660,49 @@ async function generateModelCall(prompt, modelInit, serializedTools, options) {
527
660
  maxRetries: options.maxRetries,
528
661
  abortSignal
529
662
  });
663
+ const callId = languageModelCallTelemetry?.callId;
664
+ const executeLanguageModelCall = languageModelCallTelemetry?.executeLanguageModelCallInTelemetryContext ?? (({ execute }) => execute());
665
+ await languageModelCallTelemetry?.onLanguageModelCallStart?.({
666
+ callId,
667
+ provider: model.provider,
668
+ modelId: model.modelId,
669
+ messages: prompt,
670
+ tools: modelTools,
671
+ ...settings,
672
+ providerOptions: options.providerOptions,
673
+ headers: options.headers
674
+ });
530
675
  const start = Date.now();
531
676
  const response = await retry(async () => {
532
677
  abortSignal?.throwIfAborted();
533
678
  if (options.timeoutAt != null && options.timeoutAt <= Date.now()) throw new DOMException("The generation deadline expired.", "TimeoutError");
534
- return model.doGenerate({
535
- ...settings,
536
- prompt: [...prompt],
537
- tools: modelTools,
538
- toolChoice,
539
- responseFormat: options.responseFormat,
540
- providerOptions: options.providerOptions,
541
- headers: options.headers,
542
- abortSignal
679
+ return executeLanguageModelCall({
680
+ callId: callId ?? "",
681
+ execute: () => model.doGenerate({
682
+ ...settings,
683
+ prompt: [...prompt],
684
+ tools: modelTools,
685
+ toolChoice,
686
+ responseFormat: options.responseFormat,
687
+ providerOptions: options.providerOptions,
688
+ headers: options.headers,
689
+ abortSignal
690
+ })
543
691
  });
544
692
  });
545
- abortSignal?.throwIfAborted();
546
693
  const responseTimeMs = Date.now() - start;
694
+ await languageModelCallTelemetry?.onLanguageModelCallEnd?.({
695
+ callId,
696
+ provider: model.provider,
697
+ modelId: response.response?.modelId ?? model.modelId,
698
+ finishReason: response.finishReason.unified,
699
+ usage: asLanguageModelUsage(response.usage),
700
+ content: response.content,
701
+ responseId: response.response?.id,
702
+ ...response.providerMetadata != null ? { providerMetadata: response.providerMetadata } : {},
703
+ performance: { responseTimeMs }
704
+ });
705
+ abortSignal?.throwIfAborted();
547
706
  const toolCalls = await Promise.all(response.content.filter((part) => part.type === "tool-call").map((toolCall) => parseToolCall({
548
707
  toolCall,
549
708
  tools,
@@ -627,6 +786,17 @@ doGenerateStep.maxRetries = 0;
627
786
  //#region src/do-stream-step.ts
628
787
  async function doStreamStep(conversationPrompt, modelInit, writable, serializedTools, options) {
629
788
  "use step";
789
+ return runModelCallWithTelemetry({
790
+ telemetry: options?.telemetry,
791
+ modelInit,
792
+ prompt: conversationPrompt,
793
+ serializedTools,
794
+ options,
795
+ execute: (languageModelCallTelemetry) => streamModelStep(conversationPrompt, modelInit, writable, serializedTools, options, languageModelCallTelemetry)
796
+ });
797
+ }
798
+ doStreamStep.maxRetries = 0;
799
+ async function streamModelStep(conversationPrompt, modelInit, writable, serializedTools, options, languageModelCallTelemetry) {
630
800
  const timeout = options?.timeoutAt == null ? void 0 : options.timeoutAt - Date.now();
631
801
  if (options?.abortSignal?.aborted || timeout != null && timeout <= 0) return { aborted: true };
632
802
  const abortSignal = timeout == null ? options?.abortSignal : options?.abortSignal == null ? AbortSignal.timeout(timeout) : AbortSignal.any([options.abortSignal, AbortSignal.timeout(timeout)]);
@@ -691,7 +861,8 @@ async function doStreamStep(conversationPrompt, modelInit, writable, serializedT
691
861
  frequencyPenalty: options?.frequencyPenalty,
692
862
  stopSequences: options?.stopSequences,
693
863
  seed: options?.seed,
694
- repairToolCall: options?.repairToolCall
864
+ repairToolCall: options?.repairToolCall,
865
+ ...languageModelCallTelemetry
695
866
  }));
696
867
  return stream;
697
868
  } catch (error) {
@@ -712,11 +883,13 @@ async function doStreamStep(conversationPrompt, modelInit, writable, serializedT
712
883
  let performance;
713
884
  let terminalError;
714
885
  let hasTerminalError = false;
886
+ let hasReceivedOutputChunk = false;
715
887
  const ongoingToolCallToolNames = /* @__PURE__ */ new Map();
716
888
  const writer = writable?.getWriter();
717
889
  try {
718
890
  await writer?.write({ type: "reset-step" });
719
891
  for await (const part of modelStream) {
892
+ if (isOutputChunk(part)) hasReceivedOutputChunk = true;
720
893
  switch (part.type) {
721
894
  case "tool-input-start":
722
895
  ongoingToolCallToolNames.set(part.id, part.toolName);
@@ -902,6 +1075,10 @@ async function doStreamStep(conversationPrompt, modelInit, writable, serializedT
902
1075
  writer?.releaseLock();
903
1076
  }
904
1077
  if (abortSignal?.aborted || options?.timeoutAt != null && options.timeoutAt <= Date.now()) return { aborted: true };
1078
+ if (finish == null && !hasReceivedOutputChunk && !hasTerminalError) {
1079
+ terminalError = new NoOutputGeneratedError({ message: "No output generated. The model stream ended without a finish chunk." });
1080
+ hasTerminalError = true;
1081
+ }
905
1082
  return {
906
1083
  toolCalls,
907
1084
  finish,
@@ -917,7 +1094,6 @@ async function doStreamStep(conversationPrompt, modelInit, writable, serializedT
917
1094
  ...hasTerminalError ? { terminalError } : {}
918
1095
  };
919
1096
  }
920
- doStreamStep.maxRetries = 0;
921
1097
  function applyStreamTransforms({ stream, transforms, tools }) {
922
1098
  const sourceReader = stream.getReader();
923
1099
  let stopped = false;
@@ -1032,6 +1208,7 @@ async function* modelCallIterator({ prompt, mode = "stream", include, initialIns
1032
1208
  includeRuntimeContext: telemetry?.includeRuntimeContext,
1033
1209
  includeToolsContext: telemetry?.includeToolsContext
1034
1210
  });
1211
+ const dispatchTelemetryInStep = shouldDispatchModelCallTelemetryInStep(telemetry);
1035
1212
  while (!done) {
1036
1213
  if (currentGenerationSettings.abortSignal?.aborted) {
1037
1214
  if (mode === "generate") currentGenerationSettings.abortSignal.throwIfAborted();
@@ -1135,7 +1312,13 @@ async function* modelCallIterator({ prompt, mode = "stream", include, initialIns
1135
1312
  repairToolCall,
1136
1313
  responseFormat,
1137
1314
  include,
1138
- experimental_transform
1315
+ experimental_transform,
1316
+ telemetry: dispatchTelemetryInStep ? {
1317
+ functionId: telemetry?.functionId,
1318
+ recordInputs: telemetry?.recordInputs,
1319
+ recordOutputs: telemetry?.recordOutputs,
1320
+ stepNumber
1321
+ } : void 0
1139
1322
  };
1140
1323
  const modelCallResult = mode === "generate" ? await doGenerateStep(conversationPrompt, currentModel, serializedTools, callOptions) : await doStreamStep(conversationPrompt, currentModel, writable, serializedTools, callOptions);
1141
1324
  if (modelCallResult.aborted) {
@@ -1182,10 +1365,10 @@ async function* modelCallIterator({ prompt, mode = "stream", include, initialIns
1182
1365
  const isToolExecutionAllowed = finishReason === "tool-calls" || finishReason === "stop";
1183
1366
  for (const toolCall of toolCalls) if (toolCall.providerExecuted && serializedTools[toolCall.toolName]?.supportsDeferredResults && !providerExecutedToolResults.has(toolCall.toolCallId)) pendingDeferredToolCallIds.add(toolCall.toolCallId);
1184
1367
  for (const toolCallId of providerExecutedToolResults.keys()) pendingDeferredToolCallIds.delete(toolCallId);
1185
- const shouldProcessTools = isToolExecutionAllowed && (toolCalls.length > 0 || providerExecutedToolResults.size > 0);
1186
- if (hasTerminalError) done = true;
1187
- else if (shouldProcessTools) {
1368
+ const shouldProcessTools = isToolExecutionAllowed && (toolCalls.length > 0 || providerExecutedToolResults.size > 0) || finishReason === "length" && providerExecutedToolResults.size > 0;
1369
+ if (!hasTerminalError && shouldProcessTools) {
1188
1370
  lastStepWasYielded = true;
1371
+ const processableToolCalls = isToolExecutionAllowed ? toolCalls : toolCalls.filter((toolCall) => !toolCall.providerExecuted && toolCall.invalid || toolCall.providerExecuted && providerExecutedToolResults.has(toolCall.toolCallId));
1189
1372
  const { content: assistantContent, providerExecutedToolResultPositions } = getAssistantMessageContent(step, mode);
1190
1373
  const includedToolCallIds = new Set(assistantContent.flatMap((part) => part.type === "tool-call" ? [part.toolCallId] : []));
1191
1374
  conversationPrompt.push({
@@ -1193,7 +1376,7 @@ async function* modelCallIterator({ prompt, mode = "stream", include, initialIns
1193
1376
  content: [...assistantContent, ...toolCalls.filter((toolCall) => !includedToolCallIds.has(toolCall.toolCallId)).map(toAssistantToolCallContent)]
1194
1377
  });
1195
1378
  const toolResults = yield {
1196
- toolCalls,
1379
+ toolCalls: processableToolCalls,
1197
1380
  tools: effectiveTools,
1198
1381
  messages: conversationPrompt,
1199
1382
  step,
@@ -1206,14 +1389,14 @@ async function* modelCallIterator({ prompt, mode = "stream", include, initialIns
1206
1389
  const responseMessages = addToolResultsToConversation({
1207
1390
  messages: conversationPrompt,
1208
1391
  toolResults,
1209
- providerExecutedToolCallIds: /* @__PURE__ */ new Set([...toolCalls.flatMap((toolCall) => toolCall.providerExecuted ? [toolCall.toolCallId] : []), ...providerExecutedToolResults.keys()]),
1392
+ providerExecutedToolCallIds: /* @__PURE__ */ new Set([...processableToolCalls.flatMap((toolCall) => toolCall.providerExecuted ? [toolCall.toolCallId] : []), ...providerExecutedToolResults.keys()]),
1210
1393
  providerExecutedToolResultPositions
1211
1394
  });
1212
1395
  step.response.messages.push(...mode === "generate" ? toModelResponseMessages(responseMessages) : responseMessages);
1213
1396
  const stopConditionMet = (stopConditions == null ? [] : Array.isArray(stopConditions) ? stopConditions : [stopConditions]).some((test) => test({ steps }));
1214
1397
  const hasClientToolCalls = toolCalls.some((toolCall) => !toolCall.providerExecuted);
1215
- done = stopConditionMet || !hasClientToolCalls && pendingDeferredToolCallIds.size === 0;
1216
- } else if (mode === "generate" || finishReason === "stop" || finishReason === "tool-calls" || finishReason === "length") {
1398
+ done = !isToolExecutionAllowed || stopConditionMet || !hasClientToolCalls && pendingDeferredToolCallIds.size === 0;
1399
+ } else if (hasTerminalError || mode === "generate" || finishReason === "stop" || finishReason === "tool-calls" || finishReason === "length" || finishReason === "content-filter" || finishReason === "error" || finishReason === "other" || finishReason === "unknown" || !finishReason) {
1217
1400
  const { content: assistantContent } = getAssistantMessageContent(step, mode);
1218
1401
  if (assistantContent.length > 0) {
1219
1402
  const assistantMessage = {
@@ -1224,12 +1407,7 @@ async function* modelCallIterator({ prompt, mode = "stream", include, initialIns
1224
1407
  step.response.messages.push(...mode === "generate" ? toModelResponseMessages([assistantMessage]) : [assistantMessage]);
1225
1408
  }
1226
1409
  done = true;
1227
- } else if (finishReason === "content-filter") done = true;
1228
- else if (finishReason === "error") done = true;
1229
- else if (finishReason === "other") done = true;
1230
- else if (finishReason === "unknown") done = true;
1231
- else if (!finishReason) done = true;
1232
- else throw new Error(`Unexpected finish reason: ${typeof finish?.finishReason === "object" ? JSON.stringify(finish?.finishReason) : finish?.finishReason}`);
1410
+ } else throw new Error(`Unexpected finish reason: ${typeof finish?.finishReason === "object" ? JSON.stringify(finish?.finishReason) : finish?.finishReason}`);
1233
1411
  const resolvedOnStepEnd = onStepEnd ?? onStepFinish;
1234
1412
  if (resolvedOnStepEnd) await resolvedOnStepEnd(step);
1235
1413
  await telemetryDispatcher.onStepEnd?.(normalizeStepForTelemetry$1(step));