@gullabs/xai 0.7.0 → 0.8.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/dist/index.cjs CHANGED
@@ -30,6 +30,453 @@ async function buildXaiClient(auth) {
30
30
  }
31
31
  };
32
32
  }
33
+ var webSearchFlags = {
34
+ enableImageUnderstanding: zod.z.boolean().optional().meta({
35
+ title: "Enable Image Understanding",
36
+ description: "Ask xAI to analyze images found during web search."
37
+ }),
38
+ enableImageSearch: zod.z.boolean().optional().meta({
39
+ title: "Enable Image Search",
40
+ description: "Ask xAI to include image search results."
41
+ })
42
+ };
43
+ var XaiWebSearchToolSchema = zod.z.union([
44
+ zod.z.strictObject({
45
+ type: zod.z.literal("web_search"),
46
+ allowedDomains: zod.z.array(zod.z.string()).max(5),
47
+ ...webSearchFlags
48
+ }),
49
+ zod.z.strictObject({
50
+ type: zod.z.literal("web_search"),
51
+ excludedDomains: zod.z.array(zod.z.string()).max(5),
52
+ ...webSearchFlags
53
+ }),
54
+ zod.z.strictObject({
55
+ type: zod.z.literal("web_search"),
56
+ ...webSearchFlags
57
+ })
58
+ ]).meta({
59
+ title: "XaiWebSearchTool",
60
+ description: "xAI web_search server tool. allowedDomains and excludedDomains are mutually exclusive (max 5)."
61
+ });
62
+ var xSearchFlags = {
63
+ fromDate: zod.z.iso.date().optional().meta({
64
+ title: "From Date",
65
+ description: "Inclusive ISO-8601 date lower bound for X search."
66
+ }),
67
+ toDate: zod.z.iso.date().optional().meta({
68
+ title: "To Date",
69
+ description: "Inclusive ISO-8601 date upper bound for X search."
70
+ }),
71
+ enableImageUnderstanding: zod.z.boolean().optional().meta({
72
+ title: "Enable Image Understanding",
73
+ description: "Ask xAI to analyze images in matched posts."
74
+ }),
75
+ enableVideoUnderstanding: zod.z.boolean().optional().meta({
76
+ title: "Enable Video Understanding",
77
+ description: "Ask xAI to analyze videos in matched posts."
78
+ })
79
+ };
80
+ var XaiXSearchToolSchema = zod.z.union([
81
+ zod.z.strictObject({
82
+ type: zod.z.literal("x_search"),
83
+ allowedXHandles: zod.z.array(zod.z.string()).max(20),
84
+ ...xSearchFlags
85
+ }),
86
+ zod.z.strictObject({
87
+ type: zod.z.literal("x_search"),
88
+ excludedXHandles: zod.z.array(zod.z.string()).max(20),
89
+ ...xSearchFlags
90
+ }),
91
+ zod.z.strictObject({
92
+ type: zod.z.literal("x_search"),
93
+ ...xSearchFlags
94
+ })
95
+ ]).meta({
96
+ title: "XaiXSearchTool",
97
+ description: "xAI x_search server tool. allowedXHandles and excludedXHandles are mutually exclusive (max 20)."
98
+ });
99
+ var XaiToolsSchema = zod.z.union([
100
+ zod.z.tuple([XaiWebSearchToolSchema]),
101
+ zod.z.tuple([XaiXSearchToolSchema]),
102
+ zod.z.tuple([XaiWebSearchToolSchema, XaiXSearchToolSchema]),
103
+ zod.z.tuple([XaiXSearchToolSchema, XaiWebSearchToolSchema])
104
+ ]).meta({
105
+ title: "XaiTools",
106
+ description: "xAI Live Search tools. At most one web_search and at most one x_search."
107
+ });
108
+ var XaiProviderOptionsSchema = zod.z.strictObject({
109
+ promptCacheKey: zod.z.string().min(1).optional().meta({
110
+ title: "Prompt Cache Key",
111
+ description: "xAI conversation-routing cache key \u2014 maps to Responses API `prompt_cache_key`."
112
+ }),
113
+ tools: XaiToolsSchema.optional().meta({
114
+ title: "Live Search Tools",
115
+ description: "xAI server-side web_search / x_search tools."
116
+ }),
117
+ parallelToolCalls: zod.z.boolean().optional().meta({
118
+ title: "Parallel Tool Calls",
119
+ description: "xAI Responses parallel_tool_calls. Not a generic contract field."
120
+ })
121
+ }).meta({
122
+ title: "xAI Provider Options",
123
+ description: "Allowlisted xAI provider options."
124
+ });
125
+
126
+ // src/model-config/grok-4-5.ts
127
+ var Grok45ConfigSchema = zod.z.strictObject({
128
+ temperature: zod.z.number().optional().meta({
129
+ title: "Temperature",
130
+ description: "Sampling temperature forwarded verbatim to grok-4.5."
131
+ }),
132
+ topP: zod.z.number().optional().meta({
133
+ title: "Top P",
134
+ description: "Nucleus sampling parameter forwarded verbatim to grok-4.5."
135
+ }),
136
+ maxOutputTokens: zod.z.number().int().positive().optional().meta({
137
+ title: "Max Output Tokens",
138
+ 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."
139
+ }),
140
+ reasoning: zod.z.strictObject({
141
+ effort: zod.z.enum(["low", "medium", "high"]).meta({
142
+ title: "Reasoning Effort",
143
+ description: 'Reasoning effort for grok-4.5. "low", "medium", and "high" are admitted (live-verified 2026-08-24). "xhigh" is rejected here even though /v1/language-models lists it: the reasoning guide says it is treated as high, and an echo does not prove a distinct level. "none" is rejected. Vendor default when omitted is "high".'
144
+ })
145
+ }).optional().meta({
146
+ title: "Reasoning",
147
+ description: "grok-4.5 effort-level reasoning configuration. No budgetTokens field \u2014 xAI uses level-style reasoning, not token budgets."
148
+ }),
149
+ serviceTier: zod.z.literal("priority").optional().meta({
150
+ title: "Service Tier",
151
+ description: "xAI priority processing for grok-4.5, billed at 2\xD7 on input, cached input, and output tokens. Live-verified 2026-09-25."
152
+ }),
153
+ timeoutMs: zod.z.number().int().positive().optional().meta({
154
+ title: "Timeout",
155
+ description: "Logical request timeout in milliseconds."
156
+ }),
157
+ providerOptions: zod.z.strictObject({
158
+ xai: XaiProviderOptionsSchema.optional()
159
+ }).optional().meta({
160
+ title: "Provider Options",
161
+ description: "Provider-specific options accepted for grok-4.5."
162
+ })
163
+ }).meta({
164
+ title: "Grok45Config",
165
+ description: "Strict Responses API config for model grok-4.5. Level reasoning (low/medium/high), optional priority service tier, tunable sampling, structured output, vision, Live Search tools, priced.",
166
+ examples: [{ reasoning: { effort: "high" } }]
167
+ });
168
+ var Grok46ConfigSchema = zod.z.strictObject({
169
+ temperature: zod.z.number().optional().meta({
170
+ title: "Temperature",
171
+ description: "Sampling temperature forwarded verbatim to grok-4.6."
172
+ }),
173
+ topP: zod.z.number().optional().meta({
174
+ title: "Top P",
175
+ description: "Nucleus sampling parameter forwarded verbatim to grok-4.6."
176
+ }),
177
+ maxOutputTokens: zod.z.number().int().positive().optional().meta({
178
+ title: "Max Output Tokens",
179
+ description: "Maximum output token cap for grok-4.6. No artificial ceiling \u2014 xAI accepts arbitrarily large values; truncation surfaces as finishReason:'length', not an error."
180
+ }),
181
+ reasoning: zod.z.strictObject({
182
+ effort: zod.z.enum(["low", "medium", "high", "xhigh"]).meta({
183
+ title: "Reasoning Effort",
184
+ description: 'Reasoning effort for grok-4.6. Live-verified 2026-08-12: "low", "medium", "high", and "xhigh" are accepted; "none" is rejected. Vendor default when omitted is "high".'
185
+ })
186
+ }).optional().meta({
187
+ title: "Reasoning",
188
+ description: "grok-4.6 effort-level reasoning configuration. No budgetTokens field \u2014 xAI uses level-style reasoning, not token budgets."
189
+ }),
190
+ serviceTier: zod.z.literal("priority").optional().meta({
191
+ title: "Service Tier",
192
+ description: 'xAI priority processing for grok-4.6 (Responses `service_tier: "priority"`). Echo live-verified 2026-08-12. Bills at 2\xD7 after the cache discount (uncached standard-list 2\xD7 confirmed by live ticks; cached/long-context legs follow the official 2\xD7 rule). Omitted requests stay on xAI default. "flex"/"standard"/"batch" are rejected.'
193
+ }),
194
+ timeoutMs: zod.z.number().int().positive().optional().meta({
195
+ title: "Timeout",
196
+ description: "Logical request timeout in milliseconds."
197
+ }),
198
+ providerOptions: zod.z.strictObject({
199
+ xai: XaiProviderOptionsSchema.optional()
200
+ }).optional().meta({
201
+ title: "Provider Options",
202
+ description: "Provider-specific options accepted for grok-4.6."
203
+ })
204
+ }).meta({
205
+ title: "Grok46Config",
206
+ description: "Strict Responses API config for model grok-4.6. Level reasoning (low/medium/high/xhigh), optional priority service tier, tunable sampling, structured output, vision, priced.",
207
+ examples: [{ reasoning: { effort: "high" } }]
208
+ });
209
+ var Grok47ConfigSchema = zod.z.strictObject({
210
+ temperature: zod.z.number().optional().meta({
211
+ title: "Temperature",
212
+ description: "Sampling temperature forwarded verbatim to grok-4.7."
213
+ }),
214
+ topP: zod.z.number().optional().meta({
215
+ title: "Top P",
216
+ description: "Nucleus sampling parameter forwarded verbatim to grok-4.7."
217
+ }),
218
+ maxOutputTokens: zod.z.number().int().positive().optional().meta({
219
+ title: "Max Output Tokens",
220
+ description: "Maximum output token cap for grok-4.7. No artificial ceiling \u2014 xAI accepts arbitrarily large values; truncation surfaces as finishReason:'length', not an error."
221
+ }),
222
+ reasoning: zod.z.strictObject({
223
+ effort: zod.z.enum(["low", "medium", "high", "xhigh"]).meta({
224
+ title: "Reasoning Effort",
225
+ description: 'Reasoning effort for grok-4.7. "low", "medium", "high", and "xhigh" are accepted; "none" is rejected. Vendor default when omitted is "high".'
226
+ })
227
+ }).optional().meta({
228
+ title: "Reasoning",
229
+ description: "grok-4.7 effort-level reasoning configuration. No budgetTokens field \u2014 xAI uses level-style reasoning, not token budgets."
230
+ }),
231
+ serviceTier: zod.z.literal("priority").optional().meta({
232
+ title: "Service Tier",
233
+ description: 'xAI priority processing for grok-4.7 (Responses `service_tier: "priority"`). Bills at 2\xD7 after the cache discount (uncached standard-list 2\xD7 cached/long-context legs follow the official 2\xD7 rule). Omitted requests stay on xAI default. "flex"/"standard"/"batch" are rejected.'
234
+ }),
235
+ timeoutMs: zod.z.number().int().positive().optional().meta({
236
+ title: "Timeout",
237
+ description: "Logical request timeout in milliseconds."
238
+ }),
239
+ providerOptions: zod.z.strictObject({
240
+ xai: XaiProviderOptionsSchema.optional()
241
+ }).optional().meta({
242
+ title: "Provider Options",
243
+ description: "Provider-specific options accepted for grok-4.7."
244
+ })
245
+ }).meta({
246
+ title: "Grok47Config",
247
+ description: "Strict Responses API config for model grok-4.7. Level reasoning (low/medium/high/xhigh), optional priority service tier, tunable sampling, structured output, vision, priced.",
248
+ examples: [{ reasoning: { effort: "high" } }]
249
+ });
250
+
251
+ // src/models.ts
252
+ var grok45ModelDescriptor = {
253
+ model: "grok-4.5",
254
+ provider: "xai",
255
+ pricingFamily: "grok-4.5",
256
+ capabilities: {
257
+ reasoning: true,
258
+ reasoningApi: "level",
259
+ admittedReasoningEfforts: ["low", "medium", "high"],
260
+ structuredOutput: true,
261
+ nativeStructuredOutput: true,
262
+ vision: true,
263
+ audioInput: false,
264
+ sampling: "tunable",
265
+ caching: { explicit: false, minTokens: 0 },
266
+ grounding: true,
267
+ functionCalling: true,
268
+ serviceTiers: ["priority"]
269
+ },
270
+ configSchema: Grok45ConfigSchema,
271
+ configJsonSchema: core.toConfigJsonSchema(Grok45ConfigSchema),
272
+ validateConfig: core.zodToStandardSchema(Grok45ConfigSchema)
273
+ };
274
+ var grok46ModelDescriptor = {
275
+ model: "grok-4.6",
276
+ provider: "xai",
277
+ pricingFamily: "grok-4.6",
278
+ capabilities: {
279
+ reasoning: true,
280
+ reasoningApi: "level",
281
+ admittedReasoningEfforts: ["low", "medium", "high", "xhigh"],
282
+ structuredOutput: true,
283
+ nativeStructuredOutput: true,
284
+ vision: true,
285
+ audioInput: false,
286
+ sampling: "tunable",
287
+ caching: { explicit: false, minTokens: 0 },
288
+ grounding: true,
289
+ structuredOutputWithTools: true,
290
+ functionCalling: true,
291
+ serviceTiers: ["priority"]
292
+ },
293
+ configSchema: Grok46ConfigSchema,
294
+ configJsonSchema: core.toConfigJsonSchema(Grok46ConfigSchema),
295
+ validateConfig: core.zodToStandardSchema(Grok46ConfigSchema)
296
+ };
297
+ var grok47ModelDescriptor = {
298
+ model: "grok-4.7",
299
+ provider: "xai",
300
+ pricingFamily: "grok-4.7",
301
+ capabilities: {
302
+ reasoning: true,
303
+ reasoningApi: "level",
304
+ admittedReasoningEfforts: ["low", "medium", "high", "xhigh"],
305
+ structuredOutput: true,
306
+ nativeStructuredOutput: true,
307
+ vision: true,
308
+ audioInput: false,
309
+ sampling: "tunable",
310
+ caching: { explicit: false, minTokens: 0 },
311
+ grounding: true,
312
+ functionCalling: true,
313
+ statelessReasoningReplay: true,
314
+ serviceTiers: ["priority"]
315
+ },
316
+ configSchema: Grok47ConfigSchema,
317
+ configJsonSchema: core.toConfigJsonSchema(Grok47ConfigSchema),
318
+ validateConfig: core.zodToStandardSchema(Grok47ConfigSchema)
319
+ };
320
+ var xaiModelDescriptors = [
321
+ grok45ModelDescriptor,
322
+ grok46ModelDescriptor,
323
+ grok47ModelDescriptor
324
+ ];
325
+ var xaiRegistry = core.createModelRegistry(xaiModelDescriptors);
326
+ var xaiPricingVersion = "xai-2026-09-25";
327
+ var XAI_TOOL_RATE_MICRO_USD = {
328
+ web_search_calls: 5e3,
329
+ x_posts_fetched: 5e3,
330
+ x_users_fetched: 1e4
331
+ };
332
+ var XAI_TOOL_COUNTER_KEYS = [
333
+ "web_search_calls",
334
+ "x_posts_fetched",
335
+ "x_users_fetched"
336
+ ];
337
+ var X_SEARCH_ITEM_COUNTERS = ["x_posts_fetched", "x_users_fetched"];
338
+ var LONG_CONTEXT_THRESHOLD = 2e5;
339
+ var XAI_PRICING = Object.freeze({
340
+ // ── grok-4.5 ── $2.00/$6.00 (<200k), $4.00/$12.00 (>=200k); cached $0.30/$0.60
341
+ "grok-4.5": {
342
+ inputPerM: 2e6,
343
+ cachedPerM: 3e5,
344
+ outputPerM: 6e6,
345
+ gt200k: {
346
+ inputPerM: 4e6,
347
+ cachedPerM: 6e5,
348
+ outputPerM: 12e6
349
+ },
350
+ priorityFactor: 2
351
+ },
352
+ // ── grok-4.6 ── $2.00/$6.00 (<200k), $4.00/$12.00 (>=200k); cached $0.50/$1.00
353
+ "grok-4.6": {
354
+ inputPerM: 2e6,
355
+ cachedPerM: 5e5,
356
+ outputPerM: 6e6,
357
+ gt200k: {
358
+ inputPerM: 4e6,
359
+ cachedPerM: 1e6,
360
+ outputPerM: 12e6
361
+ },
362
+ // Confirmed 2026-08-12 by fixture 12 cost_in_usd_ticks (2× list).
363
+ priorityFactor: 2
364
+ },
365
+ // ── grok-4.7 ── $2.00/$0.50/$6.00 (<200k), $4.00/$1.00/$12.00 (≥200k); priority 2×
366
+ "grok-4.7": {
367
+ inputPerM: 2e6,
368
+ cachedPerM: 5e5,
369
+ outputPerM: 6e6,
370
+ gt200k: {
371
+ inputPerM: 4e6,
372
+ cachedPerM: 1e6,
373
+ outputPerM: 12e6
374
+ },
375
+ priorityFactor: 2
376
+ }
377
+ });
378
+ function lookupRates(model) {
379
+ return Object.hasOwn(XAI_PRICING, model) ? XAI_PRICING[model] : void 0;
380
+ }
381
+ var lookupConcreteRates = (model, tier) => {
382
+ const rates = lookupRates(model);
383
+ if (rates === void 0) return void 0;
384
+ if (tier === void 0 || tier === "default") return rates;
385
+ if (tier === "priority" && rates.priorityFactor !== void 0) {
386
+ return scaleRates(rates, rates.priorityFactor);
387
+ }
388
+ return void 0;
389
+ };
390
+ function selectXaiRates(rates, grossInputTokens) {
391
+ if (rates.gt200k !== void 0 && grossInputTokens >= LONG_CONTEXT_THRESHOLD) {
392
+ return rates.gt200k;
393
+ }
394
+ return {
395
+ inputPerM: rates.inputPerM,
396
+ cachedPerM: rates.cachedPerM,
397
+ outputPerM: rates.outputPerM
398
+ };
399
+ }
400
+ function scaleRates(rates, factor) {
401
+ const scaled = {
402
+ inputPerM: rates.inputPerM * factor,
403
+ cachedPerM: rates.cachedPerM * factor,
404
+ outputPerM: rates.outputPerM * factor
405
+ };
406
+ if (rates.gt200k !== void 0) {
407
+ scaled.gt200k = {
408
+ inputPerM: rates.gt200k.inputPerM * factor,
409
+ cachedPerM: rates.gt200k.cachedPerM * factor,
410
+ outputPerM: rates.gt200k.outputPerM * factor
411
+ };
412
+ }
413
+ return scaled;
414
+ }
415
+ function computeXaiCost(model, usage, tier) {
416
+ const listed = lookupConcreteRates(model, tier);
417
+ if (listed === void 0) {
418
+ return core.computeCost(model, usage, tier, lookupConcreteRates, xaiPricingVersion);
419
+ }
420
+ const band = selectXaiRates(listed, usage.inputTokens);
421
+ const bandLookup = () => ({
422
+ inputPerM: band.inputPerM,
423
+ cachedPerM: band.cachedPerM,
424
+ outputPerM: band.outputPerM
425
+ });
426
+ const tokenCost = core.computeCost(model, usage, void 0, bandLookup, xaiPricingVersion);
427
+ const inputCost = tokenCost.details.input;
428
+ const cachedCost = tokenCost.details.cached;
429
+ const outputCost = tokenCost.details.output;
430
+ const serverToolsRequested = usage.details["server_tools_requested"] === 1;
431
+ const xSearchRequested = usage.details["x_search_requested"] === 1;
432
+ const missingXSearchCounter = xSearchRequested && X_SEARCH_ITEM_COUNTERS.some((key) => typeof usage.details[key] !== "number");
433
+ if (missingXSearchCounter || usage.details["server_tools_missing"] === 1) {
434
+ return {
435
+ microUsd: null,
436
+ usd: null,
437
+ pricingVersion: xaiPricingVersion,
438
+ confidence: "estimated",
439
+ details: { input: 0, cached: 0, output: 0, tools: 0 },
440
+ unpricedReason: missingXSearchCounter ? "x_search usage is missing x_posts_fetched or x_users_fetched; refusing to bill a per-call estimate." : "Server tool usage is missing a required counter; refusing to guess a tool cost."
441
+ };
442
+ }
443
+ const attachmentUnpinned = usage.details["attachment_search_unpinned"] === 1;
444
+ const missingWebCounter = serverToolsRequested && !xSearchRequested && !attachmentUnpinned && !XAI_TOOL_COUNTER_KEYS.some((key) => key in usage.details);
445
+ const toolsCost = missingWebCounter ? 0 : XAI_TOOL_COUNTER_KEYS.reduce((sum, key) => {
446
+ const count = usage.details[key];
447
+ if (typeof count !== "number" || count <= 0) return sum;
448
+ return sum + Math.round(count * XAI_TOOL_RATE_MICRO_USD[key]);
449
+ }, 0);
450
+ const microUsd = inputCost + cachedCost + outputCost + toolsCost;
451
+ return {
452
+ microUsd,
453
+ usd: microUsd / 1e6,
454
+ pricingVersion: xaiPricingVersion,
455
+ confidence: missingWebCounter || attachmentUnpinned ? "estimated" : "exact",
456
+ details: {
457
+ input: inputCost,
458
+ cached: cachedCost,
459
+ output: outputCost,
460
+ tools: toolsCost
461
+ }
462
+ };
463
+ }
464
+ function xaiPricingSource() {
465
+ return {
466
+ version: xaiPricingVersion,
467
+ price(model, usage, tier) {
468
+ return computeXaiCost(model, usage, tier);
469
+ },
470
+ hasModel(model) {
471
+ return lookupRates(model) !== void 0;
472
+ },
473
+ listModels() {
474
+ return Object.keys(XAI_PRICING);
475
+ }
476
+ };
477
+ }
478
+
479
+ // src/adapter.ts
33
480
  function isPlainRecord(value) {
34
481
  return typeof value === "object" && value !== null && !Array.isArray(value);
35
482
  }
