@volter/twin-xai 0.1.0 → 0.1.2

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.
@@ -1,6 +1,7 @@
1
1
  // Server half of xAI's Grok Build device-authorization UI. This is protocol UI, not an
2
2
  // invented dashboard: it is served on the OAuth paths and coupled directly to the pending
3
3
  // device authorization projected from the Twin kernel.
4
+ import { worldNow } from '@volter/world-core';
4
5
  import { createElement } from 'react';
5
6
  import { renderToStaticMarkup } from 'react-dom/server';
6
7
  import { DeviceConsentPage, DeviceDonePage, DeviceEntryPage, XAI_DEVICE_PERMISSIONS, } from "../client/xai-device-auth.js";
@@ -33,7 +34,7 @@ export function xaiDeviceAuthView(userCode, root, error, nowAt) {
33
34
  const normalized = userCode.trim().toUpperCase().replace(/[^A-Z0-9-]/gu, '');
34
35
  const state = inspectXaiTwinDeviceAuthorization(normalized, root);
35
36
  let status = typeof state?.status === 'string' ? state.status : 'unknown';
36
- if (state?.expiresAt && Date.parse(String(state.expiresAt)) <= Date.parse(nowAt ?? new Date().toISOString()) && status === 'pending')
37
+ if (state?.expiresAt && Date.parse(String(state.expiresAt)) <= Date.parse(nowAt ?? worldNow()) && status === 'pending')
37
38
  status = 'expired';
38
39
  const supported = new Set(['pending', 'approved', 'denied', 'connected', 'expired', 'unknown']);
39
40
  const scopes = new Set(String(state?.scope ?? '').split(/\s+/u).filter(Boolean));
@@ -1,11 +1,11 @@
1
- import { applyTwinWrite, applyTwinWriteAtomic, projectResources } from '@volter/world-core';
1
+ import { applyTwinWrite, applyTwinWriteAtomic, projectResources, worldNow } from '@volter/world-core';
2
2
  const SERVICE = 'xai';
3
3
  const ACCOUNT_ID = 'acct_twin_grok';
4
4
  const DEFAULT_SCOPE = 'openid profile email offline_access grok-cli:access api:access conversations:read conversations:write workspaces:read workspaces:write';
5
5
  const ACCESS_TTL_SECONDS = 3_600;
6
6
  const DEVICE_TTL_SECONDS = 900;
7
7
  function at(occurredAt) {
8
- return new Date(occurredAt ?? new Date().toISOString());
8
+ return new Date(occurredAt ?? worldNow());
9
9
  }
10
10
  /** FNV-1a over the seed, widened by re-hashing with a round counter until `n` hex chars exist. */
11
11
  function stableHex(seed, n) {
@@ -10,7 +10,7 @@
10
10
  // Scenario scripting (xai-scenario.ts): a JSON scenario file — via the scenarioPath option,
11
11
  // the TWIN_XAI_SCENARIO env var, or `world-xai serve --scenario <path>` — scripts the exact
12
12
  // assistant turns for POST /v1/chat/completions. One session per server (nthCall/once state).
13
- import { serveHttp } from '@volter/world-core';
13
+ import { isReadOnlyRequest, serveHttp, withRequestScopes } from '@volter/world-core';
14
14
  import { twinManifest, twinPublicBase, worldNow } from '@volter/world-core';
15
15
  import { createXaiScenarioEngine, loadXaiScenarioDocument } from "./xai-scenario.js";
16
16
  import { handleXaiTwinRequest } from "./xai-twin.js";
@@ -68,10 +68,14 @@ function html(body, status = 200) {
68
68
  * (xai-device-auth-ui.ts), so the OAuth protocol UI is workerd-servable too.
69
69
  */
70
70
  export function createXaiTwinFetch(options = {}) {
71
- const readOnly = options.readOnly ?? false;
72
71
  const scenarioPath = options.scenarioPath ?? process.env.TWIN_XAI_SCENARIO;
73
72
  const scenarioEngine = scenarioPath ? createXaiScenarioEngine(loadXaiScenarioDocument(scenarioPath)) : undefined;
74
- return async function xaiTwinFetch(request) {
73
+ // a read-only request (x-volter-read-only, a World's read token) is a request to a read-only twin: no
74
+ // device grant is started or decided, no token minted, no completion run or usage recorded, and a
75
+ // deferred completion is only PEEKED (xai-twin.ts retrieveDeferredCompletion: the app's one retrieval
76
+ // is never spent by a viewer); the kernel refuses any write that slips past as xAI's own error shape
77
+ return withRequestScopes(async function xaiTwinFetch(request) {
78
+ const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
75
79
  const url = new URL(request.url);
76
80
  const path = url.pathname + (url.search || '');
77
81
  const cleanPath = url.pathname.replace(/\/+$/, '');
@@ -220,7 +224,7 @@ export function createXaiTwinFetch(options = {}) {
220
224
  ...(scenarioEngine ? { scenarioEngine } : {}),
221
225
  });
222
226
  return new Response(JSON.stringify(out), { status, headers: { 'content-type': 'application/json', 'x-request-id': 'req_twin', ...(outHeaders ?? {}) } });
223
- };
227
+ }, { refuse: () => new Response(JSON.stringify({ code: 'method_not_allowed', error: 'this request is read-only' }), { status: 405, headers: { 'content-type': 'application/json' } }) });
224
228
  }
225
229
  export async function createXaiTwinServer(options) {
226
230
  const server = await serveHttp({
@@ -543,6 +543,11 @@ async function retrieveDeferredCompletion(requestId, req) {
543
543
  // consume is a WRITE, so it is skipped (the lifecycle simply cannot advance in a read-only
544
544
  // mirror, and repeated read-only polls keep serving the still-unconsumed result). §9 found
545
545
  // this consume unguarded — a read-only GET was consuming the row.
546
+ // A World's READ token (x-volter-read-only) arrives here as readOnly too, and PEEKS rather than
547
+ // being refused: xAI's own shapes already say everything a viewer may see without spending
548
+ // anything — 202 with an empty body while pending, the completion once ready — so a peek answers
549
+ // exactly the vendor's shape for the row's current state and advances nothing (neither the first
550
+ // poll nor the one-time retrieval). The app's own poll then still gets its one retrieval.
546
551
  if (!req.readOnly) {
547
552
  await applyTwinWrite(SERVICE, {
548
553
  operation: 'deferred_completion.consume',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-xai",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
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
5
  "keywords": [
6
6
  "twin",
@@ -56,10 +56,10 @@
56
56
  "react-dom": "^19.2.7"
57
57
  },
58
58
  "peerDependencies": {
59
- "@volter/world-core": "2.0.0"
59
+ "@volter/world-core": "2.0.2"
60
60
  },
61
61
  "devDependencies": {
62
- "@volter/world-core": "2.0.0",
62
+ "@volter/world-core": "2.0.2",
63
63
  "@volter/world-tooling": "0.1.0",
64
64
  "@ai-sdk/xai": "^3.0.111",
65
65
  "ai": "^6.0.0",
@@ -1,6 +1,7 @@
1
1
  // Server half of xAI's Grok Build device-authorization UI. This is protocol UI, not an
2
2
  // invented dashboard: it is served on the OAuth paths and coupled directly to the pending
3
3
  // device authorization projected from the Twin kernel.
4
+ import { worldNow } from '@volter/world-core';
4
5
  import { createElement } from 'react';
5
6
  import { renderToStaticMarkup } from 'react-dom/server';
6
7
  import {
@@ -46,7 +47,7 @@ export function xaiDeviceAuthView(userCode: string, root?: string, error?: strin
46
47
  const normalized = userCode.trim().toUpperCase().replace(/[^A-Z0-9-]/gu, '');
47
48
  const state = inspectXaiTwinDeviceAuthorization(normalized, root);
48
49
  let status = typeof state?.status === 'string' ? state.status : 'unknown';
49
- if (state?.expiresAt && Date.parse(String(state.expiresAt)) <= Date.parse(nowAt ?? new Date().toISOString()) && status === 'pending') status = 'expired';
50
+ if (state?.expiresAt && Date.parse(String(state.expiresAt)) <= Date.parse(nowAt ?? worldNow()) && status === 'pending') status = 'expired';
50
51
  const supported = new Set<XaiDeviceAuthView['status']>(['pending', 'approved', 'denied', 'connected', 'expired', 'unknown']);
51
52
  const scopes = new Set(String(state?.scope ?? '').split(/\s+/u).filter(Boolean));
52
53
  const permissions = XAI_DEVICE_PERMISSIONS.filter((_, index) => [
package/src/xai-oauth.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { applyTwinWrite, applyTwinWriteAtomic, projectResources, type TwinResource } from '@volter/world-core';
1
+ import { applyTwinWrite, applyTwinWriteAtomic, projectResources, type TwinResource, worldNow } from '@volter/world-core';
2
2
 
3
3
  const SERVICE = 'xai';
4
4
  const ACCOUNT_ID = 'acct_twin_grok';
@@ -9,7 +9,7 @@ const DEVICE_TTL_SECONDS = 900;
9
9
  export type XaiOauthResult = { status: number; body: Record<string, unknown> };
10
10
 
11
11
  function at(occurredAt?: string): Date {
12
- return new Date(occurredAt ?? new Date().toISOString());
12
+ return new Date(occurredAt ?? worldNow());
13
13
  }
14
14
 
15
15
  /** FNV-1a over the seed, widened by re-hashing with a round counter until `n` hex chars exist. */
package/src/xai-server.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  // Scenario scripting (xai-scenario.ts): a JSON scenario file — via the scenarioPath option,
11
11
  // the TWIN_XAI_SCENARIO env var, or `world-xai serve --scenario <path>` — scripts the exact
12
12
  // assistant turns for POST /v1/chat/completions. One session per server (nthCall/once state).
13
- import { serveHttp } from '@volter/world-core';
13
+ import { isReadOnlyRequest, serveHttp, withRequestScopes } from '@volter/world-core';
14
14
  import { twinManifest, twinPublicBase, worldNow } from '@volter/world-core';
15
15
  import { createXaiScenarioEngine, loadXaiScenarioDocument, type XaiScenarioEngine } from './xai-scenario.ts';
16
16
  import { handleXaiTwinRequest } from './xai-twin.ts';
@@ -89,10 +89,14 @@ export interface XaiTwinFetchOptions {
89
89
  * (xai-device-auth-ui.ts), so the OAuth protocol UI is workerd-servable too.
90
90
  */
91
91
  export function createXaiTwinFetch(options: XaiTwinFetchOptions = {}): (request: Request) => Promise<Response> {
92
- const readOnly = options.readOnly ?? false;
93
92
  const scenarioPath = options.scenarioPath ?? process.env.TWIN_XAI_SCENARIO;
94
93
  const scenarioEngine: XaiScenarioEngine | undefined = scenarioPath ? createXaiScenarioEngine(loadXaiScenarioDocument(scenarioPath)) : undefined;
95
- return async function xaiTwinFetch(request: Request): Promise<Response> {
94
+ // a read-only request (x-volter-read-only, a World's read token) is a request to a read-only twin: no
95
+ // device grant is started or decided, no token minted, no completion run or usage recorded, and a
96
+ // deferred completion is only PEEKED (xai-twin.ts retrieveDeferredCompletion: the app's one retrieval
97
+ // is never spent by a viewer); the kernel refuses any write that slips past as xAI's own error shape
98
+ return withRequestScopes(async function xaiTwinFetch(request: Request): Promise<Response> {
99
+ const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
96
100
  const url = new URL(request.url);
97
101
  const path = url.pathname + (url.search || '');
98
102
  const cleanPath = url.pathname.replace(/\/+$/, '');
@@ -245,7 +249,7 @@ export function createXaiTwinFetch(options: XaiTwinFetchOptions = {}): (request:
245
249
  ...(scenarioEngine ? { scenarioEngine } : {}),
246
250
  });
247
251
  return new Response(JSON.stringify(out), { status, headers: { 'content-type': 'application/json', 'x-request-id': 'req_twin', ...(outHeaders ?? {}) } });
248
- };
252
+ }, { refuse: () => new Response(JSON.stringify({ code: 'method_not_allowed', error: 'this request is read-only' }), { status: 405, headers: { 'content-type': 'application/json' } }) });
249
253
  }
250
254
 
251
255
  export async function createXaiTwinServer(options: { root?: string; port?: number; readOnly?: boolean; scenarioPath?: string }): Promise<{ port: number; stop: () => void }> {
package/src/xai-twin.ts CHANGED
@@ -629,6 +629,11 @@ async function retrieveDeferredCompletion(requestId: string, req: XaiRequest): P
629
629
  // consume is a WRITE, so it is skipped (the lifecycle simply cannot advance in a read-only
630
630
  // mirror, and repeated read-only polls keep serving the still-unconsumed result). §9 found
631
631
  // this consume unguarded — a read-only GET was consuming the row.
632
+ // A World's READ token (x-volter-read-only) arrives here as readOnly too, and PEEKS rather than
633
+ // being refused: xAI's own shapes already say everything a viewer may see without spending
634
+ // anything — 202 with an empty body while pending, the completion once ready — so a peek answers
635
+ // exactly the vendor's shape for the row's current state and advances nothing (neither the first
636
+ // poll nor the one-time retrieval). The app's own poll then still gets its one retrieval.
632
637
  if (!req.readOnly) {
633
638
  await applyTwinWrite(SERVICE, {
634
639
  operation: 'deferred_completion.consume',