@wahack/pi-coding-agent 15.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1152) hide show
  1. package/CHANGELOG.md +10031 -0
  2. package/README.md +36 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +104 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/extensions/README.md +142 -0
  7. package/examples/extensions/api-demo.ts +79 -0
  8. package/examples/extensions/chalk-logger.ts +25 -0
  9. package/examples/extensions/hello.ts +31 -0
  10. package/examples/extensions/pirate.ts +43 -0
  11. package/examples/extensions/plan-mode.ts +549 -0
  12. package/examples/extensions/reload-runtime.ts +38 -0
  13. package/examples/extensions/thinking-note.ts +13 -0
  14. package/examples/extensions/tools.ts +145 -0
  15. package/examples/extensions/with-deps/index.ts +36 -0
  16. package/examples/extensions/with-deps/package-lock.json +31 -0
  17. package/examples/extensions/with-deps/package.json +17 -0
  18. package/examples/hooks/README.md +56 -0
  19. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  20. package/examples/hooks/confirm-destructive.ts +58 -0
  21. package/examples/hooks/custom-compaction.ts +115 -0
  22. package/examples/hooks/dirty-repo-guard.ts +51 -0
  23. package/examples/hooks/file-trigger.ts +40 -0
  24. package/examples/hooks/git-checkpoint.ts +52 -0
  25. package/examples/hooks/handoff.ts +149 -0
  26. package/examples/hooks/permission-gate.ts +33 -0
  27. package/examples/hooks/protected-paths.ts +29 -0
  28. package/examples/hooks/qna.ts +118 -0
  29. package/examples/hooks/status-line.ts +39 -0
  30. package/examples/sdk/01-minimal.ts +21 -0
  31. package/examples/sdk/02-custom-model.ts +49 -0
  32. package/examples/sdk/03-custom-prompt.ts +46 -0
  33. package/examples/sdk/04-skills.ts +43 -0
  34. package/examples/sdk/06-extensions.ts +82 -0
  35. package/examples/sdk/06-hooks.ts +61 -0
  36. package/examples/sdk/07-context-files.ts +35 -0
  37. package/examples/sdk/08-prompt-templates.ts +41 -0
  38. package/examples/sdk/08-slash-commands.ts +46 -0
  39. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  40. package/examples/sdk/11-sessions.ts +47 -0
  41. package/examples/sdk/12-redis-sessions.ts +54 -0
  42. package/examples/sdk/13-sql-sessions.ts +61 -0
  43. package/examples/sdk/README.md +172 -0
  44. package/package.json +554 -0
  45. package/scripts/build-binary.ts +100 -0
  46. package/scripts/bundle-dist.ts +90 -0
  47. package/scripts/format-prompts.ts +68 -0
  48. package/scripts/generate-docs-index.ts +40 -0
  49. package/scripts/generate-template.ts +33 -0
  50. package/scripts/omp +42 -0
  51. package/scripts/omp.ts +19 -0
  52. package/src/async/index.ts +1 -0
  53. package/src/async/job-manager.ts +625 -0
  54. package/src/auto-thinking/classifier.ts +185 -0
  55. package/src/autoresearch/command-resume.md +14 -0
  56. package/src/autoresearch/dashboard.ts +436 -0
  57. package/src/autoresearch/git.ts +319 -0
  58. package/src/autoresearch/helpers.ts +218 -0
  59. package/src/autoresearch/index.ts +536 -0
  60. package/src/autoresearch/prompt-setup.md +43 -0
  61. package/src/autoresearch/prompt.md +103 -0
  62. package/src/autoresearch/resume-message.md +10 -0
  63. package/src/autoresearch/state.ts +273 -0
  64. package/src/autoresearch/storage.ts +699 -0
  65. package/src/autoresearch/tools/init-experiment.ts +272 -0
  66. package/src/autoresearch/tools/log-experiment.ts +524 -0
  67. package/src/autoresearch/tools/run-experiment.ts +407 -0
  68. package/src/autoresearch/tools/update-notes.ts +109 -0
  69. package/src/autoresearch/types.ts +168 -0
  70. package/src/bun-imports.d.ts +28 -0
  71. package/src/capability/context-file.ts +44 -0
  72. package/src/capability/extension-module.ts +34 -0
  73. package/src/capability/extension.ts +47 -0
  74. package/src/capability/fs.ts +117 -0
  75. package/src/capability/hook.ts +40 -0
  76. package/src/capability/index.ts +436 -0
  77. package/src/capability/instruction.ts +37 -0
  78. package/src/capability/mcp.ts +74 -0
  79. package/src/capability/prompt.ts +35 -0
  80. package/src/capability/rule-buckets.ts +66 -0
  81. package/src/capability/rule.ts +261 -0
  82. package/src/capability/settings.ts +34 -0
  83. package/src/capability/skill.ts +63 -0
  84. package/src/capability/slash-command.ts +40 -0
  85. package/src/capability/ssh.ts +41 -0
  86. package/src/capability/system-prompt.ts +34 -0
  87. package/src/capability/tool.ts +38 -0
  88. package/src/capability/types.ts +168 -0
  89. package/src/cli/agents-cli.ts +138 -0
  90. package/src/cli/args.ts +340 -0
  91. package/src/cli/auth-broker-cli.ts +895 -0
  92. package/src/cli/auth-gateway-cli.ts +611 -0
  93. package/src/cli/classify-install-target.ts +76 -0
  94. package/src/cli/claude-trace-cli.ts +795 -0
  95. package/src/cli/commands/init-xdg.ts +27 -0
  96. package/src/cli/completion-gen.ts +550 -0
  97. package/src/cli/config-cli.ts +418 -0
  98. package/src/cli/dry-balance-cli.ts +856 -0
  99. package/src/cli/extension-flags.ts +48 -0
  100. package/src/cli/file-processor.ts +133 -0
  101. package/src/cli/gallery-cli.ts +230 -0
  102. package/src/cli/gallery-fixtures/agentic.ts +407 -0
  103. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  104. package/src/cli/gallery-fixtures/edit.ts +194 -0
  105. package/src/cli/gallery-fixtures/fs.ts +220 -0
  106. package/src/cli/gallery-fixtures/index.ts +40 -0
  107. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  108. package/src/cli/gallery-fixtures/memory.ts +81 -0
  109. package/src/cli/gallery-fixtures/misc.ts +250 -0
  110. package/src/cli/gallery-fixtures/search.ts +213 -0
  111. package/src/cli/gallery-fixtures/shell.ts +167 -0
  112. package/src/cli/gallery-fixtures/types.ts +57 -0
  113. package/src/cli/gallery-fixtures/web.ts +158 -0
  114. package/src/cli/gallery-screenshot.ts +279 -0
  115. package/src/cli/grep-cli.ts +160 -0
  116. package/src/cli/grievances-cli.ts +256 -0
  117. package/src/cli/initial-message.ts +58 -0
  118. package/src/cli/list-models.ts +194 -0
  119. package/src/cli/plugin-cli.ts +996 -0
  120. package/src/cli/read-cli.ts +57 -0
  121. package/src/cli/session-picker.ts +79 -0
  122. package/src/cli/setup-cli.ts +231 -0
  123. package/src/cli/shell-cli.ts +176 -0
  124. package/src/cli/ssh-cli.ts +179 -0
  125. package/src/cli/startup-cwd.ts +68 -0
  126. package/src/cli/stats-cli.ts +238 -0
  127. package/src/cli/tiny-models-cli.ts +127 -0
  128. package/src/cli/update-cli.ts +611 -0
  129. package/src/cli/usage-cli.ts +603 -0
  130. package/src/cli/web-search-cli.ts +132 -0
  131. package/src/cli/worktree-cli.ts +291 -0
  132. package/src/cli-commands.ts +79 -0
  133. package/src/cli.ts +200 -0
  134. package/src/commands/acp.ts +24 -0
  135. package/src/commands/agents.ts +57 -0
  136. package/src/commands/auth-broker.ts +99 -0
  137. package/src/commands/auth-gateway.ts +69 -0
  138. package/src/commands/commit.ts +46 -0
  139. package/src/commands/complete.ts +66 -0
  140. package/src/commands/completions.ts +60 -0
  141. package/src/commands/config.ts +51 -0
  142. package/src/commands/dry-balance.ts +43 -0
  143. package/src/commands/gallery.ts +52 -0
  144. package/src/commands/grep.ts +48 -0
  145. package/src/commands/grievances.ts +51 -0
  146. package/src/commands/install.ts +107 -0
  147. package/src/commands/launch.ts +169 -0
  148. package/src/commands/plugin.ts +78 -0
  149. package/src/commands/read.ts +38 -0
  150. package/src/commands/setup.ts +67 -0
  151. package/src/commands/shell.ts +29 -0
  152. package/src/commands/ssh.ts +60 -0
  153. package/src/commands/stats.ts +29 -0
  154. package/src/commands/tiny-models.ts +36 -0
  155. package/src/commands/update.ts +21 -0
  156. package/src/commands/usage.ts +35 -0
  157. package/src/commands/web-search.ts +42 -0
  158. package/src/commands/worktree.ts +56 -0
  159. package/src/commit/agentic/agent.ts +317 -0
  160. package/src/commit/agentic/fallback.ts +96 -0
  161. package/src/commit/agentic/index.ts +355 -0
  162. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  163. package/src/commit/agentic/prompts/session-user.md +25 -0
  164. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  165. package/src/commit/agentic/prompts/system.md +38 -0
  166. package/src/commit/agentic/state.ts +60 -0
  167. package/src/commit/agentic/tools/analyze-file.ts +146 -0
  168. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  169. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  170. package/src/commit/agentic/tools/git-overview.ts +81 -0
  171. package/src/commit/agentic/tools/index.ts +54 -0
  172. package/src/commit/agentic/tools/propose-changelog.ts +144 -0
  173. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  174. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  175. package/src/commit/agentic/tools/schemas.ts +23 -0
  176. package/src/commit/agentic/tools/split-commit.ts +245 -0
  177. package/src/commit/agentic/topo-sort.ts +44 -0
  178. package/src/commit/agentic/trivial.ts +51 -0
  179. package/src/commit/agentic/validation.ts +183 -0
  180. package/src/commit/analysis/conventional.ts +64 -0
  181. package/src/commit/analysis/index.ts +4 -0
  182. package/src/commit/analysis/scope.ts +242 -0
  183. package/src/commit/analysis/summary.ts +105 -0
  184. package/src/commit/analysis/validation.ts +66 -0
  185. package/src/commit/changelog/detect.ts +40 -0
  186. package/src/commit/changelog/generate.ts +97 -0
  187. package/src/commit/changelog/index.ts +234 -0
  188. package/src/commit/changelog/parse.ts +44 -0
  189. package/src/commit/cli.ts +85 -0
  190. package/src/commit/git/diff.ts +148 -0
  191. package/src/commit/index.ts +5 -0
  192. package/src/commit/map-reduce/index.ts +69 -0
  193. package/src/commit/map-reduce/map-phase.ts +193 -0
  194. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  195. package/src/commit/map-reduce/utils.ts +9 -0
  196. package/src/commit/message.ts +11 -0
  197. package/src/commit/model-selection.ts +92 -0
  198. package/src/commit/pipeline.ts +243 -0
  199. package/src/commit/prompts/analysis-system.md +148 -0
  200. package/src/commit/prompts/analysis-user.md +38 -0
  201. package/src/commit/prompts/changelog-system.md +50 -0
  202. package/src/commit/prompts/changelog-user.md +18 -0
  203. package/src/commit/prompts/file-observer-system.md +24 -0
  204. package/src/commit/prompts/file-observer-user.md +8 -0
  205. package/src/commit/prompts/reduce-system.md +50 -0
  206. package/src/commit/prompts/reduce-user.md +17 -0
  207. package/src/commit/prompts/summary-retry.md +3 -0
  208. package/src/commit/prompts/summary-system.md +38 -0
  209. package/src/commit/prompts/summary-user.md +13 -0
  210. package/src/commit/prompts/types-description.md +2 -0
  211. package/src/commit/shared-llm.ts +77 -0
  212. package/src/commit/types.ts +118 -0
  213. package/src/commit/utils/exclusions.ts +42 -0
  214. package/src/commit/utils.ts +58 -0
  215. package/src/config/api-key-resolver.ts +60 -0
  216. package/src/config/append-only-context-mode.ts +31 -0
  217. package/src/config/config-file.ts +317 -0
  218. package/src/config/file-lock.ts +164 -0
  219. package/src/config/keybindings.ts +628 -0
  220. package/src/config/mcp-schema.json +230 -0
  221. package/src/config/model-discovery.ts +554 -0
  222. package/src/config/model-registry.ts +2090 -0
  223. package/src/config/model-resolver.ts +1502 -0
  224. package/src/config/model-roles.ts +74 -0
  225. package/src/config/models-config-schema.ts +226 -0
  226. package/src/config/models-config.ts +129 -0
  227. package/src/config/prompt-templates.ts +185 -0
  228. package/src/config/resolve-config-value.ts +94 -0
  229. package/src/config/settings-schema.ts +3530 -0
  230. package/src/config/settings.ts +1178 -0
  231. package/src/config.ts +242 -0
  232. package/src/cursor.ts +340 -0
  233. package/src/dap/client.ts +760 -0
  234. package/src/dap/config.ts +189 -0
  235. package/src/dap/defaults.json +212 -0
  236. package/src/dap/index.ts +4 -0
  237. package/src/dap/session.ts +1441 -0
  238. package/src/dap/types.ts +610 -0
  239. package/src/debug/index.ts +515 -0
  240. package/src/debug/log-formatting.ts +58 -0
  241. package/src/debug/log-viewer.ts +908 -0
  242. package/src/debug/profiler.ts +162 -0
  243. package/src/debug/protocol-probe.ts +267 -0
  244. package/src/debug/raw-sse-buffer.ts +273 -0
  245. package/src/debug/raw-sse.ts +292 -0
  246. package/src/debug/report-bundle.ts +374 -0
  247. package/src/debug/system-info.ts +111 -0
  248. package/src/debug/terminal-info.ts +124 -0
  249. package/src/discovery/agents-md.ts +67 -0
  250. package/src/discovery/agents.ts +230 -0
  251. package/src/discovery/at-imports.ts +273 -0
  252. package/src/discovery/builtin-defaults.ts +39 -0
  253. package/src/discovery/builtin-rules/index.ts +54 -0
  254. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  255. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  256. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  257. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  258. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  259. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  260. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  261. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  262. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  263. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
  264. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  265. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  266. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  267. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  268. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  269. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  270. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  271. package/src/discovery/builtin.ts +906 -0
  272. package/src/discovery/claude-plugins.ts +386 -0
  273. package/src/discovery/claude.ts +584 -0
  274. package/src/discovery/cline.ts +83 -0
  275. package/src/discovery/codex.ts +522 -0
  276. package/src/discovery/cursor.ts +220 -0
  277. package/src/discovery/gemini.ts +383 -0
  278. package/src/discovery/github.ts +154 -0
  279. package/src/discovery/helpers.ts +1016 -0
  280. package/src/discovery/index.ts +81 -0
  281. package/src/discovery/mcp-json.ts +171 -0
  282. package/src/discovery/omp-extension-roots.ts +190 -0
  283. package/src/discovery/omp-plugins.ts +383 -0
  284. package/src/discovery/opencode.ts +398 -0
  285. package/src/discovery/plugin-dir-roots.ts +28 -0
  286. package/src/discovery/ssh.ts +153 -0
  287. package/src/discovery/substitute-plugin-root.ts +29 -0
  288. package/src/discovery/vscode.ts +105 -0
  289. package/src/discovery/windsurf.ts +147 -0
  290. package/src/edit/apply-patch/index.ts +87 -0
  291. package/src/edit/apply-patch/parser.ts +174 -0
  292. package/src/edit/diff.ts +999 -0
  293. package/src/edit/file-snapshot-store.ts +91 -0
  294. package/src/edit/hashline/block-resolver.ts +33 -0
  295. package/src/edit/hashline/diff.ts +290 -0
  296. package/src/edit/hashline/execute.ts +242 -0
  297. package/src/edit/hashline/filesystem.ts +130 -0
  298. package/src/edit/hashline/index.ts +5 -0
  299. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  300. package/src/edit/hashline/params.ts +18 -0
  301. package/src/edit/index.ts +571 -0
  302. package/src/edit/modes/apply-patch.lark +19 -0
  303. package/src/edit/modes/apply-patch.ts +53 -0
  304. package/src/edit/modes/patch.ts +1891 -0
  305. package/src/edit/modes/replace.ts +1137 -0
  306. package/src/edit/normalize.ts +345 -0
  307. package/src/edit/notebook.ts +242 -0
  308. package/src/edit/read-file.ts +25 -0
  309. package/src/edit/renderer.ts +769 -0
  310. package/src/edit/streaming.ts +517 -0
  311. package/src/eval/__tests__/agent-bridge.test.ts +708 -0
  312. package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
  313. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  314. package/src/eval/__tests__/completion-bridge.test.ts +412 -0
  315. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  316. package/src/eval/__tests__/idle-timeout.test.ts +80 -0
  317. package/src/eval/__tests__/js-context-manager.test.ts +241 -0
  318. package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
  319. package/src/eval/agent-bridge.ts +319 -0
  320. package/src/eval/backend.ts +71 -0
  321. package/src/eval/bridge-timeout.ts +44 -0
  322. package/src/eval/budget-bridge.ts +48 -0
  323. package/src/eval/completion-bridge.ts +207 -0
  324. package/src/eval/concurrency-bridge.ts +34 -0
  325. package/src/eval/idle-timeout.ts +91 -0
  326. package/src/eval/index.ts +4 -0
  327. package/src/eval/js/context-manager.ts +502 -0
  328. package/src/eval/js/executor.ts +173 -0
  329. package/src/eval/js/index.ts +51 -0
  330. package/src/eval/js/shared/helpers.ts +283 -0
  331. package/src/eval/js/shared/indirect-eval.ts +30 -0
  332. package/src/eval/js/shared/local-module-loader.ts +342 -0
  333. package/src/eval/js/shared/prelude.ts +2 -0
  334. package/src/eval/js/shared/prelude.txt +246 -0
  335. package/src/eval/js/shared/rewrite-imports.ts +532 -0
  336. package/src/eval/js/shared/runtime.ts +352 -0
  337. package/src/eval/js/shared/types.ts +18 -0
  338. package/src/eval/js/tool-bridge.ts +162 -0
  339. package/src/eval/js/worker-core.ts +132 -0
  340. package/src/eval/js/worker-entry.ts +30 -0
  341. package/src/eval/js/worker-protocol.ts +47 -0
  342. package/src/eval/py/__tests__/prelude.test.ts +19 -0
  343. package/src/eval/py/display.ts +71 -0
  344. package/src/eval/py/executor.ts +742 -0
  345. package/src/eval/py/index.ts +68 -0
  346. package/src/eval/py/kernel.ts +748 -0
  347. package/src/eval/py/prelude.py +658 -0
  348. package/src/eval/py/prelude.ts +3 -0
  349. package/src/eval/py/runner.py +1133 -0
  350. package/src/eval/py/runtime.ts +276 -0
  351. package/src/eval/py/spawn-options.ts +126 -0
  352. package/src/eval/py/tool-bridge.ts +182 -0
  353. package/src/eval/session-id.ts +8 -0
  354. package/src/eval/types.ts +48 -0
  355. package/src/exa/index.ts +2 -0
  356. package/src/exa/mcp-client.ts +370 -0
  357. package/src/exa/types.ts +69 -0
  358. package/src/exec/bash-executor.ts +419 -0
  359. package/src/exec/exec.ts +53 -0
  360. package/src/exec/non-interactive-env.ts +48 -0
  361. package/src/export/custom-share.ts +65 -0
  362. package/src/export/html/index.ts +164 -0
  363. package/src/export/html/template.css +1051 -0
  364. package/src/export/html/template.generated.ts +2 -0
  365. package/src/export/html/template.html +46 -0
  366. package/src/export/html/template.js +2271 -0
  367. package/src/export/html/template.macro.ts +25 -0
  368. package/src/export/html/vendor/highlight.min.js +1213 -0
  369. package/src/export/html/vendor/marked.min.js +6 -0
  370. package/src/export/ttsr.ts +583 -0
  371. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  372. package/src/extensibility/custom-commands/bundled/review/index.ts +489 -0
  373. package/src/extensibility/custom-commands/index.ts +2 -0
  374. package/src/extensibility/custom-commands/loader.ts +238 -0
  375. package/src/extensibility/custom-commands/types.ts +113 -0
  376. package/src/extensibility/custom-tools/index.ts +7 -0
  377. package/src/extensibility/custom-tools/loader.ts +269 -0
  378. package/src/extensibility/custom-tools/types.ts +270 -0
  379. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  380. package/src/extensibility/extensions/compact-handler.ts +40 -0
  381. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  382. package/src/extensibility/extensions/index.ts +16 -0
  383. package/src/extensibility/extensions/loader.ts +572 -0
  384. package/src/extensibility/extensions/runner.ts +922 -0
  385. package/src/extensibility/extensions/types.ts +1322 -0
  386. package/src/extensibility/extensions/wrapper.ts +223 -0
  387. package/src/extensibility/hooks/index.ts +5 -0
  388. package/src/extensibility/hooks/loader.ts +257 -0
  389. package/src/extensibility/hooks/runner.ts +425 -0
  390. package/src/extensibility/hooks/tool-wrapper.ts +107 -0
  391. package/src/extensibility/hooks/types.ts +606 -0
  392. package/src/extensibility/legacy-pi-ai-shim.ts +24 -0
  393. package/src/extensibility/legacy-pi-coding-agent-shim.ts +15 -0
  394. package/src/extensibility/plugins/doctor.ts +65 -0
  395. package/src/extensibility/plugins/git-url.ts +367 -0
  396. package/src/extensibility/plugins/index.ts +9 -0
  397. package/src/extensibility/plugins/installer.ts +192 -0
  398. package/src/extensibility/plugins/legacy-pi-compat.ts +682 -0
  399. package/src/extensibility/plugins/loader.ts +313 -0
  400. package/src/extensibility/plugins/manager.ts +827 -0
  401. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  402. package/src/extensibility/plugins/marketplace/fetcher.ts +317 -0
  403. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  404. package/src/extensibility/plugins/marketplace/manager.ts +770 -0
  405. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  406. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  407. package/src/extensibility/plugins/marketplace/types.ts +191 -0
  408. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  409. package/src/extensibility/plugins/parser.ts +105 -0
  410. package/src/extensibility/plugins/types.ts +194 -0
  411. package/src/extensibility/shared-events.ts +343 -0
  412. package/src/extensibility/skills.ts +312 -0
  413. package/src/extensibility/slash-commands.ts +227 -0
  414. package/src/extensibility/tool-proxy.ts +25 -0
  415. package/src/extensibility/typebox.ts +418 -0
  416. package/src/extensibility/utils.ts +44 -0
  417. package/src/goals/index.ts +3 -0
  418. package/src/goals/runtime.ts +528 -0
  419. package/src/goals/state.ts +37 -0
  420. package/src/goals/tools/goal-tool.ts +251 -0
  421. package/src/hindsight/backend.ts +354 -0
  422. package/src/hindsight/bank.ts +156 -0
  423. package/src/hindsight/client.ts +598 -0
  424. package/src/hindsight/config.ts +175 -0
  425. package/src/hindsight/content.ts +210 -0
  426. package/src/hindsight/index.ts +8 -0
  427. package/src/hindsight/mental-models.ts +429 -0
  428. package/src/hindsight/seeds.json +32 -0
  429. package/src/hindsight/state.ts +488 -0
  430. package/src/hindsight/transcript.ts +71 -0
  431. package/src/index.ts +59 -0
  432. package/src/internal-urls/agent-protocol.ts +146 -0
  433. package/src/internal-urls/artifact-protocol.ts +107 -0
  434. package/src/internal-urls/docs-index.generated.ts +106 -0
  435. package/src/internal-urls/history-protocol.ts +113 -0
  436. package/src/internal-urls/index.ts +25 -0
  437. package/src/internal-urls/issue-pr-protocol.ts +584 -0
  438. package/src/internal-urls/json-query.ts +126 -0
  439. package/src/internal-urls/local-protocol.ts +287 -0
  440. package/src/internal-urls/mcp-protocol.ts +151 -0
  441. package/src/internal-urls/memory-protocol.ts +169 -0
  442. package/src/internal-urls/omp-protocol.ts +93 -0
  443. package/src/internal-urls/parse.ts +72 -0
  444. package/src/internal-urls/registry-helpers.ts +25 -0
  445. package/src/internal-urls/router.ts +105 -0
  446. package/src/internal-urls/rule-protocol.ts +45 -0
  447. package/src/internal-urls/skill-protocol.ts +96 -0
  448. package/src/internal-urls/types.ts +152 -0
  449. package/src/internal-urls/vault-protocol.ts +936 -0
  450. package/src/irc/bus.ts +292 -0
  451. package/src/lib/xai-http.ts +124 -0
  452. package/src/lsp/client.ts +1193 -0
  453. package/src/lsp/clients/biome-client.ts +264 -0
  454. package/src/lsp/clients/index.ts +50 -0
  455. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  456. package/src/lsp/clients/swiftlint-client.ts +120 -0
  457. package/src/lsp/config.ts +502 -0
  458. package/src/lsp/defaults.json +493 -0
  459. package/src/lsp/diagnostics-ledger.ts +51 -0
  460. package/src/lsp/edits.ts +267 -0
  461. package/src/lsp/index.ts +2477 -0
  462. package/src/lsp/lspmux.ts +233 -0
  463. package/src/lsp/render.ts +694 -0
  464. package/src/lsp/startup-events.ts +13 -0
  465. package/src/lsp/types.ts +455 -0
  466. package/src/lsp/utils.ts +718 -0
  467. package/src/main.ts +1325 -0
  468. package/src/mcp/client.ts +484 -0
  469. package/src/mcp/config-writer.ts +225 -0
  470. package/src/mcp/config.ts +365 -0
  471. package/src/mcp/index.ts +29 -0
  472. package/src/mcp/json-rpc.ts +122 -0
  473. package/src/mcp/loader.ts +124 -0
  474. package/src/mcp/manager.ts +1275 -0
  475. package/src/mcp/oauth-discovery.ts +442 -0
  476. package/src/mcp/oauth-flow.ts +442 -0
  477. package/src/mcp/render.ts +132 -0
  478. package/src/mcp/smithery-auth.ts +104 -0
  479. package/src/mcp/smithery-connect.ts +145 -0
  480. package/src/mcp/smithery-registry.ts +477 -0
  481. package/src/mcp/timeout.ts +59 -0
  482. package/src/mcp/tool-bridge.ts +426 -0
  483. package/src/mcp/tool-cache.ts +117 -0
  484. package/src/mcp/transports/http.ts +519 -0
  485. package/src/mcp/transports/index.ts +6 -0
  486. package/src/mcp/transports/stdio.ts +528 -0
  487. package/src/mcp/types.ts +423 -0
  488. package/src/memories/index.ts +1150 -0
  489. package/src/memories/storage.ts +577 -0
  490. package/src/memory-backend/index.ts +18 -0
  491. package/src/memory-backend/local-backend.ts +39 -0
  492. package/src/memory-backend/off-backend.ts +25 -0
  493. package/src/memory-backend/resolve.ts +25 -0
  494. package/src/memory-backend/runtime.ts +66 -0
  495. package/src/memory-backend/types.ts +166 -0
  496. package/src/mnemopi/backend.ts +547 -0
  497. package/src/mnemopi/config.ts +160 -0
  498. package/src/mnemopi/index.ts +3 -0
  499. package/src/mnemopi/state.ts +584 -0
  500. package/src/modes/acp/acp-agent.ts +2407 -0
  501. package/src/modes/acp/acp-client-bridge.ts +154 -0
  502. package/src/modes/acp/acp-event-mapper.ts +929 -0
  503. package/src/modes/acp/acp-mode.ts +23 -0
  504. package/src/modes/acp/index.ts +2 -0
  505. package/src/modes/acp/terminal-auth.ts +37 -0
  506. package/src/modes/components/agent-dashboard.ts +1206 -0
  507. package/src/modes/components/agent-hub.ts +1071 -0
  508. package/src/modes/components/assistant-message.ts +307 -0
  509. package/src/modes/components/bash-execution.ts +220 -0
  510. package/src/modes/components/bordered-loader.ts +41 -0
  511. package/src/modes/components/branch-summary-message.ts +45 -0
  512. package/src/modes/components/btw-panel.ts +104 -0
  513. package/src/modes/components/chat-block.ts +111 -0
  514. package/src/modes/components/compaction-summary-message.ts +87 -0
  515. package/src/modes/components/copy-selector.ts +206 -0
  516. package/src/modes/components/countdown-timer.ts +75 -0
  517. package/src/modes/components/custom-editor.ts +398 -0
  518. package/src/modes/components/custom-message.ts +63 -0
  519. package/src/modes/components/diff.ts +277 -0
  520. package/src/modes/components/dynamic-border.ts +34 -0
  521. package/src/modes/components/error-banner.ts +33 -0
  522. package/src/modes/components/eval-execution.ts +158 -0
  523. package/src/modes/components/execution-shared.ts +101 -0
  524. package/src/modes/components/extensions/extension-dashboard.ts +399 -0
  525. package/src/modes/components/extensions/extension-list.ts +502 -0
  526. package/src/modes/components/extensions/index.ts +9 -0
  527. package/src/modes/components/extensions/inspector-panel.ts +317 -0
  528. package/src/modes/components/extensions/state-manager.ts +627 -0
  529. package/src/modes/components/extensions/types.ts +186 -0
  530. package/src/modes/components/footer.ts +274 -0
  531. package/src/modes/components/history-search.ts +280 -0
  532. package/src/modes/components/hook-editor.ts +167 -0
  533. package/src/modes/components/hook-input.ts +87 -0
  534. package/src/modes/components/hook-message.ts +66 -0
  535. package/src/modes/components/hook-selector.ts +660 -0
  536. package/src/modes/components/index.ts +38 -0
  537. package/src/modes/components/keybinding-hints.ts +65 -0
  538. package/src/modes/components/late-diagnostics-message.ts +60 -0
  539. package/src/modes/components/login-dialog.ts +164 -0
  540. package/src/modes/components/mcp-add-wizard.ts +1340 -0
  541. package/src/modes/components/message-frame.ts +88 -0
  542. package/src/modes/components/model-selector.ts +1271 -0
  543. package/src/modes/components/oauth-selector.ts +368 -0
  544. package/src/modes/components/omfg-panel.ts +141 -0
  545. package/src/modes/components/overlay-box.ts +108 -0
  546. package/src/modes/components/plan-review-overlay.ts +820 -0
  547. package/src/modes/components/plan-toc.ts +138 -0
  548. package/src/modes/components/plugin-selector.ts +95 -0
  549. package/src/modes/components/plugin-settings.ts +722 -0
  550. package/src/modes/components/queue-mode-selector.ts +56 -0
  551. package/src/modes/components/read-tool-group.ts +670 -0
  552. package/src/modes/components/segment-track.ts +52 -0
  553. package/src/modes/components/session-selector.ts +625 -0
  554. package/src/modes/components/settings-defs.ts +189 -0
  555. package/src/modes/components/settings-selector.ts +651 -0
  556. package/src/modes/components/show-images-selector.ts +45 -0
  557. package/src/modes/components/skill-message.ts +89 -0
  558. package/src/modes/components/status-line/component.ts +869 -0
  559. package/src/modes/components/status-line/context-thresholds.ts +79 -0
  560. package/src/modes/components/status-line/git-utils.ts +42 -0
  561. package/src/modes/components/status-line/index.ts +5 -0
  562. package/src/modes/components/status-line/presets.ts +106 -0
  563. package/src/modes/components/status-line/segments.ts +584 -0
  564. package/src/modes/components/status-line/separators.ts +55 -0
  565. package/src/modes/components/status-line/token-rate.ts +66 -0
  566. package/src/modes/components/status-line/types.ts +108 -0
  567. package/src/modes/components/theme-selector.ts +63 -0
  568. package/src/modes/components/thinking-selector.ts +52 -0
  569. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  570. package/src/modes/components/tips.txt +19 -0
  571. package/src/modes/components/todo-reminder.ts +38 -0
  572. package/src/modes/components/tool-execution.ts +1024 -0
  573. package/src/modes/components/transcript-container.ts +608 -0
  574. package/src/modes/components/tree-selector.ts +978 -0
  575. package/src/modes/components/ttsr-notification.ts +122 -0
  576. package/src/modes/components/user-message-selector.ts +227 -0
  577. package/src/modes/components/user-message.ts +66 -0
  578. package/src/modes/components/visual-truncate.ts +63 -0
  579. package/src/modes/components/welcome.ts +493 -0
  580. package/src/modes/controllers/btw-controller.ts +105 -0
  581. package/src/modes/controllers/command-controller-shared.ts +109 -0
  582. package/src/modes/controllers/command-controller.ts +1566 -0
  583. package/src/modes/controllers/event-controller.ts +1054 -0
  584. package/src/modes/controllers/extension-ui-controller.ts +886 -0
  585. package/src/modes/controllers/input-controller.ts +1073 -0
  586. package/src/modes/controllers/mcp-command-controller.ts +2017 -0
  587. package/src/modes/controllers/omfg-controller.ts +283 -0
  588. package/src/modes/controllers/omfg-rule.ts +647 -0
  589. package/src/modes/controllers/selector-controller.ts +1108 -0
  590. package/src/modes/controllers/ssh-command-controller.ts +384 -0
  591. package/src/modes/controllers/streaming-reveal.ts +279 -0
  592. package/src/modes/controllers/tan-command-controller.ts +173 -0
  593. package/src/modes/controllers/todo-command-controller.ts +485 -0
  594. package/src/modes/data/emojis.json +1 -0
  595. package/src/modes/emoji-autocomplete.ts +285 -0
  596. package/src/modes/gradient-highlight.ts +87 -0
  597. package/src/modes/image-references.ts +117 -0
  598. package/src/modes/index.ts +17 -0
  599. package/src/modes/interactive-mode.ts +3370 -0
  600. package/src/modes/internal-url-autocomplete.ts +143 -0
  601. package/src/modes/loop-limit.ts +140 -0
  602. package/src/modes/magic-keywords.ts +20 -0
  603. package/src/modes/markdown-prose.ts +247 -0
  604. package/src/modes/oauth-manual-input.ts +69 -0
  605. package/src/modes/orchestrate.ts +42 -0
  606. package/src/modes/print-mode.ts +126 -0
  607. package/src/modes/prompt-action-autocomplete.ts +260 -0
  608. package/src/modes/rpc/host-tools.ts +186 -0
  609. package/src/modes/rpc/host-uris.ts +235 -0
  610. package/src/modes/rpc/rpc-client.ts +963 -0
  611. package/src/modes/rpc/rpc-mode.ts +947 -0
  612. package/src/modes/rpc/rpc-subagents.ts +265 -0
  613. package/src/modes/rpc/rpc-types.ts +458 -0
  614. package/src/modes/runtime-init.ts +116 -0
  615. package/src/modes/session-observer-registry.ts +146 -0
  616. package/src/modes/setup-version.ts +11 -0
  617. package/src/modes/setup-wizard/index.ts +99 -0
  618. package/src/modes/setup-wizard/lazy.ts +16 -0
  619. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  620. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  621. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  622. package/src/modes/setup-wizard/scenes/sign-in.ts +205 -0
  623. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  624. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  625. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  626. package/src/modes/setup-wizard/scenes/web-search.ts +129 -0
  627. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  628. package/src/modes/shared.ts +47 -0
  629. package/src/modes/theme/dark.json +95 -0
  630. package/src/modes/theme/defaults/alabaster.json +93 -0
  631. package/src/modes/theme/defaults/amethyst.json +96 -0
  632. package/src/modes/theme/defaults/anthracite.json +93 -0
  633. package/src/modes/theme/defaults/basalt.json +91 -0
  634. package/src/modes/theme/defaults/birch.json +95 -0
  635. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  636. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  637. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  638. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  639. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  640. package/src/modes/theme/defaults/dark-copper.json +95 -0
  641. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  642. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  643. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  644. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  645. package/src/modes/theme/defaults/dark-ember.json +95 -0
  646. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  647. package/src/modes/theme/defaults/dark-forest.json +96 -0
  648. package/src/modes/theme/defaults/dark-github.json +105 -0
  649. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  650. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  651. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  652. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  653. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  654. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  655. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  656. package/src/modes/theme/defaults/dark-nord.json +97 -0
  657. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  658. package/src/modes/theme/defaults/dark-one.json +100 -0
  659. package/src/modes/theme/defaults/dark-poimandres.json +142 -0
  660. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  661. package/src/modes/theme/defaults/dark-reef.json +91 -0
  662. package/src/modes/theme/defaults/dark-retro.json +92 -0
  663. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  664. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  665. package/src/modes/theme/defaults/dark-slate.json +95 -0
  666. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  667. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  668. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  669. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  670. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  671. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  672. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  673. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  674. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  675. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  676. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  677. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  678. package/src/modes/theme/defaults/graphite.json +92 -0
  679. package/src/modes/theme/defaults/index.ts +199 -0
  680. package/src/modes/theme/defaults/light-arctic.json +107 -0
  681. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  682. package/src/modes/theme/defaults/light-canyon.json +91 -0
  683. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  684. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  685. package/src/modes/theme/defaults/light-coral.json +95 -0
  686. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  687. package/src/modes/theme/defaults/light-dawn.json +90 -0
  688. package/src/modes/theme/defaults/light-dunes.json +91 -0
  689. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  690. package/src/modes/theme/defaults/light-forest.json +100 -0
  691. package/src/modes/theme/defaults/light-frost.json +95 -0
  692. package/src/modes/theme/defaults/light-github.json +115 -0
  693. package/src/modes/theme/defaults/light-glacier.json +91 -0
  694. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  695. package/src/modes/theme/defaults/light-haze.json +90 -0
  696. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  697. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  698. package/src/modes/theme/defaults/light-lavender.json +95 -0
  699. package/src/modes/theme/defaults/light-meadow.json +91 -0
  700. package/src/modes/theme/defaults/light-mint.json +95 -0
  701. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  702. package/src/modes/theme/defaults/light-ocean.json +99 -0
  703. package/src/modes/theme/defaults/light-one.json +99 -0
  704. package/src/modes/theme/defaults/light-opal.json +91 -0
  705. package/src/modes/theme/defaults/light-orchard.json +91 -0
  706. package/src/modes/theme/defaults/light-paper.json +95 -0
  707. package/src/modes/theme/defaults/light-poimandres.json +142 -0
  708. package/src/modes/theme/defaults/light-prism.json +90 -0
  709. package/src/modes/theme/defaults/light-retro.json +98 -0
  710. package/src/modes/theme/defaults/light-sand.json +95 -0
  711. package/src/modes/theme/defaults/light-savanna.json +91 -0
  712. package/src/modes/theme/defaults/light-solarized.json +102 -0
  713. package/src/modes/theme/defaults/light-soleil.json +90 -0
  714. package/src/modes/theme/defaults/light-sunset.json +99 -0
  715. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  716. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  717. package/src/modes/theme/defaults/light-wetland.json +91 -0
  718. package/src/modes/theme/defaults/light-zenith.json +89 -0
  719. package/src/modes/theme/defaults/limestone.json +94 -0
  720. package/src/modes/theme/defaults/mahogany.json +97 -0
  721. package/src/modes/theme/defaults/marble.json +93 -0
  722. package/src/modes/theme/defaults/obsidian.json +91 -0
  723. package/src/modes/theme/defaults/onyx.json +91 -0
  724. package/src/modes/theme/defaults/pearl.json +93 -0
  725. package/src/modes/theme/defaults/porcelain.json +91 -0
  726. package/src/modes/theme/defaults/quartz.json +96 -0
  727. package/src/modes/theme/defaults/sandstone.json +95 -0
  728. package/src/modes/theme/defaults/titanium.json +90 -0
  729. package/src/modes/theme/light.json +93 -0
  730. package/src/modes/theme/mermaid-cache.ts +29 -0
  731. package/src/modes/theme/shimmer.ts +235 -0
  732. package/src/modes/theme/theme-schema.json +459 -0
  733. package/src/modes/theme/theme.ts +2676 -0
  734. package/src/modes/turn-budget.ts +31 -0
  735. package/src/modes/types.ts +359 -0
  736. package/src/modes/ultrathink.ts +41 -0
  737. package/src/modes/utils/context-usage.ts +339 -0
  738. package/src/modes/utils/copy-targets.ts +360 -0
  739. package/src/modes/utils/hotkeys-markdown.ts +61 -0
  740. package/src/modes/utils/keybinding-matchers.ts +51 -0
  741. package/src/modes/utils/tools-markdown.ts +27 -0
  742. package/src/modes/utils/ui-helpers.ts +801 -0
  743. package/src/modes/workflow.ts +42 -0
  744. package/src/plan-mode/approved-plan.ts +186 -0
  745. package/src/plan-mode/plan-handoff.ts +37 -0
  746. package/src/plan-mode/plan-protection.ts +31 -0
  747. package/src/plan-mode/state.ts +6 -0
  748. package/src/priority.json +41 -0
  749. package/src/prompts/agents/designer.md +66 -0
  750. package/src/prompts/agents/explore.md +58 -0
  751. package/src/prompts/agents/frontmatter.md +11 -0
  752. package/src/prompts/agents/init.md +33 -0
  753. package/src/prompts/agents/librarian.md +119 -0
  754. package/src/prompts/agents/oracle.md +55 -0
  755. package/src/prompts/agents/plan.md +48 -0
  756. package/src/prompts/agents/reviewer.md +140 -0
  757. package/src/prompts/agents/task.md +16 -0
  758. package/src/prompts/ci-green-request.md +36 -0
  759. package/src/prompts/dry-balance-bench.md +8 -0
  760. package/src/prompts/goals/goal-budget-limit.md +16 -0
  761. package/src/prompts/goals/goal-continuation.md +28 -0
  762. package/src/prompts/goals/goal-mode-active.md +23 -0
  763. package/src/prompts/memories/consolidation.md +30 -0
  764. package/src/prompts/memories/read-path.md +11 -0
  765. package/src/prompts/memories/stage_one_input.md +6 -0
  766. package/src/prompts/memories/stage_one_system.md +21 -0
  767. package/src/prompts/review-custom-request.md +22 -0
  768. package/src/prompts/review-headless-request.md +16 -0
  769. package/src/prompts/review-request.md +69 -0
  770. package/src/prompts/steering/user-interjection.md +10 -0
  771. package/src/prompts/system/agent-creation-architect.md +50 -0
  772. package/src/prompts/system/agent-creation-user.md +6 -0
  773. package/src/prompts/system/auto-continue.md +1 -0
  774. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  775. package/src/prompts/system/auto-thinking-difficulty.md +12 -0
  776. package/src/prompts/system/background-tan-dispatch.md +8 -0
  777. package/src/prompts/system/btw-user.md +8 -0
  778. package/src/prompts/system/commit-message-system.md +14 -0
  779. package/src/prompts/system/custom-system-prompt.md +64 -0
  780. package/src/prompts/system/eager-todo.md +13 -0
  781. package/src/prompts/system/empty-stop-retry.md +6 -0
  782. package/src/prompts/system/irc-incoming.md +7 -0
  783. package/src/prompts/system/manual-continue.md +7 -0
  784. package/src/prompts/system/memory-consolidation-system.md +8 -0
  785. package/src/prompts/system/memory-extraction-system.md +26 -0
  786. package/src/prompts/system/omfg-user.md +50 -0
  787. package/src/prompts/system/orchestrate-notice.md +40 -0
  788. package/src/prompts/system/plan-mode-active.md +109 -0
  789. package/src/prompts/system/plan-mode-approved.md +25 -0
  790. package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
  791. package/src/prompts/system/plan-mode-reference.md +11 -0
  792. package/src/prompts/system/plan-mode-subagent.md +33 -0
  793. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  794. package/src/prompts/system/project-prompt.md +52 -0
  795. package/src/prompts/system/subagent-system-prompt.md +64 -0
  796. package/src/prompts/system/subagent-user-prompt.md +3 -0
  797. package/src/prompts/system/subagent-yield-reminder.md +12 -0
  798. package/src/prompts/system/system-prompt.md +258 -0
  799. package/src/prompts/system/tiny-title-system.md +8 -0
  800. package/src/prompts/system/title-system.md +16 -0
  801. package/src/prompts/system/ttsr-interrupt.md +7 -0
  802. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  803. package/src/prompts/system/ultrathink-notice.md +3 -0
  804. package/src/prompts/system/web-search.md +25 -0
  805. package/src/prompts/system/workflow-notice.md +70 -0
  806. package/src/prompts/tools/apply-patch.md +65 -0
  807. package/src/prompts/tools/ask.md +30 -0
  808. package/src/prompts/tools/ast-edit.md +39 -0
  809. package/src/prompts/tools/ast-grep.md +42 -0
  810. package/src/prompts/tools/async-result.md +8 -0
  811. package/src/prompts/tools/bash.md +46 -0
  812. package/src/prompts/tools/browser.md +73 -0
  813. package/src/prompts/tools/checkpoint.md +16 -0
  814. package/src/prompts/tools/debug.md +34 -0
  815. package/src/prompts/tools/eval.md +92 -0
  816. package/src/prompts/tools/find.md +36 -0
  817. package/src/prompts/tools/github.md +21 -0
  818. package/src/prompts/tools/goal.md +18 -0
  819. package/src/prompts/tools/image-gen.md +7 -0
  820. package/src/prompts/tools/inspect-image-system.md +20 -0
  821. package/src/prompts/tools/inspect-image.md +32 -0
  822. package/src/prompts/tools/irc.md +59 -0
  823. package/src/prompts/tools/job.md +19 -0
  824. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  825. package/src/prompts/tools/lsp.md +42 -0
  826. package/src/prompts/tools/memory-edit.md +8 -0
  827. package/src/prompts/tools/patch.md +70 -0
  828. package/src/prompts/tools/read.md +84 -0
  829. package/src/prompts/tools/recall.md +5 -0
  830. package/src/prompts/tools/reflect.md +5 -0
  831. package/src/prompts/tools/render-mermaid.md +9 -0
  832. package/src/prompts/tools/replace.md +30 -0
  833. package/src/prompts/tools/resolve.md +9 -0
  834. package/src/prompts/tools/retain.md +6 -0
  835. package/src/prompts/tools/rewind.md +13 -0
  836. package/src/prompts/tools/search-tool-bm25.md +32 -0
  837. package/src/prompts/tools/search.md +24 -0
  838. package/src/prompts/tools/ssh.md +31 -0
  839. package/src/prompts/tools/task-summary.md +17 -0
  840. package/src/prompts/tools/task.md +88 -0
  841. package/src/prompts/tools/todo.md +62 -0
  842. package/src/prompts/tools/web-search.md +10 -0
  843. package/src/prompts/tools/write.md +14 -0
  844. package/src/registry/agent-lifecycle.ts +218 -0
  845. package/src/registry/agent-registry.ts +151 -0
  846. package/src/sdk.ts +2558 -0
  847. package/src/secrets/index.ts +123 -0
  848. package/src/secrets/obfuscator.ts +298 -0
  849. package/src/secrets/regex.ts +21 -0
  850. package/src/session/agent-session.ts +10121 -0
  851. package/src/session/agent-storage.ts +455 -0
  852. package/src/session/artifacts.ts +135 -0
  853. package/src/session/auth-broker-config.ts +131 -0
  854. package/src/session/auth-storage.ts +29 -0
  855. package/src/session/blob-store.ts +255 -0
  856. package/src/session/client-bridge.ts +85 -0
  857. package/src/session/history-storage.ts +348 -0
  858. package/src/session/indexed-session-storage.ts +430 -0
  859. package/src/session/messages.ts +541 -0
  860. package/src/session/redis-session-storage.ts +170 -0
  861. package/src/session/session-dump-format.ts +209 -0
  862. package/src/session/session-history-format.ts +246 -0
  863. package/src/session/session-manager.ts +3676 -0
  864. package/src/session/session-storage.ts +529 -0
  865. package/src/session/shake-types.ts +43 -0
  866. package/src/session/sql-session-storage.ts +314 -0
  867. package/src/session/streaming-output.ts +1330 -0
  868. package/src/session/tool-choice-queue.ts +213 -0
  869. package/src/session/yield-queue.ts +173 -0
  870. package/src/slash-commands/acp-builtins.ts +70 -0
  871. package/src/slash-commands/builtin-registry.ts +1798 -0
  872. package/src/slash-commands/helpers/context-report.ts +39 -0
  873. package/src/slash-commands/helpers/format.ts +46 -0
  874. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  875. package/src/slash-commands/helpers/mcp.ts +532 -0
  876. package/src/slash-commands/helpers/parse.ts +85 -0
  877. package/src/slash-commands/helpers/ssh.ts +195 -0
  878. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  879. package/src/slash-commands/helpers/todo.ts +279 -0
  880. package/src/slash-commands/helpers/usage-report.ts +95 -0
  881. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  882. package/src/slash-commands/types.ts +135 -0
  883. package/src/ssh/config-writer.ts +183 -0
  884. package/src/ssh/connection-manager.ts +509 -0
  885. package/src/ssh/ssh-executor.ts +189 -0
  886. package/src/ssh/sshfs-mount.ts +140 -0
  887. package/src/ssh/utils.ts +8 -0
  888. package/src/stt/downloader.ts +71 -0
  889. package/src/stt/index.ts +3 -0
  890. package/src/stt/recorder.ts +351 -0
  891. package/src/stt/setup.ts +52 -0
  892. package/src/stt/stt-controller.ts +160 -0
  893. package/src/stt/transcribe.py +70 -0
  894. package/src/stt/transcriber.ts +91 -0
  895. package/src/stubs/natives/index.ts +814 -0
  896. package/src/stubs/natives/package.json +7 -0
  897. package/src/stubs/tui/index.ts +282 -0
  898. package/src/stubs/tui/package.json +7 -0
  899. package/src/system-prompt.ts +611 -0
  900. package/src/task/agents.ts +167 -0
  901. package/src/task/commands.ts +132 -0
  902. package/src/task/discovery.ts +122 -0
  903. package/src/task/executor.ts +2133 -0
  904. package/src/task/index.ts +1419 -0
  905. package/src/task/name-generator.ts +1577 -0
  906. package/src/task/omp-command.ts +26 -0
  907. package/src/task/output-manager.ts +88 -0
  908. package/src/task/parallel.ts +116 -0
  909. package/src/task/render.ts +1381 -0
  910. package/src/task/repair-args.ts +129 -0
  911. package/src/task/subprocess-tool-registry.ts +88 -0
  912. package/src/task/types.ts +336 -0
  913. package/src/task/worktree.ts +514 -0
  914. package/src/telemetry-export.ts +144 -0
  915. package/src/thinking.ts +167 -0
  916. package/src/tiny/compiled-runtime.ts +179 -0
  917. package/src/tiny/device.ts +111 -0
  918. package/src/tiny/dtype.ts +101 -0
  919. package/src/tiny/models.ts +242 -0
  920. package/src/tiny/text.ts +165 -0
  921. package/src/tiny/title-client.ts +543 -0
  922. package/src/tiny/title-protocol.ts +56 -0
  923. package/src/tiny/worker.ts +568 -0
  924. package/src/tool-discovery/mode.ts +24 -0
  925. package/src/tool-discovery/tool-index.ts +256 -0
  926. package/src/tools/approval.ts +189 -0
  927. package/src/tools/archive-reader.ts +721 -0
  928. package/src/tools/ask.ts +928 -0
  929. package/src/tools/ast-edit.ts +642 -0
  930. package/src/tools/ast-grep.ts +452 -0
  931. package/src/tools/auto-generated-guard.ts +322 -0
  932. package/src/tools/bash-command-fixup.ts +37 -0
  933. package/src/tools/bash-interactive.ts +408 -0
  934. package/src/tools/bash-interceptor.ts +67 -0
  935. package/src/tools/bash-pty-selection.ts +14 -0
  936. package/src/tools/bash-skill-urls.ts +248 -0
  937. package/src/tools/bash.ts +1386 -0
  938. package/src/tools/browser/attach.ts +175 -0
  939. package/src/tools/browser/launch.ts +660 -0
  940. package/src/tools/browser/readable.ts +112 -0
  941. package/src/tools/browser/registry.ts +197 -0
  942. package/src/tools/browser/render.ts +216 -0
  943. package/src/tools/browser/tab-protocol.ts +105 -0
  944. package/src/tools/browser/tab-supervisor.ts +628 -0
  945. package/src/tools/browser/tab-worker-entry.ts +21 -0
  946. package/src/tools/browser/tab-worker.ts +1226 -0
  947. package/src/tools/browser.ts +343 -0
  948. package/src/tools/checkpoint.ts +136 -0
  949. package/src/tools/conflict-detect.ts +718 -0
  950. package/src/tools/context.ts +39 -0
  951. package/src/tools/debug.ts +1067 -0
  952. package/src/tools/eval-backends.ts +27 -0
  953. package/src/tools/eval-render.ts +752 -0
  954. package/src/tools/eval.ts +577 -0
  955. package/src/tools/fetch.ts +1926 -0
  956. package/src/tools/file-recorder.ts +35 -0
  957. package/src/tools/find.ts +609 -0
  958. package/src/tools/fs-cache-invalidation.ts +28 -0
  959. package/src/tools/gh-cache-invalidation.ts +255 -0
  960. package/src/tools/gh-format.ts +12 -0
  961. package/src/tools/gh-renderer.ts +481 -0
  962. package/src/tools/gh.ts +3720 -0
  963. package/src/tools/github-cache.ts +637 -0
  964. package/src/tools/grouped-file-output.ts +210 -0
  965. package/src/tools/image-gen.ts +1517 -0
  966. package/src/tools/index.ts +599 -0
  967. package/src/tools/inspect-image-renderer.ts +132 -0
  968. package/src/tools/inspect-image.ts +174 -0
  969. package/src/tools/irc.ts +723 -0
  970. package/src/tools/job.ts +557 -0
  971. package/src/tools/json-tree.ts +243 -0
  972. package/src/tools/jtd-to-json-schema.ts +219 -0
  973. package/src/tools/jtd-to-typescript.ts +136 -0
  974. package/src/tools/jtd-utils.ts +102 -0
  975. package/src/tools/list-limit.ts +40 -0
  976. package/src/tools/match-line-format.ts +20 -0
  977. package/src/tools/memory-edit.ts +59 -0
  978. package/src/tools/memory-recall.ts +100 -0
  979. package/src/tools/memory-reflect.ts +88 -0
  980. package/src/tools/memory-render.ts +202 -0
  981. package/src/tools/memory-retain.ts +91 -0
  982. package/src/tools/output-meta.ts +754 -0
  983. package/src/tools/output-schema-validator.ts +132 -0
  984. package/src/tools/path-utils.ts +1054 -0
  985. package/src/tools/plan-mode-guard.ts +108 -0
  986. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  987. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  988. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  989. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  990. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  991. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  992. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  993. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  994. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  995. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  996. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  997. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  998. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  999. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  1000. package/src/tools/read.ts +2929 -0
  1001. package/src/tools/render-mermaid.ts +69 -0
  1002. package/src/tools/render-utils.ts +838 -0
  1003. package/src/tools/renderers.ts +77 -0
  1004. package/src/tools/report-tool-issue.ts +534 -0
  1005. package/src/tools/resolve.ts +276 -0
  1006. package/src/tools/review.ts +253 -0
  1007. package/src/tools/search-tool-bm25.ts +351 -0
  1008. package/src/tools/search.ts +1580 -0
  1009. package/src/tools/sqlite-reader.ts +828 -0
  1010. package/src/tools/ssh.ts +349 -0
  1011. package/src/tools/todo.ts +982 -0
  1012. package/src/tools/tool-errors.ts +62 -0
  1013. package/src/tools/tool-result.ts +94 -0
  1014. package/src/tools/tool-timeouts.ts +30 -0
  1015. package/src/tools/tts.ts +133 -0
  1016. package/src/tools/write.ts +1217 -0
  1017. package/src/tools/yield.ts +269 -0
  1018. package/src/tui/code-cell.ts +216 -0
  1019. package/src/tui/file-list.ts +55 -0
  1020. package/src/tui/hyperlink.ts +175 -0
  1021. package/src/tui/index.ts +12 -0
  1022. package/src/tui/output-block.ts +240 -0
  1023. package/src/tui/status-line.ts +54 -0
  1024. package/src/tui/tree-list.ts +84 -0
  1025. package/src/tui/types.ts +15 -0
  1026. package/src/tui/utils.ts +103 -0
  1027. package/src/utils/block-context.ts +312 -0
  1028. package/src/utils/changelog.ts +132 -0
  1029. package/src/utils/clipboard.ts +193 -0
  1030. package/src/utils/command-args.ts +76 -0
  1031. package/src/utils/commit-message-generator.ts +151 -0
  1032. package/src/utils/edit-mode.ts +41 -0
  1033. package/src/utils/enhanced-paste.ts +230 -0
  1034. package/src/utils/event-bus.ts +33 -0
  1035. package/src/utils/external-editor.ts +65 -0
  1036. package/src/utils/file-display-mode.ts +45 -0
  1037. package/src/utils/file-mentions.ts +281 -0
  1038. package/src/utils/git.ts +1833 -0
  1039. package/src/utils/image-loading.ts +132 -0
  1040. package/src/utils/image-resize.ts +309 -0
  1041. package/src/utils/jj.ts +248 -0
  1042. package/src/utils/lang-from-path.ts +239 -0
  1043. package/src/utils/markit.ts +89 -0
  1044. package/src/utils/open.ts +55 -0
  1045. package/src/utils/session-color.ts +68 -0
  1046. package/src/utils/shell-snapshot.ts +187 -0
  1047. package/src/utils/sixel.ts +69 -0
  1048. package/src/utils/title-generator.ts +373 -0
  1049. package/src/utils/tool-choice.ts +33 -0
  1050. package/src/utils/tools-manager.ts +363 -0
  1051. package/src/web/kagi.ts +305 -0
  1052. package/src/web/parallel.ts +353 -0
  1053. package/src/web/scrapers/artifacthub.ts +207 -0
  1054. package/src/web/scrapers/arxiv.ts +83 -0
  1055. package/src/web/scrapers/aur.ts +162 -0
  1056. package/src/web/scrapers/biorxiv.ts +133 -0
  1057. package/src/web/scrapers/bluesky.ts +262 -0
  1058. package/src/web/scrapers/brew.ts +172 -0
  1059. package/src/web/scrapers/cheatsh.ts +68 -0
  1060. package/src/web/scrapers/chocolatey.ts +196 -0
  1061. package/src/web/scrapers/choosealicense.ts +95 -0
  1062. package/src/web/scrapers/cisa-kev.ts +87 -0
  1063. package/src/web/scrapers/clojars.ts +154 -0
  1064. package/src/web/scrapers/coingecko.ts +177 -0
  1065. package/src/web/scrapers/crates-io.ts +97 -0
  1066. package/src/web/scrapers/crossref.ts +136 -0
  1067. package/src/web/scrapers/devto.ts +147 -0
  1068. package/src/web/scrapers/discogs.ts +306 -0
  1069. package/src/web/scrapers/discourse.ts +197 -0
  1070. package/src/web/scrapers/dockerhub.ts +138 -0
  1071. package/src/web/scrapers/docs-rs.ts +653 -0
  1072. package/src/web/scrapers/fdroid.ts +134 -0
  1073. package/src/web/scrapers/firefox-addons.ts +191 -0
  1074. package/src/web/scrapers/flathub.ts +223 -0
  1075. package/src/web/scrapers/github-gist.ts +58 -0
  1076. package/src/web/scrapers/github.ts +704 -0
  1077. package/src/web/scrapers/gitlab.ts +401 -0
  1078. package/src/web/scrapers/go-pkg.ts +266 -0
  1079. package/src/web/scrapers/hackage.ts +140 -0
  1080. package/src/web/scrapers/hackernews.ts +189 -0
  1081. package/src/web/scrapers/hex.ts +105 -0
  1082. package/src/web/scrapers/huggingface.ts +321 -0
  1083. package/src/web/scrapers/iacr.ts +89 -0
  1084. package/src/web/scrapers/index.ts +252 -0
  1085. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1086. package/src/web/scrapers/lemmy.ts +203 -0
  1087. package/src/web/scrapers/lobsters.ts +175 -0
  1088. package/src/web/scrapers/mastodon.ts +292 -0
  1089. package/src/web/scrapers/maven.ts +138 -0
  1090. package/src/web/scrapers/mdn.ts +173 -0
  1091. package/src/web/scrapers/metacpan.ts +222 -0
  1092. package/src/web/scrapers/musicbrainz.ts +250 -0
  1093. package/src/web/scrapers/npm.ts +98 -0
  1094. package/src/web/scrapers/nuget.ts +183 -0
  1095. package/src/web/scrapers/nvd.ts +222 -0
  1096. package/src/web/scrapers/ollama.ts +239 -0
  1097. package/src/web/scrapers/open-vsx.ts +106 -0
  1098. package/src/web/scrapers/opencorporates.ts +292 -0
  1099. package/src/web/scrapers/openlibrary.ts +336 -0
  1100. package/src/web/scrapers/orcid.ts +286 -0
  1101. package/src/web/scrapers/osv.ts +176 -0
  1102. package/src/web/scrapers/packagist.ts +160 -0
  1103. package/src/web/scrapers/pub-dev.ts +143 -0
  1104. package/src/web/scrapers/pubmed.ts +211 -0
  1105. package/src/web/scrapers/pypi.ts +112 -0
  1106. package/src/web/scrapers/rawg.ts +110 -0
  1107. package/src/web/scrapers/readthedocs.ts +120 -0
  1108. package/src/web/scrapers/reddit.ts +95 -0
  1109. package/src/web/scrapers/repology.ts +251 -0
  1110. package/src/web/scrapers/rfc.ts +201 -0
  1111. package/src/web/scrapers/rubygems.ts +103 -0
  1112. package/src/web/scrapers/searchcode.ts +189 -0
  1113. package/src/web/scrapers/sec-edgar.ts +261 -0
  1114. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1115. package/src/web/scrapers/snapcraft.ts +187 -0
  1116. package/src/web/scrapers/sourcegraph.ts +336 -0
  1117. package/src/web/scrapers/spdx.ts +108 -0
  1118. package/src/web/scrapers/spotify.ts +198 -0
  1119. package/src/web/scrapers/stackoverflow.ts +120 -0
  1120. package/src/web/scrapers/terraform.ts +277 -0
  1121. package/src/web/scrapers/tldr.ts +47 -0
  1122. package/src/web/scrapers/twitter.ts +94 -0
  1123. package/src/web/scrapers/types.ts +397 -0
  1124. package/src/web/scrapers/utils.ts +109 -0
  1125. package/src/web/scrapers/vimeo.ts +133 -0
  1126. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1127. package/src/web/scrapers/w3c.ts +156 -0
  1128. package/src/web/scrapers/wikidata.ts +344 -0
  1129. package/src/web/scrapers/wikipedia.ts +84 -0
  1130. package/src/web/scrapers/youtube.ts +325 -0
  1131. package/src/web/search/index.ts +292 -0
  1132. package/src/web/search/provider.ts +157 -0
  1133. package/src/web/search/providers/anthropic.ts +318 -0
  1134. package/src/web/search/providers/base.ts +89 -0
  1135. package/src/web/search/providers/brave.ts +152 -0
  1136. package/src/web/search/providers/codex.ts +591 -0
  1137. package/src/web/search/providers/exa.ts +400 -0
  1138. package/src/web/search/providers/gemini.ts +460 -0
  1139. package/src/web/search/providers/jina.ts +111 -0
  1140. package/src/web/search/providers/kagi.ts +86 -0
  1141. package/src/web/search/providers/kimi.ts +196 -0
  1142. package/src/web/search/providers/parallel.ts +225 -0
  1143. package/src/web/search/providers/perplexity.ts +730 -0
  1144. package/src/web/search/providers/searxng.ts +313 -0
  1145. package/src/web/search/providers/synthetic.ts +114 -0
  1146. package/src/web/search/providers/tavily.ts +176 -0
  1147. package/src/web/search/providers/utils.ts +128 -0
  1148. package/src/web/search/providers/zai.ts +333 -0
  1149. package/src/web/search/render.ts +262 -0
  1150. package/src/web/search/types.ts +482 -0
  1151. package/src/web/search/utils.ts +17 -0
  1152. package/src/workspace-tree.ts +286 -0
