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.
- package/CHANGELOG.md +26 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +315 -213
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +2 -1
- package/dist/internal/index.js +105 -28
- package/dist/internal/index.js.map +1 -1
- package/docs/03-agents/06-tool-approvals.mdx +16 -0
- package/docs/03-ai-sdk-core/37-speech.mdx +2 -0
- package/docs/07-reference/01-ai-sdk-core/12-generate-speech.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx +18 -0
- package/package.json +11 -11
- package/src/embed/embed-many.ts +18 -2
- package/src/generate-speech/generate-speech.ts +15 -4
- package/src/generate-speech/generated-audio-file.ts +0 -8
- package/src/generate-text/execute-tools-from-stream.ts +0 -2
- package/src/generate-text/generate-text.ts +1 -0
- package/src/generate-text/generated-file.ts +0 -8
- package/src/generate-text/invoke-tool-callbacks-from-stream.ts +9 -8
- package/src/generate-text/output.ts +0 -2
- package/src/generate-text/parse-tool-call.ts +38 -25
- package/src/generate-text/stream-text.ts +1 -0
- package/src/generate-text/to-response-messages.ts +7 -0
- package/src/generate-text/tool-call.ts +26 -0
- package/src/generate-text/validate-tool-approvals.ts +39 -3
- package/src/generate-video/generate-video.ts +0 -2
- package/src/middleware/extract-reasoning-middleware.ts +1 -1
- package/src/middleware/wrap-embedding-model.ts +9 -1
- package/src/model/get-embedding-model-provider-options-transformer.ts +17 -0
- package/src/prompt/content-part.ts +3 -0
- package/src/registry/custom-provider.ts +12 -5
- package/src/ui/chat.ts +108 -17
- package/src/ui/convert-to-model-messages.ts +8 -0
- package/src/ui/direct-chat-transport.ts +2 -0
- package/src/ui/process-ui-message-stream.ts +6 -0
- package/src/ui/ui-messages.ts +10 -0
- package/src/ui/validate-ui-messages.ts +98 -132
- package/src/ui-message-stream/to-ui-message-chunk.ts +7 -0
- package/src/ui-message-stream/ui-message-chunks.ts +2 -0
- 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
|
|
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.
|
|
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.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.91",
|
|
46
46
|
"@ai-sdk/provider": "4.0.18",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.47"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
53
|
-
"@ai-sdk/groq": "4.0.
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
55
|
-
"@ai-sdk/moonshotai": "3.0.
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
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.
|
|
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",
|
package/src/embed/embed-many.ts
CHANGED
|
@@ -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
|
-
...
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
|
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.
|
|
64
|
+
ongoingToolCall.validatedContexts[toolName] = newValidatedContext;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
return
|
|
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
|
-
|
|
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 =
|
|
124
|
+
const toolName = chunk.toolName;
|
|
124
125
|
const tool = getOwn(tools, toolName);
|
|
125
126
|
|
|
126
127
|
if (!chunk.invalid && tool?.onInputAvailable != null) {
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
}
|
|
@@ -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 {
|
|
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:
|
|
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:
|
|
138
|
+
cause: validationError,
|
|
103
139
|
}),
|
|
104
140
|
});
|
|
105
141
|
continue;
|
|
@@ -24,7 +24,7 @@ export function extractReasoningMiddleware({
|
|
|
24
24
|
startWithReasoning?: boolean;
|
|
25
25
|
}): LanguageModelMiddleware {
|
|
26
26
|
const openingTag = `<${tagName}>`;
|
|
27
|
-
const closingTag =
|
|
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
|
|