ai 6.0.313 → 6.0.314

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.
@@ -164,7 +164,7 @@ function detectMediaType({
164
164
  var import_provider_utils2 = require("@ai-sdk/provider-utils");
165
165
 
166
166
  // src/version.ts
167
- var VERSION = true ? "6.0.313" : "0.0.0-test";
167
+ var VERSION = true ? "6.0.314" : "0.0.0-test";
168
168
 
169
169
  // src/util/download/download.ts
170
170
  var download = async ({
@@ -144,7 +144,7 @@ import {
144
144
  } from "@ai-sdk/provider-utils";
145
145
 
146
146
  // src/version.ts
147
- var VERSION = true ? "6.0.313" : "0.0.0-test";
147
+ var VERSION = true ? "6.0.314" : "0.0.0-test";
148
148
 
149
149
  // src/util/download/download.ts
150
150
  var download = async ({
@@ -603,6 +603,12 @@ It supports the following settings:
603
603
  - `none`: the model must not call tools
604
604
  - `{ type: 'tool', toolName: string (typed) }`: the model must call the specified tool
605
605
 
606
+ When `toolChoice` is `'none'`, the SDK prevents local tool execution even if the
607
+ model returns a tool call. Such responses produce a
608
+ [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error).
609
+ With `streamText`, tool parts are withheld from the stream, so client-side tools
610
+ are not invoked either. Tool call repair is skipped for rejected tool calls.
611
+
606
612
  ```ts highlight="18"
607
613
  import { z } from 'zod';
608
614
  import { generateText, tool } from 'ai';
@@ -6,13 +6,15 @@ description: Learn how to handle AI_ToolChoiceViolationError
6
6
  # AI_ToolChoiceViolationError
7
7
 
8
8
  This error occurs when a model response does not satisfy an enforced tool
9
- choice. It is thrown when `toolChoice` is set to `'required'` but the response
10
- contains no structured tool call, or when a specifically selected tool was not
11
- called.
9
+ choice. It is thrown when `toolChoice` is set to `'none'` but the response
10
+ contains a tool call, when `'required'` produces no structured tool call, or
11
+ when a specifically selected tool was not called. With `streamText`, the error
12
+ is emitted in the stream when `'none'` is selected. Tool calls returned while
13
+ `'none'` is selected 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 `AI_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": "6.0.313",
3
+ "version": "6.0.314",
4
4
  "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.",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -11,7 +11,7 @@ const symbol = Symbol.for(marker);
11
11
 
12
12
  type EnforcedToolChoice = Extract<
13
13
  LanguageModelV3ToolChoice,
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;
@@ -956,7 +956,12 @@ export async function generateText<
956
956
  parseToolCall({
957
957
  toolCall,
958
958
  tools: stepToolSet,
959
- repairToolCall,
959
+ // tool calls are rejected with tool choice none,
960
+ // so user callbacks must not process them:
961
+ repairToolCall:
962
+ stepToolChoice?.type === 'none'
963
+ ? undefined
964
+ : repairToolCall,
960
965
  system,
961
966
  messages: stepInputMessages,
962
967
  }),
@@ -964,18 +969,17 @@ export async function generateText<
964
969
  );
965
970
 
966
971
  const enforcedToolChoice =
967
- stepToolChoice?.type === 'required' ||
968
- stepToolChoice?.type === 'tool'
969
- ? stepToolChoice
970
- : undefined;
972
+ stepToolChoice?.type === 'auto' ? undefined : stepToolChoice;
971
973
 
972
974
  if (
973
975
  enforcedToolChoice != null &&
974
- !stepToolCalls.some(
975
- toolCall =>
976
- enforcedToolChoice.type === 'required' ||
977
- toolCall.toolName === enforcedToolChoice.toolName,
978
- )
976
+ (enforcedToolChoice.type === 'none'
977
+ ? stepToolCalls.length > 0
978
+ : !stepToolCalls.some(
979
+ toolCall =>
980
+ enforcedToolChoice.type === 'required' ||
981
+ toolCall.toolName === enforcedToolChoice.toolName,
982
+ ))
979
983
  ) {
980
984
  throw new ToolChoiceViolationError({
981
985
  toolChoice: enforcedToolChoice,
@@ -1,5 +1,7 @@
1
1
  import type {
2
+ LanguageModelV3Content,
2
3
  LanguageModelV3StreamPart,
4
+ LanguageModelV3ToolChoice,
3
5
  SharedV3Warning,
4
6
  } from '@ai-sdk/provider';
5
7
  import {
@@ -10,6 +12,7 @@ import {
10
12
  } from '@ai-sdk/provider-utils';
11
13
  import type { Tracer } from '@opentelemetry/api';
12
14
  import { ToolCallNotFoundForApprovalError } from '../error/tool-call-not-found-for-approval-error';
15
+ import { ToolChoiceViolationError } from '../error/tool-choice-violation-error';
13
16
  import type { TelemetrySettings } from '../telemetry/telemetry-settings';
14
17
  import type {
15
18
  FinishReason,
@@ -122,6 +125,7 @@ export type SingleRequestTextStreamPart<TOOLS extends ToolSet> =
122
125
 
123
126
  export function runToolsTransformation<TOOLS extends ToolSet>({
124
127
  tools,
128
+ toolChoice,
125
129
  generatorStream,
126
130
  tracer,
127
131
  telemetry,
@@ -138,6 +142,7 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
138
142
  onToolCallFinish,
139
143
  }: {
140
144
  tools: TOOLS | undefined;
145
+ toolChoice?: LanguageModelV3ToolChoice;
141
146
  generatorStream: ReadableStream<LanguageModelV3StreamPart>;
142
147
  tracer: Tracer;
143
148
  telemetry: TelemetrySettings | undefined;
@@ -265,6 +270,13 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
265
270
  // overtake pending start or delta callbacks in a downstream stream.
266
271
  const activeToolCallToolNames = new Map<string, string>();
267
272
 
273
+ // Tool choice none rejects tool calls when the model call finishes.
274
+ // Withhold tool parts so that consumers (e.g. client-side tools) cannot
275
+ // act on them before the rejection, and skip user callbacks that would
276
+ // repair them or process their input.
277
+ const withholdToolParts = toolChoice?.type === 'none';
278
+ const withheldToolContent: Array<LanguageModelV3Content> = [];
279
+
268
280
  // forward stream
269
281
  const forwardStream = new TransformStream<
270
282
  LanguageModelV3StreamPart,
@@ -278,6 +290,24 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
278
290
  ) {
279
291
  const chunkType = chunk.type;
280
292
 
293
+ if (
294
+ withholdToolParts &&
295
+ (chunkType === 'tool-input-start' ||
296
+ chunkType === 'tool-input-delta' ||
297
+ chunkType === 'tool-input-end' ||
298
+ chunkType === 'tool-approval-request')
299
+ ) {
300
+ return;
301
+ }
302
+
303
+ if (
304
+ withholdToolParts &&
305
+ (chunkType === 'tool-call' || chunkType === 'tool-result')
306
+ ) {
307
+ withheldToolContent.push(chunk);
308
+ return;
309
+ }
310
+
281
311
  switch (chunkType) {
282
312
  // forward:
283
313
  case 'stream-start':
@@ -343,14 +373,36 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
343
373
  }
344
374
 
345
375
  case 'finish': {
376
+ const toolChoiceViolationError =
377
+ withholdToolParts &&
378
+ withheldToolContent.some(part => part.type === 'tool-call')
379
+ ? new ToolChoiceViolationError({
380
+ toolChoice: { type: 'none' },
381
+ finishReason: chunk.finishReason.unified,
382
+ provider: model?.provider ?? 'unknown',
383
+ modelId: model?.modelId ?? 'unknown',
384
+ content: withheldToolContent,
385
+ })
386
+ : undefined;
387
+
346
388
  finishChunk = {
347
389
  type: 'finish',
348
- finishReason: chunk.finishReason.unified,
390
+ finishReason:
391
+ toolChoiceViolationError == null
392
+ ? chunk.finishReason.unified
393
+ : 'error',
349
394
  rawFinishReason: chunk.finishReason.raw,
350
395
  usage: asLanguageModelUsage(chunk.usage),
351
396
  providerMetadata: chunk.providerMetadata,
352
397
  };
353
398
 
399
+ if (toolChoiceViolationError != null) {
400
+ controller.enqueue({
401
+ type: 'error',
402
+ error: toolChoiceViolationError,
403
+ });
404
+ }
405
+
354
406
  if (isToolExecutionAllowedFinishReason(chunk.finishReason.unified)) {
355
407
  for (const toolCall of toolCallsToExecute.splice(0)) {
356
408
  executeToolCallAfterFinish(toolCall);
@@ -1946,6 +1946,7 @@ class DefaultStreamTextResult<
1946
1946
 
1947
1947
  const streamWithToolResults = runToolsTransformation({
1948
1948
  tools: stepToolSet,
1949
+ toolChoice: stepToolChoice,
1949
1950
  generatorStream: stream,
1950
1951
  tracer,
1951
1952
  telemetry,