ai 6.0.288 → 6.0.289
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 +14 -0
- package/dist/index.js +52 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +52 -32
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +17 -4
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +17 -4
- package/dist/internal/index.mjs.map +1 -1
- package/docs/03-ai-sdk-core/40-middleware.mdx +92 -7
- package/package.json +4 -4
- package/src/generate-text/run-tools-transformation.ts +37 -3
- package/src/generate-text/stream-text.ts +0 -27
- package/src/prompt/convert-to-language-model-prompt.ts +7 -2
- package/src/prompt/data-content.ts +11 -2
- 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
|
@@ -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();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.289",
|
|
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.199",
|
|
49
|
+
"@ai-sdk/provider": "3.0.17",
|
|
50
|
+
"@ai-sdk/provider-utils": "4.0.53"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -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',
|
|
@@ -1965,8 +1965,6 @@ class DefaultStreamTextResult<
|
|
|
1965
1965
|
const stepToolOutputs: ToolOutput<TOOLS>[] = [];
|
|
1966
1966
|
let warnings: SharedV3Warning[] | undefined;
|
|
1967
1967
|
|
|
1968
|
-
const activeToolCallToolNames: Record<string, string> = {};
|
|
1969
|
-
|
|
1970
1968
|
let stepFinishReason: FinishReason = 'other';
|
|
1971
1969
|
let stepRawFinishReason: string | undefined = undefined;
|
|
1972
1970
|
|
|
@@ -2168,18 +2166,7 @@ class DefaultStreamTextResult<
|
|
|
2168
2166
|
}
|
|
2169
2167
|
|
|
2170
2168
|
case 'tool-input-start': {
|
|
2171
|
-
activeToolCallToolNames[chunk.id] = chunk.toolName;
|
|
2172
|
-
|
|
2173
2169
|
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
2170
|
controller.enqueue({
|
|
2184
2171
|
...chunk,
|
|
2185
2172
|
dynamic: chunk.dynamic ?? tool?.type === 'dynamic',
|
|
@@ -2189,25 +2176,11 @@ class DefaultStreamTextResult<
|
|
|
2189
2176
|
}
|
|
2190
2177
|
|
|
2191
2178
|
case 'tool-input-end': {
|
|
2192
|
-
delete activeToolCallToolNames[chunk.id];
|
|
2193
2179
|
controller.enqueue(chunk);
|
|
2194
2180
|
break;
|
|
2195
2181
|
}
|
|
2196
2182
|
|
|
2197
2183
|
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
2184
|
controller.enqueue(chunk);
|
|
2212
2185
|
break;
|
|
2213
2186
|
}
|
|
@@ -492,8 +492,11 @@ function convertPartToLanguageModelPart(
|
|
|
492
492
|
throw new Error(`Unsupported part type: ${type}`);
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
-
const {
|
|
496
|
-
|
|
495
|
+
const {
|
|
496
|
+
data: convertedData,
|
|
497
|
+
mediaType: convertedMediaType,
|
|
498
|
+
originalUrl,
|
|
499
|
+
} = convertToLanguageModelV3DataContent(originalData);
|
|
497
500
|
|
|
498
501
|
let mediaType: string | undefined = convertedMediaType ?? part.mediaType;
|
|
499
502
|
let data: Uint8Array | string | URL = convertedData; // binary | base64 | url
|
|
@@ -525,6 +528,7 @@ function convertPartToLanguageModelPart(
|
|
|
525
528
|
mediaType: mediaType ?? 'image/*', // any image
|
|
526
529
|
filename: undefined,
|
|
527
530
|
data,
|
|
531
|
+
...(data instanceof URL && originalUrl != null ? { originalUrl } : {}),
|
|
528
532
|
providerOptions: part.providerOptions,
|
|
529
533
|
};
|
|
530
534
|
}
|
|
@@ -540,6 +544,7 @@ function convertPartToLanguageModelPart(
|
|
|
540
544
|
mediaType,
|
|
541
545
|
filename: part.filename,
|
|
542
546
|
data,
|
|
547
|
+
...(data instanceof URL && originalUrl != null ? { originalUrl } : {}),
|
|
543
548
|
providerOptions: part.providerOptions,
|
|
544
549
|
};
|
|
545
550
|
}
|
|
@@ -28,7 +28,10 @@ export function convertToLanguageModelV3DataContent(
|
|
|
28
28
|
): {
|
|
29
29
|
data: LanguageModelV3DataContent;
|
|
30
30
|
mediaType: string | undefined;
|
|
31
|
+
originalUrl?: string;
|
|
31
32
|
} {
|
|
33
|
+
let originalUrl: string | undefined;
|
|
34
|
+
|
|
32
35
|
// Buffer & Uint8Array:
|
|
33
36
|
if (content instanceof Uint8Array) {
|
|
34
37
|
return { data: content, mediaType: undefined };
|
|
@@ -43,7 +46,9 @@ export function convertToLanguageModelV3DataContent(
|
|
|
43
46
|
// is not a URL and likely some other sort of data.
|
|
44
47
|
if (typeof content === 'string') {
|
|
45
48
|
try {
|
|
46
|
-
|
|
49
|
+
const url = new URL(content);
|
|
50
|
+
originalUrl = url.toString() !== content ? content : undefined;
|
|
51
|
+
content = url;
|
|
47
52
|
} catch (error) {
|
|
48
53
|
// ignored
|
|
49
54
|
}
|
|
@@ -65,7 +70,11 @@ export function convertToLanguageModelV3DataContent(
|
|
|
65
70
|
return { data: base64Content, mediaType: dataUrlMediaType };
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
return {
|
|
73
|
+
return {
|
|
74
|
+
data: content,
|
|
75
|
+
mediaType: undefined,
|
|
76
|
+
...(originalUrl != null ? { originalUrl } : {}),
|
|
77
|
+
};
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
/**
|
|
@@ -35,7 +35,7 @@ export function lastAssistantMessageIsCompleteWithApprovalResponses({
|
|
|
35
35
|
// all tool approvals must have a response
|
|
36
36
|
lastStepToolInvocations.every(
|
|
37
37
|
part =>
|
|
38
|
-
part.state === 'output-available' ||
|
|
38
|
+
(part.state === 'output-available' && part.preliminary !== true) ||
|
|
39
39
|
part.state === 'output-error' ||
|
|
40
40
|
part.state === 'output-denied' ||
|
|
41
41
|
part.state === 'approval-responded',
|
|
@@ -33,7 +33,8 @@ export function lastAssistantMessageIsCompleteWithToolCalls({
|
|
|
33
33
|
lastStepToolInvocations.length > 0 &&
|
|
34
34
|
lastStepToolInvocations.every(
|
|
35
35
|
part =>
|
|
36
|
-
part.state === 'output-available'
|
|
36
|
+
(part.state === 'output-available' && part.preliminary !== true) ||
|
|
37
|
+
part.state === 'output-error',
|
|
37
38
|
)
|
|
38
39
|
);
|
|
39
40
|
}
|