@kr78/pi-coding-agent 17.2.10

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 (1593) hide show
  1. package/CHANGELOG.md +14861 -0
  2. package/README.md +35 -0
  3. package/dist/CHANGELOG-chtdxh1z.md +14861 -0
  4. package/dist/cli.js +20483 -0
  5. package/dist/template-c2hyaytt.js +1653 -0
  6. package/dist/template-f8wx9vfn.css +1355 -0
  7. package/dist/template-qat058wr.html +55 -0
  8. package/dist/tool-views.generated-jdfmzwmn.js +35 -0
  9. package/examples/README.md +21 -0
  10. package/examples/custom-tools/README.md +104 -0
  11. package/examples/custom-tools/hello/index.ts +20 -0
  12. package/examples/extensions/README.md +142 -0
  13. package/examples/extensions/api-demo.ts +78 -0
  14. package/examples/extensions/chalk-logger.ts +25 -0
  15. package/examples/extensions/hello.ts +31 -0
  16. package/examples/extensions/pirate.ts +43 -0
  17. package/examples/extensions/plan-mode.ts +549 -0
  18. package/examples/extensions/reload-runtime.ts +38 -0
  19. package/examples/extensions/thinking-note.ts +13 -0
  20. package/examples/extensions/tools.ts +145 -0
  21. package/examples/extensions/with-deps/index.ts +36 -0
  22. package/examples/extensions/with-deps/package-lock.json +31 -0
  23. package/examples/extensions/with-deps/package.json +17 -0
  24. package/examples/hooks/README.md +56 -0
  25. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  26. package/examples/hooks/confirm-destructive.ts +58 -0
  27. package/examples/hooks/custom-compaction.ts +115 -0
  28. package/examples/hooks/dirty-repo-guard.ts +51 -0
  29. package/examples/hooks/file-trigger.ts +40 -0
  30. package/examples/hooks/git-checkpoint.ts +52 -0
  31. package/examples/hooks/handoff.ts +149 -0
  32. package/examples/hooks/permission-gate.ts +33 -0
  33. package/examples/hooks/protected-paths.ts +29 -0
  34. package/examples/hooks/qna.ts +118 -0
  35. package/examples/hooks/status-line.ts +39 -0
  36. package/examples/sdk/01-minimal.ts +21 -0
  37. package/examples/sdk/02-custom-model.ts +49 -0
  38. package/examples/sdk/03-custom-prompt.ts +46 -0
  39. package/examples/sdk/04-skills.ts +43 -0
  40. package/examples/sdk/06-extensions.ts +82 -0
  41. package/examples/sdk/06-hooks.ts +61 -0
  42. package/examples/sdk/07-context-files.ts +35 -0
  43. package/examples/sdk/08-prompt-templates.ts +41 -0
  44. package/examples/sdk/08-slash-commands.ts +46 -0
  45. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  46. package/examples/sdk/11-sessions.ts +47 -0
  47. package/examples/sdk/12-redis-sessions.ts +54 -0
  48. package/examples/sdk/13-sql-sessions.ts +61 -0
  49. package/examples/sdk/README.md +169 -0
  50. package/package.json +587 -0
  51. package/scripts/bench-guard.ts +71 -0
  52. package/scripts/bench-title-models.ts +332 -0
  53. package/scripts/build-binary.ts +117 -0
  54. package/scripts/bundle-dist.ts +123 -0
  55. package/scripts/compile-binary.ts +69 -0
  56. package/scripts/embed-mupdf-wasm.ts +67 -0
  57. package/scripts/format-prompts.ts +68 -0
  58. package/scripts/generate-aria-snapshot.ts +134 -0
  59. package/scripts/generate-docs-index.ts +58 -0
  60. package/scripts/generate-share-viewer.ts +34 -0
  61. package/scripts/legacy-pi-virtual-module.ts +205 -0
  62. package/scripts/measure-prompt-tokens.ts +63 -0
  63. package/scripts/omp +51 -0
  64. package/scripts/omp.ts +19 -0
  65. package/scripts/security-compare.ts +40 -0
  66. package/src/advisor/advise-tool.ts +234 -0
  67. package/src/advisor/config.ts +341 -0
  68. package/src/advisor/emission-guard.ts +172 -0
  69. package/src/advisor/index.ts +6 -0
  70. package/src/advisor/runtime.ts +1412 -0
  71. package/src/advisor/transcript-recorder.ts +215 -0
  72. package/src/advisor/watchdog.ts +135 -0
  73. package/src/async/index.ts +1 -0
  74. package/src/async/job-manager.ts +847 -0
  75. package/src/auto-thinking/classifier.ts +220 -0
  76. package/src/autolearn/controller.ts +152 -0
  77. package/src/autolearn/managed-skills.ts +255 -0
  78. package/src/autoresearch/command-resume.md +14 -0
  79. package/src/autoresearch/dashboard.ts +436 -0
  80. package/src/autoresearch/git.ts +331 -0
  81. package/src/autoresearch/helpers.ts +218 -0
  82. package/src/autoresearch/index.ts +541 -0
  83. package/src/autoresearch/prompt-setup.md +43 -0
  84. package/src/autoresearch/prompt.md +103 -0
  85. package/src/autoresearch/resume-message.md +10 -0
  86. package/src/autoresearch/state.ts +273 -0
  87. package/src/autoresearch/storage.ts +700 -0
  88. package/src/autoresearch/tools/init-experiment.ts +268 -0
  89. package/src/autoresearch/tools/log-experiment.ts +520 -0
  90. package/src/autoresearch/tools/run-experiment.ts +407 -0
  91. package/src/autoresearch/tools/update-notes.ts +109 -0
  92. package/src/autoresearch/types.ts +168 -0
  93. package/src/capability/context-file.ts +44 -0
  94. package/src/capability/extension-module.ts +34 -0
  95. package/src/capability/extension.ts +47 -0
  96. package/src/capability/fs.ts +117 -0
  97. package/src/capability/hook.ts +40 -0
  98. package/src/capability/index.ts +467 -0
  99. package/src/capability/instruction.ts +37 -0
  100. package/src/capability/mcp.ts +115 -0
  101. package/src/capability/prompt.ts +35 -0
  102. package/src/capability/rule-buckets.ts +66 -0
  103. package/src/capability/rule.ts +298 -0
  104. package/src/capability/settings.ts +34 -0
  105. package/src/capability/skill.ts +69 -0
  106. package/src/capability/slash-command.ts +40 -0
  107. package/src/capability/ssh.ts +41 -0
  108. package/src/capability/system-prompt.ts +34 -0
  109. package/src/capability/tool.ts +38 -0
  110. package/src/capability/types.ts +187 -0
  111. package/src/cleanse/agent.ts +226 -0
  112. package/src/cleanse/balance.ts +79 -0
  113. package/src/cleanse/checkers.ts +996 -0
  114. package/src/cleanse/index.ts +190 -0
  115. package/src/cleanse/loop.ts +51 -0
  116. package/src/cleanse/parsers.ts +726 -0
  117. package/src/cleanse/progress.ts +50 -0
  118. package/src/cleanse/prompts/assignment.md +47 -0
  119. package/src/cleanse/types.ts +72 -0
  120. package/src/cli/agents-cli.ts +138 -0
  121. package/src/cli/args.ts +368 -0
  122. package/src/cli/auth-broker-cli.ts +940 -0
  123. package/src/cli/auth-gateway-cli.ts +674 -0
  124. package/src/cli/bench-cli.ts +990 -0
  125. package/src/cli/browser-relay-cli.ts +119 -0
  126. package/src/cli/classify-install-target.ts +76 -0
  127. package/src/cli/claude-trace-cli.ts +795 -0
  128. package/src/cli/command-help.ts +107 -0
  129. package/src/cli/commands/init-xdg.ts +27 -0
  130. package/src/cli/completion-gen.ts +550 -0
  131. package/src/cli/config-cli.ts +459 -0
  132. package/src/cli/dry-balance-cli.ts +864 -0
  133. package/src/cli/extension-flags.ts +48 -0
  134. package/src/cli/file-processor.ts +132 -0
  135. package/src/cli/flag-tables.ts +367 -0
  136. package/src/cli/gallery-cli.ts +272 -0
  137. package/src/cli/gallery-fixtures/agentic.ts +420 -0
  138. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  139. package/src/cli/gallery-fixtures/edit.ts +254 -0
  140. package/src/cli/gallery-fixtures/fs.ts +245 -0
  141. package/src/cli/gallery-fixtures/index.ts +40 -0
  142. package/src/cli/gallery-fixtures/interaction.ts +46 -0
  143. package/src/cli/gallery-fixtures/memory.ts +81 -0
  144. package/src/cli/gallery-fixtures/misc.ts +177 -0
  145. package/src/cli/gallery-fixtures/search.ts +135 -0
  146. package/src/cli/gallery-fixtures/shell.ts +241 -0
  147. package/src/cli/gallery-fixtures/types.ts +57 -0
  148. package/src/cli/gallery-fixtures/web.ts +158 -0
  149. package/src/cli/gallery-screenshot.ts +279 -0
  150. package/src/cli/gc-cli.ts +1566 -0
  151. package/src/cli/grep-cli.ts +161 -0
  152. package/src/cli/grievances-cli.ts +256 -0
  153. package/src/cli/help-extra.ts +89 -0
  154. package/src/cli/initial-message.ts +58 -0
  155. package/src/cli/models-cli.ts +385 -0
  156. package/src/cli/plugin-cli.ts +996 -0
  157. package/src/cli/profile-alias.ts +369 -0
  158. package/src/cli/profile-bootstrap.ts +233 -0
  159. package/src/cli/read-cli.ts +99 -0
  160. package/src/cli/session-picker.ts +110 -0
  161. package/src/cli/setup-cli.ts +312 -0
  162. package/src/cli/setup-model-picker.ts +43 -0
  163. package/src/cli/shell-cli.ts +176 -0
  164. package/src/cli/ssh-cli.ts +179 -0
  165. package/src/cli/startup-cwd.ts +58 -0
  166. package/src/cli/stats-cli.ts +229 -0
  167. package/src/cli/thinking-levels.ts +7 -0
  168. package/src/cli/tiny-models-cli.ts +153 -0
  169. package/src/cli/ttsr-cli.ts +1013 -0
  170. package/src/cli/update-cli.ts +1184 -0
  171. package/src/cli/usage-cli.ts +1084 -0
  172. package/src/cli/usage-error.ts +7 -0
  173. package/src/cli/web-search-cli.ts +144 -0
  174. package/src/cli/worktree-cli.ts +311 -0
  175. package/src/cli-commands.ts +308 -0
  176. package/src/cli.ts +428 -0
  177. package/src/collab/crypto.ts +63 -0
  178. package/src/collab/display-name.ts +13 -0
  179. package/src/collab/guest.ts +764 -0
  180. package/src/collab/host.ts +692 -0
  181. package/src/collab/protocol.ts +296 -0
  182. package/src/collab/relay-client.ts +282 -0
  183. package/src/collab/replication-shrink.ts +111 -0
  184. package/src/commands/acp.ts +35 -0
  185. package/src/commands/agents.ts +58 -0
  186. package/src/commands/auth-broker.ts +100 -0
  187. package/src/commands/auth-gateway.ts +70 -0
  188. package/src/commands/bench.ts +64 -0
  189. package/src/commands/browser-relay.ts +53 -0
  190. package/src/commands/cleanse.ts +45 -0
  191. package/src/commands/commit.ts +47 -0
  192. package/src/commands/complete.ts +67 -0
  193. package/src/commands/completions.ts +61 -0
  194. package/src/commands/config.ts +52 -0
  195. package/src/commands/dry-balance.ts +43 -0
  196. package/src/commands/gallery.ts +61 -0
  197. package/src/commands/gc.ts +47 -0
  198. package/src/commands/grep.ts +49 -0
  199. package/src/commands/grievances.ts +52 -0
  200. package/src/commands/install.ts +107 -0
  201. package/src/commands/join.ts +40 -0
  202. package/src/commands/launch-help.ts +116 -0
  203. package/src/commands/launch.ts +34 -0
  204. package/src/commands/models.ts +61 -0
  205. package/src/commands/plugin.ts +79 -0
  206. package/src/commands/read.ts +39 -0
  207. package/src/commands/say.ts +146 -0
  208. package/src/commands/setup.ts +68 -0
  209. package/src/commands/share.ts +71 -0
  210. package/src/commands/shell.ts +30 -0
  211. package/src/commands/ssh.ts +61 -0
  212. package/src/commands/stats.ts +30 -0
  213. package/src/commands/tiny-models.ts +36 -0
  214. package/src/commands/token.ts +164 -0
  215. package/src/commands/ttsr.ts +125 -0
  216. package/src/commands/update.ts +34 -0
  217. package/src/commands/usage.ts +55 -0
  218. package/src/commands/web-search.ts +43 -0
  219. package/src/commands/worktree.ts +63 -0
  220. package/src/commit/agentic/agent.ts +322 -0
  221. package/src/commit/agentic/fallback.ts +96 -0
  222. package/src/commit/agentic/index.ts +383 -0
  223. package/src/commit/agentic/lock-files.ts +107 -0
  224. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  225. package/src/commit/agentic/prompts/session-user.md +25 -0
  226. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  227. package/src/commit/agentic/prompts/system.md +38 -0
  228. package/src/commit/agentic/state.ts +60 -0
  229. package/src/commit/agentic/tools/analyze-file.ts +148 -0
  230. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  231. package/src/commit/agentic/tools/git-hunk.ts +52 -0
  232. package/src/commit/agentic/tools/git-overview.ts +62 -0
  233. package/src/commit/agentic/tools/index.ts +54 -0
  234. package/src/commit/agentic/tools/propose-changelog.ts +147 -0
  235. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  236. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  237. package/src/commit/agentic/tools/schemas.ts +11 -0
  238. package/src/commit/agentic/tools/split-commit.ts +241 -0
  239. package/src/commit/agentic/topo-sort.ts +44 -0
  240. package/src/commit/agentic/trivial.ts +51 -0
  241. package/src/commit/agentic/validation.ts +183 -0
  242. package/src/commit/analysis/conventional.ts +64 -0
  243. package/src/commit/analysis/index.ts +4 -0
  244. package/src/commit/analysis/scope.ts +242 -0
  245. package/src/commit/analysis/summary.ts +107 -0
  246. package/src/commit/analysis/validation.ts +66 -0
  247. package/src/commit/changelog/detect.ts +40 -0
  248. package/src/commit/changelog/generate.ts +101 -0
  249. package/src/commit/changelog/index.ts +234 -0
  250. package/src/commit/changelog/parse.ts +44 -0
  251. package/src/commit/cli.ts +85 -0
  252. package/src/commit/git/diff.ts +148 -0
  253. package/src/commit/index.ts +5 -0
  254. package/src/commit/map-reduce/index.ts +69 -0
  255. package/src/commit/map-reduce/map-phase.ts +193 -0
  256. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  257. package/src/commit/map-reduce/utils.ts +9 -0
  258. package/src/commit/message.ts +11 -0
  259. package/src/commit/model-selection.ts +95 -0
  260. package/src/commit/pipeline.ts +244 -0
  261. package/src/commit/prompts/analysis-system.md +148 -0
  262. package/src/commit/prompts/analysis-user.md +38 -0
  263. package/src/commit/prompts/changelog-system.md +50 -0
  264. package/src/commit/prompts/changelog-user.md +18 -0
  265. package/src/commit/prompts/file-observer-system.md +24 -0
  266. package/src/commit/prompts/file-observer-user.md +8 -0
  267. package/src/commit/prompts/reduce-system.md +50 -0
  268. package/src/commit/prompts/reduce-user.md +17 -0
  269. package/src/commit/prompts/summary-retry.md +3 -0
  270. package/src/commit/prompts/summary-system.md +38 -0
  271. package/src/commit/prompts/summary-user.md +13 -0
  272. package/src/commit/prompts/types-description.md +2 -0
  273. package/src/commit/shared-llm.ts +70 -0
  274. package/src/commit/types.ts +118 -0
  275. package/src/commit/utils/exclusions.ts +42 -0
  276. package/src/commit/utils.ts +58 -0
  277. package/src/config/api-key-resolver.ts +81 -0
  278. package/src/config/append-only-context-mode.ts +76 -0
  279. package/src/config/config-file.ts +347 -0
  280. package/src/config/inline-tool-descriptors-mode.ts +26 -0
  281. package/src/config/keybindings.ts +703 -0
  282. package/src/config/mcp-schema.json +252 -0
  283. package/src/config/model-discovery.ts +1077 -0
  284. package/src/config/model-registry.ts +2822 -0
  285. package/src/config/model-resolver.ts +2115 -0
  286. package/src/config/model-roles.ts +113 -0
  287. package/src/config/models-config-schema-bundle.ts +315 -0
  288. package/src/config/models-config-schema.ts +14 -0
  289. package/src/config/models-config.ts +130 -0
  290. package/src/config/prompt-templates.ts +205 -0
  291. package/src/config/provider-globals.ts +25 -0
  292. package/src/config/resolve-config-value.ts +95 -0
  293. package/src/config/service-tier.ts +146 -0
  294. package/src/config/settings-schema.ts +5887 -0
  295. package/src/config/settings.ts +2394 -0
  296. package/src/config.ts +242 -0
  297. package/src/cursor-bridge-tools.ts +81 -0
  298. package/src/cursor.ts +960 -0
  299. package/src/dap/client.ts +1043 -0
  300. package/src/dap/config.ts +480 -0
  301. package/src/dap/defaults.json +212 -0
  302. package/src/dap/index.ts +4 -0
  303. package/src/dap/session.ts +1841 -0
  304. package/src/dap/types.ts +611 -0
  305. package/src/debug/index.ts +584 -0
  306. package/src/debug/log-formatting.ts +58 -0
  307. package/src/debug/log-viewer.ts +966 -0
  308. package/src/debug/profiler.ts +168 -0
  309. package/src/debug/protocol-probe.ts +267 -0
  310. package/src/debug/raw-sse-buffer.ts +421 -0
  311. package/src/debug/raw-sse.ts +312 -0
  312. package/src/debug/remote-debugger.ts +151 -0
  313. package/src/debug/report-bundle.ts +411 -0
  314. package/src/debug/system-info.ts +111 -0
  315. package/src/debug/terminal-info.ts +124 -0
  316. package/src/discovery/agent-plugin-format.ts +551 -0
  317. package/src/discovery/agent-plugins.ts +341 -0
  318. package/src/discovery/agents-md.ts +67 -0
  319. package/src/discovery/agents.ts +300 -0
  320. package/src/discovery/at-imports.ts +273 -0
  321. package/src/discovery/builtin-defaults.ts +39 -0
  322. package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
  323. package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
  324. package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
  325. package/src/discovery/builtin-rules/go-ioutil.md +37 -0
  326. package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
  327. package/src/discovery/builtin-rules/go-new-expr.md +44 -0
  328. package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
  329. package/src/discovery/builtin-rules/go-range-int.md +45 -0
  330. package/src/discovery/builtin-rules/index.ts +74 -0
  331. package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
  332. package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
  333. package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
  334. package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
  335. package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
  336. package/src/discovery/builtin-rules/rs-result-type.md +20 -0
  337. package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
  338. package/src/discovery/builtin-rules/ts-import-type.md +43 -0
  339. package/src/discovery/builtin-rules/ts-no-any.md +66 -0
  340. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
  341. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
  342. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
  343. package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
  344. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  345. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  346. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  347. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
  348. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  349. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  350. package/src/discovery/builtin.ts +945 -0
  351. package/src/discovery/claude-plugins.ts +640 -0
  352. package/src/discovery/claude.ts +591 -0
  353. package/src/discovery/cline.ts +83 -0
  354. package/src/discovery/codex.ts +553 -0
  355. package/src/discovery/contained-path.ts +78 -0
  356. package/src/discovery/cursor.ts +223 -0
  357. package/src/discovery/gemini.ts +386 -0
  358. package/src/discovery/github.ts +337 -0
  359. package/src/discovery/helpers.ts +1183 -0
  360. package/src/discovery/index.ts +82 -0
  361. package/src/discovery/mcp-json.ts +182 -0
  362. package/src/discovery/omp-extension-roots.ts +272 -0
  363. package/src/discovery/omp-plugins.ts +409 -0
  364. package/src/discovery/opencode.ts +441 -0
  365. package/src/discovery/plugin-dir-roots.ts +28 -0
  366. package/src/discovery/ssh.ts +153 -0
  367. package/src/discovery/substitute-plugin-root.ts +77 -0
  368. package/src/discovery/vscode.ts +106 -0
  369. package/src/discovery/windsurf.ts +149 -0
  370. package/src/edit/apply-patch/index.ts +87 -0
  371. package/src/edit/apply-patch/parser.ts +174 -0
  372. package/src/edit/diff.ts +1008 -0
  373. package/src/edit/edit-clipboard.ts +23 -0
  374. package/src/edit/file-snapshot-store.ts +148 -0
  375. package/src/edit/hashline/block-resolver.ts +33 -0
  376. package/src/edit/hashline/diff.ts +402 -0
  377. package/src/edit/hashline/execute.ts +313 -0
  378. package/src/edit/hashline/filesystem.ts +247 -0
  379. package/src/edit/hashline/index.ts +5 -0
  380. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  381. package/src/edit/hashline/params.ts +12 -0
  382. package/src/edit/index.ts +699 -0
  383. package/src/edit/modes/apply-patch.lark +19 -0
  384. package/src/edit/modes/apply-patch.ts +53 -0
  385. package/src/edit/modes/patch.ts +1958 -0
  386. package/src/edit/modes/replace.ts +1191 -0
  387. package/src/edit/normalize.ts +345 -0
  388. package/src/edit/notebook.ts +242 -0
  389. package/src/edit/read-file.ts +25 -0
  390. package/src/edit/renderer.ts +998 -0
  391. package/src/edit/snapshot-details.ts +77 -0
  392. package/src/edit/streaming.ts +710 -0
  393. package/src/eval/agent-bridge.ts +224 -0
  394. package/src/eval/backend-helpers.ts +48 -0
  395. package/src/eval/backend.ts +71 -0
  396. package/src/eval/bridge-timeout.ts +65 -0
  397. package/src/eval/budget-bridge.ts +48 -0
  398. package/src/eval/completion-bridge.ts +212 -0
  399. package/src/eval/concurrency-bridge.ts +34 -0
  400. package/src/eval/executor-base.ts +562 -0
  401. package/src/eval/idle-timeout.ts +91 -0
  402. package/src/eval/index.ts +6 -0
  403. package/src/eval/jl/executor.ts +551 -0
  404. package/src/eval/jl/index.ts +54 -0
  405. package/src/eval/jl/kernel.ts +236 -0
  406. package/src/eval/jl/prelude.jl +736 -0
  407. package/src/eval/jl/prelude.ts +3 -0
  408. package/src/eval/jl/runner.jl +666 -0
  409. package/src/eval/jl/runtime.ts +118 -0
  410. package/src/eval/js/context-manager.ts +872 -0
  411. package/src/eval/js/executor.ts +178 -0
  412. package/src/eval/js/index.ts +41 -0
  413. package/src/eval/js/process-entry.ts +31 -0
  414. package/src/eval/js/shared/helpers.ts +170 -0
  415. package/src/eval/js/shared/indirect-eval.ts +30 -0
  416. package/src/eval/js/shared/local-module-loader.ts +364 -0
  417. package/src/eval/js/shared/prelude.ts +2 -0
  418. package/src/eval/js/shared/prelude.txt +294 -0
  419. package/src/eval/js/shared/rewrite-imports.ts +550 -0
  420. package/src/eval/js/shared/runtime.ts +590 -0
  421. package/src/eval/js/shared/types.ts +18 -0
  422. package/src/eval/js/tool-bridge.ts +163 -0
  423. package/src/eval/js/worker-core.ts +380 -0
  424. package/src/eval/js/worker-entry.ts +37 -0
  425. package/src/eval/js/worker-protocol.ts +47 -0
  426. package/src/eval/kernel-base.ts +569 -0
  427. package/src/eval/py/display.ts +71 -0
  428. package/src/eval/py/executor.ts +643 -0
  429. package/src/eval/py/index.ts +57 -0
  430. package/src/eval/py/kernel.ts +235 -0
  431. package/src/eval/py/prelude.py +673 -0
  432. package/src/eval/py/prelude.ts +3 -0
  433. package/src/eval/py/runner.py +1425 -0
  434. package/src/eval/py/runtime.ts +276 -0
  435. package/src/eval/py/spawn-options.ts +134 -0
  436. package/src/eval/py/tool-bridge.ts +201 -0
  437. package/src/eval/rb/executor.ts +511 -0
  438. package/src/eval/rb/index.ts +54 -0
  439. package/src/eval/rb/kernel.ts +231 -0
  440. package/src/eval/rb/prelude.rb +551 -0
  441. package/src/eval/rb/prelude.ts +3 -0
  442. package/src/eval/rb/runner.rb +581 -0
  443. package/src/eval/rb/runtime.ts +132 -0
  444. package/src/eval/runtime-env.ts +104 -0
  445. package/src/eval/session-id.ts +8 -0
  446. package/src/eval/types.ts +48 -0
  447. package/src/exa/index.ts +2 -0
  448. package/src/exa/mcp-client.ts +370 -0
  449. package/src/exa/types.ts +69 -0
  450. package/src/exec/bash-executor.ts +627 -0
  451. package/src/exec/direnv.ts +145 -0
  452. package/src/exec/exec.ts +53 -0
  453. package/src/exec/non-interactive-env.ts +118 -0
  454. package/src/export/custom-share.ts +65 -0
  455. package/src/export/html/args.ts +20 -0
  456. package/src/export/html/index.ts +320 -0
  457. package/src/export/html/share-loader.js +102 -0
  458. package/src/export/html/template.css +1355 -0
  459. package/src/export/html/template.html +55 -0
  460. package/src/export/html/template.js +1653 -0
  461. package/src/export/html/tool-views.generated.js +35 -0
  462. package/src/export/html/vendor/highlight.min.js +1213 -0
  463. package/src/export/html/vendor/marked.min.js +6 -0
  464. package/src/export/html/web-palette.ts +126 -0
  465. package/src/export/share.ts +686 -0
  466. package/src/export/ttsr.ts +590 -0
  467. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  468. package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
  469. package/src/extensibility/custom-commands/index.ts +2 -0
  470. package/src/extensibility/custom-commands/loader.ts +242 -0
  471. package/src/extensibility/custom-commands/types.ts +119 -0
  472. package/src/extensibility/custom-tools/index.ts +7 -0
  473. package/src/extensibility/custom-tools/loader.ts +301 -0
  474. package/src/extensibility/custom-tools/types.ts +287 -0
  475. package/src/extensibility/custom-tools/wrapper.ts +50 -0
  476. package/src/extensibility/extensions/compact-handler.ts +40 -0
  477. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  478. package/src/extensibility/extensions/index.ts +16 -0
  479. package/src/extensibility/extensions/load-errors.ts +13 -0
  480. package/src/extensibility/extensions/loader.ts +663 -0
  481. package/src/extensibility/extensions/managed-timers.ts +83 -0
  482. package/src/extensibility/extensions/model-api.ts +39 -0
  483. package/src/extensibility/extensions/runner.ts +1396 -0
  484. package/src/extensibility/extensions/types.ts +1603 -0
  485. package/src/extensibility/extensions/wrapper.ts +395 -0
  486. package/src/extensibility/hooks/index.ts +6 -0
  487. package/src/extensibility/hooks/loader.ts +243 -0
  488. package/src/extensibility/hooks/runner.ts +425 -0
  489. package/src/extensibility/hooks/tool-wrapper.ts +124 -0
  490. package/src/extensibility/hooks/types.ts +612 -0
  491. package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
  492. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
  493. package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
  494. package/src/extensibility/legacy-typebox.ts +101 -0
  495. package/src/extensibility/plugins/bun-git-cache.ts +91 -0
  496. package/src/extensibility/plugins/doctor.ts +65 -0
  497. package/src/extensibility/plugins/git-url.ts +367 -0
  498. package/src/extensibility/plugins/index.ts +9 -0
  499. package/src/extensibility/plugins/installer.ts +201 -0
  500. package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
  501. package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
  502. package/src/extensibility/plugins/loader.ts +535 -0
  503. package/src/extensibility/plugins/manager.ts +1142 -0
  504. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  505. package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
  506. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  507. package/src/extensibility/plugins/marketplace/manager.ts +927 -0
  508. package/src/extensibility/plugins/marketplace/registry.ts +192 -0
  509. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  510. package/src/extensibility/plugins/marketplace/types.ts +192 -0
  511. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  512. package/src/extensibility/plugins/parser.ts +107 -0
  513. package/src/extensibility/plugins/runtime-config.ts +9 -0
  514. package/src/extensibility/plugins/types.ts +194 -0
  515. package/src/extensibility/session-handler-types.ts +21 -0
  516. package/src/extensibility/shared-events.ts +401 -0
  517. package/src/extensibility/skills.ts +531 -0
  518. package/src/extensibility/slash-commands.ts +131 -0
  519. package/src/extensibility/tool-event-input.ts +80 -0
  520. package/src/extensibility/tool-proxy.ts +28 -0
  521. package/src/extensibility/utils.ts +192 -0
  522. package/src/goals/index.ts +3 -0
  523. package/src/goals/runtime.ts +521 -0
  524. package/src/goals/state.ts +37 -0
  525. package/src/goals/tools/goal-tool.ts +251 -0
  526. package/src/hindsight/backend.ts +354 -0
  527. package/src/hindsight/bank.ts +156 -0
  528. package/src/hindsight/client.ts +680 -0
  529. package/src/hindsight/config.ts +193 -0
  530. package/src/hindsight/content.ts +266 -0
  531. package/src/hindsight/index.ts +8 -0
  532. package/src/hindsight/mental-models.ts +429 -0
  533. package/src/hindsight/seeds.json +32 -0
  534. package/src/hindsight/state.ts +530 -0
  535. package/src/hindsight/transcript.ts +71 -0
  536. package/src/index.ts +66 -0
  537. package/src/internal-urls/agent-protocol.ts +180 -0
  538. package/src/internal-urls/artifact-protocol.ts +151 -0
  539. package/src/internal-urls/docs-index.ts +102 -0
  540. package/src/internal-urls/filesystem-resource.ts +34 -0
  541. package/src/internal-urls/history-protocol.ts +198 -0
  542. package/src/internal-urls/index.ts +28 -0
  543. package/src/internal-urls/issue-pr-protocol.ts +594 -0
  544. package/src/internal-urls/json-query.ts +126 -0
  545. package/src/internal-urls/local-protocol.ts +471 -0
  546. package/src/internal-urls/mcp-protocol.ts +168 -0
  547. package/src/internal-urls/memory-protocol.ts +380 -0
  548. package/src/internal-urls/omp-protocol.ts +94 -0
  549. package/src/internal-urls/parse.ts +103 -0
  550. package/src/internal-urls/registry-helpers.ts +132 -0
  551. package/src/internal-urls/router.ts +153 -0
  552. package/src/internal-urls/rule-protocol.ts +45 -0
  553. package/src/internal-urls/security-protocol.ts +261 -0
  554. package/src/internal-urls/skill-protocol.ts +131 -0
  555. package/src/internal-urls/ssh-protocol.ts +368 -0
  556. package/src/internal-urls/types.ts +196 -0
  557. package/src/internal-urls/vault-protocol.ts +940 -0
  558. package/src/internal-urls/xd-protocol.ts +46 -0
  559. package/src/irc/bus.ts +380 -0
  560. package/src/jsonrpc/message-framing.ts +142 -0
  561. package/src/launch/broker.ts +1368 -0
  562. package/src/launch/client.ts +521 -0
  563. package/src/launch/ensure.ts +73 -0
  564. package/src/launch/paths.ts +14 -0
  565. package/src/launch/presence.ts +82 -0
  566. package/src/launch/protocol.ts +448 -0
  567. package/src/launch/spawn-options.ts +17 -0
  568. package/src/launch/terminal-output-worker-client.ts +53 -0
  569. package/src/launch/terminal-output-worker-protocol.ts +11 -0
  570. package/src/launch/terminal-output-worker.ts +23 -0
  571. package/src/launch/terminal-output.ts +46 -0
  572. package/src/lib/xai-http.ts +150 -0
  573. package/src/live/attestation.ts +91 -0
  574. package/src/live/controller.ts +517 -0
  575. package/src/live/prompts/agent-final-message.md +3 -0
  576. package/src/live/prompts/live-instructions.md +23 -0
  577. package/src/live/protocol.ts +233 -0
  578. package/src/live/transport.ts +422 -0
  579. package/src/live/visualizer.ts +221 -0
  580. package/src/live/voices.ts +18 -0
  581. package/src/lsp/client.ts +1465 -0
  582. package/src/lsp/clients/biome-client.ts +263 -0
  583. package/src/lsp/clients/index.ts +50 -0
  584. package/src/lsp/clients/lsp-linter-client.ts +85 -0
  585. package/src/lsp/clients/swiftlint-client.ts +120 -0
  586. package/src/lsp/config.ts +549 -0
  587. package/src/lsp/defaults.json +499 -0
  588. package/src/lsp/deferred-diagnostics.ts +66 -0
  589. package/src/lsp/diagnostics-ledger.ts +51 -0
  590. package/src/lsp/edits.ts +288 -0
  591. package/src/lsp/format-options.ts +119 -0
  592. package/src/lsp/index.ts +2821 -0
  593. package/src/lsp/lspmux.ts +233 -0
  594. package/src/lsp/mux/daemon.ts +348 -0
  595. package/src/lsp/mux/protocol.ts +96 -0
  596. package/src/lsp/mux/server.ts +797 -0
  597. package/src/lsp/render.ts +668 -0
  598. package/src/lsp/startup-events.ts +13 -0
  599. package/src/lsp/types.ts +479 -0
  600. package/src/lsp/utils.ts +747 -0
  601. package/src/main.ts +1799 -0
  602. package/src/markit/NOTICE +32 -0
  603. package/src/markit/converters/docx.ts +56 -0
  604. package/src/markit/converters/epub.ts +136 -0
  605. package/src/markit/converters/pdf/columns.ts +103 -0
  606. package/src/markit/converters/pdf/extract.ts +598 -0
  607. package/src/markit/converters/pdf/grid.ts +780 -0
  608. package/src/markit/converters/pdf/headers.ts +106 -0
  609. package/src/markit/converters/pdf/index.ts +146 -0
  610. package/src/markit/converters/pdf/render.ts +501 -0
  611. package/src/markit/converters/pdf/types.ts +84 -0
  612. package/src/markit/converters/pptx.ts +325 -0
  613. package/src/markit/converters/xlsx.ts +173 -0
  614. package/src/markit/index.ts +2 -0
  615. package/src/markit/registry.ts +59 -0
  616. package/src/markit/types.ts +35 -0
  617. package/src/mcp/client.ts +511 -0
  618. package/src/mcp/config-writer.ts +377 -0
  619. package/src/mcp/config.ts +385 -0
  620. package/src/mcp/index.ts +29 -0
  621. package/src/mcp/json-rpc.ts +122 -0
  622. package/src/mcp/loader.ts +125 -0
  623. package/src/mcp/manager.ts +1524 -0
  624. package/src/mcp/oauth-credentials.ts +104 -0
  625. package/src/mcp/oauth-discovery.ts +587 -0
  626. package/src/mcp/oauth-flow.ts +830 -0
  627. package/src/mcp/render.ts +214 -0
  628. package/src/mcp/request-id.ts +24 -0
  629. package/src/mcp/smithery-auth.ts +108 -0
  630. package/src/mcp/smithery-connect.ts +154 -0
  631. package/src/mcp/smithery-registry.ts +500 -0
  632. package/src/mcp/startup-events.ts +116 -0
  633. package/src/mcp/timeout.ts +59 -0
  634. package/src/mcp/tool-bridge.ts +691 -0
  635. package/src/mcp/tool-cache.ts +117 -0
  636. package/src/mcp/transports/header-policy.ts +95 -0
  637. package/src/mcp/transports/http.ts +508 -0
  638. package/src/mcp/transports/index.ts +7 -0
  639. package/src/mcp/transports/sse.ts +372 -0
  640. package/src/mcp/transports/stdio.ts +905 -0
  641. package/src/mcp/types.ts +467 -0
  642. package/src/memories/index.ts +1434 -0
  643. package/src/memories/storage.ts +578 -0
  644. package/src/memory-backend/index.ts +19 -0
  645. package/src/memory-backend/local-backend.ts +47 -0
  646. package/src/memory-backend/messages.ts +19 -0
  647. package/src/memory-backend/off-backend.ts +25 -0
  648. package/src/memory-backend/resolve.ts +25 -0
  649. package/src/memory-backend/runtime.ts +66 -0
  650. package/src/memory-backend/tool-names.ts +2 -0
  651. package/src/memory-backend/types.ts +166 -0
  652. package/src/mnemopi/backend.ts +629 -0
  653. package/src/mnemopi/config.ts +267 -0
  654. package/src/mnemopi/embed-client.ts +293 -0
  655. package/src/mnemopi/embed-protocol.ts +35 -0
  656. package/src/mnemopi/embed-worker.ts +114 -0
  657. package/src/mnemopi/index.ts +3 -0
  658. package/src/mnemopi/state.ts +940 -0
  659. package/src/modes/acp/acp-agent.ts +2595 -0
  660. package/src/modes/acp/acp-client-bridge.ts +154 -0
  661. package/src/modes/acp/acp-event-mapper.ts +1084 -0
  662. package/src/modes/acp/acp-mode.ts +48 -0
  663. package/src/modes/acp/index.ts +2 -0
  664. package/src/modes/acp/terminal-auth.ts +37 -0
  665. package/src/modes/components/advisor-config.ts +635 -0
  666. package/src/modes/components/advisor-message.ts +109 -0
  667. package/src/modes/components/agent-dashboard.ts +1252 -0
  668. package/src/modes/components/agent-hub-projection.ts +248 -0
  669. package/src/modes/components/agent-hub-renderer.ts +194 -0
  670. package/src/modes/components/agent-hub.ts +1126 -0
  671. package/src/modes/components/agent-transcript-viewer.ts +647 -0
  672. package/src/modes/components/ask-dialog.ts +1017 -0
  673. package/src/modes/components/assistant-message.ts +978 -0
  674. package/src/modes/components/background-tan-message.ts +36 -0
  675. package/src/modes/components/bash-execution.ts +233 -0
  676. package/src/modes/components/bordered-loader.ts +41 -0
  677. package/src/modes/components/btw-panel.ts +161 -0
  678. package/src/modes/components/cache-invalidation-marker.ts +110 -0
  679. package/src/modes/components/chat-block.ts +111 -0
  680. package/src/modes/components/chat-transcript-builder.ts +518 -0
  681. package/src/modes/components/codex-reset-fireworks.ts +369 -0
  682. package/src/modes/components/collab-prompt-message.ts +32 -0
  683. package/src/modes/components/compaction-summary-message.ts +221 -0
  684. package/src/modes/components/copy-selector.ts +218 -0
  685. package/src/modes/components/countdown-timer.ts +75 -0
  686. package/src/modes/components/custom-editor.ts +1036 -0
  687. package/src/modes/components/custom-message.ts +70 -0
  688. package/src/modes/components/diff.ts +254 -0
  689. package/src/modes/components/dynamic-border.ts +37 -0
  690. package/src/modes/components/error-banner.ts +33 -0
  691. package/src/modes/components/eval-execution.ts +169 -0
  692. package/src/modes/components/execution-shared.ts +101 -0
  693. package/src/modes/components/extensions/extension-dashboard.ts +492 -0
  694. package/src/modes/components/extensions/extension-list.ts +507 -0
  695. package/src/modes/components/extensions/index.ts +9 -0
  696. package/src/modes/components/extensions/inspector-panel.ts +326 -0
  697. package/src/modes/components/extensions/state-manager.ts +648 -0
  698. package/src/modes/components/extensions/types.ts +186 -0
  699. package/src/modes/components/footer.ts +274 -0
  700. package/src/modes/components/history-search.ts +268 -0
  701. package/src/modes/components/hook-editor.ts +213 -0
  702. package/src/modes/components/hook-input.ts +87 -0
  703. package/src/modes/components/hook-message.ts +67 -0
  704. package/src/modes/components/hook-selector.ts +691 -0
  705. package/src/modes/components/index.ts +42 -0
  706. package/src/modes/components/keybinding-hints.ts +56 -0
  707. package/src/modes/components/late-diagnostics-message.ts +60 -0
  708. package/src/modes/components/login-dialog.ts +197 -0
  709. package/src/modes/components/logout-account-selector.ts +130 -0
  710. package/src/modes/components/mcp-add-wizard.ts +1413 -0
  711. package/src/modes/components/message-frame.ts +98 -0
  712. package/src/modes/components/model-browser.ts +888 -0
  713. package/src/modes/components/model-hub.ts +2014 -0
  714. package/src/modes/components/model-picker.ts +237 -0
  715. package/src/modes/components/move-overlay.ts +293 -0
  716. package/src/modes/components/oauth-selector.ts +474 -0
  717. package/src/modes/components/omfg-panel.ts +141 -0
  718. package/src/modes/components/overlay-box.ts +109 -0
  719. package/src/modes/components/pause-screen.ts +208 -0
  720. package/src/modes/components/plan-review-overlay.ts +1226 -0
  721. package/src/modes/components/plan-toc.ts +138 -0
  722. package/src/modes/components/plugin-selector.ts +100 -0
  723. package/src/modes/components/plugin-settings.ts +745 -0
  724. package/src/modes/components/queue-mode-selector.ts +61 -0
  725. package/src/modes/components/read-tool-group.ts +856 -0
  726. package/src/modes/components/reset-usage-selector.ts +161 -0
  727. package/src/modes/components/segment-track.ts +89 -0
  728. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  729. package/src/modes/components/selector-helpers.ts +129 -0
  730. package/src/modes/components/session-account-selector.ts +62 -0
  731. package/src/modes/components/session-selector.ts +1035 -0
  732. package/src/modes/components/settings-defs.ts +267 -0
  733. package/src/modes/components/settings-selector.ts +1445 -0
  734. package/src/modes/components/show-images-selector.ts +50 -0
  735. package/src/modes/components/skill-message.ts +110 -0
  736. package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
  737. package/src/modes/components/snapcompact-shape-preview.ts +192 -0
  738. package/src/modes/components/status-line/component.ts +1843 -0
  739. package/src/modes/components/status-line/context-thresholds.ts +86 -0
  740. package/src/modes/components/status-line/git-utils.ts +42 -0
  741. package/src/modes/components/status-line/index.ts +5 -0
  742. package/src/modes/components/status-line/presets.ts +106 -0
  743. package/src/modes/components/status-line/segments.ts +710 -0
  744. package/src/modes/components/status-line/separators.ts +55 -0
  745. package/src/modes/components/status-line/types.ts +159 -0
  746. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  747. package/src/modes/components/theme-selector.ts +68 -0
  748. package/src/modes/components/thinking-selector.ts +57 -0
  749. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  750. package/src/modes/components/tips.txt +26 -0
  751. package/src/modes/components/todo-reminder.ts +43 -0
  752. package/src/modes/components/tool-execution.ts +1467 -0
  753. package/src/modes/components/transcript-container.ts +527 -0
  754. package/src/modes/components/tree-selector.ts +1006 -0
  755. package/src/modes/components/ttsr-notification.ts +123 -0
  756. package/src/modes/components/usage-row.ts +52 -0
  757. package/src/modes/components/user-message-selector.ts +227 -0
  758. package/src/modes/components/user-message.ts +167 -0
  759. package/src/modes/components/visual-truncate.ts +63 -0
  760. package/src/modes/components/welcome.ts +578 -0
  761. package/src/modes/controllers/btw-controller.ts +246 -0
  762. package/src/modes/controllers/command-controller-shared.ts +109 -0
  763. package/src/modes/controllers/command-controller.ts +2024 -0
  764. package/src/modes/controllers/event-controller.ts +2173 -0
  765. package/src/modes/controllers/extension-ui-controller.ts +1243 -0
  766. package/src/modes/controllers/input-controller.ts +2037 -0
  767. package/src/modes/controllers/live-command-controller.ts +261 -0
  768. package/src/modes/controllers/mcp-command-controller.ts +2552 -0
  769. package/src/modes/controllers/omfg-controller.ts +287 -0
  770. package/src/modes/controllers/omfg-rule.ts +647 -0
  771. package/src/modes/controllers/selector-controller.ts +2058 -0
  772. package/src/modes/controllers/session-focus-controller.ts +117 -0
  773. package/src/modes/controllers/ssh-command-controller.ts +385 -0
  774. package/src/modes/controllers/streaming-reveal.ts +399 -0
  775. package/src/modes/controllers/tan-command-controller.ts +243 -0
  776. package/src/modes/controllers/todo-command-controller.ts +487 -0
  777. package/src/modes/controllers/tool-args-reveal.ts +591 -0
  778. package/src/modes/data/emojis.json +1 -0
  779. package/src/modes/emoji-autocomplete.ts +285 -0
  780. package/src/modes/github-ref-autocomplete.ts +75 -0
  781. package/src/modes/gradient-highlight.ts +99 -0
  782. package/src/modes/image-references.ts +137 -0
  783. package/src/modes/index.ts +10 -0
  784. package/src/modes/interactive-mode.ts +5055 -0
  785. package/src/modes/internal-url-autocomplete.ts +158 -0
  786. package/src/modes/loop-limit.ts +192 -0
  787. package/src/modes/magic-keyword-boundary.ts +23 -0
  788. package/src/modes/magic-keywords.ts +42 -0
  789. package/src/modes/markdown-prose.ts +247 -0
  790. package/src/modes/oauth-manual-input.ts +69 -0
  791. package/src/modes/orchestrate.ts +43 -0
  792. package/src/modes/print-mode.ts +282 -0
  793. package/src/modes/prompt-action-autocomplete.ts +322 -0
  794. package/src/modes/queue-input.ts +132 -0
  795. package/src/modes/rpc/host-tools.ts +204 -0
  796. package/src/modes/rpc/host-uris.ts +241 -0
  797. package/src/modes/rpc/rpc-client.ts +1213 -0
  798. package/src/modes/rpc/rpc-frame.ts +316 -0
  799. package/src/modes/rpc/rpc-input.ts +38 -0
  800. package/src/modes/rpc/rpc-messages.ts +127 -0
  801. package/src/modes/rpc/rpc-mode.ts +1519 -0
  802. package/src/modes/rpc/rpc-subagents.ts +265 -0
  803. package/src/modes/rpc/rpc-types.ts +544 -0
  804. package/src/modes/running-subagent-badge.ts +13 -0
  805. package/src/modes/runtime-init.ts +144 -0
  806. package/src/modes/session-observer-registry.ts +223 -0
  807. package/src/modes/session-teardown.ts +82 -0
  808. package/src/modes/setup-version.ts +11 -0
  809. package/src/modes/setup-wizard/index.ts +103 -0
  810. package/src/modes/setup-wizard/lazy.ts +16 -0
  811. package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
  812. package/src/modes/setup-wizard/scenes/model.ts +132 -0
  813. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  814. package/src/modes/setup-wizard/scenes/providers.ts +105 -0
  815. package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
  816. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  817. package/src/modes/setup-wizard/scenes/theme.ts +330 -0
  818. package/src/modes/setup-wizard/scenes/types.ts +65 -0
  819. package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
  820. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  821. package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
  822. package/src/modes/shared.ts +49 -0
  823. package/src/modes/skill-command.ts +91 -0
  824. package/src/modes/theme/dark.json +95 -0
  825. package/src/modes/theme/defaults/alabaster.json +93 -0
  826. package/src/modes/theme/defaults/amethyst.json +96 -0
  827. package/src/modes/theme/defaults/anthracite.json +93 -0
  828. package/src/modes/theme/defaults/basalt.json +91 -0
  829. package/src/modes/theme/defaults/birch.json +95 -0
  830. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  831. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  832. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  833. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  834. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  835. package/src/modes/theme/defaults/dark-copper.json +95 -0
  836. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  837. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  838. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  839. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  840. package/src/modes/theme/defaults/dark-ember.json +95 -0
  841. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  842. package/src/modes/theme/defaults/dark-forest.json +96 -0
  843. package/src/modes/theme/defaults/dark-github.json +105 -0
  844. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  845. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  846. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  847. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  848. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  849. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  850. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  851. package/src/modes/theme/defaults/dark-nord.json +97 -0
  852. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  853. package/src/modes/theme/defaults/dark-one.json +100 -0
  854. package/src/modes/theme/defaults/dark-poimandres.json +143 -0
  855. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  856. package/src/modes/theme/defaults/dark-reef.json +91 -0
  857. package/src/modes/theme/defaults/dark-retro.json +92 -0
  858. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  859. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  860. package/src/modes/theme/defaults/dark-slate.json +95 -0
  861. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  862. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  863. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  864. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  865. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  866. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  867. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  868. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  869. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  870. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  871. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  872. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  873. package/src/modes/theme/defaults/graphite.json +92 -0
  874. package/src/modes/theme/defaults/index.ts +199 -0
  875. package/src/modes/theme/defaults/light-arctic.json +107 -0
  876. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  877. package/src/modes/theme/defaults/light-canyon.json +91 -0
  878. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  879. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  880. package/src/modes/theme/defaults/light-coral.json +95 -0
  881. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  882. package/src/modes/theme/defaults/light-dawn.json +90 -0
  883. package/src/modes/theme/defaults/light-dunes.json +91 -0
  884. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  885. package/src/modes/theme/defaults/light-forest.json +100 -0
  886. package/src/modes/theme/defaults/light-frost.json +95 -0
  887. package/src/modes/theme/defaults/light-github.json +115 -0
  888. package/src/modes/theme/defaults/light-glacier.json +91 -0
  889. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  890. package/src/modes/theme/defaults/light-haze.json +90 -0
  891. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  892. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  893. package/src/modes/theme/defaults/light-lavender.json +95 -0
  894. package/src/modes/theme/defaults/light-meadow.json +91 -0
  895. package/src/modes/theme/defaults/light-mint.json +95 -0
  896. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  897. package/src/modes/theme/defaults/light-ocean.json +99 -0
  898. package/src/modes/theme/defaults/light-one.json +99 -0
  899. package/src/modes/theme/defaults/light-opal.json +91 -0
  900. package/src/modes/theme/defaults/light-orchard.json +91 -0
  901. package/src/modes/theme/defaults/light-paper.json +95 -0
  902. package/src/modes/theme/defaults/light-poimandres.json +143 -0
  903. package/src/modes/theme/defaults/light-prism.json +90 -0
  904. package/src/modes/theme/defaults/light-retro.json +98 -0
  905. package/src/modes/theme/defaults/light-sand.json +95 -0
  906. package/src/modes/theme/defaults/light-savanna.json +91 -0
  907. package/src/modes/theme/defaults/light-solarized.json +102 -0
  908. package/src/modes/theme/defaults/light-soleil.json +90 -0
  909. package/src/modes/theme/defaults/light-sunset.json +99 -0
  910. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  911. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  912. package/src/modes/theme/defaults/light-wetland.json +91 -0
  913. package/src/modes/theme/defaults/light-zenith.json +89 -0
  914. package/src/modes/theme/defaults/limestone.json +94 -0
  915. package/src/modes/theme/defaults/mahogany.json +97 -0
  916. package/src/modes/theme/defaults/marble.json +93 -0
  917. package/src/modes/theme/defaults/obsidian.json +91 -0
  918. package/src/modes/theme/defaults/onyx.json +91 -0
  919. package/src/modes/theme/defaults/pearl.json +93 -0
  920. package/src/modes/theme/defaults/porcelain.json +91 -0
  921. package/src/modes/theme/defaults/quartz.json +96 -0
  922. package/src/modes/theme/defaults/sandstone.json +95 -0
  923. package/src/modes/theme/defaults/titanium.json +90 -0
  924. package/src/modes/theme/light.json +93 -0
  925. package/src/modes/theme/mermaid-cache.ts +92 -0
  926. package/src/modes/theme/shimmer.ts +305 -0
  927. package/src/modes/theme/theme-schema.json +463 -0
  928. package/src/modes/theme/theme.ts +3171 -0
  929. package/src/modes/turn-budget.ts +31 -0
  930. package/src/modes/types.ts +494 -0
  931. package/src/modes/ultrathink.ts +42 -0
  932. package/src/modes/utils/context-usage.ts +518 -0
  933. package/src/modes/utils/copy-targets.ts +378 -0
  934. package/src/modes/utils/hotkeys-markdown.ts +65 -0
  935. package/src/modes/utils/interactive-context-helpers.ts +31 -0
  936. package/src/modes/utils/keybinding-matchers.ts +86 -0
  937. package/src/modes/utils/tools-markdown.ts +31 -0
  938. package/src/modes/utils/transcript-render-helpers.ts +253 -0
  939. package/src/modes/utils/ui-helpers.ts +976 -0
  940. package/src/modes/warp-events.ts +232 -0
  941. package/src/modes/workflow.ts +55 -0
  942. package/src/plan-mode/approved-plan.ts +194 -0
  943. package/src/plan-mode/model-transition.ts +51 -0
  944. package/src/plan-mode/plan-files.ts +40 -0
  945. package/src/plan-mode/plan-handoff.ts +37 -0
  946. package/src/plan-mode/plan-protection.ts +31 -0
  947. package/src/plan-mode/state.ts +6 -0
  948. package/src/priority.json +60 -0
  949. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  950. package/src/prompts/advisor/advise-tool.md +3 -0
  951. package/src/prompts/advisor/context-files.md +8 -0
  952. package/src/prompts/advisor/system.md +98 -0
  953. package/src/prompts/agents/designer.md +74 -0
  954. package/src/prompts/agents/frontmatter.md +12 -0
  955. package/src/prompts/agents/init.md +33 -0
  956. package/src/prompts/agents/librarian.md +119 -0
  957. package/src/prompts/agents/reviewer.md +139 -0
  958. package/src/prompts/agents/scout.md +58 -0
  959. package/src/prompts/agents/security-reviewer.md +75 -0
  960. package/src/prompts/agents/task.md +17 -0
  961. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  962. package/src/prompts/bench/cache-prefix.md +3 -0
  963. package/src/prompts/bench/cache-suffix.md +1 -0
  964. package/src/prompts/bench.md +6 -0
  965. package/src/prompts/ci-green-request.md +36 -0
  966. package/src/prompts/dry-balance-bench.md +8 -0
  967. package/src/prompts/goals/goal-budget-limit.md +16 -0
  968. package/src/prompts/goals/goal-continuation.md +28 -0
  969. package/src/prompts/goals/goal-mode-active.md +23 -0
  970. package/src/prompts/goals/goal-mode-context.md +4 -0
  971. package/src/prompts/goals/goal-todo-context.md +12 -0
  972. package/src/prompts/goals/guided-goal-interview.md +43 -0
  973. package/src/prompts/memories/consolidation.md +30 -0
  974. package/src/prompts/memories/consolidation_system.md +4 -0
  975. package/src/prompts/memories/read-path.md +17 -0
  976. package/src/prompts/memories/stage_one_input.md +6 -0
  977. package/src/prompts/memories/stage_one_system.md +21 -0
  978. package/src/prompts/review-custom-request.md +21 -0
  979. package/src/prompts/review-headless-request.md +16 -0
  980. package/src/prompts/review-request.md +68 -0
  981. package/src/prompts/security/scan-coordinator.md +7 -0
  982. package/src/prompts/security/scan-request.md +21 -0
  983. package/src/prompts/security/validate-request.md +8 -0
  984. package/src/prompts/session/launch-completion.md +1 -0
  985. package/src/prompts/skills/autoload.md +8 -0
  986. package/src/prompts/skills/user-invocation.md +11 -0
  987. package/src/prompts/steering/parent-irc.md +5 -0
  988. package/src/prompts/steering/user-interjection.md +6 -0
  989. package/src/prompts/system/active-repo-context.md +4 -0
  990. package/src/prompts/system/agent-creation-architect.md +50 -0
  991. package/src/prompts/system/agent-creation-user.md +6 -0
  992. package/src/prompts/system/auto-continue.md +1 -0
  993. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  994. package/src/prompts/system/auto-thinking-difficulty.md +14 -0
  995. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  996. package/src/prompts/system/autolearn-guidance.md +7 -0
  997. package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
  998. package/src/prompts/system/background-tan-dispatch.md +8 -0
  999. package/src/prompts/system/btw-user.md +8 -0
  1000. package/src/prompts/system/commit-message-system.md +14 -0
  1001. package/src/prompts/system/computer-safety.md +14 -0
  1002. package/src/prompts/system/custom-system-prompt.md +64 -0
  1003. package/src/prompts/system/eager-task.md +7 -0
  1004. package/src/prompts/system/eager-todo.md +18 -0
  1005. package/src/prompts/system/empty-stop-retry.md +4 -0
  1006. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  1007. package/src/prompts/system/interrupted-thinking.md +7 -0
  1008. package/src/prompts/system/irc-autoreply.md +6 -0
  1009. package/src/prompts/system/irc-incoming.md +9 -0
  1010. package/src/prompts/system/manual-continue.md +7 -0
  1011. package/src/prompts/system/mcp-xdev-guidance.md +11 -0
  1012. package/src/prompts/system/memory-consolidation-system.md +8 -0
  1013. package/src/prompts/system/memory-extraction-system.md +26 -0
  1014. package/src/prompts/system/mid-run-todo-nudge.md +3 -0
  1015. package/src/prompts/system/omfg-user.md +50 -0
  1016. package/src/prompts/system/orchestrate-notice.md +40 -0
  1017. package/src/prompts/system/personalities/default.md +18 -0
  1018. package/src/prompts/system/personalities/friendly.md +17 -0
  1019. package/src/prompts/system/personalities/pragmatic.md +15 -0
  1020. package/src/prompts/system/plan-mode-active.md +125 -0
  1021. package/src/prompts/system/plan-mode-approved.md +22 -0
  1022. package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
  1023. package/src/prompts/system/plan-mode-reference.md +10 -0
  1024. package/src/prompts/system/plan-mode-subagent.md +33 -0
  1025. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  1026. package/src/prompts/system/plan-yolo-handoff.md +5 -0
  1027. package/src/prompts/system/prewalk-checklist.md +7 -0
  1028. package/src/prompts/system/prewalk-continue.md +1 -0
  1029. package/src/prompts/system/prewalk-plan.md +13 -0
  1030. package/src/prompts/system/project-prompt.md +61 -0
  1031. package/src/prompts/system/recap-user.md +9 -0
  1032. package/src/prompts/system/resolve-device-reminder.md +3 -0
  1033. package/src/prompts/system/rewind-report.md +6 -0
  1034. package/src/prompts/system/side-channel-no-tools.md +3 -0
  1035. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  1036. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  1037. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  1038. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  1039. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  1040. package/src/prompts/system/speech-rewrite.md +15 -0
  1041. package/src/prompts/system/subagent-async-pending.md +6 -0
  1042. package/src/prompts/system/subagent-system-prompt.md +73 -0
  1043. package/src/prompts/system/subagent-user-prompt.md +3 -0
  1044. package/src/prompts/system/subagent-yield-reminder.md +23 -0
  1045. package/src/prompts/system/system-prompt.md +263 -0
  1046. package/src/prompts/system/tan-context-switch.md +17 -0
  1047. package/src/prompts/system/task-label.md +23 -0
  1048. package/src/prompts/system/thinking-loop-redirect.md +10 -0
  1049. package/src/prompts/system/title-marker-instruction.md +1 -0
  1050. package/src/prompts/system/title-system.md +16 -0
  1051. package/src/prompts/system/tool-call-loop-redirect.md +8 -0
  1052. package/src/prompts/system/ttsr-interrupt.md +7 -0
  1053. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  1054. package/src/prompts/system/ultrathink-notice.md +3 -0
  1055. package/src/prompts/system/unexpected-stop-classifier.md +17 -0
  1056. package/src/prompts/system/unexpected-stop-retry.md +4 -0
  1057. package/src/prompts/system/vibe-mode-active.md +26 -0
  1058. package/src/prompts/system/web-search.md +25 -0
  1059. package/src/prompts/system/workflow-notice.md +112 -0
  1060. package/src/prompts/system/xdev-mount-notice.md +20 -0
  1061. package/src/prompts/tools/apply-patch.md +65 -0
  1062. package/src/prompts/tools/ask.md +22 -0
  1063. package/src/prompts/tools/ast-edit.md +11 -0
  1064. package/src/prompts/tools/ast-grep.md +19 -0
  1065. package/src/prompts/tools/async-result.md +8 -0
  1066. package/src/prompts/tools/bash.md +23 -0
  1067. package/src/prompts/tools/browser.md +29 -0
  1068. package/src/prompts/tools/checkpoint.md +15 -0
  1069. package/src/prompts/tools/computer.md +26 -0
  1070. package/src/prompts/tools/debug.md +3 -0
  1071. package/src/prompts/tools/eval.md +45 -0
  1072. package/src/prompts/tools/github.md +22 -0
  1073. package/src/prompts/tools/glob.md +16 -0
  1074. package/src/prompts/tools/goal.md +11 -0
  1075. package/src/prompts/tools/grep.md +13 -0
  1076. package/src/prompts/tools/hub.md +34 -0
  1077. package/src/prompts/tools/image-attachment-describe-system.md +8 -0
  1078. package/src/prompts/tools/image-attachment-describe.md +10 -0
  1079. package/src/prompts/tools/image-gen.md +7 -0
  1080. package/src/prompts/tools/inspect-image-system.md +20 -0
  1081. package/src/prompts/tools/inspect-image.md +22 -0
  1082. package/src/prompts/tools/learn.md +7 -0
  1083. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  1084. package/src/prompts/tools/lsp.md +19 -0
  1085. package/src/prompts/tools/manage-skill.md +9 -0
  1086. package/src/prompts/tools/memory-edit.md +12 -0
  1087. package/src/prompts/tools/patch.md +57 -0
  1088. package/src/prompts/tools/read.md +27 -0
  1089. package/src/prompts/tools/recall.md +7 -0
  1090. package/src/prompts/tools/reflect.md +5 -0
  1091. package/src/prompts/tools/replace.md +30 -0
  1092. package/src/prompts/tools/retain.md +6 -0
  1093. package/src/prompts/tools/rewind.md +14 -0
  1094. package/src/prompts/tools/security-publish.md +1 -0
  1095. package/src/prompts/tools/security-scan.md +1 -0
  1096. package/src/prompts/tools/task-async-contract.md +1 -0
  1097. package/src/prompts/tools/task-summary.md +20 -0
  1098. package/src/prompts/tools/task.md +83 -0
  1099. package/src/prompts/tools/todo.md +42 -0
  1100. package/src/prompts/tools/vibe-kill.md +3 -0
  1101. package/src/prompts/tools/vibe-list.md +3 -0
  1102. package/src/prompts/tools/vibe-send.md +9 -0
  1103. package/src/prompts/tools/vibe-spawn.md +10 -0
  1104. package/src/prompts/tools/vibe-turn-result.md +19 -0
  1105. package/src/prompts/tools/vibe-wait.md +8 -0
  1106. package/src/prompts/tools/web-search.md +8 -0
  1107. package/src/prompts/tools/write.md +14 -0
  1108. package/src/registry/agent-lifecycle.ts +505 -0
  1109. package/src/registry/agent-registry.ts +287 -0
  1110. package/src/registry/persisted-agents.ts +423 -0
  1111. package/src/sdk.ts +3820 -0
  1112. package/src/secrets/index.ts +375 -0
  1113. package/src/secrets/obfuscator.ts +2629 -0
  1114. package/src/secrets/regex.ts +21 -0
  1115. package/src/security/auth.ts +98 -0
  1116. package/src/security/cloud.ts +686 -0
  1117. package/src/security/comparison.ts +255 -0
  1118. package/src/security/contracts/ids.ts +111 -0
  1119. package/src/security/contracts/index.ts +4 -0
  1120. package/src/security/contracts/schemas.ts +219 -0
  1121. package/src/security/contracts/types.ts +254 -0
  1122. package/src/security/contracts/validation.ts +69 -0
  1123. package/src/security/coordinator.ts +708 -0
  1124. package/src/security/importers/codex-security.ts +387 -0
  1125. package/src/security/importers/index.ts +2 -0
  1126. package/src/security/importers/sarif.ts +357 -0
  1127. package/src/security/index.ts +13 -0
  1128. package/src/security/preflight.ts +405 -0
  1129. package/src/security/provenance.ts +106 -0
  1130. package/src/security/publication.ts +326 -0
  1131. package/src/security/remediation.ts +93 -0
  1132. package/src/security/resource-output.ts +50 -0
  1133. package/src/security/sarif.ts +78 -0
  1134. package/src/security/store.ts +430 -0
  1135. package/src/session/acp-permission-gate.ts +165 -0
  1136. package/src/session/agent-session-events.ts +67 -0
  1137. package/src/session/agent-session-types.ts +407 -0
  1138. package/src/session/agent-session.ts +9000 -0
  1139. package/src/session/agent-storage.ts +807 -0
  1140. package/src/session/artifacts.ts +154 -0
  1141. package/src/session/async-job-delivery.ts +82 -0
  1142. package/src/session/auth-broker-config.ts +92 -0
  1143. package/src/session/auth-storage.ts +25 -0
  1144. package/src/session/bash-runner.ts +326 -0
  1145. package/src/session/blob-store.ts +295 -0
  1146. package/src/session/checkpoint-entries.ts +81 -0
  1147. package/src/session/claude-session-store.ts +426 -0
  1148. package/src/session/client-bridge.ts +85 -0
  1149. package/src/session/codex-auto-reset.ts +673 -0
  1150. package/src/session/codex-session-store.ts +673 -0
  1151. package/src/session/compact-modes.ts +105 -0
  1152. package/src/session/credential-pin.ts +93 -0
  1153. package/src/session/eval-runner.ts +219 -0
  1154. package/src/session/exit-diagnostics.ts +310 -0
  1155. package/src/session/foreign-session-import.ts +52 -0
  1156. package/src/session/foreign-session-jsonl.ts +29 -0
  1157. package/src/session/foreign-session-store.ts +26 -0
  1158. package/src/session/history-storage.ts +329 -0
  1159. package/src/session/indexed-session-storage.ts +553 -0
  1160. package/src/session/irc-bridge.ts +203 -0
  1161. package/src/session/launch-completion.ts +37 -0
  1162. package/src/session/messages.ts +1304 -0
  1163. package/src/session/model-controls.ts +757 -0
  1164. package/src/session/prewalk.ts +279 -0
  1165. package/src/session/provider-image-budget.ts +86 -0
  1166. package/src/session/queued-messages.ts +99 -0
  1167. package/src/session/redis-session-storage.ts +257 -0
  1168. package/src/session/retry-fallback-chains.ts +455 -0
  1169. package/src/session/role-models.ts +85 -0
  1170. package/src/session/session-advisors.ts +1896 -0
  1171. package/src/session/session-context.ts +584 -0
  1172. package/src/session/session-dump-format.ts +216 -0
  1173. package/src/session/session-entries.ts +306 -0
  1174. package/src/session/session-handoff.ts +325 -0
  1175. package/src/session/session-history-format.ts +459 -0
  1176. package/src/session/session-listing.ts +715 -0
  1177. package/src/session/session-loader.ts +362 -0
  1178. package/src/session/session-maintenance.ts +3115 -0
  1179. package/src/session/session-manager.ts +2747 -0
  1180. package/src/session/session-memory.ts +222 -0
  1181. package/src/session/session-metadata.ts +53 -0
  1182. package/src/session/session-migrations.ts +78 -0
  1183. package/src/session/session-paths.ts +280 -0
  1184. package/src/session/session-persistence.ts +293 -0
  1185. package/src/session/session-provider-boundary.ts +306 -0
  1186. package/src/session/session-stats.ts +349 -0
  1187. package/src/session/session-storage.ts +774 -0
  1188. package/src/session/session-title-slot.ts +141 -0
  1189. package/src/session/session-tools.ts +1385 -0
  1190. package/src/session/session-workspace.ts +53 -0
  1191. package/src/session/settings-stream-fn.ts +79 -0
  1192. package/src/session/shake-types.ts +43 -0
  1193. package/src/session/snapcompact-inline.ts +545 -0
  1194. package/src/session/snapcompact-savings-journal.ts +113 -0
  1195. package/src/session/sql-session-storage.ts +374 -0
  1196. package/src/session/stream-guards.ts +417 -0
  1197. package/src/session/streaming-output.ts +1459 -0
  1198. package/src/session/todo-tracker.ts +380 -0
  1199. package/src/session/tool-choice-queue.ts +305 -0
  1200. package/src/session/ttsr-coordinator.ts +496 -0
  1201. package/src/session/turn-persistence.ts +142 -0
  1202. package/src/session/turn-recovery.ts +2005 -0
  1203. package/src/session/unexpected-stop-classifier.ts +141 -0
  1204. package/src/session/yield-queue.ts +288 -0
  1205. package/src/slash-commands/acp-builtins.ts +70 -0
  1206. package/src/slash-commands/available-commands.ts +105 -0
  1207. package/src/slash-commands/builtin-registry.ts +3149 -0
  1208. package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
  1209. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  1210. package/src/slash-commands/helpers/context-report.ts +66 -0
  1211. package/src/slash-commands/helpers/format.ts +46 -0
  1212. package/src/slash-commands/helpers/logout.ts +108 -0
  1213. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  1214. package/src/slash-commands/helpers/mcp.ts +533 -0
  1215. package/src/slash-commands/helpers/parse.ts +85 -0
  1216. package/src/slash-commands/helpers/reset-usage.ts +68 -0
  1217. package/src/slash-commands/helpers/security.ts +451 -0
  1218. package/src/slash-commands/helpers/session-pin.ts +44 -0
  1219. package/src/slash-commands/helpers/ssh.ts +196 -0
  1220. package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
  1221. package/src/slash-commands/helpers/todo.ts +285 -0
  1222. package/src/slash-commands/helpers/usage-report.ts +198 -0
  1223. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  1224. package/src/slash-commands/types.ts +139 -0
  1225. package/src/ssh/config-writer.ts +183 -0
  1226. package/src/ssh/connection-manager.ts +667 -0
  1227. package/src/ssh/file-transfer.ts +214 -0
  1228. package/src/ssh/sshfs-mount.ts +163 -0
  1229. package/src/ssh/utils.ts +51 -0
  1230. package/src/startup-splash.ts +19 -0
  1231. package/src/stt/asr-client.ts +401 -0
  1232. package/src/stt/asr-protocol.ts +65 -0
  1233. package/src/stt/asr-worker.ts +603 -0
  1234. package/src/stt/downloader.ts +142 -0
  1235. package/src/stt/endpointer.ts +259 -0
  1236. package/src/stt/index.ts +6 -0
  1237. package/src/stt/models.ts +150 -0
  1238. package/src/stt/sherpa-runtime.ts +71 -0
  1239. package/src/stt/stt-controller.ts +320 -0
  1240. package/src/stt/submit-trigger.ts +74 -0
  1241. package/src/subprocess/worker-client.ts +463 -0
  1242. package/src/subprocess/worker-runtime.ts +494 -0
  1243. package/src/system-prompt.ts +921 -0
  1244. package/src/task/agents.ts +170 -0
  1245. package/src/task/commands.ts +132 -0
  1246. package/src/task/discovery.ts +145 -0
  1247. package/src/task/executor.ts +3431 -0
  1248. package/src/task/index.ts +1515 -0
  1249. package/src/task/isolation-ownership.ts +106 -0
  1250. package/src/task/isolation-runner.ts +444 -0
  1251. package/src/task/label.ts +40 -0
  1252. package/src/task/name-generator.ts +1577 -0
  1253. package/src/task/omp-command.ts +26 -0
  1254. package/src/task/output-manager.ts +115 -0
  1255. package/src/task/parallel.ts +221 -0
  1256. package/src/task/persisted-revive.ts +173 -0
  1257. package/src/task/prewalk.ts +6 -0
  1258. package/src/task/prompt-policy.ts +8 -0
  1259. package/src/task/provider-concurrency.ts +100 -0
  1260. package/src/task/read-only-policy.ts +27 -0
  1261. package/src/task/render.ts +1830 -0
  1262. package/src/task/renderer.ts +14 -0
  1263. package/src/task/repair-args.ts +118 -0
  1264. package/src/task/spawn-policy.ts +72 -0
  1265. package/src/task/structured-subagent.ts +676 -0
  1266. package/src/task/subprocess-tool-registry.ts +88 -0
  1267. package/src/task/types.ts +555 -0
  1268. package/src/task/worktree.ts +967 -0
  1269. package/src/task/yield-assembly.ts +198 -0
  1270. package/src/telemetry-export.ts +504 -0
  1271. package/src/thinking.ts +377 -0
  1272. package/src/tiny/device.ts +111 -0
  1273. package/src/tiny/dtype.ts +101 -0
  1274. package/src/tiny/message-preproc.ts +155 -0
  1275. package/src/tiny/models.ts +268 -0
  1276. package/src/tiny/text.ts +290 -0
  1277. package/src/tiny/title-client.ts +460 -0
  1278. package/src/tiny/title-protocol.ts +56 -0
  1279. package/src/tiny/worker.ts +354 -0
  1280. package/src/tools/acp-bridge.ts +125 -0
  1281. package/src/tools/approval.ts +245 -0
  1282. package/src/tools/ask.ts +1459 -0
  1283. package/src/tools/ast-edit.ts +718 -0
  1284. package/src/tools/ast-grep.ts +526 -0
  1285. package/src/tools/auto-generated-guard.ts +335 -0
  1286. package/src/tools/bash-interactive.ts +435 -0
  1287. package/src/tools/bash-interceptor.ts +148 -0
  1288. package/src/tools/bash-pty-selection.ts +14 -0
  1289. package/src/tools/bash-skill-urls.ts +350 -0
  1290. package/src/tools/bash.ts +1826 -0
  1291. package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
  1292. package/src/tools/browser/aria/aria-snapshot.ts +131 -0
  1293. package/src/tools/browser/attach.ts +219 -0
  1294. package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
  1295. package/src/tools/browser/cmux/rpc.ts +200 -0
  1296. package/src/tools/browser/cmux/socket-client.ts +445 -0
  1297. package/src/tools/browser/launch.ts +939 -0
  1298. package/src/tools/browser/readable.ts +112 -0
  1299. package/src/tools/browser/registry.ts +447 -0
  1300. package/src/tools/browser/relay/bridge.ts +945 -0
  1301. package/src/tools/browser/relay/daemon.ts +117 -0
  1302. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  1303. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  1304. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  1305. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  1306. package/src/tools/browser/relay/kind.ts +41 -0
  1307. package/src/tools/browser/relay/protocol.ts +54 -0
  1308. package/src/tools/browser/relay/server.ts +141 -0
  1309. package/src/tools/browser/render.ts +229 -0
  1310. package/src/tools/browser/run-output.ts +76 -0
  1311. package/src/tools/browser/shared-daemon.ts +139 -0
  1312. package/src/tools/browser/tab-protocol.ts +123 -0
  1313. package/src/tools/browser/tab-supervisor.ts +1077 -0
  1314. package/src/tools/browser/tab-worker-entry.ts +29 -0
  1315. package/src/tools/browser/tab-worker.ts +1982 -0
  1316. package/src/tools/browser.ts +487 -0
  1317. package/src/tools/builtin-names.ts +67 -0
  1318. package/src/tools/checkpoint.ts +137 -0
  1319. package/src/tools/computer/exposure.ts +14 -0
  1320. package/src/tools/computer/protocol.ts +69 -0
  1321. package/src/tools/computer/supervisor.ts +409 -0
  1322. package/src/tools/computer/worker-entry.ts +39 -0
  1323. package/src/tools/computer/worker.ts +745 -0
  1324. package/src/tools/computer-renderer.ts +147 -0
  1325. package/src/tools/computer.ts +222 -0
  1326. package/src/tools/conflict-detect.ts +815 -0
  1327. package/src/tools/context.ts +49 -0
  1328. package/src/tools/debug.ts +1121 -0
  1329. package/src/tools/default-renderer.ts +154 -0
  1330. package/src/tools/essential-tools.ts +46 -0
  1331. package/src/tools/eval-backends.ts +34 -0
  1332. package/src/tools/eval-format/index.ts +24 -0
  1333. package/src/tools/eval-format/javascript.ts +952 -0
  1334. package/src/tools/eval-format/julia.ts +446 -0
  1335. package/src/tools/eval-format/python.ts +544 -0
  1336. package/src/tools/eval-format/ruby.ts +380 -0
  1337. package/src/tools/eval-render.ts +784 -0
  1338. package/src/tools/eval.ts +774 -0
  1339. package/src/tools/fetch.ts +1889 -0
  1340. package/src/tools/file-recorder.ts +35 -0
  1341. package/src/tools/fs-cache-invalidation.ts +28 -0
  1342. package/src/tools/gh-cache-invalidation.ts +175 -0
  1343. package/src/tools/gh-format.ts +12 -0
  1344. package/src/tools/gh-renderer.ts +484 -0
  1345. package/src/tools/gh.ts +3958 -0
  1346. package/src/tools/github-cache.ts +663 -0
  1347. package/src/tools/glob.ts +691 -0
  1348. package/src/tools/grep.ts +1920 -0
  1349. package/src/tools/grouped-file-output.ts +210 -0
  1350. package/src/tools/hub/index.ts +579 -0
  1351. package/src/tools/hub/jobs.ts +714 -0
  1352. package/src/tools/hub/launch.ts +690 -0
  1353. package/src/tools/hub/messaging.ts +735 -0
  1354. package/src/tools/hub/types.ts +117 -0
  1355. package/src/tools/image-gen.ts +1689 -0
  1356. package/src/tools/image-providers.ts +50 -0
  1357. package/src/tools/index.ts +736 -0
  1358. package/src/tools/inspect-image-renderer.ts +133 -0
  1359. package/src/tools/inspect-image.ts +316 -0
  1360. package/src/tools/json-tree.ts +260 -0
  1361. package/src/tools/jtd-to-json-schema.ts +219 -0
  1362. package/src/tools/jtd-to-typescript.ts +136 -0
  1363. package/src/tools/jtd-utils.ts +102 -0
  1364. package/src/tools/learn.ts +141 -0
  1365. package/src/tools/list-limit.ts +40 -0
  1366. package/src/tools/manage-skill.ts +102 -0
  1367. package/src/tools/match-line-format.ts +20 -0
  1368. package/src/tools/memory-edit.ts +61 -0
  1369. package/src/tools/memory-recall.ts +102 -0
  1370. package/src/tools/memory-reflect.ts +88 -0
  1371. package/src/tools/memory-render.ts +211 -0
  1372. package/src/tools/memory-retain.ts +89 -0
  1373. package/src/tools/output-meta.ts +860 -0
  1374. package/src/tools/output-schema-validator.ts +307 -0
  1375. package/src/tools/path-utils.ts +1489 -0
  1376. package/src/tools/plan-mode-guard.ts +155 -0
  1377. package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
  1378. package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
  1379. package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
  1380. package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
  1381. package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
  1382. package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
  1383. package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
  1384. package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
  1385. package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
  1386. package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
  1387. package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
  1388. package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
  1389. package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
  1390. package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
  1391. package/src/tools/read.ts +3762 -0
  1392. package/src/tools/render-utils.ts +926 -0
  1393. package/src/tools/renderers.ts +133 -0
  1394. package/src/tools/report-tool-issue.ts +568 -0
  1395. package/src/tools/resolve.ts +423 -0
  1396. package/src/tools/review.ts +103 -0
  1397. package/src/tools/run-scope.ts +417 -0
  1398. package/src/tools/security-scan.ts +287 -0
  1399. package/src/tools/shell-tokenize.ts +213 -0
  1400. package/src/tools/sqlite-reader.ts +884 -0
  1401. package/src/tools/terminal-output.ts +141 -0
  1402. package/src/tools/todo.ts +1226 -0
  1403. package/src/tools/tool-errors.ts +62 -0
  1404. package/src/tools/tool-result.ts +102 -0
  1405. package/src/tools/tool-timeouts.ts +38 -0
  1406. package/src/tools/tts.ts +266 -0
  1407. package/src/tools/vibe.ts +608 -0
  1408. package/src/tools/write.ts +1646 -0
  1409. package/src/tools/xdev.ts +560 -0
  1410. package/src/tools/yield.ts +486 -0
  1411. package/src/tts/downloader.ts +64 -0
  1412. package/src/tts/index.ts +10 -0
  1413. package/src/tts/models.ts +137 -0
  1414. package/src/tts/runtime.ts +21 -0
  1415. package/src/tts/speakable.ts +392 -0
  1416. package/src/tts/speech-enhancer.ts +206 -0
  1417. package/src/tts/streaming-player.ts +120 -0
  1418. package/src/tts/tts-client.ts +475 -0
  1419. package/src/tts/tts-protocol.ts +69 -0
  1420. package/src/tts/tts-worker.ts +434 -0
  1421. package/src/tts/vocalizer.ts +419 -0
  1422. package/src/tts/wav.ts +58 -0
  1423. package/src/tui/code-cell.ts +268 -0
  1424. package/src/tui/file-list.ts +55 -0
  1425. package/src/tui/hyperlink.ts +178 -0
  1426. package/src/tui/index.ts +13 -0
  1427. package/src/tui/output-block.ts +268 -0
  1428. package/src/tui/status-line.ts +54 -0
  1429. package/src/tui/tree-list.ts +172 -0
  1430. package/src/tui/types.ts +15 -0
  1431. package/src/tui/utils.ts +103 -0
  1432. package/src/tui/width-aware-text.ts +58 -0
  1433. package/src/utils/active-repo-context.ts +143 -0
  1434. package/src/utils/block-context.ts +312 -0
  1435. package/src/utils/changelog.ts +373 -0
  1436. package/src/utils/clipboard.ts +361 -0
  1437. package/src/utils/command-args.ts +74 -0
  1438. package/src/utils/commit-message-generator.ts +148 -0
  1439. package/src/utils/cpuprofile.ts +235 -0
  1440. package/src/utils/edit-mode.ts +61 -0
  1441. package/src/utils/enhanced-paste.ts +230 -0
  1442. package/src/utils/event-bus.ts +33 -0
  1443. package/src/utils/external-editor.ts +80 -0
  1444. package/src/utils/fetch-timeout.ts +10 -0
  1445. package/src/utils/file-display-mode.ts +44 -0
  1446. package/src/utils/file-mentions.ts +293 -0
  1447. package/src/utils/git.ts +2472 -0
  1448. package/src/utils/image-loading.ts +236 -0
  1449. package/src/utils/image-resize.ts +420 -0
  1450. package/src/utils/image-vision-fallback.ts +196 -0
  1451. package/src/utils/inspect-image-mode.ts +39 -0
  1452. package/src/utils/ipc.ts +38 -0
  1453. package/src/utils/jj.ts +422 -0
  1454. package/src/utils/lang-from-path.ts +251 -0
  1455. package/src/utils/late-cleanup.ts +17 -0
  1456. package/src/utils/local-date.ts +7 -0
  1457. package/src/utils/mac-file-urls.applescript +37 -0
  1458. package/src/utils/markit-cache.ts +166 -0
  1459. package/src/utils/markit.ts +223 -0
  1460. package/src/utils/mupdf-wasm-embed.ts +12 -0
  1461. package/src/utils/open.ts +126 -0
  1462. package/src/utils/profile-tree.ts +111 -0
  1463. package/src/utils/prompt-path.ts +3 -0
  1464. package/src/utils/qrcode.ts +535 -0
  1465. package/src/utils/sample-profile.ts +437 -0
  1466. package/src/utils/session-color.ts +142 -0
  1467. package/src/utils/shell-snapshot-fn-env.sh +63 -0
  1468. package/src/utils/shell-snapshot.ts +326 -0
  1469. package/src/utils/sixel.ts +69 -0
  1470. package/src/utils/thinking-display.ts +163 -0
  1471. package/src/utils/title-generator.ts +597 -0
  1472. package/src/utils/token-rate.ts +72 -0
  1473. package/src/utils/tool-choice.ts +50 -0
  1474. package/src/utils/tools-manager.ts +411 -0
  1475. package/src/utils/turndown.ts +82 -0
  1476. package/src/utils/zip.ts +1106 -0
  1477. package/src/vibe/runtime.ts +1684 -0
  1478. package/src/vibe/state.ts +4 -0
  1479. package/src/web/kagi.ts +305 -0
  1480. package/src/web/parallel.ts +354 -0
  1481. package/src/web/scrapers/artifacthub.ts +207 -0
  1482. package/src/web/scrapers/arxiv.ts +83 -0
  1483. package/src/web/scrapers/aur.ts +162 -0
  1484. package/src/web/scrapers/biorxiv.ts +133 -0
  1485. package/src/web/scrapers/bluesky.ts +262 -0
  1486. package/src/web/scrapers/brew.ts +172 -0
  1487. package/src/web/scrapers/cheatsh.ts +68 -0
  1488. package/src/web/scrapers/chocolatey.ts +196 -0
  1489. package/src/web/scrapers/choosealicense.ts +95 -0
  1490. package/src/web/scrapers/cisa-kev.ts +87 -0
  1491. package/src/web/scrapers/clojars.ts +154 -0
  1492. package/src/web/scrapers/coingecko.ts +177 -0
  1493. package/src/web/scrapers/crates-io.ts +97 -0
  1494. package/src/web/scrapers/crossref.ts +136 -0
  1495. package/src/web/scrapers/devto.ts +147 -0
  1496. package/src/web/scrapers/discogs.ts +306 -0
  1497. package/src/web/scrapers/discourse.ts +197 -0
  1498. package/src/web/scrapers/dockerhub.ts +138 -0
  1499. package/src/web/scrapers/docs-rs.ts +663 -0
  1500. package/src/web/scrapers/fdroid.ts +134 -0
  1501. package/src/web/scrapers/firefox-addons.ts +191 -0
  1502. package/src/web/scrapers/flathub.ts +223 -0
  1503. package/src/web/scrapers/github-gist.ts +58 -0
  1504. package/src/web/scrapers/github.ts +800 -0
  1505. package/src/web/scrapers/gitlab.ts +401 -0
  1506. package/src/web/scrapers/go-pkg.ts +266 -0
  1507. package/src/web/scrapers/hackage.ts +129 -0
  1508. package/src/web/scrapers/hackernews.ts +189 -0
  1509. package/src/web/scrapers/hex.ts +105 -0
  1510. package/src/web/scrapers/huggingface.ts +321 -0
  1511. package/src/web/scrapers/iacr.ts +89 -0
  1512. package/src/web/scrapers/index.ts +252 -0
  1513. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1514. package/src/web/scrapers/lemmy.ts +203 -0
  1515. package/src/web/scrapers/lobsters.ts +175 -0
  1516. package/src/web/scrapers/mastodon.ts +292 -0
  1517. package/src/web/scrapers/maven.ts +138 -0
  1518. package/src/web/scrapers/mdn.ts +173 -0
  1519. package/src/web/scrapers/metacpan.ts +222 -0
  1520. package/src/web/scrapers/musicbrainz.ts +250 -0
  1521. package/src/web/scrapers/npm.ts +98 -0
  1522. package/src/web/scrapers/nuget.ts +183 -0
  1523. package/src/web/scrapers/nvd.ts +222 -0
  1524. package/src/web/scrapers/ollama.ts +239 -0
  1525. package/src/web/scrapers/open-vsx.ts +106 -0
  1526. package/src/web/scrapers/opencorporates.ts +292 -0
  1527. package/src/web/scrapers/openlibrary.ts +336 -0
  1528. package/src/web/scrapers/orcid.ts +286 -0
  1529. package/src/web/scrapers/osv.ts +176 -0
  1530. package/src/web/scrapers/packagist.ts +160 -0
  1531. package/src/web/scrapers/pub-dev.ts +143 -0
  1532. package/src/web/scrapers/pubmed.ts +211 -0
  1533. package/src/web/scrapers/pypi.ts +112 -0
  1534. package/src/web/scrapers/rawg.ts +110 -0
  1535. package/src/web/scrapers/readthedocs.ts +121 -0
  1536. package/src/web/scrapers/reddit.ts +95 -0
  1537. package/src/web/scrapers/repology.ts +251 -0
  1538. package/src/web/scrapers/rfc.ts +201 -0
  1539. package/src/web/scrapers/rubygems.ts +103 -0
  1540. package/src/web/scrapers/searchcode.ts +189 -0
  1541. package/src/web/scrapers/sec-edgar.ts +261 -0
  1542. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1543. package/src/web/scrapers/snapcraft.ts +187 -0
  1544. package/src/web/scrapers/sourcegraph.ts +336 -0
  1545. package/src/web/scrapers/spdx.ts +108 -0
  1546. package/src/web/scrapers/spotify.ts +198 -0
  1547. package/src/web/scrapers/stackoverflow.ts +120 -0
  1548. package/src/web/scrapers/terraform.ts +277 -0
  1549. package/src/web/scrapers/tldr.ts +47 -0
  1550. package/src/web/scrapers/twitter.ts +94 -0
  1551. package/src/web/scrapers/types.ts +354 -0
  1552. package/src/web/scrapers/utils.ts +109 -0
  1553. package/src/web/scrapers/vimeo.ts +133 -0
  1554. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1555. package/src/web/scrapers/w3c.ts +156 -0
  1556. package/src/web/scrapers/wikidata.ts +344 -0
  1557. package/src/web/scrapers/wikipedia.ts +84 -0
  1558. package/src/web/scrapers/youtube.ts +325 -0
  1559. package/src/web/search/index.ts +393 -0
  1560. package/src/web/search/provider.ts +272 -0
  1561. package/src/web/search/providers/anthropic.ts +406 -0
  1562. package/src/web/search/providers/base.ts +112 -0
  1563. package/src/web/search/providers/brave.ts +181 -0
  1564. package/src/web/search/providers/browser-headers.ts +82 -0
  1565. package/src/web/search/providers/browser-page.ts +125 -0
  1566. package/src/web/search/providers/codex.ts +788 -0
  1567. package/src/web/search/providers/duckduckgo.ts +382 -0
  1568. package/src/web/search/providers/ecosia.ts +183 -0
  1569. package/src/web/search/providers/exa.ts +513 -0
  1570. package/src/web/search/providers/firecrawl.ts +213 -0
  1571. package/src/web/search/providers/gemini.ts +634 -0
  1572. package/src/web/search/providers/google.ts +195 -0
  1573. package/src/web/search/providers/jina.ts +144 -0
  1574. package/src/web/search/providers/kagi.ts +98 -0
  1575. package/src/web/search/providers/kimi.ts +221 -0
  1576. package/src/web/search/providers/mojeek.ts +220 -0
  1577. package/src/web/search/providers/parallel.ts +186 -0
  1578. package/src/web/search/providers/perplexity-auth.ts +142 -0
  1579. package/src/web/search/providers/perplexity.ts +997 -0
  1580. package/src/web/search/providers/public.ts +199 -0
  1581. package/src/web/search/providers/searxng.ts +467 -0
  1582. package/src/web/search/providers/startpage.ts +225 -0
  1583. package/src/web/search/providers/synthetic.ts +126 -0
  1584. package/src/web/search/providers/tavily.ts +244 -0
  1585. package/src/web/search/providers/tinyfish.ts +166 -0
  1586. package/src/web/search/providers/utils.ts +130 -0
  1587. package/src/web/search/providers/xai.ts +372 -0
  1588. package/src/web/search/providers/zai.ts +452 -0
  1589. package/src/web/search/query.ts +850 -0
  1590. package/src/web/search/render.ts +262 -0
  1591. package/src/web/search/types.ts +518 -0
  1592. package/src/web/search/utils.ts +17 -0
  1593. package/src/workspace-tree.ts +326 -0
