@ai-sdk/xai 3.0.56 → 3.0.57

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
@@ -876,3 +876,181 @@ const { images } = await generateImage({
876
876
  | -------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------- |
877
877
  | `grok-2-image` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | <Check size={18} /> |
878
878
  | `grok-imagine-image` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | <Check size={18} /> |
879
+
880
+ ## Video Models
881
+
882
+ You can create xAI video models using the `.video()` factory method.
883
+ For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video).
884
+
885
+ This provider supports three video generation modes: text-to-video, image-to-video, and video editing.
886
+
887
+ ### Text-to-Video
888
+
889
+ Generate videos from text prompts:
890
+
891
+ ```ts
892
+ import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
893
+ import { experimental_generateVideo as generateVideo } from 'ai';
894
+
895
+ const { videos } = await generateVideo({
896
+ model: xai.video('grok-imagine-video'),
897
+ prompt: 'A chicken flying into the sunset in the style of 90s anime.',
898
+ aspectRatio: '16:9',
899
+ duration: 5,
900
+ providerOptions: {
901
+ xai: {
902
+ pollTimeoutMs: 600000, // 10 minutes
903
+ } satisfies XaiVideoModelOptions,
904
+ },
905
+ });
906
+ ```
907
+
908
+ ### Image-to-Video
909
+
910
+ Generate videos using an image as the starting frame with an optional text prompt:
911
+
912
+ ```ts
913
+ import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
914
+ import { experimental_generateVideo as generateVideo } from 'ai';
915
+
916
+ const { videos } = await generateVideo({
917
+ model: xai.video('grok-imagine-video'),
918
+ prompt: {
919
+ image: 'https://example.com/start-frame.png',
920
+ text: 'The cat slowly turns its head and blinks',
921
+ },
922
+ duration: 5,
923
+ providerOptions: {
924
+ xai: {
925
+ pollTimeoutMs: 600000, // 10 minutes
926
+ } satisfies XaiVideoModelOptions,
927
+ },
928
+ });
929
+ ```
930
+
931
+ ### Video Editing
932
+
933
+ Edit an existing video using a text prompt by providing a source video URL via provider options:
934
+
935
+ ```ts
936
+ import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
937
+ import { experimental_generateVideo as generateVideo } from 'ai';
938
+
939
+ const { videos } = await generateVideo({
940
+ model: xai.video('grok-imagine-video'),
941
+ prompt: 'Give the person sunglasses and a hat',
942
+ providerOptions: {
943
+ xai: {
944
+ videoUrl: 'https://example.com/source-video.mp4',
945
+ pollTimeoutMs: 600000, // 10 minutes
946
+ } satisfies XaiVideoModelOptions,
947
+ },
948
+ });
949
+ ```
950
+
951
+ <Note>
952
+ Video editing accepts input videos up to 8.7 seconds long. The `duration`,
953
+ `aspectRatio`, and `resolution` parameters are not supported for editing - the
954
+ output matches the input video's properties (capped at 720p).
955
+ </Note>
956
+
957
+ ### Chaining and Concurrent Edits
958
+
959
+ The xAI-hosted video URL is available in `providerMetadata.xai.videoUrl`.
960
+ You can use it to chain sequential edits or branch into concurrent edits
961
+ using `Promise.all`:
962
+
963
+ ```ts
964
+ import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
965
+ import { experimental_generateVideo as generateVideo } from 'ai';
966
+
967
+ const providerOptions = {
968
+ xai: {
969
+ videoUrl: 'https://example.com/source-video.mp4',
970
+ pollTimeoutMs: 600000,
971
+ } satisfies XaiVideoModelOptions,
972
+ };
973
+
974
+ // Step 1: Apply an initial edit
975
+ const step1 = await generateVideo({
976
+ model: xai.video('grok-imagine-video'),
977
+ prompt: 'Add a party hat to the person',
978
+ providerOptions,
979
+ });
980
+
981
+ // Get the xAI-hosted URL from provider metadata
982
+ const step1VideoUrl = step1.providerMetadata?.xai?.videoUrl as string;
983
+
984
+ // Step 2: Apply two more edits concurrently, building on step 1
985
+ const [withSunglasses, withScarf] = await Promise.all([
986
+ generateVideo({
987
+ model: xai.video('grok-imagine-video'),
988
+ prompt: 'Add sunglasses',
989
+ providerOptions: {
990
+ xai: { videoUrl: step1VideoUrl, pollTimeoutMs: 600000 },
991
+ },
992
+ }),
993
+ generateVideo({
994
+ model: xai.video('grok-imagine-video'),
995
+ prompt: 'Add a scarf',
996
+ providerOptions: {
997
+ xai: { videoUrl: step1VideoUrl, pollTimeoutMs: 600000 },
998
+ },
999
+ }),
1000
+ ]);
1001
+ ```
1002
+
1003
+ ### Video Provider Options
1004
+
1005
+ The following provider options are available via `providerOptions.xai`.
1006
+ You can validate the provider options using the `XaiVideoModelOptions` type.
1007
+
1008
+ - **pollIntervalMs** _number_
1009
+
1010
+ Polling interval in milliseconds for checking task status. Defaults to 5000.
1011
+
1012
+ - **pollTimeoutMs** _number_
1013
+
1014
+ Maximum wait time in milliseconds for video generation. Defaults to 600000 (10 minutes).
1015
+
1016
+ - **resolution** _'480p' | '720p'_
1017
+
1018
+ Video resolution. When using the SDK's standard `resolution` parameter,
1019
+ `1280x720` maps to `720p` and `854x480` maps to `480p`.
1020
+ Use this provider option to pass the native format directly.
1021
+
1022
+ - **videoUrl** _string_
1023
+
1024
+ URL of a source video for video editing. When provided, the prompt is used
1025
+ to describe the desired edits to the video.
1026
+
1027
+ <Note>
1028
+ Video generation is an asynchronous process that can take several minutes.
1029
+ Consider setting `pollTimeoutMs` to at least 10 minutes (600000ms) for
1030
+ reliable operation. Generated video URLs are ephemeral and should be
1031
+ downloaded promptly.
1032
+ </Note>
1033
+
1034
+ ### Aspect Ratio and Resolution
1035
+
1036
+ For **text-to-video**, you can specify both `aspectRatio` and `resolution`.
1037
+ The default aspect ratio is `16:9` and the default resolution is `480p`.
1038
+
1039
+ For **image-to-video**, the output defaults to the input image's aspect ratio.
1040
+ If you specify `aspectRatio`, it will override this and stretch the image to the
1041
+ desired ratio.
1042
+
1043
+ For **video editing**, the output matches the input video's aspect ratio and
1044
+ resolution. Custom `duration`, `aspectRatio`, and `resolution` are not
1045
+ supported - the output resolution is capped at 720p (e.g., a 1080p input
1046
+ will be downsized to 720p).
1047
+
1048
+ ### Video Model Capabilities
1049
+
1050
+ | Model | Duration | Aspect Ratios | Resolution | Image-to-Video | Video Editing |
1051
+ | -------------------- | -------- | ------------------------------------------------- | -------------- | ------------------- | ------------------- |
1052
+ | `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} /> |
1053
+
1054
+ <Note>
1055
+ You can also pass any available provider model ID as a string if needed.
1056
+ </Note>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/xai",
3
- "version": "3.0.56",
3
+ "version": "3.0.57",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -29,8 +29,8 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@ai-sdk/openai-compatible": "2.0.30",
33
32
  "@ai-sdk/provider-utils": "4.0.15",
33
+ "@ai-sdk/openai-compatible": "2.0.30",
34
34
  "@ai-sdk/provider": "3.0.8"
35
35
  },
36
36
  "devDependencies": {
package/src/index.ts CHANGED
@@ -14,6 +14,12 @@ export type {
14
14
  /** @deprecated Use `XaiImageModelOptions` instead. */
15
15
  XaiImageModelOptions as XaiImageProviderOptions,
16
16
  } from './xai-image-options';
17
+ export type { XaiVideoModelId } from './xai-video-settings';
18
+ export type {
19
+ XaiVideoModelOptions,
20
+ /** @deprecated Use `XaiVideoModelOptions` instead. */
21
+ XaiVideoModelOptions as XaiVideoProviderOptions,
22
+ } from './xai-video-options';
17
23
  export { createXai, xai } from './xai-provider';
18
24
  export type { XaiProvider, XaiProviderSettings } from './xai-provider';
19
25
  export {
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type Experimental_VideoModelV3,
2
3
  ImageModelV3,
3
4
  LanguageModelV3,
4
5
  NoSuchModelError,
@@ -19,6 +20,8 @@ import { XaiResponsesLanguageModel } from './responses/xai-responses-language-mo
19
20
  import { XaiResponsesModelId } from './responses/xai-responses-options';
20
21
  import { xaiTools } from './tool';
21
22
  import { VERSION } from './version';
23
+ import { XaiVideoModel } from './xai-video-model';
24
+ import { XaiVideoModelId } from './xai-video-settings';
22
25
 
23
26
  export interface XaiProvider extends ProviderV3 {
24
27
  /**
@@ -51,6 +54,16 @@ export interface XaiProvider extends ProviderV3 {
51
54
  */
52
55
  imageModel(modelId: XaiImageModelId): ImageModelV3;
53
56
 
57
+ /**
58
+ * Creates an Xai video model for video generation.
59
+ */
60
+ video(modelId: XaiVideoModelId): Experimental_VideoModelV3;
61
+
62
+ /**
63
+ * Creates an Xai video model for video generation.
64
+ */
65
+ videoModel(modelId: XaiVideoModelId): Experimental_VideoModelV3;
66
+
54
67
  /**
55
68
  * Server-side agentic tools for use with the responses API.
56
69
  */
@@ -131,6 +144,15 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
131
144
  });
132
145
  };
133
146
 
147
+ const createVideoModel = (modelId: XaiVideoModelId) => {
148
+ return new XaiVideoModel(modelId, {
149
+ provider: 'xai.video',
150
+ baseURL,
151
+ headers: getHeaders,
152
+ fetch: options.fetch,
153
+ });
154
+ };
155
+
134
156
  const provider = (modelId: XaiChatModelId) =>
135
157
  createChatLanguageModel(modelId);
136
158
 
@@ -144,6 +166,8 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
144
166
  provider.textEmbeddingModel = provider.embeddingModel;
145
167
  provider.imageModel = createImageModel;
146
168
  provider.image = createImageModel;
169
+ provider.videoModel = createVideoModel;
170
+ provider.video = createVideoModel;
147
171
  provider.tools = xaiTools;
148
172
 
149
173
  return provider;
@@ -0,0 +1,302 @@
1
+ import {
2
+ AISDKError,
3
+ type Experimental_VideoModelV3,
4
+ type SharedV3Warning,
5
+ } from '@ai-sdk/provider';
6
+ import {
7
+ combineHeaders,
8
+ convertUint8ArrayToBase64,
9
+ createJsonResponseHandler,
10
+ delay,
11
+ type FetchFunction,
12
+ getFromApi,
13
+ parseProviderOptions,
14
+ postJsonToApi,
15
+ } from '@ai-sdk/provider-utils';
16
+ import { z } from 'zod/v4';
17
+ import { xaiFailedResponseHandler } from './xai-error';
18
+ import {
19
+ type XaiVideoModelOptions,
20
+ xaiVideoModelOptionsSchema,
21
+ } from './xai-video-options';
22
+ import type { XaiVideoModelId } from './xai-video-settings';
23
+
24
+ interface XaiVideoModelConfig {
25
+ provider: string;
26
+ baseURL: string | undefined;
27
+ headers: () => Record<string, string | undefined>;
28
+ fetch?: FetchFunction;
29
+ _internal?: {
30
+ currentDate?: () => Date;
31
+ };
32
+ }
33
+
34
+ const RESOLUTION_MAP: Record<string, string> = {
35
+ '1280x720': '720p',
36
+ '854x480': '480p',
37
+ '640x480': '480p',
38
+ };
39
+
40
+ export class XaiVideoModel implements Experimental_VideoModelV3 {
41
+ readonly specificationVersion = 'v3';
42
+ readonly maxVideosPerCall = 1;
43
+
44
+ get provider(): string {
45
+ return this.config.provider;
46
+ }
47
+
48
+ constructor(
49
+ readonly modelId: XaiVideoModelId,
50
+ private config: XaiVideoModelConfig,
51
+ ) {}
52
+
53
+ async doGenerate(
54
+ options: Parameters<Experimental_VideoModelV3['doGenerate']>[0],
55
+ ): Promise<Awaited<ReturnType<Experimental_VideoModelV3['doGenerate']>>> {
56
+ const currentDate = this.config._internal?.currentDate?.() ?? new Date();
57
+ const warnings: SharedV3Warning[] = [];
58
+
59
+ const xaiOptions = (await parseProviderOptions({
60
+ provider: 'xai',
61
+ providerOptions: options.providerOptions,
62
+ schema: xaiVideoModelOptionsSchema,
63
+ })) as XaiVideoModelOptions | undefined;
64
+
65
+ const isEdit = xaiOptions?.videoUrl != null;
66
+
67
+ if (options.fps != null) {
68
+ warnings.push({
69
+ type: 'unsupported',
70
+ feature: 'fps',
71
+ details: 'xAI video models do not support custom FPS.',
72
+ });
73
+ }
74
+
75
+ if (options.seed != null) {
76
+ warnings.push({
77
+ type: 'unsupported',
78
+ feature: 'seed',
79
+ details: 'xAI video models do not support seed.',
80
+ });
81
+ }
82
+
83
+ if (options.n != null && options.n > 1) {
84
+ warnings.push({
85
+ type: 'unsupported',
86
+ feature: 'n',
87
+ details:
88
+ 'xAI video models do not support generating multiple videos per call. ' +
89
+ 'Only 1 video will be generated.',
90
+ });
91
+ }
92
+
93
+ if (isEdit && options.duration != null) {
94
+ warnings.push({
95
+ type: 'unsupported',
96
+ feature: 'duration',
97
+ details: 'xAI video editing does not support custom duration.',
98
+ });
99
+ }
100
+
101
+ if (isEdit && options.aspectRatio != null) {
102
+ warnings.push({
103
+ type: 'unsupported',
104
+ feature: 'aspectRatio',
105
+ details: 'xAI video editing does not support custom aspect ratio.',
106
+ });
107
+ }
108
+
109
+ if (
110
+ isEdit &&
111
+ (xaiOptions?.resolution != null || options.resolution != null)
112
+ ) {
113
+ warnings.push({
114
+ type: 'unsupported',
115
+ feature: 'resolution',
116
+ details: 'xAI video editing does not support custom resolution.',
117
+ });
118
+ }
119
+
120
+ const body: Record<string, unknown> = {
121
+ model: this.modelId,
122
+ prompt: options.prompt,
123
+ };
124
+
125
+ if (!isEdit && options.duration != null) {
126
+ body.duration = options.duration;
127
+ }
128
+
129
+ if (!isEdit && options.aspectRatio != null) {
130
+ body.aspect_ratio = options.aspectRatio;
131
+ }
132
+
133
+ if (!isEdit && xaiOptions?.resolution != null) {
134
+ body.resolution = xaiOptions.resolution;
135
+ } else if (!isEdit && options.resolution != null) {
136
+ const mapped = RESOLUTION_MAP[options.resolution];
137
+ if (mapped != null) {
138
+ body.resolution = mapped;
139
+ } else {
140
+ warnings.push({
141
+ type: 'unsupported',
142
+ feature: 'resolution',
143
+ details:
144
+ `Unrecognized resolution "${options.resolution}". ` +
145
+ 'Use providerOptions.xai.resolution with "480p" or "720p" instead.',
146
+ });
147
+ }
148
+ }
149
+
150
+ // Video editing: pass source video URL (nested object like image)
151
+ if (xaiOptions?.videoUrl != null) {
152
+ body.video = { url: xaiOptions.videoUrl };
153
+ }
154
+
155
+ // Image-to-video: convert SDK image to nested image object
156
+ if (options.image != null) {
157
+ if (options.image.type === 'url') {
158
+ body.image = { url: options.image.url };
159
+ } else {
160
+ const base64Data =
161
+ typeof options.image.data === 'string'
162
+ ? options.image.data
163
+ : convertUint8ArrayToBase64(options.image.data);
164
+ body.image = {
165
+ url: `data:${options.image.mediaType};base64,${base64Data}`,
166
+ };
167
+ }
168
+ }
169
+
170
+ if (xaiOptions != null) {
171
+ for (const [key, value] of Object.entries(xaiOptions)) {
172
+ if (
173
+ ![
174
+ 'pollIntervalMs',
175
+ 'pollTimeoutMs',
176
+ 'resolution',
177
+ 'videoUrl',
178
+ ].includes(key)
179
+ ) {
180
+ body[key] = value;
181
+ }
182
+ }
183
+ }
184
+
185
+ const baseURL = this.config.baseURL ?? 'https://api.x.ai/v1';
186
+
187
+ // Step 1: Create video generation/edit request
188
+ const { value: createResponse } = await postJsonToApi({
189
+ url: `${baseURL}/videos/${isEdit ? 'edits' : 'generations'}`,
190
+ headers: combineHeaders(this.config.headers(), options.headers),
191
+ body,
192
+ failedResponseHandler: xaiFailedResponseHandler,
193
+ successfulResponseHandler: createJsonResponseHandler(
194
+ xaiCreateVideoResponseSchema,
195
+ ),
196
+ abortSignal: options.abortSignal,
197
+ fetch: this.config.fetch,
198
+ });
199
+
200
+ const requestId = createResponse.request_id;
201
+ if (!requestId) {
202
+ throw new AISDKError({
203
+ name: 'XAI_VIDEO_GENERATION_ERROR',
204
+ message: `No request_id returned from xAI API. Response: ${JSON.stringify(createResponse)}`,
205
+ });
206
+ }
207
+
208
+ // Step 2: Poll for completion
209
+ const pollIntervalMs = xaiOptions?.pollIntervalMs ?? 5000;
210
+ const pollTimeoutMs = xaiOptions?.pollTimeoutMs ?? 600000;
211
+ const startTime = Date.now();
212
+ let responseHeaders: Record<string, string> | undefined;
213
+
214
+ while (true) {
215
+ await delay(pollIntervalMs, { abortSignal: options.abortSignal });
216
+
217
+ if (Date.now() - startTime > pollTimeoutMs) {
218
+ throw new AISDKError({
219
+ name: 'XAI_VIDEO_GENERATION_TIMEOUT',
220
+ message: `Video generation timed out after ${pollTimeoutMs}ms`,
221
+ });
222
+ }
223
+
224
+ const { value: statusResponse, responseHeaders: pollHeaders } =
225
+ await getFromApi({
226
+ url: `${baseURL}/videos/${requestId}`,
227
+ headers: combineHeaders(this.config.headers(), options.headers),
228
+ successfulResponseHandler: createJsonResponseHandler(
229
+ xaiVideoStatusResponseSchema,
230
+ ),
231
+ failedResponseHandler: xaiFailedResponseHandler,
232
+ abortSignal: options.abortSignal,
233
+ fetch: this.config.fetch,
234
+ });
235
+
236
+ responseHeaders = pollHeaders;
237
+
238
+ if (
239
+ statusResponse.status === 'done' ||
240
+ (statusResponse.status == null && statusResponse.video?.url)
241
+ ) {
242
+ if (!statusResponse.video?.url) {
243
+ throw new AISDKError({
244
+ name: 'XAI_VIDEO_GENERATION_ERROR',
245
+ message:
246
+ 'Video generation completed but no video URL was returned.',
247
+ });
248
+ }
249
+
250
+ return {
251
+ videos: [
252
+ {
253
+ type: 'url' as const,
254
+ url: statusResponse.video.url,
255
+ mediaType: 'video/mp4',
256
+ },
257
+ ],
258
+ warnings,
259
+ response: {
260
+ timestamp: currentDate,
261
+ modelId: this.modelId,
262
+ headers: responseHeaders,
263
+ },
264
+ providerMetadata: {
265
+ xai: {
266
+ requestId,
267
+ videoUrl: statusResponse.video.url,
268
+ ...(statusResponse.video.duration != null
269
+ ? { duration: statusResponse.video.duration }
270
+ : {}),
271
+ },
272
+ },
273
+ };
274
+ }
275
+
276
+ if (statusResponse.status === 'expired') {
277
+ throw new AISDKError({
278
+ name: 'XAI_VIDEO_GENERATION_EXPIRED',
279
+ message: 'Video generation request expired.',
280
+ });
281
+ }
282
+
283
+ // 'pending' → continue polling
284
+ }
285
+ }
286
+ }
287
+
288
+ const xaiCreateVideoResponseSchema = z.object({
289
+ request_id: z.string().nullish(),
290
+ });
291
+
292
+ const xaiVideoStatusResponseSchema = z.object({
293
+ status: z.string().nullish(),
294
+ video: z
295
+ .object({
296
+ url: z.string(),
297
+ duration: z.number().nullish(),
298
+ respect_moderation: z.boolean().nullish(),
299
+ })
300
+ .nullish(),
301
+ model: z.string().nullish(),
302
+ });
@@ -0,0 +1,23 @@
1
+ import { lazySchema, zodSchema } from '@ai-sdk/provider-utils';
2
+ import { z } from 'zod/v4';
3
+
4
+ export type XaiVideoModelOptions = {
5
+ pollIntervalMs?: number | null;
6
+ pollTimeoutMs?: number | null;
7
+ resolution?: '480p' | '720p' | null;
8
+ videoUrl?: string | null;
9
+ [key: string]: unknown;
10
+ };
11
+
12
+ export const xaiVideoModelOptionsSchema = lazySchema(() =>
13
+ zodSchema(
14
+ z
15
+ .object({
16
+ pollIntervalMs: z.number().positive().nullish(),
17
+ pollTimeoutMs: z.number().positive().nullish(),
18
+ resolution: z.enum(['480p', '720p']).nullish(),
19
+ videoUrl: z.string().nullish(),
20
+ })
21
+ .passthrough(),
22
+ ),
23
+ );
@@ -0,0 +1 @@
1
+ export type XaiVideoModelId = 'grok-imagine-video' | (string & {});