@vantran-se/openchamber-web 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -24
- package/bin/cli.js +1 -3
- package/bin/cli.test.js +40 -16
- package/bin/lib/DOCUMENTATION.md +6 -5
- package/bin/lib/cli-args.js +6 -1
- package/bin/lib/cli-args.test.js +34 -0
- package/bin/lib/cli-lifecycle.js +0 -1
- package/bin/lib/cli-network.js +8 -2
- package/bin/lib/cli-process.js +1 -1
- package/bin/lib/cli-settings-accessors.test.js +21 -13
- package/bin/lib/cli-startup.js +17 -11
- package/bin/lib/cli-startup.test.js +92 -2
- package/bin/lib/commands-connect-url.js +8 -1
- package/bin/lib/commands-schedule.js +8 -2
- package/bin/lib/commands-serve.js +15 -21
- package/bin/lib/commands-update.js +12 -24
- package/bin/lib/commands-update.test.js +14 -46
- package/dist/assets/DiffView-Cv9MSXl1.js +4 -0
- package/dist/assets/FilePreviewCommentMenu-D4kLseHj.js +4 -0
- package/dist/assets/FilesView-BVoD8I2x.js +14 -0
- package/dist/assets/{GitView-DzuzhIPx.js → GitView-Bzb8p-ew.js} +4 -4
- package/dist/assets/GuestSurfacePane-Crdbvsxo.js +1 -0
- package/dist/assets/InProgressOperationBanner-CwMjNQgk.js +2 -0
- package/dist/assets/{JsonTreeView-B_EPRjzy.js → JsonTreeView-DRKHZfsn.js} +1 -1
- package/dist/assets/MarkdownImageGallery-DZtqT1Zn.js +1 -0
- package/dist/assets/MarkdownRendererImpl-CqG7HCNd.js +10 -0
- package/dist/assets/OnboardingScreen-DMIxwYK5.js +1 -0
- package/dist/assets/{PatchDiff-6yh1csza.js → PatchDiff-CUcCg1jk.js} +1 -1
- package/dist/assets/PierreDiffViewer-CnnR-dnD.js +228 -0
- package/dist/assets/PlanView-RCBadcY7.js +5 -0
- package/dist/assets/SettingsView-CjBjZFRO.js +30 -0
- package/dist/assets/SettingsWindow-DyM7QuhI.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-BWIfgMED.js +1 -0
- package/dist/assets/ToolOutputDialog-Q0P3FuAi.js +15 -0
- package/dist/assets/ToolPartDiffPreview-p7qR9FpU.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-BTJVRkyo.js → VirtualizedFileDiff-CFpjL8Ns.js} +3 -3
- package/dist/assets/{Virtualizer-xCEYz34p.js → Virtualizer-C0IsclZn.js} +1 -1
- package/dist/assets/WalkthroughView-DI31zD02.js +4 -0
- package/dist/assets/appThemeRegistry-B9gkFq9i.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-CmPe29DM.js → computeVirtualFileMetrics-C2WM6JC2.js} +2 -2
- package/dist/assets/de-YKfk5MmC.js +14 -0
- package/dist/assets/es-_gwT0kr-.js +20 -0
- package/dist/assets/fileTreeStatus-D438yi8M.js +1 -0
- package/dist/assets/fr-DqzvXmIm.js +18 -0
- package/dist/assets/index-BLaHKdtx.js +1 -0
- package/dist/assets/index-BkMXRrVQ.css +1 -0
- package/dist/assets/index-DxNa7xYB.js +65 -0
- package/dist/assets/initializeSharedPreferences-Bck5EAWP.js +1 -0
- package/dist/assets/{isDiffPlainText-CQmDCnEv.js → isDiffPlainText-Dgbw8VKk.js} +1 -1
- package/dist/assets/{isFilePlainText-CYBUzW4I.js → isFilePlainText-_ZS5UYRj.js} +1 -1
- package/dist/assets/ja-B-GFYhQE.js +20 -0
- package/dist/assets/ko-mzksfqDC.js +20 -0
- package/dist/assets/main-C5Xx8dn_.js +2 -0
- package/dist/assets/main-CfBSQM1H.js +990 -0
- package/dist/assets/markdownCore-BmWwW1ll.js +7 -0
- package/dist/assets/miniChat-mnKwy4vQ.js +2 -0
- package/dist/assets/mobile-DeEQd0wZ.js +2 -0
- package/dist/assets/nl-CWQr6NSq.js +20 -0
- package/dist/assets/{parseDiffFromFile-Cy0PNxun.js → parseDiffFromFile-BUgn-T9p.js} +2 -2
- package/dist/assets/patchFileDiff-Dt8LhgBh.js +13 -0
- package/dist/assets/pl-UmRcDUX2.js +20 -0
- package/dist/assets/pt-BR-BSLybvVF.js +20 -0
- package/dist/assets/relocateRestoredSession-B5E9xb3T.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-C0a9A5_J.js +2 -0
- package/dist/assets/renderMobileApp-BNncg3Pk.js +25 -0
- package/dist/assets/runtimeEndpointReset-CZ5aafpl.js +62 -0
- package/dist/assets/{selectionMarkdown-TkYk5h9l.js → selectionMarkdown-CUN3wUld.js} +9 -9
- package/dist/assets/{sessionOwnership-Bs1xbRfB.js → sessionOwnership-BSuxCEhb.js} +1 -1
- package/dist/assets/shikiHighlight-DHDx-6eM.js +1 -0
- package/dist/assets/space-apply-DM_2pRW7.js +1 -0
- package/dist/assets/space-close-Bpomt8z0.js +1 -0
- package/dist/assets/{surface-B6n7wJtt.js → surface-C-jQaGnL.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-CDlKffWz.js → textMateThemeFromAppTheme-DlsTphbY.js} +1 -1
- package/dist/assets/tr-gaTrX_HK.js +11 -0
- package/dist/assets/uk-CnJF6ykl.js +20 -0
- package/dist/assets/{useAppFontEffects-DCqCVGlT.js → useAppFontEffects-DGlvOqP_.js} +194 -233
- package/dist/assets/useInlineCommentController-kMN4Wg_D.js +2 -0
- package/dist/assets/{useRootScrollLock-BowwS8kq.js → useRootScrollLock-DMPpvpNP.js} +2 -2
- package/dist/assets/{vendor-base-ui-DX96QhTP.js → vendor-base-ui-Det7U81h.js} +1 -1
- package/dist/assets/{vendor-codemirror-autocomplete-DW1FQz9_.js → vendor-codemirror-autocomplete-BSHQHbKu.js} +1 -1
- package/dist/assets/{vendor-codemirror-commands-DpmhalwN.js → vendor-codemirror-commands-C4G_dPWe.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-angular-BqExbN1M.js → vendor-codemirror-lang-angular-B6LbBGHB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-cpp-DZA36HfS.js → vendor-codemirror-lang-cpp-BWWXBx7W.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-css-B07blnnZ.js → vendor-codemirror-lang-css-Cayfb29q.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-go-BM8K9D1-.js → vendor-codemirror-lang-go-BoKlCNKw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-html-DaH9adfE.js → vendor-codemirror-lang-html-BQWerHsJ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-java-CsdC0viz.js → vendor-codemirror-lang-java-DWX7Fq_K.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-javascript-CpGYXGuo.js → vendor-codemirror-lang-javascript-DbpEcZM0.js} +2 -2
- package/dist/assets/{vendor-codemirror-lang-jinja-BBeGzXw5.js → vendor-codemirror-lang-jinja-D1JIJRA7.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-json-58T70Hqs.js → vendor-codemirror-lang-json-BzgckzmB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-less-BI-xCA59.js → vendor-codemirror-lang-less-B9EpDXFi.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-liquid-zEcMa-zy.js → vendor-codemirror-lang-liquid-9U7kRbSf.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-markdown-BLKnlLP4.js → vendor-codemirror-lang-markdown-D2TEBIvZ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-php-9f35M83-.js → vendor-codemirror-lang-php-BT6sDVzE.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-python-jAMtYiA9.js +3 -0
- package/dist/assets/{vendor-codemirror-lang-rust-MNDL_jZQ.js → vendor-codemirror-lang-rust-CVO56EiL.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sass-p9epsSNm.js → vendor-codemirror-lang-sass-DZlOoUWu.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sql-Bqzh0EXX.js → vendor-codemirror-lang-sql-fZ6t1DPw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-vue-BnNUWV-p.js → vendor-codemirror-lang-vue-DxbqrLiE.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-wast-C2a-oLmk.js → vendor-codemirror-lang-wast-DyU1Gt-I.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-xml-C9a_K5_p.js +1 -0
- package/dist/assets/{vendor-codemirror-lang-yaml-C0sjswB0.js → vendor-codemirror-lang-yaml-Cptq8l0e.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-ByBn5zIW.js → vendor-codemirror-language-DufXoQvE.js} +2 -2
- package/dist/assets/vendor-codemirror-language-data-CtwSWKtX.js +2 -0
- package/dist/assets/{vendor-codemirror-search-ib-sHD5W.js → vendor-codemirror-search-CvXM_pZg.js} +1 -1
- package/dist/assets/{vendor-codemirror-state-G3UuR-be.js → vendor-codemirror-state-Dek9sMtw.js} +1 -1
- package/dist/assets/{vendor-codemirror-view-Bniqp7p5.js → vendor-codemirror-view-1e0FQ6Wn.js} +1 -1
- package/dist/assets/vendor-framer-motion-DxDVhOLc.js +9 -0
- package/dist/assets/vendor-katex-CuQvj9-L.js +257 -0
- package/dist/assets/vendor-katex-DdZNUVNG.css +1 -0
- package/dist/assets/{vendor-motion-dom-DcCgeG69.js → vendor-motion-dom-HXPOMxb7.js} +1 -1
- package/dist/assets/{vendor-opencode-client-DHzhmLwV.js → vendor-opencode-client-BmFcUkbQ.js} +1 -1
- package/dist/assets/{vendor-replit-codemirror-vim-DkzqxN2H.js → vendor-replit-codemirror-vim-BlJRn-cW.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-virtual-H_JmXOFV.js → vendor-tanstack-react-virtual-CvuLsk7D.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dxc8hSTB.js → vendor-tanstack-virtual-core-DPaNcJcT.js} +1 -1
- package/dist/assets/zh-CN-t2vigSB1.js +20 -0
- package/dist/assets/zh-TW-jAgqBokB.js +20 -0
- package/dist/index.html +6 -6
- package/dist/mini-chat.html +6 -6
- package/dist/mobile.html +6 -6
- package/package.json +8 -5
- package/server/index.js +80 -36
- package/server/lib/agent-memory/actions.js +1 -6
- package/server/lib/agent-memory/routes.js +1 -2
- package/server/lib/agent-memory/runtime.js +1 -8
- package/server/lib/agent-tool/DOCUMENTATION.md +7 -3
- package/server/lib/agent-tool/runtime.js +3 -6
- package/server/lib/agent-tool/runtime.test.js +0 -2
- package/server/lib/browser-control/DOCUMENTATION.md +6 -0
- package/server/lib/changelog/update-notes.js +1 -1
- package/server/lib/context-obligatory/runtime.js +1 -2
- package/server/lib/dev-tunnel/DOCUMENTATION.md +8 -0
- package/server/lib/dev-tunnel/client.js +21 -10
- package/server/lib/dev-tunnel/tunnel.test.js +34 -0
- package/server/lib/dispatch-results/runtime.js +1 -1
- package/server/lib/enterprise-mode.d.ts +1 -0
- package/server/lib/enterprise-mode.js +13 -2
- package/server/lib/enterprise-mode.test.js +21 -0
- package/server/lib/event-stream/DOCUMENTATION.md +2 -2
- package/server/lib/event-stream/index.js +1 -3
- package/server/lib/event-stream/translate-v2.js +1 -9
- package/server/lib/event-stream/translate-v2.test.js +0 -11
- package/server/lib/fs/DOCUMENTATION.md +5 -2
- package/server/lib/fs/routes.js +62 -15
- package/server/lib/fs/routes.test.js +278 -48
- package/server/lib/fs/workspace-file-names.js +104 -0
- package/server/lib/fs/workspace-file-names.test.js +62 -0
- package/server/lib/git/DOCUMENTATION.md +17 -5
- package/server/lib/git/contributor-provenance-storage.js +9 -6
- package/server/lib/git/contributor-provenance-storage.test.js +29 -0
- package/server/lib/git/credential-broker.js +1 -1
- package/server/lib/git/credential-resolver.js +1 -4
- package/server/lib/git/credentials.js +1 -0
- package/server/lib/git/discovery-endpoint.js +1 -1
- package/server/lib/git/identity-provisioning.js +2 -1
- package/server/lib/git/identity-storage.js +3 -4
- package/server/lib/git/lfs-discovery.js +14 -14
- package/server/lib/git/lfs-discovery.test.js +16 -6
- package/server/lib/git/network-operation-plan.js +50 -62
- package/server/lib/git/network-operation-plan.test.js +103 -1
- package/server/lib/git/network-operation-registry.js +4 -6
- package/server/lib/git/network-operation-storage.js +4 -6
- package/server/lib/git/network-operations.js +12 -4
- package/server/lib/git/network-operations.test.js +19 -6
- package/server/lib/git/redaction.js +2 -1
- package/server/lib/git/repository-credential-runtime.js +5 -3
- package/server/lib/git/repository-credential-runtime.test.js +22 -0
- package/server/lib/git/routes.js +31 -137
- package/server/lib/git/routes.test.js +2 -2
- package/server/lib/git/service.js +143 -27
- package/server/lib/git/service.test.js +215 -1
- package/server/lib/git/ssh-credential-storage.js +1 -4
- package/server/lib/git/submodule-discovery.js +1 -1
- package/server/lib/git/worktree-bootstrap-storage.js +3 -4
- package/server/lib/git/worktree-bootstrap-storage.test.js +15 -0
- package/server/lib/git/worktree-creation.js +194 -0
- package/server/lib/git/worktree-creation.test.js +155 -0
- package/server/lib/github/DOCUMENTATION.md +11 -6
- package/server/lib/github/auth.js +2 -4
- package/server/lib/github/device-flow.js +2 -2
- package/server/lib/github/gh-cli-credential.js +1 -0
- package/server/lib/github/octokit.js +31 -0
- package/server/lib/github/pr-status.js +35 -18
- package/server/lib/github/pr-status.test.js +271 -3
- package/server/lib/github/reference-search.js +102 -13
- package/server/lib/github/reference-search.test.js +48 -7
- package/server/lib/github/routes.auth.test.js +18 -0
- package/server/lib/github/routes.js +360 -6
- package/server/lib/github/routes.mutations.test.js +239 -0
- package/server/lib/github/routes.test.js +134 -2
- package/server/lib/gitlab/DOCUMENTATION.md +5 -1
- package/server/lib/gitlab/auth-storage.test.js +1 -0
- package/server/lib/gitlab/mappers.js +30 -1
- package/server/lib/gitlab/resources.js +230 -6
- package/server/lib/gitlab/resources.test.js +179 -0
- package/server/lib/gitlab/routes.js +200 -6
- package/server/lib/gitlab/routes.test.js +186 -2
- package/server/lib/gitlab/validation.js +1 -5
- package/server/lib/guests/DOCUMENTATION.md +1 -1
- package/server/lib/guests/oauth.js +2 -2
- package/server/lib/guests/service.js +1 -1
- package/server/lib/guests/sockets.js +1 -1
- package/server/lib/injected-env.d.ts +8 -0
- package/server/lib/injected-env.js +50 -0
- package/server/lib/injected-env.test.js +56 -0
- package/server/lib/linear/DOCUMENTATION.md +5 -6
- package/server/lib/linear/issues.js +62 -4
- package/server/lib/linear/issues.test.js +68 -0
- package/server/lib/linear/oauth.js +5 -5
- package/server/lib/linear/parse.js +2 -9
- package/server/lib/message-queue/DOCUMENTATION.md +23 -1
- package/server/lib/message-queue/runtime.js +85 -12
- package/server/lib/message-queue/runtime.test.js +172 -2
- package/server/lib/notifications/apns-runtime.js +5 -6
- package/server/lib/notifications/apns-runtime.test.js +20 -4
- package/server/lib/openchamber-control/DOCUMENTATION.md +19 -1
- package/server/lib/openchamber-control/actions.js +2 -1
- package/server/lib/openchamber-control/service.js +119 -20
- package/server/lib/openchamber-control/service.test.js +166 -0
- package/server/lib/openchamber-sessions/archive-store.js +1 -6
- package/server/lib/openchamber-sessions/routes.js +47 -12
- package/server/lib/openchamber-sessions/routes.test.js +65 -2
- package/server/lib/openchamber-sessions/session-metadata-store.js +1 -6
- package/server/lib/opencode/DOCUMENTATION.md +22 -8
- package/server/lib/opencode/agents.js +62 -0
- package/server/lib/opencode/auth-state-runtime.js +3 -1
- package/server/lib/opencode/auth.d.ts +1 -1
- package/server/lib/opencode/auth.js +8 -0
- package/server/lib/opencode/auth.test.js +31 -0
- package/server/lib/opencode/config-entity-routes.js +16 -0
- package/server/lib/opencode/config-entity-v2.test.js +16 -1
- package/server/lib/opencode/config-paths.test.js +2 -2
- package/server/lib/opencode/config-v2.js +2 -4
- package/server/lib/opencode/feature-routes-runtime.js +89 -49
- package/server/lib/opencode/lifecycle.js +41 -21
- package/server/lib/opencode/lifecycle.test.js +50 -2
- package/server/lib/opencode/model-discovery.js +1 -1
- package/server/lib/opencode/model-discovery.test.js +17 -0
- package/server/lib/opencode/models-metadata.js +0 -2
- package/server/lib/opencode/openchamber-routes.test.js +3 -3
- package/server/lib/opencode/plugins.js +6 -9
- package/server/lib/opencode/provider-source-route.test.js +101 -3
- package/server/lib/opencode/providers-disabled.test.js +59 -0
- package/server/lib/opencode/providers.js +34 -0
- package/server/lib/opencode/proxy.js +11 -3
- package/server/lib/opencode/routes.js +40 -2
- package/server/lib/opencode/settings-helpers.js +27 -1
- package/server/lib/opencode/settings-helpers.test.js +19 -7
- package/server/lib/opencode/settings-registry.json +27 -16
- package/server/lib/opencode/settings-runtime.test.js +18 -0
- package/server/lib/opencode/shared-service-runtime.js +23 -104
- package/server/lib/opencode/shared.js +77 -46
- package/server/lib/opencode/shared.test.js +12 -12
- package/server/lib/opencode/shutdown-runtime.test.js +2 -3
- package/server/lib/opencode/skill-routes.js +123 -38
- package/server/lib/opencode/skill-routes.test.js +201 -0
- package/server/lib/opencode/skills.js +81 -34
- package/server/lib/opencode/skills.test.js +176 -21
- package/server/lib/opencode/startup-pipeline-runtime.js +6 -3
- package/server/lib/opencode/startup-pipeline-runtime.test.js +2 -1
- package/server/lib/opencode/watcher.test.js +0 -54
- package/server/lib/outbound-proxy.js +52 -0
- package/server/lib/outbound-proxy.test.js +55 -0
- package/server/lib/package-manager.js +23 -17
- package/server/lib/package-manager.test.js +5 -18
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +1 -1
- package/server/lib/permission-auto-accept/runtime.js +19 -47
- package/server/lib/permission-auto-accept/runtime.test.js +2 -2
- package/server/lib/project-context/DOCUMENTATION.md +7 -1
- package/server/lib/project-context/routes.js +1 -2
- package/server/lib/project-context/runtime.js +12 -14
- package/server/lib/project-context/runtime.test.js +78 -2
- package/server/lib/projects/project-config.js +30 -15
- package/server/lib/projects/project-config.test.js +30 -0
- package/server/lib/projects/project-setup.js +1 -2
- package/server/lib/projects/routes.js +1 -2
- package/server/lib/quota/DOCUMENTATION.md +35 -7
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/claude/index.js +1 -1
- package/server/lib/quota/providers/cline-pass.js +1 -1
- package/server/lib/quota/providers/exe-dev.js +1 -1
- package/server/lib/quota/providers/google/index.js +1 -1
- package/server/lib/quota/providers/hyper.js +1 -1
- package/server/lib/quota/providers/kimi.js +116 -36
- package/server/lib/quota/providers/kimi.test.js +128 -3
- package/server/lib/quota/providers/opencode-go.js +144 -21
- package/server/lib/quota/providers/opencode-go.test.js +229 -6
- package/server/lib/quota/providers/openrouter.js +1 -1
- package/server/lib/quota/providers/zenmux.js +1 -1
- package/server/lib/quota/utils/auth.js +1 -0
- package/server/lib/quota/utils/transformers.js +2 -6
- package/server/lib/relay/DOCUMENTATION.md +18 -3
- package/server/lib/relay/e2ee.js +7 -7
- package/server/lib/relay/host-client.js +84 -13
- package/server/lib/relay/host-client.test.js +201 -1
- package/server/lib/relay/identity.js +6 -29
- package/server/lib/relay/identity.test.js +18 -28
- package/server/lib/relay/key-store.js +141 -0
- package/server/lib/relay/key-store.test.js +138 -0
- package/server/lib/relay/service.js +8 -8
- package/server/lib/relay/service.test.js +10 -5
- package/server/lib/relay/signing-key.js +13 -36
- package/server/lib/relay/tunnel-codec.js +10 -10
- package/server/lib/route-module-exports.test.js +22 -0
- package/server/lib/routing/DOCUMENTATION.md +23 -3
- package/server/lib/routing/cache.js +87 -0
- package/server/lib/routing/defaults.js +11 -0
- package/server/lib/routing/history.js +12 -4
- package/server/lib/routing/runtime.js +39 -3
- package/server/lib/routing/runtime.test.js +81 -2
- package/server/lib/routing/store.js +12 -1
- package/server/lib/routing/store.test.js +13 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +59 -1
- package/server/lib/scheduled-tasks/existing-session.js +119 -0
- package/server/lib/scheduled-tasks/existing-session.test.js +350 -0
- package/server/lib/scheduled-tasks/loops.js +1 -8
- package/server/lib/scheduled-tasks/routes.js +8 -8
- package/server/lib/scheduled-tasks/runtime.js +103 -13
- package/server/lib/scheduled-tasks/runtime.test.js +48 -0
- package/server/lib/scheduled-tasks/service.js +33 -11
- package/server/lib/scheduled-tasks/service.test.js +128 -2
- package/server/lib/scheduled-tasks/session-defaults.js +27 -0
- package/server/lib/scheduled-tasks/session-defaults.test.js +19 -0
- package/server/lib/session-assist/context.js +1 -0
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-goal/objectives.js +1 -1
- package/server/lib/session-knowledge/routes.js +2 -2
- package/server/lib/session-knowledge/runtime.js +1 -2
- package/server/lib/session-work/DOCUMENTATION.md +39 -14
- package/server/lib/session-work/questions.js +31 -2
- package/server/lib/session-work/runtime.js +22 -13
- package/server/lib/session-work/runtime.test.js +43 -26
- package/server/lib/session-work/state.js +10 -4
- package/server/lib/session-work/state.test.js +8 -1
- package/server/lib/shared/DOCUMENTATION.md +89 -0
- package/server/lib/shared/guards.js +53 -0
- package/server/lib/shared/guards.test.js +112 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +10 -8
- package/server/lib/skills-catalog/cache.js +3 -2
- package/server/lib/skills-catalog/curated-sources.js +5 -1
- package/server/lib/skills-catalog/git.js +107 -20
- package/server/lib/skills-catalog/git.test.js +78 -0
- package/server/lib/skills-catalog/install.js +21 -12
- package/server/lib/skills-catalog/scan.js +25 -15
- package/server/lib/skills-catalog/source.js +26 -1
- package/server/lib/skills-catalog/source.test.js +27 -0
- package/server/lib/source-control/DOCUMENTATION.md +2 -1
- package/server/lib/source-control/audit-storage.js +7 -6
- package/server/lib/source-control/audit-storage.test.js +11 -0
- package/server/lib/source-control/binding-service.js +2 -5
- package/server/lib/source-control/change-request-worktree.js +90 -0
- package/server/lib/source-control/change-request-worktree.test.js +121 -0
- package/server/lib/source-control/mutation-executor.js +1 -1
- package/server/lib/source-control/mutation-storage.js +11 -5
- package/server/lib/source-control/provider-instance.js +1 -2
- package/server/lib/source-control/repository-identity.js +1 -1
- package/server/lib/source-control/routes.js +14 -2
- package/server/lib/spaces/DOCUMENTATION.md +42 -17
- package/server/lib/spaces/command-stream.test.js +15 -0
- package/server/lib/spaces/dispatcher.js +7 -0
- package/server/lib/spaces/dispatcher.test.js +17 -0
- package/server/lib/spaces/hardening.js +2 -2
- package/server/lib/spaces/hardening.test.js +1 -1
- package/server/lib/spaces/host.js +2 -0
- package/server/lib/spaces/journey.js +66 -5
- package/server/lib/spaces/journey.test.js +95 -4
- package/server/lib/spaces/layout.js +1 -1
- package/server/lib/spaces/places/docker-disk.js +38 -14
- package/server/lib/spaces/places/docker-disk.test.js +80 -1
- package/server/lib/spaces/places/docker.js +35 -11
- package/server/lib/spaces/places/fake-docker.js +22 -3
- package/server/lib/spaces/places/memory-place.js +3 -0
- package/server/lib/spaces/places/packed.docker.live.test.js +1 -1
- package/server/lib/spaces/routes.js +5 -0
- package/server/lib/spaces/routes.test.js +4 -2
- package/server/lib/spaces/run-command.js +9 -2
- package/server/lib/spaces/tools.js +4 -5
- package/server/lib/spaces/tools.test.js +2 -2
- package/server/lib/terminal/runtime.js +18 -6
- package/server/lib/terminal/runtime.test.js +66 -0
- package/server/lib/tts/index.js +3 -0
- package/server/lib/tts/service.js +4 -2
- package/server/lib/walkthrough/DOCUMENTATION.md +3 -1
- package/server/lib/walkthrough/index.js +2 -3
- package/server/lib/walkthrough/pr-source.test.js +22 -0
- package/server/lib/walkthrough/sources.js +6 -1
- package/server/opencode-proxy.test.js +127 -0
- package/bin/lib/commands-startup.test.js +0 -15
- package/dist/assets/CodeMirrorCommentWidgets-CVDSjcrP.js +0 -1
- package/dist/assets/DiffView-Ct7vL3fD.js +0 -4
- package/dist/assets/FilesView-DT0uEYgT.js +0 -17
- package/dist/assets/GuestSurfacePane-DVvk1VPu.js +0 -1
- package/dist/assets/LinearIssuesView-C10MwA9h.js +0 -1
- package/dist/assets/MarkdownImageGallery-BbN_K_3d.js +0 -1
- package/dist/assets/MarkdownRendererImpl-BLt2Tkk9.js +0 -10
- package/dist/assets/OnboardingScreen-Bgp5A3WB.js +0 -1
- package/dist/assets/PierreDiffViewer-CWJLj-S-.js +0 -210
- package/dist/assets/PlanView-DsRWnsOI.js +0 -5
- package/dist/assets/PublishDialog-CCeKloRu.js +0 -2
- package/dist/assets/SettingsView-BuPPsNPp.js +0 -30
- package/dist/assets/SettingsWindow-CM5fL8Ot.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-CfsMcfiQ.js +0 -1
- package/dist/assets/ToolOutputDialog-mZrfSofz.js +0 -15
- package/dist/assets/ToolPartDiffPreview-BUGCRYEL.js +0 -8
- package/dist/assets/WalkthroughView-ClBo7PPd.js +0 -4
- package/dist/assets/appThemeRegistry-CxzMxR-H.js +0 -1
- package/dist/assets/de-CBla4hjd.js +0 -14
- package/dist/assets/es-BmnZtxTD.js +0 -20
- package/dist/assets/fileTreeStatus-BSHFIGEg.js +0 -1
- package/dist/assets/fr-OVKIBUZO.js +0 -18
- package/dist/assets/index-CKCFKmjF.js +0 -1
- package/dist/assets/index-Dbx4V4GN.css +0 -1
- package/dist/assets/index-vQ1Pg1cr.js +0 -61
- package/dist/assets/ja-6v-yr-gE.js +0 -20
- package/dist/assets/ko-I_ZLvgQ9.js +0 -20
- package/dist/assets/main-CmO4dRtR.js +0 -2
- package/dist/assets/main-b9vqhlYM.js +0 -999
- package/dist/assets/markdownCore-BPEZRQfP.js +0 -7
- package/dist/assets/miniChat-B5Oi7B-6.js +0 -2
- package/dist/assets/mobile-CVtShif9.js +0 -2
- package/dist/assets/nl-DcvIJf46.js +0 -20
- package/dist/assets/patchFileDiff-CW3wCH1O.js +0 -13
- package/dist/assets/pl-JxCUl0sd.js +0 -20
- package/dist/assets/pt-BR-L89mjja2.js +0 -20
- package/dist/assets/relocateRestoredSession-DYh_9oBa.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-BD-1JHSg.js +0 -2
- package/dist/assets/renderMobileApp-7s5InXt1.js +0 -25
- package/dist/assets/runtimeEndpointReset-DqLJC6L1.js +0 -53
- package/dist/assets/shikiHighlight-CXgZnvHh.js +0 -1
- package/dist/assets/space-apply-1trGVIl8.js +0 -1
- package/dist/assets/space-close-y7j-NuBv.js +0 -1
- package/dist/assets/tr-ka5J1flt.js +0 -11
- package/dist/assets/uk-PyGe6Q_u.js +0 -20
- package/dist/assets/useInlineCommentController-CrjSGCpi.js +0 -2
- package/dist/assets/vendor-codemirror-lang-python-B7ClrMIT.js +0 -3
- package/dist/assets/vendor-codemirror-lang-xml-C8tGnS64.js +0 -1
- package/dist/assets/vendor-codemirror-language-data-q2bKO1EM.js +0 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +0 -1
- package/dist/assets/vendor-katex-CHD9BdDO.js +0 -257
- package/dist/assets/vendor-katex-Cp579UoJ.css +0 -1
- package/dist/assets/zh-CN-B0P2tybJ.js +0 -20
- package/dist/assets/zh-TW-Bs9C5cN8.js +0 -20
|
@@ -12,9 +12,11 @@ const mockResponse = (body, init = {}) => ({
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
describe('Kimi for Coding quota provider', () => {
|
|
15
|
-
it('computes weekly usedPercent from the used field (
|
|
16
|
-
// Captured from GET https://api.kimi.com/coding/v1/usages
|
|
17
|
-
//
|
|
15
|
+
it('computes weekly usedPercent from the used field (pre-migration payload, no remaining field)', async () => {
|
|
16
|
+
// Captured from GET https://api.kimi.com/coding/v1/usages before Kimi
|
|
17
|
+
// migrated plans to the ratio-based `usages` map. Plans that have not
|
|
18
|
+
// migrated still answer this way: the weekly `usage` block only ever
|
|
19
|
+
// includes `used`, never `remaining`.
|
|
18
20
|
const fetchImpl = async () => (
|
|
19
21
|
mockResponse({
|
|
20
22
|
usage: { limit: '100', used: '100', resetTime: '2026-08-04T06:21:48.514003Z' },
|
|
@@ -103,6 +105,129 @@ describe('Kimi for Coding quota provider', () => {
|
|
|
103
105
|
expect(result.error).toBe('API error: 401');
|
|
104
106
|
});
|
|
105
107
|
|
|
108
|
+
it('shows the pay-as-you-go balance when the Kimi Code address refuses a platform key', async () => {
|
|
109
|
+
const urls = [];
|
|
110
|
+
const fetchImpl = async (url, init) => {
|
|
111
|
+
urls.push({ url, authorization: init.headers.Authorization });
|
|
112
|
+
return url === 'https://api.moonshot.ai/v1/users/me/balance'
|
|
113
|
+
? Response.json({ code: 0, data: { available_balance: 12.345 }, status: true })
|
|
114
|
+
: new Response('{}', { status: 401 });
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const result = await fetchQuota({ readAuth, fetchImpl });
|
|
118
|
+
|
|
119
|
+
expect(urls.map((call) => call.url)).toEqual([
|
|
120
|
+
'https://api.kimi.com/coding/v1/usages',
|
|
121
|
+
'https://api.moonshot.ai/v1/users/me/balance',
|
|
122
|
+
]);
|
|
123
|
+
expect(urls[1].authorization).toBe('Bearer test-token');
|
|
124
|
+
expect(result.ok).toBe(true);
|
|
125
|
+
expect(result.providerId).toBe('kimi-for-coding');
|
|
126
|
+
expect(result.providerName).toBe('Kimi for Coding');
|
|
127
|
+
expect(result.usage.windows.credits_balance.valueLabel).toBe('$12.35');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('keeps the original 401 when the balance read fails too', async () => {
|
|
131
|
+
const urls = [];
|
|
132
|
+
const fetchImpl = async (url) => {
|
|
133
|
+
urls.push(url);
|
|
134
|
+
return new Response('{}', { status: 401 });
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const result = await fetchQuota({ readAuth, fetchImpl });
|
|
138
|
+
|
|
139
|
+
expect(urls).toEqual(['https://api.kimi.com/coding/v1/usages', 'https://api.moonshot.ai/v1/users/me/balance']);
|
|
140
|
+
expect(result.ok).toBe(false);
|
|
141
|
+
expect(result.error).toBe('API error: 401');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('treats a 403 from the Kimi Code address like a 401', async () => {
|
|
145
|
+
const fetchImpl = async (url) => (
|
|
146
|
+
url === 'https://api.moonshot.ai/v1/users/me/balance'
|
|
147
|
+
? Response.json({ data: { available_balance: 5 } })
|
|
148
|
+
: new Response('{}', { status: 403 })
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const result = await fetchQuota({ readAuth, fetchImpl });
|
|
152
|
+
|
|
153
|
+
expect(result.ok).toBe(true);
|
|
154
|
+
expect(result.usage.windows.credits_balance.valueLabel).toBe('$5.00');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('does not try the balance address for errors other than authorisation', async () => {
|
|
158
|
+
const urls = [];
|
|
159
|
+
const fetchImpl = async (url) => {
|
|
160
|
+
urls.push(url);
|
|
161
|
+
return new Response('{}', { status: 500 });
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const result = await fetchQuota({ readAuth, fetchImpl });
|
|
165
|
+
|
|
166
|
+
expect(urls).toEqual(['https://api.kimi.com/coding/v1/usages']);
|
|
167
|
+
expect(result.error).toBe('API error: 500');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('ratio-based usages payload', () => {
|
|
171
|
+
// Captured live from GET https://api.kimi.com/coding/v1/usages on
|
|
172
|
+
// 2026-10-08. Migrated plans answer with a ratio-based `usages` map instead
|
|
173
|
+
// of the counted `usage` block, and keep a coarser `limits[]` entry for the
|
|
174
|
+
// same 5-hour window. `limit_month_code` is the code-typed share of
|
|
175
|
+
// `limit_month_total`, not a second allowance, and is not shown.
|
|
176
|
+
const migratedPlan = {
|
|
177
|
+
limits: [{
|
|
178
|
+
window: { duration: 300, timeUnit: 'TIME_UNIT_MINUTE' },
|
|
179
|
+
detail: { limit: '100', used: '50', remaining: '50', resetTime: '2026-10-08T11:50:22.234058Z' },
|
|
180
|
+
}],
|
|
181
|
+
usages: {
|
|
182
|
+
limit_5h: { used_ratio: 0.495422, reset_time: '2026-10-08T11:50:22Z' },
|
|
183
|
+
limit_month_total: { used_ratio: 0.2636, reset_time: '2026-10-18T00:00:00Z' },
|
|
184
|
+
limit_month_code: { used_ratio: 0.1907, reset_time: '2026-10-18T00:00:00Z' },
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const run = (body) => fetchQuota({ readAuth, fetchImpl: async () => mockResponse(body) });
|
|
189
|
+
|
|
190
|
+
it('reads the 5h and monthly ratios and ignores the duplicate limits[] entry', async () => {
|
|
191
|
+
const result = await run(migratedPlan);
|
|
192
|
+
|
|
193
|
+
expect(result.ok).toBe(true);
|
|
194
|
+
expect(Object.keys(result.usage.windows)).toEqual(['5h', 'monthly']);
|
|
195
|
+
expect(result.usage.windows['5h'].usedPercent).toBeCloseTo(49.5422, 9);
|
|
196
|
+
expect(result.usage.windows['5h'].windowSeconds).toBe(18000);
|
|
197
|
+
expect(result.usage.windows['5h'].resetAt).toBe(Date.parse('2026-10-08T11:50:22Z'));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('reports the monthly row as a plain percent so it follows the used/remaining setting', async () => {
|
|
201
|
+
const result = await run(migratedPlan);
|
|
202
|
+
|
|
203
|
+
expect(result.usage.windows.monthly.usedPercent).toBeCloseTo(26.36, 9);
|
|
204
|
+
expect(result.usage.windows.monthly.remainingPercent).toBeCloseTo(73.64, 9);
|
|
205
|
+
expect(result.usage.windows.monthly.valueLabel).toBeUndefined();
|
|
206
|
+
expect(result.usage.windows.monthly.windowSeconds).toBeNull();
|
|
207
|
+
expect(result.usage.windows.monthly.resetAt).toBe(Date.parse('2026-10-18T00:00:00Z'));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('maps limit_7d to the weekly window when the plan still serves it', async () => {
|
|
211
|
+
const result = await run({ usages: { limit_7d: { used_ratio: 0.25, reset_time: null } } });
|
|
212
|
+
|
|
213
|
+
expect(result.usage.windows.weekly.usedPercent).toBe(25);
|
|
214
|
+
expect(result.usage.windows.weekly.windowSeconds).toBe(604800);
|
|
215
|
+
expect(result.usage.windows.weekly.valueLabel).toBeUndefined();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('keeps a zero ratio and skips an entry without one', async () => {
|
|
219
|
+
const result = await run({
|
|
220
|
+
usages: {
|
|
221
|
+
limit_5h: { used_ratio: 0, reset_time: null },
|
|
222
|
+
limit_month_total: { used_ratio: 'not-a-number', reset_time: null }
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(result.usage.windows['5h'].usedPercent).toBe(0);
|
|
227
|
+
expect(Object.keys(result.usage.windows)).toEqual(['5h']);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
106
231
|
describe('credential lookup', () => {
|
|
107
232
|
const sentKey = async (auth) => {
|
|
108
233
|
let authorization;
|
|
@@ -1,31 +1,94 @@
|
|
|
1
1
|
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import { deleteLegacyOpenCodeGoCredential } from '../credentials/store.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
asNonEmptyString,
|
|
5
|
+
asObject,
|
|
6
|
+
buildResult,
|
|
7
|
+
getAuthEntry,
|
|
8
|
+
normalizeAuthEntry,
|
|
9
|
+
toNumber,
|
|
10
|
+
toTimestamp,
|
|
11
|
+
toUsageWindow,
|
|
12
|
+
} from '../utils/index.js';
|
|
4
13
|
|
|
5
14
|
export const providerId = 'opencode-go';
|
|
6
15
|
export const providerName = 'OpenCode Go';
|
|
7
|
-
|
|
16
|
+
const aliases = ['opencode-go'];
|
|
8
17
|
|
|
9
|
-
|
|
18
|
+
// OpenCode Go has two credential shapes. A service-account key stored under the
|
|
19
|
+
// `opencode-go` integration uses the legacy usage API. A Console sign-in lives
|
|
20
|
+
// on the shared `opencode` integration and its OAuth access token is not
|
|
21
|
+
// interchangeable with that key, so it uses the Console Go status endpoint.
|
|
22
|
+
const API_KEY_USAGE_URL = 'https://opencode.ai/zen/go/v1/usage';
|
|
23
|
+
const CONSOLE_STATUS_URL = 'https://opencode.ai/console/api/go/status';
|
|
24
|
+
const CONSOLE_SERVER = 'https://opencode.ai/console';
|
|
25
|
+
const CONSOLE_INTEGRATION_ID = 'opencode';
|
|
26
|
+
const ORGANIZATION_ID_PATTERN = /^org_[A-Za-z0-9]+$/;
|
|
27
|
+
const CONSOLE_PRODUCTS = new Set(['go', 'go-plus']);
|
|
28
|
+
const REQUEST_TIMEOUT_MS = 15_000;
|
|
29
|
+
|
|
30
|
+
const API_KEY_WINDOW_FIELDS = {
|
|
10
31
|
'5h': 'rolling',
|
|
11
32
|
weekly: 'weekly',
|
|
12
33
|
monthly: 'monthly',
|
|
13
34
|
};
|
|
14
35
|
|
|
36
|
+
// The Console page meters. `week` maps to the existing `weekly` window key the
|
|
37
|
+
// UI already labels, matching the API-key path.
|
|
38
|
+
const CONSOLE_WINDOW_METERS = {
|
|
39
|
+
'5h': 'fiveHour',
|
|
40
|
+
weekly: 'week',
|
|
41
|
+
monthly: 'month',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const clampPercent = (value) => Math.min(100, Math.max(0, value));
|
|
45
|
+
|
|
15
46
|
export const parseOpenCodeGoUsage = (payload) => {
|
|
16
|
-
const usage = payload
|
|
17
|
-
if (!usage
|
|
47
|
+
const usage = asObject(payload) ? asObject(payload.usage) : null;
|
|
48
|
+
if (!usage) return {};
|
|
18
49
|
const windows = {};
|
|
19
|
-
for (const [key,
|
|
20
|
-
const entry = usage[
|
|
21
|
-
if (!entry
|
|
22
|
-
const usedPercent = entry.percent;
|
|
23
|
-
const resetAt = entry.resetsAt;
|
|
24
|
-
if (
|
|
25
|
-
if (typeof resetAt !== 'string' || !Number.isFinite(new Date(resetAt).getTime())) continue;
|
|
50
|
+
for (const [key, field] of Object.entries(API_KEY_WINDOW_FIELDS)) {
|
|
51
|
+
const entry = asObject(usage[field]);
|
|
52
|
+
if (!entry) continue;
|
|
53
|
+
const usedPercent = toNumber(entry.percent);
|
|
54
|
+
const resetAt = toTimestamp(entry.resetsAt);
|
|
55
|
+
if (usedPercent === null || resetAt === null) continue;
|
|
26
56
|
windows[key] = toUsageWindow({
|
|
27
|
-
usedPercent:
|
|
28
|
-
resetAt
|
|
57
|
+
usedPercent: clampPercent(usedPercent),
|
|
58
|
+
resetAt,
|
|
59
|
+
windowSeconds: null,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return windows;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The Console meters report integer micro-cents as decimal strings. A missing,
|
|
67
|
+
* negative, or unparseable amount is not usable data, and a zero limit would
|
|
68
|
+
* divide by zero, so each is rejected on its own without discarding the other
|
|
69
|
+
* windows.
|
|
70
|
+
*/
|
|
71
|
+
const toMicroCents = (value) => {
|
|
72
|
+
const amount = toNumber(value);
|
|
73
|
+
if (amount === null || amount < 0) return null;
|
|
74
|
+
return amount;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const parseConsoleGoUsage = (payload) => {
|
|
78
|
+
const access = asObject(payload) ? asObject(payload.access) : null;
|
|
79
|
+
const meters = access ? asObject(access.meters) : null;
|
|
80
|
+
if (!meters) return {};
|
|
81
|
+
const windows = {};
|
|
82
|
+
for (const [key, meterName] of Object.entries(CONSOLE_WINDOW_METERS)) {
|
|
83
|
+
const meter = asObject(meters[meterName]);
|
|
84
|
+
if (!meter) continue;
|
|
85
|
+
const limit = toMicroCents(meter.limitMicroCents);
|
|
86
|
+
const used = toMicroCents(meter.usedMicroCents);
|
|
87
|
+
const resetAt = toTimestamp(meter.resetsAt);
|
|
88
|
+
if (limit === null || limit <= 0 || used === null || resetAt === null) continue;
|
|
89
|
+
windows[key] = toUsageWindow({
|
|
90
|
+
usedPercent: clampPercent((used / limit) * 100),
|
|
91
|
+
resetAt,
|
|
29
92
|
windowSeconds: null,
|
|
30
93
|
});
|
|
31
94
|
}
|
|
@@ -33,14 +96,14 @@ export const parseOpenCodeGoUsage = (payload) => {
|
|
|
33
96
|
};
|
|
34
97
|
|
|
35
98
|
export const fetchOpenCodeGoUsage = async (apiKey, fetchImpl = fetch) => {
|
|
36
|
-
const response = await fetchImpl(
|
|
99
|
+
const response = await fetchImpl(API_KEY_USAGE_URL, {
|
|
37
100
|
headers: {
|
|
38
101
|
Accept: 'application/json',
|
|
39
102
|
Authorization: `Bearer ${apiKey}`,
|
|
40
103
|
'x-opencode-session': 'openchamber-usage',
|
|
41
104
|
'User-Agent': 'OpenChamber quota provider',
|
|
42
105
|
},
|
|
43
|
-
signal: AbortSignal.timeout(
|
|
106
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
44
107
|
});
|
|
45
108
|
if (response.status === 401 || response.status === 403) {
|
|
46
109
|
throw new Error('OpenCode Go authentication failed');
|
|
@@ -51,19 +114,79 @@ export const fetchOpenCodeGoUsage = async (apiKey, fetchImpl = fetch) => {
|
|
|
51
114
|
return windows;
|
|
52
115
|
};
|
|
53
116
|
|
|
117
|
+
export const fetchConsoleGoUsage = async ({ access, orgID, expires }, fetchImpl = fetch) => {
|
|
118
|
+
if (expires !== null && expires > 0 && expires <= Date.now()) {
|
|
119
|
+
throw new Error('OpenCode Console sign-in expired. Sign in again in Providers.');
|
|
120
|
+
}
|
|
121
|
+
const response = await fetchImpl(CONSOLE_STATUS_URL, {
|
|
122
|
+
headers: {
|
|
123
|
+
Accept: 'application/json',
|
|
124
|
+
Authorization: `Bearer ${access}`,
|
|
125
|
+
'x-org-id': orgID,
|
|
126
|
+
'User-Agent': 'OpenChamber quota provider',
|
|
127
|
+
},
|
|
128
|
+
// The bearer token belongs to opencode.ai/console; never follow a redirect
|
|
129
|
+
// that would forward it to another origin.
|
|
130
|
+
redirect: 'error',
|
|
131
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
132
|
+
});
|
|
133
|
+
if (response.status === 401 || response.status === 403) {
|
|
134
|
+
throw new Error('OpenCode Console sign-in expired. Sign in again in Providers.');
|
|
135
|
+
}
|
|
136
|
+
if (!response.ok) throw new Error(`OpenCode Console Go status API returned HTTP ${response.status}`);
|
|
137
|
+
const payload = await response.json().catch(() => null);
|
|
138
|
+
if (!asObject(payload)) throw new Error('OpenCode Console Go status returned an unreadable response');
|
|
139
|
+
if (!CONSOLE_PRODUCTS.has(payload.product)) {
|
|
140
|
+
throw new Error('No active OpenCode Go subscription on the selected Console account');
|
|
141
|
+
}
|
|
142
|
+
const windows = parseConsoleGoUsage(payload);
|
|
143
|
+
if (Object.keys(windows).length === 0) throw new Error('OpenCode Go usage data could not be parsed');
|
|
144
|
+
return windows;
|
|
145
|
+
};
|
|
146
|
+
|
|
54
147
|
const getApiKey = (auth) => {
|
|
55
148
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
56
|
-
return entry?.key ?? entry?.token
|
|
149
|
+
return asNonEmptyString(entry?.key) ?? asNonEmptyString(entry?.token);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The active OpenCode Console OAuth credential, only when it is a Console
|
|
154
|
+
* sign-in (`server` is the Console origin and an organization is selected).
|
|
155
|
+
* OpenCode marks exactly one credential active per integration and credentials
|
|
156
|
+
* are global, so this is the selected account and organization; reading it per
|
|
157
|
+
* call follows a switch instead of caching the previous account.
|
|
158
|
+
*/
|
|
159
|
+
const getConsoleCredential = (auth) => {
|
|
160
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, [CONSOLE_INTEGRATION_ID]));
|
|
161
|
+
if (!entry || entry.type !== 'oauth') return null;
|
|
162
|
+
const access = asNonEmptyString(entry.access);
|
|
163
|
+
const orgID = asNonEmptyString(entry.orgID);
|
|
164
|
+
if (!access || entry.server !== CONSOLE_SERVER || !orgID || !ORGANIZATION_ID_PATTERN.test(orgID)) return null;
|
|
165
|
+
const expires = toNumber(entry.expires);
|
|
166
|
+
return { access, orgID, expires };
|
|
57
167
|
};
|
|
58
168
|
|
|
59
|
-
export const isConfigured = (auth) => Boolean(getApiKey(auth));
|
|
169
|
+
export const isConfigured = (auth) => Boolean(getConsoleCredential(auth) || getApiKey(auth));
|
|
60
170
|
|
|
61
|
-
export const fetchQuota = async () => {
|
|
171
|
+
export const fetchQuota = async ({ readAuth = readOpenCodeCredentials, fetchImpl = fetch } = {}) => {
|
|
62
172
|
try {
|
|
63
173
|
deleteLegacyOpenCodeGoCredential();
|
|
64
|
-
const
|
|
174
|
+
const auth = await readAuth();
|
|
175
|
+
// A Console sign-in serves OpenCode Go once it exists; the `opencode-go`
|
|
176
|
+
// service key is the fallback for accounts without one, and for a Console
|
|
177
|
+
// read that fails (no Go in that org, an endpoint change, a hiccup).
|
|
178
|
+
const consoleCredential = getConsoleCredential(auth);
|
|
179
|
+
const apiKey = getApiKey(auth);
|
|
180
|
+
if (consoleCredential) {
|
|
181
|
+
try {
|
|
182
|
+
const windows = await fetchConsoleGoUsage(consoleCredential, fetchImpl);
|
|
183
|
+
return buildResult({ providerId, providerName, ok: true, configured: true, usage: { windows } });
|
|
184
|
+
} catch (consoleError) {
|
|
185
|
+
if (!apiKey) throw consoleError;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
65
188
|
if (!apiKey) return buildResult({ providerId, providerName, ok: false, configured: false, error: 'Not configured' });
|
|
66
|
-
const windows = await fetchOpenCodeGoUsage(apiKey);
|
|
189
|
+
const windows = await fetchOpenCodeGoUsage(apiKey, fetchImpl);
|
|
67
190
|
return buildResult({ providerId, providerName, ok: true, configured: true, usage: { windows } });
|
|
68
191
|
} catch (error) {
|
|
69
192
|
return buildResult({ providerId, providerName, ok: false, configured: true, error: error instanceof Error ? error.message : 'Request failed' });
|
|
@@ -7,11 +7,16 @@ const previousDataDirectory = process.env.OPENCHAMBER_DATA_DIR;
|
|
|
7
7
|
const temporaryDataDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-opencode-go-'));
|
|
8
8
|
process.env.OPENCHAMBER_DATA_DIR = temporaryDataDirectory;
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
import {
|
|
11
|
+
fetchConsoleGoUsage,
|
|
12
|
+
fetchOpenCodeGoUsage,
|
|
13
|
+
fetchQuota,
|
|
14
|
+
isConfigured,
|
|
15
|
+
parseConsoleGoUsage,
|
|
16
|
+
parseOpenCodeGoUsage,
|
|
17
|
+
} from './opencode-go.js';
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
const apiKeyAuth = () => ({ 'opencode-go': { key: 'test-key' } });
|
|
15
20
|
|
|
16
21
|
afterEach(() => {
|
|
17
22
|
vi.unstubAllGlobals();
|
|
@@ -23,7 +28,33 @@ afterAll(() => {
|
|
|
23
28
|
fs.rmSync(temporaryDataDirectory, { recursive: true, force: true });
|
|
24
29
|
});
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
const CONSOLE_SERVER = 'https://opencode.ai/console';
|
|
32
|
+
const CONSOLE_STATUS_URL = `${CONSOLE_SERVER}/api/go/status`;
|
|
33
|
+
|
|
34
|
+
const consoleAuth = (overrides = {}) => ({
|
|
35
|
+
opencode: {
|
|
36
|
+
type: 'oauth',
|
|
37
|
+
access: 'console-access',
|
|
38
|
+
refresh: 'console-refresh',
|
|
39
|
+
expires: Date.now() + 3_600_000,
|
|
40
|
+
server: CONSOLE_SERVER,
|
|
41
|
+
orgID: 'org_TESTORG123',
|
|
42
|
+
...overrides,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const consolePayload = ({ product = 'go', meters } = {}) => ({
|
|
47
|
+
product,
|
|
48
|
+
access: {
|
|
49
|
+
meters: meters ?? {
|
|
50
|
+
fiveHour: { resetsAt: '2026-08-12T12:00:00.000Z', limitMicroCents: '1000000000', usedMicroCents: '250000000' },
|
|
51
|
+
week: { resetsAt: '2026-08-19T12:00:00.000Z', limitMicroCents: '2000000000', usedMicroCents: '800000000' },
|
|
52
|
+
month: { resetsAt: '2026-09-01T00:00:00.000Z', limitMicroCents: '6000000000', usedMicroCents: '3000000000' },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('OpenCode Go quota provider — legacy API key', () => {
|
|
27
58
|
it('parses partial API usage windows', () => {
|
|
28
59
|
const windows = parseOpenCodeGoUsage({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' }, weekly: { percent: 40, resetsAt: '2026-08-19T12:00:00.000Z' } } });
|
|
29
60
|
expect(windows['5h'].usedPercent).toBe(25);
|
|
@@ -83,9 +114,201 @@ describe('OpenCode Go quota provider', () => {
|
|
|
83
114
|
fs.writeFileSync(legacyPath, '{not valid json', { mode: 0o600 });
|
|
84
115
|
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' } } })));
|
|
85
116
|
vi.stubGlobal('fetch', fetchMock);
|
|
86
|
-
const result = await fetchQuota();
|
|
117
|
+
const result = await fetchQuota({ readAuth: async () => apiKeyAuth() });
|
|
87
118
|
expect(result).toMatchObject({ providerId: 'opencode-go', ok: true, configured: true });
|
|
88
119
|
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer test-key');
|
|
89
120
|
expect(fs.existsSync(legacyPath)).toBe(false);
|
|
90
121
|
});
|
|
122
|
+
|
|
123
|
+
it('still uses the API-key path when the Console integration is not a Console sign-in', async () => {
|
|
124
|
+
const fetchImpl = vi.fn().mockResolvedValue(new Response(JSON.stringify({ usage: { rolling: { percent: 10, resetsAt: '2026-08-12T12:00:00.000Z' } } })));
|
|
125
|
+
const result = await fetchQuota({
|
|
126
|
+
readAuth: async () => ({
|
|
127
|
+
...consoleAuth({ server: 'https://example.com/console' }),
|
|
128
|
+
'opencode-go': { key: 'test-key' },
|
|
129
|
+
}),
|
|
130
|
+
fetchImpl,
|
|
131
|
+
});
|
|
132
|
+
expect(result.ok).toBe(true);
|
|
133
|
+
expect(fetchImpl.mock.calls[0][0]).toBe('https://opencode.ai/zen/go/v1/usage');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('OpenCode Go quota provider — Console OAuth', () => {
|
|
138
|
+
it('maps fiveHour, week, and month to the existing windows from decimal strings', () => {
|
|
139
|
+
const windows = parseConsoleGoUsage(consolePayload());
|
|
140
|
+
expect(Object.keys(windows)).toEqual(['5h', 'weekly', 'monthly']);
|
|
141
|
+
expect(windows['5h'].usedPercent).toBe(25);
|
|
142
|
+
expect(windows.weekly.usedPercent).toBe(40);
|
|
143
|
+
expect(windows.monthly.usedPercent).toBe(50);
|
|
144
|
+
expect(windows['5h'].resetAt).toBe(Date.parse('2026-08-12T12:00:00.000Z'));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('supports Go Plus', async () => {
|
|
148
|
+
const result = await fetchQuota({
|
|
149
|
+
readAuth: async () => consoleAuth(),
|
|
150
|
+
fetchImpl: async () => new Response(JSON.stringify(consolePayload({ product: 'go-plus' }))),
|
|
151
|
+
});
|
|
152
|
+
expect(result.ok).toBe(true);
|
|
153
|
+
expect(result.usage.windows['5h'].usedPercent).toBe(25);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('skips only the unusable meters and keeps the rest', () => {
|
|
157
|
+
const windows = parseConsoleGoUsage(consolePayload({ meters: {
|
|
158
|
+
fiveHour: { resetsAt: '2026-08-12T12:00:00.000Z', limitMicroCents: '0', usedMicroCents: '10' },
|
|
159
|
+
week: { resetsAt: '2026-08-19T12:00:00.000Z', limitMicroCents: 'not-a-number', usedMicroCents: '10' },
|
|
160
|
+
month: { resetsAt: '2026-09-01T00:00:00.000Z', limitMicroCents: '1000', usedMicroCents: '250' },
|
|
161
|
+
} }));
|
|
162
|
+
expect(Object.keys(windows)).toEqual(['monthly']);
|
|
163
|
+
expect(windows.monthly.usedPercent).toBe(25);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('skips a window with an invalid reset timestamp and handles missing meters', () => {
|
|
167
|
+
const windows = parseConsoleGoUsage(consolePayload({ meters: {
|
|
168
|
+
fiveHour: { resetsAt: 'not-a-date', limitMicroCents: '1000', usedMicroCents: '250' },
|
|
169
|
+
week: { resetsAt: '2026-08-19T12:00:00.000Z', limitMicroCents: '1000', usedMicroCents: '250' },
|
|
170
|
+
} }));
|
|
171
|
+
expect(Object.keys(windows)).toEqual(['weekly']);
|
|
172
|
+
expect(parseConsoleGoUsage({ product: 'go', access: {} })).toEqual({});
|
|
173
|
+
expect(parseConsoleGoUsage(null)).toEqual({});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('calls the Console Go status endpoint with the Console token and organization', async () => {
|
|
177
|
+
let request;
|
|
178
|
+
const windows = await fetchConsoleGoUsage(
|
|
179
|
+
{ access: 'console-access', orgID: 'org_TESTORG123', expires: Date.now() + 60_000 },
|
|
180
|
+
async (url, options) => {
|
|
181
|
+
request = { url, options };
|
|
182
|
+
return new Response(JSON.stringify(consolePayload()));
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
expect(request.url).toBe(CONSOLE_STATUS_URL);
|
|
186
|
+
expect(request.options.headers).toMatchObject({
|
|
187
|
+
Accept: 'application/json',
|
|
188
|
+
Authorization: 'Bearer console-access',
|
|
189
|
+
'x-org-id': 'org_TESTORG123',
|
|
190
|
+
});
|
|
191
|
+
expect(request.options.headers['x-opencode-session']).toBeUndefined();
|
|
192
|
+
expect(request.options.redirect).toBe('error');
|
|
193
|
+
expect(windows.weekly.usedPercent).toBe(40);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('prefers the selected Console account and organization over the legacy key', async () => {
|
|
197
|
+
const fetchImpl = vi.fn().mockResolvedValue(new Response(JSON.stringify(consolePayload())));
|
|
198
|
+
const result = await fetchQuota({
|
|
199
|
+
readAuth: async () => ({ ...consoleAuth(), 'opencode-go': { key: 'stale-key' } }),
|
|
200
|
+
fetchImpl,
|
|
201
|
+
});
|
|
202
|
+
expect(result.ok).toBe(true);
|
|
203
|
+
expect(fetchImpl.mock.calls[0][0]).toBe(CONSOLE_STATUS_URL);
|
|
204
|
+
expect(fetchImpl.mock.calls[0][1].headers['x-org-id']).toBe('org_TESTORG123');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('falls back to the legacy key when the Console read fails', async () => {
|
|
208
|
+
const fetchImpl = vi.fn(async (url) => (url === CONSOLE_STATUS_URL
|
|
209
|
+
? new Response(JSON.stringify(consolePayload({ product: 'zen' })))
|
|
210
|
+
: new Response(JSON.stringify({ usage: { rolling: { percent: 10, resetsAt: '2026-08-12T12:00:00.000Z' } } }))));
|
|
211
|
+
const result = await fetchQuota({
|
|
212
|
+
readAuth: async () => ({ ...consoleAuth(), 'opencode-go': { key: 'test-key' } }),
|
|
213
|
+
fetchImpl,
|
|
214
|
+
});
|
|
215
|
+
expect(result.ok).toBe(true);
|
|
216
|
+
expect(result.usage.windows['5h'].usedPercent).toBe(10);
|
|
217
|
+
expect(fetchImpl.mock.calls.map((call) => call[0])).toEqual([CONSOLE_STATUS_URL, 'https://opencode.ai/zen/go/v1/usage']);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('reports the Console error when there is no key to fall back to', async () => {
|
|
221
|
+
const result = await fetchQuota({
|
|
222
|
+
readAuth: async () => consoleAuth(),
|
|
223
|
+
fetchImpl: async () => new Response(JSON.stringify(consolePayload({ product: 'zen' }))),
|
|
224
|
+
});
|
|
225
|
+
expect(result.ok).toBe(false);
|
|
226
|
+
expect(result.configured).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('reports an unconfigured provider without requesting', async () => {
|
|
230
|
+
const fetchImpl = vi.fn();
|
|
231
|
+
const result = await fetchQuota({ readAuth: async () => ({}), fetchImpl });
|
|
232
|
+
expect(result).toEqual(expect.objectContaining({ ok: false, configured: false, error: 'Not configured' }));
|
|
233
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('reports an unconfigured provider when the Console sign-in is for another server or has no organization', async () => {
|
|
237
|
+
expect(isConfigured(consoleAuth({ server: 'https://example.com/console' }))).toBe(false);
|
|
238
|
+
expect(isConfigured(consoleAuth({ orgID: 'not-an-org' }))).toBe(false);
|
|
239
|
+
expect(isConfigured(consoleAuth())).toBe(true);
|
|
240
|
+
expect(isConfigured({ 'opencode-go': { key: 'test-key' } })).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('distinguishes an expired sign-in without sending the stale token', async () => {
|
|
244
|
+
const fetchImpl = vi.fn();
|
|
245
|
+
const result = await fetchQuota({
|
|
246
|
+
readAuth: async () => consoleAuth({ expires: Date.now() - 1_000 }),
|
|
247
|
+
fetchImpl,
|
|
248
|
+
});
|
|
249
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
250
|
+
expect(result.configured).toBe(true);
|
|
251
|
+
expect(result.error).toMatch(/sign-in expired/i);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('surfaces a 401 as an expired sign-in', async () => {
|
|
255
|
+
const result = await fetchQuota({
|
|
256
|
+
readAuth: async () => consoleAuth(),
|
|
257
|
+
fetchImpl: async () => new Response('', { status: 401 }),
|
|
258
|
+
});
|
|
259
|
+
expect(result.configured).toBe(true);
|
|
260
|
+
expect(result.error).toMatch(/sign-in expired/i);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('distinguishes an absent subscription from a malformed response', async () => {
|
|
264
|
+
const absent = await fetchQuota({
|
|
265
|
+
readAuth: async () => consoleAuth(),
|
|
266
|
+
fetchImpl: async () => new Response(JSON.stringify({ product: 'free', access: { meters: {} } })),
|
|
267
|
+
});
|
|
268
|
+
expect(absent.configured).toBe(true);
|
|
269
|
+
expect(absent.error).toMatch(/subscription/i);
|
|
270
|
+
|
|
271
|
+
const malformed = await fetchQuota({
|
|
272
|
+
readAuth: async () => consoleAuth(),
|
|
273
|
+
fetchImpl: async () => new Response('not json', { status: 200 }),
|
|
274
|
+
});
|
|
275
|
+
expect(malformed.configured).toBe(true);
|
|
276
|
+
expect(malformed.error).toMatch(/unreadable/i);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('reports a temporary request failure as configured', async () => {
|
|
280
|
+
const result = await fetchQuota({
|
|
281
|
+
readAuth: async () => consoleAuth(),
|
|
282
|
+
fetchImpl: async () => new Response('', { status: 502 }),
|
|
283
|
+
});
|
|
284
|
+
expect(result.configured).toBe(true);
|
|
285
|
+
expect(result.error).toMatch(/HTTP 502/);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('follows the selected account on every read instead of caching usage', async () => {
|
|
289
|
+
let call = 0;
|
|
290
|
+
const seen = [];
|
|
291
|
+
const result = await fetchQuota({
|
|
292
|
+
readAuth: async () => {
|
|
293
|
+
call += 1;
|
|
294
|
+
return consoleAuth({ orgID: call === 1 ? 'org_FIRST123' : 'org_SECOND123' });
|
|
295
|
+
},
|
|
296
|
+
fetchImpl: async (_url, options) => {
|
|
297
|
+
seen.push(options.headers['x-org-id']);
|
|
298
|
+
return new Response(JSON.stringify(consolePayload({ meters: {
|
|
299
|
+
fiveHour: { resetsAt: '2026-08-12T12:00:00.000Z', limitMicroCents: '1000', usedMicroCents: call === 1 ? '250' : '900' },
|
|
300
|
+
} })));
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
expect(seen).toEqual(['org_FIRST123']);
|
|
304
|
+
expect(result.usage.windows['5h'].usedPercent).toBe(25);
|
|
305
|
+
|
|
306
|
+
const second = await fetchQuota({
|
|
307
|
+
readAuth: async () => consoleAuth({ orgID: 'org_SECOND123' }),
|
|
308
|
+
fetchImpl: async () => new Response(JSON.stringify(consolePayload({ meters: {
|
|
309
|
+
fiveHour: { resetsAt: '2026-08-12T12:00:00.000Z', limitMicroCents: '1000', usedMicroCents: '900' },
|
|
310
|
+
} }))),
|
|
311
|
+
});
|
|
312
|
+
expect(second.usage.windows['5h'].usedPercent).toBe(90);
|
|
313
|
+
});
|
|
91
314
|
});
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
|
|
15
15
|
export const providerId = 'openrouter';
|
|
16
16
|
export const providerName = 'OpenRouter';
|
|
17
|
-
|
|
17
|
+
const aliases = ['openrouter'];
|
|
18
18
|
const OPENROUTER_API_BASE = 'https://openrouter.ai/api/v1';
|
|
19
19
|
const PERIOD_SECONDS = { daily: 86400, weekly: 604800, monthly: 30 * 86400 };
|
|
20
20
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
export const providerId = 'zenmux';
|
|
12
12
|
export const providerName = 'ZenMux';
|
|
13
|
-
|
|
13
|
+
const aliases = ['zenmux'];
|
|
14
14
|
const ZENMUX_BALANCE_URL = 'https://zenmux.ai/api/v1/management/payg/balance';
|
|
15
15
|
|
|
16
16
|
const readStoredCredential = () => readManagedCredential(providerId);
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { asNonEmptyString } from '../../shared/guards.js';
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
if (typeof value !== 'string') return null;
|
|
5
|
-
const trimmed = value.trim();
|
|
6
|
-
return trimmed ? trimmed : null;
|
|
7
|
-
};
|
|
3
|
+
export const asObject = (value) => (value && typeof value === 'object' ? value : null);
|
|
8
4
|
|
|
9
5
|
export const toNumber = (value) => {
|
|
10
6
|
if (typeof value === 'number' && Number.isFinite(value)) {
|