ai 7.0.112 → 7.0.113

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 (40) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/index.d.ts +16 -0
  3. package/dist/index.js +315 -213
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.d.ts +2 -1
  6. package/dist/internal/index.js +105 -28
  7. package/dist/internal/index.js.map +1 -1
  8. package/docs/03-agents/06-tool-approvals.mdx +16 -0
  9. package/docs/03-ai-sdk-core/37-speech.mdx +2 -0
  10. package/docs/07-reference/01-ai-sdk-core/12-generate-speech.mdx +1 -1
  11. package/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx +18 -0
  12. package/package.json +11 -11
  13. package/src/embed/embed-many.ts +18 -2
  14. package/src/generate-speech/generate-speech.ts +15 -4
  15. package/src/generate-speech/generated-audio-file.ts +0 -8
  16. package/src/generate-text/execute-tools-from-stream.ts +0 -2
  17. package/src/generate-text/generate-text.ts +1 -0
  18. package/src/generate-text/generated-file.ts +0 -8
  19. package/src/generate-text/invoke-tool-callbacks-from-stream.ts +9 -8
  20. package/src/generate-text/output.ts +0 -2
  21. package/src/generate-text/parse-tool-call.ts +38 -25
  22. package/src/generate-text/stream-text.ts +1 -0
  23. package/src/generate-text/to-response-messages.ts +7 -0
  24. package/src/generate-text/tool-call.ts +26 -0
  25. package/src/generate-text/validate-tool-approvals.ts +39 -3
  26. package/src/generate-video/generate-video.ts +0 -2
  27. package/src/middleware/extract-reasoning-middleware.ts +1 -1
  28. package/src/middleware/wrap-embedding-model.ts +9 -1
  29. package/src/model/get-embedding-model-provider-options-transformer.ts +17 -0
  30. package/src/prompt/content-part.ts +3 -0
  31. package/src/registry/custom-provider.ts +12 -5
  32. package/src/ui/chat.ts +108 -17
  33. package/src/ui/convert-to-model-messages.ts +8 -0
  34. package/src/ui/direct-chat-transport.ts +2 -0
  35. package/src/ui/process-ui-message-stream.ts +6 -0
  36. package/src/ui/ui-messages.ts +10 -0
  37. package/src/ui/validate-ui-messages.ts +98 -132
  38. package/src/ui-message-stream/to-ui-message-chunk.ts +7 -0
  39. package/src/ui-message-stream/ui-message-chunks.ts +2 -0
  40. package/src/util/write-to-server-response.ts +0 -2
@@ -272,6 +272,22 @@ When a manual approval status includes a reason, it is available as
272
272
  `part.approval.requestReason`. A reason supplied with
273
273
  `addToolApprovalResponse` is stored separately as `part.approval.reason`.
274
274
 
275
+ ### Schema transforms and persisted approvals
276
+
277
+ Approval requests preserve the original schema input in `inputSchemaInput` when
278
+ it differs from the input presented for approval. Keep this field when persisting
279
+ `responseMessages` or UI messages; the UI message conversion functions preserve it
280
+ automatically.
281
+
282
+ On continuation, the SDK reconstructs the transformed input and checks that it
283
+ matches the approved input. It never replaces the approved input with a different
284
+ value. Older or projected histories that omit the original input are rejected as
285
+ invalid tool input if revalidation fails or would change the approved value.
286
+
287
+ The original input is included in persisted messages and UI approval streams,
288
+ including fields removed by schema transforms. A transform that removes a field
289
+ does not redact it from approval metadata.
290
+
275
291
  ## Security Considerations
276
292
 
277
293
  ### Trust model
