ompchamber 2.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/README.md +31 -11
  2. package/bunfig.toml +11 -0
  3. package/dist/client/index.html +16 -5
  4. package/dist/client/index.js +1393 -0
  5. package/dist/client/static/js/index-8t70jhyj.js +5149 -0
  6. package/dist/client/static/js/index-n4wkqj7t.css +1 -0
  7. package/index.html +54 -0
  8. package/package.json +9 -10
  9. package/public/apple-touch-icon.png +0 -0
  10. package/public/icon-192x192.png +0 -0
  11. package/public/icon.svg +60 -0
  12. package/public/manifest.webmanifest +38 -0
  13. package/src/cli/lib/commands/serve.js +9 -5
  14. package/src/cli/lib/runtime.js +18 -7
  15. package/src/client/components/common/code-editor/handler.test.ts +274 -0
  16. package/src/client/components/common/code-editor/handler.ts +175 -0
  17. package/src/client/components/common/code-editor/index.tsx +185 -220
  18. package/src/client/components/common/code-surface/index.tsx +109 -12
  19. package/src/client/components/common/lazy-panels.tsx +5 -1
  20. package/src/client/components/common/provider-icon/glyphs.ts +118 -0
  21. package/src/client/components/common/provider-icon/index.tsx +85 -0
  22. package/src/client/components/common/viewport.ts +5 -3
  23. package/src/client/components/layout/RightActivityBar.tsx +2 -1
  24. package/src/client/components/layout/desktop-layout/TopNavbar.tsx +21 -4
  25. package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
  26. package/src/client/components/layout/desktop-layout/index.tsx +26 -4
  27. package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
  28. package/src/client/components/mobile/RightSidebar.tsx +11 -4
  29. package/src/client/components/mobile/SessionSidebar.tsx +10 -0
  30. package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +0 -1
  31. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -1
  32. package/src/client/components/mobile/mobile-session-sidebar/List.tsx +3 -0
  33. package/src/client/components/settings/categories/ProviderSettings.tsx +45 -4
  34. package/src/client/components/settings/categories/UsageSettings.tsx +22 -7
  35. package/src/client/components/settings/categories/provider-settings/AddModelModal.tsx +322 -0
  36. package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +152 -56
  37. package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +52 -1
  38. package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +133 -0
  39. package/src/client/components/settings/categories/provider-settings/DialectFields.tsx +136 -0
  40. package/src/client/components/settings/categories/provider-settings/EffortLadder.tsx +89 -0
  41. package/src/client/components/settings/categories/provider-settings/Header.tsx +2 -2
  42. package/src/client/components/settings/categories/provider-settings/LoginModal.tsx +2 -2
  43. package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +22 -2
  44. package/src/client/components/settings/categories/provider-settings/PresetPicker.tsx +82 -0
  45. package/src/client/components/settings/categories/provider-settings/PriceFields.tsx +107 -0
  46. package/src/client/components/settings/categories/provider-settings/ReconnectModal.tsx +43 -22
  47. package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +2 -2
  48. package/src/client/components/settings/categories/usage-settings/LimitsSection.tsx +115 -0
  49. package/src/client/components/settings/categories/usage-settings/ProviderCard.tsx +19 -26
  50. package/src/client/components/settings/categories/usage-settings/ProviderDetail.tsx +29 -22
  51. package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +60 -48
  52. package/src/client/components/settings/categories/usage-settings/format.ts +36 -0
  53. package/src/client/components/settings/categories/usage-settings/providers.ts +52 -28
  54. package/src/client/components/workspace/browser-panel/AddressBar.tsx +1 -10
  55. package/src/client/components/workspace/browser-panel/Screencast.tsx +4 -5
  56. package/src/client/components/workspace/browser-panel/index.tsx +6 -6
  57. package/src/client/components/workspace/browser-toolbar/index.tsx +22 -12
  58. package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +1 -1
  59. package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +1 -1
  60. package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +1 -1
  61. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Todo.tsx +1 -1
  62. package/src/client/components/workspace/diff-panel/Toolbar.tsx +71 -38
  63. package/src/client/components/workspace/diff-panel/index.tsx +139 -53
  64. package/src/client/components/workspace/editor/CommandPalette.test.ts +242 -0
  65. package/src/client/components/workspace/editor/CommandPalette.tsx +138 -0
  66. package/src/client/components/workspace/editor/FindWidget.test.ts +261 -0
  67. package/src/client/components/workspace/editor/FindWidget.tsx +219 -0
  68. package/src/client/components/workspace/editor/Tabs.tsx +2 -2
  69. package/src/client/components/workspace/editor/Toolbar.tsx +129 -26
  70. package/src/client/components/workspace/editor/index.tsx +180 -62
  71. package/src/client/components/workspace/file-explorer/Modals.tsx +79 -2
  72. package/src/client/components/workspace/file-explorer/TreeItem.tsx +43 -113
  73. package/src/client/components/workspace/model-dropdown/Item.tsx +13 -23
  74. package/src/client/components/workspace/model-dropdown/Panel.tsx +1 -1
  75. package/src/client/components/workspace/model-dropdown/Section.tsx +13 -4
  76. package/src/client/components/workspace/model-dropdown/index.tsx +7 -3
  77. package/src/client/components/workspace/todo-panel/PhaseGroup.tsx +76 -0
  78. package/src/client/components/workspace/todo-panel/Row.tsx +69 -0
  79. package/src/client/components/workspace/todo-panel/index.tsx +161 -0
  80. package/src/client/components/workspace/usage-panel/ProviderSelect.tsx +10 -5
  81. package/src/client/components/workspace/usage-panel/index.tsx +36 -18
  82. package/src/client/data/samples/dialogue-session.ts +1 -1
  83. package/src/client/data/samples/sample-tools/other.ts +1 -1
  84. package/src/client/data/settings/provider.ts +141 -9
  85. package/src/client/hooks/editor/find-harness.test-util.ts +170 -0
  86. package/src/client/hooks/editor/scroll-container.test.ts +102 -0
  87. package/src/client/hooks/editor/scroll-container.ts +26 -0
  88. package/src/client/hooks/editor/use-code-window.ts +2 -20
  89. package/src/client/hooks/editor/use-editor-find-write.test.ts +170 -0
  90. package/src/client/hooks/editor/use-editor-find.test.ts +118 -0
  91. package/src/client/hooks/editor/use-editor-find.ts +321 -0
  92. package/src/client/hooks/editor/use-editor-history.test.ts +216 -0
  93. package/src/client/hooks/editor/use-editor-history.ts +134 -0
  94. package/src/client/hooks/editor/use-file-editor.ts +24 -27
  95. package/src/client/hooks/editor/use-occurrences.test.ts +236 -0
  96. package/src/client/hooks/editor/use-occurrences.ts +98 -0
  97. package/src/client/hooks/models/use-provider-names.ts +1 -1
  98. package/src/client/hooks/settings/providers/actions.ts +337 -0
  99. package/src/client/hooks/settings/providers/api.ts +69 -0
  100. package/src/client/hooks/settings/providers/index.ts +138 -0
  101. package/src/client/hooks/settings/providers/messages.ts +77 -0
  102. package/src/client/hooks/settings/useUsageReport.ts +8 -3
  103. package/src/client/hooks/ui/theme.ts +3 -3
  104. package/src/client/hooks/workspace/file-tabs.ts +62 -11
  105. package/src/client/hooks/workspace/file-tree-actions.test.ts +348 -0
  106. package/src/client/hooks/workspace/file-tree-actions.ts +250 -0
  107. package/src/client/hooks/workspace/session-todos.ts +102 -0
  108. package/src/client/stubs/elkjs.ts +6 -1
  109. package/src/client/tailwind.css +30 -0
  110. package/src/server/index.ts +20 -2
  111. package/src/server/lib/assets/font-css.server.ts +86 -0
  112. package/src/server/lib/assets/fonts.server.ts +117 -0
  113. package/src/server/lib/bundler/css.ts +116 -0
  114. package/src/server/lib/lifecycle/shell-route.ts +17 -0
  115. package/src/server/lib/models/provider-listing.server.ts +257 -0
  116. package/src/server/lib/models/provider-registry.server.test.ts +122 -0
  117. package/src/server/lib/models/provider-registry.server.ts +76 -25
  118. package/src/server/lib/omp/config/models-config.ts +11 -0
  119. package/src/server/lib/omp/config/models-validation.test.ts +137 -0
  120. package/src/server/lib/omp/config/models-validation.ts +111 -2
  121. package/src/server/lib/omp/config/provider-seeds.test.ts +167 -0
  122. package/src/server/lib/omp/config/provider-seeds.ts +176 -0
  123. package/src/server/lib/omp/config/providers.ts +94 -100
  124. package/src/server/lib/omp/core/auth-credentials.ts +45 -0
  125. package/src/server/lib/omp/session/todos.ts +100 -0
  126. package/src/server/lib/omp/session/{usage.ts → usage/aggregate.ts} +1 -118
  127. package/src/server/lib/omp/session/usage/shaping.ts +130 -0
  128. package/src/server/lib/usage/deepseek.ts +4 -5
  129. package/src/server/lib/usage/kenari.ts +7 -6
  130. package/src/server/lib/usage/omp-usage.server.test.ts +126 -0
  131. package/src/server/lib/usage/omp-usage.server.ts +194 -0
  132. package/src/server/lib/usage/provider-key.ts +81 -3
  133. package/src/server/lib/usage/providers.server.test.ts +47 -0
  134. package/src/server/lib/usage/providers.server.ts +208 -0
  135. package/src/server/lib/usage/vendor-balance.server.test.ts +104 -0
  136. package/src/server/lib/usage/vendor-balance.server.ts +284 -0
  137. package/src/server/plugins/shell.server.ts +56 -0
  138. package/src/server/plugins/ssr.ts +24 -36
  139. package/src/server/plugins/static.test.ts +26 -29
  140. package/src/server/plugins/static.ts +40 -19
  141. package/src/server/routes/fs/action-create.test.ts +90 -0
  142. package/src/server/routes/fs/action.ts +23 -0
  143. package/src/server/routes/omp/index.ts +2 -0
  144. package/src/server/routes/omp/session-todos.ts +80 -0
  145. package/src/server/routes/settings/index.ts +2 -0
  146. package/src/server/routes/settings/provider-model.ts +107 -0
  147. package/src/server/routes/settings/provider-models.ts +65 -202
  148. package/src/server/routes/settings/providers.ts +22 -7
  149. package/src/server/routes/settings/usage.ts +125 -45
  150. package/src/server/routes/telemetry/tokens.ts +2 -1
  151. package/src/shared/lib/chat/{todo-parser.ts → todo/parser.ts} +17 -0
  152. package/src/shared/lib/chat/todo/progress.ts +97 -0
  153. package/src/shared/lib/chat/todo/snapshot.test.ts +156 -0
  154. package/src/shared/lib/chat/todo/snapshot.ts +215 -0
  155. package/src/shared/lib/code/editor/commands.test.ts +155 -0
  156. package/src/shared/lib/code/editor/commands.ts +260 -0
  157. package/src/shared/lib/code/editor/edits.test.ts +100 -0
  158. package/src/shared/lib/code/editor/edits.ts +122 -0
  159. package/src/shared/lib/code/editor/find-status.test.ts +50 -0
  160. package/src/shared/lib/code/editor/find-status.ts +36 -0
  161. package/src/shared/lib/code/editor/find.test.ts +203 -0
  162. package/src/shared/lib/code/editor/find.ts +224 -0
  163. package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.test.ts +1 -1
  164. package/src/shared/lib/code/editor/keymap.test.ts +109 -0
  165. package/src/shared/lib/code/editor/keymap.ts +151 -0
  166. package/src/shared/lib/code/editor/layers.test.ts +77 -0
  167. package/src/shared/lib/code/editor/layers.ts +67 -0
  168. package/src/shared/lib/code/editor/line-edits.test.ts +131 -0
  169. package/src/shared/lib/code/editor/line-edits.ts +159 -0
  170. package/src/shared/lib/code/editor/lines.test.ts +81 -0
  171. package/src/shared/lib/code/editor/lines.ts +83 -0
  172. package/src/shared/lib/code/editor/marks.test.ts +86 -0
  173. package/src/shared/lib/code/editor/marks.ts +106 -0
  174. package/src/shared/lib/code/editor/occurrences.test.ts +117 -0
  175. package/src/shared/lib/code/editor/occurrences.ts +111 -0
  176. package/src/shared/lib/code/editor/palette.test.ts +91 -0
  177. package/src/shared/lib/code/editor/palette.ts +92 -0
  178. package/src/shared/lib/code/editor/reset-css.test.ts +35 -0
  179. package/src/shared/lib/code/editor/reset-css.ts +32 -0
  180. package/src/shared/lib/code/editor/text-commands.test.ts +178 -0
  181. package/src/shared/lib/code/editor/text-commands.ts +154 -0
  182. package/src/shared/lib/code/syntax-highlight.test.ts +75 -0
  183. package/src/shared/lib/code/syntax-highlight.ts +29 -17
  184. package/src/shared/lib/code/windowed-highlight.ts +52 -1
  185. package/src/shared/lib/fs/diff-parser.test.ts +116 -0
  186. package/src/shared/lib/fs/diff-parser.ts +86 -33
  187. package/src/shared/lib/models/{provider-connection.ts → provider/connection.ts} +37 -0
  188. package/src/shared/lib/models/provider/dialect.ts +179 -0
  189. package/src/shared/lib/models/provider/glyph.test.ts +116 -0
  190. package/src/shared/lib/models/provider/glyph.ts +200 -0
  191. package/src/shared/lib/models/{provider-models.ts → provider/models.ts} +91 -6
  192. package/src/shared/lib/models/provider/presets.test.ts +172 -0
  193. package/src/shared/lib/models/provider/presets.ts +83 -0
  194. package/src/shared/lib/ui/key-binding.test.ts +115 -0
  195. package/src/shared/lib/ui/key-binding.ts +143 -0
  196. package/src/shared/lib/util/platform.test.ts +87 -0
  197. package/src/shared/lib/util/platform.ts +30 -0
  198. package/src/shared/lib/workspace/file-tab-id.ts +29 -0
  199. package/src/shared/lib/workspace/keymap.test.ts +82 -0
  200. package/src/shared/lib/workspace/keymap.ts +30 -0
  201. package/src/shared/lib/workspace/refresh-cadence.ts +9 -0
  202. package/src/shared/lib/workspace/right-panels.ts +8 -0
  203. package/src/shared/lib/workspace/session-state/store.ts +0 -1
  204. package/src/shared/types/index.ts +1 -0
  205. package/src/shared/types/settings/provider.ts +81 -0
  206. package/src/shared/types/settings/usage.ts +88 -14
  207. package/src/shared/types/todo.ts +108 -0
  208. package/tsconfig.json +3 -1
  209. package/dist/client/static/css/index.5f630421b7.css +0 -2
  210. package/dist/client/static/font/KaTeX_AMS-Regular.a79f1c3119.woff2 +0 -0
  211. package/dist/client/static/font/KaTeX_Caligraphic-Bold.ec17d13264.woff2 +0 -0
  212. package/dist/client/static/font/KaTeX_Caligraphic-Regular.55fac25845.woff2 +0 -0
  213. package/dist/client/static/font/KaTeX_Fraktur-Bold.d42a5579b0.woff2 +0 -0
  214. package/dist/client/static/font/KaTeX_Fraktur-Regular.d3c882a649.woff2 +0 -0
  215. package/dist/client/static/font/KaTeX_Main-Bold.c3fb5ac22f.woff2 +0 -0
  216. package/dist/client/static/font/KaTeX_Main-BoldItalic.6f2bb1dff2.woff2 +0 -0
  217. package/dist/client/static/font/KaTeX_Main-Italic.8916142bec.woff2 +0 -0
  218. package/dist/client/static/font/KaTeX_Main-Regular.0462f03bdf.woff2 +0 -0
  219. package/dist/client/static/font/KaTeX_Math-BoldItalic.572d331f69.woff2 +0 -0
  220. package/dist/client/static/font/KaTeX_Math-Italic.f28c23acad.woff2 +0 -0
  221. package/dist/client/static/font/KaTeX_SansSerif-Bold.8c5b5494b6.woff2 +0 -0
  222. package/dist/client/static/font/KaTeX_SansSerif-Italic.3b1e59b3ba.woff2 +0 -0
  223. package/dist/client/static/font/KaTeX_SansSerif-Regular.ba21ed5f84.woff2 +0 -0
  224. package/dist/client/static/font/KaTeX_Script-Regular.03e9641d6f.woff2 +0 -0
  225. package/dist/client/static/font/KaTeX_Size1-Regular.eae34984b3.woff2 +0 -0
  226. package/dist/client/static/font/KaTeX_Size2-Regular.5916a24fa3.woff2 +0 -0
  227. package/dist/client/static/font/KaTeX_Size4-Regular.10d95fd3a2.woff2 +0 -0
  228. package/dist/client/static/font/KaTeX_Typewriter-Regular.a8709e3622.woff2 +0 -0
  229. package/dist/client/static/font/fira-code-latin-400-normal.2655b10c55.woff2 +0 -0
  230. package/dist/client/static/font/fira-code-latin-500-normal.92a991f204.woff2 +0 -0
  231. package/dist/client/static/font/fira-code-latin-600-normal.43788eb7f1.woff2 +0 -0
  232. package/dist/client/static/font/fira-code-latin-700-normal.2667215d33.woff2 +0 -0
  233. package/dist/client/static/js/77.0d81c74cf0.js +0 -139
  234. package/dist/client/static/js/77.0d81c74cf0.js.LICENSE.txt +0 -8
  235. package/dist/client/static/js/async/0.8e59f2eb5b.js +0 -1
  236. package/dist/client/static/js/async/0k.5852e6b4fa.js +0 -1
  237. package/dist/client/static/js/async/0w.9ed36d5860.js +0 -1
  238. package/dist/client/static/js/async/1.f387c12b63.js +0 -1
  239. package/dist/client/static/js/async/12.d091f91c99.js +0 -1
  240. package/dist/client/static/js/async/13.8f810a8a6c.js +0 -1
  241. package/dist/client/static/js/async/19.6fe909d55a.js +0 -160
  242. package/dist/client/static/js/async/1b.23f1141a38.js +0 -1
  243. package/dist/client/static/js/async/1k.973e8e21fc.js +0 -2
  244. package/dist/client/static/js/async/1k.973e8e21fc.js.LICENSE.txt +0 -11
  245. package/dist/client/static/js/async/1p.0d5cddda96.js +0 -1
  246. package/dist/client/static/js/async/2.b589f1733b.js +0 -1
  247. package/dist/client/static/js/async/2v.96165101de.js +0 -1
  248. package/dist/client/static/js/async/3.0cacc08423.js +0 -1
  249. package/dist/client/static/js/async/37.b28b2f61cd.js +0 -1
  250. package/dist/client/static/js/async/3d.5085fb913f.js +0 -1
  251. package/dist/client/static/js/async/3j.357c80ff38.js +0 -2
  252. package/dist/client/static/js/async/3j.357c80ff38.js.LICENSE.txt +0 -11
  253. package/dist/client/static/js/async/3p.bcf32a0983.js +0 -1
  254. package/dist/client/static/js/async/3r.3a69e98ee0.js +0 -64
  255. package/dist/client/static/js/async/3u.1228460e2a.js +0 -1
  256. package/dist/client/static/js/async/3y.3530b824ad.js +0 -198
  257. package/dist/client/static/js/async/3y.3530b824ad.js.LICENSE.txt +0 -5
  258. package/dist/client/static/js/async/4.9442eeb472.js +0 -1
  259. package/dist/client/static/js/async/4e.678285ca55.js +0 -1
  260. package/dist/client/static/js/async/4k.33a0c7eeb4.js +0 -1
  261. package/dist/client/static/js/async/4m.928a1dde85.js +0 -31
  262. package/dist/client/static/js/async/4m.928a1dde85.js.LICENSE.txt +0 -11
  263. package/dist/client/static/js/async/4q.aad20dab25.js +0 -1
  264. package/dist/client/static/js/async/5.ebaf8b89cc.js +0 -36
  265. package/dist/client/static/js/async/51.95bcae98af.js +0 -2
  266. package/dist/client/static/js/async/51.95bcae98af.js.LICENSE.txt +0 -6
  267. package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
  268. package/dist/client/static/js/async/5j.d6407c4248.js +0 -287
  269. package/dist/client/static/js/async/5o.88316d4460.js +0 -62
  270. package/dist/client/static/js/async/6.f034c9b528.js +0 -1
  271. package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
  272. package/dist/client/static/js/async/6o.5b1a06a382.js +0 -1
  273. package/dist/client/static/js/async/7.e4a9288dd0.js +0 -1
  274. package/dist/client/static/js/async/73.308dff4da8.js +0 -1
  275. package/dist/client/static/js/async/7e.a1cc9bfd88.js +0 -1
  276. package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
  277. package/dist/client/static/js/async/7l.fadbbc0583.js +0 -1
  278. package/dist/client/static/js/async/8.5067439d8c.js +0 -1
  279. package/dist/client/static/js/async/8j.ea110663fb.js +0 -1
  280. package/dist/client/static/js/async/a.bce4542392.js +0 -1
  281. package/dist/client/static/js/async/aa.65d3852576.js +0 -1
  282. package/dist/client/static/js/async/ar.9303e5f2e8.js +0 -2
  283. package/dist/client/static/js/async/ar.9303e5f2e8.js.LICENSE.txt +0 -11
  284. package/dist/client/static/js/async/az.d656062b0f.js +0 -1
  285. package/dist/client/static/js/async/b.b4f6f06515.js +0 -1
  286. package/dist/client/static/js/async/bi.09688ba75c.js +0 -1
  287. package/dist/client/static/js/async/bq.586bc738d1.js +0 -1
  288. package/dist/client/static/js/async/c.797d933882.js +0 -1
  289. package/dist/client/static/js/async/c0.0af1e640d3.js +0 -24
  290. package/dist/client/static/js/async/ca.8a52cfd447.js +0 -1
  291. package/dist/client/static/js/async/cf.d34fff5d67.js +0 -1
  292. package/dist/client/static/js/async/ct.b74928f7a3.js +0 -1
  293. package/dist/client/static/js/async/cx.3cb0242a3a.js +0 -226
  294. package/dist/client/static/js/async/d.a5fc8fe1b3.js +0 -1
  295. package/dist/client/static/js/async/da.f62c70f6c8.js +0 -1
  296. package/dist/client/static/js/async/dp.1725d6432c.js +0 -1
  297. package/dist/client/static/js/async/e.93214d622b.js +0 -79
  298. package/dist/client/static/js/async/f.ab50467719.js +0 -19
  299. package/dist/client/static/js/async/f.ab50467719.js.LICENSE.txt +0 -12
  300. package/dist/client/static/js/async/f7.46971e9d6d.js +0 -2
  301. package/dist/client/static/js/async/f7.46971e9d6d.js.LICENSE.txt +0 -6
  302. package/dist/client/static/js/async/f8.bbf0dbae70.js +0 -1
  303. package/dist/client/static/js/async/g.cb81980498.js +0 -2
  304. package/dist/client/static/js/async/g.cb81980498.js.LICENSE.txt +0 -11
  305. package/dist/client/static/js/async/h.4e790471e4.js +0 -27
  306. package/dist/client/static/js/async/h1.393f066d09.js +0 -1
  307. package/dist/client/static/js/async/h3.216e9798ab.js +0 -148
  308. package/dist/client/static/js/async/hd.0da931b622.js +0 -1
  309. package/dist/client/static/js/async/hk.ba7fb04d41.js +0 -1
  310. package/dist/client/static/js/async/hn.b645041e00.js +0 -1
  311. package/dist/client/static/js/async/ho.475a5e1ae4.js +0 -1
  312. package/dist/client/static/js/async/hp.e9fe2e395a.js +0 -2
  313. package/dist/client/static/js/async/hp.e9fe2e395a.js.LICENSE.txt +0 -17
  314. package/dist/client/static/js/async/hs.e7c59d8090.js +0 -41
  315. package/dist/client/static/js/async/i.7c148e8440.js +0 -1
  316. package/dist/client/static/js/async/id.573df20f29.js +0 -1
  317. package/dist/client/static/js/async/j.ab925882d8.js +0 -1
  318. package/dist/client/static/js/async/jd.63886a1f09.js +0 -1
  319. package/dist/client/static/js/async/jg.d86ebd4013.js +0 -86
  320. package/dist/client/static/js/async/jl.874d352278.js +0 -1
  321. package/dist/client/static/js/async/jll.11313b455a.js +0 -1
  322. package/dist/client/static/js/async/js.67182759ef.js +0 -1
  323. package/dist/client/static/js/async/jsc.23b70d6f40.js +0 -1
  324. package/dist/client/static/js/async/jse.334588ff5d.js +0 -1
  325. package/dist/client/static/js/async/jz.72eb929106.js +0 -77
  326. package/dist/client/static/js/async/k.3210286401.js +0 -217
  327. package/dist/client/static/js/async/ka.c1990ecaa3.js +0 -115
  328. package/dist/client/static/js/async/katex-vendor.de6a87c308.js +0 -1
  329. package/dist/client/static/js/async/ku.29c8a59d0c.js +0 -1
  330. package/dist/client/static/js/async/ky.8dab1dade0.js +0 -1
  331. package/dist/client/static/js/async/l.12dd34e1be.js +0 -1
  332. package/dist/client/static/js/async/l9.949710adaf.js +0 -83
  333. package/dist/client/static/js/async/l9.949710adaf.js.LICENSE.txt +0 -13
  334. package/dist/client/static/js/async/ls.9d9a01026c.js +0 -104
  335. package/dist/client/static/js/async/m.eb2e6752e2.js +0 -28
  336. package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +0 -11
  337. package/dist/client/static/js/async/m3.d2b05f9604.js +0 -2
  338. package/dist/client/static/js/async/m3.d2b05f9604.js.LICENSE.txt +0 -6
  339. package/dist/client/static/js/async/ma.2c261b04dd.js +0 -2
  340. package/dist/client/static/js/async/ma.2c261b04dd.js.LICENSE.txt +0 -6
  341. package/dist/client/static/js/async/me.8ace414f80.js +0 -23
  342. package/dist/client/static/js/async/ms.98960af4eb.js +0 -67
  343. package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +0 -12
  344. package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
  345. package/dist/client/static/js/async/n.a9f1c51701.js +0 -1
  346. package/dist/client/static/js/async/na.a36cd532d6.js +0 -1
  347. package/dist/client/static/js/async/nk.fc7e58325e.js +0 -1
  348. package/dist/client/static/js/async/o.36353dfb88.js +0 -1
  349. package/dist/client/static/js/async/oc.b73042e8bd.js +0 -8
  350. package/dist/client/static/js/async/og.8eb577893a.js +0 -193
  351. package/dist/client/static/js/async/p.48ecfcd14e.js +0 -1
  352. package/dist/client/static/js/async/px.fc6877924b.js +0 -1
  353. package/dist/client/static/js/async/q.22588845a9.js +0 -21
  354. package/dist/client/static/js/async/q.22588845a9.js.LICENSE.txt +0 -6
  355. package/dist/client/static/js/async/q5.97dc1481b6.js +0 -286
  356. package/dist/client/static/js/async/qs.2a9ddb3c72.js +0 -1
  357. package/dist/client/static/js/async/r.51010ad87c.js +0 -1
  358. package/dist/client/static/js/async/rd.cb6bf495bb.js +0 -2
  359. package/dist/client/static/js/async/rd.cb6bf495bb.js.LICENSE.txt +0 -4
  360. package/dist/client/static/js/async/s.0f395cdda5.js +0 -94
  361. package/dist/client/static/js/async/t.4dca0e2ba8.js +0 -1
  362. package/dist/client/static/js/async/t9.d44f57d518.js +0 -1
  363. package/dist/client/static/js/async/ta.973534bbec.js +0 -1
  364. package/dist/client/static/js/async/tr.e878658140.js +0 -309
  365. package/dist/client/static/js/async/u.0871ac5b22.js +0 -1
  366. package/dist/client/static/js/async/ud.3065bfee31.js +0 -2
  367. package/dist/client/static/js/async/ud.3065bfee31.js.LICENSE.txt +0 -4
  368. package/dist/client/static/js/async/up.ea990da0ff.js +0 -32
  369. package/dist/client/static/js/async/uw.8890abbabc.js +0 -1
  370. package/dist/client/static/js/async/v.7d28d3c241.js +0 -34
  371. package/dist/client/static/js/async/vp.9b13bf8610.js +0 -1
  372. package/dist/client/static/js/async/vt.42416b707d.js +0 -1
  373. package/dist/client/static/js/async/w.08ac210f60.js +0 -1
  374. package/dist/client/static/js/async/wt.ad92393a90.js +0 -1
  375. package/dist/client/static/js/async/x.563f72ca6b.js +0 -1
  376. package/dist/client/static/js/async/x0.475516418f.js +0 -1
  377. package/dist/client/static/js/async/x1.da8687159b.js +0 -90
  378. package/dist/client/static/js/async/xe.da8dcc2015.js +0 -2
  379. package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +0 -11
  380. package/dist/client/static/js/async/xl.a3c562a58c.js +0 -1
  381. package/dist/client/static/js/async/xn.6510c0abe2.js +0 -2
  382. package/dist/client/static/js/async/xn.6510c0abe2.js.LICENSE.txt +0 -6
  383. package/dist/client/static/js/async/xn1.83a7784fe1.js +0 -10
  384. package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +0 -11
  385. package/dist/client/static/js/async/xnl.feb0021e50.js +0 -2
  386. package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +0 -12
  387. package/dist/client/static/js/async/y.1a9ed7a5d8.js +0 -1
  388. package/dist/client/static/js/async/y6.43c57defe4.js +0 -1
  389. package/dist/client/static/js/async/yb.8c1342401d.js +0 -1
  390. package/dist/client/static/js/async/ybq.4491f2f2dc.js +0 -1
  391. package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
  392. package/dist/client/static/js/async/yq.917ddd7ba2.js +0 -1
  393. package/dist/client/static/js/async/ys.00687cf82d.js +0 -38
  394. package/dist/client/static/js/async/yv.d99d829136.js +0 -1
  395. package/dist/client/static/js/async/z.56f61a4374.js +0 -1
  396. package/dist/client/static/js/async/zi.5fdb07b86a.js +0 -24
  397. package/dist/client/static/js/async/zj.654f37e8ff.js +0 -1
  398. package/dist/client/static/js/async/zn.966d6f1cc6.js +0 -1
  399. package/dist/client/static/js/async/zo.26213b40c0.js +0 -1
  400. package/dist/client/static/js/async/zx.e9f4105e00.js +0 -1
  401. package/dist/client/static/js/index.e6a257402a.js +0 -38
  402. package/dist/client/static/js/index.e6a257402a.js.LICENSE.txt +0 -4
  403. package/dist/client/static/js/markdown-vendor.71e43278b3.js +0 -144
  404. package/dist/client/static/js/shiki-vendor.cb8452ddcd.js +0 -1
  405. package/src/client/components/settings/categories/provider-settings/Icons.tsx +0 -35
  406. package/src/client/hooks/settings/providers.ts +0 -326
  407. package/src/server/plugins/dev-assets.ts +0 -49
  408. package/src/shared/lib/code/editor-utils.ts +0 -16
  409. package/src/shared/lib/models/provider-presets.ts +0 -29
  410. /package/dist/client/{apple-touch-icon.png → static/apple-touch-icon-dskx5sr3.png} +0 -0
  411. /package/dist/client/{icon-192x192.png → static/icon-192x192-6gn5cr2f.png} +0 -0
  412. /package/dist/client/{icon.svg → static/icon-d07z9dtz.svg} +0 -0
  413. /package/dist/client/{manifest.webmanifest → static/manifest-bnyy75kt.webmanifest} +0 -0
  414. /package/{dist/client → public}/icon-512x512.png +0 -0
  415. /package/{dist/client → public}/icon-maskable-512x512.png +0 -0
  416. /package/{dist/client → public}/sw.js +0 -0
  417. /package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.ts +0 -0
  418. /package/src/shared/lib/models/{provider-cleanup.ts → provider/cleanup.ts} +0 -0
  419. /package/src/shared/lib/models/{provider-label.ts → provider/label.ts} +0 -0
