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
@@ -1,26 +1,43 @@
1
1
  import { json } from '@/server/lib/remix-compat';
2
2
  import type { ActionFunctionArgs } from '@/server/lib/remix-compat';
3
3
  import { isMockMode } from '@/server/mock.server';
4
- import { findCatalogModel, loadModelsDevCatalog } from '@/shared/lib/models/catalog';
5
- import { extractModels } from '@/shared/lib/models/remote-list';
6
4
  import { upsertOmpProviderModels } from '@/server/lib/omp/config/providers';
7
5
  import { invalidateModelsCaches } from '@/shared/lib/models/server-cache';
6
+ import {
7
+ enrichFromCatalog,
8
+ fetchRemoteModels,
9
+ toOmpSeed,
10
+ } from '@/server/lib/models/provider-listing.server';
11
+ import {
12
+ inferProviderApi,
13
+ isOmpProviderApi,
14
+ isProviderAuthMode,
15
+ isProviderDiscoveryType,
16
+ } from '@/shared/lib/models/provider/dialect';
8
17
  import type { ProviderModel } from '@/shared/types';
9
18
 
10
19
  /**
11
- * POST /api/settings/provider-models — lists models from an OpenAI-compatible
12
- * provider endpoint for the providers settings auto-fetch. Body:
13
- * { baseUrl: string, apiKey?: string, providerSlug?: string,
14
- * persistToOmp?: boolean } → { ok, models, omp? } | { ok: false, error }.
15
- * Tries Bearer auth, then Anthropic x-api-key, then Gemini ?key= — the first
16
- * style returning a recognizable list wins. Models missing metadata (context,
17
- * capabilities, price) are enriched from the models.dev catalog. With
18
- * persistToOmp the enriched models are also registered into the native omp
19
- * models.yml (add-only) so the agent's usage tracking knows their prices.
20
+ * POST /api/settings/provider-models — lists models from a provider endpoint
21
+ * for the providers settings auto-fetch, and optionally registers the provider
22
+ * in omp's own models.yml. Body:
23
+ * { baseUrl: string, apiKey?: string, providerSlug?: string, persistToOmp?: boolean,
24
+ * api?: OmpProviderApi, auth?: ProviderAuthMode, discovery?: ProviderDiscoveryType,
25
+ * registerOnly?: boolean } → { ok, models, omp? } | { ok: false, error }.
26
+ *
27
+ * `api` picks the dialect, which decides both the auth header the listing probe
28
+ * sends and the `api` written to models.yml; when it is omitted the endpoint is
29
+ * classified by URL and the probe tries the three common auth styles. Models
30
+ * missing metadata (context, capabilities, price) are enriched from the
31
+ * models.dev catalog. The probe itself lives in
32
+ * `@/server/lib/models/provider-listing.server`.
33
+ *
34
+ * `discovery` and `registerOnly` skip the listing entirely: the first registers
35
+ * a provider OMP will list itself, the second registers an endpoint override
36
+ * for a provider omp already bundles. Both are legitimate with an empty model
37
+ * list — writing ids there would freeze a snapshot omp would then serve
38
+ * alongside its own listing.
20
39
  */
21
40
 
22
- const FETCH_TIMEOUT_MS = 15_000;
23
- const ANTHROPIC_VERSION = '2023-06-01';
24
41
  const MASKED_KEY_PATTERN = /•{3,}/;
25
42
 
26
43
  const MOCK_REMOTE_MODELS: ProviderModel[] = [
@@ -51,187 +68,6 @@ const MOCK_REMOTE_MODELS: ProviderModel[] = [
51
68
  },
52
69
  ];
53
70
 
