@ai-sdk/xai 3.0.119 → 3.0.121

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
@@ -160,6 +160,37 @@ The following optional provider options are available for xAI chat models:
160
160
 
161
161
  Whether to enable parallel function calling during tool use. When true, the model can call multiple functions in parallel. When false, the model will call functions sequentially. Defaults to `true`.
162
162
 
163
+ ### Priority Processing
164
+
165
+ `providerOptions.xai.serviceTier` requests higher scheduling priority, which
166
+ typically lowers time-to-first-token and speeds up inter-token latency. This
167
+ works for both the Responses API (default) and the Chat Completions API
168
+ (`xai.chat()`).
169
+
170
+ ```ts
171
+ import { xai } from '@ai-sdk/xai';
172
+ import { generateText } from 'ai';
173
+
174
+ const { providerMetadata } = await generateText({
175
+ model: xai('grok-4.6'),
176
+ prompt: 'Explain quantum entanglement.',
177
+ providerOptions: {
178
+ xai: { serviceTier: 'priority' },
179
+ },
180
+ });
181
+
182
+ // 'priority' when the request was served at the priority tier,
183
+ // 'default' when priority capacity was unavailable.
184
+ console.log(providerMetadata?.xai?.serviceTier);
185
+ ```
186
+
187
+ Priority requests are billed at a premium per-token rate, and xAI only charges
188
+ that rate when the response confirms the priority tier — so read the applied
189
+ tier back from `providerMetadata.xai.serviceTier` rather than assuming the
190
+ request you sent is the tier you got. Omitting the option is equivalent to
191
+ `'default'`. See xAI's [priority processing
192
+ docs](https://docs.x.ai/developers/advanced-api-usage/priority-processing).
193
+
163
194
  ## Responses API (Agentic Tools)
164
195
 
165
196
  You can use the xAI Responses API with the `xai.responses(modelId)` factory method for server-side agentic tool calling. This enables the model to autonomously orchestrate tool calls and research on xAI's servers.
@@ -537,6 +568,10 @@ The following provider options are available:
537
568
 
538
569
  The ID of the previous response from the model. You can use it to continue a conversation.
539
570
 
571
+ - **serviceTier** _'default' | 'priority'_
572
+
573
+ Scheduling priority for the request. `'priority'` buys lower time-to-first-token and faster inter-token latency at a premium per-token price. The tier xAI actually applied comes back on `providerMetadata.xai.serviceTier`, and is `'default'` when priority capacity was unavailable. See [Priority Processing](https://docs.x.ai/developers/advanced-api-usage/priority-processing).
574
+
540
575
  <Note>
541
576
  The Responses API only supports server-side tools. You cannot mix server-side
542
577
  tools with client-side function tools in the same request.
@@ -1238,12 +1273,64 @@ const { video } = await generateVideo({
1238
1273
  });
1239
1274
  ```
1240
1275
 
1276
+ `inputReferences` accepts image references only. A reference with a non-image
1277
+ media type (for example a video or audio clip) is ignored with a warning, and a
1278
+ reference with no media type is treated as an image. If no image reference
1279
+ remains, reference-to-video is not selected and no reference images are sent.
1280
+
1281
+ #### Reference Audio
1282
+
1283
+ Reference-to-video can also give the subject a voice. `referenceVoiceIds` takes
1284
+ up to 3 xAI **preset** voice ids — you cannot upload your own audio clips.
1285
+ Reference the voices from the prompt with `<AUDIO_0>`, `<AUDIO_1>`, and
1286
+ `<AUDIO_2>`, in the order the voices are passed.
1287
+
1288
+ ```ts
1289
+ import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
1290
+ import { experimental_generateVideo as generateVideo } from 'ai';
1291
+
1292
+ const { video } = await generateVideo({
1293
+ model: xai.video('grok-imagine-video-1.5'),
1294
+ prompt:
1295
+ 'The person from <IMAGE_1> stands in the room from <IMAGE_2> and speaks ' +
1296
+ 'to the camera with the voice from <AUDIO_0>.',
1297
+ aspectRatio: '9:16',
1298
+ duration: 10,
1299
+ providerOptions: {
1300
+ xai: {
1301
+ mode: 'reference-to-video',
1302
+ referenceImageUrls: [
1303
+ 'https://example.com/person.png',
1304
+ 'https://example.com/room.png',
1305
+ ],
1306
+ referenceVoiceIds: ['eve'],
1307
+ resolution: '720p',
1308
+ pollTimeoutMs: 600000,
1309
+ } satisfies XaiVideoModelOptions,
1310
+ },
1311
+ });
1312
+ ```
1313
+
1314
+ Valid voice ids come from the xAI
1315
+ [text-to-speech voice roster](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech#voices).
1316
+ Ids are case-insensitive, and an unknown id returns a `400` response listing the
1317
+ available voices. `referenceVoiceIds` is ignored with a warning when the
1318
+ resolved operation is not reference-to-video.
1319
+
1320
+ <Note>
1321
+ xAI documents reference audio as available in the United States only, for
1322
+ trusted partners. Requests from accounts without access are rejected by the
1323
+ xAI API.
1324
+ </Note>
1325
+
1241
1326
  <Note>
1242
1327
  Reference-to-video supports `duration`, `aspectRatio`, and `resolution`. Use
1243
1328
  `mode` to select the operation — each mode is mutually exclusive. When both
1244
1329
  are provided, `frameImages` takes precedence over `inputReferences`, and
1245
1330
  `inputReferences` takes precedence over the legacy `referenceImageUrls`
1246
- provider option. Reference-to-video requires the `grok-imagine-video` model.
1331
+ provider option. Reference-to-video is supported by the `grok-imagine-video`
1332
+ and `grok-imagine-video-1.5` models; native 1080p requires
1333
+ `grok-imagine-video-1.5`.
1247
1334
  </Note>
1248
1335
 
1249
1336
  ### Video Provider Options
@@ -1259,11 +1346,14 @@ You can validate the provider options using the `XaiVideoModelOptions` type.
1259
1346
 
1260
1347
  Maximum wait time in milliseconds for video generation. Defaults to 600000 (10 minutes).
1261
1348
 
1262
- - **resolution** _'480p' | '720p'_
1349
+ - **resolution** _'480p' | '720p' | '1080p'_
1263
1350
 
1264
1351
  Video resolution. When using the SDK's standard `resolution` parameter,
1265
- `1280x720` maps to `720p` and `854x480` maps to `480p`.
1266
- Use this provider option to pass the native format directly.
1352
+ `1920x1080` maps to `1080p`, `1280x720` maps to `720p`, and `854x480` maps
1353
+ to `480p`. Use this provider option to pass the native format directly.
1354
+ `1080p` requires the `grok-imagine-video-1.5` model and is available for
1355
+ text-to-video and image-to-video; reference-to-video is capped at `720p`
1356
+ (a `1080p` request is downgraded with a warning).
1267
1357
 
1268
1358
  - **user** _string_
1269
1359
 
@@ -1295,6 +1385,17 @@ You can validate the provider options using the `XaiVideoModelOptions` type.
1295
1385
  `<IMAGE_1>`, `<IMAGE_2>`, etc. in the prompt to reference specific
1296
1386
  images. Used with `mode: 'reference-to-video'`.
1297
1387
 
1388
+ - **referenceVoiceIds** _string[]_
1389
+
1390
+ Up to 3 xAI preset voice ids that give the subject a voice in
1391
+ reference-to-video (R2V) generation. Preset voices only — audio clips
1392
+ cannot be uploaded. Ids are case-insensitive and come from the
1393
+ [text-to-speech voice roster](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech#voices);
1394
+ an unknown id returns a `400` listing the available voices. Use `<AUDIO_0>`,
1395
+ `<AUDIO_1>`, and `<AUDIO_2>` tags in the prompt to reference the voices.
1396
+ Ignored with a warning outside reference-to-video. Reference audio is
1397
+ documented as US-only and limited to trusted partners.
1398
+
1298
1399
  <Note>
1299
1400
  Video generation is an asynchronous process that can take several minutes.
1300
1401
  Consider setting `pollTimeoutMs` to at least 10 minutes (600000ms) for
@@ -1305,7 +1406,9 @@ You can validate the provider options using the `XaiVideoModelOptions` type.
1305
1406
  ### Aspect Ratio and Resolution
1306
1407
 
1307
1408
  For **text-to-video**, you can specify both `aspectRatio` and `resolution`.
1308
- The default aspect ratio is `16:9` and the default resolution is `480p`.
1409
+ The default aspect ratio is `16:9` and the default resolution is `480p`. The
1410
+ `grok-imagine-video-1.5` model additionally supports native `1080p` for
1411
+ text-to-video and image-to-video.
1309
1412
 
1310
1413
  For **image-to-video**, the output defaults to the input image's aspect ratio.
1311
1414
  If you specify `aspectRatio`, it will override this and stretch the image to the
@@ -1321,13 +1424,18 @@ from the source video. `duration` is supported and controls only the
1321
1424
  extension length.
1322
1425
 
1323
1426
  For **reference-to-video (R2V)**, you can specify `duration`, `aspectRatio`,
1324
- and `resolution` just like text-to-video.
1427
+ and `resolution`. Unlike text-to-video, R2V is capped at `720p` — a `1080p`
1428
+ request is downgraded to `720p` with a warning.
1325
1429
 
1326
1430
  ### Video Model Capabilities
1327
1431
 
1328
- | Model | Duration | Aspect Ratios | Resolution | Image-to-Video | Editing | Extension | R2V |
1329
- | -------------------- | -------- | ------------------------------------------------- | -------------- | ------------------- | ------------------- | ------------------- | ------------------- |
1330
- | `grok-imagine-video` | 1–15s | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3` | `480p`, `720p` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
1432
+ | Model | Duration | Aspect Ratios | Resolution | Image-to-Video | Editing | Extension | R2V |
1433
+ | ------------------------ | -------- | ------------------------------------------------- | ------------------------- | ------------------- | ------------------- | ------------------- | ------------------- |
1434
+ | `grok-imagine-video` | 1–15s | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3` | `480p`, `720p` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
1435
+ | `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 size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
1436
+
1437
+ \* Native `1080p` applies to text-to-video and image-to-video. Reference-to-video
1438
+ is capped at `720p` — a `1080p` request is downgraded with a warning.
1331
1439
 
1332
1440
  <Note>
1333
1441
  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": "3.0.119",
3
+ "version": "3.0.121",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -274,6 +274,7 @@ export const xaiResponsesResponseSchema = z.object({
274
274
  output: z.array(outputItemSchema),
275
275
  usage: xaiResponsesUsageSchema.nullish(),
276
276
  status: z.string(),
277
+ service_tier: z.string().nullish(),
277
278
  });
278
279
 
279
280
  export const xaiResponsesChunkSchema = z.union([
@@ -543,6 +544,7 @@ export const xaiResponsesChunkSchema = z.union([
543
544
  response: z.object({
544
545
  incomplete_details: z.object({ reason: z.string() }).nullish(),
545
546
  usage: xaiResponsesUsageSchema.nullish(),
547
+ service_tier: z.string().nullish(),
546
548
  }),
547
549
  }),
548
550
  z.object({
@@ -197,6 +197,9 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
197
197
  ...(options.previousResponseId != null && {
198
198
  previous_response_id: options.previousResponseId,
199
199
  }),
200
+ ...(options.serviceTier != null && {
201
+ service_tier: options.serviceTier,
202
+ }),
200
203
  };
201
204
 
202
205
  if (xaiTools && xaiTools.length > 0) {
@@ -432,6 +435,11 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
432
435
  inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
433
436
  outputTokens: { total: 0, text: 0, reasoning: 0 },
434
437
  },
438
+ ...(response.service_tier != null && {
439
+ providerMetadata: {
440
+ xai: { serviceTier: response.service_tier },
441
+ },
442
+ }),
435
443
  request: { body },
436
444
  response: {
437
445
  ...getResponseMetadata(response),
@@ -477,6 +485,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
477
485
  };
478
486
  let hasFunctionCall = false;
479
487
  let usage: LanguageModelV3Usage | undefined = undefined;
488
+ let serviceTier: string | undefined = undefined;
480
489
  let isFirstChunk = true;
481
490
  const contentBlocks: Record<string, { type: 'text' }> = {};
482
491
  const seenToolCalls = new Set<string>();
@@ -670,6 +679,8 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
670
679
  usage = convertXaiResponsesUsage(response.usage);
671
680
  }
672
681
 
682
+ serviceTier = response.service_tier ?? undefined;
683
+
673
684
  if (event.type === 'response.incomplete') {
674
685
  const reason =
675
686
  'incomplete_details' in response
@@ -1022,6 +1033,9 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
1022
1033
  },
1023
1034
  outputTokens: { total: 0, text: 0, reasoning: 0 },
1024
1035
  },
1036
+ ...(serviceTier != null && {
1037
+ providerMetadata: { xai: { serviceTier } },
1038
+ }),
1025
1039
  });
1026
1040
  },