@@ -0,0 +1,107 @@
1
+ import { DollarSign } from 'lucide-preact';
2
+
3
+ interface PriceFieldsProps {
4
+ costInput: string;
5
+ costOutput: string;
6
+ costCacheRead: string;
7
+ costCacheWrite: string;
8
+ onCostInputChange: (value: string) => void;
9
+ onCostOutputChange: (value: string) => void;
10
+ onCostCacheReadChange: (value: string) => void;
11
+ onCostCacheWriteChange: (value: string) => void;
12
+ error?: string;
13
+ }
14
+
15
+ /** One labelled price box. `hint` distinguishes the four rates. */
16
+ function PriceField({
17
+ value,
18
+ onChange,
19
+ label,
20
+ hint,
21
+ }: {
22
+ value: string;
23
+ onChange: (value: string) => void;
24
+ label: string;
25
+ hint: string;
26
+ }) {
27
+ return (
28
+ <div>
29
+ <label className="block text-[11px] font-medium text-ink/70 mb-1" title={hint}>
30
+ {label}
31
+ </label>
32
+ <div className="relative flex items-center">
33
+ <DollarSign size={12} className="absolute left-2.5 text-ink/40 pointer-events-none" />
34
+ <input
35
+ type="text"
36
+ inputMode="decimal"
37
+ value={value}
38
+ onChange={(e) => onChange(e.currentTarget.value)}
39
+ placeholder="0"
40
+ className="w-full bg-paper border border-ink/20 rounded-md pl-7 pr-2 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
41
+ />
42
+ </div>
43
+ </div>
44
+ );
45
+ }
46
+
47
+ /**
48
+ * The four `models.yml` cost rates, in USD per 1M tokens.
49
+ *
50
+ * omp requires all four fields together, so input/output are the pair that
51
+ * decides whether a price is written at all; the two cache rates then default
52
+ * to 0, which is the honest value for "no cached-token discount". Cache rates
53
+ * matter for accounting rather than routing: without them every cached turn is
54
+ * billed at the full input rate, which is why they are worth asking for
55
+ * separately instead of deriving from input.
56
+ */
57
+ export function PriceFields({
58
+ costInput,
59
+ costOutput,
60
+ costCacheRead,
61
+ costCacheWrite,
62
+ onCostInputChange,
63
+ onCostOutputChange,
64
+ onCostCacheReadChange,
65
+ onCostCacheWriteChange,
66
+ error,
67
+ }: PriceFieldsProps) {
68
+ return (
69
+ <div>
70
+ <label className="block text-xs font-semibold text-ink mb-1.5">
71
+ Price <span className="font-normal text-ink/40">— USD per 1M tokens, optional</span>
72
+ </label>
73
+ <div className="grid grid-cols-2 gap-3">
74
+ <PriceField
75
+ value={costInput}
76
+ onChange={onCostInputChange}
77
+ label="Input"
78
+ hint="Uncached prompt tokens"
79
+ />
80
+ <PriceField
81
+ value={costOutput}
82
+ onChange={onCostOutputChange}
83
+ label="Output"
84
+ hint="Generated tokens"
85
+ />
86
+ <PriceField
87
+ value={costCacheRead}
88
+ onChange={onCostCacheReadChange}
89
+ label="Cache read"
90
+ hint="Prompt tokens served from the provider's cache — usually a fraction of the input rate"
91
+ />
92
+ <PriceField
93
+ value={costCacheWrite}
94
+ onChange={onCostCacheWriteChange}
95
+ label="Cache write"
96
+ hint="Prompt tokens written into the provider's cache"
97
+ />
98
+ </div>
99
+ {error && <p className="text-[11px] text-error mt-1">{error}</p>}
100
+ <p className="text-[10px] text-ink/40 mt-1">
101
+ Used for omp's cost accounting. Input and output decide whether a price is
102
+ written at all; the cache rates fall back to 0, which bills cached turns at
103
+ the full input rate.
104
+ </p>
105
+ </div>
106
+ );
107
+ }
@@ -1,9 +1,9 @@
1
1
  import { useState } from 'preact/hooks';
