@ai-sdk/xai 4.0.0-beta.6 → 4.0.0-beta.75

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +667 -9
  2. package/README.md +2 -0
  3. package/dist/index.d.ts +218 -68
  4. package/dist/index.js +2081 -779
  5. package/dist/index.js.map +1 -1
  6. package/docs/01-xai.mdx +445 -54
  7. package/package.json +15 -15
  8. package/src/convert-to-xai-chat-messages.ts +48 -27
  9. package/src/convert-xai-chat-usage.ts +3 -3
  10. package/src/files/xai-files-api.ts +16 -0
  11. package/src/files/xai-files-options.ts +19 -0
  12. package/src/files/xai-files.ts +94 -0
  13. package/src/index.ts +9 -4
  14. package/src/map-xai-finish-reason.ts +2 -2
  15. package/src/realtime/index.ts +2 -0
  16. package/src/realtime/xai-realtime-event-mapper.ts +399 -0
  17. package/src/realtime/xai-realtime-model-options.ts +3 -0
  18. package/src/realtime/xai-realtime-model.ts +101 -0
  19. package/src/remove-additional-properties.ts +24 -0
  20. package/src/responses/convert-to-xai-responses-input.ts +100 -23
  21. package/src/responses/convert-xai-responses-usage.ts +3 -3
  22. package/src/responses/map-xai-responses-finish-reason.ts +3 -2
  23. package/src/responses/xai-responses-api.ts +34 -1
  24. package/src/responses/{xai-responses-options.ts → xai-responses-language-model-options.ts} +13 -7
  25. package/src/responses/xai-responses-language-model.ts +173 -64
  26. package/src/responses/xai-responses-prepare-tools.ts +10 -8
  27. package/src/tool/code-execution.ts +2 -2
  28. package/src/tool/file-search.ts +2 -2
  29. package/src/tool/mcp-server.ts +2 -2
  30. package/src/tool/view-image.ts +2 -2
  31. package/src/tool/view-x-video.ts +2 -2
  32. package/src/tool/web-search.ts +4 -2
  33. package/src/tool/x-search.ts +2 -2
  34. package/src/{xai-chat-options.ts → xai-chat-language-model-options.ts} +28 -13
  35. package/src/xai-chat-language-model.ts +65 -29
  36. package/src/xai-chat-prompt.ts +2 -1
  37. package/src/xai-error.ts +13 -3
  38. package/src/xai-image-model.ts +28 -11
  39. package/src/xai-prepare-tools.ts +9 -8
  40. package/src/xai-provider.ts +115 -19
  41. package/src/xai-speech-model-options.ts +55 -0
  42. package/src/xai-speech-model.ts +167 -0
  43. package/src/xai-transcription-model-options.ts +70 -0
  44. package/src/xai-transcription-model.ts +166 -0
  45. package/src/xai-video-model-options.ts +145 -0
  46. package/src/xai-video-model.ts +129 -22
  47. package/dist/index.d.mts +0 -372
  48. package/dist/index.mjs +0 -3061
  49. package/dist/index.mjs.map +0 -1
  50. package/src/xai-video-options.ts +0 -23
  51. /package/src/{xai-image-options.ts → xai-image-model-options.ts} +0 -0
@@ -1,13 +1,14 @@
1
- import { LanguageModelV3FinishReason } from '@ai-sdk/provider';
1
+ import type { LanguageModelV4FinishReason } from '@ai-sdk/provider';
2
2
 