1027
1041
  }),
@@ -26,6 +26,7 @@ export const xaiLanguageModelResponsesOptions = z.object({
26
26
  .optional(),
27
27
  logprobs: z.boolean().optional(),
28
28
  topLogprobs: z.number().int().min(0).max(8).optional(),
29
+ serviceTier: z.enum(['default', 'priority']).optional(),
29
30
  /**
30
31
  * Whether to store the input message(s) and model response for later retrieval.
31
32
  * Must be set to `false` for teams with Zero Data Retention (ZDR) enabled,
@@ -135,6 +135,9 @@ export class XaiChatLanguageModel implements LanguageModelV3 {
135
135
  seed,
136
136
  reasoning_effort: options.reasoningEffort,
137
137
 
138
+ // scheduling priority
139
+ service_tier: options.serviceTier,
140
+
138
141
  // parallel function calling
139
142
  parallel_function_calling: options.parallel_function_calling,
140
143
 
@@ -301,6 +304,11 @@ export class XaiChatLanguageModel implements LanguageModelV3 {
301
304
  inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
302
305
  outputTokens: { total: 0, text: 0, reasoning: 0 },
303
306
  },
307
+ ...(response.service_tier != null && {
308
+ providerMetadata: {
309
+ xai: { serviceTier: response.service_tier },
310
+ },
311
+ }),
304
312
  request: { body },
305
313
  response: {
306
314
  ...getResponseMetadata(response),
@@ -380,6 +388,7 @@ export class XaiChatLanguageModel implements LanguageModelV3 {
380
388
  raw: undefined,
381
389
  };
382
390
  let usage: LanguageModelV3Usage | undefined = undefined;
391
+ let serviceTier: string | undefined = undefined;
383
392
  let isFirstChunk = true;
384
393
  const contentBlocks: Record<
385
394
  string,
@@ -439,6 +448,11 @@ export class XaiChatLanguageModel implements LanguageModelV3 {
439
448
  usage = convertXaiChatUsage(value.usage);
440
449
  }
441
450
 
451
+ // the applied tier is repeated on every chunk; keep the latest
452
+ if (value.service_tier != null) {
453
+ serviceTier = value.service_tier;
454
+ }
455
+
442
456
  const choice = value.choices[0];
443
457
 
444
458
  // update finish reason if present
@@ -598,6 +612,9 @@ export class XaiChatLanguageModel implements LanguageModelV3 {
598
612
  },
599
613
  outputTokens: { total: 0, text: 0, reasoning: 0 },
600
614
  },
615
+ ...(serviceTier != null && {
616
+ providerMetadata: { xai: { serviceTier } },
617
+ }),
601
618
  });
602
619
  },
603
620
  }),
@@ -665,6 +682,7 @@ const xaiChatResponseSchema = z.object({
665
682
  object: z.literal('chat.completion').nullish(),
666
683
  usage: xaiUsageSchema.nullish(),
667
684
  citations: z.array(z.string().url()).nullish(),
685
+ service_tier: z.string().nullish(),
668
686
  code: z.string().nullish(),
669
687
  error: z.string().nullish(),
670
688
  });
@@ -698,6 +716,7 @@ const xaiChatChunkSchema = z.object({
698
716
  ),
699
717
  usage: xaiUsageSchema.nullish(),
700
718
  citations: z.array(z.string().url()).nullish(),
719
+ service_tier: z.string().nullish(),
701
720
  });
702
721
 
703
722
  const xaiStreamErrorSchema = z.object({
@@ -59,6 +59,8 @@ export const xaiLanguageModelChatOptions = z.object({
59
59
  logprobs: z.boolean().optional(),
60
60
  topLogprobs: z.number().int().min(0).max(8).optional(),
61
61
 
62
+ serviceTier: z.enum(['default', 'priority']).optional(),
63
+
62
64
  /**
63
65
  * Whether to enable parallel function calling during tool use.
64
66
  * When true, the model can call multiple functions in parallel.
@@ -37,6 +37,7 @@ interface XaiVideoModelConfig {
37
37
  }
38
38
 
39
39
  const RESOLUTION_MAP: Record<string, string> = {
40
+ '1920x1080': '1080p',
40
41
  '1280x720': '720p',
41
42
  '854x480': '480p',
42
43
  '640x480': '480p',
@@ -74,6 +75,11 @@ function getTopLevelMediaType(mediaType: string): string {
74
75
  const isVideoFile = (file: Experimental_VideoModelV3File): boolean =>
75
76
  file.mediaType != null && getTopLevelMediaType(file.mediaType) === 'video';
76
77
 
78
+ // References without a media type (only possible for URLs) are treated as
79
+ // images, matching the legacy `referenceImageUrls` behavior.
80
+ const isImageReference = (file: Experimental_VideoModelV3File): boolean =>
81
+ file.mediaType == null || getTopLevelMediaType(file.mediaType) === 'image';
82
+
77
83
  function fileToXaiImageUrl(file: Experimental_VideoModelV3File): string {
78
84
  if (file.type === 'url') {
79
85
  return file.url;
@@ -88,32 +94,40 @@ function fileToXaiImageUrl(file: Experimental_VideoModelV3File): string {
88
94
 
89
95
  // Resolves the reference images for R2V generation. First-class
90
96
  // `inputReferences` win over the legacy `referenceImageUrls` provider option.
91
- // Video references are not supported for reference-to-video and are skipped
92
- // with a warning.
97
+ // Non-image references (video or audio) are not supported for
98
+ // reference-to-video and are skipped with a warning.
93
99
  function resolveReferenceImages(
94
100
  options: XaiVideoDoGenerateOptions,
95
101
  xaiOptions: XaiParsedVideoModelOptions | undefined,
96
102
  warnings: SharedV3Warning[],
97
103
  ): Array<{ url: string }> | undefined {
98
104
  if (options.inputReferences != null && options.inputReferences.length > 0) {
99
- const imageReferences = options.inputReferences.filter(reference => {
100
- if (isVideoFile(reference)) {
105
+ const imageFiles: Experimental_VideoModelV3File[] = [];
106
+
107
+ for (const reference of options.inputReferences) {
108
+ if (!isImageReference(reference)) {
101
109
  warnings.push({
102
110
  type: 'unsupported',
103
111
  feature: 'inputReferences',
104
- details:
105
- 'xAI reference-to-video accepts image references only. The video ' +
106
- 'reference was ignored. Use providerOptions.xai.mode ' +
107
- '"extend-video" to continue from a video.',
112
+ details: isVideoFile(reference)
113
+ ? 'xAI reference-to-video accepts image references only. The ' +
114
+ 'video reference was ignored. Use providerOptions.xai.mode ' +
115
+ '"extend-video" to continue from a video.'
116
+ : 'xAI reference-to-video accepts image references only. The ' +
117
+ 'non-image reference was ignored.',
108
118
  });
109
- return false;
119
+ continue;
110
120
  }
111
- return true;
112
- });
113
121
 
114
- return imageReferences.map(reference => ({
115
- url: fileToXaiImageUrl(reference),
116
- }));
122
+ imageFiles.push(reference);
123
+ }
124
+
125
+ // Every reference may have been filtered out (audio- or video-only input),
126
+ // so collapse an empty list to undefined rather than sending an empty
127
+ // `reference_images` array.
128
+ return imageFiles.length > 0
129
+ ? imageFiles.map(reference => ({ url: fileToXaiImageUrl(reference) }))
130
+ : undefined;
117
131
  }
118
132
 
119
133
  if (
@@ -126,6 +140,11 @@ function resolveReferenceImages(
126
140
  return undefined;
127
141
  }
128
142
 
143
+ // True when at least one reference would survive as an image.
144
+ function hasImageInputReference(options: XaiVideoDoGenerateOptions): boolean {
145
+ return options.inputReferences?.some(isImageReference) ?? false;
146
+ }
147
+
129
148
  function resolveVideoMode(
130
149
  options: XaiVideoDoGenerateOptions,
131
150
  xaiOptions: XaiParsedVideoModelOptions | undefined,
@@ -142,13 +161,17 @@ function resolveVideoMode(
142
161
  // only auto-select reference-to-video when no frame images are provided.
143
162
  const hasFrameImages =
144
163
  options.frameImages != null && options.frameImages.length > 0;
145
- const hasInputReferences =
146
- options.inputReferences != null && options.inputReferences.length > 0;
147
164
  const hasLegacyReferenceUrls =
148
165
  xaiOptions?.referenceImageUrls != null &&
149
166
  xaiOptions.referenceImageUrls.length > 0;
150
167
 
151
- if (!hasFrameImages && (hasInputReferences || hasLegacyReferenceUrls)) {
168
+ // Reference-to-video needs at least one image reference. An audio-only (or
169
+ // video-only) `inputReferences` array must not flip a text- or
170
+ // image-to-video request into R2V.
171
+ if (
172
+ !hasFrameImages &&
173
+ (hasImageInputReference(options) || hasLegacyReferenceUrls)
174
+ ) {
152
175
  return 'reference-to-video';
153
176
  }
154
177
 
@@ -289,7 +312,8 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
289
312
  feature: 'resolution',
290
313
  details:
291
314
  `Unrecognized resolution "${options.resolution}". ` +
292
- 'Use providerOptions.xai.resolution with "480p" or "720p" instead.',
315
+ 'Use providerOptions.xai.resolution with "480p", "720p", or ' +
316
+ '"1080p" instead.',
293
317
  });
294
318
  }
295
319
  }
@@ -346,13 +370,57 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
346
370
  xaiOptions,
347
371
  warnings,
348
372
  );
373
+
349
374
  if (referenceImages != null) {
350
375
  body.reference_images = referenceImages;
376
+ } else {
377
+ // Explicit R2V with no usable image references would silently send
378
+ // a plain generations request; tell the user it is no longer R2V.
379
+ warnings.push({
380
+ type: 'unsupported',
381
+ feature: 'referenceImages',
382
+ details:
383
+ 'xAI reference-to-video requires at least one image reference. ' +
384
+ 'The video will be generated without reference images.',
385
+ });
386
+ }
387
+
388
+ const referenceVoiceIds = xaiOptions?.referenceVoiceIds;
389
+ if (referenceVoiceIds != null && referenceVoiceIds.length > 0) {
390
+ body.reference_audios = referenceVoiceIds.map(voiceId => ({
391
+ voice_id: voiceId,
392
+ }));
393
+ }
394
+
395
+ // Reference-to-video is capped at 720p; downgrade a 1080p request.
396
+ if (body.resolution === '1080p') {
397
+ warnings.push({
398
+ type: 'unsupported',
399
+ feature: 'resolution',
400
+ details:
401
+ 'xAI reference-to-video is limited to 720p. The request was ' +
402
+ 'downgraded from 1080p to 720p.',
403
+ });
404
+ body.resolution = '720p';
351
405
  }
352
406
  }
353
407
 
354
- // Warn when reference images were provided but cannot be used in the
355
- // resolved mode (e.g. alongside frameImages, or in edit/extend modes).
408
+ // 1080p requires grok-imagine-video-1.5; the original grok-imagine-video
409
+ // rejects it. Warn, but send the request as the user asked.
410
+ if (body.resolution === '1080p' && this.modelId === 'grok-imagine-video') {
411
+ warnings.push({
412
+ type: 'unsupported',
413
+ feature: 'resolution',
414
+ details:
415
+ 'xAI model "grok-imagine-video" does not support 1080p. Use ' +
416
+ '"grok-imagine-video-1.5" for 1080p, or a lower resolution. The ' +
417
+ 'request was sent with 1080p.',
418
+ });
419
+ }
420
+
421
+ // Warn when references were provided but cannot be used in the resolved
422
+ // mode (e.g. alongside frameImages, in edit/extend modes, or when the
423
+ // references carried no usable image to drive reference-to-video).
356
424
  if (
357
425
  options.inputReferences != null &&
358
426
  options.inputReferences.length > 0 &&
@@ -361,9 +429,26 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
361
429
  warnings.push({
362
430
  type: 'unsupported',
363
431
  feature: 'inputReferences',
432
+ details: hasImageInputReference(options)
433
+ ? 'xAI only supports inputReferences for reference-to-video ' +
434
+ 'generation. The reference images were ignored.'
435
+ : 'xAI reference-to-video requires at least one image reference. ' +
436
+ 'The references were ignored.',
437
+ });
438
+ }
439
+
440
+ // Preset reference voices only apply to reference-to-video generation.
441
+ if (
442
+ xaiOptions?.referenceVoiceIds != null &&
443
+ xaiOptions.referenceVoiceIds.length > 0 &&
444
+ !hasReferenceImages
445
+ ) {
446
+ warnings.push({
447
+ type: 'unsupported',
448
+ feature: 'referenceVoiceIds',
364
449
  details:
365
- 'xAI only supports inputReferences for reference-to-video ' +
366
- 'generation. The reference images were ignored.',
450
+ 'xAI only supports reference voices for reference-to-video ' +
451
+ 'generation. The reference voices were ignored.',
367
452
  });
368
453
  }
369
454
 
@@ -381,6 +466,7 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
381
466
  'resolution',
382
467
  'videoUrl',
383
468
  'referenceImageUrls',
469
+ 'referenceVoiceIds',
384
470
  'user',
385
471
  ].includes(key)
386
472
  ) {
@@ -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
  /**
@@ -87,6 +95,9 @@ interface XaiLegacyReferenceToVideoOptions
87
95
  * - `'reference-to-video'` + `referenceImageUrls` -- R2V generation (`POST /v1/videos/generations`)
88
96
  * - no `mode` -- standard generation from text prompts or image input
89
97
  *
98
+ * Reference images may also come from the top-level `inputReferences` option
99
+ * instead of `referenceImageUrls`.
100
+ *
90
101
  * Runtime remains backward compatible with legacy auto-detected provider
91
102
  * options, but the public TypeScript type is intentionally explicit so editors
92
103
  * can suggest valid modes and flag invalid field combinations.
@@ -110,12 +121,17 @@ const userField = {
110
121
  user: z.string().optional(),
111
122
  };
112
123
 
124
+ const referenceVoiceIdsField = {
125
+ referenceVoiceIds: z.array(nonEmptyStringSchema).max(3).optional(),
126
+ };
127
+
113
128
  const editVideoSchema = z.object({
114
129
  ...baseFields,
115
130
  ...userField,
116
131
  mode: z.literal('edit-video'),
117
132
  videoUrl: nonEmptyStringSchema,
118
133
  referenceImageUrls: z.undefined().optional(),
134
+ referenceVoiceIds: z.undefined().optional(),
119
135
  });
120
136
 
121
137
  const extendVideoSchema = z.object({
@@ -123,11 +139,13 @@ const extendVideoSchema = z.object({
123
139
  mode: z.literal('extend-video'),
124
140
  videoUrl: nonEmptyStringSchema,
125
141
  referenceImageUrls: z.undefined().optional(),
142
+ referenceVoiceIds: z.undefined().optional(),
126
143
  });
127
144
 
128
145
  const referenceToVideoSchema = z.object({
129
146
  ...baseFields,
130
147
  ...userField,
148
+ ...referenceVoiceIdsField,
131
149
  mode: z.literal('reference-to-video'),
132
150
  referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7),
133
151
  videoUrl: z.undefined().optional(),
@@ -136,6 +154,7 @@ const referenceToVideoSchema = z.object({
136
154
  const autoDetectSchema = z.object({
137
155
  ...baseFields,
138
156
  ...userField,
157
+ ...referenceVoiceIdsField,
139
158
  mode: z.undefined().optional(),
140
159
  videoUrl: nonEmptyStringSchema.optional(),
141
160
  referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7).optional(),
@@ -153,6 +172,7 @@ const runtimeSchema = z
153
172
  mode: modeSchema.optional(),
154
173
  videoUrl: nonEmptyStringSchema.optional(),
155
174
  referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7).optional(),
175
+ referenceVoiceIds: z.array(nonEmptyStringSchema).max(3).optional(),
156
176
  user: z.string().optional(),
157
177
  ...baseFields,
158
178
  })
@@ -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 & {});