2
2
  import type { FormEvent } from 'preact/compat';
3
- import { AlertCircle, CheckCircle2, DownloadCloud, Globe, Key, RefreshCw } from 'lucide-preact';
3
+ import { AlertCircle, CheckCircle2, DownloadCloud, Globe, Info, Key, RefreshCw } from 'lucide-preact';
4
4
  import type { ProviderItem } from '@/shared/types';
5
- import { fetchProviderModelsRemote } from '@/shared/lib/models/provider-models';
6
- import { ProviderIcon } from '@/client/components/settings/categories/provider-settings/Icons';
5
+ import { fetchProviderModelsRemote } from '@/shared/lib/models/provider/models';
6
+ import { ProviderIcon } from '@/client/components/common/provider-icon';
7
7
  import { Modal } from '@/client/components/common/Modal';
8
8
 
9
9
  interface ReconnectModalProps {
@@ -23,6 +23,7 @@ export function ReconnectModal({
23
23
  onReconnect,
24
24
  onFetchModels,
25
25
  }: ReconnectModalProps) {
26
+ const isKeyless = provider.auth === 'none';
26
27
  const [apiKey, setApiKey] = useState(provider.apiKey || 'sk-••••••••••••••••••••••••');
27
28
  const [baseUrl, setBaseUrl] = useState(provider.baseUrl || 'https://api.openai.com/v1');
28
29
  const [isTesting, setIsTesting] = useState(false);
@@ -39,7 +40,11 @@ export function ReconnectModal({
39
40
  const handleTestConnection = async () => {
40
41
  setIsTesting(true);
41
42
  setTestResult(null);
42
- const result = await fetchProviderModelsRemote(baseUrl, draftCredentials.apiKey);
43
+ const result = await fetchProviderModelsRemote({
44
+ baseUrl,
45
+ ...(draftCredentials.apiKey ? { apiKey: draftCredentials.apiKey } : {}),
46
+ ...(provider.api ? { api: provider.api } : {}),
47
+ });
43
48
  setIsTesting(false);
44
49
  setTestResult(result.ok ? 'success' : 'failed');
45
50
  };
@@ -47,7 +52,10 @@ export function ReconnectModal({
47
52
  const handleSave = async (e: FormEvent) => {
48
53
  e.preventDefault();
49
54
  onReconnect({
50
- apiKey,
55
+ // A keyless provider is configured by `auth: none`; storing the masked
56
+ // placeholder as its credential would show a key that authenticates
57
+ // nothing.
58
+ ...(isKeyless ? {} : { apiKey }),
51
59
  baseUrl,
52
60
  status: 'connected',
53
61
  });
@@ -60,7 +68,7 @@ export function ReconnectModal({
60
68
  onClose={onClose}
61
69
  header={
62
70
  <div className="flex items-center gap-2.5">
63
- <ProviderIcon icon={provider.icon} size={18} />
71
+ <ProviderIcon icon={provider.icon} slug={provider.slug} name={provider.name} size={18} />
64
72
  <div>
65
73
  <h3 className="text-sm font-semibold text-ink">
66
74
  Authentication & Credentials
@@ -108,21 +116,33 @@ export function ReconnectModal({
108
116
  </div>
109
117
  </div>
110
118
 
111
- {/* API Key */}
112
- <div>
113
- <label className="block text-xs font-semibold text-ink mb-1">
114
- API Key / Auth Token
115
- </label>
116
- <div className="relative flex items-center">
117
- <Key size={13} className="absolute left-2.5 text-ink/40 pointer-events-none" />
118
- <input
119
- type="text"
120
- value={apiKey}
121
- onChange={(e) => setApiKey(e.currentTarget.value)}
122
- 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"
123
- />
119
+ {/* API Key — omitted for a keyless provider, which omp configures by
120
+ `auth: none` and which has no credential to store. */}
121
+ {isKeyless ? (
122
+ <div className="flex items-start gap-2 px-3 py-2 rounded-md bg-ink/5 border border-ink/10 text-[11px] text-ink/60">
123
+ <Info size={13} className="mt-0.5 flex-shrink-0 text-ink/50" />
124
+ <span>
125
+ This provider is registered with <span className="font-mono">auth: none</span>, so omp
126
+ sends no credential. Set Auth to “API key” in the provider’s own entry if the endpoint
127
+ starts requiring one.
128
+ </span>
124
129
  </div>
125
- </div>
130
+ ) : (
131
+ <div>
132
+ <label className="block text-xs font-semibold text-ink mb-1">
133
+ API Key / Auth Token
134
+ </label>
135
+ <div className="relative flex items-center">
136
+ <Key size={13} className="absolute left-2.5 text-ink/40 pointer-events-none" />
137
+ <input
138
+ type="text"
139
+ value={apiKey}
140
+ onChange={(e) => setApiKey(e.currentTarget.value)}
141
+ 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"
142
+ />
143
+ </div>
144
+ </div>
145
+ )}
126
146
 
127
147
  {/* Test connection indicator */}
128
148
  <div className="flex items-center justify-between pt-1">
@@ -154,8 +174,9 @@ export function ReconnectModal({
154
174
  <div className="flex items-start gap-2 px-3 py-2 rounded-md bg-ink/5 border border-ink/10 text-[11px] text-ink/60">
155
175
  <DownloadCloud size={13} className="mt-0.5 flex-shrink-0 text-ink/50" />
156
176
  <span>
157
- On save, models are fetched from the endpoint and merged into the
158
- list below — existing models are kept, only new ones are added.
177
+ {isKeyless
178
+ ? 'On save, models are fetched from the endpoint and merged into the list below — a keyless server needs none to answer.'
179
+ : 'On save, models are fetched from the endpoint and merged into the list below — existing models are kept, only new ones are added.'}
159
180
  </span>
160
181
  </div>
161
182
  </Modal>
@@ -2,7 +2,7 @@
2
2
  import { useRef, useState } from 'preact/hooks';
3
3
  import { Check, ChevronDown, Folder, Plus } from 'lucide-preact';
4
4
  import type { ProviderItem } from '@/shared/types';
5
- import { ProviderIcon } from '@/client/components/settings/categories/provider-settings/Icons';
5
+ import { ProviderIcon } from '@/client/components/common/provider-icon';
6
6
  import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
7
7
 
8
8
  interface ProviderSidebarListProps {
@@ -115,7 +115,7 @@ export function ProviderSidebarList({
115
115
  }`}
116
116
  >
117
117
  <div className="flex items-center gap-2.5 min-w-0">
118
- <ProviderIcon icon={item.icon} size={15} className="flex-shrink-0" />
118
+ <ProviderIcon icon={item.icon} slug={item.slug} name={item.name} size={15} className="flex-shrink-0" />
119
119
  <span className="text-[12px] truncate">{item.name}</span>
120
120
  </div>
121
121
 
@@ -0,0 +1,115 @@
1
+ import type { UsageLimitWindow } from '@/shared/types';
2
+ import { formatDateTime, formatFractionPercent, formatUsageAmount } from '@/client/components/settings/categories/usage-settings/format';
3
+
4
+ interface LimitRowProps {
5
+ limit: UsageLimitWindow;
6
+ }
7
+
8
+ /** One provider-reported quota window: label, bar, used/remaining, reset. */
9
+ function LimitRow({ limit }: LimitRowProps) {
10
+ const percent = Math.min(100, Math.max(0, (limit.usedFraction ?? 0) * 100));
11
+ const scope = limit.modelId ?? limit.accountId;
12
+ const usedText = limit.used !== undefined ? formatUsageAmount(limit.used, limit.unit) : undefined;
13
+ const limitText = limit.limit !== undefined ? formatUsageAmount(limit.limit, limit.unit) : undefined;
14
+ const remainingText = limit.remaining !== undefined
15
+ ? formatUsageAmount(limit.remaining, limit.unit)
16
+ : undefined;
17
+
18
+ return (
19
+ <div className="bg-paper border border-ink/15 rounded-lg p-3 shadow-xs space-y-2">
20
+ <div className="flex items-start justify-between gap-2">
21
+ <div className="min-w-0">
22
+ <div className="text-[11px] font-semibold text-ink truncate">{limit.label}</div>
23
+ {scope && <div className="text-[10px] text-ink/50 truncate">{scope}</div>}
24
+ </div>
25
+ <span className="text-[10px] text-ink/50 flex-shrink-0 whitespace-nowrap">
26
+ {limit.resetsAt !== undefined ? `Resets ${formatDateTime(new Date(limit.resetsAt).toISOString())}` : ''}
27
+ </span>
28
+ </div>
29
+
30
+ <div className="h-1.5 w-full bg-ink/10 rounded-full overflow-hidden">
31
+ <div
32
+ className={`h-full rounded-full ${limit.status === 'exhausted' ? 'bg-error/70' : 'bg-ink/80'}`}
33
+ style={{ width: `${percent}%` }}
34
+ />
35
+ </div>
36
+
37
+ <div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-0.5 text-[10px] text-ink/60">
38
+ <span>
39
+ Used{' '}
40
+ <span className="font-semibold text-ink">
41
+ {limit.unit === 'percent' || !usedText ? formatFractionPercent(limit.usedFraction ?? 0) : usedText}
42
+ </span>
43
+ {/* A percent unit is already a share of 100, so naming the limit
44
+ again would print "42% of 100%". */}
45
+ {limit.unit !== 'percent' && usedText && limitText ? ` of ${limitText}` : ''}
46
+ </span>
47
+ <span className="flex items-center gap-2">
48
+ {limit.status !== 'ok' && limit.status !== 'unknown' && (
49
+ <span className="text-[10px] font-mono uppercase px-1.5 py-0.5 rounded bg-ink/10 text-ink/70">
50
+ {limit.status}
51
+ </span>
52
+ )}
53
+ {remainingText && (
54
+ <span>
55
+ Remaining <span className="font-semibold text-ink">{remainingText}</span>
56
+ </span>
57
+ )}
58
+ </span>
59
+ </div>
60
+
61
+ {limit.notes && limit.notes.length > 0 && (
62
+ <ul className="text-[10px] text-ink/50 leading-relaxed space-y-0.5">
63
+ {limit.notes.map((note) => (
64
+ <li key={note}>{note}</li>
65
+ ))}
66
+ </ul>
67
+ )}
68
+ </div>
69
+ );
70
+ }
71
+
72
+ interface LimitsSectionProps {
73
+ limits: UsageLimitWindow[];
74
+ /** Whether omp ships a usage adapter for this provider. */
75
+ tracked: boolean;
76
+ /** omp tracks this provider but its endpoint returned nothing. */
77
+ unavailable?: boolean;
78
+ /** Provider-wide caveats shown above the windows. */
79
+ notes?: string[];
80
+ }
81
+
82
+ /** Generic quota windows for any provider omp reports usage for. */
83
+ export function LimitsSection({ limits, tracked, unavailable, notes }: LimitsSectionProps) {
84
+ return (
85
+ <div className="space-y-2">
86
+ <h4 className="text-[10px] font-mono font-semibold uppercase tracking-wider text-ink/40">
87
+ Quota windows
88
+ </h4>
89
+
90
+ {notes && notes.length > 0 && (
91
+ <ul className="text-[10px] text-ink/50 leading-relaxed space-y-0.5">
92
+ {notes.map((note) => (
93
+ <li key={note}>{note}</li>
94
+ ))}
95
+ </ul>
96
+ )}
97
+
98
+ {limits.length === 0 ? (
99
+ <p className="text-[11px] text-ink/50">
100
+ {!tracked
101
+ ? 'No quota source for this provider: omp ships no usage adapter for it and it exposes no balance endpoint the chamber knows.'
102
+ : unavailable
103
+ ? 'omp tracks this provider but its usage endpoint returned no data — an invalid or expired credential, an unreachable endpoint, or a plan without quota windows.'
104
+ : 'No quota windows were reported for this provider.'}
105
+ </p>
106
+ ) : (
107
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-2.5">
108
+ {limits.map((limit) => (
109
+ <LimitRow key={limit.id} limit={limit} />
110
+ ))}
111
+ </div>
112
+ )}
113
+ </div>
114
+ );
115
+ }
@@ -1,47 +1,40 @@
1
1
  import type { ReactNode } from 'preact/compat';
2
- import { AlertCircle, KeyRound } from 'lucide-preact';
2
+ import { AlertCircle } from 'lucide-preact';
3
3
 
4
4
  interface ProviderCardProps {
5
5
  providerName: string;
6
- configured: boolean;
6
+ /** Where the credential was found, e.g. "models.yml · agent.db". */
7
+ credentialSources: string[];
8
+ /** Provider-side failure, already human-readable. */
7
9
  error?: string;
8
10
  note?: string;
9
11
  children: ReactNode;
10
12
  }
11
13
 
12
- /** Provider shell: header, configured empty state, error line, and content slot.
13
- * Content is rendered whenever the provider is configured, so partial data
14
- * (e.g. quota fails but balance works) still shows the sections that succeeded. */
15
- export function ProviderCard({ providerName, configured, error, note, children }: ProviderCardProps) {
14
+ /** Provider shell: header, credential provenance, error line, and content slot.
15
+ * Every listed provider has a resolved credential — a keyless provider is never
16
+ * rendered at all — so there is no "not configured" state here. */
17
+ export function ProviderCard({ providerName, credentialSources, error, note, children }: ProviderCardProps) {
16
18
  return (
17
19
  <section className="bg-paper border border-ink/15 rounded-lg shadow-xs">
18
20
  <header className="flex items-center justify-between gap-2 px-3.5 py-2.5 border-b border-ink/10">
19
21
  <span className="text-xs font-semibold text-ink">{providerName}</span>
20
- <span className="text-[10px] font-mono uppercase tracking-wider text-ink/50">
21
- {configured ? 'Connected' : 'Not configured'}
22
+ <span
23
+ className="text-[10px] font-mono uppercase tracking-wider text-ink/50 truncate"
24
+ title={`Credentials from ${credentialSources.join(', ')}`}
25
+ >
26
+ {credentialSources.join(' · ')}
22
27
  </span>
23
28
  </header>
24
29
 
25
30
  <div className="p-3.5 space-y-4">
26
- {!configured ? (
27
- <div className="flex items-start gap-2 text-ink/60 text-xs leading-relaxed">
28
- <KeyRound size={14} className="shrink-0 mt-0.5 text-ink/40" />
29
- <p>
30
- Add the <span className="font-semibold text-ink">{providerName}</span> API key in Settings →
31
- Providers to see its balance, quota, and usage here.
32
- </p>
33
- </div>
34
- ) : (
35
- <>
36
- {error && (
37
- <p className="flex items-start gap-2 text-error text-xs leading-relaxed">
38
- <AlertCircle size={14} className="shrink-0 mt-0.5" />
39
- <span className="break-words">{error}</span>
40
- </p>
41
- )}
42
- {children}
43
- </>
31
+ {error && (
32
+ <p className="flex items-start gap-2 text-error text-xs leading-relaxed">
33
+ <AlertCircle size={14} className="shrink-0 mt-0.5" />
34
+ <span className="break-words">{error}</span>
35
+ </p>
44
36
  )}
37
+ {children}
45
38
 
46
39
  {note && (
47
40
  <p className="text-[10px] text-ink/50 leading-relaxed border-t border-ink/10 pt-2.5">{note}</p>
@@ -1,39 +1,46 @@
1
- import type { UsageReport } from '@/shared/types';
1
+ import type { UsageProviderSummary } from '@/shared/types';
2
2
  import { ProviderCard } from '@/client/components/settings/categories/usage-settings/ProviderCard';
3
3
  import { KenariCard } from '@/client/components/settings/categories/usage-settings/KenariCard';
4
4
  import { DeepSeekCard } from '@/client/components/settings/categories/usage-settings/DeepSeekCard';
5
- import type { UsageProviderId } from '@/client/components/settings/categories/usage-settings/providers';
5
+ import { LimitsSection } from '@/client/components/settings/categories/usage-settings/LimitsSection';
6
6
 
7
7
  const DEEPSEEK_NOTE =
8
8
  'DeepSeek exposes no usage or quota API — only the prepaid balance (and the web console at platform.deepseek.com) is available.';
9
9
 
10
10
  interface ProviderDetailProps {
11
- providerId: UsageProviderId;
12
- report: UsageReport;
11
+ summary: UsageProviderSummary;
13
12
  }
14
13
 
15
- /** Renders one provider's card (Kenari.id or DeepSeek) from a usage report. */
16
- export function ProviderDetail({ providerId, report }: ProviderDetailProps) {
17
- if (providerId === 'kenari') {
18
- return (
19
- <ProviderCard
20
- providerName="Kenari.id"
21
- configured={report.kenari.configured}
22
- error={report.kenari.error}
23
- >
24
- <KenariCard report={report.kenari} />
25
- </ProviderCard>
26
- );
27
- }
14
+ /**
15
+ * Renders one provider's card. kenari and DeepSeek carry richer reports (wallet
16
+ * and Rupiah quota / prepaid balance); every other provider renders the generic
17
+ * quota windows, which for a pay-as-you-go vendor is its credit balance.
18
+ */
19
+ export function ProviderDetail({ summary }: ProviderDetailProps) {
20
+ const hasRichReport = Boolean(summary.kenari || summary.deepseek);
28
21
 
29
22
  return (
30
23
  <ProviderCard
31
- providerName="DeepSeek"
32
- configured={report.deepseek.configured}
33
- error={report.deepseek.error}
34
- note={DEEPSEEK_NOTE}
24
+ providerName={summary.name}
25
+ credentialSources={summary.credentialSources}
26
+ error={summary.error}
27
+ note={summary.deepseek ? DEEPSEEK_NOTE : undefined}
35
28
  >
36
- <DeepSeekCard report={report.deepseek} />
29
+ {summary.kenari && <KenariCard report={summary.kenari} />}
30
+ {summary.deepseek && <DeepSeekCard report={summary.deepseek} />}
31
+
32
+ {/* The rich reports already render their own quota and usage tables, so
33
+ the generic windows would only duplicate them. When the provider
34
+ failed outright and reported no windows, the error line above already
35
+ says so — a second "no quota source" note would contradict it. */}
36
+ {!hasRichReport && (summary.limits.length > 0 || !summary.error) && (
37
+ <LimitsSection
38
+ limits={summary.limits}
39
+ tracked={summary.tracked}
40
+ unavailable={summary.limitsUnavailable}
41
+ notes={summary.notes}
42
+ />
43
+ )}
37
44
  </ProviderCard>
38
45
  );
39
46
  }
@@ -1,6 +1,6 @@
1
- import { Bot, Coins } from 'lucide-preact';
2
1
  import type { UsageReport } from '@/shared/types';
3
- import { buildProviders, statusLabel, type UsageProviderId } from '@/client/components/settings/categories/usage-settings/providers';
2
+ import { buildProviders, statusLabel, type ProviderEntry, type UsageProviderId } from '@/client/components/settings/categories/usage-settings/providers';
3
+ import { ProviderIcon } from '@/client/components/common/provider-icon';
4
4
 
5
5
  interface UsageSidebarListProps {
6
6
  report: UsageReport;
@@ -8,6 +8,14 @@ interface UsageSidebarListProps {
8
8
  onSelectProvider: (providerId: UsageProviderId) => void;
9
9
  }
10
10
 
11
+ function ProviderBadge({ provider, selected }: { provider: ProviderEntry; selected: boolean }) {
12
+ return (
13
+ <span className={selected ? 'text-ink/80' : 'text-ink/60'}>
14
+ <ProviderIcon slug={provider.id} name={provider.name} size={14} />
15
+ </span>
16
+ );
17
+ }
18
+
11
19
  export function UsageSidebarList({
12
20
  report,
13
21
  selectedProviderId,
@@ -18,59 +26,63 @@ export function UsageSidebarList({
18
26
  return (
19
27
  <div className="w-56 sm:w-64 border-r border-ink/10 h-full flex flex-col bg-paper/50 flex-shrink-0 select-none">
20
28
  <div className="px-3.5 py-2.5 border-b border-ink/10 flex items-center justify-between">
21
- <span className="text-xs font-semibold text-ink">Total {providers.length} providers</span>
29
+ <span className="text-xs font-semibold text-ink">
30
+ {providers.length} provider{providers.length === 1 ? '' : 's'} with keys
31
+ </span>
22
32
  </div>
23
33
 
24
34
  <div className="flex-1 scrollbar-overlay-container scrollbar-overlay-static p-1.5 space-y-3 text-xs">
25
- <div>
26
- <div className="px-2.5 pt-1.5 pb-1 text-[10px] font-bold text-ink/50 uppercase tracking-wider">
27
- Providers
28
- </div>
29
- <div className="space-y-0.5">
30
- {providers.map((provider) => {
31
- const isSelected = provider.id === selectedProviderId;
32
- const isConnected = provider.configured && !provider.error;
33
- const Icon = provider.id === 'kenari' ? Coins : Bot;
35
+ {providers.length === 0 ? (
36
+ <p className="px-2.5 py-3 text-[11px] text-ink/50 leading-relaxed">
37
+ No provider credentials detected. Add an API key in Settings → Providers.
38
+ </p>
39
+ ) : (
40
+ <div>
41
+ <div className="px-2.5 pt-1.5 pb-1 text-[10px] font-bold text-ink/50 uppercase tracking-wider">
42
+ Providers
43
+ </div>
44
+ <div className="space-y-0.5">
45
+ {providers.map((provider) => {
46
+ const isSelected = provider.id === selectedProviderId;
34
47
 
35
- return (
36
- <button
37
- key={provider.id}
38
- type="button"
39
- onClick={() => onSelectProvider(provider.id)}
40
- className={`w-full text-left px-2.5 py-2 rounded-lg transition-colors cursor-pointer flex flex-col gap-0.5 group ${
41
- isSelected
42
- ? 'bg-ink/10 text-ink font-medium shadow-2xs'
43
- : 'text-ink/80 hover:bg-ink/5 hover:text-ink'
44
- }`}
45
- >
46
- <div className="flex items-center justify-between gap-1.5">
47
- <div className="flex items-center gap-1.5 min-w-0">
48
- <div className="relative">
49
- <Icon
50
- className={`w-3.5 h-3.5 flex-shrink-0 ${isSelected ? 'text-ink/80' : 'text-ink/60'}`}
51
- />
52
- <span
53
- className={`absolute -top-0.5 -right-0.5 w-1.5 h-1.5 rounded-full ${
54
- isConnected ? 'bg-ink' : 'bg-ink/20'
55
- }`}
56
- />
48
+ return (
49
+ <button
50
+ key={provider.id}
51
+ type="button"
52
+ onClick={() => onSelectProvider(provider.id)}
53
+ className={`w-full text-left px-2.5 py-2 rounded-lg transition-colors cursor-pointer flex flex-col gap-0.5 group ${
54
+ isSelected
55
+ ? 'bg-ink/10 text-ink font-medium shadow-2xs'
56
+ : 'text-ink/80 hover:bg-ink/5 hover:text-ink'
57
+ }`}
58
+ >
59
+ <div className="flex items-center justify-between gap-1.5">
60
+ <div className="flex items-center gap-1.5 min-w-0">
61
+ <div className="relative">
62
+ <ProviderBadge provider={provider} selected={isSelected} />
63
+ <span
64
+ className={`absolute -top-0.5 -right-0.5 w-1.5 h-1.5 rounded-full ${
65
+ provider.error ? 'bg-error/60' : 'bg-ink'
66
+ }`}
67
+ />
68
+ </div>
69
+ <span className="font-semibold text-xs text-ink truncate">{provider.name}</span>
57
70
  </div>
58
- <span className="font-semibold text-xs text-ink truncate">{provider.name}</span>
71
+ <span
72
+ className={`text-[10px] font-mono uppercase px-1.5 py-0.5 rounded bg-ink/10 font-medium ${
73
+ provider.error ? 'text-error' : 'text-ink/70'
74
+ }`}
75
+ >
76
+ {statusLabel(provider)}
77
+ </span>
59
78
  </div>
60
- <span
61
- className={`text-[10px] font-mono uppercase px-1.5 py-0.5 rounded bg-ink/10 font-medium ${
62
- provider.error ? 'text-error' : 'text-ink/70'
63
- }`}
64
- >
65
- {statusLabel(provider)}
66
- </span>
67
- </div>
68
- <p className="text-[11px] text-ink/60 line-clamp-1 leading-snug pl-5">{provider.hint}</p>
69
- </button>
70
- );
71
- })}
79
+ <p className="text-[11px] text-ink/60 line-clamp-1 leading-snug pl-5">{provider.hint}</p>
80
+ </button>
81
+ );
82
+ })}
83
+ </div>
72
84
  </div>
73
- </div>
85
+ )}
74
86
  </div>
75
87
  </div>
76
88
  );