@ai-sdk/xai 4.0.34 → 4.0.36

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
@@ -1223,12 +1223,64 @@ const { video } = await generateVideo({
1223
1223
  });
1224
1224
  ```
1225
1225
 
1226
+ `inputReferences` accepts image references only. A reference with a non-image
1227
+ media type (for example a video or audio clip) is ignored with a warning, and a
1228
+ reference with no media type is treated as an image. If no image reference
1229
+ remains, reference-to-video is not selected and no reference images are sent.
1230
+
1231
+ #### Reference Audio
1232
+
1233
+ Reference-to-video can also give the subject a voice. `referenceVoiceIds` takes
1234
+ up to 3 xAI **preset** voice ids — you cannot upload your own audio clips.
1235
+ Reference the voices from the prompt with `<AUDIO_0>`, `<AUDIO_1>`, and
1236
+ `<AUDIO_2>`, in the order the voices are passed.
1237
+
1238
+ ```ts
1239
+ import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
1240
+ import { experimental_generateVideo as generateVideo } from 'ai';
1241
+
1242
+ const { video } = await generateVideo({
1243
+ model: xai.video('grok-imagine-video-1.5'),
1244
+ prompt:
1245
+ 'The person from <IMAGE_0> stands in the room from <IMAGE_1> and speaks ' +
1246
+ 'to the camera with the voice from <AUDIO_0>.',
1247
+ aspectRatio: '9:16',
1248
+ duration: 10,
1249
+ providerOptions: {
1250
+ xai: {
1251
+ mode: 'reference-to-video',
1252
+ referenceImageUrls: [
1253
+ 'https://example.com/person.png',
1254
+ 'https://example.com/room.png',
1255
+ ],
1256
+ referenceVoiceIds: ['eve'],
1257
+ resolution: '720p',
1258
+ pollTimeoutMs: 600000,
1259
+ } satisfies XaiVideoModelOptions,
1260
+ },
1261
+ });
1262
+ ```
1263
+
1264
+ Valid voice ids come from the xAI
1265
+ [text-to-speech voice roster](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech#voices).
1266
+ Ids are case-insensitive, and an unknown id returns a `400` response listing the
1267
+ available voices. `referenceVoiceIds` is ignored with a warning when the
1268
+ resolved operation is not reference-to-video.
1269
+
1270
+ <Note>
1271
+ xAI documents reference audio as available in the United States only, for
1272
+ trusted partners. Requests from accounts without access are rejected by the
1273
+ xAI API.
1274
+ </Note>
1275
+
1226
1276
  <Note>
1227
1277
  Reference-to-video supports `duration`, `aspectRatio`, and `resolution`. Use
1228
1278
  `mode` to select the operation — each mode is mutually exclusive. When both
1229
1279
  are provided, `frameImages` takes precedence over `inputReferences`, and
1230
1280
  `inputReferences` takes precedence over the legacy `referenceImageUrls`
1231
- provider option. Reference-to-video requires the `grok-imagine-video` model.
1281
+ provider option. Reference-to-video is supported by the `grok-imagine-video`
1282
+ and `grok-imagine-video-1.5` models; native 1080p requires
1283
+ `grok-imagine-video-1.5`.
1232
1284
  </Note>
1233
1285
 
1234
1286
  ### Video Provider Options
@@ -1244,11 +1296,14 @@ You can validate the provider options using the `XaiVideoModelOptions` type.
1244
1296
 
1245
1297
  Maximum wait time in milliseconds for video generation. Defaults to 600000 (10 minutes).
1246
1298
 
1247
- - **resolution** _'480p' | '720p'_
1299
+ - **resolution** _'480p' | '720p' | '1080p'_
1248
1300
 
1249
1301
  Video resolution. When using the SDK's standard `resolution` parameter,
1250
- `1280x720` maps to `720p` and `854x480` maps to `480p`.
1251
- Use this provider option to pass the native format directly.
1302
+ `1920x1080` maps to `1080p`, `1280x720` maps to `720p`, and `854x480` maps
1303
+ to `480p`. Use this provider option to pass the native format directly.
1304
+ `1080p` requires the `grok-imagine-video-1.5` model and is available for
1305
+ text-to-video and image-to-video; reference-to-video is capped at `720p`
1306
+ (a `1080p` request is downgraded with a warning).
1252
1307
 
1253
1308
  - **user** _string_
1254
1309
 
@@ -1280,6 +1335,17 @@ You can validate the provider options using the `XaiVideoModelOptions` type.
1280
1335
  `<IMAGE_1>`, `<IMAGE_2>`, etc. in the prompt to reference specific
1281
1336
  images. Used with `mode: 'reference-to-video'`.
1282
1337
 
1338
+ - **referenceVoiceIds** _string[]_
1339
+
1340
+ Up to 3 xAI preset voice ids that give the subject a voice in
1341
+ reference-to-video (R2V) generation. Preset voices only — audio clips
1342
+ cannot be uploaded. Ids are case-insensitive and come from the
1343
+ [text-to-speech voice roster](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech#voices);
1344
+ an unknown id returns a `400` listing the available voices. Use `<AUDIO_0>`,
1345
+ `<AUDIO_1>`, and `<AUDIO_2>` tags in the prompt to reference the voices.
1346
+ Ignored with a warning outside reference-to-video. Reference audio is
1347
+ documented as US-only and limited to trusted partners.
1348
+
1283
1349
  <Note>
1284
1350
  Video generation is an asynchronous process that can take several minutes.
1285
1351
  Consider setting `pollTimeoutMs` to at least 10 minutes (600000ms) for
@@ -1290,7 +1356,9 @@ You can validate the provider options using the `XaiVideoModelOptions` type.
1290
1356
  ### Aspect Ratio and Resolution
1291
1357
 
1292
1358
  For **text-to-video**, you can specify both `aspectRatio` and `resolution`.
1293
- The default aspect ratio is `16:9` and the default resolution is `480p`.
1359
+ The default aspect ratio is `16:9` and the default resolution is `480p`. The
1360
+ `grok-imagine-video-1.5` model additionally supports native `1080p` for
1361
+ text-to-video and image-to-video.
1294
1362
 
1295
1363
  For **image-to-video**, the output defaults to the input image's aspect ratio.
1296
1364
  If you specify `aspectRatio`, it will override this and stretch the image to the
@@ -1306,13 +1374,18 @@ from the source video. `duration` is supported and controls only the
1306
1374
  extension length.
1307
1375
 
1308
1376
  For **reference-to-video (R2V)**, you can specify `duration`, `aspectRatio`,
1309
- and `resolution` just like text-to-video.
1377
+ and `resolution`. Unlike text-to-video, R2V is capped at `720p` — a `1080p`
1378
+ request is downgraded to `720p` with a warning.
1310
1379
 
1311
1380
  ### Video Model Capabilities
1312
1381
 
1313
- | Model | Duration | Aspect Ratios | Resolution | Image-to-Video | Editing | Extension | R2V |
1314
- | -------------------- | -------- | ------------------------------------------------- | -------------- | -------------- | --------- | --------- | --------- |
1315
- | `grok-imagine-video` | 1–15s | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3` | `480p`, `720p` | <Check /> | <Check /> | <Check /> | <Check /> |
1382
+ | Model | Duration | Aspect Ratios | Resolution | Image-to-Video | Editing | Extension | R2V |
1383
+ | ------------------------ | -------- | ------------------------------------------------- | ------------------------- | -------------- | --------- | --------- | --------- |
1384
+ | `grok-imagine-video` | 1–15s | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3` | `480p`, `720p` | <Check /> | <Check /> | <Check /> | <Check /> |
1385
+ | `grok-imagine-video-1.5` | 1–15s | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3` | `480p`, `720p`, `1080p`\* | <Check /> | <Check /> | <Check /> | <Check /> |
1386
+
1387
+ \* Native `1080p` applies to text-to-video and image-to-video. Reference-to-video
1388
+ is capped at `720p` — a `1080p` request is downgraded with a warning.
1316
1389
 
