@ai-sdk/xai 3.0.30 → 3.0.32

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": "3.0.30",
3
+ "version": "3.0.32",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -8,10 +8,14 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
10
  "dist/**/*",
11
+ "docs/**/*",
11
12
  "src",
12
13
  "CHANGELOG.md",
13
14
  "README.md"
14
15
  ],
16
+ "directories": {
17
+ "doc": "./docs"
18
+ },
15
19
  "exports": {
16
20
  "./package.json": "./package.json",
17
21
  ".": {
@@ -21,9 +25,9 @@
21
25
  }
22
26
  },
23
27
  "dependencies": {
24
- "@ai-sdk/provider": "3.0.4",
28
+ "@ai-sdk/openai-compatible": "2.0.17",
25
29
  "@ai-sdk/provider-utils": "4.0.8",
26
- "@ai-sdk/openai-compatible": "2.0.17"
30
+ "@ai-sdk/provider": "3.0.4"
27
31
  },
28
32
  "devDependencies": {
29
33
  "@types/node": "20.17.24",
@@ -56,7 +60,7 @@
56
60
  "scripts": {
57
61
  "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
58
62
  "build:watch": "pnpm clean && tsup --watch",
59
- "clean": "del-cli dist *.tsbuildinfo",
63
+ "clean": "del-cli dist docs *.tsbuildinfo",
60
64
  "lint": "eslint \"./**/*.ts*\"",
61
65
  "type-check": "tsc --build",
62
66
  "prettier-check": "prettier --check \"./**/*.ts*\"",
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { createXai, xai } from './xai-provider';
5
5
  export type { XaiProvider, XaiProviderSettings } from './xai-provider';
6
6
  export {
7
7
  codeExecution,
8
+ mcpServer,
8
9
  viewImage,
9
10
  viewXVideo,
10
11
  webSearch,
@@ -83,7 +83,15 @@ export type XaiResponsesTool =
83
83
  | { type: 'view_image' }
84
84
  | { type: 'view_x_video' }
85
85
  | { type: 'file_search' }
86
- | { type: 'mcp' }
86
+ | {
87
+ type: 'mcp';
88
+ server_url: string;
89
+ server_label?: string;
90
+ server_description?: string;
91
+ allowed_tools?: string[];
92
+ headers?: Record<string, string>;
93
+ authorization?: string;
94
+ }
87
95
  | {
88
96
  type: 'function';
89
97
  name: string;
@@ -124,6 +132,16 @@ const toolCallSchema = z.object({
124
132
  action: z.any().optional(),
125
133
  });
126
134
 
135
+ const mcpCallSchema = z.object({
136
+ name: z.string().optional(),
137
+ arguments: z.string().optional(),
138
+ output: z.string().optional(),
139
+ error: z.string().optional(),
140
+ id: z.string(),
141
+ status: z.string(),
142
+ server_label: z.string().optional(),
143
+ });
144
+
127
145
  const outputItemSchema = z.discriminatedUnion('type', [
128
146
  z.object({
129
147
  type: z.literal('web_search_call'),
@@ -153,6 +171,10 @@ const outputItemSchema = z.discriminatedUnion('type', [
153
171
  type: z.literal('custom_tool_call'),
154
172
  ...toolCallSchema.shape,
155
173
  }),
174
+ z.object({
175
+ type: z.literal('mcp_call'),
176
+ ...mcpCallSchema.shape,
177
+ }),
156
178
  z.object({
157
179
  type: z.literal('message'),
158
180
  role: z.string(),
@@ -378,6 +400,62 @@ export const xaiResponsesChunkSchema = z.union([
378
400
  output_index: z.number(),
379
401
  code: z.string(),
380
402
  }),
403
+ z.object({
404
+ type: z.literal('response.custom_tool_call_input.delta'),
405
+ item_id: z.string(),
406
+ output_index: z.number(),
407
+ delta: z.string(),
408
+ }),
409
+ z.object({
410
+ type: z.literal('response.custom_tool_call_input.done'),
411
+ item_id: z.string(),
412
+ output_index: z.number(),
413
+ input: z.string(),
414
+ }),
415
+ z.object({
416
+ type: z.literal('response.mcp_call.in_progress'),
417
+ item_id: z.string(),
418
+ output_index: z.number(),
419
+ }),
420
+ z.object({
421
+ type: z.literal('response.mcp_call.executing'),
422
+ item_id: z.string(),
423
+ output_index: z.number(),
424
+ }),
425
+ z.object({
426
+ type: z.literal('response.mcp_call.completed'),
427
+ item_id: z.string(),
428
+ output_index: z.number(),
429
+ }),
430
+ z.object({
431
+ type: z.literal('response.mcp_call.failed'),
432
+ item_id: z.string(),
433
+ output_index: z.number(),
434
+ }),
435
+ z.object({
436
+ type: z.literal('response.mcp_call_arguments.delta'),
437
+ item_id: z.string(),
438
+ output_index: z.number(),
439
+ delta: z.string(),
440
+ }),
441
+ z.object({
442
+ type: z.literal('response.mcp_call_arguments.done'),
443
+ item_id: z.string(),
444
+ output_index: z.number(),
445
+ arguments: z.string().optional(),
446
+ }),
447
+ z.object({
448
+ type: z.literal('response.mcp_call_output.delta'),
449
+ item_id: z.string(),
450
+ output_index: z.number(),
451
+ delta: z.string(),
452
+ }),
453
+ z.object({
454
+ type: z.literal('response.mcp_call_output.done'),
455
+ item_id: z.string(),
456
+ output_index: z.number(),
457
+ output: z.string().optional(),
458
+ }),
381
459
  z.object({
382
460
  type: z.literal('response.done'),
383
461
  response: xaiResponsesResponseSchema,
@@ -99,6 +99,10 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
99
99
  tool => tool.type === 'provider' && tool.id === 'xai.code_execution',
100
100
  )?.name;
101
101
 
102
+ const mcpToolName = tools?.find(
103
+ tool => tool.type === 'provider' && tool.id === 'xai.mcp',
104
+ )?.name;
105
+
102
106
  const { input, inputWarnings } = await convertToXaiResponsesInput({
103
107
  prompt,
104
108
  store: true,
@@ -162,6 +166,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
162
166
  webSearchToolName,
163
167
  xSearchToolName,
164
168
  codeExecutionToolName,
169
+ mcpToolName,
165
170
  };
166
171
  }
167
172
 
@@ -174,6 +179,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
174
179
  webSearchToolName,
175
180
  xSearchToolName,
176
181
  codeExecutionToolName,
182
+ mcpToolName,
177
183
  } = await this.getArgs(options);
178
184
 
179
185
  const {
@@ -214,7 +220,8 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
214
220
  part.type === 'code_execution_call' ||
215
221
  part.type === 'view_image_call' ||
216
222
  part.type === 'view_x_video_call' ||
217
- part.type === 'custom_tool_call'
223
+ part.type === 'custom_tool_call' ||
224
+ part.type === 'mcp_call'
218
225
  ) {
219
226
  let toolName = part.name ?? '';
220
227
  if (
@@ -233,13 +240,17 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
233
240
  part.type === 'code_execution_call'
234
241
  ) {
235
242
  toolName = codeExecutionToolName ?? 'code_execution';
243
+ } else if (part.type === 'mcp_call') {
244
+ toolName = mcpToolName ?? part.name ?? 'mcp';
236
245
  }
237
246
 
238
247
  // custom_tool_call uses 'input' field, others use 'arguments'
239
248
  const toolInput =
240
249
  part.type === 'custom_tool_call'
241
250
  ? (part.input ?? '')
242
- : (part.arguments ?? '');
251
+ : part.type === 'mcp_call'
252
+ ? (part.arguments ?? '')
253
+ : (part.arguments ?? '');
243
254
 
244
255
  content.push({
245
256
  type: 'tool-call',
@@ -352,6 +363,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
352
363
  webSearchToolName,
353
364
  xSearchToolName,
354
365
  codeExecutionToolName,
366
+ mcpToolName,
355
367
  } = await this.getArgs(options);
356
368
  const body = {
357
369
  ...args,
@@ -533,6 +545,14 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
533
545
  return;
534
546
  }
535
547
 
548
+ // Custom tool call input streaming - already handled by output_item events
549
+ if (
550
+ event.type === 'response.custom_tool_call_input.delta' ||
551
+ event.type === 'response.custom_tool_call_input.done'
552
+ ) {
553
+ return;
554
+ }
555
+
536
556
  if (
537
557
  event.type === 'response.output_item.added' ||
538
558
  event.type === 'response.output_item.done'
@@ -564,7 +584,8 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
564
584
  part.type === 'code_execution_call' ||
565
585
  part.type === 'view_image_call' ||
566
586
  part.type === 'view_x_video_call' ||
567
- part.type === 'custom_tool_call'
587
+ part.type === 'custom_tool_call' ||
588
+ part.type === 'mcp_call'
568
589
  ) {
569
590
  const webSearchSubTools = [
570
591
  'web_search',
@@ -595,13 +616,17 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
595
616
  part.type === 'code_execution_call'
596
617
  ) {
597
618
  toolName = codeExecutionToolName ?? 'code_execution';
619
+ } else if (part.type === 'mcp_call') {
620
+ toolName = mcpToolName ?? part.name ?? 'mcp';
598
621
  }
599
622
 
600
623
  // custom_tool_call uses 'input' field, others use 'arguments'
601
624
  const toolInput =
602
625
  part.type === 'custom_tool_call'
603
626
  ? (part.input ?? '')
604
- : (part.arguments ?? '');
627
+ : part.type === 'mcp_call'
628
+ ? (part.arguments ?? '')
629
+ : (part.arguments ?? '');
605
630
 
606
631
  // for custom_tool_call, input is only available on 'done' event
607
632
  // for other types, input is available on 'added' event
@@ -647,6 +672,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV3 {
647
672
  if (contentPart.text && contentPart.text.length > 0) {
648
673
  const blockId = `text-${part.id}`;
649
674
 
675
+ // Only emit text if we haven't already streamed it via output_text.delta events
650
676
  if (contentBlocks[blockId] == null) {
651
677
  contentBlocks[blockId] = { type: 'text' };
652
678
  controller.enqueue({
@@ -494,4 +494,127 @@ describe('prepareResponsesTools', () => {
494
494
  `);
495
495
  });
496
496
  });
497
+
498
+ describe('mcp', () => {
499
+ it('should prepare mcp tool with required args only', async () => {
500
+ const result = await prepareResponsesTools({
501
+ tools: [
502
+ {
503
+ type: 'provider',
504
+ id: 'xai.mcp',
505
+ name: 'mcp',
506
+ args: {
507
+ serverUrl: 'https://example.com/mcp',
508
+ serverLabel: 'test-server',
509
+ },
510
+ },
511
+ ],
512
+ });
513
+
514
+ expect(result.tools).toMatchInlineSnapshot(`
515
+ [
516
+ {
517
+ "allowed_tools": undefined,
518
+ "authorization": undefined,
519
+ "headers": undefined,
520
+ "server_description": undefined,
521
+ "server_label": "test-server",
522
+ "server_url": "https://example.com/mcp",
523
+ "type": "mcp",
524
+ },
525
+ ]
526
+ `);
527
+ });
528
+
529
+ it('should prepare mcp tool with all optional args', async () => {
530
+ const result = await prepareResponsesTools({
531
+ tools: [
532
+ {
533
+ type: 'provider',
534
+ id: 'xai.mcp',
535
+ name: 'mcp',
536
+ args: {
537
+ serverUrl: 'https://example.com/mcp',
538
+ serverLabel: 'test-server',
539
+ serverDescription: 'A test MCP server',
540
+ allowedTools: ['tool1', 'tool2'],
541
+ headers: { 'X-Custom': 'value' },
542
+ authorization: 'Bearer token123',
543
+ },
544
+ },
545
+ ],
546
+ });
547
+
548
+ expect(result.tools).toMatchInlineSnapshot(`
549
+ [
550
+ {
551
+ "allowed_tools": [
552
+ "tool1",
553
+ "tool2",
554
+ ],
555
+ "authorization": "Bearer token123",
556
+ "headers": {
557
+ "X-Custom": "value",
558
+ },
559
+ "server_description": "A test MCP server",
560
+ "server_label": "test-server",
561
+ "server_url": "https://example.com/mcp",
562
+ "type": "mcp",
563
+ },
564
+ ]
565
+ `);
566
+ });
567
+
568
+ it('should handle mcp tool choice', async () => {
569
+ const result = await prepareResponsesTools({
570
+ tools: [
571
+ {
572
+ type: 'provider',
573
+ id: 'xai.mcp',
574
+ name: 'mcp',
575
+ args: {
576
+ serverUrl: 'https://example.com/mcp',
577
+ serverLabel: 'test-server',
578
+ },
579
+ },
580
+ ],
581
+ toolChoice: { type: 'tool', toolName: 'mcp' },
582
+ });
583
+
584
+ expect(result.toolChoice).toEqual({ type: 'mcp' });
585
+ });
586
+
587
+ it('should handle multiple tools including mcp', async () => {
588
+ const result = await prepareResponsesTools({
589
+ tools: [
590
+ {
591
+ type: 'provider',
592
+ id: 'xai.web_search',
593
+ name: 'web_search',
594
+ args: {},
595
+ },
596
+ {
597
+ type: 'provider',
598
+ id: 'xai.mcp',
599
+ name: 'mcp',
600
+ args: {
601
+ serverUrl: 'https://example.com/mcp',
602
+ serverLabel: 'test-server',
603
+ },
604
+ },
605
+ {
606
+ type: 'function',
607
+ name: 'calculator',
608
+ description: 'calculate numbers',
609
+ inputSchema: { type: 'object', properties: {} },
610
+ },
611
+ ],
612
+ });
613
+
614
+ expect(result.tools).toHaveLength(3);
615
+ expect(result.tools?.[0].type).toBe('web_search');
616
+ expect(result.tools?.[1].type).toBe('mcp');
617
+ expect(result.tools?.[2].type).toBe('function');
618
+ });
619
+ });
497
620
  });
@@ -4,6 +4,7 @@ import {
4
4
  UnsupportedFunctionalityError,
5
5
  } from '@ai-sdk/provider';
6
6
  import { validateTypes } from '@ai-sdk/provider-utils';
7
+ import { mcpServerArgsSchema } from '../tool/mcp-server';
7
8
  import { webSearchArgsSchema } from '../tool/web-search';
8
9
  import { xSearchArgsSchema } from '../tool/x-search';
9
10
  import { XaiResponsesTool } from './xai-responses-api';
@@ -110,8 +111,19 @@ export async function prepareResponsesTools({
110
111
  }
111
112
 
112
113
  case 'xai.mcp': {
114
+ const args = await validateTypes({
115
+ value: tool.args,
116
+ schema: mcpServerArgsSchema,
117
+ });
118
+
113
119
  xaiTools.push({
114
120
  type: 'mcp',
121
+ server_url: args.serverUrl,
122
+ server_label: args.serverLabel,
123
+ server_description: args.serverDescription,
124
+ allowed_tools: args.allowedTools,
125
+ headers: args.headers,
126
+ authorization: args.authorization,
115
127
  });
116
128
  break;
117
129
  }
package/src/tool/index.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  import { codeExecution } from './code-execution';
2
+ import { mcpServer } from './mcp-server';
2
3
  import { viewImage } from './view-image';
3
4
  import { viewXVideo } from './view-x-video';
4
5
  import { webSearch } from './web-search';
5
6
  import { xSearch } from './x-search';
6
7
 
7
- export { codeExecution, viewImage, viewXVideo, webSearch, xSearch };
8
+ export { codeExecution, mcpServer, viewImage, viewXVideo, webSearch, xSearch };
8
9
 
9
10
  export const xaiTools = {
10
11
  codeExecution,
12
+ mcpServer,
11
13
  viewImage,
12
14
  viewXVideo,
13
15
  webSearch,
@@ -0,0 +1,66 @@
1
+ import {
2
+ createProviderToolFactoryWithOutputSchema,
3
+ lazySchema,
4
+ zodSchema,
5
+ } from '@ai-sdk/provider-utils';
6
+ import { z } from 'zod/v4';
7
+
8
+ export const mcpServerArgsSchema = lazySchema(() =>
9
+ zodSchema(
10
+ z.object({
11
+ serverUrl: z.string().describe('The URL of the MCP server'),
12
+ serverLabel: z.string().optional().describe('A label for the MCP server'),
13
+ serverDescription: z
14
+ .string()
15
+ .optional()
16
+ .describe('Description of the MCP server'),
17
+ allowedTools: z
18
+ .array(z.string())
19
+ .optional()
20
+ .describe('List of allowed tool names'),
21
+ headers: z
22
+ .record(z.string(), z.string())
23
+ .optional()
24
+ .describe('Custom headers to send'),
25
+ authorization: z
26
+ .string()
27
+ .optional()
28
+ .describe('Authorization header value'),
29
+ }),
30
+ ),
31
+ );
32
+
33
+ // MCP tool output varies based on which tool is called
34
+ const mcpServerOutputSchema = lazySchema(() =>
35
+ zodSchema(
36
+ z.object({
37
+ name: z.string(),
38
+ arguments: z.string(),
39
+ result: z.unknown(),
40
+ }),
41
+ ),
42
+ );
43
+
44
+ const mcpServerToolFactory = createProviderToolFactoryWithOutputSchema<
45
+ {},
46
+ {
47
+ name: string;
48
+ arguments: string;
49
+ result: unknown;
50
+ },
51
+ {
52
+ serverUrl: string;
53
+ serverLabel?: string;
54
+ serverDescription?: string;
55
+ allowedTools?: string[];
56
+ headers?: Record<string, string>;
57
+ authorization?: string;
58
+ }
59
+ >({
60
+ id: 'xai.mcp',
61
+ inputSchema: lazySchema(() => zodSchema(z.object({}))),
62
+ outputSchema: mcpServerOutputSchema,
63
+ });
64
+
65
+ export const mcpServer = (args: Parameters<typeof mcpServerToolFactory>[0]) =>
66
+ mcpServerToolFactory(args);
@@ -31,7 +31,7 @@ const webSearchOutputSchema = lazySchema(() =>
31
31
  );
32
32
 
33
33
  const webSearchToolFactory = createProviderToolFactoryWithOutputSchema<
34
- Record<string, never>,
34
+ {},
35
35
  {
36
36
  query: string;
37
37
  sources: Array<{
@@ -35,7 +35,7 @@ const xSearchOutputSchema = lazySchema(() =>
35
35
  );
36
36
 
37
37
  const xSearchToolFactory = createProviderToolFactoryWithOutputSchema<
38
- Record<string, never>,
38
+ {},
39
39
  {
40
40
  query: string;
41
41
  posts: Array<{