gloomberb 0.9.9 → 0.10.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 (519) hide show
  1. package/README.md +41 -3
  2. package/README.zh-CN.md +294 -0
  3. package/package.json +10 -4
  4. package/patches/@opentui%2Fcore@0.3.2.patch +26 -69
  5. package/src/api-client/auth.ts +2 -0
  6. package/src/api-client/index.test.ts +4 -0
  7. package/src/api-client/types.ts +3 -0
  8. package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -0
  9. package/src/app/pane-template-dialog-wizard.test.tsx +36 -0
  10. package/src/app/pane-template-dialog-wizard.tsx +10 -3
  11. package/src/app/runtime/desktop-deeplink.test.ts +15 -0
  12. package/src/app/runtime/desktop-deeplink.ts +21 -6
  13. package/src/app.tsx +40 -33
  14. package/src/capabilities/service-capabilities.ts +7 -2
  15. package/src/cli/commands/automation.test.ts +202 -0
  16. package/src/cli/commands/automation.ts +153 -36
  17. package/src/cli/commands/market.ts +1 -14
  18. package/src/cli/commands/system.ts +4 -1
  19. package/src/cli/context.ts +1 -1
  20. package/src/cli/desktop-pane-shot.ts +65 -4
  21. package/src/cli/entry.ts +1 -1
  22. package/src/cli/pane-functions/capabilities.ts +532 -0
  23. package/src/cli/pane-functions/catalog.ts +39 -4
  24. package/src/cli/pane-functions/data.test.ts +17 -0
  25. package/src/cli/pane-functions/data.ts +55 -13
  26. package/src/cli/pane-functions/discovery.ts +36 -9
  27. package/src/cli/pane-functions/index.test.ts +104 -1
  28. package/src/cli/pane-functions/index.ts +64 -5
  29. package/src/cli/pane-functions/options.ts +21 -6
  30. package/src/cli/pane-functions/report.ts +735 -72
  31. package/src/cli/pane-functions/resolver.ts +70 -6
  32. package/src/cli/pane-functions/screenshot.test.ts +431 -0
  33. package/src/cli/pane-functions/screenshot.ts +952 -8
  34. package/src/components/chart/composite/column-layout.ts +215 -0
  35. package/src/components/chart/composite/composite-chart.test.tsx +630 -0
  36. package/src/components/chart/composite/composite-chart.tsx +1148 -0
  37. package/src/components/chart/composite/format.test.ts +137 -0
  38. package/src/components/chart/composite/format.ts +135 -0
  39. package/src/components/chart/composite/index.ts +38 -0
  40. package/src/components/chart/composite/interactions.test.ts +109 -0
  41. package/src/components/chart/composite/interactions.ts +247 -0
  42. package/src/components/chart/composite/price-series.test.ts +39 -0
  43. package/src/components/chart/composite/price-series.ts +78 -0
  44. package/src/components/chart/composite/rasterizer.ts +359 -0
  45. package/src/components/chart/composite/renderers.test.ts +610 -0
  46. package/src/components/chart/composite/scene.test.ts +464 -0
  47. package/src/components/chart/composite/scene.ts +553 -0
  48. package/src/components/chart/composite/text-renderer.ts +285 -0
  49. package/src/components/chart/composite/time-scale.test.ts +88 -0
  50. package/src/components/chart/composite/time-scale.ts +217 -0
  51. package/src/components/chart/composite/types.ts +141 -0
  52. package/src/components/chart/core/data.ts +81 -5
  53. package/src/components/chart/core/index.test.ts +128 -43
  54. package/src/components/chart/core/resolution.ts +0 -6
  55. package/src/components/chart/native/chart-rasterizer.ts +1 -3
  56. package/src/components/chart/native/kitty/index.test.ts +99 -120
  57. package/src/components/chart/native/kitty/manager.ts +3 -0
  58. package/src/components/chart/native/kitty/protocol.ts +4 -1
  59. package/src/components/chart/native/raster/crosshair.ts +79 -46
  60. package/src/components/chart/native/raster/types.ts +2 -11
  61. package/src/components/chart/native/surface/manager.ts +48 -3
  62. package/src/components/chart/price-axis-labels.tsx +18 -4
  63. package/src/components/command-bar/commands/direct/index.ts +25 -0
  64. package/src/components/command-bar/commands/registry.ts +9 -0
  65. package/src/components/command-bar/helpers.test.ts +4 -1
  66. package/src/components/command-bar/list/view.tsx +18 -6
  67. package/src/components/command-bar/multi-select-picker.tsx +6 -5
  68. package/src/components/command-bar/pane-settings.test.ts +141 -0
  69. package/src/components/command-bar/pane-settings.ts +61 -1
  70. package/src/components/command-bar/panel/body.tsx +7 -6
  71. package/src/components/command-bar/routes/root/command-items.ts +4 -4
  72. package/src/components/command-bar/routes/root/results.ts +10 -1
  73. package/src/components/command-bar/routes/root/selection.test.ts +54 -0
  74. package/src/components/command-bar/routes/root/selection.ts +13 -0
  75. package/src/components/command-bar/routing/actions.ts +18 -2
  76. package/src/components/command-bar/routing/list-state.ts +2 -1
  77. package/src/components/command-bar/surface/index.test.tsx +36 -4
  78. package/src/components/command-bar/surface/pane.test.tsx +93 -0
  79. package/src/components/command-bar/theme-picker.tsx +2 -1
  80. package/src/components/command-bar/view-model.ts +19 -20
  81. package/src/components/command-bar/workflow/body.tsx +2 -1
  82. package/src/components/command-bar/workflow/field-row.tsx +3 -2
  83. package/src/components/command-bar/workflow/fields.ts +4 -2
  84. package/src/components/command-bar/workflow/ops.test.ts +161 -0
  85. package/src/components/command-bar/workflow/ops.ts +20 -50
  86. package/src/components/command-bar/workflow/route-actions.test.ts +34 -0
  87. package/src/components/command-bar/workflow/route-actions.ts +4 -0
  88. package/src/components/command-bar/workflow/types.ts +1 -0
  89. package/src/components/feed-data-table/stack-view.tsx +6 -5
  90. package/src/components/index.ts +13 -1
  91. package/src/components/layout/detached-pane-shell.tsx +2 -1
  92. package/src/components/layout/floating-pane.tsx +3 -0
  93. package/src/components/layout/header.tsx +15 -8
  94. package/src/components/layout/pane/footer/index.test.tsx +22 -3
  95. package/src/components/layout/pane/footer/index.tsx +3 -1
  96. package/src/components/layout/pane/footer/model.ts +9 -2
  97. package/src/components/layout/pane/header.tsx +4 -6
  98. package/src/components/layout/pane/index.tsx +3 -0
  99. package/src/components/layout/pane/sidebar.test.tsx +100 -0
  100. package/src/components/layout/pane/sidebar.tsx +239 -0
  101. package/src/components/layout/pane/title.ts +4 -3
  102. package/src/components/layout/shell/action-menu-overlay.tsx +5 -3
  103. package/src/components/layout/shell/drag/runtime.ts +2 -0
  104. package/src/components/layout/shell/menu.test.ts +19 -0
  105. package/src/components/layout/shell/menu.ts +8 -5
  106. package/src/components/layout/shell/native/pointer-runtime.ts +4 -1
  107. package/src/components/layout/shell/pane/layers.tsx +2 -2
  108. package/src/components/layout/status-bar.tsx +8 -7
  109. package/src/components/layout/window-controls.tsx +4 -3
  110. package/src/components/metric-treemap/index.tsx +3 -2
  111. package/src/components/onboarding/onboarding-steps.tsx +30 -29
  112. package/src/components/onboarding/onboarding-wizard.tsx +5 -4
  113. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +15 -12
  114. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +17 -16
  115. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +6 -5
  116. package/src/components/onboarding/portfolio-step/choose-panel.tsx +5 -4
  117. package/src/components/pane-settings-dialog/desktop.tsx +25 -3
  118. package/src/components/pane-settings-dialog/field-dialogs.tsx +5 -4
  119. package/src/components/pane-settings-dialog/tui.tsx +11 -9
  120. package/src/components/pane-settings-dialog/value.ts +8 -5
  121. package/src/components/pane-settings-dialog.test.tsx +235 -0
  122. package/src/components/pane-settings-dialog.tsx +24 -0
  123. package/src/components/pane-template-wizard.tsx +6 -2
  124. package/src/components/ticker/list-table-view.tsx +3 -2
  125. package/src/components/ui/button.tsx +3 -1
  126. package/src/components/ui/choice-dialog.tsx +6 -1
  127. package/src/components/ui/confirm-dialog.tsx +3 -2
  128. package/src/components/ui/fields.tsx +10 -1
  129. package/src/components/ui/frame.tsx +4 -1
  130. package/src/components/ui/index.ts +5 -0
  131. package/src/components/ui/inline-quick-add.tsx +104 -0
  132. package/src/components/ui/list-view.tsx +5 -4
  133. package/src/components/ui/loading.tsx +2 -1
  134. package/src/components/ui/multi-select/dialog.tsx +188 -44
  135. package/src/components/ui/popover.tsx +10 -0
  136. package/src/components/ui/shortcut-hint.tsx +2 -0
  137. package/src/components/ui/status.tsx +4 -3
  138. package/src/components/ui/tabs.tsx +19 -8
  139. package/src/components/ui/toggle.tsx +44 -3
  140. package/src/components/ui/ui.test.tsx +67 -5
  141. package/src/core/app-services.ts +4 -6
  142. package/src/core/state/session-persistence.ts +3 -2
  143. package/src/data/config/chart-settings.ts +712 -0
  144. package/src/data/config/layout.ts +24 -5
  145. package/src/data/config/store/index.test.ts +377 -14
  146. package/src/data/config/store/normalize.ts +79 -65
  147. package/src/data/fred-series.ts +137 -0
  148. package/src/i18n/index.test.ts +114 -0
  149. package/src/i18n/index.ts +147 -0
  150. package/src/i18n/ja.ts +1013 -0
  151. package/src/i18n/ko.ts +1011 -0
  152. package/src/i18n/languages.test.ts +31 -0
  153. package/src/i18n/languages.ts +72 -0
  154. package/src/i18n/zh-cn.ts +1013 -0
  155. package/src/i18n/zh-tw.ts +1013 -0
  156. package/src/plugins/builtin/account-management/footer.ts +3 -2
  157. package/src/plugins/builtin/account-management/form-components.tsx +3 -2
  158. package/src/plugins/builtin/account-management/keyboard.ts +6 -0
  159. package/src/plugins/builtin/account-management/model.ts +25 -21
  160. package/src/plugins/builtin/account-management/navigation.test.ts +27 -0
  161. package/src/plugins/builtin/account-management/navigation.ts +24 -0
  162. package/src/plugins/builtin/account-management/pane.tsx +120 -85
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +10 -9
  164. package/src/plugins/builtin/ai/agent-history.ts +139 -0
  165. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +133 -51
  166. package/src/plugins/builtin/ai/ask-ai.test.tsx +83 -22
  167. package/src/plugins/builtin/ai/index.test.ts +302 -0
  168. package/src/plugins/builtin/ai/index.tsx +374 -40
  169. package/src/plugins/builtin/ai/pane-settings.test.ts +155 -0
  170. package/src/plugins/builtin/ai/pane-settings.ts +319 -0
  171. package/src/plugins/builtin/ai/pi/credential-store.test.ts +80 -0
  172. package/src/plugins/builtin/ai/pi/credential-store.ts +138 -0
  173. package/src/plugins/builtin/ai/pi/file-store.ts +138 -0
  174. package/src/plugins/builtin/ai/pi/host.test.ts +500 -0
  175. package/src/plugins/builtin/ai/pi/host.ts +564 -0
  176. package/src/plugins/builtin/ai/pi/index.ts +5 -0
  177. package/src/plugins/builtin/ai/pi/models-store.test.ts +44 -0
  178. package/src/plugins/builtin/ai/pi/models-store.ts +141 -0
  179. package/src/plugins/builtin/ai/pi/providers.ts +68 -0
  180. package/src/plugins/builtin/ai/pi/runtime.test.ts +240 -0
  181. package/src/plugins/builtin/ai/pi/runtime.ts +635 -0
  182. package/src/plugins/builtin/ai/providers.test.ts +68 -0
  183. package/src/plugins/builtin/ai/providers.ts +163 -38
  184. package/src/plugins/builtin/ai/runner-selection.test.ts +174 -0
  185. package/src/plugins/builtin/ai/runner-selection.ts +193 -0
  186. package/src/plugins/builtin/ai/runner-selector.tsx +153 -0
  187. package/src/plugins/builtin/ai/runner.test.ts +198 -0
  188. package/src/plugins/builtin/ai/runner.ts +276 -73
  189. package/src/plugins/builtin/ai/screener/contract.test.ts +26 -73
  190. package/src/plugins/builtin/ai/screener/contract.ts +14 -52
  191. package/src/plugins/builtin/ai/screener/editor-runtime.ts +55 -5
  192. package/src/plugins/builtin/ai/screener/editor.tsx +42 -36
  193. package/src/plugins/builtin/ai/screener/footer.ts +82 -67
  194. package/src/plugins/builtin/ai/screener/keyboard.ts +27 -11
  195. package/src/plugins/builtin/ai/screener/model.test.ts +24 -0
  196. package/src/plugins/builtin/ai/screener/model.ts +9 -3
  197. package/src/plugins/builtin/ai/screener/pane.test.tsx +142 -55
  198. package/src/plugins/builtin/ai/screener/pane.tsx +95 -97
  199. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -41
  200. package/src/plugins/builtin/ai/screener/runner.ts +41 -25
  201. package/src/plugins/builtin/ai/screener/tabs-bar.tsx +2 -2
  202. package/src/plugins/builtin/ai/use-runtime-providers.ts +28 -0
  203. package/src/plugins/builtin/ai/workspace/model.test.ts +227 -0
  204. package/src/plugins/builtin/ai/workspace/model.ts +308 -0
  205. package/src/plugins/builtin/ai/workspace/pane.test.tsx +197 -0
  206. package/src/plugins/builtin/ai/workspace/pane.tsx +896 -0
  207. package/src/plugins/builtin/analytics/index.test.tsx +3 -3
  208. package/src/plugins/builtin/analytics/index.tsx +3 -8
  209. package/src/plugins/builtin/broker-manager/detail.tsx +25 -24
  210. package/src/plugins/builtin/broker-manager/index.test.tsx +1 -1
  211. package/src/plugins/builtin/broker-manager/index.tsx +9 -11
  212. package/src/plugins/builtin/broker-manager/model.ts +11 -10
  213. package/src/plugins/builtin/broker-manager/pane-actions.tsx +22 -21
  214. package/src/plugins/builtin/broker-manager/table.ts +9 -8
  215. package/src/plugins/builtin/changelog/index.tsx +3 -8
  216. package/src/plugins/builtin/chart-composer/chart-spec.ts +89 -0
  217. package/src/plugins/builtin/chart-composer/cli-options.ts +98 -0
  218. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +120 -0
  219. package/src/plugins/builtin/chart-composer/editor.test.tsx +332 -0
  220. package/src/plugins/builtin/chart-composer/editor.tsx +1107 -0
  221. package/src/plugins/builtin/chart-composer/index.tsx +228 -0
  222. package/src/plugins/builtin/chart-composer/pane.tsx +645 -0
  223. package/src/plugins/builtin/chart-composer/presets.test.ts +561 -0
  224. package/src/plugins/builtin/chart-composer/presets.ts +759 -0
  225. package/src/plugins/builtin/chart-composer/quick-add.test.tsx +245 -0
  226. package/src/plugins/builtin/chart-composer/quick-add.tsx +409 -0
  227. package/src/plugins/builtin/chart-composer/semantic.ts +86 -0
  228. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +65 -0
  229. package/src/plugins/builtin/chart-composer/series-catalog.ts +295 -0
  230. package/src/plugins/builtin/chart-composer/settings.test.ts +163 -0
  231. package/src/plugins/builtin/chart-composer/settings.ts +397 -0
  232. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +44 -0
  233. package/src/plugins/builtin/chart-composer/shortcuts.ts +35 -0
  234. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +103 -0
  235. package/src/plugins/builtin/chat/channel-labels.ts +5 -4
  236. package/src/plugins/builtin/chat/channels.ts +12 -11
  237. package/src/plugins/builtin/chat/chat.test.tsx +32 -0
  238. package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
  239. package/src/plugins/builtin/chat/content/composer.tsx +9 -8
  240. package/src/plugins/builtin/chat/content/index.tsx +36 -1
  241. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +8 -7
  242. package/src/plugins/builtin/chat/content/transcript.tsx +9 -3
  243. package/src/plugins/builtin/chat/controller/utils.ts +10 -9
  244. package/src/plugins/builtin/chat/message/desktop.tsx +17 -10
  245. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +122 -3
  246. package/src/plugins/builtin/chat/message/profile-popover.tsx +38 -1
  247. package/src/plugins/builtin/chat/message/terminal.tsx +19 -10
  248. package/src/plugins/builtin/chat/pane-state.test.ts +31 -0
  249. package/src/plugins/builtin/chat/pane-state.ts +30 -0
  250. package/src/plugins/builtin/chat/pane.tsx +27 -4
  251. package/src/plugins/builtin/chat/profile-popover.ts +62 -5
  252. package/src/plugins/builtin/chat/sidebar.test.tsx +4 -7
  253. package/src/plugins/builtin/chat/sidebar.tsx +107 -168
  254. package/src/plugins/builtin/chat/test-harness.tsx +6 -0
  255. package/src/plugins/builtin/cloud/auth-actions.tsx +5 -4
  256. package/src/plugins/builtin/cloud/plugin.tsx +150 -127
  257. package/src/plugins/builtin/composite-plugins.ts +71 -0
  258. package/src/plugins/builtin/correlation/index.test.tsx +4 -4
  259. package/src/plugins/builtin/correlation/index.tsx +3 -8
  260. package/src/plugins/builtin/earnings/index.tsx +37 -21
  261. package/src/plugins/builtin/earnings/model.test.ts +52 -0
  262. package/src/plugins/builtin/earnings/model.ts +32 -0
  263. package/src/plugins/builtin/econ/detail-view.tsx +17 -4
  264. package/src/plugins/builtin/econ/fred-cache.test.ts +57 -11
  265. package/src/plugins/builtin/econ/index.tsx +16 -20
  266. package/src/plugins/builtin/fear-greed/index.tsx +2 -8
  267. package/src/plugins/builtin/fx-matrix/index.tsx +3 -8
  268. package/src/plugins/builtin/help/components.tsx +5 -4
  269. package/src/plugins/builtin/help/index.tsx +14 -17
  270. package/src/plugins/builtin/holders/index.tsx +2 -8
  271. package/src/plugins/builtin/insider/index.tsx +2 -7
  272. package/src/plugins/builtin/kelly-sizer/index.test.tsx +2 -2
  273. package/src/plugins/builtin/kelly-sizer/index.tsx +3 -7
  274. package/src/plugins/builtin/kelly-sizer/test-support.tsx +3 -3
  275. package/src/plugins/builtin/layout-manager/index.tsx +3 -7
  276. package/src/plugins/builtin/market-heatmap/index.tsx +3 -8
  277. package/src/plugins/builtin/market-movers/index.tsx +3 -8
  278. package/src/plugins/builtin/news/index.tsx +12 -16
  279. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +3 -3
  280. package/src/plugins/builtin/news/wire/index.tsx +83 -73
  281. package/src/plugins/builtin/options/index.tsx +2 -8
  282. package/src/plugins/builtin/plugin-module.test.ts +89 -0
  283. package/src/plugins/builtin/plugin-module.ts +116 -0
  284. package/src/plugins/builtin/portfolio-list/header.tsx +4 -3
  285. package/src/plugins/builtin/portfolio-list/index.test.ts +17 -0
  286. package/src/plugins/builtin/portfolio-list/index.tsx +3 -5
  287. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +3 -3
  288. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +53 -53
  289. package/src/plugins/builtin/portfolio-list/settings.ts +5 -3
  290. package/src/plugins/builtin/portfolio-list/summary/index.tsx +4 -3
  291. package/src/plugins/builtin/research/index.tsx +2 -8
  292. package/src/plugins/builtin/sec/index.tsx +2 -8
  293. package/src/plugins/builtin/sectors/index.tsx +3 -8
  294. package/src/plugins/builtin/thirteenf/index.tsx +3 -8
  295. package/src/plugins/builtin/thirteenf/pane.test.tsx +1 -1
  296. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +49 -23
  297. package/src/plugins/builtin/ticker-detail/index.test.tsx +15 -136
  298. package/src/plugins/builtin/ticker-detail/index.tsx +2 -93
  299. package/src/plugins/builtin/ticker-detail/overview/components.tsx +6 -5
  300. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +41 -17
  301. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -3
  302. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +1 -1
  303. package/src/plugins/builtin/ticker-detail/settings.ts +3 -50
  304. package/src/plugins/builtin/ticker-research-backend-plugin.ts +17 -0
  305. package/src/plugins/builtin/ticker-research-plugin.ts +27 -0
  306. package/src/plugins/builtin/tv/channels.ts +27 -0
  307. package/src/plugins/builtin/tv/index.tsx +40 -0
  308. package/src/plugins/builtin/tv/pane.tsx +286 -0
  309. package/src/plugins/builtin/tv/youtube-stream.ts +100 -0
  310. package/src/plugins/builtin/world-indices/index.tsx +3 -8
  311. package/src/plugins/builtin/yield-curve/index.tsx +8 -8
  312. package/src/plugins/catalog-backend.test.ts +21 -0
  313. package/src/plugins/catalog-backend.ts +42 -0
  314. package/src/plugins/catalog-ui.ts +6 -14
  315. package/src/plugins/ibkr/instance-selection.test.ts +0 -1
  316. package/src/plugins/ownership.ts +143 -0
  317. package/src/plugins/prediction-markets/backend-plugin.ts +19 -0
  318. package/src/plugins/prediction-markets/chart.test.tsx +7 -5
  319. package/src/plugins/prediction-markets/chart.tsx +25 -7
  320. package/src/plugins/registry/index.test.ts +51 -0
  321. package/src/plugins/registry/index.ts +8 -0
  322. package/src/plugins/registry/pane-settings.ts +1 -1
  323. package/src/plugins/runtime/index.test.tsx +2 -2
  324. package/src/remote/app-host.tsx +16 -3
  325. package/src/remote/controller.test.ts +102 -1
  326. package/src/remote/controller.ts +122 -0
  327. package/src/remote/types.ts +11 -0
  328. package/src/renderers/electrobun/bun/core-capabilities.ts +123 -27
  329. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +5 -1
  330. package/src/renderers/electrobun/bun/desktop/initialization.ts +5 -1
  331. package/src/renderers/electrobun/bun/desktop/media.ts +15 -0
  332. package/src/renderers/electrobun/bun/desktop/window-style.ts +4 -1
  333. package/src/renderers/electrobun/bun/desktop/workspace/index.test.ts +32 -1
  334. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +10 -0
  335. package/src/renderers/electrobun/bun/index.ts +5 -0
  336. package/src/renderers/electrobun/bun/tui-entry.ts +1 -6
  337. package/src/renderers/electrobun/view/ai-host.ts +124 -19
  338. package/src/renderers/electrobun/view/backend-rpc.ts +4 -0
  339. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +151 -39
  340. package/src/renderers/electrobun/view/desktop/popover.tsx +166 -0
  341. package/src/renderers/electrobun/view/dialog-host.test.ts +25 -0
  342. package/src/renderers/electrobun/view/dialog-host.tsx +75 -12
  343. package/src/renderers/electrobun/view/host/box.tsx +5 -0
  344. package/src/renderers/electrobun/view/host/chart-surface.tsx +10 -9
  345. package/src/renderers/electrobun/view/host/input.tsx +9 -19
  346. package/src/renderers/electrobun/view/host/media-surface.tsx +170 -0
  347. package/src/renderers/electrobun/view/host/mouse.ts +1 -0
  348. package/src/renderers/electrobun/view/host/style.ts +1 -1
  349. package/src/renderers/electrobun/view/host/tabs.tsx +4 -3
  350. package/src/renderers/electrobun/view/main.tsx +10 -3
  351. package/src/renderers/electrobun/view/styles.css +17 -0
  352. package/src/renderers/electrobun/view/ui-host.tsx +7 -0
  353. package/src/renderers/opentui/chart-surface.test.tsx +52 -6
  354. package/src/renderers/opentui/chart-surface.tsx +95 -11
  355. package/src/renderers/opentui/dialog-host.tsx +36 -5
  356. package/src/renderers/opentui/host.tsx +39 -0
  357. package/src/renderers/opentui/start.tsx +30 -0
  358. package/src/renderers/opentui/ui-host.tsx +2 -0
  359. package/src/sources/provider-router/index.test.ts +43 -0
  360. package/src/sources/provider-router/index.ts +4 -0
  361. package/src/sources/provider-router/supplemental.ts +46 -0
  362. package/src/sources/sec-edgar.test.ts +203 -2
  363. package/src/sources/sec-edgar.ts +70 -15
  364. package/src/state/app/context/index.tsx +13 -1
  365. package/src/state/app/context/selector.test.tsx +62 -1
  366. package/src/state/session-persistence.test.ts +13 -2
  367. package/src/sync/controller.test.ts +190 -3
  368. package/src/sync/controller.ts +76 -45
  369. package/src/sync/core-contributors.test.ts +121 -0
  370. package/src/sync/core-contributors.ts +104 -27
  371. package/src/sync/react.ts +6 -8
  372. package/src/sync/types.ts +3 -0
  373. package/src/time-series/alignment.ts +158 -0
  374. package/src/time-series/economic.ts +49 -0
  375. package/src/time-series/field-catalog.ts +306 -0
  376. package/src/time-series/fundamentals.test.ts +505 -0
  377. package/src/time-series/fundamentals.ts +842 -0
  378. package/src/time-series/hooks.ts +45 -0
  379. package/src/time-series/index.ts +14 -0
  380. package/src/time-series/live-quotes.test.ts +219 -0
  381. package/src/time-series/live-quotes.ts +203 -0
  382. package/src/time-series/market-resolution.ts +53 -0
  383. package/src/time-series/market.ts +125 -0
  384. package/src/time-series/reporting.ts +98 -0
  385. package/src/time-series/resolve.test.ts +1281 -0
  386. package/src/time-series/resolve.ts +783 -0
  387. package/src/time-series/spec-studies.test.ts +400 -0
  388. package/src/time-series/spec.ts +487 -0
  389. package/src/time-series/studies.ts +563 -0
  390. package/src/time-series/transforms.test.ts +105 -0
  391. package/src/time-series/transforms.ts +152 -0
  392. package/src/time-series/types.ts +162 -0
  393. package/src/time-series/use-chart-resolution.ts +166 -0
  394. package/src/types/config.ts +9 -11
  395. package/src/types/desktop-window.ts +1 -0
  396. package/src/types/financials.ts +4 -0
  397. package/src/types/media.ts +17 -0
  398. package/src/types/plugin.ts +32 -1
  399. package/src/ui/context-menu.tsx +15 -3
  400. package/src/ui/dialog.tsx +14 -2
  401. package/src/ui/host.tsx +40 -1
  402. package/src/ui/index.tsx +9 -0
  403. package/src/utils/exchanges.test.ts +13 -1
  404. package/src/utils/exchanges.ts +10 -0
  405. package/src/utils/financial-statements.test.ts +138 -0
  406. package/src/utils/financial-statements.ts +115 -8
  407. package/src/utils/format.test.ts +11 -1
  408. package/src/utils/format.ts +7 -0
  409. package/src/version.ts +1 -1
  410. package/src/components/chart/bar-chart-renderer.test.ts +0 -233
  411. package/src/components/chart/bar-chart-renderer.ts +0 -445
  412. package/src/components/chart/comparison/data.test.ts +0 -159
  413. package/src/components/chart/comparison/data.ts +0 -302
  414. package/src/components/chart/comparison/renderer.test.ts +0 -187
  415. package/src/components/chart/comparison/renderer.ts +0 -282
  416. package/src/components/chart/comparison/scene.ts +0 -178
  417. package/src/components/chart/comparison-stock-chart/axis-width.ts +0 -99
  418. package/src/components/chart/comparison-stock-chart/canvas.ts +0 -148
  419. package/src/components/chart/comparison-stock-chart/controls.ts +0 -252
  420. package/src/components/chart/comparison-stock-chart/helpers.ts +0 -101
  421. package/src/components/chart/comparison-stock-chart/interactions.ts +0 -208
  422. package/src/components/chart/comparison-stock-chart/layout-metrics.ts +0 -62
  423. package/src/components/chart/comparison-stock-chart/layout.tsx +0 -155
  424. package/src/components/chart/comparison-stock-chart/legend.tsx +0 -74
  425. package/src/components/chart/comparison-stock-chart/native-surfaces.ts +0 -89
  426. package/src/components/chart/comparison-stock-chart/render-data.ts +0 -227
  427. package/src/components/chart/comparison-stock-chart/render-output.ts +0 -140
  428. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +0 -128
  429. package/src/components/chart/comparison-stock-chart/sources.ts +0 -51
  430. package/src/components/chart/comparison-stock-chart/surface-runtime.ts +0 -159
  431. package/src/components/chart/comparison-stock-chart/toolbar.tsx +0 -117
  432. package/src/components/chart/comparison-stock-chart/types.ts +0 -22
  433. package/src/components/chart/comparison-stock-chart/view.tsx +0 -572
  434. package/src/components/chart/comparison-stock-chart/viewport-runtime.ts +0 -239
  435. package/src/components/chart/comparison-stock-chart.tsx +0 -24
  436. package/src/components/chart/core/axis-measure.ts +0 -33
  437. package/src/components/chart/core/controller.test.ts +0 -157
  438. package/src/components/chart/core/controller.ts +0 -349
  439. package/src/components/chart/core/indicator-selector.tsx +0 -75
  440. package/src/components/chart/core/keyboard.ts +0 -24
  441. package/src/components/chart/core/pane-settings.test.ts +0 -52
  442. package/src/components/chart/core/pane-settings.ts +0 -141
  443. package/src/components/chart/core/scroll.test.ts +0 -64
  444. package/src/components/chart/core/scroll.ts +0 -82
  445. package/src/components/chart/core/window-viewport.test.ts +0 -69
  446. package/src/components/chart/core/window-viewport.ts +0 -162
  447. package/src/components/chart/display-cursor.ts +0 -153
  448. package/src/components/chart/indicators/bands.test.ts +0 -36
  449. package/src/components/chart/indicators/bands.ts +0 -29
  450. package/src/components/chart/indicators/moving-averages.test.ts +0 -39
  451. package/src/components/chart/indicators/moving-averages.ts +0 -42
  452. package/src/components/chart/indicators/options.test.ts +0 -32
  453. package/src/components/chart/indicators/options.ts +0 -107
  454. package/src/components/chart/indicators/oscillators.test.ts +0 -58
  455. package/src/components/chart/indicators/oscillators.ts +0 -92
  456. package/src/components/chart/native/canvas-bitmaps.ts +0 -140
  457. package/src/components/chart/native/raster/comparison-chart.ts +0 -162
  458. package/src/components/chart/native/surface/hooks.ts +0 -144
  459. package/src/components/chart/native/surface/rendering.ts +0 -211
  460. package/src/components/chart/static/bar-chart-surface.test.tsx +0 -101
  461. package/src/components/chart/static/bar-chart-surface.tsx +0 -292
  462. package/src/components/chart/stock-chart/auto.ts +0 -216
  463. package/src/components/chart/stock-chart/axis-width.ts +0 -146
  464. package/src/components/chart/stock-chart/bitmaps.ts +0 -22
  465. package/src/components/chart/stock-chart/canvas.ts +0 -202
  466. package/src/components/chart/stock-chart/controls.tsx +0 -149
  467. package/src/components/chart/stock-chart/cursor.ts +0 -60
  468. package/src/components/chart/stock-chart/data-runtime.ts +0 -128
  469. package/src/components/chart/stock-chart/footer.test.ts +0 -65
  470. package/src/components/chart/stock-chart/footer.ts +0 -303
  471. package/src/components/chart/stock-chart/geometry.ts +0 -160
  472. package/src/components/chart/stock-chart/index.test.ts +0 -532
  473. package/src/components/chart/stock-chart/index.tsx +0 -29
  474. package/src/components/chart/stock-chart/indicators.ts +0 -226
  475. package/src/components/chart/stock-chart/interaction-runtime.ts +0 -44
  476. package/src/components/chart/stock-chart/interactions.ts +0 -300
  477. package/src/components/chart/stock-chart/keyboard.ts +0 -373
  478. package/src/components/chart/stock-chart/layout-metrics.ts +0 -62
  479. package/src/components/chart/stock-chart/layout.tsx +0 -177
  480. package/src/components/chart/stock-chart/native-surfaces.ts +0 -121
  481. package/src/components/chart/stock-chart/presentation.ts +0 -67
  482. package/src/components/chart/stock-chart/renderer-switch.test.tsx +0 -263
  483. package/src/components/chart/stock-chart/rendering/bounds.ts +0 -116
  484. package/src/components/chart/stock-chart/rendering/candidates.ts +0 -145
  485. package/src/components/chart/stock-chart/rendering/data.ts +0 -370
  486. package/src/components/chart/stock-chart/rendering/invalidation.ts +0 -68
  487. package/src/components/chart/stock-chart/rendering/output.ts +0 -304
  488. package/src/components/chart/stock-chart/rendering/planning.ts +0 -122
  489. package/src/components/chart/stock-chart/rendering/projection.test.tsx +0 -98
  490. package/src/components/chart/stock-chart/rendering/projection.ts +0 -112
  491. package/src/components/chart/stock-chart/rendering/state.test.ts +0 -70
  492. package/src/components/chart/stock-chart/rendering/state.ts +0 -283
  493. package/src/components/chart/stock-chart/requests.ts +0 -337
  494. package/src/components/chart/stock-chart/resolution-support.ts +0 -96
  495. package/src/components/chart/stock-chart/resolved/runtime.ts +0 -289
  496. package/src/components/chart/stock-chart/resolved.tsx +0 -359
  497. package/src/components/chart/stock-chart/settings.ts +0 -81
  498. package/src/components/chart/stock-chart/surface-runtime.ts +0 -62
  499. package/src/components/chart/stock-chart/types.ts +0 -25
  500. package/src/components/chart/stock-chart/view.tsx +0 -155
  501. package/src/components/chart/stock-chart/viewport/effects.ts +0 -293
  502. package/src/components/chart/stock-chart/viewport/index.ts +0 -144
  503. package/src/components/chart/stock-chart/viewport/runtime.ts +0 -375
  504. package/src/components/chart/time-axis-label.tsx +0 -175
  505. package/src/plugins/builtin/ai/gloomberb-cli.ts +0 -71
  506. package/src/plugins/builtin/ai/screener/action-bar.tsx +0 -58
  507. package/src/plugins/builtin/comparison-chart/index.test.tsx +0 -848
  508. package/src/plugins/builtin/comparison-chart/index.tsx +0 -194
  509. package/src/plugins/builtin/econ/fred-cache.ts +0 -106
  510. package/src/plugins/builtin/plugin-groups.ts +0 -130
  511. package/src/plugins/builtin/ticker-detail/chart-pane.tsx +0 -91
  512. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -100
  513. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +0 -296
  514. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/data.ts +0 -62
  515. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +0 -608
  516. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +0 -65
  517. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +0 -58
  518. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +0 -138
  519. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +0 -220
