@visual-z/xeno 0.8.3 → 0.9.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 (348) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +31 -746
  3. package/bin/xeno.exe +26 -0
  4. package/install.cjs +88 -0
  5. package/package.json +18 -48
  6. package/docs/app-server.md +0 -115
  7. package/docs/foundations.md +0 -88
  8. package/docs/install.md +0 -244
  9. package/docs/providers.md +0 -172
  10. package/docs/research/foundation-clients-2026-10-09.md +0 -27
  11. package/docs/research/foundation-editing-2026-10-09.md +0 -56
  12. package/docs/research/foundation-network-2026-10-09.md +0 -37
  13. package/docs/research/foundation-workflow-2026-10-09.md +0 -37
  14. package/src/apps/acp-server.ts +0 -41
  15. package/src/apps/agent-profiles.ts +0 -19
  16. package/src/apps/app-server.ts +0 -30
  17. package/src/apps/approve.ts +0 -49
  18. package/src/apps/cli.ts +0 -99
  19. package/src/apps/commands.ts +0 -319
  20. package/src/apps/completions.ts +0 -87
  21. package/src/apps/control-transport.ts +0 -90
  22. package/src/apps/export-cli.ts +0 -24
  23. package/src/apps/goal-flow.ts +0 -70
  24. package/src/apps/image-draft.ts +0 -46
  25. package/src/apps/input.ts +0 -312
  26. package/src/apps/model-catalog.ts +0 -69
  27. package/src/apps/preferences.ts +0 -20
  28. package/src/apps/project-trust.ts +0 -9
  29. package/src/apps/provider-connect.ts +0 -346
  30. package/src/apps/question-ui.ts +0 -27
  31. package/src/apps/render.ts +0 -181
  32. package/src/apps/resources.ts +0 -66
  33. package/src/apps/review.ts +0 -109
  34. package/src/apps/session-hub.ts +0 -96
  35. package/src/apps/session-lifecycle.ts +0 -64
  36. package/src/apps/shell.ts +0 -2066
  37. package/src/apps/startup-input.ts +0 -135
  38. package/src/apps/startup.ts +0 -1677
  39. package/src/apps/ui/app.ts +0 -1713
  40. package/src/apps/ui/control-model.ts +0 -43
  41. package/src/apps/ui/extension-host.ts +0 -194
  42. package/src/apps/ui/image-cache.ts +0 -121
  43. package/src/apps/ui/palette.ts +0 -88
  44. package/src/apps/ui/project.ts +0 -127
  45. package/src/apps/ui/standalone.ts +0 -148
  46. package/src/apps/ui/status-model.ts +0 -68
  47. package/src/apps/ui/surface.ts +0 -91
  48. package/src/apps/ui/theme.ts +0 -86
  49. package/src/apps/update-check.ts +0 -112
  50. package/src/apps/workflow.ts +0 -58
  51. package/src/core/acceptance.ts +0 -139
  52. package/src/core/action-defaults.ts +0 -58
  53. package/src/core/activity-status.ts +0 -52
  54. package/src/core/agent-profile.ts +0 -130
  55. package/src/core/attachment-input.ts +0 -17
  56. package/src/core/blind-checks.ts +0 -72
  57. package/src/core/boundary.ts +0 -174
  58. package/src/core/branch-receipt.ts +0 -26
  59. package/src/core/catalog-merge.ts +0 -19
  60. package/src/core/checkpoints.ts +0 -85
  61. package/src/core/churn.ts +0 -61
  62. package/src/core/command-artifacts.ts +0 -94
  63. package/src/core/command-contract.ts +0 -151
  64. package/src/core/completion-action.ts +0 -20
  65. package/src/core/completion-review.ts +0 -98
  66. package/src/core/context-detail.ts +0 -37
  67. package/src/core/context-policy.ts +0 -133
  68. package/src/core/context-window.ts +0 -54
  69. package/src/core/continuation.ts +0 -130
  70. package/src/core/control-protocol.ts +0 -69
  71. package/src/core/control-view.ts +0 -39
  72. package/src/core/cycle.ts +0 -17
  73. package/src/core/data-query.ts +0 -201
  74. package/src/core/deadline.ts +0 -40
  75. package/src/core/derive.ts +0 -516
  76. package/src/core/draft-images.ts +0 -56
  77. package/src/core/edit-plan.ts +0 -200
  78. package/src/core/environment.ts +0 -181
  79. package/src/core/event-validation.ts +0 -220
  80. package/src/core/events.ts +0 -670
  81. package/src/core/evidence-ladder.ts +0 -62
  82. package/src/core/evidence-links.ts +0 -68
  83. package/src/core/evidence.ts +0 -240
  84. package/src/core/execution.ts +0 -18
  85. package/src/core/exhaustive.ts +0 -18
  86. package/src/core/export.ts +0 -179
  87. package/src/core/extension-flags.ts +0 -14
  88. package/src/core/external-effects.ts +0 -488
  89. package/src/core/failure-recovery.ts +0 -173
  90. package/src/core/file-precondition.ts +0 -78
  91. package/src/core/file-references.ts +0 -35
  92. package/src/core/frontmatter.ts +0 -213
  93. package/src/core/git-status.ts +0 -29
  94. package/src/core/goal-view.ts +0 -64
  95. package/src/core/goal.ts +0 -200
  96. package/src/core/handoff.ts +0 -479
  97. package/src/core/history-query.ts +0 -139
  98. package/src/core/history.ts +0 -237
  99. package/src/core/image-limits.ts +0 -72
  100. package/src/core/images.ts +0 -46
  101. package/src/core/integration-health.ts +0 -34
  102. package/src/core/json-event.ts +0 -131
  103. package/src/core/json-pointer.ts +0 -46
  104. package/src/core/keymap.ts +0 -153
  105. package/src/core/lsp-framing.ts +0 -34
  106. package/src/core/mcp-config.ts +0 -68
  107. package/src/core/model-picker.ts +0 -64
  108. package/src/core/model-preferences.ts +0 -28
  109. package/src/core/model-replay.ts +0 -22
  110. package/src/core/model-scope.ts +0 -53
  111. package/src/core/note-guidance.ts +0 -34
  112. package/src/core/oauth-fields.ts +0 -44
  113. package/src/core/observation-view.ts +0 -152
  114. package/src/core/outcome.ts +0 -44
  115. package/src/core/overflow.ts +0 -59
  116. package/src/core/packages.ts +0 -47
  117. package/src/core/patch.ts +0 -130
  118. package/src/core/path-suggestions.ts +0 -59
  119. package/src/core/permissions.ts +0 -410
  120. package/src/core/process-receipt.ts +0 -105
  121. package/src/core/process-state.ts +0 -37
  122. package/src/core/program-status.ts +0 -40
  123. package/src/core/progress.ts +0 -150
  124. package/src/core/project.ts +0 -431
  125. package/src/core/prompt-policy.ts +0 -46
  126. package/src/core/prompts.ts +0 -111
  127. package/src/core/question.ts +0 -94
  128. package/src/core/queue.ts +0 -78
  129. package/src/core/read-bounds.ts +0 -24
  130. package/src/core/read-observation.ts +0 -106
  131. package/src/core/recovery-sources.ts +0 -74
  132. package/src/core/regex-dialect.ts +0 -25
  133. package/src/core/request-budget.ts +0 -23
  134. package/src/core/request-progress.ts +0 -87
  135. package/src/core/request-state.ts +0 -273
  136. package/src/core/resource-selection.ts +0 -36
  137. package/src/core/response-recovery.ts +0 -59
  138. package/src/core/resume.ts +0 -59
  139. package/src/core/review.ts +0 -68
  140. package/src/core/ripgrep-release.ts +0 -64
  141. package/src/core/runtime-target.ts +0 -94
  142. package/src/core/scheduling.ts +0 -8
  143. package/src/core/search-observation.ts +0 -97
  144. package/src/core/secrets.ts +0 -188
  145. package/src/core/session-archive.ts +0 -4
  146. package/src/core/session-browser.ts +0 -82
  147. package/src/core/session-entry.ts +0 -61
  148. package/src/core/session-exit.ts +0 -42
  149. package/src/core/session-format.ts +0 -169
  150. package/src/core/session-position.ts +0 -26
  151. package/src/core/session-tree.ts +0 -147
  152. package/src/core/settings.ts +0 -192
  153. package/src/core/skills.ts +0 -165
  154. package/src/core/sql-query.ts +0 -211
  155. package/src/core/stall.ts +0 -128
  156. package/src/core/stop-report.ts +0 -68
  157. package/src/core/text-normalize.ts +0 -81
  158. package/src/core/theme.ts +0 -166
  159. package/src/core/thinking.ts +0 -26
  160. package/src/core/tokens.ts +0 -151
  161. package/src/core/tool-contract.ts +0 -153
  162. package/src/core/tool-discovery.ts +0 -87
  163. package/src/core/tool-failure.ts +0 -81
  164. package/src/core/tool-input.ts +0 -44
  165. package/src/core/transcript.ts +0 -108
  166. package/src/core/tui-mode.ts +0 -29
  167. package/src/core/update-check.ts +0 -157
  168. package/src/core/version.ts +0 -55
  169. package/src/core/web-content.ts +0 -80
  170. package/src/core/work-state.ts +0 -154
  171. package/src/core/workflow-mode.ts +0 -34
  172. package/src/core/working-set.ts +0 -119
  173. package/src/core/yaml.ts +0 -282
  174. package/src/runtime/acp-agent.ts +0 -289
  175. package/src/runtime/agent-profiles.ts +0 -21
  176. package/src/runtime/auth.ts +0 -367
  177. package/src/runtime/blind-check.ts +0 -36
  178. package/src/runtime/branch-jobs.ts +0 -36
  179. package/src/runtime/catalog.ts +0 -285
  180. package/src/runtime/clipboard.ts +0 -125
  181. package/src/runtime/command-artifacts.ts +0 -64
  182. package/src/runtime/connection-reuse.ts +0 -40
  183. package/src/runtime/control-client.ts +0 -149
  184. package/src/runtime/control-connection.ts +0 -139
  185. package/src/runtime/counter-check.ts +0 -74
  186. package/src/runtime/environment-tools.ts +0 -60
  187. package/src/runtime/evidence-notes.ts +0 -35
  188. package/src/runtime/exec-sessions.ts +0 -456
  189. package/src/runtime/execution-control.ts +0 -40
  190. package/src/runtime/extension-loader.ts +0 -37
  191. package/src/runtime/extension-ui.ts +0 -47
  192. package/src/runtime/extensions.ts +0 -370
  193. package/src/runtime/formatter-tool.ts +0 -38
  194. package/src/runtime/formatter.ts +0 -50
  195. package/src/runtime/foundation.ts +0 -116
  196. package/src/runtime/history-cache.ts +0 -57
  197. package/src/runtime/history-tools.ts +0 -168
  198. package/src/runtime/images.ts +0 -80
  199. package/src/runtime/integrations.ts +0 -216
  200. package/src/runtime/loop.ts +0 -3001
  201. package/src/runtime/lsp-client.ts +0 -201
  202. package/src/runtime/mcp-client.ts +0 -264
  203. package/src/runtime/mcp-oauth.ts +0 -168
  204. package/src/runtime/mcp-schema.ts +0 -51
  205. package/src/runtime/model-config.ts +0 -232
  206. package/src/runtime/model-data.LICENSE +0 -27
  207. package/src/runtime/model-data.ts +0 -21
  208. package/src/runtime/model-discovery.ts +0 -208
  209. package/src/runtime/model-replay.ts +0 -10
  210. package/src/runtime/model-scope.ts +0 -20
  211. package/src/runtime/model-sources.ts +0 -151
  212. package/src/runtime/model.ts +0 -686
  213. package/src/runtime/models-fetch.ts +0 -57
  214. package/src/runtime/oauth-device.ts +0 -86
  215. package/src/runtime/oauth.ts +0 -853
  216. package/src/runtime/open-image.ts +0 -42
  217. package/src/runtime/openai-responses.ts +0 -304
  218. package/src/runtime/openai.ts +0 -697
  219. package/src/runtime/output-schema.ts +0 -26
  220. package/src/runtime/package-manager.ts +0 -125
  221. package/src/runtime/patch-tool.ts +0 -38
  222. package/src/runtime/permission-resources.ts +0 -18
  223. package/src/runtime/pi-provider.ts +0 -423
  224. package/src/runtime/program-status.ts +0 -38
  225. package/src/runtime/progress.ts +0 -26
  226. package/src/runtime/provider-access.ts +0 -21
  227. package/src/runtime/provider-connect.ts +0 -109
  228. package/src/runtime/provider-presets.ts +0 -98
  229. package/src/runtime/provider-protocols.ts +0 -29
  230. package/src/runtime/provider-request.ts +0 -87
  231. package/src/runtime/provider-tool-policy.ts +0 -33
  232. package/src/runtime/provider.ts +0 -580
  233. package/src/runtime/question-tool.ts +0 -62
  234. package/src/runtime/retry.ts +0 -447
  235. package/src/runtime/review-tool.ts +0 -48
  236. package/src/runtime/rich-text.ts +0 -36
  237. package/src/runtime/runtime-target.ts +0 -149
  238. package/src/runtime/secrets.ts +0 -79
  239. package/src/runtime/self-update.ts +0 -74
  240. package/src/runtime/session-control.ts +0 -265
  241. package/src/runtime/session-hub.ts +0 -85
  242. package/src/runtime/stream-idle.ts +0 -80
  243. package/src/runtime/tool-broker.ts +0 -137
  244. package/src/runtime/tool-discovery.ts +0 -58
  245. package/src/runtime/tool-queue.ts +0 -77
  246. package/src/runtime/tools.ts +0 -1820
  247. package/src/runtime/turn-effects.ts +0 -31
  248. package/src/runtime/update-check.ts +0 -48
  249. package/src/runtime/usage.ts +0 -253
  250. package/src/runtime/version.ts +0 -12
  251. package/src/runtime/web-http.ts +0 -56
  252. package/src/runtime/web-tools.ts +0 -88
  253. package/src/runtime/working-notes.ts +0 -60
  254. package/src/runtime/workspace-lifecycle.ts +0 -141
  255. package/src/store/acceptance.ts +0 -15
  256. package/src/store/agent-profile-files.ts +0 -13
  257. package/src/store/atomic.ts +0 -31
  258. package/src/store/auth-store.ts +0 -50
  259. package/src/store/blind-checks.ts +0 -32
  260. package/src/store/blobs.ts +0 -143
  261. package/src/store/checkpoint-history.ts +0 -32
  262. package/src/store/command-artifacts.ts +0 -96
  263. package/src/store/command.ts +0 -111
  264. package/src/store/config.ts +0 -69
  265. package/src/store/context-files.ts +0 -436
  266. package/src/store/file-checkpoints.ts +0 -108
  267. package/src/store/file-index.ts +0 -174
  268. package/src/store/foundation-config.ts +0 -24
  269. package/src/store/git-checkpoints.ts +0 -67
  270. package/src/store/git-status.ts +0 -15
  271. package/src/store/history-search.ts +0 -19
  272. package/src/store/log.ts +0 -725
  273. package/src/store/lsp-document.ts +0 -30
  274. package/src/store/mcp-credentials.ts +0 -85
  275. package/src/store/models.ts +0 -60
  276. package/src/store/observations.ts +0 -224
  277. package/src/store/packages.ts +0 -98
  278. package/src/store/patch.ts +0 -92
  279. package/src/store/permission-paths.ts +0 -34
  280. package/src/store/process-receipts.ts +0 -34
  281. package/src/store/query-source.ts +0 -49
  282. package/src/store/recovery.ts +0 -44
  283. package/src/store/replay.ts +0 -76
  284. package/src/store/resource-index.ts +0 -170
  285. package/src/store/resources.ts +0 -285
  286. package/src/store/restore.ts +0 -110
  287. package/src/store/review.ts +0 -109
  288. package/src/store/runtime-target.ts +0 -64
  289. package/src/store/search.ts +0 -728
  290. package/src/store/secrets.ts +0 -44
  291. package/src/store/serial.ts +0 -13
  292. package/src/store/session-archive.ts +0 -165
  293. package/src/store/session-browser.ts +0 -68
  294. package/src/store/session-position.ts +0 -27
  295. package/src/store/sessions.ts +0 -525
  296. package/src/store/source-cancellation.ts +0 -6
  297. package/src/store/tool-binaries.ts +0 -90
  298. package/src/store/trust.ts +0 -82
  299. package/src/store/update-cache.ts +0 -36
  300. package/src/store/user-settings.ts +0 -30
  301. package/src/store/workspace-clone.ts +0 -42
  302. package/src/store/workspace-observation.ts +0 -113
  303. package/src/store/workspace.ts +0 -115
  304. package/src/store/worktree.ts +0 -278
  305. package/src/tui/ansi-spans.ts +0 -126
  306. package/src/tui/diff.ts +0 -160
  307. package/src/tui/exhaustive.ts +0 -14
  308. package/src/tui/fuzzy.ts +0 -75
  309. package/src/tui/style.ts +0 -208
  310. package/src/tui/ui/atomic-draft.ts +0 -121
  311. package/src/tui/ui/canvas.ts +0 -96
  312. package/src/tui/ui/colors.ts +0 -70
  313. package/src/tui/ui/composer.ts +0 -200
  314. package/src/tui/ui/control-panel.ts +0 -25
  315. package/src/tui/ui/control-sidebar.ts +0 -49
  316. package/src/tui/ui/dialog.ts +0 -398
  317. package/src/tui/ui/draft.ts +0 -23
  318. package/src/tui/ui/duration.ts +0 -16
  319. package/src/tui/ui/edge.ts +0 -186
  320. package/src/tui/ui/image-size.ts +0 -44
  321. package/src/tui/ui/ink.ts +0 -210
  322. package/src/tui/ui/items.ts +0 -42
  323. package/src/tui/ui/keys.ts +0 -40
  324. package/src/tui/ui/layout.ts +0 -74
  325. package/src/tui/ui/list-model.ts +0 -86
  326. package/src/tui/ui/list-view.ts +0 -172
  327. package/src/tui/ui/mascot.ts +0 -67
  328. package/src/tui/ui/messages.ts +0 -518
  329. package/src/tui/ui/motion.ts +0 -28
  330. package/src/tui/ui/paths.ts +0 -10
  331. package/src/tui/ui/pictures.ts +0 -108
  332. package/src/tui/ui/screen.ts +0 -548
  333. package/src/tui/ui/status.ts +0 -403
  334. package/src/tui/ui/styled.ts +0 -85
  335. package/src/tui/ui/tool-card.ts +0 -150
  336. package/src/tui/width.ts +0 -567
  337. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  338. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  339. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  340. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  341. package/src/vendor/pi-rich/html.ts +0 -51
  342. package/src/vendor/pi-rich/image-convert.ts +0 -49
  343. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  344. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  345. package/src/vendor/pi-rich/image-resize.ts +0 -123
  346. package/src/vendor/pi-rich/mime.ts +0 -116
  347. package/src/vendor/pi-rich/photon.ts +0 -139
  348. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,367 +0,0 @@
