@nghyane/arcane 0.1.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 (738) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +12 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +109 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/custom-tools/todo/index.ts +206 -0
  7. package/examples/extensions/README.md +143 -0
  8. package/examples/extensions/api-demo.ts +89 -0
  9. package/examples/extensions/chalk-logger.ts +25 -0
  10. package/examples/extensions/hello.ts +32 -0
  11. package/examples/extensions/pirate.ts +43 -0
  12. package/examples/extensions/plan-mode.ts +550 -0
  13. package/examples/extensions/reload-runtime.ts +37 -0
  14. package/examples/extensions/todo.ts +296 -0
  15. package/examples/extensions/tools.ts +144 -0
  16. package/examples/extensions/with-deps/index.ts +35 -0
  17. package/examples/extensions/with-deps/package-lock.json +31 -0
  18. package/examples/extensions/with-deps/package.json +16 -0
  19. package/examples/hooks/README.md +56 -0
  20. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  21. package/examples/hooks/confirm-destructive.ts +58 -0
  22. package/examples/hooks/custom-compaction.ts +116 -0
  23. package/examples/hooks/dirty-repo-guard.ts +51 -0
  24. package/examples/hooks/file-trigger.ts +40 -0
  25. package/examples/hooks/git-checkpoint.ts +52 -0
  26. package/examples/hooks/handoff.ts +150 -0
  27. package/examples/hooks/permission-gate.ts +33 -0
  28. package/examples/hooks/protected-paths.ts +29 -0
  29. package/examples/hooks/qna.ts +119 -0
  30. package/examples/hooks/status-line.ts +39 -0
  31. package/examples/sdk/01-minimal.ts +21 -0
  32. package/examples/sdk/02-custom-model.ts +49 -0
  33. package/examples/sdk/03-custom-prompt.ts +43 -0
  34. package/examples/sdk/04-skills.ts +43 -0
  35. package/examples/sdk/06-extensions.ts +80 -0
  36. package/examples/sdk/06-hooks.ts +61 -0
  37. package/examples/sdk/07-context-files.ts +35 -0
  38. package/examples/sdk/08-prompt-templates.ts +36 -0
  39. package/examples/sdk/08-slash-commands.ts +41 -0
  40. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  41. package/examples/sdk/11-sessions.ts +47 -0
  42. package/examples/sdk/README.md +150 -0
  43. package/package.json +464 -0
  44. package/scripts/format-prompts.ts +184 -0
  45. package/scripts/generate-docs-index.ts +40 -0
  46. package/scripts/generate-template.ts +32 -0
  47. package/src/bun-imports.d.ts +22 -0
  48. package/src/capability/context-file.ts +39 -0
  49. package/src/capability/extension-module.ts +33 -0
  50. package/src/capability/extension.ts +47 -0
  51. package/src/capability/fs.ts +89 -0
  52. package/src/capability/hook.ts +39 -0
  53. package/src/capability/index.ts +432 -0
  54. package/src/capability/instruction.ts +36 -0
  55. package/src/capability/mcp.ts +60 -0
  56. package/src/capability/prompt.ts +34 -0
  57. package/src/capability/rule.ts +223 -0
  58. package/src/capability/settings.ts +34 -0
  59. package/src/capability/skill.ts +48 -0
  60. package/src/capability/slash-command.ts +39 -0
  61. package/src/capability/ssh.ts +41 -0
  62. package/src/capability/system-prompt.ts +34 -0
  63. package/src/capability/tool.ts +37 -0
  64. package/src/capability/types.ts +156 -0
  65. package/src/cli/args.ts +259 -0
  66. package/src/cli/config-cli.ts +357 -0
  67. package/src/cli/file-processor.ts +124 -0
  68. package/src/cli/grep-cli.ts +152 -0
  69. package/src/cli/jupyter-cli.ts +106 -0
  70. package/src/cli/list-models.ts +103 -0
  71. package/src/cli/plugin-cli.ts +661 -0
  72. package/src/cli/session-picker.ts +42 -0
  73. package/src/cli/setup-cli.ts +376 -0
  74. package/src/cli/shell-cli.ts +174 -0
  75. package/src/cli/ssh-cli.ts +179 -0
  76. package/src/cli/stats-cli.ts +197 -0
  77. package/src/cli/update-cli.ts +286 -0
  78. package/src/cli/web-search-cli.ts +143 -0
  79. package/src/cli.ts +65 -0
  80. package/src/commands/commit.ts +36 -0
  81. package/src/commands/config.ts +51 -0
  82. package/src/commands/grep.ts +41 -0
  83. package/src/commands/jupyter.ts +32 -0
  84. package/src/commands/launch.ts +139 -0
  85. package/src/commands/plugin.ts +70 -0
  86. package/src/commands/setup.ts +42 -0
  87. package/src/commands/shell.ts +29 -0
  88. package/src/commands/ssh.ts +60 -0
  89. package/src/commands/stats.ts +29 -0
  90. package/src/commands/update.ts +21 -0
  91. package/src/commands/web-search.ts +42 -0
  92. package/src/commit/agentic/agent.ts +311 -0
  93. package/src/commit/agentic/fallback.ts +96 -0
  94. package/src/commit/agentic/index.ts +359 -0
  95. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  96. package/src/commit/agentic/prompts/session-user.md +25 -0
  97. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  98. package/src/commit/agentic/prompts/system.md +38 -0
  99. package/src/commit/agentic/state.ts +69 -0
  100. package/src/commit/agentic/tools/analyze-file.ts +118 -0
  101. package/src/commit/agentic/tools/git-file-diff.ts +194 -0
  102. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  103. package/src/commit/agentic/tools/git-overview.ts +84 -0
  104. package/src/commit/agentic/tools/index.ts +56 -0
  105. package/src/commit/agentic/tools/propose-changelog.ts +128 -0
  106. package/src/commit/agentic/tools/propose-commit.ts +154 -0
  107. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  108. package/src/commit/agentic/tools/split-commit.ts +280 -0
  109. package/src/commit/agentic/topo-sort.ts +44 -0
  110. package/src/commit/agentic/trivial.ts +51 -0
  111. package/src/commit/agentic/validation.ts +200 -0
  112. package/src/commit/analysis/conventional.ts +165 -0
  113. package/src/commit/analysis/index.ts +4 -0
  114. package/src/commit/analysis/scope.ts +242 -0
  115. package/src/commit/analysis/summary.ts +112 -0
  116. package/src/commit/analysis/validation.ts +66 -0
  117. package/src/commit/changelog/detect.ts +37 -0
  118. package/src/commit/changelog/generate.ts +110 -0
  119. package/src/commit/changelog/index.ts +234 -0
  120. package/src/commit/changelog/parse.ts +44 -0
  121. package/src/commit/cli.ts +93 -0
  122. package/src/commit/git/diff.ts +148 -0
  123. package/src/commit/git/errors.ts +9 -0
  124. package/src/commit/git/index.ts +211 -0
  125. package/src/commit/git/operations.ts +54 -0
  126. package/src/commit/index.ts +5 -0
  127. package/src/commit/map-reduce/index.ts +64 -0
  128. package/src/commit/map-reduce/map-phase.ts +178 -0
  129. package/src/commit/map-reduce/reduce-phase.ts +145 -0
  130. package/src/commit/map-reduce/utils.ts +9 -0
  131. package/src/commit/message.ts +11 -0
  132. package/src/commit/model-selection.ts +69 -0
  133. package/src/commit/pipeline.ts +243 -0
  134. package/src/commit/prompts/analysis-system.md +148 -0
  135. package/src/commit/prompts/analysis-user.md +38 -0
  136. package/src/commit/prompts/changelog-system.md +50 -0
  137. package/src/commit/prompts/changelog-user.md +18 -0
  138. package/src/commit/prompts/file-observer-system.md +24 -0
  139. package/src/commit/prompts/file-observer-user.md +8 -0
  140. package/src/commit/prompts/reduce-system.md +50 -0
  141. package/src/commit/prompts/reduce-user.md +17 -0
  142. package/src/commit/prompts/summary-retry.md +3 -0
  143. package/src/commit/prompts/summary-system.md +38 -0
  144. package/src/commit/prompts/summary-user.md +13 -0
  145. package/src/commit/prompts/types-description.md +2 -0
  146. package/src/commit/types.ts +109 -0
  147. package/src/commit/utils/exclusions.ts +42 -0
  148. package/src/config/file-lock.ts +121 -0
  149. package/src/config/keybindings.ts +280 -0
  150. package/src/config/model-registry.ts +1140 -0
  151. package/src/config/model-resolver.ts +812 -0
  152. package/src/config/prompt-templates.ts +526 -0
  153. package/src/config/resolve-config-value.ts +92 -0
  154. package/src/config/settings-schema.ts +1236 -0
  155. package/src/config/settings.ts +706 -0
  156. package/src/config.ts +414 -0
  157. package/src/cursor.ts +239 -0
  158. package/src/debug/index.ts +431 -0
  159. package/src/debug/log-formatting.ts +60 -0
  160. package/src/debug/log-viewer.ts +903 -0
  161. package/src/debug/profiler.ts +158 -0
  162. package/src/debug/report-bundle.ts +366 -0
  163. package/src/debug/system-info.ts +112 -0
  164. package/src/discovery/agents-md.ts +68 -0
  165. package/src/discovery/agents.ts +199 -0
  166. package/src/discovery/builtin.ts +815 -0
  167. package/src/discovery/claude-plugins.ts +205 -0
  168. package/src/discovery/claude.ts +506 -0
  169. package/src/discovery/cline.ts +83 -0
  170. package/src/discovery/codex.ts +532 -0
  171. package/src/discovery/cursor.ts +218 -0
  172. package/src/discovery/gemini.ts +395 -0
  173. package/src/discovery/github.ts +117 -0
  174. package/src/discovery/helpers.ts +698 -0
  175. package/src/discovery/index.ts +89 -0
  176. package/src/discovery/mcp-json.ts +156 -0
  177. package/src/discovery/opencode.ts +394 -0
  178. package/src/discovery/ssh.ts +160 -0
  179. package/src/discovery/vscode.ts +103 -0
  180. package/src/discovery/windsurf.ts +145 -0
  181. package/src/exa/company.ts +57 -0
  182. package/src/exa/index.ts +62 -0
  183. package/src/exa/linkedin.ts +57 -0
  184. package/src/exa/mcp-client.ts +289 -0
  185. package/src/exa/render.ts +244 -0
  186. package/src/exa/researcher.ts +89 -0
  187. package/src/exa/search.ts +330 -0
  188. package/src/exa/types.ts +166 -0
  189. package/src/exa/websets.ts +247 -0
  190. package/src/exec/bash-executor.ts +184 -0
  191. package/src/exec/exec.ts +53 -0
  192. package/src/export/custom-share.ts +65 -0
  193. package/src/export/html/index.ts +162 -0
  194. package/src/export/html/template.css +889 -0
  195. package/src/export/html/template.generated.ts +2 -0
  196. package/src/export/html/template.html +45 -0
  197. package/src/export/html/template.js +1329 -0
  198. package/src/export/html/template.macro.ts +24 -0
  199. package/src/export/html/vendor/highlight.min.js +1213 -0
  200. package/src/export/html/vendor/marked.min.js +6 -0
  201. package/src/export/ttsr.ts +434 -0
  202. package/src/extensibility/custom-commands/bundled/review/index.ts +433 -0
  203. package/src/extensibility/custom-commands/index.ts +15 -0
  204. package/src/extensibility/custom-commands/loader.ts +231 -0
  205. package/src/extensibility/custom-commands/types.ts +111 -0
  206. package/src/extensibility/custom-tools/index.ts +22 -0
  207. package/src/extensibility/custom-tools/loader.ts +235 -0
  208. package/src/extensibility/custom-tools/types.ts +226 -0
  209. package/src/extensibility/custom-tools/wrapper.ts +45 -0
  210. package/src/extensibility/extensions/index.ts +136 -0
  211. package/src/extensibility/extensions/loader.ts +520 -0
  212. package/src/extensibility/extensions/runner.ts +774 -0
  213. package/src/extensibility/extensions/types.ts +1293 -0
  214. package/src/extensibility/extensions/wrapper.ts +188 -0
  215. package/src/extensibility/hooks/index.ts +16 -0
  216. package/src/extensibility/hooks/loader.ts +273 -0
  217. package/src/extensibility/hooks/runner.ts +441 -0
  218. package/src/extensibility/hooks/tool-wrapper.ts +106 -0
  219. package/src/extensibility/hooks/types.ts +817 -0
  220. package/src/extensibility/plugins/doctor.ts +65 -0
  221. package/src/extensibility/plugins/git-url.ts +281 -0
  222. package/src/extensibility/plugins/index.ts +33 -0
  223. package/src/extensibility/plugins/installer.ts +192 -0
  224. package/src/extensibility/plugins/loader.ts +338 -0
  225. package/src/extensibility/plugins/manager.ts +716 -0
  226. package/src/extensibility/plugins/parser.ts +105 -0
  227. package/src/extensibility/plugins/types.ts +190 -0
  228. package/src/extensibility/skills.ts +385 -0
  229. package/src/extensibility/slash-commands.ts +287 -0
  230. package/src/extensibility/tool-proxy.ts +25 -0
  231. package/src/index.ts +275 -0
  232. package/src/internal-urls/agent-protocol.ts +136 -0
  233. package/src/internal-urls/artifact-protocol.ts +97 -0
  234. package/src/internal-urls/docs-index.generated.ts +54 -0
  235. package/src/internal-urls/docs-protocol.ts +84 -0
  236. package/src/internal-urls/index.ts +31 -0
  237. package/src/internal-urls/json-query.ts +126 -0
  238. package/src/internal-urls/memory-protocol.ts +133 -0
  239. package/src/internal-urls/router.ts +70 -0
  240. package/src/internal-urls/rule-protocol.ts +55 -0
  241. package/src/internal-urls/skill-protocol.ts +111 -0
  242. package/src/internal-urls/types.ts +52 -0
  243. package/src/ipy/executor.ts +556 -0
  244. package/src/ipy/gateway-coordinator.ts +426 -0
  245. package/src/ipy/kernel.ts +892 -0
  246. package/src/ipy/modules.ts +109 -0
  247. package/src/ipy/prelude.py +831 -0
  248. package/src/ipy/prelude.ts +3 -0
  249. package/src/ipy/runtime.ts +222 -0
  250. package/src/lsp/client.ts +867 -0
  251. package/src/lsp/clients/biome-client.ts +202 -0
  252. package/src/lsp/clients/index.ts +50 -0
  253. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  254. package/src/lsp/clients/swiftlint-client.ts +120 -0
  255. package/src/lsp/config.ts +397 -0
  256. package/src/lsp/defaults.json +464 -0
  257. package/src/lsp/edits.ts +109 -0
  258. package/src/lsp/index.ts +1268 -0
  259. package/src/lsp/lspmux.ts +250 -0
  260. package/src/lsp/render.ts +689 -0
  261. package/src/lsp/types.ts +414 -0
  262. package/src/lsp/utils.ts +549 -0
  263. package/src/main.ts +773 -0
  264. package/src/mcp/client.ts +239 -0
  265. package/src/mcp/config-writer.ts +215 -0
  266. package/src/mcp/config.ts +363 -0
  267. package/src/mcp/index.ts +55 -0
  268. package/src/mcp/json-rpc.ts +84 -0
  269. package/src/mcp/loader.ts +124 -0
  270. package/src/mcp/manager.ts +490 -0
  271. package/src/mcp/oauth-discovery.ts +274 -0
  272. package/src/mcp/oauth-flow.ts +229 -0
  273. package/src/mcp/render.ts +123 -0
  274. package/src/mcp/tool-bridge.ts +372 -0
  275. package/src/mcp/tool-cache.ts +121 -0
  276. package/src/mcp/transports/http.ts +332 -0
  277. package/src/mcp/transports/index.ts +6 -0
  278. package/src/mcp/transports/stdio.ts +281 -0
  279. package/src/mcp/types.ts +248 -0
  280. package/src/memories/index.ts +1099 -0
  281. package/src/memories/storage.ts +563 -0
  282. package/src/modes/components/agent-dashboard.ts +1130 -0
  283. package/src/modes/components/assistant-message.ts +144 -0
  284. package/src/modes/components/bash-execution.ts +218 -0
  285. package/src/modes/components/bordered-loader.ts +41 -0
  286. package/src/modes/components/branch-summary-message.ts +45 -0
  287. package/src/modes/components/codemode-group.ts +369 -0
  288. package/src/modes/components/compaction-summary-message.ts +51 -0
  289. package/src/modes/components/countdown-timer.ts +46 -0
  290. package/src/modes/components/custom-editor.ts +181 -0
  291. package/src/modes/components/custom-message.ts +91 -0
  292. package/src/modes/components/diff.ts +186 -0
  293. package/src/modes/components/dynamic-border.ts +25 -0
  294. package/src/modes/components/extensions/extension-dashboard.ts +325 -0
  295. package/src/modes/components/extensions/extension-list.ts +484 -0
  296. package/src/modes/components/extensions/index.ts +9 -0
  297. package/src/modes/components/extensions/inspector-panel.ts +321 -0
  298. package/src/modes/components/extensions/state-manager.ts +586 -0
  299. package/src/modes/components/extensions/types.ts +191 -0
  300. package/src/modes/components/footer.ts +315 -0
  301. package/src/modes/components/history-search.ts +157 -0
  302. package/src/modes/components/hook-editor.ts +101 -0
  303. package/src/modes/components/hook-input.ts +72 -0
  304. package/src/modes/components/hook-message.ts +100 -0
  305. package/src/modes/components/hook-selector.ts +155 -0
  306. package/src/modes/components/index.ts +41 -0
  307. package/src/modes/components/keybinding-hints.ts +65 -0
  308. package/src/modes/components/login-dialog.ts +164 -0
  309. package/src/modes/components/mcp-add-wizard.ts +1295 -0
  310. package/src/modes/components/model-selector.ts +625 -0
  311. package/src/modes/components/oauth-selector.ts +210 -0
  312. package/src/modes/components/plugin-settings.ts +477 -0
  313. package/src/modes/components/python-execution.ts +196 -0
  314. package/src/modes/components/queue-mode-selector.ts +56 -0
  315. package/src/modes/components/read-tool-group.ts +119 -0
  316. package/src/modes/components/session-selector.ts +242 -0
  317. package/src/modes/components/settings-defs.ts +340 -0
  318. package/src/modes/components/settings-selector.ts +529 -0
  319. package/src/modes/components/show-images-selector.ts +45 -0
  320. package/src/modes/components/skill-message.ts +90 -0
  321. package/src/modes/components/status-line/index.ts +4 -0
  322. package/src/modes/components/status-line/presets.ts +94 -0
  323. package/src/modes/components/status-line/segments.ts +352 -0
  324. package/src/modes/components/status-line/separators.ts +55 -0
  325. package/src/modes/components/status-line/types.ts +75 -0
  326. package/src/modes/components/status-line-segment-editor.ts +354 -0
  327. package/src/modes/components/status-line.ts +421 -0
  328. package/src/modes/components/theme-selector.ts +63 -0
  329. package/src/modes/components/thinking-selector.ts +64 -0
  330. package/src/modes/components/todo-display.ts +115 -0
  331. package/src/modes/components/todo-reminder.ts +40 -0
  332. package/src/modes/components/tool-execution.ts +703 -0
  333. package/src/modes/components/tree-selector.ts +904 -0
  334. package/src/modes/components/ttsr-notification.ts +80 -0
  335. package/src/modes/components/user-message-selector.ts +146 -0
  336. package/src/modes/components/user-message.ts +22 -0
  337. package/src/modes/components/visual-truncate.ts +63 -0
  338. package/src/modes/components/welcome.ts +247 -0
  339. package/src/modes/controllers/command-controller.ts +1120 -0
  340. package/src/modes/controllers/event-controller.ts +479 -0
  341. package/src/modes/controllers/extension-ui-controller.ts +778 -0
  342. package/src/modes/controllers/input-controller.ts +671 -0
  343. package/src/modes/controllers/mcp-command-controller.ts +1315 -0
  344. package/src/modes/controllers/selector-controller.ts +712 -0
  345. package/src/modes/controllers/ssh-command-controller.ts +452 -0
  346. package/src/modes/index.ts +15 -0
  347. package/src/modes/interactive-mode.ts +1027 -0
  348. package/src/modes/print-mode.ts +191 -0
  349. package/src/modes/rpc/rpc-client.ts +583 -0
  350. package/src/modes/rpc/rpc-mode.ts +700 -0
  351. package/src/modes/rpc/rpc-types.ts +236 -0
  352. package/src/modes/theme/dark.json +95 -0
  353. package/src/modes/theme/defaults/alabaster.json +93 -0
  354. package/src/modes/theme/defaults/amethyst.json +96 -0
  355. package/src/modes/theme/defaults/anthracite.json +93 -0
  356. package/src/modes/theme/defaults/basalt.json +91 -0
  357. package/src/modes/theme/defaults/birch.json +95 -0
  358. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  359. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  360. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  361. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  362. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  363. package/src/modes/theme/defaults/dark-copper.json +95 -0
  364. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  365. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  366. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  367. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  368. package/src/modes/theme/defaults/dark-ember.json +95 -0
  369. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  370. package/src/modes/theme/defaults/dark-forest.json +96 -0
  371. package/src/modes/theme/defaults/dark-github.json +105 -0
  372. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  373. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  374. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  375. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  376. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  377. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  378. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  379. package/src/modes/theme/defaults/dark-nord.json +97 -0
  380. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  381. package/src/modes/theme/defaults/dark-one.json +100 -0
  382. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  383. package/src/modes/theme/defaults/dark-reef.json +91 -0
  384. package/src/modes/theme/defaults/dark-retro.json +92 -0
  385. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  386. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  387. package/src/modes/theme/defaults/dark-slate.json +95 -0
  388. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  389. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  390. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  391. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  392. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  393. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  394. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  395. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  396. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  397. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  398. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  399. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  400. package/src/modes/theme/defaults/graphite.json +92 -0
  401. package/src/modes/theme/defaults/index.ts +195 -0
  402. package/src/modes/theme/defaults/light-arctic.json +107 -0
  403. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  404. package/src/modes/theme/defaults/light-canyon.json +91 -0
  405. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  406. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  407. package/src/modes/theme/defaults/light-coral.json +95 -0
  408. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  409. package/src/modes/theme/defaults/light-dawn.json +90 -0
  410. package/src/modes/theme/defaults/light-dunes.json +91 -0
  411. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  412. package/src/modes/theme/defaults/light-forest.json +100 -0
  413. package/src/modes/theme/defaults/light-frost.json +95 -0
  414. package/src/modes/theme/defaults/light-github.json +115 -0
  415. package/src/modes/theme/defaults/light-glacier.json +91 -0
  416. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  417. package/src/modes/theme/defaults/light-haze.json +90 -0
  418. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  419. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  420. package/src/modes/theme/defaults/light-lavender.json +95 -0
  421. package/src/modes/theme/defaults/light-meadow.json +91 -0
  422. package/src/modes/theme/defaults/light-mint.json +95 -0
  423. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  424. package/src/modes/theme/defaults/light-ocean.json +99 -0
  425. package/src/modes/theme/defaults/light-one.json +99 -0
  426. package/src/modes/theme/defaults/light-opal.json +91 -0
  427. package/src/modes/theme/defaults/light-orchard.json +91 -0
  428. package/src/modes/theme/defaults/light-paper.json +95 -0
  429. package/src/modes/theme/defaults/light-prism.json +90 -0
  430. package/src/modes/theme/defaults/light-retro.json +98 -0
  431. package/src/modes/theme/defaults/light-sand.json +95 -0
  432. package/src/modes/theme/defaults/light-savanna.json +91 -0
  433. package/src/modes/theme/defaults/light-solarized.json +102 -0
  434. package/src/modes/theme/defaults/light-soleil.json +90 -0
  435. package/src/modes/theme/defaults/light-sunset.json +99 -0
  436. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  437. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  438. package/src/modes/theme/defaults/light-wetland.json +91 -0
  439. package/src/modes/theme/defaults/light-zenith.json +89 -0
  440. package/src/modes/theme/defaults/limestone.json +94 -0
  441. package/src/modes/theme/defaults/mahogany.json +97 -0
  442. package/src/modes/theme/defaults/marble.json +93 -0
  443. package/src/modes/theme/defaults/obsidian.json +91 -0
  444. package/src/modes/theme/defaults/onyx.json +91 -0
  445. package/src/modes/theme/defaults/pearl.json +93 -0
  446. package/src/modes/theme/defaults/porcelain.json +91 -0
  447. package/src/modes/theme/defaults/quartz.json +96 -0
  448. package/src/modes/theme/defaults/sandstone.json +95 -0
  449. package/src/modes/theme/defaults/titanium.json +90 -0
  450. package/src/modes/theme/light.json +93 -0
  451. package/src/modes/theme/mermaid-cache.ts +111 -0
  452. package/src/modes/theme/theme-schema.json +429 -0
  453. package/src/modes/theme/theme.ts +2333 -0
  454. package/src/modes/types.ts +216 -0
  455. package/src/modes/utils/ui-helpers.ts +529 -0
  456. package/src/patch/applicator.ts +1482 -0
  457. package/src/patch/diff.ts +425 -0
  458. package/src/patch/fuzzy.ts +784 -0
  459. package/src/patch/hashline.ts +972 -0
  460. package/src/patch/index.ts +964 -0
  461. package/src/patch/normalize.ts +397 -0
  462. package/src/patch/normative.ts +72 -0
  463. package/src/patch/parser.ts +532 -0
  464. package/src/patch/shared.ts +400 -0
  465. package/src/patch/types.ts +292 -0
  466. package/src/priority.json +35 -0
  467. package/src/prompts/agents/explore.md +48 -0
  468. package/src/prompts/agents/frontmatter.md +9 -0
  469. package/src/prompts/agents/init.md +36 -0
  470. package/src/prompts/agents/librarian.md +53 -0
  471. package/src/prompts/agents/oracle.md +51 -0
  472. package/src/prompts/agents/reviewer.md +70 -0
  473. package/src/prompts/agents/task.md +14 -0
  474. package/src/prompts/compaction/branch-summary-context.md +5 -0
  475. package/src/prompts/compaction/branch-summary-preamble.md +2 -0
  476. package/src/prompts/compaction/branch-summary.md +30 -0
  477. package/src/prompts/compaction/compaction-short-summary.md +9 -0
  478. package/src/prompts/compaction/compaction-summary-context.md +5 -0
  479. package/src/prompts/compaction/compaction-summary.md +38 -0
  480. package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
  481. package/src/prompts/compaction/compaction-update-summary.md +45 -0
  482. package/src/prompts/memories/consolidation.md +30 -0
  483. package/src/prompts/memories/read_path.md +11 -0
  484. package/src/prompts/memories/stage_one_input.md +6 -0
  485. package/src/prompts/memories/stage_one_system.md +21 -0
  486. package/src/prompts/review-request.md +64 -0
  487. package/src/prompts/system/agent-creation-architect.md +65 -0
  488. package/src/prompts/system/agent-creation-user.md +6 -0
  489. package/src/prompts/system/custom-system-prompt.md +68 -0
  490. package/src/prompts/system/file-operations.md +10 -0
  491. package/src/prompts/system/subagent-submit-reminder.md +11 -0
  492. package/src/prompts/system/subagent-system-prompt.md +31 -0
  493. package/src/prompts/system/subagent-user-prompt.md +8 -0
  494. package/src/prompts/system/summarization-system.md +3 -0
  495. package/src/prompts/system/system-prompt.md +300 -0
  496. package/src/prompts/system/title-system.md +2 -0
  497. package/src/prompts/system/ttsr-interrupt.md +7 -0
  498. package/src/prompts/system/web-search.md +28 -0
  499. package/src/prompts/tools/ask.md +44 -0
  500. package/src/prompts/tools/bash.md +24 -0
  501. package/src/prompts/tools/browser.md +33 -0
  502. package/src/prompts/tools/calculator.md +12 -0
  503. package/src/prompts/tools/explore.md +29 -0
  504. package/src/prompts/tools/fetch.md +16 -0
  505. package/src/prompts/tools/find.md +18 -0
  506. package/src/prompts/tools/gemini-image.md +23 -0
  507. package/src/prompts/tools/grep.md +28 -0
  508. package/src/prompts/tools/hashline.md +232 -0
  509. package/src/prompts/tools/librarian.md +24 -0
  510. package/src/prompts/tools/lsp.md +28 -0
  511. package/src/prompts/tools/oracle.md +26 -0
  512. package/src/prompts/tools/patch.md +74 -0
  513. package/src/prompts/tools/python.md +66 -0
  514. package/src/prompts/tools/read.md +36 -0
  515. package/src/prompts/tools/replace.md +38 -0
  516. package/src/prompts/tools/reviewer.md +41 -0
  517. package/src/prompts/tools/ssh.md +51 -0
  518. package/src/prompts/tools/task-summary.md +28 -0
  519. package/src/prompts/tools/task.md +275 -0
  520. package/src/prompts/tools/todo-write.md +65 -0
  521. package/src/prompts/tools/undo-edit.md +7 -0
  522. package/src/prompts/tools/web-search.md +19 -0
  523. package/src/prompts/tools/write.md +18 -0
  524. package/src/sdk.ts +1287 -0
  525. package/src/secrets/index.ts +116 -0
  526. package/src/secrets/obfuscator.ts +269 -0
  527. package/src/secrets/regex.ts +21 -0
  528. package/src/session/agent-session.ts +4669 -0
  529. package/src/session/agent-storage.ts +621 -0
  530. package/src/session/artifacts.ts +132 -0
  531. package/src/session/auth-storage.ts +1433 -0
  532. package/src/session/blob-store.ts +103 -0
  533. package/src/session/compaction/branch-summarization.ts +315 -0
  534. package/src/session/compaction/compaction.ts +864 -0
  535. package/src/session/compaction/index.ts +7 -0
  536. package/src/session/compaction/pruning.ts +91 -0
  537. package/src/session/compaction/utils.ts +171 -0
  538. package/src/session/history-storage.ts +170 -0
  539. package/src/session/messages.ts +317 -0
  540. package/src/session/session-manager.ts +2276 -0
  541. package/src/session/session-storage.ts +342 -0
  542. package/src/session/streaming-output.ts +565 -0
  543. package/src/slash-commands/builtin-registry.ts +439 -0
  544. package/src/ssh/config-writer.ts +183 -0
  545. package/src/ssh/connection-manager.ts +444 -0
  546. package/src/ssh/ssh-executor.ts +127 -0
  547. package/src/ssh/sshfs-mount.ts +135 -0
  548. package/src/stt/downloader.ts +71 -0
  549. package/src/stt/index.ts +3 -0
  550. package/src/stt/recorder.ts +351 -0
  551. package/src/stt/setup.ts +52 -0
  552. package/src/stt/stt-controller.ts +160 -0
  553. package/src/stt/transcribe.py +70 -0
  554. package/src/stt/transcriber.ts +91 -0
  555. package/src/system-prompt.ts +685 -0
  556. package/src/task/agents.ts +155 -0
  557. package/src/task/batch.ts +102 -0
  558. package/src/task/commands.ts +134 -0
  559. package/src/task/discovery.ts +126 -0
  560. package/src/task/executor.ts +908 -0
  561. package/src/task/index.ts +223 -0
  562. package/src/task/output-manager.ts +107 -0
  563. package/src/task/parallel.ts +84 -0
  564. package/src/task/render.ts +326 -0
  565. package/src/task/subprocess-tool-registry.ts +88 -0
  566. package/src/task/template.ts +32 -0
  567. package/src/task/types.ts +144 -0
  568. package/src/tools/ask.ts +523 -0
  569. package/src/tools/bash-interactive.ts +419 -0
  570. package/src/tools/bash-interceptor.ts +105 -0
  571. package/src/tools/bash-normalize.ts +107 -0
  572. package/src/tools/bash-skill-urls.ts +177 -0
  573. package/src/tools/bash.ts +347 -0
  574. package/src/tools/browser.ts +1374 -0
  575. package/src/tools/calculator.ts +537 -0
  576. package/src/tools/context.ts +39 -0
  577. package/src/tools/explore.ts +23 -0
  578. package/src/tools/fetch.ts +1091 -0
  579. package/src/tools/find.ts +540 -0
  580. package/src/tools/fs-cache-invalidation.ts +28 -0
  581. package/src/tools/gemini-image.ts +907 -0
  582. package/src/tools/grep.ts +489 -0
  583. package/src/tools/index.ts +337 -0
  584. package/src/tools/json-tree.ts +231 -0
  585. package/src/tools/jtd-to-json-schema.ts +247 -0
  586. package/src/tools/jtd-to-typescript.ts +198 -0
  587. package/src/tools/librarian.ts +33 -0
  588. package/src/tools/list-limit.ts +40 -0
  589. package/src/tools/notebook.ts +287 -0
  590. package/src/tools/oracle.ts +40 -0
  591. package/src/tools/output-meta.ts +459 -0
  592. package/src/tools/output-utils.ts +63 -0
  593. package/src/tools/path-utils.ts +116 -0
  594. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  595. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  596. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  597. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  598. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  599. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  600. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  601. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  602. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  603. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  604. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  605. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  606. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  607. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  608. package/src/tools/python.ts +1118 -0
  609. package/src/tools/read.ts +1193 -0
  610. package/src/tools/render-utils.ts +680 -0
  611. package/src/tools/renderers.ts +60 -0
  612. package/src/tools/reviewer-tool.ts +41 -0
  613. package/src/tools/ssh.ts +326 -0
  614. package/src/tools/subagent-tool.ts +169 -0
  615. package/src/tools/submit-result.ts +152 -0
  616. package/src/tools/todo-write.ts +255 -0
  617. package/src/tools/tool-errors.ts +92 -0
  618. package/src/tools/tool-result.ts +86 -0
  619. package/src/tools/undo-edit.ts +145 -0
  620. package/src/tools/undo-history.ts +22 -0
  621. package/src/tools/write.ts +274 -0
  622. package/src/tui/code-cell.ts +108 -0
  623. package/src/tui/file-list.ts +47 -0
  624. package/src/tui/index.ts +11 -0
  625. package/src/tui/output-block.ts +144 -0
  626. package/src/tui/status-line.ts +39 -0
  627. package/src/tui/tree-list.ts +53 -0
  628. package/src/tui/types.ts +16 -0
  629. package/src/tui/utils.ts +116 -0
  630. package/src/utils/changelog.ts +98 -0
  631. package/src/utils/event-bus.ts +33 -0
  632. package/src/utils/external-editor.ts +59 -0
  633. package/src/utils/file-display-mode.ts +36 -0
  634. package/src/utils/file-mentions.ts +384 -0
  635. package/src/utils/frontmatter.ts +101 -0
  636. package/src/utils/fuzzy.ts +108 -0
  637. package/src/utils/ignore-files.ts +119 -0
  638. package/src/utils/image-convert.ts +27 -0
  639. package/src/utils/image-resize.ts +236 -0
  640. package/src/utils/mime.ts +30 -0
  641. package/src/utils/open.ts +20 -0
  642. package/src/utils/shell-snapshot.ts +199 -0
  643. package/src/utils/timings.ts +26 -0
  644. package/src/utils/title-generator.ts +167 -0
  645. package/src/utils/tools-manager.ts +362 -0
  646. package/src/web/scrapers/artifacthub.ts +215 -0
  647. package/src/web/scrapers/arxiv.ts +88 -0
  648. package/src/web/scrapers/aur.ts +175 -0
  649. package/src/web/scrapers/biorxiv.ts +141 -0
  650. package/src/web/scrapers/bluesky.ts +284 -0
  651. package/src/web/scrapers/brew.ts +177 -0
  652. package/src/web/scrapers/cheatsh.ts +78 -0
  653. package/src/web/scrapers/chocolatey.ts +158 -0
  654. package/src/web/scrapers/choosealicense.ts +110 -0
  655. package/src/web/scrapers/cisa-kev.ts +100 -0
  656. package/src/web/scrapers/clojars.ts +180 -0
  657. package/src/web/scrapers/coingecko.ts +184 -0
  658. package/src/web/scrapers/crates-io.ts +128 -0
  659. package/src/web/scrapers/crossref.ts +149 -0
  660. package/src/web/scrapers/devto.ts +177 -0
  661. package/src/web/scrapers/discogs.ts +307 -0
  662. package/src/web/scrapers/discourse.ts +221 -0
  663. package/src/web/scrapers/dockerhub.ts +160 -0
  664. package/src/web/scrapers/fdroid.ts +158 -0
  665. package/src/web/scrapers/firefox-addons.ts +214 -0
  666. package/src/web/scrapers/flathub.ts +239 -0
  667. package/src/web/scrapers/github-gist.ts +68 -0
  668. package/src/web/scrapers/github.ts +490 -0
  669. package/src/web/scrapers/gitlab.ts +456 -0
  670. package/src/web/scrapers/go-pkg.ts +275 -0
  671. package/src/web/scrapers/hackage.ts +94 -0
  672. package/src/web/scrapers/hackernews.ts +208 -0
  673. package/src/web/scrapers/hex.ts +121 -0
  674. package/src/web/scrapers/huggingface.ts +385 -0
  675. package/src/web/scrapers/iacr.ts +86 -0
  676. package/src/web/scrapers/index.ts +249 -0
  677. package/src/web/scrapers/jetbrains-marketplace.ts +169 -0
  678. package/src/web/scrapers/lemmy.ts +220 -0
  679. package/src/web/scrapers/lobsters.ts +186 -0
  680. package/src/web/scrapers/mastodon.ts +310 -0
  681. package/src/web/scrapers/maven.ts +152 -0
  682. package/src/web/scrapers/mdn.ts +172 -0
  683. package/src/web/scrapers/metacpan.ts +253 -0
  684. package/src/web/scrapers/musicbrainz.ts +272 -0
  685. package/src/web/scrapers/npm.ts +114 -0
  686. package/src/web/scrapers/nuget.ts +205 -0
  687. package/src/web/scrapers/nvd.ts +243 -0
  688. package/src/web/scrapers/ollama.ts +265 -0
  689. package/src/web/scrapers/open-vsx.ts +119 -0
  690. package/src/web/scrapers/opencorporates.ts +275 -0
  691. package/src/web/scrapers/openlibrary.ts +319 -0
  692. package/src/web/scrapers/orcid.ts +298 -0
  693. package/src/web/scrapers/osv.ts +192 -0
  694. package/src/web/scrapers/packagist.ts +174 -0
  695. package/src/web/scrapers/pub-dev.ts +185 -0
  696. package/src/web/scrapers/pubmed.ts +177 -0
  697. package/src/web/scrapers/pypi.ts +129 -0
  698. package/src/web/scrapers/rawg.ts +124 -0
  699. package/src/web/scrapers/readthedocs.ts +125 -0
  700. package/src/web/scrapers/reddit.ts +104 -0
  701. package/src/web/scrapers/repology.ts +262 -0
  702. package/src/web/scrapers/rfc.ts +209 -0
  703. package/src/web/scrapers/rubygems.ts +117 -0
  704. package/src/web/scrapers/searchcode.ts +217 -0
  705. package/src/web/scrapers/sec-edgar.ts +274 -0
  706. package/src/web/scrapers/semantic-scholar.ts +190 -0
  707. package/src/web/scrapers/snapcraft.ts +200 -0
  708. package/src/web/scrapers/sourcegraph.ts +373 -0
  709. package/src/web/scrapers/spdx.ts +121 -0
  710. package/src/web/scrapers/spotify.ts +217 -0
  711. package/src/web/scrapers/stackoverflow.ts +124 -0
  712. package/src/web/scrapers/terraform.ts +304 -0
  713. package/src/web/scrapers/tldr.ts +51 -0
  714. package/src/web/scrapers/twitter.ts +97 -0
  715. package/src/web/scrapers/types.ts +200 -0
  716. package/src/web/scrapers/utils.ts +142 -0
  717. package/src/web/scrapers/vimeo.ts +152 -0
  718. package/src/web/scrapers/vscode-marketplace.ts +195 -0
  719. package/src/web/scrapers/w3c.ts +163 -0
  720. package/src/web/scrapers/wikidata.ts +357 -0
  721. package/src/web/scrapers/wikipedia.ts +95 -0
  722. package/src/web/scrapers/youtube.ts +312 -0
  723. package/src/web/search/auth.ts +178 -0
  724. package/src/web/search/index.ts +598 -0
  725. package/src/web/search/provider.ts +77 -0
  726. package/src/web/search/providers/anthropic.ts +284 -0
  727. package/src/web/search/providers/base.ts +22 -0
  728. package/src/web/search/providers/brave.ts +165 -0
  729. package/src/web/search/providers/codex.ts +377 -0
  730. package/src/web/search/providers/exa.ts +158 -0
  731. package/src/web/search/providers/gemini.ts +437 -0
  732. package/src/web/search/providers/jina.ts +99 -0
  733. package/src/web/search/providers/kimi.ts +196 -0
  734. package/src/web/search/providers/perplexity.ts +546 -0
  735. package/src/web/search/providers/synthetic.ts +136 -0
  736. package/src/web/search/providers/zai.ts +352 -0
  737. package/src/web/search/render.ts +299 -0
  738. package/src/web/search/types.ts +437 -0
