ompchamber 2.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/README.md +31 -11
  2. package/bunfig.toml +11 -0
  3. package/dist/client/index.html +16 -5
  4. package/dist/client/index.js +1393 -0
  5. package/dist/client/static/js/index-8t70jhyj.js +5149 -0
  6. package/dist/client/static/js/index-n4wkqj7t.css +1 -0
  7. package/index.html +54 -0
  8. package/package.json +9 -10
  9. package/public/apple-touch-icon.png +0 -0
  10. package/public/icon-192x192.png +0 -0
  11. package/public/icon.svg +60 -0
  12. package/public/manifest.webmanifest +38 -0
  13. package/src/cli/lib/commands/serve.js +9 -5
  14. package/src/cli/lib/runtime.js +18 -7
  15. package/src/client/components/common/code-editor/handler.test.ts +274 -0
  16. package/src/client/components/common/code-editor/handler.ts +175 -0
  17. package/src/client/components/common/code-editor/index.tsx +185 -220
  18. package/src/client/components/common/code-surface/index.tsx +109 -12
  19. package/src/client/components/common/lazy-panels.tsx +5 -1
  20. package/src/client/components/common/provider-icon/glyphs.ts +118 -0
  21. package/src/client/components/common/provider-icon/index.tsx +85 -0
  22. package/src/client/components/common/viewport.ts +5 -3
  23. package/src/client/components/layout/RightActivityBar.tsx +2 -1
  24. package/src/client/components/layout/desktop-layout/TopNavbar.tsx +21 -4
  25. package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
  26. package/src/client/components/layout/desktop-layout/index.tsx +26 -4
  27. package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
  28. package/src/client/components/mobile/RightSidebar.tsx +11 -4
  29. package/src/client/components/mobile/SessionSidebar.tsx +10 -0
  30. package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +0 -1
  31. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -1
  32. package/src/client/components/mobile/mobile-session-sidebar/List.tsx +3 -0
  33. package/src/client/components/settings/categories/ProviderSettings.tsx +45 -4
  34. package/src/client/components/settings/categories/UsageSettings.tsx +22 -7
  35. package/src/client/components/settings/categories/provider-settings/AddModelModal.tsx +322 -0
  36. package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +152 -56
  37. package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +52 -1
  38. package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +133 -0
  39. package/src/client/components/settings/categories/provider-settings/DialectFields.tsx +136 -0
  40. package/src/client/components/settings/categories/provider-settings/EffortLadder.tsx +89 -0
  41. package/src/client/components/settings/categories/provider-settings/Header.tsx +2 -2
  42. package/src/client/components/settings/categories/provider-settings/LoginModal.tsx +2 -2
  43. package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +22 -2
  44. package/src/client/components/settings/categories/provider-settings/PresetPicker.tsx +82 -0
  45. package/src/client/components/settings/categories/provider-settings/PriceFields.tsx +107 -0
  46. package/src/client/components/settings/categories/provider-settings/ReconnectModal.tsx +43 -22
  47. package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +2 -2
  48. package/src/client/components/settings/categories/usage-settings/LimitsSection.tsx +115 -0
  49. package/src/client/components/settings/categories/usage-settings/ProviderCard.tsx +19 -26
  50. package/src/client/components/settings/categories/usage-settings/ProviderDetail.tsx +29 -22
  51. package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +60 -48
  52. package/src/client/components/settings/categories/usage-settings/format.ts +36 -0
  53. package/src/client/components/settings/categories/usage-settings/providers.ts +52 -28
  54. package/src/client/components/workspace/browser-panel/AddressBar.tsx +1 -10
  55. package/src/client/components/workspace/browser-panel/Screencast.tsx +4 -5
  56. package/src/client/components/workspace/browser-panel/index.tsx +6 -6
  57. package/src/client/components/workspace/browser-toolbar/index.tsx +22 -12
  58. package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +1 -1
  59. package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +1 -1
  60. package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +1 -1
  61. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Todo.tsx +1 -1
  62. package/src/client/components/workspace/diff-panel/Toolbar.tsx +71 -38
  63. package/src/client/components/workspace/diff-panel/index.tsx +139 -53
  64. package/src/client/components/workspace/editor/CommandPalette.test.ts +242 -0
  65. package/src/client/components/workspace/editor/CommandPalette.tsx +138 -0
  66. package/src/client/components/workspace/editor/FindWidget.test.ts +261 -0
  67. package/src/client/components/workspace/editor/FindWidget.tsx +219 -0
  68. package/src/client/components/workspace/editor/Tabs.tsx +2 -2
  69. package/src/client/components/workspace/editor/Toolbar.tsx +129 -26
  70. package/src/client/components/workspace/editor/index.tsx +180 -62
  71. package/src/client/components/workspace/file-explorer/Modals.tsx +79 -2
  72. package/src/client/components/workspace/file-explorer/TreeItem.tsx +43 -113
  73. package/src/client/components/workspace/model-dropdown/Item.tsx +13 -23
  74. package/src/client/components/workspace/model-dropdown/Panel.tsx +1 -1
  75. package/src/client/components/workspace/model-dropdown/Section.tsx +13 -4
  76. package/src/client/components/workspace/model-dropdown/index.tsx +7 -3
  77. package/src/client/components/workspace/todo-panel/PhaseGroup.tsx +76 -0
  78. package/src/client/components/workspace/todo-panel/Row.tsx +69 -0
  79. package/src/client/components/workspace/todo-panel/index.tsx +161 -0
  80. package/src/client/components/workspace/usage-panel/ProviderSelect.tsx +10 -5
  81. package/src/client/components/workspace/usage-panel/index.tsx +36 -18
  82. package/src/client/data/samples/dialogue-session.ts +1 -1
  83. package/src/client/data/samples/sample-tools/other.ts +1 -1
  84. package/src/client/data/settings/provider.ts +141 -9
  85. package/src/client/hooks/editor/find-harness.test-util.ts +170 -0
  86. package/src/client/hooks/editor/scroll-container.test.ts +102 -0
  87. package/src/client/hooks/editor/scroll-container.ts +26 -0
  88. package/src/client/hooks/editor/use-code-window.ts +2 -20
  89. package/src/client/hooks/editor/use-editor-find-write.test.ts +170 -0
  90. package/src/client/hooks/editor/use-editor-find.test.ts +118 -0
  91. package/src/client/hooks/editor/use-editor-find.ts +321 -0
  92. package/src/client/hooks/editor/use-editor-history.test.ts +216 -0
  93. package/src/client/hooks/editor/use-editor-history.ts +134 -0
  94. package/src/client/hooks/editor/use-file-editor.ts +24 -27
  95. package/src/client/hooks/editor/use-occurrences.test.ts +236 -0
  96. package/src/client/hooks/editor/use-occurrences.ts +98 -0
  97. package/src/client/hooks/models/use-provider-names.ts +1 -1
  98. package/src/client/hooks/settings/providers/actions.ts +337 -0
  99. package/src/client/hooks/settings/providers/api.ts +69 -0
  100. package/src/client/hooks/settings/providers/index.ts +138 -0
  101. package/src/client/hooks/settings/providers/messages.ts +77 -0
  102. package/src/client/hooks/settings/useUsageReport.ts +8 -3
  103. package/src/client/hooks/ui/theme.ts +3 -3
  104. package/src/client/hooks/workspace/file-tabs.ts +62 -11
  105. package/src/client/hooks/workspace/file-tree-actions.test.ts +348 -0
  106. package/src/client/hooks/workspace/file-tree-actions.ts +250 -0
  107. package/src/client/hooks/workspace/session-todos.ts +102 -0
  108. package/src/client/stubs/elkjs.ts +6 -1
  109. package/src/client/tailwind.css +30 -0
  110. package/src/server/index.ts +20 -2
  111. package/src/server/lib/assets/font-css.server.ts +86 -0
  112. package/src/server/lib/assets/fonts.server.ts +117 -0
  113. package/src/server/lib/bundler/css.ts +116 -0
  114. package/src/server/lib/lifecycle/shell-route.ts +17 -0
  115. package/src/server/lib/models/provider-listing.server.ts +257 -0
  116. package/src/server/lib/models/provider-registry.server.test.ts +122 -0
  117. package/src/server/lib/models/provider-registry.server.ts +76 -25
  118. package/src/server/lib/omp/config/models-config.ts +11 -0
  119. package/src/server/lib/omp/config/models-validation.test.ts +137 -0
  120. package/src/server/lib/omp/config/models-validation.ts +111 -2
  121. package/src/server/lib/omp/config/provider-seeds.test.ts +167 -0
  122. package/src/server/lib/omp/config/provider-seeds.ts +176 -0
  123. package/src/server/lib/omp/config/providers.ts +94 -100
  124. package/src/server/lib/omp/core/auth-credentials.ts +45 -0
  125. package/src/server/lib/omp/session/todos.ts +100 -0
  126. package/src/server/lib/omp/session/{usage.ts → usage/aggregate.ts} +1 -118
  127. package/src/server/lib/omp/session/usage/shaping.ts +130 -0
  128. package/src/server/lib/usage/deepseek.ts +4 -5
  129. package/src/server/lib/usage/kenari.ts +7 -6
  130. package/src/server/lib/usage/omp-usage.server.test.ts +126 -0
  131. package/src/server/lib/usage/omp-usage.server.ts +194 -0
  132. package/src/server/lib/usage/provider-key.ts +81 -3
  133. package/src/server/lib/usage/providers.server.test.ts +47 -0
  134. package/src/server/lib/usage/providers.server.ts +208 -0
  135. package/src/server/lib/usage/vendor-balance.server.test.ts +104 -0
  136. package/src/server/lib/usage/vendor-balance.server.ts +284 -0
  137. package/src/server/plugins/shell.server.ts +56 -0
  138. package/src/server/plugins/ssr.ts +24 -36
  139. package/src/server/plugins/static.test.ts +26 -29
  140. package/src/server/plugins/static.ts +40 -19
  141. package/src/server/routes/fs/action-create.test.ts +90 -0
  142. package/src/server/routes/fs/action.ts +23 -0
  143. package/src/server/routes/omp/index.ts +2 -0
  144. package/src/server/routes/omp/session-todos.ts +80 -0
  145. package/src/server/routes/settings/index.ts +2 -0
  146. package/src/server/routes/settings/provider-model.ts +107 -0
  147. package/src/server/routes/settings/provider-models.ts +65 -202
  148. package/src/server/routes/settings/providers.ts +22 -7
  149. package/src/server/routes/settings/usage.ts +125 -45
  150. package/src/server/routes/telemetry/tokens.ts +2 -1
  151. package/src/shared/lib/chat/{todo-parser.ts → todo/parser.ts} +17 -0
  152. package/src/shared/lib/chat/todo/progress.ts +97 -0
  153. package/src/shared/lib/chat/todo/snapshot.test.ts +156 -0
  154. package/src/shared/lib/chat/todo/snapshot.ts +215 -0
  155. package/src/shared/lib/code/editor/commands.test.ts +155 -0
  156. package/src/shared/lib/code/editor/commands.ts +260 -0
  157. package/src/shared/lib/code/editor/edits.test.ts +100 -0
  158. package/src/shared/lib/code/editor/edits.ts +122 -0
  159. package/src/shared/lib/code/editor/find-status.test.ts +50 -0
  160. package/src/shared/lib/code/editor/find-status.ts +36 -0
  161. package/src/shared/lib/code/editor/find.test.ts +203 -0
  162. package/src/shared/lib/code/editor/find.ts +224 -0
  163. package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.test.ts +1 -1
  164. package/src/shared/lib/code/editor/keymap.test.ts +109 -0
  165. package/src/shared/lib/code/editor/keymap.ts +151 -0
  166. package/src/shared/lib/code/editor/layers.test.ts +77 -0
  167. package/src/shared/lib/code/editor/layers.ts +67 -0
  168. package/src/shared/lib/code/editor/line-edits.test.ts +131 -0
  169. package/src/shared/lib/code/editor/line-edits.ts +159 -0
  170. package/src/shared/lib/code/editor/lines.test.ts +81 -0
  171. package/src/shared/lib/code/editor/lines.ts +83 -0
  172. package/src/shared/lib/code/editor/marks.test.ts +86 -0
  173. package/src/shared/lib/code/editor/marks.ts +106 -0
  174. package/src/shared/lib/code/editor/occurrences.test.ts +117 -0
  175. package/src/shared/lib/code/editor/occurrences.ts +111 -0
  176. package/src/shared/lib/code/editor/palette.test.ts +91 -0
  177. package/src/shared/lib/code/editor/palette.ts +92 -0
  178. package/src/shared/lib/code/editor/reset-css.test.ts +35 -0
  179. package/src/shared/lib/code/editor/reset-css.ts +32 -0
  180. package/src/shared/lib/code/editor/text-commands.test.ts +178 -0
  181. package/src/shared/lib/code/editor/text-commands.ts +154 -0
  182. package/src/shared/lib/code/syntax-highlight.test.ts +75 -0
  183. package/src/shared/lib/code/syntax-highlight.ts +29 -17
  184. package/src/shared/lib/code/windowed-highlight.ts +52 -1
  185. package/src/shared/lib/fs/diff-parser.test.ts +116 -0
  186. package/src/shared/lib/fs/diff-parser.ts +86 -33
  187. package/src/shared/lib/models/{provider-connection.ts → provider/connection.ts} +37 -0
  188. package/src/shared/lib/models/provider/dialect.ts +179 -0
  189. package/src/shared/lib/models/provider/glyph.test.ts +116 -0
  190. package/src/shared/lib/models/provider/glyph.ts +200 -0
  191. package/src/shared/lib/models/{provider-models.ts → provider/models.ts} +91 -6
  192. package/src/shared/lib/models/provider/presets.test.ts +172 -0
  193. package/src/shared/lib/models/provider/presets.ts +83 -0
  194. package/src/shared/lib/ui/key-binding.test.ts +115 -0
  195. package/src/shared/lib/ui/key-binding.ts +143 -0
  196. package/src/shared/lib/util/platform.test.ts +87 -0
  197. package/src/shared/lib/util/platform.ts +30 -0
  198. package/src/shared/lib/workspace/file-tab-id.ts +29 -0
  199. package/src/shared/lib/workspace/keymap.test.ts +82 -0
  200. package/src/shared/lib/workspace/keymap.ts +30 -0
  201. package/src/shared/lib/workspace/refresh-cadence.ts +9 -0
  202. package/src/shared/lib/workspace/right-panels.ts +8 -0
  203. package/src/shared/lib/workspace/session-state/store.ts +0 -1
  204. package/src/shared/types/index.ts +1 -0
  205. package/src/shared/types/settings/provider.ts +81 -0
  206. package/src/shared/types/settings/usage.ts +88 -14
  207. package/src/shared/types/todo.ts +108 -0
  208. package/tsconfig.json +3 -1
  209. package/dist/client/static/css/index.5f630421b7.css +0 -2
  210. package/dist/client/static/font/KaTeX_AMS-Regular.a79f1c3119.woff2 +0 -0
  211. package/dist/client/static/font/KaTeX_Caligraphic-Bold.ec17d13264.woff2 +0 -0
  212. package/dist/client/static/font/KaTeX_Caligraphic-Regular.55fac25845.woff2 +0 -0
  213. package/dist/client/static/font/KaTeX_Fraktur-Bold.d42a5579b0.woff2 +0 -0
  214. package/dist/client/static/font/KaTeX_Fraktur-Regular.d3c882a649.woff2 +0 -0
  215. package/dist/client/static/font/KaTeX_Main-Bold.c3fb5ac22f.woff2 +0 -0
  216. package/dist/client/static/font/KaTeX_Main-BoldItalic.6f2bb1dff2.woff2 +0 -0
  217. package/dist/client/static/font/KaTeX_Main-Italic.8916142bec.woff2 +0 -0
  218. package/dist/client/static/font/KaTeX_Main-Regular.0462f03bdf.woff2 +0 -0
  219. package/dist/client/static/font/KaTeX_Math-BoldItalic.572d331f69.woff2 +0 -0
  220. package/dist/client/static/font/KaTeX_Math-Italic.f28c23acad.woff2 +0 -0
  221. package/dist/client/static/font/KaTeX_SansSerif-Bold.8c5b5494b6.woff2 +0 -0
  222. package/dist/client/static/font/KaTeX_SansSerif-Italic.3b1e59b3ba.woff2 +0 -0
  223. package/dist/client/static/font/KaTeX_SansSerif-Regular.ba21ed5f84.woff2 +0 -0
  224. package/dist/client/static/font/KaTeX_Script-Regular.03e9641d6f.woff2 +0 -0
  225. package/dist/client/static/font/KaTeX_Size1-Regular.eae34984b3.woff2 +0 -0
  226. package/dist/client/static/font/KaTeX_Size2-Regular.5916a24fa3.woff2 +0 -0
  227. package/dist/client/static/font/KaTeX_Size4-Regular.10d95fd3a2.woff2 +0 -0
  228. package/dist/client/static/font/KaTeX_Typewriter-Regular.a8709e3622.woff2 +0 -0
  229. package/dist/client/static/font/fira-code-latin-400-normal.2655b10c55.woff2 +0 -0
  230. package/dist/client/static/font/fira-code-latin-500-normal.92a991f204.woff2 +0 -0
  231. package/dist/client/static/font/fira-code-latin-600-normal.43788eb7f1.woff2 +0 -0
  232. package/dist/client/static/font/fira-code-latin-700-normal.2667215d33.woff2 +0 -0
  233. package/dist/client/static/js/77.0d81c74cf0.js +0 -139
  234. package/dist/client/static/js/77.0d81c74cf0.js.LICENSE.txt +0 -8
  235. package/dist/client/static/js/async/0.8e59f2eb5b.js +0 -1
  236. package/dist/client/static/js/async/0k.5852e6b4fa.js +0 -1
  237. package/dist/client/static/js/async/0w.9ed36d5860.js +0 -1
  238. package/dist/client/static/js/async/1.f387c12b63.js +0 -1
  239. package/dist/client/static/js/async/12.d091f91c99.js +0 -1
  240. package/dist/client/static/js/async/13.8f810a8a6c.js +0 -1
  241. package/dist/client/static/js/async/19.6fe909d55a.js +0 -160
  242. package/dist/client/static/js/async/1b.23f1141a38.js +0 -1
  243. package/dist/client/static/js/async/1k.973e8e21fc.js +0 -2
  244. package/dist/client/static/js/async/1k.973e8e21fc.js.LICENSE.txt +0 -11
  245. package/dist/client/static/js/async/1p.0d5cddda96.js +0 -1
  246. package/dist/client/static/js/async/2.b589f1733b.js +0 -1
  247. package/dist/client/static/js/async/2v.96165101de.js +0 -1
  248. package/dist/client/static/js/async/3.0cacc08423.js +0 -1
  249. package/dist/client/static/js/async/37.b28b2f61cd.js +0 -1
  250. package/dist/client/static/js/async/3d.5085fb913f.js +0 -1
  251. package/dist/client/static/js/async/3j.357c80ff38.js +0 -2
  252. package/dist/client/static/js/async/3j.357c80ff38.js.LICENSE.txt +0 -11
  253. package/dist/client/static/js/async/3p.bcf32a0983.js +0 -1
  254. package/dist/client/static/js/async/3r.3a69e98ee0.js +0 -64
  255. package/dist/client/static/js/async/3u.1228460e2a.js +0 -1
  256. package/dist/client/static/js/async/3y.3530b824ad.js +0 -198
  257. package/dist/client/static/js/async/3y.3530b824ad.js.LICENSE.txt +0 -5
  258. package/dist/client/static/js/async/4.9442eeb472.js +0 -1
  259. package/dist/client/static/js/async/4e.678285ca55.js +0 -1
  260. package/dist/client/static/js/async/4k.33a0c7eeb4.js +0 -1
  261. package/dist/client/static/js/async/4m.928a1dde85.js +0 -31
  262. package/dist/client/static/js/async/4m.928a1dde85.js.LICENSE.txt +0 -11
  263. package/dist/client/static/js/async/4q.aad20dab25.js +0 -1
  264. package/dist/client/static/js/async/5.ebaf8b89cc.js +0 -36
  265. package/dist/client/static/js/async/51.95bcae98af.js +0 -2
  266. package/dist/client/static/js/async/51.95bcae98af.js.LICENSE.txt +0 -6
  267. package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
  268. package/dist/client/static/js/async/5j.d6407c4248.js +0 -287
  269. package/dist/client/static/js/async/5o.88316d4460.js +0 -62
  270. package/dist/client/static/js/async/6.f034c9b528.js +0 -1
  271. package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
  272. package/dist/client/static/js/async/6o.5b1a06a382.js +0 -1
  273. package/dist/client/static/js/async/7.e4a9288dd0.js +0 -1
  274. package/dist/client/static/js/async/73.308dff4da8.js +0 -1
  275. package/dist/client/static/js/async/7e.a1cc9bfd88.js +0 -1
  276. package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
  277. package/dist/client/static/js/async/7l.fadbbc0583.js +0 -1
  278. package/dist/client/static/js/async/8.5067439d8c.js +0 -1
  279. package/dist/client/static/js/async/8j.ea110663fb.js +0 -1
  280. package/dist/client/static/js/async/a.bce4542392.js +0 -1
  281. package/dist/client/static/js/async/aa.65d3852576.js +0 -1
  282. package/dist/client/static/js/async/ar.9303e5f2e8.js +0 -2
  283. package/dist/client/static/js/async/ar.9303e5f2e8.js.LICENSE.txt +0 -11
  284. package/dist/client/static/js/async/az.d656062b0f.js +0 -1
  285. package/dist/client/static/js/async/b.b4f6f06515.js +0 -1
  286. package/dist/client/static/js/async/bi.09688ba75c.js +0 -1
  287. package/dist/client/static/js/async/bq.586bc738d1.js +0 -1
  288. package/dist/client/static/js/async/c.797d933882.js +0 -1
  289. package/dist/client/static/js/async/c0.0af1e640d3.js +0 -24
  290. package/dist/client/static/js/async/ca.8a52cfd447.js +0 -1
  291. package/dist/client/static/js/async/cf.d34fff5d67.js +0 -1
  292. package/dist/client/static/js/async/ct.b74928f7a3.js +0 -1
  293. package/dist/client/static/js/async/cx.3cb0242a3a.js +0 -226
  294. package/dist/client/static/js/async/d.a5fc8fe1b3.js +0 -1
  295. package/dist/client/static/js/async/da.f62c70f6c8.js +0 -1
  296. package/dist/client/static/js/async/dp.1725d6432c.js +0 -1
  297. package/dist/client/static/js/async/e.93214d622b.js +0 -79
  298. package/dist/client/static/js/async/f.ab50467719.js +0 -19
  299. package/dist/client/static/js/async/f.ab50467719.js.LICENSE.txt +0 -12
  300. package/dist/client/static/js/async/f7.46971e9d6d.js +0 -2
  301. package/dist/client/static/js/async/f7.46971e9d6d.js.LICENSE.txt +0 -6
  302. package/dist/client/static/js/async/f8.bbf0dbae70.js +0 -1
  303. package/dist/client/static/js/async/g.cb81980498.js +0 -2
  304. package/dist/client/static/js/async/g.cb81980498.js.LICENSE.txt +0 -11
  305. package/dist/client/static/js/async/h.4e790471e4.js +0 -27
  306. package/dist/client/static/js/async/h1.393f066d09.js +0 -1
  307. package/dist/client/static/js/async/h3.216e9798ab.js +0 -148
  308. package/dist/client/static/js/async/hd.0da931b622.js +0 -1
  309. package/dist/client/static/js/async/hk.ba7fb04d41.js +0 -1
  310. package/dist/client/static/js/async/hn.b645041e00.js +0 -1
  311. package/dist/client/static/js/async/ho.475a5e1ae4.js +0 -1
  312. package/dist/client/static/js/async/hp.e9fe2e395a.js +0 -2
  313. package/dist/client/static/js/async/hp.e9fe2e395a.js.LICENSE.txt +0 -17
  314. package/dist/client/static/js/async/hs.e7c59d8090.js +0 -41
  315. package/dist/client/static/js/async/i.7c148e8440.js +0 -1
  316. package/dist/client/static/js/async/id.573df20f29.js +0 -1
  317. package/dist/client/static/js/async/j.ab925882d8.js +0 -1
  318. package/dist/client/static/js/async/jd.63886a1f09.js +0 -1
  319. package/dist/client/static/js/async/jg.d86ebd4013.js +0 -86
  320. package/dist/client/static/js/async/jl.874d352278.js +0 -1
  321. package/dist/client/static/js/async/jll.11313b455a.js +0 -1
  322. package/dist/client/static/js/async/js.67182759ef.js +0 -1
  323. package/dist/client/static/js/async/jsc.23b70d6f40.js +0 -1
  324. package/dist/client/static/js/async/jse.334588ff5d.js +0 -1
  325. package/dist/client/static/js/async/jz.72eb929106.js +0 -77
  326. package/dist/client/static/js/async/k.3210286401.js +0 -217
  327. package/dist/client/static/js/async/ka.c1990ecaa3.js +0 -115
  328. package/dist/client/static/js/async/katex-vendor.de6a87c308.js +0 -1
  329. package/dist/client/static/js/async/ku.29c8a59d0c.js +0 -1
  330. package/dist/client/static/js/async/ky.8dab1dade0.js +0 -1
  331. package/dist/client/static/js/async/l.12dd34e1be.js +0 -1
  332. package/dist/client/static/js/async/l9.949710adaf.js +0 -83
  333. package/dist/client/static/js/async/l9.949710adaf.js.LICENSE.txt +0 -13
  334. package/dist/client/static/js/async/ls.9d9a01026c.js +0 -104
  335. package/dist/client/static/js/async/m.eb2e6752e2.js +0 -28
  336. package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +0 -11
  337. package/dist/client/static/js/async/m3.d2b05f9604.js +0 -2
  338. package/dist/client/static/js/async/m3.d2b05f9604.js.LICENSE.txt +0 -6
  339. package/dist/client/static/js/async/ma.2c261b04dd.js +0 -2
  340. package/dist/client/static/js/async/ma.2c261b04dd.js.LICENSE.txt +0 -6
  341. package/dist/client/static/js/async/me.8ace414f80.js +0 -23
  342. package/dist/client/static/js/async/ms.98960af4eb.js +0 -67
  343. package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +0 -12
  344. package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
  345. package/dist/client/static/js/async/n.a9f1c51701.js +0 -1
  346. package/dist/client/static/js/async/na.a36cd532d6.js +0 -1
  347. package/dist/client/static/js/async/nk.fc7e58325e.js +0 -1
  348. package/dist/client/static/js/async/o.36353dfb88.js +0 -1
  349. package/dist/client/static/js/async/oc.b73042e8bd.js +0 -8
  350. package/dist/client/static/js/async/og.8eb577893a.js +0 -193
  351. package/dist/client/static/js/async/p.48ecfcd14e.js +0 -1
  352. package/dist/client/static/js/async/px.fc6877924b.js +0 -1
  353. package/dist/client/static/js/async/q.22588845a9.js +0 -21
  354. package/dist/client/static/js/async/q.22588845a9.js.LICENSE.txt +0 -6
  355. package/dist/client/static/js/async/q5.97dc1481b6.js +0 -286
  356. package/dist/client/static/js/async/qs.2a9ddb3c72.js +0 -1
  357. package/dist/client/static/js/async/r.51010ad87c.js +0 -1
  358. package/dist/client/static/js/async/rd.cb6bf495bb.js +0 -2
  359. package/dist/client/static/js/async/rd.cb6bf495bb.js.LICENSE.txt +0 -4
  360. package/dist/client/static/js/async/s.0f395cdda5.js +0 -94
  361. package/dist/client/static/js/async/t.4dca0e2ba8.js +0 -1
  362. package/dist/client/static/js/async/t9.d44f57d518.js +0 -1
  363. package/dist/client/static/js/async/ta.973534bbec.js +0 -1
  364. package/dist/client/static/js/async/tr.e878658140.js +0 -309
  365. package/dist/client/static/js/async/u.0871ac5b22.js +0 -1
  366. package/dist/client/static/js/async/ud.3065bfee31.js +0 -2
  367. package/dist/client/static/js/async/ud.3065bfee31.js.LICENSE.txt +0 -4
  368. package/dist/client/static/js/async/up.ea990da0ff.js +0 -32
  369. package/dist/client/static/js/async/uw.8890abbabc.js +0 -1
  370. package/dist/client/static/js/async/v.7d28d3c241.js +0 -34
  371. package/dist/client/static/js/async/vp.9b13bf8610.js +0 -1
  372. package/dist/client/static/js/async/vt.42416b707d.js +0 -1
  373. package/dist/client/static/js/async/w.08ac210f60.js +0 -1
  374. package/dist/client/static/js/async/wt.ad92393a90.js +0 -1
  375. package/dist/client/static/js/async/x.563f72ca6b.js +0 -1
  376. package/dist/client/static/js/async/x0.475516418f.js +0 -1
  377. package/dist/client/static/js/async/x1.da8687159b.js +0 -90
  378. package/dist/client/static/js/async/xe.da8dcc2015.js +0 -2
  379. package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +0 -11
  380. package/dist/client/static/js/async/xl.a3c562a58c.js +0 -1
  381. package/dist/client/static/js/async/xn.6510c0abe2.js +0 -2
  382. package/dist/client/static/js/async/xn.6510c0abe2.js.LICENSE.txt +0 -6
  383. package/dist/client/static/js/async/xn1.83a7784fe1.js +0 -10
  384. package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +0 -11
  385. package/dist/client/static/js/async/xnl.feb0021e50.js +0 -2
  386. package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +0 -12
  387. package/dist/client/static/js/async/y.1a9ed7a5d8.js +0 -1
  388. package/dist/client/static/js/async/y6.43c57defe4.js +0 -1
  389. package/dist/client/static/js/async/yb.8c1342401d.js +0 -1
  390. package/dist/client/static/js/async/ybq.4491f2f2dc.js +0 -1
  391. package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
  392. package/dist/client/static/js/async/yq.917ddd7ba2.js +0 -1
  393. package/dist/client/static/js/async/ys.00687cf82d.js +0 -38
  394. package/dist/client/static/js/async/yv.d99d829136.js +0 -1
  395. package/dist/client/static/js/async/z.56f61a4374.js +0 -1
  396. package/dist/client/static/js/async/zi.5fdb07b86a.js +0 -24
  397. package/dist/client/static/js/async/zj.654f37e8ff.js +0 -1
  398. package/dist/client/static/js/async/zn.966d6f1cc6.js +0 -1
  399. package/dist/client/static/js/async/zo.26213b40c0.js +0 -1
  400. package/dist/client/static/js/async/zx.e9f4105e00.js +0 -1
  401. package/dist/client/static/js/index.e6a257402a.js +0 -38
  402. package/dist/client/static/js/index.e6a257402a.js.LICENSE.txt +0 -4
  403. package/dist/client/static/js/markdown-vendor.71e43278b3.js +0 -144
  404. package/dist/client/static/js/shiki-vendor.cb8452ddcd.js +0 -1
  405. package/src/client/components/settings/categories/provider-settings/Icons.tsx +0 -35
  406. package/src/client/hooks/settings/providers.ts +0 -326
  407. package/src/server/plugins/dev-assets.ts +0 -49
  408. package/src/shared/lib/code/editor-utils.ts +0 -16
  409. package/src/shared/lib/models/provider-presets.ts +0 -29
  410. /package/dist/client/{apple-touch-icon.png → static/apple-touch-icon-dskx5sr3.png} +0 -0
  411. /package/dist/client/{icon-192x192.png → static/icon-192x192-6gn5cr2f.png} +0 -0
  412. /package/dist/client/{icon.svg → static/icon-d07z9dtz.svg} +0 -0
  413. /package/dist/client/{manifest.webmanifest → static/manifest-bnyy75kt.webmanifest} +0 -0
  414. /package/{dist/client → public}/icon-512x512.png +0 -0
  415. /package/{dist/client → public}/icon-maskable-512x512.png +0 -0
  416. /package/{dist/client → public}/sw.js +0 -0
  417. /package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.ts +0 -0
  418. /package/src/shared/lib/models/{provider-cleanup.ts → provider/cleanup.ts} +0 -0
  419. /package/src/shared/lib/models/{provider-label.ts → provider/label.ts} +0 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The hook that owns the editor's extra selection ranges.
