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,200 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Provider spelling -> brand glyph, plus the initials fallback for a provider
8
+ * that publishes no logo.
9
+ *
10
+ * A provider reaches this module as whatever string omp reported: a login
11
+ * provider id (`google-vertex`), a models.yml slug the user typed
12
+ * (`my-gateway`), a legacy chamber icon key (`claude`), or a display name
13
+ * (`DeepSeek`). All four must land on the same mark, so lookup folds the input
14
+ * to lowercase alphanumerics and matches it against this table.
15
+ *
16
+ * The table is DATA, not a chain of `if`s: `minimax-code-cn` is the same brand
17
+ * as `minimax`, and a new variant belongs here as one more row rather than as
18
+ * another branch at every call site.
19
+ *
20
+ * Pure and dependency-free so the server (which builds provider rows) and the
21
+ * client (which renders them) resolve a provider identically.
22
+ */
23
+
24
+ /**
25
+ * Normalised provider spelling -> glyph name in `PROVIDER_GLYPHS`.
26
+ *
27
+ * Keys are lowercase alphanumerics only (`google-vertex` is `googlevertex`).
28
+ * Values name an entry in the glyph table, or a brand whose mark stands for
29
+ * several providers (Bedrock draws Amazon's mark).
30
+ */
31
+ export const PROVIDER_GLYPH_ALIASES: Readonly<Record<string, string>> = {
32
+ abliteration: 'abliteration',
33
+ aiand: 'aiand',
34
+ aimlapi: 'aimlapi',
35
+ alibaba: 'alibaba',
36
+ alibabacloud: 'alibaba',
37
+ alibabacodingplan: 'alibaba',
38
+ alibabatokenplan: 'alibaba',
39
+ amazon: 'amazon',
40
+ amazonbedrock: 'amazon',
41
+ anthropic: 'anthropic',
42
+ antigravity: 'antigravity',
43
+ apple: 'apple',
44
+ aws: 'amazon',
45
+ azure: 'azure',
46
+ azureai: 'azure',
47
+ baidu: 'baidu',
48
+ baseten: 'baseten',
49
+ bedrock: 'amazon',
50
+ bedrockmantle: 'amazon',
51
+ cerebras: 'cerebras',
52
+ charm: 'charm',
53
+ charmhyper: 'charm',
54
+ chatglm: 'zai',
55
+ claude: 'claude',
56
+ claudecode: 'claude',
57
+ cline: 'cline',
58
+ clinepass: 'cline',
59
+ cloudflare: 'cloudflare',
60
+ cloudflareaigateway: 'cloudflare',
61
+ codex: 'codex',
62
+ commandcode: 'commandcode',
63
+ copilot: 'githubcopilot',
64
+ coreweave: 'coreweave',
65
+ cursor: 'cursor',
66
+ deepinfra: 'deepinfra',
67
+ deepseek: 'deepseek',
68
+ devin: 'devin',
69
+ ernie: 'baidu',
70
+ firepass: 'fireworks',
71
+ fireworks: 'fireworks',
72
+ gemini: 'gemini',
73
+ geminicli: 'geminicli',
74
+ githubcopilot: 'githubcopilot',
75
+ gitlab: 'gitlab',
76
+ gitlabduo: 'gitlab',
77
+ gitlabduoagent: 'gitlab',
78
+ glm: 'zai',
79
+ gmicloud: 'gmicloud',
80
+ google: 'google',
81
+ googleantigravity: 'antigravity',
82
+ googlecloud: 'google',
83
+ googlegemini: 'gemini',
84
+ googlegeminicli: 'geminicli',
85
+ googlevertex: 'vertexai',
86
+ grok: 'xai',
87
+ groq: 'groq',
88
+ huggingface: 'huggingface',
89
+ kenari: 'kenari',
90
+ kilo: 'kilocode',
91
+ kilocode: 'kilocode',
92
+ kimi: 'kimi',
93
+ kimicode: 'kimi',
94
+ litellm: 'litellm',
95
+ llama: 'meta',
96
+ llamacpp: 'llamacpp',
97
+ lmstudio: 'lmstudio',
98
+ meta: 'meta',
99
+ metaai: 'metaai',
100
+ microsoftazure: 'azure',
101
+ minimax: 'minimax',
102
+ minimaxcode: 'minimax',
103
+ minimaxcodecn: 'minimax',
104
+ mistral: 'mistral',
105
+ mistralai: 'mistral',
106
+ moonshot: 'moonshot',
107
+ moonshotai: 'moonshot',
108
+ musecode: 'metaai',
109
+ nanogpt: 'nanogpt',
110
+ novita: 'novita',
111
+ nvidia: 'nvidia',
112
+ ollama: 'ollama',
113
+ ollamacloud: 'ollama',
114
+ openai: 'openai',
115
+ openaicodex: 'codex',
116
+ opencode: 'opencode',
117
+ opencodego: 'opencode',
118
+ opencodezen: 'opencode',
119
+ openrouter: 'openrouter',
120
+ qianfan: 'baidu',
121
+ qwen: 'qwen',
122
+ qwenportal: 'qwen',
123
+ sakana: 'sakana',
124
+ sakanaai: 'sakana',
125
+ siliconcloud: 'siliconcloud',
126
+ siliconflow: 'siliconcloud',
127
+ siliconflowcn: 'siliconcloud',
128
+ singularity: 'singularity',
129
+ singularityapidev: 'singularity',
130
+ singularityapitech: 'singularity',
131
+ stepfun: 'stepfun',
132
+ synthetic: 'synthetic',
133
+ together: 'together',
134
+ togetherai: 'together',
135
+ venice: 'venice',
136
+ vercel: 'vercel',
137
+ vercelaigateway: 'vercel',
138
+ vertexai: 'vertexai',
139
+ vllm: 'vllm',
140
+ wafer: 'wafer',
141
+ waferserverless: 'wafer',
142
+ xai: 'xai',
143
+ xaioauth: 'xai',
144
+ xiaomi: 'xiaomi',
145
+ xiaomitokenplanams: 'xiaomi',
146
+ xiaomitokenplancn: 'xiaomi',
147
+ xiaomitokenplansgp: 'xiaomi',
148
+ yolo: 'yoloauto',
149
+ yoloauto: 'yoloauto',
150
+ zai: 'zai',
151
+ zaicodingplan: 'zai',
152
+ openaicodexdevice: 'codex',
153
+ perplexity: 'perplexity',
154
+ tavily: 'tavily',
155
+ kagi: 'kagi',
156
+ exa: 'exa',
157
+ stencil: 'stencil',
158
+ zenmux: 'zenmux',
159
+ zhipu: 'zhipu',
160
+ zhipucodingplan: 'zhipu',
161
+ };
162
+
163
+ /**
164
+ * The glyph name for a provider, or `null` when no brand mark is registered —
165
+ * the caller then draws the initials badge instead of an empty box.
166
+ *
167
+ * Candidates are tried in order and each is folded to lowercase alphanumerics
168
+ * first, so a stored legacy icon key (`claude`) still resolves after the
169
+ * provider's slug changes. Pass the provider's own `icon` field before its slug
170
+ * to keep that precedence.
171
+ */
172
+ export function resolveProviderGlyph(
173
+ ...candidates: Array<string | null | undefined>
174
+ ): string | null {
175
+ for (const candidate of candidates) {
176
+ if (!candidate) continue;
177
+ const key = candidate.toLowerCase().replace(/[^a-z0-9]/g, '');
178
+ const alias = key ? PROVIDER_GLYPH_ALIASES[key] : undefined;
179
+ if (alias) return alias;
180
+ }
181
+ return null;
182
+ }
183
+
184
+ /**
185
+ * Fallback badge text for a provider with no brand mark: the initial of each of
186
+ * the label's first two title-cased words, joined and closed with a single dot —
187
+ * `Tooker` -> `t.`, `Toktok ID` -> `ti.`.
188
+ *
189
+ * Lowercase because the badge renders beside lowercase UI labels, and capped at
190
+ * two initials because a third overflows the badge at icon size.
191
+ */
192
+ export function providerInitials(label: string): string {
193
+ const words = label.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
194
+ if (words.length === 0) return '?';
195
+ const initials = words
196
+ .slice(0, 2)
197
+ .map((word) => word.charAt(0).toLowerCase())
198
+ .join('');
199
+ return `${initials}.`;
200
+ }
@@ -5,26 +5,49 @@
5
5
  */
