@prometheus-ai/agent 0.5.4 → 0.5.8

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 (1145) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/cli.js +25110 -0
  3. package/dist/types/async/index.d.ts +0 -1
  4. package/dist/types/async/job-manager.d.ts +33 -0
  5. package/dist/types/autolearn/controller.d.ts +25 -0
  6. package/dist/types/autolearn/managed-skills.d.ts +45 -0
  7. package/dist/types/autoresearch/state.d.ts +1 -1
  8. package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
  9. package/dist/types/autoresearch/tools/log-experiment.d.ts +1 -1
  10. package/dist/types/autoresearch/tools/run-experiment.d.ts +1 -1
  11. package/dist/types/autoresearch/tools/update-notes.d.ts +1 -1
  12. package/dist/types/autoresearch/types.d.ts +1 -1
  13. package/dist/types/capability/context-file.d.ts +0 -13
  14. package/dist/types/capability/mcp.d.ts +1 -0
  15. package/dist/types/capability/rule-buckets.d.ts +1 -1
  16. package/dist/types/capability/rule.d.ts +6 -1
  17. package/dist/types/capability/types.d.ts +0 -4
  18. package/dist/types/cli/args.d.ts +23 -3
  19. package/dist/types/cli/bench-cli.d.ts +78 -0
  20. package/dist/types/cli/claude-trace-cli.d.ts +7 -0
  21. package/dist/types/cli/dry-balance-cli.d.ts +16 -2
  22. package/dist/types/cli/gallery-cli.d.ts +43 -0
  23. package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
  24. package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
  25. package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
  26. package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
  27. package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
  28. package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
  29. package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
  30. package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
  31. package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
  32. package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
  33. package/dist/types/cli/gallery-fixtures/types.d.ts +55 -0
  34. package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
  35. package/dist/types/cli/gallery-screenshot.d.ts +35 -0
  36. package/dist/types/cli/grievances-cli.d.ts +1 -1
  37. package/dist/types/cli/list-models.d.ts +6 -14
  38. package/dist/types/cli/models-cli.d.ts +49 -0
  39. package/dist/types/cli/session-picker.d.ts +1 -1
  40. package/dist/types/cli/setup-cli.d.ts +1 -1
  41. package/dist/types/cli/setup-model-picker.d.ts +14 -0
  42. package/dist/types/cli/startup-cwd.d.ts +2 -0
  43. package/dist/types/cli/update-cli.d.ts +13 -40
  44. package/dist/types/cli/usage-cli.d.ts +81 -0
  45. package/dist/types/cli-commands.d.ts +12 -0
  46. package/dist/types/collab/crypto.d.ts +7 -0
  47. package/dist/types/collab/guest.d.ts +37 -0
  48. package/dist/types/collab/host.d.ts +29 -0
  49. package/dist/types/collab/protocol.d.ts +119 -0
  50. package/dist/types/collab/relay-client.d.ts +22 -0
  51. package/dist/types/commands/bench.d.ts +29 -0
  52. package/dist/types/commands/gallery.d.ts +47 -0
  53. package/dist/types/commands/install.d.ts +1 -1
  54. package/dist/types/commands/join.d.ts +12 -0
  55. package/dist/types/commands/launch.d.ts +8 -4
  56. package/dist/types/commands/models.d.ts +33 -0
  57. package/dist/types/commands/read.d.ts +1 -1
  58. package/dist/types/commands/say.d.ts +24 -0
  59. package/dist/types/commands/token.d.ts +25 -0
  60. package/dist/types/commands/usage.d.ts +34 -0
  61. package/dist/types/commit/agentic/tools/analyze-file.d.ts +1 -1
  62. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +1 -1
  63. package/dist/types/commit/agentic/tools/git-hunk.d.ts +1 -1
  64. package/dist/types/commit/agentic/tools/git-overview.d.ts +1 -1
  65. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +1 -1
  66. package/dist/types/commit/agentic/tools/propose-commit.d.ts +1 -1
  67. package/dist/types/commit/agentic/tools/recent-commits.d.ts +1 -1
  68. package/dist/types/commit/agentic/tools/schemas.d.ts +1 -1
  69. package/dist/types/commit/agentic/tools/split-commit.d.ts +1 -1
  70. package/dist/types/commit/analysis/conventional.d.ts +2 -2
  71. package/dist/types/commit/analysis/summary.d.ts +2 -2
  72. package/dist/types/commit/changelog/generate.d.ts +3 -3
  73. package/dist/types/commit/changelog/index.d.ts +2 -2
  74. package/dist/types/commit/map-reduce/index.d.ts +3 -3
  75. package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
  76. package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
  77. package/dist/types/commit/model-selection.d.ts +10 -4
  78. package/dist/types/commit/shared-llm.d.ts +1 -1
  79. package/dist/types/config/api-key-resolver.d.ts +43 -0
  80. package/dist/types/config/append-only-context-mode.d.ts +2 -1
  81. package/dist/types/config/keybindings.d.ts +12 -7
  82. package/dist/types/config/model-discovery.d.ts +57 -0
  83. package/dist/types/config/model-equivalence.d.ts +1 -1
  84. package/dist/types/config/model-registry.d.ts +86 -222
  85. package/dist/types/config/model-resolver.d.ts +43 -12
  86. package/dist/types/config/model-roles.d.ts +29 -0
  87. package/dist/types/config/models-config-schema.d.ts +536 -43
  88. package/dist/types/config/models-config.d.ts +391 -0
  89. package/dist/types/config/settings-schema.d.ts +1202 -324
  90. package/dist/types/config/settings.d.ts +15 -3
  91. package/dist/types/dap/config.d.ts +14 -1
  92. package/dist/types/dap/types.d.ts +10 -0
  93. package/dist/types/debug/log-viewer.d.ts +1 -1
  94. package/dist/types/debug/raw-sse.d.ts +1 -1
  95. package/dist/types/debug/report-bundle.d.ts +3 -0
  96. package/dist/types/debug/terminal-info.d.ts +0 -1
  97. package/dist/types/discovery/at-imports.d.ts +15 -0
  98. package/dist/types/discovery/prometheus-extension-roots.d.ts +7 -7
  99. package/dist/types/edit/diff.d.ts +3 -2
  100. package/dist/types/edit/file-snapshot-store.d.ts +18 -0
  101. package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
  102. package/dist/types/edit/hashline/params.d.ts +1 -1
  103. package/dist/types/edit/index.d.ts +0 -1
  104. package/dist/types/edit/modes/apply-patch.d.ts +1 -1
  105. package/dist/types/edit/modes/patch.d.ts +1 -1
  106. package/dist/types/edit/modes/replace.d.ts +1 -1
  107. package/dist/types/edit/renderer.d.ts +1 -0
  108. package/dist/types/eval/__tests__/completion-bridge.test.d.ts +1 -0
  109. package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
  110. package/dist/types/eval/__tests__/js-context-manager.test.d.ts +1 -0
  111. package/dist/types/eval/backend.d.ts +7 -2
  112. package/dist/types/eval/bridge-timeout.d.ts +1 -1
  113. package/dist/types/eval/completion-bridge.d.ts +25 -0
  114. package/dist/types/eval/idle-timeout.d.ts +1 -5
  115. package/dist/types/eval/js/context-manager.d.ts +1 -0
  116. package/dist/types/eval/js/executor.d.ts +2 -0
  117. package/dist/types/eval/js/index.d.ts +1 -1
  118. package/dist/types/eval/js/shared/helpers.d.ts +7 -1
  119. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  120. package/dist/types/eval/js/shared/runtime.d.ts +6 -1
  121. package/dist/types/eval/js/worker-protocol.d.ts +6 -0
  122. package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
  123. package/dist/types/eval/py/executor.d.ts +12 -0
  124. package/dist/types/eval/py/index.d.ts +1 -1
  125. package/dist/types/eval/py/kernel.d.ts +6 -1
  126. package/dist/types/eval/py/runtime.d.ts +9 -0
  127. package/dist/types/exa/index.d.ts +1 -19
  128. package/dist/types/exa/mcp-client.d.ts +10 -3
  129. package/dist/types/exa/types.d.ts +0 -83
  130. package/dist/types/exec/bash-executor.d.ts +7 -0
  131. package/dist/types/export/custom-share.d.ts +1 -2
  132. package/dist/types/export/html/index.d.ts +39 -0
  133. package/dist/types/export/html/template-js.d.ts +2 -0
  134. package/dist/types/export/share.d.ts +61 -0
  135. package/dist/types/export/ttsr.d.ts +14 -0
  136. package/dist/types/extensibility/custom-commands/types.d.ts +9 -4
  137. package/dist/types/extensibility/custom-tools/loader.d.ts +30 -4
  138. package/dist/types/extensibility/custom-tools/types.d.ts +16 -8
  139. package/dist/types/extensibility/extensions/index.d.ts +1 -1
  140. package/dist/types/extensibility/extensions/loader.d.ts +20 -1
  141. package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
  142. package/dist/types/extensibility/extensions/runner.d.ts +5 -2
  143. package/dist/types/extensibility/extensions/types.d.ts +72 -11
  144. package/dist/types/extensibility/hooks/index.d.ts +2 -1
  145. package/dist/types/extensibility/hooks/loader.d.ts +1 -1
  146. package/dist/types/extensibility/hooks/types.d.ts +11 -5
  147. package/dist/types/extensibility/{legacy-pi-ai-shim.d.ts → legacy-package-ai-shim.d.ts} +2 -2
  148. package/dist/types/extensibility/plugins/{legacy-pi-compat.d.ts → legacy-package-compat.d.ts} +20 -3
  149. package/dist/types/extensibility/plugins/loader.d.ts +11 -0
  150. package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
  151. package/dist/types/extensibility/plugins/types.d.ts +2 -2
  152. package/dist/types/extensibility/shared-events.d.ts +3 -3
  153. package/dist/types/extensibility/skills.d.ts +10 -0
  154. package/dist/types/extensibility/slash-commands.d.ts +1 -11
  155. package/dist/types/goals/guided-setup.d.ts +18 -0
  156. package/dist/types/goals/state.d.ts +1 -1
  157. package/dist/types/goals/tools/goal-tool.d.ts +1 -1
  158. package/dist/types/hindsight/mental-models.d.ts +17 -8
  159. package/dist/types/hindsight/transcript.d.ts +1 -1
  160. package/dist/types/index.d.ts +5 -0
  161. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  162. package/dist/types/internal-urls/history-protocol.d.ts +14 -0
  163. package/dist/types/internal-urls/index.d.ts +1 -0
  164. package/dist/types/internal-urls/local-protocol.d.ts +14 -2
  165. package/dist/types/internal-urls/types.d.ts +1 -1
  166. package/dist/types/irc/bus.d.ts +79 -0
  167. package/dist/types/lib/xai-http.d.ts +1 -1
  168. package/dist/types/lsp/client.d.ts +10 -0
  169. package/dist/types/lsp/config.d.ts +2 -2
  170. package/dist/types/lsp/edits.d.ts +9 -0
  171. package/dist/types/lsp/format-options.d.ts +32 -0
  172. package/dist/types/lsp/index.d.ts +2 -7
  173. package/dist/types/lsp/types.d.ts +13 -1
  174. package/dist/types/lsp/utils.d.ts +6 -2
  175. package/dist/types/main.d.ts +23 -8
  176. package/dist/types/mcp/json-rpc.d.ts +5 -0
  177. package/dist/types/mcp/manager.d.ts +8 -0
  178. package/dist/types/mcp/oauth-discovery.d.ts +6 -1
  179. package/dist/types/mcp/oauth-flow.d.ts +13 -3
  180. package/dist/types/mcp/startup-events.d.ts +11 -0
  181. package/dist/types/mcp/tool-bridge.d.ts +2 -0
  182. package/dist/types/mcp/transports/stdio.d.ts +13 -0
  183. package/dist/types/mcp/types.d.ts +2 -0
  184. package/dist/types/memories/index.d.ts +7 -15
  185. package/dist/types/memories/storage.d.ts +0 -10
  186. package/dist/types/memory-backend/index.d.ts +3 -1
  187. package/dist/types/memory-backend/local-backend.d.ts +4 -3
  188. package/dist/types/memory-backend/resolve.d.ts +2 -2
  189. package/dist/types/memory-backend/runtime.d.ts +4 -0
  190. package/dist/types/memory-backend/types.d.ts +67 -2
  191. package/dist/types/mnemopi/config.d.ts +31 -1
  192. package/dist/types/mnemopi/state.d.ts +40 -2
  193. package/dist/types/modes/acp/acp-agent.d.ts +1 -2
  194. package/dist/types/modes/components/agent-dashboard.d.ts +17 -1
  195. package/dist/types/modes/components/agent-hub.d.ts +82 -0
  196. package/dist/types/modes/components/assistant-message.d.ts +5 -12
  197. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  198. package/dist/types/modes/components/chat-block.d.ts +64 -0
  199. package/dist/types/modes/components/collab-prompt-message.d.ts +10 -0
  200. package/dist/types/modes/components/compaction-summary-message.d.ts +25 -5
  201. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  202. package/dist/types/modes/components/custom-editor.d.ts +49 -2
  203. package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
  204. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  205. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  206. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  207. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  208. package/dist/types/modes/components/footer.d.ts +1 -1
  209. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  210. package/dist/types/modes/components/hook-input.d.ts +4 -0
  211. package/dist/types/modes/components/hook-selector.d.ts +5 -7
  212. package/dist/types/modes/components/index.d.ts +1 -0
  213. package/dist/types/modes/components/late-diagnostics-message.d.ts +20 -0
  214. package/dist/types/modes/components/logout-account-selector.d.ts +8 -0
  215. package/dist/types/modes/components/mcp-add-wizard.d.ts +2 -1
  216. package/dist/types/modes/components/model-selector.d.ts +1 -1
  217. package/dist/types/modes/components/oauth-selector.d.ts +10 -1
  218. package/dist/types/modes/components/overlay-box.d.ts +17 -0
  219. package/dist/types/modes/components/plan-review-overlay.d.ts +61 -0
  220. package/dist/types/modes/components/plan-toc.d.ts +41 -0
  221. package/dist/types/modes/components/read-tool-group.d.ts +8 -0
  222. package/dist/types/modes/components/reset-usage-selector.d.ts +12 -0
  223. package/dist/types/modes/components/segment-track.d.ts +11 -6
  224. package/dist/types/modes/components/session-selector.d.ts +18 -9
  225. package/dist/types/modes/components/settings-defs.d.ts +9 -2
  226. package/dist/types/modes/components/settings-selector.d.ts +17 -4
  227. package/dist/types/modes/components/snapcompact-shape-preview.d.ts +31 -0
  228. package/dist/types/modes/components/status-line/component.d.ts +61 -0
  229. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  230. package/dist/types/modes/components/status-line/types.d.ts +47 -3
  231. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  232. package/dist/types/modes/components/tool-execution.d.ts +49 -2
  233. package/dist/types/modes/components/transcript-container.d.ts +76 -26
  234. package/dist/types/modes/components/tree-selector.d.ts +2 -2
  235. package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
  236. package/dist/types/modes/components/usage-row.d.ts +3 -0
  237. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  238. package/dist/types/modes/components/user-message.d.ts +2 -1
  239. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  240. package/dist/types/modes/components/welcome.d.ts +12 -2
  241. package/dist/types/modes/controllers/command-controller.d.ts +3 -2
  242. package/dist/types/modes/controllers/event-controller.d.ts +7 -1
  243. package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
  244. package/dist/types/modes/controllers/input-controller.d.ts +25 -3
  245. package/dist/types/modes/controllers/mcp-command-controller.d.ts +8 -0
  246. package/dist/types/modes/controllers/selector-controller.d.ts +5 -2
  247. package/dist/types/modes/controllers/session-focus-controller.d.ts +31 -0
  248. package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
  249. package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
  250. package/dist/types/modes/controllers/tool-args-reveal.d.ts +43 -0
  251. package/dist/types/modes/gradient-highlight.d.ts +9 -4
  252. package/dist/types/modes/image-references.d.ts +14 -3
  253. package/dist/types/modes/index.d.ts +8 -7
  254. package/dist/types/modes/interactive-mode.d.ts +92 -16
  255. package/dist/types/modes/magic-keywords.d.ts +14 -2
  256. package/dist/types/modes/markdown-prose.d.ts +1 -1
  257. package/dist/types/modes/oauth-manual-input.d.ts +7 -0
  258. package/dist/types/modes/rpc/rpc-client.d.ts +48 -2
  259. package/dist/types/modes/rpc/rpc-mode.d.ts +67 -2
  260. package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
  261. package/dist/types/modes/rpc/rpc-types.d.ts +113 -1
  262. package/dist/types/modes/runtime-init.d.ts +4 -0
  263. package/dist/types/modes/session-observer-registry.d.ts +9 -0
  264. package/dist/types/modes/setup-version.d.ts +11 -0
  265. package/dist/types/modes/setup-wizard/index.d.ts +7 -2
  266. package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
  267. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +4 -1
  268. package/dist/types/modes/setup-wizard/scenes/types.d.ts +11 -2
  269. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +6 -2
  270. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  271. package/dist/types/modes/theme/theme.d.ts +42 -7
  272. package/dist/types/modes/types.d.ts +62 -13
  273. package/dist/types/modes/utils/context-usage.d.ts +6 -1
  274. package/dist/types/modes/utils/copy-targets.d.ts +21 -1
  275. package/dist/types/modes/utils/ui-helpers.d.ts +4 -4
  276. package/dist/types/modes/workflow.d.ts +3 -3
  277. package/dist/types/plan-mode/approved-plan.d.ts +27 -8
  278. package/dist/types/plan-mode/plan-protection.d.ts +4 -4
  279. package/dist/types/registry/agent-lifecycle.d.ts +51 -0
  280. package/dist/types/registry/agent-registry.d.ts +33 -5
  281. package/dist/types/sdk.d.ts +46 -4
  282. package/dist/types/secrets/index.d.ts +1 -1
  283. package/dist/types/secrets/obfuscator.d.ts +9 -3
  284. package/dist/types/session/agent-session.d.ts +136 -66
  285. package/dist/types/session/agent-storage.d.ts +2 -1
  286. package/dist/types/session/auth-broker-config.d.ts +4 -0
  287. package/dist/types/session/auth-storage.d.ts +1 -1
  288. package/dist/types/session/codex-auto-reset.d.ts +111 -0
  289. package/dist/types/session/indexed-session-storage.d.ts +3 -3
  290. package/dist/types/session/messages.d.ts +26 -15
  291. package/dist/types/session/session-context.d.ts +39 -0
  292. package/dist/types/session/session-entries.d.ts +159 -0
  293. package/dist/types/session/session-history-format.d.ts +12 -0
  294. package/dist/types/session/session-listing.d.ts +69 -0
  295. package/dist/types/session/session-loader.d.ts +16 -0
  296. package/dist/types/session/session-manager.d.ts +107 -440
  297. package/dist/types/session/session-migrations.d.ts +12 -0
  298. package/dist/types/session/session-paths.d.ts +25 -0
  299. package/dist/types/session/session-persistence.d.ts +8 -0
  300. package/dist/types/session/session-storage.d.ts +11 -7
  301. package/dist/types/session/snapcompact-inline.d.ts +145 -0
  302. package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
  303. package/dist/types/session/streaming-output.d.ts +46 -0
  304. package/dist/types/session/tool-choice-queue.d.ts +6 -6
  305. package/dist/types/session/yield-queue.d.ts +10 -1
  306. package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
  307. package/dist/types/slash-commands/available-commands.d.ts +34 -0
  308. package/dist/types/slash-commands/builtin-registry.d.ts +10 -0
  309. package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +14 -0
  310. package/dist/types/slash-commands/helpers/logout.d.ts +15 -0
  311. package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
  312. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  313. package/dist/types/slash-commands/types.d.ts +5 -9
  314. package/dist/types/ssh/connection-manager.d.ts +8 -0
  315. package/dist/types/stt/asr-client.d.ts +90 -0
  316. package/dist/types/stt/asr-protocol.d.ts +97 -0
  317. package/dist/types/stt/asr-worker.d.ts +2 -0
  318. package/dist/types/stt/downloader.d.ts +38 -0
  319. package/dist/types/stt/endpointer.d.ts +59 -0
  320. package/dist/types/stt/index.d.ts +5 -1
  321. package/dist/types/stt/models.d.ts +120 -0
  322. package/dist/types/stt/recorder.d.ts +17 -0
  323. package/dist/types/stt/stt-controller.d.ts +6 -0
  324. package/dist/types/stt/transcriber.d.ts +5 -7
  325. package/dist/types/stt/wav.d.ts +29 -0
  326. package/dist/types/system-prompt.d.ts +9 -1
  327. package/dist/types/task/commands.d.ts +1 -1
  328. package/dist/types/task/discovery.d.ts +1 -2
  329. package/dist/types/task/executor.d.ts +61 -2
  330. package/dist/types/task/index.d.ts +37 -6
  331. package/dist/types/task/output-manager.d.ts +0 -7
  332. package/dist/types/task/parallel.d.ts +2 -2
  333. package/dist/types/task/prometheus-command.d.ts +2 -2
  334. package/dist/types/task/render.d.ts +20 -7
  335. package/dist/types/task/repair-args.d.ts +8 -7
  336. package/dist/types/task/types.d.ts +109 -52
  337. package/dist/types/task/worktree.d.ts +2 -0
  338. package/dist/types/telemetry-export.d.ts +2 -2
  339. package/dist/types/thinking.d.ts +4 -0
  340. package/dist/types/tiny/models.d.ts +1 -1
  341. package/dist/types/tiny/title-client.d.ts +12 -1
  342. package/dist/types/tiny/title-protocol.d.ts +1 -0
  343. package/dist/types/tools/archive-reader.d.ts +5 -0
  344. package/dist/types/tools/ask.d.ts +6 -1
  345. package/dist/types/tools/ast-edit.d.ts +4 -1
  346. package/dist/types/tools/ast-grep.d.ts +4 -1
  347. package/dist/types/tools/bash.d.ts +5 -2
  348. package/dist/types/tools/browser/attach.d.ts +4 -4
  349. package/dist/types/tools/browser/cmux/cmux-tab.d.ts +202 -0
  350. package/dist/types/tools/browser/cmux/rpc.d.ts +70 -0
  351. package/dist/types/tools/browser/cmux/socket-client.d.ts +19 -0
  352. package/dist/types/tools/browser/registry.d.ts +17 -3
  353. package/dist/types/tools/browser/render.d.ts +2 -0
  354. package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
  355. package/dist/types/tools/browser/tab-supervisor.d.ts +16 -4
  356. package/dist/types/tools/browser/tab-worker.d.ts +18 -1
  357. package/dist/types/tools/browser.d.ts +3 -1
  358. package/dist/types/tools/checkpoint.d.ts +1 -1
  359. package/dist/types/tools/conflict-detect.d.ts +16 -0
  360. package/dist/types/tools/debug.d.ts +1 -1
  361. package/dist/types/tools/eval-render.d.ts +1 -8
  362. package/dist/types/tools/eval.d.ts +9 -1
  363. package/dist/types/tools/fetch.d.ts +17 -8
  364. package/dist/types/tools/find.d.ts +1 -8
  365. package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
  366. package/dist/types/tools/gh.d.ts +4 -1
  367. package/dist/types/tools/github-cache.d.ts +19 -0
  368. package/dist/types/tools/grouped-file-output.d.ts +46 -12
  369. package/dist/types/tools/image-gen.d.ts +1 -1
  370. package/dist/types/tools/index.d.ts +89 -8
  371. package/dist/types/tools/inspect-image.d.ts +1 -1
  372. package/dist/types/tools/irc.d.ts +79 -39
  373. package/dist/types/tools/job.d.ts +8 -2
  374. package/dist/types/tools/learn.d.ts +51 -0
  375. package/dist/types/tools/manage-skill.d.ts +40 -0
  376. package/dist/types/tools/memory-edit.d.ts +2 -2
  377. package/dist/types/tools/memory-recall.d.ts +1 -1
  378. package/dist/types/tools/memory-reflect.d.ts +1 -1
  379. package/dist/types/tools/memory-render.d.ts +4 -1
  380. package/dist/types/tools/memory-retain.d.ts +1 -1
  381. package/dist/types/tools/path-utils.d.ts +17 -5
  382. package/dist/types/tools/plan-mode-guard.d.ts +18 -9
  383. package/dist/types/tools/read.d.ts +3 -2
  384. package/dist/types/tools/render-mermaid.d.ts +1 -1
  385. package/dist/types/tools/render-utils.d.ts +47 -27
  386. package/dist/types/tools/renderers.d.ts +10 -2
  387. package/dist/types/tools/report-tool-issue.d.ts +6 -1
  388. package/dist/types/tools/resolve.d.ts +1 -1
  389. package/dist/types/tools/review.d.ts +1 -1
  390. package/dist/types/tools/search-tool-bm25.d.ts +1 -1
  391. package/dist/types/tools/search.d.ts +7 -3
  392. package/dist/types/tools/sqlite-reader.d.ts +4 -0
  393. package/dist/types/tools/ssh.d.ts +2 -1
  394. package/dist/types/tools/todo.d.ts +7 -15
  395. package/dist/types/tools/tool-result.d.ts +2 -0
  396. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  397. package/dist/types/tools/tts.d.ts +26 -1
  398. package/dist/types/tools/write.d.ts +6 -3
  399. package/dist/types/tools/yield.d.ts +8 -0
  400. package/dist/types/tts/downloader.d.ts +20 -0
  401. package/dist/types/tts/index.d.ts +8 -0
  402. package/dist/types/tts/models.d.ts +82 -0
  403. package/dist/types/tts/player.d.ts +32 -0
  404. package/dist/types/tts/runtime.d.ts +6 -0
  405. package/dist/types/tts/streaming-player.d.ts +41 -0
  406. package/dist/types/tts/tts-client.d.ts +93 -0
  407. package/dist/types/tts/tts-protocol.d.ts +95 -0
  408. package/dist/types/tts/tts-worker.d.ts +2 -0
  409. package/dist/types/tts/vocalizer.d.ts +41 -0
  410. package/dist/types/tts/wav.d.ts +8 -0
  411. package/dist/types/tui/code-cell.d.ts +0 -2
  412. package/dist/types/tui/hyperlink.d.ts +13 -7
  413. package/dist/types/tui/output-block.d.ts +16 -22
  414. package/dist/types/tui/status-line.d.ts +3 -0
  415. package/dist/types/utils/block-context.d.ts +35 -0
  416. package/dist/types/utils/changelog.d.ts +8 -0
  417. package/dist/types/utils/clipboard.d.ts +4 -3
  418. package/dist/types/utils/enhanced-paste.d.ts +20 -0
  419. package/dist/types/utils/file-mentions.d.ts +7 -0
  420. package/dist/types/utils/git.d.ts +22 -3
  421. package/dist/types/utils/image-loading.d.ts +30 -1
  422. package/dist/types/utils/session-color.d.ts +15 -3
  423. package/dist/types/utils/thinking-display.d.ts +17 -0
  424. package/dist/types/utils/title-generator.d.ts +3 -2
  425. package/dist/types/utils/tool-choice.d.ts +8 -0
  426. package/dist/types/utils/tools-manager.d.ts +2 -1
  427. package/dist/types/web/kagi.d.ts +2 -2
  428. package/dist/types/web/parallel.d.ts +3 -0
  429. package/dist/types/web/scrapers/github.d.ts +22 -0
  430. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  431. package/dist/types/web/scrapers/types.d.ts +12 -0
  432. package/dist/types/web/search/index.d.ts +1 -1
  433. package/dist/types/web/search/providers/anthropic.d.ts +2 -1
  434. package/dist/types/web/search/providers/base.d.ts +2 -1
  435. package/dist/types/web/search/providers/brave.d.ts +2 -1
  436. package/dist/types/web/search/providers/codex.d.ts +2 -1
  437. package/dist/types/web/search/providers/exa.d.ts +2 -1
  438. package/dist/types/web/search/providers/gemini.d.ts +10 -6
  439. package/dist/types/web/search/providers/jina.d.ts +7 -2
  440. package/dist/types/web/search/providers/kagi.d.ts +7 -2
  441. package/dist/types/web/search/providers/kimi.d.ts +7 -2
  442. package/dist/types/web/search/providers/parallel.d.ts +2 -1
  443. package/dist/types/web/search/providers/perplexity.d.ts +10 -2
  444. package/dist/types/web/search/providers/searxng.d.ts +2 -1
  445. package/dist/types/web/search/providers/synthetic.d.ts +7 -3
  446. package/dist/types/web/search/providers/tavily.d.ts +2 -1
  447. package/dist/types/web/search/providers/zai.d.ts +2 -1
  448. package/dist/types/web/search/types.d.ts +1 -1
  449. package/examples/extensions/api-demo.ts +2 -2
  450. package/package.json +41 -15
  451. package/scripts/bench-guard.ts +71 -0
  452. package/scripts/build-binary.ts +24 -25
  453. package/scripts/bundle-dist.ts +97 -0
  454. package/scripts/generate-share-viewer.ts +34 -0
  455. package/scripts/prometheus +42 -0
  456. package/scripts/prometheus.ts +20 -0
  457. package/src/async/index.ts +0 -1
  458. package/src/async/job-manager.ts +106 -3
  459. package/src/auto-thinking/classifier.ts +2 -1
  460. package/src/autolearn/controller.ts +139 -0
  461. package/src/autolearn/managed-skills.ts +257 -0
  462. package/src/autoresearch/dashboard.ts +1 -1
  463. package/src/autoresearch/prompt-setup.md +6 -6
  464. package/src/autoresearch/prompt.md +6 -6
  465. package/src/autoresearch/state.ts +1 -1
  466. package/src/autoresearch/storage.ts +2 -1
  467. package/src/autoresearch/tools/init-experiment.ts +1 -1
  468. package/src/autoresearch/tools/log-experiment.ts +1 -1
  469. package/src/autoresearch/tools/run-experiment.ts +1 -1
  470. package/src/autoresearch/tools/update-notes.ts +1 -1
  471. package/src/autoresearch/types.ts +1 -1
  472. package/src/capability/context-file.ts +0 -14
  473. package/src/capability/fs.ts +10 -0
  474. package/src/capability/index.ts +1 -6
  475. package/src/capability/mcp.ts +1 -0
  476. package/src/capability/rule-buckets.ts +4 -2
  477. package/src/capability/rule.ts +10 -1
  478. package/src/capability/types.ts +0 -4
  479. package/src/cli/args.ts +66 -13
  480. package/src/cli/auth-broker-cli.ts +6 -7
  481. package/src/cli/auth-gateway-cli.ts +8 -9
  482. package/src/cli/bench-cli.ts +437 -0
  483. package/src/cli/claude-trace-cli.ts +28 -50
  484. package/src/cli/completion-gen.ts +28 -28
  485. package/src/cli/dry-balance-cli.ts +56 -23
  486. package/src/cli/gallery-cli.ts +231 -0
  487. package/src/cli/gallery-fixtures/agentic.ts +407 -0
  488. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  489. package/src/cli/gallery-fixtures/edit.ts +194 -0
  490. package/src/cli/gallery-fixtures/fs.ts +220 -0
  491. package/src/cli/gallery-fixtures/index.ts +40 -0
  492. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  493. package/src/cli/gallery-fixtures/memory.ts +81 -0
  494. package/src/cli/gallery-fixtures/misc.ts +250 -0
  495. package/src/cli/gallery-fixtures/search.ts +213 -0
  496. package/src/cli/gallery-fixtures/shell.ts +167 -0
  497. package/src/cli/gallery-fixtures/types.ts +57 -0
  498. package/src/cli/gallery-fixtures/web.ts +158 -0
  499. package/src/cli/gallery-screenshot.ts +279 -0
  500. package/src/cli/grievances-cli.ts +1 -1
  501. package/src/cli/list-models.ts +16 -174
  502. package/src/cli/models-cli.ts +429 -0
  503. package/src/cli/session-picker.ts +2 -1
  504. package/src/cli/setup-cli.ts +148 -47
  505. package/src/cli/setup-model-picker.ts +43 -0
  506. package/src/cli/startup-cwd.ts +68 -0
  507. package/src/cli/update-cli.ts +144 -272
  508. package/src/cli/usage-cli.ts +774 -0
  509. package/src/cli-commands.ts +36 -0
  510. package/src/cli.ts +141 -32
  511. package/src/collab/crypto.ts +63 -0
  512. package/src/collab/guest.ts +451 -0
  513. package/src/collab/host.ts +565 -0
  514. package/src/collab/protocol.ts +241 -0
  515. package/src/collab/relay-client.ts +216 -0
  516. package/src/commands/bench.ts +42 -0
  517. package/src/commands/complete.ts +1 -1
  518. package/src/commands/gallery.ts +52 -0
  519. package/src/commands/install.ts +1 -1
  520. package/src/commands/join.ts +39 -0
  521. package/src/commands/launch.ts +8 -4
  522. package/src/commands/models.ts +61 -0
  523. package/src/commands/read.ts +6 -3
  524. package/src/commands/say.ts +102 -0
  525. package/src/commands/setup.ts +1 -1
  526. package/src/commands/token.ts +89 -0
  527. package/src/commands/usage.ts +43 -0
  528. package/src/commit/agentic/agent.ts +2 -1
  529. package/src/commit/agentic/tools/analyze-file.ts +42 -20
  530. package/src/commit/agentic/tools/git-file-diff.ts +1 -1
  531. package/src/commit/agentic/tools/git-hunk.ts +1 -1
  532. package/src/commit/agentic/tools/git-overview.ts +1 -1
  533. package/src/commit/agentic/tools/propose-changelog.ts +1 -1
  534. package/src/commit/agentic/tools/propose-commit.ts +1 -1
  535. package/src/commit/agentic/tools/recent-commits.ts +1 -1
  536. package/src/commit/agentic/tools/schemas.ts +1 -1
  537. package/src/commit/agentic/tools/split-commit.ts +9 -2
  538. package/src/commit/analysis/conventional.ts +2 -2
  539. package/src/commit/analysis/summary.ts +3 -3
  540. package/src/commit/changelog/generate.ts +3 -3
  541. package/src/commit/changelog/index.ts +2 -2
  542. package/src/commit/map-reduce/index.ts +3 -3
  543. package/src/commit/map-reduce/map-phase.ts +2 -2
  544. package/src/commit/map-reduce/reduce-phase.ts +2 -2
  545. package/src/commit/model-selection.ts +35 -12
  546. package/src/commit/pipeline.ts +4 -4
  547. package/src/commit/shared-llm.ts +1 -1
  548. package/src/config/api-key-resolver.ts +67 -0
  549. package/src/config/append-only-context-mode.ts +6 -12
  550. package/src/config/keybindings.ts +9 -4
  551. package/src/config/mcp-schema.json +4 -0
  552. package/src/config/model-discovery.ts +574 -0
  553. package/src/config/model-equivalence.ts +5 -4
  554. package/src/config/model-registry.ts +659 -1093
  555. package/src/config/model-resolver.ts +374 -174
  556. package/src/config/model-roles.ts +88 -0
  557. package/src/config/models-config-schema.ts +61 -9
  558. package/src/config/models-config.ts +130 -0
  559. package/src/config/settings-schema.ts +1441 -387
  560. package/src/config/settings.ts +261 -69
  561. package/src/dap/client.ts +138 -53
  562. package/src/dap/config.ts +41 -2
  563. package/src/dap/defaults.json +1 -0
  564. package/src/dap/session.ts +263 -161
  565. package/src/dap/types.ts +10 -0
  566. package/src/debug/index.ts +50 -60
  567. package/src/debug/log-viewer.ts +1 -1
  568. package/src/debug/protocol-probe.ts +1 -1
  569. package/src/debug/raw-sse-buffer.ts +7 -4
  570. package/src/debug/raw-sse.ts +1 -1
  571. package/src/debug/report-bundle.ts +9 -0
  572. package/src/debug/terminal-info.ts +0 -3
  573. package/src/discovery/agents-md.ts +25 -21
  574. package/src/discovery/agents.ts +9 -15
  575. package/src/discovery/at-imports.ts +273 -0
  576. package/src/discovery/builtin-rules/index.ts +4 -0
  577. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  578. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  579. package/src/discovery/builtin.ts +45 -23
  580. package/src/discovery/claude-plugins.ts +44 -5
  581. package/src/discovery/helpers.ts +50 -9
  582. package/src/discovery/prometheus-extension-roots.ts +10 -10
  583. package/src/discovery/prometheus-plugins.ts +10 -10
  584. package/src/edit/diff.ts +191 -4
  585. package/src/edit/file-snapshot-store.ts +34 -1
  586. package/src/edit/hashline/block-resolver.ts +20 -1
  587. package/src/edit/hashline/diff.ts +123 -2
  588. package/src/edit/hashline/execute.ts +60 -4
  589. package/src/edit/hashline/filesystem.ts +2 -1
  590. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  591. package/src/edit/hashline/params.ts +1 -1
  592. package/src/edit/index.ts +47 -18
  593. package/src/edit/modes/apply-patch.ts +1 -1
  594. package/src/edit/modes/patch.ts +59 -3
  595. package/src/edit/modes/replace.ts +58 -24
  596. package/src/edit/notebook.ts +22 -2
  597. package/src/edit/renderer.ts +315 -151
  598. package/src/eval/__tests__/agent-bridge.test.ts +105 -39
  599. package/src/eval/__tests__/budget-bridge.test.ts +1 -1
  600. package/src/eval/__tests__/completion-bridge.test.ts +412 -0
  601. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  602. package/src/eval/__tests__/js-context-manager.test.ts +241 -0
  603. package/src/eval/__tests__/llm-bridge.test.ts +6 -4
  604. package/src/eval/__tests__/shared-executors.test.ts +34 -92
  605. package/src/eval/agent-bridge.ts +39 -23
  606. package/src/eval/backend.ts +15 -2
  607. package/src/eval/bridge-timeout.ts +1 -1
  608. package/src/eval/completion-bridge.ts +203 -0
  609. package/src/eval/idle-timeout.ts +3 -10
  610. package/src/eval/js/context-manager.ts +108 -31
  611. package/src/eval/js/executor.ts +9 -2
  612. package/src/eval/js/index.ts +7 -3
  613. package/src/eval/js/shared/helpers.ts +59 -13
  614. package/src/eval/js/shared/local-module-loader.ts +2 -2
  615. package/src/eval/js/shared/prelude.txt +167 -30
  616. package/src/eval/js/shared/rewrite-imports.ts +58 -34
  617. package/src/eval/js/shared/runtime.ts +24 -16
  618. package/src/eval/js/tool-bridge.ts +4 -0
  619. package/src/eval/js/worker-core.ts +1 -0
  620. package/src/eval/js/worker-entry.ts +6 -0
  621. package/src/eval/js/worker-protocol.ts +6 -0
  622. package/src/eval/llm-bridge.ts +2 -1
  623. package/src/eval/py/__tests__/prelude.test.ts +19 -0
  624. package/src/eval/py/executor.ts +70 -26
  625. package/src/eval/py/index.ts +13 -4
  626. package/src/eval/py/kernel.ts +48 -9
  627. package/src/eval/py/prelude.py +73 -24
  628. package/src/eval/py/runner.py +133 -28
  629. package/src/eval/py/runtime.ts +38 -1
  630. package/src/exa/index.ts +1 -26
  631. package/src/exa/mcp-client.ts +10 -10
  632. package/src/exa/types.ts +0 -97
  633. package/src/exec/bash-executor.ts +104 -7
  634. package/src/export/custom-share.ts +1 -1
  635. package/src/export/html/index.ts +119 -17
  636. package/src/export/html/share-loader.js +102 -0
  637. package/src/export/html/template-js.ts +6 -0
  638. package/src/export/html/template.css +745 -459
  639. package/src/export/html/template.css.d.ts +2 -0
  640. package/src/export/html/template.html +6 -3
  641. package/src/export/html/template.js +277 -891
  642. package/src/export/html/tool-views.generated.d.ts +2 -0
  643. package/src/export/html/tool-views.generated.js +38 -0
  644. package/src/export/share.ts +269 -0
  645. package/src/export/ttsr.ts +122 -1
  646. package/src/extensibility/custom-commands/loader.ts +7 -4
  647. package/src/extensibility/custom-commands/types.ts +9 -4
  648. package/src/extensibility/custom-tools/loader.ts +51 -23
  649. package/src/extensibility/custom-tools/types.ts +16 -8
  650. package/src/extensibility/extensions/get-commands-handler.ts +2 -1
  651. package/src/extensibility/extensions/index.ts +1 -0
  652. package/src/extensibility/extensions/loader.ts +70 -20
  653. package/src/extensibility/extensions/model-api.ts +41 -0
  654. package/src/extensibility/extensions/runner.ts +12 -2
  655. package/src/extensibility/extensions/types.ts +83 -11
  656. package/src/extensibility/extensions/wrapper.ts +41 -5
  657. package/src/extensibility/hooks/index.ts +2 -1
  658. package/src/extensibility/hooks/loader.ts +6 -3
  659. package/src/extensibility/hooks/types.ts +11 -5
  660. package/src/extensibility/{legacy-pi-ai-shim.ts → legacy-package-ai-shim.ts} +2 -2
  661. package/src/extensibility/plugins/doctor.ts +1 -2
  662. package/src/extensibility/plugins/installer.ts +2 -2
  663. package/src/extensibility/plugins/{legacy-pi-compat.ts → legacy-package-compat.ts} +165 -77
  664. package/src/extensibility/plugins/loader.ts +34 -23
  665. package/src/extensibility/plugins/manager.ts +226 -95
  666. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  667. package/src/extensibility/plugins/types.ts +3 -3
  668. package/src/extensibility/shared-events.ts +3 -3
  669. package/src/extensibility/skills.ts +113 -9
  670. package/src/extensibility/slash-commands.ts +1 -97
  671. package/src/goals/guided-setup.ts +133 -0
  672. package/src/goals/state.ts +1 -1
  673. package/src/goals/tools/goal-tool.ts +38 -28
  674. package/src/hindsight/bank.ts +17 -2
  675. package/src/hindsight/client.ts +27 -2
  676. package/src/hindsight/mental-models.ts +59 -12
  677. package/src/hindsight/state.ts +12 -3
  678. package/src/hindsight/transcript.ts +1 -1
  679. package/src/index.ts +5 -0
  680. package/src/internal-urls/artifact-protocol.ts +11 -2
  681. package/src/internal-urls/docs-index.generated.ts +9 -7
  682. package/src/internal-urls/history-protocol.ts +113 -0
  683. package/src/internal-urls/index.ts +1 -0
  684. package/src/internal-urls/issue-pr-protocol.ts +22 -9
  685. package/src/internal-urls/local-protocol.ts +42 -7
  686. package/src/internal-urls/memory-protocol.ts +4 -31
  687. package/src/internal-urls/router.ts +3 -1
  688. package/src/internal-urls/types.ts +1 -1
  689. package/src/irc/bus.ts +303 -0
  690. package/src/lib/xai-http.ts +3 -3
  691. package/src/lsp/client.ts +245 -104
  692. package/src/lsp/clients/biome-client.ts +101 -39
  693. package/src/lsp/clients/lsp-linter-client.ts +2 -10
  694. package/src/lsp/config.ts +15 -5
  695. package/src/lsp/defaults.json +6 -0
  696. package/src/lsp/edits.ts +143 -95
  697. package/src/lsp/format-options.ts +119 -0
  698. package/src/lsp/index.ts +233 -93
  699. package/src/lsp/render.ts +11 -35
  700. package/src/lsp/types.ts +13 -1
  701. package/src/lsp/utils.ts +31 -12
  702. package/src/main.ts +396 -216
  703. package/src/mcp/config-writer.ts +7 -3
  704. package/src/mcp/json-rpc.ts +35 -5
  705. package/src/mcp/manager.ts +31 -16
  706. package/src/mcp/oauth-discovery.ts +34 -4
  707. package/src/mcp/oauth-flow.ts +61 -8
  708. package/src/mcp/render.ts +7 -1
  709. package/src/mcp/startup-events.ts +21 -0
  710. package/src/mcp/tool-bridge.ts +2 -0
  711. package/src/mcp/transports/stdio.ts +224 -4
  712. package/src/mcp/types.ts +2 -0
  713. package/src/memories/index.ts +174 -1128
  714. package/src/memories/storage.ts +2 -41
  715. package/src/memory-backend/index.ts +14 -1
  716. package/src/memory-backend/local-backend.ts +18 -3
  717. package/src/memory-backend/off-backend.ts +9 -0
  718. package/src/memory-backend/resolve.ts +4 -6
  719. package/src/memory-backend/runtime.ts +66 -0
  720. package/src/memory-backend/types.ts +82 -2
  721. package/src/mnemopi/backend.ts +220 -28
  722. package/src/mnemopi/config.ts +138 -33
  723. package/src/mnemopi/state.ts +91 -11
  724. package/src/modes/acp/acp-agent.ts +149 -142
  725. package/src/modes/acp/acp-event-mapper.ts +5 -1
  726. package/src/modes/components/agent-dashboard.ts +17 -11
  727. package/src/modes/components/agent-hub.ts +1346 -0
  728. package/src/modes/components/assistant-message.ts +190 -80
  729. package/src/modes/components/bash-execution.ts +1 -1
  730. package/src/modes/components/btw-panel.ts +5 -1
  731. package/src/modes/components/chat-block.ts +111 -0
  732. package/src/modes/components/collab-prompt-message.ts +30 -0
  733. package/src/modes/components/compaction-summary-message.ts +168 -33
  734. package/src/modes/components/copy-selector.ts +2 -45
  735. package/src/modes/components/custom-editor.test.ts +96 -0
  736. package/src/modes/components/custom-editor.ts +405 -118
  737. package/src/modes/components/custom-message.ts +1 -3
  738. package/src/modes/components/diff.ts +13 -2
  739. package/src/modes/components/dynamic-border.ts +12 -3
  740. package/src/modes/components/execution-shared.ts +1 -2
  741. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  742. package/src/modes/components/extensions/extension-list.ts +1 -1
  743. package/src/modes/components/extensions/inspector-panel.ts +7 -3
  744. package/src/modes/components/extensions/state-manager.ts +36 -41
  745. package/src/modes/components/footer.ts +4 -2
  746. package/src/modes/components/history-search.ts +1 -1
  747. package/src/modes/components/hook-editor.ts +8 -0
  748. package/src/modes/components/hook-input.ts +8 -0
  749. package/src/modes/components/hook-message.ts +1 -3
  750. package/src/modes/components/hook-selector.ts +6 -7
  751. package/src/modes/components/index.ts +1 -0
  752. package/src/modes/components/late-diagnostics-message.ts +60 -0
  753. package/src/modes/components/login-dialog.ts +1 -1
  754. package/src/modes/components/logout-account-selector.ts +130 -0
  755. package/src/modes/components/mcp-add-wizard.ts +14 -1
  756. package/src/modes/components/model-selector.ts +177 -75
  757. package/src/modes/components/oauth-selector.ts +102 -16
  758. package/src/modes/components/overlay-box.ts +108 -0
  759. package/src/modes/components/plan-review-overlay.ts +845 -0
  760. package/src/modes/components/plan-toc.ts +138 -0
  761. package/src/modes/components/plugin-settings.ts +22 -5
  762. package/src/modes/components/read-tool-group.ts +442 -39
  763. package/src/modes/components/reset-usage-selector.ts +161 -0
  764. package/src/modes/components/segment-track.ts +44 -7
  765. package/src/modes/components/session-selector.ts +97 -37
  766. package/src/modes/components/settings-defs.ts +28 -6
  767. package/src/modes/components/settings-selector.ts +541 -93
  768. package/src/modes/components/skill-message.ts +0 -1
  769. package/src/modes/components/snapcompact-shape-preview-doc.md +11 -0
  770. package/src/modes/components/snapcompact-shape-preview.ts +193 -0
  771. package/src/modes/components/{status-line.ts → status-line/component.ts} +205 -168
  772. package/src/modes/components/status-line/index.ts +1 -0
  773. package/src/modes/components/status-line/presets.ts +3 -3
  774. package/src/modes/components/status-line/segments.ts +26 -7
  775. package/src/modes/components/status-line/types.ts +40 -9
  776. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  777. package/src/modes/components/tips.txt +7 -3
  778. package/src/modes/components/todo-reminder.ts +0 -2
  779. package/src/modes/components/tool-execution.ts +236 -103
  780. package/src/modes/components/transcript-container.ts +724 -99
  781. package/src/modes/components/tree-selector.ts +19 -4
  782. package/src/modes/components/ttsr-notification.ts +72 -30
  783. package/src/modes/components/usage-row.ts +18 -0
  784. package/src/modes/components/user-message-selector.ts +1 -1
  785. package/src/modes/components/user-message.ts +28 -12
  786. package/src/modes/components/visual-truncate.ts +1 -1
  787. package/src/modes/components/welcome.ts +80 -22
  788. package/src/modes/controllers/command-controller-shared.ts +7 -6
  789. package/src/modes/controllers/command-controller.ts +210 -180
  790. package/src/modes/controllers/event-controller.ts +352 -142
  791. package/src/modes/controllers/extension-ui-controller.ts +167 -208
  792. package/src/modes/controllers/input-controller.ts +778 -162
  793. package/src/modes/controllers/mcp-command-controller.ts +232 -80
  794. package/src/modes/controllers/selector-controller.ts +284 -145
  795. package/src/modes/controllers/session-focus-controller.ts +112 -0
  796. package/src/modes/controllers/ssh-command-controller.ts +2 -2
  797. package/src/modes/controllers/streaming-reveal.ts +295 -0
  798. package/src/modes/controllers/tan-command-controller.ts +173 -0
  799. package/src/modes/controllers/tool-args-reveal.ts +174 -0
  800. package/src/modes/gradient-highlight.ts +21 -9
  801. package/src/modes/image-references.ts +33 -7
  802. package/src/modes/index.ts +8 -25
  803. package/src/modes/interactive-mode.ts +840 -186
  804. package/src/modes/magic-keywords.ts +28 -6
  805. package/src/modes/markdown-prose.ts +1 -1
  806. package/src/modes/oauth-manual-input.ts +30 -3
  807. package/src/modes/rpc/rpc-client.ts +186 -3
  808. package/src/modes/rpc/rpc-mode.ts +318 -24
  809. package/src/modes/rpc/rpc-subagents.ts +265 -0
  810. package/src/modes/rpc/rpc-types.ts +111 -2
  811. package/src/modes/runtime-init.ts +28 -3
  812. package/src/modes/session-observer-registry.ts +72 -3
  813. package/src/modes/setup-version.ts +11 -0
  814. package/src/modes/setup-wizard/index.ts +16 -4
  815. package/src/modes/setup-wizard/lazy.ts +16 -0
  816. package/src/modes/setup-wizard/scenes/glyph.ts +25 -7
  817. package/src/modes/setup-wizard/scenes/providers.ts +45 -12
  818. package/src/modes/setup-wizard/scenes/sign-in.ts +14 -13
  819. package/src/modes/setup-wizard/scenes/splash.ts +1 -1
  820. package/src/modes/setup-wizard/scenes/theme.ts +29 -2
  821. package/src/modes/setup-wizard/scenes/types.ts +11 -2
  822. package/src/modes/setup-wizard/scenes/web-search.ts +26 -9
  823. package/src/modes/setup-wizard/wizard-overlay.ts +40 -3
  824. package/src/modes/shared.ts +2 -0
  825. package/src/modes/theme/defaults/dark-poimandres.json +1 -1
  826. package/src/modes/theme/defaults/light-poimandres.json +1 -1
  827. package/src/modes/theme/shimmer.ts +20 -9
  828. package/src/modes/theme/theme-schema.json +1 -1
  829. package/src/modes/theme/theme.ts +342 -82
  830. package/src/modes/types.ts +60 -18
  831. package/src/modes/utils/context-usage.ts +88 -8
  832. package/src/modes/utils/copy-targets.ts +133 -27
  833. package/src/modes/utils/hotkeys-markdown.ts +3 -2
  834. package/src/modes/utils/ui-helpers.ts +191 -110
  835. package/src/modes/workflow.ts +10 -10
  836. package/src/plan-mode/approved-plan.ts +66 -43
  837. package/src/plan-mode/plan-protection.ts +4 -4
  838. package/src/priority.json +5 -1
  839. package/src/prompts/agents/designer.md +1 -1
  840. package/src/prompts/agents/explore.md +3 -3
  841. package/src/prompts/agents/librarian.md +2 -3
  842. package/src/prompts/agents/oracle.md +2 -2
  843. package/src/prompts/agents/plan.md +6 -6
  844. package/src/prompts/agents/reviewer.md +1 -1
  845. package/src/prompts/agents/task.md +6 -5
  846. package/src/prompts/bench.md +12 -0
  847. package/src/prompts/ci-green-request.md +5 -7
  848. package/src/prompts/goals/goal-budget-limit.md +2 -2
  849. package/src/prompts/goals/goal-continuation.md +4 -4
  850. package/src/prompts/goals/goal-mode-active.md +1 -1
  851. package/src/prompts/goals/guided-goal-interview.md +8 -0
  852. package/src/prompts/goals/guided-goal-system.md +12 -0
  853. package/src/prompts/memories/consolidation.md +2 -7
  854. package/src/prompts/memories/consolidation_system.md +4 -0
  855. package/src/prompts/memories/identity_review.md +2 -2
  856. package/src/prompts/memories/read-path.md +11 -10
  857. package/src/prompts/memories/stage_one_system.md +2 -2
  858. package/src/prompts/review-custom-request.md +1 -1
  859. package/src/prompts/system/agent-creation-architect.md +2 -2
  860. package/src/prompts/system/auto-continue.md +1 -1
  861. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  862. package/src/prompts/system/autolearn-guidance.md +7 -0
  863. package/src/prompts/system/autolearn-nudge.md +3 -0
  864. package/src/prompts/system/background-tan-dispatch.md +8 -0
  865. package/src/prompts/system/btw-user.md +2 -2
  866. package/src/prompts/system/commit-message-system.md +13 -1
  867. package/src/prompts/system/custom-system-prompt.md +1 -1
  868. package/src/prompts/system/eager-task.md +7 -0
  869. package/src/prompts/system/eager-todo.md +11 -6
  870. package/src/prompts/system/empty-stop-retry.md +4 -6
  871. package/src/prompts/system/irc-autoreply.md +6 -0
  872. package/src/prompts/system/irc-incoming.md +3 -4
  873. package/src/prompts/system/manual-continue.md +7 -0
  874. package/src/prompts/system/omfg-user.md +3 -4
  875. package/src/prompts/system/orchestrate-notice.md +10 -10
  876. package/src/prompts/system/personalities/default.md +26 -0
  877. package/src/prompts/system/personalities/friendly.md +17 -0
  878. package/src/prompts/system/personalities/pragmatic.md +15 -0
  879. package/src/prompts/system/plan-mode-active.md +70 -77
  880. package/src/prompts/system/plan-mode-approved.md +1 -1
  881. package/src/prompts/system/plan-mode-subagent.md +4 -5
  882. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  883. package/src/prompts/system/project-prompt.md +2 -2
  884. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  885. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  886. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  887. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  888. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  889. package/src/prompts/system/subagent-system-prompt.md +7 -8
  890. package/src/prompts/system/system-prompt.md +28 -57
  891. package/src/prompts/system/tiny-title-system.md +1 -1
  892. package/src/prompts/system/title-marker-instruction.md +1 -0
  893. package/src/prompts/system/title-system-marker.md +16 -0
  894. package/src/prompts/system/title-system.md +16 -3
  895. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  896. package/src/prompts/system/workflow-notice.md +4 -4
  897. package/src/prompts/tools/ast-edit.md +1 -1
  898. package/src/prompts/tools/ast-grep.md +2 -2
  899. package/src/prompts/tools/bash.md +16 -8
  900. package/src/prompts/tools/browser.md +33 -43
  901. package/src/prompts/tools/debug.md +1 -1
  902. package/src/prompts/tools/eval.md +31 -51
  903. package/src/prompts/tools/find.md +0 -1
  904. package/src/prompts/tools/github.md +8 -7
  905. package/src/prompts/tools/goal.md +1 -1
  906. package/src/prompts/tools/image-gen.md +1 -1
  907. package/src/prompts/tools/inspect-image-system.md +1 -1
  908. package/src/prompts/tools/irc.md +39 -31
  909. package/src/prompts/tools/job.md +2 -1
  910. package/src/prompts/tools/learn.md +7 -0
  911. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  912. package/src/prompts/tools/lsp.md +2 -2
  913. package/src/prompts/tools/manage-skill.md +9 -0
  914. package/src/prompts/tools/memory-edit.md +1 -1
  915. package/src/prompts/tools/patch.md +2 -2
  916. package/src/prompts/tools/read.md +31 -39
  917. package/src/prompts/tools/recall.md +1 -1
  918. package/src/prompts/tools/reflect.md +1 -1
  919. package/src/prompts/tools/render-mermaid.md +2 -2
  920. package/src/prompts/tools/replace.md +4 -10
  921. package/src/prompts/tools/rewind.md +2 -2
  922. package/src/prompts/tools/search-tool-bm25.md +1 -9
  923. package/src/prompts/tools/search.md +0 -1
  924. package/src/prompts/tools/ssh.md +0 -4
  925. package/src/prompts/tools/task-summary.md +5 -16
  926. package/src/prompts/tools/task.md +47 -31
  927. package/src/prompts/tools/todo.md +6 -3
  928. package/src/registry/agent-lifecycle.ts +218 -0
  929. package/src/registry/agent-registry.ts +46 -5
  930. package/src/sdk.ts +692 -219
  931. package/src/secrets/index.ts +8 -1
  932. package/src/secrets/obfuscator.ts +40 -19
  933. package/src/session/agent-session.ts +1577 -806
  934. package/src/session/agent-storage.ts +18 -9
  935. package/src/session/auth-broker-config.ts +30 -1
  936. package/src/session/auth-storage.ts +6 -0
  937. package/src/session/codex-auto-reset.ts +202 -0
  938. package/src/session/history-storage.ts +3 -2
  939. package/src/session/indexed-session-storage.ts +7 -10
  940. package/src/session/messages.ts +59 -95
  941. package/src/session/session-context.ts +352 -0
  942. package/src/session/session-dump-format.ts +12 -3
  943. package/src/session/session-entries.ts +194 -0
  944. package/src/session/session-history-format.ts +246 -0
  945. package/src/session/session-listing.ts +588 -0
  946. package/src/session/session-loader.ts +106 -0
  947. package/src/session/session-manager.ts +1003 -2920
  948. package/src/session/session-migrations.ts +78 -0
  949. package/src/session/session-paths.ts +193 -0
  950. package/src/session/session-persistence.ts +131 -0
  951. package/src/session/session-storage.ts +91 -30
  952. package/src/session/snapcompact-inline.ts +542 -0
  953. package/src/session/snapcompact-savings-journal.ts +113 -0
  954. package/src/session/streaming-output.ts +248 -11
  955. package/src/session/tool-choice-queue.ts +23 -11
  956. package/src/session/yield-queue.ts +20 -2
  957. package/src/slash-commands/acp-builtins.ts +25 -1
  958. package/src/slash-commands/available-commands.ts +105 -0
  959. package/src/slash-commands/builtin-registry.ts +575 -49
  960. package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
  961. package/src/slash-commands/helpers/context-report.ts +28 -1
  962. package/src/slash-commands/helpers/logout.ts +88 -0
  963. package/src/slash-commands/helpers/reset-usage.ts +66 -0
  964. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  965. package/src/slash-commands/helpers/usage-report.ts +38 -3
  966. package/src/slash-commands/types.ts +5 -9
  967. package/src/ssh/connection-manager.ts +27 -0
  968. package/src/ssh/ssh-executor.ts +60 -4
  969. package/src/stt/asr-client.ts +520 -0
  970. package/src/stt/asr-protocol.ts +65 -0
  971. package/src/stt/asr-worker.ts +790 -0
  972. package/src/stt/downloader.ts +107 -47
  973. package/src/stt/endpointer.ts +259 -0
  974. package/src/stt/index.ts +5 -1
  975. package/src/stt/models.ts +150 -0
  976. package/src/stt/recorder.ts +254 -67
  977. package/src/stt/stt-controller.ts +201 -22
  978. package/src/stt/transcriber.ts +37 -68
  979. package/src/stt/wav.ts +173 -0
  980. package/src/system-prompt.ts +52 -10
  981. package/src/task/agents.ts +3 -4
  982. package/src/task/commands.ts +3 -2
  983. package/src/task/discovery.ts +17 -24
  984. package/src/task/executor.ts +1054 -529
  985. package/src/task/index.ts +862 -757
  986. package/src/task/output-manager.ts +0 -11
  987. package/src/task/parallel.ts +3 -3
  988. package/src/task/prometheus-command.ts +2 -2
  989. package/src/task/render.ts +529 -182
  990. package/src/task/repair-args.ts +21 -9
  991. package/src/task/types.ts +144 -66
  992. package/src/task/worktree.ts +64 -56
  993. package/src/telemetry-export.ts +27 -9
  994. package/src/thinking.ts +9 -7
  995. package/src/tiny/models.ts +2 -2
  996. package/src/tiny/text.ts +5 -1
  997. package/src/tiny/title-client.ts +72 -20
  998. package/src/tiny/title-protocol.ts +1 -1
  999. package/src/tiny/worker.ts +23 -99
  1000. package/src/tool-discovery/tool-index.ts +2 -0
  1001. package/src/tools/archive-reader.ts +94 -2
  1002. package/src/tools/ask.ts +234 -177
  1003. package/src/tools/ast-edit.ts +136 -80
  1004. package/src/tools/ast-grep.ts +41 -45
  1005. package/src/tools/auto-generated-guard.ts +20 -3
  1006. package/src/tools/bash-interactive.ts +28 -8
  1007. package/src/tools/bash.ts +198 -35
  1008. package/src/tools/browser/attach.ts +26 -7
  1009. package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
  1010. package/src/tools/browser/cmux/rpc.ts +156 -0
  1011. package/src/tools/browser/cmux/socket-client.ts +309 -0
  1012. package/src/tools/browser/launch.ts +11 -2
  1013. package/src/tools/browser/readable.ts +19 -2
  1014. package/src/tools/browser/registry.ts +52 -5
  1015. package/src/tools/browser/render.ts +13 -5
  1016. package/src/tools/browser/tab-protocol.ts +2 -0
  1017. package/src/tools/browser/tab-supervisor.ts +256 -34
  1018. package/src/tools/browser/tab-worker.ts +259 -91
  1019. package/src/tools/browser.ts +44 -2
  1020. package/src/tools/checkpoint.ts +1 -1
  1021. package/src/tools/conflict-detect.ts +50 -4
  1022. package/src/tools/debug.ts +27 -12
  1023. package/src/tools/eval-render.ts +32 -35
  1024. package/src/tools/eval.ts +26 -12
  1025. package/src/tools/fetch.ts +450 -99
  1026. package/src/tools/find.ts +182 -142
  1027. package/src/tools/gh-cache-invalidation.ts +255 -0
  1028. package/src/tools/gh-renderer.ts +104 -51
  1029. package/src/tools/gh.ts +232 -37
  1030. package/src/tools/github-cache.ts +97 -7
  1031. package/src/tools/grouped-file-output.ts +159 -52
  1032. package/src/tools/image-gen.ts +237 -132
  1033. package/src/tools/index.ts +147 -26
  1034. package/src/tools/inspect-image-renderer.ts +74 -45
  1035. package/src/tools/inspect-image.ts +12 -6
  1036. package/src/tools/irc.ts +626 -173
  1037. package/src/tools/job.ts +106 -29
  1038. package/src/tools/learn.ts +144 -0
  1039. package/src/tools/manage-skill.ts +104 -0
  1040. package/src/tools/memory-edit.ts +4 -4
  1041. package/src/tools/memory-recall.ts +7 -9
  1042. package/src/tools/memory-reflect.ts +5 -9
  1043. package/src/tools/memory-render.ts +23 -6
  1044. package/src/tools/memory-retain.ts +4 -4
  1045. package/src/tools/path-utils.ts +102 -48
  1046. package/src/tools/plan-mode-guard.ts +101 -40
  1047. package/src/tools/read.ts +475 -120
  1048. package/src/tools/render-mermaid.ts +1 -1
  1049. package/src/tools/render-utils.ts +132 -76
  1050. package/src/tools/renderers.ts +12 -1
  1051. package/src/tools/report-tool-issue.ts +14 -6
  1052. package/src/tools/resolve.ts +20 -3
  1053. package/src/tools/review.ts +2 -2
  1054. package/src/tools/search-tool-bm25.ts +37 -24
  1055. package/src/tools/search.ts +233 -115
  1056. package/src/tools/sqlite-reader.ts +26 -17
  1057. package/src/tools/ssh.ts +20 -14
  1058. package/src/tools/todo.ts +197 -191
  1059. package/src/tools/tool-result.ts +8 -0
  1060. package/src/tools/tool-timeouts.ts +1 -1
  1061. package/src/tools/tts.ts +205 -74
  1062. package/src/tools/write.ts +291 -155
  1063. package/src/tools/yield.ts +10 -1
  1064. package/src/tts/downloader.ts +64 -0
  1065. package/src/tts/index.ts +8 -0
  1066. package/src/tts/models.ts +137 -0
  1067. package/src/tts/player.ts +137 -0
  1068. package/src/tts/runtime.ts +21 -0
  1069. package/src/tts/streaming-player.ts +266 -0
  1070. package/src/tts/tts-client.ts +647 -0
  1071. package/src/tts/tts-protocol.ts +60 -0
  1072. package/src/tts/tts-worker.ts +505 -0
  1073. package/src/tts/vocalizer.ts +162 -0
  1074. package/src/tts/wav.ts +58 -0
  1075. package/src/tui/code-cell.ts +2 -7
  1076. package/src/tui/hyperlink.ts +40 -26
  1077. package/src/tui/output-block.ts +60 -108
  1078. package/src/tui/status-line.ts +5 -1
  1079. package/src/utils/block-context.ts +312 -0
  1080. package/src/utils/changelog.ts +27 -1
  1081. package/src/utils/clipboard.ts +91 -22
  1082. package/src/utils/commit-message-generator.ts +8 -3
  1083. package/src/utils/enhanced-paste.ts +230 -0
  1084. package/src/utils/file-mentions.ts +3 -1
  1085. package/src/utils/git.ts +315 -15
  1086. package/src/utils/image-loading.ts +65 -4
  1087. package/src/utils/session-color.ts +83 -9
  1088. package/src/utils/thinking-display.ts +37 -0
  1089. package/src/utils/title-generator.ts +73 -10
  1090. package/src/utils/tool-choice.ts +16 -0
  1091. package/src/utils/tools-manager.ts +19 -1
  1092. package/src/web/kagi.ts +28 -26
  1093. package/src/web/parallel.ts +7 -3
  1094. package/src/web/scrapers/arxiv.ts +1 -1
  1095. package/src/web/scrapers/github.ts +351 -3
  1096. package/src/web/scrapers/go-pkg.ts +1 -1
  1097. package/src/web/scrapers/iacr.ts +1 -1
  1098. package/src/web/scrapers/readthedocs.ts +1 -1
  1099. package/src/web/scrapers/twitter.ts +2 -1
  1100. package/src/web/scrapers/types.ts +87 -8
  1101. package/src/web/scrapers/wikipedia.ts +1 -1
  1102. package/src/web/scrapers/youtube.ts +9 -3
  1103. package/src/web/search/index.ts +15 -2
  1104. package/src/web/search/providers/anthropic.ts +62 -21
  1105. package/src/web/search/providers/base.ts +2 -1
  1106. package/src/web/search/providers/brave.ts +5 -2
  1107. package/src/web/search/providers/codex.ts +87 -51
  1108. package/src/web/search/providers/exa.ts +101 -10
  1109. package/src/web/search/providers/gemini.ts +49 -24
  1110. package/src/web/search/providers/jina.ts +15 -5
  1111. package/src/web/search/providers/kagi.ts +9 -2
  1112. package/src/web/search/providers/kimi.ts +45 -20
  1113. package/src/web/search/providers/parallel.ts +39 -24
  1114. package/src/web/search/providers/perplexity.ts +226 -63
  1115. package/src/web/search/providers/searxng.ts +19 -3
  1116. package/src/web/search/providers/synthetic.ts +16 -11
  1117. package/src/web/search/providers/tavily.ts +12 -9
  1118. package/src/web/search/providers/zai.ts +22 -9
  1119. package/src/web/search/render.ts +59 -64
  1120. package/src/web/search/types.ts +5 -1
  1121. package/dist/types/discovery/context-files.d.ts +0 -17
  1122. package/dist/types/exa/factory.d.ts +0 -13
  1123. package/dist/types/exa/render.d.ts +0 -19
  1124. package/dist/types/exa/researcher.d.ts +0 -9
  1125. package/dist/types/exa/search.d.ts +0 -9
  1126. package/dist/types/exa/websets.d.ts +0 -9
  1127. package/dist/types/export/html/template.generated.d.ts +0 -1
  1128. package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
  1129. package/dist/types/modes/components/status-line.d.ts +0 -77
  1130. package/dist/types/slash-commands/headless-plan.d.ts +0 -3
  1131. package/dist/types/stt/setup.d.ts +0 -18
  1132. package/scripts/generate-template.ts +0 -33
  1133. package/src/discovery/context-files.ts +0 -49
  1134. package/src/exa/factory.ts +0 -60
  1135. package/src/exa/render.ts +0 -244
  1136. package/src/exa/researcher.ts +0 -36
  1137. package/src/exa/search.ts +0 -47
  1138. package/src/exa/websets.ts +0 -248
  1139. package/src/export/html/template.generated.ts +0 -2
  1140. package/src/modes/components/session-observer-overlay.ts +0 -852
  1141. package/src/slash-commands/headless-plan.ts +0 -142
  1142. package/src/stt/setup.ts +0 -52
  1143. package/src/stt/transcribe.py +0 -70
  1144. /package/dist/types/extensibility/{legacy-pi-coding-agent-shim.d.ts → legacy-package-agent-shim.d.ts} +0 -0
  1145. /package/src/extensibility/{legacy-pi-coding-agent-shim.ts → legacy-package-agent-shim.ts} +0 -0
