@ai-sdk/xai 4.0.59 → 5.0.1
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 +18 -0
- package/dist/index.d.ts +5 -61
- package/dist/index.js +908 -1790
- package/dist/index.js.map +1 -1
- package/docs/01-xai.mdx +2 -12
- package/package.json +3 -3
- package/src/index.ts +0 -5
- package/src/xai-batch.ts +86 -12
- package/src/xai-error.ts +2 -2
- package/src/xai-provider.ts +0 -18
- package/src/convert-to-xai-chat-messages.ts +0 -181
- package/src/convert-xai-chat-usage.ts +0 -29
- package/src/xai-chat-language-model-options.ts +0 -141
- package/src/xai-chat-language-model.ts +0 -778
- package/src/xai-chat-prompt.ts +0 -48
- package/src/xai-prepare-tools.ts +0 -98
|
@@ -1,778 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
APICallError,
|
|
3
|
-
type LanguageModelV4,
|
|
4
|
-
type LanguageModelV4CallOptions,
|
|
5
|
-
type LanguageModelV4Content,
|
|
6
|
-
type LanguageModelV4FinishReason,
|
|
7
|
-
type LanguageModelV4GenerateResult,
|
|
8
|
-
type LanguageModelV4StreamPart,
|
|
9
|
-
type LanguageModelV4StreamResult,
|
|
10
|
-
type LanguageModelV4Usage,
|
|
11
|
-
type SharedV4Warning,
|
|
12
|
-
} from '@ai-sdk/provider';
|
|
13
|
-
import {
|
|
14
|
-
combineHeaders,
|
|
15
|
-
createEventSourceResponseHandler,
|
|
16
|
-
createJsonResponseHandler,
|
|
17
|
-
extractResponseHeaders,
|
|
18
|
-
isCustomReasoning,
|
|
19
|
-
mapReasoningToProviderEffort,
|
|
20
|
-
parseProviderOptions,
|
|
21
|
-
postJsonToApi,
|
|
22
|
-
safeParseJSON,
|
|
23
|
-
serializeModelOptions,
|
|
24
|
-
WORKFLOW_SERIALIZE,
|
|
25
|
-
WORKFLOW_DESERIALIZE,
|
|
26
|
-
type FetchFunction,
|
|
27
|
-
type ParseResult,
|
|
28
|
-
} from '@ai-sdk/provider-utils';
|
|
29
|
-
import { z } from 'zod/v4';
|
|
30
|
-
import { convertToXaiChatMessages } from './convert-to-xai-chat-messages';
|
|
31
|
-
import { convertXaiChatUsage } from './convert-xai-chat-usage';
|
|
32
|
-
import { getResponseMetadata } from './get-response-metadata';
|
|
33
|
-
import { mapXaiFinishReason } from './map-xai-finish-reason';
|
|
34
|
-
import { supportsReasoningEffort } from './supports-reasoning-effort';
|
|
35
|
-
import {
|
|
36
|
-
xaiLanguageModelChatOptions,
|
|
37
|
-
type XaiChatModelId,
|
|
38
|
-
} from './xai-chat-language-model-options';
|
|
39
|
-
import { xaiFailedResponseHandler } from './xai-error';
|
|
40
|
-
import { prepareTools } from './xai-prepare-tools';
|
|
41
|
-
|
|
42
|
-
type XaiChatConfig = {
|
|
43
|
-
provider: string;
|
|
44
|
-
baseURL: string | undefined;
|
|
45
|
-
headers?: () => Record<string, string | undefined>;
|
|
46
|
-
generateId: () => string;
|
|
47
|
-
fetch?: FetchFunction;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export class XaiChatLanguageModel implements LanguageModelV4 {
|
|
51
|
-
readonly specificationVersion = 'v4';
|
|
52
|
-
|
|
53
|
-
readonly modelId: XaiChatModelId;
|
|
54
|
-
|
|
55
|
-
private readonly config: XaiChatConfig;
|
|
56
|
-
|
|
57
|
-
static [WORKFLOW_SERIALIZE](model: XaiChatLanguageModel) {
|
|
58
|
-
return serializeModelOptions({
|
|
59
|
-
modelId: model.modelId,
|
|
60
|
-
config: model.config,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static [WORKFLOW_DESERIALIZE](options: {
|
|
65
|
-
modelId: XaiChatModelId;
|
|
66
|
-
config: XaiChatConfig;
|
|
67
|
-
}) {
|
|
68
|
-
return new XaiChatLanguageModel(options.modelId, options.config);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
constructor(modelId: XaiChatModelId, config: XaiChatConfig) {
|
|
72
|
-
this.modelId = modelId;
|
|
73
|
-
this.config = config;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
get provider(): string {
|
|
77
|
-
return this.config.provider;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
readonly supportedUrls: Record<string, RegExp[]> = {
|
|
81
|
-
'image/*': [/^https?:\/\/.*$/],
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
private async getArgs({
|
|
85
|
-
prompt,
|
|
86
|
-
maxOutputTokens,
|
|
87
|
-
temperature,
|
|
88
|
-
topP,
|
|
89
|
-
topK,
|
|
90
|
-
frequencyPenalty,
|
|
91
|
-
presencePenalty,
|
|
92
|
-
stopSequences,
|
|
93
|
-
seed,
|
|
94
|
-
reasoning,
|
|
95
|
-
responseFormat,
|
|
96
|
-
providerOptions,
|
|
97
|
-
tools,
|
|
98
|
-
toolChoice,
|
|
99
|
-
}: LanguageModelV4CallOptions) {
|
|
100
|
-
const warnings: SharedV4Warning[] = [];
|
|
101
|
-
|
|
102
|
-
// parse xai-specific provider options
|
|
103
|
-
const options =
|
|
104
|
-
(await parseProviderOptions({
|
|
105
|
-
provider: 'xai',
|
|
106
|
-
providerOptions,
|
|
107
|
-
schema: xaiLanguageModelChatOptions,
|
|
108
|
-
})) ?? {};
|
|
109
|
-
|
|
110
|
-
// check for unsupported parameters
|
|
111
|
-
if (topK != null) {
|
|
112
|
-
warnings.push({ type: 'unsupported', feature: 'topK' });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (frequencyPenalty != null) {
|
|
116
|
-
warnings.push({ type: 'unsupported', feature: 'frequencyPenalty' });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (presencePenalty != null) {
|
|
120
|
-
warnings.push({ type: 'unsupported', feature: 'presencePenalty' });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (stopSequences != null) {
|
|
124
|
-
warnings.push({ type: 'unsupported', feature: 'stopSequences' });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// convert ai sdk messages to xai format
|
|
128
|
-
const { messages, warnings: messageWarnings } =
|
|
129
|
-
await convertToXaiChatMessages(prompt);
|
|
130
|
-
warnings.push(...messageWarnings);
|
|
131
|
-
|
|
132
|
-
// prepare tools for xai
|
|
133
|
-
const {
|
|
134
|
-
tools: xaiTools,
|
|
135
|
-
toolChoice: xaiToolChoice,
|
|
136
|
-
toolWarnings,
|
|
137
|
-
} = prepareTools({
|
|
138
|
-
tools,
|
|
139
|
-
toolChoice,
|
|
140
|
-
});
|
|
141
|
-
warnings.push(...toolWarnings);
|
|
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: this.modelId === 'grok-4.6' ? 'xhigh' : 'high',
|
|
162
|
-
},
|
|
163
|
-
warnings,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const baseArgs = {
|
|
169
|
-
// model id
|
|
170
|
-
model: this.modelId,
|
|
171
|
-
|
|
172
|
-
// standard generation settings
|
|
173
|
-
logprobs:
|
|
174
|
-
options.logprobs === true || options.topLogprobs != null
|
|
175
|
-
? true
|
|
176
|
-
: undefined,
|
|
177
|
-
top_logprobs: options.topLogprobs,
|
|
178
|
-
max_completion_tokens: maxOutputTokens,
|
|
179
|
-
temperature,
|
|
180
|
-
top_p: topP,
|
|
181
|
-
seed,
|
|
182
|
-
reasoning_effort: reasoningEffort,
|
|
183
|
-
|
|
184
|
-
// scheduling priority
|
|
185
|
-
service_tier: options.serviceTier,
|
|
186
|
-
|
|
187
|
-
// parallel function calling
|
|
188
|
-
parallel_function_calling: options.parallel_function_calling,
|
|
189
|
-
|
|
190
|
-
// response format
|
|
191
|
-
response_format:
|
|
192
|
-
responseFormat?.type === 'json'
|
|
193
|
-
? responseFormat.schema != null
|
|
194
|
-
? {
|
|
195
|
-
type: 'json_schema',
|
|
196
|
-
json_schema: {
|
|
197
|
-
name: responseFormat.name ?? 'response',
|
|
198
|
-
schema: responseFormat.schema,
|
|
199
|
-
strict: true,
|
|
200
|
-
},
|
|
201
|
-
}
|
|
202
|
-
: { type: 'json_object' }
|
|
203
|
-
: undefined,
|
|
204
|
-
|
|
205
|
-
// search parameters
|
|
206
|
-
search_parameters: options.searchParameters
|
|
207
|
-
? {
|
|
208
|
-
mode: options.searchParameters.mode,
|
|
209
|
-
return_citations: options.searchParameters.returnCitations,
|
|
210
|
-
from_date: options.searchParameters.fromDate,
|
|
211
|
-
to_date: options.searchParameters.toDate,
|
|
212
|
-
max_search_results: options.searchParameters.maxSearchResults,
|
|
213
|
-
sources: options.searchParameters.sources?.map(source => ({
|
|
214
|
-
type: source.type,
|
|
215
|
-
...(source.type === 'web' && {
|
|
216
|
-
country: source.country,
|
|
217
|
-
excluded_websites: source.excludedWebsites,
|
|
218
|
-
allowed_websites: source.allowedWebsites,
|
|
219
|
-
safe_search: source.safeSearch,
|
|
220
|
-
}),
|
|
221
|
-
...(source.type === 'x' && {
|
|
222
|
-
excluded_x_handles: source.excludedXHandles,
|
|
223
|
-
included_x_handles: source.includedXHandles ?? source.xHandles,
|
|
224
|
-
post_favorite_count: source.postFavoriteCount,
|
|
225
|
-
post_view_count: source.postViewCount,
|
|
226
|
-
}),
|
|
227
|
-
...(source.type === 'news' && {
|
|
228
|
-
country: source.country,
|
|
229
|
-
excluded_websites: source.excludedWebsites,
|
|
230
|
-
safe_search: source.safeSearch,
|
|
231
|
-
}),
|
|
232
|
-
...(source.type === 'rss' && {
|
|
233
|
-
links: source.links,
|
|
234
|
-
}),
|
|
235
|
-
})),
|
|
236
|
-
}
|
|
237
|
-
: undefined,
|
|
238
|
-
|
|
239
|
-
// messages in xai format
|
|
240
|
-
messages,
|
|
241
|
-
|
|
242
|
-
// tools in xai format
|
|
243
|
-
tools: xaiTools,
|
|
244
|
-
tool_choice: xaiToolChoice,
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
return {
|
|
248
|
-
args: baseArgs,
|
|
249
|
-
warnings,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async doGenerate(
|
|
254
|
-
options: LanguageModelV4CallOptions,
|
|
255
|
-
): Promise<LanguageModelV4GenerateResult> {
|
|
256
|
-
const { args: body, warnings } = await this.getArgs(options);
|
|
257
|
-
|
|
258
|
-
const url = `${this.config.baseURL ?? 'https://api.x.ai/v1'}/chat/completions`;
|
|
259
|
-
|
|
260
|
-
const {
|
|
261
|
-
responseHeaders,
|
|
262
|
-
value: response,
|
|
263
|
-
rawValue: rawResponse,
|
|
264
|
-
} = await postJsonToApi({
|
|
265
|
-
url,
|
|
266
|
-
headers: combineHeaders(this.config.headers?.(), options.headers),
|
|
267
|
-
body,
|
|
268
|
-
failedResponseHandler: xaiFailedResponseHandler,
|
|
269
|
-
successfulResponseHandler: createJsonResponseHandler(
|
|
270
|
-
xaiChatResponseSchema,
|
|
271
|
-
),
|
|
272
|
-
abortSignal: options.abortSignal,
|
|
273
|
-
fetch: this.config.fetch,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
if (response.error != null) {
|
|
277
|
-
throw new APICallError({
|
|
278
|
-
message: response.error,
|
|
279
|
-
url,
|
|
280
|
-
requestBodyValues: body,
|
|
281
|
-
statusCode: 200,
|
|
282
|
-
responseHeaders,
|
|
283
|
-
responseBody: JSON.stringify(rawResponse),
|
|
284
|
-
isRetryable: response.code === 'The service is currently unavailable',
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const choice = response.choices![0];
|
|
289
|
-
const content: Array<LanguageModelV4Content> = [];
|
|
290
|
-
|
|
291
|
-
// extract text content
|
|
292
|
-
if (choice.message.content != null && choice.message.content.length > 0) {
|
|
293
|
-
let text = choice.message.content;
|
|
294
|
-
|
|
295
|
-
// skip if this content duplicates the last assistant message
|
|
296
|
-
const lastMessage = body.messages[body.messages.length - 1];
|
|
297
|
-
if (lastMessage?.role === 'assistant' && text === lastMessage.content) {
|
|
298
|
-
text = '';
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (text.length > 0) {
|
|
302
|
-
content.push({ type: 'text', text });
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// extract reasoning content
|
|
307
|
-
if (
|
|
308
|
-
choice.message.reasoning_content != null &&
|
|
309
|
-
choice.message.reasoning_content.length > 0
|
|
310
|
-
) {
|
|
311
|
-
content.push({
|
|
312
|
-
type: 'reasoning',
|
|
313
|
-
text: choice.message.reasoning_content,
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// extract tool calls
|
|
318
|
-
if (choice.message.tool_calls != null) {
|
|
319
|
-
for (const toolCall of choice.message.tool_calls) {
|
|
320
|
-
content.push({
|
|
321
|
-
type: 'tool-call',
|
|
322
|
-
toolCallId: toolCall.id,
|
|
323
|
-
toolName: toolCall.function.name,
|
|
324
|
-
input: toolCall.function.arguments,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// extract citations
|
|
330
|
-
if (response.citations != null) {
|
|
331
|
-
for (const url of response.citations) {
|
|
332
|
-
content.push({
|
|
333
|
-
type: 'source',
|
|
334
|
-
sourceType: 'url',
|
|
335
|
-
id: this.config.generateId(),
|
|
336
|
-
url,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
content,
|
|
343
|
-
finishReason: {
|
|
344
|
-
unified: mapXaiFinishReason(choice.finish_reason),
|
|
345
|
-
raw: choice.finish_reason ?? undefined,
|
|
346
|
-
},
|
|
347
|
-
usage: response.usage
|
|
348
|
-
? convertXaiChatUsage(response.usage)
|
|
349
|
-
: {
|
|
350
|
-
inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
|
|
351
|
-
outputTokens: { total: 0, text: 0, reasoning: 0 },
|
|
352
|
-
},
|
|
353
|
-
...(response.service_tier != null && {
|
|
354
|
-
providerMetadata: {
|
|
355
|
-
xai: { serviceTier: response.service_tier },
|
|
356
|
-
},
|
|
357
|
-
}),
|
|
358
|
-
request: { body },
|
|
359
|
-
response: {
|
|
360
|
-
...getResponseMetadata(response),
|
|
361
|
-
headers: responseHeaders,
|
|
362
|
-
body: rawResponse,
|
|
363
|
-
},
|
|
364
|
-
warnings,
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async doStream(
|
|
369
|
-
options: LanguageModelV4CallOptions,
|
|
370
|
-
): Promise<LanguageModelV4StreamResult> {
|
|
371
|
-
const { args, warnings } = await this.getArgs(options);
|
|
372
|
-
const body = {
|
|
373
|
-
...args,
|
|
374
|
-
stream: true,
|
|
375
|
-
stream_options: {
|
|
376
|
-
include_usage: true,
|
|
377
|
-
},
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
const url = `${this.config.baseURL ?? 'https://api.x.ai/v1'}/chat/completions`;
|
|
381
|
-
|
|
382
|
-
const { responseHeaders, value: response } = await postJsonToApi({
|
|
383
|
-
url,
|
|
384
|
-
headers: combineHeaders(this.config.headers?.(), options.headers),
|
|
385
|
-
body,
|
|
386
|
-
failedResponseHandler: xaiFailedResponseHandler,
|
|
387
|
-
successfulResponseHandler: async ({ response }) => {
|
|
388
|
-
const responseHeaders = extractResponseHeaders(response);
|
|
389
|
-
const contentType = response.headers.get('content-type');
|
|
390
|
-
|
|
391
|
-
if (contentType?.includes('application/json')) {
|
|
392
|
-
const responseBody = await response.text();
|
|
393
|
-
const parsedError = await safeParseJSON({
|
|
394
|
-
text: responseBody,
|
|
395
|
-
schema: xaiStreamErrorSchema,
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
if (parsedError.success) {
|
|
399
|
-
throw new APICallError({
|
|
400
|
-
message: parsedError.value.error,
|
|
401
|
-
url,
|
|
402
|
-
requestBodyValues: body,
|
|
403
|
-
statusCode: 200,
|
|
404
|
-
responseHeaders,
|
|
405
|
-
responseBody,
|
|
406
|
-
isRetryable:
|
|
407
|
-
parsedError.value.code ===
|
|
408
|
-
'The service is currently unavailable',
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
throw new APICallError({
|
|
413
|
-
message: 'Invalid JSON response',
|
|
414
|
-
url,
|
|
415
|
-
requestBodyValues: body,
|
|
416
|
-
statusCode: 200,
|
|
417
|
-
responseHeaders,
|
|
418
|
-
responseBody,
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return createEventSourceResponseHandler(xaiChatChunkSchema)({
|
|
423
|
-
response,
|
|
424
|
-
url,
|
|
425
|
-
requestBodyValues: body,
|
|
426
|
-
});
|
|
427
|
-
},
|
|
428
|
-
abortSignal: options.abortSignal,
|
|
429
|
-
fetch: this.config.fetch,
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
let finishReason: LanguageModelV4FinishReason = {
|
|
433
|
-
unified: 'other',
|
|
434
|
-
raw: undefined,
|
|
435
|
-
};
|
|
436
|
-
let usage: LanguageModelV4Usage | undefined = undefined;
|
|
437
|
-
let serviceTier: string | undefined = undefined;
|
|
438
|
-
let isFirstChunk = true;
|
|
439
|
-
const contentBlocks: Record<
|
|
440
|
-
string,
|
|
441
|
-
{ type: 'text' | 'reasoning'; ended: boolean }
|
|
442
|
-
> = {};
|
|
443
|
-
const lastReasoningDeltas: Record<string, string> = {};
|
|
444
|
-
let activeReasoningBlockId: string | undefined = undefined;
|
|
445
|
-
|
|
446
|
-
const self = this;
|
|
447
|
-
|
|
448
|
-
return {
|
|
449
|
-
stream: response.pipeThrough(
|
|
450
|
-
new TransformStream<
|
|
451
|
-
ParseResult<z.infer<typeof xaiChatChunkSchema>>,
|
|
452
|
-
LanguageModelV4StreamPart
|
|
453
|
-
>({
|
|
454
|
-
start(controller) {
|
|
455
|
-
controller.enqueue({ type: 'stream-start', warnings });
|
|
456
|
-
},
|
|
457
|
-
|
|
458
|
-
transform(chunk, controller) {
|
|
459
|
-
// Emit raw chunk if requested (before anything else)
|
|
460
|
-
if (options.includeRawChunks) {
|
|
461
|
-
controller.enqueue({ type: 'raw', rawValue: chunk.rawValue });
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (!chunk.success) {
|
|
465
|
-
controller.enqueue({ type: 'error', error: chunk.error });
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const value = chunk.value;
|
|
470
|
-
|
|
471
|
-
// emit response metadata on first chunk
|
|
472
|
-
if (isFirstChunk) {
|
|
473
|
-
controller.enqueue({
|
|
474
|
-
type: 'response-metadata',
|
|
475
|
-
...getResponseMetadata(value),
|
|
476
|
-
});
|
|
477
|
-
isFirstChunk = false;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// emit citations if present (they come in the last chunk according to docs)
|
|
481
|
-
if (value.citations != null) {
|
|
482
|
-
for (const url of value.citations) {
|
|
483
|
-
controller.enqueue({
|
|
484
|
-
type: 'source',
|
|
485
|
-
sourceType: 'url',
|
|
486
|
-
id: self.config.generateId(),
|
|
487
|
-
url,
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// update usage if present
|
|
493
|
-
if (value.usage != null) {
|
|
494
|
-
usage = convertXaiChatUsage(value.usage);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// the applied tier is repeated on every chunk; keep the latest
|
|
498
|
-
if (value.service_tier != null) {
|
|
499
|
-
serviceTier = value.service_tier;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
const choice = value.choices[0];
|
|
503
|
-
|
|
504
|
-
// update finish reason if present
|
|
505
|
-
if (choice?.finish_reason != null) {
|
|
506
|
-
finishReason = {
|
|
507
|
-
unified: mapXaiFinishReason(choice.finish_reason),
|
|
508
|
-
raw: choice.finish_reason,
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// exit if no delta to process
|
|
513
|
-
if (choice?.delta == null) {
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const delta = choice.delta;
|
|
518
|
-
const choiceIndex = choice.index;
|
|
519
|
-
|
|
520
|
-
// process text content
|
|
521
|
-
if (delta.content != null && delta.content.length > 0) {
|
|
522
|
-
const textContent = delta.content;
|
|
523
|
-
|
|
524
|
-
// end active reasoning block when text content arrives
|
|
525
|
-
if (
|
|
526
|
-
activeReasoningBlockId != null &&
|
|
527
|
-
!contentBlocks[activeReasoningBlockId].ended
|
|
528
|
-
) {
|
|
529
|
-
controller.enqueue({
|
|
530
|
-
type: 'reasoning-end',
|
|
531
|
-
id: activeReasoningBlockId,
|
|
532
|
-
});
|
|
533
|
-
contentBlocks[activeReasoningBlockId].ended = true;
|
|
534
|
-
activeReasoningBlockId = undefined;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// skip if this content duplicates the last assistant message
|
|
538
|
-
const lastMessage = body.messages[body.messages.length - 1];
|
|
539
|
-
if (
|
|
540
|
-
lastMessage?.role === 'assistant' &&
|
|
541
|
-
textContent === lastMessage.content
|
|
542
|
-
) {
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const blockId = `text-${value.id || choiceIndex}`;
|
|
547
|
-
|
|
548
|
-
if (contentBlocks[blockId] == null) {
|
|
549
|
-
contentBlocks[blockId] = { type: 'text', ended: false };
|
|
550
|
-
controller.enqueue({
|
|
551
|
-
type: 'text-start',
|
|
552
|
-
id: blockId,
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
controller.enqueue({
|
|
557
|
-
type: 'text-delta',
|
|
558
|
-
id: blockId,
|
|
559
|
-
delta: textContent,
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// process reasoning content
|
|
564
|
-
if (
|
|
565
|
-
delta.reasoning_content != null &&
|
|
566
|
-
delta.reasoning_content.length > 0
|
|
567
|
-
) {
|
|
568
|
-
const blockId = `reasoning-${value.id || choiceIndex}`;
|
|
569
|
-
|
|
570
|
-
// skip if this reasoning content duplicates the last delta
|
|
571
|
-
if (lastReasoningDeltas[blockId] === delta.reasoning_content) {
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
lastReasoningDeltas[blockId] = delta.reasoning_content;
|
|
575
|
-
|
|
576
|
-
if (contentBlocks[blockId] == null) {
|
|
577
|
-
contentBlocks[blockId] = { type: 'reasoning', ended: false };
|
|
578
|
-
activeReasoningBlockId = blockId;
|
|
579
|
-
controller.enqueue({
|
|
580
|
-
type: 'reasoning-start',
|
|
581
|
-
id: blockId,
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
controller.enqueue({
|
|
586
|
-
type: 'reasoning-delta',
|
|
587
|
-
id: blockId,
|
|
588
|
-
delta: delta.reasoning_content,
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// process tool calls
|
|
593
|
-
if (delta.tool_calls != null && delta.tool_calls.length > 0) {
|
|
594
|
-
// end active reasoning block before tool calls start
|
|
595
|
-
if (
|
|
596
|
-
activeReasoningBlockId != null &&
|
|
597
|
-
!contentBlocks[activeReasoningBlockId].ended
|
|
598
|
-
) {
|
|
599
|
-
controller.enqueue({
|
|
600
|
-
type: 'reasoning-end',
|
|
601
|
-
id: activeReasoningBlockId,
|
|
602
|
-
});
|
|
603
|
-
contentBlocks[activeReasoningBlockId].ended = true;
|
|
604
|
-
activeReasoningBlockId = undefined;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
for (const toolCall of delta.tool_calls) {
|
|
608
|
-
// xai tool calls come in one piece (like mistral)
|
|
609
|
-
const toolCallId = toolCall.id;
|
|
610
|
-
|
|
611
|
-
controller.enqueue({
|
|
612
|
-
type: 'tool-input-start',
|
|
613
|
-
id: toolCallId,
|
|
614
|
-
toolName: toolCall.function.name,
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
controller.enqueue({
|
|
618
|
-
type: 'tool-input-delta',
|
|
619
|
-
id: toolCallId,
|
|
620
|
-
delta: toolCall.function.arguments,
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
controller.enqueue({
|
|
624
|
-
type: 'tool-input-end',
|
|
625
|
-
id: toolCallId,
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
controller.enqueue({
|
|
629
|
-
type: 'tool-call',
|
|
630
|
-
toolCallId,
|
|
631
|
-
toolName: toolCall.function.name,
|
|
632
|
-
input: toolCall.function.arguments,
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
},
|
|
637
|
-
|
|
638
|
-
flush(controller) {
|
|
639
|
-
// end any blocks that haven't been ended yet
|
|
640
|
-
for (const [blockId, block] of Object.entries(contentBlocks)) {
|
|
641
|
-
if (!block.ended) {
|
|
642
|
-
controller.enqueue({
|
|
643
|
-
type: block.type === 'text' ? 'text-end' : 'reasoning-end',
|
|
644
|
-
id: blockId,
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
controller.enqueue({
|
|
650
|
-
type: 'finish',
|
|
651
|
-
finishReason,
|
|
652
|
-
usage: usage ?? {
|
|
653
|
-
inputTokens: {
|
|
654
|
-
total: 0,
|
|
655
|
-
noCache: 0,
|
|
656
|
-
cacheRead: 0,
|
|
657
|
-
cacheWrite: 0,
|
|
658
|
-
},
|
|
659
|
-
outputTokens: { total: 0, text: 0, reasoning: 0 },
|
|
660
|
-
},
|
|
661
|
-
...(serviceTier != null && {
|
|
662
|
-
providerMetadata: { xai: { serviceTier } },
|
|
663
|
-
}),
|
|
664
|
-
});
|
|
665
|
-
},
|
|
666
|
-
}),
|
|
667
|
-
),
|
|
668
|
-
request: { body },
|
|
669
|
-
response: { headers: responseHeaders },
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// XAI API Response Schemas
|
|
675
|
-
const xaiUsageSchema = z
|
|
676
|
-
.object({
|
|
677
|
-
prompt_tokens: z.number(),
|
|
678
|
-
completion_tokens: z.number(),
|
|
679
|
-
total_tokens: z.number(),
|
|
680
|
-
cost_in_usd_ticks: z.number().nullish(),
|
|
681
|
-
prompt_tokens_details: z
|
|
682
|
-
.object({
|
|
683
|
-
text_tokens: z.number().nullish(),
|
|
684
|
-
audio_tokens: z.number().nullish(),
|
|
685
|
-
image_tokens: z.number().nullish(),
|
|
686
|
-
cached_tokens: z.number().nullish(),
|
|
687
|
-
})
|
|
688
|
-
.catchall(z.json())
|
|
689
|
-
.nullish(),
|
|
690
|
-
completion_tokens_details: z
|
|
691
|
-
.object({
|
|
692
|
-
reasoning_tokens: z.number().nullish(),
|
|
693
|
-
audio_tokens: z.number().nullish(),
|
|
694
|
-
accepted_prediction_tokens: z.number().nullish(),
|
|
695
|
-
rejected_prediction_tokens: z.number().nullish(),
|
|
696
|
-
})
|
|
697
|
-
.catchall(z.json())
|
|
698
|
-
.nullish(),
|
|
699
|
-
})
|
|
700
|
-
.catchall(z.json());
|
|
701
|
-
|
|
702
|
-
export type XaiChatUsage = z.infer<typeof xaiUsageSchema>;
|
|
703
|
-
|
|
704
|
-
export const xaiChatResponseSchema = z.object({
|
|
705
|
-
id: z.string().nullish(),
|
|
706
|
-
created: z.number().nullish(),
|
|
707
|
-
model: z.string().nullish(),
|
|
708
|
-
choices: z
|
|
709
|
-
.array(
|
|
710
|
-
z.object({
|
|
711
|
-
message: z.object({
|
|
712
|
-
role: z.enum(['assistant', 'tool']),
|
|
713
|
-
content: z.string().nullish(),
|
|
714
|
-
reasoning_content: z.string().nullish(),
|
|
715
|
-
tool_calls: z
|
|
716
|
-
.array(
|
|
717
|
-
z.object({
|
|
718
|
-
id: z.string(),
|
|
719
|
-
type: z.literal('function'),
|
|
720
|
-
function: z.object({
|
|
721
|
-
name: z.string(),
|
|
722
|
-
arguments: z.string(),
|
|
723
|
-
}),
|
|
724
|
-
}),
|
|
725
|
-
)
|
|
726
|
-
.nullish(),
|
|
727
|
-
}),
|
|
728
|
-
index: z.number(),
|
|
729
|
-
finish_reason: z.string().nullish(),
|
|
730
|
-
}),
|
|
731
|
-
)
|
|
732
|
-
.nullish(),
|
|
733
|
-
object: z.literal('chat.completion').nullish(),
|
|
734
|
-
usage: xaiUsageSchema.nullish(),
|
|
735
|
-
citations: z.array(z.string().url()).nullish(),
|
|
736
|
-
service_tier: z.string().nullish(),
|
|
737
|
-
code: z.string().nullish(),
|
|
738
|
-
error: z.string().nullish(),
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
export type XaiChatResponse = z.infer<typeof xaiChatResponseSchema>;
|
|
742
|
-
|
|
743
|
-
const xaiChatChunkSchema = z.object({
|
|
744
|
-
id: z.string().nullish(),
|
|
745
|
-
created: z.number().nullish(),
|
|
746
|
-
model: z.string().nullish(),
|
|
747
|
-
choices: z.array(
|
|
748
|
-
z.object({
|
|
749
|
-
delta: z.object({
|
|
750
|
-
role: z.enum(['assistant']).optional(),
|
|
751
|
-
content: z.string().nullish(),
|
|
752
|
-
reasoning_content: z.string().nullish(),
|
|
753
|
-
tool_calls: z
|
|
754
|
-
.array(
|
|
755
|
-
z.object({
|
|
756
|
-
id: z.string(),
|
|
757
|
-
type: z.literal('function'),
|
|
758
|
-
function: z.object({
|
|
759
|
-
name: z.string(),
|
|
760
|
-
arguments: z.string(),
|
|
761
|
-
}),
|
|
762
|
-
}),
|
|
763
|
-
)
|
|
764
|
-
.nullish(),
|
|
765
|
-
}),
|
|
766
|
-
finish_reason: z.string().nullish(),
|
|
767
|
-
index: z.number(),
|
|
768
|
-
}),
|
|
769
|
-
),
|
|
770
|
-
usage: xaiUsageSchema.nullish(),
|
|
771
|
-
citations: z.array(z.string().url()).nullish(),
|
|
772
|
-
service_tier: z.string().nullish(),
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
const xaiStreamErrorSchema = z.object({
|
|
776
|
-
code: z.string(),
|
|
777
|
-
error: z.string(),
|
|
778
|
-
});
|