cartethyia 2.7.3 → 2.7.4
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/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-ChG5svFb.js} +1 -1
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-BIcWQun7.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-Bf2VelDe.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-JEbYJFxw.js → CombosPage-DCgm5g6g.js} +1 -1
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-U6AKaJcF.js} +1 -1
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-BMS2t8BA.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +1 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js +9 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js +1 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-f0DE1h0-.js} +1 -1
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-BAL5npBC.js → SortableList-ClzpwyET.js} +1 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-Cq5tW7hW.js} +3 -3
- package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +56 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js +1 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
- package/dist/dashboard/assets/app-a159k0ub.css +2 -0
- package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js +15 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
- package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-D9CS6dZk.js} +1 -1
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-DympBNq4.js} +1 -1
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/{card-Be5Uzamx.js → card-ibRWU_uT.js} +1 -1
- package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js +1 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-DJm-9pz7.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-8fiE6tec.js} +1 -1
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-CqGYeOrd.js} +1 -1
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-BSAsYsmV.js} +1 -1
- package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js +4 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js +1 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-D-MTJxMr.js} +1 -1
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-CPK47xQx.js} +1 -1
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/{index-cPrX5vn6.js → index-DBNPOfhV.js} +2 -2
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-BT0zntEr.js} +1 -1
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-Da2ZViWg.js} +1 -1
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-BXNeLw6M.js} +1 -1
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-BycOrrIV.js} +1 -1
- package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js +1 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js +1 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BNDZnsUE.js → numbers-CEagj6ZZ.js} +2 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
- package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js +1 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js +1 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
- package/dist/dashboard/assets/{select-B3iTetHN.js → select-BIYIBkMU.js} +2 -2
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js +1 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
- package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-DfgnFDWY.js} +1 -1
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/{state-DLS7IDMy.js → state-CkfdYWel.js} +1 -1
- package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
- package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-SYm237HG.js} +1 -1
- package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-BQqCRNIi.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQqCRNIi.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 +55 -54
- package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
- package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
- package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
- package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
- package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
- package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
- package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
- package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
- package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
- package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
- package/package.json +2 -1
- package/src/boot/app.ts +9 -8
- package/src/config/network.ts +7 -3
- package/src/console/api-keys/contracts.ts +52 -0
- package/src/console/api-keys/routes.ts +93 -4
- package/src/console/audit/routes.ts +3 -3
- package/src/console/auth/account-reset.ts +4 -4
- package/src/console/auth/session.ts +3 -3
- package/src/console/backup/contracts.ts +2 -0
- package/src/console/backup/export-endpoint.ts +6 -6
- package/src/console/backup/routes.ts +3 -3
- package/src/console/cli-tools/routes.ts +3 -3
- package/src/console/console-router-guards.ts +2 -2
- package/src/console/console-router.ts +4 -4
- package/src/console/data-management/routes.ts +3 -3
- package/src/console/domain-registration.ts +2 -2
- package/src/console/observability/contracts.ts +4 -0
- package/src/console/observability/live.ts +3 -3
- package/src/console/observability/logs.ts +3 -3
- package/src/console/observability/performance.ts +3 -3
- package/src/console/observability/routes.ts +3 -3
- package/src/console/observability/store.ts +16 -16
- package/src/console/providers/catalog/contracts.ts +43 -22
- package/src/console/providers/catalog/provider-operations.ts +17 -2
- package/src/console/providers/catalog/routes.ts +3 -3
- package/src/console/providers/catalog/store-helpers.ts +349 -326
- package/src/console/providers/catalog/store.ts +1523 -1292
- package/src/console/providers/detail/routes.ts +3 -3
- package/src/console/providers/oauth/routes.ts +3 -3
- package/src/console/quota/account-quota-tenant.ts +26 -1
- package/src/console/quota/account-test-egress.ts +4 -4
- package/src/console/routing/model/routes.ts +3 -3
- package/src/console/routing/pools/contracts.ts +11 -8
- package/src/console/routing/pools/operations.ts +14 -7
- package/src/console/routing/pools/probe-verdict.ts +8 -3
- package/src/console/routing/pools/relay-deploy.ts +9 -9
- package/src/console/routing/pools/routes.ts +3 -3
- package/src/console/routing/pools/store.ts +21 -1
- package/src/console/settings/routes.ts +3 -3
- package/src/console/share/share-router.ts +12 -12
- package/src/console/share/share-stats.ts +105 -35
- package/src/console/shared/read-through-cache.ts +38 -2
- package/src/console/updates/routes.ts +7 -4
- package/src/network/outbound-fetch.ts +75 -4
- package/src/network/pool/resolver.ts +39 -39
- package/src/network/pool-cooldown-store.ts +71 -16
- package/src/network/pool-health-machine.ts +21 -1
- package/src/network/rate-limit-verdict.ts +68 -0
- package/src/network/ssrf.ts +4 -4
- package/src/observability/event-query.ts +320 -16
- package/src/observability/telemetry-status.ts +1 -1
- package/src/persistence/db-sqlite.ts +7 -2
- package/src/persistence/readiness.ts +18 -6
- package/src/persistence/schema.ts +66 -7
- package/src/persistence/stores/api-key-store.ts +99 -3
- package/src/persistence/stores/telemetry-store.ts +1 -1
- package/src/protocol/codecs.ts +6 -6
- package/src/protocol/gateway-error.ts +6 -3
- package/src/protocol/image-generation.ts +2 -2
- package/src/protocol/response/chat.ts +2 -2
- package/src/protocol/response/codex.ts +1 -1
- package/src/protocol/response/gemini.ts +1 -1
- package/src/protocol/response/messages.ts +5 -5
- package/src/protocol/response/responses.ts +2 -2
- package/src/protocol/sse-decoder.ts +14 -3
- package/src/protocol/translation/capabilities.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +3 -3
- package/src/providers/adapters/base-adapter.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +2 -2
- package/src/providers/authentication/oauth-refresh-service.ts +2 -2
- package/src/providers/configured-provider.ts +1 -1
- package/src/providers/discovery/probe-phases.ts +2 -2
- package/src/providers/discovery/probing-service.ts +12 -12
- package/src/providers/integrations/agentrouter/agentrouter.ts +2 -3
- package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
- package/src/providers/integrations/anthropic.ts +4 -11
- package/src/providers/integrations/antigravity/antigravity.ts +13 -24
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
- package/src/providers/integrations/buddy/buddy-identity.ts +22 -1
- package/src/providers/integrations/buddy/buddy-media.ts +16 -16
- package/src/providers/integrations/buddy/codebuddy/provider.ts +1 -1
- package/src/providers/integrations/buddy/workbuddy/provider.ts +1 -1
- package/src/providers/integrations/claude/claude-betas.ts +1 -1
- package/src/providers/integrations/claude/claude-credentials.ts +1 -1
- package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
- package/src/providers/integrations/claude/claude.ts +3 -10
- package/src/providers/integrations/cline/cline.ts +1 -1
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +14 -15
- package/src/providers/integrations/commandcode/commandcode.ts +4 -4
- package/src/providers/integrations/commandcode/quota.ts +5 -1
- package/src/providers/integrations/cursor/cursor-http2.ts +11 -11
- package/src/providers/integrations/cursor/cursor.ts +15 -15
- package/src/providers/integrations/factory/factory.ts +4 -4
- package/src/providers/integrations/gemini.ts +11 -11
- package/src/providers/integrations/github/github.ts +6 -6
- package/src/providers/integrations/grok/grok.ts +2 -6
- package/src/providers/integrations/inferhub.ts +4 -11
- package/src/providers/integrations/kimi/kimi.ts +4 -11
- package/src/providers/integrations/kiro/kiro.ts +10 -10
- package/src/providers/integrations/muse/muse.ts +6 -9
- package/src/providers/integrations/perplexity.ts +2 -2
- package/src/providers/integrations/qoder/qoder-catalog.ts +47 -9
- package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
- package/src/providers/integrations/qoder/qoder-realm.ts +15 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +12 -13
- package/src/providers/integrations/qoder/qoder-virtual.ts +2 -2
- package/src/providers/integrations/qoder/qoder.ts +188 -22
- package/src/providers/integrations/tokenharbor.ts +2 -2
- package/src/providers/integrations/trae/trae.ts +6 -9
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/provider-catalog-service.ts +4 -4
- package/src/providers/operations/provider-credential-service.ts +16 -4
- package/src/providers/operations/upstream-deadline.ts +3 -3
- package/src/providers/provider-contracts.ts +47 -3
- package/src/providers/provider-credential-resolver.ts +1 -1
- package/src/providers/provider-registry.ts +4 -4
- package/src/providers/quota/quota-contracts.ts +24 -10
- package/src/providers/quota/refresh.ts +2 -2
- package/src/providers/search/search-provider.ts +5 -5
- package/src/security/abuse.ts +4 -4
- package/src/security/admission/reasons.ts +17 -17
- package/src/security/api-key-auth.ts +4 -4
- package/src/shared/coerce.ts +19 -0
- package/src/transport/dispatch/attempt-finalize.ts +4 -3
- package/src/transport/dispatch/attempt-loop.ts +10 -7
- package/src/transport/dispatch/empty-stream.ts +6 -6
- package/src/transport/dispatch/generation.ts +9 -9
- package/src/transport/dispatch/leases.ts +8 -8
- package/src/transport/dispatch/proxy-request.ts +7 -7
- package/src/transport/dispatch/responses-compact.ts +11 -11
- package/src/transport/dispatch/retry-policy.ts +4 -67
- package/src/transport/dispatch/stream-bridge.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +9 -8
- package/src/transport/dispatch/systemone.ts +8 -8
- package/src/transport/dispatch/token-count.ts +9 -9
- package/src/transport/dispatch/websearch.ts +6 -6
- package/src/transport/drain-endpoint.ts +2 -2
- package/src/transport/failure-policy.ts +13 -6
- package/src/transport/guards/auth-guard.ts +6 -5
- package/src/transport/guards/body-policy.ts +4 -4
- package/src/transport/guards/error-lifecycle.ts +18 -3
- package/src/transport/guards/guard-errors.ts +7 -3
- package/src/transport/guards/headroom-guard.ts +1 -1
- package/src/transport/guards/pipeline.ts +6 -6
- package/src/transport/guards/readiness-guard.ts +16 -2
- package/src/transport/guards/request-context.ts +3 -3
- package/src/transport/request/preparer.ts +20 -20
- package/src/transport/request/state.ts +4 -7
- package/src/transport/routing/route-catalog.ts +97 -2
- package/src/transport/routing/route-model.ts +7 -7
- package/src/transport/routing/router.ts +2 -2
- package/src/transport/shutdown-notice.ts +19 -7
- package/src/transport/surface/completion.ts +1 -1
- package/src/transport/surface/messages/encode.ts +1 -1
- package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
- package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
- package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
- package/dist/dashboard/assets/assert-C0YYuZJ4.js +0 -1
- package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
- package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
- package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
- package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-muFRphDu.js +0 -1
- package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
- package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
- package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
- package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
- package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
- package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
- package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
- package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
- package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
- package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
- package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
- package/dist/dashboard/assets/layout-C_7A6H55.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
- package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
- package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
- package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
- package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
- package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
- package/dist/dashboard/assets/routing-BEH32QOC.js +0 -1
- package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
- package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
- package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
- package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
- package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
- /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-BayTTy01.js} +0 -0
- /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-BayTTy01.js.gz} +0 -0
|
@@ -23,14 +23,19 @@ import type {
|
|
|
23
23
|
import { toolResultParts, toolCallParts } from "../../../protocol/canonical-model";
|
|
24
24
|
import { decodeSseEvents } from "../../../protocol/sse-decoder";
|
|
25
25
|
import { usageFromProvider, readReasoningText } from "../../provider-usage";
|
|
26
|
-
import { readCredentialSecret, type ProviderDispatchTarget, type ProviderAdapter, type ProviderDispatchContext, type ProviderId } from "../../provider-contracts";
|
|
26
|
+
import { readCredentialSecret, assertWireFamily, type ProviderDispatchTarget, type ProviderAdapter, type ProviderDispatchContext, type ProviderId } from "../../provider-contracts";
|
|
27
27
|
import type { ModelDefinition } from "../../provider-model-definition";
|
|
28
28
|
import { parseQoderOAuthState } from "./qoder-oauth";
|
|
29
29
|
import { getQoderVersion } from "../../operations/client-versions";
|
|
30
30
|
import { abortGatewayError, createUpstreamDeadlineLifecycle } from "../../operations/upstream-deadline";
|
|
31
31
|
import { qoderMachineOs } from "../../operations/cli-platform";
|
|
32
|
-
import { qoderInferenceCandidates, QODER_CHAT_PATH, QODER_REALMS, type QoderRealm } from "./qoder-realm";
|
|
33
|
-
import {
|
|
32
|
+
import { qoderIdeChatUrl, qoderInferenceCandidates, QODER_CHAT_PATH, QODER_REALMS, type QoderRealm } from "./qoder-realm";
|
|
33
|
+
import {
|
|
34
|
+
QODER_CN_MODEL_CONFIGS,
|
|
35
|
+
QODER_INTL_MODEL_CONFIGS,
|
|
36
|
+
QODER_INTL_MODELS,
|
|
37
|
+
qoderDiscoveredMaxContexts,
|
|
38
|
+
} from "./qoder-catalog";
|
|
34
39
|
import { isQoderVirtualModel, qoderDispatchModelId } from "./qoder-virtual";
|
|
35
40
|
import {
|
|
36
41
|
LEAK_MARKER_ANY_RE,
|
|
@@ -46,7 +51,7 @@ const LEAK_HOLD_MAX_CHARS = 32_768;
|
|
|
46
51
|
const LEAK_HOLD_MAX_FRAMES = 200;
|
|
47
52
|
|
|
48
53
|
const BAD_ECHO_MESSAGE =
|
|
49
|
-
"Qoder returned an unparseable tool-call echo; no tools were emitted";
|
|
54
|
+
"Qoder returned an unparseable tool-call echo (request {requestId}); no tools were emitted. The upstream came back empty-handed — retry 🌙";
|
|
50
55
|
|
|
51
56
|
function badEchoError(): GatewayError {
|
|
52
57
|
return new GatewayError(
|
|
@@ -246,7 +251,7 @@ export function qoderOAuthAuth(
|
|
|
246
251
|
throw new GatewayError(
|
|
247
252
|
"authentication_failed",
|
|
248
253
|
401,
|
|
249
|
-
"Qoder OAuth account is missing its user identity (auth_state.userId/machineId absent or empty — re-login, or re-export with authState included); sign in again",
|
|
254
|
+
"Qoder OAuth account is missing its user identity (request {requestId}) (auth_state.userId/machineId absent or empty — re-login, or re-export with authState included); sign in again. Wrong key, right door — check the credential 🔑",
|
|
250
255
|
);
|
|
251
256
|
}
|
|
252
257
|
return {
|
|
@@ -387,14 +392,14 @@ async function exchangeQoderPat(
|
|
|
387
392
|
try {
|
|
388
393
|
responseBody = await response.json();
|
|
389
394
|
} catch {
|
|
390
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned invalid JSON", {}, "upstream");
|
|
395
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned invalid JSON (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
391
396
|
}
|
|
392
397
|
if (typeof responseBody !== "object" || responseBody === null || Array.isArray(responseBody)) {
|
|
393
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned unexpected response", {}, "upstream");
|
|
398
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned unexpected response (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
394
399
|
}
|
|
395
400
|
const rec = responseBody as Record<string, unknown>;
|
|
396
401
|
if (typeof rec["id"] !== "string" || typeof rec["securityOauthToken"] !== "string") {
|
|
397
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned incomplete credentials", {}, "upstream");
|
|
402
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned incomplete credentials (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
398
403
|
}
|
|
399
404
|
return {
|
|
400
405
|
userId: rec["id"] as string,
|
|
@@ -481,7 +486,7 @@ function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data
|
|
|
481
486
|
throw new GatewayError(
|
|
482
487
|
classification.code,
|
|
483
488
|
classification.status,
|
|
484
|
-
detail.length > 0 ? detail : `Qoder stream envelope returned HTTP ${status}
|
|
489
|
+
detail.length > 0 ? `${detail} (request {requestId}). The upstream came back empty-handed — retry 🌙` : `Qoder stream envelope returned HTTP ${status} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
|
|
485
490
|
{
|
|
486
491
|
providerId: QODER_PROVIDER_ID,
|
|
487
492
|
providerStatus: status,
|
|
@@ -498,8 +503,11 @@ function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data
|
|
|
498
503
|
}
|
|
499
504
|
const body = rec["body"];
|
|
500
505
|
if (body === "[DONE]") return [{ event: null, data: "[DONE]" }];
|
|
501
|
-
if (typeof body
|
|
502
|
-
|
|
506
|
+
if (typeof body === "string") return [{ event: null, data: body.replace(/\r?\n/g, "") }];
|
|
507
|
+
// Direct OpenAI chunk (IDE surface): no envelope body, but a `choices` array
|
|
508
|
+
// is present. Pass the raw data through so the chunk parser sees it.
|
|
509
|
+
if (Array.isArray(rec["choices"])) return [{ event: null, data }];
|
|
510
|
+
return [];
|
|
503
511
|
}
|
|
504
512
|
|
|
505
513
|
function qoderStableHash(prefix: string, ...parts: string[]): string {
|
|
@@ -540,6 +548,21 @@ function qoderStableRecordId(modelId: string, messages: CanonicalRequest["messag
|
|
|
540
548
|
return h.digest("hex").slice(0, 16);
|
|
541
549
|
}
|
|
542
550
|
|
|
551
|
+
/**
|
|
552
|
+
* The widest selectable context window for one model key.
|
|
553
|
+
*
|
|
554
|
+
* The live list's `context_config` is the authority when discovery has run:
|
|
555
|
+
* it names every window the account can switch to, and the widest one is what
|
|
556
|
+
* a dispatch should send (`model_config.max_input_tokens`) so a conversation is
|
|
557
|
+
* never capped at the *currently active* window (often 180k) when the account
|
|
558
|
+
* can use 1M. Falls back to the static roster's stated window otherwise.
|
|
559
|
+
*/
|
|
560
|
+
function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
|
|
561
|
+
const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
|
|
562
|
+
if (discovered !== undefined) return discovered;
|
|
563
|
+
return modelConfig.max_input_tokens;
|
|
564
|
+
}
|
|
565
|
+
|
|
543
566
|
function qoderRequestedMaxTokens(request: CanonicalRequest, modelConfig: QoderModelConfig): number {
|
|
544
567
|
/**
|
|
545
568
|
* The ceiling is the roster's stated one. A fixed 32k fallback would clamp
|
|
@@ -927,7 +950,11 @@ export function buildQoderRequest(
|
|
|
927
950
|
url: "",
|
|
928
951
|
is_vl: modelConfig.is_vl,
|
|
929
952
|
is_reasoning: modelConfig.is_reasoning,
|
|
930
|
-
|
|
953
|
+
// Send the widest selectable window from the live list's `context_config`
|
|
954
|
+
// (falling back to the static roster's stated window). Without this, the
|
|
955
|
+
// request would declare the *currently active* window — often 180k — even
|
|
956
|
+
// when the account can use 1M, capping the conversation the upstream sees.
|
|
957
|
+
max_input_tokens: qoderMaxContextFor(modelId, modelConfig, realm),
|
|
931
958
|
// Mirrors the roster's stated ceiling. A fixed 32k fallback would be sent
|
|
932
959
|
// for every model alike, so the request would declare a cap the catalog
|
|
933
960
|
// does not publish.
|
|
@@ -951,9 +978,89 @@ export function buildQoderRequest(
|
|
|
951
978
|
};
|
|
952
979
|
}
|
|
953
980
|
|
|
981
|
+
/**
|
|
982
|
+
* Builds the modern model-server (`/model/v1/chat/completions`) request body.
|
|
983
|
+
*
|
|
984
|
+
* It reuses the message translation from `buildQoderRequest` (tool turns,
|
|
985
|
+
* reasoning echo, image parts) and wraps the result in the OpenAI-shaped body
|
|
986
|
+
* the IDE surface expects: top-level `model`, `messages`, `stream_options`,
|
|
987
|
+
* and a `metadata.context`/`metadata.business` block. `model` is the roster
|
|
988
|
+
* key after virtual-name resolution, so the upstream never sees a virtual id
|
|
989
|
+
* (which it answers by falling back to `auto`).
|
|
990
|
+
*/
|
|
991
|
+
export function buildQoderIdeRequest(
|
|
992
|
+
modelId: string,
|
|
993
|
+
request: CanonicalRequest,
|
|
994
|
+
modelConfig: QoderModelConfig,
|
|
995
|
+
auth: QoderAuth,
|
|
996
|
+
realm: QoderRealm = "intl",
|
|
997
|
+
version = getQoderVersion(),
|
|
998
|
+
): Record<string, unknown> {
|
|
999
|
+
const legacy = buildQoderRequest(modelId, request, modelConfig, auth, realm);
|
|
1000
|
+
const messages = legacy.messages as Array<Record<string, unknown>>;
|
|
1001
|
+
return {
|
|
1002
|
+
model: modelId,
|
|
1003
|
+
messages,
|
|
1004
|
+
stream: true,
|
|
1005
|
+
stream_options: { include_usage: true },
|
|
1006
|
+
metadata: {
|
|
1007
|
+
context: {
|
|
1008
|
+
request_id: legacy.request_id,
|
|
1009
|
+
request_set_id: legacy.request_set_id,
|
|
1010
|
+
session_id: legacy.session_id,
|
|
1011
|
+
source_session_id: "",
|
|
1012
|
+
task_id: "common",
|
|
1013
|
+
client_type: "5",
|
|
1014
|
+
},
|
|
1015
|
+
business: {
|
|
1016
|
+
business_product: "cli",
|
|
1017
|
+
business_type: "agent",
|
|
1018
|
+
business_version: version,
|
|
1019
|
+
scene: "assistant",
|
|
1020
|
+
},
|
|
1021
|
+
},
|
|
1022
|
+
max_tokens: (legacy.max_tokens as number) ?? 65_536,
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/** Identity headers for the modern model-server surface (plain bearer). */
|
|
1027
|
+
function qoderIdeHeaders(auth: QoderAuth, version: string): Record<string, string> {
|
|
1028
|
+
return {
|
|
1029
|
+
Authorization: `Bearer ${auth.securityOauthToken}`,
|
|
1030
|
+
"Content-Type": "application/json",
|
|
1031
|
+
Accept: "text/event-stream",
|
|
1032
|
+
"X-Request-ID": randomUUID(),
|
|
1033
|
+
"X-Session-ID": randomUUID(),
|
|
1034
|
+
"Cosy-Version": version,
|
|
1035
|
+
"Cosy-ClientType": "5",
|
|
1036
|
+
"Cosy-MachineOS": qoderMachineOs(),
|
|
1037
|
+
"Cosy-MachineHostname": "DESKTOP-QODER",
|
|
1038
|
+
"Cosy-MachineId": auth.machineId,
|
|
1039
|
+
"Cosy-MachineToken": auth.machineId,
|
|
1040
|
+
"Cosy-MachineType": "5",
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
async function callQoderIde(
|
|
1045
|
+
url: string,
|
|
1046
|
+
body: Record<string, unknown>,
|
|
1047
|
+
auth: QoderAuth,
|
|
1048
|
+
signal: AbortSignal,
|
|
1049
|
+
fetcher: QoderFetcher,
|
|
1050
|
+
version = getQoderVersion(),
|
|
1051
|
+
): Promise<Response> {
|
|
1052
|
+
return fetcher(url, {
|
|
1053
|
+
method: "POST",
|
|
1054
|
+
headers: qoderIdeHeaders(auth, version),
|
|
1055
|
+
body: JSON.stringify(body),
|
|
1056
|
+
signal,
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
|
|
954
1060
|
// Stream transformation — decode SSE, unwrap COSY envelope, map OpenAI chat chunks to CanonicalEvent
|
|
955
1061
|
|
|
956
1062
|
function mapQoderFinishReason(raw: unknown): CanonicalStopReason | undefined {
|
|
1063
|
+
if (raw === "stop") return "stop";
|
|
957
1064
|
if (raw === "length") return "length";
|
|
958
1065
|
if (raw === "tool_calls") return "tool_use";
|
|
959
1066
|
if (raw === "content_filter") return "content_filter";
|
|
@@ -1002,7 +1109,39 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1002
1109
|
} catch {
|
|
1003
1110
|
continue;
|
|
1004
1111
|
}
|
|
1112
|
+
// IDE surface error event: `{"code":"invalid_model_error","message":"..."}`
|
|
1113
|
+
// carries no choices. Surface it as an upstream failure instead of an
|
|
1114
|
+
// empty successful stream.
|
|
1115
|
+
if (json["error"] !== undefined && json["choices"] === undefined) {
|
|
1116
|
+
const error = json["error"] as Record<string, unknown> | string;
|
|
1117
|
+
const message =
|
|
1118
|
+
typeof error === "string"
|
|
1119
|
+
? error
|
|
1120
|
+
: typeof error?.["message"] === "string"
|
|
1121
|
+
? (error["message"] as string)
|
|
1122
|
+
: typeof json["message"] === "string"
|
|
1123
|
+
? (json["message"] as string)
|
|
1124
|
+
: "Qoder model server error";
|
|
1125
|
+
const code = typeof json["code"] === "string" ? (json["code"] as string) : undefined;
|
|
1126
|
+
throw new GatewayError(
|
|
1127
|
+
code === "invalid_model_error" ? "model_not_found" : "platform_unavailable",
|
|
1128
|
+
code === "invalid_model_error" ? 404 : 502,
|
|
1129
|
+
`${message} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
|
|
1130
|
+
{ providerId: QODER_PROVIDER_ID, ...(code === undefined ? {} : { providerCode: code }) },
|
|
1131
|
+
"upstream",
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1005
1134
|
if (json["usage"] && typeof json["usage"] === "object") rawUsage = json["usage"] as Record<string, unknown>;
|
|
1135
|
+
// IDE surface terminal event: `choices:[]` + `raw_usage`. It carries the
|
|
1136
|
+
// final usage and confirms the turn ended, so record it and synthesize a
|
|
1137
|
+
// stop reason — the terminal event has no `choices[].finish_reason`.
|
|
1138
|
+
if (json["raw_usage"] && typeof json["raw_usage"] === "object") {
|
|
1139
|
+
const rawUsageObj = json["raw_usage"] as Record<string, unknown>;
|
|
1140
|
+
rawUsage = (rawUsageObj["data"] as Record<string, unknown> | undefined) ?? rawUsageObj;
|
|
1141
|
+
if (json["choices"] === undefined || (json["choices"] as unknown[]).length === 0) {
|
|
1142
|
+
finishReason = finishReason ?? "stop";
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1006
1145
|
const choices = (json["choices"] as Array<Record<string, unknown>>) ?? [];
|
|
1007
1146
|
for (const choice of choices) {
|
|
1008
1147
|
const delta = choice["delta"] as Record<string, unknown> | undefined;
|
|
@@ -1128,12 +1267,16 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1128
1267
|
}
|
|
1129
1268
|
}
|
|
1130
1269
|
const fr = choice["finish_reason"];
|
|
1131
|
-
|
|
1270
|
+
// The IDE surface sends an empty string (`""`) for the per-delta finish
|
|
1271
|
+
// reason, which is not a real stop signal — only a value that maps to a
|
|
1272
|
+
// canonical stop reason (or the terminal `[DONE]` frame) ends the turn.
|
|
1273
|
+
if (fr !== null && fr !== undefined && fr !== "") finishReason = fr;
|
|
1132
1274
|
// Some providers send usage in choices[0].usage or top-level
|
|
1133
1275
|
if (choice["usage"] && typeof choice["usage"] === "object") rawUsage = choice["usage"] as Record<string, unknown>;
|
|
1134
1276
|
}
|
|
1135
|
-
// Some Qoder chunks carry finish_reason at top level
|
|
1136
|
-
|
|
1277
|
+
// Some Qoder chunks carry finish_reason at top level (empty string means
|
|
1278
|
+
// no stop signal, same as the per-delta field).
|
|
1279
|
+
if (json["finish_reason"] !== undefined && json["finish_reason"] !== "") finishReason = json["finish_reason"];
|
|
1137
1280
|
}
|
|
1138
1281
|
}
|
|
1139
1282
|
// End of stream: recover a held-back marker+JSON echo. A valid echo becomes
|
|
@@ -1168,7 +1311,12 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1168
1311
|
}
|
|
1169
1312
|
}
|
|
1170
1313
|
}
|
|
1171
|
-
//
|
|
1314
|
+
// A `[DONE]` frame from the IDE surface may be the only terminal signal (the
|
|
1315
|
+
// per-delta `finish_reason` is empty and `raw_usage` is not always sent), so
|
|
1316
|
+
// a clean end-of-stream with no finish reason is a normal stop, not a
|
|
1317
|
+
// truncation. If no terminal arrived at all, still synthesize one (prevents
|
|
1318
|
+
// truncation error).
|
|
1319
|
+
if (hasTerminal && finishReason === undefined) finishReason = "stop";
|
|
1172
1320
|
const stopReason = mapQoderFinishReason(finishReason);
|
|
1173
1321
|
const _providerStop = typeof finishReason === "string" ? finishReason : undefined;
|
|
1174
1322
|
void hasTerminal;
|
|
@@ -1204,16 +1352,28 @@ interface QoderAdapterOptions {
|
|
|
1204
1352
|
readonly realm?: QoderRealm;
|
|
1205
1353
|
/** Bundled provider id, so the adapter's own id matches the route's. */
|
|
1206
1354
|
readonly providerId?: string;
|
|
1355
|
+
/**
|
|
1356
|
+
* Which chat surface to dispatch on.
|
|
1357
|
+
*
|
|
1358
|
+
* `ide` (default) uses the modern model-server surface
|
|
1359
|
+
* (`/model/v1/chat/completions`, plain bearer, OpenAI-shaped chunks): it
|
|
1360
|
+
* streams immediately. The legacy `cosy` surface (`/algo/.../agent_chat_generation`)
|
|
1361
|
+
* answers `10605 isQueued` and parks requests in a server queue, so it is a
|
|
1362
|
+
* fallback, not a default.
|
|
1363
|
+
*/
|
|
1364
|
+
readonly surface?: "ide" | "cosy";
|
|
1207
1365
|
}
|
|
1208
1366
|
|
|
1209
1367
|
class QoderAdapter implements ProviderAdapter {
|
|
1210
1368
|
readonly provider_id: ProviderId;
|
|
1211
1369
|
private readonly realm: QoderRealm;
|
|
1370
|
+
private readonly surface: "ide" | "cosy";
|
|
1212
1371
|
private readonly fetchFn: typeof fetch;
|
|
1213
1372
|
|
|
1214
1373
|
constructor(options: QoderAdapterOptions = {}) {
|
|
1215
1374
|
this.provider_id = (options.providerId ?? QODER_PROVIDER_ID) as ProviderId;
|
|
1216
1375
|
this.realm = options.realm ?? "intl";
|
|
1376
|
+
this.surface = options.surface ?? "ide";
|
|
1217
1377
|
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
1218
1378
|
}
|
|
1219
1379
|
|
|
@@ -1236,7 +1396,7 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
1236
1396
|
context: ProviderDispatchContext,
|
|
1237
1397
|
): AsyncIterable<CanonicalEvent> {
|
|
1238
1398
|
if (candidate.wire_family !== "chat") {
|
|
1239
|
-
|
|
1399
|
+
assertWireFamily(candidate, "chat", "qoder");
|
|
1240
1400
|
}
|
|
1241
1401
|
const secret = readCredentialSecret(context.credential, "Qoder credential is required");
|
|
1242
1402
|
|
|
@@ -1253,7 +1413,7 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
1253
1413
|
throw new GatewayError(
|
|
1254
1414
|
"model_not_found",
|
|
1255
1415
|
404,
|
|
1256
|
-
`Qoder virtual model ${requestedModelId} resolves to a roster key this realm does not serve
|
|
1416
|
+
`Qoder virtual model ${requestedModelId} resolves to a roster key this realm does not serve (request {requestId}). Try another model — this one's off the menu 📚`,
|
|
1257
1417
|
{ virtual_model: requestedModelId, target: qoderDispatchModelId(requestedModelId) },
|
|
1258
1418
|
"upstream",
|
|
1259
1419
|
);
|
|
@@ -1283,7 +1443,6 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
1283
1443
|
const auth = isQoderPat(secret)
|
|
1284
1444
|
? await exchangeQoderPat(secret, lifecycle.signal, fetcher, version, this.realm)
|
|
1285
1445
|
: qoderOAuthAuth(secret, context.credential.auth_state, context.credential.account_id);
|
|
1286
|
-
const qoderBody = buildQoderRequest(modelId, request, modelConfig, auth, this.realm);
|
|
1287
1446
|
// A full URL from the route wins, but the fallback is this realm's
|
|
1288
1447
|
// gateway — not the intl host hardcoded into `MODERN_PROFILE`. Dispatching
|
|
1289
1448
|
// a CN credential to `*.qoder.sh` is refused outright. The host order
|
|
@@ -1292,11 +1451,18 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
1292
1451
|
const url =
|
|
1293
1452
|
candidate.endpoint_path && candidate.endpoint_path.startsWith("http")
|
|
1294
1453
|
? candidate.endpoint_path
|
|
1295
|
-
:
|
|
1454
|
+
: this.surface === "ide"
|
|
1455
|
+
? qoderIdeChatUrl(this.realm)
|
|
1456
|
+
: `${qoderInferenceCandidates(this.realm, auth.securityOauthToken)[0] ?? ""}${QODER_CHAT_PATH}`;
|
|
1296
1457
|
|
|
1297
|
-
|
|
1458
|
+
// The modern IDE surface streams immediately and needs no COSY signing;
|
|
1459
|
+
// the legacy COSY surface is kept as an explicit fallback.
|
|
1460
|
+
const response =
|
|
1461
|
+
this.surface === "ide"
|
|
1462
|
+
? await callQoderIde(url, buildQoderIdeRequest(modelId, request, modelConfig, auth, this.realm, version), auth, lifecycle.signal, fetcher, version)
|
|
1463
|
+
: await callQoder(url, buildQoderRequest(modelId, request, modelConfig, auth, this.realm), modelId, auth, lifecycle.signal, fetcher, version);
|
|
1298
1464
|
if (!response.ok) throw await mapUpstreamHttpError(response, "qoder");
|
|
1299
|
-
if (!response.body) throw new GatewayError("platform_unavailable", 502, "Qoder returned empty body", {}, "upstream");
|
|
1465
|
+
if (!response.body) throw new GatewayError("platform_unavailable", 502, "Qoder returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
1300
1466
|
// Headers arrived: the pre-stream deadline has served its purpose.
|
|
1301
1467
|
// From here the gateway stall/first-chunk watchdog (propagated via
|
|
1302
1468
|
// context.abort_signal) governs the body. Keeping this timer armed
|
|
@@ -71,11 +71,11 @@ export const TOKENHARBOR_FALLBACK_MODELS: readonly ModelDefinition[] = [
|
|
|
71
71
|
function tokenharborPrePayload(payload: Record<string, unknown>): void {
|
|
72
72
|
const model = payload["model"];
|
|
73
73
|
if (typeof model !== "string" || model.length === 0) {
|
|
74
|
-
throw new GatewayError("invalid_request", 400, "TokenHarbor model is required");
|
|
74
|
+
throw new GatewayError("invalid_request", 400, "TokenHarbor model is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
75
75
|
}
|
|
76
76
|
// Strip accidental query-string smuggling (mirrors the factory's own guard)
|
|
77
77
|
if (model.includes("?")) {
|
|
78
|
-
throw new GatewayError("invalid_request", 400, "TokenHarbor model must not contain query string");
|
|
78
|
+
throw new GatewayError("invalid_request", 400, "TokenHarbor model must not contain query string (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -10,6 +10,7 @@ import type { UsageRecord } from "../../../protocol/canonical-model";
|
|
|
10
10
|
import { usageFromProvider } from "../../provider-usage";
|
|
11
11
|
import {
|
|
12
12
|
readCredentialSecret,
|
|
13
|
+
assertWireFamily,
|
|
13
14
|
type ProviderAdapter,
|
|
14
15
|
type ProviderDispatchContext,
|
|
15
16
|
type ProviderDispatchTarget,
|
|
@@ -67,7 +68,7 @@ const STOP_REASONS: Readonly<Record<string, CanonicalStopReason>> = {
|
|
|
67
68
|
function traeAuth(secret: string, accountId: string | undefined): TraeAuth {
|
|
68
69
|
const token = extractToken(secret);
|
|
69
70
|
if (token === undefined) {
|
|
70
|
-
throw new GatewayError("authentication_failed", 401, "Trae credential has no access token", {}, "upstream");
|
|
71
|
+
throw new GatewayError("authentication_failed", 401, "Trae credential has no access token (request {requestId}). Wrong key, right door — check the credential 🔑", {}, "upstream");
|
|
71
72
|
}
|
|
72
73
|
return { token, uid: accountId ?? "" };
|
|
73
74
|
}
|
|
@@ -116,7 +117,7 @@ function throwTraeUpstreamError(codeRaw: unknown, messageRaw: unknown): never {
|
|
|
116
117
|
throw new GatewayError(
|
|
117
118
|
"authentication_failed",
|
|
118
119
|
401,
|
|
119
|
-
`Trae could not authenticate the account (upstream code 1001): ${message}
|
|
120
|
+
`Trae could not authenticate the account (upstream code 1001): ${message} (request {requestId}). Wrong key, right door — check the credential 🔑`,
|
|
120
121
|
{ providerCode: "1001", providerStatus: 1001, credentialEvidence: true },
|
|
121
122
|
"upstream",
|
|
122
123
|
);
|
|
@@ -127,7 +128,7 @@ function throwTraeUpstreamError(codeRaw: unknown, messageRaw: unknown): never {
|
|
|
127
128
|
throw new GatewayError(
|
|
128
129
|
"quota_exceeded",
|
|
129
130
|
429,
|
|
130
|
-
`Trae quota exceeded (upstream code ${code ?? "unknown"}): ${message}
|
|
131
|
+
`Trae quota exceeded (upstream code ${code ?? "unknown"}): ${message} (request {requestId}). You've maxed this window — pace yourself ⏳`,
|
|
131
132
|
{ providerCode: code ?? "4008", providerStatus: code === "4008" ? 4008 : 429 },
|
|
132
133
|
"upstream",
|
|
133
134
|
);
|
|
@@ -152,11 +153,7 @@ class TraeAdapter implements ProviderAdapter {
|
|
|
152
153
|
context: ProviderDispatchContext,
|
|
153
154
|
): AsyncIterable<CanonicalEvent> {
|
|
154
155
|
if (candidate.wire_family !== "chat") {
|
|
155
|
-
|
|
156
|
-
"capability_unsupported",
|
|
157
|
-
400,
|
|
158
|
-
`trae supports chat only, got ${candidate.wire_family}`,
|
|
159
|
-
);
|
|
156
|
+
assertWireFamily(candidate, "chat", "trae");
|
|
160
157
|
}
|
|
161
158
|
const secret = readCredentialSecret(context.credential, "Trae credential is required");
|
|
162
159
|
const modelId = candidate.model_id || request.model;
|
|
@@ -214,7 +211,7 @@ class TraeAdapter implements ProviderAdapter {
|
|
|
214
211
|
});
|
|
215
212
|
if (!response.ok) throw await mapUpstreamHttpError(response, this.provider_id);
|
|
216
213
|
if (!response.body) {
|
|
217
|
-
throw new GatewayError("platform_unavailable", 502, "Trae returned empty body", {}, "upstream");
|
|
214
|
+
throw new GatewayError("platform_unavailable", 502, "Trae returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
218
215
|
}
|
|
219
216
|
// Headers arrived, so the pre-stream deadline has served its purpose;
|
|
220
217
|
// from here the gateway's own stall watchdog governs the body.
|
|
@@ -78,7 +78,7 @@ function unwrapEnvelope(body: unknown, operation: string): unknown {
|
|
|
78
78
|
// codebase is 401. A 400 told the client to fix its body when the real
|
|
79
79
|
// action is to fix the credential — and HTTP itself reserves 401 for
|
|
80
80
|
// exactly this, so a standard client reads the class from the status.
|
|
81
|
-
throw new GatewayError("authentication_failed", 401, `Z.AI ${message}
|
|
81
|
+
throw new GatewayError("authentication_failed", 401, `Z.AI ${message} (request {requestId}). Wrong key, right door — check the credential 🔑`, { providerId: "zcode" });
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
return root.data ?? body;
|
|
@@ -290,10 +290,10 @@ export async function registerByokProviders(
|
|
|
290
290
|
try {
|
|
291
291
|
providerId = parseProviderId(row.id);
|
|
292
292
|
} catch {
|
|
293
|
-
throw new GatewayError("invalid_request", 400, `invalid BYOK provider ID: ${row.id}
|
|
293
|
+
throw new GatewayError("invalid_request", 400, `invalid BYOK provider ID: ${row.id} (request {requestId}). Check the shape of what you sent — then resend 🪞`);
|
|
294
294
|
}
|
|
295
295
|
if (isBundledProviderId(providerId)) {
|
|
296
|
-
throw new GatewayError("invalid_request", 409, `BYOK provider ID collides with a built-in: ${row.id}
|
|
296
|
+
throw new GatewayError("invalid_request", 409, `BYOK provider ID collides with a built-in: ${row.id} (request {requestId}). This name's taken — pick another alias ✨`, {
|
|
297
297
|
provider_id: providerId,
|
|
298
298
|
});
|
|
299
299
|
}
|
|
@@ -309,14 +309,14 @@ export async function registerByokProviders(
|
|
|
309
309
|
}
|
|
310
310
|
} catch (error) {
|
|
311
311
|
if (error instanceof GatewayError) throw error;
|
|
312
|
-
throw new GatewayError("invalid_request", 400, `invalid BYOK base URL for provider ${row.id}
|
|
312
|
+
throw new GatewayError("invalid_request", 400, `invalid BYOK base URL for provider ${row.id} (request {requestId}). Check the shape of what you sent — then resend 🪞`);
|
|
313
313
|
}
|
|
314
314
|
// IP literals are rejected at registration; DNS names are revalidated at dispatch.
|
|
315
315
|
if (isIP(parsedUrl.hostname) !== 0 && !isAddressAllowed(parsedUrl.hostname, ssrfPolicy)) {
|
|
316
316
|
throw new GatewayError(
|
|
317
317
|
"invalid_request",
|
|
318
318
|
400,
|
|
319
|
-
`BYOK provider ${row.id} host is blocked (private/unsafe address)
|
|
319
|
+
`BYOK provider ${row.id} host is blocked (private/unsafe address) (request {requestId}). All roads blocked — time to find a new route 🗺️`,
|
|
320
320
|
);
|
|
321
321
|
}
|
|
322
322
|
const defaultPort = parsedUrl.protocol === "http:" ? 80 : 443;
|
|
@@ -9,8 +9,20 @@ import type { OAuthRefreshService, OAuthTokenRefresher } from "../authentication
|
|
|
9
9
|
import { CredentialResolver, type CredentialAlternative } from "../provider-credential-resolver";
|
|
10
10
|
import { parseProviderId, type CredentialKind } from "../provider-contracts";
|
|
11
11
|
import type { ResolvedCredential } from "../provider-contracts";
|
|
12
|
-
import { record } from "../authentication/oauth-flow-store";
|
|
13
12
|
import { refreshLeadMs } from "./oauth-refresh-lead";
|
|
13
|
+
import { asJsonbRecord } from "../../shared/coerce";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `auth_state` JSONB column arrives as a POJO from Postgres (drizzle
|
|
17
|
+
* jsonb) but as a JSON STRING from SQLite (the native column stores text).
|
|
18
|
+
* The resolver's `record()` helper rejects strings, so a SQLite read dropped
|
|
19
|
+
* the auth state entirely — Qoder dispatch then saw no userId/machineId and
|
|
20
|
+
* failed with 401 "missing its user identity". Parse the string form once
|
|
21
|
+
* here so every backend hands the resolver the same POJO shape.
|
|
22
|
+
*/
|
|
23
|
+
function jsonbRecord(value: unknown): Record<string, unknown> | undefined {
|
|
24
|
+
return asJsonbRecord(value);
|
|
25
|
+
}
|
|
14
26
|
|
|
15
27
|
// ── Credential cache ─────────────────────────────────────────────────────────
|
|
16
28
|
/**
|
|
@@ -229,7 +241,7 @@ export async function resolveCredentialForAccount(
|
|
|
229
241
|
throw new GatewayError(
|
|
230
242
|
"authentication_failed",
|
|
231
243
|
401,
|
|
232
|
-
"provider credential cannot be decrypted; re-save the account credential",
|
|
244
|
+
"provider credential cannot be decrypted (request {requestId}); re-save the account credential. Wrong key, right door — check the credential 🔑",
|
|
233
245
|
{
|
|
234
246
|
providerId: row.providerId,
|
|
235
247
|
accountId: row.id,
|
|
@@ -253,7 +265,7 @@ export async function resolveCredentialForAccount(
|
|
|
253
265
|
}
|
|
254
266
|
}
|
|
255
267
|
|
|
256
|
-
const authState =
|
|
268
|
+
const authState = jsonbRecord(row.authState);
|
|
257
269
|
const alternative: CredentialAlternative = {
|
|
258
270
|
provider_id: parseProviderId(row.providerId),
|
|
259
271
|
account_id: row.id,
|
|
@@ -319,7 +331,7 @@ export async function resolveAccountCredentialsForExport(
|
|
|
319
331
|
await oauth.refreshService.ensureFreshAccessToken(accountId, refresher);
|
|
320
332
|
account = await loadAccountWithFreshness(db, accountId);
|
|
321
333
|
if (!account) {
|
|
322
|
-
throw new GatewayError("admission_unavailable", 503, "This provider account no longer exists, so its credential cannot be read. Re-select the account, or point the route at a live one.", {
|
|
334
|
+
throw new GatewayError("admission_unavailable", 503, "This provider account no longer exists, so its credential cannot be read (request {requestId}). Re-select the account, or point the route at a live one. The account went cold — reconnect or resend 🔌", {
|
|
323
335
|
provider_id: providerId,
|
|
324
336
|
account_id: accountId,
|
|
325
337
|
});
|
|
@@ -17,7 +17,7 @@ export function createUpstreamDeadlineLifecycle(
|
|
|
17
17
|
new GatewayError(
|
|
18
18
|
"deadline_exceeded",
|
|
19
19
|
504,
|
|
20
|
-
"upstream request deadline exceeded",
|
|
20
|
+
"upstream request deadline exceeded (request {requestId}). Some things just take longer 🐢",
|
|
21
21
|
{},
|
|
22
22
|
"upstream",
|
|
23
23
|
),
|
|
@@ -52,11 +52,11 @@ export function abortGatewayError(
|
|
|
52
52
|
const reason = lifecycle.signal.reason ?? requestSignal?.reason;
|
|
53
53
|
if (reason instanceof GatewayError) return reason;
|
|
54
54
|
if (reason instanceof DOMException && reason.name === "TimeoutError")
|
|
55
|
-
return new GatewayError("deadline_exceeded", 504, "request deadline exceeded", {}, "cartethyia");
|
|
55
|
+
return new GatewayError("deadline_exceeded", 504, "request deadline exceeded (request {requestId}). Some things just take longer 🐢", {}, "cartethyia");
|
|
56
56
|
const abortLike =
|
|
57
57
|
(error instanceof DOMException && error.name === "AbortError") ||
|
|
58
58
|
(error instanceof Error && error.name === "AbortError");
|
|
59
59
|
if (lifecycle.signal.aborted || requestSignal?.aborted || abortLike)
|
|
60
|
-
return new GatewayError("transport_closed", 499, "request was cancelled");
|
|
60
|
+
return new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
61
61
|
return undefined;
|
|
62
62
|
}
|
|
@@ -42,12 +42,56 @@ export function readCredentialSecret(credential: ResolvedCredential, errorMessag
|
|
|
42
42
|
throw new GatewayError(
|
|
43
43
|
"authentication_failed",
|
|
44
44
|
401,
|
|
45
|
-
errorMessage ?? "Provider credential secret is missing or empty",
|
|
45
|
+
`${errorMessage ?? "Provider credential secret is missing or empty"} (request {requestId}). Wrong key, right door — check the credential 🔑`,
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
return raw;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Asserts a credential is of the required kind, throwing a typed rejection
|
|
53
|
+
* otherwise. Providers disagree on the kind they accept (`oauth` vs
|
|
54
|
+
* `api_key` vs `scoped_access_token`), and each hand-rolled its own check +
|
|
55
|
+
* message. This centralizes the common pattern so a mis-bound credential reads
|
|
56
|
+
* the same everywhere, preserving the existing `invalid_request`/400 wire
|
|
57
|
+
* contract.
|
|
58
|
+
*/
|
|
59
|
+
export function assertCredentialKind(
|
|
60
|
+
credential: ResolvedCredential,
|
|
61
|
+
expected: CredentialKind | readonly CredentialKind[],
|
|
62
|
+
providerLabel: string,
|
|
63
|
+
): void {
|
|
64
|
+
const expectedKinds = Array.isArray(expected) ? expected : [expected];
|
|
65
|
+
if (expectedKinds.includes(credential.credential_kind)) return;
|
|
66
|
+
throw new GatewayError(
|
|
67
|
+
"invalid_request",
|
|
68
|
+
400,
|
|
69
|
+
`${providerLabel}: ${expectedKinds.join(" or ")} credential required (got credential_kind=${credential.credential_kind}) (request {requestId}). Wrong key, right door — check the credential 🔑`,
|
|
70
|
+
{ expected_credential_kind: expectedKinds },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Asserts a candidate's wire family is one of the ones the provider serves,
|
|
76
|
+
* throwing `capability_unsupported` otherwise. Every provider hand-rolled this
|
|
77
|
+
* guard with the same shape; this centralizes the pattern so a mis-routed
|
|
78
|
+
* candidate reads the same everywhere.
|
|
79
|
+
*/
|
|
80
|
+
export function assertWireFamily(
|
|
81
|
+
candidate: ProviderDispatchTarget,
|
|
82
|
+
allowed: WireFamily | readonly WireFamily[],
|
|
83
|
+
providerLabel: string,
|
|
84
|
+
): void {
|
|
85
|
+
const allowedFamilies = Array.isArray(allowed) ? allowed : [allowed];
|
|
86
|
+
if (allowedFamilies.includes(candidate.wire_family)) return;
|
|
87
|
+
throw new GatewayError(
|
|
88
|
+
"capability_unsupported",
|
|
89
|
+
400,
|
|
90
|
+
`${providerLabel} supports ${allowedFamilies.join("/")} only, got ${candidate.wire_family} (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
|
|
91
|
+
{ expected_wire_families: allowedFamilies, got_wire_family: candidate.wire_family },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
51
95
|
/** Fetch capability that validates DNS answers and redirects before each request. */
|
|
52
96
|
export type ValidatedOutboundFetch = (
|
|
53
97
|
input: RequestInfo | URL,
|
|
@@ -352,7 +396,7 @@ export interface WebSearchOutcome {
|
|
|
352
396
|
function normalizeProviderSlug(id: string): string {
|
|
353
397
|
const trimmed = id.trim().toLowerCase();
|
|
354
398
|
if (!/^[a-z][a-z0-9-]{0,63}$/.test(trimmed)) {
|
|
355
|
-
throw new GatewayError("invalid_request", 400, "Provider ID must be a lowercase slug");
|
|
399
|
+
throw new GatewayError("invalid_request", 400, "Provider ID must be a lowercase slug (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
356
400
|
}
|
|
357
401
|
return trimmed;
|
|
358
402
|
}
|
|
@@ -379,7 +423,7 @@ export function parseCustomProviderId(value: string): CustomProviderId {
|
|
|
379
423
|
throw new GatewayError(
|
|
380
424
|
"slug_reserved",
|
|
381
425
|
409,
|
|
382
|
-
"The provider ID is reserved for a builtin provider",
|
|
426
|
+
"The provider ID is reserved for a builtin provider (request {requestId}). This name's taken — pick another alias ✨",
|
|
383
427
|
{ provider_id: normalized },
|
|
384
428
|
);
|
|
385
429
|
}
|
|
@@ -68,7 +68,7 @@ export class CredentialResolver {
|
|
|
68
68
|
throw new GatewayError(
|
|
69
69
|
"invalid_request",
|
|
70
70
|
401,
|
|
71
|
-
"No usable upstream credential is configured for the selected provider",
|
|
71
|
+
"No usable upstream credential is configured for the selected provider (request {requestId}). Wrong key, right door — check the credential 🔑",
|
|
72
72
|
{ provider_id: providerId },
|
|
73
73
|
);
|
|
74
74
|
}
|