cartethyia 2.7.8 → 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 -3
- package/README.md +29 -59
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
- package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
- package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
- 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-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
- 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-D_0PiZtg.js +1 -0
- 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-emlTuxz6.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-CctTga2C.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
- package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
- package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
- package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
- 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-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
- package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
- package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
- package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
- package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
- package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/favicon.webp +0 -0
- package/dist/dashboard/assets/brand/login_register.webp +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
- package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
- package/dist/dashboard/assets/brand/og_image.webp +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
- package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
- package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
- package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.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-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
- package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BADqIRYP.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-COaC-irN.js → entry-CECVELyy.js} +1 -1
- package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
- package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
- package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
- package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
- package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
- package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
- package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
- package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
- package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
- package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
- package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
- package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
- package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
- package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
- package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
- package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
- package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
- package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
- package/dist/dashboard/assets/providers/devin.svg +9 -0
- package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
- package/dist/dashboard/assets/providers/windsurf.svg +10 -0
- package/dist/dashboard/assets/providers/windsurf.svg.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/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
- package/dist/dashboard/assets/query-keys-IKLh0YFd.js.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-BFQX2VBc.js +1 -0
- package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
- package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
- package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
- package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
- package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
- package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
- package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
- package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
- package/dist/dashboard/assets/story/25817331.webp +0 -0
- package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
- package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
- package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
- package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
- package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
- package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
- package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
- package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
- package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
- package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.webp +0 -0
- package/dist/dashboard/console-backgrounds_initial.webp +0 -0
- package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +59 -56
- package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
- package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
- package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -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 +12 -4
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/cooldowns.ts +1 -1
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +20 -6
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +50 -31
- 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/console-router.ts +7 -0
- package/src/console/dashboard-assets.ts +5 -5
- package/src/console/domain-registration.ts +27 -2
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +93 -0
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/catalog-projections.ts +5 -2
- package/src/console/providers/catalog/contracts.ts +123 -0
- package/src/console/providers/catalog/discover-constants.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +122 -10
- package/src/console/providers/catalog/provider-operations.ts +116 -8
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +65 -4
- package/src/console/providers/catalog/store-helpers.ts +30 -1
- package/src/console/providers/catalog/store.ts +249 -4
- package/src/console/quota/account-quota-global.ts +97 -19
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +6 -18
- package/src/console/quota/native-identity.ts +2 -0
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/operations.ts +31 -46
- package/src/console/settings/contracts.ts +18 -0
- package/src/console/settings/store.ts +32 -0
- package/src/console/shared/errors.ts +33 -6
- package/src/console/system-prompt/contracts.ts +0 -1
- package/src/main.ts +3 -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/event-query.ts +18 -4
- 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 +30 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +28 -2
- package/src/persistence/ttl-cache.ts +20 -2
- package/src/protocol/codecs.ts +1 -1
- package/src/protocol/dsml.ts +35 -87
- package/src/protocol/gateway-error.ts +1 -0
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/reasoning.ts +1 -1
- package/src/protocol/request/chat.ts +14 -14
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/gemini.ts +6 -1
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/sse-decoder.ts +25 -8
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/stream-error-frames.ts +13 -3
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +1 -1
- package/src/protocol/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +15 -4
- package/src/protocol/upstream-parsing.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +78 -17
- 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 +153 -59
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +117 -27
- package/src/providers/discovery/discovery-types.ts +12 -0
- package/src/providers/discovery/probe-phases.ts +74 -33
- package/src/providers/discovery/probing-service.ts +293 -165
- 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 +121 -2
- package/src/providers/integrations/antigravity/antigravity.ts +13 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
- package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
- package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
- package/src/providers/integrations/buddy/buddy-media.ts +4 -5
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
- package/src/providers/integrations/buddy/capabilities.ts +6 -43
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
- 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-catalog.ts +2 -0
- package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
- package/src/providers/integrations/cursor/cursor-request.ts +35 -20
- package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
- package/src/providers/integrations/cursor/cursor.ts +139 -70
- 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/cursor/protocol/tool-canonical.ts +26 -100
- package/src/providers/integrations/devin/devin-auth.ts +353 -0
- package/src/providers/integrations/devin/devin-catalog.ts +207 -0
- package/src/providers/integrations/devin/devin-http.ts +91 -0
- package/src/providers/integrations/devin/devin-quota.ts +214 -0
- package/src/providers/integrations/devin/devin-shared.ts +111 -0
- package/src/providers/integrations/devin/devin.ts +539 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
- package/src/providers/integrations/devin/protocol/framing.ts +124 -0
- package/src/providers/integrations/factory/factory-oauth.ts +17 -44
- package/src/providers/integrations/factory/factory.ts +2 -2
- 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 +60 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
- package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
- package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
- package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
- package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
- package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
- package/src/providers/integrations/freepi/freepi.ts +292 -0
- package/src/providers/integrations/gemini.ts +2 -2
- 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/hermes-oauth.ts +94 -0
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
- 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/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/perch/perch-catalog.ts +105 -0
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-oauth.ts +170 -0
- package/src/providers/integrations/perch/perch-protocol.ts +208 -0
- package/src/providers/integrations/perch/perch-quota.ts +123 -0
- package/src/providers/integrations/perch/perch-realm.ts +82 -0
- package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
- package/src/providers/integrations/perch/perch.ts +305 -0
- package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
- package/src/providers/integrations/qoder/chat-proto.ts +328 -328
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
- package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
- package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
- package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
- package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
- package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
- package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
- package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
- package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
- package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
- package/src/providers/integrations/qoder/qoder.ts +3 -7
- package/src/providers/integrations/trae/trae-catalog.ts +9 -14
- package/src/providers/integrations/trae/trae-headers.ts +36 -11
- package/src/providers/integrations/trae/trae-oauth.ts +14 -17
- package/src/providers/integrations/trae/trae-protocol.ts +12 -11
- package/src/providers/integrations/trae/trae-quota.ts +96 -36
- package/src/providers/integrations/trae/trae-realm.ts +74 -15
- package/src/providers/integrations/trae/trae.ts +99 -26
- 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 +235 -278
- package/src/providers/operations/account-health-service.ts +273 -26
- package/src/providers/operations/account-refresh-status.ts +269 -0
- package/src/providers/operations/account-tier.ts +66 -0
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +42 -150
- package/src/providers/operations/cooldown-rules.ts +56 -0
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-seeder.ts +4 -0
- 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 +33 -2
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +99 -14
- package/src/providers/provider-model-definition.ts +36 -0
- 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 +96 -18
- package/src/security/abuse.ts +71 -0
- 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 +9 -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 +9 -36
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +67 -1
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +129 -37
- 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 +418 -33
- 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 +9 -2
- 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 +34 -6
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/daily-checkin.ts +1 -1
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/scheduler.ts +22 -4
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
- package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
- package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
- package/dist/dashboard/assets/brand/bansos_page.png +0 -0
- package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/favicon.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
- package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
- package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
- package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
- package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
- package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
- package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
- package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
- package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
- package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
- package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
- package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-YSoQOUh6.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-fnCsxgNO.js +0 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
- package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
- package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
- package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
- package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
- package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
- package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
- 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-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
- /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
|
@@ -11,12 +11,14 @@ import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
|
11
11
|
import { providerAccounts } from "../../persistence/schema";
|
|
12
12
|
import type { RedisClient } from "../../persistence/redis";
|
|
13
13
|
import { log } from "../../observability/logger";
|
|
14
|
+
import { pushStructuredConsoleLog } from "../../observability/log-ring";
|
|
14
15
|
import { fetchProviderQuota } from "./quota-support";
|
|
15
16
|
import { type FetchLike, type ProviderQuotaResult } from "./quota-contracts";
|
|
16
17
|
import type { ProviderId } from "../provider-contracts";
|
|
17
18
|
import type { ProviderRegistry } from "../provider-registry";
|
|
18
19
|
import { quotaLens, setCachedQuota } from "./cache";
|
|
19
20
|
import { persistQuotaSnapshot } from "./snapshot";
|
|
21
|
+
import { applyAccountVerdict } from "../operations/account-health-service";
|
|
20
22
|
import { asJsonbRecord } from "../../shared/coerce";
|
|
21
23
|
import { KIRO_DEFAULT_LABELS } from "../integrations/kiro/kiro-oauth";
|
|
22
24
|
|
|
@@ -149,12 +151,52 @@ export async function recordAccountCheck(
|
|
|
149
151
|
db: CartethyiaDatabase,
|
|
150
152
|
accountId: string,
|
|
151
153
|
outcome: { ok: boolean; error?: string | null; category?: string | null },
|
|
154
|
+
snapshotInvalidator?: { invalidate(): unknown },
|
|
152
155
|
): Promise<void> {
|
|
153
|
-
const activeOnly = and(
|
|
154
|
-
eq(providerAccounts.id, accountId),
|
|
155
|
-
eq(providerAccounts.status, "active"),
|
|
156
|
-
);
|
|
157
156
|
if (outcome.ok) {
|
|
157
|
+
// A successful quota refresh is the freshest evidence about the account's
|
|
158
|
+
// health: heal a cooldown that the refresh shows is no longer justified, so
|
|
159
|
+
// the account returns to rotation immediately instead of waiting out the
|
|
160
|
+
// remainder of a stale fallback window. Only healable scopes heal — an
|
|
161
|
+
// auth/policy cooldown must never be lifted by a quota report (the
|
|
162
|
+
// credential is still refused). `lastErrorCategory` carries the category
|
|
163
|
+
// that parked the account, so the heal is scoped by the same verdict the
|
|
164
|
+
// health machine wrote.
|
|
165
|
+
const healableCategories = new Set([
|
|
166
|
+
"quota_exhausted",
|
|
167
|
+
"rate_limit_transient",
|
|
168
|
+
"model_capacity",
|
|
169
|
+
"model_unreachable",
|
|
170
|
+
"unknown",
|
|
171
|
+
"check_failed",
|
|
172
|
+
]);
|
|
173
|
+
const rows = await db
|
|
174
|
+
.select({ status: providerAccounts.status, lastErrorCategory: providerAccounts.lastErrorCategory })
|
|
175
|
+
.from(providerAccounts)
|
|
176
|
+
.where(eq(providerAccounts.id, accountId))
|
|
177
|
+
.limit(1);
|
|
178
|
+
const account = rows[0];
|
|
179
|
+
const healable =
|
|
180
|
+
account?.status === "cooldown" &&
|
|
181
|
+
(account.lastErrorCategory === null || healableCategories.has(account.lastErrorCategory));
|
|
182
|
+
if (healable) {
|
|
183
|
+
const healed = await db
|
|
184
|
+
.update(providerAccounts)
|
|
185
|
+
.set({
|
|
186
|
+
status: "active",
|
|
187
|
+
lastSuccessAt: new Date(),
|
|
188
|
+
lastError: null,
|
|
189
|
+
lastErrorCategory: null,
|
|
190
|
+
lastErrorAt: null,
|
|
191
|
+
consecutiveFailures: 0,
|
|
192
|
+
cooldownUntil: null,
|
|
193
|
+
lastRecoveredAt: new Date(),
|
|
194
|
+
})
|
|
195
|
+
.where(eq(providerAccounts.id, accountId))
|
|
196
|
+
.returning({ id: providerAccounts.id });
|
|
197
|
+
if (healed.length > 0) await snapshotInvalidator?.invalidate();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
158
200
|
await db
|
|
159
201
|
.update(providerAccounts)
|
|
160
202
|
.set({
|
|
@@ -164,7 +206,7 @@ export async function recordAccountCheck(
|
|
|
164
206
|
lastErrorAt: null,
|
|
165
207
|
consecutiveFailures: 0,
|
|
166
208
|
})
|
|
167
|
-
.where(
|
|
209
|
+
.where(and(eq(providerAccounts.id, accountId), eq(providerAccounts.status, "active")));
|
|
168
210
|
return;
|
|
169
211
|
}
|
|
170
212
|
const transient = isTransientTimeout(outcome.error);
|
|
@@ -185,7 +227,7 @@ export async function recordAccountCheck(
|
|
|
185
227
|
lastErrorAt: new Date(),
|
|
186
228
|
...(transient ? {} : { consecutiveFailures: sql`${providerAccounts.consecutiveFailures} + 1` }),
|
|
187
229
|
})
|
|
188
|
-
.where(
|
|
230
|
+
.where(and(eq(providerAccounts.id, accountId), eq(providerAccounts.status, "active")))
|
|
189
231
|
.returning({ consecutiveFailures: providerAccounts.consecutiveFailures });
|
|
190
232
|
if (transient) return;
|
|
191
233
|
const failures = failing[0]?.consecutiveFailures ?? 0;
|
|
@@ -195,13 +237,18 @@ export async function recordAccountCheck(
|
|
|
195
237
|
// pass. The cooldown clears itself; a success before it elapses resets the
|
|
196
238
|
// count above.
|
|
197
239
|
if (failures >= QUOTA_FAILURE_PARK_THRESHOLD) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
.
|
|
240
|
+
// Converge on the health application seam (proposal §3.5): quota refresh
|
|
241
|
+
// parking is a producer into `applyAccountVerdict`, not a separate state
|
|
242
|
+
// machine.
|
|
243
|
+
const parked = await applyAccountVerdict(db, {
|
|
244
|
+
accountId,
|
|
245
|
+
status: "cooldown",
|
|
246
|
+
reason: outcome.error ?? "Account check failed",
|
|
247
|
+
category: outcome.category ?? "quota_fetch_failed",
|
|
248
|
+
cooldownUntil: new Date(Date.now() + QUOTA_FAILURE_COOLDOWN_MS),
|
|
249
|
+
fromStatus: "active",
|
|
250
|
+
});
|
|
251
|
+
if (parked) await snapshotInvalidator?.invalidate();
|
|
205
252
|
}
|
|
206
253
|
}
|
|
207
254
|
|
|
@@ -261,6 +308,12 @@ const TARGET_COLUMNS = {
|
|
|
261
308
|
* disabled account is not necessarily revoked — it may have been parked for a
|
|
262
309
|
* reason unrelated to its credential — and skipping it would let a credential
|
|
263
310
|
* die silently while the row still reads healthy.
|
|
311
|
+
*
|
|
312
|
+
* Invariant (area 04, D21): this sweep **never flips status**. The refresh it
|
|
313
|
+
* kicks off rewrites only credential/expiry columns; `recordAccountCheck` is
|
|
314
|
+
* active-only, so `disabled` rows are identical after the pass to before. The
|
|
315
|
+
* `origin: "sweep:quota"` tag on the token-refresh log lines (D26) is what
|
|
316
|
+
* lets an operator verify that in the Console Log.
|
|
264
317
|
*/
|
|
265
318
|
export async function listQuotaRefreshTargets(
|
|
266
319
|
db: CartethyiaDatabase,
|
|
@@ -343,6 +396,20 @@ async function runQuotaRefresh(
|
|
|
343
396
|
let resolveError: string | undefined;
|
|
344
397
|
try {
|
|
345
398
|
credential = await deps.resolveCredential(target.providerId as ProviderId, target.accountId);
|
|
399
|
+
// D26: every OAuth credential this sweep resolves may have been refreshed
|
|
400
|
+
// inside the refresh-aware resolver. Label that refresh with its origin so
|
|
401
|
+
// the Console Log can prove the sweep touches disabled accounts without
|
|
402
|
+
// ever flipping their status. The refresh service itself also logs the
|
|
403
|
+
// actual token refresh under the `sweep` origin family; this line names
|
|
404
|
+
// the sweep here, at the source of the trigger.
|
|
405
|
+
if (credential !== "" && target.credentialKind === "oauth") {
|
|
406
|
+
pushStructuredConsoleLog("debug", "Quota sweep resolved OAuth credential; token may have been refreshed", {
|
|
407
|
+
event: "token_refresh",
|
|
408
|
+
accountId: target.accountId,
|
|
409
|
+
providerId: target.providerId,
|
|
410
|
+
details: { origin: "sweep:quota" },
|
|
411
|
+
});
|
|
412
|
+
}
|
|
346
413
|
} catch (error) {
|
|
347
414
|
resolveError = error instanceof Error ? error.message : "Failed to resolve credential";
|
|
348
415
|
}
|
|
@@ -406,7 +473,13 @@ async function runQuotaRefresh(
|
|
|
406
473
|
try {
|
|
407
474
|
await deps.db
|
|
408
475
|
.update(providerAccounts)
|
|
409
|
-
.set({
|
|
476
|
+
.set({
|
|
477
|
+
quotaPlan: quota.plan,
|
|
478
|
+
// Freshness stamp for the plan column: routing uses it to downgrade
|
|
479
|
+
// a plan older than the freshness horizon to `unknown` instead of
|
|
480
|
+
// trusting a weeks-old string.
|
|
481
|
+
quotaPlanFetchedAt: new Date(),
|
|
482
|
+
})
|
|
410
483
|
.where(eq(providerAccounts.id, target.accountId));
|
|
411
484
|
} catch (error) {
|
|
412
485
|
log.warn(`[quota] failed to record plan for account=${target.accountId}`, error as Error);
|
|
@@ -448,10 +521,15 @@ async function runQuotaRefresh(
|
|
|
448
521
|
}
|
|
449
522
|
}
|
|
450
523
|
try {
|
|
451
|
-
await recordAccountCheck(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
524
|
+
await recordAccountCheck(
|
|
525
|
+
deps.db,
|
|
526
|
+
target.accountId,
|
|
527
|
+
{
|
|
528
|
+
ok: quota.error === null,
|
|
529
|
+
...(quota.error === null ? {} : { error: quota.error, category: failure ?? "quota_fetch_failed" }),
|
|
530
|
+
},
|
|
531
|
+
deps.snapshotInvalidator,
|
|
532
|
+
);
|
|
455
533
|
} catch (error) {
|
|
456
534
|
// A stamp failure must not lose the freshly fetched quota.
|
|
457
535
|
log.warn(`[quota] failed to stamp check outcome for account=${target.accountId}`, error as Error);
|
package/src/security/abuse.ts
CHANGED
|
@@ -113,6 +113,15 @@ export interface IpAbuseStore {
|
|
|
113
113
|
* bounded `admission_unavailable` with zero dispatches.
|
|
114
114
|
*/
|
|
115
115
|
checkAndRecord(attempt: IpAbuseAttempt): Promise<IpAbuseOutcome>;
|
|
116
|
+
/**
|
|
117
|
+
* Manually bans an identity for `durationMs` (0/negative = permanent).
|
|
118
|
+
* Throws when the store is unreachable.
|
|
119
|
+
*/
|
|
120
|
+
ban(identity: string, durationMs: number): Promise<void>;
|
|
121
|
+
/** Lifts a manual (or any) ban on an identity. No-op when not banned. */
|
|
122
|
+
unban(identity: string): Promise<void>;
|
|
123
|
+
/** Lists currently active bans with their expiry (`null` = permanent). */
|
|
124
|
+
listBans(): Promise<readonly { identity: string; bannedUntil: number | null }[]>;
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
interface IpAbuseProtectionServiceOptions {
|
|
@@ -293,6 +302,34 @@ export class InMemoryIpAbuseStore implements IpAbuseStore {
|
|
|
293
302
|
return true;
|
|
294
303
|
}
|
|
295
304
|
|
|
305
|
+
async ban(identity: string, durationMs: number): Promise<void> {
|
|
306
|
+
this.assertAvailable();
|
|
307
|
+
this.evictOldest(this.bans);
|
|
308
|
+
this.bans.set(identity, durationMs > 0 ? this.clock() + durationMs : Number.POSITIVE_INFINITY);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async unban(identity: string): Promise<void> {
|
|
312
|
+
this.assertAvailable();
|
|
313
|
+
this.bans.delete(identity);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async listBans(): Promise<readonly { identity: string; bannedUntil: number | null }[]> {
|
|
317
|
+
this.assertAvailable();
|
|
318
|
+
const now = this.clock();
|
|
319
|
+
const out: { identity: string; bannedUntil: number | null }[] = [];
|
|
320
|
+
for (const [identity, until] of this.bans) {
|
|
321
|
+
if (until <= now) {
|
|
322
|
+
this.bans.delete(identity);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
out.push({
|
|
326
|
+
identity,
|
|
327
|
+
bannedUntil: Number.isFinite(until) ? until : null,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
|
|
296
333
|
async checkAndRecord(attempt: IpAbuseAttempt): Promise<IpAbuseOutcome> {
|
|
297
334
|
this.assertAvailable();
|
|
298
335
|
const { identity, route, now, limit, banThreshold, banDurationMs } = attempt;
|
|
@@ -582,5 +619,39 @@ export class RedisIpAbuseStore implements IpAbuseStore {
|
|
|
582
619
|
);
|
|
583
620
|
return decodeAbuseOutcome(raw);
|
|
584
621
|
}
|
|
622
|
+
|
|
623
|
+
async ban(identity: string, durationMs: number): Promise<void> {
|
|
624
|
+
const marker = this.banMarkerKey(identity);
|
|
625
|
+
const now = Date.now();
|
|
626
|
+
if (durationMs > 0) {
|
|
627
|
+
await this.redis.set(marker, String(now + durationMs), "PX", durationMs);
|
|
628
|
+
} else {
|
|
629
|
+
// Permanent: no TTL; a large sentinel far in the future.
|
|
630
|
+
await this.redis.set(marker, String(Number.MAX_SAFE_INTEGER));
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async unban(identity: string): Promise<void> {
|
|
635
|
+
await this.redis.del(this.banMarkerKey(identity));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async listBans(): Promise<readonly { identity: string; bannedUntil: number | null }[]> {
|
|
639
|
+
const keys = await this.redis.keys("cartethyia:ip:ban:*");
|
|
640
|
+
const now = Date.now();
|
|
641
|
+
const out: { identity: string; bannedUntil: number | null }[] = [];
|
|
642
|
+
for (const key of keys) {
|
|
643
|
+
const value = await this.redis.get(key);
|
|
644
|
+
const until = Number(value ?? 0);
|
|
645
|
+
if (!Number.isFinite(until) || until <= now) {
|
|
646
|
+
await this.redis.del(key);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
out.push({
|
|
650
|
+
identity: key.slice("cartethyia:ip:ban:".length),
|
|
651
|
+
bannedUntil: until >= Number.MAX_SAFE_INTEGER - 1 ? null : until,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
585
656
|
}
|
|
586
657
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Bounded process-local authorization cache; shared-cache I/O stays in the auth service. */
|
|
2
2
|
export const AUTH_CACHE_TTL_MS = 3_000;
|
|
3
3
|
const AUTH_CACHE_MAX_ENTRIES = 10_000;
|
|
4
|
-
const AUTH_NEGATIVE_CACHE_TTL_MS = 1_500;
|
|
4
|
+
export const AUTH_NEGATIVE_CACHE_TTL_MS = 1_500;
|
|
5
5
|
const AUTH_FAILURE_COOLDOWN_MS = 1_000;
|
|
6
6
|
|
|
7
7
|
interface CacheEntry<T> {
|
|
@@ -21,8 +21,12 @@ import type { ApiKeyModelAccessMode } from "../persistence/schema";
|
|
|
21
21
|
* snapshot's consumed value is a reference point, so a brief stale window
|
|
22
22
|
* cannot meaningfully over-consume a budget.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
* a
|
|
24
|
+
* Unknown or revoked tokens are negative-cached for
|
|
25
|
+
* `AUTH_NEGATIVE_CACHE_TTL_MS` (1.5s): a token rejected within that window
|
|
26
|
+
* stays refused even if its key is minted or un-revoked in between. A newly
|
|
27
|
+
* minted key is usable immediately only because `invalidateApiKeyCache`
|
|
28
|
+
* clears the negative entries when a mutation lands — not because negatives
|
|
29
|
+
* are absent.
|
|
26
30
|
*/
|
|
27
31
|
const authorizationCache = createApiKeyAuthorizationCache<ResolvedApiKey>();
|
|
28
32
|
|
|
@@ -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";
|
|
@@ -263,45 +257,14 @@ export async function handleProviderProxyRequest(
|
|
|
263
257
|
...(conversationAffinity ? { conversationAffinity } : {}),
|
|
264
258
|
...(tenantSystemPromptText === undefined ? {} : { tenantSystemPrompt: tenantSystemPromptText }),
|
|
265
259
|
...(candidate.user_agent === undefined ? {} : { userAgent: candidate.user_agent }),
|
|
266
|
-
...(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
await shouldCaptureProviderBodyForRequest(
|
|
275
|
-
deps.db,
|
|
276
|
-
prepared.authorization.snapshot.tenant_id,
|
|
277
|
-
state,
|
|
278
|
-
),
|
|
279
|
-
providerCaptureMaxBytes(
|
|
280
|
-
await resolveCaptureDepthForRequest(
|
|
281
|
-
deps.db,
|
|
282
|
-
prepared.authorization.snapshot.tenant_id,
|
|
283
|
-
state,
|
|
284
|
-
),
|
|
285
|
-
),
|
|
286
|
-
),
|
|
287
|
-
}
|
|
288
|
-
: {}),
|
|
289
|
-
...(deps.networkBindingFactory
|
|
290
|
-
? {
|
|
291
|
-
outboundAttachmentFetch: deps.networkBindingFactory.fetch(
|
|
292
|
-
networkPoolId,
|
|
293
|
-
prepared.authorization.snapshot.tenant_id,
|
|
294
|
-
),
|
|
295
|
-
}
|
|
296
|
-
: {}),
|
|
297
|
-
...(deps.networkBindingFactory
|
|
298
|
-
? {
|
|
299
|
-
outboundHttp2: deps.networkBindingFactory.http2Bidi(
|
|
300
|
-
networkPoolId,
|
|
301
|
-
prepared.authorization.snapshot.tenant_id,
|
|
302
|
-
),
|
|
303
|
-
}
|
|
304
|
-
: {}),
|
|
260
|
+
...(providerCapture ? { providerCapture } : {}),
|
|
261
|
+
...(await buildDispatchNetworkBindings({
|
|
262
|
+
deps,
|
|
263
|
+
networkPoolId,
|
|
264
|
+
tenantId: prepared.authorization.snapshot.tenant_id,
|
|
265
|
+
providerCapture,
|
|
266
|
+
state,
|
|
267
|
+
})),
|
|
305
268
|
...(leaseOwnership === undefined ? {} : { leaseOwnership }),
|
|
306
269
|
...(continuationScope === undefined ? {} : { continuationScope }),
|
|
307
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
|
};
|