@@ -0,0 +1,812 @@
1
+ /**
2
+ * Model resolution, scoping, and initial selection
3
+ */
4
+ import type { ThinkingLevel } from "@nghyane/arcane-agent";
5
+ import {
6
+ type Api,
7
+ DEFAULT_MODEL_PER_PROVIDER,
8
+ type KnownProvider,
9
+ type Model,
10
+ modelsAreEqual,
11
+ } from "@nghyane/arcane-ai";
12
+ import chalk from "chalk";
13
+ import { isValidThinkingLevel } from "../cli/args";
14
+ import MODEL_PRIO from "../priority.json" with { type: "json" };
15
+ import { fuzzyMatch } from "../utils/fuzzy";
16
+ import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
17
+ import type { Settings } from "./settings";
18
+
19
+ /** Default model IDs for each known provider */
20
+ export const defaultModelPerProvider: Record<KnownProvider, string> = DEFAULT_MODEL_PER_PROVIDER;
21
+
22
+ export interface ScopedModel {
23
+ model: Model<Api>;
24
+ thinkingLevel?: ThinkingLevel;
25
+ explicitThinkingLevel: boolean;
26
+ }
27
+
28
+ /**
29
+ * Parse a model string in "provider/modelId" format.
30
+ * Returns undefined if the format is invalid.
31
+ */
32
+ export function parseModelString(modelStr: string): { provider: string; id: string } | undefined {
33
+ const slashIdx = modelStr.indexOf("/");
34
+ if (slashIdx <= 0) return undefined;
35
+ return { provider: modelStr.slice(0, slashIdx), id: modelStr.slice(slashIdx + 1) };
36
+ }
37
+
38
+ /**
39
+ * Format a model as "provider/modelId" string.
40
+ */
41
+ export function formatModelString(model: Model<Api>): string {
42
+ return `${model.provider}/${model.id}`;
43
+ }
44
+
45
+ export interface ModelMatchPreferences {
46
+ /** Most-recently-used model keys (provider/modelId) to prefer when ambiguous. */
47
+ usageOrder?: string[];
48
+ /** Providers to deprioritize when no recent usage is available. */
49
+ deprioritizeProviders?: string[];
50
+ }
51
+
52
+ interface ModelPreferenceContext {
53
+ modelUsageRank: Map<string, number>;
54
+ providerUsageRank: Map<string, number>;
55
+ deprioritizedProviders: Set<string>;
56
+ modelOrder: Map<string, number>;
57
+ }
58
+
59
+ function buildPreferenceContext(
60
+ availableModels: Model<Api>[],
61
+ preferences: ModelMatchPreferences | undefined,
62
+ ): ModelPreferenceContext {
63
+ const modelUsageRank = new Map<string, number>();
64
+ const providerUsageRank = new Map<string, number>();
65
+ const usageOrder = preferences?.usageOrder ?? [];
66
+ for (let i = 0; i < usageOrder.length; i += 1) {
67
+ const key = usageOrder[i];
68
+ if (!modelUsageRank.has(key)) {
69
+ modelUsageRank.set(key, i);
70
+ }
71
+ const parsed = parseModelString(key);
72
+ if (parsed && !providerUsageRank.has(parsed.provider)) {
73
+ providerUsageRank.set(parsed.provider, i);
74
+ }
75
+ }
76
+
77
+ const deprioritizedProviders = new Set(preferences?.deprioritizeProviders ?? ["openrouter"]);
78
+ const modelOrder = new Map<string, number>();
79
+ for (let i = 0; i < availableModels.length; i += 1) {
80
+ modelOrder.set(formatModelString(availableModels[i]), i);
81
+ }
82
+
83
+ return { modelUsageRank, providerUsageRank, deprioritizedProviders, modelOrder };
84
+ }
85
+
86
+ function pickPreferredModel(candidates: Model<Api>[], context: ModelPreferenceContext): Model<Api> {
87
+ if (candidates.length <= 1) return candidates[0];
88
+ return [...candidates].sort((a, b) => {
89
+ const aKey = formatModelString(a);
90
+ const bKey = formatModelString(b);
91
+ const aUsage = context.modelUsageRank.get(aKey);
92
+ const bUsage = context.modelUsageRank.get(bKey);
93
+ if (aUsage !== undefined || bUsage !== undefined) {
94
+ return (aUsage ?? Number.POSITIVE_INFINITY) - (bUsage ?? Number.POSITIVE_INFINITY);
95
+ }
96
+
97
+ const aProviderUsage = context.providerUsageRank.get(a.provider);
98
+ const bProviderUsage = context.providerUsageRank.get(b.provider);
99
+ if (aProviderUsage !== undefined || bProviderUsage !== undefined) {
100
+ return (aProviderUsage ?? Number.POSITIVE_INFINITY) - (bProviderUsage ?? Number.POSITIVE_INFINITY);
101
+ }
102
+
103
+ const aDeprioritized = context.deprioritizedProviders.has(a.provider);
104
+ const bDeprioritized = context.deprioritizedProviders.has(b.provider);
105
+ if (aDeprioritized !== bDeprioritized) {
106
+ return aDeprioritized ? 1 : -1;
107
+ }
108
+
109
+ const aOrder = context.modelOrder.get(aKey) ?? 0;
110
+ const bOrder = context.modelOrder.get(bKey) ?? 0;
111
+ return aOrder - bOrder;
112
+ })[0];
113
+ }
114
+
115
+ /**
116
+ * Helper to check if a model ID looks like an alias (no date suffix)
117
+ * Dates are typically in format: -20241022 or -20250929
118
+ */
119
+ function isAlias(id: string): boolean {
120
+ // Check if ID ends with -latest
121
+ if (id.endsWith("-latest")) return true;
122
+
123
+ // Check if ID ends with a date pattern (-YYYYMMDD)
124
+ const datePattern = /-\d{8}$/;
125
+ return !datePattern.test(id);
126
+ }
127
+
128
+ /**
129
+ * Try to match a pattern to a model from the available models list.
130
+ * Returns the matched model or undefined if no match found.
131
+ */
132
+ function tryMatchModel(
133
+ modelPattern: string,
134
+ availableModels: Model<Api>[],
135
+ context: ModelPreferenceContext,
136
+ ): Model<Api> | undefined {
137
+ // Check for provider/modelId format (provider is everything before the first /)
138
+ const slashIndex = modelPattern.indexOf("/");
139
+ if (slashIndex !== -1) {
140
+ const provider = modelPattern.substring(0, slashIndex);
141
+ const modelId = modelPattern.substring(slashIndex + 1);
142
+ const providerMatch = availableModels.find(
143
+ m => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),
144
+ );
145
+ if (providerMatch) {
146
+ return providerMatch;
147
+ }
148
+
149
+ const providerModels = availableModels.filter(m => m.provider.toLowerCase() === provider.toLowerCase());
150
+ if (providerModels.length > 0) {
151
+ const scored = providerModels
152
+ .map(model => ({ model, match: fuzzyMatch(modelId, model.id) }))
153
+ .filter(entry => entry.match.matches);
154
+ if (scored.length > 0) {
155
+ scored.sort((a, b) => {
156
+ if (a.match.score !== b.match.score) return a.match.score - b.match.score;
157
+ const aKey = formatModelString(a.model);
158
+ const bKey = formatModelString(b.model);
159
+ const aUsage = context.modelUsageRank.get(aKey) ?? Number.POSITIVE_INFINITY;
160
+ const bUsage = context.modelUsageRank.get(bKey) ?? Number.POSITIVE_INFINITY;
161
+ if (aUsage !== bUsage) return aUsage - bUsage;
162
+
163
+ const aProviderUsage = context.providerUsageRank.get(a.model.provider) ?? Number.POSITIVE_INFINITY;
164
+ const bProviderUsage = context.providerUsageRank.get(b.model.provider) ?? Number.POSITIVE_INFINITY;
165
+ if (aProviderUsage !== bProviderUsage) return aProviderUsage - bProviderUsage;
166
+
167
+ const aOrder = context.modelOrder.get(aKey) ?? 0;
168
+ const bOrder = context.modelOrder.get(bKey) ?? 0;
169
+ return aOrder - bOrder;
170
+ });
171
+ return scored[0]?.model;
172
+ }
173
+ }
174
+ // No exact provider/model match - fall through to other matching
175
+ }
176
+
177
+ // Check for exact ID match (case-insensitive)
178
+ const exactMatches = availableModels.filter(m => m.id.toLowerCase() === modelPattern.toLowerCase());
179
+ if (exactMatches.length > 0) {
180
+ return pickPreferredModel(exactMatches, context);
181
+ }
182
+
183
+ // No exact match - fall back to partial matching
184
+ const matches = availableModels.filter(
185
+ m =>
186
+ m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
187
+ m.name?.toLowerCase().includes(modelPattern.toLowerCase()),
188
+ );
189
+
190
+ if (matches.length === 0) {
191
+ return undefined;
192
+ }
193
+
194
+ // Separate into aliases and dated versions
195
+ const aliases = matches.filter(m => isAlias(m.id));
196
+ const datedVersions = matches.filter(m => !isAlias(m.id));
197
+
198
+ if (aliases.length > 0) {
199
+ return pickPreferredModel(aliases, context);
200
+ }
201
+ if (datedVersions.length === 0) return undefined;
202
+
203
+ if (datedVersions.length === 1) {
204
+ return datedVersions[0];
205
+ }
206
+
207
+ const sortedById = [...datedVersions].sort((a, b) => b.id.localeCompare(a.id));
208
+ const topId = sortedById[0]?.id;
209
+ if (!topId) return undefined;
210
+ const topCandidates = sortedById.filter(model => model.id === topId);
211
+ return pickPreferredModel(topCandidates, context);
212
+ }
213
+
214
+ export interface ParsedModelResult {
215
+ model: Model<Api> | undefined;
216
+ /** Thinking level if explicitly specified in pattern, undefined otherwise */
217
+ thinkingLevel?: ThinkingLevel;
218
+ warning: string | undefined;
219
+ explicitThinkingLevel: boolean;
220
+ }
221
+
222
+ /**
223
+ * Parse a pattern to extract model and thinking level.
224
+ * Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).
225
+ *
226
+ * Algorithm:
227
+ * 1. Try to match full pattern as a model
228
+ * 2. If found, return it with undefined thinking level
229
+ * 3. If not found and has colons, split on last colon:
230
+ * - If suffix is valid thinking level, use it and recurse on prefix
231
+ * - If suffix is invalid, warn and recurse on prefix
232
+ *
233
+ * @internal Exported for testing
234
+ */
235
+ function parseModelPatternWithContext(
236
+ pattern: string,
237
+ availableModels: Model<Api>[],
238
+ context: ModelPreferenceContext,
239
+ options?: { allowInvalidThinkingLevelFallback?: boolean },
240
+ ): ParsedModelResult {
241
+ // Try exact match first
242
+ const exactMatch = tryMatchModel(pattern, availableModels, context);
243
+ if (exactMatch) {
244
+ return { model: exactMatch, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
245
+ }
246
+
247
+ // No match - try splitting on last colon if present
248
+ const lastColonIndex = pattern.lastIndexOf(":");
249
+ if (lastColonIndex === -1) {
250
+ // No colons, pattern simply doesn't match any model
251
+ return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
252
+ }
253
+
254
+ const prefix = pattern.substring(0, lastColonIndex);
255
+ const suffix = pattern.substring(lastColonIndex + 1);
256
+
257
+ if (isValidThinkingLevel(suffix)) {
258
+ // Valid thinking level - recurse on prefix and use this level
259
+ const result = parseModelPatternWithContext(prefix, availableModels, context, options);
260
+ if (result.model) {
261
+ // Only use this thinking level if no warning from inner recursion
262
+ const explicitThinkingLevel = !result.warning;
263
+ return {
264
+ model: result.model,
265
+ thinkingLevel: explicitThinkingLevel ? suffix : undefined,
266
+ warning: result.warning,
267
+ explicitThinkingLevel,
268
+ };
269
+ }
270
+ return result;
271
+ }
272
+
273
+ const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;
274
+ if (!allowFallback) {
275
+ return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
276
+ }
277
+
278
+ // Invalid suffix - recurse on prefix and warn
279
+ const result = parseModelPatternWithContext(prefix, availableModels, context, options);
280
+ if (result.model) {
281
+ return {
282
+ model: result.model,
283
+ thinkingLevel: undefined,
284
+ warning: `Invalid thinking level "${suffix}" in pattern "${pattern}". Using default instead.`,
285
+ explicitThinkingLevel: false,
286
+ };
287
+ }
288
+ return result;
289
+ }
290
+
291
+ export function parseModelPattern(
292
+ pattern: string,
293
+ availableModels: Model<Api>[],
294
+ preferences?: ModelMatchPreferences,
295
+ options?: { allowInvalidThinkingLevelFallback?: boolean },
296
+ ): ParsedModelResult {
297
+ const context = buildPreferenceContext(availableModels, preferences);
298
+ return parseModelPatternWithContext(pattern, availableModels, context, options);
299
+ }
300
+
301
+ const PREFIX_MODEL_ROLE = "arcane/";
302
+ const DEFAULT_MODEL_ROLE = "default";
303
+
304
+ /**
305
+ * Check if a model override value is effectively the default role.
306
+ */
307
+ export function isDefaultModelAlias(value: string | string[] | undefined): boolean {
308
+ if (!value) return true;
309
+ if (Array.isArray(value)) return value.every(entry => isDefaultModelAlias(entry));
310
+ if (value.startsWith(PREFIX_MODEL_ROLE)) {
311
+ value = value.slice(PREFIX_MODEL_ROLE.length);
312
+ }
313
+ return value === DEFAULT_MODEL_ROLE;
314
+ }
315
+
316
+ /**
317
+ * Expand a role alias like "arcane/fast" to the configured model string.
318
+ */
319
+ export function expandRoleAlias(value: string, settings?: Settings): string {
320
+ const normalized = value.trim();
321
+ if (normalized === "default") return settings?.getModelRole("default") ?? value;
322
+ if (!normalized.startsWith(PREFIX_MODEL_ROLE)) return value;
323
+ const role = normalized.slice(PREFIX_MODEL_ROLE.length) as ModelRole;
324
+ if (!MODEL_ROLE_IDS.includes(role)) return value;
325
+ return settings?.getModelRole(role) ?? value;
326
+ }
327
+
328
+ /**
329
+ * Resolve a model identifier or pattern to a Model instance.
330
+ */
331
+ export function resolveModelFromString(
332
+ value: string,
333
+ available: Model<Api>[],
334
+ matchPreferences?: ModelMatchPreferences,
335
+ ): Model<Api> | undefined {
336
+ const parsed = parseModelString(value);
337
+ if (parsed) {
338
+ return available.find(model => model.provider === parsed.provider && model.id === parsed.id);
339
+ }
340
+ return parseModelPattern(value, available, matchPreferences).model;
341
+ }
342
+
343
+ /**
344
+ * Resolve a model from configured roles, honoring order and overrides.
345
+ */
346
+ export function resolveModelFromSettings(options: {
347
+ settings: Settings;
348
+ availableModels: Model<Api>[];
349
+ matchPreferences?: ModelMatchPreferences;
350
+ roleOrder?: readonly ModelRole[];
351
+ }): Model<Api> | undefined {
352
+ const { settings, availableModels, matchPreferences, roleOrder } = options;
353
+ const roles = roleOrder ?? MODEL_ROLE_IDS;
354
+ for (const role of roles) {
355
+ const configured = settings.getModelRole(role);
356
+ if (!configured) continue;
357
+ const resolved = resolveModelFromString(expandRoleAlias(configured, settings), availableModels, matchPreferences);
358
+ if (resolved) return resolved;
359
+ }
360
+ return availableModels[0];
361
+ }
362
+
363
+ /**
364
+ * Resolve a list of override patterns to the first matching model.
365
+ */
366
+ export function resolveModelOverride(
367
+ modelPatterns: string[],
368
+ modelRegistry: ModelRegistry,
369
+ settings?: Settings,
370
+ ): { model?: Model<Api>; thinkingLevel?: ThinkingLevel } {
371
+ if (modelPatterns.length === 0) return {};
372
+ const matchPreferences = { usageOrder: settings?.getStorage()?.getModelUsageOrder() };
373
+ for (const pattern of modelPatterns) {
374
+ const normalized = pattern.trim();
375
+ if (!normalized || isDefaultModelAlias(normalized)) {
376
+ continue;
377
+ }
378
+ const effectivePattern = expandRoleAlias(pattern, settings);
379
+ const { model, thinkingLevel } = parseModelPattern(
380
+ effectivePattern,
381
+ modelRegistry.getAvailable(),
382
+ matchPreferences,
383
+ );
384
+ if (model) {
385
+ return { model, thinkingLevel: thinkingLevel !== "off" ? thinkingLevel : undefined };
386
+ }
387
+ }
388
+ return {};
389
+ }
390
+
391
+ /**
392
+ * Resolve model patterns to actual Model objects with optional thinking levels
393
+ * Format: "pattern:level" where :level is optional
394
+ * For each pattern, finds all matching models and picks the best version:
395
+ * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)
396
+ * 2. If no alias, pick the latest dated version
397
+ *
398
+ * Supports models with colons in their IDs (e.g., OpenRouter's model:exacto).
399
+ * The algorithm tries to match the full pattern first, then progressively
400
+ * strips colon-suffixes to find a match.
401
+ */
402
+ export async function resolveModelScope(
403
+ patterns: string[],
404
+ modelRegistry: ModelRegistry,
405
+ preferences?: ModelMatchPreferences,
406
+ ): Promise<ScopedModel[]> {
407
+ const availableModels = modelRegistry.getAvailable();
408
+ const context = buildPreferenceContext(availableModels, preferences);
409
+ const scopedModels: ScopedModel[] = [];
410
+
411
+ for (const pattern of patterns) {
412
+ // Check if pattern contains glob characters
413
+ if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
414
+ // Extract optional thinking level suffix (e.g., "provider/*:high")
415
+ const colonIdx = pattern.lastIndexOf(":");
416
+ let globPattern = pattern;
417
+ let thinkingLevel: ThinkingLevel | undefined;
418
+ let explicitThinkingLevel = false;
419
+
420
+ if (colonIdx !== -1) {
421
+ const suffix = pattern.substring(colonIdx + 1);
422
+ if (isValidThinkingLevel(suffix)) {
423
+ thinkingLevel = suffix;
424
+ explicitThinkingLevel = true;
425
+ globPattern = pattern.substring(0, colonIdx);
426
+ }
427
+ }
428
+
429
+ // Match against "provider/modelId" format OR just model ID
430
+ // This allows "*sonnet*" to match without requiring "anthropic/*sonnet*"
431
+ const matchingModels = availableModels.filter(m => {
432
+ const fullId = `${m.provider}/${m.id}`;
433
+ const glob = new Bun.Glob(globPattern.toLowerCase());
434
+ return glob.match(fullId.toLowerCase()) || glob.match(m.id.toLowerCase());
435
+ });
436
+
437
+ if (matchingModels.length === 0) {
438
+ console.warn(chalk.yellow(`Warning: No models match pattern "${pattern}"`));
439
+ continue;
440
+ }
441
+
442
+ for (const model of matchingModels) {
443
+ if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
444
+ scopedModels.push({ model, thinkingLevel, explicitThinkingLevel });
445
+ }
446
+ }
447
+ continue;
448
+ }
449
+
450
+ const { model, thinkingLevel, warning, explicitThinkingLevel } = parseModelPatternWithContext(
451
+ pattern,
452
+ availableModels,
453
+ context,
454
+ );
455
+
456
+ if (warning) {
457
+ console.warn(chalk.yellow(`Warning: ${warning}`));
458
+ }
459
+
460
+ if (!model) {
461
+ console.warn(chalk.yellow(`Warning: No models match pattern "${pattern}"`));
462
+ continue;
463
+ }
464
+
465
+ // Avoid duplicates
466
+ if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
467
+ scopedModels.push({ model, thinkingLevel, explicitThinkingLevel });
468
+ }
469
+ }
470
+
471
+ return scopedModels;
472
+ }
473
+
474
+ export interface ResolveCliModelResult {
475
+ model: Model<Api> | undefined;
476
+ thinkingLevel?: ThinkingLevel;
477
+ warning: string | undefined;
478
+ error: string | undefined;
479
+ }
480
+
481
+ /**
482
+ * Resolve a single model from CLI flags.
483
+ */
484
+ export function resolveCliModel(options: {
485
+ cliProvider?: string;
486
+ cliModel?: string;
487
+ modelRegistry: ModelRegistry;
488
+ preferences?: ModelMatchPreferences;
489
+ }): ResolveCliModelResult {
490
+ const { cliProvider, cliModel, modelRegistry, preferences } = options;
491
+
492
+ if (!cliModel) {
493
+ return { model: undefined, warning: undefined, error: undefined };
494
+ }
495
+
496
+ const availableModels = modelRegistry.getAll();
497
+ if (availableModels.length === 0) {
498
+ return {
499
+ model: undefined,
500
+ warning: undefined,
501
+ error: "No models available. Check your installation or add models to models.json.",
502
+ };
503
+ }
504
+
505
+ const providerMap = new Map<string, string>();
506
+ for (const model of availableModels) {
507
+ providerMap.set(model.provider.toLowerCase(), model.provider);
508
+ }
509
+
510
+ let provider = cliProvider ? providerMap.get(cliProvider.toLowerCase()) : undefined;
511
+ if (cliProvider && !provider) {
512
+ return {
513
+ model: undefined,
514
+ warning: undefined,
515
+ error: `Unknown provider "${cliProvider}". Use --list-models to see available providers/models.`,
516
+ };
517
+ }
518
+
519
+ if (!provider) {
520
+ const lower = cliModel.toLowerCase();
521
+ const exact = availableModels.find(
522
+ model => model.id.toLowerCase() === lower || `${model.provider}/${model.id}`.toLowerCase() === lower,
523
+ );
524
+ if (exact) {
525
+ return { model: exact, warning: undefined, thinkingLevel: undefined, error: undefined };
526
+ }
527
+ }
528
+
529
+ let pattern = cliModel;
530
+
531
+ if (!provider) {
532
+ const slashIndex = cliModel.indexOf("/");
533
+ if (slashIndex !== -1) {
534
+ const maybeProvider = cliModel.substring(0, slashIndex);
535
+ const canonical = providerMap.get(maybeProvider.toLowerCase());
536
+ if (canonical) {
537
+ provider = canonical;
538
+ pattern = cliModel.substring(slashIndex + 1);
539
+ }
540
+ }
541
+ } else {
542
+ const prefix = `${provider}/`;
543
+ if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {
544
+ pattern = cliModel.substring(prefix.length);
545
+ }
546
+ }
547
+
548
+ const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
549
+ const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, preferences, {
550
+ allowInvalidThinkingLevelFallback: false,
551
+ });
552
+
553
+ if (!model) {
554
+ const display = provider ? `${provider}/${pattern}` : cliModel;
555
+ return {
556
+ model: undefined,
557
+ thinkingLevel: undefined,
558
+ warning,
559
+ error: `Model "${display}" not found. Use --list-models to see available models.`,
560
+ };
561
+ }
562
+
563
+ return { model, thinkingLevel, warning, error: undefined };
564
+ }
565
+
566
+ export interface InitialModelResult {
567
+ model: Model<Api> | undefined;
568
+ thinkingLevel: ThinkingLevel;
569
+ fallbackMessage: string | undefined;
570
+ }
571
+
572
+ /**
573
+ * Find the initial model to use based on priority:
574
+ * 1. CLI args (provider + model)
575
+ * 2. First model from scoped models (if not continuing/resuming)
576
+ * 3. Restored from session (if continuing/resuming)
577
+ * 4. Saved default from settings
578
+ * 5. First available model with valid API key
579
+ */
580
+ export async function findInitialModel(options: {
581
+ cliProvider?: string;
582
+ cliModel?: string;
583
+ scopedModels: ScopedModel[];
584
+ isContinuing: boolean;
585
+ defaultProvider?: string;
586
+ defaultModelId?: string;
587
+ defaultThinkingLevel?: ThinkingLevel;
588
+ modelRegistry: ModelRegistry;
589
+ }): Promise<InitialModelResult> {
590
+ const {
591
+ cliProvider,
592
+ cliModel,
593
+ scopedModels,
594
+ isContinuing,
595
+ defaultProvider,
596
+ defaultModelId,
597
+ defaultThinkingLevel,
598
+ modelRegistry,
599
+ } = options;
600
+
601
+ let model: Model<Api> | undefined;
602
+ let thinkingLevel: ThinkingLevel = "off";
603
+
604
+ // 1. CLI args take priority
605
+ if (cliProvider && cliModel) {
606
+ const found = modelRegistry.find(cliProvider, cliModel);
607
+ if (!found) {
608
+ console.error(chalk.red(`Model ${cliProvider}/${cliModel} not found`));
609
+ process.exit(1);
610
+ }
611
+ return { model: found, thinkingLevel: "off", fallbackMessage: undefined };
612
+ }
613
+
614
+ // 2. Use first model from scoped models (skip if continuing/resuming)
615
+ if (scopedModels.length > 0 && !isContinuing) {
616
+ const scoped = scopedModels[0];
617
+ const scopedThinkingLevel = scoped.thinkingLevel ?? defaultThinkingLevel ?? "off";
618
+ return {
619
+ model: scoped.model,
620
+ thinkingLevel: scopedThinkingLevel,
621
+ fallbackMessage: undefined,
622
+ };
623
+ }
624
+
625
+ // 3. Try saved default from settings
626
+ if (defaultProvider && defaultModelId) {
627
+ const found = modelRegistry.find(defaultProvider, defaultModelId);
628
+ if (found) {
629
+ model = found;
630
+ if (defaultThinkingLevel) {
631
+ thinkingLevel = defaultThinkingLevel;
632
+ }
633
+ return { model, thinkingLevel, fallbackMessage: undefined };
634
+ }
635
+ }
636
+
637
+ // 4. Try first available model with valid API key
638
+ const availableModels = modelRegistry.getAvailable();
639
+
640
+ if (availableModels.length > 0) {
641
+ // Try to find a default model from known providers
642
+ for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {
643
+ const defaultId = defaultModelPerProvider[provider];
644
+ const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
645
+ if (match) {
646
+ return { model: match, thinkingLevel: "off", fallbackMessage: undefined };
647
+ }
648
+ }
649
+
650
+ // If no default found, use first available
651
+ return { model: availableModels[0], thinkingLevel: "off", fallbackMessage: undefined };
652
+ }
653
+
654
+ // 5. No model found
655
+ return { model: undefined, thinkingLevel: "off", fallbackMessage: undefined };
656
+ }
657
+
658
+ /**
659
+ * Restore model from session, with fallback to available models
660
+ */
661
+ export async function restoreModelFromSession(
662
+ savedProvider: string,
663
+ savedModelId: string,
664
+ currentModel: Model<Api> | undefined,
665
+ shouldPrintMessages: boolean,
666
+ modelRegistry: ModelRegistry,
667
+ ): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
668
+ const restoredModel = modelRegistry.find(savedProvider, savedModelId);
669
+
670
+ // Check if restored model exists and has a valid API key
671
+ const hasApiKey = restoredModel ? !!(await modelRegistry.getApiKey(restoredModel)) : false;
672
+
673
+ if (restoredModel && hasApiKey) {
674
+ if (shouldPrintMessages) {
675
+ console.log(chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`));
676
+ }
677
+ return { model: restoredModel, fallbackMessage: undefined };
678
+ }
679
+
680
+ // Model not found or no API key - fall back
681
+ const reason = !restoredModel ? "model no longer exists" : "no API key available";
682
+
683
+ if (shouldPrintMessages) {
684
+ console.error(chalk.yellow(`Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`));
685
+ }
686
+
687
+ // If we already have a model, use it as fallback
688
+ if (currentModel) {
689
+ if (shouldPrintMessages) {
690
+ console.log(chalk.dim(`Falling back to: ${currentModel.provider}/${currentModel.id}`));
691
+ }
692
+ return {
693
+ model: currentModel,
694
+ fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,
695
+ };
696
+ }
697
+
698
+ // Try to find any available model
699
+ const availableModels = modelRegistry.getAvailable();
700
+
701
+ if (availableModels.length > 0) {
702
+ // Try to find a default model from known providers
703
+ let fallbackModel: Model<Api> | undefined;
704
+ for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {
705
+ const defaultId = defaultModelPerProvider[provider];
706
+ const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
707
+ if (match) {
708
+ fallbackModel = match;
709
+ break;
710
+ }
711
+ }
712
+
713
+ // If no default found, use first available
714
+ if (!fallbackModel) {
715
+ fallbackModel = availableModels[0];
716
+ }
717
+
718
+ if (shouldPrintMessages) {
719
+ console.log(chalk.dim(`Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`));
720
+ }
721
+
722
+ return {
723
+ model: fallbackModel,
724
+ fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,
725
+ };
726
+ }
727
+
728
+ // No models available
729
+ return { model: undefined, fallbackMessage: undefined };
730
+ }
731
+
732
+ /**
733
+ * Find a fast model using the priority chain.
734
+ * Tries exact matches first, then fuzzy matches.
735
+ *
736
+ * @param modelRegistry The model registry to search
737
+ * @param savedModel Optional saved model string from settings (provider/modelId)
738
+ * @returns The best available fast model, or undefined if none found
739
+ */
740
+ export async function findSmolModel(
741
+ modelRegistry: ModelRegistry,
742
+ savedModel?: string,
743
+ ): Promise<Model<Api> | undefined> {
744
+ const availableModels = modelRegistry.getAvailable();
745
+ if (availableModels.length === 0) return undefined;
746
+
747
+ // 1. Try saved model from settings
748
+ if (savedModel) {
749
+ const parsed = parseModelString(savedModel);
750
+ if (parsed) {
751
+ const match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
752
+ if (match) return match;
753
+ }
754
+ }
755
+
756
+ // 2. Try priority chain
757
+ for (const pattern of MODEL_PRIO.fast) {
758
+ // Try exact match with provider prefix
759
+ const providerMatch = availableModels.find(m => `${m.provider}/${m.id}`.toLowerCase() === pattern);
760
+ if (providerMatch) return providerMatch;
761
+
762
+ // Try exact match first
763
+ const exactMatch = availableModels.find(m => m.id.toLowerCase() === pattern);
764
+ if (exactMatch) return exactMatch;
765
+
766
+ // Try fuzzy match (substring)
767
+ const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern));
768
+ if (fuzzyMatch) return fuzzyMatch;
769
+ }
770
+
771
+ // 3. Fallback to first available (same as default)
772
+ return availableModels[0];
773
+ }
774
+
775
+ /**
776
+ * Find a oracle/comprehensive model using the priority chain.
777
+ * Prioritizes reasoning and codex models for thorough analysis.
778
+ *
779
+ * @param modelRegistry The model registry to search
780
+ * @param savedModel Optional saved model string from settings (provider/modelId)
781
+ * @returns The best available oracle model, or undefined if none found
782
+ */
783
+ export async function findSlowModel(
784
+ modelRegistry: ModelRegistry,
785
+ savedModel?: string,
786
+ ): Promise<Model<Api> | undefined> {
787
+ const availableModels = modelRegistry.getAvailable();
788
+ if (availableModels.length === 0) return undefined;
789
+
790
+ // 1. Try saved model from settings
791
+ if (savedModel) {
792
+ const parsed = parseModelString(savedModel);
793
+ if (parsed) {
794
+ const match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
795
+ if (match) return match;
796
+ }
797
+ }
798
+
799
+ // 2. Try priority chain
800
+ for (const pattern of MODEL_PRIO.oracle) {
801
+ // Try exact match first
802
+ const exactMatch = availableModels.find(m => m.id.toLowerCase() === pattern.toLowerCase());
803
+ if (exactMatch) return exactMatch;
804
+
805
+ // Try fuzzy match (substring)
806
+ const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern.toLowerCase()));
807
+ if (fuzzyMatch) return fuzzyMatch;
808
+ }
809
+
810
+ // 3. Fallback to first available (same as default)
811
+ return availableModels[0];
812
+ }