ompchamber 2.0.2 → 3.0.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 (329) hide show
  1. package/README.md +7 -6
  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-prbmx3yj.js +5123 -0
  6. package/dist/client/static/js/index-ranztes9.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/provider-icon/glyphs.ts +118 -0
  16. package/src/client/components/common/provider-icon/index.tsx +85 -0
  17. package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
  18. package/src/client/components/layout/desktop-layout/index.tsx +2 -0
  19. package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
  20. package/src/client/components/mobile/SessionSidebar.tsx +10 -0
  21. package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +0 -1
  22. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -1
  23. package/src/client/components/mobile/mobile-session-sidebar/List.tsx +3 -0
  24. package/src/client/components/settings/categories/ProviderSettings.tsx +45 -4
  25. package/src/client/components/settings/categories/UsageSettings.tsx +22 -7
  26. package/src/client/components/settings/categories/provider-settings/AddModelModal.tsx +322 -0
  27. package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +152 -56
  28. package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +52 -1
  29. package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +133 -0
  30. package/src/client/components/settings/categories/provider-settings/DialectFields.tsx +136 -0
  31. package/src/client/components/settings/categories/provider-settings/EffortLadder.tsx +89 -0
  32. package/src/client/components/settings/categories/provider-settings/Header.tsx +2 -2
  33. package/src/client/components/settings/categories/provider-settings/LoginModal.tsx +2 -2
  34. package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +22 -2
  35. package/src/client/components/settings/categories/provider-settings/PresetPicker.tsx +82 -0
  36. package/src/client/components/settings/categories/provider-settings/PriceFields.tsx +107 -0
  37. package/src/client/components/settings/categories/provider-settings/ReconnectModal.tsx +43 -22
  38. package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +2 -2
  39. package/src/client/components/settings/categories/usage-settings/LimitsSection.tsx +115 -0
  40. package/src/client/components/settings/categories/usage-settings/ProviderCard.tsx +19 -26
  41. package/src/client/components/settings/categories/usage-settings/ProviderDetail.tsx +29 -22
  42. package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +60 -48
  43. package/src/client/components/settings/categories/usage-settings/format.ts +36 -0
  44. package/src/client/components/settings/categories/usage-settings/providers.ts +52 -28
  45. package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +1 -1
  46. package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +1 -1
  47. package/src/client/components/workspace/diff-panel/Toolbar.tsx +71 -38
  48. package/src/client/components/workspace/diff-panel/index.tsx +139 -53
  49. package/src/client/components/workspace/editor/Tabs.tsx +2 -2
  50. package/src/client/components/workspace/editor/Toolbar.tsx +111 -26
  51. package/src/client/components/workspace/editor/index.tsx +27 -9
  52. package/src/client/components/workspace/model-dropdown/Item.tsx +13 -23
  53. package/src/client/components/workspace/model-dropdown/Panel.tsx +1 -1
  54. package/src/client/components/workspace/model-dropdown/Section.tsx +13 -4
  55. package/src/client/components/workspace/model-dropdown/index.tsx +7 -3
  56. package/src/client/components/workspace/usage-panel/ProviderSelect.tsx +10 -5
  57. package/src/client/components/workspace/usage-panel/index.tsx +36 -18
  58. package/src/client/data/samples/dialogue-session.ts +1 -1
  59. package/src/client/data/samples/sample-tools/other.ts +1 -1
  60. package/src/client/data/settings/provider.ts +141 -9
  61. package/src/client/hooks/editor/use-file-editor.ts +24 -27
  62. package/src/client/hooks/models/use-provider-names.ts +1 -1
  63. package/src/client/hooks/settings/providers/actions.ts +337 -0
  64. package/src/client/hooks/settings/providers/api.ts +69 -0
  65. package/src/client/hooks/settings/providers/index.ts +138 -0
  66. package/src/client/hooks/settings/providers/messages.ts +77 -0
  67. package/src/client/hooks/settings/useUsageReport.ts +8 -3
  68. package/src/client/hooks/ui/theme.ts +3 -3
  69. package/src/client/hooks/workspace/file-tabs.ts +62 -11
  70. package/src/client/stubs/elkjs.ts +6 -1
  71. package/src/server/index.ts +20 -2
  72. package/src/server/lib/assets/font-css.server.ts +86 -0
  73. package/src/server/lib/assets/fonts.server.ts +117 -0
  74. package/src/server/lib/bundler/css.ts +116 -0
  75. package/src/server/lib/lifecycle/shell-route.ts +17 -0
  76. package/src/server/lib/models/provider-listing.server.ts +257 -0
  77. package/src/server/lib/models/provider-registry.server.test.ts +122 -0
  78. package/src/server/lib/models/provider-registry.server.ts +76 -25
  79. package/src/server/lib/omp/config/models-config.ts +11 -0
  80. package/src/server/lib/omp/config/models-validation.test.ts +137 -0
  81. package/src/server/lib/omp/config/models-validation.ts +111 -2
  82. package/src/server/lib/omp/config/provider-seeds.test.ts +167 -0
  83. package/src/server/lib/omp/config/provider-seeds.ts +176 -0
  84. package/src/server/lib/omp/config/providers.ts +94 -100
  85. package/src/server/lib/omp/core/auth-credentials.ts +45 -0
  86. package/src/server/lib/omp/session/{usage.ts → usage/aggregate.ts} +1 -118
  87. package/src/server/lib/omp/session/usage/shaping.ts +130 -0
  88. package/src/server/lib/usage/deepseek.ts +4 -5
  89. package/src/server/lib/usage/kenari.ts +7 -6
  90. package/src/server/lib/usage/omp-usage.server.test.ts +126 -0
  91. package/src/server/lib/usage/omp-usage.server.ts +194 -0
  92. package/src/server/lib/usage/provider-key.ts +81 -3
  93. package/src/server/lib/usage/providers.server.test.ts +47 -0
  94. package/src/server/lib/usage/providers.server.ts +208 -0
  95. package/src/server/lib/usage/vendor-balance.server.test.ts +104 -0
  96. package/src/server/lib/usage/vendor-balance.server.ts +284 -0
  97. package/src/server/plugins/shell.server.ts +56 -0
  98. package/src/server/plugins/ssr.ts +24 -36
  99. package/src/server/plugins/static.test.ts +26 -29
  100. package/src/server/plugins/static.ts +40 -19
  101. package/src/server/routes/settings/index.ts +2 -0
  102. package/src/server/routes/settings/provider-model.ts +107 -0
  103. package/src/server/routes/settings/provider-models.ts +65 -202
  104. package/src/server/routes/settings/providers.ts +22 -7
  105. package/src/server/routes/settings/usage.ts +125 -45
  106. package/src/server/routes/telemetry/tokens.ts +2 -1
  107. package/src/shared/lib/fs/diff-parser.test.ts +116 -0
  108. package/src/shared/lib/fs/diff-parser.ts +86 -33
  109. package/src/shared/lib/models/{provider-connection.ts → provider/connection.ts} +37 -0
  110. package/src/shared/lib/models/provider/dialect.ts +179 -0
  111. package/src/shared/lib/models/provider/glyph.test.ts +116 -0
  112. package/src/shared/lib/models/provider/glyph.ts +200 -0
  113. package/src/shared/lib/models/{provider-models.ts → provider/models.ts} +91 -6
  114. package/src/shared/lib/models/provider/presets.test.ts +172 -0
  115. package/src/shared/lib/models/provider/presets.ts +83 -0
  116. package/src/shared/lib/workspace/file-tab-id.ts +29 -0
  117. package/src/shared/types/settings/provider.ts +81 -0
  118. package/src/shared/types/settings/usage.ts +88 -14
  119. package/tsconfig.json +3 -1
  120. package/dist/client/static/css/index.5f630421b7.css +0 -2
  121. package/dist/client/static/font/KaTeX_AMS-Regular.a79f1c3119.woff2 +0 -0
  122. package/dist/client/static/font/KaTeX_Caligraphic-Bold.ec17d13264.woff2 +0 -0
  123. package/dist/client/static/font/KaTeX_Caligraphic-Regular.55fac25845.woff2 +0 -0
  124. package/dist/client/static/font/KaTeX_Fraktur-Bold.d42a5579b0.woff2 +0 -0
  125. package/dist/client/static/font/KaTeX_Fraktur-Regular.d3c882a649.woff2 +0 -0
  126. package/dist/client/static/font/KaTeX_Main-Bold.c3fb5ac22f.woff2 +0 -0
  127. package/dist/client/static/font/KaTeX_Main-BoldItalic.6f2bb1dff2.woff2 +0 -0
  128. package/dist/client/static/font/KaTeX_Main-Italic.8916142bec.woff2 +0 -0
  129. package/dist/client/static/font/KaTeX_Main-Regular.0462f03bdf.woff2 +0 -0
  130. package/dist/client/static/font/KaTeX_Math-BoldItalic.572d331f69.woff2 +0 -0
  131. package/dist/client/static/font/KaTeX_Math-Italic.f28c23acad.woff2 +0 -0
  132. package/dist/client/static/font/KaTeX_SansSerif-Bold.8c5b5494b6.woff2 +0 -0
  133. package/dist/client/static/font/KaTeX_SansSerif-Italic.3b1e59b3ba.woff2 +0 -0
  134. package/dist/client/static/font/KaTeX_SansSerif-Regular.ba21ed5f84.woff2 +0 -0
  135. package/dist/client/static/font/KaTeX_Script-Regular.03e9641d6f.woff2 +0 -0
  136. package/dist/client/static/font/KaTeX_Size1-Regular.eae34984b3.woff2 +0 -0
  137. package/dist/client/static/font/KaTeX_Size2-Regular.5916a24fa3.woff2 +0 -0
  138. package/dist/client/static/font/KaTeX_Size4-Regular.10d95fd3a2.woff2 +0 -0
  139. package/dist/client/static/font/KaTeX_Typewriter-Regular.a8709e3622.woff2 +0 -0
  140. package/dist/client/static/font/fira-code-latin-400-normal.2655b10c55.woff2 +0 -0
  141. package/dist/client/static/font/fira-code-latin-500-normal.92a991f204.woff2 +0 -0
  142. package/dist/client/static/font/fira-code-latin-600-normal.43788eb7f1.woff2 +0 -0
  143. package/dist/client/static/font/fira-code-latin-700-normal.2667215d33.woff2 +0 -0
  144. package/dist/client/static/js/77.0d81c74cf0.js +0 -139
  145. package/dist/client/static/js/77.0d81c74cf0.js.LICENSE.txt +0 -8
  146. package/dist/client/static/js/async/0.8e59f2eb5b.js +0 -1
  147. package/dist/client/static/js/async/0k.5852e6b4fa.js +0 -1
  148. package/dist/client/static/js/async/0w.9ed36d5860.js +0 -1
  149. package/dist/client/static/js/async/1.f387c12b63.js +0 -1
  150. package/dist/client/static/js/async/12.d091f91c99.js +0 -1
  151. package/dist/client/static/js/async/13.8f810a8a6c.js +0 -1
  152. package/dist/client/static/js/async/19.6fe909d55a.js +0 -160
  153. package/dist/client/static/js/async/1b.23f1141a38.js +0 -1
  154. package/dist/client/static/js/async/1k.973e8e21fc.js +0 -2
  155. package/dist/client/static/js/async/1k.973e8e21fc.js.LICENSE.txt +0 -11
  156. package/dist/client/static/js/async/1p.0d5cddda96.js +0 -1
  157. package/dist/client/static/js/async/2.b589f1733b.js +0 -1
  158. package/dist/client/static/js/async/2v.96165101de.js +0 -1
  159. package/dist/client/static/js/async/3.0cacc08423.js +0 -1
  160. package/dist/client/static/js/async/37.b28b2f61cd.js +0 -1
  161. package/dist/client/static/js/async/3d.5085fb913f.js +0 -1
  162. package/dist/client/static/js/async/3j.357c80ff38.js +0 -2
  163. package/dist/client/static/js/async/3j.357c80ff38.js.LICENSE.txt +0 -11
  164. package/dist/client/static/js/async/3p.bcf32a0983.js +0 -1
  165. package/dist/client/static/js/async/3r.3a69e98ee0.js +0 -64
  166. package/dist/client/static/js/async/3u.1228460e2a.js +0 -1
  167. package/dist/client/static/js/async/3y.3530b824ad.js +0 -198
  168. package/dist/client/static/js/async/3y.3530b824ad.js.LICENSE.txt +0 -5
  169. package/dist/client/static/js/async/4.9442eeb472.js +0 -1
  170. package/dist/client/static/js/async/4e.678285ca55.js +0 -1
  171. package/dist/client/static/js/async/4k.33a0c7eeb4.js +0 -1
  172. package/dist/client/static/js/async/4m.928a1dde85.js +0 -31
  173. package/dist/client/static/js/async/4m.928a1dde85.js.LICENSE.txt +0 -11
  174. package/dist/client/static/js/async/4q.aad20dab25.js +0 -1
  175. package/dist/client/static/js/async/5.ebaf8b89cc.js +0 -36
  176. package/dist/client/static/js/async/51.95bcae98af.js +0 -2
  177. package/dist/client/static/js/async/51.95bcae98af.js.LICENSE.txt +0 -6
  178. package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
  179. package/dist/client/static/js/async/5j.d6407c4248.js +0 -287
  180. package/dist/client/static/js/async/5o.88316d4460.js +0 -62
  181. package/dist/client/static/js/async/6.f034c9b528.js +0 -1
  182. package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
  183. package/dist/client/static/js/async/6o.5b1a06a382.js +0 -1
  184. package/dist/client/static/js/async/7.e4a9288dd0.js +0 -1
  185. package/dist/client/static/js/async/73.308dff4da8.js +0 -1
  186. package/dist/client/static/js/async/7e.a1cc9bfd88.js +0 -1
  187. package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
  188. package/dist/client/static/js/async/7l.fadbbc0583.js +0 -1
  189. package/dist/client/static/js/async/8.5067439d8c.js +0 -1
  190. package/dist/client/static/js/async/8j.ea110663fb.js +0 -1
  191. package/dist/client/static/js/async/a.bce4542392.js +0 -1
  192. package/dist/client/static/js/async/aa.65d3852576.js +0 -1
  193. package/dist/client/static/js/async/ar.9303e5f2e8.js +0 -2
  194. package/dist/client/static/js/async/ar.9303e5f2e8.js.LICENSE.txt +0 -11
  195. package/dist/client/static/js/async/az.d656062b0f.js +0 -1
  196. package/dist/client/static/js/async/b.b4f6f06515.js +0 -1
  197. package/dist/client/static/js/async/bi.09688ba75c.js +0 -1
  198. package/dist/client/static/js/async/bq.586bc738d1.js +0 -1
  199. package/dist/client/static/js/async/c.797d933882.js +0 -1
  200. package/dist/client/static/js/async/c0.0af1e640d3.js +0 -24
  201. package/dist/client/static/js/async/ca.8a52cfd447.js +0 -1
  202. package/dist/client/static/js/async/cf.d34fff5d67.js +0 -1
  203. package/dist/client/static/js/async/ct.b74928f7a3.js +0 -1
  204. package/dist/client/static/js/async/cx.3cb0242a3a.js +0 -226
  205. package/dist/client/static/js/async/d.a5fc8fe1b3.js +0 -1
  206. package/dist/client/static/js/async/da.f62c70f6c8.js +0 -1
  207. package/dist/client/static/js/async/dp.1725d6432c.js +0 -1
  208. package/dist/client/static/js/async/e.93214d622b.js +0 -79
  209. package/dist/client/static/js/async/f.ab50467719.js +0 -19
  210. package/dist/client/static/js/async/f.ab50467719.js.LICENSE.txt +0 -12
  211. package/dist/client/static/js/async/f7.46971e9d6d.js +0 -2
  212. package/dist/client/static/js/async/f7.46971e9d6d.js.LICENSE.txt +0 -6
  213. package/dist/client/static/js/async/f8.bbf0dbae70.js +0 -1
  214. package/dist/client/static/js/async/g.cb81980498.js +0 -2
  215. package/dist/client/static/js/async/g.cb81980498.js.LICENSE.txt +0 -11
  216. package/dist/client/static/js/async/h.4e790471e4.js +0 -27
  217. package/dist/client/static/js/async/h1.393f066d09.js +0 -1
  218. package/dist/client/static/js/async/h3.216e9798ab.js +0 -148
  219. package/dist/client/static/js/async/hd.0da931b622.js +0 -1
  220. package/dist/client/static/js/async/hk.ba7fb04d41.js +0 -1
  221. package/dist/client/static/js/async/hn.b645041e00.js +0 -1
  222. package/dist/client/static/js/async/ho.475a5e1ae4.js +0 -1
  223. package/dist/client/static/js/async/hp.e9fe2e395a.js +0 -2
  224. package/dist/client/static/js/async/hp.e9fe2e395a.js.LICENSE.txt +0 -17
  225. package/dist/client/static/js/async/hs.e7c59d8090.js +0 -41
  226. package/dist/client/static/js/async/i.7c148e8440.js +0 -1
  227. package/dist/client/static/js/async/id.573df20f29.js +0 -1
  228. package/dist/client/static/js/async/j.ab925882d8.js +0 -1
  229. package/dist/client/static/js/async/jd.63886a1f09.js +0 -1
  230. package/dist/client/static/js/async/jg.d86ebd4013.js +0 -86
  231. package/dist/client/static/js/async/jl.874d352278.js +0 -1
  232. package/dist/client/static/js/async/jll.11313b455a.js +0 -1
  233. package/dist/client/static/js/async/js.67182759ef.js +0 -1
  234. package/dist/client/static/js/async/jsc.23b70d6f40.js +0 -1
  235. package/dist/client/static/js/async/jse.334588ff5d.js +0 -1
  236. package/dist/client/static/js/async/jz.72eb929106.js +0 -77
  237. package/dist/client/static/js/async/k.3210286401.js +0 -217
  238. package/dist/client/static/js/async/ka.c1990ecaa3.js +0 -115
  239. package/dist/client/static/js/async/katex-vendor.de6a87c308.js +0 -1
  240. package/dist/client/static/js/async/ku.29c8a59d0c.js +0 -1
  241. package/dist/client/static/js/async/ky.8dab1dade0.js +0 -1
  242. package/dist/client/static/js/async/l.12dd34e1be.js +0 -1
  243. package/dist/client/static/js/async/l9.949710adaf.js +0 -83
  244. package/dist/client/static/js/async/l9.949710adaf.js.LICENSE.txt +0 -13
  245. package/dist/client/static/js/async/ls.9d9a01026c.js +0 -104
  246. package/dist/client/static/js/async/m.eb2e6752e2.js +0 -28
  247. package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +0 -11
  248. package/dist/client/static/js/async/m3.d2b05f9604.js +0 -2
  249. package/dist/client/static/js/async/m3.d2b05f9604.js.LICENSE.txt +0 -6
  250. package/dist/client/static/js/async/ma.2c261b04dd.js +0 -2
  251. package/dist/client/static/js/async/ma.2c261b04dd.js.LICENSE.txt +0 -6
  252. package/dist/client/static/js/async/me.8ace414f80.js +0 -23
  253. package/dist/client/static/js/async/ms.98960af4eb.js +0 -67
  254. package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +0 -12
  255. package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
  256. package/dist/client/static/js/async/n.a9f1c51701.js +0 -1
  257. package/dist/client/static/js/async/na.a36cd532d6.js +0 -1
  258. package/dist/client/static/js/async/nk.fc7e58325e.js +0 -1
  259. package/dist/client/static/js/async/o.36353dfb88.js +0 -1
  260. package/dist/client/static/js/async/oc.b73042e8bd.js +0 -8
  261. package/dist/client/static/js/async/og.8eb577893a.js +0 -193
  262. package/dist/client/static/js/async/p.48ecfcd14e.js +0 -1
  263. package/dist/client/static/js/async/px.fc6877924b.js +0 -1
  264. package/dist/client/static/js/async/q.22588845a9.js +0 -21
  265. package/dist/client/static/js/async/q.22588845a9.js.LICENSE.txt +0 -6
  266. package/dist/client/static/js/async/q5.97dc1481b6.js +0 -286
  267. package/dist/client/static/js/async/qs.2a9ddb3c72.js +0 -1
  268. package/dist/client/static/js/async/r.51010ad87c.js +0 -1
  269. package/dist/client/static/js/async/rd.cb6bf495bb.js +0 -2
  270. package/dist/client/static/js/async/rd.cb6bf495bb.js.LICENSE.txt +0 -4
  271. package/dist/client/static/js/async/s.0f395cdda5.js +0 -94
  272. package/dist/client/static/js/async/t.4dca0e2ba8.js +0 -1
  273. package/dist/client/static/js/async/t9.d44f57d518.js +0 -1
  274. package/dist/client/static/js/async/ta.973534bbec.js +0 -1
  275. package/dist/client/static/js/async/tr.e878658140.js +0 -309
  276. package/dist/client/static/js/async/u.0871ac5b22.js +0 -1
  277. package/dist/client/static/js/async/ud.3065bfee31.js +0 -2
  278. package/dist/client/static/js/async/ud.3065bfee31.js.LICENSE.txt +0 -4
  279. package/dist/client/static/js/async/up.ea990da0ff.js +0 -32
  280. package/dist/client/static/js/async/uw.8890abbabc.js +0 -1
  281. package/dist/client/static/js/async/v.7d28d3c241.js +0 -34
  282. package/dist/client/static/js/async/vp.9b13bf8610.js +0 -1
  283. package/dist/client/static/js/async/vt.42416b707d.js +0 -1
  284. package/dist/client/static/js/async/w.08ac210f60.js +0 -1
  285. package/dist/client/static/js/async/wt.ad92393a90.js +0 -1
  286. package/dist/client/static/js/async/x.563f72ca6b.js +0 -1
  287. package/dist/client/static/js/async/x0.475516418f.js +0 -1
  288. package/dist/client/static/js/async/x1.da8687159b.js +0 -90
  289. package/dist/client/static/js/async/xe.da8dcc2015.js +0 -2
  290. package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +0 -11
  291. package/dist/client/static/js/async/xl.a3c562a58c.js +0 -1
  292. package/dist/client/static/js/async/xn.6510c0abe2.js +0 -2
  293. package/dist/client/static/js/async/xn.6510c0abe2.js.LICENSE.txt +0 -6
  294. package/dist/client/static/js/async/xn1.83a7784fe1.js +0 -10
  295. package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +0 -11
  296. package/dist/client/static/js/async/xnl.feb0021e50.js +0 -2
  297. package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +0 -12
  298. package/dist/client/static/js/async/y.1a9ed7a5d8.js +0 -1
  299. package/dist/client/static/js/async/y6.43c57defe4.js +0 -1
  300. package/dist/client/static/js/async/yb.8c1342401d.js +0 -1
  301. package/dist/client/static/js/async/ybq.4491f2f2dc.js +0 -1
  302. package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
  303. package/dist/client/static/js/async/yq.917ddd7ba2.js +0 -1
  304. package/dist/client/static/js/async/ys.00687cf82d.js +0 -38
  305. package/dist/client/static/js/async/yv.d99d829136.js +0 -1
  306. package/dist/client/static/js/async/z.56f61a4374.js +0 -1
  307. package/dist/client/static/js/async/zi.5fdb07b86a.js +0 -24
  308. package/dist/client/static/js/async/zj.654f37e8ff.js +0 -1
  309. package/dist/client/static/js/async/zn.966d6f1cc6.js +0 -1
  310. package/dist/client/static/js/async/zo.26213b40c0.js +0 -1
  311. package/dist/client/static/js/async/zx.e9f4105e00.js +0 -1
  312. package/dist/client/static/js/index.e6a257402a.js +0 -38
  313. package/dist/client/static/js/index.e6a257402a.js.LICENSE.txt +0 -4
  314. package/dist/client/static/js/markdown-vendor.71e43278b3.js +0 -144
  315. package/dist/client/static/js/shiki-vendor.cb8452ddcd.js +0 -1
  316. package/src/client/components/settings/categories/provider-settings/Icons.tsx +0 -35
  317. package/src/client/hooks/settings/providers.ts +0 -326
  318. package/src/server/plugins/dev-assets.ts +0 -49
  319. package/src/shared/lib/code/editor-utils.ts +0 -16
  320. package/src/shared/lib/models/provider-presets.ts +0 -29
  321. /package/dist/client/{apple-touch-icon.png → static/apple-touch-icon-dskx5sr3.png} +0 -0
  322. /package/dist/client/{icon-192x192.png → static/icon-192x192-6gn5cr2f.png} +0 -0
  323. /package/dist/client/{icon.svg → static/icon-d07z9dtz.svg} +0 -0
  324. /package/dist/client/{manifest.webmanifest → static/manifest-bnyy75kt.webmanifest} +0 -0
  325. /package/{dist/client → public}/icon-512x512.png +0 -0
  326. /package/{dist/client → public}/icon-maskable-512x512.png +0 -0
  327. /package/{dist/client → public}/sw.js +0 -0
  328. /package/src/shared/lib/models/{provider-cleanup.ts → provider/cleanup.ts} +0 -0
  329. /package/src/shared/lib/models/{provider-label.ts → provider/label.ts} +0 -0
