@ai-sdk/xai 4.0.24 → 4.0.26
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 +28 -0
- package/dist/index.js +108 -76
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/get-response-metadata.ts +7 -7
- package/src/xai-video-model.ts +145 -99
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/xai",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ai-sdk/openai-compatible": "3.0.
|
|
33
|
-
"@ai-sdk/provider
|
|
34
|
-
"@ai-sdk/provider": "
|
|
32
|
+
"@ai-sdk/openai-compatible": "3.0.21",
|
|
33
|
+
"@ai-sdk/provider": "4.0.5",
|
|
34
|
+
"@ai-sdk/provider-utils": "5.0.19"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "22.19.19",
|
|
38
38
|
"tsup": "^8.5.1",
|
|
39
39
|
"typescript": "5.8.3",
|
|
40
40
|
"zod": "3.25.76",
|
|
41
|
-
"@
|
|
42
|
-
"@ai-
|
|
41
|
+
"@ai-sdk/test-server": "2.0.1",
|
|
42
|
+
"@vercel/ai-tsconfig": "0.0.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"zod": "^3.25.76 || ^4.1.8"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createLanguageModelResponseMetadata } from '@ai-sdk/provider-utils';
|
|
2
|
+
|
|
1
3
|
export function getResponseMetadata({
|
|
2
4
|
id,
|
|
3
5
|
model,
|
|
@@ -9,11 +11,9 @@ export function getResponseMetadata({
|
|
|
9
11
|
created_at?: number | undefined | null;
|
|
10
12
|
model?: string | undefined | null;
|
|
11
13
|
}) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
timestamp: unixTime != null ? new Date(unixTime * 1000) : undefined,
|
|
18
|
-
};
|
|
14
|
+
return createLanguageModelResponseMetadata({
|
|
15
|
+
id,
|
|
16
|
+
model,
|
|
17
|
+
created: created ?? created_at,
|
|
18
|
+
});
|
|
19
19
|
}
|
package/src/xai-video-model.ts
CHANGED
|
@@ -2,14 +2,16 @@ import {
|
|
|
2
2
|
AISDKError,
|
|
3
3
|
APICallError,
|
|
4
4
|
type Experimental_VideoModelV4,
|
|
5
|
+
type Experimental_VideoModelV4CallOptions as VideoModelV4CallOptions,
|
|
5
6
|
type Experimental_VideoModelV4File,
|
|
7
|
+
type Experimental_VideoModelV4OperationStartResult as VideoModelV4OperationStartResult,
|
|
8
|
+
type Experimental_VideoModelV4OperationStatusResult as VideoModelV4OperationStatusResult,
|
|
6
9
|
type SharedV4Warning,
|
|
7
10
|
} from '@ai-sdk/provider';
|
|
8
11
|
import {
|
|
9
12
|
combineHeaders,
|
|
10
13
|
convertUint8ArrayToBase64,
|
|
11
14
|
createJsonResponseHandler,
|
|
12
|
-
delay,
|
|
13
15
|
extractResponseHeaders,
|
|
14
16
|
getFromApi,
|
|
15
17
|
getTopLevelMediaType,
|
|
@@ -27,6 +29,8 @@ import {
|
|
|
27
29
|
} from './xai-video-model-options';
|
|
28
30
|
import type { XaiVideoModelId } from './xai-video-settings';
|
|
29
31
|
|
|
32
|
+
type VideoModelV4 = Experimental_VideoModelV4;
|
|
33
|
+
|
|
30
34
|
interface XaiVideoModelConfig {
|
|
31
35
|
provider: string;
|
|
32
36
|
baseURL: string | undefined;
|
|
@@ -43,26 +47,24 @@ const RESOLUTION_MAP: Record<string, string> = {
|
|
|
43
47
|
'640x480': '480p',
|
|
44
48
|
};
|
|
45
49
|
|
|
46
|
-
type
|
|
47
|
-
Experimental_VideoModelV4['doGenerate']
|
|
48
|
-
>[0];
|
|
50
|
+
type XaiVideoCallOptions = VideoModelV4CallOptions;
|
|
49
51
|
|
|
50
52
|
function getFirstFrameImage(
|
|
51
|
-
options:
|
|
53
|
+
options: XaiVideoCallOptions,
|
|
52
54
|
): Experimental_VideoModelV4File | undefined {
|
|
53
55
|
return options.frameImages?.find(frame => frame.frameType === 'first_frame')
|
|
54
56
|
?.image;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
function getLastFrameImage(
|
|
58
|
-
options:
|
|
60
|
+
options: XaiVideoCallOptions,
|
|
59
61
|
): Experimental_VideoModelV4File | undefined {
|
|
60
62
|
return options.frameImages?.find(frame => frame.frameType === 'last_frame')
|
|
61
63
|
?.image;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
function resolveStartImage(
|
|
65
|
-
options:
|
|
67
|
+
options: XaiVideoCallOptions,
|
|
66
68
|
): Experimental_VideoModelV4File | undefined {
|
|
67
69
|
return getFirstFrameImage(options) ?? options.image;
|
|
68
70
|
}
|
|
@@ -87,7 +89,7 @@ function fileToXaiImageUrl(file: Experimental_VideoModelV4File): string {
|
|
|
87
89
|
// Video references are not supported for reference-to-video and are skipped
|
|
88
90
|
// with a warning.
|
|
89
91
|
function resolveReferenceImages(
|
|
90
|
-
options:
|
|
92
|
+
options: XaiVideoCallOptions,
|
|
91
93
|
xaiOptions: XaiParsedVideoModelOptions | undefined,
|
|
92
94
|
warnings: SharedV4Warning[],
|
|
93
95
|
): Array<{ url: string }> | undefined {
|
|
@@ -123,7 +125,7 @@ function resolveReferenceImages(
|
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
function resolveVideoMode(
|
|
126
|
-
options:
|
|
128
|
+
options: XaiVideoCallOptions,
|
|
127
129
|
xaiOptions: XaiParsedVideoModelOptions | undefined,
|
|
128
130
|
): XaiParsedVideoModelOptions['mode'] | undefined {
|
|
129
131
|
if (xaiOptions?.mode != null) {
|
|
@@ -164,10 +166,15 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
|
|
|
164
166
|
private config: XaiVideoModelConfig,
|
|
165
167
|
) {}
|
|
166
168
|
|
|
167
|
-
async
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
private async buildRequestBody(options: VideoModelV4CallOptions): Promise<{
|
|
170
|
+
body: Record<string, unknown>;
|
|
171
|
+
warnings: SharedV4Warning[];
|
|
172
|
+
xaiOptions: XaiParsedVideoModelOptions | undefined;
|
|
173
|
+
isEdit: boolean;
|
|
174
|
+
isExtension: boolean;
|
|
175
|
+
hasReferenceImages: boolean;
|
|
176
|
+
effectiveMode: XaiParsedVideoModelOptions['mode'] | undefined;
|
|
177
|
+
}> {
|
|
171
178
|
const warnings: SharedV4Warning[] = [];
|
|
172
179
|
|
|
173
180
|
const xaiOptions = (await parseProviderOptions({
|
|
@@ -385,6 +392,24 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
|
|
|
385
392
|
}
|
|
386
393
|
}
|
|
387
394
|
|
|
395
|
+
return {
|
|
396
|
+
body,
|
|
397
|
+
warnings,
|
|
398
|
+
xaiOptions,
|
|
399
|
+
isEdit,
|
|
400
|
+
isExtension,
|
|
401
|
+
hasReferenceImages,
|
|
402
|
+
effectiveMode,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async doStart(
|
|
407
|
+
options: Parameters<NonNullable<VideoModelV4['doStart']>>[0],
|
|
408
|
+
): Promise<VideoModelV4OperationStartResult> {
|
|
409
|
+
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
410
|
+
const { body, warnings, isEdit, isExtension } =
|
|
411
|
+
await this.buildRequestBody(options);
|
|
412
|
+
|
|
388
413
|
const baseURL = this.config.baseURL ?? 'https://api.x.ai/v1';
|
|
389
414
|
|
|
390
415
|
// Determine endpoint based on mode
|
|
@@ -397,8 +422,7 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
|
|
|
397
422
|
endpoint = `${baseURL}/videos/generations`;
|
|
398
423
|
}
|
|
399
424
|
|
|
400
|
-
|
|
401
|
-
const { value: createResponse } = await postJsonToApi({
|
|
425
|
+
const { value: createResponse, responseHeaders } = await postJsonToApi({
|
|
402
426
|
url: endpoint,
|
|
403
427
|
headers: combineHeaders(this.config.headers(), options.headers),
|
|
404
428
|
body,
|
|
@@ -414,107 +438,129 @@ export class XaiVideoModel implements Experimental_VideoModelV4 {
|
|
|
414
438
|
if (!requestId) {
|
|
415
439
|
throw new AISDKError({
|
|
416
440
|
name: 'XAI_VIDEO_GENERATION_ERROR',
|
|
417
|
-
message: `No request_id returned from xAI API
|
|
441
|
+
message: `No request_id returned from xAI API.`,
|
|
418
442
|
});
|
|
419
443
|
}
|
|
420
444
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
throw new AISDKError({
|
|
432
|
-
name: 'XAI_VIDEO_GENERATION_TIMEOUT',
|
|
433
|
-
message: `Video generation timed out after ${pollTimeoutMs}ms`,
|
|
434
|
-
});
|
|
435
|
-
}
|
|
445
|
+
return {
|
|
446
|
+
operation: { requestId },
|
|
447
|
+
warnings,
|
|
448
|
+
response: {
|
|
449
|
+
timestamp: currentDate,
|
|
450
|
+
modelId: this.modelId,
|
|
451
|
+
headers: responseHeaders,
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
}
|
|
436
455
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
failedResponseHandler: xaiFailedResponseHandler,
|
|
444
|
-
abortSignal: options.abortSignal,
|
|
445
|
-
fetch: this.config.fetch,
|
|
446
|
-
});
|
|
456
|
+
async doStatus(
|
|
457
|
+
options: Parameters<NonNullable<VideoModelV4['doStatus']>>[0],
|
|
458
|
+
): Promise<VideoModelV4OperationStatusResult> {
|
|
459
|
+
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
460
|
+
const { requestId } = options.operation as { requestId: string };
|
|
461
|
+
const baseURL = this.config.baseURL ?? 'https://api.x.ai/v1';
|
|
447
462
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
message:
|
|
458
|
-
'Video generation was blocked due to a content policy violation.',
|
|
459
|
-
});
|
|
460
|
-
}
|
|
463
|
+
const { value: statusResponse, responseHeaders } = await getFromApi({
|
|
464
|
+
url: `${baseURL}/videos/${requestId}`,
|
|
465
|
+
validateUrl: false,
|
|
466
|
+
headers: combineHeaders(this.config.headers(), options.headers),
|
|
467
|
+
successfulResponseHandler: xaiVideoStatusResponseHandler,
|
|
468
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
469
|
+
abortSignal: options.abortSignal,
|
|
470
|
+
fetch: this.config.fetch,
|
|
471
|
+
});
|
|
461
472
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
473
|
+
if (statusResponse.status === 'expired') {
|
|
474
|
+
return {
|
|
475
|
+
status: 'error' as const,
|
|
476
|
+
error: 'Video generation request expired.',
|
|
477
|
+
response: {
|
|
478
|
+
timestamp: currentDate,
|
|
479
|
+
modelId: this.modelId,
|
|
480
|
+
headers: responseHeaders,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
469
484
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
videoUrl: statusResponse.video.url,
|
|
488
|
-
...(statusResponse.video.duration != null
|
|
489
|
-
? { duration: statusResponse.video.duration }
|
|
490
|
-
: {}),
|
|
491
|
-
...(statusResponse.usage?.cost_in_usd_ticks != null
|
|
492
|
-
? { costInUsdTicks: statusResponse.usage.cost_in_usd_ticks }
|
|
493
|
-
: {}),
|
|
494
|
-
...(statusResponse.progress != null
|
|
495
|
-
? { progress: statusResponse.progress }
|
|
496
|
-
: {}),
|
|
497
|
-
},
|
|
498
|
-
},
|
|
499
|
-
};
|
|
500
|
-
}
|
|
485
|
+
if (statusResponse.status === 'failed') {
|
|
486
|
+
const errorDetails =
|
|
487
|
+
statusResponse.error?.message ?? statusResponse.error?.code;
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
status: 'error' as const,
|
|
491
|
+
error:
|
|
492
|
+
errorDetails != null
|
|
493
|
+
? `Video generation failed: ${errorDetails}`
|
|
494
|
+
: 'Video generation failed.',
|
|
495
|
+
response: {
|
|
496
|
+
timestamp: currentDate,
|
|
497
|
+
modelId: this.modelId,
|
|
498
|
+
headers: responseHeaders,
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
}
|
|
501
502
|
|
|
502
|
-
|
|
503
|
+
if (
|
|
504
|
+
statusResponse.status === 'done' ||
|
|
505
|
+
(statusResponse.status == null && statusResponse.video?.url)
|
|
506
|
+
) {
|
|
507
|
+
if (statusResponse.video?.respect_moderation === false) {
|
|
503
508
|
throw new AISDKError({
|
|
504
|
-
name: '
|
|
505
|
-
message:
|
|
509
|
+
name: 'XAI_VIDEO_MODERATION_ERROR',
|
|
510
|
+
message:
|
|
511
|
+
'Video generation was blocked due to a content policy violation.',
|
|
506
512
|
});
|
|
507
513
|
}
|
|
508
514
|
|
|
509
|
-
if (statusResponse.
|
|
515
|
+
if (!statusResponse.video?.url) {
|
|
510
516
|
throw new AISDKError({
|
|
511
|
-
name: '
|
|
512
|
-
message: 'Video generation
|
|
517
|
+
name: 'XAI_VIDEO_GENERATION_ERROR',
|
|
518
|
+
message: 'Video generation completed but no video URL was returned.',
|
|
513
519
|
});
|
|
514
520
|
}
|
|
515
521
|
|
|
516
|
-
|
|
522
|
+
return {
|
|
523
|
+
status: 'completed',
|
|
524
|
+
videos: [
|
|
525
|
+
{
|
|
526
|
+
type: 'url',
|
|
527
|
+
url: statusResponse.video.url,
|
|
528
|
+
mediaType: 'video/mp4',
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
warnings: [],
|
|
532
|
+
response: {
|
|
533
|
+
timestamp: currentDate,
|
|
534
|
+
modelId: this.modelId,
|
|
535
|
+
headers: responseHeaders,
|
|
536
|
+
},
|
|
537
|
+
providerMetadata: {
|
|
538
|
+
xai: {
|
|
539
|
+
requestId,
|
|
540
|
+
videoUrl: statusResponse.video.url,
|
|
541
|
+
...(statusResponse.video.duration != null
|
|
542
|
+
? { duration: statusResponse.video.duration }
|
|
543
|
+
: {}),
|
|
544
|
+
...(statusResponse.usage?.cost_in_usd_ticks != null
|
|
545
|
+
? { costInUsdTicks: statusResponse.usage.cost_in_usd_ticks }
|
|
546
|
+
: {}),
|
|
547
|
+
...(statusResponse.progress != null
|
|
548
|
+
? { progress: statusResponse.progress }
|
|
549
|
+
: {}),
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
};
|
|
517
553
|
}
|
|
554
|
+
|
|
555
|
+
// pending status
|
|
556
|
+
return {
|
|
557
|
+
status: 'pending',
|
|
558
|
+
response: {
|
|
559
|
+
timestamp: currentDate,
|
|
560
|
+
modelId: this.modelId,
|
|
561
|
+
headers: responseHeaders,
|
|
562
|
+
},
|
|
563
|
+
};
|
|
518
564
|
}
|
|
519
565
|
}
|
|
520
566
|
|