@gullabs/xai 0.9.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +6 -0
- package/README.md +294 -60
- package/dist/index.cjs +1927 -447
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +250 -51
- package/dist/index.d.ts +250 -51
- package/dist/index.js +1925 -449
- package/dist/index.js.map +1 -1
- package/package.json +16 -8
package/dist/index.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",
|
|
@@ -123,7 +919,23 @@ var XaiProviderOptionsSchema = z.strictObject({
|
|
|
123
919
|
maxTurns: z.number().int().min(1).optional().meta({
|
|
124
920
|
title: "Max Turns",
|
|
125
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."
|
|
126
|
-
})
|
|
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");
|
|
127
939
|
}).meta({
|
|
128
940
|
title: "xAI Provider Options",
|
|
129
941
|
description: "Allowlisted xAI provider options."
|
|
@@ -131,17 +943,17 @@ var XaiProviderOptionsSchema = z.strictObject({
|
|
|
131
943
|
|
|
132
944
|
// src/model-config/grok-4-5.ts
|
|
133
945
|
var Grok45ConfigSchema = z.strictObject({
|
|
134
|
-
temperature: z.number().optional().meta({
|
|
946
|
+
temperature: z.number().min(0).max(2).optional().meta({
|
|
135
947
|
title: "Temperature",
|
|
136
|
-
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.'
|
|
137
949
|
}),
|
|
138
|
-
topP: z.number().optional().meta({
|
|
950
|
+
topP: z.number().min(0).max(1).optional().meta({
|
|
139
951
|
title: "Top P",
|
|
140
|
-
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."
|
|
141
953
|
}),
|
|
142
|
-
maxOutputTokens:
|
|
954
|
+
maxOutputTokens: maxOutputTokensSchema(XAI_MODEL_LIMITS["grok-4.5"]).optional().meta({
|
|
143
955
|
title: "Max Output Tokens",
|
|
144
|
-
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."
|
|
145
957
|
}),
|
|
146
958
|
reasoning: z.strictObject({
|
|
147
959
|
effort: z.enum(["low", "medium", "high"]).meta({
|
|
@@ -156,9 +968,9 @@ var Grok45ConfigSchema = z.strictObject({
|
|
|
156
968
|
title: "Service Tier",
|
|
157
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."
|
|
158
970
|
}),
|
|
159
|
-
timeoutMs: z.number().int().positive().optional().meta({
|
|
971
|
+
timeoutMs: z.number().int().positive().max(XAI_MAX_TIMEOUT_MS).optional().meta({
|
|
160
972
|
title: "Timeout",
|
|
161
|
-
description: "Logical request timeout in milliseconds."
|
|
973
|
+
description: "Logical request timeout in milliseconds (at most 2147478647)."
|
|
162
974
|
}),
|
|
163
975
|
providerOptions: z.strictObject({
|
|
164
976
|
xai: XaiProviderOptionsSchema.optional()
|
|
@@ -172,17 +984,17 @@ var Grok45ConfigSchema = z.strictObject({
|
|
|
172
984
|
examples: [{ reasoning: { effort: "high" } }]
|
|
173
985
|
});
|
|
174
986
|
var Grok46ConfigSchema = z.strictObject({
|
|
175
|
-
temperature: z.number().optional().meta({
|
|
987
|
+
temperature: z.number().min(0).max(2).optional().meta({
|
|
176
988
|
title: "Temperature",
|
|
177
|
-
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.'
|
|
178
990
|
}),
|
|
179
|
-
topP: z.number().optional().meta({
|
|
991
|
+
topP: z.number().min(0).max(1).optional().meta({
|
|
180
992
|
title: "Top P",
|
|
181
|
-
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."
|
|
182
994
|
}),
|
|
183
|
-
maxOutputTokens:
|
|
995
|
+
maxOutputTokens: maxOutputTokensSchema(XAI_MODEL_LIMITS["grok-4.6"]).optional().meta({
|
|
184
996
|
title: "Max Output Tokens",
|
|
185
|
-
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."
|
|
186
998
|
}),
|
|
187
999
|
reasoning: z.strictObject({
|
|
188
1000
|
effort: z.enum(["low", "medium", "high", "xhigh"]).meta({
|
|
@@ -197,9 +1009,9 @@ var Grok46ConfigSchema = z.strictObject({
|
|
|
197
1009
|
title: "Service Tier",
|
|
198
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.'
|
|
199
1011
|
}),
|
|
200
|
-
timeoutMs: z.number().int().positive().optional().meta({
|
|
1012
|
+
timeoutMs: z.number().int().positive().max(XAI_MAX_TIMEOUT_MS).optional().meta({
|
|
201
1013
|
title: "Timeout",
|
|
202
|
-
description: "Logical request timeout in milliseconds."
|
|
1014
|
+
description: "Logical request timeout in milliseconds (at most 2147478647)."
|
|
203
1015
|
}),
|
|
204
1016
|
providerOptions: z.strictObject({
|
|
205
1017
|
xai: XaiProviderOptionsSchema.optional()
|
|
@@ -213,17 +1025,17 @@ var Grok46ConfigSchema = z.strictObject({
|
|
|
213
1025
|
examples: [{ reasoning: { effort: "high" } }]
|
|
214
1026
|
});
|
|
215
1027
|
var Grok47ConfigSchema = z.strictObject({
|
|
216
|
-
temperature: z.number().optional().meta({
|
|
1028
|
+
temperature: z.number().min(0).max(2).optional().meta({
|
|
217
1029
|
title: "Temperature",
|
|
218
|
-
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.'
|
|
219
1031
|
}),
|
|
220
|
-
topP: z.number().optional().meta({
|
|
1032
|
+
topP: z.number().min(0).max(1).optional().meta({
|
|
221
1033
|
title: "Top P",
|
|
222
|
-
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."
|
|
223
1035
|
}),
|
|
224
|
-
maxOutputTokens:
|
|
1036
|
+
maxOutputTokens: maxOutputTokensSchema(XAI_MODEL_LIMITS["grok-4.7"]).optional().meta({
|
|
225
1037
|
title: "Max Output Tokens",
|
|
226
|
-
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."
|
|
227
1039
|
}),
|
|
228
1040
|
reasoning: z.strictObject({
|
|
229
1041
|
effort: z.enum(["low", "medium", "high", "xhigh"]).meta({
|
|
@@ -238,9 +1050,9 @@ var Grok47ConfigSchema = z.strictObject({
|
|
|
238
1050
|
title: "Service Tier",
|
|
239
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.'
|
|
240
1052
|
}),
|
|
241
|
-
timeoutMs: z.number().int().positive().optional().meta({
|
|
1053
|
+
timeoutMs: z.number().int().positive().max(XAI_MAX_TIMEOUT_MS).optional().meta({
|
|
242
1054
|
title: "Timeout",
|
|
243
|
-
description: "Logical request timeout in milliseconds."
|
|
1055
|
+
description: "Logical request timeout in milliseconds (at most 2147478647)."
|
|
244
1056
|
}),
|
|
245
1057
|
providerOptions: z.strictObject({
|
|
246
1058
|
xai: XaiProviderOptionsSchema.optional()
|
|
@@ -258,15 +1070,15 @@ var Grok47ConfigSchema = z.strictObject({
|
|
|
258
1070
|
var grok45ModelDescriptor = {
|
|
259
1071
|
model: "grok-4.5",
|
|
260
1072
|
provider: "xai",
|
|
1073
|
+
limits: XAI_MODEL_LIMITS["grok-4.5"],
|
|
261
1074
|
pricingFamily: "grok-4.5",
|
|
262
1075
|
capabilities: {
|
|
1076
|
+
inputMimeTypes: XAI_INPUT_MIME_TYPES,
|
|
263
1077
|
reasoning: true,
|
|
264
1078
|
reasoningApi: "level",
|
|
265
1079
|
admittedReasoningEfforts: ["low", "medium", "high"],
|
|
266
1080
|
structuredOutput: true,
|
|
267
1081
|
nativeStructuredOutput: true,
|
|
268
|
-
vision: true,
|
|
269
|
-
audioInput: false,
|
|
270
1082
|
sampling: "tunable",
|
|
271
1083
|
caching: { explicit: false, minTokens: 0 },
|
|
272
1084
|
grounding: true,
|
|
@@ -275,21 +1087,22 @@ var grok45ModelDescriptor = {
|
|
|
275
1087
|
serviceTiers: ["priority"]
|
|
276
1088
|
},
|
|
277
1089
|
configSchema: Grok45ConfigSchema,
|
|
1090
|
+
configKeys: toConfigKeys(Grok45ConfigSchema),
|
|
278
1091
|
configJsonSchema: toConfigJsonSchema(Grok45ConfigSchema),
|
|
279
1092
|
validateConfig: zodToStandardSchema(Grok45ConfigSchema)
|
|
280
1093
|
};
|
|
281
1094
|
var grok46ModelDescriptor = {
|
|
282
1095
|
model: "grok-4.6",
|
|
283
1096
|
provider: "xai",
|
|
1097
|
+
limits: XAI_MODEL_LIMITS["grok-4.6"],
|
|
284
1098
|
pricingFamily: "grok-4.6",
|
|
285
1099
|
capabilities: {
|
|
1100
|
+
inputMimeTypes: XAI_INPUT_MIME_TYPES,
|
|
286
1101
|
reasoning: true,
|
|
287
1102
|
reasoningApi: "level",
|
|
288
1103
|
admittedReasoningEfforts: ["low", "medium", "high", "xhigh"],
|
|
289
1104
|
structuredOutput: true,
|
|
290
1105
|
nativeStructuredOutput: true,
|
|
291
|
-
vision: true,
|
|
292
|
-
audioInput: false,
|
|
293
1106
|
sampling: "tunable",
|
|
294
1107
|
caching: { explicit: false, minTokens: 0 },
|
|
295
1108
|
grounding: true,
|
|
@@ -298,30 +1111,33 @@ var grok46ModelDescriptor = {
|
|
|
298
1111
|
serviceTiers: ["priority"]
|
|
299
1112
|
},
|
|
300
1113
|
configSchema: Grok46ConfigSchema,
|
|
1114
|
+
configKeys: toConfigKeys(Grok46ConfigSchema),
|
|
301
1115
|
configJsonSchema: toConfigJsonSchema(Grok46ConfigSchema),
|
|
302
1116
|
validateConfig: zodToStandardSchema(Grok46ConfigSchema)
|
|
303
1117
|
};
|
|
304
1118
|
var grok47ModelDescriptor = {
|
|
305
1119
|
model: "grok-4.7",
|
|
306
1120
|
provider: "xai",
|
|
1121
|
+
limits: XAI_MODEL_LIMITS["grok-4.7"],
|
|
307
1122
|
pricingFamily: "grok-4.7",
|
|
308
1123
|
capabilities: {
|
|
1124
|
+
inputMimeTypes: XAI_INPUT_MIME_TYPES,
|
|
309
1125
|
reasoning: true,
|
|
310
1126
|
reasoningApi: "level",
|
|
311
1127
|
admittedReasoningEfforts: ["low", "medium", "high", "xhigh"],
|
|
312
1128
|
structuredOutput: true,
|
|
313
1129
|
nativeStructuredOutput: true,
|
|
314
|
-
vision: true,
|
|
315
|
-
audioInput: false,
|
|
316
1130
|
sampling: "tunable",
|
|
317
1131
|
caching: { explicit: false, minTokens: 0 },
|
|
318
1132
|
grounding: true,
|
|
319
1133
|
structuredOutputWithTools: true,
|
|
320
1134
|
functionCalling: true,
|
|
321
|
-
|
|
1135
|
+
continuation: "state",
|
|
1136
|
+
providerState: true,
|
|
322
1137
|
serviceTiers: ["priority"]
|
|
323
1138
|
},
|
|
324
1139
|
configSchema: Grok47ConfigSchema,
|
|
1140
|
+
configKeys: toConfigKeys(Grok47ConfigSchema),
|
|
325
1141
|
configJsonSchema: toConfigJsonSchema(Grok47ConfigSchema),
|
|
326
1142
|
validateConfig: zodToStandardSchema(Grok47ConfigSchema)
|
|
327
1143
|
};
|
|
@@ -331,89 +1147,48 @@ var xaiModelDescriptors = [
|
|
|
331
1147
|
grok47ModelDescriptor
|
|
332
1148
|
];
|
|
333
1149
|
var xaiRegistry = createModelRegistry(xaiModelDescriptors);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
"
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if ("nullable" in node) {
|
|
377
|
-
throw badSchema(
|
|
378
|
-
`xAI structured output takes standard JSON Schema, which has no \`nullable\` keyword (found at \`${at}\`). xAI ignores it, so the field would be required and non-null. List 'null' in \`type\` instead, e.g. type: ['string', 'null'].`
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
const type = node["type"];
|
|
382
|
-
if (type !== void 0) {
|
|
383
|
-
const names = Array.isArray(type) ? type : [type];
|
|
384
|
-
for (const name of names) {
|
|
385
|
-
if (typeof name !== "string" || !JSON_SCHEMA_TYPES.has(name)) {
|
|
386
|
-
throw badSchema(
|
|
387
|
-
`xAI structured output takes standard JSON Schema; \`type\` ${JSON.stringify(
|
|
388
|
-
name
|
|
389
|
-
)} at \`${at}\` is not a JSON Schema type. Use lowercase string, number, integer, boolean, object, array or null.`
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
const child = (segment) => path.length > 0 ? `${path}.${segment}` : segment;
|
|
395
|
-
for (const keyword of SCHEMA_MAP_KEYWORDS) {
|
|
396
|
-
const map = node[keyword];
|
|
397
|
-
if (!isPlainObject(map)) continue;
|
|
398
|
-
for (const [key, member] of Object.entries(map)) {
|
|
399
|
-
if (isPlainObject(member)) assertNode(member, child(`${keyword}.${key}`));
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
for (const keyword of SCHEMA_ARRAY_KEYWORDS) {
|
|
403
|
-
const members = node[keyword];
|
|
404
|
-
if (!Array.isArray(members)) continue;
|
|
405
|
-
members.forEach((member, index) => {
|
|
406
|
-
if (isPlainObject(member)) assertNode(member, child(`${keyword}[${index}]`));
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
for (const keyword of SINGLE_SCHEMA_KEYWORDS) {
|
|
410
|
-
const member = node[keyword];
|
|
411
|
-
if (isPlainObject(member)) assertNode(member, child(keyword));
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
function assertXaiOutputJsonSchema(schema) {
|
|
415
|
-
if (isPlainObject(schema)) assertNode(schema, "");
|
|
416
|
-
}
|
|
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
|
+
};
|
|
417
1192
|
var xaiPricingVersion = "xai-2026-09-25";
|
|
418
1193
|
var XAI_TOOL_RATE_MICRO_USD = {
|
|
419
1194
|
web_search_calls: 5e3,
|
|
@@ -427,6 +1202,51 @@ var XAI_TOOL_COUNTER_KEYS = [
|
|
|
427
1202
|
];
|
|
428
1203
|
var X_SEARCH_ITEM_COUNTERS = ["x_posts_fetched", "x_users_fetched"];
|
|
429
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
|
+
}
|
|
430
1250
|
var XAI_PRICING = Object.freeze({
|
|
431
1251
|
// ── grok-4.5 ── $2.00/$6.00 (<200k), $4.00/$12.00 (>=200k); cached $0.30/$0.60
|
|
432
1252
|
"grok-4.5": {
|
|
@@ -504,6 +1324,11 @@ function scaleRates(rates, factor) {
|
|
|
504
1324
|
return scaled;
|
|
505
1325
|
}
|
|
506
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) {
|
|
507
1332
|
const listed = lookupConcreteRates(model, tier);
|
|
508
1333
|
if (listed === void 0) {
|
|
509
1334
|
return computeCost(model, usage, tier, lookupConcreteRates, xaiPricingVersion);
|
|
@@ -539,11 +1364,12 @@ function computeXaiCost(model, usage, tier) {
|
|
|
539
1364
|
return sum + Math.round(count * XAI_TOOL_RATE_MICRO_USD[key]);
|
|
540
1365
|
}, 0);
|
|
541
1366
|
const microUsd = inputCost + cachedCost + outputCost + toolsCost;
|
|
1367
|
+
const unpricedCounters = unpricedXaiToolCounters(usage);
|
|
542
1368
|
return {
|
|
543
1369
|
microUsd,
|
|
544
1370
|
usd: microUsd / 1e6,
|
|
545
1371
|
pricingVersion: xaiPricingVersion,
|
|
546
|
-
confidence: missingWebCounter || attachmentUnpinned ? "estimated" : "exact",
|
|
1372
|
+
confidence: missingWebCounter || attachmentUnpinned || unpricedCounters.length > 0 || usage.details[XAI_ESTIMATED_USAGE_KEY] === 1 ? "estimated" : "exact",
|
|
547
1373
|
details: {
|
|
548
1374
|
input: inputCost,
|
|
549
1375
|
cached: cachedCost,
|
|
@@ -577,22 +1403,42 @@ function isXaiMessageItem(item) {
|
|
|
577
1403
|
function isXaiReasoningItem(item) {
|
|
578
1404
|
return item.type === "reasoning" && Array.isArray(item.summary);
|
|
579
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
|
+
}
|
|
580
1412
|
function badXaiRequest(message) {
|
|
581
1413
|
return new LlmError(message, { kind: "bad_request", retryable: false });
|
|
582
1414
|
}
|
|
583
|
-
var ALLOWED_XAI_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/jpg", "image/png"]);
|
|
584
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
|
+
}
|
|
585
1436
|
function mapPart(p) {
|
|
586
1437
|
switch (p.kind) {
|
|
587
1438
|
case "text":
|
|
588
1439
|
return { type: "input_text", text: p.text };
|
|
589
1440
|
case "inline-media": {
|
|
590
|
-
|
|
591
|
-
throw badXaiRequest(
|
|
592
|
-
`xAI vision only supports image/jpeg and image/png; got mimeType "${p.mimeType}".`
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
const byteLength = Buffer.from(p.data, "base64").length;
|
|
1441
|
+
const byteLength = decodedBase64Length(p.data);
|
|
596
1442
|
if (byteLength > MAX_XAI_INLINE_IMAGE_BYTES) {
|
|
597
1443
|
throw badXaiRequest(
|
|
598
1444
|
`xAI inline images must be at most 20 MiB; got ${byteLength} bytes.`
|
|
@@ -602,8 +1448,7 @@ function mapPart(p) {
|
|
|
602
1448
|
}
|
|
603
1449
|
case "file-uri": {
|
|
604
1450
|
const isPublicHttpUrl = p.uri.startsWith("http://") || p.uri.startsWith("https://");
|
|
605
|
-
|
|
606
|
-
if (!isPublicHttpUrl || !isAllowedImageType) {
|
|
1451
|
+
if (!isPublicHttpUrl) {
|
|
607
1452
|
throw badXaiRequest(
|
|
608
1453
|
`xAI only accepts public http(s) image URLs via FileUriPart; got scheme of "${p.uri}" / mimeType "${p.mimeType}".`
|
|
609
1454
|
);
|
|
@@ -635,9 +1480,75 @@ var XAI_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
635
1480
|
"tools",
|
|
636
1481
|
"parallelToolCalls",
|
|
637
1482
|
"toolChoice",
|
|
638
|
-
"maxTurns"
|
|
1483
|
+
"maxTurns",
|
|
1484
|
+
"searchBudget"
|
|
639
1485
|
]);
|
|
1486
|
+
var XAI_DEFAULT_SCHEMA_NAME = "structured_output";
|
|
1487
|
+
var XAI_SCHEMA_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
640
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
|
+
}
|
|
641
1552
|
function mapXaiProviderOptions(xaiOpts, model) {
|
|
642
1553
|
if (xaiOpts === void 0) {
|
|
643
1554
|
return {};
|
|
@@ -652,7 +1563,7 @@ function mapXaiProviderOptions(xaiOpts, model) {
|
|
|
652
1563
|
throw badXaiRequest(
|
|
653
1564
|
`providerOptions.xai contains unsupported keys [${unknownKeys.join(
|
|
654
1565
|
", "
|
|
655
|
-
)}] for model "${model}". Allowed keys: promptCacheKey, tools, parallelToolCalls, toolChoice, maxTurns.`
|
|
1566
|
+
)}] for model "${model}". Allowed keys: promptCacheKey, tools, parallelToolCalls, toolChoice, maxTurns, searchBudget.`
|
|
656
1567
|
);
|
|
657
1568
|
}
|
|
658
1569
|
const mapped = {};
|
|
@@ -703,15 +1614,25 @@ function mapXaiProviderOptions(xaiOpts, model) {
|
|
|
703
1614
|
}
|
|
704
1615
|
mapped.maxTurns = maxTurns;
|
|
705
1616
|
}
|
|
1617
|
+
if (xaiOpts["searchBudget"] !== void 0) {
|
|
1618
|
+
mapped.searchBudget = mapXaiSearchBudget(xaiOpts["searchBudget"], mapped.tools, model);
|
|
1619
|
+
}
|
|
706
1620
|
return mapped;
|
|
707
1621
|
}
|
|
708
1622
|
function parseXaiReplayState(value, model) {
|
|
709
1623
|
if (value === void 0) return void 0;
|
|
710
|
-
|
|
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(
|
|
711
1632
|
(item) => !isPlainRecord(item) || typeof item["type"] !== "string" && typeof item["role"] !== "string"
|
|
712
1633
|
)) {
|
|
713
1634
|
throw badXaiRequest(
|
|
714
|
-
`transientProviderState must
|
|
1635
|
+
`transientProviderState must be { xai: { model, input } } with the full xAI wire input, bound to the requested model "${model}".`
|
|
715
1636
|
);
|
|
716
1637
|
}
|
|
717
1638
|
return value;
|
|
@@ -845,35 +1766,56 @@ function isXaiSafetyCheckBody(rawErr) {
|
|
|
845
1766
|
const text = extractXaiErrorBodyText(rawErr);
|
|
846
1767
|
return text !== void 0 && text.startsWith(XAI_SAFETY_CHECK_MESSAGE_PREFIX);
|
|
847
1768
|
}
|
|
848
|
-
var
|
|
849
|
-
function
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
function isXaiTransportError(rawErr) {
|
|
856
|
-
if (!(rawErr instanceof Error)) return false;
|
|
857
|
-
const ctorName = rawErr.constructor.name;
|
|
858
|
-
if (ctorName === "APIConnectionError" || ctorName === "APIConnectionTimeoutError") {
|
|
859
|
-
return true;
|
|
860
|
-
}
|
|
861
|
-
if (matchesXaiTransportSignature(rawErr)) return true;
|
|
862
|
-
const cause = rawErr.cause;
|
|
863
|
-
if (cause instanceof Error) {
|
|
864
|
-
const causeCtorName = cause.constructor.name;
|
|
865
|
-
if (causeCtorName === "APIConnectionError" || causeCtorName === "APIConnectionTimeoutError") {
|
|
866
|
-
return true;
|
|
867
|
-
}
|
|
868
|
-
if (matchesXaiTransportSignature(cause)) return true;
|
|
869
|
-
}
|
|
870
|
-
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");
|
|
871
1776
|
}
|
|
872
|
-
|
|
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) {
|
|
873
1801
|
if (rawErr instanceof LlmError) {
|
|
874
1802
|
return rawErr;
|
|
875
1803
|
}
|
|
1804
|
+
if (rawErr instanceof XaiStreamError) {
|
|
1805
|
+
return classifyStreamError(rawErr, estimatedInputTokens, requestTimeoutMs);
|
|
1806
|
+
}
|
|
876
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
|
+
}
|
|
877
1819
|
if (base.httpStatus === 400 && isXaiAuthFailureBody(rawErr)) {
|
|
878
1820
|
return new LlmError(base.message, {
|
|
879
1821
|
kind: "invalid_auth",
|
|
@@ -893,24 +1835,257 @@ function classifyXaiError(rawErr) {
|
|
|
893
1835
|
cause: base.cause ?? rawErr
|
|
894
1836
|
});
|
|
895
1837
|
}
|
|
896
|
-
if (base.
|
|
897
|
-
return new LlmError(
|
|
898
|
-
|
|
899
|
-
|
|
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)) {
|
|
1855
|
+
return new LlmError(base.message, {
|
|
1856
|
+
kind: "server",
|
|
1857
|
+
retryable: true,
|
|
1858
|
+
provider: "xai",
|
|
1859
|
+
cause: base.cause ?? rawErr
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
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,
|
|
900
2002
|
provider: "xai",
|
|
901
|
-
|
|
902
|
-
|
|
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;
|
|
903
2025
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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"
|
|
911
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
|
+
};
|
|
912
2070
|
}
|
|
913
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
|
+
}
|
|
914
2089
|
return {
|
|
915
2090
|
id: "xai",
|
|
916
2091
|
async run(req, ctx) {
|
|
@@ -922,12 +2097,16 @@ function xaiAdapter(opts) {
|
|
|
922
2097
|
}
|
|
923
2098
|
const warnings = [];
|
|
924
2099
|
const model = req.model;
|
|
925
|
-
if (req.modelDescriptor !== void 0
|
|
926
|
-
|
|
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");
|
|
927
2106
|
}
|
|
928
|
-
if (xaiRegistry.resolve("xai", model)?.capabilities?.
|
|
2107
|
+
if (xaiRegistry.resolve("xai", model)?.capabilities?.continuation === "state" && req.modelDescriptor?.capabilities?.continuation !== "state") {
|
|
929
2108
|
throw badXaiRequest(
|
|
930
|
-
`A matching xAI model descriptor with
|
|
2109
|
+
`A matching xAI model descriptor with continuation "state" is required for "${model}".`
|
|
931
2110
|
);
|
|
932
2111
|
}
|
|
933
2112
|
const genConfig = req.config;
|
|
@@ -935,11 +2114,11 @@ function xaiAdapter(opts) {
|
|
|
935
2114
|
genConfig.providerOptions?.["xai"],
|
|
936
2115
|
model
|
|
937
2116
|
);
|
|
938
|
-
const replayRequired = req.modelDescriptor?.capabilities?.
|
|
2117
|
+
const replayRequired = req.modelDescriptor?.capabilities?.continuation === "state";
|
|
939
2118
|
const replayState = parseXaiReplayState(req.transientProviderState, model);
|
|
940
2119
|
if (replayState !== void 0 && !replayRequired) {
|
|
941
2120
|
throw badXaiRequest(
|
|
942
|
-
`transientProviderState requires a
|
|
2121
|
+
`transientProviderState requires a model descriptor with continuation "state" for "${model}".`
|
|
943
2122
|
);
|
|
944
2123
|
}
|
|
945
2124
|
if (replayState !== void 0 && req.messages.length === 0) {
|
|
@@ -947,12 +2126,12 @@ function xaiAdapter(opts) {
|
|
|
947
2126
|
`Stateless conversation replay for model "${model}" requires new messages to append.`
|
|
948
2127
|
);
|
|
949
2128
|
}
|
|
950
|
-
const input = [...replayState?.input ?? []];
|
|
2129
|
+
const input = [...replayState?.xai.input ?? []];
|
|
951
2130
|
const replayCallIds = new Set(
|
|
952
|
-
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") ?? []
|
|
953
2132
|
);
|
|
954
2133
|
const replayedResultIds = new Set(
|
|
955
|
-
replayState?.input.filter(
|
|
2134
|
+
replayState?.xai.input.filter(
|
|
956
2135
|
(item) => isPlainRecord(item) && item["type"] === "function_call_output"
|
|
957
2136
|
).map((item) => isPlainRecord(item) ? item["call_id"] : void 0).filter((id) => typeof id === "string") ?? []
|
|
958
2137
|
);
|
|
@@ -1072,8 +2251,18 @@ function xaiAdapter(opts) {
|
|
|
1072
2251
|
const structuredOutputRequested = req.outputJsonSchema !== void 0;
|
|
1073
2252
|
if (structuredOutputRequested) {
|
|
1074
2253
|
const schema = req.outputJsonSchema;
|
|
1075
|
-
|
|
1076
|
-
|
|
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;
|
|
1077
2266
|
params.text = {
|
|
1078
2267
|
format: { type: "json_schema", name, schema, strict: true }
|
|
1079
2268
|
};
|
|
@@ -1127,6 +2316,13 @@ function xaiAdapter(opts) {
|
|
|
1127
2316
|
`tools is not supported for xai model "${model}" (capabilities.functionCalling is not true).`
|
|
1128
2317
|
);
|
|
1129
2318
|
}
|
|
2319
|
+
req.tools.forEach((tool, index) => {
|
|
2320
|
+
assertJsonSchemaProfile(
|
|
2321
|
+
tool.inputJsonSchema,
|
|
2322
|
+
`tools[${index}].inputJsonSchema`,
|
|
2323
|
+
XAI_JSON_SCHEMA_PROFILE
|
|
2324
|
+
);
|
|
2325
|
+
});
|
|
1130
2326
|
const functionTools = req.tools.map((tool) => ({
|
|
1131
2327
|
type: "function",
|
|
1132
2328
|
name: tool.name,
|
|
@@ -1139,130 +2335,300 @@ function xaiAdapter(opts) {
|
|
|
1139
2335
|
}
|
|
1140
2336
|
}
|
|
1141
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
|
+
}
|
|
1142
2343
|
params.parallel_tool_calls = xaiProviderConfig.parallelToolCalls;
|
|
1143
2344
|
}
|
|
1144
2345
|
let response;
|
|
2346
|
+
let responseMeta;
|
|
2347
|
+
let sdkCallStart;
|
|
1145
2348
|
try {
|
|
1146
|
-
const buildClient =
|
|
1147
|
-
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);
|
|
1148
2351
|
ctx.logger.debug(
|
|
1149
2352
|
{ model, configKeys: Object.keys(params) },
|
|
1150
2353
|
"llm.adapter.dispatch"
|
|
1151
2354
|
);
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
ctx.signal !== void 0 ? { signal: ctx.signal } :
|
|
1155
|
-
|
|
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);
|
|
1156
2365
|
} catch (rawErr) {
|
|
1157
|
-
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
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
for (const message of responseMeta?.streamNotes ?? []) {
|
|
2391
|
+
warnings.push({ type: "other", message });
|
|
1158
2392
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
if (
|
|
1169
|
-
|
|
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
|
+
}
|
|
1170
2443
|
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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);
|
|
1180
2460
|
}
|
|
1181
2461
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
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
|
+
});
|
|
1184
2473
|
}
|
|
1185
2474
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
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";
|
|
1191
2495
|
warnings.push({
|
|
1192
2496
|
type: "other",
|
|
1193
|
-
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}".`
|
|
1194
2498
|
});
|
|
1195
2499
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
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
|
+
});
|
|
1202
2507
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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
|
+
}
|
|
1213
2523
|
}
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
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) {
|
|
1217
2528
|
warnings.push({
|
|
1218
2529
|
type: "other",
|
|
1219
|
-
message: `xai: server
|
|
1220
|
-
", "
|
|
1221
|
-
)}]; 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.`
|
|
1222
2531
|
});
|
|
1223
2532
|
}
|
|
1224
|
-
if (
|
|
1225
|
-
|
|
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;
|
|
1226
2537
|
warnings.push({
|
|
1227
2538
|
type: "other",
|
|
1228
|
-
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.`
|
|
1229
2540
|
});
|
|
1230
2541
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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" } : {}
|
|
1246
2616
|
};
|
|
1247
|
-
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
...citations.length > 0 ? { citations } : {},
|
|
1263
|
-
...toolCalls.length > 0 ? { toolCalls, finishReason: "tool_calls" } : {}
|
|
1264
|
-
};
|
|
1265
|
-
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
|
+
}
|
|
1266
2632
|
},
|
|
1267
2633
|
async countTokens(req, ctx) {
|
|
1268
2634
|
if (req.provider !== "xai") {
|
|
@@ -1278,16 +2644,32 @@ function xaiAdapter(opts) {
|
|
|
1278
2644
|
}
|
|
1279
2645
|
const text = concatenateTokenizeText(req);
|
|
1280
2646
|
const apiKey = requireApiKey(ctx.auth);
|
|
1281
|
-
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);
|
|
1282
2661
|
try {
|
|
1283
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,
|
|
1284
2666
|
method: "POST",
|
|
1285
2667
|
headers: {
|
|
1286
2668
|
Authorization: `Bearer ${apiKey}`,
|
|
1287
2669
|
"Content-Type": "application/json"
|
|
1288
2670
|
},
|
|
1289
2671
|
body: JSON.stringify({ model: req.model, text }),
|
|
1290
|
-
|
|
2672
|
+
signal: controller.signal
|
|
1291
2673
|
});
|
|
1292
2674
|
if (!res.ok) {
|
|
1293
2675
|
let parsed;
|
|
@@ -1296,12 +2678,26 @@ function xaiAdapter(opts) {
|
|
|
1296
2678
|
} catch {
|
|
1297
2679
|
parsed = await res.text().catch(() => "");
|
|
1298
2680
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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
|
|
1302
2699
|
});
|
|
1303
2700
|
}
|
|
1304
|
-
const raw = await res.json();
|
|
1305
2701
|
if (!isPlainRecord(raw) || !Array.isArray(raw["token_ids"])) {
|
|
1306
2702
|
throw new LlmError(
|
|
1307
2703
|
"xAI tokenize-text response is malformed: missing required field: token_ids",
|
|
@@ -1316,6 +2712,9 @@ function xaiAdapter(opts) {
|
|
|
1316
2712
|
raw
|
|
1317
2713
|
};
|
|
1318
2714
|
} catch (rawErr) {
|
|
2715
|
+
if (controller.signal.aborted && controller.signal.reason === timeout) {
|
|
2716
|
+
throw timeout;
|
|
2717
|
+
}
|
|
1319
2718
|
if (rawErr instanceof Error && rawErr.name === "AbortError") {
|
|
1320
2719
|
throw new LlmError("xAI tokenize-text aborted", {
|
|
1321
2720
|
kind: "aborted",
|
|
@@ -1325,12 +2724,15 @@ function xaiAdapter(opts) {
|
|
|
1325
2724
|
});
|
|
1326
2725
|
}
|
|
1327
2726
|
throw classifyXaiError(rawErr);
|
|
2727
|
+
} finally {
|
|
2728
|
+
clearTimeout(timer);
|
|
2729
|
+
ctx.signal?.removeEventListener("abort", forwardAbort);
|
|
1328
2730
|
}
|
|
1329
2731
|
}
|
|
1330
2732
|
};
|
|
1331
2733
|
}
|
|
1332
2734
|
var WEB_SEARCH_COUNTER = "web_search_calls";
|
|
1333
|
-
function expectedServerToolCounters(tools
|
|
2735
|
+
function expectedServerToolCounters(tools) {
|
|
1334
2736
|
const keys = [];
|
|
1335
2737
|
if (tools !== void 0) {
|
|
1336
2738
|
for (const tool of tools) {
|
|
@@ -1340,49 +2742,71 @@ function expectedServerToolCounters(tools, _hasFileRef) {
|
|
|
1340
2742
|
}
|
|
1341
2743
|
return keys;
|
|
1342
2744
|
}
|
|
1343
|
-
function
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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 {
|
|
1358
2762
|
}
|
|
1359
|
-
|
|
2763
|
+
byUrl.set(url, citation);
|
|
1360
2764
|
}
|
|
1361
|
-
|
|
2765
|
+
if (citation.title === void 0 && typeof title === "string" && title.length > 0 && title !== url && title !== markerLabel) {
|
|
2766
|
+
citation.title = title;
|
|
2767
|
+
}
|
|
2768
|
+
return citation;
|
|
1362
2769
|
};
|
|
1363
2770
|
if (Array.isArray(response.citations)) {
|
|
1364
2771
|
for (const item of response.citations) {
|
|
1365
2772
|
if (typeof item === "string") {
|
|
1366
|
-
|
|
2773
|
+
upsert(item, void 0);
|
|
1367
2774
|
} else if (isPlainRecord(item)) {
|
|
1368
|
-
|
|
2775
|
+
upsert(item["url"] ?? item["uri"], item["title"]);
|
|
1369
2776
|
}
|
|
1370
2777
|
}
|
|
1371
2778
|
}
|
|
1372
2779
|
const lastMessage = messageItems.at(-1);
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
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
|
+
}
|
|
1382
2805
|
}
|
|
2806
|
+
partOffset += (partText(part) ?? "").length;
|
|
1383
2807
|
}
|
|
1384
2808
|
}
|
|
1385
|
-
return
|
|
2809
|
+
return [...byUrl.values()];
|
|
1386
2810
|
}
|
|
1387
2811
|
function concatenateTokenizeText(req) {
|
|
1388
2812
|
const chunks = [];
|
|
@@ -1414,6 +2838,7 @@ var XAI_FILE_TTL_MIN_SECONDS = 3600;
|
|
|
1414
2838
|
var XAI_FILE_TTL_MAX_SECONDS = 2592e3;
|
|
1415
2839
|
var XAI_FILE_MAX_BYTES = 48 * 1024 * 1024;
|
|
1416
2840
|
var XAI_FILES_DEFAULT_BASE_URL = "https://api.x.ai/v1";
|
|
2841
|
+
var XAI_FILES_DEFAULT_TIMEOUT_MS = 6e4;
|
|
1417
2842
|
function isPlainRecord2(value) {
|
|
1418
2843
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1419
2844
|
}
|
|
@@ -1482,19 +2907,23 @@ function toBlob(data, mimeType) {
|
|
|
1482
2907
|
var XaiFilesHttpError = class extends Error {
|
|
1483
2908
|
status;
|
|
1484
2909
|
error;
|
|
1485
|
-
|
|
2910
|
+
/** The response headers: `classifyError` reads `Retry-After` from them. */
|
|
2911
|
+
headers;
|
|
2912
|
+
constructor(status, message, errorBody, headers) {
|
|
1486
2913
|
super(message);
|
|
1487
2914
|
this.name = "XaiFilesHttpError";
|
|
1488
2915
|
this.status = status;
|
|
1489
2916
|
this.error = errorBody;
|
|
2917
|
+
this.headers = headers;
|
|
1490
2918
|
}
|
|
1491
2919
|
};
|
|
1492
|
-
async function throwHttpFailure(res) {
|
|
2920
|
+
async function throwHttpFailure(res, signal, operation) {
|
|
1493
2921
|
const status = res.status;
|
|
1494
2922
|
let bodyText = "";
|
|
1495
2923
|
try {
|
|
1496
2924
|
bodyText = await res.text();
|
|
1497
|
-
} catch {
|
|
2925
|
+
} catch (e) {
|
|
2926
|
+
if (signal.aborted) throw abortedError(signal, `xAI file ${operation} aborted`, e);
|
|
1498
2927
|
bodyText = "";
|
|
1499
2928
|
}
|
|
1500
2929
|
let parsed = bodyText;
|
|
@@ -1511,7 +2940,9 @@ async function throwHttpFailure(res) {
|
|
|
1511
2940
|
} else if (isPlainRecord2(parsed) && typeof parsed["error"] === "string") {
|
|
1512
2941
|
message = parsed["error"];
|
|
1513
2942
|
}
|
|
1514
|
-
|
|
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);
|
|
1515
2946
|
}
|
|
1516
2947
|
function isNotFoundError(err) {
|
|
1517
2948
|
if (err instanceof LlmError && err.httpStatus === 404) {
|
|
@@ -1580,16 +3011,41 @@ function classifyStoreError(raw) {
|
|
|
1580
3011
|
cause: classified.cause ?? raw
|
|
1581
3012
|
});
|
|
1582
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
|
+
}
|
|
1583
3031
|
var XaiFileStore = class {
|
|
1584
3032
|
apiKey;
|
|
1585
3033
|
baseUrl;
|
|
1586
3034
|
fetchImpl;
|
|
3035
|
+
timeoutMs;
|
|
1587
3036
|
onDeleteError;
|
|
1588
3037
|
logger;
|
|
1589
3038
|
constructor(opts) {
|
|
1590
3039
|
this.apiKey = requireApiKey(opts.auth);
|
|
1591
3040
|
this.baseUrl = (opts.baseUrl ?? XAI_FILES_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1592
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;
|
|
1593
3049
|
this.logger = opts.logger;
|
|
1594
3050
|
this.onDeleteError = opts.onDeleteError ?? ((fileId, err) => {
|
|
1595
3051
|
const message = redactSecrets(classifyError(err).message);
|
|
@@ -1623,6 +3079,37 @@ var XaiFileStore = class {
|
|
|
1623
3079
|
}
|
|
1624
3080
|
return init;
|
|
1625
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
|
+
}
|
|
1626
3113
|
/**
|
|
1627
3114
|
* Upload bytes to xAI Files. Returns immediately with metadata (no poll).
|
|
1628
3115
|
*
|
|
@@ -1654,77 +3141,64 @@ var XaiFileStore = class {
|
|
|
1654
3141
|
}
|
|
1655
3142
|
form.append("purpose", purpose);
|
|
1656
3143
|
form.append("file", toBlob(input.data, input.mimeType), input.filename);
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
res = await this.fetchImpl(
|
|
1660
|
-
this.filesUrl(),
|
|
1661
|
-
this.requestInit("POST", {
|
|
1662
|
-
body: form,
|
|
1663
|
-
...signal !== void 0 ? { signal } : {}
|
|
1664
|
-
})
|
|
1665
|
-
);
|
|
1666
|
-
} catch (e) {
|
|
1667
|
-
if (signal?.aborted === true) {
|
|
1668
|
-
throw new LlmError("xAI file upload aborted", {
|
|
1669
|
-
kind: "aborted",
|
|
1670
|
-
retryable: false,
|
|
1671
|
-
provider: "xai"
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
throw classifyStoreError(e);
|
|
1675
|
-
}
|
|
1676
|
-
if (!res.ok) {
|
|
3144
|
+
return this.bounded(signal, async (bound) => {
|
|
3145
|
+
let res;
|
|
1677
3146
|
try {
|
|
1678
|
-
await
|
|
3147
|
+
res = await this.fetchImpl(
|
|
3148
|
+
this.filesUrl(),
|
|
3149
|
+
this.requestInit("POST", { body: form, signal: bound })
|
|
3150
|
+
);
|
|
1679
3151
|
} catch (e) {
|
|
3152
|
+
if (bound.aborted) throw abortedError(bound, "xAI file upload aborted");
|
|
1680
3153
|
throw classifyStoreError(e);
|
|
1681
3154
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
})
|
|
1693
|
-
|
|
1694
|
-
|
|
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
|
+
});
|
|
1695
3176
|
}
|
|
1696
3177
|
async get(fileId, signal) {
|
|
1697
3178
|
if (typeof fileId !== "string" || fileId.trim() === "") {
|
|
1698
3179
|
throw badRequest("fileId must be a non-empty string.");
|
|
1699
3180
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
res
|
|
1703
|
-
this.filesUrl(fileId),
|
|
1704
|
-
this.requestInit("GET", signal !== void 0 ? { signal } : {})
|
|
1705
|
-
);
|
|
1706
|
-
} catch (e) {
|
|
1707
|
-
if (signal?.aborted === true) {
|
|
1708
|
-
throw new LlmError("xAI file get aborted", {
|
|
1709
|
-
kind: "aborted",
|
|
1710
|
-
retryable: false,
|
|
1711
|
-
provider: "xai"
|
|
1712
|
-
});
|
|
1713
|
-
}
|
|
1714
|
-
throw classifyStoreError(e);
|
|
1715
|
-
}
|
|
1716
|
-
if (res.status === 404) {
|
|
1717
|
-
throw notFoundError(fileId, "get");
|
|
1718
|
-
}
|
|
1719
|
-
if (!res.ok) {
|
|
3181
|
+
const url = this.filesUrl(pathSegment(fileId));
|
|
3182
|
+
return this.bounded(signal, async (bound) => {
|
|
3183
|
+
let res;
|
|
1720
3184
|
try {
|
|
1721
|
-
await
|
|
3185
|
+
res = await this.fetchImpl(url, this.requestInit("GET", { signal: bound }));
|
|
1722
3186
|
} catch (e) {
|
|
3187
|
+
if (bound.aborted) throw abortedError(bound, "xAI file get aborted");
|
|
1723
3188
|
throw classifyStoreError(e);
|
|
1724
3189
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
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
|
+
});
|
|
1728
3202
|
}
|
|
1729
3203
|
async list(opts = {}, signal) {
|
|
1730
3204
|
if (opts.limit !== void 0) {
|
|
@@ -1741,36 +3215,29 @@ var XaiFileStore = class {
|
|
|
1741
3215
|
}
|
|
1742
3216
|
const qs = params.toString();
|
|
1743
3217
|
const url = qs.length > 0 ? `${this.filesUrl()}?${qs}` : this.filesUrl();
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
res = await this.fetchImpl(
|
|
1747
|
-
url,
|
|
1748
|
-
this.requestInit("GET", signal !== void 0 ? { signal } : {})
|
|
1749
|
-
);
|
|
1750
|
-
} catch (e) {
|
|
1751
|
-
if (signal?.aborted === true) {
|
|
1752
|
-
throw new LlmError("xAI file list aborted", {
|
|
1753
|
-
kind: "aborted",
|
|
1754
|
-
retryable: false,
|
|
1755
|
-
provider: "xai"
|
|
1756
|
-
});
|
|
1757
|
-
}
|
|
1758
|
-
throw classifyStoreError(e);
|
|
1759
|
-
}
|
|
1760
|
-
if (!res.ok) {
|
|
3218
|
+
return this.bounded(signal, async (bound) => {
|
|
3219
|
+
let res;
|
|
1761
3220
|
try {
|
|
1762
|
-
await
|
|
3221
|
+
res = await this.fetchImpl(url, this.requestInit("GET", { signal: bound }));
|
|
1763
3222
|
} catch (e) {
|
|
3223
|
+
if (bound.aborted) throw abortedError(bound, "xAI file list aborted");
|
|
1764
3224
|
throw classifyStoreError(e);
|
|
1765
3225
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
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
|
+
});
|
|
1774
3241
|
}
|
|
1775
3242
|
/**
|
|
1776
3243
|
* Delete a file. Idempotent: HTTP 404 → success.
|
|
@@ -1786,34 +3253,31 @@ var XaiFileStore = class {
|
|
|
1786
3253
|
if (typeof fileId !== "string" || fileId.trim() === "") {
|
|
1787
3254
|
throw badRequest("fileId must be a non-empty string.");
|
|
1788
3255
|
}
|
|
3256
|
+
const url = this.filesUrl(pathSegment(fileId));
|
|
1789
3257
|
const failClosed = opts?.failClosed === true;
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
}) : classifyStoreError(err);
|
|
1812
|
-
if (failClosed) {
|
|
1813
|
-
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);
|
|
1814
3279
|
}
|
|
1815
|
-
|
|
1816
|
-
}
|
|
3280
|
+
});
|
|
1817
3281
|
}
|
|
1818
3282
|
/**
|
|
1819
3283
|
* Delete many files.
|
|
@@ -1835,36 +3299,48 @@ var XaiFileStore = class {
|
|
|
1835
3299
|
if (typeof fileId !== "string" || fileId.trim() === "") {
|
|
1836
3300
|
throw badRequest("fileId must be a non-empty string.");
|
|
1837
3301
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
res
|
|
1841
|
-
|
|
1842
|
-
this.requestInit("GET",
|
|
1843
|
-
)
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
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
|
+
}
|
|
1851
3320
|
}
|
|
1852
|
-
throw classifyStoreError(e);
|
|
1853
|
-
}
|
|
1854
|
-
if (res.status === 404) {
|
|
1855
|
-
throw notFoundError(fileId, "getContent");
|
|
1856
|
-
}
|
|
1857
|
-
if (!res.ok) {
|
|
1858
3321
|
try {
|
|
1859
|
-
await
|
|
3322
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1860
3323
|
} catch (e) {
|
|
3324
|
+
if (bound.aborted)
|
|
3325
|
+
throw abortedError(bound, "xAI file content download aborted", e);
|
|
1861
3326
|
throw classifyStoreError(e);
|
|
1862
3327
|
}
|
|
1863
|
-
}
|
|
1864
|
-
const buf = await res.arrayBuffer();
|
|
1865
|
-
return new Uint8Array(buf);
|
|
3328
|
+
});
|
|
1866
3329
|
}
|
|
1867
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
|
+
}
|
|
1868
3344
|
|
|
1869
3345
|
// src/provider.ts
|
|
1870
3346
|
function xaiProvider(opts) {
|
|
@@ -1875,6 +3351,6 @@ function xaiProvider(opts) {
|
|
|
1875
3351
|
};
|
|
1876
3352
|
}
|
|
1877
3353
|
|
|
1878
|
-
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 };
|
|
1879
3355
|
//# sourceMappingURL=index.js.map
|
|
1880
3356
|
//# sourceMappingURL=index.js.map
|