@visual-z/xeno 0.8.4 → 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 (350) hide show
  1. package/CHANGELOG.md +8 -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 -2071
  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 -1818
  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 -3108
  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/branch-panel.ts +0 -47
  312. package/src/tui/ui/canvas.ts +0 -96
  313. package/src/tui/ui/colors.ts +0 -70
  314. package/src/tui/ui/composer.ts +0 -199
  315. package/src/tui/ui/control-panel.ts +0 -25
  316. package/src/tui/ui/control-sidebar.ts +0 -49
  317. package/src/tui/ui/dialog.ts +0 -398
  318. package/src/tui/ui/draft.ts +0 -23
  319. package/src/tui/ui/duration.ts +0 -16
  320. package/src/tui/ui/edge.ts +0 -186
  321. package/src/tui/ui/image-size.ts +0 -44
  322. package/src/tui/ui/ink.ts +0 -210
  323. package/src/tui/ui/items.ts +0 -42
  324. package/src/tui/ui/keys.ts +0 -40
  325. package/src/tui/ui/layout.ts +0 -74
  326. package/src/tui/ui/list-model.ts +0 -86
  327. package/src/tui/ui/list-view.ts +0 -172
  328. package/src/tui/ui/mascot.ts +0 -67
  329. package/src/tui/ui/messages.ts +0 -518
  330. package/src/tui/ui/motion.ts +0 -28
  331. package/src/tui/ui/paths.ts +0 -10
  332. package/src/tui/ui/pictures.ts +0 -108
  333. package/src/tui/ui/prompt-history.ts +0 -60
  334. package/src/tui/ui/screen.ts +0 -579
  335. package/src/tui/ui/status.ts +0 -403
  336. package/src/tui/ui/styled.ts +0 -85
  337. package/src/tui/ui/tool-card.ts +0 -150
  338. package/src/tui/width.ts +0 -567
  339. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  340. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  341. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  342. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  343. package/src/vendor/pi-rich/html.ts +0 -51
  344. package/src/vendor/pi-rich/image-convert.ts +0 -49
  345. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  346. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  347. package/src/vendor/pi-rich/image-resize.ts +0 -123
  348. package/src/vendor/pi-rich/mime.ts +0 -116
  349. package/src/vendor/pi-rich/photon.ts +0 -139
  350. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,580 +0,0 @@
