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
@@ -9,6 +9,10 @@ import { ReconnectModal } from '@/client/components/settings/categories/provider
9
9
  import { LoginModal } from '@/client/components/settings/categories/provider-settings/LoginModal';
10
10
  import { ModelConfigModal } from '@/client/components/settings/categories/provider-settings/ModelConfigModal';
11
11
  import { ModelCapabilitiesModal } from '@/client/components/settings/categories/provider-settings/ModelCapabilitiesModal';
12
+ import { AddModelModal } from '@/client/components/settings/categories/provider-settings/AddModelModal';
13
+ import { DeleteProviderModal } from '@/client/components/settings/categories/provider-settings/DeleteProviderModal';
14
+ import { providerEmptyReason } from '@/shared/lib/models/provider/dialect';
15
+ import { configuredProviderSlugs } from '@/shared/lib/models/provider/presets';
12
16
 
13
17
  interface ProviderSettingsProps {
14
18
  autoOpenAdd?: boolean;
@@ -28,7 +32,15 @@ export function ProviderSettings({
28
32
  setCurrentProject,
29
33
  isFetchingModels,
30
34
  toasts,
35
+ pushToast,
31
36
  dismissToast,
37
+ isAddModelModalOpen,
38
+ setIsAddModelModalOpen,
39
+ handleModelAdded,
40
+ isDeleteModalOpen,
41
+ setIsDeleteModalOpen,
42
+ isDeletingProvider,
43
+ handleDeleteProvider,
32
44
  isAddModalOpen,
33
45
  setIsAddModalOpen,
34
46
  isReconnectModalOpen,
@@ -72,19 +84,31 @@ export function ProviderSettings({
72
84
  <ProviderAuthSection
73
85
  provider={selectedProvider}
74
86
  onOpenReconnectModal={() => {
75
- if (selectedProvider.id.startsWith('omp-auth-')) setIsLoginModalOpen(true);
76
- else setIsReconnectModalOpen(true);
87
+ // A provider omp signs in through `/login` needs the OAuth
88
+ // dialog; one whose endpoint and key live in models.yml (or the
89
+ // chamber overlay) is edited in place. Testing the provider ID
90
+ // for `omp-auth-` was wrong: a native models.yml entry shares
91
+ // that ID prefix once merged, and a keyless local server has no
92
+ // login flow to complete at all.
93
+ if (selectedProvider.credentialSource === 'omp-auth' && selectedProvider.auth !== 'none') {
94
+ setIsLoginModalOpen(true);
95
+ } else {
96
+ setIsReconnectModalOpen(true);
97
+ }
77
98
  }}
78
99
  onToggleDisconnect={handleToggleDisconnect}
79
100
  onToggleDisabled={handleToggleProviderDisabled}
101
+ onDelete={() => setIsDeleteModalOpen(true)}
80
102
  />
81
103
 
82
104
  <ProviderModelsList
83
105
  models={selectedProvider.models}
106
+ emptyReason={providerEmptyReason(selectedProvider)}
84
107
  onToggleModelVisibility={handleToggleModelVisibility}
85
108
  onHideAll={handleHideAll}
86
109
  onShowAll={handleShowAll}
87
110
  onFetchModels={handleFetchModelsFromList}
111
+ onAddModel={() => setIsAddModelModalOpen(true)}
88
112
  canFetchModels={Boolean(selectedProvider.baseUrl)}
89
113
  isFetchingModels={isFetchingModels}
90
114
  onOpenModelConfig={(model) => setConfigModel(model)}
@@ -108,7 +132,7 @@ export function ProviderSettings({
108
132
  }}
109
133
  onAddProvider={handleAddProvider}
110
134
  presets={availablePresetProviders}
111
- existingSlugs={providers.map((provider) => provider.slug)}
135
+ existingSlugs={configuredProviderSlugs(providers)}
112
136
  />
113
137
 
114
138
  {selectedProvider && (
@@ -131,13 +155,30 @@ export function ProviderSettings({
131
155
  />
132
156
  )}
133
157
 
158
+ {selectedProvider && (
159
+ <AddModelModal
160
+ isOpen={isAddModelModalOpen}
161
+ provider={selectedProvider}
162
+ onClose={() => setIsAddModelModalOpen(false)}
163
+ onAdded={(modelId) => void handleModelAdded(modelId)}
164
+ onError={(message) => pushToast(message, 'error')}
165
+ />
166
+ )}
167
+
168
+ <DeleteProviderModal
169
+ isOpen={isDeleteModalOpen}
170
+ provider={selectedProvider ?? null}
171
+ isDeleting={isDeletingProvider}
172
+ onClose={() => setIsDeleteModalOpen(false)}
173
+ onConfirm={() => void handleDeleteProvider()}
174
+ />
175
+
134
176
  <ModelConfigModal
135
177
  isOpen={!!configModel}
136
178
  model={configModel}
137
179
  onClose={() => setConfigModel(null)}
138
180
  onSaveModelConfig={handleSaveModelConfig}
139
181
  />
140
-
141
182
  <ModelCapabilitiesModal
142
183
  isOpen={!!capabilitiesModel}
143
184
  model={capabilitiesModel}
@@ -1,15 +1,24 @@
1
- import { useState } from 'preact/hooks';
1
+ import { useEffect, useState } from 'preact/hooks';
2
2
  import { AlertCircle, RefreshCw } from 'lucide-preact';
3
3
  import { UsageSidebarList } from '@/client/components/settings/categories/usage-settings/SidebarList';
4
- import type { UsageProviderId } from '@/client/components/settings/categories/usage-settings/providers';
4
+ import { buildProviders, type UsageProviderId } from '@/client/components/settings/categories/usage-settings/providers';
5
5
  import { ProviderDetail } from '@/client/components/settings/categories/usage-settings/ProviderDetail';
6
6
  import { formatDateTime } from '@/client/components/settings/categories/usage-settings/format';
7
7
  import { useUsageReport } from '@/client/hooks/settings/useUsageReport';
8
8
 
9
- /** Settings → Usage: provider balances, quota, and 30-day spend. */
9
+ /** Settings → Usage: provider quota, balances, and locally recorded burn. */
10
10
  export function UsageSettings() {
11
11
  const { report, isLoading, error, reload } = useUsageReport();
12
- const [selectedProviderId, setSelectedProviderId] = useState<UsageProviderId>('kenari');
12
+ const [selectedProviderId, setSelectedProviderId] = useState<UsageProviderId>('');
13
+
14
+ const providers = report ? buildProviders(report) : [];
15
+ const selected = providers.find((provider) => provider.id === selectedProviderId) ?? providers[0];
16
+
17
+ // The report can arrive with a different provider set (a key was added or
18
+ // removed); keep the selection pointing at something that exists.
19
+ useEffect(() => {
20
+ if (selected && selected.id !== selectedProviderId) setSelectedProviderId(selected.id);
21
+ }, [selected, selectedProviderId]);
13
22
 
14
23
  if (error && !report) {
15
24
  return (
@@ -39,7 +48,7 @@ export function UsageSettings() {
39
48
  <div className="flex h-full w-full overflow-hidden bg-paper text-xs text-ink">
40
49
  <UsageSidebarList
41
50
  report={report}
42
- selectedProviderId={selectedProviderId}
51
+ selectedProviderId={selected?.id ?? ''}
43
52
  onSelectProvider={setSelectedProviderId}
44
53
  />
45
54
 
@@ -57,7 +66,7 @@ export function UsageSettings() {
57
66
  </div>
58
67
  <button
59
68
  type="button"
60
- onClick={() => reload()}
69
+ onClick={() => reload({ force: true })}
61
70
  disabled={isLoading}
62
71
  className="p-1.5 bg-paper border border-ink/15 rounded-md hover:bg-ink/5 text-ink/80 transition-colors disabled:opacity-50 flex-shrink-0"
63
72
  title="Refresh usage"
@@ -69,7 +78,13 @@ export function UsageSettings() {
69
78
  <div className="flex-1 overflow-y-auto scrollbar-overlay-container scrollbar-overlay-static p-3.5 space-y-4">
70
79
  {error && <p className="text-error text-xs">{error}</p>}
71
80
 
72
- <ProviderDetail providerId={selectedProviderId} report={report} />
81
+ {selected ? (
82
+ <ProviderDetail summary={selected.summary} />
83
+ ) : (
84
+ <p className="text-[11px] text-ink/50 leading-relaxed">
85
+ No provider credentials detected. Add an API key in Settings → Providers and it will appear here.
86
+ </p>
87
+ )}
73
88
  </div>
74
89
  </div>
75
90
  </div>
@@ -0,0 +1,322 @@
1
+ import { useState } from 'preact/hooks';
2
+ import type { FormEvent } from 'preact/compat';
3
+ import { Check, Globe, Hash, Layers, Plus, Sparkles } from 'lucide-preact';
4
+ import type { ProviderItem } from '@/shared/types';
5
+ import { addProviderModel, type ManualModelRequest } from '@/shared/lib/models/provider/models';
6
+ import {
7
+ PROVIDER_THINKING_EFFORTS,
8
+ type ProviderThinkingEffort,
9
+ } from '@/shared/lib/models/provider/dialect';
10
+ import { Modal } from '@/client/components/common/Modal';
11
+ import { EffortLadder } from '@/client/components/settings/categories/provider-settings/EffortLadder';
12
+ import { PriceFields } from '@/client/components/settings/categories/provider-settings/PriceFields';
13
+
14
+ interface AddModelModalProps {
15
+ isOpen: boolean;
16
+ provider: ProviderItem;
17
+ onClose: () => void;
18
+ /** Called after a successful write so the list and the chat picker refresh. */
19
+ onAdded: (modelId: string) => void;
20
+ /** Reports a refusal the dialog cannot fix itself (bad endpoint, bad key). */
21
+ onError: (message: string) => void;
22
+ }
23
+
24
+ /**
25
+ * Register ONE model by hand.
26
+ *
27
+ * The auto-fetch needs a listing endpoint, and plenty of real providers have
28
+ * none worth using: an Azure deployment is a name the user chose, a gateway may
29
+ * serve a model its `/models` route omits, and Bedrock/Vertex expose no
30
+ * `/models` at all. This dialog writes the same models.yml entry the fetch
31
+ * would, with the metadata the user actually knows.
32
+ *
33
+ * Only `id` is required. Every other field is metadata omp can work without —
34
+ * a missing context window is estimated, a missing price is simply not
35
+ * accounted — so requiring them would block the common case of "just add this
36
+ * id I know exists".
37
+ */
38
+ export function AddModelModal({
39
+ isOpen,
40
+ provider,
41
+ onClose,
42
+ onAdded,
43
+ onError,
44
+ }: AddModelModalProps) {
45
+ const [id, setId] = useState('');
46
+ const [name, setName] = useState('');
47
+ const [contextWindow, setContextWindow] = useState('');
48
+ const [maxTokens, setMaxTokens] = useState('');
49
+ const [costInput, setCostInput] = useState('');
50
+ const [costOutput, setCostOutput] = useState('');
51
+ const [costCacheRead, setCostCacheRead] = useState('');
52
+ const [costCacheWrite, setCostCacheWrite] = useState('');
53
+ const [reasoning, setReasoning] = useState(false);
54
+ const [imageInput, setImageInput] = useState(false);
55
+ const [efforts, setEfforts] = useState<ProviderThinkingEffort[]>([]);
56
+ const [isSubmitting, setIsSubmitting] = useState(false);
57
+
58
+ if (!isOpen) return null;
59
+
60
+ const trimmedId = id.trim();
61
+ const duplicate = provider.models.some((model) => model.id === trimmedId);
62
+ const idError = !trimmedId
63
+ ? 'Enter the model id exactly as the provider expects it.'
64
+ : duplicate
65
+ ? `"${trimmedId}" is already registered under this provider.`
66
+ : '';
67
+ const numberError = (value: string, label: string): string =>
68
+ value.trim() && !(Number(value) > 0) ? `${label} must be a positive number of tokens.` : '';
69
+ const limitsError = numberError(contextWindow, 'Context window') || numberError(maxTokens, 'Max output');
70
+ const costError = (value: string): string =>
71
+ value.trim() && !(Number(value) >= 0) ? 'Prices must be zero or more USD per 1M tokens.' : '';
72
+ const priceError = costError(costInput) || costError(costOutput)
73
+ || costError(costCacheRead) || costError(costCacheWrite);
74
+ // A price is written only when BOTH headline sides are known: omp's schema
75
+ // requires all four cost fields together, so half a price is not a price. The
76
+ // two cache rates may be left empty — they are written as 0, which is what
77
+ // "no cached-token discount" means.
78
+ const halfPrice = Boolean(costInput.trim()) !== Boolean(costOutput.trim())
79
+ ? 'Enter both input and output price, or neither.'
80
+ : (costCacheRead.trim() || costCacheWrite.trim()) && !costInput.trim()
81
+ ? 'A cache rate needs the input and output price too.'
82
+ : '';
83
+ // The ladder is a capability of a reasoning model; ticking levels while
84
+ // Reasoning is off would describe a model omp is not told can reason.
85
+ const ladderError = !reasoning && efforts.length > 0
86
+ ? 'Turn on Reasoning to declare its levels.'
87
+ : '';
88
+ const canSubmit = Boolean(trimmedId) && !idError && !limitsError && !priceError
89
+ && !halfPrice && !ladderError && !isSubmitting;
90
+
91
+ const numberOrUndefined = (value: string): number | undefined => {
92
+ const parsed = Number(value);
93
+ return value.trim() && Number.isFinite(parsed) ? parsed : undefined;
94
+ };
95
+
96
+ const toggleEffort = (effort: ProviderThinkingEffort) => {
97
+ setEfforts((prev) => (
98
+ prev.includes(effort)
99
+ ? prev.filter((entry) => entry !== effort)
100
+ // Kept in omp's own order so the written ladder reads lowest-first
101
+ // regardless of the order the user ticked the boxes.
102
+ : PROVIDER_THINKING_EFFORTS.filter((level) => level === effort || prev.includes(level))
103
+ ));
104
+ };
105
+
106
+ const handleSubmit = async (e: FormEvent) => {
107
+ e.preventDefault();
108
+ if (!canSubmit) return;
109
+ setIsSubmitting(true);
110
+ try {
111
+ const request: ManualModelRequest = {
112
+ provider: provider.slug,
113
+ id: trimmedId,
114
+ ...(name.trim() ? { name: name.trim() } : {}),
115
+ ...(numberOrUndefined(contextWindow) !== undefined
116
+ ? { contextWindow: numberOrUndefined(contextWindow) }
117
+ : {}),
118
+ ...(numberOrUndefined(maxTokens) !== undefined ? { maxTokens: numberOrUndefined(maxTokens) } : {}),
119
+ ...(reasoning ? { reasoning: true } : {}),
120
+ ...(reasoning && efforts.length > 0 ? { efforts } : {}),
121
+ ...(imageInput ? { imageInput: true } : {}),
122
+ ...(costInput.trim() && costOutput.trim()
123
+ ? {
124
+ costInput: numberOrUndefined(costInput),
125
+ costOutput: numberOrUndefined(costOutput),
126
+ // Empty cache rates become 0 in the seed builder; sending them only
127
+ // when typed keeps the request honest about what the user entered.
128
+ ...(numberOrUndefined(costCacheRead) !== undefined
129
+ ? { costCacheRead: numberOrUndefined(costCacheRead) }
130
+ : {}),
131
+ ...(numberOrUndefined(costCacheWrite) !== undefined
132
+ ? { costCacheWrite: numberOrUndefined(costCacheWrite) }
133
+ : {}),
134
+ }
135
+ : {}),
136
+ // The endpoint and credential are only consulted for a provider with no
137
+ // models.yml entry yet; the writer is add-only and keeps an existing
138
+ // one. A keyless provider passes `auth: none` so omp accepts the entry
139
+ // without a credential.
140
+ ...(provider.baseUrl ? { baseUrl: provider.baseUrl } : {}),
141
+ ...(provider.apiKey ? { apiKey: provider.apiKey } : {}),
142
+ ...(provider.auth ? { auth: provider.auth } : {}),
143
+ };
144
+ const result = await addProviderModel(request);
145
+ if (!result.success) {
146
+ // The refusal is the actionable half (an endpoint override with no
147
+ // credential, a provider not in models.yml) — surface it verbatim.
148
+ onError(result.error || result.reason || `Could not register ${trimmedId}.`);
149
+ return;
150
+ }
151
+ onAdded(trimmedId);
152
+ onClose();
153
+ } finally {
154
+ setIsSubmitting(false);
155
+ }
156
+ };
157
+
158
+ return (
159
+ <Modal
160
+ onClose={onClose}
161
+ header={
162
+ <div className="flex items-center gap-2">
163
+ <Plus size={16} className="text-ink/70" />
164
+ <div>
165
+ <h3 className="text-sm font-semibold text-ink">Add Model</h3>
166
+ <p className="text-[11px] text-ink/50 font-mono truncate max-w-[280px]">{provider.slug}</p>
167
+ </div>
168
+ </div>
169
+ }
170
+ form={{ onSubmit: handleSubmit, className: 'p-5 space-y-4' }}
171
+ footer={
172
+ <div className="pt-2 flex items-center justify-end gap-2 border-t border-ink/10">
173
+ <button
174
+ type="button"
175
+ onClick={onClose}
176
+ 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"
177
+ >
178
+ Cancel
179
+ </button>
180
+ <button
181
+ type="submit"
182
+ disabled={!canSubmit}
183
+ 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"
184
+ >
185
+ {isSubmitting ? (
186
+ <>
187
+ <Layers size={14} className="animate-pulse" />
188
+ <span>Registering...</span>
189
+ </>
190
+ ) : (
191
+ <>
192
+ <Check size={14} />
193
+ <span>Add Model</span>
194
+ </>
195
+ )}
196
+ </button>
197
+ </div>
198
+ }
199
+ >
200
+ {/* Model id */}
201
+ <div>
202
+ <label className="block text-xs font-semibold text-ink mb-1">Model ID</label>
203
+ <div className="relative flex items-center">
204
+ <Hash size={13} className="absolute left-2.5 text-ink/40 pointer-events-none" />
205
+ <input
206
+ type="text"
207
+ required
208
+ autoFocus
209
+ value={id}
210
+ onChange={(e) => setId(e.currentTarget.value)}
211
+ placeholder="e.g. claude-sonnet-5 or my-deployment"
212
+ 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"
213
+ />
214
+ </div>
215
+ {idError && <p className="text-[11px] text-error mt-1">{idError}</p>}
216
+ <p className="text-[10px] text-ink/40 mt-1">
217
+ Sent to the provider verbatim — this is the id omp puts on the wire.
218
+ </p>
219
+ </div>
220
+
221
+ {/* Display name */}
222
+ <div>
223
+ <label className="block text-xs font-semibold text-ink mb-1">
224
+ Display Name <span className="font-normal text-ink/40">— optional</span>
225
+ </label>
226
+ <input
227
+ type="text"
228
+ value={name}
229
+ onChange={(e) => setName(e.currentTarget.value)}
230
+ placeholder="Shown in the chat model picker"
231
+ className="w-full bg-paper border border-ink/20 rounded-md px-3 py-1.5 text-xs text-ink outline-none focus:border-ink/60"
232
+ />
233
+ </div>
234
+
235
+ {/* Limits */}
236
+ <div className="grid grid-cols-2 gap-3">
237
+ <div>
238
+ <label className="block text-xs font-semibold text-ink mb-1">
239
+ Context window <span className="font-normal text-ink/40">— tokens</span>
240
+ </label>
241
+ <input
242
+ type="text"
243
+ inputMode="numeric"
244
+ value={contextWindow}
245
+ onChange={(e) => setContextWindow(e.currentTarget.value)}
246
+ placeholder="200000"
247
+ className="w-full bg-paper border border-ink/20 rounded-md px-3 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
248
+ />
249
+ </div>
250
+ <div>
251
+ <label className="block text-xs font-semibold text-ink mb-1">
252
+ Max output <span className="font-normal text-ink/40">— tokens</span>
253
+ </label>
254
+ <input
255
+ type="text"
256
+ inputMode="numeric"
257
+ value={maxTokens}
258
+ onChange={(e) => setMaxTokens(e.currentTarget.value)}
259
+ placeholder="8192"
260
+ className="w-full bg-paper border border-ink/20 rounded-md px-3 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
261
+ />
262
+ </div>
263
+ </div>
264
+ {limitsError && <p className="text-[11px] text-error -mt-2">{limitsError}</p>}
265
+
266
+ {/* Capabilities */}
267
+ <div>
268
+ <label className="block text-xs font-semibold text-ink mb-1.5">Capabilities</label>
269
+ <div className="grid grid-cols-2 gap-2">
270
+ <button
271
+ type="button"
272
+ onClick={() => setReasoning((prev) => !prev)}
273
+ aria-pressed={reasoning}
274
+ className={`flex items-center gap-2 px-2.5 py-2 rounded-lg border text-left text-[11px] transition-colors cursor-pointer ${
275
+ reasoning ? 'border-ink/50 bg-ink/5 font-semibold text-ink' : 'border-ink/15 hover:border-ink/30 text-ink/70'
276
+ }`}
277
+ >
278
+ <Sparkles size={13} className="shrink-0" />
279
+ <span className="truncate">Reasoning</span>
280
+ </button>
281
+ <button
282
+ type="button"
283
+ onClick={() => setImageInput((prev) => !prev)}
284
+ aria-pressed={imageInput}
285
+ className={`flex items-center gap-2 px-2.5 py-2 rounded-lg border text-left text-[11px] transition-colors cursor-pointer ${
286
+ imageInput ? 'border-ink/50 bg-ink/5 font-semibold text-ink' : 'border-ink/15 hover:border-ink/30 text-ink/70'
287
+ }`}
288
+ >
289
+ <Globe size={13} className="shrink-0" />
290
+ <span className="truncate">Vision</span>
291
+ </button>
292
+ </div>
293
+ <p className="text-[10px] text-ink/40 mt-1">
294
+ Leave both off when unsure — omp still serves the model; only its declared abilities change.
295
+ </p>
296
+ </div>
297
+
298
+ {/* Reasoning levels — only meaningful for a reasoning model */}
299
+ {reasoning && (
300
+ <EffortLadder
301
+ selected={efforts}
302
+ onToggle={toggleEffort}
303
+ onSelectAll={() => setEfforts([...PROVIDER_THINKING_EFFORTS])}
304
+ onClear={() => setEfforts([])}
305
+ />
306
+ )}
307
+ {ladderError && <p className="text-[11px] text-error -mt-2">{ladderError}</p>}
308
+
309
+ <PriceFields
310
+ costInput={costInput}
311
+ costOutput={costOutput}
312
+ costCacheRead={costCacheRead}
313
+ costCacheWrite={costCacheWrite}
314
+ onCostInputChange={setCostInput}
315
+ onCostOutputChange={setCostOutput}
316
+ onCostCacheReadChange={setCostCacheRead}
317
+ onCostCacheWriteChange={setCostCacheWrite}
318
+ error={priceError || halfPrice}
319
+ />
320
+ </Modal>
321
+ );
322
+ }