ai 7.0.6 → 7.0.8
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 +16 -0
- package/dist/index.d.ts +19 -2
- package/dist/index.js +68 -48
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/03-ai-sdk-core/38-video-generation.mdx +40 -0
- package/docs/03-ai-sdk-core/40-middleware.mdx +15 -7
- package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +11 -2
- package/docs/07-reference/01-ai-sdk-core/13-generate-video.mdx +12 -0
- package/docs/07-reference/01-ai-sdk-core/65-language-model-v2-middleware.mdx +2 -2
- package/package.json +4 -4
- package/src/generate-video/generate-video.ts +122 -49
- package/src/ui/convert-to-model-messages.ts +6 -4
package/dist/internal/index.js
CHANGED
|
@@ -171,6 +171,46 @@ const { video } = await generateVideo({
|
|
|
171
171
|
});
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
+
### First and Last Frame
|
|
175
|
+
|
|
176
|
+
Some video models support first-last-frame generation, where you provide the
|
|
177
|
+
starting and/or ending frames of the video. Use the `frameImages` option to pass
|
|
178
|
+
role-tagged images in a provider-agnostic way:
|
|
179
|
+
|
|
180
|
+
```tsx highlight={"5-16"}
|
|
181
|
+
const { video } = await generateVideo({
|
|
182
|
+
model: __VIDEO_MODEL__,
|
|
183
|
+
prompt: 'The cat walks across the scene and transforms into a dog by the end',
|
|
184
|
+
frameImages: [
|
|
185
|
+
{
|
|
186
|
+
image: 'https://example.com/first-frame.png',
|
|
187
|
+
frameType: 'first_frame',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
image: 'https://example.com/last-frame.png',
|
|
191
|
+
frameType: 'last_frame',
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Reference Images
|
|
198
|
+
|
|
199
|
+
Some video models support reference-to-video generation, where you provide one or
|
|
200
|
+
more reference images that the model incorporates into the generated video. Use the `inputReferences` option to pass
|
|
201
|
+
these images in a provider-agnostic way:
|
|
202
|
+
|
|
203
|
+
```tsx highlight={"5-8"}
|
|
204
|
+
const { video } = await generateVideo({
|
|
205
|
+
model: __VIDEO_MODEL__,
|
|
206
|
+
prompt: 'The two characters meet in a bustling market',
|
|
207
|
+
inputReferences: [
|
|
208
|
+
'https://example.com/character-1.png',
|
|
209
|
+
'https://example.com/character-2.png',
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
174
214
|
### Providing a Seed
|
|
175
215
|
|
|
176
216
|
You can provide a seed to the `experimental_generateVideo` function to control the output of the video generation process.
|
|
@@ -271,15 +271,15 @@ Find more examples at this [link](https://github.com/minpeter/ai-sdk-tool-call-m
|
|
|
271
271
|
<Note>
|
|
272
272
|
Implementing language model middleware is advanced functionality and requires
|
|
273
273
|
a solid understanding of the [language model
|
|
274
|
-
specification](https://github.com/vercel/ai/blob/
|
|
274
|
+
specification](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts).
|
|
275
275
|
</Note>
|
|
276
276
|
|
|
277
277
|
You can implement any of the following three function to modify the behavior of the language model:
|
|
278
278
|
|
|
279
279
|
1. `transformParams`: Transforms the parameters before they are passed to the language model, for both `doGenerate` and `doStream`.
|
|
280
|
-
2. `wrapGenerate`: Wraps the `doGenerate` method of the [language model](https://github.com/vercel/ai/blob/
|
|
280
|
+
2. `wrapGenerate`: Wraps the `doGenerate` method of the [language model](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts).
|
|
281
281
|
You can modify the parameters, call the language model, and modify the result.
|
|
282
|
-
3. `wrapStream`: Wraps the `doStream` method of the [language model](https://github.com/vercel/ai/blob/
|
|
282
|
+
3. `wrapStream`: Wraps the `doStream` method of the [language model](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts).
|
|
283
283
|
You can modify the parameters, call the language model, and modify the result.
|
|
284
284
|
|
|
285
285
|
Here are some examples of how to implement language model middleware:
|
|
@@ -307,9 +307,13 @@ export const yourLogMiddleware: LanguageModelV4Middleware = {
|
|
|
307
307
|
console.log(`params: ${JSON.stringify(params, null, 2)}`);
|
|
308
308
|
|
|
309
309
|
const result = await doGenerate();
|
|
310
|
+
const generatedText = result.content
|
|
311
|
+
.filter(part => part.type === 'text')
|
|
312
|
+
.map(part => part.text)
|
|
313
|
+
.join('');
|
|
310
314
|
|
|
311
315
|
console.log('doGenerate finished');
|
|
312
|
-
console.log(`generated text: ${
|
|
316
|
+
console.log(`generated text: ${generatedText}`);
|
|
313
317
|
|
|
314
318
|
return result;
|
|
315
319
|
},
|
|
@@ -437,12 +441,16 @@ import type { LanguageModelV4Middleware } from '@ai-sdk/provider';
|
|
|
437
441
|
|
|
438
442
|
export const yourGuardrailMiddleware: LanguageModelV4Middleware = {
|
|
439
443
|
wrapGenerate: async ({ doGenerate }) => {
|
|
440
|
-
const
|
|
444
|
+
const result = await doGenerate();
|
|
441
445
|
|
|
442
446
|
// filtering approach, e.g. for PII or other sensitive information:
|
|
443
|
-
const
|
|
447
|
+
const content = result.content.map(part =>
|
|
448
|
+
part.type === 'text'
|
|
449
|
+
? { ...part, text: part.text.replace(/badword/g, '<REDACTED>') }
|
|
450
|
+
: part,
|
|
451
|
+
);
|
|
444
452
|
|
|
445
|
-
return {
|
|
453
|
+
return { ...result, content };
|
|
446
454
|
},
|
|
447
455
|
|
|
448
456
|
// here you would implement the guardrail logic for streaming
|
|
@@ -359,11 +359,20 @@ By default, message IDs are generated client-side:
|
|
|
359
359
|
- AI response message IDs are generated by `streamText` on the server
|
|
360
360
|
|
|
361
361
|
For applications without persistence, client-side ID generation works perfectly.
|
|
362
|
-
However, **for persistence, you
|
|
362
|
+
However, **for persistence, you should use IDs that are stable before messages
|
|
363
|
+
are stored** to ensure consistency across sessions and prevent ID conflicts when
|
|
364
|
+
messages are restored.
|
|
365
|
+
|
|
366
|
+
The server-side options below control IDs for generated assistant response
|
|
367
|
+
messages. User message IDs are created by `useChat` before the request is sent
|
|
368
|
+
to your API route, so keep those client-generated IDs when saving incoming
|
|
369
|
+
request messages, or generate and persist your own user message IDs before
|
|
370
|
+
sending/storing them.
|
|
363
371
|
|
|
364
372
|
### Setting Up Server-side ID Generation
|
|
365
373
|
|
|
366
|
-
When implementing persistence, you have two options for generating server-side
|
|
374
|
+
When implementing persistence, you have two options for generating server-side
|
|
375
|
+
IDs for assistant response messages:
|
|
367
376
|
|
|
368
377
|
1. **Using `generateMessageId` in `toUIMessageStream`**
|
|
369
378
|
2. **Setting IDs in your start message part with `createUIMessageStream`**
|
|
@@ -110,6 +110,18 @@ console.log(videos);
|
|
|
110
110
|
isOptional: true,
|
|
111
111
|
description: 'Seed for the video generation.',
|
|
112
112
|
},
|
|
113
|
+
{
|
|
114
|
+
name: 'frameImages',
|
|
115
|
+
type: 'Array<{ image: DataContent; frameType: "first_frame" | "last_frame" }>',
|
|
116
|
+
isOptional: true,
|
|
117
|
+
description: 'Role-tagged image inputs for first-last-frame generation.',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'inputReferences',
|
|
121
|
+
type: 'Array<DataContent>',
|
|
122
|
+
isOptional: true,
|
|
123
|
+
description: 'Reference image inputs for reference-to-video generation.',
|
|
124
|
+
},
|
|
113
125
|
{
|
|
114
126
|
name: 'generateAudio',
|
|
115
127
|
type: 'boolean',
|
|
@@ -28,8 +28,8 @@ See [Language Model Middleware](/docs/ai-sdk-core/middleware) for more informati
|
|
|
28
28
|
content={[
|
|
29
29
|
{
|
|
30
30
|
name: 'specificationVersion',
|
|
31
|
-
type: "'
|
|
32
|
-
description: 'The specification version of the middleware. Must be "
|
|
31
|
+
type: "'v4'",
|
|
32
|
+
description: 'The specification version of the middleware. Must be "v4".',
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
name: 'transformParams',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.8",
|
|
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,9 +42,9 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
46
|
-
"@ai-sdk/provider
|
|
47
|
-
"@ai-sdk/provider": "
|
|
45
|
+
"@ai-sdk/gateway": "4.0.6",
|
|
46
|
+
"@ai-sdk/provider": "4.0.1",
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -2,6 +2,8 @@ import type {
|
|
|
2
2
|
Experimental_VideoModelV4,
|
|
3
3
|
Experimental_VideoModelV4CallOptions,
|
|
4
4
|
Experimental_VideoModelV4File,
|
|
5
|
+
Experimental_VideoModelV4FrameImage,
|
|
6
|
+
Experimental_VideoModelV4FrameType,
|
|
5
7
|
SharedV4ProviderMetadata,
|
|
6
8
|
} from '@ai-sdk/provider';
|
|
7
9
|
import {
|
|
@@ -26,6 +28,7 @@ import { prepareRetries } from '../util/prepare-retries';
|
|
|
26
28
|
import { VERSION } from '../version';
|
|
27
29
|
import type { GenerateVideoResult } from './generate-video-result';
|
|
28
30
|
import { splitDataUrl } from '../prompt/split-data-url';
|
|
31
|
+
import { convertDataContentToUint8Array } from '../prompt/data-content';
|
|
29
32
|
|
|
30
33
|
export type GenerateVideoPrompt =
|
|
31
34
|
| string
|
|
@@ -45,6 +48,8 @@ export type GenerateVideoPrompt =
|
|
|
45
48
|
* @param duration - Duration of the video in seconds.
|
|
46
49
|
* @param fps - Frames per second for the video.
|
|
47
50
|
* @param seed - Seed for the video generation.
|
|
51
|
+
* @param frameImages - Role-tagged image inputs for image-to-video and first-last-frame generation.
|
|
52
|
+
* @param inputReferences - Reference image inputs for reference-to-video generation.
|
|
48
53
|
* @param generateAudio - Whether the model should generate audio alongside the video.
|
|
49
54
|
* @param providerOptions - Additional provider-specific options that are passed through to the provider
|
|
50
55
|
* as body parameters.
|
|
@@ -66,6 +71,8 @@ export async function experimental_generateVideo({
|
|
|
66
71
|
duration,
|
|
67
72
|
fps,
|
|
68
73
|
seed,
|
|
74
|
+
frameImages,
|
|
75
|
+
inputReferences,
|
|
69
76
|
generateAudio,
|
|
70
77
|
providerOptions,
|
|
71
78
|
maxRetries: maxRetriesArg,
|
|
@@ -118,6 +125,26 @@ export async function experimental_generateVideo({
|
|
|
118
125
|
*/
|
|
119
126
|
seed?: number;
|
|
120
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Role-tagged image inputs for image-to-video and first-last-frame generation.
|
|
130
|
+
*/
|
|
131
|
+
frameImages?: Array<{
|
|
132
|
+
/**
|
|
133
|
+
* The image for this frame.
|
|
134
|
+
*/
|
|
135
|
+
image: DataContent;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Which frame this image represents.
|
|
139
|
+
*/
|
|
140
|
+
frameType: Experimental_VideoModelV4FrameType;
|
|
141
|
+
}>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reference image inputs for reference-to-video generation.
|
|
145
|
+
*/
|
|
146
|
+
inputReferences?: Array<DataContent>;
|
|
147
|
+
|
|
121
148
|
/**
|
|
122
149
|
* Whether the model should generate audio alongside the video.
|
|
123
150
|
*/
|
|
@@ -172,6 +199,55 @@ export async function experimental_generateVideo({
|
|
|
172
199
|
|
|
173
200
|
const { prompt, image } = normalizePrompt(promptArg);
|
|
174
201
|
|
|
202
|
+
const normalizedFrameImages:
|
|
203
|
+
| Array<Experimental_VideoModelV4FrameImage>
|
|
204
|
+
| undefined = frameImages?.map(frame => ({
|
|
205
|
+
image: normalizeImageData(frame.image),
|
|
206
|
+
frameType: frame.frameType,
|
|
207
|
+
}));
|
|
208
|
+
|
|
209
|
+
const normalizedInputReferences:
|
|
210
|
+
| Array<Experimental_VideoModelV4File>
|
|
211
|
+
| undefined = inputReferences?.map(reference =>
|
|
212
|
+
normalizeImageData(reference),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const effectiveInputReferences =
|
|
216
|
+
normalizedFrameImages != null && normalizedFrameImages.length > 0
|
|
217
|
+
? undefined
|
|
218
|
+
: normalizedInputReferences;
|
|
219
|
+
|
|
220
|
+
const warnings: Array<Warning> = [];
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
normalizedFrameImages != null &&
|
|
224
|
+
normalizedFrameImages.length > 0 &&
|
|
225
|
+
normalizedInputReferences != null &&
|
|
226
|
+
normalizedInputReferences.length > 0
|
|
227
|
+
) {
|
|
228
|
+
warnings.push({
|
|
229
|
+
type: 'other',
|
|
230
|
+
message:
|
|
231
|
+
'inputReferences were ignored because frameImages were provided; ' +
|
|
232
|
+
'frameImages and inputReferences cannot be combined.',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const firstFrameImage = normalizedFrameImages?.find(
|
|
237
|
+
frame => frame.frameType === 'first_frame',
|
|
238
|
+
)?.image;
|
|
239
|
+
|
|
240
|
+
if (image != null && firstFrameImage != null) {
|
|
241
|
+
warnings.push({
|
|
242
|
+
type: 'other',
|
|
243
|
+
message:
|
|
244
|
+
'prompt.image was ignored because a first_frame frameImage was provided; ' +
|
|
245
|
+
'the first_frame frameImage takes precedence as the start image.',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const resolvedImage = firstFrameImage ?? image;
|
|
250
|
+
|
|
175
251
|
const maxVideosPerCallWithDefault =
|
|
176
252
|
maxVideosPerCall ?? (await invokeModelMaxVideosPerCall(model)) ?? 1;
|
|
177
253
|
|
|
@@ -194,8 +270,10 @@ export async function experimental_generateVideo({
|
|
|
194
270
|
duration,
|
|
195
271
|
fps,
|
|
196
272
|
seed,
|
|
273
|
+
image: resolvedImage,
|
|
274
|
+
frameImages: normalizedFrameImages,
|
|
275
|
+
inputReferences: effectiveInputReferences,
|
|
197
276
|
generateAudio,
|
|
198
|
-
image,
|
|
199
277
|
providerOptions: providerOptions ?? {},
|
|
200
278
|
headers: headersWithUserAgent,
|
|
201
279
|
abortSignal,
|
|
@@ -206,7 +284,6 @@ export async function experimental_generateVideo({
|
|
|
206
284
|
|
|
207
285
|
// collect result videos, warnings, and response metadata
|
|
208
286
|
const videos: Array<GeneratedFile> = [];
|
|
209
|
-
const warnings: Array<Warning> = [];
|
|
210
287
|
const responses: Array<VideoModelResponseMetadata> = [];
|
|
211
288
|
const providerMetadata: SharedV4ProviderMetadata = {};
|
|
212
289
|
|
|
@@ -342,59 +419,55 @@ function normalizePrompt(promptArg: GenerateVideoPrompt): {
|
|
|
342
419
|
};
|
|
343
420
|
}
|
|
344
421
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
image = {
|
|
375
|
-
type: 'file',
|
|
376
|
-
mediaType,
|
|
377
|
-
data: bytes,
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
} else if (dataContent instanceof Uint8Array) {
|
|
381
|
-
const mediaType =
|
|
382
|
-
detectMediaType({
|
|
383
|
-
data: dataContent,
|
|
384
|
-
topLevelType: 'image',
|
|
385
|
-
}) ?? 'image/png';
|
|
386
|
-
|
|
387
|
-
image = {
|
|
422
|
+
return {
|
|
423
|
+
prompt: promptArg.text,
|
|
424
|
+
image:
|
|
425
|
+
promptArg.image != null ? normalizeImageData(promptArg.image) : undefined,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Normalizes a {@link DataContent} image into a {@link Experimental_VideoModelV4File}.
|
|
431
|
+
* Accepts a URL string, a data URL, a base64 string, or binary image data.
|
|
432
|
+
*/
|
|
433
|
+
function normalizeImageData(
|
|
434
|
+
dataContent: DataContent,
|
|
435
|
+
): Experimental_VideoModelV4File {
|
|
436
|
+
if (typeof dataContent === 'string') {
|
|
437
|
+
if (
|
|
438
|
+
dataContent.startsWith('http://') ||
|
|
439
|
+
dataContent.startsWith('https://')
|
|
440
|
+
) {
|
|
441
|
+
return {
|
|
442
|
+
type: 'url',
|
|
443
|
+
url: dataContent,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (dataContent.startsWith('data:')) {
|
|
448
|
+
const { mediaType, base64Content } = splitDataUrl(dataContent);
|
|
449
|
+
return {
|
|
388
450
|
type: 'file',
|
|
389
|
-
mediaType,
|
|
390
|
-
data:
|
|
451
|
+
mediaType: mediaType ?? 'image/png',
|
|
452
|
+
data: convertBase64ToUint8Array(base64Content ?? ''),
|
|
391
453
|
};
|
|
392
454
|
}
|
|
455
|
+
|
|
456
|
+
const bytes = convertBase64ToUint8Array(dataContent);
|
|
457
|
+
return {
|
|
458
|
+
type: 'file',
|
|
459
|
+
mediaType:
|
|
460
|
+
detectMediaType({ data: bytes, topLevelType: 'image' }) ?? 'image/png',
|
|
461
|
+
data: bytes,
|
|
462
|
+
};
|
|
393
463
|
}
|
|
394
464
|
|
|
465
|
+
const bytes = convertDataContentToUint8Array(dataContent);
|
|
395
466
|
return {
|
|
396
|
-
|
|
397
|
-
|
|
467
|
+
type: 'file',
|
|
468
|
+
mediaType:
|
|
469
|
+
detectMediaType({ data: bytes, topLevelType: 'image' }) ?? 'image/png',
|
|
470
|
+
data: bytes,
|
|
398
471
|
};
|
|
399
472
|
}
|
|
400
473
|
|
|
@@ -280,10 +280,12 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
283
|
+
if (content.length > 0) {
|
|
284
|
+
modelMessages.push({
|
|
285
|
+
role: 'assistant',
|
|
286
|
+
content,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
287
289
|
|
|
288
290
|
// check if there are tool invocations with results in the block
|
|
289
291
|
// Include non-provider-executed tools, OR provider-executed tools with approval responses
|