cartethyia 2.7.0 → 2.7.2
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 +107 -106
- package/README.md +25 -2
- package/dist/dashboard/assets/{BannedPage-P76QLGJA.js → BannedPage-XaUmiBrC.js} +1 -1
- package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-DdBggJS9.js → CliToolDetailPage-CqD7QiLM.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-aN1iXhhP.js → CliToolsPage-0XUo4UMn.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-DE-F1nxZ.js → CombosPage-CXGS_Ct-.js} +1 -1
- package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-3CUxMy2X.js → CustomizationPage-DAG5J9EY.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js +1 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-DKVslqG3.js → ModelPicker-BH8Tehdi.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +1 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +4 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +9 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-BjldQwIU.js → QuotaPage-7P2n--Fv.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-DUIoLRxk.js → SettingsPage-CxZ1fTkN.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +1 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-fI7JkdIB.js → SortableList-Dqf4zpS-.js} +1 -1
- package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CHVZqskZ.js → StudioPage-Byj2lAEm.js} +3 -3
- package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js +56 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DFY_fZU4.js → api-keys-CUi6UXsR.js} +1 -1
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/{assert-CwdnZARt.js → assert-67xXH5rf.js} +1 -1
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/{badge-Dr-uf0Au.js → badge-DC2L43Mj.js} +1 -1
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BddmMdoS.js → banner-BBVcb0J3.js} +1 -1
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/{card-CK0PXWlX.js → card-Cca1XC6G.js} +1 -1
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CcB6mGh2.js → cli-tools-KvsWa1Jl.js} +1 -1
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DGb2P9Yx.js → clipboard-button-DUzEbynq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-LkfuL50u.js → dialog-DFvWhwTa.js} +1 -1
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-ZbEZ_84c.js → drawer-CFM2sMX1.js} +1 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BJfsVMvA.js → entry-Bqy8RFvM.js} +1 -1
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CWBY5A0f.js → entry-ClCMCWWQ.js} +3 -3
- package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CvC1-Zqx.js → entry-_xHo5x6r.js} +1 -1
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js +1 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BxpznFLt.js → github-badge-C9e8Ts7e.js} +1 -1
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/{index-CDzP3ovJ.js → index-DFVFrnWW.js} +2 -2
- package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
- package/dist/dashboard/assets/{inline-Ae13MQ5w.js → inline-BFUcvllj.js} +1 -1
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/{input-D23fo7cp.js → input-C_ILAvBb.js} +1 -1
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CSsxjj5P.js → layout-CBIyT6Rt.js} +1 -1
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-dmbwZMDY.js → list-row-ebJ_-Nsb.js} +1 -1
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/{live-BG1oUsZZ.js → live-D0mm--ME.js} +1 -1
- package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-DSF-e4ho.js → mutation-sMD965oO.js} +1 -1
- package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-Bm_aECY7.js → numbers-BO9dKaFr.js} +1 -1
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/providers/inception.svg +5 -0
- package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CF56nYIV.js → query-keys-CfL_Zdoh.js} +1 -1
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CeA71n7G.js → routing-BnIpi1IJ.js} +1 -1
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/{select-KIT0rC6G.js → select-DBNVLdUu.js} +1 -1
- package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
- package/dist/dashboard/assets/{settings-B_2gkePf.js → settings-BAFhDgX8.js} +1 -1
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/{stack-D7WOSzcX.js → stack-EMrOMt4I.js} +1 -1
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/{state-BBxtKZOQ.js → state-CWoo7ty5.js} +1 -1
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DNS_D5Ea.js → switch-CCjT6IkF.js} +1 -1
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BgoxdoMk.js → useMutation-DjkLHUcE.js} +1 -1
- package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -53
- package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
- package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
- package/package.json +96 -96
- package/scripts/cli/console-menu.ts +5 -2
- package/scripts/cli/tray.ts +21 -6
- package/src/boot/app.ts +684 -714
- package/src/boot/dependencies.ts +65 -62
- package/src/config/cooldowns.ts +16 -0
- package/src/config/providers.ts +13 -1
- package/src/config/resources.ts +70 -70
- package/src/config/security.ts +37 -37
- package/src/config/spec.ts +371 -358
- package/src/console/cli-tools/contracts.ts +10 -4
- package/src/console/console-router.ts +233 -225
- package/src/console/domain-registration.ts +398 -381
- package/src/console/providers/catalog/catalog-projections.ts +2 -6
- package/src/console/providers/catalog/contracts.ts +19 -0
- package/src/console/providers/catalog/provider-operations.ts +38 -0
- package/src/console/providers/catalog/routes.ts +14 -0
- package/src/console/providers/catalog/store-helpers.ts +326 -0
- package/src/console/providers/catalog/store.ts +1292 -1563
- package/src/console/quota/account-quota-global.ts +2 -2
- package/src/console/quota/account-quota-shared.ts +1 -1
- package/src/console/quota/account-quota-tenant.ts +35 -12
- package/src/console/quota/quota-view.ts +2 -2
- package/src/console/routing/pools/contracts.ts +41 -1
- package/src/console/routing/pools/operations.ts +183 -4
- package/src/console/routing/pools/relay-deploy.ts +17 -4
- package/src/console/routing/pools/routes.ts +30 -0
- package/src/console/routing/pools/store.ts +98 -8
- package/src/main.ts +183 -167
- package/src/network/outbound-fetch.ts +124 -208
- package/src/network/pool/agent.ts +15 -0
- package/src/network/pool/pool-state.ts +7 -0
- package/src/network/pool/selector.ts +110 -2
- package/src/network/pool-cooldown-store.ts +252 -0
- package/src/network/pool-health-machine.ts +5 -3
- package/src/network/pool-health.ts +36 -9
- package/src/observability/event-query.ts +57 -0
- package/src/observability/metrics.ts +467 -467
- package/src/observability/runtime-metrics.ts +269 -269
- package/src/persistence/health-event-flush.ts +2 -0
- package/src/persistence/schema.ts +19 -0
- package/src/persistence/usage-flush.ts +8 -3
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/dsml.ts +377 -0
- package/src/protocol/gateway-error.ts +232 -232
- package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
- package/src/protocol/request/chat.ts +7 -7
- package/src/protocol/request/codex.ts +2 -2
- package/src/protocol/request/gemini.ts +1 -1
- package/src/protocol/request/messages.ts +6 -6
- package/src/protocol/request/responses.ts +7 -7
- package/src/protocol/response/dsml.ts +12 -133
- package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
- package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
- package/src/{transport → protocol}/translation/capabilities.ts +2 -2
- package/src/{transport → protocol}/translation/normalize.ts +1 -1
- package/src/{transport → protocol}/translation/quirks.ts +1 -1
- package/src/{transport → protocol}/translation/search.ts +1 -1
- package/src/{transport → protocol}/translation/thinking.ts +23 -1
- package/src/{transport → protocol}/translation/tool-id.ts +1 -1
- package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream-parsing.ts +176 -0
- package/src/providers/adapters/compatible-adapter.ts +11 -2
- package/src/providers/authentication/oauth-refresh-service.ts +9 -5
- package/src/providers/default-registry.ts +22 -5
- package/src/providers/discovery/probe-phases.ts +16 -3
- package/src/providers/discovery/probing-service.ts +4 -1
- package/src/providers/integrations/anthropic.ts +1 -1
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
- package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
- package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
- package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
- package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
- package/src/providers/integrations/claude/claude.ts +1 -1
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +1 -1
- package/src/providers/integrations/factory/factory.ts +1 -1
- package/src/providers/integrations/github/github.ts +1 -1
- package/src/providers/integrations/grok/grok.ts +1 -1
- package/src/providers/integrations/inception/provider.ts +91 -0
- package/src/providers/integrations/inferhub.ts +1 -1
- package/src/providers/integrations/kiro/kiro-request.ts +1 -1
- package/src/providers/integrations/mistral.ts +193 -19
- package/src/providers/integrations/ollamacloud-models.ts +50 -0
- package/src/providers/integrations/opencode/fingerprint.ts +19 -2
- package/src/providers/integrations/opencode/prompt.ts +111 -0
- package/src/providers/integrations/opencode/provider.ts +101 -23
- package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
- package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +12 -1
- package/src/providers/integrations/qoder/qoder-realm.ts +20 -0
- package/src/providers/integrations/qoder/qoder-text-tools.ts +277 -0
- package/src/providers/integrations/qoder/qoder.ts +315 -400
- package/src/providers/integrations/shared/install-id.ts +2 -1
- package/src/providers/integrations/trae/trae-cache.ts +110 -0
- package/src/providers/integrations/trae/trae-oauth.ts +29 -4
- package/src/providers/integrations/trae/trae-protocol.ts +2 -1
- package/src/providers/integrations/trae/trae.ts +106 -23
- package/src/providers/operations/account-error-classification.ts +1684 -0
- package/src/providers/operations/account-health-service.ts +27 -1503
- package/src/providers/operations/account-reset-service.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +11 -1
- package/src/providers/provider-contracts.ts +7 -4
- package/src/providers/provider-metadata.ts +25 -4
- package/src/providers/provider-probe.ts +2 -2
- package/src/{console → providers}/quota/cache.ts +1 -1
- package/src/{console → providers}/quota/refresh.ts +5 -5
- package/src/security/cli-client-fingerprint.ts +9 -2
- package/src/transport/dispatch/attempt-finalize.ts +114 -12
- package/src/transport/dispatch/attempt-loop.ts +3 -0
- package/src/transport/dispatch/empty-stream.ts +9 -2
- package/src/transport/dispatch/generation.ts +37 -55
- package/src/transport/dispatch/handler-deps.ts +32 -0
- package/src/transport/dispatch/leases.ts +9 -4
- package/src/transport/dispatch/proxy-request.ts +6 -30
- package/src/transport/dispatch/responses-compact.ts +22 -43
- package/src/transport/dispatch/streaming/finalize.ts +3 -0
- package/src/transport/dispatch/streaming/stream.ts +0 -8
- package/src/transport/dispatch/systemone.ts +22 -42
- package/src/transport/dispatch/token-count.ts +23 -39
- package/src/transport/dispatch/upstream.ts +1 -3
- package/src/transport/dispatch/websearch.ts +22 -41
- package/src/transport/failure-policy.ts +92 -178
- package/src/transport/guards/abuse-guard.ts +10 -0
- package/src/transport/guards/auth-guard.ts +109 -109
- package/src/transport/guards/error-lifecycle.ts +1 -1
- package/src/transport/guards/pipeline.ts +220 -220
- package/src/transport/guards/request-context.ts +245 -238
- package/src/transport/request/degradation.ts +20 -1
- package/src/transport/request/inflight.ts +4 -114
- package/src/transport/request/preparer.ts +6 -6
- package/src/transport/request/state.ts +66 -38
- package/src/transport/request/tenant-preferences.ts +1 -1
- package/src/transport/routing/route-catalog.ts +75 -43
- package/src/transport/routing/router.ts +30 -2
- package/src/transport/surface/messages/parse.ts +1 -1
- package/src/transport/surface/responses/encode.ts +2 -3
- package/src/transport/surface/responses/parse.ts +49 -7
- package/src/types/undici.d.ts +33 -0
- package/src/workers/buddy-growth-tasks.ts +346 -425
- package/src/workers/daily-checkin.ts +17 -6
- package/src/workers/quota-refresh-sweep.ts +2 -2
- package/src/workers/scheduler.ts +17 -1
- package/dist/dashboard/assets/BannedPage-P76QLGJA.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DdBggJS9.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-aN1iXhhP.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DE-F1nxZ.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-3CUxMy2X.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-B_0FUKWc.js +0 -1
- package/dist/dashboard/assets/LoginPage-B_0FUKWc.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-DKVslqG3.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js +0 -1
- package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js +0 -7
- package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BjldQwIU.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-_bCaTM0v.js +0 -1
- package/dist/dashboard/assets/SetupPage-_bCaTM0v.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-fI7JkdIB.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CHVZqskZ.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DiFBymHj.js +0 -56
- package/dist/dashboard/assets/UsagePage-DiFBymHj.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DFY_fZU4.js.gz +0 -0
- package/dist/dashboard/assets/app-BOJ1_xUB.css +0 -2
- package/dist/dashboard/assets/app-BOJ1_xUB.css.gz +0 -0
- package/dist/dashboard/assets/assert-CwdnZARt.js.gz +0 -0
- package/dist/dashboard/assets/badge-Dr-uf0Au.js.gz +0 -0
- package/dist/dashboard/assets/banner-BddmMdoS.js.gz +0 -0
- package/dist/dashboard/assets/card-CK0PXWlX.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CcB6mGh2.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DGb2P9Yx.js.gz +0 -0
- package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
- package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
- package/dist/dashboard/assets/dialog-LkfuL50u.js.gz +0 -0
- package/dist/dashboard/assets/drawer-ZbEZ_84c.js.gz +0 -0
- package/dist/dashboard/assets/entry-BJfsVMvA.js.gz +0 -0
- package/dist/dashboard/assets/entry-CWBY5A0f.js.gz +0 -0
- package/dist/dashboard/assets/entry-CvC1-Zqx.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BxpznFLt.js.gz +0 -0
- package/dist/dashboard/assets/index-CDzP3ovJ.js.gz +0 -0
- package/dist/dashboard/assets/inline-Ae13MQ5w.js.gz +0 -0
- package/dist/dashboard/assets/input-D23fo7cp.js.gz +0 -0
- package/dist/dashboard/assets/layout-CSsxjj5P.js.gz +0 -0
- package/dist/dashboard/assets/list-row-dmbwZMDY.js.gz +0 -0
- package/dist/dashboard/assets/live-BG1oUsZZ.js.gz +0 -0
- package/dist/dashboard/assets/mutation-DSF-e4ho.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bm_aECY7.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CF56nYIV.js.gz +0 -0
- package/dist/dashboard/assets/routing-CeA71n7G.js.gz +0 -0
- package/dist/dashboard/assets/select-KIT0rC6G.js.gz +0 -0
- package/dist/dashboard/assets/settings-B_2gkePf.js.gz +0 -0
- package/dist/dashboard/assets/stack-D7WOSzcX.js.gz +0 -0
- package/dist/dashboard/assets/state-BBxtKZOQ.js.gz +0 -0
- package/dist/dashboard/assets/switch-DNS_D5Ea.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BgoxdoMk.js.gz +0 -0
- /package/dist/dashboard/assets/{app-CaXZ9SCn.js → app-BaWd0puM.js} +0 -0
- /package/dist/dashboard/assets/{app-CaXZ9SCn.js.gz → app-BaWd0puM.js.gz} +0 -0
|
@@ -642,6 +642,7 @@ async function recordResetActivity(
|
|
|
642
642
|
})
|
|
643
643
|
.where(eq(providerAccounts.id, accountId));
|
|
644
644
|
await db.insert(healthEvents).values({
|
|
645
|
+
id: randomUUID(),
|
|
645
646
|
entityKind: "account",
|
|
646
647
|
accountId,
|
|
647
648
|
fromStatus: currentStatus,
|
|
@@ -655,6 +656,7 @@ async function recordResetActivity(
|
|
|
655
656
|
}
|
|
656
657
|
|
|
657
658
|
await db.insert(healthEvents).values({
|
|
659
|
+
id: randomUUID(),
|
|
658
660
|
entityKind: "account",
|
|
659
661
|
accountId,
|
|
660
662
|
fromStatus: currentStatus,
|
|
@@ -338,7 +338,17 @@ export async function registerByokProviders(
|
|
|
338
338
|
provider_id: providerId,
|
|
339
339
|
base_url: baseUrl,
|
|
340
340
|
authentication_header_shape: wireProfile.authHeaderShape,
|
|
341
|
-
buildExtraHeaders: (
|
|
341
|
+
buildExtraHeaders: (context, _request, candidate) => {
|
|
342
|
+
// An operator-set route identity wins over both auto modes: Routing
|
|
343
|
+
// Strategy now serves custom providers too, and its value (including
|
|
344
|
+
// the empty "None" choice) must be honored verbatim.
|
|
345
|
+
const routeAgent = context.user_agent;
|
|
346
|
+
if (routeAgent !== undefined) {
|
|
347
|
+
return {
|
|
348
|
+
...(routeAgent.length > 0 ? { "user-agent": routeAgent } : {}),
|
|
349
|
+
...(compatibilityProfile?.extra_headers ?? {}),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
342
352
|
// Explicit gateway identity wins over CLI cloaking, and Codex /
|
|
343
353
|
// Claude-Code-shaped traffic can never take this path: this hook
|
|
344
354
|
// only runs for BYOK custom rows, never bundled first-party
|
|
@@ -134,11 +134,13 @@ export interface ProviderDispatchContext {
|
|
|
134
134
|
readonly outbound_fetch?: ValidatedOutboundFetch | undefined;
|
|
135
135
|
/** Validated image/attachment egress; deliberately excluded from payload capture. */
|
|
136
136
|
readonly outbound_attachment_fetch?: ValidatedOutboundFetch | undefined;
|
|
137
|
-
/** Validated WSS egress bound to the same selected network pool as HTTP. */
|
|
138
|
-
readonly outbound_websocket?: ValidatedOutboundWebSocket | undefined;
|
|
139
137
|
/** Validated bidirectional HTTP/2 egress bound to the same selected network pool. */
|
|
140
138
|
readonly outbound_http2?: ValidatedOutboundHttp2Bidi | undefined;
|
|
141
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* Route-selected fallback User-Agent from Routing Strategy; set when the
|
|
141
|
+
* operator configured one (bundled API-key providers default to the Claude
|
|
142
|
+
* CLI identity). Empty means the operator chose "None".
|
|
143
|
+
*/
|
|
142
144
|
readonly user_agent?: string | undefined;
|
|
143
145
|
/** Gateway request id, stable across provider retries for one logical request. */
|
|
144
146
|
readonly request_id?: string | undefined;
|
|
@@ -190,7 +192,8 @@ export function applyRouteUserAgent(
|
|
|
190
192
|
if (
|
|
191
193
|
credential.credential_kind === "oauth" ||
|
|
192
194
|
credential.credential_kind === "scoped_access_token" ||
|
|
193
|
-
userAgent === undefined
|
|
195
|
+
userAgent === undefined ||
|
|
196
|
+
userAgent.trim().length === 0
|
|
194
197
|
) {
|
|
195
198
|
return;
|
|
196
199
|
}
|
|
@@ -15,6 +15,15 @@ export interface CompatibilityProfile {
|
|
|
15
15
|
}>;
|
|
16
16
|
/** Whether requests to OpenAI/Anthropic-compatible upstream should send official CLI headers. Defaults to true. */
|
|
17
17
|
readonly cli_identity?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Direct egress for this provider uses the runtime-native fetch instead of
|
|
20
|
+
* the Node-stack pinned agent. Some upstreams meter per client transport
|
|
21
|
+
* fingerprint — OpenCode's anonymous free tier refuses the Node agent with
|
|
22
|
+
* 429 FreeUsageLimitError while accepting the native fetch byte-for-byte
|
|
23
|
+
* (verified live). The SSRF resolve/validate pass still runs for every hop;
|
|
24
|
+
* only the socket-level address pin is traded away.
|
|
25
|
+
*/
|
|
26
|
+
readonly preferNativeFetch?: boolean;
|
|
18
27
|
/**
|
|
19
28
|
* Opts this provider into the gateway identity (`user-agent:
|
|
20
29
|
* `Cartethyia/<version>`) instead of official CLI cloaking. Explicit only:
|
|
@@ -322,6 +331,14 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
|
|
|
322
331
|
hasAdapterUserAgent: true,
|
|
323
332
|
},
|
|
324
333
|
{ id: "bai", displayName: "B.AI", baseUrl: "https://api.b.ai/v1", credentialUrl: "https://b.ai" },
|
|
334
|
+
{
|
|
335
|
+
id: "inception",
|
|
336
|
+
displayName: "Inception Labs",
|
|
337
|
+
// Version-rooted: the chat wire lives below the `/v1` root, so the
|
|
338
|
+
// declared path is `/chat/completions` (same rule as Groq/DeepInfra).
|
|
339
|
+
baseUrl: "https://api.inceptionlabs.ai/v1",
|
|
340
|
+
credentialUrl: "https://platform.inceptionlabs.ai/dashboard/api-keys",
|
|
341
|
+
},
|
|
325
342
|
{
|
|
326
343
|
id: "larprouter",
|
|
327
344
|
displayName: "LarRouter",
|
|
@@ -630,16 +647,20 @@ export function providerBaseUrl(providerId: string): string {
|
|
|
630
647
|
export const PROVIDER_COMPATIBILITY_PROFILES: Record<string, CompatibilityProfile> = {
|
|
631
648
|
opencode: {
|
|
632
649
|
endpoint_paths_by_wire_family: {
|
|
633
|
-
chat: "/inference/v1/chat/completions",
|
|
634
|
-
responses: "/inference/v1/responses",
|
|
650
|
+
chat: "/inference/openai/v1/chat/completions",
|
|
651
|
+
responses: "/inference/openai/v1/responses",
|
|
635
652
|
},
|
|
653
|
+
// Direct egress must use the native fetch: the anonymous free tier refuses
|
|
654
|
+
// the Node-stack agent with 429 FreeUsageLimitError (verified live).
|
|
655
|
+
preferNativeFetch: true,
|
|
636
656
|
model_wire_families: [{ pattern: "^muse-spark", wire_family: "responses" }],
|
|
637
657
|
},
|
|
638
658
|
"opencode-zen": {
|
|
639
659
|
endpoint_paths_by_wire_family: {
|
|
640
|
-
chat: "/inference/v1/chat/completions",
|
|
641
|
-
responses: "/inference/v1/responses",
|
|
660
|
+
chat: "/inference/openai/v1/chat/completions",
|
|
661
|
+
responses: "/inference/openai/v1/responses",
|
|
642
662
|
},
|
|
663
|
+
preferNativeFetch: true,
|
|
643
664
|
model_wire_families: [{ pattern: "^muse-spark", wire_family: "responses" }],
|
|
644
665
|
},
|
|
645
666
|
"opencode-go": {
|
|
@@ -110,13 +110,13 @@ export async function buildProviderProbeRequest(
|
|
|
110
110
|
provider_id: providerId,
|
|
111
111
|
model_id: PROBE_MODEL,
|
|
112
112
|
wire_family: "chat",
|
|
113
|
-
endpoint_path: "/inference/v1/chat/completions",
|
|
113
|
+
endpoint_path: "/inference/openai/v1/chat/completions",
|
|
114
114
|
capabilities: {},
|
|
115
115
|
} as ProviderDispatchTarget,
|
|
116
116
|
);
|
|
117
117
|
const headers = (await spec.buildExtraHeaders?.(probeContext(providerId), source)) ?? {};
|
|
118
118
|
return {
|
|
119
|
-
url: `${providerBaseUrl(providerId).replace(/\/+$/, "")}/inference/v1/chat/completions`,
|
|
119
|
+
url: `${providerBaseUrl(providerId).replace(/\/+$/, "")}/inference/openai/v1/chat/completions`,
|
|
120
120
|
headers: { "content-type": "application/json", ...headers },
|
|
121
121
|
body: payload,
|
|
122
122
|
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Reads the pre-envelope bare result and v2-v4 envelopes during upgrade;
|
|
7
7
|
// writes use v5, which records attempt time, retained-refresh errors, and
|
|
8
8
|
// forces a one-time refresh for required quota accounting metadata.
|
|
9
|
-
import type { ProviderQuotaResult } from "
|
|
9
|
+
import type { ProviderQuotaResult } from "./quota-contracts";
|
|
10
10
|
import type { RedisClient } from "../../persistence/redis";
|
|
11
11
|
import { TtlCache } from "../../persistence/ttl-cache";
|
|
12
12
|
import { log } from "../../observability/logger";
|
|
@@ -11,12 +11,12 @@ 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 { fetchProviderQuota } from "
|
|
15
|
-
import { type FetchLike, type ProviderQuotaResult } from "
|
|
16
|
-
import type { ProviderId } from "
|
|
17
|
-
import type { ProviderRegistry } from "
|
|
14
|
+
import { fetchProviderQuota } from "./quota-support";
|
|
15
|
+
import { type FetchLike, type ProviderQuotaResult } from "./quota-contracts";
|
|
16
|
+
import type { ProviderId } from "../provider-contracts";
|
|
17
|
+
import type { ProviderRegistry } from "../provider-registry";
|
|
18
18
|
import { GLOBAL_QUOTA_LENS, setCachedQuota } from "./cache";
|
|
19
|
-
import { record } from "
|
|
19
|
+
import { record } from "../authentication/oauth-flow-store";
|
|
20
20
|
|
|
21
21
|
/** Why an account quota refresh failed; `null` means the fetch succeeded. */
|
|
22
22
|
export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed";
|
|
@@ -50,13 +50,20 @@ const REMOTE_CLI_TOOL_UA: ReadonlyArray<{
|
|
|
50
50
|
// filtering; MCP traffic uses `claude-code/<version>` instead.
|
|
51
51
|
{ toolId: "claude", pattern: /(^|[^a-z0-9])claude-cli\//i },
|
|
52
52
|
{ toolId: "claude", pattern: /(^|[^a-z0-9])claude-code\//i },
|
|
53
|
+
// Codex CLI (TUI/CLI builds) — the Guardian auto-review agent sends a
|
|
54
|
+
// `codex-auto-review` model that is only useful when the gateway can
|
|
55
|
+
// remap it to a serving provider. `codex-tui`, `codex-cli`, and the
|
|
56
|
+
// desktop agent's `opencode`-ish UAs all carry this prefix.
|
|
57
|
+
{ toolId: "codex", pattern: /(^|[^a-z0-9])codex-(tui|cli)\//i },
|
|
53
58
|
];
|
|
54
59
|
|
|
55
60
|
/**
|
|
56
61
|
* Returns the remote CLI tool id recognised from the probe, or `null` when
|
|
57
62
|
* the caller is not a remote-mapping client. Ordinary SDKs, curl, and local
|
|
58
|
-
* mapping tools
|
|
59
|
-
*
|
|
63
|
+
* mapping tools stay unlabelled so their model ids resolve through ordinary
|
|
64
|
+
* aliases / allowlists. Codex is labelled because its Guardian auto-review
|
|
65
|
+
* agent sends the hardcoded `codex-auto-review` model, which must be remapable
|
|
66
|
+
* to a serving provider for approval reviews to work at all.
|
|
60
67
|
*/
|
|
61
68
|
export function detectRemoteCliToolId(probe: CliClientProbe): string | null {
|
|
62
69
|
const userAgent = readUserAgent(probe);
|
|
@@ -13,7 +13,7 @@ import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer"
|
|
|
13
13
|
import type { ProxyRequestOutcome, ProxyRequestState } from "../request/state";
|
|
14
14
|
import type { PayloadCaptureState } from "../../observability/payload-capture";
|
|
15
15
|
import { finalizeRequestTelemetry } from "../guards/error-lifecycle";
|
|
16
|
-
import { debitCachedQuota, quotaLens } from "../../
|
|
16
|
+
import { debitCachedQuota, quotaLens } from "../../providers/quota/cache";
|
|
17
17
|
import { BUDDY_PROVIDER_IDS } from "../../providers/provider-metadata";
|
|
18
18
|
import { log } from "../../observability/logger";
|
|
19
19
|
import {
|
|
@@ -195,6 +195,43 @@ export function providerRequestMetadataOnly(request: unknown): unknown {
|
|
|
195
195
|
return Object.keys(metadata).length === 0 ? null : metadata;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Captures one provider request body into the stored provider-request panel.
|
|
200
|
+
*
|
|
201
|
+
* Providers send bodies in several shapes: a plain string (the common case), a
|
|
202
|
+
* `Uint8Array`/Buffer (Qoder's custom cosy encoding), or another BodyInit
|
|
203
|
+
* type (URLSearchParams, Blob, FormData) that cannot be sliced safely here.
|
|
204
|
+
* Strings and byte arrays are decoded and stored; anything else is recorded as
|
|
205
|
+
* present-but-opaque, same as the metadata path.
|
|
206
|
+
*/
|
|
207
|
+
function captureRequestBodyBody(
|
|
208
|
+
body: BodyInit,
|
|
209
|
+
maxBytes: number,
|
|
210
|
+
): { body: unknown } | { body_present: true; body_bytes: number } {
|
|
211
|
+
if (typeof body === "string") {
|
|
212
|
+
return { body: parseCapturedBody(truncateUtf8(body, maxBytes)) };
|
|
213
|
+
}
|
|
214
|
+
if (body instanceof Uint8Array) {
|
|
215
|
+
return { body: parseCapturedBody(truncateUtf8(new TextDecoder().decode(body), maxBytes)) };
|
|
216
|
+
}
|
|
217
|
+
return { body_present: true, body_bytes: capturedBodyByteLength(body) };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Byte length of a request body in whatever shape the provider sent it. */
|
|
221
|
+
function capturedBodyByteLength(body: BodyInit): number {
|
|
222
|
+
if (typeof body === "string") return Buffer.byteLength(body, "utf8");
|
|
223
|
+
if (body instanceof Uint8Array) return body.byteLength;
|
|
224
|
+
if (typeof body === "object" && body !== null && "size" in body) {
|
|
225
|
+
const size = (body as { size?: unknown }).size;
|
|
226
|
+
if (typeof size === "number" && Number.isFinite(size)) return Math.round(size);
|
|
227
|
+
}
|
|
228
|
+
if (typeof body === "object" && body !== null && "byteLength" in body) {
|
|
229
|
+
const byteLength = (body as { byteLength?: unknown }).byteLength;
|
|
230
|
+
if (typeof byteLength === "number" && Number.isFinite(byteLength)) return Math.round(byteLength);
|
|
231
|
+
}
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
198
235
|
/**
|
|
199
236
|
* Wraps a validated outbound fetch to tee the provider request/response.
|
|
200
237
|
*
|
|
@@ -216,7 +253,7 @@ export function captureProviderExchange(
|
|
|
216
253
|
: input instanceof URL
|
|
217
254
|
? input.toString()
|
|
218
255
|
: input.url;
|
|
219
|
-
const rawBody =
|
|
256
|
+
const rawBody = init?.body;
|
|
220
257
|
const headers = selectCapturedHeaders(init);
|
|
221
258
|
sink.request = {
|
|
222
259
|
method: init?.method ?? "GET",
|
|
@@ -227,11 +264,11 @@ export function captureProviderExchange(
|
|
|
227
264
|
// shape sliced up to `maxBytes` of the translated body and ran it through
|
|
228
265
|
// `JSON.parse` for every attempt — content a metadata-mode tenant had
|
|
229
266
|
// explicitly declined to retain, paid on every dispatch.
|
|
230
|
-
...(rawBody === null
|
|
267
|
+
...(rawBody === null || rawBody === undefined
|
|
231
268
|
? {}
|
|
232
269
|
: captureBody
|
|
233
|
-
?
|
|
234
|
-
: { body_present: true, body_bytes:
|
|
270
|
+
? captureRequestBodyBody(rawBody, maxBytes)
|
|
271
|
+
: { body_present: true, body_bytes: capturedBodyByteLength(rawBody) }),
|
|
235
272
|
};
|
|
236
273
|
const response = await inner(input, init);
|
|
237
274
|
sink.response =
|
|
@@ -463,6 +500,13 @@ export function estimatedUsage(inputTokens: number, outputTokens: number): Usage
|
|
|
463
500
|
export interface AttemptCompletion extends ProxyRequestOutcome {
|
|
464
501
|
readonly modelId?: string;
|
|
465
502
|
readonly error?: unknown;
|
|
503
|
+
/**
|
|
504
|
+
* The HTTP status the terminal classifier derived for a raw (non-GatewayError)
|
|
505
|
+
* failure — an upstream socket drop is 502, not the 500 the fallback implied.
|
|
506
|
+
* Only consulted when the error carries no status of its own and no wire
|
|
507
|
+
* response named one.
|
|
508
|
+
*/
|
|
509
|
+
readonly terminalHttpStatus?: number;
|
|
466
510
|
readonly lease?: AdmissionLease;
|
|
467
511
|
/** Usage to reconcile, or undefined when this attempt consumed nothing billable. */
|
|
468
512
|
readonly commitUsage?: UsageRecord;
|
|
@@ -538,6 +582,63 @@ export function completionContext(input: {
|
|
|
538
582
|
};
|
|
539
583
|
}
|
|
540
584
|
|
|
585
|
+
/**
|
|
586
|
+
* The terminal-completion base the native routes (generation, responses
|
|
587
|
+
* compact, system one, web search, token count) all assemble. The proxy and
|
|
588
|
+
* streaming paths use `completionContext`; these routes previously spelled
|
|
589
|
+
* the same twelve fields by hand in six places. Callers spread this and add
|
|
590
|
+
* only what differs: status, response body, error classification.
|
|
591
|
+
*/
|
|
592
|
+
export function nativeCompletionBase(input: {
|
|
593
|
+
readonly providerId: string;
|
|
594
|
+
readonly modelId: string;
|
|
595
|
+
readonly tenantId: string | null;
|
|
596
|
+
readonly ingressBody: unknown;
|
|
597
|
+
readonly accountId?: string;
|
|
598
|
+
readonly accountLabel?: string;
|
|
599
|
+
readonly networkPoolId?: string;
|
|
600
|
+
readonly lease?: AdmissionLease;
|
|
601
|
+
readonly db: CartethyiaDatabase;
|
|
602
|
+
readonly telemetryBuffer?: TelemetryBatchBuffer;
|
|
603
|
+
readonly snapshotService?: { invalidate(): unknown };
|
|
604
|
+
readonly providerCapture?: ProviderExchangeCapture;
|
|
605
|
+
readonly usage?: AttemptCompletion["usage"];
|
|
606
|
+
readonly commitUsage?: AttemptCompletion["commitUsage"];
|
|
607
|
+
}): Omit<
|
|
608
|
+
AttemptCompletion,
|
|
609
|
+
| "status"
|
|
610
|
+
| "responseBody"
|
|
611
|
+
| "httpStatus"
|
|
612
|
+
| "terminalHttpStatus"
|
|
613
|
+
| "error"
|
|
614
|
+
| "clientResponseText"
|
|
615
|
+
| "firstContentDeltaAtMs"
|
|
616
|
+
| "lastEventAtMs"
|
|
617
|
+
| "reportedModel"
|
|
618
|
+
| "errorCategory"
|
|
619
|
+
| "errorOrigin"
|
|
620
|
+
| "ttfbMs"
|
|
621
|
+
| "tokensPerSec"
|
|
622
|
+
> {
|
|
623
|
+
return {
|
|
624
|
+
providerId: input.providerId,
|
|
625
|
+
modelId: input.modelId,
|
|
626
|
+
tenantId: input.tenantId,
|
|
627
|
+
ingressBody: input.ingressBody,
|
|
628
|
+
terminal: true,
|
|
629
|
+
...(input.accountId === undefined ? {} : { accountId: input.accountId }),
|
|
630
|
+
...(input.accountLabel === undefined ? {} : { accountLabel: input.accountLabel }),
|
|
631
|
+
...(input.networkPoolId === undefined ? {} : { networkPoolId: input.networkPoolId }),
|
|
632
|
+
...(input.lease === undefined ? {} : { lease: input.lease }),
|
|
633
|
+
db: input.db,
|
|
634
|
+
...(input.telemetryBuffer === undefined ? {} : { telemetryBuffer: input.telemetryBuffer }),
|
|
635
|
+
...(input.snapshotService === undefined ? {} : { snapshotService: input.snapshotService }),
|
|
636
|
+
...(input.providerCapture === undefined ? {} : { providerCapture: input.providerCapture }),
|
|
637
|
+
...(input.usage === undefined ? {} : { usage: input.usage }),
|
|
638
|
+
...(input.commitUsage === undefined ? {} : { commitUsage: input.commitUsage }),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
541
642
|
/**
|
|
542
643
|
* Decides whether a failed attempt proves *the pool* is unusable, and which
|
|
543
644
|
* HTTP status that verdict rests on.
|
|
@@ -639,13 +740,14 @@ export async function completeAttempt(
|
|
|
639
740
|
completion.httpStatus ??
|
|
640
741
|
(completion.error instanceof GatewayError
|
|
641
742
|
? completion.error.status
|
|
642
|
-
: completion.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
743
|
+
: completion.terminalHttpStatus ??
|
|
744
|
+
(completion.status === "completed"
|
|
745
|
+
? 200
|
|
746
|
+
: completion.status === "cancelled"
|
|
747
|
+
? 499
|
|
748
|
+
: completion.status === "truncated"
|
|
749
|
+
? 502
|
|
750
|
+
: 500)),
|
|
649
751
|
...(completion.providerId === undefined ? {} : { providerId: completion.providerId }),
|
|
650
752
|
...(completion.accountId === undefined ? {} : { accountId: completion.accountId }),
|
|
651
753
|
...(completion.accountLabel === undefined ? {} : { accountLabel: completion.accountLabel }),
|
|
@@ -288,6 +288,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
288
288
|
status: terminal.status,
|
|
289
289
|
errorCategory: terminal.errorCategory,
|
|
290
290
|
errorOrigin: terminal.errorOrigin,
|
|
291
|
+
...(terminal.httpStatus === undefined ? {} : { terminalHttpStatus: terminal.httpStatus }),
|
|
291
292
|
error: lastError,
|
|
292
293
|
tenantId: input.tenantId,
|
|
293
294
|
terminal: true,
|
|
@@ -419,6 +420,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
419
420
|
status: terminal.status,
|
|
420
421
|
errorCategory: terminal.errorCategory,
|
|
421
422
|
errorOrigin: terminal.errorOrigin,
|
|
423
|
+
...(terminal.httpStatus === undefined ? {} : { terminalHttpStatus: terminal.httpStatus }),
|
|
422
424
|
error,
|
|
423
425
|
tenantId: input.tenantId,
|
|
424
426
|
terminal: terminalAttempt,
|
|
@@ -539,6 +541,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
|
|
|
539
541
|
status: terminal.status,
|
|
540
542
|
errorCategory: terminal.errorCategory,
|
|
541
543
|
errorOrigin: terminal.errorOrigin,
|
|
544
|
+
...(terminal.httpStatus === undefined ? {} : { terminalHttpStatus: terminal.httpStatus }),
|
|
542
545
|
error: lastError,
|
|
543
546
|
terminal: true,
|
|
544
547
|
tenantId: input.tenantId,
|
|
@@ -87,7 +87,9 @@ export function isEmptyStream(tally: StreamTally): boolean {
|
|
|
87
87
|
* failing over across ordered endpoints pre-start.
|
|
88
88
|
*
|
|
89
89
|
* Throws GatewayError (platform_unavailable, 502, upstream) on exhaustion,
|
|
90
|
-
* GatewayError (
|
|
90
|
+
* GatewayError (deadline_exceeded, 504, upstream) when a first event never
|
|
91
|
+
* arrives within the window, and GatewayError (transport_closed, 499) on
|
|
92
|
+
* client abort.
|
|
91
93
|
*/
|
|
92
94
|
export async function fetchWithEmptyStreamRecovery(
|
|
93
95
|
endpoints: readonly string[],
|
|
@@ -197,7 +199,12 @@ async function fetchWithTimeout(
|
|
|
197
199
|
// Race the fetch against a first-event timeout
|
|
198
200
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
199
201
|
const timer = setTimeout(() => {
|
|
200
|
-
|
|
202
|
+
// A first-event timeout is the gateway waiting on a silent upstream (e.g.
|
|
203
|
+
// a provider that holds the request in a queue), not the client leaving.
|
|
204
|
+
// It must surface as a deadline failure, never as a `transport_closed`
|
|
205
|
+
// 499 — that code is reserved for genuine client cancellations and would
|
|
206
|
+
// otherwise hide a real upstream wait behind "cancelled by client".
|
|
207
|
+
reject(new GatewayError("deadline_exceeded", 504, "First event timeout", {}, "upstream"));
|
|
201
208
|
}, timeoutMs);
|
|
202
209
|
signal.addEventListener("abort", () => {
|
|
203
210
|
clearTimeout(timer);
|
|
@@ -21,34 +21,16 @@
|
|
|
21
21
|
import type { ImageGenerationOutcome, ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
|
|
22
22
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
23
23
|
import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
|
|
24
|
-
import type { OAuthTokenRefresher, OAuthRefreshService } from "../../providers/authentication/oauth-refresh-service";
|
|
25
|
-
import type { ValidatedNetworkBindingFactory } from "../../network/pool/resolver";
|
|
26
|
-
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
27
|
-
import type { RouteSnapshotService } from "../routing/route-model";
|
|
28
24
|
import type { ServiceKind } from "../../protocol/canonical-model";
|
|
29
25
|
import { metrics } from "../../observability/metrics";
|
|
30
|
-
import type { NetworkPoolSelector } from "../../network/pool/selector";
|
|
31
|
-
import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
|
|
32
26
|
import type { ProxyRequestState } from "../request/state";
|
|
33
|
-
import {
|
|
34
|
-
import { ProxyRequestPreparer } from "../request/preparer";
|
|
35
|
-
import { completeAttempt, estimatedUsage } from "./attempt-finalize";
|
|
27
|
+
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
36
28
|
import { runAttemptLoop } from "./attempt-loop";
|
|
37
29
|
import { captureProviderRateLimit } from "./ratelimit-capture";
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
readonly resolveProviderAdapter?: (providerId: string) => Promise<ProviderAdapter | undefined>;
|
|
43
|
-
readonly proxyPreparer: ProxyRequestPreparer;
|
|
44
|
-
readonly stateStore: ProxyRequestStateStore;
|
|
45
|
-
readonly poolSelector?: NetworkPoolSelector;
|
|
46
|
-
readonly networkBindingFactory?: ValidatedNetworkBindingFactory;
|
|
47
|
-
readonly snapshotService?: RouteSnapshotService;
|
|
48
|
-
readonly telemetryBuffer?: TelemetryBatchBuffer;
|
|
49
|
-
readonly resolveOAuthRefresher?: (providerId: string) => Promise<OAuthTokenRefresher | undefined>;
|
|
50
|
-
readonly oauthRefreshService?: OAuthRefreshService;
|
|
51
|
-
}
|
|
31
|
+
import type { GatewayHandlerDeps } from "./handler-deps";
|
|
32
|
+
|
|
33
|
+
export type GenerationHandlerDeps = GatewayHandlerDeps;
|
|
52
34
|
|
|
53
35
|
/** Which surface a handler serves. Drives candidate filtering and error copy. */
|
|
54
36
|
export type GenerationKind = Extract<ServiceKind, "imageGeneration" | "videoGeneration">;
|
|
@@ -133,22 +115,23 @@ async function passThrough(
|
|
|
133
115
|
captureProviderRateLimit(args.candidate.provider_id, args.candidate.provider_account_id, upstream.headers);
|
|
134
116
|
await completeAttempt(state, {
|
|
135
117
|
status: "completed",
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
118
|
+
...nativeCompletionBase({
|
|
119
|
+
providerId: args.candidate.provider_id,
|
|
120
|
+
...(args.candidate.provider_account_id ? { accountId: args.candidate.provider_account_id } : {}),
|
|
121
|
+
...(args.candidate.provider_account_label ? { accountLabel: args.candidate.provider_account_label } : {}),
|
|
122
|
+
...(args.networkPoolId ? { networkPoolId: args.networkPoolId } : {}),
|
|
123
|
+
usage,
|
|
124
|
+
modelId: args.candidate.model_id,
|
|
125
|
+
lease: args.lease as never,
|
|
126
|
+
commitUsage: usage,
|
|
127
|
+
tenantId: args.tenantId,
|
|
128
|
+
ingressBody: state.ingressBody,
|
|
129
|
+
providerCapture: args.providerCapture as never,
|
|
130
|
+
db: args.deps.db,
|
|
131
|
+
...(args.deps.telemetryBuffer ? { telemetryBuffer: args.deps.telemetryBuffer } : {}),
|
|
132
|
+
...(args.deps.snapshotService ? { snapshotService: args.deps.snapshotService } : {}),
|
|
133
|
+
}),
|
|
147
134
|
responseBody: null,
|
|
148
|
-
providerCapture: args.providerCapture as never,
|
|
149
|
-
db: args.deps.db,
|
|
150
|
-
...(args.deps.telemetryBuffer ? { telemetryBuffer: args.deps.telemetryBuffer } : {}),
|
|
151
|
-
...(args.deps.snapshotService ? { snapshotService: args.deps.snapshotService } : {}),
|
|
152
135
|
});
|
|
153
136
|
metrics.proxy_requests_total.inc(1, { status: "completed" });
|
|
154
137
|
return new Response(upstream.body, {
|
|
@@ -251,9 +234,7 @@ export function createNativeSurfaceHandler(
|
|
|
251
234
|
provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
|
|
252
235
|
credential_kind: "none" as const,
|
|
253
236
|
};
|
|
254
|
-
const adapter = deps.resolveProviderAdapter
|
|
255
|
-
? await deps.resolveProviderAdapter(candidate.provider_id)
|
|
256
|
-
: deps.providerAdapters.get(candidate.provider_id);
|
|
237
|
+
const adapter = await deps.resolveProviderAdapter?.(candidate.provider_id);
|
|
257
238
|
if (!adapter) {
|
|
258
239
|
throw new GatewayError(
|
|
259
240
|
"capability_unsupported",
|
|
@@ -292,22 +273,23 @@ export function createNativeSurfaceHandler(
|
|
|
292
273
|
const usage = estimatedUsage(0, 0);
|
|
293
274
|
await completeAttempt(state, {
|
|
294
275
|
status: "completed",
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
276
|
+
...nativeCompletionBase({
|
|
277
|
+
providerId: candidate.provider_id,
|
|
278
|
+
...(candidate.provider_account_id ? { accountId: candidate.provider_account_id } : {}),
|
|
279
|
+
...(candidate.provider_account_label ? { accountLabel: candidate.provider_account_label } : {}),
|
|
280
|
+
...(leases.proxySlot ? { networkPoolId: leases.proxySlot.poolId } : {}),
|
|
281
|
+
usage,
|
|
282
|
+
modelId: candidate.model_id,
|
|
283
|
+
lease: leases.lease,
|
|
284
|
+
commitUsage: usage,
|
|
285
|
+
tenantId: prepared.authorization.tenantId,
|
|
286
|
+
ingressBody: state.ingressBody,
|
|
287
|
+
providerCapture,
|
|
288
|
+
db: deps.db,
|
|
289
|
+
...(deps.telemetryBuffer ? { telemetryBuffer: deps.telemetryBuffer } : {}),
|
|
290
|
+
...(deps.snapshotService ? { snapshotService: deps.snapshotService } : {}),
|
|
291
|
+
}),
|
|
306
292
|
responseBody: payload,
|
|
307
|
-
providerCapture,
|
|
308
|
-
db: deps.db,
|
|
309
|
-
...(deps.telemetryBuffer ? { telemetryBuffer: deps.telemetryBuffer } : {}),
|
|
310
|
-
...(deps.snapshotService ? { snapshotService: deps.snapshotService } : {}),
|
|
311
293
|
});
|
|
312
294
|
metrics.proxy_requests_total.inc(1, { status: "completed" });
|
|
313
295
|
return jsonResponse(payload, 200, state.requestId);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one dependency shape every gateway route handler takes.
|
|
3
|
+
*
|
|
4
|
+
* Proxy, generation, responses-compact, system one, web search, and token
|
|
5
|
+
* count all receive the same wiring. Before this module each handler spelled
|
|
6
|
+
* its own near-identical interface and `app.ts` spelled the same object five
|
|
7
|
+
* times — a new handler dep had to be added to five interfaces and five
|
|
8
|
+
* literals to reach the runtime. One shape means one place to grow.
|
|
9
|
+
*/
|
|
10
|
+
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
11
|
+
import type { ProviderAdapter } from "../../providers/provider-contracts";
|
|
12
|
+
import type { OAuthRefreshService, OAuthTokenRefresher } from "../../providers/authentication/oauth-refresh-service";
|
|
13
|
+
import type { NetworkPoolSelector } from "../../network/pool/selector";
|
|
14
|
+
import type { ValidatedNetworkBindingFactory } from "../../network/pool/resolver";
|
|
15
|
+
import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
|
|
16
|
+
import type { ProxyRequestPreparer } from "../request/preparer";
|
|
17
|
+
import type { ProxyRequestStateStore } from "../request/state";
|
|
18
|
+
import type { RouteSnapshotService } from "../routing/route-model";
|
|
19
|
+
|
|
20
|
+
export interface GatewayHandlerDeps {
|
|
21
|
+
readonly db: CartethyiaDatabase;
|
|
22
|
+
/** Preferred over a static map: resolves an adapter on demand and caches it. */
|
|
23
|
+
readonly resolveProviderAdapter?: (providerId: string) => Promise<ProviderAdapter | undefined>;
|
|
24
|
+
readonly proxyPreparer: ProxyRequestPreparer;
|
|
25
|
+
readonly stateStore: ProxyRequestStateStore;
|
|
26
|
+
readonly poolSelector?: NetworkPoolSelector;
|
|
27
|
+
readonly networkBindingFactory?: ValidatedNetworkBindingFactory;
|
|
28
|
+
readonly snapshotService?: RouteSnapshotService;
|
|
29
|
+
readonly telemetryBuffer?: TelemetryBatchBuffer;
|
|
30
|
+
readonly resolveOAuthRefresher?: (providerId: string) => Promise<OAuthTokenRefresher | undefined>;
|
|
31
|
+
readonly oauthRefreshService?: OAuthRefreshService;
|
|
32
|
+
}
|
|
@@ -124,11 +124,16 @@ async function acquirePoolSlot(
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
// Round-robin rotation rides the strategy the snapshot attached to the
|
|
127
|
-
// candidate (the pool-owning tenant scopes the cursor);
|
|
128
|
-
//
|
|
127
|
+
// candidate (the pool-owning tenant scopes the cursor); `random` scatters
|
|
128
|
+
// each selection across eligible pools. Absent strategy or `least_loaded`
|
|
129
|
+
// keeps the default weighted least-loaded scan.
|
|
129
130
|
const rotation: PoolRotation | undefined =
|
|
130
|
-
routing?.strategy === "round_robin"
|
|
131
|
-
? {
|
|
131
|
+
routing?.strategy === "round_robin" || routing?.strategy === "random"
|
|
132
|
+
? {
|
|
133
|
+
key: routing.tenantId,
|
|
134
|
+
rotateCount: routing.rotateCount,
|
|
135
|
+
strategy: routing.strategy,
|
|
136
|
+
}
|
|
132
137
|
: undefined;
|
|
133
138
|
|
|
134
139
|
if (!poolIds?.length || !poolSelector) {
|