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