@ai-sdk/xai 4.0.25 → 4.0.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/xai",
3
- "version": "4.0.25",
3
+ "version": "4.0.27",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -29,9 +29,9 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@ai-sdk/openai-compatible": "3.0.20",
33
- "@ai-sdk/provider": "4.0.4",
34
- "@ai-sdk/provider-utils": "5.0.18"
32
+ "@ai-sdk/openai-compatible": "3.0.22",
33
+ "@ai-sdk/provider": "4.0.5",
34
+ "@ai-sdk/provider-utils": "5.0.20"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "22.19.19",
@@ -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 XaiVideoDoGenerateOptions = Parameters<
47
- Experimental_VideoModelV4['doGenerate']
48
- >[0];
50
+ type XaiVideoCallOptions = VideoModelV4CallOptions;
49
51
 
50
52
  function getFirstFrameImage(
51
- options: XaiVideoDoGenerateOptions,
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: XaiVideoDoGenerateOptions,
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: XaiVideoDoGenerateOptions,
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: XaiVideoDoGenerateOptions,
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: XaiVideoDoGenerateOptions,
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 doGenerate(
168
- options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
169
- ): Promise<Awaited<ReturnType<Experimental_VideoModelV4['doGenerate']>>> {
170
- const currentDate = this.config._internal?.currentDate?.() ?? new Date();
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
- // Step 1: Create video generation/edit/extension request
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. Response: ${JSON.stringify(createResponse)}`,
441
+ message: `No request_id returned from xAI API.`,
418
442
  });
419
443
  }
420
444
 
421
- // Step 2: Poll for completion
422
- const pollIntervalMs = xaiOptions?.pollIntervalMs ?? 5000;
423
- const pollTimeoutMs = xaiOptions?.pollTimeoutMs ?? 600000;
424
- const startTime = Date.now();
425
- let responseHeaders: Record<string, string> | undefined;
426
-
427
- while (true) {
428
- await delay(pollIntervalMs, { abortSignal: options.abortSignal });
429
-
430
- if (Date.now() - startTime > pollTimeoutMs) {
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
- const { value: statusResponse, responseHeaders: pollHeaders } =
438
- await getFromApi({
439
- url: `${baseURL}/videos/${requestId}`,
440
- validateUrl: false,
441
- headers: combineHeaders(this.config.headers(), options.headers),
442
- successfulResponseHandler: xaiVideoStatusResponseHandler,
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
- responseHeaders = pollHeaders;
449
-
450
- if (
451
- statusResponse.status === 'done' ||
452
- (statusResponse.status == null && statusResponse.video?.url)
453
- ) {
454
- if (statusResponse.video?.respect_moderation === false) {
455
- throw new AISDKError({
456
- name: 'XAI_VIDEO_MODERATION_ERROR',
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
- if (!statusResponse.video?.url) {
463
- throw new AISDKError({
464
- name: 'XAI_VIDEO_GENERATION_ERROR',
465
- message:
466
- 'Video generation completed but no video URL was returned.',
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
- return {
471
- videos: [
472
- {
473
- type: 'url' as const,
474
- url: statusResponse.video.url,
475
- mediaType: 'video/mp4',
476
- },
477
- ],
478
- warnings,
479
- response: {
480
- timestamp: currentDate,
481
- modelId: this.modelId,
482
- headers: responseHeaders,
483
- },
484
- providerMetadata: {
485
- xai: {
486
- requestId,
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
- if (statusResponse.status === 'expired') {
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: 'XAI_VIDEO_GENERATION_EXPIRED',
505
- message: 'Video generation request expired.',
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.status === 'failed') {
515
+ if (!statusResponse.video?.url) {
510
516
  throw new AISDKError({
511
- name: 'XAI_VIDEO_GENERATION_FAILED',
512
- message: 'Video generation failed.',
517
+ name: 'XAI_VIDEO_GENERATION_ERROR',
518
+ message: 'Video generation completed but no video URL was returned.',
513
519
  });
514
520
  }
515
521
 
516
- // 'pending' → continue polling
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