54
- interface FetchAttempt {
55
- models: ProviderModel[] | null;
56
- error: string;
57
- }
58
-
59
- async function attemptStyle(
60
- label: string,
61
- url: string,
62
- headers: Record<string, string>,
63
- ): Promise<FetchAttempt> {
64
- const response = await fetch(url, { headers, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
65
- let payload: unknown = null;
66
- try {
67
- payload = await response.json();
68
- } catch {
69
- // Non-JSON body (HTML error page, empty body) — payload stays null and is
70
- // reported below as an unrecognized response format.
71
- }
72
- if (response.status < 200 || response.status >= 300) {
73
- return { models: null, error: `${label}: HTTP ${response.status}` };
74
- }
75
- const models = extractModels(payload);
76
- if (!models) return { models: null, error: `${label}: unrecognized response format` };
77
- return { models, error: '' };
78
- }
79
-
80
- /**
81
- * Try the common listing styles in order: OpenAI-compatible Bearer auth,
82
- * Anthropic x-api-key, then Gemini ?key=. The first style that returns a
83
- * recognizable model list wins; otherwise the collected errors are reported.
84
- */
85
- async function fetchRemoteModels(baseUrl: string, apiKey?: string): Promise<FetchAttempt> {
86
- const base = baseUrl.replace(/\/+$/, '');
87
- const attempts: Array<{ label: string; url: string; headers: Record<string, string> }> = [
88
- {
89
- label: 'OpenAI-compatible',
90
- url: `${base}/models`,
91
- headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
92
- },
93
- ];
94
- if (apiKey) {
95
- attempts.push({
96
- label: 'Anthropic',
97
- url: `${base}/models`,
98
- headers: { 'x-api-key': apiKey, 'anthropic-version': ANTHROPIC_VERSION },
99
- });
100
- attempts.push({
101
- label: 'Gemini',
102
- url: `${base}/models?key=${encodeURIComponent(apiKey)}`,
103
- headers: {},
104
- });
105
- }
106
-
107
- const errors: string[] = [];
108
- for (const attempt of attempts) {
109
- try {
110
- const result = await attemptStyle(attempt.label, attempt.url, attempt.headers);
111
- if (result.models) return result;
112
- errors.push(result.error);
113
- } catch (error) {
114
- // Network-level failure (DNS, refused, timeout) applies to every auth
115
- // style — no point retrying the same host with different headers.
116
- const message = error instanceof Error ? error.message : 'request failed';
117
- errors.push(`${attempt.label}: ${message}`);
118
- break;
119
- }
120
- }
121
- return {
122
- models: null,
123
- error: errors.length > 0 ? `Failed to fetch models — ${errors.join(' · ')}` : 'Failed to fetch models',
124
- };
125
- }
126
-
127
- /**
128
- * omp's `api` for a custom provider, inferred from its endpoint. omp accepts
129
- * eleven `api` values and there is no way to ask a gateway which one it speaks,
130
- * so the chamber only claims the two dialects it can recognise by URL; every
131
- * other endpoint gets the OpenAI-compatible default, which is what the
132
- * model-listing probe already assumes. A gateway that speaks something else
133
- * (Azure, Bedrock, Vertex, …) needs its `api` set by hand in models.yml.
134
- */
135
- function inferProviderApi(baseUrl: string): 'openai-completions' | 'anthropic-messages' | 'google-generative-ai' {
136
- if (/anthropic\./i.test(baseUrl)) return 'anthropic-messages';
137
- if (/generativelanguage\.googleapis\.com/i.test(baseUrl)) return 'google-generative-ai';
138
- return 'openai-completions';
139
- }
140
-
141
- /**
142
- * omp's models.yml schema requires all four cost fields when cost is present,
143
- * so a price is only seeded when both sides are known; missing ones default
144
- * to 0. Context/output labels ("128K ctx · 16K out") parse back to tokens.
145
- * Cache prices come from the catalog and are kept when known — dropping them
146
- * makes every cached turn look free in omp's usage accounting.
147
- */
148
- function toOmpSeed(model: ProviderModel): {
149
- id: string;
150
- name?: string;
151
- reasoning?: boolean;
152
- imageInput?: boolean;
153
- contextWindow?: number;
154
- maxTokens?: number;
155
- cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
156
- } {
157
- const parseTokens = (label: string, suffix: string): number | undefined => {
158
- const match = label.match(new RegExp(`(\\d+(?:\\.\\d+)?)([KM])? ${suffix}`));
159
- if (!match) return undefined;
160
- const value = Number.parseFloat(match[1]);
161
- if (!Number.isFinite(value) || value <= 0) return undefined;
162
- const multiplier = match[2] === 'M' ? 1_000_000 : 1000;
163
- return Math.round(value * multiplier);
164
- };
165
- const contextWindow = parseTokens(model.contextWindow, 'ctx');
166
- const outTokens = parseTokens(model.contextWindow, 'out');
167
- // omp rejects the whole file on a non-finite number, so a price has to be a
168
- // real finite value before it may be written at all.
169
- const price = (value: unknown): number | undefined => (
170
- typeof value === 'number' && Number.isFinite(value) ? value : undefined
171
- );
172
- const priceInput = price(model.priceInput);
173
- const priceOutput = price(model.priceOutput);
174
- const hasCost = priceInput !== undefined && priceOutput !== undefined;
175
- return {
176
- id: model.id,
177
- ...(model.name && model.name !== model.id ? { name: model.name } : {}),
178
- ...(model.hasReasoning ? { reasoning: true } : {}),
179
- ...(model.hasVision ? { imageInput: true } : {}),
180
- ...(contextWindow ? { contextWindow } : {}),
181
- ...(outTokens && outTokens > 0 ? { maxTokens: Math.round(outTokens) } : {}),
182
- ...(hasCost ? {
183
- cost: {
184
- input: priceInput,
185
- output: priceOutput,
186
- cacheRead: price(model.priceCacheRead) ?? 0,
187
- cacheWrite: price(model.priceCacheWrite) ?? 0,
188
- },
189
- } : {}),
190
- };
191
- }
192
-
193
- /**
194
- * Fill in metadata the listing endpoint did not provide from the models.dev
195
- * catalog: vision/reasoning/tool flags, context + output window, and
196
- * per-1M-token pricing (cache rates included). Only empty fields are filled —
197
- * anything the provider reported itself is kept.
198
- */
199
- async function enrichFromCatalog(
200
- models: ProviderModel[],
201
- providerSlug: string | undefined,
202
- ): Promise<ProviderModel[]> {
203
- const catalog = await loadModelsDevCatalog();
204
- if (!catalog || Object.keys(catalog).length === 0) return models;
205
- return models.map((model) => {
206
- const match = findCatalogModel(catalog, providerSlug, model.id);
207
- if (!match) return model;
208
- const { info } = match;
209
- const ctx = info.limit?.context;
210
- const out = info.limit?.output;
211
- const ctxLabel = !model.contextWindow && ctx && ctx > 0
212
- ? Math.round(ctx / 1000)
213
- : null;
214
- const outLabel = out && out > 0 ? Math.round(out / 1000) : null;
215
- const catalogContext = ctxLabel
216
- ? (outLabel ? `${ctxLabel}K ctx · ${outLabel}K out` : `${ctxLabel}K ctx`)
217
- : '';
218
- return {
219
- ...model,
220
- name: model.name === model.id && info.name ? info.name : model.name,
221
- contextWindow: model.contextWindow || catalogContext,
222
- hasVision: model.hasVision || info.attachment === true,
223
- hasReasoning: model.hasReasoning ?? (info.reasoning === true || undefined),
224
- hasTools: model.hasTools || info.tool_call === true,
225
- priceInput: model.priceInput ?? (typeof info.cost?.input === 'number' ? info.cost.input : undefined),
226
- priceOutput: model.priceOutput ?? (typeof info.cost?.output === 'number' ? info.cost.output : undefined),
227
- priceCacheRead: model.priceCacheRead
228
- ?? (typeof info.cost?.cache_read === 'number' ? info.cost.cache_read : undefined),
229
- priceCacheWrite: model.priceCacheWrite
230
- ?? (typeof info.cost?.cache_write === 'number' ? info.cost.cache_write : undefined),
231
- };
232
- });
233
- }
234
-
235
71
  export async function action({ request }: ActionFunctionArgs) {
236
72
  if (request.method !== 'POST') {
237
73
  return json({ ok: false, error: 'Method not allowed' }, { status: 405 });
@@ -242,6 +78,10 @@ export async function action({ request }: ActionFunctionArgs) {
242
78
  apiKey?: unknown;
243
79
  providerSlug?: unknown;
244
80
  persistToOmp?: unknown;
81
+ api?: unknown;
82
+ auth?: unknown;
83
+ discovery?: unknown;
84
+ registerOnly?: unknown;
245
85
  };
246
86
  const baseUrl = typeof body.baseUrl === 'string' ? body.baseUrl.trim() : '';
247
87
  const rawApiKey = typeof body.apiKey === 'string' && body.apiKey.trim().length > 0 ? body.apiKey.trim() : undefined;
@@ -251,27 +91,50 @@ export async function action({ request }: ActionFunctionArgs) {
251
91
  ? body.providerSlug.trim()
252
92
  : undefined;
253
93
  const persistToOmp = body.persistToOmp === true;
94
+ const registerOnly = body.registerOnly === true;
95
+ // An unrecognized dialect from the client is dropped rather than written:
96
+ // omp disables EVERY custom provider in the file over one unknown `api`.
97
+ const api = isOmpProviderApi(body.api) ? body.api : undefined;
98
+ const auth = isProviderAuthMode(body.auth) ? body.auth : undefined;
99
+ const discovery = isProviderDiscoveryType(body.discovery) ? body.discovery : undefined;
254
100
  if (!baseUrl || !/^https?:\/\//i.test(baseUrl)) {
255
101
  return json({ ok: false, error: 'Base URL must be a valid http(s) endpoint' }, { status: 400 });
256
102
  }
103
+ // A keyless or discovery-only provider has no listing to fetch, so a
104
+ // register-only call is the whole operation — and it must not demand the
105
+ // model list that the caller deliberately did not ask for.
106
+ const skipListing = registerOnly || discovery !== undefined;
257
107
 
258
108
  if (isMockMode()) {
259
- return json({ ok: true, models: MOCK_REMOTE_MODELS });
109
+ return json({ ok: true, models: skipListing ? [] : MOCK_REMOTE_MODELS });
260
110
  }
261
111
 
262
- const result = await fetchRemoteModels(baseUrl, apiKey);
263
- if (!result.models) {
264
- return json({ ok: false, error: result.error }, { status: 502 });
112
+ let enriched: ProviderModel[] = [];
113
+ if (!skipListing) {
114
+ const result = await fetchRemoteModels(baseUrl, apiKey, api);
115
+ if (!result.models) {
116
+ return json({ ok: false, error: result.error }, { status: 502 });
117
+ }
118
+ enriched = await enrichFromCatalog(result.models, providerSlug);
265
119
  }
266
- const enriched = await enrichFromCatalog(result.models, providerSlug);
267
120
 
268
- let omp: { written: boolean; addedCount: number; backfilledCount: number; reason?: string } | undefined;
269
- if (persistToOmp && providerSlug && enriched.length > 0) {
121
+ let omp: {
122
+ written: boolean;
123
+ addedCount: number;
124
+ backfilledCount: number;
125
+ reason?: string;
126
+ } | undefined;
127
+ if (persistToOmp && providerSlug) {
270
128
  try {
271
129
  const upsert = await upsertOmpProviderModels(providerSlug, {
272
130
  baseUrl,
273
131
  apiKey,
274
- api: inferProviderApi(baseUrl),
132
+ api: api ?? inferProviderApi(baseUrl),
133
+ ...(auth ? { auth } : {}),
134
+ ...(discovery ? { discovery } : {}),
135
+ // An override/discovery provider keeps omp's own model list; writing
136
+ // ids here would freeze a snapshot omp would then serve twice.
137
+ overrideOnly: skipListing,
275
138
  models: enriched.map(toOmpSeed),
276
139
  });
277
140
  if (upsert.written) invalidateModelsCaches();
@@ -113,17 +113,32 @@ export async function action({ request, params }: ActionFunctionArgs) {
113
113
  if (!id) return json({ error: 'id is required' }, { status: 400 });
114
114
 
115
115
  const stored = await readStoredProviders();
116
- const target = stored.find((provider) => provider.id === id);
117
- const list = await providersStore.remove(db, id, { absent: DEFAULT_PROVIDERS_LIST, empty: DEFAULT_PROVIDERS_LIST });
116
+ // The target is looked up in the MERGED registry, not just the overlay.
117
+ // A provider that exists only in models.yml has no overlay row at all, so
118
+ // an overlay-only lookup found nothing, skipped the `removeOmpProvider`
119
+ // call below, and reported a delete that had removed no file entry — the
120
+ // provider came straight back on the next load.
121
+ const merged = await mergeProviders(stored).catch(() => null);
122
+ const target = merged?.providers.find((provider) => provider.id === id)
123
+ ?? stored.find((provider) => provider.id === id);
124
+ // The fallback list is MOCK-only demo data: seeding it in real mode would
125
+ // write shipped sample providers into a user's overlay on the first
126
+ // delete. Real mode starts from an empty overlay, which is what
127
+ // `providersStore.read` already does for the loader.
128
+ const fallback = isMockMode() ? DEFAULT_PROVIDERS_LIST : [];
129
+ const list = await providersStore.remove(db, id, { absent: fallback, empty: fallback });
118
130
 
119
131
  // Removing the overlay row alone left the provider registered in
120
132
  // models.yml, so omp kept serving it and the next load merged it straight
121
133
  // back into this list — a delete that visibly did nothing. Unregister it
122
134
  // from the agent's own registry too.
123
135
  let removedFromOmp = false;
136
+ let modelsRemoved = 0;
124
137
  if (target?.slug && !isMockMode()) {
125
138
  try {
126
- removedFromOmp = (await removeOmpProvider(target.slug)).removed;
139
+ const removal = await removeOmpProvider(target.slug);
140
+ removedFromOmp = removal.removed;
141
+ modelsRemoved = removal.removedModels;
127
142
  // A provider omp still holds in disabledProviders would linger as a
128
143
  // "disconnected" ghost entry after its models are gone.
129
144
  await enableNativeProvider(target.slug);
@@ -131,18 +146,18 @@ export async function action({ request, params }: ActionFunctionArgs) {
131
146
  // The overlay row is already gone; report the native failure instead
132
147
  // of failing the whole request, so the UI can tell the user the entry
133
148
  // is still in models.yml.
134
- const merged = await mergeProviders(list).catch(() => null);
149
+ const afterFailure = await mergeProviders(list).catch(() => null);
135
150
  return json({
136
151
  success: true,
137
- providers: merged?.providers ?? list,
152
+ providers: afterFailure?.providers ?? list,
138
153
  removedFromOmp: false,
139
154
  warning: error instanceof Error ? error.message : 'models.yml cleanup failed',
140
155
  });
141
156
  }
142
157
  }
143
158
  invalidateModelsCaches();
144
- const merged = await mergeProviders(list);
145
- return json({ success: true, providers: merged.providers, removedFromOmp });
159
+ const after = await mergeProviders(list);
160
+ return json({ success: true, providers: after.providers, removedFromOmp, modelsRemoved });
146
161
  }
147
162
 
148
163
  if (request.method === 'POST' || request.method === 'PUT') {
@@ -2,56 +2,137 @@ import { json } from '@/server/lib/remix-compat';
2
2
  import type { LoaderFunctionArgs } from '@/server/lib/remix-compat';
3
3
  import { methodNotAllowed } from '@/server/lib/route-adapter';
4
4
  import { isMockMode } from '@/server/mock.server';
5
- import { buildKenariReport } from '@/server/lib/usage/kenari';
6
- import { buildDeepSeekReport } from '@/server/lib/usage/deepseek';
7
- import type { UsageReport } from '@/shared/types';
5
+ import { buildUsageProviders } from '@/server/lib/usage/providers.server';
6
+ import type { UsageProviderSummary, UsageReport } from '@/shared/types';
8
7
 
9
8
  const NO_STORE_HEADERS = { 'Cache-Control': 'no-store' };
10
9
 
11
- /** Deterministic demo report so the Usage panel renders under MOCK=true. */
10
+ /** Deterministic demo report so the Usage surfaces render under MOCK=true. */
12
11
  function mockUsageReport(): UsageReport {
13
- return {
14
- isMock: true,
15
- generatedAt: new Date().toISOString(),
16
- kenari: {
17
- configured: true,
18
- balance: { amountRp: 25_000, raw: 'Saldo: Rp 25.000' },
19
- usage: {
20
- rows: [
21
- { model: 'gemini-3-7-flash', requests: 37, inputTokens: 22255, outputTokens: 11487, costRp: 0 },
22
- { model: 'glm-5-3-flash', requests: 2122, inputTokens: 369435677, outputTokens: 914679, costRp: 0 },
23
- ],
24
- totalRequests: 5941,
25
- totalCostRp: 0,
26
- raw: 'Pemakaian 30 hari terakhir (biaya Rupiah).',
27
- },
28
- quota: {
29
- planName: 'Pro',
30
- windows: [
31
- { key: 'five_hour', label: '5 hours', usedRp: 0, remainingRp: 50_000, resetsAt: '2026-09-14T10:00:00.000Z' },
32
- { key: 'week', label: 'Weekly', usedRp: 12_500, remainingRp: 287_500, resetsAt: '2026-09-20T00:00:00.000Z' },
33
- { key: 'month', label: 'Monthly', usedRp: 120_000, remainingRp: 880_000, resetsAt: '2026-10-01T00:00:00.000Z' },
34
- ],
35
- coupon: null,
12
+ const providers: UsageProviderSummary[] = [
13
+ {
14
+ id: 'kenari',
15
+ name: 'Kenari.id',
16
+ credentialSources: ['models.yml'],
17
+ // kenari is a plain OpenAI-compatible gateway: omp ships no usage adapter.
18
+ tracked: false,
19
+ limits: [],
20
+ kenari: {
21
+ balance: { amountRp: 25_000, raw: 'Saldo: Rp 25.000' },
22
+ usage: {
23
+ rows: [
24
+ { model: 'gemini-3-7-flash', requests: 37, inputTokens: 22255, outputTokens: 11487, costRp: 0 },
25
+ { model: 'glm-5-3-flash', requests: 2122, inputTokens: 369435677, outputTokens: 914679, costRp: 0 },
26
+ ],
27
+ totalRequests: 5941,
28
+ totalCostRp: 0,
29
+ raw: 'Pemakaian 30 hari terakhir (biaya Rupiah).',
30
+ },
31
+ quota: {
32
+ planName: 'Pro',
33
+ windows: [
34
+ { key: 'five_hour', label: '5 hours', usedRp: 0, remainingRp: 50_000, resetsAt: '2026-09-14T10:00:00.000Z' },
35
+ { key: 'week', label: 'Weekly', usedRp: 12_500, remainingRp: 287_500, resetsAt: '2026-09-20T00:00:00.000Z' },
36
+ { key: 'month', label: 'Monthly', usedRp: 120_000, remainingRp: 880_000, resetsAt: '2026-10-01T00:00:00.000Z' },
37
+ ],
38
+ coupon: null,
39
+ },
36
40
  },
37
41
  },
38
- deepseek: {
39
- configured: true,
40
- balance: {
41
- isAvailable: true,
42
- entries: [
43
- { currency: 'USD', totalBalance: '10.00', grantedBalance: '10.00', toppedUpBalance: '0.00' },
44
- ],
42
+ {
43
+ id: 'deepseek',
44
+ name: 'DeepSeek',
45
+ credentialSources: ['agent.db'],
46
+ tracked: false,
47
+ limits: [],
48
+ deepseek: {
49
+ balance: {
50
+ isAvailable: true,
51
+ entries: [
52
+ { currency: 'USD', totalBalance: '10.00', grantedBalance: '10.00', toppedUpBalance: '0.00' },
53
+ ],
54
+ },
45
55
  },
46
56
  },
47
- };
57
+ {
58
+ id: 'anthropic',
59
+ name: 'Anthropic (Claude Pro/Max)',
60
+ credentialSources: ['agent.db'],
61
+ tracked: true,
62
+ limits: [
63
+ {
64
+ id: 'anthropic:5h',
65
+ label: 'Claude 5 hour limit',
66
+ windowLabel: '5h',
67
+ used: 42,
68
+ limit: 100,
69
+ usedFraction: 0.42,
70
+ unit: 'percent',
71
+ status: 'ok',
72
+ resetsAt: Date.now() + 3 * 60 * 60 * 1000,
73
+ },
74
+ {
75
+ id: 'anthropic:7d',
76
+ label: 'Claude weekly limit',
77
+ windowLabel: '7d',
78
+ used: 88,
79
+ limit: 100,
80
+ usedFraction: 0.88,
81
+ unit: 'percent',
82
+ status: 'warning',
83
+ resetsAt: Date.now() + 4 * 24 * 60 * 60 * 1000,
84
+ },
85
+ {
86
+ id: 'anthropic:fast',
87
+ label: 'Fast requests',
88
+ windowLabel: '24h',
89
+ used: 320,
90
+ limit: 500,
91
+ usedFraction: 0.64,
92
+ unit: 'requests',
93
+ status: 'ok',
94
+ resetsAt: Date.now() + 6 * 60 * 60 * 1000,
95
+ },
96
+ ],
97
+ },
98
+ {
99
+ // A pay-as-you-go vendor: no omp adapter, only a credit balance.
100
+ id: 'openrouter',
101
+ name: 'OpenRouter',
102
+ credentialSources: ['agent.db'],
103
+ tracked: false,
104
+ limits: [
105
+ {
106
+ id: 'openrouter:credits',
107
+ label: 'Key credits',
108
+ used: 25.5,
109
+ limit: 100,
110
+ remaining: 74.5,
111
+ usedFraction: 0.255,
112
+ unit: 'usd',
113
+ status: 'ok',
114
+ },
115
+ {
116
+ id: 'openrouter:daily',
117
+ label: 'Spent today (monthly)',
118
+ used: 1.82,
119
+ unit: 'usd',
120
+ status: 'unknown',
121
+ },
122
+ ],
123
+ },
124
+ ];
125
+
126
+ return { isMock: true, generatedAt: new Date().toISOString(), providers };
48
127
  }
49
128
 
50
129
  /**
51
- * GET /api/settings/usage — balance / usage / quota for the configured
52
- * providers. Both provider reports are always present; a missing key is
53
- * `configured: false`, an upstream failure is a per-provider `error` (HTTP
54
- * 200), and only a genuinely unexpected internal failure returns 500.
130
+ * GET /api/settings/usage — provider quota and balance.
131
+ *
132
+ * Only providers with a detected credential appear in `providers`; a provider
133
+ * whose key is missing is omitted rather than rendered as an empty row. A
134
+ * provider-side failure is a per-provider `error` (HTTP 200), and only a
135
+ * genuinely unexpected internal failure returns 500.
55
136
  */
56
137
  export async function loader({ request, params }: LoaderFunctionArgs) {
57
138
  if (request.method !== 'GET') {
@@ -61,15 +142,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
61
142
  return json(mockUsageReport(), { headers: NO_STORE_HEADERS });
62
143
  }
63
144
  try {
64
- const [kenari, deepseek] = await Promise.all([
65
- buildKenariReport(),
66
- buildDeepSeekReport(),
67
- ]);
145
+ // `?refresh=1` is the user's own Refresh button: provider quota is cached
146
+ // for a minute (each read calls every provider's endpoint), but a manual
147
+ // refresh must show fresh numbers rather than that snapshot.
148
+ const force = new URL(request.url).searchParams.get('refresh') === '1';
68
149
  const report: UsageReport = {
69
150
  isMock: false,
70
151
  generatedAt: new Date().toISOString(),
71
- kenari,
72
- deepseek,
152
+ providers: await buildUsageProviders({ force }),
73
153
  };
74
154
  return json(report, { headers: NO_STORE_HEADERS });
75
155
  } catch (error) {
@@ -4,7 +4,8 @@ import { methodNotAllowed } from '@/server/lib/route-adapter';
4
4
  import { BREAKDOWN_DATA, MOCK_CHART_SERIES, MOCK_CUSTOM_RANGE_DATA, MOCK_RANGE_DATA, TIME_RANGES } from '@/client/data/mock/token-usage';
5
5
  import { isMockMode } from '@/server/mock.server';
6
6
  import { loadModelsDevCatalog } from '@/shared/lib/models/catalog';
7
- import { aggregateUsage, buildChartSeries, toBreakdownRows, toMetricSet, type UsageWindow } from '@/server/lib/omp/session/usage';
7
+ import { aggregateUsage, type UsageWindow } from '@/server/lib/omp/session/usage/aggregate';
8
+ import { buildChartSeries, toBreakdownRows, toMetricSet } from '@/server/lib/omp/session/usage/shaping';
8
9
  import type { BreakdownRow, BreakdownTab, CadenceType, ChartSeriesPoint, TimeRangeType, TokenUsageMetricSet } from '@/shared/types';
9
10
 
10
11
  const REAL_ZERO_METRIC: TokenUsageMetricSet = {
@@ -1,3 +1,20 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Renderer-side parser for one `todo` tool call: reads the human-readable
8
+ * checklist omp returns as the tool's text output (and the `list` argument as a
9
+ * fallback) into phase groups for the chat timeline's tool card.
10
+ *
11
+ * This is NOT the authoritative list — a tool card shows what that ONE call
12
+ * did, while the session's live list is the deepest committed snapshot on the
13
+ * active branch (`./snapshot.ts`, surfaced by the right-panel Todo view). The
14
+ * two must not be merged: a card that displayed the session-wide list would
15
+ * attribute every other call's work to this one.
16
+ */
17
+
1
18
  import type { ToolCallData } from '@/shared/types';
2
19
 
3
20
  export interface ParsedTask {