8
+ *
9
+ * It is a thin shell over `shared/lib/code/editor/occurrences`: the rules live
10
+ * there as pure functions, and this holds the ranges in a ref (the keydown and
11
+ * `input` handlers read them synchronously, inside one event) and tells the
12
+ * caller through `onChange` so the surface can paint them.
13
+ *
14
+ * The split is deliberate: a hook that decides where a range goes cannot be
15
+ * tested without a DOM, and every rule here is one a user notices when it is
16
+ * wrong.
17
+ */
18
+
19
+ import { useRef } from 'preact/hooks';
20
+
21
+ import {
22
+ growToNextOccurrence,
23
+ offsetBelow,
24
+ replicateAtRanges,
25
+ selectAllOccurrences,
26
+ } from '@/shared/lib/code/editor/occurrences';
27
+ import type { TextRange } from '@/shared/lib/code/editor/commands';
28
+
29
+ export interface UseOccurrencesOptions {
30
+ /** Called with the extra ranges whenever they change, so the surface repaints. */
31
+ onChange: (ranges: readonly TextRange[]) => void;
32
+ /** The caller's copy of the ranges — clearing them there must clear them here. */
33
+ controlled?: readonly TextRange[];
34
+ }
35
+
36
+ export interface OccurrencesApi {
37
+ /** The extra ranges, excluding the textarea's own (primary) selection. */
38
+ readonly ranges: readonly TextRange[];
39
+ /** Replace the ranges and notify the surface. */
40
+ set: (ranges: readonly TextRange[]) => void;
41
+ /** Drop every extra range. */
42
+ clear: () => void;
43
+ /** ⌘D — grow the primary selection to the next occurrence of what it covers. */
44
+ selectNext: (input: HTMLTextAreaElement, select: (start: number, end: number) => void) => void;
45
+ /** ⌘⇧L — select every occurrence of the selection/word at the caret. */
46
+ selectAll: (input: HTMLTextAreaElement, select: (start: number, end: number) => void) => void;
47
+ /** ⌘⇧D — add an empty caret on the line below. */
48
+ insertCursorBelow: (input: HTMLTextAreaElement) => void;
49
+ /**
50
+ * Replay the browser's edit at every extra range. Returns the buffer and
51
+ * caret the caller should commit, or null when there is nothing to replicate.
52
+ */
53
+ replicate: (before: string, after: string, primaryBefore: TextRange) => { value: string; caret: number; rest: TextRange[] } | null;
54
+ }
55
+
56
+ export function useOccurrences({ onChange, controlled }: UseOccurrencesOptions): OccurrencesApi {
57
+ const ref = useRef<readonly TextRange[]>([]);
58
+ // The caller may clear the ranges without the editor asking (a file switch, a
59
+ // save), so its copy is mirrored into the ref the handlers read.
60
+ if (controlled && controlled !== ref.current) ref.current = controlled;
61
+
62
+ const set = (ranges: readonly TextRange[]) => {
63
+ ref.current = ranges;
64
+ onChange(ranges);
65
+ };
66
+
67
+ return {
68
+ get ranges() {
69
+ return ref.current;
70
+ },
71
+ set,
72
+ clear: () => set([]),
73
+
74
+ selectNext: (input, select) => {
75
+ const current: TextRange = { start: input.selectionStart, end: input.selectionEnd };
76
+ const next = growToNextOccurrence(input.value, current, ref.current);
77
+ if (!next) return;
78
+ select(next.primary.start, next.primary.end);
79
+ set(next.extras);
80
+ },
81
+
82
+ selectAll: (input, select) => {
83
+ const current: TextRange = { start: input.selectionStart, end: input.selectionEnd };
84
+ const all = selectAllOccurrences(input.value, current);
85
+ if (!all) return;
86
+ select(all.primary.start, all.primary.end);
87
+ set(all.extras);
88
+ },
89
+
90
+ insertCursorBelow: (input) => {
91
+ const target = offsetBelow(input.value, input.selectionStart);
92
+ if (target === null) return;
93
+ set([...ref.current, { start: target, end: target }]);
94
+ },
95
+
96
+ replicate: (before, after, primaryBefore) => replicateAtRanges(before, after, primaryBefore, ref.current),
97
+ };
98
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ModelsResponse } from '@/shared/lib/models/client';
2
- import { providerNamesFromConnected } from '@/shared/lib/models/provider-label';
2
+ import { providerNamesFromConnected } from '@/shared/lib/models/provider/label';
3
3
  import { useModelsCatalog } from '@/client/hooks/models/use-models-catalog';
