@better-agent/providers 0.1.0-canary.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/README.md +3 -0
- package/dist/anthropic/index.d.mts +1196 -0
- package/dist/anthropic/index.d.mts.map +1 -0
- package/dist/anthropic/index.mjs +1909 -0
- package/dist/anthropic/index.mjs.map +1 -0
- package/dist/fetch-CW0dWlVC.mjs +84 -0
- package/dist/fetch-CW0dWlVC.mjs.map +1 -0
- package/dist/openai/index.d.mts +1629 -0
- package/dist/openai/index.d.mts.map +1 -0
- package/dist/openai/index.mjs +6690 -0
- package/dist/openai/index.mjs.map +1 -0
- package/dist/xai/index.d.mts +382 -0
- package/dist/xai/index.d.mts.map +1 -0
- package/dist/xai/index.mjs +1277 -0
- package/dist/xai/index.mjs.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
import { n as extractPassthroughOptions, r as omitNullish, t as baFetch } from "../fetch-CW0dWlVC.mjs";
|
|
2
|
+
import { Events } from "@better-agent/core/events";
|
|
3
|
+
import { BetterAgentError } from "@better-agent/shared/errors";
|
|
4
|
+
import { err, ok } from "@better-agent/shared/neverthrow";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { safeJsonParse } from "@better-agent/shared/utils";
|
|
7
|
+
import { TOOL_JSON_SCHEMA, isCallableToolDefinition } from "@better-agent/core";
|
|
8
|
+
|
|
9
|
+
//#region src/xai/client/auth.ts
|
|
10
|
+
const buildXAIHeaders = (config) => {
|
|
11
|
+
const headers = { ...config.headers ?? {} };
|
|
12
|
+
if (config.apiKey) headers.Authorization = `Bearer ${config.apiKey}`;
|
|
13
|
+
return headers;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/xai/client/errors.ts
|
|
18
|
+
const mapXAIHttpError = (httpError, ctx) => {
|
|
19
|
+
if (!httpError) return BetterAgentError.fromCode("UPSTREAM_FAILED", "xAI request failed", { context: { provider: "xai" } }).at({
|
|
20
|
+
at: ctx.at,
|
|
21
|
+
data: { path: ctx.path }
|
|
22
|
+
});
|
|
23
|
+
const { status, statusText, error } = httpError;
|
|
24
|
+
const message = error?.message ?? statusText ?? "xAI request failed";
|
|
25
|
+
const code = String(error?.code ?? error?.type ?? status);
|
|
26
|
+
return BetterAgentError.fromCode("UPSTREAM_FAILED", message, {
|
|
27
|
+
status,
|
|
28
|
+
context: {
|
|
29
|
+
provider: "xai",
|
|
30
|
+
upstreamCode: code,
|
|
31
|
+
raw: error
|
|
32
|
+
}
|
|
33
|
+
}).at({
|
|
34
|
+
at: ctx.at,
|
|
35
|
+
data: {
|
|
36
|
+
path: ctx.path,
|
|
37
|
+
status
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/xai/client/stream.ts
|
|
44
|
+
const unwrapXAIStreamJson = (parsed) => {
|
|
45
|
+
if (!parsed || typeof parsed !== "object") return parsed;
|
|
46
|
+
const obj = parsed;
|
|
47
|
+
if ("data" in obj) return obj.data;
|
|
48
|
+
if ("value" in obj) return obj.value;
|
|
49
|
+
return obj;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/xai/client/create-client.ts
|
|
54
|
+
const createXAIClient = (config = {}) => {
|
|
55
|
+
const baseUrl = (config.baseURL ?? "https://api.x.ai").replace(/\/+$/, "");
|
|
56
|
+
const headers = buildXAIHeaders(config);
|
|
57
|
+
const post = async (path, body, at, options) => {
|
|
58
|
+
try {
|
|
59
|
+
const result = await baFetch(`${baseUrl}${path}`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
headers: {
|
|
63
|
+
...headers,
|
|
64
|
+
"Content-Type": "application/json"
|
|
65
|
+
},
|
|
66
|
+
signal: options?.signal ?? null,
|
|
67
|
+
throw: false
|
|
68
|
+
});
|
|
69
|
+
if (result.error) return err(mapXAIHttpError(result.error, {
|
|
70
|
+
at,
|
|
71
|
+
path
|
|
72
|
+
}).at({
|
|
73
|
+
at,
|
|
74
|
+
data: { path }
|
|
75
|
+
}));
|
|
76
|
+
if (!result.data) return err(BetterAgentError.fromCode("UPSTREAM_FAILED", "xAI returned no data", { context: { provider: "xai" } }).at({
|
|
77
|
+
at,
|
|
78
|
+
data: { path }
|
|
79
|
+
}));
|
|
80
|
+
return ok(result.data);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
return err(BetterAgentError.wrap({
|
|
83
|
+
err: e,
|
|
84
|
+
message: "xAI request failed",
|
|
85
|
+
opts: {
|
|
86
|
+
code: "UPSTREAM_FAILED",
|
|
87
|
+
context: { provider: "xai" }
|
|
88
|
+
}
|
|
89
|
+
}).at({
|
|
90
|
+
at,
|
|
91
|
+
data: { path }
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const get = async (path, at, options) => {
|
|
96
|
+
try {
|
|
97
|
+
const result = await baFetch(`${baseUrl}${path}`, {
|
|
98
|
+
method: "GET",
|
|
99
|
+
headers,
|
|
100
|
+
signal: options?.signal ?? null,
|
|
101
|
+
throw: false
|
|
102
|
+
});
|
|
103
|
+
if (result.error) return err(mapXAIHttpError(result.error, {
|
|
104
|
+
at,
|
|
105
|
+
path
|
|
106
|
+
}).at({
|
|
107
|
+
at,
|
|
108
|
+
data: { path }
|
|
109
|
+
}));
|
|
110
|
+
if (!result.data) return err(BetterAgentError.fromCode("UPSTREAM_FAILED", "xAI returned no data", { context: { provider: "xai" } }).at({
|
|
111
|
+
at,
|
|
112
|
+
data: { path }
|
|
113
|
+
}));
|
|
114
|
+
return ok(result.data);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return err(BetterAgentError.wrap({
|
|
117
|
+
err: e,
|
|
118
|
+
message: "xAI request failed",
|
|
119
|
+
opts: {
|
|
120
|
+
code: "UPSTREAM_FAILED",
|
|
121
|
+
context: { provider: "xai" }
|
|
122
|
+
}
|
|
123
|
+
}).at({
|
|
124
|
+
at,
|
|
125
|
+
data: { path }
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const del = async (path, at, options) => {
|
|
130
|
+
try {
|
|
131
|
+
const result = await baFetch(`${baseUrl}${path}`, {
|
|
132
|
+
method: "DELETE",
|
|
133
|
+
headers,
|
|
134
|
+
signal: options?.signal ?? null,
|
|
135
|
+
throw: false
|
|
136
|
+
});
|
|
137
|
+
if (result.error) return err(mapXAIHttpError(result.error, {
|
|
138
|
+
at,
|
|
139
|
+
path
|
|
140
|
+
}).at({
|
|
141
|
+
at,
|
|
142
|
+
data: { path }
|
|
143
|
+
}));
|
|
144
|
+
if (!result.data) return err(BetterAgentError.fromCode("UPSTREAM_FAILED", "xAI returned no data", { context: { provider: "xai" } }).at({
|
|
145
|
+
at,
|
|
146
|
+
data: { path }
|
|
147
|
+
}));
|
|
148
|
+
return ok(result.data);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
return err(BetterAgentError.wrap({
|
|
151
|
+
err: e,
|
|
152
|
+
message: "xAI request failed",
|
|
153
|
+
opts: {
|
|
154
|
+
code: "UPSTREAM_FAILED",
|
|
155
|
+
context: { provider: "xai" }
|
|
156
|
+
}
|
|
157
|
+
}).at({
|
|
158
|
+
at,
|
|
159
|
+
data: { path }
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const uploadFile = async (path, body, at, options) => {
|
|
164
|
+
const formData = new FormData();
|
|
165
|
+
try {
|
|
166
|
+
const filename = body.filename ?? (typeof File !== "undefined" && body.file instanceof File ? body.file.name : "file");
|
|
167
|
+
const blob = body.file instanceof Blob ? body.file : new Blob([new Uint8Array(body.file)], { type: body.mimeType ?? "application/octet-stream" });
|
|
168
|
+
formData.append("purpose", body.purpose ?? "assistants");
|
|
169
|
+
formData.append("file", blob, filename);
|
|
170
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
body: formData,
|
|
173
|
+
headers,
|
|
174
|
+
signal: options?.signal ?? null
|
|
175
|
+
});
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
let xaiError;
|
|
178
|
+
try {
|
|
179
|
+
xaiError = await response.json();
|
|
180
|
+
} catch {}
|
|
181
|
+
return err(mapXAIHttpError({
|
|
182
|
+
status: response.status,
|
|
183
|
+
statusText: response.statusText,
|
|
184
|
+
error: xaiError?.error
|
|
185
|
+
}, {
|
|
186
|
+
at,
|
|
187
|
+
path
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
const data = await response.json();
|
|
191
|
+
if (!data) return err(BetterAgentError.fromCode("UPSTREAM_FAILED", "xAI returned no data", { context: { provider: "xai" } }).at({
|
|
192
|
+
at,
|
|
193
|
+
data: { path }
|
|
194
|
+
}));
|
|
195
|
+
return ok(data);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return err(BetterAgentError.wrap({
|
|
198
|
+
err: e,
|
|
199
|
+
message: "xAI file upload failed",
|
|
200
|
+
opts: {
|
|
201
|
+
code: "UPSTREAM_FAILED",
|
|
202
|
+
context: { provider: "xai" }
|
|
203
|
+
}
|
|
204
|
+
}).at({
|
|
205
|
+
at,
|
|
206
|
+
data: { path }
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const getBinary = async (path, at, options) => {
|
|
211
|
+
let response;
|
|
212
|
+
try {
|
|
213
|
+
response = await fetch(`${baseUrl}${path}`, {
|
|
214
|
+
method: "GET",
|
|
215
|
+
headers,
|
|
216
|
+
signal: options?.signal ?? null
|
|
217
|
+
});
|
|
218
|
+
} catch (e) {
|
|
219
|
+
return err(BetterAgentError.wrap({
|
|
220
|
+
err: e,
|
|
221
|
+
message: "xAI request failed",
|
|
222
|
+
opts: {
|
|
223
|
+
code: "UPSTREAM_FAILED",
|
|
224
|
+
context: { provider: "xai" }
|
|
225
|
+
}
|
|
226
|
+
}).at({
|
|
227
|
+
at,
|
|
228
|
+
data: { path }
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
let xaiError;
|
|
233
|
+
try {
|
|
234
|
+
xaiError = await response.json();
|
|
235
|
+
} catch {}
|
|
236
|
+
return err(mapXAIHttpError({
|
|
237
|
+
status: response.status,
|
|
238
|
+
statusText: response.statusText,
|
|
239
|
+
error: xaiError?.error
|
|
240
|
+
}, {
|
|
241
|
+
at,
|
|
242
|
+
path
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
return ok(new Uint8Array(await response.arrayBuffer()));
|
|
246
|
+
};
|
|
247
|
+
const streamSSE = async (path, body, at, options) => {
|
|
248
|
+
let response;
|
|
249
|
+
try {
|
|
250
|
+
response = await fetch(`${baseUrl}${path}`, {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: {
|
|
253
|
+
...headers,
|
|
254
|
+
Accept: "text/event-stream",
|
|
255
|
+
"Content-Type": "application/json"
|
|
256
|
+
},
|
|
257
|
+
body: JSON.stringify(body),
|
|
258
|
+
signal: options?.signal ?? null
|
|
259
|
+
});
|
|
260
|
+
} catch (e) {
|
|
261
|
+
return err(BetterAgentError.wrap({
|
|
262
|
+
err: e,
|
|
263
|
+
message: "xAI stream request failed",
|
|
264
|
+
opts: {
|
|
265
|
+
code: "UPSTREAM_FAILED",
|
|
266
|
+
context: { provider: "xai" }
|
|
267
|
+
}
|
|
268
|
+
}).at({
|
|
269
|
+
at,
|
|
270
|
+
data: { path }
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
if (!response.ok || !response.body) {
|
|
274
|
+
let xaiError;
|
|
275
|
+
try {
|
|
276
|
+
xaiError = await response.json();
|
|
277
|
+
} catch {}
|
|
278
|
+
return err(mapXAIHttpError({
|
|
279
|
+
status: response.status,
|
|
280
|
+
statusText: response.statusText,
|
|
281
|
+
error: xaiError?.error
|
|
282
|
+
}, {
|
|
283
|
+
at,
|
|
284
|
+
path
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
const reader = response.body.getReader();
|
|
288
|
+
const decoder = new TextDecoder();
|
|
289
|
+
return ok((async function* () {
|
|
290
|
+
let buffer = "";
|
|
291
|
+
try {
|
|
292
|
+
while (true) {
|
|
293
|
+
const { done, value } = await reader.read();
|
|
294
|
+
if (done) break;
|
|
295
|
+
buffer += decoder.decode(value, { stream: true });
|
|
296
|
+
const chunks = buffer.split("\n\n");
|
|
297
|
+
buffer = chunks.pop() ?? "";
|
|
298
|
+
for (const chunk of chunks) {
|
|
299
|
+
const dataLines = chunk.split("\n").map((line) => line.trim()).filter(Boolean).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
|
|
300
|
+
for (const data of dataLines) {
|
|
301
|
+
if (!data || data === "[DONE]") continue;
|
|
302
|
+
const parsed = safeJsonParse(String(unwrapXAIStreamJson(data)));
|
|
303
|
+
if (parsed.isErr() || !parsed.value || typeof parsed.value !== "object") {
|
|
304
|
+
yield err(BetterAgentError.fromCode("UPSTREAM_FAILED", "xAI returned an invalid stream event", { context: {
|
|
305
|
+
provider: "xai",
|
|
306
|
+
raw: data
|
|
307
|
+
} }).at({
|
|
308
|
+
at,
|
|
309
|
+
data: { path }
|
|
310
|
+
}));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
yield ok(parsed.value);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch (e) {
|
|
318
|
+
yield err(BetterAgentError.wrap({
|
|
319
|
+
err: e,
|
|
320
|
+
message: "xAI stream read failed",
|
|
321
|
+
opts: {
|
|
322
|
+
code: "UPSTREAM_FAILED",
|
|
323
|
+
context: { provider: "xai" }
|
|
324
|
+
}
|
|
325
|
+
}).at({
|
|
326
|
+
at,
|
|
327
|
+
data: { path }
|
|
328
|
+
}));
|
|
329
|
+
} finally {
|
|
330
|
+
reader.releaseLock();
|
|
331
|
+
}
|
|
332
|
+
})());
|
|
333
|
+
};
|
|
334
|
+
return {
|
|
335
|
+
responses: {
|
|
336
|
+
create: (body, options) => post("/v1/responses", body, "xai.http.responses.create", options),
|
|
337
|
+
stream: (body, options) => streamSSE("/v1/responses", {
|
|
338
|
+
...body,
|
|
339
|
+
stream: true
|
|
340
|
+
}, "xai.http.responses.stream", options)
|
|
341
|
+
},
|
|
342
|
+
images: {
|
|
343
|
+
create: (body, options) => post("/v1/images/generations", body, "xai.http.images.create", options),
|
|
344
|
+
edit: (body, options) => post("/v1/images/edits", body, "xai.http.images.edit", options)
|
|
345
|
+
},
|
|
346
|
+
files: {
|
|
347
|
+
upload: (body, options) => uploadFile("/v1/files", body, "xai.http.files.upload", options),
|
|
348
|
+
list: (options) => get("/v1/files", "xai.http.files.list", options),
|
|
349
|
+
retrieve: (fileId, options) => get(`/v1/files/${encodeURIComponent(fileId)}`, "xai.http.files.retrieve", options),
|
|
350
|
+
delete: (fileId, options) => del(`/v1/files/${encodeURIComponent(fileId)}`, "xai.http.files.delete", options),
|
|
351
|
+
content: (fileId, options) => getBinary(`/v1/files/${encodeURIComponent(fileId)}/content`, "xai.http.files.content", options)
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region src/xai/images/mappers.ts
|
|
358
|
+
/**
|
|
359
|
+
* Keys explicitly handled by the xAI images mapper.
|
|
360
|
+
* Everything else on the options object is passed through to the API body.
|
|
361
|
+
*/
|
|
362
|
+
const XAI_IMAGE_KNOWN_KEYS = new Set([
|
|
363
|
+
"input",
|
|
364
|
+
"tools",
|
|
365
|
+
"toolChoice",
|
|
366
|
+
"modalities",
|
|
367
|
+
"structured_output",
|
|
368
|
+
"n",
|
|
369
|
+
"quality",
|
|
370
|
+
"response_format",
|
|
371
|
+
"size",
|
|
372
|
+
"style",
|
|
373
|
+
"aspect_ratio",
|
|
374
|
+
"resolution",
|
|
375
|
+
"user"
|
|
376
|
+
]);
|
|
377
|
+
const toImageDataUrl = (source) => source.kind === "url" ? source.url : `data:${source.mimeType};base64,${source.data}`;
|
|
378
|
+
const parseXAIImagePromptInput = (args) => {
|
|
379
|
+
const input = args.options.input;
|
|
380
|
+
if (Array.isArray(input) && input.length > 1) throw BetterAgentError.fromCode("VALIDATION_FAILED", "Image generation supports a single input item", { context: {
|
|
381
|
+
provider: "xai",
|
|
382
|
+
model: args.modelId
|
|
383
|
+
} }).at({ at: "xai.images.map.inputLimit" });
|
|
384
|
+
const first = Array.isArray(input) ? input[0] : input;
|
|
385
|
+
if (typeof first === "string") return {
|
|
386
|
+
prompt: first,
|
|
387
|
+
images: []
|
|
388
|
+
};
|
|
389
|
+
if (!first || typeof first !== "object" || !("type" in first) || first.type !== "message") throw BetterAgentError.fromCode("VALIDATION_FAILED", "xAI image generation requires a prompt string or a single message input.", { context: {
|
|
390
|
+
provider: "xai",
|
|
391
|
+
model: args.modelId
|
|
392
|
+
} }).at({ at: "xai.images.map.inputShape" });
|
|
393
|
+
if (typeof first.content === "string") return {
|
|
394
|
+
prompt: first.content,
|
|
395
|
+
images: []
|
|
396
|
+
};
|
|
397
|
+
const promptParts = [];
|
|
398
|
+
const images = [];
|
|
399
|
+
for (const part of first.content) {
|
|
400
|
+
if (part.type === "text") {
|
|
401
|
+
promptParts.push(part.text);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (part.type === "image") {
|
|
405
|
+
images.push(part.source);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
throw BetterAgentError.fromCode("VALIDATION_FAILED", "xAI image generation only supports text and image input parts.", { context: {
|
|
409
|
+
provider: "xai",
|
|
410
|
+
model: args.modelId
|
|
411
|
+
} }).at({ at: "xai.images.map.partType" });
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
prompt: promptParts.join("\n"),
|
|
415
|
+
images
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
function mapToXAIImagesRequest(args) {
|
|
419
|
+
try {
|
|
420
|
+
const o = args.options;
|
|
421
|
+
if ("stream" in o && o.stream) return err(BetterAgentError.fromCode("VALIDATION_FAILED", "Image streaming is not supported by the xAI provider.", { context: {
|
|
422
|
+
provider: "xai",
|
|
423
|
+
model: args.modelId
|
|
424
|
+
} }).at({ at: "xai.images.map.streamUnsupported" }));
|
|
425
|
+
const parsed = parseXAIImagePromptInput(args);
|
|
426
|
+
const prompt = parsed.prompt;
|
|
427
|
+
if (!prompt || prompt.trim().length === 0) return err(BetterAgentError.fromCode("VALIDATION_FAILED", "xAI image generation requires a text prompt.", { context: {
|
|
428
|
+
provider: "xai",
|
|
429
|
+
model: args.modelId
|
|
430
|
+
} }).at({ at: "xai.images.map.prompt" }));
|
|
431
|
+
const imageOptions = o;
|
|
432
|
+
if (parsed.images.length === 0) return ok({
|
|
433
|
+
mode: "generate",
|
|
434
|
+
body: {
|
|
435
|
+
...extractPassthroughOptions(imageOptions, XAI_IMAGE_KNOWN_KEYS),
|
|
436
|
+
model: args.modelId,
|
|
437
|
+
prompt,
|
|
438
|
+
...omitNullish({
|
|
439
|
+
n: imageOptions.n,
|
|
440
|
+
quality: imageOptions.quality,
|
|
441
|
+
response_format: imageOptions.response_format,
|
|
442
|
+
size: imageOptions.size,
|
|
443
|
+
style: imageOptions.style,
|
|
444
|
+
user: imageOptions.user
|
|
445
|
+
})
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
const firstImage = parsed.images[0];
|
|
449
|
+
if (!firstImage) throw BetterAgentError.fromCode("VALIDATION_FAILED", "xAI image generation requires at least one image when editing", { context: {
|
|
450
|
+
provider: "xai",
|
|
451
|
+
model: args.modelId
|
|
452
|
+
} }).at({ at: "xai.images.map.firstImageMissing" });
|
|
453
|
+
return ok({
|
|
454
|
+
mode: "edit",
|
|
455
|
+
body: {
|
|
456
|
+
...extractPassthroughOptions(imageOptions, XAI_IMAGE_KNOWN_KEYS),
|
|
457
|
+
model: args.modelId,
|
|
458
|
+
prompt,
|
|
459
|
+
...parsed.images.length === 1 ? { image: {
|
|
460
|
+
type: "image_url",
|
|
461
|
+
image_url: toImageDataUrl(firstImage)
|
|
462
|
+
} } : { images: parsed.images.map((image) => ({
|
|
463
|
+
type: "image_url",
|
|
464
|
+
image_url: toImageDataUrl(image)
|
|
465
|
+
})) },
|
|
466
|
+
...omitNullish({
|
|
467
|
+
n: imageOptions.n,
|
|
468
|
+
quality: imageOptions.quality,
|
|
469
|
+
response_format: imageOptions.response_format,
|
|
470
|
+
size: imageOptions.size,
|
|
471
|
+
style: imageOptions.style,
|
|
472
|
+
aspect_ratio: imageOptions.aspect_ratio,
|
|
473
|
+
resolution: imageOptions.resolution,
|
|
474
|
+
user: imageOptions.user
|
|
475
|
+
})
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
} catch (e) {
|
|
479
|
+
return err(BetterAgentError.wrap({
|
|
480
|
+
err: e,
|
|
481
|
+
message: "Failed to map xAI image request",
|
|
482
|
+
opts: {
|
|
483
|
+
code: "INTERNAL",
|
|
484
|
+
context: {
|
|
485
|
+
provider: "xai",
|
|
486
|
+
model: args.modelId
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}).at({ at: "xai.images.mapToRequest" }));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function mapFromXAIImagesResponse(raw) {
|
|
493
|
+
const output = (raw.data ?? []).map(outputMapper).filter(isDefined);
|
|
494
|
+
const usage = typeof raw.usage?.cost_in_usd_ticks === "number" ? { totalTokens: raw.usage.cost_in_usd_ticks } : {};
|
|
495
|
+
const images = (raw.data ?? []).map((item) => ({ ...omitNullish({
|
|
496
|
+
url: typeof item.url === "string" ? item.url : void 0,
|
|
497
|
+
b64_json: typeof item.b64_json === "string" ? item.b64_json : void 0,
|
|
498
|
+
mime_type: typeof item.mime_type === "string" ? item.mime_type : void 0,
|
|
499
|
+
revised_prompt: typeof item.revised_prompt === "string" ? item.revised_prompt : void 0
|
|
500
|
+
}) }));
|
|
501
|
+
return {
|
|
502
|
+
output,
|
|
503
|
+
finishReason: "stop",
|
|
504
|
+
usage,
|
|
505
|
+
response: { body: {
|
|
506
|
+
...raw,
|
|
507
|
+
normalized: {
|
|
508
|
+
images,
|
|
509
|
+
usage: raw.usage
|
|
510
|
+
}
|
|
511
|
+
} }
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function outputMapper(item) {
|
|
515
|
+
if (!item || typeof item !== "object") return null;
|
|
516
|
+
const image = item;
|
|
517
|
+
const url = typeof image.url === "string" ? image.url : null;
|
|
518
|
+
const b64 = typeof image.b64_json === "string" ? image.b64_json : null;
|
|
519
|
+
const mimeType = typeof image.mime_type === "string" ? image.mime_type : void 0;
|
|
520
|
+
const revisedPrompt = typeof image.revised_prompt === "string" ? image.revised_prompt : void 0;
|
|
521
|
+
if (!url && !b64) return null;
|
|
522
|
+
if (b64) return {
|
|
523
|
+
type: "message",
|
|
524
|
+
role: "assistant",
|
|
525
|
+
content: [{
|
|
526
|
+
type: "image",
|
|
527
|
+
source: {
|
|
528
|
+
kind: "base64",
|
|
529
|
+
data: b64,
|
|
530
|
+
mimeType: mimeType ?? "image/png"
|
|
531
|
+
},
|
|
532
|
+
...revisedPrompt ? { revisedPrompt } : {}
|
|
533
|
+
}]
|
|
534
|
+
};
|
|
535
|
+
if (url == null) return null;
|
|
536
|
+
return {
|
|
537
|
+
type: "message",
|
|
538
|
+
role: "assistant",
|
|
539
|
+
content: [{
|
|
540
|
+
type: "image",
|
|
541
|
+
source: {
|
|
542
|
+
kind: "url",
|
|
543
|
+
url
|
|
544
|
+
},
|
|
545
|
+
...mimeType ? { mimeType } : {},
|
|
546
|
+
...revisedPrompt ? { revisedPrompt } : {}
|
|
547
|
+
}]
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function isDefined(value) {
|
|
551
|
+
return value !== null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/xai/images/model.ts
|
|
556
|
+
const XAI_IMAGE_CAPS = {
|
|
557
|
+
inputModalities: {
|
|
558
|
+
text: true,
|
|
559
|
+
image: true
|
|
560
|
+
},
|
|
561
|
+
inputShape: "prompt",
|
|
562
|
+
replayMode: "single_turn_persistent",
|
|
563
|
+
supportsInstruction: false,
|
|
564
|
+
outputModalities: { image: true },
|
|
565
|
+
additionalSupportedRoles: ["developer"]
|
|
566
|
+
};
|
|
567
|
+
const createXAIImagesModel = (modelId, client) => {
|
|
568
|
+
const doGenerate = async (options, ctx) => {
|
|
569
|
+
const requestBodyResult = mapToXAIImagesRequest({
|
|
570
|
+
modelId,
|
|
571
|
+
options
|
|
572
|
+
});
|
|
573
|
+
if (requestBodyResult.isErr()) return err(requestBodyResult.error.at({ at: "xai.images.generate.mapRequest" }));
|
|
574
|
+
const request = requestBodyResult.value;
|
|
575
|
+
const raw = request.mode === "edit" ? await client.images.edit(request.body, { signal: ctx.signal ?? null }) : await client.images.create(request.body, { signal: ctx.signal ?? null });
|
|
576
|
+
if (raw.isErr()) return err(raw.error.at({ at: "xai.images.generate.http" }));
|
|
577
|
+
return ok({ response: {
|
|
578
|
+
...mapFromXAIImagesResponse(raw.value),
|
|
579
|
+
request: { body: request.body }
|
|
580
|
+
} });
|
|
581
|
+
};
|
|
582
|
+
return {
|
|
583
|
+
providerId: "xai",
|
|
584
|
+
modelId,
|
|
585
|
+
caps: XAI_IMAGE_CAPS,
|
|
586
|
+
doGenerate
|
|
587
|
+
};
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/xai/tools/index.ts
|
|
592
|
+
function createXAINativeToolBuilders() {
|
|
593
|
+
return {
|
|
594
|
+
webSearch: (config = {}) => createNativeTool("web_search", config),
|
|
595
|
+
xSearch: (config = {}) => createNativeTool("x_search", config),
|
|
596
|
+
codeExecution: (config = {}) => createNativeTool("code_execution", config),
|
|
597
|
+
codeInterpreter: (config = {}) => createNativeTool("code_interpreter", config),
|
|
598
|
+
attachmentSearch: (config = {}) => createNativeTool("attachment_search", config),
|
|
599
|
+
collectionsSearch: (config = {}) => createNativeTool("collections_search", config),
|
|
600
|
+
fileSearch: (config = {}) => createNativeTool("file_search", config),
|
|
601
|
+
mcp: (config) => createNativeTool("mcp", config)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function createNativeTool(type, config) {
|
|
605
|
+
return {
|
|
606
|
+
kind: "hosted",
|
|
607
|
+
provider: "xai",
|
|
608
|
+
type,
|
|
609
|
+
config
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function isXAINativeToolDefinition(tool) {
|
|
613
|
+
if (!tool || typeof tool !== "object") return false;
|
|
614
|
+
const t = tool;
|
|
615
|
+
return t.kind === "hosted" && t.provider === "xai" && typeof t.type === "string";
|
|
616
|
+
}
|
|
617
|
+
function mapXAINativeToolToRequest(tool) {
|
|
618
|
+
return {
|
|
619
|
+
type: tool.type,
|
|
620
|
+
...tool.config
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
//#endregion
|
|
625
|
+
//#region src/xai/responses/mappers.ts
|
|
626
|
+
/**
|
|
627
|
+
* Keys explicitly handled by the xAI responses mapper.
|
|
628
|
+
*/
|
|
629
|
+
const XAI_RESPONSE_KNOWN_KEYS = new Set([
|
|
630
|
+
"input",
|
|
631
|
+
"tools",
|
|
632
|
+
"toolChoice",
|
|
633
|
+
"modalities",
|
|
634
|
+
"structured_output",
|
|
635
|
+
"include",
|
|
636
|
+
"instructions",
|
|
637
|
+
"logprobs",
|
|
638
|
+
"max_output_tokens",
|
|
639
|
+
"metadata",
|
|
640
|
+
"parallel_tool_calls",
|
|
641
|
+
"previous_response_id",
|
|
642
|
+
"reasoning",
|
|
643
|
+
"search_parameters",
|
|
644
|
+
"service_tier",
|
|
645
|
+
"store",
|
|
646
|
+
"temperature",
|
|
647
|
+
"top_logprobs",
|
|
648
|
+
"top_p",
|
|
649
|
+
"user"
|
|
650
|
+
]);
|
|
651
|
+
function mapToXAIResponsesRequest(args) {
|
|
652
|
+
try {
|
|
653
|
+
const o = args.options;
|
|
654
|
+
const raw = o;
|
|
655
|
+
const input = typeof o.input === "string" ? [{
|
|
656
|
+
role: "user",
|
|
657
|
+
content: [{
|
|
658
|
+
type: "input_text",
|
|
659
|
+
text: o.input
|
|
660
|
+
}]
|
|
661
|
+
}] : o.input.flatMap((item) => {
|
|
662
|
+
if (typeof item === "string") return [{
|
|
663
|
+
role: "user",
|
|
664
|
+
content: [{
|
|
665
|
+
type: "input_text",
|
|
666
|
+
text: item
|
|
667
|
+
}]
|
|
668
|
+
}];
|
|
669
|
+
if (item.type === "message") {
|
|
670
|
+
const role = typeof item.role === "string" && (item.role === "system" || item.role === "user" || item.role === "assistant" || item.role === "developer") ? item.role : "user";
|
|
671
|
+
const isAssistant = role === "assistant";
|
|
672
|
+
return [{
|
|
673
|
+
role,
|
|
674
|
+
content: typeof item.content === "string" ? [isAssistant ? {
|
|
675
|
+
type: "output_text",
|
|
676
|
+
text: item.content
|
|
677
|
+
} : {
|
|
678
|
+
type: "input_text",
|
|
679
|
+
text: item.content
|
|
680
|
+
}] : item.content.map((part) => {
|
|
681
|
+
if (part.type === "text") return isAssistant ? {
|
|
682
|
+
type: "output_text",
|
|
683
|
+
text: part.text
|
|
684
|
+
} : {
|
|
685
|
+
type: "input_text",
|
|
686
|
+
text: part.text
|
|
687
|
+
};
|
|
688
|
+
if (isAssistant) throw BetterAgentError.fromCode("VALIDATION_FAILED", "Assistant messages do not support image or file inputs", { context: {
|
|
689
|
+
provider: "xai",
|
|
690
|
+
model: args.modelId
|
|
691
|
+
} }).at({ at: "xai.responses.map.input.assistantImage" });
|
|
692
|
+
if (part.type === "file") {
|
|
693
|
+
if (part.source.kind !== "provider-file") throw BetterAgentError.fromCode("VALIDATION_FAILED", "xAI file inputs currently require a provider-file source", { context: {
|
|
694
|
+
provider: "xai",
|
|
695
|
+
model: args.modelId,
|
|
696
|
+
sourceKind: part.source.kind
|
|
697
|
+
} }).at({ at: "xai.responses.map.input.fileSource" });
|
|
698
|
+
if (part.source.ref.provider !== "xai") throw BetterAgentError.fromCode("VALIDATION_FAILED", "xAI file inputs require a provider-file reference for provider=xai", { context: {
|
|
699
|
+
provider: "xai",
|
|
700
|
+
model: args.modelId,
|
|
701
|
+
sourceProvider: part.source.ref.provider
|
|
702
|
+
} }).at({ at: "xai.responses.map.input.fileProvider" });
|
|
703
|
+
return {
|
|
704
|
+
type: "input_file",
|
|
705
|
+
file_id: part.source.ref.id,
|
|
706
|
+
...part.source.filename != null ? { filename: part.source.filename } : {}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
if (part.type !== "image") throw BetterAgentError.fromCode("VALIDATION_FAILED", `Unsupported message part for xAI Responses: ${part.type}`, { context: {
|
|
710
|
+
provider: "xai",
|
|
711
|
+
model: args.modelId,
|
|
712
|
+
partType: part.type
|
|
713
|
+
} }).at({ at: "xai.responses.map.input.unsupportedPart" });
|
|
714
|
+
return {
|
|
715
|
+
type: "input_image",
|
|
716
|
+
detail: "auto",
|
|
717
|
+
image_url: part.source.kind === "url" ? part.source.url : `data:${part.source.mimeType};base64,${part.source.data}`
|
|
718
|
+
};
|
|
719
|
+
})
|
|
720
|
+
}];
|
|
721
|
+
}
|
|
722
|
+
const callArguments = "arguments" in item && typeof item.arguments === "string" ? item.arguments : "{}";
|
|
723
|
+
const call = {
|
|
724
|
+
type: "function_call",
|
|
725
|
+
call_id: item.callId,
|
|
726
|
+
name: item.name,
|
|
727
|
+
arguments: callArguments
|
|
728
|
+
};
|
|
729
|
+
if (item.result == null) return [call];
|
|
730
|
+
return [call, {
|
|
731
|
+
type: "function_call_output",
|
|
732
|
+
call_id: item.callId,
|
|
733
|
+
output: typeof item.result === "string" ? item.result : JSON.stringify(item.result),
|
|
734
|
+
status: item.isError === true ? "incomplete" : "completed"
|
|
735
|
+
}];
|
|
736
|
+
});
|
|
737
|
+
let text;
|
|
738
|
+
if ("structured_output" in o && o.structured_output) {
|
|
739
|
+
const out = o.structured_output;
|
|
740
|
+
const { name, strict = true } = out;
|
|
741
|
+
if (out.schema.type !== "object") return err(BetterAgentError.fromCode("VALIDATION_FAILED", "Structured output schema must be an object", { context: {
|
|
742
|
+
provider: "xai",
|
|
743
|
+
model: args.modelId
|
|
744
|
+
} }).at({ at: "xai.responses.map.structuredOutput" }));
|
|
745
|
+
text = { format: {
|
|
746
|
+
type: "json_schema",
|
|
747
|
+
name,
|
|
748
|
+
strict,
|
|
749
|
+
schema: out.schema
|
|
750
|
+
} };
|
|
751
|
+
}
|
|
752
|
+
const xaiTools = [];
|
|
753
|
+
const tools = ("tools" in o ? o.tools : []) ?? [];
|
|
754
|
+
if (tools.length) for (const tool of tools) {
|
|
755
|
+
if (isXAINativeToolDefinition(tool)) {
|
|
756
|
+
xaiTools.push(mapXAINativeToolToRequest(tool));
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const callableTool = tool;
|
|
760
|
+
if (!callableTool || !isCallableToolDefinition(callableTool)) continue;
|
|
761
|
+
const parameters = callableTool[TOOL_JSON_SCHEMA];
|
|
762
|
+
if (parameters.type !== "object") return err(BetterAgentError.fromCode("VALIDATION_FAILED", "Tool parameters schema must be an object", { context: {
|
|
763
|
+
provider: "xai",
|
|
764
|
+
model: args.modelId
|
|
765
|
+
} }).at({ at: "xai.responses.map.tools" }));
|
|
766
|
+
xaiTools.push({
|
|
767
|
+
type: "function",
|
|
768
|
+
name: String(callableTool.name ?? ""),
|
|
769
|
+
description: typeof callableTool.description === "string" ? callableTool.description : null,
|
|
770
|
+
parameters,
|
|
771
|
+
strict: typeof callableTool.strict === "boolean" ? callableTool.strict : null
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
let xaiToolChoice;
|
|
775
|
+
if ("tools" in o && o.tools && "toolChoice" in o && o.toolChoice && o.toolChoice.type !== "auto") {
|
|
776
|
+
const tc = o.toolChoice;
|
|
777
|
+
if (tc.type === "none") xaiToolChoice = "none";
|
|
778
|
+
else if (tc.type === "required") xaiToolChoice = "required";
|
|
779
|
+
else if (tc.type === "tool") {
|
|
780
|
+
if (!xaiTools.length) return err(BetterAgentError.fromCode("VALIDATION_FAILED", "toolChoice=tool requires tools[] to be provided", { context: {
|
|
781
|
+
provider: "xai",
|
|
782
|
+
model: args.modelId
|
|
783
|
+
} }).at({ at: "xai.responses.map.toolChoice" }));
|
|
784
|
+
if (!xaiTools.some((t) => typeof t === "object" && t && "type" in t && t.type === "function" && "name" in t && t.name === tc.name)) return err(BetterAgentError.fromCode("VALIDATION_FAILED", `Requested tool not found: ${tc.name}`, { context: {
|
|
785
|
+
provider: "xai",
|
|
786
|
+
model: args.modelId
|
|
787
|
+
} }).at({ at: "xai.responses.map.toolChoice" }));
|
|
788
|
+
xaiToolChoice = {
|
|
789
|
+
type: "function",
|
|
790
|
+
name: tc.name
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const request = {
|
|
795
|
+
...extractPassthroughOptions(o, XAI_RESPONSE_KNOWN_KEYS),
|
|
796
|
+
model: args.modelId,
|
|
797
|
+
input,
|
|
798
|
+
...omitNullish({
|
|
799
|
+
text,
|
|
800
|
+
tools: xaiTools.length ? xaiTools : void 0,
|
|
801
|
+
tool_choice: xaiToolChoice,
|
|
802
|
+
include: o.include,
|
|
803
|
+
instructions: raw.instructions,
|
|
804
|
+
metadata: o.metadata,
|
|
805
|
+
parallel_tool_calls: o.parallel_tool_calls,
|
|
806
|
+
previous_response_id: o.previous_response_id,
|
|
807
|
+
reasoning: o.reasoning,
|
|
808
|
+
search_parameters: o.search_parameters,
|
|
809
|
+
service_tier: o.service_tier,
|
|
810
|
+
store: o.store,
|
|
811
|
+
user: o.user
|
|
812
|
+
})
|
|
813
|
+
};
|
|
814
|
+
const responseOpts = o;
|
|
815
|
+
if (responseOpts.modalities === void 0 || responseOpts.modalities.includes("text")) {
|
|
816
|
+
if (responseOpts.max_output_tokens != null) request.max_output_tokens = responseOpts.max_output_tokens;
|
|
817
|
+
if (responseOpts.logprobs != null) request.logprobs = responseOpts.logprobs;
|
|
818
|
+
if (responseOpts.temperature != null) request.temperature = responseOpts.temperature;
|
|
819
|
+
if (responseOpts.top_logprobs != null) request.top_logprobs = responseOpts.top_logprobs;
|
|
820
|
+
if (responseOpts.top_p != null) request.top_p = responseOpts.top_p;
|
|
821
|
+
}
|
|
822
|
+
return ok(request);
|
|
823
|
+
} catch (e) {
|
|
824
|
+
return err(BetterAgentError.wrap({
|
|
825
|
+
err: e,
|
|
826
|
+
message: "Failed to map xAI Responses request",
|
|
827
|
+
opts: {
|
|
828
|
+
code: "INTERNAL",
|
|
829
|
+
context: {
|
|
830
|
+
provider: "xai",
|
|
831
|
+
model: args.modelId
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}).at({ at: "xai.responses.mapToRequest" }));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function mapFromXAIResponsesResponse(response) {
|
|
838
|
+
const outputItems = [];
|
|
839
|
+
for (const item of response.output ?? []) {
|
|
840
|
+
if (!item || typeof item !== "object" || !("type" in item)) continue;
|
|
841
|
+
if (item.type === "message") {
|
|
842
|
+
const parts = Array.isArray(item.content) ? item.content.flatMap((part) => {
|
|
843
|
+
if (!part || typeof part !== "object" || !("type" in part)) return [];
|
|
844
|
+
const candidate = part;
|
|
845
|
+
if (candidate.type === "output_text" && typeof candidate.text === "string") return [{
|
|
846
|
+
type: "text",
|
|
847
|
+
text: candidate.text
|
|
848
|
+
}];
|
|
849
|
+
return [];
|
|
850
|
+
}) : [];
|
|
851
|
+
if (parts.length > 0) outputItems.push({
|
|
852
|
+
type: "message",
|
|
853
|
+
role: item.role ?? "assistant",
|
|
854
|
+
content: parts
|
|
855
|
+
});
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
if (item.type === "function_call" && typeof item.name === "string" && typeof item.call_id === "string") {
|
|
859
|
+
outputItems.push({
|
|
860
|
+
type: "tool-call",
|
|
861
|
+
name: item.name,
|
|
862
|
+
arguments: typeof item.arguments === "string" ? item.arguments : "{}",
|
|
863
|
+
callId: item.call_id
|
|
864
|
+
});
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
if (typeof item.type === "string" && item.type.endsWith("_call")) {
|
|
868
|
+
if (!("id" in item) || !(typeof item.id === "string" && item.id.length > 0)) continue;
|
|
869
|
+
outputItems.push({
|
|
870
|
+
type: "provider-tool-result",
|
|
871
|
+
name: item.type.slice(0, -5),
|
|
872
|
+
callId: item.id,
|
|
873
|
+
result: item
|
|
874
|
+
});
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (typeof item.type === "string" && item.type.endsWith("_call_output")) {
|
|
878
|
+
if (!("call_id" in item) || typeof item.call_id !== "string" || item.call_id.length === 0) continue;
|
|
879
|
+
outputItems.push({
|
|
880
|
+
type: "provider-tool-result",
|
|
881
|
+
name: item.type.slice(0, -12),
|
|
882
|
+
callId: item.call_id,
|
|
883
|
+
result: item
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
const inputTokens = response.usage?.input_tokens;
|
|
888
|
+
const outputTokens = response.usage?.output_tokens;
|
|
889
|
+
const usage = omitNullish({
|
|
890
|
+
inputTokens,
|
|
891
|
+
outputTokens,
|
|
892
|
+
totalTokens: typeof inputTokens === "number" && typeof outputTokens === "number" ? inputTokens + outputTokens : void 0,
|
|
893
|
+
reasoningTokens: response.usage?.output_tokens_details?.reasoning_tokens,
|
|
894
|
+
cachedInputTokens: response.usage?.input_tokens_details?.cached_tokens
|
|
895
|
+
});
|
|
896
|
+
const hasToolCalls = outputItems.some((item) => item.type === "tool-call" && "arguments" in item);
|
|
897
|
+
let finishReason;
|
|
898
|
+
const incompleteReason = response.incomplete_details?.reason;
|
|
899
|
+
if (incompleteReason === "max_output_tokens") finishReason = "length";
|
|
900
|
+
else if (incompleteReason === "content_filter") finishReason = "content-filter";
|
|
901
|
+
else if (incompleteReason) finishReason = "other";
|
|
902
|
+
else if (hasToolCalls) finishReason = "tool-calls";
|
|
903
|
+
else finishReason = "stop";
|
|
904
|
+
return {
|
|
905
|
+
output: outputItems,
|
|
906
|
+
finishReason,
|
|
907
|
+
usage,
|
|
908
|
+
response: { body: response }
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
function mapFromXAIResponsesStreamEvent(event, messageId) {
|
|
912
|
+
if (event.type === "response.reasoning_summary_part.added") return ok({
|
|
913
|
+
kind: "event",
|
|
914
|
+
event: {
|
|
915
|
+
type: Events.REASONING_MESSAGE_START,
|
|
916
|
+
messageId,
|
|
917
|
+
role: "assistant",
|
|
918
|
+
visibility: "summary",
|
|
919
|
+
provider: "xai",
|
|
920
|
+
timestamp: Date.now()
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
if (event.type === "response.reasoning_summary_text.delta") return ok({
|
|
924
|
+
kind: "event",
|
|
925
|
+
event: {
|
|
926
|
+
type: Events.REASONING_MESSAGE_CONTENT,
|
|
927
|
+
messageId,
|
|
928
|
+
visibility: "summary",
|
|
929
|
+
delta: event.delta ?? "",
|
|
930
|
+
provider: "xai",
|
|
931
|
+
timestamp: Date.now()
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
if (event.type === "response.reasoning_summary_text.done") return ok({
|
|
935
|
+
kind: "event",
|
|
936
|
+
event: {
|
|
937
|
+
type: Events.REASONING_MESSAGE_END,
|
|
938
|
+
messageId,
|
|
939
|
+
visibility: "summary",
|
|
940
|
+
provider: "xai",
|
|
941
|
+
timestamp: Date.now()
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
if (event.type === "response.reasoning_text.delta") return ok({
|
|
945
|
+
kind: "event",
|
|
946
|
+
event: {
|
|
947
|
+
type: Events.REASONING_MESSAGE_CONTENT,
|
|
948
|
+
messageId,
|
|
949
|
+
visibility: "full",
|
|
950
|
+
delta: event.delta ?? "",
|
|
951
|
+
provider: "xai",
|
|
952
|
+
timestamp: Date.now()
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
if (event.type === "response.reasoning_text.done") return ok({
|
|
956
|
+
kind: "event",
|
|
957
|
+
event: {
|
|
958
|
+
type: Events.REASONING_MESSAGE_END,
|
|
959
|
+
messageId,
|
|
960
|
+
visibility: "full",
|
|
961
|
+
provider: "xai",
|
|
962
|
+
timestamp: Date.now()
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
if (event.type === "response.output_item.added") {
|
|
966
|
+
if (event.item?.type === "message") return ok({
|
|
967
|
+
kind: "event",
|
|
968
|
+
event: {
|
|
969
|
+
type: Events.TEXT_MESSAGE_START,
|
|
970
|
+
messageId,
|
|
971
|
+
role: "assistant",
|
|
972
|
+
timestamp: Date.now()
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
if (event.item?.type === "function_call") {
|
|
976
|
+
const toolCallId = typeof event.item.call_id === "string" && event.item.call_id.length > 0 ? event.item.call_id : event.item.id;
|
|
977
|
+
if (!(typeof toolCallId === "string" && toolCallId.length > 0)) return ok(null);
|
|
978
|
+
return ok({
|
|
979
|
+
kind: "event",
|
|
980
|
+
event: {
|
|
981
|
+
type: Events.TOOL_CALL_START,
|
|
982
|
+
toolCallId,
|
|
983
|
+
toolCallName: event.item.name ?? "",
|
|
984
|
+
parentMessageId: messageId,
|
|
985
|
+
timestamp: Date.now()
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
if (typeof event.item?.type === "string" && event.item.type.endsWith("_call") && event.item.type !== "function_call" && typeof event.item.id === "string" && event.item.id.length > 0) return ok({
|
|
990
|
+
kind: "event",
|
|
991
|
+
event: {
|
|
992
|
+
type: Events.TOOL_CALL_START,
|
|
993
|
+
toolCallId: event.item.id,
|
|
994
|
+
toolCallName: event.item.type.slice(0, -5),
|
|
995
|
+
parentMessageId: messageId,
|
|
996
|
+
timestamp: Date.now()
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
if (event.type === "response.output_text.delta") return ok({
|
|
1001
|
+
kind: "event",
|
|
1002
|
+
event: {
|
|
1003
|
+
type: Events.TEXT_MESSAGE_CONTENT,
|
|
1004
|
+
messageId,
|
|
1005
|
+
delta: event.delta ?? "",
|
|
1006
|
+
timestamp: Date.now()
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
if (event.type === "response.output_text.done") return ok({
|
|
1010
|
+
kind: "event",
|
|
1011
|
+
event: {
|
|
1012
|
+
type: Events.TEXT_MESSAGE_END,
|
|
1013
|
+
messageId,
|
|
1014
|
+
timestamp: Date.now()
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
if (event.type === "response.function_call_arguments.delta") {
|
|
1018
|
+
const toolCallId = typeof event.call_id === "string" && event.call_id.length > 0 ? event.call_id : event.item_id;
|
|
1019
|
+
if (!(typeof toolCallId === "string" && toolCallId.length > 0)) return ok(null);
|
|
1020
|
+
return ok({
|
|
1021
|
+
kind: "event",
|
|
1022
|
+
event: {
|
|
1023
|
+
type: Events.TOOL_CALL_ARGS,
|
|
1024
|
+
parentMessageId: messageId,
|
|
1025
|
+
toolCallId,
|
|
1026
|
+
toolCallName: "unknown",
|
|
1027
|
+
delta: event.delta ?? "",
|
|
1028
|
+
timestamp: Date.now()
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
if (event.type === "response.function_call_arguments.done") {
|
|
1033
|
+
const toolCallId = typeof event.call_id === "string" && event.call_id.length > 0 ? event.call_id : event.item_id;
|
|
1034
|
+
if (!(typeof toolCallId === "string" && toolCallId.length > 0)) return ok(null);
|
|
1035
|
+
return ok({
|
|
1036
|
+
kind: "event",
|
|
1037
|
+
event: {
|
|
1038
|
+
type: Events.TOOL_CALL_END,
|
|
1039
|
+
parentMessageId: messageId,
|
|
1040
|
+
toolCallId,
|
|
1041
|
+
toolCallName: "unknown",
|
|
1042
|
+
timestamp: Date.now()
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
if (event.type === "response.web_search_call.in_progress" || event.type === "response.web_search_call.searching" || event.type === "response.web_search_call.completed" || event.type === "response.x_search_call.in_progress" || event.type === "response.x_search_call.searching" || event.type === "response.x_search_call.completed" || event.type === "response.file_search_call.in_progress" || event.type === "response.file_search_call.searching" || event.type === "response.file_search_call.completed" || event.type === "response.code_interpreter_call.in_progress" || event.type === "response.code_interpreter_call.interpreting" || event.type === "response.code_interpreter_call.completed" || event.type === "response.code_execution_call.in_progress" || event.type === "response.code_execution_call.interpreting" || event.type === "response.code_execution_call.completed" || event.type === "response.mcp_call.in_progress" || event.type === "response.mcp_call.completed" || event.type === "response.mcp_call.failed" || event.type === "response.mcp_list_tools.in_progress" || event.type === "response.mcp_list_tools.completed" || event.type === "response.mcp_list_tools.failed") {
|
|
1047
|
+
const fullType = event.type.slice(9);
|
|
1048
|
+
const dotIndex = fullType.lastIndexOf(".");
|
|
1049
|
+
const toolSegment = dotIndex >= 0 ? fullType.slice(0, dotIndex) : fullType;
|
|
1050
|
+
const status = dotIndex >= 0 ? fullType.slice(dotIndex + 1) : "unknown";
|
|
1051
|
+
const tool = toolSegment.endsWith("_call") ? toolSegment.slice(0, -5) : toolSegment;
|
|
1052
|
+
return ok({
|
|
1053
|
+
kind: "event",
|
|
1054
|
+
event: {
|
|
1055
|
+
type: Events.DATA_PART,
|
|
1056
|
+
...typeof event.item_id === "string" ? { id: event.item_id } : {},
|
|
1057
|
+
data: {
|
|
1058
|
+
endpoint: "responses",
|
|
1059
|
+
tool,
|
|
1060
|
+
status,
|
|
1061
|
+
raw: event
|
|
1062
|
+
},
|
|
1063
|
+
timestamp: Date.now()
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
if (event.type === "response.output_item.done") {
|
|
1068
|
+
if (typeof event.item?.type === "string" && event.item.type.endsWith("_call") && event.item.type !== "function_call" && typeof event.item.id === "string" && event.item.id.length > 0) return ok({
|
|
1069
|
+
kind: "event",
|
|
1070
|
+
event: {
|
|
1071
|
+
type: Events.TOOL_CALL_RESULT,
|
|
1072
|
+
parentMessageId: messageId,
|
|
1073
|
+
toolCallId: event.item.id,
|
|
1074
|
+
toolCallName: event.item.type.slice(0, -5),
|
|
1075
|
+
result: event.item,
|
|
1076
|
+
timestamp: Date.now()
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
if (event.type === "response.completed") return ok({
|
|
1081
|
+
kind: "final",
|
|
1082
|
+
response: mapFromXAIResponsesResponse(event.response)
|
|
1083
|
+
});
|
|
1084
|
+
if (event.type === "error") return err(BetterAgentError.fromCode("UPSTREAM_FAILED", event.message ?? "xAI streaming error", { context: {
|
|
1085
|
+
provider: "xai",
|
|
1086
|
+
upstreamCode: "STREAM_ERROR",
|
|
1087
|
+
raw: event
|
|
1088
|
+
} }).at({ at: "xai.responses.stream.event" }));
|
|
1089
|
+
return ok(null);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
//#endregion
|
|
1093
|
+
//#region src/xai/responses/model.ts
|
|
1094
|
+
const XAI_RESPONSE_CAPS = {
|
|
1095
|
+
inputModalities: {
|
|
1096
|
+
text: true,
|
|
1097
|
+
image: true,
|
|
1098
|
+
file: true
|
|
1099
|
+
},
|
|
1100
|
+
inputShape: "chat",
|
|
1101
|
+
replayMode: "multi_turn",
|
|
1102
|
+
supportsInstruction: true,
|
|
1103
|
+
outputModalities: { text: { options: {} } },
|
|
1104
|
+
tools: true,
|
|
1105
|
+
structured_output: true,
|
|
1106
|
+
additionalSupportedRoles: ["developer"]
|
|
1107
|
+
};
|
|
1108
|
+
const createDeferred = () => {
|
|
1109
|
+
let resolve;
|
|
1110
|
+
let reject;
|
|
1111
|
+
return {
|
|
1112
|
+
promise: new Promise((res, rej) => {
|
|
1113
|
+
resolve = res;
|
|
1114
|
+
reject = rej;
|
|
1115
|
+
}),
|
|
1116
|
+
resolve,
|
|
1117
|
+
reject
|
|
1118
|
+
};
|
|
1119
|
+
};
|
|
1120
|
+
const createXAIResponsesModel = (modelId, client) => {
|
|
1121
|
+
const doGenerate = async (options, ctx) => {
|
|
1122
|
+
const requestBodyResult = mapToXAIResponsesRequest({
|
|
1123
|
+
modelId,
|
|
1124
|
+
options
|
|
1125
|
+
});
|
|
1126
|
+
if (requestBodyResult.isErr()) return err(requestBodyResult.error.at({ at: "xai.generate.mapRequest" }));
|
|
1127
|
+
const raw = await client.responses.create(requestBodyResult.value, { signal: ctx.signal ?? null });
|
|
1128
|
+
if (raw.isErr()) return err(raw.error.at({ at: "xai.generate.http" }));
|
|
1129
|
+
return ok({ response: {
|
|
1130
|
+
...mapFromXAIResponsesResponse(raw.value),
|
|
1131
|
+
request: { body: requestBodyResult.value }
|
|
1132
|
+
} });
|
|
1133
|
+
};
|
|
1134
|
+
const doGenerateStream = async (options, ctx) => {
|
|
1135
|
+
const requestBodyResult = mapToXAIResponsesRequest({
|
|
1136
|
+
modelId,
|
|
1137
|
+
options
|
|
1138
|
+
});
|
|
1139
|
+
if (requestBodyResult.isErr()) return err(requestBodyResult.error.at({ at: "xai.generateStream.mapRequest" }));
|
|
1140
|
+
const streamResult = await client.responses.stream(requestBodyResult.value, { signal: ctx.signal ?? null });
|
|
1141
|
+
if (streamResult.isErr()) return err(streamResult.error.at({ at: "xai.generateStream.http" }));
|
|
1142
|
+
const { promise: final, resolve: resolveFinal, reject: rejectFinal } = createDeferred();
|
|
1143
|
+
return ok({
|
|
1144
|
+
events: (async function* () {
|
|
1145
|
+
const messageId = ctx.generateMessageId();
|
|
1146
|
+
let sawFinal = false;
|
|
1147
|
+
try {
|
|
1148
|
+
for await (const raw of streamResult.value) {
|
|
1149
|
+
if (raw.isErr()) {
|
|
1150
|
+
rejectFinal(raw.error);
|
|
1151
|
+
yield err(raw.error);
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const mapped = mapFromXAIResponsesStreamEvent(raw.value, messageId);
|
|
1155
|
+
if (mapped.isErr()) {
|
|
1156
|
+
const error = mapped.error.at({ at: "xai.generateStream.mapEvent" });
|
|
1157
|
+
rejectFinal(error);
|
|
1158
|
+
yield err(error);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const value = mapped.value;
|
|
1162
|
+
if (!value) continue;
|
|
1163
|
+
if (value.kind === "final") {
|
|
1164
|
+
sawFinal = true;
|
|
1165
|
+
resolveFinal(value.response);
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
yield ok(value.event);
|
|
1169
|
+
}
|
|
1170
|
+
} finally {
|
|
1171
|
+
if (!sawFinal) rejectFinal(BetterAgentError.fromCode("UPSTREAM_FAILED", "xAI stream ended without a final response event.", { context: {
|
|
1172
|
+
provider: "xai",
|
|
1173
|
+
model: String(modelId)
|
|
1174
|
+
} }).at({ at: "xai.generateStream.final" }));
|
|
1175
|
+
}
|
|
1176
|
+
})(),
|
|
1177
|
+
final
|
|
1178
|
+
});
|
|
1179
|
+
};
|
|
1180
|
+
return {
|
|
1181
|
+
providerId: "xai",
|
|
1182
|
+
modelId,
|
|
1183
|
+
caps: XAI_RESPONSE_CAPS,
|
|
1184
|
+
doGenerate,
|
|
1185
|
+
doGenerateStream
|
|
1186
|
+
};
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/xai/shared/schemas.ts
|
|
1191
|
+
const XAI_RESPONSE_MODEL_NAMES = [
|
|
1192
|
+
"grok-3",
|
|
1193
|
+
"grok-3-latest",
|
|
1194
|
+
"grok-3-beta",
|
|
1195
|
+
"grok-3-fast",
|
|
1196
|
+
"grok-3-fast-latest",
|
|
1197
|
+
"grok-3-fast-beta",
|
|
1198
|
+
"grok-3-mini",
|
|
1199
|
+
"grok-4",
|
|
1200
|
+
"grok-4-1-fast-reasoning",
|
|
1201
|
+
"grok-4.20-beta-latest-non-reasoning",
|
|
1202
|
+
"grok-4.20-multi-agent-beta-0309",
|
|
1203
|
+
"grok-code-fast-1"
|
|
1204
|
+
];
|
|
1205
|
+
const XAI_IMAGE_MODEL_NAMES = ["grok-imagine-image"];
|
|
1206
|
+
const XAIResponseModels = z.enum(XAI_RESPONSE_MODEL_NAMES).describe("Current documented xAI response model names. For the latest team-specific availability, also check <https://console.x.ai/team/default/models> and <https://docs.x.ai/docs/models>.");
|
|
1207
|
+
const XAIImageModels = z.enum(XAI_IMAGE_MODEL_NAMES).describe("Current documented xAI image model names. For the latest team-specific availability, also check <https://console.x.ai/team/default/models> and <https://docs.x.ai/docs/models>.");
|
|
1208
|
+
const XAI_MODEL_KINDS = {
|
|
1209
|
+
"grok-3": "text",
|
|
1210
|
+
"grok-3-latest": "text",
|
|
1211
|
+
"grok-3-beta": "text",
|
|
1212
|
+
"grok-3-fast": "text",
|
|
1213
|
+
"grok-3-fast-latest": "text",
|
|
1214
|
+
"grok-3-fast-beta": "text",
|
|
1215
|
+
"grok-3-mini": "text",
|
|
1216
|
+
"grok-4": "text",
|
|
1217
|
+
"grok-4-1-fast-reasoning": "text",
|
|
1218
|
+
"grok-4.20-beta-latest-non-reasoning": "text",
|
|
1219
|
+
"grok-4.20-multi-agent-beta-0309": "text",
|
|
1220
|
+
"grok-code-fast-1": "text",
|
|
1221
|
+
"grok-imagine-image": "image"
|
|
1222
|
+
};
|
|
1223
|
+
const getXAIModelKind = (modelId) => XAI_MODEL_KINDS[modelId];
|
|
1224
|
+
const XAIFileObjectSchema = z.object({
|
|
1225
|
+
id: z.string().describe("Unique file identifier."),
|
|
1226
|
+
object: z.string().optional().describe("Object type, typically `file`."),
|
|
1227
|
+
bytes: z.number().int().nonnegative().optional().describe("File size in bytes."),
|
|
1228
|
+
created_at: z.number().int().optional().describe("Unix timestamp when the file was created."),
|
|
1229
|
+
expires_at: z.number().int().nullish().describe("Optional expiration time."),
|
|
1230
|
+
filename: z.string().optional().describe("Original file name."),
|
|
1231
|
+
purpose: z.string().optional().describe("Declared file purpose."),
|
|
1232
|
+
status: z.string().optional().describe("Provider-side processing status."),
|
|
1233
|
+
status_details: z.any().optional().describe("Provider-side status details.")
|
|
1234
|
+
}).passthrough().describe("xAI file object.");
|
|
1235
|
+
z.object({
|
|
1236
|
+
object: z.string().optional().describe("List object type."),
|
|
1237
|
+
data: z.array(XAIFileObjectSchema).describe("Files returned by the provider."),
|
|
1238
|
+
first_id: z.string().nullish().optional().describe("First file id in the page."),
|
|
1239
|
+
last_id: z.string().nullish().optional().describe("Last file id in the page."),
|
|
1240
|
+
has_more: z.boolean().optional().describe("Whether additional pages are available.")
|
|
1241
|
+
}).passthrough().describe("xAI files list response.");
|
|
1242
|
+
z.object({
|
|
1243
|
+
id: z.string().describe("Deleted file identifier."),
|
|
1244
|
+
object: z.string().optional().describe("Delete response object type."),
|
|
1245
|
+
deleted: z.boolean().describe("Whether the file was deleted.")
|
|
1246
|
+
}).passthrough().describe("xAI delete file response.");
|
|
1247
|
+
|
|
1248
|
+
//#endregion
|
|
1249
|
+
//#region src/xai/models/index.ts
|
|
1250
|
+
function createXAIModel(modelId, client) {
|
|
1251
|
+
if (getXAIModelKind(modelId) === "image") return createXAIImagesModel(modelId, client);
|
|
1252
|
+
return createXAIResponsesModel(modelId, client);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/xai/provider.ts
|
|
1257
|
+
const createXAI = (config) => {
|
|
1258
|
+
const httpClient = createXAIClient(config);
|
|
1259
|
+
return {
|
|
1260
|
+
id: "xai",
|
|
1261
|
+
tools: createXAINativeToolBuilders(),
|
|
1262
|
+
files: httpClient.files,
|
|
1263
|
+
model(modelId) {
|
|
1264
|
+
return createXAIModel(modelId, httpClient);
|
|
1265
|
+
},
|
|
1266
|
+
text(modelId) {
|
|
1267
|
+
return createXAIResponsesModel(modelId, httpClient);
|
|
1268
|
+
},
|
|
1269
|
+
image(modelId) {
|
|
1270
|
+
return createXAIImagesModel(modelId, httpClient);
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
//#endregion
|
|
1276
|
+
export { createXAI };
|
|
1277
|
+
//# sourceMappingURL=index.mjs.map
|