@gullabs/xai 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,6 +58,71 @@ const result = await client.generate(
58
58
  )
59
59
  ```
60
60
 
61
+ ### Function-calling seam (no agent loop)
62
+
63
+ ```ts
64
+ const tools = [
65
+ {
66
+ name: 'get_temperature',
67
+ description: 'Get current temperature for a location',
68
+ inputJsonSchema: {
69
+ type: 'object',
70
+ properties: { location: { type: 'string' } },
71
+ required: ['location'],
72
+ },
73
+ },
74
+ ]
75
+
76
+ const first = await client.generate(
77
+ {
78
+ provider: 'xai',
79
+ model: 'grok-4.6',
80
+ messages: [{ role: 'user', parts: [{ kind: 'text', text: 'Temperature in SF?' }] }],
81
+ tools,
82
+ toolChoice: 'required',
83
+ },
84
+ { auth: { apiKey: 'YOUR_XAI_API_KEY' } },
85
+ )
86
+ // first.finishReason === 'tool_calls'
87
+ // first.toolCalls === [{ toolCallId, toolName, args }]
88
+
89
+ const call = first.toolCalls![0]!
90
+ const replay = await client.generate(
91
+ {
92
+ provider: 'xai',
93
+ model: 'grok-4.6',
94
+ messages: [
95
+ { role: 'user', parts: [{ kind: 'text', text: 'Temperature in SF?' }] },
96
+ {
97
+ role: 'assistant',
98
+ parts: [
99
+ {
100
+ kind: 'tool-call',
101
+ toolCallId: call.toolCallId,
102
+ toolName: call.toolName,
103
+ args: call.args,
104
+ },
105
+ ],
106
+ },
107
+ {
108
+ role: 'user',
109
+ parts: [
110
+ {
111
+ kind: 'tool-result',
112
+ toolCallId: call.toolCallId,
113
+ toolName: call.toolName,
114
+ result: { temperature: 59 },
115
+ },
116
+ ],
117
+ },
118
+ ],
119
+ tools,
120
+ },
121
+ { auth: { apiKey: 'YOUR_XAI_API_KEY' } },
122
+ )
123
+ // replay.text — model answer after the host dispatched the tool
124
+ ```
125
+
61
126
  ## grok-4.5 and grok-4.6
62
127
 
63
128
  The default registry ships two canonical models (500k token context window each). They route through this adapter and support:
@@ -116,7 +181,7 @@ try {
116
181
  | ZDR teams | New uploads and `file_id` attachments are blocked by xAI; errors mention Zero Data Retention when detectable |
117
182
  | Max size | 48 MiB (conservative vs docs 48–50 MB) |
118
183
 
119
- **Billing note:** attaching files on Responses implicitly enables xAI's `attachment_search` agentic tool. Per-invocation fees land in `Cost.details.tools`. Live 2026-08-24 `/v1/responses` pins counters at `usage.server_side_tool_usage_details` (`web_search_calls`, `x_search_calls`, `document_search_calls`, …); the adapter flattens those names into `usage.details`. File-attach confirmation of the attachment lane is blocked on ZDR keys (uploads return Zero Data Retention); `document_search_calls` is the live-payload key used for the $10/1k attachment lane. When the adapter requested server tools (`providerOptions.xai.tools` or `file-ref`) it also sets synthetic `usage.details.server_tools_requested = 1` (adapter-owned, not a provider field). Missing counters → `tools: 0`, `confidence: 'estimated'`, plus an adapter warning.
184
+ **Billing note:** attaching files on Responses implicitly enables xAI's `attachment_search` agentic tool. Live 2026-08-24 pins `web_search_calls` and `x_search_calls` in `usage.server_side_tool_usage_details` (flattened into `usage.details`). The attachment_search counter is **not** live-pinned (ZDR blocks file attach on this key); a `file-ref` call sets synthetic `usage.details.attachment_search_unpinned = 1` and `Cost.confidence: 'estimated'` — it is never reported as exact `$0`. `server_tools_requested = 1` is adapter-owned. Missing expected web/X counters → `tools: 0`, `estimated`, plus an adapter warning.
120
185
 
121
186
  **Host tests:** `@gullabs/testing` exports `FakeXaiFileStore` (in-memory upload/get/delete with optional TTL clock and `failClosed`).
122
187
 
package/dist/index.cjs CHANGED
@@ -558,6 +558,13 @@ function xaiAdapter(opts) {
558
558
  )}]; tool cost will be estimated.`
559
559
  });
560
560
  }
561
+ if (hasFileRef) {
562
+ usage.details["attachment_search_unpinned"] = 1;
563
+ warnings.push({
564
+ type: "other",
565
+ message: "xai: file-ref enables attachment_search but that counter is not live-pinned; tool cost is estimated."
566
+ });
567
+ }
561
568
  }
562
569
  const citations = collectXaiCitations(response, messageItems);
563
570
  const providerMeta = {};
@@ -1494,7 +1501,8 @@ function computeXaiCost(model, usage, tier) {
1494
1501
  usage.outputTokens * base.outputPerM * factor / 1e6
1495
1502
  );
1496
1503
  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);
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;
1498
1506
  const toolsCost = missingRequestedCounters ? 0 : XAI_TOOL_COUNTER_KEYS.reduce((sum, key) => {
1499
1507
  const count = usage.details[key];
1500
1508
  if (typeof count !== "number" || count <= 0) return sum;
@@ -1505,7 +1513,7 @@ function computeXaiCost(model, usage, tier) {
1505
1513
  microUsd,
1506
1514
  usd: microUsd / 1e6,
1507
1515
  pricingVersion: xaiPricingVersion,
1508
- confidence: missingRequestedCounters ? "estimated" : "exact",
1516
+ confidence: missingRequestedCounters || attachmentUnpinned ? "estimated" : "exact",
1509
1517
  details: {
1510
1518
  input: inputCost,
1511
1519
  cached: cachedCost,