@@ -1,21 +1,27 @@
1
1
  import { useEffect, useState } from 'preact/hooks';
2
2
  import { Check, DownloadCloud, Globe, Key, Layers } from 'lucide-preact';
3
3
  import type { ProviderItem } from '@/shared/types';
4
- import type { PresetProviderOption } from '@/shared/types/settings/provider';
5
- import { fetchProviderModelsRemote } from '@/shared/lib/models/provider-models';
6
- import { ProviderIcon } from '@/client/components/settings/categories/provider-settings/Icons';
4
+ import type {
5
+ OmpProviderApi,
6
+ PresetProviderOption,
7
+ ProviderAuthMode,
8
+ ProviderDiscoveryType,
9
+ ProviderModelSource,
10
+ } from '@/shared/types/settings/provider';
11
+ import { fetchProviderModelsRemote } from '@/shared/lib/models/provider/models';
12
+ import { inferProviderApi } from '@/shared/lib/models/provider/dialect';
7
13
  import { Modal } from '@/client/components/common/Modal';
14
+ import { DialectFields } from '@/client/components/settings/categories/provider-settings/DialectFields';
15
+ import { PresetPicker } from '@/client/components/settings/categories/provider-settings/PresetPicker';
8
16
 
9
17
  interface AddProviderModalProps {
10
18
  isOpen: boolean;
11
19
  onClose: () => void;
12
- onAddProvider: (newProvider: ProviderItem, options: { fetchedCount: number }) => void;
20
+ onAddProvider: (newProvider: ProviderItem, options: { fetchedCount: number; ompNote?: string }) => void;
13
21
  presets: PresetProviderOption[];
14
22
  /**
15
23
  * Slugs already present in the omp registry. A new provider's slug becomes a
16
- * `models.yml` key, so reusing one silently overwrites that provider's
17
- * `baseUrl`, `apiKey`, `api` and per-model metadata — the entry is written
18
- * under the same key, not merged into it.
24
+ * models.yml key, so reusing one would overwrite that provider's entry.
19
25
  */
20
26
  existingSlugs: string[];
21
27
  }
