@gullabs/xai 0.5.1 → 0.6.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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { zodToStandardSchema, toConfigJsonSchema, createModelRegistry, LlmError, classifyError, redactSecrets, assertNever } from '@gullabs/core';
1
+ import { zodToStandardSchema, toConfigJsonSchema, createModelRegistry, LlmError, classifyError, assertNever, redactSecrets } from '@gullabs/core';
2
2
  import { z } from 'zod';
3
3
 
4
4
  // src/client.ts
@@ -31,6 +31,12 @@ async function buildXaiClient(auth) {
31
31
  function isPlainRecord(value) {
32
32
  return typeof value === "object" && value !== null && !Array.isArray(value);
33
33
  }
34
+ function isXaiMessageItem(item) {
35
+ return item.type === "message" && Array.isArray(item.content);
36
+ }
37
+ function isXaiReasoningItem(item) {
38
+ return item.type === "reasoning" && Array.isArray(item.summary);
39
+ }
34
40
  function badXaiRequest(message) {
35
41
  return new LlmError(message, { kind: "bad_request", retryable: false });
36
42
  }
@@ -75,10 +81,16 @@ function mapPart(p) {
75
81
  }
76
82
  return { type: "input_file", file_id: p.fileId };
77
83
  }
84
+ case "tool-call":
85
+ case "tool-result":
86
+ throw badXaiRequest(
87
+ `xAI mapPart does not emit ${p.kind} as a content part; the request mapper handles replay items.`
88
+ );
78
89
  default:
79
90
  return assertNever(p);
80
91
  }
81
92
  }