@@ -147,6 +147,8 @@ try {
147
147
  | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-2.5-flash-preview-tts` |
148
148
  | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-2.5-pro-preview-tts` |
149
149
  | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-3.1-flash-tts-preview` |
150
+ | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-3.8-flash-tts` |
151
+ | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-3.8-flash-lite-tts` |
150
152
  | [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-flash-tts` |
151
153
  | [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-pro-tts` |
152
154
  | [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-flash-lite-preview-tts` |
@@ -79,7 +79,7 @@ const { audio } = await generateSpeech({
79
79
  type: 'string',
80
80
  isOptional: true,
81
81
  description:
82
- 'The output format to use for the speech e.g. "mp3", "wav", etc.',
82
+ 'The output format to use for the speech, such as "mp3", "wav", or headerless "audio/l16", "audio/mulaw", and "audio/alaw". Supported formats and defaults vary by provider and model.',
83
83
  },
84
84
  {
85
85
  name: 'instructions',
@@ -100,6 +100,24 @@ const validatedMessages = await validateUIMessages({
100
100
  });
101
101
  ```
102
102
 
103
+ When validating approval messages produced with
104
+ `experimental_refineToolInput`, pass the same refinement functions so
105
+ `validateUIMessages` can reconstruct and verify the approved input:
106
+
107
+ ```typescript
108
+ const experimental_refineToolInput = {
109
+ weather: (input: { location: string }) => ({
110
+ location: input.location.trim(),
111
+ }),
112
+ };
113
+
114
+ const validatedMessages = await validateUIMessages({
115
+ messages,
116
+ tools,
117
+ experimental_refineToolInput,
118
+ });
119
+ ```
120
+
103
121
  ## Deprecated `rawInput` field
104
122
 
105
123
  For backward compatibility, validation still accepts `rawInput` on tool parts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.112",
3
+ "version": "7.0.113",
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.90",
45
+ "@ai-sdk/gateway": "4.0.91",
46
46
  "@ai-sdk/provider": "4.0.18",
47
- "@ai-sdk/provider-utils": "5.0.46"
47
+ "@ai-sdk/provider-utils": "5.0.47"
48
48
  },
49
49
  "devDependencies": {
50
- "@ai-sdk/amazon-bedrock": "5.0.92",
51
- "@ai-sdk/deepseek": "3.0.51",
52
- "@ai-sdk/google": "4.0.78",
53
- "@ai-sdk/groq": "4.0.47",
54
- "@ai-sdk/huggingface": "2.0.54",
55
- "@ai-sdk/moonshotai": "3.0.55",
56
- "@ai-sdk/openai": "4.0.73",
50
+ "@ai-sdk/amazon-bedrock": "5.0.93",
51
+ "@ai-sdk/deepseek": "3.0.52",
52
+ "@ai-sdk/google": "4.0.79",
53
+ "@ai-sdk/groq": "4.0.48",
54
+ "@ai-sdk/huggingface": "2.0.55",
55
+ "@ai-sdk/moonshotai": "3.0.56",
56
+ "@ai-sdk/openai": "4.0.74",
57
57
  "@ai-sdk/test-server": "2.0.1",
58
- "@ai-sdk/xai": "5.0.6",
58
+ "@ai-sdk/xai": "5.0.7",
59
59
  "@edge-runtime/vm": "^5.0.0",
60
60
  "@smithy/eventstream-codec": "^4.3.3",
61
61
  "@smithy/util-utf8": "^4.3.3",
@@ -7,6 +7,7 @@ import {
7
7
  } from '@ai-sdk/provider-utils';
8
8
  import { logWarnings } from '../logger/log-warnings';
9
9
  import { getEmbeddingModelMaxInputBytesPerCall } from '../model/get-embedding-model-max-input-bytes-per-call';
10
+ import { getEmbeddingModelProviderOptionsTransformer } from '../model/get-embedding-model-provider-options-transformer';
10
11
  import { resolveEmbeddingModel } from '../model/resolve-model';
11
12
  import { createRestrictedTelemetryDispatcher } from './restricted-telemetry-dispatcher';
12
13
  import type { TelemetryOptions } from '../telemetry/telemetry-options';
@@ -321,6 +322,8 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
321
322
  ? maxInputBytesPerCall
322
323
  : Infinity,
323
324
  });
325
+ const providerOptionsTransformer =
326
+ getEmbeddingModelProviderOptionsTransformer(model);
324
327
 
325
328
  const embeddings: Array<Embedding> = [];
326
329
  const warnings: Array<Warning> = [];
@@ -339,9 +342,22 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
339
342
  supportsParallelCalls ? maxParallelCalls : 1,
340
343
  );
341
344
 
345
+ let nextChunkStartIndex = 0;
342
346
  for (const parallelChunk of parallelChunks) {
343
347
  const results = await Promise.all(
344
348
  parallelChunk.map(async chunk => {
349
+ // Capture the range before awaiting transformations or retrying.
350
+ const startIndex = nextChunkStartIndex;
351
+ nextChunkStartIndex += chunk.length;
352
+ const chunkProviderOptions = providerOptionsTransformer
353
+ ? await providerOptionsTransformer({
354
+ providerOptions,
355
+ values,
356
+ startIndex,
357
+ endIndex: startIndex + chunk.length,
358
+ })
359
+ : providerOptions;
360
+
345
361
  const result = await retry(async () => {
346
362
  const embedCallId = generateCallId();
347
363
 
@@ -361,7 +377,7 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
361
377
  values: chunk,
362
378
  abortSignal,
363
379
  headers: headersWithUserAgent,
364
- providerOptions,
380
+ providerOptions: chunkProviderOptions,
365
381
  });
366
382
 
367
383
  const chunkEmbeddings = modelResponse.embeddings;
@@ -412,7 +428,7 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
412
428
  result.providerMetadata,
413
429
  )) {
414
430
  providerMetadata[providerName] = {
415
- ...(providerMetadata[providerName] ?? {}),
431
+ ...providerMetadata[providerName],
416
432
  ...metadata,
417
433
  };
418
434
  }
@@ -204,10 +204,21 @@ function getOutputFormatMediaType(outputFormat: string | undefined) {
204
204
 
205
205
  const normalizedOutputFormat = outputFormat.trim().toLowerCase();
206
206
 
207
- return normalizedOutputFormat === 'pcm' ||
208
- normalizedOutputFormat === 'audio/pcm'
209
- ? 'audio/pcm'
210
- : undefined;
207
+ switch (normalizedOutputFormat) {
208
+ case 'pcm':
209
+ case 'audio/pcm':
210
+ return 'audio/pcm';
211
+ case 'audio/l16':
212
+ return 'audio/l16';
213
+ case 'mulaw':
214
+ case 'audio/mulaw':
215
+ return 'audio/mulaw';
216
+ case 'alaw':
217
+ case 'audio/alaw':
218
+ return 'audio/alaw';
219
+ default:
220
+ return undefined;
221
+ }
211
222
  }
212
223
 
213
224
  class DefaultSpeechResult implements SpeechResult {
@@ -53,12 +53,4 @@ export class DefaultGeneratedAudioFile
53
53
 
54
54
  export class DefaultGeneratedAudioFileWithType extends DefaultGeneratedAudioFile {
55
55
  readonly type = 'audio';
56
-
57
- constructor(options: {
58
- data: string | Uint8Array;
59
- mediaType: string;
60
- format: string;
61
- }) {
62
- super(options);
63
- }
64
56
  }
@@ -265,8 +265,6 @@ export function executeToolsFromStream<
265
265
  }
266
266
  }),
267
267
  );
268
-
269
- return;
270
268
  }
271
269
  }
272
270
  },
@@ -726,6 +726,7 @@ export async function generateText<
726
726
  toolsContext,
727
727
  runtimeContext,
728
728
  toolApprovalSecret: experimental_toolApprovalSecret,
729
+ refineToolInput,
729
730
  });
730
731
 
731
732
  const deniedToolApprovals = [
@@ -78,12 +78,4 @@ export class DefaultGeneratedFile implements GeneratedFile {
78
78
 
79
79
  export class DefaultGeneratedFileWithType extends DefaultGeneratedFile {
80
80
  readonly type = 'file';
81
-
82
- constructor(options: {
83
- data: string | Uint8Array;
84
- mediaType: string;
85
- providerMetadata?: Record<string, JSONObject>;
86
- }) {
87
- super(options);
88
- }
89
81
  }
@@ -35,7 +35,7 @@ export function invokeToolCallbacksFromStream<TOOLS extends ToolSet>({
35
35
  string,
36
36
  {
37
37
  toolName: string;
38
- validatedContext: Promise<unknown> | undefined;
38
+ validatedContexts: Record<string, Promise<unknown> | undefined>;
39
39
  }
40
40
  > = createIdMap();
41
41
 
@@ -48,22 +48,23 @@ export function invokeToolCallbacksFromStream<TOOLS extends ToolSet>({
48
48
  }): Promise<unknown> => {
49
49
  const ongoingToolCall = ongoingToolCalls[toolCallId];
50
50
 
51
- if (ongoingToolCall?.validatedContext != null) {
52
- return ongoingToolCall.validatedContext;
51
+ const validatedContext = ongoingToolCall?.validatedContexts[toolName];
52
+ if (validatedContext != null) {
53
+ return validatedContext;
53
54
  }
54
55
 
55
56
  const tool = getOwn(tools, toolName);
56
- const validatedContext = validateToolContext({
57
+ const newValidatedContext = validateToolContext({
57
58
  toolName,
58
59
  context: getOwn(toolsContext, toolName),
59
60
  contextSchema: tool?.contextSchema,
60
61
  });
61
62
 
62
63
  if (ongoingToolCall != null) {
63
- ongoingToolCall.validatedContext = validatedContext;
64
+ ongoingToolCall.validatedContexts[toolName] = newValidatedContext;
64
65
  }
65
66
 
66
- return validatedContext;
67
+ return newValidatedContext;
67
68
  };
68
69
 
69
70
  return stream.pipeThrough(
@@ -80,7 +81,7 @@ export function invokeToolCallbacksFromStream<TOOLS extends ToolSet>({
80
81
  case 'tool-input-start': {
81
82
  ongoingToolCalls[chunk.id] = {
82
83
  toolName: chunk.toolName,
83
- validatedContext: undefined,
84
+ validatedContexts: createIdMap(),
84
85
  };
85
86
 
86
87
  const tool = getOwn(tools, chunk.toolName);
@@ -120,7 +121,7 @@ export function invokeToolCallbacksFromStream<TOOLS extends ToolSet>({
120
121
  }
121
122
 
122
123
  case 'tool-call': {
123
- const toolName = ongoingToolCalls[chunk.toolCallId]?.toolName;
124
+ const toolName = chunk.toolName;
124
125
  const tool = getOwn(tools, toolName);
125
126
 
126
127
  if (!chunk.invalid && tool?.onInputAvailable != null) {
@@ -487,8 +487,6 @@ function getArrayLengthValidationError({
487
487
  cause: `elements array must contain at most ${maxItems} items`,
488
488
  });
489
489
  }
490
-
491
- return undefined;
492
490
  }
493
491
 
494
492
  /**
@@ -12,7 +12,12 @@ import { NoSuchToolError } from '../error/no-such-tool-error';
12
12
  import { ToolCallRepairError } from '../error/tool-call-repair-error';
13
13
  import type { Instructions } from '../prompt';
14
14
  import { getOwn } from '../util/get-own';
15
- import type { DynamicToolCall, TypedToolCall } from './tool-call';
15
+ import {
16
+ getToolCallInputSchemaInput,
17
+ setToolCallInputSchemaInput,
18
+ type DynamicToolCall,
19
+ type TypedToolCall,
20
+ } from './tool-call';
16
21
  import type { ToolCallRepairFunction } from './tool-call-repair-function';
17
22
  import type { ToolInputRefinement } from './tool-input-refinement';
18
23
 
@@ -167,7 +172,7 @@ async function waitForPromiseWithAbortSignal<T>({
167
172
  });
168
173
  }
169
174
 
170
- async function refineParsedToolCallInput<TOOLS extends ToolSet>({
175
+ export async function refineParsedToolCallInput<TOOLS extends ToolSet>({
171
176
  toolCall,
172
177
  refineToolInput,
173
178
  }: {
@@ -180,10 +185,15 @@ async function refineParsedToolCallInput<TOOLS extends ToolSet>({
180
185
  return toolCall;
181
186
  }
182
187
 
183
- return {
188
+ const refinedToolCall = {
184
189
  ...toolCall,
185
190
  input: await refine(toolCall.input as InferToolInput<TOOLS[keyof TOOLS]>),
186
191
  } as TypedToolCall<TOOLS>;
192
+
193
+ const inputSchemaInput = getToolCallInputSchemaInput(toolCall);
194
+ return inputSchemaInput == null
195
+ ? refinedToolCall
196
+ : setToolCallInputSchemaInput(refinedToolCall, inputSchemaInput.value);
187
197
  }
188
198
 
189
199
  async function parseProviderExecutedDynamicToolCall(
@@ -253,26 +263,29 @@ async function doParseToolCall<TOOLS extends ToolSet>({
253
263
  });
254
264
  }
255
265
 
256
- return tool.type === 'dynamic'
257
- ? {
258
- type: 'tool-call',
259
- toolCallId: toolCall.toolCallId,
260
- toolName: toolCall.toolName,
261
- input: parseResult.value,
262
- providerExecuted: toolCall.providerExecuted,
263
- providerMetadata: toolCall.providerMetadata,
264
- ...(tool.metadata != null ? { toolMetadata: tool.metadata } : {}),
265
- dynamic: true,
266
- title: tool.title,
267
- }
268
- : {
269
- type: 'tool-call',
270
- toolCallId: toolCall.toolCallId,
271
- toolName,
272
- input: parseResult.value,
273
- providerExecuted: toolCall.providerExecuted,
274
- providerMetadata: toolCall.providerMetadata,
275
- ...(tool.metadata != null ? { toolMetadata: tool.metadata } : {}),
276
- title: tool.title,
277
- };
266
+ return setToolCallInputSchemaInput(
267
+ tool.type === 'dynamic'
268
+ ? {
269
+ type: 'tool-call',
270
+ toolCallId: toolCall.toolCallId,
271
+ toolName: toolCall.toolName,
272
+ input: parseResult.value,
273
+ providerExecuted: toolCall.providerExecuted,
274
+ providerMetadata: toolCall.providerMetadata,
275
+ ...(tool.metadata != null ? { toolMetadata: tool.metadata } : {}),
276
+ dynamic: true,
277
+ title: tool.title,
278
+ }
279
+ : {
280
+ type: 'tool-call',
281
+ toolCallId: toolCall.toolCallId,
282
+ toolName,
283
+ input: parseResult.value,
284
+ providerExecuted: toolCall.providerExecuted,
285
+ providerMetadata: toolCall.providerMetadata,
286
+ ...(tool.metadata != null ? { toolMetadata: tool.metadata } : {}),
287
+ title: tool.title,
288
+ },
289
+ parseResult.rawValue,
290
+ );
278
291
  }
@@ -2046,6 +2046,7 @@ class DefaultStreamTextResult<
2046
2046
  toolsContext,
2047
2047
  runtimeContext,
2048
2048
  toolApprovalSecret: experimental_toolApprovalSecret,
2049
+ refineToolInput,
2049
2050
  });
2050
2051
 
2051
2052
  const localDeniedToolApprovals = [
@@ -6,8 +6,10 @@ import type {
6
6
  } from '../prompt';
7
7
  import { createToolModelOutput } from '../prompt/create-tool-model-output';
8
8
  import { getOwn } from '../util/get-own';
9
+ import { isDeepEqualData } from '../util/is-deep-equal-data';
9
10
  import type { ContentPart } from './content-part';
10
11
  import type { ToolSet } from '@ai-sdk/provider-utils';
12
+ import { getToolCallInputSchemaInput } from './tool-call';
11
13
 
12
14
  /**
13
15
  * Converts the result of a `generateText` or `streamText` call to a list of response messages.
@@ -129,6 +131,7 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
129
131
  break;
130
132
  }
131
133
  case 'tool-approval-request':
134
+ const inputSchemaInput = getToolCallInputSchemaInput(part.toolCall);
132
135
  content.push({
133
136
  type: 'tool-approval-request',
134
137
  approvalId: part.approvalId,
@@ -136,6 +139,10 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
136
139
  ...(part.reason != null ? { reason: part.reason } : {}),
137
140
  isAutomatic: part.isAutomatic,
138
141
  ...(part.signature != null ? { signature: part.signature } : {}),
142
+ ...(inputSchemaInput != null &&
143
+ !isDeepEqualData(inputSchemaInput.value, part.toolCall.input)
144
+ ? { inputSchemaInput: inputSchemaInput.value }
145
+ : {}),
139
146
  });
140
147
  break;
141
148
  }
@@ -3,6 +3,8 @@ import type { InferToolInput, ToolSet } from '@ai-sdk/provider-utils';
3
3
  import type { ProviderMetadata } from '../types';
4
4
  import type { ValueOf } from '../util/value-of';
5
5
 
6
+ const inputSchemaInputSymbol = Symbol('ai-sdk-tool-call-input-schema-input');
7
+
6
8
  type BaseToolCall = {
7
9
  type: 'tool-call';
8
10
  toolCallId: string;
@@ -58,3 +60,27 @@ export type DynamicToolCall = BaseToolCall & {
58
60
  export type TypedToolCall<TOOLS extends ToolSet> =
59
61
  | StaticToolCall<TOOLS>
60
62
  | DynamicToolCall;
63
+
64
+ export function setToolCallInputSchemaInput<TOOLS extends ToolSet>(
65
+ toolCall: TypedToolCall<TOOLS>,
66
+ inputSchemaInput: unknown,
67
+ ): TypedToolCall<TOOLS> {
68
+ Object.defineProperty(toolCall, inputSchemaInputSymbol, {
69
+ value: inputSchemaInput,
70
+ });
71
+ return toolCall;
72
+ }
73
+
74
+ export function getToolCallInputSchemaInput<TOOLS extends ToolSet>(
75
+ toolCall: TypedToolCall<TOOLS>,
76
+ ): { value: unknown } | undefined {
77
+ return inputSchemaInputSymbol in toolCall
78
+ ? {
79
+ value: (
80
+ toolCall as TypedToolCall<TOOLS> & {
81
+ [inputSchemaInputSymbol]: unknown;
82
+ }
83
+ )[inputSchemaInputSymbol],
84
+ }
85
+ : undefined;
86
+ }
@@ -10,10 +10,13 @@ import {
10
10
  import { InvalidToolApprovalSignatureError } from '../error/invalid-tool-approval-signature-error';
11
11
  import { InvalidToolInputError } from '../error/invalid-tool-input-error';
12
12
  import { getOwn } from '../util/get-own';
13
+ import { isDeepEqualData } from '../util/is-deep-equal-data';
13
14
  import type { CollectedToolApprovals } from './collect-tool-approvals';
15
+ import { refineParsedToolCallInput } from './parse-tool-call';
14
16
  import { resolveToolApproval } from './resolve-tool-approval';
15
17
  import { verifyToolApprovalSignature } from './tool-approval-signature';
16
18
  import type { ToolApprovalConfiguration } from './tool-approval-configuration';
19
+ import type { ToolInputRefinement } from './tool-input-refinement';
17
20
 
18
21
  /**
19
22
  * Re-validates approved tool approvals reconstructed from client-supplied
@@ -31,6 +34,7 @@ export async function validateApprovedToolApprovals<
31
34
  toolsContext,
32
35
  runtimeContext,
33
36
  toolApprovalSecret,
37
+ refineToolInput,
34
38
  }: {
35
39
  approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
36
40
  tools: TOOLS | undefined;
@@ -39,6 +43,7 @@ export async function validateApprovedToolApprovals<
39
43
  toolsContext: InferToolSetContext<TOOLS>;
40
44
  runtimeContext: RUNTIME_CONTEXT;
41
45
  toolApprovalSecret?: string | Uint8Array;
46
+ refineToolInput?: ToolInputRefinement<TOOLS>;
42
47
  }): Promise<{
43
48
  approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
44
49
  deniedToolApprovals: Array<CollectedToolApprovals<TOOLS>>;
@@ -53,7 +58,7 @@ export async function validateApprovedToolApprovals<
53
58
  > = [];
54
59
 
55
60
  for (const approval of approvedToolApprovals) {
56
- const { toolCall, approvalRequest } = approval;
61
+ const { approvalRequest, toolCall } = approval;
57
62
  // Look up the tool by own property only: `toolName` comes from
58
63
  // client-supplied history, so a name matching an inherited object property
59
64
  // (e.g. `constructor`, `toString`) must resolve to "no such tool" rather
@@ -88,18 +93,49 @@ export async function validateApprovedToolApprovals<
88
93
  }
89
94
 
90
95
  if (isExecutableTool(tool) && tool.inputSchema != null) {
96
+ const hasInputSchemaInput = Object.prototype.hasOwnProperty.call(
97
+ approvalRequest,
98
+ 'inputSchemaInput',
99
+ );
91
100
  const validation = await safeValidateTypes({
92
- value: toolCall.input,
101
+ value: hasInputSchemaInput
102
+ ? approvalRequest.inputSchemaInput
103
+ : toolCall.input,
93
104
  schema: asSchema(tool.inputSchema),
94
105
  });
95
106
 
107
+ let validationError: unknown;
96
108
  if (!validation.success) {
109
+ validationError = validation.error;
110
+ } else {
111
+ try {
112
+ const revalidatedToolCall = await refineParsedToolCallInput({
113
+ toolCall: {
114
+ ...toolCall,
115
+ input: validation.value,
116
+ },
117
+ refineToolInput,
118
+ });
119
+
120
+ // Revalidation must never change the operation that was approved,
121
+ // including when older or projected history omits the schema input.
122
+ if (!isDeepEqualData(revalidatedToolCall.input, toolCall.input)) {
123
+ validationError = new Error(
124
+ 'Approved tool input does not match the validated schema output.',
125
+ );
126
+ }
127
+ } catch (error) {
128
+ validationError = error;
129
+ }
130
+ }
131
+
132
+ if (validationError != null) {
97
133
  invalid.push({
98
134
  ...approval,
99
135
  error: new InvalidToolInputError({
100
136
  toolName: toolCall.toolName,
101
137
  toolInput: JSON.stringify(toolCall.input),
102
- cause: validation.error,
138
+ cause: validationError,
103
139
  }),
104
140
  });
105
141
  continue;
@@ -890,8 +890,6 @@ function normalizeImageData(
890
890
  data: bytes,
891
891
  };
892
892
  }
893
-
894
- return undefined;
895
893
  }
896
894
 
897
895
  /**
@@ -24,7 +24,7 @@ export function extractReasoningMiddleware({
24
24
  startWithReasoning?: boolean;
25
25
  }): LanguageModelMiddleware {
26
26
  const openingTag = `<${tagName}>`;
27
- const closingTag = `<\/${tagName}>`;
27
+ const closingTag = `</${tagName}>`;
28
28
 
29
29
  return {
30
30
  specificationVersion: 'v4',
@@ -7,12 +7,17 @@ import type {
7
7
  import {
8
8
  asArray,
9
9
  EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL,
10
+ EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER,
10
11
  } from '@ai-sdk/provider-utils';
11
12
  import { asEmbeddingModelV4 } from '../model/as-embedding-model-v4';
12
13
  import {
13
14
  getEmbeddingModelMaxInputBytesPerCall,
14
15
  type EmbeddingModelWithMaxInputBytesPerCall,
15
16
  } from '../model/get-embedding-model-max-input-bytes-per-call';
17
+ import {
18
+ getEmbeddingModelProviderOptionsTransformer,
19
+ type EmbeddingModelWithProviderOptionsTransformer,
20
+ } from '../model/get-embedding-model-provider-options-transformer';
16
21
  import type { EmbeddingModelMiddleware } from '../types';
17
22
 
18
23
  /**
@@ -63,7 +68,8 @@ const doWrap = ({
63
68
  middleware: EmbeddingModelMiddleware;
64
69
  modelId?: string;
65
70
  providerId?: string;
66
- }): EmbeddingModelWithMaxInputBytesPerCall => {
71
+ }): EmbeddingModelWithMaxInputBytesPerCall &
72
+ EmbeddingModelWithProviderOptionsTransformer => {
67
73
  async function doTransform({
68
74
  params,
69
75
  }: {
@@ -80,6 +86,8 @@ const doWrap = ({
80
86
  overrideMaxEmbeddingsPerCall?.({ model }) ?? model.maxEmbeddingsPerCall,
81
87
  [EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL]:
82
88
  getEmbeddingModelMaxInputBytesPerCall(model),
89
+ [EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER]:
90
+ getEmbeddingModelProviderOptionsTransformer(model),
83
91
  supportsParallelCalls:
84
92
  overrideSupportsParallelCalls?.({ model }) ?? model.supportsParallelCalls,
85
93
  async doEmbed(
@@ -0,0 +1,17 @@
1
+ import type { EmbeddingModelV4 } from '@ai-sdk/provider';
2
+ import {
3
+ EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER,
4
+ type EmbeddingModelProviderOptionsTransformer,
5
+ } from '@ai-sdk/provider-utils';
6
+
7
+ export type EmbeddingModelWithProviderOptionsTransformer = EmbeddingModelV4 & {
8
+ readonly [EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER]?: EmbeddingModelProviderOptionsTransformer;
9
+ };
10
+
11
+ export function getEmbeddingModelProviderOptionsTransformer(
12
+ model: EmbeddingModelV4,
13
+ ) {
14
+ return (model as EmbeddingModelWithProviderOptionsTransformer)[
15
+ EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER
16
+ ];
17
+ }
@@ -284,6 +284,9 @@ export const toolApprovalRequestSchema: ZodType<ToolApprovalRequest> = z.object(
284
284
  approvalId: z.string(),
285
285
  toolCallId: z.string(),
286
286
  reason: z.string().optional(),
287
+ isAutomatic: z.boolean().optional(),
288
+ signature: z.string().optional(),
289
+ inputSchemaInput: z.unknown().optional(),
287
290
  },
288
291
  );
289
292