@gullabs/xai 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,433 @@
1
+ import { AuthMaterial, LlmError, ProviderAdapter, ModelDescriptor, ModelRegistry, Usage, Cost, PricingSource, ProviderPlugin } from '@gullabs/core';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * xAI-specific provider options for `@gullabs/xai`.
6
+ *
7
+ * Importing anything from this module (including this type-only re-export)
8
+ * pulls in the `declare module '@gullabs/core'` augmentation below, which
9
+ * adds the `xai` key to `ProviderOptionsMap`. `packages/xai/src/index.ts`
10
+ * re-exports these types unconditionally so the augmentation always loads
11
+ * when anything is imported from `@gullabs/xai`.
12
+ *
13
+ * @module
14
+ */
15
+ type XaiProviderOptions = {
16
+ /** xAI conversation-routing cache key — maps to Responses API `prompt_cache_key`. */
17
+ promptCacheKey?: string;
18
+ };
19
+ declare module '@gullabs/core' {
20
+ interface ProviderOptionsMap {
21
+ xai?: XaiProviderOptions;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Structural XaiClientLike interface + buildXaiClient factory.
27
+ *
28
+ * This module defines the structural interface the adapter depends on.
29
+ * The real `openai` SDK is imported ONLY in buildXaiClient so tests can
30
+ * inject a fake without pulling in the real SDK. This is the ONLY file in
31
+ * `packages/xai/src` that imports `openai`.
32
+ *
33
+ * @module
34
+ */
35
+
36
+ /**
37
+ * Narrows {@link AuthMaterial} to its `apiKey` string, rejecting the
38
+ * dev-only `CliSessionAuth` variant.
39
+ *
40
+ * xAI is a production API provider and only ever accepts API-key
41
+ * credentials; `{ cliSession: true }` is reserved for the dev-only CLI
42
+ * provider packages (`@gullabs/claude-cli`, `@gullabs/codex-cli`).
43
+ */
44
+ declare function requireApiKey(auth: AuthMaterial): string;
45
+ /** A text content item within an xAI Responses API input message. */
46
+ interface XaiInputTextPart {
47
+ type: 'input_text';
48
+ text: string;
49
+ }
50
+ /**
51
+ * An image content item within an xAI Responses API input message.
52
+ * `image_url` may be a data URL (`data:image/png;base64,...`) or a public URL.
53
+ */
54
+ interface XaiInputImagePart {
55
+ type: 'input_image';
56
+ image_url: string;
57
+ }
58
+ /** Union of content-part shapes an input message may carry. */
59
+ type XaiInputContentPart = XaiInputTextPart | XaiInputImagePart;
60
+ /** A single role+content input item constructed by the (future) adapter. */
61
+ interface XaiInputItem {
62
+ role: 'user' | 'assistant' | 'system' | 'developer';
63
+ content: XaiInputContentPart[];
64
+ }
65
+ /**
66
+ * Structured-output text-format request shape.
67
+ * Real xAI field: `text.format`, NOT `response_format`.
68
+ * `name` and `strict` are included per xAI's Structured Outputs docs
69
+ * conventions even though the live fixture's request-echo does not surface
70
+ * them (only the schema is echoed back).
71
+ */
72
+ type XaiTextFormat = {
73
+ type: 'json_schema';
74
+ name: string;
75
+ schema: unknown;
76
+ strict: boolean;
77
+ } | {
78
+ type: 'text';
79
+ };
80
+ /**
81
+ * Parameters for `client.responses.create`.
82
+ * Structurally modeled from live-captured xAI Responses API fixtures
83
+ * (see docs/provider-plugins-and-xai-grok-4-5-plan.md §3.1), not from the
84
+ * `openai` npm package's TS types — xAI's actual endpoint shape differs.
85
+ */
86
+ interface XaiResponseCreateParams {
87
+ model: string;
88
+ input: XaiInputItem[];
89
+ instructions?: string;
90
+ reasoning?: {
91
+ effort: 'low' | 'high';
92
+ };
93
+ text?: {
94
+ format: XaiTextFormat;
95
+ };
96
+ temperature?: number;
97
+ top_p?: number;
98
+ max_output_tokens?: number;
99
+ prompt_cache_key?: string;
100
+ /** Always `false` — this library never relies on xAI-side conversation storage. */
101
+ store: false;
102
+ }
103
+ /** A single summary-text segment of a `type: 'reasoning'` output item. */
104
+ interface XaiReasoningSummaryPart {
105
+ type: 'summary_text';
106
+ text: string;
107
+ }
108
+ /** A `type: 'reasoning'` item in `output`. */
109
+ interface XaiReasoningOutputItem {
110
+ type: 'reasoning';
111
+ id?: string;
112
+ summary: XaiReasoningSummaryPart[];
113
+ status?: string;
114
+ }
115
+ /** A single text content segment of a `type: 'message'` output item. */
116
+ interface XaiOutputTextPart {
117
+ type: 'output_text';
118
+ text: string;
119
+ logprobs?: unknown[];
120
+ annotations?: unknown[];
121
+ }
122
+ /** A `type: 'message'` item in `output`. */
123
+ interface XaiMessageOutputItem {
124
+ type: 'message';
125
+ id?: string;
126
+ role?: string;
127
+ status?: string;
128
+ content: XaiOutputTextPart[];
129
+ }
130
+ /** Union of output-item shapes the Responses API may return. */
131
+ type XaiOutputItem = XaiReasoningOutputItem | XaiMessageOutputItem;
132
+ /**
133
+ * Token usage metadata returned alongside an xAI response.
134
+ *
135
+ * Kept loose/open: the known fields are typed, but xAI has been observed to
136
+ * add additional numeric fields (e.g. `num_sources_used`,
137
+ * `cost_in_usd_ticks`, `context_details`) that must not break this type.
138
+ */
139
+ interface XaiUsageShape {
140
+ input_tokens: number;
141
+ input_tokens_details?: {
142
+ cached_tokens?: number;
143
+ };
144
+ output_tokens: number;
145
+ output_tokens_details?: {
146
+ reasoning_tokens?: number;
147
+ };
148
+ total_tokens?: number;
149
+ /** Additional provider-specific usage fields, passed through raw. */
150
+ [otherKeys: string]: unknown;
151
+ }
152
+ /**
153
+ * Structural equivalent of the xAI Responses API response body.
154
+ * Only the fields the (future) adapter reads are represented here.
155
+ */
156
+ interface XaiResponseShape {
157
+ id: string;
158
+ model: string;
159
+ /**
160
+ * Real field: `status`. Observed values: "completed", "incomplete" — kept
161
+ * as a plain `string` since xAI may add further status values over time.
162
+ */
163
+ status: string;
164
+ incomplete_details?: {
165
+ reason?: string;
166
+ } | null;
167
+ output: XaiOutputItem[];
168
+ usage: XaiUsageShape;
169
+ reasoning?: {
170
+ effort?: string;
171
+ summary?: string;
172
+ };
173
+ store?: boolean;
174
+ prompt_cache_key?: string | null;
175
+ /**
176
+ * Response-level metadata (e.g. `system_fingerprint`) — surfaced into
177
+ * `AdapterResult.providerMetadata` by the adapter when present.
178
+ */
179
+ metadata?: {
180
+ [key: string]: unknown;
181
+ } | null;
182
+ }
183
+ /**
184
+ * Structural interface for the `openai` SDK's `client.responses` surface
185
+ * the adapter uses.
186
+ *
187
+ * Satisfied by:
188
+ * - The real `openai` `OpenAI` client (via `buildXaiClient` wrapper), pointed
189
+ * at xAI's `https://api.x.ai/v1` base URL.
190
+ * - `FakeXaiClient` from `@gullabs/testing`.
191
+ */
192
+ interface XaiClientLike {
193
+ responses: {
194
+ create(params: XaiResponseCreateParams, options?: {
195
+ signal?: AbortSignal;
196
+ }): Promise<XaiResponseShape>;
197
+ };
198
+ }
199
+ /**
200
+ * Build a real `openai`-SDK-backed client from AuthMaterial, pointed at
201
+ * xAI's Responses API endpoint.
202
+ *
203
+ * Only API-key authentication is supported.
204
+ *
205
+ * @param auth - API key credentials ({ apiKey }).
206
+ */
207
+ declare function buildXaiClient(auth: AuthMaterial): Promise<XaiClientLike>;
208
+
209
+ /**
210
+ * xaiAdapter — @gullabs/xai xAI Grok provider adapter.
211
+ *
212
+ * Pure request⇄response mapping over the xAI Responses API (via
213
+ * XaiClientLike). Never persists, never computes cost, never loops.
214
+ *
215
+ * @module
216
+ */
217
+
218
+ /**
219
+ * Classify a raw error thrown from the xAI Responses API call into a typed
220
+ * {@link LlmError}.
221
+ *
222
+ * xAI's Responses API returns HTTP 400 (NOT 401) for an invalid API key, so
223
+ * generic {@link classifyHttpStatus}-based classification (which maps 400 →
224
+ * `bad_request`) is wrong for this one case. This function special-cases it:
225
+ * a 400 response whose STRUCTURED parsed body matches the exact recorded
226
+ * xAI auth-failure signature (`code: 'invalid-argument'` AND message prefix
227
+ * `"Incorrect API key provided"` — see fixture 09) is reclassified as
228
+ * `invalid_auth`. Free-form `Error.message` text is never scanned, so a 400
229
+ * whose message merely *mentions* an API key (e.g. schema validation echoing
230
+ * user content) stays `bad_request`. When the structured body is unavailable
231
+ * or unparseable, classification falls through to the status-based
232
+ * `classifyError` from `@gullabs/core`.
233
+ */
234
+ declare function classifyXaiError(rawErr: unknown): LlmError;
235
+ interface XaiAdapterOptions {
236
+ /**
237
+ * Inject a pre-built client (real or fake).
238
+ * When omitted, `buildXaiClient` is called with `ctx.auth` at call time,
239
+ * inside the classified try/catch so any construction failure is wrapped
240
+ * as a typed `LlmError`.
241
+ */
242
+ client?: XaiClientLike;
243
+ /**
244
+ * @internal Testing-only.
245
+ *
246
+ * Override the default `buildXaiClient` factory. Allows unit tests to
247
+ * simulate construction failures without importing the real `openai` SDK.
248
+ * Never set this in production code. Mirrors `GeminiAdapterOptions._clientFactory`.
249
+ */
250
+ _clientFactory?: (auth: AuthMaterial) => XaiClientLike | Promise<XaiClientLike>;
251
+ }
252
+ /**
253
+ * Create an xAI Grok provider adapter (Responses API).
254
+ *
255
+ * @param opts.client - Optional pre-built client (e.g. for testing).
256
+ */
257
+ declare function xaiAdapter(opts?: XaiAdapterOptions): ProviderAdapter;
258
+
259
+ /**
260
+ * Strict Zod config schema for xAI's `grok-4.5` model.
261
+ *
262
+ * Mirrors `@gullabs/core`'s `packages/core/src/model-config/*.ts` doc-density
263
+ * style, but is a single self-contained `z.strictObject` — unlike Gemini's
264
+ * schemas, xai has no service-tier branching (no `z.union` of tier variants
265
+ * needed), no `topK`, and only a single reasoning-effort union (`'low'|'high'`).
266
+ *
267
+ * @module
268
+ */
269
+
270
+ declare const Grok45ConfigSchema: z.ZodObject<{
271
+ temperature: z.ZodOptional<z.ZodNumber>;
272
+ topP: z.ZodOptional<z.ZodNumber>;
273
+ maxOutputTokens: z.ZodOptional<z.ZodNumber>;
274
+ reasoning: z.ZodOptional<z.ZodObject<{
275
+ effort: z.ZodEnum<{
276
+ low: "low";
277
+ high: "high";
278
+ }>;
279
+ }, z.core.$strict>>;
280
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
281
+ providerOptions: z.ZodOptional<z.ZodObject<{
282
+ xai: z.ZodOptional<z.ZodObject<{
283
+ promptCacheKey: z.ZodOptional<z.ZodString>;
284
+ }, z.core.$strict>>;
285
+ }, z.core.$strict>>;
286
+ }, z.core.$strict>;
287
+
288
+ /**
289
+ * Model descriptor + registry for @gullabs/xai.
290
+ *
291
+ * v1 ships exactly one model: `grok-4.5` (canonical id only — the
292
+ * `grok-4.5-latest` / `grok-build-latest` aliases visible in xAI's
293
+ * `/v1/models` listing are intentionally NOT registered as separate
294
+ * descriptors; reject-don't-map, callers must use `grok-4.5` verbatim).
295
+ *
296
+ * @module
297
+ */
298
+
299
+ declare const grok45ModelDescriptor: ModelDescriptor;
300
+ /** Every model descriptor `@gullabs/xai` contributes. v1 ships only grok-4.5. */
301
+ declare const xaiModelDescriptors: ModelDescriptor[];
302
+ declare const xaiRegistry: ModelRegistry;
303
+
304
+ /**
305
+ * xAI pricing snapshot + cost computation for @gullabs/xai.
306
+ *
307
+ * All rates are in **micro-USD per million tokens** (µUSD/M), matching
308
+ * `@gullabs/core`'s `ModelRates` convention exactly: `cost_µUSD = N *
309
+ * ratePerM / 1_000_000`.
310
+ *
311
+ * This is a SELF-CONTAINED, xai-owned reimplementation — it does NOT import
312
+ * core's Gemini-specific `computeCost`/`GEMINI_PRICING`/`geminiPricingSource`
313
+ * (those are Gemini-only). `PricingSource` is provider-scoped by contract
314
+ * (see `packages/core/src/ports.ts`); this module is xai's own.
315
+ *
316
+ * **Long-context tier.** grok-4.5 charges a premium when the GROSS input
317
+ * token count exceeds 200,000 (`long_context_threshold` in xAI's
318
+ * `/v1/models` listing). Selected by `inputTokens` (incl. cached), not by
319
+ * billable input — mirrors core's `selectRates` convention exactly (strictly
320
+ * greater than 200,000).
321
+ *
322
+ * **Service tiers.** xAI has no service-tier concept for grok-4.5 — there is
323
+ * no `TIER_FACTOR`-equivalent here. `price()`'s `tier` param is accepted for
324
+ * `PricingSource` structural conformance but any *defined* tier is treated
325
+ * as unrecognized → unpriced (reject-don't-map), mirroring core's own
326
+ * defensive stance for an unrecognized tier. `undefined` (no tier requested,
327
+ * the only value xAI adapters ever pass — `xaiAdapter` rejects `serviceTier`
328
+ * upstream) prices normally.
329
+ *
330
+ * **Conversion factor.** xAI's `/v1/models` raw `*_token_price` fields are
331
+ * in hundred-thousandths of a dollar per token (i.e. divide the raw integer
332
+ * by 10,000 to get USD per million tokens): e.g. `grok-4.5`'s raw
333
+ * `prompt_text_token_price: 20000` ÷ 10,000 = $2.00/M, which matches the
334
+ * confirmed live-verified figure.
335
+ *
336
+ * Verified against `/v1/models` fixture captured 2026-07-09 (see
337
+ * `docs/provider-plugins-and-xai-grok-4-5-plan.md` and the live-verification
338
+ * fixture directory referenced in the commit-3 task brief).
339
+ *
340
+ * @module
341
+ */
342
+
343
+ /** Identifies this pricing snapshot — bump the date when rates change. */
344
+ declare const xaiPricingVersion: "xai-2026-07-09";
345
+ /**
346
+ * Per-model rate entry (all values in µUSD per million tokens).
347
+ *
348
+ * `gt200k` (when present) applies when GROSS input tokens > 200,000.
349
+ */
350
+ interface XaiModelRates {
351
+ /** µUSD per million input tokens (billable = gross − cached). */
352
+ inputPerM: number;
353
+ /** µUSD per million cache-read tokens. */
354
+ cachedPerM: number;
355
+ /** µUSD per million output tokens (reasoning tokens are folded in). */
356
+ outputPerM: number;
357
+ /** Optional high-tier rates for long-context (GROSS input > 200k). */
358
+ gt200k?: {
359
+ inputPerM: number;
360
+ cachedPerM: number;
361
+ outputPerM: number;
362
+ };
363
+ }
364
+ /**
365
+ * Frozen xAI pricing snapshot (per-1M in µUSD).
366
+ *
367
+ * Keys are EXACT canonical model identifiers — no prefix or alias matching.
368
+ * xAI aliases (e.g. `grok-4.5-latest`, `grok-build-latest`) are deliberately
369
+ * NOT registered/priced (reject-don't-map): callers must use the canonical
370
+ * id; anything else resolves to the unpriced path.
371
+ */
372
+ declare const XAI_PRICING: Readonly<Record<string, XaiModelRates>>;
373
+ /**
374
+ * Compute the cost of an xAI LLM call given a model name and usage data.
375
+ *
376
+ * Pure function — no side effects, always returns a well-formed {@link Cost}.
377
+ *
378
+ * **Algorithm** (mirrors `@gullabs/core`'s `computeCost` exactly, xai-owned):
379
+ * 1. Look up rates for `model`; if not found, return an unpriced `Cost`
380
+ * (`microUsd: null`) naming the model.
381
+ * 2. A defined `tier` is always unpriced (xai has no tiers; reject-don't-map).
382
+ * `undefined` prices normally.
383
+ * 3. Select base vs. `>200k` long-context rates from GROSS `inputTokens`.
384
+ * 4. Billable input = `inputTokens − (cachedInputTokens ?? 0)`, clamped to 0.
385
+ * 5. Round each component (input, cached, output) independently to the
386
+ * nearest integer micro-USD.
387
+ * 6. `microUsd` is the sum of the three components — guarantees
388
+ * `details.input + details.cached + details.output === microUsd` exactly.
389
+ */
390
+ declare function computeXaiCost(model: string, usage: Usage, tier?: string): Cost;
391
+ /**
392
+ * Factory that returns the **xai-scoped** {@link PricingSource} port
393
+ * implementation backed by {@link XAI_PRICING}.
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * import { xaiPricingSource } from '@gullabs/xai'
398
+ *
399
+ * const pricing = xaiPricingSource()
400
+ * const cost = pricing.price('grok-4.5', usage)
401
+ * ```
402
+ */
403
+ declare function xaiPricingSource(): PricingSource;
404
+
405
+ /**
406
+ * `xaiProvider` — {@link ProviderPlugin} factory for @gullabs/xai.
407
+ *
408
+ * Bundles the xAI Grok adapter, the `grok-4.5` model descriptor, and the
409
+ * xai pricing source into a single plugin for {@link composeProviders}.
410
+ *
411
+ * @module
412
+ */
413
+
414
+ /**
415
+ * Create a {@link ProviderPlugin} for the xAI Grok provider.
416
+ *
417
+ * @param opts - Forwarded to {@link xaiAdapter}.
418
+ * @returns A plugin bundling the xAI adapter, the `grok-4.5` model
419
+ * descriptor, and the built-in xai pricing source.
420
+ *
421
+ * @example
422
+ * ```ts
423
+ * import { createClient, composeProviders } from '@gullabs/core'
424
+ * import { xaiProvider } from '@gullabs/xai'
425
+ *
426
+ * const client = createClient({
427
+ * ...composeProviders([xaiProvider()]),
428
+ * })
429
+ * ```
430
+ */
431
+ declare function xaiProvider(opts?: XaiAdapterOptions): ProviderPlugin;
432
+
433
+ export { Grok45ConfigSchema, XAI_PRICING, type XaiAdapterOptions, type XaiClientLike, type XaiInputContentPart, type XaiInputImagePart, type XaiInputItem, type XaiInputTextPart, type XaiMessageOutputItem, type XaiModelRates, type XaiOutputItem, type XaiOutputTextPart, type XaiProviderOptions, type XaiReasoningOutputItem, type XaiReasoningSummaryPart, type XaiResponseCreateParams, type XaiResponseShape, type XaiTextFormat, type XaiUsageShape, buildXaiClient, classifyXaiError, computeXaiCost, grok45ModelDescriptor, requireApiKey, xaiAdapter, xaiModelDescriptors, xaiPricingSource, xaiPricingVersion, xaiProvider, xaiRegistry };