@ai-sdk/xai 4.0.57 → 4.0.58

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.57",
3
+ "version": "4.0.58",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -29,8 +29,8 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@ai-sdk/provider": "4.0.13",
33
- "@ai-sdk/provider-utils": "5.0.39"
32
+ "@ai-sdk/provider": "4.0.14",
33
+ "@ai-sdk/provider-utils": "5.0.40"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@ai-sdk/test-server": "2.0.1",
package/src/xai-batch.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  InvalidArgumentError,
3
+ UnsupportedFunctionalityError,
3
4
  type Experimental_BatchV4 as BatchV4,
4
5
  type Experimental_BatchV4CancelResult as BatchV4CancelResult,
5
6
  type Experimental_BatchV4Error as BatchV4Error,
@@ -10,17 +11,23 @@ import {
10
11
  type Experimental_BatchV4StartResult as BatchV4StartResult,
11
12
  type Experimental_BatchV4Status as BatchV4Status,
12
13
  type Experimental_TextBatchV4Request as TextBatchV4Request,
14
+ type Experimental_ImageBatchV4Request as ImageBatchV4Request,
15
+ type Experimental_ImageBatchV4ItemResult as ImageBatchV4ItemResult,
13
16
  type Experimental_TextBatchV4ItemResult as TextBatchV4ItemResult,
14
17
  type Experimental_BatchV4StartOptions as BatchV4StartOptions,
15
18
  type LanguageModelV4Content,
16
19
  type LanguageModelV4GenerateResult,
17
20
  type SharedV4ProviderMetadata,
18
21
  type SharedV4Warning,
22
+ type ImageModelV4Result,
19
23
  } from '@ai-sdk/provider';
20
24
  import {
21
25
  combineHeaders,
26
+ convertImageModelFileToDataUri,
27
+ convertBase64ToUint8Array,
22
28
  convertAsyncIteratorToReadableStream,
23
29
  createJsonResponseHandler,
30
+ createBinaryResponseHandler,
24
31
  createNullLanguageModelUsage,
25
32
  getFromApi,
26
33
  lazySchema,
@@ -48,6 +55,8 @@ import {
48
55
  type XaiResponsesConfig,
49
56
  } from './responses/xai-responses-language-model';
50
57
  import type { XaiResponsesModelId } from './responses/xai-responses-language-model-options';
58
+ import type { XaiImageModelId } from './xai-image-settings';
59
+ import { xaiImageModelOptions } from './xai-image-model-options';
51
60
 
52
61
  const xaiBatchEndpoint = '/v1/responses';
53
62
  const xaiBatchName = 'ai-sdk-text-batch';
@@ -71,14 +80,45 @@ const xaiBatchProviderOptionsSchema = lazySchema(() =>
71
80
  ),
72
81
  );
73
82
 
74
- type XaiBatchModelIds = { readonly text: XaiResponsesModelId };
83
+ type XaiBatchModelIds = {
84
+ readonly text: XaiResponsesModelId;
85
+ readonly image: XaiImageModelId;
86
+ };
75
87
  type XaiBatchRequest = TextBatchV4Request<XaiResponsesModelId>;
88
+ type XaiImageBatchRequest = ImageBatchV4Request<XaiImageModelId>;
89
+
90
+ function assertSupportedBatchRequests(
91
+ requests: BatchV4StartOptions<XaiBatchModelIds>['requests'],
92
+ ) {
93
+ for (const request of requests) {
94
+ const requestType = request.type;
95
+ if (requestType !== 'text' && requestType !== 'image') {
96
+ throw new UnsupportedFunctionalityError({
97
+ functionality: `batch request type: ${requestType}`,
98
+ message: `The xAI Batch API does not support batch requests with type "${requestType}".`,
99
+ });
100
+ }
101
+ }
102
+ }
76
103
 
77
104
  type XaiBatchPreparedRequest = {
105
+ endpoint: string;
78
106
  body: unknown;
79
107
  warnings: SharedV4Warning[];
80
108
  };
81
109
 
110
+ const xaiBatchImageResponseSchema = z.object({
111
+ data: z.array(
112
+ z.object({
113
+ url: z.string().nullish(),
114
+ b64_json: z.string().nullish(),
115
+ revised_prompt: z.string().nullish(),
116
+ respect_moderation: z.boolean().nullish(),
117
+ }),
118
+ ),
119
+ usage: z.object({ cost_in_usd_ticks: z.number().nullish() }).nullish(),
120
+ });
121
+
82
122
  type XaiBatchResponseConversion =
83
123
  | { success: true; result: LanguageModelV4GenerateResult }
84
124
  | { success: false; error: BatchV4Error };
