oma-coding-agent 1.1.4

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 (1298) hide show
  1. package/CHANGELOG.md +12164 -0
  2. package/README.md +35 -0
  3. package/dist/cli.js +18266 -0
  4. package/examples/README.md +21 -0
  5. package/examples/custom-tools/README.md +104 -0
  6. package/examples/custom-tools/hello/index.ts +20 -0
  7. package/examples/extensions/README.md +142 -0
  8. package/examples/extensions/api-demo.ts +79 -0
  9. package/examples/extensions/chalk-logger.ts +25 -0
  10. package/examples/extensions/hello.ts +31 -0
  11. package/examples/extensions/pirate.ts +43 -0
  12. package/examples/extensions/plan-mode.ts +549 -0
  13. package/examples/extensions/reload-runtime.ts +38 -0
  14. package/examples/extensions/thinking-note.ts +13 -0
  15. package/examples/extensions/tools.ts +145 -0
  16. package/examples/extensions/with-deps/index.ts +36 -0
  17. package/examples/extensions/with-deps/package-lock.json +31 -0
  18. package/examples/extensions/with-deps/package.json +17 -0
  19. package/examples/hooks/README.md +56 -0
  20. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  21. package/examples/hooks/confirm-destructive.ts +58 -0
  22. package/examples/hooks/custom-compaction.ts +115 -0
  23. package/examples/hooks/dirty-repo-guard.ts +51 -0
  24. package/examples/hooks/file-trigger.ts +40 -0
  25. package/examples/hooks/git-checkpoint.ts +52 -0
  26. package/examples/hooks/handoff.ts +149 -0
  27. package/examples/hooks/permission-gate.ts +33 -0
  28. package/examples/hooks/protected-paths.ts +29 -0
  29. package/examples/hooks/qna.ts +118 -0
  30. package/examples/hooks/status-line.ts +39 -0
  31. package/examples/sdk/01-minimal.ts +21 -0
  32. package/examples/sdk/02-custom-model.ts +49 -0
  33. package/examples/sdk/03-custom-prompt.ts +46 -0
  34. package/examples/sdk/04-skills.ts +43 -0
  35. package/examples/sdk/06-extensions.ts +82 -0
  36. package/examples/sdk/06-hooks.ts +61 -0
  37. package/examples/sdk/07-context-files.ts +35 -0
  38. package/examples/sdk/08-prompt-templates.ts +41 -0
  39. package/examples/sdk/08-slash-commands.ts +46 -0
  40. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  41. package/examples/sdk/11-sessions.ts +47 -0
  42. package/examples/sdk/12-redis-sessions.ts +54 -0
  43. package/examples/sdk/13-sql-sessions.ts +61 -0
  44. package/examples/sdk/README.md +172 -0
  45. package/package.json +573 -0
  46. package/scripts/bench-guard.ts +71 -0
  47. package/scripts/build-binary.ts +108 -0
  48. package/scripts/bundle-dist.ts +110 -0
  49. package/scripts/embed-mupdf-wasm.ts +67 -0
  50. package/scripts/format-prompts.ts +68 -0
  51. package/scripts/generate-docs-index.ts +56 -0
  52. package/scripts/generate-share-viewer.ts +34 -0
  53. package/scripts/measure-prompt-tokens.ts +63 -0
  54. package/scripts/omp +42 -0
  55. package/scripts/omp.ts +19 -0
  56. package/src/advisor/__tests__/advisor.test.ts +915 -0
  57. package/src/advisor/advise-tool.ts +165 -0
  58. package/src/advisor/index.ts +4 -0
  59. package/src/advisor/runtime.ts +270 -0
  60. package/src/advisor/transcript-recorder.ts +136 -0
  61. package/src/advisor/watchdog.ts +83 -0
  62. package/src/async/index.ts +1 -0
  63. package/src/async/job-manager.ts +674 -0
  64. package/src/auto-thinking/classifier.ts +190 -0
  65. package/src/autolearn/controller.ts +139 -0
  66. package/src/autolearn/managed-skills.ts +255 -0
  67. package/src/autoresearch/command-resume.md +14 -0
  68. package/src/autoresearch/dashboard.ts +436 -0
  69. package/src/autoresearch/git.ts +319 -0
  70. package/src/autoresearch/helpers.ts +218 -0
  71. package/src/autoresearch/index.ts +536 -0
  72. package/src/autoresearch/prompt-setup.md +43 -0
  73. package/src/autoresearch/prompt.md +103 -0
  74. package/src/autoresearch/resume-message.md +10 -0
  75. package/src/autoresearch/state.ts +273 -0
  76. package/src/autoresearch/storage.ts +700 -0
  77. package/src/autoresearch/tools/init-experiment.ts +269 -0
  78. package/src/autoresearch/tools/log-experiment.ts +521 -0
  79. package/src/autoresearch/tools/run-experiment.ts +407 -0
  80. package/src/autoresearch/tools/update-notes.ts +109 -0
  81. package/src/autoresearch/types.ts +168 -0
  82. package/src/capability/context-file.ts +44 -0
  83. package/src/capability/extension-module.ts +34 -0
  84. package/src/capability/extension.ts +47 -0
  85. package/src/capability/fs.ts +117 -0
  86. package/src/capability/hook.ts +40 -0
  87. package/src/capability/index.ts +436 -0
  88. package/src/capability/instruction.ts +37 -0
  89. package/src/capability/mcp.ts +76 -0
  90. package/src/capability/prompt.ts +35 -0
  91. package/src/capability/rule-buckets.ts +66 -0
  92. package/src/capability/rule.ts +261 -0
  93. package/src/capability/settings.ts +34 -0
  94. package/src/capability/skill.ts +63 -0
  95. package/src/capability/slash-command.ts +40 -0
  96. package/src/capability/ssh.ts +41 -0
  97. package/src/capability/system-prompt.ts +34 -0
  98. package/src/capability/tool.ts +38 -0
  99. package/src/capability/types.ts +168 -0
  100. package/src/cli/agents-cli.ts +138 -0
  101. package/src/cli/args.ts +361 -0
  102. package/src/cli/auth-broker-cli.ts +893 -0
  103. package/src/cli/auth-gateway-cli.ts +608 -0
  104. package/src/cli/bench-cli.ts +552 -0
  105. package/src/cli/classify-install-target.ts +76 -0
  106. package/src/cli/claude-trace-cli.ts +795 -0
  107. package/src/cli/commands/init-xdg.ts +27 -0
  108. package/src/cli/completion-gen.ts +550 -0
  109. package/src/cli/config-cli.ts +418 -0
  110. package/src/cli/dry-balance-cli.ts +858 -0
  111. package/src/cli/extension-flags.ts +48 -0
  112. package/src/cli/file-processor.ts +133 -0
  113. package/src/cli/flag-tables.ts +280 -0
  114. package/src/cli/gallery-cli.ts +231 -0
  115. package/src/cli/gallery-fixtures/agentic.ts +407 -0
  116. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  117. package/src/cli/gallery-fixtures/edit.ts +194 -0
  118. package/src/cli/gallery-fixtures/fs.ts +220 -0
  119. package/src/cli/gallery-fixtures/index.ts +40 -0
  120. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  121. package/src/cli/gallery-fixtures/memory.ts +81 -0
  122. package/src/cli/gallery-fixtures/misc.ts +250 -0
  123. package/src/cli/gallery-fixtures/search.ts +213 -0
  124. package/src/cli/gallery-fixtures/shell.ts +167 -0
  125. package/src/cli/gallery-fixtures/types.ts +57 -0
  126. package/src/cli/gallery-fixtures/web.ts +158 -0
  127. package/src/cli/gallery-screenshot.ts +279 -0
  128. package/src/cli/grep-cli.ts +160 -0
  129. package/src/cli/grievances-cli.ts +256 -0
  130. package/src/cli/initial-message.ts +58 -0
  131. package/src/cli/models-cli.ts +427 -0
  132. package/src/cli/plugin-cli.ts +996 -0
  133. package/src/cli/profile-alias.ts +338 -0
  134. package/src/cli/profile-bootstrap.ts +243 -0
  135. package/src/cli/read-cli.ts +57 -0
  136. package/src/cli/session-picker.ts +80 -0
  137. package/src/cli/setup-cli.ts +332 -0
  138. package/src/cli/setup-model-picker.ts +43 -0
  139. package/src/cli/shell-cli.ts +176 -0
  140. package/src/cli/ssh-cli.ts +179 -0
  141. package/src/cli/startup-cwd.ts +58 -0
  142. package/src/cli/stats-cli.ts +229 -0
  143. package/src/cli/tiny-models-cli.ts +127 -0
  144. package/src/cli/ttsr-cli.ts +995 -0
  145. package/src/cli/update-cli.ts +671 -0
  146. package/src/cli/usage-cli.ts +774 -0
  147. package/src/cli/web-search-cli.ts +132 -0
  148. package/src/cli/worktree-cli.ts +291 -0
  149. package/src/cli-commands.ts +85 -0
  150. package/src/cli.ts +326 -0
  151. package/src/collab/crypto.ts +63 -0
  152. package/src/collab/guest.ts +450 -0
  153. package/src/collab/host.ts +577 -0
  154. package/src/collab/protocol.ts +274 -0
  155. package/src/collab/relay-client.ts +216 -0
  156. package/src/commands/acp.ts +24 -0
  157. package/src/commands/agents.ts +57 -0
  158. package/src/commands/auth-broker.ts +99 -0
  159. package/src/commands/auth-gateway.ts +69 -0
  160. package/src/commands/bench.ts +42 -0
  161. package/src/commands/commit.ts +46 -0
  162. package/src/commands/complete.ts +66 -0
  163. package/src/commands/completions.ts +60 -0
  164. package/src/commands/config.ts +51 -0
  165. package/src/commands/dry-balance.ts +43 -0
  166. package/src/commands/gallery.ts +52 -0
  167. package/src/commands/grep.ts +48 -0
  168. package/src/commands/grievances.ts +51 -0
  169. package/src/commands/install.ts +107 -0
  170. package/src/commands/join.ts +39 -0
  171. package/src/commands/launch.ts +182 -0
  172. package/src/commands/models.ts +61 -0
  173. package/src/commands/plugin.ts +78 -0
  174. package/src/commands/read.ts +38 -0
  175. package/src/commands/say.ts +102 -0
  176. package/src/commands/setup.ts +67 -0
  177. package/src/commands/shell.ts +29 -0
  178. package/src/commands/ssh.ts +60 -0
  179. package/src/commands/stats.ts +29 -0
  180. package/src/commands/tiny-models.ts +36 -0
  181. package/src/commands/token.ts +108 -0
  182. package/src/commands/ttsr.ts +125 -0
  183. package/src/commands/update.ts +21 -0
  184. package/src/commands/usage.ts +43 -0
  185. package/src/commands/web-search.ts +42 -0
  186. package/src/commands/worktree.ts +56 -0
  187. package/src/commit/agentic/agent.ts +318 -0
  188. package/src/commit/agentic/fallback.ts +96 -0
  189. package/src/commit/agentic/index.ts +355 -0
  190. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  191. package/src/commit/agentic/prompts/session-user.md +25 -0
  192. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  193. package/src/commit/agentic/prompts/system.md +38 -0
  194. package/src/commit/agentic/state.ts +60 -0
  195. package/src/commit/agentic/tools/analyze-file.ts +149 -0
  196. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  197. package/src/commit/agentic/tools/git-hunk.ts +52 -0
  198. package/src/commit/agentic/tools/git-overview.ts +81 -0
  199. package/src/commit/agentic/tools/index.ts +54 -0
  200. package/src/commit/agentic/tools/propose-changelog.ts +147 -0
  201. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  202. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  203. package/src/commit/agentic/tools/schemas.ts +11 -0
  204. package/src/commit/agentic/tools/split-commit.ts +241 -0
  205. package/src/commit/agentic/topo-sort.ts +44 -0
  206. package/src/commit/agentic/trivial.ts +51 -0
  207. package/src/commit/agentic/validation.ts +183 -0
  208. package/src/commit/analysis/conventional.ts +64 -0
  209. package/src/commit/analysis/index.ts +4 -0
  210. package/src/commit/analysis/scope.ts +242 -0
  211. package/src/commit/analysis/summary.ts +107 -0
  212. package/src/commit/analysis/validation.ts +66 -0
  213. package/src/commit/changelog/detect.ts +40 -0
  214. package/src/commit/changelog/generate.ts +101 -0
  215. package/src/commit/changelog/index.ts +234 -0
  216. package/src/commit/changelog/parse.ts +44 -0
  217. package/src/commit/cli.ts +85 -0
  218. package/src/commit/git/diff.ts +148 -0
  219. package/src/commit/index.ts +5 -0
  220. package/src/commit/map-reduce/index.ts +69 -0
  221. package/src/commit/map-reduce/map-phase.ts +193 -0
  222. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  223. package/src/commit/map-reduce/utils.ts +9 -0
  224. package/src/commit/message.ts +11 -0
  225. package/src/commit/model-selection.ts +89 -0
  226. package/src/commit/pipeline.ts +243 -0
  227. package/src/commit/prompts/analysis-system.md +148 -0
  228. package/src/commit/prompts/analysis-user.md +38 -0
  229. package/src/commit/prompts/changelog-system.md +50 -0
  230. package/src/commit/prompts/changelog-user.md +18 -0
  231. package/src/commit/prompts/file-observer-system.md +24 -0
  232. package/src/commit/prompts/file-observer-user.md +8 -0
  233. package/src/commit/prompts/reduce-system.md +50 -0
  234. package/src/commit/prompts/reduce-user.md +17 -0
  235. package/src/commit/prompts/summary-retry.md +3 -0
  236. package/src/commit/prompts/summary-system.md +38 -0
  237. package/src/commit/prompts/summary-user.md +13 -0
  238. package/src/commit/prompts/types-description.md +2 -0
  239. package/src/commit/shared-llm.ts +70 -0
  240. package/src/commit/types.ts +118 -0
  241. package/src/commit/utils/exclusions.ts +42 -0
  242. package/src/commit/utils.ts +58 -0
  243. package/src/config/api-key-resolver.ts +67 -0
  244. package/src/config/append-only-context-mode.ts +76 -0
  245. package/src/config/config-file.ts +315 -0
  246. package/src/config/file-lock.ts +164 -0
  247. package/src/config/keybindings.ts +634 -0
  248. package/src/config/mcp-schema.json +238 -0
  249. package/src/config/model-discovery.ts +589 -0
  250. package/src/config/model-registry.ts +2260 -0
  251. package/src/config/model-resolver.ts +1819 -0
  252. package/src/config/model-roles.ts +99 -0
  253. package/src/config/models-config-schema.ts +266 -0
  254. package/src/config/models-config.ts +131 -0
  255. package/src/config/prompt-templates.ts +185 -0
  256. package/src/config/resolve-config-value.ts +94 -0
  257. package/src/config/settings-schema.ts +4740 -0
  258. package/src/config/settings.ts +1243 -0
  259. package/src/config.ts +242 -0
  260. package/src/cursor.ts +340 -0
  261. package/src/dap/client.ts +760 -0
  262. package/src/dap/config.ts +189 -0
  263. package/src/dap/defaults.json +212 -0
  264. package/src/dap/index.ts +4 -0
  265. package/src/dap/session.ts +1441 -0
  266. package/src/dap/types.ts +610 -0
  267. package/src/debug/index.ts +559 -0
  268. package/src/debug/log-formatting.ts +58 -0
  269. package/src/debug/log-viewer.ts +908 -0
  270. package/src/debug/profiler.ts +162 -0
  271. package/src/debug/protocol-probe.ts +267 -0
  272. package/src/debug/raw-sse-buffer.ts +294 -0
  273. package/src/debug/raw-sse.ts +292 -0
  274. package/src/debug/remote-debugger.ts +151 -0
  275. package/src/debug/report-bundle.ts +375 -0
  276. package/src/debug/system-info.ts +111 -0
  277. package/src/debug/terminal-info.ts +124 -0
  278. package/src/discovery/agents-md.ts +67 -0
  279. package/src/discovery/agents.ts +230 -0
  280. package/src/discovery/at-imports.ts +273 -0
  281. package/src/discovery/builtin-defaults.ts +39 -0
  282. package/src/discovery/builtin-rules/index.ts +63 -0
  283. package/src/discovery/builtin-rules/low-end/no-hallucinated-apis.md +14 -0
  284. package/src/discovery/builtin-rules/low-end/no-hallucinated-paths.md +14 -0
  285. package/src/discovery/builtin-rules/low-end/no-premature-completion.md +14 -0
  286. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  287. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  288. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  289. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  290. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  291. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  292. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  293. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  294. package/src/discovery/builtin-rules/ts-no-any.md +65 -0
  295. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
  296. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  297. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
  298. package/src/discovery/builtin-rules/ts-no-return-type.md +44 -0
  299. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  300. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  301. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  302. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  303. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  304. package/src/discovery/builtin.ts +934 -0
  305. package/src/discovery/claude-plugins.ts +386 -0
  306. package/src/discovery/claude.ts +584 -0
  307. package/src/discovery/cline.ts +83 -0
  308. package/src/discovery/codex.ts +522 -0
  309. package/src/discovery/cursor.ts +220 -0
  310. package/src/discovery/gemini.ts +383 -0
  311. package/src/discovery/github.ts +337 -0
  312. package/src/discovery/helpers.ts +1092 -0
  313. package/src/discovery/index.ts +81 -0
  314. package/src/discovery/mcp-json.ts +172 -0
  315. package/src/discovery/omp-extension-roots.ts +190 -0
  316. package/src/discovery/omp-plugins.ts +383 -0
  317. package/src/discovery/opencode.ts +398 -0
  318. package/src/discovery/plugin-dir-roots.ts +28 -0
  319. package/src/discovery/ssh.ts +153 -0
  320. package/src/discovery/substitute-plugin-root.ts +29 -0
  321. package/src/discovery/vscode.ts +105 -0
  322. package/src/discovery/windsurf.ts +147 -0
  323. package/src/edit/apply-patch/index.ts +87 -0
  324. package/src/edit/apply-patch/parser.ts +174 -0
  325. package/src/edit/diff.ts +999 -0
  326. package/src/edit/file-snapshot-store.ts +143 -0
  327. package/src/edit/hashline/block-resolver.ts +33 -0
  328. package/src/edit/hashline/diff.ts +290 -0
  329. package/src/edit/hashline/execute.ts +237 -0
  330. package/src/edit/hashline/filesystem.ts +130 -0
  331. package/src/edit/hashline/index.ts +5 -0
  332. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  333. package/src/edit/hashline/params.ts +19 -0
  334. package/src/edit/index.ts +620 -0
  335. package/src/edit/modes/apply-patch.lark +19 -0
  336. package/src/edit/modes/apply-patch.ts +53 -0
  337. package/src/edit/modes/patch.ts +1888 -0
  338. package/src/edit/modes/replace.ts +1133 -0
  339. package/src/edit/normalize.ts +345 -0
  340. package/src/edit/notebook.ts +242 -0
  341. package/src/edit/read-file.ts +25 -0
  342. package/src/edit/renderer.ts +823 -0
  343. package/src/edit/streaming.ts +517 -0
  344. package/src/eval/__tests__/agent-bridge.test.ts +769 -0
  345. package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
  346. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  347. package/src/eval/__tests__/completion-bridge.test.ts +412 -0
  348. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  349. package/src/eval/__tests__/idle-timeout.test.ts +80 -0
  350. package/src/eval/__tests__/js-context-manager.test.ts +291 -0
  351. package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
  352. package/src/eval/__tests__/prelude-agent.test.ts +73 -0
  353. package/src/eval/agent-bridge.ts +319 -0
  354. package/src/eval/backend.ts +71 -0
  355. package/src/eval/bridge-timeout.ts +44 -0
  356. package/src/eval/budget-bridge.ts +48 -0
  357. package/src/eval/completion-bridge.ts +211 -0
  358. package/src/eval/concurrency-bridge.ts +34 -0
  359. package/src/eval/idle-timeout.ts +91 -0
  360. package/src/eval/index.ts +4 -0
  361. package/src/eval/js/context-manager.ts +621 -0
  362. package/src/eval/js/executor.ts +173 -0
  363. package/src/eval/js/index.ts +51 -0
  364. package/src/eval/js/shared/helpers.ts +283 -0
  365. package/src/eval/js/shared/indirect-eval.ts +30 -0
  366. package/src/eval/js/shared/local-module-loader.ts +342 -0
  367. package/src/eval/js/shared/prelude.ts +2 -0
  368. package/src/eval/js/shared/prelude.txt +307 -0
  369. package/src/eval/js/shared/rewrite-imports.ts +532 -0
  370. package/src/eval/js/shared/runtime.ts +580 -0
  371. package/src/eval/js/shared/types.ts +18 -0
  372. package/src/eval/js/tool-bridge.ts +163 -0
  373. package/src/eval/js/worker-core.ts +151 -0
  374. package/src/eval/js/worker-entry.ts +37 -0
  375. package/src/eval/js/worker-protocol.ts +47 -0
  376. package/src/eval/py/__tests__/prelude.test.ts +19 -0
  377. package/src/eval/py/display.ts +71 -0
  378. package/src/eval/py/executor.ts +742 -0
  379. package/src/eval/py/index.ts +68 -0
  380. package/src/eval/py/kernel.ts +748 -0
  381. package/src/eval/py/prelude.py +683 -0
  382. package/src/eval/py/prelude.ts +3 -0
  383. package/src/eval/py/runner.py +1177 -0
  384. package/src/eval/py/runtime.ts +276 -0
  385. package/src/eval/py/spawn-options.ts +126 -0
  386. package/src/eval/py/tool-bridge.ts +182 -0
  387. package/src/eval/session-id.ts +8 -0
  388. package/src/eval/types.ts +48 -0
  389. package/src/exa/index.ts +2 -0
  390. package/src/exa/mcp-client.ts +370 -0
  391. package/src/exa/types.ts +69 -0
  392. package/src/exec/bash-executor.ts +434 -0
  393. package/src/exec/exec.ts +53 -0
  394. package/src/exec/non-interactive-env.ts +119 -0
  395. package/src/export/custom-share.ts +65 -0
  396. package/src/export/html/index.ts +266 -0
  397. package/src/export/html/share-loader.js +102 -0
  398. package/src/export/html/template.css +1337 -0
  399. package/src/export/html/template.html +49 -0
  400. package/src/export/html/template.js +1626 -0
  401. package/src/export/html/tool-views.generated.js +37 -0
  402. package/src/export/html/vendor/highlight.min.js +1213 -0
  403. package/src/export/html/vendor/marked.min.js +6 -0
  404. package/src/export/share.ts +268 -0
  405. package/src/export/ttsr.ts +583 -0
  406. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  407. package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
  408. package/src/extensibility/custom-commands/index.ts +2 -0
  409. package/src/extensibility/custom-commands/loader.ts +242 -0
  410. package/src/extensibility/custom-commands/types.ts +119 -0
  411. package/src/extensibility/custom-tools/index.ts +7 -0
  412. package/src/extensibility/custom-tools/loader.ts +268 -0
  413. package/src/extensibility/custom-tools/types.ts +277 -0
  414. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  415. package/src/extensibility/extensions/compact-handler.ts +40 -0
  416. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  417. package/src/extensibility/extensions/index.ts +16 -0
  418. package/src/extensibility/extensions/loader.ts +587 -0
  419. package/src/extensibility/extensions/model-api.ts +41 -0
  420. package/src/extensibility/extensions/runner.ts +989 -0
  421. package/src/extensibility/extensions/types.ts +1394 -0
  422. package/src/extensibility/extensions/wrapper.ts +259 -0
  423. package/src/extensibility/hooks/index.ts +6 -0
  424. package/src/extensibility/hooks/loader.ts +262 -0
  425. package/src/extensibility/hooks/runner.ts +425 -0
  426. package/src/extensibility/hooks/tool-wrapper.ts +107 -0
  427. package/src/extensibility/hooks/types.ts +613 -0
  428. package/src/extensibility/legacy-pi-ai-shim.ts +61 -0
  429. package/src/extensibility/legacy-pi-coding-agent-shim.ts +128 -0
  430. package/src/extensibility/plugins/doctor.ts +65 -0
  431. package/src/extensibility/plugins/git-url.ts +367 -0
  432. package/src/extensibility/plugins/index.ts +9 -0
  433. package/src/extensibility/plugins/installer.ts +192 -0
  434. package/src/extensibility/plugins/legacy-pi-compat.ts +712 -0
  435. package/src/extensibility/plugins/loader.ts +458 -0
  436. package/src/extensibility/plugins/manager.ts +1026 -0
  437. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  438. package/src/extensibility/plugins/marketplace/fetcher.ts +315 -0
  439. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  440. package/src/extensibility/plugins/marketplace/manager.ts +770 -0
  441. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  442. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  443. package/src/extensibility/plugins/marketplace/types.ts +191 -0
  444. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  445. package/src/extensibility/plugins/parser.ts +105 -0
  446. package/src/extensibility/plugins/runtime-config.ts +9 -0
  447. package/src/extensibility/plugins/types.ts +194 -0
  448. package/src/extensibility/shared-events.ts +367 -0
  449. package/src/extensibility/skills.ts +408 -0
  450. package/src/extensibility/slash-commands.ts +131 -0
  451. package/src/extensibility/tool-proxy.ts +28 -0
  452. package/src/extensibility/typebox.ts +945 -0
  453. package/src/extensibility/utils.ts +44 -0
  454. package/src/goals/guided-setup.ts +142 -0
  455. package/src/goals/index.ts +3 -0
  456. package/src/goals/runtime.ts +521 -0
  457. package/src/goals/state.ts +37 -0
  458. package/src/goals/tools/goal-tool.ts +251 -0
  459. package/src/hindsight/backend.ts +354 -0
  460. package/src/hindsight/bank.ts +156 -0
  461. package/src/hindsight/client.ts +623 -0
  462. package/src/hindsight/config.ts +175 -0
  463. package/src/hindsight/content.ts +210 -0
  464. package/src/hindsight/index.ts +8 -0
  465. package/src/hindsight/mental-models.ts +429 -0
  466. package/src/hindsight/seeds.json +32 -0
  467. package/src/hindsight/state.ts +492 -0
  468. package/src/hindsight/transcript.ts +71 -0
  469. package/src/index.ts +66 -0
  470. package/src/internal-urls/agent-protocol.ts +146 -0
  471. package/src/internal-urls/artifact-protocol.ts +107 -0
  472. package/src/internal-urls/docs-index.generated.txt +2 -0
  473. package/src/internal-urls/docs-index.ts +102 -0
  474. package/src/internal-urls/history-protocol.ts +118 -0
  475. package/src/internal-urls/index.ts +25 -0
  476. package/src/internal-urls/issue-pr-protocol.ts +594 -0
  477. package/src/internal-urls/json-query.ts +126 -0
  478. package/src/internal-urls/local-protocol.ts +309 -0
  479. package/src/internal-urls/mcp-protocol.ts +151 -0
  480. package/src/internal-urls/memory-protocol.ts +169 -0
  481. package/src/internal-urls/omp-protocol.ts +94 -0
  482. package/src/internal-urls/parse.ts +72 -0
  483. package/src/internal-urls/registry-helpers.ts +25 -0
  484. package/src/internal-urls/router.ts +105 -0
  485. package/src/internal-urls/rule-protocol.ts +45 -0
  486. package/src/internal-urls/skill-protocol.ts +96 -0
  487. package/src/internal-urls/types.ts +152 -0
  488. package/src/internal-urls/vault-protocol.ts +936 -0
  489. package/src/irc/bus.ts +311 -0
  490. package/src/lib/xai-http.ts +124 -0
  491. package/src/lsp/client.ts +1217 -0
  492. package/src/lsp/clients/biome-client.ts +264 -0
  493. package/src/lsp/clients/index.ts +50 -0
  494. package/src/lsp/clients/lsp-linter-client.ts +85 -0
  495. package/src/lsp/clients/swiftlint-client.ts +120 -0
  496. package/src/lsp/config.ts +502 -0
  497. package/src/lsp/defaults.json +499 -0
  498. package/src/lsp/diagnostics-ledger.ts +51 -0
  499. package/src/lsp/edits.ts +267 -0
  500. package/src/lsp/format-options.ts +119 -0
  501. package/src/lsp/index.ts +2480 -0
  502. package/src/lsp/lspmux.ts +233 -0
  503. package/src/lsp/render.ts +668 -0
  504. package/src/lsp/startup-events.ts +13 -0
  505. package/src/lsp/types.ts +444 -0
  506. package/src/lsp/utils.ts +718 -0
  507. package/src/main.ts +1421 -0
  508. package/src/markit/NOTICE +32 -0
  509. package/src/markit/converters/docx.ts +56 -0
  510. package/src/markit/converters/epub.ts +136 -0
  511. package/src/markit/converters/mammoth.d.ts +24 -0
  512. package/src/markit/converters/pdf/columns.ts +103 -0
  513. package/src/markit/converters/pdf/extract.ts +574 -0
  514. package/src/markit/converters/pdf/grid.ts +780 -0
  515. package/src/markit/converters/pdf/headers.ts +106 -0
  516. package/src/markit/converters/pdf/index.ts +146 -0
  517. package/src/markit/converters/pdf/render.ts +501 -0
  518. package/src/markit/converters/pdf/types.ts +84 -0
  519. package/src/markit/converters/pptx.ts +325 -0
  520. package/src/markit/converters/xlsx.ts +173 -0
  521. package/src/markit/index.ts +2 -0
  522. package/src/markit/registry.ts +59 -0
  523. package/src/markit/types.ts +35 -0
  524. package/src/mcp/client.ts +509 -0
  525. package/src/mcp/config-writer.ts +229 -0
  526. package/src/mcp/config.ts +365 -0
  527. package/src/mcp/index.ts +29 -0
  528. package/src/mcp/json-rpc.ts +122 -0
  529. package/src/mcp/loader.ts +124 -0
  530. package/src/mcp/manager.ts +1326 -0
  531. package/src/mcp/oauth-credentials.ts +104 -0
  532. package/src/mcp/oauth-discovery.ts +467 -0
  533. package/src/mcp/oauth-flow.ts +555 -0
  534. package/src/mcp/render.ts +155 -0
  535. package/src/mcp/smithery-auth.ts +104 -0
  536. package/src/mcp/smithery-connect.ts +145 -0
  537. package/src/mcp/smithery-registry.ts +477 -0
  538. package/src/mcp/startup-events.ts +21 -0
  539. package/src/mcp/timeout.ts +59 -0
  540. package/src/mcp/tool-bridge.ts +429 -0
  541. package/src/mcp/tool-cache.ts +117 -0
  542. package/src/mcp/transports/http.ts +519 -0
  543. package/src/mcp/transports/index.ts +6 -0
  544. package/src/mcp/transports/stdio.ts +606 -0
  545. package/src/mcp/types.ts +427 -0
  546. package/src/memories/index.ts +1281 -0
  547. package/src/memories/storage.ts +578 -0
  548. package/src/memory-backend/index.ts +18 -0
  549. package/src/memory-backend/local-backend.ts +45 -0
  550. package/src/memory-backend/off-backend.ts +25 -0
  551. package/src/memory-backend/resolve.ts +25 -0
  552. package/src/memory-backend/runtime.ts +66 -0
  553. package/src/memory-backend/types.ts +166 -0
  554. package/src/mnemopi/backend.ts +612 -0
  555. package/src/mnemopi/config.ts +265 -0
  556. package/src/mnemopi/embed-client.ts +401 -0
  557. package/src/mnemopi/embed-protocol.ts +35 -0
  558. package/src/mnemopi/embed-worker.ts +113 -0
  559. package/src/mnemopi/index.ts +3 -0
  560. package/src/mnemopi/state.ts +657 -0
  561. package/src/modes/acp/acp-agent.ts +2362 -0
  562. package/src/modes/acp/acp-client-bridge.ts +154 -0
  563. package/src/modes/acp/acp-event-mapper.ts +933 -0
  564. package/src/modes/acp/acp-mode.ts +23 -0
  565. package/src/modes/acp/index.ts +2 -0
  566. package/src/modes/acp/terminal-auth.ts +37 -0
  567. package/src/modes/components/__tests__/skill-message.test.ts +92 -0
  568. package/src/modes/components/advisor-message.ts +99 -0
  569. package/src/modes/components/agent-dashboard.ts +1206 -0
  570. package/src/modes/components/agent-hub.ts +566 -0
  571. package/src/modes/components/agent-transcript-viewer.ts +461 -0
  572. package/src/modes/components/assistant-message.ts +612 -0
  573. package/src/modes/components/background-tan-message.ts +36 -0
  574. package/src/modes/components/bash-execution.ts +220 -0
  575. package/src/modes/components/bordered-loader.ts +41 -0
  576. package/src/modes/components/btw-panel.ts +112 -0
  577. package/src/modes/components/cache-invalidation-marker.ts +110 -0
  578. package/src/modes/components/chat-block.ts +111 -0
  579. package/src/modes/components/chat-transcript-builder.ts +476 -0
  580. package/src/modes/components/collab-prompt-message.ts +32 -0
  581. package/src/modes/components/compaction-summary-message.ts +215 -0
  582. package/src/modes/components/copy-selector.ts +206 -0
  583. package/src/modes/components/countdown-timer.ts +75 -0
  584. package/src/modes/components/custom-editor.test.ts +142 -0
  585. package/src/modes/components/custom-editor.ts +620 -0
  586. package/src/modes/components/custom-message.ts +67 -0
  587. package/src/modes/components/diff.ts +254 -0
  588. package/src/modes/components/dynamic-border.ts +34 -0
  589. package/src/modes/components/error-banner.ts +33 -0
  590. package/src/modes/components/eval-execution.ts +158 -0
  591. package/src/modes/components/execution-shared.ts +101 -0
  592. package/src/modes/components/extensions/extension-dashboard.ts +399 -0
  593. package/src/modes/components/extensions/extension-list.ts +502 -0
  594. package/src/modes/components/extensions/index.ts +9 -0
  595. package/src/modes/components/extensions/inspector-panel.ts +321 -0
  596. package/src/modes/components/extensions/state-manager.ts +627 -0
  597. package/src/modes/components/extensions/types.ts +186 -0
  598. package/src/modes/components/footer.ts +275 -0
  599. package/src/modes/components/history-search.ts +280 -0
  600. package/src/modes/components/hook-editor.ts +167 -0
  601. package/src/modes/components/hook-input.ts +87 -0
  602. package/src/modes/components/hook-message.ts +67 -0
  603. package/src/modes/components/hook-selector.ts +659 -0
  604. package/src/modes/components/index.ts +38 -0
  605. package/src/modes/components/keybinding-hints.ts +65 -0
  606. package/src/modes/components/late-diagnostics-message.ts +60 -0
  607. package/src/modes/components/login-dialog.ts +164 -0
  608. package/src/modes/components/logout-account-selector.ts +130 -0
  609. package/src/modes/components/mcp-add-wizard.ts +1360 -0
  610. package/src/modes/components/message-frame.ts +92 -0
  611. package/src/modes/components/model-selector.ts +1315 -0
  612. package/src/modes/components/oauth-selector.ts +457 -0
  613. package/src/modes/components/omfg-panel.ts +141 -0
  614. package/src/modes/components/overlay-box.ts +109 -0
  615. package/src/modes/components/plan-review-overlay.ts +847 -0
  616. package/src/modes/components/plan-toc.ts +138 -0
  617. package/src/modes/components/plugin-selector.ts +95 -0
  618. package/src/modes/components/plugin-settings.ts +739 -0
  619. package/src/modes/components/queue-mode-selector.ts +56 -0
  620. package/src/modes/components/read-tool-group.ts +676 -0
  621. package/src/modes/components/reset-usage-selector.ts +161 -0
  622. package/src/modes/components/segment-track.ts +89 -0
  623. package/src/modes/components/session-selector.ts +631 -0
  624. package/src/modes/components/settings-defs.ts +225 -0
  625. package/src/modes/components/settings-selector.ts +1095 -0
  626. package/src/modes/components/show-images-selector.ts +45 -0
  627. package/src/modes/components/skill-message.ts +110 -0
  628. package/src/modes/components/snapcompact-shape-preview-doc.md +18 -0
  629. package/src/modes/components/snapcompact-shape-preview.ts +192 -0
  630. package/src/modes/components/status-line/component.ts +1001 -0
  631. package/src/modes/components/status-line/context-thresholds.ts +78 -0
  632. package/src/modes/components/status-line/git-utils.ts +42 -0
  633. package/src/modes/components/status-line/index.ts +5 -0
  634. package/src/modes/components/status-line/presets.ts +106 -0
  635. package/src/modes/components/status-line/segments.ts +616 -0
  636. package/src/modes/components/status-line/separators.ts +55 -0
  637. package/src/modes/components/status-line/token-rate.ts +66 -0
  638. package/src/modes/components/status-line/types.ts +124 -0
  639. package/src/modes/components/theme-selector.ts +63 -0
  640. package/src/modes/components/thinking-selector.ts +52 -0
  641. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  642. package/src/modes/components/tips.txt +24 -0
  643. package/src/modes/components/todo-reminder.ts +39 -0
  644. package/src/modes/components/tool-execution.ts +1165 -0
  645. package/src/modes/components/transcript-container.ts +806 -0
  646. package/src/modes/components/tree-selector.ts +994 -0
  647. package/src/modes/components/ttsr-notification.ts +123 -0
  648. package/src/modes/components/usage-row.ts +18 -0
  649. package/src/modes/components/user-message-selector.ts +227 -0
  650. package/src/modes/components/user-message.ts +68 -0
  651. package/src/modes/components/visual-truncate.ts +63 -0
  652. package/src/modes/components/welcome.ts +581 -0
  653. package/src/modes/controllers/btw-controller.ts +173 -0
  654. package/src/modes/controllers/command-controller-shared.ts +109 -0
  655. package/src/modes/controllers/command-controller.ts +1653 -0
  656. package/src/modes/controllers/event-controller.ts +1153 -0
  657. package/src/modes/controllers/extension-ui-controller.ts +893 -0
  658. package/src/modes/controllers/input-controller.ts +1627 -0
  659. package/src/modes/controllers/mcp-command-controller.ts +2162 -0
  660. package/src/modes/controllers/omfg-controller.ts +283 -0
  661. package/src/modes/controllers/omfg-rule.ts +647 -0
  662. package/src/modes/controllers/selector-controller.ts +1285 -0
  663. package/src/modes/controllers/session-focus-controller.ts +112 -0
  664. package/src/modes/controllers/ssh-command-controller.ts +384 -0
  665. package/src/modes/controllers/streaming-reveal.ts +295 -0
  666. package/src/modes/controllers/tan-command-controller.ts +190 -0
  667. package/src/modes/controllers/todo-command-controller.ts +485 -0
  668. package/src/modes/controllers/tool-args-reveal.ts +174 -0
  669. package/src/modes/data/emojis.json +1 -0
  670. package/src/modes/emoji-autocomplete.ts +285 -0
  671. package/src/modes/gradient-highlight.ts +99 -0
  672. package/src/modes/image-references.ts +137 -0
  673. package/src/modes/index.ts +17 -0
  674. package/src/modes/interactive-mode.ts +3940 -0
  675. package/src/modes/internal-url-autocomplete.ts +143 -0
  676. package/src/modes/loop-limit.ts +192 -0
  677. package/src/modes/magic-keywords.ts +42 -0
  678. package/src/modes/markdown-prose.ts +247 -0
  679. package/src/modes/oauth-manual-input.ts +69 -0
  680. package/src/modes/orchestrate.ts +42 -0
  681. package/src/modes/print-mode.ts +130 -0
  682. package/src/modes/prompt-action-autocomplete.ts +260 -0
  683. package/src/modes/rpc/host-tools.ts +186 -0
  684. package/src/modes/rpc/host-uris.ts +235 -0
  685. package/src/modes/rpc/rpc-client.ts +995 -0
  686. package/src/modes/rpc/rpc-mode.ts +1156 -0
  687. package/src/modes/rpc/rpc-subagents.ts +265 -0
  688. package/src/modes/rpc/rpc-types.ts +487 -0
  689. package/src/modes/runtime-init.ts +142 -0
  690. package/src/modes/session-observer-registry.ts +215 -0
  691. package/src/modes/setup-version.ts +11 -0
  692. package/src/modes/setup-wizard/index.ts +101 -0
  693. package/src/modes/setup-wizard/lazy.ts +16 -0
  694. package/src/modes/setup-wizard/scenes/glyph.ts +114 -0
  695. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  696. package/src/modes/setup-wizard/scenes/providers.ts +103 -0
  697. package/src/modes/setup-wizard/scenes/sign-in.ts +286 -0
  698. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  699. package/src/modes/setup-wizard/scenes/theme.ts +326 -0
  700. package/src/modes/setup-wizard/scenes/types.ts +57 -0
  701. package/src/modes/setup-wizard/scenes/web-search.ts +145 -0
  702. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  703. package/src/modes/setup-wizard/wizard-overlay.ts +334 -0
  704. package/src/modes/shared.ts +49 -0
  705. package/src/modes/theme/dark.json +95 -0
  706. package/src/modes/theme/defaults/alabaster.json +93 -0
  707. package/src/modes/theme/defaults/amethyst.json +96 -0
  708. package/src/modes/theme/defaults/anthracite.json +93 -0
  709. package/src/modes/theme/defaults/basalt.json +91 -0
  710. package/src/modes/theme/defaults/birch.json +95 -0
  711. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  712. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  713. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  714. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  715. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  716. package/src/modes/theme/defaults/dark-copper.json +95 -0
  717. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  718. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  719. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  720. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  721. package/src/modes/theme/defaults/dark-ember.json +95 -0
  722. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  723. package/src/modes/theme/defaults/dark-forest.json +96 -0
  724. package/src/modes/theme/defaults/dark-github.json +105 -0
  725. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  726. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  727. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  728. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  729. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  730. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  731. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  732. package/src/modes/theme/defaults/dark-nord.json +97 -0
  733. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  734. package/src/modes/theme/defaults/dark-one.json +100 -0
  735. package/src/modes/theme/defaults/dark-poimandres.json +142 -0
  736. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  737. package/src/modes/theme/defaults/dark-reef.json +91 -0
  738. package/src/modes/theme/defaults/dark-retro.json +92 -0
  739. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  740. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  741. package/src/modes/theme/defaults/dark-slate.json +95 -0
  742. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  743. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  744. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  745. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  746. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  747. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  748. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  749. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  750. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  751. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  752. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  753. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  754. package/src/modes/theme/defaults/graphite.json +92 -0
  755. package/src/modes/theme/defaults/index.ts +199 -0
  756. package/src/modes/theme/defaults/light-arctic.json +107 -0
  757. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  758. package/src/modes/theme/defaults/light-canyon.json +91 -0
  759. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  760. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  761. package/src/modes/theme/defaults/light-coral.json +95 -0
  762. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  763. package/src/modes/theme/defaults/light-dawn.json +90 -0
  764. package/src/modes/theme/defaults/light-dunes.json +91 -0
  765. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  766. package/src/modes/theme/defaults/light-forest.json +100 -0
  767. package/src/modes/theme/defaults/light-frost.json +95 -0
  768. package/src/modes/theme/defaults/light-github.json +115 -0
  769. package/src/modes/theme/defaults/light-glacier.json +91 -0
  770. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  771. package/src/modes/theme/defaults/light-haze.json +90 -0
  772. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  773. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  774. package/src/modes/theme/defaults/light-lavender.json +95 -0
  775. package/src/modes/theme/defaults/light-meadow.json +91 -0
  776. package/src/modes/theme/defaults/light-mint.json +95 -0
  777. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  778. package/src/modes/theme/defaults/light-ocean.json +99 -0
  779. package/src/modes/theme/defaults/light-one.json +99 -0
  780. package/src/modes/theme/defaults/light-opal.json +91 -0
  781. package/src/modes/theme/defaults/light-orchard.json +91 -0
  782. package/src/modes/theme/defaults/light-paper.json +95 -0
  783. package/src/modes/theme/defaults/light-poimandres.json +142 -0
  784. package/src/modes/theme/defaults/light-prism.json +90 -0
  785. package/src/modes/theme/defaults/light-retro.json +98 -0
  786. package/src/modes/theme/defaults/light-sand.json +95 -0
  787. package/src/modes/theme/defaults/light-savanna.json +91 -0
  788. package/src/modes/theme/defaults/light-solarized.json +102 -0
  789. package/src/modes/theme/defaults/light-soleil.json +90 -0
  790. package/src/modes/theme/defaults/light-sunset.json +99 -0
  791. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  792. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  793. package/src/modes/theme/defaults/light-wetland.json +91 -0
  794. package/src/modes/theme/defaults/light-zenith.json +89 -0
  795. package/src/modes/theme/defaults/limestone.json +94 -0
  796. package/src/modes/theme/defaults/mahogany.json +97 -0
  797. package/src/modes/theme/defaults/marble.json +93 -0
  798. package/src/modes/theme/defaults/obsidian.json +91 -0
  799. package/src/modes/theme/defaults/onyx.json +91 -0
  800. package/src/modes/theme/defaults/pearl.json +93 -0
  801. package/src/modes/theme/defaults/porcelain.json +91 -0
  802. package/src/modes/theme/defaults/quartz.json +96 -0
  803. package/src/modes/theme/defaults/sandstone.json +95 -0
  804. package/src/modes/theme/defaults/titanium.json +90 -0
  805. package/src/modes/theme/light.json +93 -0
  806. package/src/modes/theme/mermaid-cache.ts +92 -0
  807. package/src/modes/theme/shimmer.ts +235 -0
  808. package/src/modes/theme/theme-schema.json +459 -0
  809. package/src/modes/theme/theme.ts +2915 -0
  810. package/src/modes/turn-budget.ts +31 -0
  811. package/src/modes/types.ts +406 -0
  812. package/src/modes/ultrathink.ts +41 -0
  813. package/src/modes/utils/context-usage.ts +432 -0
  814. package/src/modes/utils/copy-targets.ts +360 -0
  815. package/src/modes/utils/hotkeys-markdown.ts +62 -0
  816. package/src/modes/utils/keybinding-matchers.ts +51 -0
  817. package/src/modes/utils/tools-markdown.ts +27 -0
  818. package/src/modes/utils/ui-helpers.ts +886 -0
  819. package/src/modes/workflow.ts +42 -0
  820. package/src/plan-mode/approved-plan.ts +186 -0
  821. package/src/plan-mode/plan-handoff.ts +37 -0
  822. package/src/plan-mode/plan-protection.ts +31 -0
  823. package/src/plan-mode/state.ts +6 -0
  824. package/src/priority.json +45 -0
  825. package/src/prompts/advisor/advise-tool.md +3 -0
  826. package/src/prompts/advisor/system.md +113 -0
  827. package/src/prompts/agents/designer.md +74 -0
  828. package/src/prompts/agents/explore.md +58 -0
  829. package/src/prompts/agents/frontmatter.md +11 -0
  830. package/src/prompts/agents/init.md +33 -0
  831. package/src/prompts/agents/librarian.md +119 -0
  832. package/src/prompts/agents/oracle.md +54 -0
  833. package/src/prompts/agents/plan.md +48 -0
  834. package/src/prompts/agents/reviewer.md +139 -0
  835. package/src/prompts/agents/task.md +17 -0
  836. package/src/prompts/bench.md +12 -0
  837. package/src/prompts/ci-green-request.md +36 -0
  838. package/src/prompts/dry-balance-bench.md +8 -0
  839. package/src/prompts/goals/goal-budget-limit.md +16 -0
  840. package/src/prompts/goals/goal-continuation.md +28 -0
  841. package/src/prompts/goals/goal-mode-active.md +23 -0
  842. package/src/prompts/goals/guided-goal-interview.md +8 -0
  843. package/src/prompts/goals/guided-goal-system.md +12 -0
  844. package/src/prompts/low-end/system.md +47 -0
  845. package/src/prompts/memories/consolidation.md +30 -0
  846. package/src/prompts/memories/consolidation_system.md +4 -0
  847. package/src/prompts/memories/read-path.md +17 -0
  848. package/src/prompts/memories/stage_one_input.md +6 -0
  849. package/src/prompts/memories/stage_one_system.md +21 -0
  850. package/src/prompts/review-custom-request.md +22 -0
  851. package/src/prompts/review-headless-request.md +16 -0
  852. package/src/prompts/review-request.md +69 -0
  853. package/src/prompts/steering/user-interjection.md +9 -0
  854. package/src/prompts/system/agent-creation-architect.md +50 -0
  855. package/src/prompts/system/agent-creation-user.md +6 -0
  856. package/src/prompts/system/auto-continue.md +1 -0
  857. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  858. package/src/prompts/system/auto-thinking-difficulty.md +12 -0
  859. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  860. package/src/prompts/system/autolearn-guidance.md +7 -0
  861. package/src/prompts/system/autolearn-nudge.md +3 -0
  862. package/src/prompts/system/background-tan-dispatch.md +8 -0
  863. package/src/prompts/system/btw-user.md +8 -0
  864. package/src/prompts/system/commit-message-system.md +14 -0
  865. package/src/prompts/system/custom-system-prompt.md +64 -0
  866. package/src/prompts/system/eager-task.md +7 -0
  867. package/src/prompts/system/eager-todo.md +18 -0
  868. package/src/prompts/system/empty-stop-retry.md +4 -0
  869. package/src/prompts/system/irc-autoreply.md +6 -0
  870. package/src/prompts/system/irc-incoming.md +7 -0
  871. package/src/prompts/system/manual-continue.md +7 -0
  872. package/src/prompts/system/memory-consolidation-system.md +8 -0
  873. package/src/prompts/system/memory-extraction-system.md +26 -0
  874. package/src/prompts/system/omfg-user.md +50 -0
  875. package/src/prompts/system/orchestrate-notice.md +40 -0
  876. package/src/prompts/system/personalities/default.md +18 -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 +109 -0
  880. package/src/prompts/system/plan-mode-approved.md +25 -0
  881. package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
  882. package/src/prompts/system/plan-mode-reference.md +11 -0
  883. package/src/prompts/system/plan-mode-subagent.md +33 -0
  884. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  885. package/src/prompts/system/project-prompt.md +52 -0
  886. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  887. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  888. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  889. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  890. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  891. package/src/prompts/system/subagent-system-prompt.md +71 -0
  892. package/src/prompts/system/subagent-user-prompt.md +3 -0
  893. package/src/prompts/system/subagent-yield-reminder.md +12 -0
  894. package/src/prompts/system/system-prompt.md +251 -0
  895. package/src/prompts/system/tiny-title-system.md +8 -0
  896. package/src/prompts/system/title-marker-instruction.md +1 -0
  897. package/src/prompts/system/title-system-marker.md +16 -0
  898. package/src/prompts/system/title-system.md +16 -0
  899. package/src/prompts/system/ttsr-interrupt.md +7 -0
  900. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  901. package/src/prompts/system/ultrathink-notice.md +3 -0
  902. package/src/prompts/system/unexpected-stop-classifier.md +17 -0
  903. package/src/prompts/system/unexpected-stop-retry.md +4 -0
  904. package/src/prompts/system/web-search.md +25 -0
  905. package/src/prompts/system/workflow-notice.md +70 -0
  906. package/src/prompts/tools/apply-patch.md +65 -0
  907. package/src/prompts/tools/ask.md +22 -0
  908. package/src/prompts/tools/ast-edit.md +22 -0
  909. package/src/prompts/tools/ast-grep.md +25 -0
  910. package/src/prompts/tools/async-result.md +8 -0
  911. package/src/prompts/tools/bash.md +45 -0
  912. package/src/prompts/tools/browser.md +42 -0
  913. package/src/prompts/tools/checkpoint.md +15 -0
  914. package/src/prompts/tools/debug.md +17 -0
  915. package/src/prompts/tools/eval.md +70 -0
  916. package/src/prompts/tools/find.md +19 -0
  917. package/src/prompts/tools/github.md +17 -0
  918. package/src/prompts/tools/goal.md +11 -0
  919. package/src/prompts/tools/image-attachment-describe-system.md +8 -0
  920. package/src/prompts/tools/image-attachment-describe.md +10 -0
  921. package/src/prompts/tools/image-gen.md +7 -0
  922. package/src/prompts/tools/inspect-image-system.md +20 -0
  923. package/src/prompts/tools/inspect-image.md +22 -0
  924. package/src/prompts/tools/irc.md +33 -0
  925. package/src/prompts/tools/job.md +17 -0
  926. package/src/prompts/tools/learn.md +7 -0
  927. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  928. package/src/prompts/tools/lsp.md +39 -0
  929. package/src/prompts/tools/manage-skill.md +9 -0
  930. package/src/prompts/tools/memory-edit.md +8 -0
  931. package/src/prompts/tools/patch.md +57 -0
  932. package/src/prompts/tools/read.md +76 -0
  933. package/src/prompts/tools/recall.md +5 -0
  934. package/src/prompts/tools/reflect.md +5 -0
  935. package/src/prompts/tools/replace.md +29 -0
  936. package/src/prompts/tools/resolve.md +4 -0
  937. package/src/prompts/tools/retain.md +6 -0
  938. package/src/prompts/tools/rewind.md +13 -0
  939. package/src/prompts/tools/search-tool-bm25.md +32 -0
  940. package/src/prompts/tools/search.md +22 -0
  941. package/src/prompts/tools/ssh.md +22 -0
  942. package/src/prompts/tools/task-summary.md +17 -0
  943. package/src/prompts/tools/task.md +91 -0
  944. package/src/prompts/tools/todo.md +39 -0
  945. package/src/prompts/tools/web-search.md +6 -0
  946. package/src/prompts/tools/write.md +14 -0
  947. package/src/registry/agent-lifecycle.ts +270 -0
  948. package/src/registry/agent-registry.ts +190 -0
  949. package/src/sdk.ts +2919 -0
  950. package/src/secrets/index.ts +123 -0
  951. package/src/secrets/obfuscator.ts +298 -0
  952. package/src/secrets/regex.ts +21 -0
  953. package/src/session/agent-session.ts +12539 -0
  954. package/src/session/agent-storage.ts +478 -0
  955. package/src/session/artifacts.ts +153 -0
  956. package/src/session/auth-broker-config.ts +92 -0
  957. package/src/session/auth-storage.ts +24 -0
  958. package/src/session/blob-store.ts +255 -0
  959. package/src/session/client-bridge.ts +85 -0
  960. package/src/session/codex-auto-reset.ts +202 -0
  961. package/src/session/compact-modes.ts +105 -0
  962. package/src/session/history-storage.ts +361 -0
  963. package/src/session/indexed-session-storage.ts +427 -0
  964. package/src/session/messages.ts +546 -0
  965. package/src/session/redis-session-storage.ts +170 -0
  966. package/src/session/session-context.ts +399 -0
  967. package/src/session/session-dump-format.ts +216 -0
  968. package/src/session/session-entries.ts +198 -0
  969. package/src/session/session-history-format.ts +308 -0
  970. package/src/session/session-listing.ts +588 -0
  971. package/src/session/session-loader.ts +93 -0
  972. package/src/session/session-manager.ts +1748 -0
  973. package/src/session/session-migrations.ts +78 -0
  974. package/src/session/session-paths.ts +193 -0
  975. package/src/session/session-persistence.ts +147 -0
  976. package/src/session/session-storage.ts +590 -0
  977. package/src/session/shake-types.ts +43 -0
  978. package/src/session/snapcompact-inline.ts +542 -0
  979. package/src/session/snapcompact-savings-journal.ts +113 -0
  980. package/src/session/sql-session-storage.ts +314 -0
  981. package/src/session/streaming-output.ts +1330 -0
  982. package/src/session/tool-choice-queue.ts +290 -0
  983. package/src/session/unexpected-stop-classifier.ts +129 -0
  984. package/src/session/yield-queue.ts +183 -0
  985. package/src/slash-commands/acp-builtins.ts +70 -0
  986. package/src/slash-commands/available-commands.ts +105 -0
  987. package/src/slash-commands/builtin-registry.ts +2332 -0
  988. package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
  989. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  990. package/src/slash-commands/helpers/context-report.ts +66 -0
  991. package/src/slash-commands/helpers/format.ts +46 -0
  992. package/src/slash-commands/helpers/logout.ts +88 -0
  993. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  994. package/src/slash-commands/helpers/mcp.ts +532 -0
  995. package/src/slash-commands/helpers/parse.ts +85 -0
  996. package/src/slash-commands/helpers/reset-usage.ts +66 -0
  997. package/src/slash-commands/helpers/ssh.ts +195 -0
  998. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  999. package/src/slash-commands/helpers/todo.ts +279 -0
  1000. package/src/slash-commands/helpers/usage-report.ts +128 -0
  1001. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  1002. package/src/slash-commands/types.ts +135 -0
  1003. package/src/ssh/config-writer.ts +183 -0
  1004. package/src/ssh/connection-manager.ts +510 -0
  1005. package/src/ssh/ssh-executor.ts +189 -0
  1006. package/src/ssh/sshfs-mount.ts +140 -0
  1007. package/src/ssh/utils.ts +8 -0
  1008. package/src/startup-splash.ts +19 -0
  1009. package/src/stt/asr-client.ts +521 -0
  1010. package/src/stt/asr-protocol.ts +65 -0
  1011. package/src/stt/asr-worker.ts +790 -0
  1012. package/src/stt/downloader.ts +138 -0
  1013. package/src/stt/endpointer.ts +259 -0
  1014. package/src/stt/index.ts +7 -0
  1015. package/src/stt/models.ts +150 -0
  1016. package/src/stt/recorder.ts +538 -0
  1017. package/src/stt/stt-controller.ts +380 -0
  1018. package/src/stt/transcriber.ts +60 -0
  1019. package/src/stt/wav.ts +173 -0
  1020. package/src/system-prompt.ts +709 -0
  1021. package/src/task/agents.ts +166 -0
  1022. package/src/task/commands.ts +132 -0
  1023. package/src/task/discovery.ts +122 -0
  1024. package/src/task/executor.ts +2356 -0
  1025. package/src/task/index.ts +1580 -0
  1026. package/src/task/name-generator.ts +1577 -0
  1027. package/src/task/omp-command.ts +26 -0
  1028. package/src/task/output-manager.ts +93 -0
  1029. package/src/task/parallel.ts +116 -0
  1030. package/src/task/persisted-revive.ts +128 -0
  1031. package/src/task/render.ts +1558 -0
  1032. package/src/task/repair-args.ts +129 -0
  1033. package/src/task/subprocess-tool-registry.ts +88 -0
  1034. package/src/task/types.ts +401 -0
  1035. package/src/task/worktree.ts +514 -0
  1036. package/src/telemetry-export.ts +144 -0
  1037. package/src/thinking.ts +187 -0
  1038. package/src/tiny/device.ts +111 -0
  1039. package/src/tiny/dtype.ts +101 -0
  1040. package/src/tiny/models.ts +252 -0
  1041. package/src/tiny/text.ts +169 -0
  1042. package/src/tiny/title-client.ts +538 -0
  1043. package/src/tiny/title-protocol.ts +56 -0
  1044. package/src/tiny/worker.ts +491 -0
  1045. package/src/tool-discovery/mode.ts +24 -0
  1046. package/src/tool-discovery/tool-index.ts +271 -0
  1047. package/src/tools/__tests__/json-tree.test.ts +35 -0
  1048. package/src/tools/approval.ts +189 -0
  1049. package/src/tools/ask.ts +977 -0
  1050. package/src/tools/ast-edit.ts +700 -0
  1051. package/src/tools/ast-grep.ts +483 -0
  1052. package/src/tools/auto-generated-guard.ts +322 -0
  1053. package/src/tools/bash-command-fixup.ts +37 -0
  1054. package/src/tools/bash-interactive.ts +408 -0
  1055. package/src/tools/bash-interceptor.ts +67 -0
  1056. package/src/tools/bash-pty-selection.ts +14 -0
  1057. package/src/tools/bash-skill-urls.ts +248 -0
  1058. package/src/tools/bash.ts +1405 -0
  1059. package/src/tools/browser/attach.ts +194 -0
  1060. package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
  1061. package/src/tools/browser/cmux/rpc.ts +156 -0
  1062. package/src/tools/browser/cmux/socket-client.ts +309 -0
  1063. package/src/tools/browser/launch.ts +673 -0
  1064. package/src/tools/browser/readable.ts +112 -0
  1065. package/src/tools/browser/registry.ts +241 -0
  1066. package/src/tools/browser/render.ts +221 -0
  1067. package/src/tools/browser/tab-protocol.ts +107 -0
  1068. package/src/tools/browser/tab-supervisor.ts +799 -0
  1069. package/src/tools/browser/tab-worker-entry.ts +29 -0
  1070. package/src/tools/browser/tab-worker.ts +1226 -0
  1071. package/src/tools/browser.ts +403 -0
  1072. package/src/tools/builtin-names.ts +34 -0
  1073. package/src/tools/checkpoint.ts +136 -0
  1074. package/src/tools/conflict-detect.ts +718 -0
  1075. package/src/tools/context.ts +39 -0
  1076. package/src/tools/debug.ts +1087 -0
  1077. package/src/tools/eval-backends.ts +27 -0
  1078. package/src/tools/eval-render.ts +762 -0
  1079. package/src/tools/eval.ts +600 -0
  1080. package/src/tools/fetch.ts +1902 -0
  1081. package/src/tools/file-recorder.ts +35 -0
  1082. package/src/tools/find.ts +629 -0
  1083. package/src/tools/fs-cache-invalidation.ts +28 -0
  1084. package/src/tools/gh-cache-invalidation.ts +255 -0
  1085. package/src/tools/gh-format.ts +12 -0
  1086. package/src/tools/gh-renderer.ts +481 -0
  1087. package/src/tools/gh.ts +3752 -0
  1088. package/src/tools/github-cache.ts +663 -0
  1089. package/src/tools/grouped-file-output.ts +210 -0
  1090. package/src/tools/image-gen.ts +1586 -0
  1091. package/src/tools/index.ts +649 -0
  1092. package/src/tools/inspect-image-renderer.ts +132 -0
  1093. package/src/tools/inspect-image.ts +260 -0
  1094. package/src/tools/irc.ts +788 -0
  1095. package/src/tools/job.ts +612 -0
  1096. package/src/tools/json-tree.ts +260 -0
  1097. package/src/tools/jtd-to-json-schema.ts +219 -0
  1098. package/src/tools/jtd-to-typescript.ts +136 -0
  1099. package/src/tools/jtd-utils.ts +102 -0
  1100. package/src/tools/learn.ts +141 -0
  1101. package/src/tools/list-limit.ts +40 -0
  1102. package/src/tools/manage-skill.ts +100 -0
  1103. package/src/tools/match-line-format.ts +20 -0
  1104. package/src/tools/memory-edit.ts +59 -0
  1105. package/src/tools/memory-recall.ts +102 -0
  1106. package/src/tools/memory-reflect.ts +88 -0
  1107. package/src/tools/memory-render.ts +202 -0
  1108. package/src/tools/memory-retain.ts +89 -0
  1109. package/src/tools/output-meta.ts +768 -0
  1110. package/src/tools/output-schema-validator.ts +132 -0
  1111. package/src/tools/path-utils.ts +1116 -0
  1112. package/src/tools/plan-mode-guard.ts +142 -0
  1113. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  1114. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  1115. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  1116. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  1117. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  1118. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  1119. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  1120. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  1121. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  1122. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  1123. package/src/tools/puppeteer/10_stealth_plugins.txt +208 -0
  1124. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  1125. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  1126. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  1127. package/src/tools/read.ts +3124 -0
  1128. package/src/tools/render-utils.ts +895 -0
  1129. package/src/tools/renderers.ts +86 -0
  1130. package/src/tools/report-tool-issue.ts +530 -0
  1131. package/src/tools/resolve.ts +302 -0
  1132. package/src/tools/review.ts +251 -0
  1133. package/src/tools/search-tool-bm25.ts +351 -0
  1134. package/src/tools/search.ts +1583 -0
  1135. package/src/tools/sqlite-reader.ts +828 -0
  1136. package/src/tools/ssh.ts +369 -0
  1137. package/src/tools/todo.ts +938 -0
  1138. package/src/tools/tool-errors.ts +62 -0
  1139. package/src/tools/tool-result.ts +102 -0
  1140. package/src/tools/tool-timeouts.ts +30 -0
  1141. package/src/tools/tts.ts +265 -0
  1142. package/src/tools/write.ts +1182 -0
  1143. package/src/tools/yield.ts +269 -0
  1144. package/src/tts/downloader.ts +64 -0
  1145. package/src/tts/index.ts +8 -0
  1146. package/src/tts/models.ts +137 -0
  1147. package/src/tts/player.ts +137 -0
  1148. package/src/tts/runtime.ts +21 -0
  1149. package/src/tts/streaming-player.ts +266 -0
  1150. package/src/tts/tts-client.ts +642 -0
  1151. package/src/tts/tts-protocol.ts +60 -0
  1152. package/src/tts/tts-worker.ts +505 -0
  1153. package/src/tts/vocalizer.ts +162 -0
  1154. package/src/tts/wav.ts +58 -0
  1155. package/src/tui/code-cell.ts +257 -0
  1156. package/src/tui/file-list.ts +55 -0
  1157. package/src/tui/hyperlink.ts +178 -0
  1158. package/src/tui/index.ts +13 -0
  1159. package/src/tui/output-block.ts +240 -0
  1160. package/src/tui/status-line.ts +54 -0
  1161. package/src/tui/tree-list.ts +133 -0
  1162. package/src/tui/types.ts +15 -0
  1163. package/src/tui/utils.ts +103 -0
  1164. package/src/tui/width-aware-text.ts +58 -0
  1165. package/src/utils/block-context.ts +312 -0
  1166. package/src/utils/changelog.ts +132 -0
  1167. package/src/utils/clipboard.ts +262 -0
  1168. package/src/utils/command-args.ts +76 -0
  1169. package/src/utils/commit-message-generator.ts +147 -0
  1170. package/src/utils/edit-mode.ts +41 -0
  1171. package/src/utils/enhanced-paste.ts +230 -0
  1172. package/src/utils/event-bus.ts +33 -0
  1173. package/src/utils/external-editor.ts +78 -0
  1174. package/src/utils/file-display-mode.ts +45 -0
  1175. package/src/utils/file-mentions.ts +284 -0
  1176. package/src/utils/git.ts +1838 -0
  1177. package/src/utils/image-loading.ts +231 -0
  1178. package/src/utils/image-resize.ts +309 -0
  1179. package/src/utils/image-vision-fallback.ts +197 -0
  1180. package/src/utils/ipc.ts +38 -0
  1181. package/src/utils/jj.ts +248 -0
  1182. package/src/utils/lang-from-path.ts +244 -0
  1183. package/src/utils/markit.ts +143 -0
  1184. package/src/utils/mupdf-wasm-embed.ts +12 -0
  1185. package/src/utils/open.ts +55 -0
  1186. package/src/utils/qrcode.ts +535 -0
  1187. package/src/utils/session-color.ts +142 -0
  1188. package/src/utils/shell-snapshot.ts +187 -0
  1189. package/src/utils/sixel.ts +69 -0
  1190. package/src/utils/thinking-display.ts +11 -0
  1191. package/src/utils/title-generator.ts +416 -0
  1192. package/src/utils/tool-choice.ts +49 -0
  1193. package/src/utils/tools-manager.ts +372 -0
  1194. package/src/utils/turndown.ts +83 -0
  1195. package/src/utils/zip.ts +1091 -0
  1196. package/src/web/kagi.ts +304 -0
  1197. package/src/web/parallel.ts +353 -0
  1198. package/src/web/scrapers/artifacthub.ts +207 -0
  1199. package/src/web/scrapers/arxiv.ts +83 -0
  1200. package/src/web/scrapers/aur.ts +162 -0
  1201. package/src/web/scrapers/biorxiv.ts +133 -0
  1202. package/src/web/scrapers/bluesky.ts +262 -0
  1203. package/src/web/scrapers/brew.ts +172 -0
  1204. package/src/web/scrapers/cheatsh.ts +68 -0
  1205. package/src/web/scrapers/chocolatey.ts +196 -0
  1206. package/src/web/scrapers/choosealicense.ts +95 -0
  1207. package/src/web/scrapers/cisa-kev.ts +87 -0
  1208. package/src/web/scrapers/clojars.ts +154 -0
  1209. package/src/web/scrapers/coingecko.ts +177 -0
  1210. package/src/web/scrapers/crates-io.ts +97 -0
  1211. package/src/web/scrapers/crossref.ts +136 -0
  1212. package/src/web/scrapers/devto.ts +147 -0
  1213. package/src/web/scrapers/discogs.ts +306 -0
  1214. package/src/web/scrapers/discourse.ts +197 -0
  1215. package/src/web/scrapers/dockerhub.ts +138 -0
  1216. package/src/web/scrapers/docs-rs.ts +652 -0
  1217. package/src/web/scrapers/fdroid.ts +134 -0
  1218. package/src/web/scrapers/firefox-addons.ts +191 -0
  1219. package/src/web/scrapers/flathub.ts +223 -0
  1220. package/src/web/scrapers/github-gist.ts +58 -0
  1221. package/src/web/scrapers/github.ts +800 -0
  1222. package/src/web/scrapers/gitlab.ts +401 -0
  1223. package/src/web/scrapers/go-pkg.ts +266 -0
  1224. package/src/web/scrapers/hackage.ts +140 -0
  1225. package/src/web/scrapers/hackernews.ts +189 -0
  1226. package/src/web/scrapers/hex.ts +105 -0
  1227. package/src/web/scrapers/huggingface.ts +321 -0
  1228. package/src/web/scrapers/iacr.ts +89 -0
  1229. package/src/web/scrapers/index.ts +252 -0
  1230. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1231. package/src/web/scrapers/lemmy.ts +203 -0
  1232. package/src/web/scrapers/lobsters.ts +175 -0
  1233. package/src/web/scrapers/mastodon.ts +292 -0
  1234. package/src/web/scrapers/maven.ts +138 -0
  1235. package/src/web/scrapers/mdn.ts +173 -0
  1236. package/src/web/scrapers/metacpan.ts +222 -0
  1237. package/src/web/scrapers/musicbrainz.ts +250 -0
  1238. package/src/web/scrapers/npm.ts +98 -0
  1239. package/src/web/scrapers/nuget.ts +183 -0
  1240. package/src/web/scrapers/nvd.ts +222 -0
  1241. package/src/web/scrapers/ollama.ts +239 -0
  1242. package/src/web/scrapers/open-vsx.ts +106 -0
  1243. package/src/web/scrapers/opencorporates.ts +292 -0
  1244. package/src/web/scrapers/openlibrary.ts +336 -0
  1245. package/src/web/scrapers/orcid.ts +286 -0
  1246. package/src/web/scrapers/osv.ts +176 -0
  1247. package/src/web/scrapers/packagist.ts +160 -0
  1248. package/src/web/scrapers/pub-dev.ts +143 -0
  1249. package/src/web/scrapers/pubmed.ts +211 -0
  1250. package/src/web/scrapers/pypi.ts +112 -0
  1251. package/src/web/scrapers/rawg.ts +110 -0
  1252. package/src/web/scrapers/readthedocs.ts +120 -0
  1253. package/src/web/scrapers/reddit.ts +95 -0
  1254. package/src/web/scrapers/repology.ts +251 -0
  1255. package/src/web/scrapers/rfc.ts +201 -0
  1256. package/src/web/scrapers/rubygems.ts +103 -0
  1257. package/src/web/scrapers/searchcode.ts +189 -0
  1258. package/src/web/scrapers/sec-edgar.ts +261 -0
  1259. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1260. package/src/web/scrapers/snapcraft.ts +187 -0
  1261. package/src/web/scrapers/sourcegraph.ts +336 -0
  1262. package/src/web/scrapers/spdx.ts +108 -0
  1263. package/src/web/scrapers/spotify.ts +198 -0
  1264. package/src/web/scrapers/stackoverflow.ts +120 -0
  1265. package/src/web/scrapers/terraform.ts +277 -0
  1266. package/src/web/scrapers/tldr.ts +47 -0
  1267. package/src/web/scrapers/twitter.ts +94 -0
  1268. package/src/web/scrapers/types.ts +354 -0
  1269. package/src/web/scrapers/utils.ts +109 -0
  1270. package/src/web/scrapers/vimeo.ts +133 -0
  1271. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1272. package/src/web/scrapers/w3c.ts +156 -0
  1273. package/src/web/scrapers/wikidata.ts +344 -0
  1274. package/src/web/scrapers/wikipedia.ts +84 -0
  1275. package/src/web/scrapers/youtube.ts +325 -0
  1276. package/src/web/search/index.ts +317 -0
  1277. package/src/web/search/provider.ts +169 -0
  1278. package/src/web/search/providers/anthropic.ts +343 -0
  1279. package/src/web/search/providers/base.ts +90 -0
  1280. package/src/web/search/providers/brave.ts +152 -0
  1281. package/src/web/search/providers/codex.ts +593 -0
  1282. package/src/web/search/providers/exa.ts +400 -0
  1283. package/src/web/search/providers/gemini.ts +518 -0
  1284. package/src/web/search/providers/jina.ts +111 -0
  1285. package/src/web/search/providers/kagi.ts +86 -0
  1286. package/src/web/search/providers/kimi.ts +196 -0
  1287. package/src/web/search/providers/parallel.ts +225 -0
  1288. package/src/web/search/providers/perplexity-auth.ts +133 -0
  1289. package/src/web/search/providers/perplexity.ts +866 -0
  1290. package/src/web/search/providers/searxng.ts +325 -0
  1291. package/src/web/search/providers/synthetic.ts +114 -0
  1292. package/src/web/search/providers/tavily.ts +176 -0
  1293. package/src/web/search/providers/utils.ts +128 -0
  1294. package/src/web/search/providers/zai.ts +333 -0
  1295. package/src/web/search/render.ts +262 -0
  1296. package/src/web/search/types.ts +462 -0
  1297. package/src/web/search/utils.ts +17 -0
  1298. package/src/workspace-tree.ts +326 -0
