cartethyia 2.7.2 → 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/.env.example +2 -0
- package/dist/dashboard/assets/{BannedPage-XaUmiBrC.js → BannedPage-ChG5svFb.js} +1 -1
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-CXGS_Ct-.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-BKtAPYVF.js → HealthEventsModal-DzSWzE_j.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BdmRLnq2.js → LoginPage-U6AKaJcF.js} +1 -1
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BH8Tehdi.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-f0DE1h0-.js +1 -0
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-Dqf4zpS-.js → SortableList-ClzpwyET.js} +1 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Byj2lAEm.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/arrow-up-59ms1iiT.js +1 -0
- package/dist/dashboard/assets/arrow-up-59ms1iiT.js.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-DC2L43Mj.js → badge-D9CS6dZk.js} +1 -1
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BBVcb0J3.js → banner-DympBNq4.js} +1 -1
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/{card-Cca1XC6G.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-DUzEbynq.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-DFvWhwTa.js → dialog-8fiE6tec.js} +1 -1
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js +1 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/{entry-Bqy8RFvM.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/{concurrency-BDF0ok7y.js → fingerprint-pattern-BWuHtbW4.js} +1 -1
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CWWZ-uCZ.js → formatters-D-MTJxMr.js} +1 -1
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-C9e8Ts7e.js → github-badge-CPK47xQx.js} +1 -1
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/{index-DFVFrnWW.js → index-DBNPOfhV.js} +2 -2
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BFUcvllj.js → inline-BT0zntEr.js} +1 -1
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/{input-C_ILAvBb.js → input-Da2ZViWg.js} +1 -1
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CBIyT6Rt.js → layout-BXNeLw6M.js} +1 -1
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-ebJ_-Nsb.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-sMD965oO.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-BO9dKaFr.js → numbers-CEagj6ZZ.js} +2 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CfL_Zdoh.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-DBNVLdUu.js → select-BIYIBkMU.js} +2 -2
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-2-Ds9DQp0t.js +1 -0
- package/dist/dashboard/assets/settings-2-Ds9DQp0t.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-EMrOMt4I.js → stack-DfgnFDWY.js} +1 -1
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/{state-CWoo7ty5.js → state-CkfdYWel.js} +1 -1
- package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
- package/dist/dashboard/assets/{switch-CCjT6IkF.js → switch-SYm237HG.js} +1 -1
- package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-DjkLHUcE.js → useMutation-BQqCRNIi.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
- package/dist/dashboard/index.html +3 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +118 -115
- package/migrations/full-postgres/0026_add_qoder_native_identity.sql +20 -0
- package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +21 -0
- package/migrations/full-postgres/0028_add_family_template_key_telemetry.sql +40 -0
- package/migrations/full-postgres/0029_null_share_child_policy_columns.sql +38 -0
- package/migrations/full-postgres/0030_add_api_key_max_children.sql +10 -0
- package/migrations/full-postgres/0031_tenant_system_prompts.sql +16 -0
- 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/0026_add_qoder_native_identity.sql +15 -0
- package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +21 -0
- package/migrations/lite-sqlite3/0028_add_family_template_key_telemetry.sql +40 -0
- package/migrations/lite-sqlite3/0029_null_share_child_policy_columns.sql +38 -0
- package/migrations/lite-sqlite3/0030_add_api_key_max_children.sql +10 -0
- package/migrations/lite-sqlite3/0031_tenant_system_prompts.sql +10 -0
- 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/bucket-spend.ts +7 -7
- package/src/console/api-keys/contracts.ts +475 -410
- package/src/console/api-keys/routes.ts +211 -23
- 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 +14 -17
- package/src/console/backup/routes.ts +43 -11
- package/src/console/backup/service.ts +11 -6
- package/src/console/backup/store.ts +384 -88
- 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/dashboard-assets.ts +16 -1
- package/src/console/data-management/routes.ts +3 -3
- package/src/console/domain-registration.ts +4 -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 +34 -2
- package/src/console/quota/account-quota.ts +4 -1
- package/src/console/quota/account-test-egress.ts +4 -4
- package/src/console/quota/native-identity.ts +189 -0
- package/src/console/remote-support/contracts.ts +12 -6
- package/src/console/remote-support/service.ts +2 -2
- 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 +51 -18
- package/src/console/share/share-stats.ts +158 -49
- package/src/console/share/share-usage.ts +24 -7
- package/src/console/shared/read-through-cache.ts +38 -2
- package/src/console/system-prompt/contracts.ts +102 -0
- package/src/console/system-prompt/routes.ts +165 -0
- package/src/console/updates/routes.ts +7 -4
- package/src/main.ts +11 -0
- 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 +437 -22
- package/src/observability/telemetry-status.ts +1 -1
- package/src/persistence/db-pg.ts +42 -0
- package/src/persistence/db-sqlite.ts +7 -2
- package/src/persistence/readiness.ts +18 -6
- package/src/persistence/schema.ts +162 -1
- package/src/persistence/stores/api-key-store.ts +849 -581
- package/src/persistence/stores/share-store.ts +65 -13
- package/src/persistence/stores/telemetry-store.ts +58 -2
- package/src/persistence/system-prompt-store.ts +104 -0
- 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/reasoning.ts +65 -1
- package/src/protocol/request/chat.ts +1 -0
- 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 +33 -47
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +30 -2
- 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 +21 -17
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -2
- 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 +8 -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 +26 -7
- package/src/providers/integrations/grok/grok.ts +2 -6
- package/src/providers/integrations/hermes.ts +5 -1
- package/src/providers/integrations/inferhub.ts +4 -11
- package/src/providers/integrations/kilo/kilo.ts +4 -0
- 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/openrouter/provider.ts +4 -0
- 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 +242 -51
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +157 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +12 -3
- package/src/providers/integrations/qoder/qoder-realm.ts +15 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +287 -0
- package/src/providers/integrations/qoder/qoder-virtual.ts +2 -2
- package/src/providers/integrations/qoder/qoder.ts +192 -24
- 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/account-error-classification.ts +6 -8
- package/src/providers/operations/account-health-service.ts +14 -16
- package/src/providers/operations/client-versions.ts +37 -22
- 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 +55 -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/access-control.ts +19 -0
- package/src/security/admission/reasons.ts +17 -17
- package/src/security/api-key-auth.ts +18 -5
- 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 +38 -9
- 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/streaming/stream.ts +16 -1
- package/src/transport/dispatch/systemone.ts +8 -8
- package/src/transport/dispatch/token-count.ts +9 -9
- package/src/transport/dispatch/upstream.ts +10 -0
- 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 +37 -21
- package/src/transport/request/state.ts +4 -7
- package/src/transport/request/system-prompt-injection.ts +89 -0
- package/src/transport/request/tenant-preferences.ts +43 -0
- package/src/transport/routing/admission.ts +13 -3
- package/src/transport/routing/route-catalog.ts +104 -2
- package/src/transport/routing/route-model.ts +60 -7
- package/src/transport/routing/router.ts +306 -256
- 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/src/workers/daily-checkin.ts +2 -0
- package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +0 -4
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js +0 -1
- package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +0 -1
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js +0 -56
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js +0 -1
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +0 -2
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/assert-67xXH5rf.js +0 -1
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js +0 -1
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CFM2sMX1.js +0 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/entry-ClCMCWWQ.js +0 -4
- package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
- package/dist/dashboard/assets/entry-_xHo5x6r.js +0 -1
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/live-D0mm--ME.js +0 -1
- package/dist/dashboard/assets/live-D0mm--ME.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-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.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-BnIpi1IJ.js +0 -1
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
- package/dist/dashboard/assets/settings-BAFhDgX8.js +0 -1
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
- /package/dist/dashboard/assets/{app-BaWd0puM.js → app-BayTTy01.js} +0 -0
- /package/dist/dashboard/assets/{app-BaWd0puM.js.gz → app-BayTTy01.js.gz} +0 -0
|
@@ -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 {
|
|
@@ -2,6 +2,7 @@ import { GatewayError } from "../../protocol/gateway-error";
|
|
|
2
2
|
import type { CanonicalRequest, ServiceKind } from "../../protocol/canonical-model";
|
|
3
3
|
import { toolResultParts } from "../../protocol/canonical-model";
|
|
4
4
|
import type { ApiKeyAdmissionService } from "../../security/admission/service";
|
|
5
|
+
import type { CallerIdentity } from "../../security/access-control";
|
|
5
6
|
import type { RouteCandidate as RouteCandidate, InMemoryRouteSnapshotService, RoutePlan } from "../routing/route-model";
|
|
6
7
|
import type { RouteContinuationAffinity } from "../routing/route-model";
|
|
7
8
|
import { resolveAliasTarget, type RoutingEngine } from "../routing/router";
|
|
@@ -80,6 +81,8 @@ export function resolveContinuationAffinity(
|
|
|
80
81
|
apiKeyId: authorization.id,
|
|
81
82
|
};
|
|
82
83
|
let affinity: RouteContinuationAffinity | undefined;
|
|
84
|
+
let anyResolved = false;
|
|
85
|
+
let allResolved = true;
|
|
83
86
|
for (const callId of ids) {
|
|
84
87
|
// The registry resolves by tenant + API key + provider + account, so a call
|
|
85
88
|
// id replayed from another key or tenant resolves nothing.
|
|
@@ -91,22 +94,32 @@ export function resolveContinuationAffinity(
|
|
|
91
94
|
// through the ids that still resolve; when none do, the ordinary routing
|
|
92
95
|
// path decides (a Cursor adapter may rebase the full history into a
|
|
93
96
|
// fresh Run).
|
|
97
|
+
allResolved = false;
|
|
94
98
|
continue;
|
|
95
99
|
}
|
|
96
100
|
if (affinity !== undefined && affinity.providerAccountId !== record.providerAccountId) {
|
|
97
101
|
throw new GatewayError(
|
|
98
102
|
"invalid_request",
|
|
99
103
|
400,
|
|
100
|
-
"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 🔌",
|
|
101
105
|
{ callIds: [...ids] },
|
|
102
106
|
);
|
|
103
107
|
}
|
|
108
|
+
anyResolved = true;
|
|
104
109
|
affinity = {
|
|
105
110
|
providerId: record.providerId,
|
|
106
111
|
providerAccountId: record.providerAccountId,
|
|
112
|
+
rebaseable: false,
|
|
107
113
|
};
|
|
108
114
|
}
|
|
109
|
-
|
|
115
|
+
if (affinity !== undefined) {
|
|
116
|
+
// Rebaseable when every supplied call id resolved to a live pending call
|
|
117
|
+
// (so none is owed to another account) — or when none resolved and the
|
|
118
|
+
// whole history is present. In both cases the conversation is complete and
|
|
119
|
+
// may travel as history to a fresh Run.
|
|
120
|
+
return { ...affinity, rebaseable: anyResolved ? allResolved : true };
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
/** Looks up one pending call across every provider that records continuations. */
|
|
@@ -137,6 +150,8 @@ export interface PreparedProxyRequest {
|
|
|
137
150
|
* second account and pool slot.
|
|
138
151
|
*/
|
|
139
152
|
readonly continuationAffinity?: RouteContinuationAffinity;
|
|
153
|
+
/** Single "who is speaking" identity, derived at auth. */
|
|
154
|
+
readonly caller: CallerIdentity;
|
|
140
155
|
readonly estimatedInputTokens: number;
|
|
141
156
|
readonly estimatedOutputTokens: number;
|
|
142
157
|
readonly deadlineMs: number;
|
|
@@ -174,9 +189,9 @@ export class ProxyRequestPreparer {
|
|
|
174
189
|
}): Promise<PreparedProxyRequest> {
|
|
175
190
|
const { canonicalRequest: initialRequest, authorization, signal } = input;
|
|
176
191
|
if (signal?.aborted)
|
|
177
|
-
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 🙃");
|
|
178
193
|
if (!initialRequest.model || initialRequest.model.trim().length === 0) {
|
|
179
|
-
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 🪞", {
|
|
180
195
|
field: "model",
|
|
181
196
|
});
|
|
182
197
|
}
|
|
@@ -227,13 +242,13 @@ export class ProxyRequestPreparer {
|
|
|
227
242
|
!allowedForKey
|
|
228
243
|
)
|
|
229
244
|
throw new GatewayError(
|
|
230
|
-
"
|
|
231
|
-
|
|
232
|
-
`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 🔒`,
|
|
233
248
|
{ model: request.model, required_prefix: authorization.modelPrefix },
|
|
234
249
|
);
|
|
235
250
|
if (!allowedForKey) {
|
|
236
|
-
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 🔒", {
|
|
237
252
|
model: request.model,
|
|
238
253
|
});
|
|
239
254
|
}
|
|
@@ -292,7 +307,7 @@ export class ProxyRequestPreparer {
|
|
|
292
307
|
throw new GatewayError(
|
|
293
308
|
"capability_unsupported",
|
|
294
309
|
400,
|
|
295
|
-
"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 🧰",
|
|
296
311
|
{ model: request.model },
|
|
297
312
|
);
|
|
298
313
|
const policyCandidates = plan.candidates.filter((candidate) =>
|
|
@@ -304,7 +319,7 @@ export class ProxyRequestPreparer {
|
|
|
304
319
|
),
|
|
305
320
|
);
|
|
306
321
|
if (policyCandidates.length === 0)
|
|
307
|
-
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 🔒", {
|
|
308
323
|
model: request.model,
|
|
309
324
|
});
|
|
310
325
|
if (policyCandidates.length !== plan.candidates.length)
|
|
@@ -326,7 +341,7 @@ export class ProxyRequestPreparer {
|
|
|
326
341
|
throw new GatewayError(
|
|
327
342
|
"capability_unsupported",
|
|
328
343
|
400,
|
|
329
|
-
`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 🧰`,
|
|
330
345
|
{ model: request.model, service_kind: kind, ...(route === undefined ? {} : { route }) },
|
|
331
346
|
);
|
|
332
347
|
}
|
|
@@ -351,7 +366,7 @@ export class ProxyRequestPreparer {
|
|
|
351
366
|
}
|
|
352
367
|
}
|
|
353
368
|
if (signal?.aborted)
|
|
354
|
-
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 🙃");
|
|
355
370
|
const eligible = plan.candidates;
|
|
356
371
|
// The buddy gateway rejects partial tool rounds outright (`11148`) where
|
|
357
372
|
// the generic synthesis policy would insert a placeholder result, so a
|
|
@@ -386,7 +401,7 @@ export class ProxyRequestPreparer {
|
|
|
386
401
|
effectiveRequest = sanitizeRequestToolIds(effectiveRequest);
|
|
387
402
|
}
|
|
388
403
|
if (signal?.aborted)
|
|
389
|
-
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 🙃");
|
|
390
405
|
// Project against the chosen candidate, not the intersection of every
|
|
391
406
|
// candidate in the plan. Still required for generation-control stripping
|
|
392
407
|
// even on the no-tools path — keep it, but it is cheap vs message clones.
|
|
@@ -407,6 +422,7 @@ export class ProxyRequestPreparer {
|
|
|
407
422
|
degradedCapabilities: degraded,
|
|
408
423
|
plan,
|
|
409
424
|
...(continuationAffinity === undefined ? {} : { continuationAffinity }),
|
|
425
|
+
caller: authorization.caller,
|
|
410
426
|
estimatedInputTokens,
|
|
411
427
|
estimatedOutputTokens,
|
|
412
428
|
deadlineMs: input.deadlineMs,
|
|
@@ -432,7 +448,7 @@ export class ProxyRequestPreparer {
|
|
|
432
448
|
readonly emptyMessage: string;
|
|
433
449
|
}): Promise<PreparedNativeRequest> {
|
|
434
450
|
if (input.signal?.aborted)
|
|
435
|
-
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 🙃");
|
|
436
452
|
const snapshot = await this.deps.snapshotService.getSnapshot();
|
|
437
453
|
const allowCliMappings = allowsCliToolMappings(input.authorization.scopes, {
|
|
438
454
|
...(input.clientUserAgent === undefined ? {} : { userAgent: input.clientUserAgent }),
|
|
@@ -457,13 +473,13 @@ export class ProxyRequestPreparer {
|
|
|
457
473
|
!allowedForKey
|
|
458
474
|
)
|
|
459
475
|
throw new GatewayError(
|
|
460
|
-
"
|
|
461
|
-
|
|
462
|
-
`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 🔒`,
|
|
463
479
|
{ model: input.model, required_prefix: input.authorization.modelPrefix },
|
|
464
480
|
);
|
|
465
481
|
if (!allowedForKey)
|
|
466
|
-
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 🔒", {
|
|
467
483
|
model: input.model,
|
|
468
484
|
});
|
|
469
485
|
const plan = await this.deps.routingEngine.plan(
|
|
@@ -475,7 +491,7 @@ export class ProxyRequestPreparer {
|
|
|
475
491
|
input.authorization.cliMappingOwnerId ?? input.authorization.id,
|
|
476
492
|
);
|
|
477
493
|
if (input.signal?.aborted)
|
|
478
|
-
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 🙃");
|
|
479
495
|
const policyCandidates = plan.candidates.filter((candidate) =>
|
|
480
496
|
isModelAllowed(
|
|
481
497
|
input.authorization.snapshot,
|
|
@@ -485,12 +501,12 @@ export class ProxyRequestPreparer {
|
|
|
485
501
|
),
|
|
486
502
|
);
|
|
487
503
|
if (policyCandidates.length === 0)
|
|
488
|
-
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 🔒", {
|
|
489
505
|
model: input.model,
|
|
490
506
|
});
|
|
491
507
|
const candidates = policyCandidates.filter(input.eligible);
|
|
492
508
|
if (candidates.length === 0)
|
|
493
|
-
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 🧰`, {
|
|
494
510
|
model: input.model,
|
|
495
511
|
});
|
|
496
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
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant system-prompt injection at the canonical level.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside `applyTenantPreferences`, after reasoning/thinking shaping and
|
|
5
|
+
* before RTK/PonyTail, so the injected text composes with (and is then
|
|
6
|
+
* appended-to by) the compression directive. Wire-agnostic: one transform
|
|
7
|
+
* covers messages/chat/responses/gemini/codex/kiro; provider-native
|
|
8
|
+
* injection (Claude identity/billing prepend, buddy fixed system turn,
|
|
9
|
+
* antigravity identity) happens later, in the adapters, and is untouched.
|
|
10
|
+
*
|
|
11
|
+
* Resolution is most-specific-wins: model > provider > global, where a
|
|
12
|
+
* provider either follows the tenant's global prompt or carries a custom one.
|
|
13
|
+
* Failure is non-fatal — an unusable configuration must not fail a
|
|
14
|
+
* dispatchable request.
|
|
15
|
+
*/
|
|
16
|
+
import type { CanonicalRequest } from "../../protocol/canonical-model";
|
|
17
|
+
import type { ServiceKind } from "../../protocol/canonical-model";
|
|
18
|
+
import type {
|
|
19
|
+
PromptSetting,
|
|
20
|
+
TenantSystemPrompts,
|
|
21
|
+
} from "../../console/system-prompt/contracts";
|
|
22
|
+
import {
|
|
23
|
+
systemPromptModelKey,
|
|
24
|
+
} from "../../console/system-prompt/contracts";
|
|
25
|
+
|
|
26
|
+
/** The route facts the resolution needs. */
|
|
27
|
+
export interface SystemPromptRoute {
|
|
28
|
+
readonly providerId: string;
|
|
29
|
+
readonly modelId: string;
|
|
30
|
+
readonly serviceKind: ServiceKind;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolves the effective prompt for a route: the most-specific enabled scope
|
|
35
|
+
* wins. A provider scope honours its `followGlobal` toggle; model scope is
|
|
36
|
+
* always explicit. Returns `undefined` when nothing applies.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveTenantSystemPrompt(
|
|
39
|
+
prompts: TenantSystemPrompts | undefined,
|
|
40
|
+
route: Pick<SystemPromptRoute, "providerId" | "modelId">,
|
|
41
|
+
): PromptSetting | undefined {
|
|
42
|
+
if (prompts === undefined) return undefined;
|
|
43
|
+
const model = prompts.models[systemPromptModelKey(route.providerId, route.modelId)];
|
|
44
|
+
if (model !== undefined && model.enabled) return model;
|
|
45
|
+
const provider = prompts.providers[route.providerId];
|
|
46
|
+
if (provider !== undefined) {
|
|
47
|
+
if (provider.followGlobal) {
|
|
48
|
+
return prompts.global !== null && prompts.global.enabled ? prompts.global : undefined;
|
|
49
|
+
}
|
|
50
|
+
if (provider.setting !== undefined && provider.setting.enabled) return provider.setting;
|
|
51
|
+
}
|
|
52
|
+
return prompts.global !== null && prompts.global.enabled ? prompts.global : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Marker text guard so retries never double-inject. */
|
|
56
|
+
function carriesText(system: readonly { kind: string }[] | undefined, text: string): boolean {
|
|
57
|
+
return (system ?? []).some((part) => part.kind === "text" && (part as { text?: string }).text === text);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Applies the resolved tenant prompt to the canonical request. Appends after
|
|
62
|
+
* existing system content (or replaces the caller's system content for
|
|
63
|
+
* `override` — provider-native identity injection later re-adds the fixed
|
|
64
|
+
* provider prompt, so override is never a bare wire).
|
|
65
|
+
*/
|
|
66
|
+
export function applyInjectSystemPrompt(
|
|
67
|
+
request: CanonicalRequest,
|
|
68
|
+
prompts: TenantSystemPrompts | undefined,
|
|
69
|
+
route: SystemPromptRoute,
|
|
70
|
+
): CanonicalRequest {
|
|
71
|
+
// Non-chat native protocols have no system surface to inject into.
|
|
72
|
+
if (route.serviceKind !== "llm") return request;
|
|
73
|
+
const setting = resolveTenantSystemPrompt(prompts, route);
|
|
74
|
+
if (setting === undefined || setting.text.length === 0) return request;
|
|
75
|
+
|
|
76
|
+
const text = setting.text;
|
|
77
|
+
// `override` replaces only the CALLER's system content: the adapters later
|
|
78
|
+
// prepend their mandatory identity/billing blocks (claude, antigravity) or
|
|
79
|
+
// install their fixed system turn (buddy), so the wire always keeps the
|
|
80
|
+
// provider-native prompt. For wrapped-injection providers the override
|
|
81
|
+
// cannot survive the replace at all — the identity must stay the opener —
|
|
82
|
+
// so it degrades to append there.
|
|
83
|
+
const existing = request.system ?? [];
|
|
84
|
+
if (setting.mode === "override" && existing.length > 0) {
|
|
85
|
+
return { ...request, system: [{ kind: "text", text }] };
|
|
86
|
+
}
|
|
87
|
+
if (carriesText(existing, text)) return request;
|
|
88
|
+
return { ...request, system: [...existing, { kind: "text", text }] };
|
|
89
|
+
}
|
|
@@ -12,9 +12,42 @@ import type { CanonicalRequest } from "../../protocol/canonical-model";
|
|
|
12
12
|
import type { PreparedProxyRequest } from "./preparer";
|
|
13
13
|
import { normalizeThinkingConfig } from "../../protocol/translation/thinking";
|
|
14
14
|
import { compressRequest } from "./rtk/compress-request";
|
|
15
|
+
import { applyInjectSystemPrompt } from "./system-prompt-injection";
|
|
15
16
|
import { preferencesReaderFor } from "../../persistence/tenant-preferences";
|
|
17
|
+
import {
|
|
18
|
+
readTenantSystemPrompts,
|
|
19
|
+
type StoredTenantSystemPrompts,
|
|
20
|
+
} from "../../persistence/system-prompt-store";
|
|
21
|
+
import { currentSettingsRevision } from "../../persistence/settings-revision";
|
|
22
|
+
import { TtlCache } from "../../persistence/ttl-cache";
|
|
16
23
|
import { resolveUpstreamTimeoutMs } from "../../config/timeouts";
|
|
17
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Revision-keyed cached read of the tenant's system-prompt configuration, the
|
|
27
|
+
* same convergence mechanism as the runtime-preferences reader: a mutation
|
|
28
|
+
* bumps the settings revision, which changes every cache key, so writes are
|
|
29
|
+
* visible to the dispatch path immediately without a per-request round trip.
|
|
30
|
+
*/
|
|
31
|
+
const systemPromptCache = new TtlCache<StoredTenantSystemPrompts>({
|
|
32
|
+
ttlMs: 5_000,
|
|
33
|
+
maxEntries: 128,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
async function readTenantSystemPromptsCached(
|
|
37
|
+
db: CartethyiaDatabase,
|
|
38
|
+
tenantId: string,
|
|
39
|
+
): Promise<StoredTenantSystemPrompts> {
|
|
40
|
+
const cacheKey = `${tenantId}:${currentSettingsRevision()}`;
|
|
41
|
+
const cached = systemPromptCache.get(cacheKey);
|
|
42
|
+
if (cached !== undefined) return cached;
|
|
43
|
+
const prompts = await readTenantSystemPrompts(db, tenantId);
|
|
44
|
+
systemPromptCache.set(cacheKey, prompts);
|
|
45
|
+
return prompts;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Cached read for the dispatch path (same revision-keyed cache). */
|
|
49
|
+
export { readTenantSystemPromptsCached as readTenantSystemPromptsForDispatch };
|
|
50
|
+
|
|
18
51
|
/**
|
|
19
52
|
* Re-arms the request deadline from the tenant's timeout override.
|
|
20
53
|
*
|
|
@@ -91,6 +124,16 @@ export async function applyTenantPreferences(
|
|
|
91
124
|
const next = normalizeThinkingConfig(canonicalRequest);
|
|
92
125
|
if (next !== canonicalRequest) canonicalRequest = next;
|
|
93
126
|
}
|
|
127
|
+
// Tenant system-prompt injection runs before compression: the PonyTail
|
|
128
|
+
// directive must stay the tail of the system content, and the injected
|
|
129
|
+
// prompt is itself stable per scope, so the prefix-cache invariant holds.
|
|
130
|
+
const prompts = await readTenantSystemPromptsCached(db, tenantId);
|
|
131
|
+
const candidate = prepared.candidate;
|
|
132
|
+
canonicalRequest = applyInjectSystemPrompt(canonicalRequest, prompts, {
|
|
133
|
+
providerId: candidate.provider_id,
|
|
134
|
+
modelId: candidate.model_id,
|
|
135
|
+
serviceKind: candidate.service_kind ?? "llm",
|
|
136
|
+
});
|
|
94
137
|
if (prefs) {
|
|
95
138
|
// When the routed model is Responses (codex/openai/muse-spark) and the
|
|
96
139
|
// client hit a Chat surface, force the configured summary mode so the
|
|
@@ -17,15 +17,25 @@ import type {
|
|
|
17
17
|
/**
|
|
18
18
|
* The admission bucket one candidate draws from.
|
|
19
19
|
*
|
|
20
|
+
* Bucket identity is `provider:model[:account][:ownerTenant]`:
|
|
21
|
+
* - A candidate with no account (credential-less provider) buckets per
|
|
22
|
+
* provider:model.
|
|
23
|
+
* - A **shared** account (`owner_tenant_id` null) is one bucket across all
|
|
24
|
+
* tenants — the account is pool-wide, so its concurrency ceiling is global.
|
|
25
|
+
* - A **tenant-owned** account is scoped to its owner tenant, so two tenants
|
|
26
|
+
* cannot drain each other's ceiling on an account only one of them owns.
|
|
27
|
+
*
|
|
20
28
|
* Exported so the router can order candidates by the same counters admission
|
|
21
29
|
* enforces against: a second copy of this format would drift and quietly
|
|
22
30
|
* deprioritize the wrong accounts.
|
|
23
31
|
*/
|
|
24
32
|
export function admissionKey(candidate: RouteCandidate): string {
|
|
25
33
|
const base = `${candidate.provider_id}:${candidate.model_id}`;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
if (candidate.provider_account_id === undefined) return base;
|
|
35
|
+
const account = `${base}:${candidate.provider_account_id}`;
|
|
36
|
+
return candidate.owner_tenant_id === undefined || candidate.owner_tenant_id === null
|
|
37
|
+
? account
|
|
38
|
+
: `${account}:${candidate.owner_tenant_id}`;
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
/**
|