@@ -1,96 +1,34 @@
1
+ import { execSync } from "node:child_process";
1
2
  import * as path from "node:path";
3
+ import { registerCustomApi, unregisterCustomApis } from "@prometheus-ai/ai/api-registry";
4
+ import type { Api, Context, Model, ModelSpec, SimpleStreamOptions, ThinkingConfig } from "@prometheus-ai/ai/types";
5
+ import type { AssistantMessageEventStream } from "@prometheus-ai/ai/utils/event-stream";
6
+ import { buildModel } from "@prometheus-ai/catalog/build";
7
+ import { isVertexExpressOpenAIUrl } from "@prometheus-ai/catalog/hosts";
8
+ import { readModelCache } from "@prometheus-ai/catalog/model-cache";
2
9
  import {
3
- type Api,
4
- type AssistantMessageEventStream,
5
- type Context,
6
10
  createModelManager,
7
- enrichModelThinking,
8
- getBundledModels,
9
- getBundledProviders,
10
- googleAntigravityModelManagerOptions,
11
- googleGeminiCliModelManagerOptions,
12
- type Model,
13
11
  type ModelManagerOptions,
14
12
  type ModelRefreshStrategy,
13
+ } from "@prometheus-ai/catalog/model-manager";
14
+ import { getBundledModels, getBundledProviders } from "@prometheus-ai/catalog/models";
15
+ import {
16
+ googleAntigravityModelManagerOptions,
17
+ googleGeminiCliModelManagerOptions,
15
18
  openaiCodexModelManagerOptions,
16
19
  PROVIDER_DESCRIPTORS,
17
- readModelCache,
18
- registerCustomApi,
19
- type SimpleStreamOptions,
20
- type ThinkingConfig,
21
- UNK_CONTEXT_WINDOW,
22
- UNK_MAX_TOKENS,
23
- unregisterCustomApis,
24
- } from "@prometheus-ai/ai";
25
-
26
- // Sentinel for local-only OAuth token (LM Studio, vLLM) — declared inline to avoid loading
27
- // any provider module at startup. Must match `DEFAULT_LOCAL_TOKEN` in oauth/lm-studio.ts.
20
+ } from "@prometheus-ai/catalog/provider-models";
21
+ import {
22
+ collapseBuiltModelVariants,
23
+ getVariantAliasSources,
24
+ resolveVariantAlias,
25
+ } from "@prometheus-ai/catalog/variant-collapse";
26
+
27
+ // Sentinels for local-only OAuth tokens — declared inline to avoid loading
28
+ // provider modules at startup. Must match packages/ai/src/registry/lm-studio.ts
29
+ // and packages/ai/src/registry/vllm.ts.
28
30
  const DEFAULT_LOCAL_TOKEN = "lm-studio-local";
