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