@vantran-se/openchamber-web 1.24.4 → 2.0.2
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 +2 -8
- package/bin/lib/commands-connect-url.js +3 -8
- package/bin/lib/commands-tunnel.js +3 -0
- package/dist/assets/{CodeMirrorCommentWidgets-B4a0JkUz.js → CodeMirrorCommentWidgets-B82Oye3V.js} +1 -1
- package/dist/assets/DiffView-DJLmWn6Z.js +4 -0
- package/dist/assets/FilesView-DjksmZRo.js +17 -0
- package/dist/assets/{GitView-Byu2DY7s.js → GitView-DiUsUIgr.js} +4 -4
- package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-CYwWYLr3.js} +1 -1
- package/dist/assets/GuestSurfacePane-CmctyRI4.js +1 -0
- package/dist/assets/{JsonTreeView-VW_7czo3.js → JsonTreeView-DOHHcbgJ.js} +1 -1
- package/dist/assets/LinearIssuesView-DaqnylcV.js +1 -0
- package/dist/assets/MarkdownImageGallery-HJNvWt7L.js +1 -0
- package/dist/assets/MarkdownRendererImpl-nDZRGg2I.js +12 -0
- package/dist/assets/OnboardingScreen-C2B5Bsof.js +1 -0
- package/dist/assets/{PatchDiff-DCjGhlYV.js → PatchDiff-C4FLBjpt.js} +1 -1
- package/dist/assets/{PierreDiffViewer-CEDZNkU2.js → PierreDiffViewer-C_1S9c9n.js} +1 -1
- package/dist/assets/PlanView-_eZvb-G5.js +5 -0
- package/dist/assets/PluginPane-sM8fcZIV.js +3 -0
- package/dist/assets/SettingsView-BRfr46hb.js +29 -0
- package/dist/assets/SettingsWindow-CcUDwQHg.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-CrN1ZGwl.js +1 -0
- package/dist/assets/ToolOutputDialog-Cij4jI09.js +15 -0
- package/dist/assets/ToolPartDiffPreview-BXzwQ-JY.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-NU0FHFhT.js → VirtualizedFileDiff-DSWTLJNH.js} +4 -4
- package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-CpUUgY8H.js} +2 -2
- package/dist/assets/appThemeRegistry-B5tHTkR8.js +1 -0
- package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-D7D8qV8p.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.js → computeVirtualFileMetrics-cq9uQ30i.js} +1 -1
- package/dist/assets/de-CUO1-KqF.js +10 -0
- package/dist/assets/es-BlbC6sGJ.js +16 -0
- package/dist/assets/fr-D2nwe_0W.js +14 -0
- package/dist/assets/index-BQOoYIOu.js +1 -0
- package/dist/assets/index-CLcM8HKx.css +1 -0
- package/dist/assets/index-CvLgnuNI.js +41 -0
- package/dist/assets/{isDiffPlainText-CO4e5J_S.js → isDiffPlainText-D0obJ2p8.js} +1 -1
- package/dist/assets/{isFilePlainText-ue6kCou7.js → isFilePlainText-DB3_rjyW.js} +1 -1
- package/dist/assets/ja-CA7vnDsZ.js +16 -0
- package/dist/assets/ko-Chy6a9l-.js +16 -0
- package/dist/assets/main-B9JFK3vF.js +896 -0
- package/dist/assets/main-Bhiqnrhd.js +2 -0
- package/dist/assets/{markdownCore-CK48LG0z.js → markdownCore-tfqOFaBd.js} +4 -4
- package/dist/assets/miniChat-B0UOL5zA.js +2 -0
- package/dist/assets/mobile-BJppFIEo.js +2 -0
- package/dist/assets/{patchFileDiff-D7QVlovD.js → patchFileDiff-Db6KuwCP.js} +3 -3
- package/dist/assets/pl-CTR5A_1P.js +16 -0
- package/dist/assets/pt-BR-BE1O1KEx.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-Yz5sfoFh.js +2 -0
- package/dist/assets/renderMobileApp-QQ-yB8aY.js +25 -0
- package/dist/assets/runtimeEndpointReset-DKDdzRzm.js +27 -0
- package/dist/assets/scheduledTasksApi-C5sCuNqf.js +1 -0
- package/dist/assets/{shikiHighlight-BPu3JiQs.js → shikiHighlight-xURoo5t_.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.js → textMateThemeFromAppTheme-BZLGuYTU.js} +1 -1
- package/dist/assets/tr-Bh5vK6cW.js +7 -0
- package/dist/assets/uk-CLDkZFy_.js +16 -0
- package/dist/assets/useAppFontEffects-BxrtCnfR.js +7337 -0
- package/dist/assets/{useInlineCommentController-CgS0fnw_.js → useInlineCommentController-CeXuR-SR.js} +2 -2
- package/dist/assets/useRootScrollLock-DgWxezc1.js +2 -0
- package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.js → vendor-codemirror-language-data-DOuaQUZ0.js} +2 -2
- package/dist/assets/vendor-cron-parser-CjvYVigk.js +2 -0
- package/dist/assets/vendor-opencode-client-_BAHQo4R.js +12 -0
- package/dist/assets/{vendor-remixicon-react-BDmGmFCA.js → vendor-remixicon-react-D6dZ0UwF.js} +1 -1
- package/dist/assets/{vendor-zod-CgAdXWZO.js → vendor-zod-Gea9uBlY.js} +1 -1
- package/dist/assets/{web-components-DxX8bZ1o.js → web-components-63H1h21j.js} +2 -2
- package/dist/assets/zh-CN-DODgcmYj.js +16 -0
- package/dist/assets/zh-TW-ByNfIwLA.js +16 -0
- package/dist/index.html +13 -7
- package/dist/mini-chat.html +5 -5
- package/dist/mobile.html +5 -5
- package/package.json +6 -5
- package/server/index.d.ts +1 -0
- package/server/index.js +246 -91
- package/server/lib/agent-tool/DOCUMENTATION.md +32 -15
- package/server/lib/agent-tool/runtime.js +135 -54
- package/server/lib/agent-tool/runtime.test.js +155 -87
- package/server/lib/browser-control/DOCUMENTATION.md +37 -2
- package/server/lib/browser-control/provider.js +217 -0
- package/server/lib/browser-control/provider.test.js +251 -0
- package/server/lib/context-obligatory/runtime.js +61 -50
- package/server/lib/context-obligatory/runtime.test.js +62 -162
- package/server/lib/event-stream/DOCUMENTATION.md +12 -1
- package/server/lib/event-stream/delta-coalescer.js +0 -0
- package/server/lib/event-stream/delta-coalescer.test.js +68 -57
- package/server/lib/event-stream/directory-ws-bridge.js +1 -1
- package/server/lib/event-stream/global-hub.js +10 -1
- package/server/lib/event-stream/global-hub.test.js +27 -26
- package/server/lib/event-stream/index.js +6 -0
- package/server/lib/event-stream/protocol.js +26 -19
- package/server/lib/event-stream/protocol.test.js +18 -7
- package/server/lib/event-stream/rebind.test.js +5 -5
- package/server/lib/event-stream/runtime.test.js +17 -15
- package/server/lib/event-stream/translate-v2.js +313 -0
- package/server/lib/event-stream/translate-v2.test.js +209 -0
- package/server/lib/event-stream/upstream-reader.test.js +16 -16
- package/server/lib/fs/DOCUMENTATION.md +4 -2
- package/server/lib/fs/byte-range.js +35 -0
- package/server/lib/fs/byte-range.test.js +31 -0
- package/server/lib/fs/routes.js +47 -42
- package/server/lib/fs/routes.test.js +123 -18
- package/server/lib/git/service.js +12 -0
- package/server/lib/github/DOCUMENTATION.md +1 -1
- package/server/lib/github/pr-status.js +22 -2
- package/server/lib/github/pr-status.test.js +27 -14
- package/server/lib/guests/DOCUMENTATION.md +13 -0
- package/server/lib/guests/catalog.js +12 -0
- package/server/lib/guests/catalog.test.js +33 -0
- package/server/lib/guests/routes.js +10 -1
- package/server/lib/guests/service.js +192 -14
- package/server/lib/guests/service.test.js +201 -1
- package/server/lib/guests/surface.js +630 -0
- package/server/lib/guests/surface.test.js +486 -0
- package/server/lib/markdown-image-grants/routes.js +18 -5
- package/server/lib/markdown-image-grants/routes.test.js +9 -6
- package/server/lib/message-queue/DOCUMENTATION.md +33 -22
- package/server/lib/message-queue/runtime.js +155 -100
- package/server/lib/message-queue/runtime.test.js +168 -82
- package/server/lib/notifications/DOCUMENTATION.md +28 -0
- package/server/lib/notifications/control-stream.http.test.js +68 -0
- package/server/lib/notifications/emitter-runtime.js +15 -0
- package/server/lib/notifications/emitter-runtime.test.js +32 -0
- package/server/lib/notifications/push-runtime.js +5 -1
- package/server/lib/notifications/push-runtime.test.js +2 -2
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +115 -26
- package/server/lib/notifications/template-runtime.js +13 -15
- package/server/lib/notifications/template-runtime.test.js +16 -9
- package/server/lib/notifications/trigger-runtime.test.js +187 -0
- package/server/lib/openchamber-control/DOCUMENTATION.md +10 -0
- package/server/lib/openchamber-control/actions.js +3 -2
- package/server/lib/openchamber-control/error.js +1 -1
- package/server/lib/openchamber-control/file-open.js +59 -0
- package/server/lib/openchamber-control/file-open.test.js +57 -0
- package/server/lib/openchamber-control/routes.test.js +12 -0
- package/server/lib/openchamber-control/service.js +102 -49
- package/server/lib/openchamber-control/service.test.js +84 -44
- package/server/lib/openchamber-sessions/archive-store.js +224 -0
- package/server/lib/openchamber-sessions/archive-store.test.js +187 -0
- package/server/lib/openchamber-sessions/opencode-client.js +22 -0
- package/server/lib/openchamber-sessions/routes.js +398 -328
- package/server/lib/openchamber-sessions/routes.test.js +777 -740
- package/server/lib/openchamber-sessions/session-metadata-store.js +285 -0
- package/server/lib/openchamber-sessions/session-metadata-store.test.js +259 -0
- package/server/lib/opencode/DOCUMENTATION.md +449 -29
- package/server/lib/opencode/agents.js +233 -327
- package/server/lib/opencode/auth.js +41 -44
- package/server/lib/opencode/auth.test.js +92 -0
- package/server/lib/opencode/cli-upgrade.d.ts +4 -0
- package/server/lib/opencode/cli-upgrade.js +18 -0
- package/server/lib/opencode/cli-upgrade.test.js +36 -0
- package/server/lib/opencode/commands.js +164 -145
- package/server/lib/opencode/compatibility.d.ts +16 -0
- package/server/lib/opencode/compatibility.js +88 -0
- package/server/lib/opencode/compatibility.test.js +62 -0
- package/server/lib/opencode/config-entity-routes.js +58 -29
- package/server/lib/opencode/config-entity-v2.test.js +290 -0
- package/server/lib/opencode/config-mutation-response.js +17 -7
- package/server/lib/opencode/config-mutation-response.test.js +4 -7
- package/server/lib/opencode/config-paths.test.js +2 -2
- package/server/lib/opencode/config-v2.d.ts +183 -0
- package/server/lib/opencode/config-v2.js +801 -0
- package/server/lib/opencode/config-v2.test.js +466 -0
- package/server/lib/opencode/credential-db.js +144 -0
- package/server/lib/opencode/credential-db.test.js +104 -0
- package/server/lib/opencode/env-runtime.js +48 -9
- package/server/lib/opencode/env-runtime.test.js +45 -0
- package/server/lib/opencode/feature-routes-runtime.js +15 -5
- package/server/lib/opencode/lifecycle-process.test.js +7 -4
- package/server/lib/opencode/lifecycle.js +85 -30
- package/server/lib/opencode/lifecycle.test.js +80 -8
- package/server/lib/opencode/login-shell-env.js +27 -0
- package/server/lib/opencode/managed-config-file.js +119 -0
- package/server/lib/opencode/managed-config-file.test.js +152 -0
- package/server/lib/opencode/managed-plugin-config.js +33 -13
- package/server/lib/opencode/mcp.js +43 -149
- package/server/lib/opencode/network-runtime.js +6 -7
- package/server/lib/opencode/openchamber-routes.test.js +2 -5
- package/server/lib/opencode/plugin-routes.js +3 -3
- package/server/lib/opencode/plugin-routes.test.js +6 -24
- package/server/lib/opencode/plugins.js +91 -40
- package/server/lib/opencode/plugins.test.js +28 -15
- package/server/lib/opencode/providers.js +63 -83
- package/server/lib/opencode/providers.test.js +128 -59
- package/server/lib/opencode/proxy-agent-wiring.test.js +4 -2
- package/server/lib/opencode/proxy.js +179 -129
- package/server/lib/opencode/pwa-manifest-routes.js +4 -2
- package/server/lib/opencode/pwa-manifest-routes.test.js +3 -2
- package/server/lib/opencode/response-envelope.js +14 -0
- package/server/lib/opencode/response-envelope.test.js +23 -0
- package/server/lib/opencode/routes-upgrade.test.js +73 -116
- package/server/lib/opencode/routes.js +121 -428
- package/server/lib/opencode/server-utils-runtime.js +10 -4
- package/server/lib/opencode/session-activity.js +78 -0
- package/server/lib/opencode/session-runtime.js +21 -10
- package/server/lib/opencode/session-runtime.test.js +9 -9
- package/server/lib/opencode/settings-helpers.js +9 -9
- package/server/lib/opencode/settings-helpers.test.js +7 -12
- package/server/lib/opencode/settings-registry.json +6 -8
- package/server/lib/opencode/settings-runtime.js +18 -1
- package/server/lib/opencode/settings-runtime.test.js +31 -2
- package/server/lib/opencode/shared.js +54 -22
- package/server/lib/opencode/shared.test.js +8 -4
- package/server/lib/opencode/shutdown-runtime.js +54 -7
- package/server/lib/opencode/shutdown-runtime.test.js +224 -30
- package/server/lib/opencode/skill-routes.js +23 -24
- package/server/lib/opencode/skills.js +7 -5
- package/server/lib/opencode/tunnel-wiring-runtime.js +2 -1
- package/server/lib/opencode/upgrade-capability.test.js +1 -1
- package/server/lib/opencode/v1-migration-topup.js +288 -0
- package/server/lib/opencode/v1-migration-topup.test.js +266 -0
- package/server/lib/opencode/v2-install.d.ts +8 -0
- package/server/lib/opencode/v2-install.js +158 -0
- package/server/lib/opencode/v2-install.test.js +124 -0
- package/server/lib/opencode/watcher.js +10 -3
- package/server/lib/opencode/watcher.test.js +17 -21
- package/server/lib/opencode/websearch-config.js +66 -0
- package/server/lib/package-manager.js +55 -68
- package/server/lib/package-manager.test.js +241 -75
- package/server/lib/permission-auto-accept/runtime.js +38 -22
- package/server/lib/permission-auto-accept/runtime.test.js +39 -17
- package/server/lib/quota/DOCUMENTATION.md +4 -0
- package/server/lib/quota/providers/xai.js +4 -4
- package/server/lib/relay/tunnel-host.js +4 -1
- package/server/lib/relay/tunnel-host.test.js +7 -0
- package/server/lib/routing/DOCUMENTATION.md +52 -21
- package/server/lib/routing/defaults.js +23 -6
- package/server/lib/routing/jev.js +17 -7
- package/server/lib/routing/routes.http.test.js +68 -33
- package/server/lib/routing/routes.js +65 -22
- package/server/lib/routing/runtime.js +145 -48
- package/server/lib/routing/runtime.test.js +79 -55
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +4 -0
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +51 -46
- package/server/lib/scheduled-tasks/runtime.js +81 -74
- package/server/lib/scheduled-tasks/runtime.test.js +53 -1
- package/server/lib/session-assist/DOCUMENTATION.md +8 -3
- package/server/lib/session-assist/context.js +122 -46
- package/server/lib/session-assist/context.test.js +117 -49
- package/server/lib/session-assist/runtime.js +52 -22
- package/server/lib/session-assist/runtime.test.js +145 -173
- package/server/lib/session-goal/DOCUMENTATION.md +23 -15
- package/server/lib/session-goal/create.js +9 -12
- package/server/lib/session-goal/create.test.js +27 -28
- package/server/lib/session-goal/runtime.js +164 -77
- package/server/lib/session-goal/runtime.test.js +271 -519
- package/server/lib/session-knowledge/DOCUMENTATION.md +5 -0
- package/server/lib/session-knowledge/runtime.js +59 -41
- package/server/lib/session-knowledge/runtime.test.js +90 -20
- package/server/lib/small-model/DOCUMENTATION.md +154 -208
- package/server/lib/small-model/client.js +113 -0
- package/server/lib/small-model/index.js +296 -158
- package/server/lib/small-model/index.test.js +423 -256
- package/server/lib/small-model/routes.js +1 -1
- package/server/lib/spaces/DOCUMENTATION.md +807 -0
- package/server/lib/spaces/code-in-bait.js +185 -0
- package/server/lib/spaces/code-in.js +641 -0
- package/server/lib/spaces/code-in.test.js +1048 -0
- package/server/lib/spaces/errors.js +8 -0
- package/server/lib/spaces/exec-http.js +94 -0
- package/server/lib/spaces/gatekeeper-channel.js +202 -0
- package/server/lib/spaces/gatekeeper-channel.test.js +215 -0
- package/server/lib/spaces/gatekeeper-program.cjs +914 -0
- package/server/lib/spaces/gatekeeper-program.test.js +1391 -0
- package/server/lib/spaces/hardening.js +457 -0
- package/server/lib/spaces/hardening.test.js +368 -0
- package/server/lib/spaces/host-git.js +127 -0
- package/server/lib/spaces/host-git.test.js +137 -0
- package/server/lib/spaces/labels.js +234 -0
- package/server/lib/spaces/labels.test.js +219 -0
- package/server/lib/spaces/layout.js +184 -0
- package/server/lib/spaces/layout.test.js +30 -0
- package/server/lib/spaces/manager.js +82 -0
- package/server/lib/spaces/manager.test.js +114 -0
- package/server/lib/spaces/places/code-in.docker.live.test.js +216 -0
- package/server/lib/spaces/places/contract-suite.js +156 -0
- package/server/lib/spaces/places/contract.docker.live.test.js +7 -0
- package/server/lib/spaces/places/contract.test.js +6 -0
- package/server/lib/spaces/places/docker-engine.js +126 -0
- package/server/lib/spaces/places/docker-live-support.js +202 -0
- package/server/lib/spaces/places/docker-tools.js +186 -0
- package/server/lib/spaces/places/docker.js +587 -0
- package/server/lib/spaces/places/docker.test.js +1610 -0
- package/server/lib/spaces/places/escape-suite.js +1018 -0
- package/server/lib/spaces/places/escape.docker.live.test.js +7 -0
- package/server/lib/spaces/places/fake-docker.js +375 -0
- package/server/lib/spaces/places/memory-place.js +103 -0
- package/server/lib/spaces/places/packed.docker.live.test.js +77 -0
- package/server/lib/spaces/places/registry.js +43 -0
- package/server/lib/spaces/places/registry.test.js +50 -0
- package/server/lib/spaces/places/server.docker.live.test.js +225 -0
- package/server/lib/spaces/run-command.js +261 -0
- package/server/lib/spaces/run-command.test.js +396 -0
- package/server/lib/spaces/space-server.js +190 -0
- package/server/lib/spaces/space-server.test.js +335 -0
- package/server/lib/spaces/tools-filler.js +82 -0
- package/server/lib/spaces/tools-filler.test.js +148 -0
- package/server/lib/spaces/tools-pack.js +49 -0
- package/server/lib/spaces/tools-pack.test.js +57 -0
- package/server/lib/spaces/tools.js +138 -0
- package/server/lib/spaces/tools.test.js +153 -0
- package/server/lib/tts/openai-sdk.js +11 -0
- package/server/lib/tts/service.js +7 -5
- package/server/lib/tts/stt.js +3 -2
- package/server/lib/tunnels/DOCUMENTATION.md +2 -0
- package/server/lib/tunnels/routes.js +7 -0
- package/server/lib/tunnels/routes.test.js +55 -0
- package/server/lib/ui-auth/ui-auth.js +38 -2
- package/server/lib/ui-auth/ui-auth.test.js +42 -0
- package/server/lib/ui-auth/ui-passkeys.js +12 -10
- package/server/lib/walkthrough/DOCUMENTATION.md +10 -1
- package/server/lib/walkthrough/pull-request.js +61 -8
- package/server/lib/walkthrough/pull-request.test.js +54 -1
- package/server/lib/walkthrough/routes.js +20 -0
- package/server/lib/walkthrough/routes.test.js +19 -0
- package/server/opencode-proxy.test.js +190 -166
- package/server/opencode-worktree-readiness.test.js +5 -4
- package/dist/assets/DiffView-B2CD9wC8.js +0 -4
- package/dist/assets/FilesView-C4vMFclc.js +0 -13
- package/dist/assets/LinearIssuesView-EzGNCGrd.js +0 -1
- package/dist/assets/MarkdownImageGallery-B2YUDFaB.js +0 -1
- package/dist/assets/MarkdownRendererImpl-B4ikIzR3.js +0 -12
- package/dist/assets/OnboardingScreen-CQHBSTEi.js +0 -1
- package/dist/assets/PlanView-D3x-pTkZ.js +0 -5
- package/dist/assets/PluginPane-DEcgDQ9h.js +0 -3
- package/dist/assets/SettingsView-CSinuH-1.js +0 -29
- package/dist/assets/SettingsWindow-B5CHoJFZ.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-oxeACCWs.js +0 -1
- package/dist/assets/ToolOutputDialog-tLNWmfcW.js +0 -15
- package/dist/assets/ToolPartDiffPreview-CflzmK-x.js +0 -8
- package/dist/assets/appThemeRegistry-DLicw4dH.js +0 -1
- package/dist/assets/de-Bro6_zyl.js +0 -10
- package/dist/assets/es-Bb9XQZvd.js +0 -16
- package/dist/assets/fr-Dgfzn-IL.js +0 -14
- package/dist/assets/index-CtQNmqUL.js +0 -1
- package/dist/assets/index-DqaMWNrC.css +0 -1
- package/dist/assets/index-Dqc6USl3.js +0 -40
- package/dist/assets/ja-BYsakGFh.js +0 -16
- package/dist/assets/ko-BSaQz9aN.js +0 -16
- package/dist/assets/main-B62_pjL5.js +0 -2
- package/dist/assets/main-BEdcOI2P.js +0 -896
- package/dist/assets/miniChat-LT8Rqofr.js +0 -2
- package/dist/assets/mobile-CqarEynh.js +0 -2
- package/dist/assets/pl-OIviPdoj.js +0 -16
- package/dist/assets/pt-BR-Cltg-E6s.js +0 -16
- package/dist/assets/renderElectronMiniChatApp-COyLTtZx.js +0 -2
- package/dist/assets/renderMobileApp-BC1MHg6g.js +0 -25
- package/dist/assets/runtimeEndpointReset-DdrazOVH.js +0 -25
- package/dist/assets/scheduledTasksApi-B2SWMGEU.js +0 -1
- package/dist/assets/tr-CQI-KPre.js +0 -7
- package/dist/assets/uk-CqW7R4DA.js +0 -16
- package/dist/assets/useAppFontEffects-ne84JBVL.js +0 -7333
- package/dist/assets/useRootScrollLock-Cpf1qoGp.js +0 -2
- package/dist/assets/vendor-cron-parser-CUcP7OfX.js +0 -2
- package/dist/assets/vendor-opencode-sdk-BJ73u-bH.js +0 -7
- package/dist/assets/zh-CN-CVZG_o_J.js +0 -16
- package/dist/assets/zh-TW-D5ZxzCZp.js +0 -16
- package/server/lib/opencode/mcp-oauth-callback.test.js +0 -111
- package/server/lib/routing/feature-flag.js +0 -11
- package/server/lib/small-model/call.js +0 -851
- package/server/lib/small-model/call.test.js +0 -1292
- package/server/lib/small-model/catalog.js +0 -13
- package/server/lib/small-model/resolve.js +0 -131
- package/server/lib/small-model/resolve.test.js +0 -197
- package/server/lib/small-model/runtime-providers.js +0 -164
- package/server/lib/small-model/runtime-providers.test.js +0 -118
- package/server/lib/system-prompt/DOCUMENTATION.md +0 -36
- package/server/lib/system-prompt/runtime.js +0 -49
- package/server/lib/system-prompt/runtime.test.js +0 -76
- package/server/lib/walkthrough/reproduce-2607.test.js +0 -148
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
3
|
import os from 'os';
|
|
3
4
|
import path from 'path';
|
|
4
|
-
import { readAuthFile } from '../opencode/auth.js';
|
|
5
|
-
import { readConfigLayers } from '../opencode/shared.js';
|
|
6
|
-
import { getModelCatalog } from './catalog.js';
|
|
7
|
-
import { resolveSmallModel, parseModelRef, isUsableAuthEntry, getAuthEntryForProvider } from './resolve.js';
|
|
8
|
-
import { DEDICATED_WIRE_FORMAT_PROVIDERS, callSmallModel, resolveProviderLogin } from './call.js';
|
|
9
5
|
import { readMergedSettingsSync } from '../opencode/settings-files.js';
|
|
10
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
findModelInfo,
|
|
8
|
+
getDefaultModelInfo,
|
|
9
|
+
getSmallModelClient,
|
|
10
|
+
listModelInfos,
|
|
11
|
+
listProviderInfos,
|
|
12
|
+
} from './client.js';
|
|
11
13
|
|
|
12
14
|
// Never a small model, whatever the transport looks like. A plugin can publish
|
|
13
15
|
// an OpenAI-compatible endpoint for Claude Code, but it is a façade over the
|
|
@@ -17,6 +19,8 @@ import { getRuntimeProviderSnapshot } from './runtime-providers.js';
|
|
|
17
19
|
// conditional on an endpoint existing.
|
|
18
20
|
const CLAUDE_CODE_PROVIDER = 'claude-code';
|
|
19
21
|
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
23
|
+
|
|
20
24
|
const OPENCHAMBER_SETTINGS_FILE = path.join(
|
|
21
25
|
process.env.OPENCHAMBER_DATA_DIR
|
|
22
26
|
? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
|
|
@@ -25,7 +29,7 @@ const OPENCHAMBER_SETTINGS_FILE = path.join(
|
|
|
25
29
|
);
|
|
26
30
|
|
|
27
31
|
// OpenChamber's own settings: when the user unchecks "use default small model"
|
|
28
|
-
// their explicit override outranks
|
|
32
|
+
// their explicit override outranks the model OpenCode would pick.
|
|
29
33
|
const readSmallModelSettingsOverride = () => {
|
|
30
34
|
const settings = readMergedSettingsSync({ fs, path, settingsFilePath: OPENCHAMBER_SETTINGS_FILE });
|
|
31
35
|
if (settings.smallModelUseDefault !== false) return null;
|
|
@@ -33,10 +37,20 @@ const readSmallModelSettingsOverride = () => {
|
|
|
33
37
|
return override || null;
|
|
34
38
|
};
|
|
35
39
|
|
|
40
|
+
export function parseModelRef(value) {
|
|
41
|
+
if (typeof value !== 'string') return null;
|
|
42
|
+
const trimmed = value.trim();
|
|
43
|
+
const slash = trimmed.indexOf('/');
|
|
44
|
+
if (slash <= 0 || slash === trimmed.length - 1) return null;
|
|
45
|
+
return {
|
|
46
|
+
providerID: trimmed.slice(0, slash),
|
|
47
|
+
modelID: trimmed.slice(slash + 1),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
// Rough safety clamp so a huge input never blows the model's context window.
|
|
37
|
-
// Token estimate is ~4 chars/token; when
|
|
38
|
-
// model
|
|
39
|
-
// applies.
|
|
52
|
+
// Token estimate is ~4 chars/token; when OpenCode reports no limit for the
|
|
53
|
+
// model a conservative default applies.
|
|
40
54
|
const DEFAULT_CONTEXT_TOKENS = 64_000;
|
|
41
55
|
const OUTPUT_RESERVE_TOKENS = 4_000;
|
|
42
56
|
|
|
@@ -45,10 +59,10 @@ const OUTPUT_RESERVE_TOKENS = 4_000;
|
|
|
45
59
|
* to leave for the answer. The reserve must match the output budget the caller
|
|
46
60
|
* will actually request, or the two disagree and the model overruns its context.
|
|
47
61
|
*/
|
|
48
|
-
export const getModelInputCharBudget = ({
|
|
49
|
-
const
|
|
50
|
-
const known =
|
|
51
|
-
const contextTokens = known ?
|
|
62
|
+
export const getModelInputCharBudget = ({ modelInfo, outputReserveTokens }) => {
|
|
63
|
+
const context = Number(modelInfo?.limit?.context);
|
|
64
|
+
const known = context > 0;
|
|
65
|
+
const contextTokens = known ? context : DEFAULT_CONTEXT_TOKENS;
|
|
52
66
|
const reserve = Number(outputReserveTokens) > 0 ? Number(outputReserveTokens) : OUTPUT_RESERVE_TOKENS;
|
|
53
67
|
const inputBudgetTokens = Math.max(1_000, contextTokens - reserve);
|
|
54
68
|
return { maxChars: inputBudgetTokens * 4, contextTokens, contextKnown: known };
|
|
@@ -56,21 +70,24 @@ export const getModelInputCharBudget = ({ catalog, providerID, modelID, outputRe
|
|
|
56
70
|
|
|
57
71
|
/**
|
|
58
72
|
* The output budget to actually request: what the caller asked for, capped by
|
|
59
|
-
* what the model admits it can emit.
|
|
60
|
-
*
|
|
73
|
+
* what the model admits it can emit.
|
|
74
|
+
*
|
|
75
|
+
* `/api/experimental/generate` takes no output budget of its own, so this number only
|
|
76
|
+
* shapes the input reserve — but it has to stay the same number on both sides
|
|
77
|
+
* or a caller that asks for a large answer overruns the context.
|
|
61
78
|
*/
|
|
62
|
-
const resolveOutputTokens = ({
|
|
79
|
+
const resolveOutputTokens = ({ modelInfo, maxOutputTokens }) => {
|
|
63
80
|
const requested = Number(maxOutputTokens) > 0 ? Number(maxOutputTokens) : 0;
|
|
64
81
|
if (!requested) return undefined;
|
|
65
|
-
const limit = Number(
|
|
82
|
+
const limit = Number(modelInfo?.limit?.output);
|
|
66
83
|
return limit > 0 ? Math.min(requested, limit) : requested;
|
|
67
84
|
};
|
|
68
85
|
|
|
69
86
|
// `truncate` keeps the historical behavior for callers whose prompt losing its
|
|
70
87
|
// tail is survivable (summaries, commit messages). `error` is for callers whose
|
|
71
88
|
// output would be quietly wrong on a clipped input — they need the failure.
|
|
72
|
-
const clampPromptToModelLimit = ({ prompt,
|
|
73
|
-
const { maxChars } = getModelInputCharBudget({
|
|
89
|
+
const clampPromptToModelLimit = ({ prompt, modelInfo, providerID, modelID, onOverflow, outputReserveTokens }) => {
|
|
90
|
+
const { maxChars } = getModelInputCharBudget({ modelInfo, outputReserveTokens });
|
|
74
91
|
if (prompt.length <= maxChars) {
|
|
75
92
|
return { prompt, truncated: false };
|
|
76
93
|
}
|
|
@@ -83,43 +100,162 @@ const clampPromptToModelLimit = ({ prompt, catalog, providerID, modelID, onOverf
|
|
|
83
100
|
return { prompt: `${prompt.slice(0, maxChars)}…`, truncated: true };
|
|
84
101
|
};
|
|
85
102
|
|
|
86
|
-
const
|
|
103
|
+
const noClientError = () => Object.assign(
|
|
104
|
+
new Error('No small model available — OpenCode is not reachable'),
|
|
105
|
+
{ statusCode: 404 },
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The model families that count as "small", most preferred first. The same
|
|
110
|
+
* list OpenCode uses for its own session titles (`Catalog.model.small` in
|
|
111
|
+
* `packages/core/src/catalog.ts`); OpenCode does not expose that lookup over
|
|
112
|
+
* HTTP, so the scan is repeated here on `GET /api/model`.
|
|
113
|
+
*/
|
|
114
|
+
export const SMALL_MODEL_FAMILY_PRIORITY = ['gpt-luna', 'gemini-flash-lite', 'gemini-flash', 'claude-haiku', 'gpt-nano', 'gpt-mini'];
|
|
115
|
+
// The last two are not on OpenCode's list; v1 counted them as small and a
|
|
116
|
+
// provider with nothing else cheap (Copilot's utility models, for one)
|
|
117
|
+
// would otherwise fall through to the session's big model.
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A model's family: the catalog's `family` (models.dev) when it has one,
|
|
121
|
+
* else read from the id. A custom provider or a subscription outside the
|
|
122
|
+
* catalog has no `family`, yet its `gemini-3.6-flash` is still a flash.
|
|
123
|
+
*/
|
|
124
|
+
export const familyOf = (model) => {
|
|
125
|
+
if (model?.family) return String(model.family);
|
|
126
|
+
const id = String(model?.id ?? '').toLowerCase();
|
|
127
|
+
if (id.includes('luna')) return 'gpt-luna';
|
|
128
|
+
if (id.includes('flash-lite') || id.includes('flash_lite')) return 'gemini-flash-lite';
|
|
129
|
+
if (id.includes('flash')) return 'gemini-flash';
|
|
130
|
+
if (id.includes('haiku')) return 'claude-haiku';
|
|
131
|
+
if (id.includes('nano')) return 'gpt-nano';
|
|
132
|
+
if (id.includes('mini') && !id.includes('minimax')) return 'gpt-mini';
|
|
133
|
+
return null;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The small model within one provider: the newest enabled, active, text-in
|
|
138
|
+
* text-out model of the first family in `SMALL_MODEL_FAMILY_PRIORITY` that the
|
|
139
|
+
* provider has. Null when the provider has none of those families.
|
|
140
|
+
*/
|
|
141
|
+
export const pickSmallModelInProvider = (models, providerID) => pickSmallModel(models, (model) => model.providerID === providerID);
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The small model across every provider OpenCode can call: same family
|
|
145
|
+
* order, newest release first within a family. What v1 did after the
|
|
146
|
+
* session provider came up empty; for callers without a session (commit
|
|
147
|
+
* messages, spoken summaries) it is the first place to look.
|
|
148
|
+
*/
|
|
149
|
+
export const pickSmallModelAnywhere = (models) => pickSmallModel(models, () => true);
|
|
150
|
+
|
|
151
|
+
const pickSmallModel = (models, accept) => {
|
|
152
|
+
const candidates = models
|
|
153
|
+
.filter((model) => model && accept(model)
|
|
154
|
+
&& model.providerID !== CLAUDE_CODE_PROVIDER
|
|
155
|
+
&& model.enabled !== false
|
|
156
|
+
&& (model.status === undefined || model.status === 'active')
|
|
157
|
+
&& (model.capabilities?.input ?? ['text']).some((item) => String(item).startsWith('text'))
|
|
158
|
+
&& (model.capabilities?.output ?? ['text']).some((item) => String(item).startsWith('text')))
|
|
159
|
+
.sort((a, b) => (Number(b.time?.released) || 0) - (Number(a.time?.released) || 0));
|
|
160
|
+
for (const family of SMALL_MODEL_FAMILY_PRIORITY) {
|
|
161
|
+
const found = candidates.find((model) => familyOf(model) === family);
|
|
162
|
+
if (found) return { providerID: found.providerID, modelID: found.id };
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Which model this call runs on, in order:
|
|
169
|
+
*
|
|
170
|
+
* 1. An explicit request model.
|
|
171
|
+
* 2. OpenChamber's settings override (Settings → Sessions → Small Model).
|
|
172
|
+
* 3. The small model of the session's provider (family scan above) —
|
|
173
|
+
* `session-provider-small`. A caller that must not leave that provider
|
|
174
|
+
* then falls back to the session's own model (`session-model`): costlier
|
|
175
|
+
* than a small model elsewhere, but never someone else's subscription.
|
|
176
|
+
* 4. The small model of any provider OpenCode can call, newest first —
|
|
177
|
+
* `small`.
|
|
178
|
+
* 5. `GET /api/model/default`: OpenCode's default model — `default`. This is
|
|
179
|
+
* the chat default, not a small model; OpenCode's own small-model chain is
|
|
180
|
+
* not reachable over HTTP, which is why steps 3 and 4 live here.
|
|
181
|
+
*/
|
|
182
|
+
const resolveSmallModel = async ({ client, directory, model, preferredProviderID, preferredModelID, restrictToPreferredProvider }) => {
|
|
183
|
+
const explicit = parseModelRef(model);
|
|
184
|
+
if (explicit) return { ...explicit, source: 'request' };
|
|
185
|
+
|
|
186
|
+
const fromSettings = parseModelRef(readSmallModelSettingsOverride());
|
|
187
|
+
if (fromSettings) return { ...fromSettings, source: 'settings' };
|
|
188
|
+
|
|
189
|
+
const models = await listModelInfos(client, directory);
|
|
190
|
+
if (preferredProviderID) {
|
|
191
|
+
const small = pickSmallModelInProvider(models, preferredProviderID);
|
|
192
|
+
if (small) return { ...small, source: 'session-provider-small' };
|
|
193
|
+
}
|
|
194
|
+
if (restrictToPreferredProvider && preferredProviderID && preferredModelID) {
|
|
195
|
+
return { providerID: preferredProviderID, modelID: preferredModelID, source: 'session-model' };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const anywhere = pickSmallModelAnywhere(models);
|
|
199
|
+
if (anywhere) return { ...anywhere, source: 'small' };
|
|
200
|
+
|
|
201
|
+
const fallback = await getDefaultModelInfo(client);
|
|
202
|
+
if (!fallback) return null;
|
|
203
|
+
return { providerID: fallback.providerID, modelID: fallback.id, source: 'default' };
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const JSON_FENCE = /^```(?:json)?\s*\n?([\s\S]*?)\n?```$/;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* `/api/experimental/generate` has no structured-output mode, so the schema travels in the
|
|
210
|
+
* prompt and the reply is parsed here.
|
|
211
|
+
*/
|
|
212
|
+
const buildSchemaInstruction = (responseSchema) =>
|
|
213
|
+
`Reply with JSON matching this schema and nothing else: ${JSON.stringify(responseSchema)}`;
|
|
214
|
+
|
|
215
|
+
const extractJsonText = (raw) => {
|
|
216
|
+
const trimmed = typeof raw === 'string' ? raw.trim() : '';
|
|
217
|
+
if (!trimmed) return null;
|
|
218
|
+
const fenced = JSON_FENCE.exec(trimmed);
|
|
219
|
+
const candidate = (fenced ? fenced[1] : trimmed).trim();
|
|
87
220
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return typeof value === 'string' ? value : null;
|
|
221
|
+
JSON.parse(candidate);
|
|
222
|
+
return candidate;
|
|
91
223
|
} catch {
|
|
92
224
|
return null;
|
|
93
225
|
}
|
|
94
226
|
};
|
|
95
227
|
|
|
228
|
+
const requestOptions = ({ timeoutMs, signal }) => {
|
|
229
|
+
const timeout = Number(timeoutMs) > 0 ? Number(timeoutMs) : DEFAULT_TIMEOUT_MS;
|
|
230
|
+
const signals = [AbortSignal.timeout(timeout)];
|
|
231
|
+
if (signal) signals.push(signal);
|
|
232
|
+
return { signal: AbortSignal.any(signals) };
|
|
233
|
+
};
|
|
234
|
+
|
|
96
235
|
/**
|
|
97
|
-
* Generates text with the user's small model
|
|
98
|
-
*
|
|
236
|
+
* Generates text with the user's small model through the running OpenCode.
|
|
237
|
+
* Credentials stay inside OpenCode; this server only sends a prompt.
|
|
99
238
|
*/
|
|
100
|
-
export async function generateSmallModelText({ prompt, system, maxOutputTokens, model, directory,
|
|
239
|
+
export async function generateSmallModelText({ prompt, system, maxOutputTokens, model, directory, preferredProviderID, preferredModelID, restrictToPreferredProvider = false, responseSchema, timeoutMs, signal, onOverflow = 'truncate' }) {
|
|
101
240
|
if (typeof prompt !== 'string' || !prompt.trim()) {
|
|
102
241
|
throw Object.assign(new Error('prompt is required'), { statusCode: 400 });
|
|
103
242
|
}
|
|
104
243
|
|
|
105
|
-
const
|
|
106
|
-
|
|
244
|
+
const client = getSmallModelClient(directory);
|
|
245
|
+
if (!client) throw noClientError();
|
|
107
246
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
preferredProviderID,
|
|
117
|
-
preferredModelID,
|
|
118
|
-
});
|
|
247
|
+
const resolved = await resolveSmallModel({
|
|
248
|
+
client,
|
|
249
|
+
directory,
|
|
250
|
+
model,
|
|
251
|
+
preferredProviderID,
|
|
252
|
+
preferredModelID,
|
|
253
|
+
restrictToPreferredProvider,
|
|
254
|
+
});
|
|
119
255
|
|
|
120
256
|
if (!resolved) {
|
|
121
257
|
throw Object.assign(
|
|
122
|
-
new Error('No small model available —
|
|
258
|
+
new Error('No small model available — OpenCode reports no default model'),
|
|
123
259
|
{ statusCode: 404 },
|
|
124
260
|
);
|
|
125
261
|
}
|
|
@@ -131,12 +267,11 @@ export async function generateSmallModelText({ prompt, system, maxOutputTokens,
|
|
|
131
267
|
);
|
|
132
268
|
}
|
|
133
269
|
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// model) is always allowed, anything else must stay on the session's
|
|
137
|
-
// provider.
|
|
270
|
+
// A caller that must stay on its session's provider is only overruled by an
|
|
271
|
+
// explicit user choice (the settings override or a request model).
|
|
138
272
|
if (restrictToPreferredProvider
|
|
139
|
-
&& !['settings', '
|
|
273
|
+
&& !['settings', 'request'].includes(resolved.source)
|
|
274
|
+
&& preferredProviderID
|
|
140
275
|
&& resolved.providerID !== preferredProviderID) {
|
|
141
276
|
throw Object.assign(
|
|
142
277
|
new Error('No small model available within the session provider'),
|
|
@@ -144,36 +279,76 @@ export async function generateSmallModelText({ prompt, system, maxOutputTokens,
|
|
|
144
279
|
);
|
|
145
280
|
}
|
|
146
281
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
maxOutputTokens,
|
|
152
|
-
});
|
|
282
|
+
const models = await listModelInfos(client, directory);
|
|
283
|
+
const modelInfo = findModelInfo(models, resolved.providerID, resolved.modelID);
|
|
284
|
+
|
|
285
|
+
const outputTokens = resolveOutputTokens({ modelInfo, maxOutputTokens });
|
|
153
286
|
|
|
154
287
|
const clamped = clampPromptToModelLimit({
|
|
155
288
|
prompt: prompt.trim(),
|
|
156
|
-
|
|
289
|
+
modelInfo,
|
|
157
290
|
providerID: resolved.providerID,
|
|
158
291
|
modelID: resolved.modelID,
|
|
159
292
|
onOverflow,
|
|
160
293
|
outputReserveTokens: outputTokens,
|
|
161
294
|
});
|
|
162
295
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
296
|
+
// `/api/experimental/generate` takes a single prompt, so the system instructions lead it.
|
|
297
|
+
const sections = [];
|
|
298
|
+
if (typeof system === 'string' && system.trim()) sections.push(system.trim());
|
|
299
|
+
sections.push(clamped.prompt);
|
|
300
|
+
if (responseSchema) sections.push(buildSchemaInstruction(responseSchema));
|
|
301
|
+
const fullPrompt = sections.join('\n\n');
|
|
302
|
+
|
|
303
|
+
const generationOptions = requestOptions({ timeoutMs, signal });
|
|
304
|
+
const unavailableMessage = `Model unavailable: ${resolved.providerID}/${resolved.modelID}`;
|
|
305
|
+
let retriedUnavailable = false;
|
|
306
|
+
const send = async () => {
|
|
307
|
+
const result = await client.generate.text(
|
|
308
|
+
{ prompt: fullPrompt, model: { id: resolved.modelID, providerID: resolved.providerID } },
|
|
309
|
+
generationOptions,
|
|
310
|
+
);
|
|
311
|
+
return typeof result?.text === 'string' ? result.text : '';
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const sendWithCatalogRetry = async () => {
|
|
315
|
+
try {
|
|
316
|
+
return await send();
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (error?._tag !== 'InvalidRequestError' || error.message !== unavailableMessage) throw error;
|
|
319
|
+
// OpenCode 2 can resolve a cold catalog before its models arrive.
|
|
320
|
+
// This rejection precedes provider dispatch; other failures must not retry.
|
|
321
|
+
if (!retriedUnavailable) {
|
|
322
|
+
retriedUnavailable = true;
|
|
323
|
+
await delay(500, undefined, { signal: generationOptions.signal });
|
|
324
|
+
return sendWithCatalogRetry();
|
|
325
|
+
}
|
|
326
|
+
throw Object.assign(new Error(unavailableMessage), {
|
|
327
|
+
statusCode: 503,
|
|
328
|
+
code: 'small-model-unavailable',
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
let text = await sendWithCatalogRetry();
|
|
334
|
+
|
|
335
|
+
if (responseSchema) {
|
|
336
|
+
// One retry: a model that ignored the shape once often honours it on a
|
|
337
|
+
// second pass, and the alternative is failing a walkthrough over a stray
|
|
338
|
+
// sentence of preamble.
|
|
339
|
+
let json = extractJsonText(text);
|
|
340
|
+
if (json === null) {
|
|
341
|
+
text = await sendWithCatalogRetry();
|
|
342
|
+
json = extractJsonText(text);
|
|
343
|
+
}
|
|
344
|
+
if (json === null) {
|
|
345
|
+
throw Object.assign(
|
|
346
|
+
new Error(`${resolved.providerID}/${resolved.modelID} did not return JSON matching the requested schema`),
|
|
347
|
+
{ statusCode: 422, code: 'structured-output-unsupported', providerID: resolved.providerID, modelID: resolved.modelID },
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
text = json;
|
|
351
|
+
}
|
|
177
352
|
|
|
178
353
|
return {
|
|
179
354
|
text: text.trim(),
|
|
@@ -185,29 +360,32 @@ export async function generateSmallModelText({ prompt, system, maxOutputTokens,
|
|
|
185
360
|
}
|
|
186
361
|
|
|
187
362
|
/**
|
|
188
|
-
* Provider ids the small model can actually call
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
363
|
+
* Provider ids the small model can actually call. A provider counts when
|
|
364
|
+
* OpenCode has at least one enabled model for it — that is the same test
|
|
365
|
+
* OpenCode applies before letting a chat turn use it.
|
|
366
|
+
*
|
|
367
|
+
* The provider list alone is not enough: it comes back empty on setups where
|
|
368
|
+
* models are perfectly usable, so the model list is the authority and the
|
|
369
|
+
* provider list only contributes names.
|
|
192
370
|
*/
|
|
193
371
|
export async function listAuthenticatedProviders() {
|
|
372
|
+
const client = getSmallModelClient();
|
|
373
|
+
if (!client) return [];
|
|
194
374
|
try {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
375
|
+
const [providers, models] = await Promise.all([
|
|
376
|
+
listProviderInfos(client),
|
|
377
|
+
listModelInfos(client),
|
|
378
|
+
]);
|
|
379
|
+
const enabled = new Set();
|
|
380
|
+
for (const model of models) {
|
|
381
|
+
if (model?.enabled === false) continue;
|
|
382
|
+
if (typeof model?.providerID === 'string' && model.providerID) enabled.add(model.providerID);
|
|
203
383
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
for (const providerID of await listRuntimeCallableProviders()) ids.add(providerID);
|
|
208
|
-
} catch {
|
|
209
|
-
// The auth.json set below stands on its own.
|
|
384
|
+
const ids = new Set();
|
|
385
|
+
for (const provider of providers) {
|
|
386
|
+
if (typeof provider?.id === 'string' && enabled.has(provider.id)) ids.add(provider.id);
|
|
210
387
|
}
|
|
388
|
+
for (const id of enabled) ids.add(id);
|
|
211
389
|
ids.delete(CLAUDE_CODE_PROVIDER);
|
|
212
390
|
return Array.from(ids);
|
|
213
391
|
} catch {
|
|
@@ -215,43 +393,6 @@ export async function listAuthenticatedProviders() {
|
|
|
215
393
|
}
|
|
216
394
|
}
|
|
217
395
|
|
|
218
|
-
/**
|
|
219
|
-
* Providers that only the running OpenCode knows about — plugin-registered
|
|
220
|
-
* ones, and any whose endpoint is resolved at startup.
|
|
221
|
-
*
|
|
222
|
-
* The test is the same one applied to an auth.json login: a credential we may
|
|
223
|
-
* use and somewhere to send it. Whether the endpoint answers the protocol we
|
|
224
|
-
* speak is not knowable from any field OpenCode reports, and guessing it wrong
|
|
225
|
-
* removes a working model from the picker with nothing to explain it.
|
|
226
|
-
*/
|
|
227
|
-
async function listRuntimeCallableProviders() {
|
|
228
|
-
const snapshot = await getRuntimeProviderSnapshot();
|
|
229
|
-
if (!snapshot) return [];
|
|
230
|
-
const ids = [];
|
|
231
|
-
for (const id of snapshot.connected) {
|
|
232
|
-
const provider = snapshot.providers.get(id);
|
|
233
|
-
// No credential we may use — including the zen sentinel, whose free models
|
|
234
|
-
// belong to OpenCode's own server.
|
|
235
|
-
if (!provider?.apiKey || !provider.baseURL) continue;
|
|
236
|
-
// Reached through a dedicated wire format and already covered by the
|
|
237
|
-
// auth.json scan above.
|
|
238
|
-
if (DEDICATED_WIRE_FORMAT_PROVIDERS.has(id)) continue;
|
|
239
|
-
ids.push(id);
|
|
240
|
-
}
|
|
241
|
-
return ids;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Reports which model would be used, without calling it.
|
|
246
|
-
*
|
|
247
|
-
* `inputCharBudget` and `structuredOutput` let callers refuse work before
|
|
248
|
-
* spending a request: the walkthrough needs both a big enough context and
|
|
249
|
-
* schema-shaped output, and would rather tell the user to pick another model
|
|
250
|
-
* than send a doomed prompt. `structuredOutput` is deliberately tri-state —
|
|
251
|
-
* the catalog omits the field for roughly half of all models (aggregators and
|
|
252
|
-
* proxies especially), and treating "unknown" as "unsupported" would hide
|
|
253
|
-
* models that work fine.
|
|
254
|
-
*/
|
|
255
396
|
/**
|
|
256
397
|
* The reserve, resolved against the model that was actually picked.
|
|
257
398
|
*
|
|
@@ -264,48 +405,45 @@ const resolveReserveTokens = (outputReserveTokens, limits) => (
|
|
|
264
405
|
typeof outputReserveTokens === 'function' ? outputReserveTokens(limits) : outputReserveTokens
|
|
265
406
|
);
|
|
266
407
|
|
|
408
|
+
/**
|
|
409
|
+
* Reports which model would be used, without calling it.
|
|
410
|
+
*
|
|
411
|
+
* `structuredOutput` stays `null`: `/api/experimental/generate` has no structured-output
|
|
412
|
+
* mode for any model, and this module emulates it through the prompt. Callers
|
|
413
|
+
* must read `null` as "try it", which is exactly right here — the verdict
|
|
414
|
+
* comes from the reply, not from a capability flag.
|
|
415
|
+
*/
|
|
267
416
|
export async function describeSmallModel({ directory, preferredProviderID, preferredModelID, outputReserveTokens, overrideModel } = {}) {
|
|
268
|
-
const
|
|
269
|
-
|
|
417
|
+
const client = getSmallModelClient(directory);
|
|
418
|
+
if (!client) return null;
|
|
419
|
+
|
|
270
420
|
// A caller with its own model setting (the diff walkthrough) outranks the
|
|
271
421
|
// small-model chain entirely — it asked for this model on purpose.
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const outputTokenLimit = Number(entry?.limit?.output) > 0 ? Number(entry.limit.output) : null;
|
|
422
|
+
const resolved = await resolveSmallModel({
|
|
423
|
+
client,
|
|
424
|
+
directory,
|
|
425
|
+
model: overrideModel,
|
|
426
|
+
preferredProviderID,
|
|
427
|
+
preferredModelID,
|
|
428
|
+
restrictToPreferredProvider: false,
|
|
429
|
+
});
|
|
430
|
+
if (!resolved) return null;
|
|
431
|
+
|
|
432
|
+
const models = await listModelInfos(client, directory);
|
|
433
|
+
const modelInfo = findModelInfo(models, resolved.providerID, resolved.modelID);
|
|
434
|
+
const outputTokenLimit = Number(modelInfo?.limit?.output) > 0 ? Number(modelInfo.limit.output) : null;
|
|
435
|
+
|
|
287
436
|
// Two passes: the first only to learn the context, which a caller-supplied
|
|
288
437
|
// reserve function needs before it can answer.
|
|
289
|
-
const { contextTokens, contextKnown } = getModelInputCharBudget({
|
|
290
|
-
catalog,
|
|
291
|
-
providerID: resolved.providerID,
|
|
292
|
-
modelID: resolved.modelID,
|
|
293
|
-
});
|
|
438
|
+
const { contextTokens, contextKnown } = getModelInputCharBudget({ modelInfo });
|
|
294
439
|
const reserveTokens = resolveReserveTokens(outputReserveTokens, { contextTokens, outputTokenLimit });
|
|
295
|
-
const { maxChars } = getModelInputCharBudget({
|
|
296
|
-
catalog,
|
|
297
|
-
providerID: resolved.providerID,
|
|
298
|
-
modelID: resolved.modelID,
|
|
299
|
-
outputReserveTokens: reserveTokens,
|
|
300
|
-
});
|
|
440
|
+
const { maxChars } = getModelInputCharBudget({ modelInfo, outputReserveTokens: reserveTokens });
|
|
301
441
|
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
providerID: resolved.providerID,
|
|
308
|
-
}));
|
|
442
|
+
// An override can name a model OpenCode has no credential for. It reports
|
|
443
|
+
// that as a disabled model, and readiness refuses before the user pays for
|
|
444
|
+
// a failed request. A model we cannot find at all is not evidence either
|
|
445
|
+
// way, so it counts as usable.
|
|
446
|
+
const hasLogin = modelInfo ? modelInfo.enabled !== false : true;
|
|
309
447
|
|
|
310
448
|
return {
|
|
311
449
|
...resolved,
|
|
@@ -316,7 +454,7 @@ export async function describeSmallModel({ directory, preferredProviderID, prefe
|
|
|
316
454
|
// What the caller should ask for, so the request and the reserve above
|
|
317
455
|
// cannot drift apart.
|
|
318
456
|
outputTokens: Number(reserveTokens) > 0 ? Number(reserveTokens) : null,
|
|
319
|
-
structuredOutput:
|
|
457
|
+
structuredOutput: null,
|
|
320
458
|
outputTokenLimit,
|
|
321
459
|
};
|
|
322
460
|
}
|