ai 7.0.148 → 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 (30) hide show
  1. package/CHANGELOG.md +21 -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/36-transcription.mdx +40 -39
  13. package/docs/03-ai-sdk-core/37-speech.mdx +1 -0
  14. package/docs/03-ai-sdk-core/60-telemetry.mdx +25 -2
  15. package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +1 -1
  16. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +4 -4
  17. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +7 -7
  18. package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +1 -1
  19. package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +1 -1
  20. package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +1 -1
  21. package/docs/07-reference/05-ai-sdk-errors/ai-tool-choice-violation-error.mdx +12 -7
  22. package/package.json +12 -12
  23. package/src/error/tool-choice-violation-error.ts +6 -4
  24. package/src/generate-text/collect-tool-approvals.ts +10 -0
  25. package/src/generate-text/generate-text.ts +24 -12
  26. package/src/generate-text/stream-language-model-call.ts +37 -12
  27. package/src/generate-text/stream-text.ts +4 -1
  28. package/src/types/language-model.ts +2 -0
  29. package/src/ui/chat.ts +40 -12
  30. package/src/ui-message-stream/ui-message-chunks.ts +1 -0
@@ -819,6 +819,13 @@ It supports the following settings:
819
819
  - `none`: the model must not call tools
820
820
  - `{ type: 'tool', toolName: string (typed) }`: the model must call the specified tool
821
821
 
