@visual-z/xeno 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 (180) hide show
  1. package/README.md +498 -0
  2. package/docs/install.md +130 -0
  3. package/package.json +43 -0
  4. package/src/apps/approve.ts +49 -0
  5. package/src/apps/cli.ts +37 -0
  6. package/src/apps/commands.ts +226 -0
  7. package/src/apps/completions.ts +28 -0
  8. package/src/apps/input.ts +297 -0
  9. package/src/apps/render.ts +207 -0
  10. package/src/apps/shell.ts +2353 -0
  11. package/src/apps/startup.ts +1302 -0
  12. package/src/core/benchmark.ts +195 -0
  13. package/src/core/boundary.ts +174 -0
  14. package/src/core/churn.ts +59 -0
  15. package/src/core/completion-review.ts +66 -0
  16. package/src/core/context-policy.ts +60 -0
  17. package/src/core/context-window.ts +54 -0
  18. package/src/core/continuation.ts +114 -0
  19. package/src/core/cycle.ts +17 -0
  20. package/src/core/data-query.ts +201 -0
  21. package/src/core/deadline.ts +39 -0
  22. package/src/core/derive.ts +489 -0
  23. package/src/core/edit-plan.ts +200 -0
  24. package/src/core/environment.ts +29 -0
  25. package/src/core/event-validation.ts +99 -0
  26. package/src/core/events.ts +486 -0
  27. package/src/core/evidence-links.ts +71 -0
  28. package/src/core/evidence.ts +89 -0
  29. package/src/core/execution.ts +12 -0
  30. package/src/core/exhaustive.ts +18 -0
  31. package/src/core/experiment.ts +40 -0
  32. package/src/core/export.ts +169 -0
  33. package/src/core/file-references.ts +35 -0
  34. package/src/core/frontmatter.ts +213 -0
  35. package/src/core/handoff.ts +397 -0
  36. package/src/core/harness-comparison.ts +90 -0
  37. package/src/core/history-query.ts +103 -0
  38. package/src/core/history.ts +100 -0
  39. package/src/core/json-event.ts +135 -0
  40. package/src/core/json-pointer.ts +46 -0
  41. package/src/core/keymap.ts +241 -0
  42. package/src/core/observation-view.ts +87 -0
  43. package/src/core/outcome.ts +45 -0
  44. package/src/core/overflow.ts +59 -0
  45. package/src/core/permissions.ts +182 -0
  46. package/src/core/policy-evaluation.ts +55 -0
  47. package/src/core/progress.ts +101 -0
  48. package/src/core/project.ts +372 -0
  49. package/src/core/prompts.ts +83 -0
  50. package/src/core/queue.ts +60 -0
  51. package/src/core/read-bounds.ts +15 -0
  52. package/src/core/recall-analysis.ts +90 -0
  53. package/src/core/recall-trial.ts +153 -0
  54. package/src/core/response-recovery.ts +46 -0
  55. package/src/core/scheduling.ts +8 -0
  56. package/src/core/settings.ts +174 -0
  57. package/src/core/skills.ts +121 -0
  58. package/src/core/stall.ts +48 -0
  59. package/src/core/text-normalize.ts +81 -0
  60. package/src/core/theme.ts +179 -0
  61. package/src/core/tokens.ts +149 -0
  62. package/src/core/tool-contract.ts +102 -0
  63. package/src/core/tool-input.ts +38 -0
  64. package/src/core/transcript.ts +60 -0
  65. package/src/core/window-trial.ts +65 -0
  66. package/src/core/work-state.ts +103 -0
  67. package/src/core/yaml.ts +282 -0
  68. package/src/index.ts +10 -0
  69. package/src/runtime/auth.ts +255 -0
  70. package/src/runtime/benchmark.ts +187 -0
  71. package/src/runtime/catalog.ts +365 -0
  72. package/src/runtime/evidence-notes.ts +33 -0
  73. package/src/runtime/exec-sessions.ts +214 -0
  74. package/src/runtime/execution-control.ts +39 -0
  75. package/src/runtime/harness-comparison.ts +356 -0
  76. package/src/runtime/history-tools.ts +108 -0
  77. package/src/runtime/loop.ts +1983 -0
  78. package/src/runtime/model.ts +614 -0
  79. package/src/runtime/models-fetch.ts +155 -0
  80. package/src/runtime/oauth-device.ts +86 -0
  81. package/src/runtime/oauth.ts +759 -0
  82. package/src/runtime/openai-responses.ts +286 -0
  83. package/src/runtime/openai.ts +675 -0
  84. package/src/runtime/provider.ts +498 -0
  85. package/src/runtime/recall-trial.ts +226 -0
  86. package/src/runtime/resume.ts +66 -0
  87. package/src/runtime/retry.ts +297 -0
  88. package/src/runtime/tools.ts +1193 -0
  89. package/src/runtime/usage.ts +225 -0
  90. package/src/runtime/window-trial.ts +140 -0
  91. package/src/runtime/working-notes.ts +30 -0
  92. package/src/store/atomic.ts +31 -0
  93. package/src/store/auth-store.ts +50 -0
  94. package/src/store/blobs.ts +142 -0
  95. package/src/store/command.ts +75 -0
  96. package/src/store/config.ts +69 -0
  97. package/src/store/context-files.ts +356 -0
  98. package/src/store/file-index.ts +81 -0
  99. package/src/store/git-branch.ts +35 -0
  100. package/src/store/history-search.ts +19 -0
  101. package/src/store/log.ts +642 -0
  102. package/src/store/models.ts +41 -0
  103. package/src/store/observations.ts +181 -0
  104. package/src/store/query-source.ts +44 -0
  105. package/src/store/recovery.ts +39 -0
  106. package/src/store/replay.ts +63 -0
  107. package/src/store/resources.ts +361 -0
  108. package/src/store/restore.ts +302 -0
  109. package/src/store/search.ts +644 -0
  110. package/src/store/serial.ts +13 -0
  111. package/src/store/sessions.ts +463 -0
  112. package/src/store/trust.ts +69 -0
  113. package/src/store/workspace.ts +256 -0
  114. package/src/store/worktree.ts +233 -0
  115. package/src/tui/ansi.ts +67 -0
  116. package/src/tui/autocomplete.ts +29 -0
  117. package/src/tui/complete.ts +87 -0
  118. package/src/tui/diff.ts +160 -0
  119. package/src/tui/editor.ts +893 -0
  120. package/src/tui/exhaustive.ts +14 -0
  121. package/src/tui/fuzzy.ts +75 -0
  122. package/src/tui/host.ts +223 -0
  123. package/src/tui/input.ts +69 -0
  124. package/src/tui/keys.ts +378 -0
  125. package/src/tui/markdown.ts +17 -0
  126. package/src/tui/render.ts +153 -0
  127. package/src/tui/select.ts +240 -0
  128. package/src/tui/settings.ts +40 -0
  129. package/src/tui/statusline.ts +162 -0
  130. package/src/tui/style.ts +205 -0
  131. package/src/tui/terminal.ts +30 -0
  132. package/src/tui/text.ts +52 -0
  133. package/src/tui/toolview.ts +386 -0
  134. package/src/tui/transcript.ts +71 -0
  135. package/src/tui/tree.ts +405 -0
  136. package/src/tui/width.ts +567 -0
  137. package/src/vendor/pi-tui/LICENSE.upstream +21 -0
  138. package/src/vendor/pi-tui/VENDOR.md +23 -0
  139. package/src/vendor/pi-tui/alt-screen-search.ts +327 -0
  140. package/src/vendor/pi-tui/autocomplete.ts +835 -0
  141. package/src/vendor/pi-tui/components/alt-screen-flash.ts +51 -0
  142. package/src/vendor/pi-tui/components/box.ts +167 -0
  143. package/src/vendor/pi-tui/components/cancellable-loader.ts +40 -0
  144. package/src/vendor/pi-tui/components/editor.ts +2470 -0
  145. package/src/vendor/pi-tui/components/h-stack.ts +44 -0
  146. package/src/vendor/pi-tui/components/image.ts +127 -0
  147. package/src/vendor/pi-tui/components/input.ts +494 -0
  148. package/src/vendor/pi-tui/components/loader.ts +101 -0
  149. package/src/vendor/pi-tui/components/markdown.ts +1015 -0
  150. package/src/vendor/pi-tui/components/mouse-region.ts +33 -0
  151. package/src/vendor/pi-tui/components/scroll-view.ts +224 -0
  152. package/src/vendor/pi-tui/components/select-list.ts +273 -0
  153. package/src/vendor/pi-tui/components/settings-list.ts +328 -0
  154. package/src/vendor/pi-tui/components/spacer.ts +28 -0
  155. package/src/vendor/pi-tui/components/stack.ts +154 -0
  156. package/src/vendor/pi-tui/components/text.ts +107 -0
  157. package/src/vendor/pi-tui/components/truncated-text.ts +65 -0
  158. package/src/vendor/pi-tui/components/v-stack.ts +33 -0
  159. package/src/vendor/pi-tui/editor-component.ts +74 -0
  160. package/src/vendor/pi-tui/fuzzy.ts +138 -0
  161. package/src/vendor/pi-tui/index.ts +156 -0
  162. package/src/vendor/pi-tui/keybindings.ts +320 -0
  163. package/src/vendor/pi-tui/keys.ts +1401 -0
  164. package/src/vendor/pi-tui/kill-ring.ts +46 -0
  165. package/src/vendor/pi-tui/latex.ts +1506 -0
  166. package/src/vendor/pi-tui/layout-node.ts +51 -0
  167. package/src/vendor/pi-tui/layout.ts +449 -0
  168. package/src/vendor/pi-tui/native-modifiers.ts +13 -0
  169. package/src/vendor/pi-tui/native-module-path.ts +31 -0
  170. package/src/vendor/pi-tui/native-platform.ts +63 -0
  171. package/src/vendor/pi-tui/stdin-buffer.ts +444 -0
  172. package/src/vendor/pi-tui/terminal-colors.ts +73 -0
  173. package/src/vendor/pi-tui/terminal-image.ts +696 -0
  174. package/src/vendor/pi-tui/terminal.ts +547 -0
  175. package/src/vendor/pi-tui/tui-alt-screen.ts +1742 -0
  176. package/src/vendor/pi-tui/tui-main-screen.ts +655 -0
  177. package/src/vendor/pi-tui/tui.ts +1456 -0
  178. package/src/vendor/pi-tui/undo-stack.ts +28 -0
  179. package/src/vendor/pi-tui/utils.ts +1345 -0
  180. package/src/vendor/pi-tui/word-navigation.ts +117 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Explicit catalog refresh. The only network read in this area.