@@ -132,6 +579,17 @@ function mapXaiProviderOptions(xaiOpts, model) {
132
579
  }
133
580
  return mapped;
134
581
  }
582
+ function parseXaiReplayState(value, model) {
583
+ if (value === void 0) return void 0;
584
+ if (!isPlainRecord(value) || value["model"] !== model || !Array.isArray(value["input"]) || value["input"].length === 0 || value["input"].some(
585
+ (item) => !isPlainRecord(item) || typeof item["type"] !== "string" && typeof item["role"] !== "string"
586
+ )) {
587
+ throw badXaiRequest(
588
+ `transientProviderState must contain the full xAI wire input for model "${model}".`
589
+ );
590
+ }
591
+ return value;
592
+ }
135
593
  function mapXaiSearchTools(tools, model) {
136
594
  if (!Array.isArray(tools)) {
137
595
  throw badXaiRequest(
@@ -338,9 +796,53 @@ function xaiAdapter(opts) {
338
796
  }
339
797
  const warnings = [];
340
798
  const model = req.model;
799
+ if (req.modelDescriptor !== void 0 && (req.modelDescriptor.model !== model || req.modelDescriptor.provider !== "xai")) {
800
+ throw badXaiRequest(`Mismatched xAI model descriptor for "${model}".`);
801
+ }
802
+ if (xaiRegistry.resolve("xai", model)?.capabilities?.statelessReasoningReplay === true && req.modelDescriptor?.capabilities?.statelessReasoningReplay !== true) {
803
+ throw badXaiRequest(
804
+ `A matching xAI model descriptor with statelessReasoningReplay is required for "${model}".`
805
+ );
806
+ }
341
807
  const genConfig = req.config;
342
- const input = [];
808
+ const xaiProviderConfig = mapXaiProviderOptions(
809
+ genConfig.providerOptions?.["xai"],
810
+ model
811
+ );
812
+ const replayRequired = req.modelDescriptor?.capabilities?.statelessReasoningReplay === true;
813
+ const replayState = parseXaiReplayState(req.transientProviderState, model);
814
+ if (replayState !== void 0 && !replayRequired) {
815
+ throw badXaiRequest(
816
+ `transientProviderState requires a statelessReasoningReplay model descriptor for "${model}".`
817
+ );
818
+ }
819
+ if (replayState !== void 0 && req.messages.length === 0) {
820
+ throw badXaiRequest(
821
+ `Stateless conversation replay for model "${model}" requires new messages to append.`
822
+ );
823
+ }
824
+ const input = [...replayState?.input ?? []];
825
+ const replayCallIds = new Set(
826
+ replayState?.input.filter((item) => isPlainRecord(item) && item["type"] === "function_call").map((item) => isPlainRecord(item) ? item["call_id"] : void 0).filter((id) => typeof id === "string") ?? []
827
+ );
828
+ const replayedResultIds = new Set(
829
+ replayState?.input.filter(
830
+ (item) => isPlainRecord(item) && item["type"] === "function_call_output"
831
+ ).map((item) => isPlainRecord(item) ? item["call_id"] : void 0).filter((id) => typeof id === "string") ?? []
832
+ );
343
833
  for (const msg of req.messages) {
834
+ if (replayState !== void 0 && msg.role === "assistant") {
835
+ throw badXaiRequest(
836
+ `New messages for model "${model}" cannot contain assistant history when transientProviderState is supplied.`
837
+ );
838
+ }
839
+ if (replayRequired && replayState === void 0 && msg.parts.some(
840
+ (part) => part.kind === "tool-call" || part.kind === "tool-result"
841
+ )) {
842
+ throw badXaiRequest(
843
+ `Function-call history for model "${model}" requires transientProviderState from the prior result.`
844
+ );
845
+ }
344
846
  const contentParts = [];
345
847
  for (const part of msg.parts) {
346
848
  if (part.kind === "tool-call") {
@@ -359,6 +861,12 @@ function xaiAdapter(opts) {
359
861
  continue;
360
862
  }
361
863
  if (part.kind === "tool-result") {
864
+ if (replayState !== void 0 && (!replayCallIds.has(part.toolCallId) || replayedResultIds.has(part.toolCallId))) {
865
+ throw badXaiRequest(
866
+ `Tool result "${part.toolCallId}" must match an unanswered function call in transientProviderState.`
867
+ );
868
+ }
869
+ replayedResultIds.add(part.toolCallId);
362
870
  if (contentParts.length > 0) {
363
871
  input.push({
364
872
  role: msg.role === "assistant" ? "assistant" : "user",
@@ -421,9 +929,9 @@ function xaiAdapter(opts) {
421
929
  }
422
930
  if (reasoning.effort !== void 0) {
423
931
  const effort = reasoning.effort;
424
- if (effort === "none") {
932
+ if (effort === "none" || effort === "max") {
425
933
  throw badXaiRequest(
426
- `reasoning.effort "none" is not supported for xai model "${model}".`
934
+ `reasoning.effort "${effort}" is not supported for xai model "${model}".`
427
935
  );
428
936
  }
429
937
  const admitted = req.modelDescriptor?.capabilities?.admittedReasoningEfforts;
@@ -443,15 +951,13 @@ function xaiAdapter(opts) {
443
951
  format: { type: "json_schema", name, schema, strict: true }
444
952
  };
445
953
  }
446
- const xaiProviderConfig = mapXaiProviderOptions(
447
- genConfig.providerOptions?.["xai"],
448
- model
449
- );
450
954
  if (xaiProviderConfig.promptCacheKey !== void 0) {
451
955
  params.prompt_cache_key = xaiProviderConfig.promptCacheKey;
452
956
  }
453
- const hasFileRef = req.messages.some(
454
- (msg) => msg.parts.some((part) => part.kind === "file-ref")
957
+ const hasFileRef = input.some(
958
+ (item) => isPlainRecord(item) && Array.isArray(item["content"]) && item["content"].some(
959
+ (part) => isPlainRecord(part) && part["type"] === "input_file"
960
+ )
455
961
  );
456
962
  const searchTools = xaiProviderConfig.tools;
457
963
  if (searchTools !== void 0) {
@@ -460,6 +966,11 @@ function xaiAdapter(opts) {
460
966
  `providerOptions.xai.tools requires capabilities.grounding on the model descriptor for "${model}".`
461
967
  );
462
968
  }
969
+ if (structuredOutputRequested && req.modelDescriptor.capabilities.structuredOutputWithTools !== true) {
970
+ throw badXaiRequest(
971
+ `Structured output with providerOptions.xai.tools is not supported for model "${model}".`
972
+ );
973
+ }
463
974
  params.tools = searchTools;
464
975
  }
465
976
  if (req.tools !== void 0 && req.tools.length > 0) {
@@ -548,6 +1059,9 @@ function xaiAdapter(opts) {
548
1059
  xaiProviderConfig.tools);
549
1060
  if (expectedToolCounters.length > 0 || hasFileRef) {
550
1061
  usage.details["server_tools_requested"] = 1;
1062
+ if (xaiProviderConfig.tools?.some((tool) => tool["type"] === "x_search") === true) {
1063
+ usage.details["x_search_requested"] = 1;
1064
+ }
551
1065
  const missing = expectedToolCounters.filter((key) => !(key in usage.details));
552
1066
  if (missing.length > 0) {
553
1067
  usage.details["server_tools_missing"] = 1;
@@ -555,7 +1069,7 @@ function xaiAdapter(opts) {
555
1069
  type: "other",
556
1070
  message: `xai: server tools were requested but usage is missing counters [${missing.join(
557
1071
  ", "
558
- )}]; tool cost will be estimated.`
1072
+ )}]; the call is unpriced.`
559
1073
  });
560
1074
  }
561
1075
  if (hasFileRef) {
@@ -575,6 +1089,14 @@ function xaiAdapter(opts) {
575
1089
  if (isPlainRecord(response.metadata)) {
576
1090
  providerMeta["metadata"] = response.metadata;
577
1091
  }
1092
+ let transientProviderState;
1093
+ if (replayRequired) {
1094
+ const state = {
1095
+ model,
1096
+ input: [...params.input, ...response.output]
1097
+ };
1098
+ transientProviderState = state;
1099
+ }
578
1100
  const servedServiceTier = typeof response.service_tier === "string" && response.service_tier.length > 0 ? response.service_tier : void 0;
579
1101
  const result = {
580
1102
  model: response.model,
@@ -587,6 +1109,7 @@ function xaiAdapter(opts) {
587
1109
  ...rawStructured !== void 0 ? { rawStructured } : {},
588
1110
  ...servedServiceTier !== void 0 ? { servedServiceTier } : {},
589
1111
  ...Object.keys(providerMeta).length > 0 ? { providerMetadata: providerMeta } : {},
1112
+ ...transientProviderState !== void 0 ? { transientProviderState } : {},
590
1113
  ...citations.length > 0 ? { citations } : {},
591
1114
  ...toolCalls.length > 0 ? { toolCalls, finishReason: "tool_calls" } : {}
592
1115
  };
@@ -658,13 +1181,12 @@ function xaiAdapter(opts) {
658
1181
  };
659
1182
  }
660
1183
  var WEB_SEARCH_COUNTER = "web_search_calls";
661
- var X_SEARCH_COUNTER = "x_search_calls";
662
1184
  function expectedServerToolCounters(tools, _hasFileRef) {
663
1185
  const keys = [];
664
1186
  if (tools !== void 0) {
665
1187
  for (const tool of tools) {
666
1188
  if (tool["type"] === "web_search") keys.push(WEB_SEARCH_COUNTER);
667
- if (tool["type"] === "x_search") keys.push(X_SEARCH_COUNTER);
1189
+ if (tool["type"] === "x_search") keys.push(...X_SEARCH_ITEM_COUNTERS);
668
1190
  }
669
1191
  }
670
1192
  return keys;
@@ -989,553 +1511,211 @@ var XaiFileStore = class {
989
1511
  this.filesUrl(),
990
1512
  this.requestInit("POST", {
991
1513
  body: form,
992
- ...signal !== void 0 ? { signal } : {}
993
- })
994
- );
995
- } catch (e) {
996
- if (signal?.aborted === true) {
997
- throw new core.LlmError("xAI file upload aborted", {
998
- kind: "aborted",
999
- retryable: false,
1000
- provider: "xai"
1001
- });
1002
- }
1003
- throw classifyStoreError(e);
1004
- }
1005
- if (!res.ok) {
1006
- try {
1007
- await throwHttpFailure(res);
1008
- } catch (e) {
1009
- throw classifyStoreError(e);
1010
- }
1011
- }
1012
- let json;
1013
- try {
1014
- json = await res.json();
1015
- } catch (e) {
1016
- throw new core.LlmError("xAI file upload returned non-JSON body", {
1017
- kind: "server",
1018
- retryable: false,
1019
- provider: "xai",
1020
- cause: e
1021
- });
1022
- }
1023
- return makeHandle(json);
1024
- }
1025
- async get(fileId, signal) {
1026
- if (typeof fileId !== "string" || fileId.trim() === "") {
1027
- throw badRequest("fileId must be a non-empty string.");
1028
- }
1029
- let res;
1030
- try {
1031
- res = await this.fetchImpl(
1032
- this.filesUrl(fileId),
1033
- this.requestInit("GET", signal !== void 0 ? { signal } : {})
1034
- );
1035
- } catch (e) {
1036
- if (signal?.aborted === true) {
1037
- throw new core.LlmError("xAI file get aborted", {
1038
- kind: "aborted",
1039
- retryable: false,
1040
- provider: "xai"
1041
- });
1042
- }
1043
- throw classifyStoreError(e);
1044
- }
1045
- if (res.status === 404) {
1046
- throw notFoundError(fileId, "get");
1047
- }
1048
- if (!res.ok) {
1049
- try {
1050
- await throwHttpFailure(res);
1051
- } catch (e) {
1052
- throw classifyStoreError(e);
1053
- }
1054
- }
1055
- const json = await res.json();
1056
- return makeHandle(json);
1057
- }
1058
- async list(opts = {}, signal) {
1059
- if (opts.limit !== void 0) {
1060
- if (typeof opts.limit !== "number" || !Number.isInteger(opts.limit) || opts.limit < 1 || opts.limit > 100) {
1061
- throw badRequest("list.limit must be an integer in [1, 100].");
1062
- }
1063
- }
1064
- const params = new URLSearchParams();
1065
- if (opts.limit !== void 0) params.set("limit", String(opts.limit));
1066
- if (opts.order !== void 0) params.set("order", opts.order);
1067
- if (opts.sortBy !== void 0) params.set("sort_by", opts.sortBy);
1068
- if (opts.paginationToken !== void 0) {
1069
- params.set("pagination_token", opts.paginationToken);
1070
- }
1071
- const qs = params.toString();
1072
- const url = qs.length > 0 ? `${this.filesUrl()}?${qs}` : this.filesUrl();
1073
- let res;
1074
- try {
1075
- res = await this.fetchImpl(
1076
- url,
1077
- this.requestInit("GET", signal !== void 0 ? { signal } : {})
1078
- );
1079
- } catch (e) {
1080
- if (signal?.aborted === true) {
1081
- throw new core.LlmError("xAI file list aborted", {
1082
- kind: "aborted",
1083
- retryable: false,
1084
- provider: "xai"
1085
- });
1086
- }
1087
- throw classifyStoreError(e);
1088
- }
1089
- if (!res.ok) {
1090
- try {
1091
- await throwHttpFailure(res);
1092
- } catch (e) {
1093
- throw classifyStoreError(e);
1094
- }
1095
- }
1096
- const json = await res.json();
1097
- const files = Array.isArray(json.data) ? json.data.map((f) => makeHandle(f)) : [];
1098
- const result = { files };
1099
- if (typeof json.pagination_token === "string" && json.pagination_token.length > 0) {
1100
- result.paginationToken = json.pagination_token;
1101
- }
1102
- return result;
1103
- }
1104
- /**
1105
- * Delete a file. Idempotent: HTTP 404 → success.
1106
- *
1107
- * Default (`failClosed` omitted/false): non-404 errors go to `onDeleteError`
1108
- * and resolve (P5 fail-open). With `failClosed: true`, non-404 errors throw
1109
- * typed `LlmError` and `onDeleteError` is not called.
1110
- *
1111
- * Empty/blank ids always throw `bad_request` (caller fault).
1112
- */
1113
- async delete(fileIdOrHandle, opts) {
1114
- const fileId = resolveFileId(fileIdOrHandle);
1115
- if (typeof fileId !== "string" || fileId.trim() === "") {
1116
- throw badRequest("fileId must be a non-empty string.");
1117
- }
1118
- const failClosed = opts?.failClosed === true;
1119
- const signal = opts?.signal;
1120
- try {
1121
- const res = await this.fetchImpl(
1122
- this.filesUrl(fileId),
1123
- this.requestInit("DELETE", signal !== void 0 ? { signal } : {})
1124
- );
1125
- if (res.status === 404) {
1126
- return;
1127
- }
1128
- if (!res.ok) {
1129
- await throwHttpFailure(res);
1130
- }
1131
- } catch (err) {
1132
- if (isNotFoundError(err)) {
1133
- return;
1134
- }
1135
- const classified = signal?.aborted === true && !(err instanceof core.LlmError) ? new core.LlmError("xAI file delete aborted", {
1136
- kind: "aborted",
1137
- retryable: false,
1138
- provider: "xai",
1139
- cause: err
1140
- }) : classifyStoreError(err);
1141
- if (failClosed) {
1142
- throw classified;
1143
- }
1144
- this.onDeleteError(fileId, classified);
1145
- }
1146
- }
1147
- /**
1148
- * Delete many files.
1149
- *
1150
- * Fail-open (default): `Promise.allSettled` — each failure → `onDeleteError`.
1151
- * Fail-closed: `Promise.all` — first throw rejects; in-flight siblings are
1152
- * not cancelled (partial deletes may already have succeeded at the provider).
1153
- * Prefer per-id delete + host DB mark when gating durable release state.
1154
- */
1155
- async deleteAll(ids, opts) {
1156
- if (opts?.failClosed === true) {
1157
- await Promise.all(ids.map((id) => this.delete(id, opts)));
1158
- return;
1159
- }
1160
- await Promise.allSettled(ids.map((id) => this.delete(id, opts)));
1161
- }
1162
- /** Download raw file bytes. */
1163
- async getContent(fileId, signal) {
1164
- if (typeof fileId !== "string" || fileId.trim() === "") {
1165
- throw badRequest("fileId must be a non-empty string.");
1166
- }
1167
- let res;
1168
- try {
1169
- res = await this.fetchImpl(
1170
- this.filesUrl(`${fileId}/content`),
1171
- this.requestInit("GET", signal !== void 0 ? { signal } : {})
1514
+ ...signal !== void 0 ? { signal } : {}
1515
+ })
1172
1516
  );
1173
1517
  } catch (e) {
1174
1518
  if (signal?.aborted === true) {
1175
- throw new core.LlmError("xAI file content download aborted", {
1176
- kind: "aborted",
1177
- retryable: false,
1178
- provider: "xai"
1179
- });
1180
- }
1181
- throw classifyStoreError(e);
1182
- }
1183
- if (res.status === 404) {
1184
- throw notFoundError(fileId, "getContent");
1185
- }
1186
- if (!res.ok) {
1187
- try {
1188
- await throwHttpFailure(res);
1189
- } catch (e) {
1190
- throw classifyStoreError(e);
1191
- }
1192
- }
1193
- const buf = await res.arrayBuffer();
1194
- return new Uint8Array(buf);
1195
- }
1196
- };
1197
- var webSearchFlags = {
1198
- enableImageUnderstanding: zod.z.boolean().optional().meta({
1199
- title: "Enable Image Understanding",
1200
- description: "Ask xAI to analyze images found during web search."
1201
- }),
1202
- enableImageSearch: zod.z.boolean().optional().meta({
1203
- title: "Enable Image Search",
1204
- description: "Ask xAI to include image search results."
1205
- })
1206
- };
1207
- var XaiWebSearchToolSchema = zod.z.union([
1208
- zod.z.strictObject({
1209
- type: zod.z.literal("web_search"),
1210
- allowedDomains: zod.z.array(zod.z.string()).max(5),
1211
- ...webSearchFlags
1212
- }),
1213
- zod.z.strictObject({
1214
- type: zod.z.literal("web_search"),
1215
- excludedDomains: zod.z.array(zod.z.string()).max(5),
1216
- ...webSearchFlags
1217
- }),
1218
- zod.z.strictObject({
1219
- type: zod.z.literal("web_search"),
1220
- ...webSearchFlags
1221
- })
1222
- ]).meta({
1223
- title: "XaiWebSearchTool",
1224
- description: "xAI web_search server tool. allowedDomains and excludedDomains are mutually exclusive (max 5)."
1225
- });
1226
- var xSearchFlags = {
1227
- fromDate: zod.z.iso.date().optional().meta({
1228
- title: "From Date",
1229
- description: "Inclusive ISO-8601 date lower bound for X search."
1230
- }),
1231
- toDate: zod.z.iso.date().optional().meta({
1232
- title: "To Date",
1233
- description: "Inclusive ISO-8601 date upper bound for X search."
1234
- }),
1235
- enableImageUnderstanding: zod.z.boolean().optional().meta({
1236
- title: "Enable Image Understanding",
1237
- description: "Ask xAI to analyze images in matched posts."
1238
- }),
1239
- enableVideoUnderstanding: zod.z.boolean().optional().meta({
1240
- title: "Enable Video Understanding",
1241
- description: "Ask xAI to analyze videos in matched posts."
1242
- })
1243
- };
1244
- var XaiXSearchToolSchema = zod.z.union([
1245
- zod.z.strictObject({
1246
- type: zod.z.literal("x_search"),
1247
- allowedXHandles: zod.z.array(zod.z.string()).max(20),
1248
- ...xSearchFlags
1249
- }),
1250
- zod.z.strictObject({
1251
- type: zod.z.literal("x_search"),
1252
- excludedXHandles: zod.z.array(zod.z.string()).max(20),
1253
- ...xSearchFlags
1254
- }),
1255
- zod.z.strictObject({
1256
- type: zod.z.literal("x_search"),
1257
- ...xSearchFlags
1258
- })
1259
- ]).meta({
1260
- title: "XaiXSearchTool",
1261
- description: "xAI x_search server tool. allowedXHandles and excludedXHandles are mutually exclusive (max 20)."
1262
- });
1263
- var XaiToolsSchema = zod.z.union([
1264
- zod.z.tuple([XaiWebSearchToolSchema]),
1265
- zod.z.tuple([XaiXSearchToolSchema]),
1266
- zod.z.tuple([XaiWebSearchToolSchema, XaiXSearchToolSchema]),
1267
- zod.z.tuple([XaiXSearchToolSchema, XaiWebSearchToolSchema])
1268
- ]).meta({
1269
- title: "XaiTools",
1270
- description: "xAI Live Search tools. At most one web_search and at most one x_search."
1271
- });
1272
- var XaiProviderOptionsSchema = zod.z.strictObject({
1273
- promptCacheKey: zod.z.string().min(1).optional().meta({
1274
- title: "Prompt Cache Key",
1275
- description: "xAI conversation-routing cache key \u2014 maps to Responses API `prompt_cache_key`."
1276
- }),
1277
- tools: XaiToolsSchema.optional().meta({
1278
- title: "Live Search Tools",
1279
- description: "xAI server-side web_search / x_search tools."
1280
- }),
1281
- parallelToolCalls: zod.z.boolean().optional().meta({
1282
- title: "Parallel Tool Calls",
1283
- description: "xAI Responses parallel_tool_calls. Not a generic contract field."
1284
- })
1285
- }).meta({
1286
- title: "xAI Provider Options",
1287
- description: "Allowlisted xAI provider options."
1288
- });
1289
-
1290
- // src/model-config/grok-4-5.ts
1291
- var Grok45ConfigSchema = zod.z.strictObject({
1292
- temperature: zod.z.number().optional().meta({
1293
- title: "Temperature",
1294
- description: "Sampling temperature forwarded verbatim to grok-4.5."
1295
- }),
1296
- topP: zod.z.number().optional().meta({
1297
- title: "Top P",
1298
- description: "Nucleus sampling parameter forwarded verbatim to grok-4.5."
1299
- }),
1300
- maxOutputTokens: zod.z.number().int().positive().optional().meta({
1301
- title: "Max Output Tokens",
1302
- 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."
1303
- }),
1304
- reasoning: zod.z.strictObject({
1305
- effort: zod.z.enum(["low", "medium", "high"]).meta({
1306
- title: "Reasoning Effort",
1307
- description: 'Reasoning effort for grok-4.5. "low", "medium", and "high" are admitted (live-verified 2026-08-24); "none"/"xhigh" are rejected by the live API. Vendor default when omitted is "high".'
1308
- })
1309
- }).optional().meta({
1310
- title: "Reasoning",
1311
- description: "grok-4.5 effort-level reasoning configuration. No budgetTokens field \u2014 xAI uses level-style reasoning, not token budgets."
1312
- }),
1313
- timeoutMs: zod.z.number().int().positive().optional().meta({
1314
- title: "Timeout",
1315
- description: "Logical request timeout in milliseconds."
1316
- }),
1317
- providerOptions: zod.z.strictObject({
1318
- xai: XaiProviderOptionsSchema.optional()
1319
- }).optional().meta({
1320
- title: "Provider Options",
1321
- description: "Provider-specific options accepted for grok-4.5."
1322
- })
1323
- }).meta({
1324
- title: "Grok45Config",
1325
- description: "Strict Responses API config for model grok-4.5. Level reasoning (low/medium/high), tunable sampling, no service tiers, structured output, vision, Live Search tools, priced.",
1326
- examples: [{ reasoning: { effort: "high" } }]
1327
- });
1328
- var Grok46ConfigSchema = zod.z.strictObject({
1329
- temperature: zod.z.number().optional().meta({
1330
- title: "Temperature",
1331
- description: "Sampling temperature forwarded verbatim to grok-4.6."
1332
- }),
1333
- topP: zod.z.number().optional().meta({
1334
- title: "Top P",
1335
- description: "Nucleus sampling parameter forwarded verbatim to grok-4.6."
1336
- }),
1337
- maxOutputTokens: zod.z.number().int().positive().optional().meta({
1338
- title: "Max Output Tokens",
1339
- description: "Maximum output token cap for grok-4.6. No artificial ceiling \u2014 xAI accepts arbitrarily large values; truncation surfaces as finishReason:'length', not an error."
1340
- }),
1341
- reasoning: zod.z.strictObject({
1342
- effort: zod.z.enum(["low", "medium", "high", "xhigh"]).meta({
1343
- title: "Reasoning Effort",
1344
- description: 'Reasoning effort for grok-4.6. Live-verified 2026-08-12: "low", "medium", "high", and "xhigh" are accepted; "none" is rejected. Vendor default when omitted is "high".'
1345
- })
1346
- }).optional().meta({
1347
- title: "Reasoning",
1348
- description: "grok-4.6 effort-level reasoning configuration. No budgetTokens field \u2014 xAI uses level-style reasoning, not token budgets."
1349
- }),
1350
- serviceTier: zod.z.literal("priority").optional().meta({
1351
- title: "Service Tier",
1352
- description: 'xAI priority processing for grok-4.6 (Responses `service_tier: "priority"`). Echo live-verified 2026-08-12. Bills at 2\xD7 after the cache discount (uncached standard-list 2\xD7 confirmed by live ticks; cached/long-context legs follow the official 2\xD7 rule). Omitted requests stay on xAI default. "flex"/"standard"/"batch" are rejected.'
1353
- }),
1354
- timeoutMs: zod.z.number().int().positive().optional().meta({
1355
- title: "Timeout",
1356
- description: "Logical request timeout in milliseconds."
1357
- }),
1358
- providerOptions: zod.z.strictObject({
1359
- xai: XaiProviderOptionsSchema.optional()
1360
- }).optional().meta({
1361
- title: "Provider Options",
1362
- description: "Provider-specific options accepted for grok-4.6."
1363
- })
1364
- }).meta({
1365
- title: "Grok46Config",
1366
- description: "Strict Responses API config for model grok-4.6. Level reasoning (low/medium/high/xhigh), optional priority service tier, tunable sampling, structured output, vision, priced.",
1367
- examples: [{ reasoning: { effort: "high" } }]
1368
- });
1369
-
1370
- // src/models.ts
1371
- var grok45ModelDescriptor = {
1372
- model: "grok-4.5",
1373
- provider: "xai",
1374
- pricingFamily: "grok-4.5",
1375
- capabilities: {
1376
- reasoning: true,
1377
- reasoningApi: "level",
1378
- admittedReasoningEfforts: ["low", "medium", "high"],
1379
- structuredOutput: true,
1380
- nativeStructuredOutput: true,
1381
- vision: true,
1382
- audioInput: false,
1383
- sampling: "tunable",
1384
- caching: { explicit: false, minTokens: 0 },
1385
- grounding: true,
1386
- functionCalling: true
1387
- // No serviceTiers key — grok-4.5 has no admitted service-tier vocabulary.
1388
- },
1389
- configSchema: Grok45ConfigSchema,
1390
- configJsonSchema: core.toConfigJsonSchema(Grok45ConfigSchema),
1391
- validateConfig: core.zodToStandardSchema(Grok45ConfigSchema)
1392
- };
1393
- var grok46ModelDescriptor = {
1394
- model: "grok-4.6",
1395
- provider: "xai",
1396
- pricingFamily: "grok-4.6",
1397
- capabilities: {
1398
- reasoning: true,
1399
- reasoningApi: "level",
1400
- admittedReasoningEfforts: ["low", "medium", "high", "xhigh"],
1401
- structuredOutput: true,
1402
- nativeStructuredOutput: true,
1403
- vision: true,
1404
- audioInput: false,
1405
- sampling: "tunable",
1406
- caching: { explicit: false, minTokens: 0 },
1407
- grounding: true,
1408
- functionCalling: true,
1409
- serviceTiers: ["priority"]
1410
- },
1411
- configSchema: Grok46ConfigSchema,
1412
- configJsonSchema: core.toConfigJsonSchema(Grok46ConfigSchema),
1413
- validateConfig: core.zodToStandardSchema(Grok46ConfigSchema)
1414
- };
1415
- var xaiModelDescriptors = [
1416
- grok45ModelDescriptor,
1417
- grok46ModelDescriptor
1418
- ];
1419
- var xaiRegistry = core.createModelRegistry(xaiModelDescriptors);
1420
-
1421
- // src/pricing.ts
1422
- var xaiPricingVersion = "xai-2026-08-24";
1423
- var XAI_TOOL_RATE_MICRO_USD = {
1424
- web_search_calls: 5e3,
1425
- x_search_calls: 5e3
1426
- };
1427
- var XAI_TOOL_COUNTER_KEYS = ["web_search_calls", "x_search_calls"];
1428
- var XAI_PRICING = Object.freeze({
1429
- // ── grok-4.5 ── $2.00/$6.00 (≤200k), $4.00/$12.00 (>200k); cached $0.30/$0.60
1430
- "grok-4.5": {
1431
- inputPerM: 2e6,
1432
- cachedPerM: 3e5,
1433
- outputPerM: 6e6,
1434
- gt200k: {
1435
- inputPerM: 4e6,
1436
- cachedPerM: 6e5,
1437
- outputPerM: 12e6
1519
+ throw new core.LlmError("xAI file upload aborted", {
1520
+ kind: "aborted",
1521
+ retryable: false,
1522
+ provider: "xai"
1523
+ });
1524
+ }
1525
+ throw classifyStoreError(e);
1438
1526
  }
1439
- },
1440
- // ── grok-4.6 ── $2.00/$6.00 (≤200k), $4.00/$12.00 (>200k); cached $0.50/$1.00
1441
- "grok-4.6": {
1442
- inputPerM: 2e6,
1443
- cachedPerM: 5e5,
1444
- outputPerM: 6e6,
1445
- gt200k: {
1446
- inputPerM: 4e6,
1447
- cachedPerM: 1e6,
1448
- outputPerM: 12e6
1449
- },
1450
- // Confirmed 2026-08-12 by fixture 12 cost_in_usd_ticks (2× list).
1451
- priorityFactor: 2
1527
+ if (!res.ok) {
1528
+ try {
1529
+ await throwHttpFailure(res);
1530
+ } catch (e) {
1531
+ throw classifyStoreError(e);
1532
+ }
1533
+ }
1534
+ let json;
1535
+ try {
1536
+ json = await res.json();
1537
+ } catch (e) {
1538
+ throw new core.LlmError("xAI file upload returned non-JSON body", {
1539
+ kind: "server",
1540
+ retryable: false,
1541
+ provider: "xai",
1542
+ cause: e
1543
+ });
1544
+ }
1545
+ return makeHandle(json);
1452
1546
  }
1453
- });
1454
- var LONG_CONTEXT_THRESHOLD = 2e5;
1455
- function lookupRates(model) {
1456
- return XAI_PRICING[model];
1457
- }
1458
- function selectRates(rates, grossInputTokens) {
1459
- if (rates.gt200k !== void 0 && grossInputTokens > LONG_CONTEXT_THRESHOLD) {
1460
- return rates.gt200k;
1547
+ async get(fileId, signal) {
1548
+ if (typeof fileId !== "string" || fileId.trim() === "") {
1549
+ throw badRequest("fileId must be a non-empty string.");
1550
+ }
1551
+ let res;
1552
+ try {
1553
+ res = await this.fetchImpl(
1554
+ this.filesUrl(fileId),
1555
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
1556
+ );
1557
+ } catch (e) {
1558
+ if (signal?.aborted === true) {
1559
+ throw new core.LlmError("xAI file get aborted", {
1560
+ kind: "aborted",
1561
+ retryable: false,
1562
+ provider: "xai"
1563
+ });
1564
+ }
1565
+ throw classifyStoreError(e);
1566
+ }
1567
+ if (res.status === 404) {
1568
+ throw notFoundError(fileId, "get");
1569
+ }
1570
+ if (!res.ok) {
1571
+ try {
1572
+ await throwHttpFailure(res);
1573
+ } catch (e) {
1574
+ throw classifyStoreError(e);
1575
+ }
1576
+ }
1577
+ const json = await res.json();
1578
+ return makeHandle(json);
1461
1579
  }
1462
- return {
1463
- inputPerM: rates.inputPerM,
1464
- cachedPerM: rates.cachedPerM,
1465
- outputPerM: rates.outputPerM
1466
- };
1467
- }
1468
- function computeXaiCost(model, usage, tier) {
1469
- const rates = lookupRates(model);
1470
- if (rates === void 0) {
1471
- return {
1472
- microUsd: null,
1473
- usd: null,
1474
- pricingVersion: xaiPricingVersion,
1475
- confidence: "estimated",
1476
- details: { input: 0, cached: 0, output: 0, tools: 0 },
1477
- unpricedReason: `Unknown model "${model}"; no pricing entry found.`
1478
- };
1580
+ async list(opts = {}, signal) {
1581
+ if (opts.limit !== void 0) {
1582
+ if (typeof opts.limit !== "number" || !Number.isInteger(opts.limit) || opts.limit < 1 || opts.limit > 100) {
1583
+ throw badRequest("list.limit must be an integer in [1, 100].");
1584
+ }
1585
+ }
1586
+ const params = new URLSearchParams();
1587
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
1588
+ if (opts.order !== void 0) params.set("order", opts.order);
1589
+ if (opts.sortBy !== void 0) params.set("sort_by", opts.sortBy);
1590
+ if (opts.paginationToken !== void 0) {
1591
+ params.set("pagination_token", opts.paginationToken);
1592
+ }
1593
+ const qs = params.toString();
1594
+ const url = qs.length > 0 ? `${this.filesUrl()}?${qs}` : this.filesUrl();
1595
+ let res;
1596
+ try {
1597
+ res = await this.fetchImpl(
1598
+ url,
1599
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
1600
+ );
1601
+ } catch (e) {
1602
+ if (signal?.aborted === true) {
1603
+ throw new core.LlmError("xAI file list aborted", {
1604
+ kind: "aborted",
1605
+ retryable: false,
1606
+ provider: "xai"
1607
+ });
1608
+ }
1609
+ throw classifyStoreError(e);
1610
+ }
1611
+ if (!res.ok) {
1612
+ try {
1613
+ await throwHttpFailure(res);
1614
+ } catch (e) {
1615
+ throw classifyStoreError(e);
1616
+ }
1617
+ }
1618
+ const json = await res.json();
1619
+ const files = Array.isArray(json.data) ? json.data.map((f) => makeHandle(f)) : [];
1620
+ const result = { files };
1621
+ if (typeof json.pagination_token === "string" && json.pagination_token.length > 0) {
1622
+ result.paginationToken = json.pagination_token;
1623
+ }
1624
+ return result;
1479
1625
  }
1480
- let factor = 1;
1481
- if (tier !== void 0 && tier !== "default") {
1482
- if (tier === "priority" && rates.priorityFactor !== void 0) {
1483
- factor = rates.priorityFactor;
1484
- } else {
1485
- return {
1486
- microUsd: null,
1487
- usd: null,
1488
- pricingVersion: xaiPricingVersion,
1489
- confidence: "estimated",
1490
- details: { input: 0, cached: 0, output: 0, tools: 0 },
1491
- unpricedReason: `Unknown service tier "${tier}"; xai model "${model}" has no such tier, refusing to guess a pricing multiplier.`
1492
- };
1626
+ /**
1627
+ * Delete a file. Idempotent: HTTP 404 → success.
1628
+ *
1629
+ * Default (`failClosed` omitted/false): non-404 errors go to `onDeleteError`
1630
+ * and resolve (P5 fail-open). With `failClosed: true`, non-404 errors throw
1631
+ * typed `LlmError` and `onDeleteError` is not called.
1632
+ *
1633
+ * Empty/blank ids always throw `bad_request` (caller fault).
1634
+ */
1635
+ async delete(fileIdOrHandle, opts) {
1636
+ const fileId = resolveFileId(fileIdOrHandle);
1637
+ if (typeof fileId !== "string" || fileId.trim() === "") {
1638
+ throw badRequest("fileId must be a non-empty string.");
1639
+ }
1640
+ const failClosed = opts?.failClosed === true;
1641
+ const signal = opts?.signal;
1642
+ try {
1643
+ const res = await this.fetchImpl(
1644
+ this.filesUrl(fileId),
1645
+ this.requestInit("DELETE", signal !== void 0 ? { signal } : {})
1646
+ );
1647
+ if (res.status === 404) {
1648
+ return;
1649
+ }
1650
+ if (!res.ok) {
1651
+ await throwHttpFailure(res);
1652
+ }
1653
+ } catch (err) {
1654
+ if (isNotFoundError(err)) {
1655
+ return;
1656
+ }
1657
+ const classified = signal?.aborted === true && !(err instanceof core.LlmError) ? new core.LlmError("xAI file delete aborted", {
1658
+ kind: "aborted",
1659
+ retryable: false,
1660
+ provider: "xai",
1661
+ cause: err
1662
+ }) : classifyStoreError(err);
1663
+ if (failClosed) {
1664
+ throw classified;
1665
+ }
1666
+ this.onDeleteError(fileId, classified);
1493
1667
  }
1494
1668
  }
1495
- const base = selectRates(rates, usage.inputTokens);
1496
- const cached = usage.cachedInputTokens ?? 0;
1497
- const billableInput = Math.max(0, usage.inputTokens - cached);
1498
- const inputCost = Math.round(billableInput * base.inputPerM * factor / 1e6);
1499
- const cachedCost = Math.round(cached * base.cachedPerM * factor / 1e6);
1500
- const outputCost = Math.round(
1501
- usage.outputTokens * base.outputPerM * factor / 1e6
1502
- );
1503
- const serverToolsRequested = usage.details["server_tools_requested"] === 1;
1504
- const missingRequestedCounters = usage.details["server_tools_missing"] === 1 || serverToolsRequested && usage.details["attachment_search_unpinned"] !== 1 && !XAI_TOOL_COUNTER_KEYS.some((key) => key in usage.details);
1505
- const attachmentUnpinned = usage.details["attachment_search_unpinned"] === 1;
1506
- const toolsCost = missingRequestedCounters ? 0 : XAI_TOOL_COUNTER_KEYS.reduce((sum, key) => {
1507
- const count = usage.details[key];
1508
- if (typeof count !== "number" || count <= 0) return sum;
1509
- return sum + Math.round(count * XAI_TOOL_RATE_MICRO_USD[key]);
1510
- }, 0);
1511
- const microUsd = inputCost + cachedCost + outputCost + toolsCost;
1512
- return {
1513
- microUsd,
1514
- usd: microUsd / 1e6,
1515
- pricingVersion: xaiPricingVersion,
1516
- confidence: missingRequestedCounters || attachmentUnpinned ? "estimated" : "exact",
1517
- details: {
1518
- input: inputCost,
1519
- cached: cachedCost,
1520
- output: outputCost,
1521
- tools: toolsCost
1669
+ /**
1670
+ * Delete many files.
1671
+ *
1672
+ * Fail-open (default): `Promise.allSettled` — each failure → `onDeleteError`.
1673
+ * Fail-closed: `Promise.all` — first throw rejects; in-flight siblings are
1674
+ * not cancelled (partial deletes may already have succeeded at the provider).
1675
+ * Prefer per-id delete + host DB mark when gating durable release state.
1676
+ */
1677
+ async deleteAll(ids, opts) {
1678
+ if (opts?.failClosed === true) {
1679
+ await Promise.all(ids.map((id) => this.delete(id, opts)));
1680
+ return;
1522
1681
  }
1523
- };
1524
- }
1525
- function xaiPricingSource() {
1526
- return {
1527
- version: xaiPricingVersion,
1528
- price(model, usage, tier) {
1529
- return computeXaiCost(model, usage, tier);
1530
- },
1531
- hasModel(model) {
1532
- return lookupRates(model) !== void 0;
1533
- },
1534
- listModels() {
1535
- return Object.keys(XAI_PRICING);
1682
+ await Promise.allSettled(ids.map((id) => this.delete(id, opts)));
1683
+ }
1684
+ /** Download raw file bytes. */
1685
+ async getContent(fileId, signal) {
1686
+ if (typeof fileId !== "string" || fileId.trim() === "") {
1687
+ throw badRequest("fileId must be a non-empty string.");
1536
1688
  }
1537
- };
1538
- }
1689
+ let res;
1690
+ try {
1691
+ res = await this.fetchImpl(
1692
+ this.filesUrl(`${fileId}/content`),
1693
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
1694
+ );
1695
+ } catch (e) {
1696
+ if (signal?.aborted === true) {
1697
+ throw new core.LlmError("xAI file content download aborted", {
1698
+ kind: "aborted",
1699
+ retryable: false,
1700
+ provider: "xai"
1701
+ });
1702
+ }
1703
+ throw classifyStoreError(e);
1704
+ }
1705
+ if (res.status === 404) {
1706
+ throw notFoundError(fileId, "getContent");
1707
+ }
1708
+ if (!res.ok) {
1709
+ try {
1710
+ await throwHttpFailure(res);
1711
+ } catch (e) {
1712
+ throw classifyStoreError(e);
1713
+ }
1714
+ }
1715
+ const buf = await res.arrayBuffer();
1716
+ return new Uint8Array(buf);
1717
+ }
1718
+ };
1539
1719
 
1540
1720
  // src/provider.ts
1541
1721
  function xaiProvider(opts) {
@@ -1548,6 +1728,7 @@ function xaiProvider(opts) {
1548
1728
 
1549
1729
  exports.Grok45ConfigSchema = Grok45ConfigSchema;
1550
1730
  exports.Grok46ConfigSchema = Grok46ConfigSchema;
1731
+ exports.Grok47ConfigSchema = Grok47ConfigSchema;
1551
1732
  exports.XAI_FILES_DEFAULT_BASE_URL = XAI_FILES_DEFAULT_BASE_URL;
1552
1733
  exports.XAI_FILE_MAX_BYTES = XAI_FILE_MAX_BYTES;
1553
1734
  exports.XAI_FILE_TTL_MAX_SECONDS = XAI_FILE_TTL_MAX_SECONDS;
@@ -1560,6 +1741,7 @@ exports.classifyXaiError = classifyXaiError;
1560
1741
  exports.computeXaiCost = computeXaiCost;
1561
1742
  exports.grok45ModelDescriptor = grok45ModelDescriptor;
1562
1743
  exports.grok46ModelDescriptor = grok46ModelDescriptor;
1744
+ exports.grok47ModelDescriptor = grok47ModelDescriptor;
1563
1745
  exports.requireApiKey = requireApiKey;
1564
1746
  exports.xaiAdapter = xaiAdapter;
1565
1747
  exports.xaiModelDescriptors = xaiModelDescriptors;