@openchambery/web 2.0.0-beta.3 → 2.0.0-beta.30
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 +14 -0
- package/dist/assets/{AgentAvatar-B_JQoreg.js → AgentAvatar-O2x_1cFU.js} +1 -1
- package/dist/assets/AssistantView-pBywAsqy.js +3 -0
- package/dist/assets/{DiagramView-BNnda8qO.js → DiagramView-B984aE-R.js} +1 -1
- package/dist/assets/{FadeInOnReveal-VYQTSAkr.js → FadeInOnReveal-CMBd5BTd.js} +1 -1
- package/dist/assets/Icon-BN87CyS4.js +1 -0
- package/dist/assets/MarkdownRenderer-DHiNU8n3.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-DwrHFGUp.js → MarkdownRendererImpl-xJHMGHju.js} +3 -3
- package/dist/assets/MarkstreamRendererImpl-BZflns6x.js +3 -0
- package/dist/assets/{MobileDetailNavigation-BXr6AGLT.js → MobileDetailNavigation-DUvxsvrB.js} +1 -1
- package/dist/assets/{MobileShareBridge-D7OE9OP_.js → MobileShareBridge-DWo-J3Vc.js} +3 -3
- package/dist/assets/MobileSurface-B-Y6U5xm.js +2 -0
- package/dist/assets/{MultiRunWindow-DF3iIFCd.js → MultiRunWindow-rYiVSNKK.js} +1 -1
- package/dist/assets/{OnboardingScreen-BjHmlwlw.js → OnboardingScreen-BLjZIBOs.js} +1 -1
- package/dist/assets/{OverlayScrollbar-DLtWO_Gp.js → OverlayScrollbar-CoZ4wtau.js} +1 -1
- package/dist/assets/SettingsView-pjD0LrHE.js +25 -0
- package/dist/assets/{SettingsWindow-BI5swspb.js → SettingsWindow-CFOQRwjA.js} +1 -1
- package/dist/assets/TerminalView-Dw5jaYkc.js +8 -0
- package/dist/assets/{ToolOutputDialog-BEWhBcRd.js → ToolOutputDialog-B4v-ona2.js} +1 -1
- package/dist/assets/appThemeRegistry-BqDCGocy.js +1 -0
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-sG8YQsVs.js} +1 -1
- package/dist/assets/{button-DKuz3L8l.js → button-Ck8ipzBm.js} +1 -1
- package/dist/assets/collapsible-nU99prkD.js +1 -0
- package/dist/assets/context-menu-DMzZN1oi.js +1 -0
- package/dist/assets/{desktopHosts-CoDkOnyS.js → desktopHosts-DuftEj8B.js} +1 -1
- package/dist/assets/desktopRelayRestore-g378BdZf.js +1 -0
- package/dist/assets/{diagram-D7T8HQgj.js → diagram-CvO4P8ds.js} +1 -1
- package/dist/assets/dialog-ByGUkS3c.js +1 -0
- package/dist/assets/dropdown-menu.styles-ayLWk2gf.js +1 -0
- package/dist/assets/es-DCjl24dV.js +15 -0
- package/dist/assets/fileMentionTouchSelection-Cgkxwzqb.js +2 -0
- package/dist/assets/{fileQueries-JrpcsOCs.js → fileQueries-DuF89nFO.js} +1 -1
- package/dist/assets/fr-DfJ5j0t5.js +13 -0
- package/dist/assets/{gitApi-CM42qUed.js → gitApi-Dwqkc2nZ.js} +3 -3
- package/dist/assets/{imageSource-CIl0FL10.js → imageSource-BpUsiwFd.js} +1 -1
- package/dist/assets/{input-CzQy8UhV.js → input-CXhFmHcq.js} +1 -1
- package/dist/assets/insertionBoundaries-BhWfAOPY.js +1 -0
- package/dist/assets/ja-B_9_w2Xf.js +15 -0
- package/dist/assets/ko-DlGbo3kV.js +15 -0
- package/dist/assets/main-CGMinhgQ.js +169 -0
- package/dist/assets/main-pMpcLJha.js +2 -0
- package/dist/assets/{markdownHeightCache-Cj1I5NDw.js → markdownHeightCache-g_UsWzbA.js} +1 -1
- package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
- package/dist/assets/{mcpQueries-D5mQymgM.js → mcpQueries-DfrW3VHF.js} +1 -1
- package/dist/assets/miniChat-YOt30ECc.js +2 -0
- package/dist/assets/mobile-BK1bBCYD.js +2 -0
- package/dist/assets/multirun-Dua24A69.js +3 -0
- package/dist/assets/persistence-Dnj1QOdX.js +3 -0
- package/dist/assets/pl-DjUsS8Ei.js +15 -0
- package/dist/assets/platform-X3Y3GyM5.js +6 -0
- package/dist/assets/projectMeta-DXdoPmbz.js +1 -0
- package/dist/assets/pt-BR-B8sV7qo1.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-D2XooXKZ.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-BOuEz_oT.js +2 -0
- package/dist/assets/renderMobileApp-C8Alpv-B.js +28 -0
- package/dist/assets/runtime-fetch-CiqQd__Y.js +1 -0
- package/dist/assets/runtime-switch-BJM2k_uH.js +1 -0
- package/dist/assets/runtimeConfig-DX_NP2Zm.js +2 -0
- package/dist/assets/runtimeEndpointReset-BSQU3e5U.js +148 -0
- package/dist/assets/runtimeEndpointReset-DsuP5FPT.css +1 -0
- package/dist/assets/sessionChangeSummary-8AIBkExf.js +2 -0
- package/dist/assets/{sessionLookup-myHe5ltu.js → sessionLookup-Bdwd1Fit.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-BQtmHx9r.js → sortable-tabs-strip-T-dk_L69.js} +1 -1
- package/dist/assets/src-OzbnJsph.css +2 -0
- package/dist/assets/store-Cuktref-.js +15 -0
- package/dist/assets/{terminalApi-BxcdiduX.js → terminalApi-C_4joKUG.js} +1 -1
- package/dist/assets/textarea-biAqiqnJ.js +1 -0
- package/dist/assets/{tooltip-Cx5gTbdY.js → tooltip-BKL7J5hx.js} +1 -1
- package/dist/assets/uk-Bh7HXyLp.js +15 -0
- package/dist/assets/{url-CDHnjlsm.js → url-DNRMq_0R.js} +1 -1
- package/dist/assets/{useAppFontEffects-CNeo0aZs.js → useAppFontEffects-bafOEpgK.js} +57 -52
- package/dist/assets/{useAssistantUIStore-asNG6K7n.js → useAssistantUIStore-DBwfg9nX.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-CkCVOkxa.js +1 -0
- package/dist/assets/{useEffectiveDirectory-B4LvR8Gt.js → useEffectiveDirectory-B7htpJfU.js} +1 -1
- package/dist/assets/{useFontPreferences-CUnQ4TVm.js → useFontPreferences-CMrbTA0r.js} +1 -1
- package/dist/assets/useMobileNavigationStore-D2tnhKYV.js +415 -0
- package/dist/assets/{useRuntimeAPIs-DwLtO0c_.js → useRuntimeAPIs-DSLXJybW.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-BC5OOH3M.js +2 -0
- package/dist/assets/{useThemeSystem-BlNUWH47.js → useThemeSystem-DMzWVJaW.js} +1 -1
- package/dist/assets/useUIStore-Dfx6TKvi.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-CJZbJDW3.js → useWorkerHighlightedLines-BNQUAMEB.js} +1 -1
- package/dist/assets/{vendor-base-ui-CDjyofeD.js → vendor-base-ui-CnHfUmDN.js} +1 -1
- package/dist/assets/{vendor-cmdk-BFH0UpXX.js → vendor-cmdk-CeAGfrLo.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-accessibility-BrR3AKvM.js → vendor-dnd-kit-accessibility-BY5LhhUB.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-core-CNfdvk6H.js → vendor-dnd-kit-core-CvWxUPdb.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-sortable-DQUaTeGj.js → vendor-dnd-kit-sortable-hHdP9lLb.js} +1 -1
- package/dist/assets/{vendor-framer-motion-4dogsUk3.js → vendor-framer-motion-DFy_BSVd.js} +1 -1
- package/dist/assets/{vendor-legendapp-list-BU7MdPZR.js → vendor-legendapp-list-WE32lwNk.js} +1 -1
- package/dist/assets/{vendor-markstream-react-qHC2FykU.js → vendor-markstream-react-DX55XLGO.js} +3 -3
- package/dist/assets/vendor-opencode-client-MF2GBrFx.js +12 -0
- package/dist/assets/{vendor-pierre-diffs-CUfqC003.js → vendor-pierre-diffs-DX7XTPG3.js} +1 -1
- package/dist/assets/{vendor-react-C94A0AHl.js → vendor-react-DLPElpoQ.js} +1 -1
- package/dist/assets/{vendor-react-drawio-FVlWqKVE.js → vendor-react-drawio-ECtEwvbc.js} +1 -1
- package/dist/assets/{vendor-reactuses-core-C2A3EjUi.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
- package/dist/assets/{vendor-sonner-DNqHmUQs.js → vendor-sonner-DVzioF3d.js} +1 -1
- package/dist/assets/{vendor-stream-diffs-CwbKsmlv.js → vendor-stream-diffs-DWy3xSmp.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-query-D0iFK57C.js → vendor-tanstack-react-query-D7pys_c9.js} +1 -1
- package/dist/assets/{vendor-tanstack-react-virtual-B2kV6cnT.js → vendor-tanstack-react-virtual-tlDgH6PG.js} +1 -1
- package/dist/assets/{vendor-zustand-BqLKf6ZY.js → vendor-zustand-DzRqcymP.js} +2 -2
- package/dist/assets/zh-CN-Uzia0gk4.js +15 -0
- package/dist/assets/zh-TW-CiI7DLdB.js +15 -0
- package/dist/index.html +15 -15
- package/dist/mini-chat.html +15 -15
- package/dist/mobile.html +15 -15
- package/package.json +3 -2
- package/server/index.js +46 -27
- package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
- package/server/lib/browser-provider/contract.js +312 -0
- package/server/lib/browser-provider/host.js +421 -0
- package/server/lib/browser-provider/host.test.js +304 -0
- package/server/lib/browser-provider/routes.js +122 -0
- package/server/lib/browser-provider/routes.test.js +120 -0
- package/server/lib/browser-provider/selection-store.js +43 -0
- package/server/lib/browser-provider/selection-store.test.js +35 -0
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- package/server/lib/git/DOCUMENTATION.md +1 -0
- package/server/lib/git/routes.js +7 -4
- package/server/lib/git/routes.test.js +34 -0
- package/server/lib/git/service.js +3 -1
- package/server/lib/llm/DOCUMENTATION.md +6 -4
- package/server/lib/llm/generate.js +8 -9
- package/server/lib/llm/generate.test.js +33 -6
- package/server/lib/message-queue/service.js +1 -1
- package/server/lib/notifications/DOCUMENTATION.md +3 -1
- package/server/lib/notifications/runtime.js +128 -31
- package/server/lib/notifications/runtime.test.js +103 -0
- package/server/lib/notifications/template-runtime.js +130 -38
- package/server/lib/notifications/template-runtime.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +36 -19
- package/server/lib/opencode/agents.js +13 -22
- package/server/lib/opencode/auth-state-runtime.js +3 -3
- package/server/lib/opencode/auth-state-runtime.test.js +59 -0
- package/server/lib/opencode/auth.d.ts +13 -0
- package/server/lib/opencode/auth.js +74 -64
- package/server/lib/opencode/auth.test.js +57 -0
- package/server/lib/opencode/commands.js +1 -1
- package/server/lib/opencode/config-entities-layering.test.js +72 -0
- package/server/lib/opencode/config-entity-routes.js +16 -124
- package/server/lib/opencode/config-entity-routes.test.js +48 -11
- package/server/lib/opencode/config-files.d.ts +17 -0
- package/server/lib/opencode/config-files.js +187 -0
- package/server/lib/opencode/config-files.test.js +201 -0
- package/server/lib/opencode/core-routes.js +0 -1
- package/server/lib/opencode/core-routes.test.js +0 -15
- package/server/lib/opencode/credential-boundary.d.ts +1 -0
- package/server/lib/opencode/credential-boundary.js +11 -0
- package/server/lib/opencode/ensure-cli.d.ts +8 -0
- package/server/lib/opencode/ensure-cli.js +4 -1
- package/server/lib/opencode/env-config.js +4 -1
- package/server/lib/opencode/env-config.test.js +28 -0
- package/server/lib/opencode/env-runtime.test.js +18 -1
- package/server/lib/opencode/feature-routes-runtime.js +19 -7
- package/server/lib/opencode/feature-routes-runtime.test.js +7 -0
- package/server/lib/opencode/hmr-state-runtime.js +4 -4
- package/server/lib/opencode/lifecycle.js +1 -49
- package/server/lib/opencode/lifecycle.test.js +30 -4
- package/server/lib/opencode/location-release.d.ts +8 -0
- package/server/lib/opencode/location-release.js +32 -0
- package/server/lib/opencode/location-release.test.js +43 -0
- package/server/lib/opencode/mcp.js +1 -1
- package/server/lib/opencode/opencode2-pin.js +2 -1
- package/server/lib/opencode/owned-runtime-upgrade.test.js +5 -5
- package/server/lib/opencode/plugin-routes.test.js +4 -2
- package/server/lib/opencode/plugins.js +12 -60
- package/server/lib/opencode/plugins.test.js +5 -3
- package/server/lib/opencode/providers.js +6 -3
- package/server/lib/opencode/proxy.js +13 -1
- package/server/lib/opencode/proxy.test.js +31 -3
- package/server/lib/opencode/remote-upgrade.integration.test.js +66 -0
- package/server/lib/opencode/routes.js +40 -8
- package/server/lib/opencode/routes.test.js +51 -10
- package/server/lib/opencode/runtime-contract.js +3 -2
- package/server/lib/opencode/runtime-contract.test.js +12 -12
- package/server/lib/opencode/shared.js +6 -175
- package/server/lib/opencode/update-discovery.js +39 -0
- package/server/lib/opencode/update-discovery.test.js +39 -0
- package/server/lib/opencode/upgrade-screen.d.ts +30 -0
- package/server/lib/opencode/upgrade-screen.js +292 -0
- package/server/lib/opencode/upgrade-screen.test.js +196 -0
- package/server/lib/opencode/version-alignment.test.js +40 -0
- package/server/lib/question-auto-delegate/DOCUMENTATION.md +21 -6
- package/server/lib/question-auto-delegate/core.d.ts +5 -1
- package/server/lib/question-auto-delegate/core.js +21 -13
- package/server/lib/question-auto-delegate/form-io.d.ts +6 -0
- package/server/lib/question-auto-delegate/form-io.js +39 -0
- package/server/lib/question-auto-delegate/runtime.js +4 -24
- package/server/lib/question-auto-delegate/runtime.test.js +138 -0
- package/server/lib/quota/DOCUMENTATION.md +9 -0
- package/server/lib/quota/provider-credentials.test.js +29 -0
- package/server/lib/quota/providers/claude.js +4 -4
- package/server/lib/quota/providers/codex.js +4 -4
- package/server/lib/quota/providers/codex.test.js +1 -1
- package/server/lib/quota/providers/copilot.js +5 -5
- package/server/lib/quota/providers/google/auth.js +3 -3
- package/server/lib/quota/providers/google/index.js +2 -2
- package/server/lib/quota/providers/index.js +3 -7
- package/server/lib/quota/providers/index.test.js +2 -2
- package/server/lib/quota/providers/kimi.js +4 -4
- package/server/lib/quota/providers/minimax-shared.js +4 -4
- package/server/lib/quota/providers/nanogpt.js +4 -4
- package/server/lib/quota/providers/openai.js +4 -4
- package/server/lib/quota/providers/openrouter.js +4 -4
- package/server/lib/quota/providers/wafer.js +4 -4
- package/server/lib/quota/providers/zai.js +4 -4
- package/server/lib/quota/providers/zhipuai-coding-plan.js +6 -20
- package/server/lib/quota/routes.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +3 -1
- package/server/lib/relay/service.js +1 -0
- package/server/lib/relay/service.test.js +13 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -1
- package/server/lib/scheduled-tasks/runtime.js +12 -4
- package/server/lib/scheduled-tasks/runtime.test.js +81 -6
- package/server/lib/session-goal/DOCUMENTATION.md +11 -0
- package/server/lib/session-goal/runtime.js +4 -4
- package/server/lib/session-goal/runtime.test.js +32 -3
- package/server/lib/session-index/DOCUMENTATION.md +7 -0
- package/server/lib/session-index/event-ingest.js +13 -0
- package/server/lib/session-index/event-ingest.test.js +7 -0
- package/server/lib/session-index/service.js +45 -0
- package/server/lib/session-index/service.test.js +47 -0
- package/server/lib/session-metadata/DOCUMENTATION.md +33 -17
- package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
- package/server/lib/session-metadata/opencode-session-record.js +316 -0
- package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
- package/server/lib/session-metadata/routes.js +5 -1
- package/server/lib/session-metadata/session-archive.js +1 -1
- package/server/lib/session-metadata/session-metadata-store.d.ts +2 -0
- package/server/lib/session-metadata/session-metadata-store.js +63 -16
- package/server/lib/session-metadata/session-metadata-store.test.js +23 -0
- package/server/lib/session-metadata/session-projection.d.ts +1 -0
- package/server/lib/session-metadata/session-projection.js +49 -2
- package/server/lib/session-metadata/session-projection.test.js +35 -0
- package/server/lib/session-title/DOCUMENTATION.md +52 -14
- package/server/lib/session-title/native-runtime.test.js +59 -5
- package/server/lib/session-title/runtime.js +65 -19
- package/server/lib/session-title/session-access.js +108 -3
- package/server/lib/session-title/session-access.test.js +125 -7
- package/server/lib/tts/DOCUMENTATION.md +7 -5
- package/server/lib/tts/credential-source.test.js +23 -0
- package/server/lib/tts/routes.js +2 -2
- package/server/lib/tts/service.js +16 -37
- package/dist/assets/AssistantView-ClQAfGOT.js +0 -3
- package/dist/assets/Icon-BiEQEcih.js +0 -1
- package/dist/assets/MarkdownRenderer-CJHSsWin.js +0 -2
- package/dist/assets/MarkstreamRendererImpl-CSnbQMu3.js +0 -3
- package/dist/assets/MobileSurface-D2jUkX9l.js +0 -2
- package/dist/assets/SettingsView-BRxELjow.js +0 -25
- package/dist/assets/TerminalView-BU3x8-fr.js +0 -8
- package/dist/assets/appThemeRegistry-4MR0F7Jz.js +0 -1
- package/dist/assets/collapsible-DXbDynN4.js +0 -3
- package/dist/assets/context-menu-CqolsLCm.js +0 -1
- package/dist/assets/desktopRelayRestore-DDlVKyOE.js +0 -1
- package/dist/assets/dialog-DQYl3W_o.js +0 -1
- package/dist/assets/dropdown-menu.styles-Bzo6L3FH.js +0 -1
- package/dist/assets/es-CN3E_zXj.js +0 -15
- package/dist/assets/fileMentionTouchSelection-BZ6ovBYH.js +0 -4
- package/dist/assets/fr-9zZvQZD2.js +0 -13
- package/dist/assets/insertionBoundaries-CtkZ9720.js +0 -1
- package/dist/assets/ja-Dnjk_82N.js +0 -15
- package/dist/assets/ko-BxCEE99C.js +0 -15
- package/dist/assets/main-BILgBI3B.js +0 -169
- package/dist/assets/main-BeveiW6F.js +0 -2
- package/dist/assets/markdownTheme-CKac3wKC.js +0 -1
- package/dist/assets/miniChat-BgrzM3u7.js +0 -2
- package/dist/assets/mobile-DtYoQE8C.js +0 -2
- package/dist/assets/multirun-jy8dEI96.js +0 -4
- package/dist/assets/persistence-C9LQpiOS.js +0 -3
- package/dist/assets/pl-CNNsWnPM.js +0 -15
- package/dist/assets/platform-BQghHRfp.js +0 -6
- package/dist/assets/pt-BR-BZB2DJMp.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +0 -2
- package/dist/assets/renderMobileApp-u3c4GtFC.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtime-switch-LvWDU-SL.js +0 -1
- package/dist/assets/runtimeConfig-BIGBRGQI.js +0 -2
- package/dist/assets/runtimeEndpointReset-D2savkuw.js +0 -147
- package/dist/assets/sessionChangeSummary-eIbpSfrO.js +0 -2
- package/dist/assets/src-CLN7Z-zx.css +0 -2
- package/dist/assets/store-DLCiZaA6.js +0 -15
- package/dist/assets/textarea-CakKvqFw.js +0 -1
- package/dist/assets/uk-rwQrxeYM.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +0 -1
- package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +0 -414
- package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +0 -2
- package/dist/assets/useUIStore-CQYKJnrg.js +0 -1
- package/dist/assets/vendor-opencode-client-BOe2qpQb.js +0 -12
- package/dist/assets/zh-CN-BvmBDnkD.js +0 -15
- package/dist/assets/zh-TW-9wcHTX0y.js +0 -15
- package/server/lib/opencode/snippets.js +0 -242
- package/server/lib/opencode/snippets.test.js +0 -97
|
@@ -25,7 +25,8 @@ const normalizeQuestion = (raw, directoryHint = '') => {
|
|
|
25
25
|
const sessionID = asTrimmedString(raw.sessionID);
|
|
26
26
|
if (!requestID || !sessionID) return null;
|
|
27
27
|
const directory = asTrimmedString(raw.directory) || asTrimmedString(directoryHint);
|
|
28
|
-
|
|
28
|
+
if (Array.isArray(raw.fields) && raw.metadata?.kind !== 'question') return null;
|
|
29
|
+
const questions = Array.isArray(raw.fields) ? raw.fields : Array.isArray(raw.questions) ? raw.questions : [];
|
|
29
30
|
return {
|
|
30
31
|
requestID,
|
|
31
32
|
sessionID,
|
|
@@ -608,18 +609,18 @@ export function createQuestionAutoDelegateCore({
|
|
|
608
609
|
try {
|
|
609
610
|
if (kind === 'reject') {
|
|
610
611
|
entry.upstreamDispatched = true;
|
|
611
|
-
upstream = await io.postReject(requestID, directory, input.body);
|
|
612
|
+
upstream = await io.postReject(requestID, directory, input.body, entry.sessionID);
|
|
612
613
|
} else if (authority === 'manual') {
|
|
613
614
|
entry.upstreamDispatched = true;
|
|
614
615
|
// Forward the caller payload exactly (answers may be empty).
|
|
615
|
-
upstream = await io.postReply(requestID, directory, input.answers);
|
|
616
|
+
upstream = await io.postReply(requestID, directory, input.answers, entry.sessionID);
|
|
616
617
|
} else {
|
|
617
618
|
const count = entry.questionCount > 0 ? entry.questionCount : 1;
|
|
618
619
|
const answers = Array.isArray(input.answers) && input.answers.length > 0
|
|
619
620
|
? input.answers
|
|
620
621
|
: buildAutoDelegateAnswers(count, autoAnswer);
|
|
621
622
|
entry.upstreamDispatched = true;
|
|
622
|
-
upstream = await io.postReply(requestID, directory, answers);
|
|
623
|
+
upstream = await io.postReply(requestID, directory, answers, entry.sessionID);
|
|
623
624
|
}
|
|
624
625
|
} catch (error) {
|
|
625
626
|
// Throw before a classified upstream result: treat as pre-send failure → release for retry.
|
|
@@ -1157,12 +1158,18 @@ export function createQuestionAutoDelegateCore({
|
|
|
1157
1158
|
const processEvent = (payload, directoryHint = '') => {
|
|
1158
1159
|
if (disposed || !payload || typeof payload !== 'object') return;
|
|
1159
1160
|
const type = typeof payload.type === 'string' ? payload.type : '';
|
|
1160
|
-
const properties = payload.
|
|
1161
|
-
|
|
1161
|
+
const properties = payload.data && typeof payload.data === 'object'
|
|
1162
|
+
? payload.data
|
|
1163
|
+
: payload.properties && typeof payload.properties === 'object' ? payload.properties : {};
|
|
1164
|
+
const hint = asTrimmedString(payload.location?.directory) || asTrimmedString(directoryHint);
|
|
1162
1165
|
|
|
1163
1166
|
if (type === 'session.created' || type === 'session.updated') {
|
|
1164
|
-
|
|
1165
|
-
|
|
1167
|
+
const info = properties.info ?? {
|
|
1168
|
+
id: properties.sessionID,
|
|
1169
|
+
parentID: properties.parentID,
|
|
1170
|
+
directory: properties.location?.directory,
|
|
1171
|
+
};
|
|
1172
|
+
rememberSession(info, hint);
|
|
1166
1173
|
const sessionId = asTrimmedString(info?.id);
|
|
1167
1174
|
const directory = asTrimmedString(info?.directory) || hint;
|
|
1168
1175
|
if (sessionId && directory) {
|
|
@@ -1176,8 +1183,9 @@ export function createQuestionAutoDelegateCore({
|
|
|
1176
1183
|
return;
|
|
1177
1184
|
}
|
|
1178
1185
|
|
|
1179
|
-
if (type === 'question.asked') {
|
|
1180
|
-
|
|
1186
|
+
if (type === 'question.asked' || type === 'form.created') {
|
|
1187
|
+
if (type === 'form.created' && properties.form?.metadata?.kind !== 'question') return;
|
|
1188
|
+
const normalized = normalizeQuestion(type === 'form.created' ? properties.form : properties, hint);
|
|
1181
1189
|
if (!normalized) return;
|
|
1182
1190
|
rememberSession({ id: normalized.sessionID, directory: normalized.directory }, normalized.directory || hint);
|
|
1183
1191
|
if (!normalized.directory) {
|
|
@@ -1191,13 +1199,13 @@ export function createQuestionAutoDelegateCore({
|
|
|
1191
1199
|
return;
|
|
1192
1200
|
}
|
|
1193
1201
|
|
|
1194
|
-
if (type === 'question.replied' || type === 'question.rejected') {
|
|
1195
|
-
const requestID = asTrimmedString(properties.id) || asTrimmedString(properties.requestID);
|
|
1202
|
+
if (type === 'question.replied' || type === 'question.rejected' || type === 'form.replied' || type === 'form.cancelled') {
|
|
1203
|
+
const requestID = asTrimmedString(properties.formID) || asTrimmedString(properties.id) || asTrimmedString(properties.requestID);
|
|
1196
1204
|
if (!requestID) return;
|
|
1197
1205
|
const entry = requests.get(requestID);
|
|
1198
1206
|
if (!entry) return;
|
|
1199
1207
|
if (entry.state === TERMINAL_SETTLED) return;
|
|
1200
|
-
const resolution = type === 'question.rejected' ? 'rejected' : 'replied';
|
|
1208
|
+
const resolution = type === 'question.rejected' || type === 'form.cancelled' ? 'rejected' : 'replied';
|
|
1201
1209
|
if (entry.state === 'uncertain' || entry.state === 'submitting') {
|
|
1202
1210
|
markSettled(entry, resolution, entry.submittedBy ?? null);
|
|
1203
1211
|
bump();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { QuestionAutoDelegateIO, QuestionAutoDelegateUpstreamResult } from './core.js';
|
|
2
|
+
|
|
3
|
+
export function createQuestionFormIO(upstream: (
|
|
4
|
+
path: string,
|
|
5
|
+
options?: { directory?: string | null; method?: string; body?: unknown },
|
|
6
|
+
) => Promise<QuestionAutoDelegateUpstreamResult>): Pick<QuestionAutoDelegateIO, 'listQuestions' | 'postReply' | 'postReject'>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Native OpenCode form IO shared by Web and the VS Code host. */
|
|
2
|
+
export function createQuestionFormIO(upstream) {
|
|
3
|
+
const formPath = (sessionID, requestID) => {
|
|
4
|
+
if (!sessionID) throw new Error('Question form requires a sessionID');
|
|
5
|
+
return `/session/${encodeURIComponent(sessionID)}/form/${encodeURIComponent(requestID)}`;
|
|
6
|
+
};
|
|
7
|
+
return {
|
|
8
|
+
async listQuestions(directory) {
|
|
9
|
+
const result = await upstream('/form', { directory });
|
|
10
|
+
if (!result.ok) return null;
|
|
11
|
+
const rows = result.body?.data;
|
|
12
|
+
if (!Array.isArray(rows)) return null;
|
|
13
|
+
return rows.filter((form) => form?.metadata?.kind === 'question');
|
|
14
|
+
},
|
|
15
|
+
async postReply(requestID, directory, answers, sessionID) {
|
|
16
|
+
const path = formPath(sessionID, requestID);
|
|
17
|
+
const result = await upstream(path, { directory });
|
|
18
|
+
// A failed schema read never means the answer POST was dispatched.
|
|
19
|
+
if (!result.ok) return { ...result, notSent: true, uncertain: false };
|
|
20
|
+
const form = result.body?.data;
|
|
21
|
+
if (form?.id !== requestID || form?.sessionID !== sessionID
|
|
22
|
+
|| form.metadata?.kind !== 'question' || !Array.isArray(form.fields) || !form.fields.length) {
|
|
23
|
+
throw new Error('Invalid question form schema');
|
|
24
|
+
}
|
|
25
|
+
const answer = Object.fromEntries(form.fields.map((field, index) => {
|
|
26
|
+
if (!field.key || (field.type !== 'string' && field.type !== 'multiselect')) {
|
|
27
|
+
throw new Error('Unsupported question form field');
|
|
28
|
+
}
|
|
29
|
+
const values = (answers[index] ?? []).map((value) =>
|
|
30
|
+
field.options?.find((option) => (option.label || option.value) === value)?.value ?? value);
|
|
31
|
+
return [field.key, field.type === 'multiselect' ? values : values[0] ?? ''];
|
|
32
|
+
}));
|
|
33
|
+
return upstream(`${path}/reply`, { directory, method: 'POST', body: { answer } });
|
|
34
|
+
},
|
|
35
|
+
async postReject(requestID, directory, _body, sessionID) {
|
|
36
|
+
return upstream(`${formPath(sessionID, requestID)}/cancel`, { directory, method: 'POST' });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
createQuestionAutoDelegateCore,
|
|
3
3
|
QUESTION_AUTO_DELEGATE_DELAY_MS,
|
|
4
4
|
} from './core.js';
|
|
5
|
+
import { createQuestionFormIO } from './form-io.js';
|
|
5
6
|
|
|
6
7
|
const REQUEST_TIMEOUT_MS = 15_000;
|
|
7
8
|
|
|
@@ -44,7 +45,7 @@ export function createQuestionAutoDelegateRuntime({
|
|
|
44
45
|
} = {}) {
|
|
45
46
|
const upstream = async (path, { directory, method = 'GET', body } = {}) => {
|
|
46
47
|
const url = new URL(buildOpenCodeUrl(path, ''));
|
|
47
|
-
if (directory) url.searchParams.set('directory', directory);
|
|
48
|
+
if (directory) url.searchParams.set('location[directory]', directory);
|
|
48
49
|
let response;
|
|
49
50
|
try {
|
|
50
51
|
response = await fetchImpl(url, {
|
|
@@ -149,14 +150,7 @@ export function createQuestionAutoDelegateRuntime({
|
|
|
149
150
|
}
|
|
150
151
|
return Array.from(dirs);
|
|
151
152
|
},
|
|
152
|
-
|
|
153
|
-
const result = await upstream('/question', { directory });
|
|
154
|
-
if (!result.ok) return null;
|
|
155
|
-
const payload = result.body;
|
|
156
|
-
if (Array.isArray(payload)) return payload;
|
|
157
|
-
if (Array.isArray(payload?.data)) return payload.data;
|
|
158
|
-
return null;
|
|
159
|
-
},
|
|
153
|
+
...createQuestionFormIO(upstream),
|
|
160
154
|
async getSession(sessionID, directory) {
|
|
161
155
|
const result = await upstream(`/session/${encodeURIComponent(sessionID)}`, { directory });
|
|
162
156
|
if (!result.ok) return null;
|
|
@@ -167,23 +161,9 @@ export function createQuestionAutoDelegateRuntime({
|
|
|
167
161
|
return {
|
|
168
162
|
id: asTrimmedString(info.id) || sessionID,
|
|
169
163
|
parentID: asTrimmedString(info.parentID) || null,
|
|
170
|
-
directory: asTrimmedString(info.directory) || asTrimmedString(directory) || null,
|
|
164
|
+
directory: asTrimmedString(info.location?.directory) || asTrimmedString(info.directory) || asTrimmedString(directory) || null,
|
|
171
165
|
};
|
|
172
166
|
},
|
|
173
|
-
async postReply(requestID, directory, answers) {
|
|
174
|
-
return upstream(`/question/${encodeURIComponent(requestID)}/reply`, {
|
|
175
|
-
directory,
|
|
176
|
-
method: 'POST',
|
|
177
|
-
body: { answers },
|
|
178
|
-
});
|
|
179
|
-
},
|
|
180
|
-
async postReject(requestID, directory, body) {
|
|
181
|
-
return upstream(`/question/${encodeURIComponent(requestID)}/reject`, {
|
|
182
|
-
directory,
|
|
183
|
-
method: 'POST',
|
|
184
|
-
body: body && typeof body === 'object' ? body : {},
|
|
185
|
-
});
|
|
186
|
-
},
|
|
187
167
|
},
|
|
188
168
|
});
|
|
189
169
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { afterEach, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createQuestionAutoDelegateRuntime } from './runtime.js';
|
|
3
|
+
import { QUESTION_AUTO_DELEGATE_ANSWER } from './core.js';
|
|
4
|
+
|
|
5
|
+
const form = {
|
|
6
|
+
id: 'frm_question', sessionID: 'ses_test', title: 'Choose',
|
|
7
|
+
metadata: { kind: 'question' },
|
|
8
|
+
fields: [{ key: 'approach', type: 'string', custom: true, options: [{ label: 'A', value: 'a' }] }],
|
|
9
|
+
};
|
|
10
|
+
const runtimes = [];
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
runtimes.splice(0).forEach((runtime) => runtime.dispose());
|
|
13
|
+
vi.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const setup = async (pending = []) => {
|
|
17
|
+
vi.useFakeTimers();
|
|
18
|
+
let onEvent;
|
|
19
|
+
const fetchImpl = vi.fn(async (url, options) => {
|
|
20
|
+
const path = url.pathname;
|
|
21
|
+
if (path === '/api/form') return Response.json({ data: pending });
|
|
22
|
+
if (path === '/api/session/ses_test') return Response.json({ data: { id: 'ses_test', location: { directory: '/project' } } });
|
|
23
|
+
if (path === '/api/session/ses_test/form/frm_question') return Response.json({ data: form });
|
|
24
|
+
if (path === '/api/session/ses_test/form/frm_question/reply' && options.method === 'POST') return new Response(null, { status: 204 });
|
|
25
|
+
return Response.json({ error: 'Not found' }, { status: 404 });
|
|
26
|
+
});
|
|
27
|
+
const runtime = createQuestionAutoDelegateRuntime({
|
|
28
|
+
globalEventHub: { subscribeEvent: (fn) => { onEvent = fn; return () => {}; } },
|
|
29
|
+
buildOpenCodeUrl: (path) => `http://opencode.test/api${path}`,
|
|
30
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
31
|
+
readSettingsFromDiskMigrated: async () => ({ projects: [{ path: '/project' }] }),
|
|
32
|
+
fetchImpl,
|
|
33
|
+
});
|
|
34
|
+
runtimes.push(runtime);
|
|
35
|
+
runtime.start();
|
|
36
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
37
|
+
return {
|
|
38
|
+
runtime, fetchImpl,
|
|
39
|
+
emit: (type, data, directory = '/project') => onEvent({ directory: 'global', payload: {
|
|
40
|
+
type, data, ...(directory ? { location: { directory } } : {}),
|
|
41
|
+
} }),
|
|
42
|
+
posts: () => fetchImpl.mock.calls.filter(([, options]) => options.method === 'POST'),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
it('auto-delegates a native question form after exactly 30 seconds without a UI', async () => {
|
|
47
|
+
const { emit, posts, runtime } = await setup();
|
|
48
|
+
emit('form.created', { sessionID: form.sessionID, form });
|
|
49
|
+
await vi.advanceTimersByTimeAsync(29_999);
|
|
50
|
+
expect(posts()).toHaveLength(0);
|
|
51
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
52
|
+
expect(posts()).toHaveLength(1);
|
|
53
|
+
expect(posts()[0][0].pathname).toBe('/api/session/ses_test/form/frm_question/reply');
|
|
54
|
+
expect(JSON.parse(posts()[0][1].body)).toEqual({ answer: { approach: QUESTION_AUTO_DELEGATE_ANSWER } });
|
|
55
|
+
expect(runtime.snapshot().requests[0].state).toBe('settled');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('recovers pending native question forms on startup', async () => {
|
|
59
|
+
const { posts } = await setup([form]);
|
|
60
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
61
|
+
expect(posts()).toHaveLength(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it.each(['form.replied', 'form.cancelled'])('cancels the timer on %s', async (type) => {
|
|
65
|
+
const { emit, posts, runtime } = await setup();
|
|
66
|
+
emit('form.created', { sessionID: form.sessionID, form });
|
|
67
|
+
expect(runtime.snapshot().requests).toHaveLength(1);
|
|
68
|
+
emit(type, { sessionID: form.sessionID, id: form.id });
|
|
69
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
70
|
+
expect(posts()).toHaveLength(0);
|
|
71
|
+
expect(runtime.snapshot().requests[0].state).toBe('settled');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('does not auto-answer non-question forms', async () => {
|
|
75
|
+
const other = { ...form, metadata: { kind: 'configuration' } };
|
|
76
|
+
const { emit, posts, runtime } = await setup([other]);
|
|
77
|
+
emit('form.created', { sessionID: other.sessionID, form: other });
|
|
78
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
79
|
+
expect(posts()).toHaveLength(0);
|
|
80
|
+
expect(runtime.snapshot().requests).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('resolves a directory-less event through the native session location', async () => {
|
|
84
|
+
const { emit, posts } = await setup();
|
|
85
|
+
emit('form.created', { form }, '');
|
|
86
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
87
|
+
expect(posts()).toHaveLength(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('preserves pending questions when native list reconciliation fails', async () => {
|
|
91
|
+
const { emit, runtime, fetchImpl, posts } = await setup();
|
|
92
|
+
emit('form.created', { form });
|
|
93
|
+
const original = fetchImpl.getMockImplementation();
|
|
94
|
+
fetchImpl.mockImplementation((url, options) => url.pathname === '/api/form'
|
|
95
|
+
? Promise.resolve(new Response(null, { status: 503 })) : original(url, options));
|
|
96
|
+
await runtime.reconcile();
|
|
97
|
+
expect(runtime.snapshot().coverage.state).toBe('partial');
|
|
98
|
+
expect(runtime.snapshot().requests[0].state).toBe('counting');
|
|
99
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
100
|
+
expect(posts()).toHaveLength(1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('pauses for retry when schema lookup fails, without sending an answer', async () => {
|
|
104
|
+
const { emit, runtime, fetchImpl, posts } = await setup();
|
|
105
|
+
emit('form.created', { form });
|
|
106
|
+
const original = fetchImpl.getMockImplementation();
|
|
107
|
+
fetchImpl.mockImplementation((url, options) => url.pathname.endsWith('/form/frm_question')
|
|
108
|
+
? Promise.resolve(new Response(null, { status: 503 })) : original(url, options));
|
|
109
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
110
|
+
expect(posts()).toHaveLength(0);
|
|
111
|
+
expect(runtime.snapshot().requests[0].state).toBe('paused');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('honors user takeover and explicit delegation of native forms', async () => {
|
|
115
|
+
const { emit, runtime, posts } = await setup();
|
|
116
|
+
emit('form.created', { form });
|
|
117
|
+
const identity = { requestID: form.id, sessionID: form.sessionID, directory: '/project' };
|
|
118
|
+
await runtime.pause({ ...identity, reason: 'interaction' });
|
|
119
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
120
|
+
expect(posts()).toHaveLength(0);
|
|
121
|
+
await runtime.delegate(identity);
|
|
122
|
+
expect(posts()).toHaveLength(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('uses the freshly read schema for multiple fields and array answers', async () => {
|
|
126
|
+
const { emit, fetchImpl, posts } = await setup();
|
|
127
|
+
const original = fetchImpl.getMockImplementation();
|
|
128
|
+
fetchImpl.mockImplementation((url, options) => url.pathname.endsWith('/form/frm_question')
|
|
129
|
+
? Promise.resolve(Response.json({ data: { ...form, fields: [
|
|
130
|
+
{ key: 'actual_key', type: 'string', custom: true },
|
|
131
|
+
{ key: 'targets', type: 'multiselect', custom: true, options: [] },
|
|
132
|
+
] } })) : original(url, options));
|
|
133
|
+
emit('form.created', { form: { ...form, fields: [...form.fields, { key: 'old', type: 'multiselect' }] } });
|
|
134
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
135
|
+
expect(JSON.parse(posts()[0][1].body)).toEqual({ answer: {
|
|
136
|
+
actual_key: QUESTION_AUTO_DELEGATE_ANSWER, targets: [QUESTION_AUTO_DELEGATE_ANSWER],
|
|
137
|
+
} });
|
|
138
|
+
});
|
|
@@ -38,6 +38,15 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
|
|
|
38
38
|
- `providers/openai.js` exists for logic parity/reuse but is intentionally not registered for dispatcher ID routing.
|
|
39
39
|
|
|
40
40
|
## Response contract
|
|
41
|
+
Provider credentials are read asynchronously from the current OpenCode service via
|
|
42
|
+
`opencode/auth.js`; only the selected credential is used. Account switching and
|
|
43
|
+
logout are observed on the next request, without file fallback or persisted secret
|
|
44
|
+
cache. Credential-list transport/auth/schema failure rejects the configured-provider
|
|
45
|
+
list instead of returning an authoritative empty list; per-provider quota requests
|
|
46
|
+
return `ok: false` with a sanitized error. VS Code uses the same credential adapter.
|
|
47
|
+
OpenChamber-owned quota cookies and Grok/Antigravity-specific stores retain their
|
|
48
|
+
separate explicit sources; they are not legacy OpenCode `auth.json` fallbacks.
|
|
49
|
+
|
|
41
50
|
All providers should return results via shared helpers to preserve API shape:
|
|
42
51
|
- Required fields: `providerId`, `providerName`, `ok`, `configured`, `usage`, `fetchedAt`
|
|
43
52
|
- Optional field: `error`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { afterEach, expect, it, vi } from 'vitest';
|
|
2
|
+
import { configureOpenCodeCredentials } from '../opencode/auth.js';
|
|
3
|
+
import { fetchQuota as fetchCodexQuota } from './providers/codex.js';
|
|
4
|
+
import { fetchQuotaForProvider, listConfiguredQuotaProviders } from './providers/index.js';
|
|
5
|
+
|
|
6
|
+
afterEach(() => { configureOpenCodeCredentials(null); vi.unstubAllGlobals(); });
|
|
7
|
+
|
|
8
|
+
it('quota requests follow the current active account, then stop sending after logout', async () => {
|
|
9
|
+
let access = 'first-account';
|
|
10
|
+
configureOpenCodeCredentials(() => ({ credential: { list: async () => access ? [{ id: 'selected', integrationID: 'openai', active: true, value: { type: 'oauth', access, refresh: 'refresh', expires: 123 } }] : [] } }));
|
|
11
|
+
const fetchMock = vi.fn(async () => Response.json({ rate_limit: { primary_window: { used_percent: 20 } } }));
|
|
12
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
13
|
+
await fetchCodexQuota();
|
|
14
|
+
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer first-account');
|
|
15
|
+
access = 'second-account';
|
|
16
|
+
await fetchCodexQuota();
|
|
17
|
+
expect(fetchMock.mock.calls[1][1].headers.Authorization).toBe('Bearer second-account');
|
|
18
|
+
access = '';
|
|
19
|
+
await expect(fetchCodexQuota()).resolves.toMatchObject({ ok: false, configured: false });
|
|
20
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('quota source failure remains an error with no upstream secret in the response', async () => {
|
|
24
|
+
configureOpenCodeCredentials(() => ({ credential: { list: async () => { throw new Error('secret-from-upstream'); } } }));
|
|
25
|
+
await expect(listConfiguredQuotaProviders()).rejects.toThrow('Unable to read OpenCode credentials');
|
|
26
|
+
const result = await fetchQuotaForProvider('codex');
|
|
27
|
+
expect(result).toMatchObject({ ok: false, error: 'Unable to read OpenCode credentials' });
|
|
28
|
+
expect(JSON.stringify(result)).not.toContain('secret-from-upstream');
|
|
29
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -12,14 +12,14 @@ export const providerId = 'claude';
|
|
|
12
12
|
export const providerName = 'Claude';
|
|
13
13
|
const aliases = ['anthropic', 'claude'];
|
|
14
14
|
|
|
15
|
-
export const isConfigured = () => {
|
|
16
|
-
const auth =
|
|
15
|
+
export const isConfigured = async () => {
|
|
16
|
+
const auth = await readOpenCodeCredentials();
|
|
17
17
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
18
|
return Boolean(entry?.access || entry?.token);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const fetchQuota = async () => {
|
|
22
|
-
const auth =
|
|
22
|
+
const auth = await readOpenCodeCredentials();
|
|
23
23
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
24
|
const accessToken = entry?.access ?? entry?.token;
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'codex';
|
|
|
14
14
|
export const providerName = 'Codex';
|
|
15
15
|
const aliases = ['openai', 'codex', 'chatgpt'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.access || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const accessToken = entry?.access ?? entry?.token;
|
|
27
27
|
const accountId = entry?.accountId;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
vi.mock('../../opencode/auth.js', () => ({
|
|
4
|
-
|
|
4
|
+
readOpenCodeCredentials: async () => ({ openai: { access: 'test-token' } }),
|
|
5
5
|
}));
|
|
6
6
|
|
|
7
7
|
import { fetchQuota } from './codex.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -42,14 +42,14 @@ export const providerId = 'github-copilot';
|
|
|
42
42
|
export const providerName = 'GitHub Copilot';
|
|
43
43
|
const aliases = ['github-copilot', 'copilot'];
|
|
44
44
|
|
|
45
|
-
export const isConfigured = () => {
|
|
46
|
-
const auth =
|
|
45
|
+
export const isConfigured = async () => {
|
|
46
|
+
const auth = await readOpenCodeCredentials();
|
|
47
47
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
48
48
|
return Boolean(entry?.access || entry?.token);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
export const fetchQuota = async () => {
|
|
52
|
-
const auth =
|
|
52
|
+
const auth = await readOpenCodeCredentials();
|
|
53
53
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
54
54
|
const accessToken = entry?.access ?? entry?.token;
|
|
55
55
|
|
|
@@ -107,7 +107,7 @@ export const providerIdAddon = 'github-copilot-addon';
|
|
|
107
107
|
export const providerNameAddon = 'GitHub Copilot Add-on';
|
|
108
108
|
|
|
109
109
|
export const fetchQuotaAddon = async () => {
|
|
110
|
-
const auth =
|
|
110
|
+
const auth = await readOpenCodeCredentials();
|
|
111
111
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
112
112
|
const accessToken = entry?.access ?? entry?.token;
|
|
113
113
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
asNonEmptyString,
|
|
15
15
|
toTimestamp
|
|
16
16
|
} from '../../utils/index.js';
|
|
17
|
-
import {
|
|
17
|
+
import { readOpenCodeCredentials } from '../../../opencode/auth.js';
|
|
18
18
|
import { parseGoogleRefreshToken } from './transforms.js';
|
|
19
19
|
|
|
20
20
|
const ANTIGRAVITY_GOOGLE_CLIENT_ID =
|
|
@@ -90,8 +90,8 @@ const resolveAntigravityAuth = () => {
|
|
|
90
90
|
return null;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
export const resolveGoogleAuthSources = () => {
|
|
94
|
-
const auth =
|
|
93
|
+
export const resolveGoogleAuthSources = async () => {
|
|
94
|
+
const auth = await readOpenCodeCredentials();
|
|
95
95
|
const sources = [];
|
|
96
96
|
|
|
97
97
|
const geminiAuth = resolveGeminiCliAuth(auth);
|
|
@@ -17,10 +17,10 @@ export const providerId = 'google';
|
|
|
17
17
|
export const providerName = 'Google';
|
|
18
18
|
export const aliases = ['google', 'google.oauth'];
|
|
19
19
|
|
|
20
|
-
export const isConfigured = () => resolveGoogleAuthSources().length > 0;
|
|
20
|
+
export const isConfigured = async () => (await resolveGoogleAuthSources()).length > 0;
|
|
21
21
|
|
|
22
22
|
export const fetchGoogleQuota = async () => {
|
|
23
|
-
const authSources = resolveGoogleAuthSources();
|
|
23
|
+
const authSources = await resolveGoogleAuthSources();
|
|
24
24
|
if (!authSources.length) {
|
|
25
25
|
return buildResult({
|
|
26
26
|
providerId,
|
|
@@ -130,16 +130,12 @@ const registry = {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
export const listConfiguredQuotaProviders = () => {
|
|
133
|
+
export const listConfiguredQuotaProviders = async () => {
|
|
134
134
|
const configured = [];
|
|
135
135
|
|
|
136
136
|
for (const [id, provider] of Object.entries(registry)) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
configured.push(id);
|
|
140
|
-
}
|
|
141
|
-
} catch {
|
|
142
|
-
// Ignore provider-specific config errors in list API.
|
|
137
|
+
if (await provider.isConfigured()) {
|
|
138
|
+
configured.push(id);
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
141
|
|
|
@@ -11,7 +11,7 @@ describe('quota provider registry', () => {
|
|
|
11
11
|
expect(typeof google.resolveGoogleAuthSources).toBe('function');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it('
|
|
15
|
-
expect(
|
|
14
|
+
it('reports unavailable credentials instead of an empty configured-provider list', async () => {
|
|
15
|
+
await expect(listConfiguredQuotaProviders()).rejects.toThrow('Unable to read OpenCode credentials');
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'kimi-for-coding';
|
|
|
14
14
|
export const providerName = 'Kimi for Coding';
|
|
15
15
|
const aliases = ['kimi-for-coding', 'kimi'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.key || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const apiKey = entry?.key ?? entry?.token;
|
|
27
27
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -138,14 +138,14 @@ const calculateUsage = (model, isTokenPlan) => {
|
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
export const createMiniMaxCodingPlanProvider = ({ providerId, providerName, aliases, tokenPlanUrl, codingPlanUrl }) => {
|
|
141
|
-
const isConfigured = () => {
|
|
142
|
-
const auth =
|
|
141
|
+
const isConfigured = async () => {
|
|
142
|
+
const auth = await readOpenCodeCredentials();
|
|
143
143
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
144
144
|
return Boolean(entry?.key || entry?.token);
|
|
145
145
|
};
|
|
146
146
|
|
|
147
147
|
const fetchQuota = async () => {
|
|
148
|
-
const auth =
|
|
148
|
+
const auth = await readOpenCodeCredentials();
|
|
149
149
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
150
150
|
const apiKey = entry?.key ?? entry?.token;
|
|
151
151
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'nano-gpt';
|
|
|
14
14
|
export const providerName = 'NanoGPT';
|
|
15
15
|
const aliases = ['nano-gpt', 'nanogpt', 'nano_gpt'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.key || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const apiKey = entry?.key ?? entry?.token;
|
|
27
27
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -12,14 +12,14 @@ const providerId = 'openai';
|
|
|
12
12
|
const providerName = 'OpenAI';
|
|
13
13
|
const aliases = ['openai', 'codex', 'chatgpt'];
|
|
14
14
|
|
|
15
|
-
const isConfigured = () => {
|
|
16
|
-
const auth =
|
|
15
|
+
const isConfigured = async () => {
|
|
16
|
+
const auth = await readOpenCodeCredentials();
|
|
17
17
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
18
|
return Boolean(entry?.access || entry?.token);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const fetchQuota = async () => {
|
|
22
|
-
const auth =
|
|
22
|
+
const auth = await readOpenCodeCredentials();
|
|
23
23
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
24
|
const accessToken = entry?.access ?? entry?.token;
|
|
25
25
|
|