29
-
30
- // Default cap on `max_tokens` for auto-discovered models that do not advertise
31
- // their own output limit (OpenAI-models-list, Ollama, llama.cpp, new-api/
32
- // one-api proxies). 32K matches the upper end of what mainstream
33
- // OpenAI-compatible providers (DeepSeek, MiMo, OpenRouter, etc.) actually
34
- // accept and keeps `min(contextWindow, …)` honoring smaller local windows.
35
- // Conservative caps below this caused providers to drop the connection
36
- // mid-stream when models hit the cap on legitimate large tool calls (see
37
- // issue #1528: `write` payloads >~5KB on deepseek-v4-pro surfaced as
38
- // "socket connection was closed unexpectedly").
39
- const DISCOVERY_DEFAULT_MAX_TOKENS = 32_768;
40
-
41
- const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
42
- const OLLAMA_HOST_DEFAULT_PORT = "11434";
43
-
44
- function normalizeOllamaHostEnv(value: string | undefined): string | undefined {
45
- const trimmed = value?.trim();
46
- if (!trimmed) return undefined;
47
- const candidate = trimmed.includes("://")
48
- ? trimmed
49
- : trimmed.startsWith("//")
50
- ? `http:${trimmed}`
51
- : trimmed.startsWith(":")
52
- ? `http://127.0.0.1${trimmed}`
53
- : `http://${trimmed}`;
54
- try {
55
- const parsed = new URL(candidate);
56
- if (!parsed.hostname || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
57
- return undefined;
58
- }
59
- if (!parsed.port && parsed.protocol === "http:") {
60
- parsed.port = OLLAMA_HOST_DEFAULT_PORT;
61
- }
62
- return `${parsed.protocol}//${parsed.host}`;
63
- } catch {
64
- return undefined;
65
- }
66
- }
67
-
68
- function getImplicitOllamaBaseUrl(): string {
69
- const baseUrl = Bun.env.OLLAMA_BASE_URL?.trim();
70
- return baseUrl || normalizeOllamaHostEnv(Bun.env.OLLAMA_HOST) || DEFAULT_OLLAMA_BASE_URL;
71
- }
72
-
73
- function getOllamaContextLengthOverride(): number | undefined {
74
- const value = Bun.env.OLLAMA_CONTEXT_LENGTH?.trim();
75
- if (!value) return undefined;
76
- const parsed = Number(value);
77
- return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
78
- }
79
-
80
- // Anthropic-safe variant of the discovery cap. The Anthropic stream converter
81
- // in `packages/ai/src/providers/anthropic.ts` derives the request limit as
82
- // `(model.maxTokens / 3) | 0`, so the 32K default would surface as 10,922
83
- // requested output tokens — above the 8,192 hard cap on classic Claude 3.x
84
- // Sonnet/Haiku/Opus endpoints. Discovered models routed through
85
- // `anthropic-messages` (proxy `supported_endpoint_types: ["anthropic"]` or a
86
- // custom provider with `api: anthropic-messages` + openai-models-list
87
- // discovery) fall back to this conservative value.
88
- const DISCOVERY_DEFAULT_MAX_TOKENS_ANTHROPIC = 8_192;
89
-
90
- /** Routes discovered-model `maxTokens` defaults around Anthropic's 3× output divisor. */
91
- function discoveryDefaultMaxTokens(api: Api | undefined): number {
92
- return api === "anthropic-messages" ? DISCOVERY_DEFAULT_MAX_TOKENS_ANTHROPIC : DISCOVERY_DEFAULT_MAX_TOKENS;
93
- }
31
+ const DEFAULT_VLLM_LOCAL_TOKEN = "vllm-local";
94
32
 