@@ -0,0 +1,2822 @@
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 {
5
+ Api,
6
+ Context,
7
+ Model,
8
+ ModelSpec,
9
+ RemoteCompactionConfig,
10
+ SimpleStreamOptions,
11
+ ThinkingConfig,
12
+ } from "@oh-my-pi/pi-ai/types";
13
+ import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
14
+ import { buildModel } from "@oh-my-pi/pi-catalog/build";
15
+ import { isVertexExpressOpenAIUrl } from "@oh-my-pi/pi-catalog/hosts";
16
+ import { readModelCache } from "@oh-my-pi/pi-catalog/model-cache";
17
+ import {
18
+ createModelManager,
19
+ type ModelManagerOptions,
20
+ type ModelRefreshStrategy,
21
+ } from "@oh-my-pi/pi-catalog/model-manager";
22
+ import { getBundledModels, getBundledProviders } from "@oh-my-pi/pi-catalog/models";
23
+ import {
24
+ googleAntigravityModelManagerOptions,
25
+ googleGeminiCliModelManagerOptions,
26
+ type OpenAICodexAccount,
27
+ openaiCodexModelManagerOptions,
28
+ PROVIDER_DESCRIPTORS,
29
+ resolveModelCacheProviderId,
30
+ resolveOllamaModelCacheProviderId,
31
+ } from "@oh-my-pi/pi-catalog/provider-models";
32
+ import {
33
+ collapseBuiltModelVariants,
34
+ getVariantAliasSources,
35
+ resolveVariantAlias,
36
+ } from "@oh-my-pi/pi-catalog/variant-collapse";
37
+
38
+ const SPECIAL_MODEL_MANAGER_PROVIDER_IDS: readonly string[] = [
39
+ "google-antigravity",
40
+ "google-gemini-cli",
41
+ "openai-codex",
42
+ ];
43
+
44
+ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
45
+ ...PROVIDER_DESCRIPTORS.map(descriptor => descriptor.providerId),
46
+ ...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
47
+ ];
48
+
49
+ // Sentinels for local-only OAuth tokens — declared inline to avoid loading
50
+ // provider modules at startup. Must match packages/ai/src/registry/llama-cpp.ts,
51
+ // packages/ai/src/registry/lm-studio.ts, and packages/ai/src/registry/vllm.ts.
52
+ const LOCAL_PROVIDER_PLACEHOLDERS = new Set<string>(["llama-cpp-local", "lm-studio-local", "vllm-local"]);
53
+
54
+ /**
55
+ * Hard bound for extension-provided fetchDynamicModels to prevent indefinite hangs
56
+ * during runtime provider discovery. Uses a cancellable manual timer (not AbortSignal.timeout)
57
+ * so a successful fast path does not leave an armed timeout signal for concurrent GC.
58
+ */
59
+ const RUNTIME_DYNAMIC_MODEL_FETCH_TIMEOUT_MS = 15_000;
60
+ // Built-in discovery preflight mirror of the catalog model-manager's private
61
+ // cache timings (model-manager.ts: DEFAULT_CACHE_TTL_MS / NON_AUTHORITATIVE_RETRY_MS).
62
+ // Built-in descriptors never override cacheTtlMs, so agreeing with these values
63
+ // makes the OAuth-refresh preflight fire exactly when the manager will fetch.
64
+ const BUILT_IN_DISCOVERY_CACHE_TTL_MS = 2 * 60 * 60 * 1000;
65
+ const BUILT_IN_DISCOVERY_NON_AUTHORITATIVE_RETRY_MS = 5 * 60 * 1000;
66
+
67
+ import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai";
68
+ import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
69
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
70
+ import { setCodexAttestationProvider } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
71
+ import { getProviderDefinition } from "@oh-my-pi/pi-ai/registry";
72
+ import {
73
+ getBundledModelReferenceIndex,
74
+ inheritReferenceThinking,
75
+ resolveModelReference,
76
+ } from "@oh-my-pi/pi-catalog/identity";
77
+ import { $envExact, isBunTestRuntime, isRecord, logger, wrapFetchForExtraCa } from "@oh-my-pi/pi-utils";
78
+ import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
79
+ import { generateCodexAttestation } from "../live/attestation";
80
+ import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
81
+ import { type ApiKeyResolverModel, type ApiKeyResolverOptions, createApiKeyResolver } from "./api-key-resolver";
82
+ import type { ConfigError, ConfigFile } from "./config-file";
83
+ import {
84
+ applyLlamaCppQwenThinking,
85
+ DISCOVERY_DEFAULT_MAX_TOKENS,
86
+ type DiscoveryContext,
87
+ type DiscoveryProviderConfig,
88
+ discoverLlamaCppModelRuntimeMetadata,
89
+ discoverModelsByProviderType,
90
+ getImplicitOllamaBaseUrl,
91
+ getOllamaContextLengthOverride,
92
+ normalizeLiteLLMDiscoveryBaseUrl,
93
+ } from "./model-discovery";
94
+ import { ModelsConfigFile, type ProviderValidationModel, validateProviderConfiguration } from "./models-config";
95
+ import type { ModelOverride, ModelsConfig, ProviderAuthMode } from "./models-config-schema";
96
+ import { settings } from "./settings";
97
+
98
+ // DeviceCheck attestation (`x-oai-attestation`) for ChatGPT-OAuth Codex
99
+ // requests; the pi-ai provider resolves it just-in-time per request.
100
+ setCodexAttestationProvider(generateCodexAttestation);
101
+
102
+ export const kNoAuth = "N/A";
103
+
104
+ export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
105
+ return Boolean(apiKey) && apiKey !== kNoAuth;
106
+ }
107
+
108
+ function isDiscoveryBearerApiKey(apiKey: string | undefined | null): apiKey is string {
109
+ return isAuthenticated(apiKey) && !LOCAL_PROVIDER_PLACEHOLDERS.has(apiKey);
110
+ }
111
+
112
+ /**
113
+ * Wraps an extension-provided fetchDynamicModels call with a hard timeout.
114
+ * Uses a cancellable manual timer (not AbortSignal.timeout) so that a fast
115
+ * successful path does not leave an armed timeout signal for concurrent GC.
116
+ * The inner fetcher does not receive a signal (extension contract has none).
117
+ */
118
+ async function withRuntimeDynamicModelsTimeout<T>(timeoutMs: number, run: () => Promise<T>): Promise<T> {
119
+ const { promise: timeoutPromise, reject: timeoutReject } = Promise.withResolvers<never>();
120
+ const timer = setTimeout(() => {
121
+ timeoutReject(new Error(`fetchDynamicModels timed out after ${timeoutMs}ms`));
122
+ }, timeoutMs);
123
+ try {
124
+ return await Promise.race([run(), timeoutPromise]);
125
+ } finally {
126
+ clearTimeout(timer);
127
+ }
128
+ }
129
+
130
+ /** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
131
+ interface ProviderOverride {
132
+ baseUrl?: string;
133
+ headers?: Record<string, string>;
134
+ apiKey?: string;
135
+ authHeader?: boolean;
136
+ compat?: ModelSpec<Api>["compat"];
137
+ remoteCompaction?: RemoteCompactionConfig<Api>;
138
+ transport?: Model<Api>["transport"];
139
+ }
140
+
141
+ /**
142
+ * Merge a freshly discovered model with the matching bundled/configured entry
143
+ * (or a runtime provider override when no bundled entry exists).
144
+ *
145
+ * `baseUrl` resolution priority:
146
+ * 1. User-set `providerOverride.baseUrl` (explicit override in models.json)
147
+ * 2. Discovered baseUrl (xiaomi `tp-` token-plan keys resolve to
148
+ * `token-plan-sgp.xiaomimimo.com` at discovery time)
149
+ * 3. Existing bundled baseUrl (the host baked into `models.json`)
150
+ *
151
+ * `transport` resolution priority:
152
+ * 1. `providerOverride.transport` (e.g. `pi-native` for auth-gateway users)
153
+ * 2. `existing.transport` (carried over from boot-time override application)
154
+ * 3. `model.transport` (rarely set — discovery defaults omit it)
155
+ *
156
+ * Without (1), the user's override would lose to discovery; without (2)
157
+ * preferred over (3), the bundled `api.xiaomimimo.com` would shadow the
158
+ * tp- token-plan host and produce 401s on the first stream call.
159
+ * Without explicit transport propagation, an openrouter (or any) entry
160
+ * marked `transport: pi-native` in models.yml silently reverts to the
161
+ * default openai-completions transport after the background catalog
162
+ * refresh — so the first `/model` switch after boot hits the raw OpenAI
163
+ * chat-completions URL instead of the gateway's `/v1/pi/stream` (#2555).
164
+ * See `xiaomi-tp-discovery-merge.test.ts` and the `refresh()` baseUrl-override
165
+ * regression in `model-registry.test.ts`.
166
+ */
167
+ export function mergeDiscoveredModel<TApi extends Api>(
168
+ model: Model<TApi>,
169
+ existing: Model<Api> | undefined,
170
+ providerOverride?: Pick<ProviderOverride, "baseUrl" | "compat" | "headers" | "remoteCompaction" | "transport">,
171
+ ): Model<TApi> {
172
+ if (existing) {
173
+ const supportsTools = model.supportsTools ?? existing.supportsTools;
174
+ return buildModel({
175
+ ...model,
176
+ baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
177
+ headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
178
+ transport: providerOverride?.transport ?? existing.transport ?? model.transport,
179
+ remoteCompaction: mergeProviderRemoteCompactionConfig(
180
+ mergeRemoteCompactionConfig(existing.remoteCompaction, model.remoteCompaction),
181
+ providerOverride?.remoteCompaction,
182
+ ),
183
+ ...(supportsTools !== undefined ? { supportsTools } : {}),
184
+ compat: mergeCompat(model.compatConfig, providerOverride?.compat),
185
+ } as ModelSpec<TApi>);
186
+ }
187
+ if (providerOverride) {
188
+ return buildModel({
189
+ ...model,
190
+ baseUrl: providerOverride.baseUrl ?? model.baseUrl,
191
+ headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
192
+ ...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
193
+ remoteCompaction: mergeProviderRemoteCompactionConfig(
194
+ model.remoteCompaction,
195
+ providerOverride.remoteCompaction,
196
+ ),
197
+ compat: mergeCompat(model.compatConfig, providerOverride.compat),
198
+ } as ModelSpec<TApi>);
199
+ }
200
+ return model;
201
+ }
202
+
203
+ const AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS = new Set<string>(
204
+ PROVIDER_DESCRIPTORS.filter(descriptor => descriptor.dynamicModelsAuthoritative).map(
205
+ descriptor => descriptor.providerId,
206
+ ),
207
+ );
208
+
209
+ function isAuthoritativeProjectCatalogModel(model: Model<Api>): boolean {
210
+ return (
211
+ model.provider === "google-vertex" &&
212
+ model.api === "openai-completions" &&
213
+ isVertexExpressOpenAIUrl(model.baseUrl)
214
+ );
215
+ }
216
+
217
+ function providersWithAuthoritativeProjectCatalog(models: readonly Model<Api>[]): Set<string> {
218
+ const providers = new Set<string>();
219
+ for (const model of models) {
220
+ if (isAuthoritativeProjectCatalogModel(model)) {
221
+ providers.add(model.provider);
222
+ }
223
+ }
224
+ return providers;
225
+ }
226
+
227
+ function dropProviderModels(models: readonly Model<Api>[], providers: ReadonlySet<string>): Model<Api>[] {
228
+ return models.filter(model => !providers.has(model.provider));
229
+ }
230
+
231
+ /**
232
+ * Merge `incoming` entries into a copy of `base`, keyed by `provider`+`id`.
233
+ * Matches are replaced with `combine(existing, entry)`; new entries are
234
+ * appended as `combine(undefined, entry)`.
235
+ */
236
+ function mergeByModelKey<T extends { provider: string; id: string }>(
237
+ base: readonly Model<Api>[],
238
+ incoming: readonly T[],
239
+ combine: (existing: Model<Api> | undefined, entry: T) => Model<Api>,
240
+ ): Model<Api>[] {
241
+ const merged = [...base];
242
+ const indexByKey = new Map<string, number>();
243
+ for (let i = 0; i < merged.length; i += 1) {
244
+ indexByKey.set(`${merged[i].provider}\u0000${merged[i].id}`, i);
245
+ }
246
+ for (const entry of incoming) {
247
+ const key = `${entry.provider}\u0000${entry.id}`;
248
+ const existingIndex = indexByKey.get(key);
249
+ if (existingIndex !== undefined) {
250
+ merged[existingIndex] = combine(merged[existingIndex], entry);
251
+ } else {
252
+ merged.push(combine(undefined, entry));
253
+ indexByKey.set(key, merged.length - 1);
254
+ }
255
+ }
256
+ return merged;
257
+ }
258
+
259
+ interface BuiltInDiscoveryResult {
260
+ models: Model<Api>[];
261
+ authoritativeProviders: Set<string>;
262
+ }
263
+
264
+ export type ProviderDiscoveryStatus = "idle" | "ok" | "empty" | "cached" | "unavailable" | "unauthenticated";
265
+
266
+ export interface ProviderDiscoveryState {
267
+ provider: string;
268
+ status: ProviderDiscoveryStatus;
269
+ optional: boolean;
270
+ stale: boolean;
271
+ fetchedAt?: number;
272
+ models: string[];
273
+ error?: string;
274
+ }
275
+
276
+ /** Result of loading custom models config. */
277
+ interface CustomModelsResult {
278
+ models?: CustomModelOverlay[];
279
+ overrides?: Map<string, ProviderOverride>;
280
+ modelOverrides?: Map<string, Map<string, ModelOverride>>;
281
+ keylessProviders?: Set<string>;
282
+ discoverableProviders?: DiscoveryProviderConfig[];
283
+ configuredProviders?: Set<string>;
284
+ error?: ConfigError;
285
+ found: boolean;
286
+ }
287
+
288
+ const commandValueCache = new Map<string, string>();
289
+ // Failed `!command` resolutions (non-zero exit, empty stdout) are negative-cached
290
+ // with a TTL instead of forever: a transient failure (locked password manager,
291
+ // network hiccup) must not disable the key until process restart, but re-running
292
+ // the command on every resolution would restore the execSync storm this cache
293
+ // exists to prevent. One probe per TTL window bounds both.
294
+ const COMMAND_FAILURE_RETRY_MS = 30_000;
295
+ const commandFailureRetryAt = new Map<string, number>();
296
+
297
+ function isCommandConfigValue(valueConfig: string | undefined): valueConfig is string {
298
+ return valueConfig?.startsWith("!") === true;
299
+ }
300
+
301
+ function resolveCommandConfig(command: string): string | undefined {
302
+ const cached = commandValueCache.get(command);
303
+ if (cached !== undefined) return cached;
304
+ const retryAt = commandFailureRetryAt.get(command);
305
+ if (retryAt !== undefined && Date.now() < retryAt) return undefined;
306
+ try {
307
+ const stdout = execSync(command, { encoding: "utf8", timeout: 10_000, windowsHide: true });
308
+ const trimmed = stdout.trim();
309
+ if (trimmed.length === 0) {
310
+ commandFailureRetryAt.set(command, Date.now() + COMMAND_FAILURE_RETRY_MS);
311
+ return undefined;
312
+ }
313
+ commandFailureRetryAt.delete(command);
314
+ commandValueCache.set(command, trimmed);
315
+ return trimmed;
316
+ } catch {
317
+ commandFailureRetryAt.set(command, Date.now() + COMMAND_FAILURE_RETRY_MS);
318
+ return undefined;
319
+ }
320
+ }
321
+
322
+ interface CommandApiKeyResolution {
323
+ configured: boolean;
324
+ value?: string;
325
+ }
326
+ /**
327
+ * Resolve a models.yml/models.yaml secret/config value to an actual value.
328
+ * `!cmd` runs a shell command and returns trimmed stdout, otherwise env vars are
329
+ * checked first and the input falls back to a literal value.
330
+ */
331
+ function resolveConfigValue(valueConfig: string): string | undefined {
332
+ if (valueConfig.startsWith("!")) return resolveCommandConfig(valueConfig.slice(1).trim());
333
+ const envValue = $envExact(valueConfig);
334
+ if (envValue) return envValue;
335
+ return valueConfig;
336
+ }
337
+
338
+ type HeaderSource = Record<string, string> | undefined;
339
+
340
+ interface HeaderResolutionOptions {
341
+ authHeader?: boolean;
342
+ apiKeyConfig?: string;
343
+ }
344
+
345
+ function materializeConfigHeaderSources(
346
+ sources: readonly HeaderSource[],
347
+ options?: HeaderResolutionOptions,
348
+ ): Record<string, string> | undefined {
349
+ const resolved: Record<string, string> = {};
350
+ for (const source of sources) {
351
+ if (!source) continue;
352
+ for (const [key, value] of Object.entries(source)) {
353
+ const next = resolveConfigValue(value);
354
+ if (next) resolved[key] = next;
355
+ }
356
+ }
357
+ if (options?.authHeader && options.apiKeyConfig) {
358
+ const resolvedKey = resolveConfigValue(options.apiKeyConfig);
359
+ if (resolvedKey) resolved.Authorization = `Bearer ${resolvedKey}`;
360
+ }
361
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
362
+ }
363
+
364
+ function createLiveConfigHeaders(
365
+ sources: readonly HeaderSource[],
366
+ options?: HeaderResolutionOptions,
367
+ ): Record<string, string> | undefined {
368
+ const liveSources = sources.filter((source): source is Record<string, string> => source !== undefined);
369
+ if (liveSources.length === 0 && (!options?.authHeader || !options.apiKeyConfig)) return undefined;
370
+
371
+ const localHeaders: Record<string, string> = {};
372
+ const allSources = [...liveSources, localHeaders];
373
+ const current = () => materializeConfigHeaderSources(allSources, options) ?? {};
374
+ return new Proxy(localHeaders, {
375
+ get(target, property, receiver) {
376
+ if (typeof property !== "string") return Reflect.get(target, property, receiver);
377
+ return current()[property];
378
+ },
379
+ set(target, property, value) {
380
+ if (typeof property !== "string" || typeof value !== "string") return false;
381
+ target[property] = value;
382
+ return true;
383
+ },
384
+ deleteProperty(target, property) {
385
+ if (typeof property !== "string") return false;
386
+ delete target[property];
387
+ return true;
388
+ },
389
+ has(_target, property) {
390
+ if (typeof property !== "string") return false;
391
+ return Object.hasOwn(current(), property);
392
+ },
393
+ ownKeys() {
394
+ return Reflect.ownKeys(current());
395
+ },
396
+ getOwnPropertyDescriptor(_target, property) {
397
+ if (typeof property !== "string") return undefined;
398
+ const headers = current();
399
+ if (!Object.hasOwn(headers, property)) return undefined;
400
+ return {
401
+ configurable: true,
402
+ enumerable: true,
403
+ value: headers[property],
404
+ writable: true,
405
+ };
406
+ },
407
+ });
408
+ }
409
+
410
+ function resolveConfigHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {
411
+ return materializeConfigHeaderSources([headers]);
412
+ }
413
+
414
+ function extractGoogleOAuthToken(value: string | undefined): string | undefined {
415
+ if (!isAuthenticated(value)) return undefined;
416
+ try {
417
+ const parsed = JSON.parse(value) as { token?: unknown };
418
+ if (Object.hasOwn(parsed, "token")) {
419
+ if (typeof parsed.token !== "string") {
420
+ return undefined;
421
+ }
422
+ const token = parsed.token.trim();
423
+ return token.length > 0 ? token : undefined;
424
+ }
425
+ } catch {
426
+ // OAuth values for Google providers are expected to be JSON, but custom setups may already provide raw token.
427
+ }
428
+ return value;
429
+ }
430
+
431
+ function getOAuthCredentialsForProvider(authStorage: AuthStorage, provider: string): OAuthCredential[] {
432
+ const providerEntry = authStorage.getAll()[provider];
433
+ if (!providerEntry) {
434
+ return [];
435
+ }
436
+ const entries = Array.isArray(providerEntry) ? providerEntry : [providerEntry];
437
+ return entries.filter((entry): entry is OAuthCredential => entry.type === "oauth");
438
+ }
439
+
440
+ /**
441
+ * Resolve every configured Codex OAuth account for catalog discovery, refreshing
442
+ * each credential exactly once. Codex `/models` is account-scoped, so discovery
443
+ * must fetch per account and union the results; resolving a single access token
444
+ * (as before) hid models available only through a sibling account (#6265).
445
+ *
446
+ * Returns `null` when any stored account fails to resolve (e.g. a transient
447
+ * refresh failure): the Codex manager is authoritative, so unioning only the
448
+ * accounts that resolved would cache a partial catalog and hide the failed
449
+ * account's models for the cache TTL. Aborting keeps the previous/bundled
450
+ * catalog instead.
451
+ */
452
+ async function resolveCodexDiscoveryAccounts(
453
+ authStorage: AuthStorage,
454
+ resolvedAccessToken: string,
455
+ ): Promise<OpenAICodexAccount[] | null> {
456
+ const accesses = await authStorage.getOAuthAccesses("openai-codex");
457
+ const accounts: OpenAICodexAccount[] = [];
458
+ for (const access of accesses) {
459
+ if (!access.ok) return null;
460
+ accounts.push({ accessToken: access.accessToken, accountId: access.accountId });
461
+ }
462
+ if (!accounts.some(account => account.accessToken === resolvedAccessToken)) {
463
+ const matchingCredential = getOAuthCredentialsForProvider(authStorage, "openai-codex").find(
464
+ credential => credential.access === resolvedAccessToken,
465
+ );
466
+ accounts.push({ accessToken: resolvedAccessToken, accountId: matchingCredential?.accountId });
467
+ }
468
+ return accounts;
469
+ }
470
+
471
+ function mergeCompat<TBase extends object, TOverride extends object>(
472
+ baseCompat: TBase | null | undefined,
473
+ overrideCompat: TOverride | null | undefined,
474
+ ): (TBase & TOverride) | TBase | TOverride | undefined {
475
+ if (!baseCompat) return overrideCompat ?? undefined;
476
+ if (!overrideCompat) return baseCompat;
477
+
478
+ const merged: Record<string, unknown> = { ...(baseCompat as Record<string, unknown>) };
479
+ for (const [key, overrideValue] of Object.entries(overrideCompat)) {
480
+ const baseValue = (baseCompat as Record<string, unknown>)[key];
481
+ merged[key] =
482
+ isRecord(baseValue) && isRecord(overrideValue) ? mergeCompat(baseValue, overrideValue) : overrideValue;
483
+ }
484
+ return merged as TBase & TOverride;
485
+ }
486
+
487
+ function mergeRemoteCompactionConfig(
488
+ baseConfig: RemoteCompactionConfig<Api> | undefined,
489
+ overrideConfig: RemoteCompactionConfig<Api> | undefined,
490
+ ): RemoteCompactionConfig<Api> | undefined {
491
+ if (!baseConfig) return overrideConfig;
492
+ if (!overrideConfig) return baseConfig;
493
+ return { ...baseConfig, ...overrideConfig };
494
+ }
495
+
496
+ function mergeProviderRemoteCompactionConfig(
497
+ modelConfig: RemoteCompactionConfig<Api> | undefined,
498
+ providerConfig: RemoteCompactionConfig<Api> | undefined,
499
+ ): RemoteCompactionConfig<Api> | undefined {
500
+ return mergeRemoteCompactionConfig(providerConfig, modelConfig);
501
+ }
502
+
503
+ /**
504
+ * Project a built model back to spec shape for the model-manager/cache
505
+ * boundary: sparse compat comes from `compatConfig`, never from the resolved
506
+ * record.
507
+ */
508
+ function toModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi> {
509
+ return { ...model, compat: model.compatConfig } as ModelSpec<TApi>;
510
+ }
511
+
512
+ /**
513
+ * The patchable subset of `Model` fields shared by `modelOverrides` entries,
514
+ * custom model definitions, and parsed custom-model overlays. `undefined`
515
+ * always means "leave the base value alone".
516
+ */
517
+ interface ModelPatch {
518
+ name?: string;
519
+ reasoning?: boolean;
520
+ thinking?: ThinkingConfig;
521
+ input?: ("text" | "image")[];
522
+ supportsTools?: boolean;
523
+ cost?: Partial<Model<Api>["cost"]>;
524
+ contextWindow?: number;
525
+ maxTokens?: number;
526
+ omitMaxOutputTokens?: boolean;
527
+ headers?: Record<string, string>;
528
+ compat?: ModelSpec<Api>["compat"];
529
+ contextPromotionTarget?: string;
530
+ compactionModel?: string;
531
+ remoteCompaction?: RemoteCompactionConfig<Api>;
532
+ premiumMultiplier?: number;
533
+ }
534
+
535
+ /**
536
+ * How a patch treats the base model's transport metadata (headers/compat):
537
+ * - `merge`: fold the patch into the base's (modelOverrides semantics).
538
+ * - `replace`: the patch owns transport wholesale — same-id custom definitions
539
+ * already folded provider-level headers/compat in during parsing, so bundled
540
+ * transport metadata must not be re-merged (see `#mergeCustomModels`).
541
+ */
542
+ type ModelTransportPolicy = "merge" | "replace";
543
+
544
+ /**
545
+ * Credential-aware model projection supplied by an extension provider. Receives
546
+ * the fully composed catalog and returns the list the host should serve.
547
+ */
548
+ type ModifyModelsHook = (models: Model<Api>[], credentials: OAuthCredentials) => Model<Api>[];
549
+
550
+ function applyModelPatch(base: Model<Api>, patch: ModelPatch, transport: ModelTransportPolicy): Model<Api> {
551
+ const result = { ...base };
552
+ if (patch.name !== undefined) result.name = patch.name;
553
+ if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
554
+ if (patch.thinking !== undefined) result.thinking = patch.thinking;
555
+ if (patch.input !== undefined) result.input = patch.input;
556
+ if (patch.supportsTools !== undefined) result.supportsTools = patch.supportsTools;
557
+ if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
558
+ if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
559
+ if (patch.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = patch.omitMaxOutputTokens;
560
+ if (patch.contextPromotionTarget !== undefined) result.contextPromotionTarget = patch.contextPromotionTarget;
561
+ if (patch.compactionModel !== undefined) result.compactionModel = patch.compactionModel;
562
+ if (patch.remoteCompaction !== undefined) {
563
+ result.remoteCompaction = mergeRemoteCompactionConfig(base.remoteCompaction, patch.remoteCompaction);
564
+ }
565
+ if (patch.premiumMultiplier !== undefined) result.premiumMultiplier = patch.premiumMultiplier;
566
+ if (patch.cost) {
567
+ result.cost = {
568
+ input: patch.cost.input ?? base.cost.input,
569
+ output: patch.cost.output ?? base.cost.output,
570
+ cacheRead: patch.cost.cacheRead ?? base.cost.cacheRead,
571
+ cacheWrite: patch.cost.cacheWrite ?? base.cost.cacheWrite,
572
+ };
573
+ }
574
+ let compat: ModelSpec<Api>["compat"];
575
+ if (transport === "merge") {
576
+ if (patch.headers) {
577
+ result.headers = { ...base.headers, ...patch.headers };
578
+ }
579
+ compat = mergeCompat(base.compatConfig, patch.compat);
580
+ } else {
581
+ result.headers = patch.headers;
582
+ compat = patch.compat;
583
+ }
584
+ const built = buildModel({ ...result, compat } as ModelSpec<Api>);
585
+ if (patch.thinking !== undefined && built.thinking !== undefined) {
586
+ // Config-authored capability metadata owns the explicit surface; build
587
+ // first so non-reasoning and wire-disabled models still suppress it.
588
+ built.thinking = patch.thinking;
589
+ }
590
+ return built;
591
+ }
592
+
593
+ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {
594
+ return applyModelPatch(model, override as ModelPatch, "merge");
595
+ }
596
+
597
+ interface CustomModelDefinitionLike extends ModelPatch {
598
+ id: string;
599
+ api?: Api;
600
+ baseUrl?: string;
601
+ cost?: Model<Api>["cost"];
602
+ }
603
+
604
+ interface CustomModelBuildOptions {
605
+ useDefaults: boolean;
606
+ }
607
+
608
+ interface CustomModelOverlay extends ModelPatch {
609
+ id: string;
610
+ provider: string;
611
+ api: Api;
612
+ baseUrl: string;
613
+ cost?: Model<Api>["cost"];
614
+ isOAuth?: boolean;
615
+ }
616
+
617
+ function mergeCustomModelHeaders(
618
+ providerHeaders: Record<string, string> | undefined,
619
+ modelHeaders: Record<string, string> | undefined,
620
+ authHeader: boolean | undefined,
621
+ apiKeyConfig: string | undefined,
622
+ ): Record<string, string> | undefined {
623
+ return createLiveConfigHeaders([providerHeaders, modelHeaders], { authHeader, apiKeyConfig });
624
+ }
625
+
626
+ function mergeAuthHeaderSources(
627
+ sources: readonly HeaderSource[],
628
+ authHeader: boolean | undefined,
629
+ apiKeyConfig: string | undefined,
630
+ ): Record<string, string> | undefined {
631
+ return createLiveConfigHeaders(sources, { authHeader, apiKeyConfig });
632
+ }
633
+
634
+ /**
635
+ * Decide whether a custom-yaml model should force OAuth-style request shaping.
636
+ * - Explicit `auth: oauth` → force on.
637
+ * endpoints are typically Claude-Code-style proxies (e.g. CLIProxyAPI) that expect
638
+ * the cloaked request shape regardless of how the proxy itself is authenticated.
639
+ * - Otherwise → unset.
640
+ */
641
+ function resolveCustomModelIsOAuth(api: Api, providerAuth: ProviderAuthMode | undefined): boolean | undefined {
642
+ if (providerAuth === "oauth") return true;
643
+ if (providerAuth !== undefined) return undefined;
644
+ if (api === "anthropic-messages") return true;
645
+ return undefined;
646
+ }
647
+
648
+ function buildCustomModelOverlay(
649
+ providerName: string,
650
+ providerBaseUrl: string,
651
+ providerApi: Api | undefined,
652
+ providerHeaders: Record<string, string> | undefined,
653
+ providerApiKey: string | undefined,
654
+ authHeader: boolean | undefined,
655
+ providerCompat: ModelSpec<Api>["compat"] | undefined,
656
+ providerAuth: ProviderAuthMode | undefined,
657
+ providerRemoteCompaction: RemoteCompactionConfig<Api> | undefined,
658
+ modelDef: CustomModelDefinitionLike,
659
+ ): CustomModelOverlay | undefined {
660
+ const api = modelDef.api ?? providerApi;
661
+ if (!api) return undefined;
662
+ return {
663
+ id: modelDef.id,
664
+ provider: providerName,
665
+ api,
666
+ baseUrl: modelDef.baseUrl ?? providerBaseUrl,
667
+ name: modelDef.name,
668
+ reasoning: modelDef.reasoning,
669
+ thinking: modelDef.thinking,
670
+ input: modelDef.input,
671
+ supportsTools: modelDef.supportsTools,
672
+ cost: modelDef.cost,
673
+ contextWindow: modelDef.contextWindow,
674
+ maxTokens: modelDef.maxTokens,
675
+ omitMaxOutputTokens: modelDef.omitMaxOutputTokens,
676
+ headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
677
+ compat: mergeCompat(providerCompat, modelDef.compat),
678
+ contextPromotionTarget: modelDef.contextPromotionTarget,
679
+ compactionModel: modelDef.compactionModel,
680
+ remoteCompaction: mergeRemoteCompactionConfig(providerRemoteCompaction, modelDef.remoteCompaction),
681
+ premiumMultiplier: modelDef.premiumMultiplier,
682
+ isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
683
+ };
684
+ }
685
+
686
+ function applyStandaloneCustomModelPolicies(model: CustomModelOverlay): CustomModelOverlay {
687
+ if (model.id !== "gpt-5.4" || model.provider === "github-copilot" || model.contextWindow !== undefined) {
688
+ return model;
689
+ }
690
+ return { ...model, contextWindow: 1_000_000 };
691
+ }
692
+
693
+ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuildOptions): Model<Api> {
694
+ const resolvedModel = options.useDefaults ? applyStandaloneCustomModelPolicies(model) : model;
695
+ const reference = options.useDefaults
696
+ ? resolveModelReference(resolvedModel.id, getBundledModelReferenceIndex())
697
+ : undefined;
698
+ const cost =
699
+ resolvedModel.cost ??
700
+ reference?.cost ??
701
+ (options.useDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
702
+ const input = resolvedModel.input ?? reference?.input ?? (options.useDefaults ? ["text"] : undefined);
703
+ const supportsTools = resolvedModel.supportsTools ?? reference?.supportsTools;
704
+ return buildModel({
705
+ id: resolvedModel.id,
706
+ name: resolvedModel.name ?? (options.useDefaults ? resolvedModel.id : undefined),
707
+ api: resolvedModel.api,
708
+ provider: resolvedModel.provider,
709
+ baseUrl: resolvedModel.baseUrl,
710
+ reasoning: resolvedModel.reasoning ?? reference?.reasoning ?? (options.useDefaults ? false : undefined),
711
+ thinking: inheritReferenceThinking(resolvedModel.thinking, reference, resolvedModel.provider),
712
+ input: input as ("text" | "image")[],
713
+ ...(supportsTools !== undefined ? { supportsTools } : {}),
714
+ cost,
715
+ contextWindow: resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : null),
716
+ maxTokens: resolvedModel.maxTokens ?? reference?.maxTokens ?? (options.useDefaults ? 16384 : null),
717
+ headers: resolvedModel.headers,
718
+ omitMaxOutputTokens: resolvedModel.omitMaxOutputTokens ?? reference?.omitMaxOutputTokens,
719
+ compat: mergeCompat(reference?.compatConfig, resolvedModel.compat),
720
+ contextPromotionTarget: resolvedModel.contextPromotionTarget,
721
+ compactionModel: resolvedModel.compactionModel,
722
+ remoteCompaction: resolvedModel.remoteCompaction,
723
+ premiumMultiplier: resolvedModel.premiumMultiplier,
724
+ isOAuth: resolvedModel.isOAuth,
725
+ } as ModelSpec<Api>);
726
+ }
727
+
728
+ function normalizeSuppressedSelector(
729
+ selector: string,
730
+ hasLiveModel?: (provider: string, id: string) => boolean,
731
+ ): string {
732
+ const trimmed = selector.trim();
733
+ if (!trimmed) return trimmed;
734
+ const parsed = parseModelString(trimmed, {
735
+ allowMaxSuffix: true,
736
+ allowAutoAlias: true,
737
+ isLiteralModelId: (provider, id) => hasLiveModel?.(provider, id) === true,
738
+ });
739
+ if (!parsed) return trimmed;
740
+ // Retired effort-tier variant ids normalize to their collapsed logical id
741
+ // so persisted suppressions keyed by raw member ids still bind.
742
+ const aliasId = resolveVariantAlias(parsed.provider, parsed.id);
743
+ return `${parsed.provider}/${aliasId ?? parsed.id}`;
744
+ }
745
+
746
+ /**
747
+ * Look up a model's override, falling back to entries keyed by retired
748
+ * effort-tier variant ids (models.yml authored before collapsing). A raw key
749
+ * only re-binds when no live model holds that id.
750
+ */
751
+ function resolveModelOverrideWithAliases(
752
+ overrides: Map<string, ModelOverride>,
753
+ model: Model<Api>,
754
+ hasLiveModel: (provider: string, id: string) => boolean,
755
+ ): ModelOverride | undefined {
756
+ const direct = overrides.get(model.id);
757
+ if (direct) return direct;
758
+ for (const rawId of getVariantAliasSources(model.provider, model.id)) {
759
+ if (hasLiveModel(model.provider, rawId)) continue;
760
+ const remapped = overrides.get(rawId);
761
+ if (remapped) {
762
+ logger.debug("model override re-keyed through variant alias", {
763
+ provider: model.provider,
764
+ from: rawId,
765
+ to: model.id,
766
+ });
767
+ return remapped;
768
+ }
769
+ }
770
+ return undefined;
771
+ }
772
+
773
+ function getDisabledProviderIdsFromSettings(): Set<string> {
774
+ try {
775
+ return new Set(settings.get("disabledProviders"));
776
+ } catch {
777
+ return new Set();
778
+ }
779
+ }
780
+
781
+ /** Authentication material returned to legacy extensions for one model request. */
782
+ export type ResolvedRequestAuth =
783
+ | {
784
+ ok: true;
785
+ apiKey?: string;
786
+ headers?: Record<string, string>;
787
+ env?: Record<string, string>;
788
+ }
789
+ | { ok: false; error: string };
790
+
791
+ /**
792
+ * Model registry - loads and manages models, resolves API keys via AuthStorage.
793
+ */
794
+ export class ModelRegistry {
795
+ #models: Model<Api>[] = [];
796
+ #unprojectedModels: Model<Api>[] = [];
797
+ #hasFullSnapshot = false;
798
+ #cachedStandardModels: Model<Api>[] = [];
799
+ #cachedDiscoverableModels: Model<Api>[] = [];
800
+ #cachedAuthoritativeProviders: Set<string> = new Set();
801
+ #internedStaticModels: Map<string, Model<Api>> = new Map();
802
+ #providerLookupSnapshots: Map<string, Model<Api>[]> = new Map();
803
+ #customProviderApiKeys: Map<string, string> = new Map();
804
+ #keylessProviders: Set<string> = new Set();
805
+ #discoverableProviders: DiscoveryProviderConfig[] = [];
806
+ #customModelOverlays: CustomModelOverlay[] = [];
807
+ #providerOverrides: Map<string, ProviderOverride> = new Map();
808
+ #modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
809
+ #configError: ConfigError | undefined = undefined;
810
+ #modelsConfigFile: ConfigFile<ModelsConfig>;
811
+ #lastStaticLoadMtime: number | null = null;
812
+ #registeredProviderSources: Set<string> = new Set();
813
+ #providerDiscoveryStates: Map<string, ProviderDiscoveryState> = new Map();
814
+ #cacheDbPath?: string;
815
+ #suppressedSelectors: Map<string, number> = new Map();
816
+ #backgroundRefresh?: Promise<void>;
817
+ #lastDiscoveryWarnings: Map<string, string> = new Map();
818
+ // Runtime extension model overlays — persist across refresh() cycles so that
819
+ // models registered by extensions survive the model selector's offline reload.
820
+ #runtimeModelOverlays: CustomModelOverlay[] = [];
821
+ #runtimeProviderApiKeys: Map<string, string> = new Map();
822
+ #runtimeProviderOverrides: Map<string, ProviderOverride> = new Map();
823
+ // Credential-aware model projections registered via
824
+ // `registerProvider({ oauth: { modifyModels } })`. Persisted for the same
825
+ // reason as #runtimeModelOverlays: the overlays hold the *pre-projection*
826
+ // definitions, so without re-applying the projection every static reload
827
+ // would silently revert the provider to its unprojected catalog.
828
+ #runtimeModelModifiers: Map<string, ModifyModelsHook> = new Map();
829
+ #lastModelModifierWarnings: Map<string, string> = new Map();
830
+ #runtimeProvidersBySource: Map<string, Set<string>> = new Map();
831
+ #runtimeProviderSourceByName: Map<string, string> = new Map();
832
+ // Runtime model managers registered by extensions via fetchDynamicModels.
833
+ // Keyed by provider name; use the same SQLite cache path as builtins.
834
+ #runtimeModelManagers: Map<string, { options: ModelManagerOptions<Api>; sourceId: string }> = new Map();
835
+ #ignoreLocalModelConfig: boolean;
836
+ #fetch: FetchImpl;
837
+
838
+ #resolveCommandBackedApiKey(provider: string): CommandApiKeyResolution {
839
+ const keyConfig = this.#customProviderApiKeys.get(provider);
840
+ if (!isCommandConfigValue(keyConfig)) return { configured: false };
841
+ const value = resolveConfigValue(keyConfig);
842
+ if (value) {
843
+ this.authStorage.setConfigApiKey(provider, value);
844
+ return { configured: true, value };
845
+ }
846
+ this.authStorage.removeConfigApiKey(provider);
847
+ return { configured: true };
848
+ }
849
+
850
+ #installProviderApiKey(provider: string, keyConfig: string): void {
851
+ this.#customProviderApiKeys.set(provider, keyConfig);
852
+ const resolved = resolveConfigValue(keyConfig);
853
+ if (resolved) {
854
+ this.authStorage.setConfigApiKey(provider, resolved);
855
+ } else if (isCommandConfigValue(keyConfig)) {
856
+ this.authStorage.removeConfigApiKey(provider);
857
+ }
858
+ }
859
+
860
+ /**
861
+ * @param authStorage - Auth storage for API key resolution
862
+ *
863
+ * Sync constructor — eagerly loads config (including migrations), cache
864
+ * metadata, and custom models. Bundled providers are enriched selectively
865
+ * when synchronous callers query them. Production boot paths SHOULD prefer
866
+ * {@link ModelRegistry.create} so the YAML/JSONC migration step lands off the
867
+ * event loop's hot path before the first `tryLoad()` runs.
868
+ */
869
+ constructor(
870
+ readonly authStorage: AuthStorage,
871
+ modelsPath?: string,
872
+ options?: {
873
+ /**
874
+ * Gateway mode: ignore local `models.yml` entirely (provider overrides,
875
+ * config API keys, custom models, custom discovery). A broker-backed
876
+ * gateway serves only bundled + broker-discovered catalog metadata and
877
+ * must never apply client-side credential or routing overrides.
878
+ */
879
+ ignoreLocalModelConfig?: boolean;
880
+ fetch?: FetchImpl;
881
+ },
882
+ ) {
883
+ this.#ignoreLocalModelConfig = options?.ignoreLocalModelConfig ?? false;
884
+ this.#fetch =
885
+ options?.fetch ??
886
+ (isBunTestRuntime()
887
+ ? () => Promise.reject(new Error("network disabled in model-registry runtime test"))
888
+ : wrapFetchForExtraCa(fetch));
889
+ this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
890
+ this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
891
+ // Set up fallback resolver for custom provider API keys
892
+ this.authStorage.setFallbackResolver(provider => {
893
+ const keyConfig = this.#customProviderApiKeys.get(provider);
894
+ if (!keyConfig) return undefined;
895
+ return resolveConfigValue(keyConfig);
896
+ });
897
+ // Load config and cache-backed layers synchronously in the constructor.
898
+ this.#loadModels();
899
+ }
900
+
901
+ /**
902
+ * Reload models from disk (built-in + custom config).
903
+ */
904
+ async refresh(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
905
+ this.#reloadStaticModels();
906
+ this.#suppressedSelectors.clear();
907
+ await this.#refreshRuntimeDiscoveries(strategy);
908
+ }
909
+
910
+ refreshInBackground(strategy: ModelRefreshStrategy = "online-if-uncached"): void {
911
+ if (this.#backgroundRefresh) {
912
+ return;
913
+ }
914
+ const refreshPromise = this.refresh(strategy)
915
+ .catch(error => {
916
+ logger.warn("background model refresh failed", {
917
+ error: error instanceof Error ? error.message : String(error),
918
+ });
919
+ })
920
+ .finally(() => {
921
+ if (this.#backgroundRefresh === refreshPromise) {
922
+ this.#backgroundRefresh = undefined;
923
+ }
924
+ });
925
+ this.#backgroundRefresh = refreshPromise;
926
+ }
927
+
928
+ /**
929
+ * Wait for any in-flight background model discovery to settle.
930
+ *
931
+ * Background discovery started by {@link refreshInBackground} is
932
+ * fire-and-forget; RPC consumers (e.g. `get_available_models`,
933
+ * `set_model`) and deferred `--model` resolution that read the registry
934
+ * immediately after session creation can otherwise observe a partial
935
+ * catalog before discovery-backed providers have populated `#models`.
936
+ * Awaiting the tracked promise ensures the response reflects every
937
+ * configured provider once the initial background refresh resolves.
938
+ *
939
+ * No-op when no refresh is in flight (`#backgroundRefresh` cleared in the
940
+ * `finally` of `refreshInBackground` on completion). Resolves immediately
941
+ * in that case so already-warm sessions are unaffected. Discovery errors
942
+ * remain swallowed by `refreshInBackground`'s existing `.catch`.
943
+ */
944
+ async awaitBackgroundRefresh(): Promise<void> {
945
+ if (this.#backgroundRefresh) {
946
+ await this.#backgroundRefresh;
947
+ }
948
+ }
949
+
950
+ async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
951
+ this.#reloadStaticModels();
952
+ for (const selector of this.#suppressedSelectors.keys()) {
953
+ if (selector.startsWith(`${providerId}/`)) {
954
+ this.#suppressedSelectors.delete(selector);
955
+ }
956
+ }
957
+ await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]));
958
+ // #reloadStaticModels above may have rebuilt #models from static sources,
959
+ // dropping models previously discovered by OTHER runtime providers (their
960
+ // fetchDynamicModels results live only in #models + the SQLite cache, not
961
+ // in #loadModels' static inputs). Restore them from cache with the default
962
+ // online-if-uncached strategy: no network while their cached row is
963
+ // fresh, so the scoped refresh above stays the only forced fetch.
964
+ const otherRuntimeProviderIds = new Set(
965
+ [...this.#runtimeModelManagers.keys()].filter(runtimeId => runtimeId !== providerId),
966
+ );
967
+ if (otherRuntimeProviderIds.size > 0) {
968
+ await this.#refreshRuntimeDiscoveries("online-if-uncached", otherRuntimeProviderIds);
969
+ }
970
+ }
971
+
972
+ /**
973
+ * Refresh dynamic metadata that can appear only after a local model loads.
974
+ */
975
+ async refreshSelectedModelMetadata(model: Model<Api>): Promise<Model<Api>> {
976
+ const llamaCppDiscoveryConfig = this.#discoverableProviders.find(
977
+ providerConfig => providerConfig.provider === model.provider && providerConfig.discovery.type === "llama.cpp",
978
+ );
979
+ if (!llamaCppDiscoveryConfig) {
980
+ return model;
981
+ }
982
+ this.#ensureFullSnapshot();
983
+ const runtimeMetadata = await discoverLlamaCppModelRuntimeMetadata(
984
+ model,
985
+ this.#nonResolvingDiscoveryContext(),
986
+ llamaCppDiscoveryConfig.discovery.timeoutMs,
987
+ );
988
+ if (runtimeMetadata === undefined) {
989
+ return this.find(model.provider, model.id) ?? model;
990
+ }
991
+ const { contextWindow, maxTokens, input } = runtimeMetadata;
992
+ const current = this.find(model.provider, model.id) ?? model;
993
+ const override = this.#resolveLiveModelOverride(current);
994
+ const customModel = this.#resolveLiveCustomModelOverlay(current);
995
+ const patch: ModelPatch = {};
996
+ if (
997
+ contextWindow !== undefined &&
998
+ override?.contextWindow === undefined &&
999
+ customModel?.contextWindow === undefined &&
1000
+ current.contextWindow !== contextWindow
1001
+ ) {
1002
+ patch.contextWindow = contextWindow;
1003
+ }
1004
+ const effectiveContextWindow =
1005
+ override?.contextWindow ??
1006
+ customModel?.contextWindow ??
1007
+ patch.contextWindow ??
1008
+ current.contextWindow ??
1009
+ contextWindow;
1010
+ if (maxTokens !== undefined && effectiveContextWindow !== undefined) {
1011
+ const effectiveMaxTokens = Math.min(maxTokens, effectiveContextWindow);
1012
+ if (
1013
+ override?.maxTokens === undefined &&
1014
+ customModel?.maxTokens === undefined &&
1015
+ current.maxTokens !== effectiveMaxTokens
1016
+ ) {
1017
+ patch.maxTokens = effectiveMaxTokens;
1018
+ }
1019
+ }
1020
+ if (
1021
+ input !== undefined &&
1022
+ override?.input === undefined &&
1023
+ customModel?.input === undefined &&
1024
+ (current.input.length !== input.length || current.input.some((value, index) => value !== input[index]))
1025
+ ) {
1026
+ patch.input = input;
1027
+ }
1028
+ if (patch.contextWindow === undefined && patch.maxTokens === undefined && patch.input === undefined) {
1029
+ return current;
1030
+ }
1031
+ const unprojected = resolveProviderModelReference(current.provider, current.id, this.#unprojectedModels);
1032
+ if (unprojected) {
1033
+ const patchedBase = applyModelPatch(unprojected, patch, "merge");
1034
+ this.#unprojectedModels = this.#unprojectedModels.map(candidate =>
1035
+ candidate.provider === unprojected.provider && candidate.id === unprojected.id ? patchedBase : candidate,
1036
+ );
1037
+ this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
1038
+ return resolveProviderModelReference(current.provider, current.id, this.#models) ?? patchedBase;
1039
+ }
1040
+ const patched = applyModelPatch(current, patch, "merge");
1041
+ this.#models = this.#models.map(candidate =>
1042
+ candidate.provider === current.provider && candidate.id === current.id ? patched : candidate,
1043
+ );
1044
+ return patched;
1045
+ }
1046
+
1047
+ /**
1048
+ * Discover models for providers registered at runtime via `fetchDynamicModels`
1049
+ * (extension providers). Merges the discovered catalog into the existing model
1050
+ * set without reloading static models, so dynamically-discovered models from
1051
+ * other providers are preserved. No-op when no runtime providers are registered.
1052
+ *
1053
+ * Drives the same SQLite model cache as built-in providers, so the default
1054
+ * `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
1055
+ */
1056
+ async refreshRuntimeProviders(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
1057
+ if (this.#runtimeModelManagers.size === 0) {
1058
+ return;
1059
+ }
1060
+ await this.#refreshRuntimeDiscoveries(strategy, new Set(this.#runtimeModelManagers.keys()));
1061
+ }
1062
+
1063
+ #reloadStaticModels(): void {
1064
+ const currentMtime = this.#modelsConfigFile.getMtimeMs();
1065
+ if (currentMtime !== null && currentMtime === this.#lastStaticLoadMtime) {
1066
+ // Models config unchanged since last load; reloading would be redundant.
1067
+ return;
1068
+ }
1069
+ this.#modelsConfigFile.invalidate();
1070
+ this.#customProviderApiKeys.clear();
1071
+ this.#keylessProviders.clear();
1072
+ this.#discoverableProviders = [];
1073
+ // Drop config-sourced apiKeys from AuthStorage before reload; entries
1074
+ // removed from models.yml must actually disappear from the resolver, not
1075
+ // linger from the previous parse. The post-load setters below repopulate.
1076
+ this.authStorage.clearConfigApiKeys();
1077
+ // Restore runtime API keys before #loadModels — survives because
1078
+ // #loadModels only calls .set() on #customProviderApiKeys, never reassigns it.
1079
+ for (const [k, v] of this.#runtimeProviderApiKeys) {
1080
+ this.#installProviderApiKey(k, v);
1081
+ }
1082
+ this.#providerOverrides.clear();
1083
+ this.#modelOverrides.clear();
1084
+ this.#configError = undefined;
1085
+ this.#providerDiscoveryStates.clear();
1086
+ this.#loadModels();
1087
+ }
1088
+
1089
+ /**
1090
+ * Get any error from loading custom models config (undefined if no error).
1091
+ */
1092
+ getError(): ConfigError | undefined {
1093
+ return this.#configError;
1094
+ }
1095
+
1096
+ #loadModels() {
1097
+ this.#resetStaticComposition();
1098
+ // Load custom config first (to know which providers to override).
1099
+ const {
1100
+ models: customModels = [],
1101
+ overrides = new Map(),
1102
+ modelOverrides = new Map(),
1103
+ keylessProviders = new Set(),
1104
+ discoverableProviders = [],
1105
+ configuredProviders = new Set(),
1106
+ error: configError,
1107
+ } = this.#loadCustomModels();
1108
+ this.#configError = configError;
1109
+ this.#keylessProviders = keylessProviders;
1110
+ this.#discoverableProviders = discoverableProviders;
1111
+ this.#customModelOverlays = customModels;
1112
+ this.#providerOverrides = overrides;
1113
+ this.#modelOverrides = modelOverrides;
1114
+
1115
+ this.#addImplicitDiscoverableProviders(configuredProviders);
1116
+ const cachedStandardResult = this.#loadCachedStandardProviderModels();
1117
+ this.#cachedStandardModels = this.#applyHardcodedModelPolicies(cachedStandardResult.models);
1118
+ this.#cachedDiscoverableModels = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
1119
+ // Only drop bundled fallback models when the cached project-catalog row is
1120
+ // itself fresh AND authoritative. A stale or non-authoritative snapshot
1121
+ // (e.g. after ADC discovery failure rewrote the row with authoritative=0)
1122
+ // must not strip bundled Vertex Gemini entries — that would leave only the
1123
+ // stale project-scoped rows in API-key-only environments.
1124
+ this.#cachedAuthoritativeProviders = new Set<string>();
1125
+ for (const provider of providersWithAuthoritativeProjectCatalog(this.#cachedStandardModels)) {
1126
+ if (cachedStandardResult.authoritativeFreshProviders.has(provider)) {
1127
+ this.#cachedAuthoritativeProviders.add(provider);
1128
+ }
1129
+ }
1130
+ for (const provider of cachedStandardResult.authoritativeFreshProviders) {
1131
+ if (AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS.has(provider)) {
1132
+ this.#cachedAuthoritativeProviders.add(provider);
1133
+ }
1134
+ }
1135
+ this.#lastStaticLoadMtime = this.#modelsConfigFile.getMtimeMs();
1136
+ }
1137
+
1138
+ #resetStaticComposition(): void {
1139
+ this.#models = [];
1140
+ this.#unprojectedModels = [];
1141
+ this.#hasFullSnapshot = false;
1142
+ this.#internedStaticModels.clear();
1143
+ this.#providerLookupSnapshots.clear();
1144
+ }
1145
+
1146
+ #knownStaticProviders(): string[] {
1147
+ const providers = new Set<string>(getBundledProviders());
1148
+ for (const model of this.#cachedStandardModels) providers.add(model.provider);
1149
+ for (const model of this.#cachedDiscoverableModels) providers.add(model.provider);
1150
+ for (const model of this.#customModelOverlays) providers.add(model.provider);
1151
+ for (const model of this.#runtimeModelOverlays) providers.add(model.provider);
1152
+ return [...providers];
1153
+ }
1154
+
1155
+ #internStaticModels(models: Model<Api>[]): Model<Api>[] {
1156
+ return models.map(model => {
1157
+ const key = `${model.provider}\u0000${model.id}`;
1158
+ const interned = this.#internedStaticModels.get(key);
1159
+ if (interned) return interned;
1160
+ this.#internedStaticModels.set(key, model);
1161
+ return model;
1162
+ });
1163
+ }
1164
+
1165
+ /**
1166
+ * Re-apply the credential-aware projections registered by extension providers.
1167
+ *
1168
+ * Runtime overlays hold the pre-projection definitions, so the registry keeps
1169
+ * those definitions separate from `#models` and reruns the ordered hooks after
1170
+ * every catalog rebuild. Otherwise an offline refresh silently restores the
1171
+ * provider's placeholder catalog.
1172
+ *
1173
+ * A throwing hook falls back to the catalog produced by earlier hooks instead
1174
+ * of failing the whole composition; one bad extension must not empty the
1175
+ * registry. The failure is logged (deduped per provider) so it is not silent.
1176
+ * Each hook receives a deep clone because the public contract permits
1177
+ * mutation of both the array and its model records before returning.
1178
+ */
1179
+ #applyRuntimeModelModifiers(models: Model<Api>[]): Model<Api>[] {
1180
+ if (this.#runtimeModelModifiers.size === 0) return models;
1181
+ let projected = models;
1182
+ for (const [providerName, modifyModels] of this.#runtimeModelModifiers) {
1183
+ const credential = this.authStorage.getOAuthCredential(providerName);
1184
+ if (!credential) continue;
1185
+ try {
1186
+ projected = modifyModels(structuredClone(projected), credential);
1187
+ } catch (error) {
1188
+ this.#warnModelModifierFailure(providerName, error instanceof Error ? error.message : String(error));
1189
+ }
1190
+ }
1191
+ return projected;
1192
+ }
1193
+
1194
+ /**
1195
+ * Dedup key is separate from `#lastDiscoveryWarnings` so a repeated modifier
1196
+ * failure cannot mask a subsequent discovery failure for the same provider.
1197
+ */
1198
+ #warnModelModifierFailure(provider: string, error: string): void {
1199
+ if (this.#lastModelModifierWarnings.get(provider) === error) return;
1200
+ this.#lastModelModifierWarnings.set(provider, error);
1201
+ logger.warn("extension model projection failed; serving unprojected catalog", { provider, error });
1202
+ }
1203
+
1204
+ #composeUnprojectedStaticModels(providerFilter?: ReadonlySet<string>): Model<Api>[] {
1205
+ const select = <T extends { provider: string }>(models: readonly T[]): T[] =>
1206
+ providerFilter ? models.filter(model => providerFilter.has(model.provider)) : [...models];
1207
+ let builtInModels = this.#applyHardcodedModelPolicies(
1208
+ this.#loadBuiltInModels(this.#providerOverrides, providerFilter),
1209
+ );
1210
+ if (this.#cachedAuthoritativeProviders.size > 0) {
1211
+ builtInModels = dropProviderModels(builtInModels, this.#cachedAuthoritativeProviders);
1212
+ }
1213
+ const resolvedDefaults = this.#mergeResolvedModels(
1214
+ this.#mergeResolvedModels(builtInModels, select(this.#cachedStandardModels)),
1215
+ select(this.#cachedDiscoverableModels),
1216
+ );
1217
+ const withConfigModels = this.#mergeCustomModels(resolvedDefaults, select(this.#customModelOverlays));
1218
+ const combined = this.#mergeCustomModels(withConfigModels, select(this.#runtimeModelOverlays));
1219
+ const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
1220
+ return this.#applyLlamaCppQwenThinkingToModels(this.#applyRuntimeProviderOverrides(withModelOverrides));
1221
+ }
1222
+
1223
+ #composeStaticModels(providerFilter?: ReadonlySet<string>): Model<Api>[] {
1224
+ // A modifier is a whole-catalog transform. Build and project the full catalog
1225
+ // before narrowing a lazy lookup, matching getAll() followed by filtering.
1226
+ const projectFullCatalog = providerFilter !== undefined && this.#runtimeModelModifiers.size > 0;
1227
+ const unprojected = this.#composeUnprojectedStaticModels(projectFullCatalog ? undefined : providerFilter);
1228
+ const projected = this.#applyRuntimeModelModifiers(unprojected);
1229
+ const selected = projectFullCatalog ? projected.filter(model => providerFilter.has(model.provider)) : projected;
1230
+ return this.#internStaticModels(selected);
1231
+ }
1232
+
1233
+ #ensureFullSnapshot(): Model<Api>[] {
1234
+ if (!this.#hasFullSnapshot) {
1235
+ this.#unprojectedModels = this.#composeUnprojectedStaticModels();
1236
+ this.#models = this.#internStaticModels(this.#applyRuntimeModelModifiers(this.#unprojectedModels));
1237
+ this.#hasFullSnapshot = true;
1238
+ this.#providerLookupSnapshots.clear();
1239
+ }
1240
+ return this.#models;
1241
+ }
1242
+
1243
+ /** Load built-in models, applying provider-level overrides only.
1244
+ * Per-model overrides are applied later by #applyModelOverrides. */
1245
+ #loadBuiltInModels(overrides: Map<string, ProviderOverride>, providerFilter?: ReadonlySet<string>): Model<Api>[] {
1246
+ return getBundledProviders().flatMap(provider => {
1247
+ if (providerFilter && !providerFilter.has(provider)) return [];
1248
+ const models = getBundledModels(provider as Parameters<typeof getBundledModels>[0]) as Model<Api>[];
1249
+ const providerOverride = overrides.get(provider);
1250
+
1251
+ return models.map(m => {
1252
+ if (!providerOverride) return m;
1253
+ const withTransportOverride = this.#applyProviderTransportOverride(m, providerOverride);
1254
+ return buildModel({
1255
+ ...withTransportOverride,
1256
+ compat: mergeCompat(m.compatConfig, providerOverride.compat),
1257
+ } as ModelSpec<Api>);
1258
+ });
1259
+ });
1260
+ }
1261
+
1262
+ #mergeResolvedModels(baseModels: Model<Api>[], replacementModels: Model<Api>[]): Model<Api>[] {
1263
+ return mergeByModelKey(baseModels, replacementModels, (existing, replacementModel) => {
1264
+ if (!existing) return replacementModel;
1265
+ const supportsTools = replacementModel.supportsTools ?? existing.supportsTools;
1266
+ return {
1267
+ ...replacementModel,
1268
+ contextWindow: replacementModel.contextWindow ?? existing.contextWindow,
1269
+ maxTokens: replacementModel.maxTokens ?? existing.maxTokens,
1270
+ omitMaxOutputTokens: replacementModel.omitMaxOutputTokens ?? existing.omitMaxOutputTokens,
1271
+ ...(supportsTools !== undefined ? { supportsTools } : {}),
1272
+ };
1273
+ });
1274
+ }
1275
+
1276
+ /** Merge custom models with built-in, replacing by provider+id match */
1277
+ #mergeCustomModels(builtInModels: Model<Api>[], customModels: CustomModelOverlay[]): Model<Api>[] {
1278
+ return mergeByModelKey(builtInModels, customModels, (existingModel, customModel) => {
1279
+ if (!existingModel) return finalizeCustomModel(customModel, { useDefaults: true });
1280
+ // Same-id custom definitions replace bundled transport behavior, so the
1281
+ // patch is applied with the `replace` transport policy.
1282
+ return applyModelPatch(
1283
+ {
1284
+ ...existingModel,
1285
+ id: customModel.id,
1286
+ provider: customModel.provider,
1287
+ api: customModel.api,
1288
+ baseUrl: customModel.baseUrl,
1289
+ },
1290
+ customModel,
1291
+ "replace",
1292
+ );
1293
+ });
1294
+ }
1295
+
1296
+ #descriptorBaseUrl(providerId: string): string | undefined {
1297
+ return (
1298
+ this.#runtimeProviderOverrides.get(providerId)?.baseUrl ??
1299
+ this.#providerOverrides.get(providerId)?.baseUrl ??
1300
+ (this.#hasFullSnapshot ? this.getProviderBaseUrl(providerId) : undefined)
1301
+ );
1302
+ }
1303
+
1304
+ #resolveStartupModelCacheProviderId(providerId: string): string {
1305
+ const baseUrl =
1306
+ this.#runtimeProviderOverrides.get(providerId)?.baseUrl ??
1307
+ this.#providerOverrides.get(providerId)?.baseUrl ??
1308
+ (this.#hasFullSnapshot ? this.getProviderBaseUrl(providerId) : undefined);
1309
+ return resolveModelCacheProviderId(providerId, { baseUrl });
1310
+ }
1311
+
1312
+ #loadCachedStandardProviderModels(): { models: Model<Api>[]; authoritativeFreshProviders: Set<string> } {
1313
+ const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(provider => provider.provider));
1314
+ const cachedModels: Model<Api>[] = [];
1315
+ const authoritativeFreshProviders = new Set<string>();
1316
+ for (const providerId of STARTUP_MODEL_CACHE_PROVIDER_IDS) {
1317
+ if (configuredDiscoveryProviders.has(providerId)) {
1318
+ continue;
1319
+ }
1320
+ const cacheProviderId = this.#resolveStartupModelCacheProviderId(providerId);
1321
+ const cache = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
1322
+ if (!cache) {
1323
+ continue;
1324
+ }
1325
+ if (cache.fresh && cache.authoritative) {
1326
+ authoritativeFreshProviders.add(providerId);
1327
+ }
1328
+ // The v10 model cache never persists request headers (#5780): restore
1329
+ // them from the bundled static catalog, and drop cached rows whose
1330
+ // headers cannot be rebuilt so the bundled fallback (which still
1331
+ // carries its headers) wins the startup merge instead of a cached
1332
+ // model with required transport headers missing.
1333
+ const omittedHeaderIds = new Set(cache.headerOmittedModelIds);
1334
+ const unrestorableHeaderIds = new Set(cache.unrestorableHeaderModelIds);
1335
+ const bundledById =
1336
+ omittedHeaderIds.size > 0
1337
+ ? new Map(
1338
+ (getBundledModels(providerId as Parameters<typeof getBundledModels>[0]) as Model<Api>[]).map(
1339
+ bundledModel => [bundledModel.id, bundledModel],
1340
+ ),
1341
+ )
1342
+ : undefined;
1343
+ const models: ModelSpec<Api>[] = [];
1344
+ for (const cachedModel of cache.models) {
1345
+ const spec = cachedModel.provider === providerId ? cachedModel : { ...cachedModel, provider: providerId };
1346
+ if (!omittedHeaderIds.has(spec.id)) {
1347
+ models.push(spec);
1348
+ continue;
1349
+ }
1350
+ // Current unrestorable markers prove that neither same-id nor
1351
+ // request-model bundled headers matched the live model. Only markers
1352
+ // from the old id-only writer may recover through `requestModelId`.
1353
+ const unrestorable = unrestorableHeaderIds.has(spec.id);
1354
+ const bundledHeaders = (
1355
+ unrestorable
1356
+ ? cache.legacyHeaderRestoreMarkers && spec.requestModelId
1357
+ ? bundledById?.get(spec.requestModelId)
1358
+ : undefined
1359
+ : (bundledById?.get(spec.id) ??
1360
+ (spec.requestModelId ? bundledById?.get(spec.requestModelId) : undefined))
1361
+ )?.headers;
1362
+ if (!bundledHeaders) continue;
1363
+ models.push({ ...spec, headers: bundledHeaders });
1364
+ }
1365
+ const providerOverride = this.#providerOverrides.get(providerId);
1366
+ const withTransport = providerOverride
1367
+ ? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
1368
+ : models;
1369
+ const withCompat = providerOverride?.compat
1370
+ ? withTransport.map(model =>
1371
+ buildModel({
1372
+ ...model,
1373
+ compat: mergeCompat(model.compat, providerOverride.compat),
1374
+ } as ModelSpec<Api>),
1375
+ )
1376
+ : withTransport.map(model => buildModel(model));
1377
+ cachedModels.push(...this.#applyProviderModelOverrides(providerId, withCompat));
1378
+ }
1379
+ return { models: cachedModels, authoritativeFreshProviders };
1380
+ }
1381
+
1382
+ #loadCachedDiscoverableModels(): Model<Api>[] {
1383
+ const cachedModels: Model<Api>[] = [];
1384
+ for (const providerConfig of this.#discoverableProviders) {
1385
+ const cache = readModelCache<Api>(
1386
+ this.#configuredDiscoveryCacheProviderId(providerConfig),
1387
+ 24 * 60 * 60 * 1000,
1388
+ Date.now,
1389
+ this.#cacheDbPath,
1390
+ );
1391
+ if (!cache) {
1392
+ this.#providerDiscoveryStates.set(providerConfig.provider, {
1393
+ provider: providerConfig.provider,
1394
+ status: "idle",
1395
+ optional: providerConfig.optional ?? false,
1396
+ stale: false,
1397
+ models: [],
1398
+ });
1399
+ continue;
1400
+ }
1401
+ const configStale = this.#isDiscoveryCacheOlderThanModelsConfig(cache.updatedAt);
1402
+ // Cached rows never persist headers (#5780); models that had live
1403
+ // headers cannot be rebuilt here, so exclude them and mark the
1404
+ // discovery stale to force a refetch instead of returning models
1405
+ // missing required transport headers.
1406
+ const omittedHeaderIds = new Set(cache.headerOmittedModelIds);
1407
+ const usableCacheModels =
1408
+ omittedHeaderIds.size > 0 ? cache.models.filter(model => !omittedHeaderIds.has(model.id)) : cache.models;
1409
+ const models = this.#applyProviderModelOverrides(
1410
+ providerConfig.provider,
1411
+ this.#normalizeDiscoverableModels(
1412
+ providerConfig,
1413
+ this.#applyProviderCompat(
1414
+ providerConfig.compat,
1415
+ usableCacheModels.map(model => buildModel(model)),
1416
+ ),
1417
+ ),
1418
+ );
1419
+ cachedModels.push(...models);
1420
+ this.#providerDiscoveryStates.set(providerConfig.provider, {
1421
+ provider: providerConfig.provider,
1422
+ status: "cached",
1423
+ optional: providerConfig.optional ?? false,
1424
+ stale:
1425
+ providerConfig.discovery.type === "llama.cpp" ||
1426
+ !cache.fresh ||
1427
+ !cache.authoritative ||
1428
+ configStale ||
1429
+ omittedHeaderIds.size > 0,
1430
+ fetchedAt: cache.updatedAt,
1431
+ models: models.map(model => model.id),
1432
+ });
1433
+ }
1434
+ return cachedModels;
1435
+ }
1436
+
1437
+ #applyProviderCompat(compat: ModelSpec<Api>["compat"] | undefined, models: Model<Api>[]): Model<Api>[] {
1438
+ if (!compat) return models;
1439
+ return models.map(model =>
1440
+ buildModel({ ...model, compat: mergeCompat(model.compatConfig, compat) } as ModelSpec<Api>),
1441
+ );
1442
+ }
1443
+
1444
+ #normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
1445
+ const withDecoderMetadata =
1446
+ providerConfig.discovery.type === "ollama" ||
1447
+ providerConfig.discovery.type === "llama.cpp" ||
1448
+ providerConfig.discovery.type === "lm-studio"
1449
+ ? models.map(model =>
1450
+ buildModel({ ...model, imageInputDecoder: "stb", compat: model.compatConfig } as ModelSpec<Api>),
1451
+ )
1452
+ : models;
1453
+
1454
+ const withRemoteCompaction = providerConfig.remoteCompaction
1455
+ ? withDecoderMetadata.map(model =>
1456
+ buildModel({
1457
+ ...model,
1458
+ remoteCompaction: mergeProviderRemoteCompactionConfig(
1459
+ model.remoteCompaction,
1460
+ providerConfig.remoteCompaction,
1461
+ ),
1462
+ compat: model.compatConfig,
1463
+ } as ModelSpec<Api>),
1464
+ )
1465
+ : withDecoderMetadata;
1466
+
1467
+ if (providerConfig.provider !== "ollama" || providerConfig.api !== "openai-responses") {
1468
+ return withRemoteCompaction;
1469
+ }
1470
+
1471
+ const contextLengthOverride = getOllamaContextLengthOverride();
1472
+ return withRemoteCompaction.map(model => {
1473
+ const normalized =
1474
+ model.api === "openai-completions"
1475
+ ? buildModel({
1476
+ ...model,
1477
+ api: "openai-responses" as const,
1478
+ compat: model.compatConfig,
1479
+ } as ModelSpec<Api>)
1480
+ : model;
1481
+ if (contextLengthOverride === undefined) {
1482
+ return normalized;
1483
+ }
1484
+ return {
1485
+ ...normalized,
1486
+ contextWindow: contextLengthOverride,
1487
+ maxTokens: Math.min(contextLengthOverride, DISCOVERY_DEFAULT_MAX_TOKENS),
1488
+ };
1489
+ });
1490
+ }
1491
+
1492
+ #addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
1493
+ const disabledProviders = getDisabledProviderIdsFromSettings();
1494
+ if (!configuredProviders.has("ollama") && !disabledProviders.has("ollama")) {
1495
+ this.#discoverableProviders.push({
1496
+ provider: "ollama",
1497
+ api: "openai-responses",
1498
+ baseUrl: getImplicitOllamaBaseUrl(),
1499
+ discovery: { type: "ollama" },
1500
+ optional: true,
1501
+ });
1502
+ this.#keylessProviders.add("ollama");
1503
+ }
1504
+ if (!configuredProviders.has("llama.cpp") && !disabledProviders.has("llama.cpp")) {
1505
+ this.#discoverableProviders.push({
1506
+ provider: "llama.cpp",
1507
+ api: "openai-responses",
1508
+ baseUrl: Bun.env.LLAMA_CPP_BASE_URL || "http://127.0.0.1:8080",
1509
+ discovery: { type: "llama.cpp" },
1510
+ optional: true,
1511
+ });
1512
+ // Only mark as keyless if no API key is configured
1513
+ if (!this.authStorage.hasAuth("llama.cpp")) {
1514
+ this.#keylessProviders.add("llama.cpp");
1515
+ }
1516
+ }
1517
+ if (!configuredProviders.has("lm-studio") && !disabledProviders.has("lm-studio")) {
1518
+ this.#discoverableProviders.push({
1519
+ provider: "lm-studio",
1520
+ api: "openai-completions",
1521
+ baseUrl: Bun.env.LM_STUDIO_BASE_URL || "http://127.0.0.1:1234/v1",
1522
+ discovery: { type: "lm-studio" },
1523
+ optional: true,
1524
+ });
1525
+ this.#keylessProviders.add("lm-studio");
1526
+ }
1527
+ }
1528
+
1529
+ #loadCustomModels(): CustomModelsResult {
1530
+ // Gateway mode: serve bundled + broker-discovered catalog metadata only.
1531
+ // Local models.yml provider overrides (baseUrl/apiKey/headers/transport),
1532
+ // custom models, custom discovery, and config API keys are all client-side
1533
+ // routing that MUST NOT reach a broker-backed gateway — applying them would
1534
+ // send broker bearers to a configured endpoint, install config keys that
1535
+ // shadow broker credentials (bypassing account pooling/refresh/accounting),
1536
+ // or route a pi-native gateway back into itself.
1537
+ if (this.#ignoreLocalModelConfig) {
1538
+ return {
1539
+ models: [],
1540
+ overrides: new Map(),
1541
+ modelOverrides: new Map(),
1542
+ keylessProviders: new Set(),
1543
+ discoverableProviders: [],
1544
+ configuredProviders: new Set(),
1545
+ found: false,
1546
+ };
1547
+ }
1548
+ const { value, error, status } = this.#modelsConfigFile.tryLoad();
1549
+
1550
+ if (status === "error") {
1551
+ return {
1552
+ models: [],
1553
+ overrides: new Map(),
1554
+ modelOverrides: new Map(),
1555
+ keylessProviders: new Set(),
1556
+ discoverableProviders: [],
1557
+ configuredProviders: new Set(),
1558
+ error,
1559
+ found: true,
1560
+ };
1561
+ } else if (status === "not-found") {
1562
+ return {
1563
+ models: [],
1564
+ overrides: new Map(),
1565
+ modelOverrides: new Map(),
1566
+ keylessProviders: new Set(),
1567
+ discoverableProviders: [],
1568
+ configuredProviders: new Set(),
1569
+ found: false,
1570
+ };
1571
+ }
1572
+
1573
+ const overrides = new Map<string, ProviderOverride>();
1574
+ const allModelOverrides = new Map<string, Map<string, ModelOverride>>();
1575
+ const keylessProviders = new Set<string>();
1576
+ const discoverableProviders: DiscoveryProviderConfig[] = [];
1577
+ const providerEntries = Object.entries(value.providers ?? {});
1578
+ const configuredProviders = new Set(Object.keys(value.providers ?? {}));
1579
+ for (const [providerName, providerConfig] of providerEntries) {
1580
+ const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
1581
+ // Always set overrides when baseUrl/headers/apiKey/authHeader/compat/disableStrictTools/transport are present
1582
+ if (
1583
+ providerConfig.baseUrl ||
1584
+ resolvedProviderHeaders ||
1585
+ providerConfig.apiKey ||
1586
+ providerConfig.authHeader !== undefined ||
1587
+ providerConfig.compat ||
1588
+ providerConfig.disableStrictTools ||
1589
+ providerConfig.remoteCompaction ||
1590
+ providerConfig.transport
1591
+ ) {
1592
+ const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
1593
+ overrides.set(providerName, {
1594
+ baseUrl:
1595
+ providerConfig.discovery?.type === "litellm"
1596
+ ? normalizeLiteLLMDiscoveryBaseUrl(providerConfig.baseUrl)
1597
+ : providerConfig.baseUrl,
1598
+ headers: resolvedProviderHeaders,
1599
+ apiKey: providerConfig.apiKey,
1600
+ authHeader: providerConfig.authHeader,
1601
+ compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1602
+ remoteCompaction: providerConfig.remoteCompaction,
1603
+ transport: providerConfig.transport,
1604
+ });
1605
+ }
1606
+
1607
+ const authMode = (providerConfig.auth ?? "apiKey") as ProviderAuthMode;
1608
+ if (authMode === "none") {
1609
+ keylessProviders.add(providerName);
1610
+ }
1611
+
1612
+ if (providerConfig.discovery && (providerConfig.api || providerConfig.discovery.type === "proxy")) {
1613
+ const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
1614
+ discoverableProviders.push({
1615
+ provider: providerName,
1616
+ // Proxy discovery derives per-model api from /v1/models's
1617
+ // supported_endpoint_types; the provider-level api is only a
1618
+ // fallback for entries that don't advertise one.
1619
+ api: (providerConfig.api ?? "openai-completions") as Api,
1620
+ baseUrl: providerConfig.baseUrl,
1621
+ headers: resolvedProviderHeaders,
1622
+ compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1623
+ remoteCompaction: providerConfig.remoteCompaction,
1624
+ discovery: providerConfig.discovery,
1625
+ optional: false,
1626
+ });
1627
+ }
1628
+
1629
+ // Store API key for fallback resolver AND register as config override
1630
+ // so it wins over OAuth tokens from the broker — when the user pins a
1631
+ // bearer in models.yml (e.g. for an auth-gateway baseUrl), that bearer
1632
+ // must authenticate the outbound request.
1633
+ if (providerConfig.apiKey) {
1634
+ this.#installProviderApiKey(providerName, providerConfig.apiKey);
1635
+ }
1636
+
1637
+ // Parse per-model overrides
1638
+ if (providerConfig.modelOverrides) {
1639
+ const perModel = new Map<string, ModelOverride>();
1640
+ for (const [modelId, override] of Object.entries(providerConfig.modelOverrides)) {
1641
+ perModel.set(
1642
+ modelId,
1643
+ override.headers ? { ...override, headers: resolveConfigHeaders(override.headers) } : override,
1644
+ );
1645
+ }
1646
+ allModelOverrides.set(providerName, perModel);
1647
+ }
1648
+ }
1649
+
1650
+ return {
1651
+ models: this.#parseModels(value),
1652
+ overrides,
1653
+ modelOverrides: allModelOverrides,
1654
+ keylessProviders,
1655
+ discoverableProviders,
1656
+ configuredProviders,
1657
+ found: true,
1658
+ };
1659
+ }
1660
+
1661
+ async #refreshRuntimeDiscoveries(
1662
+ strategy: ModelRefreshStrategy,
1663
+ providerFilter?: ReadonlySet<string>,
1664
+ ): Promise<void> {
1665
+ const disabledProviders = getDisabledProviderIdsFromSettings();
1666
+ const selectedDiscoverableProviders = (
1667
+ providerFilter
1668
+ ? this.#discoverableProviders.filter(provider => providerFilter.has(provider.provider))
1669
+ : this.#discoverableProviders
1670
+ ).filter(provider => !disabledProviders.has(provider.provider));
1671
+ const configuredDiscoveriesPromise =
1672
+ selectedDiscoverableProviders.length === 0
1673
+ ? Promise.resolve<Model<Api>[]>([])
1674
+ : Promise.all(
1675
+ selectedDiscoverableProviders.map(provider => this.#discoverProviderModels(provider, strategy)),
1676
+ ).then(results => results.flat());
1677
+ const [configuredDiscovered, builtInDiscovery] = await Promise.all([
1678
+ configuredDiscoveriesPromise,
1679
+ this.#discoverBuiltInProviderModels(strategy, providerFilter),
1680
+ ]);
1681
+ const discovered = [...configuredDiscovered, ...builtInDiscovery.models];
1682
+ if (discovered.length === 0 && builtInDiscovery.authoritativeProviders.size === 0) {
1683
+ return;
1684
+ }
1685
+ this.#ensureFullSnapshot();
1686
+ const discoveredModels = this.#applyHardcodedModelPolicies(
1687
+ discovered.map(model =>
1688
+ mergeDiscoveredModel(
1689
+ model,
1690
+ resolveProviderModelReference(model.provider, model.id, this.#unprojectedModels),
1691
+ this.#providerOverrides.get(model.provider),
1692
+ ),
1693
+ ),
1694
+ );
1695
+ const authoritativeProviders = providersWithAuthoritativeProjectCatalog(discoveredModels);
1696
+ for (const provider of builtInDiscovery.authoritativeProviders) {
1697
+ authoritativeProviders.add(provider);
1698
+ }
1699
+ const baseModels =
1700
+ authoritativeProviders.size > 0
1701
+ ? dropProviderModels(this.#unprojectedModels, authoritativeProviders)
1702
+ : this.#unprojectedModels;
1703
+ const resolved = this.#mergeResolvedModels(baseModels, discoveredModels);
1704
+ const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
1705
+ const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
1706
+ const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
1707
+ this.#unprojectedModels = this.#applyLlamaCppQwenThinkingToModels(
1708
+ this.#applyRuntimeProviderOverrides(withModelOverrides),
1709
+ );
1710
+ this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
1711
+ }
1712
+
1713
+ #configuredDiscoveryCacheProviderId(providerConfig: DiscoveryProviderConfig): string {
1714
+ if (providerConfig.discovery.type === "ollama") {
1715
+ return resolveOllamaModelCacheProviderId(providerConfig.provider, providerConfig.baseUrl);
1716
+ }
1717
+ if (providerConfig.discovery.type === "openai-models-list") {
1718
+ // context-v3 invalidates rows cached before server-advertised input
1719
+ // modalities were parsed from `/v1/models`; warm v2 rows pinned
1720
+ // vision-capable ids at `input: ["text"]` until a forced refresh.
1721
+ return `${providerConfig.provider}:openai-models-list-context-v3`;
1722
+ }
1723
+ if (providerConfig.discovery.type === "litellm") {
1724
+ // rich-v2 invalidates rows cached before reseller usage-suffix stripping
1725
+ // (stale display names like `MiniMax-M3 (3x usage)`); keep in lockstep
1726
+ // with the catalog package's `litellm:rich-vN` namespace.
1727
+ return `${providerConfig.provider}:litellm-rich-v2`;
1728
+ }
1729
+ return providerConfig.provider;
1730
+ }
1731
+
1732
+ #isDiscoveryCacheOlderThanModelsConfig(cacheUpdatedAt: number): boolean {
1733
+ const configMtime = this.#modelsConfigFile.getMtimeMs();
1734
+ return configMtime !== null && cacheUpdatedAt < Math.floor(configMtime);
1735
+ }
1736
+
1737
+ async #discoverProviderModels(
1738
+ providerConfig: DiscoveryProviderConfig,
1739
+ strategy: ModelRefreshStrategy,
1740
+ ): Promise<Model<Api>[]> {
1741
+ const cacheProviderId = this.#configuredDiscoveryCacheProviderId(providerConfig);
1742
+ const cached = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
1743
+ const cacheOlderThanConfig = cached !== null && this.#isDiscoveryCacheOlderThanModelsConfig(cached.updatedAt);
1744
+ const bypassFreshCache = providerConfig.discovery.type === "llama.cpp" && strategy === "online-if-uncached";
1745
+ const effectiveStrategy =
1746
+ strategy === "online-if-uncached" && (cacheOlderThanConfig || bypassFreshCache) ? "online" : strategy;
1747
+ const requiresAuth = !this.#keylessProviders.has(providerConfig.provider);
1748
+ if (requiresAuth) {
1749
+ const apiKey = await this.#peekApiKeyForProvider(providerConfig.provider);
1750
+ if (!isAuthenticated(apiKey)) {
1751
+ this.#providerDiscoveryStates.set(providerConfig.provider, {
1752
+ provider: providerConfig.provider,
1753
+ status: "unauthenticated",
1754
+ optional: providerConfig.optional ?? false,
1755
+ stale: cached !== null,
1756
+ fetchedAt: cached?.updatedAt,
1757
+ models: cached?.models.map(model => model.id) ?? [],
1758
+ });
1759
+ this.#lastDiscoveryWarnings.delete(providerConfig.provider);
1760
+ return cached
1761
+ ? this.#normalizeDiscoverableModels(
1762
+ providerConfig,
1763
+ cached.models.map(model => buildModel(model)),
1764
+ )
1765
+ : [];
1766
+ }
1767
+ }
1768
+
1769
+ const providerId = providerConfig.provider;
1770
+ let discoveryError: string | undefined;
1771
+ const fetchDynamicModels = async (): Promise<readonly ModelSpec<Api>[] | null> => {
1772
+ try {
1773
+ const models = this.#applyProviderModelOverrides(
1774
+ providerId,
1775
+ await discoverModelsByProviderType(providerConfig, this.#discoveryContext()),
1776
+ );
1777
+ this.#lastDiscoveryWarnings.delete(providerId);
1778
+ return models.map(toModelSpec);
1779
+ } catch (error) {
1780
+ discoveryError = error instanceof Error ? error.message : String(error);
1781
+ return null;
1782
+ }
1783
+ };
1784
+
1785
+ const manager = createModelManager<Api>({
1786
+ providerId,
1787
+ staticModels: [],
1788
+ cacheDbPath: this.#cacheDbPath,
1789
+ cacheProviderId,
1790
+ cacheTtlMs: 24 * 60 * 60 * 1000,
1791
+ fetchDynamicModels,
1792
+ });
1793
+ const result = await manager.refresh(effectiveStrategy);
1794
+ const status = discoveryError
1795
+ ? result.models.length > 0
1796
+ ? "cached"
1797
+ : "unavailable"
1798
+ : effectiveStrategy === "offline"
1799
+ ? cached
1800
+ ? "cached"
1801
+ : "idle"
1802
+ : result.models.length > 0
1803
+ ? "ok"
1804
+ : "empty";
1805
+ this.#providerDiscoveryStates.set(providerId, {
1806
+ provider: providerId,
1807
+ status,
1808
+ optional: providerConfig.optional ?? false,
1809
+ stale: result.stale || status === "cached" || ((cacheOlderThanConfig || bypassFreshCache) && status !== "ok"),
1810
+ fetchedAt: discoveryError ? cached?.updatedAt : Date.now(),
1811
+ models: result.models.map(model => model.id),
1812
+ error: discoveryError,
1813
+ });
1814
+ if (discoveryError) {
1815
+ this.#warnProviderDiscoveryFailure(providerConfig, discoveryError);
1816
+ }
1817
+ return this.#applyProviderModelOverrides(
1818
+ providerId,
1819
+ this.#normalizeDiscoverableModels(
1820
+ providerConfig,
1821
+ this.#applyProviderCompat(providerConfig.compat, result.models),
1822
+ ),
1823
+ );
1824
+ }
1825
+
1826
+ #discoveryContext(): DiscoveryContext {
1827
+ return {
1828
+ fetch: this.#fetch,
1829
+ getBearerApiKeyResolver: async provider => {
1830
+ const apiKey = await this.getApiKeyForProvider(provider);
1831
+ if (!isDiscoveryBearerApiKey(apiKey)) {
1832
+ return undefined;
1833
+ }
1834
+ return this.resolver(provider);
1835
+ },
1836
+ };
1837
+ }
1838
+
1839
+ #nonResolvingDiscoveryContext(): DiscoveryContext {
1840
+ return {
1841
+ fetch: this.#fetch,
1842
+ getBearerApiKeyResolver: async () => undefined,
1843
+ };
1844
+ }
1845
+
1846
+ #warnProviderDiscoveryFailure(providerConfig: DiscoveryProviderConfig, error: string): void {
1847
+ const previous = this.#lastDiscoveryWarnings.get(providerConfig.provider);
1848
+ if (previous === error) {
1849
+ return;
1850
+ }
1851
+ this.#lastDiscoveryWarnings.set(providerConfig.provider, error);
1852
+ logger.warn("model discovery failed for provider", {
1853
+ provider: providerConfig.provider,
1854
+ url: providerConfig.baseUrl,
1855
+ error,
1856
+ });
1857
+ }
1858
+
1859
+ async #discoverBuiltInProviderModels(
1860
+ strategy: ModelRefreshStrategy,
1861
+ providerFilter?: ReadonlySet<string>,
1862
+ ): Promise<BuiltInDiscoveryResult> {
1863
+ // Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
1864
+ const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(p => p.provider));
1865
+ const managerOptions = await this.#collectBuiltInModelManagerOptions(
1866
+ strategy,
1867
+ providerFilter,
1868
+ configuredDiscoveryProviders,
1869
+ );
1870
+ if (managerOptions.length === 0) {
1871
+ return { models: [], authoritativeProviders: new Set() };
1872
+ }
1873
+ const discoveries = await Promise.all(
1874
+ managerOptions.map(options => this.#discoverWithModelManager(options, strategy)),
1875
+ );
1876
+ const authoritativeProviders = new Set<string>();
1877
+ const models: Model<Api>[] = [];
1878
+ for (const discovery of discoveries) {
1879
+ models.push(...discovery.models);
1880
+ for (const provider of discovery.authoritativeProviders) {
1881
+ authoritativeProviders.add(provider);
1882
+ }
1883
+ }
1884
+ return { models, authoritativeProviders };
1885
+ }
1886
+
1887
+ async #resolveBuiltInDiscoveryApiKey(
1888
+ providerId: string,
1889
+ strategy: ModelRefreshStrategy,
1890
+ cacheProviderId: string,
1891
+ authoritative: boolean,
1892
+ ): Promise<string | undefined> {
1893
+ const peekedKey = await this.#peekApiKeyForProvider(providerId);
1894
+ if (isAuthenticated(peekedKey) || strategy === "offline") {
1895
+ return peekedKey;
1896
+ }
1897
+ const oauthCredentials = getOAuthCredentialsForProvider(this.authStorage, providerId);
1898
+ if (oauthCredentials.length === 0) {
1899
+ return peekedKey;
1900
+ }
1901
+ // Authoritative providers prune bundled models only when their manager is
1902
+ // actually constructed, which needs an authenticated key. A fresh cache does
1903
+ // not let us skip the refresh here: with an expired OAuth token peekedKey is
1904
+ // undefined, the manager is never added, and stale bundled models survive the
1905
+ // full cache TTL. So only take the no-refresh shortcut for non-authoritative
1906
+ // providers, whose bundled models stay visible regardless.
1907
+ if (strategy === "online-if-uncached" && !authoritative) {
1908
+ // Mirror shouldFetchRemoteSources: built-in managers use the catalog's
1909
+ // default TTL, so only refresh when the manager will actually fetch.
1910
+ const cache = readModelCache<Api>(
1911
+ cacheProviderId,
1912
+ BUILT_IN_DISCOVERY_CACHE_TTL_MS,
1913
+ Date.now,
1914
+ this.#cacheDbPath,
1915
+ );
1916
+ const cacheAgeMs = cache ? Date.now() - cache.updatedAt : Number.POSITIVE_INFINITY;
1917
+ if (cache?.fresh && (cache.authoritative || cacheAgeMs < BUILT_IN_DISCOVERY_NON_AUTHORITATIVE_RETRY_MS)) {
1918
+ return peekedKey;
1919
+ }
1920
+ }
1921
+ try {
1922
+ return await this.getApiKeyForProvider(providerId);
1923
+ } catch (error) {
1924
+ logger.debug("OAuth refresh failed during model discovery preflight", {
1925
+ provider: providerId,
1926
+ error: error instanceof Error ? error.message : String(error),
1927
+ });
1928
+ return peekedKey;
1929
+ }
1930
+ }
1931
+
1932
+ async #collectBuiltInModelManagerOptions(
1933
+ strategy: ModelRefreshStrategy,
1934
+ providerFilter: ReadonlySet<string> | undefined,
1935
+ configuredDiscoveryProviders: ReadonlySet<string>,
1936
+ ): Promise<ModelManagerOptions<Api>[]> {
1937
+ const specialProviderDescriptors: Array<{
1938
+ providerId: string;
1939
+ authoritative: boolean;
1940
+ resolveKey: (value: string | undefined) => string | undefined;
1941
+ createOptions: (key: string) => ModelManagerOptions<Api>;
1942
+ }> = [
1943
+ {
1944
+ providerId: "google-antigravity",
1945
+ authoritative: false,
1946
+ resolveKey: extractGoogleOAuthToken,
1947
+ createOptions: oauthToken =>
1948
+ googleAntigravityModelManagerOptions({
1949
+ oauthToken,
1950
+ endpoint: this.#descriptorBaseUrl("google-antigravity"),
1951
+ fetch: this.#fetch,
1952
+ }),
1953
+ },
1954
+ {
1955
+ providerId: "google-gemini-cli",
1956
+ authoritative: false,
1957
+ resolveKey: extractGoogleOAuthToken,
1958
+ createOptions: oauthToken =>
1959
+ googleGeminiCliModelManagerOptions({
1960
+ oauthToken,
1961
+ endpoint: this.#descriptorBaseUrl("google-gemini-cli"),
1962
+ fetch: this.#fetch,
1963
+ }),
1964
+ },
1965
+ {
1966
+ providerId: "openai-codex",
1967
+ authoritative: true,
1968
+ resolveKey: value => value,
1969
+ createOptions: accessToken =>
1970
+ openaiCodexModelManagerOptions({
1971
+ resolveAccounts: () => resolveCodexDiscoveryAccounts(this.authStorage, accessToken),
1972
+ fetch: this.#fetch,
1973
+ }),
1974
+ },
1975
+ ];
1976
+ const disabledProviders = getDisabledProviderIdsFromSettings();
1977
+ const standardProviderDescriptors = PROVIDER_DESCRIPTORS.filter(descriptor => {
1978
+ if (disabledProviders.has(descriptor.providerId)) return false;
1979
+ if (configuredDiscoveryProviders.has(descriptor.providerId)) return false;
1980
+ return providerFilter ? providerFilter.has(descriptor.providerId) : true;
1981
+ });
1982
+ const enabledSpecialProviderDescriptors = specialProviderDescriptors.filter(descriptor => {
1983
+ if (disabledProviders.has(descriptor.providerId)) return false;
1984
+ if (configuredDiscoveryProviders.has(descriptor.providerId)) return false;
1985
+ return providerFilter ? providerFilter.has(descriptor.providerId) : true;
1986
+ });
1987
+ const standardProviderKeys = await Promise.all(
1988
+ standardProviderDescriptors.map(descriptor => {
1989
+ const cacheProviderId = this.#resolveStartupModelCacheProviderId(descriptor.providerId);
1990
+ return this.#resolveBuiltInDiscoveryApiKey(
1991
+ descriptor.providerId,
1992
+ strategy,
1993
+ cacheProviderId,
1994
+ descriptor.dynamicModelsAuthoritative ?? false,
1995
+ );
1996
+ }),
1997
+ );
1998
+ const specialKeys = await Promise.all(
1999
+ enabledSpecialProviderDescriptors.map(descriptor =>
2000
+ this.#resolveBuiltInDiscoveryApiKey(
2001
+ descriptor.providerId,
2002
+ strategy,
2003
+ descriptor.providerId,
2004
+ descriptor.authoritative,
2005
+ ),
2006
+ ),
2007
+ );
2008
+ const options: ModelManagerOptions<Api>[] = [];
2009
+ for (let i = 0; i < standardProviderDescriptors.length; i++) {
2010
+ const descriptor = standardProviderDescriptors[i];
2011
+ const apiKey = standardProviderKeys[i];
2012
+ const hasExplicitVllmConfig =
2013
+ descriptor.providerId === "vllm" &&
2014
+ (this.#runtimeProviderOverrides.has(descriptor.providerId) ||
2015
+ this.#providerOverrides.has(descriptor.providerId) ||
2016
+ this.#keylessProviders.has(descriptor.providerId));
2017
+ if (isAuthenticated(apiKey) || descriptor.allowUnauthenticated || hasExplicitVllmConfig) {
2018
+ const discoveryConfig = {
2019
+ apiKey: isDiscoveryBearerApiKey(apiKey) ? apiKey : undefined,
2020
+ baseUrl: this.#descriptorBaseUrl(descriptor.providerId),
2021
+ fetch: this.#fetch,
2022
+ };
2023
+ const preparedConfig =
2024
+ getProviderDefinition(descriptor.providerId)?.prepareModelDiscovery?.(discoveryConfig) ??
2025
+ discoveryConfig;
2026
+ options.push(descriptor.createModelManagerOptions(preparedConfig));
2027
+ }
2028
+ }
2029
+
2030
+ for (let i = 0; i < enabledSpecialProviderDescriptors.length; i++) {
2031
+ const descriptor = enabledSpecialProviderDescriptors[i];
2032
+ const key = descriptor.resolveKey(specialKeys[i]);
2033
+ if (!isAuthenticated(key)) {
2034
+ continue;
2035
+ }
2036
+ options.push(descriptor.createOptions(key));
2037
+ }
2038
+ // Append runtime model managers registered by extensions via fetchDynamicModels.
2039
+ for (const { options: managerOpts } of this.#runtimeModelManagers.values()) {
2040
+ if (
2041
+ !configuredDiscoveryProviders.has(managerOpts.providerId) &&
2042
+ (!providerFilter || providerFilter.has(managerOpts.providerId))
2043
+ ) {
2044
+ options.push(managerOpts);
2045
+ }
2046
+ }
2047
+ return options;
2048
+ }
2049
+
2050
+ async #discoverWithModelManager(
2051
+ options: ModelManagerOptions<Api>,
2052
+ strategy: ModelRefreshStrategy,
2053
+ ): Promise<BuiltInDiscoveryResult> {
2054
+ try {
2055
+ const manager = createModelManager({ ...options, cacheDbPath: this.#cacheDbPath });
2056
+ const result = await manager.refresh(strategy);
2057
+ const models = result.models.map(model =>
2058
+ model.provider === options.providerId ? model : { ...model, provider: options.providerId },
2059
+ );
2060
+ const authoritativeProviders = new Set<string>();
2061
+ if (options.dynamicModelsAuthoritative && !result.stale) {
2062
+ authoritativeProviders.add(options.providerId);
2063
+ }
2064
+ return { models, authoritativeProviders };
2065
+ } catch (error) {
2066
+ logger.warn("model discovery failed for provider", {
2067
+ provider: options.providerId,
2068
+ error: error instanceof Error ? error.message : String(error),
2069
+ });
2070
+ return { models: [], authoritativeProviders: new Set() };
2071
+ }
2072
+ }
2073
+
2074
+ #applyProviderModelOverrides(provider: string, models: Model<Api>[]): Model<Api>[] {
2075
+ const overrides = this.#modelOverrides.get(provider);
2076
+ if (!overrides || overrides.size === 0) return models;
2077
+ let liveIds: Set<string> | null = null;
2078
+ const hasLiveModel = (_provider: string, id: string) => {
2079
+ liveIds ??= new Set(models.map(m => m.id));
2080
+ return liveIds.has(id);
2081
+ };
2082
+ return models.map(model => {
2083
+ const override = resolveModelOverrideWithAliases(overrides, model, hasLiveModel);
2084
+ if (!override) return model;
2085
+ return applyModelOverride(model, override);
2086
+ });
2087
+ }
2088
+
2089
+ // #applyLlamaCppQwenThinkingToModels re-runs applyLlamaCppQwenThinking as the
2090
+ // outermost transform for llama.cpp-provider models, after discovery merges,
2091
+ // cache fallbacks, and provider/transport overrides have run. It is
2092
+ // idempotent, so it restores the routed Qwen model's chat-completions api,
2093
+ // `/v1` runtime base URL, and disable dialect even when a configured `baseUrl`
2094
+ // override (which wins in mergeDiscoveredModel) or a fallback to a pre-fix
2095
+ // cached row would otherwise leave the old spec in place.
2096
+ #applyLlamaCppQwenThinkingToModels(models: Model<Api>[]): Model<Api>[] {
2097
+ const llamaCppProviders = new Set<string>();
2098
+ for (const provider of this.#discoverableProviders) {
2099
+ if (provider.discovery.type === "llama.cpp") llamaCppProviders.add(provider.provider);
2100
+ }
2101
+ if (llamaCppProviders.size === 0) return models;
2102
+ return models.map(model => (llamaCppProviders.has(model.provider) ? applyLlamaCppQwenThinking(model) : model));
2103
+ }
2104
+
2105
+ #mergeProviderOverride(baseOverride: ProviderOverride | undefined, override: ProviderOverride): ProviderOverride {
2106
+ return {
2107
+ baseUrl: override.baseUrl ?? baseOverride?.baseUrl,
2108
+ apiKey: override.apiKey ?? baseOverride?.apiKey,
2109
+ authHeader: override.authHeader ?? baseOverride?.authHeader,
2110
+ headers: override.headers
2111
+ ? createLiveConfigHeaders([baseOverride?.headers, override.headers])
2112
+ : baseOverride?.headers,
2113
+ compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
2114
+ remoteCompaction: mergeRemoteCompactionConfig(baseOverride?.remoteCompaction, override.remoteCompaction),
2115
+ transport: override.transport ?? baseOverride?.transport,
2116
+ };
2117
+ }
2118
+ #applyProviderTransportOverride<
2119
+ T extends { baseUrl?: string; headers?: Record<string, string>; remoteCompaction?: RemoteCompactionConfig<Api> },
2120
+ >(
2121
+ entry: T,
2122
+ override: Pick<
2123
+ ProviderOverride,
2124
+ "baseUrl" | "headers" | "authHeader" | "apiKey" | "remoteCompaction" | "transport"
2125
+ >,
2126
+ ): T {
2127
+ const headers = mergeAuthHeaderSources(
2128
+ override.headers ? [entry.headers, override.headers] : [entry.headers],
2129
+ override.authHeader,
2130
+ override.apiKey,
2131
+ );
2132
+ return {
2133
+ ...entry,
2134
+ baseUrl: override.baseUrl ?? entry.baseUrl,
2135
+ headers,
2136
+ // Preserve the model's existing transport when the override omits one;
2137
+ // providers without a `transport` field keep the default per-API dispatch.
2138
+ ...(override.transport !== undefined ? { transport: override.transport } : {}),
2139
+ remoteCompaction: mergeProviderRemoteCompactionConfig(entry.remoteCompaction, override.remoteCompaction),
2140
+ };
2141
+ }
2142
+ #applyProviderTransportOverrideToModel(
2143
+ model: Model<Api>,
2144
+ override: Pick<
2145
+ ProviderOverride,
2146
+ "baseUrl" | "headers" | "authHeader" | "apiKey" | "remoteCompaction" | "transport"
2147
+ >,
2148
+ ): Model<Api> {
2149
+ return buildModel(this.#applyProviderTransportOverride(toModelSpec(model), override));
2150
+ }
2151
+
2152
+ #applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
2153
+ if (this.#runtimeProviderOverrides.size === 0) return models;
2154
+ return models.map(model => {
2155
+ const override = this.#runtimeProviderOverrides.get(model.provider);
2156
+ if (!override) return model;
2157
+ return this.#applyProviderTransportOverrideToModel(model, override);
2158
+ });
2159
+ }
2160
+ #resolveLiveModelOverride(model: Model<Api>): ModelOverride | undefined {
2161
+ const providerOverrides = this.#modelOverrides.get(model.provider);
2162
+ if (!providerOverrides) return undefined;
2163
+ return resolveModelOverrideWithAliases(
2164
+ providerOverrides,
2165
+ model,
2166
+ (provider, id) => this.find(provider, id) !== undefined,
2167
+ );
2168
+ }
2169
+
2170
+ #resolveLiveCustomModelOverlay(model: Model<Api>): CustomModelOverlay | undefined {
2171
+ return (
2172
+ this.#customModelOverlays.find(overlay => overlay.provider === model.provider && overlay.id === model.id) ??
2173
+ this.#runtimeModelOverlays.find(overlay => overlay.provider === model.provider && overlay.id === model.id)
2174
+ );
2175
+ }
2176
+
2177
+ #applyModelOverrides(models: Model<Api>[], overrides: Map<string, Map<string, ModelOverride>>): Model<Api>[] {
2178
+ if (overrides.size === 0) return models;
2179
+ let liveKeys: Set<string> | null = null;
2180
+ const hasLiveModel = (provider: string, id: string) => {
2181
+ liveKeys ??= new Set(models.map(m => `${m.provider}\u0000${m.id}`));
2182
+ return liveKeys.has(`${provider}\u0000${id}`);
2183
+ };
2184
+ return models.map(model => {
2185
+ const providerOverrides = overrides.get(model.provider);
2186
+ if (!providerOverrides) return model;
2187
+ const override = resolveModelOverrideWithAliases(providerOverrides, model, hasLiveModel);
2188
+ if (!override) return model;
2189
+ return applyModelOverride(model, override);
2190
+ });
2191
+ }
2192
+ #applyHardcodedModelPolicies(models: Model<Api>[]): Model<Api>[] {
2193
+ return models.map(model => {
2194
+ if (model.provider === "ollama-cloud" && model.omitMaxOutputTokens !== true) {
2195
+ model = applyModelOverride(model, { omitMaxOutputTokens: true });
2196
+ }
2197
+ if (model.id !== "gpt-5.4" || model.provider === "github-copilot") {
2198
+ return model;
2199
+ }
2200
+ const overrides = this.#modelOverrides.get(model.provider)?.get(model.id);
2201
+ if (!overrides) {
2202
+ return applyModelOverride(model, { contextWindow: 1_000_000 });
2203
+ }
2204
+ return applyModelOverride(model, {
2205
+ contextWindow: overrides.contextWindow ?? 1_000_000,
2206
+ ...overrides,
2207
+ });
2208
+ });
2209
+ }
2210
+
2211
+ #parseModels(config: ModelsConfig): CustomModelOverlay[] {
2212
+ const models: CustomModelOverlay[] = [];
2213
+ for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
2214
+ const modelDefs = providerConfig.models ?? [];
2215
+ if (modelDefs.length === 0) continue; // Override-only, no custom models
2216
+ const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
2217
+ if (providerConfig.apiKey) {
2218
+ this.#installProviderApiKey(providerName, providerConfig.apiKey);
2219
+ }
2220
+ for (const modelDef of modelDefs) {
2221
+ const providerCompat = providerConfig.disableStrictTools
2222
+ ? mergeCompat(providerConfig.compat, { disableStrictTools: true })
2223
+ : providerConfig.compat;
2224
+ const model = buildCustomModelOverlay(
2225
+ providerName,
2226
+ providerConfig.baseUrl!,
2227
+ providerConfig.api as Api | undefined,
2228
+ resolvedProviderHeaders,
2229
+ providerConfig.apiKey,
2230
+ providerConfig.authHeader,
2231
+ providerCompat,
2232
+ (providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
2233
+ providerConfig.remoteCompaction,
2234
+ modelDef as CustomModelDefinitionLike,
2235
+ );
2236
+ if (!model) continue;
2237
+ models.push(model);
2238
+ }
2239
+ }
2240
+ return models;
2241
+ }
2242
+
2243
+ #modelsForProviderLookup(provider: string): Model<Api>[] {
2244
+ if (this.#hasFullSnapshot) return this.#models;
2245
+ const normalizedProvider = provider.trim().toLowerCase();
2246
+ if (!normalizedProvider) return [];
2247
+ const cached = this.#providerLookupSnapshots.get(normalizedProvider);
2248
+ if (cached) return cached;
2249
+ const matchingProviders = new Set(
2250
+ this.#knownStaticProviders().filter(candidate => candidate.toLowerCase() === normalizedProvider),
2251
+ );
2252
+ const models = this.#composeStaticModels(matchingProviders);
2253
+ this.#providerLookupSnapshots.set(normalizedProvider, models);
2254
+ return models;
2255
+ }
2256
+
2257
+ /**
2258
+ * Get all models (built-in + custom).
2259
+ * If custom config had errors, returns only built-in models.
2260
+ */
2261
+ getAll(): Model<Api>[] {
2262
+ return this.#ensureFullSnapshot();
2263
+ }
2264
+
2265
+ /**
2266
+ * Availability predicate with per-provider memoization. Auth lookups
2267
+ * (`authStorage.hasAuth`) and the disabled-provider set are resolved once
2268
+ * per provider instead of once per model, which matters when filtering the
2269
+ * full bundled catalog (thousands of models, ~50 providers).
2270
+ */
2271
+ #createProviderAvailabilityCheck(): (provider: string) => boolean {
2272
+ const disabledProviders = getDisabledProviderIdsFromSettings();
2273
+ const byProvider = new Map<string, boolean>();
2274
+ return provider => {
2275
+ let available = byProvider.get(provider);
2276
+ if (available === undefined) {
2277
+ available =
2278
+ !disabledProviders.has(provider) &&
2279
+ (this.#keylessProviders.has(provider) || this.authStorage.hasAuth(provider));
2280
+ byProvider.set(provider, available);
2281
+ }
2282
+ return available;
2283
+ };
2284
+ }
2285
+
2286
+ /**
2287
+ * Get only models that have auth configured.
2288
+ * This is a fast check that doesn't refresh OAuth tokens.
2289
+ */
2290
+ getAvailable(): Model<Api>[] {
2291
+ const isProviderAvailable = this.#createProviderAvailabilityCheck();
2292
+ if (this.#hasFullSnapshot) {
2293
+ return this.#models.filter(model => isProviderAvailable(model.provider));
2294
+ }
2295
+ const availableProviders = new Set(this.#knownStaticProviders().filter(isProviderAvailable));
2296
+ return this.#composeStaticModels(availableProviders);
2297
+ }
2298
+
2299
+ /**
2300
+ * Check whether auth is configured for a model's provider.
2301
+ *
2302
+ * Mirrors the upstream `@mariozechner/pi-coding-agent` API surface so that
2303
+ * external plugins/extensions and downstream wrappers (e.g. subagent launch
2304
+ * paths that pre-flight auth before model resolution) can probe a model
2305
+ * without resolving an API key. Returns true for keyless providers as well
2306
+ * as providers with stored credentials. See issue #993.
2307
+ *
2308
+ * Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
2309
+ * configured by its presence alone — the program is NOT executed — and OAuth
2310
+ * tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the
2311
+ * model-switch pre-flight off the event loop's hot path; the real key
2312
+ * (command execution + OAuth refresh) is resolved lazily per request via
2313
+ * {@link ModelRegistry.resolver}.
2314
+ */
2315
+ hasConfiguredAuth(model: Model<Api>): boolean {
2316
+ const keyConfig = this.#customProviderApiKeys.get(model.provider);
2317
+ return (
2318
+ isCommandConfigValue(keyConfig) ||
2319
+ this.#keylessProviders.has(model.provider) ||
2320
+ this.authStorage.hasAuth(model.provider)
2321
+ );
2322
+ }
2323
+
2324
+ /**
2325
+ * Whether the provider's configured API key is resolved from a command.
2326
+ *
2327
+ * Callers use this to distinguish the registry's command-first resolver
2328
+ * path from lower-priority credentials in {@link authStorage}.
2329
+ */
2330
+ hasCommandBackedApiKey(provider: string): boolean {
2331
+ const keyConfig = this.#customProviderApiKeys.get(provider);
2332
+ return isCommandConfigValue(keyConfig);
2333
+ }
2334
+
2335
+ getDiscoverableProviders(): string[] {
2336
+ const disabledProviders = getDisabledProviderIdsFromSettings();
2337
+ return this.#discoverableProviders
2338
+ .filter(provider => !disabledProviders.has(provider.provider))
2339
+ .map(provider => provider.provider);
2340
+ }
2341
+
2342
+ /**
2343
+ * Whether `providerId` is known to the registry: it has at least one live
2344
+ * model, or it is configured for dynamic discovery (models.yml `discovery:`
2345
+ * or a runtime extension provider) and is not disabled. Discovery-only
2346
+ * providers can hold zero models at startup — cached rows never persist
2347
+ * live auth headers (#5780), so a provider whose discovered models all
2348
+ * carry config headers (`authHeader: true`) only materializes models after
2349
+ * the online refresh completes.
2350
+ */
2351
+ hasProvider(providerId: string): boolean {
2352
+ const providerModels = this.#hasFullSnapshot ? this.#models : this.#composeStaticModels(new Set([providerId]));
2353
+ if (providerModels.some(model => model.provider === providerId)) return true;
2354
+ if (getDisabledProviderIdsFromSettings().has(providerId)) return false;
2355
+ return (
2356
+ this.#discoverableProviders.some(provider => provider.provider === providerId) ||
2357
+ this.#runtimeModelManagers.has(providerId)
2358
+ );
2359
+ }
2360
+
2361
+ getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
2362
+ return this.#providerDiscoveryStates.get(provider);
2363
+ }
2364
+
2365
+ /**
2366
+ * Find a model by provider and ID.
2367
+ */
2368
+ find(provider: string, modelId: string): Model<Api> | undefined {
2369
+ return resolveProviderModelReference(provider, modelId, this.#modelsForProviderLookup(provider));
2370
+ }
2371
+
2372
+ /**
2373
+ * Get the base URL associated with a provider, if any model defines one.
2374
+ */
2375
+ getProviderBaseUrl(provider: string): string | undefined {
2376
+ return this.#modelsForProviderLookup(provider).find(m => m.provider === provider && m.baseUrl)?.baseUrl;
2377
+ }
2378
+ /**
2379
+ * Get provider-level headers without including per-model overrides.
2380
+ */
2381
+ getProviderHeaders(provider: string): Record<string, string> | undefined {
2382
+ return createLiveConfigHeaders([
2383
+ this.#providerOverrides.get(provider)?.headers,
2384
+ this.#runtimeProviderOverrides.get(provider)?.headers,
2385
+ ]);
2386
+ }
2387
+
2388
+ /**
2389
+ * Get API key for a model.
2390
+ */
2391
+ async getApiKey(
2392
+ model: Model<Api>,
2393
+ sessionId?: string,
2394
+ options?: { signal?: AbortSignal },
2395
+ ): Promise<string | undefined> {
2396
+ const commandKey = this.#resolveCommandBackedApiKey(model.provider);
2397
+ if (commandKey.configured) return commandKey.value;
2398
+ if (this.#keylessProviders.has(model.provider) && !this.authStorage.hasAuth(model.provider)) {
2399
+ return kNoAuth;
2400
+ }
2401
+ return this.authStorage.getApiKey(model.provider, sessionId, {
2402
+ baseUrl: model.baseUrl,
2403
+ modelId: model.id,
2404
+ signal: options?.signal,
2405
+ });
2406
+ }
2407
+
2408
+ /** Resolve request authentication through the historical Pi extension facade. */
2409
+ async getApiKeyAndHeaders(model: Model<Api>): Promise<ResolvedRequestAuth> {
2410
+ try {
2411
+ const apiKey = await this.getApiKey(model);
2412
+ if (apiKey === undefined) {
2413
+ return { ok: false, error: `No API key found for "${model.provider}"` };
2414
+ }
2415
+ const headers = this.getProviderHeaders(model.provider);
2416
+ return { ok: true, apiKey, headers };
2417
+ } catch (error) {
2418
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
2419
+ }
2420
+ }
2421
+
2422
+ /**
2423
+ * Get API key for a provider (e.g., "openai").
2424
+ *
2425
+ * `options.forceRefresh` powers step (b) of the auth-retry policy — it
2426
+ * re-mints the session-sticky OAuth token even when the cached copy still
2427
+ * looks valid. `options.signal` is threaded into any broker-bound refresh.
2428
+ */
2429
+ async getApiKeyForProvider(
2430
+ provider: string,
2431
+ sessionId?: string,
2432
+ options?: { baseUrl?: string; modelId?: string; forceRefresh?: boolean; signal?: AbortSignal },
2433
+ ): Promise<string | undefined> {
2434
+ const commandKey = this.#resolveCommandBackedApiKey(provider);
2435
+ if (commandKey.configured) return commandKey.value;
2436
+ if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
2437
+ return kNoAuth;
2438
+ }
2439
+ return this.authStorage.getApiKey(provider, sessionId, {
2440
+ baseUrl: options?.baseUrl,
2441
+ modelId: options?.modelId,
2442
+ forceRefresh: options?.forceRefresh,
2443
+ signal: options?.signal,
2444
+ });
2445
+ }
2446
+
2447
+ /**
2448
+ * Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
2449
+ * policy. Accepts a provider id with options, or a model with an optional
2450
+ * session id (`resolver(model, sessionId)`) which derives `baseUrl`/`modelId`
2451
+ * from the model. Callers that need the initial key for a guard can call
2452
+ * `resolveApiKeyOnce(resolver)`.
2453
+ */
2454
+ resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
2455
+ resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
2456
+ resolver(target: string | ApiKeyResolverModel, optionsOrSessionId?: ApiKeyResolverOptions | string): ApiKeyResolver {
2457
+ const options = typeof optionsOrSessionId === "string" ? { sessionId: optionsOrSessionId } : optionsOrSessionId;
2458
+ if (typeof target === "string") {
2459
+ return createApiKeyResolver(this, target, options);
2460
+ }
2461
+ return createApiKeyResolver(this, target.provider, {
2462
+ ...options,
2463
+ baseUrl: target.baseUrl,
2464
+ modelId: target.id,
2465
+ });
2466
+ }
2467
+
2468
+ async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
2469
+ const commandKey = this.#resolveCommandBackedApiKey(provider);
2470
+ if (commandKey.configured) return commandKey.value;
2471
+ if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
2472
+ return kNoAuth;
2473
+ }
2474
+ return this.authStorage.peekApiKey(provider);
2475
+ }
2476
+
2477
+ /**
2478
+ * Check if a model is using OAuth credentials (subscription).
2479
+ */
2480
+ isUsingOAuth(model: Model<Api>): boolean {
2481
+ return this.authStorage.hasOAuth(model.provider);
2482
+ }
2483
+
2484
+ #clearRuntimeProviderState(providerName: string): void {
2485
+ this.#runtimeProviderApiKeys.delete(providerName);
2486
+ this.#runtimeProviderOverrides.delete(providerName);
2487
+ this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(overlay => overlay.provider !== providerName);
2488
+ this.#runtimeModelManagers.delete(providerName);
2489
+ this.#runtimeModelModifiers.delete(providerName);
2490
+ this.#lastModelModifierWarnings.delete(providerName);
2491
+ this.authStorage.removeConfigApiKey(providerName);
2492
+ }
2493
+
2494
+ /**
2495
+ * Remove custom API/OAuth registrations for a specific extension source.
2496
+ */
2497
+ clearSourceRegistrations(sourceId: string): void {
2498
+ unregisterCustomApis(sourceId);
2499
+ unregisterOAuthProviders(sourceId);
2500
+ const sourceProviders = this.#runtimeProvidersBySource.get(sourceId);
2501
+ if (!sourceProviders || sourceProviders.size === 0) {
2502
+ return;
2503
+ }
2504
+ this.#ensureFullSnapshot();
2505
+ this.#runtimeProvidersBySource.delete(sourceId);
2506
+ for (const providerName of sourceProviders) {
2507
+ if (this.#runtimeProviderSourceByName.get(providerName) !== sourceId) {
2508
+ continue;
2509
+ }
2510
+ this.#runtimeProviderSourceByName.delete(providerName);
2511
+ this.#clearRuntimeProviderState(providerName);
2512
+ }
2513
+ this.#lastStaticLoadMtime = null;
2514
+ this.#reloadStaticModels();
2515
+ }
2516
+
2517
+ /**
2518
+ * Remove registrations for extension sources that are no longer active.
2519
+ */
2520
+ syncExtensionSources(activeSourceIds: string[]): void {
2521
+ const activeSources = new Set(activeSourceIds);
2522
+ for (const sourceId of this.#registeredProviderSources) {
2523
+ if (activeSources.has(sourceId)) {
2524
+ continue;
2525
+ }
2526
+ this.clearSourceRegistrations(sourceId);
2527
+ this.#registeredProviderSources.delete(sourceId);
2528
+ }
2529
+ }
2530
+
2531
+ /**
2532
+ * Register a provider dynamically (from extensions).
2533
+ *
2534
+ * If provider has models: replaces all existing models for this provider.
2535
+ * If provider has only baseUrl/headers: overrides existing models' URLs.
2536
+ * If provider has streamSimple: registers a custom API streaming function.
2537
+ * If provider has oauth: registers OAuth provider for /login support.
2538
+ */
2539
+ registerProvider(providerName: string, config: ProviderConfigInput, sourceId?: string): void {
2540
+ if (config.streamSimple && !config.api) {
2541
+ throw new Error(`Provider ${providerName}: "api" is required when registering streamSimple.`);
2542
+ }
2543
+
2544
+ validateProviderConfiguration(
2545
+ providerName,
2546
+ {
2547
+ baseUrl: config.baseUrl,
2548
+ headers: config.headers,
2549
+ apiKey: config.apiKey,
2550
+ api: config.api,
2551
+ oauthConfigured: Boolean(config.oauth),
2552
+ models: (config.models ?? []) as ProviderValidationModel[],
2553
+ },
2554
+ "runtime-register",
2555
+ );
2556
+
2557
+ if (config.streamSimple && config.api) {
2558
+ const streamSimple = config.streamSimple;
2559
+ registerCustomApi(config.api, streamSimple, sourceId, (model, context, options) =>
2560
+ streamSimple(model, context, options as SimpleStreamOptions),
2561
+ );
2562
+ }
2563
+
2564
+ if (config.oauth) {
2565
+ registerOAuthProvider({
2566
+ ...config.oauth,
2567
+ id: providerName,
2568
+ sourceId,
2569
+ });
2570
+ }
2571
+
2572
+ let sourceHandoff = false;
2573
+ if (sourceId) {
2574
+ this.#registeredProviderSources.add(sourceId);
2575
+ const previousSourceId = this.#runtimeProviderSourceByName.get(providerName);
2576
+ if (previousSourceId && previousSourceId !== sourceId) {
2577
+ const previousProviders = this.#runtimeProvidersBySource.get(previousSourceId);
2578
+ previousProviders?.delete(providerName);
2579
+ if (previousProviders && previousProviders.size === 0) {
2580
+ this.#runtimeProvidersBySource.delete(previousSourceId);
2581
+ }
2582
+ this.#clearRuntimeProviderState(providerName);
2583
+ sourceHandoff = true;
2584
+ }
2585
+ const sourceProviders = this.#runtimeProvidersBySource.get(sourceId) ?? new Set<string>();
2586
+ sourceProviders.add(providerName);
2587
+ this.#runtimeProvidersBySource.set(sourceId, sourceProviders);
2588
+ this.#runtimeProviderSourceByName.set(providerName, sourceId);
2589
+ }
2590
+ if (sourceHandoff) {
2591
+ this.#lastStaticLoadMtime = null;
2592
+ this.#reloadStaticModels();
2593
+ }
2594
+
2595
+ this.#ensureFullSnapshot();
2596
+ if (config.apiKey) {
2597
+ this.#installProviderApiKey(providerName, config.apiKey);
2598
+ // Persist runtime API keys so they survive #reloadStaticModels() cycles
2599
+ this.#runtimeProviderApiKeys.set(providerName, config.apiKey);
2600
+ }
2601
+
2602
+ if (config.models && config.models.length > 0) {
2603
+ // Build model overlays that persist across refresh() cycles
2604
+ const newOverlays: CustomModelOverlay[] = [];
2605
+ for (const modelDef of config.models) {
2606
+ const overlay = buildCustomModelOverlay(
2607
+ providerName,
2608
+ config.baseUrl!,
2609
+ config.api,
2610
+ config.headers,
2611
+ config.apiKey,
2612
+ config.authHeader,
2613
+ config.compat,
2614
+ undefined,
2615
+ config.remoteCompaction,
2616
+ modelDef as CustomModelDefinitionLike,
2617
+ );
2618
+ if (!overlay) {
2619
+ throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
2620
+ }
2621
+ newOverlays.push(overlay);
2622
+ }
2623
+ // Store as runtime overlays so they survive #reloadStaticModels()
2624
+ this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(m => m.provider !== providerName);
2625
+ this.#runtimeModelOverlays.push(...newOverlays);
2626
+
2627
+ // Update the unprojected snapshot, then rerun every whole-catalog
2628
+ // projection exactly once. Incremental projection is not safe because one
2629
+ // provider's hook may inspect or suppress another provider's models.
2630
+ const nextModels = this.#unprojectedModels.filter(model => model.provider !== providerName);
2631
+ for (const overlay of newOverlays) {
2632
+ nextModels.push(finalizeCustomModel(overlay, { useDefaults: true }));
2633
+ }
2634
+ const runtimeTransportOverride = this.#runtimeProviderOverrides.get(providerName);
2635
+ this.#unprojectedModels = runtimeTransportOverride
2636
+ ? nextModels.map(model => {
2637
+ if (model.provider !== providerName) return model;
2638
+ return this.#applyProviderTransportOverrideToModel(model, runtimeTransportOverride);
2639
+ })
2640
+ : nextModels;
2641
+
2642
+ if (config.oauth?.modifyModels) {
2643
+ this.#runtimeModelModifiers.set(providerName, config.oauth.modifyModels);
2644
+ } else {
2645
+ this.#runtimeModelModifiers.delete(providerName);
2646
+ }
2647
+ this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
2648
+ this.#providerLookupSnapshots.clear();
2649
+ return;
2650
+ }
2651
+
2652
+ if (config.fetchDynamicModels) {
2653
+ const fetcher = config.fetchDynamicModels;
2654
+ const providerBaseUrl = config.baseUrl ?? "";
2655
+ const providerApi = config.api;
2656
+ const providerHeaders = config.headers;
2657
+ const providerApiKey = config.apiKey;
2658
+ const providerAuthHeader = config.authHeader;
2659
+ const providerCompat = config.compat;
2660
+ const managerOptions: ModelManagerOptions<Api> = {
2661
+ providerId: providerName as Parameters<typeof createModelManager>[0]["providerId"],
2662
+ staticModels: [],
2663
+ cacheDbPath: this.#cacheDbPath,
2664
+ cacheTtlMs: 24 * 60 * 60 * 1000,
2665
+ dynamicModelsAuthoritative: true,
2666
+ fetchDynamicModels: async () => {
2667
+ const apiKey = await this.#peekApiKeyForProvider(providerName);
2668
+ const resolvedKey = isAuthenticated(apiKey) ? apiKey : undefined;
2669
+ const modelDefs = await withRuntimeDynamicModelsTimeout(RUNTIME_DYNAMIC_MODEL_FETCH_TIMEOUT_MS, () =>
2670
+ fetcher(resolvedKey),
2671
+ );
2672
+ const results: Model<Api>[] = [];
2673
+ for (const modelDef of modelDefs) {
2674
+ const overlay = buildCustomModelOverlay(
2675
+ providerName,
2676
+ modelDef.baseUrl ?? providerBaseUrl,
2677
+ modelDef.api ?? providerApi,
2678
+ providerHeaders,
2679
+ providerApiKey,
2680
+ providerAuthHeader,
2681
+ providerCompat,
2682
+ undefined,
2683
+ config.remoteCompaction,
2684
+ modelDef as CustomModelDefinitionLike,
2685
+ );
2686
+ if (overlay) results.push(finalizeCustomModel(overlay, { useDefaults: true }));
2687
+ }
2688
+ return results.map(toModelSpec);
2689
+ },
2690
+ };
2691
+ this.#runtimeModelManagers.set(providerName, { options: managerOptions, sourceId: sourceId ?? "" });
2692
+ // Discovery is driven by refreshRuntimeProviders() after the drain — not
2693
+ // here, so registration has no network side effect and callers can await.
2694
+ }
2695
+
2696
+ if (
2697
+ config.baseUrl ||
2698
+ config.headers ||
2699
+ config.apiKey ||
2700
+ config.authHeader !== undefined ||
2701
+ config.remoteCompaction !== undefined ||
2702
+ config.transport !== undefined
2703
+ ) {
2704
+ const transportOverride = {
2705
+ baseUrl: config.baseUrl,
2706
+ headers: config.headers,
2707
+ apiKey: config.apiKey,
2708
+ authHeader: config.authHeader,
2709
+ remoteCompaction: config.remoteCompaction,
2710
+ transport: config.transport,
2711
+ };
2712
+ const nextRuntimeOverride = this.#mergeProviderOverride(
2713
+ this.#runtimeProviderOverrides.get(providerName),
2714
+ transportOverride,
2715
+ );
2716
+ this.#runtimeProviderOverrides.set(providerName, nextRuntimeOverride);
2717
+ this.#unprojectedModels = this.#applyLlamaCppQwenThinkingToModels(
2718
+ this.#unprojectedModels.map(model => {
2719
+ if (model.provider !== providerName) return model;
2720
+ return this.#applyProviderTransportOverrideToModel(model, transportOverride);
2721
+ }),
2722
+ );
2723
+ this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
2724
+ this.#providerLookupSnapshots.clear();
2725
+ }
2726
+ }
2727
+
2728
+ /**
2729
+ * Suppress a specific model selector (e.g., "provider/id") until a specific timestamp.
2730
+ */
2731
+ suppressSelector(selector: string, untilMs: number): void {
2732
+ this.#suppressedSelectors.set(
2733
+ normalizeSuppressedSelector(selector, (provider, id) => this.find(provider, id) !== undefined),
2734
+ untilMs,
2735
+ );
2736
+ }
2737
+
2738
+ /**
2739
+ * Check if a model selector is currently suppressed due to rate limits.
2740
+ */
2741
+ isSelectorSuppressed(selector: string): boolean {
2742
+ const normalizedSelector = normalizeSuppressedSelector(
2743
+ selector,
2744
+ (provider, id) => this.find(provider, id) !== undefined,
2745
+ );
2746
+ const suppressedUntil = this.#suppressedSelectors.get(normalizedSelector);
2747
+ if (!suppressedUntil) return false;
2748
+ if (suppressedUntil <= Date.now()) {
2749
+ this.#suppressedSelectors.delete(normalizedSelector);
2750
+ return false;
2751
+ }
2752
+ return true;
2753
+ }
2754
+
2755
+ /**
2756
+ * Clear the cooldown suppression for one selector after an explicit user selection.
2757
+ */
2758
+ clearSuppressedSelector(selector: string): void {
2759
+ this.#suppressedSelectors.delete(
2760
+ normalizeSuppressedSelector(selector, (provider, id) => this.find(provider, id) !== undefined),
2761
+ );
2762
+ }
2763
+
2764
+ /**
2765
+ * Clear all cooldown suppressions recorded via {@link suppressSelector}.
2766
+ * Used to reset retry-fallback cooldown state without a full {@link refresh}.
2767
+ */
2768
+ clearSuppressedSelectors(): void {
2769
+ this.#suppressedSelectors.clear();
2770
+ }
2771
+ }
2772
+
2773
+ /**
2774
+ * Input type for registerProvider API (from extensions).
2775
+ */
2776
+ export interface ProviderConfigInput {
2777
+ baseUrl?: string;
2778
+ apiKey?: string;
2779
+ api?: Api;
2780
+ streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
2781
+ headers?: Record<string, string>;
2782
+ compat?: ModelSpec<Api>["compat"];
2783
+ remoteCompaction?: RemoteCompactionConfig<Api>;
2784
+ authHeader?: boolean;
2785
+ /** Streaming transport override — see {@link Model.transport}. */
2786
+ transport?: Model<Api>["transport"];
2787
+ oauth?: {
2788
+ name: string;
2789
+ login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials | string>;
2790
+ refreshToken?(credentials: OAuthCredentials): Promise<OAuthCredentials>;
2791
+ getApiKey?(credentials: OAuthCredentials): string;
2792
+ modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
2793
+ };
2794
+ /**
2795
+ * Async factory that fetches the live model list from the provider endpoint.
2796
+ * When present, the result is run through the same SQLite model-cache as
2797
+ * built-in providers (keyed by provider name, default 24 h TTL).
2798
+ * The factory receives the resolved API key (undefined when unauthenticated).
2799
+ */
2800
+ fetchDynamicModels?: (
2801
+ apiKey: string | undefined,
2802
+ ) => Promise<readonly NonNullable<ProviderConfigInput["models"]>[number][]>;
2803
+ models?: Array<{
2804
+ id: string;
2805
+ name: string;
2806
+ api?: Api;
2807
+ baseUrl?: string;
2808
+ reasoning: boolean;
2809
+ thinking?: ThinkingConfig;
2810
+ input: ("text" | "image")[];
2811
+ supportsTools?: boolean;
2812
+ cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
2813
+ contextWindow: number;
2814
+ maxTokens: number;
2815
+ headers?: Record<string, string>;
2816
+ compat?: ModelSpec<Api>["compat"];
2817
+ contextPromotionTarget?: string;
2818
+ compactionModel?: string;
2819
+ remoteCompaction?: RemoteCompactionConfig<Api>;
2820
+ premiumMultiplier?: number;
2821
+ }>;
2822
+ }