ai 7.0.147 → 7.0.149

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.d.ts +5 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +47 -32
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +2 -1
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +4 -1
  9. package/dist/internal/index.js.map +1 -1
  10. package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +7 -0
  11. package/docs/03-ai-sdk-core/18-code-mode.mdx +3 -1
  12. package/docs/03-ai-sdk-core/18-tools.mdx +19 -0
  13. package/docs/03-ai-sdk-core/36-transcription.mdx +40 -39
  14. package/docs/03-ai-sdk-core/37-speech.mdx +1 -0
  15. package/docs/03-ai-sdk-core/60-telemetry.mdx +25 -2
  16. package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +32 -0
  17. package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +1 -1
  18. package/docs/06-advanced/11-secure-url-fetching.mdx +2 -2
  19. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +4 -4
  20. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +7 -7
  21. package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +1 -1
  22. package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +1 -1
  23. package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +1 -1
  24. package/docs/07-reference/05-ai-sdk-errors/ai-tool-choice-violation-error.mdx +12 -7
  25. package/package.json +12 -12
  26. package/src/error/tool-choice-violation-error.ts +6 -4
  27. package/src/generate-text/collect-tool-approvals.ts +10 -0
  28. package/src/generate-text/generate-text.ts +24 -12
  29. package/src/generate-text/stream-language-model-call.ts +37 -12
  30. package/src/generate-text/stream-text.ts +4 -1
  31. package/src/types/language-model.ts +2 -0
  32. package/src/ui/chat.ts +40 -12
  33. package/src/ui-message-stream/ui-message-chunks.ts +1 -0
@@ -448,6 +448,12 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
448
448
  let previousOutputChunkTimestampMs: number | undefined;
449
449
  const timeBetweenOutputChunksMs: number[] = [];
450
450
 
451
+ // Tool choice none rejects tool calls when the model call finishes.
452
+ // Withhold tool parts so that consumers (e.g. client-side tools) cannot
453
+ // act on them before the rejection, and skip user callbacks that would
454
+ // repair or refine them.
455
+ const withholdToolParts = toolChoice.type === 'none';
456
+
451
457
  return new TransformStream<
452
458
  LanguageModelV4StreamPart,
453
459
  LanguageModelStreamPart<TOOLS>
@@ -467,6 +473,15 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
467
473
  previousOutputChunkTimestampMs = outputChunkTimestampMs;
468
474
  }
469
475
 