3
+ *
4
+ * Startup never imports a call to this. `xeno models --refresh` and
5
+ * `/models refresh` do. A failure (no network, timeout, 404) is a reason
6
+ * string, not a throw — the caller falls back to the built-in list and
7
+ * says so. Timeout is 15s, assumed: long enough for a 4.7MB JSON on a
8
+ * slow link, short enough that a hang is distinguishable from "still
9
+ * starting".
10
+ */
11
+
12
+ import { PROVIDER_WIRE, type Api, type ModelSpec, type Price } from "./model.ts";
13
+
14
+ export const MODELS_DEV_URL = "https://models.dev/api.json";
15
+
16
+ /** Assumed. See the file header. */
17
+ const REFRESH_TIMEOUT_MS = 15_000;
18
+
19
+ export type RefreshResult =
20
+ | { ok: true; count: number }
21
+ | { ok: false; reason: string };
22
+
23
+ export async function refreshRemoteCatalog(opts: {
24
+ /** Call signature only — `typeof fetch` also has `preconnect`, which a test spy does not. */
25
+ fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
26
+ write: (value: unknown) => Promise<void>;
27
+ url?: string;
28
+ }): Promise<RefreshResult> {
29
+ const url = opts.url ?? MODELS_DEV_URL;
30
+ let response: Response;
31
+ try {
32
+ response = await opts.fetch(url, { signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS) });
33
+ } catch (err) {
34
+ const detail = err instanceof Error ? err.message : String(err);
35
+ return { ok: false, reason: `could not refresh models: ${detail}` };
36
+ }
37
+ if (!response.ok) {
38
+ return { ok: false, reason: `could not refresh models: ${url} returned ${response.status}` };
39
+ }
40
+ let json: unknown;
41
+ try {
42
+ json = await response.json();
43
+ } catch (err) {
44
+ return { ok: false, reason: `could not refresh models: body is not JSON (${(err as Error).message})` };
45
+ }
46
+ const models = modelsFromRemote(json);
47
+ if (models.length === 0) {
48
+ return { ok: false, reason: `could not refresh models: ${url} named no provider we can speak` };
49
+ }
50
+ try {
51
+ await opts.write({ models: models.map(serializeSpec) });
52
+ } catch (err) {
53
+ return { ok: false, reason: `could not write models: ${(err as Error).message}` };
54
+ }
55
+ return { ok: true, count: models.length };
56
+ }
57
+
58
+ function modelsFromRemote(json: unknown): ModelSpec[] {
59
+ if (typeof json !== "object" || json === null) return [];
60
+ const out: ModelSpec[] = [];
61
+ for (const [rawId, value] of Object.entries(json as Record<string, unknown>)) {
62
+ const providerId = rawId === "togetherai" ? "together" : rawId;
63
+ const wire = PROVIDER_WIRE[rawId] ?? PROVIDER_WIRE[providerId];
64
+ if (wire === undefined) continue;
65
+ if (typeof value !== "object" || value === null) continue;
66
+ const provider = value as Record<string, unknown>;
67
+ const models = provider["models"];
68
+ if (typeof models !== "object" || models === null) continue;
69
+ for (const model of Object.values(models as Record<string, unknown>)) {
70
+ const spec = specFromRemote(model, providerId, wire);
71
+ if (spec !== null) out.push(spec);
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+
77
+ function specFromRemote(
78
+ raw: unknown,
79
+ provider: string,
80
+ wire: { api: Api; baseUrl?: string },
81
+ ): ModelSpec | null {
82
+ if (typeof raw !== "object" || raw === null) return null;
83
+ const m = raw as Record<string, unknown>;
84
+ if (m["tool_call"] === false) return null;
85
+ if (typeof m["id"] !== "string" || m["id"].length === 0) return null;
86
+ const cost = m["cost"];
87
+ const limit = m["limit"];
88
+ const ctx = isRecord(limit) && typeof limit["context"] === "number" ? limit["context"] : undefined;
89
+ const max = isRecord(limit) && typeof limit["output"] === "number" ? limit["output"] : undefined;
90
+ const price = priceFromCost(cost);
91
+ const cache = cacheFromCost(provider, cost);
92
+ const spec: ModelSpec = {
93
+ id: m["id"],
94
+ name: typeof m["name"] === "string" && m["name"].length > 0 ? m["name"] : m["id"],
95
+ api: wire.api,
96
+ provider,
97
+ reasoning: m["reasoning"] === true,
98
+ ...(wire.baseUrl !== undefined ? { baseUrl: wire.baseUrl } : {}),
99
+ ...(ctx !== undefined ? { contextWindow: ctx } : {}),
100
+ ...(max !== undefined ? { maxTokens: max } : {}),
101
+ ...(price !== undefined ? { price } : {}),
102
+ ...(cache !== undefined ? cache : {}),
103
+ };
104
+ return spec;
105
+ }
106
+
107
+ function priceFromCost(cost: unknown): Price | undefined {
108
+ if (!isRecord(cost)) return undefined;
109
+ const input = cost["input"];
110
+ const output = cost["output"];
111
+ if (typeof input !== "number" || typeof output !== "number") return undefined;
112
+ // Cache dollars are optional. Missing is missing — a 0 here would be
113
+ // DeepSeek's term, not a default for everyone else.
114
+ const price: Price = { input, output };
115
+ if (typeof cost["cache_read"] === "number") price.cacheRead = cost["cache_read"];
116
+ if (typeof cost["cache_write"] === "number") price.cacheWrite = cost["cache_write"];
117
+ return price;
118
+ }
119
+
120
+ function cacheFromCost(
121
+ provider: string,
122
+ cost: unknown,
123
+ ): { cache: "explicit" | "automatic"; rates: { read: number; write: number } } | undefined {
124
+ if (!isRecord(cost)) return undefined;
125
+ const input = cost["input"];
126
+ const cacheRead = cost["cache_read"];
127
+ const cacheWrite = cost["cache_write"];
128
+ if (typeof input !== "number" || input === 0) return undefined;
129
+ if (typeof cacheRead !== "number" || typeof cacheWrite !== "number") return undefined;
130
+ return {
131
+ cache: provider === "anthropic" ? "explicit" : "automatic",
132
+ rates: { read: cacheRead / input, write: cacheWrite / input },
133
+ };
134
+ }
135
+
136
+ function serializeSpec(spec: ModelSpec): Record<string, unknown> {
137
+ return {
138
+ id: spec.id,
139
+ name: spec.name,
140
+ api: spec.api,
141
+ provider: spec.provider,
142
+ reasoning: spec.reasoning,
143
+ ...(spec.baseUrl !== undefined ? { baseUrl: spec.baseUrl } : {}),
144
+ ...(spec.cache !== undefined ? { cache: spec.cache } : {}),
145
+ ...(spec.rates !== undefined ? { rates: spec.rates } : {}),
146
+ ...(spec.price !== undefined ? { price: spec.price } : {}),
147
+ ...(spec.contextWindow !== undefined ? { contextWindow: spec.contextWindow } : {}),
148
+ ...(spec.maxTokens !== undefined ? { maxTokens: spec.maxTokens } : {}),
149
+ ...(spec.compat !== undefined ? { compat: spec.compat } : {}),
150
+ };
151
+ }
152
+
153
+ function isRecord(v: unknown): v is Record<string, unknown> {
154
+ return typeof v === "object" && v !== null && !Array.isArray(v);
155
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * RFC 8628 device-code polling.
3
+ *
4
+ * Ported from pi (earendil-works/pi, MIT) — packages/ai/src/auth/oauth/device-code.ts
5
+ *
6
+ * Isolated from the authorization-code module so Copilot's poll loop does not
7
+ * drag a callback HTTP server into a path that never binds a port.
8
+ */
9
+
10
+ const CANCEL_MESSAGE = "Login cancelled";
11
+ const TIMEOUT_MESSAGE = "Device flow timed out";
12
+ /**
13
+ * RFC 8628 §3.2: if the authorization server omits `interval`, poll every 5s.
14
+ * Assumed from the RFC, not measured.
15
+ */
16
+ const DEFAULT_POLL_INTERVAL_SECONDS = 5;
17
+ const MINIMUM_INTERVAL_MS = 1000;
18
+ /** RFC 8628 §3.5: `slow_down` increases the interval by 5 seconds. */
19
+ const SLOW_DOWN_INTERVAL_INCREMENT_MS = 5000;
20
+
21
+ type Incomplete =
22
+ | { status: "pending" }
23
+ | { status: "slow_down"; intervalSeconds?: number }
24
+ | { status: "failed"; message: string };
25
+
26
+ export type DevicePollResult<T> = Incomplete | { status: "complete"; value: T };
27
+
28
+ export async function abortableSleep(ms: number, signal: AbortSignal): Promise<void> {
29
+ return new Promise((resolve, reject) => {
30
+ if (signal.aborted) {
31
+ reject(new Error(CANCEL_MESSAGE));
32
+ return;
33
+ }
34
+ const onAbort = (): void => {
35
+ clearTimeout(timeout);
36
+ reject(new Error(CANCEL_MESSAGE));
37
+ };
38
+ const timeout = setTimeout(() => {
39
+ signal.removeEventListener("abort", onAbort);
40
+ resolve();
41
+ }, ms);
42
+ signal.addEventListener("abort", onAbort, { once: true });
43
+ });
44
+ }
45
+
46
+ export async function pollDeviceCode<T>(opts: {
47
+ intervalSeconds?: number;
48
+ expiresInSeconds?: number;
49
+ waitBeforeFirstPoll?: boolean;
50
+ poll: () => Promise<DevicePollResult<T>>;
51
+ signal: AbortSignal;
52
+ sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
53
+ }): Promise<T> {
54
+ const sleep = opts.sleep ?? abortableSleep;
55
+ const deadline =
56
+ typeof opts.expiresInSeconds === "number"
57
+ ? Date.now() + opts.expiresInSeconds * 1000
58
+ : Number.POSITIVE_INFINITY;
59
+ let intervalMs = Math.max(
60
+ MINIMUM_INTERVAL_MS,
61
+ Math.floor((opts.intervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * 1000),
62
+ );
63
+
64
+ if (opts.waitBeforeFirstPoll === true) {
65
+ const remaining = deadline - Date.now();
66
+ if (remaining > 0) await sleep(Math.min(intervalMs, remaining), opts.signal);
67
+ }
68
+
69
+ while (Date.now() < deadline) {
70
+ if (opts.signal.aborted) throw new Error(CANCEL_MESSAGE);
71
+ const result = await opts.poll();
72
+ if (result.status === "complete") return result.value;
73
+ if (result.status === "failed") throw new Error(result.message);
74
+ if (result.status === "slow_down") {
75
+ const next = result.intervalSeconds;
76
+ intervalMs =
77
+ typeof next === "number" && Number.isFinite(next) && next > 0
78
+ ? Math.max(MINIMUM_INTERVAL_MS, Math.floor(next * 1000))
79
+ : Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INTERVAL_INCREMENT_MS);
80
+ }
81
+ const remaining = deadline - Date.now();
82
+ if (remaining <= 0) break;
83
+ await sleep(Math.min(intervalMs, remaining), opts.signal);
84
+ }
85
+ throw new Error(TIMEOUT_MESSAGE);
86
+ }