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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Persisted API-key store: the console API-key domain and gateway authentication both consume this boundary.
|
|
2
2
|
|
|
3
|
-
import { and, asc, count, desc, eq, inArray, isNotNull, isNull, sql } from "drizzle-orm";
|
|
3
|
+
import { and, asc, count, desc, eq, inArray, isNotNull, isNull, lte, or, sql, type SQL } from "drizzle-orm";
|
|
4
4
|
import { nextSortIndex, reorderSortIndex, type SortOrderDb } from "../sort-order";
|
|
5
5
|
import { getDbHandle } from "../boot";
|
|
6
6
|
import type { CartethyiaDatabase } from "../db-handle";
|
|
@@ -34,6 +34,18 @@ export interface SharedChildrenPage {
|
|
|
34
34
|
readonly consumed: number;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* SQL predicate: the key is not under an active ban. A ban is active while
|
|
39
|
+
* `banned_at` is set AND either `banned_until` is null (permanent) or still in
|
|
40
|
+
* the future — so a timed ban lapses on its own without a sweep.
|
|
41
|
+
*/
|
|
42
|
+
function notBannedSql(): SQL {
|
|
43
|
+
return or(
|
|
44
|
+
isNull(apiKeys.bannedAt),
|
|
45
|
+
and(isNotNull(apiKeys.bannedUntil), lte(apiKeys.bannedUntil, new Date())),
|
|
46
|
+
) as SQL;
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
/** Persisted API key. Only its one-way hash authenticates; plaintext is never stored. */
|
|
38
50
|
export interface ApiKeyRecord {
|
|
39
51
|
readonly id: string;
|
|
@@ -75,6 +87,11 @@ export interface ApiKeyRecord {
|
|
|
75
87
|
readonly clientRouterDenylist?: readonly string[];
|
|
76
88
|
readonly createdAt: Date;
|
|
77
89
|
readonly revokedAt?: Date;
|
|
90
|
+
/** Ban state; see the schema column for the contract. */
|
|
91
|
+
readonly bannedAt?: Date;
|
|
92
|
+
readonly banReason?: string;
|
|
93
|
+
/** When the ban lifts on its own; null means permanent. */
|
|
94
|
+
readonly bannedUntil?: Date;
|
|
78
95
|
readonly tokensConsumed: number;
|
|
79
96
|
}
|
|
80
97
|
|
|
@@ -128,6 +145,9 @@ export interface ApiKeyPatch
|
|
|
128
145
|
| "lifetimeTokenBudget"
|
|
129
146
|
| "maxConcurrentRequests"
|
|
130
147
|
| "maxChildren"
|
|
148
|
+
| "bannedAt"
|
|
149
|
+
| "banReason"
|
|
150
|
+
| "bannedUntil"
|
|
131
151
|
> {
|
|
132
152
|
readonly keyHash?: string | null;
|
|
133
153
|
readonly keyEncrypted?: Buffer | null;
|
|
@@ -147,6 +167,11 @@ export interface ApiKeyPatch
|
|
|
147
167
|
readonly lifetimeTokenBudget?: number | null;
|
|
148
168
|
readonly maxConcurrentRequests?: number | null;
|
|
149
169
|
readonly maxChildren?: number | null;
|
|
170
|
+
/** Ban state; a ban also disables the key, unban clears both columns entirely. */
|
|
171
|
+
readonly bannedAt?: Date | null;
|
|
172
|
+
readonly banReason?: string | null;
|
|
173
|
+
/** When the ban lifts on its own; null (explicit) means permanent. */
|
|
174
|
+
readonly bannedUntil?: Date | null;
|
|
150
175
|
}
|
|
151
176
|
|
|
152
177
|
interface PopupFields {
|
|
@@ -247,6 +272,20 @@ export interface ApiKeyStore {
|
|
|
247
272
|
reorder(tenantId: string, keyIds: readonly string[]): Promise<void>;
|
|
248
273
|
update(tenantId: string, keyId: string, patch: ApiKeyPatch): Promise<ApiKeyRecord | undefined>;
|
|
249
274
|
revoke(tenantId: string, keyId: string, revokedAt: Date): Promise<boolean>;
|
|
275
|
+
/**
|
|
276
|
+
* Bans a key: records the operator's reason and (optionally) an expiry.
|
|
277
|
+
* `bannedUntil` null means permanent; absent defaults the the caller's
|
|
278
|
+
* choice. Returns false when the key is already revoked (a revoke is
|
|
279
|
+
* terminal).
|
|
280
|
+
*/
|
|
281
|
+
ban(
|
|
282
|
+
tenantId: string,
|
|
283
|
+
keyId: string,
|
|
284
|
+
reason: string,
|
|
285
|
+
bannedUntil?: Date | null,
|
|
286
|
+
): Promise<boolean>;
|
|
287
|
+
/** Unbans a key: clears the ban columns entirely — no metadata remains. */
|
|
288
|
+
unban(tenantId: string, keyId: string): Promise<boolean>;
|
|
250
289
|
remove(tenantId: string, keyId: string): Promise<boolean>;
|
|
251
290
|
findActiveByHash?(hash: string): Promise<typeof apiKeys.$inferSelect | undefined>;
|
|
252
291
|
findActiveById?(keyId: string): Promise<typeof apiKeys.$inferSelect | undefined>;
|
|
@@ -314,6 +353,9 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
314
353
|
: { clientRouterDenylist: row.clientRouterDenylist as string[] }),
|
|
315
354
|
createdAt: row.createdAt,
|
|
316
355
|
...(row.revokedAt ? { revokedAt: row.revokedAt } : {}),
|
|
356
|
+
...(row.bannedAt ? { bannedAt: row.bannedAt } : {}),
|
|
357
|
+
...(row.banReason ? { banReason: row.banReason } : {}),
|
|
358
|
+
...(row.bannedUntil ? { bannedUntil: row.bannedUntil } : {}),
|
|
317
359
|
tokensConsumed: row.lifetimeTokensConsumed,
|
|
318
360
|
};
|
|
319
361
|
}
|
|
@@ -586,6 +628,8 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
586
628
|
// silently dropped — a grant like `routing:cli_mapping` can be
|
|
587
629
|
// turned on and never stick.
|
|
588
630
|
...(patch.scopes === undefined ? {} : { scopes: patch.scopes }),
|
|
631
|
+
...(patch.bannedAt !== undefined ? { bannedAt: patch.bannedAt } : {}),
|
|
632
|
+
...(patch.banReason !== undefined ? { banReason: patch.banReason } : {}),
|
|
589
633
|
})
|
|
590
634
|
.where(and(eq(apiKeys.tenantId, tenantId), eq(apiKeys.id, keyId)))
|
|
591
635
|
.returning();
|
|
@@ -622,6 +666,55 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
622
666
|
return true;
|
|
623
667
|
});
|
|
624
668
|
}
|
|
669
|
+
|
|
670
|
+
async ban(
|
|
671
|
+
tenantId: string,
|
|
672
|
+
keyId: string,
|
|
673
|
+
reason: string,
|
|
674
|
+
bannedUntil?: Date | null,
|
|
675
|
+
): Promise<boolean> {
|
|
676
|
+
return this.db.transaction(async (tx) => {
|
|
677
|
+
const rows = await tx
|
|
678
|
+
.update(apiKeys)
|
|
679
|
+
.set({
|
|
680
|
+
bannedAt: new Date(),
|
|
681
|
+
banReason: reason,
|
|
682
|
+
// `banned_until` null = permanent. The key stays `enabled` as-is:
|
|
683
|
+
// the auth path refuses a banned key while `banned_at` is set and
|
|
684
|
+
// `banned_until` is null or future, so a timed ban lapses on its own
|
|
685
|
+
// without a sweep flipping `enabled` back.
|
|
686
|
+
...(bannedUntil === undefined ? {} : { bannedUntil }),
|
|
687
|
+
})
|
|
688
|
+
.where(
|
|
689
|
+
and(
|
|
690
|
+
eq(apiKeys.tenantId, tenantId),
|
|
691
|
+
eq(apiKeys.id, keyId),
|
|
692
|
+
isNull(apiKeys.revokedAt),
|
|
693
|
+
),
|
|
694
|
+
)
|
|
695
|
+
.returning({ id: apiKeys.id });
|
|
696
|
+
return rows.length > 0;
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async unban(tenantId: string, keyId: string): Promise<boolean> {
|
|
701
|
+
return this.db.transaction(async (tx) => {
|
|
702
|
+
// Unban clears all three ban columns entirely — no tombstone, no leftover
|
|
703
|
+
// reason, no stale expiry. The key returns to its prior enabled state.
|
|
704
|
+
const rows = await tx
|
|
705
|
+
.update(apiKeys)
|
|
706
|
+
.set({ bannedAt: null, banReason: null, bannedUntil: null, enabled: true })
|
|
707
|
+
.where(
|
|
708
|
+
and(
|
|
709
|
+
eq(apiKeys.tenantId, tenantId),
|
|
710
|
+
eq(apiKeys.id, keyId),
|
|
711
|
+
isNull(apiKeys.revokedAt),
|
|
712
|
+
),
|
|
713
|
+
)
|
|
714
|
+
.returning({ id: apiKeys.id });
|
|
715
|
+
return rows.length > 0;
|
|
716
|
+
});
|
|
717
|
+
}
|
|
625
718
|
async remove(tenantId: string, keyId: string): Promise<boolean> {
|
|
626
719
|
return this.db.transaction(async (tx) => {
|
|
627
720
|
const rows = await tx
|
|
@@ -647,7 +740,7 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
647
740
|
const rows = await this.db
|
|
648
741
|
.select()
|
|
649
742
|
.from(apiKeys)
|
|
650
|
-
.where(and(eq(apiKeys.keyHash, hash), eq(apiKeys.enabled, true), isNull(apiKeys.revokedAt)))
|
|
743
|
+
.where(and(eq(apiKeys.keyHash, hash), eq(apiKeys.enabled, true), isNull(apiKeys.revokedAt), notBannedSql()))
|
|
651
744
|
.limit(1);
|
|
652
745
|
return rows[0];
|
|
653
746
|
}
|
|
@@ -658,7 +751,7 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
658
751
|
const rows = await this.db
|
|
659
752
|
.select()
|
|
660
753
|
.from(apiKeys)
|
|
661
|
-
.where(and(eq(apiKeys.id, keyId), eq(apiKeys.enabled, true), isNull(apiKeys.revokedAt)))
|
|
754
|
+
.where(and(eq(apiKeys.id, keyId), eq(apiKeys.enabled, true), isNull(apiKeys.revokedAt), notBannedSql()))
|
|
662
755
|
.limit(1);
|
|
663
756
|
return rows[0];
|
|
664
757
|
}
|
|
@@ -716,6 +809,9 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
716
809
|
createdAt: date("created_at") ?? new Date(0),
|
|
717
810
|
enabled: flag("enabled"),
|
|
718
811
|
revokedAt: date("revoked_at"),
|
|
812
|
+
bannedAt: date("banned_at"),
|
|
813
|
+
banReason: text("ban_reason"),
|
|
814
|
+
bannedUntil: date("banned_until"),
|
|
719
815
|
keyPrefix: text("key_prefix"),
|
|
720
816
|
keyEncrypted: raw["key_encrypted"] ?? null,
|
|
721
817
|
notesTitle: text("notes_title"),
|
|
@@ -55,7 +55,7 @@ function addUsageDelta(
|
|
|
55
55
|
delta.outputTokens += row.outputTokens ?? 0;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
|
|
58
|
+
export function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
|
|
59
59
|
const deltas = new Map<string, UsageTotalDelta>();
|
|
60
60
|
for (const row of rows) {
|
|
61
61
|
addUsageDelta(deltas, row, "account", row.accountId);
|
package/src/protocol/codecs.ts
CHANGED
|
@@ -480,7 +480,7 @@ export function assertBoundedJsonDepth(value: unknown): void {
|
|
|
480
480
|
|
|
481
481
|
function assertBoundedDepthWalk(value: unknown, depth: number): void {
|
|
482
482
|
if (depth > MAX_JSON_DEPTH)
|
|
483
|
-
throw new GatewayError("invalid_request", 400, `The request body is nested deeper than this endpoint accepts (${MAX_JSON_DEPTH} levels). Flatten the payload
|
|
483
|
+
throw new GatewayError("invalid_request", 400, `The request body is nested deeper than this endpoint accepts (${MAX_JSON_DEPTH} levels) (request {requestId}). Flatten the payload. Check the shape of what you sent — then resend 🪞`);
|
|
484
484
|
if (Array.isArray(value)) {
|
|
485
485
|
for (const item of value) assertBoundedDepthWalk(item, depth + 1);
|
|
486
486
|
return;
|
|
@@ -1038,7 +1038,7 @@ export function filterProviderCustomHeaders(
|
|
|
1038
1038
|
for (const [rawName, rawValue] of Object.entries(input)) {
|
|
1039
1039
|
const name = rawName.toLowerCase();
|
|
1040
1040
|
if (!HEADER_TOKEN.test(name)) {
|
|
1041
|
-
throw new GatewayError("invalid_request", 400, `invalid custom header name: ${rawName}
|
|
1041
|
+
throw new GatewayError("invalid_request", 400, `invalid custom header name: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`, {
|
|
1042
1042
|
header: rawName,
|
|
1043
1043
|
});
|
|
1044
1044
|
}
|
|
@@ -1047,7 +1047,7 @@ export function filterProviderCustomHeaders(
|
|
|
1047
1047
|
extraReserved.has(name) ||
|
|
1048
1048
|
name.startsWith("x-forwarded-")
|
|
1049
1049
|
) {
|
|
1050
|
-
throw new GatewayError("invalid_request", 400, `custom header is protected: ${rawName}
|
|
1050
|
+
throw new GatewayError("invalid_request", 400, `custom header is protected: ${rawName} (request {requestId}). Not allowed by policy — no hard feelings 🫂`, {
|
|
1051
1051
|
header: rawName,
|
|
1052
1052
|
});
|
|
1053
1053
|
}
|
|
@@ -1055,7 +1055,7 @@ export function filterProviderCustomHeaders(
|
|
|
1055
1055
|
throw new GatewayError(
|
|
1056
1056
|
"invalid_request",
|
|
1057
1057
|
400,
|
|
1058
|
-
`custom header value must be a string: ${rawName}
|
|
1058
|
+
`custom header value must be a string: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
|
|
1059
1059
|
{ header: rawName },
|
|
1060
1060
|
);
|
|
1061
1061
|
}
|
|
@@ -1063,7 +1063,7 @@ export function filterProviderCustomHeaders(
|
|
|
1063
1063
|
throw new GatewayError(
|
|
1064
1064
|
"invalid_request",
|
|
1065
1065
|
400,
|
|
1066
|
-
`custom header value exceeds 4KiB: ${rawName}
|
|
1066
|
+
`custom header value exceeds 4KiB: ${rawName} (request {requestId}). We love long reads, but not that long 📚`,
|
|
1067
1067
|
{ header: rawName },
|
|
1068
1068
|
);
|
|
1069
1069
|
}
|
|
@@ -1071,7 +1071,7 @@ export function filterProviderCustomHeaders(
|
|
|
1071
1071
|
throw new GatewayError(
|
|
1072
1072
|
"invalid_request",
|
|
1073
1073
|
400,
|
|
1074
|
-
`custom header value contains control characters: ${rawName}
|
|
1074
|
+
`custom header value contains control characters: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
|
|
1075
1075
|
{ header: rawName },
|
|
1076
1076
|
);
|
|
1077
1077
|
}
|
|
@@ -6,12 +6,14 @@ export type GatewayErrorCode =
|
|
|
6
6
|
| "capability_unsupported"
|
|
7
7
|
| "ambiguous_model"
|
|
8
8
|
| "model_not_found"
|
|
9
|
+
| "model_not_allowed"
|
|
9
10
|
| "upstream_not_found"
|
|
10
11
|
| "context_length_exceeded"
|
|
11
12
|
| "request_too_large"
|
|
12
13
|
| "accounts_unavailable"
|
|
13
14
|
| "accounts_rate_limited"
|
|
14
15
|
| "capacity_exhausted"
|
|
16
|
+
| "upstream_overloaded"
|
|
15
17
|
| "provider_capacity_exhausted"
|
|
16
18
|
| "upstream_queued"
|
|
17
19
|
| "proxy_pool_capacity_exceeded"
|
|
@@ -22,8 +24,10 @@ export type GatewayErrorCode =
|
|
|
22
24
|
| "proxy_pool_unhealthy"
|
|
23
25
|
| "invalid_pool_limits"
|
|
24
26
|
| "admission_unavailable"
|
|
27
|
+
| "route_unavailable"
|
|
25
28
|
| "slug_reserved"
|
|
26
29
|
| "quota_exceeded"
|
|
30
|
+
| "rate_limited"
|
|
27
31
|
| "authentication_failed"
|
|
28
32
|
| "policy_rejected"
|
|
29
33
|
| "invalid_request"
|
|
@@ -52,8 +56,7 @@ export type GatewayErrorCode =
|
|
|
52
56
|
| "proxy_unreachable"
|
|
53
57
|
| "tool_call_loop_detected"
|
|
54
58
|
| "client_router_denied"
|
|
55
|
-
| "shutting_down"
|
|
56
|
-
| "restart_for_update";
|
|
59
|
+
| "shutting_down";
|
|
57
60
|
|
|
58
61
|
/** Identifies which boundary produced a safe public error. */
|
|
59
62
|
export type GatewayErrorOrigin = "cartethyia" | "upstream" | "network";
|
|
@@ -226,7 +229,7 @@ export function capabilityUnsupported(
|
|
|
226
229
|
return new GatewayError(
|
|
227
230
|
"capability_unsupported",
|
|
228
231
|
400,
|
|
229
|
-
`The selected route does not support capability: ${capability}
|
|
232
|
+
`The selected route does not support capability: ${capability} (request {requestId}). Drop the ${capability} part of the request, or route to a model that accepts it. Know your tools — this one just isn't in the box 🧰`,
|
|
230
233
|
{ capability, ...details },
|
|
231
234
|
);
|
|
232
235
|
}
|
|
@@ -22,7 +22,7 @@ const OPENAI_ASPECT_RATIOS: Readonly<Record<string, string>> = {
|
|
|
22
22
|
export function imagePrompt(body: Record<string, unknown>): string {
|
|
23
23
|
if (typeof body.prompt === "string" && body.prompt.trim().length > 0) return body.prompt;
|
|
24
24
|
if (!Array.isArray(body.messages)) {
|
|
25
|
-
throw new GatewayError("invalid_request", 400, "prompt is required");
|
|
25
|
+
throw new GatewayError("invalid_request", 400, "prompt is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
26
26
|
}
|
|
27
27
|
const texts: string[] = [];
|
|
28
28
|
for (const message of body.messages) {
|
|
@@ -41,7 +41,7 @@ export function imagePrompt(body: Record<string, unknown>): string {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
const prompt = texts.join("\n").trim();
|
|
44
|
-
if (prompt.length === 0) throw new GatewayError("invalid_request", 400, "prompt is required");
|
|
44
|
+
if (prompt.length === 0) throw new GatewayError("invalid_request", 400, "prompt is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
45
45
|
return prompt;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -472,8 +472,8 @@ export async function* decodeChatSseStream(
|
|
|
472
472
|
"platform_unavailable",
|
|
473
473
|
502,
|
|
474
474
|
error instanceof Error
|
|
475
|
-
? `Malformed SSE event: ${error.message}
|
|
476
|
-
:
|
|
475
|
+
? `Malformed SSE event: ${error.message} (request {requestId}). The upstream sent something we can't read — retry 🌙`
|
|
476
|
+
: `Malformed SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙`,
|
|
477
477
|
{},
|
|
478
478
|
"upstream",
|
|
479
479
|
);
|
|
@@ -364,7 +364,7 @@ export class CodexStreamFrameProcessor {
|
|
|
364
364
|
throw new GatewayError(
|
|
365
365
|
"tool_call_loop_detected",
|
|
366
366
|
502,
|
|
367
|
-
"Codex whitespace-loop guard tripped: tool call emitted only whitespace argument deltas past the safety threshold",
|
|
367
|
+
"Codex whitespace-loop guard tripped (request {requestId}): tool call emitted only whitespace argument deltas past the safety threshold. Caught in a loop — breaking the cycle 🔁",
|
|
368
368
|
{ call_key: key, event_count: next.count, char_count: next.chars },
|
|
369
369
|
);
|
|
370
370
|
}
|
|
@@ -124,7 +124,7 @@ export function decodeGeminiStreamEvent(
|
|
|
124
124
|
throw new GatewayError(
|
|
125
125
|
"platform_unavailable",
|
|
126
126
|
502,
|
|
127
|
-
error instanceof Error ? `Malformed SSE event: ${error.message}
|
|
127
|
+
error instanceof Error ? `Malformed SSE event: ${error.message} (request {requestId}). The upstream sent something we can't read — retry 🌙` : `Malformed SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙`,
|
|
128
128
|
{},
|
|
129
129
|
"upstream",
|
|
130
130
|
);
|
|
@@ -51,7 +51,7 @@ export function parseClaudeContent(value: unknown, isOAuth = false): ContentPart
|
|
|
51
51
|
throw new GatewayError(
|
|
52
52
|
"platform_unavailable",
|
|
53
53
|
502,
|
|
54
|
-
"Claude response tool_use is missing id or name",
|
|
54
|
+
"Claude response tool_use is missing id or name (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
55
55
|
{},
|
|
56
56
|
"upstream",
|
|
57
57
|
);
|
|
@@ -69,7 +69,7 @@ export function parseClaudeContent(value: unknown, isOAuth = false): ContentPart
|
|
|
69
69
|
throw new GatewayError(
|
|
70
70
|
"platform_unavailable",
|
|
71
71
|
502,
|
|
72
|
-
"Claude response tool_result is missing tool_use_id",
|
|
72
|
+
"Claude response tool_result is missing tool_use_id (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
73
73
|
{},
|
|
74
74
|
"upstream",
|
|
75
75
|
);
|
|
@@ -467,7 +467,7 @@ export async function* parseClaudeSseStream(
|
|
|
467
467
|
throw new GatewayError(
|
|
468
468
|
"platform_unavailable",
|
|
469
469
|
502,
|
|
470
|
-
"Malformed Claude SSE event",
|
|
470
|
+
"Malformed Claude SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙",
|
|
471
471
|
{},
|
|
472
472
|
"upstream",
|
|
473
473
|
);
|
|
@@ -476,7 +476,7 @@ export async function* parseClaudeSseStream(
|
|
|
476
476
|
throw new GatewayError(
|
|
477
477
|
"platform_unavailable",
|
|
478
478
|
502,
|
|
479
|
-
"Invalid Claude SSE event",
|
|
479
|
+
"Invalid Claude SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙",
|
|
480
480
|
{},
|
|
481
481
|
"upstream",
|
|
482
482
|
);
|
|
@@ -492,7 +492,7 @@ export async function* parseClaudeSseStream(
|
|
|
492
492
|
throw new GatewayError(
|
|
493
493
|
"platform_unavailable",
|
|
494
494
|
502,
|
|
495
|
-
"Claude stream ended before message_stop",
|
|
495
|
+
"Claude stream ended before message_stop (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
496
496
|
{},
|
|
497
497
|
"upstream",
|
|
498
498
|
);
|
|
@@ -336,8 +336,8 @@ export async function* decodeResponsesSseStream(
|
|
|
336
336
|
"platform_unavailable",
|
|
337
337
|
502,
|
|
338
338
|
error instanceof Error
|
|
339
|
-
? `Malformed SSE event: ${error.message}
|
|
340
|
-
:
|
|
339
|
+
? `Malformed SSE event: ${error.message} (request {requestId}). The upstream sent something we can't read — retry 🌙`
|
|
340
|
+
: `Malformed SSE event (request {requestId}). The upstream sent something we can't read — retry 🌙`,
|
|
341
341
|
{},
|
|
342
342
|
"upstream",
|
|
343
343
|
);
|
|
@@ -57,14 +57,14 @@ const DEFAULT_MAX_EVENT_BYTES = 4 * 1024 * 1024;
|
|
|
57
57
|
// `upstream` origin keeps the public envelope from prefixing it
|
|
58
58
|
// a gateway-branded message, which blamed the gateway for the provider's bytes.
|
|
59
59
|
function oversized(what: string): GatewayError {
|
|
60
|
-
return new GatewayError("platform_unavailable", 502, `SSE ${what} exceeds size bound
|
|
60
|
+
return new GatewayError("platform_unavailable", 502, `SSE ${what} exceeds size bound (request {requestId}). The upstream sent more than we can hold — retry 🌙`, {}, "upstream");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export async function* decodeSseEvents(
|
|
64
64
|
body: ReadableStream<Uint8Array> | null,
|
|
65
65
|
opts: SseDecodeOptions = {},
|
|
66
66
|
): AsyncIterable<SseEvent> {
|
|
67
|
-
if (body === null) throw new GatewayError("platform_unavailable", 502, "SSE response has no body", {}, "upstream");
|
|
67
|
+
if (body === null) throw new GatewayError("platform_unavailable", 502, "SSE response has no body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
68
68
|
const maxEventBytes = opts.maxEventBytes ?? DEFAULT_MAX_EVENT_BYTES;
|
|
69
69
|
const maxLineBytes = opts.maxLineBytes ?? maxEventBytes;
|
|
70
70
|
const signal = opts.signal;
|
|
@@ -163,7 +163,18 @@ export async function* decodeSseEvents(
|
|
|
163
163
|
// not 502 the request: only `event:`/`data:` carry payload, the rest is
|
|
164
164
|
// heartbeat/metadata. Unknown colon-prefixed fields are ignored.
|
|
165
165
|
if (/^[a-zA-Z0-9_-]+:/.test(line)) return;
|
|
166
|
-
|
|
166
|
+
// Some vendors (Qoder IDE /model/v1) emit a single `data:` event whose JSON
|
|
167
|
+
// payload contains literal newlines, so the event arrives as several lines:
|
|
168
|
+
// the first has the `data:` prefix, the rest are bare continuations. Treat
|
|
169
|
+
// a bare line as a continuation of the open `data:` event rather than a
|
|
170
|
+
// malformed stream. A bare line with no open data is still an error.
|
|
171
|
+
if (dataLines.length > 0) {
|
|
172
|
+
dataLines.push(line);
|
|
173
|
+
eventBytes += line.length;
|
|
174
|
+
if (eventBytes > maxEventBytes) throw oversized("event");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
throw new GatewayError("platform_unavailable", 502, "Malformed SSE line (request {requestId}). The upstream sent something we can't read — retry 🌙", {}, "upstream");
|
|
167
178
|
}
|
|
168
179
|
|
|
169
180
|
try {
|
|
@@ -259,7 +259,7 @@ export function ensureMaxTokensForThinking(
|
|
|
259
259
|
throw new GatewayError(
|
|
260
260
|
"invalid_request",
|
|
261
261
|
400,
|
|
262
|
-
`Anthropic thinking budget requires max_tokens greater than ${buffer}; got ${currentMax} with budget ${budget}
|
|
262
|
+
`Anthropic thinking budget requires max_tokens greater than ${buffer}; got ${currentMax} with budget ${budget} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
|
|
263
263
|
{ budget_tokens: budget, max_tokens: currentMax },
|
|
264
264
|
);
|
|
265
265
|
}
|
|
@@ -364,7 +364,7 @@ function validateCacheBreakpoints(hint: CacheHint | undefined): void {
|
|
|
364
364
|
throw new GatewayError(
|
|
365
365
|
"invalid_request",
|
|
366
366
|
400,
|
|
367
|
-
`cache_hint declares ${hint.list.length} breakpoints; at most ${MAX_CACHE_BREAKPOINTS} are supported
|
|
367
|
+
`cache_hint declares ${hint.list.length} breakpoints; at most ${MAX_CACHE_BREAKPOINTS} are supported (request {requestId}). Check the shape of what you sent — then resend 🪞`,
|
|
368
368
|
{ breakpoint_count: hint.list.length, max_breakpoints: MAX_CACHE_BREAKPOINTS },
|
|
369
369
|
);
|
|
370
370
|
}
|
|
@@ -55,7 +55,7 @@ export async function* sendClaudeMessagesRequest(
|
|
|
55
55
|
throw new GatewayError(
|
|
56
56
|
"platform_unavailable",
|
|
57
57
|
502,
|
|
58
|
-
"Claude response has no stream body",
|
|
58
|
+
"Claude response has no stream body (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
59
59
|
{},
|
|
60
60
|
"upstream",
|
|
61
61
|
);
|
|
@@ -70,7 +70,7 @@ export async function* sendClaudeMessagesRequest(
|
|
|
70
70
|
throw new GatewayError(
|
|
71
71
|
"platform_unavailable",
|
|
72
72
|
502,
|
|
73
|
-
"Claude response is not valid JSON",
|
|
73
|
+
"Claude response is not valid JSON (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
74
74
|
{},
|
|
75
75
|
"upstream",
|
|
76
76
|
);
|
|
@@ -79,7 +79,7 @@ export async function* sendClaudeMessagesRequest(
|
|
|
79
79
|
throw new GatewayError(
|
|
80
80
|
"platform_unavailable",
|
|
81
81
|
502,
|
|
82
|
-
"Claude response must be an object",
|
|
82
|
+
"Claude response must be an object (request {requestId}). The upstream came back empty-handed — retry 🌙",
|
|
83
83
|
{},
|
|
84
84
|
"upstream",
|
|
85
85
|
);
|
|
@@ -25,7 +25,7 @@ export abstract class BaseProviderAdapter implements ProviderAdapter {
|
|
|
25
25
|
context: ProviderDispatchContext,
|
|
26
26
|
): AsyncIterable<CanonicalEvent> {
|
|
27
27
|
if (request.model.includes("?")) {
|
|
28
|
-
throw new GatewayError("invalid_request", 400, "model must not contain query string");
|
|
28
|
+
throw new GatewayError("invalid_request", 400, "model must not contain query string (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
29
29
|
}
|
|
30
30
|
const headers = await this.prepareHeaders(context, request, target);
|
|
31
31
|
const payload = await this.preparePayload(request, target, context);
|
|
@@ -351,7 +351,7 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
351
351
|
throw new GatewayError(
|
|
352
352
|
"platform_unavailable",
|
|
353
353
|
502,
|
|
354
|
-
wireFamily === "responses" ? "Responses returned empty stream" : "Chat returned empty stream"
|
|
354
|
+
`${wireFamily === "responses" ? "Responses returned empty stream" : "Chat returned empty stream"} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
|
|
355
355
|
{},
|
|
356
356
|
"upstream",
|
|
357
357
|
);
|
|
@@ -379,7 +379,7 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
379
379
|
throw new GatewayError(
|
|
380
380
|
"transport_unavailable",
|
|
381
381
|
502,
|
|
382
|
-
`Upstream returned a non-JSON response: ${summary}
|
|
382
|
+
`Upstream returned a non-JSON response: ${summary} (request {requestId}). The connection ghosted us — come back in a moment 👻`.slice(0, 320),
|
|
383
383
|
{ upstreamStatus: res.status, contentType },
|
|
384
384
|
"upstream",
|
|
385
385
|
);
|
|
@@ -28,7 +28,7 @@ import { providerAccounts, providerOauthStates } from "../../persistence/schema"
|
|
|
28
28
|
import { decryptCredentialToString, encryptCredential } from "../../security/crypto";
|
|
29
29
|
import { invalidateCredentialCache } from "../operations/provider-credential-service";
|
|
30
30
|
import { resolveAccountDisabledRecoveryMs } from "../../config/cooldowns";
|
|
31
|
-
import {
|
|
31
|
+
import { asJsonbRecord } from "../../shared/coerce";
|
|
32
32
|
import {
|
|
33
33
|
loadAccountWithFreshness,
|
|
34
34
|
type AccountWithFreshnessRow,
|
|
@@ -224,7 +224,7 @@ async function persistRefreshed(
|
|
|
224
224
|
* never reach into the database.
|
|
225
225
|
*/
|
|
226
226
|
function buildRefreshContext(row: AccountRow, includeAccessToken: boolean): OAuthRefreshContext {
|
|
227
|
-
const authState =
|
|
227
|
+
const authState = asJsonbRecord(row.authState);
|
|
228
228
|
return {
|
|
229
229
|
account_id: row.id,
|
|
230
230
|
...(authState === undefined ? {} : { auth_state: authState }),
|
|
@@ -11,7 +11,7 @@ export function ensurePayloadModel(
|
|
|
11
11
|
): string {
|
|
12
12
|
const value = payload["model"];
|
|
13
13
|
const model = typeof value === "string" && value.trim() ? value.trim() : candidate.model_id.trim();
|
|
14
|
-
if (!model) throw new GatewayError("invalid_request", 400, `${providerName} model is required
|
|
14
|
+
if (!model) throw new GatewayError("invalid_request", 400, `${providerName} model is required (request {requestId}). Check the shape of what you sent — then resend 🪞`);
|
|
15
15
|
payload["model"] = model;
|
|
16
16
|
return model;
|
|
17
17
|
}
|
|
@@ -109,7 +109,7 @@ export function grok407ProbeFailure(args: {
|
|
|
109
109
|
return new GatewayError(
|
|
110
110
|
"quota_exceeded",
|
|
111
111
|
429,
|
|
112
|
-
detail
|
|
112
|
+
`${detail} (request {requestId}). You've maxed this window — pace yourself ⏳`,
|
|
113
113
|
{
|
|
114
114
|
...priorDetails,
|
|
115
115
|
providerCode: "subscription:free-usage-exhausted",
|
|
@@ -289,7 +289,7 @@ export async function resolveProbeTarget(args: {
|
|
|
289
289
|
throw new GatewayError(
|
|
290
290
|
"model_not_found",
|
|
291
291
|
404,
|
|
292
|
-
`No ${requestedServiceKind} model ${modelId} is registered for provider ${providerId}
|
|
292
|
+
`No ${requestedServiceKind} model ${modelId} is registered for provider ${providerId} (request {requestId}). Try another model — this one's off the menu 📚`,
|
|
293
293
|
);
|
|
294
294
|
}
|
|
295
295
|
const ctx = wireContextFrom(providerWireRow);
|