@tanstack/ai-grok 0.12.3 → 0.13.0

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/src/model-meta.ts CHANGED
@@ -1,13 +1,18 @@
1
1
  /**
2
2
  * Model metadata interface for documentation and type inference
3
3
  */
4
+ import type {
5
+ GrokBuildProviderOptions,
6
+ GrokTextProviderOptions,
7
+ } from './text/text-provider-options'
8
+
4
9
  interface ModelMeta {
5
10
  name: string
6
11
  supports: {
7
12
  input: Array<'text' | 'image' | 'audio' | 'video' | 'document'>
8
13
  output: Array<'text' | 'image' | 'audio' | 'video'>
9
14
  capabilities?: Array<'reasoning' | 'tool_calling' | 'structured_outputs'>
10
- tools?: ReadonlyArray<never>
15
+ tools?: ReadonlyArray<GrokProviderToolKind>
11
16
  }
12
17
  max_input_tokens?: number
13
18
  max_output_tokens?: number
@@ -24,184 +29,18 @@ interface ModelMeta {
24
29
  }
25
30
  }
26
31
 
27
- const GROK_4_1_FAST_REASONING = {
28
- name: 'grok-4-1-fast-reasoning',
29
- context_window: 2_000_000,
30
- supports: {
31
- input: ['text', 'image'],
32
- output: ['text'],
33
- capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
34
- tools: [] as const,
35
- },
36
- pricing: {
37
- input: {
38
- normal: 0.2,
39
- cached: 0.05,
40
- },
41
- output: {
42
- normal: 0.5,
43
- },
44
- },
45
- } as const satisfies ModelMeta
46
-
47
- const GROK_4_1_FAST_NON_REASONING = {
48
- name: 'grok-4-1-fast-non-reasoning',
49
- context_window: 2_000_000,
50
- supports: {
51
- input: ['text', 'image'],
52
- output: ['text'],
53
- capabilities: ['structured_outputs', 'tool_calling'],
54
- tools: [] as const,
55
- },
56
- pricing: {
57
- input: {
58
- normal: 0.2,
59
- cached: 0.05,
60
- },
61
- output: {
62
- normal: 0.5,
63
- },
64
- },
65
- } as const satisfies ModelMeta
66
-
67
- const GROK_CODE_FAST_1 = {
68
- name: 'grok-code-fast-1',
69
- context_window: 256_000,
70
- supports: {
71
- input: ['text'],
72
- output: ['text'],
73
- capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
74
- tools: [] as const,
75
- },
76
- pricing: {
77
- input: {
78
- normal: 0.2,
79
- cached: 0.02,
80
- },
81
- output: {
82
- normal: 1.5,
83
- },
84
- },
85
- } as const satisfies ModelMeta
86
-
87
- const GROK_4_FAST_REASONING = {
88
- name: 'grok-4-fast-reasoning',
89
- context_window: 2_000_000,
90
- supports: {
91
- input: ['text', 'image'],
92
- output: ['text'],
93
- capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
94
- tools: [] as const,
95
- },
96
- pricing: {
97
- input: {
98
- normal: 0.2,
99
- cached: 0.05,
100
- },
101
- output: {
102
- normal: 0.5,
103
- },
104
- },
105
- } as const satisfies ModelMeta
106
-
107
- const GROK_4_FAST_NON_REASONING = {
108
- name: 'grok-4-fast-non-reasoning',
109
- context_window: 2_000_000,
110
- supports: {
111
- input: ['text', 'image'],
112
- output: ['text'],
113
- capabilities: ['structured_outputs', 'tool_calling'],
114
- tools: [] as const,
115
- },
116
- pricing: {
117
- input: {
118
- normal: 0.2,
119
- cached: 0.05,
120
- },
121
- output: {
122
- normal: 0.5,
123
- },
124
- },
125
- } as const satisfies ModelMeta
126
-
127
- const GROK_4 = {
128
- name: 'grok-4',
129
- context_window: 256_000,
130
- supports: {
131
- input: ['text', 'image'],
132
- output: ['text'],
133
- capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
134
- tools: [] as const,
135
- },
136
- pricing: {
137
- input: {
138
- normal: 3,
139
- cached: 0.75,
140
- },
141
- output: {
142
- normal: 15,
143
- },
144
- },
145
- } as const satisfies ModelMeta
146
-
147
- const GROK_3_MINI = {
148
- name: 'grok-3-mini',
149
- context_window: 131_072,
150
- supports: {
151
- input: ['text'],
152
- output: ['text'],
153
- capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
154
- tools: [] as const,
155
- },
156
- pricing: {
157
- input: {
158
- normal: 0.3,
159
- cached: 0.075,
160
- },
161
- output: {
162
- normal: 0.5,
163
- },
164
- },
165
- } as const satisfies ModelMeta
32
+ export type GrokProviderToolKind =
33
+ | 'web_search'
34
+ | 'x_search'
35
+ | 'file_search'
36
+ | 'mcp'
166
37
 
