@ai-sdk/xai 4.0.44 → 4.0.47

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,6 +625,66 @@ 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
629
+
630
+ <Note type="warning">
631
+ Text batch support is experimental and the API may change in patch releases.
632
+ </Note>
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:
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.
681
+
682
+ <Note>
683
+ The xAI Batch API does not support per-batch webhooks. When you provide a
684
+ `webhookUrl`, the provider returns an unsupported warning and starts the batch
685
+ without a webhook.
686
+ </Note>
687
+
628
688
  ## Model Capabilities
629
689
 
630
690
  | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | Reasoning |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/xai",
3
- "version": "4.0.44",
3
+ "version": "4.0.47",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@ai-sdk/provider": "4.0.8",
33
- "@ai-sdk/provider-utils": "5.0.30"
33
+ "@ai-sdk/provider-utils": "5.0.32"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@ai-sdk/test-server": "2.0.1",
@@ -260,24 +260,28 @@ const outputItemSchema = z.discriminatedUnion('type', [
260
260
  }),
261
261
  ]);
262
262
 
263
- export const xaiResponsesUsageSchema = z.object({
264
- input_tokens: z.number(),
265
- output_tokens: z.number(),
266
- total_tokens: z.number().optional(),
267
- input_tokens_details: z
268
- .object({
269
- cached_tokens: z.number().optional(),
270
- })
271
- .optional(),
272
- output_tokens_details: z
273
- .object({
274
- reasoning_tokens: z.number().optional(),
275
- })
276
- .optional(),
277
- num_sources_used: z.number().optional(),
278
- num_server_side_tools_used: z.number().optional(),
279
- cost_in_usd_ticks: z.number().nullish(),
280
- });
263
+ export const xaiResponsesUsageSchema = z
264
+ .object({
265
+ input_tokens: z.number(),
266
+ output_tokens: z.number(),
267
+ total_tokens: z.number().optional(),
268
+ input_tokens_details: z
269
+ .object({
270
+ cached_tokens: z.number().optional(),
271
+ })
272
+ .catchall(z.json())
273
+ .optional(),
274
+ output_tokens_details: z
275
+ .object({
276
+ reasoning_tokens: z.number().optional(),
277
+ })
278
+ .catchall(z.json())
279
+ .optional(),
280
+ num_sources_used: z.number().optional(),
281
+ num_server_side_tools_used: z.number().optional(),
282
+ cost_in_usd_ticks: z.number().nullish(),
283
+ })
284
+ .catchall(z.json());
281
285
 
