cartethyia 2.7.9 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +6 -5
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
- package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
- package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
- package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
- package/dist/dashboard/assets/providers/sensenova.png +0 -0
- package/dist/dashboard/assets/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
- package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +11 -14
- package/src/boot/dependencies.ts +5 -7
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/providers.ts +6 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +28 -8
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -4
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/live.ts +2 -2
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/contracts.ts +9 -3
- package/src/console/providers/catalog/model-operations.ts +2 -3
- package/src/console/providers/catalog/provider-operations.ts +1 -1
- package/src/console/providers/catalog/routes.ts +3 -2
- package/src/console/providers/catalog/store.ts +1914 -1902
- package/src/console/providers/detail/routes.ts +1 -0
- package/src/console/providers/detail/store.ts +4 -0
- package/src/console/providers/oauth/callback-listener.ts +30 -11
- package/src/console/providers/oauth/device-egress.ts +123 -0
- package/src/console/providers/oauth/routes.ts +123 -16
- package/src/console/quota/account-quota-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +11 -23
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +32 -47
- package/src/console/routing/pools/probe-verdict.ts +2 -2
- package/src/console/routing/pools/relay-deploy.ts +1 -1
- package/src/console/routing/pools/store.ts +4 -4
- package/src/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- package/src/network/client.ts +67 -0
- package/src/network/egress/bidi-session.ts +322 -0
- package/src/network/egress/dial.ts +201 -0
- package/src/network/egress/factory.ts +191 -0
- package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
- package/src/network/egress/tls-profile.ts +123 -0
- package/src/network/egress/ws.ts +175 -0
- package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
- package/src/network/{types.ts → pool/agent-config.ts} +1 -1
- package/src/network/pool/agent.ts +2 -2
- package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
- package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
- package/src/network/pool/resolver.ts +10 -849
- package/src/network/pool/selector.ts +184 -41
- package/src/network/ssrf.ts +7 -7
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
- package/src/observability/event-log.ts +7 -361
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +16 -26
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +17 -0
- package/src/persistence/ttl-cache.ts +21 -3
- package/src/protocol/codecs.ts +59 -2
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +34 -22
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/messages.ts +34 -7
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +15 -0
- package/src/protocol/upstream/send-claude-messages.ts +2 -2
- package/src/providers/adapters/compatible-adapter.ts +76 -12
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +105 -11
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -758
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +137 -36
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +20 -6
- package/src/providers/discovery/probing-service.ts +22 -2
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +12 -12
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
- package/src/providers/integrations/buddy/headers.ts +311 -0
- package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
- package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +5 -6
- package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
- package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
- package/src/providers/integrations/clixad/provider.ts +145 -0
- package/src/providers/integrations/codex/codex-oauth.ts +8 -5
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
- package/src/providers/integrations/cursor/cursor-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
- package/src/providers/integrations/cursor/cursor.ts +574 -110
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
- package/src/providers/integrations/deepai/deepai-common.ts +145 -0
- package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
- package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
- package/src/providers/integrations/deepai/provider.ts +42 -0
- package/src/providers/integrations/devin/devin-auth.ts +7 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +33 -51
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
- package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
- package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
- package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
- package/src/providers/integrations/freepi/freepi-session.ts +158 -0
- package/src/providers/integrations/freepi/freepi.ts +80 -54
- package/src/providers/integrations/github/github-discovery.ts +30 -13
- package/src/providers/integrations/github/github-oauth.ts +217 -44
- package/src/providers/integrations/github/github-quota.ts +22 -5
- package/src/providers/integrations/github/github.ts +40 -32
- package/src/providers/integrations/grok/grok-oauth.ts +52 -49
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
- package/src/providers/integrations/kilo/kilo.ts +1 -1
- package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
- package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -6
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +6 -16
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
- package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
- package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
- package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
- package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
- package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
- package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
- package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
- package/src/providers/integrations/qoder/qoder-types.ts +9 -0
- package/src/providers/integrations/qoder/qoder.ts +3 -26
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +21 -29
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- package/src/providers/integrations/vercel/provider.ts +117 -0
- package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
- package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
- package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +205 -0
- package/src/providers/operations/account-error-classification.ts +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +36 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +100 -81
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +118 -51
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +6 -4
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +70 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-contracts.ts +2 -0
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +100 -21
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +15 -14
- package/src/transport/dispatch/attempt-loop.ts +73 -19
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +8 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +10 -7
- package/src/transport/dispatch/streaming/stream.ts +9 -37
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +393 -32
- package/src/transport/shutdown-notice.ts +1 -1
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +7 -1
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/buddy-growth-tasks.ts +8 -8
- package/src/workers/daily-checkin.ts +4 -4
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/zcode-gift-claim.ts +1 -1
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/network/pool-health.ts +0 -210
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
- package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
- package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
- package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
- package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
- package/src/providers/integrations/qoder/chat-proto.ts +0 -328
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
- /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
- /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
- /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
- /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
- /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
|
@@ -11,7 +11,7 @@ import { chatAdapter } from "../surface/chat/adapter";
|
|
|
11
11
|
import { responsesAdapter } from "../surface/responses/adapter";
|
|
12
12
|
import { messagesAdapter } from "../surface/messages/adapter";
|
|
13
13
|
import { completionAdapter } from "../surface/completion";
|
|
14
|
-
import { forwardedRequestHeaders, proxySuccessHeaders, buildUpstreamDispatchContext, reportedModelFrom } from "./upstream";
|
|
14
|
+
import { forwardedRequestHeaders, proxySuccessHeaders, buildUpstreamDispatchContext, buildDispatchNetworkBindings, reportedModelFrom } from "./upstream";
|
|
15
15
|
import { resolvePromptCacheKey } from "../../providers/operations/session-resolution";
|
|
16
16
|
import {
|
|
17
17
|
applyTenantPreferences,
|
|
@@ -21,18 +21,12 @@ import {
|
|
|
21
21
|
import { metrics } from "../../observability/metrics";
|
|
22
22
|
import { repriceUsage } from "../../providers/provider-usage";
|
|
23
23
|
import {
|
|
24
|
-
captureProviderExchange,
|
|
25
24
|
completeAttempt,
|
|
26
25
|
completionContext,
|
|
27
26
|
estimatedUsage,
|
|
28
27
|
parseCapturedBody,
|
|
29
28
|
terminalFailure,
|
|
30
29
|
} from "./attempt-finalize";
|
|
31
|
-
import {
|
|
32
|
-
providerCaptureMaxBytes,
|
|
33
|
-
resolveCaptureDepthForRequest,
|
|
34
|
-
shouldCaptureProviderBodyForRequest,
|
|
35
|
-
} from "./capture-policy";
|
|
36
30
|
import { runAttemptLoop } from "./attempt-loop";
|
|
37
31
|
import { projectForRoute, routeCapabilitiesFor } from "../../protocol/translation/capabilities";
|
|
38
32
|
import { dispatchStreamingAttempt } from "./streaming/stream";
|
|
@@ -264,45 +258,13 @@ export async function handleProviderProxyRequest(
|
|
|
264
258
|
...(tenantSystemPromptText === undefined ? {} : { tenantSystemPrompt: tenantSystemPromptText }),
|
|
265
259
|
...(candidate.user_agent === undefined ? {} : { userAgent: candidate.user_agent }),
|
|
266
260
|
...(providerCapture ? { providerCapture } : {}),
|
|
267
|
-
...(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
providerCapture,
|
|
275
|
-
await shouldCaptureProviderBodyForRequest(
|
|
276
|
-
deps.db,
|
|
277
|
-
prepared.authorization.snapshot.tenant_id,
|
|
278
|
-
state,
|
|
279
|
-
),
|
|
280
|
-
providerCaptureMaxBytes(
|
|
281
|
-
await resolveCaptureDepthForRequest(
|
|
282
|
-
deps.db,
|
|
283
|
-
prepared.authorization.snapshot.tenant_id,
|
|
284
|
-
state,
|
|
285
|
-
),
|
|
286
|
-
),
|
|
287
|
-
),
|
|
288
|
-
}
|
|
289
|
-
: {}),
|
|
290
|
-
...(deps.networkBindingFactory
|
|
291
|
-
? {
|
|
292
|
-
outboundAttachmentFetch: deps.networkBindingFactory.fetch(
|
|
293
|
-
networkPoolId,
|
|
294
|
-
prepared.authorization.snapshot.tenant_id,
|
|
295
|
-
),
|
|
296
|
-
}
|
|
297
|
-
: {}),
|
|
298
|
-
...(deps.networkBindingFactory
|
|
299
|
-
? {
|
|
300
|
-
outboundHttp2: deps.networkBindingFactory.http2Bidi(
|
|
301
|
-
networkPoolId,
|
|
302
|
-
prepared.authorization.snapshot.tenant_id,
|
|
303
|
-
),
|
|
304
|
-
}
|
|
305
|
-
: {}),
|
|
261
|
+
...(await buildDispatchNetworkBindings({
|
|
262
|
+
deps,
|
|
263
|
+
networkPoolId,
|
|
264
|
+
tenantId: prepared.authorization.snapshot.tenant_id,
|
|
265
|
+
providerCapture,
|
|
266
|
+
state,
|
|
267
|
+
})),
|
|
306
268
|
...(leaseOwnership === undefined ? {} : { leaseOwnership }),
|
|
307
269
|
...(continuationScope === undefined ? {} : { continuationScope }),
|
|
308
270
|
});
|
|
@@ -4,6 +4,7 @@ import type { CodexCompactAdapter } from "../../providers/integrations/codex/cod
|
|
|
4
4
|
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
5
5
|
import { runAttemptLoop } from "./attempt-loop";
|
|
6
6
|
import {
|
|
7
|
+
forwardUpstreamResponse,
|
|
7
8
|
networkFetchContext,
|
|
8
9
|
requireRecordBody,
|
|
9
10
|
resolveAttemptCredential,
|
|
@@ -143,14 +144,7 @@ export function createResponsesCompactHandler(deps: ResponsesCompactHandlerDeps)
|
|
|
143
144
|
responseBody: null,
|
|
144
145
|
});
|
|
145
146
|
metrics.proxy_requests_total.inc(1, { status: "completed" });
|
|
146
|
-
return
|
|
147
|
-
status: response.status,
|
|
148
|
-
headers: {
|
|
149
|
-
"content-type": response.headers.get("content-type") ?? "application/json",
|
|
150
|
-
"cache-control": "no-store",
|
|
151
|
-
"x-request-id": state.requestId,
|
|
152
|
-
},
|
|
153
|
-
});
|
|
147
|
+
return forwardUpstreamResponse(response, state.requestId);
|
|
154
148
|
},
|
|
155
149
|
});
|
|
156
150
|
};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* cooldown eligibility (here).
|
|
11
11
|
*
|
|
12
12
|
* The egress-scoped half of the rate-limit decision lives in
|
|
13
|
-
* `../../network/
|
|
13
|
+
* `../../network/verdict`; this module keeps only the credential-
|
|
14
14
|
* scoped predicates.
|
|
15
15
|
*/
|
|
16
16
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
@@ -23,8 +23,8 @@ import type { AdmissionLease } from "../../../security/admission/contracts";
|
|
|
23
23
|
import type { Reservation } from "../../routing/route-model";
|
|
24
24
|
import { repriceUsage } from "../../../providers/provider-usage";
|
|
25
25
|
import { metrics } from "../../../observability/metrics";
|
|
26
|
-
import {
|
|
27
|
-
import { classifyEgressFailure } from "../../../network/
|
|
26
|
+
import { flagPoolOnEgressFailure } from "../../../network/pool/health";
|
|
27
|
+
import { classifyEgressFailure } from "../../../network/verdict";
|
|
28
28
|
import { releaseAttemptLeases } from "../leases";
|
|
29
29
|
import type { AttemptLeaseOwnership } from "../leases";
|
|
30
30
|
import { finalizeRequestTelemetry } from "../../guards/error-lifecycle";
|
|
@@ -581,11 +581,14 @@ export class StreamFinalizer {
|
|
|
581
581
|
this.streamSettled = true;
|
|
582
582
|
if (candidate.networkPoolId && !cancelled && deps.poolSelector && err instanceof GatewayError) {
|
|
583
583
|
const egressVerdict = classifyEgressFailure(err, candidate.providerId);
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
584
|
+
flagPoolOnEgressFailure(
|
|
585
|
+
deps.poolSelector,
|
|
586
|
+
deps.db,
|
|
587
|
+
candidate.networkPoolId,
|
|
588
|
+
candidate.providerId,
|
|
589
|
+
err,
|
|
590
|
+
egressVerdict,
|
|
591
|
+
);
|
|
589
592
|
}
|
|
590
593
|
if (!cancelled) metrics.proxy_requests_total.inc(1, { status: "failed" });
|
|
591
594
|
else metrics.proxy_requests_total.inc(1, { status: "cancelled" });
|
|
@@ -36,24 +36,18 @@
|
|
|
36
36
|
import { GatewayError } from "../../../protocol/gateway-error";
|
|
37
37
|
import type { CanonicalEvent, CanonicalRequest } from "../../../protocol/canonical-model";
|
|
38
38
|
import type { ProviderAdapter, ResolvedCredential } from "../../../providers/provider-contracts";
|
|
39
|
-
import type { ValidatedNetworkBindingFactory } from "../../../network/
|
|
39
|
+
import type { ValidatedNetworkBindingFactory } from "../../../network/egress/factory";
|
|
40
40
|
import type { CartethyiaDatabase } from "../../../persistence/db-handle";
|
|
41
41
|
import type { Reservation, RouteCandidate, RouteSnapshotService } from "../../routing/route-model";
|
|
42
42
|
import type { AdmissionLease } from "../../../security/admission/contracts";
|
|
43
43
|
import type { NetworkPoolSelector } from "../../../network/pool/selector";
|
|
44
44
|
import type { TelemetryBatchBuffer } from "../../../observability/telemetry-buffer";
|
|
45
45
|
import type { ProviderExchangeCapture } from "../attempt-finalize";
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
providerCaptureMaxBytes,
|
|
49
|
-
resolveCaptureDepthForRequest,
|
|
50
|
-
resolveCaptureModeForRequest,
|
|
51
|
-
shouldCaptureProviderBodyForRequest,
|
|
52
|
-
} from "../capture-policy";
|
|
46
|
+
import { resolveCaptureModeForRequest } from "../capture-policy";
|
|
53
47
|
import { resolveTenantImageClampMaxBytes, readTenantSystemPromptsForDispatch } from "../../request/tenant-preferences";
|
|
54
48
|
import { resolveTenantSystemPrompt } from "../../request/system-prompt-injection";
|
|
55
49
|
import { isWrappedInjectionProvider } from "../../../console/system-prompt/contracts";
|
|
56
|
-
import { proxySuccessHeaders, buildUpstreamDispatchContext } from "../upstream";
|
|
50
|
+
import { proxySuccessHeaders, buildUpstreamDispatchContext, buildDispatchNetworkBindings } from "../upstream";
|
|
57
51
|
import type { AttemptLeaseOwnership } from "../leases";
|
|
58
52
|
import type { ProviderContinuationScope } from "../../../providers/provider-contracts";
|
|
59
53
|
import { resolveStreamFirstChunkTimeoutMs, resolveStreamStallTimeoutMs, resolveUpstreamTimeoutMs } from "../../../config/timeouts";
|
|
@@ -170,18 +164,13 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
|
|
|
170
164
|
continuationScope,
|
|
171
165
|
} = input;
|
|
172
166
|
|
|
173
|
-
const
|
|
167
|
+
const bindings = await buildDispatchNetworkBindings({
|
|
168
|
+
deps,
|
|
174
169
|
networkPoolId,
|
|
175
|
-
prepared.authorization.snapshot.tenant_id,
|
|
176
|
-
|
|
177
|
-
const captureBody = outboundFetch
|
|
178
|
-
? await shouldCaptureProviderBodyForRequest(deps.db, prepared.authorization.snapshot.tenant_id, state)
|
|
179
|
-
: false;
|
|
180
|
-
const captureDepth = await resolveCaptureDepthForRequest(
|
|
181
|
-
deps.db,
|
|
182
|
-
prepared.authorization.snapshot.tenant_id,
|
|
170
|
+
tenantId: prepared.authorization.snapshot.tenant_id,
|
|
171
|
+
providerCapture,
|
|
183
172
|
state,
|
|
184
|
-
);
|
|
173
|
+
});
|
|
185
174
|
// Resolved once, before the pipeline exists: the pipeline's diagnostics are
|
|
186
175
|
// gated on it from construction, so `none`/`metadata` tenants never pay the
|
|
187
176
|
// transcript serialization or the client-response copy for any event.
|
|
@@ -190,14 +179,6 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
|
|
|
190
179
|
prepared.authorization.snapshot.tenant_id,
|
|
191
180
|
state,
|
|
192
181
|
);
|
|
193
|
-
const wrappedOutboundFetch = outboundFetch
|
|
194
|
-
? captureProviderExchange(
|
|
195
|
-
outboundFetch,
|
|
196
|
-
providerCapture,
|
|
197
|
-
captureBody,
|
|
198
|
-
providerCaptureMaxBytes(captureDepth),
|
|
199
|
-
)
|
|
200
|
-
: undefined;
|
|
201
182
|
// Same memoized ceiling the non-streaming path resolves, read before the
|
|
202
183
|
// dispatch context is built so a stream gets the same limit as a normal
|
|
203
184
|
// request rather than silently falling back to the deployment default.
|
|
@@ -237,16 +218,7 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
|
|
|
237
218
|
return text === undefined ? {} : { tenantSystemPrompt: text };
|
|
238
219
|
})()),
|
|
239
220
|
...(candidate.user_agent === undefined ? {} : { userAgent: candidate.user_agent }),
|
|
240
|
-
...
|
|
241
|
-
...(outboundFetch ? { outboundAttachmentFetch: outboundFetch } : {}),
|
|
242
|
-
...(deps.networkBindingFactory
|
|
243
|
-
? {
|
|
244
|
-
outboundHttp2: deps.networkBindingFactory.http2Bidi(
|
|
245
|
-
networkPoolId,
|
|
246
|
-
prepared.authorization.snapshot.tenant_id,
|
|
247
|
-
),
|
|
248
|
-
}
|
|
249
|
-
: {}),
|
|
221
|
+
...bindings,
|
|
250
222
|
...(leaseOwnership === undefined ? {} : { leaseOwnership }),
|
|
251
223
|
...(continuationScope === undefined ? {} : { continuationScope }),
|
|
252
224
|
...(providerCapture ? { providerCapture } : {}),
|
|
@@ -4,6 +4,7 @@ import { metrics } from "../../observability/metrics";
|
|
|
4
4
|
import { estimatedUsage, nativeCompletionBase, completeAttempt } from "./attempt-finalize";
|
|
5
5
|
import { runAttemptLoop } from "./attempt-loop";
|
|
6
6
|
import {
|
|
7
|
+
forwardUpstreamResponse,
|
|
7
8
|
networkFetchContext,
|
|
8
9
|
requireRecordBody,
|
|
9
10
|
resolveAttemptCredential,
|
|
@@ -164,14 +165,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
164
165
|
responseBody: null,
|
|
165
166
|
});
|
|
166
167
|
metrics.proxy_requests_total.inc(1, { status: "completed" });
|
|
167
|
-
return
|
|
168
|
-
status: response.status,
|
|
169
|
-
headers: {
|
|
170
|
-
"content-type": response.headers.get("content-type") ?? "application/json",
|
|
171
|
-
"cache-control": "no-store",
|
|
172
|
-
"x-request-id": state.requestId,
|
|
173
|
-
},
|
|
174
|
-
});
|
|
168
|
+
return forwardUpstreamResponse(response, state.requestId);
|
|
175
169
|
},
|
|
176
170
|
});
|
|
177
171
|
};
|
|
@@ -43,7 +43,7 @@ import type { PreparedNativeRequest } from "../request/preparer";
|
|
|
43
43
|
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
44
44
|
import { finalizeRequestTelemetry } from "../guards/error-lifecycle";
|
|
45
45
|
import { runAttemptLoop } from "./attempt-loop";
|
|
46
|
-
import { hasScope, networkFetchContext, requireRecordBody, resolveAttemptCredential } from "./native-shared";
|
|
46
|
+
import { forwardUpstreamResponse, hasScope, networkFetchContext, requireRecordBody, resolveAttemptCredential } from "./native-shared";
|
|
47
47
|
|
|
48
48
|
import type { GatewayHandlerDeps } from "./handler-deps";
|
|
49
49
|
|
|
@@ -189,11 +189,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
189
189
|
|
|
190
190
|
// Verbatim: the client reads the provider's own number, and the
|
|
191
191
|
// provider's own error when it refuses to produce one.
|
|
192
|
-
|
|
193
|
-
return new Response(upstream.body, {
|
|
194
|
-
status: upstream.status,
|
|
195
|
-
headers: { "content-type": contentType, "cache-control": "no-store", "x-request-id": state.requestId },
|
|
196
|
-
});
|
|
192
|
+
return forwardUpstreamResponse(upstream, state.requestId);
|
|
197
193
|
},
|
|
198
194
|
});
|
|
199
195
|
};
|
|
@@ -2,6 +2,15 @@ import type { CanonicalEvent } from "../../protocol/canonical-model";
|
|
|
2
2
|
import type { AttemptLeaseOwnership, ValidatedOutboundFetch, ValidatedOutboundHttp2Bidi } from "../../providers/provider-contracts";
|
|
3
3
|
import type { ProviderCaptureExchange, ProviderContinuationScope } from "../../providers/provider-contracts";
|
|
4
4
|
import { GATEWAY_SECURITY_HEADERS } from "../../security/outbound-headers";
|
|
5
|
+
import type { ValidatedNetworkBindingFactory } from "../../network/egress/factory";
|
|
6
|
+
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
7
|
+
import { captureProviderExchange, type ProviderExchangeCapture } from "./attempt-finalize";
|
|
8
|
+
import {
|
|
9
|
+
providerCaptureMaxBytes,
|
|
10
|
+
resolveCaptureDepthForRequest,
|
|
11
|
+
shouldCaptureProviderBodyForRequest,
|
|
12
|
+
type CapturePreferenceState,
|
|
13
|
+
} from "./capture-policy";
|
|
5
14
|
|
|
6
15
|
const FORWARDED_REQUEST_HEADERS = new Set([
|
|
7
16
|
"user-agent",
|
|
@@ -34,6 +43,60 @@ export function proxySuccessHeaders(state: { requestId: string }): Record<string
|
|
|
34
43
|
};
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
/** The three validated egress bindings shared by every dispatch attempt. */
|
|
47
|
+
export interface DispatchNetworkBindings {
|
|
48
|
+
readonly outboundFetch?: ValidatedOutboundFetch;
|
|
49
|
+
readonly outboundAttachmentFetch?: ValidatedOutboundFetch;
|
|
50
|
+
readonly outboundHttp2?: ValidatedOutboundHttp2Bidi;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Builds the outbound network bindings for one attempt's dispatch context,
|
|
55
|
+
* shared by the non-streaming proxy path and the streaming branch.
|
|
56
|
+
*
|
|
57
|
+
* Both previously assembled the same three objects by hand (wrapped fetch,
|
|
58
|
+
* attachment fetch, HTTP/2 bidi), which drifted in the capture wrap. The
|
|
59
|
+
* capture-body gating is preserved exactly: the DB-backed capture-preference
|
|
60
|
+
* read is skipped when no network binding exists (`outboundFetch ? ... :
|
|
61
|
+
* false`), so a request that can never tee a provider exchange does not pay
|
|
62
|
+
* for the preference read. The reads are memoized on the request state, so
|
|
63
|
+
* resolving them here costs no extra DB reads.
|
|
64
|
+
*/
|
|
65
|
+
export async function buildDispatchNetworkBindings(input: {
|
|
66
|
+
readonly deps: {
|
|
67
|
+
readonly networkBindingFactory?: ValidatedNetworkBindingFactory | undefined;
|
|
68
|
+
readonly db: CartethyiaDatabase;
|
|
69
|
+
};
|
|
70
|
+
readonly networkPoolId: string | undefined;
|
|
71
|
+
readonly tenantId: string | null;
|
|
72
|
+
readonly providerCapture: ProviderExchangeCapture;
|
|
73
|
+
readonly state: CapturePreferenceState;
|
|
74
|
+
}): Promise<DispatchNetworkBindings> {
|
|
75
|
+
const factory = input.deps.networkBindingFactory;
|
|
76
|
+
if (factory === undefined) return {};
|
|
77
|
+
const outboundFetch = factory.fetch(input.networkPoolId, input.tenantId ?? undefined);
|
|
78
|
+
const captureBody = await shouldCaptureProviderBodyForRequest(
|
|
79
|
+
input.deps.db,
|
|
80
|
+
input.tenantId,
|
|
81
|
+
input.state,
|
|
82
|
+
);
|
|
83
|
+
const captureDepth = await resolveCaptureDepthForRequest(
|
|
84
|
+
input.deps.db,
|
|
85
|
+
input.tenantId,
|
|
86
|
+
input.state,
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
outboundFetch: captureProviderExchange(
|
|
90
|
+
outboundFetch,
|
|
91
|
+
input.providerCapture,
|
|
92
|
+
captureBody,
|
|
93
|
+
providerCaptureMaxBytes(captureDepth),
|
|
94
|
+
),
|
|
95
|
+
outboundAttachmentFetch: factory.fetch(input.networkPoolId, input.tenantId ?? undefined),
|
|
96
|
+
outboundHttp2: factory.http2Bidi(input.networkPoolId, input.tenantId ?? undefined),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
37
100
|
export function buildUpstreamDispatchContext(input: {
|
|
38
101
|
readonly credential: unknown;
|
|
39
102
|
readonly deadline: number;
|
|
@@ -50,14 +50,41 @@ export function tokenMatches(provided: string | null, expected: string): boolean
|
|
|
50
50
|
return a.length === b.length && timingSafeEqual(a, b);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
interface LoopbackAdminGate {
|
|
54
|
+
/** Peer address of the request, resolved by the composition root. */
|
|
55
|
+
readonly peer: string | null;
|
|
56
|
+
/** Shared secret; the route is registered only when this is set. */
|
|
57
|
+
readonly token: string;
|
|
58
|
+
/** `policy_rejected` (403) message, preserved per surface. */
|
|
59
|
+
readonly policyRejectedMessage: string;
|
|
60
|
+
/** `authentication_failed` (401) message, preserved per surface. */
|
|
61
|
+
readonly authenticationFailedMessage: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The shared security gate for host-local admin operations: the request must
|
|
66
|
+
* come from loopback, then it must carry the drain token compared in constant
|
|
67
|
+
* time. The peer check is independent of the secret, so a leaked token alone
|
|
68
|
+
* cannot reach a remote surface. The peer refusal comes first, then the token,
|
|
69
|
+
* with the codes and messages preserved verbatim per caller.
|
|
70
|
+
*/
|
|
71
|
+
export function requireLoopbackAdmin(request: Request, gate: LoopbackAdminGate): void {
|
|
72
|
+
if (!isLoopbackPeer(gate.peer))
|
|
73
|
+
throw new GatewayError("policy_rejected", 403, gate.policyRejectedMessage);
|
|
74
|
+
if (!tokenMatches(request.headers.get("x-drain-token"), gate.token))
|
|
75
|
+
throw new GatewayError("authentication_failed", 401, gate.authenticationFailedMessage);
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
export function createDrainHandler(
|
|
54
79
|
deps: DrainEndpointDeps,
|
|
55
80
|
): (context: { request: Request }) => Promise<Response> {
|
|
56
81
|
return async ({ request }) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
requireLoopbackAdmin(request, {
|
|
83
|
+
peer: deps.resolvePeerAddress(request),
|
|
84
|
+
token: deps.token,
|
|
85
|
+
policyRejectedMessage: "drain is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂",
|
|
86
|
+
authenticationFailedMessage: "invalid drain token (request {requestId}). Wrong key, right door — check the token 🔑",
|
|
87
|
+
});
|
|
61
88
|
// Acknowledge first, then drain: the response must be on the wire before the
|
|
62
89
|
// shutdown path starts closing sockets, or the caller sees a dropped
|
|
63
90
|
// connection instead of a 202.
|
|
@@ -2,7 +2,6 @@ import { GatewayError, type GatewayErrorCode, type GatewayErrorOrigin } from "..
|
|
|
2
2
|
import { isRecord } from "../protocol/codecs";
|
|
3
3
|
import { resolveFallbackRetryBaseMs, resolveFallbackRetryCapMs } from "../config/timeouts";
|
|
4
4
|
import { parseUpstreamBackoff } from "../protocol/upstream-parsing";
|
|
5
|
-
import { resolveAccountQuotaCooldownMs } from "../config/cooldowns";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* How much of the upstream's error body is kept and surfaced.
|
|
@@ -260,31 +259,6 @@ export function classifyUpstreamFailure(error: unknown): UpstreamFailurePolicy {
|
|
|
260
259
|
// DNS failure. Failing the client request instead of failing over is the wrong
|
|
261
260
|
// outcome for a whole class the table already names as platform-wide.
|
|
262
261
|
const statusCode = error.status;
|
|
263
|
-
// Qoder answers an exhausted quota by closing the stream with an upstream 499
|
|
264
|
-
// (nginx client-close style). That is a refusal from the upstream — the
|
|
265
|
-
// gateway's own aborts are `transport_closed`, produced locally, and never
|
|
266
|
-
// carry a qoder providerId — so a 499 scoped to qoder is quota, not a
|
|
267
|
-
// malformed request. Without this it fell through to `invalid_request`
|
|
268
|
-
// (non-retryable, no cooldown) and the account was re-dialled on every
|
|
269
|
-
// request inside the exhausted window until a late 429 finally classified it.
|
|
270
|
-
if (
|
|
271
|
-
error.origin === "upstream" &&
|
|
272
|
-
statusCode === 499 &&
|
|
273
|
-
(providerId === "qoder" || providerId === "qodercn")
|
|
274
|
-
) {
|
|
275
|
-
const cooldownMs = resolveAccountQuotaCooldownMs();
|
|
276
|
-
return {
|
|
277
|
-
retryable: true,
|
|
278
|
-
mutatesAccount: true,
|
|
279
|
-
category: "quota_exceeded",
|
|
280
|
-
scope: "account",
|
|
281
|
-
origin: error.origin,
|
|
282
|
-
...(providerCode ? { providerCode } : {}),
|
|
283
|
-
...(providerId ? { providerId } : {}),
|
|
284
|
-
cooldownMs,
|
|
285
|
-
statusCode,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
262
|
const retryableByStatus =
|
|
289
263
|
statusCode === 401 ||
|
|
290
264
|
statusCode === 403 ||
|
|
@@ -684,10 +658,8 @@ const QUOTA_ERROR_IDENTIFIERS: ReadonlySet<string> = new Set([
|
|
|
684
658
|
// quota (a cooldown) instead of `auth_invalidated`.
|
|
685
659
|
"110",
|
|
686
660
|
// Qoder also reports quota exhaustion as `{"code":116,"error":"quota
|
|
687
|
-
// exceeded"}
|
|
688
|
-
//
|
|
689
|
-
// naming the code here makes any status that carries it classify as quota
|
|
690
|
-
// instead of `invalid_request`/`authentication_failed`.
|
|
661
|
+
// exceeded"}`. Naming the code here makes any status that carries it classify
|
|
662
|
+
// as quota instead of `invalid_request`/`authentication_failed`.
|
|
691
663
|
"116",
|
|
692
664
|
// InferHub publishes its own vocabulary in `error.code`. The two 403s below
|
|
693
665
|
// are deliberate absences: `account_blocked` means the account was blocked
|
|
@@ -846,6 +818,20 @@ export function classifyUpstreamError(
|
|
|
846
818
|
return { code: "proxy_auth_required", status: 407, origin: "network" };
|
|
847
819
|
if (normalized === "11140")
|
|
848
820
|
return { code: "policy_rejected", status: 403, origin: "upstream" };
|
|
821
|
+
// ZCode plan-lane envelope codes (verified live 2026-10-30 by the donor and
|
|
822
|
+
// mirrored by the zcode-start provider). `3009` is model concurrency
|
|
823
|
+
// (transient, retried in place), `3008` user concurrency (the desktop client
|
|
824
|
+
// is busy on that account — switchable), `3012` is the identity/prompt-shape
|
|
825
|
+
// gate, `1113` the paid-lane "insufficient balance or no resource package"
|
|
826
|
+
// (a request that was not recognized as start-plan traffic).
|
|
827
|
+
if (normalized === "3009")
|
|
828
|
+
return { code: "rate_limited", status: 429, origin: "upstream" };
|
|
829
|
+
if (normalized === "3008")
|
|
830
|
+
return { code: "accounts_rate_limited", status: 429, origin: "upstream" };
|
|
831
|
+
if (normalized === "3012")
|
|
832
|
+
return { code: "policy_rejected", status: 405, origin: "upstream" };
|
|
833
|
+
if (normalized === "1113")
|
|
834
|
+
return { code: "quota_exceeded", status: 429, origin: "upstream", rateLimitScope: "provider" };
|
|
849
835
|
if (normalized !== undefined && CONTEXT_LENGTH_CODES.has(normalized))
|
|
850
836
|
return { code: "context_length_exceeded", status: 413, origin: "upstream" };
|
|
851
837
|
if (normalized !== undefined && QUOTA_ERROR_IDENTIFIERS.has(normalized))
|
|
@@ -58,8 +58,14 @@ export function isProxyDispatchRoute(path: string | undefined): path is string {
|
|
|
58
58
|
return path !== undefined && isJsonProxyRoute(path);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* The same canonical JSON-route membership as `isProxyDispatchRoute`, kept as
|
|
63
|
+
* a non-narrowing alias for callers that already hold a string path. One
|
|
64
|
+
* predicate, two spellings, so a route-table change cannot diverge between
|
|
65
|
+
* the body-read policy and the canonical-parse gate.
|
|
66
|
+
*/
|
|
61
67
|
export function isJsonProxyRoutePath(path: string): boolean {
|
|
62
|
-
return
|
|
68
|
+
return isProxyDispatchRoute(path);
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
interface IngressPolicyOptions {
|
|
@@ -77,8 +77,20 @@ export function createErrorNormalizationMiddleware(deps: {
|
|
|
77
77
|
.error(({ request, error, set }) => {
|
|
78
78
|
const state = deps.stateStore.get(request as Request);
|
|
79
79
|
const gateway = error instanceof GatewayError ? error : undefined;
|
|
80
|
+
// Elysia's built-in errors (NotFound, ValidationError, ParseError,
|
|
81
|
+
// InternalServerError) always carry a numeric `status` **and** a code.
|
|
82
|
+
// A raw runtime error can carry a `.code` on its own — Bun's fetch
|
|
83
|
+
// surfaces `TypeError` with `code: "ECONNRESET"` when an upstream socket
|
|
84
|
+
// dies mid-response — so a `code` alone is not evidence of an Elysia
|
|
85
|
+
// built-in. Requiring the numeric status keeps that raw error from
|
|
86
|
+
// leaking into the public envelope as a fake code/400; the dispatch loop
|
|
87
|
+
// already normalizes socket drops to `transport_unavailable`/502 before
|
|
88
|
+
// they reach here.
|
|
80
89
|
const builtin =
|
|
81
|
-
!gateway &&
|
|
90
|
+
!gateway &&
|
|
91
|
+
typeof error === "object" &&
|
|
92
|
+
error !== null &&
|
|
93
|
+
typeof (error as ElysiaBuiltinError).status === "number"
|
|
82
94
|
? (error as ElysiaBuiltinError)
|
|
83
95
|
: undefined;
|
|
84
96
|
const builtinStatus = typeof builtin?.status === "number" ? builtin.status : undefined;
|
|
@@ -12,7 +12,7 @@ import { resolveClientIdentity } from "../../security/ip-boundary";
|
|
|
12
12
|
import { GATEWAY_SECURITY_HEADERS } from "../../security/outbound-headers";
|
|
13
13
|
import { pushStructuredConsoleLog } from "../../observability/log-ring";
|
|
14
14
|
import { log } from "../../observability/logger";
|
|
15
|
-
import {
|
|
15
|
+
import { isProxyDispatchRoute } from "./body-policy";
|
|
16
16
|
import { isOpaqueBodyRoute } from "../contracts";
|
|
17
17
|
import { peerAddressUnavailableError } from "./guard-errors";
|
|
18
18
|
|
|
@@ -24,6 +24,24 @@ interface RequestApp {
|
|
|
24
24
|
): RequestApp;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Whether this request falls outside the canonical JSON-proxy pipeline the
|
|
29
|
+
* canonical-parse and route-preparation stages serve.
|
|
30
|
+
*
|
|
31
|
+
* Shared gate, applied in the exact order both middlewares historically
|
|
32
|
+
* checked: non-`/v1/` paths, GET/HEAD (which carry no body to parse), then
|
|
33
|
+
* the JSON route table with opaque-body routes excluded — `/v1/completions`
|
|
34
|
+
* needs no separate arm because it is already in the JSON routes table, and
|
|
35
|
+
* native routes (compact, System One, token count) read a body but are never
|
|
36
|
+
* parsed into a canonical request, their bodies being opaque by contract.
|
|
37
|
+
*/
|
|
38
|
+
function skipNonCanonicalProxyRequest(request: Request): boolean {
|
|
39
|
+
const path = fastPathname(request.url);
|
|
40
|
+
if (!path.startsWith("/v1/")) return true;
|
|
41
|
+
if (request.method === "GET" || request.method === "HEAD") return true;
|
|
42
|
+
return !isProxyDispatchRoute(path) || isOpaqueBodyRoute(path);
|
|
43
|
+
}
|
|
44
|
+
|
|
27
45
|
interface RequestContextDeps {
|
|
28
46
|
readonly stateStore: ProxyRequestStateStore;
|
|
29
47
|
readonly clock?: () => number;
|
|
@@ -141,15 +159,9 @@ export function createCanonicalRequestMiddleware(deps: {
|
|
|
141
159
|
}): Elysia {
|
|
142
160
|
return new Elysia()
|
|
143
161
|
.beforeHandle(async ({ request }) => {
|
|
144
|
-
const path = fastPathname(request.url);
|
|
145
|
-
if (!path.startsWith("/v1/")) return;
|
|
146
|
-
if (request.method === "GET" || request.method === "HEAD") return;
|
|
147
|
-
// `/v1/completions` needs no separate arm: it is already in
|
|
148
|
-
// the JSON routes table, so the body-policy predicate covers it.
|
|
149
|
-
// Native routes (compact, System One) read a body but are never parsed
|
|
150
|
-
// into a canonical request — their bodies are opaque by contract.
|
|
151
|
-
if (!isJsonProxyRoutePath(path) || isOpaqueBodyRoute(path)) return;
|
|
152
162
|
// Model discovery and other GET/multimodal routes must not go through canonical parsing.
|
|
163
|
+
if (skipNonCanonicalProxyRequest(request)) return;
|
|
164
|
+
const path = fastPathname(request.url);
|
|
153
165
|
const state = deps.stateStore.require(request);
|
|
154
166
|
// Single-read invariant: the ingress policy middleware already decoded the
|
|
155
167
|
// body once into `state.ingressBody`. Never re-read `request.body` here —
|
|
@@ -216,11 +228,7 @@ export function createProxyRoutePreparationMiddleware(deps: {
|
|
|
216
228
|
}): Elysia {
|
|
217
229
|
return new Elysia()
|
|
218
230
|
.beforeHandle(async ({ request }) => {
|
|
219
|
-
|
|
220
|
-
if (!path.startsWith("/v1/")) return;
|
|
221
|
-
if (request.method === "GET" || request.method === "HEAD") return;
|
|
222
|
-
const isJsonRoute = isJsonProxyRoutePath(path);
|
|
223
|
-
if (!isJsonRoute || isOpaqueBodyRoute(path)) return;
|
|
231
|
+
if (skipNonCanonicalProxyRequest(request)) return;
|
|
224
232
|
const state = deps.stateStore.require(request);
|
|
225
233
|
if (!state.authorization || !state.canonicalRequest)
|
|
226
234
|
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 🚪");
|