167
- const GROK_3 = {
168
- name: 'grok-3',
169
- context_window: 131_072,
170
- supports: {
171
- input: ['text'],
172
- output: ['text'],
173
- capabilities: ['structured_outputs', 'tool_calling'],
174
- tools: [] as const,
175
- },
176
- pricing: {
177
- input: {
178
- normal: 3,
179
- cached: 0.75,
180
- },
181
- output: {
182
- normal: 15,
183
- },
184
- },
185
- } as const satisfies ModelMeta
186
-
187
- const GROK_2_VISION = {
188
- name: 'grok-2-vision-1212',
189
- context_window: 32_768,
190
- supports: {
191
- input: ['text', 'image'],
192
- output: ['text'],
193
- capabilities: ['structured_outputs', 'tool_calling'],
194
- tools: [] as const,
195
- },
196
- pricing: {
197
- input: {
198
- normal: 2,
199
- },
200
- output: {
201
- normal: 10,
202
- },
203
- },
204
- } as const satisfies ModelMeta
38
+ const GROK_RESPONSES_TOOLS = [
39
+ 'web_search',
40
+ 'x_search',
41
+ 'file_search',
42
+ 'mcp',
43
+ ] as const satisfies ReadonlyArray<GrokProviderToolKind>
205
44
 
206
45
  const GROK_2_IMAGE = {
207
46
  name: 'grok-2-image-1212',
@@ -252,50 +91,6 @@ const GROK_IMAGINE_IMAGE_QUALITY = {
252
91
  },
253
92
  } as const satisfies ModelMeta
254
93
 
