@ai-sdk/xai 4.0.7 → 4.0.9
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/CHANGELOG.md +16 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +786 -713
- package/dist/index.js.map +1 -1
- package/docs/01-xai.mdx +36 -0
- package/package.json +5 -5
- package/src/convert-to-xai-chat-messages.ts +61 -43
- package/src/index.ts +1 -0
- package/src/responses/convert-to-xai-responses-input.ts +11 -0
- package/src/responses/xai-responses-api.ts +5 -1
- package/src/responses/xai-responses-language-model.ts +25 -17
- package/src/supports-reasoning-effort.ts +12 -0
- package/src/xai-chat-language-model.ts +28 -18
- package/src/xai-chat-prompt.ts +4 -1
- package/src/xai-file-part-options.ts +21 -0
package/docs/01-xai.mdx
CHANGED
|
@@ -212,6 +212,42 @@ const { text } = await generateText({
|
|
|
212
212
|
});
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
You can control the resolution at which the model processes an image with
|
|
216
|
+
the `imageDetail` provider option on the image part:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { xai } from '@ai-sdk/xai';
|
|
220
|
+
import { generateText } from 'ai';
|
|
221
|
+
|
|
222
|
+
const { text } = await generateText({
|
|
223
|
+
model: xai('grok-4.3'),
|
|
224
|
+
messages: [
|
|
225
|
+
{
|
|
226
|
+
role: 'user',
|
|
227
|
+
content: [
|
|
228
|
+
{ type: 'text', text: 'What do you see in this image?' },
|
|
229
|
+
{
|
|
230
|
+
type: 'file',
|
|
231
|
+
mediaType: 'image/png',
|
|
232
|
+
data: fs.readFileSync('./image.png'),
|
|
233
|
+
providerOptions: {
|
|
234
|
+
xai: { imageDetail: 'low' },
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The following image detail values are supported:
|
|
244
|
+
|
|
245
|
+
- **low**: processes the image at reduced resolution and consumes fewer input tokens.
|
|
246
|
+
- **high**: processes the image at full resolution.
|
|
247
|
+
- **auto**: lets the xAI API decide.
|
|
248
|
+
|
|
249
|
+
When not set, the image is processed at full resolution.
|
|
250
|
+
|
|
215
251
|
### Web Search Tool
|
|
216
252
|
|
|
217
253
|
The web search tool enables autonomous web research with optional domain filtering and image understanding:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/xai",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ai-sdk/openai-compatible": "3.0.
|
|
32
|
+
"@ai-sdk/openai-compatible": "3.0.6",
|
|
33
33
|
"@ai-sdk/provider": "4.0.2",
|
|
34
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
34
|
+
"@ai-sdk/provider-utils": "5.0.6"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "22.19.19",
|
|
38
38
|
"tsup": "^8.5.1",
|
|
39
39
|
"typescript": "5.8.3",
|
|
40
40
|
"zod": "3.25.76",
|
|
41
|
-
"@ai-
|
|
42
|
-
"@
|
|
41
|
+
"@vercel/ai-tsconfig": "0.0.0",
|
|
42
|
+
"@ai-sdk/test-server": "2.0.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"zod": "^3.25.76 || ^4.1.8"
|
|
@@ -6,15 +6,19 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
convertToBase64,
|
|
8
8
|
getTopLevelMediaType,
|
|
9
|
+
parseProviderOptions,
|
|
9
10
|
resolveFullMediaType,
|
|
10
11
|
resolveProviderReference,
|
|
11
12
|
} from '@ai-sdk/provider-utils';
|
|
12
|
-
import type { XaiChatPrompt } from './xai-chat-prompt';
|
|
13
|
+
import type { XaiChatPrompt, XaiUserMessageContent } from './xai-chat-prompt';
|
|
14
|
+
import { xaiFilePartProviderOptions } from './xai-file-part-options';
|
|
13
15
|
|
|
14
|
-
export function convertToXaiChatMessages(
|
|
16
|
+
export async function convertToXaiChatMessages(
|
|
17
|
+
prompt: LanguageModelV4Prompt,
|
|
18
|
+
): Promise<{
|
|
15
19
|
messages: XaiChatPrompt;
|
|
16
20
|
warnings: Array<SharedV4Warning>;
|
|
17
|
-
} {
|
|
21
|
+
}> {
|
|
18
22
|
const messages: XaiChatPrompt = [];
|
|
19
23
|
const warnings: Array<SharedV4Warning> = [];
|
|
20
24
|
|
|
@@ -31,54 +35,68 @@ export function convertToXaiChatMessages(prompt: LanguageModelV4Prompt): {
|
|
|
31
35
|
break;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
const userContent: Array<XaiUserMessageContent> = [];
|
|
39
|
+
|
|
40
|
+
for (const part of content) {
|
|
41
|
+
switch (part.type) {
|
|
42
|
+
case 'text': {
|
|
43
|
+
userContent.push({ type: 'text', text: part.text });
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case 'file': {
|
|
47
|
+
switch (part.data.type) {
|
|
48
|
+
case 'reference': {
|
|
49
|
+
userContent.push({
|
|
50
|
+
type: 'file',
|
|
51
|
+
file: {
|
|
52
|
+
file_id: resolveProviderReference({
|
|
53
|
+
reference: part.data.reference,
|
|
54
|
+
provider: 'xai',
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'text': {
|
|
61
|
+
throw new UnsupportedFunctionalityError({
|
|
62
|
+
functionality: 'text file parts',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
case 'url':
|
|
66
|
+
case 'data': {
|
|
67
|
+
if (getTopLevelMediaType(part.mediaType) === 'image') {
|
|
68
|
+
const filePartOptions = await parseProviderOptions({
|
|
69
|
+
provider: 'xai',
|
|
70
|
+
providerOptions: part.providerOptions,
|
|
71
|
+
schema: xaiFilePartProviderOptions,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
userContent.push({
|
|
75
|
+
type: 'image_url',
|
|
76
|
+
image_url: {
|
|
77
|
+
url:
|
|
78
|
+
part.data.type === 'url'
|
|
79
|
+
? part.data.url.toString()
|
|
80
|
+
: `data:${resolveFullMediaType({ part })};base64,${convertToBase64(part.data.data)}`,
|
|
81
|
+
...(filePartOptions?.imageDetail != null && {
|
|
82
|
+
detail: filePartOptions.imageDetail,
|
|
50
83
|
}),
|
|
51
84
|
},
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
case 'text': {
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
55
87
|
throw new UnsupportedFunctionalityError({
|
|
56
|
-
functionality:
|
|
88
|
+
functionality: `file part media type ${part.mediaType}`,
|
|
57
89
|
});
|
|
58
90
|
}
|
|
59
|
-
|
|
60
|
-
case 'data': {
|
|
61
|
-
if (getTopLevelMediaType(part.mediaType) === 'image') {
|
|
62
|
-
return {
|
|
63
|
-
type: 'image_url',
|
|
64
|
-
image_url: {
|
|
65
|
-
url:
|
|
66
|
-
part.data.type === 'url'
|
|
67
|
-
? part.data.url.toString()
|
|
68
|
-
: `data:${resolveFullMediaType({ part })};base64,${convertToBase64(part.data.data)}`,
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
} else {
|
|
72
|
-
throw new UnsupportedFunctionalityError({
|
|
73
|
-
functionality: `file part media type ${part.mediaType}`,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
91
|
+
break;
|
|
77
92
|
}
|
|
78
93
|
}
|
|
94
|
+
break;
|
|
79
95
|
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
messages.push({ role: 'user', content: userContent });
|
|
82
100
|
|
|
83
101
|
break;
|
|
84
102
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type {
|
|
|
4
4
|
XaiLanguageModelChatOptions as XaiProviderOptions,
|
|
5
5
|
} from './xai-chat-language-model-options';
|
|
6
6
|
export type { XaiErrorData } from './xai-error';
|
|
7
|
+
export type { XaiFilePartProviderOptions } from './xai-file-part-options';
|
|
7
8
|
export type {
|
|
8
9
|
XaiLanguageModelResponsesOptions,
|
|
9
10
|
/** @deprecated Use `XaiLanguageModelResponsesOptions` instead. */
|
|
@@ -6,9 +6,11 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
convertToBase64,
|
|
8
8
|
getTopLevelMediaType,
|
|
9
|
+
parseProviderOptions,
|
|
9
10
|
resolveFullMediaType,
|
|
10
11
|
resolveProviderReference,
|
|
11
12
|
} from '@ai-sdk/provider-utils';
|
|
13
|
+
import { xaiFilePartProviderOptions } from '../xai-file-part-options';
|
|
12
14
|
import type {
|
|
13
15
|
XaiResponsesInput,
|
|
14
16
|
XaiResponsesUserMessageContentPart,
|
|
@@ -71,9 +73,18 @@ export async function convertToXaiResponsesInput({
|
|
|
71
73
|
? block.data.url.toString()
|
|
72
74
|
: `data:${resolveFullMediaType({ part: block })};base64,${convertToBase64(block.data.data)}`;
|
|
73
75
|
|
|
76
|
+
const filePartOptions = await parseProviderOptions({
|
|
77
|
+
provider: 'xai',
|
|
78
|
+
providerOptions: block.providerOptions,
|
|
79
|
+
schema: xaiFilePartProviderOptions,
|
|
80
|
+
});
|
|
81
|
+
|
|
74
82
|
contentParts.push({
|
|
75
83
|
type: 'input_image',
|
|
76
84
|
image_url: imageUrl,
|
|
85
|
+
...(filePartOptions?.imageDetail != null && {
|
|
86
|
+
detail: filePartOptions.imageDetail,
|
|
87
|
+
}),
|
|
77
88
|
});
|
|
78
89
|
} else if (block.data.type === 'url') {
|
|
79
90
|
// xAI's Responses API accepts non-image documents (PDF, text, CSV, etc.)
|
|
@@ -26,7 +26,11 @@ export type XaiResponsesSystemMessage = {
|
|
|
26
26
|
|
|
27
27
|
export type XaiResponsesUserMessageContentPart =
|
|
28
28
|
| { type: 'input_text'; text: string }
|
|
29
|
-
| {
|
|
29
|
+
| {
|
|
30
|
+
type: 'input_image';
|
|
31
|
+
image_url: string;
|
|
32
|
+
detail?: 'low' | 'high' | 'auto';
|
|
33
|
+
}
|
|
30
34
|
| { type: 'input_file'; file_id: string }
|
|
31
35
|
| { type: 'input_file'; file_url: string };
|
|
32
36
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from '@ai-sdk/provider-utils';
|
|
26
26
|
import type { z } from 'zod/v4';
|
|
27
27
|
import { getResponseMetadata } from '../get-response-metadata';
|
|
28
|
+
import { supportsReasoningEffort } from '../supports-reasoning-effort';
|
|
28
29
|
import { xaiFailedResponseHandler } from '../xai-error';
|
|
29
30
|
import { convertToXaiResponsesInput } from './convert-to-xai-responses-input';
|
|
30
31
|
import { convertXaiResponsesUsage } from './convert-xai-responses-usage';
|
|
@@ -164,23 +165,30 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
168
|
+
let resolvedReasoningEffort = options.reasoningEffort;
|
|
169
|
+
if (resolvedReasoningEffort == null && isCustomReasoning(reasoning)) {
|
|
170
|
+
if (!supportsReasoningEffort(this.modelId)) {
|
|
171
|
+
warnings.push({
|
|
172
|
+
type: 'unsupported',
|
|
173
|
+
feature: 'reasoning',
|
|
174
|
+
details: `reasoning "${reasoning}" is not supported by this model.`,
|
|
175
|
+
});
|
|
176
|
+
} else if (reasoning === 'none') {
|
|
177
|
+
resolvedReasoningEffort = 'none';
|
|
178
|
+
} else {
|
|
179
|
+
resolvedReasoningEffort = mapReasoningToProviderEffort({
|
|
180
|
+
reasoning,
|
|
181
|
+
effortMap: {
|
|
182
|
+
minimal: 'low',
|
|
183
|
+
low: 'low',
|
|
184
|
+
medium: 'medium',
|
|
185
|
+
high: 'high',
|
|
186
|
+
xhigh: 'high',
|
|
187
|
+
},
|
|
188
|
+
warnings,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
184
192
|
|
|
185
193
|
const baseArgs: Record<string, unknown> = {
|
|
186
194
|
model: this.modelId,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grok-4.20 reasoning and non-reasoning models (including dated variants
|
|
3
|
+
* such as `grok-4.20-0309-reasoning`) reject the reasoning effort parameter
|
|
4
|
+
* with an invalid-argument error for every value, including `none`.
|
|
5
|
+
* Other models such as `grok-4.3`, `grok-latest`, and
|
|
6
|
+
* `grok-4.20-multi-agent` accept it.
|
|
7
|
+
*/
|
|
8
|
+
const modelsWithoutReasoningEffort = /^grok-4\.20(-\d{4})?-(non-)?reasoning$/;
|
|
9
|
+
|
|
10
|
+
export function supportsReasoningEffort(modelId: string): boolean {
|
|
11
|
+
return !modelsWithoutReasoningEffort.test(modelId);
|
|
12
|
+
}
|
|
@@ -31,6 +31,7 @@ import { convertToXaiChatMessages } from './convert-to-xai-chat-messages';
|
|
|
31
31
|
import { convertXaiChatUsage } from './convert-xai-chat-usage';
|
|
32
32
|
import { getResponseMetadata } from './get-response-metadata';
|
|
33
33
|
import { mapXaiFinishReason } from './map-xai-finish-reason';
|
|
34
|
+
import { supportsReasoningEffort } from './supports-reasoning-effort';
|
|
34
35
|
import {
|
|
35
36
|
xaiLanguageModelChatOptions,
|
|
36
37
|
type XaiChatModelId,
|
|
@@ -125,7 +126,7 @@ export class XaiChatLanguageModel implements LanguageModelV4 {
|
|
|
125
126
|
|
|
126
127
|
// convert ai sdk messages to xai format
|
|
127
128
|
const { messages, warnings: messageWarnings } =
|
|
128
|
-
convertToXaiChatMessages(prompt);
|
|
129
|
+
await convertToXaiChatMessages(prompt);
|
|
129
130
|
warnings.push(...messageWarnings);
|
|
130
131
|
|
|
131
132
|
// prepare tools for xai
|
|
@@ -139,6 +140,31 @@ export class XaiChatLanguageModel implements LanguageModelV4 {
|
|
|
139
140
|
});
|
|
140
141
|
warnings.push(...toolWarnings);
|
|
141
142
|
|
|
143
|
+
let reasoningEffort = options.reasoningEffort;
|
|
144
|
+
if (reasoningEffort == null && isCustomReasoning(reasoning)) {
|
|
145
|
+
if (!supportsReasoningEffort(this.modelId)) {
|
|
146
|
+
warnings.push({
|
|
147
|
+
type: 'unsupported',
|
|
148
|
+
feature: 'reasoning',
|
|
149
|
+
details: `reasoning "${reasoning}" is not supported by this model.`,
|
|
150
|
+
});
|
|
151
|
+
} else if (reasoning === 'none') {
|
|
152
|
+
reasoningEffort = 'none';
|
|
153
|
+
} else {
|
|
154
|
+
reasoningEffort = mapReasoningToProviderEffort({
|
|
155
|
+
reasoning,
|
|
156
|
+
effortMap: {
|
|
157
|
+
minimal: 'low',
|
|
158
|
+
low: 'low',
|
|
159
|
+
medium: 'medium',
|
|
160
|
+
high: 'high',
|
|
161
|
+
xhigh: 'high',
|
|
162
|
+
},
|
|
163
|
+
warnings,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
const baseArgs = {
|
|
143
169
|
// model id
|
|
144
170
|
model: this.modelId,
|
|
@@ -153,23 +179,7 @@ export class XaiChatLanguageModel implements LanguageModelV4 {
|
|
|
153
179
|
temperature,
|
|
154
180
|
top_p: topP,
|
|
155
181
|
seed,
|
|
156
|
-
reasoning_effort:
|
|
157
|
-
options.reasoningEffort ??
|
|
158
|
-
(isCustomReasoning(reasoning)
|
|
159
|
-
? reasoning === 'none'
|
|
160
|
-
? undefined
|
|
161
|
-
: mapReasoningToProviderEffort({
|
|
162
|
-
reasoning,
|
|
163
|
-
effortMap: {
|
|
164
|
-
minimal: 'low',
|
|
165
|
-
low: 'low',
|
|
166
|
-
medium: 'medium',
|
|
167
|
-
high: 'high',
|
|
168
|
-
xhigh: 'high',
|
|
169
|
-
},
|
|
170
|
-
warnings,
|
|
171
|
-
})
|
|
172
|
-
: undefined),
|
|
182
|
+
reasoning_effort: reasoningEffort,
|
|
173
183
|
|
|
174
184
|
// parallel function calling
|
|
175
185
|
parallel_function_calling: options.parallel_function_calling,
|
package/src/xai-chat-prompt.ts
CHANGED
|
@@ -18,7 +18,10 @@ export interface XaiUserMessage {
|
|
|
18
18
|
|
|
19
19
|
export type XaiUserMessageContent =
|
|
20
20
|
| { type: 'text'; text: string }
|
|
21
|
-
| {
|
|
21
|
+
| {
|
|
22
|
+
type: 'image_url';
|
|
23
|
+
image_url: { url: string; detail?: 'low' | 'high' | 'auto' };
|
|
24
|
+
}
|
|
22
25
|
| { type: 'file'; file: { file_id: string } };
|
|
23
26
|
|
|
24
27
|
export interface XaiAssistantMessage {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
// provider options for file parts (images) in user messages
|
|
4
|
+
export const xaiFilePartProviderOptions = z.object({
|
|
5
|
+
/**
|
|
6
|
+
* Controls the resolution at which the model processes the image.
|
|
7
|
+
* `low` processes the image at reduced resolution and consumes fewer
|
|
8
|
+
* input tokens, `high` processes the image at full resolution, and
|
|
9
|
+
* `auto` lets the API decide. Defaults to full resolution when not set.
|
|
10
|
+
*
|
|
11
|
+
* Note: the xAI API silently ignores invalid values, so the value is
|
|
12
|
+
* validated client-side.
|
|
13
|
+
*
|
|
14
|
+
* @see https://docs.x.ai/developers/model-capabilities/images/understanding
|
|
15
|
+
*/
|
|
16
|
+
imageDetail: z.enum(['low', 'high', 'auto']).optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type XaiFilePartProviderOptions = z.infer<
|
|
20
|
+
typeof xaiFilePartProviderOptions
|
|
21
|
+
>;
|