93
+ var XAI_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set(["promptCacheKey", "tools", "parallelToolCalls"]);
82
94
  function mapXaiProviderOptions(xaiOpts, model) {
83
95
  if (xaiOpts === void 0) {
84
96
  return {};
@@ -86,12 +98,14 @@ function mapXaiProviderOptions(xaiOpts, model) {
86
98
  if (!isPlainRecord(xaiOpts)) {
87
99
  throw badXaiRequest(`providerOptions.xai must be an object for model "${model}".`);
88
100
  }
89
- const unknownKeys = Object.keys(xaiOpts).filter((key) => key !== "promptCacheKey");
101
+ const unknownKeys = Object.keys(xaiOpts).filter(
102
+ (key) => !XAI_PROVIDER_OPTION_KEYS.has(key)
103
+ );
90
104
  if (unknownKeys.length > 0) {
91
105
  throw badXaiRequest(
92
106
  `providerOptions.xai contains unsupported keys [${unknownKeys.join(
93
107
  ", "
94
- )}] for model "${model}". Allowed keys: promptCacheKey.`
108
+ )}] for model "${model}". Allowed keys: promptCacheKey, tools, parallelToolCalls.`
95
109
  );
96
110
  }
97
111
  const mapped = {};
@@ -103,8 +117,69 @@ function mapXaiProviderOptions(xaiOpts, model) {
103
117
  }
104
118
  mapped.promptCacheKey = xaiOpts["promptCacheKey"];
105
119
  }
120
+ if (xaiOpts["tools"] !== void 0) {
121
+ mapped.tools = mapXaiSearchTools(xaiOpts["tools"], model);
122
+ }
123
+ if (xaiOpts["parallelToolCalls"] !== void 0) {
124
+ if (typeof xaiOpts["parallelToolCalls"] !== "boolean") {
125
+ throw badXaiRequest(
126
+ `providerOptions.xai.parallelToolCalls must be a boolean for model "${model}".`
127
+ );
128
+ }
129
+ mapped.parallelToolCalls = xaiOpts["parallelToolCalls"];
130
+ }
106
131
  return mapped;
107
132
  }
133
+ function mapXaiSearchTools(tools, model) {
134
+ if (!Array.isArray(tools)) {
135
+ throw badXaiRequest(
136
+ `providerOptions.xai.tools must be an array for model "${model}".`
137
+ );
138
+ }
139
+ return tools.map((tool, index) => {
140
+ if (!isPlainRecord(tool) || typeof tool["type"] !== "string") {
141
+ throw badXaiRequest(
142
+ `providerOptions.xai.tools[${index}] must be an object with a type for model "${model}".`
143
+ );
144
+ }
145
+ if (tool["type"] === "web_search") {
146
+ const wire = { type: "web_search" };
147
+ if (tool["allowedDomains"] !== void 0)
148
+ wire["allowed_domains"] = tool["allowedDomains"];
149
+ if (tool["excludedDomains"] !== void 0) {
150
+ wire["excluded_domains"] = tool["excludedDomains"];
151
+ }
152
+ if (tool["enableImageUnderstanding"] !== void 0) {
153
+ wire["enable_image_understanding"] = tool["enableImageUnderstanding"];
154
+ }
155
+ if (tool["enableImageSearch"] !== void 0) {
156
+ wire["enable_image_search"] = tool["enableImageSearch"];
157
+ }
158
+ return wire;
159
+ }
160
+ if (tool["type"] === "x_search") {
161
+ const wire = { type: "x_search" };
162
+ if (tool["allowedXHandles"] !== void 0) {
163
+ wire["allowed_x_handles"] = tool["allowedXHandles"];
164
+ }
165
+ if (tool["excludedXHandles"] !== void 0) {
166
+ wire["excluded_x_handles"] = tool["excludedXHandles"];
167
+ }
168
+ if (tool["fromDate"] !== void 0) wire["from_date"] = tool["fromDate"];
169
+ if (tool["toDate"] !== void 0) wire["to_date"] = tool["toDate"];
170
+ if (tool["enableImageUnderstanding"] !== void 0) {
171
+ wire["enable_image_understanding"] = tool["enableImageUnderstanding"];
172
+ }
173
+ if (tool["enableVideoUnderstanding"] !== void 0) {
174
+ wire["enable_video_understanding"] = tool["enableVideoUnderstanding"];
175
+ }
176
+ return wire;
177
+ }
178
+ throw badXaiRequest(
179
+ `providerOptions.xai.tools[${index}].type "${String(tool["type"])}" is not supported for model "${model}".`
180
+ );
181
+ });
182
+ }
108
183
  function mapFinishReason(response) {
109
184
  if (response.status === "completed") {
110
185
  return "stop";
@@ -136,6 +211,14 @@ function mapUsage(usage) {
136
211
  details[key] = value;
137
212
  }
138
213
  }
214
+ const toolUsage = usage["server_side_tool_usage_details"];
215
+ if (isPlainRecord(toolUsage)) {
216
+ for (const [key, value] of Object.entries(toolUsage)) {
217
+ if (typeof value === "number") {
218
+ details[key] = value;
219
+ }
220
+ }
221
+ }
139
222
  const raw = usage;
140
223
  const result = {
141
224
  inputTokens,
@@ -254,10 +337,48 @@ function xaiAdapter(opts) {
254
337
  const warnings = [];
255
338
  const model = req.model;
256
339
  const genConfig = req.config;
257
- const input = req.messages.map((msg) => ({
258
- role: msg.role === "assistant" ? "assistant" : "user",
259
- content: msg.parts.map(mapPart)
260
- }));
340
+ const input = [];
341
+ for (const msg of req.messages) {
342
+ const contentParts = [];
343
+ for (const part of msg.parts) {
344
+ if (part.kind === "tool-call") {
345
+ if (contentParts.length > 0) {
346
+ input.push({
347
+ role: msg.role === "assistant" ? "assistant" : "user",
348
+ content: contentParts.splice(0)
349
+ });
350
+ }
351
+ input.push({
352
+ type: "function_call",
353
+ call_id: part.toolCallId,
354
+ name: part.toolName,
355
+ arguments: JSON.stringify(part.args)
356
+ });
357
+ continue;
358
+ }
359
+ if (part.kind === "tool-result") {
360
+ if (contentParts.length > 0) {
361
+ input.push({
362
+ role: msg.role === "assistant" ? "assistant" : "user",
363
+ content: contentParts.splice(0)
364
+ });
365
+ }
366
+ input.push({
367
+ type: "function_call_output",
368
+ call_id: part.toolCallId,
369
+ output: JSON.stringify(part.result)
370
+ });
371
+ continue;
372
+ }
373
+ contentParts.push(mapPart(part));
374
+ }
375
+ if (contentParts.length > 0) {
376
+ input.push({
377
+ role: msg.role === "assistant" ? "assistant" : "user",
378
+ content: contentParts
379
+ });
380
+ }
381
+ }
261
382
  const params = {
262
383
  model,
263
384
  input,
@@ -327,6 +448,38 @@ function xaiAdapter(opts) {
327
448
  if (xaiProviderConfig.promptCacheKey !== void 0) {
328
449
  params.prompt_cache_key = xaiProviderConfig.promptCacheKey;
329
450
  }
451
+ const hasFileRef = req.messages.some(
452
+ (msg) => msg.parts.some((part) => part.kind === "file-ref")
453
+ );
454
+ const searchTools = xaiProviderConfig.tools;
455
+ if (searchTools !== void 0) {
456
+ if (req.modelDescriptor?.capabilities?.grounding !== true) {
457
+ throw badXaiRequest(
458
+ `providerOptions.xai.tools requires capabilities.grounding on the model descriptor for "${model}".`
459
+ );
460
+ }
461
+ params.tools = searchTools;
462
+ }
463
+ if (req.tools !== void 0 && req.tools.length > 0) {
464
+ if (req.modelDescriptor?.capabilities?.functionCalling !== true) {
465
+ throw badXaiRequest(
466
+ `tools is not supported for xai model "${model}" (capabilities.functionCalling is not true).`
467
+ );
468
+ }
469
+ const functionTools = req.tools.map((tool) => ({
470
+ type: "function",
471
+ name: tool.name,
472
+ description: tool.description,
473
+ parameters: tool.inputJsonSchema
474
+ }));
475
+ params.tools = [...params.tools ?? [], ...functionTools];
476
+ if (req.toolChoice !== void 0) {
477
+ params.tool_choice = typeof req.toolChoice === "string" ? req.toolChoice : { type: "function", name: req.toolChoice.name };
478
+ }
479
+ }
480
+ if (xaiProviderConfig.parallelToolCalls !== void 0) {
481
+ params.parallel_tool_calls = xaiProviderConfig.parallelToolCalls;
482
+ }
330
483
  let response;
331
484
  try {
332
485
  const buildClient = opts?._clientFactory ?? buildXaiClient;
@@ -345,14 +498,29 @@ function xaiAdapter(opts) {
345
498
  let text = "";
346
499
  let reasoningText;
347
500
  const messageItems = [];
501
+ const toolCalls = [];
348
502
  for (const item of response.output) {
349
- if (item.type === "message") {
503
+ if (isXaiMessageItem(item)) {
350
504
  messageItems.push(item);
351
- } else {
505
+ } else if (isXaiReasoningItem(item)) {
352
506
  const joined = item.summary.map((s) => s.text).join("");
353
507
  if (joined.length > 0) {
354
508
  reasoningText = (reasoningText ?? "") + joined;
355
509
  }
510
+ } else if (item.type === "function_call") {
511
+ const callId = typeof item["call_id"] === "string" ? item["call_id"] : "";
512
+ const name = typeof item["name"] === "string" ? item["name"] : "";
513
+ let args = {};
514
+ if (typeof item["arguments"] === "string") {
515
+ try {
516
+ args = JSON.parse(item["arguments"]);
517
+ } catch {
518
+ args = item["arguments"];
519
+ }
520
+ }
521
+ if (callId.length > 0 && name.length > 0) {
522
+ toolCalls.push({ toolCallId: callId, toolName: name, args });
523
+ }
356
524
  }
357
525
  }
358
526
  if (messageItems.length > 0) {
@@ -374,6 +542,22 @@ function xaiAdapter(opts) {
374
542
  }
375
543
  const usage = mapUsage(response.usage);
376
544
  const finishReason = mapFinishReason(response);
545
+ const expectedToolCounters = expectedServerToolCounters(
546
+ xaiProviderConfig.tools);
547
+ if (expectedToolCounters.length > 0 || hasFileRef) {
548
+ usage.details["server_tools_requested"] = 1;
549
+ const missing = expectedToolCounters.filter((key) => !(key in usage.details));
550
+ if (missing.length > 0) {
551
+ usage.details["server_tools_missing"] = 1;
552
+ warnings.push({
553
+ type: "other",
554
+ message: `xai: server tools were requested but usage is missing counters [${missing.join(
555
+ ", "
556
+ )}]; tool cost will be estimated.`
557
+ });
558
+ }
559
+ }
560
+ const citations = collectXaiCitations(response, messageItems);
377
561
  const providerMeta = {};
378
562
  const contextDetails = response.usage["context_details"];
379
563
  if (isPlainRecord(contextDetails)) {
@@ -393,11 +577,158 @@ function xaiAdapter(opts) {
393
577
  ...reasoningText !== void 0 ? { reasoningText } : {},
394
578
  ...rawStructured !== void 0 ? { rawStructured } : {},
395
579
  ...servedServiceTier !== void 0 ? { servedServiceTier } : {},
396
- ...Object.keys(providerMeta).length > 0 ? { providerMetadata: providerMeta } : {}
580
+ ...Object.keys(providerMeta).length > 0 ? { providerMetadata: providerMeta } : {},
581
+ ...citations.length > 0 ? { citations } : {},
582
+ ...toolCalls.length > 0 ? { toolCalls, finishReason: "tool_calls" } : {}
397
583
  };
398
584
  return result;
585
+ },
586
+ async countTokens(req, ctx) {
587
+ if (req.provider !== "xai") {
588
+ throw new LlmError(
589
+ `xaiAdapter received a request for provider "${req.provider}", expected "xai".`,
590
+ { kind: "bad_request", retryable: false }
591
+ );
592
+ }
593
+ if (req.tools !== void 0 && req.tools.length > 0) {
594
+ throw badXaiRequest(
595
+ "xAI countTokens rejects tools; tokenize-text cannot represent tool declarations."
596
+ );
597
+ }
598
+ const text = concatenateTokenizeText(req);
599
+ const apiKey = requireApiKey(ctx.auth);
600
+ const fetchImpl = opts?._fetch ?? fetch;
601
+ try {
602
+ const res = await fetchImpl("https://api.x.ai/v1/tokenize-text", {
603
+ method: "POST",
604
+ headers: {
605
+ Authorization: `Bearer ${apiKey}`,
606
+ "Content-Type": "application/json"
607
+ },
608
+ body: JSON.stringify({ model: req.model, text }),
609
+ ...ctx.signal !== void 0 ? { signal: ctx.signal } : {}
610
+ });
611
+ if (!res.ok) {
612
+ let parsed;
613
+ try {
614
+ parsed = await res.json();
615
+ } catch {
616
+ parsed = await res.text().catch(() => "");
617
+ }
618
+ throw Object.assign(new Error(`xAI tokenize-text HTTP ${res.status}`), {
619
+ status: res.status,
620
+ error: parsed
621
+ });
622
+ }
623
+ const raw = await res.json();
624
+ if (!isPlainRecord(raw) || !Array.isArray(raw["token_ids"])) {
625
+ throw new LlmError(
626
+ "xAI tokenize-text response is malformed: missing required field: token_ids",
627
+ { kind: "server", retryable: true, provider: "xai" }
628
+ );
629
+ }
630
+ const n = raw["token_ids"].length;
631
+ return {
632
+ totalTokens: n,
633
+ accuracy: "lower-bound",
634
+ details: { textTokens: n },
635
+ raw
636
+ };
637
+ } catch (rawErr) {
638
+ if (rawErr instanceof Error && rawErr.name === "AbortError") {
639
+ throw new LlmError("xAI tokenize-text aborted", {
640
+ kind: "aborted",
641
+ retryable: false,
642
+ provider: "xai",
643
+ cause: rawErr
644
+ });
645
+ }
646
+ throw classifyXaiError(rawErr);
647
+ }
648
+ }
649
+ };
650
+ }
651
+ var WEB_SEARCH_COUNTER = "web_search_calls";
652
+ var X_SEARCH_COUNTER = "x_search_calls";
653
+ function expectedServerToolCounters(tools, _hasFileRef) {
654
+ const keys = [];
655
+ if (tools !== void 0) {
656
+ for (const tool of tools) {
657
+ if (tool["type"] === "web_search") keys.push(WEB_SEARCH_COUNTER);
658
+ if (tool["type"] === "x_search") keys.push(X_SEARCH_COUNTER);
659
+ }
660
+ }
661
+ return keys;
662
+ }
663
+ function collectXaiCitations(response, messageItems) {
664
+ const seen = /* @__PURE__ */ new Set();
665
+ const citations = [];
666
+ const push = (url, title) => {
667
+ if (typeof url !== "string" || url.length === 0) return;
668
+ if (seen.has(url)) return;
669
+ seen.add(url);
670
+ const citation = { url };
671
+ if (typeof title === "string" && title.length > 0 && title !== url) {
672
+ citation.title = title;
673
+ }
674
+ try {
675
+ const parsed = new URL(url);
676
+ if (parsed.hostname.length > 0) {
677
+ citation.sourceName = parsed.hostname.startsWith("www.") ? parsed.hostname.slice(4) : parsed.hostname;
678
+ }
679
+ } catch {
399
680
  }
681
+ citations.push(citation);
400
682
  };
683
+ if (Array.isArray(response.citations)) {
684
+ for (const item of response.citations) {
685
+ if (typeof item === "string") {
686
+ push(item, void 0);
687
+ } else if (isPlainRecord(item)) {
688
+ push(item["url"] ?? item["uri"], item["title"]);
689
+ }
690
+ }
691
+ }
692
+ const lastMessage = messageItems.at(-1);
693
+ const citationMessages = lastMessage === void 0 ? [] : [lastMessage];
694
+ for (const item of citationMessages) {
695
+ for (const part of item.content) {
696
+ const annotations = part.annotations;
697
+ if (!Array.isArray(annotations)) continue;
698
+ for (const ann of annotations) {
699
+ if (!isPlainRecord(ann)) continue;
700
+ if (ann["type"] !== void 0 && ann["type"] !== "url_citation") continue;
701
+ push(ann["url"], ann["title"]);
702
+ }
703
+ }
704
+ }
705
+ return citations;
706
+ }
707
+ function concatenateTokenizeText(req) {
708
+ const chunks = [];
709
+ if (req.system !== void 0 && req.system.length > 0) {
710
+ chunks.push(req.system);
711
+ }
712
+ for (const message of req.messages) {
713
+ for (const part of message.parts) {
714
+ switch (part.kind) {
715
+ case "text":
716
+ chunks.push(part.text);
717
+ break;
718
+ case "inline-media":
719
+ case "file-uri":
720
+ case "file-ref":
721
+ case "tool-call":
722
+ case "tool-result":
723
+ throw badXaiRequest(
724
+ `xAI countTokens rejects ${part.kind} parts; tokenize-text is text-only.`
725
+ );
726
+ default:
727
+ return assertNever(part);
728
+ }
729
+ }
730
+ }
731
+ return chunks.join("\n");
401
732
  }
402
733
  var XAI_FILE_TTL_MIN_SECONDS = 3600;
403
734
  var XAI_FILE_TTL_MAX_SECONDS = 2592e3;
@@ -854,6 +1185,100 @@ var XaiFileStore = class {
854
1185
  return new Uint8Array(buf);
855
1186
  }
856
1187
  };
1188
+ var webSearchFlags = {
1189
+ enableImageUnderstanding: z.boolean().optional().meta({
1190
+ title: "Enable Image Understanding",
1191
+ description: "Ask xAI to analyze images found during web search."
1192
+ }),
1193
+ enableImageSearch: z.boolean().optional().meta({
1194
+ title: "Enable Image Search",
1195
+ description: "Ask xAI to include image search results."
1196
+ })
1197
+ };
1198
+ var XaiWebSearchToolSchema = z.union([
1199
+ z.strictObject({
1200
+ type: z.literal("web_search"),
1201
+ allowedDomains: z.array(z.string()).max(5),
1202
+ ...webSearchFlags
1203
+ }),
1204
+ z.strictObject({
1205
+ type: z.literal("web_search"),
1206
+ excludedDomains: z.array(z.string()).max(5),
1207
+ ...webSearchFlags
1208
+ }),
1209
+ z.strictObject({
1210
+ type: z.literal("web_search"),
1211
+ ...webSearchFlags
1212
+ })
1213
+ ]).meta({
1214
+ title: "XaiWebSearchTool",
1215
+ description: "xAI web_search server tool. allowedDomains and excludedDomains are mutually exclusive (max 5)."
1216
+ });
1217
+ var xSearchFlags = {
1218
+ fromDate: z.iso.date().optional().meta({
1219
+ title: "From Date",
1220
+ description: "Inclusive ISO-8601 date lower bound for X search."
1221
+ }),
1222
+ toDate: z.iso.date().optional().meta({
1223
+ title: "To Date",
1224
+ description: "Inclusive ISO-8601 date upper bound for X search."
1225
+ }),
1226
+ enableImageUnderstanding: z.boolean().optional().meta({
1227
+ title: "Enable Image Understanding",
1228
+ description: "Ask xAI to analyze images in matched posts."
1229
+ }),
1230
+ enableVideoUnderstanding: z.boolean().optional().meta({
1231
+ title: "Enable Video Understanding",
1232
+ description: "Ask xAI to analyze videos in matched posts."
1233
+ })
1234
+ };
1235
+ var XaiXSearchToolSchema = z.union([
1236
+ z.strictObject({
1237
+ type: z.literal("x_search"),
1238
+ allowedXHandles: z.array(z.string()).max(20),
1239
+ ...xSearchFlags
1240
+ }),
1241
+ z.strictObject({
1242
+ type: z.literal("x_search"),
1243
+ excludedXHandles: z.array(z.string()).max(20),
1244
+ ...xSearchFlags
1245
+ }),
1246
+ z.strictObject({
1247
+ type: z.literal("x_search"),
1248
+ ...xSearchFlags
1249
+ })
1250
+ ]).meta({
1251
+ title: "XaiXSearchTool",
1252
+ description: "xAI x_search server tool. allowedXHandles and excludedXHandles are mutually exclusive (max 20)."
1253
+ });
1254
+ var XaiToolsSchema = z.union([
1255
+ z.tuple([XaiWebSearchToolSchema]),
1256
+ z.tuple([XaiXSearchToolSchema]),
1257
+ z.tuple([XaiWebSearchToolSchema, XaiXSearchToolSchema]),
1258
+ z.tuple([XaiXSearchToolSchema, XaiWebSearchToolSchema])
1259
+ ]).meta({
1260
+ title: "XaiTools",
1261
+ description: "xAI Live Search tools. At most one web_search and at most one x_search."
1262
+ });
1263
+ var XaiProviderOptionsSchema = z.strictObject({
1264
+ promptCacheKey: z.string().min(1).optional().meta({
1265
+ title: "Prompt Cache Key",
1266
+ description: "xAI conversation-routing cache key \u2014 maps to Responses API `prompt_cache_key`."
1267
+ }),
1268
+ tools: XaiToolsSchema.optional().meta({
1269
+ title: "Live Search Tools",
1270
+ description: "xAI server-side web_search / x_search tools."
1271
+ }),
1272
+ parallelToolCalls: z.boolean().optional().meta({
1273
+ title: "Parallel Tool Calls",
1274
+ description: "xAI Responses parallel_tool_calls. Not a generic contract field."
1275
+ })
1276
+ }).meta({
1277
+ title: "xAI Provider Options",
1278
+ description: "Allowlisted xAI provider options."
1279
+ });
1280
+
1281
+ // src/model-config/grok-4-5.ts
857
1282
  var Grok45ConfigSchema = z.strictObject({
858
1283
  temperature: z.number().optional().meta({
859
1284
  title: "Temperature",
@@ -868,9 +1293,9 @@ var Grok45ConfigSchema = z.strictObject({
868
1293
  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."
869
1294
  }),
870
1295
  reasoning: z.strictObject({
871
- effort: z.enum(["low", "high"]).meta({
1296
+ effort: z.enum(["low", "medium", "high"]).meta({
872
1297
  title: "Reasoning Effort",
873
- description: 'Reasoning effort for grok-4.5. Only "low" and "high" are admitted (live-verified); "none"/"medium"/"xhigh" are rejected by the live API.'
1298
+ 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".'
874
1299
  })
875
1300
  }).optional().meta({
876
1301
  title: "Reasoning",
@@ -881,22 +1306,14 @@ var Grok45ConfigSchema = z.strictObject({
881
1306
  description: "Logical request timeout in milliseconds."
882
1307
  }),
883
1308
  providerOptions: z.strictObject({
884
- xai: z.strictObject({
885
- promptCacheKey: z.string().min(1).optional().meta({
886
- title: "Prompt Cache Key",
887
- description: "xAI conversation-routing cache key \u2014 maps to Responses API `prompt_cache_key`."
888
- })
889
- }).optional().meta({
890
- title: "xAI Provider Options",
891
- description: "Allowlisted xAI provider options for grok-4.5."
892
- })
1309
+ xai: XaiProviderOptionsSchema.optional()
893
1310
  }).optional().meta({
894
1311
  title: "Provider Options",
895
1312
  description: "Provider-specific options accepted for grok-4.5."
896
1313
  })
897
1314
  }).meta({
898
1315
  title: "Grok45Config",
899
- description: "Strict Responses API config for model grok-4.5. Level reasoning (low/high only), tunable sampling, no service tiers, structured output, vision, priced.",
1316
+ 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.",
900
1317
  examples: [{ reasoning: { effort: "high" } }]
901
1318
  });
902
1319
  var Grok46ConfigSchema = z.strictObject({
@@ -930,15 +1347,7 @@ var Grok46ConfigSchema = z.strictObject({
930
1347
  description: "Logical request timeout in milliseconds."
931
1348
  }),
932
1349
  providerOptions: z.strictObject({
933
- xai: z.strictObject({
934
- promptCacheKey: z.string().min(1).optional().meta({
935
- title: "Prompt Cache Key",
936
- description: "xAI conversation-routing cache key \u2014 maps to Responses API `prompt_cache_key`."
937
- })
938
- }).optional().meta({
939
- title: "xAI Provider Options",
940
- description: "Allowlisted xAI provider options for grok-4.6."
941
- })
1350
+ xai: XaiProviderOptionsSchema.optional()
942
1351
  }).optional().meta({
943
1352
  title: "Provider Options",
944
1353
  description: "Provider-specific options accepted for grok-4.6."
@@ -957,14 +1366,15 @@ var grok45ModelDescriptor = {
957
1366
  capabilities: {
958
1367
  reasoning: true,
959
1368
  reasoningApi: "level",
960
- admittedReasoningEfforts: ["low", "high"],
1369
+ admittedReasoningEfforts: ["low", "medium", "high"],
961
1370
  structuredOutput: true,
962
1371
  nativeStructuredOutput: true,
963
1372
  vision: true,
964
1373
  audioInput: false,
965
1374
  sampling: "tunable",
966
1375
  caching: { explicit: false, minTokens: 0 },
967
- grounding: false
1376
+ grounding: true,
1377
+ functionCalling: true
968
1378
  // No serviceTiers key — grok-4.5 has no admitted service-tier vocabulary.
969
1379
  },
970
1380
  configSchema: Grok45ConfigSchema,
@@ -985,7 +1395,8 @@ var grok46ModelDescriptor = {
985
1395
  audioInput: false,
986
1396
  sampling: "tunable",
987
1397
  caching: { explicit: false, minTokens: 0 },
988
- grounding: false,
1398
+ grounding: true,
1399
+ functionCalling: true,
989
1400
  serviceTiers: ["priority"]
990
1401
  },
991
1402
  configSchema: Grok46ConfigSchema,
@@ -999,7 +1410,12 @@ var xaiModelDescriptors = [
999
1410
  var xaiRegistry = createModelRegistry(xaiModelDescriptors);
1000
1411
 
1001
1412
  // src/pricing.ts
1002
- var xaiPricingVersion = "xai-2026-08-12";
1413
+ var xaiPricingVersion = "xai-2026-08-24";
1414
+ var XAI_TOOL_RATE_MICRO_USD = {
1415
+ web_search_calls: 5e3,
1416
+ x_search_calls: 5e3
1417
+ };
1418
+ var XAI_TOOL_COUNTER_KEYS = ["web_search_calls", "x_search_calls"];
1003
1419
  var XAI_PRICING = Object.freeze({
1004
1420
  // ── grok-4.5 ── $2.00/$6.00 (≤200k), $4.00/$12.00 (>200k); cached $0.30/$0.60
1005
1421
  "grok-4.5": {
@@ -1048,7 +1464,7 @@ function computeXaiCost(model, usage, tier) {
1048
1464
  usd: null,
1049
1465
  pricingVersion: xaiPricingVersion,
1050
1466
  confidence: "estimated",
1051
- details: { input: 0, cached: 0, output: 0 },
1467
+ details: { input: 0, cached: 0, output: 0, tools: 0 },
1052
1468
  unpricedReason: `Unknown model "${model}"; no pricing entry found.`
1053
1469
  };
1054
1470
  }
@@ -1062,7 +1478,7 @@ function computeXaiCost(model, usage, tier) {
1062
1478
  usd: null,
1063
1479
  pricingVersion: xaiPricingVersion,
1064
1480
  confidence: "estimated",
1065
- details: { input: 0, cached: 0, output: 0 },
1481
+ details: { input: 0, cached: 0, output: 0, tools: 0 },
1066
1482
  unpricedReason: `Unknown service tier "${tier}"; xai model "${model}" has no such tier, refusing to guess a pricing multiplier.`
1067
1483
  };
1068
1484
  }
@@ -1075,16 +1491,24 @@ function computeXaiCost(model, usage, tier) {
1075
1491
  const outputCost = Math.round(
1076
1492
  usage.outputTokens * base.outputPerM * factor / 1e6
1077
1493
  );
1078
- const microUsd = inputCost + cachedCost + outputCost;
1494
+ const serverToolsRequested = usage.details["server_tools_requested"] === 1;
1495
+ const missingRequestedCounters = usage.details["server_tools_missing"] === 1 || serverToolsRequested && !XAI_TOOL_COUNTER_KEYS.some((key) => key in usage.details);
1496
+ const toolsCost = missingRequestedCounters ? 0 : XAI_TOOL_COUNTER_KEYS.reduce((sum, key) => {
1497
+ const count = usage.details[key];
1498
+ if (typeof count !== "number" || count <= 0) return sum;
1499
+ return sum + Math.round(count * XAI_TOOL_RATE_MICRO_USD[key]);
1500
+ }, 0);
1501
+ const microUsd = inputCost + cachedCost + outputCost + toolsCost;
1079
1502
  return {
1080
1503
  microUsd,
1081
1504
  usd: microUsd / 1e6,
1082
1505
  pricingVersion: xaiPricingVersion,
1083
- confidence: "exact",
1506
+ confidence: missingRequestedCounters ? "estimated" : "exact",
1084
1507
  details: {
1085
1508
  input: inputCost,
1086
1509
  cached: cachedCost,
1087
- output: outputCost
1510
+ output: outputCost,
1511
+ tools: toolsCost
1088
1512
  }
1089
1513
  };
1090
1514
  }
@@ -1112,6 +1536,6 @@ function xaiProvider(opts) {
1112
1536
  };
1113
1537
  }
1114
1538
 
1115
- export { Grok45ConfigSchema, Grok46ConfigSchema, XAI_FILES_DEFAULT_BASE_URL, XAI_FILE_MAX_BYTES, XAI_FILE_TTL_MAX_SECONDS, XAI_FILE_TTL_MIN_SECONDS, XAI_PRICING, XaiFileStore, buildXaiClient, classifyXaiError, computeXaiCost, grok45ModelDescriptor, grok46ModelDescriptor, requireApiKey, xaiAdapter, xaiModelDescriptors, xaiPricingSource, xaiPricingVersion, xaiProvider, xaiRegistry };
1539
+ export { Grok45ConfigSchema, Grok46ConfigSchema, XAI_FILES_DEFAULT_BASE_URL, XAI_FILE_MAX_BYTES, XAI_FILE_TTL_MAX_SECONDS, XAI_FILE_TTL_MIN_SECONDS, XAI_PRICING, XAI_TOOL_RATE_MICRO_USD, XaiFileStore, buildXaiClient, classifyXaiError, computeXaiCost, grok45ModelDescriptor, grok46ModelDescriptor, requireApiKey, xaiAdapter, xaiModelDescriptors, xaiPricingSource, xaiPricingVersion, xaiProvider, xaiRegistry };
1116
1540
  //# sourceMappingURL=index.js.map
1117
1541
  //# sourceMappingURL=index.js.map