@@ -0,0 +1,1502 @@
1
+ /**
2
+ * Model resolution, scoping, and initial selection.
3
+ *
4
+ * Layering:
5
+ * - `matchModel` is the single matching engine. Order: exact `provider/id`
6
+ * reference (with OpenRouter routed/date fallbacks) → exact canonical id →
7
+ * exact bare id → provider-scoped fuzzy → substring with alias-vs-dated pick.
8
+ * - `parseModelPatternWithContext`/`parseModelPattern` layer the selector
9
+ * grammar on top: trailing `:level` thinking suffixes (`splitThinkingSuffix`)
10
+ * and `@upstream` provider routing (`splitUpstreamRouting`).
11
+ * - Everything else (`resolveModelFromString`, `resolveModelOverride*`,
12
+ * `resolveRoleSelection`, `resolveModelScope`, `resolveCliModel`,
13
+ * `findSmolModel`/`findSlowModel`) adapts inputs — roles, settings patterns,
14
+ * CLI flags, scope globs — onto that pipeline.
15
+ */
16
+
17
+ import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
18
+ import type { Api, Effort, KnownProvider, Model, ModelSpec } from "@oh-my-pi/pi-ai";
19
+ import { buildModel } from "@oh-my-pi/pi-catalog/build";
20
+ import { modelMatchesHost } from "@oh-my-pi/pi-catalog/hosts";
21
+ import { buildModelProviderPriorityRank } from "@oh-my-pi/pi-catalog/identity";
22
+ import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
23
+ import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
24
+ import { DEFAULT_MODEL_PER_PROVIDER } from "@oh-my-pi/pi-catalog/provider-models";
25
+ import { fuzzyMatch } from './stubs/tui/index.ts';
26
+ import { logger } from "@oh-my-pi/pi-utils";
27
+ import chalk from "chalk";
28
+ import MODEL_PRIO from "../priority.json" with { type: "json" };
29
+ import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking";
30
+ import { isAuthenticated, kNoAuth, type ModelRegistry } from "./model-registry";
31
+ import { MODEL_ROLE_IDS, type ModelRole } from "./model-roles";
32
+ import type { Settings } from "./settings";
33
+
34
+ /**
35
+ * Pick the first available model matching a known provider's default id
36
+ * (catalog table order), falling back to the first available model.
37
+ */
38
+ function pickDefaultAvailableModel(availableModels: Model<Api>[]): Model<Api> | undefined {
39
+ for (const provider of Object.keys(DEFAULT_MODEL_PER_PROVIDER) as KnownProvider[]) {
40
+ const defaultId = DEFAULT_MODEL_PER_PROVIDER[provider];
41
+ const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
42
+ if (match) return match;
43
+ }
44
+ return availableModels[0];
45
+ }
46
+
47
+ export interface ScopedModel {
48
+ model: Model<Api>;
49
+ thinkingLevel?: ThinkingLevel;
50
+ explicitThinkingLevel: boolean;
51
+ }
52
+
53
+ /**
54
+ * Split a trailing `:<level>` thinking selector off a model pattern.
55
+ *
56
+ * `level` is set only when the suffix parses as a valid thinking level, in
57
+ * which case `base` has the suffix stripped; otherwise `base` is the input.
58
+ * `minColonIndex` requires the colon to appear strictly after that index —
59
+ * role-alias callers pass `PREFIX_MODEL_ROLE.length` so the base is at least
60
+ * as long as the `pi/` prefix.
61
+ */
62
+ function splitThinkingSuffix(pattern: string, minColonIndex = -1): { base: string; level?: ThinkingLevel } {
63
+ const colonIdx = pattern.lastIndexOf(":");
64
+ if (colonIdx <= minColonIndex) return { base: pattern };
65
+ const level = parseThinkingLevel(pattern.slice(colonIdx + 1));
66
+ return level ? { base: pattern.slice(0, colonIdx), level } : { base: pattern };
67
+ }
68
+
69
+ /**
70
+ * Parse a model string in "provider/modelId" format.
71
+ * Returns undefined if the format is invalid.
72
+ */
73
+ export function parseModelString(
74
+ modelStr: string,
75
+ ): { provider: string; id: string; thinkingLevel?: ThinkingLevel } | undefined {
76
+ const slashIdx = modelStr.indexOf("/");
77
+ if (slashIdx <= 0) return undefined;
78
+ const id = modelStr.slice(slashIdx + 1);
79
+ const provider = modelStr.slice(0, slashIdx);
80
+ // Strip valid thinking level suffix (e.g., "claude-sonnet-4-6:high" -> id "claude-sonnet-4-6", thinkingLevel "high")
81
+ const { base, level } = splitThinkingSuffix(id);
82
+ return level ? { provider, id: base, thinkingLevel: level } : { provider, id };
83
+ }
84
+
85
+ /**
86
+ * Format a model as "provider/modelId" string.
87
+ */
88
+ export function formatModelString(model: Model<Api>): string {
89
+ return `${model.provider}/${model.id}`;
90
+ }
91
+
92
+ export function formatModelSelectorValue(selector: string, thinkingLevel: ThinkingLevel | undefined): string {
93
+ return thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit ? `${selector}:${thinkingLevel}` : selector;
94
+ }
95
+
96
+ function getOpenRouterRouteSuffix(modelId: string): { baseId: string; suffix: string } | undefined {
97
+ const colonIdx = modelId.lastIndexOf(":");
98
+ if (colonIdx === -1) {
99
+ return undefined;
100
+ }
101
+
102
+ const suffix = modelId.slice(colonIdx + 1).trim();
103
+ if (!suffix || parseThinkingLevel(suffix)) {
104
+ return undefined;
105
+ }
106
+
107
+ return { baseId: modelId.slice(0, colonIdx), suffix };
108
+ }
109
+
110
+ function stripOpenRouterDateSuffix(modelId: string): string | undefined {
111
+ const stripped = modelId.replace(/-\d{8}(?=$|:)/i, "");
112
+ return stripped !== modelId ? stripped : undefined;
113
+ }
114
+
115
+ function getOpenRouterFallbackModelIds(modelId: string): string[] {
116
+ const orderedCandidates: string[] = [];
117
+ const queue = [modelId];
118
+ const seen = new Set<string>();
119
+
120
+ while (queue.length > 0) {
121
+ const candidate = queue.shift();
122
+ if (!candidate || seen.has(candidate)) {
123
+ continue;
124
+ }
125
+ seen.add(candidate);
126
+ orderedCandidates.push(candidate);
127
+
128
+ const routedSuffix = getOpenRouterRouteSuffix(candidate);
129
+ if (routedSuffix) {
130
+ queue.push(routedSuffix.baseId);
131
+ }
132
+
133
+ const strippedDate = stripOpenRouterDateSuffix(candidate);
134
+ if (strippedDate) {
135
+ queue.push(strippedDate);
136
+ }
137
+ }
138
+
139
+ return orderedCandidates;
140
+ }
141
+
142
+ function cloneModelWithRequestedId(model: Model<Api>, requestedId: string): Model<Api> {
143
+ return {
144
+ ...model,
145
+ id: requestedId,
146
+ ...(model.name === model.id ? { name: requestedId } : {}),
147
+ };
148
+ }
149
+
150
+ const UPSTREAM_ROUTING_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
151
+
152
+ /**
153
+ * Split a trailing `@<upstream>` provider-routing selector off a model pattern.
154
+ *
155
+ * `openrouter/z-ai/glm-4.7@cerebras` -> base `openrouter/z-ai/glm-4.7`, upstream
156
+ * `cerebras`. A `:thinking` suffix after the slug is kept on the base
157
+ * (`...@cerebras:high` -> base `...:high`). Returns undefined when there is no
158
+ * `@` or the suffix is not a bare provider slug, so model ids that legitimately
159
+ * contain `@` (`claude-opus-4-8@default`, `workers-ai/@cf/...`) are never split.
160
+ */
161
+ function splitUpstreamRouting(pattern: string): { base: string; upstream: string } | undefined {
162
+ const at = pattern.lastIndexOf("@");
163
+ if (at <= 0) return undefined;
164
+ const rest = pattern.slice(at + 1);
165
+ const colon = rest.indexOf(":");
166
+ const upstream = colon === -1 ? rest : rest.slice(0, colon);
167
+ if (!UPSTREAM_ROUTING_SLUG.test(upstream)) return undefined;
168
+ const trailing = colon === -1 ? "" : rest.slice(colon);
169
+ return { base: pattern.slice(0, at) + trailing, upstream };
170
+ }
171
+
172
+ /** OpenRouter and Vercel AI Gateway are the aggregators that honor per-request upstream routing. */
173
+ function supportsUpstreamRouting(model: Model<Api>): boolean {
174
+ return modelMatchesHost(model, "openrouter") || modelMatchesHost(model, "vercelAIGateway");
175
+ }
176
+
177
+ /** Pin a resolved aggregator model to a single upstream provider via its compat routing block. */
178
+ function applyUpstreamRouting(model: Model<Api>, upstream: string): Model<Api> {
179
+ const aggregatorModel = model as Model<"openai-completions">;
180
+ const routing = { only: [upstream] };
181
+ return buildModel({
182
+ ...model,
183
+ compat: modelMatchesHost(model, "vercelAIGateway")
184
+ ? { ...aggregatorModel.compatConfig, vercelGatewayRouting: routing }
185
+ : { ...aggregatorModel.compatConfig, openRouterRouting: routing },
186
+ } as ModelSpec<Api>);
187
+ }
188
+
189
+ const kProviderModelIndex = Symbol("model-resolver.providerIndex");
190
+ type ModelsWithProviderIndex = readonly Model<Api>[] & {
191
+ [kProviderModelIndex]?: Map<string, Model<Api> | null>;
192
+ };
193
+
194
+ function getProviderModelIndex(availableModels: readonly Model<Api>[]): Map<string, Model<Api> | null> {
195
+ const tagged = availableModels as ModelsWithProviderIndex;
196
+ const cached = tagged[kProviderModelIndex];
197
+ if (cached) return cached;
198
+ const index = new Map<string, Model<Api> | null>();
199
+ for (const m of availableModels) {
200
+ const key = `${m.provider.toLowerCase()}\u0000${m.id.toLowerCase()}`;
201
+ if (index.has(key)) {
202
+ index.set(key, null); // ambiguous sentinel; do not overwrite back
203
+ } else {
204
+ index.set(key, m);
205
+ }
206
+ }
207
+ tagged[kProviderModelIndex] = index;
208
+ return index;
209
+ }
210
+
211
+ export function resolveProviderModelReference(
212
+ provider: string,
213
+ modelId: string,
214
+ availableModels: readonly Model<Api>[],
215
+ ): Model<Api> | undefined {
216
+ const normalizedProvider = provider.trim().toLowerCase();
217
+ const normalizedModelId = modelId.trim().toLowerCase();
218
+ if (!normalizedProvider || !normalizedModelId) {
219
+ return undefined;
220
+ }
221
+
222
+ const index = getProviderModelIndex(availableModels);
223
+ const exact = index.get(`${normalizedProvider}\u0000${normalizedModelId}`);
224
+ if (exact === null) {
225
+ return undefined; // ambiguous
226
+ }
227
+ if (exact !== undefined) {
228
+ return exact;
229
+ }
230
+
231
+ if (normalizedProvider !== "openrouter") {
232
+ return undefined;
233
+ }
234
+
235
+ for (const fallbackId of getOpenRouterFallbackModelIds(modelId).slice(1)) {
236
+ const fallback = index.get(`${normalizedProvider}\u0000${fallbackId.toLowerCase()}`);
237
+ if (fallback === null) {
238
+ return undefined;
239
+ }
240
+ if (fallback !== undefined) {
241
+ return cloneModelWithRequestedId(fallback, modelId);
242
+ }
243
+ }
244
+
245
+ return undefined;
246
+ }
247
+
248
+ export interface ModelMatchPreferences {
249
+ /** Most-recently-used model keys (provider/modelId) to prefer when ambiguous. */
250
+ usageOrder?: string[];
251
+ /** Provider precedence used for ambiguous unqualified model patterns. */
252
+ providerOrder?: readonly string[];
253
+ /** Providers to deprioritize when no recent usage or provider priority is available. */
254
+ deprioritizeProviders?: string[];
255
+ }
256
+
257
+ export type CanonicalModelRegistry = Partial<
258
+ Pick<ModelRegistry, "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId">
259
+ >;
260
+ export type ModelLookupRegistry = Pick<ModelRegistry, "getAvailable"> & Partial<CanonicalModelRegistry>;
261
+ type CliModelRegistry = Pick<ModelRegistry, "getAll"> & Partial<CanonicalModelRegistry>;
262
+ type InitialModelRegistry = Pick<ModelRegistry, "getAvailable" | "find">;
263
+ type RestorableModelRegistry = Pick<ModelRegistry, "getAvailable" | "find" | "getApiKey">;
264
+
265
+ interface ModelPreferenceContext {
266
+ modelUsageRank: Map<string, number>;
267
+ providerUsageRank: Map<string, number>;
268
+ providerPriorityRank: Map<string, number>;
269
+ deprioritizedProviders: Set<string>;
270
+ modelOrder: Map<string, number>;
271
+ }
272
+
273
+ function buildPreferenceContext(
274
+ availableModels: Model<Api>[],
275
+ preferences: ModelMatchPreferences | undefined,
276
+ ): ModelPreferenceContext {
277
+ const modelUsageRank = new Map<string, number>();
278
+ const providerUsageRank = new Map<string, number>();
279
+ const usageOrder = preferences?.usageOrder ?? [];
280
+ for (let i = 0; i < usageOrder.length; i += 1) {
281
+ const key = usageOrder[i];
282
+ if (!modelUsageRank.has(key)) {
283
+ modelUsageRank.set(key, i);
284
+ }
285
+ const parsed = parseModelString(key);
286
+ if (parsed && !providerUsageRank.has(parsed.provider)) {
287
+ providerUsageRank.set(parsed.provider, i);
288
+ }
289
+ }
290
+ const providerPriorityRank = buildModelProviderPriorityRank(preferences?.providerOrder);
291
+ const deprioritizedProviders = new Set(preferences?.deprioritizeProviders ?? []);
292
+ const modelOrder = new Map<string, number>();
293
+ for (let i = 0; i < availableModels.length; i += 1) {
294
+ modelOrder.set(formatModelString(availableModels[i]), i);
295
+ }
296
+
297
+ return { modelUsageRank, providerUsageRank, providerPriorityRank, deprioritizedProviders, modelOrder };
298
+ }
299
+
300
+ export function getModelMatchPreferences(
301
+ settings?: Partial<Pick<Settings, "get" | "getStorage">>,
302
+ ): ModelMatchPreferences {
303
+ return {
304
+ usageOrder: settings?.getStorage?.()?.getModelUsageOrder(),
305
+ providerOrder: settings?.get?.("modelProviderOrder"),
306
+ };
307
+ }
308
+
309
+ function mergeModelMatchPreferences(
310
+ settings: Settings | undefined,
311
+ preferences: ModelMatchPreferences | undefined,
312
+ ): ModelMatchPreferences {
313
+ const settingsPreferences = getModelMatchPreferences(settings);
314
+ return {
315
+ usageOrder: preferences?.usageOrder ?? settingsPreferences.usageOrder,
316
+ providerOrder: preferences?.providerOrder ?? settingsPreferences.providerOrder,
317
+ deprioritizeProviders: preferences?.deprioritizeProviders,
318
+ };
319
+ }
320
+
321
+ function pickPreferredModel(candidates: Model<Api>[], context: ModelPreferenceContext): Model<Api> {
322
+ if (candidates.length <= 1) return candidates[0];
323
+ return [...candidates].sort((a, b) => {
324
+ const aKey = formatModelString(a);
325
+ const bKey = formatModelString(b);
326
+ const aUsage = context.modelUsageRank.get(aKey);
327
+ const bUsage = context.modelUsageRank.get(bKey);
328
+ if (aUsage !== undefined || bUsage !== undefined) {
329
+ return (aUsage ?? Number.POSITIVE_INFINITY) - (bUsage ?? Number.POSITIVE_INFINITY);
330
+ }
331
+
332
+ const aProviderPriority = context.providerPriorityRank.get(a.provider.toLowerCase());
333
+ const bProviderPriority = context.providerPriorityRank.get(b.provider.toLowerCase());
334
+ if (aProviderPriority !== undefined || bProviderPriority !== undefined) {
335
+ return (aProviderPriority ?? Number.POSITIVE_INFINITY) - (bProviderPriority ?? Number.POSITIVE_INFINITY);
336
+ }
337
+
338
+ const aProviderUsage = context.providerUsageRank.get(a.provider);
339
+ const bProviderUsage = context.providerUsageRank.get(b.provider);
340
+ if (aProviderUsage !== undefined || bProviderUsage !== undefined) {
341
+ return (aProviderUsage ?? Number.POSITIVE_INFINITY) - (bProviderUsage ?? Number.POSITIVE_INFINITY);
342
+ }
343
+
344
+ const aDeprioritized = context.deprioritizedProviders.has(a.provider);
345
+ const bDeprioritized = context.deprioritizedProviders.has(b.provider);
346
+ if (aDeprioritized !== bDeprioritized) {
347
+ return aDeprioritized ? 1 : -1;
348
+ }
349
+
350
+ const aOrder = context.modelOrder.get(aKey) ?? 0;
351
+ const bOrder = context.modelOrder.get(bKey) ?? 0;
352
+ return aOrder - bOrder;
353
+ })[0];
354
+ }
355
+
356
+ /**
357
+ * Helper to check if a model ID looks like an alias (no date suffix)
358
+ * Dates are typically in format: -20241022 or -20250929
359
+ */
360
+ function isAlias(id: string): boolean {
361
+ // Check if ID ends with -latest
362
+ if (id.endsWith("-latest")) return true;
363
+
364
+ // Check if ID ends with a date pattern (-YYYYMMDD)
365
+ const datePattern = /-\d{8}$/;
366
+ return !datePattern.test(id);
367
+ }
368
+
369
+ /**
370
+ * Find an exact explicit provider/model match.
371
+ * Bare model ids are handled separately so canonical ids can coalesce variants.
372
+ */
373
+ function findExactModelReferenceMatch(modelReference: string, availableModels: Model<Api>[]): Model<Api> | undefined {
374
+ const trimmedReference = modelReference.trim();
375
+ if (!trimmedReference) {
376
+ return undefined;
377
+ }
378
+
379
+ const slashIndex = trimmedReference.indexOf("/");
380
+ if (slashIndex !== -1) {
381
+ const provider = trimmedReference.substring(0, slashIndex).trim();
382
+ const modelId = trimmedReference.substring(slashIndex + 1).trim();
383
+ if (provider && modelId) {
384
+ return resolveProviderModelReference(provider, modelId, availableModels);
385
+ }
386
+ }
387
+ return undefined;
388
+ }
389
+
390
+ function findExactCanonicalModelMatch(
391
+ modelReference: string,
392
+ availableModels: Model<Api>[],
393
+ modelRegistry: CanonicalModelRegistry | undefined,
394
+ ): Model<Api> | undefined {
395
+ if (!modelRegistry) {
396
+ return undefined;
397
+ }
398
+ const trimmedReference = modelReference.trim();
399
+ if (!trimmedReference || trimmedReference.includes("/")) {
400
+ return undefined;
401
+ }
402
+ return modelRegistry.resolveCanonicalModel?.(trimmedReference, {
403
+ availableOnly: false,
404
+ candidates: availableModels,
405
+ });
406
+ }
407
+
408
+ /**
409
+ * The single model-matching engine. Tries, in order:
410
+ * 1. exact `provider/id` reference (OpenRouter routed/date fallbacks included),
411
+ * 2. exact canonical id (coalesces provider variants),
412
+ * 3. exact bare id (preference-ranked),
413
+ * 4. provider-scoped fuzzy match,
414
+ * 5. substring match with the alias-vs-dated pick.
415
+ * Returns the matched model or undefined if no match found.
416
+ */
417
+ function matchModel(
418
+ modelPattern: string,
419
+ availableModels: Model<Api>[],
420
+ context: ModelPreferenceContext,
421
+ options?: { modelRegistry?: CanonicalModelRegistry },
422
+ ): Model<Api> | undefined {
423
+ // Explicit provider/model selectors always bypass canonical coalescing.
424
+ const exactRefMatch = findExactModelReferenceMatch(modelPattern, availableModels);
425
+ if (exactRefMatch) {
426
+ return exactRefMatch;
427
+ }
428
+
429
+ // Exact canonical ids coalesce provider variants before bare-id matching.
430
+ const exactCanonicalMatch = findExactCanonicalModelMatch(modelPattern, availableModels, options?.modelRegistry);
431
+ if (exactCanonicalMatch) {
432
+ return exactCanonicalMatch;
433
+ }
434
+
435
+ // Exact ID match (case-insensitive) — this must happen before provider-scoped
436
+ // fuzzy matching so raw IDs that contain slashes (for example OpenRouter model
437
+ // IDs like "openai/gpt-4o:extended") still resolve as IDs instead of being
438
+ // misread as a provider-qualified selector.
439
+ const exactMatches = availableModels.filter(m => m.id.toLowerCase() === modelPattern.toLowerCase());
440
+ if (exactMatches.length > 0) {
441
+ return pickPreferredModel(exactMatches, context);
442
+ }
443
+ // Check for provider/modelId format — fuzzy match within provider only.
444
+ const slashIndex = modelPattern.indexOf("/");
445
+ if (slashIndex !== -1) {
446
+ const provider = modelPattern.substring(0, slashIndex);
447
+ const modelId = modelPattern.substring(slashIndex + 1);
448
+ const providerModels = availableModels.filter(m => m.provider.toLowerCase() === provider.toLowerCase());
449
+ if (providerModels.length === 0) {
450
+ // The prefix is not a known provider in this candidate set, so treat the
451
+ // slash as part of the raw model ID and continue with generic matching.
452
+ } else {
453
+ const scored = providerModels
454
+ .map(model => ({ model, match: fuzzyMatch(modelId, model.id) }))
455
+ .filter(entry => entry.match.matches);
456
+ if (scored.length === 0) {
457
+ return undefined;
458
+ }
459
+
460
+ scored.sort((a, b) => {
461
+ if (a.match.score !== b.match.score) return a.match.score - b.match.score;
462
+ const aKey = formatModelString(a.model);
463
+ const bKey = formatModelString(b.model);
464
+ const aUsage = context.modelUsageRank.get(aKey) ?? Number.POSITIVE_INFINITY;
465
+ const bUsage = context.modelUsageRank.get(bKey) ?? Number.POSITIVE_INFINITY;
466
+ if (aUsage !== bUsage) return aUsage - bUsage;
467
+
468
+ const aProviderUsage = context.providerUsageRank.get(a.model.provider) ?? Number.POSITIVE_INFINITY;
469
+ const bProviderUsage = context.providerUsageRank.get(b.model.provider) ?? Number.POSITIVE_INFINITY;
470
+ if (aProviderUsage !== bProviderUsage) return aProviderUsage - bProviderUsage;
471
+
472
+ const aOrder = context.modelOrder.get(aKey) ?? 0;
473
+ const bOrder = context.modelOrder.get(bKey) ?? 0;
474
+ return aOrder - bOrder;
475
+ });
476
+ return scored[0]?.model;
477
+ }
478
+ }
479
+
480
+ // No exact match - fall back to partial matching
481
+ const matches = availableModels.filter(
482
+ m =>
483
+ m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
484
+ m.name?.toLowerCase().includes(modelPattern.toLowerCase()),
485
+ );
486
+
487
+ if (matches.length === 0) {
488
+ return undefined;
489
+ }
490
+
491
+ // Separate into aliases and dated versions
492
+ const aliases = matches.filter(m => isAlias(m.id));
493
+ const datedVersions = matches.filter(m => !isAlias(m.id));
494
+
495
+ if (aliases.length > 0) {
496
+ return pickPreferredModel(aliases, context);
497
+ }
498
+ if (datedVersions.length === 0) return undefined;
499
+
500
+ if (datedVersions.length === 1) {
501
+ return datedVersions[0];
502
+ }
503
+
504
+ const sortedById = [...datedVersions].sort((a, b) => b.id.localeCompare(a.id));
505
+ const topId = sortedById[0]?.id;
506
+ if (!topId) return undefined;
507
+ const topCandidates = sortedById.filter(model => model.id === topId);
508
+ return pickPreferredModel(topCandidates, context);
509
+ }
510
+
511
+ export interface ParsedModelResult {
512
+ model: Model<Api> | undefined;
513
+ /** Thinking level if explicitly specified in pattern, undefined otherwise */
514
+ thinkingLevel?: ThinkingLevel;
515
+ /** Upstream provider slug from an `@upstream` routing selector, if present. */
516
+ upstream?: string;
517
+ warning: string | undefined;
518
+ explicitThinkingLevel: boolean;
519
+ }
520
+
521
+ /**
522
+ * Parse a pattern to extract model and thinking level.
523
+ * Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).
524
+ *
525
+ * Algorithm:
526
+ * 1. Try to match full pattern as a model
527
+ * 2. If found, return it with undefined thinking level
528
+ * 3. If not found and has colons, split on last colon:
529
+ * - If suffix is valid thinking level, use it and recurse on prefix
530
+ * - If suffix is invalid, warn and recurse on prefix
531
+ *
532
+ * @internal Exported for testing
533
+ */
534
+ function parseModelPatternWithContext(
535
+ pattern: string,
536
+ availableModels: Model<Api>[],
537
+ context: ModelPreferenceContext,
538
+ options?: { allowInvalidThinkingSelectorFallback?: boolean; modelRegistry?: CanonicalModelRegistry },
539
+ ): ParsedModelResult {
540
+ // Try exact match first
541
+ const exactMatch = matchModel(pattern, availableModels, context, options);
542
+ if (exactMatch) {
543
+ return { model: exactMatch, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
544
+ }
545
+
546
+ // No match - try stripping a valid thinking suffix and recursing
547
+ const { base, level } = splitThinkingSuffix(pattern);
548
+ if (level) {
549
+ const result = parseModelPatternWithContext(base, availableModels, context, options);
550
+ if (result.model) {
551
+ // Only use this thinking level if no warning from inner recursion
552
+ const explicitThinkingLevel = !result.warning;
553
+ return {
554
+ model: result.model,
555
+ thinkingLevel: explicitThinkingLevel ? level : undefined,
556
+ warning: result.warning,
557
+ explicitThinkingLevel,
558
+ };
559
+ }
560
+ return result;
561
+ }
562
+
563
+ const lastColonIndex = pattern.lastIndexOf(":");
564
+ if (lastColonIndex === -1) {
565
+ // No colons, pattern simply doesn't match any model
566
+ return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
567
+ }
568
+ const prefix = pattern.substring(0, lastColonIndex);
569
+ const suffix = pattern.substring(lastColonIndex + 1);
570
+
571
+ const allowFallback = options?.allowInvalidThinkingSelectorFallback ?? true;
572
+ if (!allowFallback) {
573
+ return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
574
+ }
575
+
576
+ // Invalid suffix - recurse on prefix and warn
577
+ const result = parseModelPatternWithContext(prefix, availableModels, context, options);
578
+ if (result.model) {
579
+ return {
580
+ model: result.model,
581
+ thinkingLevel: undefined,
582
+ warning: `Invalid thinking level "${suffix}" in pattern "${pattern}". Using default instead.`,
583
+ explicitThinkingLevel: false,
584
+ };
585
+ }
586
+ return result;
587
+ }
588
+
589
+ export function parseModelPattern(
590
+ pattern: string,
591
+ availableModels: Model<Api>[],
592
+ preferences?: ModelMatchPreferences,
593
+ options?: { allowInvalidThinkingSelectorFallback?: boolean; modelRegistry?: CanonicalModelRegistry },
594
+ ): ParsedModelResult {
595
+ const context = buildPreferenceContext(availableModels, preferences);
596
+ const direct = parseModelPatternWithContext(pattern, availableModels, context, options);
597
+ if (direct.model) return direct;
598
+
599
+ // No direct match: a trailing `@upstream` may be a provider-routing selector.
600
+ // Only honor it when the base resolves to an aggregator model (OpenRouter /
601
+ // Vercel Gateway); otherwise `@` stays part of the id and `direct` stands.
602
+ const routing = splitUpstreamRouting(pattern);
603
+ if (routing) {
604
+ const routed = parseModelPatternWithContext(routing.base, availableModels, context, options);
605
+ if (routed.model && supportsUpstreamRouting(routed.model)) {
606
+ return { ...routed, model: applyUpstreamRouting(routed.model, routing.upstream), upstream: routing.upstream };
607
+ }
608
+ }
609
+ return direct;
610
+ }
611
+
612
+ const PREFIX_MODEL_ROLE = "pi/";
613
+ const DEFAULT_MODEL_ROLE = "default";
614
+
615
+ function getModelRoleAlias(value: string): ModelRole | undefined {
616
+ const normalized = value.trim();
617
+ if (!normalized.startsWith(PREFIX_MODEL_ROLE)) return undefined;
618
+
619
+ const candidate = normalized.slice(PREFIX_MODEL_ROLE.length);
620
+ for (const role of MODEL_ROLE_IDS) {
621
+ if (candidate === role) return role;
622
+ }
623
+ return undefined;
624
+ }
625
+
626
+ function normalizeModelPatternList(value: string | string[] | undefined): string[] {
627
+ if (!value) return [];
628
+ const patterns = Array.isArray(value) ? value : value.split(",");
629
+ return patterns.map(pattern => pattern.trim()).filter(Boolean);
630
+ }
631
+
632
+ function isSessionInheritedAgentPattern(value: string): boolean {
633
+ return value === DEFAULT_MODEL_ROLE || value === `${PREFIX_MODEL_ROLE}${DEFAULT_MODEL_ROLE}` || value === "pi/task";
634
+ }
635
+
636
+ function resolveConfiguredRolePattern(value: string, settings?: Settings): string[] | undefined {
637
+ const normalized = value.trim();
638
+ if (!normalized) return undefined;
639
+
640
+ const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(normalized, PREFIX_MODEL_ROLE.length);
641
+ const role = getModelRoleAlias(aliasCandidate);
642
+ if (!role) return [normalized];
643
+
644
+ const configured = settings?.getModelRole(role)?.trim();
645
+ const roleDefaults = normalizeModelPatternList(MODEL_PRIO[role as keyof typeof MODEL_PRIO]);
646
+ const resolved = configured ? normalizeModelPatternList(configured) : roleDefaults;
647
+ if (!resolved || resolved.length === 0) {
648
+ return undefined;
649
+ }
650
+
651
+ return thinkingLevel ? resolved.map(pattern => `${pattern}:${thinkingLevel}`) : resolved;
652
+ }
653
+
654
+ /**
655
+ * Expand a role alias like "pi/smol" to the configured model string.
656
+ */
657
+ export function expandRoleAlias(value: string, settings?: Settings): string {
658
+ const normalized = value.trim();
659
+ if (normalized === DEFAULT_MODEL_ROLE) {
660
+ return settings?.getModelRole("default") ?? value;
661
+ }
662
+
663
+ const resolved = resolveConfiguredRolePattern(value, settings)?.[0];
664
+ return resolved ?? value;
665
+ }
666
+
667
+ export function resolveConfiguredModelPatterns(value: string | string[] | undefined, settings?: Settings): string[] {
668
+ const patterns = normalizeModelPatternList(value);
669
+ return patterns.flatMap(pattern => {
670
+ const resolved = resolveConfiguredRolePattern(pattern, settings);
671
+ return resolved ?? [];
672
+ });
673
+ }
674
+ export interface AgentModelPatternResolutionOptions {
675
+ settingsOverride?: string | string[];
676
+ agentModel?: string | string[];
677
+ settings?: Settings;
678
+ activeModelPattern?: string;
679
+ fallbackModelPattern?: string;
680
+ }
681
+
682
+ export function resolveAgentModelPatterns(options: AgentModelPatternResolutionOptions): string[] {
683
+ const { settingsOverride, agentModel, settings, activeModelPattern, fallbackModelPattern } = options;
684
+
685
+ const overridePatterns = resolveConfiguredModelPatterns(settingsOverride, settings);
686
+ if (overridePatterns.length > 0) return overridePatterns;
687
+
688
+ const normalizedAgentPatterns = normalizeModelPatternList(agentModel);
689
+ const configuredAgentPatterns = resolveConfiguredModelPatterns(agentModel, settings);
690
+ const singleAgentPattern = normalizedAgentPatterns.length === 1 ? normalizedAgentPatterns[0] : undefined;
691
+ const agentInheritsSessionModel = singleAgentPattern ? isSessionInheritedAgentPattern(singleAgentPattern) : false;
692
+ if (configuredAgentPatterns.length > 0) {
693
+ if (!agentInheritsSessionModel) return configuredAgentPatterns;
694
+ if (singleAgentPattern === "pi/task") return configuredAgentPatterns;
695
+ }
696
+
697
+ const fallback =
698
+ activeModelPattern?.trim() || fallbackModelPattern?.trim() || settings?.getModelRole("default")?.trim() || "";
699
+ return resolveConfiguredModelPatterns(fallback, settings);
700
+ }
701
+
702
+ /**
703
+ * Resolve a model role value into a concrete model and thinking metadata.
704
+ */
705
+ export interface ResolvedModelRoleValue {
706
+ model: Model<Api> | undefined;
707
+ thinkingLevel?: ThinkingLevel;
708
+ explicitThinkingLevel: boolean;
709
+ warning: string | undefined;
710
+ }
711
+
712
+ export function resolveModelRoleValue(
713
+ roleValue: string | undefined,
714
+ availableModels: Model<Api>[],
715
+ options?: { settings?: Settings; matchPreferences?: ModelMatchPreferences; modelRegistry?: CanonicalModelRegistry },
716
+ ): ResolvedModelRoleValue {
717
+ if (!roleValue) {
718
+ return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
719
+ }
720
+
721
+ const normalized = roleValue.trim();
722
+ if (!normalized || normalized === DEFAULT_MODEL_ROLE) {
723
+ return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
724
+ }
725
+
726
+ const effectivePatterns = resolveConfiguredModelPatterns(normalized, options?.settings);
727
+ if (!effectivePatterns || effectivePatterns.length === 0) {
728
+ return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
729
+ }
730
+
731
+ let warning: string | undefined;
732
+ const matchPreferences = mergeModelMatchPreferences(options?.settings, options?.matchPreferences);
733
+ for (const effectivePattern of effectivePatterns) {
734
+ const resolved = parseModelPattern(effectivePattern, availableModels, matchPreferences, {
735
+ modelRegistry: options?.modelRegistry,
736
+ });
737
+ if (resolved.model) {
738
+ return {
739
+ model: resolved.model,
740
+ thinkingLevel: resolved.explicitThinkingLevel
741
+ ? (resolveThinkingLevelForModel(resolved.model, resolved.thinkingLevel) ?? resolved.thinkingLevel)
742
+ : resolved.thinkingLevel,
743
+ explicitThinkingLevel: resolved.explicitThinkingLevel,
744
+ warning: resolved.warning,
745
+ };
746
+ }
747
+ if (!warning && resolved.warning) {
748
+ warning = resolved.warning;
749
+ }
750
+ }
751
+
752
+ return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning };
753
+ }
754
+
755
+ export function extractExplicitThinkingSelector(
756
+ value: string | undefined,
757
+ settings?: Settings,
758
+ ): ThinkingLevel | undefined {
759
+ if (!value) return undefined;
760
+ const normalized = value.trim();
761
+ if (!normalized || normalized === DEFAULT_MODEL_ROLE) return undefined;
762
+
763
+ const visited = new Set<string>();
764
+ let current = normalized;
765
+ while (!visited.has(current)) {
766
+ visited.add(current);
767
+ const thinkingSelector = splitThinkingSuffix(current, PREFIX_MODEL_ROLE.length).level;
768
+ if (thinkingSelector) {
769
+ return thinkingSelector;
770
+ }
771
+ const expanded = expandRoleAlias(current, settings).trim();
772
+ if (!expanded || expanded === current) break;
773
+ if (expanded === DEFAULT_MODEL_ROLE) return undefined;
774
+ current = expanded;
775
+ }
776
+
777
+ return undefined;
778
+ }
779
+
780
+ /**
781
+ * Resolve a model identifier or pattern to a Model instance.
782
+ */
783
+ export function resolveModelFromString(
784
+ value: string,
785
+ available: Model<Api>[],
786
+ matchPreferences?: ModelMatchPreferences,
787
+ modelRegistry?: CanonicalModelRegistry,
788
+ ): Model<Api> | undefined {
789
+ const parsed = parseModelString(value);
790
+ if (parsed) {
791
+ const exact = available.find(model => model.provider === parsed.provider && model.id === parsed.id);
792
+ if (exact) return exact;
793
+ }
794
+ return parseModelPattern(value, available, matchPreferences, { modelRegistry }).model;
795
+ }
796
+
797
+ /**
798
+ * Resolve a model from configured roles, honoring order and overrides.
799
+ */
800
+ export function resolveModelFromSettings(options: {
801
+ settings: Settings;
802
+ availableModels: Model<Api>[];
803
+ matchPreferences?: ModelMatchPreferences;
804
+ roleOrder?: readonly ModelRole[];
805
+ modelRegistry?: CanonicalModelRegistry;
806
+ }): Model<Api> | undefined {
807
+ const { settings, availableModels, matchPreferences, roleOrder, modelRegistry } = options;
808
+ const roles = roleOrder ?? MODEL_ROLE_IDS;
809
+ let sawConfiguredProviderQualifiedRole = false;
810
+ for (const role of roles) {
811
+ const configured = settings.getModelRole(role);
812
+ if (!configured) continue;
813
+ const expanded = expandRoleAlias(configured, settings).trim();
814
+ if (expanded.includes("/")) {
815
+ sawConfiguredProviderQualifiedRole = true;
816
+ }
817
+ const resolved = resolveModelFromString(expanded, availableModels, matchPreferences, modelRegistry);
818
+ if (resolved) return resolved;
819
+ }
820
+ return sawConfiguredProviderQualifiedRole ? undefined : availableModels[0];
821
+ }
822
+
823
+ /**
824
+ * Resolve a list of override patterns to the first matching model.
825
+ */
826
+ export function resolveModelOverride(
827
+ modelPatterns: string[],
828
+ modelRegistry: ModelLookupRegistry,
829
+ settings?: Settings,
830
+ ): { model?: Model<Api>; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } {
831
+ if (modelPatterns.length === 0) return { explicitThinkingLevel: false };
832
+ const availableModels = modelRegistry.getAvailable();
833
+ const matchPreferences = getModelMatchPreferences(settings);
834
+ for (const pattern of modelPatterns) {
835
+ const { model, thinkingLevel, explicitThinkingLevel } = resolveModelRoleValue(pattern, availableModels, {
836
+ settings,
837
+ matchPreferences,
838
+ modelRegistry,
839
+ });
840
+ if (model) {
841
+ return { model, thinkingLevel, explicitThinkingLevel };
842
+ }
843
+ }
844
+ return { explicitThinkingLevel: false };
845
+ }
846
+
847
+ /**
848
+ * Resolve a list of override patterns to the first matching model, with an
849
+ * auth-aware fallback to the parent session's active model.
850
+ *
851
+ * If the resolved subagent model has no working credentials (provider has no
852
+ * usable auth), and the parent's active model resolves with working auth,
853
+ * use the parent's model instead. This prevents subagent dispatch from
854
+ * silently routing to a provider the user can't actually call (e.g.
855
+ * `modelRoles.task` pointing at an unqualified id whose only available
856
+ * provider variant has no configured credentials — see #985).
857
+ *
858
+ * Keyless-by-design providers (llama.cpp, ollama, lm-studio) advertise the
859
+ * `kNoAuth` sentinel from `getApiKey` to signal that they do not require
860
+ * credentials. Those are treated as authenticated here so an explicitly
861
+ * configured local model is never silently rerouted to the parent's remote
862
+ * provider (see #1008).
863
+ *
864
+ * If neither the subagent nor the parent has working auth, returns the
865
+ * primary resolution unchanged so the existing error path still surfaces
866
+ * a meaningful failure downstream.
867
+ */
868
+ export async function resolveModelOverrideWithAuthFallback(
869
+ modelPatterns: string[],
870
+ parentActiveModelPattern: string | undefined,
871
+ modelRegistry: ModelLookupRegistry & Pick<ModelRegistry, "getApiKey">,
872
+ settings?: Settings,
873
+ ): Promise<{
874
+ model?: Model<Api>;
875
+ thinkingLevel?: ThinkingLevel;
876
+ explicitThinkingLevel: boolean;
877
+ authFallbackUsed: boolean;
878
+ }> {
879
+ const primary = resolveModelOverride(modelPatterns, modelRegistry, settings);
880
+ if (!primary.model || !parentActiveModelPattern) {
881
+ return { ...primary, authFallbackUsed: false };
882
+ }
883
+
884
+ const primaryKey = await modelRegistry.getApiKey(primary.model);
885
+ if (primaryKey === kNoAuth || isAuthenticated(primaryKey)) {
886
+ return { ...primary, authFallbackUsed: false };
887
+ }
888
+
889
+ const fallback = resolveModelOverride([parentActiveModelPattern], modelRegistry, settings);
890
+ if (!fallback.model) {
891
+ return { ...primary, authFallbackUsed: false };
892
+ }
893
+ if (modelsAreEqual(fallback.model, primary.model)) {
894
+ return { ...primary, authFallbackUsed: false };
895
+ }
896
+ const fallbackKey = await modelRegistry.getApiKey(fallback.model);
897
+ if (!isAuthenticated(fallbackKey)) {
898
+ return { ...primary, authFallbackUsed: false };
899
+ }
900
+
901
+ return { ...fallback, authFallbackUsed: true };
902
+ }
903
+
904
+ /**
905
+ * Resolve a list of role patterns to the first matching model.
906
+ */
907
+ export function resolveRoleSelection(
908
+ roles: readonly string[],
909
+ settings: Settings,
910
+ availableModels: Model<Api>[],
911
+ modelRegistry?: CanonicalModelRegistry,
912
+ ): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
913
+ const matchPreferences = getModelMatchPreferences(settings);
914
+ for (const role of roles) {
915
+ const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
916
+ settings,
917
+ matchPreferences,
918
+ modelRegistry,
919
+ });
920
+ if (resolved.model) {
921
+ return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
922
+ }
923
+ }
924
+ return undefined;
925
+ }
926
+
927
+ function resolveExactCanonicalScopePattern(
928
+ pattern: string,
929
+ modelRegistry: Pick<ModelRegistry, "getCanonicalVariants">,
930
+ availableModels: Model<Api>[],
931
+ ): { models: Model<Api>[]; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } | undefined {
932
+ const { base: canonicalId, level: thinkingLevel } = splitThinkingSuffix(pattern);
933
+ const explicitThinkingLevel = thinkingLevel !== undefined;
934
+
935
+ const variants = modelRegistry
936
+ .getCanonicalVariants(canonicalId, { availableOnly: true, candidates: availableModels })
937
+ .map(variant => variant.model);
938
+ if (variants.length === 0) {
939
+ return undefined;
940
+ }
941
+
942
+ return { models: variants, thinkingLevel, explicitThinkingLevel };
943
+ }
944
+
945
+ /**
946
+ * Resolve model patterns to actual Model objects with optional thinking levels
947
+ * Format: "pattern:level" where :level is optional
948
+ * For each pattern, finds all matching models and picks the best version:
949
+ * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)
950
+ * 2. If no alias, pick the latest dated version
951
+ *
952
+ * Supports models with colons in their IDs (e.g., OpenRouter's model:exacto).
953
+ * The algorithm tries to match the full pattern first, then progressively
954
+ * strips colon-suffixes to find a match.
955
+ */
956
+ export async function resolveModelScope(
957
+ patterns: string[],
958
+ modelRegistry: Pick<ModelRegistry, "getAvailable" | "getCanonicalVariants">,
959
+ preferences?: ModelMatchPreferences,
960
+ ): Promise<ScopedModel[]> {
961
+ const availableModels = modelRegistry.getAvailable();
962
+ const context = buildPreferenceContext(availableModels, preferences);
963
+ const scopedModels: ScopedModel[] = [];
964
+ const addScopedModel = (model: Model<Api>, thinkingLevel: ThinkingLevel | undefined, explicit: boolean) => {
965
+ if (scopedModels.some(sm => modelsAreEqual(sm.model, model))) return;
966
+ scopedModels.push({
967
+ model,
968
+ thinkingLevel: explicit
969
+ ? (resolveThinkingLevelForModel(model, thinkingLevel) ?? thinkingLevel)
970
+ : thinkingLevel,
971
+ explicitThinkingLevel: explicit,
972
+ });
973
+ };
974
+
975
+ for (const pattern of patterns) {
976
+ // Check if pattern contains glob characters
977
+ if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
978
+ // Extract optional thinking level suffix (e.g., "provider/*:high")
979
+ const { base: globPattern, level: thinkingLevel } = splitThinkingSuffix(pattern);
980
+ const explicitThinkingLevel = thinkingLevel !== undefined;
981
+
982
+ // Match against "provider/modelId" format OR just model ID
983
+ // This allows "*sonnet*" to match without requiring "anthropic/*sonnet*"
984
+ const matchingModels = availableModels.filter(m => {
985
+ const fullId = `${m.provider}/${m.id}`;
986
+ const glob = new Bun.Glob(globPattern.toLowerCase());
987
+ return glob.match(fullId.toLowerCase()) || glob.match(m.id.toLowerCase());
988
+ });
989
+
990
+ if (matchingModels.length === 0) {
991
+ logger.warn(`No models match pattern "${pattern}"`);
992
+ continue;
993
+ }
994
+
995
+ for (const model of matchingModels) {
996
+ addScopedModel(model, thinkingLevel, explicitThinkingLevel);
997
+ }
998
+ continue;
999
+ }
1000
+
1001
+ const exactCanonical = resolveExactCanonicalScopePattern(pattern, modelRegistry, availableModels);
1002
+ if (exactCanonical) {
1003
+ for (const model of exactCanonical.models) {
1004
+ addScopedModel(model, exactCanonical.thinkingLevel, exactCanonical.explicitThinkingLevel);
1005
+ }
1006
+ continue;
1007
+ }
1008
+
1009
+ const { model, thinkingLevel, warning, explicitThinkingLevel } = parseModelPatternWithContext(
1010
+ pattern,
1011
+ availableModels,
1012
+ context,
1013
+ { modelRegistry },
1014
+ );
1015
+
1016
+ if (warning) {
1017
+ logger.warn(warning);
1018
+ }
1019
+
1020
+ if (!model) {
1021
+ logger.warn(`No models match pattern "${pattern}"`);
1022
+ continue;
1023
+ }
1024
+
1025
+ addScopedModel(model, thinkingLevel, explicitThinkingLevel);
1026
+ }
1027
+
1028
+ return scopedModels;
1029
+ }
1030
+
1031
+ /**
1032
+ * Resolve the set of models a session is allowed to use, given the active
1033
+ * settings. Starts from `modelRegistry.getAvailable()` (so disabled providers
1034
+ * and providers without credentials are already filtered out) and, when
1035
+ * `enabledModels` is configured for the current path scope, further restricts
1036
+ * the result to models matching those patterns.
1037
+ *
1038
+ * Returns the unfiltered available list when `enabledModels` is empty.
1039
+ * Returns an empty list when `enabledModels` is configured but no available
1040
+ * model matches any pattern — callers MUST treat this as "no usable model"
1041
+ * rather than falling back to the global default (see issue #1022).
1042
+ */
1043
+ export async function resolveAllowedModels(
1044
+ modelRegistry: Pick<ModelRegistry, "getAvailable" | "getCanonicalVariants">,
1045
+ settings: Settings | undefined,
1046
+ preferences?: ModelMatchPreferences,
1047
+ ): Promise<Model<Api>[]> {
1048
+ const available = modelRegistry.getAvailable();
1049
+ const patterns = settings?.get("enabledModels");
1050
+ if (!patterns || patterns.length === 0) {
1051
+ return available;
1052
+ }
1053
+ const scoped = await resolveModelScope(patterns, modelRegistry, preferences);
1054
+ if (scoped.length === 0) {
1055
+ return [];
1056
+ }
1057
+ const allowed = new Set(scoped.map(entry => `${entry.model.provider}/${entry.model.id}`));
1058
+ return available.filter(model => allowed.has(`${model.provider}/${model.id}`));
1059
+ }
1060
+
1061
+ /**
1062
+ * Synchronous subset of {@link resolveAllowedModels} for contexts where async is unavailable
1063
+ * (e.g. `getAvailableModels()` which is called from the ACP model-list advertisement, RPC
1064
+ * `get_available_models`, and the `/model` slash command). Uses the same effective
1065
+ * `enabledModels` scope semantics as startup resolution:
1066
+ *
1067
+ * - Glob selectors match `provider/modelId` and bare model id
1068
+ * - Exact canonical ids expand to all available concrete variants
1069
+ * - Exact `provider/modelId`, bare ids, provider-scoped fuzzy, and substring selectors
1070
+ * resolve through the shared model-pattern matcher
1071
+ * - Optional `:thinkingLevel` suffixes are stripped only when valid
1072
+ *
1073
+ * When no pattern resolves to any model (misconfiguration / typo) an empty list is returned,
1074
+ * consistent with the empty-list contract of {@link resolveAllowedModels}. Callers that render
1075
+ * a UI picker should treat an empty list as "hide the picker entry", matching how the SDK
1076
+ * surfaces the same misconfiguration during session initialization.
1077
+ */
1078
+ export function filterAvailableModelsByEnabledPatterns(
1079
+ available: Model<Api>[],
1080
+ patterns: readonly string[],
1081
+ registry: Pick<ModelRegistry, "getCanonicalVariants">,
1082
+ ): Model<Api>[] {
1083
+ if (patterns.length === 0) return available;
1084
+
1085
+ const context = buildPreferenceContext(available, undefined);
1086
+ const allowed = new Set<string>();
1087
+ const addAllowed = (model: Model<Api>) => {
1088
+ allowed.add(`${model.provider}/${model.id}`);
1089
+ };
1090
+
1091
+ for (const pattern of patterns) {
1092
+ if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
1093
+ const { base: globPattern } = splitThinkingSuffix(pattern);
1094
+ const glob = new Bun.Glob(globPattern.toLowerCase());
1095
+ for (const model of available) {
1096
+ const fullId = `${model.provider}/${model.id}`.toLowerCase();
1097
+ if (glob.match(fullId) || glob.match(model.id.toLowerCase())) {
1098
+ addAllowed(model);
1099
+ }
1100
+ }
1101
+ continue;
1102
+ }
1103
+
1104
+ const exactCanonical = resolveExactCanonicalScopePattern(pattern, registry, available);
1105
+ if (exactCanonical) {
1106
+ for (const model of exactCanonical.models) {
1107
+ addAllowed(model);
1108
+ }
1109
+ continue;
1110
+ }
1111
+
1112
+ const { model } = parseModelPatternWithContext(pattern, available, context, { modelRegistry: registry });
1113
+ if (model) {
1114
+ addAllowed(model);
1115
+ }
1116
+ }
1117
+
1118
+ return allowed.size === 0 ? [] : available.filter(model => allowed.has(`${model.provider}/${model.id}`));
1119
+ }
1120
+
1121
+ export interface ResolveCliModelResult {
1122
+ model: Model<Api> | undefined;
1123
+ selector?: string;
1124
+ thinkingLevel?: ThinkingLevel;
1125
+ warning: string | undefined;
1126
+ error: string | undefined;
1127
+ }
1128
+
1129
+ /**
1130
+ * Resolve a single model from CLI flags.
1131
+ */
1132
+ export function resolveCliModel(options: {
1133
+ cliProvider?: string;
1134
+ cliModel?: string;
1135
+ modelRegistry: CliModelRegistry;
1136
+ preferences?: ModelMatchPreferences;
1137
+ }): ResolveCliModelResult {
1138
+ const { cliProvider, cliModel, modelRegistry, preferences } = options;
1139
+
1140
+ if (!cliModel) {
1141
+ return { model: undefined, selector: undefined, warning: undefined, error: undefined };
1142
+ }
1143
+
1144
+ const availableModels = modelRegistry.getAll();
1145
+ if (availableModels.length === 0) {
1146
+ return {
1147
+ model: undefined,
1148
+ selector: undefined,
1149
+ warning: undefined,
1150
+ error: "No models available. Check your installation or add models to models.json.",
1151
+ };
1152
+ }
1153
+
1154
+ const providerMap = new Map<string, string>();
1155
+ for (const model of availableModels) {
1156
+ providerMap.set(model.provider.toLowerCase(), model.provider);
1157
+ }
1158
+
1159
+ let provider = cliProvider ? providerMap.get(cliProvider.toLowerCase()) : undefined;
1160
+ if (cliProvider && !provider) {
1161
+ return {
1162
+ model: undefined,
1163
+ selector: undefined,
1164
+ warning: undefined,
1165
+ error: `Unknown provider "${cliProvider}". Use --list-models to see available providers/models.`,
1166
+ };
1167
+ }
1168
+
1169
+ const trimmedModel = cliModel.trim();
1170
+ if (!provider) {
1171
+ const lower = trimmedModel.toLowerCase();
1172
+ // When input has provider/id format (e.g. "zai/glm-5"), prefer decomposed
1173
+ // provider+id match over flat id match. Without this, a model with id
1174
+ // "zai/glm-5" on provider "vercel-ai-gateway" wins over provider "zai"
1175
+ // with id "glm-5", because Array.find returns the first catalog hit.
1176
+ let exact = findExactModelReferenceMatch(trimmedModel, availableModels);
1177
+ if (!exact && !trimmedModel.includes(":")) {
1178
+ // CLI flags address the full catalog, so unlike the engine's canonical
1179
+ // step this lookup is unrestricted; the `:`-guard defers suffixed
1180
+ // selectors (thinking levels, ollama-style ids) to the grammar below.
1181
+ const canonicalMatch = modelRegistry.resolveCanonicalModel?.(trimmedModel, { availableOnly: false });
1182
+ if (canonicalMatch) {
1183
+ return {
1184
+ model: canonicalMatch,
1185
+ selector: modelRegistry.getCanonicalId?.(canonicalMatch) ?? trimmedModel,
1186
+ warning: undefined,
1187
+ thinkingLevel: undefined,
1188
+ error: undefined,
1189
+ };
1190
+ }
1191
+ }
1192
+ if (!exact) {
1193
+ // Flat exact id (or full selector) by catalog order: CLI resolution
1194
+ // stays deterministic across runs regardless of usage-based ranking.
1195
+ exact = availableModels.find(
1196
+ model => model.id.toLowerCase() === lower || `${model.provider}/${model.id}`.toLowerCase() === lower,
1197
+ );
1198
+ }
1199
+ if (exact) {
1200
+ return {
1201
+ model: exact,
1202
+ selector: formatModelString(exact),
1203
+ warning: undefined,
1204
+ thinkingLevel: undefined,
1205
+ error: undefined,
1206
+ };
1207
+ }
1208
+ }
1209
+
1210
+ let pattern = trimmedModel;
1211
+
1212
+ if (!provider) {
1213
+ const slashIndex = cliModel.indexOf("/");
1214
+ if (slashIndex !== -1) {
1215
+ const maybeProvider = cliModel.substring(0, slashIndex);
1216
+ const canonical = providerMap.get(maybeProvider.toLowerCase());
1217
+ if (canonical) {
1218
+ provider = canonical;
1219
+ pattern = cliModel.substring(slashIndex + 1);
1220
+ }
1221
+ }
1222
+ } else {
1223
+ const prefix = `${provider}/`;
1224
+ if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {
1225
+ pattern = cliModel.substring(prefix.length);
1226
+ }
1227
+ }
1228
+
1229
+ if (provider) {
1230
+ const exactProviderMatch = resolveProviderModelReference(provider, pattern, availableModels);
1231
+ if (exactProviderMatch) {
1232
+ return {
1233
+ model: exactProviderMatch,
1234
+ selector: formatModelString(exactProviderMatch),
1235
+ warning: undefined,
1236
+ thinkingLevel: undefined,
1237
+ error: undefined,
1238
+ };
1239
+ }
1240
+ }
1241
+
1242
+ const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
1243
+ const { model, thinkingLevel, warning, upstream } = parseModelPattern(pattern, candidates, preferences, {
1244
+ allowInvalidThinkingSelectorFallback: false,
1245
+ modelRegistry,
1246
+ });
1247
+
1248
+ if (!model) {
1249
+ const display = provider ? `${provider}/${pattern}` : cliModel;
1250
+ return {
1251
+ model: undefined,
1252
+ selector: undefined,
1253
+ thinkingLevel: undefined,
1254
+ warning,
1255
+ error: `Model "${display}" not found. Use --list-models to see available models.`,
1256
+ };
1257
+ }
1258
+
1259
+ let selector = provider ? formatModelString(model) : undefined;
1260
+ if (!provider) {
1261
+ const canonicalCandidate = splitThinkingSuffix(pattern).base;
1262
+ if (!canonicalCandidate.includes("/")) {
1263
+ const canonicalResolved = modelRegistry.resolveCanonicalModel?.(canonicalCandidate, { availableOnly: false });
1264
+ if (canonicalResolved && canonicalResolved.provider === model.provider && canonicalResolved.id === model.id) {
1265
+ selector = modelRegistry.getCanonicalId?.(canonicalResolved) ?? canonicalCandidate;
1266
+ }
1267
+ }
1268
+ }
1269
+ if (selector !== undefined && upstream) {
1270
+ selector = `${selector}@${upstream}`;
1271
+ }
1272
+
1273
+ return {
1274
+ model,
1275
+ selector,
1276
+ thinkingLevel,
1277
+ warning,
1278
+ error: undefined,
1279
+ };
1280
+ }
1281
+
1282
+ export interface InitialModelResult {
1283
+ model: Model<Api> | undefined;
1284
+ thinkingLevel?: ThinkingLevel;
1285
+ fallbackMessage: string | undefined;
1286
+ }
1287
+
1288
+ /**
1289
+ * Find the initial model to use based on priority:
1290
+ * 1. CLI args (provider + model)
1291
+ * 2. First model from scoped models (if not continuing/resuming)
1292
+ * 3. Restored from session (if continuing/resuming)
1293
+ * 4. Saved default from settings
1294
+ * 5. First available model with valid API key
1295
+ */
1296
+ export async function findInitialModel(options: {
1297
+ cliProvider?: string;
1298
+ cliModel?: string;
1299
+ scopedModels: ScopedModel[];
1300
+ isContinuing: boolean;
1301
+ defaultProvider?: string;
1302
+ defaultModelId?: string;
1303
+ defaultThinkingSelector?: Effort;
1304
+ modelRegistry: InitialModelRegistry;
1305
+ }): Promise<InitialModelResult> {
1306
+ const {
1307
+ cliProvider,
1308
+ cliModel,
1309
+ scopedModels,
1310
+ isContinuing,
1311
+ defaultProvider,
1312
+ defaultModelId,
1313
+ defaultThinkingSelector,
1314
+ modelRegistry,
1315
+ } = options;
1316
+
1317
+ let model: Model<Api> | undefined;
1318
+ let thinkingLevel: Effort | undefined;
1319
+
1320
+ // 1. CLI args take priority
1321
+ if (cliProvider && cliModel) {
1322
+ const found = modelRegistry.find(cliProvider, cliModel);
1323
+ if (!found) {
1324
+ console.error(chalk.red(`Model ${cliProvider}/${cliModel} not found`));
1325
+ process.exit(1);
1326
+ }
1327
+ return { model: found, thinkingLevel: undefined, fallbackMessage: undefined };
1328
+ }
1329
+
1330
+ // 2. Use first model from scoped models (skip if continuing/resuming)
1331
+ if (scopedModels.length > 0 && !isContinuing) {
1332
+ const scoped = scopedModels[0];
1333
+ const scopedThinkingSelector =
1334
+ scoped.thinkingLevel === ThinkingLevel.Inherit
1335
+ ? defaultThinkingSelector
1336
+ : (scoped.thinkingLevel ?? defaultThinkingSelector);
1337
+ return {
1338
+ model: scoped.model,
1339
+ thinkingLevel:
1340
+ scopedThinkingSelector === ThinkingLevel.Off
1341
+ ? ThinkingLevel.Off
1342
+ : clampThinkingLevelForModel(scoped.model, scopedThinkingSelector),
1343
+ fallbackMessage: undefined,
1344
+ };
1345
+ }
1346
+
1347
+ // 3. Try saved default from settings
1348
+ if (defaultProvider && defaultModelId) {
1349
+ const found = modelRegistry.find(defaultProvider, defaultModelId);
1350
+ if (found) {
1351
+ model = found;
1352
+ thinkingLevel = clampThinkingLevelForModel(found, defaultThinkingSelector);
1353
+ return { model, thinkingLevel, fallbackMessage: undefined };
1354
+ }
1355
+ }
1356
+
1357
+ // 4. Try first available model with valid API key
1358
+ const availableModels = modelRegistry.getAvailable();
1359
+
1360
+ const fallback = pickDefaultAvailableModel(availableModels);
1361
+ if (fallback) {
1362
+ return { model: fallback, thinkingLevel: undefined, fallbackMessage: undefined };
1363
+ }
1364
+
1365
+ // 5. No model found
1366
+ return { model: undefined, thinkingLevel: undefined, fallbackMessage: undefined };
1367
+ }
1368
+
1369
+ /**
1370
+ * Restore model from session, with fallback to available models
1371
+ */
1372
+ export async function restoreModelFromSession(
1373
+ savedProvider: string,
1374
+ savedModelId: string,
1375
+ currentModel: Model<Api> | undefined,
1376
+ shouldPrintMessages: boolean,
1377
+ modelRegistry: RestorableModelRegistry,
1378
+ ): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
1379
+ const restoredModel = modelRegistry.find(savedProvider, savedModelId);
1380
+
1381
+ // Check if restored model exists and has a valid API key
1382
+ const hasApiKey = restoredModel ? !!(await modelRegistry.getApiKey(restoredModel)) : false;
1383
+
1384
+ if (restoredModel && hasApiKey) {
1385
+ if (shouldPrintMessages) {
1386
+ console.log(chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`));
1387
+ }
1388
+ return { model: restoredModel, fallbackMessage: undefined };
1389
+ }
1390
+
1391
+ // Model not found or no API key - fall back
1392
+ const reason = !restoredModel ? "model no longer exists" : "no API key available";
1393
+
1394
+ if (shouldPrintMessages) {
1395
+ console.error(chalk.yellow(`Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`));
1396
+ }
1397
+
1398
+ // If we already have a model, use it as fallback
1399
+ if (currentModel) {
1400
+ if (shouldPrintMessages) {
1401
+ console.log(chalk.dim(`Falling back to: ${currentModel.provider}/${currentModel.id}`));
1402
+ }
1403
+ return {
1404
+ model: currentModel,
1405
+ fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,
1406
+ };
1407
+ }
1408
+
1409
+ // Try to find any available model
1410
+ const availableModels = modelRegistry.getAvailable();
1411
+
1412
+ const fallbackModel = pickDefaultAvailableModel(availableModels);
1413
+ if (fallbackModel) {
1414
+ if (shouldPrintMessages) {
1415
+ console.log(chalk.dim(`Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`));
1416
+ }
1417
+
1418
+ return {
1419
+ model: fallbackModel,
1420
+ fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,
1421
+ };
1422
+ }
1423
+
1424
+ // No models available
1425
+ return { model: undefined, fallbackMessage: undefined };
1426
+ }
1427
+
1428
+ /**
1429
+ * Find a smol/fast model using the priority chain.
1430
+ * Tries exact matches first, then fuzzy matches.
1431
+ *
1432
+ * @param modelRegistry The model registry to search
1433
+ * @param savedModel Optional saved model string from settings (provider/modelId)
1434
+ * @returns The best available smol model, or undefined if none found
1435
+ */
1436
+ export async function findSmolModel(
1437
+ modelRegistry: ModelLookupRegistry,
1438
+ savedModel?: string,
1439
+ ): Promise<Model<Api> | undefined> {
1440
+ const availableModels = modelRegistry.getAvailable();
1441
+ if (availableModels.length === 0) return undefined;
1442
+
1443
+ // 1. Try saved model from settings
1444
+ if (savedModel) {
1445
+ const match = resolveModelFromString(savedModel, availableModels, undefined, modelRegistry);
1446
+ if (match) return match;
1447
+ }
1448
+
1449
+ // 2. Try priority chain
1450
+ for (const pattern of MODEL_PRIO.smol) {
1451
+ // Try exact match with provider prefix
1452
+ const providerMatch = availableModels.find(m => `${m.provider}/${m.id}`.toLowerCase() === pattern);
1453
+ if (providerMatch) return providerMatch;
1454
+
1455
+ // Try exact match first
1456
+ const exactMatch = parseModelPattern(pattern, availableModels, undefined, { modelRegistry }).model;
1457
+ if (exactMatch) return exactMatch;
1458
+
1459
+ // Try fuzzy match (substring)
1460
+ const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern));
1461
+ if (fuzzyMatch) return fuzzyMatch;
1462
+ }
1463
+
1464
+ // 3. Fallback to first available (same as default)
1465
+ return availableModels[0];
1466
+ }
1467
+
1468
+ /**
1469
+ * Find a slow/comprehensive model using the priority chain.
1470
+ * Prioritizes reasoning and codex models for thorough analysis.
1471
+ *
1472
+ * @param modelRegistry The model registry to search
1473
+ * @param savedModel Optional saved model string from settings (provider/modelId)
1474
+ * @returns The best available slow model, or undefined if none found
1475
+ */
1476
+ export async function findSlowModel(
1477
+ modelRegistry: ModelLookupRegistry,
1478
+ savedModel?: string,
1479
+ ): Promise<Model<Api> | undefined> {
1480
+ const availableModels = modelRegistry.getAvailable();
1481
+ if (availableModels.length === 0) return undefined;
1482
+
1483
+ // 1. Try saved model from settings
1484
+ if (savedModel) {
1485
+ const match = resolveModelFromString(savedModel, availableModels, undefined, modelRegistry);
1486
+ if (match) return match;
1487
+ }
1488
+
1489
+ // 2. Try priority chain
1490
+ for (const pattern of MODEL_PRIO.slow) {
1491
+ // Try exact match first
1492
+ const exactMatch = parseModelPattern(pattern, availableModels, undefined, { modelRegistry }).model;
1493
+ if (exactMatch) return exactMatch;
1494
+
1495
+ // Try fuzzy match (substring)
1496
+ const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern.toLowerCase()));
1497
+ if (fuzzyMatch) return fuzzyMatch;
1498
+ }
1499
+
1500
+ // 3. Fallback to first available (same as default)
1501
+ return availableModels[0];
1502
+ }