@gullabs/xai 0.9.0 → 0.16.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/NOTICE +6 -0
- package/README.md +294 -60
- package/dist/index.cjs +1927 -447
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +250 -51
- package/dist/index.d.ts +250 -51
- package/dist/index.js +1925 -449
- package/dist/index.js.map +1 -1
- package/package.json +16 -8
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,582 @@
|
|
|
3
3
|
var core = require('@gullabs/core');
|
|
4
4
|
var zod = require('zod');
|
|
5
5
|
|
|
6
|
+
// src/client.ts
|
|
7
|
+
|
|
8
|
+
// src/sse.ts
|
|
9
|
+
async function* readSseFrames(body, options = {}) {
|
|
10
|
+
const reader = body.getReader();
|
|
11
|
+
const decoder = new TextDecoder("utf-8");
|
|
12
|
+
let pieces = [];
|
|
13
|
+
let skipLf = false;
|
|
14
|
+
let event;
|
|
15
|
+
let data = [];
|
|
16
|
+
let first = true;
|
|
17
|
+
const field = (line) => {
|
|
18
|
+
if (line.startsWith(":")) return;
|
|
19
|
+
const colon = line.indexOf(":");
|
|
20
|
+
const name = colon === -1 ? line : line.slice(0, colon);
|
|
21
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
22
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
23
|
+
if (name === "event") event = value;
|
|
24
|
+
else if (name === "data") data.push(value);
|
|
25
|
+
};
|
|
26
|
+
const abort = {
|
|
27
|
+
happened: false,
|
|
28
|
+
error: new Error("aborted")
|
|
29
|
+
};
|
|
30
|
+
let wake;
|
|
31
|
+
options.aborted?.catch((error) => {
|
|
32
|
+
abort.happened = true;
|
|
33
|
+
abort.error = error instanceof Error ? error : new Error(String(error));
|
|
34
|
+
wake?.(error);
|
|
35
|
+
});
|
|
36
|
+
const nextChunk = () => {
|
|
37
|
+
if (abort.happened) return Promise.reject(abort.error);
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
wake = reject;
|
|
40
|
+
reader.read().then(resolve, reject);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
for (; ; ) {
|
|
45
|
+
const result = await nextChunk();
|
|
46
|
+
if (!result.done) options.onChunk?.();
|
|
47
|
+
let text = result.done ? decoder.decode() : decoder.decode(result.value, { stream: true });
|
|
48
|
+
if (first && text.length > 0) {
|
|
49
|
+
text = text.replace(/^\uFEFF/, "");
|
|
50
|
+
first = false;
|
|
51
|
+
}
|
|
52
|
+
let start = 0;
|
|
53
|
+
if (skipLf && text.length > 0) {
|
|
54
|
+
skipLf = false;
|
|
55
|
+
if (text.charCodeAt(0) === 10) start = 1;
|
|
56
|
+
}
|
|
57
|
+
const lineEnd = /[\r\n]/g;
|
|
58
|
+
lineEnd.lastIndex = start;
|
|
59
|
+
for (let end = lineEnd.exec(text); end !== null; end = lineEnd.exec(text)) {
|
|
60
|
+
pieces.push(text.slice(start, end.index));
|
|
61
|
+
const line = pieces.length === 1 ? pieces[0] : pieces.join("");
|
|
62
|
+
pieces = [];
|
|
63
|
+
start = end.index + 1;
|
|
64
|
+
if (text.charCodeAt(end.index) === 13) {
|
|
65
|
+
if (start < text.length) {
|
|
66
|
+
if (text.charCodeAt(start) === 10) start += 1;
|
|
67
|
+
} else if (!result.done) {
|
|
68
|
+
skipLf = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
lineEnd.lastIndex = start;
|
|
72
|
+
if (line !== "") {
|
|
73
|
+
field(line);
|
|
74
|
+
} else {
|
|
75
|
+
if (event !== void 0 || data.length > 0) {
|
|
76
|
+
yield { event, data: data.join("\n") };
|
|
77
|
+
}
|
|
78
|
+
event = void 0;
|
|
79
|
+
data = [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (start < text.length) pieces.push(text.slice(start));
|
|
83
|
+
if (result.done) return;
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
wake = void 0;
|
|
87
|
+
reader.cancel().catch(() => void 0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/stream.ts
|
|
92
|
+
var XaiStreamError = class extends Error {
|
|
93
|
+
failure;
|
|
94
|
+
progress;
|
|
95
|
+
terminalUsage;
|
|
96
|
+
servedServiceTier;
|
|
97
|
+
constructor(failure, context = {}) {
|
|
98
|
+
super(describeFailure(failure, context.cause), { cause: context.cause });
|
|
99
|
+
this.name = "XaiStreamError";
|
|
100
|
+
this.failure = failure;
|
|
101
|
+
this.progress = context.progress ?? { progressed: false, outputChars: 0 };
|
|
102
|
+
this.terminalUsage = context.terminalUsage;
|
|
103
|
+
this.servedServiceTier = context.serviceTier;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
function describeFailure(failure, cause) {
|
|
107
|
+
switch (failure.kind) {
|
|
108
|
+
case "ended_early":
|
|
109
|
+
return `xAI stream ended before response.completed${failure.lastEventType !== void 0 ? ` (last event: ${failure.lastEventType})` : " (no events)"}`;
|
|
110
|
+
case "error_event":
|
|
111
|
+
return `xAI stream reported an error${failure.code !== void 0 ? ` (code "${failure.code}")` : ""}${failure.message !== void 0 ? `: ${failure.message}` : ""}`;
|
|
112
|
+
case "malformed":
|
|
113
|
+
return `xAI stream is malformed: ${failure.detail}`;
|
|
114
|
+
case "deadline":
|
|
115
|
+
return `the stream was still open after the ${failure.timeoutMs} ms request timeout`;
|
|
116
|
+
case "aborted":
|
|
117
|
+
return `xAI stream was stopped after output began${cause instanceof Error ? `: ${cause.message}` : ""}`;
|
|
118
|
+
case "idle":
|
|
119
|
+
return `the stream sent no bytes (heartbeats included) for ${failure.idleTimeoutMs} ms`;
|
|
120
|
+
case "cut":
|
|
121
|
+
return `xAI stream was cut after output began: ${cause instanceof Error ? cause.message : failure.detail}`;
|
|
122
|
+
case "not_event_stream":
|
|
123
|
+
return `xAI transport returned a non-event-stream body (content-type "${failure.contentType}"); transport.fetch must return the text/event-stream response of the request unchanged`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function isRecord(value) {
|
|
127
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
128
|
+
}
|
|
129
|
+
function deepEqual(a, b) {
|
|
130
|
+
if (a === b) return true;
|
|
131
|
+
if (Array.isArray(a)) {
|
|
132
|
+
return Array.isArray(b) && a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
133
|
+
}
|
|
134
|
+
if (isRecord(a) && isRecord(b)) {
|
|
135
|
+
const keys = Object.keys(a);
|
|
136
|
+
return keys.length === Object.keys(b).length && keys.every((k) => k in b && deepEqual(a[k], b[k]));
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
function sameItemContent(a, b) {
|
|
141
|
+
const strip = (item) => {
|
|
142
|
+
const { id: _id, status: _status, ...rest } = item;
|
|
143
|
+
return rest;
|
|
144
|
+
};
|
|
145
|
+
return deepEqual(strip(a), strip(b));
|
|
146
|
+
}
|
|
147
|
+
function clone(value) {
|
|
148
|
+
return structuredClone(value);
|
|
149
|
+
}
|
|
150
|
+
var TERMINAL_EVENTS = /* @__PURE__ */ new Set([
|
|
151
|
+
"response.completed",
|
|
152
|
+
"response.incomplete",
|
|
153
|
+
"response.failed"
|
|
154
|
+
]);
|
|
155
|
+
var MAX_STREAM_INDEX = 1e4;
|
|
156
|
+
function errorField(event, name) {
|
|
157
|
+
const nested = isRecord(event["error"]) ? event["error"] : void 0;
|
|
158
|
+
const value = event[name] ?? nested?.[name];
|
|
159
|
+
return typeof value === "string" ? value : void 0;
|
|
160
|
+
}
|
|
161
|
+
var CONSUMED_ITEM_TYPES = /* @__PURE__ */ new Set(["message", "reasoning", "function_call"]);
|
|
162
|
+
var OPENING_EVENTS = /* @__PURE__ */ new Set([
|
|
163
|
+
"response.created",
|
|
164
|
+
"response.in_progress",
|
|
165
|
+
"response.queued"
|
|
166
|
+
]);
|
|
167
|
+
function usageOf(response) {
|
|
168
|
+
const usage = response?.["usage"];
|
|
169
|
+
if (isRecord(usage) && typeof usage["input_tokens"] === "number" && typeof usage["output_tokens"] === "number") {
|
|
170
|
+
return usage;
|
|
171
|
+
}
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
function itemOutputChars(item) {
|
|
175
|
+
let chars = 0;
|
|
176
|
+
const text = (value) => {
|
|
177
|
+
if (isRecord(value) && typeof value["text"] === "string")
|
|
178
|
+
chars += value["text"].length;
|
|
179
|
+
};
|
|
180
|
+
if (Array.isArray(item["content"])) item["content"].forEach(text);
|
|
181
|
+
if (Array.isArray(item["summary"])) item["summary"].forEach(text);
|
|
182
|
+
if (typeof item["arguments"] === "string") chars += item["arguments"].length;
|
|
183
|
+
if (typeof item["input"] === "string") chars += item["input"].length;
|
|
184
|
+
return chars;
|
|
185
|
+
}
|
|
186
|
+
var XaiStreamReducer = class {
|
|
187
|
+
items = /* @__PURE__ */ new Map();
|
|
188
|
+
snapshot;
|
|
189
|
+
terminal;
|
|
190
|
+
lastType;
|
|
191
|
+
progressed = false;
|
|
192
|
+
skipped = [];
|
|
193
|
+
/**
|
|
194
|
+
* Applies one SSE frame. A frame with no `data` (a bare `event: keepalive`) or
|
|
195
|
+
* the `[DONE]` sentinel carries nothing and is skipped. A body that is not JSON
|
|
196
|
+
* is a malformed stream; JSON that is not an event is skipped and reported.
|
|
197
|
+
* Returns `true` once the terminal event has been seen.
|
|
198
|
+
*/
|
|
199
|
+
pushFrame(frame) {
|
|
200
|
+
if (frame.data === "" || frame.data === "[DONE]") return false;
|
|
201
|
+
let parsed;
|
|
202
|
+
try {
|
|
203
|
+
parsed = JSON.parse(frame.data);
|
|
204
|
+
} catch (cause) {
|
|
205
|
+
throw new XaiStreamError(
|
|
206
|
+
{
|
|
207
|
+
kind: "malformed",
|
|
208
|
+
detail: `an event body is not valid JSON (${cause instanceof Error ? cause.message : String(cause)})`
|
|
209
|
+
},
|
|
210
|
+
{ ...this.context(), cause }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (isRecord(parsed) && typeof parsed["type"] !== "string") {
|
|
214
|
+
if (frame.event === "error" || isRecord(parsed["error"])) {
|
|
215
|
+
return this.push({ ...parsed, type: "error" });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return this.push(parsed);
|
|
219
|
+
}
|
|
220
|
+
/** Applies one event. Returns `true` once the terminal event has been seen. */
|
|
221
|
+
push(event) {
|
|
222
|
+
if (!isRecord(event) || typeof event["type"] !== "string") {
|
|
223
|
+
this.skipped.push("an event that has no string `type`");
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const type = event["type"];
|
|
227
|
+
this.lastType = type;
|
|
228
|
+
if (TERMINAL_EVENTS.has(type)) {
|
|
229
|
+
const response = event["response"];
|
|
230
|
+
if (!isRecord(response)) {
|
|
231
|
+
throw new XaiStreamError(
|
|
232
|
+
{ kind: "malformed", detail: `${type} carries no response object` },
|
|
233
|
+
this.context()
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
this.terminal = { type, response };
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
if (!OPENING_EVENTS.has(type) && type !== "error" && type.startsWith("response.")) {
|
|
240
|
+
this.progressed = true;
|
|
241
|
+
}
|
|
242
|
+
switch (type) {
|
|
243
|
+
case "response.created":
|
|
244
|
+
case "response.in_progress":
|
|
245
|
+
if (isRecord(event["response"])) this.snapshot = event["response"];
|
|
246
|
+
return false;
|
|
247
|
+
case "error":
|
|
248
|
+
throw new XaiStreamError(
|
|
249
|
+
{
|
|
250
|
+
kind: "error_event",
|
|
251
|
+
code: errorField(event, "code"),
|
|
252
|
+
message: errorField(event, "message")
|
|
253
|
+
},
|
|
254
|
+
this.context()
|
|
255
|
+
);
|
|
256
|
+
case "response.output_item.added":
|
|
257
|
+
case "response.output_item.done": {
|
|
258
|
+
const rawIndex = event["output_index"];
|
|
259
|
+
const index = typeof rawIndex === "number" && Number.isInteger(rawIndex) && rawIndex >= 0 ? this.index(rawIndex, "output_index") : void 0;
|
|
260
|
+
const item = event["item"];
|
|
261
|
+
if (index === void 0 || !isRecord(item) || typeof item["type"] !== "string") {
|
|
262
|
+
this.skipped.push(`a ${type} event with no integer output_index or typed item`);
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
this.items.set(index, { item: clone(item), done: type.endsWith(".done") });
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
default:
|
|
269
|
+
this.applyDelta(type, event);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* An event index, or `undefined` when the event has none or it is not a
|
|
275
|
+
* non-negative integer (the event is then skipped, noted). An index above
|
|
276
|
+
* {@link MAX_STREAM_INDEX} is a malformed stream, not a value to write.
|
|
277
|
+
*/
|
|
278
|
+
index(value, name) {
|
|
279
|
+
if (typeof value !== "number") return void 0;
|
|
280
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
281
|
+
this.skipped.push(`an event whose ${name} is not a non-negative integer`);
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
if (value > MAX_STREAM_INDEX) {
|
|
285
|
+
throw new XaiStreamError(
|
|
286
|
+
{
|
|
287
|
+
kind: "malformed",
|
|
288
|
+
detail: `${name} ${value} is above the ${MAX_STREAM_INDEX} this client accepts`
|
|
289
|
+
},
|
|
290
|
+
this.context()
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return value;
|
|
294
|
+
}
|
|
295
|
+
/** The error for a stream that ended without a terminal event. */
|
|
296
|
+
endedEarly() {
|
|
297
|
+
return new XaiStreamError(
|
|
298
|
+
{ kind: "ended_early", lastEventType: this.lastType },
|
|
299
|
+
this.context()
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
/** What the stream had delivered so far. */
|
|
303
|
+
progress() {
|
|
304
|
+
let outputChars = 0;
|
|
305
|
+
for (const entry of this.items.values()) outputChars += itemOutputChars(entry.item);
|
|
306
|
+
return { progressed: this.progressed || this.terminal !== void 0, outputChars };
|
|
307
|
+
}
|
|
308
|
+
/** The progress, the terminal usage when one arrived, and the tier, for a failure. */
|
|
309
|
+
context() {
|
|
310
|
+
const terminalUsage = usageOf(this.terminal?.response);
|
|
311
|
+
const tier = (this.terminal?.response ?? this.snapshot)?.["service_tier"];
|
|
312
|
+
return {
|
|
313
|
+
progress: this.progress(),
|
|
314
|
+
...terminalUsage !== void 0 ? { terminalUsage } : {},
|
|
315
|
+
...typeof tier === "string" && tier.length > 0 ? { serviceTier: tier } : {}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The terminal response with its output reconciled against the events. Never
|
|
320
|
+
* fails once a terminal event with a response arrived.
|
|
321
|
+
*
|
|
322
|
+
* @throws XaiStreamError only when the stream has no terminal event yet.
|
|
323
|
+
*/
|
|
324
|
+
result() {
|
|
325
|
+
const terminal = this.terminal;
|
|
326
|
+
if (terminal === void 0) throw this.endedEarly();
|
|
327
|
+
const response = terminal.response;
|
|
328
|
+
if (terminal.type === "response.failed") {
|
|
329
|
+
const status2 = response["status"];
|
|
330
|
+
return {
|
|
331
|
+
response: {
|
|
332
|
+
...response,
|
|
333
|
+
status: status2 === "failed" || status2 === "cancelled" ? status2 : "failed"
|
|
334
|
+
},
|
|
335
|
+
notes: [],
|
|
336
|
+
outputBegan: this.progressed
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const status = terminal.type === "response.incomplete" ? "incomplete" : response["status"];
|
|
340
|
+
const { output, notes } = this.reconcile(response["output"], status);
|
|
341
|
+
return {
|
|
342
|
+
response: { ...response, status, output },
|
|
343
|
+
notes: [...this.skippedNotes(), ...notes],
|
|
344
|
+
outputBegan: this.progressed
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
skippedNotes() {
|
|
348
|
+
if (this.skipped.length === 0) return [];
|
|
349
|
+
const counts = /* @__PURE__ */ new Map();
|
|
350
|
+
for (const what of this.skipped) counts.set(what, (counts.get(what) ?? 0) + 1);
|
|
351
|
+
return [...counts].map(
|
|
352
|
+
([what, n]) => `xai: skipped ${n} stream event(s) that could not be used (${what}); the final response was used for them.`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Deltas build up the item an `added` event opened. A delta for an item the
|
|
357
|
+
* stream never opened is ignored: the terminal object or a `done` event still
|
|
358
|
+
* carries the item, and a lost delta must not fail a billed call.
|
|
359
|
+
*/
|
|
360
|
+
applyDelta(type, event) {
|
|
361
|
+
const entry = this.items.get(
|
|
362
|
+
typeof event["output_index"] === "number" ? event["output_index"] : -1
|
|
363
|
+
);
|
|
364
|
+
if (entry === void 0) return;
|
|
365
|
+
const item = entry.item;
|
|
366
|
+
const contentIndex = this.index(event["content_index"], "content_index");
|
|
367
|
+
const summaryIndex = this.index(event["summary_index"], "summary_index");
|
|
368
|
+
const textAt = (list, index, defaults) => {
|
|
369
|
+
if (!Array.isArray(list) || typeof index !== "number") return {};
|
|
370
|
+
const existing = list[index];
|
|
371
|
+
if (isRecord(existing)) return existing;
|
|
372
|
+
const created = { ...defaults };
|
|
373
|
+
list[index] = created;
|
|
374
|
+
return created;
|
|
375
|
+
};
|
|
376
|
+
const content = () => {
|
|
377
|
+
if (!Array.isArray(item["content"])) item["content"] = [];
|
|
378
|
+
return item["content"];
|
|
379
|
+
};
|
|
380
|
+
const summary = () => {
|
|
381
|
+
if (!Array.isArray(item["summary"])) item["summary"] = [];
|
|
382
|
+
return item["summary"];
|
|
383
|
+
};
|
|
384
|
+
const append = (target, key, delta) => {
|
|
385
|
+
if (typeof delta !== "string") return;
|
|
386
|
+
target[key] = `${typeof target[key] === "string" ? target[key] : ""}${delta}`;
|
|
387
|
+
};
|
|
388
|
+
switch (type) {
|
|
389
|
+
case "response.content_part.added":
|
|
390
|
+
case "response.content_part.done":
|
|
391
|
+
if (typeof contentIndex === "number" && isRecord(event["part"])) {
|
|
392
|
+
content()[contentIndex] = clone(event["part"]);
|
|
393
|
+
}
|
|
394
|
+
return;
|
|
395
|
+
case "response.output_text.delta":
|
|
396
|
+
append(
|
|
397
|
+
textAt(content(), contentIndex, { type: "output_text", text: "" }),
|
|
398
|
+
"text",
|
|
399
|
+
event["delta"]
|
|
400
|
+
);
|
|
401
|
+
return;
|
|
402
|
+
case "response.output_text.done": {
|
|
403
|
+
const part = textAt(content(), contentIndex, { type: "output_text", text: "" });
|
|
404
|
+
if (typeof event["text"] === "string") part["text"] = event["text"];
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
case "response.output_text.annotation.added": {
|
|
408
|
+
const part = textAt(content(), contentIndex, { type: "output_text", text: "" });
|
|
409
|
+
if (!Array.isArray(part["annotations"])) part["annotations"] = [];
|
|
410
|
+
const index = this.index(event["annotation_index"], "annotation_index");
|
|
411
|
+
if (index !== void 0 && event["annotation"] !== void 0) {
|
|
412
|
+
part["annotations"][index] = clone(event["annotation"]);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
case "response.reasoning_summary_part.added":
|
|
417
|
+
case "response.reasoning_summary_part.done":
|
|
418
|
+
if (typeof summaryIndex === "number" && isRecord(event["part"])) {
|
|
419
|
+
summary()[summaryIndex] = clone(event["part"]);
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
case "response.reasoning_summary_text.delta":
|
|
423
|
+
append(
|
|
424
|
+
textAt(summary(), summaryIndex, { type: "summary_text", text: "" }),
|
|
425
|
+
"text",
|
|
426
|
+
event["delta"]
|
|
427
|
+
);
|
|
428
|
+
return;
|
|
429
|
+
case "response.reasoning_summary_text.done": {
|
|
430
|
+
const part = textAt(summary(), summaryIndex, { type: "summary_text", text: "" });
|
|
431
|
+
if (typeof event["text"] === "string") part["text"] = event["text"];
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
case "response.function_call_arguments.delta":
|
|
435
|
+
append(item, "arguments", event["delta"]);
|
|
436
|
+
return;
|
|
437
|
+
case "response.function_call_arguments.done":
|
|
438
|
+
if (typeof event["arguments"] === "string") item["arguments"] = event["arguments"];
|
|
439
|
+
return;
|
|
440
|
+
// xAI streams the input of a server tool call (x_search) as a custom tool call.
|
|
441
|
+
case "response.custom_tool_call_input.delta":
|
|
442
|
+
append(item, "input", event["delta"]);
|
|
443
|
+
return;
|
|
444
|
+
case "response.custom_tool_call_input.done":
|
|
445
|
+
if (typeof event["input"] === "string") item["input"] = event["input"];
|
|
446
|
+
return;
|
|
447
|
+
default:
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/** The items the events built, in output order, finished for a response that ended. */
|
|
452
|
+
eventItems(responseStatus) {
|
|
453
|
+
return [...this.items.entries()].sort(([a], [b]) => a - b).map(([, entry]) => {
|
|
454
|
+
const item = clone(entry.item);
|
|
455
|
+
if (!entry.done) item["status"] = finishedStatus(responseStatus);
|
|
456
|
+
return item;
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
reconcile(finalOutput, responseStatus) {
|
|
460
|
+
if (finalOutput !== void 0 && !Array.isArray(finalOutput)) {
|
|
461
|
+
return {
|
|
462
|
+
output: this.eventItems(responseStatus),
|
|
463
|
+
notes: [
|
|
464
|
+
"xai: the final response `output` is not an array; the output items were built from the stream events."
|
|
465
|
+
]
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const finalItems = [];
|
|
469
|
+
let dropped = 0;
|
|
470
|
+
for (const item of finalOutput ?? []) {
|
|
471
|
+
if (isRecord(item)) finalItems.push(clone(item));
|
|
472
|
+
else dropped++;
|
|
473
|
+
}
|
|
474
|
+
const notes = [];
|
|
475
|
+
if (dropped > 0) {
|
|
476
|
+
notes.push(
|
|
477
|
+
`xai: dropped ${dropped} non-object item(s) from the final response \`output\`.`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
const groupOf = (item) => {
|
|
481
|
+
const id = item["id"];
|
|
482
|
+
return typeof id === "string" && id.length > 0 ? `id:${id}` : `anon:${String(item["type"])}`;
|
|
483
|
+
};
|
|
484
|
+
const labelOf = (item) => typeof item["id"] === "string" && item["id"].length > 0 ? `${String(item["type"])} ${item["id"]}` : String(item["type"]);
|
|
485
|
+
const finalGroups = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const item of finalItems) {
|
|
487
|
+
const group = groupOf(item);
|
|
488
|
+
finalGroups.set(group, [...finalGroups.get(group) ?? [], item]);
|
|
489
|
+
}
|
|
490
|
+
const eventGroups = /* @__PURE__ */ new Map();
|
|
491
|
+
for (const entry of [...this.items.entries()].sort(([a], [b]) => a - b)) {
|
|
492
|
+
const group = groupOf(entry[1].item);
|
|
493
|
+
eventGroups.set(group, [...eventGroups.get(group) ?? [], entry]);
|
|
494
|
+
}
|
|
495
|
+
const bound = /* @__PURE__ */ new Set();
|
|
496
|
+
const unbound = [];
|
|
497
|
+
for (const [group, entries] of eventGroups) {
|
|
498
|
+
const finals = finalGroups.get(group) ?? [];
|
|
499
|
+
const endOffset = finals.length - entries.length;
|
|
500
|
+
const matches = (offset2) => entries.filter(([, streamed], i) => {
|
|
501
|
+
const finalItem = finals[i + offset2];
|
|
502
|
+
return finalItem !== void 0 && sameItemContent(finalItem, streamed.item);
|
|
503
|
+
}).length;
|
|
504
|
+
const offset = matches(0) > matches(endOffset) ? 0 : endOffset;
|
|
505
|
+
entries.forEach((entry, i) => {
|
|
506
|
+
const finalItem = finals[i + offset];
|
|
507
|
+
if (finalItem === void 0) {
|
|
508
|
+
unbound.push(entry);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
bound.add(finalItem);
|
|
512
|
+
const [, streamed] = entry;
|
|
513
|
+
if (finalItem["type"] !== streamed.item["type"]) {
|
|
514
|
+
notes.push(
|
|
515
|
+
`xai: output item ${labelOf(finalItem)} is a "${String(streamed.item["type"])}" in the stream and a "${String(finalItem["type"])}" in the final response; the final response is used.`
|
|
516
|
+
);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (!streamed.done) return;
|
|
520
|
+
const filled = [];
|
|
521
|
+
const diverged = [];
|
|
522
|
+
for (const [field, value] of Object.entries(streamed.item)) {
|
|
523
|
+
if (!(field in finalItem)) {
|
|
524
|
+
finalItem[field] = clone(value);
|
|
525
|
+
filled.push(field);
|
|
526
|
+
} else if (!deepEqual(finalItem[field], value)) {
|
|
527
|
+
diverged.push(field);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (filled.length > 0) {
|
|
531
|
+
notes.push(
|
|
532
|
+
`xai: output item ${labelOf(finalItem)} lacked field(s) [${filled.join(", ")}] in the final response; taken from the stream.`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
if (diverged.length > 0 && CONSUMED_ITEM_TYPES.has(String(finalItem["type"]))) {
|
|
536
|
+
notes.push(
|
|
537
|
+
`xai: output item ${labelOf(finalItem)} differs between the stream and the final response in field(s) [${diverged.join(", ")}]; the final response is used.`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
const spare = finalItems.filter((item) => !bound.has(item));
|
|
543
|
+
const insertions = [];
|
|
544
|
+
const rebuilt = [];
|
|
545
|
+
const assembled = [];
|
|
546
|
+
for (const [index, entry] of unbound.sort(([a], [b]) => a - b)) {
|
|
547
|
+
const twin = spare.findIndex((item2) => sameItemContent(item2, entry.item));
|
|
548
|
+
if (twin !== -1) {
|
|
549
|
+
spare.splice(twin, 1);
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const item = clone(entry.item);
|
|
553
|
+
if (!entry.done) {
|
|
554
|
+
item["status"] = finishedStatus(responseStatus);
|
|
555
|
+
assembled.push(labelOf(item));
|
|
556
|
+
} else {
|
|
557
|
+
rebuilt.push(labelOf(item));
|
|
558
|
+
}
|
|
559
|
+
insertions.push({ index, item });
|
|
560
|
+
}
|
|
561
|
+
const output = [...finalItems];
|
|
562
|
+
for (const { index, item } of insertions) {
|
|
563
|
+
output.splice(Math.min(index, output.length), 0, item);
|
|
564
|
+
}
|
|
565
|
+
if (rebuilt.length > 0) {
|
|
566
|
+
notes.push(
|
|
567
|
+
`xai: the final response lacked ${rebuilt.length} output item(s) [${rebuilt.join(", ")}] that the stream completed; they were rebuilt from the stream events.`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
if (assembled.length > 0) {
|
|
571
|
+
notes.push(
|
|
572
|
+
`xai: the final response lacked ${assembled.length} output item(s) [${assembled.join(", ")}] that the stream never completed; they were assembled from the stream deltas and may lack provider-only fields.`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
return { output, notes };
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
function finishedStatus(responseStatus) {
|
|
579
|
+
return responseStatus === "incomplete" ? "incomplete" : "completed";
|
|
580
|
+
}
|
|
581
|
+
|
|
6
582
|
// src/client.ts
|
|
7
583
|
function requireApiKey(auth) {
|
|
8
584
|
if (!("apiKey" in auth) || typeof auth.apiKey !== "string" || auth.apiKey.trim() === "") {
|
|
@@ -14,22 +590,227 @@ function requireApiKey(auth) {
|
|
|
14
590
|
}
|
|
15
591
|
return auth.apiKey;
|
|
16
592
|
}
|
|
17
|
-
|
|
593
|
+
function isRemainingQuotaHeader(name) {
|
|
594
|
+
return name.startsWith("x-ratelimit-remaining");
|
|
595
|
+
}
|
|
596
|
+
function readXaiResponseMeta(headers, sdkRequestId) {
|
|
597
|
+
const requestId = headers.get("x-request-id") ?? void 0;
|
|
598
|
+
const remaining = {};
|
|
599
|
+
headers.forEach((value, name) => {
|
|
600
|
+
const lower = name.toLowerCase();
|
|
601
|
+
if (isRemainingQuotaHeader(lower)) remaining[lower] = value;
|
|
602
|
+
});
|
|
603
|
+
return {
|
|
604
|
+
...requestId !== void 0 && requestId !== "" ? { requestId } : {},
|
|
605
|
+
...Object.keys(remaining).length > 0 ? { rateLimitRemaining: remaining } : {}
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
var XAI_RESERVED_FETCH_OPTION_KEYS = [
|
|
609
|
+
"headers",
|
|
610
|
+
"signal",
|
|
611
|
+
"body",
|
|
612
|
+
"method"
|
|
613
|
+
];
|
|
614
|
+
var XAI_DEFAULT_TIMEOUT_MS = 36e5;
|
|
615
|
+
var XAI_TIMEOUT_BUFFER_MS = 5e3;
|
|
616
|
+
var XAI_MAX_TIMEOUT_MS = 2147483647 - XAI_TIMEOUT_BUFFER_MS;
|
|
617
|
+
async function buildXaiClient(auth, transport) {
|
|
18
618
|
const apiKey = requireApiKey(auth);
|
|
19
619
|
const { default: OpenAI } = await import('openai');
|
|
20
620
|
const client = new OpenAI({
|
|
21
621
|
apiKey,
|
|
22
622
|
baseURL: "https://api.x.ai/v1",
|
|
23
|
-
maxRetries: 0
|
|
623
|
+
maxRetries: 0,
|
|
624
|
+
...transport !== void 0 ? {
|
|
625
|
+
fetch: transport.fetch,
|
|
626
|
+
...transport.fetchOptions !== void 0 ? { fetchOptions: transport.fetchOptions } : {}
|
|
627
|
+
} : {}
|
|
24
628
|
});
|
|
629
|
+
const idleTimeoutMs = transport?.idleTimeoutMs;
|
|
25
630
|
return {
|
|
26
631
|
responses: {
|
|
27
632
|
async create(params, options) {
|
|
28
|
-
|
|
633
|
+
const { onResponse, timeout, signal } = options ?? {};
|
|
634
|
+
const send = (p, o) => client.responses.create(
|
|
635
|
+
p,
|
|
636
|
+
o
|
|
637
|
+
);
|
|
638
|
+
const controller = new AbortController();
|
|
639
|
+
const forwardAbort = () => {
|
|
640
|
+
controller.abort(signal?.reason);
|
|
641
|
+
};
|
|
642
|
+
if (signal?.aborted === true) forwardAbort();
|
|
643
|
+
else signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
644
|
+
const startedAt = performance.now();
|
|
645
|
+
let deadlineTimer;
|
|
646
|
+
let idleTimer;
|
|
647
|
+
const ended = { deadline: false, idle: false };
|
|
648
|
+
const reducer = new XaiStreamReducer();
|
|
649
|
+
const failure = () => {
|
|
650
|
+
if (ended.deadline) {
|
|
651
|
+
return new XaiStreamError(
|
|
652
|
+
{ kind: "deadline", timeoutMs: timeout ?? 0 },
|
|
653
|
+
reducer.context()
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
if (ended.idle) {
|
|
657
|
+
return new XaiStreamError(
|
|
658
|
+
{ kind: "idle", idleTimeoutMs: idleTimeoutMs ?? 0 },
|
|
659
|
+
reducer.context()
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
return void 0;
|
|
663
|
+
};
|
|
664
|
+
const stopped = () => {
|
|
665
|
+
const ours = failure();
|
|
666
|
+
if (ours !== void 0) return ours;
|
|
667
|
+
if (signal?.aborted === true) return abortFailure(signal, reducer);
|
|
668
|
+
return void 0;
|
|
669
|
+
};
|
|
670
|
+
try {
|
|
671
|
+
const response = await send(
|
|
672
|
+
{ ...params, stream: true },
|
|
673
|
+
{
|
|
674
|
+
signal: controller.signal,
|
|
675
|
+
headers: { accept: "text/event-stream" },
|
|
676
|
+
...timeout !== void 0 ? { timeout } : {}
|
|
677
|
+
}
|
|
678
|
+
).asResponse();
|
|
679
|
+
if (timeout !== void 0) {
|
|
680
|
+
deadlineTimer = setTimeout(
|
|
681
|
+
() => {
|
|
682
|
+
ended.deadline = true;
|
|
683
|
+
controller.abort();
|
|
684
|
+
},
|
|
685
|
+
Math.max(0, timeout - (performance.now() - startedAt))
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
const touch = () => {
|
|
689
|
+
if (idleTimeoutMs === void 0) return;
|
|
690
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
691
|
+
idleTimer = setTimeout(() => {
|
|
692
|
+
ended.idle = true;
|
|
693
|
+
controller.abort();
|
|
694
|
+
}, idleTimeoutMs);
|
|
695
|
+
};
|
|
696
|
+
touch();
|
|
697
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
698
|
+
const onAbort = () => {
|
|
699
|
+
reject(new Error("aborted"));
|
|
700
|
+
};
|
|
701
|
+
if (controller.signal.aborted) onAbort();
|
|
702
|
+
else controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
703
|
+
});
|
|
704
|
+
aborted.catch(() => void 0);
|
|
705
|
+
const contentType = response.headers.get("content-type");
|
|
706
|
+
if (contentType !== null && !/text\/event-stream/i.test(contentType)) {
|
|
707
|
+
const bodySnippet = await readBodySnippet(response.body, aborted);
|
|
708
|
+
const why = stopped();
|
|
709
|
+
if (why instanceof Error) throw why;
|
|
710
|
+
throw new XaiStreamError(
|
|
711
|
+
{
|
|
712
|
+
kind: "not_event_stream",
|
|
713
|
+
contentType,
|
|
714
|
+
...bodySnippet !== void 0 ? { bodySnippet } : {}
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
cause: {
|
|
718
|
+
contentType,
|
|
719
|
+
...bodySnippet !== void 0 ? { bodySnippet } : {}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
if (response.body === null) throw reducer.endedEarly();
|
|
725
|
+
let terminal = false;
|
|
726
|
+
try {
|
|
727
|
+
for await (const frame of readSseFrames(response.body, {
|
|
728
|
+
onChunk: touch,
|
|
729
|
+
aborted
|
|
730
|
+
})) {
|
|
731
|
+
if (reducer.pushFrame(frame)) {
|
|
732
|
+
terminal = true;
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
} catch (err) {
|
|
737
|
+
const why = stopped() ?? streamFailure(err, reducer);
|
|
738
|
+
throw why;
|
|
739
|
+
}
|
|
740
|
+
if (!terminal) {
|
|
741
|
+
const why = stopped() ?? reducer.endedEarly();
|
|
742
|
+
throw why;
|
|
743
|
+
}
|
|
744
|
+
const { response: reduced, notes, outputBegan } = reducer.result();
|
|
745
|
+
onResponse?.({
|
|
746
|
+
...readXaiResponseMeta(response.headers),
|
|
747
|
+
...notes.length > 0 ? { streamNotes: notes } : {},
|
|
748
|
+
streamProgressed: outputBegan
|
|
749
|
+
});
|
|
750
|
+
return reduced;
|
|
751
|
+
} finally {
|
|
752
|
+
if (deadlineTimer !== void 0) clearTimeout(deadlineTimer);
|
|
753
|
+
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
754
|
+
signal?.removeEventListener("abort", forwardAbort);
|
|
755
|
+
}
|
|
29
756
|
}
|
|
30
757
|
}
|
|
31
758
|
};
|
|
32
759
|
}
|
|
760
|
+
function abortError(signal) {
|
|
761
|
+
const reason = signal.reason;
|
|
762
|
+
if (reason instanceof core.LlmError) return reason;
|
|
763
|
+
return Object.assign(new Error("Request was aborted."), {
|
|
764
|
+
name: "AbortError",
|
|
765
|
+
cause: reason
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
function abortFailure(signal, reducer) {
|
|
769
|
+
if (!reducer.progress().progressed) return abortError(signal);
|
|
770
|
+
return new XaiStreamError(
|
|
771
|
+
{ kind: "aborted" },
|
|
772
|
+
{ ...reducer.context(), cause: signal.reason }
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
var BODY_SNIPPET_CHARS = 500;
|
|
776
|
+
async function readBodySnippet(body, aborted) {
|
|
777
|
+
if (body === null) return void 0;
|
|
778
|
+
const reader = body.getReader();
|
|
779
|
+
const decoder = new TextDecoder("utf-8");
|
|
780
|
+
let text = "";
|
|
781
|
+
try {
|
|
782
|
+
while (text.length < 2 * BODY_SNIPPET_CHARS) {
|
|
783
|
+
const chunk = await Promise.race([reader.read(), aborted]);
|
|
784
|
+
if (chunk.done) break;
|
|
785
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
786
|
+
}
|
|
787
|
+
} catch {
|
|
788
|
+
} finally {
|
|
789
|
+
reader.cancel().catch(() => void 0);
|
|
790
|
+
}
|
|
791
|
+
const snippet = core.redactSecrets(text).slice(0, BODY_SNIPPET_CHARS);
|
|
792
|
+
return snippet.length > 0 ? snippet : void 0;
|
|
793
|
+
}
|
|
794
|
+
function streamFailure(err, reducer) {
|
|
795
|
+
if (err instanceof XaiStreamError) return err;
|
|
796
|
+
if (!reducer.progress().progressed) return err;
|
|
797
|
+
return new XaiStreamError(
|
|
798
|
+
{ kind: "cut", detail: err instanceof Error ? err.message : String(err) },
|
|
799
|
+
{ ...reducer.context(), cause: err }
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/model-limits.ts
|
|
804
|
+
var grok4Limits = () => Object.freeze({ contextWindow: 5e5, maxOutputTokens: null });
|
|
805
|
+
var XAI_MODEL_LIMITS = {
|
|
806
|
+
"grok-4.5": grok4Limits(),
|
|
807
|
+
"grok-4.6": grok4Limits(),
|
|
808
|
+
"grok-4.7": grok4Limits()
|
|
809
|
+
};
|
|
810
|
+
var XAI_INPUT_MIME_TYPES = Object.freeze([
|
|
811
|
+
"image/jpeg",
|
|
812
|
+
"image/png"
|
|
813
|
+
]);
|
|
33
814
|
var webSearchFlags = {
|
|
34
815
|
enableImageUnderstanding: zod.z.boolean().optional().meta({
|
|
35
816
|
title: "Enable Image Understanding",
|
|
@@ -105,6 +886,21 @@ var XaiToolsSchema = zod.z.union([
|
|
|
105
886
|
title: "XaiTools",
|
|
106
887
|
description: "xAI Live Search tools. At most one web_search and at most one x_search."
|
|
107
888
|
});
|
|
889
|
+
var maxWebSearchCalls = zod.z.number().int().min(1).meta({
|
|
890
|
+
title: "Max Web Search Calls",
|
|
891
|
+
description: "Ceiling on web_search calls. Requires a web_search tool. Compared with xAI's reported counter after the call."
|
|
892
|
+
});
|
|
893
|
+
var maxXItems = zod.z.number().int().min(1).meta({
|
|
894
|
+
title: "Max X Items",
|
|
895
|
+
description: "Ceiling on X posts plus users fetched. Requires an x_search tool. Compared with xAI's reported counters after the call."
|
|
896
|
+
});
|
|
897
|
+
var XaiSearchBudgetSchema = zod.z.union([
|
|
898
|
+
zod.z.strictObject({ maxWebSearchCalls, maxXItems: maxXItems.optional() }),
|
|
899
|
+
zod.z.strictObject({ maxWebSearchCalls: maxWebSearchCalls.optional(), maxXItems })
|
|
900
|
+
]).meta({
|
|
901
|
+
title: "Search Budget",
|
|
902
|
+
description: "Observed after the call, never sent to xAI (which has no per-call search ceiling). Over budget: a warning and usage.details.search_budget_exceeded = 1; the already-billed result is still returned. Needs at least one ceiling, and the tool each ceiling counts (maxWebSearchCalls: web_search, maxXItems: x_search) in tools."
|
|
903
|
+
});
|
|
108
904
|
var XaiProviderOptionsSchema = zod.z.strictObject({
|
|
109
905
|
promptCacheKey: zod.z.string().min(1).optional().meta({
|
|
110
906
|
title: "Prompt Cache Key",
|
|
@@ -125,7 +921,23 @@ var XaiProviderOptionsSchema = zod.z.strictObject({
|
|
|
125
921
|
maxTurns: zod.z.number().int().min(1).optional().meta({
|
|
126
922
|
title: "Max Turns",
|
|
127
923
|
description: "xAI Responses `max_turns`: cap on agentic tool-calling turns for the server-side search tools. Requires `tools`. A turn can run several searches. Not enforced by xAI as of 2026-10-02."
|
|
128
|
-
})
|
|
924
|
+
}),
|
|
925
|
+
searchBudget: XaiSearchBudgetSchema.optional()
|
|
926
|
+
}).superRefine((options, ctx) => {
|
|
927
|
+
const budget = options.searchBudget;
|
|
928
|
+
if (budget === void 0) return;
|
|
929
|
+
const kinds = new Set((options.tools ?? []).map((tool) => tool.type));
|
|
930
|
+
const need = (ceiling, tool) => {
|
|
931
|
+
if (budget[ceiling] !== void 0 && !kinds.has(tool)) {
|
|
932
|
+
ctx.addIssue({
|
|
933
|
+
code: "custom",
|
|
934
|
+
path: ["searchBudget", ceiling],
|
|
935
|
+
message: `searchBudget.${ceiling} requires ${tool === "x_search" ? "an" : "a"} ${tool} tool in tools.`
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
need("maxWebSearchCalls", "web_search");
|
|
940
|
+
need("maxXItems", "x_search");
|
|
129
941
|
}).meta({
|
|
130
942
|
title: "xAI Provider Options",
|
|
131
943
|
description: "Allowlisted xAI provider options."
|
|
@@ -133,17 +945,17 @@ var XaiProviderOptionsSchema = zod.z.strictObject({
|
|
|
133
945
|
|
|
134
946
|
// src/model-config/grok-4-5.ts
|
|
135
947
|
var Grok45ConfigSchema = zod.z.strictObject({
|
|
136
|
-
temperature: zod.z.number().optional().meta({
|
|
948
|
+
temperature: zod.z.number().min(0).max(2).optional().meta({
|
|
137
949
|
title: "Temperature",
|
|
138
|
-
description:
|
|
950
|
+
description: 'Sampling temperature forwarded verbatim to grok-4.5, from 0 to 2 (the range xAI documents for the Responses API: "between 0 and 2"); a value outside it is rejected.'
|
|
139
951
|
}),
|
|
140
|
-
topP: zod.z.number().optional().meta({
|
|
952
|
+
topP: zod.z.number().min(0).max(1).optional().meta({
|
|
141
953
|
title: "Top P",
|
|
142
|
-
description: "Nucleus sampling
|
|
954
|
+
description: "Nucleus sampling probability forwarded verbatim to grok-4.5, from 0 to 1 (xAI documents no range for it; a probability mass outside 0 to 1 is meaningless); a value outside it is rejected."
|
|
143
955
|
}),
|
|
144
|
-
maxOutputTokens:
|
|
956
|
+
maxOutputTokens: core.maxOutputTokensSchema(XAI_MODEL_LIMITS["grok-4.5"]).optional().meta({
|
|
145
957
|
title: "Max Output Tokens",
|
|
146
|
-
description: "Maximum output token cap for grok-4.5. No
|
|
958
|
+
description: "Maximum output token cap for grok-4.5, including reasoning tokens. No ceiling is applied: xAI documents no output limit and has accepted very large values (live-verified). Truncation surfaces as finishReason:'length', not an error."
|
|
147
959
|
}),
|
|
148
960
|
reasoning: zod.z.strictObject({
|
|
149
961
|
effort: zod.z.enum(["low", "medium", "high"]).meta({
|
|
@@ -158,9 +970,9 @@ var Grok45ConfigSchema = zod.z.strictObject({
|
|
|
158
970
|
title: "Service Tier",
|
|
159
971
|
description: "xAI priority processing for grok-4.5, billed at 2\xD7 on input, cached input, and output tokens. Live-verified 2026-09-25."
|
|
160
972
|
}),
|
|
161
|
-
timeoutMs: zod.z.number().int().positive().optional().meta({
|
|
973
|
+
timeoutMs: zod.z.number().int().positive().max(XAI_MAX_TIMEOUT_MS).optional().meta({
|
|
162
974
|
title: "Timeout",
|
|
163
|
-
description: "Logical request timeout in milliseconds."
|
|
975
|
+
description: "Logical request timeout in milliseconds (at most 2147478647)."
|
|
164
976
|
}),
|
|
165
977
|
providerOptions: zod.z.strictObject({
|
|
166
978
|
xai: XaiProviderOptionsSchema.optional()
|
|
@@ -174,17 +986,17 @@ var Grok45ConfigSchema = zod.z.strictObject({
|
|
|
174
986
|
examples: [{ reasoning: { effort: "high" } }]
|
|
175
987
|
});
|
|
176
988
|
var Grok46ConfigSchema = zod.z.strictObject({
|
|
177
|
-
temperature: zod.z.number().optional().meta({
|
|
989
|
+
temperature: zod.z.number().min(0).max(2).optional().meta({
|
|
178
990
|
title: "Temperature",
|
|
179
|
-
description:
|
|
991
|
+
description: 'Sampling temperature forwarded verbatim to grok-4.6, from 0 to 2 (the range xAI documents for the Responses API: "between 0 and 2"); a value outside it is rejected.'
|
|
180
992
|
}),
|
|
181
|
-
topP: zod.z.number().optional().meta({
|
|
993
|
+
topP: zod.z.number().min(0).max(1).optional().meta({
|
|
182
994
|
title: "Top P",
|
|
183
|
-
description: "Nucleus sampling
|
|
995
|
+
description: "Nucleus sampling probability forwarded verbatim to grok-4.6, from 0 to 1 (xAI documents no range for it; a probability mass outside 0 to 1 is meaningless); a value outside it is rejected."
|
|
184
996
|
}),
|
|
185
|
-
maxOutputTokens:
|
|
997
|
+
maxOutputTokens: core.maxOutputTokensSchema(XAI_MODEL_LIMITS["grok-4.6"]).optional().meta({
|
|
186
998
|
title: "Max Output Tokens",
|
|
187
|
-
description: "Maximum output token cap for grok-4.6. No
|
|
999
|
+
description: "Maximum output token cap for grok-4.6, including reasoning tokens. No ceiling is applied: xAI documents no output limit and has accepted very large values (live-verified). Truncation surfaces as finishReason:'length', not an error."
|
|
188
1000
|
}),
|
|
189
1001
|
reasoning: zod.z.strictObject({
|
|
190
1002
|
effort: zod.z.enum(["low", "medium", "high", "xhigh"]).meta({
|
|
@@ -199,9 +1011,9 @@ var Grok46ConfigSchema = zod.z.strictObject({
|
|
|
199
1011
|
title: "Service Tier",
|
|
200
1012
|
description: 'xAI priority processing for grok-4.6 (Responses `service_tier: "priority"`). Echo live-verified 2026-08-12. Bills at 2\xD7 after the cache discount (uncached standard-list 2\xD7 confirmed by live ticks; cached/long-context legs follow the official 2\xD7 rule). Omitted requests stay on xAI default. "flex"/"standard"/"batch" are rejected.'
|
|
201
1013
|
}),
|
|
202
|
-
timeoutMs: zod.z.number().int().positive().optional().meta({
|
|
1014
|
+
timeoutMs: zod.z.number().int().positive().max(XAI_MAX_TIMEOUT_MS).optional().meta({
|
|
203
1015
|
title: "Timeout",
|
|
204
|
-
description: "Logical request timeout in milliseconds."
|
|
1016
|
+
description: "Logical request timeout in milliseconds (at most 2147478647)."
|
|
205
1017
|
}),
|
|
206
1018
|
providerOptions: zod.z.strictObject({
|
|
207
1019
|
xai: XaiProviderOptionsSchema.optional()
|
|
@@ -215,17 +1027,17 @@ var Grok46ConfigSchema = zod.z.strictObject({
|
|
|
215
1027
|
examples: [{ reasoning: { effort: "high" } }]
|
|
216
1028
|
});
|
|
217
1029
|
var Grok47ConfigSchema = zod.z.strictObject({
|
|
218
|
-
temperature: zod.z.number().optional().meta({
|
|
1030
|
+
temperature: zod.z.number().min(0).max(2).optional().meta({
|
|
219
1031
|
title: "Temperature",
|
|
220
|
-
description:
|
|
1032
|
+
description: 'Sampling temperature forwarded verbatim to grok-4.7, from 0 to 2 (the range xAI documents for the Responses API: "between 0 and 2"); a value outside it is rejected.'
|
|
221
1033
|
}),
|
|
222
|
-
topP: zod.z.number().optional().meta({
|
|
1034
|
+
topP: zod.z.number().min(0).max(1).optional().meta({
|
|
223
1035
|
title: "Top P",
|
|
224
|
-
description: "Nucleus sampling
|
|
1036
|
+
description: "Nucleus sampling probability forwarded verbatim to grok-4.7, from 0 to 1 (xAI documents no range for it; a probability mass outside 0 to 1 is meaningless); a value outside it is rejected."
|
|
225
1037
|
}),
|
|
226
|
-
maxOutputTokens:
|
|
1038
|
+
maxOutputTokens: core.maxOutputTokensSchema(XAI_MODEL_LIMITS["grok-4.7"]).optional().meta({
|
|
227
1039
|
title: "Max Output Tokens",
|
|
228
|
-
description: "Maximum output token cap for grok-4.7. No
|
|
1040
|
+
description: "Maximum output token cap for grok-4.7, including reasoning tokens. No ceiling is applied: xAI documents no output limit and has accepted very large values (live-verified). Truncation surfaces as finishReason:'length', not an error."
|
|
229
1041
|
}),
|
|
230
1042
|
reasoning: zod.z.strictObject({
|
|
231
1043
|
effort: zod.z.enum(["low", "medium", "high", "xhigh"]).meta({
|
|
@@ -240,9 +1052,9 @@ var Grok47ConfigSchema = zod.z.strictObject({
|
|
|
240
1052
|
title: "Service Tier",
|
|
241
1053
|
description: 'xAI priority processing for grok-4.7 (Responses `service_tier: "priority"`). Bills at 2\xD7 after the cache discount (uncached standard-list 2\xD7 cached/long-context legs follow the official 2\xD7 rule). Omitted requests stay on xAI default. "flex"/"standard"/"batch" are rejected.'
|
|
242
1054
|
}),
|
|
243
|
-
timeoutMs: zod.z.number().int().positive().optional().meta({
|
|
1055
|
+
timeoutMs: zod.z.number().int().positive().max(XAI_MAX_TIMEOUT_MS).optional().meta({
|
|
244
1056
|
title: "Timeout",
|
|
245
|
-
description: "Logical request timeout in milliseconds."
|
|
1057
|
+
description: "Logical request timeout in milliseconds (at most 2147478647)."
|
|
246
1058
|
}),
|
|
247
1059
|
providerOptions: zod.z.strictObject({
|
|
248
1060
|
xai: XaiProviderOptionsSchema.optional()
|
|
@@ -260,15 +1072,15 @@ var Grok47ConfigSchema = zod.z.strictObject({
|
|
|
260
1072
|
var grok45ModelDescriptor = {
|
|
261
1073
|
model: "grok-4.5",
|
|
262
1074
|
provider: "xai",
|
|
1075
|
+
limits: XAI_MODEL_LIMITS["grok-4.5"],
|
|
263
1076
|
pricingFamily: "grok-4.5",
|
|
264
1077
|
capabilities: {
|
|
1078
|
+
inputMimeTypes: XAI_INPUT_MIME_TYPES,
|
|
265
1079
|
reasoning: true,
|
|
266
1080
|
reasoningApi: "level",
|
|
267
1081
|
admittedReasoningEfforts: ["low", "medium", "high"],
|
|
268
1082
|
structuredOutput: true,
|
|
269
1083
|
nativeStructuredOutput: true,
|
|
270
|
-
vision: true,
|
|
271
|
-
audioInput: false,
|
|
272
1084
|
sampling: "tunable",
|
|
273
1085
|
caching: { explicit: false, minTokens: 0 },
|
|
274
1086
|
grounding: true,
|
|
@@ -277,21 +1089,22 @@ var grok45ModelDescriptor = {
|
|
|
277
1089
|
serviceTiers: ["priority"]
|
|
278
1090
|
},
|
|
279
1091
|
configSchema: Grok45ConfigSchema,
|
|
1092
|
+
configKeys: core.toConfigKeys(Grok45ConfigSchema),
|
|
280
1093
|
configJsonSchema: core.toConfigJsonSchema(Grok45ConfigSchema),
|
|
281
1094
|
validateConfig: core.zodToStandardSchema(Grok45ConfigSchema)
|
|
282
1095
|
};
|
|
283
1096
|
var grok46ModelDescriptor = {
|
|
284
1097
|
model: "grok-4.6",
|
|
285
1098
|
provider: "xai",
|
|
1099
|
+
limits: XAI_MODEL_LIMITS["grok-4.6"],
|
|
286
1100
|
pricingFamily: "grok-4.6",
|
|
287
1101
|
capabilities: {
|
|
1102
|
+
inputMimeTypes: XAI_INPUT_MIME_TYPES,
|
|
288
1103
|
reasoning: true,
|
|
289
1104
|
reasoningApi: "level",
|
|
290
1105
|
admittedReasoningEfforts: ["low", "medium", "high", "xhigh"],
|
|
291
1106
|
structuredOutput: true,
|
|
292
1107
|
nativeStructuredOutput: true,
|
|
293
|
-
vision: true,
|
|
294
|
-
audioInput: false,
|
|
295
1108
|
sampling: "tunable",
|
|
296
1109
|
caching: { explicit: false, minTokens: 0 },
|
|
297
1110
|
grounding: true,
|
|
@@ -300,30 +1113,33 @@ var grok46ModelDescriptor = {
|
|
|
300
1113
|
serviceTiers: ["priority"]
|
|
301
1114
|
},
|
|
302
1115
|
configSchema: Grok46ConfigSchema,
|
|
1116
|
+
configKeys: core.toConfigKeys(Grok46ConfigSchema),
|
|
303
1117
|
configJsonSchema: core.toConfigJsonSchema(Grok46ConfigSchema),
|
|
304
1118
|
validateConfig: core.zodToStandardSchema(Grok46ConfigSchema)
|
|
305
1119
|
};
|
|
306
1120
|
var grok47ModelDescriptor = {
|
|
307
1121
|
model: "grok-4.7",
|
|
308
1122
|
provider: "xai",
|
|
1123
|
+
limits: XAI_MODEL_LIMITS["grok-4.7"],
|
|
309
1124
|
pricingFamily: "grok-4.7",
|
|
310
1125
|
capabilities: {
|
|
1126
|
+
inputMimeTypes: XAI_INPUT_MIME_TYPES,
|
|
311
1127
|
reasoning: true,
|
|
312
1128
|
reasoningApi: "level",
|
|
313
1129
|
admittedReasoningEfforts: ["low", "medium", "high", "xhigh"],
|
|
314
1130
|
structuredOutput: true,
|
|
315
1131
|
nativeStructuredOutput: true,
|
|
316
|
-
vision: true,
|
|
317
|
-
audioInput: false,
|
|
318
1132
|
sampling: "tunable",
|
|
319
1133
|
caching: { explicit: false, minTokens: 0 },
|
|
320
1134
|
grounding: true,
|
|
321
1135
|
structuredOutputWithTools: true,
|
|
322
1136
|
functionCalling: true,
|
|
323
|
-
|
|
1137
|
+
continuation: "state",
|
|
1138
|
+
providerState: true,
|
|
324
1139
|
serviceTiers: ["priority"]
|
|
325
1140
|
},
|
|
326
1141
|
configSchema: Grok47ConfigSchema,
|
|
1142
|
+
configKeys: core.toConfigKeys(Grok47ConfigSchema),
|
|
327
1143
|
configJsonSchema: core.toConfigJsonSchema(Grok47ConfigSchema),
|
|
328
1144
|
validateConfig: core.zodToStandardSchema(Grok47ConfigSchema)
|
|
329
1145
|
};
|
|
@@ -333,89 +1149,48 @@ var xaiModelDescriptors = [
|
|
|
333
1149
|
grok47ModelDescriptor
|
|
334
1150
|
];
|
|
335
1151
|
var xaiRegistry = core.createModelRegistry(xaiModelDescriptors);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
"
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if ("nullable" in node) {
|
|
379
|
-
throw badSchema(
|
|
380
|
-
`xAI structured output takes standard JSON Schema, which has no \`nullable\` keyword (found at \`${at}\`). xAI ignores it, so the field would be required and non-null. List 'null' in \`type\` instead, e.g. type: ['string', 'null'].`
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
const type = node["type"];
|
|
384
|
-
if (type !== void 0) {
|
|
385
|
-
const names = Array.isArray(type) ? type : [type];
|
|
386
|
-
for (const name of names) {
|
|
387
|
-
if (typeof name !== "string" || !JSON_SCHEMA_TYPES.has(name)) {
|
|
388
|
-
throw badSchema(
|
|
389
|
-
`xAI structured output takes standard JSON Schema; \`type\` ${JSON.stringify(
|
|
390
|
-
name
|
|
391
|
-
)} at \`${at}\` is not a JSON Schema type. Use lowercase string, number, integer, boolean, object, array or null.`
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
const child = (segment) => path.length > 0 ? `${path}.${segment}` : segment;
|
|
397
|
-
for (const keyword of SCHEMA_MAP_KEYWORDS) {
|
|
398
|
-
const map = node[keyword];
|
|
399
|
-
if (!isPlainObject(map)) continue;
|
|
400
|
-
for (const [key, member] of Object.entries(map)) {
|
|
401
|
-
if (isPlainObject(member)) assertNode(member, child(`${keyword}.${key}`));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
for (const keyword of SCHEMA_ARRAY_KEYWORDS) {
|
|
405
|
-
const members = node[keyword];
|
|
406
|
-
if (!Array.isArray(members)) continue;
|
|
407
|
-
members.forEach((member, index) => {
|
|
408
|
-
if (isPlainObject(member)) assertNode(member, child(`${keyword}[${index}]`));
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
for (const keyword of SINGLE_SCHEMA_KEYWORDS) {
|
|
412
|
-
const member = node[keyword];
|
|
413
|
-
if (isPlainObject(member)) assertNode(member, child(keyword));
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
function assertXaiOutputJsonSchema(schema) {
|
|
417
|
-
if (isPlainObject(schema)) assertNode(schema, "");
|
|
418
|
-
}
|
|
1152
|
+
|
|
1153
|
+
// src/json-schema.ts
|
|
1154
|
+
var XAI_JSON_SCHEMA_PROFILE = {
|
|
1155
|
+
provider: "xai",
|
|
1156
|
+
keywords: [
|
|
1157
|
+
"type",
|
|
1158
|
+
"properties",
|
|
1159
|
+
"required",
|
|
1160
|
+
"additionalProperties",
|
|
1161
|
+
"enum",
|
|
1162
|
+
"const",
|
|
1163
|
+
"anyOf",
|
|
1164
|
+
"$ref",
|
|
1165
|
+
"$defs",
|
|
1166
|
+
"items",
|
|
1167
|
+
"prefixItems",
|
|
1168
|
+
"minItems",
|
|
1169
|
+
"maxItems",
|
|
1170
|
+
"minimum",
|
|
1171
|
+
"maximum",
|
|
1172
|
+
"exclusiveMinimum",
|
|
1173
|
+
"exclusiveMaximum",
|
|
1174
|
+
"minLength",
|
|
1175
|
+
"maxLength",
|
|
1176
|
+
"minProperties",
|
|
1177
|
+
"maxProperties",
|
|
1178
|
+
"pattern",
|
|
1179
|
+
"format"
|
|
1180
|
+
],
|
|
1181
|
+
formats: ["date", "time", "date-time", "email", "uuid", "ipv4", "ipv6", "uri"],
|
|
1182
|
+
limits: {
|
|
1183
|
+
minLength: 2048,
|
|
1184
|
+
maxLength: 2048,
|
|
1185
|
+
minItems: 256,
|
|
1186
|
+
maxItems: 256,
|
|
1187
|
+
minProperties: 64,
|
|
1188
|
+
maxProperties: 64
|
|
1189
|
+
},
|
|
1190
|
+
circularRefs: false,
|
|
1191
|
+
booleanItems: false,
|
|
1192
|
+
patternSubset: true
|
|
1193
|
+
};
|
|
419
1194
|
var xaiPricingVersion = "xai-2026-09-25";
|
|
420
1195
|
var XAI_TOOL_RATE_MICRO_USD = {
|
|
421
1196
|
web_search_calls: 5e3,
|
|
@@ -429,6 +1204,51 @@ var XAI_TOOL_COUNTER_KEYS = [
|
|
|
429
1204
|
];
|
|
430
1205
|
var X_SEARCH_ITEM_COUNTERS = ["x_posts_fetched", "x_users_fetched"];
|
|
431
1206
|
var LONG_CONTEXT_THRESHOLD = 2e5;
|
|
1207
|
+
var XAI_SERVER_TOOL_COUNTERS = Object.freeze({
|
|
1208
|
+
web_search_calls: "priced",
|
|
1209
|
+
x_posts_fetched: "priced",
|
|
1210
|
+
x_users_fetched: "priced",
|
|
1211
|
+
x_search_calls: "superseded",
|
|
1212
|
+
code_interpreter_calls: "fee_unpriced",
|
|
1213
|
+
file_search_calls: "fee_unpriced",
|
|
1214
|
+
document_search_calls: "fee_unpriced",
|
|
1215
|
+
image_generation_calls: "fee_unpriced",
|
|
1216
|
+
mcp_calls: "token_only"
|
|
1217
|
+
});
|
|
1218
|
+
function classifyUnpricedXaiToolCounters(usage) {
|
|
1219
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1220
|
+
const raw = usage.raw;
|
|
1221
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
1222
|
+
const nested = raw["server_side_tool_usage_details"];
|
|
1223
|
+
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) {
|
|
1224
|
+
for (const [key, value] of Object.entries(nested)) candidates.set(key, value);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
for (const [key, value] of Object.entries(usage.details)) {
|
|
1228
|
+
if (Object.hasOwn(XAI_SERVER_TOOL_COUNTERS, key) && !candidates.has(key)) {
|
|
1229
|
+
candidates.set(key, value);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const feeUnpriced = [];
|
|
1233
|
+
const unknown = [];
|
|
1234
|
+
for (const [key, value] of candidates) {
|
|
1235
|
+
if (typeof value !== "number" || !(value > 0)) continue;
|
|
1236
|
+
if (!Object.hasOwn(XAI_SERVER_TOOL_COUNTERS, key)) unknown.push(key);
|
|
1237
|
+
else if (XAI_SERVER_TOOL_COUNTERS[key] === "fee_unpriced") feeUnpriced.push(key);
|
|
1238
|
+
}
|
|
1239
|
+
return { feeUnpriced, unknown };
|
|
1240
|
+
}
|
|
1241
|
+
function unpricedXaiToolCounters(usage) {
|
|
1242
|
+
const { feeUnpriced, unknown } = classifyUnpricedXaiToolCounters(usage);
|
|
1243
|
+
return [...feeUnpriced, ...unknown];
|
|
1244
|
+
}
|
|
1245
|
+
var XAI_ESTIMATED_USAGE_KEY = "usage_estimated";
|
|
1246
|
+
var TICKS_PER_MICRO_USD = 1e4;
|
|
1247
|
+
function providerReportedCost(usage) {
|
|
1248
|
+
const ticks = usage.details["cost_in_usd_ticks"];
|
|
1249
|
+
if (typeof ticks !== "number" || !Number.isFinite(ticks) || ticks < 0) return void 0;
|
|
1250
|
+
return { microUsd: Math.round(ticks / TICKS_PER_MICRO_USD) };
|
|
1251
|
+
}
|
|
432
1252
|
var XAI_PRICING = Object.freeze({
|
|
433
1253
|
// ── grok-4.5 ── $2.00/$6.00 (<200k), $4.00/$12.00 (>=200k); cached $0.30/$0.60
|
|
434
1254
|
"grok-4.5": {
|
|
@@ -506,6 +1326,11 @@ function scaleRates(rates, factor) {
|
|
|
506
1326
|
return scaled;
|
|
507
1327
|
}
|
|
508
1328
|
function computeXaiCost(model, usage, tier) {
|
|
1329
|
+
const cost = priceXaiCall(model, usage, tier);
|
|
1330
|
+
const providerReported = providerReportedCost(usage);
|
|
1331
|
+
return providerReported === void 0 ? cost : { ...cost, providerReported };
|
|
1332
|
+
}
|
|
1333
|
+
function priceXaiCall(model, usage, tier) {
|
|
509
1334
|
const listed = lookupConcreteRates(model, tier);
|
|
510
1335
|
if (listed === void 0) {
|
|
511
1336
|
return core.computeCost(model, usage, tier, lookupConcreteRates, xaiPricingVersion);
|
|
@@ -541,11 +1366,12 @@ function computeXaiCost(model, usage, tier) {
|
|
|
541
1366
|
return sum + Math.round(count * XAI_TOOL_RATE_MICRO_USD[key]);
|
|
542
1367
|
}, 0);
|
|
543
1368
|
const microUsd = inputCost + cachedCost + outputCost + toolsCost;
|
|
1369
|
+
const unpricedCounters = unpricedXaiToolCounters(usage);
|
|
544
1370
|
return {
|
|
545
1371
|
microUsd,
|
|
546
1372
|
usd: microUsd / 1e6,
|
|
547
1373
|
pricingVersion: xaiPricingVersion,
|
|
548
|
-
confidence: missingWebCounter || attachmentUnpinned ? "estimated" : "exact",
|
|
1374
|
+
confidence: missingWebCounter || attachmentUnpinned || unpricedCounters.length > 0 || usage.details[XAI_ESTIMATED_USAGE_KEY] === 1 ? "estimated" : "exact",
|
|
549
1375
|
details: {
|
|
550
1376
|
input: inputCost,
|
|
551
1377
|
cached: cachedCost,
|
|
@@ -579,22 +1405,42 @@ function isXaiMessageItem(item) {
|
|
|
579
1405
|
function isXaiReasoningItem(item) {
|
|
580
1406
|
return item.type === "reasoning" && Array.isArray(item.summary);
|
|
581
1407
|
}
|
|
1408
|
+
function partText(part) {
|
|
1409
|
+
return isPlainRecord(part) && typeof part["text"] === "string" ? part["text"] : void 0;
|
|
1410
|
+
}
|
|
1411
|
+
function boundedNote(value) {
|
|
1412
|
+
return value.length > 500 ? `${value.slice(0, 500)}...` : value;
|
|
1413
|
+
}
|
|
582
1414
|
function badXaiRequest(message) {
|
|
583
1415
|
return new core.LlmError(message, { kind: "bad_request", retryable: false });
|
|
584
1416
|
}
|
|
585
|
-
var ALLOWED_XAI_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/jpg", "image/png"]);
|
|
586
1417
|
var MAX_XAI_INLINE_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
1418
|
+
function decodedBase64Length(data) {
|
|
1419
|
+
const plain = (length2, tail) => {
|
|
1420
|
+
let padding = 0;
|
|
1421
|
+
while (padding < 2 && tail.charCodeAt(tail.length - 1 - padding) === 61)
|
|
1422
|
+
padding += 1;
|
|
1423
|
+
return Math.floor((length2 - padding) * 3 / 4);
|
|
1424
|
+
};
|
|
1425
|
+
const upper = plain(data.length, data);
|
|
1426
|
+
if (upper <= MAX_XAI_INLINE_IMAGE_BYTES) return upper;
|
|
1427
|
+
let length = 0;
|
|
1428
|
+
let end = data.length;
|
|
1429
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
1430
|
+
const c = data.charCodeAt(i);
|
|
1431
|
+
if (c !== 32 && c !== 10 && c !== 13 && c !== 9) {
|
|
1432
|
+
length += 1;
|
|
1433
|
+
end = i + 1;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return plain(length, data.slice(0, end));
|
|
1437
|
+
}
|
|
587
1438
|
function mapPart(p) {
|
|
588
1439
|
switch (p.kind) {
|
|
589
1440
|
case "text":
|
|
590
1441
|
return { type: "input_text", text: p.text };
|
|
591
1442
|
case "inline-media": {
|
|
592
|
-
|
|
593
|
-
throw badXaiRequest(
|
|
594
|
-
`xAI vision only supports image/jpeg and image/png; got mimeType "${p.mimeType}".`
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
const byteLength = Buffer.from(p.data, "base64").length;
|
|
1443
|
+
const byteLength = decodedBase64Length(p.data);
|
|
598
1444
|
if (byteLength > MAX_XAI_INLINE_IMAGE_BYTES) {
|
|
599
1445
|
throw badXaiRequest(
|
|
600
1446
|
`xAI inline images must be at most 20 MiB; got ${byteLength} bytes.`
|
|
@@ -604,8 +1450,7 @@ function mapPart(p) {
|
|
|
604
1450
|
}
|
|
605
1451
|
case "file-uri": {
|
|
606
1452
|
const isPublicHttpUrl = p.uri.startsWith("http://") || p.uri.startsWith("https://");
|
|
607
|
-
|
|
608
|
-
if (!isPublicHttpUrl || !isAllowedImageType) {
|
|
1453
|
+
if (!isPublicHttpUrl) {
|
|
609
1454
|
throw badXaiRequest(
|
|
610
1455
|
`xAI only accepts public http(s) image URLs via FileUriPart; got scheme of "${p.uri}" / mimeType "${p.mimeType}".`
|
|
611
1456
|
);
|
|
@@ -637,9 +1482,75 @@ var XAI_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
637
1482
|
"tools",
|
|
638
1483
|
"parallelToolCalls",
|
|
639
1484
|
"toolChoice",
|
|
640
|
-
"maxTurns"
|
|
1485
|
+
"maxTurns",
|
|
1486
|
+
"searchBudget"
|
|
641
1487
|
]);
|
|
1488
|
+
var XAI_DEFAULT_SCHEMA_NAME = "structured_output";
|
|
1489
|
+
var XAI_SCHEMA_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
642
1490
|
var XAI_SERVER_TOOL_CHOICES = /* @__PURE__ */ new Set(["auto", "required", "none"]);
|
|
1491
|
+
var XAI_SEARCH_BUDGET_KEYS = ["maxWebSearchCalls", "maxXItems"];
|
|
1492
|
+
function mapXaiSearchBudget(value, tools, model) {
|
|
1493
|
+
if (!isPlainRecord(value)) {
|
|
1494
|
+
throw badXaiRequest(
|
|
1495
|
+
`providerOptions.xai.searchBudget must be an object for model "${model}".`
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
const unknown = Object.keys(value).filter(
|
|
1499
|
+
(key) => !XAI_SEARCH_BUDGET_KEYS.includes(key)
|
|
1500
|
+
);
|
|
1501
|
+
if (unknown.length > 0) {
|
|
1502
|
+
throw badXaiRequest(
|
|
1503
|
+
`providerOptions.xai.searchBudget contains unsupported keys [${unknown.join(
|
|
1504
|
+
", "
|
|
1505
|
+
)}] for model "${model}". Allowed keys: ${XAI_SEARCH_BUDGET_KEYS.join(", ")}.`
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
const budget = {};
|
|
1509
|
+
for (const key of XAI_SEARCH_BUDGET_KEYS) {
|
|
1510
|
+
const entry = value[key];
|
|
1511
|
+
if (entry === void 0) continue;
|
|
1512
|
+
if (typeof entry !== "number" || !Number.isInteger(entry) || entry < 1) {
|
|
1513
|
+
throw badXaiRequest(
|
|
1514
|
+
`providerOptions.xai.searchBudget.${key} must be an integer >= 1 for model "${model}".`
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
budget[key] = entry;
|
|
1518
|
+
}
|
|
1519
|
+
if (budget.maxWebSearchCalls === void 0 && budget.maxXItems === void 0) {
|
|
1520
|
+
throw badXaiRequest(
|
|
1521
|
+
`providerOptions.xai.searchBudget must set maxWebSearchCalls or maxXItems for model "${model}".`
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
const hasTool = (type) => tools?.some((tool) => tool["type"] === type) === true;
|
|
1525
|
+
if (budget.maxWebSearchCalls !== void 0 && !hasTool("web_search")) {
|
|
1526
|
+
throw badXaiRequest(
|
|
1527
|
+
`providerOptions.xai.searchBudget.maxWebSearchCalls requires a web_search tool in providerOptions.xai.tools for model "${model}".`
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
if (budget.maxXItems !== void 0 && !hasTool("x_search")) {
|
|
1531
|
+
throw badXaiRequest(
|
|
1532
|
+
`providerOptions.xai.searchBudget.maxXItems requires an x_search tool in providerOptions.xai.tools for model "${model}".`
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
return budget;
|
|
1536
|
+
}
|
|
1537
|
+
function exceededSearchBudget(budget, details) {
|
|
1538
|
+
const over = [];
|
|
1539
|
+
const webCalls = details[WEB_SEARCH_COUNTER];
|
|
1540
|
+
if (budget.maxWebSearchCalls !== void 0 && webCalls !== void 0 && webCalls > budget.maxWebSearchCalls) {
|
|
1541
|
+
over.push(
|
|
1542
|
+
`${WEB_SEARCH_COUNTER} ${webCalls} > maxWebSearchCalls ${budget.maxWebSearchCalls}`
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
if (budget.maxXItems !== void 0) {
|
|
1546
|
+
const reported = X_SEARCH_ITEM_COUNTERS.filter((key) => details[key] !== void 0);
|
|
1547
|
+
const items = reported.reduce((sum, key) => sum + details[key], 0);
|
|
1548
|
+
if (reported.length > 0 && items > budget.maxXItems) {
|
|
1549
|
+
over.push(`X items ${items} > maxXItems ${budget.maxXItems}`);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return over;
|
|
1553
|
+
}
|
|
643
1554
|
function mapXaiProviderOptions(xaiOpts, model) {
|
|
644
1555
|
if (xaiOpts === void 0) {
|
|
645
1556
|
return {};
|
|
@@ -654,7 +1565,7 @@ function mapXaiProviderOptions(xaiOpts, model) {
|
|
|
654
1565
|
throw badXaiRequest(
|
|
655
1566
|
`providerOptions.xai contains unsupported keys [${unknownKeys.join(
|
|
656
1567
|
", "
|
|
657
|
-
)}] for model "${model}". Allowed keys: promptCacheKey, tools, parallelToolCalls, toolChoice, maxTurns.`
|
|
1568
|
+
)}] for model "${model}". Allowed keys: promptCacheKey, tools, parallelToolCalls, toolChoice, maxTurns, searchBudget.`
|
|
658
1569
|
);
|
|
659
1570
|
}
|
|
660
1571
|
const mapped = {};
|
|
@@ -705,15 +1616,25 @@ function mapXaiProviderOptions(xaiOpts, model) {
|
|
|
705
1616
|
}
|
|
706
1617
|
mapped.maxTurns = maxTurns;
|
|
707
1618
|
}
|
|
1619
|
+
if (xaiOpts["searchBudget"] !== void 0) {
|
|
1620
|
+
mapped.searchBudget = mapXaiSearchBudget(xaiOpts["searchBudget"], mapped.tools, model);
|
|
1621
|
+
}
|
|
708
1622
|
return mapped;
|
|
709
1623
|
}
|
|
710
1624
|
function parseXaiReplayState(value, model) {
|
|
711
1625
|
if (value === void 0) return void 0;
|
|
712
|
-
|
|
1626
|
+
const keys = isPlainRecord(value) ? Object.keys(value) : [];
|
|
1627
|
+
if (keys.some((key) => key !== "xai")) {
|
|
1628
|
+
throw badXaiRequest(
|
|
1629
|
+
`transientProviderState has unexpected key(s) [${keys.join(", ")}]; xAI state is exactly { xai: { model, input } } (another provider's state is rejected).`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
const inner = isPlainRecord(value) ? value["xai"] : void 0;
|
|
1633
|
+
if (!isPlainRecord(inner) || inner["model"] !== model || !Array.isArray(inner["input"]) || inner["input"].length === 0 || inner["input"].some(
|
|
713
1634
|
(item) => !isPlainRecord(item) || typeof item["type"] !== "string" && typeof item["role"] !== "string"
|
|
714
1635
|
)) {
|
|
715
1636
|
throw badXaiRequest(
|
|
716
|
-
`transientProviderState must
|
|
1637
|
+
`transientProviderState must be { xai: { model, input } } with the full xAI wire input, bound to the requested model "${model}".`
|
|
717
1638
|
);
|
|
718
1639
|
}
|
|
719
1640
|
return value;
|
|
@@ -847,35 +1768,56 @@ function isXaiSafetyCheckBody(rawErr) {
|
|
|
847
1768
|
const text = extractXaiErrorBodyText(rawErr);
|
|
848
1769
|
return text !== void 0 && text.startsWith(XAI_SAFETY_CHECK_MESSAGE_PREFIX);
|
|
849
1770
|
}
|
|
850
|
-
var
|
|
851
|
-
function
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
function isXaiTransportError(rawErr) {
|
|
858
|
-
if (!(rawErr instanceof Error)) return false;
|
|
859
|
-
const ctorName = rawErr.constructor.name;
|
|
860
|
-
if (ctorName === "APIConnectionError" || ctorName === "APIConnectionTimeoutError") {
|
|
861
|
-
return true;
|
|
862
|
-
}
|
|
863
|
-
if (matchesXaiTransportSignature(rawErr)) return true;
|
|
864
|
-
const cause = rawErr.cause;
|
|
865
|
-
if (cause instanceof Error) {
|
|
866
|
-
const causeCtorName = cause.constructor.name;
|
|
867
|
-
if (causeCtorName === "APIConnectionError" || causeCtorName === "APIConnectionTimeoutError") {
|
|
868
|
-
return true;
|
|
869
|
-
}
|
|
870
|
-
if (matchesXaiTransportSignature(cause)) return true;
|
|
871
|
-
}
|
|
872
|
-
return false;
|
|
1771
|
+
var XAI_CREDITS_EXHAUSTED_BODY = /^Your team \S+ has either used all available credits or reached its monthly spending limit/;
|
|
1772
|
+
function isXaiCreditsExhaustedBody(rawErr) {
|
|
1773
|
+
const text = extractXaiErrorBodyText(rawErr);
|
|
1774
|
+
return text !== void 0 && XAI_CREDITS_EXHAUSTED_BODY.test(text);
|
|
1775
|
+
}
|
|
1776
|
+
function isOpenAiSdkConnectionError(rawErr) {
|
|
1777
|
+
return rawErr instanceof Error && (rawErr.constructor.name === "APIConnectionError" || rawErr.constructor.name === "APIConnectionTimeoutError");
|
|
873
1778
|
}
|
|
874
|
-
|
|
1779
|
+
var UNDICI_HEADERS_TIMEOUT_CODE = "UND_ERR_HEADERS_TIMEOUT";
|
|
1780
|
+
var UNDICI_BODY_TIMEOUT_CODE = "UND_ERR_BODY_TIMEOUT";
|
|
1781
|
+
var SDK_DEADLINE_SLACK_MS = 5;
|
|
1782
|
+
function isSdkDeadline(rawErr, deadline) {
|
|
1783
|
+
if (deadline === void 0) return false;
|
|
1784
|
+
if (!(rawErr instanceof Error) || rawErr.constructor.name !== "APIConnectionTimeoutError") {
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
const causes = core.causeChain(rawErr).slice(1);
|
|
1788
|
+
if (!causes.every((e) => e.name === "AbortError")) return false;
|
|
1789
|
+
return deadline.elapsedMs >= deadline.timeoutMs - SDK_DEADLINE_SLACK_MS;
|
|
1790
|
+
}
|
|
1791
|
+
function xaiTransportTimeoutKind(rawErr, deadline) {
|
|
1792
|
+
const chain = core.causeChain(rawErr);
|
|
1793
|
+
const has = (code, name) => chain.some((e) => {
|
|
1794
|
+
const o = e;
|
|
1795
|
+
return o.code === code || o.name === name;
|
|
1796
|
+
});
|
|
1797
|
+
if (has(UNDICI_HEADERS_TIMEOUT_CODE, "HeadersTimeoutError")) return "headers";
|
|
1798
|
+
if (has(UNDICI_BODY_TIMEOUT_CODE, "BodyTimeoutError")) return "body";
|
|
1799
|
+
if (isSdkDeadline(rawErr, deadline)) return "sdk";
|
|
1800
|
+
return void 0;
|
|
1801
|
+
}
|
|
1802
|
+
function classifyXaiError(rawErr, deadline, estimatedInputTokens = 0, requestTimeoutMs) {
|
|
875
1803
|
if (rawErr instanceof core.LlmError) {
|
|
876
1804
|
return rawErr;
|
|
877
1805
|
}
|
|
1806
|
+
if (rawErr instanceof XaiStreamError) {
|
|
1807
|
+
return classifyStreamError(rawErr, estimatedInputTokens, requestTimeoutMs);
|
|
1808
|
+
}
|
|
878
1809
|
const base = core.classifyError(rawErr);
|
|
1810
|
+
const transportTimeout = xaiTransportTimeoutKind(rawErr, deadline);
|
|
1811
|
+
if (transportTimeout !== void 0) {
|
|
1812
|
+
const which = transportTimeout === "sdk" ? "SDK deadline" : `transport ${transportTimeout} timer`;
|
|
1813
|
+
return new core.LlmError(`xAI request hit the ${which}: ${base.message}`, {
|
|
1814
|
+
kind: "timeout",
|
|
1815
|
+
retryable: false,
|
|
1816
|
+
reason: "transport_timeout",
|
|
1817
|
+
provider: "xai",
|
|
1818
|
+
cause: base.cause ?? rawErr
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
879
1821
|
if (base.httpStatus === 400 && isXaiAuthFailureBody(rawErr)) {
|
|
880
1822
|
return new core.LlmError(base.message, {
|
|
881
1823
|
kind: "invalid_auth",
|
|
@@ -895,24 +1837,257 @@ function classifyXaiError(rawErr) {
|
|
|
895
1837
|
cause: base.cause ?? rawErr
|
|
896
1838
|
});
|
|
897
1839
|
}
|
|
898
|
-
if (base.
|
|
899
|
-
return new core.LlmError(
|
|
900
|
-
|
|
901
|
-
|
|
1840
|
+
if ((base.httpStatus === 429 || base.httpStatus === 403) && isXaiCreditsExhaustedBody(rawErr)) {
|
|
1841
|
+
return new core.LlmError(
|
|
1842
|
+
(extractXaiErrorBodyText(rawErr) ?? base.message).replace(
|
|
1843
|
+
/^Your team \S+ /,
|
|
1844
|
+
"Your team "
|
|
1845
|
+
),
|
|
1846
|
+
{
|
|
1847
|
+
kind: "rate_limited",
|
|
1848
|
+
retryable: false,
|
|
1849
|
+
reason: "credits_exhausted",
|
|
1850
|
+
httpStatus: base.httpStatus,
|
|
1851
|
+
provider: "xai",
|
|
1852
|
+
cause: base.cause ?? rawErr
|
|
1853
|
+
}
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
if (base.kind === "unknown" && isOpenAiSdkConnectionError(rawErr)) {
|
|
1857
|
+
return new core.LlmError(base.message, {
|
|
1858
|
+
kind: "server",
|
|
1859
|
+
retryable: true,
|
|
1860
|
+
provider: "xai",
|
|
1861
|
+
cause: base.cause ?? rawErr
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
return new core.LlmError(base.message, {
|
|
1865
|
+
kind: base.kind,
|
|
1866
|
+
retryable: base.retryable,
|
|
1867
|
+
...base.httpStatus !== void 0 ? { httpStatus: base.httpStatus } : {},
|
|
1868
|
+
...base.retryAfterMs !== void 0 ? { retryAfterMs: base.retryAfterMs } : {},
|
|
1869
|
+
provider: "xai",
|
|
1870
|
+
cause: base.cause ?? rawErr
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
function classifyFailedResponseCode(code) {
|
|
1874
|
+
switch (code) {
|
|
1875
|
+
case "server_error":
|
|
1876
|
+
return { kind: "server", retryable: true };
|
|
1877
|
+
case "rate_limit_exceeded":
|
|
1878
|
+
return { kind: "rate_limited", retryable: true };
|
|
1879
|
+
case "bio_policy":
|
|
1880
|
+
case "misalignment_policy_violation":
|
|
1881
|
+
case "image_content_policy_violation":
|
|
1882
|
+
return { kind: "content_filter", retryable: false };
|
|
1883
|
+
case "invalid_prompt":
|
|
1884
|
+
case "data_residency_mismatch":
|
|
1885
|
+
case "invalid_image":
|
|
1886
|
+
case "invalid_image_format":
|
|
1887
|
+
case "invalid_base64_image":
|
|
1888
|
+
case "invalid_image_url":
|
|
1889
|
+
case "image_too_large":
|
|
1890
|
+
case "image_too_small":
|
|
1891
|
+
case "image_parse_error":
|
|
1892
|
+
case "invalid_image_mode":
|
|
1893
|
+
case "image_file_too_large":
|
|
1894
|
+
case "unsupported_image_media_type":
|
|
1895
|
+
case "empty_image_file":
|
|
1896
|
+
case "failed_to_download_image":
|
|
1897
|
+
case "image_file_not_found":
|
|
1898
|
+
return { kind: "bad_request", retryable: false };
|
|
1899
|
+
default:
|
|
1900
|
+
return { kind: "unknown", retryable: false };
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
function estimatedStreamUsage(outputChars, inputTokens) {
|
|
1904
|
+
const outputTokens = Math.ceil(outputChars / 4);
|
|
1905
|
+
return {
|
|
1906
|
+
inputTokens,
|
|
1907
|
+
outputTokens,
|
|
1908
|
+
details: { input: inputTokens, output: outputTokens, [XAI_ESTIMATED_USAGE_KEY]: 1 },
|
|
1909
|
+
raw: {
|
|
1910
|
+
estimated: true,
|
|
1911
|
+
basis: "estimate: wire input characters / 4 for input (replayed state included, media not counted), received output characters / 4 for output; hidden reasoning is not counted",
|
|
1912
|
+
outputChars
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
function classifyStreamError(err, estimatedInputTokens, requestTimeoutMs) {
|
|
1917
|
+
const failure = err.failure;
|
|
1918
|
+
const { progressed, outputChars } = err.progress;
|
|
1919
|
+
const usage = err.terminalUsage !== void 0 ? mapUsage(err.terminalUsage) : progressed ? estimatedStreamUsage(outputChars, estimatedInputTokens) : void 0;
|
|
1920
|
+
const common = {
|
|
1921
|
+
provider: "xai",
|
|
1922
|
+
...usage !== void 0 ? { usage } : {},
|
|
1923
|
+
...err.servedServiceTier !== void 0 ? { servedServiceTier: err.servedServiceTier } : {},
|
|
1924
|
+
cause: err
|
|
1925
|
+
};
|
|
1926
|
+
switch (failure.kind) {
|
|
1927
|
+
case "ended_early":
|
|
1928
|
+
case "malformed":
|
|
1929
|
+
return new core.LlmError(err.message, {
|
|
1930
|
+
kind: "server",
|
|
1931
|
+
retryable: !progressed,
|
|
1932
|
+
...common
|
|
1933
|
+
});
|
|
1934
|
+
case "cut": {
|
|
1935
|
+
const base = classifyXaiError(err.cause);
|
|
1936
|
+
return base.kind === "timeout" ? new core.LlmError(base.message, {
|
|
1937
|
+
kind: "timeout",
|
|
1938
|
+
retryable: false,
|
|
1939
|
+
...base.reason !== void 0 ? { reason: base.reason } : {},
|
|
1940
|
+
...common
|
|
1941
|
+
}) : new core.LlmError(err.message, { kind: "server", retryable: false, ...common });
|
|
1942
|
+
}
|
|
1943
|
+
case "deadline":
|
|
1944
|
+
return new core.LlmError(
|
|
1945
|
+
`xAI request hit the client deadline: the stream was still open after the ${requestTimeoutMs ?? failure.timeoutMs} ms request timeout`,
|
|
1946
|
+
{
|
|
1947
|
+
kind: "timeout",
|
|
1948
|
+
retryable: false,
|
|
1949
|
+
reason: "transport_timeout",
|
|
1950
|
+
...common
|
|
1951
|
+
}
|
|
1952
|
+
);
|
|
1953
|
+
case "aborted": {
|
|
1954
|
+
const reason = err.cause;
|
|
1955
|
+
return reason instanceof core.LlmError ? new core.LlmError(reason.message, {
|
|
1956
|
+
kind: reason.kind,
|
|
1957
|
+
retryable: reason.retryable,
|
|
1958
|
+
...reason.reason !== void 0 ? { reason: reason.reason } : {},
|
|
1959
|
+
...common,
|
|
1960
|
+
cause: reason
|
|
1961
|
+
}) : new core.LlmError("Request aborted by caller", {
|
|
1962
|
+
kind: "aborted",
|
|
1963
|
+
retryable: false,
|
|
1964
|
+
...common,
|
|
1965
|
+
cause: reason ?? err
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
case "idle":
|
|
1969
|
+
return new core.LlmError(`xAI request hit the idle timeout: ${err.message}`, {
|
|
1970
|
+
kind: "timeout",
|
|
1971
|
+
retryable: false,
|
|
1972
|
+
reason: "transport_timeout",
|
|
1973
|
+
...common
|
|
1974
|
+
});
|
|
1975
|
+
case "error_event": {
|
|
1976
|
+
const mapped = classifyFailedResponseCode(failure.code);
|
|
1977
|
+
return new core.LlmError(err.message, {
|
|
1978
|
+
kind: mapped.kind,
|
|
1979
|
+
retryable: mapped.kind === "server" && !progressed,
|
|
1980
|
+
mayHaveBilled: true,
|
|
1981
|
+
...common
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
case "not_event_stream":
|
|
1985
|
+
return new core.LlmError(err.message, {
|
|
1986
|
+
kind: "bad_request",
|
|
1987
|
+
retryable: false,
|
|
1988
|
+
mayHaveBilled: true,
|
|
1989
|
+
...common
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
function failedResponseError(response, streamProgressed) {
|
|
1994
|
+
const reported = isPlainRecord(response.error) ? response.error : void 0;
|
|
1995
|
+
const code = typeof reported?.["code"] === "string" ? reported["code"] : void 0;
|
|
1996
|
+
const detail = typeof reported?.["message"] === "string" ? `: ${reported["message"]}` : "";
|
|
1997
|
+
const { kind, retryable } = response.status === "cancelled" ? { kind: "unknown", retryable: false } : classifyFailedResponseCode(code);
|
|
1998
|
+
return new core.LlmError(
|
|
1999
|
+
response.status === "cancelled" ? `xAI response reported status "cancelled"${detail}` : `xAI response failed${code !== void 0 ? ` (error.code "${code}")` : ""}${detail}`,
|
|
2000
|
+
{
|
|
2001
|
+
kind,
|
|
2002
|
+
retryable: retryable && !streamProgressed,
|
|
2003
|
+
mayHaveBilled: true,
|
|
902
2004
|
provider: "xai",
|
|
903
|
-
|
|
904
|
-
|
|
2005
|
+
// Usage is attached only when the failed response billed tokens.
|
|
2006
|
+
...isPlainRecord(response.usage) && typeof response.usage.input_tokens === "number" && typeof response.usage.output_tokens === "number" ? { usage: mapUsage(response.usage) } : {},
|
|
2007
|
+
...typeof response.service_tier === "string" && response.service_tier.length > 0 ? { servedServiceTier: response.service_tier } : {},
|
|
2008
|
+
cause: response.error ?? { status: response.status }
|
|
2009
|
+
}
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
function estimateWireInputTokens(params) {
|
|
2013
|
+
const withoutImageBytes = (key, value) => key === "image_url" && typeof value === "string" && value.startsWith("data:") ? "" : value;
|
|
2014
|
+
try {
|
|
2015
|
+
const wire = JSON.stringify(
|
|
2016
|
+
{
|
|
2017
|
+
input: params.input,
|
|
2018
|
+
instructions: params.instructions,
|
|
2019
|
+
tools: params.tools,
|
|
2020
|
+
text: params.text
|
|
2021
|
+
},
|
|
2022
|
+
withoutImageBytes
|
|
2023
|
+
);
|
|
2024
|
+
return Math.ceil(wire.length / 4);
|
|
2025
|
+
} catch {
|
|
2026
|
+
return 0;
|
|
905
2027
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
2028
|
+
}
|
|
2029
|
+
var XAI_COUNT_TOKENS_TIMEOUT_MS = 6e4;
|
|
2030
|
+
function snapshotXaiTransport(transport) {
|
|
2031
|
+
if (transport === void 0) return void 0;
|
|
2032
|
+
const reject = (message) => new core.LlmError(`xaiAdapter: ${message}`, {
|
|
2033
|
+
kind: "bad_request",
|
|
2034
|
+
retryable: false,
|
|
2035
|
+
provider: "xai"
|
|
913
2036
|
});
|
|
2037
|
+
const candidate = transport;
|
|
2038
|
+
if (candidate === null || typeof candidate !== "object") {
|
|
2039
|
+
throw reject("transport must be an object { fetch, fetchOptions?, idleTimeoutMs? }.");
|
|
2040
|
+
}
|
|
2041
|
+
if (typeof transport.fetch !== "function") {
|
|
2042
|
+
throw reject("transport.fetch must be a function.");
|
|
2043
|
+
}
|
|
2044
|
+
const idle = transport.idleTimeoutMs;
|
|
2045
|
+
if (idle !== void 0 && (typeof idle !== "number" || !Number.isInteger(idle) || idle < 1 || idle > XAI_MAX_TIMEOUT_MS)) {
|
|
2046
|
+
throw reject(
|
|
2047
|
+
`transport.idleTimeoutMs must be an integer from 1 to ${XAI_MAX_TIMEOUT_MS}.`
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
const fetchOptions = transport.fetchOptions;
|
|
2051
|
+
if (fetchOptions !== void 0 && (fetchOptions === null || typeof fetchOptions !== "object" || Array.isArray(fetchOptions))) {
|
|
2052
|
+
throw reject("transport.fetchOptions must be an object.");
|
|
2053
|
+
}
|
|
2054
|
+
if (fetchOptions !== void 0) {
|
|
2055
|
+
for (const key of XAI_RESERVED_FETCH_OPTION_KEYS) {
|
|
2056
|
+
if (key in fetchOptions) {
|
|
2057
|
+
throw reject(
|
|
2058
|
+
`transport.fetchOptions.${key} is not supported; the request owns it.`
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return {
|
|
2064
|
+
fetch: transport.fetch,
|
|
2065
|
+
...fetchOptions !== void 0 ? {
|
|
2066
|
+
fetchOptions: {
|
|
2067
|
+
...fetchOptions
|
|
2068
|
+
}
|
|
2069
|
+
} : {},
|
|
2070
|
+
...transport.idleTimeoutMs !== void 0 ? { idleTimeoutMs: transport.idleTimeoutMs } : {}
|
|
2071
|
+
};
|
|
914
2072
|
}
|
|
915
2073
|
function xaiAdapter(opts) {
|
|
2074
|
+
return xaiAdapterWithSeams(opts, {});
|
|
2075
|
+
}
|
|
2076
|
+
function xaiAdapterWithSeams(opts, seams) {
|
|
2077
|
+
if (opts?.client !== void 0 && opts.transport !== void 0) {
|
|
2078
|
+
throw new core.LlmError(
|
|
2079
|
+
"xaiAdapter: `transport` has no effect on an injected `client`; configure the transport on the client itself.",
|
|
2080
|
+
{ kind: "bad_request", retryable: false, provider: "xai" }
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
const transport = snapshotXaiTransport(opts?.transport);
|
|
2084
|
+
const countTokensTimeoutMs = opts?.countTokensTimeoutMs ?? XAI_COUNT_TOKENS_TIMEOUT_MS;
|
|
2085
|
+
if (!Number.isInteger(countTokensTimeoutMs) || countTokensTimeoutMs < 1 || countTokensTimeoutMs > XAI_MAX_TIMEOUT_MS) {
|
|
2086
|
+
throw new core.LlmError(
|
|
2087
|
+
`xaiAdapter: countTokensTimeoutMs must be an integer from 1 to ${XAI_MAX_TIMEOUT_MS}.`,
|
|
2088
|
+
{ kind: "bad_request", retryable: false, provider: "xai" }
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
916
2091
|
return {
|
|
917
2092
|
id: "xai",
|
|
918
2093
|
async run(req, ctx) {
|
|
@@ -924,12 +2099,16 @@ function xaiAdapter(opts) {
|
|
|
924
2099
|
}
|
|
925
2100
|
const warnings = [];
|
|
926
2101
|
const model = req.model;
|
|
927
|
-
if (req.modelDescriptor !== void 0
|
|
928
|
-
|
|
2102
|
+
if (req.modelDescriptor !== void 0) {
|
|
2103
|
+
core.assertModelMatchesDescriptor(req, req.modelDescriptor, "xai");
|
|
2104
|
+
}
|
|
2105
|
+
const mediaDescriptor = req.modelDescriptor ?? xaiRegistry.resolve("xai", model);
|
|
2106
|
+
if (mediaDescriptor !== void 0) {
|
|
2107
|
+
core.assertInputMimeTypesAdmitted(req.messages, mediaDescriptor, "xai");
|
|
929
2108
|
}
|
|
930
|
-
if (xaiRegistry.resolve("xai", model)?.capabilities?.
|
|
2109
|
+
if (xaiRegistry.resolve("xai", model)?.capabilities?.continuation === "state" && req.modelDescriptor?.capabilities?.continuation !== "state") {
|
|
931
2110
|
throw badXaiRequest(
|
|
932
|
-
`A matching xAI model descriptor with
|
|
2111
|
+
`A matching xAI model descriptor with continuation "state" is required for "${model}".`
|
|
933
2112
|
);
|
|
934
2113
|
}
|
|
935
2114
|
const genConfig = req.config;
|
|
@@ -937,11 +2116,11 @@ function xaiAdapter(opts) {
|
|
|
937
2116
|
genConfig.providerOptions?.["xai"],
|
|
938
2117
|
model
|
|
939
2118
|
);
|
|
940
|
-
const replayRequired = req.modelDescriptor?.capabilities?.
|
|
2119
|
+
const replayRequired = req.modelDescriptor?.capabilities?.continuation === "state";
|
|
941
2120
|
const replayState = parseXaiReplayState(req.transientProviderState, model);
|
|
942
2121
|
if (replayState !== void 0 && !replayRequired) {
|
|
943
2122
|
throw badXaiRequest(
|
|
944
|
-
`transientProviderState requires a
|
|
2123
|
+
`transientProviderState requires a model descriptor with continuation "state" for "${model}".`
|
|
945
2124
|
);
|
|
946
2125
|
}
|
|
947
2126
|
if (replayState !== void 0 && req.messages.length === 0) {
|
|
@@ -949,12 +2128,12 @@ function xaiAdapter(opts) {
|
|
|
949
2128
|
`Stateless conversation replay for model "${model}" requires new messages to append.`
|
|
950
2129
|
);
|
|
951
2130
|
}
|
|
952
|
-
const input = [...replayState?.input ?? []];
|
|
2131
|
+
const input = [...replayState?.xai.input ?? []];
|
|
953
2132
|
const replayCallIds = new Set(
|
|
954
|
-
replayState?.input.filter((item) => isPlainRecord(item) && item["type"] === "function_call").map((item) => isPlainRecord(item) ? item["call_id"] : void 0).filter((id) => typeof id === "string") ?? []
|
|
2133
|
+
replayState?.xai.input.filter((item) => isPlainRecord(item) && item["type"] === "function_call").map((item) => isPlainRecord(item) ? item["call_id"] : void 0).filter((id) => typeof id === "string") ?? []
|
|
955
2134
|
);
|
|
956
2135
|
const replayedResultIds = new Set(
|
|
957
|
-
replayState?.input.filter(
|
|
2136
|
+
replayState?.xai.input.filter(
|
|
958
2137
|
(item) => isPlainRecord(item) && item["type"] === "function_call_output"
|
|
959
2138
|
).map((item) => isPlainRecord(item) ? item["call_id"] : void 0).filter((id) => typeof id === "string") ?? []
|
|
960
2139
|
);
|
|
@@ -1074,8 +2253,18 @@ function xaiAdapter(opts) {
|
|
|
1074
2253
|
const structuredOutputRequested = req.outputJsonSchema !== void 0;
|
|
1075
2254
|
if (structuredOutputRequested) {
|
|
1076
2255
|
const schema = req.outputJsonSchema;
|
|
1077
|
-
|
|
1078
|
-
|
|
2256
|
+
core.assertJsonSchemaProfile(
|
|
2257
|
+
schema,
|
|
2258
|
+
"output.jsonSchema",
|
|
2259
|
+
XAI_JSON_SCHEMA_PROFILE
|
|
2260
|
+
);
|
|
2261
|
+
const title = isPlainRecord(schema) ? schema["title"] : void 0;
|
|
2262
|
+
if (typeof title === "string" && !XAI_SCHEMA_NAME.test(title)) {
|
|
2263
|
+
throw badXaiRequest(
|
|
2264
|
+
`output.jsonSchema.title "${boundedNote(title)}" cannot be the xAI structured-output name for model "${model}": it must match ${XAI_SCHEMA_NAME.source} (letters, digits, "_" and "-", 1 to 64 characters). Rename the title or remove it to use "${XAI_DEFAULT_SCHEMA_NAME}".`
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
const name = typeof title === "string" ? title : XAI_DEFAULT_SCHEMA_NAME;
|
|
1079
2268
|
params.text = {
|
|
1080
2269
|
format: { type: "json_schema", name, schema, strict: true }
|
|
1081
2270
|
};
|
|
@@ -1129,6 +2318,13 @@ function xaiAdapter(opts) {
|
|
|
1129
2318
|
`tools is not supported for xai model "${model}" (capabilities.functionCalling is not true).`
|
|
1130
2319
|
);
|
|
1131
2320
|
}
|
|
2321
|
+
req.tools.forEach((tool, index) => {
|
|
2322
|
+
core.assertJsonSchemaProfile(
|
|
2323
|
+
tool.inputJsonSchema,
|
|
2324
|
+
`tools[${index}].inputJsonSchema`,
|
|
2325
|
+
XAI_JSON_SCHEMA_PROFILE
|
|
2326
|
+
);
|
|
2327
|
+
});
|
|
1132
2328
|
const functionTools = req.tools.map((tool) => ({
|
|
1133
2329
|
type: "function",
|
|
1134
2330
|
name: tool.name,
|
|
@@ -1141,130 +2337,300 @@ function xaiAdapter(opts) {
|
|
|
1141
2337
|
}
|
|
1142
2338
|
}
|
|
1143
2339
|
if (xaiProviderConfig.parallelToolCalls !== void 0) {
|
|
2340
|
+
if (params.tools === void 0 || params.tools.length === 0) {
|
|
2341
|
+
throw badXaiRequest(
|
|
2342
|
+
`providerOptions.xai.parallelToolCalls requires at least one tool (function tools or providerOptions.xai.tools) for model "${model}".`
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
1144
2345
|
params.parallel_tool_calls = xaiProviderConfig.parallelToolCalls;
|
|
1145
2346
|
}
|
|
1146
2347
|
let response;
|
|
2348
|
+
let responseMeta;
|
|
2349
|
+
let sdkCallStart;
|
|
1147
2350
|
try {
|
|
1148
|
-
const buildClient =
|
|
1149
|
-
const client = opts?.client !== void 0 ? opts.client : await buildClient(ctx.auth);
|
|
2351
|
+
const buildClient = seams.clientFactory ?? buildXaiClient;
|
|
2352
|
+
const client = opts?.client !== void 0 ? opts.client : await buildClient(ctx.auth, transport);
|
|
1150
2353
|
ctx.logger.debug(
|
|
1151
2354
|
{ model, configKeys: Object.keys(params) },
|
|
1152
2355
|
"llm.adapter.dispatch"
|
|
1153
2356
|
);
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
ctx.signal !== void 0 ? { signal: ctx.signal } :
|
|
1157
|
-
|
|
2357
|
+
const sdkTimeoutMs = genConfig.timeoutMs !== void 0 ? genConfig.timeoutMs + XAI_TIMEOUT_BUFFER_MS : XAI_DEFAULT_TIMEOUT_MS;
|
|
2358
|
+
const requestOptions = {
|
|
2359
|
+
...ctx.signal !== void 0 ? { signal: ctx.signal } : {},
|
|
2360
|
+
timeout: sdkTimeoutMs,
|
|
2361
|
+
onResponse: (meta) => {
|
|
2362
|
+
responseMeta = meta;
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
sdkCallStart = { startedAt: performance.now(), timeoutMs: sdkTimeoutMs };
|
|
2366
|
+
response = await client.responses.create(params, requestOptions);
|
|
1158
2367
|
} catch (rawErr) {
|
|
1159
|
-
throw classifyXaiError(
|
|
2368
|
+
throw classifyXaiError(
|
|
2369
|
+
rawErr,
|
|
2370
|
+
sdkCallStart !== void 0 ? {
|
|
2371
|
+
timeoutMs: sdkCallStart.timeoutMs,
|
|
2372
|
+
elapsedMs: performance.now() - sdkCallStart.startedAt
|
|
2373
|
+
} : void 0,
|
|
2374
|
+
estimateWireInputTokens(params),
|
|
2375
|
+
genConfig.timeoutMs ?? XAI_DEFAULT_TIMEOUT_MS
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
if (response.status === "failed" || response.status === "cancelled") {
|
|
2379
|
+
throw failedResponseError(response, responseMeta?.streamProgressed === true);
|
|
2380
|
+
}
|
|
2381
|
+
if (!isPlainRecord(response.usage) || typeof response.usage.input_tokens !== "number" || typeof response.usage.output_tokens !== "number") {
|
|
2382
|
+
throw new core.LlmError(
|
|
2383
|
+
`xAI response ${String(response.id)} (status "${String(response.status)}") carries no numeric usage; it cannot be priced.`,
|
|
2384
|
+
{
|
|
2385
|
+
kind: "server",
|
|
2386
|
+
retryable: false,
|
|
2387
|
+
provider: "xai",
|
|
2388
|
+
cause: { id: response.id }
|
|
2389
|
+
}
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
for (const message of responseMeta?.streamNotes ?? []) {
|
|
2393
|
+
warnings.push({ type: "other", message });
|
|
1160
2394
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
if (
|
|
1171
|
-
|
|
2395
|
+
try {
|
|
2396
|
+
let text = "";
|
|
2397
|
+
const reasoningChunks = [];
|
|
2398
|
+
const messageItems = [];
|
|
2399
|
+
const toolCalls = [];
|
|
2400
|
+
const droppedCalls = [];
|
|
2401
|
+
const droppedItems = /* @__PURE__ */ new Set();
|
|
2402
|
+
const outputOrder = [];
|
|
2403
|
+
for (const item of response.output) {
|
|
2404
|
+
if (isXaiMessageItem(item)) {
|
|
2405
|
+
messageItems.push(item);
|
|
2406
|
+
outputOrder.push(item);
|
|
2407
|
+
} else if (isXaiReasoningItem(item)) {
|
|
2408
|
+
for (const part of item.summary) {
|
|
2409
|
+
const summary = partText(part);
|
|
2410
|
+
if (summary !== void 0 && summary.length > 0) {
|
|
2411
|
+
reasoningChunks.push(summary);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
} else if (item.type === "function_call") {
|
|
2415
|
+
const callId = typeof item["call_id"] === "string" ? item["call_id"] : "";
|
|
2416
|
+
const name = typeof item["name"] === "string" ? item["name"] : "";
|
|
2417
|
+
if (response.status !== "completed" || typeof item["status"] === "string" && item["status"] !== "completed") {
|
|
2418
|
+
droppedCalls.push(name.length > 0 ? name : callId);
|
|
2419
|
+
droppedItems.add(item);
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2422
|
+
let args = {};
|
|
2423
|
+
if (typeof item["arguments"] === "string") {
|
|
2424
|
+
try {
|
|
2425
|
+
args = JSON.parse(item["arguments"]);
|
|
2426
|
+
} catch (cause) {
|
|
2427
|
+
throw new core.LlmError(
|
|
2428
|
+
`xAI response ${String(response.id)} holds a completed function call "${name}" whose arguments are not valid JSON.`,
|
|
2429
|
+
{
|
|
2430
|
+
kind: "server",
|
|
2431
|
+
retryable: false,
|
|
2432
|
+
provider: "xai",
|
|
2433
|
+
usage: mapUsage(response.usage),
|
|
2434
|
+
...typeof response.service_tier === "string" && response.service_tier.length > 0 ? { servedServiceTier: response.service_tier } : {},
|
|
2435
|
+
cause
|
|
2436
|
+
}
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
if (callId.length > 0 && name.length > 0) {
|
|
2441
|
+
const call = { toolCallId: callId, toolName: name, args };
|
|
2442
|
+
toolCalls.push(call);
|
|
2443
|
+
outputOrder.push(call);
|
|
2444
|
+
}
|
|
1172
2445
|
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
2446
|
+
}
|
|
2447
|
+
const reasoningText = reasoningChunks.length > 0 ? reasoningChunks.join("\n\n") : void 0;
|
|
2448
|
+
let refusal;
|
|
2449
|
+
if (messageItems.length > 0) {
|
|
2450
|
+
const lastMessage = messageItems[messageItems.length - 1];
|
|
2451
|
+
const ignoredTypes = /* @__PURE__ */ new Map();
|
|
2452
|
+
for (const part of lastMessage.content) {
|
|
2453
|
+
const own = partText(part);
|
|
2454
|
+
if (own !== void 0) {
|
|
2455
|
+
text += own;
|
|
2456
|
+
} else if (isPlainRecord(part) && part["type"] === "refusal") {
|
|
2457
|
+
const said = typeof part["refusal"] === "string" ? part["refusal"] : "";
|
|
2458
|
+
refusal = `${refusal === void 0 ? "" : `${refusal} `}${said}`;
|
|
2459
|
+
} else {
|
|
2460
|
+
const type = isPlainRecord(part) && typeof part["type"] === "string" ? part["type"] : "unknown";
|
|
2461
|
+
ignoredTypes.set(type, (ignoredTypes.get(type) ?? 0) + 1);
|
|
1182
2462
|
}
|
|
1183
2463
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
2464
|
+
for (const [type, count] of ignoredTypes) {
|
|
2465
|
+
warnings.push({
|
|
2466
|
+
type: "other",
|
|
2467
|
+
message: `xai: ignored ${count} message content part(s) of type "${type}" that carry no text; the answer text is built from the output_text parts.`
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
if (messageItems.length > 1) {
|
|
2471
|
+
warnings.push({
|
|
2472
|
+
type: "other",
|
|
2473
|
+
message: `xai: response contained ${messageItems.length} message output items; using the last one and discarding ${messageItems.length - 1} earlier message item(s).`
|
|
2474
|
+
});
|
|
1186
2475
|
}
|
|
1187
2476
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
|
|
2477
|
+
const lastMessageItem = messageItems[messageItems.length - 1];
|
|
2478
|
+
const messageParts = [];
|
|
2479
|
+
for (const entry of outputOrder) {
|
|
2480
|
+
if ("toolCallId" in entry) {
|
|
2481
|
+
messageParts.push({ kind: "tool-call", ...entry });
|
|
2482
|
+
} else if (entry === lastMessageItem && text.length > 0) {
|
|
2483
|
+
messageParts.push({ kind: "text", text });
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
let rawStructured;
|
|
2487
|
+
if (structuredOutputRequested && text.length > 0) {
|
|
2488
|
+
try {
|
|
2489
|
+
rawStructured = JSON.parse(text);
|
|
2490
|
+
} catch {
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
const usage = mapUsage(response.usage);
|
|
2494
|
+
let finishReason = mapFinishReason(response);
|
|
2495
|
+
if (refusal !== void 0) {
|
|
2496
|
+
if (finishReason !== "length") finishReason = "content_filter";
|
|
1193
2497
|
warnings.push({
|
|
1194
2498
|
type: "other",
|
|
1195
|
-
message: `xai:
|
|
2499
|
+
message: `xai: the model refused to answer${refusal.length > 0 ? `: "${boundedNote(refusal)}"` : ""}; the result carries no text for it and finishReason is "${finishReason}".`
|
|
1196
2500
|
});
|
|
1197
2501
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
2502
|
+
if (droppedCalls.length > 0) {
|
|
2503
|
+
const named = droppedCalls.map((name) => `"${name}"`).join(", ");
|
|
2504
|
+
const reason = response.incomplete_details?.reason;
|
|
2505
|
+
warnings.push({
|
|
2506
|
+
type: "other",
|
|
2507
|
+
message: `xai: dropped ${droppedCalls.length} function call(s) (${named}) because the response ended with status "${String(response.status)}"${typeof reason === "string" ? ` (${reason})` : ""}, not "completed": ${reason === "max_output_tokens" ? "the call was cut by the output cap and its arguments are incomplete" : "a call beside an abnormal end is not a call to run"}. The result carries no tool call; finishReason is "${finishReason}".`
|
|
2508
|
+
});
|
|
1204
2509
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
2510
|
+
const expectedToolCounters = expectedServerToolCounters(xaiProviderConfig.tools);
|
|
2511
|
+
const noServerToolRan = response.usage["num_server_side_tools_used"] === 0 && response.usage["server_side_tool_usage_details"] === void 0;
|
|
2512
|
+
if (xaiProviderConfig.tools?.some((tool) => tool["type"] === "web_search") === true) {
|
|
2513
|
+
usage.details["web_search_requested"] = 1;
|
|
2514
|
+
if (noServerToolRan) usage.details[WEB_SEARCH_COUNTER] = 0;
|
|
2515
|
+
}
|
|
2516
|
+
if (xaiProviderConfig.searchBudget !== void 0) {
|
|
2517
|
+
const over = exceededSearchBudget(xaiProviderConfig.searchBudget, usage.details);
|
|
2518
|
+
if (over.length > 0) {
|
|
2519
|
+
usage.details["search_budget_exceeded"] = 1;
|
|
2520
|
+
warnings.push({
|
|
2521
|
+
type: "other",
|
|
2522
|
+
message: `xai: search budget exceeded (${over.join("; ")}); the call is already billed and its result is returned.`
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
1215
2525
|
}
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
|
|
2526
|
+
const { feeUnpriced, unknown } = classifyUnpricedXaiToolCounters(usage);
|
|
2527
|
+
const shown = (keys) => keys.map((key) => `${key}=${String(usage.details[key])}`).join(", ");
|
|
2528
|
+
const feeCounters = hasFileRef ? feeUnpriced.filter((key) => key !== "document_search_calls") : feeUnpriced;
|
|
2529
|
+
if (feeCounters.length > 0) {
|
|
1219
2530
|
warnings.push({
|
|
1220
2531
|
type: "other",
|
|
1221
|
-
message: `xai: server
|
|
1222
|
-
", "
|
|
1223
|
-
)}]; the call is unpriced.`
|
|
2532
|
+
message: `xai: server tool counter(s) [${shown(feeCounters)}] are non-zero but have no rate in the pricing snapshot (xAI bills the tool per use); the call's cost is estimated and understates.`
|
|
1224
2533
|
});
|
|
1225
2534
|
}
|
|
1226
|
-
if (
|
|
1227
|
-
|
|
2535
|
+
if (unknown.length > 0) {
|
|
2536
|
+
const understanding = xaiProviderConfig.tools?.some(
|
|
2537
|
+
(tool) => tool["enable_image_understanding"] === true || tool["enable_video_understanding"] === true
|
|
2538
|
+
) === true;
|
|
1228
2539
|
warnings.push({
|
|
1229
2540
|
type: "other",
|
|
1230
|
-
message:
|
|
2541
|
+
message: understanding ? `xai: server tool counter(s) [${shown(unknown)}] are not in the pricing snapshot. The request enabled image or video understanding, which xAI prices by tokens with no invocation fee, so the counter is probably that tool's and the priced token cost may be complete; the counter's name was never captured, so the call stays estimated.` : `xai: server tool counter(s) [${shown(unknown)}] are not in the pricing snapshot; xAI may bill the tool per use, so the call's cost is estimated and may understate.`
|
|
1231
2542
|
});
|
|
1232
2543
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
2544
|
+
if ((expectedToolCounters.length > 0 || hasFileRef) && !noServerToolRan) {
|
|
2545
|
+
usage.details["server_tools_requested"] = 1;
|
|
2546
|
+
if (xaiProviderConfig.tools?.some((tool) => tool["type"] === "x_search") === true) {
|
|
2547
|
+
usage.details["x_search_requested"] = 1;
|
|
2548
|
+
}
|
|
2549
|
+
const missing = expectedToolCounters.filter((key) => !(key in usage.details));
|
|
2550
|
+
if (missing.length > 0) {
|
|
2551
|
+
usage.details["server_tools_missing"] = 1;
|
|
2552
|
+
warnings.push({
|
|
2553
|
+
type: "other",
|
|
2554
|
+
message: `xai: server tools were requested but usage is missing counters [${missing.join(
|
|
2555
|
+
", "
|
|
2556
|
+
)}]; the call is unpriced.`
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
if (hasFileRef) {
|
|
2560
|
+
usage.details["attachment_search_unpinned"] = 1;
|
|
2561
|
+
warnings.push({
|
|
2562
|
+
type: "other",
|
|
2563
|
+
message: "xai: file-ref enables attachment_search but that counter is not live-pinned; tool cost is estimated."
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
const citations = collectXaiCitations(
|
|
2568
|
+
response,
|
|
2569
|
+
messageItems,
|
|
2570
|
+
(message) => warnings.push({ type: "other", message })
|
|
2571
|
+
);
|
|
2572
|
+
const providerMeta = {};
|
|
2573
|
+
const contextDetails = response.usage["context_details"];
|
|
2574
|
+
if (isPlainRecord(contextDetails)) {
|
|
2575
|
+
providerMeta["context_details"] = contextDetails;
|
|
2576
|
+
}
|
|
2577
|
+
if (isPlainRecord(response.metadata)) {
|
|
2578
|
+
providerMeta["metadata"] = response.metadata;
|
|
2579
|
+
}
|
|
2580
|
+
if (responseMeta !== void 0) {
|
|
2581
|
+
const xaiMeta = {};
|
|
2582
|
+
if (responseMeta.requestId !== void 0)
|
|
2583
|
+
xaiMeta["requestId"] = responseMeta.requestId;
|
|
2584
|
+
if (responseMeta.rateLimitRemaining !== void 0) {
|
|
2585
|
+
xaiMeta["rateLimitRemaining"] = { ...responseMeta.rateLimitRemaining };
|
|
2586
|
+
}
|
|
2587
|
+
if (Object.keys(xaiMeta).length > 0) providerMeta["xai"] = xaiMeta;
|
|
2588
|
+
}
|
|
2589
|
+
let transientProviderState;
|
|
2590
|
+
if (replayRequired) {
|
|
2591
|
+
const state = {
|
|
2592
|
+
xai: {
|
|
2593
|
+
model,
|
|
2594
|
+
input: [
|
|
2595
|
+
...params.input,
|
|
2596
|
+
...response.output.filter((item) => !droppedItems.has(item))
|
|
2597
|
+
]
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
transientProviderState = state;
|
|
2601
|
+
}
|
|
2602
|
+
const servedServiceTier = typeof response.service_tier === "string" && response.service_tier.length > 0 ? response.service_tier : void 0;
|
|
2603
|
+
const result = {
|
|
2604
|
+
model: response.model,
|
|
2605
|
+
message: { role: "assistant", parts: messageParts },
|
|
2606
|
+
usage,
|
|
2607
|
+
warnings,
|
|
2608
|
+
finishReason,
|
|
2609
|
+
responseId: response.id,
|
|
2610
|
+
...text.length > 0 ? { text } : {},
|
|
2611
|
+
...reasoningText !== void 0 ? { reasoningText } : {},
|
|
2612
|
+
...rawStructured !== void 0 ? { rawStructured } : {},
|
|
2613
|
+
...servedServiceTier !== void 0 ? { servedServiceTier } : {},
|
|
2614
|
+
...Object.keys(providerMeta).length > 0 ? { providerMetadata: providerMeta } : {},
|
|
2615
|
+
...transientProviderState !== void 0 ? { transientProviderState } : {},
|
|
2616
|
+
...citations.length > 0 ? { citations } : {},
|
|
2617
|
+
...toolCalls.length > 0 ? { toolCalls, finishReason: "tool_calls" } : {}
|
|
1248
2618
|
};
|
|
1249
|
-
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
...citations.length > 0 ? { citations } : {},
|
|
1265
|
-
...toolCalls.length > 0 ? { toolCalls, finishReason: "tool_calls" } : {}
|
|
1266
|
-
};
|
|
1267
|
-
return result;
|
|
2619
|
+
return result;
|
|
2620
|
+
} catch (mapErr) {
|
|
2621
|
+
if (mapErr instanceof core.LlmError) throw mapErr;
|
|
2622
|
+
throw new core.LlmError(
|
|
2623
|
+
`xAI response ${String(response.id)} could not be mapped: ${mapErr instanceof Error ? mapErr.message : String(mapErr)}`,
|
|
2624
|
+
{
|
|
2625
|
+
kind: "server",
|
|
2626
|
+
retryable: false,
|
|
2627
|
+
provider: "xai",
|
|
2628
|
+
usage: mapUsage(response.usage),
|
|
2629
|
+
...typeof response.service_tier === "string" && response.service_tier.length > 0 ? { servedServiceTier: response.service_tier } : {},
|
|
2630
|
+
cause: mapErr
|
|
2631
|
+
}
|
|
2632
|
+
);
|
|
2633
|
+
}
|
|
1268
2634
|
},
|
|
1269
2635
|
async countTokens(req, ctx) {
|
|
1270
2636
|
if (req.provider !== "xai") {
|
|
@@ -1280,16 +2646,32 @@ function xaiAdapter(opts) {
|
|
|
1280
2646
|
}
|
|
1281
2647
|
const text = concatenateTokenizeText(req);
|
|
1282
2648
|
const apiKey = requireApiKey(ctx.auth);
|
|
1283
|
-
const fetchImpl =
|
|
2649
|
+
const fetchImpl = seams.fetch ?? transport?.fetch ?? fetch;
|
|
2650
|
+
const controller = new AbortController();
|
|
2651
|
+
const forwardAbort = () => {
|
|
2652
|
+
controller.abort(ctx.signal?.reason);
|
|
2653
|
+
};
|
|
2654
|
+
if (ctx.signal?.aborted === true) forwardAbort();
|
|
2655
|
+
else ctx.signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
2656
|
+
const timeout = new core.LlmError(
|
|
2657
|
+
`xAI tokenize-text timed out after ${countTokensTimeoutMs}ms`,
|
|
2658
|
+
{ kind: "timeout", retryable: true, provider: "xai" }
|
|
2659
|
+
);
|
|
2660
|
+
const timer = setTimeout(() => {
|
|
2661
|
+
controller.abort(timeout);
|
|
2662
|
+
}, countTokensTimeoutMs);
|
|
1284
2663
|
try {
|
|
1285
2664
|
const res = await fetchImpl("https://api.x.ai/v1/tokenize-text", {
|
|
2665
|
+
// The host transport's init (a dispatcher, say) first; the request
|
|
2666
|
+
// owns the keys below, which `snapshotXaiTransport` keeps out of it.
|
|
2667
|
+
...seams.fetch === void 0 ? transport?.fetchOptions : void 0,
|
|
1286
2668
|
method: "POST",
|
|
1287
2669
|
headers: {
|
|
1288
2670
|
Authorization: `Bearer ${apiKey}`,
|
|
1289
2671
|
"Content-Type": "application/json"
|
|
1290
2672
|
},
|
|
1291
2673
|
body: JSON.stringify({ model: req.model, text }),
|
|
1292
|
-
|
|
2674
|
+
signal: controller.signal
|
|
1293
2675
|
});
|
|
1294
2676
|
if (!res.ok) {
|
|
1295
2677
|
let parsed;
|
|
@@ -1298,12 +2680,26 @@ function xaiAdapter(opts) {
|
|
|
1298
2680
|
} catch {
|
|
1299
2681
|
parsed = await res.text().catch(() => "");
|
|
1300
2682
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
2683
|
+
const requestId = res.headers.get("x-request-id");
|
|
2684
|
+
throw Object.assign(
|
|
2685
|
+
new Error(
|
|
2686
|
+
`xAI tokenize-text HTTP ${res.status}${requestId !== null && requestId !== "" ? ` (request id ${requestId})` : ""}`
|
|
2687
|
+
),
|
|
2688
|
+
{ status: res.status, error: parsed, headers: res.headers }
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
let raw;
|
|
2692
|
+
try {
|
|
2693
|
+
raw = await res.json();
|
|
2694
|
+
} catch (cause) {
|
|
2695
|
+
if (controller.signal.aborted) throw cause;
|
|
2696
|
+
throw new core.LlmError("xAI tokenize-text response is not JSON", {
|
|
2697
|
+
kind: "server",
|
|
2698
|
+
retryable: true,
|
|
2699
|
+
provider: "xai",
|
|
2700
|
+
cause
|
|
1304
2701
|
});
|
|
1305
2702
|
}
|
|
1306
|
-
const raw = await res.json();
|
|
1307
2703
|
if (!isPlainRecord(raw) || !Array.isArray(raw["token_ids"])) {
|
|
1308
2704
|
throw new core.LlmError(
|
|
1309
2705
|
"xAI tokenize-text response is malformed: missing required field: token_ids",
|
|
@@ -1318,6 +2714,9 @@ function xaiAdapter(opts) {
|
|
|
1318
2714
|
raw
|
|
1319
2715
|
};
|
|
1320
2716
|
} catch (rawErr) {
|
|
2717
|
+
if (controller.signal.aborted && controller.signal.reason === timeout) {
|
|
2718
|
+
throw timeout;
|
|
2719
|
+
}
|
|
1321
2720
|
if (rawErr instanceof Error && rawErr.name === "AbortError") {
|
|
1322
2721
|
throw new core.LlmError("xAI tokenize-text aborted", {
|
|
1323
2722
|
kind: "aborted",
|
|
@@ -1327,12 +2726,15 @@ function xaiAdapter(opts) {
|
|
|
1327
2726
|
});
|
|
1328
2727
|
}
|
|
1329
2728
|
throw classifyXaiError(rawErr);
|
|
2729
|
+
} finally {
|
|
2730
|
+
clearTimeout(timer);
|
|
2731
|
+
ctx.signal?.removeEventListener("abort", forwardAbort);
|
|
1330
2732
|
}
|
|
1331
2733
|
}
|
|
1332
2734
|
};
|
|
1333
2735
|
}
|
|
1334
2736
|
var WEB_SEARCH_COUNTER = "web_search_calls";
|
|
1335
|
-
function expectedServerToolCounters(tools
|
|
2737
|
+
function expectedServerToolCounters(tools) {
|
|
1336
2738
|
const keys = [];
|
|
1337
2739
|
if (tools !== void 0) {
|
|
1338
2740
|
for (const tool of tools) {
|
|
@@ -1342,49 +2744,71 @@ function expectedServerToolCounters(tools, _hasFileRef) {
|
|
|
1342
2744
|
}
|
|
1343
2745
|
return keys;
|
|
1344
2746
|
}
|
|
1345
|
-
function
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
2747
|
+
function inlineMarkerLabel(marker, url) {
|
|
2748
|
+
const tail = `]](${url})`;
|
|
2749
|
+
return marker.startsWith("[[") && marker.endsWith(tail) && marker.length >= 2 + tail.length ? marker.slice(2, marker.length - tail.length) : void 0;
|
|
2750
|
+
}
|
|
2751
|
+
function collectXaiCitations(response, messageItems, onDropped) {
|
|
2752
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
2753
|
+
const upsert = (url, title, markerLabel) => {
|
|
2754
|
+
if (typeof url !== "string" || url.length === 0) return void 0;
|
|
2755
|
+
let citation = byUrl.get(url);
|
|
2756
|
+
if (citation === void 0) {
|
|
2757
|
+
citation = { url };
|
|
2758
|
+
try {
|
|
2759
|
+
const parsed = new URL(url);
|
|
2760
|
+
if (parsed.hostname.length > 0) {
|
|
2761
|
+
citation.sourceName = parsed.hostname.startsWith("www.") ? parsed.hostname.slice(4) : parsed.hostname;
|
|
2762
|
+
}
|
|
2763
|
+
} catch {
|
|
1360
2764
|
}
|
|
1361
|
-
|
|
2765
|
+
byUrl.set(url, citation);
|
|
1362
2766
|
}
|
|
1363
|
-
|
|
2767
|
+
if (citation.title === void 0 && typeof title === "string" && title.length > 0 && title !== url && title !== markerLabel) {
|
|
2768
|
+
citation.title = title;
|
|
2769
|
+
}
|
|
2770
|
+
return citation;
|
|
1364
2771
|
};
|
|
1365
2772
|
if (Array.isArray(response.citations)) {
|
|
1366
2773
|
for (const item of response.citations) {
|
|
1367
2774
|
if (typeof item === "string") {
|
|
1368
|
-
|
|
2775
|
+
upsert(item, void 0);
|
|
1369
2776
|
} else if (isPlainRecord(item)) {
|
|
1370
|
-
|
|
2777
|
+
upsert(item["url"] ?? item["uri"], item["title"]);
|
|
1371
2778
|
}
|
|
1372
2779
|
}
|
|
1373
2780
|
}
|
|
1374
2781
|
const lastMessage = messageItems.at(-1);
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
2782
|
+
if (lastMessage !== void 0) {
|
|
2783
|
+
const joined = lastMessage.content.map((part) => partText(part) ?? "").join("");
|
|
2784
|
+
let partOffset = 0;
|
|
2785
|
+
for (const part of lastMessage.content) {
|
|
2786
|
+
const annotations = isPlainRecord(part) ? part["annotations"] : void 0;
|
|
2787
|
+
if (Array.isArray(annotations)) {
|
|
2788
|
+
for (const ann of annotations) {
|
|
2789
|
+
if (!isPlainRecord(ann)) continue;
|
|
2790
|
+
if (ann["type"] !== void 0 && ann["type"] !== "url_citation") continue;
|
|
2791
|
+
const start = ann["start_index"];
|
|
2792
|
+
const end = ann["end_index"];
|
|
2793
|
+
const hasRange = typeof start === "number" && typeof end === "number" && Number.isInteger(start) && Number.isInteger(end) && start >= 0 && end > start;
|
|
2794
|
+
const url = ann["url"];
|
|
2795
|
+
const label = hasRange && typeof url === "string" ? inlineMarkerLabel(joined.slice(partOffset + start, partOffset + end), url) : void 0;
|
|
2796
|
+
const citation = upsert(url, ann["title"], label);
|
|
2797
|
+
if (citation === void 0 || !hasRange) continue;
|
|
2798
|
+
citation.cited = true;
|
|
2799
|
+
if (label === void 0) {
|
|
2800
|
+
onDropped(
|
|
2801
|
+
`xai: dropped a textRange for a citation of ${citation.url}: the answer at start_index ${start}, end_index ${end} is not the inline [[N]](url) marker. The source stays cited without a range.`
|
|
2802
|
+
);
|
|
2803
|
+
} else {
|
|
2804
|
+
citation.textRange ??= { start: partOffset + start, end: partOffset + end };
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
1384
2807
|
}
|
|
2808
|
+
partOffset += (partText(part) ?? "").length;
|
|
1385
2809
|
}
|
|
1386
2810
|
}
|
|
1387
|
-
return
|
|
2811
|
+
return [...byUrl.values()];
|
|
1388
2812
|
}
|
|
1389
2813
|
function concatenateTokenizeText(req) {
|
|
1390
2814
|
const chunks = [];
|
|
@@ -1416,6 +2840,7 @@ var XAI_FILE_TTL_MIN_SECONDS = 3600;
|
|
|
1416
2840
|
var XAI_FILE_TTL_MAX_SECONDS = 2592e3;
|
|
1417
2841
|
var XAI_FILE_MAX_BYTES = 48 * 1024 * 1024;
|
|
1418
2842
|
var XAI_FILES_DEFAULT_BASE_URL = "https://api.x.ai/v1";
|
|
2843
|
+
var XAI_FILES_DEFAULT_TIMEOUT_MS = 6e4;
|
|
1419
2844
|
function isPlainRecord2(value) {
|
|
1420
2845
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1421
2846
|
}
|
|
@@ -1484,19 +2909,23 @@ function toBlob(data, mimeType) {
|
|
|
1484
2909
|
var XaiFilesHttpError = class extends Error {
|
|
1485
2910
|
status;
|
|
1486
2911
|
error;
|
|
1487
|
-
|
|
2912
|
+
/** The response headers: `classifyError` reads `Retry-After` from them. */
|
|
2913
|
+
headers;
|
|
2914
|
+
constructor(status, message, errorBody, headers) {
|
|
1488
2915
|
super(message);
|
|
1489
2916
|
this.name = "XaiFilesHttpError";
|
|
1490
2917
|
this.status = status;
|
|
1491
2918
|
this.error = errorBody;
|
|
2919
|
+
this.headers = headers;
|
|
1492
2920
|
}
|
|
1493
2921
|
};
|
|
1494
|
-
async function throwHttpFailure(res) {
|
|
2922
|
+
async function throwHttpFailure(res, signal, operation) {
|
|
1495
2923
|
const status = res.status;
|
|
1496
2924
|
let bodyText = "";
|
|
1497
2925
|
try {
|
|
1498
2926
|
bodyText = await res.text();
|
|
1499
|
-
} catch {
|
|
2927
|
+
} catch (e) {
|
|
2928
|
+
if (signal.aborted) throw abortedError(signal, `xAI file ${operation} aborted`, e);
|
|
1500
2929
|
bodyText = "";
|
|
1501
2930
|
}
|
|
1502
2931
|
let parsed = bodyText;
|
|
@@ -1513,7 +2942,9 @@ async function throwHttpFailure(res) {
|
|
|
1513
2942
|
} else if (isPlainRecord2(parsed) && typeof parsed["error"] === "string") {
|
|
1514
2943
|
message = parsed["error"];
|
|
1515
2944
|
}
|
|
1516
|
-
|
|
2945
|
+
const requestId = res.headers.get("x-request-id");
|
|
2946
|
+
if (requestId !== null && requestId !== "") message += ` (request id ${requestId})`;
|
|
2947
|
+
throw new XaiFilesHttpError(status, message, parsed, res.headers);
|
|
1517
2948
|
}
|
|
1518
2949
|
function isNotFoundError(err) {
|
|
1519
2950
|
if (err instanceof core.LlmError && err.httpStatus === 404) {
|
|
@@ -1582,16 +3013,41 @@ function classifyStoreError(raw) {
|
|
|
1582
3013
|
cause: classified.cause ?? raw
|
|
1583
3014
|
});
|
|
1584
3015
|
}
|
|
3016
|
+
var DEADLINE_ERRORS = /* @__PURE__ */ new WeakSet();
|
|
3017
|
+
function abortedError(signal, message, cause) {
|
|
3018
|
+
const reason = signal.reason;
|
|
3019
|
+
if (reason instanceof core.LlmError && DEADLINE_ERRORS.has(reason)) return reason;
|
|
3020
|
+
return new core.LlmError(message, {
|
|
3021
|
+
kind: "aborted",
|
|
3022
|
+
retryable: false,
|
|
3023
|
+
provider: "xai",
|
|
3024
|
+
...cause !== void 0 ? { cause } : {}
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
function pathSegment(id) {
|
|
3028
|
+
if (id === "." || id === "..") {
|
|
3029
|
+
throw badRequest(`fileId "${id}" is not a valid file id.`);
|
|
3030
|
+
}
|
|
3031
|
+
return encodeURIComponent(id);
|
|
3032
|
+
}
|
|
1585
3033
|
var XaiFileStore = class {
|
|
1586
3034
|
apiKey;
|
|
1587
3035
|
baseUrl;
|
|
1588
3036
|
fetchImpl;
|
|
3037
|
+
timeoutMs;
|
|
1589
3038
|
onDeleteError;
|
|
1590
3039
|
logger;
|
|
1591
3040
|
constructor(opts) {
|
|
1592
3041
|
this.apiKey = requireApiKey(opts.auth);
|
|
1593
3042
|
this.baseUrl = (opts.baseUrl ?? XAI_FILES_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1594
3043
|
this.fetchImpl = opts.fetch ?? fetch;
|
|
3044
|
+
const timeoutMs = opts.timeoutMs ?? XAI_FILES_DEFAULT_TIMEOUT_MS;
|
|
3045
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > XAI_MAX_TIMEOUT_MS) {
|
|
3046
|
+
throw badRequest(
|
|
3047
|
+
`XaiFileStoreOptions.timeoutMs must be an integer from 1 to ${XAI_MAX_TIMEOUT_MS}.`
|
|
3048
|
+
);
|
|
3049
|
+
}
|
|
3050
|
+
this.timeoutMs = timeoutMs;
|
|
1595
3051
|
this.logger = opts.logger;
|
|
1596
3052
|
this.onDeleteError = opts.onDeleteError ?? ((fileId, err) => {
|
|
1597
3053
|
const message = core.redactSecrets(core.classifyError(err).message);
|
|
@@ -1625,6 +3081,37 @@ var XaiFileStore = class {
|
|
|
1625
3081
|
}
|
|
1626
3082
|
return init;
|
|
1627
3083
|
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Runs one call under the store's deadline: `run` gets a signal that aborts
|
|
3086
|
+
* when the caller's does or when `timeoutMs` passes (headers and body read
|
|
3087
|
+
* both count), and the timer is cleared however the call ends.
|
|
3088
|
+
*/
|
|
3089
|
+
async bounded(caller, run) {
|
|
3090
|
+
const controller = new AbortController();
|
|
3091
|
+
const forward = () => {
|
|
3092
|
+
controller.abort(caller?.reason);
|
|
3093
|
+
};
|
|
3094
|
+
if (caller?.aborted === true) forward();
|
|
3095
|
+
else caller?.addEventListener("abort", forward, { once: true });
|
|
3096
|
+
const timer = setTimeout(() => {
|
|
3097
|
+
const error = new core.LlmError(
|
|
3098
|
+
`xAI Files API call timed out after ${this.timeoutMs}ms`,
|
|
3099
|
+
{
|
|
3100
|
+
kind: "timeout",
|
|
3101
|
+
retryable: true,
|
|
3102
|
+
provider: "xai"
|
|
3103
|
+
}
|
|
3104
|
+
);
|
|
3105
|
+
DEADLINE_ERRORS.add(error);
|
|
3106
|
+
controller.abort(error);
|
|
3107
|
+
}, this.timeoutMs);
|
|
3108
|
+
try {
|
|
3109
|
+
return await run(controller.signal);
|
|
3110
|
+
} finally {
|
|
3111
|
+
clearTimeout(timer);
|
|
3112
|
+
caller?.removeEventListener("abort", forward);
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
1628
3115
|
/**
|
|
1629
3116
|
* Upload bytes to xAI Files. Returns immediately with metadata (no poll).
|
|
1630
3117
|
*
|
|
@@ -1656,77 +3143,64 @@ var XaiFileStore = class {
|
|
|
1656
3143
|
}
|
|
1657
3144
|
form.append("purpose", purpose);
|
|
1658
3145
|
form.append("file", toBlob(input.data, input.mimeType), input.filename);
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
res = await this.fetchImpl(
|
|
1662
|
-
this.filesUrl(),
|
|
1663
|
-
this.requestInit("POST", {
|
|
1664
|
-
body: form,
|
|
1665
|
-
...signal !== void 0 ? { signal } : {}
|
|
1666
|
-
})
|
|
1667
|
-
);
|
|
1668
|
-
} catch (e) {
|
|
1669
|
-
if (signal?.aborted === true) {
|
|
1670
|
-
throw new core.LlmError("xAI file upload aborted", {
|
|
1671
|
-
kind: "aborted",
|
|
1672
|
-
retryable: false,
|
|
1673
|
-
provider: "xai"
|
|
1674
|
-
});
|
|
1675
|
-
}
|
|
1676
|
-
throw classifyStoreError(e);
|
|
1677
|
-
}
|
|
1678
|
-
if (!res.ok) {
|
|
3146
|
+
return this.bounded(signal, async (bound) => {
|
|
3147
|
+
let res;
|
|
1679
3148
|
try {
|
|
1680
|
-
await
|
|
3149
|
+
res = await this.fetchImpl(
|
|
3150
|
+
this.filesUrl(),
|
|
3151
|
+
this.requestInit("POST", { body: form, signal: bound })
|
|
3152
|
+
);
|
|
1681
3153
|
} catch (e) {
|
|
3154
|
+
if (bound.aborted) throw abortedError(bound, "xAI file upload aborted");
|
|
1682
3155
|
throw classifyStoreError(e);
|
|
1683
3156
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
})
|
|
1695
|
-
|
|
1696
|
-
|
|
3157
|
+
if (!res.ok) {
|
|
3158
|
+
try {
|
|
3159
|
+
await throwHttpFailure(res, bound, "upload");
|
|
3160
|
+
} catch (e) {
|
|
3161
|
+
throw classifyStoreError(e);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
let json;
|
|
3165
|
+
try {
|
|
3166
|
+
json = await res.json();
|
|
3167
|
+
} catch (e) {
|
|
3168
|
+
if (bound.aborted) throw abortedError(bound, "xAI file upload aborted", e);
|
|
3169
|
+
throw new core.LlmError("xAI file upload returned non-JSON body", {
|
|
3170
|
+
kind: "server",
|
|
3171
|
+
retryable: false,
|
|
3172
|
+
provider: "xai",
|
|
3173
|
+
cause: e
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
return makeHandle(json);
|
|
3177
|
+
});
|
|
1697
3178
|
}
|
|
1698
3179
|
async get(fileId, signal) {
|
|
1699
3180
|
if (typeof fileId !== "string" || fileId.trim() === "") {
|
|
1700
3181
|
throw badRequest("fileId must be a non-empty string.");
|
|
1701
3182
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
res
|
|
1705
|
-
this.filesUrl(fileId),
|
|
1706
|
-
this.requestInit("GET", signal !== void 0 ? { signal } : {})
|
|
1707
|
-
);
|
|
1708
|
-
} catch (e) {
|
|
1709
|
-
if (signal?.aborted === true) {
|
|
1710
|
-
throw new core.LlmError("xAI file get aborted", {
|
|
1711
|
-
kind: "aborted",
|
|
1712
|
-
retryable: false,
|
|
1713
|
-
provider: "xai"
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
|
-
throw classifyStoreError(e);
|
|
1717
|
-
}
|
|
1718
|
-
if (res.status === 404) {
|
|
1719
|
-
throw notFoundError(fileId, "get");
|
|
1720
|
-
}
|
|
1721
|
-
if (!res.ok) {
|
|
3183
|
+
const url = this.filesUrl(pathSegment(fileId));
|
|
3184
|
+
return this.bounded(signal, async (bound) => {
|
|
3185
|
+
let res;
|
|
1722
3186
|
try {
|
|
1723
|
-
await
|
|
3187
|
+
res = await this.fetchImpl(url, this.requestInit("GET", { signal: bound }));
|
|
1724
3188
|
} catch (e) {
|
|
3189
|
+
if (bound.aborted) throw abortedError(bound, "xAI file get aborted");
|
|
1725
3190
|
throw classifyStoreError(e);
|
|
1726
3191
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
3192
|
+
if (res.status === 404) {
|
|
3193
|
+
throw notFoundError(fileId, "get");
|
|
3194
|
+
}
|
|
3195
|
+
if (!res.ok) {
|
|
3196
|
+
try {
|
|
3197
|
+
await throwHttpFailure(res, bound, "get");
|
|
3198
|
+
} catch (e) {
|
|
3199
|
+
throw classifyStoreError(e);
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
return makeHandle(await readJsonBody(res, bound, "get"));
|
|
3203
|
+
});
|
|
1730
3204
|
}
|
|
1731
3205
|
async list(opts = {}, signal) {
|
|
1732
3206
|
if (opts.limit !== void 0) {
|
|
@@ -1743,36 +3217,29 @@ var XaiFileStore = class {
|
|
|
1743
3217
|
}
|
|
1744
3218
|
const qs = params.toString();
|
|
1745
3219
|
const url = qs.length > 0 ? `${this.filesUrl()}?${qs}` : this.filesUrl();
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
res = await this.fetchImpl(
|
|
1749
|
-
url,
|
|
1750
|
-
this.requestInit("GET", signal !== void 0 ? { signal } : {})
|
|
1751
|
-
);
|
|
1752
|
-
} catch (e) {
|
|
1753
|
-
if (signal?.aborted === true) {
|
|
1754
|
-
throw new core.LlmError("xAI file list aborted", {
|
|
1755
|
-
kind: "aborted",
|
|
1756
|
-
retryable: false,
|
|
1757
|
-
provider: "xai"
|
|
1758
|
-
});
|
|
1759
|
-
}
|
|
1760
|
-
throw classifyStoreError(e);
|
|
1761
|
-
}
|
|
1762
|
-
if (!res.ok) {
|
|
3220
|
+
return this.bounded(signal, async (bound) => {
|
|
3221
|
+
let res;
|
|
1763
3222
|
try {
|
|
1764
|
-
await
|
|
3223
|
+
res = await this.fetchImpl(url, this.requestInit("GET", { signal: bound }));
|
|
1765
3224
|
} catch (e) {
|
|
3225
|
+
if (bound.aborted) throw abortedError(bound, "xAI file list aborted");
|
|
1766
3226
|
throw classifyStoreError(e);
|
|
1767
3227
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
3228
|
+
if (!res.ok) {
|
|
3229
|
+
try {
|
|
3230
|
+
await throwHttpFailure(res, bound, "list");
|
|
3231
|
+
} catch (e) {
|
|
3232
|
+
throw classifyStoreError(e);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
const json = await readJsonBody(res, bound, "list");
|
|
3236
|
+
const files = Array.isArray(json.data) ? json.data.map((f) => makeHandle(f)) : [];
|
|
3237
|
+
const result = { files };
|
|
3238
|
+
if (typeof json.pagination_token === "string" && json.pagination_token.length > 0) {
|
|
3239
|
+
result.paginationToken = json.pagination_token;
|
|
3240
|
+
}
|
|
3241
|
+
return result;
|
|
3242
|
+
});
|
|
1776
3243
|
}
|
|
1777
3244
|
/**
|
|
1778
3245
|
* Delete a file. Idempotent: HTTP 404 → success.
|
|
@@ -1788,34 +3255,31 @@ var XaiFileStore = class {
|
|
|
1788
3255
|
if (typeof fileId !== "string" || fileId.trim() === "") {
|
|
1789
3256
|
throw badRequest("fileId must be a non-empty string.");
|
|
1790
3257
|
}
|
|
3258
|
+
const url = this.filesUrl(pathSegment(fileId));
|
|
1791
3259
|
const failClosed = opts?.failClosed === true;
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
}) : classifyStoreError(err);
|
|
1814
|
-
if (failClosed) {
|
|
1815
|
-
throw classified;
|
|
3260
|
+
await this.bounded(opts?.signal, async (bound) => {
|
|
3261
|
+
try {
|
|
3262
|
+
const res = await this.fetchImpl(
|
|
3263
|
+
url,
|
|
3264
|
+
this.requestInit("DELETE", { signal: bound })
|
|
3265
|
+
);
|
|
3266
|
+
if (res.status === 404) {
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
if (!res.ok) {
|
|
3270
|
+
await throwHttpFailure(res, bound, "delete");
|
|
3271
|
+
}
|
|
3272
|
+
} catch (err) {
|
|
3273
|
+
if (isNotFoundError(err)) {
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
const classified = bound.aborted && !(err instanceof core.LlmError) ? abortedError(bound, "xAI file delete aborted", err) : classifyStoreError(err);
|
|
3277
|
+
if (failClosed) {
|
|
3278
|
+
throw classified;
|
|
3279
|
+
}
|
|
3280
|
+
this.onDeleteError(fileId, classified);
|
|
1816
3281
|
}
|
|
1817
|
-
|
|
1818
|
-
}
|
|
3282
|
+
});
|
|
1819
3283
|
}
|
|
1820
3284
|
/**
|
|
1821
3285
|
* Delete many files.
|
|
@@ -1837,36 +3301,48 @@ var XaiFileStore = class {
|
|
|
1837
3301
|
if (typeof fileId !== "string" || fileId.trim() === "") {
|
|
1838
3302
|
throw badRequest("fileId must be a non-empty string.");
|
|
1839
3303
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
res
|
|
1843
|
-
|
|
1844
|
-
this.requestInit("GET",
|
|
1845
|
-
)
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
3304
|
+
const url = this.filesUrl(`${pathSegment(fileId)}/content`);
|
|
3305
|
+
return this.bounded(signal, async (bound) => {
|
|
3306
|
+
let res;
|
|
3307
|
+
try {
|
|
3308
|
+
res = await this.fetchImpl(url, this.requestInit("GET", { signal: bound }));
|
|
3309
|
+
} catch (e) {
|
|
3310
|
+
if (bound.aborted) throw abortedError(bound, "xAI file content download aborted");
|
|
3311
|
+
throw classifyStoreError(e);
|
|
3312
|
+
}
|
|
3313
|
+
if (res.status === 404) {
|
|
3314
|
+
throw notFoundError(fileId, "getContent");
|
|
3315
|
+
}
|
|
3316
|
+
if (!res.ok) {
|
|
3317
|
+
try {
|
|
3318
|
+
await throwHttpFailure(res, bound, "getContent");
|
|
3319
|
+
} catch (e) {
|
|
3320
|
+
throw classifyStoreError(e);
|
|
3321
|
+
}
|
|
1853
3322
|
}
|
|
1854
|
-
throw classifyStoreError(e);
|
|
1855
|
-
}
|
|
1856
|
-
if (res.status === 404) {
|
|
1857
|
-
throw notFoundError(fileId, "getContent");
|
|
1858
|
-
}
|
|
1859
|
-
if (!res.ok) {
|
|
1860
3323
|
try {
|
|
1861
|
-
await
|
|
3324
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1862
3325
|
} catch (e) {
|
|
3326
|
+
if (bound.aborted)
|
|
3327
|
+
throw abortedError(bound, "xAI file content download aborted", e);
|
|
1863
3328
|
throw classifyStoreError(e);
|
|
1864
3329
|
}
|
|
1865
|
-
}
|
|
1866
|
-
const buf = await res.arrayBuffer();
|
|
1867
|
-
return new Uint8Array(buf);
|
|
3330
|
+
});
|
|
1868
3331
|
}
|
|
1869
3332
|
};
|
|
3333
|
+
async function readJsonBody(res, signal, operation) {
|
|
3334
|
+
try {
|
|
3335
|
+
return await res.json();
|
|
3336
|
+
} catch (e) {
|
|
3337
|
+
if (signal.aborted) throw abortedError(signal, `xAI file ${operation} aborted`, e);
|
|
3338
|
+
throw new core.LlmError(`xAI file ${operation} returned a non-JSON body`, {
|
|
3339
|
+
kind: "server",
|
|
3340
|
+
retryable: false,
|
|
3341
|
+
provider: "xai",
|
|
3342
|
+
cause: e
|
|
3343
|
+
});
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
1870
3346
|
|
|
1871
3347
|
// src/provider.ts
|
|
1872
3348
|
function xaiProvider(opts) {
|
|
@@ -1880,11 +3356,15 @@ function xaiProvider(opts) {
|
|
|
1880
3356
|
exports.Grok45ConfigSchema = Grok45ConfigSchema;
|
|
1881
3357
|
exports.Grok46ConfigSchema = Grok46ConfigSchema;
|
|
1882
3358
|
exports.Grok47ConfigSchema = Grok47ConfigSchema;
|
|
3359
|
+
exports.XAI_COUNT_TOKENS_TIMEOUT_MS = XAI_COUNT_TOKENS_TIMEOUT_MS;
|
|
3360
|
+
exports.XAI_DEFAULT_TIMEOUT_MS = XAI_DEFAULT_TIMEOUT_MS;
|
|
1883
3361
|
exports.XAI_FILES_DEFAULT_BASE_URL = XAI_FILES_DEFAULT_BASE_URL;
|
|
3362
|
+
exports.XAI_FILES_DEFAULT_TIMEOUT_MS = XAI_FILES_DEFAULT_TIMEOUT_MS;
|
|
1884
3363
|
exports.XAI_FILE_MAX_BYTES = XAI_FILE_MAX_BYTES;
|
|
1885
3364
|
exports.XAI_FILE_TTL_MAX_SECONDS = XAI_FILE_TTL_MAX_SECONDS;
|
|
1886
3365
|
exports.XAI_FILE_TTL_MIN_SECONDS = XAI_FILE_TTL_MIN_SECONDS;
|
|
1887
3366
|
exports.XAI_PRICING = XAI_PRICING;
|
|
3367
|
+
exports.XAI_TIMEOUT_BUFFER_MS = XAI_TIMEOUT_BUFFER_MS;
|
|
1888
3368
|
exports.XAI_TOOL_RATE_MICRO_USD = XAI_TOOL_RATE_MICRO_USD;
|
|
1889
3369
|
exports.XaiFileStore = XaiFileStore;
|
|
1890
3370
|
exports.buildXaiClient = buildXaiClient;
|