@ai-sdk/xai 4.0.0-beta.4 → 4.0.0-beta.40

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,4 +1,4 @@
1
- import { ImageModelV3, SharedV3Warning } from '@ai-sdk/provider';
1
+ import { ImageModelV4, SharedV4Warning } from '@ai-sdk/provider';
2
2
  import {
3
3
  combineHeaders,
4
4
  convertImageModelFileToDataUri,
@@ -9,6 +9,9 @@ import {
9
9
  getFromApi,
10
10
  parseProviderOptions,
11
11
  postJsonToApi,
12
+ serializeModelOptions,
13
+ WORKFLOW_SERIALIZE,
14
+ WORKFLOW_DESERIALIZE,
12
15
  } from '@ai-sdk/provider-utils';
13
16
  import { z } from 'zod/v4';
14
17
  import { xaiFailedResponseHandler } from './xai-error';
@@ -18,21 +21,35 @@ import { XaiImageModelId } from './xai-image-settings';
18
21
  interface XaiImageModelConfig {
19
22
  provider: string;
20
23
  baseURL: string | undefined;
21
- headers: () => Record<string, string | undefined>;
24
+ headers?: () => Record<string, string | undefined>;
22
25
  fetch?: FetchFunction;
23
26
  _internal?: {
24
27
  currentDate?: () => Date;
25
28
  };
26
29
  }
27
30
 
28
- export class XaiImageModel implements ImageModelV3 {
29
- readonly specificationVersion = 'v3';
31
+ export class XaiImageModel implements ImageModelV4 {
32
+ readonly specificationVersion = 'v4';
30
33
  readonly maxImagesPerCall = 3;
31
34
 
32
35
  get provider(): string {
33
36
  return this.config.provider;
34
37
  }
35
38
 
39
+ static [WORKFLOW_SERIALIZE](model: XaiImageModel) {
40
+ return serializeModelOptions({
41
+ modelId: model.modelId,
42
+ config: model.config,
43
+ });
44
+ }
45
+
46
+ static [WORKFLOW_DESERIALIZE](options: {
47
+ modelId: XaiImageModelId;
48
+ config: XaiImageModelConfig;
49
+ }) {
50
+ return new XaiImageModel(options.modelId, options.config);
51
+ }
52
+
36
53
  constructor(
37
54
  readonly modelId: XaiImageModelId,
38
55
  private config: XaiImageModelConfig,
@@ -49,10 +66,10 @@ export class XaiImageModel implements ImageModelV3 {
49
66
  abortSignal,
50
67
  files,
51
68
  mask,
52
- }: Parameters<ImageModelV3['doGenerate']>[0]): Promise<
53
- Awaited<ReturnType<ImageModelV3['doGenerate']>>
69
+ }: Parameters<ImageModelV4['doGenerate']>[0]): Promise<
70
+ Awaited<ReturnType<ImageModelV4['doGenerate']>>
54
71
  > {
55
- const warnings: Array<SharedV3Warning> = [];
72
+ const warnings: Array<SharedV4Warning> = [];
56
73
 
57
74
  if (size != null) {
58
75
  warnings.push({
@@ -135,7 +152,7 @@ export class XaiImageModel implements ImageModelV3 {
135
152
  const currentDate = this.config._internal?.currentDate?.() ?? new Date();
136
153
  const { value: response, responseHeaders } = await postJsonToApi({
137
154
  url: `${baseURL}${endpoint}`,
138
- headers: combineHeaders(this.config.headers(), headers),
155
+ headers: combineHeaders(this.config.headers?.(), headers),
139
156
  body,
140
157
  failedResponseHandler: xaiFailedResponseHandler,
141
158
  successfulResponseHandler: createJsonResponseHandler(
@@ -1,6 +1,4 @@
1
1
  export type XaiImageModelId =
2
- | 'grok-2-image'
3
- | 'grok-2-image-1212'
4
2
  | 'grok-imagine-image'
5
3
  | 'grok-imagine-image-pro'
6
4
  | (string & {});
@@ -1,6 +1,6 @@
1
1
  import {
2
- LanguageModelV3CallOptions,
3
- SharedV3Warning,
2
+ LanguageModelV4CallOptions,
3
+ SharedV4Warning,
4
4
  UnsupportedFunctionalityError,
5
5
  } from '@ai-sdk/provider';
6
6
  import { XaiToolChoice } from './xai-chat-prompt';
@@ -9,8 +9,8 @@ export function prepareTools({
9
9
  tools,
10
10
  toolChoice,
11
11
  }: {
12
- tools: LanguageModelV3CallOptions['tools'];
13
- toolChoice?: LanguageModelV3CallOptions['toolChoice'];
12
+ tools: LanguageModelV4CallOptions['tools'];
13
+ toolChoice?: LanguageModelV4CallOptions['toolChoice'];
14
14
  }): {
15
15
  tools:
16
16
  | Array<{
@@ -24,12 +24,12 @@ export function prepareTools({
24
24
  }>
25
25
  | undefined;
26
26
  toolChoice: XaiToolChoice | undefined;
27
- toolWarnings: SharedV3Warning[];
27
+ toolWarnings: SharedV4Warning[];
28
28
  } {
29
29
  // when the tools array is empty, change it to undefined to prevent errors
30
30
  tools = tools?.length ? tools : undefined;
31
31
 
32
- const toolWarnings: SharedV3Warning[] = [];
32
+ const toolWarnings: SharedV4Warning[] = [];
33
33
 
34
34
  if (tools == null) {
35
35
  return { tools: undefined, toolChoice: undefined, toolWarnings };
@@ -1,9 +1,10 @@
1
1
  import {
2
- type Experimental_VideoModelV3,
3
- ImageModelV3,
4
- LanguageModelV3,
2
+ type Experimental_VideoModelV4,
3
+ FilesV4,
4
+ ImageModelV4,
5
+ LanguageModelV4,
5
6
  NoSuchModelError,
6
- ProviderV3,
7
+ ProviderV4,
7
8
  } from '@ai-sdk/provider';
8
9
  import {
9
10
  FetchFunction,
@@ -20,49 +21,52 @@ 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
 
26
- export interface XaiProvider extends ProviderV3 {
27
- /**
28
- * Creates an Xai chat model for text generation.
29
- */
30
- (modelId: XaiChatModelId): LanguageModelV3;
28
+ export interface XaiProvider extends ProviderV4 {
29
+ (modelId: XaiResponsesModelId): LanguageModelV4;
31
30
 
32
31
  /**
33
32
  * Creates an Xai language model for text generation.
34
33
  */
35
- languageModel(modelId: XaiChatModelId): LanguageModelV3;
34
+ languageModel(modelId: XaiResponsesModelId): LanguageModelV4;
36
35
 
37
36
  /**
38
37
  * Creates an Xai chat model for text generation.
39
38
  */
40
- chat: (modelId: XaiChatModelId) => LanguageModelV3;
39
+ chat: (modelId: XaiChatModelId) => LanguageModelV4;
41
40
 
42
41
  /**
43
- * Creates an Xai responses model for agentic tool calling.
42
+ * Creates an Xai responses model for text generation.
44
43
  */
45
- responses: (modelId: XaiResponsesModelId) => LanguageModelV3;
44
+ responses: (modelId: XaiResponsesModelId) => LanguageModelV4;
46
45
 
47
46
  /**
48
47
  * Creates an Xai image model for image generation.
49
48
  */
50
- image(modelId: XaiImageModelId): ImageModelV3;
49
+ image(modelId: XaiImageModelId): ImageModelV4;
51
50
 
52
51
  /**
53
52
  * Creates an Xai image model for image generation.
54
53
  */
55
- imageModel(modelId: XaiImageModelId): ImageModelV3;
54
+ imageModel(modelId: XaiImageModelId): ImageModelV4;
56
55
 
57
56
  /**
58
57
  * Creates an Xai video model for video generation.
59
58
  */
60
- video(modelId: XaiVideoModelId): Experimental_VideoModelV3;
59
+ video(modelId: XaiVideoModelId): Experimental_VideoModelV4;
61
60
 
62
61
  /**
63
62
  * Creates an Xai video model for video generation.
64
63
  */
65
- videoModel(modelId: XaiVideoModelId): Experimental_VideoModelV3;
64
+ videoModel(modelId: XaiVideoModelId): Experimental_VideoModelV4;
65
+
66
+ /**
67
+ * Returns the xAI files interface for uploading files.
68
+ */
69
+ files(): FilesV4;
66
70
 
67
71
  /**
68
72
  * Server-side agentic tools for use with the responses API.
@@ -153,11 +157,19 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
153
157
  });
154
158
  };
155
159
 
156
- const provider = (modelId: XaiChatModelId) =>
157
- createChatLanguageModel(modelId);
160
+ const createFiles = () =>
161
+ new XaiFiles({
162
+ provider: 'xai.files',
163
+ baseURL,
164
+ headers: getHeaders,
165
+ fetch: options.fetch,
166
+ });
167
+
168
+ const provider = (modelId: XaiResponsesModelId) =>
169
+ createResponsesLanguageModel(modelId);
158
170
 
159
- provider.specificationVersion = 'v3' as const;
160
- provider.languageModel = createChatLanguageModel;
171
+ provider.specificationVersion = 'v4' as const;
172
+ provider.languageModel = createResponsesLanguageModel;
161
173
  provider.chat = createChatLanguageModel;
162
174
  provider.responses = createResponsesLanguageModel;
163
175
  provider.embeddingModel = (modelId: string) => {
@@ -168,6 +180,7 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
168
180
  provider.image = createImageModel;
169
181
  provider.videoModel = createVideoModel;
170
182
  provider.video = createVideoModel;
183
+ provider.files = createFiles;
171
184
  provider.tools = xaiTools;
172
185
 
173
186
  return provider;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  AISDKError,
3
- type Experimental_VideoModelV3,
4
- type SharedV3Warning,
3
+ type Experimental_VideoModelV4,
4
+ type SharedV4Warning,
5
5
  } from '@ai-sdk/provider';
6
6
  import {
7
7
  combineHeaders,
@@ -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,8 +37,29 @@ const RESOLUTION_MAP: Record<string, string> = {
37
37
  '640x480': '480p',
38
38
  };
39
39
 
40
- export class XaiVideoModel implements Experimental_VideoModelV3 {
41
- readonly specificationVersion = 'v3';
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
+
61
+ export class XaiVideoModel implements Experimental_VideoModelV4 {
62
+ readonly specificationVersion = 'v4';
42
63
  readonly maxVideosPerCall = 1;
43
64
 
44
65
  get provider(): string {
@@ -51,18 +72,22 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
51
72
  ) {}
52
73
 
53
74
  async doGenerate(
54
- options: Parameters<Experimental_VideoModelV3['doGenerate']>[0],
55
- ): Promise<Awaited<ReturnType<Experimental_VideoModelV3['doGenerate']>>> {
75
+ options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
76
+ ): Promise<Awaited<ReturnType<Experimental_VideoModelV4['doGenerate']>>> {
56
77
  const currentDate = this.config._internal?.currentDate?.() ?? new Date();
57
- const warnings: SharedV3Warning[] = [];
78
+ const warnings: SharedV4Warning[] = [];
58
79
 
59
80
  const xaiOptions = (await parseProviderOptions({
60
81
  provider: 'xai',
61
82
  providerOptions: options.providerOptions,
62
83
  schema: xaiVideoModelOptionsSchema,
63
- })) as XaiVideoModelOptions | undefined;
84
+ })) as XaiParsedVideoModelOptions | undefined;
85
+
86
+ const effectiveMode = resolveVideoMode(xaiOptions);
64
87
 
65
- const isEdit = xaiOptions?.videoUrl != null;
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_VideoModelV3 {
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_VideoModelV3 {
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_VideoModelV3 {
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 };
203
+ }
204
+
205
+ // Video extension: pass source video URL (nested object)
206
+ if (isExtension) {
207
+ body.video = { url: xaiOptions!.videoUrl };
153
208
  }
154
209
 
155
- // Image-to-video: convert SDK image to nested image object
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_VideoModelV3 {
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_VideoModelV3 {
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,
@@ -239,6 +313,14 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
239
313
  statusResponse.status === 'done' ||
240
314
  (statusResponse.status == null && statusResponse.video?.url)
241
315
  ) {
316
+ if (statusResponse.video?.respect_moderation === false) {
317
+ throw new AISDKError({
318
+ name: 'XAI_VIDEO_MODERATION_ERROR',
319
+ message:
320
+ 'Video generation was blocked due to a content policy violation.',
321
+ });
322
+ }
323
+
242
324
  if (!statusResponse.video?.url) {
243
325
  throw new AISDKError({
244
326
  name: 'XAI_VIDEO_GENERATION_ERROR',
@@ -268,6 +350,12 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
268
350
  ...(statusResponse.video.duration != null
269
351
  ? { duration: statusResponse.video.duration }
270
352
  : {}),
353
+ ...(statusResponse.usage?.cost_in_usd_ticks != null
354
+ ? { costInUsdTicks: statusResponse.usage.cost_in_usd_ticks }
355
+ : {}),
356
+ ...(statusResponse.progress != null
357
+ ? { progress: statusResponse.progress }
358
+ : {}),
271
359
  },
272
360
  },
273
361
  };
@@ -280,6 +368,13 @@ export class XaiVideoModel implements Experimental_VideoModelV3 {
280
368
  });
281
369
  }
282
370
 
371
+ if (statusResponse.status === 'failed') {
372
+ throw new AISDKError({
373
+ name: 'XAI_VIDEO_GENERATION_FAILED',
374
+ message: 'Video generation failed.',
375
+ });
376
+ }
377
+
283
378
  // 'pending' → continue polling
284
379
  }
285
380
  }
@@ -299,4 +394,16 @@ const xaiVideoStatusResponseSchema = z.object({
299
394
  })
300
395
  .nullish(),
301
396
  model: z.string().nullish(),
397
+ usage: z
398
+ .object({
399
+ cost_in_usd_ticks: z.number().nullish(),
400
+ })
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(),
302
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
  );