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