@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.
- package/LICENSE +202 -0
- package/README.md +246 -0
- package/client/xai-device-auth.css +246 -0
- package/client/xai-device-auth.tsx +138 -0
- package/dist/client/xai-device-auth.bundle.js +18 -0
- package/dist/client/xai-device-auth.css +246 -0
- package/dist/client/xai-device-auth.d.ts +19 -0
- package/dist/client/xai-device-auth.js +50 -0
- package/dist/client/xai-device-auth.tsx +138 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +28 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.js +70 -0
- package/dist/src/xai-budget.d.ts +60 -0
- package/dist/src/xai-budget.js +139 -0
- package/dist/src/xai-capabilities.d.ts +4 -0
- package/dist/src/xai-capabilities.js +1072 -0
- package/dist/src/xai-conformance.d.ts +13 -0
- package/dist/src/xai-conformance.js +148 -0
- package/dist/src/xai-connector.d.ts +82 -0
- package/dist/src/xai-connector.js +174 -0
- package/dist/src/xai-device-auth-css.gen.d.ts +1 -0
- package/dist/src/xai-device-auth-css.gen.js +6 -0
- package/dist/src/xai-device-auth-ui.d.ts +13 -0
- package/dist/src/xai-device-auth-ui.js +72 -0
- package/dist/src/xai-models.d.ts +57 -0
- package/dist/src/xai-models.js +102 -0
- package/dist/src/xai-oauth.d.ts +30 -0
- package/dist/src/xai-oauth.js +279 -0
- package/dist/src/xai-scenario.d.ts +33 -0
- package/dist/src/xai-scenario.js +139 -0
- package/dist/src/xai-server.d.ts +36 -0
- package/dist/src/xai-server.js +232 -0
- package/dist/src/xai-stub.d.ts +69 -0
- package/dist/src/xai-stub.js +210 -0
- package/dist/src/xai-twin.d.ts +89 -0
- package/dist/src/xai-twin.js +883 -0
- package/dist/src/xai-types.d.ts +118 -0
- package/dist/src/xai-types.js +6 -0
- package/package.json +76 -0
- package/src/cli.ts +27 -0
- package/src/index.ts +120 -0
- package/src/xai-budget.ts +165 -0
- package/src/xai-capabilities.ts +1046 -0
- package/src/xai-conformance.ts +136 -0
- package/src/xai-connector.ts +212 -0
- package/src/xai-device-auth-css.gen.ts +6 -0
- package/src/xai-device-auth-ui.ts +90 -0
- package/src/xai-journey.uitest.ts +155 -0
- package/src/xai-models.ts +154 -0
- package/src/xai-oauth.ts +301 -0
- package/src/xai-scenario.ts +148 -0
- package/src/xai-server.ts +258 -0
- package/src/xai-stub.ts +213 -0
- package/src/xai-twin.ts +960 -0
- package/src/xai-types.ts +111 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// xAI model catalog — the static model-listing surface. xAI serves THREE catalogs:
|
|
2
|
+
// • GET /v1/models — OpenAI-compatible ({ id, object:'model', created, owned_by })
|
|
3
|
+
// • GET /v1/language-models — xAI-native rich objects ({ models: [...] } with modalities,
|
|
4
|
+
// per-token prices, aliases, fingerprint)
|
|
5
|
+
// • GET /v1/image-generation-models — same rich shape for the image models
|
|
6
|
+
// The twin returns a faithful, deterministic slice of the published grok catalog so
|
|
7
|
+
// `models.list()` / alias resolution round-trip like the vendor. This is a STATIC surface (the
|
|
8
|
+
// registry doesn't change at runtime), so it is a plain data table rather than kernel state —
|
|
9
|
+
// the connector can additionally fold OBSERVED real models into the served list (xai-twin.ts).
|
|
10
|
+
//
|
|
11
|
+
// Price fields follow the vendor's integer per-token price representation; the values here are
|
|
12
|
+
// deterministic representative constants, not a live price sheet (see README ## Coverage).
|
|
13
|
+
// The grok-4-family + supporting catalog (ids are the exact vendor strings; aliases resolve for
|
|
14
|
+
// inference AND retrieval, like the vendor).
|
|
15
|
+
const LANGUAGE_CATALOG = [
|
|
16
|
+
{
|
|
17
|
+
id: 'grok-4-0709', fingerprint: 'fp_twin_grok4', created: 1752019200, object: 'model', owned_by: 'xai',
|
|
18
|
+
version: '1.0.0', input_modalities: ['text', 'image'], output_modalities: ['text'],
|
|
19
|
+
prompt_text_token_price: 30000, cached_prompt_text_token_price: 7500, prompt_image_token_price: 30000,
|
|
20
|
+
completion_text_token_price: 150000, search_price: 250000, aliases: ['grok-4', 'grok-4-latest'],
|
|
21
|
+
behavior: { supportsReasoningEffort: false, isReasoningModel: true },
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'grok-4-fast-reasoning', fingerprint: 'fp_twin_grok4fr', created: 1758240000, object: 'model', owned_by: 'xai',
|
|
25
|
+
version: '1.0.0', input_modalities: ['text', 'image'], output_modalities: ['text'],
|
|
26
|
+
prompt_text_token_price: 2000, cached_prompt_text_token_price: 500, prompt_image_token_price: 2000,
|
|
27
|
+
completion_text_token_price: 5000, search_price: 250000, aliases: ['grok-4-fast', 'grok-4-fast-latest'],
|
|
28
|
+
behavior: { supportsReasoningEffort: false, isReasoningModel: true },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'grok-4-fast-non-reasoning', fingerprint: 'fp_twin_grok4fnr', created: 1758240000, object: 'model', owned_by: 'xai',
|
|
32
|
+
version: '1.0.0', input_modalities: ['text', 'image'], output_modalities: ['text'],
|
|
33
|
+
prompt_text_token_price: 2000, cached_prompt_text_token_price: 500, prompt_image_token_price: 2000,
|
|
34
|
+
completion_text_token_price: 5000, search_price: 250000, aliases: [],
|
|
35
|
+
behavior: { supportsReasoningEffort: false, isReasoningModel: false },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'grok-code-fast-1', fingerprint: 'fp_twin_grokcode', created: 1756166400, object: 'model', owned_by: 'xai',
|
|
39
|
+
version: '1.0.0', input_modalities: ['text'], output_modalities: ['text'],
|
|
40
|
+
prompt_text_token_price: 2000, cached_prompt_text_token_price: 200, prompt_image_token_price: 0,
|
|
41
|
+
completion_text_token_price: 15000, search_price: 0, aliases: ['grok-build'],
|
|
42
|
+
behavior: { supportsReasoningEffort: false, isReasoningModel: true },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'grok-3', fingerprint: 'fp_twin_grok3', created: 1743724800, object: 'model', owned_by: 'xai',
|
|
46
|
+
version: '1.0.0', input_modalities: ['text'], output_modalities: ['text'],
|
|
47
|
+
prompt_text_token_price: 30000, cached_prompt_text_token_price: 7500, prompt_image_token_price: 0,
|
|
48
|
+
completion_text_token_price: 150000, search_price: 250000, aliases: ['grok-3-latest', 'grok-3-beta'],
|
|
49
|
+
behavior: { supportsReasoningEffort: false, isReasoningModel: false },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'grok-3-mini', fingerprint: 'fp_twin_grok3mini', created: 1743724800, object: 'model', owned_by: 'xai',
|
|
53
|
+
version: '1.0.0', input_modalities: ['text'], output_modalities: ['text'],
|
|
54
|
+
prompt_text_token_price: 3000, cached_prompt_text_token_price: 750, prompt_image_token_price: 0,
|
|
55
|
+
completion_text_token_price: 5000, search_price: 250000, aliases: ['grok-3-mini-latest', 'grok-3-mini-beta'],
|
|
56
|
+
behavior: { supportsReasoningEffort: true, isReasoningModel: true },
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
export const XAI_IMAGE_GENERATION_MODELS = [
|
|
60
|
+
{
|
|
61
|
+
id: 'grok-2-image-1212', fingerprint: 'fp_twin_grok2img', created: 1734307200, object: 'model', owned_by: 'xai',
|
|
62
|
+
version: '1.0.0', input_modalities: ['text'], output_modalities: ['image'],
|
|
63
|
+
image_price: 70000, aliases: ['grok-2-image', 'grok-2-image-latest'],
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
export const XAI_LANGUAGE_MODELS = LANGUAGE_CATALOG.map(({ behavior: _b, ...m }) => m);
|
|
67
|
+
/** The OpenAI-compatible /v1/models view (language + image-generation models, flat). */
|
|
68
|
+
export const XAI_MODELS = [
|
|
69
|
+
...LANGUAGE_CATALOG.map((m) => ({ id: m.id, object: 'model', created: m.created, owned_by: 'xai' })),
|
|
70
|
+
...XAI_IMAGE_GENERATION_MODELS.map((m) => ({ id: m.id, object: 'model', created: m.created, owned_by: 'xai' })),
|
|
71
|
+
];
|
|
72
|
+
/** Resolve a model id OR one of its aliases to the canonical language-model entry. */
|
|
73
|
+
export function findLanguageModel(id) {
|
|
74
|
+
const hit = LANGUAGE_CATALOG.find((m) => m.id === id || m.aliases.includes(id));
|
|
75
|
+
if (!hit)
|
|
76
|
+
return undefined;
|
|
77
|
+
const { behavior: _b, ...m } = hit;
|
|
78
|
+
return m;
|
|
79
|
+
}
|
|
80
|
+
/** Resolve a model id/alias to the flat OpenAI-compatible model object. */
|
|
81
|
+
export function findModel(id) {
|
|
82
|
+
const lm = LANGUAGE_CATALOG.find((m) => m.id === id || m.aliases.includes(id));
|
|
83
|
+
if (lm)
|
|
84
|
+
return { id: lm.id, object: 'model', created: lm.created, owned_by: 'xai' };
|
|
85
|
+
const im = XAI_IMAGE_GENERATION_MODELS.find((m) => m.id === id || m.aliases.includes(id));
|
|
86
|
+
if (im)
|
|
87
|
+
return { id: im.id, object: 'model', created: im.created, owned_by: 'xai' };
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
export function findImageGenerationModel(id) {
|
|
91
|
+
return XAI_IMAGE_GENERATION_MODELS.find((m) => m.id === id || m.aliases.includes(id));
|
|
92
|
+
}
|
|
93
|
+
/** Twin-internal: behavior flags for an INFERENCE model id/alias (undefined for unknown ids
|
|
94
|
+
* and for image models, which cannot chat). */
|
|
95
|
+
export function modelBehavior(id) {
|
|
96
|
+
return LANGUAGE_CATALOG.find((m) => m.id === id || m.aliases.includes(id))?.behavior;
|
|
97
|
+
}
|
|
98
|
+
/** Canonical id for a model id/alias (the vendor echoes the CANONICAL id in responses). */
|
|
99
|
+
export function canonicalModelId(id) {
|
|
100
|
+
return LANGUAGE_CATALOG.find((m) => m.id === id || m.aliases.includes(id))?.id
|
|
101
|
+
?? XAI_IMAGE_GENERATION_MODELS.find((m) => m.id === id || m.aliases.includes(id))?.id;
|
|
102
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type XaiOauthResult = {
|
|
2
|
+
status: number;
|
|
3
|
+
body: Record<string, unknown>;
|
|
4
|
+
};
|
|
5
|
+
export declare function startXaiTwinDeviceAuthorization(input: {
|
|
6
|
+
body: URLSearchParams;
|
|
7
|
+
occurredAt?: string;
|
|
8
|
+
origin: string;
|
|
9
|
+
root?: string;
|
|
10
|
+
}): Promise<XaiOauthResult>;
|
|
11
|
+
export declare function decideXaiTwinDeviceAuthorization(input: {
|
|
12
|
+
approved: boolean;
|
|
13
|
+
occurredAt?: string;
|
|
14
|
+
root?: string;
|
|
15
|
+
userCode: string;
|
|
16
|
+
}): Promise<XaiOauthResult>;
|
|
17
|
+
export declare function inspectXaiTwinDeviceAuthorization(userCode: string, root?: string): Record<string, unknown> | undefined;
|
|
18
|
+
export declare function exchangeXaiTwinToken(input: {
|
|
19
|
+
body: URLSearchParams;
|
|
20
|
+
occurredAt?: string;
|
|
21
|
+
root?: string;
|
|
22
|
+
}): Promise<XaiOauthResult>;
|
|
23
|
+
export declare function isCurrentXaiTwinAccessToken(accessToken: string, root?: string, occurredAt?: string): boolean;
|
|
24
|
+
export declare function recordXaiTwinUsage(input: {
|
|
25
|
+
completionTokens: number;
|
|
26
|
+
occurredAt?: string;
|
|
27
|
+
promptTokens: number;
|
|
28
|
+
requestId: string;
|
|
29
|
+
root?: string;
|
|
30
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { applyTwinWrite, applyTwinWriteAtomic, projectResources } from '@volter/world-core';
|
|
2
|
+
const SERVICE = 'xai';
|
|
3
|
+
const ACCOUNT_ID = 'acct_twin_grok';
|
|
4
|
+
const DEFAULT_SCOPE = 'openid profile email offline_access grok-cli:access api:access conversations:read conversations:write workspaces:read workspaces:write';
|
|
5
|
+
const ACCESS_TTL_SECONDS = 3_600;
|
|
6
|
+
const DEVICE_TTL_SECONDS = 900;
|
|
7
|
+
function at(occurredAt) {
|
|
8
|
+
return new Date(occurredAt ?? new Date().toISOString());
|
|
9
|
+
}
|
|
10
|
+
/** FNV-1a over the seed, widened by re-hashing with a round counter until `n` hex chars exist. */
|
|
11
|
+
function stableHex(seed, n) {
|
|
12
|
+
let out = '';
|
|
13
|
+
for (let round = 0; out.length < n; round += 1) {
|
|
14
|
+
let h = 0x811c9dc5;
|
|
15
|
+
const s = `${round}:${seed}`;
|
|
16
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
17
|
+
h ^= s.charCodeAt(i);
|
|
18
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
19
|
+
}
|
|
20
|
+
out += h.toString(16).padStart(8, '0');
|
|
21
|
+
}
|
|
22
|
+
return out.slice(0, n);
|
|
23
|
+
}
|
|
24
|
+
// Device codes, user codes and OAuth tokens are STATE (stored on the device-authorization row
|
|
25
|
+
// and served straight back), so they are DETERMINISTIC (R9): a stable hash over the world
|
|
26
|
+
// instant plus the ordinal the write takes, never randomUUID. The shapes are unchanged — 32 hex
|
|
27
|
+
// characters after the prefix, exactly what `randomUUID().replaceAll('-','')` produced. Every
|
|
28
|
+
// mint happens INSIDE the actions lock, from the row count read under that lock, so two
|
|
29
|
+
// identical requests in one millisecond still take different ordinals and both land.
|
|
30
|
+
function token(prefix, seed) {
|
|
31
|
+
return `${prefix}_${stableHex(seed, 32)}`;
|
|
32
|
+
}
|
|
33
|
+
function deviceRows(resources) {
|
|
34
|
+
return resources.filter((resource) => resource.type === 'oauth_device_authorization');
|
|
35
|
+
}
|
|
36
|
+
function oauthError(status, error, description) {
|
|
37
|
+
return { status, body: { error, error_description: description } };
|
|
38
|
+
}
|
|
39
|
+
function accountClaims() {
|
|
40
|
+
return {
|
|
41
|
+
email: 'grok-builder@twin.invalid',
|
|
42
|
+
name: 'Grok Builder (Twin)',
|
|
43
|
+
preferred_username: 'grok-builder-twin',
|
|
44
|
+
sub: 'xai_twin_user_grok_builder',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function idToken() {
|
|
48
|
+
const header = Buffer.from(JSON.stringify({ alg: 'none', typ: 'JWT' })).toString('base64url');
|
|
49
|
+
const payload = Buffer.from(JSON.stringify({ ...accountClaims(), aud: 'grok-build', iss: 'xai-twin' })).toString('base64url');
|
|
50
|
+
return `${header}.${payload}.`;
|
|
51
|
+
}
|
|
52
|
+
async function ensureAccount(root, occurredAt) {
|
|
53
|
+
// ENSURE means "make sure one exists", not "write one per request". Every other ensure* in the
|
|
54
|
+
// catalog checks first; this one wrote unconditionally and was only ever one row because the
|
|
55
|
+
// kernel used to collapse identical writes — a local vendor write is now an OCCURRENCE, so
|
|
56
|
+
// without this check every device-authorization start appended a pending oauth_account.ensure
|
|
57
|
+
// (2026-09-04, in review).
|
|
58
|
+
if (projectResources(SERVICE, root).some((r) => r.type === 'oauth_account' && r.id === ACCOUNT_ID))
|
|
59
|
+
return;
|
|
60
|
+
await applyTwinWrite(SERVICE, {
|
|
61
|
+
operation: 'oauth_account.ensure',
|
|
62
|
+
subjectType: 'oauth_account',
|
|
63
|
+
subjectId: ACCOUNT_ID,
|
|
64
|
+
fields: { ...accountClaims(), status: 'active' },
|
|
65
|
+
...(occurredAt ? { occurredAt } : {}),
|
|
66
|
+
actor: { kind: 'system' },
|
|
67
|
+
}, root);
|
|
68
|
+
}
|
|
69
|
+
export async function startXaiTwinDeviceAuthorization(input) {
|
|
70
|
+
const clientId = input.body.get('client_id')?.trim() ?? '';
|
|
71
|
+
const scope = input.body.get('scope')?.trim() || DEFAULT_SCOPE;
|
|
72
|
+
if (!clientId)
|
|
73
|
+
return oauthError(400, 'invalid_request', 'client_id is required');
|
|
74
|
+
if (!new Set(scope.split(/\s+/u)).has('grok-cli:access')) {
|
|
75
|
+
return oauthError(400, 'invalid_scope', 'grok-cli:access is required');
|
|
76
|
+
}
|
|
77
|
+
await ensureAccount(input.root, input.occurredAt);
|
|
78
|
+
const now = at(input.occurredAt);
|
|
79
|
+
const expiresAt = new Date(now.getTime() + DEVICE_TTL_SECONDS * 1_000).toISOString();
|
|
80
|
+
// The ordinal is read INSIDE the lock, so two identical device-authorization requests at the
|
|
81
|
+
// same instant mint different codes and both land — what `uniqueness: randomUUID()` bought
|
|
82
|
+
// before, now without randomness in served content.
|
|
83
|
+
const minted = await applyTwinWriteAtomic(SERVICE, (resources) => {
|
|
84
|
+
const seed = `oauth_device_authorization:${input.occurredAt ?? ''}:${deviceRows(resources).length}:${clientId}`;
|
|
85
|
+
const deviceCode = token('xai_twin_device', seed);
|
|
86
|
+
const userCode = stableHex(`user_code:${seed}`, 8).toUpperCase().replace(/(.{4})/u, '$1-');
|
|
87
|
+
return {
|
|
88
|
+
kind: 'write',
|
|
89
|
+
value: { deviceCode, userCode },
|
|
90
|
+
write: {
|
|
91
|
+
operation: 'oauth_device_authorization.create',
|
|
92
|
+
subjectType: 'oauth_device_authorization',
|
|
93
|
+
subjectId: deviceCode,
|
|
94
|
+
fields: {
|
|
95
|
+
account_id: ACCOUNT_ID,
|
|
96
|
+
client_id: clientId,
|
|
97
|
+
expires_at: expiresAt,
|
|
98
|
+
scope,
|
|
99
|
+
status: 'pending',
|
|
100
|
+
user_code: userCode,
|
|
101
|
+
},
|
|
102
|
+
...(input.occurredAt ? { occurredAt: input.occurredAt } : {}),
|
|
103
|
+
actor: { kind: 'agent' },
|
|
104
|
+
uniqueness: deviceCode,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}, input.root);
|
|
108
|
+
const { deviceCode, userCode } = minted.value;
|
|
109
|
+
const base = input.origin.replace(/\/+$/, '');
|
|
110
|
+
const verificationUri = `${base}/oauth2/device`;
|
|
111
|
+
const verificationUriComplete = `${base}/oauth2/device?user_code=${encodeURIComponent(userCode)}`;
|
|
112
|
+
return {
|
|
113
|
+
status: 200,
|
|
114
|
+
body: {
|
|
115
|
+
device_code: deviceCode,
|
|
116
|
+
user_code: userCode,
|
|
117
|
+
verification_uri: verificationUri,
|
|
118
|
+
verification_uri_complete: verificationUriComplete,
|
|
119
|
+
expires_in: DEVICE_TTL_SECONDS,
|
|
120
|
+
interval: 1,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export async function decideXaiTwinDeviceAuthorization(input) {
|
|
125
|
+
const now = at(input.occurredAt);
|
|
126
|
+
const decision = await applyTwinWriteAtomic(SERVICE, (resources) => {
|
|
127
|
+
const device = deviceRows(resources).find((resource) => resource.user_code === input.userCode);
|
|
128
|
+
if (!device)
|
|
129
|
+
return { kind: 'skip', value: oauthError(404, 'invalid_user_code', 'The device code is unknown.') };
|
|
130
|
+
if (Date.parse(String(device.expires_at)) <= now.getTime()) {
|
|
131
|
+
return { kind: 'skip', value: oauthError(400, 'expired_token', 'The device code expired.') };
|
|
132
|
+
}
|
|
133
|
+
if (device.status !== 'pending') {
|
|
134
|
+
return { kind: 'skip', value: oauthError(409, 'invalid_request', 'The device authorization was already decided.') };
|
|
135
|
+
}
|
|
136
|
+
const status = input.approved ? 'approved' : 'denied';
|
|
137
|
+
return {
|
|
138
|
+
kind: 'write',
|
|
139
|
+
value: { status: 200, body: { approved: input.approved, status, user_code: input.userCode } },
|
|
140
|
+
write: {
|
|
141
|
+
operation: input.approved ? 'oauth_device_authorization.approve' : 'oauth_device_authorization.deny',
|
|
142
|
+
subjectType: 'oauth_device_authorization',
|
|
143
|
+
subjectId: device.id,
|
|
144
|
+
fields: { decided_at: now.toISOString(), status },
|
|
145
|
+
...(input.occurredAt ? { occurredAt: input.occurredAt } : {}),
|
|
146
|
+
actor: { kind: 'human', id: ACCOUNT_ID },
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}, input.root);
|
|
150
|
+
return decision.value;
|
|
151
|
+
}
|
|
152
|
+
export function inspectXaiTwinDeviceAuthorization(userCode, root) {
|
|
153
|
+
const resources = projectResources(SERVICE, root);
|
|
154
|
+
const device = resources.find((resource) => resource.type === 'oauth_device_authorization' && resource.user_code === userCode);
|
|
155
|
+
if (!device)
|
|
156
|
+
return undefined;
|
|
157
|
+
const account = resources.find((resource) => resource.type === 'oauth_account' && resource.id === device.account_id);
|
|
158
|
+
return {
|
|
159
|
+
account: account ? { email: account.email, id: account.id, name: account.name } : undefined,
|
|
160
|
+
expiresAt: device.expires_at,
|
|
161
|
+
scope: device.scope,
|
|
162
|
+
status: device.status,
|
|
163
|
+
userCode: device.user_code,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export async function exchangeXaiTwinToken(input) {
|
|
167
|
+
const grantType = input.body.get('grant_type') ?? '';
|
|
168
|
+
const now = at(input.occurredAt);
|
|
169
|
+
if (grantType === 'urn:ietf:params:oauth:grant-type:device_code') {
|
|
170
|
+
const clientId = input.body.get('client_id')?.trim() ?? '';
|
|
171
|
+
const deviceCode = input.body.get('device_code') ?? '';
|
|
172
|
+
const result = await applyTwinWriteAtomic(SERVICE, (resources) => {
|
|
173
|
+
const device = deviceRows(resources).find((resource) => resource.id === deviceCode);
|
|
174
|
+
// Minted under the lock off the row this exchange consumes plus its token generation, so a
|
|
175
|
+
// re-issue against the same device code at the same instant is still a distinct token.
|
|
176
|
+
const accessToken = token('xai_twin_access', `access_token:${input.occurredAt ?? ''}:${deviceCode}:1`);
|
|
177
|
+
const refreshToken = token('xai_twin_refresh', `refresh_token:${input.occurredAt ?? ''}:${deviceCode}:1`);
|
|
178
|
+
if (!device)
|
|
179
|
+
return { kind: 'skip', value: oauthError(400, 'invalid_grant', 'The device code is invalid.') };
|
|
180
|
+
if (!clientId || clientId !== device.client_id)
|
|
181
|
+
return { kind: 'skip', value: oauthError(400, 'invalid_grant', 'The device code was not issued to this client.') };
|
|
182
|
+
if (Date.parse(String(device.expires_at)) <= now.getTime())
|
|
183
|
+
return { kind: 'skip', value: oauthError(400, 'expired_token', 'The device code expired.') };
|
|
184
|
+
if (device.status === 'pending')
|
|
185
|
+
return { kind: 'skip', value: oauthError(400, 'authorization_pending', 'Authorization is pending.') };
|
|
186
|
+
if (device.status === 'denied')
|
|
187
|
+
return { kind: 'skip', value: oauthError(400, 'access_denied', 'Authorization was denied.') };
|
|
188
|
+
if (device.status !== 'approved')
|
|
189
|
+
return { kind: 'skip', value: oauthError(400, 'invalid_grant', 'The device code was already consumed.') };
|
|
190
|
+
const scope = String(device.scope ?? DEFAULT_SCOPE);
|
|
191
|
+
return {
|
|
192
|
+
kind: 'write',
|
|
193
|
+
value: { status: 200, body: { access_token: accessToken, expires_in: ACCESS_TTL_SECONDS, id_token: idToken(), refresh_token: refreshToken, scope, token_type: 'Bearer' } },
|
|
194
|
+
write: {
|
|
195
|
+
operation: 'oauth_device_authorization.exchange',
|
|
196
|
+
subjectType: 'oauth_device_authorization',
|
|
197
|
+
subjectId: device.id,
|
|
198
|
+
fields: {
|
|
199
|
+
access_expires_at: new Date(now.getTime() + ACCESS_TTL_SECONDS * 1_000).toISOString(),
|
|
200
|
+
access_token: accessToken,
|
|
201
|
+
refresh_token: refreshToken,
|
|
202
|
+
status: 'connected',
|
|
203
|
+
token_generation: 1,
|
|
204
|
+
},
|
|
205
|
+
...(input.occurredAt ? { occurredAt: input.occurredAt } : {}),
|
|
206
|
+
actor: { kind: 'agent' },
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}, input.root);
|
|
210
|
+
return result.value;
|
|
211
|
+
}
|
|
212
|
+
if (grantType === 'refresh_token') {
|
|
213
|
+
const clientId = input.body.get('client_id')?.trim() ?? '';
|
|
214
|
+
const presentedRefresh = input.body.get('refresh_token') ?? '';
|
|
215
|
+
const result = await applyTwinWriteAtomic(SERVICE, (resources) => {
|
|
216
|
+
const device = deviceRows(resources).find((resource) => resource.refresh_token === presentedRefresh && resource.status === 'connected');
|
|
217
|
+
if (!device)
|
|
218
|
+
return { kind: 'skip', value: oauthError(400, 'invalid_grant', 'The refresh token is invalid or revoked.') };
|
|
219
|
+
if (!clientId || clientId !== device.client_id)
|
|
220
|
+
return { kind: 'skip', value: oauthError(400, 'invalid_grant', 'The refresh token was not issued to this client.') };
|
|
221
|
+
const scope = String(device.scope ?? DEFAULT_SCOPE);
|
|
222
|
+
// The NEXT generation is the discriminator: each refresh of the same row mints a new pair.
|
|
223
|
+
const generation = Number(device.token_generation ?? 1) + 1;
|
|
224
|
+
const accessToken = token('xai_twin_access', `access_token:${input.occurredAt ?? ''}:${String(device.id)}:${generation}`);
|
|
225
|
+
const refreshToken = token('xai_twin_refresh', `refresh_token:${input.occurredAt ?? ''}:${String(device.id)}:${generation}`);
|
|
226
|
+
return {
|
|
227
|
+
kind: 'write',
|
|
228
|
+
value: { status: 200, body: { access_token: accessToken, expires_in: ACCESS_TTL_SECONDS, id_token: idToken(), refresh_token: refreshToken, scope, token_type: 'Bearer' } },
|
|
229
|
+
write: {
|
|
230
|
+
operation: 'oauth_token.refresh',
|
|
231
|
+
subjectType: 'oauth_device_authorization',
|
|
232
|
+
subjectId: device.id,
|
|
233
|
+
fields: {
|
|
234
|
+
access_expires_at: new Date(now.getTime() + ACCESS_TTL_SECONDS * 1_000).toISOString(),
|
|
235
|
+
access_token: accessToken,
|
|
236
|
+
refresh_token: refreshToken,
|
|
237
|
+
token_generation: generation,
|
|
238
|
+
},
|
|
239
|
+
...(input.occurredAt ? { occurredAt: input.occurredAt } : {}),
|
|
240
|
+
actor: { kind: 'agent' },
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}, input.root);
|
|
244
|
+
return result.value;
|
|
245
|
+
}
|
|
246
|
+
return oauthError(400, 'unsupported_grant_type', 'The requested OAuth grant is unsupported.');
|
|
247
|
+
}
|
|
248
|
+
export function isCurrentXaiTwinAccessToken(accessToken, root, occurredAt) {
|
|
249
|
+
const now = at(occurredAt).getTime();
|
|
250
|
+
return projectResources(SERVICE, root).some((resource) => resource.type === 'oauth_device_authorization'
|
|
251
|
+
&& resource.status === 'connected'
|
|
252
|
+
&& resource.access_token === accessToken
|
|
253
|
+
&& Date.parse(String(resource.access_expires_at)) > now);
|
|
254
|
+
}
|
|
255
|
+
export async function recordXaiTwinUsage(input) {
|
|
256
|
+
await applyTwinWriteAtomic(SERVICE, (resources) => {
|
|
257
|
+
const prior = resources.find((resource) => resource.type === 'oauth_usage' && resource.id === ACCOUNT_ID);
|
|
258
|
+
return {
|
|
259
|
+
kind: 'write',
|
|
260
|
+
value: undefined,
|
|
261
|
+
write: {
|
|
262
|
+
operation: 'oauth_usage.spend',
|
|
263
|
+
subjectType: 'oauth_usage',
|
|
264
|
+
subjectId: ACCOUNT_ID,
|
|
265
|
+
fields: {
|
|
266
|
+
account_id: ACCOUNT_ID,
|
|
267
|
+
completion_tokens: Number(prior?.completion_tokens ?? 0) + input.completionTokens,
|
|
268
|
+
prompt_tokens: Number(prior?.prompt_tokens ?? 0) + input.promptTokens,
|
|
269
|
+
requests: Number(prior?.requests ?? 0) + 1,
|
|
270
|
+
total_tokens: Number(prior?.total_tokens ?? 0) + input.promptTokens + input.completionTokens,
|
|
271
|
+
},
|
|
272
|
+
...(input.occurredAt ? { occurredAt: input.occurredAt } : {}),
|
|
273
|
+
actor: { kind: 'agent' },
|
|
274
|
+
correlationId: input.requestId,
|
|
275
|
+
uniqueness: input.requestId,
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}, input.root);
|
|
279
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type PackScenarioAdapter, type ScenarioDocument, ScenarioEngine } from '@volter/world-core';
|
|
2
|
+
import type { ChatMessageParam, ChatToolCall } from './xai-types.js';
|
|
3
|
+
export type XaiScenarioRequest = {
|
|
4
|
+
model: string;
|
|
5
|
+
messages: ChatMessageParam[];
|
|
6
|
+
tools?: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type XaiScenarioEngine = ScenarioEngine<XaiScenarioRequest>;
|
|
9
|
+
/** A scripted tool call — the full arguments object is emitted verbatim (JSON-encoded on the
|
|
10
|
+
* wire, and streamed as delta.tool_calls on the streaming path). */
|
|
11
|
+
export type ScenarioToolCall = {
|
|
12
|
+
name: string;
|
|
13
|
+
arguments: Record<string, unknown>;
|
|
14
|
+
id?: string;
|
|
15
|
+
};
|
|
16
|
+
/** What the assistant says when a handler fires: text and/or one-or-more tool calls. */
|
|
17
|
+
export type XaiScenarioRespond = {
|
|
18
|
+
text?: string;
|
|
19
|
+
toolCalls?: ScenarioToolCall | ScenarioToolCall[];
|
|
20
|
+
/** Defaults to 'tool_calls' when any tool call is present, else 'stop'. */
|
|
21
|
+
finishReason?: 'stop' | 'length' | 'tool_calls' | 'content_filter';
|
|
22
|
+
};
|
|
23
|
+
/** What a fired handler yields — assistant text/tool_calls + finish_reason for the envelope. */
|
|
24
|
+
export type ScriptedResult = {
|
|
25
|
+
text: string | null;
|
|
26
|
+
toolCalls: ChatToolCall[];
|
|
27
|
+
finishReason: 'stop' | 'length' | 'tool_calls' | 'content_filter';
|
|
28
|
+
};
|
|
29
|
+
export declare const xaiScenarioAdapter: PackScenarioAdapter<XaiScenarioRequest>;
|
|
30
|
+
export declare function loadXaiScenarioDocument(path: string): ScenarioDocument;
|
|
31
|
+
export declare function createXaiScenarioEngine(document?: ScenarioDocument): XaiScenarioEngine;
|
|
32
|
+
/** Realize a fired handler's respond into the envelope builder's scripted-turn shape. */
|
|
33
|
+
export declare function realizeXaiRespond(respond: XaiScenarioRespond): ScriptedResult;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// The xai pack's HALF of the scenario system on the kernel's ONE engine (@volter/world-core
|
|
2
|
+
// scenario.ts): the chat-completions vocabulary + the scripted-turn respond shape the
|
|
3
|
+
// envelope builder consumes. The handler FILE (handlers/xai.json in a world dir) is the only
|
|
4
|
+
// write surface. Deferred chat calls answer { request_id } before this machinery runs and are
|
|
5
|
+
// never counted.
|
|
6
|
+
import { getActiveWorldStore, parseScenarioDocument, ScenarioError, ScenarioEngine } from '@volter/world-core';
|
|
7
|
+
import { contentToText, lastUserText } from "./xai-stub.js";
|
|
8
|
+
const RESPOND_KEYS = new Set(['text', 'toolCalls', 'finishReason']);
|
|
9
|
+
const FINISH_REASONS = new Set(['stop', 'length', 'tool_calls', 'content_filter']);
|
|
10
|
+
const nonEmptyString = (cond) => typeof cond === 'string' && cond.length > 0;
|
|
11
|
+
/** The function NAMES a tool-role last message answers: resolve its tool_call_id against the
|
|
12
|
+
* tool_calls of earlier assistant turns. */
|
|
13
|
+
function lastToolResultNames(messages) {
|
|
14
|
+
const names = new Set();
|
|
15
|
+
const last = messages[messages.length - 1];
|
|
16
|
+
if (!last || last.role !== 'tool')
|
|
17
|
+
return names;
|
|
18
|
+
const idToName = new Map();
|
|
19
|
+
for (const m of messages) {
|
|
20
|
+
if (m.role !== 'assistant')
|
|
21
|
+
continue;
|
|
22
|
+
for (const tc of m.tool_calls ?? [])
|
|
23
|
+
idToName.set(tc.id, tc.function.name);
|
|
24
|
+
}
|
|
25
|
+
if (typeof last.tool_call_id === 'string') {
|
|
26
|
+
const name = idToName.get(last.tool_call_id);
|
|
27
|
+
if (name)
|
|
28
|
+
names.add(name);
|
|
29
|
+
}
|
|
30
|
+
return names;
|
|
31
|
+
}
|
|
32
|
+
function toolNames(tools) {
|
|
33
|
+
if (!Array.isArray(tools))
|
|
34
|
+
return [];
|
|
35
|
+
return tools.map((t) => typeof t?.function?.name === 'string' ? t.function.name : typeof t?.name === 'string' ? t.name : null).filter((n) => n !== null);
|
|
36
|
+
}
|
|
37
|
+
export const xaiScenarioAdapter = {
|
|
38
|
+
vendor: 'xai',
|
|
39
|
+
features: (req) => ({
|
|
40
|
+
model: req.model,
|
|
41
|
+
lastUserText: lastUserText(req.messages).slice(0, 300),
|
|
42
|
+
tools: toolNames(req.tools),
|
|
43
|
+
lastMessageIsToolResult: req.messages[req.messages.length - 1]?.role === 'tool',
|
|
44
|
+
toolResultFor: [...lastToolResultNames(req.messages)],
|
|
45
|
+
}),
|
|
46
|
+
matchers: {
|
|
47
|
+
modelEquals: (req, cond) => nonEmptyString(cond) && req.model === cond,
|
|
48
|
+
userTextIncludes: (req, cond) => nonEmptyString(cond) && lastUserText(req.messages).toLowerCase().includes(cond.toLowerCase()),
|
|
49
|
+
anyTextIncludes: (req, cond) => nonEmptyString(cond) && req.messages.map((m) => contentToText(m.content)).join('\n').toLowerCase().includes(cond.toLowerCase()),
|
|
50
|
+
lastMessageIsToolResult: (req, cond) => typeof cond === 'boolean' && (req.messages[req.messages.length - 1]?.role === 'tool') === cond,
|
|
51
|
+
toolResultFor: (req, cond) => nonEmptyString(cond) && lastToolResultNames(req.messages).has(cond),
|
|
52
|
+
hasTool: (req, cond) => nonEmptyString(cond) && toolNames(req.tools).includes(cond),
|
|
53
|
+
},
|
|
54
|
+
text: (req) => req.messages.map((m) => contentToText(m.content)).join('\n'),
|
|
55
|
+
validateOn: (on) => {
|
|
56
|
+
for (const k of ['modelEquals', 'userTextIncludes', 'anyTextIncludes', 'toolResultFor', 'hasTool'])
|
|
57
|
+
if (on[k] !== undefined && (typeof on[k] !== 'string' || !on[k]))
|
|
58
|
+
return `on.${k} is a non-empty string`;
|
|
59
|
+
for (const k of ['lastMessageIsToolResult'])
|
|
60
|
+
if (on[k] !== undefined && typeof on[k] !== 'boolean')
|
|
61
|
+
return `on.${k} is a boolean`;
|
|
62
|
+
return null;
|
|
63
|
+
},
|
|
64
|
+
validateRespond: (respond) => {
|
|
65
|
+
if (typeof respond !== 'object' || respond === null || Array.isArray(respond))
|
|
66
|
+
return 'respond is an object { text?, toolCalls?, finishReason? }';
|
|
67
|
+
const r = respond;
|
|
68
|
+
for (const k of Object.keys(r))
|
|
69
|
+
if (!RESPOND_KEYS.has(k))
|
|
70
|
+
return `respond: unknown key "${k}" (valid: ${[...RESPOND_KEYS].join(', ')})`;
|
|
71
|
+
if (r.text !== undefined && typeof r.text !== 'string')
|
|
72
|
+
return 'respond.text is a string';
|
|
73
|
+
if (r.finishReason !== undefined && (typeof r.finishReason !== 'string' || !FINISH_REASONS.has(r.finishReason)))
|
|
74
|
+
return `respond.finishReason is one of ${[...FINISH_REASONS].join(', ')}`;
|
|
75
|
+
if (r.toolCalls !== undefined) {
|
|
76
|
+
for (const tc of Array.isArray(r.toolCalls) ? r.toolCalls : [r.toolCalls]) {
|
|
77
|
+
const t = tc;
|
|
78
|
+
if (!t || typeof t !== 'object' || Array.isArray(t))
|
|
79
|
+
return 'respond.toolCalls entries are objects';
|
|
80
|
+
if (typeof t.name !== 'string' || !t.name)
|
|
81
|
+
return 'respond.toolCalls[].name is a non-empty string';
|
|
82
|
+
if (!t.arguments || typeof t.arguments !== 'object' || Array.isArray(t.arguments))
|
|
83
|
+
return 'respond.toolCalls[].arguments is an object';
|
|
84
|
+
if (t.id !== undefined && typeof t.id !== 'string')
|
|
85
|
+
return 'respond.toolCalls[].id is a string';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (r.text === undefined && r.toolCalls === undefined)
|
|
89
|
+
return 'respond needs text or toolCalls';
|
|
90
|
+
return null;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
export function loadXaiScenarioDocument(path) {
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
// Read through the ACTIVE WorldStore, never the filesystem directly (runtime contract
|
|
97
|
+
// R12b): the handlers document is WORLD STATE, so a MemoryWorldStore / DO-backed world
|
|
98
|
+
// serves ITS OWN scenario instead of whatever happens to sit on the host disk — and the
|
|
99
|
+
// serve path stays workerd-clean. A missing document keeps the historical ENOENT wording,
|
|
100
|
+
// so the loud load-time failure reads byte-identically to the read it replaces.
|
|
101
|
+
const raw = getActiveWorldStore().read(path);
|
|
102
|
+
if (raw === null)
|
|
103
|
+
throw new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
104
|
+
parsed = JSON.parse(raw);
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
throw new ScenarioError(`xai scenario: cannot read/parse ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
108
|
+
}
|
|
109
|
+
return parseScenarioDocument(parsed, xaiScenarioAdapter);
|
|
110
|
+
}
|
|
111
|
+
export function createXaiScenarioEngine(document) {
|
|
112
|
+
return new ScenarioEngine(xaiScenarioAdapter, document);
|
|
113
|
+
}
|
|
114
|
+
// Scripted tool_call ids are unique per run and deterministic for a given request sequence.
|
|
115
|
+
/** A scripted tool call's id, DERIVED from the call itself — never a process counter, so two identical
|
|
116
|
+
* worlds answer the same request with the same ids (protocol 2, R9). */
|
|
117
|
+
function scriptedCallId(name, args, seq) {
|
|
118
|
+
let h = 0x811c9dc5;
|
|
119
|
+
const seed = `${name}|${JSON.stringify(args)}|${seq}`;
|
|
120
|
+
for (let i = 0; i < seed.length; i += 1) {
|
|
121
|
+
h ^= seed.charCodeAt(i);
|
|
122
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
123
|
+
}
|
|
124
|
+
return `call_scripted_${h.toString(16).padStart(8, '0')}`;
|
|
125
|
+
}
|
|
126
|
+
/** Realize a fired handler's respond into the envelope builder's scripted-turn shape. */
|
|
127
|
+
export function realizeXaiRespond(respond) {
|
|
128
|
+
const toolCalls = [];
|
|
129
|
+
let seq = 0; // this response's own ordinal, not the process's
|
|
130
|
+
for (const tc of respond.toolCalls ? (Array.isArray(respond.toolCalls) ? respond.toolCalls : [respond.toolCalls]) : []) {
|
|
131
|
+
seq += 1;
|
|
132
|
+
toolCalls.push({ id: tc.id ?? scriptedCallId(tc.name, tc.arguments, seq), type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.arguments) } });
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
text: respond.text ?? (toolCalls.length ? null : ''),
|
|
136
|
+
toolCalls,
|
|
137
|
+
finishReason: respond.finishReason ?? (toolCalls.length ? 'tool_calls' : 'stop'),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Options every xAI-twin HTTP surface needs, independent of who owns the socket. */
|
|
2
|
+
export interface XaiTwinFetchOptions {
|
|
3
|
+
root?: string;
|
|
4
|
+
readOnly?: boolean;
|
|
5
|
+
scenarioPath?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The pack's whole HTTP surface as a plain `fetch` — Request in, Response out, no listener.
|
|
9
|
+
*
|
|
10
|
+
* This is the composable form (runtime contract R12b): a Worker / Durable Object entry has NO
|
|
11
|
+
* loopback ports, so it must mount a pack's handler IN-PROCESS. `createXaiTwinServer` is
|
|
12
|
+
* nothing but `Bun.serve` wrapped around this closure, so the standalone (R1) and hosted
|
|
13
|
+
* surfaces are the SAME code — there is no second HTTP adaptation to drift. openai is the
|
|
14
|
+
* reference for this shape; xai is its generative sibling.
|
|
15
|
+
*
|
|
16
|
+
* WHAT IT SERVES IS UNCHANGED (R9): xai is a GENERATIVE pack, so chat completions answer a
|
|
17
|
+
* labeled deterministic stub or a scripted scenario — never a model. The only wall-clock-shaped
|
|
18
|
+
* call on this path is `worldNow()`, the world's frozen instant, and GET /twin is built from
|
|
19
|
+
* constants plus the scenario engine's own counters, so replaying it on identical state is
|
|
20
|
+
* byte-identical.
|
|
21
|
+
*
|
|
22
|
+
* NOTHING ON THIS PATH TOUCHES A FILESYSTEM. The scenario document is read through the ACTIVE
|
|
23
|
+
* WORLD STORE (xai-scenario.ts), once, when the factory is called; the device-authorization
|
|
24
|
+
* stylesheet, script and favicon are all served from committed constants
|
|
25
|
+
* (xai-device-auth-ui.ts), so the OAuth protocol UI is workerd-servable too.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createXaiTwinFetch(options?: XaiTwinFetchOptions): (request: Request) => Promise<Response>;
|
|
28
|
+
export declare function createXaiTwinServer(options: {
|
|
29
|
+
root?: string;
|
|
30
|
+
port?: number;
|
|
31
|
+
readOnly?: boolean;
|
|
32
|
+
scenarioPath?: string;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
port: number;
|
|
35
|
+
stop: () => void;
|
|
36
|
+
}>;
|