@@ -0,0 +1,2260 @@
1
+ import { execSync } from "node:child_process";
2
+ import * as path from "node:path";
3
+ import { registerCustomApi, unregisterCustomApis } from "@oh-my-pi/pi-ai/api-registry";
4
+ import type { Api, Context, Model, ModelSpec, SimpleStreamOptions, ThinkingConfig } from "@oh-my-pi/pi-ai/types";
5
+ import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
6
+ import { buildModel } from "@oh-my-pi/pi-catalog/build";
7
+ import { isVertexExpressOpenAIUrl } from "@oh-my-pi/pi-catalog/hosts";
8
+ import { readModelCache } from "@oh-my-pi/pi-catalog/model-cache";
9
+ import {
10
+ createModelManager,
11
+ type ModelManagerOptions,
12
+ type ModelRefreshStrategy,
13
+ } from "@oh-my-pi/pi-catalog/model-manager";
14
+ import { getBundledModels, getBundledProviders } from "@oh-my-pi/pi-catalog/models";
15
+ import {
16
+ googleAntigravityModelManagerOptions,
17
+ googleGeminiCliModelManagerOptions,
18
+ openaiCodexModelManagerOptions,
19
+ PROVIDER_DESCRIPTORS,
20
+ } from "@oh-my-pi/pi-catalog/provider-models";
21
+ import {
22
+ collapseBuiltModelVariants,
23
+ getVariantAliasSources,
24
+ resolveVariantAlias,
25
+ } from "@oh-my-pi/pi-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.
30
+ const DEFAULT_LOCAL_TOKEN = "lm-studio-local";
31
+ const DEFAULT_VLLM_LOCAL_TOKEN = "vllm-local";
32
+
33
+ const SPECIAL_MODEL_MANAGER_PROVIDER_IDS: readonly string[] = [
34
+ "google-antigravity",
35
+ "google-gemini-cli",
36
+ "openai-codex",
37
+ ];
38
+
39
+ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
40
+ ...PROVIDER_DESCRIPTORS.map(descriptor => descriptor.providerId),
41
+ ...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
42
+ ];
43
+
44
+ import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai";
45
+ import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
46
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
47
+ import {
48
+ buildCanonicalModelIndex,
49
+ buildCanonicalModelOrder,
50
+ buildModelProviderPriorityRank,
51
+ type CanonicalModelIndex,
52
+ type CanonicalModelRecord,
53
+ type CanonicalModelVariant,
54
+ type CanonicalVariantPreferences,
55
+ formatCanonicalVariantSelector,
56
+ getBundledCanonicalReferenceData,
57
+ getBundledModelReferenceIndex,
58
+ type ModelEquivalenceConfig,
59
+ resolveCanonicalVariant,
60
+ resolveModelReference,
61
+ } from "@oh-my-pi/pi-catalog/identity";
62
+ import { isBunTestRuntime, isRecord, logger } from "@oh-my-pi/pi-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";
67
+ import {
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";
78
+
79
+ export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig };
80
+
81
+ export const kNoAuth = "N/A";
82
+
83
+ export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
84
+ return Boolean(apiKey) && apiKey !== kNoAuth;
85
+ }
86
+
87
+ function isDiscoveryBearerApiKey(apiKey: string | undefined | null): apiKey is string {
88
+ return isAuthenticated(apiKey) && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== DEFAULT_VLLM_LOCAL_TOKEN;
89
+ }
90
+
91
+ /** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
92
+ interface ProviderOverride {
93
+ baseUrl?: string;
94
+ headers?: Record<string, string>;
95
+ apiKey?: string;
96
+ authHeader?: boolean;
97
+ compat?: ModelSpec<Api>["compat"];
98
+ transport?: Model<Api>["transport"];
99
+ }
100
+
101
+ /**
102
+ * Merge a freshly discovered model with the matching bundled/configured entry
103
+ * (or a runtime provider override when no bundled entry exists).
104
+ *
105
+ * `baseUrl` resolution priority:
106
+ * 1. User-set `providerOverride.baseUrl` (explicit override in models.json)
107
+ * 2. Discovered baseUrl (xiaomi `tp-` token-plan keys resolve to
108
+ * `token-plan-sgp.xiaomimimo.com` at discovery time)
109
+ * 3. Existing bundled baseUrl (the host baked into `models.json`)
110
+ *
111
+ * `transport` resolution priority:
112
+ * 1. `providerOverride.transport` (e.g. `pi-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
+ *
116
+ * Without (1), the user's override would lose to discovery; without (2)
117
+ * preferred over (3), the bundled `api.xiaomimimo.com` would shadow the
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: pi-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).
124
+ * See `xiaomi-tp-discovery-merge.test.ts` and the `refresh()` baseUrl-override
125
+ * regression in `model-registry.test.ts`.
126
+ */
127
+ export function mergeDiscoveredModel<TApi extends Api>(
128
+ model: Model<TApi>,
129
+ existing: Model<Api> | undefined,
130
+ providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "transport">,
131
+ ): Model<TApi> {
132
+ if (existing) {
133
+ const supportsTools = model.supportsTools ?? existing.supportsTools;
134
+ return buildModel({
135
+ ...model,
136
+ baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
137
+ headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
138
+ transport: providerOverride?.transport ?? existing.transport ?? model.transport,
139
+ ...(supportsTools !== undefined ? { supportsTools } : {}),
140
+ compat: model.compatConfig,
141
+ } as ModelSpec<TApi>);
142
+ }
143
+ if (providerOverride) {
144
+ return buildModel({
145
+ ...model,
146
+ baseUrl: providerOverride.baseUrl ?? model.baseUrl,
147
+ headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
148
+ ...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
149
+ compat: model.compatConfig,
150
+ } as ModelSpec<TApi>);
151
+ }
152
+ return model;
153
+ }
154
+
155
+ const AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS = new Set<string>(
156
+ PROVIDER_DESCRIPTORS.filter(descriptor => descriptor.dynamicModelsAuthoritative).map(
157
+ descriptor => descriptor.providerId,
158
+ ),
159
+ );
160
+
161
+ function isAuthoritativeProjectCatalogModel(model: Model<Api>): boolean {
162
+ return (
163
+ model.provider === "google-vertex" &&
164
+ model.api === "openai-completions" &&
165
+ isVertexExpressOpenAIUrl(model.baseUrl)
166
+ );
167
+ }
168
+
169
+ function providersWithAuthoritativeProjectCatalog(models: readonly Model<Api>[]): Set<string> {
170
+ const providers = new Set<string>();
171
+ for (const model of models) {
172
+ if (isAuthoritativeProjectCatalogModel(model)) {
173
+ providers.add(model.provider);
174
+ }
175
+ }
176
+ return providers;
177
+ }
178
+
179
+ function dropProviderModels(models: readonly Model<Api>[], providers: ReadonlySet<string>): Model<Api>[] {
180
+ return models.filter(model => !providers.has(model.provider));
181
+ }
182
+
183
+ /**
184
+ * Merge `incoming` entries into a copy of `base`, keyed by `provider`+`id`.
185
+ * Matches are replaced with `combine(existing, entry)`; new entries are
186
+ * appended as `combine(undefined, entry)`.
187
+ */
188
+ function mergeByModelKey<T extends { provider: string; id: string }>(
189
+ base: readonly Model<Api>[],
190
+ incoming: readonly T[],
191
+ combine: (existing: Model<Api> | undefined, entry: T) => Model<Api>,
192
+ ): Model<Api>[] {
193
+ const merged = [...base];
194
+ const indexByKey = new Map<string, number>();
195
+ for (let i = 0; i < merged.length; i += 1) {
196
+ indexByKey.set(`${merged[i].provider}\u0000${merged[i].id}`, i);
197
+ }
198
+ for (const entry of incoming) {
199
+ const key = `${entry.provider}\u0000${entry.id}`;
200
+ const existingIndex = indexByKey.get(key);
201
+ if (existingIndex !== undefined) {
202
+ merged[existingIndex] = combine(merged[existingIndex], entry);
203
+ } else {
204
+ merged.push(combine(undefined, entry));
205
+ indexByKey.set(key, merged.length - 1);
206
+ }
207
+ }
208
+ return merged;
209
+ }
210
+
211
+ interface BuiltInDiscoveryResult {
212
+ models: Model<Api>[];
213
+ authoritativeProviders: Set<string>;
214
+ }
215
+
216
+ export type ProviderDiscoveryStatus = "idle" | "ok" | "empty" | "cached" | "unavailable" | "unauthenticated";
217
+
218
+ export interface ProviderDiscoveryState {
219
+ provider: string;
220
+ status: ProviderDiscoveryStatus;
221
+ optional: boolean;
222
+ stale: boolean;
223
+ fetchedAt?: number;
224
+ models: string[];
225
+ error?: string;
226
+ }
227
+
228
+ export interface CanonicalModelQueryOptions {
229
+ availableOnly?: boolean;
230
+ candidates?: readonly Model<Api>[];
231
+ }
232
+
233
+ /** A canonical record (with query-filtered variants) plus the variant model selected for it. */
234
+ export interface CanonicalModelSelection {
235
+ record: CanonicalModelRecord;
236
+ model: Model<Api>;
237
+ }
238
+
239
+ /** Result of loading custom models from models.json */
240
+ interface CustomModelsResult {
241
+ models?: CustomModelOverlay[];
242
+ overrides?: Map<string, ProviderOverride>;
243
+ modelOverrides?: Map<string, Map<string, ModelOverride>>;
244
+ keylessProviders?: Set<string>;
245
+ discoverableProviders?: DiscoveryProviderConfig[];
246
+ configuredProviders?: Set<string>;
247
+ equivalence?: ModelEquivalenceConfig;
248
+ error?: ConfigError;
249
+ found: boolean;
250
+ }
251
+
252
+ const commandValueCache = new Map<string, string>();
253
+
254
+ function isCommandConfigValue(valueConfig: string | undefined): valueConfig is string {
255
+ return valueConfig?.startsWith("!") === true;
256
+ }
257
+
258
+ function resolveCommandConfig(command: string): string | undefined {
259
+ const cached = commandValueCache.get(command);
260
+ if (cached !== undefined) return cached;
261
+ try {
262
+ const stdout = execSync(command, { encoding: "utf8", timeout: 10_000, windowsHide: true });
263
+ const trimmed = stdout.trim();
264
+ if (trimmed.length === 0) return undefined;
265
+ commandValueCache.set(command, trimmed);
266
+ return trimmed;
267
+ } catch {
268
+ return undefined;
269
+ }
270
+ }
271
+
272
+ interface CommandApiKeyResolution {
273
+ configured: boolean;
274
+ value?: string;
275
+ }
276
+ /**
277
+ * Resolve a models.yml secret/config value to an actual value.
278
+ * `!cmd` runs a shell command and returns trimmed stdout, otherwise env vars are
279
+ * checked first and the input falls back to a literal value.
280
+ */
281
+ function resolveConfigValue(valueConfig: string): string | undefined {
282
+ if (valueConfig.startsWith("!")) return resolveCommandConfig(valueConfig.slice(1).trim());
283
+ const envValue = Bun.env[valueConfig];
284
+ if (envValue) return envValue;
285
+ return valueConfig;
286
+ }
287
+
288
+ function resolveConfigHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {
289
+ if (!headers) return undefined;
290
+ const resolved: Record<string, string> = {};
291
+ for (const [key, value] of Object.entries(headers)) {
292
+ const next = resolveConfigValue(value);
293
+ if (next) resolved[key] = next;
294
+ }
295
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
296
+ }
297
+
298
+ function extractGoogleOAuthToken(value: string | undefined): string | undefined {
299
+ if (!isAuthenticated(value)) return undefined;
300
+ try {
301
+ const parsed = JSON.parse(value) as { token?: unknown };
302
+ if (Object.hasOwn(parsed, "token")) {
303
+ if (typeof parsed.token !== "string") {
304
+ return undefined;
305
+ }
306
+ const token = parsed.token.trim();
307
+ return token.length > 0 ? token : undefined;
308
+ }
309
+ } catch {
310
+ // OAuth values for Google providers are expected to be JSON, but custom setups may already provide raw token.
311
+ }
312
+ return value;
313
+ }
314
+
315
+ function getOAuthCredentialsForProvider(authStorage: AuthStorage, provider: string): OAuthCredential[] {
316
+ const providerEntry = authStorage.getAll()[provider];
317
+ if (!providerEntry) {
318
+ return [];
319
+ }
320
+ const entries = Array.isArray(providerEntry) ? providerEntry : [providerEntry];
321
+ return entries.filter((entry): entry is OAuthCredential => entry.type === "oauth");
322
+ }
323
+
324
+ function resolveOAuthAccountIdForAccessToken(
325
+ authStorage: AuthStorage,
326
+ provider: string,
327
+ accessToken: string,
328
+ ): string | undefined {
329
+ const oauthCredentials = getOAuthCredentialsForProvider(authStorage, provider);
330
+ const matchingCredential = oauthCredentials.find(credential => credential.access === accessToken);
331
+ if (matchingCredential) {
332
+ return matchingCredential.accountId;
333
+ }
334
+ if (oauthCredentials.length === 1) {
335
+ return oauthCredentials[0].accountId;
336
+ }
337
+ return undefined;
338
+ }
339
+
340
+ function mergeCompat<TBase extends object, TOverride extends object>(
341
+ baseCompat: TBase | null | undefined,
342
+ overrideCompat: TOverride | null | undefined,
343
+ ): (TBase & TOverride) | TBase | TOverride | undefined {
344
+ if (!baseCompat) return overrideCompat ?? undefined;
345
+ if (!overrideCompat) return baseCompat;
346
+
347
+ const merged: Record<string, unknown> = { ...(baseCompat as Record<string, unknown>) };
348
+ for (const [key, overrideValue] of Object.entries(overrideCompat)) {
349
+ const baseValue = (baseCompat as Record<string, unknown>)[key];
350
+ merged[key] =
351
+ isRecord(baseValue) && isRecord(overrideValue) ? mergeCompat(baseValue, overrideValue) : overrideValue;
352
+ }
353
+ return merged as TBase & TOverride;
354
+ }
355
+
356
+ /**
357
+ * Project a built model back to spec shape for the model-manager/cache
358
+ * boundary: sparse compat comes from `compatConfig`, never from the resolved
359
+ * record.
360
+ */
361
+ function toModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi> {
362
+ return { ...model, compat: model.compatConfig } as ModelSpec<TApi>;
363
+ }
364
+
365
+ /**
366
+ * The patchable subset of `Model` fields shared by `modelOverrides` entries,
367
+ * custom model definitions, and parsed custom-model overlays. `undefined`
368
+ * always means "leave the base value alone".
369
+ */
370
+ interface ModelPatch {
371
+ name?: string;
372
+ reasoning?: boolean;
373
+ thinking?: ThinkingConfig;
374
+ input?: ("text" | "image")[];
375
+ supportsTools?: boolean;
376
+ cost?: Partial<Model<Api>["cost"]>;
377
+ contextWindow?: number;
378
+ maxTokens?: number;
379
+ omitMaxOutputTokens?: boolean;
380
+ headers?: Record<string, string>;
381
+ compat?: ModelSpec<Api>["compat"];
382
+ contextPromotionTarget?: string;
383
+ premiumMultiplier?: number;
384
+ }
385
+
386
+ /**
387
+ * How a patch treats the base model's transport metadata (headers/compat):
388
+ * - `merge`: fold the patch into the base's (modelOverrides semantics).
389
+ * - `replace`: the patch owns transport wholesale — same-id custom definitions
390
+ * already folded provider-level headers/compat in during parsing, so bundled
391
+ * transport metadata must not be re-merged (see `#mergeCustomModels`).
392
+ */
393
+ type ModelTransportPolicy = "merge" | "replace";
394
+
395
+ function applyModelPatch(base: Model<Api>, patch: ModelPatch, transport: ModelTransportPolicy): Model<Api> {
396
+ const result = { ...base };
397
+ if (patch.name !== undefined) result.name = patch.name;
398
+ if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
399
+ if (patch.thinking !== undefined) result.thinking = patch.thinking;
400
+ if (patch.input !== undefined) result.input = patch.input;
401
+ if (patch.supportsTools !== undefined) result.supportsTools = patch.supportsTools;
402
+ if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
403
+ if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
404
+ if (patch.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = patch.omitMaxOutputTokens;
405
+ if (patch.contextPromotionTarget !== undefined) result.contextPromotionTarget = patch.contextPromotionTarget;
406
+ if (patch.premiumMultiplier !== undefined) result.premiumMultiplier = patch.premiumMultiplier;
407
+ if (patch.cost) {
408
+ result.cost = {
409
+ input: patch.cost.input ?? base.cost.input,
410
+ output: patch.cost.output ?? base.cost.output,
411
+ cacheRead: patch.cost.cacheRead ?? base.cost.cacheRead,
412
+ cacheWrite: patch.cost.cacheWrite ?? base.cost.cacheWrite,
413
+ };
414
+ }
415
+ let compat: ModelSpec<Api>["compat"];
416
+ if (transport === "merge") {
417
+ if (patch.headers) {
418
+ result.headers = { ...base.headers, ...patch.headers };
419
+ }
420
+ compat = mergeCompat(base.compatConfig, patch.compat);
421
+ } else {
422
+ result.headers = patch.headers;
423
+ compat = patch.compat;
424
+ }
425
+ return buildModel({ ...result, compat } as ModelSpec<Api>);
426
+ }
427
+
428
+ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {
429
+ return applyModelPatch(model, override as ModelPatch, "merge");
430
+ }
431
+
432
+ interface CustomModelDefinitionLike extends ModelPatch {
433
+ id: string;
434
+ api?: Api;
435
+ baseUrl?: string;
436
+ cost?: Model<Api>["cost"];
437
+ }
438
+
439
+ interface CustomModelBuildOptions {
440
+ useDefaults: boolean;
441
+ }
442
+
443
+ interface CustomModelOverlay extends ModelPatch {
444
+ id: string;
445
+ provider: string;
446
+ api: Api;
447
+ baseUrl: string;
448
+ cost?: Model<Api>["cost"];
449
+ isOAuth?: boolean;
450
+ }
451
+
452
+ function mergeCustomModelHeaders(
453
+ providerHeaders: Record<string, string> | undefined,
454
+ modelHeaders: Record<string, string> | undefined,
455
+ authHeader: boolean | undefined,
456
+ apiKeyConfig: string | undefined,
457
+ ): Record<string, string> | undefined {
458
+ const resolvedModelHeaders = resolveConfigHeaders(modelHeaders);
459
+ return mergeAuthHeader({ ...providerHeaders, ...resolvedModelHeaders }, authHeader, apiKeyConfig);
460
+ }
461
+
462
+ function mergeAuthHeader(
463
+ headers: Record<string, string> | undefined,
464
+ authHeader: boolean | undefined,
465
+ apiKeyConfig: string | undefined,
466
+ ): Record<string, string> | undefined {
467
+ const nextHeaders = headers && Object.keys(headers).length > 0 ? { ...headers } : undefined;
468
+ if (!authHeader || !apiKeyConfig) {
469
+ return nextHeaders;
470
+ }
471
+ const resolvedKey = resolveConfigValue(apiKeyConfig);
472
+ return resolvedKey ? { ...nextHeaders, Authorization: `Bearer ${resolvedKey}` } : nextHeaders;
473
+ }
474
+
475
+ /**
476
+ * Decide whether a custom-yaml model should force OAuth-style request shaping.
477
+ * - Explicit `auth: oauth` → force on.
478
+ * - Explicit `auth: apiKey` / `auth: none` → leave unset (auto-detect by key prefix).
479
+ * - No `auth` specified and `api: anthropic-messages` → default on. Custom Anthropic
480
+ * endpoints are typically Claude-Code-style proxies (e.g. CLIProxyAPI) that expect
481
+ * the cloaked request shape regardless of how the proxy itself is authenticated.
482
+ * - Otherwise → unset.
483
+ */
484
+ function resolveCustomModelIsOAuth(api: Api, providerAuth: ProviderAuthMode | undefined): boolean | undefined {
485
+ if (providerAuth === "oauth") return true;
486
+ if (providerAuth !== undefined) return undefined;
487
+ if (api === "anthropic-messages") return true;
488
+ return undefined;
489
+ }
490
+
491
+ function buildCustomModelOverlay(
492
+ providerName: string,
493
+ providerBaseUrl: string,
494
+ providerApi: Api | undefined,
495
+ providerHeaders: Record<string, string> | undefined,
496
+ providerApiKey: string | undefined,
497
+ authHeader: boolean | undefined,
498
+ providerCompat: ModelSpec<Api>["compat"] | undefined,
499
+ providerAuth: ProviderAuthMode | undefined,
500
+ modelDef: CustomModelDefinitionLike,
501
+ ): CustomModelOverlay | undefined {
502
+ const api = modelDef.api ?? providerApi;
503
+ if (!api) return undefined;
504
+ return {
505
+ id: modelDef.id,
506
+ provider: providerName,
507
+ api,
508
+ baseUrl: modelDef.baseUrl ?? providerBaseUrl,
509
+ name: modelDef.name,
510
+ reasoning: modelDef.reasoning,
511
+ thinking: modelDef.thinking,
512
+ input: modelDef.input,
513
+ supportsTools: modelDef.supportsTools,
514
+ cost: modelDef.cost,
515
+ contextWindow: modelDef.contextWindow,
516
+ maxTokens: modelDef.maxTokens,
517
+ omitMaxOutputTokens: modelDef.omitMaxOutputTokens,
518
+ headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
519
+ compat: mergeCompat(providerCompat, modelDef.compat),
520
+ contextPromotionTarget: modelDef.contextPromotionTarget,
521
+ premiumMultiplier: modelDef.premiumMultiplier,
522
+ isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
523
+ };
524
+ }
525
+
526
+ function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomModelOverlay {
527
+ if (model.id !== "gpt-5.4" || model.provider === "github-copilot" || model.contextWindow !== undefined) {
528
+ return model;
529
+ }
530
+ return { ...model, contextWindow: 1_000_000 };
531
+ }
532
+
533
+ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuildOptions): Model<Api> {
534
+ const resolvedModel = options.useDefaults ? applyStandaloneCustomModelPolicies(model) : model;
535
+ const reference = options.useDefaults
536
+ ? resolveModelReference(resolvedModel.id, getBundledModelReferenceIndex())
537
+ : undefined;
538
+ const cost =
539
+ resolvedModel.cost ??
540
+ reference?.cost ??
541
+ (options.useDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
542
+ const input = resolvedModel.input ?? reference?.input ?? (options.useDefaults ? ["text"] : undefined);
543
+ const supportsTools = resolvedModel.supportsTools ?? reference?.supportsTools;
544
+ return buildModel({
545
+ id: resolvedModel.id,
546
+ name: resolvedModel.name ?? (options.useDefaults ? resolvedModel.id : undefined),
547
+ api: resolvedModel.api,
548
+ provider: resolvedModel.provider,
549
+ baseUrl: resolvedModel.baseUrl,
550
+ reasoning: resolvedModel.reasoning ?? reference?.reasoning ?? (options.useDefaults ? false : undefined),
551
+ thinking: resolvedModel.thinking ?? reference?.thinking,
552
+ input: input as ("text" | "image")[],
553
+ ...(supportsTools !== undefined ? { supportsTools } : {}),
554
+ cost,
555
+ contextWindow: resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : null),
556
+ maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : null),
557
+ headers: resolvedModel.headers,
558
+ omitMaxOutputTokens: resolvedModel.omitMaxOutputTokens ?? reference?.omitMaxOutputTokens,
559
+ compat: mergeCompat(reference?.compatConfig, resolvedModel.compat),
560
+ contextPromotionTarget: resolvedModel.contextPromotionTarget,
561
+ premiumMultiplier: resolvedModel.premiumMultiplier,
562
+ isOAuth: resolvedModel.isOAuth,
563
+ } as ModelSpec<Api>);
564
+ }
565
+
566
+ function normalizeSuppressedSelector(
567
+ selector: string,
568
+ hasLiveModel?: (provider: string, id: string) => boolean,
569
+ ): string {
570
+ const trimmed = selector.trim();
571
+ if (!trimmed) return trimmed;
572
+ const parsed = parseModelString(trimmed, {
573
+ allowMaxAlias: true,
574
+ isLiteralModelId: (provider, id) => hasLiveModel?.(provider, id) === true,
575
+ });
576
+ if (!parsed) return trimmed;
577
+ // Retired effort-tier variant ids normalize to their collapsed logical id
578
+ // so persisted suppressions keyed by raw member ids still bind.
579
+ const aliasId = resolveVariantAlias(parsed.provider, parsed.id);
580
+ return `${parsed.provider}/${aliasId ?? parsed.id}`;
581
+ }
582
+
583
+ /**
584
+ * Look up a model's override, falling back to entries keyed by retired
585
+ * effort-tier variant ids (models.yml authored before collapsing). A raw key
586
+ * only re-binds when no live model holds that id.
587
+ */
588
+ function resolveModelOverrideWithAliases(
589
+ overrides: Map<string, ModelOverride>,
590
+ model: Model<Api>,
591
+ hasLiveModel: (provider: string, id: string) => boolean,
592
+ ): ModelOverride | undefined {
593
+ const direct = overrides.get(model.id);
594
+ if (direct) return direct;
595
+ for (const rawId of getVariantAliasSources(model.provider, model.id)) {
596
+ if (hasLiveModel(model.provider, rawId)) continue;
597
+ const remapped = overrides.get(rawId);
598
+ if (remapped) {
599
+ logger.debug("model override re-keyed through variant alias", {
600
+ provider: model.provider,
601
+ from: rawId,
602
+ to: model.id,
603
+ });
604
+ return remapped;
605
+ }
606
+ }
607
+ return undefined;
608
+ }
609
+
610
+ function getDisabledProviderIdsFromSettings(): Set<string> {
611
+ try {
612
+ return new Set(settings.get("disabledProviders"));
613
+ } catch {
614
+ return new Set();
615
+ }
616
+ }
617
+
618
+ function getConfiguredProviderOrderFromSettings(): string[] {
619
+ try {
620
+ return settings.get("modelProviderOrder");
621
+ } catch {
622
+ return [];
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Model registry - loads and manages models, resolves API keys via AuthStorage.
628
+ */
629
+ export class ModelRegistry {
630
+ #models: Model<Api>[] = [];
631
+ #canonicalIndex: CanonicalModelIndex = { records: [], byId: new Map(), bySelector: new Map() };
632
+ #canonicalIndexDirty: boolean = true;
633
+ #customProviderApiKeys: Map<string, string> = new Map();
634
+ #keylessProviders: Set<string> = new Set();
635
+ #discoverableProviders: DiscoveryProviderConfig[] = [];
636
+ #customModelOverlays: CustomModelOverlay[] = [];
637
+ #providerOverrides: Map<string, ProviderOverride> = new Map();
638
+ #modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
639
+ #equivalenceConfig: ModelEquivalenceConfig | undefined;
640
+ #configError: ConfigError | undefined = undefined;
641
+ #modelsConfigFile: ConfigFile<ModelsConfig>;
642
+ #lastStaticLoadMtime: number | null = null;
643
+ #registeredProviderSources: Set<string> = new Set();
644
+ #providerDiscoveryStates: Map<string, ProviderDiscoveryState> = new Map();
645
+ #cacheDbPath?: string;
646
+ #suppressedSelectors: Map<string, number> = new Map();
647
+ #backgroundRefresh?: Promise<void>;
648
+ #lastDiscoveryWarnings: Map<string, string> = new Map();
649
+ // Runtime extension model overlays — persist across refresh() cycles so that
650
+ // models registered by extensions survive the model selector's offline reload.
651
+ #runtimeModelOverlays: CustomModelOverlay[] = [];
652
+ #runtimeProviderApiKeys: Map<string, string> = new Map();
653
+ #runtimeProviderOverrides: Map<string, ProviderOverride> = new Map();
654
+ #runtimeProvidersBySource: Map<string, Set<string>> = new Map();
655
+ #runtimeProviderSourceByName: Map<string, string> = new Map();
656
+ // Runtime model managers registered by extensions via fetchDynamicModels.
657
+ // Keyed by provider name; use the same SQLite cache path as builtins.
658
+ #runtimeModelManagers: Map<string, { options: ModelManagerOptions<Api>; sourceId: string }> = new Map();
659
+ #rebuildPending: boolean = false;
660
+ #rebuildSuspended: number = 0;
661
+ #fetch: FetchImpl;
662
+
663
+ #resolveCommandBackedApiKey(provider: string): CommandApiKeyResolution {
664
+ const keyConfig = this.#customProviderApiKeys.get(provider);
665
+ if (!isCommandConfigValue(keyConfig)) return { configured: false };
666
+ const value = resolveConfigValue(keyConfig);
667
+ if (value) {
668
+ this.authStorage.setConfigApiKey(provider, value);
669
+ return { configured: true, value };
670
+ }
671
+ this.authStorage.removeConfigApiKey(provider);
672
+ return { configured: true };
673
+ }
674
+
675
+ #installProviderApiKey(provider: string, keyConfig: string): void {
676
+ this.#customProviderApiKeys.set(provider, keyConfig);
677
+ const resolved = resolveConfigValue(keyConfig);
678
+ if (resolved) {
679
+ this.authStorage.setConfigApiKey(provider, resolved);
680
+ } else if (isCommandConfigValue(keyConfig)) {
681
+ this.authStorage.removeConfigApiKey(provider);
682
+ }
683
+ }
684
+
685
+ /**
686
+ * @param authStorage - Auth storage for API key resolution
687
+ *
688
+ * Sync constructor — eagerly loads bundled + cached models so tests and
689
+ * synchronous callers see a fully-populated registry immediately. Production
690
+ * boot paths SHOULD prefer {@link ModelRegistry.create} so the YAML/JSONC
691
+ * migration step lands off the event loop's hot path before the first
692
+ * `tryLoad()` runs.
693
+ */
694
+ constructor(
695
+ readonly authStorage: AuthStorage,
696
+ modelsPath?: string,
697
+ options?: { fetch?: FetchImpl },
698
+ ) {
699
+ this.#fetch =
700
+ options?.fetch ??
701
+ (isBunTestRuntime()
702
+ ? () => Promise.reject(new Error("network disabled in model-registry runtime test"))
703
+ : fetch);
704
+ this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
705
+ this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
706
+ // Set up fallback resolver for custom provider API keys
707
+ this.authStorage.setFallbackResolver(provider => {
708
+ const keyConfig = this.#customProviderApiKeys.get(provider);
709
+ if (!keyConfig) return undefined;
710
+ return resolveConfigValue(keyConfig);
711
+ });
712
+ // Load models synchronously in constructor.
713
+ this.#loadModels();
714
+ }
715
+
716
+ /**
717
+ * Reload models from disk (built-in + custom from models.json).
718
+ */
719
+ async refresh(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
720
+ this.#suspendRebuild();
721
+ try {
722
+ this.#reloadStaticModels();
723
+ this.#suppressedSelectors.clear();
724
+ await this.#refreshRuntimeDiscoveries(strategy);
725
+ } finally {
726
+ this.#resumeRebuild();
727
+ }
728
+ }
729
+
730
+ refreshInBackground(strategy: ModelRefreshStrategy = "online-if-uncached"): void {
731
+ if (this.#backgroundRefresh) {
732
+ return;
733
+ }
734
+ const refreshPromise = this.refresh(strategy)
735
+ .catch(error => {
736
+ logger.warn("background model refresh failed", {
737
+ error: error instanceof Error ? error.message : String(error),
738
+ });
739
+ })
740
+ .finally(() => {
741
+ if (this.#backgroundRefresh === refreshPromise) {
742
+ this.#backgroundRefresh = undefined;
743
+ }
744
+ });
745
+ this.#backgroundRefresh = refreshPromise;
746
+ }
747
+
748
+ async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
749
+ this.#suspendRebuild();
750
+ try {
751
+ this.#reloadStaticModels();
752
+ for (const selector of this.#suppressedSelectors.keys()) {
753
+ if (selector.startsWith(`${providerId}/`)) {
754
+ this.#suppressedSelectors.delete(selector);
755
+ }
756
+ }
757
+ await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]));
758
+ } finally {
759
+ this.#resumeRebuild();
760
+ }
761
+ }
762
+
763
+ /**
764
+ * Discover models for providers registered at runtime via `fetchDynamicModels`
765
+ * (extension providers). Merges the discovered catalog into the existing model
766
+ * set without reloading static models, so dynamically-discovered models from
767
+ * other providers are preserved. No-op when no runtime providers are registered.
768
+ *
769
+ * Drives the same SQLite model cache as built-in providers, so the default
770
+ * `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
771
+ */
772
+ async refreshRuntimeProviders(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
773
+ if (this.#runtimeModelManagers.size === 0) {
774
+ return;
775
+ }
776
+ this.#suspendRebuild();
777
+ try {
778
+ await this.#refreshRuntimeDiscoveries(strategy, new Set(this.#runtimeModelManagers.keys()));
779
+ } finally {
780
+ this.#resumeRebuild();
781
+ }
782
+ }
783
+
784
+ #reloadStaticModels(): void {
785
+ const currentMtime = this.#modelsConfigFile.getMtimeMs();
786
+ if (currentMtime !== null && currentMtime === this.#lastStaticLoadMtime) {
787
+ // models.json unchanged since last load; reload + canonical rebuild would be redundant.
788
+ return;
789
+ }
790
+ this.#modelsConfigFile.invalidate();
791
+ this.#customProviderApiKeys.clear();
792
+ this.#keylessProviders.clear();
793
+ this.#discoverableProviders = [];
794
+ // Drop config-sourced apiKeys from AuthStorage before reload; entries
795
+ // removed from models.yml must actually disappear from the resolver, not
796
+ // linger from the previous parse. The post-load setters below repopulate.
797
+ this.authStorage.clearConfigApiKeys();
798
+ // Restore runtime API keys before #loadModels — survives because
799
+ // #loadModels only calls .set() on #customProviderApiKeys, never reassigns it.
800
+ for (const [k, v] of this.#runtimeProviderApiKeys) {
801
+ this.#installProviderApiKey(k, v);
802
+ }
803
+ this.#providerOverrides.clear();
804
+ this.#modelOverrides.clear();
805
+ this.#equivalenceConfig = undefined;
806
+ this.#configError = undefined;
807
+ this.#providerDiscoveryStates.clear();
808
+ this.#loadModels();
809
+ }
810
+
811
+ /**
812
+ * Get any error from loading models.json (undefined if no error).
813
+ */
814
+ getError(): ConfigError | undefined {
815
+ return this.#configError;
816
+ }
817
+
818
+ #loadModels() {
819
+ // Load custom models from models.json first (to know which providers to override)
820
+ const {
821
+ models: customModels = [],
822
+ overrides = new Map(),
823
+ modelOverrides = new Map(),
824
+ keylessProviders = new Set(),
825
+ discoverableProviders = [],
826
+ configuredProviders = new Set(),
827
+ equivalence,
828
+ error: configError,
829
+ } = this.#loadCustomModels();
830
+ this.#configError = configError;
831
+ this.#keylessProviders = keylessProviders;
832
+ this.#discoverableProviders = discoverableProviders;
833
+ this.#customModelOverlays = customModels;
834
+ this.#providerOverrides = overrides;
835
+ this.#modelOverrides = modelOverrides;
836
+ this.#equivalenceConfig = equivalence;
837
+
838
+ this.#addImplicitDiscoverableProviders(configuredProviders);
839
+ let builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
840
+ const cachedStandardResult = this.#loadCachedStandardProviderModels();
841
+ const cachedStandardModels = this.#applyHardcodedModelPolicies(cachedStandardResult.models);
842
+ const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
843
+ // Only drop bundled fallback models when the cached project-catalog row is
844
+ // itself fresh AND authoritative. A stale or non-authoritative snapshot
845
+ // (e.g. after ADC discovery failure rewrote the row with authoritative=0)
846
+ // must not strip bundled Vertex Gemini entries — that would leave only the
847
+ // stale project-scoped rows in API-key-only environments.
848
+ const cachedAuthoritativeProviders = new Set<string>();
849
+ for (const provider of providersWithAuthoritativeProjectCatalog(cachedStandardModels)) {
850
+ if (cachedStandardResult.authoritativeFreshProviders.has(provider)) {
851
+ cachedAuthoritativeProviders.add(provider);
852
+ }
853
+ }
854
+ for (const provider of cachedStandardResult.authoritativeFreshProviders) {
855
+ if (AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS.has(provider)) {
856
+ cachedAuthoritativeProviders.add(provider);
857
+ }
858
+ }
859
+ if (cachedAuthoritativeProviders.size > 0) {
860
+ builtInModels = dropProviderModels(builtInModels, cachedAuthoritativeProviders);
861
+ }
862
+ const resolvedDefaults = this.#mergeResolvedModels(
863
+ this.#mergeResolvedModels(builtInModels, cachedStandardModels),
864
+ cachedDiscoveries,
865
+ );
866
+ const withConfigModels = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
867
+ // Merge runtime extension models so they survive refresh() cycles
868
+ const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
869
+ // Custom/config providers bypass the model-manager merge point —
870
+ // collapse effort-tier variants here so X/X-thinking twins fold.
871
+ const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
872
+ this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
873
+ this.#rebuildCanonicalIndex();
874
+ this.#lastStaticLoadMtime = this.#modelsConfigFile.getMtimeMs();
875
+ }
876
+
877
+ /** Load built-in models, applying provider-level overrides only.
878
+ * Per-model overrides are applied later by #applyModelOverrides. */
879
+ #loadBuiltInModels(overrides: Map<string, ProviderOverride>): Model<Api>[] {
880
+ return getBundledProviders().flatMap(provider => {
881
+ const models = getBundledModels(provider as Parameters<typeof getBundledModels>[0]) as Model<Api>[];
882
+ const providerOverride = overrides.get(provider);
883
+
884
+ return models.map(m => {
885
+ if (!providerOverride) return m;
886
+ const withTransportOverride = this.#applyProviderTransportOverride(m, providerOverride);
887
+ return buildModel({
888
+ ...withTransportOverride,
889
+ compat: mergeCompat(m.compatConfig, providerOverride.compat),
890
+ } as ModelSpec<Api>);
891
+ });
892
+ });
893
+ }
894
+
895
+ #mergeResolvedModels(baseModels: Model<Api>[], replacementModels: Model<Api>[]): Model<Api>[] {
896
+ return mergeByModelKey(baseModels, replacementModels, (existing, replacementModel) => {
897
+ if (!existing) return replacementModel;
898
+ const supportsTools = replacementModel.supportsTools ?? existing.supportsTools;
899
+ return {
900
+ ...replacementModel,
901
+ contextWindow: replacementModel.contextWindow ?? existing.contextWindow,
902
+ maxTokens: replacementModel.maxTokens ?? existing.maxTokens,
903
+ omitMaxOutputTokens: replacementModel.omitMaxOutputTokens ?? existing.omitMaxOutputTokens,
904
+ ...(supportsTools !== undefined ? { supportsTools } : {}),
905
+ };
906
+ });
907
+ }
908
+
909
+ /** Merge custom models with built-in, replacing by provider+id match */
910
+ #mergeCustomModels(builtInModels: Model<Api>[], customModels: CustomModelOverlay[]): Model<Api>[] {
911
+ return mergeByModelKey(builtInModels, customModels, (existingModel, customModel) => {
912
+ if (!existingModel) return finalizeCustomModel(customModel, { useDefaults: true });
913
+ // Same-id custom definitions replace bundled transport behavior, so the
914
+ // patch is applied with the `replace` transport policy.
915
+ return applyModelPatch(
916
+ {
917
+ ...existingModel,
918
+ id: customModel.id,
919
+ provider: customModel.provider,
920
+ api: customModel.api,
921
+ baseUrl: customModel.baseUrl,
922
+ },
923
+ customModel,
924
+ "replace",
925
+ );
926
+ });
927
+ }
928
+
929
+ #resolveStartupModelCacheProviderId(providerId: string): string {
930
+ const descriptor = PROVIDER_DESCRIPTORS.find(candidate => candidate.providerId === providerId);
931
+ if (!descriptor) {
932
+ return providerId;
933
+ }
934
+ const baseUrl =
935
+ this.#runtimeProviderOverrides.get(providerId)?.baseUrl ??
936
+ this.#providerOverrides.get(providerId)?.baseUrl ??
937
+ this.getProviderBaseUrl(providerId);
938
+ return descriptor.createModelManagerOptions({ baseUrl, fetch: this.#fetch }).cacheProviderId ?? providerId;
939
+ }
940
+
941
+ #loadCachedStandardProviderModels(): { models: Model<Api>[]; authoritativeFreshProviders: Set<string> } {
942
+ const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(provider => provider.provider));
943
+ const cachedModels: Model<Api>[] = [];
944
+ const authoritativeFreshProviders = new Set<string>();
945
+ for (const providerId of STARTUP_MODEL_CACHE_PROVIDER_IDS) {
946
+ if (configuredDiscoveryProviders.has(providerId)) {
947
+ continue;
948
+ }
949
+ const cacheProviderId = this.#resolveStartupModelCacheProviderId(providerId);
950
+ const cache = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
951
+ if (!cache) {
952
+ continue;
953
+ }
954
+ if (cache.fresh && cache.authoritative) {
955
+ authoritativeFreshProviders.add(providerId);
956
+ }
957
+ const models = cache.models.map(model =>
958
+ model.provider === providerId ? model : { ...model, provider: providerId },
959
+ );
960
+ const providerOverride = this.#providerOverrides.get(providerId);
961
+ const withTransport = providerOverride
962
+ ? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
963
+ : models;
964
+ const withCompat = providerOverride?.compat
965
+ ? withTransport.map(model =>
966
+ buildModel({
967
+ ...model,
968
+ compat: mergeCompat(model.compat, providerOverride.compat),
969
+ } as ModelSpec<Api>),
970
+ )
971
+ : withTransport.map(model => buildModel(model));
972
+ cachedModels.push(...this.#applyProviderModelOverrides(providerId, withCompat));
973
+ }
974
+ return { models: cachedModels, authoritativeFreshProviders };
975
+ }
976
+
977
+ #loadCachedDiscoverableModels(): Model<Api>[] {
978
+ const cachedModels: Model<Api>[] = [];
979
+ for (const providerConfig of this.#discoverableProviders) {
980
+ const cache = readModelCache<Api>(
981
+ this.#configuredDiscoveryCacheProviderId(providerConfig),
982
+ 24 * 60 * 60 * 1000,
983
+ Date.now,
984
+ this.#cacheDbPath,
985
+ );
986
+ if (!cache) {
987
+ this.#providerDiscoveryStates.set(providerConfig.provider, {
988
+ provider: providerConfig.provider,
989
+ status: "idle",
990
+ optional: providerConfig.optional ?? false,
991
+ stale: false,
992
+ models: [],
993
+ });
994
+ continue;
995
+ }
996
+ const models = this.#applyProviderModelOverrides(
997
+ providerConfig.provider,
998
+ this.#normalizeDiscoverableModels(
999
+ providerConfig,
1000
+ this.#applyProviderCompat(
1001
+ providerConfig.compat,
1002
+ cache.models.map(model => buildModel(model)),
1003
+ ),
1004
+ ),
1005
+ );
1006
+ cachedModels.push(...models);
1007
+ this.#providerDiscoveryStates.set(providerConfig.provider, {
1008
+ provider: providerConfig.provider,
1009
+ status: "cached",
1010
+ optional: providerConfig.optional ?? false,
1011
+ stale: !cache.fresh || !cache.authoritative,
1012
+ fetchedAt: cache.updatedAt,
1013
+ models: models.map(model => model.id),
1014
+ });
1015
+ }
1016
+ return cachedModels;
1017
+ }
1018
+
1019
+ #applyProviderCompat(compat: ModelSpec<Api>["compat"] | undefined, models: Model<Api>[]): Model<Api>[] {
1020
+ if (!compat) return models;
1021
+ return models.map(model =>
1022
+ buildModel({ ...model, compat: mergeCompat(model.compatConfig, compat) } as ModelSpec<Api>),
1023
+ );
1024
+ }
1025
+
1026
+ #normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
1027
+ const withDecoderMetadata =
1028
+ providerConfig.discovery.type === "ollama" ||
1029
+ providerConfig.discovery.type === "llama.cpp" ||
1030
+ providerConfig.discovery.type === "lm-studio"
1031
+ ? models.map(model =>
1032
+ buildModel({ ...model, imageInputDecoder: "stb", compat: model.compatConfig } as ModelSpec<Api>),
1033
+ )
1034
+ : models;
1035
+
1036
+ if (providerConfig.provider !== "ollama" || providerConfig.api !== "openai-responses") {
1037
+ return withDecoderMetadata;
1038
+ }
1039
+
1040
+ const contextLengthOverride = getOllamaContextLengthOverride();
1041
+ return withDecoderMetadata.map(model => {
1042
+ const normalized =
1043
+ model.api === "openai-completions"
1044
+ ? buildModel({
1045
+ ...model,
1046
+ api: "openai-responses" as const,
1047
+ compat: model.compatConfig,
1048
+ } as ModelSpec<Api>)
1049
+ : model;
1050
+ if (contextLengthOverride === undefined) {
1051
+ return normalized;
1052
+ }
1053
+ return {
1054
+ ...normalized,
1055
+ contextWindow: contextLengthOverride,
1056
+ maxTokens: Math.min(contextLengthOverride, DISCOVERY_DEFAULT_MAX_TOKENS),
1057
+ };
1058
+ });
1059
+ }
1060
+
1061
+ #addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
1062
+ const disabledProviders = getDisabledProviderIdsFromSettings();
1063
+ if (!configuredProviders.has("ollama") && !disabledProviders.has("ollama")) {
1064
+ this.#discoverableProviders.push({
1065
+ provider: "ollama",
1066
+ api: "openai-responses",
1067
+ baseUrl: getImplicitOllamaBaseUrl(),
1068
+ discovery: { type: "ollama" },
1069
+ optional: true,
1070
+ });
1071
+ this.#keylessProviders.add("ollama");
1072
+ }
1073
+ if (!configuredProviders.has("llama.cpp") && !disabledProviders.has("llama.cpp")) {
1074
+ this.#discoverableProviders.push({
1075
+ provider: "llama.cpp",
1076
+ api: "openai-responses",
1077
+ baseUrl: Bun.env.LLAMA_CPP_BASE_URL || "http://127.0.0.1:8080",
1078
+ discovery: { type: "llama.cpp" },
1079
+ optional: true,
1080
+ });
1081
+ // Only mark as keyless if no API key is configured
1082
+ if (!this.authStorage.hasAuth("llama.cpp")) {
1083
+ this.#keylessProviders.add("llama.cpp");
1084
+ }
1085
+ }
1086
+ if (!configuredProviders.has("lm-studio") && !disabledProviders.has("lm-studio")) {
1087
+ this.#discoverableProviders.push({
1088
+ provider: "lm-studio",
1089
+ api: "openai-completions",
1090
+ baseUrl: Bun.env.LM_STUDIO_BASE_URL || "http://127.0.0.1:1234/v1",
1091
+ discovery: { type: "lm-studio" },
1092
+ optional: true,
1093
+ });
1094
+ this.#keylessProviders.add("lm-studio");
1095
+ }
1096
+ }
1097
+
1098
+ #loadCustomModels(): CustomModelsResult {
1099
+ const { value, error, status } = this.#modelsConfigFile.tryLoad();
1100
+
1101
+ if (status === "error") {
1102
+ return {
1103
+ models: [],
1104
+ overrides: new Map(),
1105
+ modelOverrides: new Map(),
1106
+ keylessProviders: new Set(),
1107
+ discoverableProviders: [],
1108
+ configuredProviders: new Set(),
1109
+ error,
1110
+ found: true,
1111
+ };
1112
+ } else if (status === "not-found") {
1113
+ return {
1114
+ models: [],
1115
+ overrides: new Map(),
1116
+ modelOverrides: new Map(),
1117
+ keylessProviders: new Set(),
1118
+ discoverableProviders: [],
1119
+ configuredProviders: new Set(),
1120
+ found: false,
1121
+ };
1122
+ }
1123
+
1124
+ const overrides = new Map<string, ProviderOverride>();
1125
+ const allModelOverrides = new Map<string, Map<string, ModelOverride>>();
1126
+ const keylessProviders = new Set<string>();
1127
+ const discoverableProviders: DiscoveryProviderConfig[] = [];
1128
+ const providerEntries = Object.entries(value.providers ?? {});
1129
+ const configuredProviders = new Set(Object.keys(value.providers ?? {}));
1130
+
1131
+ for (const [providerName, providerConfig] of providerEntries) {
1132
+ const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
1133
+ // Always set overrides when baseUrl/headers/apiKey/authHeader/compat/disableStrictTools/transport are present
1134
+ if (
1135
+ providerConfig.baseUrl ||
1136
+ resolvedProviderHeaders ||
1137
+ providerConfig.apiKey ||
1138
+ providerConfig.authHeader !== undefined ||
1139
+ providerConfig.compat ||
1140
+ providerConfig.disableStrictTools ||
1141
+ providerConfig.transport
1142
+ ) {
1143
+ const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
1144
+ overrides.set(providerName, {
1145
+ baseUrl: providerConfig.baseUrl,
1146
+ headers: resolvedProviderHeaders,
1147
+ apiKey: providerConfig.apiKey,
1148
+ authHeader: providerConfig.authHeader,
1149
+ compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1150
+ transport: providerConfig.transport,
1151
+ });
1152
+ }
1153
+
1154
+ const authMode = (providerConfig.auth ?? "apiKey") as ProviderAuthMode;
1155
+ if (authMode === "none") {
1156
+ keylessProviders.add(providerName);
1157
+ }
1158
+
1159
+ if (providerConfig.discovery && (providerConfig.api || providerConfig.discovery.type === "proxy")) {
1160
+ const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
1161
+ discoverableProviders.push({
1162
+ provider: providerName,
1163
+ // Proxy discovery derives per-model api from /v1/models's
1164
+ // supported_endpoint_types; the provider-level api is only a
1165
+ // fallback for entries that don't advertise one.
1166
+ api: (providerConfig.api ?? "openai-completions") as Api,
1167
+ baseUrl: providerConfig.baseUrl,
1168
+ headers: resolvedProviderHeaders,
1169
+ compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1170
+ discovery: providerConfig.discovery,
1171
+ optional: false,
1172
+ });
1173
+ }
1174
+
1175
+ // Store API key for fallback resolver AND register as config override
1176
+ // so it wins over OAuth tokens from the broker — when the user pins a
1177
+ // bearer in models.yml (e.g. for an auth-gateway baseUrl), that bearer
1178
+ // must authenticate the outbound request.
1179
+ if (providerConfig.apiKey) {
1180
+ this.#installProviderApiKey(providerName, providerConfig.apiKey);
1181
+ }
1182
+
1183
+ // Parse per-model overrides
1184
+ if (providerConfig.modelOverrides) {
1185
+ const perModel = new Map<string, ModelOverride>();
1186
+ for (const [modelId, override] of Object.entries(providerConfig.modelOverrides)) {
1187
+ perModel.set(
1188
+ modelId,
1189
+ override.headers ? { ...override, headers: resolveConfigHeaders(override.headers) } : override,
1190
+ );
1191
+ }
1192
+ allModelOverrides.set(providerName, perModel);
1193
+ }
1194
+ }
1195
+
1196
+ return {
1197
+ models: this.#parseModels(value),
1198
+ overrides,
1199
+ modelOverrides: allModelOverrides,
1200
+ keylessProviders,
1201
+ discoverableProviders,
1202
+ configuredProviders,
1203
+ equivalence: value.equivalence,
1204
+ found: true,
1205
+ };
1206
+ }
1207
+
1208
+ async #refreshRuntimeDiscoveries(
1209
+ strategy: ModelRefreshStrategy,
1210
+ providerFilter?: ReadonlySet<string>,
1211
+ ): Promise<void> {
1212
+ const disabledProviders = getDisabledProviderIdsFromSettings();
1213
+ const selectedDiscoverableProviders = (
1214
+ providerFilter
1215
+ ? this.#discoverableProviders.filter(provider => providerFilter.has(provider.provider))
1216
+ : this.#discoverableProviders
1217
+ ).filter(provider => !disabledProviders.has(provider.provider));
1218
+ const configuredDiscoveriesPromise =
1219
+ selectedDiscoverableProviders.length === 0
1220
+ ? Promise.resolve<Model<Api>[]>([])
1221
+ : Promise.all(
1222
+ selectedDiscoverableProviders.map(provider => this.#discoverProviderModels(provider, strategy)),
1223
+ ).then(results => results.flat());
1224
+ const [configuredDiscovered, builtInDiscovery] = await Promise.all([
1225
+ configuredDiscoveriesPromise,
1226
+ this.#discoverBuiltInProviderModels(strategy, providerFilter),
1227
+ ]);
1228
+ const discovered = [...configuredDiscovered, ...builtInDiscovery.models];
1229
+ if (discovered.length === 0 && builtInDiscovery.authoritativeProviders.size === 0) {
1230
+ return;
1231
+ }
1232
+ const discoveredModels = this.#applyHardcodedModelPolicies(
1233
+ discovered.map(model =>
1234
+ mergeDiscoveredModel(
1235
+ model,
1236
+ this.find(model.provider, model.id),
1237
+ this.#providerOverrides.get(model.provider),
1238
+ ),
1239
+ ),
1240
+ );
1241
+ const authoritativeProviders = providersWithAuthoritativeProjectCatalog(discoveredModels);
1242
+ for (const provider of builtInDiscovery.authoritativeProviders) {
1243
+ authoritativeProviders.add(provider);
1244
+ }
1245
+ const baseModels =
1246
+ authoritativeProviders.size > 0 ? dropProviderModels(this.#models, authoritativeProviders) : this.#models;
1247
+ const resolved = this.#mergeResolvedModels(baseModels, discoveredModels);
1248
+ const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
1249
+ // Merge runtime extension models so they survive online discovery completion
1250
+ const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
1251
+ const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
1252
+ this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
1253
+ this.#rebuildCanonicalIndex();
1254
+ }
1255
+
1256
+ #configuredDiscoveryCacheProviderId(providerConfig: DiscoveryProviderConfig): string {
1257
+ if (providerConfig.discovery.type === "openai-models-list") {
1258
+ return `${providerConfig.provider}:openai-models-list-context-v2`;
1259
+ }
1260
+ return providerConfig.provider;
1261
+ }
1262
+
1263
+ async #discoverProviderModels(
1264
+ providerConfig: DiscoveryProviderConfig,
1265
+ strategy: ModelRefreshStrategy,
1266
+ ): Promise<Model<Api>[]> {
1267
+ const cacheProviderId = this.#configuredDiscoveryCacheProviderId(providerConfig);
1268
+ const cached = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
1269
+ const requiresAuth = !this.#keylessProviders.has(providerConfig.provider);
1270
+ if (requiresAuth) {
1271
+ const apiKey = await this.#peekApiKeyForProvider(providerConfig.provider);
1272
+ if (!isAuthenticated(apiKey)) {
1273
+ this.#providerDiscoveryStates.set(providerConfig.provider, {
1274
+ provider: providerConfig.provider,
1275
+ status: "unauthenticated",
1276
+ optional: providerConfig.optional ?? false,
1277
+ stale: cached !== null,
1278
+ fetchedAt: cached?.updatedAt,
1279
+ models: cached?.models.map(model => model.id) ?? [],
1280
+ });
1281
+ this.#lastDiscoveryWarnings.delete(providerConfig.provider);
1282
+ return cached
1283
+ ? this.#normalizeDiscoverableModels(
1284
+ providerConfig,
1285
+ cached.models.map(model => buildModel(model)),
1286
+ )
1287
+ : [];
1288
+ }
1289
+ }
1290
+
1291
+ const providerId = providerConfig.provider;
1292
+ let discoveryError: string | undefined;
1293
+ const fetchDynamicModels = async (): Promise<readonly ModelSpec<Api>[] | null> => {
1294
+ try {
1295
+ const models = this.#applyProviderModelOverrides(
1296
+ providerId,
1297
+ await discoverModelsByProviderType(providerConfig, this.#discoveryContext()),
1298
+ );
1299
+ this.#lastDiscoveryWarnings.delete(providerId);
1300
+ return models.map(toModelSpec);
1301
+ } catch (error) {
1302
+ discoveryError = error instanceof Error ? error.message : String(error);
1303
+ return null;
1304
+ }
1305
+ };
1306
+
1307
+ const manager = createModelManager<Api>({
1308
+ providerId,
1309
+ staticModels: [],
1310
+ cacheDbPath: this.#cacheDbPath,
1311
+ cacheProviderId,
1312
+ cacheTtlMs: 24 * 60 * 60 * 1000,
1313
+ fetchDynamicModels,
1314
+ });
1315
+ const result = await manager.refresh(strategy);
1316
+ const status = discoveryError
1317
+ ? result.models.length > 0
1318
+ ? "cached"
1319
+ : "unavailable"
1320
+ : strategy === "offline"
1321
+ ? cached
1322
+ ? "cached"
1323
+ : "idle"
1324
+ : result.models.length > 0
1325
+ ? "ok"
1326
+ : "empty";
1327
+ this.#providerDiscoveryStates.set(providerId, {
1328
+ provider: providerId,
1329
+ status,
1330
+ optional: providerConfig.optional ?? false,
1331
+ stale: result.stale || status === "cached",
1332
+ fetchedAt: discoveryError ? cached?.updatedAt : Date.now(),
1333
+ models: result.models.map(model => model.id),
1334
+ error: discoveryError,
1335
+ });
1336
+ if (discoveryError) {
1337
+ this.#warnProviderDiscoveryFailure(providerConfig, discoveryError);
1338
+ }
1339
+ return this.#applyProviderModelOverrides(
1340
+ providerId,
1341
+ this.#normalizeDiscoverableModels(
1342
+ providerConfig,
1343
+ this.#applyProviderCompat(providerConfig.compat, result.models),
1344
+ ),
1345
+ );
1346
+ }
1347
+
1348
+ #discoveryContext(): DiscoveryContext {
1349
+ return {
1350
+ fetch: this.#fetch,
1351
+ getBearerApiKeyResolver: async provider => {
1352
+ const apiKey = await this.getApiKeyForProvider(provider);
1353
+ if (!isDiscoveryBearerApiKey(apiKey)) {
1354
+ return undefined;
1355
+ }
1356
+ return this.resolver(provider);
1357
+ },
1358
+ };
1359
+ }
1360
+
1361
+ #warnProviderDiscoveryFailure(providerConfig: DiscoveryProviderConfig, error: string): void {
1362
+ const previous = this.#lastDiscoveryWarnings.get(providerConfig.provider);
1363
+ if (previous === error) {
1364
+ return;
1365
+ }
1366
+ this.#lastDiscoveryWarnings.set(providerConfig.provider, error);
1367
+ logger.warn("model discovery failed for provider", {
1368
+ provider: providerConfig.provider,
1369
+ url: providerConfig.baseUrl,
1370
+ error,
1371
+ });
1372
+ }
1373
+
1374
+ async #discoverBuiltInProviderModels(
1375
+ strategy: ModelRefreshStrategy,
1376
+ providerFilter?: ReadonlySet<string>,
1377
+ ): Promise<BuiltInDiscoveryResult> {
1378
+ // Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
1379
+ const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(p => p.provider));
1380
+ const managerOptions = (await this.#collectBuiltInModelManagerOptions()).filter(opts => {
1381
+ if (configuredDiscoveryProviders.has(opts.providerId)) {
1382
+ return false;
1383
+ }
1384
+ return providerFilter ? providerFilter.has(opts.providerId) : true;
1385
+ });
1386
+ if (managerOptions.length === 0) {
1387
+ return { models: [], authoritativeProviders: new Set() };
1388
+ }
1389
+ const discoveries = await Promise.all(
1390
+ managerOptions.map(options => this.#discoverWithModelManager(options, strategy)),
1391
+ );
1392
+ const authoritativeProviders = new Set<string>();
1393
+ const models: Model<Api>[] = [];
1394
+ for (const discovery of discoveries) {
1395
+ models.push(...discovery.models);
1396
+ for (const provider of discovery.authoritativeProviders) {
1397
+ authoritativeProviders.add(provider);
1398
+ }
1399
+ }
1400
+ return { models, authoritativeProviders };
1401
+ }
1402
+
1403
+ async #collectBuiltInModelManagerOptions(): Promise<ModelManagerOptions<Api>[]> {
1404
+ const specialProviderDescriptors: Array<{
1405
+ providerId: string;
1406
+ resolveKey: (value: string | undefined) => string | undefined;
1407
+ createOptions: (key: string) => ModelManagerOptions<Api>;
1408
+ }> = [
1409
+ {
1410
+ providerId: "google-antigravity",
1411
+ resolveKey: extractGoogleOAuthToken,
1412
+ createOptions: oauthToken =>
1413
+ googleAntigravityModelManagerOptions({
1414
+ oauthToken,
1415
+ endpoint: this.getProviderBaseUrl("google-antigravity"),
1416
+ fetch: this.#fetch,
1417
+ }),
1418
+ },
1419
+ {
1420
+ providerId: "google-gemini-cli",
1421
+ resolveKey: extractGoogleOAuthToken,
1422
+ createOptions: oauthToken =>
1423
+ googleGeminiCliModelManagerOptions({
1424
+ oauthToken,
1425
+ endpoint: this.getProviderBaseUrl("google-gemini-cli"),
1426
+ fetch: this.#fetch,
1427
+ }),
1428
+ },
1429
+ {
1430
+ providerId: "openai-codex",
1431
+ resolveKey: value => value,
1432
+ createOptions: accessToken => {
1433
+ const accountId = resolveOAuthAccountIdForAccessToken(this.authStorage, "openai-codex", accessToken);
1434
+ return openaiCodexModelManagerOptions({
1435
+ accessToken,
1436
+ accountId,
1437
+ });
1438
+ },
1439
+ },
1440
+ ];
1441
+ const disabledProviders = getDisabledProviderIdsFromSettings();
1442
+ const standardProviderDescriptors = PROVIDER_DESCRIPTORS.filter(
1443
+ descriptor => !disabledProviders.has(descriptor.providerId),
1444
+ );
1445
+ const enabledSpecialProviderDescriptors = specialProviderDescriptors.filter(
1446
+ descriptor => !disabledProviders.has(descriptor.providerId),
1447
+ );
1448
+ // Use peekApiKey to avoid OAuth token refresh during discovery.
1449
+ // The token is only needed if the dynamic fetch fires (cache miss),
1450
+ // and failures there are handled gracefully.
1451
+ const peekKey = (descriptor: { providerId: string }) => this.#peekApiKeyForProvider(descriptor.providerId);
1452
+ const [standardProviderKeys, specialKeys] = await Promise.all([
1453
+ Promise.all(standardProviderDescriptors.map(peekKey)),
1454
+ Promise.all(enabledSpecialProviderDescriptors.map(peekKey)),
1455
+ ]);
1456
+ const options: ModelManagerOptions<Api>[] = [];
1457
+ for (let i = 0; i < standardProviderDescriptors.length; i++) {
1458
+ const descriptor = standardProviderDescriptors[i];
1459
+ const apiKey = standardProviderKeys[i];
1460
+ const hasExplicitVllmConfig =
1461
+ descriptor.providerId === "vllm" &&
1462
+ (this.#runtimeProviderOverrides.has(descriptor.providerId) ||
1463
+ this.#providerOverrides.has(descriptor.providerId) ||
1464
+ this.#keylessProviders.has(descriptor.providerId));
1465
+ if (isAuthenticated(apiKey) || descriptor.allowUnauthenticated || hasExplicitVllmConfig) {
1466
+ const discoveryBaseUrl =
1467
+ this.#runtimeProviderOverrides.get(descriptor.providerId)?.baseUrl ??
1468
+ this.#providerOverrides.get(descriptor.providerId)?.baseUrl ??
1469
+ this.getProviderBaseUrl(descriptor.providerId);
1470
+ options.push(
1471
+ descriptor.createModelManagerOptions({
1472
+ apiKey: isDiscoveryBearerApiKey(apiKey) ? apiKey : undefined,
1473
+ baseUrl: discoveryBaseUrl,
1474
+ fetch: this.#fetch,
1475
+ }),
1476
+ );
1477
+ }
1478
+ }
1479
+
1480
+ for (let i = 0; i < enabledSpecialProviderDescriptors.length; i++) {
1481
+ const descriptor = enabledSpecialProviderDescriptors[i];
1482
+ const key = descriptor.resolveKey(specialKeys[i]);
1483
+ if (!isAuthenticated(key)) {
1484
+ continue;
1485
+ }
1486
+ options.push(descriptor.createOptions(key));
1487
+ }
1488
+ // Append runtime model managers registered by extensions via fetchDynamicModels.
1489
+ for (const { options: managerOpts } of this.#runtimeModelManagers.values()) {
1490
+ options.push(managerOpts);
1491
+ }
1492
+ return options;
1493
+ }
1494
+
1495
+ async #discoverWithModelManager(
1496
+ options: ModelManagerOptions<Api>,
1497
+ strategy: ModelRefreshStrategy,
1498
+ ): Promise<BuiltInDiscoveryResult> {
1499
+ try {
1500
+ const manager = createModelManager({ ...options, cacheDbPath: this.#cacheDbPath });
1501
+ const result = await manager.refresh(strategy);
1502
+ const models = result.models.map(model =>
1503
+ model.provider === options.providerId ? model : { ...model, provider: options.providerId },
1504
+ );
1505
+ const authoritativeProviders = new Set<string>();
1506
+ if (options.dynamicModelsAuthoritative && !result.stale) {
1507
+ authoritativeProviders.add(options.providerId);
1508
+ }
1509
+ return { models, authoritativeProviders };
1510
+ } catch (error) {
1511
+ logger.warn("model discovery failed for provider", {
1512
+ provider: options.providerId,
1513
+ error: error instanceof Error ? error.message : String(error),
1514
+ });
1515
+ return { models: [], authoritativeProviders: new Set() };
1516
+ }
1517
+ }
1518
+
1519
+ #applyProviderModelOverrides(provider: string, models: Model<Api>[]): Model<Api>[] {
1520
+ const overrides = this.#modelOverrides.get(provider);
1521
+ if (!overrides || overrides.size === 0) return models;
1522
+ let liveIds: Set<string> | null = null;
1523
+ const hasLiveModel = (_provider: string, id: string) => {
1524
+ liveIds ??= new Set(models.map(m => m.id));
1525
+ return liveIds.has(id);
1526
+ };
1527
+ return models.map(model => {
1528
+ const override = resolveModelOverrideWithAliases(overrides, model, hasLiveModel);
1529
+ if (!override) return model;
1530
+ return applyModelOverride(model, override);
1531
+ });
1532
+ }
1533
+
1534
+ #mergeProviderOverride(baseOverride: ProviderOverride | undefined, override: ProviderOverride): ProviderOverride {
1535
+ return {
1536
+ baseUrl: override.baseUrl ?? baseOverride?.baseUrl,
1537
+ apiKey: override.apiKey ?? baseOverride?.apiKey,
1538
+ authHeader: override.authHeader ?? baseOverride?.authHeader,
1539
+ headers: override.headers ? { ...(baseOverride?.headers ?? {}), ...override.headers } : baseOverride?.headers,
1540
+ compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
1541
+ transport: override.transport ?? baseOverride?.transport,
1542
+ };
1543
+ }
1544
+ #applyProviderTransportOverride<T extends { baseUrl?: string; headers?: Record<string, string> }>(
1545
+ entry: T,
1546
+ override: Pick<ProviderOverride, "baseUrl" | "headers" | "authHeader" | "apiKey" | "transport">,
1547
+ ): T {
1548
+ const headers = mergeAuthHeader(
1549
+ override.headers ? { ...entry.headers, ...override.headers } : entry.headers,
1550
+ override.authHeader,
1551
+ override.apiKey,
1552
+ );
1553
+ return {
1554
+ ...entry,
1555
+ baseUrl: override.baseUrl ?? entry.baseUrl,
1556
+ headers,
1557
+ // Preserve the model's existing transport when the override omits one;
1558
+ // providers without a `transport` field keep the default per-API dispatch.
1559
+ ...(override.transport !== undefined ? { transport: override.transport } : {}),
1560
+ };
1561
+ }
1562
+ #applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
1563
+ if (this.#runtimeProviderOverrides.size === 0) return models;
1564
+ return models.map(model => {
1565
+ const override = this.#runtimeProviderOverrides.get(model.provider);
1566
+ if (!override) return model;
1567
+ return this.#applyProviderTransportOverride(model, override);
1568
+ });
1569
+ }
1570
+ #applyModelOverrides(models: Model<Api>[], overrides: Map<string, Map<string, ModelOverride>>): Model<Api>[] {
1571
+ if (overrides.size === 0) return models;
1572
+ let liveKeys: Set<string> | null = null;
1573
+ const hasLiveModel = (provider: string, id: string) => {
1574
+ liveKeys ??= new Set(models.map(m => `${m.provider}\u0000${m.id}`));
1575
+ return liveKeys.has(`${provider}\u0000${id}`);
1576
+ };
1577
+ return models.map(model => {
1578
+ const providerOverrides = overrides.get(model.provider);
1579
+ if (!providerOverrides) return model;
1580
+ const override = resolveModelOverrideWithAliases(providerOverrides, model, hasLiveModel);
1581
+ if (!override) return model;
1582
+ return applyModelOverride(model, override);
1583
+ });
1584
+ }
1585
+ #applyHardcodedModelPolicies(models: Model<Api>[]): Model<Api>[] {
1586
+ return models.map(model => {
1587
+ if (model.provider === "ollama-cloud" && model.omitMaxOutputTokens !== true) {
1588
+ model = applyModelOverride(model, { omitMaxOutputTokens: true });
1589
+ }
1590
+ if (model.id !== "gpt-5.4" || model.provider === "github-copilot") {
1591
+ return model;
1592
+ }
1593
+ const overrides = this.#modelOverrides.get(model.provider)?.get(model.id);
1594
+ if (!overrides) {
1595
+ return applyModelOverride(model, { contextWindow: 1_000_000 });
1596
+ }
1597
+ return applyModelOverride(model, {
1598
+ contextWindow: overrides.contextWindow ?? 1_000_000,
1599
+ ...overrides,
1600
+ });
1601
+ });
1602
+ }
1603
+
1604
+ #rebuildCanonicalIndex(): void {
1605
+ if (this.#rebuildSuspended > 0) {
1606
+ this.#rebuildPending = true;
1607
+ return;
1608
+ }
1609
+ // Defer the catalog-wide index build to first read. Boot model
1610
+ // resolution reads it only when enabledModels or a default-role pattern
1611
+ // is configured; the empty interactive launch never reads it pre-paint,
1612
+ // so the ~200ms build over the full catalog moves off the first-paint
1613
+ // critical path.
1614
+ this.#canonicalIndexDirty = true;
1615
+ this.#rebuildPending = false;
1616
+ }
1617
+
1618
+ #ensureCanonicalIndex(): CanonicalModelIndex {
1619
+ if (this.#canonicalIndexDirty) {
1620
+ this.#canonicalIndex = logger.time("buildCanonicalModelIndex", () =>
1621
+ buildCanonicalModelIndex(this.#models, getBundledCanonicalReferenceData(), this.#equivalenceConfig),
1622
+ );
1623
+ this.#canonicalIndexDirty = false;
1624
+ }
1625
+ return this.#canonicalIndex;
1626
+ }
1627
+
1628
+ #suspendRebuild(): void {
1629
+ this.#rebuildSuspended += 1;
1630
+ }
1631
+
1632
+ #resumeRebuild(): void {
1633
+ if (this.#rebuildSuspended > 0) {
1634
+ this.#rebuildSuspended -= 1;
1635
+ }
1636
+ if (this.#rebuildSuspended === 0 && this.#rebuildPending) {
1637
+ this.#rebuildPending = false;
1638
+ this.#canonicalIndexDirty = true;
1639
+ }
1640
+ }
1641
+
1642
+ #parseModels(config: ModelsConfig): CustomModelOverlay[] {
1643
+ const models: CustomModelOverlay[] = [];
1644
+
1645
+ for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
1646
+ const modelDefs = providerConfig.models ?? [];
1647
+ if (modelDefs.length === 0) continue; // Override-only, no custom models
1648
+ const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
1649
+ if (providerConfig.apiKey) {
1650
+ this.#installProviderApiKey(providerName, providerConfig.apiKey);
1651
+ }
1652
+ for (const modelDef of modelDefs) {
1653
+ const providerCompat = providerConfig.disableStrictTools
1654
+ ? mergeCompat(providerConfig.compat, { disableStrictTools: true })
1655
+ : providerConfig.compat;
1656
+ const model = buildCustomModelOverlay(
1657
+ providerName,
1658
+ providerConfig.baseUrl!,
1659
+ providerConfig.api as Api | undefined,
1660
+ resolvedProviderHeaders,
1661
+ providerConfig.apiKey,
1662
+ providerConfig.authHeader,
1663
+ providerCompat,
1664
+ (providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
1665
+ modelDef as CustomModelDefinitionLike,
1666
+ );
1667
+ if (!model) continue;
1668
+ models.push(model);
1669
+ }
1670
+ }
1671
+ return models;
1672
+ }
1673
+
1674
+ /**
1675
+ * Get all models (built-in + custom).
1676
+ * If models.json had errors, returns only built-in models.
1677
+ */
1678
+ getAll(): Model<Api>[] {
1679
+ return this.#models;
1680
+ }
1681
+
1682
+ /**
1683
+ * Availability predicate with per-provider memoization. Auth lookups
1684
+ * (`authStorage.hasAuth`) and the disabled-provider set are resolved once
1685
+ * per provider instead of once per model, which matters when filtering the
1686
+ * full bundled catalog (thousands of models, ~50 providers).
1687
+ */
1688
+ #createAvailabilityCheck(): (model: Model<Api>) => boolean {
1689
+ const disabledProviders = getDisabledProviderIdsFromSettings();
1690
+ const byProvider = new Map<string, boolean>();
1691
+ return model => {
1692
+ let available = byProvider.get(model.provider);
1693
+ if (available === undefined) {
1694
+ available =
1695
+ !disabledProviders.has(model.provider) &&
1696
+ (this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider));
1697
+ byProvider.set(model.provider, available);
1698
+ }
1699
+ return available;
1700
+ };
1701
+ }
1702
+
1703
+ /**
1704
+ * Build the shared per-query filter state for canonical model queries.
1705
+ * Hoisted out of the per-record loop: building the candidate-selector set
1706
+ * and availability memo once per query instead of once per record is what
1707
+ * keeps `getCanonicalModelSelections` linear instead of O(records × candidates).
1708
+ */
1709
+ #canonicalQueryFilters(options: CanonicalModelQueryOptions | undefined): {
1710
+ candidateKeys: Set<string> | undefined;
1711
+ isAvailable: ((model: Model<Api>) => boolean) | undefined;
1712
+ } {
1713
+ return {
1714
+ candidateKeys: options?.candidates
1715
+ ? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
1716
+ : undefined,
1717
+ isAvailable: options?.availableOnly ? this.#createAvailabilityCheck() : undefined,
1718
+ };
1719
+ }
1720
+
1721
+ #filterCanonicalVariants(
1722
+ record: CanonicalModelRecord,
1723
+ candidateKeys: ReadonlySet<string> | undefined,
1724
+ isAvailable: ((model: Model<Api>) => boolean) | undefined,
1725
+ ): CanonicalModelVariant[] {
1726
+ return record.variants.filter(variant => {
1727
+ if (candidateKeys && !candidateKeys.has(variant.selector)) {
1728
+ return false;
1729
+ }
1730
+ if (isAvailable && !isAvailable(variant.model)) {
1731
+ return false;
1732
+ }
1733
+ return true;
1734
+ });
1735
+ }
1736
+
1737
+ #variantPreferences(candidates: readonly Model<Api>[]): CanonicalVariantPreferences {
1738
+ return {
1739
+ modelOrder: buildCanonicalModelOrder(candidates),
1740
+ providerRank: buildModelProviderPriorityRank(getConfiguredProviderOrderFromSettings()),
1741
+ };
1742
+ }
1743
+
1744
+ getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[] {
1745
+ const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
1746
+ const records: CanonicalModelRecord[] = [];
1747
+ for (const record of this.#ensureCanonicalIndex().records) {
1748
+ const variants = this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
1749
+ if (variants.length === 0) {
1750
+ continue;
1751
+ }
1752
+ records.push({
1753
+ id: record.id,
1754
+ name: record.name,
1755
+ variants,
1756
+ });
1757
+ }
1758
+ return records;
1759
+ }
1760
+
1761
+ /**
1762
+ * One-pass equivalent of `getCanonicalModels` + `resolveCanonicalModel` per
1763
+ * record. The per-query state (candidate-selector set, availability memo,
1764
+ * provider rank, candidate order) is built once, so the whole catalog
1765
+ * resolves in O(records + candidates) instead of O(records × candidates).
1766
+ * This is the path the model selector hydrates from synchronously on open.
1767
+ */
1768
+ getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[] {
1769
+ const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
1770
+ const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
1771
+ const preferences = this.#variantPreferences(candidates);
1772
+ const selections: CanonicalModelSelection[] = [];
1773
+ for (const record of this.#ensureCanonicalIndex().records) {
1774
+ const variants = this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
1775
+ if (variants.length === 0) {
1776
+ continue;
1777
+ }
1778
+ const resolved = resolveCanonicalVariant(variants, preferences);
1779
+ if (!resolved) {
1780
+ continue;
1781
+ }
1782
+ selections.push({
1783
+ record: { id: record.id, name: record.name, variants },
1784
+ model: resolved.model,
1785
+ });
1786
+ }
1787
+ return selections;
1788
+ }
1789
+
1790
+ getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[] {
1791
+ const record = this.#ensureCanonicalIndex().byId.get(canonicalId.trim().toLowerCase());
1792
+ if (!record) {
1793
+ return [];
1794
+ }
1795
+ const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
1796
+ return this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
1797
+ }
1798
+
1799
+ resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined {
1800
+ const variants = this.getCanonicalVariants(canonicalId, options);
1801
+ if (variants.length === 0) {
1802
+ return undefined;
1803
+ }
1804
+ const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
1805
+ return resolveCanonicalVariant(variants, this.#variantPreferences(candidates))?.model;
1806
+ }
1807
+
1808
+ getCanonicalId(model: Model<Api>): string | undefined {
1809
+ return this.#ensureCanonicalIndex().bySelector.get(formatCanonicalVariantSelector(model).toLowerCase());
1810
+ }
1811
+
1812
+ /**
1813
+ * Get only models that have auth configured.
1814
+ * This is a fast check that doesn't refresh OAuth tokens.
1815
+ */
1816
+ getAvailable(): Model<Api>[] {
1817
+ return this.#models.filter(this.#createAvailabilityCheck());
1818
+ }
1819
+
1820
+ /**
1821
+ * Check whether auth is configured for a model's provider.
1822
+ *
1823
+ * Mirrors the upstream `@mariozechner/pi-coding-agent` API surface so that
1824
+ * external plugins/extensions and downstream wrappers (e.g. subagent launch
1825
+ * paths that pre-flight auth before model resolution) can probe a model
1826
+ * without resolving an API key. Returns true for keyless providers as well
1827
+ * as providers with stored credentials. See issue #993.
1828
+ *
1829
+ * Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
1830
+ * configured by its presence alone — the program is NOT executed — and OAuth
1831
+ * tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the
1832
+ * model-switch pre-flight off the event loop's hot path; the real key
1833
+ * (command execution + OAuth refresh) is resolved lazily per request via
1834
+ * {@link ModelRegistry.resolver}.
1835
+ */
1836
+ hasConfiguredAuth(model: Model<Api>): boolean {
1837
+ const keyConfig = this.#customProviderApiKeys.get(model.provider);
1838
+ return (
1839
+ isCommandConfigValue(keyConfig) ||
1840
+ this.#keylessProviders.has(model.provider) ||
1841
+ this.authStorage.hasAuth(model.provider)
1842
+ );
1843
+ }
1844
+
1845
+ getDiscoverableProviders(): string[] {
1846
+ const disabledProviders = getDisabledProviderIdsFromSettings();
1847
+ return this.#discoverableProviders
1848
+ .filter(provider => !disabledProviders.has(provider.provider))
1849
+ .map(provider => provider.provider);
1850
+ }
1851
+
1852
+ getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
1853
+ return this.#providerDiscoveryStates.get(provider);
1854
+ }
1855
+
1856
+ /**
1857
+ * Find a model by provider and ID.
1858
+ */
1859
+ find(provider: string, modelId: string): Model<Api> | undefined {
1860
+ return resolveProviderModelReference(provider, modelId, this.#models);
1861
+ }
1862
+
1863
+ /**
1864
+ * Get the base URL associated with a provider, if any model defines one.
1865
+ */
1866
+ getProviderBaseUrl(provider: string): string | undefined {
1867
+ return this.#models.find(m => m.provider === provider && m.baseUrl)?.baseUrl;
1868
+ }
1869
+
1870
+ /**
1871
+ * Get API key for a model.
1872
+ */
1873
+ async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
1874
+ const commandKey = this.#resolveCommandBackedApiKey(model.provider);
1875
+ if (commandKey.configured) return commandKey.value;
1876
+ if (this.#keylessProviders.has(model.provider) && !this.authStorage.hasAuth(model.provider)) {
1877
+ return kNoAuth;
1878
+ }
1879
+ return this.authStorage.getApiKey(model.provider, sessionId, { baseUrl: model.baseUrl, modelId: model.id });
1880
+ }
1881
+
1882
+ /**
1883
+ * Get API key for a provider (e.g., "openai").
1884
+ *
1885
+ * `options.forceRefresh` powers step (b) of the auth-retry policy — it
1886
+ * re-mints the session-sticky OAuth token even when the cached copy still
1887
+ * looks valid. `options.signal` is threaded into any broker-bound refresh.
1888
+ */
1889
+ async getApiKeyForProvider(
1890
+ provider: string,
1891
+ sessionId?: string,
1892
+ options?: { baseUrl?: string; modelId?: string; forceRefresh?: boolean; signal?: AbortSignal },
1893
+ ): Promise<string | undefined> {
1894
+ const commandKey = this.#resolveCommandBackedApiKey(provider);
1895
+ if (commandKey.configured) return commandKey.value;
1896
+ if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
1897
+ return kNoAuth;
1898
+ }
1899
+ return this.authStorage.getApiKey(provider, sessionId, {
1900
+ baseUrl: options?.baseUrl,
1901
+ modelId: options?.modelId,
1902
+ forceRefresh: options?.forceRefresh,
1903
+ signal: options?.signal,
1904
+ });
1905
+ }
1906
+
1907
+ /**
1908
+ * Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
1909
+ * policy. Accepts a provider id with options, or a model with an optional
1910
+ * session id (`resolver(model, sessionId)`) which derives `baseUrl`/`modelId`
1911
+ * from the model. Callers that need the initial key for a guard can call
1912
+ * `resolveApiKeyOnce(resolver)`.
1913
+ */
1914
+ resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
1915
+ resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
1916
+ resolver(target: string | ApiKeyResolverModel, optionsOrSessionId?: ApiKeyResolverOptions | string): ApiKeyResolver {
1917
+ const options = typeof optionsOrSessionId === "string" ? { sessionId: optionsOrSessionId } : optionsOrSessionId;
1918
+ if (typeof target === "string") {
1919
+ return createApiKeyResolver(this, target, options);
1920
+ }
1921
+ return createApiKeyResolver(this, target.provider, {
1922
+ ...options,
1923
+ baseUrl: target.baseUrl,
1924
+ modelId: target.id,
1925
+ });
1926
+ }
1927
+
1928
+ async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
1929
+ const commandKey = this.#resolveCommandBackedApiKey(provider);
1930
+ if (commandKey.configured) return commandKey.value;
1931
+ if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
1932
+ return kNoAuth;
1933
+ }
1934
+ return this.authStorage.peekApiKey(provider);
1935
+ }
1936
+
1937
+ /**
1938
+ * Check if a model is using OAuth credentials (subscription).
1939
+ */
1940
+ isUsingOAuth(model: Model<Api>): boolean {
1941
+ return this.authStorage.hasOAuth(model.provider);
1942
+ }
1943
+
1944
+ #clearRuntimeProviderState(providerName: string): void {
1945
+ this.#runtimeProviderApiKeys.delete(providerName);
1946
+ this.#runtimeProviderOverrides.delete(providerName);
1947
+ this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(overlay => overlay.provider !== providerName);
1948
+ this.#runtimeModelManagers.delete(providerName);
1949
+ this.authStorage.removeConfigApiKey(providerName);
1950
+ }
1951
+
1952
+ /**
1953
+ * Remove custom API/OAuth registrations for a specific extension source.
1954
+ */
1955
+ clearSourceRegistrations(sourceId: string): void {
1956
+ unregisterCustomApis(sourceId);
1957
+ unregisterOAuthProviders(sourceId);
1958
+ const sourceProviders = this.#runtimeProvidersBySource.get(sourceId);
1959
+ if (!sourceProviders || sourceProviders.size === 0) {
1960
+ return;
1961
+ }
1962
+ this.#runtimeProvidersBySource.delete(sourceId);
1963
+ for (const providerName of sourceProviders) {
1964
+ if (this.#runtimeProviderSourceByName.get(providerName) !== sourceId) {
1965
+ continue;
1966
+ }
1967
+ this.#runtimeProviderSourceByName.delete(providerName);
1968
+ this.#clearRuntimeProviderState(providerName);
1969
+ }
1970
+ this.#lastStaticLoadMtime = null;
1971
+ this.#reloadStaticModels();
1972
+ this.#rebuildCanonicalIndex();
1973
+ }
1974
+
1975
+ /**
1976
+ * Remove registrations for extension sources that are no longer active.
1977
+ */
1978
+ syncExtensionSources(activeSourceIds: string[]): void {
1979
+ const activeSources = new Set(activeSourceIds);
1980
+ for (const sourceId of this.#registeredProviderSources) {
1981
+ if (activeSources.has(sourceId)) {
1982
+ continue;
1983
+ }
1984
+ this.clearSourceRegistrations(sourceId);
1985
+ this.#registeredProviderSources.delete(sourceId);
1986
+ }
1987
+ }
1988
+
1989
+ /**
1990
+ * Register a provider dynamically (from extensions).
1991
+ *
1992
+ * If provider has models: replaces all existing models for this provider.
1993
+ * If provider has only baseUrl/headers: overrides existing models' URLs.
1994
+ * If provider has streamSimple: registers a custom API streaming function.
1995
+ * If provider has oauth: registers OAuth provider for /login support.
1996
+ */
1997
+ registerProvider(providerName: string, config: ProviderConfigInput, sourceId?: string): void {
1998
+ if (config.streamSimple && !config.api) {
1999
+ throw new Error(`Provider ${providerName}: "api" is required when registering streamSimple.`);
2000
+ }
2001
+
2002
+ validateProviderConfiguration(
2003
+ providerName,
2004
+ {
2005
+ baseUrl: config.baseUrl,
2006
+ headers: config.headers,
2007
+ apiKey: config.apiKey,
2008
+ api: config.api,
2009
+ oauthConfigured: Boolean(config.oauth),
2010
+ models: (config.models ?? []) as ProviderValidationModel[],
2011
+ },
2012
+ "runtime-register",
2013
+ );
2014
+
2015
+ if (config.streamSimple && config.api) {
2016
+ const streamSimple = config.streamSimple;
2017
+ registerCustomApi(config.api, streamSimple, sourceId, (model, context, options) =>
2018
+ streamSimple(model, context, options as SimpleStreamOptions),
2019
+ );
2020
+ }
2021
+
2022
+ if (config.oauth) {
2023
+ registerOAuthProvider({
2024
+ ...config.oauth,
2025
+ id: providerName,
2026
+ sourceId,
2027
+ });
2028
+ }
2029
+
2030
+ let sourceHandoff = false;
2031
+ if (sourceId) {
2032
+ this.#registeredProviderSources.add(sourceId);
2033
+ const previousSourceId = this.#runtimeProviderSourceByName.get(providerName);
2034
+ if (previousSourceId && previousSourceId !== sourceId) {
2035
+ const previousProviders = this.#runtimeProvidersBySource.get(previousSourceId);
2036
+ previousProviders?.delete(providerName);
2037
+ if (previousProviders && previousProviders.size === 0) {
2038
+ this.#runtimeProvidersBySource.delete(previousSourceId);
2039
+ }
2040
+ this.#clearRuntimeProviderState(providerName);
2041
+ sourceHandoff = true;
2042
+ }
2043
+ const sourceProviders = this.#runtimeProvidersBySource.get(sourceId) ?? new Set<string>();
2044
+ sourceProviders.add(providerName);
2045
+ this.#runtimeProvidersBySource.set(sourceId, sourceProviders);
2046
+ this.#runtimeProviderSourceByName.set(providerName, sourceId);
2047
+ }
2048
+ if (sourceHandoff) {
2049
+ this.#lastStaticLoadMtime = null;
2050
+ this.#reloadStaticModels();
2051
+ }
2052
+
2053
+ if (config.apiKey) {
2054
+ this.#installProviderApiKey(providerName, config.apiKey);
2055
+ // Persist runtime API keys so they survive #reloadStaticModels() cycles
2056
+ this.#runtimeProviderApiKeys.set(providerName, config.apiKey);
2057
+ }
2058
+
2059
+ if (config.models && config.models.length > 0) {
2060
+ // Build model overlays that persist across refresh() cycles
2061
+ const newOverlays: CustomModelOverlay[] = [];
2062
+ for (const modelDef of config.models) {
2063
+ const overlay = buildCustomModelOverlay(
2064
+ providerName,
2065
+ config.baseUrl!,
2066
+ config.api,
2067
+ config.headers,
2068
+ config.apiKey,
2069
+ config.authHeader,
2070
+ config.compat,
2071
+ undefined,
2072
+ modelDef as CustomModelDefinitionLike,
2073
+ );
2074
+ if (!overlay) {
2075
+ throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
2076
+ }
2077
+ newOverlays.push(overlay);
2078
+ }
2079
+ // Store as runtime overlays so they survive #reloadStaticModels()
2080
+ this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(m => m.provider !== providerName);
2081
+ this.#runtimeModelOverlays.push(...newOverlays);
2082
+
2083
+ // Also update #models immediately for the current cycle
2084
+ const nextModels = this.#models.filter(m => m.provider !== providerName);
2085
+ for (const overlay of newOverlays) {
2086
+ nextModels.push(finalizeCustomModel(overlay, { useDefaults: true }));
2087
+ }
2088
+ const runtimeTransportOverride = this.#runtimeProviderOverrides.get(providerName);
2089
+ const withRuntimeTransportOverride = runtimeTransportOverride
2090
+ ? nextModels.map(model => {
2091
+ if (model.provider !== providerName) return model;
2092
+ return this.#applyProviderTransportOverride(model, runtimeTransportOverride);
2093
+ })
2094
+ : nextModels;
2095
+
2096
+ if (config.oauth?.modifyModels) {
2097
+ const credential = this.authStorage.getOAuthCredential(providerName);
2098
+ if (credential) {
2099
+ this.#models = config.oauth.modifyModels(withRuntimeTransportOverride, credential);
2100
+ this.#rebuildCanonicalIndex();
2101
+ return;
2102
+ }
2103
+ }
2104
+
2105
+ this.#models = withRuntimeTransportOverride;
2106
+ this.#rebuildCanonicalIndex();
2107
+ return;
2108
+ }
2109
+
2110
+ if (config.fetchDynamicModels) {
2111
+ const fetcher = config.fetchDynamicModels;
2112
+ const providerBaseUrl = config.baseUrl ?? "";
2113
+ const providerApi = config.api;
2114
+ const providerHeaders = config.headers;
2115
+ const providerApiKey = config.apiKey;
2116
+ const providerAuthHeader = config.authHeader;
2117
+ const providerCompat = config.compat;
2118
+ const managerOptions: ModelManagerOptions<Api> = {
2119
+ providerId: providerName as Parameters<typeof createModelManager>[0]["providerId"],
2120
+ staticModels: [],
2121
+ cacheDbPath: this.#cacheDbPath,
2122
+ cacheTtlMs: 24 * 60 * 60 * 1000,
2123
+ dynamicModelsAuthoritative: true,
2124
+ fetchDynamicModels: async () => {
2125
+ const apiKey = await this.#peekApiKeyForProvider(providerName);
2126
+ const resolvedKey = isAuthenticated(apiKey) ? apiKey : undefined;
2127
+ const modelDefs = await fetcher(resolvedKey);
2128
+ const results: Model<Api>[] = [];
2129
+ for (const modelDef of modelDefs) {
2130
+ const overlay = buildCustomModelOverlay(
2131
+ providerName,
2132
+ modelDef.baseUrl ?? providerBaseUrl,
2133
+ modelDef.api ?? providerApi,
2134
+ providerHeaders,
2135
+ providerApiKey,
2136
+ providerAuthHeader,
2137
+ providerCompat,
2138
+ undefined,
2139
+ modelDef as CustomModelDefinitionLike,
2140
+ );
2141
+ if (overlay) results.push(finalizeCustomModel(overlay, { useDefaults: true }));
2142
+ }
2143
+ return results.map(toModelSpec);
2144
+ },
2145
+ };
2146
+ this.#runtimeModelManagers.set(providerName, { options: managerOptions, sourceId: sourceId ?? "" });
2147
+ // Discovery is driven by refreshRuntimeProviders() after the drain — not
2148
+ // here, so registration has no network side effect and callers can await.
2149
+ }
2150
+
2151
+ if (
2152
+ config.baseUrl ||
2153
+ config.headers ||
2154
+ config.apiKey ||
2155
+ config.authHeader !== undefined ||
2156
+ config.transport !== undefined
2157
+ ) {
2158
+ const transportOverride = {
2159
+ baseUrl: config.baseUrl,
2160
+ headers: config.headers,
2161
+ apiKey: config.apiKey,
2162
+ authHeader: config.authHeader,
2163
+ transport: config.transport,
2164
+ };
2165
+ const nextRuntimeOverride = this.#mergeProviderOverride(
2166
+ this.#runtimeProviderOverrides.get(providerName),
2167
+ transportOverride,
2168
+ );
2169
+ this.#runtimeProviderOverrides.set(providerName, nextRuntimeOverride);
2170
+ this.#models = this.#models.map(m => {
2171
+ if (m.provider !== providerName) return m;
2172
+ return this.#applyProviderTransportOverride(m, transportOverride);
2173
+ });
2174
+ this.#rebuildCanonicalIndex();
2175
+ }
2176
+ }
2177
+
2178
+ /**
2179
+ * Suppress a specific model selector (e.g., "provider/id") until a specific timestamp.
2180
+ */
2181
+ suppressSelector(selector: string, untilMs: number): void {
2182
+ this.#suppressedSelectors.set(
2183
+ normalizeSuppressedSelector(selector, (provider, id) => this.find(provider, id) !== undefined),
2184
+ untilMs,
2185
+ );
2186
+ }
2187
+
2188
+ /**
2189
+ * Check if a model selector is currently suppressed due to rate limits.
2190
+ */
2191
+ isSelectorSuppressed(selector: string): boolean {
2192
+ const normalizedSelector = normalizeSuppressedSelector(
2193
+ selector,
2194
+ (provider, id) => this.find(provider, id) !== undefined,
2195
+ );
2196
+ const suppressedUntil = this.#suppressedSelectors.get(normalizedSelector);
2197
+ if (!suppressedUntil) return false;
2198
+ if (suppressedUntil <= Date.now()) {
2199
+ this.#suppressedSelectors.delete(normalizedSelector);
2200
+ return false;
2201
+ }
2202
+ return true;
2203
+ }
2204
+
2205
+ /**
2206
+ * Clear all cooldown suppressions recorded via {@link suppressSelector}.
2207
+ * Used to reset retry-fallback cooldown state without a full {@link refresh}.
2208
+ */
2209
+ clearSuppressedSelectors(): void {
2210
+ this.#suppressedSelectors.clear();
2211
+ }
2212
+ }
2213
+
2214
+ /**
2215
+ * Input type for registerProvider API (from extensions).
2216
+ */
2217
+ export interface ProviderConfigInput {
2218
+ baseUrl?: string;
2219
+ apiKey?: string;
2220
+ api?: Api;
2221
+ streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
2222
+ headers?: Record<string, string>;
2223
+ compat?: ModelSpec<Api>["compat"];
2224
+ authHeader?: boolean;
2225
+ /** Streaming transport override — see {@link Model.transport}. */
2226
+ transport?: Model<Api>["transport"];
2227
+ oauth?: {
2228
+ name: string;
2229
+ login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials | string>;
2230
+ refreshToken?(credentials: OAuthCredentials): Promise<OAuthCredentials>;
2231
+ getApiKey?(credentials: OAuthCredentials): string;
2232
+ modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
2233
+ };
2234
+ /**
2235
+ * Async factory that fetches the live model list from the provider endpoint.
2236
+ * When present, the result is run through the same SQLite model-cache as
2237
+ * built-in providers (keyed by provider name, default 24 h TTL).
2238
+ * The factory receives the resolved API key (undefined when unauthenticated).
2239
+ */
2240
+ fetchDynamicModels?: (
2241
+ apiKey: string | undefined,
2242
+ ) => Promise<readonly NonNullable<ProviderConfigInput["models"]>[number][]>;
2243
+ models?: Array<{
2244
+ id: string;
2245
+ name: string;
2246
+ api?: Api;
2247
+ baseUrl?: string;
2248
+ reasoning: boolean;
2249
+ thinking?: ThinkingConfig;
2250
+ input: ("text" | "image")[];
2251
+ supportsTools?: boolean;
2252
+ cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
2253
+ contextWindow: number;
2254
+ maxTokens: number;
2255
+ headers?: Record<string, string>;
2256
+ compat?: ModelSpec<Api>["compat"];
2257
+ contextPromotionTarget?: string;
2258
+ premiumMultiplier?: number;
2259
+ }>;
2260
+ }