@ai-sdk/xai 4.0.0-beta.21 → 4.0.0-beta.24

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.
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type Experimental_VideoModelV4,
3
+ FilesV4,
3
4
  ImageModelV4,
4
5
  LanguageModelV4,
5
6
  NoSuchModelError,
@@ -20,6 +21,7 @@ import { XaiResponsesLanguageModel } from './responses/xai-responses-language-mo
20
21
  import { XaiResponsesModelId } from './responses/xai-responses-options';
21
22
  import { xaiTools } from './tool';
22
23
  import { VERSION } from './version';
24
+ import { XaiFiles } from './files/xai-files';
23
25
  import { XaiVideoModel } from './xai-video-model';
24
26
  import { XaiVideoModelId } from './xai-video-settings';
25
27
 
@@ -61,6 +63,11 @@ export interface XaiProvider extends ProviderV4 {
61
63
  */
62
64
  videoModel(modelId: XaiVideoModelId): Experimental_VideoModelV4;
63
65
 
66
+ /**
67
+ * Returns the xAI files interface for uploading files.
68
+ */
69
+ files(): FilesV4;
70
+
64
71
  /**
65
72
  * Server-side agentic tools for use with the responses API.
66
73
  */
@@ -150,6 +157,14 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
150
157
  });
151
158
  };
152
159
 
160
+ const createFiles = () =>
161
+ new XaiFiles({
162
+ provider: 'xai.files',
163
+ baseURL,
164
+ headers: getHeaders,
165
+ fetch: options.fetch,
166
+ });
167
+
153
168
  const provider = (modelId: XaiResponsesModelId) =>
154
169
  createResponsesLanguageModel(modelId);
155
170
 
@@ -165,6 +180,7 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
165
180
  provider.image = createImageModel;
166
181
  provider.videoModel = createVideoModel;
167
182
  provider.video = createVideoModel;
183
+ provider.files = createFiles;
168
184
  provider.tools = xaiTools;
169
185
 
170
186
  return provider;
@@ -16,7 +16,7 @@ import {
16
16
  import { z } from 'zod/v4';
17
17
  import { xaiFailedResponseHandler } from './xai-error';
18
18
  import {
19
- type XaiVideoModelOptions,
19
+ type XaiParsedVideoModelOptions,
20
20
  xaiVideoModelOptionsSchema,
21
21
  } from './xai-video-options';
22
22
  import type { XaiVideoModelId } from './xai-video-settings';
@@ -37,6 +37,27 @@ const RESOLUTION_MAP: Record<string, string> = {
37
37
  '640x480': '480p',
38
38
  };
39
39
 
40
+ function resolveVideoMode(
41
+ options: XaiParsedVideoModelOptions | undefined,
42
+ ): XaiParsedVideoModelOptions['mode'] | undefined {
43
+ if (options?.mode != null) {
44
+ return options.mode;
45
+ }
46
+
47
+ if (options?.videoUrl != null) {
48
+ return 'edit-video';
49
+ }
50
+
51
+ if (
52
+ options?.referenceImageUrls != null &&
53
+ options.referenceImageUrls.length > 0
54
+ ) {
55
+ return 'reference-to-video';
56
+ }
57
+
58
+ return undefined;
59
+ }
60
+
40
61
  export class XaiVideoModel implements Experimental_VideoModelV4 {
41
62
  readonly specificationVersion = 'v4';
42
63
  readonly maxVideosPerCall = 1;
@@ -60,9 +81,13 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
60
81
  provider: 'xai',
61
82
  providerOptions: options.providerOptions,
62
83
  schema: xaiVideoModelOptionsSchema,
63
- })) as XaiVideoModelOptions | undefined;
84
+ })) as XaiParsedVideoModelOptions | undefined;
64
85
 
65
- const isEdit = xaiOptions?.videoUrl != null;
86
+ const effectiveMode = resolveVideoMode(xaiOptions);
87
+
88
+ const isEdit = effectiveMode === 'edit-video';
89
+ const isExtension = effectiveMode === 'extend-video';
90
+ const hasReferenceImages = effectiveMode === 'reference-to-video';
66
91
 
