@ai-sdk/xai 4.0.54 → 4.0.56

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/docs/01-xai.mdx CHANGED
@@ -625,59 +625,19 @@ The following provider options are available:
625
625
  tools with client-side function tools in the same request.
626
626
  </Note>
627
627
 
628
- ## Text Batches
628
+ ## Batch
629
629
 
630
630
  <Note type="warning">
631
- Text batch support is experimental and the API may change in patch releases.
631
+ Batch support is experimental and the API may change in patch releases.
632
632
  </Note>
633
633
 
634
- The xAI provider supports asynchronous text generation through the
635
- [Batch API](https://docs.x.ai/developers/advanced-api-usage/batch-api). Use the
636
- experimental text batch APIs to start a batch, poll its status, and stream its
637
- results:
634
+ The xAI provider supports asynchronous text generation through the [Batch
635
+ API](https://docs.x.ai/developers/advanced-api-usage/batch-api). Pass the xAI
636
+ provider to the AI SDK's [Batch](/docs/ai-sdk-core/batch) API
637
+ for the complete workflow, including polling, persistence, and result handling.
638
638
 
639
- ```ts
640
- import { xai } from '@ai-sdk/xai';
641
- import {
642
- experimental_getBatchResults as getBatchResults,
643
- experimental_getBatchStatus as getBatchStatus,
644
- experimental_startTextBatch as startTextBatch,
645
- } from 'ai';
646
- import { setTimeout } from 'node:timers/promises';
647
-
648
- const model = xai('grok-4.3');
649
-
650
- const batch = await startTextBatch({
651
- model,
652
- requests: [
653
- { id: 'capital-france', prompt: 'What is the capital of France?' },
654
- { id: 'capital-germany', prompt: 'What is the capital of Germany?' },
655
- ],
656
- });
657
-
658
- let status = batch.status;
659
- while (status === 'pending') {
660
- await setTimeout(60_000);
661
- ({ status } = await getBatchStatus({ model, batch }));
662
- }
663
-
664
- for await (const item of getBatchResults({ model, batch })) {
665
- if (item.status === 'succeeded') {
666
- console.log(item.id, item.text);
667
- } else {
668
- console.error(item.id, item.error);
669
- }
670
- }
671
- ```
672
-
673
- `startTextBatch` returns a serializable batch reference. Persist this reference
674
- to check the batch status or retrieve its results from another process. Results
675
- can arrive in a different order from the input requests, so match each result by
676
- its `id`.
677
-
678
- Text batches are available through `xai(modelId)` and
679
- `xai.responses(modelId)`. The legacy `xai.chat(modelId)` models do not expose
680
- the batch APIs.
639
+ Each request specifies its `type` and `model`. xAI supports using different
640
+ text models within the same batch.
681
641
 
682
642
  <Note>
683
643
  The xAI Batch API does not support per-batch webhooks. When you provide a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/xai",
3
- "version": "4.0.54",
3
+ "version": "4.0.56",
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.10",
33
- "@ai-sdk/provider-utils": "5.0.36"
32
+ "@ai-sdk/provider": "4.0.12",
33
+ "@ai-sdk/provider-utils": "5.0.38"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@ai-sdk/test-server": "2.0.1",
@@ -139,6 +139,15 @@ function isRetryableStatusCode(statusCode: number): boolean {
139
139
  );
140
140
  }
141
141
 
142
+ export const xaiResponsesSupportedUrls: Record<string, RegExp[]> = {
143
+ 'image/*': [/^https?:\/\/.*$/],
144
+ // xAI's Responses API accepts non-image documents (PDF, plain text, CSV, etc.) as
145
+ // `{ type: 'input_file', file_url }`. Keeping these URLs intact here lets them pass
146
+ // through to the converter instead of being downloaded to bytes by the SDK.
147
+ 'application/pdf': [/^https?:\/\/.*$/],
148
+ 'text/*': [/^https?:\/\/.*$/],
149
+ };
150
+
142
151
  export class XaiResponsesLanguageModel implements LanguageModelV4 {
143
152
  readonly specificationVersion = 'v4';
144
153
 
@@ -169,31 +178,30 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
169
178
  return this.config.provider;
170
179
  }
171
180
 
172
- readonly supportedUrls: Record<string, RegExp[]> = {
173
- 'image/*': [/^https?:\/\/.*$/],
174
- // xAI's Responses API accepts non-image documents (PDF, plain text, CSV, etc.) as
175
- // `{ type: 'input_file', file_url }`. Keeping these URLs intact here lets them pass
176
- // through to the converter instead of being downloaded to bytes by the SDK.
177
- 'application/pdf': [/^https?:\/\/.*$/],
178
- 'text/*': [/^https?:\/\/.*$/],
179
- };
180
-
181
- protected async getArgs({
182
- prompt,
183
- maxOutputTokens,
184
- temperature,
185
- topP,
186
- topK,
187
- frequencyPenalty,
188
- presencePenalty,
189
- stopSequences,
190
- seed,
191
- responseFormat,
192
- providerOptions,
193
- tools,
194
- toolChoice,
195
- reasoning,
196
- }: LanguageModelV4CallOptions) {
181
+ readonly supportedUrls = xaiResponsesSupportedUrls;
182
+
183
+ static async prepareRequest({
184
+ modelId,
185
+ options: {
186
+ prompt,
187
+ maxOutputTokens,
188
+ temperature,
189
+ topP,
190
+ topK,
191
+ frequencyPenalty,
192
+ presencePenalty,
193
+ stopSequences,
194
+ seed,
195
+ responseFormat,
196
+ providerOptions,
197
+ tools,
198
+ toolChoice,
199
+ reasoning,
200
+ },
201
+ }: {
202
+ modelId: XaiResponsesModelId;
203
+ options: LanguageModelV4CallOptions;
204
+ }) {
197
205
  const warnings: SharedV4Warning[] = [];
198
206
 
199
207
  const options =
@@ -276,7 +284,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
276
284
 
277
285
  let resolvedReasoningEffort = options.reasoningEffort;
278
286
  if (resolvedReasoningEffort == null && isCustomReasoning(reasoning)) {
279
- if (!supportsReasoningEffort(this.modelId)) {
287
+ if (!supportsReasoningEffort(modelId)) {
280
288
  warnings.push({
281
289
  type: 'unsupported',
282
290
  feature: 'reasoning',
@@ -292,7 +300,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
292
300
  low: 'low',
293
301
  medium: 'medium',
294
302
  high: 'high',
295
- xhigh: this.modelId === 'grok-4.6' ? 'xhigh' : 'high',
303
+ xhigh: modelId === 'grok-4.6' ? 'xhigh' : 'high',
296
304
  },
297
305
  warnings,
298
306
  });
@@ -300,7 +308,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
300
308
  }
301
309
 
302
310
  const baseArgs: Record<string, unknown> = {
303
- model: this.modelId,
311
+ model: modelId,
304
312
  input,
305
313
  logprobs:
306
314
  options.logprobs === true || options.topLogprobs != null
@@ -370,6 +378,13 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
370
378
  };
371
379
  }
372
380
 
381
+ private getArgs(options: LanguageModelV4CallOptions) {
382
+ return XaiResponsesLanguageModel.prepareRequest({
383
+ modelId: this.modelId,
384
+ options,
385
+ });
386
+ }
387
+
373
388
  async doGenerate(
374
389
  options: LanguageModelV4CallOptions,
375
390
  ): Promise<LanguageModelV4GenerateResult> {
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  InvalidArgumentError,
3
- type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
3
+ type Experimental_BatchV4 as BatchV4,
4
4
  type Experimental_BatchV4Error as BatchV4Error,
5
5
  type Experimental_BatchV4ItemResult as BatchV4ItemResult,
6
6
  type Experimental_BatchV4OperationOptions as BatchV4OperationOptions,
7
- type Experimental_BatchV4StartOptions as BatchV4StartOptions,
8
7
  type Experimental_BatchV4StartResult as BatchV4StartResult,
9
8
  type Experimental_BatchV4Status as BatchV4Status,
9
+ type Experimental_TextBatchV4Request as TextBatchV4Request,
10
+ type Experimental_TextBatchV4ItemResult as TextBatchV4ItemResult,
11
+ type Experimental_BatchV4StartOptions as BatchV4StartOptions,
10
12
  type LanguageModelV4Content,
11
13
  type LanguageModelV4GenerateResult,
12
14
  type SharedV4ProviderMetadata,
@@ -24,26 +26,25 @@ import {
24
26
  postFormDataToApi,
25
27
  postJsonToApi,
26
28
  safeValidateTypes,
27
- WORKFLOW_DESERIALIZE,
28
- WORKFLOW_SERIALIZE,
29
29
  zodSchema,
30
30
  type InferSchema,
31
31
  } from '@ai-sdk/provider-utils';
32
32
  import { z } from 'zod/v4';
33
- import { convertXaiChatUsage } from '../convert-xai-chat-usage';
34
- import { getResponseMetadata } from '../get-response-metadata';
35
- import { mapXaiFinishReason } from '../map-xai-finish-reason';
33
+ import { convertXaiChatUsage } from './convert-xai-chat-usage';
34
+ import { getResponseMetadata } from './get-response-metadata';
35
+ import { mapXaiFinishReason } from './map-xai-finish-reason';
36
36
  import {
37
37
  xaiChatResponseSchema,
38
38
  type XaiChatResponse,
39
- } from '../xai-chat-language-model';
40
- import { xaiFailedResponseHandler } from '../xai-error';
41
- import { xaiFilesResponseSchema } from '../files/xai-files-api';
39
+ } from './xai-chat-language-model';
40
+ import { xaiFailedResponseHandler } from './xai-error';
41
+ import { xaiFilesResponseSchema } from './files/xai-files-api';
42
42
  import {
43
43
  XaiResponsesLanguageModel,
44
+ xaiResponsesSupportedUrls,
44
45
  type XaiResponsesConfig,
45
- } from './xai-responses-language-model';
46
- import type { XaiResponsesModelId } from './xai-responses-language-model-options';
46
+ } from './responses/xai-responses-language-model';
47
+ import type { XaiResponsesModelId } from './responses/xai-responses-language-model-options';
47
48
 
48
49
  const xaiBatchEndpoint = '/v1/responses';
49
50
  const xaiBatchName = 'ai-sdk-text-batch';
@@ -67,9 +68,8 @@ const xaiBatchProviderOptionsSchema = lazySchema(() =>
67
68
  ),
68
69
  );
69
70
 
70
- type XaiBatchRequest = Parameters<
71
- BatchLanguageModelV4['experimental_doStartBatch']
72
- >[0]['requests'][number];
71
+ type XaiBatchModelIds = { readonly text: XaiResponsesModelId };
72
+ type XaiBatchRequest = TextBatchV4Request<XaiResponsesModelId>;
73
73
 
74
74
  type XaiBatchPreparedRequest = {
75
75
  body: unknown;
@@ -135,18 +135,22 @@ const xaiBatchResultsPageSchema = lazySchema(() =>
135
135
  ),
136
136
  );
137
137
 
138
- class XaiResponsesBatch {
138
+ export class XaiBatch implements BatchV4<XaiBatchModelIds> {
139
+ readonly specificationVersion = 'v4' as const;
140
+ readonly provider: string;
141
+ readonly supportedUrls = xaiResponsesSupportedUrls;
142
+
139
143
  constructor(
140
144
  private readonly options: {
145
+ provider: string;
141
146
  config: XaiResponsesConfig;
142
- prepareRequest: (
143
- request: XaiBatchRequest,
144
- ) => PromiseLike<XaiBatchPreparedRequest>;
145
147
  },
146
- ) {}
148
+ ) {
149
+ this.provider = options.provider;
150
+ }
147
151
 
148
- async startBatch(
149
- options: BatchV4StartOptions<XaiBatchRequest>,
152
+ async doStartBatch(
153
+ options: BatchV4StartOptions<XaiBatchModelIds>,
150
154
  ): Promise<BatchV4StartResult> {
151
155
  const fileParts: string[] = [];
152
156
  const warnings: BatchV4StartResult['warnings'] =
@@ -170,7 +174,7 @@ class XaiResponsesBatch {
170
174
  });
171
175
 
172
176
  for (const request of options.requests) {
173
- const preparedRequest = await this.options.prepareRequest(request);
177
+ const preparedRequest = await this.prepareRequest(request);
174
178
 
175
179
  fileParts.push(
176
180
  JSON.stringify({
@@ -252,15 +256,15 @@ class XaiResponsesBatch {
252
256
  };
253
257
  }
254
258
 
255
- async getBatchStatus(
259
+ async doGetBatchStatus(
256
260
  options: BatchV4OperationOptions,
257
261
  ): Promise<BatchV4Status> {
258
262
  return convertXaiBatchStatus(await this.retrieveBatch(options));
259
263
  }
260
264
 
261
- async getBatchResults(
265
+ async doGetBatchResults(
262
266
  options: BatchV4OperationOptions,
263
- ): Promise<ReadableStream<BatchV4ItemResult<LanguageModelV4GenerateResult>>> {
267
+ ): Promise<ReadableStream<BatchV4ItemResult>> {
264
268
  const batch = await this.retrieveBatch(options);
265
269
  if (convertXaiBatchStatus(batch).status === 'pending') {
266
270
  throw new InvalidArgumentError({
@@ -294,7 +298,7 @@ class XaiResponsesBatch {
294
298
 
295
299
  private async *iterateBatchResults(
296
300
  options: BatchV4OperationOptions,
297
- ): AsyncGenerator<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
301
+ ): AsyncGenerator<BatchV4ItemResult> {
298
302
  let paginationToken: string | undefined;
299
303
 
300
304
  do {
@@ -332,7 +336,7 @@ class XaiResponsesBatch {
332
336
 
333
337
  private async convertBatchResult(
334
338
  result: XaiBatchResult,
335
- ): Promise<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
339
+ ): Promise<TextBatchV4ItemResult> {
336
340
  const error = result.batch_result?.error;
337
341
  if (
338
342
  (result.error_message?.length ?? 0) > 0 ||
@@ -341,6 +345,7 @@ class XaiResponsesBatch {
341
345
  ) {
342
346
  const convertedError = convertXaiBatchError(result);
343
347
  return {
348
+ type: 'text',
344
349
  id: result.batch_request_id,
345
350
  status: isXaiCancellationError(error?.code) ? 'cancelled' : 'failed',
346
351
  error: convertedError,
@@ -362,11 +367,13 @@ class XaiResponsesBatch {
362
367
  const conversion = convertXaiChatBatchResponse(validation.value);
363
368
  return conversion.success
364
369
  ? {
370
+ type: 'text',
365
371
  id: result.batch_request_id,
366
372
  status: 'succeeded',
367
373
  result: conversion.result,
368
374
  }
369
375
  : {
376
+ type: 'text',
370
377
  id: result.batch_request_id,
371
378
  status: 'failed',
372
379
  error: conversion.error,
@@ -376,51 +383,19 @@ class XaiResponsesBatch {
376
383
  return invalidXaiBatchResult(result.batch_request_id);
377
384
  }
378
385
 
379
- private getUrl(path: string) {
380
- return `${this.options.config.baseURL ?? 'https://api.x.ai/v1'}${path}`;
381
- }
382
- }
383
-
384
- export class XaiResponsesBatchLanguageModel
385
- extends XaiResponsesLanguageModel
386
- implements BatchLanguageModelV4
387
- {
388
- private readonly batch: XaiResponsesBatch;
389
-
390
- static [WORKFLOW_SERIALIZE](model: XaiResponsesLanguageModel) {
391
- return XaiResponsesLanguageModel[WORKFLOW_SERIALIZE](model);
392
- }
393
-
394
- static [WORKFLOW_DESERIALIZE](options: {
395
- modelId: XaiResponsesModelId;
396
- config: XaiResponsesConfig;
397
- }) {
398
- return new XaiResponsesBatchLanguageModel(options.modelId, options.config);
399
- }
400
-
401
- constructor(modelId: XaiResponsesModelId, config: XaiResponsesConfig) {
402
- super(modelId, config);
403
- this.batch = new XaiResponsesBatch({
404
- config,
405
- prepareRequest: async request => {
406
- const { args: body, warnings } = await this.getArgs(request.options);
407
- return { body, warnings };
408
- },
409
- });
410
- }
411
-
412
- experimental_doStartBatch(
413
- options: Parameters<BatchLanguageModelV4['experimental_doStartBatch']>[0],
414
- ) {
415
- return this.batch.startBatch(options);
416
- }
417
-
418
- experimental_doGetBatchStatus(options: BatchV4OperationOptions) {
419
- return this.batch.getBatchStatus(options);
386
+ private async prepareRequest(
387
+ request: XaiBatchRequest,
388
+ ): Promise<XaiBatchPreparedRequest> {
389
+ const { args: body, warnings } =
390
+ await XaiResponsesLanguageModel.prepareRequest({
391
+ modelId: request.modelId,
392
+ options: request.options,
393
+ });
394
+ return { body, warnings };
420
395
  }
421
396
 
422
- experimental_doGetBatchResults(options: BatchV4OperationOptions) {
423
- return this.batch.getBatchResults(options);
397
+ private getUrl(path: string) {
398
+ return `${this.options.config.baseURL ?? 'https://api.x.ai/v1'}${path}`;
424
399
  }
425
400
  }
426
401
 
@@ -500,10 +475,9 @@ function isXaiCancellationError(code: string | number | null | undefined) {
500
475
  );
501
476
  }
502
477
 
503
- function invalidXaiBatchResult(
504
- id: string,
505
- ): BatchV4ItemResult<LanguageModelV4GenerateResult> {
478
+ function invalidXaiBatchResult(id: string): TextBatchV4ItemResult {
506
479
  return {
480
+ type: 'text',
507
481
  id,
508
482
  status: 'failed',
509
483
  error: {
@@ -2,7 +2,7 @@ import {
2
2
  type Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
3
3
  type Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
4
4
  type Experimental_VideoModelV4,
5
- type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
5
+ type Experimental_BatchV4 as BatchV4,
6
6
  type FilesV4,
7
7
  type ImageModelV4,
8
8
  type LanguageModelV4,
@@ -23,7 +23,8 @@ import { XaiChatLanguageModel } from './xai-chat-language-model';
23
23
  import type { XaiChatModelId } from './xai-chat-language-model-options';
24
24
  import { XaiImageModel } from './xai-image-model';
25
25
  import type { XaiImageModelId } from './xai-image-settings';
26
- import { XaiResponsesBatchLanguageModel } from './responses/xai-responses-batch';
26
+ import { XaiBatch } from './xai-batch';
27
+ import { XaiResponsesLanguageModel } from './responses/xai-responses-language-model';
27
28
  import type { XaiResponsesModelId } from './responses/xai-responses-language-model-options';
28
29
  import { XaiRealtimeModel } from './realtime/xai-realtime-model';
29
30
  import { xaiTools } from './tool';
@@ -35,12 +36,12 @@ import { XaiSpeechModel } from './xai-speech-model';
35
36
  import { XaiTranscriptionModel } from './xai-transcription-model';
36
37
 
37
38
  export interface XaiProvider extends ProviderV4 {
38
- (modelId: XaiResponsesModelId): BatchLanguageModelV4;
39
+ (modelId: XaiResponsesModelId): LanguageModelV4;
39
40
 
40
41
  /**
41
42
  * Creates an Xai language model for text generation.
42
43
  */
43
- languageModel(modelId: XaiResponsesModelId): BatchLanguageModelV4;
44
+ languageModel(modelId: XaiResponsesModelId): LanguageModelV4;
44
45
 
45
46
  /**
46
47
  * Creates an Xai chat model for text generation.
@@ -50,7 +51,12 @@ export interface XaiProvider extends ProviderV4 {
50
51
  /**
51
52
  * Creates an Xai responses model for text generation.
52
53
  */
53
- responses: (modelId: XaiResponsesModelId) => BatchLanguageModelV4;
54
+ responses: (modelId: XaiResponsesModelId) => LanguageModelV4;
55
+
56
+ /**
57
+ * Returns a BatchV4 interface for processing batches with xAI.
58
+ */
59
+ experimental_batch(): BatchV4<{ text: XaiResponsesModelId }>;
54
60
 
55
61
  /**
56
62
  * Creates an Xai image model for image generation.
@@ -167,7 +173,7 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
167
173
  };
168
174
 
169
175
  const createResponsesLanguageModel = (modelId: XaiResponsesModelId) => {
170
- return new XaiResponsesBatchLanguageModel(modelId, {
176
+ return new XaiResponsesLanguageModel(modelId, {
171
177
  provider: 'xai.responses',
172
178
  baseURL,
173
179
  headers: getHeaders,
@@ -249,6 +255,18 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
249
255
  fetch: options.fetch,
250
256
  });
251
257
 
258
+ const createBatch = () =>
259
+ new XaiBatch({
260
+ provider: 'xai.batch',
261
+ config: {
262
+ provider: 'xai.responses',
263
+ baseURL,
264
+ headers: getHeaders,
265
+ generateId,
266
+ fetch: options.fetch,
267
+ },
268
+ });
269
+
252
270
  const provider = (modelId: XaiResponsesModelId) =>
253
271
  createResponsesLanguageModel(modelId);
254
272
 
@@ -270,6 +288,7 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
270
288
  provider.transcriptionModel = createTranscriptionModel;
271
289
  provider.transcription = createTranscriptionModel;
272
290
  provider.files = createFiles;
291
+ provider.experimental_batch = createBatch;
273
292
  provider.tools = xaiTools;
274
293
 
275
294
  return provider;