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
@@ -0,0 +1,635 @@
1
+ import {
2
+ getSupportedThinkingLevels,
3
+ type Api,
4
+ type AssistantMessage,
5
+ type AssistantMessageEvent,
6
+ type AuthEvent,
7
+ type AuthInteraction,
8
+ type AuthPrompt,
9
+ type AuthType,
10
+ type Context,
11
+ type CredentialStore,
12
+ type Message,
13
+ type Model,
14
+ type Models,
15
+ type ModelsStore,
16
+ type ModelThinkingLevel,
17
+ type ThinkingLevel,
18
+ type Tool,
19
+ } from "@earendil-works/pi-ai";
20
+ import {
21
+ Agent,
22
+ type AgentEvent,
23
+ type AgentMessage,
24
+ type AgentOptions,
25
+ type AgentTool,
26
+ type ThinkingLevel as AgentThinkingLevel,
27
+ } from "@earendil-works/pi-agent-core";
28
+ import {
29
+ getAiProviderDefinition,
30
+ isAiProviderId,
31
+ type AiProviderId,
32
+ } from "../providers";
33
+ import type { AiAgentHistoryMessage } from "../agent-history";
34
+ import { PiFileCredentialStore } from "./credential-store";
35
+ import { PiFileModelsStore } from "./models-store";
36
+ import { createGloomberbPiModels, type PiProviderFactory } from "./providers";
37
+
38
+ export type PiRuntimeErrorCode =
39
+ | "provider_not_found"
40
+ | "provider_not_configured"
41
+ | "model_not_found"
42
+ | "model_unavailable"
43
+ | "login_failed"
44
+ | "request_failed"
45
+ | "cancelled";
46
+
47
+ export class PiRuntimeError extends Error {
48
+ readonly code: PiRuntimeErrorCode;
49
+
50
+ constructor(code: PiRuntimeErrorCode, message: string, options?: ErrorOptions) {
51
+ super(message, options);
52
+ this.name = "PiRuntimeError";
53
+ this.code = code;
54
+ }
55
+ }
56
+
57
+ export class PiRunCancelledError extends PiRuntimeError {
58
+ constructor() {
59
+ super("cancelled", "AI run cancelled");
60
+ this.name = "PiRunCancelledError";
61
+ }
62
+ }
63
+
64
+ export function isPiRunCancelled(error: unknown): error is PiRunCancelledError {
65
+ return error instanceof PiRunCancelledError;
66
+ }
67
+
68
+ export interface PiModelSummary {
69
+ id: string;
70
+ providerId: string;
71
+ name: string;
72
+ api: string;
73
+ reasoning: boolean;
74
+ thinkingLevels: ModelThinkingLevel[];
75
+ input: ("text" | "image")[];
76
+ contextWindow: number;
77
+ maxTokens: number;
78
+ available: boolean;
79
+ }
80
+
81
+ export interface PiAuthMethodSummary {
82
+ type: AuthType;
83
+ label: string;
84
+ canLogin: boolean;
85
+ }
86
+
87
+ export type PiProviderConnection =
88
+ | {
89
+ state: "connected";
90
+ type: AuthType;
91
+ source?: string;
92
+ origin: "stored" | "external";
93
+ disconnectable: boolean;
94
+ }
95
+ | { state: "not_connected" }
96
+ | { state: "error"; message: string };
97
+
98
+ export interface PiProviderSummary {
99
+ id: AiProviderId;
100
+ label: string;
101
+ name: string;
102
+ defaultModelId?: string;
103
+ authMethods: PiAuthMethodSummary[];
104
+ connection: PiProviderConnection;
105
+ models: PiModelSummary[];
106
+ }
107
+
108
+ export interface PiCatalog {
109
+ providers: PiProviderSummary[];
110
+ refreshErrors: Record<string, string>;
111
+ }
112
+
113
+ export interface PiModelSelection {
114
+ providerId: AiProviderId;
115
+ /** Empty/undefined uses a curated available default for this provider. */
116
+ modelId?: string;
117
+ }
118
+
119
+ export interface PiConversationMessage {
120
+ role: "user" | "assistant";
121
+ content: string;
122
+ }
123
+
124
+ type SerializableAuthPrompt<T> = T extends unknown ? Omit<T, "signal"> : never;
125
+ export type PiSerializableAuthPrompt = SerializableAuthPrompt<AuthPrompt>;
126
+
127
+ export interface PiLoginInteraction {
128
+ signal?: AbortSignal;
129
+ notify(event: AuthEvent): void;
130
+ prompt(prompt: PiSerializableAuthPrompt, signal?: AbortSignal): Promise<string>;
131
+ }
132
+
133
+ export interface PiLoginRequest {
134
+ providerId: AiProviderId;
135
+ type: AuthType;
136
+ }
137
+
138
+ export interface PiPromptRequest {
139
+ providerId: AiProviderId;
140
+ modelId?: string;
141
+ prompt: string;
142
+ systemPrompt?: string;
143
+ messages?: PiConversationMessage[];
144
+ tools?: Tool[];
145
+ reasoning?: ThinkingLevel;
146
+ sessionId?: string;
147
+ signal?: AbortSignal;
148
+ onChunk?: (cumulativeText: string) => void;
149
+ onEvent?: (event: AssistantMessageEvent) => void;
150
+ }
151
+
152
+ export interface PiPromptResult {
153
+ text: string;
154
+ message: AssistantMessage;
155
+ }
156
+
157
+ export interface PiRunController<T> {
158
+ done: Promise<T>;
159
+ cancel(): void;
160
+ }
161
+
162
+ export type PiTextRunController = PiRunController<string>;
163
+
164
+ export interface PiCreateAgentRequest {
165
+ providerId: AiProviderId;
166
+ modelId?: string;
167
+ systemPrompt?: string;
168
+ messages?: PiConversationMessage[];
169
+ agentMessages?: AiAgentHistoryMessage[];
170
+ tools?: AgentTool[];
171
+ thinkingLevel?: AgentThinkingLevel;
172
+ agentOptions?: Omit<AgentOptions, "initialState" | "streamFn">;
173
+ }
174
+
175
+ export interface PiAgentRunRequest extends PiCreateAgentRequest {
176
+ prompt: string;
177
+ onChunk?: (cumulativeText: string) => void;
178
+ onEvent?: (event: AgentEvent) => void | Promise<void>;
179
+ }
180
+
181
+ export interface PiAgentRunResult {
182
+ text: string;
183
+ messages: AgentMessage[];
184
+ }
185
+
186
+ export interface PiAiRuntimeOptions {
187
+ dataDir?: string;
188
+ credentials?: CredentialStore;
189
+ modelsStore?: ModelsStore;
190
+ models?: Models;
191
+ providerFactories?: readonly PiProviderFactory[];
192
+ }
193
+
194
+ function sanitizeErrorMessage(value: unknown): string {
195
+ const raw = value instanceof Error ? value.message : String(value);
196
+ return raw
197
+ .replace(/(bearer\s+)[^\s]+/gi, "$1[redacted]")
198
+ .replace(/((?:access|refresh|auth|oauth)[_ -]?token|api[_ -]?key|authorization)(["']?\s*[:=]\s*["']?)[^\s"']+/gi, "$1$2[redacted]")
199
+ .trim()
200
+ .slice(0, 2_000);
201
+ }
202
+
203
+ function assistantText(message: AssistantMessage): string {
204
+ return message.content
205
+ .filter((content): content is Extract<AssistantMessage["content"][number], { type: "text" }> => content.type === "text")
206
+ .map((content) => content.text)
207
+ .join("");
208
+ }
209
+
210
+ function isAssistantMessage(message: AgentMessage): message is AssistantMessage {
211
+ return !!message && typeof message === "object" && "role" in message && message.role === "assistant";
212
+ }
213
+
214
+ function modelSummary(model: Model<Api>, available: boolean): PiModelSummary {
215
+ return {
216
+ id: model.id,
217
+ providerId: model.provider,
218
+ name: model.name,
219
+ api: model.api,
220
+ reasoning: model.reasoning,
221
+ thinkingLevels: getSupportedThinkingLevels(model),
222
+ input: [...model.input],
223
+ contextWindow: model.contextWindow,
224
+ maxTokens: model.maxTokens,
225
+ available,
226
+ };
227
+ }
228
+
229
+ const EMPTY_USAGE: AssistantMessage["usage"] = {
230
+ input: 0,
231
+ output: 0,
232
+ cacheRead: 0,
233
+ cacheWrite: 0,
234
+ totalTokens: 0,
235
+ cost: {
236
+ input: 0,
237
+ output: 0,
238
+ cacheRead: 0,
239
+ cacheWrite: 0,
240
+ total: 0,
241
+ },
242
+ };
243
+
244
+ function toPiMessages(
245
+ messages: readonly PiConversationMessage[] | undefined,
246
+ model: Model<Api>,
247
+ ): Message[] {
248
+ const timestamp = Date.now();
249
+ return (messages ?? []).map((message, index) => {
250
+ if (message.role === "user") {
251
+ return {
252
+ role: "user",
253
+ content: message.content,
254
+ timestamp: timestamp + index,
255
+ };
256
+ }
257
+ return {
258
+ role: "assistant",
259
+ content: [{ type: "text", text: message.content }],
260
+ api: model.api,
261
+ provider: model.provider,
262
+ model: model.id,
263
+ usage: EMPTY_USAGE,
264
+ stopReason: "stop",
265
+ timestamp: timestamp + index,
266
+ };
267
+ });
268
+ }
269
+
270
+ function toPiAgentMessages(
271
+ messages: readonly AiAgentHistoryMessage[] | undefined,
272
+ model: Model<Api>,
273
+ ): Message[] {
274
+ const timestamp = Date.now();
275
+ return (messages ?? []).map((message, index) => {
276
+ if (message.role === "user") {
277
+ return {
278
+ role: "user",
279
+ content: message.content,
280
+ timestamp: timestamp + index,
281
+ };
282
+ }
283
+ if (message.role === "toolResult") {
284
+ return {
285
+ role: "toolResult",
286
+ toolCallId: message.toolCallId,
287
+ toolName: message.toolName,
288
+ content: message.content.map((content) => ({ ...content })),
289
+ isError: message.isError,
290
+ timestamp: timestamp + index,
291
+ };
292
+ }
293
+ return {
294
+ role: "assistant",
295
+ content: message.content.map((content) => (
296
+ content.type === "toolCall"
297
+ ? { ...content, arguments: { ...content.arguments } }
298
+ : { ...content }
299
+ )),
300
+ api: model.api,
301
+ provider: model.provider,
302
+ model: model.id,
303
+ usage: EMPTY_USAGE,
304
+ stopReason: message.content.some((content) => content.type === "toolCall")
305
+ ? "toolUse"
306
+ : "stop",
307
+ timestamp: timestamp + index,
308
+ };
309
+ });
310
+ }
311
+
312
+ function stripPromptSignal(prompt: AuthPrompt): PiSerializableAuthPrompt {
313
+ const { signal: _signal, ...serializable } = prompt;
314
+ return serializable as PiSerializableAuthPrompt;
315
+ }
316
+
317
+ export class PiAiRuntime {
318
+ private readonly models: Models;
319
+
320
+ constructor(options: PiAiRuntimeOptions) {
321
+ if (options.models) {
322
+ this.models = options.models;
323
+ return;
324
+ }
325
+ if (!options.dataDir && !options.credentials) {
326
+ throw new Error("PiAiRuntime requires a dataDir or an injected credential store.");
327
+ }
328
+ const credentials = options.credentials ?? new PiFileCredentialStore(options.dataDir!);
329
+ const modelsStore = options.modelsStore ?? (options.dataDir ? new PiFileModelsStore(options.dataDir) : undefined);
330
+ this.models = createGloomberbPiModels({
331
+ credentials,
332
+ modelsStore,
333
+ providerFactories: options.providerFactories,
334
+ });
335
+ }
336
+
337
+ async getCatalog(options: {
338
+ refresh?: boolean;
339
+ allowNetwork?: boolean;
340
+ force?: boolean;
341
+ signal?: AbortSignal;
342
+ } = {}): Promise<PiCatalog> {
343
+ const refreshErrors: Record<string, string> = {};
344
+ if (options.refresh) {
345
+ const result = await this.models.refresh({
346
+ allowNetwork: options.allowNetwork,
347
+ force: options.force,
348
+ signal: options.signal,
349
+ });
350
+ for (const [providerId, error] of result.errors) {
351
+ refreshErrors[providerId] = sanitizeErrorMessage(error);
352
+ }
353
+ }
354
+
355
+ const providers = await Promise.all(this.models.getProviders().map((provider) => this.getProviderSummary(provider.id)));
356
+ return { providers, refreshErrors };
357
+ }
358
+
359
+ async getProviderSummary(providerId: string): Promise<PiProviderSummary> {
360
+ const provider = this.models.getProvider(providerId);
361
+ if (!provider) throw new PiRuntimeError("provider_not_found", `Unknown AI provider: ${providerId}`);
362
+ if (!isAiProviderId(provider.id)) {
363
+ throw new PiRuntimeError("provider_not_found", `Unsupported AI provider: ${provider.id}`);
364
+ }
365
+ const definition = getAiProviderDefinition(provider.id);
366
+ if (!definition) {
367
+ throw new PiRuntimeError("provider_not_found", `Unsupported AI provider: ${providerId}`);
368
+ }
369
+
370
+ const authMethods: PiAuthMethodSummary[] = [];
371
+ if (provider.auth.oauth) {
372
+ authMethods.push({
373
+ type: "oauth",
374
+ label: provider.auth.oauth.loginLabel ?? provider.auth.oauth.name,
375
+ canLogin: true,
376
+ });
377
+ }
378
+ if (provider.auth.apiKey) {
379
+ authMethods.push({
380
+ type: "api_key",
381
+ label: provider.auth.apiKey.name,
382
+ canLogin: provider.auth.apiKey.login !== undefined,
383
+ });
384
+ }
385
+
386
+ let connection: PiProviderConnection;
387
+ let availableIds = new Set<string>();
388
+ try {
389
+ // getAuth performs the real OAuth refresh path. checkAuth only reports
390
+ // that a stored token exists, even when it is expired or revoked.
391
+ const auth = await this.models.getAuth(provider.id);
392
+ const authCheck = auth ? await this.models.checkAuth(provider.id) : undefined;
393
+ const disconnectable = authCheck?.type === "oauth" || auth?.source === "stored credential";
394
+ connection = auth
395
+ ? {
396
+ state: "connected",
397
+ type: authCheck?.type ?? (provider.auth.oauth ? "oauth" : "api_key"),
398
+ source: auth.source,
399
+ origin: disconnectable ? "stored" : "external",
400
+ disconnectable,
401
+ }
402
+ : { state: "not_connected" };
403
+ if (auth) {
404
+ availableIds = new Set((await this.models.getAvailable(provider.id)).map((model) => model.id));
405
+ }
406
+ } catch (error) {
407
+ connection = { state: "error", message: sanitizeErrorMessage(error) };
408
+ }
409
+
410
+ return {
411
+ id: definition.id,
412
+ label: definition.name,
413
+ name: provider.name,
414
+ defaultModelId: definition.preferredModelIds.find((modelId) => availableIds.has(modelId))
415
+ ?? definition.preferredModelIds[0],
416
+ authMethods,
417
+ connection,
418
+ models: provider.getModels().map((model) => modelSummary(model, availableIds.has(model.id))),
419
+ };
420
+ }
421
+
422
+ async login(request: PiLoginRequest, interaction: PiLoginInteraction): Promise<PiProviderSummary> {
423
+ const providerId = request.providerId;
424
+ if (!this.models.getProvider(providerId)) {
425
+ throw new PiRuntimeError("provider_not_found", `Unknown AI provider: ${request.providerId}`);
426
+ }
427
+ const authInteraction: AuthInteraction = {
428
+ signal: interaction.signal,
429
+ notify: (event) => interaction.notify(event),
430
+ prompt: (prompt) => interaction.prompt(stripPromptSignal(prompt), prompt.signal),
431
+ };
432
+ try {
433
+ // Deliberately discard Pi's Credential return value. Credentials remain
434
+ // inside the backend store and only non-secret status crosses the host API.
435
+ await this.models.login(providerId, request.type, authInteraction);
436
+ return await this.getProviderSummary(providerId);
437
+ } catch (error) {
438
+ if (interaction.signal?.aborted) throw new PiRunCancelledError();
439
+ throw new PiRuntimeError("login_failed", sanitizeErrorMessage(error), { cause: error });
440
+ }
441
+ }
442
+
443
+ async logout(providerId: string): Promise<PiProviderSummary> {
444
+ if (!this.models.getProvider(providerId)) {
445
+ throw new PiRuntimeError("provider_not_found", `Unknown AI provider: ${providerId}`);
446
+ }
447
+ await this.models.logout(providerId);
448
+ return this.getProviderSummary(providerId);
449
+ }
450
+
451
+ async resolveModel(selection: PiModelSelection): Promise<Model<Api>> {
452
+ const providerId = selection.providerId;
453
+ const provider = this.models.getProvider(providerId);
454
+ if (!provider) throw new PiRuntimeError("provider_not_found", `Unknown AI provider: ${selection.providerId}`);
455
+ const definition = getAiProviderDefinition(providerId);
456
+ if (!definition) {
457
+ throw new PiRuntimeError("provider_not_found", `Unsupported AI provider: ${selection.providerId}`);
458
+ }
459
+ const auth = await this.models.getAuth(providerId).catch((error) => {
460
+ throw new PiRuntimeError("provider_not_configured", sanitizeErrorMessage(error), { cause: error });
461
+ });
462
+ if (!auth) {
463
+ throw new PiRuntimeError("provider_not_configured", `${provider.name} is not connected.`);
464
+ }
465
+ const available = await this.models.getAvailable(providerId);
466
+ const requestedModelId = selection.modelId?.trim();
467
+ const model = requestedModelId
468
+ ? this.models.getModel(providerId, requestedModelId)
469
+ : definition.preferredModelIds
470
+ .map((modelId) => available.find((candidate) => candidate.id === modelId))
471
+ .find((candidate): candidate is Model<Api> => candidate !== undefined);
472
+ if (!model) {
473
+ if (!requestedModelId) {
474
+ throw new PiRuntimeError(
475
+ "model_unavailable",
476
+ `${definition.name} has no curated default available for the connected account. Choose an available model explicitly.`,
477
+ );
478
+ }
479
+ throw new PiRuntimeError("model_not_found", `Unknown ${provider.name} model: ${requestedModelId}`);
480
+ }
481
+ if (!available.some((candidate) => candidate.id === model.id)) {
482
+ throw new PiRuntimeError("model_unavailable", `${model.name} is not available for the connected ${provider.name} account.`);
483
+ }
484
+ return model;
485
+ }
486
+
487
+ run(request: PiPromptRequest): PiRunController<PiPromptResult> {
488
+ const controller = new AbortController();
489
+ const abortFromCaller = () => controller.abort();
490
+ request.signal?.addEventListener("abort", abortFromCaller, { once: true });
491
+
492
+ const done = (async () => {
493
+ try {
494
+ if (controller.signal.aborted) throw new PiRunCancelledError();
495
+ const model = await this.resolveModel(request);
496
+ if (controller.signal.aborted) throw new PiRunCancelledError();
497
+
498
+ const context: Context = {
499
+ systemPrompt: request.systemPrompt,
500
+ messages: [
501
+ ...toPiMessages(request.messages, model),
502
+ { role: "user", content: request.prompt, timestamp: Date.now() },
503
+ ],
504
+ tools: request.tools,
505
+ };
506
+ const stream = this.models.streamSimple(model, context, {
507
+ reasoning: request.reasoning,
508
+ sessionId: request.sessionId,
509
+ signal: controller.signal,
510
+ });
511
+
512
+ let cumulativeText = "";
513
+ let terminalMessage: AssistantMessage | null = null;
514
+ let terminalError: AssistantMessage | null = null;
515
+ for await (const event of stream) {
516
+ request.onEvent?.(event);
517
+ if (event.type === "text_delta") {
518
+ cumulativeText += event.delta;
519
+ request.onChunk?.(cumulativeText);
520
+ } else if (event.type === "done") {
521
+ terminalMessage = event.message;
522
+ } else if (event.type === "error") {
523
+ terminalError = event.error;
524
+ }
525
+ }
526
+
527
+ const message = terminalMessage ?? terminalError ?? await stream.result();
528
+ if (controller.signal.aborted || message.stopReason === "aborted") throw new PiRunCancelledError();
529
+ if (message.stopReason === "error") {
530
+ throw new PiRuntimeError("request_failed", sanitizeErrorMessage(message.errorMessage ?? "AI request failed."));
531
+ }
532
+ const finalText = assistantText(message);
533
+ if (finalText !== cumulativeText) request.onChunk?.(finalText);
534
+ return { text: finalText, message };
535
+ } catch (error) {
536
+ if (controller.signal.aborted || isPiRunCancelled(error)) throw new PiRunCancelledError();
537
+ if (error instanceof PiRuntimeError) throw error;
538
+ throw new PiRuntimeError("request_failed", sanitizeErrorMessage(error), { cause: error });
539
+ } finally {
540
+ request.signal?.removeEventListener("abort", abortFromCaller);
541
+ }
542
+ })();
543
+
544
+ return {
545
+ done,
546
+ cancel: () => controller.abort(),
547
+ };
548
+ }
549
+
550
+ runText(request: PiPromptRequest): PiTextRunController {
551
+ const run = this.run(request);
552
+ return {
553
+ done: run.done.then((result) => result.text),
554
+ cancel: run.cancel,
555
+ };
556
+ }
557
+
558
+ async createAgent(request: PiCreateAgentRequest): Promise<Agent> {
559
+ const model = await this.resolveModel(request);
560
+ return new Agent({
561
+ ...request.agentOptions,
562
+ streamFn: (nextModel, context, options) => this.models.streamSimple(nextModel, context, options),
563
+ initialState: {
564
+ systemPrompt: request.systemPrompt ?? "",
565
+ model,
566
+ thinkingLevel: request.thinkingLevel ?? "off",
567
+ messages: [
568
+ ...toPiMessages(request.messages, model),
569
+ ...toPiAgentMessages(request.agentMessages, model),
570
+ ],
571
+ tools: request.tools ?? [],
572
+ },
573
+ });
574
+ }
575
+
576
+ runAgent(request: PiAgentRunRequest): PiRunController<PiAgentRunResult> {
577
+ let agent: Agent | null = null;
578
+ let cancelled = false;
579
+
580
+ const done = (async () => {
581
+ try {
582
+ if (cancelled) throw new PiRunCancelledError();
583
+ agent = await this.createAgent(request);
584
+ if (cancelled) throw new PiRunCancelledError();
585
+
586
+ let cumulativeText = "";
587
+ let resultMessages: AgentMessage[] = [];
588
+ agent.subscribe(async (event) => {
589
+ await request.onEvent?.(event);
590
+ if (event.type === "message_start" && isAssistantMessage(event.message)) {
591
+ cumulativeText = "";
592
+ request.onChunk?.(cumulativeText);
593
+ } else if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
594
+ cumulativeText += event.assistantMessageEvent.delta;
595
+ request.onChunk?.(cumulativeText);
596
+ } else if (event.type === "agent_end") {
597
+ resultMessages = event.messages;
598
+ }
599
+ });
600
+
601
+ await agent.prompt(request.prompt);
602
+ if (cancelled || agent.state.errorMessage?.toLowerCase().includes("abort")) throw new PiRunCancelledError();
603
+ if (agent.state.errorMessage) {
604
+ throw new PiRuntimeError("request_failed", sanitizeErrorMessage(agent.state.errorMessage));
605
+ }
606
+ const finalAssistant = resultMessages.findLast(isAssistantMessage);
607
+ const finalText = finalAssistant ? assistantText(finalAssistant) : cumulativeText;
608
+ if (finalText && finalText !== cumulativeText) request.onChunk?.(finalText);
609
+ return {
610
+ text: finalText,
611
+ messages: [
612
+ { role: "user", content: request.prompt, timestamp: Date.now() },
613
+ ...resultMessages,
614
+ ],
615
+ };
616
+ } catch (error) {
617
+ if (cancelled || isPiRunCancelled(error)) throw new PiRunCancelledError();
618
+ if (error instanceof PiRuntimeError) throw error;
619
+ throw new PiRuntimeError("request_failed", sanitizeErrorMessage(error), { cause: error });
620
+ }
621
+ })();
622
+
623
+ return {
624
+ done,
625
+ cancel: () => {
626
+ cancelled = true;
627
+ agent?.abort();
628
+ },
629
+ };
630
+ }
631
+ }
632
+
633
+ export function createPiAiRuntime(options: PiAiRuntimeOptions): PiAiRuntime {
634
+ return new PiAiRuntime(options);
635
+ }
@@ -0,0 +1,68 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ AI_PROVIDER_IDS,
4
+ getAiProvider,
5
+ getAiProviderDefinitions,
6
+ migrateLegacyAiProviderId,
7
+ setDetectedProviders,
8
+ } from "./providers";
9
+ import {
10
+ GLOOMBERB_PI_PROVIDER_FACTORIES,
11
+ GLOOMBERB_PI_PROVIDER_IDS,
12
+ } from "./pi/providers";
13
+
14
+ describe("Pi provider catalog", () => {
15
+ test("exposes exactly the curated canonical providers in a stable order", () => {
16
+ expect(AI_PROVIDER_IDS).toEqual([
17
+ "anthropic",
18
+ "openai-codex",
19
+ "openai",
20
+ "google",
21
+ "github-copilot",
22
+ "xai",
23
+ "openrouter",
24
+ ]);
25
+ expect(GLOOMBERB_PI_PROVIDER_IDS).toEqual(AI_PROVIDER_IDS);
26
+ expect(new Set(AI_PROVIDER_IDS).size).toBe(AI_PROVIDER_IDS.length);
27
+ expect(AI_PROVIDER_IDS).not.toContain("opencode");
28
+ expect(AI_PROVIDER_IDS).not.toContain("pi");
29
+ });
30
+
31
+ test("keeps UI metadata and curated defaults aligned with Pi models", () => {
32
+ const definitions = getAiProviderDefinitions();
33
+ const piProviders = GLOOMBERB_PI_PROVIDER_FACTORIES.map((createProvider) => createProvider());
34
+
35
+ expect(definitions.map((provider) => provider.id)).toEqual([...AI_PROVIDER_IDS]);
36
+ for (const definition of definitions) {
37
+ const provider = piProviders.find((candidate) => candidate.id === definition.id);
38
+ expect(provider).toBeDefined();
39
+ expect(definition.name.length).toBeGreaterThan(0);
40
+ expect(definition.outputModes).toEqual(["plain", "structured", "screener"]);
41
+ expect(definition.preferredModelIds.length).toBeGreaterThan(0);
42
+ expect(provider?.getModels().some((model) => (
43
+ definition.preferredModelIds.includes(model.id)
44
+ ))).toBe(true);
45
+ expect(definition).not.toHaveProperty("command");
46
+ expect(definition).not.toHaveProperty("buildArgs");
47
+ }
48
+ });
49
+
50
+ test("uses legacy ids only to migrate persisted selections", () => {
51
+ expect(migrateLegacyAiProviderId("claude")).toBe("anthropic");
52
+ expect(migrateLegacyAiProviderId("codex")).toBe("openai-codex");
53
+ expect(migrateLegacyAiProviderId("gemini")).toBe("google");
54
+ expect(migrateLegacyAiProviderId("openai")).toBe("openai");
55
+ expect(migrateLegacyAiProviderId("opencode")).toBe("opencode");
56
+
57
+ setDetectedProviders(getAiProviderDefinitions().map((definition) => ({
58
+ id: definition.id,
59
+ name: definition.name,
60
+ available: false,
61
+ status: "not_authenticated",
62
+ outputModes: [...definition.outputModes],
63
+ })));
64
+ expect(getAiProvider("claude")?.id).toBe("anthropic");
65
+ expect(getAiProvider("codex")?.id).toBe("openai-codex");
66
+ setDetectedProviders(null);
67
+ });
68
+ });