@pellux/goodvibes-tui 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (732) hide show
  1. package/.goodvibes/GOODVIBES.md +35 -0
  2. package/.goodvibes/agents/reviewer.md +89 -0
  3. package/.goodvibes/skills/add-provider/SKILL.md +199 -0
  4. package/CHANGELOG.md +1681 -0
  5. package/README.md +1607 -0
  6. package/bin/goodvibes +66 -0
  7. package/docs/README.md +32 -0
  8. package/docs/foundation-artifacts/README.md +16 -0
  9. package/docs/foundation-artifacts/knowledge-graphql.graphql +397 -0
  10. package/docs/foundation-artifacts/knowledge-store.sql +183 -0
  11. package/docs/foundation-artifacts/operator-contract.json +55157 -0
  12. package/docs/foundation-artifacts/peer-contract.json +2384 -0
  13. package/package.json +114 -0
  14. package/scripts/postinstall.mjs +203 -0
  15. package/src/acp/connection.ts +447 -0
  16. package/src/acp/index.ts +7 -0
  17. package/src/acp/manager.ts +133 -0
  18. package/src/adapters/bluebubbles/index.ts +127 -0
  19. package/src/adapters/discord/index.ts +297 -0
  20. package/src/adapters/github/index.ts +73 -0
  21. package/src/adapters/google-chat/index.ts +119 -0
  22. package/src/adapters/imessage/index.ts +92 -0
  23. package/src/adapters/index.ts +15 -0
  24. package/src/adapters/matrix/index.ts +116 -0
  25. package/src/adapters/mattermost/index.ts +151 -0
  26. package/src/adapters/msteams/index.ts +180 -0
  27. package/src/adapters/ntfy/index.ts +118 -0
  28. package/src/adapters/signal/index.ts +92 -0
  29. package/src/adapters/slack/index.ts +323 -0
  30. package/src/adapters/telegram/index.ts +160 -0
  31. package/src/adapters/types.ts +97 -0
  32. package/src/adapters/webhook/index.ts +178 -0
  33. package/src/adapters/whatsapp/index.ts +135 -0
  34. package/src/agents/message-bus-core.ts +312 -0
  35. package/src/agents/message-bus.ts +2 -0
  36. package/src/agents/orchestrator-prompts.ts +351 -0
  37. package/src/agents/orchestrator-runner.ts +668 -0
  38. package/src/agents/orchestrator.ts +437 -0
  39. package/src/agents/session.ts +108 -0
  40. package/src/agents/worktree.ts +153 -0
  41. package/src/agents/wrfc-config.ts +47 -0
  42. package/src/agents/wrfc-controller.ts +747 -0
  43. package/src/agents/wrfc-gate-runtime.ts +75 -0
  44. package/src/agents/wrfc-reporting.ts +284 -0
  45. package/src/agents/wrfc-runtime-events.ts +150 -0
  46. package/src/agents/wrfc-types.ts +67 -0
  47. package/src/automation/delivery-manager.ts +368 -0
  48. package/src/automation/index.ts +72 -0
  49. package/src/automation/manager-runtime-delivery.ts +139 -0
  50. package/src/automation/manager-runtime-events.ts +131 -0
  51. package/src/automation/manager-runtime-execution.ts +511 -0
  52. package/src/automation/manager-runtime-helpers.ts +433 -0
  53. package/src/automation/manager-runtime-job-mutations.ts +175 -0
  54. package/src/automation/manager-runtime-reconcile.ts +148 -0
  55. package/src/automation/manager-runtime-scheduling.ts +189 -0
  56. package/src/automation/manager-runtime-sync.ts +54 -0
  57. package/src/automation/manager-runtime.ts +721 -0
  58. package/src/automation/manager.ts +10 -0
  59. package/src/automation/service.ts +242 -0
  60. package/src/channels/builtin/account-actions.ts +490 -0
  61. package/src/channels/builtin/accounts.ts +433 -0
  62. package/src/channels/builtin/contracts.ts +405 -0
  63. package/src/channels/builtin/plugins.ts +308 -0
  64. package/src/channels/builtin/rendering.ts +174 -0
  65. package/src/channels/builtin/setup-schema.ts +504 -0
  66. package/src/channels/builtin/shared.ts +96 -0
  67. package/src/channels/builtin/surfaces.ts +57 -0
  68. package/src/channels/builtin/targets.ts +693 -0
  69. package/src/channels/builtin-runtime.ts +443 -0
  70. package/src/channels/delivery/shared.ts +199 -0
  71. package/src/channels/delivery/strategies-bridge.ts +246 -0
  72. package/src/channels/delivery/strategies-core.ts +299 -0
  73. package/src/channels/delivery/strategies-enterprise.ts +178 -0
  74. package/src/channels/delivery/types.ts +59 -0
  75. package/src/channels/delivery-router.ts +127 -0
  76. package/src/channels/index.ts +77 -0
  77. package/src/channels/plugin-registry.ts +551 -0
  78. package/src/channels/provider-runtime.ts +330 -0
  79. package/src/channels/reply-pipeline.ts +522 -0
  80. package/src/channels/route-manager.ts +340 -0
  81. package/src/channels/surface-registry.ts +186 -0
  82. package/src/config/helper-model.ts +233 -0
  83. package/src/config/index.ts +193 -0
  84. package/src/config/manager.ts +404 -0
  85. package/src/config/secrets.ts +547 -0
  86. package/src/config/service-registry.ts +329 -0
  87. package/src/config/subscription-auth.ts +31 -0
  88. package/src/config/subscription-providers.ts +127 -0
  89. package/src/config/tool-llm.ts +110 -0
  90. package/src/control-plane/approval-broker.ts +351 -0
  91. package/src/control-plane/gateway.ts +713 -0
  92. package/src/control-plane/index.ts +54 -0
  93. package/src/control-plane/media-contract-schemas.ts +208 -0
  94. package/src/control-plane/method-catalog-admin.ts +136 -0
  95. package/src/control-plane/method-catalog-channels.ts +591 -0
  96. package/src/control-plane/method-catalog-control-automation.ts +475 -0
  97. package/src/control-plane/method-catalog-control-core.ts +594 -0
  98. package/src/control-plane/method-catalog-control.ts +8 -0
  99. package/src/control-plane/method-catalog-events.ts +74 -0
  100. package/src/control-plane/method-catalog-knowledge.ts +531 -0
  101. package/src/control-plane/method-catalog-media.ts +279 -0
  102. package/src/control-plane/method-catalog-runtime.ts +304 -0
  103. package/src/control-plane/method-catalog-shared.ts +223 -0
  104. package/src/control-plane/method-catalog.ts +242 -0
  105. package/src/control-plane/operator-contract-schemas-admin.ts +639 -0
  106. package/src/control-plane/operator-contract-schemas-channels.ts +375 -0
  107. package/src/control-plane/operator-contract-schemas-control.ts +226 -0
  108. package/src/control-plane/operator-contract-schemas-domains.ts +4 -0
  109. package/src/control-plane/operator-contract-schemas-knowledge.ts +582 -0
  110. package/src/control-plane/operator-contract-schemas-media.ts +297 -0
  111. package/src/control-plane/operator-contract-schemas-permissions.ts +100 -0
  112. package/src/control-plane/operator-contract-schemas-remote.ts +38 -0
  113. package/src/control-plane/operator-contract-schemas-runtime.ts +563 -0
  114. package/src/control-plane/operator-contract-schemas-shared.ts +85 -0
  115. package/src/control-plane/operator-contract-schemas-telemetry.ts +349 -0
  116. package/src/control-plane/operator-contract-schemas.ts +6 -0
  117. package/src/control-plane/operator-contract.ts +163 -0
  118. package/src/control-plane/session-broker.ts +780 -0
  119. package/src/core/compaction-sections.ts +492 -0
  120. package/src/core/compaction-types.ts +147 -0
  121. package/src/core/composer-state.ts +59 -0
  122. package/src/core/context-compaction.ts +542 -0
  123. package/src/core/conversation-compaction.ts +68 -0
  124. package/src/core/conversation-diff.ts +55 -0
  125. package/src/core/conversation-rendering.ts +343 -0
  126. package/src/core/conversation-utils.ts +72 -0
  127. package/src/core/conversation.ts +775 -0
  128. package/src/core/event-replay.ts +287 -0
  129. package/src/core/orchestrator-context-runtime.ts +407 -0
  130. package/src/core/orchestrator-follow-up-runtime.ts +134 -0
  131. package/src/core/orchestrator-runtime.ts +132 -0
  132. package/src/core/orchestrator-tool-runtime.ts +468 -0
  133. package/src/core/orchestrator-turn-helpers.ts +355 -0
  134. package/src/core/orchestrator-turn-loop.ts +443 -0
  135. package/src/core/orchestrator.ts +733 -0
  136. package/src/core/plan-command-handler.ts +169 -0
  137. package/src/core/system-message-router.ts +210 -0
  138. package/src/core/transcript-events/classify.ts +95 -0
  139. package/src/core/transcript-events/index.ts +15 -0
  140. package/src/daemon/cli.ts +88 -0
  141. package/src/daemon/control-plane.ts +522 -0
  142. package/src/daemon/facade-composition.ts +397 -0
  143. package/src/daemon/facade.ts +638 -0
  144. package/src/daemon/helpers.ts +74 -0
  145. package/src/daemon/http/router-route-contexts.ts +370 -0
  146. package/src/daemon/http/router.ts +531 -0
  147. package/src/daemon/http-listener.ts +301 -0
  148. package/src/daemon/index.ts +3 -0
  149. package/src/daemon/server.ts +1 -0
  150. package/src/daemon/service-manager.ts +413 -0
  151. package/src/daemon/surface-actions.ts +183 -0
  152. package/src/daemon/surface-delivery.ts +530 -0
  153. package/src/daemon/surface-policy.ts +60 -0
  154. package/src/daemon/transport-events.ts +110 -0
  155. package/src/daemon/types.ts +191 -0
  156. package/src/export/markdown.ts +213 -0
  157. package/src/export/session-export.ts +633 -0
  158. package/src/git/index.ts +1 -0
  159. package/src/git/service.ts +414 -0
  160. package/src/hooks/chain-engine.ts +414 -0
  161. package/src/hooks/dispatcher.ts +414 -0
  162. package/src/hooks/hook-api.ts +170 -0
  163. package/src/hooks/index.ts +48 -0
  164. package/src/hooks/runners/agent.ts +93 -0
  165. package/src/hooks/runners/prompt.ts +69 -0
  166. package/src/hooks/workbench.ts +360 -0
  167. package/src/input/autocomplete.ts +96 -0
  168. package/src/input/bookmark-modal.ts +115 -0
  169. package/src/input/command-registry.ts +300 -0
  170. package/src/input/commands/branch-runtime.ts +72 -0
  171. package/src/input/commands/config.ts +515 -0
  172. package/src/input/commands/control-room-runtime.ts +255 -0
  173. package/src/input/commands/conversation-runtime.ts +207 -0
  174. package/src/input/commands/diff-runtime.ts +161 -0
  175. package/src/input/commands/discovery-runtime.ts +45 -0
  176. package/src/input/commands/eval.ts +204 -0
  177. package/src/input/commands/experience-runtime.ts +278 -0
  178. package/src/input/commands/git-runtime.ts +81 -0
  179. package/src/input/commands/guidance-runtime.ts +101 -0
  180. package/src/input/commands/health-runtime.ts +434 -0
  181. package/src/input/commands/hooks-runtime.ts +148 -0
  182. package/src/input/commands/incident-runtime.ts +95 -0
  183. package/src/input/commands/integration-runtime.ts +394 -0
  184. package/src/input/commands/intelligence-runtime.ts +223 -0
  185. package/src/input/commands/knowledge.ts +368 -0
  186. package/src/input/commands/local-auth-runtime.ts +105 -0
  187. package/src/input/commands/local-provider-runtime.ts +170 -0
  188. package/src/input/commands/local-runtime.ts +458 -0
  189. package/src/input/commands/local-setup-review.ts +192 -0
  190. package/src/input/commands/local-setup-transfer.ts +134 -0
  191. package/src/input/commands/local-setup.ts +292 -0
  192. package/src/input/commands/managed-runtime.ts +208 -0
  193. package/src/input/commands/marketplace-runtime.ts +290 -0
  194. package/src/input/commands/mcp-runtime.ts +202 -0
  195. package/src/input/commands/memory-product-runtime.ts +111 -0
  196. package/src/input/commands/memory.ts +151 -0
  197. package/src/input/commands/notify-runtime.ts +83 -0
  198. package/src/input/commands/operator-panel-runtime.ts +141 -0
  199. package/src/input/commands/operator-runtime.ts +392 -0
  200. package/src/input/commands/permissions-runtime.ts +104 -0
  201. package/src/input/commands/planning-runtime.ts +97 -0
  202. package/src/input/commands/platform-access-runtime.ts +422 -0
  203. package/src/input/commands/platform-runtime.ts +6 -0
  204. package/src/input/commands/platform-sandbox-qemu.ts +137 -0
  205. package/src/input/commands/platform-sandbox-runtime.ts +406 -0
  206. package/src/input/commands/platform-sandbox-session.ts +128 -0
  207. package/src/input/commands/platform-services-runtime.ts +246 -0
  208. package/src/input/commands/policy-dispatch.ts +339 -0
  209. package/src/input/commands/policy.ts +17 -0
  210. package/src/input/commands/product-runtime.ts +351 -0
  211. package/src/input/commands/profile-sync-runtime.ts +99 -0
  212. package/src/input/commands/provider-accounts-runtime.ts +113 -0
  213. package/src/input/commands/provider.ts +363 -0
  214. package/src/input/commands/quit-shared.ts +162 -0
  215. package/src/input/commands/recall-bundle.ts +132 -0
  216. package/src/input/commands/recall-capture.ts +152 -0
  217. package/src/input/commands/recall-query.ts +229 -0
  218. package/src/input/commands/recall-review.ts +98 -0
  219. package/src/input/commands/recall-shared.ts +22 -0
  220. package/src/input/commands/remote-runtime-pool.ts +106 -0
  221. package/src/input/commands/remote-runtime-setup.ts +199 -0
  222. package/src/input/commands/remote-runtime.ts +531 -0
  223. package/src/input/commands/replay-runtime.ts +18 -0
  224. package/src/input/commands/runtime-services.ts +279 -0
  225. package/src/input/commands/schedule-runtime.ts +332 -0
  226. package/src/input/commands/services-runtime.ts +207 -0
  227. package/src/input/commands/session-content.ts +408 -0
  228. package/src/input/commands/session-workflow.ts +464 -0
  229. package/src/input/commands/session.ts +376 -0
  230. package/src/input/commands/settings-sync-runtime.ts +173 -0
  231. package/src/input/commands/share-runtime.ts +114 -0
  232. package/src/input/commands/shell-core.ts +320 -0
  233. package/src/input/commands/skills-runtime.ts +221 -0
  234. package/src/input/commands/subscription-runtime.ts +434 -0
  235. package/src/input/commands/tasks-runtime.ts +230 -0
  236. package/src/input/commands/teamwork-runtime.ts +374 -0
  237. package/src/input/commands/teleport-runtime.ts +57 -0
  238. package/src/input/commands/worktree-runtime.ts +137 -0
  239. package/src/input/commands.ts +127 -0
  240. package/src/input/file-picker.ts +192 -0
  241. package/src/input/handler-command-route.ts +106 -0
  242. package/src/input/handler-content-actions.ts +465 -0
  243. package/src/input/handler-feed-routes.ts +541 -0
  244. package/src/input/handler-feed.ts +361 -0
  245. package/src/input/handler-modal-routes.ts +335 -0
  246. package/src/input/handler-modal-stack.ts +237 -0
  247. package/src/input/handler-modal-token-routes.ts +272 -0
  248. package/src/input/handler-picker-routes.ts +416 -0
  249. package/src/input/handler-prompt-buffer.ts +320 -0
  250. package/src/input/handler-shortcuts.ts +195 -0
  251. package/src/input/handler-ui-state.ts +294 -0
  252. package/src/input/handler.ts +798 -0
  253. package/src/input/input-history.ts +267 -0
  254. package/src/input/keybindings.ts +256 -0
  255. package/src/input/model-picker.ts +730 -0
  256. package/src/input/panel-integration-actions.ts +77 -0
  257. package/src/input/profile-picker-modal.ts +222 -0
  258. package/src/input/search.ts +100 -0
  259. package/src/input/selection-modal.ts +163 -0
  260. package/src/input/selection.ts +135 -0
  261. package/src/input/session-picker-modal.ts +136 -0
  262. package/src/input/settings-modal.ts +718 -0
  263. package/src/input/submission-intent.ts +18 -0
  264. package/src/input/submission-router.ts +64 -0
  265. package/src/integrations/index.ts +42 -0
  266. package/src/integrations/notifier.ts +206 -0
  267. package/src/integrations/webhooks.ts +177 -0
  268. package/src/knowledge/consolidation.ts +346 -0
  269. package/src/knowledge/graphql.ts +324 -0
  270. package/src/knowledge/index.ts +60 -0
  271. package/src/knowledge/ingest-compile.ts +386 -0
  272. package/src/knowledge/ingest-context.ts +18 -0
  273. package/src/knowledge/ingest-inputs.ts +387 -0
  274. package/src/knowledge/ingest.ts +20 -0
  275. package/src/knowledge/internal.ts +257 -0
  276. package/src/knowledge/knowledge-api.ts +432 -0
  277. package/src/knowledge/lint.ts +121 -0
  278. package/src/knowledge/memory-sync.ts +62 -0
  279. package/src/knowledge/packet.ts +370 -0
  280. package/src/knowledge/scheduling.ts +283 -0
  281. package/src/knowledge/service.ts +715 -0
  282. package/src/main.ts +798 -0
  283. package/src/mcp/client.ts +383 -0
  284. package/src/mcp/index.ts +12 -0
  285. package/src/mcp/mcp-api.ts +90 -0
  286. package/src/mcp/registry.ts +508 -0
  287. package/src/media/builtin-image-understanding.ts +303 -0
  288. package/src/media/builtin-providers.ts +26 -0
  289. package/src/media/index.ts +18 -0
  290. package/src/multimodal/index.ts +13 -0
  291. package/src/multimodal/service.ts +492 -0
  292. package/src/panels/agent-inspector-panel.ts +515 -0
  293. package/src/panels/agent-inspector-shared.ts +94 -0
  294. package/src/panels/agent-logs-panel.ts +539 -0
  295. package/src/panels/agent-logs-shared.ts +129 -0
  296. package/src/panels/approval-panel.ts +169 -0
  297. package/src/panels/automation-control-panel.ts +253 -0
  298. package/src/panels/base-panel.ts +72 -0
  299. package/src/panels/builtin/agent.ts +88 -0
  300. package/src/panels/builtin/development.ts +111 -0
  301. package/src/panels/builtin/knowledge.ts +26 -0
  302. package/src/panels/builtin/operations.ts +385 -0
  303. package/src/panels/builtin/session.ts +61 -0
  304. package/src/panels/builtin/shared.ts +240 -0
  305. package/src/panels/builtin-panels.ts +23 -0
  306. package/src/panels/cockpit-panel.ts +183 -0
  307. package/src/panels/communication-panel.ts +191 -0
  308. package/src/panels/context-visualizer-panel.ts +199 -0
  309. package/src/panels/control-plane-panel.ts +266 -0
  310. package/src/panels/cost-tracker-panel.ts +444 -0
  311. package/src/panels/debug-panel.ts +432 -0
  312. package/src/panels/diff-panel.ts +518 -0
  313. package/src/panels/docs-panel.ts +283 -0
  314. package/src/panels/eval-panel.ts +399 -0
  315. package/src/panels/file-explorer-panel.ts +556 -0
  316. package/src/panels/file-preview-panel.ts +412 -0
  317. package/src/panels/forensics-panel.ts +364 -0
  318. package/src/panels/git-panel.ts +630 -0
  319. package/src/panels/hooks-panel.ts +274 -0
  320. package/src/panels/incident-review-panel.ts +247 -0
  321. package/src/panels/index.ts +48 -0
  322. package/src/panels/intelligence-panel.ts +176 -0
  323. package/src/panels/knowledge-panel.ts +328 -0
  324. package/src/panels/local-auth-panel.ts +146 -0
  325. package/src/panels/marketplace-panel.ts +223 -0
  326. package/src/panels/mcp-panel.ts +260 -0
  327. package/src/panels/memory-panel.ts +293 -0
  328. package/src/panels/ops-control-panel.ts +184 -0
  329. package/src/panels/ops-strategy-panel.ts +235 -0
  330. package/src/panels/orchestration-panel.ts +254 -0
  331. package/src/panels/panel-list-panel.ts +508 -0
  332. package/src/panels/panel-manager.ts +538 -0
  333. package/src/panels/panel-picker.ts +106 -0
  334. package/src/panels/plan-dashboard-panel.ts +272 -0
  335. package/src/panels/plugins-panel.ts +201 -0
  336. package/src/panels/policy-panel.ts +308 -0
  337. package/src/panels/polish.ts +668 -0
  338. package/src/panels/provider-account-snapshot.ts +259 -0
  339. package/src/panels/provider-accounts-panel.ts +221 -0
  340. package/src/panels/provider-health-domains.ts +211 -0
  341. package/src/panels/provider-health-panel.ts +725 -0
  342. package/src/panels/provider-health-tracker.ts +115 -0
  343. package/src/panels/provider-stats-panel.ts +366 -0
  344. package/src/panels/remote-panel.ts +449 -0
  345. package/src/panels/routes-panel.ts +228 -0
  346. package/src/panels/sandbox-panel.ts +289 -0
  347. package/src/panels/schedule-panel.ts +344 -0
  348. package/src/panels/search-focus.ts +32 -0
  349. package/src/panels/security-panel.ts +329 -0
  350. package/src/panels/services-panel.ts +271 -0
  351. package/src/panels/session-browser-panel.ts +399 -0
  352. package/src/panels/session-maintenance.ts +125 -0
  353. package/src/panels/settings-sync-panel.ts +164 -0
  354. package/src/panels/skills-panel.ts +475 -0
  355. package/src/panels/subscription-panel.ts +273 -0
  356. package/src/panels/symbol-outline-panel.ts +486 -0
  357. package/src/panels/system-messages-panel.ts +224 -0
  358. package/src/panels/tasks-panel.ts +448 -0
  359. package/src/panels/thinking-panel.ts +304 -0
  360. package/src/panels/token-budget-panel.ts +469 -0
  361. package/src/panels/tool-inspector-panel.ts +434 -0
  362. package/src/panels/types.ts +44 -0
  363. package/src/panels/watchers-panel.ts +241 -0
  364. package/src/panels/welcome-panel.ts +64 -0
  365. package/src/panels/worktree-panel.ts +180 -0
  366. package/src/panels/wrfc-panel.ts +480 -0
  367. package/src/permissions/briefs/build.ts +88 -0
  368. package/src/permissions/manager.ts +356 -0
  369. package/src/permissions/prompt.ts +184 -0
  370. package/src/plugins/api.ts +383 -0
  371. package/src/plugins/loader.ts +304 -0
  372. package/src/plugins/manager.ts +481 -0
  373. package/src/profiles/shape.ts +58 -0
  374. package/src/providers/amazon-bedrock-mantle.ts +50 -0
  375. package/src/providers/amazon-bedrock.ts +61 -0
  376. package/src/providers/anthropic-compat.ts +373 -0
  377. package/src/providers/anthropic-sdk-provider.ts +230 -0
  378. package/src/providers/anthropic-vertex.ts +59 -0
  379. package/src/providers/anthropic.ts +469 -0
  380. package/src/providers/auto-register.ts +417 -0
  381. package/src/providers/builtin-catalog.ts +326 -0
  382. package/src/providers/builtin-registry.ts +575 -0
  383. package/src/providers/cache-planner.ts +258 -0
  384. package/src/providers/capabilities.ts +601 -0
  385. package/src/providers/custom-loader.ts +425 -0
  386. package/src/providers/discovered-compat.ts +18 -0
  387. package/src/providers/discovered-factory.ts +61 -0
  388. package/src/providers/discovered-traits.ts +138 -0
  389. package/src/providers/gemini.ts +462 -0
  390. package/src/providers/github-copilot.ts +254 -0
  391. package/src/providers/index.ts +47 -0
  392. package/src/providers/interface.ts +185 -0
  393. package/src/providers/llama-cpp.ts +402 -0
  394. package/src/providers/lm-studio-helpers.ts +367 -0
  395. package/src/providers/lm-studio.ts +484 -0
  396. package/src/providers/model-catalog-cache.ts +221 -0
  397. package/src/providers/model-catalog-notifications.ts +97 -0
  398. package/src/providers/model-catalog-synthetic.ts +202 -0
  399. package/src/providers/model-catalog.ts +211 -0
  400. package/src/providers/model-limits.ts +280 -0
  401. package/src/providers/ollama.ts +469 -0
  402. package/src/providers/openai-codex.ts +472 -0
  403. package/src/providers/openai-compat.ts +615 -0
  404. package/src/providers/openai.ts +231 -0
  405. package/src/providers/optimizer.ts +381 -0
  406. package/src/providers/provider-api.ts +553 -0
  407. package/src/providers/registry-helpers.ts +34 -0
  408. package/src/providers/registry-models.ts +77 -0
  409. package/src/providers/registry-types.ts +67 -0
  410. package/src/providers/registry.ts +729 -0
  411. package/src/providers/runtime-metadata.ts +149 -0
  412. package/src/providers/runtime-snapshot.ts +130 -0
  413. package/src/providers/synthetic.ts +561 -0
  414. package/src/providers/tier-prompts.ts +84 -0
  415. package/src/providers/tool-formats.ts +414 -0
  416. package/src/renderer/agent-detail-modal.ts +285 -0
  417. package/src/renderer/autocomplete-overlay.ts +154 -0
  418. package/src/renderer/block-actions.ts +76 -0
  419. package/src/renderer/bookmark-modal.ts +101 -0
  420. package/src/renderer/bottom-bar.ts +58 -0
  421. package/src/renderer/buffer.ts +34 -0
  422. package/src/renderer/code-block.ts +373 -0
  423. package/src/renderer/compositor.ts +261 -0
  424. package/src/renderer/context-inspector.ts +219 -0
  425. package/src/renderer/conversation-layout.ts +67 -0
  426. package/src/renderer/conversation-overlays.ts +123 -0
  427. package/src/renderer/conversation-surface.ts +260 -0
  428. package/src/renderer/diff-view.ts +132 -0
  429. package/src/renderer/diff.ts +122 -0
  430. package/src/renderer/file-picker-overlay.ts +101 -0
  431. package/src/renderer/file-tree.ts +153 -0
  432. package/src/renderer/git-status.ts +89 -0
  433. package/src/renderer/help-overlay.ts +247 -0
  434. package/src/renderer/history-search-overlay.ts +73 -0
  435. package/src/renderer/layout-engine.ts +97 -0
  436. package/src/renderer/layout.ts +32 -0
  437. package/src/renderer/live-tail-modal.ts +156 -0
  438. package/src/renderer/markdown.ts +777 -0
  439. package/src/renderer/modal-factory.ts +467 -0
  440. package/src/renderer/modal-utils.ts +24 -0
  441. package/src/renderer/model-picker-overlay.ts +396 -0
  442. package/src/renderer/overlay-box.ts +165 -0
  443. package/src/renderer/overlay-viewport.ts +104 -0
  444. package/src/renderer/panel-composite.ts +80 -0
  445. package/src/renderer/panel-picker-overlay.ts +202 -0
  446. package/src/renderer/panel-tab-bar.ts +69 -0
  447. package/src/renderer/panel-workspace-bar.ts +38 -0
  448. package/src/renderer/process-indicator.ts +96 -0
  449. package/src/renderer/process-modal.ts +295 -0
  450. package/src/renderer/profile-picker-modal.ts +129 -0
  451. package/src/renderer/progress.ts +98 -0
  452. package/src/renderer/search-overlay.ts +54 -0
  453. package/src/renderer/selection-modal-overlay.ts +214 -0
  454. package/src/renderer/semantic-diff.ts +369 -0
  455. package/src/renderer/session-picker-modal.ts +127 -0
  456. package/src/renderer/settings-modal.ts +701 -0
  457. package/src/renderer/shell-surface.ts +88 -0
  458. package/src/renderer/surface-layout.ts +101 -0
  459. package/src/renderer/syntax-highlighter.ts +542 -0
  460. package/src/renderer/system-message.ts +83 -0
  461. package/src/renderer/tab-strip.ts +108 -0
  462. package/src/renderer/text-layout.ts +31 -0
  463. package/src/renderer/thinking.ts +17 -0
  464. package/src/renderer/tool-call.ts +233 -0
  465. package/src/renderer/ui-factory.ts +524 -0
  466. package/src/renderer/ui-primitives.ts +96 -0
  467. package/src/runtime/auth/inspection.ts +125 -0
  468. package/src/runtime/bootstrap-background.ts +147 -0
  469. package/src/runtime/bootstrap-command-context.ts +265 -0
  470. package/src/runtime/bootstrap-command-parts.ts +357 -0
  471. package/src/runtime/bootstrap-core.ts +375 -0
  472. package/src/runtime/bootstrap-helpers.ts +88 -0
  473. package/src/runtime/bootstrap-hook-bridge.ts +271 -0
  474. package/src/runtime/bootstrap-runtime-events.ts +254 -0
  475. package/src/runtime/bootstrap-services.ts +197 -0
  476. package/src/runtime/bootstrap-shell.ts +262 -0
  477. package/src/runtime/bootstrap.ts +488 -0
  478. package/src/runtime/compaction/index.ts +90 -0
  479. package/src/runtime/compaction/lifecycle.ts +167 -0
  480. package/src/runtime/compaction/manager.ts +474 -0
  481. package/src/runtime/compaction/quality-score.ts +279 -0
  482. package/src/runtime/compaction/resume-repair.ts +183 -0
  483. package/src/runtime/compaction/strategies/autocompact.ts +65 -0
  484. package/src/runtime/compaction/strategies/boundary-commit.ts +106 -0
  485. package/src/runtime/compaction/strategies/collapse.ts +90 -0
  486. package/src/runtime/compaction/strategies/index.ts +23 -0
  487. package/src/runtime/compaction/strategies/microcompact.ts +74 -0
  488. package/src/runtime/compaction/strategies/reactive.ts +89 -0
  489. package/src/runtime/compaction/types.ts +221 -0
  490. package/src/runtime/context.ts +158 -0
  491. package/src/runtime/diagnostics/actions.ts +776 -0
  492. package/src/runtime/diagnostics/index.ts +99 -0
  493. package/src/runtime/diagnostics/panels/agents.ts +252 -0
  494. package/src/runtime/diagnostics/panels/events.ts +188 -0
  495. package/src/runtime/diagnostics/panels/health.ts +242 -0
  496. package/src/runtime/diagnostics/panels/index.ts +24 -0
  497. package/src/runtime/diagnostics/panels/ops.ts +156 -0
  498. package/src/runtime/diagnostics/panels/policy.ts +176 -0
  499. package/src/runtime/diagnostics/panels/tasks.ts +251 -0
  500. package/src/runtime/diagnostics/panels/tool-calls.ts +267 -0
  501. package/src/runtime/diagnostics/provider.ts +262 -0
  502. package/src/runtime/ecosystem/catalog.ts +606 -0
  503. package/src/runtime/ecosystem/recommendations.ts +117 -0
  504. package/src/runtime/emitters/agents.ts +96 -0
  505. package/src/runtime/emitters/automation.ts +112 -0
  506. package/src/runtime/emitters/communication.ts +53 -0
  507. package/src/runtime/emitters/compaction.ts +161 -0
  508. package/src/runtime/emitters/control-plane.ts +65 -0
  509. package/src/runtime/emitters/deliveries.ts +65 -0
  510. package/src/runtime/emitters/forensics.ts +17 -0
  511. package/src/runtime/emitters/index.ts +59 -0
  512. package/src/runtime/emitters/knowledge.ts +129 -0
  513. package/src/runtime/emitters/mcp.ts +95 -0
  514. package/src/runtime/emitters/ops.ts +163 -0
  515. package/src/runtime/emitters/orchestration.ts +87 -0
  516. package/src/runtime/emitters/permissions.ts +98 -0
  517. package/src/runtime/emitters/planner.ts +23 -0
  518. package/src/runtime/emitters/plugins.ts +78 -0
  519. package/src/runtime/emitters/providers.ts +30 -0
  520. package/src/runtime/emitters/routes.ts +57 -0
  521. package/src/runtime/emitters/security.ts +53 -0
  522. package/src/runtime/emitters/session.ts +93 -0
  523. package/src/runtime/emitters/surfaces.ts +57 -0
  524. package/src/runtime/emitters/tasks.ts +69 -0
  525. package/src/runtime/emitters/tools.ts +140 -0
  526. package/src/runtime/emitters/transport.ts +78 -0
  527. package/src/runtime/emitters/turn.ts +155 -0
  528. package/src/runtime/emitters/ui.ts +57 -0
  529. package/src/runtime/emitters/watchers.ts +57 -0
  530. package/src/runtime/emitters/workflows.ts +79 -0
  531. package/src/runtime/eval/index.ts +48 -0
  532. package/src/runtime/eval/runner.ts +163 -0
  533. package/src/runtime/eval/suites.ts +264 -0
  534. package/src/runtime/events/domain-map.ts +148 -0
  535. package/src/runtime/events/index.ts +194 -0
  536. package/src/runtime/events/turn.ts +60 -0
  537. package/src/runtime/events/workflows.ts +17 -0
  538. package/src/runtime/forensics/collector.ts +693 -0
  539. package/src/runtime/forensics/index.ts +23 -0
  540. package/src/runtime/foundation-clients.ts +78 -0
  541. package/src/runtime/foundation-services.ts +96 -0
  542. package/src/runtime/guidance.ts +183 -0
  543. package/src/runtime/health/effect-handlers.ts +189 -0
  544. package/src/runtime/health/index.ts +70 -0
  545. package/src/runtime/health/wiring.ts +115 -0
  546. package/src/runtime/index.ts +174 -0
  547. package/src/runtime/integration/helpers.ts +640 -0
  548. package/src/runtime/lifecycle.ts +107 -0
  549. package/src/runtime/mcp/index.ts +68 -0
  550. package/src/runtime/mcp/manager.ts +513 -0
  551. package/src/runtime/network/inbound.ts +131 -0
  552. package/src/runtime/network/index.ts +30 -0
  553. package/src/runtime/network/outbound.ts +292 -0
  554. package/src/runtime/network/shared.ts +82 -0
  555. package/src/runtime/operator-client.ts +235 -0
  556. package/src/runtime/ops/control-plane.ts +363 -0
  557. package/src/runtime/ops/index.ts +122 -0
  558. package/src/runtime/ops/playbooks/index.ts +10 -0
  559. package/src/runtime/ops/playbooks/session-unrecoverable.ts +196 -0
  560. package/src/runtime/ops/playbooks/stuck-turn.ts +197 -0
  561. package/src/runtime/ops/runtime-context.ts +100 -0
  562. package/src/runtime/ops-api.ts +27 -0
  563. package/src/runtime/orchestration/spawn-policy.ts +83 -0
  564. package/src/runtime/peer-client.ts +404 -0
  565. package/src/runtime/perf/index.ts +57 -0
  566. package/src/runtime/perf/slo-collector.ts +375 -0
  567. package/src/runtime/permissions/index.ts +190 -0
  568. package/src/runtime/permissions/policy-runtime.ts +175 -0
  569. package/src/runtime/permissions/preflight.ts +101 -0
  570. package/src/runtime/permissions/rule-suggestions.ts +36 -0
  571. package/src/runtime/plugins/hot-reload.ts +221 -0
  572. package/src/runtime/plugins/index.ts +84 -0
  573. package/src/runtime/plugins/lifecycle.ts +95 -0
  574. package/src/runtime/plugins/manager.ts +474 -0
  575. package/src/runtime/plugins/manifest.ts +167 -0
  576. package/src/runtime/plugins/quarantine.ts +202 -0
  577. package/src/runtime/plugins/trust.ts +291 -0
  578. package/src/runtime/plugins/types.ts +205 -0
  579. package/src/runtime/provider-accounts/registry.ts +326 -0
  580. package/src/runtime/remote/distributed-runtime-contract-schemas.ts +386 -0
  581. package/src/runtime/remote/index.ts +488 -0
  582. package/src/runtime/remote/runner-registry.ts +438 -0
  583. package/src/runtime/remote/supervisor.ts +70 -0
  584. package/src/runtime/runtime-hook-api.ts +5 -0
  585. package/src/runtime/runtime-knowledge-api.ts +14 -0
  586. package/src/runtime/runtime-mcp-api.ts +5 -0
  587. package/src/runtime/runtime-ops-api.ts +86 -0
  588. package/src/runtime/runtime-provider-api.ts +18 -0
  589. package/src/runtime/sandbox/backend.ts +291 -0
  590. package/src/runtime/sandbox/manager.ts +364 -0
  591. package/src/runtime/sandbox/provisioning.ts +422 -0
  592. package/src/runtime/sandbox/session-registry.ts +289 -0
  593. package/src/runtime/services.ts +541 -0
  594. package/src/runtime/session-maintenance.ts +188 -0
  595. package/src/runtime/session-persistence.ts +288 -0
  596. package/src/runtime/session-return-context.ts +195 -0
  597. package/src/runtime/settings/control-plane-store.ts +258 -0
  598. package/src/runtime/settings/control-plane.ts +599 -0
  599. package/src/runtime/shell-command-extensions.ts +54 -0
  600. package/src/runtime/shell-command-ops.ts +207 -0
  601. package/src/runtime/shell-command-platform.ts +47 -0
  602. package/src/runtime/shell-command-services.ts +143 -0
  603. package/src/runtime/shell-command-workspace.ts +31 -0
  604. package/src/runtime/store/domains/conversation.ts +181 -0
  605. package/src/runtime/store/domains/domain-read-matrix.ts +17 -0
  606. package/src/runtime/store/domains/index.ts +222 -0
  607. package/src/runtime/store/domains/panels.ts +117 -0
  608. package/src/runtime/store/domains/permissions.ts +143 -0
  609. package/src/runtime/store/domains/ui-perf.ts +103 -0
  610. package/src/runtime/store/helpers/reducers/conversation.ts +228 -0
  611. package/src/runtime/store/helpers/reducers/lifecycle.ts +440 -0
  612. package/src/runtime/store/helpers/reducers/shared.ts +60 -0
  613. package/src/runtime/store/helpers/reducers/sync.ts +555 -0
  614. package/src/runtime/store/helpers/reducers.ts +30 -0
  615. package/src/runtime/store/index.ts +304 -0
  616. package/src/runtime/store/selectors/index.ts +354 -0
  617. package/src/runtime/store/state.ts +137 -0
  618. package/src/runtime/tasks/adapters/acp-adapter.ts +211 -0
  619. package/src/runtime/tasks/adapters/agent-adapter.ts +208 -0
  620. package/src/runtime/tasks/adapters/index.ts +16 -0
  621. package/src/runtime/tasks/adapters/process-adapter.ts +214 -0
  622. package/src/runtime/tasks/adapters/scheduler-adapter.ts +193 -0
  623. package/src/runtime/tasks/index.ts +68 -0
  624. package/src/runtime/tasks/manager.ts +415 -0
  625. package/src/runtime/telemetry/api-helpers.ts +517 -0
  626. package/src/runtime/telemetry/api.ts +768 -0
  627. package/src/runtime/telemetry/index.ts +178 -0
  628. package/src/runtime/telemetry/instrumentation/domain-bridge-agent-session.ts +440 -0
  629. package/src/runtime/telemetry/instrumentation/domain-bridge-plugin-mcp.ts +200 -0
  630. package/src/runtime/telemetry/instrumentation/domain-bridge-shared.ts +18 -0
  631. package/src/runtime/telemetry/instrumentation/domain-bridge-transport-task.ts +204 -0
  632. package/src/runtime/telemetry/instrumentation/domain-bridge.ts +125 -0
  633. package/src/runtime/telemetry/instrumentation/index.ts +67 -0
  634. package/src/runtime/tools/context.ts +114 -0
  635. package/src/runtime/tools/index.ts +46 -0
  636. package/src/runtime/tools/phased-executor.ts +448 -0
  637. package/src/runtime/tools/phases/budget.ts +130 -0
  638. package/src/runtime/tools/phases/execute.ts +69 -0
  639. package/src/runtime/tools/phases/index.ts +13 -0
  640. package/src/runtime/tools/phases/map-output.ts +98 -0
  641. package/src/runtime/tools/phases/permission.ts +133 -0
  642. package/src/runtime/tools/phases/posthook.ts +57 -0
  643. package/src/runtime/tools/phases/prehook.ts +68 -0
  644. package/src/runtime/tools/phases/validate.ts +53 -0
  645. package/src/runtime/transports/direct.ts +73 -0
  646. package/src/runtime/transports/http-helpers.ts +218 -0
  647. package/src/runtime/transports/http-types.ts +364 -0
  648. package/src/runtime/transports/http.ts +629 -0
  649. package/src/runtime/transports/realtime.ts +50 -0
  650. package/src/runtime/transports/remote-events.ts +16 -0
  651. package/src/runtime/transports/shared.ts +39 -0
  652. package/src/runtime/transports/ui-runtime-events.ts +35 -0
  653. package/src/runtime/ui/index.ts +39 -0
  654. package/src/runtime/ui/model-picker/data-provider.ts +182 -0
  655. package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
  656. package/src/runtime/ui/model-picker/index.ts +59 -0
  657. package/src/runtime/ui/model-picker/types.ts +149 -0
  658. package/src/runtime/ui/provider-health/data-provider.ts +244 -0
  659. package/src/runtime/ui/provider-health/fallback-visualizer.ts +69 -0
  660. package/src/runtime/ui/provider-health/index.ts +46 -0
  661. package/src/runtime/ui/provider-health/types.ts +146 -0
  662. package/src/runtime/ui-events.ts +46 -0
  663. package/src/runtime/ui-read-model-helpers.ts +32 -0
  664. package/src/runtime/ui-read-models-core.ts +95 -0
  665. package/src/runtime/ui-read-models-observability-maintenance.ts +81 -0
  666. package/src/runtime/ui-read-models-observability-options.ts +5 -0
  667. package/src/runtime/ui-read-models-observability-remote.ts +73 -0
  668. package/src/runtime/ui-read-models-observability-security.ts +172 -0
  669. package/src/runtime/ui-read-models-observability-system.ts +217 -0
  670. package/src/runtime/ui-read-models-observability.ts +59 -0
  671. package/src/runtime/ui-read-models-operations.ts +203 -0
  672. package/src/runtime/ui-read-models.ts +61 -0
  673. package/src/runtime/ui-service-queries.ts +114 -0
  674. package/src/runtime/ui-services.ts +163 -0
  675. package/src/runtime/worktree/registry.ts +252 -0
  676. package/src/scripts/process-messages.ts +42 -0
  677. package/src/sessions/manager.ts +388 -0
  678. package/src/shell/blocking-input.ts +89 -0
  679. package/src/shell/ui-openers.ts +163 -0
  680. package/src/state/file-watcher.ts +294 -0
  681. package/src/state/index.ts +56 -0
  682. package/src/state/knowledge-injection.ts +214 -0
  683. package/src/state/memory-embedding-http.ts +642 -0
  684. package/src/state/memory-embeddings.ts +312 -0
  685. package/src/state/memory-ingest.ts +132 -0
  686. package/src/state/memory-registry.ts +111 -0
  687. package/src/state/memory-store-helpers.ts +160 -0
  688. package/src/state/memory-store.ts +728 -0
  689. package/src/state/memory-vector-store.ts +418 -0
  690. package/src/templates/manager.ts +187 -0
  691. package/src/tools/agent/index.ts +610 -0
  692. package/src/tools/agent/manager.ts +476 -0
  693. package/src/tools/analyze/git-modes.ts +380 -0
  694. package/src/tools/analyze/index.ts +128 -0
  695. package/src/tools/channel/agent-tools.ts +16 -0
  696. package/src/tools/channel/index.ts +268 -0
  697. package/src/tools/control/index.ts +90 -0
  698. package/src/tools/edit/core.ts +619 -0
  699. package/src/tools/edit/index.ts +4 -0
  700. package/src/tools/edit/phased.ts +33 -0
  701. package/src/tools/fetch/index.ts +3 -0
  702. package/src/tools/fetch/phased.ts +34 -0
  703. package/src/tools/fetch/runtime.ts +499 -0
  704. package/src/tools/index.ts +186 -0
  705. package/src/tools/mcp/index.ts +190 -0
  706. package/src/tools/remote-trigger/index.ts +130 -0
  707. package/src/tools/repl/index.ts +318 -0
  708. package/src/tools/shared/auto-heal.ts +282 -0
  709. package/src/tools/state/index.ts +688 -0
  710. package/src/tools/web-search/index.ts +38 -0
  711. package/src/tools/write/index.ts +604 -0
  712. package/src/tools/write/phased.ts +41 -0
  713. package/src/types/generated/foundation-client-types.ts +22 -0
  714. package/src/types/sql-js.d.ts +15 -0
  715. package/src/utils/splash-lines.ts +46 -0
  716. package/src/version.ts +17 -0
  717. package/src/watchers/index.ts +11 -0
  718. package/src/watchers/registry.ts +517 -0
  719. package/src/web-search/index.ts +26 -0
  720. package/src/web-search/provider-registry.ts +64 -0
  721. package/src/web-search/providers/brave.ts +100 -0
  722. package/src/web-search/providers/duckduckgo.ts +270 -0
  723. package/src/web-search/providers/exa.ts +77 -0
  724. package/src/web-search/providers/firecrawl.ts +90 -0
  725. package/src/web-search/providers/perplexity.ts +86 -0
  726. package/src/web-search/providers/searxng.ts +88 -0
  727. package/src/web-search/providers/shared.ts +249 -0
  728. package/src/web-search/providers/tavily.ts +90 -0
  729. package/src/web-search/service.ts +142 -0
  730. package/src/widget/index.ts +2 -0
  731. package/src/widget/types.ts +9 -0
  732. package/src/widget/widget.ts +8 -0