822
+ When `toolChoice` is `'none'`, the SDK prevents local tool execution even if the
823
+ model returns a tool call. Such responses produce a
824
+ [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error).
825
+ With `streamText`, tool parts are withheld from the stream, so client-side tools
826
+ are not invoked either. Tool call repair and input refinement are skipped for
827
+ rejected tool calls.
828
+
822
829
  ```ts highlight="19"
823
830
  import { z } from 'zod';
824
831
  import { generateText, tool } from 'ai';
@@ -251,6 +251,7 @@ payloads, console output, and tool calls. Override them with
251
251
  const codeMode = codeModeTool({
252
252
  executionPolicy: {
253
253
  timeoutMs: 30_000,
254
+ cpuTimeoutMs: 10_000,
254
255
  memoryLimitBytes: 64 * 1024 * 1024,
255
256
  maxResultBytes: 1024 * 1024,
256
257
  maxBridgeRequests: 100,
@@ -261,7 +262,8 @@ const codeMode = codeModeTool({
261
262
 
262
263
  The available limits are:
263
264
 
264
- - `timeoutMs`: total execution time
265
+ - `timeoutMs`: total execution time, including time spent waiting on tools
266
+ - `cpuTimeoutMs`: CPU time spent running sandboxed code, excluding tool waits
265
267
  - `memoryLimitBytes`: QuickJS memory
266
268
  - `maxStackSizeBytes`: QuickJS stack
267
269
  - `maxSourceBytes`: generated source code
@@ -331,44 +331,45 @@ try {
331
331
 
332
332
  ## Transcription Models
333
333
 
334
- | Provider | Model |
335
- | ----------------------------------------------------------------------------------- | ---------------------------- |
336
- | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `whisper-1` |
337
- | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-transcribe` |
338
- | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-mini-transcribe` |
339
- | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-transcribe-diarize` |
340
- | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v1` |
341
- | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v1_experimental` |
342
- | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v2` |
343
- | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#streaming-transcription-models) | `scribe_v2_realtime` |
344
- | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3-turbo` |
345
- | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3` |
346
- | [Mistral](/providers/ai-sdk-providers/mistral#transcription-models) | `voxtral-mini-latest` |
347
- | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `whisper-1` |
348
- | [Azure Speech](/providers/ai-sdk-providers/azure#mai-transcribe) | `mai-transcribe-2` |
349
- | [Azure Speech](/providers/ai-sdk-providers/azure#mai-transcribe) | `mai-transcribe-1.5` |
350
- | [Azure MAI](/providers/ai-sdk-providers/azure#mai-transcribe-2-streaming) | `mai-transcribe-2-streaming` |
351
- | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-transcribe` |
352
- | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-mini-transcribe` |
353
- | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `machine` |
354
- | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `low_cost` |
355
- | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `fusion` |
356
- | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `base` (+ variants) |
357
- | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `enhanced` (+ variants) |
358
- | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova` (+ variants) |
359
- | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova-2` (+ variants) |
360
- | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova-3` (+ variants) |
361
- | [Gladia](/providers/ai-sdk-providers/gladia#transcription-models) | `default` |
362
- | [AssemblyAI](/providers/ai-sdk-providers/assemblyai#transcription-models) | `universal-3-5-pro` |
363
- | [AssemblyAI](/providers/ai-sdk-providers/assemblyai#transcription-models) | `universal-3-pro` |
364
- | [Fal](/providers/ai-sdk-providers/fal#transcription-models) | `whisper` |
365
- | [Fal](/providers/ai-sdk-providers/fal#transcription-models) | `wizper` |
366
- | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `chirp_2` |
367
- | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `chirp_3` |
368
- | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `telephony` |
369
- | [xAI](/providers/ai-sdk-providers/xai#transcription-models) | `default` |
370
- | [Cartesia](/providers/ai-sdk-providers/cartesia#transcription-models) | `ink-whisper` |
371
- | [Cartesia](/providers/ai-sdk-providers/cartesia#streaming-transcription-models) | `ink-2` |
372
- | [Fish Audio](/providers/ai-sdk-providers/fish-audio#transcription-models) | `transcribe-1` |
334
+ | Provider | Model |
335
+ | ----------------------------------------------------------------------------------- | ------------------------------ |
336
+ | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `whisper-1` |
337
+ | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-transcribe` |
338
+ | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-mini-transcribe` |
339
+ | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-transcribe-diarize` |
340
+ | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v1` |
341
+ | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v1_experimental` |
342
+ | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v2` |
343
+ | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#streaming-transcription-models) | `scribe_v2_realtime` |
344
+ | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3-turbo` |
345
+ | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3` |
346
+ | [Mistral](/providers/ai-sdk-providers/mistral#transcription-models) | `voxtral-mini-transcribe-3-0` |
347
+ | [Mistral](/providers/ai-sdk-providers/mistral#transcription-models) | `voxtral-small-transcribe-3-0` |
348
+ | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `whisper-1` |
349
+ | [Azure Speech](/providers/ai-sdk-providers/azure#mai-transcribe) | `mai-transcribe-2` |
350
+ | [Azure Speech](/providers/ai-sdk-providers/azure#mai-transcribe) | `mai-transcribe-1.5` |
351
+ | [Azure MAI](/providers/ai-sdk-providers/azure#mai-transcribe-2-streaming) | `mai-transcribe-2-streaming` |
352
+ | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-transcribe` |
353
+ | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-mini-transcribe` |
354
+ | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `machine` |
355
+ | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `low_cost` |
356
+ | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `fusion` |
357
+ | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `base` (+ variants) |
358
+ | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `enhanced` (+ variants) |
359
+ | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova` (+ variants) |
360
+ | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova-2` (+ variants) |
361
+ | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova-3` (+ variants) |
362
+ | [Gladia](/providers/ai-sdk-providers/gladia#transcription-models) | `default` |
363
+ | [AssemblyAI](/providers/ai-sdk-providers/assemblyai#transcription-models) | `universal-3-5-pro` |
364
+ | [AssemblyAI](/providers/ai-sdk-providers/assemblyai#transcription-models) | `universal-3-pro` |
365
+ | [Fal](/providers/ai-sdk-providers/fal#transcription-models) | `whisper` |
366
+ | [Fal](/providers/ai-sdk-providers/fal#transcription-models) | `wizper` |
367
+ | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `chirp_2` |
368
+ | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `chirp_3` |
369
+ | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `telephony` |
370
+ | [xAI](/providers/ai-sdk-providers/xai#transcription-models) | `default` |
371
+ | [Cartesia](/providers/ai-sdk-providers/cartesia#transcription-models) | `ink-whisper` |
372
+ | [Cartesia](/providers/ai-sdk-providers/cartesia#streaming-transcription-models) | `ink-2` |
373
+ | [Fish Audio](/providers/ai-sdk-providers/fish-audio#transcription-models) | `transcribe-1` |
373
374
 
374
375
  Above are a small subset of the transcription models supported by the AI SDK providers. For more, see the respective provider documentation.
@@ -158,6 +158,7 @@ try {
158
158
  | [OpenAI](/providers/ai-sdk-providers/openai#speech-models) | `tts-1-hd` |
159
159
  | [OpenAI](/providers/ai-sdk-providers/openai#speech-models) | `gpt-4o-mini-tts` |
160
160
  | [Mistral](/providers/ai-sdk-providers/mistral#speech-models) | `voxtral-mini-tts-2603` |
161
+ | [Mistral](/providers/ai-sdk-providers/mistral#speech-models) | `voxtral-mini-tts-3-0` |
161
162
  | [Azure Speech](/providers/ai-sdk-providers/azure#mai-voice) | `mai-voice-2.1-flash` |
162
163
  | [Azure Speech](/providers/ai-sdk-providers/azure#mai-voice) | `mai-voice-2.1` |
163
164
  | [Azure Speech](/providers/ai-sdk-providers/azure#mai-voice) | `mai-voice-2-flash` |
@@ -214,6 +214,29 @@ Properties set to `false` or omitted are excluded. If `telemetry.includeToolsCon
214
214
  between tool execution context and telemetry event context.
215
215
  </Note>
216
216
 
217
+ ## Agent span names
218
+
219
+ `agentSpanName` controls root text/object generation span names. The strings
220
+ `'modelId'` (default) and `'functionId'` select a naming strategy, producing
221
+ `invoke_agent {modelId}` or `invoke_agent {telemetry.functionId}`:
222
+
223
+ ```ts
224
+ registerTelemetry(new OpenTelemetry({ agentSpanName: 'functionId' }));
225
+ // With telemetry: { functionId: 'travel-planner' }, the span is:
226
+ // invoke_agent travel-planner
227
+ ```
228
+
229
+ For a literal custom name, use a callback returning the complete span name:
230
+
231
+ ```ts
232
+ registerTelemetry(new OpenTelemetry({ agentSpanName: () => 'my-custom-span' }));
233
+ ```
234
+
235
+ The callback receives `{ modelId, functionId }` for dynamic names. If it returns
236
+ `undefined` or throws, the default name is used. The `'functionId'` strategy also
237
+ falls back to `modelId` when `functionId` is absent. Child span names and
238
+ attributes are unchanged.
239
+
217
240
  ## Custom Tracer
218
241
 
219
242
  If you want your traces to use a `TracerProvider` other than the one provided by the `@opentelemetry/api` singleton, pass a custom `Tracer` to the `OpenTelemetry` constructor:
@@ -481,7 +504,7 @@ By default, attributes use the `gen_ai.*` prefix. Provider names are mapped to w
481
504
 
482
505
  For `generateText` and `streamText`, the integration records 3 types of spans:
483
506
 
484
- - **`invoke_agent {modelId}`** (root span, `INTERNAL`): covers the full operation including all steps and tool calls.
507
+ - **`invoke_agent {modelId}`** (default root span name, `INTERNAL`): covers the full operation including all steps and tool calls. Use [`agentSpanName`](#agent-span-names) to customize the name.
485
508
 
486
509
  Initial attributes:
487
510
  - `gen_ai.operation.name`: `"invoke_agent"`
@@ -739,7 +762,7 @@ registerTelemetry(
739
762
  );
740
763
  ```
741
764
 
742
- This does not create additional AI SDK-specific spans such as `ai.generateText`, `ai.generateText.doGenerate`, or `ai.toolCall`. Instead, it adds only the selected supplemental attributes to the spans emitted by `OpenTelemetry`, such as `invoke_agent {modelId}`, `step {n}`, `chat {modelId}`, and `execute_tool {toolName}`.
765
+ This does not create additional AI SDK-specific spans such as `ai.generateText`, `ai.generateText.doGenerate`, or `ai.toolCall`. Instead, it adds only the selected supplemental attributes to the spans emitted by `OpenTelemetry`, such as the default root span `invoke_agent {modelId}`, `step {n}`, `chat {modelId}`, and `execute_tool {toolName}`.
743
766
 
744
767
  All supplemental attributes are disabled by default. Enable only the data you want to collect:
745
768
 
@@ -40,7 +40,7 @@ The AI SDK includes the following harness adapters:
40
40
  | [Codex](/providers/ai-sdk-harnesses/codex) | Sandbox bridge | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Check /> | <Cross /> |
41
41
  | [Cursor](/providers/ai-sdk-harnesses/cursor) | Sandbox via ACP | <Check /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Cross /> |
42
42
  | [Deep Agents](/providers/ai-sdk-harnesses/deepagents) | Sandbox bridge | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> via auto-rejection | <Check /> | <Cross /> |
43
- | [fx](/providers/ai-sdk-harnesses/fx) | Host process | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> |
43
+ | [fx](/providers/ai-sdk-harnesses/fx) | Host process | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> |
44
44
  | [GitHub Copilot](/providers/ai-sdk-harnesses/github-copilot) | Sandbox via ACP | <Check /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Cross /> |
45
45
  | [Grok Build](/providers/ai-sdk-harnesses/grok-build) | Sandbox via ACP | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Check /> | <Cross /> |
46
46
  | [OpenCode](/providers/ai-sdk-harnesses/opencode) | Sandbox bridge | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> |
@@ -1735,7 +1735,7 @@ To see `generateText` in action, check out [these examples](#examples).
1735
1735
  },
1736
1736
  {
1737
1737
  name: 'finishReason',
1738
- type: '"stop" | "length" | "content-filter" | "tool-calls" | "error" | "other"',
1738
+ type: '"stop" | "length" | "content-filter" | "tool-calls" | "error" | "interrupted" | "other"',
1739
1739
  description: 'The unified reason why the generation finished.',
1740
1740
  },
1741
1741
  {
@@ -2029,7 +2029,7 @@ To see `generateText` in action, check out [these examples](#examples).
2029
2029
  },
2030
2030
  {
2031
2031
  name: 'finishReason',
2032
- type: '"stop" | "length" | "content-filter" | "tool-calls" | "error" | "other"',
2032
+ type: '"stop" | "length" | "content-filter" | "tool-calls" | "error" | "interrupted" | "other"',
2033
2033
  description: 'The unified reason why the generation finished.',
2034
2034
  },
2035
2035
  {
@@ -2634,7 +2634,7 @@ To see `generateText` in action, check out [these examples](#examples).
2634
2634
  },
2635
2635
  {
2636
2636
  name: 'finishReason',
2637
- type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'",
2637
+ type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'",
2638
2638
  description: 'The reason the model finished generating the text.',
2639
2639
  },
2640
2640
  {
@@ -3039,7 +3039,7 @@ To see `generateText` in action, check out [these examples](#examples).
3039
3039
  },
3040
3040
  {
3041
3041
  name: 'finishReason',
3042
- type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'",
3042
+ type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'",
3043
3043
  description: 'The reason why the generation finished.',
3044
3044
  },
3045
3045
  {
@@ -1411,7 +1411,7 @@ retain the content their contracts require.
1411
1411
  },
1412
1412
  {
1413
1413
  name: 'finishReason',
1414
- type: '"stop" | "length" | "content-filter" | "tool-calls" | "error" | "other"',
1414
+ type: '"stop" | "length" | "content-filter" | "tool-calls" | "error" | "interrupted" | "other"',
1415
1415
  description:
1416
1416
  'The unified finish reason why the generation finished.',
1417
1417
  },
@@ -1746,7 +1746,7 @@ retain the content their contracts require.
1746
1746
  parameters: [
1747
1747
  {
1748
1748
  name: 'finishReason',
1749
- type: '"stop" | "length" | "content-filter" | "tool-calls" | "error" | "other"',
1749
+ type: '"stop" | "length" | "content-filter" | "tool-calls" | "error" | "interrupted" | "other"',
1750
1750
  description:
1751
1751
  'The unified finish reason why the generation finished.',
1752
1752
  },
@@ -2674,9 +2674,9 @@ retain the content their contracts require.
2674
2674
  },
2675
2675
  {
2676
2676
  name: 'finishReason',
2677
- type: "PromiseLike<'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'>",
2677
+ type: "PromiseLike<'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'>",
2678
2678
  description:
2679
- 'The reason why the generation finished. Automatically consumes the stream.',
2679
+ 'The reason why the generation finished. A stream that ends cleanly without its provider completion signal can report "interrupted". Partial output is preserved; interruption alone does not emit an error. Streams that produce no output and no finish chunk still reject with NoOutputGeneratedError. OpenAI-compatible retains its existing error behavior. Automatically consumes the stream.',
2680
2680
  },
2681
2681
  {
2682
2682
  name: 'rawFinishReason',
@@ -3165,7 +3165,7 @@ retain the content their contracts require.
3165
3165
  },
3166
3166
  {
3167
3167
  name: 'finishReason',
3168
- type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'",
3168
+ type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'",
3169
3169
  description: 'The reason the model finished generating the text.',
3170
3170
  },
3171
3171
  {
@@ -3873,7 +3873,7 @@ retain the content their contracts require.
3873
3873
  },
3874
3874
  {
3875
3875
  name: 'finishReason',
3876
- type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'",
3876
+ type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'",
3877
3877
  description: 'The reason the model finished generating the text.',
3878
3878
  },
3879
3879
  {
@@ -3911,7 +3911,7 @@ retain the content their contracts require.
3911
3911
  },
3912
3912
  {
3913
3913
  name: 'finishReason',
3914
- type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'",
3914
+ type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'",
3915
3915
  description: 'The reason the model finished generating the text.',
3916
3916
  },
3917
3917
  {
@@ -317,7 +317,7 @@ Allows you to easily create a conversational user interface for your chatbot app
317
317
  },
318
318
  {
319
319
  name: 'finishReason',
320
- type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'",
320
+ type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'",
321
321
  isOptional: true,
322
322
  description:
323
323
  'The reason why the model finished generating the response. Undefined if the finish reason was not provided by the model.',
@@ -179,7 +179,7 @@ outcomes and call `setOutcome` once.
179
179
  name: 'finishReason',
180
180
  type: 'FinishReason | undefined',
181
181
  description:
182
- "The reason why the generation finished. One of: 'stop', 'length', 'content-filter', 'tool-calls', 'error', or 'other'.",
182
+ "The reason why the generation finished. One of: 'stop', 'length', 'content-filter', 'tool-calls', 'error', 'interrupted', or 'other'.",
183
183
  },
184
184
  ],
185
185
  },
@@ -649,7 +649,7 @@ To see `streamUI` in action, check out [these examples](#examples).
649
649
  },
650
650
  {
651
651
  name: 'finishReason',
652
- type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'",
652
+ type: "'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'",
653
653
  description: 'The reason the model finished generating the text.',
654
654
  },
655
655
  {
@@ -5,14 +5,16 @@ description: Learn how to fix AI SDK ToolChoiceViolationError
5
5
 
6
6
  # ToolChoiceViolationError
7
7
 
8
- This error occurs when a `generateText` response does not satisfy an enforced
9
- tool choice. It is thrown when `toolChoice` is set to `'required'` but the
10
- response contains no structured tool call, or when a specifically selected tool
11
- was not called.
8
+ This error occurs when a `generateText` or `streamText` response does not satisfy
9
+ an enforced tool choice. It is thrown when `toolChoice` is set to `'none'` but
10
+ the response contains a tool call, when `'required'` produces no structured tool
11
+ call, or when a specifically selected tool was not called. With `streamText`,
12
+ the error is emitted in the stream. Tool calls returned while `'none'` is selected
13
+ are not executed.
12
14
 
13
15
  The error does not automatically interpret text or reasoning as an executable
14
- tool call. You can inspect `content` to implement opt-in recovery, including
15
- schema validation before executing any recovered call.
16
+ tool call. When tool calls are allowed, you can inspect `content` to implement
17
+ opt-in recovery, including schema validation before executing any recovered call.
16
18
 
17
19
  ## Properties
18
20
 
@@ -30,7 +32,10 @@ You can check if an error is an instance of `ToolChoiceViolationError` using:
30
32
  ```typescript
31
33
  import { ToolChoiceViolationError } from 'ai';
32
34
 
33
- if (ToolChoiceViolationError.isInstance(error)) {
35
+ if (
36
+ ToolChoiceViolationError.isInstance(error) &&
37
+ error.toolChoice.type !== 'none'
38
+ ) {
34
39
  const serializedCall = error.content.find(part => part.type === 'text')?.text;
35
40
 
36
41
  // Parse and validate serializedCall before treating it as a tool call.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.148",
3
+ "version": "7.0.149",
4
4
  "type": "module",
5
5
  "description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
6
6
  "license": "Apache-2.0",
@@ -42,20 +42,20 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.121",
46
- "@ai-sdk/provider": "4.0.31",
47
- "@ai-sdk/provider-utils": "5.0.65"
45
+ "@ai-sdk/gateway": "4.0.122",
46
+ "@ai-sdk/provider": "4.0.32",
47
+ "@ai-sdk/provider-utils": "5.0.66"
48
48
  },
49
49
  "devDependencies": {
50
- "@ai-sdk/amazon-bedrock": "5.0.121",
51
- "@ai-sdk/deepseek": "3.0.70",
52
- "@ai-sdk/google": "4.0.102",
53
- "@ai-sdk/groq": "4.0.67",
54
- "@ai-sdk/huggingface": "2.0.75",
55
- "@ai-sdk/moonshotai": "3.0.74",
56
- "@ai-sdk/openai": "4.0.99",
50
+ "@ai-sdk/amazon-bedrock": "5.0.122",
51
+ "@ai-sdk/deepseek": "3.0.71",
52
+ "@ai-sdk/google": "4.0.103",
53
+ "@ai-sdk/groq": "4.0.68",
54
+ "@ai-sdk/huggingface": "2.0.76",
55
+ "@ai-sdk/moonshotai": "3.0.75",
56
+ "@ai-sdk/openai": "4.0.100",
57
57
  "@ai-sdk/test-server": "2.0.4",
58
- "@ai-sdk/xai": "5.0.27",
58
+ "@ai-sdk/xai": "5.0.28",
59
59
  "@edge-runtime/vm": "^5.0.0",
60
60
  "@smithy/eventstream-codec": "^4.3.3",
61
61
  "@smithy/util-utf8": "^4.3.3",
@@ -11,7 +11,7 @@ const symbol = Symbol.for(marker);
11
11
 
12
12
  type EnforcedToolChoice = Extract<
13
13
  LanguageModelV4ToolChoice,
14
- { type: 'required' } | { type: 'tool' }
14
+ { type: 'none' } | { type: 'required' } | { type: 'tool' }
15
15
  >;
16
16
 
17
17
  /**
@@ -54,9 +54,11 @@ export class ToolChoiceViolationError extends AISDKError {
54
54
  provider,
55
55
  modelId,
56
56
  content,
57
- message = toolChoice.type === 'required'
58
- ? 'Model response did not contain a tool call even though tool choice was required.'
59
- : `Model response did not contain a call to the required tool '${toolChoice.toolName}'.`,
57
+ message = toolChoice.type === 'none'
58
+ ? 'Model response contained a tool call even though tool choice was none.'
59
+ : toolChoice.type === 'required'
60
+ ? 'Model response did not contain a tool call even though tool choice was required.'
61
+ : `Model response did not contain a call to the required tool '${toolChoice.toolName}'.`,
60
62
  }: {
61
63
  toolChoice: EnforcedToolChoice;
62
64
  finishReason: FinishReason;
@@ -88,6 +88,7 @@ export function collectToolApprovals<TOOLS extends ToolSet>({
88
88
 
89
89
  const approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>> = [];
90
90
  const deniedToolApprovals: Array<CollectedToolApprovals<TOOLS>> = [];
91
+ const collectedToolCallIds = new Set<string>();
91
92
 
92
93
  const approvalResponses = lastMessage.content.filter(
93
94
  part => part.type === 'tool-approval-response',
@@ -119,6 +120,15 @@ export function collectToolApprovals<TOOLS extends ToolSet>({
119
120
  });
120
121
  }
121
122
 
123
+ // Only the first response for a tool call is collected. Later responses
124
+ // (replays, conflicting decisions, or responses to another approval of the
125
+ // same call) would otherwise execute the tool twice or both execute and
126
+ // deny it.
127
+ if (collectedToolCallIds.has(toolCall.toolCallId)) {
128
+ continue;
129
+ }
130
+ collectedToolCallIds.add(toolCall.toolCallId);
131
+
122
132
  const approval: CollectedToolApprovals<TOOLS> = {
123
133
  approvalRequest,
124
134
  approvalResponse,
@@ -939,7 +939,7 @@ export async function generateText<
939
939
  activeTools: prepareStepResult?.activeTools ?? activeTools,
940
940
  });
941
941
  const {
942
- executionTools: stepExecutionTools,
942
+ executionTools,
943
943
  modelTools: stepModelTools,
944
944
  toolCallerMessages,
945
945
  } = prepareToolsForToolCallers({
@@ -969,6 +969,8 @@ export async function generateText<
969
969
  const stepToolChoice = prepareToolChoice({
970
970
  toolChoice: prepareStepResult?.toolChoice ?? toolChoice,
971
971
  });
972
+ const stepExecutionTools =
973
+ stepToolChoice.type === 'none' ? undefined : executionTools;
972
974
 
973
975
  const stepMessages = appendToolCallerMessages({
974
976
  messages: prepareStepResult?.messages ?? stepInputMessages,
@@ -1097,8 +1099,16 @@ export async function generateText<
1097
1099
  parseToolCall({
1098
1100
  toolCall,
1099
1101
  tools: stepModelTools as TOOLS,
1100
- repairToolCall,
1101
- refineToolInput,
1102
+ // tool calls are rejected with tool choice none,
1103
+ // so user callbacks must not process them:
1104
+ repairToolCall:
1105
+ stepToolChoice.type === 'none'
1106
+ ? undefined
1107
+ : repairToolCall,
1108
+ refineToolInput:
1109
+ stepToolChoice.type === 'none'
1110
+ ? undefined
1111
+ : refineToolInput,
1102
1112
  instructions: stepInstructions,
1103
1113
  messages: stepMessages,
1104
1114
  abortSignal: mergedAbortSignal,
@@ -1171,18 +1181,19 @@ export async function generateText<
1171
1181
  });
1172
1182
 
1173
1183
  const enforcedToolChoice =
1174
- stepToolChoice.type === 'required' ||
1175
- stepToolChoice.type === 'tool'
1176
- ? stepToolChoice
1177
- : undefined;
1184
+ stepToolChoice.type === 'auto' ? undefined : stepToolChoice;
1178
1185
 
1179
1186
  if (
1180
1187
  enforcedToolChoice != null &&
1181
- !stepToolCalls.some(
1182
- toolCall =>
1183
- enforcedToolChoice.type === 'required' ||
1184
- toolCall.toolName === enforcedToolChoice.toolName,
1185
- )
1188
+ (enforcedToolChoice.type === 'none'
1189
+ ? stepToolCalls.length > 0
1190
+ : currentModelResponse.finishReason.unified !==
1191
+ 'interrupted' &&
1192
+ !stepToolCalls.some(
1193
+ toolCall =>
1194
+ enforcedToolChoice.type === 'required' ||
1195
+ toolCall.toolName === enforcedToolChoice.toolName,
1196
+ ))
1186
1197
  ) {
1187
1198
  throw new ToolChoiceViolationError({
1188
1199
  toolChoice: enforcedToolChoice,
@@ -1531,6 +1542,7 @@ export async function generateText<
1531
1542
  }
1532
1543
  }
1533
1544
  } while (
1545
+ steps[steps.length - 1].finishReason !== 'interrupted' &&
1534
1546
  // Continue only after all client tool calls have been executed or denied,
1535
1547
  // and if there are client results or pending deferred provider results.
1536
1548
  clientToolOutputs.length + deniedToolApprovalResponses.length ===
@@ -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;