cartethyia 2.8.0 → 2.8.1
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/.env.example +3 -3
- package/dist/dashboard/assets/{BannedPage-Cc0vKpMG.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-Bqj2aa1T.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-CXsaOAO2.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BeLpJ4ma.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-DnV0uZ3m.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-CBxAjo63.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-B5yEE0k6.js → HealthEventsModal-Clq8E9cW.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BWFLECmz.js → InjectSystemPromptPanel-6Teoq9Se.js} +1 -1
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-CgoPc5B9.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-D9bFQern.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-B_p4tL8k.js → OverviewPage-0yfhSI1C.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-D_0PiZtg.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-CLI2bJTK.js → ProvidersPage-C3fYuu6i.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-DA-a1iSN.js → QuotaPage-D1RV_2BX.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-CctTga2C.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-febLU4r1.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-M6fvR5Wz.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-nyeuRqz9.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BFOlvrJC.js → api-keys-2hc12Uz2.js} +1 -1
- package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CqTLw5e7.js → assert-BMcj1xZE.js} +1 -1
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-C83EqLB0.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-D1xn7pIe.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-DU3Lq5Lm.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-Dw1U_drK.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CEL5Sg_J.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BjFR5UHy.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-HvBW0AjI.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CECVELyy.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DYYofQ7W.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BSu3gFlt.js → entry-DScKYvZj.js} +3 -3
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DLW4i8zK.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-1-fCqe64.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DGvU2oLK.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-iNnO1X2K.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-0q8UGypb.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-C0w2bQjJ.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-M5aJ_xCa.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-BKeQGT3K.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-CivAk3da.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-CpzvI5ep.js → pagination-iUoI8GeE.js} +1 -1
- package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
- package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
- package/dist/dashboard/assets/providers/sensenova.png +0 -0
- package/dist/dashboard/assets/{routing-BFQX2VBc.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-CXbck0jG.js → select-oO1FxMVb.js} +1 -1
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-C_XvMHh9.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DggRiCxE.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-ClFzfbYp.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-CTIFxKqS.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BClG3yWj.js → useMutation-DJ9GYEgf.js} +1 -1
- package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -51
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +1 -1
- package/src/boot/dependencies.ts +4 -6
- package/src/config/providers.ts +6 -5
- package/src/config/spec.ts +8 -8
- package/src/console/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -2
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/observability/live.ts +2 -2
- package/src/console/providers/catalog/contracts.ts +9 -3
- package/src/console/providers/catalog/model-operations.ts +2 -3
- package/src/console/providers/catalog/provider-operations.ts +1 -1
- package/src/console/providers/catalog/routes.ts +2 -2
- package/src/console/providers/catalog/store.ts +1914 -1907
- package/src/console/providers/detail/routes.ts +1 -0
- package/src/console/providers/detail/store.ts +4 -0
- package/src/console/providers/oauth/callback-listener.ts +30 -11
- package/src/console/providers/oauth/device-egress.ts +123 -0
- package/src/console/providers/oauth/routes.ts +123 -16
- package/src/console/quota/account-quota-tenant.ts +5 -5
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +1 -1
- package/src/console/routing/pools/probe-verdict.ts +2 -2
- package/src/console/routing/pools/relay-deploy.ts +1 -1
- package/src/console/routing/pools/store.ts +4 -4
- package/src/network/client.ts +67 -0
- package/src/network/egress/bidi-session.ts +322 -0
- package/src/network/egress/dial.ts +201 -0
- package/src/network/egress/factory.ts +191 -0
- package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
- package/src/network/egress/tls-profile.ts +123 -0
- package/src/network/egress/ws.ts +175 -0
- package/src/network/{types.ts → pool/agent-config.ts} +1 -1
- package/src/network/pool/agent.ts +2 -2
- package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +7 -7
- package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
- package/src/network/pool/resolver.ts +10 -849
- package/src/network/pool/selector.ts +2 -2
- package/src/network/ssrf.ts +1 -1
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/observability/telemetry-buffer.ts +1 -1
- package/src/persistence/schema.ts +9 -0
- package/src/persistence/ttl-cache.ts +1 -1
- package/src/protocol/codecs.ts +58 -1
- package/src/protocol/request/messages.ts +31 -18
- package/src/protocol/response/messages.ts +34 -7
- package/src/protocol/translation/quirks.ts +15 -0
- package/src/protocol/upstream/send-claude-messages.ts +2 -2
- package/src/providers/adapters/compatible-adapter.ts +5 -0
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +7 -3
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -739
- package/src/providers/default-registry.ts +76 -0
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +6 -2
- package/src/providers/discovery/probing-service.ts +7 -0
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +11 -0
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +10 -10
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +5 -5
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +4 -4
- package/src/providers/integrations/buddy/{buddy-identity.ts → headers.ts} +138 -140
- package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
- package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
- package/src/providers/integrations/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +3 -4
- package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
- package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
- package/src/providers/integrations/clixad/provider.ts +145 -0
- package/src/providers/integrations/codex/codex-oauth.ts +8 -5
- package/src/providers/integrations/cursor/cursor-auth.ts +1 -1
- package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
- package/src/providers/integrations/cursor/cursor-shared.ts +1 -1
- package/src/providers/integrations/cursor/cursor.ts +485 -70
- package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
- package/src/providers/integrations/deepai/deepai-common.ts +145 -0
- package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
- package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
- package/src/providers/integrations/deepai/provider.ts +42 -0
- package/src/providers/integrations/devin/devin-auth.ts +7 -0
- package/src/providers/integrations/factory/factory-oauth.ts +16 -7
- package/src/providers/integrations/freebuff/freebuff.ts +2 -2
- package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
- package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
- package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
- package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
- package/src/providers/integrations/freepi/freepi-session.ts +158 -0
- package/src/providers/integrations/freepi/freepi.ts +80 -54
- package/src/providers/integrations/github/github-discovery.ts +30 -13
- package/src/providers/integrations/github/github-oauth.ts +217 -44
- package/src/providers/integrations/github/github-quota.ts +22 -5
- package/src/providers/integrations/github/github.ts +40 -32
- package/src/providers/integrations/grok/grok-oauth.ts +4 -4
- package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
- package/src/providers/integrations/hermes/hermes.ts +34 -5
- package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
- package/src/providers/integrations/kilo/kilo.ts +1 -1
- package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
- package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-protocol.ts +0 -3
- package/src/providers/integrations/perch/perch.ts +3 -3
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
- package/src/providers/integrations/qoder/qoder-adapter.ts +19 -113
- package/src/providers/integrations/qoder/qoder-auth.ts +8 -1
- package/src/providers/integrations/qoder/qoder-catalog.ts +1 -1
- package/src/providers/integrations/qoder/qoder-chat.ts +25 -8
- package/src/providers/integrations/qoder/qoder-checkin.ts +1 -1
- package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
- package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
- package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
- package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
- package/src/providers/integrations/qoder/qoder-types.ts +9 -0
- package/src/providers/integrations/qoder/qoder.ts +3 -25
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-quota.ts +15 -1
- package/src/providers/integrations/vercel/provider.ts +117 -0
- package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
- package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
- package/src/providers/integrations/zro/zro.ts +2 -1
- package/src/providers/operations/account-refresh-status.ts +14 -0
- package/src/providers/operations/client-versions.ts +95 -30
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/provider-caches.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +22 -25
- package/src/providers/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +2 -2
- package/src/providers/provider-metadata.ts +30 -0
- package/src/providers/quota/quota-contracts.ts +2 -0
- package/src/providers/quota/refresh.ts +25 -3
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +1 -1
- package/src/transport/dispatch/attempt-loop.ts +3 -3
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +2 -2
- package/src/transport/dispatch/streaming/stream.ts +1 -1
- package/src/transport/dispatch/upstream.ts +1 -1
- package/src/transport/shutdown-notice.ts +1 -1
- package/src/workers/buddy-growth-tasks.ts +8 -8
- package/src/workers/daily-checkin.ts +4 -4
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/zcode-gift-claim.ts +1 -1
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +0 -2
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/src/console/providers/catalog/discover-constants.ts +0 -6
- package/src/network/pool-health.ts +0 -198
- package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
- package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
- package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -188
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -179
- package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
- package/src/providers/integrations/qoder/chat-proto.ts +0 -328
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -870
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -481
- /package/dist/dashboard/assets/{app-DIXLjojg.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-DIXLjojg.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
- /package/src/network/pool/{byte-accounting.ts → accounting.ts} +0 -0
- /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
- /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
- /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
- /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
- /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
- /package/src/network/{rate-limit-verdict.ts → verdict.ts} +0 -0
|
@@ -25,10 +25,14 @@ import type {
|
|
|
25
25
|
OAuthDeviceStartResult,
|
|
26
26
|
OAuthExchangeResult,
|
|
27
27
|
} from "../../authentication/oauth-flow-store";
|
|
28
|
-
import type {
|
|
28
|
+
import type {
|
|
29
|
+
OAuthRefreshContext,
|
|
30
|
+
OAuthTokenRefresher,
|
|
31
|
+
OAuthTokenRefreshResult,
|
|
32
|
+
} from "../../authentication/oauth-refresh-service";
|
|
29
33
|
import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
|
|
30
34
|
import type { FetchLike } from "../../authentication/oauth-client";
|
|
31
|
-
import { resolveGithubEnterpriseDomain } from "../../../config/providers";
|
|
35
|
+
import { resolveGithubCopilotMode, resolveGithubEnterpriseDomain } from "../../../config/providers";
|
|
32
36
|
import { randomUUID, createHash } from "node:crypto";
|
|
33
37
|
import { getOrCreateInstallId } from "../shared/install-id";
|
|
34
38
|
|
|
@@ -55,6 +59,26 @@ export const GITHUB_OPENCODE_API_HOST = "api.githubcopilot.com" as const;
|
|
|
55
59
|
*/
|
|
56
60
|
const GITHUB_OPENCODE_TOKEN_LIFETIME_MS = 365 * 24 * 60 * 60 * 1000;
|
|
57
61
|
|
|
62
|
+
export type GithubCopilotCredentialProfile = "opencode-copilot" | "vscode-client-real";
|
|
63
|
+
|
|
64
|
+
export function githubCopilotProfileForClientId(clientId: string): GithubCopilotCredentialProfile {
|
|
65
|
+
return clientId === GITHUB_OPENCODE_CLIENT_ID ? "opencode-copilot" : "vscode-client-real";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function githubCopilotClientIdForProfile(profile: GithubCopilotCredentialProfile): string {
|
|
69
|
+
return profile === "opencode-copilot" ? GITHUB_OPENCODE_CLIENT_ID : GITHUB_CLIENT_ID;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function githubCopilotApiHostForProfile(
|
|
73
|
+
profile: GithubCopilotCredentialProfile,
|
|
74
|
+
domain: string,
|
|
75
|
+
): string {
|
|
76
|
+
if (profile === "opencode-copilot") {
|
|
77
|
+
return domain === "github.com" ? GITHUB_OPENCODE_API_HOST : `copilot-api.${domain}`;
|
|
78
|
+
}
|
|
79
|
+
return GITHUB_DEFAULT_API_HOST;
|
|
80
|
+
}
|
|
81
|
+
|
|
58
82
|
/**
|
|
59
83
|
* Headers every Copilot inference and policy request must carry.
|
|
60
84
|
*
|
|
@@ -129,15 +153,18 @@ export function copilotGitHubDomain(): string {
|
|
|
129
153
|
return resolveGithubEnterpriseDomain() || "github.com";
|
|
130
154
|
}
|
|
131
155
|
|
|
132
|
-
/** Persisted credential shape:
|
|
133
|
-
export function encodeGithubCredential(
|
|
156
|
+
/** Persisted credential shape: token, upstream host, and OAuth app origin. */
|
|
157
|
+
export function encodeGithubCredential(
|
|
158
|
+
access: string,
|
|
159
|
+
apiHost: string,
|
|
160
|
+
githubToken: string | undefined,
|
|
161
|
+
profile: GithubCopilotCredentialProfile,
|
|
162
|
+
): string {
|
|
134
163
|
return JSON.stringify({
|
|
135
164
|
access,
|
|
136
165
|
apiHost,
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
// usage endpoint takes the GitHub token, not the minted one. It is optional
|
|
140
|
-
// so a credential written before this field existed still parses.
|
|
166
|
+
profile,
|
|
167
|
+
// The GitHub token is carried beside the minted token for quota reads.
|
|
141
168
|
...(githubToken === undefined || githubToken.length === 0 ? {} : { github: githubToken }),
|
|
142
169
|
});
|
|
143
170
|
}
|
|
@@ -145,10 +172,93 @@ export function encodeGithubCredential(access: string, apiHost: string, githubTo
|
|
|
145
172
|
export interface GithubCredential {
|
|
146
173
|
readonly access: string;
|
|
147
174
|
readonly apiHost: string;
|
|
175
|
+
readonly profile: GithubCopilotCredentialProfile;
|
|
148
176
|
/** The long-lived GitHub token, when the envelope carries it. */
|
|
149
177
|
readonly github?: string;
|
|
150
178
|
}
|
|
151
179
|
|
|
180
|
+
export interface ImportedGithubCredential {
|
|
181
|
+
readonly credential: string;
|
|
182
|
+
readonly profile: GithubCopilotCredentialProfile;
|
|
183
|
+
readonly refreshToken?: string;
|
|
184
|
+
/** True when a raw GitHub token should be minted on first use in VS Code mode. */
|
|
185
|
+
readonly expiresAt: Date;
|
|
186
|
+
readonly requiresRefresh: boolean;
|
|
187
|
+
/** Non-fatal facts an importer must surface: the credential is usable, but its origin is not fully verified. */
|
|
188
|
+
readonly warnings: readonly string[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Binds an exported/imported GitHub credential to the currently selected wire
|
|
193
|
+
* profile. This is a local normalization only; it never contacts GitHub.
|
|
194
|
+
*/
|
|
195
|
+
export function bindImportedGithubCredential(
|
|
196
|
+
value: string,
|
|
197
|
+
refreshValue: string | null | undefined,
|
|
198
|
+
profile: GithubCopilotCredentialProfile,
|
|
199
|
+
domain: string,
|
|
200
|
+
expiresAt?: Date,
|
|
201
|
+
): ImportedGithubCredential {
|
|
202
|
+
const parsed = (() => {
|
|
203
|
+
try {
|
|
204
|
+
return parseGithubCredential(value);
|
|
205
|
+
} catch {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
})();
|
|
209
|
+
const access = parsed?.access ?? nonEmptyTrimmedString(value);
|
|
210
|
+
if (access === undefined) throw new Error("GitHub Copilot import credential is empty");
|
|
211
|
+
const refresh = nonEmptyTrimmedString(refreshValue);
|
|
212
|
+
const rawToken = parsed?.github ?? refresh;
|
|
213
|
+
const isMinted =
|
|
214
|
+
access.includes("proxy-ep=") ||
|
|
215
|
+
(parsed !== undefined && rawToken !== undefined && access !== rawToken);
|
|
216
|
+
const githubToken = rawToken ?? (isMinted ? undefined : access);
|
|
217
|
+
if (profile === "opencode-copilot") {
|
|
218
|
+
if (isMinted && githubToken === undefined) {
|
|
219
|
+
throw new Error("a VS Code Copilot export needs its raw GitHub refresh token to import as opencode-copilot");
|
|
220
|
+
}
|
|
221
|
+
const token = githubToken ?? access;
|
|
222
|
+
return {
|
|
223
|
+
credential: encodeGithubCredential(
|
|
224
|
+
token,
|
|
225
|
+
githubCopilotApiHostForProfile(profile, domain),
|
|
226
|
+
token,
|
|
227
|
+
profile,
|
|
228
|
+
),
|
|
229
|
+
profile,
|
|
230
|
+
refreshToken: token,
|
|
231
|
+
expiresAt: new Date(Date.now() + GITHUB_OPENCODE_TOKEN_LIFETIME_MS),
|
|
232
|
+
requiresRefresh: false,
|
|
233
|
+
warnings: [],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (isMinted) {
|
|
237
|
+
const apiHost = parsed?.apiHost ?? apiHostFromGithubToken(access);
|
|
238
|
+
return {
|
|
239
|
+
credential: encodeGithubCredential(access, apiHost, githubToken, profile),
|
|
240
|
+
profile,
|
|
241
|
+
...(githubToken === undefined ? {} : { refreshToken: githubToken }),
|
|
242
|
+
expiresAt: expiresAt ?? new Date(Date.now() + COPILOT_TOKEN_FALLBACK_MS),
|
|
243
|
+
requiresRefresh: false,
|
|
244
|
+
warnings: githubToken === undefined
|
|
245
|
+
? ["the minted Copilot token has no raw GitHub refresh token; it will expire without a way to mint a replacement"]
|
|
246
|
+
: [],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const token = githubToken ?? access;
|
|
250
|
+
return {
|
|
251
|
+
credential: encodeGithubCredential(token, GITHUB_DEFAULT_API_HOST, token, profile),
|
|
252
|
+
profile,
|
|
253
|
+
refreshToken: token,
|
|
254
|
+
expiresAt: new Date(0),
|
|
255
|
+
requiresRefresh: true,
|
|
256
|
+
warnings: [
|
|
257
|
+
"the imported token has no Copilot mint metadata; its GitHub OAuth app origin could not be verified",
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
152
262
|
/**
|
|
153
263
|
* Parses a stored Copilot credential.
|
|
154
264
|
*
|
|
@@ -172,9 +282,17 @@ export function parseGithubCredential(value: string): GithubCredential {
|
|
|
172
282
|
}
|
|
173
283
|
const apiHost = nonEmptyTrimmedString(root.apiHost) ?? GITHUB_DEFAULT_API_HOST;
|
|
174
284
|
const github = nonEmptyTrimmedString(root.github);
|
|
285
|
+
const rawProfile = nonEmptyTrimmedString(root.profile);
|
|
286
|
+
const profile: GithubCopilotCredentialProfile =
|
|
287
|
+
rawProfile === "opencode-copilot" || rawProfile === "vscode-client-real"
|
|
288
|
+
? rawProfile
|
|
289
|
+
: github !== undefined && access === github && apiHost === GITHUB_OPENCODE_API_HOST
|
|
290
|
+
? "opencode-copilot"
|
|
291
|
+
: "vscode-client-real";
|
|
175
292
|
return {
|
|
176
293
|
access,
|
|
177
294
|
apiHost,
|
|
295
|
+
profile,
|
|
178
296
|
...(github === undefined ? {} : { github }),
|
|
179
297
|
};
|
|
180
298
|
}
|
|
@@ -236,6 +354,7 @@ export async function mintGithubToken(
|
|
|
236
354
|
async function fetchGitHubLogin(
|
|
237
355
|
githubAccessToken: string,
|
|
238
356
|
domain: string,
|
|
357
|
+
profile: GithubCopilotCredentialProfile,
|
|
239
358
|
fetchFn: FetchLike,
|
|
240
359
|
signal: AbortSignal,
|
|
241
360
|
): Promise<string | undefined> {
|
|
@@ -244,10 +363,8 @@ async function fetchGitHubLogin(
|
|
|
244
363
|
headers: {
|
|
245
364
|
accept: "application/json",
|
|
246
365
|
authorization: `Bearer ${githubAccessToken}`,
|
|
247
|
-
// The
|
|
248
|
-
|
|
249
|
-
// the dialog's label — so the second one overwrites the first.
|
|
250
|
-
"user-agent": GITHUB_REQUEST_HEADERS["user-agent"]!,
|
|
366
|
+
// The GitHub account API only needs the selected client's user-agent.
|
|
367
|
+
"user-agent": githubCopilotUserAgent(profile),
|
|
251
368
|
},
|
|
252
369
|
signal,
|
|
253
370
|
});
|
|
@@ -277,6 +394,7 @@ interface CopilotUserProfile {
|
|
|
277
394
|
async function fetchGitHubCopilotEntitlement(
|
|
278
395
|
githubAccessToken: string,
|
|
279
396
|
domain: string,
|
|
397
|
+
profile: GithubCopilotCredentialProfile,
|
|
280
398
|
fetchFn: FetchLike,
|
|
281
399
|
signal: AbortSignal,
|
|
282
400
|
): Promise<{ denied: string } | { denied?: undefined }> {
|
|
@@ -289,18 +407,18 @@ async function fetchGitHubCopilotEntitlement(
|
|
|
289
407
|
headers: {
|
|
290
408
|
accept: "application/json",
|
|
291
409
|
authorization: `Bearer ${githubAccessToken}`,
|
|
292
|
-
"user-agent":
|
|
410
|
+
"user-agent": githubCopilotUserAgent(profile),
|
|
293
411
|
"x-github-api-version": "2025-04-01",
|
|
294
412
|
},
|
|
295
413
|
signal,
|
|
296
414
|
});
|
|
297
415
|
if (!response.ok) return { denied: undefined };
|
|
298
|
-
const
|
|
299
|
-
if (
|
|
300
|
-
if (
|
|
416
|
+
const userProfile = record(await response.json()) as CopilotUserProfile | undefined;
|
|
417
|
+
if (userProfile?.chat_enabled === false) {
|
|
418
|
+
if (userProfile.can_signup_for_limited) {
|
|
301
419
|
return {
|
|
302
420
|
denied:
|
|
303
|
-
"This GitHub account is not
|
|
421
|
+
"This GitHub account is not assigned to use GitHub Copilot. Activate Copilot Free at https://github.com/features/copilot and connect again.",
|
|
304
422
|
};
|
|
305
423
|
}
|
|
306
424
|
return {
|
|
@@ -316,7 +434,43 @@ async function fetchGitHubCopilotEntitlement(
|
|
|
316
434
|
}
|
|
317
435
|
}
|
|
318
436
|
|
|
319
|
-
|
|
437
|
+
function parseGithubDeviceProviderState(value: string | undefined): { domain?: string; clientId?: string; profile?: GithubCopilotCredentialProfile } | undefined {
|
|
438
|
+
if (value === undefined || value.length === 0) return undefined;
|
|
439
|
+
try {
|
|
440
|
+
const parsed: unknown = JSON.parse(value);
|
|
441
|
+
const state = record(parsed);
|
|
442
|
+
const domain = nonEmptyTrimmedString(state?.domain);
|
|
443
|
+
const clientId = nonEmptyTrimmedString(state?.clientId);
|
|
444
|
+
const rawProfile = nonEmptyTrimmedString(state?.profile);
|
|
445
|
+
const profile = rawProfile === "opencode-copilot" || rawProfile === "vscode-client-real"
|
|
446
|
+
? rawProfile
|
|
447
|
+
: undefined;
|
|
448
|
+
if (domain !== undefined) {
|
|
449
|
+
return {
|
|
450
|
+
domain,
|
|
451
|
+
...(clientId === undefined ? {} : { clientId }),
|
|
452
|
+
...(profile === undefined ? {} : { profile }),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
} catch {
|
|
456
|
+
// Older in-flight logins persisted the bare GitHub domain as providerState.
|
|
457
|
+
}
|
|
458
|
+
return { domain: value };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function githubCopilotUserAgent(profile: GithubCopilotCredentialProfile): string {
|
|
462
|
+
return profile === "opencode-copilot" ? "opencode/0.1.0" : GITHUB_REQUEST_HEADERS["user-agent"]!;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function readGithubCopilotProfile(
|
|
466
|
+
authState: Readonly<Record<string, unknown>> | undefined,
|
|
467
|
+
): GithubCopilotCredentialProfile | undefined {
|
|
468
|
+
const value = authState?.["github_copilot_profile"];
|
|
469
|
+
return value === "opencode-copilot" || value === "vscode-client-real" ? value : undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export class GithubOAuthClient extends OAuthDeviceFlow implements OAuthTokenRefresher {
|
|
473
|
+
readonly requiresAccessToken = true;
|
|
320
474
|
override readonly supportsDeviceCode = true;
|
|
321
475
|
override readonly supportsBrowserCode = false;
|
|
322
476
|
|
|
@@ -330,20 +484,20 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
|
|
|
330
484
|
|
|
331
485
|
constructor(
|
|
332
486
|
fetchFn: FetchLike = globalThis.fetch,
|
|
333
|
-
clientId: string =
|
|
487
|
+
clientId: string = githubCopilotClientIdForProfile(resolveGithubCopilotMode()),
|
|
334
488
|
) {
|
|
335
489
|
super(fetchFn);
|
|
336
490
|
this.clientId = clientId;
|
|
337
491
|
}
|
|
338
492
|
|
|
339
|
-
override async startDeviceAuth(
|
|
493
|
+
override async startDeviceAuth(context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
|
|
340
494
|
const domain = copilotGitHubDomain();
|
|
341
|
-
const response = await this.
|
|
495
|
+
const response = await this.fetchFor(context)(`https://${domain}/login/device/code`, {
|
|
342
496
|
method: "POST",
|
|
343
497
|
headers: {
|
|
344
498
|
accept: "application/json",
|
|
345
499
|
"content-type": "application/x-www-form-urlencoded",
|
|
346
|
-
"user-agent":
|
|
500
|
+
"user-agent": githubCopilotUserAgent(githubCopilotProfileForClientId(this.clientId)),
|
|
347
501
|
},
|
|
348
502
|
body: new URLSearchParams({ client_id: this.clientId, scope: "read:user" }),
|
|
349
503
|
});
|
|
@@ -380,9 +534,13 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
|
|
|
380
534
|
deviceAuthId: deviceCode,
|
|
381
535
|
intervalSeconds: interval,
|
|
382
536
|
expiresInSeconds: expiresIn,
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
providerState:
|
|
537
|
+
// Pin the OAuth app and host across the complete device flow, even if
|
|
538
|
+
// process configuration changes before the user finishes authorization.
|
|
539
|
+
providerState: JSON.stringify({
|
|
540
|
+
domain,
|
|
541
|
+
clientId: this.clientId,
|
|
542
|
+
profile: githubCopilotProfileForClientId(this.clientId),
|
|
543
|
+
}),
|
|
386
544
|
};
|
|
387
545
|
}
|
|
388
546
|
|
|
@@ -390,16 +548,19 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
|
|
|
390
548
|
deviceAuthId: string,
|
|
391
549
|
context?: OAuthDeviceFlowContext,
|
|
392
550
|
): Promise<OAuthDevicePollResult> {
|
|
393
|
-
const
|
|
394
|
-
const
|
|
551
|
+
const providerState = parseGithubDeviceProviderState(context?.providerState);
|
|
552
|
+
const domain = providerState?.domain ?? copilotGitHubDomain();
|
|
553
|
+
const clientId = providerState?.clientId ?? this.clientId;
|
|
554
|
+
const profile = providerState?.profile ?? githubCopilotProfileForClientId(clientId);
|
|
555
|
+
const response = await this.fetchFor(context)(`https://${domain}/login/oauth/access_token`, {
|
|
395
556
|
method: "POST",
|
|
396
557
|
headers: {
|
|
397
558
|
accept: "application/json",
|
|
398
559
|
"content-type": "application/x-www-form-urlencoded",
|
|
399
|
-
"user-agent":
|
|
560
|
+
"user-agent": githubCopilotUserAgent(profile),
|
|
400
561
|
},
|
|
401
562
|
body: new URLSearchParams({
|
|
402
|
-
client_id:
|
|
563
|
+
client_id: clientId,
|
|
403
564
|
device_code: deviceAuthId,
|
|
404
565
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
405
566
|
}),
|
|
@@ -451,6 +612,7 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
|
|
|
451
612
|
const entitlement = await fetchGitHubCopilotEntitlement(
|
|
452
613
|
githubToken,
|
|
453
614
|
domain,
|
|
615
|
+
profile,
|
|
454
616
|
this.fetchFn,
|
|
455
617
|
AbortSignal.timeout(10_000),
|
|
456
618
|
);
|
|
@@ -462,7 +624,7 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
|
|
|
462
624
|
// no minted Copilot token. The VS Code mint endpoint
|
|
463
625
|
// (`/copilot_internal/v2/token`) rejects non-VS-Code client ids with 403
|
|
464
626
|
// (GitHub's anti-scraping gate), so an OpenCode login must not mint.
|
|
465
|
-
if (
|
|
627
|
+
if (profile === "vscode-client-real") {
|
|
466
628
|
minted = await mintGithubToken(githubToken, domain, this.fetchFn);
|
|
467
629
|
}
|
|
468
630
|
} catch (error) {
|
|
@@ -470,40 +632,50 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
|
|
|
470
632
|
// not an exception that escapes the dashboard's poll request.
|
|
471
633
|
return { status: "failed", reason: error instanceof Error ? error.message : "GitHub Copilot token mint failed" };
|
|
472
634
|
}
|
|
473
|
-
const label = await fetchGitHubLogin(githubToken, domain, this.fetchFn, AbortSignal.timeout(10_000));
|
|
635
|
+
const label = await fetchGitHubLogin(githubToken, domain, profile, this.fetchFn, AbortSignal.timeout(10_000));
|
|
474
636
|
const access = minted === undefined ? githubToken : minted.access;
|
|
475
|
-
const apiHost = minted === undefined ?
|
|
637
|
+
const apiHost = minted === undefined ? githubCopilotApiHostForProfile(profile, domain) : minted.apiHost;
|
|
476
638
|
const expiresAt =
|
|
477
639
|
minted === undefined
|
|
478
640
|
? new Date(Date.now() + GITHUB_OPENCODE_TOKEN_LIFETIME_MS)
|
|
479
641
|
: minted.expiresAt;
|
|
480
642
|
const result: OAuthExchangeResult = {
|
|
481
|
-
access: encodeGithubCredential(access, apiHost, githubToken),
|
|
482
|
-
// The GitHub token is the refresh value
|
|
483
|
-
// token (VS Code app) or is the raw credential itself (OpenCode app),
|
|
484
|
-
// depending on which app the flow presented.
|
|
643
|
+
access: encodeGithubCredential(access, apiHost, githubToken, profile),
|
|
644
|
+
// The raw GitHub token is the refresh value for the selected profile.
|
|
485
645
|
refresh: githubToken,
|
|
486
646
|
expiresAt,
|
|
647
|
+
auth_state: { github_copilot_profile: profile },
|
|
487
648
|
...(label === undefined ? {} : { accountLabel: label }),
|
|
488
649
|
};
|
|
489
650
|
return { status: "complete", result };
|
|
490
651
|
}
|
|
491
652
|
|
|
492
653
|
|
|
493
|
-
override async refresh(
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
654
|
+
override async refresh(
|
|
655
|
+
refreshToken: string,
|
|
656
|
+
signal?: AbortSignal,
|
|
657
|
+
context?: OAuthRefreshContext,
|
|
658
|
+
): Promise<OAuthTokenRefreshResult> {
|
|
659
|
+
const profile = githubCopilotProfileForClientId(this.clientId);
|
|
660
|
+
const storedProfile =
|
|
661
|
+
readGithubCopilotProfile(context?.auth_state) ??
|
|
662
|
+
(context?.access_token === undefined
|
|
663
|
+
? undefined
|
|
664
|
+
: parseGithubCredential(context.access_token).profile);
|
|
665
|
+
if (storedProfile !== undefined && storedProfile !== profile) {
|
|
666
|
+
throw new Error("github_copilot_profile_mismatch: reconnect this GitHub account under the selected CARTETHYIA_GITHUB_COPILOT_MODE");
|
|
667
|
+
}
|
|
668
|
+
if (profile === "opencode-copilot") {
|
|
499
669
|
return {
|
|
500
670
|
access: encodeGithubCredential(
|
|
501
671
|
refreshToken,
|
|
502
|
-
|
|
672
|
+
githubCopilotApiHostForProfile(profile, copilotGitHubDomain()),
|
|
503
673
|
refreshToken,
|
|
674
|
+
profile,
|
|
504
675
|
),
|
|
505
676
|
refresh: refreshToken,
|
|
506
677
|
expiresAt: new Date(Date.now() + GITHUB_OPENCODE_TOKEN_LIFETIME_MS),
|
|
678
|
+
auth_state: { github_copilot_profile: profile },
|
|
507
679
|
};
|
|
508
680
|
}
|
|
509
681
|
const minted = await mintGithubToken(
|
|
@@ -513,10 +685,11 @@ export class GithubOAuthClient extends OAuthDeviceFlow {
|
|
|
513
685
|
signal,
|
|
514
686
|
);
|
|
515
687
|
return {
|
|
516
|
-
access: encodeGithubCredential(minted.access, minted.apiHost, refreshToken),
|
|
688
|
+
access: encodeGithubCredential(minted.access, minted.apiHost, refreshToken, profile),
|
|
517
689
|
// The GitHub token is unchanged by a re-mint, so it stays the refresh value.
|
|
518
690
|
refresh: refreshToken,
|
|
519
691
|
expiresAt: minted.expiresAt,
|
|
692
|
+
auth_state: { github_copilot_profile: profile },
|
|
520
693
|
};
|
|
521
694
|
}
|
|
522
695
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../quota/quota-contracts";
|
|
14
14
|
import { getJson, isoDate, number, percentWindow, quotaRecord, text } from "../../quota/quota-contracts";
|
|
15
|
-
import { resolveGithubEnterpriseDomain } from "../../../config/providers";
|
|
15
|
+
import { resolveGithubCopilotMode, resolveGithubEnterpriseDomain } from "../../../config/providers";
|
|
16
16
|
import { GITHUB_REQUEST_HEADERS, parseGithubCredential } from "./github-oauth";
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -113,14 +113,31 @@ export async function fetchGithubQuota(
|
|
|
113
113
|
// predates that field, the minted token is sent instead and the upstream
|
|
114
114
|
// answers with an auth error that surfaces honestly rather than as a
|
|
115
115
|
// fabricated window.
|
|
116
|
+
const mode = resolveGithubCopilotMode();
|
|
117
|
+
if (parsed.profile !== mode) {
|
|
118
|
+
return {
|
|
119
|
+
source: "github",
|
|
120
|
+
plan: null,
|
|
121
|
+
windows: [],
|
|
122
|
+
error: `GitHub Copilot account is bound to ${parsed.profile}; reconnect it after selecting ${mode}.`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
116
125
|
const token = parsed.github ?? parsed.access;
|
|
117
126
|
const domain = resolveGithubEnterpriseDomain() || "github.com";
|
|
127
|
+
const headers = mode === "opencode-copilot"
|
|
128
|
+
? {
|
|
129
|
+
authorization: `Bearer ${token}`,
|
|
130
|
+
"user-agent": "opencode/0.1.0",
|
|
131
|
+
"x-github-api-version": "2025-04-01",
|
|
132
|
+
}
|
|
133
|
+
: {
|
|
134
|
+
authorization: `Bearer ${token}`,
|
|
135
|
+
...GITHUB_REQUEST_HEADERS,
|
|
136
|
+
"x-github-api-version": "2025-04-01",
|
|
137
|
+
};
|
|
118
138
|
const body = await getJson(
|
|
119
139
|
`https://api.${domain}/copilot_internal/user`,
|
|
120
|
-
|
|
121
|
-
authorization: `Bearer ${token}`,
|
|
122
|
-
...GITHUB_REQUEST_HEADERS,
|
|
123
|
-
},
|
|
140
|
+
headers,
|
|
124
141
|
fetcher,
|
|
125
142
|
);
|
|
126
143
|
return parseGithubQuota(body);
|
|
@@ -387,6 +387,18 @@ export const GITHUB_MODELS: readonly ModelDefinition[] = [
|
|
|
387
387
|
* pipeline, so only the two Copilot-specific facts (the host and the client
|
|
388
388
|
* identity) are supplied here.
|
|
389
389
|
*/
|
|
390
|
+
function assertGithubProfileMatchesMode(credential: GithubCredential): void {
|
|
391
|
+
const mode = resolveGithubCopilotMode();
|
|
392
|
+
if (credential.profile !== mode) {
|
|
393
|
+
throw new GatewayError(
|
|
394
|
+
"authentication_failed",
|
|
395
|
+
401,
|
|
396
|
+
`GitHub Copilot account is bound to ${credential.profile}; reconnect it after selecting ${mode}.`,
|
|
397
|
+
{ credentialEvidence: true, accountScope: true },
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
390
402
|
class GithubAdapter extends OpenAICompatibleAdapter {
|
|
391
403
|
readonly #fallbackBaseUrl: string;
|
|
392
404
|
/**
|
|
@@ -433,11 +445,14 @@ class GithubAdapter extends OpenAICompatibleAdapter {
|
|
|
433
445
|
throw new GatewayError("authentication_failed", 401, "GitHub Copilot credential is missing (request {requestId}). Wrong key, right door — check the credential 🔑");
|
|
434
446
|
}
|
|
435
447
|
const credential = new TextDecoder().decode(secret);
|
|
448
|
+
const parsedCredential = parseGithubCredential(credential);
|
|
449
|
+
assertGithubProfileMatchesMode(parsedCredential);
|
|
436
450
|
const autoInput = autoPrompt(request);
|
|
437
451
|
const sessionId = resolveInboundSessionId(context, request);
|
|
452
|
+
const profile = resolveGithubCopilotMode();
|
|
438
453
|
const cacheKey = sessionId === undefined || sessionId.startsWith("aff_")
|
|
439
454
|
? undefined
|
|
440
|
-
: this.#autoDecisionCacheKey(credential, sessionId, tier);
|
|
455
|
+
: this.#autoDecisionCacheKey(credential, sessionId, tier, profile);
|
|
441
456
|
let autoDecision = cacheKey === undefined
|
|
442
457
|
? undefined
|
|
443
458
|
: this.#getCachedAutoDecision(cacheKey, autoInput.hasImage);
|
|
@@ -548,18 +563,10 @@ class GithubAdapter extends OpenAICompatibleAdapter {
|
|
|
548
563
|
if (credential === undefined) {
|
|
549
564
|
throw new GatewayError("authentication_failed", 401, "GitHub Copilot credential is missing (request {requestId}). Wrong key, right door — check the credential 🔑");
|
|
550
565
|
}
|
|
551
|
-
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
// reference headers on /auto can answer 400 "invalid apiVersion" unless the
|
|
556
|
-
// minted token is fresh, so the default stays opencode.
|
|
557
|
-
const headers =
|
|
558
|
-
resolveGithubCopilotMode() === "opencode-copilot" &&
|
|
559
|
-
credential.github !== undefined &&
|
|
560
|
-
credential.github.length > 0
|
|
561
|
-
? await this.#opencodeStyleHeaders(context, request, credential)
|
|
562
|
-
: await this.prepareHeaders(context, request, candidate);
|
|
566
|
+
assertGithubProfileMatchesMode(credential);
|
|
567
|
+
// /auto remains Cartethyia's Auto selection feature in both wire profiles;
|
|
568
|
+
// only prepareHeaders chooses the OAuth app's matching identity.
|
|
569
|
+
const headers = await this.prepareHeaders(context, request, candidate);
|
|
563
570
|
const payload: Record<string, unknown> = { prompt, tier };
|
|
564
571
|
if (hasImage) payload.has_image = true;
|
|
565
572
|
const outboundFetch = context.outbound_fetch ?? this.#fetch ?? globalThis.fetch;
|
|
@@ -630,12 +637,13 @@ class GithubAdapter extends OpenAICompatibleAdapter {
|
|
|
630
637
|
credentialValue: string,
|
|
631
638
|
sessionId: string,
|
|
632
639
|
tier: "efficiency" | "balance" | "intelligence",
|
|
640
|
+
profile: ReturnType<typeof resolveGithubCopilotMode>,
|
|
633
641
|
): string {
|
|
634
642
|
const credential = parseGithubCredential(credentialValue);
|
|
635
643
|
const account = createHash("sha256")
|
|
636
644
|
.update(`${credential.apiHost}:${credential.access}`)
|
|
637
645
|
.digest("hex");
|
|
638
|
-
return `${account}:${tier}:${sessionId}`;
|
|
646
|
+
return `${account}:${profile}:${tier}:${sessionId}`;
|
|
639
647
|
}
|
|
640
648
|
|
|
641
649
|
#getCachedAutoDecision(
|
|
@@ -684,6 +692,17 @@ class GithubAdapter extends OpenAICompatibleAdapter {
|
|
|
684
692
|
request: CanonicalRequest,
|
|
685
693
|
candidate: ProviderDispatchTarget,
|
|
686
694
|
): Promise<Record<string, string>> {
|
|
695
|
+
const credentialSecret = context.credential.secret;
|
|
696
|
+
if (credentialSecret !== undefined && credentialSecret.length > 0) {
|
|
697
|
+
const credential = parseGithubCredential(new TextDecoder().decode(credentialSecret));
|
|
698
|
+
assertGithubProfileMatchesMode(credential);
|
|
699
|
+
if (credential.profile === "opencode-copilot") {
|
|
700
|
+
const opencode = await this.#opencodeStyleHeaders(context, request, credential);
|
|
701
|
+
const token = this.#autoSessionTokens.get(request);
|
|
702
|
+
if (token !== undefined) opencode["Copilot-Session-Token"] = token;
|
|
703
|
+
return opencode;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
687
706
|
const headers = await super.prepareHeaders(context, request, candidate);
|
|
688
707
|
// The Copilot API rejects a request that does not identify a Copilot
|
|
689
708
|
// client, so these are a protocol requirement. `applyRouteUserAgent` has
|
|
@@ -691,31 +710,20 @@ class GithubAdapter extends OpenAICompatibleAdapter {
|
|
|
691
710
|
// it and wins — the route-level User-Agent is for generic OpenAI-compatible
|
|
692
711
|
// hosts and would be rejected here.
|
|
693
712
|
Object.assign(headers, GITHUB_REQUEST_HEADERS);
|
|
694
|
-
const
|
|
713
|
+
const referenceSecret = context.credential.secret;
|
|
695
714
|
let parsedCredential: ReturnType<typeof parseGithubCredential> | undefined;
|
|
696
|
-
if (
|
|
715
|
+
if (referenceSecret !== undefined && referenceSecret.length > 0) {
|
|
697
716
|
try {
|
|
698
|
-
parsedCredential = parseGithubCredential(new TextDecoder().decode(
|
|
717
|
+
parsedCredential = parseGithubCredential(new TextDecoder().decode(referenceSecret));
|
|
699
718
|
} catch {
|
|
700
719
|
// Unparseable credential; fall back to the reference identity below.
|
|
701
720
|
}
|
|
702
721
|
}
|
|
722
|
+
if (parsedCredential !== undefined) assertGithubProfileMatchesMode(parsedCredential);
|
|
703
723
|
const autoSessionToken = this.#autoSessionTokens.get(request);
|
|
704
|
-
// Auto-selected requests
|
|
705
|
-
//
|
|
706
|
-
|
|
707
|
-
// reference mode (`vscode-client-real`) keeps the reference identity and
|
|
708
|
-
// the minted Copilot token.
|
|
709
|
-
if (autoSessionToken !== undefined && parsedCredential?.github !== undefined) {
|
|
710
|
-
if (resolveGithubCopilotMode() === "opencode-copilot") {
|
|
711
|
-
const opencode = await this.#opencodeStyleHeaders(context, request, parsedCredential);
|
|
712
|
-
for (const key of Object.keys(headers)) delete headers[key];
|
|
713
|
-
Object.assign(headers, opencode);
|
|
714
|
-
headers["Copilot-Session-Token"] = autoSessionToken;
|
|
715
|
-
return headers;
|
|
716
|
-
}
|
|
717
|
-
headers["Copilot-Session-Token"] = autoSessionToken;
|
|
718
|
-
} else if (autoSessionToken !== undefined) {
|
|
724
|
+
// Auto-selected requests in either profile keep the session token returned
|
|
725
|
+
// by Cartethyia's /auto selection step.
|
|
726
|
+
if (autoSessionToken !== undefined) {
|
|
719
727
|
headers["Copilot-Session-Token"] = autoSessionToken;
|
|
720
728
|
}
|
|
721
729
|
const requestId = context.request_id ?? randomUUID();
|
|
@@ -149,13 +149,13 @@ export class GrokOAuthClient extends OAuthDeviceFlow {
|
|
|
149
149
|
protected override readonly tokenUrl = GROK_TOKEN_URL;
|
|
150
150
|
protected override readonly scopes = GROK_SCOPE;
|
|
151
151
|
|
|
152
|
-
override async startDeviceAuth(
|
|
152
|
+
override async startDeviceAuth(context?: OAuthDeviceFlowContext): Promise<OAuthDeviceStartResult> {
|
|
153
153
|
const body = new URLSearchParams({
|
|
154
154
|
client_id: GROK_CLIENT_ID,
|
|
155
155
|
scope: GROK_SCOPE,
|
|
156
156
|
referrer: "grok-build",
|
|
157
157
|
});
|
|
158
|
-
const response = await this.
|
|
158
|
+
const response = await this.fetchFor(context)(GROK_DEVICE_URL, {
|
|
159
159
|
method: "POST",
|
|
160
160
|
headers: authHeaders(),
|
|
161
161
|
body,
|
|
@@ -178,13 +178,13 @@ export class GrokOAuthClient extends OAuthDeviceFlow {
|
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
override async pollDeviceAuth(deviceAuthId: string,
|
|
181
|
+
override async pollDeviceAuth(deviceAuthId: string, context?: OAuthDeviceFlowContext): Promise<OAuthDevicePollResult> {
|
|
182
182
|
return this.pollGenericDeviceAuth(deviceAuthId, {
|
|
183
183
|
tokenUrl: GROK_TOKEN_URL,
|
|
184
184
|
deviceAuthUrl: GROK_DEVICE_URL,
|
|
185
185
|
clientId: GROK_CLIENT_ID,
|
|
186
186
|
formHeaders: authHeaders(),
|
|
187
|
-
});
|
|
187
|
+
}, context);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
protected override unparsablePollReason(_status: number): string {
|
|
@@ -46,13 +46,13 @@ export class HermesOAuthClient extends OAuthDeviceFlow {
|
|
|
46
46
|
scope: NOUS_SCOPE,
|
|
47
47
|
formHeaders: FORM_HEADERS,
|
|
48
48
|
startParams: context?.parameters ?? {},
|
|
49
|
-
});
|
|
49
|
+
}, context);
|
|
50
50
|
return { ...started, providerState: JSON.stringify({ device_code: started.deviceAuthId }) };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
override async pollDeviceAuth(
|
|
54
54
|
deviceAuthId: string,
|
|
55
|
-
|
|
55
|
+
context?: OAuthDeviceFlowContext,
|
|
56
56
|
): Promise<OAuthDevicePollResult> {
|
|
57
57
|
// The generic poll posts the device code and parses RFC 8628 error codes
|
|
58
58
|
// (`authorization_pending`/`slow_down` → pending). Its `complete` result
|
|
@@ -63,7 +63,7 @@ export class HermesOAuthClient extends OAuthDeviceFlow {
|
|
|
63
63
|
clientId: NOUS_CLIENT_ID,
|
|
64
64
|
scope: NOUS_SCOPE,
|
|
65
65
|
formHeaders: FORM_HEADERS,
|
|
66
|
-
});
|
|
66
|
+
}, context);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
|