1
- /**
2
- * Where a credential comes from.
3
- *
4
- * The difficulty here is entirely in the priority, not in the reading, so the
5
- * reading is injected: `env` and `run` are parameters. That is what lets the
6
- * one rule that actually bites be pinned by a test that touches no
7
- * environment and no keychain --
8
- *
9
- * A STORED CREDENTIAL OWNS ITS PROVIDER. The environment is consulted only
10
- * when nothing is stored, and never as a fallback after a stored entry
11
- * fails to resolve. Otherwise someone edits `auth.json`, sees no change,
12
- * and has no way to discover that a forgotten `export` from months ago is
13
- * still winning. pi states this rule outright in its resolver; it is worth
14
- * copying verbatim.
15
- *
16
- * The order is command line, auth.json, models.json, then the environment.
17
- */
18
-
19
- import { assertNever } from "../core/exhaustive.ts";
20
- import type { ModelSpec } from "./model.ts";
21
- import { providerAccess } from "./provider-access.ts";
22
- import { SOURCE_PROVIDERS } from "./model-data.ts";
23
- import {
24
- refreshOAuthCredential,
25
- GROK_PROVIDER, GROK_BASE_URL,
26
- type OAuthCredential,
27
- type RefreshEffects,
28
- } from "./oauth.ts";
29
-
30
- export type KeyValue =
31
- | { kind: "literal"; value: string }
32
- | { kind: "command"; command: string }
33
- | { kind: "interpolate"; template: string };
34
-
35
- export type StoredCredential =
36
- | { type: "api_key"; key: string }
37
- | OAuthCredential;
38
-
39
- export interface AuthFile {
40
- [provider: string]: StoredCredential | undefined;
41
- }
42
-
43
- export function storedIsOAuth(stored: AuthFile, provider: string): boolean {
44
- const entry = storedCredential(stored, provider);
45
- return entry !== undefined && entry.type === "oauth";
46
- }
47
-
48
- function storedCredential(stored: AuthFile, provider: string): StoredCredential | undefined {
49
- // Earlier xeno stored Codex subscriptions under openai. Only OAuth gets this read-compatible alias;
50
- // API keys and explicit native records retain their own provider and billing account.
51
- return stored[provider] ?? (provider === "openai-codex" && stored["openai"]?.type === "oauth" ? stored["openai"] : undefined);
52
- }
53
-
54
- export type Resolved =
55
- | { ok: true; key: string; source: "flag" | "auth.json" | "models.json" | "environment" | "ambient" }
56
- | { ok: false; reason: string };
57
-
58
- /** The two effects resolution needs, injected so the rules stay testable. */
59
- export interface ResolveEffects {
60
- env(name: string): string | undefined;
61
- run(command: string): string;
62
- }
63
-
64
- /**
65
- * Which of the three forms a configured key value is.
66
- *
67
- * A plain uppercase string is a LITERAL, not a variable name. That choice
68
- * matters: a provider whose key really is `public` or `ollama` is common, and
69
- * guessing that an uppercase word means a lookup would break those silently.
70
- * `$` marks a lookup, and nothing else does.
71
- */
72
- export function parseKeyValue(raw: string): KeyValue {
73
- if (raw.startsWith("!")) return { kind: "command", command: raw.slice(1) };
74
- // The escapes are checked before interpolation so a key that genuinely
75
- // begins with `$` or `!` can be written down at all.
76
- if (raw.startsWith("$$")) return { kind: "literal", value: `$${raw.slice(2)}` };
77
- if (raw.startsWith("$!")) return { kind: "literal", value: `!${raw.slice(2)}` };
78
- if (raw.includes("$")) return { kind: "interpolate", template: raw };
79
- return { kind: "literal", value: raw };
80
- }
81
-
82
- /** `$FOO_BAR` is the variable FOO_BAR; `${FOO}_BAR` is FOO followed by literal text. */
83
- const VARIABLE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g;
84
-
85
- export function resolveKeyValue(
86
- raw: string,
87
- fx: ResolveEffects,
88
- ): { ok: true; key: string } | { ok: false; reason: string } {
89
- const parsed = parseKeyValue(raw);
90
- switch (parsed.kind) {
91
- case "literal":
92
- return { ok: true, key: parsed.value };
93
-
94
- case "command": {
95
- let out: string;
96
- try {
97
- out = fx.run(parsed.command);
98
- } catch (err) {
99
- return { ok: false, reason: `key command failed: ${(err as Error).message}` };
100
- }
101
- // Every CLI that prints a secret prints a newline after it, and a key
102
- // with a trailing newline is rejected by the API as malformed.
103
- const key = out.trim();
104
- if (key.length === 0) {
105
- return { ok: false, reason: `key command produced no output: ${parsed.command}` };
106
- }
107
- return { ok: true, key };
108
- }
109
-
110
- case "interpolate": {
111
- const missing: string[] = [];
112
- const key = parsed.template.replace(VARIABLE, (_whole, braced?: string, bare?: string) => {
113
- const name = braced ?? bare ?? "";
114
- const value = fx.env(name);
115
- if (value === undefined || value.length === 0) {
116
- missing.push(name);
117
- return "";
118
- }
119
- return value;
120
- });
121
- // Unresolved is NOT the empty string. An empty key looks legal, gets
122
- // sent, and comes back as a 401 that says nothing about the real
123
- // mistake -- which is that a variable was never exported.
124
- if (missing.length > 0) {
125
- return { ok: false, reason: `unset environment variable: ${missing.join(", ")}` };
126
- }
127
- return { ok: true, key };
128
- }
129
-
130
- default: {
131
- const never: never = parsed;
132
- throw new Error(`unreachable key value: ${JSON.stringify(never)}`);
133
- }
134
- }
135
- }
136
-
137
- /** Per-provider variables resolve lazily, so order cannot change their meaning or mutate process.env. */
138
- export function providerEnvironment(spec: ModelSpec, fx: ResolveEffects): { env: Record<string, string>; effects: ResolveEffects } {
139
- const configured = providerAccess(spec).env ?? {};
140
- const env: Record<string, string> = {};
141
- const resolving = new Set<string>();
142
- const lookup = (name: string): string | undefined => {
143
- if (Object.hasOwn(env, name)) return env[name];
144
- const raw = configured[name];
145
- if (raw === undefined) return fx.env(name);
146
- if (resolving.has(name)) throw new Error(`${spec.provider} env.${name}: cyclic variable reference`);
147
- resolving.add(name);
148
- try {
149
- const got = resolveKeyValue(raw, { ...fx, env: lookup });
150
- if (!got.ok) throw new Error(`${spec.provider} env.${name}: could not resolve configured value`);
151
- env[name] = got.key;
152
- return got.key;
153
- } finally { resolving.delete(name); }
154
- };
155
- for (const name of Object.keys(configured)) lookup(name);
156
- const aliases: Record<string, readonly string[]> = {
157
- AZURE_OPENAI_API_KEY: ["AZURE_API_KEY"], AZURE_OPENAI_RESOURCE_NAME: ["AZURE_RESOURCE_NAME"], AZURE_OPENAI_BASE_URL: ["AZURE_BASE_URL"],
158
- GOOGLE_CLOUD_PROJECT: ["GOOGLE_VERTEX_PROJECT", "GCLOUD_PROJECT"], GOOGLE_CLOUD_LOCATION: ["GOOGLE_VERTEX_LOCATION"],
159
- };
160
- for (const [target, sources] of Object.entries(aliases)) {
161
- const value = lookup(target) ?? sources.map(lookup).find(Boolean);
162
- if (value) env[target] = value;
163
- }
164
- return { env, effects: { ...fx, env: lookup } };
165
- }
166
-
167
- function configuredEffects(spec: ModelSpec, fx: ResolveEffects): { ok: true; effects: ResolveEffects } | { ok: false; reason: string } {
168
- if (providerAccess(spec).invalid) return { ok: false, reason: providerAccess(spec).invalid! };
169
- try {
170
- const scoped = providerEnvironment(spec, fx).effects;
171
- if (spec.provider === GROK_PROVIDER && spec.baseUrl?.replace(/\/$/, "") !== GROK_BASE_URL) {
172
- return { ok: false, reason: "Grok account tokens require the official inference proxy" };
173
- }
174
- const access = providerAccess(spec);
175
- if (["google-generative-ai", "google-vertex"].includes(spec.api) && (access.auth === false || access.authHeader === false || typeof access.authHeader === "string")) {
176
- return { ok: false, reason: `${spec.api} owns API-key authentication; use apiKey or an OpenAI-compatible local endpoint` };
177
- }
178
- if (!spec.baseUrl && !["anthropic-messages", "google-generative-ai", "google-vertex", "bedrock-converse-stream", "azure-openai-responses"].includes(spec.api)) {
179
- return { ok: false, reason: `${spec.provider}: configure baseUrl for ${spec.api}` };
180
- }
181
- if (spec.baseUrl?.includes("$")) {
182
- const endpoint = resolveKeyValue(spec.baseUrl, scoped);
183
- if (!endpoint.ok) return { ok: false, reason: `${spec.provider} baseUrl: ${endpoint.reason}` };
184
- }
185
- if (spec.api === "azure-openai-responses" && !spec.baseUrl && !scoped.env("AZURE_OPENAI_BASE_URL") && !scoped.env("AZURE_OPENAI_RESOURCE_NAME")) {
186
- return { ok: false, reason: `${spec.provider}: configure baseUrl or AZURE_OPENAI_RESOURCE_NAME` };
187
- }
188
- return { ok: true, effects: scoped };
189
- } catch (err) { return { ok: false, reason: err instanceof Error ? err.message : "could not resolve provider environment" }; }
190
- }
191
-
192
- export function resolveCredential(
193
- spec: ModelSpec,
194
- stored: AuthFile,
195
- flagKey: string | undefined,
196
- fx: ResolveEffects,
197
- ): Resolved {
198
- const access = providerAccess(spec);
199
- const configured = configuredEffects(spec, fx);
200
- if (!configured.ok) return configured;
201
- fx = configured.effects;
202
- if (flagKey !== undefined && flagKey.length > 0) {
203
- const got = resolveKeyValue(flagKey, fx);
204
- return got.ok ? { ok: true, key: got.key, source: "flag" } : got;
205
- }
206
-
207
- const entry = storedCredential(stored, spec.provider);
208
- if (entry !== undefined) {
209
- // Deliberately no fallback to the environment on failure. See the header.
210
- if (entry.type === "oauth") {
211
- if (spec.provider === "openai" && spec.api !== "openai-codex-responses") return { ok: false, reason: "ChatGPT OAuth belongs to openai-codex; choose an openai-codex model or configure an OpenAI API key" };
212
- return { ok: true, key: entry.access, source: "auth.json" };
213
- }
214
- if (entry.type === "api_key") {
215
- const got = resolveKeyValue(entry.key, fx);
216
- return got.ok
217
- ? { ok: true, key: got.key, source: "auth.json" }
218
- : { ok: false, reason: `${spec.provider} in auth.json: ${got.reason}` };
219
- }
220
- return assertNever(entry, "stored credential");
221
- }
222
-
223
- if (access.apiKey !== undefined) {
224
- const got = resolveKeyValue(access.apiKey, fx);
225
- return got.ok ? { ok: true, key: got.key, source: "models.json" }
226
- : { ok: false, reason: `${spec.provider} in models.json: could not resolve apiKey` };
227
- }
228
- if (access.auth === false || access.authHeader === false) return { ok: true, key: "", source: "models.json" };
229
- if (access.auth === "ambient") {
230
- if (spec.api !== "bedrock-converse-stream" && spec.api !== "google-vertex") return { ok: false, reason: `${spec.api} does not support ambient credentials` };
231
- if (spec.api === "google-vertex" && (!fx.env("GOOGLE_CLOUD_PROJECT") || !fx.env("GOOGLE_CLOUD_LOCATION"))) return { ok: false, reason: `${spec.provider}: ambient Vertex authentication requires GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION` };
232
- return { ok: true, key: "", source: "ambient" };
233
- }
234
- for (const variable of envVarsOf(spec.provider, spec.credentialEnv)) {
235
- const value = fx.env(variable);
236
- if (value !== undefined && value.length > 0) {
237
- return { ok: true, key: value, source: "environment" };
238
- }
239
- }
240
-
241
- if (spec.api === "bedrock-converse-stream" && (fx.env("AWS_PROFILE")
242
- || (fx.env("AWS_ACCESS_KEY_ID") && fx.env("AWS_SECRET_ACCESS_KEY"))
243
- || fx.env("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") || fx.env("AWS_CONTAINER_CREDENTIALS_FULL_URI") || fx.env("AWS_WEB_IDENTITY_TOKEN_FILE"))) {
244
- return { ok: true, key: "", source: "ambient" };
245
- }
246
- if (spec.api === "google-vertex" && fx.env("GOOGLE_APPLICATION_CREDENTIALS")
247
- && (fx.env("GOOGLE_CLOUD_PROJECT") || fx.env("GOOGLE_VERTEX_PROJECT") || fx.env("GCLOUD_PROJECT"))
248
- && (fx.env("GOOGLE_CLOUD_LOCATION") || fx.env("GOOGLE_VERTEX_LOCATION"))) return { ok: true, key: "", source: "ambient" };
249
-
250
- return { ok: false, reason: describeMissingCredential(spec.provider) };
251
- }
252
-
253
- /**
254
- * Environment variable each provider reads. Same names pi uses, so a machine
255
- * already set up for one works for the other without re-exporting anything.
256
- */
257
- export const ENV_VAR: Readonly<Record<string, string | readonly string[]>> = {
258
- anthropic: "ANTHROPIC_API_KEY",
259
- openai: "OPENAI_API_KEY",
260
- google: ["GOOGLE_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY"],
261
- deepseek: "DEEPSEEK_API_KEY",
262
- xai: "XAI_API_KEY",
263
- groq: "GROQ_API_KEY",
264
- cerebras: "CEREBRAS_API_KEY",
265
- mistral: "MISTRAL_API_KEY",
266
- openrouter: "OPENROUTER_API_KEY",
267
- zai: "ZHIPU_API_KEY",
268
- moonshotai: "MOONSHOT_API_KEY",
269
- together: "TOGETHER_API_KEY",
270
- "amazon-bedrock": "AWS_BEARER_TOKEN_BEDROCK",
271
- "azure-openai-responses": ["AZURE_OPENAI_API_KEY", "AZURE_API_KEY"],
272
- "google-vertex": "GOOGLE_CLOUD_API_KEY",
273
- "kimi-coding": ["KIMI_CODING_API_KEY", "KIMI_API_KEY"],
274
- "openai-codex": "OPENAI_CODEX_ACCESS_TOKEN",
275
- "xai-grok": "GROK_ACCESS_TOKEN",
276
- "ant-ling": "ANT_LING_API_KEY",
277
- "qwen-token-plan": "QWEN_TOKEN_PLAN_API_KEY",
278
- "qwen-token-plan-cn": "QWEN_TOKEN_PLAN_CN_API_KEY",
279
- "qwen-token-plan-individual": "QWEN_TOKEN_PLAN_API_KEY",
280
- "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
281
- "zai-coding-cn": "ZAI_CODING_CN_API_KEY",
282
- "fireworks": "FIREWORKS_API_KEY",
283
- "typesafe": "TYPESAFE_API_KEY",
284
- "radius": "RADIUS_API_KEY",
285
- "opencode-go": "OPENCODE_API_KEY",
286
- };
287
-
288
- export function envVarsOf(provider: string, additional: readonly string[] = []): readonly string[] {
289
- const v = ENV_VAR[provider];
290
- // A provider name is free text in a user catalog; an environment variable
291
- // is not. `kimi-coding` must become KIMI_CODING, or no shell can set it.
292
- const declared = [...additional, ...(SOURCE_PROVIDERS[provider]?.env ?? [])].filter(name => /(?:API_KEY|TOKEN|_KEY)$/.test(name)
293
- && !/(?:SECRET_ACCESS_KEY|ACCESS_KEY_ID|RESOURCE|PROJECT|LOCATION)/.test(name));
294
- const known = typeof v === "string" ? [v] : v ?? [];
295
- const names = [...new Set([...known, ...declared])];
296
- return names.length ? names : [`${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`];
297
- }
298
-
299
- export function envLabel(provider: string): string {
300
- return envVarsOf(provider).join(" or ");
301
- }
302
-
303
- /**
304
- * What to say when there is no credential.
305
- *
306
- * Both routes, named concretely. The failure a person hits on first run
307
- * should carry its own fix; sending them to read the docs for something this
308
- * small is the difference between trying the tool and closing it.
309
- */
310
- export function describeMissingCredential(provider: string): string {
311
- const subscription = provider === "openai-codex" ? "codex" : provider === GROK_PROVIDER ? "grok" : provider === "github-copilot" ? "github-copilot" : undefined;
312
- if (subscription) return `no credential for ${provider}. Run /provider ${subscription} to sign in, or export ${envLabel(provider)}`;
313
- return (
314
- `no credential for ${provider}. Run /provider ${provider} to add a key, ` +
315
- `or export ${envLabel(provider)}`
316
- );
317
- }
318
-
319
- export interface FreshEffects extends RefreshEffects {
320
- persist: (auth: AuthFile) => Promise<void>;
321
- }
322
-
323
- /**
324
- * Resolve, refreshing an expired OAuth access token first.
325
- *
326
- * A dead refresh fails here and names `/provider <provider>`. It does not fall
327
- * through to the environment: that is the same rule as a stored API key that
328
- * cannot resolve. Otherwise a forgotten export quietly starts billing a
329
- * different account.
330
- */
331
- export async function ensureFreshCredential(
332
- spec: ModelSpec,
333
- stored: AuthFile,
334
- flagKey: string | undefined,
335
- fx: ResolveEffects,
336
- oauth: FreshEffects,
337
- ): Promise<Resolved> {
338
- const configured = configuredEffects(spec, fx);
339
- if (!configured.ok) return configured;
340
- if (flagKey !== undefined && flagKey.length > 0) {
341
- return resolveCredential(spec, stored, flagKey, fx);
342
- }
343
- const entry = storedCredential(stored, spec.provider);
344
- if (entry !== undefined && entry.type === "oauth") {
345
- const resolved = resolveCredential(spec, stored, flagKey, fx);
346
- if (!resolved.ok) return resolved;
347
- if (oauth.now() < entry.expires) {
348
- return { ok: true, key: entry.access, source: "auth.json" };
349
- }
350
- const refreshOpts: RefreshEffects = {
351
- fetch: oauth.fetch,
352
- now: oauth.now,
353
- };
354
- if (oauth.signal !== undefined) refreshOpts.signal = oauth.signal;
355
- const refreshed = await refreshOAuthCredential(spec.provider, entry, refreshOpts);
356
- if (!refreshed.ok) {
357
- return {
358
- ok: false,
359
- reason: `${spec.provider} OAuth expired. Run /provider ${spec.provider} to sign in again.`,
360
- };
361
- }
362
- const next: AuthFile = { ...stored, [spec.provider]: refreshed.credential };
363
- await oauth.persist(next);
364
- return { ok: true, key: refreshed.credential.access, source: "auth.json" };
365
- }
366
- return resolveCredential(spec, stored, flagKey, fx);
367
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Running blind checks (feat-blind-checks): in a disposable clone of the
3
- * final workspace, with the blind files copied in under `.xeno-blind/`. The
4
- * files are verified against the digests frozen when the session began, so a
5
- * directory edited since then is reported rather than silently run.
6
- */
7
- import { cp, readFile } from "node:fs/promises";
8
- import { join } from "node:path";
9
- import type { BlindChecks } from "../core/blind-checks.ts";
10
- import { observeCommand } from "../store/command.ts";
11
- import { cloneWorkspace } from "../store/workspace-clone.ts";
12
- import { blobAddress } from "../store/blobs.ts";
13
-
14
- export type BlindCheckResult = { id: string; command: string } & ({ exit: number } | { reason: string });
15
-
16
- export async function runBlindChecks(input: { root: string; blind: BlindChecks; timeoutMs: number; maxOutputBytes: number;
17
- outputStore: string; signal?: AbortSignal }): Promise<BlindCheckResult[]> {
18
- const checks = input.blind.manifest.checks.map(check => ({ id: check.id, command: check.command }));
19
- const unavailable = (reason: string) => checks.map(check => ({ ...check, reason }));
20
- for (const [path, sha] of Object.entries(input.blind.files)) {
21
- const bytes = await readFile(join(input.blind.dir, path)).catch(() => null);
22
- if (bytes === null || blobAddress(bytes) !== sha) return unavailable(`blind check file ${path} changed or disappeared since the session began`);
23
- }
24
- const clone = await cloneWorkspace(input.root, input.signal);
25
- if (!clone.ok) return unavailable(clone.reason);
26
- try {
27
- await cp(input.blind.dir, join(clone.path, ".xeno-blind"), { recursive: true });
28
- const results: BlindCheckResult[] = [];
29
- for (const check of checks) {
30
- const captured = await observeCommand({ command: check.command, cwd: clone.path, outputStore: input.outputStore,
31
- maxOutputBytes: input.maxOutputBytes, timeoutMs: input.timeoutMs, ...(input.signal ? { signal: input.signal } : {}) });
32
- results.push(captured.stopped ? { ...check, reason: `the check did not finish (${captured.stopped})` } : { ...check, exit: captured.exit });
33
- }
34
- return results;
35
- } finally { await clone.dispose(); }
36
- }
@@ -1,36 +0,0 @@
1
- /** O.14: volatile ownership, never a replacement for the durable branch log. */
2
- export class BranchJobs<T> {
3
- private readonly jobs = new Map<number, { controller: AbortController; done: Promise<void>; value?: T; error?: string; detach(): void; ready: boolean }>();
4
-
5
- get active(): number { return [...this.jobs.values()].filter(job => !job.ready).length; }
6
- state(node: number): "running" | "ready" | undefined {
7
- const job = this.jobs.get(node); return job ? job.ready ? "ready" : "running" : undefined;
8
- }
9
- nodes(): number[] { return [...this.jobs.keys()]; }
10
- start(node: number, controller: AbortController, run: () => Promise<T>, detach: () => void): void {
11
- if (this.jobs.has(node)) throw new Error("branch already owned");
12
- const job: { controller: AbortController; done: Promise<void>; value?: T; error?: string; detach(): void; ready: boolean } = {
13
- controller, ready: false, detach, done: Promise.resolve(),
14
- };
15
- this.jobs.set(node, job);
16
- job.done = run().then(value => { job.value = value; }, () => { job.error = "branch finalization failed; inspect its original log"; })
17
- .finally(() => { job.ready = true; job.detach(); });
18
- }
19
- cancel(node?: number): void {
20
- for (const [id, job] of this.jobs) if (node === undefined || node === id) job.controller.abort();
21
- }
22
- async drain(node?: number): Promise<void> { await Promise.all([...this.jobs].filter(([id]) => node === undefined || node === id).map(([, job]) => job.done)); }
23
- async wait(node: number, ms: number): Promise<{ value?: T; error?: string; running: boolean } | undefined> {
24
- const job = this.jobs.get(node); if (!job) return undefined;
25
- if (!Number.isFinite(ms) || ms < 0 || ms > 60_000) throw new RangeError("wait_ms must be between 0 and 60000");
26
- let timer: ReturnType<typeof setTimeout> | undefined;
27
- try {
28
- if (!job.ready && ms > 0) await Promise.race([job.done, new Promise<void>(resolve => { timer = setTimeout(resolve, ms); })]);
29
- } finally { clearTimeout(timer); }
30
- return { ...(job.value !== undefined ? { value: job.value } : {}), ...(job.error ? { error: job.error } : {}), running: !job.ready };
31
- }
32
- forget(node: number): void {
33
- if (this.state(node) === "running") throw new Error("cannot forget a running branch");
34
- this.jobs.delete(node);
35
- }
36
- }