@visual-z/xeno 0.8.4 → 0.9.1

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 +19 -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
package/src/core/theme.ts DELETED
@@ -1,166 +0,0 @@
1
- /**
2
- * Theme schema and validation. Pure: a file is read elsewhere, this module
3
- * only turns JSON into a complete Theme plus named problems.
4
- *
5
- * A broken theme must still produce a usable Theme (the dark builtin).
6
- * Throwing here would take the agent down the moment the user edited a
7
- * comma, which is when they most need it (feat-theming design).
8
- */
9
-
10
- export const THEME_TOKENS = [
11
- "text",
12
- "thinkingText",
13
- "toolTitle",
14
- "toolOutput",
15
- "error",
16
- "toolDiffAdded",
17
- "toolDiffRemoved",
18
- "status",
19
- "prompt",
20
- "selectedBg",
21
- "muted",
22
- "warning",
23
- "mdHeading",
24
- "mdCode",
25
- "mdLink",
26
- "userMessageBg",
27
- "toolPendingBg",
28
- "toolSuccessBg",
29
- "toolErrorBg",
30
- "thinkingLow",
31
- "thinkingMedium",
32
- "thinkingHigh",
33
- "thinkingXhigh",
34
- "thinkingMax",
35
- ] as const;
36
-
37
- export type ThemeToken = (typeof THEME_TOKENS)[number];
38
-
39
- export type ThemeColors = { readonly [K in ThemeToken]: string } & Readonly<Record<string, string>>;
40
- const PI_THEME_TOKENS: readonly string[] = ["background","accent","border","borderAccent","borderMuted","success","error","warning","muted","dim","text","thinkingText","scrollbarTrack","scrollbarThumb","searchMatchText","userMessageText","customMessageText","customMessageLabel","toolTitle","toolOutput","mdHeading","mdLink","mdLinkUrl","mdCode","mdCodeBlock","mdCodeBlockBorder","mdQuote","mdQuoteBorder","mdHr","mdListBullet","toolDiffAdded","toolDiffRemoved","toolDiffContext","syntaxComment","syntaxKeyword","syntaxFunction","syntaxVariable","syntaxString","syntaxNumber","syntaxType","syntaxOperator","syntaxPunctuation","thinkingOff","thinkingMinimal","thinkingLow","thinkingMedium","thinkingHigh","thinkingXhigh","thinkingMax","bashMode","selectedBg","searchMatchBg","userMessageBg","customMessageBg","toolPendingBg","toolSuccessBg","toolErrorBg"];
41
-
42
- export interface Theme {
43
- readonly name: string;
44
- readonly colors: ThemeColors;
45
- }
46
-
47
- export interface ThemeParse {
48
- readonly theme: Theme;
49
- readonly diagnostics: readonly string[];
50
- }
51
-
52
- /**
53
- * Built-in appearances own no colours of their own: every token is one of the
54
- * terminal's 16 slots or its default (empty), and no token paints a
55
- * background (docs/tasks/refactor-tui-terminal-colors.md). The interactive
56
- * view derives finer greys from the terminal's reported palette; these
57
- * tokens are what line mode and an unreporting terminal get.
58
- *
59
- * `dark` and `light` differ only in which half of the palette stays legible:
60
- * bright slots on a dark ground, normal slots on a light one. Muted text is a
61
- * grey slot, never SGR faint (faint made muted arguments unreadable).
62
- */
63
- const DARK_COLORS: ThemeColors = terminalTokens({ muted: "37", red: "91", green: "92", yellow: "93", blue: "94", magenta: "95", cyan: "96", rule: "90" });
64
- const LIGHT_COLORS: ThemeColors = terminalTokens({ muted: "90", red: "31", green: "32", yellow: "33", blue: "34", magenta: "35", cyan: "36", rule: "37" });
65
-
66
- function terminalTokens(slot: { muted: string; red: string; green: string; yellow: string; blue: string; magenta: string; cyan: string; rule: string }): ThemeColors {
67
- return {
68
- text: "", prompt: "", background: "",
69
- thinkingText: slot.magenta, toolTitle: "", toolOutput: slot.muted,
70
- error: slot.red, toolDiffAdded: slot.green, toolDiffRemoved: slot.red,
71
- status: slot.muted, muted: slot.muted, dim: slot.muted, warning: slot.yellow, success: slot.green, accent: slot.blue,
72
- mdHeading: slot.blue, mdCode: slot.cyan, mdLink: slot.blue,
73
- selectedBg: "", userMessageBg: "", toolPendingBg: "", toolSuccessBg: "", toolErrorBg: "",
74
- border: slot.rule, borderMuted: slot.rule,
75
- thinkingOff: slot.muted, thinkingMinimal: slot.magenta, thinkingLow: slot.magenta, thinkingMedium: slot.magenta,
76
- thinkingHigh: slot.magenta, thinkingXhigh: slot.magenta, thinkingMax: slot.magenta,
77
- syntaxKeyword: slot.magenta, syntaxString: slot.green, syntaxNumber: slot.yellow, syntaxFunction: slot.blue, syntaxComment: slot.muted,
78
- };
79
- }
80
-
81
- export function builtinTheme(name: "dark" | "light"): Theme {
82
- return name === "light"
83
- ? { name: "light", colors: LIGHT_COLORS }
84
- : { name: "dark", colors: DARK_COLORS };
85
- }
86
-
87
- /**
88
- * A built-in appearance (dark, light, or system/auto, which reuse them) as
89
- * opposed to a theme file. Identity, not name: a file may call itself "dark",
90
- * and parseTheme always copies colours into a new object.
91
- */
92
- export function isBuiltinTheme(theme: Theme): boolean {
93
- return theme.colors === DARK_COLORS || theme.colors === LIGHT_COLORS;
94
- }
95
-
96
- export function parseTheme(json: unknown): ThemeParse {
97
- const fallback = builtinTheme("dark");
98
- if (typeof json !== "object" || json === null || Array.isArray(json)) {
99
- return {
100
- theme: fallback,
101
- diagnostics: ["theme is not an object; using dark"],
102
- };
103
- }
104
- const rec = json as { readonly [k: string]: unknown };
105
- const diagnostics: string[] = [];
106
- const name = typeof rec["name"] === "string" && rec["name"].length > 0 ? rec["name"] : "custom";
107
- const rawColors = rec["colors"];
108
- const src: { readonly [k: string]: unknown } =
109
- typeof rawColors === "object" && rawColors !== null && !Array.isArray(rawColors)
110
- ? (rawColors as { readonly [k: string]: unknown })
111
- : (() => {
112
- diagnostics.push("theme.colors is missing or not an object; filling from dark");
113
- return {};
114
- })();
115
-
116
- for (const key of Object.keys(src)) {
117
- if (!isThemeToken(key) && !PI_THEME_TOKENS.includes(key)) diagnostics.push(`unknown token: ${key}`);
118
- }
119
-
120
- const colors: { [K in ThemeToken]: string } & Record<string, string> = { ...fallback.colors };
121
- const vars = typeof rec["vars"] === "object" && rec["vars"] !== null ? rec["vars"] as Record<string, unknown> : {};
122
- const resolve = (value: unknown, seen = new Set<string>()): unknown => {
123
- if (typeof value !== "string" || !(value in vars)) return value;
124
- if (seen.has(value)) return null;
125
- return resolve(vars[value], new Set([...seen, value]));
126
- };
127
- for (const token of new Set<string>([...THEME_TOKENS, ...PI_THEME_TOKENS])) {
128
- if (!(token in src)) continue;
129
- const parsed = parseColor(resolve(src[token]));
130
- if (parsed === null) {
131
- diagnostics.push(`invalid colour for ${token}; using built-in`);
132
- continue;
133
- }
134
- colors[token] = (token.endsWith("Bg") || token === "background") ? parsed.replace(/^38;/, "48;") : parsed;
135
- }
136
-
137
- if (!("status" in src) && "dim" in colors) colors.status = colors["dim"]!;
138
- if (!("prompt" in src)) colors.prompt = colors.text;
139
- return { theme: { name, colors }, diagnostics };
140
- }
141
-
142
- function isThemeToken(key: string): key is ThemeToken {
143
- return (THEME_TOKENS as readonly string[]).includes(key);
144
- }
145
-
146
- /**
147
- * Accept SGR params (`31`, `38;5;180`, `38;2;r;g;b`), `#RRGGBB`, a 256-colour
148
- * index, or empty (terminal default). Anything else is invalid.
149
- */
150
- export function parseColor(value: unknown): string | null {
151
- if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 255) {
152
- return `38;5;${value}`;
153
- }
154
- if (typeof value !== "string") return null;
155
- if (value === "") return "";
156
- const hex = /^#([0-9a-fA-F]{6})$/.exec(value);
157
- if (hex) {
158
- const n = Number.parseInt(hex[1]!, 16);
159
- const r = (n >> 16) & 255;
160
- const g = (n >> 8) & 255;
161
- const b = n & 255;
162
- return `38;2;${r};${g};${b}`;
163
- }
164
- if (/^[0-9]+(?:;[0-9]+)*$/.test(value)) return value;
165
- return null;
166
- }
@@ -1,26 +0,0 @@
1
- /** Ported from Pi (MIT), ai/models.ts at 02eed88. Capability policy, independent of provider I/O. */
2
- export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
3
- export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
4
- export interface ThinkingModel {
5
- readonly reasoning: boolean;
6
- readonly thinkingLevelMap?: Partial<Record<ThinkingLevel, string | null>>;
7
- }
8
- export function asThinkingLevel(value: unknown): ThinkingLevel | undefined {
9
- return typeof value === "string" && (THINKING_LEVELS as readonly string[]).includes(value) ? value as ThinkingLevel : undefined;
10
- }
11
- export function supportedThinkingLevels(model: ThinkingModel): ThinkingLevel[] {
12
- if (!model.reasoning) return ["off"];
13
- return THINKING_LEVELS.filter(level => {
14
- const mapped = model.thinkingLevelMap?.[level];
15
- if (mapped === null) return false;
16
- return level === "xhigh" || level === "max" ? mapped !== undefined : true;
17
- });
18
- }
19
- export function clampThinkingLevel(model: ThinkingModel, requested: ThinkingLevel): ThinkingLevel {
20
- const available = supportedThinkingLevels(model);
21
- const index = THINKING_LEVELS.indexOf(requested);
22
- for (const level of [...THINKING_LEVELS.slice(index), ...THINKING_LEVELS.slice(0, index).reverse()]) {
23
- if (available.includes(level)) return level;
24
- }
25
- return "off";
26
- }
@@ -1,151 +0,0 @@
1
- /**
2
- * Token accounting.
3
- *
4
- * The default here is an ESTIMATE and is labelled as one. Real counting needs the
5
- * provider's own counter (Anthropic's `messages.count_tokens`); a bundled
6
- * tokenizer would drift from whatever model is actually in use and would be wrong
7
- * in a way nobody notices. The boundary logic therefore takes a counter as a
8
- * parameter, so wiring the real one in is a one-line change and nothing in the
9
- * core pretends to be authoritative.
10
- */
11
-
12
- import type { Message } from "./project.ts";
13
- import { estimateImageTokens } from "./images.ts";
14
-
15
- export interface TokenCounter {
16
- readonly kind: "estimate" | "exact";
17
- /**
18
- * Counts everything the API bills as prompt: tool schemas, the system
19
- * prompt and the messages.
20
- *
21
- * Tools are a parameter because leaving them out made two sides of a
22
- * boundary decision incomparable -- one came from the provider's billed
23
- * figure, which includes schemas, the other from a count that silently
24
- * skipped them, and the difference read as work the node had done.
25
- */
26
- count(messages: readonly Message[], system: string, tools?: readonly unknown[]): number;
27
- }
28
-
29
- /**
30
- * Estimated tokens for one string.
31
- *
32
- * ASCII keeps the original chars/3.6: code and JSON tokenize more densely than
33
- * prose, so this sits below the usual chars/4 rule, and every measured cut
34
- * point so far was taken with it. Ideographic scripts are a different
35
- * regime -- one Han character is roughly 0.6-1.5 tokens across current
36
- * tokenizers, where chars/3.6 would say 0.28 -- so they are charged a full
37
- * token each. Overestimating only moves a cut earlier; underestimating is a
38
- * 400 from the provider. Other non-ASCII (accents, symbols, emoji) sits
39
- * between the two at half a token per code point.
40
- */
41
- export function estimateText(text: string): number {
42
- let ascii = 0;
43
- let weighted = 0;
44
- for (let i = 0; i < text.length; i++) {
45
- const code = text.charCodeAt(i);
46
- if (code < 0x80) { ascii++; continue; }
47
- // A surrogate pair is one code point; charge it once, at its high half.
48
- if (code >= 0xdc00 && code <= 0xdfff) continue;
49
- const ideographic =
50
- (code >= 0x2e80 && code <= 0x9fff) || // CJK radicals .. unified ideographs, kana, bopomofo
51
- (code >= 0xac00 && code <= 0xd7af) || // Hangul syllables
52
- (code >= 0xf900 && code <= 0xfaff) || // CJK compatibility ideographs
53
- (code >= 0xff00 && code <= 0xffef); // full-width forms
54
- weighted += ideographic ? 1 : 0.5;
55
- }
56
- return ascii / 3.6 + weighted;
57
- }
58
-
59
- /**
60
- * estimateText across message and system text, plus serialized schemas and
61
- * per-message framing. Expect roughly +/-20% on ASCII; treat the trigger
62
- * threshold as approximate until an exact counter is wired in.
63
- */
64
- export const estimateTokens: TokenCounter = {
65
- kind: "estimate",
66
- count(messages, system, tools = []) {
67
- // Serialized schemas are what goes on the wire, so their length is the
68
- // right proxy -- and they are billed on every single request.
69
- let tokens = estimateText(system) + (tools.length > 0 ? estimateText(JSON.stringify(tools)) : 0);
70
- for (const m of messages) {
71
- for (const b of m.content) {
72
- if (b.type === "text") tokens += estimateText(b.text);
73
- // Thinking is opaque here; its serialized size is the best proxy.
74
- else if (b.type === "thinking") tokens += estimateText(JSON.stringify(b.raw));
75
- else if (b.type === "tool_use") tokens += estimateText(b.name) + estimateText(JSON.stringify(b.input));
76
- else if (b.type === "tool_result") tokens += estimateText(b.content);
77
- else if (b.type === "image") tokens += estimateImageTokens(b.image);
78
- }
79
- // Per-message framing overhead, roughly.
80
- tokens += 8 / 3.6;
81
- }
82
- return Math.ceil(tokens);
83
- },
84
- };
85
-
86
- /**
87
- * A counter whose every count is multiplied by `factor`.
88
- *
89
- * Used once the provider has rejected a prompt the estimate said would fit:
90
- * that is a measurement that this content tokenizes `factor` times denser
91
- * than estimated, and sizing the retained context with the unscaled counter
92
- * would reproduce the overflow.
93
- */
94
- export function scaledCounter(counter: TokenCounter, factor: number): TokenCounter {
95
- return { kind: "estimate", count: (messages, system, tools) => Math.ceil(counter.count(messages, system, tools) * factor) };
96
- }
97
-
98
- /**
99
- * An anchor: the prompt size a provider actually charged, and how far into the
100
- * conversation that measurement covered.
101
- *
102
- * Measuring the estimator against a real tokenizer showed it running between
103
- * -21% and +38% across realistic content -- prose, source, JSON and YAML all
104
- * tokenize at different densities, so no single chars-per-token constant fits
105
- * them at once. Underestimating source by a fifth is the dangerous direction:
106
- * a 30k trigger would fire at an actual 38k.
107
- *
108
- * Calibrating the constant would not fix that, because the error depends on
109
- * what the content happens to be. Anchoring does: every response reports the
110
- * prompt it was actually billed for, so everything up to that point is known
111
- * exactly and only the delta since needs estimating. The error then scales
112
- * with one turn instead of with the whole conversation.
113
- */
114
- export interface Anchor {
115
- /** Sum of uncached, cache-write and cache-read input tokens. */
116
- promptTokens: number;
117
- /** Tokens the model generated, which become part of the next prompt. */
118
- outputTokens: number;
119
- /** Number of messages the measurement covered. */
120
- messageCount: number;
121
- }
122
-
123
- /**
124
- * Prompt size for the next request: exact up to the anchor, estimated after.
125
- *
126
- * Falls back to a plain estimate before the first response, which is the only
127
- * point where nothing has been measured yet.
128
- */
129
- export function anchoredCount(
130
- messages: readonly Message[],
131
- system: string,
132
- anchor: Anchor | null,
133
- counter: TokenCounter = estimateTokens,
134
- tools: readonly unknown[] = [],
135
- ): { tokens: number; exactUpTo: number } {
136
- if (anchor === null || anchor.messageCount > messages.length) {
137
- return { tokens: counter.count(messages, system, tools), exactUpTo: 0 };
138
- }
139
- // The anchored prompt already includes the system prompt and the tools, so
140
- // the delta is counted without either to avoid charging for them twice.
141
- // The measured output is already the first assistant message after the
142
- // measured prompt. Its billed count replaces, rather than adds to, an
143
- // estimate of that same message (D1).
144
- const outputIndex = messages[anchor.messageCount]?.role === "assistant" ? 1 : 0;
145
- const since = messages.slice(anchor.messageCount + outputIndex);
146
- const delta = counter.count(since, "", []);
147
- return {
148
- tokens: anchor.promptTokens + anchor.outputTokens + delta,
149
- exactUpTo: anchor.promptTokens,
150
- };
151
- }
@@ -1,153 +0,0 @@
1
- /** O.3: one declarative input contract serves model schemas and runtime admission. */
2
- export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue };
3
- export type ToolDiagnostic = {
4
- code: "invalid_input" | "version_mismatch" | "not_found" | "ambiguous_edit" | "permission_denied" | "execution_failed" |
5
- "environment_missing" | "transient" | "cancelled" | "timeout" | "unknown_effect" | "command_failed";
6
- message: string;
7
- recover?: { tool: string; input: { readonly [key: string]: JsonValue } };
8
- /** R3: optional only for legacy records; supplied together by current failure owners. */
9
- execution?: "not_started" | "started" | "unknown";
10
- effects?: "none" | "possible";
11
- retry?: "never" | "after_change" | "after_inspection" | "backoff";
12
- next?: string;
13
- /** A schema-valid shape with placeholders, never an automatically executable recovery. */
14
- example?: { readonly [key: string]: JsonValue };
15
- };
16
-
17
- export function isJsonValue(value: unknown): value is JsonValue {
18
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
19
- if (typeof value === "number") return Number.isFinite(value);
20
- return typeof value === "object" && (Array.isArray(value) ? value : Object.values(value)).every(isJsonValue);
21
- }
22
-
23
- export function isToolDiagnostic(value: unknown): value is ToolDiagnostic {
24
- if (value === null || typeof value !== "object") return false;
25
- const item = value as Record<string, unknown>;
26
- if (!["invalid_input", "version_mismatch", "not_found", "ambiguous_edit", "permission_denied", "execution_failed",
27
- "environment_missing", "transient", "cancelled", "timeout", "unknown_effect", "command_failed"].includes(String(item["code"])) || typeof item["message"] !== "string") return false;
28
- if (["execution", "effects", "retry", "next"].some(key => item[key] !== undefined)) {
29
- if (!["not_started", "started", "unknown"].includes(String(item["execution"])) ||
30
- !["none", "possible"].includes(String(item["effects"])) ||
31
- !["never", "after_change", "after_inspection", "backoff"].includes(String(item["retry"])) || typeof item["next"] !== "string") return false;
32
- if (item["retry"] === "backoff" && (item["code"] !== "transient" || item["effects"] !== "none")) return false;
33
- if (item["code"] === "permission_denied" && (item["retry"] !== "never" || item["recover"] !== undefined)) return false;
34
- if (item["code"] === "unknown_effect" && item["retry"] !== "after_inspection") return false;
35
- }
36
- if (item["example"] !== undefined && (item["example"] === null || typeof item["example"] !== "object" || Array.isArray(item["example"]) || !isJsonValue(item["example"]))) return false;
37
- const recover = item["recover"];
38
- if (recover === undefined) return true;
39
- if (recover === null || typeof recover !== "object") return false;
40
- const recovery = recover as Record<string, unknown>;
41
- return typeof recovery["tool"] === "string" && recovery["input"] !== null &&
42
- typeof recovery["input"] === "object" && !Array.isArray(recovery["input"]) && isJsonValue(recovery["input"]);
43
- }
44
-
45
- /** Required fields only; examples express a valid shape, not permission or a safe command. */
46
- export function minimalInput(schema: unknown): JsonValue | undefined {
47
- if (schema === null || typeof schema !== "object") return undefined;
48
- const s = schema as Record<string, unknown>;
49
- if (Array.isArray(s["enum"]) && s["enum"].length && isJsonValue(s["enum"][0])) return s["enum"][0];
50
- switch (s["type"]) {
51
- case "string": return "<value>".padEnd(typeof s["minLength"] === "number" ? Math.min(4096, s["minLength"]) : 0, "x");
52
- case "number": case "integer": return typeof s["minimum"] === "number" ? s["minimum"] : 0;
53
- case "boolean": return false;
54
- case "array": return [];
55
- case "object": {
56
- const value: { [key: string]: JsonValue } = {};
57
- const properties = s["properties"];
58
- for (const key of Array.isArray(s["required"]) ? s["required"] : []) {
59
- if (typeof key !== "string" || properties === null || typeof properties !== "object") return undefined;
60
- const item = minimalInput((properties as Record<string, unknown>)[key]);
61
- if (item === undefined) return undefined;
62
- value[key] = item;
63
- }
64
- return value;
65
- }
66
- default: return null;
67
- }
68
- }
69
-
70
- /** Diagnostics are projected as tool data; they never become trusted system instructions. */
71
- export function diagnosticContent(content: string, diagnostic?: ToolDiagnostic): string {
72
- if (diagnostic?.execution === undefined) return content;
73
- return content + "\nTool diagnostic: " + JSON.stringify(diagnostic);
74
- }
75
-
76
- import { editDistance } from "./text-normalize.ts";
77
-
78
- /**
79
- * Names models send for arguments that exist under another name here. Edit
80
- * distance cannot see these (cwd and workdir share no letters in order);
81
- * each entry is a spelling another harness uses for the same argument.
82
- */
83
- const SYNONYMS: Readonly<Record<string, readonly string[]>> = {
84
- cwd: ["workdir"], dir: ["workdir", "path"], directory: ["workdir", "path"],
85
- file: ["path"], file_path: ["path"], filepath: ["path"], filename: ["path"],
86
- cmd: ["command"], script: ["command"],
87
- text: ["content"], contents: ["content"], old_str: ["old_string"], new_str: ["new_string"],
88
- query: ["pattern"], regex: ["pattern"], timeout_ms: ["timeout"], offset: ["start"], limit: ["end", "limit"],
89
- // Different units from max_output_tokens, so a hint rather than a silent conversion.
90
- max_output_chars: ["max_output_tokens"], max_output_bytes: ["max_output_tokens"], max_tokens: ["max_output_tokens"],
91
- };
92
-
93
- /** The candidate nearest to `name`, when it is near enough to be the likely intent. */
94
- export function closestName(name: string, candidates: readonly string[]): string | undefined {
95
- const synonym = SYNONYMS[name.toLowerCase()]?.find((alias) => candidates.includes(alias));
96
- if (synonym !== undefined) return synonym;
97
- let best: string | undefined, distance = Infinity;
98
- for (const candidate of candidates) {
99
- const d = Math.min(editDistance(name.toLowerCase(), candidate.toLowerCase()),
100
- // A common drift is a synonym sharing a stem (replaceAll / replace_all).
101
- editDistance(name.toLowerCase().replace(/[_-]/g, ""), candidate.toLowerCase().replace(/[_-]/g, "")));
102
- if (d < distance) { best = candidate; distance = d; }
103
- }
104
- return best !== undefined && distance <= Math.max(2, Math.floor(best.length / 2)) ? best : undefined;
105
- }
106
-
107
- /**
108
- * Validate the schema vocabulary used by built-ins; semantic constraints stay with their owner.
109
- *
110
- * An argument the schema does not declare is an error, not noise: a model
111
- * that sends `cwd` believes it changed directory, and dropping the field
112
- * silently runs the command somewhere it did not intend. `hidden` names
113
- * internal arguments accepted at the top level without being advertised.
114
- */
115
- export function inputError(schema: unknown, value: unknown, path = "input", hidden: readonly string[] = []): string | null {
116
- if (schema === null || typeof schema !== "object") return null;
117
- const s = schema as Record<string, unknown>;
118
- const type = s["type"];
119
- if (type === "object" && (value === null || typeof value !== "object" || Array.isArray(value))) return `${path} must be an object`;
120
- if (type === "array" && !Array.isArray(value)) return `${path} must be an array`;
121
- if (["string", "boolean", "number"].includes(String(type)) && typeof value !== type) return `${path} must be ${String(type)}`;
122
- if (type === "integer" && (typeof value !== "number" || !Number.isSafeInteger(value))) return `${path} must be a positive integer or zero where allowed`;
123
- if (typeof value === "number" && !Number.isFinite(value)) return `${path} must be finite`;
124
- if (typeof value === "number" && typeof s["minimum"] === "number" && value < s["minimum"]) return `${path} must be >= ${s["minimum"]}`;
125
- if (typeof value === "number" && typeof s["maximum"] === "number" && value > s["maximum"]) return `${path} must be <= ${s["maximum"]}`;
126
- if (typeof value === "string" && typeof s["minLength"] === "number" && value.length < s["minLength"]) return `${path} must not be empty`;
127
- if (Array.isArray(s["enum"]) && !s["enum"].includes(value)) return `${path} must be one of ${s["enum"].join(", ")}`;
128
- if (type === "array" && Array.isArray(value)) {
129
- for (let i = 0; i < value.length; i++) { const error = inputError(s["items"], value[i], `${path}[${i}]`); if (error) return error; }
130
- }
131
- if (type === "object" && value !== null && typeof value === "object") {
132
- const object = value as Record<string, unknown>;
133
- for (const key of Array.isArray(s["required"]) ? s["required"] : []) {
134
- if (typeof key === "string" && object[key] === undefined) return `${path}.${key} is required`;
135
- }
136
- const properties = s["properties"];
137
- if (properties !== null && typeof properties === "object") {
138
- const declared = Object.keys(properties);
139
- for (const key of Object.keys(object)) {
140
- if (declared.includes(key) || hidden.includes(key) || object[key] === undefined) continue;
141
- const near = closestName(key, declared);
142
- return `unknown argument ${path === "input" ? key : `${path}.${key}`}` + (near === undefined ? "" : `; did you mean ${near}?`) +
143
- ` Valid arguments: ${[...declared].sort().join(", ")}`;
144
- }
145
- }
146
- if (properties !== null && typeof properties === "object") for (const [key, property] of Object.entries(properties)) {
147
- if (object[key] === undefined) continue;
148
- const error = inputError(property, object[key], `${path}.${key}`);
149
- if (error) return error;
150
- }
151
- }
152
- return null;
153
- }
@@ -1,87 +0,0 @@
1
- /** R11 / D6 / D8: local discovery proposes declarations; only a paired checkpoint installs them. */
2
- import type { LogEvent, ToolInvocationEvent } from "./events.ts";
3
-
4
- /** Assumed response bounds, not measured model optima; prevent a directory dump from replacing a lookup. */
5
- export const MAX_DISCOVERY_RESULTS = 8;
6
- export const MAX_DISCOVERY_QUERY = 256;
7
- const MAX_PARAMETER_HINTS = 8;
8
- const MAX_PURPOSE_CHARS = 320;
9
-
10
- export interface DiscoverableTool {
11
- readonly name: string;
12
- readonly description: string;
13
- readonly input_schema: { readonly properties: Readonly<Record<string, unknown>>; readonly required: readonly string[] };
14
- }
15
- export interface DeclarationRequest { readonly name: string; readonly fingerprint: string }
16
- export interface DiscoveryMatch {
17
- name: string; purpose: string; inputs: { name: string; type: string; required: boolean }[]; omitted_inputs: number;
18
- }
19
- export function declaredToolNames(tools: readonly unknown[]): string[] {
20
- return tools.flatMap(tool => tool !== null && typeof tool === "object" && "name" in tool && typeof tool.name === "string" ? [tool.name] : []);
21
- }
22
- const compare = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;
23
- function record(raw: unknown): Record<string, unknown> | undefined {
24
- return raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : undefined;
25
- }
26
- function parameterType(raw: unknown): string {
27
- if (raw === null || typeof raw !== "object") return "schema";
28
- const type = (raw as Record<string, unknown>)["type"];
29
- if (typeof type === "string") return type.slice(0, 64);
30
- if (Array.isArray(type) && type.every(item => typeof item === "string")) return type.join("|").slice(0, 64);
31
- return "schema";
32
- }
33
-
34
- /** Pure, deterministic matching over the caller's already-selected directory; never perform discovery I/O. */
35
- export function discoverTools(catalog: readonly DiscoverableTool[], query: string, limit = 5): DiscoveryMatch[] {
36
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_DISCOVERY_RESULTS) throw new Error("invalid discovery limit");
37
- const normalized = query.trim().toLowerCase();
38
- if (!normalized || query.length > MAX_DISCOVERY_QUERY) throw new Error("query must contain 1 to 256 characters");
39
- const terms = normalized.split(/[^\p{L}\p{N}_-]+/u).filter(Boolean);
40
- if (terms.length === 0) return [];
41
- return catalog.map(tool => {
42
- const name = tool.name.toLowerCase(), text = `${name} ${tool.description.toLowerCase()}`;
43
- const score = name === normalized ? 100 : terms.every(term => text.includes(term))
44
- ? 1 + terms.filter(term => name.includes(term)).length : 0;
45
- return { tool, score };
46
- }).filter(item => item.score > 0).sort((a, b) => b.score - a.score || compare(a.tool.name, b.tool.name))
47
- .slice(0, limit).map(({ tool }) => {
48
- const required = new Set(tool.input_schema.required);
49
- const parameters = Object.entries(tool.input_schema.properties).sort(([a], [b]) =>
50
- Number(required.has(b)) - Number(required.has(a)) || compare(a, b));
51
- return { name: tool.name, purpose: tool.description.slice(0, MAX_PURPOSE_CHARS),
52
- inputs: parameters.slice(0, MAX_PARAMETER_HINTS).map(([name, raw]) => ({ name: name.slice(0, 128), type: parameterType(raw), required: required.has(name) })),
53
- omitted_inputs: Math.max(0, parameters.length - MAX_PARAMETER_HINTS) };
54
- });
55
- }
56
-
57
- /** A model assertion or another extension's data cannot authorize a declaration (D11). */
58
- export function recordedDeclarations(events: readonly LogEvent[]): DeclarationRequest[] {
59
- const invocations = new Map<string, ToolInvocationEvent>();
60
- const requested = new Map<string, { request: DeclarationRequest; user: string | undefined }>();
61
- let user: string | undefined;
62
- for (const event of events) {
63
- if (event.t === "user") { user = event.id; continue; }
64
- if (event.t === "interrupt") {
65
- for (const [name, value] of requested) if (value.user === event.user_id) requested.delete(name);
66
- continue;
67
- }
68
- if (event.t === "tool_invocation") { invocations.set(event.id, event); continue; }
69
- if (event.t !== "tool_result" || !event.ok || !event.invocation_ref) continue;
70
- if (event.outcome !== undefined && event.outcome.status !== "completed") continue;
71
- const invocation = invocations.get(event.invocation_ref.event);
72
- if (invocation?.name !== "tool_search" || invocation.call_id !== event.call_id) continue;
73
- const data = record(event.data);
74
- if (!data || data["kind"] !== "tool_discovery" || data["version"] !== 1) continue;
75
- const entries = data["requested"];
76
- if (!Array.isArray(entries) || entries.length > MAX_DISCOVERY_RESULTS) continue;
77
- for (const entry of entries) {
78
- const item = record(entry);
79
- if (!item) continue;
80
- const name = item["name"], fingerprint = item["fingerprint"];
81
- if (typeof name === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(name) && typeof fingerprint === "string" && /^[a-f0-9]{64}$/.test(fingerprint)) {
82
- requested.set(name, { request: { name, fingerprint }, user });
83
- }
84
- }
85
- }
86
- return [...requested.values()].map(value => value.request);
87
- }