282
286
  export const xaiResponsesResponseSchema = z.object({
283
287
  id: z.string().nullish(),
@@ -0,0 +1,558 @@
1
+ import {
2
+ InvalidArgumentError,
3
+ type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
4
+ type Experimental_BatchV4Error as BatchV4Error,
5
+ type Experimental_BatchV4ItemResult as BatchV4ItemResult,
6
+ type Experimental_BatchV4OperationOptions as BatchV4OperationOptions,
7
+ type Experimental_BatchV4StartOptions as BatchV4StartOptions,
8
+ type Experimental_BatchV4StartResult as BatchV4StartResult,
9
+ type Experimental_BatchV4Status as BatchV4Status,
10
+ type LanguageModelV4Content,
11
+ type LanguageModelV4GenerateResult,
12
+ type SharedV4ProviderMetadata,
13
+ type SharedV4Warning,
14
+ } from '@ai-sdk/provider';
15
+ import {
16
+ combineHeaders,
17
+ convertAsyncIteratorToReadableStream,
18
+ createJsonResponseHandler,
19
+ createNullLanguageModelUsage,
20
+ getFromApi,
21
+ lazySchema,
22
+ normalizeBatchRequestCounts,
23
+ postFormDataToApi,
24
+ postJsonToApi,
25
+ safeValidateTypes,
26
+ WORKFLOW_DESERIALIZE,
27
+ WORKFLOW_SERIALIZE,
28
+ zodSchema,
29
+ type InferSchema,
30
+ } from '@ai-sdk/provider-utils';
31
+ import { z } from 'zod/v4';
32
+ import { convertXaiChatUsage } from '../convert-xai-chat-usage';
33
+ import { getResponseMetadata } from '../get-response-metadata';
34
+ import { mapXaiFinishReason } from '../map-xai-finish-reason';
35
+ import {
36
+ xaiChatResponseSchema,
37
+ type XaiChatResponse,
38
+ } from '../xai-chat-language-model';
39
+ import { xaiFailedResponseHandler } from '../xai-error';
40
+ import { xaiFilesResponseSchema } from '../files/xai-files-api';
41
+ import {
42
+ XaiResponsesLanguageModel,
43
+ type XaiResponsesConfig,
44
+ } from './xai-responses-language-model';
45
+ import type { XaiResponsesModelId } from './xai-responses-language-model-options';
46
+
47
+ const xaiBatchEndpoint = '/v1/responses';
48
+ const xaiBatchName = 'ai-sdk-text-batch';
49
+ const xaiBatchResultsPageSize = 1000;
50
+
51
+ type XaiBatchRequest = Parameters<
52
+ BatchLanguageModelV4['experimental_doStartBatch']
53
+ >[0]['requests'][number];
54
+
55
+ type XaiBatchPreparedRequest = {
56
+ body: unknown;
57
+ warnings: SharedV4Warning[];
58
+ };
59
+
60
+ type XaiBatchResponseConversion =
61
+ | { success: true; result: LanguageModelV4GenerateResult }
62
+ | { success: false; error: BatchV4Error };
63
+
64
+ const xaiBatchResponseSchema = lazySchema(() =>
65
+ zodSchema(
66
+ z.object({
67
+ batch_id: z.string(),
68
+ name: z.string().nullish(),
69
+ create_time: z.string().nullish(),
70
+ expire_time: z.string().nullish(),
71
+ cancel_time: z.string().nullish(),
72
+ cancel_by_xai_message: z.string().nullish(),
73
+ state: z
74
+ .object({
75
+ num_requests: z.number().nullish(),
76
+ num_pending: z.number().nullish(),
77
+ num_success: z.number().nullish(),
78
+ num_error: z.number().nullish(),
79
+ num_cancelled: z.number().nullish(),
80
+ })
81
+ .nullish(),
82
+ }),
83
+ ),
84
+ );
85
+
86
+ type XaiBatchResponse = InferSchema<typeof xaiBatchResponseSchema>;
87
+
88
+ const xaiBatchErrorSchema = z.object({
89
+ code: z.union([z.string(), z.number()]).nullish(),
90
+ message: z.string().nullish(),
91
+ });
92
+
93
+ const xaiBatchResultSchema = z.object({
94
+ batch_request_id: z.string(),
95
+ batch_result: z
96
+ .object({
97
+ response: z
98
+ .object({
99
+ chat_get_completion: z.unknown().nullish(),
100
+ })
101
+ .nullish(),
102
+ error: xaiBatchErrorSchema.nullish(),
103
+ })
104
+ .nullish(),
105
+ error_message: z.string().nullish(),
106
+ });
107
+
108
+ type XaiBatchResult = z.infer<typeof xaiBatchResultSchema>;
109
+
110
+ const xaiBatchResultsPageSchema = lazySchema(() =>
111
+ zodSchema(
112
+ z.object({
113
+ results: z.array(xaiBatchResultSchema),
114
+ pagination_token: z.string().nullish(),
115
+ }),
116
+ ),
117
+ );
118
+
119
+ class XaiResponsesBatch {
120
+ constructor(
121
+ private readonly options: {
122
+ config: XaiResponsesConfig;
123
+ prepareRequest: (
124
+ request: XaiBatchRequest,
125
+ ) => PromiseLike<XaiBatchPreparedRequest>;
126
+ },
127
+ ) {}
128
+
129
+ async startBatch(
130
+ options: BatchV4StartOptions<XaiBatchRequest>,
131
+ ): Promise<BatchV4StartResult> {
132
+ const fileParts: string[] = [];
133
+ const warnings: BatchV4StartResult['warnings'] =
134
+ options.webhookUrl == null
135
+ ? []
136
+ : [
137
+ {
138
+ warning: {
139
+ type: 'unsupported',
140
+ feature: 'webhookUrl',
141
+ details:
142
+ 'The xAI Batch API does not support per-batch webhook URLs.',
143
+ },
144
+ },
145
+ ];
146
+
147
+ for (const request of options.requests) {
148
+ const preparedRequest = await this.options.prepareRequest(request);
149
+
150
+ fileParts.push(
151
+ JSON.stringify({
152
+ custom_id: request.id,
153
+ method: 'POST',
154
+ url: xaiBatchEndpoint,
155
+ body: preparedRequest.body,
156
+ }),
157
+ '\n',
158
+ );
159
+
160
+ for (const warning of preparedRequest.warnings) {
161
+ warnings.push({ requestId: request.id, warning });
162
+ }
163
+ }
164
+
165
+ const filename = 'batch.jsonl';
166
+ const file = new Blob(fileParts, { type: 'application/jsonl' });
167
+ fileParts.length = 0;
168
+ const formData = new FormData();
169
+ formData.append('file', file, filename);
170
+
171
+ const headers = combineHeaders(
172
+ this.options.config.headers?.(),
173
+ options.headers,
174
+ );
175
+
176
+ const { value: uploadedFile } = await postFormDataToApi({
177
+ url: this.getUrl('/files'),
178
+ headers,
179
+ formData,
180
+ failedResponseHandler: xaiFailedResponseHandler,
181
+ successfulResponseHandler: createJsonResponseHandler(
182
+ xaiFilesResponseSchema,
183
+ ),
184
+ abortSignal: options.abortSignal,
185
+ fetch: this.options.config.fetch,
186
+ });
187
+
188
+ const { value: batch } = await postJsonToApi({
189
+ url: this.getUrl('/batches'),
190
+ headers,
191
+ body: {
192
+ name: xaiBatchName,
193
+ input_file_id: uploadedFile.id,
194
+ },
195
+ failedResponseHandler: xaiFailedResponseHandler,
196
+ successfulResponseHandler: createJsonResponseHandler(
197
+ xaiBatchResponseSchema,
198
+ ),
199
+ abortSignal: options.abortSignal,
200
+ fetch: this.options.config.fetch,
201
+ });
202
+
203
+ return {
204
+ batchId: batch.batch_id,
205
+ ...convertXaiBatchStatus(batch),
206
+ warnings,
207
+ };
208
+ }
209
+
210
+ async getBatchStatus(
211
+ options: BatchV4OperationOptions,
212
+ ): Promise<BatchV4Status> {
213
+ return convertXaiBatchStatus(await this.retrieveBatch(options));
214
+ }
215
+
216
+ async getBatchResults(
217
+ options: BatchV4OperationOptions,
218
+ ): Promise<ReadableStream<BatchV4ItemResult<LanguageModelV4GenerateResult>>> {
219
+ const batch = await this.retrieveBatch(options);
220
+ if (convertXaiBatchStatus(batch).status === 'pending') {
221
+ throw new InvalidArgumentError({
222
+ argument: 'batchId',
223
+ message: `xAI batch "${options.batchId}" is not complete.`,
224
+ });
225
+ }
226
+
227
+ return convertAsyncIteratorToReadableStream(
228
+ this.iterateBatchResults(options),
229
+ );
230
+ }
231
+
232
+ private async retrieveBatch(
233
+ options: BatchV4OperationOptions,
234
+ ): Promise<XaiBatchResponse> {
235
+ const { value: batch } = await getFromApi({
236
+ url: this.getUrl(`/batches/${encodeURIComponent(options.batchId)}`),
237
+ headers: combineHeaders(this.options.config.headers?.(), options.headers),
238
+ failedResponseHandler: xaiFailedResponseHandler,
239
+ successfulResponseHandler: createJsonResponseHandler(
240
+ xaiBatchResponseSchema,
241
+ ),
242
+ abortSignal: options.abortSignal,
243
+ fetch: this.options.config.fetch,
244
+ validateUrl: false,
245
+ });
246
+
247
+ return batch;
248
+ }
249
+
250
+ private async *iterateBatchResults(
251
+ options: BatchV4OperationOptions,
252
+ ): AsyncGenerator<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
253
+ let paginationToken: string | undefined;
254
+
255
+ do {
256
+ const query = new URLSearchParams({
257
+ limit: String(xaiBatchResultsPageSize),
258
+ });
259
+ if (paginationToken != null) {
260
+ query.set('pagination_token', paginationToken);
261
+ }
262
+
263
+ const { value: page } = await getFromApi({
264
+ url: this.getUrl(
265
+ `/batches/${encodeURIComponent(options.batchId)}/results?${query}`,
266
+ ),
267
+ headers: combineHeaders(
268
+ this.options.config.headers?.(),
269
+ options.headers,
270
+ ),
271
+ failedResponseHandler: xaiFailedResponseHandler,
272
+ successfulResponseHandler: createJsonResponseHandler(
273
+ xaiBatchResultsPageSchema,
274
+ ),
275
+ abortSignal: options.abortSignal,
276
+ fetch: this.options.config.fetch,
277
+ validateUrl: false,
278
+ });
279
+
280
+ for (const result of page.results) {
281
+ yield await this.convertBatchResult(result);
282
+ }
283
+
284
+ paginationToken = page.pagination_token ?? undefined;
285
+ } while (paginationToken != null);
286
+ }
287
+
288
+ private async convertBatchResult(
289
+ result: XaiBatchResult,
290
+ ): Promise<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
291
+ const error = result.batch_result?.error;
292
+ if (
293
+ (result.error_message?.length ?? 0) > 0 ||
294
+ (error?.code != null && error.code !== 0 && error.code !== '0') ||
295
+ (error?.code == null && (error?.message?.length ?? 0) > 0)
296
+ ) {
297
+ const convertedError = convertXaiBatchError(result);
298
+ return {
299
+ id: result.batch_request_id,
300
+ status: isXaiCancellationError(error?.code) ? 'cancelled' : 'failed',
301
+ error: convertedError,
302
+ };
303
+ }
304
+
305
+ // xAI returns text batch results in chat completion format, including for
306
+ // requests submitted to the Responses API endpoint.
307
+ const response = result.batch_result?.response;
308
+ if (response?.chat_get_completion != null) {
309
+ const validation = await safeValidateTypes({
310
+ value: response.chat_get_completion,
311
+ schema: xaiChatResponseSchema,
312
+ });
313
+ if (!validation.success) {
314
+ return invalidXaiBatchResult(result.batch_request_id);
315
+ }
316
+
317
+ const conversion = convertXaiChatBatchResponse(validation.value);
318
+ return conversion.success
319
+ ? {
320
+ id: result.batch_request_id,
321
+ status: 'succeeded',
322
+ result: conversion.result,
323
+ }
324
+ : {
325
+ id: result.batch_request_id,
326
+ status: 'failed',
327
+ error: conversion.error,
328
+ };
329
+ }
330
+
331
+ return invalidXaiBatchResult(result.batch_request_id);
332
+ }
333
+
334
+ private getUrl(path: string) {
335
+ return `${this.options.config.baseURL ?? 'https://api.x.ai/v1'}${path}`;
336
+ }
337
+ }
338
+
339
+ export class XaiResponsesBatchLanguageModel
340
+ extends XaiResponsesLanguageModel
341
+ implements BatchLanguageModelV4
342
+ {
343
+ private readonly batch: XaiResponsesBatch;
344
+
345
+ static [WORKFLOW_SERIALIZE](model: XaiResponsesLanguageModel) {
346
+ return XaiResponsesLanguageModel[WORKFLOW_SERIALIZE](model);
347
+ }
348
+
349
+ static [WORKFLOW_DESERIALIZE](options: {
350
+ modelId: XaiResponsesModelId;
351
+ config: XaiResponsesConfig;
352
+ }) {
353
+ return new XaiResponsesBatchLanguageModel(options.modelId, options.config);
354
+ }
355
+
356
+ constructor(modelId: XaiResponsesModelId, config: XaiResponsesConfig) {
357
+ super(modelId, config);
358
+ this.batch = new XaiResponsesBatch({
359
+ config,
360
+ prepareRequest: async request => {
361
+ const { args: body, warnings } = await this.getArgs(request.options);
362
+ return { body, warnings };
363
+ },
364
+ });
365
+ }
366
+
367
+ experimental_doStartBatch(
368
+ options: Parameters<BatchLanguageModelV4['experimental_doStartBatch']>[0],
369
+ ) {
370
+ return this.batch.startBatch(options);
371
+ }
372
+
373
+ experimental_doGetBatchStatus(options: BatchV4OperationOptions) {
374
+ return this.batch.getBatchStatus(options);
375
+ }
376
+
377
+ experimental_doGetBatchResults(options: BatchV4OperationOptions) {
378
+ return this.batch.getBatchResults(options);
379
+ }
380
+ }
381
+
382
+ function convertXaiBatchStatus(batch: XaiBatchResponse): BatchV4Status {
383
+ const requestCounts = normalizeBatchRequestCounts({
384
+ total: batch.state?.num_requests,
385
+ pending: batch.state?.num_pending,
386
+ completed: batch.state?.num_success,
387
+ failed:
388
+ batch.state?.num_error != null && batch.state?.num_cancelled != null
389
+ ? batch.state.num_error + batch.state.num_cancelled
390
+ : undefined,
391
+ });
392
+ const isCancelled =
393
+ batch.cancel_time != null || batch.cancel_by_xai_message != null;
394
+ const isExpired = isPastDate(batch.expire_time);
395
+ const status: BatchV4Status['status'] =
396
+ isCancelled || isExpired
397
+ ? 'failed'
398
+ : requestCounts != null &&
399
+ requestCounts.total > 0 &&
400
+ requestCounts.pending === 0
401
+ ? 'completed'
402
+ : 'pending';
403
+
404
+ return {
405
+ status,
406
+ ...(requestCounts != null ? { requestCounts } : {}),
407
+ ...(isCancelled
408
+ ? {
409
+ error: {
410
+ message:
411
+ batch.cancel_by_xai_message ??
412
+ `xAI batch "${batch.batch_id}" was cancelled.`,
413
+ code: 'batch_cancelled',
414
+ },
415
+ }
416
+ : isExpired
417
+ ? {
418
+ error: {
419
+ message: `xAI batch "${batch.batch_id}" expired.`,
420
+ code: 'batch_expired',
421
+ },
422
+ }
423
+ : {}),
424
+ ...(batch.create_time != null ? { createdAt: batch.create_time } : {}),
425
+ ...(batch.expire_time != null ? { expiresAt: batch.expire_time } : {}),
426
+ };
427
+ }
428
+
429
+ function isPastDate(value: string | null | undefined) {
430
+ if (value == null) {
431
+ return false;
432
+ }
433
+ const timestamp = Date.parse(value);
434
+ return !Number.isNaN(timestamp) && timestamp <= Date.now();
435
+ }
436
+
437
+ function convertXaiBatchError(result: XaiBatchResult): BatchV4Error {
438
+ const error = result.batch_result?.error;
439
+ return {
440
+ message:
441
+ result.error_message || error?.message || 'xAI batch request failed.',
442
+ ...(error?.code != null && error.code !== 0 && error.code !== '0'
443
+ ? { code: String(error.code) }
444
+ : {}),
445
+ };
446
+ }
447
+
448
+ function isXaiCancellationError(code: string | number | null | undefined) {
449
+ const normalizedCode = String(code).toLowerCase();
450
+ return (
451
+ code === 1 ||
452
+ normalizedCode === '1' ||
453
+ normalizedCode === 'cancelled' ||
454
+ normalizedCode === 'batch_cancelled'
455
+ );
456
+ }
457
+
458
+ function invalidXaiBatchResult(
459
+ id: string,
460
+ ): BatchV4ItemResult<LanguageModelV4GenerateResult> {
461
+ return {
462
+ id,
463
+ status: 'failed',
464
+ error: {
465
+ message: 'xAI returned an invalid Responses batch result.',
466
+ code: 'invalid_response',
467
+ },
468
+ };
469
+ }
470
+
471
+ function convertXaiChatBatchResponse(
472
+ response: XaiChatResponse,
473
+ ): XaiBatchResponseConversion {
474
+ if (response.error != null) {
475
+ return {
476
+ success: false,
477
+ error: {
478
+ message: response.error,
479
+ ...(response.code != null ? { code: response.code } : {}),
480
+ },
481
+ };
482
+ }
483
+
484
+ const choice = response.choices?.[0];
485
+ if (choice == null) {
486
+ return {
487
+ success: false,
488
+ error: {
489
+ message: 'xAI returned a batch response without any choices.',
490
+ code: 'invalid_response',
491
+ },
492
+ };
493
+ }
494
+
495
+ if (choice.message.tool_calls?.length) {
496
+ return unsupportedXaiBatchContent('tool_calls');
497
+ }
498
+
499
+ const content: LanguageModelV4Content[] = [];
500
+ if (choice.message.content) {
501
+ content.push({ type: 'text', text: choice.message.content });
502
+ }
503
+ if (choice.message.reasoning_content) {
504
+ content.push({
505
+ type: 'reasoning',
506
+ text: choice.message.reasoning_content,
507
+ });
508
+ }
509
+ for (const url of response.citations ?? []) {
510
+ content.push({
511
+ type: 'source',
512
+ sourceType: 'url',
513
+ id: url,
514
+ url,
515
+ });
516
+ }
517
+
518
+ return {
519
+ success: true,
520
+ result: {
521
+ content,
522
+ finishReason: {
523
+ unified: mapXaiFinishReason(choice.finish_reason),
524
+ raw: choice.finish_reason ?? undefined,
525
+ },
526
+ usage: response.usage
527
+ ? convertXaiChatUsage(response.usage)
528
+ : createNullLanguageModelUsage(),
529
+ response: getResponseMetadata(response),
530
+ warnings: [],
531
+ ...((response.usage?.cost_in_usd_ticks != null ||
532
+ response.service_tier != null) && {
533
+ providerMetadata: {
534
+ xai: {
535
+ ...(response.usage?.cost_in_usd_ticks != null
536
+ ? { costInUsdTicks: response.usage.cost_in_usd_ticks }
537
+ : {}),
538
+ ...(response.service_tier != null
539
+ ? { serviceTier: response.service_tier }
540
+ : {}),
541
+ },
542
+ } satisfies SharedV4ProviderMetadata,
543
+ }),
544
+ },
545
+ };
546
+ }
547
+
548
+ function unsupportedXaiBatchContent(type: string): XaiBatchResponseConversion {
549
+ return {
550
+ success: false,
551
+ error: {
552
+ message:
553
+ `xAI returned "${type}" content, but tool content is not supported ` +
554
+ 'in AI SDK text batches.',
555
+ code: 'unsupported_content',
556
+ },
557
+ };
558
+ }