@@ -0,0 +1,729 @@
1
+ import type { LLMProvider, ProviderRuntimeMetadata, ProviderRuntimeMetadataDeps } from './interface.ts';
2
+ import { join } from 'node:path';
3
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
4
+ import {
5
+ ProviderCapabilityRegistry,
6
+ type ProviderCapability,
7
+ type RequestProfile,
8
+ type RouteExplanation,
9
+ } from './capabilities.ts';
10
+ import type { DiscoveredServer } from '@pellux/goodvibes-sdk/platform/discovery/scanner';
11
+ import { createDiscoveredProvider, getDiscoveredReasoningFormat } from './discovered-factory.ts';
12
+ import { getDiscoveredTraits } from './discovered-traits.ts';
13
+ import { getConfiguredApiKeys, getConfiguredModelId, getConfiguredProviderId } from '../config/index.ts';
14
+ import type { RuntimeEventBus } from '../runtime/events/index.ts';
15
+ import { emitProvidersChanged, emitProviderWarning } from '../runtime/emitters/index.ts';
16
+ import { loadCustomProviders, watchCustomProviders } from './custom-loader.ts';
17
+ import {
18
+ buildSyntheticCanonicalModels,
19
+ fetchCatalog,
20
+ getCatalogCachePath,
21
+ getCatalogModelDefinitionsFrom,
22
+ getCatalogTmpPath,
23
+ getCostFromPricingCatalog,
24
+ getSyntheticBackendModelIds,
25
+ getSyntheticModelDefinitions,
26
+ getSyntheticModelInfo,
27
+ isCatalogCacheStale,
28
+ loadCatalogCache,
29
+ notifyCatalogChanges,
30
+ saveCatalogCache,
31
+ type CatalogModel,
32
+ type CatalogProvider,
33
+ type MinimalModelDefinition,
34
+ type PricingCatalog,
35
+ } from './model-catalog.ts';
36
+ import { registerBuiltinProviders, CATALOG_PROVIDER_NAME_ALIASES } from './builtin-registry.ts';
37
+ import type { CacheHitTracker } from '@pellux/goodvibes-sdk/platform/providers/cache-strategy';
38
+ import type { ConfigManager } from '../config/manager.ts';
39
+ import type { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config/subscriptions';
40
+ import type { FeatureFlagManager } from '@pellux/goodvibes-sdk/platform/runtime/feature-flags/index';
41
+ import type { FavoritesStore } from '@pellux/goodvibes-sdk/platform/providers/favorites';
42
+ import type { BenchmarkStore } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
43
+ import type { CanonicalModel } from './synthetic.ts';
44
+ import { LocalContextIngestionService } from '@pellux/goodvibes-sdk/platform/providers/local-context-ingestion';
45
+ import { getModelLimitsCachePath, ModelLimitsService } from './model-limits.ts';
46
+ import { getGitHubCopilotTokenCachePath } from './github-copilot.ts';
47
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
48
+ import {
49
+ getBaseModelId,
50
+ splitModelRegistryKey,
51
+ stableStringify,
52
+ withRegistryKey,
53
+ } from './registry-helpers.ts';
54
+ import {
55
+ buildModelRegistry,
56
+ findModelDefinition,
57
+ findModelDefinitionForProvider,
58
+ } from './registry-models.ts';
59
+ import type {
60
+ ModelDefinition,
61
+ ProviderRegistryOptions,
62
+ RuntimeProviderRegistration,
63
+ TokenLimits,
64
+ } from './registry-types.ts';
65
+ export type {
66
+ ContextWindowProvenance,
67
+ ModelDefinition,
68
+ ModelTier,
69
+ ProviderRegistryOptions,
70
+ RuntimeProviderRegistration,
71
+ TokenLimits,
72
+ } from './registry-types.ts';
73
+
74
+ /**
75
+ * ProviderRegistry — manages LLM provider instances and model selection.
76
+ * Lazily instantiates providers on first use.
77
+ */
78
+ export class ProviderRegistry {
79
+ private providers: Map<string, LLMProvider> = new Map();
80
+ private currentModelId: string;
81
+ private discoveredProviderNames: Set<string> = new Set();
82
+ private runtimeProviderNames: Set<string> = new Set();
83
+ private customModels: ModelDefinition[] = [];
84
+ private runtimeModels: ModelDefinition[] = [];
85
+ private discoveredModels: ModelDefinition[] = [];
86
+ private readonly runtimeCatalogSuppressions = new Map<string, readonly string[]>();
87
+ private _watcher: { close: () => void } | undefined;
88
+ private _readyPromise: Promise<void> | null = null;
89
+ private readonly configManager: Pick<ConfigManager, 'get' | 'getCategory' | 'getControlPlaneConfigDir'>;
90
+ private readonly subscriptionManager: Pick<SubscriptionManager, 'get' | 'getPending' | 'saveSubscription' | 'resolveAccessToken'>;
91
+ private readonly capabilityRegistry: ProviderCapabilityRegistry;
92
+ private readonly cacheHitTracker: CacheHitTracker;
93
+ private readonly featureFlags: Pick<FeatureFlagManager, 'isEnabled'> | null;
94
+ private readonly favoritesStore: Pick<FavoritesStore, 'load'>;
95
+ private readonly benchmarkStore: Pick<BenchmarkStore, 'getBenchmarks' | 'getTopBenchmarkModelIds'>;
96
+ private readonly modelLimitsService: ModelLimitsService;
97
+ private readonly runtimeMetadataDeps: ProviderRuntimeMetadataDeps;
98
+ private readonly runtimeBus: RuntimeEventBus | null;
99
+ private readonly localContextIngestionService = new LocalContextIngestionService();
100
+ private catalogModels: CatalogModel[] = [];
101
+ private pricingCatalog: PricingCatalog | null = null;
102
+ private syntheticCanonicalModels: CanonicalModel[] = [];
103
+
104
+ constructor(options: ProviderRegistryOptions) {
105
+ this.configManager = options.configManager;
106
+ this.subscriptionManager = options.subscriptionManager;
107
+ this.capabilityRegistry = options.capabilityRegistry;
108
+ this.cacheHitTracker = options.cacheHitTracker;
109
+ this.favoritesStore = options.favoritesStore;
110
+ this.benchmarkStore = options.benchmarkStore;
111
+ this.modelLimitsService = options.modelLimitsService ?? new ModelLimitsService({
112
+ cachePath: getModelLimitsCachePath(this.getPersistenceRoot()),
113
+ });
114
+ this.runtimeMetadataDeps = {
115
+ secretsManager: options.secretsManager,
116
+ serviceRegistry: options.serviceRegistry,
117
+ subscriptionManager: options.subscriptionManager,
118
+ };
119
+ this.featureFlags = options.featureFlags ?? null;
120
+ this.runtimeBus = options.runtimeBus ?? null;
121
+ this.currentModelId = getConfiguredModelId(this.configManager) || 'openrouter/free';
122
+ this.registerBuiltins();
123
+ }
124
+
125
+ private registerBuiltins(): void {
126
+ const apiKey = (name: string): string => getConfiguredApiKeys()[name] ?? '';
127
+ registerBuiltinProviders(
128
+ this,
129
+ (name) => this.providers.has(name),
130
+ apiKey,
131
+ {
132
+ cacheHitTracker: this.cacheHitTracker,
133
+ resolveProvider: (providerName) => this.get(providerName),
134
+ getCatalogModels: () => this.syntheticCanonicalModels,
135
+ getBenchmarks: (modelId) => this.benchmarkStore.getBenchmarks(modelId),
136
+ githubCopilotTokenCachePath: getGitHubCopilotTokenCachePath(this.getPersistenceRoot()),
137
+ subscriptionManager: this.subscriptionManager,
138
+ runtimeBus: this.runtimeBus,
139
+ },
140
+ );
141
+ }
142
+
143
+ private getPersistenceRoot(): string {
144
+ return this.configManager.getControlPlaneConfigDir();
145
+ }
146
+
147
+ private getCustomProvidersDir(): string {
148
+ return join(this.getPersistenceRoot(), 'providers');
149
+ }
150
+
151
+ private getCatalogCachePaths(): { cachePath: string; tmpPath: string } {
152
+ const cachePath = getCatalogCachePath(this.getPersistenceRoot());
153
+ return {
154
+ cachePath,
155
+ tmpPath: getCatalogTmpPath(this.getPersistenceRoot()),
156
+ };
157
+ }
158
+
159
+ private getCatalogBuiltins(): ModelDefinition[] {
160
+ return getCatalogModelDefinitionsFrom(this.catalogModels) as ModelDefinition[];
161
+ }
162
+
163
+ private getSyntheticBuiltins(): ModelDefinition[] {
164
+ return getSyntheticModelDefinitions(this.catalogModels, this.syntheticCanonicalModels) as ModelDefinition[];
165
+ }
166
+
167
+ private updateCatalogState(models: readonly CatalogModel[]): void {
168
+ this.catalogModels = [...models];
169
+ this.pricingCatalog = { fetchedAt: Date.now(), models: this.catalogModels };
170
+ this.syntheticCanonicalModels = buildSyntheticCanonicalModels(this.catalogModels);
171
+ }
172
+
173
+ private getSuppressedCatalogModelIds(): Set<string> {
174
+ return new Set([...this.runtimeCatalogSuppressions.values()].flat());
175
+ }
176
+
177
+ private getModelRegistry(): ModelDefinition[] {
178
+ return buildModelRegistry({
179
+ customModels: this.customModels,
180
+ runtimeModels: this.runtimeModels,
181
+ syntheticModels: this.getSyntheticBuiltins(),
182
+ catalogModels: this.getCatalogBuiltins(),
183
+ discoveredModels: this.discoveredModels,
184
+ suppressedCatalogIds: this.getSuppressedCatalogModelIds(),
185
+ });
186
+ }
187
+
188
+ /** Register a provider. Overwrites any existing entry with the same name. */
189
+ register(provider: LLMProvider): void {
190
+ this.providers.set(provider.name, provider);
191
+ }
192
+
193
+ /**
194
+ * Register a runtime/plugin-owned provider and optional model definitions.
195
+ * Returns an unregister callback so plugin cleanup can remove the provider and
196
+ * its runtime model/catalog contributions.
197
+ */
198
+ registerRuntimeProvider(registration: RuntimeProviderRegistration): () => void {
199
+ const { provider, models = [], suppressCatalogModels = [], replace = false } = registration;
200
+ if (this.providers.has(provider.name) && !this.runtimeProviderNames.has(provider.name) && !replace) {
201
+ throw new Error(`Provider '${provider.name}' is already registered.`);
202
+ }
203
+ this.providers.set(provider.name, provider);
204
+ this.runtimeProviderNames.add(provider.name);
205
+ this.runtimeModels = [
206
+ ...this.runtimeModels.filter((model) => model.provider !== provider.name),
207
+ ...models.map((model) => withRegistryKey({
208
+ ...model,
209
+ provider: provider.name,
210
+ registryKey: model.registryKey || `${provider.name}:${model.id}`,
211
+ })),
212
+ ];
213
+ this.runtimeCatalogSuppressions.set(provider.name, [...new Set(suppressCatalogModels)]);
214
+ this.capabilityRegistry.invalidate();
215
+ return () => {
216
+ if (!this.runtimeProviderNames.has(provider.name)) return;
217
+ this.providers.delete(provider.name);
218
+ this.runtimeProviderNames.delete(provider.name);
219
+ this.runtimeModels = this.runtimeModels.filter((model) => model.provider !== provider.name);
220
+ this.runtimeCatalogSuppressions.delete(provider.name);
221
+ this.capabilityRegistry.invalidate();
222
+ };
223
+ }
224
+
225
+ listProviders(): readonly LLMProvider[] {
226
+ return [...this.providers.values()].sort((a, b) => a.name.localeCompare(b.name));
227
+ }
228
+
229
+ /**
230
+ * Register providers discovered by the local LLM scanner.
231
+ * Clears previously discovered providers before re-registering.
232
+ * Does not overwrite built-in or custom-loaded providers/models.
233
+ */
234
+ registerDiscoveredProviders(servers: DiscoveredServer[]): void {
235
+ // Unregister previously discovered providers
236
+ for (const name of this.discoveredProviderNames) {
237
+ this.providers.delete(name);
238
+ }
239
+ this.discoveredProviderNames.clear();
240
+ this.discoveredModels = [];
241
+
242
+ for (const server of servers) {
243
+ // Skip if a non-discovered provider already holds this name
244
+ if (this.providers.has(server.name)) continue;
245
+ // Skip servers with no models — defaultModel would be undefined
246
+ if (server.models.length === 0) continue;
247
+
248
+ const reasoningFormat = getDiscoveredReasoningFormat(server.serverType);
249
+ const traits = getDiscoveredTraits(server.serverType);
250
+
251
+ const provider = createDiscoveredProvider(server);
252
+
253
+ this.providers.set(server.name, provider);
254
+ this.discoveredProviderNames.add(server.name);
255
+
256
+ for (const modelId of server.models) {
257
+ this.discoveredModels.push({
258
+ id: modelId,
259
+ provider: server.name,
260
+ registryKey: `${server.name}:${modelId}`,
261
+ displayName: modelId,
262
+ description: `Discovered local model on ${server.baseURL}`,
263
+ capabilities: traits.modelCapabilities,
264
+ ...(traits.reasoningEffort ? { reasoningEffort: traits.reasoningEffort } : {}),
265
+ contextWindow: server.modelContextWindows?.[modelId] ?? 8192,
266
+ ...(server.modelContextWindows?.[modelId] != null
267
+ ? { contextWindowProvenance: 'provider_api' as const }
268
+ : {}),
269
+ ...(server.modelOutputLimits?.[modelId] != null
270
+ ? { tokenLimits: { maxOutputTokens: server.modelOutputLimits[modelId] } }
271
+ : {}),
272
+ selectable: true,
273
+ tier: 'standard',
274
+ });
275
+ }
276
+ }
277
+ }
278
+
279
+ /** Retrieve a provider by name. Throws if not found. */
280
+ get(name: string): LLMProvider {
281
+ if (name === 'openai' && this.subscriptionManager.get('openai')) {
282
+ const subscriber = this.providers.get('openai-subscriber');
283
+ if (subscriber) return subscriber;
284
+ }
285
+ return this.getRegistered(name);
286
+ }
287
+
288
+ /** Retrieve the directly-registered provider without subscription route aliasing. */
289
+ getRegistered(name: string): LLMProvider {
290
+ const p = this.providers.get(name);
291
+ if (p) return p;
292
+ // Check alias map — catalog may use a different name than the registered provider
293
+ const aliased = CATALOG_PROVIDER_NAME_ALIASES[name];
294
+ if (aliased) {
295
+ const pa = this.providers.get(aliased);
296
+ if (pa) return pa;
297
+ }
298
+ throw new Error(`Provider '${name}' is not registered.`);
299
+ }
300
+
301
+ async describeRuntime(name: string): Promise<ProviderRuntimeMetadata | null> {
302
+ const provider = this.getRegistered(name);
303
+ if (!provider.describeRuntime) return null;
304
+ return await provider.describeRuntime(this.runtimeMetadataDeps);
305
+ }
306
+
307
+ /** Return the provider responsible for a given model ID.
308
+ * Accepts a registryKey (`provider:modelId`) OR a plain modelId.
309
+ * - If input contains `:`, treats as registryKey — exact match on `m.registryKey`
310
+ * - If no `:`, treats as plain modelId — exact match on `m.id`
311
+ * When `provider` is supplied alongside a plain modelId, it disambiguates.
312
+ * Explicit provider constraints do not fall through to other providers.
313
+ */
314
+ getForModel(modelId: string, provider?: string): LLMProvider {
315
+ const registry = this.getModelRegistry();
316
+ const def = provider
317
+ ? findModelDefinitionForProvider(modelId, provider, registry, CATALOG_PROVIDER_NAME_ALIASES)
318
+ : findModelDefinition(modelId, registry);
319
+ if (!def) {
320
+ if (provider) throw new Error(`No model '${modelId}' for provider '${provider}' in registry.`);
321
+ throw new Error(`No model '${modelId}' in registry.`);
322
+ }
323
+ return this.get(def.provider);
324
+ }
325
+
326
+ /** All registered model definitions. */
327
+ listModels(): ModelDefinition[] {
328
+ return this.getModelRegistry();
329
+ }
330
+
331
+ getCostFromCatalog(modelId: string): { input: number; output: number } {
332
+ return getCostFromPricingCatalog(
333
+ modelId,
334
+ this.pricingCatalog ?? { fetchedAt: Date.now(), models: this.catalogModels },
335
+ this.modelLimitsService,
336
+ );
337
+ }
338
+
339
+ getContextWindowForModel(modelDef: ModelDefinition): number {
340
+ return this.modelLimitsService.getContextWindowForModel(modelDef);
341
+ }
342
+
343
+ getTokenLimitsForModel(modelDef: ModelDefinition): Required<TokenLimits> {
344
+ return this.modelLimitsService.getTokenLimitsForModel(modelDef);
345
+ }
346
+
347
+ getPricingForModel(modelId: string, provider: string): { prompt: number; completion: number } | null {
348
+ return this.modelLimitsService.getPricingForModel(modelId, provider);
349
+ }
350
+
351
+ getToolResultMaxCharsForModel(model: ModelDefinition | null | undefined): number {
352
+ return this.modelLimitsService.getToolResultMaxCharsForModel(model);
353
+ }
354
+
355
+ initModelLimits(): void {
356
+ this.modelLimitsService.init();
357
+ }
358
+
359
+ refreshModelLimits(): Promise<number> {
360
+ return this.modelLimitsService.refresh();
361
+ }
362
+
363
+ getRawCatalogModels(): readonly CatalogModel[] {
364
+ return [...this.catalogModels];
365
+ }
366
+
367
+ getCatalogModelDefinitions(): readonly MinimalModelDefinition[] {
368
+ return getCatalogModelDefinitionsFrom(this.catalogModels);
369
+ }
370
+
371
+ getSyntheticModelDefinitions(): readonly MinimalModelDefinition[] {
372
+ return getSyntheticModelDefinitions(this.catalogModels, this.syntheticCanonicalModels);
373
+ }
374
+
375
+ getSyntheticCanonicalModels(): readonly CanonicalModel[] {
376
+ return [...this.syntheticCanonicalModels];
377
+ }
378
+
379
+ getSyntheticBackendModelIds(): Set<string> {
380
+ return getSyntheticBackendModelIds(this.syntheticCanonicalModels);
381
+ }
382
+
383
+ getSyntheticModelInfoFromCatalog(modelId: string) {
384
+ return getSyntheticModelInfo(
385
+ modelId,
386
+ this.syntheticCanonicalModels,
387
+ (candidateId) => this.benchmarkStore.getBenchmarks(candidateId),
388
+ );
389
+ }
390
+
391
+ getConfiguredProviderIds(): string[] {
392
+ const configured = new Set<string>();
393
+ const providerEnvMap = new Map<string, string[]>();
394
+
395
+ for (const model of this.catalogModels) {
396
+ if (!providerEnvMap.has(model.providerId)) {
397
+ providerEnvMap.set(model.providerId, model.providerEnvVars);
398
+ }
399
+ }
400
+
401
+ for (const [providerId, envVars] of providerEnvMap) {
402
+ if (envVars.length === 0) {
403
+ configured.add(providerId);
404
+ } else if (envVars.some((envVar) => {
405
+ const value = process.env[envVar];
406
+ return typeof value === 'string' && value.length > 0;
407
+ })) {
408
+ configured.add(providerId);
409
+ }
410
+ }
411
+
412
+ try {
413
+ const configApiKeys = getConfiguredApiKeys();
414
+ const configToCatalog: Record<string, string> = { gemini: 'google', inceptionlabs: 'inception' };
415
+ for (const [configName, key] of Object.entries(configApiKeys)) {
416
+ if (key) {
417
+ configured.add(configToCatalog[configName] ?? configName);
418
+ }
419
+ }
420
+ } catch {
421
+ // non-fatal
422
+ }
423
+
424
+ if (this.getSyntheticBackendModelIds().size > 0) {
425
+ configured.add('synthetic');
426
+ }
427
+
428
+ return [...configured];
429
+ }
430
+
431
+ /** Only the models the user can switch to. */
432
+ getSelectableModels(): ModelDefinition[] {
433
+ return this.getModelRegistry().filter((m) => m.selectable);
434
+ }
435
+
436
+ /** Currently active model definition. */
437
+ getCurrentModel(): ModelDefinition {
438
+ const registry = this.getModelRegistry();
439
+ const def = findModelDefinition(this.currentModelId, registry);
440
+ if (!def) {
441
+ const baseId = getBaseModelId(this.currentModelId);
442
+ const isInCatalog = this.getCatalogBuiltins().some((m) => m.id === baseId || m.id === this.currentModelId);
443
+ if (!isInCatalog && this.currentModelId) {
444
+ const placeholderProvider = this.currentModelId.includes(':')
445
+ ? this.currentModelId.split(':')[0]
446
+ : (getConfiguredProviderId(this.configManager) || 'unknown');
447
+ return {
448
+ id: baseId,
449
+ provider: placeholderProvider ?? 'unknown',
450
+ registryKey: this.currentModelId.includes(':') ? this.currentModelId : `${placeholderProvider}:${baseId}`,
451
+ displayName: baseId,
452
+ description: 'Waiting for provider discovery...',
453
+ capabilities: { toolCalling: false, codeEditing: false, reasoning: false, multimodal: false },
454
+ contextWindow: 0, // Unknown until provider discovery completes; 0 = no progress bar
455
+ selectable: true,
456
+ tier: 'standard',
457
+ };
458
+ }
459
+ // Builtin model not found — genuinely broken, fall back to first selectable
460
+ const fallback = this.getModelRegistry().find((m) => m.selectable);
461
+ if (fallback) {
462
+ this.currentModelId = fallback.id;
463
+ return fallback;
464
+ }
465
+ throw new Error(`Current model '${this.currentModelId}' not in registry.`);
466
+ }
467
+ return def;
468
+ }
469
+
470
+ /**
471
+ * Override the context window cap for a custom or discovered (local) model.
472
+ * Mutates the live model entry in customModels or discoveredModels so the change
473
+ * is reflected immediately without requiring a full provider reload.
474
+ *
475
+ * @param registryKey - The model's registryKey (`provider:id`).
476
+ * @param cap - New context window value in tokens.
477
+ * @remarks Context cap is session-only. Not persisted to config — lost on restart.
478
+ */
479
+ setModelContextCap(registryKey: string, cap: number): void {
480
+ // Try customModels first, then discoveredModels
481
+ const customIdx = this.customModels.findIndex((m) => m.registryKey === registryKey || m.id === registryKey);
482
+ if (customIdx >= 0) {
483
+ this.customModels[customIdx] = {
484
+ ...this.customModels[customIdx]!,
485
+ contextWindow: cap,
486
+ contextWindowProvenance: 'configured_cap',
487
+ };
488
+ return;
489
+ }
490
+ const discoveredIdx = this.discoveredModels.findIndex((m) => m.registryKey === registryKey || m.id === registryKey);
491
+ if (discoveredIdx >= 0) {
492
+ this.discoveredModels[discoveredIdx] = {
493
+ ...this.discoveredModels[discoveredIdx]!,
494
+ contextWindow: cap,
495
+ contextWindowProvenance: 'configured_cap',
496
+ };
497
+ return;
498
+ }
499
+ logger.warn('[registry] setModelContextCap: model not found', { registryKey });
500
+ }
501
+
502
+ /** Switch to a different model. Accepts registryKey or plain modelId. Throws if not selectable. */
503
+ setCurrentModel(modelId: string): void {
504
+ const def = findModelDefinition(modelId, this.getModelRegistry());
505
+ if (!def) throw new Error(`Model '${modelId}' not found.`);
506
+ if (!def.selectable) throw new Error(`Model '${modelId}' is not selectable.`);
507
+ // Store the registryKey for unambiguous future lookups
508
+ this.currentModelId = def.registryKey ?? modelId;
509
+ }
510
+
511
+ /**
512
+ * Load custom providers from ~/.goodvibes/tui/providers/ and merge them
513
+ * into the live model registry. Returns any warnings collected during loading.
514
+ * Call this after construction to populate custom providers.
515
+ */
516
+ async loadCustomProviders(): Promise<{ warnings: string[]; added: string[]; removed: string[]; updated: string[] }> {
517
+ const result = await loadCustomProviders({
518
+ providersDir: this.getCustomProvidersDir(),
519
+ ingestContextWindows: this.featureFlags?.isEnabled('local-provider-context-ingestion') ?? false,
520
+ contextIngestion: this.localContextIngestionService,
521
+ });
522
+ const previousIds = new Set(this.customModels.map((m) => m.id));
523
+ const newIds = new Set(result.models.map((m) => m.id));
524
+
525
+ const added: string[] = [];
526
+ const removed: string[] = [];
527
+ const updated: string[] = [];
528
+
529
+ for (const id of newIds) {
530
+ if (!previousIds.has(id)) {
531
+ added.push(id);
532
+ } else {
533
+ // Only mark as updated if the model definition actually changed
534
+ const oldModel = this.customModels.find((m) => m.id === id);
535
+ const newModel = result.models.find((m) => m.id === id);
536
+ if (stableStringify(oldModel) !== stableStringify(newModel)) {
537
+ updated.push(id);
538
+ }
539
+ }
540
+ }
541
+ for (const id of previousIds) {
542
+ if (!newIds.has(id)) removed.push(id);
543
+ }
544
+
545
+ // Warn about collisions with catalog models
546
+ const catalogIds = new Set(this.getCatalogBuiltins().map((b) => b.id));
547
+ for (const model of result.models) {
548
+ if (catalogIds.has(model.id)) {
549
+ const msg = `[registry] Custom model '${model.id}' from provider '${model.provider}' overrides catalog model.`;
550
+ result.warnings.push(msg);
551
+ // Warning already added to result.warnings — don't console.warn (corrupts TUI)
552
+ }
553
+ }
554
+
555
+ // Register provider instances
556
+ for (const { provider } of result.providers) {
557
+ this.register(provider);
558
+ }
559
+
560
+ // Swap custom models
561
+ this.customModels = result.models;
562
+
563
+ return { warnings: result.warnings, added, removed, updated };
564
+ }
565
+
566
+ /**
567
+ * Start watching ~/.goodvibes/tui/providers/ for file changes.
568
+ * On change, reloads custom providers and emits typed provider runtime events.
569
+ * Safe to call multiple times — stops the previous watcher first.
570
+ */
571
+ startWatching(runtimeBus: RuntimeEventBus | null = null): void {
572
+ this.stopWatching();
573
+ this._watcher = watchCustomProviders(runtimeBus, async () => {
574
+ const result = await this.loadCustomProviders();
575
+ for (const msg of result.warnings) {
576
+ if (runtimeBus) {
577
+ emitProviderWarning(runtimeBus, {
578
+ sessionId: 'system',
579
+ traceId: `providers:warning:${Date.now()}`,
580
+ source: 'provider-registry',
581
+ }, { message: msg });
582
+ }
583
+ }
584
+ if (runtimeBus) {
585
+ emitProvidersChanged(runtimeBus, {
586
+ sessionId: 'system',
587
+ traceId: `providers:changed:${Date.now()}`,
588
+ source: 'provider-registry',
589
+ }, {
590
+ added: result.added,
591
+ removed: result.removed,
592
+ updated: result.updated,
593
+ });
594
+ }
595
+ }, this.getCustomProvidersDir());
596
+ }
597
+
598
+ /** Stop the file watcher started by startWatching(). */
599
+ stopWatching(): void {
600
+ if (this._watcher) {
601
+ this._watcher.close();
602
+ this._watcher = undefined;
603
+ }
604
+ }
605
+
606
+ /**
607
+ * Returns a promise that resolves when the initial custom provider load
608
+ * completes. Callers can await this before calling getForModel() with a
609
+ * custom model ID to avoid a "model not found" race window.
610
+ */
611
+ ready(): Promise<void> {
612
+ return this._readyPromise ?? Promise.resolve();
613
+ }
614
+
615
+ /**
616
+ * Find an alternative model when the current provider fails non-transiently.
617
+ * Prefers a synthetic failover wrapper; falls back to same-tier model on a different provider.
618
+ */
619
+ findAlternativeModel(currentModelId: string): ModelDefinition | null {
620
+ const current = findModelDefinition(currentModelId, this.getModelRegistry());
621
+ if (!current || current.provider === 'synthetic') return null;
622
+ // Check if synthetic wrapper exists
623
+ const baseName = current.id.split('/').pop() ?? '';
624
+ const syntheticMatch = this.getModelRegistry().find((model) => model.provider === 'synthetic' && (model.id === baseName || model.id.endsWith('/' + baseName)));
625
+ if (syntheticMatch) return syntheticMatch;
626
+ // Find same-tier model on different provider
627
+ return this.getModelRegistry().find((model) => model.id !== currentModelId && model.provider !== current.provider && model.tier === current.tier && model.selectable) ?? null;
628
+ }
629
+
630
+ /**
631
+ * Resolve the full capability record for a model.
632
+ * Accepts a plain model ID or a `provider:modelId` registryKey.
633
+ *
634
+ * @param modelId - Plain model ID or registryKey (`provider:modelId`).
635
+ * @returns A fully-resolved, immutable `ProviderCapability`.
636
+ */
637
+ getCapabilityForModel(modelId: string): ProviderCapability {
638
+ const { providerId, resolvedModelId, provider } = this._resolveModelContext(modelId);
639
+ return this.capabilityRegistry.getCapability(providerId, resolvedModelId, provider);
640
+ }
641
+
642
+ /**
643
+ * Check whether a model can handle a request described by `profile`.
644
+ * Fails early with a typed explanation when unsupported — avoids mid-stream errors.
645
+ *
646
+ * @param modelId - Plain model ID or registryKey.
647
+ * @param profile - The capability requirements for this request.
648
+ * @returns A `RouteExplanation` with `accepted` flag, rejections, and capability.
649
+ */
650
+ explainRoute(modelId: string, profile: RequestProfile): RouteExplanation {
651
+ const { providerId, resolvedModelId, provider } = this._resolveModelContext(modelId);
652
+ return this.capabilityRegistry.getRouteExplanation(providerId, resolvedModelId, profile, provider);
653
+ }
654
+
655
+ /**
656
+ * Resolve the provider identity and instance for a plain model ID or registryKey.
657
+ * Shared by `getCapabilityForModel` and `explainRoute` to avoid duplication.
658
+ *
659
+ * @param modelId - Plain model ID or registryKey (`provider:modelId`).
660
+ */
661
+ private _resolveModelContext(modelId: string): {
662
+ providerId: string;
663
+ resolvedModelId: string;
664
+ provider: LLMProvider | undefined;
665
+ } {
666
+ const registry = this.getModelRegistry();
667
+ const def = findModelDefinition(modelId, registry);
668
+ const { providerId: fallbackProviderId, resolvedModelId: fallbackModelId } = splitModelRegistryKey(modelId);
669
+ const providerId = def?.provider ?? fallbackProviderId;
670
+ const resolvedModelId = def?.id ?? fallbackModelId;
671
+ let provider: LLMProvider | undefined;
672
+ try {
673
+ provider = this.get(providerId);
674
+ } catch {
675
+ // Provider not registered yet — proceed without self-declared capabilities
676
+ }
677
+ return { providerId, resolvedModelId, provider };
678
+ }
679
+
680
+ /** Kick off async custom provider loading. Called once from bootstrap-owned construction. */
681
+ initCustomProviders(): void {
682
+ this._readyPromise = this.loadCustomProviders()
683
+ .then((result) => {
684
+ // Warnings captured in result.warnings — don't console.warn (corrupts TUI)
685
+ // Invalidate the capability cache so custom providers' self-declared
686
+ // capabilities are reflected in subsequent getCapabilityForModel calls.
687
+ this.capabilityRegistry.invalidate();
688
+ this._readyPromise = null;
689
+ })
690
+ .catch((err) => {
691
+ // Non-fatal — don't console.warn (corrupts TUI display)
692
+ this._readyPromise = null;
693
+ });
694
+ }
695
+
696
+ initCatalog(): void {
697
+ const cached = loadCatalogCache(this.getCatalogCachePaths().cachePath);
698
+ if (cached) {
699
+ this.catalogModels = [...cached.models];
700
+ this.pricingCatalog = { fetchedAt: cached.fetchedAt, models: this.catalogModels };
701
+ this.syntheticCanonicalModels = buildSyntheticCanonicalModels(this.catalogModels);
702
+ }
703
+ if (!cached || isCatalogCacheStale(cached)) {
704
+ void this.refreshCatalog().catch((err) => {
705
+ logger.debug('[model-catalog] Background refresh failed', { error: summarizeError(err) });
706
+ });
707
+ }
708
+ }
709
+
710
+ async refreshCatalog(): Promise<void> {
711
+ const previous = [...this.catalogModels];
712
+ const models = await fetchCatalog();
713
+ if (models.length === 0) {
714
+ logger.warn('[model-catalog] Refresh returned 0 models — keeping existing catalog');
715
+ return;
716
+ }
717
+ const { cachePath, tmpPath } = this.getCatalogCachePaths();
718
+ saveCatalogCache(models, cachePath, tmpPath);
719
+ this.updateCatalogState(models);
720
+ const favorites = await this.favoritesStore.load();
721
+ notifyCatalogChanges(
722
+ previous,
723
+ this.catalogModels,
724
+ favorites,
725
+ this.benchmarkStore.getTopBenchmarkModelIds(10),
726
+ );
727
+ logger.debug('[model-catalog] Catalog updated', { count: models.length });
728
+ }
729
+ }