@@ -1,74 +1,199 @@
1
+ import type { AiRunOutputMode, AiRuntimeProvider } from "./runner";
2
+
3
+ export const AI_PROVIDER_IDS = [
4
+ "anthropic",
5
+ "openai-codex",
6
+ "openai",
7
+ "google",
8
+ "github-copilot",
9
+ "xai",
10
+ "openrouter",
11
+ ] as const;
12
+
13
+ export type AiProviderId = (typeof AI_PROVIDER_IDS)[number];
14
+
15
+ /**
16
+ * These aliases exist only to migrate persisted pre-Pi selections. Runtime
17
+ * catalogs and requests always use the canonical Pi provider ids above.
18
+ */
19
+ export const LEGACY_AI_PROVIDER_ID_ALIASES = {
20
+ claude: "anthropic",
21
+ codex: "openai-codex",
22
+ gemini: "google",
23
+ } as const satisfies Readonly<Record<string, AiProviderId>>;
24
+
25
+ export type AiProviderStatus = "ready" | "not_authenticated" | "check_failed";
26
+
27
+ export interface AiProviderDefinition {
28
+ id: AiProviderId;
29
+ name: string;
30
+ outputModes: readonly AiRunOutputMode[];
31
+ /**
32
+ * Ordered, curated defaults. The runtime chooses the first one available to
33
+ * the connected account and never falls back to provider array order.
34
+ */
35
+ preferredModelIds: readonly string[];
36
+ }
37
+
1
38
  export interface AiProvider {
2
- id: string;
39
+ id: AiProviderId;
3
40
  name: string;
4
- command: string;
5
41
  available: boolean;
6
- buildArgs: (prompt: string) => string[];
42
+ status: AiProviderStatus;
43
+ unavailableReason?: string;
44
+ outputModes: AiRunOutputMode[];
45
+ defaultModelId?: string;
7
46
  }
