ompchamber 2.0.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/bunfig.toml +11 -0
- package/dist/client/index.html +16 -5
- package/dist/client/index.js +1393 -0
- package/dist/client/static/js/index-prbmx3yj.js +5123 -0
- package/dist/client/static/js/index-ranztes9.css +1 -0
- package/index.html +54 -0
- package/package.json +9 -10
- package/public/apple-touch-icon.png +0 -0
- package/public/icon-192x192.png +0 -0
- package/public/icon.svg +60 -0
- package/public/manifest.webmanifest +38 -0
- package/src/cli/lib/commands/serve.js +9 -5
- package/src/cli/lib/runtime.js +18 -7
- package/src/client/components/common/provider-icon/glyphs.ts +118 -0
- package/src/client/components/common/provider-icon/index.tsx +85 -0
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
- package/src/client/components/layout/desktop-layout/index.tsx +2 -0
- package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
- package/src/client/components/mobile/SessionSidebar.tsx +10 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +0 -1
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +16 -1
- package/src/client/components/mobile/mobile-session-sidebar/List.tsx +3 -0
- package/src/client/components/settings/categories/ProviderSettings.tsx +45 -4
- package/src/client/components/settings/categories/UsageSettings.tsx +22 -7
- package/src/client/components/settings/categories/provider-settings/AddModelModal.tsx +322 -0
- package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +152 -56
- package/src/client/components/settings/categories/provider-settings/AuthSection.tsx +52 -1
- package/src/client/components/settings/categories/provider-settings/DeleteProviderModal.tsx +133 -0
- package/src/client/components/settings/categories/provider-settings/DialectFields.tsx +136 -0
- package/src/client/components/settings/categories/provider-settings/EffortLadder.tsx +89 -0
- package/src/client/components/settings/categories/provider-settings/Header.tsx +2 -2
- package/src/client/components/settings/categories/provider-settings/LoginModal.tsx +2 -2
- package/src/client/components/settings/categories/provider-settings/ModelsList.tsx +22 -2
- package/src/client/components/settings/categories/provider-settings/PresetPicker.tsx +82 -0
- package/src/client/components/settings/categories/provider-settings/PriceFields.tsx +107 -0
- package/src/client/components/settings/categories/provider-settings/ReconnectModal.tsx +43 -22
- package/src/client/components/settings/categories/provider-settings/SidebarList.tsx +2 -2
- package/src/client/components/settings/categories/usage-settings/LimitsSection.tsx +115 -0
- package/src/client/components/settings/categories/usage-settings/ProviderCard.tsx +19 -26
- package/src/client/components/settings/categories/usage-settings/ProviderDetail.tsx +29 -22
- package/src/client/components/settings/categories/usage-settings/SidebarList.tsx +60 -48
- package/src/client/components/settings/categories/usage-settings/format.ts +36 -0
- package/src/client/components/settings/categories/usage-settings/providers.ts +52 -28
- package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +1 -1
- package/src/client/components/workspace/diff-panel/Toolbar.tsx +71 -38
- package/src/client/components/workspace/diff-panel/index.tsx +139 -53
- package/src/client/components/workspace/editor/Tabs.tsx +2 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +111 -26
- package/src/client/components/workspace/editor/index.tsx +27 -9
- package/src/client/components/workspace/model-dropdown/Item.tsx +13 -23
- package/src/client/components/workspace/model-dropdown/Panel.tsx +1 -1
- package/src/client/components/workspace/model-dropdown/Section.tsx +13 -4
- package/src/client/components/workspace/model-dropdown/index.tsx +7 -3
- package/src/client/components/workspace/usage-panel/ProviderSelect.tsx +10 -5
- package/src/client/components/workspace/usage-panel/index.tsx +36 -18
- package/src/client/data/samples/dialogue-session.ts +1 -1
- package/src/client/data/samples/sample-tools/other.ts +1 -1
- package/src/client/data/settings/provider.ts +141 -9
- package/src/client/hooks/editor/use-file-editor.ts +24 -27
- package/src/client/hooks/models/use-provider-names.ts +1 -1
- package/src/client/hooks/settings/providers/actions.ts +337 -0
- package/src/client/hooks/settings/providers/api.ts +69 -0
- package/src/client/hooks/settings/providers/index.ts +138 -0
- package/src/client/hooks/settings/providers/messages.ts +77 -0
- package/src/client/hooks/settings/useUsageReport.ts +8 -3
- package/src/client/hooks/ui/theme.ts +3 -3
- package/src/client/hooks/workspace/file-tabs.ts +62 -11
- package/src/client/stubs/elkjs.ts +6 -1
- package/src/server/index.ts +20 -2
- package/src/server/lib/assets/font-css.server.ts +86 -0
- package/src/server/lib/assets/fonts.server.ts +117 -0
- package/src/server/lib/bundler/css.ts +116 -0
- package/src/server/lib/lifecycle/shell-route.ts +17 -0
- package/src/server/lib/models/provider-listing.server.ts +257 -0
- package/src/server/lib/models/provider-registry.server.test.ts +122 -0
- package/src/server/lib/models/provider-registry.server.ts +76 -25
- package/src/server/lib/omp/config/models-config.ts +11 -0
- package/src/server/lib/omp/config/models-validation.test.ts +137 -0
- package/src/server/lib/omp/config/models-validation.ts +111 -2
- package/src/server/lib/omp/config/provider-seeds.test.ts +167 -0
- package/src/server/lib/omp/config/provider-seeds.ts +176 -0
- package/src/server/lib/omp/config/providers.ts +94 -100
- package/src/server/lib/omp/core/auth-credentials.ts +45 -0
- package/src/server/lib/omp/session/{usage.ts → usage/aggregate.ts} +1 -118
- package/src/server/lib/omp/session/usage/shaping.ts +130 -0
- package/src/server/lib/usage/deepseek.ts +4 -5
- package/src/server/lib/usage/kenari.ts +7 -6
- package/src/server/lib/usage/omp-usage.server.test.ts +126 -0
- package/src/server/lib/usage/omp-usage.server.ts +194 -0
- package/src/server/lib/usage/provider-key.ts +81 -3
- package/src/server/lib/usage/providers.server.test.ts +47 -0
- package/src/server/lib/usage/providers.server.ts +208 -0
- package/src/server/lib/usage/vendor-balance.server.test.ts +104 -0
- package/src/server/lib/usage/vendor-balance.server.ts +284 -0
- package/src/server/plugins/shell.server.ts +56 -0
- package/src/server/plugins/ssr.ts +24 -36
- package/src/server/plugins/static.test.ts +26 -29
- package/src/server/plugins/static.ts +40 -19
- package/src/server/routes/settings/index.ts +2 -0
- package/src/server/routes/settings/provider-model.ts +107 -0
- package/src/server/routes/settings/provider-models.ts +65 -202
- package/src/server/routes/settings/providers.ts +22 -7
- package/src/server/routes/settings/usage.ts +125 -45
- package/src/server/routes/telemetry/tokens.ts +2 -1
- package/src/shared/lib/fs/diff-parser.test.ts +116 -0
- package/src/shared/lib/fs/diff-parser.ts +86 -33
- package/src/shared/lib/models/{provider-connection.ts → provider/connection.ts} +37 -0
- package/src/shared/lib/models/provider/dialect.ts +179 -0
- package/src/shared/lib/models/provider/glyph.test.ts +116 -0
- package/src/shared/lib/models/provider/glyph.ts +200 -0
- package/src/shared/lib/models/{provider-models.ts → provider/models.ts} +91 -6
- package/src/shared/lib/models/provider/presets.test.ts +172 -0
- package/src/shared/lib/models/provider/presets.ts +83 -0
- package/src/shared/lib/workspace/file-tab-id.ts +29 -0
- package/src/shared/types/settings/provider.ts +81 -0
- package/src/shared/types/settings/usage.ts +88 -14
- package/tsconfig.json +3 -1
- package/dist/client/static/css/index.5f630421b7.css +0 -2
- package/dist/client/static/font/KaTeX_AMS-Regular.a79f1c3119.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Bold.ec17d13264.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Regular.55fac25845.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Bold.d42a5579b0.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Regular.d3c882a649.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Bold.c3fb5ac22f.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-BoldItalic.6f2bb1dff2.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Italic.8916142bec.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Main-Regular.0462f03bdf.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Math-BoldItalic.572d331f69.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Math-Italic.f28c23acad.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Bold.8c5b5494b6.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Italic.3b1e59b3ba.woff2 +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Regular.ba21ed5f84.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Script-Regular.03e9641d6f.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size1-Regular.eae34984b3.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size2-Regular.5916a24fa3.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Size4-Regular.10d95fd3a2.woff2 +0 -0
- package/dist/client/static/font/KaTeX_Typewriter-Regular.a8709e3622.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-400-normal.2655b10c55.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-500-normal.92a991f204.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-600-normal.43788eb7f1.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-700-normal.2667215d33.woff2 +0 -0
- package/dist/client/static/js/77.0d81c74cf0.js +0 -139
- package/dist/client/static/js/77.0d81c74cf0.js.LICENSE.txt +0 -8
- package/dist/client/static/js/async/0.8e59f2eb5b.js +0 -1
- package/dist/client/static/js/async/0k.5852e6b4fa.js +0 -1
- package/dist/client/static/js/async/0w.9ed36d5860.js +0 -1
- package/dist/client/static/js/async/1.f387c12b63.js +0 -1
- package/dist/client/static/js/async/12.d091f91c99.js +0 -1
- package/dist/client/static/js/async/13.8f810a8a6c.js +0 -1
- package/dist/client/static/js/async/19.6fe909d55a.js +0 -160
- package/dist/client/static/js/async/1b.23f1141a38.js +0 -1
- package/dist/client/static/js/async/1k.973e8e21fc.js +0 -2
- package/dist/client/static/js/async/1k.973e8e21fc.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/1p.0d5cddda96.js +0 -1
- package/dist/client/static/js/async/2.b589f1733b.js +0 -1
- package/dist/client/static/js/async/2v.96165101de.js +0 -1
- package/dist/client/static/js/async/3.0cacc08423.js +0 -1
- package/dist/client/static/js/async/37.b28b2f61cd.js +0 -1
- package/dist/client/static/js/async/3d.5085fb913f.js +0 -1
- package/dist/client/static/js/async/3j.357c80ff38.js +0 -2
- package/dist/client/static/js/async/3j.357c80ff38.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/3p.bcf32a0983.js +0 -1
- package/dist/client/static/js/async/3r.3a69e98ee0.js +0 -64
- package/dist/client/static/js/async/3u.1228460e2a.js +0 -1
- package/dist/client/static/js/async/3y.3530b824ad.js +0 -198
- package/dist/client/static/js/async/3y.3530b824ad.js.LICENSE.txt +0 -5
- package/dist/client/static/js/async/4.9442eeb472.js +0 -1
- package/dist/client/static/js/async/4e.678285ca55.js +0 -1
- package/dist/client/static/js/async/4k.33a0c7eeb4.js +0 -1
- package/dist/client/static/js/async/4m.928a1dde85.js +0 -31
- package/dist/client/static/js/async/4m.928a1dde85.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/4q.aad20dab25.js +0 -1
- package/dist/client/static/js/async/5.ebaf8b89cc.js +0 -36
- package/dist/client/static/js/async/51.95bcae98af.js +0 -2
- package/dist/client/static/js/async/51.95bcae98af.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
- package/dist/client/static/js/async/5j.d6407c4248.js +0 -287
- package/dist/client/static/js/async/5o.88316d4460.js +0 -62
- package/dist/client/static/js/async/6.f034c9b528.js +0 -1
- package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
- package/dist/client/static/js/async/6o.5b1a06a382.js +0 -1
- package/dist/client/static/js/async/7.e4a9288dd0.js +0 -1
- package/dist/client/static/js/async/73.308dff4da8.js +0 -1
- package/dist/client/static/js/async/7e.a1cc9bfd88.js +0 -1
- package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
- package/dist/client/static/js/async/7l.fadbbc0583.js +0 -1
- package/dist/client/static/js/async/8.5067439d8c.js +0 -1
- package/dist/client/static/js/async/8j.ea110663fb.js +0 -1
- package/dist/client/static/js/async/a.bce4542392.js +0 -1
- package/dist/client/static/js/async/aa.65d3852576.js +0 -1
- package/dist/client/static/js/async/ar.9303e5f2e8.js +0 -2
- package/dist/client/static/js/async/ar.9303e5f2e8.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/az.d656062b0f.js +0 -1
- package/dist/client/static/js/async/b.b4f6f06515.js +0 -1
- package/dist/client/static/js/async/bi.09688ba75c.js +0 -1
- package/dist/client/static/js/async/bq.586bc738d1.js +0 -1
- package/dist/client/static/js/async/c.797d933882.js +0 -1
- package/dist/client/static/js/async/c0.0af1e640d3.js +0 -24
- package/dist/client/static/js/async/ca.8a52cfd447.js +0 -1
- package/dist/client/static/js/async/cf.d34fff5d67.js +0 -1
- package/dist/client/static/js/async/ct.b74928f7a3.js +0 -1
- package/dist/client/static/js/async/cx.3cb0242a3a.js +0 -226
- package/dist/client/static/js/async/d.a5fc8fe1b3.js +0 -1
- package/dist/client/static/js/async/da.f62c70f6c8.js +0 -1
- package/dist/client/static/js/async/dp.1725d6432c.js +0 -1
- package/dist/client/static/js/async/e.93214d622b.js +0 -79
- package/dist/client/static/js/async/f.ab50467719.js +0 -19
- package/dist/client/static/js/async/f.ab50467719.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/f7.46971e9d6d.js +0 -2
- package/dist/client/static/js/async/f7.46971e9d6d.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/f8.bbf0dbae70.js +0 -1
- package/dist/client/static/js/async/g.cb81980498.js +0 -2
- package/dist/client/static/js/async/g.cb81980498.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/h.4e790471e4.js +0 -27
- package/dist/client/static/js/async/h1.393f066d09.js +0 -1
- package/dist/client/static/js/async/h3.216e9798ab.js +0 -148
- package/dist/client/static/js/async/hd.0da931b622.js +0 -1
- package/dist/client/static/js/async/hk.ba7fb04d41.js +0 -1
- package/dist/client/static/js/async/hn.b645041e00.js +0 -1
- package/dist/client/static/js/async/ho.475a5e1ae4.js +0 -1
- package/dist/client/static/js/async/hp.e9fe2e395a.js +0 -2
- package/dist/client/static/js/async/hp.e9fe2e395a.js.LICENSE.txt +0 -17
- package/dist/client/static/js/async/hs.e7c59d8090.js +0 -41
- package/dist/client/static/js/async/i.7c148e8440.js +0 -1
- package/dist/client/static/js/async/id.573df20f29.js +0 -1
- package/dist/client/static/js/async/j.ab925882d8.js +0 -1
- package/dist/client/static/js/async/jd.63886a1f09.js +0 -1
- package/dist/client/static/js/async/jg.d86ebd4013.js +0 -86
- package/dist/client/static/js/async/jl.874d352278.js +0 -1
- package/dist/client/static/js/async/jll.11313b455a.js +0 -1
- package/dist/client/static/js/async/js.67182759ef.js +0 -1
- package/dist/client/static/js/async/jsc.23b70d6f40.js +0 -1
- package/dist/client/static/js/async/jse.334588ff5d.js +0 -1
- package/dist/client/static/js/async/jz.72eb929106.js +0 -77
- package/dist/client/static/js/async/k.3210286401.js +0 -217
- package/dist/client/static/js/async/ka.c1990ecaa3.js +0 -115
- package/dist/client/static/js/async/katex-vendor.de6a87c308.js +0 -1
- package/dist/client/static/js/async/ku.29c8a59d0c.js +0 -1
- package/dist/client/static/js/async/ky.8dab1dade0.js +0 -1
- package/dist/client/static/js/async/l.12dd34e1be.js +0 -1
- package/dist/client/static/js/async/l9.949710adaf.js +0 -83
- package/dist/client/static/js/async/l9.949710adaf.js.LICENSE.txt +0 -13
- package/dist/client/static/js/async/ls.9d9a01026c.js +0 -104
- package/dist/client/static/js/async/m.eb2e6752e2.js +0 -28
- package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/m3.d2b05f9604.js +0 -2
- package/dist/client/static/js/async/m3.d2b05f9604.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/ma.2c261b04dd.js +0 -2
- package/dist/client/static/js/async/ma.2c261b04dd.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/me.8ace414f80.js +0 -23
- package/dist/client/static/js/async/ms.98960af4eb.js +0 -67
- package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
- package/dist/client/static/js/async/n.a9f1c51701.js +0 -1
- package/dist/client/static/js/async/na.a36cd532d6.js +0 -1
- package/dist/client/static/js/async/nk.fc7e58325e.js +0 -1
- package/dist/client/static/js/async/o.36353dfb88.js +0 -1
- package/dist/client/static/js/async/oc.b73042e8bd.js +0 -8
- package/dist/client/static/js/async/og.8eb577893a.js +0 -193
- package/dist/client/static/js/async/p.48ecfcd14e.js +0 -1
- package/dist/client/static/js/async/px.fc6877924b.js +0 -1
- package/dist/client/static/js/async/q.22588845a9.js +0 -21
- package/dist/client/static/js/async/q.22588845a9.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/q5.97dc1481b6.js +0 -286
- package/dist/client/static/js/async/qs.2a9ddb3c72.js +0 -1
- package/dist/client/static/js/async/r.51010ad87c.js +0 -1
- package/dist/client/static/js/async/rd.cb6bf495bb.js +0 -2
- package/dist/client/static/js/async/rd.cb6bf495bb.js.LICENSE.txt +0 -4
- package/dist/client/static/js/async/s.0f395cdda5.js +0 -94
- package/dist/client/static/js/async/t.4dca0e2ba8.js +0 -1
- package/dist/client/static/js/async/t9.d44f57d518.js +0 -1
- package/dist/client/static/js/async/ta.973534bbec.js +0 -1
- package/dist/client/static/js/async/tr.e878658140.js +0 -309
- package/dist/client/static/js/async/u.0871ac5b22.js +0 -1
- package/dist/client/static/js/async/ud.3065bfee31.js +0 -2
- package/dist/client/static/js/async/ud.3065bfee31.js.LICENSE.txt +0 -4
- package/dist/client/static/js/async/up.ea990da0ff.js +0 -32
- package/dist/client/static/js/async/uw.8890abbabc.js +0 -1
- package/dist/client/static/js/async/v.7d28d3c241.js +0 -34
- package/dist/client/static/js/async/vp.9b13bf8610.js +0 -1
- package/dist/client/static/js/async/vt.42416b707d.js +0 -1
- package/dist/client/static/js/async/w.08ac210f60.js +0 -1
- package/dist/client/static/js/async/wt.ad92393a90.js +0 -1
- package/dist/client/static/js/async/x.563f72ca6b.js +0 -1
- package/dist/client/static/js/async/x0.475516418f.js +0 -1
- package/dist/client/static/js/async/x1.da8687159b.js +0 -90
- package/dist/client/static/js/async/xe.da8dcc2015.js +0 -2
- package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/xl.a3c562a58c.js +0 -1
- package/dist/client/static/js/async/xn.6510c0abe2.js +0 -2
- package/dist/client/static/js/async/xn.6510c0abe2.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/xn1.83a7784fe1.js +0 -10
- package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +0 -11
- package/dist/client/static/js/async/xnl.feb0021e50.js +0 -2
- package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +0 -12
- package/dist/client/static/js/async/y.1a9ed7a5d8.js +0 -1
- package/dist/client/static/js/async/y6.43c57defe4.js +0 -1
- package/dist/client/static/js/async/yb.8c1342401d.js +0 -1
- package/dist/client/static/js/async/ybq.4491f2f2dc.js +0 -1
- package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
- package/dist/client/static/js/async/yq.917ddd7ba2.js +0 -1
- package/dist/client/static/js/async/ys.00687cf82d.js +0 -38
- package/dist/client/static/js/async/yv.d99d829136.js +0 -1
- package/dist/client/static/js/async/z.56f61a4374.js +0 -1
- package/dist/client/static/js/async/zi.5fdb07b86a.js +0 -24
- package/dist/client/static/js/async/zj.654f37e8ff.js +0 -1
- package/dist/client/static/js/async/zn.966d6f1cc6.js +0 -1
- package/dist/client/static/js/async/zo.26213b40c0.js +0 -1
- package/dist/client/static/js/async/zx.e9f4105e00.js +0 -1
- package/dist/client/static/js/index.e6a257402a.js +0 -38
- package/dist/client/static/js/index.e6a257402a.js.LICENSE.txt +0 -4
- package/dist/client/static/js/markdown-vendor.71e43278b3.js +0 -144
- package/dist/client/static/js/shiki-vendor.cb8452ddcd.js +0 -1
- package/src/client/components/settings/categories/provider-settings/Icons.tsx +0 -35
- package/src/client/hooks/settings/providers.ts +0 -326
- package/src/server/plugins/dev-assets.ts +0 -49
- package/src/shared/lib/code/editor-utils.ts +0 -16
- package/src/shared/lib/models/provider-presets.ts +0 -29
- /package/dist/client/{apple-touch-icon.png → static/apple-touch-icon-dskx5sr3.png} +0 -0
- /package/dist/client/{icon-192x192.png → static/icon-192x192-6gn5cr2f.png} +0 -0
- /package/dist/client/{icon.svg → static/icon-d07z9dtz.svg} +0 -0
- /package/dist/client/{manifest.webmanifest → static/manifest-bnyy75kt.webmanifest} +0 -0
- /package/{dist/client → public}/icon-512x512.png +0 -0
- /package/{dist/client → public}/icon-maskable-512x512.png +0 -0
- /package/{dist/client → public}/sw.js +0 -0
- /package/src/shared/lib/models/{provider-cleanup.ts → provider/cleanup.ts} +0 -0
- /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
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* models.yml
|
|
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
|
-
|
|
263
|
-
if (!
|
|
264
|
-
|
|
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: {
|
|
269
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
|
149
|
+
const afterFailure = await mergeProviders(list).catch(() => null);
|
|
135
150
|
return json({
|
|
136
151
|
success: true,
|
|
137
|
-
providers:
|
|
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
|
|
145
|
-
return json({ success: true, providers:
|
|
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 {
|
|
6
|
-
import {
|
|
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
|
|
10
|
+
/** Deterministic demo report so the Usage surfaces render under MOCK=true. */
|
|
12
11
|
function mockUsageReport(): UsageReport {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 —
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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,
|
|
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 = {
|