1
- /**
2
- * The Anthropic adapter, and the interface every adapter implements.
3
- *
4
- * Everything the core produces is provider-neutral by construction, so the
5
- * mapping lives here and nowhere else.
6
- *
7
- * `Provider` is an interface rather than a base class on purpose. Two adapters
8
- * share a contract, not an implementation -- there is nothing for a base class
9
- * to hold. Worse, a base class that constructs an Anthropic client drags the
10
- * SDK into every subclass, including test fakes that never make a request,
11
- * which is how an SDK dependency ends up in code that has nothing to do with
12
- * Anthropic.
13
- *
14
- * Three rules this file exists to honour, all of which would otherwise be
15
- * discovered as production failures:
16
- *
17
- * THINKING BLOCKS REPLAY VERBATIM. The API rejects blocks whose content was
18
- * modified, and a block whose text is empty still has to go back unchanged.
19
- * The core therefore carries the raw block opaquely and this file passes it
20
- * straight through -- it never reconstructs one from text.
21
- *
22
- * THE FROZEN PREFIX CARRIES THE CACHE BREAKPOINT. System and tools render at
23
- * the front of the prompt, so one explicit breakpoint on the system block
24
- * gives the expensive shared part a read point that survives whatever
25
- * happens later in messages. Top-level auto-caching then handles the growing
26
- * tail.
27
- *
28
- * EVERY RESPONSE IS MEASURED. Usage comes back on each call and is returned
29
- * alongside the message, because a cache that silently stopped working costs
30
- * money without ever raising an error.
31
- */
32
-
33
- import Anthropic from "@anthropic-ai/sdk";
34
- import type { Message, ContentBlock, FrozenPrefix } from "../core/project.ts";
35
- import { imageData } from "./images.ts";
36
- import type { AssistantBlock } from "../core/events.ts";
37
- import { assertNever } from "../core/exhaustive.ts";
38
- import { isToolStopWithoutCall } from "../core/response-recovery.ts";
39
- import type { UsageSample } from "./usage.ts";
40
- import { type Effort, type ModelSpec, CLAUDE_OPUS_5 } from "./model.ts";
41
- import { withRetry, retryLabel, DEFAULT_RETRY, type RetryPolicy } from "./retry.ts";
42
- import { ConnectionReuse } from "./connection-reuse.ts";
43
- import { watchIdle, type IdleWatch } from "./stream-idle.ts";
44
-
45
- import { anthropicOAuthHeaders } from "./oauth.ts";
46
- import { XENO_VERSION } from "./version.ts";
47
- import type { ProviderSendOptions } from "./provider-tool-policy.ts";
48
-
49
- export interface ToolSchema {
50
- name: string;
51
- description: string;
52
- input_schema: {
53
- /** Standard JSON Schema keywords from MCP must survive the frozen declaration. */
54
- [keyword: string]: unknown;
55
- type: "object";
56
- properties: Record<string, unknown>;
57
- required: string[];
58
- };
59
- }
60
-
61
- export interface ProviderConfig {
62
- /** Which model, and everything the runtime needs to know about it. */
63
- spec: ModelSpec;
64
- maxTokens: number;
65
- /** Controls thinking depth and spend. */
66
- effort: Effort;
67
- /**
68
- * Summarized thinking is opt-in: the default returns thinking blocks with
69
- * empty text, which to a user looks like a long pause before any output.
70
- */
71
- thinkingDisplay: "summarized" | "omitted";
72
- }
73
-
74
- export const DEFAULT_PROVIDER: ProviderConfig = {
75
- spec: CLAUDE_OPUS_5,
76
- maxTokens: 16_000,
77
- effort: "high",
78
- thinkingDisplay: "summarized",
79
- };
80
-
81
- /**
82
- * What the loop needs from a model, and the entire surface an adapter has to
83
- * implement. Anything wider would be an Anthropic concept leaking into the
84
- * contract; anything narrower could not report usage, which the whole design
85
- * rests on measuring.
86
- */
87
- /**
88
- * A display increment. Adapters emit these through `onDelta`; the loop
89
- * turns them into UI events. They never become log events -- a turn that
90
- * landed N deltas in the log would project as N assistant messages and
91
- * break prefix stability (P7).
92
- *
93
- * Shared by both adapters so the loop never sees an Anthropic or OpenAI
94
- * wire name. Discard is the same channel: a retry or a refusal has to
95
- * wipe what the user already saw, and inventing a second callback would
96
- * let one path forget.
97
- */
98
- export type StreamDelta =
99
- | { kind: "text"; text: string }
100
- | { kind: "thinking"; text: string }
101
- | { kind: "tool"; id: string; name: string; arguments: string }
102
- // `max` is the policy's budget and `reason` a few words from retryLabel;
103
- // optional so a view written against the older shape still renders.
104
- | { kind: "retry"; attempt: number; delayMs: number; max?: number; reason?: string }
105
- | { kind: "discard" };
106
-
107
- export type OnDelta = (delta: StreamDelta) => void;
108
-
109
- export interface Provider {
110
- readonly config: ProviderConfig;
111
- send(
112
- frozen: FrozenPrefix,
113
- messages: readonly Message[],
114
- node: number,
115
- signal?: AbortSignal,
116
- onDelta?: OnDelta,
117
- options?: ProviderSendOptions,
118
- ): Promise<Turn>;
119
- }
120
-
121
- /**
122
- * The SDK methods this adapter actually calls.
123
- *
124
- * Structural rather than `Anthropic` so a test can inject a fake without
125
- * constructing a client -- that would pull credentials and the SDK retry
126
- * loop, which is the opposite of what a fake is for. Tests cannot import
127
- * the SDK package (the boundary checker forbids package imports outside
128
- * runtime/apps).
129
- */
130
- /** The complete-message shape, also what a non-streaming test fake returns. */
131
- export interface AnthropicMessage {
132
- stop_reason: string | null;
133
- stop_details?: { category?: string | null; explanation?: string | null } | null;
134
- content: Anthropic.ContentBlock[];
135
- usage: {
136
- input_tokens: number;
137
- output_tokens: number;
138
- cache_creation_input_tokens?: number | null;
139
- cache_read_input_tokens?: number | null;
140
- };
141
- }
142
-
143
- export interface AnthropicTransport {
144
- messages: {
145
- create(
146
- params: Anthropic.MessageCreateParamsNonStreaming | Anthropic.MessageCreateParamsStreaming,
147
- options?: { signal?: AbortSignal | null },
148
- ): Promise<AnthropicMessage | AsyncIterable<unknown>>;
149
- };
150
- }
151
-
152
- export interface Turn {
153
- blocks: AssistantBlock[];
154
- stopReason: string | null;
155
- usage: UsageSample;
156
- }
157
-
158
- /** D11: a rejected response can still prove usage; arbitrary errors cannot prove a billable receipt. */
159
- export class FailedResponseError extends Error {
160
- readonly usage: UsageSample | undefined;
161
-
162
- constructor(message: string, usage?: UsageSample) {
163
- super(message);
164
- this.name = "FailedResponseError";
165
- this.usage = usage === undefined ? undefined : { ...usage };
166
- }
167
- }
168
-
169
- /**
170
- * A 200 response that contradicts itself, such as a tool stop with no tool
171
- * call. retry.ts treats it like a transient stream failure: a fresh sample is
172
- * the remedy, and exhausting retries must surface as a provider error rather
173
- * than reach the loop as a silent final answer. The output token count tells
174
- * "generated but lost in transit" apart from "nothing generated".
175
- */
176
- export class MalformedResponseError extends FailedResponseError {
177
- constructor(detail: string, usage?: UsageSample) {
178
- super(`malformed provider response: ${detail}${usage ? ` (${usage.output_tokens} output tokens)` : ""}`, usage);
179
- this.name = "MalformedResponseError";
180
- }
181
- }
182
-
183
- /** Every adapter returns through this, so no dialect can hand the loop a contradictory turn. */
184
- export function coherentTurn(turn: Turn, usageKnown = true): Turn {
185
- if (isToolStopWithoutCall(turn.stopReason, turn.blocks))
186
- throw new MalformedResponseError(`stop_reason ${JSON.stringify(turn.stopReason)} without a tool call`, usageKnown ? turn.usage : undefined);
187
- return turn;
188
- }
189
-
190
- /** Thrown for a refusal, which arrives as HTTP 200 rather than an error. */
191
- export class RefusalError extends FailedResponseError {
192
- readonly category: string | null;
193
- readonly explanation: string | null;
194
-
195
- constructor(category: string | null, explanation: string | null, usage?: UsageSample) {
196
- super(`model refused the request${category ? ` (${category})` : ""}`, usage);
197
- this.name = "RefusalError";
198
- this.category = category;
199
- this.explanation = explanation;
200
- }
201
- }
202
-
203
- function buildAnthropicClient(
204
- apiKey: string | undefined,
205
- options: { fetch?: typeof fetch; oauth?: boolean; baseUrl?: string },
206
- ): AnthropicTransport {
207
- // D18: the User-Agent, not SDK headers, identifies xeno on the wire. Left alone
208
- // the SDK sends `Anthropic/JS <version>`, which model gateways fold into "custom
209
- // client" attribution; provider-request.ts already stamps xeno's UA on the Pi
210
- // path. Constructor defaultHeaders merge after the SDK's built-in UA, so this
211
- // entry wins without touching per-request headers.
212
- const identity = { "User-Agent": `xeno/${XENO_VERSION}` };
213
- if (options.oauth === true && apiKey) {
214
- // authToken is Bearer. Passing apiKey here would send x-api-key, which
215
- // an OAuth token is not accepted as.
216
- return new Anthropic({
217
- maxRetries: 0,
218
- ...(options.baseUrl ? { baseURL: options.baseUrl } : {}),
219
- authToken: apiKey,
220
- defaultHeaders: { ...anthropicOAuthHeaders(), ...identity },
221
- ...(options.fetch ? { fetch: options.fetch } : {}),
222
- });
223
- }
224
- return new Anthropic({
225
- maxRetries: 0,
226
- ...(options.baseUrl ? { baseURL: options.baseUrl } : {}),
227
- ...(apiKey ? { apiKey } : {}),
228
- defaultHeaders: identity,
229
- ...(options.fetch ? { fetch: options.fetch } : {}),
230
- });
231
- }
232
-
233
- export class AnthropicProvider implements Provider {
234
- readonly config: ProviderConfig;
235
- private readonly client: AnthropicTransport;
236
- private readonly connections = new ConnectionReuse();
237
- private readonly streamIdleMs: number | undefined;
238
- private readonly retry: RetryPolicy;
239
- private readonly onRetry: ((attempt: number, delayMs: number, error: unknown) => void) | undefined;
240
-
241
- /**
242
- * `apiKey` comes from the resolution chain when there is one. Omitting it
243
- * falls back to the SDK's own lookup (environment, `ant auth login`
244
- * profile), which is what makes a machine already set up for Claude work
245
- * with no configuration at all.
246
- */
247
- constructor(
248
- config: ProviderConfig = DEFAULT_PROVIDER,
249
- apiKey?: string,
250
- options: {
251
- client?: AnthropicTransport;
252
- retry?: RetryPolicy;
253
- onRetry?: (attempt: number, delayMs: number, error: unknown) => void;
254
- fetch?: typeof fetch;
255
- /** OAuth access token: Bearer + oauth-2025-04-20, never x-api-key (D18). */
256
- oauth?: boolean;
257
- /** Silence after which one attempt is abandoned; tests shorten it (stream-idle.ts). */
258
- streamIdleMs?: number;
259
- } = {},
260
- ) {
261
- this.config = config;
262
- // maxRetries: 0 because this adapter retries. Leaving the SDK's own on
263
- // would multiply the two -- four attempts becoming sixteen -- and the
264
- // outer layer could neither see how long the inner one slept nor count
265
- // it, so the usage report would quietly stop adding up. The SDK timer
266
- // also ignores AbortSignal, so an outer wait is the only one Escape
267
- // can actually cancel.
268
- this.client = options.client ?? buildAnthropicClient(apiKey, { ...options, fetch: this.connections.wrap(options.fetch ?? fetch),
269
- ...(config.spec.baseUrl ? { baseUrl: config.spec.baseUrl } : {}) });
270
- this.retry = options.retry ?? DEFAULT_RETRY;
271
- this.onRetry = options.onRetry;
272
- this.streamIdleMs = options.streamIdleMs;
273
- }
274
-
275
- send(
276
- frozen: FrozenPrefix,
277
- messages: readonly Message[],
278
- node: number,
279
- signal?: AbortSignal,
280
- onDelta?: OnDelta,
281
- options?: ProviderSendOptions,
282
- ): Promise<Turn> {
283
- return withRetry(
284
- () => watchIdle(signal, this.streamIdleMs, idle => this.attempt(frozen, messages, node, idle, onDelta, options)).catch((error: unknown) => { this.connections.note(error); throw error; }),
285
- this.retry,
286
- undefined,
287
- (attempt, delayMs, error) => {
288
- // The next attempt starts from empty. Without this the UI concatenates
289
- // two openings of the same sentence and has no sign a retry happened.
290
- onDelta?.({ kind: "discard" });
291
- onDelta?.({ kind: "retry", attempt, delayMs, max: this.retry.maxRetries, reason: retryLabel(error) });
292
- this.onRetry?.(attempt, delayMs, error);
293
- },
294
- signal,
295
- );
296
- }
297
-
298
- private async attempt(
299
- frozen: FrozenPrefix,
300
- messages: readonly Message[],
301
- node: number,
302
- idle: IdleWatch,
303
- onDelta?: OnDelta,
304
- options?: ProviderSendOptions,
305
- ): Promise<Turn> {
306
- const started = Date.now();
307
-
308
- const response = await this.client.messages.create(
309
- {
310
- model: this.config.spec.id,
311
- max_tokens: this.config.maxTokens,
312
- stream: true,
313
- // One explicit breakpoint on the frozen prefix. It renders before
314
- // messages, so this read point holds regardless of how the tail grows.
315
- system: [
316
- {
317
- type: "text",
318
- text: frozen.system,
319
- cache_control: { type: "ephemeral" },
320
- },
321
- ],
322
- // Auto-caching walks the breakpoint forward over the growing tail.
323
- cache_control: { type: "ephemeral" },
324
- tools: frozen.tools as Anthropic.Tool[],
325
- ...(options?.toolUse === "disabled" ? { tool_choice: { type: "none" as const } } : {}),
326
- // Only a reasoning model understands these. An older Claude, or an
327
- // Anthropic-compatible endpoint serving some other model, answers
328
- // them with a 400 or silently ignores them.
329
- ...(this.config.spec.reasoning
330
- ? this.config.effort === "off" ? { thinking: { type: "disabled" as const } }
331
- : { thinking: { type: "adaptive" as const, display: this.config.thinkingDisplay },
332
- output_config: { effort: anthropicEffort(this.config) } }
333
- : {}),
334
- messages: messages.map(toWireMessage),
335
- },
336
- { signal: idle.signal },
337
- );
338
-
339
- const assembled = isAsyncIterable(response)
340
- ? await consumeAnthropicStream(idle.events(response), onDelta)
341
- : {
342
- blocks: response.content.map(fromWireBlock),
343
- stopReason: response.stop_reason,
344
- stopDetails: response.stop_details ?? null,
345
- usageKnown: true,
346
- usage: {
347
- input_tokens: response.usage.input_tokens,
348
- output_tokens: response.usage.output_tokens,
349
- cache_creation_input_tokens: response.usage.cache_creation_input_tokens ?? 0,
350
- cache_read_input_tokens: response.usage.cache_read_input_tokens ?? 0,
351
- },
352
- };
353
- const usage = { ...assembled.usage, node, ms: Date.now() - started };
354
-
355
- // A refusal is HTTP 200 with stop_reason "refusal", so content must not be
356
- // trusted after it. Anything already painted has to go with it.
357
- if (assembled.stopReason === "refusal") {
358
- onDelta?.({ kind: "discard" });
359
- const details = assembled.stopDetails;
360
- throw new RefusalError(details?.category ?? null, details?.explanation ?? null, assembled.usageKnown ? usage : undefined);
361
- }
362
-
363
- return coherentTurn({
364
- blocks: assembled.blocks,
365
- stopReason: assembled.stopReason,
366
- usage,
367
- }, assembled.usageKnown);
368
- }
369
- }
370
-
371
- /** Pi maps minimal to low for adaptive thinking; off is a thinking switch, never an effort string. */
372
- function anthropicEffort(config: ProviderConfig): "low" | "medium" | "high" | "xhigh" | "max" {
373
- const level = config.spec.thinkingLevelMap?.[config.effort] ?? config.effort;
374
- return level === "minimal" || level === "off" ? "low" :
375
- level === "low" || level === "medium" || level === "high" || level === "xhigh" || level === "max" ? level : "high";
376
- }
377
-
378
- function isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {
379
- return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
380
- }
381
-
382
- interface AssembledTurn {
383
- blocks: AssistantBlock[];
384
- stopReason: string | null;
385
- stopDetails: { category?: string | null; explanation?: string | null } | null;
386
- /** Provider receipt presence distinguishes a measured zero from absent usage (D11). */
387
- usageKnown: boolean;
388
- usage: {
389
- input_tokens: number;
390
- output_tokens: number;
391
- cache_creation_input_tokens: number;
392
- cache_read_input_tokens: number;
393
- };
394
- }
395
-
396
- interface UsageFields {
397
- input_tokens?: number | null;
398
- output_tokens?: number | null;
399
- cache_creation_input_tokens?: number | null;
400
- cache_read_input_tokens?: number | null;
401
- }
402
-
403
- function takeUsage(
404
- into: AssembledTurn["usage"],
405
- raw: UsageFields | undefined,
406
- ): void {
407
- if (raw === undefined) return;
408
- if (raw.input_tokens != null) into.input_tokens = raw.input_tokens;
409
- if (raw.output_tokens != null) into.output_tokens = raw.output_tokens;
410
- if (raw.cache_creation_input_tokens != null) {
411
- into.cache_creation_input_tokens = raw.cache_creation_input_tokens;
412
- }
413
- if (raw.cache_read_input_tokens != null) {
414
- into.cache_read_input_tokens = raw.cache_read_input_tokens;
415
- }
416
- }
417
-
418
- function parseToolInput(raw: string): unknown {
419
- if (raw.length === 0) return {};
420
- try {
421
- return JSON.parse(raw);
422
- } catch {
423
- return { __unparsed_arguments: raw };
424
- }
425
- }
426
-
427
- /**
428
- * Walks the SDK stream (or a test fake of the same events) into a Turn.
429
- *
430
- * Usage is the sum of message_start (input / cache) and message_delta
431
- * (output, and any field a proxy omitted at start). Deltas go to onDelta
432
- * as they arrive so the loop can paint before this function returns.
433
- */
434
- async function consumeAnthropicStream(
435
- stream: AsyncIterable<unknown>,
436
- onDelta: OnDelta | undefined,
437
- ): Promise<AssembledTurn> {
438
- const content: Anthropic.ContentBlock[] = [];
439
- const jsonBuf = new Map<number, string>();
440
- const assembled: AssembledTurn = {
441
- blocks: [],
442
- stopReason: null,
443
- stopDetails: null,
444
- usageKnown: false,
445
- usage: {
446
- input_tokens: 0,
447
- output_tokens: 0,
448
- cache_creation_input_tokens: 0,
449
- cache_read_input_tokens: 0,
450
- },
451
- };
452
-
453
- for await (const raw of stream) {
454
- const event = raw as {
455
- type?: string;
456
- index?: number;
457
- message?: { usage?: UsageFields };
458
- content_block?: Anthropic.ContentBlock;
459
- delta?: {
460
- type?: string;
461
- text?: string;
462
- thinking?: string;
463
- signature?: string;
464
- partial_json?: string;
465
- stop_reason?: string | null;
466
- stop_details?: { category?: string | null; explanation?: string | null } | null;
467
- };
468
- usage?: UsageFields;
469
- };
470
-
471
- switch (event.type) {
472
- case "message_start":
473
- takeUsage(assembled.usage, event.message?.usage);
474
- if (event.message?.usage !== undefined) assembled.usageKnown = true;
475
- break;
476
- case "content_block_start": {
477
- const index = event.index ?? content.length;
478
- if (event.content_block !== undefined) {
479
- content[index] = { ...event.content_block } as Anthropic.ContentBlock;
480
- if (event.content_block.type === "tool_use") {
481
- jsonBuf.set(index, "");
482
- onDelta?.({ kind: "tool", id: event.content_block.id, name: event.content_block.name, arguments: "" });
483
- }
484
- }
485
- break;
486
- }
487
- case "content_block_delta": {
488
- const index = event.index ?? 0;
489
- const delta = event.delta;
490
- const block = content[index];
491
- if (delta === undefined || block === undefined) break;
492
- if (delta.type === "text_delta" && delta.text) {
493
- if (block.type === "text") block.text += delta.text;
494
- onDelta?.({ kind: "text", text: delta.text });
495
- } else if (delta.type === "thinking_delta" && delta.thinking) {
496
- if (block.type === "thinking") block.thinking += delta.thinking;
497
- onDelta?.({ kind: "thinking", text: delta.thinking });
498
- } else if (delta.type === "signature_delta" && delta.signature && block.type === "thinking") {
499
- block.signature = (block.signature ?? "") + delta.signature;
500
- } else if (delta.type === "input_json_delta" && delta.partial_json) {
501
- jsonBuf.set(index, (jsonBuf.get(index) ?? "") + delta.partial_json);
502
- if (block.type === "tool_use") onDelta?.({ kind: "tool", id: block.id, name: block.name, arguments: jsonBuf.get(index)! });
503
- }
504
- break;
505
- }
506
- case "content_block_stop": {
507
- const index = event.index ?? 0;
508
- const block = content[index];
509
- if (block?.type === "tool_use") {
510
- block.input = parseToolInput(jsonBuf.get(index) ?? "");
511
- }
512
- break;
513
- }
514
- case "message_delta": {
515
- if (event.delta?.stop_reason) assembled.stopReason = event.delta.stop_reason;
516
- if (event.delta?.stop_details !== undefined) {
517
- assembled.stopDetails = event.delta.stop_details;
518
- }
519
- takeUsage(assembled.usage, event.usage);
520
- if (event.usage !== undefined) assembled.usageKnown = true;
521
- break;
522
- }
523
- default:
524
- break;
525
- }
526
- }
527
-
528
- assembled.blocks = content.filter((b) => b !== undefined).map(fromWireBlock);
529
- return assembled;
530
- }
531
-
532
- function toWireMessage(m: Message): Anthropic.MessageParam {
533
- return {
534
- role: m.role,
535
- // Anthropic requires all parallel tool results before any text or image in the user message.
536
- content: [...m.content.filter(b => b.type === "tool_result"), ...m.content.filter(b => b.type !== "tool_result")].map(toWireBlock) as Anthropic.ContentBlockParam[],
537
- };
538
- }
539
-
540
- function toWireBlock(b: ContentBlock): unknown {
541
- switch (b.type) {
542
- case "image": return { type: "image", source: { type: "base64", media_type: b.image.mimeType, data: imageData(b) } };
543
- case "text":
544
- return { type: "text", text: b.text };
545
- case "thinking":
546
- // Verbatim. Reconstructing the block would drop its signature and the
547
- // next request would be rejected.
548
- return b.raw;
549
- case "tool_use":
550
- return { type: "tool_use", id: b.id, name: b.name, input: b.input };
551
- case "tool_result":
552
- return {
553
- type: "tool_result",
554
- tool_use_id: b.tool_use_id,
555
- content: b.content,
556
- ...(b.is_error ? { is_error: true } : {}),
557
- };
558
- default:
559
- return assertNever(b, "toWireBlock");
560
- }
561
- }
562
-
563
- function fromWireBlock(b: Anthropic.ContentBlock): AssistantBlock {
564
- switch (b.type) {
565
- case "text":
566
- return { type: "text", text: b.text };
567
- case "thinking":
568
- // `raw` is what goes back to the API; `text` is only for display.
569
- return { type: "thinking", text: b.thinking, raw: b };
570
- case "redacted_thinking":
571
- return { type: "thinking", text: "", raw: b };
572
- case "tool_use":
573
- return { type: "tool_use", call_id: b.id, name: b.name, input: b.input };
574
- default:
575
- // Server-tool blocks land here. They are kept verbatim as thinking-like
576
- // opaque content so a replay stays byte-identical, rather than being
577
- // dropped and breaking the turn.
578
- return { type: "thinking", text: "", raw: b };
579
- }
580
- }
@@ -1,62 +0,0 @@
1
- /** R13: UI and protocol clients share one active-turn question adapter. */
2
- import { parseQuestions, questionRequest, validateQuestionReply, MAX_QUESTIONS, MAX_QUESTION_CHOICES,
3
- type QuestionRequest, type QuestionReply } from "../core/question.ts";
4
- import { failureDiagnostic, NOT_STARTED } from "../core/tool-failure.ts";
5
- import { executionOutcome } from "../core/outcome.ts";
6
- import type { JsonValue, ToolDiagnostic } from "../core/tool-contract.ts";
7
- import type { ToolContext, ToolDefinition, ToolResult } from "./tools.ts";
8
-
9
- export type QuestionAdapter = (request: QuestionRequest, signal: AbortSignal) => Promise<unknown | undefined>;
10
- export interface QuestionSlot {
11
- /** The Session/app owner changes this at user-turn boundaries, never model input. */
12
- activeTurn?: string;
13
- current?: QuestionAdapter;
14
- }
15
- function failure(code: ToolDiagnostic["code"], message: string): ToolResult {
16
- return { ok: false, content: message, diagnostic: failureDiagnostic(code, message, NOT_STARTED),
17
- outcome: executionOutcome(code === "cancelled" ? "cancelled" : "tool_failed") };
18
- }
19
- function plainReply(reply: QuestionReply): JsonValue {
20
- return { request_id: reply.request_id, turn_id: reply.turn_id,
21
- answers: reply.answers.map(answer => ({ id: answer.id, choices: [...answer.choices], ...(answer.text !== undefined ? { text: answer.text } : {}) })) };
22
- }
23
- async function ask(slot: QuestionSlot, ctx: ToolContext, input: Record<string, unknown>): Promise<ToolResult> {
24
- let questions;
25
- try { questions = parseQuestions(input); }
26
- catch (error) { return failure("invalid_input", error instanceof Error ? error.message : "Invalid question"); }
27
- const turn = slot.activeTurn, call = ctx.callId, adapter = slot.current;
28
- if (!turn || !call) return failure("permission_denied", "question requires an active turn and tool invocation");
29
- if (!adapter) return failure("environment_missing", "question requires an interactive terminal or a connected client that can answer questions");
30
- const signal = ctx.signal ?? new AbortController().signal;
31
- if (signal.aborted) return failure("cancelled", "Question cancelled");
32
- const request = questionRequest(turn, call, questions);
33
- let stop = () => {};
34
- const cancelled = new Promise<undefined>(resolve => { stop = () => resolve(undefined); signal.addEventListener("abort", stop, { once: true }); });
35
- try {
36
- // Late adapter rejection is observed by race; an adapter ignoring its
37
- // signal cannot keep the original tool exchange unpaired (D5).
38
- const answer = await Promise.race([Promise.resolve().then(() => adapter(structuredClone(request), signal)), cancelled]);
39
- if (signal.aborted || answer === undefined || slot.activeTurn !== turn) return failure("cancelled", "Question cancelled or its turn is no longer active");
40
- let reply;
41
- try { reply = validateQuestionReply(request, answer); }
42
- catch { return failure("invalid_input", "Question client returned an invalid answer or stale invocation identity"); }
43
- const data = plainReply(reply);
44
- return { ok: true, content: JSON.stringify(data), data, outcome: executionOutcome("tool_completed") };
45
- } catch { return failure("execution_failed", "Question client failed before returning a validated answer"); }
46
- finally { signal.removeEventListener("abort", stop); }
47
- }
48
-
49
- export function createQuestionTool(slot: QuestionSlot): ToolDefinition {
50
- return { name: "question", access: "state", execution: "exclusive", branch: "none",
51
- description: "Ask 1..3 concise questions for missing requirements or material decisions; await the user's answer in this turn. Use stable question and option ids. Set multiple=true for multiple choices; custom text is enabled by default. An absent/cancelled answer is not approval. This tool does not approve tool execution or change workflow mode.",
52
- input_schema: { type: "object", required: ["questions"], properties: {
53
- questions: { type: "array", minItems: 1, maxItems: MAX_QUESTIONS, items: { type: "object", additionalProperties: false,
54
- required: ["id", "header", "question"], properties: {
55
- id: { type: "string", pattern: "^[a-z][a-z0-9_-]{0,63}$" }, header: { type: "string", minLength: 1, maxLength: 40 },
56
- question: { type: "string", minLength: 1, maxLength: 2000 }, multiple: { type: "boolean" }, custom: { type: "boolean" },
57
- options: { type: "array", maxItems: MAX_QUESTION_CHOICES, items: { type: "object", additionalProperties: false,
58
- required: ["id", "label"], properties: { id: { type: "string", pattern: "^[a-z][a-z0-9_-]{0,63}$" },
59
- label: { type: "string", minLength: 1, maxLength: 120 }, description: { type: "string", minLength: 1, maxLength: 600 } } } },
60
- } } },
61
- } }, run: (ctx, input) => ask(slot, ctx, input) };
62
- }