@@ -23,6 +29,40 @@ interface AddProviderModalProps {
23
29
  /** Slug shape omp accepts as a `models.yml` provider key. */
24
30
  const SLUG_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
25
31
 
32
+ /** The dialect a preset seeds, with the model-source its shape implies. */
33
+ function presetDefaults(preset: PresetProviderOption | undefined) {
34
+ const api: OmpProviderApi = preset?.api ?? inferProviderApi(preset?.defaultUrl ?? '');
35
+ return {
36
+ api,
37
+ auth: preset?.auth ?? ('apiKey' as ProviderAuthMode),
38
+ discovery: preset?.discovery ?? ('openai-models-list' as ProviderDiscoveryType),
39
+ source: (preset?.discovery
40
+ ? 'discovery'
41
+ : preset?.bundled
42
+ ? 'override'
43
+ : 'fetch') as ProviderModelSource,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Add Provider.
49
+ *
50
+ * The form exists to write ONE `models.yml` provider entry, so it asks for
51
+ * exactly the fields omp reads: the endpoint, the wire dialect, the auth mode,
52
+ * and where the model list comes from. Everything else is derived.
53
+ *
54
+ * Two shapes the previous version could not express are the point of this
55
+ * dialog:
56
+ *
57
+ * - A provider omp already bundles (Azure, Bedrock, Vertex, Codex) is written
58
+ * as an OVERRIDE entry: omp keeps its own model list and only the endpoint
59
+ * changes. Writing ids for it would shadow the bundled catalog with a
60
+ * snapshot.
61
+ * - A provider omp can discover (Ollama, LM Studio, llama.cpp, LiteLLM, any
62
+ * OpenAI-compatible endpoint) is registered with an EMPTY model list and a
63
+ * `discovery` block, so omp lists the models live instead of serving a frozen
64
+ * copy that goes stale the moment the endpoint adds one.
65
+ */
26
66
  export function AddProviderModal({
27
67
  isOpen,
28
68
  onClose,
@@ -32,16 +72,26 @@ export function AddProviderModal({
32
72
  }: AddProviderModalProps) {
33
73
  const presetList = presets;
34
74
  const initialPreset = presetList[0];
75
+ const initial = presetDefaults(initialPreset);
35
76
  const [selectedPresetId, setSelectedPresetId] = useState(initialPreset?.id || 'openai');
36
77
  const [name, setName] = useState(initialPreset?.name || 'OpenAI');
37
78
  const [baseUrl, setBaseUrl] = useState(initialPreset?.defaultUrl || 'https://api.openai.com/v1');
38
79
  const [apiKey, setApiKey] = useState('');
80
+ const [api, setApi] = useState<OmpProviderApi>(initial.api);
81
+ const [auth, setAuth] = useState<ProviderAuthMode>(initial.auth);
82
+ const [source, setSource] = useState<ProviderModelSource>(initial.source);
83
+ const [discovery, setDiscovery] = useState<ProviderDiscoveryType>(initial.discovery);
39
84
  const [isSubmitting, setIsSubmitting] = useState(false);
40
85
 
41
86
  const handleSelectPreset = (preset: PresetProviderOption) => {
87
+ const defaults = presetDefaults(preset);
42
88
  setSelectedPresetId(preset.id);
43
89
  setName(preset.name);
44
90
  setBaseUrl(preset.defaultUrl);
91
+ setApi(defaults.api);
92
+ setAuth(defaults.auth);
93
+ setDiscovery(defaults.discovery);
94
+ setSource(defaults.source);
45
95
  };
46
96
 
47
97
  useEffect(() => {
@@ -62,21 +112,37 @@ export function AddProviderModal({
62
112
  : trimmedName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
63
113
  ).toLowerCase();
64
114
  const takenSlugs = new Set(existingSlugs.map((value) => value.trim().toLowerCase()));
115
+ // A bundled preset names a provider omp already ships, and its entry in
116
+ // models.yml is an add-only OVERRIDE — reusing that slug is the point.
117
+ const isBundledOverride = selectedPreset?.bundled === true;
65
118
  const slugError = !slug
66
119
  ? 'Enter a provider name to derive an id.'
67
120
  : !SLUG_PATTERN.test(slug)
68
121
  ? 'The derived id must start with a letter or digit.'
69
- : takenSlugs.has(slug)
122
+ : takenSlugs.has(slug) && !isBundledOverride
70
123
  ? `"${slug}" is already registered in models.yml — connecting it would overwrite that provider. Pick another name.`
71
124
  : '';
125
+ // omp requires a key only for a provider that carries MODELS. An endpoint
126
+ // override (omp keeps its own model list) and a discovery shell are both
127
+ // valid without one — that is how a keyless local server and a proxy that
128
+ // authenticates through `/login` are declared — so the key is optional there
129
+ // rather than hidden, since a proxy usually still wants its own bearer.
130
+ const showsApiKey = auth === 'apiKey';
131
+ const requiresApiKey = showsApiKey && source === 'fetch';
132
+ const keyError = requiresApiKey && !apiKey.trim()
133
+ ? 'Enter the API key, or set Auth to "No auth" for a keyless local server.'
134
+ : '';
135
+ const canSubmit = Boolean(trimmedName) && !slugError && !keyError && !isSubmitting;
72
136
 
73
137
  const handleSubmit = async (e: { preventDefault: () => void }) => {
74
138
  e.preventDefault();
75
- if (!trimmedName || presetList.length === 0 || isSubmitting || slugError) return;
139
+ if (!canSubmit) return;
76
140
  setIsSubmitting(true);
77
141
 
78
142
  try {
79
143
  const timestamp = Date.now();
144
+ const trimmedUrl = baseUrl.trim();
145
+ const trimmedKey = apiKey.trim();
80
146
  const newProvider: ProviderItem = {
81
147
  id: `provider-${timestamp}`,
82
148
  name: trimmedName,
@@ -84,17 +150,41 @@ export function AddProviderModal({
84
150
  icon: selectedPreset?.icon || 'custom',
85
151
  status: 'connected',
86
152
  configuredIn: 'auth credentials',
87
- baseUrl: baseUrl.trim(),
88
- apiKey: apiKey.trim() || 'sk-custom-••••••••••••••••••••••••',
153
+ credentialSource: 'models.yml',
154
+ baseUrl: trimmedUrl,
155
+ ...(trimmedKey ? { apiKey: trimmedKey } : {}),
156
+ api,
157
+ ...(auth !== 'apiKey' ? { auth } : {}),
158
+ ...(source === 'discovery' ? { discovery } : {}),
159
+ modelSource: source,
89
160
  models: [],
90
161
  };
91
162
 
92
- const fetchResult = await fetchProviderModelsRemote(baseUrl.trim(), apiKey.trim() || undefined, newProvider.slug, true);
163
+ const fetchResult = await fetchProviderModelsRemote({
164
+ baseUrl: trimmedUrl,
165
+ apiKey: trimmedKey || undefined,
166
+ providerSlug: newProvider.slug,
167
+ persistToOmp: true,
168
+ api,
169
+ auth,
170
+ ...(source === 'discovery' ? { discovery } : {}),
171
+ ...(source === 'override' ? { registerOnly: true } : {}),
172
+ });
173
+
93
174
  if (fetchResult.ok && fetchResult.models) {
94
175
  newProvider.models = fetchResult.models;
95
- onAddProvider(newProvider, { fetchedCount: fetchResult.models.length });
176
+ onAddProvider(newProvider, {
177
+ fetchedCount: fetchResult.models.length,
178
+ ompNote: fetchResult.omp?.reason,
179
+ });
96
180
  } else {
97
- onAddProvider(newProvider, { fetchedCount: -1 });
181
+ // The listing failed but the provider may still have been registered
182
+ // (discovery/override shapes never list). Report what omp said rather
183
+ // than a bare "failed" that hides a written file.
184
+ onAddProvider(newProvider, {
185
+ fetchedCount: -1,
186
+ ompNote: fetchResult.omp?.written ? undefined : fetchResult.error,
187
+ });
98
188
  }
99
189
 
100
190
  onClose();
@@ -113,6 +203,7 @@ export function AddProviderModal({
113
203
  </div>
114
204
  }
115
205
  form={{ onSubmit: handleSubmit, className: 'p-5 space-y-4' }}
206
+ maxWidthClass="max-w-lg"
116
207
  footer={
117
208
  <div className="pt-2 flex items-center justify-end gap-2 border-t border-ink/10">
118
209
  <button
@@ -124,13 +215,13 @@ export function AddProviderModal({
124
215
  </button>
125
216
  <button
126
217
  type="submit"
127
- disabled={isSubmitting || presetList.length === 0 || Boolean(slugError)}
218
+ disabled={!canSubmit}
128
219
  className="px-3.5 py-1.5 rounded-md bg-ink text-canvas text-xs font-medium hover:opacity-90 transition-opacity cursor-pointer disabled:opacity-50 flex items-center gap-1.5"
129
220
  >
130
221
  {isSubmitting ? (
131
222
  <>
132
223
  <DownloadCloud size={14} className="animate-pulse" />
133
- <span>Fetching models...</span>
224
+ <span>{source === 'fetch' ? 'Fetching models...' : 'Registering...'}</span>
134
225
  </>
135
226
  ) : (
136
227
  <>
@@ -147,31 +238,11 @@ export function AddProviderModal({
147
238
  <label className="block text-xs font-semibold text-ink mb-1.5">
148
239
  Provider Preset
149
240
  </label>
150
- <div className="grid grid-cols-2 gap-2 max-h-36 scrollbar-overlay-container scrollbar-overlay-static pr-1">
151
- {presetList.length === 0 && (
152
- <p className="col-span-2 px-2 py-3 text-xs text-ink/50">
153
- All known provider types are already connected.
154
- </p>
155
- )}
156
- {presetList.map((preset) => {
157
- const isSelected = preset.id === selectedPresetId;
158
- return (
159
- <button
160
- key={preset.id}
161
- type="button"
162
- onClick={() => handleSelectPreset(preset)}
163
- className={`flex items-center gap-2 p-2 rounded-lg border text-left text-xs transition-colors cursor-pointer ${
164
- isSelected
165
- ? 'border-ink/50 bg-ink/5 font-semibold text-ink shadow-2xs'
166
- : 'border-ink/15 hover:border-ink/30 text-ink/70'
167
- }`}
168
- >
169
- <ProviderIcon icon={preset.icon} size={14} />
170
- <span className="truncate">{preset.name}</span>
171
- </button>
172
- );
173
- })}
174
- </div>
241
+ <PresetPicker
242
+ presets={presetList}
243
+ selectedPresetId={selectedPresetId}
244
+ onSelectPreset={handleSelectPreset}
245
+ />
175
246
  </div>
176
247
 
177
248
  {/* Provider Name */}
@@ -217,22 +288,47 @@ export function AddProviderModal({
217
288
  </div>
218
289
  </div>
219
290
 
220
- {/* API Key */}
221
- <div>
222
- <label className="block text-xs font-semibold text-ink mb-1">
223
- API Key (stored securely)
224
- </label>
225
- <div className="relative flex items-center">
226
- <Key size={13} className="absolute left-2.5 text-ink/40 pointer-events-none" />
227
- <input
228
- type="password"
229
- value={apiKey}
230
- onChange={(e) => setApiKey(e.currentTarget.value)}
231
- placeholder="sk-••••••••••••••••"
232
- className="w-full bg-paper border border-ink/20 rounded-md pl-8 pr-3 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
233
- />
291
+ <DialectFields
292
+ api={api}
293
+ onApiChange={setApi}
294
+ auth={auth}
295
+ onAuthChange={setAuth}
296
+ source={source}
297
+ onSourceChange={setSource}
298
+ discovery={discovery}
299
+ onDiscoveryChange={setDiscovery}
300
+ />
301
+
302
+ {/* API Key — hidden only for a keyless provider, which omp accepts
303
+ without a credential at all. */}
304
+ {showsApiKey && (
305
+ <div>
306
+ <label className="block text-xs font-semibold text-ink mb-1">
307
+ API Key (stored securely)
308
+ {!requiresApiKey && <span className="font-normal text-ink/40"> — optional</span>}
309
+ </label>
310
+ <div className="relative flex items-center">
311
+ <Key size={13} className="absolute left-2.5 text-ink/40 pointer-events-none" />
312
+ <input
313
+ type="password"
314
+ value={apiKey}
315
+ onChange={(e) => setApiKey(e.currentTarget.value)}
316
+ placeholder="sk-••••••••••••••••"
317
+ className="w-full bg-paper border border-ink/20 rounded-md pl-8 pr-3 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
318
+ />
319
+ </div>
320
+ {keyError && (
321
+ <p className="text-[11px] text-error mt-1">{keyError}</p>
322
+ )}
323
+ <p className="text-[10px] text-ink/40 mt-1">
324
+ {requiresApiKey
325
+ ? 'May name an environment variable (e.g. '
326
+ : 'Leave empty to keep using the provider’s existing credential. May name an environment variable (e.g. '}
327
+ <span className="font-mono">MY_KEY</span>) or run a command with a leading{' '}
328
+ <span className="font-mono">!</span>.
329
+ </p>
234
330
  </div>
235
- </div>
331
+ )}
236
332
  </Modal>
237
333
  );
238
334
  }
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'preact/hooks';
2
- import { AlertCircle, Ban, Check, Info } from 'lucide-preact';
2
+ import { AlertCircle, Ban, Check, Info, Trash2 } from 'lucide-preact';
3
3
  import type { ProviderItem } from '@/shared/types';
4
4
 
5
5
  interface ProviderAuthSectionProps {
@@ -8,6 +8,8 @@ interface ProviderAuthSectionProps {
8
8
  onToggleDisconnect: () => void;
9
9
  /** Adds/removes the provider in omp's config.yml `disabledProviders`. */
10
10
  onToggleDisabled: () => void;
11
+ /** Opens the delete confirmation for a provider registered in models.yml. */
12
+ onDelete: () => void;
11
13
  }
12
14
 
13
15
  export function ProviderAuthSection({
@@ -15,6 +17,7 @@ export function ProviderAuthSection({
15
17
  onOpenReconnectModal,
16
18
  onToggleDisconnect,
17
19
  onToggleDisabled,
20
+ onDelete,
18
21
  }: ProviderAuthSectionProps) {
19
22
  const [showInfoTooltip, setShowInfoTooltip] = useState(false);
20
23
  const isConnected = provider.status === 'connected';
@@ -82,6 +85,22 @@ export function ProviderAuthSection({
82
85
  >
83
86
  {isConnected ? 'reconnect' : 'connect'}
84
87
  </button>
88
+
89
+ {/* Delete only exists for a provider that HAS a models.yml entry:
90
+ there is nothing in the file to remove otherwise, and the button
91
+ would promise an action it cannot perform. A login provider's
92
+ credential is never touched by this. */}
93
+ {provider.inModelsYml && (
94
+ <button
95
+ type="button"
96
+ onClick={onDelete}
97
+ title="Remove this provider's models.yml entry"
98
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md bg-error/10 hover:bg-error/20 text-error text-xs font-medium transition-colors cursor-pointer border border-error/30"
99
+ >
100
+ <Trash2 size={12} />
101
+ <span>delete</span>
102
+ </button>
103
+ )}
85
104
  </div>
86
105
  </div>
87
106
  </div>
@@ -113,6 +132,38 @@ export function ProviderAuthSection({
113
132
  </span>
114
133
  )}
115
134
  </div>
135
+
136
+ {/* Dialect: the wire api and auth mode omp will use. A provider whose
137
+ endpoint is right but whose dialect is wrong fails on every request,
138
+ so it belongs next to the connection status rather than in a log. */}
139
+ {(provider.api || provider.auth || provider.discovery) && (
140
+ <div className="flex flex-wrap items-center gap-1.5 pt-0.5">
141
+ {provider.api && (
142
+ <span
143
+ className="px-1.5 py-0.5 rounded border border-ink/15 text-[10px] font-mono text-ink/70"
144
+ title="Wire API omp uses for this provider"
145
+ >
146
+ {provider.api}
147
+ </span>
148
+ )}
149
+ {provider.auth && provider.auth !== 'apiKey' && (
150
+ <span
151
+ className="px-1.5 py-0.5 rounded border border-ink/15 text-[10px] font-mono text-ink/70"
152
+ title="Auth mode from models.yml"
153
+ >
154
+ auth: {provider.auth}
155
+ </span>
156
+ )}
157
+ {provider.discovery && (
158
+ <span
159
+ className="px-1.5 py-0.5 rounded border border-ink/15 text-[10px] font-mono text-ink/70"
160
+ title="omp lists this provider's models live"
161
+ >
162
+ discovery: {provider.discovery}
163
+ </span>
164
+ )}
165
+ </div>
166
+ )}
116
167
  </div>
117
168
 
118
169
  <div className="border-b border-ink/10" />
@@ -0,0 +1,133 @@
1
+ import { useEffect } from 'preact/hooks';
2
+ import { AlertTriangle, Loader2, Trash2 } from 'lucide-preact';
3
+ import type { ProviderItem } from '@/shared/types';
4
+ import { ProviderIcon } from '@/client/components/common/provider-icon';
5
+
6
+ interface DeleteProviderModalProps {
7
+ isOpen: boolean;
8
+ provider: ProviderItem | null;
9
+ /** True while the delete request is in flight. */
10
+ isDeleting: boolean;
11
+ onClose: () => void;
12
+ onConfirm: () => void;
13
+ }
14
+
15
+ /**
16
+ * Confirmation for deleting a provider registered in `models.yml`.
17
+ *
18
+ * The dialog names what actually disappears — the file entry, its model list,
19
+ * and the endpoint/credential stored on it — because the action is not
20
+ * reversible from the UI: the endpoint and API key are only ever shown masked,
21
+ * so a deleted provider has to be reconfigured from scratch.
22
+ *
23
+ * It also says what is NOT touched. A provider omp ships (a login provider, or
24
+ * a bundled catalog entry) keeps its own configuration; only the `models.yml`
25
+ * override goes, and that distinction is the difference between "I can add this
26
+ * back" and "I just deleted my Anthropic login".
27
+ */
28
+ export function DeleteProviderModal({
29
+ isOpen,
30
+ provider,
31
+ isDeleting,
32
+ onClose,
33
+ onConfirm,
34
+ }: DeleteProviderModalProps) {
35
+ useEffect(() => {
36
+ // No dismissing mid-delete: the modal resolves itself on success, and an
37
+ // early close would let a second delete be queued against a stale list.
38
+ const handler = (e: globalThis.KeyboardEvent) => {
39
+ if (e.key !== 'Escape' || isDeleting) return;
40
+ e.preventDefault();
41
+ onClose();
42
+ };
43
+ window.addEventListener('keydown', handler);
44
+ return () => window.removeEventListener('keydown', handler);
45
+ }, [onClose, isDeleting]);
46
+
47
+ if (!isOpen || !provider) return null;
48
+
49
+ const modelCount = provider.models.length;
50
+
51
+ return (
52
+ <div
53
+ className="fixed inset-0 z-[70] flex items-center justify-center bg-ink/40 backdrop-blur-[2px] p-4"
54
+ onClick={isDeleting ? undefined : onClose}
55
+ role="dialog"
56
+ aria-modal="true"
57
+ aria-label="Confirm provider deletion"
58
+ >
59
+ <div
60
+ className="bg-paper border border-ink/20 rounded-xl shadow-2xl w-[min(94vw,460px)] overflow-hidden text-ink"
61
+ onClick={(e) => e.stopPropagation()}
62
+ >
63
+ <div className="flex items-center gap-3 px-5 py-4 border-b border-ink/10">
64
+ <Trash2 size={16} className="text-error shrink-0" />
65
+ <div className="min-w-0">
66
+ <h2 className="text-sm font-semibold text-ink">Delete provider?</h2>
67
+ <p className="text-[11px] text-ink/50 truncate">
68
+ <span className="font-mono">{provider.slug}</span>
69
+ </p>
70
+ </div>
71
+ </div>
72
+
73
+ <div className="px-5 py-4 space-y-3">
74
+ <div className="flex items-center gap-2.5 px-3 py-2.5 rounded-lg bg-ink/5 border border-ink/10">
75
+ <ProviderIcon icon={provider.icon} slug={provider.slug} name={provider.name} size={18} />
76
+ <span className="text-xs font-medium text-ink truncate">{provider.name}</span>
77
+ </div>
78
+
79
+ <ul className="text-[11.5px] leading-relaxed text-ink/70 space-y-1.5">
80
+ <li>
81
+ Its entry is removed from{' '}
82
+ <span className="font-mono text-ink">~/.omp/agent/models.yml</span>
83
+ {modelCount > 0
84
+ ? `, along with its ${modelCount} registered model${modelCount === 1 ? '' : 's'}.`
85
+ : '.'}
86
+ </li>
87
+ <li>The endpoint and API key stored on that entry are removed with it — they are shown masked here, so you would need to enter them again.</li>
88
+ {provider.credentialSource === 'omp-auth' && (
89
+ <li className="text-ink/60">
90
+ Your omp login for <span className="font-mono">{provider.slug}</span> is NOT touched — only the models.yml
91
+ entry above. The provider stays available through that login.
92
+ </li>
93
+ )}
94
+ </ul>
95
+
96
+ <div className="flex items-start gap-2 px-3 py-2 rounded-md border border-error/30 bg-error/5 text-[11px] text-ink/75">
97
+ <AlertTriangle size={13} className="mt-0.5 shrink-0 text-error" />
98
+ <span>This cannot be undone from the UI. Re-adding the provider starts from an empty endpoint and key.</span>
99
+ </div>
100
+ </div>
101
+
102
+ <div className="flex items-center justify-end gap-2 px-5 py-3.5 border-t border-ink/10">
103
+ <button
104
+ type="button"
105
+ onClick={onClose}
106
+ disabled={isDeleting}
107
+ className="px-3.5 py-1.5 rounded-md border border-ink/20 text-xs font-medium text-ink hover:bg-ink/5 transition-colors cursor-pointer disabled:opacity-50"
108
+ >
109
+ Cancel
110
+ </button>
111
+ <button
112
+ type="button"
113
+ onClick={onConfirm}
114
+ disabled={isDeleting}
115
+ className="px-3.5 py-1.5 rounded-md bg-error text-canvas text-xs font-medium hover:opacity-90 transition-opacity cursor-pointer disabled:opacity-50 flex items-center gap-1.5"
116
+ >
117
+ {isDeleting ? (
118
+ <>
119
+ <Loader2 size={13} className="animate-spin" />
120
+ <span>Deleting...</span>
121
+ </>
122
+ ) : (
123
+ <>
124
+ <Trash2 size={13} />
125
+ <span>Delete provider</span>
126
+ </>
127
+ )}
128
+ </button>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ );
133
+ }
@@ -0,0 +1,136 @@
1
+ import type {
2
+ OmpProviderApi,
3
+ ProviderAuthMode,
4
+ ProviderDiscoveryType,
5
+ ProviderModelSource,
6
+ } from '@/shared/types/settings/provider';
7
+ import {
8
+ PROVIDER_APIS,
9
+ PROVIDER_API_LABELS,
10
+ PROVIDER_AUTH_MODES,
11
+ PROVIDER_DISCOVERY_TYPES,
12
+ } from '@/shared/lib/models/provider/dialect';
13
+
14
+ interface DialectFieldsProps {
15
+ api: OmpProviderApi;
16
+ onApiChange: (api: OmpProviderApi) => void;
17
+ auth: ProviderAuthMode;
18
+ onAuthChange: (auth: ProviderAuthMode) => void;
19
+ source: ProviderModelSource;
20
+ onSourceChange: (source: ProviderModelSource) => void;
21
+ discovery: ProviderDiscoveryType;
22
+ onDiscoveryChange: (discovery: ProviderDiscoveryType) => void;
23
+ }
24
+
25
+ const SOURCE_OPTIONS: ReadonlyArray<{ value: ProviderModelSource; label: string; hint: string }> = [
26
+ { value: 'fetch', label: 'Fetch now', hint: 'List the endpoint here and write the ids into models.yml' },
27
+ { value: 'discovery', label: 'Let omp discover', hint: 'omp lists the models live on every run' },
28
+ { value: 'override', label: 'Endpoint only', hint: 'Keep omp’s own model list, change only the URL' },
29
+ ];
30
+
31
+ const fieldClass = 'w-full text-xs py-2 px-3 rounded-lg border border-ink/20 hover:border-ink/40 '
32
+ + 'bg-paper focus:outline-none focus:border-ink text-ink transition-colors cursor-pointer';
33
+
34
+ /**
35
+ * The provider DIALECT controls: wire API, auth mode, and where the model list
36
+ * comes from.
37
+ *
38
+ * These three are the `models.yml` fields that decide whether a provider works
39
+ * at all, and each one is a decision the chamber cannot make for the user:
40
+ *
41
+ * - An Anthropic-shaped proxy reached with a Bearer header authorises nothing,
42
+ * and omp rejects an unknown `api` by disabling every custom provider in the
43
+ * file — so the dialect is picked from omp's own list, never guessed into it.
44
+ * - `auth: none` is what makes a local server keyless; without it omp demands a
45
+ * key and the provider is reported as unconfigured.
46
+ * - A provider omp can discover should not also receive a snapshot of ids, or
47
+ * the two lists drift as soon as the endpoint adds a model.
48
+ */
49
+ export function DialectFields({
50
+ api,
51
+ onApiChange,
52
+ auth,
53
+ onAuthChange,
54
+ source,
55
+ onSourceChange,
56
+ discovery,
57
+ onDiscoveryChange,
58
+ }: DialectFieldsProps) {
59
+ return (
60
+ <>
61
+ <div className="grid grid-cols-2 gap-3">
62
+ <div>
63
+ <label className="block text-xs font-semibold text-ink mb-1">Wire API</label>
64
+ <select
65
+ value={api}
66
+ onChange={(e) => onApiChange(e.currentTarget.value as OmpProviderApi)}
67
+ className={fieldClass}
68
+ >
69
+ {PROVIDER_APIS.map((value) => (
70
+ <option key={value} value={value}>{value}</option>
71
+ ))}
72
+ </select>
73
+ </div>
74
+ <div>
75
+ <label className="block text-xs font-semibold text-ink mb-1">Auth</label>
76
+ <select
77
+ value={auth}
78
+ onChange={(e) => onAuthChange(e.currentTarget.value as ProviderAuthMode)}
79
+ className={fieldClass}
80
+ >
81
+ {PROVIDER_AUTH_MODES.map((mode) => (
82
+ <option key={mode.value} value={mode.value}>{mode.label}</option>
83
+ ))}
84
+ </select>
85
+ </div>
86
+ </div>
87
+ <p className="text-[10px] text-ink/40 leading-relaxed -mt-1">
88
+ {PROVIDER_API_LABELS[api]} · {PROVIDER_AUTH_MODES.find((m) => m.value === auth)?.hint}
89
+ </p>
90
+
91
+ <div>
92
+ <label className="block text-xs font-semibold text-ink mb-1">Model source</label>
93
+ <div className="grid grid-cols-3 gap-2">
94
+ {SOURCE_OPTIONS.map((option) => {
95
+ const isSelected = source === option.value;
96
+ return (
97
+ <button
98
+ key={option.value}
99
+ type="button"
100
+ onClick={() => onSourceChange(option.value)}
101
+ className={`px-2 py-1.5 rounded-lg border text-[11px] transition-colors cursor-pointer ${
102
+ isSelected
103
+ ? 'border-ink/50 bg-ink/5 font-semibold text-ink'
104
+ : 'border-ink/15 hover:border-ink/30 text-ink/70'
105
+ }`}
106
+ >
107
+ {option.label}
108
+ </button>
109
+ );
110
+ })}
111
+ </div>
112
+ <p className="text-[10px] text-ink/40 mt-1 leading-relaxed">
113
+ {SOURCE_OPTIONS.find((option) => option.value === source)?.hint}
114
+ </p>
115
+ </div>
116
+
117
+ {source === 'discovery' && (
118
+ <div>
119
+ <label className="block text-xs font-semibold text-ink mb-1">Discovery protocol</label>
120
+ <select
121
+ value={discovery}
122
+ onChange={(e) => onDiscoveryChange(e.currentTarget.value as ProviderDiscoveryType)}
123
+ className={fieldClass}
124
+ >
125
+ {PROVIDER_DISCOVERY_TYPES.map((type) => (
126
+ <option key={type.value} value={type.value}>{type.label}</option>
127
+ ))}
128
+ </select>
129
+ <p className="text-[10px] text-ink/40 mt-1">
130
+ {PROVIDER_DISCOVERY_TYPES.find((type) => type.value === discovery)?.hint}
131
+ </p>
132
+ </div>
133
+ )}
134
+ </>
135
+ );
136
+ }