95
33
  const SPECIAL_MODEL_MANAGER_PROVIDER_IDS: readonly string[] = [
96
34
  "google-antigravity",
@@ -103,35 +41,40 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
103
41
  ...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
104
42
  ];
105
43
 
106
- import { registerOAuthProvider, unregisterOAuthProviders } from "@prometheus-ai/ai/utils/oauth";
107
- import type { OAuthCredentials, OAuthLoginCallbacks } from "@prometheus-ai/ai/utils/oauth/types";
108
- import { isRecord, logger } from "@prometheus-ai/utils";
109
- import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
110
- import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
111
- import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
112
- import { type ConfigError, ConfigFile } from "./config-file";
44
+ import type { ApiKeyResolver, FetchImpl } from "@prometheus-ai/ai";
45
+ import { registerOAuthProvider, unregisterOAuthProviders } from "@prometheus-ai/ai/oauth";
46
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@prometheus-ai/ai/oauth/types";
113
47
  import {
114
48
  buildCanonicalModelIndex,
49
+ buildCanonicalModelOrder,
50
+ buildModelProviderPriorityRank,
115
51
  type CanonicalModelIndex,
116
52
  type CanonicalModelRecord,
117
53
  type CanonicalModelVariant,
54
+ type CanonicalVariantPreferences,
118
55
  formatCanonicalVariantSelector,
56
+ getBundledCanonicalReferenceData,
57
+ getBundledModelReferenceIndex,
119
58
  type ModelEquivalenceConfig,
120
- } from "./model-equivalence";
121
- import {
122
- getBracketStrippedModelIdCandidates,
123
- getLongestModelLikeIdSegment,
124
- getModelLikeIdSegments,
125
- stripBracketedModelIdAffixes,
126
- } from "./model-id-affixes";
59
+ resolveCanonicalVariant,
60
+ resolveModelReference,
61
+ } from "@prometheus-ai/catalog/identity";
62
+ import { isRecord, logger } from "@prometheus-ai/utils";
63
+ import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
64
+ import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
65
+ import { type ApiKeyResolverModel, type ApiKeyResolverOptions, createApiKeyResolver } from "./api-key-resolver";
66
+ import type { ConfigError, ConfigFile } from "./config-file";
127
67
  import {
128
- type ModelOverride,
129
- type ModelsConfig,
130
- ModelsConfigSchema,
131
- type ProviderAuthMode,
132
- type ProviderDiscovery,
133
- } from "./models-config-schema";
134
- import { type Settings, settings } from "./settings";
68
+ DISCOVERY_DEFAULT_MAX_TOKENS,
69
+ type DiscoveryContext,
70
+ type DiscoveryProviderConfig,
71
+ discoverModelsByProviderType,
72
+ getImplicitOllamaBaseUrl,
73
+ getOllamaContextLengthOverride,
74
+ } from "./model-discovery";
75
+ import { ModelsConfigFile, type ProviderValidationModel, validateProviderConfiguration } from "./models-config";
76
+ import type { ModelOverride, ModelsConfig, ProviderAuthMode } from "./models-config-schema";
77
+ import { settings } from "./settings";
135
78
 
136
79
  export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig };
137
80
 
@@ -141,196 +84,17 @@ export function isAuthenticated(apiKey: string | undefined | null): apiKey is st
141
84
  return Boolean(apiKey) && apiKey !== kNoAuth;
142
85
  }
143
86
 
144
- export type ModelRole = "default" | "smol" | "slow" | "vision" | "plan" | "designer" | "commit" | "task";
145
-
146
- export interface ModelRoleInfo {
147
- tag?: string;
148
- name: string;
149
- color?: ThemeColor;
150
- }
151
-
152
- export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
153
- default: { tag: "DEFAULT", name: "Default", color: "success" },
154
- smol: { tag: "SMOL", name: "Fast", color: "warning" },
155
- slow: { tag: "SLOW", name: "Thinking", color: "accent" },
156
- vision: { tag: "VISION", name: "Vision", color: "error" },
157
- plan: { tag: "PLAN", name: "Architect", color: "muted" },
158
- designer: { tag: "DESIGNER", name: "Designer", color: "muted" },
159
- commit: { tag: "COMMIT", name: "Commit", color: "dim" },
160
- task: { tag: "TASK", name: "Subtask", color: "muted" },
161
- };
162
-
163
- export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "vision", "plan", "designer", "commit", "task"];
164
-
165
- /** Alias for ModelRoleInfo - used for both built-in and custom roles */
166
- export type RoleInfo = ModelRoleInfo;
167
-
168
- /**
169
- * Return the canonical set of known roles for selector/carousel UI.
170
- *
171
- * Built-ins always come first. Configured cycle order, model assignments, and
172
- * tag metadata can introduce additional custom roles without requiring duplicate
173
- * entries across settings.
174
- */
175
- export function getKnownRoleIds(settings: Settings): string[] {
176
- const roles = [...MODEL_ROLE_IDS] as string[];
177
- const seen = new Set<string>(roles);
178
- const addRole = (role: string) => {
179
- if (seen.has(role)) return;
180
- seen.add(role);
181
- roles.push(role);
182
- };
183
-
184
- for (const role of settings.get("cycleOrder")) addRole(role);
185
- for (const role of Object.keys(settings.getModelRoles())) addRole(role);
186
- for (const role of Object.keys(settings.get("modelTags"))) addRole(role);
187
-
188
- return roles;
87
+ function isDiscoveryBearerApiKey(apiKey: string | undefined | null): apiKey is string {
88
+ return isAuthenticated(apiKey) && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== DEFAULT_VLLM_LOCAL_TOKEN;
189
89
  }
190
90
 
191
- /**
192
- * Get role info for a role name (built-in or custom).
193
- * Configured metadata overrides built-in defaults when present.
194
- */
195
- export function getRoleInfo(role: string, settings: Settings): RoleInfo {
196
- const builtIn = role in MODEL_ROLES ? MODEL_ROLES[role as ModelRole] : undefined;
197
- const configured = settings.get("modelTags")[role];
198
-
199
- if (configured) {
200
- return {
201
- tag: builtIn?.tag,
202
- name: configured.name || builtIn?.name || role,
203
- color: configured.color && isValidThemeColor(configured.color) ? configured.color : builtIn?.color,
204
- };
205
- }
206
-
207
- if (builtIn) return builtIn;
208
-
209
- return { name: role, color: "muted" };
210
- }
211
-
212
- type ProviderValidationMode = "models-config" | "runtime-register";
213
-
214
- interface ProviderValidationModel {
215
- id: string;
216
- api?: Api;
217
- contextWindow?: number;
218
- maxTokens?: number;
219
- }
220
-
221
- interface ProviderValidationConfig {
222
- baseUrl?: string;
223
- headers?: Record<string, string>;
224
- apiKey?: string;
225
- api?: Api;
226
- auth?: ProviderAuthMode;
227
- oauthConfigured?: boolean;
228
- discovery?: ProviderDiscovery;
229
- compat?: Model<Api>["compat"];
230
- disableStrictTools?: boolean;
231
- modelOverrides?: Record<string, unknown>;
232
- models: ProviderValidationModel[];
233
- }
234
-
235
- function validateProviderConfiguration(
236
- providerName: string,
237
- config: ProviderValidationConfig,
238
- mode: ProviderValidationMode,
239
- ): void {
240
- const hasProviderApi = !!config.api;
241
- const models = config.models;
242
-
243
- if (models.length === 0) {
244
- if (mode === "models-config") {
245
- const hasModelOverrides = config.modelOverrides && Object.keys(config.modelOverrides).length > 0;
246
- if (
247
- !config.baseUrl &&
248
- !config.headers &&
249
- !config.compat &&
250
- !config.apiKey &&
251
- !config.disableStrictTools &&
252
- !hasModelOverrides &&
253
- !config.discovery
254
- ) {
255
- throw new Error(
256
- `Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
257
- );
258
- }
259
- }
260
- } else {
261
- if (!config.baseUrl) {
262
- throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
263
- }
264
- const requiresAuth =
265
- mode === "runtime-register"
266
- ? !config.apiKey && !config.oauthConfigured
267
- : !config.apiKey && (config.auth ?? "apiKey") !== "none";
268
- if (requiresAuth) {
269
- throw new Error(
270
- mode === "runtime-register"
271
- ? `Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`
272
- : `Provider ${providerName}: "apiKey" is required when defining custom models unless auth is "none".`,
273
- );
274
- }
275
- }
276
-
277
- if (mode === "models-config" && config.discovery && !config.api && config.discovery.type !== "proxy") {
278
- throw new Error(`Provider ${providerName}: "api" is required when discovery is enabled at provider level.`);
279
- }
280
-
281
- for (const modelDef of models) {
282
- if (!hasProviderApi && !modelDef.api) {
283
- throw new Error(
284
- mode === "runtime-register"
285
- ? `Provider ${providerName}, model ${modelDef.id}: no "api" specified.`
286
- : `Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
287
- );
288
- }
289
- if (!modelDef.id) {
290
- throw new Error(`Provider ${providerName}: model missing "id"`);
291
- }
292
- if (mode === "models-config") {
293
- if (modelDef.contextWindow !== undefined && modelDef.contextWindow <= 0) {
294
- throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
295
- }
296
- if (modelDef.maxTokens !== undefined && modelDef.maxTokens <= 0) {
297
- throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);
298
- }
299
- }
300
- }
301
- }
302
-
303
- export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsConfigSchema).withValidation(
304
- "models",
305
- config => {
306
- for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
307
- validateProviderConfiguration(
308
- providerName,
309
- {
310
- baseUrl: providerConfig.baseUrl,
311
- headers: providerConfig.headers,
312
- apiKey: providerConfig.apiKey,
313
- api: providerConfig.api as Api | undefined,
314
- auth: (providerConfig.auth ?? "apiKey") as ProviderAuthMode,
315
- discovery: providerConfig.discovery as ProviderDiscovery | undefined,
316
- compat: providerConfig.compat,
317
- disableStrictTools: providerConfig.disableStrictTools,
318
- modelOverrides: providerConfig.modelOverrides,
319
- models: (providerConfig.models ?? []) as ProviderValidationModel[],
320
- },
321
- "models-config",
322
- );
323
- }
324
- },
325
- );
326
-
327
91
  /** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
328
92
  interface ProviderOverride {
329
93
  baseUrl?: string;
330
94
  headers?: Record<string, string>;
331
95
  apiKey?: string;
332
96
  authHeader?: boolean;
333
- compat?: Model<Api>["compat"];
97
+ compat?: ModelSpec<Api>["compat"];
334
98
  transport?: Model<Api>["transport"];
335
99
  }
336
100
 
@@ -344,9 +108,19 @@ interface ProviderOverride {
344
108
  * `token-plan-sgp.xiaomimimo.com` at discovery time)
345
109
  * 3. Existing bundled baseUrl (the host baked into `models.json`)
346
110
  *
111
+ * `transport` resolution priority:
112
+ * 1. `providerOverride.transport` (e.g. `prometheus-native` for auth-gateway users)
113
+ * 2. `existing.transport` (carried over from boot-time override application)
114
+ * 3. `model.transport` (rarely set — discovery defaults omit it)
115
+ *
347
116
  * Without (1), the user's override would lose to discovery; without (2)
348
117
  * preferred over (3), the bundled `api.xiaomimimo.com` would shadow the
349
118
  * tp- token-plan host and produce 401s on the first stream call.
119
+ * Without explicit transport propagation, an openrouter (or any) entry
120
+ * marked `transport: prometheus-native` in models.yml silently reverts to the
121
+ * default openai-completions transport after the background catalog
122
+ * refresh — so the first `/model` switch after boot hits the raw OpenAI
123
+ * chat-completions URL instead of the gateway's `/v1/pi/stream` (#2555).
350
124
  * See `xiaomi-tp-discovery-merge.test.ts` and the `refresh()` baseUrl-override
351
125
  * regression in `model-registry.test.ts`.
352
126
  */
@@ -356,19 +130,22 @@ export function mergeDiscoveredModel<TApi extends Api>(
356
130
  providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "transport">,
357
131
  ): Model<TApi> {
358
132
  if (existing) {
359
- return {
133
+ return buildModel({
360
134
  ...model,
361
135
  baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
362
136
  headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
363
- };
137
+ transport: providerOverride?.transport ?? existing.transport ?? model.transport,
138
+ compat: model.compatConfig,
139
+ } as ModelSpec<TApi>);
364
140
  }
365
141
  if (providerOverride) {
366
- return {
142
+ return buildModel({
367
143
  ...model,
368
144
  baseUrl: providerOverride.baseUrl ?? model.baseUrl,
369
145
  headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
370
146
  ...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
371
- };
147
+ compat: model.compatConfig,
148
+ } as ModelSpec<TApi>);
372
149
  }
373
150
  return model;
374
151
  }
