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,42 +1,26 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
|
-
import { join } from 'path';
|
|
3
2
|
import { getDb } from '@/server/db.server';
|
|
4
3
|
import { tryServeStatic } from '@/server/plugins/static';
|
|
5
|
-
import {
|
|
4
|
+
import { renderShell } from '@/server/plugins/shell.server';
|
|
5
|
+
import { FONT_STYLESHEET_ROUTE } from '@/server/lib/assets/font-css.server';
|
|
6
6
|
import { resolveTheme } from '@/shared/lib/theme/catalog';
|
|
7
7
|
import { THEME_STYLE_ELEMENT_ID, themeStyleSheet } from '@/shared/lib/theme/css';
|
|
8
8
|
|
|
9
|
-
const CLIENT_INDEX = join(process.cwd(), 'dist/client/index.html');
|
|
10
|
-
|
|
11
|
-
let cachedTemplate: string | null = null;
|
|
12
|
-
let cachedMtimeMs = 0;
|
|
13
|
-
|
|
14
9
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* Actionable message instead of a bare 500 when the shell cannot be rendered.
|
|
11
|
+
*
|
|
12
|
+
* The shell is a Bun HTML route now, not a file on disk, so "not found" is no
|
|
13
|
+
* longer the failure mode — a bundle error is, and Bun renders its own error
|
|
14
|
+
* page for that. This covers the remaining path: the bundle is fine but the
|
|
15
|
+
* markup could not be read back.
|
|
17
16
|
*/
|
|
18
|
-
|
|
19
|
-
const file = Bun.file(CLIENT_INDEX);
|
|
20
|
-
if (!(await file.exists())) {
|
|
21
|
-
throw new Error(`Client build not found at ${CLIENT_INDEX}. Run \`bun run build\` first.`);
|
|
22
|
-
}
|
|
23
|
-
const { mtimeMs } = await file.stat();
|
|
24
|
-
if (cachedTemplate !== null && mtimeMs === cachedMtimeMs) return cachedTemplate;
|
|
25
|
-
cachedTemplate = await file.text();
|
|
26
|
-
cachedMtimeMs = mtimeMs;
|
|
27
|
-
return cachedTemplate;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Actionable message instead of a bare 500 when the client build is absent. */
|
|
31
|
-
function missingBuildResponse(): Response {
|
|
17
|
+
function shellUnavailableResponse(reason: string): Response {
|
|
32
18
|
return new Response(
|
|
33
19
|
`<!doctype html><meta charset="utf-8"><title>OMPChamber</title>
|
|
34
20
|
<body style="font:14px/1.6 ui-monospace,monospace;padding:2rem">
|
|
35
|
-
<h1 style="font-size:1.1rem">
|
|
36
|
-
<p>The server
|
|
37
|
-
<pre style="background:#f4f1ea;padding:1rem;border-radius:6px"
|
|
38
|
-
<p>For a rebuild-on-change loop while developing:</p>
|
|
39
|
-
<pre style="background:#f4f1ea;padding:1rem;border-radius:6px">bunx rsbuild build --watch</pre>
|
|
21
|
+
<h1 style="font-size:1.1rem">Shell not available</h1>
|
|
22
|
+
<p>The server could not render its HTML shell.</p>
|
|
23
|
+
<pre style="background:#f4f1ea;padding:1rem;border-radius:6px">${reason}</pre>
|
|
40
24
|
</body>`,
|
|
41
25
|
{ status: 503, headers: HTML_HEADERS },
|
|
42
26
|
);
|
|
@@ -102,11 +86,6 @@ export const ssrRoutes = new Elysia({ name: 'ssr' }).get('*', async ({ request }
|
|
|
102
86
|
const asset = await tryServeStatic(pathname);
|
|
103
87
|
if (asset) return asset;
|
|
104
88
|
|
|
105
|
-
const devAsset = await tryProxyDevAsset(request, pathname);
|
|
106
|
-
if (devAsset) return devAsset;
|
|
107
|
-
|
|
108
|
-
if (!(await Bun.file(CLIENT_INDEX).exists())) return missingBuildResponse();
|
|
109
|
-
|
|
110
89
|
const settings = await readSettings();
|
|
111
90
|
const chamberSettings = (settings.omp_chamber_settings ?? {}) as { theme?: string };
|
|
112
91
|
// Resolved through the catalog: an id this build no longer ships (a
|
|
@@ -115,19 +94,28 @@ export const ssrRoutes = new Elysia({ name: 'ssr' }).get('*', async ({ request }
|
|
|
115
94
|
const theme = resolveTheme(chamberSettings.theme);
|
|
116
95
|
const initialIsMobile = MOBILE_UA.test(request.headers.get('user-agent') ?? '');
|
|
117
96
|
|
|
118
|
-
const
|
|
97
|
+
const rendered = await renderShell();
|
|
98
|
+
if (!rendered.ok) return shellUnavailableResponse(rendered.reason);
|
|
99
|
+
|
|
119
100
|
const bootstrap = JSON.stringify({ initialIsMobile, appSettings: settings }).replace(/</g, '\\u003c');
|
|
120
101
|
// The template's own `<meta name="theme-color">` is REWRITTEN, not joined by
|
|
121
102
|
// a second tag: with both present Chrome reads the first one, so an appended
|
|
122
103
|
// tag would leave a dark theme painting light browser chrome.
|
|
123
104
|
const themeStyle = `<style id="${THEME_STYLE_ELEMENT_ID}">${themeStyleSheet()}</style>`;
|
|
105
|
+
// The font faces are a stylesheet the bundler cannot carry: Bun resolves every
|
|
106
|
+
// local `url()` in CSS, so a bundled face is either base64 or an absolute
|
|
107
|
+
// filesystem path. `lib/bundler/css.ts` lifts the blocks out and the route
|
|
108
|
+
// below serves them, which is why the link is injected here — a
|
|
109
|
+
// `<link href="/fonts.css">` in index.html would be resolved by the HTML
|
|
110
|
+
// loader and fail the same way.
|
|
111
|
+
const fontLink = `<link rel="stylesheet" href="${FONT_STYLESHEET_ROUTE}">`;
|
|
124
112
|
|
|
125
113
|
return new Response(
|
|
126
|
-
|
|
114
|
+
rendered.html
|
|
127
115
|
.replace('data-theme="paper"', `data-theme="${theme.id}"`)
|
|
128
116
|
.replace('data-theme-variant="light"', `data-theme-variant="${theme.variant}"`)
|
|
129
117
|
.replace('<meta name="theme-color" content="#faf8f3" />', `<meta name="theme-color" content="${theme.canvas}" />`)
|
|
130
|
-
.replace('<!--app-head-->', themeStyle)
|
|
118
|
+
.replace('<!--app-head-->', themeStyle + fontLink)
|
|
131
119
|
.replace('<!--app-bootstrap-->', `<script>window.__OMP_BOOTSTRAP__=${bootstrap}</script>`),
|
|
132
120
|
{ headers: HTML_HEADERS },
|
|
133
121
|
);
|
|
@@ -5,40 +5,37 @@ import { join } from 'path';
|
|
|
5
5
|
|
|
6
6
|
import { servePublicAsset } from '@/server/plugins/static';
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const ROOTS = [
|
|
8
|
+
// `public/` is the only asset root now: the client bundle is served by Bun's own
|
|
9
|
+
// routing table (`/_bun/*` in dev, `/chunk-*` in production), so there is no
|
|
10
|
+
// second build-output root to fall back to.
|
|
11
|
+
const PUBLIC_DIR = join(mkdtempSync(join(tmpdir(), 'omp-static-')), 'public');
|
|
12
|
+
const ROOTS = [PUBLIC_DIR] as const;
|
|
13
13
|
|
|
14
14
|
beforeAll(() => {
|
|
15
|
-
mkdirSync(
|
|
16
|
-
|
|
17
|
-
writeFileSync(join(
|
|
18
|
-
writeFileSync(join(
|
|
19
|
-
writeFileSync(join(
|
|
20
|
-
writeFileSync(join(BUILD_OUTPUT, 'index.html'), '<!doctype html><title>shell</title>');
|
|
15
|
+
mkdirSync(PUBLIC_DIR, { recursive: true });
|
|
16
|
+
writeFileSync(join(PUBLIC_DIR, 'icon.svg'), '<svg id="public"/>');
|
|
17
|
+
writeFileSync(join(PUBLIC_DIR, 'manifest.webmanifest'), '{"name":"OMPChamber"}');
|
|
18
|
+
writeFileSync(join(PUBLIC_DIR, 'sw.js'), 'self.addEventListener("fetch", () => {});');
|
|
19
|
+
writeFileSync(join(PUBLIC_DIR, 'index.html'), '<!doctype html><title>shell</title>');
|
|
21
20
|
});
|
|
22
21
|
|
|
23
22
|
afterAll(() => {
|
|
24
|
-
rmSync(
|
|
25
|
-
rmSync(BUILD_OUTPUT, { recursive: true, force: true });
|
|
23
|
+
rmSync(PUBLIC_DIR, { recursive: true, force: true });
|
|
26
24
|
});
|
|
27
25
|
|
|
28
26
|
describe('servePublicAsset', () => {
|
|
29
|
-
|
|
30
|
-
// lookup confined to it answered the HTML shell for `/icon.svg` and the About
|
|
31
|
-
// modal rendered a broken image.
|
|
32
|
-
test('falls back to the build output when the source folder has no copy', async () => {
|
|
33
|
-
const response = await servePublicAsset('/manifest.webmanifest', ROOTS);
|
|
34
|
-
expect(response?.headers.get('content-type')).toBe('application/manifest+json');
|
|
35
|
-
expect(await response?.text()).toBe('{"name":"OMPChamber"}');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('prefers the source folder so a dev edit lands without a rebuild', async () => {
|
|
27
|
+
test('serves a public asset with its own content type', async () => {
|
|
39
28
|
const response = await servePublicAsset('/icon.svg', ROOTS);
|
|
40
29
|
expect(response?.headers.get('content-type')).toBe('image/svg+xml');
|
|
41
|
-
expect(await response?.text()).toBe('<svg id="
|
|
30
|
+
expect(await response?.text()).toBe('<svg id="public"/>');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('serves the web manifest and the service worker', async () => {
|
|
34
|
+
expect((await servePublicAsset('/manifest.webmanifest', ROOTS))?.headers.get('content-type'))
|
|
35
|
+
.toBe('application/manifest+json');
|
|
36
|
+
// The service worker is only referenced from an inline script in the shell,
|
|
37
|
+
// so the HTML loader never copies it — this route is what serves it.
|
|
38
|
+
expect(await (await servePublicAsset('/sw.js', ROOTS))?.text()).toContain('addEventListener');
|
|
42
39
|
});
|
|
43
40
|
|
|
44
41
|
test('leaves the HTML shell to the SSR route', async () => {
|
|
@@ -46,14 +43,14 @@ describe('servePublicAsset', () => {
|
|
|
46
43
|
});
|
|
47
44
|
|
|
48
45
|
test('keeps a sibling of the root unreachable', async () => {
|
|
49
|
-
const outside = join(
|
|
46
|
+
const outside = join(PUBLIC_DIR, '..', 'secret.txt');
|
|
50
47
|
writeFileSync(outside, 'not an asset');
|
|
51
|
-
expect(await servePublicAsset('/../secret.txt',
|
|
52
|
-
expect(await servePublicAsset('/%2e%2e/secret.txt',
|
|
48
|
+
expect(await servePublicAsset('/../secret.txt', ROOTS)).toBeNull();
|
|
49
|
+
expect(await servePublicAsset('/%2e%2e/secret.txt', ROOTS)).toBeNull();
|
|
53
50
|
});
|
|
54
51
|
|
|
55
|
-
test('short-caches
|
|
56
|
-
const response = await servePublicAsset('/
|
|
52
|
+
test('short-caches, because these filenames carry no content hash', async () => {
|
|
53
|
+
const response = await servePublicAsset('/icon.svg', ROOTS);
|
|
57
54
|
expect(response?.headers.get('cache-control')).toBe('public, max-age=3600');
|
|
58
55
|
});
|
|
59
56
|
|
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { extname, join, normalize, resolve, sep } from 'path';
|
|
2
2
|
|
|
3
|
+
import { FONT_STYLESHEET_ROUTE, serveFontFile, serveFontStylesheet } from '@/server/lib/assets/font-css.server';
|
|
4
|
+
import { FONT_ROUTE_PREFIX, packageRoot } from '@/server/lib/assets/fonts.server';
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
|
-
* File serving for `
|
|
7
|
+
* File serving for `public/` and the font routes.
|
|
8
|
+
*
|
|
9
|
+
* The client bundle is NOT served from here any more. Bun's own routing owns
|
|
10
|
+
* the assets it emits — `/_bun/asset/*` and `/_bun/client/*` in development,
|
|
11
|
+
* `/chunk-*` in production — and answers them from its bundle table before a
|
|
12
|
+
* request reaches Elysia. `dist/client` no longer exists, so the `/static/`
|
|
13
|
+
* branch and its content-hashed caching went with it.
|
|
5
14
|
*
|
|
6
15
|
* Hand-rolled rather than `@elysiajs/static`: that plugin registers its own
|
|
7
16
|
* catch-all, which would shadow this app's SSR route. Exposing one lookup
|
|
8
17
|
* function lets the web catch-all try a file first and fall back to the shell.
|
|
9
18
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
// Resolved from the package root, not the cwd: `serve --prod` may run the built
|
|
20
|
+
// server from anywhere, and a cwd-relative `public` would not exist — every
|
|
21
|
+
// icon, the manifest and the service worker would 404.
|
|
22
|
+
const PUBLIC_ROOT = resolve(packageRoot(), 'public');
|
|
23
|
+
/** Where `scripts/build-client.ts` writes the production bundle and its chunks. */
|
|
24
|
+
const CLIENT_ROOT = resolve(packageRoot(), 'dist', 'client');
|
|
12
25
|
|
|
13
26
|
const IMMUTABLE = 'public, max-age=31536000, immutable';
|
|
14
27
|
const SHORT = 'public, max-age=3600';
|
|
15
28
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
29
|
+
// Production output carries content hashes in its filenames, so it can be
|
|
30
|
+
// cached hard. In development these paths are served by Bun's own table and
|
|
31
|
+
// this branch is not reached at all — a stable-name dev asset must never be
|
|
32
|
+
// pinned, which is why the distinction is kept here.
|
|
19
33
|
const ASSET_CACHE = Bun.env.NODE_ENV === 'production' ? IMMUTABLE : 'no-store';
|
|
20
34
|
|
|
21
35
|
const MIME: Record<string, string> = {
|
|
@@ -68,22 +82,13 @@ async function serveFile(filePath: string, cacheControl: string): Promise<Respon
|
|
|
68
82
|
});
|
|
69
83
|
}
|
|
70
84
|
|
|
71
|
-
/** Roots searched for a non-`/static/` asset, in preference order. */
|
|
72
|
-
const PUBLIC_ROOTS = [PUBLIC_ROOT, CLIENT_ROOT] as const;
|
|
73
|
-
|
|
74
85
|
/**
|
|
75
86
|
* The first readable copy of `pathname` under `roots`, or null.
|
|
76
87
|
*
|
|
77
88
|
* Non-`/static/` paths are the public assets (`icon.svg`, the web manifest, the
|
|
78
|
-
* service worker, the touch icons).
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* `public/` alone therefore 404s every one of them into the SSR shell when the
|
|
82
|
-
* chamber runs from an installed package. The source folder is tried first so a
|
|
83
|
-
* dev edit lands without a rebuild; the build output is the fallback. HTML is
|
|
84
|
-
* excluded so `/index.html` keeps going through the SSR route that injects the
|
|
85
|
-
* theme and bootstrap, and the cache stays short because these filenames carry
|
|
86
|
-
* no content hash.
|
|
89
|
+
* service worker, the touch icons). HTML is excluded so `/index.html` keeps
|
|
90
|
+
* going through the SSR route that injects the theme and bootstrap, and the
|
|
91
|
+
* cache stays short because these filenames carry no content hash.
|
|
87
92
|
*/
|
|
88
93
|
export async function servePublicAsset(pathname: string, roots: readonly string[]): Promise<Response | null> {
|
|
89
94
|
for (const root of roots) {
|
|
@@ -97,9 +102,25 @@ export async function servePublicAsset(pathname: string, roots: readonly string[
|
|
|
97
102
|
|
|
98
103
|
/** A file for `pathname`, or null when the request should reach the app. */
|
|
99
104
|
export async function tryServeStatic(pathname: string): Promise<Response | null> {
|
|
105
|
+
// The production bundle's own assets. `scripts/build-client.ts` emits the
|
|
106
|
+
// server into `dist/client` with its chunks under `dist/client/static`, and
|
|
107
|
+
// the generated HTML references them as `./static/...` — a page URL of `/`
|
|
108
|
+
// makes that `/static/...`, which is this branch. In development the same
|
|
109
|
+
// files are served by Bun's own routing table (`/_bun/asset/*`,
|
|
110
|
+
// `/_bun/client/*`) and never reach here.
|
|
100
111
|
if (pathname.startsWith('/static/')) {
|
|
101
112
|
const filePath = resolveWithin(CLIENT_ROOT, pathname);
|
|
102
113
|
return filePath ? serveFile(filePath, ASSET_CACHE) : null;
|
|
103
114
|
}
|
|
104
|
-
|
|
115
|
+
// The extracted `@font-face` stylesheet and the files it names. Bun's CSS
|
|
116
|
+
// loader cannot carry either — every local `url()` is resolved, so a bundled
|
|
117
|
+
// face is base64 or an absolute path — which is why they are served rather
|
|
118
|
+
// than emitted. Both live outside `public/`: the stylesheet is assembled from
|
|
119
|
+
// the faces `lib/bundler/css.ts` wrote, and the files come from the installed
|
|
120
|
+
// packages.
|
|
121
|
+
if (pathname === FONT_STYLESHEET_ROUTE) return serveFontStylesheet();
|
|
122
|
+
if (pathname.startsWith(FONT_ROUTE_PREFIX)) {
|
|
123
|
+
return serveFontFile(decodeURIComponent(pathname.slice(FONT_ROUTE_PREFIX.length)));
|
|
124
|
+
}
|
|
125
|
+
return servePublicAsset(pathname, [PUBLIC_ROOT]);
|
|
105
126
|
}
|
|
@@ -9,6 +9,7 @@ import * as settingsMcpTest from '@/server/routes/settings/mcp-test';
|
|
|
9
9
|
import * as settingsOmpConfig from '@/server/routes/settings/omp-config';
|
|
10
10
|
import * as settingsProjects from '@/server/routes/settings/projects';
|
|
11
11
|
import * as settingsProviderModels from '@/server/routes/settings/provider-models';
|
|
12
|
+
import * as settingsProviderModel from '@/server/routes/settings/provider-model';
|
|
12
13
|
import * as settingsProviders from '@/server/routes/settings/providers';
|
|
13
14
|
import * as settingsSkills from '@/server/routes/settings/skills';
|
|
14
15
|
import * as settingsUsage from '@/server/routes/settings/usage';
|
|
@@ -24,6 +25,7 @@ export const settingsBindings: HandlerBinding[] = [
|
|
|
24
25
|
...bindingsFor(settingsOmpConfig, '/api/settings/omp-config'),
|
|
25
26
|
...bindingsFor(settingsProjects, '/api/settings/projects'),
|
|
26
27
|
...bindingsFor(settingsProviderModels, '/api/settings/provider-models'),
|
|
28
|
+
...bindingsFor(settingsProviderModel, '/api/settings/provider-model'),
|
|
27
29
|
...bindingsFor(settingsProviders, '/api/settings/providers'),
|
|
28
30
|
...bindingsFor(settingsSkills, '/api/settings/skills'),
|
|
29
31
|
...bindingsFor(settingsUsage, '/api/settings/usage'),
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { json } from '@/server/lib/remix-compat';
|
|
2
|
+
import type { ActionFunctionArgs } from '@/server/lib/remix-compat';
|
|
3
|
+
import { methodNotAllowed } from '@/server/lib/route-adapter';
|
|
4
|
+
import { isMockMode } from '@/server/mock.server';
|
|
5
|
+
import { upsertOmpProviderModels } from '@/server/lib/omp/config/providers';
|
|
6
|
+
import { manualModelSeed } from '@/server/lib/omp/config/provider-seeds';
|
|
7
|
+
import { readNativeProviders } from '@/server/lib/omp/config/models-config';
|
|
8
|
+
import { isProviderAuthMode, isProviderThinkingEffort } from '@/shared/lib/models/provider/dialect';
|
|
9
|
+
import { invalidateModelsCaches } from '@/shared/lib/models/server-cache';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* POST /api/settings/provider-model — register ONE model by hand.
|
|
13
|
+
*
|
|
14
|
+
* A provider endpoint does not have to be listable to be usable: a gateway may
|
|
15
|
+
* serve a model its `/models` route omits, a deployment may be named locally
|
|
16
|
+
* (Azure), or the endpoint may have no listing route at all (Bedrock, Vertex).
|
|
17
|
+
* The "Add model" dialog is that path, and it writes the same models.yml entry
|
|
18
|
+
* the auto-fetch would.
|
|
19
|
+
*
|
|
20
|
+
* Body: { provider, id, name?, contextWindow?, maxTokens?, reasoning?,
|
|
21
|
+
* imageInput?, efforts?, costInput?, costOutput?, costCacheRead?,
|
|
22
|
+
* costCacheWrite?, baseUrl?, apiKey?, auth? }
|
|
23
|
+
* → { success, written, addedModels, alreadyKnown, reason? }
|
|
24
|
+
*
|
|
25
|
+
* `baseUrl`/`apiKey`/`auth` are only used when the provider has no models.yml
|
|
26
|
+
* entry yet — an existing entry keeps its own endpoint and credential (the
|
|
27
|
+
* writer is add-only). Registering the FIRST model under a provider omp only
|
|
28
|
+
* knows from its bundled catalog therefore needs a real endpoint and key; the
|
|
29
|
+
* refusal says so instead of writing half an entry.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** Context/output limits arrive as token counts; anything unusable is dropped. */
|
|
33
|
+
function positiveInt(value: unknown): number | undefined {
|
|
34
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.round(value) : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function action({ request, params }: ActionFunctionArgs) {
|
|
38
|
+
if (request.method !== 'POST') {
|
|
39
|
+
return methodNotAllowed({ request, params });
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const body = await request.json() as Record<string, unknown>;
|
|
43
|
+
const provider = typeof body.provider === 'string' ? body.provider.trim() : '';
|
|
44
|
+
const id = typeof body.id === 'string' ? body.id.trim() : '';
|
|
45
|
+
if (!provider || !id) {
|
|
46
|
+
return json({ error: 'provider and model id are required' }, { status: 400 });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const rawEfforts = Array.isArray(body.efforts) ? body.efforts : [];
|
|
50
|
+
const seed = manualModelSeed({
|
|
51
|
+
id,
|
|
52
|
+
name: typeof body.name === 'string' ? body.name : undefined,
|
|
53
|
+
contextWindow: positiveInt(body.contextWindow),
|
|
54
|
+
maxTokens: positiveInt(body.maxTokens),
|
|
55
|
+
reasoning: body.reasoning === true,
|
|
56
|
+
imageInput: body.imageInput === true,
|
|
57
|
+
// An effort outside omp's vocabulary is dropped rather than written: the
|
|
58
|
+
// schema rejects the whole file over one unknown level.
|
|
59
|
+
efforts: rawEfforts.filter(isProviderThinkingEffort),
|
|
60
|
+
costInput: typeof body.costInput === 'number' ? body.costInput : undefined,
|
|
61
|
+
costOutput: typeof body.costOutput === 'number' ? body.costOutput : undefined,
|
|
62
|
+
costCacheRead: typeof body.costCacheRead === 'number' ? body.costCacheRead : undefined,
|
|
63
|
+
costCacheWrite: typeof body.costCacheWrite === 'number' ? body.costCacheWrite : undefined,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Mock mode has no models.yml; the caller's own overlay row is the store.
|
|
67
|
+
if (isMockMode()) {
|
|
68
|
+
return json({ success: true, written: false, isMock: true, addedModels: [seed.id] });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Read the registry BEFORE writing: `skippedModels` means "not added", not
|
|
72
|
+
// "already there" — the writer also skips when it refuses to create a
|
|
73
|
+
// provider it has no endpoint or credential for. The pre-write file is what
|
|
74
|
+
// tells a genuine duplicate apart from that refusal, and reading it after
|
|
75
|
+
// the write would count the model this call just added.
|
|
76
|
+
const registeredBefore = await readNativeProviders();
|
|
77
|
+
const alreadyKnown = registeredBefore
|
|
78
|
+
.find((info) => info.slug === provider)
|
|
79
|
+
?.modelIds.includes(seed.id) === true;
|
|
80
|
+
|
|
81
|
+
const result = await upsertOmpProviderModels(provider, {
|
|
82
|
+
baseUrl: typeof body.baseUrl === 'string' ? body.baseUrl.trim() : '',
|
|
83
|
+
// A provider the chamber overlay knows but models.yml does not needs the
|
|
84
|
+
// credential to be created; a provider already in the file keeps its own
|
|
85
|
+
// (the writer is add-only). A masked placeholder is dropped by the writer
|
|
86
|
+
// rather than persisted as a credential.
|
|
87
|
+
apiKey: typeof body.apiKey === 'string' && body.apiKey.trim() ? body.apiKey.trim() : undefined,
|
|
88
|
+
...(isProviderAuthMode(body.auth) ? { auth: body.auth } : {}),
|
|
89
|
+
overrideOnly: false,
|
|
90
|
+
models: [seed],
|
|
91
|
+
});
|
|
92
|
+
if (result.written) invalidateModelsCaches();
|
|
93
|
+
|
|
94
|
+
return json({
|
|
95
|
+
success: result.written || alreadyKnown,
|
|
96
|
+
written: result.written,
|
|
97
|
+
addedModels: result.addedModels,
|
|
98
|
+
backfilledModels: result.backfilledModels,
|
|
99
|
+
alreadyKnown,
|
|
100
|
+
reason: alreadyKnown
|
|
101
|
+
? `${seed.id} is already registered under "${provider}" in models.yml`
|
|
102
|
+
: result.reason,
|
|
103
|
+
}, { status: result.written || alreadyKnown ? 200 : 400 });
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return json({ error: error instanceof Error ? error.message : String(error) }, { status: 400 });
|
|
106
|
+
}
|
|
107
|
+
}
|