6
6
 
7
7
  import type { ProviderModel } from '@/shared/types';
8
+ import type {
9
+ OmpProviderApi,
10
+ ProviderAuthMode,
11
+ ProviderDiscoveryType,
12
+ } from '@/shared/types/settings/provider';
13
+ import type { ProviderThinkingEffort } from '@/shared/lib/models/provider/dialect';
8
14
  import { notifyModelsUpdated } from '@/shared/lib/models/client';
9
15
 
10
16
  export interface ProviderModelsFetchResult {
11
17
  ok: boolean;
12
18
  models?: ProviderModel[];
13
19
  error?: string;
14
- omp?: { written: boolean; addedCount: number; backfilledCount: number; reason?: string };
20
+ omp?: {
21
+ written: boolean;
22
+ addedCount: number;
23
+ backfilledCount: number;
24
+ reason?: string;
25
+ };
26
+ }
27
+
28
+ export interface ProviderModelsFetchRequest {
29
+ baseUrl: string;
30
+ apiKey?: string;
31
+ providerSlug?: string;
32
+ persistToOmp?: boolean;
33
+ /** Wire dialect to probe and to write into models.yml. */
34
+ api?: OmpProviderApi;
35
+ /** omp auth mode; `none` marks a keyless local server. */
36
+ auth?: ProviderAuthMode;
37
+ /** Register the provider for omp's own live discovery — no listing here. */
38
+ discovery?: ProviderDiscoveryType;
39
+ /** Register the provider WITHOUT listing models (endpoint override only). */
40
+ registerOnly?: boolean;
15
41
  }
