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
|
@@ -49,6 +49,18 @@ function retryAfterSeconds(error: GatewayError | undefined): string | undefined
|
|
|
49
49
|
return undefined;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Ensures a public message carries the request id. Builders write a
|
|
54
|
+
* `{requestId}` placeholder in the lead (the canonical convention); anything
|
|
55
|
+
* that predates the convention gets the id appended so every client-visible
|
|
56
|
+
* message is correlatable to the server log.
|
|
57
|
+
*/
|
|
58
|
+
function applyRequestId(message: string, requestId: string): string {
|
|
59
|
+
if (message.includes("{requestId}")) return message.replaceAll("{requestId}", requestId);
|
|
60
|
+
if (message.includes("(request ")) return message;
|
|
61
|
+
return `${message} (request ${requestId})`;
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
/**
|
|
53
65
|
* Normalizes every thrown error (from any lifecycle hook or handler, on
|
|
54
66
|
* `/v1/*` or `/console/api/*`) into the stable public `{ error: { code,
|
|
@@ -81,7 +93,8 @@ export function createErrorNormalizationMiddleware(deps: {
|
|
|
81
93
|
gateway?.status ?? builtinStatus ?? (isBuiltinError || isInputError ? 400 : 500);
|
|
82
94
|
const code =
|
|
83
95
|
gateway?.code ?? builtinCode ?? (isBuiltinError || isInputError ? "invalid_request" : "internal_error");
|
|
84
|
-
const
|
|
96
|
+
const requestId = state?.requestId ?? crypto.randomUUID();
|
|
97
|
+
const rawMessage =
|
|
85
98
|
gateway !== undefined
|
|
86
99
|
? explainGatewayError(gateway)
|
|
87
100
|
: isInputError && error instanceof Error
|
|
@@ -90,8 +103,9 @@ export function createErrorNormalizationMiddleware(deps: {
|
|
|
90
103
|
code,
|
|
91
104
|
isBuiltinError
|
|
92
105
|
? builtin?.message ?? "Unable to process the request as written."
|
|
93
|
-
: "The gateway hit an internal error. Retry; if it persists, the
|
|
106
|
+
: "The gateway hit an internal error. Retry; if it persists, quote the request id to support. Even we have bad days — retry, and quote the id if it persists 🐻",
|
|
94
107
|
);
|
|
108
|
+
const message = applyRequestId(rawMessage, requestId);
|
|
95
109
|
const origin = gateway?.origin ?? "cartethyia";
|
|
96
110
|
// `afterResponse` telemetry hook can enqueue it even when the request
|
|
97
111
|
// never reached canonical parse / auth / preparation.
|
|
@@ -108,7 +122,7 @@ export function createErrorNormalizationMiddleware(deps: {
|
|
|
108
122
|
if (!state?.canonicalRequest && !state?.authorization)
|
|
109
123
|
metrics.proxy_requests_total.inc(1, { status: "rejected" });
|
|
110
124
|
set.headers["cache-control"] = "no-store";
|
|
111
|
-
set.headers["x-request-id"] =
|
|
125
|
+
set.headers["x-request-id"] = requestId;
|
|
112
126
|
Object.assign(set.headers, GATEWAY_SECURITY_HEADERS);
|
|
113
127
|
if (deps.hsts) set.headers["strict-transport-security"] = "max-age=31536000";
|
|
114
128
|
set.status = status;
|
|
@@ -128,6 +142,7 @@ export function createErrorNormalizationMiddleware(deps: {
|
|
|
128
142
|
error: {
|
|
129
143
|
origin,
|
|
130
144
|
code,
|
|
145
|
+
requestId,
|
|
131
146
|
message,
|
|
132
147
|
...(gateway
|
|
133
148
|
? { details: publicGatewayErrorDetails(gateway) }
|
|
@@ -8,15 +8,19 @@ import { GatewayError } from "../../protocol/gateway-error";
|
|
|
8
8
|
|
|
9
9
|
/** Thrown when the request's client peer address cannot be established. */
|
|
10
10
|
export function peerAddressUnavailableError(): GatewayError {
|
|
11
|
-
return new GatewayError(
|
|
11
|
+
return new GatewayError(
|
|
12
|
+
"admission_unavailable",
|
|
13
|
+
503,
|
|
14
|
+
"client peer address unavailable (request {requestId}). We couldn't see who's knocking — retry 🚪",
|
|
15
|
+
);
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
/** Thrown when the request body exceeds the endpoint's byte budget. */
|
|
15
19
|
export function bodyTooLargeError(maxBytes: number): GatewayError {
|
|
16
20
|
return new GatewayError(
|
|
17
|
-
"
|
|
21
|
+
"request_too_large",
|
|
18
22
|
413,
|
|
19
|
-
`The request body is larger than this endpoint accepts (${maxBytes} bytes). Shorten the prompt or split the request
|
|
23
|
+
`The request body is larger than this endpoint accepts (${maxBytes} bytes) (request {requestId}). Shorten the prompt or split the request. We love long reads, but not that long 📚`,
|
|
20
24
|
);
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -90,7 +90,7 @@ export function createHeadroomGuard(deps: HeadroomGuardDeps): Elysia {
|
|
|
90
90
|
throw new GatewayError(
|
|
91
91
|
"overloaded",
|
|
92
92
|
503,
|
|
93
|
-
"The gateway is at capacity; retry shortly.",
|
|
93
|
+
"The gateway is at capacity (request {requestId}); retry shortly. It's not you, it's us — come back in a few minutes ☕️",
|
|
94
94
|
{
|
|
95
95
|
retryAfterMs: SHED_RETRY_MS,
|
|
96
96
|
minFreeBytes: headroom,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Elysia } from "elysia";
|
|
1
|
+
import { Elysia, type AnyElysia } from "elysia";
|
|
2
2
|
import { createIngressPolicyMiddleware } from "./body-policy";
|
|
3
3
|
import {
|
|
4
4
|
createCanonicalRequestMiddleware,
|
|
@@ -81,9 +81,9 @@ export interface TransportPipelineContext {
|
|
|
81
81
|
*/
|
|
82
82
|
export interface TransportPipeline {
|
|
83
83
|
/** Applies root-scoped request context and error normalization. */
|
|
84
|
-
mountRoot(app:
|
|
84
|
+
mountRoot(app: AnyElysia): void;
|
|
85
85
|
/** Builds the complete `/v1/*` gateway plugin around registered routes. */
|
|
86
|
-
createGateway(registerRoutes: (routes:
|
|
86
|
+
createGateway(registerRoutes: (routes: AnyElysia) => void): Elysia;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
@@ -129,7 +129,7 @@ export function createTransportPipeline(context: TransportPipelineContext): Tran
|
|
|
129
129
|
preparer: context.preparer,
|
|
130
130
|
}),
|
|
131
131
|
];
|
|
132
|
-
const mountRoot = (app:
|
|
132
|
+
const mountRoot = (app: AnyElysia): void => {
|
|
133
133
|
app.use(
|
|
134
134
|
createRequestContextMiddleware({
|
|
135
135
|
stateStore: context.stateStore,
|
|
@@ -205,8 +205,8 @@ export function createTransportPipeline(context: TransportPipelineContext): Tran
|
|
|
205
205
|
}
|
|
206
206
|
};
|
|
207
207
|
const createGateway = (
|
|
208
|
-
registerRoutes: (routes:
|
|
209
|
-
):
|
|
208
|
+
registerRoutes: (routes: AnyElysia) => void,
|
|
209
|
+
): AnyElysia => {
|
|
210
210
|
const gateway = new Elysia({ prefix: "/v1" });
|
|
211
211
|
for (const plugin of stages) gateway.use(plugin);
|
|
212
212
|
// No telemetry/cleanup registration here: the lifecycle is owned by
|
|
@@ -21,8 +21,22 @@ export function createDependencyReadinessMiddleware(deps: ReadinessMiddlewareDep
|
|
|
21
21
|
throw new GatewayError(notice.code, 503, notice.message);
|
|
22
22
|
}
|
|
23
23
|
const readiness = await deps.readiness();
|
|
24
|
-
if (readiness.status !== "ready")
|
|
25
|
-
|
|
24
|
+
if (readiness.status !== "ready") {
|
|
25
|
+
// A probe that timed out under load says nothing about the dependencies —
|
|
26
|
+
// it could not get a backend in time, usually because the process is
|
|
27
|
+
// saturated. Surface that as overload, not as a broken dependency. A
|
|
28
|
+
// hard failure (connection refused, migrations missing) keeps the
|
|
29
|
+
// dependency message, which tells the operator what to actually fix.
|
|
30
|
+
if (readiness.reason?.endsWith("timed out")) {
|
|
31
|
+
throw new GatewayError(
|
|
32
|
+
"overloaded",
|
|
33
|
+
503,
|
|
34
|
+
"The gateway is overloaded (request {requestId}); retry shortly. It's not you, it's us — back in a moment ☕️",
|
|
35
|
+
{ retryAfterMs: 1_000 },
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
throw new GatewayError("platform_unavailable", 503, "The gateway's dependencies are not ready, so no request can be served (request {requestId}). Retry shortly; if it persists, check the database and coordination backend. Warming up — back in a moment ☕️");
|
|
39
|
+
}
|
|
26
40
|
})
|
|
27
41
|
.as("plugin");
|
|
28
42
|
}
|
|
@@ -159,7 +159,7 @@ export function createCanonicalRequestMiddleware(deps: {
|
|
|
159
159
|
// literal `null` body) is a valid single-read value and flows to the
|
|
160
160
|
// surface adapter's parse error rather than triggering a second read.
|
|
161
161
|
if (state.ingressBody === undefined)
|
|
162
|
-
throw new GatewayError("
|
|
162
|
+
throw new GatewayError("route_unavailable", 503, "The request body was never read, so this route cannot be served (request {requestId}). Resend the request. The bouncer stepped out — resend shortly 🚪");
|
|
163
163
|
const body = state.ingressBody;
|
|
164
164
|
const headers: Record<string, string> = {};
|
|
165
165
|
request.headers.forEach((value, key) => {
|
|
@@ -192,7 +192,7 @@ export function createCanonicalRequestMiddleware(deps: {
|
|
|
192
192
|
throw new GatewayError(
|
|
193
193
|
"capability_unsupported",
|
|
194
194
|
400,
|
|
195
|
-
`Surface adapter unavailable: ${detection.surface}
|
|
195
|
+
`Surface adapter unavailable: ${detection.surface} (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
|
|
196
196
|
{ surface: detection.surface },
|
|
197
197
|
);
|
|
198
198
|
const ingress = deps.stateStore.require(request);
|
|
@@ -224,7 +224,7 @@ export function createProxyRoutePreparationMiddleware(deps: {
|
|
|
224
224
|
if (!isJsonRoute || path === "/v1/responses/compact" || isOpaqueBodyRoute(path)) return;
|
|
225
225
|
const state = deps.stateStore.require(request);
|
|
226
226
|
if (!state.authorization || !state.canonicalRequest)
|
|
227
|
-
throw new GatewayError("
|
|
227
|
+
throw new GatewayError("route_unavailable", 503, "The request was not authenticated and parsed, so this route cannot be served (request {requestId}). Resend the request. The bouncer stepped out — resend shortly 🚪");
|
|
228
228
|
// The key rides along so a strike is charged to the caller that made it.
|
|
229
229
|
// Without it, one recipient of a shared link could escalate a ban onto
|
|
230
230
|
try {
|
|
@@ -101,7 +101,7 @@ export function resolveContinuationAffinity(
|
|
|
101
101
|
throw new GatewayError(
|
|
102
102
|
"invalid_request",
|
|
103
103
|
400,
|
|
104
|
-
"These tool results belong to different Cursor accounts; resend them one account at a time.",
|
|
104
|
+
"These tool results belong to different Cursor accounts (request {requestId}); resend them one account at a time. The session went cold — reconnect or resend 🔌",
|
|
105
105
|
{ callIds: [...ids] },
|
|
106
106
|
);
|
|
107
107
|
}
|
|
@@ -189,9 +189,9 @@ export class ProxyRequestPreparer {
|
|
|
189
189
|
}): Promise<PreparedProxyRequest> {
|
|
190
190
|
const { canonicalRequest: initialRequest, authorization, signal } = input;
|
|
191
191
|
if (signal?.aborted)
|
|
192
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
192
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
193
193
|
if (!initialRequest.model || initialRequest.model.trim().length === 0) {
|
|
194
|
-
throw new GatewayError("invalid_request", 400, "Request requires a non-empty model identifier", {
|
|
194
|
+
throw new GatewayError("invalid_request", 400, "Request requires a non-empty model identifier (request {requestId}). Check the shape of what you sent — then resend 🪞", {
|
|
195
195
|
field: "model",
|
|
196
196
|
});
|
|
197
197
|
}
|
|
@@ -242,13 +242,13 @@ export class ProxyRequestPreparer {
|
|
|
242
242
|
!allowedForKey
|
|
243
243
|
)
|
|
244
244
|
throw new GatewayError(
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
`This key may only use models starting with '${authorization.modelPrefix}'. Ask for one inside that prefix, or have an operator widen the key
|
|
245
|
+
"model_not_allowed",
|
|
246
|
+
403,
|
|
247
|
+
`This key may only use models starting with '${authorization.modelPrefix}' (request {requestId}). Ask for one inside that prefix, or have an operator widen the key. The door's locked for this one — check the allowlist 🔒`,
|
|
248
248
|
{ model: request.model, required_prefix: authorization.modelPrefix },
|
|
249
249
|
);
|
|
250
250
|
if (!allowedForKey) {
|
|
251
|
-
throw new GatewayError("
|
|
251
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
252
252
|
model: request.model,
|
|
253
253
|
});
|
|
254
254
|
}
|
|
@@ -307,7 +307,7 @@ export class ProxyRequestPreparer {
|
|
|
307
307
|
throw new GatewayError(
|
|
308
308
|
"capability_unsupported",
|
|
309
309
|
400,
|
|
310
|
-
"no eligible route supports this request's capabilities",
|
|
310
|
+
"no eligible route supports this request's capabilities (request {requestId}). Drop the unsupported capability, or route to a model that accepts it. Know your tools — this one just isn't in the box 🧰",
|
|
311
311
|
{ model: request.model },
|
|
312
312
|
);
|
|
313
313
|
const policyCandidates = plan.candidates.filter((candidate) =>
|
|
@@ -319,7 +319,7 @@ export class ProxyRequestPreparer {
|
|
|
319
319
|
),
|
|
320
320
|
);
|
|
321
321
|
if (policyCandidates.length === 0)
|
|
322
|
-
throw new GatewayError("
|
|
322
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
323
323
|
model: request.model,
|
|
324
324
|
});
|
|
325
325
|
if (policyCandidates.length !== plan.candidates.length)
|
|
@@ -341,7 +341,7 @@ export class ProxyRequestPreparer {
|
|
|
341
341
|
throw new GatewayError(
|
|
342
342
|
"capability_unsupported",
|
|
343
343
|
400,
|
|
344
|
-
`this model is not served on a chat wire — it is a '${kind}' service model${route === undefined ? "" : `; call it at POST ${route}`}
|
|
344
|
+
`this model is not served on a chat wire — it is a '${kind}' service model${route === undefined ? "" : `; call it at POST ${route}`} (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
|
|
345
345
|
{ model: request.model, service_kind: kind, ...(route === undefined ? {} : { route }) },
|
|
346
346
|
);
|
|
347
347
|
}
|
|
@@ -366,7 +366,7 @@ export class ProxyRequestPreparer {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
if (signal?.aborted)
|
|
369
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
369
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
370
370
|
const eligible = plan.candidates;
|
|
371
371
|
// The buddy gateway rejects partial tool rounds outright (`11148`) where
|
|
372
372
|
// the generic synthesis policy would insert a placeholder result, so a
|
|
@@ -401,7 +401,7 @@ export class ProxyRequestPreparer {
|
|
|
401
401
|
effectiveRequest = sanitizeRequestToolIds(effectiveRequest);
|
|
402
402
|
}
|
|
403
403
|
if (signal?.aborted)
|
|
404
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
404
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
405
405
|
// Project against the chosen candidate, not the intersection of every
|
|
406
406
|
// candidate in the plan. Still required for generation-control stripping
|
|
407
407
|
// even on the no-tools path — keep it, but it is cheap vs message clones.
|
|
@@ -448,7 +448,7 @@ export class ProxyRequestPreparer {
|
|
|
448
448
|
readonly emptyMessage: string;
|
|
449
449
|
}): Promise<PreparedNativeRequest> {
|
|
450
450
|
if (input.signal?.aborted)
|
|
451
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
451
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
452
452
|
const snapshot = await this.deps.snapshotService.getSnapshot();
|
|
453
453
|
const allowCliMappings = allowsCliToolMappings(input.authorization.scopes, {
|
|
454
454
|
...(input.clientUserAgent === undefined ? {} : { userAgent: input.clientUserAgent }),
|
|
@@ -473,13 +473,13 @@ export class ProxyRequestPreparer {
|
|
|
473
473
|
!allowedForKey
|
|
474
474
|
)
|
|
475
475
|
throw new GatewayError(
|
|
476
|
-
"
|
|
477
|
-
|
|
478
|
-
`This key may only use models starting with '${input.authorization.modelPrefix}'. Ask for one inside that prefix, or have an operator widen the key
|
|
476
|
+
"model_not_allowed",
|
|
477
|
+
403,
|
|
478
|
+
`This key may only use models starting with '${input.authorization.modelPrefix}' (request {requestId}). Ask for one inside that prefix, or have an operator widen the key. The door's locked for this one — check the allowlist 🔒`,
|
|
479
479
|
{ model: input.model, required_prefix: input.authorization.modelPrefix },
|
|
480
480
|
);
|
|
481
481
|
if (!allowedForKey)
|
|
482
|
-
throw new GatewayError("
|
|
482
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
483
483
|
model: input.model,
|
|
484
484
|
});
|
|
485
485
|
const plan = await this.deps.routingEngine.plan(
|
|
@@ -491,7 +491,7 @@ export class ProxyRequestPreparer {
|
|
|
491
491
|
input.authorization.cliMappingOwnerId ?? input.authorization.id,
|
|
492
492
|
);
|
|
493
493
|
if (input.signal?.aborted)
|
|
494
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
494
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
495
495
|
const policyCandidates = plan.candidates.filter((candidate) =>
|
|
496
496
|
isModelAllowed(
|
|
497
497
|
input.authorization.snapshot,
|
|
@@ -501,12 +501,12 @@ export class ProxyRequestPreparer {
|
|
|
501
501
|
),
|
|
502
502
|
);
|
|
503
503
|
if (policyCandidates.length === 0)
|
|
504
|
-
throw new GatewayError("
|
|
504
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
505
505
|
model: input.model,
|
|
506
506
|
});
|
|
507
507
|
const candidates = policyCandidates.filter(input.eligible);
|
|
508
508
|
if (candidates.length === 0)
|
|
509
|
-
throw new GatewayError("capability_unsupported", 400, input.emptyMessage
|
|
509
|
+
throw new GatewayError("capability_unsupported", 400, `${input.emptyMessage} (request {requestId}). Know your tools — this one just isn't in the box 🧰`, {
|
|
510
510
|
model: input.model,
|
|
511
511
|
});
|
|
512
512
|
return {
|
|
@@ -191,15 +191,12 @@ function subscribeInFlightSnapshot(
|
|
|
191
191
|
store: { inFlightSnapshot(): InFlightSnapshot },
|
|
192
192
|
listener: (snapshot: InFlightSnapshot) => void,
|
|
193
193
|
): () => void {
|
|
194
|
-
const timers = new Set<ReturnType<typeof setTimeout>>();
|
|
195
194
|
const push = (): void => listener(store.inFlightSnapshot());
|
|
196
195
|
push();
|
|
197
196
|
const handle = setInterval(push, 1_000);
|
|
198
197
|
(handle as unknown as { unref?: () => void })?.unref?.();
|
|
199
198
|
return () => {
|
|
200
199
|
clearInterval(handle);
|
|
201
|
-
for (const t of timers) clearTimeout(t);
|
|
202
|
-
timers.clear();
|
|
203
200
|
};
|
|
204
201
|
}
|
|
205
202
|
|
|
@@ -259,7 +256,7 @@ export class ProxyRequestStateStore {
|
|
|
259
256
|
try {
|
|
260
257
|
if (controller && !controller.signal.aborted) {
|
|
261
258
|
controller.abort(
|
|
262
|
-
new GatewayError("deadline_exceeded", 504, "in-flight request exceeded its deadline"),
|
|
259
|
+
new GatewayError("deadline_exceeded", 504, "in-flight request exceeded its deadline (request {requestId}). Some things just take longer 🐢"),
|
|
263
260
|
);
|
|
264
261
|
}
|
|
265
262
|
} catch {
|
|
@@ -292,7 +289,7 @@ export class ProxyRequestStateStore {
|
|
|
292
289
|
const abortReason =
|
|
293
290
|
reason instanceof Error || reason instanceof DOMException
|
|
294
291
|
? reason
|
|
295
|
-
: new GatewayError("shutting_down", 503, "The gateway is shutting down, so this request cannot be served. Retry against a live instance.");
|
|
292
|
+
: new GatewayError("shutting_down", 503, "The gateway is shutting down, so this request cannot be served (request {requestId}). Retry against a live instance. Maintenance mode — back in a minute 🧘");
|
|
296
293
|
for (const controller of controllers) {
|
|
297
294
|
try {
|
|
298
295
|
if (!controller.signal.aborted) controller.abort(abortReason);
|
|
@@ -330,7 +327,7 @@ export class ProxyRequestStateStore {
|
|
|
330
327
|
new GatewayError(
|
|
331
328
|
"deadline_exceeded",
|
|
332
329
|
504,
|
|
333
|
-
"request deadline exceeded",
|
|
330
|
+
"request deadline exceeded (request {requestId}). Some things just take longer 🐢",
|
|
334
331
|
{},
|
|
335
332
|
"cartethyia",
|
|
336
333
|
),
|
|
@@ -418,7 +415,7 @@ export class ProxyRequestStateStore {
|
|
|
418
415
|
require(request: Request): ProxyRequestState {
|
|
419
416
|
const state = this.states.get(request);
|
|
420
417
|
if (!state)
|
|
421
|
-
throw new GatewayError("
|
|
418
|
+
throw new GatewayError("route_unavailable", 503, "The request was never registered, so it cannot be served (request {requestId}). Resend the request. The bouncer stepped out — resend shortly 🚪");
|
|
422
419
|
return state;
|
|
423
420
|
}
|
|
424
421
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
networkPools,
|
|
11
11
|
poolRoutingSettings,
|
|
12
12
|
providerAccounts,
|
|
13
|
+
providerPoolAccounts,
|
|
13
14
|
providerRoutingSettings,
|
|
14
15
|
providers,
|
|
15
16
|
tenantDisabledModels,
|
|
@@ -372,6 +373,17 @@ const POOL_COLUMNS = {
|
|
|
372
373
|
weight: networkPools.weight,
|
|
373
374
|
} as const;
|
|
374
375
|
|
|
376
|
+
const POOL_ACCOUNT_COLUMNS = {
|
|
377
|
+
id: providerPoolAccounts.id,
|
|
378
|
+
tenantId: providerPoolAccounts.tenantId,
|
|
379
|
+
networkPoolId: providerPoolAccounts.networkPoolId,
|
|
380
|
+
providerId: providerPoolAccounts.providerId,
|
|
381
|
+
label: providerPoolAccounts.label,
|
|
382
|
+
status: providerPoolAccounts.status,
|
|
383
|
+
cooldownUntil: providerPoolAccounts.cooldownUntil,
|
|
384
|
+
lastErrorCategory: providerPoolAccounts.lastErrorCategory,
|
|
385
|
+
} as const;
|
|
386
|
+
|
|
375
387
|
const DISABLED_MODEL_COLUMNS = {
|
|
376
388
|
tenantId: tenantDisabledModels.tenantId,
|
|
377
389
|
providerId: tenantDisabledModels.providerId,
|
|
@@ -414,7 +426,7 @@ class RouteCatalogRepository {
|
|
|
414
426
|
constructor(private readonly db: CartethyiaDatabase) {}
|
|
415
427
|
|
|
416
428
|
async loadRouteCatalogSnapshot(tenantId?: string): Promise<RouteCatalogSnapshotResult> {
|
|
417
|
-
const [providerRows, modelRows, accountRows, aliasRows, comboRows, routingRows, poolRows, disabledModelRows, cliMappingRows, cliMappingSettingRows, poolSettingRows, settingsRows] =
|
|
429
|
+
const [providerRows, modelRows, accountRows, aliasRows, comboRows, routingRows, poolRows, poolAccountRows, disabledModelRows, cliMappingRows, cliMappingSettingRows, poolSettingRows, settingsRows] =
|
|
418
430
|
await Promise.all([
|
|
419
431
|
this.db.select(PROVIDER_COLUMNS).from(providers).where(eq(providers.enabled, true)),
|
|
420
432
|
this.db.select(MODEL_COLUMNS).from(models),
|
|
@@ -427,6 +439,7 @@ class RouteCatalogRepository {
|
|
|
427
439
|
this.db.select(COMBO_COLUMNS).from(modelCombos),
|
|
428
440
|
this.db.select(ROUTING_COLUMNS).from(providerRoutingSettings),
|
|
429
441
|
this.db.select(POOL_COLUMNS).from(networkPools),
|
|
442
|
+
this.db.select(POOL_ACCOUNT_COLUMNS).from(providerPoolAccounts),
|
|
430
443
|
this.db.select(DISABLED_MODEL_COLUMNS).from(tenantDisabledModels),
|
|
431
444
|
this.db.select(CLI_MAPPING_COLUMNS).from(cliToolMappings),
|
|
432
445
|
this.db.select(CLI_MAPPING_SETTING_COLUMNS).from(cliToolSettings),
|
|
@@ -500,6 +513,28 @@ class RouteCatalogRepository {
|
|
|
500
513
|
activePoolsByTenant.set(pool.tenantId, list);
|
|
501
514
|
}
|
|
502
515
|
|
|
516
|
+
// Pool-as-account rows, grouped by tenant: one per (pool × ip-scoped
|
|
517
|
+
// provider). For a credential-less ip-scoped provider these are the
|
|
518
|
+
// account-equivalent routing entities — a spent egress is excluded from
|
|
519
|
+
// the plan exactly like a cooling account, and the candidate carries the
|
|
520
|
+
// pool-account's health (see the public-candidate fan-out below).
|
|
521
|
+
const poolAccountsByTenant = new Map<
|
|
522
|
+
string,
|
|
523
|
+
Array<{
|
|
524
|
+
id: string;
|
|
525
|
+
networkPoolId: string;
|
|
526
|
+
providerId: string;
|
|
527
|
+
status: string;
|
|
528
|
+
cooldownUntil: Date | null;
|
|
529
|
+
lastErrorCategory: string | null;
|
|
530
|
+
}>
|
|
531
|
+
>();
|
|
532
|
+
for (const poolAccount of poolAccountRows) {
|
|
533
|
+
const list = poolAccountsByTenant.get(poolAccount.tenantId) ?? [];
|
|
534
|
+
list.push(poolAccount);
|
|
535
|
+
poolAccountsByTenant.set(poolAccount.tenantId, list);
|
|
536
|
+
}
|
|
537
|
+
|
|
503
538
|
// Per-tenant pool selection strategy; an absent row reads as the
|
|
504
539
|
// `least_loaded` default at selection time.
|
|
505
540
|
const poolRouting: Record<string, PoolRoutingSetting> = {};
|
|
@@ -657,7 +692,67 @@ class RouteCatalogRepository {
|
|
|
657
692
|
if (accounts.length === 0) {
|
|
658
693
|
const providerRequiresAccount = requiresAccountById.get(model.providerId) ?? true;
|
|
659
694
|
if (!providerRequiresAccount) {
|
|
660
|
-
// Public models inherit active pools for each tenant that has active pools
|
|
695
|
+
// Public models inherit active pools for each tenant that has active pools.
|
|
696
|
+
// When the provider is ip-scoped (limits follow the egress address), the
|
|
697
|
+
// tenant's pool-account rows are the account-equivalent routing entities:
|
|
698
|
+
// one candidate per pool, carrying that pool-account's health, so a spent
|
|
699
|
+
// egress is excluded from the plan exactly like a cooling account.
|
|
700
|
+
const ownedTenantIds = [...poolAccountsByTenant.keys()].filter((tid) =>
|
|
701
|
+
(poolAccountsByTenant.get(tid) ?? []).some((pa) => pa.providerId === model.providerId),
|
|
702
|
+
);
|
|
703
|
+
let emittedPoolAccount = false;
|
|
704
|
+
for (const tenantOwnerId of ownedTenantIds) {
|
|
705
|
+
const poolAccounts = poolAccountsByTenant.get(tenantOwnerId) ?? [];
|
|
706
|
+
const tenantPoolAccounts = new Map<string, typeof poolAccounts>();
|
|
707
|
+
for (const pa of poolAccounts) {
|
|
708
|
+
if (pa.providerId !== model.providerId) continue;
|
|
709
|
+
const list = tenantPoolAccounts.get(pa.networkPoolId) ?? [];
|
|
710
|
+
list.push(pa);
|
|
711
|
+
tenantPoolAccounts.set(pa.networkPoolId, list);
|
|
712
|
+
}
|
|
713
|
+
if (tenantPoolAccounts.size === 0) continue;
|
|
714
|
+
for (const [poolId, pas] of tenantPoolAccounts) {
|
|
715
|
+
const first = pas[0];
|
|
716
|
+
if (!first) continue;
|
|
717
|
+
const tenantPools = resolveNetworkPools(model.providerId, tenantOwnerId);
|
|
718
|
+
if (!tenantPools) continue;
|
|
719
|
+
emittedPoolAccount = true;
|
|
720
|
+
const tenantCandidate: RouteCandidateWithHealth = {
|
|
721
|
+
provider_id: model.providerId,
|
|
722
|
+
model_id: model.modelId,
|
|
723
|
+
wire_family: model.wireFamily as WireFamily,
|
|
724
|
+
service_kind: model.serviceKind,
|
|
725
|
+
endpoint: model.endpointPath,
|
|
726
|
+
...(model.outputLimit == null ? {} : { output_limit: model.outputLimit }),
|
|
727
|
+
capability_profile: capabilityProfile,
|
|
728
|
+
tenant_id: tenantOwnerId,
|
|
729
|
+
// Public credential-less candidates have no account to own them.
|
|
730
|
+
owner_tenant_id: null,
|
|
731
|
+
requires_account: false as const,
|
|
732
|
+
network_pool_ids: [poolId],
|
|
733
|
+
network_pool_required: true,
|
|
734
|
+
network_pool_limits: tenantPools.limits,
|
|
735
|
+
network_pool_weights: tenantPools.weights,
|
|
736
|
+
...(tenantPools.routing ? { network_pool_routing: tenantPools.routing } : {}),
|
|
737
|
+
...providerLimitFor(model.providerId, tenantOwnerId),
|
|
738
|
+
...(first.status === "disabled"
|
|
739
|
+
? { health_status: "disabled" as const }
|
|
740
|
+
: first.status === "cooldown"
|
|
741
|
+
? {
|
|
742
|
+
health_status: "cooldown" as const,
|
|
743
|
+
cooldown_kind: hardCooldownCategory(first.lastErrorCategory) ? "hard" : "soft",
|
|
744
|
+
...(first.cooldownUntil && first.cooldownUntil.getTime() > Date.now()
|
|
745
|
+
? { cooldown_until: first.cooldownUntil.getTime() }
|
|
746
|
+
: {}),
|
|
747
|
+
}
|
|
748
|
+
: {}),
|
|
749
|
+
};
|
|
750
|
+
if (tenantId === undefined || tenantId === tenantOwnerId) {
|
|
751
|
+
candidates.push(tenantCandidate);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (emittedPoolAccount) continue;
|
|
661
756
|
for (const tId of configuredPoolTenants) {
|
|
662
757
|
const tenantPools = resolveNetworkPools(model.providerId, tId);
|
|
663
758
|
if (tenantPools) {
|
|
@@ -358,7 +358,7 @@ export function ambiguousModelError(bare: string, owners: string[]): GatewayErro
|
|
|
358
358
|
return new GatewayError(
|
|
359
359
|
"ambiguous_model",
|
|
360
360
|
400,
|
|
361
|
-
`'${bare}' matches more than one provider (${owners.join(", ")}). Qualify it as '${owners[0]}/${bare}', or add an alias to pick one
|
|
361
|
+
`'${bare}' matches more than one provider (${owners.join(", ")}). Qualify it as '${owners[0]}/${bare}', or add an alias to pick one. Too popular for its own good — qualify it 🧩`,
|
|
362
362
|
{
|
|
363
363
|
model: bare,
|
|
364
364
|
owners,
|
|
@@ -380,7 +380,7 @@ export function modelNotFoundError(requested: string, resolved: readonly string[
|
|
|
380
380
|
404,
|
|
381
381
|
`'${requested}' is not routed${
|
|
382
382
|
targets.length > 0 ? ` — it resolved to ${hint}, which no account serves` : ""
|
|
383
|
-
}. Enable the model on an account, or point the alias at one that exists
|
|
383
|
+
} (request {requestId}). Enable the model on an account, or point the alias at one that exists. Try another model — this one's off the menu 📚`,
|
|
384
384
|
{
|
|
385
385
|
model: requested,
|
|
386
386
|
...(targets.length > 0 ? { resolved_models: targets } : {}),
|
|
@@ -392,7 +392,7 @@ export function capabilityUnsupportedError(capability: string): GatewayError {
|
|
|
392
392
|
return new GatewayError(
|
|
393
393
|
"capability_unsupported",
|
|
394
394
|
400,
|
|
395
|
-
`No account serving this model supports ${capability}. Drop the ${capability} part of the request, or route to a model that accepts it
|
|
395
|
+
`No account serving this model supports ${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 🧰`,
|
|
396
396
|
{ capability },
|
|
397
397
|
);
|
|
398
398
|
}
|
|
@@ -424,8 +424,8 @@ export function capacityExhaustedError(context?: {
|
|
|
424
424
|
"provider_capacity_exhausted",
|
|
425
425
|
429,
|
|
426
426
|
onlyOne
|
|
427
|
-
? "The only account routing this model is at its concurrency limit. Retry shortly; if it persists, raise the provider's max-inflight or add another account."
|
|
428
|
-
: "Every account is at its concurrency limit. Retry shortly; if it persists, raise the provider's max-inflight or add an account.",
|
|
427
|
+
? "The only account routing this model is at its concurrency limit (request {requestId}). Retry shortly; if it persists, raise the provider's max-inflight or add another account. Even the machine needs a rest — chill 🦋"
|
|
428
|
+
: "Every account is at its concurrency limit (request {requestId}). Retry shortly; if it persists, raise the provider's max-inflight or add an account. Even the machine needs a rest — chill 🦋",
|
|
429
429
|
{
|
|
430
430
|
...(context?.providerId !== undefined ? { providerId: context.providerId } : {}),
|
|
431
431
|
...(context?.model !== undefined ? { model: context.model } : {}),
|
|
@@ -456,7 +456,7 @@ export function accountsUnavailableError(
|
|
|
456
456
|
return new GatewayError(
|
|
457
457
|
"accounts_unavailable",
|
|
458
458
|
503,
|
|
459
|
-
`No account in the pool can serve '${requested}' right now (${explainEligibilityReasons(distinct)}). Add an account, or wait for an operator to re-enable one
|
|
459
|
+
`No account in the pool can serve '${requested}' right now (request {requestId}) (${explainEligibilityReasons(distinct)}). Add an account, or wait for an operator to re-enable one. Every account is on a coffee break — try later ☕️`,
|
|
460
460
|
{ model: requested, reasons: distinct, candidate_count: reasons.length, gateway_hint: "no_healthy_account" },
|
|
461
461
|
);
|
|
462
462
|
}
|
|
@@ -492,7 +492,7 @@ export function accountsRateLimitedError(
|
|
|
492
492
|
return new GatewayError(
|
|
493
493
|
"accounts_rate_limited",
|
|
494
494
|
429,
|
|
495
|
-
`Every account serving '${requested}'${routeNote} is cooling down after hitting upstream limits. The request is fine — retry once the cooldown resets
|
|
495
|
+
`Every account serving '${requested}'${routeNote} is cooling down after hitting upstream limits (request {requestId}). The request is fine — retry once the cooldown resets. They're resting up 🌸`,
|
|
496
496
|
{
|
|
497
497
|
model: requested,
|
|
498
498
|
routed_model: routed,
|
|
@@ -624,7 +624,7 @@ export class RoutingEngine {
|
|
|
624
624
|
throw new GatewayError(
|
|
625
625
|
"invalid_request",
|
|
626
626
|
400,
|
|
627
|
-
"This tool result belongs to a Cursor account that is no longer routable. Reconnect the account, or resend the complete conversation.",
|
|
627
|
+
"This tool result belongs to a Cursor account that is no longer routable (request {requestId}). Reconnect the account, or resend the complete conversation. The session went cold — reconnect or resend 🔌",
|
|
628
628
|
{
|
|
629
629
|
providerId: affinity.providerId,
|
|
630
630
|
accountId: affinity.providerAccountId,
|
|
@@ -652,7 +652,7 @@ export class RoutingEngine {
|
|
|
652
652
|
throw new GatewayError(
|
|
653
653
|
"invalid_request",
|
|
654
654
|
400,
|
|
655
|
-
"This tool result belongs to an account that is no longer routable. Reconnect the account, or resend the complete conversation.",
|
|
655
|
+
"This tool result belongs to an account that is no longer routable (request {requestId}). Reconnect the account, or resend the complete conversation. The session went cold — reconnect or resend 🔌",
|
|
656
656
|
{
|
|
657
657
|
providerId: affinity.providerId,
|
|
658
658
|
accountId: affinity.providerAccountId,
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
import { GatewayError, type GatewayErrorCode } from "../protocol/gateway-error";
|
|
17
17
|
|
|
18
18
|
export interface ShutdownNotice {
|
|
19
|
-
readonly code: Extract<GatewayErrorCode, "shutting_down"
|
|
19
|
+
readonly code: Extract<GatewayErrorCode, "shutting_down">;
|
|
20
20
|
readonly message: string;
|
|
21
|
+
readonly reason?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -30,9 +31,17 @@ export const UPDATE_SHUTDOWN_REASON = "update";
|
|
|
30
31
|
|
|
31
32
|
export function shutdownNotice(reason: string | undefined): ShutdownNotice {
|
|
32
33
|
if (reason === UPDATE_SHUTDOWN_REASON) {
|
|
33
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
code: "shutting_down",
|
|
36
|
+
message:
|
|
37
|
+
"Service is shutting down for an update; it will be back in about a minute (request {requestId}). Maintenance mode — back in a minute 🧘",
|
|
38
|
+
reason: "restart_for_update",
|
|
39
|
+
};
|
|
34
40
|
}
|
|
35
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
code: "shutting_down",
|
|
43
|
+
message: "Service is shutting down (request {requestId}). Come back shortly 🧘",
|
|
44
|
+
};
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
/**
|
|
@@ -47,7 +56,12 @@ export function shutdownNotice(reason: string | undefined): ShutdownNotice {
|
|
|
47
56
|
*/
|
|
48
57
|
export function shutdownError(reason: string | undefined): GatewayError {
|
|
49
58
|
const notice = shutdownNotice(reason);
|
|
50
|
-
return new GatewayError(
|
|
59
|
+
return new GatewayError(
|
|
60
|
+
notice.code,
|
|
61
|
+
503,
|
|
62
|
+
notice.message,
|
|
63
|
+
notice.reason === undefined ? {} : { reason: notice.reason },
|
|
64
|
+
);
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/**
|
|
@@ -62,7 +76,5 @@ export function shutdownError(reason: string | undefined): GatewayError {
|
|
|
62
76
|
*/
|
|
63
77
|
export function drainAbortReason(reason: unknown): GatewayError | undefined {
|
|
64
78
|
if (!(reason instanceof GatewayError)) return undefined;
|
|
65
|
-
return reason.code === "shutting_down"
|
|
66
|
-
? reason
|
|
67
|
-
: undefined;
|
|
79
|
+
return reason.code === "shutting_down" ? reason : undefined;
|
|
68
80
|
}
|
|
@@ -166,7 +166,7 @@ export class CompletionAdapter implements SurfaceAdapter {
|
|
|
166
166
|
throw new GatewayError(
|
|
167
167
|
"invalid_request",
|
|
168
168
|
400,
|
|
169
|
-
"completion requests require a non-empty prompt",
|
|
169
|
+
"completion requests require a non-empty prompt (request {requestId}). Check the shape of what you sent — then resend 🪞",
|
|
170
170
|
);
|
|
171
171
|
const controls: GenerationControls = {};
|
|
172
172
|
for (const field of ["temperature", "top_p", "n", "seed"] as const)
|