3
3
  export function mapXaiResponsesFinishReason(
4
4
  finishReason: string | null | undefined,
5
- ): LanguageModelV3FinishReason['unified'] {
5
+ ): LanguageModelV4FinishReason['unified'] {
6
6
  switch (finishReason) {
7
7
  case 'stop':
8
8
  case 'completed':
9
9
  return 'stop';
10
10
  case 'length':
11
+ case 'max_output_tokens':
11
12
  return 'length';
12
13
  case 'tool_calls':
13
14
  case 'function_call':
@@ -26,7 +26,9 @@ export type XaiResponsesSystemMessage = {
26
26
 
27
27
  export type XaiResponsesUserMessageContentPart =
28
28
  | { type: 'input_text'; text: string }
29
- | { type: 'input_image'; image_url: string };
29
+ | { type: 'input_image'; image_url: string }
30
+ | { type: 'input_file'; file_id: string }
31
+ | { type: 'input_file'; file_url: string };
30
32
 
31
33
  export type XaiResponsesUserMessage = {
32
34
  role: 'user';
@@ -77,6 +79,7 @@ export type XaiResponsesTool =
77
79
  type: 'web_search';
78
80
  allowed_domains?: string[];
79
81
  excluded_domains?: string[];
82
+ enable_image_search?: boolean;
80
83
  enable_image_understanding?: boolean;
81
84
  }
82
85
  | {
@@ -223,6 +226,9 @@ const outputItemSchema = z.discriminatedUnion('type', [
223
226
  type: z.literal('reasoning'),
224
227
  id: z.string(),
225
228
  summary: z.array(reasoningSummaryPartSchema),
229
+ content: z
230
+ .array(z.object({ type: z.string(), text: z.string() }))
231
+ .nullish(),
226
232
  status: z.string(),
227
233
  encrypted_content: z.string().nullish(),
228
234
  }),
@@ -244,6 +250,7 @@ export const xaiResponsesUsageSchema = z.object({
244
250
  .optional(),
245
251
  num_sources_used: z.number().optional(),
246
252
  num_server_side_tools_used: z.number().optional(),
253
+ cost_in_usd_ticks: z.number().nullish(),
247
254
  });
248
255
 
249
256
  export const xaiResponsesResponseSchema = z.object({
@@ -518,6 +525,32 @@ export const xaiResponsesChunkSchema = z.union([
518
525
  output_index: z.number(),
519
526
  output: z.string().optional(),
520
527
  }),
528
+ z.object({
529
+ type: z.literal('response.incomplete'),
530
+ response: z.object({
531
+ incomplete_details: z.object({ reason: z.string() }).nullish(),
532
+ usage: xaiResponsesUsageSchema.nullish(),
533
+ }),
534
+ }),
535
+ z.object({
536
+ type: z.literal('response.failed'),
537
+ response: z.object({
538
+ error: z
539
+ .object({
540
+ code: z.string().nullish(),
541
+ message: z.string(),
542
+ })
543
+ .nullish(),
544
+ incomplete_details: z.object({ reason: z.string() }).nullish(),
545
+ usage: xaiResponsesUsageSchema.nullish(),
546
+ }),
547
+ }),
548
+ z.object({
549
+ type: z.literal('error'),
550
+ code: z.string().nullish(),
551
+ message: z.string(),
552
+ param: z.string().nullish(),
553
+ }),
521
554
  z.object({
522
555
  type: z.literal('response.done'),
523
556
  response: xaiResponsesResponseSchema,
@@ -1,11 +1,10 @@
1
1
  import { z } from 'zod/v4';
2
2
 
3
3
  export type XaiResponsesModelId =
4
- | 'grok-4-1-fast-reasoning'
5
- | 'grok-4-1-fast-non-reasoning'
6
- | 'grok-4'
7
- | 'grok-4-fast-non-reasoning'
8
- | 'grok-4-fast-reasoning'
4
+ | 'grok-4.20-non-reasoning'
5
+ | 'grok-4.20-reasoning'
6
+ | 'grok-4.3'
7
+ | 'grok-latest'
9
8
  | (string & {});
10
9
 
11
10
  /**
@@ -14,13 +13,20 @@ export type XaiResponsesModelId =
14
13
  export const xaiLanguageModelResponsesOptions = z.object({
15
14
  /**
16
15
  * Constrains how hard a reasoning model thinks before responding.
17
- * Possible values are `low` (uses fewer reasoning tokens), `medium` and `high` (uses more reasoning tokens).
16
+ * Possible values are `none` (disables reasoning entirely; supported by
17
+ * `grok-4.3` and newer reasoning models), `low` (uses fewer reasoning
18
+ * tokens), `medium`, and `high` (uses more reasoning tokens).
19
+ *
20
+ * @see https://docs.x.ai/docs/guides/reasoning
18
21
  */
19
- reasoningEffort: z.enum(['low', 'medium', 'high']).optional(),
22
+ reasoningEffort: z.enum(['none', 'low', 'medium', 'high']).optional(),
23
+ reasoningSummary: z.enum(['auto', 'concise', 'detailed']).optional(),
20
24
  logprobs: z.boolean().optional(),
21
25
  topLogprobs: z.number().int().min(0).max(8).optional(),
22
26
  /**
23
27
  * Whether to store the input message(s) and model response for later retrieval.
28
+ * Must be set to `false` for teams with Zero Data Retention (ZDR) enabled,
29
+ * otherwise the API will return an error.
24
30
  * @default true
25
31
  */
26
32
  store: z.boolean().optional(),
@@ -1,55 +1,74 @@
1
- import {
2
- LanguageModelV3,
3
- LanguageModelV3CallOptions,
4
- LanguageModelV3Content,
5
- LanguageModelV3FinishReason,
6
- LanguageModelV3GenerateResult,
7
- LanguageModelV3StreamPart,
8
- LanguageModelV3StreamResult,
9
- LanguageModelV3Usage,
10
- SharedV3Warning,
1
+ import type {
2
+ LanguageModelV4,
3
+ LanguageModelV4CallOptions,
4
+ LanguageModelV4Content,
5
+ LanguageModelV4FinishReason,
6
+ LanguageModelV4GenerateResult,
7
+ LanguageModelV4StreamPart,
8
+ LanguageModelV4StreamResult,
9
+ LanguageModelV4Usage,
10
+ SharedV4Warning,
11
11
  } from '@ai-sdk/provider';
12
12
  import {
13
13
  combineHeaders,
14
14
  createEventSourceResponseHandler,
15
15
  createJsonResponseHandler,
16
- FetchFunction,
16
+ isCustomReasoning,
17
+ mapReasoningToProviderEffort,
17
18
  parseProviderOptions,
18
- ParseResult,
19
19
  postJsonToApi,
20
+ serializeModelOptions,
21
+ WORKFLOW_SERIALIZE,
22
+ WORKFLOW_DESERIALIZE,
23
+ type FetchFunction,
24
+ type ParseResult,
20
25
  } from '@ai-sdk/provider-utils';
21
- import { z } from 'zod/v4';
26
+ import type { z } from 'zod/v4';
22
27
  import { getResponseMetadata } from '../get-response-metadata';
23
28
  import { xaiFailedResponseHandler } from '../xai-error';
24
29
  import { convertToXaiResponsesInput } from './convert-to-xai-responses-input';
25
30
  import { convertXaiResponsesUsage } from './convert-xai-responses-usage';
26
31
  import { mapXaiResponsesFinishReason } from './map-xai-responses-finish-reason';
27
32
  import {
28
- XaiResponsesIncludeOptions,
29
33
  xaiResponsesChunkSchema,
30
34
  xaiResponsesResponseSchema,
35
+ type XaiResponsesIncludeOptions,
31
36
  } from './xai-responses-api';
32
37
  import {
33
- XaiResponsesModelId,
34
38
  xaiLanguageModelResponsesOptions,
35
- } from './xai-responses-options';
39
+ type XaiResponsesModelId,
40
+ } from './xai-responses-language-model-options';
36
41
  import { prepareResponsesTools } from './xai-responses-prepare-tools';
37
42
 
38
43
  type XaiResponsesConfig = {
39
44
  provider: string;
40
45
  baseURL: string | undefined;
41
- headers: () => Record<string, string | undefined>;
46
+ headers?: () => Record<string, string | undefined>;
42
47
  generateId: () => string;
43
48
  fetch?: FetchFunction;
44
49
  };
45
50
 
46
- export class XaiResponsesLanguageModel implements LanguageModelV3 {
47
- readonly specificationVersion = 'v3';
51
+ export class XaiResponsesLanguageModel implements LanguageModelV4 {
52
+ readonly specificationVersion = 'v4';
48
53
 
49
54
  readonly modelId: XaiResponsesModelId;
50
55
 
51
56
  private readonly config: XaiResponsesConfig;
52
57
 
58
+ static [WORKFLOW_SERIALIZE](model: XaiResponsesLanguageModel) {
59
+ return serializeModelOptions({
60
+ modelId: model.modelId,
61
+ config: model.config,
62
+ });
63
+ }
64
+
65
+ static [WORKFLOW_DESERIALIZE](options: {
66
+ modelId: XaiResponsesModelId;
67
+ config: XaiResponsesConfig;
68
+ }) {
69
+ return new XaiResponsesLanguageModel(options.modelId, options.config);
70
+ }
71
+
53
72
  constructor(modelId: XaiResponsesModelId, config: XaiResponsesConfig) {
54
73
  this.modelId = modelId;
55
74
  this.config = config;
@@ -61,6 +80,11 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
61
80
 
62
81
  readonly supportedUrls: Record<string, RegExp[]> = {
63
82
  'image/*': [/^https?:\/\/.*$/],
83
+ // xAI's Responses API accepts non-image documents (PDF, plain text, CSV, etc.) as
84
+ // `{ type: 'input_file', file_url }`. Keeping these URLs intact here lets them pass
85
+ // through to the converter instead of being downloaded to bytes by the SDK.
86
+ 'application/pdf': [/^https?:\/\/.*$/],
87
+ 'text/*': [/^https?:\/\/.*$/],
64
88
  };
65
89
 
66
90
  private async getArgs({
@@ -74,8 +98,9 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
74
98
  providerOptions,
75
99
  tools,
76
100
  toolChoice,
77
- }: LanguageModelV3CallOptions) {
78
- const warnings: SharedV3Warning[] = [];
101
+ reasoning,
102
+ }: LanguageModelV4CallOptions) {
103
+ const warnings: SharedV4Warning[] = [];
79
104
 
80
105
  const options =
81
106
  (await parseProviderOptions({
@@ -110,7 +135,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
110
135
 
111
136
  const { input, inputWarnings } = await convertToXaiResponsesInput({
112
137
  prompt,
113
- store: true,
138
+ store: options.store ?? true,
114
139
  });
115
140
  warnings.push(...inputWarnings);
116
141
 
@@ -139,6 +164,24 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
139
164
  }
140
165
  }
141
166
 
167
+ const resolvedReasoningEffort =
168
+ options.reasoningEffort ??
169
+ (isCustomReasoning(reasoning)
170
+ ? reasoning === 'none'
171
+ ? undefined
172
+ : mapReasoningToProviderEffort({
173
+ reasoning,
174
+ effortMap: {
175
+ minimal: 'low',
176
+ low: 'low',
177
+ medium: 'medium',
178
+ high: 'high',
179
+ xhigh: 'high',
180
+ },
181
+ warnings,
182
+ })
183
+ : undefined);
184
+
142
185
  const baseArgs: Record<string, unknown> = {
143
186
  model: this.modelId,
144
187
  input,
@@ -165,8 +208,16 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
165
208
  : { type: 'json_object' },
166
209
  },
167
210
  }),
168
- ...(options.reasoningEffort != null && {
169
- reasoning: { effort: options.reasoningEffort },
211
+ ...((resolvedReasoningEffort != null ||
212
+ options.reasoningSummary != null) && {
213
+ reasoning: {
214
+ ...(resolvedReasoningEffort != null && {
215
+ effort: resolvedReasoningEffort,
216
+ }),
217
+ ...(options.reasoningSummary != null && {
218
+ summary: options.reasoningSummary,
219
+ }),
220
+ },
170
221
  }),
171
222
  ...(options.store === false && {
172
223
  store: options.store,
@@ -199,8 +250,8 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
199
250
  }
200
251
 
201
252
  async doGenerate(
202
- options: LanguageModelV3CallOptions,
203
- ): Promise<LanguageModelV3GenerateResult> {
253
+ options: LanguageModelV4CallOptions,
254
+ ): Promise<LanguageModelV4GenerateResult> {
204
255
  const {
205
256
  args: body,
206
257
  warnings,
@@ -217,7 +268,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
217
268
  rawValue: rawResponse,
218
269
  } = await postJsonToApi({
219
270
  url: `${this.config.baseURL ?? 'https://api.x.ai/v1'}/responses`,
220
- headers: combineHeaders(this.config.headers(), options.headers),
271
+ headers: combineHeaders(this.config.headers?.(), options.headers),
221
272
  body,
222
273
  failedResponseHandler: xaiFailedResponseHandler,
223
274
  successfulResponseHandler: createJsonResponseHandler(
@@ -227,7 +278,8 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
227
278
  fetch: this.config.fetch,
228
279
  });
229
280
 
230
- const content: Array<LanguageModelV3Content> = [];
281
+ const content: Array<LanguageModelV4Content> = [];
282
+ let hasFunctionCall = false;
231
283
 
232
284
  const webSearchSubTools = [
233
285
  'web_search',
@@ -350,6 +402,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
350
402
  }
351
403
 
352
404
  case 'function_call': {
405
+ hasFunctionCall = true;
353
406
  content.push({
354
407
  type: 'tool-call',
355
408
  toolCallId: part.call_id,
@@ -360,16 +413,22 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
360
413
  }
361
414
 
362
415
  case 'reasoning': {
363
- const summaryTexts = part.summary
364
- .map(s => s.text)
365
- .filter(text => text && text.length > 0);
366
-
367
- if (summaryTexts.length > 0) {
368
- const reasoningText = summaryTexts.join('');
369
- if (part.encrypted_content || part.id) {
370
- content.push({
371
- type: 'reasoning',
372
- text: reasoningText,
416
+ const texts =
417
+ part.summary.length > 0
418
+ ? part.summary.map(s => s.text)
419
+ : (part.content ?? []).map(c => c.text);
420
+
421
+ const reasoningText = texts
422
+ .filter(text => text && text.length > 0)
423
+ .join('');
424
+
425
+ // condition changed here since encrypted content can now come with empty reasoning text
426
+ if (reasoningText || part.encrypted_content) {
427
+ const hasMetadata = part.encrypted_content || part.id;
428
+ content.push({
429
+ type: 'reasoning',
430
+ text: reasoningText,
431
+ ...(hasMetadata && {
373
432
  providerMetadata: {
374
433
  xai: {
375
434
  ...(part.encrypted_content && {
@@ -378,13 +437,8 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
378
437
  ...(part.id && { itemId: part.id }),
379
438
  },
380
439
  },
381
- });
382
- } else {
383
- content.push({
384
- type: 'reasoning',
385
- text: reasoningText,
386
- });
387
- }
440
+ }),
441
+ });
388
442
  }
389
443
  break;
390
444
  }
@@ -398,7 +452,9 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
398
452
  return {
399
453
  content,
400
454
  finishReason: {
401
- unified: mapXaiResponsesFinishReason(response.status),
455
+ unified: hasFunctionCall
456
+ ? 'tool-calls'
457
+ : mapXaiResponsesFinishReason(response.status),
402
458
  raw: response.status ?? undefined,
403
459
  },
404
460
  usage: response.usage
@@ -407,6 +463,13 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
407
463
  inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
408
464
  outputTokens: { total: 0, text: 0, reasoning: 0 },
409
465
  },
466
+ ...(response.usage?.cost_in_usd_ticks != null && {
467
+ providerMetadata: {
468
+ xai: {
469
+ costInUsdTicks: response.usage.cost_in_usd_ticks,
470
+ },
471
+ },
472
+ }),
410
473
  request: { body },
411
474
  response: {
412
475
  ...getResponseMetadata(response),
@@ -418,8 +481,8 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
418
481
  }
419
482
 
420
483
  async doStream(
421
- options: LanguageModelV3CallOptions,
422
- ): Promise<LanguageModelV3StreamResult> {
484
+ options: LanguageModelV4CallOptions,
485
+ ): Promise<LanguageModelV4StreamResult> {
423
486
  const {
424
487
  args,
425
488
  warnings,
@@ -436,7 +499,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
436
499
 
437
500
  const { responseHeaders, value: response } = await postJsonToApi({
438
501
  url: `${this.config.baseURL ?? 'https://api.x.ai/v1'}/responses`,
439
- headers: combineHeaders(this.config.headers(), options.headers),
502
+ headers: combineHeaders(this.config.headers?.(), options.headers),
440
503
  body,
441
504
  failedResponseHandler: xaiFailedResponseHandler,
442
505
  successfulResponseHandler: createEventSourceResponseHandler(
@@ -446,11 +509,13 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
446
509
  fetch: this.config.fetch,
447
510
  });
448
511
 
449
- let finishReason: LanguageModelV3FinishReason = {
512
+ let finishReason: LanguageModelV4FinishReason = {
450
513
  unified: 'other',
451
514
  raw: undefined,
452
515
  };
453
- let usage: LanguageModelV3Usage | undefined = undefined;
516
+ let hasFunctionCall = false;
517
+ let usage: LanguageModelV4Usage | undefined = undefined;
518
+ let costInUsdTicks: number | undefined = undefined;
454
519
  let isFirstChunk = true;
455
520
  const contentBlocks: Record<string, { type: 'text' }> = {};
456
521
  const seenToolCalls = new Set<string>();
@@ -473,7 +538,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
473
538
  stream: response.pipeThrough(
474
539
  new TransformStream<
475
540
  ParseResult<z.infer<typeof xaiResponsesChunkSchema>>,
476
- LanguageModelV3StreamPart
541
+ LanguageModelV4StreamPart
477
542
  >({
478
543
  start(controller) {
479
544
  controller.enqueue({ type: 'stream-start', warnings });
@@ -508,16 +573,18 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
508
573
  if (event.type === 'response.reasoning_summary_part.added') {
509
574
  const blockId = `reasoning-${event.item_id}`;
510
575
 
511
- activeReasoning[event.item_id] = {};
512
- controller.enqueue({
513
- type: 'reasoning-start',
514
- id: blockId,
515
- providerMetadata: {
516
- xai: {
517
- itemId: event.item_id,
576
+ if (activeReasoning[event.item_id] == null) {
577
+ activeReasoning[event.item_id] = {};
578
+ controller.enqueue({
579
+ type: 'reasoning-start',
580
+ id: blockId,
581
+ providerMetadata: {
582
+ xai: {
583
+ itemId: event.item_id,
584
+ },
518
585
  },
519
- },
520
- });
586
+ });
587
+ }
521
588
  }
522
589
 
523
590
  if (event.type === 'response.reasoning_summary_text.delta') {
@@ -633,17 +700,32 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
633
700
 
634
701
  if (
635
702
  event.type === 'response.done' ||
636
- event.type === 'response.completed'
703
+ event.type === 'response.completed' ||
704
+ event.type === 'response.incomplete'
637
705
  ) {
638
706
  const response = event.response;
639
707
 
640
708
  if (response.usage) {
641
709
  usage = convertXaiResponsesUsage(response.usage);
710
+ costInUsdTicks = response.usage.cost_in_usd_ticks ?? undefined;
642
711
  }
643
712
 
644
- if (response.status) {
713
+ if (event.type === 'response.incomplete') {
714
+ const reason =
715
+ 'incomplete_details' in response
716
+ ? response.incomplete_details?.reason
717
+ : undefined;
718
+ finishReason = {
719
+ unified: reason
720
+ ? mapXaiResponsesFinishReason(reason)
721
+ : 'other',
722
+ raw: reason ?? 'incomplete',
723
+ };
724
+ } else if ('status' in response && response.status) {
645
725
  finishReason = {
646
- unified: mapXaiResponsesFinishReason(response.status),
726
+ unified: hasFunctionCall
727
+ ? 'tool-calls'
728
+ : mapXaiResponsesFinishReason(response.status),
647
729
  raw: response.status,
648
730
  };
649
731
  }
@@ -651,6 +733,25 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
651
733
  return;
652
734
  }
653
735
 
736
+ if (event.type === 'response.failed') {
737
+ const reason = event.response.incomplete_details?.reason;
738
+ finishReason = {
739
+ unified: reason ? mapXaiResponsesFinishReason(reason) : 'error',
740
+ raw: reason ?? 'error',
741
+ };
742
+
743
+ if (event.response.usage) {
744
+ usage = convertXaiResponsesUsage(event.response.usage);
745
+ }
746
+
747
+ return;
748
+ }
749
+
750
+ if (event.type === 'error') {
751
+ controller.enqueue({ type: 'error', error: event });
752
+ return;
753
+ }
754
+
654
755
  // Custom tool call input streaming - already handled by output_item events
655
756
  if (
656
757
  event.type === 'response.custom_tool_call_input.delta' ||
@@ -911,6 +1012,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
911
1012
  toolName: part.name,
912
1013
  });
913
1014
  } else if (event.type === 'response.output_item.done') {
1015
+ hasFunctionCall = true;
914
1016
  ongoingToolCalls[event.output_index] = undefined;
915
1017
 
916
1018
  controller.enqueue({
@@ -951,6 +1053,13 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
951
1053
  },
952
1054
  outputTokens: { total: 0, text: 0, reasoning: 0 },
953
1055
  },
1056
+ ...(costInUsdTicks != null && {
1057
+ providerMetadata: {
1058
+ xai: {
1059
+ costInUsdTicks,
1060
+ },
1061
+ },
1062
+ }),
954
1063
  });
955
1064
  },
956
1065
  }),
@@ -1,14 +1,15 @@
1
1
  import {
2
- LanguageModelV3CallOptions,
3
- SharedV3Warning,
4
2
  UnsupportedFunctionalityError,
3
+ type LanguageModelV4CallOptions,
4
+ type SharedV4Warning,
5
5
  } from '@ai-sdk/provider';
6
6
  import { validateTypes } from '@ai-sdk/provider-utils';
7
+ import { removeAdditionalPropertiesFalse } from '../remove-additional-properties';
7
8
  import { fileSearchArgsSchema } from '../tool/file-search';
8
9
  import { mcpServerArgsSchema } from '../tool/mcp-server';
9
10
  import { webSearchArgsSchema } from '../tool/web-search';
10
11
  import { xSearchArgsSchema } from '../tool/x-search';
11
- import { XaiResponsesTool } from './xai-responses-api';
12
+ import type { XaiResponsesTool } from './xai-responses-api';
12
13
 
13
14
  type XaiResponsesToolChoice =
14
15
  | 'auto'
@@ -20,16 +21,16 @@ export async function prepareResponsesTools({
20
21
  tools,
21
22
  toolChoice,
22
23
  }: {
23
- tools: LanguageModelV3CallOptions['tools'];
24
- toolChoice?: LanguageModelV3CallOptions['toolChoice'];
24
+ tools: LanguageModelV4CallOptions['tools'];
25
+ toolChoice?: LanguageModelV4CallOptions['toolChoice'];
25
26
  }): Promise<{
26
27
  tools: Array<XaiResponsesTool> | undefined;
27
28
  toolChoice: XaiResponsesToolChoice | undefined;
28
- toolWarnings: SharedV3Warning[];
29
+ toolWarnings: SharedV4Warning[];
29
30
  }> {
30
31
  const normalizedTools = tools?.length ? tools : undefined;
31
32
 
32
- const toolWarnings: SharedV3Warning[] = [];
33
+ const toolWarnings: SharedV4Warning[] = [];
33
34
 
34
35
  if (normalizedTools == null) {
35
36
  return { tools: undefined, toolChoice: undefined, toolWarnings };
@@ -53,6 +54,7 @@ export async function prepareResponsesTools({
53
54
  type: 'web_search',
54
55
  allowed_domains: args.allowedDomains,
55
56
  excluded_domains: args.excludedDomains,
57
+ enable_image_search: args.enableImageSearch,
56
58
  enable_image_understanding: args.enableImageUnderstanding,
57
59
  });
58
60
  break;
@@ -142,7 +144,7 @@ export async function prepareResponsesTools({
142
144
  type: 'function',
143
145
  name: tool.name,
144
146
  description: tool.description,
145
- parameters: tool.inputSchema,
147
+ parameters: removeAdditionalPropertiesFalse(tool.inputSchema),
146
148
  ...(tool.strict != null ? { strict: tool.strict } : {}),
147
149
  });
148
150
  }
@@ -1,4 +1,4 @@
1
- import { createProviderToolFactoryWithOutputSchema } from '@ai-sdk/provider-utils';
1
+ import { createProviderExecutedToolFactory } from '@ai-sdk/provider-utils';
2
2
  import { z } from 'zod/v4';
3
3
 
4
4
  const codeExecutionOutputSchema = z.object({
@@ -6,7 +6,7 @@ const codeExecutionOutputSchema = z.object({
6
6
  error: z.string().optional().describe('any error that occurred'),
7
7
  });
8
8
 
9
- const codeExecutionToolFactory = createProviderToolFactoryWithOutputSchema({
9
+ const codeExecutionToolFactory = createProviderExecutedToolFactory({
10
10
  id: 'xai.code_execution',
11
11
  inputSchema: z.object({}).describe('no input parameters'),
12
12
  outputSchema: codeExecutionOutputSchema,
@@ -1,5 +1,5 @@
1
1
  import {
2
- createProviderToolFactoryWithOutputSchema,
2
+ createProviderExecutedToolFactory,
3
3
  lazySchema,
4
4
  zodSchema,
5
5
  } from '@ai-sdk/provider-utils';
@@ -36,7 +36,7 @@ const fileSearchOutputSchema = lazySchema(() =>
36
36
  ),
37
37
  );
38
38
 
39
- const fileSearchToolFactory = createProviderToolFactoryWithOutputSchema<
39
+ const fileSearchToolFactory = createProviderExecutedToolFactory<
40
40
  {},
41
41
  {
42
42
  /**
@@ -1,5 +1,5 @@
1
1
  import {
2
- createProviderToolFactoryWithOutputSchema,
2
+ createProviderExecutedToolFactory,
3
3
  lazySchema,
4
4
  zodSchema,
5
5
  } from '@ai-sdk/provider-utils';
@@ -41,7 +41,7 @@ const mcpServerOutputSchema = lazySchema(() =>
41
41
  ),
42
42
  );
43
43
 
44
- const mcpServerToolFactory = createProviderToolFactoryWithOutputSchema<
44
+ const mcpServerToolFactory = createProviderExecutedToolFactory<
45
45
  {},
46
46
  {
47
47
  name: string;
@@ -1,4 +1,4 @@
1
- import { createProviderToolFactoryWithOutputSchema } from '@ai-sdk/provider-utils';
1
+ import { createProviderExecutedToolFactory } from '@ai-sdk/provider-utils';
2
2
  import { z } from 'zod/v4';
3
3
 
4
4
  const viewImageOutputSchema = z.object({
@@ -9,7 +9,7 @@ const viewImageOutputSchema = z.object({
9
9
  .describe('objects detected in the image'),
10
10
  });
11
11
 
12
- const viewImageToolFactory = createProviderToolFactoryWithOutputSchema({
12
+ const viewImageToolFactory = createProviderExecutedToolFactory({
13
13
  id: 'xai.view_image',
14
14
  inputSchema: z.object({}).describe('no input parameters'),
15
15
  outputSchema: viewImageOutputSchema,