1317
1390
  <Note>
1318
1391
  You can also pass any available provider model ID as a string if needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/xai",
3
- "version": "4.0.34",
3
+ "version": "4.0.36",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@ai-sdk/provider": "4.0.7",
33
- "@ai-sdk/provider-utils": "5.0.26"
33
+ "@ai-sdk/provider-utils": "5.0.27"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "22.19.19",
@@ -2,7 +2,7 @@ import { lazySchema, zodSchema } from '@ai-sdk/provider-utils';
2
2
  import { z } from 'zod/v4';
3
3
 
4
4
  const nonEmptyStringSchema = z.string().min(1);
5
- const resolutionSchema = z.enum(['480p', '720p']);
5
+ const resolutionSchema = z.enum(['480p', '720p', '1080p']);
6
6
  const modeSchema = z.enum(['edit-video', 'extend-video', 'reference-to-video']);
7
7
 
8
8
  export type XaiVideoMode = z.infer<typeof modeSchema>;
@@ -48,6 +48,10 @@ interface XaiVideoReferenceToVideoOptions
48
48
  mode: 'reference-to-video';
49
49
  /** Reference image URLs (1-7) for R2V generation. */
50
50
  referenceImageUrls: string[];
51
+ /**
52
+ * Preset voice ids (up to 3) that give the subject a voice.
53
+ */
54
+ referenceVoiceIds?: string[];
51
55
  }