476
+ if (
477
+ withholdToolParts &&
478
+ (chunk.type === 'tool-input-start' ||
479
+ chunk.type === 'tool-input-delta' ||
480
+ chunk.type === 'tool-input-end')
481
+ ) {
482
+ return;
483
+ }
484
+
470
485
  switch (chunk.type) {
471
486
  case 'error':
472
487
  controller.enqueue({
@@ -655,17 +670,18 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
655
670
  });
656
671
 
657
672
  const enforcedToolChoice =
658
- toolChoice.type === 'required' || toolChoice.type === 'tool'
659
- ? toolChoice
660
- : undefined;
673
+ toolChoice.type === 'auto' ? undefined : toolChoice;
661
674
 
662
675
  const toolChoiceViolationError =
663
676
  enforcedToolChoice != null &&
664
- ![...toolCallsByToolCallId.values()].some(
665
- toolCall =>
666
- enforcedToolChoice.type === 'required' ||
667
- toolCall.toolName === enforcedToolChoice.toolName,
668
- )
677
+ (enforcedToolChoice.type === 'none'
678
+ ? toolCallsByToolCallId.size > 0
679
+ : chunk.finishReason.unified !== 'interrupted' &&
680
+ ![...toolCallsByToolCallId.values()].some(
681
+ toolCall =>
682
+ enforcedToolChoice.type === 'required' ||
683
+ toolCall.toolName === enforcedToolChoice.toolName,
684
+ ))
669
685
  ? new ToolChoiceViolationError({
670
686
  toolChoice: enforcedToolChoice,
671
687
  finishReason: chunk.finishReason.unified,
@@ -708,17 +724,22 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
708
724
  const toolCall = await parseToolCall({
709
725
  toolCall: chunk,
710
726
  tools,
711
- repairToolCall,
712
- refineToolInput,
727
+ repairToolCall: withholdToolParts ? undefined : repairToolCall,
728
+ refineToolInput: withholdToolParts ? undefined : refineToolInput,
713
729
  instructions,
714
730
  messages,
715
731
  abortSignal,
716
732
  });
717
733
 
718
734
  toolCallsByToolCallId.set(toolCall.toolCallId, toolCall);
719
- controller.enqueue(toolCall);
720
735
  if (collectContent) modelCallContent.push(toolCall);
721
736
 
737
+ if (withholdToolParts) {
738
+ break;
739
+ }
740
+
741
+ controller.enqueue(toolCall);
742
+
722
743
  if (toolCall.invalid) {
723
744
  if (!toolCall.providerExecuted) {
724
745
  controller.enqueue({
@@ -746,6 +767,10 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
746
767
  case 'tool-approval-request': {
747
768
  if (collectContent) rawModelCallContent.push(chunk);
748
769
 
770
+ if (withholdToolParts) {
771
+ break;
772
+ }
773
+
749
774
  const toolCall = toolCallsByToolCallId.get(chunk.toolCallId);
750
775
 
751
776
  if (toolCall == null) {
@@ -808,7 +833,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
808
833
  : {}),
809
834
  } as TypedToolResult<TOOLS>);
810
835
 
811
- controller.enqueue(toolResultPart);
836
+ if (!withholdToolParts) controller.enqueue(toolResultPart);
812
837
  if (collectContent) modelCallContent.push(toolResultPart);
813
838
 
814
839
  break;
@@ -2465,7 +2465,7 @@ class DefaultStreamTextResult<
2465
2465
  activeTools: prepareStepResult?.activeTools ?? activeTools,
2466
2466
  });
2467
2467
  const {
2468
- executionTools: stepExecutionTools,
2468
+ executionTools,
2469
2469
  modelTools: stepModelTools,
2470
2470
  toolCallerMessages,
2471
2471
  } = prepareToolsForToolCallers({
@@ -2495,6 +2495,8 @@ class DefaultStreamTextResult<
2495
2495
  const stepToolChoice = prepareToolChoice({
2496
2496
  toolChoice: prepareStepResult?.toolChoice ?? toolChoice,
2497
2497
  });
2498
+ const stepExecutionTools =
2499
+ stepToolChoice.type === 'none' ? undefined : executionTools;
2498
2500
 
2499
2501
  const stepMessages = appendToolCallerMessages({
2500
2502
  messages: prepareStepResult?.messages ?? stepInputMessages,
@@ -3215,6 +3217,7 @@ class DefaultStreamTextResult<
3215
3217
  cleanupStepTimeouts();
3216
3218
 
3217
3219
  if (
3220
+ stepFinishReason !== 'interrupted' &&
3218
3221
  // Continue only after all client tool calls have been executed or denied,
3219
3222
  // and if there are client results or pending deferred provider results.
3220
3223
  clientToolCalls.length ===
@@ -70,6 +70,7 @@ export type LanguageModel =
70
70
  * - `content-filter`: content filter violation stopped the model
71
71
  * - `tool-calls`: model triggered tool calls
72
72
  * - `error`: model stopped because of an error
73
+ * - `interrupted`: provider stream ended without a completion signal; output may be incomplete
73
74
  * - `other`: model stopped for other reasons
74
75
  */
75
76
  export type FinishReason =
@@ -78,6 +79,7 @@ export type FinishReason =
78
79
  | 'content-filter'
79
80
  | 'tool-calls'
80
81
  | 'error'
82
+ | 'interrupted'
81
83
  | 'other';
82
84
 
83
85
  /**
package/src/ui/chat.ts CHANGED
@@ -274,6 +274,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
274
274
 
275
275
  private pendingMessagePreparations = new Set<AbortController>();
276
276
  private pendingApprovalMessageId: string | undefined;
277
+ // Keep interruption state after activeResponse is cleared so late tool updates
278
+ // cannot automatically continue an interrupted message.
279
+ private interruptedMessageIds = new Set<string>();
277
280
  private activeResponse: ActiveResponse<UI_MESSAGE> | undefined = undefined;
278
281
  private activeResumeRequest: ActiveResumeRequest | undefined = undefined;
279
282
  private resumableStreamState:
@@ -638,7 +641,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
638
641
  this.status !== 'submitted' &&
639
642
  this.sendAutomaticallyWhen
640
643
  ) {
641
- const shouldSend = await this.shouldSendAutomatically();
644
+ const shouldSend = await this.shouldSendAutomatically(
645
+ messages[messageIndex].id,
646
+ );
642
647
 
643
648
  if (shouldSend) {
644
649
  // no await to avoid deadlocking
@@ -735,7 +740,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
735
740
  this.status !== 'submitted' &&
736
741
  this.sendAutomaticallyWhen
737
742
  ) {
738
- const shouldSend = await this.shouldSendAutomatically();
743
+ const shouldSend = await this.shouldSendAutomatically(
744
+ messages[messageIndex].id,
745
+ );
739
746
 
740
747
  if (shouldSend) {
741
748
  // no await to avoid deadlocking
@@ -794,19 +801,29 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
794
801
  this.transport.close?.();
795
802
  };
796
803
 
797
- private async shouldSendAutomatically(): Promise<boolean> {
798
- if (!this.sendAutomaticallyWhen) return false;
804
+ private async shouldSendAutomatically(
805
+ messageId = this.lastMessage?.id,
806
+ ): Promise<boolean> {
807
+ const isInterrupted = (id: string | undefined) =>
808
+ id != null && this.interruptedMessageIds.has(id);
799
809
 
800
- const result = this.sendAutomaticallyWhen({
810
+ if (
811
+ !this.sendAutomaticallyWhen ||
812
+ isInterrupted(messageId) ||
813
+ isInterrupted(this.lastMessage?.id)
814
+ ) {
815
+ return false;
816
+ }
817
+
818
+ const result = await this.sendAutomaticallyWhen({
801
819
  messages: this.state.messages,
802
820
  });
803
821
 
804
- // Check if result is a promise
805
- if (result && typeof result === 'object' && 'then' in result) {
806
- return await result;
807
- }
808
-
809
- return result as boolean;
822
+ return (
823
+ result &&
824
+ !isInterrupted(messageId) &&
825
+ !isInterrupted(this.lastMessage?.id)
826
+ );
810
827
  }
811
828
 
812
829
  private async runAutomaticRequest(
@@ -1182,6 +1199,13 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
1182
1199
  } finally {
1183
1200
  try {
1184
1201
  if (activeResponse) {
1202
+ const { message, finishReason } = activeResponse.state;
1203
+ if (finishReason === 'interrupted') {
1204
+ this.interruptedMessageIds.add(message.id);
1205
+ } else if (!isAbort && !isError) {
1206
+ this.interruptedMessageIds.delete(message.id);
1207
+ }
1208
+
1185
1209
  this.onFinish?.({
1186
1210
  message: activeResponse.state.message,
1187
1211
  messages: this.state.messages,
@@ -1201,7 +1225,11 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
1201
1225
  }
1202
1226
 
1203
1227
  // automatically send the message if the sendAutomaticallyWhen function returns true
1204
- if (!isAbort && !isError) {
1228
+ if (
1229
+ !isAbort &&
1230
+ !isError &&
1231
+ activeResponse?.state.finishReason !== 'interrupted'
1232
+ ) {
1205
1233
  await this.runAutomaticRequest(() =>
1206
1234
  this.makeRequest({
1207
1235
  trigger: 'submit-message',
@@ -206,6 +206,7 @@ export const uiMessageChunkSchema = lazySchema(() =>
206
206
  'content-filter',
207
207
  'tool-calls',
208
208
  'error',
209
+ 'interrupted',
209
210
  'other',
210
211
  ] as const satisfies readonly FinishReason[])
211
212
  .optional(),