@ai-sdk/xai 0.0.0-64aae7dd-20260114144918 → 0.0.0-98261322-20260122142521
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 +64 -5
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/docs/01-xai.mdx +697 -0
- package/package.json +11 -6
- package/src/convert-to-xai-chat-messages.test.ts +243 -0
- package/src/convert-to-xai-chat-messages.ts +142 -0
- package/src/convert-xai-chat-usage.test.ts +240 -0
- package/src/convert-xai-chat-usage.ts +23 -0
- package/src/get-response-metadata.ts +19 -0
- package/src/index.ts +14 -0
- package/src/map-xai-finish-reason.ts +19 -0
- package/src/responses/__fixtures__/xai-code-execution-tool.1.json +68 -0
- package/src/responses/__fixtures__/xai-text-streaming.1.chunks.txt +698 -0
- package/src/responses/__fixtures__/xai-text-with-reasoning-streaming-store-false.1.chunks.txt +655 -0
- package/src/responses/__fixtures__/xai-text-with-reasoning-streaming.1.chunks.txt +679 -0
- package/src/responses/__fixtures__/xai-web-search-tool.1.chunks.txt +274 -0
- package/src/responses/__fixtures__/xai-web-search-tool.1.json +90 -0
- package/src/responses/__fixtures__/xai-x-search-tool.1.json +149 -0
- package/src/responses/__fixtures__/xai-x-search-tool.chunks.txt +1757 -0
- package/src/responses/__snapshots__/xai-responses-language-model.test.ts.snap +21929 -0
- package/src/responses/convert-to-xai-responses-input.test.ts +463 -0
- package/src/responses/convert-to-xai-responses-input.ts +206 -0
- package/src/responses/convert-xai-responses-usage.ts +24 -0
- package/src/responses/map-xai-responses-finish-reason.ts +20 -0
- package/src/responses/xai-responses-api.ts +393 -0
- package/src/responses/xai-responses-language-model.test.ts +1803 -0
- package/src/responses/xai-responses-language-model.ts +732 -0
- package/src/responses/xai-responses-options.ts +34 -0
- package/src/responses/xai-responses-prepare-tools.test.ts +497 -0
- package/src/responses/xai-responses-prepare-tools.ts +226 -0
- package/src/tool/code-execution.ts +17 -0
- package/src/tool/index.ts +15 -0
- package/src/tool/view-image.ts +20 -0
- package/src/tool/view-x-video.ts +18 -0
- package/src/tool/web-search.ts +56 -0
- package/src/tool/x-search.ts +63 -0
- package/src/version.ts +6 -0
- package/src/xai-chat-language-model.test.ts +1805 -0
- package/src/xai-chat-language-model.ts +681 -0
- package/src/xai-chat-options.ts +131 -0
- package/src/xai-chat-prompt.ts +44 -0
- package/src/xai-error.ts +19 -0
- package/src/xai-image-settings.ts +1 -0
- package/src/xai-prepare-tools.ts +95 -0
- package/src/xai-provider.test.ts +167 -0
- package/src/xai-provider.ts +162 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
// https://console.x.ai and see "View models"
|
|
4
|
+
export type XaiChatModelId =
|
|
5
|
+
| 'grok-4-1'
|
|
6
|
+
| 'grok-4-1-fast-reasoning'
|
|
7
|
+
| 'grok-4-1-fast-non-reasoning'
|
|
8
|
+
| 'grok-4-fast-non-reasoning'
|
|
9
|
+
| 'grok-4-fast-reasoning'
|
|
10
|
+
| 'grok-code-fast-1'
|
|
11
|
+
| 'grok-4'
|
|
12
|
+
| 'grok-4-0709'
|
|
13
|
+
| 'grok-4-latest'
|
|
14
|
+
| 'grok-3'
|
|
15
|
+
| 'grok-3-latest'
|
|
16
|
+
| 'grok-3-fast'
|
|
17
|
+
| 'grok-3-fast-latest'
|
|
18
|
+
| 'grok-3-mini'
|
|
19
|
+
| 'grok-3-mini-latest'
|
|
20
|
+
| 'grok-3-mini-fast'
|
|
21
|
+
| 'grok-3-mini-fast-latest'
|
|
22
|
+
| 'grok-2-vision-1212'
|
|
23
|
+
| 'grok-2-vision'
|
|
24
|
+
| 'grok-2-vision-latest'
|
|
25
|
+
| 'grok-2-image-1212'
|
|
26
|
+
| 'grok-2-image'
|
|
27
|
+
| 'grok-2-image-latest'
|
|
28
|
+
| 'grok-2-1212'
|
|
29
|
+
| 'grok-2'
|
|
30
|
+
| 'grok-2-latest'
|
|
31
|
+
| 'grok-vision-beta'
|
|
32
|
+
| 'grok-beta'
|
|
33
|
+
| (string & {});
|
|
34
|
+
|
|
35
|
+
// search source schemas
|
|
36
|
+
const webSourceSchema = z.object({
|
|
37
|
+
type: z.literal('web'),
|
|
38
|
+
country: z.string().length(2).optional(),
|
|
39
|
+
excludedWebsites: z.array(z.string()).max(5).optional(),
|
|
40
|
+
allowedWebsites: z.array(z.string()).max(5).optional(),
|
|
41
|
+
safeSearch: z.boolean().optional(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const xSourceSchema = z.object({
|
|
45
|
+
type: z.literal('x'),
|
|
46
|
+
excludedXHandles: z.array(z.string()).optional(),
|
|
47
|
+
includedXHandles: z.array(z.string()).optional(),
|
|
48
|
+
postFavoriteCount: z.number().int().optional(),
|
|
49
|
+
postViewCount: z.number().int().optional(),
|
|
50
|
+
/**
|
|
51
|
+
* @deprecated use `includedXHandles` instead
|
|
52
|
+
*/
|
|
53
|
+
xHandles: z.array(z.string()).optional(),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const newsSourceSchema = z.object({
|
|
57
|
+
type: z.literal('news'),
|
|
58
|
+
country: z.string().length(2).optional(),
|
|
59
|
+
excludedWebsites: z.array(z.string()).max(5).optional(),
|
|
60
|
+
safeSearch: z.boolean().optional(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const rssSourceSchema = z.object({
|
|
64
|
+
type: z.literal('rss'),
|
|
65
|
+
links: z.array(z.string().url()).max(1), // currently only supports one RSS link
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const searchSourceSchema = z.discriminatedUnion('type', [
|
|
69
|
+
webSourceSchema,
|
|
70
|
+
xSourceSchema,
|
|
71
|
+
newsSourceSchema,
|
|
72
|
+
rssSourceSchema,
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// xai-specific provider options
|
|
76
|
+
export const xaiProviderOptions = z.object({
|
|
77
|
+
reasoningEffort: z.enum(['low', 'high']).optional(),
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Whether to enable parallel function calling during tool use.
|
|
81
|
+
* When true, the model can call multiple functions in parallel.
|
|
82
|
+
* When false, the model will call functions sequentially.
|
|
83
|
+
* Defaults to true.
|
|
84
|
+
*/
|
|
85
|
+
parallel_function_calling: z.boolean().optional(),
|
|
86
|
+
|
|
87
|
+
searchParameters: z
|
|
88
|
+
.object({
|
|
89
|
+
/**
|
|
90
|
+
* search mode preference
|
|
91
|
+
* - "off": disables search completely
|
|
92
|
+
* - "auto": model decides whether to search (default)
|
|
93
|
+
* - "on": always enables search
|
|
94
|
+
*/
|
|
95
|
+
mode: z.enum(['off', 'auto', 'on']),
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* whether to return citations in the response
|
|
99
|
+
* defaults to true
|
|
100
|
+
*/
|
|
101
|
+
returnCitations: z.boolean().optional(),
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* start date for search data (ISO8601 format: YYYY-MM-DD)
|
|
105
|
+
*/
|
|
106
|
+
fromDate: z.string().optional(),
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* end date for search data (ISO8601 format: YYYY-MM-DD)
|
|
110
|
+
*/
|
|
111
|
+
toDate: z.string().optional(),
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* maximum number of search results to consider
|
|
115
|
+
* defaults to 20
|
|
116
|
+
*/
|
|
117
|
+
maxSearchResults: z.number().min(1).max(50).optional(),
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* data sources to search from.
|
|
121
|
+
* defaults to [{ type: 'web' }, { type: 'x' }] if not specified.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* sources: [{ type: 'web', country: 'US' }, { type: 'x' }]
|
|
125
|
+
*/
|
|
126
|
+
sources: z.array(searchSourceSchema).optional(),
|
|
127
|
+
})
|
|
128
|
+
.optional(),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export type XaiProviderOptions = z.infer<typeof xaiProviderOptions>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type XaiChatPrompt = Array<XaiChatMessage>;
|
|
2
|
+
|
|
3
|
+
export type XaiChatMessage =
|
|
4
|
+
| XaiSystemMessage
|
|
5
|
+
| XaiUserMessage
|
|
6
|
+
| XaiAssistantMessage
|
|
7
|
+
| XaiToolMessage;
|
|
8
|
+
|
|
9
|
+
export interface XaiSystemMessage {
|
|
10
|
+
role: 'system';
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface XaiUserMessage {
|
|
15
|
+
role: 'user';
|
|
16
|
+
content: string | Array<XaiUserMessageContent>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type XaiUserMessageContent =
|
|
20
|
+
| { type: 'text'; text: string }
|
|
21
|
+
| { type: 'image_url'; image_url: { url: string } };
|
|
22
|
+
|
|
23
|
+
export interface XaiAssistantMessage {
|
|
24
|
+
role: 'assistant';
|
|
25
|
+
content: string;
|
|
26
|
+
tool_calls?: Array<{
|
|
27
|
+
id: string;
|
|
28
|
+
type: 'function';
|
|
29
|
+
function: { name: string; arguments: string };
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface XaiToolMessage {
|
|
34
|
+
role: 'tool';
|
|
35
|
+
tool_call_id: string;
|
|
36
|
+
content: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// xai tool choice
|
|
40
|
+
export type XaiToolChoice =
|
|
41
|
+
| 'auto'
|
|
42
|
+
| 'none'
|
|
43
|
+
| 'required'
|
|
44
|
+
| { type: 'function'; function: { name: string } };
|
package/src/xai-error.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createJsonErrorResponseHandler } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
// Add error schema and structure
|
|
5
|
+
export const xaiErrorDataSchema = z.object({
|
|
6
|
+
error: z.object({
|
|
7
|
+
message: z.string(),
|
|
8
|
+
type: z.string().nullish(),
|
|
9
|
+
param: z.any().nullish(),
|
|
10
|
+
code: z.union([z.string(), z.number()]).nullish(),
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type XaiErrorData = z.infer<typeof xaiErrorDataSchema>;
|
|
15
|
+
|
|
16
|
+
export const xaiFailedResponseHandler = createJsonErrorResponseHandler({
|
|
17
|
+
errorSchema: xaiErrorDataSchema,
|
|
18
|
+
errorToMessage: data => data.error.message,
|
|
19
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type XaiImageModelId = 'grok-2-image' | (string & {});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LanguageModelV3CallOptions,
|
|
3
|
+
SharedV3Warning,
|
|
4
|
+
UnsupportedFunctionalityError,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
6
|
+
import { XaiToolChoice } from './xai-chat-prompt';
|
|
7
|
+
|
|
8
|
+
export function prepareTools({
|
|
9
|
+
tools,
|
|
10
|
+
toolChoice,
|
|
11
|
+
}: {
|
|
12
|
+
tools: LanguageModelV3CallOptions['tools'];
|
|
13
|
+
toolChoice?: LanguageModelV3CallOptions['toolChoice'];
|
|
14
|
+
}): {
|
|
15
|
+
tools:
|
|
16
|
+
| Array<{
|
|
17
|
+
type: 'function';
|
|
18
|
+
function: {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string | undefined;
|
|
21
|
+
parameters: unknown;
|
|
22
|
+
};
|
|
23
|
+
}>
|
|
24
|
+
| undefined;
|
|
25
|
+
toolChoice: XaiToolChoice | undefined;
|
|
26
|
+
toolWarnings: SharedV3Warning[];
|
|
27
|
+
} {
|
|
28
|
+
// when the tools array is empty, change it to undefined to prevent errors
|
|
29
|
+
tools = tools?.length ? tools : undefined;
|
|
30
|
+
|
|
31
|
+
const toolWarnings: SharedV3Warning[] = [];
|
|
32
|
+
|
|
33
|
+
if (tools == null) {
|
|
34
|
+
return { tools: undefined, toolChoice: undefined, toolWarnings };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// convert ai sdk tools to xai format
|
|
38
|
+
const xaiTools: Array<{
|
|
39
|
+
type: 'function';
|
|
40
|
+
function: {
|
|
41
|
+
name: string;
|
|
42
|
+
description: string | undefined;
|
|
43
|
+
parameters: unknown;
|
|
44
|
+
};
|
|
45
|
+
}> = [];
|
|
46
|
+
|
|
47
|
+
for (const tool of tools) {
|
|
48
|
+
if (tool.type === 'provider') {
|
|
49
|
+
toolWarnings.push({
|
|
50
|
+
type: 'unsupported',
|
|
51
|
+
feature: `provider-defined tool ${tool.name}`,
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
xaiTools.push({
|
|
55
|
+
type: 'function',
|
|
56
|
+
function: {
|
|
57
|
+
name: tool.name,
|
|
58
|
+
description: tool.description,
|
|
59
|
+
parameters: tool.inputSchema,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (toolChoice == null) {
|
|
66
|
+
return { tools: xaiTools, toolChoice: undefined, toolWarnings };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const type = toolChoice.type;
|
|
70
|
+
|
|
71
|
+
switch (type) {
|
|
72
|
+
case 'auto':
|
|
73
|
+
case 'none':
|
|
74
|
+
return { tools: xaiTools, toolChoice: type, toolWarnings };
|
|
75
|
+
case 'required':
|
|
76
|
+
// xai supports 'required' directly
|
|
77
|
+
return { tools: xaiTools, toolChoice: 'required', toolWarnings };
|
|
78
|
+
case 'tool':
|
|
79
|
+
// xai supports specific tool selection
|
|
80
|
+
return {
|
|
81
|
+
tools: xaiTools,
|
|
82
|
+
toolChoice: {
|
|
83
|
+
type: 'function',
|
|
84
|
+
function: { name: toolChoice.toolName },
|
|
85
|
+
},
|
|
86
|
+
toolWarnings,
|
|
87
|
+
};
|
|
88
|
+
default: {
|
|
89
|
+
const _exhaustiveCheck: never = type;
|
|
90
|
+
throw new UnsupportedFunctionalityError({
|
|
91
|
+
functionality: `tool choice type: ${_exhaustiveCheck}`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
|
2
|
+
import { createXai } from './xai-provider';
|
|
3
|
+
import { loadApiKey } from '@ai-sdk/provider-utils';
|
|
4
|
+
import { XaiChatLanguageModel } from './xai-chat-language-model';
|
|
5
|
+
import { OpenAICompatibleImageModel } from '@ai-sdk/openai-compatible';
|
|
6
|
+
|
|
7
|
+
const XaiChatLanguageModelMock = XaiChatLanguageModel as unknown as Mock;
|
|
8
|
+
const OpenAICompatibleImageModelMock =
|
|
9
|
+
OpenAICompatibleImageModel as unknown as Mock;
|
|
10
|
+
|
|
11
|
+
vi.mock('./xai-chat-language-model', () => ({
|
|
12
|
+
XaiChatLanguageModel: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('@ai-sdk/openai-compatible', () => ({
|
|
16
|
+
OpenAICompatibleChatLanguageModel: vi.fn(),
|
|
17
|
+
OpenAICompatibleCompletionLanguageModel: vi.fn(),
|
|
18
|
+
OpenAICompatibleEmbeddingModel: vi.fn(),
|
|
19
|
+
OpenAICompatibleImageModel: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock('./xai-image-model', () => ({
|
|
23
|
+
XaiImageModel: vi.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
vi.mock('@ai-sdk/provider-utils', async () => {
|
|
27
|
+
const actual = await vi.importActual('@ai-sdk/provider-utils');
|
|
28
|
+
return {
|
|
29
|
+
...actual,
|
|
30
|
+
loadApiKey: vi.fn().mockReturnValue('mock-api-key'),
|
|
31
|
+
withoutTrailingSlash: vi.fn(url => url),
|
|
32
|
+
createJsonErrorResponseHandler: vi.fn().mockReturnValue(() => {}),
|
|
33
|
+
generateId: vi.fn().mockReturnValue('mock-id'),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
vi.mock('./version', () => ({
|
|
38
|
+
VERSION: '0.0.0-test',
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
describe('xAIProvider', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('createXAI', () => {
|
|
47
|
+
it('should create an XAIProvider instance with default options', () => {
|
|
48
|
+
const provider = createXai();
|
|
49
|
+
const model = provider('model-id');
|
|
50
|
+
|
|
51
|
+
const constructorCall = XaiChatLanguageModelMock.mock.calls[0];
|
|
52
|
+
const config = constructorCall[1];
|
|
53
|
+
config.headers();
|
|
54
|
+
|
|
55
|
+
expect(loadApiKey).toHaveBeenCalledWith({
|
|
56
|
+
apiKey: undefined,
|
|
57
|
+
environmentVariableName: 'XAI_API_KEY',
|
|
58
|
+
description: 'xAI API key',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should create an XAIProvider instance with custom options', () => {
|
|
63
|
+
const options = {
|
|
64
|
+
apiKey: 'custom-key',
|
|
65
|
+
baseURL: 'https://custom.url',
|
|
66
|
+
headers: { 'Custom-Header': 'value' },
|
|
67
|
+
};
|
|
68
|
+
const provider = createXai(options);
|
|
69
|
+
provider('model-id');
|
|
70
|
+
|
|
71
|
+
const constructorCall = XaiChatLanguageModelMock.mock.calls[0];
|
|
72
|
+
const config = constructorCall[1];
|
|
73
|
+
config.headers();
|
|
74
|
+
|
|
75
|
+
expect(loadApiKey).toHaveBeenCalledWith({
|
|
76
|
+
apiKey: 'custom-key',
|
|
77
|
+
environmentVariableName: 'XAI_API_KEY',
|
|
78
|
+
description: 'xAI API key',
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should return a chat model when called as a function', () => {
|
|
83
|
+
const provider = createXai();
|
|
84
|
+
const modelId = 'foo-model-id';
|
|
85
|
+
|
|
86
|
+
const model = provider(modelId);
|
|
87
|
+
expect(model).toBeInstanceOf(XaiChatLanguageModel);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('chatModel', () => {
|
|
92
|
+
it('should construct a chat model with correct configuration', () => {
|
|
93
|
+
const provider = createXai();
|
|
94
|
+
const modelId = 'xai-chat-model';
|
|
95
|
+
|
|
96
|
+
const model = provider.chat(modelId);
|
|
97
|
+
|
|
98
|
+
expect(model).toBeInstanceOf(XaiChatLanguageModel);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should pass the includeUsage option to the chat model, to make sure usage is reported while streaming', () => {
|
|
102
|
+
const provider = createXai();
|
|
103
|
+
const modelId = 'xai-chat-model';
|
|
104
|
+
|
|
105
|
+
const model = provider.chat(modelId);
|
|
106
|
+
|
|
107
|
+
expect(model).toBeInstanceOf(XaiChatLanguageModel);
|
|
108
|
+
|
|
109
|
+
const constructorCall = XaiChatLanguageModelMock.mock.calls[0];
|
|
110
|
+
|
|
111
|
+
expect(constructorCall[0]).toBe(modelId);
|
|
112
|
+
expect(constructorCall[1].provider).toBe('xai.chat');
|
|
113
|
+
expect(constructorCall[1].baseURL).toBe('https://api.x.ai/v1');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('imageModel', () => {
|
|
118
|
+
it('should construct an image model with correct configuration', () => {
|
|
119
|
+
const provider = createXai();
|
|
120
|
+
const modelId = 'grok-2-image';
|
|
121
|
+
|
|
122
|
+
const model = provider.imageModel(modelId);
|
|
123
|
+
|
|
124
|
+
expect(model).toBeInstanceOf(OpenAICompatibleImageModel);
|
|
125
|
+
|
|
126
|
+
const constructorCall = OpenAICompatibleImageModelMock.mock.calls[0];
|
|
127
|
+
expect(constructorCall[0]).toBe(modelId);
|
|
128
|
+
|
|
129
|
+
const config = constructorCall[1];
|
|
130
|
+
expect(config.provider).toBe('xai.image');
|
|
131
|
+
expect(config.url({ path: '/test-path' })).toBe(
|
|
132
|
+
'https://api.x.ai/v1/test-path',
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should use custom baseURL for image model', () => {
|
|
137
|
+
const customBaseURL = 'https://custom.xai.api';
|
|
138
|
+
const provider = createXai({ baseURL: customBaseURL });
|
|
139
|
+
const modelId = 'grok-2-image';
|
|
140
|
+
|
|
141
|
+
provider.imageModel(modelId);
|
|
142
|
+
|
|
143
|
+
const constructorCall = OpenAICompatibleImageModelMock.mock.calls[0];
|
|
144
|
+
const config = constructorCall[1];
|
|
145
|
+
expect(config.url({ path: '/test-path' })).toBe(
|
|
146
|
+
`${customBaseURL}/test-path`,
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should pass custom headers to image model', () => {
|
|
151
|
+
const customHeaders = { 'Custom-Header': 'test-value' };
|
|
152
|
+
const provider = createXai({ headers: customHeaders });
|
|
153
|
+
|
|
154
|
+
provider.imageModel('grok-2-image');
|
|
155
|
+
|
|
156
|
+
const constructorCall = OpenAICompatibleImageModelMock.mock.calls[0];
|
|
157
|
+
const config = constructorCall[1];
|
|
158
|
+
const headers = config.headers();
|
|
159
|
+
|
|
160
|
+
expect(headers).toMatchObject({
|
|
161
|
+
authorization: 'Bearer mock-api-key',
|
|
162
|
+
'custom-header': 'test-value',
|
|
163
|
+
'user-agent': 'ai-sdk/xai/0.0.0-test',
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpenAICompatibleImageModel,
|
|
3
|
+
ProviderErrorStructure,
|
|
4
|
+
} from '@ai-sdk/openai-compatible';
|
|
5
|
+
import {
|
|
6
|
+
ImageModelV3,
|
|
7
|
+
LanguageModelV3,
|
|
8
|
+
NoSuchModelError,
|
|
9
|
+
ProviderV3,
|
|
10
|
+
} from '@ai-sdk/provider';
|
|
11
|
+
import {
|
|
12
|
+
FetchFunction,
|
|
13
|
+
generateId,
|
|
14
|
+
loadApiKey,
|
|
15
|
+
withoutTrailingSlash,
|
|
16
|
+
withUserAgentSuffix,
|
|
17
|
+
} from '@ai-sdk/provider-utils';
|
|
18
|
+
import { XaiChatLanguageModel } from './xai-chat-language-model';
|
|
19
|
+
import { XaiChatModelId } from './xai-chat-options';
|
|
20
|
+
import { XaiErrorData, xaiErrorDataSchema } from './xai-error';
|
|
21
|
+
import { XaiImageModelId } from './xai-image-settings';
|
|
22
|
+
import { XaiResponsesLanguageModel } from './responses/xai-responses-language-model';
|
|
23
|
+
import { XaiResponsesModelId } from './responses/xai-responses-options';
|
|
24
|
+
import { xaiTools } from './tool';
|
|
25
|
+
import { VERSION } from './version';
|
|
26
|
+
|
|
27
|
+
const xaiErrorStructure: ProviderErrorStructure<XaiErrorData> = {
|
|
28
|
+
errorSchema: xaiErrorDataSchema,
|
|
29
|
+
errorToMessage: data => data.error.message,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface XaiProvider extends ProviderV3 {
|
|
33
|
+
/**
|
|
34
|
+
Creates an Xai chat model for text generation.
|
|
35
|
+
*/
|
|
36
|
+
(modelId: XaiChatModelId): LanguageModelV3;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
Creates an Xai language model for text generation.
|
|
40
|
+
*/
|
|
41
|
+
languageModel(modelId: XaiChatModelId): LanguageModelV3;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
Creates an Xai chat model for text generation.
|
|
45
|
+
*/
|
|
46
|
+
chat: (modelId: XaiChatModelId) => LanguageModelV3;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
Creates an Xai responses model for agentic tool calling.
|
|
50
|
+
*/
|
|
51
|
+
responses: (modelId: XaiResponsesModelId) => LanguageModelV3;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
Creates an Xai image model for image generation.
|
|
55
|
+
*/
|
|
56
|
+
image(modelId: XaiImageModelId): ImageModelV3;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
Creates an Xai image model for image generation.
|
|
60
|
+
*/
|
|
61
|
+
imageModel(modelId: XaiImageModelId): ImageModelV3;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
Server-side agentic tools for use with the responses API.
|
|
65
|
+
*/
|
|
66
|
+
tools: typeof xaiTools;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @deprecated Use `embeddingModel` instead.
|
|
70
|
+
*/
|
|
71
|
+
textEmbeddingModel(modelId: string): never;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface XaiProviderSettings {
|
|
75
|
+
/**
|
|
76
|
+
Base URL for the xAI API calls.
|
|
77
|
+
*/
|
|
78
|
+
baseURL?: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
API key for authenticating requests.
|
|
82
|
+
*/
|
|
83
|
+
apiKey?: string;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
Custom headers to include in the requests.
|
|
87
|
+
*/
|
|
88
|
+
headers?: Record<string, string>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
Custom fetch implementation. You can use it as a middleware to intercept requests,
|
|
92
|
+
or to provide a custom fetch implementation for e.g. testing.
|
|
93
|
+
*/
|
|
94
|
+
fetch?: FetchFunction;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function createXai(options: XaiProviderSettings = {}): XaiProvider {
|
|
98
|
+
const baseURL = withoutTrailingSlash(
|
|
99
|
+
options.baseURL ?? 'https://api.x.ai/v1',
|
|
100
|
+
);
|
|
101
|
+
const getHeaders = () =>
|
|
102
|
+
withUserAgentSuffix(
|
|
103
|
+
{
|
|
104
|
+
Authorization: `Bearer ${loadApiKey({
|
|
105
|
+
apiKey: options.apiKey,
|
|
106
|
+
environmentVariableName: 'XAI_API_KEY',
|
|
107
|
+
description: 'xAI API key',
|
|
108
|
+
})}`,
|
|
109
|
+
...options.headers,
|
|
110
|
+
},
|
|
111
|
+
`ai-sdk/xai/${VERSION}`,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const createChatLanguageModel = (modelId: XaiChatModelId) => {
|
|
115
|
+
return new XaiChatLanguageModel(modelId, {
|
|
116
|
+
provider: 'xai.chat',
|
|
117
|
+
baseURL,
|
|
118
|
+
headers: getHeaders,
|
|
119
|
+
generateId,
|
|
120
|
+
fetch: options.fetch,
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const createResponsesLanguageModel = (modelId: XaiResponsesModelId) => {
|
|
125
|
+
return new XaiResponsesLanguageModel(modelId, {
|
|
126
|
+
provider: 'xai.responses',
|
|
127
|
+
baseURL,
|
|
128
|
+
headers: getHeaders,
|
|
129
|
+
generateId,
|
|
130
|
+
fetch: options.fetch,
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const createImageModel = (modelId: XaiImageModelId) => {
|
|
135
|
+
return new OpenAICompatibleImageModel(modelId, {
|
|
136
|
+
provider: 'xai.image',
|
|
137
|
+
url: ({ path }) => `${baseURL}${path}`,
|
|
138
|
+
headers: getHeaders,
|
|
139
|
+
fetch: options.fetch,
|
|
140
|
+
errorStructure: xaiErrorStructure,
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const provider = (modelId: XaiChatModelId) =>
|
|
145
|
+
createChatLanguageModel(modelId);
|
|
146
|
+
|
|
147
|
+
provider.specificationVersion = 'v3' as const;
|
|
148
|
+
provider.languageModel = createChatLanguageModel;
|
|
149
|
+
provider.chat = createChatLanguageModel;
|
|
150
|
+
provider.responses = createResponsesLanguageModel;
|
|
151
|
+
provider.embeddingModel = (modelId: string) => {
|
|
152
|
+
throw new NoSuchModelError({ modelId, modelType: 'embeddingModel' });
|
|
153
|
+
};
|
|
154
|
+
provider.textEmbeddingModel = provider.embeddingModel;
|
|
155
|
+
provider.imageModel = createImageModel;
|
|
156
|
+
provider.image = createImageModel;
|
|
157
|
+
provider.tools = xaiTools;
|
|
158
|
+
|
|
159
|
+
return provider;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const xai = createXai();
|