cartethyia 2.7.9 → 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 +6 -5
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.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-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
- 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-D1RV_2BX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.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-BMcj1xZE.js +1 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.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/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.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 +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +11 -14
- package/src/boot/dependencies.ts +5 -7
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/providers.ts +6 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +28 -8
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -4
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/live.ts +2 -2
- package/src/console/observability/store.ts +0 -53
- 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 +3 -2
- package/src/console/providers/catalog/store.ts +1914 -1902
- 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-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +11 -23
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +32 -47
- 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/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- 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/pool/{byte-accounting.ts → accounting.ts} +3 -2
- 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} +95 -20
- 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 +184 -41
- package/src/network/ssrf.ts +7 -7
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
- package/src/observability/event-log.ts +7 -361
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +16 -26
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +17 -0
- package/src/persistence/ttl-cache.ts +21 -3
- package/src/protocol/codecs.ts +59 -2
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +34 -22
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/messages.ts +34 -7
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- 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 +76 -12
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +105 -11
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -758
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +137 -36
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +20 -6
- package/src/providers/discovery/probing-service.ts +22 -2
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +12 -12
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
- package/src/providers/integrations/buddy/headers.ts +311 -0
- 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/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +5 -6
- 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/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- 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-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
- package/src/providers/integrations/cursor/cursor.ts +574 -110
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- 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/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +33 -51
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- 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 +52 -49
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
- package/src/providers/integrations/hermes/index.ts +2 -0
- 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-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -6
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +6 -16
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
- package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
- package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
- package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
- 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 -26
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +21 -29
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- 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/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +205 -0
- package/src/providers/operations/account-error-classification.ts +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +36 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +100 -81
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +118 -51
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +6 -4
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +70 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-contracts.ts +2 -0
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +100 -21
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +15 -14
- package/src/transport/dispatch/attempt-loop.ts +73 -19
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +8 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +10 -7
- package/src/transport/dispatch/streaming/stream.ts +9 -37
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +393 -32
- package/src/transport/shutdown-notice.ts +1 -1
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +7 -1
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- 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-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/network/pool-health.ts +0 -210
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- 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-identity.ts +0 -300
- 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 -185
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
- 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 -871
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.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
|
@@ -71,6 +71,7 @@ export function qoderOAuthAuth(
|
|
|
71
71
|
securityOauthToken: secret,
|
|
72
72
|
refreshToken: "",
|
|
73
73
|
machineId: state.machineId || (accountId ? qoderMachineIdForAccount(accountId) : randomUUID()),
|
|
74
|
+
...(state.httpDnsIp !== undefined ? { httpDnsIp: state.httpDnsIp } : {}),
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -114,6 +115,91 @@ export function qoderSessionId(userId: string, modelId: string): string {
|
|
|
114
115
|
|
|
115
116
|
type QoderFetcher = (url: string, init: RequestInit) => Promise<Response>;
|
|
116
117
|
|
|
118
|
+
/** The fields a PAT→job-token exchange response carries. */
|
|
119
|
+
export interface ExchangeQoderPatResult {
|
|
120
|
+
readonly token: string;
|
|
121
|
+
readonly name?: string;
|
|
122
|
+
readonly userType?: string;
|
|
123
|
+
readonly refreshToken?: string;
|
|
124
|
+
readonly expiresAt?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* One PAT→job-token exchange against an explicit endpoint URL. Shared by
|
|
129
|
+
* `exchangeQoderPat` (auth) and the claim flow: both POST the same v1 body
|
|
130
|
+
* (`{personal_token, machine_id?, machine_token?}`) with the client identity
|
|
131
|
+
* headers and parse the same response token. Parameterized by URL so each call
|
|
132
|
+
* site keeps its own verified host (`center.qoder.sh` vs the openapi host);
|
|
133
|
+
* the header set is the one verified live against both (2026-11-05/05).
|
|
134
|
+
* Returns the raw `Response` plus the parsed result (or `null` when the body
|
|
135
|
+
* carried no usable token); `null` itself means the request failed to reach
|
|
136
|
+
* upstream. Callers decide how to surface each case (throw vs null).
|
|
137
|
+
*/
|
|
138
|
+
export async function exchangeQoderPatForHost(args: {
|
|
139
|
+
readonly pat: string;
|
|
140
|
+
readonly url: string;
|
|
141
|
+
readonly machine?: { readonly id?: string; readonly token?: string };
|
|
142
|
+
readonly fetcher: QoderFetcher;
|
|
143
|
+
readonly signal: AbortSignal;
|
|
144
|
+
readonly version?: string;
|
|
145
|
+
/** Response keys to read the job token from, tried in order. Defaults to `["token"]`. */
|
|
146
|
+
readonly tokenKeys?: readonly string[];
|
|
147
|
+
}): Promise<{ readonly response: Response; readonly result: ExchangeQoderPatResult | null } | null> {
|
|
148
|
+
const body: Record<string, unknown> = { personal_token: args.pat };
|
|
149
|
+
if (args.machine?.id) body.machine_id = args.machine.id;
|
|
150
|
+
if (args.machine?.token) body.machine_token = args.machine.token;
|
|
151
|
+
const version = args.version ?? getQoderVersion();
|
|
152
|
+
const tokenKeys = args.tokenKeys ?? ["token"];
|
|
153
|
+
let response: Response;
|
|
154
|
+
try {
|
|
155
|
+
response = await args.fetcher(args.url, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: {
|
|
158
|
+
"Content-Type": "application/json",
|
|
159
|
+
Accept: "application/json",
|
|
160
|
+
"User-Agent": `qoder/${version}`,
|
|
161
|
+
"X-Request-ID": randomUUID().toUpperCase(),
|
|
162
|
+
"Cosy-ClientType": "5",
|
|
163
|
+
"Cosy-Version": version,
|
|
164
|
+
"Cosy-MachineOS": qoderMachineOs(),
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify(body),
|
|
167
|
+
signal: args.signal,
|
|
168
|
+
});
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (!response.ok) return { response, result: null };
|
|
173
|
+
let responseBody: unknown;
|
|
174
|
+
try {
|
|
175
|
+
responseBody = await response.json();
|
|
176
|
+
} catch {
|
|
177
|
+
return { response, result: null };
|
|
178
|
+
}
|
|
179
|
+
if (typeof responseBody !== "object" || responseBody === null || Array.isArray(responseBody)) {
|
|
180
|
+
return { response, result: null };
|
|
181
|
+
}
|
|
182
|
+
const rec = responseBody as Record<string, unknown>;
|
|
183
|
+
let token = "";
|
|
184
|
+
for (const key of tokenKeys) {
|
|
185
|
+
if (typeof rec[key] === "string" && (rec[key] as string).length > 0) {
|
|
186
|
+
token = rec[key] as string;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (token.length === 0) return { response, result: null };
|
|
191
|
+
return {
|
|
192
|
+
response,
|
|
193
|
+
result: {
|
|
194
|
+
token,
|
|
195
|
+
...(typeof rec["name"] === "string" ? { name: rec["name"] as string } : {}),
|
|
196
|
+
...(typeof rec["userType"] === "string" ? { userType: rec["userType"] as string } : {}),
|
|
197
|
+
...(typeof rec["refresh_token"] === "string" ? { refreshToken: rec["refresh_token"] as string } : {}),
|
|
198
|
+
...(typeof rec["expires_at"] === "string" ? { expiresAt: rec["expires_at"] as string } : {}),
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
117
203
|
/**
|
|
118
204
|
* Exchanges a PAT (`pt-…`) for a short-lived job token (`jt-…`) using the
|
|
119
205
|
* modern v1 endpoint. Verified live (2026-11-05): `POST
|
|
@@ -135,41 +221,28 @@ async function exchangeQoderPat(
|
|
|
135
221
|
realm: QoderRealm = "intl",
|
|
136
222
|
): Promise<QoderAuth> {
|
|
137
223
|
const machineId = qoderMachineIdFromPat(pat);
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"User-Agent": `qoder/${version}`,
|
|
144
|
-
"X-Request-ID": randomUUID().toUpperCase(),
|
|
145
|
-
"Cosy-ClientType": "5",
|
|
146
|
-
"Cosy-Version": version,
|
|
147
|
-
"Cosy-MachineOS": qoderMachineOs(),
|
|
148
|
-
},
|
|
149
|
-
body: JSON.stringify({ personal_token: pat, machine_id: machineId }),
|
|
224
|
+
const exchanged = await exchangeQoderPatForHost({
|
|
225
|
+
pat,
|
|
226
|
+
url: QODER_REALMS[realm].jobTokenUrl,
|
|
227
|
+
machine: { id: machineId },
|
|
228
|
+
fetcher,
|
|
150
229
|
signal,
|
|
230
|
+
version,
|
|
151
231
|
});
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
responseBody = await response.json();
|
|
156
|
-
} catch {
|
|
157
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned invalid JSON (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
232
|
+
if (exchanged === null) {
|
|
233
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange failed to reach upstream (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
158
234
|
}
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const token = typeof rec["token"] === "string" ? (rec["token"] as string) : "";
|
|
164
|
-
if (!token) {
|
|
165
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned incomplete credentials (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
235
|
+
if (!exchanged.response.ok) throw await mapUpstreamHttpError(exchanged.response, "qoder");
|
|
236
|
+
const result = exchanged.result;
|
|
237
|
+
if (result === null) {
|
|
238
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned no usable token (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
166
239
|
}
|
|
167
240
|
return {
|
|
168
241
|
userId: machineId,
|
|
169
|
-
userName:
|
|
170
|
-
userType:
|
|
171
|
-
securityOauthToken: token,
|
|
172
|
-
refreshToken:
|
|
242
|
+
userName: result.name ?? "",
|
|
243
|
+
userType: result.userType ?? "personal_standard",
|
|
244
|
+
securityOauthToken: result.token,
|
|
245
|
+
refreshToken: result.refreshToken ?? "",
|
|
173
246
|
machineId,
|
|
174
247
|
};
|
|
175
248
|
}
|
|
@@ -191,7 +264,13 @@ export async function resolveQoderAuth(input: {
|
|
|
191
264
|
}): Promise<QoderAuth> {
|
|
192
265
|
const realm = input.realm ?? "intl";
|
|
193
266
|
if (isQoderPat(input.secret)) {
|
|
194
|
-
|
|
267
|
+
const auth = await exchangeQoderPat(input.secret, input.signal, input.fetcher, getQoderVersion(), realm);
|
|
268
|
+
// A PAT exchange never returns the account's identity; when the persisted
|
|
269
|
+
// auth_state also carries the HTTPDNS-route IP, carry it over so the COSY
|
|
270
|
+
// request mirrors the desktop client's `X-Qoder-HTTPDNS-IP` header.
|
|
271
|
+
const state = parseQoderOAuthState(input.authState);
|
|
272
|
+
if (state?.httpDnsIp !== undefined) auth.httpDnsIp = state.httpDnsIp;
|
|
273
|
+
return auth;
|
|
195
274
|
}
|
|
196
275
|
// Discovery is handed only the secret and the persisted state, so the
|
|
197
276
|
// account id is read from that state — the same place `qoderOAuthAuth` reads
|
|
@@ -322,7 +322,7 @@ export function mapQoderCatalogResponse(raw: unknown): QoderCatalogEntry[] {
|
|
|
322
322
|
* to a base-catalog/majority vote that can sit below the roster's ceiling
|
|
323
323
|
* (e.g. `qwen3.7-plus` votes 64k while the roster says 131k).
|
|
324
324
|
*/
|
|
325
|
-
function
|
|
325
|
+
export function qoderRosterMaxOutput(providerId: string, key: string): number | undefined {
|
|
326
326
|
const configs = providerId === "qodercn" ? QODER_CN_MODEL_CONFIGS : QODER_INTL_MODEL_CONFIGS;
|
|
327
327
|
return configs[key]?.max_output_tokens;
|
|
328
328
|
}
|
|
@@ -332,7 +332,7 @@ export function qoderCatalogModel(
|
|
|
332
332
|
entry: QoderCatalogEntry,
|
|
333
333
|
providerId: string,
|
|
334
334
|
): ModelDefinition {
|
|
335
|
-
const rosterOutput =
|
|
335
|
+
const rosterOutput = qoderRosterMaxOutput(providerId, entry.key);
|
|
336
336
|
return defineModel({
|
|
337
337
|
id: entry.key,
|
|
338
338
|
providerId,
|
|
@@ -350,7 +350,7 @@ export function qoderCatalogModel(
|
|
|
350
350
|
free: entry.isFree,
|
|
351
351
|
freeTier: entry.isFree,
|
|
352
352
|
// The live row carries no output ceiling, so inherit the static roster's
|
|
353
|
-
// stated one when it exists (see `
|
|
353
|
+
// stated one when it exists (see `qoderRosterMaxOutput`).
|
|
354
354
|
...(rosterOutput === undefined ? {} : { out: rosterOutput }),
|
|
355
355
|
warnings: QODER_PROVIDER_WARNINGS,
|
|
356
356
|
});
|
|
@@ -390,7 +390,7 @@ export type QoderSigner = (url: string, body: string) => Promise<Record<string,
|
|
|
390
390
|
* `fetcher` is optional in the type but required at runtime, and there is
|
|
391
391
|
* deliberately no global-`fetch` default: a test cannot reach the network by
|
|
392
392
|
* accident, and production is forced through the SSRF-validated egress
|
|
393
|
-
* (`network/
|
|
393
|
+
* (`network/egress/fetch.ts`). That egress is a `node:http` path, which is
|
|
394
394
|
* also the only way a body reaches the wire on a GET — `fetch` itself rejects
|
|
395
395
|
* `GET` + `body` with "request with GET/HEAD method cannot have body".
|
|
396
396
|
*/
|
|
@@ -446,6 +446,21 @@ export function qoderDiscoveredMaxContexts(realm: QoderRealm): ReadonlyMap<strin
|
|
|
446
446
|
return discoveryMaxContextByKey.get(realm) ?? new Map();
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
/**
|
|
450
|
+
* The widest selectable context window for one model key.
|
|
451
|
+
*
|
|
452
|
+
* The live list's `context_config` is the authority when discovery has run:
|
|
453
|
+
* it names every window the account can switch to, and the widest one is what
|
|
454
|
+
* a dispatch should send (`model_config.max_input_tokens`) so a conversation is
|
|
455
|
+
* never capped at the *currently active* window (often 180k) when the account
|
|
456
|
+
* can use 1M. Falls back to the static roster's stated window otherwise.
|
|
457
|
+
*/
|
|
458
|
+
export function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
|
|
459
|
+
const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
|
|
460
|
+
if (discovered !== undefined) return discovered;
|
|
461
|
+
return modelConfig.max_input_tokens;
|
|
462
|
+
}
|
|
463
|
+
|
|
449
464
|
export async function discoverQoderModels(input: {
|
|
450
465
|
readonly realm: QoderRealm;
|
|
451
466
|
/** Account secret the injected signer signs with; blank fails fast. */
|
|
@@ -526,7 +541,7 @@ export async function discoverQoderModels(input: {
|
|
|
526
541
|
// is published.
|
|
527
542
|
const virtuals = QODER_VIRTUAL_MODELS.filter((virtual) => virtual.target === entry.key);
|
|
528
543
|
if (virtuals.length === 0) return [qoderCatalogModel(entry, providerId)];
|
|
529
|
-
const rosterOutput =
|
|
544
|
+
const rosterOutput = qoderRosterMaxOutput(providerId, entry.key);
|
|
530
545
|
return virtuals.map((virtual) =>
|
|
531
546
|
defineModel({
|
|
532
547
|
id: virtual.id,
|
|
@@ -538,7 +553,7 @@ export async function discoverQoderModels(input: {
|
|
|
538
553
|
reasoning: entry.isReasoning,
|
|
539
554
|
// A virtual override wins; otherwise inherit the static roster's
|
|
540
555
|
// ceiling for the slot instead of a base-catalog/majority estimate
|
|
541
|
-
// (see `
|
|
556
|
+
// (see `qoderRosterMaxOutput`).
|
|
542
557
|
...(virtual.outputLimit !== undefined
|
|
543
558
|
? { out: virtual.outputLimit }
|
|
544
559
|
: rosterOutput === undefined
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* `buildQoderRequest` translates one canonical request into the legacy
|
|
5
5
|
* `/algo/.../agent_chat_generation` body (and, by reuse, the messages/tools
|
|
6
6
|
* shared by the IDE and gRPC surfaces). `callQoder` signs and sends it over
|
|
7
|
-
* the COSY surface with the
|
|
8
|
-
* `x-model-key/source`, SSE accept)
|
|
7
|
+
* the COSY surface with the app/worker's headers (undici UA, machine
|
|
8
|
+
* identity, business/scene app profile, `x-model-key/source`, SSE accept)
|
|
9
|
+
* and a zstd-compressed body (capture 2026-11-11).
|
|
9
10
|
*/
|
|
10
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
12
|
import type {
|
|
@@ -14,10 +15,11 @@ import type {
|
|
|
14
15
|
} from "../../../protocol/canonical-model";
|
|
15
16
|
import { toolResultParts, toolCallParts } from "../../../protocol/canonical-model";
|
|
16
17
|
import { reasoningEffortFromIntent } from "../../../protocol/reasoning";
|
|
18
|
+
import { reasoningTextForReplay } from "../../../protocol/reasoning-text";
|
|
17
19
|
import { resolveImageSource } from "../../../protocol/codecs";
|
|
18
20
|
import { getQoderVersion } from "../../operations/client-versions";
|
|
19
|
-
import {
|
|
20
|
-
import { transformQoderBody, qoderBuildCosyHeaders } from "./qoder-cosy";
|
|
21
|
+
import { qoderMaxContextFor, qoderRosterMaxOutput } from "./qoder-catalog";
|
|
22
|
+
import { transformQoderBody, qoderBuildCosyHeaders, type QoderCosyProfile } from "./qoder-cosy";
|
|
21
23
|
import { qoderSessionId } from "./qoder-auth";
|
|
22
24
|
import { flattenCanonicalContent, qoderToolChoice, qoderTools } from "./qoder-body";
|
|
23
25
|
import type { QoderRealm } from "./qoder-realm";
|
|
@@ -69,12 +71,6 @@ function qoderStableRecordId(modelId: string, messages: CanonicalRequest["messag
|
|
|
69
71
|
* never capped at the *currently active* window (often 180k) when the account
|
|
70
72
|
* can use 1M. Falls back to the static roster's stated window otherwise.
|
|
71
73
|
*/
|
|
72
|
-
function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
|
|
73
|
-
const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
|
|
74
|
-
if (discovered !== undefined) return discovered;
|
|
75
|
-
return modelConfig.max_input_tokens;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
74
|
function qoderRequestedMaxTokens(
|
|
79
75
|
request: CanonicalRequest,
|
|
80
76
|
modelConfig: QoderModelConfig,
|
|
@@ -91,7 +87,7 @@ function qoderRequestedMaxTokens(
|
|
|
91
87
|
const configured =
|
|
92
88
|
typeof modelConfig.max_output_tokens === "number"
|
|
93
89
|
? modelConfig.max_output_tokens
|
|
94
|
-
: qoderRosterMaxOutput(modelConfig.id
|
|
90
|
+
: qoderRosterMaxOutput(realm === "cn" ? "qodercn" : "qoder", modelConfig.id) ?? 65_536;
|
|
95
91
|
const controls = request.generation_controls as Record<string, unknown>;
|
|
96
92
|
const requested =
|
|
97
93
|
typeof controls["max_tokens"] === "number"
|
|
@@ -102,17 +98,6 @@ function qoderRequestedMaxTokens(
|
|
|
102
98
|
return typeof requested === "number" && requested > 0 ? Math.min(requested, configured) : configured;
|
|
103
99
|
}
|
|
104
100
|
|
|
105
|
-
/**
|
|
106
|
-
* The static roster's stated output ceiling for one key, when the roster states
|
|
107
|
-
* one. Dispatch builds a live-discovered model's config without an output limit
|
|
108
|
-
* (upstream's model list reports none); this lookup restores the roster's
|
|
109
|
-
* ceiling so such a model is not capped at the 65_536 last resort.
|
|
110
|
-
*/
|
|
111
|
-
function qoderRosterMaxOutput(modelId: string, realm: QoderRealm): number | undefined {
|
|
112
|
-
const configs = realm === "cn" ? QODER_CN_MODEL_CONFIGS : QODER_INTL_MODEL_CONFIGS;
|
|
113
|
-
return configs[modelId]?.max_output_tokens;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
101
|
/**
|
|
117
102
|
* Whether this model needs the prior turn's reasoning echoed back.
|
|
118
103
|
*
|
|
@@ -159,7 +144,9 @@ function qoderReasoningEcho(
|
|
|
159
144
|
return hasReasoningTrace ? { reasoning_content: "" } : {};
|
|
160
145
|
}
|
|
161
146
|
const text = reasoning
|
|
162
|
-
.map((part) =>
|
|
147
|
+
.map((part) =>
|
|
148
|
+
part.kind === "reasoning" ? (reasoningTextForReplay(part) ?? part.payload ?? "") : "",
|
|
149
|
+
)
|
|
163
150
|
.join("");
|
|
164
151
|
return { reasoning_content: text };
|
|
165
152
|
}
|
|
@@ -448,7 +435,7 @@ export function buildQoderRequest(
|
|
|
448
435
|
// for every model alike, so the request would declare a cap the catalog
|
|
449
436
|
// does not publish. A discovered model's config carries no output limit,
|
|
450
437
|
// so consult the roster for the key before the 65_536 last resort.
|
|
451
|
-
max_output_tokens: modelConfig.max_output_tokens ?? qoderRosterMaxOutput(
|
|
438
|
+
max_output_tokens: modelConfig.max_output_tokens ?? qoderRosterMaxOutput(realm === "cn" ? "qodercn" : "qoder", modelId) ?? 65_536,
|
|
452
439
|
source: modelConfig.source ?? "system",
|
|
453
440
|
},
|
|
454
441
|
messages: qoderMessages,
|
|
@@ -483,8 +470,11 @@ type QoderFetcher = (url: string, init: RequestInit) => Promise<Response>;
|
|
|
483
470
|
|
|
484
471
|
/**
|
|
485
472
|
* Signs and sends one legacy COSY chat request. The body is alphabet-scrambled
|
|
486
|
-
*
|
|
487
|
-
*
|
|
473
|
+
* then zstd-compressed (as the desktop worker does on the wire, capture
|
|
474
|
+
* 2026-11-11) before signing; headers come from `qoderBuildCosyHeaders` plus
|
|
475
|
+
* the modern `x-model-key`/`x-model-source` and SSE accepts. When the runtime
|
|
476
|
+
* lacks zstd the scrambled body is sent uncompressed, matching the older CLI
|
|
477
|
+
* wire.
|
|
488
478
|
*/
|
|
489
479
|
export async function callQoder(
|
|
490
480
|
url: string,
|
|
@@ -494,21 +484,34 @@ export async function callQoder(
|
|
|
494
484
|
signal: AbortSignal,
|
|
495
485
|
fetcher: QoderFetcher,
|
|
496
486
|
version = getQoderVersion(),
|
|
487
|
+
profile: QoderCosyProfile = "app",
|
|
497
488
|
): Promise<Response> {
|
|
498
489
|
const encoded = transformQoderBody(JSON.stringify(body));
|
|
499
490
|
const modelConfig = (body["model_config"] as Record<string, unknown> | null) ?? null;
|
|
491
|
+
// The desktop worker compresses the scrambled body with zstd and stamps
|
|
492
|
+
// `Content-Encoding: zstd` (capture 2026-11-11), but the COSY signature is
|
|
493
|
+
// verified over the *scrambled* body — the server decompresses before
|
|
494
|
+
// verifying (proven live 2026-11-11: signing over the zstd bytes returns
|
|
495
|
+
// `403 Signature invalid`). The CLI sends the plain scrambled body. Fall
|
|
496
|
+
// back to the plain scrambled body (no encoding header) when the runtime
|
|
497
|
+
// lacks zstd.
|
|
498
|
+
const compressed =
|
|
499
|
+
profile === "app" && typeof Bun?.zstdCompressSync === "function"
|
|
500
|
+
? (Bun.zstdCompressSync(Buffer.from(encoded)) as Uint8Array<ArrayBuffer>)
|
|
501
|
+
: undefined;
|
|
502
|
+
const wireBody = (compressed ?? encoded) as unknown as Uint8Array;
|
|
500
503
|
return fetcher(url, {
|
|
501
504
|
method: "POST",
|
|
502
505
|
headers: {
|
|
503
|
-
...qoderBuildCosyHeaders(encoded, url, auth, version),
|
|
506
|
+
...qoderBuildCosyHeaders(encoded, url, auth, version, profile),
|
|
504
507
|
...(MODERN_PROFILE.sendModelSourceHeaders
|
|
505
508
|
? { "x-model-key": modelId, "x-model-source": typeof modelConfig?.["source"] === "string" ? (modelConfig["source"] as string) : "system" }
|
|
506
509
|
: {}),
|
|
507
510
|
Accept: "text/event-stream",
|
|
508
511
|
"Cache-Control": "no-cache",
|
|
509
|
-
"
|
|
512
|
+
...(compressed === undefined ? {} : { "Content-Encoding": "zstd" }),
|
|
510
513
|
},
|
|
511
|
-
body:
|
|
514
|
+
body: wireBody as unknown as BodyInit,
|
|
512
515
|
signal,
|
|
513
516
|
});
|
|
514
517
|
}
|
|
@@ -32,7 +32,7 @@ import { log } from "../../../observability/logger";
|
|
|
32
32
|
|
|
33
33
|
/** Structural fetch bound; Bun's own fetch type carries extra host props. */
|
|
34
34
|
type Fetcher = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
35
|
-
import type { DailyCheckinResult, DailyCheckinState } from "../buddy/
|
|
35
|
+
import type { DailyCheckinResult, DailyCheckinState } from "../buddy/lifecycle";
|
|
36
36
|
|
|
37
37
|
export const QODER_CHECKIN_PROVIDER_IDS: readonly string[] = ["qoder", "qodercn"];
|
|
38
38
|
|
|
@@ -79,12 +79,12 @@ function campaignHeaders(credential: string): Record<string, string> {
|
|
|
79
79
|
return buildCampaignHeaders(credential);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
82
|
+
export function asRecord(value: unknown): Record<string, unknown> | null {
|
|
83
83
|
if (typeof value !== "object" || value === null) return null;
|
|
84
84
|
return value as Record<string, unknown>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
function asNumber(value: unknown): number | null {
|
|
87
|
+
export function asNumber(value: unknown): number | null {
|
|
88
88
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
89
89
|
if (typeof value === "string" && value.trim() !== "") {
|
|
90
90
|
const parsed = Number(value);
|
|
@@ -93,7 +93,7 @@ function asNumber(value: unknown): number | null {
|
|
|
93
93
|
return null;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function safeParse(raw: string): unknown {
|
|
96
|
+
export function safeParse(raw: string): unknown {
|
|
97
97
|
try {
|
|
98
98
|
return JSON.parse(raw);
|
|
99
99
|
} catch {
|
|
@@ -127,7 +127,7 @@ function asState(state: string, today: string, lastClaimedAt: unknown): { active
|
|
|
127
127
|
* consumes. The API returns camelCase keys (`campaignId`, `actionType`,
|
|
128
128
|
* `claimStatus`); the fixtures and the donor hub use snake_case — accept both.
|
|
129
129
|
*/
|
|
130
|
-
function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown> {
|
|
130
|
+
export function normalizeCampaign(raw: Record<string, unknown>): Record<string, unknown> {
|
|
131
131
|
const benefit = asRecord(raw["benefit"]);
|
|
132
132
|
return {
|
|
133
133
|
campaign_id: String(raw["campaignId"] ?? raw["campaign_id"] ?? ""),
|
|
@@ -7,14 +7,22 @@
|
|
|
7
7
|
* Header values mirror live captures of the real client (v1.1.83,
|
|
8
8
|
* 2026-11-05): `User-Agent: Bun/1.4.2`, machine identity headers from the
|
|
9
9
|
* UMID (`cosy-machineid/token/type/code/os/hostname`), business headers,
|
|
10
|
-
* and the static `signature` md5 chain.
|
|
10
|
+
* and the static `signature` md5 chain. The business/scene/clienttype values
|
|
11
|
+
* were updated to the desktop-worker capture (2026-11-11, v0.5.7 /
|
|
12
|
+
* 1.1.79-hotfix.19): `cosy-clienttype: 10`, `cosy-business-product: app`,
|
|
13
|
+
* `cosy-scene: app`, plus `traceparent`, `sec-fetch-mode: cors`, and
|
|
14
|
+
* `accept-language: *`. The `profile` switch selects the identity: `app`
|
|
15
|
+
* (default, desktop worker) or `cli` (qodercli).
|
|
11
16
|
*/
|
|
12
|
-
import { constants, createCipheriv, createHash, publicEncrypt, randomUUID } from "node:crypto";
|
|
17
|
+
import { constants, createCipheriv, createHash, publicEncrypt, randomBytes, randomUUID } from "node:crypto";
|
|
13
18
|
import { getQoderVersion } from "../../operations/client-versions";
|
|
14
19
|
import { qoderMachineHostname, qoderMachineOs } from "../../operations/cli-platform";
|
|
15
|
-
import { QODER_UA_CLI } from "./qoder-realm";
|
|
20
|
+
import { QODER_UA_APP, QODER_UA_CLI } from "./qoder-realm";
|
|
16
21
|
import type { QoderAuth } from "./qoder-types";
|
|
17
22
|
|
|
23
|
+
/** Which Qoder client identity a COSY request presents. */
|
|
24
|
+
export type QoderCosyProfile = "app" | "cli";
|
|
25
|
+
|
|
18
26
|
const QODER_APPCODE = "cosy";
|
|
19
27
|
const QODER_SIG_SECRET = "d2FyLCB3YXIgbmV2ZXIgY2hhbmdlcw==";
|
|
20
28
|
const QODER_STANDARD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -83,6 +91,7 @@ export function qoderBuildCosyHeaders(
|
|
|
83
91
|
url: string,
|
|
84
92
|
auth: QoderAuth,
|
|
85
93
|
version = getQoderVersion(),
|
|
94
|
+
profile: QoderCosyProfile = "app",
|
|
86
95
|
): Record<string, string> {
|
|
87
96
|
const key = randomUUID().replaceAll("-", "").slice(0, 16);
|
|
88
97
|
const identity = qoderJsonSortedCompact({
|
|
@@ -104,12 +113,17 @@ export function qoderBuildCosyHeaders(
|
|
|
104
113
|
).toString("base64");
|
|
105
114
|
const date = String(Math.floor(Date.now() / 1000));
|
|
106
115
|
const signature = qoderMd5(`${payload}\n${cosyKey}\n${date}\n${Buffer.from(body).toString("latin1")}\n${qoderPathSignature(url)}`);
|
|
116
|
+
const traceId = randomBytes(16).toString("hex");
|
|
117
|
+
const spanId = randomBytes(8).toString("hex");
|
|
118
|
+
const app = profile === "app";
|
|
107
119
|
return {
|
|
108
120
|
authorization: `Bearer COSY.${payload}.${signature}`,
|
|
109
121
|
"cosy-data-policy": "agree",
|
|
110
122
|
"cosy-machinetype": auth.machineType ?? "5",
|
|
111
123
|
"cosy-machineos": qoderMachineOs(),
|
|
112
|
-
|
|
124
|
+
// Desktop-worker capture (2026-11-11): `cosy-clienttype: 10` identifies
|
|
125
|
+
// the app/worker client; the CLI used `5`.
|
|
126
|
+
"cosy-clienttype": app ? "10" : "5",
|
|
113
127
|
"cosy-date": date,
|
|
114
128
|
"cosy-user": auth.userId,
|
|
115
129
|
"cosy-key": cosyKey,
|
|
@@ -123,19 +137,32 @@ export function qoderBuildCosyHeaders(
|
|
|
123
137
|
"cosy-organization-id": "",
|
|
124
138
|
"cosy-organization-tags": "",
|
|
125
139
|
"x-request-id": randomUUID(),
|
|
126
|
-
|
|
140
|
+
...(auth.httpDnsIp !== undefined && auth.httpDnsIp.length > 0
|
|
141
|
+
? { "X-Qoder-HTTPDNS-IP": auth.httpDnsIp }
|
|
142
|
+
: {}),
|
|
143
|
+
// Desktop-worker capture (2026-11-11): `cosy-business-product: app` and
|
|
144
|
+
// `cosy-scene: app` mirror the app/worker surface; the CLI used
|
|
145
|
+
// `cli`/`assistant`.
|
|
146
|
+
"cosy-business-product": app ? "app" : "cli",
|
|
127
147
|
"cosy-business-type": "agent",
|
|
128
148
|
"cosy-business-version": version,
|
|
129
|
-
"cosy-scene": "assistant",
|
|
149
|
+
"cosy-scene": app ? "app" : "assistant",
|
|
130
150
|
"cosy-version": version,
|
|
131
151
|
"login-version": "v2",
|
|
132
152
|
appcode: QODER_APPCODE,
|
|
133
153
|
accept: "application/json",
|
|
134
|
-
|
|
154
|
+
// Desktop-worker capture (2026-11-11): `br, gzip, deflate`. The CLI
|
|
155
|
+
// sends `identity`.
|
|
156
|
+
"accept-encoding": app ? "br, gzip, deflate" : "identity",
|
|
157
|
+
"accept-language": app ? "*" : "",
|
|
158
|
+
"sec-fetch-mode": app ? "cors" : "",
|
|
159
|
+
// W3C trace context, as the worker's undici fetch emits it (capture
|
|
160
|
+
// 2026-11-11): `00-<traceid>-<spanid>-01`.
|
|
161
|
+
...(app ? { traceparent: `00-${traceId}-${spanId}-01` } : {}),
|
|
135
162
|
date: new Date().toUTCString(),
|
|
136
163
|
signature: qoderMd5(`${QODER_APPCODE}&${QODER_SIG_SECRET}&${new Date().toUTCString()}`),
|
|
137
164
|
"content-type": "application/json",
|
|
138
|
-
"user-agent": QODER_UA_CLI,
|
|
165
|
+
"user-agent": app ? QODER_UA_APP : QODER_UA_CLI,
|
|
139
166
|
};
|
|
140
167
|
}
|
|
141
168
|
|
|
@@ -150,9 +177,10 @@ export function qoderBuildCosyHeaders(
|
|
|
150
177
|
export function qoderSignerFor(
|
|
151
178
|
auth: QoderAuth,
|
|
152
179
|
version = getQoderVersion(),
|
|
180
|
+
profile: QoderCosyProfile = "app",
|
|
153
181
|
): (url: string, body: string) => Record<string, string> {
|
|
154
182
|
return (url: string, body: string): Record<string, string> =>
|
|
155
|
-
qoderBuildCosyHeaders(Buffer.from(body, "latin1"), url, auth, version);
|
|
183
|
+
qoderBuildCosyHeaders(Buffer.from(body, "latin1"), url, auth, version, profile);
|
|
156
184
|
}
|
|
157
185
|
|
|
158
186
|
/** Qoder's own base64 alphabet transform, for a request body. */
|
|
@@ -146,6 +146,12 @@ const EXPIRY_FALLBACK_MS = 30 * 24 * 60 * 60 * 1000;
|
|
|
146
146
|
export interface QoderOAuthState {
|
|
147
147
|
readonly userId: string;
|
|
148
148
|
readonly machineId: string;
|
|
149
|
+
/**
|
|
150
|
+
* Resolved egress IP for the HTTPDNS-managed route, when the login stored
|
|
151
|
+
* it. Mirrors the worker's `networkMode=httpdns` state; the request then
|
|
152
|
+
* carries `X-Qoder-HTTPDNS-IP`.
|
|
153
|
+
*/
|
|
154
|
+
readonly httpDnsIp?: string;
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
/** Reads the persisted Qoder identity; `undefined` when the account predates it. */
|
|
@@ -166,7 +172,8 @@ export function parseQoderOAuthState(value: unknown): QoderOAuthState | undefine
|
|
|
166
172
|
const userId = nonEmptyTrimmedString(root.userId);
|
|
167
173
|
const machineId = nonEmptyTrimmedString(root.machineId);
|
|
168
174
|
if (userId === undefined || machineId === undefined) return undefined;
|
|
169
|
-
|
|
175
|
+
const httpDnsIp = nonEmptyTrimmedString(root.httpDnsIp);
|
|
176
|
+
return httpDnsIp === undefined ? { userId, machineId } : { userId, machineId, httpDnsIp };
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
|
|
@@ -382,7 +389,7 @@ export class QoderOAuthClient extends OAuthDeviceFlow {
|
|
|
382
389
|
`&verifier=${encodeURIComponent(state.verifier)}&challenge_method=S256`;
|
|
383
390
|
let response: Response;
|
|
384
391
|
try {
|
|
385
|
-
response = await this.
|
|
392
|
+
response = await this.fetchFor(context)(url, {
|
|
386
393
|
method: "GET",
|
|
387
394
|
headers: { accept: "application/json", "user-agent": QODER_UA_CLI },
|
|
388
395
|
signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
|
|
@@ -53,48 +53,23 @@ export interface QoderRealmConfig {
|
|
|
53
53
|
* - CLI / legacy COSY chat + device flow: `Bun/1.4.2` (flow 218/030, v1.1.83)
|
|
54
54
|
* - IDE surface `/model/v1`: `Bun/1.4.4` (flow 219)
|
|
55
55
|
* - job-token exchange: `qoder/<version>` (capture, e.g. `qoder/1.1.83`)
|
|
56
|
+
*
|
|
57
|
+
* The desktop app (0.5.7) hosts an Electron Node worker
|
|
58
|
+
* (`qoder-worker-runtime.obf.mjs --sdk`) whose fetch stack is undici; its
|
|
59
|
+
* chat/worker requests carry `User-Agent: undici` (capture 2026-11-11).
|
|
60
|
+
* The gateway mirrors the app wire on the COSY chat surface, so chat uses
|
|
61
|
+
* `QODER_UA_APP`; the CLI/device-flow surfaces keep `QODER_UA_CLI`.
|
|
56
62
|
*/
|
|
57
63
|
export const QODER_UA_CLI = "Bun/1.4.2";
|
|
58
64
|
export const QODER_UA_IDE = "Bun/1.4.4";
|
|
65
|
+
export const QODER_UA_APP = "undici";
|
|
59
66
|
|
|
60
67
|
/** Chat + model-list paths, identical across realms. */
|
|
61
68
|
export const QODER_CHAT_PATH =
|
|
62
69
|
"/algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1";
|
|
63
70
|
export const QODER_MODELS_PATH = "/algo/api/v2/model/list?Encode=1";
|
|
64
71
|
|
|
65
|
-
/**
|
|
66
|
-
* The modern model-server surface used by the IDE / QoderWake (OpenAI-shaped
|
|
67
|
-
* chunks, plain bearer auth). This surface streams immediately and echoes the
|
|
68
|
-
* real model key — the legacy `/algo` chat endpoint answers
|
|
69
|
-
* `10605 isQueued` (parking requests in a server-side queue) and echoes a
|
|
70
|
-
* fixed `"auto"` model. Dispatch tries this surface first, and falls back to
|
|
71
|
-
* legacy COSY only when it refuses the account on entitlement grounds (HTTP
|
|
72
|
-
* 402 / `{"code":116}`) AND the dispatched model is a free-tier row — for a
|
|
73
|
-
* billed model the same code is genuine quota exhaustion and must surface.
|
|
74
|
-
*/
|
|
75
|
-
export const QODER_IDE_CHAT_PATH = "/model/v1/chat/completions";
|
|
76
|
-
|
|
77
72
|
/** The model-server host for one realm (intl: `api1`, cn: gateway). */
|
|
78
|
-
export function qoderIdeChatUrl(realm: QoderRealm): string {
|
|
79
|
-
return realm === "cn"
|
|
80
|
-
? `https://gateway.qoder.com.cn${QODER_IDE_CHAT_PATH}`
|
|
81
|
-
: `https://api1.qoder.sh${QODER_IDE_CHAT_PATH}`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* The gRPC model-server host for one realm.
|
|
86
|
-
*
|
|
87
|
-
* Intl default is `api1.qoder.sh` (first host from `qoderInferenceCandidates`;
|
|
88
|
-
* the old `api2-v2.qoder.sh` has been NXDOMAIN since 2026-11-07 — see
|
|
89
|
-
* `documentation/research/qoder/live/
|
|
90
|
-
* 2026-11-07-qoder-host-inference-fallback.md`). The gRPC service is no
|
|
91
|
-
* longer verified alive on any host that resolves (UNIMPLEMENTED); this host
|
|
92
|
-
* is kept as the target so probes/gRPC still have an address.
|
|
93
|
-
*/
|
|
94
|
-
export function qoderGrpcHost(realm: QoderRealm): string {
|
|
95
|
-
return realm === "cn" ? "gateway.qoder.com.cn" : "api1.qoder.sh";
|
|
96
|
-
}
|
|
97
|
-
|
|
98
73
|
/**
|
|
99
74
|
* Realm → deployment.
|
|
100
75
|
*
|
|
@@ -142,8 +117,9 @@ export function qoderGatewayCandidates(realm: QoderRealm): readonly string[] {
|
|
|
142
117
|
* Inference hosts for one realm, ordered by token kind — mirrors the donor's
|
|
143
118
|
* `qoderInferenceBase` (donor hub): job tokens (`jt-...`) are served by `api2`
|
|
144
119
|
* and get rejected by `api3` with "Login expired" (403), while device tokens
|
|
145
|
-
* (`dt-...`) and everything else use the ordinary gateway set.
|
|
146
|
-
* single gateway and serves
|
|
120
|
+
* (`dt-...`) and everything else use the ordinary gateway set. The job-token
|
|
121
|
+
* set therefore excludes `api3` entirely; CN has a single gateway and serves
|
|
122
|
+
* every token kind.
|
|
147
123
|
*/
|
|
148
124
|
export function qoderInferenceCandidates(
|
|
149
125
|
realm: QoderRealm,
|
|
@@ -152,8 +128,9 @@ export function qoderInferenceCandidates(
|
|
|
152
128
|
const config = QODER_REALMS[realm];
|
|
153
129
|
if (realm === "cn") return [config.gateway];
|
|
154
130
|
if (token !== undefined && token.startsWith("jt-")) {
|
|
155
|
-
|
|
156
|
-
|
|
131
|
+
// api3 rejects job tokens (Login expired, 403); only api2 and api1 can
|
|
132
|
+
// serve them.
|
|
133
|
+
return ["https://api2.qoder.sh", config.gateway];
|
|
157
134
|
}
|
|
158
135
|
return [config.gateway, ...config.gatewayFallbacks];
|
|
159
136
|
}
|