67
92
  if (options.fps != null) {
68
93
  warnings.push({
@@ -90,6 +115,7 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
90
115
  });
91
116
  }
92
117
 
118
+ // Edit mode: duration, aspectRatio, resolution not supported
93
119
  if (isEdit && options.duration != null) {
94
120
  warnings.push({
95
121
  type: 'unsupported',
@@ -117,22 +143,46 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
117
143
  });
118
144
  }
119
145
 
146
+ // Extension mode: aspectRatio and resolution not supported
147
+ if (isExtension && options.aspectRatio != null) {
148
+ warnings.push({
149
+ type: 'unsupported',
150
+ feature: 'aspectRatio',
151
+ details: 'xAI video extension does not support custom aspect ratio.',
152
+ });
153
+ }
154
+
155
+ if (
156
+ isExtension &&
157
+ (xaiOptions?.resolution != null || options.resolution != null)
158
+ ) {
159
+ warnings.push({
160
+ type: 'unsupported',
161
+ feature: 'resolution',
162
+ details: 'xAI video extension does not support custom resolution.',
163
+ });
164
+ }
165
+
120
166
  const body: Record<string, unknown> = {
121
167
  model: this.modelId,
122
168
  prompt: options.prompt,
123
169
  };
124
170
 
125
- if (!isEdit && options.duration != null) {
171
+ const allowDuration = !isEdit;
172
+ const allowAspectRatio = !isEdit && !isExtension;
173
+ const allowResolution = !isEdit && !isExtension;
174
+
175
+ if (allowDuration && options.duration != null) {
126
176
  body.duration = options.duration;
127
177
  }
128
178
 
129
- if (!isEdit && options.aspectRatio != null) {
179
+ if (allowAspectRatio && options.aspectRatio != null) {
130
180
  body.aspect_ratio = options.aspectRatio;
131
181
  }
132
182
 
133
- if (!isEdit && xaiOptions?.resolution != null) {
183
+ if (allowResolution && xaiOptions?.resolution != null) {
134
184
  body.resolution = xaiOptions.resolution;
135
- } else if (!isEdit && options.resolution != null) {
185
+ } else if (allowResolution && options.resolution != null) {
136
186
  const mapped = RESOLUTION_MAP[options.resolution];
137
187
  if (mapped != null) {
138
188
  body.resolution = mapped;
@@ -147,12 +197,17 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
147
197
  }
148
198
  }
149
199
 
150
- // Video editing: pass source video URL (nested object like image)
151
- if (xaiOptions?.videoUrl != null) {
152
- body.video = { url: xaiOptions.videoUrl };
200
+ // Video editing: pass source video URL (nested object)
201
+ if (isEdit) {
202
+ body.video = { url: xaiOptions!.videoUrl };
153
203
  }
154
204
 
155
- // Image-to-video: convert SDK image to nested image object
205
+ // Video extension: pass source video URL (nested object)
206
+ if (isExtension) {
207
+ body.video = { url: xaiOptions!.videoUrl };
208
+ }
209
+
210
+ // Convert SDK image input to the nested xAI request image object
156
211
  if (options.image != null) {
157
212
  if (options.image.type === 'url') {
158
213
  body.image = { url: options.image.url };
@@ -167,14 +222,23 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
167
222
  }
168
223
  }
169
224
 
225
+ // Reference images for R2V (reference-to-video) generation
226
+ if (hasReferenceImages) {
227
+ body.reference_images = xaiOptions!.referenceImageUrls!.map(url => ({
228
+ url,
229
+ }));
230
+ }
231
+
170
232
  if (xaiOptions != null) {
171
233
  for (const [key, value] of Object.entries(xaiOptions)) {
172
234
  if (
173
235
  ![
236
+ 'mode',
174
237
  'pollIntervalMs',
175
238
  'pollTimeoutMs',
176
239
  'resolution',
177
240
  'videoUrl',
241
+ 'referenceImageUrls',
178
242
  ].includes(key)
179
243
  ) {
180
244
  body[key] = value;
@@ -184,9 +248,19 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
184
248
 
185
249
  const baseURL = this.config.baseURL ?? 'https://api.x.ai/v1';
186
250
 
187
- // Step 1: Create video generation/edit request
251
+ // Determine endpoint based on mode
252
+ let endpoint: string;
253
+ if (isEdit) {
254
+ endpoint = `${baseURL}/videos/edits`;
255
+ } else if (isExtension) {
256
+ endpoint = `${baseURL}/videos/extensions`;
257
+ } else {
258
+ endpoint = `${baseURL}/videos/generations`;
259
+ }
260
+
261
+ // Step 1: Create video generation/edit/extension request
188
262
  const { value: createResponse } = await postJsonToApi({
189
- url: `${baseURL}/videos/${isEdit ? 'edits' : 'generations'}`,
263
+ url: endpoint,
190
264
  headers: combineHeaders(this.config.headers(), options.headers),
191
265
  body,
192
266
  failedResponseHandler: xaiFailedResponseHandler,
@@ -279,6 +353,9 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
279
353
  ...(statusResponse.usage?.cost_in_usd_ticks != null
280
354
  ? { costInUsdTicks: statusResponse.usage.cost_in_usd_ticks }
281
355
  : {}),
356
+ ...(statusResponse.progress != null
357
+ ? { progress: statusResponse.progress }
358
+ : {}),
282
359
  },
283
360
  },
284
361
  };
@@ -291,6 +368,13 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
291
368
  });
292
369
  }
293
370
 
371
+ if (statusResponse.status === 'failed') {
372
+ throw new AISDKError({
373
+ name: 'XAI_VIDEO_GENERATION_FAILED',
374
+ message: 'Video generation failed.',
375
+ });
376
+ }
377
+
294
378
  // 'pending' → continue polling
295
379
  }
296
380
  }
@@ -315,4 +399,11 @@ const xaiVideoStatusResponseSchema = z.object({
315
399
  cost_in_usd_ticks: z.number().nullish(),
316
400
  })
317
401
  .nullish(),
402
+ progress: z.number().nullish(),
403
+ error: z
404
+ .object({
405
+ code: z.string().nullish(),
406
+ message: z.string().nullish(),
407
+ })
408
+ .nullish(),
318
409
  });