8
47
 
9
- export interface AiProviderDefinition extends Omit<AiProvider, "available"> {}
48
+ const ALL_OUTPUT_MODES: readonly AiRunOutputMode[] = ["plain", "structured", "screener"];
10
49
 
11
- const PROVIDER_DEFS: AiProviderDefinition[] = [
50
+ const PROVIDER_DEFINITIONS: readonly AiProviderDefinition[] = [
12
51
  {
13
- id: "claude",
52
+ id: "anthropic",
14
53
  name: "Claude",
15
- command: "claude",
16
- buildArgs: (prompt) => ["-p", prompt],
54
+ outputModes: ALL_OUTPUT_MODES,
55
+ preferredModelIds: ["claude-opus-4-8", "claude-sonnet-5"],
56
+ },
57
+ {
58
+ id: "openai-codex",
59
+ name: "OpenAI (ChatGPT)",
60
+ outputModes: ALL_OUTPUT_MODES,
61
+ preferredModelIds: ["gpt-5.6-sol", "gpt-5.6-terra"],
62
+ },
63
+ {
64
+ id: "openai",
65
+ name: "OpenAI API",
66
+ outputModes: ALL_OUTPUT_MODES,
67
+ preferredModelIds: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.4"],
68
+ },
69
+ {
70
+ id: "google",
71
+ name: "Google Gemini",
72
+ outputModes: ALL_OUTPUT_MODES,
73
+ preferredModelIds: ["gemini-3.6-flash", "gemini-3.5-flash"],
17
74
  },
18
75
  {
19
- id: "gemini",
20
- name: "Gemini",
21
- command: "gemini",
22
- buildArgs: (prompt) => ["-p", prompt],
76
+ id: "github-copilot",
77
+ name: "GitHub Copilot",
78
+ outputModes: ALL_OUTPUT_MODES,
79
+ preferredModelIds: ["gpt-5.6-sol", "claude-sonnet-5", "gpt-5.4"],
23
80
  },
24
81
  {
25
- id: "codex",
26
- name: "Codex",
27
- command: "codex",
28
- buildArgs: (prompt) => ["exec", "--skip-git-repo-check", prompt],
82
+ id: "xai",
83
+ name: "xAI / Grok",
84
+ outputModes: ALL_OUTPUT_MODES,
85
+ preferredModelIds: ["grok-4.5", "grok-4.3"],
86
+ },
87
+ {
88
+ id: "openrouter",
89
+ name: "OpenRouter",
90
+ outputModes: ALL_OUTPUT_MODES,
91
+ preferredModelIds: [
92
+ "anthropic/claude-sonnet-5",
93
+ "openai/gpt-5.6-sol",
94
+ "google/gemini-3.6-flash",
95
+ ],
29
96
  },
30
97
  ];
31
98
 
32
99
  let detectedProviders: AiProvider[] | null = null;
33
100
 
34
- function commandExists(command: string): boolean {
35
- try {
36
- if (typeof Bun !== "undefined" && typeof Bun.which === "function") {
37
- return !!Bun.which(command);
38
- }
39
- } catch {
40
- return false;
41
- }
42
- return false;
101
+ export function isAiProviderId(providerId: string): providerId is AiProviderId {
102
+ return (AI_PROVIDER_IDS as readonly string[]).includes(providerId);
43
103
  }
44
104
 
45
- export function detectProviders(): AiProvider[] {
46
- if (detectedProviders) return detectedProviders;
105
+ export function migrateLegacyAiProviderId(providerId: string): string {
106
+ return LEGACY_AI_PROVIDER_ID_ALIASES[
107
+ providerId as keyof typeof LEGACY_AI_PROVIDER_ID_ALIASES
108
+ ] ?? providerId;
109
+ }
47
110
 
48
- detectedProviders = PROVIDER_DEFS.map((definition) => ({
111
+ export function getAiProviderDefinition(
112
+ providerId: string | null | undefined,
113
+ ): AiProviderDefinition | null {
114
+ if (!providerId) return null;
115
+ const canonicalId = migrateLegacyAiProviderId(providerId);
116
+ return PROVIDER_DEFINITIONS.find((provider) => provider.id === canonicalId) ?? null;
117
+ }
118
+
119
+ export function getAiProviderDefinitions(): AiProviderDefinition[] {
120
+ return PROVIDER_DEFINITIONS.map((definition) => ({
49
121
  ...definition,
50
- available: commandExists(definition.command),
122
+ outputModes: [...definition.outputModes],
123
+ preferredModelIds: [...definition.preferredModelIds],
51
124
  }));
125
+ }
126
+
127
+ function disconnectedProvider(definition: AiProviderDefinition): AiProvider {
128
+ return {
129
+ id: definition.id,
130
+ name: definition.name,
131
+ available: false,
132
+ status: "not_authenticated",
133
+ unavailableReason: `${definition.name} is not connected.`,
134
+ outputModes: [...definition.outputModes],
135
+ defaultModelId: definition.preferredModelIds[0],
136
+ };
137
+ }
138
+
139
+ export function aiProviderFromRuntime(provider: AiRuntimeProvider): AiProvider {
140
+ return {
141
+ id: provider.providerId,
142
+ name: provider.label,
143
+ available: provider.status === "ready",
144
+ status: provider.status,
145
+ ...(provider.unavailableReason ? { unavailableReason: provider.unavailableReason } : {}),
146
+ outputModes: [...provider.outputModes],
147
+ ...(provider.defaultModelId ? { defaultModelId: provider.defaultModelId } : {}),
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Compatibility accessor for pane code while it moves to the reactive runtime
153
+ * catalog. It contains only Pi providers and never performs CLI discovery.
154
+ */
155
+ export function detectProviders(): AiProvider[] {
156
+ if (detectedProviders) return detectedProviders;
157
+ detectedProviders = PROVIDER_DEFINITIONS.map(disconnectedProvider);
52
158
  return detectedProviders;
53
159
  }
54
160
 
55
- export function getAvailableProviders(providers = detectProviders()): AiProvider[] {
161
+ export function getAvailableProviders(
162
+ providers: readonly AiProvider[] = detectProviders(),
163
+ ): AiProvider[] {
56
164
  return providers.filter((provider) => provider.available);
57
165
  }
58
166
 
59
- export function getAiProvider(providerId: string | null | undefined, providers = detectProviders()): AiProvider | null {
167
+ export function getAiProvider(
168
+ providerId: string | null | undefined,
169
+ providers: readonly AiProvider[] = detectProviders(),
170
+ ): AiProvider | null {
60
171
  if (!providerId) return null;
61
- return providers.find((provider) => provider.id === providerId) ?? null;
172
+ const canonicalId = migrateLegacyAiProviderId(providerId);
173
+ return providers.find((provider) => provider.id === canonicalId) ?? null;
62
174
  }
63
175
 
64
- export function resolveDefaultAiProviderId(providers = detectProviders()): string {
65
- return getAvailableProviders(providers)[0]?.id ?? providers[0]?.id ?? "claude";
176
+ export function resolveDefaultAiProviderId(
177
+ providers: readonly AiProvider[] = detectProviders(),
178
+ ): AiProviderId {
179
+ return providers.find((provider) => provider.status === "ready")?.id
180
+ ?? providers[0]?.id
181
+ ?? "anthropic";
66
182
  }
67
183
 
68
- export function __setDetectedProvidersForTests(providers: AiProvider[] | null): void {
69
- detectedProviders = providers;
184
+ export function getAiProviderUnavailableReason(provider: AiProvider): string {
185
+ return provider.unavailableReason ?? `${provider.name} is not connected.`;
70
186
  }
71
187
 
72
- export function getAiProviderDefinitions(): AiProviderDefinition[] {
73
- return PROVIDER_DEFS.map((definition) => ({ ...definition }));
188
+ export function getAiProviderUnavailableLabel(provider: AiProvider): string {
189
+ return provider.status === "check_failed" ? "unavailable" : "sign in";
190
+ }
191
+
192
+ export function setDetectedProviders(providers: AiProvider[] | null): void {
193
+ detectedProviders = providers?.map((provider) => ({
194
+ ...provider,
195
+ outputModes: [...provider.outputModes],
196
+ })) ?? null;
74
197
  }
198
+
199
+ export const __setDetectedProvidersForTests = setDetectedProviders;
@@ -0,0 +1,174 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { AiProvider, AiProviderId } from "./providers";
3
+ import {
4
+ AI_AUTO_MODEL_VALUE,
5
+ buildAiRunnerWizard,
6
+ getAiModelSelectionOptions,
7
+ getAiRunnerWizardModelKey,
8
+ getSelectableAiRunners,
9
+ isAiProviderReady,
10
+ modelIdAfterAiProviderChange,
11
+ normalizeAiModelId,
12
+ resolveAiRunnerWizardModel,
13
+ resolveReadyAiRunnerDefault,
14
+ supportsAiRunOutputMode,
15
+ } from "./runner-selection";
16
+ import type { AiRuntimeCatalog } from "./runner";
17
+
18
+ function provider(
19
+ id: AiProviderId,
20
+ status: AiProvider["status"],
21
+ outputModes: AiProvider["outputModes"] = ["plain", "structured", "screener"],
22
+ ): AiProvider {
23
+ return {
24
+ id,
25
+ name: id.toUpperCase(),
26
+ available: status === "ready",
27
+ status,
28
+ ...(status !== "ready" ? { unavailableReason: `${id} is not connected.` } : {}),
29
+ outputModes,
30
+ };
31
+ }
32
+
33
+ function catalog(providers: AiProvider[]): AiRuntimeCatalog {
34
+ return {
35
+ providers: providers.map((entry) => ({
36
+ providerId: entry.id,
37
+ label: entry.name,
38
+ status: entry.status,
39
+ ...(entry.unavailableReason ? { unavailableReason: entry.unavailableReason } : {}),
40
+ outputModes: [...entry.outputModes],
41
+ })),
42
+ accounts: [],
43
+ models: [],
44
+ };
45
+ }
46
+
47
+ describe("AI runner selection", () => {
48
+ test("shows every capable provider while preselecting one that is ready", () => {
49
+ const providers = [
50
+ provider("anthropic", "not_authenticated"),
51
+ provider("openai-codex", "ready"),
52
+ provider("google", "ready", ["plain"]),
53
+ ];
54
+
55
+ expect(getSelectableAiRunners(providers, {
56
+ outputMode: "structured",
57
+ runtimeCatalog: catalog(providers),
58
+ }).map(({ id }) => id)).toEqual(["anthropic", "openai-codex"]);
59
+ expect(buildAiRunnerWizard(providers, {
60
+ outputMode: "structured",
61
+ runtimeCatalog: catalog(providers),
62
+ })[0]?.defaultValue).toBe("openai-codex");
63
+ });
64
+
65
+ test("shows the full capable catalog with connection labels when none are ready", () => {
66
+ const providers = [
67
+ provider("anthropic", "not_authenticated"),
68
+ provider("openai-codex", "check_failed"),
69
+ provider("google", "not_authenticated", ["plain"]),
70
+ ];
71
+ const wizard = buildAiRunnerWizard(providers, {
72
+ outputMode: "structured",
73
+ runtimeCatalog: catalog(providers),
74
+ });
75
+
76
+ expect(getSelectableAiRunners(providers, {
77
+ outputMode: "structured",
78
+ runtimeCatalog: catalog(providers),
79
+ }).map(({ id }) => id)).toEqual(["anthropic", "openai-codex"]);
80
+ expect(wizard[0]?.type === "select" ? wizard[0].options?.map((option) => option.label) : [])
81
+ .toEqual(["ANTHROPIC (sign in)", "OPENAI-CODEX (unavailable)"]);
82
+ expect(wizard[0]?.defaultValue).toBe("anthropic");
83
+ });
84
+
85
+ test("derives capabilities only from Pi catalog output modes", () => {
86
+ const plain = provider("google", "ready", ["plain"]);
87
+ const structured = provider("anthropic", "ready");
88
+ const runtimeCatalog = catalog([plain, structured]);
89
+
90
+ expect(supportsAiRunOutputMode(plain, "structured", runtimeCatalog)).toBe(false);
91
+ expect(supportsAiRunOutputMode(structured, "structured", runtimeCatalog)).toBe(true);
92
+ });
93
+
94
+ test("preselects configured defaults from a provider-scoped Pi model catalog", () => {
95
+ const providers = [
96
+ provider("anthropic", "ready"),
97
+ provider("openai-codex", "ready"),
98
+ ];
99
+ const wizard = buildAiRunnerWizard(providers, {
100
+ defaultProviderId: "anthropic",
101
+ defaultModelId: " claude-opus-4-8 ",
102
+ outputMode: "structured",
103
+ });
104
+
105
+ expect(wizard[0]?.defaultValue).toBe("anthropic");
106
+ expect(wizard[1]).toMatchObject({
107
+ key: getAiRunnerWizardModelKey("anthropic"),
108
+ type: "select",
109
+ required: true,
110
+ defaultValue: "claude-opus-4-8",
111
+ dependsOn: { key: "providerId", value: "anthropic" },
112
+ });
113
+ expect(wizard[1]?.options?.map((option) => option.value))
114
+ .toEqual([AI_AUTO_MODEL_VALUE, "claude-opus-4-8"]);
115
+ expect(resolveAiRunnerWizardModel({
116
+ [getAiRunnerWizardModelKey("anthropic")]: "claude-opus-4-8",
117
+ }, "anthropic")).toBe("claude-opus-4-8");
118
+ expect(resolveAiRunnerWizardModel({
119
+ [getAiRunnerWizardModelKey("anthropic")]: AI_AUTO_MODEL_VALUE,
120
+ }, "anthropic")).toBeNull();
121
+ expect(normalizeAiModelId(" provider/model ")).toBe("provider/model");
122
+ expect(normalizeAiModelId(" ")).toBeNull();
123
+ });
124
+
125
+ test("uses Pi labels and availability in model choices", () => {
126
+ const runtimeCatalog = catalog([provider("anthropic", "ready")]);
127
+ runtimeCatalog.providers[0]!.defaultModelId = "claude-sonnet";
128
+ runtimeCatalog.models = [
129
+ {
130
+ id: "claude-sonnet",
131
+ providerId: "anthropic",
132
+ label: "Claude Sonnet",
133
+ available: true,
134
+ },
135
+ {
136
+ id: "claude-opus",
137
+ providerId: "anthropic",
138
+ label: "Claude Opus",
139
+ available: false,
140
+ },
141
+ ];
142
+
143
+ expect(getAiModelSelectionOptions("anthropic", null, runtimeCatalog)).toEqual([
144
+ expect.objectContaining({ value: AI_AUTO_MODEL_VALUE, label: "Auto · Claude Sonnet" }),
145
+ expect.objectContaining({ value: "claude-sonnet", label: "Claude Sonnet" }),
146
+ expect.objectContaining({ value: "claude-opus", label: "Claude Opus · connect to use" }),
147
+ ]);
148
+ });
149
+
150
+ test("replaces an unready configured provider with a ready one and clears its model", () => {
151
+ const providers = [
152
+ provider("anthropic", "not_authenticated"),
153
+ provider("openai-codex", "ready"),
154
+ ];
155
+
156
+ expect(resolveReadyAiRunnerDefault(providers, "claude", "claude-opus-4-8"))
157
+ .toEqual({ providerId: "openai-codex", modelId: null });
158
+ });
159
+
160
+ test("uses legacy aliases only when migrating defaults and model selections", () => {
161
+ const providers = [provider("anthropic", "ready")];
162
+
163
+ expect(resolveReadyAiRunnerDefault(providers, "claude", "claude-opus-4-8"))
164
+ .toEqual({ providerId: "anthropic", modelId: "claude-opus-4-8" });
165
+ expect(modelIdAfterAiProviderChange("anthropic", "claude", " claude-opus-4-8 "))
166
+ .toBe("claude-opus-4-8");
167
+ expect(modelIdAfterAiProviderChange("openai-codex", "claude", "claude-opus-4-8"))
168
+ .toBe("");
169
+ });
170
+
171
+ test("does not present a disconnected provider as ready", () => {
172
+ expect(isAiProviderReady(provider("anthropic", "not_authenticated"))).toBe(false);
173
+ });
174
+ });
@@ -0,0 +1,193 @@
1
+ import type { WizardStep } from "../../../types/plugin";
2
+ import type { AiProvider } from "./providers";
3
+ import {
4
+ getAiProviderUnavailableLabel,
5
+ migrateLegacyAiProviderId,
6
+ resolveDefaultAiProviderId,
7
+ } from "./providers";
8
+ import {
9
+ getAiRuntimeCatalog,
10
+ type AiRunOutputMode,
11
+ type AiRuntimeCatalog,
12
+ } from "./runner";
13
+
14
+ export const AI_AUTO_MODEL_VALUE = "__auto__";
15
+
16
+ export interface AiRunnerSelectionScope {
17
+ outputMode?: AiRunOutputMode;
18
+ defaultProviderId?: string | null;
19
+ defaultModelId?: string | null;
20
+ runtimeCatalog?: AiRuntimeCatalog;
21
+ }
22
+
23
+ export interface AiRunnerDefault {
24
+ providerId: string;
25
+ modelId: string | null;
26
+ }
27
+
28
+ export interface AiModelSelectionOption {
29
+ value: string;
30
+ label: string;
31
+ description?: string;
32
+ }
33
+
34
+ export function isAiProviderReady(provider: AiProvider): boolean {
35
+ return provider.available && (provider.status === undefined || provider.status === "ready");
36
+ }
37
+
38
+ export function supportsAiRunOutputMode(
39
+ provider: AiProvider,
40
+ outputMode: AiRunOutputMode,
41
+ runtimeCatalog = getAiRuntimeCatalog(),
42
+ ): boolean {
43
+ const runtimeProvider = runtimeCatalog.providers.find((candidate) => (
44
+ candidate.providerId === provider.id
45
+ ));
46
+ return (runtimeProvider?.outputModes ?? provider.outputModes).includes(outputMode);
47
+ }
48
+
49
+ export function getSelectableAiRunners(
50
+ providers: readonly AiProvider[],
51
+ scope: AiRunnerSelectionScope = {},
52
+ ): AiProvider[] {
53
+ const outputMode = scope.outputMode ?? "plain";
54
+ const catalog = scope.runtimeCatalog ?? getAiRuntimeCatalog();
55
+ const capable = providers.filter((provider) => (
56
+ supportsAiRunOutputMode(provider, outputMode, catalog)
57
+ ));
58
+ return capable;
59
+ }
60
+
61
+ export function normalizeAiModelId(value: string | null | undefined): string | null {
62
+ const modelId = value?.trim() ?? "";
63
+ return modelId && modelId !== AI_AUTO_MODEL_VALUE ? modelId : null;
64
+ }
65
+
66
+ export function getAiModelSelectionOptions(
67
+ providerId: string,
68
+ currentModelId?: string | null,
69
+ runtimeCatalog = getAiRuntimeCatalog(),
70
+ ): AiModelSelectionOption[] {
71
+ const canonicalProviderId = migrateLegacyAiProviderId(providerId);
72
+ const provider = runtimeCatalog.providers.find((candidate) => (
73
+ candidate.providerId === canonicalProviderId
74
+ ));
75
+ const models = runtimeCatalog.models.filter((model) => (
76
+ model.providerId === canonicalProviderId
77
+ ));
78
+ const defaultModel = models.find((model) => model.id === provider?.defaultModelId);
79
+ const options: AiModelSelectionOption[] = [
80
+ {
81
+ value: AI_AUTO_MODEL_VALUE,
82
+ label: defaultModel
83
+ ? `Auto · ${defaultModel.label}`
84
+ : provider?.defaultModelId
85
+ ? `Auto · ${provider.defaultModelId}`
86
+ : "Auto · provider default",
87
+ description: "Use the provider's recommended model.",
88
+ },
89
+ ...models.map((model) => ({
90
+ value: model.id,
91
+ label: model.available ? model.label : `${model.label} · connect to use`,
92
+ description: model.available
93
+ ? "Available for the connected account."
94
+ : "This model becomes available after the provider is connected.",
95
+ })),
96
+ ];
97
+ const normalizedCurrent = normalizeAiModelId(currentModelId);
98
+ if (normalizedCurrent && !models.some((model) => model.id === normalizedCurrent)) {
99
+ options.push({
100
+ value: normalizedCurrent,
101
+ label: `${normalizedCurrent} · current`,
102
+ description: "Saved before the current Pi model catalog was loaded.",
103
+ });
104
+ }
105
+ return options;
106
+ }
107
+
108
+ export function getAiRunnerWizardModelKey(providerId: string): string {
109
+ return `modelId:${migrateLegacyAiProviderId(providerId)}`;
110
+ }
111
+
112
+ export function resolveAiRunnerWizardModel(
113
+ values: Record<string, string> | undefined,
114
+ providerId: string,
115
+ fallbackModelId?: string | null,
116
+ ): string | null {
117
+ const selected = values?.[getAiRunnerWizardModelKey(providerId)]
118
+ ?? values?.modelId
119
+ ?? fallbackModelId;
120
+ return normalizeAiModelId(selected);
121
+ }
122
+
123
+ export function modelIdAfterAiProviderChange(
124
+ providerId: string,
125
+ defaultProviderId: string,
126
+ defaultModelId: string | null | undefined,
127
+ ): string {
128
+ return migrateLegacyAiProviderId(providerId) === migrateLegacyAiProviderId(defaultProviderId)
129
+ ? normalizeAiModelId(defaultModelId) ?? ""
130
+ : "";
131
+ }
132
+
133
+ export function resolveReadyAiRunnerDefault(
134
+ providers: readonly AiProvider[],
135
+ configuredProviderId?: string | null,
136
+ configuredModelId?: string | null,
137
+ ): AiRunnerDefault {
138
+ const configuredId = migrateLegacyAiProviderId(configuredProviderId?.trim() ?? "");
139
+ const configured = providers.find((provider) => provider.id === configuredId);
140
+ const ready = providers.find(isAiProviderReady);
141
+ const providerId = configured && (isAiProviderReady(configured) || !ready)
142
+ ? configured.id
143
+ : ready?.id ?? resolveDefaultAiProviderId(providers);
144
+ return {
145
+ providerId,
146
+ modelId: providerId === configuredId ? normalizeAiModelId(configuredModelId) : null,
147
+ };
148
+ }
149
+
150
+ export function buildAiRunnerWizard(
151
+ providers: readonly AiProvider[],
152
+ scope: AiRunnerSelectionScope = {},
153
+ ): WizardStep[] {
154
+ const runners = getSelectableAiRunners(providers, scope);
155
+ const defaults = resolveReadyAiRunnerDefault(
156
+ runners,
157
+ scope.defaultProviderId,
158
+ scope.defaultModelId,
159
+ );
160
+ const runtimeCatalog = scope.runtimeCatalog ?? getAiRuntimeCatalog();
161
+ return [
162
+ {
163
+ key: "providerId",
164
+ label: "AI Provider",
165
+ type: "select",
166
+ defaultValue: defaults.providerId,
167
+ options: runners.map((provider) => ({
168
+ label: isAiProviderReady(provider)
169
+ ? provider.name
170
+ : `${provider.name} (${getAiProviderUnavailableLabel(provider)})`,
171
+ value: provider.id,
172
+ })),
173
+ body: ["Choose the AI provider for this conversation."],
174
+ },
175
+ ...runners.map((provider): WizardStep => {
176
+ const selectedModelId = provider.id === defaults.providerId ? defaults.modelId : null;
177
+ return {
178
+ key: getAiRunnerWizardModelKey(provider.id),
179
+ label: "AI Model",
180
+ type: "select",
181
+ required: true,
182
+ dependsOn: { key: "providerId", value: provider.id },
183
+ defaultValue: selectedModelId ?? AI_AUTO_MODEL_VALUE,
184
+ options: getAiModelSelectionOptions(provider.id, selectedModelId, runtimeCatalog),
185
+ body: ["Choose from the models published by Pi, or use the provider default."],
186
+ };
187
+ }),
188
+ ];
189
+ }
190
+
191
+ export function formatAiRunnerSelection(providerName: string, modelId: string | null | undefined): string {
192
+ return normalizeAiModelId(modelId) ? `${providerName} · ${normalizeAiModelId(modelId)}` : providerName;
193
+ }