@ai-sdk/xai 3.0.99 → 3.0.101

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/docs/01-xai.mdx CHANGED
@@ -984,6 +984,37 @@ const { video } = await generateVideo({
984
984
  });
985
985
  ```
986
986
 
987
+ You can also pass the starting frame via the top-level `frameImages` option using the `first_frame` role. This is provider-agnostic and accepts file data (e.g. a `Buffer`) or a `{ type: 'url', url }` object:
988
+
989
+ ```ts
990
+ import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
991
+ import { experimental_generateVideo as generateVideo } from 'ai';
992
+ import fs from 'node:fs';
993
+
994
+ const { video } = await generateVideo({
995
+ model: xai.video('grok-imagine-video'),
996
+ prompt: 'The cat slowly turns its head and blinks',
997
+ frameImages: [
998
+ {
999
+ image: fs.readFileSync('./start-frame.png'),
1000
+ frameType: 'first_frame',
1001
+ },
1002
+ ],
1003
+ duration: 5,
1004
+ providerOptions: {
1005
+ xai: {
1006
+ pollTimeoutMs: 600000, // 10 minutes
1007
+ } satisfies XaiVideoModelOptions,
1008
+ },
1009
+ });
1010
+ ```
1011
+
1012
+ <Note>
1013
+ xAI does not support first-last-frame interpolation. A `last_frame` entry in
1014
+ `frameImages` is ignored with a warning — use the `extend-video` mode to
1015
+ continue from a video's last frame instead.
1016
+ </Note>
1017
+
987
1018
  ### Video Editing
988
1019
 
989
1020
  Edit an existing video using a text prompt by providing a source video URL via provider options:
@@ -1133,9 +1164,38 @@ const { video } = await generateVideo({
1133
1164
 
1134
1165
  Use `<IMAGE_1>`, `<IMAGE_2>`, etc. in your prompt to reference specific images. Up to 7 reference images are supported per request.
1135
1166
 
1167
+ Alternatively, use the provider-agnostic top-level `inputReferences` option. Providing it automatically selects reference-to-video mode, and it accepts file data (e.g. a `Buffer`) or `{ type: 'url', url }` objects:
1168
+
1169
+ ```ts
1170
+ import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
1171
+ import { experimental_generateVideo as generateVideo } from 'ai';
1172
+ import fs from 'node:fs';
1173
+
1174
+ const { video } = await generateVideo({
1175
+ model: xai.video('grok-imagine-video'),
1176
+ prompt:
1177
+ 'The comic cat and the comic dog are having a playful chase ' +
1178
+ 'through a sunlit park. Cinematic slow-motion, warm afternoon light.',
1179
+ inputReferences: [
1180
+ fs.readFileSync('./comic-cat.png'),
1181
+ fs.readFileSync('./comic-dog.png'),
1182
+ ],
1183
+ duration: 8,
1184
+ aspectRatio: '16:9',
1185
+ providerOptions: {
1186
+ xai: {
1187
+ pollTimeoutMs: 600000,
1188
+ } satisfies XaiVideoModelOptions,
1189
+ },
1190
+ });
1191
+ ```
1192
+
1136
1193
  <Note>
1137
1194
  Reference-to-video supports `duration`, `aspectRatio`, and `resolution`. Use
1138
- `mode` to select the operation — each mode is mutually exclusive.
1195
+ `mode` to select the operation — each mode is mutually exclusive. When both
1196
+ are provided, `frameImages` takes precedence over `inputReferences`, and
1197
+ `inputReferences` takes precedence over the legacy `referenceImageUrls`
1198
+ provider option. Reference-to-video requires the `grok-imagine-video` model.
1139
1199
  </Note>
1140
1200
 
1141
1201
  ### Video Provider Options
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/xai",
3
- "version": "3.0.99",
3
+ "version": "3.0.101",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -29,9 +29,9 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@ai-sdk/openai-compatible": "2.0.53",
33
- "@ai-sdk/provider": "3.0.12",
34
- "@ai-sdk/provider-utils": "4.0.32"
32
+ "@ai-sdk/openai-compatible": "2.0.55",
33
+ "@ai-sdk/provider": "3.0.13",
34
+ "@ai-sdk/provider-utils": "4.0.34"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "20.17.24",
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AISDKError,
3
3
  type Experimental_VideoModelV3,
4
+ type Experimental_VideoModelV3File,
4
5
  type SharedV3Warning,
5
6
  } from '@ai-sdk/provider';
6
7
  import {
@@ -37,21 +38,87 @@ const RESOLUTION_MAP: Record<string, string> = {
37
38
  '640x480': '480p',
38
39
  };
39
40
 
41
+ type XaiVideoDoGenerateOptions = Parameters<
42
+ Experimental_VideoModelV3['doGenerate']
43
+ >[0];
44
+
45
+ function getFirstFrameImage(
46
+ options: XaiVideoDoGenerateOptions,
47
+ ): Experimental_VideoModelV3File | undefined {
48
+ return options.frameImages?.find(frame => frame.frameType === 'first_frame')
49
+ ?.image;
50
+ }
51
+
52
+ function getLastFrameImage(
53
+ options: XaiVideoDoGenerateOptions,
54
+ ): Experimental_VideoModelV3File | undefined {
55
+ return options.frameImages?.find(frame => frame.frameType === 'last_frame')
56
+ ?.image;
57
+ }
58
+
59
+ function resolveStartImage(
60
+ options: XaiVideoDoGenerateOptions,
61
+ ): Experimental_VideoModelV3File | undefined {
62
+ return getFirstFrameImage(options) ?? options.image;
63
+ }
64
+
65
+ function fileToXaiImageUrl(file: Experimental_VideoModelV3File): string {
66
+ if (file.type === 'url') {
67
+ return file.url;
68
+ }
69
+
70
+ const base64Data =
71
+ typeof file.data === 'string'
72
+ ? file.data
73
+ : convertUint8ArrayToBase64(file.data);
74
+ return `data:${file.mediaType ?? 'image/png'};base64,${base64Data}`;
75
+ }
76
+
77
+ // Resolves the reference images for R2V generation. First-class
78
+ // `inputReferences` win over the legacy `referenceImageUrls` provider option.
79
+ function resolveReferenceImages(
80
+ options: XaiVideoDoGenerateOptions,
81
+ xaiOptions: XaiParsedVideoModelOptions | undefined,
82
+ ): Array<{ url: string }> | undefined {
83
+ if (options.inputReferences != null && options.inputReferences.length > 0) {
84
+ return options.inputReferences.map(reference => ({
85
+ url: fileToXaiImageUrl(reference),
86
+ }));
87
+ }
88
+
89
+ if (
90
+ xaiOptions?.referenceImageUrls != null &&
91
+ xaiOptions.referenceImageUrls.length > 0
92
+ ) {
93
+ return xaiOptions.referenceImageUrls.map(url => ({ url }));
94
+ }
95
+
96
+ return undefined;
97
+ }
98
+
40
99
  function resolveVideoMode(
41
- options: XaiParsedVideoModelOptions | undefined,
100
+ options: XaiVideoDoGenerateOptions,
101
+ xaiOptions: XaiParsedVideoModelOptions | undefined,
42
102
  ): XaiParsedVideoModelOptions['mode'] | undefined {
43
- if (options?.mode != null) {
44
- return options.mode;
103
+ if (xaiOptions?.mode != null) {
104
+ return xaiOptions.mode;
45
105
  }
46
106
 
47
- if (options?.videoUrl != null) {
107
+ if (xaiOptions?.videoUrl != null) {
48
108
  return 'edit-video';
49
109
  }
50
110
 
51
- if (
52
- options?.referenceImageUrls != null &&
53
- options.referenceImageUrls.length > 0
54
- ) {
111
+ // frameImages (first/last frame) take precedence over reference images, so
112
+ // only auto-select reference-to-video when no frame images are provided.
113
+ const hasFrameImages =
114
+ options.frameImages != null && options.frameImages.length > 0;
115
+ const hasInputReferences =
116
+ options.inputReferences != null && options.inputReferences.length > 0;
117
+ const hasLegacyReferenceUrls =
118
+ xaiOptions?.referenceImageUrls != null &&
119
+ xaiOptions.referenceImageUrls.length > 0;
120
+
121
+ if (!hasFrameImages && (hasInputReferences || hasLegacyReferenceUrls)) {
55
122
  return 'reference-to-video';
56
123
  }
57
124
 
@@ -83,7 +150,7 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
83
150
  schema: xaiVideoModelOptionsSchema,
84
151
  })) as XaiParsedVideoModelOptions | undefined;
85
152
 
86
- const effectiveMode = resolveVideoMode(xaiOptions);
153
+ const effectiveMode = resolveVideoMode(options, xaiOptions);
87
154
 
88
155
  const isEdit = effectiveMode === 'edit-video';
89
156
  const isExtension = effectiveMode === 'extend-video';
@@ -207,26 +274,47 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
207
274
  body.video = { url: xaiOptions!.videoUrl };
208
275
  }
209
276
 
210
- // Convert SDK image input to the nested xAI request image object
211
- if (options.image != null) {
212
- if (options.image.type === 'url') {
213
- body.image = { url: options.image.url };
214
- } else {
215
- const base64Data =
216
- typeof options.image.data === 'string'
217
- ? options.image.data
218
- : convertUint8ArrayToBase64(options.image.data);
219
- body.image = {
220
- url: `data:${options.image.mediaType};base64,${base64Data}`,
221
- };
222
- }
277
+ // Convert the start image (first_frame or image-to-video input) to the
278
+ // nested xAI request image object.
279
+ const startImage = resolveStartImage(options);
280
+ if (startImage != null) {
281
+ body.image = { url: fileToXaiImageUrl(startImage) };
282
+ }
283
+
284
+ // xAI has no first-last-frame interpolation; warn and ignore last_frame.
285
+ if (getLastFrameImage(options) != null) {
286
+ warnings.push({
287
+ type: 'unsupported',
288
+ feature: 'frameImages',
289
+ details:
290
+ 'xAI video models do not support last_frame. Use ' +
291
+ 'providerOptions.xai.mode "extend-video" to continue from a ' +
292
+ "video's last frame. The last frame image was ignored.",
293
+ });
223
294
  }
224
295
 
225
296
  // Reference images for R2V (reference-to-video) generation
226
297
  if (hasReferenceImages) {
227
- body.reference_images = xaiOptions!.referenceImageUrls!.map(url => ({
228
- url,
229
- }));
298
+ const referenceImages = resolveReferenceImages(options, xaiOptions);
299
+ if (referenceImages != null) {
300
+ body.reference_images = referenceImages;
301
+ }
302
+ }
303
+
304
+ // Warn when reference images were provided but cannot be used in the
305
+ // resolved mode (e.g. alongside frameImages, or in edit/extend modes).
306
+ if (
307
+ options.inputReferences != null &&
308
+ options.inputReferences.length > 0 &&
309
+ !hasReferenceImages
310
+ ) {
311
+ warnings.push({
312
+ type: 'unsupported',
313
+ feature: 'inputReferences',
314
+ details:
315
+ 'xAI only supports inputReferences for reference-to-video ' +
316
+ 'generation. The reference images were ignored.',
317
+ });
230
318
  }
231
319
 
232
320
  if (xaiOptions != null) {