52
56
 
53
57
  interface XaiVideoGenerationOptions
@@ -75,6 +79,10 @@ interface XaiLegacyReferenceToVideoOptions
75
79
  */
76
80
  mode?: undefined;
77
81
  referenceImageUrls: string[];
82
+ /**
83
+ * Preset voice ids (up to 3) that give the subject a voice.
84
+ */
85
+ referenceVoiceIds?: string[];
78
86
  }
79
87
 
80
88
  /**
@@ -106,52 +114,11 @@ const baseFields = {
106
114
  resolution: resolutionSchema.nullish(),
107
115
  };
108
116
 
109
- const userField = {
110
- user: z.string().optional(),
111
- };
112
-
113
- const editVideoSchema = z.object({
114
- ...baseFields,
115
- ...userField,
116
- mode: z.literal('edit-video'),
117
- videoUrl: nonEmptyStringSchema,
118
- referenceImageUrls: z.undefined().optional(),
119
- });
120
-
121
- const extendVideoSchema = z.object({
122
- ...baseFields,
123
- mode: z.literal('extend-video'),
124
- videoUrl: nonEmptyStringSchema,
125
- referenceImageUrls: z.undefined().optional(),
126
- });
127
-
128
- const referenceToVideoSchema = z.object({
129
- ...baseFields,
130
- ...userField,
131
- mode: z.literal('reference-to-video'),
132
- referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7),
133
- videoUrl: z.undefined().optional(),
134
- });
135
-
136
- const autoDetectSchema = z.object({
137
- ...baseFields,
138
- ...userField,
139
- mode: z.undefined().optional(),
140
- videoUrl: nonEmptyStringSchema.optional(),
141
- referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7).optional(),
142
- });
143
-
144
- export const xaiVideoModelOptions = z.union([
145
- editVideoSchema,
146
- extendVideoSchema,
147
- referenceToVideoSchema,
148
- autoDetectSchema,
149
- ]);
150
-
151
117
  const runtimeSchema = z.looseObject({
152
118
  mode: modeSchema.optional(),
153
119
  videoUrl: nonEmptyStringSchema.optional(),
154
120
  referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7).optional(),
121
+ referenceVoiceIds: z.array(nonEmptyStringSchema).max(3).optional(),
155
122
  user: z.string().optional(),
156
123
  ...baseFields,
157
124
  });
@@ -40,6 +40,7 @@ interface XaiVideoModelConfig {
40
40
  }
41
41
 
42
42
  const RESOLUTION_MAP: Record<string, string> = {
43
+ '1920x1080': '1080p',
43
44
  '1280x720': '720p',
44
45
  '854x480': '480p',
45
46
  '640x480': '480p',
@@ -70,7 +71,12 @@ function resolveStartImage(
70
71
  const isVideoFile = (file: VideoModelV4File): boolean =>
71
72
  file.mediaType != null && getTopLevelMediaType(file.mediaType) === 'video';
72
73
 
73
- function fileToXaiImageUrl(file: VideoModelV4File): string {
74
+ // References without a media type (only possible for URLs) are treated as
75
+ // images, matching the legacy `referenceImageUrls` behavior.
76
+ const isImageReference = (file: VideoModelV4File): boolean =>
77
+ file.mediaType == null || getTopLevelMediaType(file.mediaType) === 'image';
78
+
79
+ function fileToXaiUrl(file: VideoModelV4File): string {
74
80
  if (file.type === 'url') {
75
81
  return file.url;
76
82
  }
@@ -84,32 +90,37 @@ function fileToXaiImageUrl(file: VideoModelV4File): string {
84
90
 
85
91
  // Resolves the reference images for R2V generation. First-class
86
92
  // `inputReferences` win over the legacy `referenceImageUrls` provider option.
87
- // Video references are not supported for reference-to-video and are skipped
88
- // with a warning.
89
- function resolveReferenceImages(
93
+ // Non-image references (video or audio) are not supported for
94
+ // reference-to-video and are skipped with a warning.
95
+ function resolveReferences(
90
96
  options: XaiVideoCallOptions,
91
97
  xaiOptions: XaiParsedVideoModelOptions | undefined,
92
98
  warnings: SharedV4Warning[],
93
99
  ): Array<{ url: string }> | undefined {
94
100
  if (options.inputReferences != null && options.inputReferences.length > 0) {
95
- const imageReferences = options.inputReferences.filter(reference => {
96
- if (isVideoFile(reference)) {
101
+ const imageFiles: VideoModelV4File[] = [];
102
+
103
+ for (const reference of options.inputReferences) {
104
+ if (!isImageReference(reference)) {
97
105
  warnings.push({
98
106
  type: 'unsupported',
99
107
  feature: 'inputReferences',
100
- details:
101
- 'xAI reference-to-video accepts image references only. The video ' +
102
- 'reference was ignored. Use providerOptions.xai.mode ' +
103
- '"extend-video" to continue from a video.',
108
+ details: isVideoFile(reference)
109
+ ? 'xAI reference-to-video accepts image references only. The ' +
110
+ 'video reference was ignored. Use providerOptions.xai.mode ' +
111
+ '"extend-video" to continue from a video.'
112
+ : 'xAI reference-to-video accepts image references only. The ' +
113
+ 'non-image reference was ignored.',
104
114
  });
105
- return false;
115
+ continue;
106
116
  }
107
- return true;
108
- });
109
117
 
110
- return imageReferences.map(reference => ({
111
- url: fileToXaiImageUrl(reference),
112
- }));
118
+ imageFiles.push(reference);
119
+ }
120
+
121
+ return imageFiles.length > 0
122
+ ? imageFiles.map(reference => ({ url: fileToXaiUrl(reference) }))
123
+ : undefined;
113
124
  }
114
125
 
115
126
  if (
@@ -122,6 +133,11 @@ function resolveReferenceImages(
122
133
  return undefined;
123
134
  }
124
135
 
136
+ // True when at least one reference would survive as an image.
137
+ function hasImageInputReference(options: XaiVideoCallOptions): boolean {
138
+ return options.inputReferences?.some(isImageReference) ?? false;
139
+ }
140
+
125
141
  function resolveVideoMode(
126
142
  options: XaiVideoCallOptions,
127
143
  xaiOptions: XaiParsedVideoModelOptions | undefined,
@@ -138,13 +154,17 @@ function resolveVideoMode(
138
154
  // only auto-select reference-to-video when no frame images are provided.
139
155
  const hasFrameImages =
140
156
  options.frameImages != null && options.frameImages.length > 0;
141
- const hasInputReferences =
142
- options.inputReferences != null && options.inputReferences.length > 0;
143
157
  const hasLegacyReferenceUrls =
144
158
  xaiOptions?.referenceImageUrls != null &&
145
159
  xaiOptions.referenceImageUrls.length > 0;
146
160
 
147
- if (!hasFrameImages && (hasInputReferences || hasLegacyReferenceUrls)) {
161
+ // Reference-to-video needs at least one image reference. An audio-only (or
162
+ // video-only) `inputReferences` array must not flip a text- or
163
+ // image-to-video request into R2V.
164
+ if (
165
+ !hasFrameImages &&
166
+ (hasImageInputReference(options) || hasLegacyReferenceUrls)
167
+ ) {
148
168
  return 'reference-to-video';
149
169
  }
150
170
 
@@ -290,7 +310,8 @@ export class XaiVideoModel implements VideoModelV4 {
290
310
  feature: 'resolution',
291
311
  details:
292
312
  `Unrecognized resolution "${options.resolution}". ` +
293
- 'Use providerOptions.xai.resolution with "480p" or "720p" instead.',
313
+ 'Use providerOptions.xai.resolution with "480p", "720p", or ' +
314
+ '"1080p" instead.',
294
315
  });
295
316
  }
296
317
  }
@@ -320,7 +341,7 @@ export class XaiVideoModel implements VideoModelV4 {
320
341
  'continue from a video instead.',
321
342
  });
322
343
  } else {
323
- body.image = { url: fileToXaiImageUrl(startImage) };
344
+ body.image = { url: fileToXaiUrl(startImage) };
324
345
  }
325
346
  }
326
347
 
@@ -342,18 +363,58 @@ export class XaiVideoModel implements VideoModelV4 {
342
363
 
343
364
  // Reference images for R2V (reference-to-video) generation
344
365
  if (hasReferenceImages) {
345
- const referenceImages = resolveReferenceImages(
346
- options,
347
- xaiOptions,
348
- warnings,
349
- );
366
+ const referenceImages = resolveReferences(options, xaiOptions, warnings);
367
+
350
368
  if (referenceImages != null) {
351
369
  body.reference_images = referenceImages;
370
+ } else {
371
+ // Explicit R2V with no usable image references would silently send
372
+ // a plain generations request; tell the user it is no longer R2V.
373
+ warnings.push({
374
+ type: 'unsupported',
375
+ feature: 'referenceImages',
376
+ details:
377
+ 'xAI reference-to-video requires at least one image reference. ' +
378
+ 'The video will be generated without reference images.',
379
+ });
380
+ }
381
+
382
+ const referenceVoiceIds = xaiOptions?.referenceVoiceIds;
383
+ if (referenceVoiceIds != null && referenceVoiceIds.length > 0) {
384
+ body.reference_audios = referenceVoiceIds.map(voiceId => ({
385
+ voice_id: voiceId,
386
+ }));
387
+ }
388
+
389
+ // Reference-to-video is capped at 720p; downgrade a 1080p request.
390
+ if (body.resolution === '1080p') {
391
+ warnings.push({
392
+ type: 'unsupported',
393
+ feature: 'resolution',
394
+ details:
395
+ 'xAI reference-to-video is limited to 720p. The request was ' +
396
+ 'downgraded from 1080p to 720p.',
397
+ });
398
+ body.resolution = '720p';
352
399
  }
353
400
  }
354
401
 
355
- // Warn when reference images were provided but cannot be used in the
356
- // resolved mode (e.g. alongside frameImages, or in edit/extend modes).
402
+ // 1080p requires grok-imagine-video-1.5; the original grok-imagine-video
403
+ // rejects it. Warn, but send the request as the user asked.
404
+ if (body.resolution === '1080p' && this.modelId === 'grok-imagine-video') {
405
+ warnings.push({
406
+ type: 'unsupported',
407
+ feature: 'resolution',
408
+ details:
409
+ 'xAI model "grok-imagine-video" does not support 1080p. Use ' +
410
+ '"grok-imagine-video-1.5" for 1080p, or a lower resolution. The ' +
411
+ 'request was sent with 1080p.',
412
+ });
413
+ }
414
+
415
+ // Warn when references were provided but cannot be used in the resolved
416
+ // mode (e.g. alongside frameImages, in edit/extend modes, or when the
417
+ // references carried no usable image to drive reference-to-video).
357
418
  if (
358
419
  options.inputReferences != null &&
359
420
  options.inputReferences.length > 0 &&
@@ -362,9 +423,26 @@ export class XaiVideoModel implements VideoModelV4 {
362
423
  warnings.push({
363
424
  type: 'unsupported',
364
425
  feature: 'inputReferences',
426
+ details: hasImageInputReference(options)
427
+ ? 'xAI only supports inputReferences for reference-to-video ' +
428
+ 'generation. The reference images were ignored.'
429
+ : 'xAI reference-to-video requires at least one image reference. ' +
430
+ 'The references were ignored.',
431
+ });
432
+ }
433
+
434
+ // Preset reference voices only apply to reference-to-video generation.
435
+ if (
436
+ xaiOptions?.referenceVoiceIds != null &&
437
+ xaiOptions.referenceVoiceIds.length > 0 &&
438
+ !hasReferenceImages
439
+ ) {
440
+ warnings.push({
441
+ type: 'unsupported',
442
+ feature: 'referenceVoiceIds',
365
443
  details:
366
- 'xAI only supports inputReferences for reference-to-video ' +
367
- 'generation. The reference images were ignored.',
444
+ 'xAI only supports reference voices for reference-to-video ' +
445
+ 'generation. The reference voices were ignored.',
368
446
  });
369
447
  }
370
448
 
@@ -382,6 +460,7 @@ export class XaiVideoModel implements VideoModelV4 {
382
460
  'resolution',
383
461
  'videoUrl',
384
462
  'referenceImageUrls',
463
+ 'referenceVoiceIds',
385
464
  'user',
386
465
  ].includes(key)
387
466
  ) {
@@ -1 +1,4 @@
1
- export type XaiVideoModelId = 'grok-imagine-video' | (string & {});
1
+ export type XaiVideoModelId =
2
+ | 'grok-imagine-video'
3
+ | 'grok-imagine-video-1.5'
4
+ | (string & {});