@gullabs/xai 0.2.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/LICENSE +191 -0
- package/README.md +115 -0
- package/dist/index.cjs +503 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +433 -0
- package/dist/index.d.ts +433 -0
- package/dist/index.js +489 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@gullabs/core');
|
|
4
|
+
var zod = require('zod');
|
|
5
|
+
|
|
6
|
+
// src/client.ts
|
|
7
|
+
function requireApiKey(auth) {
|
|
8
|
+
if (!("apiKey" in auth) || typeof auth.apiKey !== "string" || auth.apiKey.trim() === "") {
|
|
9
|
+
throw new core.LlmError("@gullabs/xai requires auth.apiKey", {
|
|
10
|
+
kind: "invalid_auth",
|
|
11
|
+
retryable: false,
|
|
12
|
+
provider: "xai"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return auth.apiKey;
|
|
16
|
+
}
|
|
17
|
+
async function buildXaiClient(auth) {
|
|
18
|
+
const apiKey = requireApiKey(auth);
|
|
19
|
+
const { default: OpenAI } = await import('openai');
|
|
20
|
+
const client = new OpenAI({
|
|
21
|
+
apiKey,
|
|
22
|
+
baseURL: "https://api.x.ai/v1",
|
|
23
|
+
maxRetries: 0
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
responses: {
|
|
27
|
+
async create(params, options) {
|
|
28
|
+
return client.responses.create(params, options);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function isPlainRecord(value) {
|
|
34
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
function badXaiRequest(message) {
|
|
37
|
+
return new core.LlmError(message, { kind: "bad_request", retryable: false });
|
|
38
|
+
}
|
|
39
|
+
var ALLOWED_XAI_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/jpg", "image/png"]);
|
|
40
|
+
var MAX_XAI_INLINE_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
41
|
+
function mapPart(p) {
|
|
42
|
+
switch (p.kind) {
|
|
43
|
+
case "text":
|
|
44
|
+
return { type: "input_text", text: p.text };
|
|
45
|
+
case "inline-media": {
|
|
46
|
+
if (!ALLOWED_XAI_IMAGE_MIME_TYPES.has(p.mimeType)) {
|
|
47
|
+
throw badXaiRequest(
|
|
48
|
+
`xAI vision only supports image/jpeg and image/png; got mimeType "${p.mimeType}".`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const byteLength = Buffer.from(p.data, "base64").length;
|
|
52
|
+
if (byteLength > MAX_XAI_INLINE_IMAGE_BYTES) {
|
|
53
|
+
throw badXaiRequest(
|
|
54
|
+
`xAI inline images must be at most 20 MiB; got ${byteLength} bytes.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return { type: "input_image", image_url: `data:${p.mimeType};base64,${p.data}` };
|
|
58
|
+
}
|
|
59
|
+
case "file-uri": {
|
|
60
|
+
const isPublicHttpUrl = p.uri.startsWith("http://") || p.uri.startsWith("https://");
|
|
61
|
+
const isAllowedImageType = ALLOWED_XAI_IMAGE_MIME_TYPES.has(p.mimeType);
|
|
62
|
+
if (!isPublicHttpUrl || !isAllowedImageType) {
|
|
63
|
+
throw badXaiRequest(
|
|
64
|
+
`xAI only accepts public http(s) image URLs via FileUriPart; got scheme of "${p.uri}" / mimeType "${p.mimeType}".`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return { type: "input_image", image_url: p.uri };
|
|
68
|
+
}
|
|
69
|
+
default:
|
|
70
|
+
return core.assertNever(p);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function mapXaiProviderOptions(xaiOpts, model) {
|
|
74
|
+
if (xaiOpts === void 0) {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
if (!isPlainRecord(xaiOpts)) {
|
|
78
|
+
throw badXaiRequest(`providerOptions.xai must be an object for model "${model}".`);
|
|
79
|
+
}
|
|
80
|
+
const unknownKeys = Object.keys(xaiOpts).filter((key) => key !== "promptCacheKey");
|
|
81
|
+
if (unknownKeys.length > 0) {
|
|
82
|
+
throw badXaiRequest(
|
|
83
|
+
`providerOptions.xai contains unsupported keys [${unknownKeys.join(
|
|
84
|
+
", "
|
|
85
|
+
)}] for model "${model}". Allowed keys: promptCacheKey.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const mapped = {};
|
|
89
|
+
if (xaiOpts["promptCacheKey"] !== void 0) {
|
|
90
|
+
if (typeof xaiOpts["promptCacheKey"] !== "string" || xaiOpts["promptCacheKey"].length === 0) {
|
|
91
|
+
throw badXaiRequest(
|
|
92
|
+
`providerOptions.xai.promptCacheKey must be a non-empty string for model "${model}".`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
mapped.promptCacheKey = xaiOpts["promptCacheKey"];
|
|
96
|
+
}
|
|
97
|
+
return mapped;
|
|
98
|
+
}
|
|
99
|
+
function mapFinishReason(response) {
|
|
100
|
+
if (response.status === "completed") {
|
|
101
|
+
return "stop";
|
|
102
|
+
}
|
|
103
|
+
if (response.status === "incomplete" && response.incomplete_details?.reason === "max_output_tokens") {
|
|
104
|
+
return "length";
|
|
105
|
+
}
|
|
106
|
+
return "other";
|
|
107
|
+
}
|
|
108
|
+
var CANONICALLY_MAPPED_USAGE_KEYS = /* @__PURE__ */ new Set([
|
|
109
|
+
"input_tokens",
|
|
110
|
+
"output_tokens",
|
|
111
|
+
"total_tokens"
|
|
112
|
+
]);
|
|
113
|
+
function mapUsage(usage) {
|
|
114
|
+
const inputTokens = usage.input_tokens;
|
|
115
|
+
const outputTokens = usage.output_tokens;
|
|
116
|
+
const cachedInputTokens = usage.input_tokens_details?.cached_tokens;
|
|
117
|
+
const thinkingTokens = usage.output_tokens_details?.reasoning_tokens;
|
|
118
|
+
const totalTokens = usage.total_tokens;
|
|
119
|
+
const details = {
|
|
120
|
+
input: inputTokens,
|
|
121
|
+
output: outputTokens,
|
|
122
|
+
...cachedInputTokens !== void 0 ? { cached: cachedInputTokens } : {},
|
|
123
|
+
...thinkingTokens !== void 0 ? { thinking: thinkingTokens } : {}
|
|
124
|
+
};
|
|
125
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
126
|
+
if (typeof value === "number" && !CANONICALLY_MAPPED_USAGE_KEYS.has(key)) {
|
|
127
|
+
details[key] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const raw = usage;
|
|
131
|
+
const result = {
|
|
132
|
+
inputTokens,
|
|
133
|
+
outputTokens,
|
|
134
|
+
details,
|
|
135
|
+
raw,
|
|
136
|
+
...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
|
|
137
|
+
...thinkingTokens !== void 0 ? { thinkingTokens } : {},
|
|
138
|
+
...totalTokens !== void 0 ? { totalTokens } : {}
|
|
139
|
+
};
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
var XAI_AUTH_ERROR_MESSAGE_PREFIX = "Incorrect API key provided";
|
|
143
|
+
function extractXaiErrorBodyText(rawErr) {
|
|
144
|
+
if (rawErr === null || typeof rawErr !== "object") {
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
const body = rawErr["error"];
|
|
148
|
+
if (typeof body === "string") {
|
|
149
|
+
return body;
|
|
150
|
+
}
|
|
151
|
+
if (isPlainRecord(body)) {
|
|
152
|
+
if (typeof body["error"] === "string") {
|
|
153
|
+
return body["error"];
|
|
154
|
+
}
|
|
155
|
+
if (typeof body["message"] === "string") {
|
|
156
|
+
return body["message"];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
function isXaiAuthFailureBody(rawErr) {
|
|
162
|
+
const text = extractXaiErrorBodyText(rawErr);
|
|
163
|
+
return text !== void 0 && text.startsWith(XAI_AUTH_ERROR_MESSAGE_PREFIX);
|
|
164
|
+
}
|
|
165
|
+
function classifyXaiError(rawErr) {
|
|
166
|
+
if (rawErr instanceof core.LlmError) {
|
|
167
|
+
return rawErr;
|
|
168
|
+
}
|
|
169
|
+
const base = core.classifyError(rawErr);
|
|
170
|
+
if (base.httpStatus === 400 && isXaiAuthFailureBody(rawErr)) {
|
|
171
|
+
return new core.LlmError(base.message, {
|
|
172
|
+
kind: "invalid_auth",
|
|
173
|
+
retryable: false,
|
|
174
|
+
httpStatus: base.httpStatus,
|
|
175
|
+
provider: "xai",
|
|
176
|
+
cause: base.cause ?? rawErr
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return new core.LlmError(base.message, {
|
|
180
|
+
kind: base.kind,
|
|
181
|
+
retryable: base.retryable,
|
|
182
|
+
...base.httpStatus !== void 0 ? { httpStatus: base.httpStatus } : {},
|
|
183
|
+
...base.retryAfterMs !== void 0 ? { retryAfterMs: base.retryAfterMs } : {},
|
|
184
|
+
provider: "xai",
|
|
185
|
+
cause: base.cause ?? rawErr
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function xaiAdapter(opts) {
|
|
189
|
+
return {
|
|
190
|
+
id: "xai",
|
|
191
|
+
async run(req, ctx) {
|
|
192
|
+
if (req.provider !== "xai") {
|
|
193
|
+
throw new core.LlmError(
|
|
194
|
+
`xaiAdapter received a request for provider "${req.provider}", expected "xai".`,
|
|
195
|
+
{ kind: "bad_request", retryable: false }
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
const warnings = [];
|
|
199
|
+
const model = req.model;
|
|
200
|
+
const genConfig = req.config;
|
|
201
|
+
const input = req.messages.map((msg) => ({
|
|
202
|
+
role: msg.role === "assistant" ? "assistant" : "user",
|
|
203
|
+
content: msg.parts.map(mapPart)
|
|
204
|
+
}));
|
|
205
|
+
const params = {
|
|
206
|
+
model,
|
|
207
|
+
input,
|
|
208
|
+
store: false
|
|
209
|
+
};
|
|
210
|
+
if (req.system !== void 0) {
|
|
211
|
+
params.instructions = req.system;
|
|
212
|
+
}
|
|
213
|
+
if (genConfig.temperature !== void 0) {
|
|
214
|
+
params.temperature = genConfig.temperature;
|
|
215
|
+
}
|
|
216
|
+
if (genConfig.topP !== void 0) {
|
|
217
|
+
params.top_p = genConfig.topP;
|
|
218
|
+
}
|
|
219
|
+
if (genConfig.maxOutputTokens !== void 0) {
|
|
220
|
+
params.max_output_tokens = genConfig.maxOutputTokens;
|
|
221
|
+
}
|
|
222
|
+
if (genConfig.serviceTier !== void 0) {
|
|
223
|
+
throw badXaiRequest(
|
|
224
|
+
`serviceTier is not supported for xai models (got "${genConfig.serviceTier}").`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const reasoning = genConfig.reasoning;
|
|
228
|
+
if (reasoning !== void 0) {
|
|
229
|
+
if (reasoning.budgetTokens !== void 0) {
|
|
230
|
+
throw badXaiRequest(
|
|
231
|
+
`reasoning.budgetTokens is not supported for model "${model}" (xAI uses effort-level reasoning, not token budgets); use reasoning.effort instead.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (reasoning.effort !== void 0) {
|
|
235
|
+
const effort = reasoning.effort;
|
|
236
|
+
if (effort !== "low" && effort !== "high") {
|
|
237
|
+
throw badXaiRequest(
|
|
238
|
+
`reasoning.effort "${effort}" is not supported for xai model "${model}" (only "low" and "high" are admitted).`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const admitted = req.modelDescriptor?.capabilities?.admittedReasoningEfforts;
|
|
242
|
+
if (admitted !== void 0 && !admitted.includes(effort)) {
|
|
243
|
+
throw badXaiRequest(
|
|
244
|
+
`reasoning.effort "${effort}" is not supported for xai model "${model}".`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
params.reasoning = { effort };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const structuredOutputRequested = req.outputJsonSchema !== void 0;
|
|
251
|
+
if (structuredOutputRequested) {
|
|
252
|
+
const schema = req.outputJsonSchema;
|
|
253
|
+
const name = isPlainRecord(schema) && typeof schema["title"] === "string" && schema["title"].length > 0 ? schema["title"] : "structured_output";
|
|
254
|
+
params.text = {
|
|
255
|
+
format: { type: "json_schema", name, schema, strict: true }
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const xaiProviderConfig = mapXaiProviderOptions(
|
|
259
|
+
genConfig.providerOptions?.["xai"],
|
|
260
|
+
model
|
|
261
|
+
);
|
|
262
|
+
if (xaiProviderConfig.promptCacheKey !== void 0) {
|
|
263
|
+
params.prompt_cache_key = xaiProviderConfig.promptCacheKey;
|
|
264
|
+
}
|
|
265
|
+
let response;
|
|
266
|
+
try {
|
|
267
|
+
const buildClient = opts?._clientFactory ?? buildXaiClient;
|
|
268
|
+
const client = opts?.client !== void 0 ? opts.client : await buildClient(ctx.auth);
|
|
269
|
+
ctx.logger.debug(
|
|
270
|
+
{ model, configKeys: Object.keys(params) },
|
|
271
|
+
"llm.adapter.dispatch"
|
|
272
|
+
);
|
|
273
|
+
response = await client.responses.create(
|
|
274
|
+
params,
|
|
275
|
+
ctx.signal !== void 0 ? { signal: ctx.signal } : void 0
|
|
276
|
+
);
|
|
277
|
+
} catch (rawErr) {
|
|
278
|
+
throw classifyXaiError(rawErr);
|
|
279
|
+
}
|
|
280
|
+
let text = "";
|
|
281
|
+
let reasoningText;
|
|
282
|
+
for (const item of response.output) {
|
|
283
|
+
if (item.type === "message") {
|
|
284
|
+
text += item.content.map((part) => part.text).join("");
|
|
285
|
+
} else {
|
|
286
|
+
const joined = item.summary.map((s) => s.text).join("");
|
|
287
|
+
if (joined.length > 0) {
|
|
288
|
+
reasoningText = (reasoningText ?? "") + joined;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
let rawStructured;
|
|
293
|
+
if (structuredOutputRequested && text.length > 0) {
|
|
294
|
+
try {
|
|
295
|
+
rawStructured = JSON.parse(text);
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const usage = mapUsage(response.usage);
|
|
300
|
+
const finishReason = mapFinishReason(response);
|
|
301
|
+
const providerMeta = {};
|
|
302
|
+
const contextDetails = response.usage["context_details"];
|
|
303
|
+
if (isPlainRecord(contextDetails)) {
|
|
304
|
+
providerMeta["context_details"] = contextDetails;
|
|
305
|
+
}
|
|
306
|
+
if (isPlainRecord(response.metadata)) {
|
|
307
|
+
providerMeta["metadata"] = response.metadata;
|
|
308
|
+
}
|
|
309
|
+
const result = {
|
|
310
|
+
model: response.model,
|
|
311
|
+
usage,
|
|
312
|
+
warnings,
|
|
313
|
+
finishReason,
|
|
314
|
+
responseId: response.id,
|
|
315
|
+
...text.length > 0 ? { text } : {},
|
|
316
|
+
...reasoningText !== void 0 ? { reasoningText } : {},
|
|
317
|
+
...rawStructured !== void 0 ? { rawStructured } : {},
|
|
318
|
+
...Object.keys(providerMeta).length > 0 ? { providerMetadata: providerMeta } : {}
|
|
319
|
+
};
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
var Grok45ConfigSchema = zod.z.strictObject({
|
|
325
|
+
temperature: zod.z.number().optional().meta({
|
|
326
|
+
title: "Temperature",
|
|
327
|
+
description: "Sampling temperature forwarded verbatim to grok-4.5."
|
|
328
|
+
}),
|
|
329
|
+
topP: zod.z.number().optional().meta({
|
|
330
|
+
title: "Top P",
|
|
331
|
+
description: "Nucleus sampling parameter forwarded verbatim to grok-4.5."
|
|
332
|
+
}),
|
|
333
|
+
maxOutputTokens: zod.z.number().int().positive().optional().meta({
|
|
334
|
+
title: "Max Output Tokens",
|
|
335
|
+
description: "Maximum output token cap for grok-4.5. No artificial ceiling \u2014 xAI accepts arbitrarily large values (live-verified); truncation surfaces as finishReason:'length', not an error."
|
|
336
|
+
}),
|
|
337
|
+
reasoning: zod.z.strictObject({
|
|
338
|
+
effort: zod.z.enum(["low", "high"]).meta({
|
|
339
|
+
title: "Reasoning Effort",
|
|
340
|
+
description: 'Reasoning effort for grok-4.5. Only "low" and "high" are admitted (live-verified); "none"/"medium"/"xhigh" are rejected by the live API.'
|
|
341
|
+
})
|
|
342
|
+
}).optional().meta({
|
|
343
|
+
title: "Reasoning",
|
|
344
|
+
description: "grok-4.5 effort-level reasoning configuration. No budgetTokens field \u2014 xAI uses level-style reasoning, not token budgets."
|
|
345
|
+
}),
|
|
346
|
+
timeoutMs: zod.z.number().int().positive().optional().meta({
|
|
347
|
+
title: "Timeout",
|
|
348
|
+
description: "Logical request timeout in milliseconds."
|
|
349
|
+
}),
|
|
350
|
+
providerOptions: zod.z.strictObject({
|
|
351
|
+
xai: zod.z.strictObject({
|
|
352
|
+
promptCacheKey: zod.z.string().min(1).optional().meta({
|
|
353
|
+
title: "Prompt Cache Key",
|
|
354
|
+
description: "xAI conversation-routing cache key \u2014 maps to Responses API `prompt_cache_key`."
|
|
355
|
+
})
|
|
356
|
+
}).optional().meta({
|
|
357
|
+
title: "xAI Provider Options",
|
|
358
|
+
description: "Allowlisted xAI provider options for grok-4.5."
|
|
359
|
+
})
|
|
360
|
+
}).optional().meta({
|
|
361
|
+
title: "Provider Options",
|
|
362
|
+
description: "Provider-specific options accepted for grok-4.5."
|
|
363
|
+
})
|
|
364
|
+
}).meta({
|
|
365
|
+
title: "Grok45Config",
|
|
366
|
+
description: "Strict Responses API config for model grok-4.5. Level reasoning (low/high only), tunable sampling, no service tiers, structured output, vision, priced.",
|
|
367
|
+
examples: [{ reasoning: { effort: "high" } }]
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// src/models.ts
|
|
371
|
+
var grok45ModelDescriptor = {
|
|
372
|
+
model: "grok-4.5",
|
|
373
|
+
provider: "xai",
|
|
374
|
+
pricingFamily: "grok-4.5",
|
|
375
|
+
capabilities: {
|
|
376
|
+
reasoning: true,
|
|
377
|
+
reasoningApi: "level",
|
|
378
|
+
admittedReasoningEfforts: ["low", "high"],
|
|
379
|
+
structuredOutput: true,
|
|
380
|
+
nativeStructuredOutput: true,
|
|
381
|
+
vision: true,
|
|
382
|
+
audioInput: false,
|
|
383
|
+
sampling: "tunable",
|
|
384
|
+
caching: { explicit: false, minTokens: 0 },
|
|
385
|
+
grounding: false
|
|
386
|
+
// No serviceTiers key — xai has no service-tier concept.
|
|
387
|
+
},
|
|
388
|
+
configSchema: Grok45ConfigSchema,
|
|
389
|
+
configJsonSchema: core.toConfigJsonSchema(Grok45ConfigSchema),
|
|
390
|
+
validateConfig: core.zodToStandardSchema(Grok45ConfigSchema)
|
|
391
|
+
};
|
|
392
|
+
var xaiModelDescriptors = [grok45ModelDescriptor];
|
|
393
|
+
var xaiRegistry = core.createModelRegistry(xaiModelDescriptors);
|
|
394
|
+
|
|
395
|
+
// src/pricing.ts
|
|
396
|
+
var xaiPricingVersion = "xai-2026-07-09";
|
|
397
|
+
var XAI_PRICING = Object.freeze({
|
|
398
|
+
// ── grok-4.5 ── $2.00/$6.00 (≤200k), $4.00/$12.00 (>200k); cached $0.50/$1.00
|
|
399
|
+
"grok-4.5": {
|
|
400
|
+
inputPerM: 2e6,
|
|
401
|
+
cachedPerM: 5e5,
|
|
402
|
+
outputPerM: 6e6,
|
|
403
|
+
gt200k: {
|
|
404
|
+
inputPerM: 4e6,
|
|
405
|
+
cachedPerM: 1e6,
|
|
406
|
+
outputPerM: 12e6
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
var LONG_CONTEXT_THRESHOLD = 2e5;
|
|
411
|
+
function lookupRates(model) {
|
|
412
|
+
return XAI_PRICING[model];
|
|
413
|
+
}
|
|
414
|
+
function selectRates(rates, grossInputTokens) {
|
|
415
|
+
if (rates.gt200k !== void 0 && grossInputTokens > LONG_CONTEXT_THRESHOLD) {
|
|
416
|
+
return rates.gt200k;
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
inputPerM: rates.inputPerM,
|
|
420
|
+
cachedPerM: rates.cachedPerM,
|
|
421
|
+
outputPerM: rates.outputPerM
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function computeXaiCost(model, usage, tier) {
|
|
425
|
+
const rates = lookupRates(model);
|
|
426
|
+
if (rates === void 0) {
|
|
427
|
+
return {
|
|
428
|
+
microUsd: null,
|
|
429
|
+
usd: null,
|
|
430
|
+
pricingVersion: xaiPricingVersion,
|
|
431
|
+
confidence: "estimated",
|
|
432
|
+
details: { input: 0, cached: 0, output: 0 },
|
|
433
|
+
unpricedReason: `Unknown model "${model}"; no pricing entry found.`
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
if (tier !== void 0) {
|
|
437
|
+
return {
|
|
438
|
+
microUsd: null,
|
|
439
|
+
usd: null,
|
|
440
|
+
pricingVersion: xaiPricingVersion,
|
|
441
|
+
confidence: "estimated",
|
|
442
|
+
details: { input: 0, cached: 0, output: 0 },
|
|
443
|
+
unpricedReason: `Unknown service tier "${tier}"; xai has no service tiers, refusing to guess a pricing multiplier.`
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const base = selectRates(rates, usage.inputTokens);
|
|
447
|
+
const cached = usage.cachedInputTokens ?? 0;
|
|
448
|
+
const billableInput = Math.max(0, usage.inputTokens - cached);
|
|
449
|
+
const inputCost = Math.round(billableInput * base.inputPerM / 1e6);
|
|
450
|
+
const cachedCost = Math.round(cached * base.cachedPerM / 1e6);
|
|
451
|
+
const outputCost = Math.round(usage.outputTokens * base.outputPerM / 1e6);
|
|
452
|
+
const microUsd = inputCost + cachedCost + outputCost;
|
|
453
|
+
return {
|
|
454
|
+
microUsd,
|
|
455
|
+
usd: microUsd / 1e6,
|
|
456
|
+
pricingVersion: xaiPricingVersion,
|
|
457
|
+
confidence: "exact",
|
|
458
|
+
details: {
|
|
459
|
+
input: inputCost,
|
|
460
|
+
cached: cachedCost,
|
|
461
|
+
output: outputCost
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function xaiPricingSource() {
|
|
466
|
+
return {
|
|
467
|
+
version: xaiPricingVersion,
|
|
468
|
+
price(model, usage, tier) {
|
|
469
|
+
return computeXaiCost(model, usage, tier);
|
|
470
|
+
},
|
|
471
|
+
hasModel(model) {
|
|
472
|
+
return lookupRates(model) !== void 0;
|
|
473
|
+
},
|
|
474
|
+
listModels() {
|
|
475
|
+
return Object.keys(XAI_PRICING);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/provider.ts
|
|
481
|
+
function xaiProvider(opts) {
|
|
482
|
+
return {
|
|
483
|
+
adapter: xaiAdapter(opts),
|
|
484
|
+
modelDescriptors: xaiModelDescriptors,
|
|
485
|
+
pricingSource: xaiPricingSource()
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
exports.Grok45ConfigSchema = Grok45ConfigSchema;
|
|
490
|
+
exports.XAI_PRICING = XAI_PRICING;
|
|
491
|
+
exports.buildXaiClient = buildXaiClient;
|
|
492
|
+
exports.classifyXaiError = classifyXaiError;
|
|
493
|
+
exports.computeXaiCost = computeXaiCost;
|
|
494
|
+
exports.grok45ModelDescriptor = grok45ModelDescriptor;
|
|
495
|
+
exports.requireApiKey = requireApiKey;
|
|
496
|
+
exports.xaiAdapter = xaiAdapter;
|
|
497
|
+
exports.xaiModelDescriptors = xaiModelDescriptors;
|
|
498
|
+
exports.xaiPricingSource = xaiPricingSource;
|
|
499
|
+
exports.xaiPricingVersion = xaiPricingVersion;
|
|
500
|
+
exports.xaiProvider = xaiProvider;
|
|
501
|
+
exports.xaiRegistry = xaiRegistry;
|
|
502
|
+
//# sourceMappingURL=index.cjs.map
|
|
503
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/adapter.ts","../src/model-config/grok-4-5.ts","../src/models.ts","../src/pricing.ts","../src/provider.ts"],"names":["LlmError","assertNever","classifyError","z","toConfigJsonSchema","zodToStandardSchema","createModelRegistry"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Structural XaiClientLike interface + buildXaiClient factory.\n *\n * This module defines the structural interface the adapter depends on.\n * The real `openai` SDK is imported ONLY in buildXaiClient so tests can\n * inject a fake without pulling in the real SDK. This is the ONLY file in\n * `packages/xai/src` that imports `openai`.\n *\n * @module\n */\n\nimport { LlmError } from '@gullabs/core'\nimport type { AuthMaterial } from '@gullabs/core'\n\n// ---------------------------------------------------------------------------\n// Auth narrowing — xAI only accepts ApiKeyAuth\n// ---------------------------------------------------------------------------\n\n/**\n * Narrows {@link AuthMaterial} to its `apiKey` string, rejecting the\n * dev-only `CliSessionAuth` variant.\n *\n * xAI is a production API provider and only ever accepts API-key\n * credentials; `{ cliSession: true }` is reserved for the dev-only CLI\n * provider packages (`@gullabs/claude-cli`, `@gullabs/codex-cli`).\n */\nexport function requireApiKey(auth: AuthMaterial): string {\n if (\n !('apiKey' in auth) ||\n typeof auth.apiKey !== 'string' ||\n auth.apiKey.trim() === ''\n ) {\n throw new LlmError('@gullabs/xai requires auth.apiKey', {\n kind: 'invalid_auth',\n retryable: false,\n provider: 'xai',\n })\n }\n return auth.apiKey\n}\n\n// ---------------------------------------------------------------------------\n// Request shape — what the (future) adapter sends to responses.create\n// ---------------------------------------------------------------------------\n\n/** A text content item within an xAI Responses API input message. */\nexport interface XaiInputTextPart {\n type: 'input_text'\n text: string\n}\n\n/**\n * An image content item within an xAI Responses API input message.\n * `image_url` may be a data URL (`data:image/png;base64,...`) or a public URL.\n */\nexport interface XaiInputImagePart {\n type: 'input_image'\n image_url: string\n}\n\n/** Union of content-part shapes an input message may carry. */\nexport type XaiInputContentPart = XaiInputTextPart | XaiInputImagePart\n\n/** A single role+content input item constructed by the (future) adapter. */\nexport interface XaiInputItem {\n role: 'user' | 'assistant' | 'system' | 'developer'\n content: XaiInputContentPart[]\n}\n\n/**\n * Structured-output text-format request shape.\n * Real xAI field: `text.format`, NOT `response_format`.\n * `name` and `strict` are included per xAI's Structured Outputs docs\n * conventions even though the live fixture's request-echo does not surface\n * them (only the schema is echoed back).\n */\nexport type XaiTextFormat =\n | { type: 'json_schema'; name: string; schema: unknown; strict: boolean }\n | { type: 'text' }\n\n/**\n * Parameters for `client.responses.create`.\n * Structurally modeled from live-captured xAI Responses API fixtures\n * (see docs/provider-plugins-and-xai-grok-4-5-plan.md §3.1), not from the\n * `openai` npm package's TS types — xAI's actual endpoint shape differs.\n */\nexport interface XaiResponseCreateParams {\n model: string\n input: XaiInputItem[]\n instructions?: string\n reasoning?: { effort: 'low' | 'high' }\n text?: { format: XaiTextFormat }\n temperature?: number\n top_p?: number\n max_output_tokens?: number\n prompt_cache_key?: string\n /** Always `false` — this library never relies on xAI-side conversation storage. */\n store: false\n}\n\n// ---------------------------------------------------------------------------\n// Response shape — mirrors the xAI Responses API surface we actually consume\n// ---------------------------------------------------------------------------\n\n/** A single summary-text segment of a `type: 'reasoning'` output item. */\nexport interface XaiReasoningSummaryPart {\n type: 'summary_text'\n text: string\n}\n\n/** A `type: 'reasoning'` item in `output`. */\nexport interface XaiReasoningOutputItem {\n type: 'reasoning'\n id?: string\n summary: XaiReasoningSummaryPart[]\n status?: string\n}\n\n/** A single text content segment of a `type: 'message'` output item. */\nexport interface XaiOutputTextPart {\n type: 'output_text'\n text: string\n logprobs?: unknown[]\n annotations?: unknown[]\n}\n\n/** A `type: 'message'` item in `output`. */\nexport interface XaiMessageOutputItem {\n type: 'message'\n id?: string\n role?: string\n status?: string\n content: XaiOutputTextPart[]\n}\n\n/** Union of output-item shapes the Responses API may return. */\nexport type XaiOutputItem = XaiReasoningOutputItem | XaiMessageOutputItem\n\n/**\n * Token usage metadata returned alongside an xAI response.\n *\n * Kept loose/open: the known fields are typed, but xAI has been observed to\n * add additional numeric fields (e.g. `num_sources_used`,\n * `cost_in_usd_ticks`, `context_details`) that must not break this type.\n */\nexport interface XaiUsageShape {\n input_tokens: number\n input_tokens_details?: { cached_tokens?: number }\n output_tokens: number\n output_tokens_details?: { reasoning_tokens?: number }\n total_tokens?: number\n /** Additional provider-specific usage fields, passed through raw. */\n [otherKeys: string]: unknown\n}\n\n/**\n * Structural equivalent of the xAI Responses API response body.\n * Only the fields the (future) adapter reads are represented here.\n */\nexport interface XaiResponseShape {\n id: string\n model: string\n /**\n * Real field: `status`. Observed values: \"completed\", \"incomplete\" — kept\n * as a plain `string` since xAI may add further status values over time.\n */\n status: string\n incomplete_details?: { reason?: string } | null\n output: XaiOutputItem[]\n usage: XaiUsageShape\n reasoning?: { effort?: string; summary?: string }\n store?: boolean\n prompt_cache_key?: string | null\n /**\n * Response-level metadata (e.g. `system_fingerprint`) — surfaced into\n * `AdapterResult.providerMetadata` by the adapter when present.\n */\n metadata?: { [key: string]: unknown } | null\n}\n\n// ---------------------------------------------------------------------------\n// XaiClientLike — structural interface (no `openai` dependency)\n// ---------------------------------------------------------------------------\n\n/**\n * Structural interface for the `openai` SDK's `client.responses` surface\n * the adapter uses.\n *\n * Satisfied by:\n * - The real `openai` `OpenAI` client (via `buildXaiClient` wrapper), pointed\n * at xAI's `https://api.x.ai/v1` base URL.\n * - `FakeXaiClient` from `@gullabs/testing`.\n */\nexport interface XaiClientLike {\n responses: {\n create(\n params: XaiResponseCreateParams,\n options?: { signal?: AbortSignal },\n ): Promise<XaiResponseShape>\n }\n}\n\n// ---------------------------------------------------------------------------\n// buildXaiClient — imports the real `openai` SDK\n// ---------------------------------------------------------------------------\n\n/**\n * Build a real `openai`-SDK-backed client from AuthMaterial, pointed at\n * xAI's Responses API endpoint.\n *\n * Only API-key authentication is supported.\n *\n * @param auth - API key credentials ({ apiKey }).\n */\nexport async function buildXaiClient(auth: AuthMaterial): Promise<XaiClientLike> {\n // Resolve and validate auth BEFORE importing the SDK so auth-rejection\n // tests never need to touch the real `openai` module (and thus never hit\n // the network).\n const apiKey = requireApiKey(auth)\n\n const { default: OpenAI } = await import('openai')\n\n const client = new OpenAI({\n apiKey,\n baseURL: 'https://api.x.ai/v1',\n maxRetries: 0,\n })\n\n return {\n responses: {\n async create(\n params: XaiResponseCreateParams,\n options?: { signal?: AbortSignal },\n ): Promise<XaiResponseShape> {\n // Cast needed: our structural types are subsets of the real SDK types,\n // and the real SDK's types do not exactly match xAI's actual response\n // shape (see module doc comment).\n return (\n client.responses.create as unknown as (\n p: unknown,\n o?: { signal?: AbortSignal },\n ) => Promise<XaiResponseShape>\n )(params, options)\n },\n },\n }\n}\n","/**\n * xaiAdapter — @gullabs/xai xAI Grok provider adapter.\n *\n * Pure request⇄response mapping over the xAI Responses API (via\n * XaiClientLike). Never persists, never computes cost, never loops.\n *\n * @module\n */\n\nimport { LlmError, classifyError, assertNever } from '@gullabs/core'\nimport type {\n ProviderAdapter,\n ResolvedRequest,\n AdapterCtx,\n AdapterResult,\n Usage,\n Warning,\n FinishReason,\n JsonValue,\n AuthMaterial,\n Part,\n} from '@gullabs/core'\nimport { buildXaiClient } from './client.js'\nimport type {\n XaiClientLike,\n XaiResponseCreateParams,\n XaiInputItem,\n XaiInputContentPart,\n XaiResponseShape,\n XaiUsageShape,\n} from './client.js'\n\n// ---------------------------------------------------------------------------\n// Small object-shape helpers (mirrors google adapter's local helpers)\n// ---------------------------------------------------------------------------\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction badXaiRequest(message: string): LlmError {\n return new LlmError(message, { kind: 'bad_request', retryable: false })\n}\n\n// ---------------------------------------------------------------------------\n// Vision / media mapping\n// ---------------------------------------------------------------------------\n\nconst ALLOWED_XAI_IMAGE_MIME_TYPES = new Set(['image/jpeg', 'image/jpg', 'image/png'])\n\n/** 20 MiB, xAI's documented inline-image size ceiling. */\nconst MAX_XAI_INLINE_IMAGE_BYTES = 20 * 1024 * 1024\n\n/**\n * Map a single {@link Part} to its xAI Responses API input-content-part\n * equivalent.\n *\n * - `text` → `{ type: 'input_text', text }`\n * - `inline-media` → `{ type: 'input_image', image_url: 'data:<mime>;base64,<data>' }`;\n * rejected (`bad_request`) when `mimeType` is not jpg/jpeg/png, or when the\n * decoded payload exceeds 20 MiB.\n * - `file-uri` → `{ type: 'input_image', image_url: uri }` ONLY when\n * `uri` is a public `http(s)://` URL AND `mimeType` is an allowed image\n * type — a provider-hosted URI from another provider (e.g. Gemini's Files\n * API `https://generativelanguage.googleapis.com/...` — which itself\n * happens to be `https://`, but is not dereferenceable by xAI) is not\n * portable and callers should not reuse `FileUriPart` cross-provider.\n *\n * Note: xAI enforces an undocumented server-side minimum image size\n * (observed ~8px/side, ~512 total px). This adapter does not pre-validate\n * pixel dimensions — a too-small image surfaces as a `bad_request` from the\n * live API, classified normally by {@link classifyXaiError}.\n */\nfunction mapPart(p: Part): XaiInputContentPart {\n switch (p.kind) {\n case 'text':\n return { type: 'input_text', text: p.text }\n\n case 'inline-media': {\n if (!ALLOWED_XAI_IMAGE_MIME_TYPES.has(p.mimeType)) {\n throw badXaiRequest(\n `xAI vision only supports image/jpeg and image/png; got mimeType \"${p.mimeType}\".`,\n )\n }\n const byteLength = Buffer.from(p.data, 'base64').length\n if (byteLength > MAX_XAI_INLINE_IMAGE_BYTES) {\n throw badXaiRequest(\n `xAI inline images must be at most 20 MiB; got ${byteLength} bytes.`,\n )\n }\n return { type: 'input_image', image_url: `data:${p.mimeType};base64,${p.data}` }\n }\n\n case 'file-uri': {\n const isPublicHttpUrl = p.uri.startsWith('http://') || p.uri.startsWith('https://')\n const isAllowedImageType = ALLOWED_XAI_IMAGE_MIME_TYPES.has(p.mimeType)\n if (!isPublicHttpUrl || !isAllowedImageType) {\n throw badXaiRequest(\n `xAI only accepts public http(s) image URLs via FileUriPart; got scheme of \"${p.uri}\" / mimeType \"${p.mimeType}\".`,\n )\n }\n return { type: 'input_image', image_url: p.uri }\n }\n\n default:\n return assertNever(p)\n }\n}\n\n// ---------------------------------------------------------------------------\n// providerOptions.xai → explicit allowlisted mapping\n// ---------------------------------------------------------------------------\n\nfunction mapXaiProviderOptions(\n xaiOpts: unknown,\n model: string,\n): { promptCacheKey?: string } {\n if (xaiOpts === undefined) {\n return {}\n }\n\n if (!isPlainRecord(xaiOpts)) {\n throw badXaiRequest(`providerOptions.xai must be an object for model \"${model}\".`)\n }\n\n const unknownKeys = Object.keys(xaiOpts).filter((key) => key !== 'promptCacheKey')\n if (unknownKeys.length > 0) {\n throw badXaiRequest(\n `providerOptions.xai contains unsupported keys [${unknownKeys.join(\n ', ',\n )}] for model \"${model}\". Allowed keys: promptCacheKey.`,\n )\n }\n\n const mapped: { promptCacheKey?: string } = {}\n if (xaiOpts['promptCacheKey'] !== undefined) {\n if (\n typeof xaiOpts['promptCacheKey'] !== 'string' ||\n xaiOpts['promptCacheKey'].length === 0\n ) {\n throw badXaiRequest(\n `providerOptions.xai.promptCacheKey must be a non-empty string for model \"${model}\".`,\n )\n }\n mapped.promptCacheKey = xaiOpts['promptCacheKey']\n }\n\n return mapped\n}\n\n// ---------------------------------------------------------------------------\n// FinishReason mapping (xAI status/incomplete_details → our FinishReason)\n// ---------------------------------------------------------------------------\n\nfunction mapFinishReason(response: XaiResponseShape): FinishReason {\n if (response.status === 'completed') {\n return 'stop'\n }\n if (\n response.status === 'incomplete' &&\n response.incomplete_details?.reason === 'max_output_tokens'\n ) {\n return 'length'\n }\n // Any other status/incomplete_details combination without fixture\n // evidence (or a status we don't recognize) maps to 'other' — the core\n // FinishReason union is closed to these four values.\n return 'other'\n}\n\n// ---------------------------------------------------------------------------\n// Usage mapping — #1 correctness rule\n// ---------------------------------------------------------------------------\n\n/**\n * Usage fields already mapped into canonical {@link Usage} counters — never\n * duplicated into `details` under their raw xAI names.\n */\nconst CANONICALLY_MAPPED_USAGE_KEYS = new Set([\n 'input_tokens',\n 'output_tokens',\n 'total_tokens',\n])\n\n/**\n * Map xAI's `usage` object to our {@link Usage} type.\n *\n * **GROSS convention (ADR-004):**\n * - `usage.input_tokens` is already GROSS (includes cached) → `inputTokens`.\n * - `usage.output_tokens` is already GROSS (includes reasoning) → `outputTokens`.\n * Unlike Gemini, xAI does not require us to sum sub-fields into the gross\n * total — the top-level fields are already gross.\n *\n * **xAI extras** (captured, not billed): every additional NUMERIC top-level\n * usage field (`num_sources_used`, `num_server_side_tools_used`,\n * `cost_in_usd_ticks`, and anything xAI adds later) is surfaced into\n * `details` under its raw name. Non-numeric extras (e.g. `context_details`)\n * belong to `AdapterResult.providerMetadata` (see the adapter) and the full\n * raw payload always lands in `Usage.raw` verbatim.\n */\nfunction mapUsage(usage: XaiUsageShape): Usage {\n const inputTokens = usage.input_tokens\n const outputTokens = usage.output_tokens\n const cachedInputTokens = usage.input_tokens_details?.cached_tokens\n const thinkingTokens = usage.output_tokens_details?.reasoning_tokens\n const totalTokens = usage.total_tokens\n\n // Canonical details keys: input, output, cached, thinking.\n const details: Record<string, number> = {\n input: inputTokens,\n output: outputTokens,\n ...(cachedInputTokens !== undefined ? { cached: cachedInputTokens } : {}),\n ...(thinkingTokens !== undefined ? { thinking: thinkingTokens } : {}),\n }\n\n // Numeric xAI extras — surfaced under their raw names.\n for (const [key, value] of Object.entries(usage)) {\n if (typeof value === 'number' && !CANONICALLY_MAPPED_USAGE_KEYS.has(key)) {\n details[key] = value\n }\n }\n\n const raw: JsonValue = usage as unknown as { [k: string]: JsonValue }\n\n const result: Usage = {\n inputTokens,\n outputTokens,\n details,\n raw,\n ...(cachedInputTokens !== undefined ? { cachedInputTokens } : {}),\n ...(thinkingTokens !== undefined ? { thinkingTokens } : {}),\n ...(totalTokens !== undefined ? { totalTokens } : {}),\n }\n\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Error classification\n// ---------------------------------------------------------------------------\n\n/**\n * Structured-body auth-failure signature, taken verbatim from the recorded\n * live error taxonomy (`__fixtures__/09-error-taxonomy.json`,\n * `invalid_api_key` case): HTTP 400 with body\n * `{ code: 'invalid-argument', error: 'Incorrect API key provided. …' }`.\n *\n * The body `code` (`'invalid-argument'`) is NOT discriminating — xAI uses it\n * for genuinely bad requests too (e.g. `Model not found: grok-99`) — and the\n * `openai` SDK's `APIError` drops it (it hoists only the body's `error`\n * field onto `.error`). The exact message PREFIX xAI emits for bad keys is\n * therefore the signature.\n */\nconst XAI_AUTH_ERROR_MESSAGE_PREFIX = 'Incorrect API key provided'\n\n/**\n * Extract the STRUCTURED error-body text from a raw thrown value.\n *\n * Consulted shapes (both are parsed-body fields, never free-form\n * `Error.message` text):\n * - `rawErr.error` as a string — the `openai` SDK's `APIError` hoists the\n * response body's `error` field onto `.error`, which for xAI's\n * `{ code, error }` bodies is the message string itself.\n * - `rawErr.error` as an object — the full parsed body (test fakes and\n * hand-rolled throws of the fixture shape `{ status, error: <body> }`);\n * its `error` (xAI) or `message` (OpenAI-style) string field is read.\n *\n * Free-form `Error.message` is deliberately ignored so arbitrary request\n * content echoed into a message (e.g. a schema-validation error quoting user\n * text that mentions \"api key\") can never influence classification.\n */\nfunction extractXaiErrorBodyText(rawErr: unknown): string | undefined {\n if (rawErr === null || typeof rawErr !== 'object') {\n return undefined\n }\n const body = (rawErr as Record<string, unknown>)['error']\n if (typeof body === 'string') {\n return body\n }\n if (isPlainRecord(body)) {\n if (typeof body['error'] === 'string') {\n return body['error']\n }\n if (typeof body['message'] === 'string') {\n return body['message']\n }\n }\n return undefined\n}\n\n/** True iff the structured body matches xAI's recorded bad-API-key signature. */\nfunction isXaiAuthFailureBody(rawErr: unknown): boolean {\n const text = extractXaiErrorBodyText(rawErr)\n return text !== undefined && text.startsWith(XAI_AUTH_ERROR_MESSAGE_PREFIX)\n}\n\n/**\n * Classify a raw error thrown from the xAI Responses API call into a typed\n * {@link LlmError}.\n *\n * xAI's Responses API returns HTTP 400 (NOT 401) for an invalid API key, so\n * generic {@link classifyHttpStatus}-based classification (which maps 400 →\n * `bad_request`) is wrong for this one case. This function special-cases it:\n * a 400 response whose STRUCTURED parsed body matches the exact recorded\n * xAI auth-failure signature (`code: 'invalid-argument'` AND message prefix\n * `\"Incorrect API key provided\"` — see fixture 09) is reclassified as\n * `invalid_auth`. Free-form `Error.message` text is never scanned, so a 400\n * whose message merely *mentions* an API key (e.g. schema validation echoing\n * user content) stays `bad_request`. When the structured body is unavailable\n * or unparseable, classification falls through to the status-based\n * `classifyError` from `@gullabs/core`.\n */\nexport function classifyXaiError(rawErr: unknown): LlmError {\n if (rawErr instanceof LlmError) {\n return rawErr\n }\n\n const base = classifyError(rawErr)\n\n if (base.httpStatus === 400 && isXaiAuthFailureBody(rawErr)) {\n return new LlmError(base.message, {\n kind: 'invalid_auth',\n retryable: false,\n httpStatus: base.httpStatus,\n provider: 'xai',\n cause: base.cause ?? rawErr,\n })\n }\n\n return new LlmError(base.message, {\n kind: base.kind,\n retryable: base.retryable,\n ...(base.httpStatus !== undefined ? { httpStatus: base.httpStatus } : {}),\n ...(base.retryAfterMs !== undefined ? { retryAfterMs: base.retryAfterMs } : {}),\n provider: 'xai',\n cause: base.cause ?? rawErr,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Adapter options\n// ---------------------------------------------------------------------------\n\nexport interface XaiAdapterOptions {\n /**\n * Inject a pre-built client (real or fake).\n * When omitted, `buildXaiClient` is called with `ctx.auth` at call time,\n * inside the classified try/catch so any construction failure is wrapped\n * as a typed `LlmError`.\n */\n client?: XaiClientLike\n /**\n * @internal Testing-only.\n *\n * Override the default `buildXaiClient` factory. Allows unit tests to\n * simulate construction failures without importing the real `openai` SDK.\n * Never set this in production code. Mirrors `GeminiAdapterOptions._clientFactory`.\n */\n _clientFactory?: (auth: AuthMaterial) => XaiClientLike | Promise<XaiClientLike>\n}\n\n// ---------------------------------------------------------------------------\n// xaiAdapter factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an xAI Grok provider adapter (Responses API).\n *\n * @param opts.client - Optional pre-built client (e.g. for testing).\n */\nexport function xaiAdapter(opts?: XaiAdapterOptions): ProviderAdapter {\n return {\n id: 'xai',\n\n async run(req: ResolvedRequest, ctx: AdapterCtx): Promise<AdapterResult> {\n if (req.provider !== 'xai') {\n throw new LlmError(\n `xaiAdapter received a request for provider \"${req.provider}\", expected \"xai\".`,\n { kind: 'bad_request', retryable: false },\n )\n }\n\n const warnings: Warning[] = []\n const model = req.model\n const genConfig = req.config\n\n // ------------------------------------------------------------------\n // 1. Map messages → input\n // ------------------------------------------------------------------\n const input: XaiInputItem[] = req.messages.map((msg) => ({\n role: msg.role === 'assistant' ? 'assistant' : 'user',\n content: msg.parts.map(mapPart),\n }))\n\n // ------------------------------------------------------------------\n // 2. Build request params\n // ------------------------------------------------------------------\n const params: XaiResponseCreateParams = {\n model,\n input,\n store: false,\n }\n\n if (req.system !== undefined) {\n params.instructions = req.system\n }\n\n // Sampling — forwarded verbatim, no clamping (schema enforces bounds\n // upstream of the adapter).\n if (genConfig.temperature !== undefined) {\n params.temperature = genConfig.temperature\n }\n if (genConfig.topP !== undefined) {\n params.top_p = genConfig.topP\n }\n\n // max_output_tokens — no artificial ceiling; truncation surfaces as\n // finishReason:'length', not an error (see mapFinishReason).\n if (genConfig.maxOutputTokens !== undefined) {\n params.max_output_tokens = genConfig.maxOutputTokens\n }\n\n // serviceTier — xAI has no service-tier concept for grok-4.5.\n if (genConfig.serviceTier !== undefined) {\n throw badXaiRequest(\n `serviceTier is not supported for xai models (got \"${genConfig.serviceTier}\").`,\n )\n }\n\n // ------------------------------------------------------------------\n // 3. Reasoning → { effort: 'low' | 'high' }\n //\n // grok-4.5 admits ONLY 'low' | 'high' (default 'high'); 'none' and\n // 'medium' are rejected by the live API. budgetTokens is not\n // supported (level-style reasoning, no token budget). includeThoughts\n // is a no-op for xAI — reasoning summaries come back unconditionally\n // whenever reasoning ran, so reasoningText is always surfaced below\n // regardless of this flag; we do not throw on it since it is a\n // legitimate ReasoningIntent field this provider simply doesn't need.\n // ------------------------------------------------------------------\n const reasoning = genConfig.reasoning\n if (reasoning !== undefined) {\n if (reasoning.budgetTokens !== undefined) {\n throw badXaiRequest(\n `reasoning.budgetTokens is not supported for model \"${model}\" (xAI uses effort-level reasoning, not token budgets); use reasoning.effort instead.`,\n )\n }\n\n if (reasoning.effort !== undefined) {\n const effort = reasoning.effort\n if (effort !== 'low' && effort !== 'high') {\n throw badXaiRequest(\n `reasoning.effort \"${effort}\" is not supported for xai model \"${model}\" (only \"low\" and \"high\" are admitted).`,\n )\n }\n const admitted = req.modelDescriptor?.capabilities?.admittedReasoningEfforts\n if (admitted !== undefined && !admitted.includes(effort)) {\n throw badXaiRequest(\n `reasoning.effort \"${effort}\" is not supported for xai model \"${model}\".`,\n )\n }\n params.reasoning = { effort }\n }\n }\n\n // ------------------------------------------------------------------\n // 4. Structured output → text.format (NOT response_format)\n // ------------------------------------------------------------------\n const structuredOutputRequested = req.outputJsonSchema !== undefined\n if (structuredOutputRequested) {\n const schema = req.outputJsonSchema\n const name =\n isPlainRecord(schema) &&\n typeof schema['title'] === 'string' &&\n schema['title'].length > 0\n ? schema['title']\n : 'structured_output'\n params.text = {\n format: { type: 'json_schema', name, schema, strict: true },\n }\n }\n\n // ------------------------------------------------------------------\n // 5. providerOptions.xai → prompt_cache_key\n // ------------------------------------------------------------------\n const xaiProviderConfig = mapXaiProviderOptions(\n genConfig.providerOptions?.['xai'],\n model,\n )\n if (xaiProviderConfig.promptCacheKey !== undefined) {\n params.prompt_cache_key = xaiProviderConfig.promptCacheKey\n }\n\n // ------------------------------------------------------------------\n // 6. Client construction + SDK call — inside the classifier so ANY\n // failure (including bad auth construction) is rethrown as a typed\n // LlmError(provider:'xai').\n // ------------------------------------------------------------------\n let response: XaiResponseShape\n try {\n const buildClient = opts?._clientFactory ?? buildXaiClient\n const client: XaiClientLike =\n opts?.client !== undefined ? opts.client : await buildClient(ctx.auth)\n ctx.logger.debug(\n { model, configKeys: Object.keys(params) },\n 'llm.adapter.dispatch',\n )\n response = await client.responses.create(\n params,\n ctx.signal !== undefined ? { signal: ctx.signal } : undefined,\n )\n } catch (rawErr) {\n throw classifyXaiError(rawErr)\n }\n\n // ------------------------------------------------------------------\n // 7. Map response\n // ------------------------------------------------------------------\n let text = ''\n let reasoningText: string | undefined\n\n for (const item of response.output) {\n if (item.type === 'message') {\n text += item.content.map((part) => part.text).join('')\n } else {\n const joined = item.summary.map((s) => s.text).join('')\n if (joined.length > 0) {\n reasoningText = (reasoningText ?? '') + joined\n }\n }\n }\n\n // Parse structured output (JSON text → rawStructured).\n let rawStructured: unknown\n if (structuredOutputRequested && text.length > 0) {\n try {\n rawStructured = JSON.parse(text)\n } catch {\n // Core reports unparsed via absence of rawStructured; callers own\n // validation/retry policy (ADR-009).\n }\n }\n\n const usage = mapUsage(response.usage)\n const finishReason = mapFinishReason(response)\n\n // Response-level metadata → providerMetadata: usage.context_details\n // (non-numeric usage extra) and response.metadata (e.g.\n // system_fingerprint). Numeric usage extras live in usage.details; the\n // full raw usage payload is already in usage.raw.\n const providerMeta: { [k: string]: JsonValue } = {}\n const contextDetails = response.usage['context_details']\n if (isPlainRecord(contextDetails)) {\n providerMeta['context_details'] = contextDetails as unknown as JsonValue\n }\n if (isPlainRecord(response.metadata)) {\n providerMeta['metadata'] = response.metadata as unknown as JsonValue\n }\n\n const result: AdapterResult = {\n model: response.model,\n usage,\n warnings,\n finishReason,\n responseId: response.id,\n ...(text.length > 0 ? { text } : {}),\n ...(reasoningText !== undefined ? { reasoningText } : {}),\n ...(rawStructured !== undefined ? { rawStructured } : {}),\n ...(Object.keys(providerMeta).length > 0\n ? { providerMetadata: providerMeta }\n : {}),\n }\n\n return result\n },\n }\n}\n","/**\n * Strict Zod config schema for xAI's `grok-4.5` model.\n *\n * Mirrors `@gullabs/core`'s `packages/core/src/model-config/*.ts` doc-density\n * style, but is a single self-contained `z.strictObject` — unlike Gemini's\n * schemas, xai has no service-tier branching (no `z.union` of tier variants\n * needed), no `topK`, and only a single reasoning-effort union (`'low'|'high'`).\n *\n * @module\n */\n\nimport { z } from 'zod'\n\nexport const Grok45ConfigSchema = z\n .strictObject({\n temperature: z.number().optional().meta({\n title: 'Temperature',\n description: 'Sampling temperature forwarded verbatim to grok-4.5.',\n }),\n topP: z.number().optional().meta({\n title: 'Top P',\n description: 'Nucleus sampling parameter forwarded verbatim to grok-4.5.',\n }),\n maxOutputTokens: z\n .number()\n .int()\n .positive()\n .optional()\n .meta({\n title: 'Max Output Tokens',\n description:\n 'Maximum output token cap for grok-4.5. No artificial ceiling — xAI ' +\n 'accepts arbitrarily large values (live-verified); truncation surfaces ' +\n \"as finishReason:'length', not an error.\",\n }),\n reasoning: z\n .strictObject({\n effort: z.enum(['low', 'high']).meta({\n title: 'Reasoning Effort',\n description:\n 'Reasoning effort for grok-4.5. Only \"low\" and \"high\" are admitted ' +\n '(live-verified); \"none\"/\"medium\"/\"xhigh\" are rejected by the live API.',\n }),\n })\n .optional()\n .meta({\n title: 'Reasoning',\n description:\n 'grok-4.5 effort-level reasoning configuration. No budgetTokens field — ' +\n 'xAI uses level-style reasoning, not token budgets.',\n }),\n timeoutMs: z.number().int().positive().optional().meta({\n title: 'Timeout',\n description: 'Logical request timeout in milliseconds.',\n }),\n providerOptions: z\n .strictObject({\n xai: z\n .strictObject({\n promptCacheKey: z\n .string()\n .min(1)\n .optional()\n .meta({\n title: 'Prompt Cache Key',\n description:\n 'xAI conversation-routing cache key — maps to Responses API ' +\n '`prompt_cache_key`.',\n }),\n })\n .optional()\n .meta({\n title: 'xAI Provider Options',\n description: 'Allowlisted xAI provider options for grok-4.5.',\n }),\n })\n .optional()\n .meta({\n title: 'Provider Options',\n description: 'Provider-specific options accepted for grok-4.5.',\n }),\n })\n .meta({\n title: 'Grok45Config',\n description:\n 'Strict Responses API config for model grok-4.5. Level reasoning ' +\n '(low/high only), tunable sampling, no service tiers, structured output, ' +\n 'vision, priced.',\n examples: [{ reasoning: { effort: 'high' } }],\n })\n","/**\n * Model descriptor + registry for @gullabs/xai.\n *\n * v1 ships exactly one model: `grok-4.5` (canonical id only — the\n * `grok-4.5-latest` / `grok-build-latest` aliases visible in xAI's\n * `/v1/models` listing are intentionally NOT registered as separate\n * descriptors; reject-don't-map, callers must use `grok-4.5` verbatim).\n *\n * @module\n */\n\nimport type { ModelDescriptor, ModelRegistry } from '@gullabs/core'\nimport {\n createModelRegistry,\n toConfigJsonSchema,\n zodToStandardSchema,\n} from '@gullabs/core'\n\nimport { Grok45ConfigSchema } from './model-config/grok-4-5.js'\n\nexport { Grok45ConfigSchema } from './model-config/grok-4-5.js'\n\nexport const grok45ModelDescriptor: ModelDescriptor = {\n model: 'grok-4.5',\n provider: 'xai',\n pricingFamily: 'grok-4.5',\n capabilities: {\n reasoning: true,\n reasoningApi: 'level',\n admittedReasoningEfforts: ['low', 'high'],\n structuredOutput: true,\n nativeStructuredOutput: true,\n vision: true,\n audioInput: false,\n sampling: 'tunable',\n caching: { explicit: false, minTokens: 0 },\n grounding: false,\n // No serviceTiers key — xai has no service-tier concept.\n },\n configSchema: Grok45ConfigSchema,\n configJsonSchema: toConfigJsonSchema(Grok45ConfigSchema),\n validateConfig: zodToStandardSchema(Grok45ConfigSchema),\n}\n\n/** Every model descriptor `@gullabs/xai` contributes. v1 ships only grok-4.5. */\nexport const xaiModelDescriptors: ModelDescriptor[] = [grok45ModelDescriptor]\n\nexport const xaiRegistry: ModelRegistry = createModelRegistry(xaiModelDescriptors)\n","/**\n * xAI pricing snapshot + cost computation for @gullabs/xai.\n *\n * All rates are in **micro-USD per million tokens** (µUSD/M), matching\n * `@gullabs/core`'s `ModelRates` convention exactly: `cost_µUSD = N *\n * ratePerM / 1_000_000`.\n *\n * This is a SELF-CONTAINED, xai-owned reimplementation — it does NOT import\n * core's Gemini-specific `computeCost`/`GEMINI_PRICING`/`geminiPricingSource`\n * (those are Gemini-only). `PricingSource` is provider-scoped by contract\n * (see `packages/core/src/ports.ts`); this module is xai's own.\n *\n * **Long-context tier.** grok-4.5 charges a premium when the GROSS input\n * token count exceeds 200,000 (`long_context_threshold` in xAI's\n * `/v1/models` listing). Selected by `inputTokens` (incl. cached), not by\n * billable input — mirrors core's `selectRates` convention exactly (strictly\n * greater than 200,000).\n *\n * **Service tiers.** xAI has no service-tier concept for grok-4.5 — there is\n * no `TIER_FACTOR`-equivalent here. `price()`'s `tier` param is accepted for\n * `PricingSource` structural conformance but any *defined* tier is treated\n * as unrecognized → unpriced (reject-don't-map), mirroring core's own\n * defensive stance for an unrecognized tier. `undefined` (no tier requested,\n * the only value xAI adapters ever pass — `xaiAdapter` rejects `serviceTier`\n * upstream) prices normally.\n *\n * **Conversion factor.** xAI's `/v1/models` raw `*_token_price` fields are\n * in hundred-thousandths of a dollar per token (i.e. divide the raw integer\n * by 10,000 to get USD per million tokens): e.g. `grok-4.5`'s raw\n * `prompt_text_token_price: 20000` ÷ 10,000 = $2.00/M, which matches the\n * confirmed live-verified figure.\n *\n * Verified against `/v1/models` fixture captured 2026-07-09 (see\n * `docs/provider-plugins-and-xai-grok-4-5-plan.md` and the live-verification\n * fixture directory referenced in the commit-3 task brief).\n *\n * @module\n */\n\nimport type { Cost, PricingSource, Usage } from '@gullabs/core'\n\n/** Identifies this pricing snapshot — bump the date when rates change. */\nexport const xaiPricingVersion = 'xai-2026-07-09' as const\n\n/**\n * Per-model rate entry (all values in µUSD per million tokens).\n *\n * `gt200k` (when present) applies when GROSS input tokens > 200,000.\n */\nexport interface XaiModelRates {\n /** µUSD per million input tokens (billable = gross − cached). */\n inputPerM: number\n /** µUSD per million cache-read tokens. */\n cachedPerM: number\n /** µUSD per million output tokens (reasoning tokens are folded in). */\n outputPerM: number\n /** Optional high-tier rates for long-context (GROSS input > 200k). */\n gt200k?: {\n inputPerM: number\n cachedPerM: number\n outputPerM: number\n }\n}\n\n/**\n * Frozen xAI pricing snapshot (per-1M in µUSD).\n *\n * Keys are EXACT canonical model identifiers — no prefix or alias matching.\n * xAI aliases (e.g. `grok-4.5-latest`, `grok-build-latest`) are deliberately\n * NOT registered/priced (reject-don't-map): callers must use the canonical\n * id; anything else resolves to the unpriced path.\n */\nexport const XAI_PRICING: Readonly<Record<string, XaiModelRates>> = Object.freeze({\n // ── grok-4.5 ── $2.00/$6.00 (≤200k), $4.00/$12.00 (>200k); cached $0.50/$1.00\n 'grok-4.5': {\n inputPerM: 2_000_000,\n cachedPerM: 500_000,\n outputPerM: 6_000_000,\n gt200k: {\n inputPerM: 4_000_000,\n cachedPerM: 1_000_000,\n outputPerM: 12_000_000,\n },\n },\n})\n\nconst LONG_CONTEXT_THRESHOLD = 200_000\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Look up rates for a model — EXACT match only.\n *\n * Deliberately no prefix matching (unlike core's Gemini lookup): xAI aliases\n * such as `grok-4.5-latest` would otherwise prefix-match `grok-4.5` and\n * silently reintroduce the alias behavior the model registry rejects. An id\n * that is not an exact `XAI_PRICING` key is unpriced.\n */\nfunction lookupRates(model: string): XaiModelRates | undefined {\n return XAI_PRICING[model]\n}\n\n/**\n * Select the applicable rate set for a model given the GROSS input token\n * count. When a model has a `gt200k` tier, that tier's rates apply when\n * `grossInputTokens` is **strictly greater than** 200,000.\n */\nfunction selectRates(\n rates: XaiModelRates,\n grossInputTokens: number,\n): { inputPerM: number; cachedPerM: number; outputPerM: number } {\n if (rates.gt200k !== undefined && grossInputTokens > LONG_CONTEXT_THRESHOLD) {\n return rates.gt200k\n }\n return {\n inputPerM: rates.inputPerM,\n cachedPerM: rates.cachedPerM,\n outputPerM: rates.outputPerM,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the cost of an xAI LLM call given a model name and usage data.\n *\n * Pure function — no side effects, always returns a well-formed {@link Cost}.\n *\n * **Algorithm** (mirrors `@gullabs/core`'s `computeCost` exactly, xai-owned):\n * 1. Look up rates for `model`; if not found, return an unpriced `Cost`\n * (`microUsd: null`) naming the model.\n * 2. A defined `tier` is always unpriced (xai has no tiers; reject-don't-map).\n * `undefined` prices normally.\n * 3. Select base vs. `>200k` long-context rates from GROSS `inputTokens`.\n * 4. Billable input = `inputTokens − (cachedInputTokens ?? 0)`, clamped to 0.\n * 5. Round each component (input, cached, output) independently to the\n * nearest integer micro-USD.\n * 6. `microUsd` is the sum of the three components — guarantees\n * `details.input + details.cached + details.output === microUsd` exactly.\n */\nexport function computeXaiCost(model: string, usage: Usage, tier?: string): Cost {\n const rates = lookupRates(model)\n\n if (rates === undefined) {\n return {\n microUsd: null,\n usd: null,\n pricingVersion: xaiPricingVersion,\n confidence: 'estimated',\n details: { input: 0, cached: 0, output: 0 },\n unpricedReason: `Unknown model \"${model}\"; no pricing entry found.`,\n }\n }\n\n if (tier !== undefined) {\n return {\n microUsd: null,\n usd: null,\n pricingVersion: xaiPricingVersion,\n confidence: 'estimated',\n details: { input: 0, cached: 0, output: 0 },\n unpricedReason: `Unknown service tier \"${tier}\"; xai has no service tiers, refusing to guess a pricing multiplier.`,\n }\n }\n\n const base = selectRates(rates, usage.inputTokens)\n\n const cached = usage.cachedInputTokens ?? 0\n const billableInput = Math.max(0, usage.inputTokens - cached)\n\n const inputCost = Math.round((billableInput * base.inputPerM) / 1_000_000)\n const cachedCost = Math.round((cached * base.cachedPerM) / 1_000_000)\n const outputCost = Math.round((usage.outputTokens * base.outputPerM) / 1_000_000)\n\n const microUsd = inputCost + cachedCost + outputCost\n\n return {\n microUsd,\n usd: microUsd / 1_000_000,\n pricingVersion: xaiPricingVersion,\n confidence: 'exact',\n details: {\n input: inputCost,\n cached: cachedCost,\n output: outputCost,\n },\n }\n}\n\n/**\n * Factory that returns the **xai-scoped** {@link PricingSource} port\n * implementation backed by {@link XAI_PRICING}.\n *\n * @example\n * ```ts\n * import { xaiPricingSource } from '@gullabs/xai'\n *\n * const pricing = xaiPricingSource()\n * const cost = pricing.price('grok-4.5', usage)\n * ```\n */\nexport function xaiPricingSource(): PricingSource {\n return {\n version: xaiPricingVersion,\n price(model: string, usage: Usage, tier?: string): Cost {\n return computeXaiCost(model, usage, tier)\n },\n hasModel(model: string): boolean {\n return lookupRates(model) !== undefined\n },\n listModels(): readonly string[] {\n return Object.keys(XAI_PRICING)\n },\n }\n}\n","/**\n * `xaiProvider` — {@link ProviderPlugin} factory for @gullabs/xai.\n *\n * Bundles the xAI Grok adapter, the `grok-4.5` model descriptor, and the\n * xai pricing source into a single plugin for {@link composeProviders}.\n *\n * @module\n */\n\nimport type { ProviderPlugin } from '@gullabs/core'\n\nimport { xaiAdapter } from './adapter.js'\nimport type { XaiAdapterOptions } from './adapter.js'\nimport { xaiModelDescriptors } from './models.js'\nimport { xaiPricingSource } from './pricing.js'\n\n/**\n * Create a {@link ProviderPlugin} for the xAI Grok provider.\n *\n * @param opts - Forwarded to {@link xaiAdapter}.\n * @returns A plugin bundling the xAI adapter, the `grok-4.5` model\n * descriptor, and the built-in xai pricing source.\n *\n * @example\n * ```ts\n * import { createClient, composeProviders } from '@gullabs/core'\n * import { xaiProvider } from '@gullabs/xai'\n *\n * const client = createClient({\n * ...composeProviders([xaiProvider()]),\n * })\n * ```\n */\nexport function xaiProvider(opts?: XaiAdapterOptions): ProviderPlugin {\n return {\n adapter: xaiAdapter(opts),\n modelDescriptors: xaiModelDescriptors,\n pricingSource: xaiPricingSource(),\n }\n}\n"]}
|