4
4
 
5
5
  /** slug → display-name map from `connectedProviders` ({} when none connected). */
@@ -0,0 +1,337 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The provider mutation actions behind Settings → Providers.
8
+ *
9
+ * Split from the hook (`providers.ts`) so that file holds state wiring only:
10
+ * every action here is a write whose failure has to be reported, and keeping
11
+ * them together means the "what does each mutation actually change" contract
12
+ * has one home. `deps` is a single record rather than a positional list, so a
13
+ * caller cannot silently pass the wrong setter.
14
+ */
15
+
16
+ import type { ProviderItem, ProviderModel } from '@/shared/types';
17
+ import { notifyModelsUpdated } from '@/shared/lib/models/client';
18
+ import {
19
+ deleteProvider,
20
+ saveProviderOverlay,
21
+ setProviderEnabled,
22
+ } from '@/shared/lib/models/provider/connection';
23
+ import {
24
+ fetchProviderModelsRemote,
25
+ mergeProviderModels,
26
+ syncProviderModelsToCatalog,
27
+ } from '@/shared/lib/models/provider/models';
28
+ import { removeLegacyKenariModels } from '@/shared/lib/models/provider/cleanup';
29
+ import { addProviderMessage, fetchModelsNote } from '@/client/hooks/settings/providers/messages';
30
+ import { saveModelOverrideRemote } from '@/client/hooks/settings/providers/api';
31
+
32
+ export interface ProviderActionsDeps {
33
+ providers: ProviderItem[];
34
+ selectedProvider: ProviderItem | undefined;
35
+ connectedProviders: ProviderItem[];
36
+ isFetchingModels: boolean;
37
+ isDeletingProvider: boolean;
38
+ pushToast: (message: string, tone: 'success' | 'error') => void;
39
+ setProviders: (providers: ProviderItem[]) => void;
40
+ setSelectedProviderId: (id: string) => void;
41
+ setIsAddModalOpen: (open: boolean) => void;
42
+ setIsDeleteModalOpen: (open: boolean) => void;
43
+ setIsDeletingProvider: (deleting: boolean) => void;
44
+ setIsFetchingModels: (fetching: boolean) => void;
45
+ onAddModalClose?: () => void;
46
+ }
47
+
48
+ export function createProviderActions(deps: ProviderActionsDeps) {
49
+ const {
50
+ providers,
51
+ selectedProvider,
52
+ connectedProviders,
53
+ isFetchingModels,
54
+ isDeletingProvider,
55
+ pushToast,
56
+ setProviders,
57
+ setSelectedProviderId,
58
+ setIsAddModalOpen,
59
+ setIsDeleteModalOpen,
60
+ setIsDeletingProvider,
61
+ setIsFetchingModels,
62
+ onAddModalClose,
63
+ } = deps;
64
+
65
+ /** Publish an overlay edit and adopt whatever the server merged back. */
66
+ const persistProviders = async (updated: ProviderItem[]) => {
67
+ setProviders(updated);
68
+ try {
69
+ const merged = await saveProviderOverlay(updated);
70
+ if (merged) setProviders(merged);
71
+ } catch (error) {
72
+ console.error('Failed to save providers via API:', error);
73
+ pushToast('Failed to save provider settings.', 'error');
74
+ }
75
+ };
76
+
77
+ const toggleProviderEnabled = async (
78
+ slug: string,
79
+ enabled: boolean,
80
+ ): Promise<ProviderItem[] | null> => {
81
+ try {
82
+ const merged = await setProviderEnabled(slug, enabled);
83
+ if (merged) setProviders(merged);
84
+ return merged;
85
+ } catch (error) {
86
+ console.error('Failed to update provider connection:', error);
87
+ pushToast('Failed to update the provider connection.', 'error');
88
+ return null;
89
+ }
90
+ };
91
+
92
+ const handleAddProvider = async (
93
+ newProvider: ProviderItem,
94
+ options: { fetchedCount: number; ompNote?: string },
95
+ ) => {
96
+ // Replace by slug rather than append: an endpoint override reuses the slug
97
+ // of a provider omp already ships, and two overlay rows for one slug would
98
+ // let the stale one win the next merge.
99
+ const slug = newProvider.slug.trim().toLowerCase();
100
+ const withoutExisting = providers.filter((p) => p.slug.trim().toLowerCase() !== slug);
101
+ await persistProviders([...withoutExisting, newProvider]);
102
+ const merged = await toggleProviderEnabled(newProvider.slug, true);
103
+ const registered = merged?.find((provider) => provider.slug.trim().toLowerCase() === slug);
104
+ setSelectedProviderId(registered?.id || newProvider.id);
105
+ setIsAddModalOpen(false);
106
+ onAddModalClose?.();
107
+
108
+ const message = addProviderMessage(newProvider, options);
109
+ pushToast(message.text, message.tone);
110
+ };
111
+
112
+ const handleReconnect = async (updates: Partial<ProviderItem>) => {
113
+ if (!selectedProvider) return;
114
+ const target = selectedProvider;
115
+ // A keyless provider stores no credential, so a masked placeholder echoed
116
+ // back from the dialog must never be persisted as one — it would show a key
117
+ // that authenticates nothing.
118
+ const patch: Partial<ProviderItem> = target.auth === 'none'
119
+ ? Object.fromEntries(Object.entries(updates).filter(([key]) => key !== 'apiKey'))
120
+ : updates;
121
+ await persistProviders(providers.map((p) => (
122
+ p.id === target.id ? { ...p, ...patch, status: 'connected' as const } : p
123
+ )));
124
+ if (target.status !== 'connected') {
125
+ await toggleProviderEnabled(target.slug, true);
126
+ }
127
+ };
128
+
129
+ const handleFetchModels = async (credentials: { apiKey?: string; baseUrl?: string }) => {
130
+ if (!selectedProvider || isFetchingModels) return;
131
+ const baseUrl = credentials.baseUrl || selectedProvider.baseUrl;
132
+ if (!baseUrl) {
133
+ pushToast('Base URL is required to fetch models.', 'error');
134
+ return;
135
+ }
136
+ setIsFetchingModels(true);
137
+ try {
138
+ const result = await fetchModelsFor(selectedProvider, baseUrl, credentials.apiKey);
139
+ if (!result) return;
140
+ if (result.omp?.written) notifyModelsUpdated();
141
+ } finally {
142
+ setIsFetchingModels(false);
143
+ }
144
+ };
145
+
146
+ /**
147
+ * The listing probe plus its reporting. Kept as one unit because the three
148
+ * outcomes — models added, legacy models pruned, nothing new — each have their
149
+ * own message, and a partial report reads as a failed fetch.
150
+ */
151
+ const fetchModelsFor = async (
152
+ provider: ProviderItem,
153
+ baseUrl: string,
154
+ credential: string | undefined,
155
+ ): Promise<{ omp?: { written: boolean; reason?: string } } | null> => {
156
+ const result = await fetchProviderModelsRemote({
157
+ baseUrl,
158
+ apiKey: credential || provider.apiKey,
159
+ providerSlug: provider.slug,
160
+ persistToOmp: true,
161
+ // The provider's own dialect decides the auth header the probe sends;
162
+ // falling back to the URL classifier would send a Bearer header to an
163
+ // Anthropic-shaped proxy that only accepts x-api-key.
164
+ ...(provider.api ? { api: provider.api } : {}),
165
+ ...(provider.auth ? { auth: provider.auth } : {}),
166
+ });
167
+ if (!result.ok || !result.models) {
168
+ pushToast(result.error || 'Failed to fetch models from the provider.', 'error');
169
+ return null;
170
+ }
171
+ const cleaned = removeLegacyKenariModels(provider, provider.models);
172
+ const removedCount = provider.models.length - cleaned.length;
173
+ const { merged, addedCount } = mergeProviderModels(cleaned, result.models);
174
+ if (addedCount > 0 || removedCount > 0) {
175
+ await handleReconnect({ models: merged });
176
+ }
177
+ if (addedCount > 0) {
178
+ pushToast(`Fetched ${addedCount} new model${addedCount === 1 ? '' : 's'} from the provider.`, 'success');
179
+ } else if (removedCount > 0) {
180
+ pushToast('Removed legacy fallback models from the provider.', 'success');
181
+ } else {
182
+ pushToast('No new models found — all fetched models already exist.', 'success');
183
+ }
184
+ const note = fetchModelsNote(result);
185
+ if (note) pushToast(note.text, note.tone);
186
+ if (addedCount > 0) void syncProviderModelsToCatalog(provider.name, result.models);
187
+ return { omp: result.omp };
188
+ };
189
+
190
+ const handleFetchModelsFromList = async () => {
191
+ if (!selectedProvider) return;
192
+ await handleFetchModels({
193
+ apiKey: selectedProvider.apiKey,
194
+ baseUrl: selectedProvider.baseUrl,
195
+ });
196
+ };
197
+
198
+ const handleOmpAuthSuccess = () => {
199
+ if (!selectedProvider) return;
200
+ const target = selectedProvider;
201
+ void persistProviders(providers.map((p) => (
202
+ p.id === target.id ? { ...p, status: 'connected' as const } : p
203
+ )));
204
+ void toggleProviderEnabled(target.slug, true);
205
+ };
206
+
207
+ const handleToggleDisconnect = async () => {
208
+ if (!selectedProvider) return;
209
+ const target = selectedProvider;
210
+ const disconnecting = target.status === 'connected';
211
+ if (disconnecting) {
212
+ setSelectedProviderId(connectedProviders.find((provider) => provider.id !== target.id)?.id || '');
213
+ }
214
+ const merged = await toggleProviderEnabled(target.slug, !disconnecting);
215
+ if (!merged) return;
216
+ pushToast(
217
+ disconnecting
218
+ ? `${target.name} disconnected — its models are no longer offered in the chat picker.`
219
+ : `${target.name} reconnected.`,
220
+ 'success',
221
+ );
222
+ };
223
+
224
+ const updateSelectedModels = (transform: (models: ProviderModel[]) => ProviderModel[]) => {
225
+ if (!selectedProvider) return;
226
+ void persistProviders(providers.map((p) => (
227
+ p.id === selectedProvider.id ? { ...p, models: transform(p.models) } : p
228
+ )));
229
+ };
230
+
231
+ /**
232
+ * Disable/enable from omp's own config.yml `disabledProviders`. This is the
233
+ * coarse switch that removes the provider — and every model it serves — from
234
+ * the chat picker; the per-model eye toggle is the fine-grained one.
235
+ */
236
+ const handleToggleProviderDisabled = async () => {
237
+ if (!selectedProvider) return;
238
+ const target = selectedProvider;
239
+ const disabling = target.disabled !== true;
240
+ const merged = await toggleProviderEnabled(target.slug, !disabling);
241
+ if (!merged) return;
242
+ pushToast(
243
+ disabling
244
+ ? `${target.name} disabled — it no longer appears in the model list.`
245
+ : `${target.name} enabled — its models are offered again.`,
246
+ 'success',
247
+ );
248
+ };
249
+
250
+ /**
251
+ * Delete a provider's `models.yml` entry (and the chamber's overlay row).
252
+ *
253
+ * Only offered for a provider the server reports as `inModelsYml`, because
254
+ * the action is a file edit: a login provider's credential is untouched, and
255
+ * a provider with no file entry has nothing to delete. The selection moves off
256
+ * the deleted provider BEFORE the list is republished — leaving it selected
257
+ * would render a detail pane for a provider that no longer exists.
258
+ */
259
+ const handleDeleteProvider = async () => {
260
+ if (!selectedProvider || isDeletingProvider) return;
261
+ const target = selectedProvider;
262
+ setIsDeletingProvider(true);
263
+ try {
264
+ const result = await deleteProvider(target.id);
265
+ if (!result.ok) {
266
+ pushToast(result.error || 'Failed to delete the provider.', 'error');
267
+ return;
268
+ }
269
+ setIsDeleteModalOpen(false);
270
+ const remaining = result.providers ?? [];
271
+ setSelectedProviderId(remaining.find((provider) => provider.status === 'connected')?.id || '');
272
+ setProviders(remaining);
273
+ // The overlay row is gone even when the file cleanup failed, so the
274
+ // warning is reported rather than presented as a clean delete.
275
+ if (result.warning) {
276
+ pushToast(`${target.name} removed from the chamber, but models.yml cleanup failed: ${result.warning}`, 'error');
277
+ } else if (result.removedFromOmp) {
278
+ const count = result.modelsRemoved ?? 0;
279
+ pushToast(
280
+ `${target.name} deleted from models.yml${count > 0 ? ` with its ${count} model${count === 1 ? '' : 's'}` : ''}.`,
281
+ 'success',
282
+ );
283
+ } else {
284
+ pushToast(`${target.name} removed from the chamber list.`, 'success');
285
+ }
286
+ } finally {
287
+ setIsDeletingProvider(false);
288
+ }
289
+ };
290
+
291
+ const handleHideAll = () => updateSelectedModels(
292
+ (models) => models.map((m) => ({ ...m, isVisible: false })),
293
+ );
294
+
295
+ const handleShowAll = () => updateSelectedModels(
296
+ (models) => models.map((m) => ({ ...m, isVisible: true })),
297
+ );
298
+
299
+ const handleToggleModelVisibility = (modelId: string) => updateSelectedModels(
300
+ (models) => models.map((m) => (m.id === modelId ? { ...m, isVisible: !m.isVisible } : m)),
301
+ );
302
+
303
+ /**
304
+ * Save the per-model knobs omp honours (`models.yml` modelOverrides). The
305
+ * overlay still stores the display value so the dialog reopens where the user
306
+ * left it, but omp only reads the override — writing just the overlay was the
307
+ * bug that made these controls inert.
308
+ */
309
+ const handleSaveModelConfig = async (modelId: string, updates: Partial<ProviderModel>) => {
310
+ if (!selectedProvider) return;
311
+ const provider = selectedProvider;
312
+ updateSelectedModels((models) => models.map((m) => (m.id === modelId ? { ...m, ...updates } : m)));
313
+
314
+ const result = await saveModelOverrideRemote(provider.slug, modelId, updates);
315
+ if (!result.ok) {
316
+ pushToast(result.error || 'Failed to save the model configuration.', 'error');
317
+ return;
318
+ }
319
+ if (result.written) notifyModelsUpdated();
320
+ else if (result.reason) pushToast(result.reason, 'error');
321
+ };
322
+
323
+ return {
324
+ handleAddProvider,
325
+ handleReconnect,
326
+ handleFetchModels,
327
+ handleFetchModelsFromList,
328
+ handleOmpAuthSuccess,
329
+ handleToggleDisconnect,
330
+ handleToggleProviderDisabled,
331
+ handleDeleteProvider,
332
+ handleHideAll,
333
+ handleShowAll,
334
+ handleToggleModelVisibility,
335
+ handleSaveModelConfig,
336
+ };
337
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The settings API calls behind Settings → Providers.
8
+ *
9
+ * Split from the hook so the hook holds state transitions only — each of these
10
+ * is a request whose failure has to be reported to the user, and keeping them
11
+ * here means the "what does the server answer" contract has one home.
12
+ */
13
+
14
+ import type { PresetProviderOption, ProviderItem, ProviderModel } from '@/shared/types';
15
+
16
+ export interface ProviderSettingsPayload {
17
+ providers: ProviderItem[];
18
+ presetProviders?: PresetProviderOption[];
19
+ }
20
+
21
+ /** Load the merged provider registry (omp's sources + the chamber overlay). */
22
+ export async function loadProvidersFromApi(): Promise<ProviderSettingsPayload | null> {
23
+ try {
24
+ const response = await fetch('/api/settings/providers');
25
+ const data = await response.json() as Partial<ProviderSettingsPayload>;
26
+ if (!Array.isArray(data?.providers)) return null;
27
+ return { providers: data.providers, ...(data.presetProviders ? { presetProviders: data.presetProviders } : {}) };
28
+ } catch (error) {
29
+ console.error('Failed to load providers from API:', error);
30
+ return null;
31
+ }
32
+ }
33
+
34
+ export interface ModelOverrideResult {
35
+ ok: boolean;
36
+ written?: boolean;
37
+ reason?: string;
38
+ error?: string;
39
+ }
40
+
41
+ /**
42
+ * Write the per-model knobs omp honours (`maxTokens`, reasoning effort) into
43
+ * models.yml `modelOverrides`. Writing only the chamber overlay left the
44
+ * controls inert — omp never read them.
45
+ */
46
+ export async function saveModelOverrideRemote(
47
+ providerSlug: string,
48
+ modelId: string,
49
+ updates: Partial<ProviderModel>,
50
+ ): Promise<ModelOverrideResult> {
51
+ const body: Record<string, unknown> = { provider: providerSlug, modelId };
52
+ if ('maxTokens' in updates) body.maxTokens = updates.maxTokens ?? null;
53
+ if ('reasoningEffort' in updates) body.reasoningEffort = updates.reasoningEffort ?? null;
54
+ // Nothing omp would read — the overlay update alone is the whole change.
55
+ if (Object.keys(body).length === 2) return { ok: true, written: false };
56
+ try {
57
+ const response = await fetch('/api/settings/model-override', {
58
+ method: 'PUT',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify(body),
61
+ });
62
+ const data = await response.json() as { success?: boolean; written?: boolean; reason?: string; error?: string };
63
+ if (!response.ok) return { ok: false, error: data.error || 'Failed to save the model configuration.' };
64
+ return { ok: true, written: data.written, reason: data.reason };
65
+ } catch (error) {
66
+ console.error('Failed to save model override:', error);
67
+ return { ok: false, error: 'Failed to save the model configuration.' };
68
+ }
69
+ }
@@ -0,0 +1,138 @@
1
+ import { useEffect, useMemo, useState } from 'preact/hooks';
2
+ import type { PresetProviderOption, ProviderItem, ProviderModel } from '@/shared/types';
3
+ import { PRESET_NEW_PROVIDERS } from '@/client/data/settings/provider';
4
+ import { buildAvailableProviderPresets } from '@/shared/lib/models/provider/presets';
5
+ import { useToasts } from '@/client/hooks/ui/toasts';
6
+ import { loadProvidersFromApi } from '@/client/hooks/settings/providers/api';
7
+ import { createProviderActions } from '@/client/hooks/settings/providers/actions';
8
+
9
+ interface UseProviderSettingsOptions {
10
+ autoOpenAdd?: boolean;
11
+ onAddModalClose?: () => void;
12
+ }
13
+
14
+ /**
15
+ * State wiring behind Settings → Providers.
16
+ *
17
+ * The mutations themselves live in `providers/actions.ts`, which receives this
18
+ * hook's setters as one `deps` record — the split keeps both files under the
19
+ * repo's size ceiling without changing closure semantics, since every action
20
+ * still closes over the same state.
21
+ *
22
+ * Every mutation lands in one of two stores: the chamber-local overlay (per-model
23
+ * visibility and sampling config) or omp's own config.yml `disabledProviders`
24
+ * (connect / disconnect). Disconnect has to use the latter — the overlay is
25
+ * re-merged away on load, which is why a disconnected provider used to come back
26
+ * as connected.
27
+ */
28
+ export function useProviderSettings({
29
+ autoOpenAdd = false,
30
+ onAddModalClose,
31
+ }: UseProviderSettingsOptions = {}) {
32
+ const [providers, setProviders] = useState<ProviderItem[]>([]);
33
+ const [presetProviders, setPresetProviders] = useState<PresetProviderOption[]>(PRESET_NEW_PROVIDERS);
34
+ const [selectedProviderId, setSelectedProviderId] = useState<string>('provider-deepseek');
35
+ const [currentProject, setCurrentProject] = useState('ompchamber');
36
+ const [isFetchingModels, setIsFetchingModels] = useState(false);
37
+ const { toasts, pushToast, dismissToast } = useToasts();
38
+
39
+ const [isAddModalOpen, setIsAddModalOpen] = useState(autoOpenAdd);
40
+ const [isAddModelModalOpen, setIsAddModelModalOpen] = useState(false);
41
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
42
+ const [isDeletingProvider, setIsDeletingProvider] = useState(false);
43
+ const [isReconnectModalOpen, setIsReconnectModalOpen] = useState(false);
44
+ const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
45
+ const [configModel, setConfigModel] = useState<ProviderModel | null>(null);
46
+ const [capabilitiesModel, setCapabilitiesModel] = useState<ProviderModel | null>(null);
47
+
48
+ useEffect(() => {
49
+ if (autoOpenAdd) setIsAddModalOpen(true);
50
+ }, [autoOpenAdd]);
51
+
52
+ useEffect(() => {
53
+ let active = true;
54
+ void loadProvidersFromApi().then((data) => {
55
+ if (!active || !data) return;
56
+ setProviders(data.providers);
57
+ const connected = data.providers.filter((provider) => provider.status === 'connected');
58
+ setSelectedProviderId(connected.length > 0 ? connected[0].id : '');
59
+ if (data.presetProviders) setPresetProviders(data.presetProviders);
60
+ });
61
+ return () => { active = false; };
62
+ }, []);
63
+
64
+ // The sidebar lists connected providers plus every provider the user disabled:
65
+ // a disabled provider reports `disconnected`, so filtering on status alone
66
+ // would make the switch that hid it unreachable.
67
+ const connectedProviders = useMemo(
68
+ () => providers.filter(
69
+ (provider) => provider.status === 'connected' || provider.disabled === true,
70
+ ),
71
+ [providers],
72
+ );
73
+ const availablePresetProviders = useMemo(
74
+ () => buildAvailableProviderPresets(providers, presetProviders),
75
+ [providers, presetProviders],
76
+ );
77
+ const selectedProvider = connectedProviders.find((p) => p.id === selectedProviderId) || connectedProviders[0];
78
+
79
+ const actions = createProviderActions({
80
+ providers,
81
+ selectedProvider,
82
+ connectedProviders,
83
+ isFetchingModels,
84
+ isDeletingProvider,
85
+ pushToast,
86
+ setProviders,
87
+ setSelectedProviderId,
88
+ setIsAddModalOpen,
89
+ setIsDeleteModalOpen,
90
+ setIsDeletingProvider,
91
+ setIsFetchingModels,
92
+ ...(onAddModalClose ? { onAddModalClose } : {}),
93
+ });
94
+
95
+ /**
96
+ * After a hand-registered model lands in models.yml, the registry has to be
97
+ * re-read: the new row exists only in omp's file, so nothing in the chamber's
98
+ * own overlay knows about it yet. Re-fetching the MERGED list (rather than
99
+ * re-saving the overlay, whose response is the overlay alone) is what puts
100
+ * the model in the sidebar count and the chat picker.
101
+ */
102
+ const handleModelAdded = async (modelId: string) => {
103
+ pushToast(`${modelId} registered in models.yml.`, 'success');
104
+ const data = await loadProvidersFromApi();
105
+ if (data) setProviders(data.providers);
106
+ };
107
+
108
+ return {
109
+ providers,
110
+ connectedProviders,
111
+ availablePresetProviders,
112
+ selectedProvider,
113
+ currentProject,
114
+ setCurrentProject,
115
+ isFetchingModels,
116
+ toasts,
117
+ pushToast,
118
+ dismissToast,
119
+ isAddModelModalOpen,
120
+ setIsAddModelModalOpen,
121
+ handleModelAdded,
122
+ isDeleteModalOpen,
123
+ setIsDeleteModalOpen,
124
+ isDeletingProvider,
125
+ isAddModalOpen,
126
+ setIsAddModalOpen,
127
+ isReconnectModalOpen,
128
+ setIsReconnectModalOpen,
129
+ isLoginModalOpen,
130
+ setIsLoginModalOpen,
131
+ configModel,
132
+ setConfigModel,
133
+ capabilitiesModel,
134
+ setCapabilitiesModel,
135
+ setSelectedProviderId,
136
+ ...actions,
137
+ };
138
+ }