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 whitespace toggle's contract. Trimming the displayed text left a
|
|
8
|
+
* whitespace-only rewrite painted red/green and counted in `+n/-n`, so the
|
|
9
|
+
* toggle changed the glyphs and nothing else — these cases pin the behavior a
|
|
10
|
+
* reader actually expects from it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, expect, test } from 'bun:test';
|
|
14
|
+
|
|
15
|
+
import { parseUnifiedDiff } from '@/shared/lib/fs/diff-parser';
|
|
16
|
+
|
|
17
|
+
const WHITESPACE_ONLY = [
|
|
18
|
+
'diff --git a/ws.ts b/ws.ts',
|
|
19
|
+
'index 46378b4..c824424 100644',
|
|
20
|
+
'--- a/ws.ts',
|
|
21
|
+
'+++ b/ws.ts',
|
|
22
|
+
'@@ -1,3 +1,3 @@',
|
|
23
|
+
' function ws() {',
|
|
24
|
+
'- return 1;',
|
|
25
|
+
'+ return 1;',
|
|
26
|
+
' }',
|
|
27
|
+
'',
|
|
28
|
+
].join('\n');
|
|
29
|
+
|
|
30
|
+
const REAL_CHANGE = [
|
|
31
|
+
'diff --git a/main.ts b/main.ts',
|
|
32
|
+
'--- a/main.ts',
|
|
33
|
+
'+++ b/main.ts',
|
|
34
|
+
'@@ -1,2 +1,2 @@',
|
|
35
|
+
' export const a = 1;',
|
|
36
|
+
'-export function f() { return a; }',
|
|
37
|
+
'+export function f() { return a + 1; }',
|
|
38
|
+
'',
|
|
39
|
+
].join('\n');
|
|
40
|
+
|
|
41
|
+
const MIXED = [
|
|
42
|
+
'diff --git a/mixed.ts b/mixed.ts',
|
|
43
|
+
'--- a/mixed.ts',
|
|
44
|
+
'+++ b/mixed.ts',
|
|
45
|
+
'@@ -1,3 +1,3 @@',
|
|
46
|
+
'- const a = 1;',
|
|
47
|
+
'+ const a = 1;',
|
|
48
|
+
'- const b = 2;',
|
|
49
|
+
'+ const b = 3;',
|
|
50
|
+
'',
|
|
51
|
+
].join('\n');
|
|
52
|
+
|
|
53
|
+
describe('parseUnifiedDiff', () => {
|
|
54
|
+
test('counts and marks a whitespace-only rewrite by default', () => {
|
|
55
|
+
const { lines, additions, deletions } = parseUnifiedDiff(WHITESPACE_ONLY);
|
|
56
|
+
|
|
57
|
+
expect(additions).toBe(1);
|
|
58
|
+
expect(deletions).toBe(1);
|
|
59
|
+
expect(lines.filter((l) => l.type === 'add')).toHaveLength(1);
|
|
60
|
+
expect(lines.filter((l) => l.type === 'del')).toHaveLength(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('folds a whitespace-only rewrite into context when ignoring whitespace', () => {
|
|
64
|
+
const { lines, splitRows, additions, deletions } = parseUnifiedDiff(WHITESPACE_ONLY, true);
|
|
65
|
+
|
|
66
|
+
// The counts the toolbar renders must match what is on screen.
|
|
67
|
+
expect(additions).toBe(0);
|
|
68
|
+
expect(deletions).toBe(0);
|
|
69
|
+
expect(lines.some((l) => l.type === 'add' || l.type === 'del')).toBe(false);
|
|
70
|
+
|
|
71
|
+
const folded = lines.find((l) => l.type === 'context' && l.oldLineNumber === 2);
|
|
72
|
+
expect(folded?.newLineNumber).toBe(2);
|
|
73
|
+
expect(folded?.text).toBe(' return 1;');
|
|
74
|
+
|
|
75
|
+
// Split view reads the same decision — it is built by the same flush.
|
|
76
|
+
const row = splitRows.find((r) => r.left?.lineNumber === 2);
|
|
77
|
+
expect(row?.left?.type).toBe('context');
|
|
78
|
+
expect(row?.right?.type).toBe('context');
|
|
79
|
+
expect(row?.left?.text).toBe(' return 1;');
|
|
80
|
+
expect(row?.right?.text).toBe(' return 1;');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('keeps a real change when ignoring whitespace', () => {
|
|
84
|
+
const { lines, additions, deletions } = parseUnifiedDiff(REAL_CHANGE, true);
|
|
85
|
+
|
|
86
|
+
expect(additions).toBe(1);
|
|
87
|
+
expect(deletions).toBe(1);
|
|
88
|
+
expect(lines.find((l) => l.type === 'del')?.text).toBe('export function f() { return a; }');
|
|
89
|
+
expect(lines.find((l) => l.type === 'add')?.text).toBe('export function f() { return a + 1; }');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('folds only the whitespace pair of a mixed hunk', () => {
|
|
93
|
+
const { lines, additions, deletions } = parseUnifiedDiff(MIXED, true);
|
|
94
|
+
|
|
95
|
+
// Line 1 differs by indentation only; line 2 is a genuine edit.
|
|
96
|
+
expect(additions).toBe(1);
|
|
97
|
+
expect(deletions).toBe(1);
|
|
98
|
+
expect(lines.filter((l) => l.type === 'del').map((l) => l.text)).toEqual([' const b = 2;']);
|
|
99
|
+
expect(lines.filter((l) => l.type === 'add').map((l) => l.text)).toEqual([' const b = 3;']);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('emits unified lines in diff order (deletions before additions)', () => {
|
|
103
|
+
const { lines } = parseUnifiedDiff(REAL_CHANGE);
|
|
104
|
+
const kinds = lines.map((l) => l.type);
|
|
105
|
+
|
|
106
|
+
// A git diff ends with a newline, which `split` turns into one trailing
|
|
107
|
+
// empty context line — pre-existing, and the same row the view has always
|
|
108
|
+
// drawn under the last change.
|
|
109
|
+
expect(kinds.slice(0, 4)).toEqual(['meta', 'context', 'del', 'add']);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('returns empty results for an empty diff', () => {
|
|
113
|
+
expect(parseUnifiedDiff('', true)).toEqual({ lines: [], splitRows: [], additions: 0, deletions: 0 });
|
|
114
|
+
expect(parseUnifiedDiff(' \n', false)).toEqual({ lines: [], splitRows: [], additions: 0, deletions: 0 });
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -6,20 +6,31 @@ export interface DiffLine {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export interface SplitDiffRow {
|
|
9
|
-
left?: {
|
|
10
|
-
|
|
11
|
-
text: string;
|
|
12
|
-
lineNumber: number;
|
|
13
|
-
};
|
|
14
|
-
right?: {
|
|
15
|
-
type: 'add' | 'context';
|
|
16
|
-
text: string;
|
|
17
|
-
lineNumber: number;
|
|
18
|
-
};
|
|
9
|
+
left?: { type: 'del' | 'context'; text: string; lineNumber: number };
|
|
10
|
+
right?: { type: 'add' | 'context'; text: string; lineNumber: number };
|
|
19
11
|
isMeta?: boolean;
|
|
20
12
|
metaText?: string;
|
|
21
13
|
}
|
|
22
14
|
|
|
15
|
+
/** A buffered `-`/`+` line, before it is decided whether it is a real change. */
|
|
16
|
+
interface PendingChange {
|
|
17
|
+
text: string;
|
|
18
|
+
lineNumber: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse a unified diff into the flat line list (Unified view) and the paired
|
|
23
|
+
* row list (Split view), plus the addition/deletion counts the toolbar shows.
|
|
24
|
+
*
|
|
25
|
+
* Both outputs are produced by the same flush, so a decision made here cannot
|
|
26
|
+
* land in one view and not the other.
|
|
27
|
+
*
|
|
28
|
+
* With `ignoreWhitespace`, a `-`/`+` pair whose text differs only in whitespace
|
|
29
|
+
* is folded back into context: the change is still in the file, but the reader
|
|
30
|
+
* asked not to be shown formatting noise. Merely trimming the displayed text —
|
|
31
|
+
* which is what this used to do — left the row painted red/green and counted in
|
|
32
|
+
* `+n/-n`, so the toggle changed the glyphs and nothing else.
|
|
33
|
+
*/
|
|
23
34
|
export function parseUnifiedDiff(rawDiff: string, ignoreWhitespace: boolean = false): {
|
|
24
35
|
lines: DiffLine[];
|
|
25
36
|
splitRows: SplitDiffRow[];
|
|
@@ -44,16 +55,60 @@ export function parseUnifiedDiff(rawDiff: string, ignoreWhitespace: boolean = fa
|
|
|
44
55
|
let oldCounter = 1;
|
|
45
56
|
let newCounter = 1;
|
|
46
57
|
|
|
47
|
-
//
|
|
48
|
-
let delBuffer:
|
|
49
|
-
let addBuffer:
|
|
58
|
+
// Consecutive deletions and additions, paired up in split view.
|
|
59
|
+
let delBuffer: PendingChange[] = [];
|
|
60
|
+
let addBuffer: PendingChange[] = [];
|
|
50
61
|
|
|
51
62
|
const flushBuffers = () => {
|
|
52
63
|
const maxLen = Math.max(delBuffer.length, addBuffer.length);
|
|
64
|
+
// A whitespace-only rewrite is decided per pair, but the flat line list
|
|
65
|
+
// keeps unified-diff order (every `-` of the group, then every `+`), which
|
|
66
|
+
// is what the original loop produced as it walked the raw diff.
|
|
67
|
+
const folded = new Array<boolean>(maxLen).fill(false);
|
|
68
|
+
if (ignoreWhitespace) {
|
|
69
|
+
for (let i = 0; i < maxLen; i++) {
|
|
70
|
+
const del = delBuffer[i];
|
|
71
|
+
const add = addBuffer[i];
|
|
72
|
+
folded[i] = Boolean(del && add && del.text.trim() === add.text.trim());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < delBuffer.length; i++) {
|
|
77
|
+
const del = delBuffer[i];
|
|
78
|
+
const add = addBuffer[i];
|
|
79
|
+
if (folded[i] && add) {
|
|
80
|
+
// Show it as unchanged on both sides: the change is still in the file,
|
|
81
|
+
// the reader asked not to be shown formatting noise.
|
|
82
|
+
lines.push({
|
|
83
|
+
type: 'context',
|
|
84
|
+
// The new form, so the row reads as the file's current state.
|
|
85
|
+
text: add.text,
|
|
86
|
+
oldLineNumber: del.lineNumber,
|
|
87
|
+
newLineNumber: add.lineNumber,
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
lines.push({ type: 'del', text: del.text, oldLineNumber: del.lineNumber });
|
|
92
|
+
}
|
|
93
|
+
for (let i = 0; i < addBuffer.length; i++) {
|
|
94
|
+
const add = addBuffer[i];
|
|
95
|
+
if (folded[i]) continue;
|
|
96
|
+
lines.push({ type: 'add', text: add.text, newLineNumber: add.lineNumber });
|
|
97
|
+
}
|
|
98
|
+
|
|
53
99
|
for (let i = 0; i < maxLen; i++) {
|
|
100
|
+
const del = delBuffer[i];
|
|
101
|
+
const add = addBuffer[i];
|
|
102
|
+
if (folded[i] && del && add) {
|
|
103
|
+
splitRows.push({
|
|
104
|
+
left: { type: 'context', text: del.text, lineNumber: del.lineNumber },
|
|
105
|
+
right: { type: 'context', text: add.text, lineNumber: add.lineNumber },
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
54
109
|
splitRows.push({
|
|
55
|
-
left:
|
|
56
|
-
right:
|
|
110
|
+
left: del ? { type: 'del', text: del.text, lineNumber: del.lineNumber } : undefined,
|
|
111
|
+
right: add ? { type: 'add', text: add.text, lineNumber: add.lineNumber } : undefined,
|
|
57
112
|
});
|
|
58
113
|
}
|
|
59
114
|
delBuffer = [];
|
|
@@ -90,39 +145,26 @@ export function parseUnifiedDiff(rawDiff: string, ignoreWhitespace: boolean = fa
|
|
|
90
145
|
|
|
91
146
|
if (rawLine.startsWith('+')) {
|
|
92
147
|
const text = rawLine.slice(1);
|
|
93
|
-
const normalizedText = ignoreWhitespace ? text.trim() : text;
|
|
94
148
|
additions++;
|
|
95
|
-
|
|
96
|
-
type: 'add',
|
|
97
|
-
text: normalizedText,
|
|
98
|
-
newLineNumber: newCounter,
|
|
99
|
-
});
|
|
100
|
-
addBuffer.push({ text: normalizedText, lineNumber: newCounter });
|
|
149
|
+
addBuffer.push({ text, lineNumber: newCounter });
|
|
101
150
|
newCounter++;
|
|
102
151
|
} else if (rawLine.startsWith('-')) {
|
|
103
152
|
const text = rawLine.slice(1);
|
|
104
|
-
const normalizedText = ignoreWhitespace ? text.trim() : text;
|
|
105
153
|
deletions++;
|
|
106
|
-
|
|
107
|
-
type: 'del',
|
|
108
|
-
text: normalizedText,
|
|
109
|
-
oldLineNumber: oldCounter,
|
|
110
|
-
});
|
|
111
|
-
delBuffer.push({ text: normalizedText, lineNumber: oldCounter });
|
|
154
|
+
delBuffer.push({ text, lineNumber: oldCounter });
|
|
112
155
|
oldCounter++;
|
|
113
156
|
} else {
|
|
114
157
|
flushBuffers();
|
|
115
158
|
const text = rawLine.startsWith(' ') ? rawLine.slice(1) : rawLine;
|
|
116
|
-
const normalizedText = ignoreWhitespace ? text.trim() : text;
|
|
117
159
|
lines.push({
|
|
118
160
|
type: 'context',
|
|
119
|
-
text
|
|
161
|
+
text,
|
|
120
162
|
oldLineNumber: oldCounter,
|
|
121
163
|
newLineNumber: newCounter,
|
|
122
164
|
});
|
|
123
165
|
splitRows.push({
|
|
124
|
-
left: { type: 'context', text
|
|
125
|
-
right: { type: 'context', text
|
|
166
|
+
left: { type: 'context', text, lineNumber: oldCounter },
|
|
167
|
+
right: { type: 'context', text, lineNumber: newCounter },
|
|
126
168
|
});
|
|
127
169
|
oldCounter++;
|
|
128
170
|
newCounter++;
|
|
@@ -131,5 +173,16 @@ export function parseUnifiedDiff(rawDiff: string, ignoreWhitespace: boolean = fa
|
|
|
131
173
|
|
|
132
174
|
flushBuffers();
|
|
133
175
|
|
|
176
|
+
if (ignoreWhitespace) {
|
|
177
|
+
// The counts were tallied before the pairs could be folded, so re-derive
|
|
178
|
+
// them from what is actually shown.
|
|
179
|
+
additions = 0;
|
|
180
|
+
deletions = 0;
|
|
181
|
+
for (const line of lines) {
|
|
182
|
+
if (line.type === 'add') additions++;
|
|
183
|
+
else if (line.type === 'del') deletions++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
134
187
|
return { lines, splitRows, additions, deletions };
|
|
135
188
|
}
|
|
@@ -39,3 +39,40 @@ export function saveProviderOverlay(providers: ProviderItem[]): Promise<Provider
|
|
|
39
39
|
export function setProviderEnabled(slug: string, enabled: boolean): Promise<ProviderItem[] | null> {
|
|
40
40
|
return postProviders(enabled ? { enableProvider: slug } : { disableProvider: slug });
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
export interface ProviderDeleteResult {
|
|
44
|
+
ok: boolean;
|
|
45
|
+
providers?: ProviderItem[];
|
|
46
|
+
/** True when a `models.yml` entry was actually removed. */
|
|
47
|
+
removedFromOmp?: boolean;
|
|
48
|
+
/** How many model entries went with it. */
|
|
49
|
+
modelsRemoved?: number;
|
|
50
|
+
/** The overlay row went but the file cleanup failed — surfaced, not swallowed. */
|
|
51
|
+
warning?: string;
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Delete a provider: its chamber overlay row AND its `models.yml` entry.
|
|
57
|
+
*
|
|
58
|
+
* The provider is identified by the id the registry served, because a provider
|
|
59
|
+
* that exists only in models.yml has no overlay row to match on — the server
|
|
60
|
+
* resolves the id against the merged registry to find the slug to unregister.
|
|
61
|
+
*/
|
|
62
|
+
export async function deleteProvider(id: string): Promise<ProviderDeleteResult> {
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(`${PROVIDERS_ENDPOINT}?id=${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
65
|
+
const data = await response.json() as ProviderDeleteResult;
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
return { ok: false, error: data?.error || `Delete failed (HTTP ${response.status})` };
|
|
68
|
+
}
|
|
69
|
+
// The provider — and every model it served — leaves the chat picker.
|
|
70
|
+
notifyModelsUpdated();
|
|
71
|
+
return { ...data, ok: true };
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: error instanceof Error ? error.message : 'Network error while deleting the provider',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The provider DIALECT — which wire API a custom provider speaks, how it
|
|
8
|
+
* authenticates, and how its model list is obtained. All three are omp
|
|
9
|
+
* `models.yml` provider fields, and all three change what a provider can do:
|
|
10
|
+
*
|
|
11
|
+
* - `api` selects the request/response shape. A gateway that speaks
|
|
12
|
+
* `anthropic-messages` is not reachable through the OpenAI-compatible path,
|
|
13
|
+
* and omp rejects an unknown `api` by disabling EVERY custom provider in the
|
|
14
|
+
* file — so the value must come from omp's own `ApiSchema`.
|
|
15
|
+
* - `auth: none` is what makes a local server keyless; without it omp demands
|
|
16
|
+
* an `apiKey` and a keyless provider is rejected as unconfigured.
|
|
17
|
+
* - `discovery` lets OMP list the models live from the endpoint instead of the
|
|
18
|
+
* chamber writing a snapshot of ids into `models.yml`.
|
|
19
|
+
*
|
|
20
|
+
* Pure data + pure functions, shared by the server (which validates and writes
|
|
21
|
+
* the file) and the client (which offers the choices), so the two cannot
|
|
22
|
+
* disagree about what omp accepts.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { OmpProviderApi, ProviderAuthMode, ProviderDiscoveryType, ProviderModelSource } from '@/shared/types/settings/provider';
|
|
26
|
+
|
|
27
|
+
/** Every `api` omp accepts, in omp's own `ApiSchema` order. */
|
|
28
|
+
export const PROVIDER_APIS: readonly OmpProviderApi[] = [
|
|
29
|
+
'openai-completions',
|
|
30
|
+
'openai-responses',
|
|
31
|
+
'openai-codex-responses',
|
|
32
|
+
'azure-openai-responses',
|
|
33
|
+
'anthropic-messages',
|
|
34
|
+
'bedrock-converse-stream',
|
|
35
|
+
'google-generative-ai',
|
|
36
|
+
'google-gemini-cli',
|
|
37
|
+
'google-vertex',
|
|
38
|
+
'openrouter-decisions',
|
|
39
|
+
'typesafe',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/** One-line explanation of what each dialect is, for the picker. */
|
|
43
|
+
export const PROVIDER_API_LABELS: Readonly<Record<OmpProviderApi, string>> = {
|
|
44
|
+
'openai-completions': 'OpenAI Chat Completions — the default for gateways and local servers',
|
|
45
|
+
'openai-responses': 'OpenAI Responses API — newer OpenAI-shaped endpoints',
|
|
46
|
+
'openai-codex-responses': 'OpenAI Codex Responses — Codex-style endpoints',
|
|
47
|
+
'azure-openai-responses': 'Azure OpenAI Responses — api-key header, api-version query',
|
|
48
|
+
'anthropic-messages': 'Anthropic Messages — Claude-shaped proxies (x-api-key)',
|
|
49
|
+
'bedrock-converse-stream': 'Amazon Bedrock Converse — AWS credential chain',
|
|
50
|
+
'google-generative-ai': 'Google Generative AI — Gemini API (x-goog-api-key)',
|
|
51
|
+
'google-gemini-cli': 'Google Gemini CLI — OAuth-backed Gemini route',
|
|
52
|
+
'google-vertex': 'Google Vertex AI — service-account / ADC route',
|
|
53
|
+
'openrouter-decisions': 'OpenRouter decisions — judge role only',
|
|
54
|
+
typesafe: 'Typesafe System One — judge role only',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* omp's `EffortSchema` — the reasoning ladder, lowest first, in omp's own
|
|
59
|
+
* `EFFORT_ORDER`.
|
|
60
|
+
*
|
|
61
|
+
* `off` is deliberately absent: omp rejects it inside `thinking.efforts`
|
|
62
|
+
* ("must be minimal, low, medium, high, xhigh or max"), because turning thinking
|
|
63
|
+
* off is a per-turn choice rather than a declared capability. A model that
|
|
64
|
+
* declares no effort ladder at all still gets omp's default one.
|
|
65
|
+
*/
|
|
66
|
+
export const PROVIDER_THINKING_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;
|
|
67
|
+
|
|
68
|
+
export type ProviderThinkingEffort = (typeof PROVIDER_THINKING_EFFORTS)[number];
|
|
69
|
+
|
|
70
|
+
/** True when `value` is an effort omp's `EffortSchema` accepts. */
|
|
71
|
+
export function isProviderThinkingEffort(value: unknown): value is ProviderThinkingEffort {
|
|
72
|
+
return typeof value === 'string' && (PROVIDER_THINKING_EFFORTS as readonly string[]).includes(value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Auth modes omp accepts, with the meaning the picker shows. */
|
|
76
|
+
export const PROVIDER_AUTH_MODES: ReadonlyArray<{
|
|
77
|
+
value: ProviderAuthMode;
|
|
78
|
+
label: string;
|
|
79
|
+
hint: string;
|
|
80
|
+
}> = [
|
|
81
|
+
{ value: 'apiKey', label: 'API key', hint: 'omp requires a key for this provider' },
|
|
82
|
+
{ value: 'none', label: 'No auth (keyless)', hint: 'Local server that needs no credential' },
|
|
83
|
+
{ value: 'oauth', label: 'OAuth', hint: 'Claude-Code-style proxy expecting the cloaked request shape' },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
/** Discovery types omp can drive itself, with what each one probes. */
|
|
87
|
+
export const PROVIDER_DISCOVERY_TYPES: ReadonlyArray<{
|
|
88
|
+
value: ProviderDiscoveryType;
|
|
89
|
+
label: string;
|
|
90
|
+
hint: string;
|
|
91
|
+
}> = [
|
|
92
|
+
{ value: 'openai-models-list', label: 'OpenAI /v1/models', hint: 'Any OpenAI-compatible endpoint' },
|
|
93
|
+
{ value: 'ollama', label: 'Ollama', hint: 'Native Ollama /api/tags + /api/show' },
|
|
94
|
+
{ value: 'llama.cpp', label: 'llama.cpp', hint: 'llama.cpp server model endpoints' },
|
|
95
|
+
{ value: 'lm-studio', label: 'LM Studio', hint: 'LM Studio /v1/models + native metadata' },
|
|
96
|
+
{ value: 'litellm', label: 'LiteLLM proxy', hint: 'LiteLLM proxy with rich model metadata' },
|
|
97
|
+
{ value: 'proxy', label: 'New-API / One-API proxy', hint: 'Wire api read per model from the proxy' },
|
|
98
|
+
{
|
|
99
|
+
value: 'apple-foundation-models',
|
|
100
|
+
label: 'Apple Foundation Models',
|
|
101
|
+
hint: 'On-device model (Apple silicon only)',
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
/** True when `value` is a dialect omp's `ApiSchema` accepts. */
|
|
106
|
+
export function isOmpProviderApi(value: unknown): value is OmpProviderApi {
|
|
107
|
+
return typeof value === 'string' && (PROVIDER_APIS as readonly string[]).includes(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* True when the value is a credential the UI masked rather than a real key.
|
|
112
|
+
*
|
|
113
|
+
* The settings UI echoes a stored key back as `sk-••••…`; that placeholder must
|
|
114
|
+
* never be persisted, or a provider looks configured while it cannot
|
|
115
|
+
* authenticate. Shared by the client (which produces the mask) and the server
|
|
116
|
+
* (which is the last gate before the file).
|
|
117
|
+
*/
|
|
118
|
+
export function isMaskedApiKey(value: unknown): boolean {
|
|
119
|
+
return typeof value === 'string' && /•{3,}/.test(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** True when `value` is an auth mode omp accepts. */
|
|
123
|
+
export function isProviderAuthMode(value: unknown): value is ProviderAuthMode {
|
|
124
|
+
return value === 'apiKey' || value === 'none' || value === 'oauth';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** True when `value` is a discovery type omp accepts. */
|
|
128
|
+
export function isProviderDiscoveryType(value: unknown): value is ProviderDiscoveryType {
|
|
129
|
+
return PROVIDER_DISCOVERY_TYPES.some((entry) => entry.value === value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* omp's `api` for a provider whose endpoint is only recognizable by URL. omp
|
|
134
|
+
* accepts eleven dialects and no endpoint can be asked which one it speaks, so
|
|
135
|
+
* only the two hosts that are unambiguous by name are claimed here; everything
|
|
136
|
+
* else gets the OpenAI-compatible default that the model-listing probe already
|
|
137
|
+
* assumes. Anything else (Azure, Bedrock, Vertex, a Codex proxy) has to be
|
|
138
|
+
* picked by hand — guessing would write a dialect that misroutes every request.
|
|
139
|
+
*/
|
|
140
|
+
export function inferProviderApi(baseUrl: string): OmpProviderApi {
|
|
141
|
+
if (/anthropic\./i.test(baseUrl)) return 'anthropic-messages';
|
|
142
|
+
if (/generativelanguage\.googleapis\.com/i.test(baseUrl)) return 'google-generative-ai';
|
|
143
|
+
if (/openai\.azure\.com/i.test(baseUrl)) return 'azure-openai-responses';
|
|
144
|
+
if (/openrouter\.ai/i.test(baseUrl)) return 'openai-completions';
|
|
145
|
+
return 'openai-completions';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Why a provider's model list is empty — or `undefined` when the emptiness is
|
|
150
|
+
* unexplained and should read as a problem.
|
|
151
|
+
*
|
|
152
|
+
* A provider with no models is not automatically broken: an endpoint override
|
|
153
|
+
* leaves the model list to omp's own catalog, a discovery provider is listed by
|
|
154
|
+
* omp at runtime, and an authenticated login provider's models arrive with the
|
|
155
|
+
* agent's own snapshot. Printing "no models" for all of them made a correctly
|
|
156
|
+
* configured proxy look like a failed setup.
|
|
157
|
+
*/
|
|
158
|
+
export function providerEmptyReason(provider: {
|
|
159
|
+
modelSource?: ProviderModelSource;
|
|
160
|
+
discovery?: ProviderDiscoveryType;
|
|
161
|
+
auth?: ProviderAuthMode;
|
|
162
|
+
}): string | undefined {
|
|
163
|
+
if (provider.discovery || provider.modelSource === 'discovery') {
|
|
164
|
+
return 'omp discovers this provider’s models live — they appear in the chat model picker, not here.';
|
|
165
|
+
}
|
|
166
|
+
if (provider.modelSource === 'override') {
|
|
167
|
+
// An override entry with no models and no credential is the one shape that
|
|
168
|
+
// genuinely cannot serve anything: omp has no key to reach the endpoint
|
|
169
|
+
// with. Naming the credential is the honest instruction.
|
|
170
|
+
return provider.auth === 'none'
|
|
171
|
+
? 'Endpoint override — omp keeps its own model list for this provider. Use “fetch models” to add specific ids.'
|
|
172
|
+
: 'Endpoint override — omp keeps its own model list for this provider. It still needs a credential: use “connect” to supply one, or “fetch models” to add specific ids.';
|
|
173
|
+
}
|
|
174
|
+
if (provider.auth === 'none') {
|
|
175
|
+
return 'No models registered yet. This server is keyless, so use “fetch models” to list what it serves.';
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The provider mark resolver is shared by the server (which builds provider
|
|
8
|
+
* rows) and the client (which renders them), and its two failure modes are both
|
|
9
|
+
* silent: a provider whose spelling does not fold onto its brand draws the wrong
|
|
10
|
+
* logo, and a provider with no mark at all would draw nothing. These lock the
|
|
11
|
+
* folding rules and the initials fallback.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, expect, test } from 'bun:test';
|
|
15
|
+
|
|
16
|
+
import { PROVIDER_GLYPHS } from '@/client/components/common/provider-icon/glyphs';
|
|
17
|
+
import { providerInitials, resolveProviderGlyph } from '@/shared/lib/models/provider/glyph';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Every provider id omp 18.3.0 ships (`KnownProvider`) plus the chamber's own
|
|
21
|
+
* additions. A provider added upstream appears here as one more id, and the
|
|
22
|
+
* coverage test below is what makes its missing mark a loud failure instead of a
|
|
23
|
+
* blank badge in the picker.
|
|
24
|
+
*/
|
|
25
|
+
const PROVIDER_IDS = [
|
|
26
|
+
'abliteration', 'aiand', 'aimlapi', 'alibaba-coding-plan', 'alibaba-token-plan', 'amazon-bedrock',
|
|
27
|
+
'anthropic', 'apple', 'azure', 'baseten', 'bedrock-mantle', 'cerebras', 'charm-hyper', 'cline-pass',
|
|
28
|
+
'cloudflare-ai-gateway', 'commandcode', 'coreweave', 'cursor', 'deepinfra', 'deepseek', 'devin',
|
|
29
|
+
'firepass', 'fireworks', 'github-copilot', 'gitlab-duo', 'gitlab-duo-agent', 'gmi-cloud', 'google',
|
|
30
|
+
'google-antigravity', 'google-gemini-cli', 'google-vertex', 'groq', 'huggingface', 'kilo', 'kimi-code',
|
|
31
|
+
'litellm', 'llama.cpp', 'lm-studio', 'local', 'meta', 'minimax', 'minimax-code', 'minimax-code-cn',
|
|
32
|
+
'mistral', 'moonshot', 'muse-code', 'nanogpt', 'novita', 'nvidia', 'ollama', 'ollama-cloud', 'openai',
|
|
33
|
+
'openai-codex', 'opencode-go', 'opencode-zen', 'openrouter', 'qianfan', 'qwen-portal', 'sakana',
|
|
34
|
+
'siliconflow', 'siliconflow-cn', 'singularityapi-dev', 'singularityapi-tech', 'stepfun', 'synthetic',
|
|
35
|
+
'together', 'typesafe', 'umans', 'venice', 'vercel-ai-gateway', 'vllm', 'wafer-serverless', 'web',
|
|
36
|
+
'xai', 'xai-oauth', 'xiaomi', 'xiaomi-token-plan-ams', 'xiaomi-token-plan-cn', 'xiaomi-token-plan-sgp',
|
|
37
|
+
'yolo-auto', 'zai', 'zenmux', 'zhipu-coding-plan',
|
|
38
|
+
// Chamber-side spellings that are not omp provider ids, plus the OAuth
|
|
39
|
+
// flavour ids the providers API actually serves (verified against a live
|
|
40
|
+
// /api/settings/providers response).
|
|
41
|
+
'kenari', 'agentrouter', 'claude', 'gemini', 'opencode', 'custom',
|
|
42
|
+
'zai-coding-plan', 'openai-codex-device', 'perplexity', 'tavily', 'kagi', 'exa', 'stencil',
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
describe('resolveProviderGlyph', () => {
|
|
46
|
+
test('a resolved mark exists in the glyph table', () => {
|
|
47
|
+
for (const id of PROVIDER_IDS) {
|
|
48
|
+
const name = resolveProviderGlyph(id);
|
|
49
|
+
if (name) expect(PROVIDER_GLYPHS[name]).toBeDefined();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('every glyph draws something', () => {
|
|
54
|
+
// A bake that produced an empty body renders an invisible icon with no
|
|
55
|
+
// error anywhere — the provider simply loses its mark.
|
|
56
|
+
for (const [name, glyph] of Object.entries(PROVIDER_GLYPHS)) {
|
|
57
|
+
expect(glyph.viewBox, name).toMatch(/^[-\d.]+ [-\d.]+ [\d.]+ [\d.]+$/);
|
|
58
|
+
expect(/<(path|circle|rect|g)\b/.test(glyph.body), name).toBe(true);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('no provider id leaves a blank badge', () => {
|
|
63
|
+
// The one promise of this module: a provider is drawn as its brand mark or
|
|
64
|
+
// as readable initials, never as an empty box.
|
|
65
|
+
for (const id of PROVIDER_IDS) {
|
|
66
|
+
const mark = resolveProviderGlyph(id);
|
|
67
|
+
expect(mark !== null || providerInitials(id).length > 0).toBe(true);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('variants of one brand share that brand mark', () => {
|
|
72
|
+
expect(resolveProviderGlyph('minimax-code-cn')).toBe(resolveProviderGlyph('minimax'));
|
|
73
|
+
expect(resolveProviderGlyph('xiaomi-token-plan-ams')).toBe(resolveProviderGlyph('xiaomi'));
|
|
74
|
+
expect(resolveProviderGlyph('google-vertex')).toBe(resolveProviderGlyph('vertexai'));
|
|
75
|
+
expect(resolveProviderGlyph('openai-codex')).toBe(resolveProviderGlyph('codex'));
|
|
76
|
+
expect(resolveProviderGlyph('opencode-zen')).toBe(resolveProviderGlyph('opencode-go'));
|
|
77
|
+
expect(resolveProviderGlyph('gitlab-duo-agent')).toBe(resolveProviderGlyph('gitlab-duo'));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('spelling differences fold to the same key', () => {
|
|
81
|
+
// omp reports ids, a models.yml slug is user-typed, and a display name is
|
|
82
|
+
// prose — all three reach this function.
|
|
83
|
+
for (const spelling of ['DeepSeek', 'deepseek', 'deep-seek', 'DEEP_SEEK', 'deepseek ']) {
|
|
84
|
+
expect(resolveProviderGlyph(spelling)).toBe('deepseek');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('a stored icon key wins over the slug', () => {
|
|
89
|
+
// Legacy preset rows carry `icon: 'claude'` while their slug moved on.
|
|
90
|
+
expect(resolveProviderGlyph('claude', 'anthropic')).toBe('claude');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('an unknown provider resolves to null rather than a wrong brand', () => {
|
|
94
|
+
expect(resolveProviderGlyph('my-private-gateway')).toBeNull();
|
|
95
|
+
expect(resolveProviderGlyph('')).toBeNull();
|
|
96
|
+
expect(resolveProviderGlyph(undefined, null)).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('providerInitials', () => {
|
|
101
|
+
test('takes one initial per word, joined with a single trailing dot', () => {
|
|
102
|
+
expect(providerInitials('Tooker')).toBe('t.');
|
|
103
|
+
expect(providerInitials('Toktok ID')).toBe('ti.');
|
|
104
|
+
expect(providerInitials('My Local Gateway')).toBe('ml.');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('a hyphenated or underscored slug reads as separate words', () => {
|
|
108
|
+
expect(providerInitials('toktok-id')).toBe('ti.');
|
|
109
|
+
expect(providerInitials('tok_tok')).toBe('tt.');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('an unlabelled provider still yields a visible badge', () => {
|
|
113
|
+
expect(providerInitials('')).toBe('?');
|
|
114
|
+
expect(providerInitials(' ')).toBe('?');
|
|
115
|
+
});
|
|
116
|
+
});
|