ompchamber 2.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- 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-8t70jhyj.js +5149 -0
- package/dist/client/static/js/index-n4wkqj7t.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/code-editor/handler.test.ts +274 -0
- package/src/client/components/common/code-editor/handler.ts +175 -0
- package/src/client/components/common/code-editor/index.tsx +185 -220
- package/src/client/components/common/code-surface/index.tsx +109 -12
- package/src/client/components/common/lazy-panels.tsx +5 -1
- 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/common/viewport.ts +5 -3
- package/src/client/components/layout/RightActivityBar.tsx +2 -1
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +21 -4
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +4 -0
- package/src/client/components/layout/desktop-layout/index.tsx +26 -4
- package/src/client/components/mobile/LayoutWrapper.tsx +16 -0
- package/src/client/components/mobile/RightSidebar.tsx +11 -4
- 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/browser-panel/AddressBar.tsx +1 -10
- package/src/client/components/workspace/browser-panel/Screencast.tsx +4 -5
- package/src/client/components/workspace/browser-panel/index.tsx +6 -6
- package/src/client/components/workspace/browser-toolbar/index.tsx +22 -12
- 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/chat-timeline/ToolCallCard.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Todo.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/CommandPalette.test.ts +242 -0
- package/src/client/components/workspace/editor/CommandPalette.tsx +138 -0
- package/src/client/components/workspace/editor/FindWidget.test.ts +261 -0
- package/src/client/components/workspace/editor/FindWidget.tsx +219 -0
- package/src/client/components/workspace/editor/Tabs.tsx +2 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +129 -26
- package/src/client/components/workspace/editor/index.tsx +180 -62
- package/src/client/components/workspace/file-explorer/Modals.tsx +79 -2
- package/src/client/components/workspace/file-explorer/TreeItem.tsx +43 -113
- 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/todo-panel/PhaseGroup.tsx +76 -0
- package/src/client/components/workspace/todo-panel/Row.tsx +69 -0
- package/src/client/components/workspace/todo-panel/index.tsx +161 -0
- 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/find-harness.test-util.ts +170 -0
- package/src/client/hooks/editor/scroll-container.test.ts +102 -0
- package/src/client/hooks/editor/scroll-container.ts +26 -0
- package/src/client/hooks/editor/use-code-window.ts +2 -20
- package/src/client/hooks/editor/use-editor-find-write.test.ts +170 -0
- package/src/client/hooks/editor/use-editor-find.test.ts +118 -0
- package/src/client/hooks/editor/use-editor-find.ts +321 -0
- package/src/client/hooks/editor/use-editor-history.test.ts +216 -0
- package/src/client/hooks/editor/use-editor-history.ts +134 -0
- package/src/client/hooks/editor/use-file-editor.ts +24 -27
- package/src/client/hooks/editor/use-occurrences.test.ts +236 -0
- package/src/client/hooks/editor/use-occurrences.ts +98 -0
- 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/hooks/workspace/file-tree-actions.test.ts +348 -0
- package/src/client/hooks/workspace/file-tree-actions.ts +250 -0
- package/src/client/hooks/workspace/session-todos.ts +102 -0
- package/src/client/stubs/elkjs.ts +6 -1
- package/src/client/tailwind.css +30 -0
- 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/todos.ts +100 -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/fs/action-create.test.ts +90 -0
- package/src/server/routes/fs/action.ts +23 -0
- package/src/server/routes/omp/index.ts +2 -0
- package/src/server/routes/omp/session-todos.ts +80 -0
- 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/chat/{todo-parser.ts → todo/parser.ts} +17 -0
- package/src/shared/lib/chat/todo/progress.ts +97 -0
- package/src/shared/lib/chat/todo/snapshot.test.ts +156 -0
- package/src/shared/lib/chat/todo/snapshot.ts +215 -0
- package/src/shared/lib/code/editor/commands.test.ts +155 -0
- package/src/shared/lib/code/editor/commands.ts +260 -0
- package/src/shared/lib/code/editor/edits.test.ts +100 -0
- package/src/shared/lib/code/editor/edits.ts +122 -0
- package/src/shared/lib/code/editor/find-status.test.ts +50 -0
- package/src/shared/lib/code/editor/find-status.ts +36 -0
- package/src/shared/lib/code/editor/find.test.ts +203 -0
- package/src/shared/lib/code/editor/find.ts +224 -0
- package/src/{client/components/common/code-editor → shared/lib/code/editor}/history.test.ts +1 -1
- package/src/shared/lib/code/editor/keymap.test.ts +109 -0
- package/src/shared/lib/code/editor/keymap.ts +151 -0
- package/src/shared/lib/code/editor/layers.test.ts +77 -0
- package/src/shared/lib/code/editor/layers.ts +67 -0
- package/src/shared/lib/code/editor/line-edits.test.ts +131 -0
- package/src/shared/lib/code/editor/line-edits.ts +159 -0
- package/src/shared/lib/code/editor/lines.test.ts +81 -0
- package/src/shared/lib/code/editor/lines.ts +83 -0
- package/src/shared/lib/code/editor/marks.test.ts +86 -0
- package/src/shared/lib/code/editor/marks.ts +106 -0
- package/src/shared/lib/code/editor/occurrences.test.ts +117 -0
- package/src/shared/lib/code/editor/occurrences.ts +111 -0
- package/src/shared/lib/code/editor/palette.test.ts +91 -0
- package/src/shared/lib/code/editor/palette.ts +92 -0
- package/src/shared/lib/code/editor/reset-css.test.ts +35 -0
- package/src/shared/lib/code/editor/reset-css.ts +32 -0
- package/src/shared/lib/code/editor/text-commands.test.ts +178 -0
- package/src/shared/lib/code/editor/text-commands.ts +154 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +75 -0
- package/src/shared/lib/code/syntax-highlight.ts +29 -17
- package/src/shared/lib/code/windowed-highlight.ts +52 -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/ui/key-binding.test.ts +115 -0
- package/src/shared/lib/ui/key-binding.ts +143 -0
- package/src/shared/lib/util/platform.test.ts +87 -0
- package/src/shared/lib/util/platform.ts +30 -0
- package/src/shared/lib/workspace/file-tab-id.ts +29 -0
- package/src/shared/lib/workspace/keymap.test.ts +82 -0
- package/src/shared/lib/workspace/keymap.ts +30 -0
- package/src/shared/lib/workspace/refresh-cadence.ts +9 -0
- package/src/shared/lib/workspace/right-panels.ts +8 -0
- package/src/shared/lib/workspace/session-state/store.ts +0 -1
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/settings/provider.ts +81 -0
- package/src/shared/types/settings/usage.ts +88 -14
- package/src/shared/types/todo.ts +108 -0
- 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/{client/components/common/code-editor → shared/lib/code/editor}/history.ts +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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The one CSS loader for every bundler pass Bun runs for this app.
|
|
8
|
+
*
|
|
9
|
+
* Bun's `serve.static` plugins and `Bun.build`'s `plugins` array both feed the
|
|
10
|
+
* client bundle, and both need the same two transforms — so they live in ONE
|
|
11
|
+
* plugin rather than two. `onLoad({ filter: /\.css$/ })` has a single winner per
|
|
12
|
+
* module: whichever plugin returns contents first ends the chain, so two
|
|
13
|
+
* plugins would silently starve each other (Tailwind's `@theme` blocks would
|
|
14
|
+
* survive untouched, or the fonts would keep their unresolvable urls).
|
|
15
|
+
*
|
|
16
|
+
* 1. **Tailwind v4** — `@import "tailwindcss"` and `@theme` are PostCSS syntax,
|
|
17
|
+
* not CSS. Bun's CSS parser logs `invalid @ rule encountered: '@theme'` and
|
|
18
|
+
* emits the file verbatim, which costs every utility class in the app. The
|
|
19
|
+
* project's own `@tailwindcss/postcss` (the same version the previous bundler drove)
|
|
20
|
+
* expands them here.
|
|
21
|
+
*
|
|
22
|
+
* 2. **`@font-face` extraction** — a font face names a file inside
|
|
23
|
+
* `node_modules`, and Bun's CSS loader resolves EVERY local `url()`: with the
|
|
24
|
+
* default `file` loader it inlines the font as base64 (a 592 kB stylesheet,
|
|
25
|
+
* ~377 kB gzipped, every face downloaded whether or not a formula renders)
|
|
26
|
+
* and with `dataurl` it writes the ABSOLUTE FILESYSTEM PATH into the CSS. A
|
|
27
|
+
* same-origin path is not an escape hatch either — `url("/fonts/x.woff2")`
|
|
28
|
+
* fails the build with "Could not resolve". Measured against Bun 1.4.2: only
|
|
29
|
+
* `data:`, `http(s):` and protocol-relative urls are left alone, so no
|
|
30
|
+
* rewrite inside the bundle can work.
|
|
31
|
+
*
|
|
32
|
+
* The faces therefore leave the bundle. Each block is rewritten to
|
|
33
|
+
* `<FONT_ROUTE_PREFIX><basename>` and written to its own file under
|
|
34
|
+
* `FONT_FACE_DIR`, keyed by the stylesheet it came from — a document Bun
|
|
35
|
+
* never parses, which is what lets the urls survive. The server concatenates
|
|
36
|
+
* that directory into `/fonts.css` (`lib/assets/font-css.server.ts`) and the
|
|
37
|
+
* shell links it, so the layout rules stay bundled while the faces stay
|
|
38
|
+
* cacheable files.
|
|
39
|
+
*
|
|
40
|
+
* Per-source files rather than one accumulated file because `onEnd` is NOT
|
|
41
|
+
* invoked by `serve.static` (verified: `onStart` and `onLoad` fire, `onEnd`
|
|
42
|
+
* never does), so there is no "build finished" moment to flush at. Writing
|
|
43
|
+
* each source's faces as its `onLoad` returns is order-independent and works
|
|
44
|
+
* identically under both entry points.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { basename, dirname, isAbsolute, join, resolve as resolvePath } from 'path';
|
|
48
|
+
import postcss from 'postcss';
|
|
49
|
+
import tailwind from '@tailwindcss/postcss';
|
|
50
|
+
import type { BunPlugin } from 'bun';
|
|
51
|
+
|
|
52
|
+
import { FONT_FACE_DIR } from '@/server/lib/assets/font-css.server';
|
|
53
|
+
import { FONT_ROUTE_PREFIX, packageRoot } from '@/server/lib/assets/fonts.server';
|
|
54
|
+
|
|
55
|
+
/** A `url(...)` whose target is a font file. */
|
|
56
|
+
const URL_FUNCTION = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g;
|
|
57
|
+
const FONT_FILE = /\.(?:woff2?|ttf|otf)$/i;
|
|
58
|
+
/** Schemes Bun leaves alone, and the prefix this plugin itself emits. */
|
|
59
|
+
const EXTERNAL = /^(?:data:|https?:|blob:|\/\/)/i;
|
|
60
|
+
|
|
61
|
+
/** One `@font-face` block, braces included. Faces cannot nest. */
|
|
62
|
+
const FONT_FACE = /@font-face\s*\{[^{}]*\}/g;
|
|
63
|
+
|
|
64
|
+
/** Absolute path of a local font url, or null when it is not one. */
|
|
65
|
+
function localFont(specifier: string, cssDir: string): string | null {
|
|
66
|
+
if (EXTERNAL.test(specifier) || specifier.startsWith(FONT_ROUTE_PREFIX)) return null;
|
|
67
|
+
if (!FONT_FILE.test(specifier)) return null;
|
|
68
|
+
return isAbsolute(specifier) ? specifier : resolvePath(cssDir, specifier);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Point every font `url()` at the served path. */
|
|
72
|
+
function rewriteFontUrls(css: string, cssDir: string): string {
|
|
73
|
+
return css.replace(URL_FUNCTION, (whole, quote: string, specifier: string) => {
|
|
74
|
+
const absolute = localFont(specifier, cssDir);
|
|
75
|
+
if (!absolute) return whole;
|
|
76
|
+
return `url(${quote}${FONT_ROUTE_PREFIX}${basename(absolute)}${quote})`;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Split `@font-face` blocks out of a stylesheet.
|
|
82
|
+
*
|
|
83
|
+
* Only blocks whose `src` names a local font move: a face pointing at a remote
|
|
84
|
+
* url is valid CSS the bundle can keep.
|
|
85
|
+
*/
|
|
86
|
+
function splitFontFaces(css: string, cssDir: string): { rest: string; faces: string[] } {
|
|
87
|
+
const faces: string[] = [];
|
|
88
|
+
const rest = css.replace(FONT_FACE, (block) => {
|
|
89
|
+
const hasLocal = [...block.matchAll(URL_FUNCTION)].some(([, , spec]) => localFont(spec, cssDir));
|
|
90
|
+
if (!hasLocal) return block;
|
|
91
|
+
faces.push(rewriteFontUrls(block, cssDir));
|
|
92
|
+
return '';
|
|
93
|
+
});
|
|
94
|
+
return { rest, faces };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const plugin: BunPlugin = {
|
|
98
|
+
name: 'ompchamber-css',
|
|
99
|
+
setup(build) {
|
|
100
|
+
build.onLoad({ filter: /\.css$/ }, async (args) => {
|
|
101
|
+
const source = await Bun.file(args.path).text();
|
|
102
|
+
// Tailwind first: it parses `@import "tailwindcss"` and `@theme`, and the
|
|
103
|
+
// urls it emits are the ones the extraction below has to recognise.
|
|
104
|
+
const expanded = await postcss([tailwind()]).process(source, { from: args.path });
|
|
105
|
+
const { rest, faces } = splitFontFaces(expanded.css, dirname(args.path));
|
|
106
|
+
if (faces.length > 0) {
|
|
107
|
+
const target = join(packageRoot(), FONT_FACE_DIR, `${basename(args.path)}`);
|
|
108
|
+
const header = `/* GENERATED from ${args.path} — do not edit. */\n`;
|
|
109
|
+
await Bun.write(target, header + faces.join('\n') + '\n');
|
|
110
|
+
}
|
|
111
|
+
return { contents: rest, loader: 'css' };
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default plugin;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Where the HTML shell lives in Bun's routing table.
|
|
8
|
+
*
|
|
9
|
+
* One constant for both ends: the server registers the bundle on this path and
|
|
10
|
+
* `plugins/shell.server.ts` fetches it back. Kept apart from either so the two
|
|
11
|
+
* cannot drift — a rename on one side alone would 404 the shell route and every
|
|
12
|
+
* page would answer 503.
|
|
13
|
+
*
|
|
14
|
+
* Underscored and unlinked: nothing serves a user-facing page from here, and a
|
|
15
|
+
* name outside the app's own paths cannot collide with a client route.
|
|
16
|
+
*/
|
|
17
|
+
export const SHELL_ROUTE = '/_shell';
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The model-listing probe behind the providers settings auto-fetch.
|
|
8
|
+
*
|
|
9
|
+
* A provider endpoint can be asked for its models in a handful of shapes, and
|
|
10
|
+
* which one it answers depends on the WIRE DIALECT it speaks: an
|
|
11
|
+
* Anthropic-shaped proxy rejects a Bearer header, a Gemini endpoint wants the
|
|
12
|
+
* key in the query string, and a dialect with no listing route at all (Bedrock,
|
|
13
|
+
* Vertex, Codex, the judge APIs) answers nothing. The probe therefore follows
|
|
14
|
+
* the dialect when the caller knows it, and only falls back to trying the three
|
|
15
|
+
* common styles when the endpoint is unclassified.
|
|
16
|
+
*
|
|
17
|
+
* Server-only: it performs the outbound HTTP the settings page must not.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { extractModels } from '@/shared/lib/models/remote-list';
|
|
21
|
+
import { findCatalogModel, loadModelsDevCatalog } from '@/shared/lib/models/catalog';
|
|
22
|
+
import type { OmpProviderApi } from '@/shared/types/settings/provider';
|
|
23
|
+
import type { ProviderModel } from '@/shared/types';
|
|
24
|
+
|
|
25
|
+
const FETCH_TIMEOUT_MS = 15_000;
|
|
26
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
27
|
+
/** omp's own Azure default (`DEFAULT_AZURE_API_VERSION` in pi-ai). */
|
|
28
|
+
const AZURE_API_VERSION = 'v1';
|
|
29
|
+
|
|
30
|
+
export interface FetchAttempt {
|
|
31
|
+
models: ProviderModel[] | null;
|
|
32
|
+
error: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function attemptStyle(
|
|
36
|
+
label: string,
|
|
37
|
+
url: string,
|
|
38
|
+
headers: Record<string, string>,
|
|
39
|
+
): Promise<FetchAttempt> {
|
|
40
|
+
const response = await fetch(url, { headers, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
41
|
+
let payload: unknown = null;
|
|
42
|
+
try {
|
|
43
|
+
payload = await response.json();
|
|
44
|
+
} catch {
|
|
45
|
+
// Non-JSON body (HTML error page, empty body) — payload stays null and is
|
|
46
|
+
// reported below as an unrecognized response format.
|
|
47
|
+
}
|
|
48
|
+
if (response.status < 200 || response.status >= 300) {
|
|
49
|
+
return { models: null, error: `${label}: HTTP ${response.status}` };
|
|
50
|
+
}
|
|
51
|
+
const models = extractModels(payload);
|
|
52
|
+
if (!models) return { models: null, error: `${label}: unrecognized response format` };
|
|
53
|
+
return { models, error: '' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Listing probe for one dialect, or `null` when the dialect has no listing
|
|
58
|
+
* route to probe at all. Bedrock, Vertex, Codex, Gemini-CLI and the two judge
|
|
59
|
+
* APIs are reached through non-`/models` protocols (SigV4, gcloud auth,
|
|
60
|
+
* ChatGPT's backend, `{baseUrl}/decisions`), so probing `/models` there would
|
|
61
|
+
* report a 404 as if the endpoint were broken. Those providers are registered
|
|
62
|
+
* with an empty list instead.
|
|
63
|
+
*/
|
|
64
|
+
function listingAttempts(
|
|
65
|
+
base: string,
|
|
66
|
+
apiKey: string | undefined,
|
|
67
|
+
api: OmpProviderApi,
|
|
68
|
+
): Array<{ label: string; url: string; headers: Record<string, string> }> | null {
|
|
69
|
+
switch (api) {
|
|
70
|
+
case 'anthropic-messages':
|
|
71
|
+
return [{
|
|
72
|
+
label: 'Anthropic',
|
|
73
|
+
url: `${base}/models`,
|
|
74
|
+
headers: { 'anthropic-version': ANTHROPIC_VERSION, ...(apiKey ? { 'x-api-key': apiKey } : {}) },
|
|
75
|
+
}];
|
|
76
|
+
case 'google-generative-ai':
|
|
77
|
+
return [{
|
|
78
|
+
label: 'Gemini',
|
|
79
|
+
url: apiKey ? `${base}/models?key=${encodeURIComponent(apiKey)}` : `${base}/models`,
|
|
80
|
+
headers: {},
|
|
81
|
+
}];
|
|
82
|
+
case 'openai-completions':
|
|
83
|
+
case 'openai-responses':
|
|
84
|
+
return [{
|
|
85
|
+
label: 'OpenAI-compatible',
|
|
86
|
+
url: `${base}/models`,
|
|
87
|
+
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
88
|
+
}];
|
|
89
|
+
case 'azure-openai-responses':
|
|
90
|
+
// Azure never takes a bearer: a string key is an `api-key` header, and
|
|
91
|
+
// the api version rides as a query parameter (omp's own Azure client
|
|
92
|
+
// builds the request the same way). A Bearer probe there answers 401 and
|
|
93
|
+
// reads as a bad key.
|
|
94
|
+
return [{
|
|
95
|
+
label: 'Azure OpenAI',
|
|
96
|
+
url: `${base}/models?api-version=${AZURE_API_VERSION}`,
|
|
97
|
+
headers: apiKey ? { 'api-key': apiKey } : {},
|
|
98
|
+
}];
|
|
99
|
+
default:
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Try the styles a DIALECT implies, in order. An explicit dialect from the
|
|
106
|
+
* request is authoritative — probing an Anthropic endpoint with a Bearer header
|
|
107
|
+
* would fail with a 401 that reads like a bad key. With no dialect the endpoint
|
|
108
|
+
* is unclassified, so the three styles are tried and the first recognizable
|
|
109
|
+
* list wins, which is what makes the picker work for a gateway nobody named.
|
|
110
|
+
*
|
|
111
|
+
* A dialect with no listing route yields an empty list and no error: the
|
|
112
|
+
* provider is still registered, it just cannot be enumerated from here.
|
|
113
|
+
*/
|
|
114
|
+
export async function fetchRemoteModels(
|
|
115
|
+
baseUrl: string,
|
|
116
|
+
apiKey: string | undefined,
|
|
117
|
+
api: OmpProviderApi | undefined,
|
|
118
|
+
): Promise<FetchAttempt> {
|
|
119
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
120
|
+
const attempts: Array<{ label: string; url: string; headers: Record<string, string> }> = [];
|
|
121
|
+
if (api) {
|
|
122
|
+
const dialectAttempts = listingAttempts(base, apiKey, api);
|
|
123
|
+
if (!dialectAttempts) return { models: [], error: '' };
|
|
124
|
+
attempts.push(...dialectAttempts);
|
|
125
|
+
} else {
|
|
126
|
+
attempts.push({
|
|
127
|
+
label: 'OpenAI-compatible',
|
|
128
|
+
url: `${base}/models`,
|
|
129
|
+
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
130
|
+
});
|
|
131
|
+
if (apiKey) {
|
|
132
|
+
attempts.push({
|
|
133
|
+
label: 'Anthropic',
|
|
134
|
+
url: `${base}/models`,
|
|
135
|
+
headers: { 'x-api-key': apiKey, 'anthropic-version': ANTHROPIC_VERSION },
|
|
136
|
+
});
|
|
137
|
+
attempts.push({
|
|
138
|
+
label: 'Gemini',
|
|
139
|
+
url: `${base}/models?key=${encodeURIComponent(apiKey)}`,
|
|
140
|
+
headers: {},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const errors: string[] = [];
|
|
146
|
+
for (const attempt of attempts) {
|
|
147
|
+
try {
|
|
148
|
+
const result = await attemptStyle(attempt.label, attempt.url, attempt.headers);
|
|
149
|
+
if (result.models) return result;
|
|
150
|
+
errors.push(result.error);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
// Network-level failure (DNS, refused, timeout) applies to every auth
|
|
153
|
+
// style — no point retrying the same host with different headers.
|
|
154
|
+
const message = error instanceof Error ? error.message : 'request failed';
|
|
155
|
+
errors.push(`${attempt.label}: ${message}`);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
models: null,
|
|
161
|
+
error: errors.length > 0 ? `Failed to fetch models — ${errors.join(' · ')}` : 'Failed to fetch models',
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Fill in metadata the listing endpoint did not provide from the models.dev
|
|
167
|
+
* catalog: vision/reasoning/tool flags, context + output window, and
|
|
168
|
+
* per-1M-token pricing (cache rates included). Only empty fields are filled —
|
|
169
|
+
* anything the provider reported itself is kept.
|
|
170
|
+
*/
|
|
171
|
+
export async function enrichFromCatalog(
|
|
172
|
+
models: ProviderModel[],
|
|
173
|
+
providerSlug: string | undefined,
|
|
174
|
+
): Promise<ProviderModel[]> {
|
|
175
|
+
const catalog = await loadModelsDevCatalog();
|
|
176
|
+
if (!catalog || Object.keys(catalog).length === 0) return models;
|
|
177
|
+
return models.map((model) => {
|
|
178
|
+
const match = findCatalogModel(catalog, providerSlug, model.id);
|
|
179
|
+
if (!match) return model;
|
|
180
|
+
const { info } = match;
|
|
181
|
+
const ctx = info.limit?.context;
|
|
182
|
+
const out = info.limit?.output;
|
|
183
|
+
const ctxLabel = !model.contextWindow && ctx && ctx > 0
|
|
184
|
+
? Math.round(ctx / 1000)
|
|
185
|
+
: null;
|
|
186
|
+
const outLabel = out && out > 0 ? Math.round(out / 1000) : null;
|
|
187
|
+
const catalogContext = ctxLabel
|
|
188
|
+
? (outLabel ? `${ctxLabel}K ctx · ${outLabel}K out` : `${ctxLabel}K ctx`)
|
|
189
|
+
: '';
|
|
190
|
+
return {
|
|
191
|
+
...model,
|
|
192
|
+
name: model.name === model.id && info.name ? info.name : model.name,
|
|
193
|
+
contextWindow: model.contextWindow || catalogContext,
|
|
194
|
+
hasVision: model.hasVision || info.attachment === true,
|
|
195
|
+
hasReasoning: model.hasReasoning ?? (info.reasoning === true || undefined),
|
|
196
|
+
hasTools: model.hasTools || info.tool_call === true,
|
|
197
|
+
priceInput: model.priceInput ?? (typeof info.cost?.input === 'number' ? info.cost.input : undefined),
|
|
198
|
+
priceOutput: model.priceOutput ?? (typeof info.cost?.output === 'number' ? info.cost.output : undefined),
|
|
199
|
+
priceCacheRead: model.priceCacheRead
|
|
200
|
+
?? (typeof info.cost?.cache_read === 'number' ? info.cost.cache_read : undefined),
|
|
201
|
+
priceCacheWrite: model.priceCacheWrite
|
|
202
|
+
?? (typeof info.cost?.cache_write === 'number' ? info.cost.cache_write : undefined),
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* omp's models.yml schema requires all four cost fields when cost is present,
|
|
209
|
+
* so a price is only seeded when both sides are known; missing ones default
|
|
210
|
+
* to 0. Context/output labels ("128K ctx · 16K out") parse back to tokens.
|
|
211
|
+
* Cache prices come from the catalog and are kept when known — dropping them
|
|
212
|
+
* makes every cached turn look free in omp's usage accounting.
|
|
213
|
+
*/
|
|
214
|
+
export function toOmpSeed(model: ProviderModel): {
|
|
215
|
+
id: string;
|
|
216
|
+
name?: string;
|
|
217
|
+
reasoning?: boolean;
|
|
218
|
+
imageInput?: boolean;
|
|
219
|
+
contextWindow?: number;
|
|
220
|
+
maxTokens?: number;
|
|
221
|
+
cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
222
|
+
} {
|
|
223
|
+
const parseTokens = (label: string, suffix: string): number | undefined => {
|
|
224
|
+
const match = label.match(new RegExp(`(\\d+(?:\\.\\d+)?)([KM])? ${suffix}`));
|
|
225
|
+
if (!match) return undefined;
|
|
226
|
+
const value = Number.parseFloat(match[1]);
|
|
227
|
+
if (!Number.isFinite(value) || value <= 0) return undefined;
|
|
228
|
+
const multiplier = match[2] === 'M' ? 1_000_000 : 1000;
|
|
229
|
+
return Math.round(value * multiplier);
|
|
230
|
+
};
|
|
231
|
+
const contextWindow = parseTokens(model.contextWindow, 'ctx');
|
|
232
|
+
const outTokens = parseTokens(model.contextWindow, 'out');
|
|
233
|
+
// omp rejects the whole file on a non-finite number, so a price has to be a
|
|
234
|
+
// real finite value before it may be written at all.
|
|
235
|
+
const price = (value: unknown): number | undefined => (
|
|
236
|
+
typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
237
|
+
);
|
|
238
|
+
const priceInput = price(model.priceInput);
|
|
239
|
+
const priceOutput = price(model.priceOutput);
|
|
240
|
+
const hasCost = priceInput !== undefined && priceOutput !== undefined;
|
|
241
|
+
return {
|
|
242
|
+
id: model.id,
|
|
243
|
+
...(model.name && model.name !== model.id ? { name: model.name } : {}),
|
|
244
|
+
...(model.hasReasoning ? { reasoning: true } : {}),
|
|
245
|
+
...(model.hasVision ? { imageInput: true } : {}),
|
|
246
|
+
...(contextWindow ? { contextWindow } : {}),
|
|
247
|
+
...(outTokens && outTokens > 0 ? { maxTokens: Math.round(outTokens) } : {}),
|
|
248
|
+
...(hasCost ? {
|
|
249
|
+
cost: {
|
|
250
|
+
input: priceInput,
|
|
251
|
+
output: priceOutput,
|
|
252
|
+
cacheRead: price(model.priceCacheRead) ?? 0,
|
|
253
|
+
cacheWrite: price(model.priceCacheWrite) ?? 0,
|
|
254
|
+
},
|
|
255
|
+
} : {}),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The provider merge, whose dialect and credential-source fields decide what
|
|
8
|
+
* the settings UI offers.
|
|
9
|
+
*
|
|
10
|
+
* Two behaviours are locked here because both were silent when wrong:
|
|
11
|
+
*
|
|
12
|
+
* - A provider that exists BOTH as an omp login provider and as a models.yml
|
|
13
|
+
* entry must be reported as `models.yml`: that is where its endpoint and key
|
|
14
|
+
* live, so it is edited in place. Reporting `omp-auth` sent a keyless local
|
|
15
|
+
* server into an OAuth flow it can never complete.
|
|
16
|
+
* - Dialect fields come from whichever side knows them, so a merged entry keeps
|
|
17
|
+
* the wire api omp will actually use.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, expect, test } from 'bun:test';
|
|
21
|
+
|
|
22
|
+
import { deduplicateProviderItems } from '@/server/lib/models/provider-registry.server';
|
|
23
|
+
import type { ProviderItem } from '@/shared/types/settings/provider';
|
|
24
|
+
|
|
25
|
+
function item(overrides: Partial<ProviderItem> & { slug: string }): ProviderItem {
|
|
26
|
+
return {
|
|
27
|
+
id: `id-${overrides.slug}`,
|
|
28
|
+
name: overrides.slug,
|
|
29
|
+
icon: overrides.slug,
|
|
30
|
+
status: 'disconnected',
|
|
31
|
+
configuredIn: 'x',
|
|
32
|
+
models: [],
|
|
33
|
+
...overrides,
|
|
34
|
+
} as ProviderItem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('deduplicateProviderItems', () => {
|
|
38
|
+
test('a models.yml entry wins the credential source over the login half', () => {
|
|
39
|
+
const merged = deduplicateProviderItems([
|
|
40
|
+
item({ slug: 'lm-studio', id: 'omp-auth-lm-studio', credentialSource: 'omp-auth' }),
|
|
41
|
+
item({
|
|
42
|
+
slug: 'lm-studio',
|
|
43
|
+
id: 'omp-native-lm-studio',
|
|
44
|
+
credentialSource: 'models.yml',
|
|
45
|
+
status: 'connected',
|
|
46
|
+
baseUrl: 'http://127.0.0.1:1234/v1',
|
|
47
|
+
api: 'openai-completions',
|
|
48
|
+
auth: 'none',
|
|
49
|
+
discovery: 'lm-studio',
|
|
50
|
+
}),
|
|
51
|
+
]);
|
|
52
|
+
expect(merged).toHaveLength(1);
|
|
53
|
+
expect(merged[0].credentialSource).toBe('models.yml');
|
|
54
|
+
expect(merged[0].auth).toBe('none');
|
|
55
|
+
expect(merged[0].discovery).toBe('lm-studio');
|
|
56
|
+
expect(merged[0].api).toBe('openai-completions');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('the login provider alone keeps its own credential source', () => {
|
|
60
|
+
const merged = deduplicateProviderItems([
|
|
61
|
+
item({ slug: 'anthropic', id: 'omp-auth-anthropic', credentialSource: 'omp-auth', status: 'connected' }),
|
|
62
|
+
]);
|
|
63
|
+
expect(merged[0].credentialSource).toBe('omp-auth');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('a disabled provider stays disabled through the merge', () => {
|
|
67
|
+
// Disable is a union: the auth half and the native half see different
|
|
68
|
+
// config.yml sets, so a flag on either side has to survive.
|
|
69
|
+
const merged = deduplicateProviderItems([
|
|
70
|
+
item({ slug: 'groq', id: 'omp-auth-groq', status: 'connected' }),
|
|
71
|
+
item({ slug: 'groq', id: 'omp-disabled-groq', disabled: true, status: 'disconnected' }),
|
|
72
|
+
]);
|
|
73
|
+
expect(merged[0].disabled).toBe(true);
|
|
74
|
+
expect(merged[0].status).toBe('disconnected');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('inModelsYml', () => {
|
|
79
|
+
test('a models.yml entry is flagged, a login provider is not', () => {
|
|
80
|
+
// The flag gates a destructive button: only a provider with a file entry
|
|
81
|
+
// has something to delete.
|
|
82
|
+
const merged = deduplicateProviderItems([
|
|
83
|
+
item({ slug: 'native', id: 'omp-native-native', inModelsYml: true }),
|
|
84
|
+
item({ slug: 'anthropic', id: 'omp-auth-anthropic' }),
|
|
85
|
+
]);
|
|
86
|
+
expect(merged.find((p) => p.slug === 'native')?.inModelsYml).toBe(true);
|
|
87
|
+
expect(merged.find((p) => p.slug === 'anthropic')?.inModelsYml).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('a login provider that ALSO has a models.yml entry is flagged', () => {
|
|
91
|
+
// This is the case the old DELETE handler missed: the merged entry carries
|
|
92
|
+
// both halves, and only the native half knows the file holds it.
|
|
93
|
+
const merged = deduplicateProviderItems([
|
|
94
|
+
item({ slug: 'openai', id: 'omp-auth-openai', credentialSource: 'omp-auth' }),
|
|
95
|
+
item({ slug: 'openai', id: 'omp-native-openai', inModelsYml: true, credentialSource: 'models.yml' }),
|
|
96
|
+
]);
|
|
97
|
+
expect(merged).toHaveLength(1);
|
|
98
|
+
expect(merged[0].inModelsYml).toBe(true);
|
|
99
|
+
expect(merged[0].credentialSource).toBe('models.yml');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('inModelsYml is stamped from the file, not the supplying half', () => {
|
|
104
|
+
test('a stale overlay row for a file-backed slug still gets the flag', () => {
|
|
105
|
+
// Reproduced live: `keepme` existed as an overlay row while models.yml held
|
|
106
|
+
// a different provider, and the item came back with no flag at all — so the
|
|
107
|
+
// delete button was missing for a provider the file did hold. The flag must
|
|
108
|
+
// come from the file's own slug set.
|
|
109
|
+
const merged = deduplicateProviderItems([
|
|
110
|
+
item({ slug: 'keepme', id: 'omp-native-keepme', inModelsYml: true }),
|
|
111
|
+
]);
|
|
112
|
+
// The merge itself preserves the flag...
|
|
113
|
+
expect(merged[0].inModelsYml).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('an entry with no native half carries no flag', () => {
|
|
117
|
+
const merged = deduplicateProviderItems([
|
|
118
|
+
item({ slug: 'ghost', id: 'omp-native-ghost', credentialSource: 'models.yml' }),
|
|
119
|
+
]);
|
|
120
|
+
expect(merged[0].inModelsYml).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
import { readDisabledProviders } from '@/server/lib/omp/config/disabled-providers';
|
|
15
15
|
import { getModelsConfigPath, readNativeProviders } from '@/server/lib/omp/config/models-config';
|
|
16
16
|
import { runUtilityCommand, type OmpModel } from '@/server/lib/omp/rpc/utility';
|
|
17
|
-
import { isKenariProvider, removeLegacyKenariModels } from '@/shared/lib/models/provider
|
|
17
|
+
import { isKenariProvider, removeLegacyKenariModels } from '@/shared/lib/models/provider/cleanup';
|
|
18
|
+
import {
|
|
19
|
+
isOmpProviderApi,
|
|
20
|
+
isProviderAuthMode,
|
|
21
|
+
isProviderDiscoveryType,
|
|
22
|
+
} from '@/shared/lib/models/provider/dialect';
|
|
18
23
|
import { formatContextWindow } from '@/shared/lib/code/format';
|
|
19
24
|
import type { ProviderItem } from '@/shared/types';
|
|
20
25
|
|
|
@@ -90,10 +95,11 @@ async function loadRpcProviderItems(): Promise<ProviderItem[]> {
|
|
|
90
95
|
id: `omp-auth-${provider.id}`,
|
|
91
96
|
name: provider.name,
|
|
92
97
|
slug: provider.id,
|
|
93
|
-
icon:
|
|
98
|
+
icon: provider.id,
|
|
94
99
|
status: (provider.authenticated && !isDisabled ? 'connected' : 'disconnected') as ProviderItem['status'],
|
|
95
100
|
disabled: isDisabled,
|
|
96
101
|
configuredIn: 'omp auth credentials',
|
|
102
|
+
credentialSource: 'omp-auth' as const,
|
|
97
103
|
models: providerModels.map((model) => ({
|
|
98
104
|
id: model.id,
|
|
99
105
|
name: typeof model.name === 'string' && model.name.length > 0 ? model.name : model.id,
|
|
@@ -122,7 +128,7 @@ function disabledProviderItem(slug: string): ProviderItem {
|
|
|
122
128
|
id: `omp-disabled-${slug}`,
|
|
123
129
|
name: slug,
|
|
124
130
|
slug,
|
|
125
|
-
icon:
|
|
131
|
+
icon: slug,
|
|
126
132
|
status: 'disconnected',
|
|
127
133
|
disabled: true,
|
|
128
134
|
configuredIn: 'omp disabledProviders',
|
|
@@ -135,19 +141,25 @@ function disabledProviderItem(slug: string): ProviderItem {
|
|
|
135
141
|
* omp's own stores — chamber only surfaces registration info.
|
|
136
142
|
*/
|
|
137
143
|
function nativeProviderItem(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
info: {
|
|
145
|
+
slug: string;
|
|
146
|
+
baseUrl?: string;
|
|
147
|
+
api?: string;
|
|
148
|
+
auth?: string;
|
|
149
|
+
discovery?: string;
|
|
150
|
+
models: Array<{
|
|
151
|
+
id: string;
|
|
152
|
+
name?: string;
|
|
153
|
+
contextWindow?: number;
|
|
154
|
+
maxTokens?: number;
|
|
155
|
+
reasoning?: boolean;
|
|
156
|
+
imageInput?: boolean;
|
|
157
|
+
}>;
|
|
158
|
+
},
|
|
148
159
|
isDisabled = false,
|
|
149
160
|
): ProviderItem {
|
|
150
|
-
const
|
|
161
|
+
const { slug, baseUrl } = info;
|
|
162
|
+
const models = info.models.map((model) => ({
|
|
151
163
|
id: model.id,
|
|
152
164
|
name: model.name || model.id,
|
|
153
165
|
contextWindow: model.contextWindow
|
|
@@ -163,10 +175,27 @@ function nativeProviderItem(
|
|
|
163
175
|
id: `omp-native-${slug}`,
|
|
164
176
|
name: slug,
|
|
165
177
|
slug,
|
|
166
|
-
icon:
|
|
178
|
+
icon: slug,
|
|
167
179
|
status: isDisabled ? 'disconnected' : 'connected',
|
|
168
180
|
disabled: isDisabled,
|
|
169
181
|
configuredIn: baseUrl ? `models.yml · ${baseUrl}` : 'models.yml',
|
|
182
|
+
credentialSource: 'models.yml' as const,
|
|
183
|
+
// This entry was built FROM models.yml, so the file holds it by definition.
|
|
184
|
+
inModelsYml: true,
|
|
185
|
+
// The dialect is what makes a native entry legible in the settings UI: an
|
|
186
|
+
// Anthropic-shaped proxy and an OpenAI one look identical without it, and
|
|
187
|
+
// the keyless/auth choice decides whether a missing credential is a bug.
|
|
188
|
+
...(isOmpProviderApi(info.api) ? { api: info.api } : {}),
|
|
189
|
+
...(isProviderAuthMode(info.auth) ? { auth: info.auth } : {}),
|
|
190
|
+
...(isProviderDiscoveryType(info.discovery) ? { discovery: info.discovery } : {}),
|
|
191
|
+
// The registry can tell the two shapes apart without guessing: a
|
|
192
|
+
// discovery block means omp owns the list, and no models at all under a
|
|
193
|
+
// configured endpoint means this is an override for a bundled provider.
|
|
194
|
+
modelSource: isProviderDiscoveryType(info.discovery)
|
|
195
|
+
? 'discovery'
|
|
196
|
+
: info.models.length === 0 && Boolean(baseUrl)
|
|
197
|
+
? 'override'
|
|
198
|
+
: 'fetch',
|
|
170
199
|
models: removeLegacyKenariModels({ name: slug, slug, baseUrl }, models),
|
|
171
200
|
};
|
|
172
201
|
}
|
|
@@ -209,6 +238,22 @@ function mergeProviderItems(existing: ProviderItem, incoming: ProviderItem): Pro
|
|
|
209
238
|
disabled,
|
|
210
239
|
baseUrl: primary.baseUrl || secondary.baseUrl,
|
|
211
240
|
apiKey: primary.apiKey || secondary.apiKey,
|
|
241
|
+
// A models.yml entry means the endpoint and key live in that file, so the
|
|
242
|
+
// provider is edited in place; the login-provider half of the merge only
|
|
243
|
+
// says omp KNOWS the id. Preferring `omp-auth` here is what sent a keyless
|
|
244
|
+
// local server into an OAuth flow it can never complete.
|
|
245
|
+
credentialSource: primary.credentialSource === 'models.yml' || secondary.credentialSource === 'models.yml'
|
|
246
|
+
? 'models.yml'
|
|
247
|
+
: primary.credentialSource ?? secondary.credentialSource,
|
|
248
|
+
// A union, not a winner-takes-all: the two halves see different files
|
|
249
|
+
// (auth sees login providers, native sees models.yml), and the flag decides
|
|
250
|
+
// whether the delete button may appear at all.
|
|
251
|
+
inModelsYml: primary.inModelsYml === true || secondary.inModelsYml === true,
|
|
252
|
+
// Dialect fields come from whichever side knows them: the auth source
|
|
253
|
+
// never carries them, the native models.yml source always does.
|
|
254
|
+
api: primary.api ?? secondary.api,
|
|
255
|
+
auth: primary.auth ?? secondary.auth,
|
|
256
|
+
discovery: primary.discovery ?? secondary.discovery,
|
|
212
257
|
configuredIn: primary.apiKey || primary.baseUrl ? primary.configuredIn : secondary.configuredIn,
|
|
213
258
|
models,
|
|
214
259
|
};
|
|
@@ -269,12 +314,7 @@ export async function mergeProviders(custom: ProviderItem[]): Promise<{ provider
|
|
|
269
314
|
const disabled = await readDisabledProviders().catch(() => new Set<string>());
|
|
270
315
|
const authItems = await loadRpcProviderItems();
|
|
271
316
|
const native = await readNativeProviders();
|
|
272
|
-
const nativeItems = native.map((info) => nativeProviderItem(
|
|
273
|
-
info.slug,
|
|
274
|
-
info.baseUrl,
|
|
275
|
-
info.models,
|
|
276
|
-
disabled.has(info.slug),
|
|
277
|
-
));
|
|
317
|
+
const nativeItems = native.map((info) => nativeProviderItem(info, disabled.has(info.slug)));
|
|
278
318
|
const disabledItems = [...disabled]
|
|
279
319
|
.filter((slug) => !nativeItems.some((n) => n.slug === slug) && !authItems.some((a) => a.slug === slug))
|
|
280
320
|
.map(disabledProviderItem);
|
|
@@ -283,11 +323,22 @@ export async function mergeProviders(custom: ProviderItem[]): Promise<{ provider
|
|
|
283
323
|
custom,
|
|
284
324
|
);
|
|
285
325
|
const nativeSlugs = new Set(registryItems.map((provider) => provider.slug.toLowerCase()));
|
|
326
|
+
// `inModelsYml` is stamped from the FILE's own slug set, not from whichever
|
|
327
|
+
// source supplied the entry. A provider can be described by the auth half or
|
|
328
|
+
// by a stale overlay row while its models.yml entry is what a delete would
|
|
329
|
+
// remove — deriving the flag from the supplying half left those entries with
|
|
330
|
+
// no delete button even though the file held them.
|
|
331
|
+
const modelsYmlSlugs = new Set(native.map((info) => info.slug.toLowerCase()));
|
|
332
|
+
const providers = deduplicateProviderItems([
|
|
333
|
+
...registryItems,
|
|
334
|
+
...custom.filter((p) => !nativeSlugs.has(p.slug.toLowerCase())),
|
|
335
|
+
]).map((provider) => (
|
|
336
|
+
modelsYmlSlugs.has(provider.slug.trim().toLowerCase())
|
|
337
|
+
? { ...provider, inModelsYml: true }
|
|
338
|
+
: provider
|
|
339
|
+
));
|
|
286
340
|
return {
|
|
287
|
-
providers
|
|
288
|
-
...registryItems,
|
|
289
|
-
...custom.filter((p) => !nativeSlugs.has(p.slug.toLowerCase())),
|
|
290
|
-
]),
|
|
341
|
+
providers,
|
|
291
342
|
modelsConfigPath: await getModelsConfigPath(),
|
|
292
343
|
};
|
|
293
344
|
}
|