@volter/twin-xai 0.1.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.
Files changed (56) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +246 -0
  3. package/client/xai-device-auth.css +246 -0
  4. package/client/xai-device-auth.tsx +138 -0
  5. package/dist/client/xai-device-auth.bundle.js +18 -0
  6. package/dist/client/xai-device-auth.css +246 -0
  7. package/dist/client/xai-device-auth.d.ts +19 -0
  8. package/dist/client/xai-device-auth.js +50 -0
  9. package/dist/client/xai-device-auth.tsx +138 -0
  10. package/dist/src/cli.d.ts +2 -0
  11. package/dist/src/cli.js +28 -0
  12. package/dist/src/index.d.ts +17 -0
  13. package/dist/src/index.js +70 -0
  14. package/dist/src/xai-budget.d.ts +60 -0
  15. package/dist/src/xai-budget.js +139 -0
  16. package/dist/src/xai-capabilities.d.ts +4 -0
  17. package/dist/src/xai-capabilities.js +1072 -0
  18. package/dist/src/xai-conformance.d.ts +13 -0
  19. package/dist/src/xai-conformance.js +148 -0
  20. package/dist/src/xai-connector.d.ts +82 -0
  21. package/dist/src/xai-connector.js +174 -0
  22. package/dist/src/xai-device-auth-css.gen.d.ts +1 -0
  23. package/dist/src/xai-device-auth-css.gen.js +6 -0
  24. package/dist/src/xai-device-auth-ui.d.ts +13 -0
  25. package/dist/src/xai-device-auth-ui.js +72 -0
  26. package/dist/src/xai-models.d.ts +57 -0
  27. package/dist/src/xai-models.js +102 -0
  28. package/dist/src/xai-oauth.d.ts +30 -0
  29. package/dist/src/xai-oauth.js +279 -0
  30. package/dist/src/xai-scenario.d.ts +33 -0
  31. package/dist/src/xai-scenario.js +139 -0
  32. package/dist/src/xai-server.d.ts +36 -0
  33. package/dist/src/xai-server.js +232 -0
  34. package/dist/src/xai-stub.d.ts +69 -0
  35. package/dist/src/xai-stub.js +210 -0
  36. package/dist/src/xai-twin.d.ts +89 -0
  37. package/dist/src/xai-twin.js +883 -0
  38. package/dist/src/xai-types.d.ts +118 -0
  39. package/dist/src/xai-types.js +6 -0
  40. package/package.json +76 -0
  41. package/src/cli.ts +27 -0
  42. package/src/index.ts +120 -0
  43. package/src/xai-budget.ts +165 -0
  44. package/src/xai-capabilities.ts +1046 -0
  45. package/src/xai-conformance.ts +136 -0
  46. package/src/xai-connector.ts +212 -0
  47. package/src/xai-device-auth-css.gen.ts +6 -0
  48. package/src/xai-device-auth-ui.ts +90 -0
  49. package/src/xai-journey.uitest.ts +155 -0
  50. package/src/xai-models.ts +154 -0
  51. package/src/xai-oauth.ts +301 -0
  52. package/src/xai-scenario.ts +148 -0
  53. package/src/xai-server.ts +258 -0
  54. package/src/xai-stub.ts +213 -0
  55. package/src/xai-twin.ts +960 -0
  56. package/src/xai-types.ts +111 -0