@@ -120,6 +160,7 @@ const xaiBatchResultSchema = z.object({
120
160
  response: z
121
161
  .object({
122
162
  chat_get_completion: z.unknown().nullish(),
163
+ image_generation: z.unknown().nullish(),
123
164
  })
124
165
  .nullish(),
125
166
  error: xaiBatchErrorSchema.nullish(),
@@ -165,6 +206,7 @@ export class XaiBatch implements BatchV4<XaiBatchModelIds> {
165
206
  async doStartBatch(
166
207
  options: BatchV4StartOptions<XaiBatchModelIds>,
167
208
  ): Promise<BatchV4StartResult> {
209
+ assertSupportedBatchRequests(options.requests);
168
210
  const fileParts: string[] = [];
169
211
  const warnings: BatchV4StartResult['warnings'] =
170
212
  options.webhookUrl == null
@@ -193,7 +235,7 @@ export class XaiBatch implements BatchV4<XaiBatchModelIds> {
193
235
  JSON.stringify({
194
236
  custom_id: request.id,
195
237
  method: 'POST',
196
- url: xaiBatchEndpoint,
238
+ url: preparedRequest.endpoint,
197
239
  body: preparedRequest.body,
198
240
  }),
199
241
  '\n',
@@ -392,7 +434,7 @@ export class XaiBatch implements BatchV4<XaiBatchModelIds> {
392
434
  });
393
435
 
394
436
  for (const result of page.results) {
395
- yield await this.convertBatchResult(result);
437
+ yield await this.convertBatchResult(result, options.abortSignal);
396
438
  }
397
439
 
398
440
  paginationToken = page.pagination_token ?? undefined;
@@ -401,7 +443,8 @@ export class XaiBatch implements BatchV4<XaiBatchModelIds> {
401
443
 
402
444
  private async convertBatchResult(
403
445
  result: XaiBatchResult,
404
- ): Promise<TextBatchV4ItemResult> {
446
+ abortSignal: AbortSignal | undefined,
447
+ ): Promise<TextBatchV4ItemResult | ImageBatchV4ItemResult> {
405
448
  const error = result.batch_result?.error;
406
449
  if (
407
450
  (result.error_message?.length ?? 0) > 0 ||
@@ -445,18 +488,149 @@ export class XaiBatch implements BatchV4<XaiBatchModelIds> {
445
488
  };
446
489
  }
447
490
 
491
+ if (response?.image_generation != null) {
492
+ const validation = await safeValidateTypes({
493
+ value: response.image_generation,
494
+ schema: zodSchema(xaiBatchImageResponseSchema),
495
+ });
496
+ if (!validation.success) {
497
+ return invalidXaiImageBatchResult(result.batch_request_id);
498
+ }
499
+ if (
500
+ validation.value.data.some(image => image.respect_moderation === false)
501
+ ) {
502
+ return {
503
+ type: 'image',
504
+ id: result.batch_request_id,
505
+ status: 'failed',
506
+ error: {
507
+ message:
508
+ 'Image generation was blocked due to a content policy violation.',
509
+ },
510
+ };
511
+ }
512
+ const imageResult = await this.convertImageBatchResponse(
513
+ validation.value,
514
+ abortSignal,
515
+ );
516
+ return {
517
+ type: 'image',
518
+ id: result.batch_request_id,
519
+ status: 'succeeded',
520
+ result: imageResult,
521
+ };
522
+ }
523
+
448
524
  return invalidXaiBatchResult(result.batch_request_id);
449
525
  }
450
526
 
451
527
  private async prepareRequest(
452
- request: XaiBatchRequest,
528
+ request: XaiBatchRequest | XaiImageBatchRequest,
453
529
  ): Promise<XaiBatchPreparedRequest> {
454
- const { args: body, warnings } =
455
- await XaiResponsesLanguageModel.prepareRequest({
456
- modelId: request.modelId,
457
- options: request.options,
530
+ if (request.type === 'text') {
531
+ const { args: body, warnings } =
532
+ await XaiResponsesLanguageModel.prepareRequest({
533
+ modelId: request.modelId,
534
+ options: request.options,
535
+ });
536
+ return { endpoint: xaiBatchEndpoint, body, warnings };
537
+ }
538
+
539
+ const { prompt, n, size, aspectRatio, seed, files, mask, providerOptions } =
540
+ request.options;
541
+ const warnings: SharedV4Warning[] = [];
542
+ if (size != null) {
543
+ warnings.push({
544
+ type: 'unsupported',
545
+ feature: 'size',
546
+ details:
547
+ 'This model does not support the `size` option. Use `aspectRatio` instead.',
458
548
  });
459
- return { body, warnings };
549
+ }
550
+ if (seed != null) warnings.push({ type: 'unsupported', feature: 'seed' });
551
+ if (mask != null) warnings.push({ type: 'unsupported', feature: 'mask' });
552
+
553
+ const xaiOptions = await parseProviderOptions({
554
+ provider: 'xai',
555
+ providerOptions,
556
+ schema: xaiImageModelOptions,
557
+ });
558
+ const imageUrls = (files ?? []).map(convertImageModelFileToDataUri);
559
+ const body: Record<string, unknown> = {
560
+ model: request.modelId,
561
+ prompt,
562
+ n,
563
+ response_format: 'b64_json',
564
+ };
565
+ if (aspectRatio != null) body.aspect_ratio = aspectRatio;
566
+ if (xaiOptions?.output_format != null)
567
+ body.output_format = xaiOptions.output_format;
568
+ if (xaiOptions?.sync_mode != null) body.sync_mode = xaiOptions.sync_mode;
569
+ if (xaiOptions?.aspect_ratio != null && aspectRatio == null)
570
+ body.aspect_ratio = xaiOptions.aspect_ratio;
571
+ if (xaiOptions?.resolution != null) body.resolution = xaiOptions.resolution;
572
+ if (xaiOptions?.quality != null) body.quality = xaiOptions.quality;
573
+ if (xaiOptions?.user != null) body.user = xaiOptions.user;
574
+ if (imageUrls.length === 1) {
575
+ body.image = { url: imageUrls[0], type: 'image_url' };
576
+ } else if (imageUrls.length > 1) {
577
+ body.images = imageUrls.map(url => ({ url, type: 'image_url' }));
578
+ }
579
+
580
+ return {
581
+ endpoint: files?.length ? '/v1/images/edits' : '/v1/images/generations',
582
+ body,
583
+ warnings,
584
+ };
585
+ }
586
+
587
+ private async convertImageBatchResponse(
588
+ response: z.infer<typeof xaiBatchImageResponseSchema>,
589
+ abortSignal: AbortSignal | undefined,
590
+ ): Promise<ImageModelV4Result> {
591
+ const hasAllBase64 = response.data.every(image => image.b64_json != null);
592
+ const images = hasAllBase64
593
+ ? response.data.map(image => image.b64_json!)
594
+ : await Promise.all(
595
+ response.data.map(async image => {
596
+ if (image.b64_json != null) {
597
+ return convertBase64ToUint8Array(image.b64_json);
598
+ }
599
+ if (image.url == null) {
600
+ throw new InvalidArgumentError({
601
+ argument: 'batchResult',
602
+ message: 'xAI returned an image without data or a URL.',
603
+ });
604
+ }
605
+ const { value } = await getFromApi({
606
+ url: image.url,
607
+ validateUrl: true,
608
+ trustedOrigin: this.options.config.baseURL,
609
+ abortSignal,
610
+ failedResponseHandler: xaiFailedResponseHandler,
611
+ successfulResponseHandler: createBinaryResponseHandler(),
612
+ fetch: this.options.config.fetch,
613
+ });
614
+ return value;
615
+ }),
616
+ );
617
+ return {
618
+ images,
619
+ warnings: [],
620
+ response: { timestamp: new Date(), modelId: '', headers: undefined },
621
+ providerMetadata: {
622
+ xai: {
623
+ images: response.data.map(item =>
624
+ item.revised_prompt != null
625
+ ? { revisedPrompt: item.revised_prompt }
626
+ : {},
627
+ ),
628
+ ...(response.usage?.cost_in_usd_ticks != null
629
+ ? { costInUsdTicks: response.usage.cost_in_usd_ticks }
630
+ : {}),
631
+ },
632
+ },
633
+ };
460
634
  }
461
635
 
462
636
  private getUrl(path: string) {
@@ -552,6 +726,18 @@ function invalidXaiBatchResult(id: string): TextBatchV4ItemResult {
552
726
  };
553
727
  }
554
728
 
729
+ function invalidXaiImageBatchResult(id: string): ImageBatchV4ItemResult {
730
+ return {
731
+ type: 'image',
732
+ id,
733
+ status: 'failed',
734
+ error: {
735
+ message: 'xAI returned an invalid image batch result.',
736
+ code: 'invalid_response',
737
+ },
738
+ };
739
+ }
740
+
555
741
  function convertXaiChatBatchResponse(
556
742
  response: XaiChatResponse,
557
743
  ): XaiBatchResponseConversion {
@@ -590,7 +590,7 @@ export class XaiChatLanguageModel implements LanguageModelV4 {
590
590
  }
591
591
 
592
592
  // process tool calls
593
- if (delta.tool_calls != null) {
593
+ if (delta.tool_calls != null && delta.tool_calls.length > 0) {
594
594
  // end active reasoning block before tool calls start
595
595
  if (
596
596
  activeReasoningBlockId != null &&
@@ -56,7 +56,10 @@ export interface XaiProvider extends ProviderV4 {
56
56
  /**
57
57
  * Returns a BatchV4 interface for processing batches with xAI.
58
58
  */
59
- experimental_batch(): BatchV4<{ text: XaiResponsesModelId }>;
59
+ experimental_batch(): BatchV4<{
60
+ text: XaiResponsesModelId;
61
+ image: XaiImageModelId;
62
+ }>;
60
63
 
61
64
  /**
62
65
  * Creates an Xai image model for image generation.