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,4 +1,4 @@
|
|
|
1
|
-
import type { ProviderItem } from '@/shared/types';
|
|
1
|
+
import type { PresetProviderOption, ProviderItem } from '@/shared/types/settings/provider';
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_PROVIDERS_LIST: ProviderItem[] = [
|
|
4
4
|
{
|
|
@@ -8,6 +8,7 @@ export const DEFAULT_PROVIDERS_LIST: ProviderItem[] = [
|
|
|
8
8
|
icon: 'deepseek',
|
|
9
9
|
status: 'connected',
|
|
10
10
|
configuredIn: 'auth credentials',
|
|
11
|
+
credentialSource: 'chamber',
|
|
11
12
|
apiKey: 'sk-ds-••••••••••••••••••••38f1',
|
|
12
13
|
baseUrl: 'https://api.deepseek.com/v1',
|
|
13
14
|
models: [
|
|
@@ -59,6 +60,7 @@ export const DEFAULT_PROVIDERS_LIST: ProviderItem[] = [
|
|
|
59
60
|
icon: 'agentrouter',
|
|
60
61
|
status: 'connected',
|
|
61
62
|
configuredIn: 'auth credentials',
|
|
63
|
+
credentialSource: 'chamber',
|
|
62
64
|
apiKey: 'ar-live-•••••••••••••••••••89c2',
|
|
63
65
|
baseUrl: 'https://router.agent.ai/v1',
|
|
64
66
|
models: [
|
|
@@ -104,6 +106,7 @@ export const DEFAULT_PROVIDERS_LIST: ProviderItem[] = [
|
|
|
104
106
|
icon: 'commandcode',
|
|
105
107
|
status: 'connected',
|
|
106
108
|
configuredIn: 'auth credentials',
|
|
109
|
+
credentialSource: 'chamber',
|
|
107
110
|
apiKey: 'cc-key-••••••••••••••••••••7a12',
|
|
108
111
|
baseUrl: 'https://api.commandcode.dev/v1',
|
|
109
112
|
models: Array.from({ length: 41 }, (_, i) => {
|
|
@@ -142,6 +145,7 @@ export const DEFAULT_PROVIDERS_LIST: ProviderItem[] = [
|
|
|
142
145
|
icon: 'opencode',
|
|
143
146
|
status: 'connected',
|
|
144
147
|
configuredIn: 'auth credentials',
|
|
148
|
+
credentialSource: 'chamber',
|
|
145
149
|
apiKey: 'zen-live-•••••••••••••••••••44fa',
|
|
146
150
|
baseUrl: 'https://zen.opencode.ai/v1',
|
|
147
151
|
models: [
|
|
@@ -161,6 +165,7 @@ export const DEFAULT_PROVIDERS_LIST: ProviderItem[] = [
|
|
|
161
165
|
icon: 'claude',
|
|
162
166
|
status: 'connected',
|
|
163
167
|
configuredIn: 'auth credentials',
|
|
168
|
+
credentialSource: 'chamber',
|
|
164
169
|
apiKey: 'sk-ant-•••••••••••••••••••••991c',
|
|
165
170
|
baseUrl: 'https://api.anthropic.com/v1',
|
|
166
171
|
models: [
|
|
@@ -174,12 +179,139 @@ export const DEFAULT_PROVIDERS_LIST: ProviderItem[] = [
|
|
|
174
179
|
},
|
|
175
180
|
];
|
|
176
181
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Providers offered by the Add Provider picker. `group` is the section the
|
|
184
|
+
* picker renders; `api`/`auth`/`discovery` are the `models.yml` fields the
|
|
185
|
+
* preset seeds, so a choice that omp understands is made once here rather than
|
|
186
|
+
* re-derived from the URL at write time.
|
|
187
|
+
*
|
|
188
|
+
* A preset whose id names a provider omp already bundles (`bundled: true`) is
|
|
189
|
+
* an OVERRIDE entry: omp keeps serving its own model list for that provider and
|
|
190
|
+
* only the endpoint changes. That is the documented way to point a bundled
|
|
191
|
+
* provider at a proxy, and it is deliberately not offered for providers whose
|
|
192
|
+
* models the chamber would then have to duplicate.
|
|
193
|
+
*/
|
|
194
|
+
export const PRESET_NEW_PROVIDERS: PresetProviderOption[] = [
|
|
195
|
+
{
|
|
196
|
+
id: 'openai', name: 'OpenAI', slug: 'openai', icon: 'openai',
|
|
197
|
+
defaultUrl: 'https://api.openai.com/v1', api: 'openai-responses', group: 'Frontier APIs',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'anthropic', name: 'Anthropic', slug: 'anthropic', icon: 'claude',
|
|
201
|
+
defaultUrl: 'https://api.anthropic.com/v1', api: 'anthropic-messages', group: 'Frontier APIs',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'google', name: 'Google Gemini', slug: 'google', icon: 'gemini',
|
|
205
|
+
defaultUrl: 'https://generativelanguage.googleapis.com/v1beta',
|
|
206
|
+
api: 'google-generative-ai', group: 'Frontier APIs',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'groq', name: 'Groq Cloud', slug: 'groq', icon: 'groq',
|
|
210
|
+
defaultUrl: 'https://api.groq.com/openai/v1', api: 'openai-completions', group: 'Frontier APIs',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'mistral', name: 'Mistral AI', slug: 'mistral', icon: 'mistral',
|
|
214
|
+
defaultUrl: 'https://api.mistral.ai/v1', api: 'openai-completions', group: 'Frontier APIs',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'xai', name: 'xAI', slug: 'xai', icon: 'xai',
|
|
218
|
+
defaultUrl: 'https://api.x.ai/v1', api: 'openai-completions', group: 'Frontier APIs',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'cerebras', name: 'Cerebras', slug: 'cerebras', icon: 'cerebras',
|
|
222
|
+
defaultUrl: 'https://api.cerebras.ai/v1', api: 'openai-completions', group: 'Frontier APIs',
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: 'deepseek', name: 'DeepSeek', slug: 'deepseek', icon: 'deepseek',
|
|
226
|
+
defaultUrl: 'https://api.deepseek.com/v1', api: 'openai-completions', group: 'Frontier APIs',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: 'openrouter', name: 'OpenRouter', slug: 'openrouter', icon: 'openrouter',
|
|
230
|
+
defaultUrl: 'https://openrouter.ai/api/v1', api: 'openai-completions', group: 'Gateways',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'together', name: 'Together AI', slug: 'together', icon: 'together',
|
|
234
|
+
defaultUrl: 'https://api.together.xyz/v1', api: 'openai-completions', group: 'Gateways',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: 'fireworks', name: 'Fireworks', slug: 'fireworks', icon: 'fireworks',
|
|
238
|
+
defaultUrl: 'https://api.fireworks.ai/inference/v1', api: 'openai-completions', group: 'Gateways',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'deepinfra', name: 'DeepInfra', slug: 'deepinfra', icon: 'deepinfra',
|
|
242
|
+
defaultUrl: 'https://api.deepinfra.com/v1/openai', api: 'openai-completions', group: 'Gateways',
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: 'siliconflow', name: 'SiliconFlow', slug: 'siliconflow', icon: 'siliconflow',
|
|
246
|
+
defaultUrl: 'https://api.siliconflow.com/v1', api: 'openai-completions', group: 'Gateways',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'nvidia', name: 'NVIDIA NIM', slug: 'nvidia', icon: 'nvidia',
|
|
250
|
+
defaultUrl: 'https://integrate.api.nvidia.com/v1', api: 'openai-completions', group: 'Gateways',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: 'novita', name: 'Novita AI', slug: 'novita', icon: 'novita',
|
|
254
|
+
defaultUrl: 'https://api.novita.ai/v3/openai', api: 'openai-completions', group: 'Gateways',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'vercel-ai-gateway', name: 'Vercel AI Gateway', slug: 'vercel-ai-gateway', icon: 'vercel',
|
|
258
|
+
defaultUrl: 'https://ai-gateway.vercel.sh/v1', api: 'openai-completions', group: 'Gateways',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'ollama', name: 'Ollama (Local)', slug: 'ollama', icon: 'ollama',
|
|
262
|
+
defaultUrl: 'http://127.0.0.1:11434', api: 'openai-responses',
|
|
263
|
+
auth: 'none', discovery: 'ollama', group: 'Local & self-hosted',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: 'lm-studio', name: 'LM Studio (Local)', slug: 'lm-studio', icon: 'lmstudio',
|
|
267
|
+
defaultUrl: 'http://127.0.0.1:1234/v1', api: 'openai-completions',
|
|
268
|
+
auth: 'none', discovery: 'lm-studio', group: 'Local & self-hosted',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 'llama-cpp', name: 'llama.cpp (Local)', slug: 'llama.cpp', icon: 'llamacpp',
|
|
272
|
+
defaultUrl: 'http://127.0.0.1:8080', api: 'openai-responses',
|
|
273
|
+
auth: 'none', discovery: 'llama.cpp', group: 'Local & self-hosted',
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 'vllm', name: 'vLLM (Local)', slug: 'vllm', icon: 'vllm',
|
|
277
|
+
defaultUrl: 'http://127.0.0.1:8000/v1', api: 'openai-completions',
|
|
278
|
+
auth: 'none', discovery: 'openai-models-list', group: 'Local & self-hosted',
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: 'litellm', name: 'LiteLLM Proxy', slug: 'litellm', icon: 'litellm',
|
|
282
|
+
defaultUrl: 'http://localhost:4000/v1', api: 'openai-completions',
|
|
283
|
+
discovery: 'litellm', group: 'Local & self-hosted',
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: 'azure', name: 'Azure OpenAI', slug: 'azure', icon: 'azure',
|
|
287
|
+
defaultUrl: 'https://YOUR-RESOURCE.openai.azure.com/openai/v1',
|
|
288
|
+
api: 'azure-openai-responses', bundled: true, group: 'Other dialects',
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: 'amazon-bedrock', name: 'Amazon Bedrock', slug: 'amazon-bedrock', icon: 'amazon',
|
|
292
|
+
defaultUrl: 'https://bedrock-runtime.us-east-1.amazonaws.com',
|
|
293
|
+
api: 'bedrock-converse-stream', bundled: true, group: 'Other dialects',
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
id: 'google-vertex', name: 'Google Vertex AI', slug: 'google-vertex', icon: 'vertexai',
|
|
297
|
+
defaultUrl: 'https://us-central1-aiplatform.googleapis.com/v1',
|
|
298
|
+
api: 'google-vertex', bundled: true, group: 'Other dialects',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: 'openai-codex', name: 'OpenAI Codex', slug: 'openai-codex', icon: 'codex',
|
|
302
|
+
defaultUrl: 'https://chatgpt.com/backend-api/codex',
|
|
303
|
+
api: 'openai-codex-responses', bundled: true, group: 'Other dialects',
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: 'anthropic-custom', name: 'Anthropic-Compatible Proxy', slug: 'anthropic-custom', icon: 'claude',
|
|
307
|
+
defaultUrl: 'https://', api: 'anthropic-messages', group: 'Custom',
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: 'openai-custom', name: 'OpenAI-Compatible Gateway', slug: 'openai-custom', icon: 'openai',
|
|
311
|
+
defaultUrl: 'https://', api: 'openai-completions', group: 'Custom',
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: 'custom', name: 'Custom OpenAI-Compatible', slug: 'custom', icon: 'custom',
|
|
315
|
+
defaultUrl: 'https://', api: 'openai-completions', group: 'Custom',
|
|
316
|
+
},
|
|
185
317
|
];
|
|
@@ -19,10 +19,6 @@ export interface FileEditorTarget {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export interface UseFileEditorOptions {
|
|
22
|
-
/** Content substituted when a read fails (desktop). Omit to report the failure (mobile). */
|
|
23
|
-
fallback?: (name: string) => string;
|
|
24
|
-
/** Surface read failures through `loadError` instead of silently substituting `fallback`. */
|
|
25
|
-
reportLoadError?: boolean;
|
|
26
22
|
/** Flush a pending debounced write on unmount so closing the editor cannot drop an edit. */
|
|
27
23
|
flushOnUnmount?: boolean;
|
|
28
24
|
/** Mark a failed write as `'error'`; otherwise the status returns to `'idle'`. */
|
|
@@ -74,8 +70,6 @@ export function useFileEditor(
|
|
|
74
70
|
options: UseFileEditorOptions = {},
|
|
75
71
|
): UseFileEditorResult {
|
|
76
72
|
const {
|
|
77
|
-
fallback,
|
|
78
|
-
reportLoadError = false,
|
|
79
73
|
flushOnUnmount = false,
|
|
80
74
|
reportSaveError = false,
|
|
81
75
|
downloadMimeType = 'text/plain',
|
|
@@ -184,11 +178,9 @@ export function useFileEditor(
|
|
|
184
178
|
}
|
|
185
179
|
|
|
186
180
|
if (!target.path) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
remember(fileKey, fallback ? fallback(target.name) : '');
|
|
181
|
+
// An attachment chip with no path has nothing to read and nothing to
|
|
182
|
+
// save; the message is the honest outcome.
|
|
183
|
+
setLoadError('No file path supplied for this attachment.');
|
|
192
184
|
return;
|
|
193
185
|
}
|
|
194
186
|
|
|
@@ -203,14 +195,11 @@ export function useFileEditor(
|
|
|
203
195
|
if (target.repo && target.repo !== '.') params.set('repo', target.repo);
|
|
204
196
|
fetch(`/api/fs/read?${params.toString()}`)
|
|
205
197
|
.then(async (res) => {
|
|
206
|
-
const data = await res.json().catch(() => null);
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
throw new Error((data as { error?: string } | null)?.error || `HTTP ${res.status}`);
|
|
210
|
-
}
|
|
211
|
-
return data.content as string;
|
|
198
|
+
const data = (await res.json().catch(() => null)) as { content?: unknown; error?: string } | null;
|
|
199
|
+
if (!res.ok || !data || typeof data.content !== 'string') {
|
|
200
|
+
throw new Error(data?.error || `HTTP ${res.status}`);
|
|
212
201
|
}
|
|
213
|
-
return
|
|
202
|
+
return data.content;
|
|
214
203
|
})
|
|
215
204
|
.then((text) => {
|
|
216
205
|
if (cancelled) return;
|
|
@@ -222,18 +211,16 @@ export function useFileEditor(
|
|
|
222
211
|
if (typed !== undefined && typed !== baselinesRef.current[fileKey]) return;
|
|
223
212
|
const queued = pendingRef.current;
|
|
224
213
|
if (queued && targetKey(queued.file) === fileKey) return;
|
|
225
|
-
remember(fileKey, text
|
|
214
|
+
remember(fileKey, text);
|
|
226
215
|
})
|
|
227
216
|
.catch((err: unknown) => {
|
|
228
217
|
if (cancelled) return;
|
|
229
218
|
// A failed re-read keeps what is on screen; only a first load reports.
|
|
230
219
|
if (!firstLoad) return;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
remember(fileKey, fallback ? fallback(target.name) : '');
|
|
236
|
-
}
|
|
220
|
+
// An empty buffer, never a substitute: the next save would write the
|
|
221
|
+
// substitute over the real file.
|
|
222
|
+
remember(fileKey, '');
|
|
223
|
+
setLoadError(err instanceof Error ? err.message : 'Failed to read file');
|
|
237
224
|
})
|
|
238
225
|
.finally(() => {
|
|
239
226
|
if (!cancelled) setIsLoading(false);
|
|
@@ -242,7 +229,7 @@ export function useFileEditor(
|
|
|
242
229
|
return () => {
|
|
243
230
|
cancelled = true;
|
|
244
231
|
};
|
|
245
|
-
}, [key, target,
|
|
232
|
+
}, [key, target, remember, revision, isImage]);
|
|
246
233
|
|
|
247
234
|
useEffect(() => {
|
|
248
235
|
if (saveStatus !== 'saved') return;
|
|
@@ -250,6 +237,13 @@ export function useFileEditor(
|
|
|
250
237
|
return () => window.clearTimeout(timer);
|
|
251
238
|
}, [saveStatus]);
|
|
252
239
|
|
|
240
|
+
// The save status describes the file that was written, so it must not outlive
|
|
241
|
+
// that tab: opening another file used to keep showing the previous file's
|
|
242
|
+
// "Save failed" (and its error icon) against a file that was never saved.
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
setSaveStatus('idle');
|
|
245
|
+
}, [key]);
|
|
246
|
+
|
|
253
247
|
const onChange = useCallback(
|
|
254
248
|
(next: string) => {
|
|
255
249
|
if (!target || isImage) return;
|
|
@@ -268,6 +262,9 @@ export function useFileEditor(
|
|
|
268
262
|
|
|
269
263
|
const saveNow = useCallback(() => {
|
|
270
264
|
if (!target || isImage) return;
|
|
265
|
+
// A file whose read failed has no trustworthy buffer: saving it would write
|
|
266
|
+
// the empty placeholder over the real file. The user must reopen it first.
|
|
267
|
+
if (loadError) return;
|
|
271
268
|
const text = contents[targetKey(target)];
|
|
272
269
|
if (text === undefined) return;
|
|
273
270
|
if (pendingRef.current) {
|
|
@@ -275,7 +272,7 @@ export function useFileEditor(
|
|
|
275
272
|
pendingRef.current = null;
|
|
276
273
|
}
|
|
277
274
|
void persistRef.current(target, text);
|
|
278
|
-
}, [target, contents, isImage]);
|
|
275
|
+
}, [target, contents, isImage, loadError]);
|
|
279
276
|
|
|
280
277
|
const { copied, copy, download } = useFileTransfer({
|
|
281
278
|
target,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelsResponse } from '@/shared/lib/models/client';
|
|
2
|
-
import { providerNamesFromConnected } from '@/shared/lib/models/provider
|
|
2
|
+
import { providerNamesFromConnected } from '@/shared/lib/models/provider/label';
|
|
3
3
|
import { useModelsCatalog } from '@/client/hooks/models/use-models-catalog';
|
|
4
4
|
|
|
5
5
|
/** slug → display-name map from `connectedProviders` ({} when none connected). */
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The provider mutation actions behind Settings → Providers.
|
|
8
|
+
*
|
|
9
|
+
* Split from the hook (`providers.ts`) so that file holds state wiring only:
|
|
10
|
+
* every action here is a write whose failure has to be reported, and keeping
|
|
11
|
+
* them together means the "what does each mutation actually change" contract
|
|
12
|
+
* has one home. `deps` is a single record rather than a positional list, so a
|
|
13
|
+
* caller cannot silently pass the wrong setter.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ProviderItem, ProviderModel } from '@/shared/types';
|
|
17
|
+
import { notifyModelsUpdated } from '@/shared/lib/models/client';
|
|
18
|
+
import {
|
|
19
|
+
deleteProvider,
|
|
20
|
+
saveProviderOverlay,
|
|
21
|
+
setProviderEnabled,
|
|
22
|
+
} from '@/shared/lib/models/provider/connection';
|
|
23
|
+
import {
|
|
24
|
+
fetchProviderModelsRemote,
|
|
25
|
+
mergeProviderModels,
|
|
26
|
+
syncProviderModelsToCatalog,
|
|
27
|
+
} from '@/shared/lib/models/provider/models';
|
|
28
|
+
import { removeLegacyKenariModels } from '@/shared/lib/models/provider/cleanup';
|
|
29
|
+
import { addProviderMessage, fetchModelsNote } from '@/client/hooks/settings/providers/messages';
|
|
30
|
+
import { saveModelOverrideRemote } from '@/client/hooks/settings/providers/api';
|
|
31
|
+
|
|
32
|
+
export interface ProviderActionsDeps {
|
|
33
|
+
providers: ProviderItem[];
|
|
34
|
+
selectedProvider: ProviderItem | undefined;
|
|
35
|
+
connectedProviders: ProviderItem[];
|
|
36
|
+
isFetchingModels: boolean;
|
|
37
|
+
isDeletingProvider: boolean;
|
|
38
|
+
pushToast: (message: string, tone: 'success' | 'error') => void;
|
|
39
|
+
setProviders: (providers: ProviderItem[]) => void;
|
|
40
|
+
setSelectedProviderId: (id: string) => void;
|
|
41
|
+
setIsAddModalOpen: (open: boolean) => void;
|
|
42
|
+
setIsDeleteModalOpen: (open: boolean) => void;
|
|
43
|
+
setIsDeletingProvider: (deleting: boolean) => void;
|
|
44
|
+
setIsFetchingModels: (fetching: boolean) => void;
|
|
45
|
+
onAddModalClose?: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createProviderActions(deps: ProviderActionsDeps) {
|
|
49
|
+
const {
|
|
50
|
+
providers,
|
|
51
|
+
selectedProvider,
|
|
52
|
+
connectedProviders,
|
|
53
|
+
isFetchingModels,
|
|
54
|
+
isDeletingProvider,
|
|
55
|
+
pushToast,
|
|
56
|
+
setProviders,
|
|
57
|
+
setSelectedProviderId,
|
|
58
|
+
setIsAddModalOpen,
|
|
59
|
+
setIsDeleteModalOpen,
|
|
60
|
+
setIsDeletingProvider,
|
|
61
|
+
setIsFetchingModels,
|
|
62
|
+
onAddModalClose,
|
|
63
|
+
} = deps;
|
|
64
|
+
|
|
65
|
+
/** Publish an overlay edit and adopt whatever the server merged back. */
|
|
66
|
+
const persistProviders = async (updated: ProviderItem[]) => {
|
|
67
|
+
setProviders(updated);
|
|
68
|
+
try {
|
|
69
|
+
const merged = await saveProviderOverlay(updated);
|
|
70
|
+
if (merged) setProviders(merged);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Failed to save providers via API:', error);
|
|
73
|
+
pushToast('Failed to save provider settings.', 'error');
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const toggleProviderEnabled = async (
|
|
78
|
+
slug: string,
|
|
79
|
+
enabled: boolean,
|
|
80
|
+
): Promise<ProviderItem[] | null> => {
|
|
81
|
+
try {
|
|
82
|
+
const merged = await setProviderEnabled(slug, enabled);
|
|
83
|
+
if (merged) setProviders(merged);
|
|
84
|
+
return merged;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Failed to update provider connection:', error);
|
|
87
|
+
pushToast('Failed to update the provider connection.', 'error');
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleAddProvider = async (
|
|
93
|
+
newProvider: ProviderItem,
|
|
94
|
+
options: { fetchedCount: number; ompNote?: string },
|
|
95
|
+
) => {
|
|
96
|
+
// Replace by slug rather than append: an endpoint override reuses the slug
|
|
97
|
+
// of a provider omp already ships, and two overlay rows for one slug would
|
|
98
|
+
// let the stale one win the next merge.
|
|
99
|
+
const slug = newProvider.slug.trim().toLowerCase();
|
|
100
|
+
const withoutExisting = providers.filter((p) => p.slug.trim().toLowerCase() !== slug);
|
|
101
|
+
await persistProviders([...withoutExisting, newProvider]);
|
|
102
|
+
const merged = await toggleProviderEnabled(newProvider.slug, true);
|
|
103
|
+
const registered = merged?.find((provider) => provider.slug.trim().toLowerCase() === slug);
|
|
104
|
+
setSelectedProviderId(registered?.id || newProvider.id);
|
|
105
|
+
setIsAddModalOpen(false);
|
|
106
|
+
onAddModalClose?.();
|
|
107
|
+
|
|
108
|
+
const message = addProviderMessage(newProvider, options);
|
|
109
|
+
pushToast(message.text, message.tone);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleReconnect = async (updates: Partial<ProviderItem>) => {
|
|
113
|
+
if (!selectedProvider) return;
|
|
114
|
+
const target = selectedProvider;
|
|
115
|
+
// A keyless provider stores no credential, so a masked placeholder echoed
|
|
116
|
+
// back from the dialog must never be persisted as one — it would show a key
|
|
117
|
+
// that authenticates nothing.
|
|
118
|
+
const patch: Partial<ProviderItem> = target.auth === 'none'
|
|
119
|
+
? Object.fromEntries(Object.entries(updates).filter(([key]) => key !== 'apiKey'))
|
|
120
|
+
: updates;
|
|
121
|
+
await persistProviders(providers.map((p) => (
|
|
122
|
+
p.id === target.id ? { ...p, ...patch, status: 'connected' as const } : p
|
|
123
|
+
)));
|
|
124
|
+
if (target.status !== 'connected') {
|
|
125
|
+
await toggleProviderEnabled(target.slug, true);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const handleFetchModels = async (credentials: { apiKey?: string; baseUrl?: string }) => {
|
|
130
|
+
if (!selectedProvider || isFetchingModels) return;
|
|
131
|
+
const baseUrl = credentials.baseUrl || selectedProvider.baseUrl;
|
|
132
|
+
if (!baseUrl) {
|
|
133
|
+
pushToast('Base URL is required to fetch models.', 'error');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setIsFetchingModels(true);
|
|
137
|
+
try {
|
|
138
|
+
const result = await fetchModelsFor(selectedProvider, baseUrl, credentials.apiKey);
|
|
139
|
+
if (!result) return;
|
|
140
|
+
if (result.omp?.written) notifyModelsUpdated();
|
|
141
|
+
} finally {
|
|
142
|
+
setIsFetchingModels(false);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The listing probe plus its reporting. Kept as one unit because the three
|
|
148
|
+
* outcomes — models added, legacy models pruned, nothing new — each have their
|
|
149
|
+
* own message, and a partial report reads as a failed fetch.
|
|
150
|
+
*/
|
|
151
|
+
const fetchModelsFor = async (
|
|
152
|
+
provider: ProviderItem,
|
|
153
|
+
baseUrl: string,
|
|
154
|
+
credential: string | undefined,
|
|
155
|
+
): Promise<{ omp?: { written: boolean; reason?: string } } | null> => {
|
|
156
|
+
const result = await fetchProviderModelsRemote({
|
|
157
|
+
baseUrl,
|
|
158
|
+
apiKey: credential || provider.apiKey,
|
|
159
|
+
providerSlug: provider.slug,
|
|
160
|
+
persistToOmp: true,
|
|
161
|
+
// The provider's own dialect decides the auth header the probe sends;
|
|
162
|
+
// falling back to the URL classifier would send a Bearer header to an
|
|
163
|
+
// Anthropic-shaped proxy that only accepts x-api-key.
|
|
164
|
+
...(provider.api ? { api: provider.api } : {}),
|
|
165
|
+
...(provider.auth ? { auth: provider.auth } : {}),
|
|
166
|
+
});
|
|
167
|
+
if (!result.ok || !result.models) {
|
|
168
|
+
pushToast(result.error || 'Failed to fetch models from the provider.', 'error');
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const cleaned = removeLegacyKenariModels(provider, provider.models);
|
|
172
|
+
const removedCount = provider.models.length - cleaned.length;
|
|
173
|
+
const { merged, addedCount } = mergeProviderModels(cleaned, result.models);
|
|
174
|
+
if (addedCount > 0 || removedCount > 0) {
|
|
175
|
+
await handleReconnect({ models: merged });
|
|
176
|
+
}
|
|
177
|
+
if (addedCount > 0) {
|
|
178
|
+
pushToast(`Fetched ${addedCount} new model${addedCount === 1 ? '' : 's'} from the provider.`, 'success');
|
|
179
|
+
} else if (removedCount > 0) {
|
|
180
|
+
pushToast('Removed legacy fallback models from the provider.', 'success');
|
|
181
|
+
} else {
|
|
182
|
+
pushToast('No new models found — all fetched models already exist.', 'success');
|
|
183
|
+
}
|
|
184
|
+
const note = fetchModelsNote(result);
|
|
185
|
+
if (note) pushToast(note.text, note.tone);
|
|
186
|
+
if (addedCount > 0) void syncProviderModelsToCatalog(provider.name, result.models);
|
|
187
|
+
return { omp: result.omp };
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const handleFetchModelsFromList = async () => {
|
|
191
|
+
if (!selectedProvider) return;
|
|
192
|
+
await handleFetchModels({
|
|
193
|
+
apiKey: selectedProvider.apiKey,
|
|
194
|
+
baseUrl: selectedProvider.baseUrl,
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const handleOmpAuthSuccess = () => {
|
|
199
|
+
if (!selectedProvider) return;
|
|
200
|
+
const target = selectedProvider;
|
|
201
|
+
void persistProviders(providers.map((p) => (
|
|
202
|
+
p.id === target.id ? { ...p, status: 'connected' as const } : p
|
|
203
|
+
)));
|
|
204
|
+
void toggleProviderEnabled(target.slug, true);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleToggleDisconnect = async () => {
|
|
208
|
+
if (!selectedProvider) return;
|
|
209
|
+
const target = selectedProvider;
|
|
210
|
+
const disconnecting = target.status === 'connected';
|
|
211
|
+
if (disconnecting) {
|
|
212
|
+
setSelectedProviderId(connectedProviders.find((provider) => provider.id !== target.id)?.id || '');
|
|
213
|
+
}
|
|
214
|
+
const merged = await toggleProviderEnabled(target.slug, !disconnecting);
|
|
215
|
+
if (!merged) return;
|
|
216
|
+
pushToast(
|
|
217
|
+
disconnecting
|
|
218
|
+
? `${target.name} disconnected — its models are no longer offered in the chat picker.`
|
|
219
|
+
: `${target.name} reconnected.`,
|
|
220
|
+
'success',
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const updateSelectedModels = (transform: (models: ProviderModel[]) => ProviderModel[]) => {
|
|
225
|
+
if (!selectedProvider) return;
|
|
226
|
+
void persistProviders(providers.map((p) => (
|
|
227
|
+
p.id === selectedProvider.id ? { ...p, models: transform(p.models) } : p
|
|
228
|
+
)));
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Disable/enable from omp's own config.yml `disabledProviders`. This is the
|
|
233
|
+
* coarse switch that removes the provider — and every model it serves — from
|
|
234
|
+
* the chat picker; the per-model eye toggle is the fine-grained one.
|
|
235
|
+
*/
|
|
236
|
+
const handleToggleProviderDisabled = async () => {
|
|
237
|
+
if (!selectedProvider) return;
|
|
238
|
+
const target = selectedProvider;
|
|
239
|
+
const disabling = target.disabled !== true;
|
|
240
|
+
const merged = await toggleProviderEnabled(target.slug, !disabling);
|
|
241
|
+
if (!merged) return;
|
|
242
|
+
pushToast(
|
|
243
|
+
disabling
|
|
244
|
+
? `${target.name} disabled — it no longer appears in the model list.`
|
|
245
|
+
: `${target.name} enabled — its models are offered again.`,
|
|
246
|
+
'success',
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Delete a provider's `models.yml` entry (and the chamber's overlay row).
|
|
252
|
+
*
|
|
253
|
+
* Only offered for a provider the server reports as `inModelsYml`, because
|
|
254
|
+
* the action is a file edit: a login provider's credential is untouched, and
|
|
255
|
+
* a provider with no file entry has nothing to delete. The selection moves off
|
|
256
|
+
* the deleted provider BEFORE the list is republished — leaving it selected
|
|
257
|
+
* would render a detail pane for a provider that no longer exists.
|
|
258
|
+
*/
|
|
259
|
+
const handleDeleteProvider = async () => {
|
|
260
|
+
if (!selectedProvider || isDeletingProvider) return;
|
|
261
|
+
const target = selectedProvider;
|
|
262
|
+
setIsDeletingProvider(true);
|
|
263
|
+
try {
|
|
264
|
+
const result = await deleteProvider(target.id);
|
|
265
|
+
if (!result.ok) {
|
|
266
|
+
pushToast(result.error || 'Failed to delete the provider.', 'error');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
setIsDeleteModalOpen(false);
|
|
270
|
+
const remaining = result.providers ?? [];
|
|
271
|
+
setSelectedProviderId(remaining.find((provider) => provider.status === 'connected')?.id || '');
|
|
272
|
+
setProviders(remaining);
|
|
273
|
+
// The overlay row is gone even when the file cleanup failed, so the
|
|
274
|
+
// warning is reported rather than presented as a clean delete.
|
|
275
|
+
if (result.warning) {
|
|
276
|
+
pushToast(`${target.name} removed from the chamber, but models.yml cleanup failed: ${result.warning}`, 'error');
|
|
277
|
+
} else if (result.removedFromOmp) {
|
|
278
|
+
const count = result.modelsRemoved ?? 0;
|
|
279
|
+
pushToast(
|
|
280
|
+
`${target.name} deleted from models.yml${count > 0 ? ` with its ${count} model${count === 1 ? '' : 's'}` : ''}.`,
|
|
281
|
+
'success',
|
|
282
|
+
);
|
|
283
|
+
} else {
|
|
284
|
+
pushToast(`${target.name} removed from the chamber list.`, 'success');
|
|
285
|
+
}
|
|
286
|
+
} finally {
|
|
287
|
+
setIsDeletingProvider(false);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const handleHideAll = () => updateSelectedModels(
|
|
292
|
+
(models) => models.map((m) => ({ ...m, isVisible: false })),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const handleShowAll = () => updateSelectedModels(
|
|
296
|
+
(models) => models.map((m) => ({ ...m, isVisible: true })),
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const handleToggleModelVisibility = (modelId: string) => updateSelectedModels(
|
|
300
|
+
(models) => models.map((m) => (m.id === modelId ? { ...m, isVisible: !m.isVisible } : m)),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Save the per-model knobs omp honours (`models.yml` modelOverrides). The
|
|
305
|
+
* overlay still stores the display value so the dialog reopens where the user
|
|
306
|
+
* left it, but omp only reads the override — writing just the overlay was the
|
|
307
|
+
* bug that made these controls inert.
|
|
308
|
+
*/
|
|
309
|
+
const handleSaveModelConfig = async (modelId: string, updates: Partial<ProviderModel>) => {
|
|
310
|
+
if (!selectedProvider) return;
|
|
311
|
+
const provider = selectedProvider;
|
|
312
|
+
updateSelectedModels((models) => models.map((m) => (m.id === modelId ? { ...m, ...updates } : m)));
|
|
313
|
+
|
|
314
|
+
const result = await saveModelOverrideRemote(provider.slug, modelId, updates);
|
|
315
|
+
if (!result.ok) {
|
|
316
|
+
pushToast(result.error || 'Failed to save the model configuration.', 'error');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (result.written) notifyModelsUpdated();
|
|
320
|
+
else if (result.reason) pushToast(result.reason, 'error');
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
handleAddProvider,
|
|
325
|
+
handleReconnect,
|
|
326
|
+
handleFetchModels,
|
|
327
|
+
handleFetchModelsFromList,
|
|
328
|
+
handleOmpAuthSuccess,
|
|
329
|
+
handleToggleDisconnect,
|
|
330
|
+
handleToggleProviderDisabled,
|
|
331
|
+
handleDeleteProvider,
|
|
332
|
+
handleHideAll,
|
|
333
|
+
handleShowAll,
|
|
334
|
+
handleToggleModelVisibility,
|
|
335
|
+
handleSaveModelConfig,
|
|
336
|
+
};
|
|
337
|
+
}
|