@openchambery/web 2.0.0-beta.1 → 2.0.0-beta.4
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 +15 -11
- package/bin/cli-opencode.test.js +4 -4
- package/bin/cli.js +1 -1
- package/bin/lib/cli-startup.js +1 -1
- package/bin/lib/cli-startup.test.js +18 -4
- package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-Q1SmFRx2.js} +1 -1
- package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
- package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
- package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
- package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
- package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
- package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
- package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
- package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
- package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
- package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
- package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
- package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
- package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
- package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
- package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
- package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
- package/dist/assets/appThemeRegistry-DK7q2juD.js +1 -0
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
- package/dist/assets/{button-D-rATN7w.js → button-CcNJUxUd.js} +1 -1
- package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
- package/dist/assets/context-menu-B2LXJgXL.js +1 -0
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
- package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
- package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
- package/dist/assets/es-CN3E_zXj.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
- package/dist/assets/fr-9zZvQZD2.js +13 -0
- package/dist/assets/gitApi-yuWg9BN4.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
- package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
- package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
- package/dist/assets/ja-Dnjk_82N.js +15 -0
- package/dist/assets/ko-BxCEE99C.js +15 -0
- package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
- package/dist/assets/main-DiL6w6KF.js +169 -0
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
- package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
- package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
- package/dist/assets/miniChat-CwtUaElU.js +2 -0
- package/dist/assets/mobile-DB-6--ws.js +2 -0
- package/dist/assets/multirun-TAsxFI8e.js +4 -0
- package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
- package/dist/assets/pl-CNNsWnPM.js +15 -0
- package/dist/assets/platform-C13fMVEs.js +6 -0
- package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
- package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
- package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
- package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
- package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
- package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
- package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
- package/dist/assets/src-ubCWXZhR.css +2 -0
- package/dist/assets/store-DLCiZaA6.js +15 -0
- package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
- package/dist/assets/textarea-BBOlRI9O.js +1 -0
- package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
- package/dist/assets/uk-rwQrxeYM.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
- package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
- package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
- package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
- package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
- package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +2 -0
- package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
- package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.js} +2 -2
- package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
- package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
- package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
- package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
- package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
- package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
- package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
- package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
- package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
- package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
- package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
- package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
- package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
- package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
- package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
- package/dist/index.html +13 -13
- package/dist/mini-chat.html +13 -13
- package/dist/mobile.html +13 -13
- package/package.json +1 -1
- package/server/index.js +327 -62
- package/server/lib/assistants/DOCUMENTATION.md +14 -9
- package/server/lib/assistants/contracts.js +11 -1
- package/server/lib/assistants/projection-history.test.js +265 -18
- package/server/lib/assistants/routes.js +6 -0
- package/server/lib/assistants/service.js +417 -231
- package/server/lib/assistants/service.test.js +392 -148
- package/server/lib/conversations/DOCUMENTATION.md +1 -1
- package/server/lib/conversations/service.js +1 -0
- package/server/lib/conversations/service.test.js +16 -0
- package/server/lib/conversations/validation.js +11 -1
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +49 -4
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- package/server/lib/event-stream/runtime.js +57 -20
- package/server/lib/event-stream/runtime.test.js +112 -0
- package/server/lib/llm/DOCUMENTATION.md +33 -9
- package/server/lib/llm/catalog.js +1 -1
- package/server/lib/llm/completions.js +5 -1
- package/server/lib/llm/completions.test.js +3 -3
- package/server/lib/llm/generate.js +237 -145
- package/server/lib/llm/generate.test.js +201 -46
- package/server/lib/llm/routes.js +2 -0
- package/server/lib/message-queue/runtime.js +15 -2
- package/server/lib/notifications/runtime.js +23 -5
- package/server/lib/notifications/runtime.test.js +11 -0
- package/server/lib/opencode/DOCUMENTATION.md +85 -17
- package/server/lib/opencode/agent-document.d.ts +28 -0
- package/server/lib/opencode/agent-document.js +280 -0
- package/server/lib/opencode/agent-document.test.js +137 -0
- package/server/lib/opencode/agents.js +96 -0
- package/server/lib/opencode/auth-state-runtime.js +5 -0
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-entity-routes.js +42 -99
- package/server/lib/opencode/config-entity-routes.test.js +107 -0
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
- package/server/lib/opencode/ensure-cli.js +1 -2
- package/server/lib/opencode/env-runtime.js +40 -26
- package/server/lib/opencode/env-runtime.test.js +10 -17
- package/server/lib/opencode/feature-routes-runtime.js +43 -4
- package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
- package/server/lib/opencode/http-server-connections.js +98 -0
- package/server/lib/opencode/http-server-connections.test.js +321 -0
- package/server/lib/opencode/lifecycle.js +349 -39
- package/server/lib/opencode/lifecycle.test.js +322 -0
- package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
- package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
- package/server/lib/opencode/openchamber-routes.js +7 -1
- package/server/lib/opencode/opencode2-pin.js +4 -4
- package/server/lib/opencode/opencode2-pin.test.js +1 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
- package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
- package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
- package/server/lib/opencode/plugin-routes.js +3 -18
- package/server/lib/opencode/plugin-routes.test.js +9 -9
- package/server/lib/opencode/provider-catalog.js +58 -12
- package/server/lib/opencode/provider-catalog.test.js +48 -0
- package/server/lib/opencode/proxy.js +252 -46
- package/server/lib/opencode/proxy.test.js +235 -2
- package/server/lib/opencode/routes.js +296 -62
- package/server/lib/opencode/routes.test.js +278 -12
- package/server/lib/opencode/runtime-contract.js +401 -0
- package/server/lib/opencode/runtime-contract.test.js +214 -0
- package/server/lib/opencode/server-opencode-fetch.js +242 -0
- package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
- package/server/lib/opencode/server-utils-runtime.js +33 -0
- package/server/lib/opencode/shared-service.integration.test.js +66 -0
- package/server/lib/opencode/shared-service.js +147 -0
- package/server/lib/opencode/shared-service.test.js +93 -0
- package/server/lib/opencode/shared.js +3 -3
- package/server/lib/opencode/shutdown-runtime.js +15 -6
- package/server/lib/opencode/shutdown-runtime.test.js +49 -1
- package/server/lib/opencode/skill-routes.js +20 -33
- package/server/lib/opencode/skill-routes.test.js +35 -2
- package/server/lib/opencode/skills.test.js +15 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
- package/server/lib/opencode/static-routes-runtime.js +3 -1
- package/server/lib/opencode/static-routes-runtime.test.js +33 -0
- package/server/lib/opencode/v2-client.js +14 -3
- package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
- package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
- package/server/lib/permission-auto-accept/runtime.js +14 -7
- package/server/lib/permission-auto-accept/runtime.test.js +27 -12
- package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
- package/server/lib/scheduled-tasks/runtime.js +52 -4
- package/server/lib/scheduled-tasks/runtime.test.js +80 -1
- package/server/lib/session-goal/DOCUMENTATION.md +113 -13
- package/server/lib/session-goal/routes.js +9 -0
- package/server/lib/session-goal/runtime.js +1579 -117
- package/server/lib/session-goal/runtime.test.js +1453 -84
- package/server/lib/session-index/DOCUMENTATION.md +18 -15
- package/server/lib/session-index/event-ingest.js +89 -16
- package/server/lib/session-index/event-ingest.test.js +73 -0
- package/server/lib/session-index/host-authority-snapshot.js +92 -0
- package/server/lib/session-index/routes.host-archive.test.js +201 -0
- package/server/lib/session-index/routes.js +63 -10
- package/server/lib/session-index/routes.test.js +8 -8
- package/server/lib/session-index/service.js +7 -1
- package/server/lib/session-index/sync-runtime.js +161 -34
- package/server/lib/session-index/sync-runtime.test.js +155 -1
- package/server/lib/session-metadata/DOCUMENTATION.md +61 -16
- package/server/lib/session-metadata/routes.js +108 -9
- package/server/lib/session-metadata/routes.test.js +47 -2
- package/server/lib/session-metadata/session-archive.d.ts +73 -0
- package/server/lib/session-metadata/session-archive.js +654 -0
- package/server/lib/session-metadata/session-archive.test.js +359 -0
- package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
- package/server/lib/session-metadata/session-metadata-store.js +212 -72
- package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
- package/server/lib/session-metadata/session-projection.d.ts +41 -0
- package/server/lib/session-metadata/session-projection.js +255 -0
- package/server/lib/session-metadata/session-projection.test.js +64 -0
- package/server/lib/session-metadata/system-session.js +55 -0
- package/server/lib/session-metadata/system-session.test.js +55 -0
- package/server/lib/session-title/DOCUMENTATION.md +40 -7
- package/server/lib/session-title/native-runtime.test.js +167 -0
- package/server/lib/session-title/runtime.js +67 -42
- package/server/lib/session-title/session-access.js +46 -0
- package/server/lib/session-title/session-access.test.js +91 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
- package/server/lib/session-turn-pages/routes.js +17 -102
- package/server/lib/session-turn-pages/routes.test.js +281 -0
- package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
- package/server/lib/session-turn-pages/session-message-projection.js +281 -0
- package/server/lib/small-model/DOCUMENTATION.md +101 -147
- package/server/lib/small-model/catalog.js +31 -51
- package/server/lib/small-model/catalog.test.js +65 -10
- package/server/lib/small-model/custom-api.js +68 -0
- package/server/lib/small-model/custom-api.test.js +64 -0
- package/server/lib/small-model/index.js +124 -240
- package/server/lib/small-model/resolve.js +35 -120
- package/server/lib/small-model/resolve.test.js +20 -110
- package/server/lib/small-model/routes.js +1 -3
- package/server/lib/small-model/summary-settings.test.js +157 -253
- package/server/opencode-proxy.test.js +2 -0
- package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
- package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
- package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
- package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
- package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
- package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
- package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
- package/dist/assets/context-menu-jGtH6fR9.js +0 -1
- package/dist/assets/es-c52LTHHe.js +0 -15
- package/dist/assets/fr-PisFbUE8.js +0 -13
- package/dist/assets/gitApi-ButwzQ0N.js +0 -9
- package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
- package/dist/assets/ja-C8Ikun3Z.js +0 -15
- package/dist/assets/ko-ClFys5o2.js +0 -15
- package/dist/assets/main-Dn9rpsgl.js +0 -169
- package/dist/assets/markdown-worker-CptoICmq.js +0 -2
- package/dist/assets/miniChat-BpjxVqgg.js +0 -2
- package/dist/assets/mobile-nMJCaNq1.js +0 -2
- package/dist/assets/multirun-BvSG94mW.js +0 -4
- package/dist/assets/pl-CnaKWN5F.js +0 -15
- package/dist/assets/platform-CWTIp02q.js +0 -4
- package/dist/assets/projectMeta-DOe49dM_.js +0 -3
- package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
- package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
- package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
- package/dist/assets/src-BzX5UI-c.css +0 -2
- package/dist/assets/store-DJczmczv.js +0 -15
- package/dist/assets/textarea-3XR8sUfy.js +0 -1
- package/dist/assets/uk-CaX0iqkB.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
- package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
- package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
- package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
- package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
- package/dist/assets/zh-CN-20MYEjha.js +0 -15
- package/dist/assets/zh-TW-DiN04qTw.js +0 -15
- package/server/lib/small-model/call.js +0 -427
- package/server/lib/small-model/call.test.js +0 -382
- package/server/lib/small-model/opencode-session.js +0 -183
- package/server/lib/small-model/opencode-session.test.js +0 -147
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { readAuthFile } from '../opencode/auth.js';
|
|
5
4
|
import { readConfigLayers } from '../opencode/shared.js';
|
|
6
5
|
import { OpenCode } from '@opencode/client';
|
|
7
|
-
import {
|
|
8
|
-
import { createModelCatalogLoader
|
|
9
|
-
import { resolveSmallModel, parseModelRef
|
|
10
|
-
import {
|
|
11
|
-
import { generateViaOpenCodeSession, stop as stopOpenCodeSessionTemp } from './opencode-session.js';
|
|
12
|
-
import { listCustomSummaryModels, testCustomSummaryApi } from './custom-api.js';
|
|
6
|
+
import { createChatCompletion } from '../llm/completions.js';
|
|
7
|
+
import { createModelCatalogLoader } from './catalog.js';
|
|
8
|
+
import { resolveSmallModel, parseModelRef } from './resolve.js';
|
|
9
|
+
import { generateCustomSummaryText, listCustomSummaryModels, testCustomSummaryApi } from './custom-api.js';
|
|
13
10
|
|
|
14
11
|
const OPENCHAMBER_SETTINGS_FILE = path.join(
|
|
15
12
|
process.env.OPENCHAMBER_DATA_DIR
|
|
@@ -68,8 +65,7 @@ const readSmallModelSettingsOverride = () => {
|
|
|
68
65
|
|
|
69
66
|
// Rough safety clamp so a huge input never blows the model's context window.
|
|
70
67
|
// Token estimate is ~4 chars/token; when the catalog has no limit for the
|
|
71
|
-
// model (
|
|
72
|
-
// applies.
|
|
68
|
+
// model (or the call goes to a custom API) a conservative default applies.
|
|
73
69
|
const DEFAULT_CONTEXT_TOKENS = 64_000;
|
|
74
70
|
const OUTPUT_RESERVE_TOKENS = 4_000;
|
|
75
71
|
|
|
@@ -84,106 +80,6 @@ const clampPromptToModelLimit = ({ prompt, catalog, providerID, modelID }) => {
|
|
|
84
80
|
return { prompt: `${prompt.slice(0, maxChars)}…`, truncated: true };
|
|
85
81
|
};
|
|
86
82
|
|
|
87
|
-
// Dedicated adapters keep the direct wire-format path. Everything else
|
|
88
|
-
// (plugin providers, credential-chain, missing api.url, …) goes through a
|
|
89
|
-
// temporary OpenCode session so auth/endpoint rewrite stay in the runtime.
|
|
90
|
-
const DEDICATED_DIRECT_PROVIDERS = new Set(['openai', 'anthropic', 'google', 'github-copilot']);
|
|
91
|
-
|
|
92
|
-
// Mirrors pickWithinProvider / callSmallModel auth gates so Settings pickers
|
|
93
|
-
// never list dedicated providers that would only fail at dispatch time.
|
|
94
|
-
const isCallableDedicatedAuth = (auth, providerID) => {
|
|
95
|
-
if (providerID === 'openai') {
|
|
96
|
-
const entry = auth?.openai;
|
|
97
|
-
return isUsableAuthEntry(entry) && (entry.type === 'api' || entry.type === 'oauth');
|
|
98
|
-
}
|
|
99
|
-
if (providerID === 'anthropic') {
|
|
100
|
-
const entry = auth?.anthropic;
|
|
101
|
-
return entry?.type === 'api' && isUsableAuthEntry(entry);
|
|
102
|
-
}
|
|
103
|
-
if (providerID === 'google') {
|
|
104
|
-
const entry = auth?.google;
|
|
105
|
-
return entry?.type === 'api' && isUsableAuthEntry(entry);
|
|
106
|
-
}
|
|
107
|
-
if (providerID === 'github-copilot') {
|
|
108
|
-
return isUsableAuthEntry(getAuthEntryForProvider(auth, 'github-copilot'));
|
|
109
|
-
}
|
|
110
|
-
return false;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const providerHasCatalogModels = (provider) => {
|
|
114
|
-
if (!provider || typeof provider !== 'object') return false;
|
|
115
|
-
return Object.values(provider.models || {}).some(
|
|
116
|
-
(model) => typeof model?.id === 'string' && model.id.trim().length > 0,
|
|
117
|
-
);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const shouldUseDirectAdapter = (providerID, auth) => {
|
|
121
|
-
if (!DEDICATED_DIRECT_PROVIDERS.has(providerID)) return false;
|
|
122
|
-
return isCallableDedicatedAuth(auth, providerID);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const addNonDedicatedCallableProvider = (ids, catalog, providerID) => {
|
|
126
|
-
if (!providerID || providerID === 'copilot' || DEDICATED_DIRECT_PROVIDERS.has(providerID)) return;
|
|
127
|
-
const provider = getCatalogProvider(catalog, providerID);
|
|
128
|
-
if (providerHasCatalogModels(provider)) ids.add(providerID);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const listCallableProviderIDsForState = (auth, catalog, connectedIDs) => {
|
|
132
|
-
const ids = new Set();
|
|
133
|
-
for (const providerID of DEDICATED_DIRECT_PROVIDERS) {
|
|
134
|
-
if (isCallableDedicatedAuth(auth, providerID)) ids.add(providerID);
|
|
135
|
-
}
|
|
136
|
-
if (isUsableAuthEntry(getAuthEntryForProvider(auth, 'github-copilot'))) {
|
|
137
|
-
ids.add('github-copilot');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (Array.isArray(connectedIDs)) {
|
|
141
|
-
for (const providerID of connectedIDs) {
|
|
142
|
-
addNonDedicatedCallableProvider(ids, catalog, providerID);
|
|
143
|
-
}
|
|
144
|
-
} else {
|
|
145
|
-
// Connected catalog failed: do not pretend plugin providers are absent.
|
|
146
|
-
for (const providerID of Object.keys(auth || {})) {
|
|
147
|
-
if (providerID === 'copilot') continue;
|
|
148
|
-
if (!isUsableAuthEntry(auth[providerID])) continue;
|
|
149
|
-
addNonDedicatedCallableProvider(ids, catalog, providerID);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return Array.from(ids);
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const listCallableModelsForState = (auth, catalog, connectedIDs) => {
|
|
156
|
-
const result = {};
|
|
157
|
-
for (const providerID of listCallableProviderIDsForState(auth, catalog, connectedIDs)) {
|
|
158
|
-
if (providerID === 'openai' && auth.openai?.type === 'oauth') {
|
|
159
|
-
result[providerID] = ['gpt-5.4-mini'];
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
if (providerID === 'github-copilot') {
|
|
163
|
-
result[providerID] = ['gpt-5.4-nano'];
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
const provider = getCatalogProvider(catalog, providerID);
|
|
167
|
-
const modelIDs = Object.values(provider?.models || {})
|
|
168
|
-
.map((model) => (typeof model?.id === 'string' ? model.id : ''))
|
|
169
|
-
.filter(Boolean);
|
|
170
|
-
if (modelIDs.length > 0) {
|
|
171
|
-
result[providerID] = modelIDs;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return result;
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const resolveDefaultSummaryModel = (auth, catalog, connectedIDs) => {
|
|
178
|
-
const callableModels = listCallableModelsForState(auth, catalog, connectedIDs);
|
|
179
|
-
const providerIDs = Object.keys(callableModels);
|
|
180
|
-
const providerID = callableModels.openai ? 'openai' : providerIDs[0];
|
|
181
|
-
const modelID = providerID ? callableModels[providerID]?.[0] : undefined;
|
|
182
|
-
return providerID && modelID
|
|
183
|
-
? { providerID, modelID, source: 'summary-default' }
|
|
184
|
-
: null;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
83
|
const readConfiguredSmallModel = (workingDirectory) => {
|
|
188
84
|
try {
|
|
189
85
|
const { mergedConfig } = readConfigLayers(workingDirectory);
|
|
@@ -203,39 +99,117 @@ const readConfiguredSmallModel = (workingDirectory) => {
|
|
|
203
99
|
* buildOpenCodeUrl: (pathname: string, search?: string) => string,
|
|
204
100
|
* getOpenCodeAuthHeaders: () => Record<string, string>,
|
|
205
101
|
* getModelCatalog?: (directory?: string) => Promise<object>,
|
|
206
|
-
*
|
|
102
|
+
* openCodeClientFactory?: () => object,
|
|
103
|
+
* createChatCompletion?: typeof createChatCompletion,
|
|
104
|
+
* persistSessionMetadata?: (sessionID: string, patch: object) => Promise<unknown>,
|
|
105
|
+
* onSystemSessionPersisted?: (input: { sessionID: string, directory: string, metadata: object }) => void,
|
|
207
106
|
* }} dependencies
|
|
208
107
|
*/
|
|
209
108
|
export function createSmallModelService(dependencies) {
|
|
210
109
|
const buildOpenCodeUrl = dependencies.buildOpenCodeUrl;
|
|
211
110
|
const getOpenCodeAuthHeaders = dependencies.getOpenCodeAuthHeaders;
|
|
111
|
+
const persistSessionMetadata = dependencies.persistSessionMetadata || null;
|
|
112
|
+
const onSystemSessionPersisted = dependencies.onSystemSessionPersisted || null;
|
|
212
113
|
const getModelCatalog = dependencies.getModelCatalog
|
|
213
114
|
|| createModelCatalogLoader({
|
|
214
115
|
buildOpenCodeUrl,
|
|
215
116
|
getOpenCodeAuthHeaders,
|
|
216
117
|
}).getModelCatalog;
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
let connectedIDs = null;
|
|
118
|
+
const openCodeClient = dependencies.openCodeClientFactory || (() => OpenCode.make({
|
|
119
|
+
baseUrl: buildOpenCodeUrl('/', '').replace(/\/$/, ''),
|
|
120
|
+
headers: getOpenCodeAuthHeaders(),
|
|
121
|
+
}));
|
|
122
|
+
const runChatCompletion = dependencies.createChatCompletion || createChatCompletion;
|
|
123
|
+
|
|
124
|
+
// No saved Summary AI model follows OpenCode's own default model.
|
|
125
|
+
const resolveSummaryProviderModel = async (summarySettings, directory) => {
|
|
126
|
+
if (summarySettings?.providerID && summarySettings?.modelID) {
|
|
127
|
+
return { providerID: summarySettings.providerID, modelID: summarySettings.modelID, source: 'summary-provider' };
|
|
128
|
+
}
|
|
129
|
+
let result;
|
|
230
130
|
try {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
131
|
+
result = await openCodeClient().model.default(directory ? { location: { directory } } : undefined);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw Object.assign(
|
|
134
|
+
new Error(`OpenCode default model is unavailable (${error?.message || error})`),
|
|
135
|
+
{ statusCode: 502 },
|
|
136
|
+
);
|
|
235
137
|
}
|
|
236
|
-
|
|
138
|
+
const providerID = typeof result?.data?.providerID === 'string' ? result.data.providerID : '';
|
|
139
|
+
const modelID = typeof result?.data?.id === 'string' ? result.data.id : '';
|
|
140
|
+
if (!providerID || !modelID) {
|
|
141
|
+
throw Object.assign(new Error('No OpenCode default model is configured'), { statusCode: 404 });
|
|
142
|
+
}
|
|
143
|
+
return { providerID, modelID, source: 'summary-default' };
|
|
237
144
|
};
|
|
238
145
|
|
|
146
|
+
// Every OpenCode-backed call shares the Assistant LLM gateway: connected
|
|
147
|
+
// catalog check, then a throwaway-session `session.generate`.
|
|
148
|
+
const generateThroughGateway = async ({ resolved, prompt, system, directory }) => {
|
|
149
|
+
const catalog = await getModelCatalog(directory);
|
|
150
|
+
const clamped = clampPromptToModelLimit({ prompt, catalog, providerID: resolved.providerID, modelID: resolved.modelID });
|
|
151
|
+
const { completion } = await runChatCompletion({
|
|
152
|
+
body: {
|
|
153
|
+
providerID: resolved.providerID,
|
|
154
|
+
modelID: resolved.modelID,
|
|
155
|
+
messages: [
|
|
156
|
+
...(system ? [{ role: 'system', content: system }] : []),
|
|
157
|
+
{ role: 'user', content: clamped.prompt },
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
buildOpenCodeUrl,
|
|
161
|
+
getOpenCodeAuthHeaders,
|
|
162
|
+
clientFactory: openCodeClient,
|
|
163
|
+
persistSessionMetadata,
|
|
164
|
+
onSystemSessionPersisted,
|
|
165
|
+
});
|
|
166
|
+
const text = completion?.choices?.[0]?.message?.content;
|
|
167
|
+
return {
|
|
168
|
+
text: typeof text === 'string' ? text.trim() : '',
|
|
169
|
+
providerID: resolved.providerID,
|
|
170
|
+
modelID: resolved.modelID,
|
|
171
|
+
source: resolved.source,
|
|
172
|
+
...(clamped.truncated ? { inputTruncated: true } : {}),
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Summary AI (commit / session-title): the saved OpenCode model or OpenCode's
|
|
177
|
+
// default through the gateway, or the user's custom OpenAI-compatible API.
|
|
178
|
+
async function generateSummaryText({ prompt, system, maxOutputTokens, directory, purpose }) {
|
|
179
|
+
const summarySettings = readSummarySettings();
|
|
180
|
+
const effectiveSystem = summarySettings?.prompts?.[purpose]
|
|
181
|
+
|| (typeof system === 'string' && system.trim() ? system.trim() : undefined);
|
|
182
|
+
|
|
183
|
+
if (summarySettings?.mode === 'custom') {
|
|
184
|
+
const custom = summarySettings.custom;
|
|
185
|
+
if (!custom) {
|
|
186
|
+
throw Object.assign(
|
|
187
|
+
new Error('Custom summary API requires a Base URL, model ID, and API token'),
|
|
188
|
+
{ statusCode: 400 },
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const clamped = clampPromptToModelLimit({ prompt, catalog: {}, providerID: 'custom', modelID: custom.modelID });
|
|
192
|
+
const text = await generateCustomSummaryText({
|
|
193
|
+
baseURL: custom.baseURL,
|
|
194
|
+
apiToken: custom.apiToken,
|
|
195
|
+
modelID: custom.modelID,
|
|
196
|
+
prompt: clamped.prompt,
|
|
197
|
+
system: effectiveSystem,
|
|
198
|
+
maxOutputTokens,
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
text: text.trim(),
|
|
202
|
+
providerID: 'custom',
|
|
203
|
+
modelID: custom.modelID,
|
|
204
|
+
source: 'summary-custom',
|
|
205
|
+
...(clamped.truncated ? { inputTruncated: true } : {}),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const resolved = await resolveSummaryProviderModel(summarySettings, directory);
|
|
210
|
+
return generateThroughGateway({ resolved, prompt, system: effectiveSystem, directory });
|
|
211
|
+
}
|
|
212
|
+
|
|
239
213
|
const resolveCustomApiInput = (body = {}) => {
|
|
240
214
|
const summarySettings = readSummarySettings();
|
|
241
215
|
const baseURL = typeof body.baseURL === 'string' && body.baseURL.trim()
|
|
@@ -251,8 +225,8 @@ export function createSmallModelService(dependencies) {
|
|
|
251
225
|
};
|
|
252
226
|
|
|
253
227
|
/**
|
|
254
|
-
* Generates text with the user's small model, resolved
|
|
255
|
-
*
|
|
228
|
+
* Generates text with the user's small model, resolved server-side from
|
|
229
|
+
* OpenChamber settings, the OpenCode config, and the connected catalog.
|
|
256
230
|
*/
|
|
257
231
|
async function generateSmallModelText({
|
|
258
232
|
prompt,
|
|
@@ -269,48 +243,24 @@ export function createSmallModelService(dependencies) {
|
|
|
269
243
|
throw Object.assign(new Error('prompt is required'), { statusCode: 400 });
|
|
270
244
|
}
|
|
271
245
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const defaultSummaryModel = SUMMARY_PURPOSES.has(purpose)
|
|
276
|
-
? resolveDefaultSummaryModel(auth, catalog, connectedIDs)
|
|
277
|
-
: null;
|
|
246
|
+
if (SUMMARY_PURPOSES.has(purpose)) {
|
|
247
|
+
return generateSummaryText({ prompt: prompt.trim(), system, maxOutputTokens, directory, purpose });
|
|
248
|
+
}
|
|
278
249
|
|
|
279
250
|
const explicit = parseModelRef(model);
|
|
280
|
-
const
|
|
281
|
-
? {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const summaryCustom = summarySettings?.mode === 'custom' ? summarySettings.custom : null;
|
|
290
|
-
if (summarySettings?.mode === 'custom' && !summaryCustom) {
|
|
291
|
-
throw Object.assign(
|
|
292
|
-
new Error('Custom summary API requires a Base URL, model ID, and API token'),
|
|
293
|
-
{ statusCode: 400 },
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
const resolved = summaryCustom
|
|
297
|
-
? { providerID: 'custom', modelID: summaryCustom.modelID, source: 'summary-custom' }
|
|
298
|
-
: summaryProvider
|
|
299
|
-
? summaryProvider
|
|
300
|
-
: explicit
|
|
301
|
-
? { ...explicit, source: 'request' }
|
|
302
|
-
: resolveSmallModel({
|
|
303
|
-
auth,
|
|
304
|
-
catalog,
|
|
305
|
-
settingsSmallModel: readSmallModelSettingsOverride(),
|
|
306
|
-
configSmallModel: readConfiguredSmallModel(directory),
|
|
307
|
-
preferredProviderID: summarySettings?.providerID || preferredProviderID,
|
|
308
|
-
preferredModelID,
|
|
309
|
-
});
|
|
251
|
+
const resolved = explicit
|
|
252
|
+
? { ...explicit, source: 'request' }
|
|
253
|
+
: resolveSmallModel({
|
|
254
|
+
catalog: await getModelCatalog(directory),
|
|
255
|
+
settingsSmallModel: readSmallModelSettingsOverride(),
|
|
256
|
+
configSmallModel: readConfiguredSmallModel(directory),
|
|
257
|
+
preferredProviderID,
|
|
258
|
+
preferredModelID,
|
|
259
|
+
});
|
|
310
260
|
|
|
311
261
|
if (!resolved) {
|
|
312
262
|
throw Object.assign(
|
|
313
|
-
new Error('No small model available — no
|
|
263
|
+
new Error('No small model available — no connected OpenCode provider has a suitable model'),
|
|
314
264
|
{ statusCode: 404 },
|
|
315
265
|
);
|
|
316
266
|
}
|
|
@@ -320,7 +270,7 @@ export function createSmallModelService(dependencies) {
|
|
|
320
270
|
// model) is always allowed, anything else must stay on the session's
|
|
321
271
|
// provider.
|
|
322
272
|
if (restrictToPreferredProvider
|
|
323
|
-
&& !['settings', 'config', 'request'
|
|
273
|
+
&& !['settings', 'config', 'request'].includes(resolved.source)
|
|
324
274
|
&& resolved.providerID !== preferredProviderID) {
|
|
325
275
|
throw Object.assign(
|
|
326
276
|
new Error('No small model available within the session provider'),
|
|
@@ -328,71 +278,12 @@ export function createSmallModelService(dependencies) {
|
|
|
328
278
|
);
|
|
329
279
|
}
|
|
330
280
|
|
|
331
|
-
|
|
281
|
+
return generateThroughGateway({
|
|
282
|
+
resolved,
|
|
332
283
|
prompt: prompt.trim(),
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
modelID: resolved.modelID,
|
|
284
|
+
system: typeof system === 'string' && system.trim() ? system.trim() : undefined,
|
|
285
|
+
directory,
|
|
336
286
|
});
|
|
337
|
-
|
|
338
|
-
const effectiveSystem = summaryPrompt || (typeof system === 'string' && system.trim() ? system.trim() : undefined);
|
|
339
|
-
|
|
340
|
-
let text;
|
|
341
|
-
if (summaryCustom || shouldUseDirectAdapter(resolved.providerID, auth)) {
|
|
342
|
-
text = await callSmallModel({
|
|
343
|
-
auth,
|
|
344
|
-
catalog,
|
|
345
|
-
workingDirectory: directory,
|
|
346
|
-
providerID: resolved.providerID,
|
|
347
|
-
modelID: resolved.modelID,
|
|
348
|
-
prompt: clamped.prompt,
|
|
349
|
-
system: effectiveSystem,
|
|
350
|
-
maxOutputTokens,
|
|
351
|
-
custom: summaryCustom,
|
|
352
|
-
});
|
|
353
|
-
} else {
|
|
354
|
-
text = await generateViaOpenCodeSession({
|
|
355
|
-
buildOpenCodeUrl,
|
|
356
|
-
getOpenCodeAuthHeaders,
|
|
357
|
-
providerID: resolved.providerID,
|
|
358
|
-
modelID: resolved.modelID,
|
|
359
|
-
prompt: clamped.prompt,
|
|
360
|
-
system: effectiveSystem,
|
|
361
|
-
purpose: typeof purpose === 'string' && purpose.trim() ? purpose.trim() : 'generate',
|
|
362
|
-
directory,
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return {
|
|
367
|
-
text: text.trim(),
|
|
368
|
-
providerID: resolved.providerID,
|
|
369
|
-
modelID: resolved.modelID,
|
|
370
|
-
source: resolved.source,
|
|
371
|
-
...(clamped.truncated ? { inputTruncated: true } : {}),
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Provider ids with a usable OpenCode login — the set the small model can
|
|
377
|
-
* actually call. Used by the settings override picker to hide providers that
|
|
378
|
-
* would only ever fail (e.g. opencode free models without a token).
|
|
379
|
-
*/
|
|
380
|
-
async function listCallableProviders({ directory } = {}) {
|
|
381
|
-
try {
|
|
382
|
-
const { auth, catalog, connectedIDs } = await loadCallableContext(directory);
|
|
383
|
-
return listCallableProviderIDsForState(auth, catalog, connectedIDs);
|
|
384
|
-
} catch {
|
|
385
|
-
return [];
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
async function listCallableModels({ directory } = {}) {
|
|
390
|
-
try {
|
|
391
|
-
const { auth, catalog, connectedIDs } = await loadCallableContext(directory);
|
|
392
|
-
return listCallableModelsForState(auth, catalog, connectedIDs);
|
|
393
|
-
} catch {
|
|
394
|
-
return {};
|
|
395
|
-
}
|
|
396
287
|
}
|
|
397
288
|
|
|
398
289
|
async function testCustomApi(body) {
|
|
@@ -408,26 +299,19 @@ export function createSmallModelService(dependencies) {
|
|
|
408
299
|
* Reports which model would be used, without calling it.
|
|
409
300
|
*/
|
|
410
301
|
async function describeSmallModel({ directory, preferredProviderID, preferredModelID } = {}) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const resolved = resolveSmallModel({
|
|
414
|
-
auth,
|
|
415
|
-
catalog,
|
|
302
|
+
return resolveSmallModel({
|
|
303
|
+
catalog: await getModelCatalog(directory),
|
|
416
304
|
settingsSmallModel: readSmallModelSettingsOverride(),
|
|
417
305
|
configSmallModel: readConfiguredSmallModel(directory),
|
|
418
306
|
preferredProviderID,
|
|
419
307
|
preferredModelID,
|
|
420
308
|
});
|
|
421
|
-
return resolved;
|
|
422
309
|
}
|
|
423
310
|
|
|
424
311
|
return {
|
|
425
312
|
generateSmallModelText,
|
|
426
|
-
listCallableProviders,
|
|
427
|
-
listCallableModels,
|
|
428
313
|
testCustomApi,
|
|
429
314
|
listCustomModels,
|
|
430
315
|
describeSmallModel,
|
|
431
|
-
stop: stopOpenCodeSessionTemp,
|
|
432
316
|
};
|
|
433
317
|
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { getCatalogProvider } from './catalog.js';
|
|
2
2
|
|
|
3
|
-
// Mirrors OpenCode's getSmallModel fallback chain
|
|
4
|
-
// keyword + cheapest-first default when no
|
|
3
|
+
// Mirrors OpenCode's getSmallModel fallback chain over the connected OpenCode
|
|
4
|
+
// catalog, with OpenChamber's own keyword + cheapest-first default when no
|
|
5
|
+
// session/family match applies:
|
|
5
6
|
// 1. `small_model` from the merged config layers ("provider/model").
|
|
6
|
-
// 2. Family-priority / keyword scan of
|
|
7
|
-
// 3. GitHub Copilot's hidden utility models when Copilot is logged in.
|
|
7
|
+
// 2. Family-priority / keyword scan of connected providers' catalog models.
|
|
8
8
|
const FAMILY_PRIORITY = ['gemini-flash', 'gpt-nano', 'claude-haiku'];
|
|
9
|
-
const COPILOT_UTILITY_MODELS = ['gpt-5.4-nano', 'gpt-4.1', 'gpt-4o', 'gpt-4o-mini'];
|
|
10
|
-
// The ChatGPT-plan codex backend only accepts a small allowlist of models
|
|
11
|
-
// (nano/API-key models are rejected with 400) — this is its cheapest one.
|
|
12
|
-
const OPENAI_OAUTH_SMALL_MODEL = 'gpt-5.4-mini';
|
|
13
9
|
|
|
14
10
|
// Tokenize model ids so "mini" does not match inside "gemini".
|
|
15
11
|
const modelIdTokens = (id) => String(id || '')
|
|
@@ -33,32 +29,6 @@ const SMALL_MODEL_KEYWORD_TIERS = Object.freeze([
|
|
|
33
29
|
{ tier: 8, test: (id) => idHasToken(id, 'chat') },
|
|
34
30
|
]);
|
|
35
31
|
|
|
36
|
-
const AUTH_PROVIDER_ALIASES = {
|
|
37
|
-
'github-copilot': ['github-copilot', 'copilot'],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export function getAuthEntryForProvider(auth, providerID) {
|
|
41
|
-
const aliases = AUTH_PROVIDER_ALIASES[providerID] || [providerID];
|
|
42
|
-
for (const alias of aliases) {
|
|
43
|
-
const entry = auth?.[alias];
|
|
44
|
-
if (entry && typeof entry === 'object') {
|
|
45
|
-
return entry;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function isUsableAuthEntry(entry) {
|
|
52
|
-
if (!entry || typeof entry !== 'object') return false;
|
|
53
|
-
if (entry.type === 'api') return typeof entry.key === 'string' && entry.key.length > 0;
|
|
54
|
-
if (entry.type === 'oauth') {
|
|
55
|
-
return (typeof entry.access === 'string' && entry.access.length > 0)
|
|
56
|
-
|| (typeof entry.refresh === 'string' && entry.refresh.length > 0);
|
|
57
|
-
}
|
|
58
|
-
if (entry.type === 'wellknown') return typeof entry.token === 'string' && entry.token.length > 0;
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
32
|
export function parseModelRef(value) {
|
|
63
33
|
if (typeof value !== 'string') return null;
|
|
64
34
|
const trimmed = value.trim();
|
|
@@ -115,71 +85,25 @@ export function rankSmallModelCandidates(models) {
|
|
|
115
85
|
.map((entry) => entry.model);
|
|
116
86
|
}
|
|
117
87
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
if (!isUsableAuthEntry(auth.openai)) return null;
|
|
127
|
-
return family === 'gpt-nano'
|
|
128
|
-
? { providerID, modelID: OPENAI_OAUTH_SMALL_MODEL, source: 'codex-small' }
|
|
129
|
-
: null;
|
|
130
|
-
}
|
|
131
|
-
if (providerID === 'github-copilot') {
|
|
132
|
-
if (!isUsableAuthEntry(getAuthEntryForProvider(auth, 'github-copilot'))) return null;
|
|
133
|
-
return family === 'gpt-nano'
|
|
134
|
-
? { providerID, modelID: COPILOT_UTILITY_MODELS[0], source: 'copilot-utility' }
|
|
135
|
-
: null;
|
|
136
|
-
}
|
|
137
|
-
if (providerID === 'openai') {
|
|
138
|
-
if (auth.openai?.type !== 'api' || !isUsableAuthEntry(auth.openai)) return null;
|
|
139
|
-
}
|
|
140
|
-
if (providerID === 'anthropic') {
|
|
141
|
-
if (auth.anthropic?.type !== 'api' || !isUsableAuthEntry(auth.anthropic)) return null;
|
|
142
|
-
}
|
|
143
|
-
if (providerID === 'google') {
|
|
144
|
-
if (auth.google?.type !== 'api' || !isUsableAuthEntry(auth.google)) return null;
|
|
145
|
-
}
|
|
146
|
-
const provider = getCatalogProvider(catalog, providerID);
|
|
147
|
-
if (!provider || !provider.models || typeof provider.models !== 'object') return null;
|
|
148
|
-
const model = pickByFamily(provider.models, family);
|
|
88
|
+
const catalogModels = (catalog, providerID) => {
|
|
89
|
+
const models = getCatalogProvider(catalog, providerID)?.models;
|
|
90
|
+
return models && typeof models === 'object' ? models : null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const pickWithinProvider = (catalog, providerID, family) => {
|
|
94
|
+
const models = catalogModels(catalog, providerID);
|
|
95
|
+
const model = models ? pickByFamily(models, family) : null;
|
|
149
96
|
return model?.id ? { providerID, modelID: model.id, source: 'family-scan' } : null;
|
|
150
97
|
};
|
|
151
98
|
|
|
152
|
-
const pickKeywordDefault = (
|
|
99
|
+
const pickKeywordDefault = (catalog, providerIDs) => {
|
|
153
100
|
/** @type {Array<{ providerID: string, model: object }>} */
|
|
154
101
|
const candidates = [];
|
|
155
|
-
const providerIDs = Object.keys(auth || {}).filter((providerID) => {
|
|
156
|
-
if (providerID === 'copilot') return false;
|
|
157
|
-
if (excludeProviderID && providerID === excludeProviderID) return false;
|
|
158
|
-
return isUsableAuthEntry(getAuthEntryForProvider(auth, providerID));
|
|
159
|
-
});
|
|
160
|
-
// Surface github-copilot when only the `copilot` alias is present.
|
|
161
|
-
if (
|
|
162
|
-
!providerIDs.includes('github-copilot')
|
|
163
|
-
&& isUsableAuthEntry(getAuthEntryForProvider(auth, 'github-copilot'))
|
|
164
|
-
&& excludeProviderID !== 'github-copilot'
|
|
165
|
-
) {
|
|
166
|
-
providerIDs.push('github-copilot');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
102
|
for (const providerID of providerIDs) {
|
|
170
|
-
|
|
171
|
-
if (providerID === 'github-copilot') continue;
|
|
172
|
-
if (providerID === 'openai' && (auth.openai?.type !== 'api' || !isUsableAuthEntry(auth.openai))) continue;
|
|
173
|
-
if (providerID === 'anthropic' && (auth.anthropic?.type !== 'api' || !isUsableAuthEntry(auth.anthropic))) continue;
|
|
174
|
-
if (providerID === 'google' && (auth.google?.type !== 'api' || !isUsableAuthEntry(auth.google))) continue;
|
|
175
|
-
|
|
176
|
-
const provider = getCatalogProvider(catalog, providerID);
|
|
177
|
-
if (!provider?.models) continue;
|
|
178
|
-
for (const model of rankSmallModelCandidates(provider.models)) {
|
|
103
|
+
for (const model of rankSmallModelCandidates(catalogModels(catalog, providerID))) {
|
|
179
104
|
candidates.push({ providerID, model });
|
|
180
105
|
}
|
|
181
106
|
}
|
|
182
|
-
|
|
183
107
|
if (candidates.length === 0) return null;
|
|
184
108
|
candidates.sort((a, b) => {
|
|
185
109
|
const tierDiff = smallModelKeywordTier(a.model) - smallModelKeywordTier(b.model);
|
|
@@ -190,7 +114,16 @@ const pickKeywordDefault = (auth, catalog, excludeProviderID) => {
|
|
|
190
114
|
return { providerID: best.providerID, modelID: best.model.id, source: 'keyword-scan' };
|
|
191
115
|
};
|
|
192
116
|
|
|
193
|
-
|
|
117
|
+
/**
|
|
118
|
+
* @param {{
|
|
119
|
+
* catalog: object | null,
|
|
120
|
+
* settingsSmallModel?: string | null,
|
|
121
|
+
* configSmallModel?: string | null,
|
|
122
|
+
* preferredProviderID?: string,
|
|
123
|
+
* preferredModelID?: string,
|
|
124
|
+
* }} input
|
|
125
|
+
*/
|
|
126
|
+
export function resolveSmallModel({ catalog, settingsSmallModel, configSmallModel, preferredProviderID, preferredModelID }) {
|
|
194
127
|
// OpenChamber's own setting (Settings → Sessions → Small Model override)
|
|
195
128
|
// outranks everything, including the OpenCode config.
|
|
196
129
|
const fromSettings = parseModelRef(settingsSmallModel);
|
|
@@ -210,47 +143,29 @@ export function resolveSmallModel({ auth, catalog, settingsSmallModel, configSma
|
|
|
210
143
|
const preferred = typeof preferredProviderID === 'string' && preferredProviderID
|
|
211
144
|
? preferredProviderID
|
|
212
145
|
: null;
|
|
213
|
-
if (preferred &&
|
|
146
|
+
if (preferred && getCatalogProvider(catalog, preferred)) {
|
|
214
147
|
for (const family of FAMILY_PRIORITY) {
|
|
215
|
-
const match = pickWithinProvider(
|
|
148
|
+
const match = pickWithinProvider(catalog, preferred, family);
|
|
216
149
|
if (match) return match;
|
|
217
150
|
}
|
|
218
|
-
const
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
if (ranked[0]?.id) {
|
|
222
|
-
return { providerID: preferred, modelID: ranked[0].id, source: 'keyword-scan' };
|
|
223
|
-
}
|
|
151
|
+
const ranked = rankSmallModelCandidates(catalogModels(catalog, preferred));
|
|
152
|
+
if (ranked[0]?.id) {
|
|
153
|
+
return { providerID: preferred, modelID: ranked[0].id, source: 'keyword-scan' };
|
|
224
154
|
}
|
|
225
155
|
if (typeof preferredModelID === 'string' && preferredModelID) {
|
|
226
156
|
return { providerID: preferred, modelID: preferredModelID, source: 'session-model' };
|
|
227
157
|
}
|
|
228
158
|
}
|
|
229
159
|
|
|
230
|
-
// No session context (or its provider
|
|
231
|
-
//
|
|
232
|
-
const
|
|
233
|
-
providerID !== preferred && isUsableAuthEntry(auth[providerID]));
|
|
234
|
-
|
|
160
|
+
// No session context (or its provider is not connected): scan the other
|
|
161
|
+
// connected providers by family priority, then keyword+cost default.
|
|
162
|
+
const otherProviders = Object.keys(catalog || {}).filter((providerID) => providerID !== preferred);
|
|
235
163
|
for (const family of FAMILY_PRIORITY) {
|
|
236
|
-
for (const providerID of
|
|
237
|
-
const match = pickWithinProvider(
|
|
164
|
+
for (const providerID of otherProviders) {
|
|
165
|
+
const match = pickWithinProvider(catalog, providerID, family);
|
|
238
166
|
if (match) return match;
|
|
239
167
|
}
|
|
240
168
|
}
|
|
241
169
|
|
|
242
|
-
|
|
243
|
-
if (keywordDefault) return keywordDefault;
|
|
244
|
-
|
|
245
|
-
// Copilot's utility fallback for legacy auth aliases the loop above missed.
|
|
246
|
-
const copilotEntry = getAuthEntryForProvider(auth, 'github-copilot');
|
|
247
|
-
if (isUsableAuthEntry(copilotEntry)) {
|
|
248
|
-
return {
|
|
249
|
-
providerID: 'github-copilot',
|
|
250
|
-
modelID: COPILOT_UTILITY_MODELS[0],
|
|
251
|
-
source: 'copilot-utility',
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return null;
|
|
170
|
+
return pickKeywordDefault(catalog, otherProviders);
|
|
256
171
|
}
|