16
42
 
17
43
  export async function fetchProviderModelsRemote(
18
- baseUrl: string,
19
- apiKey?: string,
20
- providerSlug?: string,
21
- persistToOmp = false,
44
+ request: ProviderModelsFetchRequest,
22
45
  ): Promise<ProviderModelsFetchResult> {
23
46
  try {
24
47
  const response = await fetch('/api/settings/provider-models', {
25
48
  method: 'POST',
26
49
  headers: { 'Content-Type': 'application/json' },
27
- body: JSON.stringify({ baseUrl, apiKey, providerSlug, persistToOmp }),
50
+ body: JSON.stringify(request),
28
51
  });
29
52
  const data = await response.json() as ProviderModelsFetchResult;
30
53
  if (!response.ok) {
@@ -45,6 +68,68 @@ export interface ProviderModelsMergeResult {
45
68
  addedCount: number;
46
69
  }
47
70
 
71
+ /** One model registered by hand — the Add Model dialog's payload. */
72
+ export interface ManualModelRequest {
73
+ provider: string;
74
+ id: string;
75
+ name?: string;
76
+ /** Token counts, not the compact labels a listing produces. */
77
+ contextWindow?: number;
78
+ maxTokens?: number;
79
+ reasoning?: boolean;
80
+ imageInput?: boolean;
81
+ /** USD per 1M tokens; both input and output are needed for a cost to be written. */
82
+ costInput?: number;
83
+ costOutput?: number;
84
+ costCacheRead?: number;
85
+ costCacheWrite?: number;
86
+ /** Reasoning ladder this model accepts; omitted means "leave omp's default". */
87
+ efforts?: ProviderThinkingEffort[];
88
+ /** Only used when the provider has no models.yml entry yet. */
89
+ baseUrl?: string;
90
+ apiKey?: string;
91
+ auth?: ProviderAuthMode;
92
+ }
93
+
94
+ export interface ManualModelResult {
95
+ success: boolean;
96
+ written: boolean;
97
+ addedModels?: string[];
98
+ backfilledModels?: string[];
99
+ /** The provider already registers this id; nothing was overwritten. */
100
+ alreadyKnown?: boolean;
101
+ reason?: string;
102
+ error?: string;
103
+ }
104
+
105
+ /**
106
+ * Register one model in models.yml by hand. Unlike the auto-fetch this does not
107
+ * need a listing endpoint — the id and its metadata come from the user — so it
108
+ * is the path for a gateway model its `/models` route omits, an Azure
109
+ * deployment name, or any provider omp cannot enumerate.
110
+ */
111
+ export async function addProviderModel(request: ManualModelRequest): Promise<ManualModelResult> {
112
+ try {
113
+ const response = await fetch('/api/settings/provider-model', {
114
+ method: 'POST',
115
+ headers: { 'Content-Type': 'application/json' },
116
+ body: JSON.stringify(request),
117
+ });
118
+ const data = await response.json() as ManualModelResult;
119
+ if (!response.ok) {
120
+ return { ...data, success: false, written: false, error: data?.error || data?.reason };
121
+ }
122
+ notifyModelsUpdated();
123
+ return data;
124
+ } catch (error) {
125
+ return {
126
+ success: false,
127
+ written: false,
128
+ error: error instanceof Error ? error.message : 'Network error while adding the model',
129
+ };
130
+ }
131
+ }
132
+
48
133
  /**
49
134
  * Existing entries pass through untouched (visibility/config survive); the
50
135
  * chat model catalog is synced from the newly added entries only.
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The Add Provider picker's rules, which are all silent when wrong: a preset
8
+ * offered for a CONNECTED provider produces a second `models.yml` entry under
9
+ * the same slug (the upsert then writes into the live one), and a preset
10
+ * dropped while its provider is only DISCONNECTED makes the switch that hid it
11
+ * unreachable. The dialect fields are checked too — a preset whose `api` omp
12
+ * does not know would disable every custom provider in the file.
13
+ */
14
+
15
+ import { describe, expect, test } from 'bun:test';
16
+
17
+ import {
18
+ buildAvailableProviderPresets,
19
+ configuredProviderSlugs,
20
+ groupProviderPresets,
21
+ } from '@/shared/lib/models/provider/presets';
22
+ import { PROVIDER_APIS, providerEmptyReason } from '@/shared/lib/models/provider/dialect';
23
+ import { PRESET_NEW_PROVIDERS } from '@/client/data/settings/provider';
24
+ import type { PresetProviderOption, ProviderItem } from '@/shared/types/settings/provider';
25
+
26
+ function provider(overrides: Partial<ProviderItem> & { slug: string }): ProviderItem {
27
+ return {
28
+ id: `id-${overrides.slug}`,
29
+ name: overrides.slug,
30
+ icon: overrides.slug,
31
+ status: 'connected',
32
+ configuredIn: 'models.yml',
33
+ models: [],
34
+ ...overrides,
35
+ } as ProviderItem;
36
+ }
37
+
38
+ const PRESETS: PresetProviderOption[] = [
39
+ { id: 'openai', name: 'OpenAI', slug: 'openai', icon: 'openai', defaultUrl: 'https://api.openai.com/v1', group: 'Frontier APIs' },
40
+ { id: 'groq', name: 'Groq', slug: 'groq', icon: 'groq', defaultUrl: 'https://api.groq.com/openai/v1', group: 'Frontier APIs' },
41
+ { id: 'ollama', name: 'Ollama', slug: 'ollama', icon: 'ollama', defaultUrl: 'http://127.0.0.1:11434', auth: 'none', discovery: 'ollama', group: 'Local & self-hosted' },
42
+ ];
43
+
44
+ describe('buildAvailableProviderPresets', () => {
45
+ test('drops a non-bundled preset whose slug already has an endpoint', () => {
46
+ // A second entry under one slug is the same entry to omp; that provider is
47
+ // managed from the sidebar instead.
48
+ const available = buildAvailableProviderPresets(
49
+ [provider({ slug: 'openai', baseUrl: 'https://api.openai.com/v1' })],
50
+ PRESETS,
51
+ );
52
+ expect(available.map((entry) => entry.slug)).toEqual(['groq', 'ollama']);
53
+ });
54
+
55
+ test('keeps a preset whose provider omp merely KNOWS', () => {
56
+ // omp reports ~60 providers it knows with no endpoint and no credential;
57
+ // treating those as configured hid every gateway preset — the one case
58
+ // where the user wants to bring their own key.
59
+ const available = buildAvailableProviderPresets(
60
+ [provider({ slug: 'openai', status: 'disconnected', disabled: true })],
61
+ PRESETS,
62
+ );
63
+ expect(available.map((entry) => entry.slug)).toEqual(['openai', 'groq', 'ollama']);
64
+ });
65
+
66
+ test('keeps a bundled preset even when its provider is configured', () => {
67
+ // An override for a provider omp already ships is the documented way to
68
+ // point it at a proxy, and the writer is add-only, so re-offering is safe.
69
+ const available = buildAvailableProviderPresets(
70
+ [provider({ slug: 'azure', status: 'connected', baseUrl: 'https://x.openai.azure.com' })],
71
+ [{ ...PRESETS[0], slug: 'azure', id: 'azure', bundled: true }],
72
+ );
73
+ expect(available.map((entry) => entry.slug)).toEqual(['azure']);
74
+ });
75
+
76
+ test('never offers one slug twice', () => {
77
+ const available = buildAvailableProviderPresets(
78
+ [provider({ slug: 'ollama', status: 'disconnected' })],
79
+ PRESETS,
80
+ );
81
+ const slugs = available.map((entry) => entry.slug);
82
+ expect(new Set(slugs).size).toBe(slugs.length);
83
+ });
84
+ });
85
+
86
+ describe('groupProviderPresets', () => {
87
+ test('buckets by group in declaration order and keeps members in order', () => {
88
+ const groups = groupProviderPresets(PRESETS);
89
+ expect(groups.map((entry) => entry.group)).toEqual(['Frontier APIs', 'Local & self-hosted']);
90
+ expect(groups[0].options.map((entry) => entry.slug)).toEqual(['openai', 'groq']);
91
+ });
92
+ });
93
+
94
+ describe('shipped presets', () => {
95
+ test('every declared api is one omp accepts', () => {
96
+ for (const preset of PRESET_NEW_PROVIDERS) {
97
+ if (!preset.api) continue;
98
+ expect(PROVIDER_APIS).toContain(preset.api);
99
+ }
100
+ });
101
+
102
+ test('every preset carries a group and a unique id', () => {
103
+ const ids = PRESET_NEW_PROVIDERS.map((preset) => preset.id);
104
+ expect(new Set(ids).size).toBe(ids.length);
105
+ for (const preset of PRESET_NEW_PROVIDERS) {
106
+ expect(preset.group.length).toBeGreaterThan(0);
107
+ }
108
+ });
109
+
110
+ test('a discovery preset declares a keyless or authenticated shape, never neither', () => {
111
+ // omp needs a dialect for every discovery provider except `proxy`, which
112
+ // reports one per model; a preset without either would be rejected on load.
113
+ for (const preset of PRESET_NEW_PROVIDERS) {
114
+ if (!preset.discovery) continue;
115
+ expect(preset.api !== undefined || preset.discovery === 'proxy').toBe(true);
116
+ }
117
+ });
118
+
119
+ test('a built-in local engine preset is keyless', () => {
120
+ // omp's implicit ollama/llama.cpp/lm-studio engines are keyless; a preset
121
+ // that omitted `auth: none` would be registered as a provider omp reports
122
+ // as unconfigured. LiteLLM is deliberately excluded — it is a proxy that
123
+ // authenticates with its own master key.
124
+ const engines = PRESET_NEW_PROVIDERS.filter(
125
+ (preset) => preset.discovery === 'ollama'
126
+ || preset.discovery === 'llama.cpp'
127
+ || preset.discovery === 'lm-studio',
128
+ );
129
+ expect(engines.length).toBe(3);
130
+ for (const preset of engines) {
131
+ expect(preset.auth).toBe('none');
132
+ }
133
+ });
134
+ });
135
+
136
+ describe('configuredProviderSlugs', () => {
137
+ test('names only the providers that already own a slug', () => {
138
+ // omp reports ~60 providers it merely knows; naming those as taken would
139
+ // block the Add Provider dialog from ever configuring one of them.
140
+ const slugs = configuredProviderSlugs([
141
+ provider({ slug: 'openai', status: 'disconnected' }),
142
+ provider({ slug: 'azure', status: 'disconnected', baseUrl: 'https://x.openai.azure.com' }),
143
+ provider({ slug: 'deepseek' }),
144
+ ]);
145
+ expect(slugs).toEqual(['azure', 'deepseek']);
146
+ });
147
+ });
148
+
149
+ describe('providerEmptyReason', () => {
150
+ test('explains a discovery provider without calling it empty', () => {
151
+ expect(providerEmptyReason({ discovery: 'lm-studio' })).toContain('discovers this provider');
152
+ expect(providerEmptyReason({ modelSource: 'discovery' })).toContain('discovers this provider');
153
+ });
154
+
155
+ test('an override names the missing credential rather than only the shape', () => {
156
+ const withKey = providerEmptyReason({ modelSource: 'override' });
157
+ expect(withKey).toContain('Endpoint override');
158
+ expect(withKey).toContain('still needs a credential');
159
+
160
+ // A keyless override has nothing missing, so it must not claim one is.
161
+ const keyless = providerEmptyReason({ modelSource: 'override', auth: 'none' });
162
+ expect(keyless).toContain('Endpoint override');
163
+ expect(keyless).not.toContain('still needs a credential');
164
+ });
165
+
166
+ test('an unexplained empty list stays unexplained', () => {
167
+ // A hosted provider with no models is a problem; inventing a reassuring
168
+ // reason for it would hide the failure.
169
+ expect(providerEmptyReason({})).toBeUndefined();
170
+ expect(providerEmptyReason({ modelSource: 'fetch' })).toBeUndefined();
171
+ });
172
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Which presets the Add Provider picker may offer, and how they are grouped.
8
+ *
9
+ * The rule differs by preset kind, because the two kinds write different things:
10
+ *
11
+ * - A **bundled** preset names a provider omp already ships (Anthropic, Azure,
12
+ * Bedrock, Vertex, Codex). Its entry in `models.yml` is an OVERRIDE — omp
13
+ * keeps serving its own model list and only the endpoint changes — and the
14
+ * writer is add-only, so re-offering it is safe and is the documented way to
15
+ * point a built-in provider at a proxy.
16
+ * - Any other preset creates a provider entry of its own, so a slug that is
17
+ * already DEFINED — connected, or carrying its own `baseUrl` in models.yml or
18
+ * the chamber overlay — is not re-offered; that provider is managed from the
19
+ * sidebar, which can reconnect it and edit its endpoint.
20
+ *
21
+ * `status` alone is the wrong test: omp reports ~60 providers it merely
22
+ * KNOWS (`get_login_providers`), which are `disconnected` with no endpoint and
23
+ * no credential. Treating those as "already added" hid every gateway preset —
24
+ * the one case where a user wants to bring their own key.
25
+ */
26
+
27
+ import type { PresetProviderOption, ProviderItem } from '@/shared/types/settings/provider';
28
+
29
+ /**
30
+ * True when the provider already owns this slug in a config file — connected,
31
+ * or carrying its own `baseUrl` from models.yml or the chamber overlay.
32
+ */
33
+ export function providerOwnsSlug(provider: ProviderItem | undefined): boolean {
34
+ return provider !== undefined && (provider.status === 'connected' || Boolean(provider.baseUrl));
35
+ }
36
+
37
+ /**
38
+ * Slugs already DEFINED in a config file — the ones a new provider entry would
39
+ * collide with. Deliberately not every provider omp reports: it lists ~60 it
40
+ * merely knows (`get_login_providers`), all `disconnected` with no endpoint, and
41
+ * treating those as taken blocks the user from ever configuring them.
42
+ */
43
+ export function configuredProviderSlugs(providers: ProviderItem[]): string[] {
44
+ return providers
45
+ .filter((provider) => provider.slug.trim().length > 0 && providerOwnsSlug(provider))
46
+ .map((provider) => provider.slug);
47
+ }
48
+
49
+ export function buildAvailableProviderPresets(
50
+ providers: ProviderItem[],
51
+ presets: PresetProviderOption[],
52
+ ): PresetProviderOption[] {
53
+ const bySlug = new Map<string, ProviderItem>();
54
+ for (const provider of providers) {
55
+ const slug = provider.slug.trim().toLowerCase();
56
+ if (slug) bySlug.set(slug, provider);
57
+ }
58
+ const seenSlugs = new Set<string>();
59
+ const available: PresetProviderOption[] = [];
60
+
61
+ for (const preset of presets) {
62
+ const slug = preset.slug.trim().toLowerCase();
63
+ if (seenSlugs.has(slug)) continue;
64
+ if (!preset.bundled && providerOwnsSlug(bySlug.get(slug))) continue;
65
+ seenSlugs.add(slug);
66
+ available.push(preset);
67
+ }
68
+
69
+ return available;
70
+ }
71
+
72
+ /** Presets bucketed into their `group`, in the order the presets declare them. */
73
+ export function groupProviderPresets(
74
+ presets: PresetProviderOption[],
75
+ ): Array<{ group: string; options: PresetProviderOption[] }> {
76
+ const groups: Array<{ group: string; options: PresetProviderOption[] }> = [];
77
+ for (const preset of presets) {
78
+ const existing = groups.find((entry) => entry.group === preset.group);
79
+ if (existing) existing.options.push(preset);
80
+ else groups.push({ group: preset.group, options: [preset] });
81
+ }
82
+ return groups;
83
+ }