@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/CHANGELOG.md +6 -0
- package/dist/index.d.mts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +249 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +251 -1
- package/dist/index.mjs.map +1 -1
- package/docs/01-xai.mdx +178 -0
- package/package.json +2 -2
- package/src/index.ts +6 -0
- package/src/xai-provider.ts +24 -0
- package/src/xai-video-model.ts +302 -0
- package/src/xai-video-options.ts +23 -0
- package/src/xai-video-settings.ts +1 -0
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.
|
|
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 {
|
package/src/xai-provider.ts
CHANGED
|
@@ -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 & {});
|