ai 6.0.288 → 6.0.290
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 +35 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +377 -281
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -213
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +20 -5
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +20 -5
- package/dist/internal/index.mjs.map +1 -1
- package/docs/03-ai-sdk-core/16-mcp-tools.mdx +57 -0
- package/docs/03-ai-sdk-core/40-middleware.mdx +92 -7
- package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +7 -0
- package/package.json +5 -4
- package/src/embed/embed-many.ts +19 -3
- package/src/generate-speech/generated-audio-file.ts +0 -8
- package/src/generate-text/generated-file.ts +0 -4
- package/src/generate-text/parse-tool-call.ts +30 -23
- package/src/generate-text/run-tools-transformation.ts +37 -3
- package/src/generate-text/stream-text.ts +9 -28
- 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 +20 -2
- package/src/middleware/extract-reasoning-middleware.ts +1 -1
- package/src/middleware/wrap-embedding-model.ts +12 -2
- package/src/model/get-embedding-model-provider-options-transformer.ts +17 -0
- package/src/prompt/content-part.ts +2 -0
- package/src/prompt/convert-to-language-model-prompt.ts +7 -2
- package/src/prompt/data-content.ts +11 -2
- package/src/ui/chat.ts +108 -17
- package/src/ui/convert-to-model-messages.ts +8 -0
- package/src/ui/http-chat-transport.ts +1 -1
- package/src/ui/last-assistant-message-is-complete-with-approval-responses.ts +1 -1
- package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +2 -1
- 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 +79 -114
- package/src/ui-message-stream/ui-message-chunks.ts +2 -0
- package/src/util/write-to-server-response.ts +0 -2
|
@@ -282,6 +282,9 @@ You can implement any of the following three function to modify the behavior of
|
|
|
282
282
|
3. `wrapStream`: Wraps the `doStream` method of the [language model](https://github.com/vercel/ai/blob/release-v6.0/packages/provider/src/language-model/v3/language-model-v3.ts).
|
|
283
283
|
You can modify the parameters, call the language model, and modify the result.
|
|
284
284
|
|
|
285
|
+
Every `LanguageModelV3Middleware` object must set
|
|
286
|
+
`specificationVersion: 'v3'`.
|
|
287
|
+
|
|
285
288
|
Here are some examples of how to implement language model middleware:
|
|
286
289
|
|
|
287
290
|
## Examples
|
|
@@ -302,6 +305,8 @@ import type {
|
|
|
302
305
|
} from '@ai-sdk/provider';
|
|
303
306
|
|
|
304
307
|
export const yourLogMiddleware: LanguageModelV3Middleware = {
|
|
308
|
+
specificationVersion: 'v3',
|
|
309
|
+
|
|
305
310
|
wrapGenerate: async ({ doGenerate, params }) => {
|
|
306
311
|
console.log('doGenerate called');
|
|
307
312
|
console.log(`params: ${JSON.stringify(params, null, 2)}`);
|
|
@@ -379,6 +384,8 @@ import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
|
379
384
|
const cache = new Map<string, any>();
|
|
380
385
|
|
|
381
386
|
export const yourCacheMiddleware: LanguageModelV3Middleware = {
|
|
387
|
+
specificationVersion: 'v3',
|
|
388
|
+
|
|
382
389
|
wrapGenerate: async ({ doGenerate, params }) => {
|
|
383
390
|
const cacheKey = JSON.stringify(params);
|
|
384
391
|
|
|
@@ -411,6 +418,8 @@ This example shows how to use RAG as middleware.
|
|
|
411
418
|
import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
412
419
|
|
|
413
420
|
export const yourRagMiddleware: LanguageModelV3Middleware = {
|
|
421
|
+
specificationVersion: 'v3',
|
|
422
|
+
|
|
414
423
|
transformParams: async ({ params }) => {
|
|
415
424
|
const lastUserMessageText = getLastUserMessageText({
|
|
416
425
|
prompt: params.prompt,
|
|
@@ -437,28 +446,102 @@ Guard rails are a way to ensure that the generated text of a language model call
|
|
|
437
446
|
is safe and appropriate. This example shows how to use guardrails as middleware.
|
|
438
447
|
|
|
439
448
|
```ts
|
|
440
|
-
import type {
|
|
449
|
+
import type {
|
|
450
|
+
LanguageModelV3Middleware,
|
|
451
|
+
LanguageModelV3StreamPart,
|
|
452
|
+
} from '@ai-sdk/provider';
|
|
453
|
+
|
|
454
|
+
const redactText = (text: string) => text.replace(/badword/g, '<REDACTED>');
|
|
441
455
|
|
|
442
456
|
export const yourGuardrailMiddleware: LanguageModelV3Middleware = {
|
|
457
|
+
specificationVersion: 'v3',
|
|
458
|
+
|
|
443
459
|
wrapGenerate: async ({ doGenerate }) => {
|
|
444
460
|
const result = await doGenerate();
|
|
445
461
|
|
|
446
462
|
// filtering approach, e.g. for PII or other sensitive information:
|
|
447
463
|
const content = result.content.map(part =>
|
|
448
|
-
part.type === 'text'
|
|
449
|
-
? { ...part, text: part.text.replace(/badword/g, '<REDACTED>') }
|
|
450
|
-
: part,
|
|
464
|
+
part.type === 'text' ? { ...part, text: redactText(part.text) } : part,
|
|
451
465
|
);
|
|
452
466
|
|
|
453
467
|
return { ...result, content };
|
|
454
468
|
},
|
|
455
469
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
470
|
+
wrapStream: async ({ doStream }) => {
|
|
471
|
+
const { stream, ...rest } = await doStream();
|
|
472
|
+
|
|
473
|
+
// Keep a separate buffer for each text block in the stream.
|
|
474
|
+
const buffers = new Map<string, string>();
|
|
475
|
+
|
|
476
|
+
const transformStream = new TransformStream<
|
|
477
|
+
LanguageModelV3StreamPart,
|
|
478
|
+
LanguageModelV3StreamPart
|
|
479
|
+
>({
|
|
480
|
+
transform(chunk, controller) {
|
|
481
|
+
if (chunk.type === 'text-start') {
|
|
482
|
+
buffers.set(chunk.id, '');
|
|
483
|
+
controller.enqueue(chunk);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (chunk.type === 'text-delta') {
|
|
488
|
+
buffers.set(chunk.id, (buffers.get(chunk.id) ?? '') + chunk.delta);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (chunk.type === 'text-end') {
|
|
493
|
+
const bufferedText = buffers.get(chunk.id);
|
|
494
|
+
|
|
495
|
+
if (bufferedText != null) {
|
|
496
|
+
const redactedText = redactText(bufferedText);
|
|
497
|
+
|
|
498
|
+
if (redactedText) {
|
|
499
|
+
controller.enqueue({
|
|
500
|
+
type: 'text-delta',
|
|
501
|
+
id: chunk.id,
|
|
502
|
+
delta: redactedText,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
buffers.delete(chunk.id);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
controller.enqueue(chunk);
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
flush(controller) {
|
|
514
|
+
for (const [id, bufferedText] of buffers) {
|
|
515
|
+
const redactedText = redactText(bufferedText);
|
|
516
|
+
|
|
517
|
+
if (redactedText) {
|
|
518
|
+
controller.enqueue({
|
|
519
|
+
type: 'text-delta',
|
|
520
|
+
id,
|
|
521
|
+
delta: redactedText,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
stream: stream.pipeThrough(transformStream),
|
|
530
|
+
...rest,
|
|
531
|
+
};
|
|
532
|
+
},
|
|
459
533
|
};
|
|
460
534
|
```
|
|
461
535
|
|
|
536
|
+
<Note>
|
|
537
|
+
The streaming example buffers each text block until `text-end` so matches
|
|
538
|
+
split across `text-delta` chunks cannot leak through. This delays output and
|
|
539
|
+
uses memory proportional to the text block size. Do not redact each delta
|
|
540
|
+
independently. An incremental implementation must retain every possible
|
|
541
|
+
incomplete match, and a fixed-size buffer alone is not safe for unbounded
|
|
542
|
+
variable-length patterns.
|
|
543
|
+
</Note>
|
|
544
|
+
|
|
462
545
|
## Configuring Per Request Custom Metadata
|
|
463
546
|
|
|
464
547
|
To send and access custom metadata in Middleware, you can use `providerOptions`. This is useful when building logging middleware where you want to pass additional context like user IDs, timestamps, or other contextual data that can help with tracking and debugging.
|
|
@@ -469,6 +552,8 @@ __PROVIDER_IMPORT__;
|
|
|
469
552
|
import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
470
553
|
|
|
471
554
|
export const yourLogMiddleware: LanguageModelV3Middleware = {
|
|
555
|
+
specificationVersion: 'v3',
|
|
556
|
+
|
|
472
557
|
wrapGenerate: async ({ doGenerate, params }) => {
|
|
473
558
|
console.log('METADATA', params?.providerMetadata?.yourLogMiddleware);
|
|
474
559
|
const result = await doGenerate();
|
|
@@ -487,6 +487,13 @@ the `approval-responded` state after `addToolApprovalResponse` is called. This
|
|
|
487
487
|
lets clients render or persist server-computed approval metadata without using
|
|
488
488
|
it to determine whether the tool was approved.
|
|
489
489
|
|
|
490
|
+
When a tool input schema transforms its input, approval requests also preserve
|
|
491
|
+
the original value as `part.approval.inputSchemaInput`. Keep this field when
|
|
492
|
+
persisting UI messages or model response messages so the SDK can reconstruct
|
|
493
|
+
and verify the approved input before execution. The original value can include
|
|
494
|
+
fields removed by the transform, so approval metadata should not be treated as
|
|
495
|
+
redacted input.
|
|
496
|
+
|
|
490
497
|
### Auto-Submit After Approval
|
|
491
498
|
|
|
492
499
|
<Note>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.290",
|
|
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,
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@opentelemetry/api": "^1.9.0",
|
|
48
|
-
"@ai-sdk/gateway": "3.0.
|
|
49
|
-
"@ai-sdk/provider": "3.0.
|
|
50
|
-
"@ai-sdk/provider-utils": "4.0.
|
|
48
|
+
"@ai-sdk/gateway": "3.0.200",
|
|
49
|
+
"@ai-sdk/provider": "3.0.17",
|
|
50
|
+
"@ai-sdk/provider-utils": "4.0.54"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"tsx": "^4.19.2",
|
|
59
59
|
"typescript": "5.8.3",
|
|
60
60
|
"zod": "3.25.76",
|
|
61
|
+
"@ai-sdk/google": "3.0.127",
|
|
61
62
|
"@ai-sdk/test-server": "1.0.6",
|
|
62
63
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
63
64
|
},
|
package/src/embed/embed-many.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from '@ai-sdk/provider-utils';
|
|
5
5
|
import { logWarnings } from '../logger/log-warnings';
|
|
6
6
|
import { getEmbeddingModelMaxInputBytesPerCall } from '../model/get-embedding-model-max-input-bytes-per-call';
|
|
7
|
+
import { getEmbeddingModelProviderOptionsTransformer } from '../model/get-embedding-model-provider-options-transformer';
|
|
7
8
|
import { resolveEmbeddingModel } from '../model/resolve-model';
|
|
8
9
|
import { assembleOperationName } from '../telemetry/assemble-operation-name';
|
|
9
10
|
import { getBaseTelemetryAttributes } from '../telemetry/get-base-telemetry-attributes';
|
|
@@ -250,6 +251,8 @@ export async function embedMany({
|
|
|
250
251
|
? maxInputBytesPerCall
|
|
251
252
|
: Infinity,
|
|
252
253
|
});
|
|
254
|
+
const providerOptionsTransformer =
|
|
255
|
+
getEmbeddingModelProviderOptionsTransformer(model);
|
|
253
256
|
|
|
254
257
|
// serially embed the chunks:
|
|
255
258
|
const embeddings: Array<Embedding> = [];
|
|
@@ -269,9 +272,22 @@ export async function embedMany({
|
|
|
269
272
|
supportsParallelCalls ? maxParallelCalls : 1,
|
|
270
273
|
);
|
|
271
274
|
|
|
275
|
+
let nextChunkStartIndex = 0;
|
|
272
276
|
for (const parallelChunk of parallelChunks) {
|
|
273
277
|
const results = await Promise.all(
|
|
274
|
-
parallelChunk.map(chunk => {
|
|
278
|
+
parallelChunk.map(async chunk => {
|
|
279
|
+
// Capture the range before awaiting transformations or retrying.
|
|
280
|
+
const startIndex = nextChunkStartIndex;
|
|
281
|
+
nextChunkStartIndex += chunk.length;
|
|
282
|
+
const chunkProviderOptions = providerOptionsTransformer
|
|
283
|
+
? await providerOptionsTransformer({
|
|
284
|
+
providerOptions,
|
|
285
|
+
values,
|
|
286
|
+
startIndex,
|
|
287
|
+
endIndex: startIndex + chunk.length,
|
|
288
|
+
})
|
|
289
|
+
: providerOptions;
|
|
290
|
+
|
|
275
291
|
return retry(() => {
|
|
276
292
|
// nested spans to align with the embedMany telemetry data:
|
|
277
293
|
return recordSpan({
|
|
@@ -296,7 +312,7 @@ export async function embedMany({
|
|
|
296
312
|
values: chunk,
|
|
297
313
|
abortSignal,
|
|
298
314
|
headers: headersWithUserAgent,
|
|
299
|
-
providerOptions,
|
|
315
|
+
providerOptions: chunkProviderOptions,
|
|
300
316
|
});
|
|
301
317
|
|
|
302
318
|
const embeddings = modelResponse.embeddings;
|
|
@@ -343,7 +359,7 @@ export async function embedMany({
|
|
|
343
359
|
result.providerMetadata,
|
|
344
360
|
)) {
|
|
345
361
|
providerMetadata[providerName] = {
|
|
346
|
-
...
|
|
362
|
+
...providerMetadata[providerName],
|
|
347
363
|
...metadata,
|
|
348
364
|
};
|
|
349
365
|
}
|
|
@@ -54,12 +54,4 @@ export class DefaultGeneratedAudioFile
|
|
|
54
54
|
|
|
55
55
|
export class DefaultGeneratedAudioFileWithType extends DefaultGeneratedAudioFile {
|
|
56
56
|
readonly type = 'audio';
|
|
57
|
-
|
|
58
|
-
constructor(options: {
|
|
59
|
-
data: string | Uint8Array;
|
|
60
|
-
mediaType: string;
|
|
61
|
-
format: string;
|
|
62
|
-
}) {
|
|
63
|
-
super(options);
|
|
64
|
-
}
|
|
65
57
|
}
|
|
@@ -63,8 +63,4 @@ export class DefaultGeneratedFile implements GeneratedFile {
|
|
|
63
63
|
|
|
64
64
|
export class DefaultGeneratedFileWithType extends DefaultGeneratedFile {
|
|
65
65
|
readonly type = 'file';
|
|
66
|
-
|
|
67
|
-
constructor(options: { data: string | Uint8Array; mediaType: string }) {
|
|
68
|
-
super(options);
|
|
69
|
-
}
|
|
70
66
|
}
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
import { InvalidToolInputError } from '../error/invalid-tool-input-error';
|
|
10
10
|
import { NoSuchToolError } from '../error/no-such-tool-error';
|
|
11
11
|
import { ToolCallRepairError } from '../error/tool-call-repair-error';
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
setToolCallInputSchemaInput,
|
|
14
|
+
type DynamicToolCall,
|
|
15
|
+
type TypedToolCall,
|
|
16
|
+
} from './tool-call';
|
|
13
17
|
import type { ToolCallRepairFunction } from './tool-call-repair-function';
|
|
14
18
|
import type { ToolSet } from './tool-set';
|
|
15
19
|
|
|
@@ -167,26 +171,29 @@ async function doParseToolCall<TOOLS extends ToolSet>({
|
|
|
167
171
|
});
|
|
168
172
|
}
|
|
169
173
|
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
174
|
+
return setToolCallInputSchemaInput(
|
|
175
|
+
tool.type === 'dynamic'
|
|
176
|
+
? {
|
|
177
|
+
type: 'tool-call',
|
|
178
|
+
toolCallId: toolCall.toolCallId,
|
|
179
|
+
toolName: toolCall.toolName,
|
|
180
|
+
input: parseResult.value,
|
|
181
|
+
providerExecuted: toolCall.providerExecuted,
|
|
182
|
+
providerMetadata: toolCall.providerMetadata,
|
|
183
|
+
...(tool.metadata != null ? { toolMetadata: tool.metadata } : {}),
|
|
184
|
+
dynamic: true,
|
|
185
|
+
title: tool.title,
|
|
186
|
+
}
|
|
187
|
+
: {
|
|
188
|
+
type: 'tool-call',
|
|
189
|
+
toolCallId: toolCall.toolCallId,
|
|
190
|
+
toolName,
|
|
191
|
+
input: parseResult.value,
|
|
192
|
+
providerExecuted: toolCall.providerExecuted,
|
|
193
|
+
providerMetadata: toolCall.providerMetadata,
|
|
194
|
+
...(tool.metadata != null ? { toolMetadata: tool.metadata } : {}),
|
|
195
|
+
title: tool.title,
|
|
196
|
+
},
|
|
197
|
+
parseResult.rawValue,
|
|
198
|
+
);
|
|
192
199
|
}
|
|
@@ -261,6 +261,10 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
|
|
|
261
261
|
});
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// Keep input callbacks in the same transform so input availability cannot
|
|
265
|
+
// overtake pending start or delta callbacks in a downstream stream.
|
|
266
|
+
const activeToolCallToolNames = new Map<string, string>();
|
|
267
|
+
|
|
264
268
|
// forward stream
|
|
265
269
|
const forwardStream = new TransformStream<
|
|
266
270
|
LanguageModelV3StreamPart,
|
|
@@ -283,9 +287,6 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
|
|
|
283
287
|
case 'reasoning-start':
|
|
284
288
|
case 'reasoning-delta':
|
|
285
289
|
case 'reasoning-end':
|
|
286
|
-
case 'tool-input-start':
|
|
287
|
-
case 'tool-input-delta':
|
|
288
|
-
case 'tool-input-end':
|
|
289
290
|
case 'source':
|
|
290
291
|
case 'response-metadata':
|
|
291
292
|
case 'error':
|
|
@@ -294,6 +295,39 @@ export function runToolsTransformation<TOOLS extends ToolSet>({
|
|
|
294
295
|
break;
|
|
295
296
|
}
|
|
296
297
|
|
|
298
|
+
case 'tool-input-start': {
|
|
299
|
+
activeToolCallToolNames.set(chunk.id, chunk.toolName);
|
|
300
|
+
await tools?.[chunk.toolName]?.onInputStart?.({
|
|
301
|
+
toolCallId: chunk.id,
|
|
302
|
+
messages,
|
|
303
|
+
abortSignal,
|
|
304
|
+
experimental_context,
|
|
305
|
+
});
|
|
306
|
+
controller.enqueue(chunk);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
case 'tool-input-delta': {
|
|
311
|
+
const toolName = activeToolCallToolNames.get(chunk.id);
|
|
312
|
+
if (toolName != null) {
|
|
313
|
+
await tools?.[toolName]?.onInputDelta?.({
|
|
314
|
+
inputTextDelta: chunk.delta,
|
|
315
|
+
toolCallId: chunk.id,
|
|
316
|
+
messages,
|
|
317
|
+
abortSignal,
|
|
318
|
+
experimental_context,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
controller.enqueue(chunk);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
case 'tool-input-end': {
|
|
326
|
+
activeToolCallToolNames.delete(chunk.id);
|
|
327
|
+
controller.enqueue(chunk);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
|
|
297
331
|
case 'file': {
|
|
298
332
|
controller.enqueue({
|
|
299
333
|
type: 'file',
|
|
@@ -79,6 +79,7 @@ import { consumeStream } from '../util/consume-stream';
|
|
|
79
79
|
import { createIdMap } from '../util/create-id-map';
|
|
80
80
|
import { createStitchableStream } from '../util/create-stitchable-stream';
|
|
81
81
|
import type { DownloadFunction } from '../util/download/download-function';
|
|
82
|
+
import { isDeepEqualData } from '../util/is-deep-equal-data';
|
|
82
83
|
import { mergeAbortSignals } from '../util/merge-abort-signals';
|
|
83
84
|
import { mergeObjects } from '../util/merge-objects';
|
|
84
85
|
import { now as originalNow } from '../util/now';
|
|
@@ -123,7 +124,7 @@ import type {
|
|
|
123
124
|
UIMessageStreamOptions,
|
|
124
125
|
} from './stream-text-result';
|
|
125
126
|
import { toResponseMessages } from './to-response-messages';
|
|
126
|
-
import type
|
|
127
|
+
import { getToolCallInputSchemaInput, type TypedToolCall } from './tool-call';
|
|
127
128
|
import type { ToolCallRepairFunction } from './tool-call-repair-function';
|
|
128
129
|
import type { ToolOutput } from './tool-output';
|
|
129
130
|
import type { StaticToolOutputDenied } from './tool-output-denied';
|
|
@@ -1965,8 +1966,6 @@ class DefaultStreamTextResult<
|
|
|
1965
1966
|
const stepToolOutputs: ToolOutput<TOOLS>[] = [];
|
|
1966
1967
|
let warnings: SharedV3Warning[] | undefined;
|
|
1967
1968
|
|
|
1968
|
-
const activeToolCallToolNames: Record<string, string> = {};
|
|
1969
|
-
|
|
1970
1969
|
let stepFinishReason: FinishReason = 'other';
|
|
1971
1970
|
let stepRawFinishReason: string | undefined = undefined;
|
|
1972
1971
|
|
|
@@ -2168,18 +2167,7 @@ class DefaultStreamTextResult<
|
|
|
2168
2167
|
}
|
|
2169
2168
|
|
|
2170
2169
|
case 'tool-input-start': {
|
|
2171
|
-
activeToolCallToolNames[chunk.id] = chunk.toolName;
|
|
2172
|
-
|
|
2173
2170
|
const tool = stepToolSet?.[chunk.toolName];
|
|
2174
|
-
if (tool?.onInputStart != null) {
|
|
2175
|
-
await tool.onInputStart({
|
|
2176
|
-
toolCallId: chunk.id,
|
|
2177
|
-
messages: stepInputMessages,
|
|
2178
|
-
abortSignal,
|
|
2179
|
-
experimental_context,
|
|
2180
|
-
});
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
2171
|
controller.enqueue({
|
|
2184
2172
|
...chunk,
|
|
2185
2173
|
dynamic: chunk.dynamic ?? tool?.type === 'dynamic',
|
|
@@ -2189,25 +2177,11 @@ class DefaultStreamTextResult<
|
|
|
2189
2177
|
}
|
|
2190
2178
|
|
|
2191
2179
|
case 'tool-input-end': {
|
|
2192
|
-
delete activeToolCallToolNames[chunk.id];
|
|
2193
2180
|
controller.enqueue(chunk);
|
|
2194
2181
|
break;
|
|
2195
2182
|
}
|
|
2196
2183
|
|
|
2197
2184
|
case 'tool-input-delta': {
|
|
2198
|
-
const toolName = activeToolCallToolNames[chunk.id];
|
|
2199
|
-
const tool = stepToolSet?.[toolName];
|
|
2200
|
-
|
|
2201
|
-
if (tool?.onInputDelta != null) {
|
|
2202
|
-
await tool.onInputDelta({
|
|
2203
|
-
inputTextDelta: chunk.delta,
|
|
2204
|
-
toolCallId: chunk.id,
|
|
2205
|
-
messages: stepInputMessages,
|
|
2206
|
-
abortSignal,
|
|
2207
|
-
experimental_context,
|
|
2208
|
-
});
|
|
2209
|
-
}
|
|
2210
|
-
|
|
2211
2185
|
controller.enqueue(chunk);
|
|
2212
2186
|
break;
|
|
2213
2187
|
}
|
|
@@ -3011,10 +2985,17 @@ class DefaultStreamTextResult<
|
|
|
3011
2985
|
}
|
|
3012
2986
|
|
|
3013
2987
|
case 'tool-approval-request': {
|
|
2988
|
+
const inputSchemaInput = getToolCallInputSchemaInput(
|
|
2989
|
+
part.toolCall,
|
|
2990
|
+
);
|
|
3014
2991
|
controller.enqueue({
|
|
3015
2992
|
type: 'tool-approval-request',
|
|
3016
2993
|
approvalId: part.approvalId,
|
|
3017
2994
|
toolCallId: part.toolCall.toolCallId,
|
|
2995
|
+
...(inputSchemaInput != null &&
|
|
2996
|
+
!isDeepEqualData(inputSchemaInput.value, part.toolCall.input)
|
|
2997
|
+
? { inputSchemaInput: inputSchemaInput.value }
|
|
2998
|
+
: {}),
|
|
3018
2999
|
...(part.signature != null
|
|
3019
3000
|
? { signature: part.signature }
|
|
3020
3001
|
: {}),
|
|
@@ -5,8 +5,10 @@ import type {
|
|
|
5
5
|
ToolModelMessage,
|
|
6
6
|
} from '../prompt';
|
|
7
7
|
import { createToolModelOutput } from '../prompt/create-tool-model-output';
|
|
8
|
+
import { isDeepEqualData } from '../util/is-deep-equal-data';
|
|
8
9
|
import type { ContentPart } from './content-part';
|
|
9
10
|
import type { ToolSet } from './tool-set';
|
|
11
|
+
import { getToolCallInputSchemaInput } from './tool-call';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Converts the result of a `generateText` or `streamText` call to a list of response messages.
|
|
@@ -113,11 +115,16 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
|
|
|
113
115
|
break;
|
|
114
116
|
}
|
|
115
117
|
case 'tool-approval-request':
|
|
118
|
+
const inputSchemaInput = getToolCallInputSchemaInput(part.toolCall);
|
|
116
119
|
content.push({
|
|
117
120
|
type: 'tool-approval-request',
|
|
118
121
|
approvalId: part.approvalId,
|
|
119
122
|
toolCallId: part.toolCall.toolCallId,
|
|
120
123
|
...(part.signature != null ? { signature: part.signature } : {}),
|
|
124
|
+
...(inputSchemaInput != null &&
|
|
125
|
+
!isDeepEqualData(inputSchemaInput.value, part.toolCall.input)
|
|
126
|
+
? { inputSchemaInput: inputSchemaInput.value }
|
|
127
|
+
: {}),
|
|
121
128
|
});
|
|
122
129
|
break;
|
|
123
130
|
}
|
|
@@ -4,6 +4,8 @@ import type { ProviderMetadata } from '../types';
|
|
|
4
4
|
import type { ValueOf } from '../util/value-of';
|
|
5
5
|
import type { ToolSet } from './tool-set';
|
|
6
6
|
|
|
7
|
+
const inputSchemaInputSymbol = Symbol('ai-sdk-tool-call-input-schema-input');
|
|
8
|
+
|
|
7
9
|
type BaseToolCall = {
|
|
8
10
|
type: 'tool-call';
|
|
9
11
|
toolCallId: string;
|
|
@@ -47,3 +49,27 @@ export type DynamicToolCall = BaseToolCall & {
|
|
|
47
49
|
export type TypedToolCall<TOOLS extends ToolSet> =
|
|
48
50
|
| StaticToolCall<TOOLS>
|
|
49
51
|
| DynamicToolCall;
|
|
52
|
+
|
|
53
|
+
export function setToolCallInputSchemaInput<TOOLS extends ToolSet>(
|
|
54
|
+
toolCall: TypedToolCall<TOOLS>,
|
|
55
|
+
inputSchemaInput: unknown,
|
|
56
|
+
): TypedToolCall<TOOLS> {
|
|
57
|
+
Object.defineProperty(toolCall, inputSchemaInputSymbol, {
|
|
58
|
+
value: inputSchemaInput,
|
|
59
|
+
});
|
|
60
|
+
return toolCall;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getToolCallInputSchemaInput<TOOLS extends ToolSet>(
|
|
64
|
+
toolCall: TypedToolCall<TOOLS>,
|
|
65
|
+
): { value: unknown } | undefined {
|
|
66
|
+
return inputSchemaInputSymbol in toolCall
|
|
67
|
+
? {
|
|
68
|
+
value: (
|
|
69
|
+
toolCall as TypedToolCall<TOOLS> & {
|
|
70
|
+
[inputSchemaInputSymbol]: unknown;
|
|
71
|
+
}
|
|
72
|
+
)[inputSchemaInputSymbol],
|
|
73
|
+
}
|
|
74
|
+
: undefined;
|
|
75
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '@ai-sdk/provider-utils';
|
|
6
6
|
import { InvalidToolApprovalSignatureError } from '../error/invalid-tool-approval-signature-error';
|
|
7
7
|
import { InvalidToolInputError } from '../error/invalid-tool-input-error';
|
|
8
|
+
import { isDeepEqualData } from '../util/is-deep-equal-data';
|
|
8
9
|
import type { CollectedToolApprovals } from './collect-tool-approvals';
|
|
9
10
|
import { isApprovalNeeded } from './is-approval-needed';
|
|
10
11
|
import { verifyToolApprovalSignature } from './tool-approval-signature';
|
|
@@ -86,18 +87,35 @@ export async function validateApprovedToolApprovals<TOOLS extends ToolSet>({
|
|
|
86
87
|
typeof tool.execute === 'function' &&
|
|
87
88
|
tool.inputSchema != null
|
|
88
89
|
) {
|
|
90
|
+
const hasInputSchemaInput = Object.prototype.hasOwnProperty.call(
|
|
91
|
+
approvalRequest,
|
|
92
|
+
'inputSchemaInput',
|
|
93
|
+
);
|
|
89
94
|
const validation = await safeValidateTypes({
|
|
90
|
-
value:
|
|
95
|
+
value: hasInputSchemaInput
|
|
96
|
+
? approvalRequest.inputSchemaInput
|
|
97
|
+
: toolCall.input,
|
|
91
98
|
schema: asSchema(tool.inputSchema),
|
|
92
99
|
});
|
|
93
100
|
|
|
101
|
+
let validationError: unknown;
|
|
94
102
|
if (!validation.success) {
|
|
103
|
+
validationError = validation.error;
|
|
104
|
+
} else if (!isDeepEqualData(validation.value, toolCall.input)) {
|
|
105
|
+
// Revalidation must never change the operation that was approved,
|
|
106
|
+
// including when older or projected history omits the schema input.
|
|
107
|
+
validationError = new Error(
|
|
108
|
+
'Approved tool input does not match the validated schema output.',
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (validationError != null) {
|
|
95
113
|
invalid.push({
|
|
96
114
|
...approval,
|
|
97
115
|
error: new InvalidToolInputError({
|
|
98
116
|
toolName: toolCall.toolName,
|
|
99
117
|
toolInput: JSON.stringify(toolCall.input),
|
|
100
|
-
cause:
|
|
118
|
+
cause: validationError,
|
|
101
119
|
}),
|
|
102
120
|
});
|
|
103
121
|
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: 'v3',
|
|
@@ -2,11 +2,18 @@ import type {
|
|
|
2
2
|
EmbeddingModelV3,
|
|
3
3
|
EmbeddingModelV3CallOptions,
|
|
4
4
|
} from '@ai-sdk/provider';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL,
|
|
7
|
+
EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER,
|
|
8
|
+
} from '@ai-sdk/provider-utils';
|
|
6
9
|
import {
|
|
7
10
|
getEmbeddingModelMaxInputBytesPerCall,
|
|
8
11
|
type EmbeddingModelWithMaxInputBytesPerCall,
|
|
9
12
|
} from '../model/get-embedding-model-max-input-bytes-per-call';
|
|
13
|
+
import {
|
|
14
|
+
getEmbeddingModelProviderOptionsTransformer,
|
|
15
|
+
type EmbeddingModelWithProviderOptionsTransformer,
|
|
16
|
+
} from '../model/get-embedding-model-provider-options-transformer';
|
|
10
17
|
import type { EmbeddingModelMiddleware } from '../types';
|
|
11
18
|
import { asArray } from '../util/as-array';
|
|
12
19
|
|
|
@@ -57,7 +64,8 @@ const doWrap = ({
|
|
|
57
64
|
middleware: EmbeddingModelMiddleware;
|
|
58
65
|
modelId?: string;
|
|
59
66
|
providerId?: string;
|
|
60
|
-
}): EmbeddingModelWithMaxInputBytesPerCall
|
|
67
|
+
}): EmbeddingModelWithMaxInputBytesPerCall &
|
|
68
|
+
EmbeddingModelWithProviderOptionsTransformer => {
|
|
61
69
|
async function doTransform({
|
|
62
70
|
params,
|
|
63
71
|
}: {
|
|
@@ -74,6 +82,8 @@ const doWrap = ({
|
|
|
74
82
|
overrideMaxEmbeddingsPerCall?.({ model }) ?? model.maxEmbeddingsPerCall,
|
|
75
83
|
[EXPERIMENTAL_EMBEDDING_MODEL_MAX_INPUT_BYTES_PER_CALL]:
|
|
76
84
|
getEmbeddingModelMaxInputBytesPerCall(model),
|
|
85
|
+
[EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER]:
|
|
86
|
+
getEmbeddingModelProviderOptionsTransformer(model),
|
|
77
87
|
supportsParallelCalls:
|
|
78
88
|
overrideSupportsParallelCalls?.({ model }) ?? model.supportsParallelCalls,
|
|
79
89
|
async doEmbed(
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EmbeddingModelV3 } 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 = EmbeddingModelV3 & {
|
|
8
|
+
readonly [EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER]?: EmbeddingModelProviderOptionsTransformer;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function getEmbeddingModelProviderOptionsTransformer(
|
|
12
|
+
model: EmbeddingModelV3,
|
|
13
|
+
) {
|
|
14
|
+
return (model as EmbeddingModelWithProviderOptionsTransformer)[
|
|
15
|
+
EXPERIMENTAL_EMBEDDING_MODEL_PROVIDER_OPTIONS_TRANSFORMER
|
|
16
|
+
];
|
|
17
|
+
}
|