@@ -1,23 +1,145 @@
1
1
  import { lazySchema, zodSchema } from '@ai-sdk/provider-utils';
2
2
  import { z } from 'zod/v4';
3
3
 
4
- export type XaiVideoModelOptions = {
4
+ const nonEmptyStringSchema = z.string().min(1);
5
+ const resolutionSchema = z.enum(['480p', '720p']);
6
+ const modeSchema = z.enum(['edit-video', 'extend-video', 'reference-to-video']);
7
+
8
+ export type XaiVideoMode = z.infer<typeof modeSchema>;
9
+ type XaiVideoResolution = z.infer<typeof resolutionSchema>;
10
+
11
+ interface XaiVideoSharedOptions {
5
12
  pollIntervalMs?: number | null;
6
13
  pollTimeoutMs?: number | null;
7
- resolution?: '480p' | '720p' | null;
8
- videoUrl?: string | null;
9
- [key: string]: unknown;
14
+ resolution?: XaiVideoResolution | null;
15
+ }
16
+
17
+ interface XaiVideoEditModeOptions extends XaiVideoSharedOptions {
18
+ /**
19
+ * Select edit-video mode explicitly for best autocomplete and narrowing.
20
+ */
21
+ mode: 'edit-video';
22
+ /** Source video URL to edit. */
23
+ videoUrl: string;
24
+ }
25
+
26
+ interface XaiVideoExtendModeOptions extends XaiVideoSharedOptions {
27
+ /**
28
+ * Select extend-video mode explicitly for best autocomplete and narrowing.
29
+ */
30
+ mode: 'extend-video';
31
+ /** Source video URL to extend from its last frame. */
32
+ videoUrl: string;
33
+ }
34
+
35
+ interface XaiVideoReferenceToVideoOptions extends XaiVideoSharedOptions {
36
+ /**
37
+ * Select reference-to-video mode explicitly for best autocomplete and narrowing.
38
+ */
39
+ mode: 'reference-to-video';
40
+ /** Reference image URLs (1-7) for R2V generation. */
41
+ referenceImageUrls: string[];
42
+ }
43
+
44
+ interface XaiVideoGenerationOptions extends XaiVideoSharedOptions {
45
+ mode?: undefined;
46
+ videoUrl?: undefined;
47
+ referenceImageUrls?: undefined;
48
+ }
49
+
50
+ interface XaiLegacyEditVideoOptions extends XaiVideoSharedOptions {
51
+ /**
52
+ * Legacy backward-compatible shape: omitting `mode` while providing
53
+ * `videoUrl` behaves like edit-video.
54
+ */
55
+ mode?: undefined;
56
+ videoUrl: string;
57
+ }
58
+
59
+ interface XaiLegacyReferenceToVideoOptions extends XaiVideoSharedOptions {
60
+ /**
61
+ * Legacy backward-compatible shape: omitting `mode` while providing
62
+ * `referenceImageUrls` behaves like reference-to-video.
63
+ */
64
+ mode?: undefined;
65
+ referenceImageUrls: string[];
66
+ }
67
+
68
+ /**
69
+ * Provider options for xAI video generation.
70
+ *
71
+ * Use the `mode` option to select the operation:
72
+ *
73
+ * - `'edit-video'` + `videoUrl` -- video editing (`POST /v1/videos/edits`)
74
+ * - `'extend-video'` + `videoUrl` -- video extension (`POST /v1/videos/extensions`)
75
+ * - `'reference-to-video'` + `referenceImageUrls` -- R2V generation (`POST /v1/videos/generations`)
76
+ * - no `mode` -- standard generation from text prompts or image input
77
+ *
78
+ * Runtime remains backward compatible with legacy auto-detected provider
79
+ * options, but the public TypeScript type is intentionally explicit so editors
80
+ * can suggest valid modes and flag invalid field combinations.
81
+ */
82
+ export type XaiVideoModelOptions =
83
+ | XaiVideoGenerationOptions
84
+ | XaiVideoEditModeOptions
85
+ | XaiVideoExtendModeOptions
86
+ | XaiVideoReferenceToVideoOptions
87
+ | XaiLegacyEditVideoOptions
88
+ | XaiLegacyReferenceToVideoOptions;
89
+
90
+ // ── Runtime schemas ───────────────────────────────────────────────────
91
+ const baseFields = {
92
+ pollIntervalMs: z.number().positive().nullish(),
93
+ pollTimeoutMs: z.number().positive().nullish(),
94
+ resolution: resolutionSchema.nullish(),
10
95
  };
11
96
 
97
+ const editVideoSchema = z.object({
98
+ ...baseFields,
99
+ mode: z.literal('edit-video'),
100
+ videoUrl: nonEmptyStringSchema,
101
+ referenceImageUrls: z.undefined().optional(),
102
+ });
103
+
104
+ const extendVideoSchema = z.object({
105
+ ...baseFields,
106
+ mode: z.literal('extend-video'),
107
+ videoUrl: nonEmptyStringSchema,
108
+ referenceImageUrls: z.undefined().optional(),
109
+ });
110
+
111
+ const referenceToVideoSchema = z.object({
112
+ ...baseFields,
113
+ mode: z.literal('reference-to-video'),
114
+ referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7),
115
+ videoUrl: z.undefined().optional(),
116
+ });
117
+
118
+ const autoDetectSchema = z.object({
119
+ ...baseFields,
120
+ mode: z.undefined().optional(),
121
+ videoUrl: nonEmptyStringSchema.optional(),
122
+ referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7).optional(),
123
+ });
124
+
125
+ export const xaiVideoModelOptions = z.union([
126
+ editVideoSchema,
127
+ extendVideoSchema,
128
+ referenceToVideoSchema,
129
+ autoDetectSchema,
130
+ ]);
131
+
132
+ const runtimeSchema = z
133
+ .object({
134
+ mode: modeSchema.optional(),
135
+ videoUrl: nonEmptyStringSchema.optional(),
136
+ referenceImageUrls: z.array(nonEmptyStringSchema).min(1).max(7).optional(),
137
+ ...baseFields,
138
+ })
139
+ .passthrough();
140
+
141
+ export type XaiParsedVideoModelOptions = z.infer<typeof runtimeSchema>;
142
+
12
143
  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
- ),
144
+ zodSchema(runtimeSchema),
23
145
  );