@@ -0,0 +1,118 @@
1
+ /** A chat message param as the caller sends it (content is a string OR a content-part array). */
2
+ export type ChatMessageParam = {
3
+ role: 'system' | 'user' | 'assistant' | 'tool';
4
+ content?: string | Array<Record<string, unknown>> | null;
5
+ name?: string;
6
+ tool_calls?: ChatToolCall[];
7
+ tool_call_id?: string;
8
+ };
9
+ /** A function tool_call inside an assistant message (faithful shape). */
10
+ export type ChatToolCall = {
11
+ id: string;
12
+ type: 'function';
13
+ function: {
14
+ name: string;
15
+ arguments: string;
16
+ };
17
+ };
18
+ /** xAI reports token detail objects inside `usage` (prompt_tokens_details with text/audio/
19
+ * image/cached splits; completion_tokens_details with reasoning tokens), plus the Live Search
20
+ * `num_sources_used` counter. Deterministic in the twin. */
21
+ export type ChatUsage = {
22
+ prompt_tokens: number;
23
+ completion_tokens: number;
24
+ total_tokens: number;
25
+ prompt_tokens_details: {
26
+ text_tokens: number;
27
+ audio_tokens: number;
28
+ image_tokens: number;
29
+ cached_tokens: number;
30
+ };
31
+ completion_tokens_details: {
32
+ reasoning_tokens: number;
33
+ audio_tokens: number;
34
+ accepted_prediction_tokens: number;
35
+ rejected_prediction_tokens: number;
36
+ };
37
+ num_sources_used: number;
38
+ };
39
+ export type ChatChoice = {
40
+ index: number;
41
+ message: {
42
+ role: 'assistant';
43
+ content: string | null;
44
+ /** xAI delta: grok-3-mini exposes its chain-of-thought here (the twin's is a labeled stub);
45
+ * grok-4-family reasoning models spend reasoning tokens but never expose the content. */
46
+ reasoning_content?: string;
47
+ tool_calls?: ChatToolCall[];
48
+ refusal?: null;
49
+ };
50
+ logprobs: null;
51
+ finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter';
52
+ };
53
+ /** The unary chat.completion response envelope (faithful shape; xAI ids are UUID-formatted). */
54
+ export type ChatCompletion = {
55
+ id: string;
56
+ object: 'chat.completion';
57
+ created: number;
58
+ model: string;
59
+ choices: ChatChoice[];
60
+ usage: ChatUsage;
61
+ system_fingerprint: string;
62
+ /** Live Search: the source URLs consulted (deterministic labeled stubs in the twin). Present
63
+ * only when search ran and return_citations was not disabled. */
64
+ citations?: string[];
65
+ };
66
+ export type TextCompletion = {
67
+ id: string;
68
+ object: 'text_completion';
69
+ created: number;
70
+ model: string;
71
+ choices: Array<{
72
+ text: string;
73
+ index: number;
74
+ logprobs: null;
75
+ finish_reason: 'stop' | 'length';
76
+ }>;
77
+ usage: ChatUsage;
78
+ system_fingerprint: string;
79
+ };
80
+ export type MessagesContentBlock = {
81
+ type: 'text';
82
+ text: string;
83
+ } | {
84
+ type: 'tool_use';
85
+ id: string;
86
+ name: string;
87
+ input: Record<string, unknown>;
88
+ };
89
+ export type MessagesResponse = {
90
+ id: string;
91
+ type: 'message';
92
+ role: 'assistant';
93
+ model: string;
94
+ content: MessagesContentBlock[];
95
+ stop_reason: 'end_turn' | 'max_tokens' | 'stop_sequence' | 'tool_use';
96
+ stop_sequence: string | null;
97
+ usage: {
98
+ input_tokens: number;
99
+ output_tokens: number;
100
+ };
101
+ };
102
+ /** The xAI error envelope. Unlike OpenAI's nested `{ error: {...} }`, xAI's REST layer is a
103
+ * gRPC transcoding and returns TWO FLAT STRINGS: `code` (a gRPC status description, e.g.
104
+ * "Client specified an invalid argument") and `error` (the human message). The real
105
+ * `@ai-sdk/xai` provider parses exactly this shape. */
106
+ export type XaiError = {
107
+ code: string;
108
+ error: string;
109
+ };
110
+ /** A single Server-Sent Event the streaming path emits (collected, never socketed in tests).
111
+ * `data` is the JSON payload; `[DONE]` is signalled with `done: true` (no data object). */
112
+ export type SseEvent = {
113
+ data?: Record<string, unknown>;
114
+ done?: boolean;
115
+ };
116
+ /** A sink the streaming path writes events into (an injected collector in tests / a real
117
+ * HTTP SSE writer in the server). NO real sockets or setTimeout in the handler. */
118
+ export type SseSink = (event: SseEvent) => void;
@@ -0,0 +1,6 @@
1
+ // Shared wire-shape types for the xAI (Grok) API surface. These mirror the real vendor JSON
2
+ // shapes (not any SDK's internal types — the twin never imports an SDK at runtime; the real
3
+ // `@ai-sdk/xai` provider is exercised only in *.test.ts). xAI's chat surface is OpenAI-chat-
4
+ // compatible with xAI-specific deltas (Live Search citations, reasoning_content, deferred
5
+ // completions, the {code, error} error envelope), and those deltas are modeled here explicitly.
6
+ export {};
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@volter/twin-xai",
3
+ "version": "0.1.0",
4
+ "description": "Local xAI (Grok) twin — a faithful, stateful local API plus the Grok Build device-OAuth browser surface. The model is stubbed (deterministic) and Live Search citations are labeled stubs, while the OAuth/account/usage world and API protocol envelopes remain coherent and vendor-shaped. Built on @volter/world-core.",
5
+ "keywords": [
6
+ "twin",
7
+ "local",
8
+ "mock",
9
+ "mirror",
10
+ "simulator",
11
+ "fixtures",
12
+ "testing",
13
+ "sdk",
14
+ "api",
15
+ "localstack",
16
+ "xai",
17
+ "grok",
18
+ "llm"
19
+ ],
20
+ "author": "Volter (https://github.com/volter-ai)",
21
+ "license": "Apache-2.0",
22
+ "files": [
23
+ "src",
24
+ "client",
25
+ "README.md",
26
+ "LICENSE",
27
+ "!**/*.test.ts",
28
+ "!**/*.test.tsx",
29
+ "dist"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/volter-ai/twin.git",
34
+ "directory": "packages/twin/xai"
35
+ },
36
+ "homepage": "https://github.com/volter-ai/twin/tree/main/packages/twin/xai#readme",
37
+ "type": "module",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/src/index.d.ts",
41
+ "default": "./dist/src/index.js"
42
+ }
43
+ },
44
+ "bin": {
45
+ "world-xai": "dist/src/cli.js"
46
+ },
47
+ "scripts": {
48
+ "test": "bun test src/*.test.ts",
49
+ "typecheck": "tsc --noEmit",
50
+ "build": "node ../../../scripts/publish/build.mjs",
51
+ "prepack": "node ../../../scripts/publish/prepare-publish.mjs prepack",
52
+ "postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
53
+ },
54
+ "dependencies": {
55
+ "react": "^19.2.7",
56
+ "react-dom": "^19.2.7"
57
+ },
58
+ "peerDependencies": {
59
+ "@volter/world-core": "2.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@volter/world-core": "2.0.0",
63
+ "@volter/world-tooling": "0.1.0",
64
+ "@ai-sdk/xai": "^3.0.111",
65
+ "ai": "^6.0.0",
66
+ "zod": "^4.0.0",
67
+ "@types/bun": "^1.2.20",
68
+ "@types/node": "^24.0.0",
69
+ "@types/react": "^19.2.17",
70
+ "@types/react-dom": "^19.2.3",
71
+ "typescript": "^5.9.0"
72
+ },
73
+ "engines": {
74
+ "node": ">=22.3"
75
+ }
76
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { keepProcessAlive } from '@volter/world-core/lifecycle';
3
+ // world-xai CLI: serve the xAI/Grok API, OAuth protocol UI, or run conformance. `mirror` is an
4
+ // alias for the SAME server because entry and consent are the vendor-owned browser leg of OAuth;
5
+ // denial and completion are explicitly Twin-local terminal states, not a second dashboard.
6
+ import { hasFlag, optionValue } from '@volter/world-core/args';
7
+ import { createXaiTwinServer } from './xai-server.ts';
8
+
9
+ const [cmd, ...rest] = process.argv.slice(2);
10
+ const port = Number(optionValue(rest, '--port', '0')) || undefined;
11
+ const root = optionValue(rest, '--root') || undefined;
12
+ const readOnly = hasFlag(rest, '--read-only'); // a twin accepts writes unless started read-only
13
+ const scenario = optionValue(rest, '--scenario') || undefined; // scripted completions (JSON scenario file)
14
+
15
+ if (cmd === 'serve' || cmd === 'mirror') {
16
+ const s = await createXaiTwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}), ...(scenario ? { scenarioPath: scenario } : {}) });
17
+ process.stdout.write(`xai twin (chat/completions/messages output is a deterministic stub)${readOnly ? ' [read-only]' : ''}${scenario ? ` [scenario: ${scenario}]` : ''} at http://127.0.0.1:${s.port}\n`);
18
+ await keepProcessAlive();
19
+ } else if (cmd === 'conformance') {
20
+ // dev-only; lazy so the bin runs without @volter/world-tooling
21
+ const { checkXaiConformance } = await import('./xai-conformance.ts');
22
+ const report = await checkXaiConformance({ ...(root ? { root } : {}) });
23
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
24
+ if (!report.ok) process.exitCode = 1;
25
+ } else {
26
+ process.stdout.write('Usage: world-xai serve|mirror|conformance [--port N] [--root DIR] [--read-only] [--scenario FILE]\n');
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,120 @@
1
+ // @volter/twin-xai — the xAI (Grok) twin (one vendor, one package), built on the shared
2
+ // @volter/world-core kernel. The xAI API: an OpenAI-chat-compatible protocol envelope with xAI's
3
+ // deltas modeled explicitly — the grok-4-family model catalogs (/v1/models,
4
+ // /v1/language-models, /v1/image-generation-models), the flat {code, error} error envelope,
5
+ // deferred completions (stateful, kernel-backed), Live Search parameters + citations,
6
+ // reasoning_content/reasoning-token accounting, the Anthropic-compatible /v1/messages
7
+ // endpoint, legacy /v1/completions, api-key introspection, tokenize-text, the vendor-owned Grok
8
+ // Build device-entry/consent UI, and explicitly Twin-local terminal result states. The xAI console
9
+ // itself remains outside this API-first pack.
10
+ //
11
+ // THE DETERMINISTIC STUBS ARE THE ANSWER: every inference endpoint returns a clearly-labeled
12
+ // STUB completion, and Live Search returns deterministic labeled citations. Everything around
13
+ // them — the wire protocol — is faithful. (Conformance + capability tooling live in @volter/world-tooling, a dev
14
+ // dependency — NOT re-exported here, per E2.)
15
+ export { handleXaiTwinRequest, streamChat, buildChatCompletion } from './xai-twin.ts';
16
+ export type { XaiRequest, XaiResponseEnvelope } from './xai-twin.ts';
17
+ export { createXaiTwinFetch, createXaiTwinServer, type XaiTwinFetchOptions } from './xai-server.ts';
18
+ export {
19
+ deviceConsentPageHtml,
20
+ deviceDonePageHtml,
21
+ deviceEntryPageHtml,
22
+ xaiDeviceAuthView,
23
+ XAI_DEVICE_STYLE_PATH,
24
+ } from './xai-device-auth-ui.ts';
25
+ export {
26
+ XAI_MODELS, XAI_LANGUAGE_MODELS, XAI_IMAGE_GENERATION_MODELS,
27
+ findModel, findLanguageModel, findImageGenerationModel, modelBehavior, canonicalModelId,
28
+ } from './xai-models.ts';
29
+ export type { XaiModel, XaiLanguageModel, XaiImageGenerationModel, XaiModelBehavior } from './xai-models.ts';
30
+ export {
31
+ contentToText, countPromptTokens, countPromptTokenDetails, estimateTokens, lastUserText,
32
+ pseudoTokenize, stubAssistantText, stubCitations, stubJsonObject, stubReasoningText,
33
+ stubToolArguments, stubToolCall, uuidFromSeed,
34
+ } from './xai-stub.ts';
35
+ export type { XaiToken } from './xai-stub.ts';
36
+ export type {
37
+ ChatMessageParam, ChatToolCall, ChatCompletion, ChatChoice, ChatUsage,
38
+ TextCompletion, MessagesResponse, MessagesContentBlock, XaiError, SseEvent, SseSink,
39
+ } from './xai-types.ts';
40
+ export { createXaiScenarioEngine, loadXaiScenarioDocument, realizeXaiRespond, xaiScenarioAdapter } from './xai-scenario.ts';
41
+ export type { ScenarioToolCall, ScriptedResult, XaiScenarioEngine, XaiScenarioRequest, XaiScenarioRespond } from './xai-scenario.ts';
42
+ export {
43
+ XAI_BUDGET_CEILING,
44
+ XAI_BUDGET_MAX_RETRY_AFTER_S,
45
+ XAI_BUDGET_WINDOW_MS,
46
+ XAI_CALL_WEIGHTS,
47
+ XAI_RATE_BUDGET,
48
+ XaiBudget,
49
+ XaiBudgetError,
50
+ xaiBudgetPath,
51
+ xaiCallWeight,
52
+ splitXaiPath,
53
+ } from './xai-budget.ts';
54
+ export type {
55
+ XaiBudgetErrorKind,
56
+ XaiBudgetOptions,
57
+ XaiBudgetReservation,
58
+ XaiBudgetSnapshot,
59
+ } from './xai-budget.ts';
60
+ export {
61
+ XAI_API_BASE,
62
+ liveXaiExecute,
63
+ mapModel,
64
+ mapLanguageModel,
65
+ pullXaiModels,
66
+ syncXaiFromReal,
67
+ pushXaiAction,
68
+ } from './xai-connector.ts';
69
+ export type { XaiExecute, LiveXaiOptions } from './xai-connector.ts';
70
+
71
+ // Registry descriptor: the pack self-describes so tooling can discover it.
72
+ import { registerPack, type TwinPack } from '@volter/world-core';
73
+ import { XAI_RATE_BUDGET as RATE_BUDGET } from './xai-budget.ts';
74
+ import { performXaiAction, syncXaiFromRemote } from './xai-connector.ts';
75
+
76
+ export const pack: TwinPack = {
77
+ // PROTOCOL 2 (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin): the pack is a plugin — its wire, its tree, and its half of the real
78
+ // state system. Moved 2026-09-08. The xAI API has NO client-writable resource, so `perform` settles every
79
+ // entry with that reason and only `refresh` reads; the pack's executor is GET-only by type to say so.
80
+ protocol: '2',
81
+ refresh: { every: '30m', onDemand: { atMost: '60s' } }, // a model catalogue moves slowly
82
+ stateSystem: { perform: performXaiAction, refresh: syncXaiFromRemote },
83
+ // the round trip: a DEFERRED completion — a plain completion is answered and forgotten, but xAI's
84
+ // deferred mode persists the request so a caller can poll it, which is the row this twin keeps
85
+ roundTrip: { method: 'POST', path: '/v1/chat/completions', body: { model: 'grok-4-latest', deferred: true, messages: [{ role: 'user', content: 'round trip' }] }, headers: { authorization: 'Bearer round-trip' } },
86
+ parityOrigin: 'http://twin',
87
+ vendor: 'xai',
88
+ // The client-side rate budget, armed by `registerPack` as well as by importing the budget module.
89
+ rateBudget: RATE_BUDGET,
90
+ transport: 'rest',
91
+ archetype: 'generative',
92
+ bin: 'world-xai',
93
+ // R2 adopted-as-debt, legibly: declared types no replay can create, each with its reason.
94
+ resourcesUnreachable: {
95
+ 'language_model': 'the vendor catalog',
96
+ 'model': 'the vendor catalog',
97
+ },
98
+ resources: ['deferred_completion', 'language_model', 'model', 'oauth_account', 'oauth_device_authorization', 'oauth_usage'],
99
+ specSource: 'xAI (Grok) REST API + Grok Build OAuth/device and chat-proxy protocols (envelope-faithful; model output is a deterministic stub, Live Search citations are deterministic labeled stubs)',
100
+ description: 'xAI (Grok) twin — coherent virtual account/device OAuth/refresh/usage plus the Grok CLI proxy and OpenAI-chat-compatible API envelope; generative output is a labeled stub.',
101
+ // Adoption + interception, moved off the central maps unchanged (descriptor-first back-migration, adding-a-twin.md §3,
102
+ // 2026-08-31). Both credential-env spellings the vendor and its users write —
103
+ // XAI_* and GROK_*.
104
+ adoption: {
105
+ // xAI's official Python SDK (xai-org/xai-sdk-python). An `openai` client pointed at api.x.ai is NOT
106
+ // claimed here: that distribution is the openai pack's and XAI_API_BASE is what routes it.
107
+ pypi: ['xai-sdk'],
108
+ sdks: ['@ai-sdk/xai'], envStems: ['XAI', 'GROK'],
109
+ },
110
+ // Inference lives on https://api.x.ai — the pack's connector pins XAI_API_BASE =
111
+ // 'https://api.x.ai' (xai-connector.ts). management-api.x.ai is a separate console/
112
+ // administration product the pack deliberately does NOT twin (README §Management API) — left
113
+ // unclaimed so strict egress fails closed rather than letting a half-modeled flow through.
114
+ hosts: [{ host: 'api.x.ai' }],
115
+ // The SDK calls the same-origin '/v1/…' and loads from api.x.ai — the dev proxy forwards
116
+ // '/v1/' to the twin and strips the absolute host so calls come back same-origin.
117
+ browserRouting: { apiPathPrefix: '/v1/', loaderHost: 'https://api.x.ai' },
118
+ };
119
+
120
+ registerPack(pack);
@@ -0,0 +1,165 @@
1
+ // xAI's CLIENT-SIDE RATE BUDGET — the pack's DECLARATION (the numbers) plus the thin typed bindings
2
+ // `liveXaiExecute` uses. The MECHANISM — the durable token-keyed ledger, the rolling window,
3
+ // reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a corrupt ledger — lives ONCE
4
+ // in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`). Read that module's header for
5
+ // the full rationale AND for the honest list of what the guard does not guarantee (an injected clock
6
+ // or ledger path still defeats it — it guards carelessness, not malice).
7
+ //
8
+ // ── WHY THIS EXISTS ──────────────────────────────────────────────────────────────────────────
9
+ // A ~4.5-day Figma token lockout (2026-07-25) came from a burst of raw vendor calls made OUTSIDE any
10
+ // guarded client. `liveXaiExecute` was exactly that shape: this pack's one construction site for a
11
+ // real client, and a BARE `fetch` with the credential attached as `Authorization: Bearer` and
12
+ // NOTHING in front of it — no ceiling, no cooldown, no ledger. `scripts/rate-budget-coverage.test.ts`
13
+ // is the gate that found it; this module is the fix.
14
+ //
15
+ // ── HOW THE CEILING WAS CHOSEN: xAI PUBLISHES NO SCALAR LIMIT AT ALL ─────────────────────────
16
+ // This is a DISCLAIMER, not a model of a limit, and it is a stronger disclaimer than gemini's.
17
+ //
18
+ // xAI's public documentation states no rate limit anywhere that could bind this connector: neither
19
+ // the API reference (docs.x.ai/docs/api-reference), nor the models page (docs.x.ai/docs/models,
20
+ // which carries pricing and capability tables but no RPM/RPS/TPM figures), nor the overview or
21
+ // quickstart pages publish a requests-per-minute, requests-per-second or tokens-per-minute number,
22
+ // and none of them even direct the reader to a dashboard where one could be read. (The URL commonly
23
+ // cited for this, docs.x.ai/docs/consumption-and-rate-limits, returns 404.)
24
+ //
25
+ // So there is no vendor figure to size against, and the rule for that case is to SAY SO and pin to
26
+ // the kernel's austere fallback rather than borrow an adjacent-but-different number. Copying, say,
27
+ // OpenAI's tier limits because xAI's API is OpenAI-chat-compatible would be exactly the fabrication
28
+ // this rule exists to prevent — protocol compatibility is not quota compatibility.
29
+ //
30
+ // Therefore: 60 weighted units per 60s at a default weight of 2 — EXACTLY `DEFAULT_RATE_BUDGET`.
31
+ // 30 calls a minute, one every two seconds. Comfortably above any real pull (the whole pull surface
32
+ // is TWO calls: the flat catalog and the rich one) and far below the shape that causes lockouts.
33
+ // Nothing here is more permissive than the fallback.
34
+ //
35
+ // ── HOW THE WEIGHTS WERE CHOSEN: THEY ARE FLAT, DELIBERATELY ─────────────────────────────────
36
+ // There are NO pricing rules, and their absence is a decision rather than an omission.
37
+ //
38
+ // `XaiExecute` is typed `method: 'GET'` — the pull surface is read-only catalogs
39
+ // (`/v1/models`, `/v1/language-models`) and the push surface REFUSES LOUDLY for every operation,
40
+ // because the xAI API has no client-writable resources. So no caller of this execute can reach the
41
+ // token-billed inference endpoints (`POST /v1/chat/completions`, `/v1/messages`), and there is no
42
+ // endpoint in reach that is credibly dearer than another.
43
+ //
44
+ // Inventing a ratio anyway — pricing some GET at 4 "to be safe" — would be a made-up number wearing
45
+ // the costume of a vendor fact, which is precisely what the anti-fabrication rule forbids. Every
46
+ // call therefore costs the same 2, and nothing is free.
47
+ import {
48
+ declareRateBudget,
49
+ rateBudgetPath,
50
+ rateBudgetWeight,
51
+ RateBudget,
52
+ type RateBudgetDeclaration,
53
+ type RateBudgetOptions,
54
+ type RateBudgetReservation,
55
+ type RateBudgetSnapshot,
56
+ } from '@volter/world-core';
57
+
58
+ const VENDOR = 'xai';
59
+
60
+ /** Rolling window, in ms. The fallback's, because xAI publishes no per-minute figure to match. */
61
+ export const XAI_BUDGET_WINDOW_MS = 60_000;
62
+
63
+ /**
64
+ * Weighted units allowed inside one window. 60/60s at the default weight of 2 = 30 calls a minute —
65
+ * the kernel's austere fallback exactly, adopted deliberately because xAI publishes no scalar limit.
66
+ */
67
+ export const XAI_BUDGET_CEILING = 60;
68
+
69
+ /** Seconds. A `Retry-After` above this means the key is throttled hard — fail loudly, don't sleep. */
70
+ export const XAI_BUDGET_MAX_RETRY_AFTER_S = 300;
71
+
72
+ /**
73
+ * Per-call cost. FLAT by design — see the header: the execute is GET-only over read-only catalogs,
74
+ * so no reachable endpoint is credibly dearer than another, and inventing a ratio would be a
75
+ * fabricated vendor fact. `other` is the only weight, and it is never zero.
76
+ */
77
+ export const XAI_CALL_WEIGHTS = {
78
+ /** Every call. xAI publishes no per-endpoint cost, and this execute reaches only catalog reads. */
79
+ other: 2,
80
+ } as const;
81
+
82
+ /**
83
+ * THE PACK'S DECLARATION — pure data, the only xAI-specific thing in the whole budget.
84
+ *
85
+ * `rules` is EMPTY on purpose (see the header). Also exported as `pack.rateBudget` (see index.ts),
86
+ * so `registerPack` arms it too.
87
+ */
88
+ export const XAI_RATE_BUDGET: RateBudgetDeclaration = {
89
+ windowMs: XAI_BUDGET_WINDOW_MS,
90
+ ceiling: XAI_BUDGET_CEILING,
91
+ defaultWeight: XAI_CALL_WEIGHTS.other,
92
+ maxRetryAfterSeconds: XAI_BUDGET_MAX_RETRY_AFTER_S,
93
+ rules: [],
94
+ reason:
95
+ 'xAI publishes NO scalar rate limit anywhere in its public documentation that could bind this ' +
96
+ 'connector: neither the API reference (docs.x.ai/docs/api-reference), nor the models page (which ' +
97
+ 'carries pricing and capability tables but no RPM/RPS/TPM figures), nor the overview or quickstart ' +
98
+ 'pages state a requests-per-minute, requests-per-second or tokens-per-minute number, and none even ' +
99
+ 'points at a dashboard where one could be read (the commonly-cited consumption-and-rate-limits URL ' +
100
+ '404s). With no vendor figure to model, this pins to the kernel\'s austere fallback rather than ' +
101
+ 'borrowing OpenAI\'s tier limits on the strength of xAI\'s OpenAI-compatible protocol — protocol ' +
102
+ 'compatibility is not quota compatibility. 60 weighted units / 60s at 2 per call = 30 calls a ' +
103
+ 'minute, identical to DEFAULT_RATE_BUDGET and no more permissive than it. Pricing is FLAT and that ' +
104
+ 'is deliberate: XaiExecute is typed GET-only over read-only catalogs and the push surface refuses ' +
105
+ 'loudly, so no caller can reach a token-billed inference endpoint and no reachable endpoint is ' +
106
+ 'credibly dearer than another; inventing a ratio would be a made-up number dressed as vendor fact.',
107
+ };
108
+
109
+ // Declared at module load, so merely importing this module (which `xai-connector.ts` does) is enough
110
+ // to arm the real ceiling. Here the declaration and the kernel's DEFAULT_RATE_BUDGET happen to be
111
+ // numerically identical, so the usual "the fallback is not uniformly tighter" hazard does not bite —
112
+ // but `RateBudget` still reads its policy live, and constructing through the subclass below (which
113
+ // imports this module) is what keeps the ordering a non-issue in general.
114
+ declareRateBudget(VENDOR, XAI_RATE_BUDGET);
115
+
116
+ /** Split a connector path (which may carry its own query string) into pathname + parsed query. */
117
+ export function splitXaiPath(path: string): { pathname: string; query: Record<string, string> } {
118
+ const q = path.indexOf('?');
119
+ if (q < 0) return { pathname: path, query: {} };
120
+ const query: Record<string, string> = {};
121
+ for (const [k, v] of new URLSearchParams(path.slice(q + 1))) query[k] = v;
122
+ return { pathname: path.slice(0, q), query };
123
+ }
124
+
125
+ /**
126
+ * Price one call. Keyed off the request the connector is ABOUT to make. With no rules declared every
127
+ * call takes `defaultWeight` — which is the point: an unclassified endpoint must never be free.
128
+ */
129
+ export function xaiCallWeight(method: string, path: string): number {
130
+ const { pathname, query } = splitXaiPath(path);
131
+ return rateBudgetWeight(VENDOR, `${(method || 'GET').toUpperCase()} ${pathname}`, query);
132
+ }
133
+
134
+ /** Where xAI's ledger lives. API-key-keyed and cwd-independent by default (the limit, whatever it
135
+ * is, is per key, so a cwd-scoped ledger would hand the same key a fresh allowance in every
136
+ * checkout, worktree and CI matrix leg); pass `root` to opt into world-scoped accounting instead. */
137
+ export function xaiBudgetPath(opts: { root?: string; token?: string } | string = {}): string {
138
+ const o = typeof opts === 'string' ? { root: opts } : opts;
139
+ // VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through (TypeScript's
140
+ // excess-property check only catches object literals) must not redirect this pack's ledger to
141
+ // another vendor's file.
142
+ return rateBudgetPath({ ...o, vendor: VENDOR });
143
+ }
144
+
145
+ /** Construction options for xAI's budget. The vendor is fixed; everything else may only TIGHTEN. */
146
+ export type XaiBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
147
+
148
+ /**
149
+ * xAI's budget — the shared kernel guard bound to this vendor's declaration. A real subclass, not an
150
+ * alias, so the guard check in `liveXaiExecute` still means "a budget that accounts against XAI's
151
+ * ledger under XAI's ceiling": another vendor's `RateBudget` (with its own, possibly larger,
152
+ * ceiling) is NOT assignable there.
153
+ */
154
+ export class XaiBudget extends RateBudget {
155
+ constructor(opts: XaiBudgetOptions = {}) {
156
+ super({ ...opts, vendor: VENDOR });
157
+ }
158
+ }
159
+
160
+ /** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
161
+ * which one refused, and `err.kind` says why. */
162
+ export { RateBudgetError as XaiBudgetError } from '@volter/world-core';
163
+ export type { RateBudgetErrorKind as XaiBudgetErrorKind } from '@volter/world-core';
164
+ export type XaiBudgetReservation = RateBudgetReservation;
165
+ export type XaiBudgetSnapshot = RateBudgetSnapshot;