cartethyia 2.7.9 → 2.8.0
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 +3 -2
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Cc0vKpMG.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BeLpJ4ma.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-DnV0uZ3m.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-CBxAjo63.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-B_p4tL8k.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-D_0PiZtg.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BJ87Pp0U.js → ProxyPage-BwNC_-Cx.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
- package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-CctTga2C.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-nyeuRqz9.js} +1 -1
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-BFOlvrJC.js} +1 -1
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
- package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js +1 -0
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-Dw1U_drK.js} +1 -1
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-CEL5Sg_J.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-HvBW0AjI.js} +1 -1
- package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
- package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-DYYofQ7W.js} +1 -1
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +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-BFQX2VBc.js} +1 -1
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-C_XvMHh9.js} +1 -1
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.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/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/package.json +1 -1
- package/src/boot/app.ts +10 -13
- package/src/boot/dependencies.ts +1 -1
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -0
- 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/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/domain-registration.ts +0 -2
- 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/store.ts +0 -53
- package/src/console/providers/catalog/contracts.ts +2 -2
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/routes.ts +1 -0
- package/src/console/providers/catalog/store.ts +21 -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 +6 -18
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- package/src/network/pool/byte-accounting.ts +3 -2
- package/src/network/pool/selector.ts +182 -39
- package/src/network/pool-cooldown-store.ts +89 -14
- package/src/network/pool-health.ts +49 -61
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/ssrf.ts +6 -6
- 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 +15 -25
- 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 +8 -0
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- 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 +3 -4
- package/src/protocol/response/chat.ts +12 -20
- 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/providers/adapters/compatible-adapter.ts +71 -12
- package/src/providers/authentication/oauth-client.ts +1 -1
- package/src/providers/authentication/oauth-device-flow.ts +98 -8
- package/src/providers/authentication/oauth-refresh-service.ts +30 -49
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +61 -36
- package/src/providers/discovery/probe-phases.ts +14 -4
- package/src/providers/discovery/probing-service.ts +15 -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 +5 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +173 -17
- package/src/providers/integrations/buddy/buddy-identity.ts +19 -6
- package/src/providers/integrations/buddy/capabilities.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +3 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +37 -22
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- package/src/providers/integrations/cursor/cursor-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +7 -4
- package/src/providers/integrations/cursor/cursor.ts +89 -40
- 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/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- 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/grok/grok-oauth.ts +48 -45
- 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.ts → hermes/hermes.ts} +9 -9
- package/src/providers/integrations/hermes/index.ts +2 -0
- 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-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- 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/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -3
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +3 -13
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +18 -59
- package/src/providers/integrations/qoder/qoder-adapter.ts +1 -12
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-catalog.ts +20 -5
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +4 -4
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -1
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -107
- package/src/providers/integrations/qoder/qoder.ts +0 -1
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +6 -28
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- 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 +204 -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 +22 -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 +5 -51
- package/src/providers/operations/cooldown-rules.ts +11 -16
- 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 +102 -32
- 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/provider-contracts.ts +4 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +40 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- 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 +75 -18
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/transport/dispatch/attempt-finalize.ts +14 -13
- package/src/transport/dispatch/attempt-loop.ts +71 -17
- package/src/transport/dispatch/generation.ts +2 -8
- 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/streaming/finalize.ts +9 -6
- package/src/transport/dispatch/streaming/stream.ts +8 -36
- 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/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/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.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/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- 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-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -622,19 +622,20 @@ export function nativeCompletionBase(input: {
|
|
|
622
622
|
| "tokensPerSec"
|
|
623
623
|
> {
|
|
624
624
|
return {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
625
|
+
...completionContext({
|
|
626
|
+
providerId: input.providerId,
|
|
627
|
+
modelId: input.modelId,
|
|
628
|
+
tenantId: input.tenantId,
|
|
629
|
+
ingressBody: input.ingressBody,
|
|
630
|
+
...(input.providerCapture === undefined ? {} : { providerCapture: input.providerCapture }),
|
|
631
|
+
db: input.db,
|
|
632
|
+
...(input.accountId === undefined ? {} : { accountId: input.accountId }),
|
|
633
|
+
...(input.accountLabel === undefined ? {} : { accountLabel: input.accountLabel }),
|
|
634
|
+
...(input.networkPoolId === undefined ? {} : { networkPoolId: input.networkPoolId }),
|
|
635
|
+
...(input.lease === undefined ? {} : { lease: input.lease }),
|
|
636
|
+
...(input.telemetryBuffer === undefined ? {} : { telemetryBuffer: input.telemetryBuffer }),
|
|
637
|
+
...(input.snapshotService === undefined ? {} : { snapshotService: input.snapshotService }),
|
|
638
|
+
}),
|
|
638
639
|
...(input.usage === undefined ? {} : { usage: input.usage }),
|
|
639
640
|
...(input.commitUsage === undefined ? {} : { commitUsage: input.commitUsage }),
|
|
640
641
|
};
|
|
@@ -32,7 +32,7 @@ import { metrics } from "../../observability/metrics";
|
|
|
32
32
|
import type { NetworkPoolSelector } from "../../network/pool/selector";
|
|
33
33
|
import type { ProxyRequestState } from "../request/state";
|
|
34
34
|
import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
|
|
35
|
-
import {
|
|
35
|
+
import { flagPoolOnEgressFailure } from "../../network/pool-health";
|
|
36
36
|
import {
|
|
37
37
|
completeAttempt,
|
|
38
38
|
errorClientResponseBody,
|
|
@@ -61,6 +61,41 @@ function comboCandidateKey(candidate: RouteCandidate): string {
|
|
|
61
61
|
].join(":");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Extra dials allowed for a mid-response upstream socket drop when the
|
|
66
|
+
* candidate list is exhausted (single-account providers). One extra dial
|
|
67
|
+
* recovers the transient reset; more would stack latency on a dying origin.
|
|
68
|
+
*/
|
|
69
|
+
const MAX_SOCKET_DROP_RETRIES = 1;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Normalizes a raw (non-GatewayError) failure before it escapes the loop.
|
|
73
|
+
*
|
|
74
|
+
* A transport-level throw — e.g. Bun's `TypeError` with
|
|
75
|
+
* `code: "ECONNRESET"` when the upstream socket dies mid-response — carries a
|
|
76
|
+
* `.code` of its own. The error middleware reads that `.code` for Elysia
|
|
77
|
+
* built-in errors, so a raw socket drop leaked to the client as
|
|
78
|
+
* `{"code":"ECONNRESET","status":400}` with origin `cartethyia`: the wrong
|
|
79
|
+
* code, the wrong status, and the wrong blame. The classifier already knows
|
|
80
|
+
* what this is (`transport_unavailable`, 502, upstream) — materialize it as a
|
|
81
|
+
* real GatewayError so the client and the captured Client Response see the
|
|
82
|
+
* same stable envelope every other upstream transport failure gets.
|
|
83
|
+
*/
|
|
84
|
+
function normalizeEscapeError(error: unknown, signal: AbortSignal): unknown {
|
|
85
|
+
if (error instanceof GatewayError) return error;
|
|
86
|
+
const terminal = classifyTerminalOutcome(error, signal);
|
|
87
|
+
if (terminal.errorCategory === "transport_unavailable") {
|
|
88
|
+
return new GatewayError(
|
|
89
|
+
"transport_unavailable",
|
|
90
|
+
502,
|
|
91
|
+
"The upstream closed the connection mid-response (request {requestId}). Retry; if it keeps happening, the account or pool is unstable. The connection ghosted us — come back in a moment 👻",
|
|
92
|
+
{},
|
|
93
|
+
"upstream",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return error;
|
|
97
|
+
}
|
|
98
|
+
|
|
64
99
|
/**
|
|
65
100
|
* True when the rejection is about the API key or the gateway's own policy,
|
|
66
101
|
* not about the provider account that was dialled.
|
|
@@ -321,7 +356,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
321
356
|
status: terminal.status === "cancelled" ? "cancelled" : "failed",
|
|
322
357
|
});
|
|
323
358
|
}
|
|
324
|
-
throw lastError;
|
|
359
|
+
throw normalizeEscapeError(lastError, state.abortController.signal);
|
|
325
360
|
}
|
|
326
361
|
}
|
|
327
362
|
let lease: AdmissionLease | undefined;
|
|
@@ -381,6 +416,13 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
381
416
|
// names the real cause.
|
|
382
417
|
const drain = drainAbortReason(state.abortController.signal.reason);
|
|
383
418
|
if (drain !== undefined) error = drain;
|
|
419
|
+
// Normalize a raw transport throw (e.g. Bun's `TypeError` with
|
|
420
|
+
// `code: "ECONNRESET"` when the upstream socket dies mid-response) into
|
|
421
|
+
// the stable `transport_unavailable`/502/upstream GatewayError **before**
|
|
422
|
+
// classification, so the telemetry row, the captured Client Response, and
|
|
423
|
+
// the escaped error all agree — and the raw `.code` never leaks to the
|
|
424
|
+
// client as a fake Elysia built-in code (see `normalizeEscapeError`).
|
|
425
|
+
error = normalizeEscapeError(error, state.abortController.signal);
|
|
384
426
|
lastError = error;
|
|
385
427
|
// A drain is a *server* close, not a client cancel: it must be recorded
|
|
386
428
|
// as a failure with the shutdown code, and it is never retryable.
|
|
@@ -411,21 +453,33 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
411
453
|
// the pool excluded. Both flag helpers write the local entry
|
|
412
454
|
// synchronously, so the ordering holds even though the Redis mirror is
|
|
413
455
|
// fire-and-forget.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
456
|
+
flagPoolOnEgressFailure(
|
|
457
|
+
deps.poolSelector,
|
|
458
|
+
deps.db,
|
|
459
|
+
networkPoolId,
|
|
460
|
+
candidate.provider_id,
|
|
461
|
+
error,
|
|
462
|
+
egressVerdict,
|
|
463
|
+
);
|
|
464
|
+
// A mid-response upstream socket drop (`transport_unavailable`) is the one
|
|
465
|
+
// retryable failure that receives an extra dial even when the candidate
|
|
466
|
+
// list is exhausted: an origin that closes the connection before the
|
|
467
|
+
// response is often transient (Trae's edge resets under concurrency),
|
|
468
|
+
// and the fresh dial recovers it — verified live 2026-11-09, 7/7
|
|
469
|
+
// recovered on the first retry. Without this, a single-account provider
|
|
470
|
+
// (the common case) tripped `index === candidates.length - 1` on the very
|
|
471
|
+
// first attempt and the client saw the raw socket error. The retry is
|
|
472
|
+
// bounded to one extra dial per request, never unbounded.
|
|
473
|
+
const memberAttempts = attemptsByMember.get(memberKey) ?? 0;
|
|
474
|
+
const socketDropRetryLeft =
|
|
475
|
+
terminal.errorCategory === "transport_unavailable" &&
|
|
476
|
+
!cancelled &&
|
|
477
|
+
memberAttempts < MAX_SOCKET_DROP_RETRIES;
|
|
424
478
|
const terminalAttempt =
|
|
425
479
|
cancelled ||
|
|
426
480
|
isClientScopedRejection(error) ||
|
|
427
481
|
!isRetryableFailure(error) ||
|
|
428
|
-
(index === candidates.length - 1 && !poolScopedFailure);
|
|
482
|
+
((index === candidates.length - 1 && !poolScopedFailure) && !socketDropRetryLeft);
|
|
429
483
|
// Track only the most recent fired exchange; the array this replaced
|
|
430
484
|
// accumulated every attempt's capture until the loop returned.
|
|
431
485
|
if (providerCapture.request !== null) lastFiredCapture = providerCapture;
|
|
@@ -474,7 +528,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
474
528
|
// A 403 can be a deterministic provider policy rejection, not an
|
|
475
529
|
// invalid OAuth credential. Only the evidence-based classifier above
|
|
476
530
|
// may trigger a refresh.
|
|
477
|
-
const candidateKey =
|
|
531
|
+
const candidateKey = comboCandidateKey(candidate);
|
|
478
532
|
const authInvalidated = isOAuthCredentialInvalidated(error);
|
|
479
533
|
if (
|
|
480
534
|
!cancelled &&
|
|
@@ -499,7 +553,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
499
553
|
}
|
|
500
554
|
if (terminalAttempt) {
|
|
501
555
|
metrics.proxy_requests_total.inc(1, { status: cancelled ? "cancelled" : "failed" });
|
|
502
|
-
throw error;
|
|
556
|
+
throw normalizeEscapeError(error, state.abortController.signal);
|
|
503
557
|
}
|
|
504
558
|
// A failed combo member stays flagged for this request. Move it behind
|
|
505
559
|
// every candidate that has not failed yet, so the next attempt cannot
|
|
@@ -542,7 +596,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
542
596
|
// as the sleep's own error would leave `proxy_requests_total` missing
|
|
543
597
|
// the request.
|
|
544
598
|
metrics.proxy_requests_total.inc(1, { status: "cancelled" });
|
|
545
|
-
throw lastError;
|
|
599
|
+
throw normalizeEscapeError(lastError, state.abortController.signal);
|
|
546
600
|
}
|
|
547
601
|
}
|
|
548
602
|
}
|
|
@@ -577,5 +631,5 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
577
631
|
}));
|
|
578
632
|
}
|
|
579
633
|
metrics.proxy_requests_total.inc(1, { status: "failed" });
|
|
580
|
-
throw lastError;
|
|
634
|
+
throw normalizeEscapeError(lastError, state.abortController.signal);
|
|
581
635
|
}
|
|
@@ -27,6 +27,7 @@ import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt
|
|
|
27
27
|
import { runAttemptLoop } from "./attempt-loop";
|
|
28
28
|
import { captureProviderRateLimit } from "./ratelimit-capture";
|
|
29
29
|
import {
|
|
30
|
+
forwardUpstreamResponse,
|
|
30
31
|
hasScope,
|
|
31
32
|
jsonResponse,
|
|
32
33
|
networkFetchContext,
|
|
@@ -133,14 +134,7 @@ async function passThrough(
|
|
|
133
134
|
responseBody: null,
|
|
134
135
|
});
|
|
135
136
|
metrics.proxy_requests_total.inc(1, { status: "completed" });
|
|
136
|
-
return
|
|
137
|
-
status: upstream.status,
|
|
138
|
-
headers: {
|
|
139
|
-
"content-type": upstream.headers.get("content-type") ?? "text/event-stream",
|
|
140
|
-
"cache-control": "no-store",
|
|
141
|
-
"x-request-id": state.requestId,
|
|
142
|
-
},
|
|
143
|
-
});
|
|
137
|
+
return forwardUpstreamResponse(upstream, state.requestId, "text/event-stream");
|
|
144
138
|
}
|
|
145
139
|
|
|
146
140
|
/** Builds the handler for one generation surface. */
|
|
@@ -71,6 +71,31 @@ export function jsonResponse(payload: unknown, status: number, requestId: string
|
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Forwards an upstream response body verbatim with the shared native-response
|
|
76
|
+
* header set.
|
|
77
|
+
*
|
|
78
|
+
* The native handlers (System One, responses-compact, token-count,
|
|
79
|
+
* generation's passThrough) each spelled the same three headers by hand; the
|
|
80
|
+
* copies drifted at least once (generation's JSON builder lost `cache-control`).
|
|
81
|
+
* Status and body come from the upstream untouched; only the content-type
|
|
82
|
+
* default differs per route (SSE vs JSON) and is passed per call.
|
|
83
|
+
*/
|
|
84
|
+
export function forwardUpstreamResponse(
|
|
85
|
+
response: Response,
|
|
86
|
+
requestId: string,
|
|
87
|
+
defaultContentType = "application/json",
|
|
88
|
+
): Response {
|
|
89
|
+
return new Response(response.body, {
|
|
90
|
+
status: response.status,
|
|
91
|
+
headers: {
|
|
92
|
+
"content-type": response.headers.get("content-type") ?? defaultContentType,
|
|
93
|
+
"cache-control": "no-store",
|
|
94
|
+
"x-request-id": requestId,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
/**
|
|
75
100
|
* Strips a thinking suffix (`model(high)`) from the model name and rebuilds
|
|
76
101
|
* the body with the bare model when it changed. Returns the bare model and
|
|
@@ -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
|
};
|
|
@@ -23,7 +23,7 @@ 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 {
|
|
26
|
+
import { flagPoolOnEgressFailure } from "../../../network/pool-health";
|
|
27
27
|
import { classifyEgressFailure } from "../../../network/rate-limit-verdict";
|
|
28
28
|
import { releaseAttemptLeases } from "../leases";
|
|
29
29
|
import type { AttemptLeaseOwnership } from "../leases";
|
|
@@ -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" });
|
|
@@ -43,17 +43,11 @@ 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/pool/resolver";
|
|
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.
|