@@ -383,7 +160,7 @@ function isAuthoritativeProjectCatalogModel(model: Model<Api>): boolean {
383
160
  return (
384
161
  model.provider === "google-vertex" &&
385
162
  model.api === "openai-completions" &&
386
- model.baseUrl.includes("/endpoints/openapi")
163
+ isVertexExpressOpenAIUrl(model.baseUrl)
387
164
  );
388
165
  }
389
166
 
@@ -401,14 +178,32 @@ function dropProviderModels(models: readonly Model<Api>[], providers: ReadonlySe
401
178
  return models.filter(model => !providers.has(model.provider));
402
179
  }
403
180
 
404
- interface DiscoveryProviderConfig {
405
- provider: string;
406
- api: Api;
407
- baseUrl?: string;
408
- headers?: Record<string, string>;
409
- compat?: Model<Api>["compat"];
410
- discovery: ProviderDiscovery;
411
- optional?: boolean;
181
+ /**
182
+ * Merge `incoming` entries into a copy of `base`, keyed by `provider`+`id`.
183
+ * Matches are replaced with `combine(existing, entry)`; new entries are
184
+ * appended as `combine(undefined, entry)`.
185
+ */
186
+ function mergeByModelKey<T extends { provider: string; id: string }>(
187
+ base: readonly Model<Api>[],
188
+ incoming: readonly T[],
189
+ combine: (existing: Model<Api> | undefined, entry: T) => Model<Api>,
190
+ ): Model<Api>[] {
191
+ const merged = [...base];
192
+ const indexByKey = new Map<string, number>();
193
+ for (let i = 0; i < merged.length; i += 1) {
194
+ indexByKey.set(`${merged[i].provider}\u0000${merged[i].id}`, i);
195
+ }
196
+ for (const entry of incoming) {
197
+ const key = `${entry.provider}\u0000${entry.id}`;
198
+ const existingIndex = indexByKey.get(key);
199
+ if (existingIndex !== undefined) {
200
+ merged[existingIndex] = combine(merged[existingIndex], entry);
201
+ } else {
202
+ merged.push(combine(undefined, entry));
203
+ indexByKey.set(key, merged.length - 1);
204
+ }
205
+ }
206
+ return merged;
412
207
  }
413
208
 
414
209
  interface BuiltInDiscoveryResult {
@@ -433,6 +228,12 @@ export interface CanonicalModelQueryOptions {
433
228
  candidates?: readonly Model<Api>[];
434
229
  }
435
230
 
231
+ /** A canonical record (with query-filtered variants) plus the variant model selected for it. */
232
+ export interface CanonicalModelSelection {
233
+ record: CanonicalModelRecord;
234
+ model: Model<Api>;
235
+ }
236
+
436
237
  /** Result of loading custom models from models.json */
437
238
  interface CustomModelsResult {
438
239
  models?: CustomModelOverlay[];
@@ -446,78 +247,50 @@ interface CustomModelsResult {
446
247
  found: boolean;
447
248
  }
448
249
 
449
- type OllamaDiscoveredModelMetadata = {
450
- reasoning: boolean;
451
- input: ("text" | "image")[];
452
- contextWindow?: number;
453
- };
250
+ const commandValueCache = new Map<string, string>();
454
251
 
455
- type LlamaCppDiscoveredServerMetadata = {
456
- contextWindow?: number;
457
- input?: ("text" | "image")[];
458
- };
459
-
460
- /**
461
- * Resolve an API key config value to an actual key.
462
- * Checks environment variable first, then treats as literal.
463
- */
464
- function resolveApiKeyConfig(keyConfig: string): string | undefined {
465
- const envValue = Bun.env[keyConfig];
466
- if (envValue) return envValue;
467
- return keyConfig;
252
+ function isCommandConfigValue(valueConfig: string | undefined): valueConfig is string {
253
+ return valueConfig?.startsWith("!") === true;
468
254
  }
469
255
 
470
- function toPositiveNumberOrUndefined(value: unknown): number | undefined {
471
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
472
- return value;
473
- }
474
- if (typeof value === "string" && value.trim()) {
475
- const parsed = Number(value);
476
- if (Number.isFinite(parsed) && parsed > 0) {
477
- return parsed;
478
- }
479
- }
480
- return undefined;
481
- }
482
-
483
- function extractOllamaContextWindow(payload: Record<string, unknown>): number | undefined {
484
- const modelInfo = payload.model_info;
485
- if (isRecord(modelInfo)) {
486
- for (const [key, value] of Object.entries(modelInfo)) {
487
- if (key === "context_length" || key.endsWith(".context_length")) {
488
- const contextWindow = toPositiveNumberOrUndefined(value);
489
- if (contextWindow !== undefined) {
490
- return contextWindow;
491
- }
492
- }
493
- }
494
- }
495
-
496
- const parameters = payload.parameters;
497
- if (typeof parameters !== "string") {
256
+ function resolveCommandConfig(command: string): string | undefined {
257
+ const cached = commandValueCache.get(command);
258
+ if (cached !== undefined) return cached;
259
+ try {
260
+ const stdout = execSync(command, { encoding: "utf8", timeout: 10_000, windowsHide: true });
261
+ const trimmed = stdout.trim();
262
+ if (trimmed.length === 0) return undefined;
263
+ commandValueCache.set(command, trimmed);
264
+ return trimmed;
265
+ } catch {
498
266
  return undefined;
499
267
  }
500
- const match = parameters.match(/(?:^|\n)\s*num_ctx\s+(\d+)\s*(?:$|\n)/m);
501
- return match ? toPositiveNumberOrUndefined(match[1]) : undefined;
502
268
  }
503
269
 
504
- function extractLlamaCppContextWindow(payload: Record<string, unknown>): number | undefined {
505
- const generationSettings = payload.default_generation_settings;
506
- if (isRecord(generationSettings)) {
507
- const contextWindow = toPositiveNumberOrUndefined(generationSettings.n_ctx);
508
- if (contextWindow !== undefined) {
509
- return contextWindow;
510
- }
511
- }
512
- return toPositiveNumberOrUndefined(payload.n_ctx);
270
+ interface CommandApiKeyResolution {
271
+ configured: boolean;
272
+ value?: string;
273
+ }
274
+ /**
275
+ * Resolve a models.yml secret/config value to an actual value.
276
+ * `!cmd` runs a shell command and returns trimmed stdout, otherwise env vars are
277
+ * checked first and the input falls back to a literal value.
278
+ */
279
+ function resolveConfigValue(valueConfig: string): string | undefined {
280
+ if (valueConfig.startsWith("!")) return resolveCommandConfig(valueConfig.slice(1).trim());
281
+ const envValue = Bun.env[valueConfig];
282
+ if (envValue) return envValue;
283
+ return valueConfig;
513
284
  }
514
285
 
515
- function extractLlamaCppInputCapabilities(payload: Record<string, unknown>): ("text" | "image")[] | undefined {
516
- const modalities = payload.modalities;
517
- if (!isRecord(modalities)) {
518
- return undefined;
286
+ function resolveConfigHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {
287
+ if (!headers) return undefined;
288
+ const resolved: Record<string, string> = {};
289
+ for (const [key, value] of Object.entries(headers)) {
290
+ const next = resolveConfigValue(value);
291
+ if (next) resolved[key] = next;
519
292
  }
520
- return modalities.vision === true ? ["text", "image"] : ["text"];
293
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
521
294
  }
522
295
 
523
296
  function extractGoogleOAuthToken(value: string | undefined): string | undefined {
@@ -578,73 +351,99 @@ function mergeCompat<TBase extends object, TOverride extends object>(
578
351
  return merged as TBase & TOverride;
579
352
  }
580
353
 
581
- function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {
582
- const result = { ...model };
583
- if (override.name !== undefined) result.name = override.name;
584
- if (override.reasoning !== undefined) result.reasoning = override.reasoning;
585
- if (override.thinking !== undefined) result.thinking = override.thinking as ThinkingConfig;
586
- if (override.input !== undefined) result.input = override.input as ("text" | "image")[];
587
- if (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;
588
- if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
589
- if (override.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = override.omitMaxOutputTokens;
590
- if (override.contextPromotionTarget !== undefined) result.contextPromotionTarget = override.contextPromotionTarget;
591
- if (override.premiumMultiplier !== undefined) result.premiumMultiplier = override.premiumMultiplier;
592
- if (override.cost) {
593
- result.cost = {
594
- input: override.cost.input ?? model.cost.input,
595
- output: override.cost.output ?? model.cost.output,
596
- cacheRead: override.cost.cacheRead ?? model.cost.cacheRead,
597
- cacheWrite: override.cost.cacheWrite ?? model.cost.cacheWrite,
598
- };
599
- }
600
- if (override.headers) {
601
- result.headers = { ...model.headers, ...override.headers };
602
- }
603
- result.compat = mergeCompat(model.compat, override.compat);
604
- return enrichModelThinking(result);
354
+ /**
355
+ * Project a built model back to spec shape for the model-manager/cache
356
+ * boundary: sparse compat comes from `compatConfig`, never from the resolved
357
+ * record.
358
+ */
359
+ function toModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi> {
360
+ return { ...model, compat: model.compatConfig } as ModelSpec<TApi>;
605
361
  }
606
362
 
607
- interface CustomModelDefinitionLike {
608
- id: string;
363
+ /**
364
+ * The patchable subset of `Model` fields shared by `modelOverrides` entries,
365
+ * custom model definitions, and parsed custom-model overlays. `undefined`
366
+ * always means "leave the base value alone".
367
+ */
368
+ interface ModelPatch {
609
369
  name?: string;
610
- api?: Api;
611
- baseUrl?: string;
612
370
  reasoning?: boolean;
613
371
  thinking?: ThinkingConfig;
614
372
  input?: ("text" | "image")[];
615
- cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
373
+ cost?: Partial<Model<Api>["cost"]>;
616
374
  contextWindow?: number;
617
375
  maxTokens?: number;
618
376
  omitMaxOutputTokens?: boolean;
619
377
  headers?: Record<string, string>;
620
- compat?: Model<Api>["compat"];
378
+ compat?: ModelSpec<Api>["compat"];
621
379
  contextPromotionTarget?: string;
622
380
  premiumMultiplier?: number;
623
381
  }
624
382
 
383
+ /**
384
+ * How a patch treats the base model's transport metadata (headers/compat):
385
+ * - `merge`: fold the patch into the base's (modelOverrides semantics).
386
+ * - `replace`: the patch owns transport wholesale — same-id custom definitions
387
+ * already folded provider-level headers/compat in during parsing, so bundled
388
+ * transport metadata must not be re-merged (see `#mergeCustomModels`).
389
+ */
390
+ type ModelTransportPolicy = "merge" | "replace";
391
+
392
+ function applyModelPatch(base: Model<Api>, patch: ModelPatch, transport: ModelTransportPolicy): Model<Api> {
393
+ const result = { ...base };
394
+ if (patch.name !== undefined) result.name = patch.name;
395
+ if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
396
+ if (patch.thinking !== undefined) result.thinking = patch.thinking;
397
+ if (patch.input !== undefined) result.input = patch.input;
398
+ if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
399
+ if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
400
+ if (patch.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = patch.omitMaxOutputTokens;
401
+ if (patch.contextPromotionTarget !== undefined) result.contextPromotionTarget = patch.contextPromotionTarget;
402
+ if (patch.premiumMultiplier !== undefined) result.premiumMultiplier = patch.premiumMultiplier;
403
+ if (patch.cost) {
404
+ result.cost = {
405
+ input: patch.cost.input ?? base.cost.input,
406
+ output: patch.cost.output ?? base.cost.output,
407
+ cacheRead: patch.cost.cacheRead ?? base.cost.cacheRead,
408
+ cacheWrite: patch.cost.cacheWrite ?? base.cost.cacheWrite,
409
+ };
410
+ }
411
+ let compat: ModelSpec<Api>["compat"];
412
+ if (transport === "merge") {
413
+ if (patch.headers) {
414
+ result.headers = { ...base.headers, ...patch.headers };
415
+ }
416
+ compat = mergeCompat(base.compatConfig, patch.compat);
417
+ } else {
418
+ result.headers = patch.headers;
419
+ compat = patch.compat;
420
+ }
421
+ return buildModel({ ...result, compat } as ModelSpec<Api>);
422
+ }
423
+
424
+ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {
425
+ return applyModelPatch(model, override as ModelPatch, "merge");
426
+ }
427
+
428
+ interface CustomModelDefinitionLike extends ModelPatch {
429
+ id: string;
430
+ api?: Api;
431
+ baseUrl?: string;
432
+ cost?: Model<Api>["cost"];
433
+ }
434
+
625
435
  interface CustomModelBuildOptions {
626
436
  useDefaults: boolean;
627
437
  }
628
438
 
629
- type CustomModelOverlay = {
439
+ interface CustomModelOverlay extends ModelPatch {
630
440
  id: string;
631
441
  provider: string;
632
442
  api: Api;
633
443
  baseUrl: string;
634
- name?: string;
635
- reasoning?: boolean;
636
- thinking?: ThinkingConfig;
637
- input?: ("text" | "image")[];
638
- cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
639
- contextWindow?: number;
640
- maxTokens?: number;
641
- omitMaxOutputTokens?: boolean;
642
- headers?: Record<string, string>;
643
- compat?: Model<Api>["compat"];
644
- contextPromotionTarget?: string;
645
- premiumMultiplier?: number;
444
+ cost?: Model<Api>["cost"];
646
445
  isOAuth?: boolean;
647
- };
446
+ }
648
447
 
649
448
  function mergeCustomModelHeaders(
650
449
  providerHeaders: Record<string, string> | undefined,
@@ -652,7 +451,8 @@ function mergeCustomModelHeaders(
652
451
  authHeader: boolean | undefined,
653
452
  apiKeyConfig: string | undefined,
654
453
  ): Record<string, string> | undefined {
655
- return mergeAuthHeader({ ...providerHeaders, ...modelHeaders }, authHeader, apiKeyConfig);
454
+ const resolvedModelHeaders = resolveConfigHeaders(modelHeaders);
455
+ return mergeAuthHeader({ ...providerHeaders, ...resolvedModelHeaders }, authHeader, apiKeyConfig);
656
456
  }
657
457
 
658
458
  function mergeAuthHeader(
@@ -664,7 +464,7 @@ function mergeAuthHeader(
664
464
  if (!authHeader || !apiKeyConfig) {
665
465
  return nextHeaders;
666
466
  }
667
- const resolvedKey = resolveApiKeyConfig(apiKeyConfig);
467
+ const resolvedKey = resolveConfigValue(apiKeyConfig);
668
468
  return resolvedKey ? { ...nextHeaders, Authorization: `Bearer ${resolvedKey}` } : nextHeaders;
669
469
  }
670
470
 
@@ -691,7 +491,7 @@ function buildCustomModelOverlay(
691
491
  providerHeaders: Record<string, string> | undefined,
692
492
  providerApiKey: string | undefined,
693
493
  authHeader: boolean | undefined,
694
- providerCompat: Model<Api>["compat"] | undefined,
494
+ providerCompat: ModelSpec<Api>["compat"] | undefined,
695
495
  providerAuth: ProviderAuthMode | undefined,
696
496
  modelDef: CustomModelDefinitionLike,
697
497
  ): CustomModelOverlay | undefined {
@@ -704,8 +504,8 @@ function buildCustomModelOverlay(
704
504
  baseUrl: modelDef.baseUrl ?? providerBaseUrl,
705
505
  name: modelDef.name,
706
506
  reasoning: modelDef.reasoning,
707
- thinking: modelDef.thinking as ThinkingConfig | undefined,
708
- input: modelDef.input as ("text" | "image")[] | undefined,
507
+ thinking: modelDef.thinking,
508
+ input: modelDef.input,
709
509
  cost: modelDef.cost,
710
510
  contextWindow: modelDef.contextWindow,
711
511
  maxTokens: modelDef.maxTokens,
@@ -718,125 +518,6 @@ function buildCustomModelOverlay(
718
518
  };
719
519
  }
720
520
 
721
- // Custom provider entries often front a known upstream model through a local proxy.
722
- // Use bundled metadata for missing pricing/capability fields, but keep the custom transport.
723
- function shouldReplaceCustomReference(existing: Model<Api> | undefined, candidate: Model<Api>): boolean {
724
- if (!existing) return true;
725
- if (candidate.contextWindow !== existing.contextWindow) {
726
- return candidate.contextWindow > existing.contextWindow;
727
- }
728
- if (candidate.maxTokens !== existing.maxTokens) {
729
- return candidate.maxTokens > existing.maxTokens;
730
- }
731
- const existingHasCachePricing = existing.cost.cacheRead > 0 || existing.cost.cacheWrite > 0;
732
- const candidateHasCachePricing = candidate.cost.cacheRead > 0 || candidate.cost.cacheWrite > 0;
733
- if (candidateHasCachePricing !== existingHasCachePricing) {
734
- return candidateHasCachePricing;
735
- }
736
- return existing.provider !== "openai" && candidate.provider === "openai";
737
- }
738
-
739
- function normalizeCustomReferenceKey(value: string): string {
740
- return value.trim().toLowerCase();
741
- }
742
-
743
- function buildCustomReferenceMap(): Map<string, Model<Api>> {
744
- const references = new Map<string, Model<Api>>();
745
- for (const provider of getBundledProviders()) {
746
- for (const model of getBundledModels(provider as Parameters<typeof getBundledModels>[0])) {
747
- const candidate = model as Model<Api>;
748
- const key = normalizeCustomReferenceKey(candidate.id);
749
- if (shouldReplaceCustomReference(references.get(key), candidate)) {
750
- references.set(key, candidate);
751
- }
752
- }
753
- }
754
- return references;
755
- }
756
-
757
- function buildCustomReferenceSuffixAliasMap(exactReferences: ReadonlyMap<string, Model<Api>>): Map<string, Model<Api>> {
758
- const aliases = new Map<string, Model<Api>>();
759
- for (const reference of exactReferences.values()) {
760
- const slashIndex = reference.id.lastIndexOf("/");
761
- if (slashIndex === -1) {
762
- continue;
763
- }
764
- const suffix = reference.id.slice(slashIndex + 1);
765
- const alias = getLongestModelLikeIdSegment(suffix);
766
- if (!alias) {
767
- continue;
768
- }
769
- if (shouldReplaceCustomReference(aliases.get(alias), reference)) {
770
- aliases.set(alias, reference);
771
- }
772
- }
773
- return aliases;
774
- }
775
-
776
- const customReferenceMap = buildCustomReferenceMap();
777
- const customReferenceSuffixAliasMap = buildCustomReferenceSuffixAliasMap(customReferenceMap);
778
-
779
- const CUSTOM_REFERENCE_TRAILING_MARKER_PATTERN =
780
- /[-:](?:thinking|customtools|high|low|medium|minimal|xhigh|free|cloud|exacto|nitro|original|optimized|nvfp4|fp8|fp4|bf16|int8|int4|search)$/i;
781
-
782
- function stripCustomReferenceTrailingMarker(candidate: string): string | undefined {
783
- const match = CUSTOM_REFERENCE_TRAILING_MARKER_PATTERN.exec(candidate);
784
- return match ? candidate.slice(0, match.index) : undefined;
785
- }
786
-
787
- function getCustomReferenceCandidateIds(modelId: string): string[] {
788
- const candidates = new Set<string>();
789
- const queue = [modelId];
790
- for (let index = 0; index < queue.length; index += 1) {
791
- const candidate = queue[index]?.trim();
792
- if (!candidate || candidates.has(candidate)) continue;
793
- candidates.add(candidate);
794
-
795
- for (const stripped of getBracketStrippedModelIdCandidates(candidate)) {
796
- queue.push(stripped);
797
- }
798
- for (const segment of getModelLikeIdSegments(candidate)) {
799
- queue.push(segment);
800
- }
801
-
802
- for (const suffix of [":cloud", "-cloud"] as const) {
803
- if (candidate.toLowerCase().endsWith(suffix)) {
804
- queue.push(candidate.slice(0, -suffix.length));
805
- }
806
- }
807
-
808
- const slashIndex = candidate.lastIndexOf("/");
809
- if (slashIndex !== -1) {
810
- queue.push(candidate.slice(slashIndex + 1));
811
- }
812
-
813
- const colonToDash = candidate.replace(/:/g, "-");
814
- if (colonToDash !== candidate) {
815
- queue.push(colonToDash);
816
- }
817
-
818
- const lowercased = candidate.toLowerCase();
819
- if (lowercased !== candidate) {
820
- queue.push(lowercased);
821
- }
822
-
823
- const strippedMarker = stripCustomReferenceTrailingMarker(candidate);
824
- if (strippedMarker) {
825
- queue.push(strippedMarker);
826
- }
827
- }
828
- return [...candidates];
829
- }
830
-
831
- function resolveCustomModelReference(modelId: string): Model<Api> | undefined {
832
- for (const candidate of getCustomReferenceCandidateIds(modelId)) {
833
- const key = normalizeCustomReferenceKey(candidate);
834
- const reference = customReferenceMap.get(key) ?? customReferenceSuffixAliasMap.get(key);
835
- if (reference) return reference;
836
- }
837
- return undefined;
838
- }
839
-
840
521
  function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomModelOverlay {
841
522
  if (model.id !== "gpt-5.4" || model.provider === "github-copilot" || model.contextWindow !== undefined) {
842
523
  return model;
@@ -846,13 +527,15 @@ function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomMo
846
527
 
847
528
  function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuildOptions): Model<Api> {
848
529
  const resolvedModel = options.useDefaults ? applyStandaloneCustomModelPolicies(model) : model;
849
- const reference = options.useDefaults ? resolveCustomModelReference(resolvedModel.id) : undefined;
530
+ const reference = options.useDefaults
531
+ ? resolveModelReference(resolvedModel.id, getBundledModelReferenceIndex())
532
+ : undefined;
850
533
  const cost =
851
534
  resolvedModel.cost ??
852
535
  reference?.cost ??
853
536
  (options.useDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
854
537
  const input = resolvedModel.input ?? reference?.input ?? (options.useDefaults ? ["text"] : undefined);
855
- return enrichModelThinking({
538
+ return buildModel({
856
539
  id: resolvedModel.id,
857
540
  name: resolvedModel.name ?? (options.useDefaults ? resolvedModel.id : undefined),
858
541
  api: resolvedModel.api,
@@ -862,16 +545,15 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
862
545
  thinking: resolvedModel.thinking ?? reference?.thinking,
863
546
  input: input as ("text" | "image")[],
864
547
  cost,
865
- contextWindow:
866
- resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : undefined),
867
- maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : undefined),
548
+ contextWindow: resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : null),
549
+ maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : null),
868
550
  headers: resolvedModel.headers,
869
551
  omitMaxOutputTokens: resolvedModel.omitMaxOutputTokens ?? reference?.omitMaxOutputTokens,
870
- compat: mergeCompat(reference?.compat, resolvedModel.compat),
552
+ compat: mergeCompat(reference?.compatConfig, resolvedModel.compat),
871
553
  contextPromotionTarget: resolvedModel.contextPromotionTarget,
872
554
  premiumMultiplier: resolvedModel.premiumMultiplier,
873
555
  isOAuth: resolvedModel.isOAuth,
874
- } as Model<Api>);
556
+ } as ModelSpec<Api>);
875
557
  }
876
558
 
877
559
  function normalizeSuppressedSelector(selector: string): string {
@@ -879,7 +561,37 @@ function normalizeSuppressedSelector(selector: string): string {
879
561
  if (!trimmed) return trimmed;
880
562
  const parsed = parseModelString(trimmed);
881
563
  if (!parsed) return trimmed;
882
- return `${parsed.provider}/${parsed.id}`;
564
+ // Retired effort-tier variant ids normalize to their collapsed logical id
565
+ // so persisted suppressions keyed by raw member ids still bind.
566
+ const aliasId = resolveVariantAlias(parsed.provider, parsed.id);
567
+ return `${parsed.provider}/${aliasId ?? parsed.id}`;
568
+ }
569
+
570
+ /**
571
+ * Look up a model's override, falling back to entries keyed by retired
572
+ * effort-tier variant ids (models.yml authored before collapsing). A raw key
573
+ * only re-binds when no live model holds that id.
574
+ */
575
+ function resolveModelOverrideWithAliases(
576
+ overrides: Map<string, ModelOverride>,
577
+ model: Model<Api>,
578
+ hasLiveModel: (provider: string, id: string) => boolean,
579
+ ): ModelOverride | undefined {
580
+ const direct = overrides.get(model.id);
581
+ if (direct) return direct;
582
+ for (const rawId of getVariantAliasSources(model.provider, model.id)) {
583
+ if (hasLiveModel(model.provider, rawId)) continue;
584
+ const remapped = overrides.get(rawId);
585
+ if (remapped) {
586
+ logger.debug("model override re-keyed through variant alias", {
587
+ provider: model.provider,
588
+ from: rawId,
589
+ to: model.id,
590
+ });
591
+ return remapped;
592
+ }
593
+ }
594
+ return undefined;
883
595
  }
884
596
 
885
597
  function getDisabledProviderIdsFromSettings(): Set<string> {
@@ -904,6 +616,7 @@ function getConfiguredProviderOrderFromSettings(): string[] {
904
616
  export class ModelRegistry {
905
617
  #models: Model<Api>[] = [];
906
618
  #canonicalIndex: CanonicalModelIndex = { records: [], byId: new Map(), bySelector: new Map() };
619
+ #canonicalIndexDirty: boolean = true;
907
620
  #customProviderApiKeys: Map<string, string> = new Map();
908
621
  #keylessProviders: Set<string> = new Set();
909
622
  #discoverableProviders: DiscoveryProviderConfig[] = [];
@@ -927,8 +640,34 @@ export class ModelRegistry {
927
640
  #runtimeProviderOverrides: Map<string, ProviderOverride> = new Map();
928
641
  #runtimeProvidersBySource: Map<string, Set<string>> = new Map();
929
642
  #runtimeProviderSourceByName: Map<string, string> = new Map();
643
+ // Runtime model managers registered by extensions via fetchDynamicModels.
644
+ // Keyed by provider name; use the same SQLite cache path as builtins.
645
+ #runtimeModelManagers: Map<string, { options: ModelManagerOptions<Api>; sourceId: string }> = new Map();
930
646
  #rebuildPending: boolean = false;
931
647
  #rebuildSuspended: number = 0;
648
+ #fetch: FetchImpl;
649
+
650
+ #resolveCommandBackedApiKey(provider: string): CommandApiKeyResolution {
651
+ const keyConfig = this.#customProviderApiKeys.get(provider);
652
+ if (!isCommandConfigValue(keyConfig)) return { configured: false };
653
+ const value = resolveConfigValue(keyConfig);
654
+ if (value) {
655
+ this.authStorage.setConfigApiKey(provider, value);
656
+ return { configured: true, value };
657
+ }
658
+ this.authStorage.removeConfigApiKey(provider);
659
+ return { configured: true };
660
+ }
661
+
662
+ #installProviderApiKey(provider: string, keyConfig: string): void {
663
+ this.#customProviderApiKeys.set(provider, keyConfig);
664
+ const resolved = resolveConfigValue(keyConfig);
665
+ if (resolved) {
666
+ this.authStorage.setConfigApiKey(provider, resolved);
667
+ } else if (isCommandConfigValue(keyConfig)) {
668
+ this.authStorage.removeConfigApiKey(provider);
669
+ }
670
+ }
932
671
 
933
672
  /**
934
673
  * @param authStorage - Auth storage for API key resolution
@@ -942,16 +681,16 @@ export class ModelRegistry {
942
681
  constructor(
943
682
  readonly authStorage: AuthStorage,
944
683
  modelsPath?: string,
684
+ options?: { fetch?: FetchImpl },
945
685
  ) {
686
+ this.#fetch = options?.fetch ?? fetch;
946
687
  this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
947
688
  this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
948
689
  // Set up fallback resolver for custom provider API keys
949
690
  this.authStorage.setFallbackResolver(provider => {
950
691
  const keyConfig = this.#customProviderApiKeys.get(provider);
951
- if (keyConfig) {
952
- return resolveApiKeyConfig(keyConfig);
953
- }
954
- return undefined;
692
+ if (!keyConfig) return undefined;
693
+ return resolveConfigValue(keyConfig);
955
694
  });
956
695
  // Load models synchronously in constructor.
957
696
  this.#loadModels();
@@ -1004,6 +743,27 @@ export class ModelRegistry {
1004
743
  }
1005
744
  }
1006
745
 
746
+ /**
747
+ * Discover models for providers registered at runtime via `fetchDynamicModels`
748
+ * (extension providers). Merges the discovered catalog into the existing model
749
+ * set without reloading static models, so dynamically-discovered models from
750
+ * other providers are preserved. No-op when no runtime providers are registered.
751
+ *
752
+ * Drives the same SQLite model cache as built-in providers, so the default
753
+ * `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
754
+ */
755
+ async refreshRuntimeProviders(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
756
+ if (this.#runtimeModelManagers.size === 0) {
757
+ return;
758
+ }
759
+ this.#suspendRebuild();
760
+ try {
761
+ await this.#refreshRuntimeDiscoveries(strategy, new Set(this.#runtimeModelManagers.keys()));
762
+ } finally {
763
+ this.#resumeRebuild();
764
+ }
765
+ }
766
+
1007
767
  #reloadStaticModels(): void {
1008
768
  const currentMtime = this.#modelsConfigFile.getMtimeMs();
1009
769
  if (currentMtime !== null && currentMtime === this.#lastStaticLoadMtime) {
@@ -1021,7 +781,7 @@ export class ModelRegistry {
1021
781
  // Restore runtime API keys before #loadModels — survives because
1022
782
  // #loadModels only calls .set() on #customProviderApiKeys, never reassigns it.
1023
783
  for (const [k, v] of this.#runtimeProviderApiKeys) {
1024
- this.#customProviderApiKeys.set(k, v);
784
+ this.#installProviderApiKey(k, v);
1025
785
  }
1026
786
  this.#providerOverrides.clear();
1027
787
  this.#modelOverrides.clear();
@@ -1089,7 +849,9 @@ export class ModelRegistry {
1089
849
  const withConfigModels = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
1090
850
  // Merge runtime extension models so they survive refresh() cycles
1091
851
  const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
1092
- const withModelOverrides = this.#applyModelOverrides(combined, this.#modelOverrides);
852
+ // Custom/config providers bypass the model-manager merge point —
853
+ // collapse effort-tier variants here so X/X-thinking twins fold.
854
+ const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
1093
855
  this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
1094
856
  this.#rebuildCanonicalIndex();
1095
857
  this.#lastStaticLoadMtime = this.#modelsConfigFile.getMtimeMs();
@@ -1105,84 +867,55 @@ export class ModelRegistry {
1105
867
  return models.map(m => {
1106
868
  if (!providerOverride) return m;
1107
869
  const withTransportOverride = this.#applyProviderTransportOverride(m, providerOverride);
1108
- return {
870
+ return buildModel({
1109
871
  ...withTransportOverride,
1110
- compat: mergeCompat(m.compat, providerOverride.compat),
1111
- };
872
+ compat: mergeCompat(m.compatConfig, providerOverride.compat),
873
+ } as ModelSpec<Api>);
1112
874
  });
1113
875
  });
1114
876
  }
1115
877
 
1116
878
  #mergeResolvedModels(baseModels: Model<Api>[], replacementModels: Model<Api>[]): Model<Api>[] {
1117
- const merged = [...baseModels];
1118
- const indexByKey = new Map<string, number>();
1119
- for (let i = 0; i < merged.length; i += 1) {
1120
- const m = merged[i];
1121
- indexByKey.set(`${m.provider}\u0000${m.id}`, i);
1122
- }
1123
- for (const replacementModel of replacementModels) {
1124
- const key = `${replacementModel.provider}\u0000${replacementModel.id}`;
1125
- const existingIndex = indexByKey.get(key);
1126
- if (existingIndex !== undefined) {
1127
- const existing = merged[existingIndex];
1128
- merged[existingIndex] = {
1129
- ...replacementModel,
1130
- contextWindow:
1131
- replacementModel.contextWindow === UNK_CONTEXT_WINDOW
1132
- ? existing.contextWindow
1133
- : replacementModel.contextWindow,
1134
- maxTokens:
1135
- replacementModel.maxTokens === UNK_MAX_TOKENS ? existing.maxTokens : replacementModel.maxTokens,
1136
- };
1137
- } else {
1138
- merged.push(replacementModel);
1139
- indexByKey.set(key, merged.length - 1);
1140
- }
1141
- }
1142
- return merged;
879
+ return mergeByModelKey(baseModels, replacementModels, (existing, replacementModel) => {
880
+ if (!existing) return replacementModel;
881
+ return {
882
+ ...replacementModel,
883
+ contextWindow: replacementModel.contextWindow ?? existing.contextWindow,
884
+ maxTokens: replacementModel.maxTokens ?? existing.maxTokens,
885
+ };
886
+ });
1143
887
  }
1144
888
 
1145
889
  /** Merge custom models with built-in, replacing by provider+id match */
1146
890
  #mergeCustomModels(builtInModels: Model<Api>[], customModels: CustomModelOverlay[]): Model<Api>[] {
1147
- const merged = [...builtInModels];
1148
- const indexByKey = new Map<string, number>();
1149
- for (let i = 0; i < merged.length; i += 1) {
1150
- const m = merged[i];
1151
- indexByKey.set(`${m.provider}\u0000${m.id}`, i);
1152
- }
1153
- for (const customModel of customModels) {
1154
- const key = `${customModel.provider}\u0000${customModel.id}`;
1155
- const existingIndex = indexByKey.get(key);
1156
- if (existingIndex !== undefined) {
1157
- const existingModel = merged[existingIndex];
1158
- merged[existingIndex] = enrichModelThinking({
891
+ return mergeByModelKey(builtInModels, customModels, (existingModel, customModel) => {
892
+ if (!existingModel) return finalizeCustomModel(customModel, { useDefaults: true });
893
+ // Same-id custom definitions replace bundled transport behavior, so the
894
+ // patch is applied with the `replace` transport policy.
895
+ return applyModelPatch(
896
+ {
1159
897
  ...existingModel,
1160
898
  id: customModel.id,
1161
899
  provider: customModel.provider,
1162
900
  api: customModel.api,
1163
901
  baseUrl: customModel.baseUrl,
1164
- name: customModel.name ?? existingModel.name,
1165
- reasoning: customModel.reasoning ?? existingModel.reasoning,
1166
- thinking: customModel.thinking ?? existingModel.thinking,
1167
- input: customModel.input ?? existingModel.input,
1168
- cost: customModel.cost ?? existingModel.cost,
1169
- contextWindow: customModel.contextWindow ?? existingModel.contextWindow,
1170
- maxTokens: customModel.maxTokens ?? existingModel.maxTokens,
1171
- omitMaxOutputTokens: customModel.omitMaxOutputTokens ?? existingModel.omitMaxOutputTokens,
1172
- // Same-id custom definitions replace bundled transport behavior. Provider-level
1173
- // headers/compat were already folded into customModel during parsing; do not
1174
- // re-merge bundled transport metadata here.
1175
- headers: customModel.headers,
1176
- compat: customModel.compat,
1177
- contextPromotionTarget: customModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
1178
- premiumMultiplier: customModel.premiumMultiplier ?? existingModel.premiumMultiplier,
1179
- } as Model<Api>);
1180
- } else {
1181
- merged.push(finalizeCustomModel(customModel, { useDefaults: true }));
1182
- indexByKey.set(key, merged.length - 1);
1183
- }
902
+ },
903
+ customModel,
904
+ "replace",
905
+ );
906
+ });
907
+ }
908
+
909
+ #resolveStartupModelCacheProviderId(providerId: string): string {
910
+ const descriptor = PROVIDER_DESCRIPTORS.find(candidate => candidate.providerId === providerId);
911
+ if (!descriptor) {
912
+ return providerId;
1184
913
  }
1185
- return merged;
914
+ const baseUrl =
915
+ this.#runtimeProviderOverrides.get(providerId)?.baseUrl ??
916
+ this.#providerOverrides.get(providerId)?.baseUrl ??
917
+ this.getProviderBaseUrl(providerId);
918
+ return descriptor.createModelManagerOptions({ baseUrl, fetch: this.#fetch }).cacheProviderId ?? providerId;
1186
919
  }
1187
920
 
1188
921
  #loadCachedStandardProviderModels(): { models: Model<Api>[]; authoritativeFreshProviders: Set<string> } {
@@ -1193,7 +926,8 @@ export class ModelRegistry {
1193
926
  if (configuredDiscoveryProviders.has(providerId)) {
1194
927
  continue;
1195
928
  }
1196
- const cache = readModelCache<Api>(providerId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
929
+ const cacheProviderId = this.#resolveStartupModelCacheProviderId(providerId);
930
+ const cache = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
1197
931
  if (!cache) {
1198
932
  continue;
1199
933
  }
@@ -1208,8 +942,13 @@ export class ModelRegistry {
1208
942
  ? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
1209
943
  : models;
1210
944
  const withCompat = providerOverride?.compat
1211
- ? withTransport.map(model => ({ ...model, compat: mergeCompat(model.compat, providerOverride.compat) }))
1212
- : withTransport;
945
+ ? withTransport.map(model =>
946
+ buildModel({
947
+ ...model,
948
+ compat: mergeCompat(model.compat, providerOverride.compat),
949
+ } as ModelSpec<Api>),
950
+ )
951
+ : withTransport.map(model => buildModel(model));
1213
952
  cachedModels.push(...this.#applyProviderModelOverrides(providerId, withCompat));
1214
953
  }
1215
954
  return { models: cachedModels, authoritativeFreshProviders };
@@ -1218,7 +957,12 @@ export class ModelRegistry {
1218
957
  #loadCachedDiscoverableModels(): Model<Api>[] {
1219
958
  const cachedModels: Model<Api>[] = [];
1220
959
  for (const providerConfig of this.#discoverableProviders) {
1221
- const cache = readModelCache<Api>(providerConfig.provider, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
960
+ const cache = readModelCache<Api>(
961
+ this.#configuredDiscoveryCacheProviderId(providerConfig),
962
+ 24 * 60 * 60 * 1000,
963
+ Date.now,
964
+ this.#cacheDbPath,
965
+ );
1222
966
  if (!cache) {
1223
967
  this.#providerDiscoveryStates.set(providerConfig.provider, {
1224
968
  provider: providerConfig.provider,
@@ -1233,7 +977,10 @@ export class ModelRegistry {
1233
977
  providerConfig.provider,
1234
978
  this.#normalizeDiscoverableModels(
1235
979
  providerConfig,
1236
- this.#applyProviderCompat(providerConfig.compat, cache.models),
980
+ this.#applyProviderCompat(
981
+ providerConfig.compat,
982
+ cache.models.map(model => buildModel(model)),
983
+ ),
1237
984
  ),
1238
985
  );
1239
986
  cachedModels.push(...models);
@@ -1249,9 +996,11 @@ export class ModelRegistry {
1249
996
  return cachedModels;
1250
997
  }
1251
998
 
1252
- #applyProviderCompat(compat: Model<Api>["compat"] | undefined, models: Model<Api>[]): Model<Api>[] {
999
+ #applyProviderCompat(compat: ModelSpec<Api>["compat"] | undefined, models: Model<Api>[]): Model<Api>[] {
1253
1000
  if (!compat) return models;
1254
- return models.map(model => ({ ...model, compat: mergeCompat(model.compat, compat) }));
1001
+ return models.map(model =>
1002
+ buildModel({ ...model, compat: mergeCompat(model.compatConfig, compat) } as ModelSpec<Api>),
1003
+ );
1255
1004
  }
1256
1005
 
1257
1006
  #normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
@@ -1261,7 +1010,14 @@ export class ModelRegistry {
1261
1010
 
1262
1011
  const contextLengthOverride = getOllamaContextLengthOverride();
1263
1012
  return models.map(model => {
1264
- const normalized = model.api === "openai-completions" ? { ...model, api: "openai-responses" as const } : model;
1013
+ const normalized =
1014
+ model.api === "openai-completions"
1015
+ ? buildModel({
1016
+ ...model,
1017
+ api: "openai-responses" as const,
1018
+ compat: model.compatConfig,
1019
+ } as ModelSpec<Api>)
1020
+ : model;
1265
1021
  if (contextLengthOverride === undefined) {
1266
1022
  return normalized;
1267
1023
  }
@@ -1344,10 +1100,11 @@ export class ModelRegistry {
1344
1100
  const configuredProviders = new Set(Object.keys(value.providers ?? {}));
1345
1101
 
1346
1102
  for (const [providerName, providerConfig] of providerEntries) {
1103
+ const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
1347
1104
  // Always set overrides when baseUrl/headers/apiKey/authHeader/compat/disableStrictTools/transport are present
1348
1105
  if (
1349
1106
  providerConfig.baseUrl ||
1350
- providerConfig.headers ||
1107
+ resolvedProviderHeaders ||
1351
1108
  providerConfig.apiKey ||
1352
1109
  providerConfig.authHeader !== undefined ||
1353
1110
  providerConfig.compat ||
@@ -1357,7 +1114,7 @@ export class ModelRegistry {
1357
1114
  const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
1358
1115
  overrides.set(providerName, {
1359
1116
  baseUrl: providerConfig.baseUrl,
1360
- headers: providerConfig.headers,
1117
+ headers: resolvedProviderHeaders,
1361
1118
  apiKey: providerConfig.apiKey,
1362
1119
  authHeader: providerConfig.authHeader,
1363
1120
  compat: mergeCompat(providerConfig.compat, disableStrictCompat),
@@ -1379,7 +1136,7 @@ export class ModelRegistry {
1379
1136
  // fallback for entries that don't advertise one.
1380
1137
  api: (providerConfig.api ?? "openai-completions") as Api,
1381
1138
  baseUrl: providerConfig.baseUrl,
1382
- headers: providerConfig.headers,
1139
+ headers: resolvedProviderHeaders,
1383
1140
  compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1384
1141
  discovery: providerConfig.discovery,
1385
1142
  optional: false,
@@ -1391,16 +1148,17 @@ export class ModelRegistry {
1391
1148
  // bearer in models.yml (e.g. for an auth-gateway baseUrl), that bearer
1392
1149
  // must authenticate the outbound request.
1393
1150
  if (providerConfig.apiKey) {
1394
- this.#customProviderApiKeys.set(providerName, providerConfig.apiKey);
1395
- const resolved = resolveApiKeyConfig(providerConfig.apiKey);
1396
- if (resolved) this.authStorage.setConfigApiKey(providerName, resolved);
1151
+ this.#installProviderApiKey(providerName, providerConfig.apiKey);
1397
1152
  }
1398
1153
 
1399
1154
  // Parse per-model overrides
1400
1155
  if (providerConfig.modelOverrides) {
1401
1156
  const perModel = new Map<string, ModelOverride>();
1402
1157
  for (const [modelId, override] of Object.entries(providerConfig.modelOverrides)) {
1403
- perModel.set(modelId, override);
1158
+ perModel.set(
1159
+ modelId,
1160
+ override.headers ? { ...override, headers: resolveConfigHeaders(override.headers) } : override,
1161
+ );
1404
1162
  }
1405
1163
  allModelOverrides.set(providerName, perModel);
1406
1164
  }
@@ -1461,16 +1219,24 @@ export class ModelRegistry {
1461
1219
  const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
1462
1220
  // Merge runtime extension models so they survive online discovery completion
1463
1221
  const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
1464
- const withModelOverrides = this.#applyModelOverrides(combined, this.#modelOverrides);
1222
+ const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
1465
1223
  this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
1466
1224
  this.#rebuildCanonicalIndex();
1467
1225
  }
1468
1226
 
1227
+ #configuredDiscoveryCacheProviderId(providerConfig: DiscoveryProviderConfig): string {
1228
+ if (providerConfig.discovery.type === "openai-models-list") {
1229
+ return `${providerConfig.provider}:openai-models-list-context-v2`;
1230
+ }
1231
+ return providerConfig.provider;
1232
+ }
1233
+
1469
1234
  async #discoverProviderModels(
1470
1235
  providerConfig: DiscoveryProviderConfig,
1471
1236
  strategy: ModelRefreshStrategy,
1472
1237
  ): Promise<Model<Api>[]> {
1473
- const cached = readModelCache<Api>(providerConfig.provider, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
1238
+ const cacheProviderId = this.#configuredDiscoveryCacheProviderId(providerConfig);
1239
+ const cached = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
1474
1240
  const requiresAuth = !this.#keylessProviders.has(providerConfig.provider);
1475
1241
  if (requiresAuth) {
1476
1242
  const apiKey = await this.#peekApiKeyForProvider(providerConfig.provider);
@@ -1484,17 +1250,20 @@ export class ModelRegistry {
1484
1250
  models: cached?.models.map(model => model.id) ?? [],
1485
1251
  });
1486
1252
  this.#lastDiscoveryWarnings.delete(providerConfig.provider);
1487
- return cached?.models ?? [];
1253
+ return cached ? cached.models.map(model => buildModel(model)) : [];
1488
1254
  }
1489
1255
  }
1490
1256
 
1491
1257
  const providerId = providerConfig.provider;
1492
1258
  let discoveryError: string | undefined;
1493
- const fetchDynamicModels = async (): Promise<readonly Model<Api>[] | null> => {
1259
+ const fetchDynamicModels = async (): Promise<readonly ModelSpec<Api>[] | null> => {
1494
1260
  try {
1495
- const models = await this.#discoverModelsByProviderType(providerConfig);
1261
+ const models = this.#applyProviderModelOverrides(
1262
+ providerId,
1263
+ await discoverModelsByProviderType(providerConfig, this.#discoveryContext()),
1264
+ );
1496
1265
  this.#lastDiscoveryWarnings.delete(providerId);
1497
- return models;
1266
+ return models.map(toModelSpec);
1498
1267
  } catch (error) {
1499
1268
  discoveryError = error instanceof Error ? error.message : String(error);
1500
1269
  return null;
@@ -1505,6 +1274,7 @@ export class ModelRegistry {
1505
1274
  providerId,
1506
1275
  staticModels: [],
1507
1276
  cacheDbPath: this.#cacheDbPath,
1277
+ cacheProviderId,
1508
1278
  cacheTtlMs: 24 * 60 * 60 * 1000,
1509
1279
  fetchDynamicModels,
1510
1280
  });
@@ -1541,18 +1311,17 @@ export class ModelRegistry {
1541
1311
  );
1542
1312
  }
1543
1313
 
1544
- #discoverModelsByProviderType(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
1545
- switch (providerConfig.discovery.type) {
1546
- case "ollama":
1547
- return this.#discoverOllamaModels(providerConfig);
1548
- case "llama.cpp":
1549
- return this.#discoverLlamaCppModels(providerConfig);
1550
- case "lm-studio":
1551
- case "openai-models-list":
1552
- return this.#discoverOpenAIModelsList(providerConfig);
1553
- case "proxy":
1554
- return this.#discoverProxyModels(providerConfig);
1555
- }
1314
+ #discoveryContext(): DiscoveryContext {
1315
+ return {
1316
+ fetch: this.#fetch,
1317
+ getBearerApiKeyResolver: async provider => {
1318
+ const apiKey = await this.getApiKeyForProvider(provider);
1319
+ if (!isDiscoveryBearerApiKey(apiKey)) {
1320
+ return undefined;
1321
+ }
1322
+ return this.resolver(provider);
1323
+ },
1324
+ };
1556
1325
  }
1557
1326
 
1558
1327
  #warnProviderDiscoveryFailure(providerConfig: DiscoveryProviderConfig, error: string): void {
@@ -1610,6 +1379,7 @@ export class ModelRegistry {
1610
1379
  googleAntigravityModelManagerOptions({
1611
1380
  oauthToken,
1612
1381
  endpoint: this.getProviderBaseUrl("google-antigravity"),
1382
+ fetch: this.#fetch,
1613
1383
  }),
1614
1384
  },
1615
1385
  {
@@ -1619,6 +1389,7 @@ export class ModelRegistry {
1619
1389
  googleGeminiCliModelManagerOptions({
1620
1390
  oauthToken,
1621
1391
  endpoint: this.getProviderBaseUrl("google-gemini-cli"),
1392
+ fetch: this.#fetch,
1622
1393
  }),
1623
1394
  },
1624
1395
  {
@@ -1652,11 +1423,21 @@ export class ModelRegistry {
1652
1423
  for (let i = 0; i < standardProviderDescriptors.length; i++) {
1653
1424
  const descriptor = standardProviderDescriptors[i];
1654
1425
  const apiKey = standardProviderKeys[i];
1655
- if (isAuthenticated(apiKey) || descriptor.allowUnauthenticated) {
1426
+ const hasExplicitVllmConfig =
1427
+ descriptor.providerId === "vllm" &&
1428
+ (this.#runtimeProviderOverrides.has(descriptor.providerId) ||
1429
+ this.#providerOverrides.has(descriptor.providerId) ||
1430
+ this.#keylessProviders.has(descriptor.providerId));
1431
+ if (isAuthenticated(apiKey) || descriptor.allowUnauthenticated || hasExplicitVllmConfig) {
1432
+ const discoveryBaseUrl =
1433
+ this.#runtimeProviderOverrides.get(descriptor.providerId)?.baseUrl ??
1434
+ this.#providerOverrides.get(descriptor.providerId)?.baseUrl ??
1435
+ this.getProviderBaseUrl(descriptor.providerId);
1656
1436
  options.push(
1657
1437
  descriptor.createModelManagerOptions({
1658
- apiKey: isAuthenticated(apiKey) ? apiKey : undefined,
1659
- baseUrl: this.getProviderBaseUrl(descriptor.providerId),
1438
+ apiKey: isDiscoveryBearerApiKey(apiKey) ? apiKey : undefined,
1439
+ baseUrl: discoveryBaseUrl,
1440
+ fetch: this.#fetch,
1660
1441
  }),
1661
1442
  );
1662
1443
  }
@@ -1670,6 +1451,10 @@ export class ModelRegistry {
1670
1451
  }
1671
1452
  options.push(descriptor.createOptions(key));
1672
1453
  }
1454
+ // Append runtime model managers registered by extensions via fetchDynamicModels.
1455
+ for (const { options: managerOpts } of this.#runtimeModelManagers.values()) {
1456
+ options.push(managerOpts);
1457
+ }
1673
1458
  return options;
1674
1459
  }
1675
1460
 
@@ -1697,366 +1482,16 @@ export class ModelRegistry {
1697
1482
  }
1698
1483
  }
1699
1484
 
1700
- async #discoverOllamaModelMetadata(
1701
- endpoint: string,
1702
- modelId: string,
1703
- headers: Record<string, string> | undefined,
1704
- ): Promise<OllamaDiscoveredModelMetadata | null> {
1705
- const showUrl = `${endpoint}/api/show`;
1706
- try {
1707
- const response = await fetch(showUrl, {
1708
- method: "POST",
1709
- headers: { ...(headers ?? {}), "Content-Type": "application/json" },
1710
- body: JSON.stringify({ model: modelId }),
1711
- signal: AbortSignal.timeout(150),
1712
- });
1713
- if (!response.ok) {
1714
- return null;
1715
- }
1716
- const payload = (await response.json()) as unknown;
1717
- if (!isRecord(payload)) {
1718
- return null;
1719
- }
1720
- const contextWindow = extractOllamaContextWindow(payload);
1721
- const capabilities = payload.capabilities;
1722
- if (Array.isArray(capabilities)) {
1723
- const normalized = new Set(
1724
- capabilities.flatMap(capability => (typeof capability === "string" ? [capability.toLowerCase()] : [])),
1725
- );
1726
- const supportsVision = normalized.has("vision") || normalized.has("image");
1727
- return {
1728
- reasoning: normalized.has("thinking"),
1729
- input: supportsVision ? ["text", "image"] : ["text"],
1730
- contextWindow,
1731
- };
1732
- }
1733
- if (!isRecord(capabilities)) {
1734
- return {
1735
- reasoning: false,
1736
- input: ["text"],
1737
- contextWindow,
1738
- };
1739
- }
1740
- const supportsVision = capabilities.vision === true || capabilities.image === true;
1741
- return {
1742
- reasoning: capabilities.thinking === true,
1743
- input: supportsVision ? ["text", "image"] : ["text"],
1744
- contextWindow,
1745
- };
1746
- } catch {
1747
- return null;
1748
- }
1749
- }
1750
-
1751
- async #discoverOllamaModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
1752
- const endpoint = this.#normalizeOllamaBaseUrl(providerConfig.baseUrl);
1753
- const tagsUrl = `${endpoint}/api/tags`;
1754
- const headers = { ...(providerConfig.headers ?? {}) };
1755
- const response = await fetch(tagsUrl, {
1756
- headers,
1757
- signal: AbortSignal.timeout(250),
1758
- });
1759
- if (!response.ok) {
1760
- throw new Error(`HTTP ${response.status} from ${tagsUrl}`);
1761
- }
1762
- const payload = (await response.json()) as { models?: Array<{ name?: string; model?: string }> };
1763
- const entries = (payload.models ?? []).flatMap(item => {
1764
- const id = item.model || item.name;
1765
- return id ? [{ id, name: item.name || id }] : [];
1766
- });
1767
- const metadataById = new Map(
1768
- await Promise.all(
1769
- entries.map(
1770
- async entry => [entry.id, await this.#discoverOllamaModelMetadata(endpoint, entry.id, headers)] as const,
1771
- ),
1772
- ),
1773
- );
1774
- const discovered = entries.map(entry => {
1775
- const metadata = metadataById.get(entry.id);
1776
- return enrichModelThinking({
1777
- id: entry.id,
1778
- name: entry.name,
1779
- api: providerConfig.api,
1780
- provider: providerConfig.provider,
1781
- baseUrl: `${endpoint}/v1`,
1782
- reasoning: metadata?.reasoning ?? false,
1783
- input: metadata?.input ?? ["text"],
1784
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1785
- contextWindow: metadata?.contextWindow ?? 128000,
1786
- maxTokens: Math.min(metadata?.contextWindow ?? Number.POSITIVE_INFINITY, DISCOVERY_DEFAULT_MAX_TOKENS),
1787
- headers: providerConfig.headers,
1788
- });
1789
- });
1790
- return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
1791
- }
1792
-
1793
- async #discoverLlamaCppServerMetadata(
1794
- baseUrl: string,
1795
- headers: Record<string, string> | undefined,
1796
- ): Promise<LlamaCppDiscoveredServerMetadata | null> {
1797
- const propsUrl = `${this.#toLlamaCppNativeBaseUrl(baseUrl)}/props`;
1798
- try {
1799
- const response = await fetch(propsUrl, {
1800
- headers,
1801
- signal: AbortSignal.timeout(150),
1802
- });
1803
- if (!response.ok) {
1804
- return null;
1805
- }
1806
- const payload = (await response.json()) as unknown;
1807
- if (!isRecord(payload)) {
1808
- return null;
1809
- }
1810
- return {
1811
- contextWindow: extractLlamaCppContextWindow(payload),
1812
- input: extractLlamaCppInputCapabilities(payload),
1813
- };
1814
- } catch {
1815
- return null;
1816
- }
1817
- }
1818
-
1819
- async #discoverLlamaCppModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
1820
- const baseUrl = this.#normalizeLlamaCppBaseUrl(providerConfig.baseUrl);
1821
- const modelsUrl = `${baseUrl}/models`;
1822
-
1823
- const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
1824
- const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
1825
- if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
1826
- headers.Authorization = `Bearer ${apiKey}`;
1827
- }
1828
-
1829
- const [response, serverMetadata] = await Promise.all([
1830
- fetch(modelsUrl, {
1831
- headers,
1832
- signal: AbortSignal.timeout(250),
1833
- }),
1834
- this.#discoverLlamaCppServerMetadata(baseUrl, headers),
1835
- ]);
1836
- if (!response.ok) {
1837
- throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
1838
- }
1839
- const payload = (await response.json()) as { data?: Array<{ id: string }> };
1840
- const models = payload.data ?? [];
1841
- const discovered: Model<Api>[] = [];
1842
- for (const item of models) {
1843
- const id = item.id;
1844
- if (!id) continue;
1845
- discovered.push(
1846
- enrichModelThinking({
1847
- id,
1848
- name: id,
1849
- api: providerConfig.api,
1850
- provider: providerConfig.provider,
1851
- baseUrl,
1852
- reasoning: false,
1853
- input: serverMetadata?.input ?? ["text"],
1854
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1855
- contextWindow: serverMetadata?.contextWindow ?? 128000,
1856
- maxTokens: Math.min(
1857
- serverMetadata?.contextWindow ?? Number.POSITIVE_INFINITY,
1858
- DISCOVERY_DEFAULT_MAX_TOKENS,
1859
- ),
1860
- headers,
1861
- compat: {
1862
- supportsStore: false,
1863
- supportsDeveloperRole: false,
1864
- supportsReasoningEffort: false,
1865
- },
1866
- }),
1867
- );
1868
- }
1869
- return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
1870
- }
1871
-
1872
- async #discoverOpenAIModelsList(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
1873
- const baseUrl = this.#normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
1874
- const modelsUrl = `${baseUrl}/models`;
1875
-
1876
- const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
1877
- const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
1878
- if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
1879
- headers.Authorization = `Bearer ${apiKey}`;
1880
- }
1881
-
1882
- const response = await fetch(modelsUrl, {
1883
- headers,
1884
- signal: AbortSignal.timeout(10_000),
1885
- });
1886
- if (!response.ok) {
1887
- throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
1888
- }
1889
- const payload = (await response.json()) as { data?: Array<{ id: string }> };
1890
- const models = payload.data ?? [];
1891
- const discovered: Model<Api>[] = [];
1892
- for (const item of models) {
1893
- const id = item.id;
1894
- if (!id) continue;
1895
- discovered.push(
1896
- enrichModelThinking({
1897
- id,
1898
- name: id,
1899
- api: providerConfig.api,
1900
- provider: providerConfig.provider,
1901
- baseUrl,
1902
- reasoning: false,
1903
- input: ["text"],
1904
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1905
- contextWindow: 128000,
1906
- maxTokens: discoveryDefaultMaxTokens(providerConfig.api),
1907
- headers,
1908
- compat: {
1909
- supportsStore: false,
1910
- supportsDeveloperRole: false,
1911
- supportsReasoningEffort: false,
1912
- },
1913
- }),
1914
- );
1915
- }
1916
- return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
1917
- }
1918
-
1919
- /**
1920
- * Discover models from an Anthropic+OpenAI-compatible reseller proxy that
1921
- * exposes both `/v1/messages` and `/v1/chat/completions`, advertising each
1922
- * model's wire capabilities through `supported_endpoint_types` on
1923
- * `GET /v1/models` (new-api / one-api-style proxies).
1924
- *
1925
- * Routing per model:
1926
- * supported_endpoint_types: ["anthropic", ...] -> api: "anthropic-messages"
1927
- * supported_endpoint_types: ["openai"] -> api: "openai-completions"
1928
- * missing / neither -> provider-level api fallback
1929
- *
1930
- * Anthropic models share the same baseUrl; the Anthropic SDK strips a
1931
- * trailing `/v1` itself before appending `/v1/messages`, so the discovery
1932
- * URL (which ends in `/v1`) round-trips correctly.
1933
- */
1934
- async #discoverProxyModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
1935
- const baseUrl = this.#normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
1936
- const modelsUrl = `${baseUrl}/models`;
1937
-
1938
- const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
1939
- const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
1940
- if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
1941
- headers.Authorization = `Bearer ${apiKey}`;
1942
- }
1943
-
1944
- const response = await fetch(modelsUrl, {
1945
- headers,
1946
- signal: AbortSignal.timeout(10_000),
1947
- });
1948
- if (!response.ok) {
1949
- throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
1950
- }
1951
- const payload = (await response.json()) as {
1952
- data?: Array<{ id?: string; name?: string; supported_endpoint_types?: string[] }>;
1953
- };
1954
- const items = payload.data ?? [];
1955
- const discovered: Model<Api>[] = [];
1956
- for (const item of items) {
1957
- const id = item.id;
1958
- if (!id) continue;
1959
- const endpoints = item.supported_endpoint_types ?? [];
1960
- const api: Api | undefined = endpoints.includes("anthropic")
1961
- ? "anthropic-messages"
1962
- : endpoints.includes("openai")
1963
- ? "openai-completions"
1964
- : providerConfig.api;
1965
- if (!api) continue;
1966
- const isAnthropic = api === "anthropic-messages";
1967
- const reference = resolveCustomModelReference(id);
1968
- const discoveryName = typeof item.name === "string" ? item.name.trim() : "";
1969
- const displayName =
1970
- reference?.name ??
1971
- (discoveryName && discoveryName !== id ? discoveryName : undefined) ??
1972
- stripBracketedModelIdAffixes(id) ??
1973
- id;
1974
- discovered.push(
1975
- enrichModelThinking({
1976
- id,
1977
- name: displayName,
1978
- api,
1979
- provider: providerConfig.provider,
1980
- baseUrl,
1981
- reasoning: reference?.reasoning ?? false,
1982
- thinking: reference?.thinking,
1983
- input: reference?.input ?? ["text"],
1984
- // Proxy pricing is provider-specific and usually does not match
1985
- // upstream bundled catalogs, so keep costs local-unknown even when
1986
- // we successfully recover the upstream model identity.
1987
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1988
- contextWindow: reference?.contextWindow ?? 128000,
1989
- maxTokens: reference?.maxTokens ?? discoveryDefaultMaxTokens(api),
1990
- headers,
1991
- // OpenAI-compat fields are no-ops on anthropic models; the
1992
- // Anthropic SDK ignores them. Provider-level disableStrictTools
1993
- // flows in via #applyProviderCompat for the third-party-Anthropic
1994
- // path. Cross-wire bundled compat is intentionally not copied:
1995
- // request-shaping fields are provider-wire specific.
1996
- compat: isAnthropic
1997
- ? undefined
1998
- : {
1999
- supportsStore: false,
2000
- supportsDeveloperRole: false,
2001
- supportsReasoningEffort: false,
2002
- },
2003
- }),
2004
- );
2005
- }
2006
- return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
2007
- }
2008
-
2009
- #normalizeLlamaCppBaseUrl(baseUrl?: string): string {
2010
- const defaultBaseUrl = "http://127.0.0.1:8080";
2011
- const raw = baseUrl || defaultBaseUrl;
2012
- try {
2013
- const parsed = new URL(raw);
2014
- const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
2015
- return `${parsed.protocol}//${parsed.host}${trimmedPath}`;
2016
- } catch {
2017
- return raw;
2018
- }
2019
- }
2020
-
2021
- #toLlamaCppNativeBaseUrl(baseUrl: string): string {
2022
- try {
2023
- const parsed = new URL(baseUrl);
2024
- const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
2025
- parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath.slice(0, -3) || "/" : trimmedPath || "/";
2026
- const normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
2027
- return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
2028
- } catch {
2029
- return baseUrl.endsWith("/v1") ? baseUrl.slice(0, -3) : baseUrl;
2030
- }
2031
- }
2032
-
2033
- #normalizeOpenAIModelsListBaseUrl(baseUrl?: string): string {
2034
- const defaultBaseUrl = "http://127.0.0.1:1234/v1";
2035
- const raw = baseUrl || defaultBaseUrl;
2036
- try {
2037
- const parsed = new URL(raw);
2038
- const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
2039
- parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath || "/v1" : `${trimmedPath}/v1`;
2040
- return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
2041
- } catch {
2042
- return raw;
2043
- }
2044
- }
2045
- #normalizeOllamaBaseUrl(baseUrl?: string): string {
2046
- const raw = baseUrl || DEFAULT_OLLAMA_BASE_URL;
2047
- try {
2048
- const parsed = new URL(raw);
2049
- return `${parsed.protocol}//${parsed.host}`;
2050
- } catch {
2051
- return DEFAULT_OLLAMA_BASE_URL;
2052
- }
2053
- }
2054
-
2055
1485
  #applyProviderModelOverrides(provider: string, models: Model<Api>[]): Model<Api>[] {
2056
1486
  const overrides = this.#modelOverrides.get(provider);
2057
1487
  if (!overrides || overrides.size === 0) return models;
1488
+ let liveIds: Set<string> | null = null;
1489
+ const hasLiveModel = (_provider: string, id: string) => {
1490
+ liveIds ??= new Set(models.map(m => m.id));
1491
+ return liveIds.has(id);
1492
+ };
2058
1493
  return models.map(model => {
2059
- const override = overrides.get(model.id);
1494
+ const override = resolveModelOverrideWithAliases(overrides, model, hasLiveModel);
2060
1495
  if (!override) return model;
2061
1496
  return applyModelOverride(model, override);
2062
1497
  });
@@ -2100,10 +1535,15 @@ export class ModelRegistry {
2100
1535
  }
2101
1536
  #applyModelOverrides(models: Model<Api>[], overrides: Map<string, Map<string, ModelOverride>>): Model<Api>[] {
2102
1537
  if (overrides.size === 0) return models;
1538
+ let liveKeys: Set<string> | null = null;
1539
+ const hasLiveModel = (provider: string, id: string) => {
1540
+ liveKeys ??= new Set(models.map(m => `${m.provider}\u0000${m.id}`));
1541
+ return liveKeys.has(`${provider}\u0000${id}`);
1542
+ };
2103
1543
  return models.map(model => {
2104
1544
  const providerOverrides = overrides.get(model.provider);
2105
1545
  if (!providerOverrides) return model;
2106
- const override = providerOverrides.get(model.id);
1546
+ const override = resolveModelOverrideWithAliases(providerOverrides, model, hasLiveModel);
2107
1547
  if (!override) return model;
2108
1548
  return applyModelOverride(model, override);
2109
1549
  });
@@ -2129,10 +1569,25 @@ export class ModelRegistry {
2129
1569
  this.#rebuildPending = true;
2130
1570
  return;
2131
1571
  }
2132
- this.#canonicalIndex = buildCanonicalModelIndex(this.#models, this.#equivalenceConfig);
1572
+ // Defer the catalog-wide index build to first read. Boot model
1573
+ // resolution reads it only when enabledModels or a default-role pattern
1574
+ // is configured; the empty interactive launch never reads it pre-paint,
1575
+ // so the ~200ms build over the full catalog moves off the first-paint
1576
+ // critical path.
1577
+ this.#canonicalIndexDirty = true;
2133
1578
  this.#rebuildPending = false;
2134
1579
  }
2135
1580
 
1581
+ #ensureCanonicalIndex(): CanonicalModelIndex {
1582
+ if (this.#canonicalIndexDirty) {
1583
+ this.#canonicalIndex = logger.time("buildCanonicalModelIndex", () =>
1584
+ buildCanonicalModelIndex(this.#models, getBundledCanonicalReferenceData(), this.#equivalenceConfig),
1585
+ );
1586
+ this.#canonicalIndexDirty = false;
1587
+ }
1588
+ return this.#canonicalIndex;
1589
+ }
1590
+
2136
1591
  #suspendRebuild(): void {
2137
1592
  this.#rebuildSuspended += 1;
2138
1593
  }
@@ -2143,7 +1598,7 @@ export class ModelRegistry {
2143
1598
  }
2144
1599
  if (this.#rebuildSuspended === 0 && this.#rebuildPending) {
2145
1600
  this.#rebuildPending = false;
2146
- this.#canonicalIndex = buildCanonicalModelIndex(this.#models, this.#equivalenceConfig);
1601
+ this.#canonicalIndexDirty = true;
2147
1602
  }
2148
1603
  }
2149
1604
 
@@ -2153,10 +1608,9 @@ export class ModelRegistry {
2153
1608
  for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
2154
1609
  const modelDefs = providerConfig.models ?? [];
2155
1610
  if (modelDefs.length === 0) continue; // Override-only, no custom models
1611
+ const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
2156
1612
  if (providerConfig.apiKey) {
2157
- this.#customProviderApiKeys.set(providerName, providerConfig.apiKey);
2158
- const resolved = resolveApiKeyConfig(providerConfig.apiKey);
2159
- if (resolved) this.authStorage.setConfigApiKey(providerName, resolved);
1613
+ this.#installProviderApiKey(providerName, providerConfig.apiKey);
2160
1614
  }
2161
1615
  for (const modelDef of modelDefs) {
2162
1616
  const providerCompat = providerConfig.disableStrictTools
@@ -2166,7 +1620,7 @@ export class ModelRegistry {
2166
1620
  providerName,
2167
1621
  providerConfig.baseUrl!,
2168
1622
  providerConfig.api as Api | undefined,
2169
- providerConfig.headers,
1623
+ resolvedProviderHeaders,
2170
1624
  providerConfig.apiKey,
2171
1625
  providerConfig.authHeader,
2172
1626
  providerCompat,
@@ -2188,100 +1642,73 @@ export class ModelRegistry {
2188
1642
  return this.#models;
2189
1643
  }
2190
1644
 
2191
- #isModelAvailable(model: Model<Api>): boolean {
1645
+ /**
1646
+ * Availability predicate with per-provider memoization. Auth lookups
1647
+ * (`authStorage.hasAuth`) and the disabled-provider set are resolved once
1648
+ * per provider instead of once per model, which matters when filtering the
1649
+ * full bundled catalog (thousands of models, ~50 providers).
1650
+ */
1651
+ #createAvailabilityCheck(): (model: Model<Api>) => boolean {
2192
1652
  const disabledProviders = getDisabledProviderIdsFromSettings();
2193
- return (
2194
- !disabledProviders.has(model.provider) &&
2195
- (this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider))
2196
- );
1653
+ const byProvider = new Map<string, boolean>();
1654
+ return model => {
1655
+ let available = byProvider.get(model.provider);
1656
+ if (available === undefined) {
1657
+ available =
1658
+ !disabledProviders.has(model.provider) &&
1659
+ (this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider));
1660
+ byProvider.set(model.provider, available);
1661
+ }
1662
+ return available;
1663
+ };
1664
+ }
1665
+
1666
+ /**
1667
+ * Build the shared per-query filter state for canonical model queries.
1668
+ * Hoisted out of the per-record loop: building the candidate-selector set
1669
+ * and availability memo once per query instead of once per record is what
1670
+ * keeps `getCanonicalModelSelections` linear instead of O(records × candidates).
1671
+ */
1672
+ #canonicalQueryFilters(options: CanonicalModelQueryOptions | undefined): {
1673
+ candidateKeys: Set<string> | undefined;
1674
+ isAvailable: ((model: Model<Api>) => boolean) | undefined;
1675
+ } {
1676
+ return {
1677
+ candidateKeys: options?.candidates
1678
+ ? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
1679
+ : undefined,
1680
+ isAvailable: options?.availableOnly ? this.#createAvailabilityCheck() : undefined,
1681
+ };
2197
1682
  }
2198
1683
 
2199
1684
  #filterCanonicalVariants(
2200
1685
  record: CanonicalModelRecord,
2201
- options: CanonicalModelQueryOptions | undefined,
1686
+ candidateKeys: ReadonlySet<string> | undefined,
1687
+ isAvailable: ((model: Model<Api>) => boolean) | undefined,
2202
1688
  ): CanonicalModelVariant[] {
2203
- const candidateKeys = options?.candidates
2204
- ? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
2205
- : undefined;
2206
1689
  return record.variants.filter(variant => {
2207
1690
  if (candidateKeys && !candidateKeys.has(variant.selector)) {
2208
1691
  return false;
2209
1692
  }
2210
- if (options?.availableOnly && !this.#isModelAvailable(variant.model)) {
1693
+ if (isAvailable && !isAvailable(variant.model)) {
2211
1694
  return false;
2212
1695
  }
2213
1696
  return true;
2214
1697
  });
2215
1698
  }
2216
1699
 
2217
- #providerRank(models: readonly Model<Api>[]): Map<string, number> {
2218
- const configuredProviders = getConfiguredProviderOrderFromSettings();
2219
- const result = new Map<string, number>();
2220
- let nextRank = 0;
2221
- for (const provider of configuredProviders) {
2222
- const normalized = provider.trim().toLowerCase();
2223
- if (!normalized || result.has(normalized)) {
2224
- continue;
2225
- }
2226
- result.set(normalized, nextRank);
2227
- nextRank += 1;
2228
- }
2229
- for (const model of models) {
2230
- const normalized = model.provider.toLowerCase();
2231
- if (result.has(normalized)) {
2232
- continue;
2233
- }
2234
- result.set(normalized, nextRank);
2235
- nextRank += 1;
2236
- }
2237
- return result;
2238
- }
2239
-
2240
- #resolveCanonicalVariant(
2241
- variants: readonly CanonicalModelVariant[],
2242
- allCandidates: readonly Model<Api>[],
2243
- ): CanonicalModelVariant | undefined {
2244
- if (variants.length === 0) {
2245
- return undefined;
2246
- }
2247
- const providerRank = this.#providerRank(allCandidates);
2248
- const modelOrder = new Map<string, number>();
2249
- for (let index = 0; index < allCandidates.length; index += 1) {
2250
- modelOrder.set(formatCanonicalVariantSelector(allCandidates[index]!), index);
2251
- }
2252
- const sourceRank: Record<CanonicalModelVariant["source"], number> = {
2253
- override: 1,
2254
- bundled: 1,
2255
- heuristic: 2,
2256
- fallback: 3,
1700
+ #variantPreferences(candidates: readonly Model<Api>[]): CanonicalVariantPreferences {
1701
+ return {
1702
+ modelOrder: buildCanonicalModelOrder(candidates),
1703
+ providerRank: buildModelProviderPriorityRank(getConfiguredProviderOrderFromSettings()),
2257
1704
  };
2258
- return [...variants].sort((left, right) => {
2259
- const leftProviderRank = providerRank.get(left.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
2260
- const rightProviderRank = providerRank.get(right.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
2261
- if (leftProviderRank !== rightProviderRank) {
2262
- return leftProviderRank - rightProviderRank;
2263
- }
2264
- const leftExact = left.model.id === left.canonicalId ? 0 : 1;
2265
- const rightExact = right.model.id === right.canonicalId ? 0 : 1;
2266
- if (leftExact !== rightExact) {
2267
- return leftExact - rightExact;
2268
- }
2269
- if (sourceRank[left.source] !== sourceRank[right.source]) {
2270
- return sourceRank[left.source] - sourceRank[right.source];
2271
- }
2272
- if (left.model.id.length !== right.model.id.length) {
2273
- return left.model.id.length - right.model.id.length;
2274
- }
2275
- const leftOrder = modelOrder.get(left.selector) ?? Number.MAX_SAFE_INTEGER;
2276
- const rightOrder = modelOrder.get(right.selector) ?? Number.MAX_SAFE_INTEGER;
2277
- return leftOrder - rightOrder;
2278
- })[0];
2279
1705
  }
2280
1706
 
2281
1707
  getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[] {
1708
+ const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
2282
1709
  const records: CanonicalModelRecord[] = [];
2283
- for (const record of this.#canonicalIndex.records) {
2284
- const variants = this.#filterCanonicalVariants(record, options);
1710
+ for (const record of this.#ensureCanonicalIndex().records) {
1711
+ const variants = this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
2285
1712
  if (variants.length === 0) {
2286
1713
  continue;
2287
1714
  }
@@ -2294,12 +1721,42 @@ export class ModelRegistry {
2294
1721
  return records;
2295
1722
  }
2296
1723
 
1724
+ /**
1725
+ * One-pass equivalent of `getCanonicalModels` + `resolveCanonicalModel` per
1726
+ * record. The per-query state (candidate-selector set, availability memo,
1727
+ * provider rank, candidate order) is built once, so the whole catalog
1728
+ * resolves in O(records + candidates) instead of O(records × candidates).
1729
+ * This is the path the model selector hydrates from synchronously on open.
1730
+ */
1731
+ getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[] {
1732
+ const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
1733
+ const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
1734
+ const preferences = this.#variantPreferences(candidates);
1735
+ const selections: CanonicalModelSelection[] = [];
1736
+ for (const record of this.#ensureCanonicalIndex().records) {
1737
+ const variants = this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
1738
+ if (variants.length === 0) {
1739
+ continue;
1740
+ }
1741
+ const resolved = resolveCanonicalVariant(variants, preferences);
1742
+ if (!resolved) {
1743
+ continue;
1744
+ }
1745
+ selections.push({
1746
+ record: { id: record.id, name: record.name, variants },
1747
+ model: resolved.model,
1748
+ });
1749
+ }
1750
+ return selections;
1751
+ }
1752
+
2297
1753
  getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[] {
2298
- const record = this.#canonicalIndex.byId.get(canonicalId.trim().toLowerCase());
1754
+ const record = this.#ensureCanonicalIndex().byId.get(canonicalId.trim().toLowerCase());
2299
1755
  if (!record) {
2300
1756
  return [];
2301
1757
  }
2302
- return this.#filterCanonicalVariants(record, options);
1758
+ const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
1759
+ return this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
2303
1760
  }
2304
1761
 
2305
1762
  resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined {
@@ -2308,11 +1765,11 @@ export class ModelRegistry {
2308
1765
  return undefined;
2309
1766
  }
2310
1767
  const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
2311
- return this.#resolveCanonicalVariant(variants, candidates)?.model;
1768
+ return resolveCanonicalVariant(variants, this.#variantPreferences(candidates))?.model;
2312
1769
  }
2313
1770
 
2314
1771
  getCanonicalId(model: Model<Api>): string | undefined {
2315
- return this.#canonicalIndex.bySelector.get(formatCanonicalVariantSelector(model).toLowerCase());
1772
+ return this.#ensureCanonicalIndex().bySelector.get(formatCanonicalVariantSelector(model).toLowerCase());
2316
1773
  }
2317
1774
 
2318
1775
  /**
@@ -2320,20 +1777,32 @@ export class ModelRegistry {
2320
1777
  * This is a fast check that doesn't refresh OAuth tokens.
2321
1778
  */
2322
1779
  getAvailable(): Model<Api>[] {
2323
- return this.#models.filter(model => this.#isModelAvailable(model));
1780
+ return this.#models.filter(this.#createAvailabilityCheck());
2324
1781
  }
2325
1782
 
2326
1783
  /**
2327
1784
  * Check whether auth is configured for a model's provider.
2328
1785
  *
2329
- * Mirrors the upstream `@mariozechner/pi-coding-agent` API surface so that
1786
+ * Mirrors the upstream `@mariozechner/agent` API surface so that
2330
1787
  * external plugins/extensions and downstream wrappers (e.g. subagent launch
2331
1788
  * paths that pre-flight auth before model resolution) can probe a model
2332
1789
  * without resolving an API key. Returns true for keyless providers as well
2333
1790
  * as providers with stored credentials. See issue #993.
1791
+ *
1792
+ * Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
1793
+ * configured by its presence alone — the program is NOT executed — and OAuth
1794
+ * tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the
1795
+ * model-switch pre-flight off the event loop's hot path; the real key
1796
+ * (command execution + OAuth refresh) is resolved lazily per request via
1797
+ * {@link ModelRegistry.resolver}.
2334
1798
  */
2335
1799
  hasConfiguredAuth(model: Model<Api>): boolean {
2336
- return this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider);
1800
+ const keyConfig = this.#customProviderApiKeys.get(model.provider);
1801
+ return (
1802
+ isCommandConfigValue(keyConfig) ||
1803
+ this.#keylessProviders.has(model.provider) ||
1804
+ this.authStorage.hasAuth(model.provider)
1805
+ );
2337
1806
  }
2338
1807
 
2339
1808
  getDiscoverableProviders(): string[] {
@@ -2365,6 +1834,8 @@ export class ModelRegistry {
2365
1834
  * Get API key for a model.
2366
1835
  */
2367
1836
  async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
1837
+ const commandKey = this.#resolveCommandBackedApiKey(model.provider);
1838
+ if (commandKey.configured) return commandKey.value;
2368
1839
  if (this.#keylessProviders.has(model.provider) && !this.authStorage.hasAuth(model.provider)) {
2369
1840
  return kNoAuth;
2370
1841
  }
@@ -2373,15 +1844,53 @@ export class ModelRegistry {
2373
1844
 
2374
1845
  /**
2375
1846
  * Get API key for a provider (e.g., "openai").
1847
+ *
1848
+ * `options.forceRefresh` powers step (b) of the auth-retry policy — it
1849
+ * re-mints the session-sticky OAuth token even when the cached copy still
1850
+ * looks valid. `options.signal` is threaded into any broker-bound refresh.
2376
1851
  */
2377
- async getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string): Promise<string | undefined> {
1852
+ async getApiKeyForProvider(
1853
+ provider: string,
1854
+ sessionId?: string,
1855
+ options?: { baseUrl?: string; modelId?: string; forceRefresh?: boolean; signal?: AbortSignal },
1856
+ ): Promise<string | undefined> {
1857
+ const commandKey = this.#resolveCommandBackedApiKey(provider);
1858
+ if (commandKey.configured) return commandKey.value;
2378
1859
  if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
2379
1860
  return kNoAuth;
2380
1861
  }
2381
- return this.authStorage.getApiKey(provider, sessionId, { baseUrl });
1862
+ return this.authStorage.getApiKey(provider, sessionId, {
1863
+ baseUrl: options?.baseUrl,
1864
+ modelId: options?.modelId,
1865
+ forceRefresh: options?.forceRefresh,
1866
+ signal: options?.signal,
1867
+ });
1868
+ }
1869
+
1870
+ /**
1871
+ * Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
1872
+ * policy. Accepts a provider id with options, or a model with an optional
1873
+ * session id (`resolver(model, sessionId)`) which derives `baseUrl`/`modelId`
1874
+ * from the model. Callers that need the initial key for a guard can call
1875
+ * `resolveApiKeyOnce(resolver)`.
1876
+ */
1877
+ resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
1878
+ resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
1879
+ resolver(target: string | ApiKeyResolverModel, optionsOrSessionId?: ApiKeyResolverOptions | string): ApiKeyResolver {
1880
+ const options = typeof optionsOrSessionId === "string" ? { sessionId: optionsOrSessionId } : optionsOrSessionId;
1881
+ if (typeof target === "string") {
1882
+ return createApiKeyResolver(this, target, options);
1883
+ }
1884
+ return createApiKeyResolver(this, target.provider, {
1885
+ ...options,
1886
+ baseUrl: target.baseUrl,
1887
+ modelId: target.id,
1888
+ });
2382
1889
  }
2383
1890
 
2384
1891
  async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
1892
+ const commandKey = this.#resolveCommandBackedApiKey(provider);
1893
+ if (commandKey.configured) return commandKey.value;
2385
1894
  if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
2386
1895
  return kNoAuth;
2387
1896
  }
@@ -2399,6 +1908,7 @@ export class ModelRegistry {
2399
1908
  this.#runtimeProviderApiKeys.delete(providerName);
2400
1909
  this.#runtimeProviderOverrides.delete(providerName);
2401
1910
  this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(overlay => overlay.provider !== providerName);
1911
+ this.#runtimeModelManagers.delete(providerName);
2402
1912
  this.authStorage.removeConfigApiKey(providerName);
2403
1913
  }
2404
1914
 
@@ -2504,11 +2014,9 @@ export class ModelRegistry {
2504
2014
  }
2505
2015
 
2506
2016
  if (config.apiKey) {
2507
- this.#customProviderApiKeys.set(providerName, config.apiKey);
2017
+ this.#installProviderApiKey(providerName, config.apiKey);
2508
2018
  // Persist runtime API keys so they survive #reloadStaticModels() cycles
2509
2019
  this.#runtimeProviderApiKeys.set(providerName, config.apiKey);
2510
- const resolved = resolveApiKeyConfig(config.apiKey);
2511
- if (resolved) this.authStorage.setConfigApiKey(providerName, resolved);
2512
2020
  }
2513
2021
 
2514
2022
  if (config.models && config.models.length > 0) {
@@ -2562,6 +2070,47 @@ export class ModelRegistry {
2562
2070
  return;
2563
2071
  }
2564
2072
 
2073
+ if (config.fetchDynamicModels) {
2074
+ const fetcher = config.fetchDynamicModels;
2075
+ const providerBaseUrl = config.baseUrl ?? "";
2076
+ const providerApi = config.api;
2077
+ const providerHeaders = config.headers;
2078
+ const providerApiKey = config.apiKey;
2079
+ const providerAuthHeader = config.authHeader;
2080
+ const providerCompat = config.compat;
2081
+ const managerOptions: ModelManagerOptions<Api> = {
2082
+ providerId: providerName as Parameters<typeof createModelManager>[0]["providerId"],
2083
+ staticModels: [],
2084
+ cacheDbPath: this.#cacheDbPath,
2085
+ cacheTtlMs: 24 * 60 * 60 * 1000,
2086
+ dynamicModelsAuthoritative: true,
2087
+ fetchDynamicModels: async () => {
2088
+ const apiKey = await this.#peekApiKeyForProvider(providerName);
2089
+ const resolvedKey = isAuthenticated(apiKey) ? apiKey : undefined;
2090
+ const modelDefs = await fetcher(resolvedKey);
2091
+ const results: Model<Api>[] = [];
2092
+ for (const modelDef of modelDefs) {
2093
+ const overlay = buildCustomModelOverlay(
2094
+ providerName,
2095
+ modelDef.baseUrl ?? providerBaseUrl,
2096
+ modelDef.api ?? providerApi,
2097
+ providerHeaders,
2098
+ providerApiKey,
2099
+ providerAuthHeader,
2100
+ providerCompat,
2101
+ undefined,
2102
+ modelDef as CustomModelDefinitionLike,
2103
+ );
2104
+ if (overlay) results.push(finalizeCustomModel(overlay, { useDefaults: true }));
2105
+ }
2106
+ return results.map(toModelSpec);
2107
+ },
2108
+ };
2109
+ this.#runtimeModelManagers.set(providerName, { options: managerOptions, sourceId: sourceId ?? "" });
2110
+ // Discovery is driven by refreshRuntimeProviders() after the drain — not
2111
+ // here, so registration has no network side effect and callers can await.
2112
+ }
2113
+
2565
2114
  if (
2566
2115
  config.baseUrl ||
2567
2116
  config.headers ||
@@ -2609,6 +2158,14 @@ export class ModelRegistry {
2609
2158
  }
2610
2159
  return true;
2611
2160
  }
2161
+
2162
+ /**
2163
+ * Clear all cooldown suppressions recorded via {@link suppressSelector}.
2164
+ * Used to reset retry-fallback cooldown state without a full {@link refresh}.
2165
+ */
2166
+ clearSuppressedSelectors(): void {
2167
+ this.#suppressedSelectors.clear();
2168
+ }
2612
2169
  }
2613
2170
 
2614
2171
  /**
@@ -2620,7 +2177,7 @@ export interface ProviderConfigInput {
2620
2177
  api?: Api;
2621
2178
  streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
2622
2179
  headers?: Record<string, string>;
2623
- compat?: Model<Api>["compat"];
2180
+ compat?: ModelSpec<Api>["compat"];
2624
2181
  authHeader?: boolean;
2625
2182
  /** Streaming transport override — see {@link Model.transport}. */
2626
2183
  transport?: Model<Api>["transport"];
@@ -2631,6 +2188,15 @@ export interface ProviderConfigInput {
2631
2188
  getApiKey?(credentials: OAuthCredentials): string;
2632
2189
  modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
2633
2190
  };
2191
+ /**
2192
+ * Async factory that fetches the live model list from the provider endpoint.
2193
+ * When present, the result is run through the same SQLite model-cache as
2194
+ * built-in providers (keyed by provider name, default 24 h TTL).
2195
+ * The factory receives the resolved API key (undefined when unauthenticated).
2196
+ */
2197
+ fetchDynamicModels?: (
2198
+ apiKey: string | undefined,
2199
+ ) => Promise<readonly NonNullable<ProviderConfigInput["models"]>[number][]>;
2634
2200
  models?: Array<{
2635
2201
  id: string;
2636
2202
  name: string;
@@ -2643,7 +2209,7 @@ export interface ProviderConfigInput {
2643
2209
  contextWindow: number;
2644
2210
  maxTokens: number;
2645
2211
  headers?: Record<string, string>;
2646
- compat?: Model<Api>["compat"];
2212
+ compat?: ModelSpec<Api>["compat"];
2647
2213
  contextPromotionTarget?: string;
2648
2214
  premiumMultiplier?: number;
2649
2215
  }>;