255
- /**
256
- * Grok Chat Models
257
- * Based on xAI's available models as of 2025
258
- */
259
- const GROK_4_20 = {
260
- name: 'grok-4.20',
261
- context_window: 2_000_000,
262
- supports: {
263
- input: ['text', 'image', 'document'],
264
- output: ['text'],
265
- capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
266
- tools: [] as const,
267
- },
268
- pricing: {
269
- input: {
270
- normal: 2,
271
- cached: 0.2,
272
- },
273
- output: {
274
- normal: 6,
275
- },
276
- },
277
- } as const satisfies ModelMeta
278
-
279
- const GROK_4_20_MULTI_AGENT = {
280
- name: 'grok-4.20-multi-agent',
281
- context_window: 2_000_000,
282
- supports: {
283
- input: ['text', 'image', 'document'],
284
- output: ['text'],
285
- capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
286
- tools: [] as const,
287
- },
288
- pricing: {
289
- input: {
290
- normal: 2,
291
- cached: 0.2,
292
- },
293
- output: {
294
- normal: 6,
295
- },
296
- },
297
- } as const satisfies ModelMeta
298
-
299
94
  const GROK_4_3 = {
300
95
  name: 'grok-4.3',
301
96
  context_window: 1_000_000,
@@ -303,7 +98,7 @@ const GROK_4_3 = {
303
98
  input: ['text', 'image'],
304
99
  output: ['text'],
305
100
  capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
306
- tools: [],
101
+ tools: GROK_RESPONSES_TOOLS,
307
102
  },
308
103
  pricing: {
309
104
  input: {
@@ -323,7 +118,7 @@ const GROK_BUILD_0_1 = {
323
118
  input: ['text', 'image'],
324
119
  output: ['text'],
325
120
  capabilities: ['reasoning', 'structured_outputs', 'tool_calling'],
326
- tools: [],
121
+ tools: GROK_RESPONSES_TOOLS,
327
122
  },
328
123
  pricing: {
329
124
  input: {
@@ -336,48 +131,10 @@ const GROK_BUILD_0_1 = {
336
131
  },
337
132
  } as const satisfies ModelMeta
338
133
 
339
- export const GROK_CHAT_MODELS = [
340
- GROK_4_1_FAST_REASONING.name,
341
- GROK_4_1_FAST_NON_REASONING.name,
342
- GROK_CODE_FAST_1.name,
343
- GROK_4_FAST_REASONING.name,
344
- GROK_4_FAST_NON_REASONING.name,
345
- GROK_4.name,
346
- GROK_3.name,
347
- GROK_3_MINI.name,
348
- GROK_2_VISION.name,
349
-
350
- GROK_4_20.name,
351
- GROK_4_20_MULTI_AGENT.name,
352
-
353
- GROK_4_3.name,
354
-
355
- GROK_BUILD_0_1.name,
356
- ] as const
357
-
358
134
  /**
359
- * Grok models that support combining `tools` + `response_format: json_schema`
360
- * in a single streaming Chat Completions request (per issue #605). xAI
361
- * docs gate this to the Grok 4 family — Grok 2 / 3 reject the
362
- * combination. Grok 2 image generation is not a chat model, omitted.
363
- *
364
- * Note: Grok streams tool-call arguments atomically (not token-streamed)
365
- * per the issue's source matrix; partial-JSON tool-arg parsing should be
366
- * skipped for Grok specifically. That's a separate adapter concern from
367
- * this set — the set only gates whether the engine takes the native
368
- * combined path vs the legacy finalization path.
135
+ * Grok chat models supported by the Responses adapter.
369
136
  */
370
- export const GROK_COMBINED_TOOLS_AND_SCHEMA_MODELS = new Set<string>([
371
- GROK_4_1_FAST_REASONING.name,
372
- GROK_4_1_FAST_NON_REASONING.name,
373
- GROK_CODE_FAST_1.name,
374
- GROK_4_FAST_REASONING.name,
375
- GROK_4_FAST_NON_REASONING.name,
376
- GROK_4.name,
377
- GROK_4_20.name,
378
- GROK_4_20_MULTI_AGENT.name,
379
- GROK_4_3.name,
380
- ])
137
+ export const GROK_CHAT_MODELS = [GROK_BUILD_0_1.name, GROK_4_3.name] as const
381
138
 
382
139
  /**
383
140
  * Grok Image Generation Models
@@ -450,68 +207,28 @@ export type GrokRealtimeModel = (typeof GROK_REALTIME_MODELS)[number]
450
207
  * Used for type inference when constructing multimodal messages.
451
208
  */
452
209
  export type GrokModelInputModalitiesByName = {
453
- [GROK_4_1_FAST_REASONING.name]: typeof GROK_4_1_FAST_REASONING.supports.input
454
- [GROK_4_1_FAST_NON_REASONING.name]: typeof GROK_4_1_FAST_NON_REASONING.supports.input
455
- [GROK_CODE_FAST_1.name]: typeof GROK_CODE_FAST_1.supports.input
456
- [GROK_4_FAST_REASONING.name]: typeof GROK_4_FAST_REASONING.supports.input
457
- [GROK_4_FAST_NON_REASONING.name]: typeof GROK_4_FAST_NON_REASONING.supports.input
458
- [GROK_4.name]: typeof GROK_4.supports.input
459
- [GROK_3.name]: typeof GROK_3.supports.input
460
- [GROK_3_MINI.name]: typeof GROK_3_MINI.supports.input
461
- [GROK_2_VISION.name]: typeof GROK_2_VISION.supports.input
462
- [GROK_4_20.name]: typeof GROK_4_20.supports.input
463
- [GROK_4_20_MULTI_AGENT.name]: typeof GROK_4_20_MULTI_AGENT.supports.input
464
210
  [GROK_4_3.name]: typeof GROK_4_3.supports.input
465
211
  [GROK_BUILD_0_1.name]: typeof GROK_BUILD_0_1.supports.input
466
212
  }
467
213
 
468
- /**
469
- * Type-only map from Grok chat model name to its provider options type.
470
- * Since Grok uses OpenAI-compatible API, we reuse OpenAI provider options.
471
- */
472
- export type GrokChatModelProviderOptionsByName = {
473
- [K in (typeof GROK_CHAT_MODELS)[number]]: GrokProviderOptions
474
- }
475
-
476
214
  /**
477
215
  * Type-only map from Grok chat model name to its supported provider tools.
478
- * Grok exposes no provider-specific tool factories, so every model gets an
479
- * empty tuple. This ensures that passing an Anthropic/OpenAI ProviderTool to
480
- * a Grok adapter produces a compile-time type error.
216
+ * Keeps Grok provider-tool factories type-checked against the models that
217
+ * advertise xAI Responses server-side tools.
481
218
  */
482
219
  export type GrokChatModelToolCapabilitiesByName = {
483
- [GROK_4_1_FAST_REASONING.name]: typeof GROK_4_1_FAST_REASONING.supports.tools
484
- [GROK_4_1_FAST_NON_REASONING.name]: typeof GROK_4_1_FAST_NON_REASONING.supports.tools
485
- [GROK_CODE_FAST_1.name]: typeof GROK_CODE_FAST_1.supports.tools
486
- [GROK_4_FAST_REASONING.name]: typeof GROK_4_FAST_REASONING.supports.tools
487
- [GROK_4_FAST_NON_REASONING.name]: typeof GROK_4_FAST_NON_REASONING.supports.tools
488
- [GROK_4.name]: typeof GROK_4.supports.tools
489
- [GROK_3.name]: typeof GROK_3.supports.tools
490
- [GROK_3_MINI.name]: typeof GROK_3_MINI.supports.tools
491
- [GROK_2_VISION.name]: typeof GROK_2_VISION.supports.tools
492
- [GROK_4_20.name]: typeof GROK_4_20.supports.tools
493
- [GROK_4_20_MULTI_AGENT.name]: typeof GROK_4_20_MULTI_AGENT.supports.tools
220
+ [GROK_4_3.name]: typeof GROK_4_3.supports.tools
221
+ [GROK_BUILD_0_1.name]: typeof GROK_BUILD_0_1.supports.tools
494
222
  }
495
223
 
224
+ export type GrokProviderOptions = GrokTextProviderOptions
225
+
496
226
  /**
497
- * Grok-specific provider options
498
- * Based on OpenAI-compatible API options
227
+ * Type-only map from Grok chat model name to its provider options type.
499
228
  */
500
- export interface GrokProviderOptions {
501
- /** Temperature for response generation (0-2) */
502
- temperature?: number
503
- /** Maximum tokens in the response */
504
- max_tokens?: number
505
- /** Top-p sampling parameter */
506
- top_p?: number
507
- /** Frequency penalty (-2.0 to 2.0) */
508
- frequency_penalty?: number
509
- /** Presence penalty (-2.0 to 2.0) */
510
- presence_penalty?: number
511
- /** Stop sequences */
512
- stop?: string | Array<string>
513
- /** A unique identifier representing your end-user */
514
- user?: string
229
+ export type GrokChatModelProviderOptionsByName = {
230
+ [GROK_4_3.name]: GrokProviderOptions
231
+ [GROK_BUILD_0_1.name]: GrokBuildProviderOptions
515
232
  }
516
233
 
517
234
  // ===========================
@@ -1,10 +1,22 @@
1
1
  /**
2
2
  * Grok Text Provider Options
3
3
  *
4
- * Grok uses an OpenAI-compatible Chat Completions API.
5
- * However, not all OpenAI features may be supported by Grok.
4
+ * Grok uses xAI's OpenAI-compatible Responses API. Engine-managed fields
5
+ * such as `model`, `input`, `tools`, and `text.format` are owned by the
6
+ * adapter; user-supplied values live under `modelOptions`.
6
7
  */
7
8
 
9
+ import type { ResponseCreateParams } from 'openai/resources/responses/responses'
10
+
11
+ export type GrokReasoningEffort = 'none' | 'low' | 'medium' | 'high'
12
+
13
+ export type GrokReasoning = Omit<
14
+ NonNullable<ResponseCreateParams['reasoning']>,
15
+ 'effort'
16
+ > & {
17
+ effort?: GrokReasoningEffort
18
+ }
19
+
8
20
  /**
9
21
  * Base provider options for Grok text/chat models
10
22
  */
@@ -18,9 +30,10 @@ export interface GrokBaseOptions {
18
30
 
19
31
  /**
20
32
  * Grok-specific provider options for text/chat
21
- * Based on OpenAI-compatible API options
33
+ * Based on xAI Responses API options
22
34
  */
23
- export interface GrokTextProviderOptions extends GrokBaseOptions {
35
+ export interface GrokTextProviderOptions
36
+ extends GrokBaseOptions, Record<string, unknown> {
24
37
  /**
25
38
  * Temperature for response generation (0-2)
26
39
  * Higher values make output more random, lower values more focused
@@ -34,19 +47,26 @@ export interface GrokTextProviderOptions extends GrokBaseOptions {
34
47
  /**
35
48
  * Maximum tokens in the response
36
49
  */
37
- max_tokens?: number
50
+ max_output_tokens?: number
38
51
  /**
39
- * Frequency penalty (-2.0 to 2.0)
52
+ * Whether xAI should store the response. Defaults to `false` in the adapter.
40
53
  */
41
- frequency_penalty?: number
54
+ store?: boolean
42
55
  /**
43
- * Presence penalty (-2.0 to 2.0)
56
+ * Additional response fields to include. Defaults to encrypted reasoning.
44
57
  */
45
- presence_penalty?: number
58
+ include?: ResponseCreateParams['include']
46
59
  /**
47
- * Stop sequences
60
+ * xAI/OpenAI-compatible reasoning controls for reasoning-capable models.
48
61
  */
49
- stop?: string | Array<string>
62
+ reasoning?: GrokReasoning
63
+ }
64
+
65
+ export type GrokBuildProviderOptions = Omit<
66
+ GrokTextProviderOptions,
67
+ 'reasoning'
68
+ > & {
69
+ reasoning?: never
50
70
  }
51
71
 
52
72
  /**
@@ -1,5 +1,212 @@
1
- export {
2
- type ChatCompletionFunctionTool as FunctionTool,
3
- convertFunctionToolToChatCompletionsFormat as convertFunctionToolToAdapterFormat,
4
- convertToolsToChatCompletionsFormat as convertToolsToProviderFormat,
5
- } from '@tanstack/openai-base'
1
+ import { brandProviderTool } from '@tanstack/ai'
2
+ import { convertFunctionToolToResponsesFormat } from '@tanstack/openai-base'
3
+ import type { ProviderTool, Tool } from '@tanstack/ai'
4
+ import type { ResponsesFunctionTool } from '@tanstack/openai-base'
5
+ import type { GrokProviderToolKind } from '../model-meta'
6
+
7
+ export type FunctionTool = ResponsesFunctionTool
8
+
9
+ export { convertFunctionToolToResponsesFormat as convertFunctionToolToAdapterFormat }
10
+
11
+ export type GrokProviderTool<TKind extends GrokProviderToolKind> = ProviderTool<
12
+ 'grok',
13
+ TKind
14
+ >
15
+
16
+ type GrokToolKindMarker<TKind extends GrokProviderToolKind> = `grok.${TKind}`
17
+
18
+ export interface GrokWebSearchToolConfig {
19
+ type: 'web_search'
20
+ filters?: {
21
+ allowed_domains?: Array<string>
22
+ excluded_domains?: Array<string>
23
+ }
24
+ enable_image_understanding?: boolean
25
+ enable_image_search?: boolean
26
+ }
27
+
28
+ export interface GrokXSearchToolConfig {
29
+ type: 'x_search'
30
+ allowed_x_handles?: Array<string>
31
+ excluded_x_handles?: Array<string>
32
+ from_date?: string
33
+ to_date?: string
34
+ enable_image_understanding?: boolean
35
+ enable_video_understanding?: boolean
36
+ }
37
+
38
+ export interface GrokFileSearchToolConfig {
39
+ type: 'file_search'
40
+ vector_store_ids: Array<string>
41
+ max_num_results?: number
42
+ }
43
+
44
+ export interface GrokMCPToolConfig {
45
+ type: 'mcp'
46
+ server_label: string
47
+ server_url: string
48
+ allowed_tools?: Array<string>
49
+ server_description?: string
50
+ authorization?: string
51
+ headers?: Record<string, string>
52
+ }
53
+
54
+ export type GrokServerTool =
55
+ | GrokWebSearchToolConfig
56
+ | GrokXSearchToolConfig
57
+ | GrokFileSearchToolConfig
58
+ | GrokMCPToolConfig
59
+
60
+ type GrokProviderToolMetadata<TKind extends GrokProviderToolKind> = Extract<
61
+ GrokServerTool,
62
+ { type: TKind }
63
+ > & {
64
+ __kind: GrokToolKindMarker<TKind>
65
+ }
66
+
67
+ export type GrokResponsesTool = GrokServerTool | ResponsesFunctionTool
68
+
69
+ function providerTool<TKind extends GrokProviderToolKind>(
70
+ kind: TKind,
71
+ description: string,
72
+ metadata: Extract<GrokServerTool, { type: TKind }>,
73
+ ): GrokProviderTool<TKind> {
74
+ return brandProviderTool<GrokProviderTool<TKind>>({
75
+ name: kind,
76
+ description,
77
+ metadata: {
78
+ __kind: `grok.${kind}`,
79
+ ...metadata,
80
+ },
81
+ })
82
+ }
83
+
84
+ export function grokWebSearchTool(
85
+ config: Omit<GrokWebSearchToolConfig, 'type'> = {},
86
+ ): GrokProviderTool<'web_search'> {
87
+ if (
88
+ config.filters?.allowed_domains !== undefined &&
89
+ config.filters.excluded_domains !== undefined
90
+ ) {
91
+ throw new Error(
92
+ 'allowed_domains and excluded_domains cannot both be provided.',
93
+ )
94
+ }
95
+ if (
96
+ config.filters?.allowed_domains !== undefined &&
97
+ config.filters.allowed_domains.length > 5
98
+ ) {
99
+ throw new Error('allowed_domains supports at most 5 domains.')
100
+ }
101
+ if (
102
+ config.filters?.excluded_domains !== undefined &&
103
+ config.filters.excluded_domains.length > 5
104
+ ) {
105
+ throw new Error('excluded_domains supports at most 5 domains.')
106
+ }
107
+ return providerTool('web_search', 'Search the web', {
108
+ type: 'web_search',
109
+ ...config,
110
+ })
111
+ }
112
+
113
+ export function grokXSearchTool(
114
+ config: Omit<GrokXSearchToolConfig, 'type'> = {},
115
+ ): GrokProviderTool<'x_search'> {
116
+ if (
117
+ config.allowed_x_handles !== undefined &&
118
+ config.excluded_x_handles !== undefined
119
+ ) {
120
+ throw new Error(
121
+ 'allowed_x_handles and excluded_x_handles cannot both be provided.',
122
+ )
123
+ }
124
+ if (
125
+ config.allowed_x_handles !== undefined &&
126
+ config.allowed_x_handles.length > 20
127
+ ) {
128
+ throw new Error('allowed_x_handles supports at most 20 handles.')
129
+ }
130
+ if (
131
+ config.excluded_x_handles !== undefined &&
132
+ config.excluded_x_handles.length > 20
133
+ ) {
134
+ throw new Error('excluded_x_handles supports at most 20 handles.')
135
+ }
136
+ return providerTool('x_search', 'Search X posts', {
137
+ type: 'x_search',
138
+ ...config,
139
+ })
140
+ }
141
+
142
+ export function grokFileSearchTool(
143
+ config: Omit<GrokFileSearchToolConfig, 'type'>,
144
+ ): GrokProviderTool<'file_search'> {
145
+ if (config.vector_store_ids.length === 0) {
146
+ throw new Error('vector_store_ids must contain at least one collection id.')
147
+ }
148
+ if (config.max_num_results !== undefined) {
149
+ if (config.max_num_results < 1 || config.max_num_results > 50) {
150
+ throw new Error('max_num_results must be between 1 and 50.')
151
+ }
152
+ }
153
+ return providerTool('file_search', 'Search xAI file collections', {
154
+ type: 'file_search',
155
+ ...config,
156
+ })
157
+ }
158
+
159
+ export function grokMCPTool(
160
+ config: Omit<GrokMCPToolConfig, 'type'>,
161
+ ): GrokProviderTool<'mcp'> {
162
+ if (!config.server_url) {
163
+ throw new Error('server_url must be provided.')
164
+ }
165
+ return providerTool('mcp', config.server_description || 'Remote MCP server', {
166
+ type: 'mcp',
167
+ ...config,
168
+ })
169
+ }
170
+
171
+ function getGrokProviderToolKind(tool: Tool): GrokProviderToolKind | undefined {
172
+ const kind = (tool.metadata as { __kind?: unknown } | undefined)?.__kind
173
+ switch (kind) {
174
+ case 'grok.web_search':
175
+ return 'web_search'
176
+ case 'grok.x_search':
177
+ return 'x_search'
178
+ case 'grok.file_search':
179
+ return 'file_search'
180
+ case 'grok.mcp':
181
+ return 'mcp'
182
+ default:
183
+ return undefined
184
+ }
185
+ }
186
+
187
+ function convertGrokProviderToolToAdapterFormat(
188
+ tool: Tool,
189
+ kind: GrokProviderToolKind,
190
+ ): GrokServerTool {
191
+ const metadata = tool.metadata as GrokProviderToolMetadata<typeof kind>
192
+ if (metadata.type !== kind) {
193
+ throw new Error(
194
+ `convertGrokProviderToolToAdapterFormat: tool "${tool.name}" has mismatched Grok tool metadata.`,
195
+ )
196
+ }
197
+ const { __kind: _kind, ...toolConfig } = metadata
198
+ void _kind
199
+ return toolConfig
200
+ }
201
+
202
+ export function convertToolsToProviderFormat(
203
+ tools: Array<Tool>,
204
+ ): Array<GrokResponsesTool> {
205
+ return tools.map((tool) => {
206
+ const grokProviderToolKind = getGrokProviderToolKind(tool)
207
+ if (grokProviderToolKind) {
208
+ return convertGrokProviderToolToAdapterFormat(tool, grokProviderToolKind)
209
+ }
210
+ return convertFunctionToolToResponsesFormat(tool)
211
+ })
212
+ }