cartethyia 2.7.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +107 -106
- package/README.md +25 -2
- package/dist/dashboard/assets/{BannedPage-P76QLGJA.js → BannedPage-D-J75hBM.js} +1 -1
- package/dist/dashboard/assets/BannedPage-D-J75hBM.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-DdBggJS9.js → CliToolDetailPage-CVe6RaBH.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-aN1iXhhP.js → CliToolsPage-Dd8ZSamp.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-DE-F1nxZ.js → CombosPage-QoJ6foxo.js} +1 -1
- package/dist/dashboard/assets/CombosPage-QoJ6foxo.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-3CUxMy2X.js → CustomizationPage-CKUFTcW4.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DubdQcvX.js +1 -0
- package/dist/dashboard/assets/LoginPage-DubdQcvX.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-DuWzAQFL.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js +4 -0
- package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.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-Bwotu8UJ.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js +9 -0
- package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-BjldQwIU.js → QuotaPage-ATpaxrbl.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-ATpaxrbl.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-DUIoLRxk.js → SettingsPage-CETophpx.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CETophpx.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BdgECjqq.js +1 -0
- package/dist/dashboard/assets/SetupPage-BdgECjqq.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-fI7JkdIB.js → SortableList-CAK-EN9z.js} +1 -1
- package/dist/dashboard/assets/SortableList-CAK-EN9z.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CHVZqskZ.js → StudioPage-B7W2_RhT.js} +3 -3
- package/dist/dashboard/assets/StudioPage-B7W2_RhT.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-6T4EFDjW.js +56 -0
- package/dist/dashboard/assets/UsagePage-6T4EFDjW.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-CWBY5A0f.js → entry-BLBLxX0h.js} +3 -3
- package/dist/dashboard/assets/entry-BLBLxX0h.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-CvC1-Zqx.js → entry-_xHo5x6r.js} +1 -1
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-mpBPqAQ8.js +1 -0
- package/dist/dashboard/assets/formatters-mpBPqAQ8.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-D6QzEC_n.js} +2 -2
- package/dist/dashboard/assets/index-D6QzEC_n.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-Cj5Nw43g.js} +1 -1
- package/dist/dashboard/assets/select-Cj5Nw43g.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/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 +226 -365
- package/src/network/pool/agent.ts +8 -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 +21 -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/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.ts +1641 -1458
- 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 +1660 -0
- package/src/providers/operations/account-health-service.ts +621 -2097
- package/src/providers/operations/account-reset-service.ts +2 -0
- package/src/providers/provider-contracts.ts +0 -2
- 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/transport/dispatch/attempt-finalize.ts +114 -12
- package/src/transport/dispatch/attempt-loop.ts +3 -0
- package/src/transport/dispatch/empty-stream.ts +224 -217
- 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 +59 -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 +41 -31
- package/src/transport/routing/router.ts +2 -2
- package/src/transport/surface/messages/parse.ts +1 -1
- 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
|
@@ -1,217 +1,224 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Empty-stream replay + pre-start failover.
|
|
3
|
-
*
|
|
4
|
-
* When a provider returns HTTP 200 but the response body decodes to zero
|
|
5
|
-
* meaningful events, the request is replayed byte-identical (same headers,
|
|
6
|
-
* same body) up to `maxEmptyReplays` times with exponential backoff. If all
|
|
7
|
-
* replays on one endpoint fail, the helper advances to the next endpoint in
|
|
8
|
-
* the caller-supplied ordered list.
|
|
9
|
-
*
|
|
10
|
-
* The helper is transport-layer: it owns fetch, watchdog, backoff, and
|
|
11
|
-
* endpoint rotation. The caller owns SSE decoding via the `DrainStream`
|
|
12
|
-
* callback and supplies materialized headers + serialized body so replays
|
|
13
|
-
* are byte-identical.
|
|
14
|
-
*
|
|
15
|
-
* Design: the replay count, backoff, and the ordered endpoint list are the
|
|
16
|
-
* caller's contract; this helper owns fetch, watchdog, and rotation so a
|
|
17
|
-
* provider that answers 200 with no events is retried on the next endpoint
|
|
18
|
-
* rather than surfacing an empty body to the client.
|
|
19
|
-
*/
|
|
20
|
-
import { GatewayError } from "../../protocol/gateway-error";
|
|
21
|
-
import {
|
|
22
|
-
fallbackRetryDelayMs,
|
|
23
|
-
MIN_RETRY_BUDGET_MS,
|
|
24
|
-
sleep as abortableSleep,
|
|
25
|
-
} from "../failure-policy";
|
|
26
|
-
|
|
27
|
-
// ─── Public types ──────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
/** What the caller's drain observed. Counts only dispatched data events:
|
|
30
|
-
* comments/keepalives, `[DONE]`, and whitespace-only data are excluded. */
|
|
31
|
-
export interface StreamTally {
|
|
32
|
-
readonly dataEvents: number;
|
|
33
|
-
readonly meaningful: boolean;
|
|
34
|
-
readonly thoughtOnly: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Drain one response body to a tally. Provider-supplied; uses decodeSseEvents. */
|
|
38
|
-
export type DrainStream = (response: Response, signal: AbortSignal) => Promise<StreamTally>;
|
|
39
|
-
|
|
40
|
-
/** Bound fetch for one endpoint. Receives materialized headers + serialized body. */
|
|
41
|
-
export type EmptyStreamFetch = (
|
|
42
|
-
url: string,
|
|
43
|
-
init: { headers: Record<string, string>; body: string; signal: AbortSignal },
|
|
44
|
-
) => Promise<Response>;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Retry-delay policy.
|
|
48
|
-
*
|
|
49
|
-
* Delays come from `fallbackRetryDelayMs` (capped exponential with full
|
|
50
|
-
* jitter), whose base and cap are operator-tunable. There is deliberately no
|
|
51
|
-
* per-caller base: the local formula this replaced was uncapped and
|
|
52
|
-
* unjittered, so a provider returning 200-with-no-events could drive an
|
|
53
|
-
* unbounded wait, and concurrent clients could resynchronize their retries.
|
|
54
|
-
*/
|
|
55
|
-
export interface EmptyStreamPolicy {
|
|
56
|
-
readonly maxEmptyReplays?: number;
|
|
57
|
-
readonly firstEventTimeoutMs?: number;
|
|
58
|
-
readonly sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface EmptyStreamResult {
|
|
62
|
-
readonly response: Response;
|
|
63
|
-
readonly tally: StreamTally;
|
|
64
|
-
readonly endpoint: string;
|
|
65
|
-
readonly replaysUsed: number;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ─── Defaults ──────────────────────────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
const DEFAULT_MAX_REPLAYS = 2;
|
|
71
|
-
const DEFAULT_FIRST_EVENT_TIMEOUT_MS = 200_000; // 200s, matches CARTETHYIA_STREAM_FIRST_CHUNK_TIMEOUT_MS default
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
75
|
-
|
|
76
|
-
/** Pure predicate: zero data events, or data events with nothing meaningful. */
|
|
77
|
-
export function isEmptyStream(tally: StreamTally): boolean {
|
|
78
|
-
return tally.dataEvents === 0 || (!tally.meaningful && !tally.thoughtOnly);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// ─── Main function ─────────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Replays the byte-identical request while responses drain empty,
|
|
87
|
-
* failing over across ordered endpoints pre-start.
|
|
88
|
-
*
|
|
89
|
-
* Throws GatewayError (platform_unavailable, 502, upstream) on exhaustion,
|
|
90
|
-
* GatewayError (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"
|
|
183
|
-
|
|
184
|
-
"
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Empty-stream replay + pre-start failover.
|
|
3
|
+
*
|
|
4
|
+
* When a provider returns HTTP 200 but the response body decodes to zero
|
|
5
|
+
* meaningful events, the request is replayed byte-identical (same headers,
|
|
6
|
+
* same body) up to `maxEmptyReplays` times with exponential backoff. If all
|
|
7
|
+
* replays on one endpoint fail, the helper advances to the next endpoint in
|
|
8
|
+
* the caller-supplied ordered list.
|
|
9
|
+
*
|
|
10
|
+
* The helper is transport-layer: it owns fetch, watchdog, backoff, and
|
|
11
|
+
* endpoint rotation. The caller owns SSE decoding via the `DrainStream`
|
|
12
|
+
* callback and supplies materialized headers + serialized body so replays
|
|
13
|
+
* are byte-identical.
|
|
14
|
+
*
|
|
15
|
+
* Design: the replay count, backoff, and the ordered endpoint list are the
|
|
16
|
+
* caller's contract; this helper owns fetch, watchdog, and rotation so a
|
|
17
|
+
* provider that answers 200 with no events is retried on the next endpoint
|
|
18
|
+
* rather than surfacing an empty body to the client.
|
|
19
|
+
*/
|
|
20
|
+
import { GatewayError } from "../../protocol/gateway-error";
|
|
21
|
+
import {
|
|
22
|
+
fallbackRetryDelayMs,
|
|
23
|
+
MIN_RETRY_BUDGET_MS,
|
|
24
|
+
sleep as abortableSleep,
|
|
25
|
+
} from "../failure-policy";
|
|
26
|
+
|
|
27
|
+
// ─── Public types ──────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
/** What the caller's drain observed. Counts only dispatched data events:
|
|
30
|
+
* comments/keepalives, `[DONE]`, and whitespace-only data are excluded. */
|
|
31
|
+
export interface StreamTally {
|
|
32
|
+
readonly dataEvents: number;
|
|
33
|
+
readonly meaningful: boolean;
|
|
34
|
+
readonly thoughtOnly: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Drain one response body to a tally. Provider-supplied; uses decodeSseEvents. */
|
|
38
|
+
export type DrainStream = (response: Response, signal: AbortSignal) => Promise<StreamTally>;
|
|
39
|
+
|
|
40
|
+
/** Bound fetch for one endpoint. Receives materialized headers + serialized body. */
|
|
41
|
+
export type EmptyStreamFetch = (
|
|
42
|
+
url: string,
|
|
43
|
+
init: { headers: Record<string, string>; body: string; signal: AbortSignal },
|
|
44
|
+
) => Promise<Response>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Retry-delay policy.
|
|
48
|
+
*
|
|
49
|
+
* Delays come from `fallbackRetryDelayMs` (capped exponential with full
|
|
50
|
+
* jitter), whose base and cap are operator-tunable. There is deliberately no
|
|
51
|
+
* per-caller base: the local formula this replaced was uncapped and
|
|
52
|
+
* unjittered, so a provider returning 200-with-no-events could drive an
|
|
53
|
+
* unbounded wait, and concurrent clients could resynchronize their retries.
|
|
54
|
+
*/
|
|
55
|
+
export interface EmptyStreamPolicy {
|
|
56
|
+
readonly maxEmptyReplays?: number;
|
|
57
|
+
readonly firstEventTimeoutMs?: number;
|
|
58
|
+
readonly sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface EmptyStreamResult {
|
|
62
|
+
readonly response: Response;
|
|
63
|
+
readonly tally: StreamTally;
|
|
64
|
+
readonly endpoint: string;
|
|
65
|
+
readonly replaysUsed: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Defaults ──────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
const DEFAULT_MAX_REPLAYS = 2;
|
|
71
|
+
const DEFAULT_FIRST_EVENT_TIMEOUT_MS = 200_000; // 200s, matches CARTETHYIA_STREAM_FIRST_CHUNK_TIMEOUT_MS default
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/** Pure predicate: zero data events, or data events with nothing meaningful. */
|
|
77
|
+
export function isEmptyStream(tally: StreamTally): boolean {
|
|
78
|
+
return tally.dataEvents === 0 || (!tally.meaningful && !tally.thoughtOnly);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
// ─── Main function ─────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Replays the byte-identical request while responses drain empty,
|
|
87
|
+
* failing over across ordered endpoints pre-start.
|
|
88
|
+
*
|
|
89
|
+
* Throws GatewayError (platform_unavailable, 502, upstream) on exhaustion,
|
|
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.
|
|
93
|
+
*/
|
|
94
|
+
export async function fetchWithEmptyStreamRecovery(
|
|
95
|
+
endpoints: readonly string[],
|
|
96
|
+
request: { headers: Record<string, string>; body: string },
|
|
97
|
+
deps: {
|
|
98
|
+
fetch: EmptyStreamFetch;
|
|
99
|
+
drain: DrainStream;
|
|
100
|
+
signal: AbortSignal;
|
|
101
|
+
deadlineMs: number;
|
|
102
|
+
policy?: EmptyStreamPolicy;
|
|
103
|
+
},
|
|
104
|
+
): Promise<EmptyStreamResult> {
|
|
105
|
+
const maxReplays = deps.policy?.maxEmptyReplays ?? DEFAULT_MAX_REPLAYS;
|
|
106
|
+
const firstEventTimeout = deps.policy?.firstEventTimeoutMs ?? DEFAULT_FIRST_EVENT_TIMEOUT_MS;
|
|
107
|
+
const sleep = deps.policy?.sleep ?? abortableSleep;
|
|
108
|
+
|
|
109
|
+
if (endpoints.length === 0) {
|
|
110
|
+
throw new GatewayError("platform_unavailable", 502, "No endpoints available", {}, "upstream");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (let endpointIndex = 0; endpointIndex < endpoints.length; endpointIndex++) {
|
|
114
|
+
const endpoint = endpoints[endpointIndex]!;
|
|
115
|
+
const isLastEndpoint = endpointIndex === endpoints.length - 1;
|
|
116
|
+
const replaysAllowed = isLastEndpoint ? maxReplays : maxReplays;
|
|
117
|
+
|
|
118
|
+
for (let replay = 0; replay <= replaysAllowed; replay++) {
|
|
119
|
+
// Check abort before each fetch
|
|
120
|
+
if (deps.signal.aborted) {
|
|
121
|
+
throw new GatewayError("transport_closed", 499, "Aborted");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check deadline floor
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
const remaining = deps.deadlineMs - now;
|
|
127
|
+
if (remaining < MIN_RETRY_BUDGET_MS) {
|
|
128
|
+
throw new GatewayError(
|
|
129
|
+
"platform_unavailable",
|
|
130
|
+
502,
|
|
131
|
+
"Deadline exhausted before empty-stream replay",
|
|
132
|
+
{},
|
|
133
|
+
"upstream",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const fetchTimeout = Math.min(firstEventTimeout, remaining);
|
|
138
|
+
|
|
139
|
+
let response: Response;
|
|
140
|
+
try {
|
|
141
|
+
response = await fetchWithTimeout(deps.fetch, endpoint, request, deps.signal, fetchTimeout);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (error instanceof GatewayError) throw error;
|
|
144
|
+
break; // advance to next endpoint
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Drain the response
|
|
148
|
+
let tally: StreamTally;
|
|
149
|
+
try {
|
|
150
|
+
tally = await deps.drain(response, deps.signal);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error instanceof GatewayError) throw error;
|
|
153
|
+
break; // advance to next endpoint
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check if empty
|
|
157
|
+
if (!isEmptyStream(tally)) {
|
|
158
|
+
// Success — return the response and tally
|
|
159
|
+
return {
|
|
160
|
+
response,
|
|
161
|
+
tally,
|
|
162
|
+
endpoint,
|
|
163
|
+
replaysUsed: replay,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Empty — replay if we haven't exhausted this endpoint's budget
|
|
168
|
+
if (replay < replaysAllowed) {
|
|
169
|
+
const delay = fallbackRetryDelayMs(replay);
|
|
170
|
+
try {
|
|
171
|
+
await sleep(delay, deps.signal);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof GatewayError) throw error;
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// All endpoints exhausted
|
|
181
|
+
throw new GatewayError(
|
|
182
|
+
"platform_unavailable",
|
|
183
|
+
502,
|
|
184
|
+
"All endpoints returned empty streams",
|
|
185
|
+
{},
|
|
186
|
+
"upstream",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ─── Internal helpers ──────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
async function fetchWithTimeout(
|
|
193
|
+
fetch: EmptyStreamFetch,
|
|
194
|
+
url: string,
|
|
195
|
+
request: { headers: Record<string, string>; body: string },
|
|
196
|
+
signal: AbortSignal,
|
|
197
|
+
timeoutMs: number,
|
|
198
|
+
): Promise<Response> {
|
|
199
|
+
// Race the fetch against a first-event timeout
|
|
200
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
201
|
+
const timer = setTimeout(() => {
|
|
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"));
|
|
208
|
+
}, timeoutMs);
|
|
209
|
+
signal.addEventListener("abort", () => {
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
reject(new GatewayError("transport_closed", 499, "Aborted"));
|
|
212
|
+
}, { once: true });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
return await Promise.race([
|
|
217
|
+
fetch(url, { headers: request.headers, body: request.body, signal }),
|
|
218
|
+
timeoutPromise,
|
|
219
|
+
]);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error instanceof GatewayError) throw error;
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -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) {
|