cartethyia 2.7.3 → 2.7.4
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/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-ChG5svFb.js} +1 -1
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-BIcWQun7.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-Bf2VelDe.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-JEbYJFxw.js → CombosPage-DCgm5g6g.js} +1 -1
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-U6AKaJcF.js} +1 -1
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-BMS2t8BA.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +1 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js +9 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js +1 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-f0DE1h0-.js} +1 -1
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-BAL5npBC.js → SortableList-ClzpwyET.js} +1 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-Cq5tW7hW.js} +3 -3
- package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +56 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js +1 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
- package/dist/dashboard/assets/app-a159k0ub.css +2 -0
- package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js +15 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
- package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-D9CS6dZk.js} +1 -1
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-DympBNq4.js} +1 -1
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/{card-Be5Uzamx.js → card-ibRWU_uT.js} +1 -1
- package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js +1 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-DJm-9pz7.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-8fiE6tec.js} +1 -1
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-CqGYeOrd.js} +1 -1
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-BSAsYsmV.js} +1 -1
- package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js +4 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js +1 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-D-MTJxMr.js} +1 -1
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-CPK47xQx.js} +1 -1
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/{index-cPrX5vn6.js → index-DBNPOfhV.js} +2 -2
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-BT0zntEr.js} +1 -1
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-Da2ZViWg.js} +1 -1
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-BXNeLw6M.js} +1 -1
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-BycOrrIV.js} +1 -1
- package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js +1 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js +1 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BNDZnsUE.js → numbers-CEagj6ZZ.js} +2 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
- package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js +1 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js +1 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
- package/dist/dashboard/assets/{select-B3iTetHN.js → select-BIYIBkMU.js} +2 -2
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js +1 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
- package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-DfgnFDWY.js} +1 -1
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/{state-DLS7IDMy.js → state-CkfdYWel.js} +1 -1
- package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
- package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-SYm237HG.js} +1 -1
- package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-BQqCRNIi.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +55 -54
- package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
- package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
- package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
- package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
- package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
- package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
- package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
- package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
- package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
- package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
- package/package.json +2 -1
- package/src/boot/app.ts +9 -8
- package/src/config/network.ts +7 -3
- package/src/console/api-keys/contracts.ts +52 -0
- package/src/console/api-keys/routes.ts +93 -4
- package/src/console/audit/routes.ts +3 -3
- package/src/console/auth/account-reset.ts +4 -4
- package/src/console/auth/session.ts +3 -3
- package/src/console/backup/contracts.ts +2 -0
- package/src/console/backup/export-endpoint.ts +6 -6
- package/src/console/backup/routes.ts +3 -3
- package/src/console/cli-tools/routes.ts +3 -3
- package/src/console/console-router-guards.ts +2 -2
- package/src/console/console-router.ts +4 -4
- package/src/console/data-management/routes.ts +3 -3
- package/src/console/domain-registration.ts +2 -2
- package/src/console/observability/contracts.ts +4 -0
- package/src/console/observability/live.ts +3 -3
- package/src/console/observability/logs.ts +3 -3
- package/src/console/observability/performance.ts +3 -3
- package/src/console/observability/routes.ts +3 -3
- package/src/console/observability/store.ts +16 -16
- package/src/console/providers/catalog/contracts.ts +43 -22
- package/src/console/providers/catalog/provider-operations.ts +17 -2
- package/src/console/providers/catalog/routes.ts +3 -3
- package/src/console/providers/catalog/store-helpers.ts +349 -326
- package/src/console/providers/catalog/store.ts +1523 -1292
- package/src/console/providers/detail/routes.ts +3 -3
- package/src/console/providers/oauth/routes.ts +3 -3
- package/src/console/quota/account-quota-tenant.ts +26 -1
- package/src/console/quota/account-test-egress.ts +4 -4
- package/src/console/routing/model/routes.ts +3 -3
- package/src/console/routing/pools/contracts.ts +11 -8
- package/src/console/routing/pools/operations.ts +14 -7
- package/src/console/routing/pools/probe-verdict.ts +8 -3
- package/src/console/routing/pools/relay-deploy.ts +9 -9
- package/src/console/routing/pools/routes.ts +3 -3
- package/src/console/routing/pools/store.ts +21 -1
- package/src/console/settings/routes.ts +3 -3
- package/src/console/share/share-router.ts +12 -12
- package/src/console/share/share-stats.ts +105 -35
- package/src/console/shared/read-through-cache.ts +38 -2
- package/src/console/updates/routes.ts +7 -4
- package/src/network/outbound-fetch.ts +75 -4
- package/src/network/pool/resolver.ts +39 -39
- package/src/network/pool-cooldown-store.ts +71 -16
- package/src/network/pool-health-machine.ts +21 -1
- package/src/network/rate-limit-verdict.ts +68 -0
- package/src/network/ssrf.ts +4 -4
- package/src/observability/event-query.ts +320 -16
- package/src/observability/telemetry-status.ts +1 -1
- package/src/persistence/db-sqlite.ts +7 -2
- package/src/persistence/readiness.ts +18 -6
- package/src/persistence/schema.ts +66 -7
- package/src/persistence/stores/api-key-store.ts +99 -3
- package/src/persistence/stores/telemetry-store.ts +1 -1
- package/src/protocol/codecs.ts +6 -6
- package/src/protocol/gateway-error.ts +6 -3
- package/src/protocol/image-generation.ts +2 -2
- package/src/protocol/response/chat.ts +2 -2
- package/src/protocol/response/codex.ts +1 -1
- package/src/protocol/response/gemini.ts +1 -1
- package/src/protocol/response/messages.ts +5 -5
- package/src/protocol/response/responses.ts +2 -2
- package/src/protocol/sse-decoder.ts +14 -3
- package/src/protocol/translation/capabilities.ts +2 -2
- package/src/protocol/upstream/send-claude-messages.ts +3 -3
- package/src/providers/adapters/base-adapter.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +2 -2
- package/src/providers/authentication/oauth-refresh-service.ts +2 -2
- package/src/providers/configured-provider.ts +1 -1
- package/src/providers/discovery/probe-phases.ts +2 -2
- package/src/providers/discovery/probing-service.ts +12 -12
- package/src/providers/integrations/agentrouter/agentrouter.ts +2 -3
- package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
- package/src/providers/integrations/anthropic.ts +4 -11
- package/src/providers/integrations/antigravity/antigravity.ts +13 -24
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
- package/src/providers/integrations/buddy/buddy-identity.ts +22 -1
- package/src/providers/integrations/buddy/buddy-media.ts +16 -16
- package/src/providers/integrations/buddy/codebuddy/provider.ts +1 -1
- package/src/providers/integrations/buddy/workbuddy/provider.ts +1 -1
- package/src/providers/integrations/claude/claude-betas.ts +1 -1
- package/src/providers/integrations/claude/claude-credentials.ts +1 -1
- package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
- package/src/providers/integrations/claude/claude.ts +3 -10
- package/src/providers/integrations/cline/cline.ts +1 -1
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +14 -15
- package/src/providers/integrations/commandcode/commandcode.ts +4 -4
- package/src/providers/integrations/commandcode/quota.ts +5 -1
- package/src/providers/integrations/cursor/cursor-http2.ts +11 -11
- package/src/providers/integrations/cursor/cursor.ts +15 -15
- package/src/providers/integrations/factory/factory.ts +4 -4
- package/src/providers/integrations/gemini.ts +11 -11
- package/src/providers/integrations/github/github.ts +6 -6
- package/src/providers/integrations/grok/grok.ts +2 -6
- package/src/providers/integrations/inferhub.ts +4 -11
- package/src/providers/integrations/kimi/kimi.ts +4 -11
- package/src/providers/integrations/kiro/kiro.ts +10 -10
- package/src/providers/integrations/muse/muse.ts +6 -9
- package/src/providers/integrations/perplexity.ts +2 -2
- package/src/providers/integrations/qoder/qoder-catalog.ts +47 -9
- package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
- package/src/providers/integrations/qoder/qoder-realm.ts +15 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +12 -13
- package/src/providers/integrations/qoder/qoder-virtual.ts +2 -2
- package/src/providers/integrations/qoder/qoder.ts +188 -22
- package/src/providers/integrations/tokenharbor.ts +2 -2
- package/src/providers/integrations/trae/trae.ts +6 -9
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/provider-catalog-service.ts +4 -4
- package/src/providers/operations/provider-credential-service.ts +16 -4
- package/src/providers/operations/upstream-deadline.ts +3 -3
- package/src/providers/provider-contracts.ts +47 -3
- package/src/providers/provider-credential-resolver.ts +1 -1
- package/src/providers/provider-registry.ts +4 -4
- package/src/providers/quota/quota-contracts.ts +24 -10
- package/src/providers/quota/refresh.ts +2 -2
- package/src/providers/search/search-provider.ts +5 -5
- package/src/security/abuse.ts +4 -4
- package/src/security/admission/reasons.ts +17 -17
- package/src/security/api-key-auth.ts +4 -4
- package/src/shared/coerce.ts +19 -0
- package/src/transport/dispatch/attempt-finalize.ts +4 -3
- package/src/transport/dispatch/attempt-loop.ts +10 -7
- package/src/transport/dispatch/empty-stream.ts +6 -6
- package/src/transport/dispatch/generation.ts +9 -9
- package/src/transport/dispatch/leases.ts +8 -8
- package/src/transport/dispatch/proxy-request.ts +7 -7
- package/src/transport/dispatch/responses-compact.ts +11 -11
- package/src/transport/dispatch/retry-policy.ts +4 -67
- package/src/transport/dispatch/stream-bridge.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +9 -8
- package/src/transport/dispatch/systemone.ts +8 -8
- package/src/transport/dispatch/token-count.ts +9 -9
- package/src/transport/dispatch/websearch.ts +6 -6
- package/src/transport/drain-endpoint.ts +2 -2
- package/src/transport/failure-policy.ts +13 -6
- package/src/transport/guards/auth-guard.ts +6 -5
- package/src/transport/guards/body-policy.ts +4 -4
- package/src/transport/guards/error-lifecycle.ts +18 -3
- package/src/transport/guards/guard-errors.ts +7 -3
- package/src/transport/guards/headroom-guard.ts +1 -1
- package/src/transport/guards/pipeline.ts +6 -6
- package/src/transport/guards/readiness-guard.ts +16 -2
- package/src/transport/guards/request-context.ts +3 -3
- package/src/transport/request/preparer.ts +20 -20
- package/src/transport/request/state.ts +4 -7
- package/src/transport/routing/route-catalog.ts +97 -2
- package/src/transport/routing/route-model.ts +7 -7
- package/src/transport/routing/router.ts +2 -2
- package/src/transport/shutdown-notice.ts +19 -7
- package/src/transport/surface/completion.ts +1 -1
- package/src/transport/surface/messages/encode.ts +1 -1
- package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
- package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
- package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
- package/dist/dashboard/assets/assert-C0YYuZJ4.js +0 -1
- package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
- package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
- package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
- package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-muFRphDu.js +0 -1
- package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
- package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
- package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
- package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
- package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
- package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
- package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
- package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
- package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
- package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
- package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
- package/dist/dashboard/assets/layout-C_7A6H55.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
- package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
- package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
- package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
- package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
- package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
- package/dist/dashboard/assets/routing-BEH32QOC.js +0 -1
- package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
- package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
- package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
- package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
- package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
- /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-BayTTy01.js} +0 -0
- /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-BayTTy01.js.gz} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Elysia, t } from "elysia";
|
|
1
|
+
import { Elysia, t, type AnyElysia } from "elysia";
|
|
2
2
|
import { ConsoleDomainError, errorResponse, requireTenantScope } from "../../shared/errors";
|
|
3
3
|
import { literalUnion } from "../../shared/elysia-schema";
|
|
4
4
|
import type { AccessDecision } from "../../../security/access-control";
|
|
@@ -160,7 +160,7 @@ const updateRoutingBody = t.Object({
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
|
|
163
|
-
export function createProviderDetailRoutes(config: ProviderDetailConfig):
|
|
163
|
+
export function createProviderDetailRoutes(config: ProviderDetailConfig): AnyElysia {
|
|
164
164
|
const factory = createProviderDetailOperations(config);
|
|
165
165
|
return new Elysia({ prefix: "/providers" })
|
|
166
166
|
.get("/:providerId/routing", async ({ request, params, set }) => {
|
|
@@ -191,5 +191,5 @@ export function createProviderDetailRoutes(config: ProviderDetailConfig): Elysia
|
|
|
191
191
|
return providerDetailErrorResponse(e, set);
|
|
192
192
|
}
|
|
193
193
|
},
|
|
194
|
-
)
|
|
194
|
+
)
|
|
195
195
|
}
|
|
@@ -9,7 +9,7 @@ import type { ProviderRegistry } from "../../../providers/provider-registry";
|
|
|
9
9
|
import { browserAuthorizeRedirectUri } from "../../../config/providers";
|
|
10
10
|
import { log } from "../../../observability/logger";
|
|
11
11
|
import { GatewayError } from "../../../protocol/gateway-error";
|
|
12
|
-
import { Elysia, t } from "elysia";
|
|
12
|
+
import { Elysia, t, type AnyElysia } from "elysia";
|
|
13
13
|
import { ConsoleDomainError, errorResponse, requireScope } from "../../shared/errors";
|
|
14
14
|
import { generateOAuthState, generatePkcePair } from "../../../providers/authentication/oauth-flow-store";
|
|
15
15
|
import type { OAuthFlowStorage } from "../../../providers/authentication/oauth-flow-store";
|
|
@@ -639,7 +639,7 @@ const importBody = t.Object({
|
|
|
639
639
|
accountLabel: t.Optional(t.String()),
|
|
640
640
|
});
|
|
641
641
|
|
|
642
|
-
export function createOAuthLoginRoutes(config: OAuthLoginConfig):
|
|
642
|
+
export function createOAuthLoginRoutes(config: OAuthLoginConfig): AnyElysia {
|
|
643
643
|
const factory = createOAuthLoginOperations(config);
|
|
644
644
|
return new Elysia({ prefix: "/providers" })
|
|
645
645
|
.post(
|
|
@@ -721,6 +721,6 @@ export function createOAuthLoginRoutes(config: OAuthLoginConfig): Elysia {
|
|
|
721
721
|
return oauthErrorResponse(e, set);
|
|
722
722
|
}
|
|
723
723
|
},
|
|
724
|
-
)
|
|
724
|
+
)
|
|
725
725
|
}
|
|
726
726
|
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
listAccountResetCredits,
|
|
55
55
|
} from "../../providers/operations/account-reset-service";
|
|
56
56
|
import {
|
|
57
|
+
enqueueBackgroundQuotaRefresh,
|
|
57
58
|
pendingQuotaRefreshes,
|
|
58
59
|
toQuotaAccountHealth,
|
|
59
60
|
toQuotaView,
|
|
@@ -369,6 +370,15 @@ export function registerAccountQuotaTenantRoutes(
|
|
|
369
370
|
icon: providerId,
|
|
370
371
|
}));
|
|
371
372
|
let refreshing = 0;
|
|
373
|
+
// Cold-cache backfill: the overview renders whatever is cached, and the
|
|
374
|
+
// periodic sweep owns freshness — but with many accounts (e.g. a large
|
|
375
|
+
// Qoder roster) the sweep's per-pass cap cannot keep every cache warm,
|
|
376
|
+
// so a page open would show accounts with no badge for minutes. Enqueue
|
|
377
|
+
// a bounded background refresh for accounts whose cache entry is missing
|
|
378
|
+
// (not merely stale), so the badge fills in without waiting for the next
|
|
379
|
+
// sweep pass and without fanning out to the whole roster at once.
|
|
380
|
+
const coldCacheCap = 20;
|
|
381
|
+
let coldCacheEnqueued = 0;
|
|
372
382
|
// The sweep's day markers answer "attempted today" without an upstream
|
|
373
383
|
// request; only check-in-capable accounts can carry one, so only they
|
|
374
384
|
// are read.
|
|
@@ -404,6 +414,21 @@ export function registerAccountQuotaTenantRoutes(
|
|
|
404
414
|
const canRefresh = providerRegistry.hasQuotaCollector(row.providerId);
|
|
405
415
|
const isRefreshing = canRefresh && (pending.has(row.id) || entry === undefined);
|
|
406
416
|
if (isRefreshing) refreshing += 1;
|
|
417
|
+
// Backfill a missing cache entry on page open (bounded, see above).
|
|
418
|
+
if (
|
|
419
|
+
canRefresh &&
|
|
420
|
+
entry === undefined &&
|
|
421
|
+
!pending.has(row.id) &&
|
|
422
|
+
coldCacheEnqueued < coldCacheCap
|
|
423
|
+
) {
|
|
424
|
+
coldCacheEnqueued += 1;
|
|
425
|
+
enqueueBackgroundQuotaRefresh(refreshDeps, {
|
|
426
|
+
accountId: row.id,
|
|
427
|
+
providerId: row.providerId,
|
|
428
|
+
tenantId: row.tenantId,
|
|
429
|
+
credentialKind: row.credentialKind,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
407
432
|
return {
|
|
408
433
|
id: row.id,
|
|
409
434
|
provider: row.providerId,
|
|
@@ -525,7 +550,7 @@ export function registerAccountQuotaTenantRoutes(
|
|
|
525
550
|
throw new GatewayError(
|
|
526
551
|
"proxy_pool_unavailable",
|
|
527
552
|
503,
|
|
528
|
-
"Account test cannot select a proxy because proxy routing is not configured.",
|
|
553
|
+
"Account test cannot select a proxy because proxy routing is not configured (request {requestId}). No bridge to cross — configure a pool first 🏗️",
|
|
529
554
|
{ reason: "proxy_routing_unconfigured" },
|
|
530
555
|
"network",
|
|
531
556
|
);
|
|
@@ -138,7 +138,7 @@ export async function selectAccountTestEgress(
|
|
|
138
138
|
throw new GatewayError(
|
|
139
139
|
"proxy_pool_unavailable",
|
|
140
140
|
503,
|
|
141
|
-
"No active proxy pool is available because coordination is unavailable.",
|
|
141
|
+
"No active proxy pool is available because coordination is unavailable (request {requestId}). The bouncer stepped out — come back shortly 🚪",
|
|
142
142
|
{ reason: "coordination_unavailable" },
|
|
143
143
|
"network",
|
|
144
144
|
);
|
|
@@ -159,10 +159,10 @@ export async function selectAccountTestEgress(
|
|
|
159
159
|
const status = failure.reason === "at_capacity" ? 429 : 503;
|
|
160
160
|
const message =
|
|
161
161
|
failure.reason === "at_capacity"
|
|
162
|
-
? "Every proxy pool for this provider is at its concurrency limit. Retry shortly, or add a pool or raise its capacity."
|
|
162
|
+
? "Every proxy pool for this provider is at its concurrency limit (request {requestId}). Retry shortly, or add a pool or raise its capacity. The pools are full right now — chill 🌊"
|
|
163
163
|
: failure.reason === "cooldown"
|
|
164
|
-
? "Every proxy pool for this provider is cooling down after upstream failures. Retry once the cooldown resets."
|
|
165
|
-
: "No active proxy pool is configured for this tenant and provider. Add one, or route this provider through direct egress.";
|
|
164
|
+
? "Every proxy pool for this provider is cooling down after upstream failures (request {requestId}). Retry once the cooldown resets. The proxies are catching their breath — zzz 💤"
|
|
165
|
+
: "No active proxy pool is configured for this tenant and provider (request {requestId}). Add one, or route this provider through direct egress. No bridge to cross — configure a pool first 🏗️";
|
|
166
166
|
throw new GatewayError(
|
|
167
167
|
code,
|
|
168
168
|
status,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Model routing control-plane HTTP routes. The only module that touches Elysia.
|
|
2
|
-
import { Elysia, t } from "elysia";
|
|
2
|
+
import { Elysia, t, type AnyElysia } from "elysia";
|
|
3
3
|
import { errorResponse } from "../../shared/errors";
|
|
4
4
|
import { literalUnion } from "../../shared/elysia-schema";
|
|
5
5
|
import { modelComboStrategy, type ComboStrategy } from "../../../persistence/schema";
|
|
@@ -41,7 +41,7 @@ const updateComboBody = t.Object({
|
|
|
41
41
|
members: t.Optional(t.Array(t.String(), { minItems: 1 })),
|
|
42
42
|
strategy: t.Optional(comboStrategySchema),
|
|
43
43
|
});
|
|
44
|
-
export function createModelRoutingRoutes(config: ModelRoutingConfig):
|
|
44
|
+
export function createModelRoutingRoutes(config: ModelRoutingConfig): AnyElysia {
|
|
45
45
|
const factory = createModelRoutingOperations(config);
|
|
46
46
|
return new Elysia({ prefix: "/routing" })
|
|
47
47
|
.get("/aliases", async ({ request, set }) => {
|
|
@@ -132,5 +132,5 @@ export function createModelRoutingRoutes(config: ModelRoutingConfig): Elysia {
|
|
|
132
132
|
} catch (e) {
|
|
133
133
|
return modelRoutingErrorResponse(e, set);
|
|
134
134
|
}
|
|
135
|
-
})
|
|
135
|
+
});
|
|
136
136
|
}
|
|
@@ -188,7 +188,7 @@ export function validateTransportConfig(
|
|
|
188
188
|
case "http":
|
|
189
189
|
case "https":
|
|
190
190
|
if (cfg.timeout !== undefined && typeof cfg.timeout !== "number")
|
|
191
|
-
throw new ConsoleDomainError("
|
|
191
|
+
throw new ConsoleDomainError("invalid_pool", 400, "HTTP timeout must be a number", { field: "timeout" });
|
|
192
192
|
return;
|
|
193
193
|
case "socks5":
|
|
194
194
|
return;
|
|
@@ -196,9 +196,10 @@ export function validateTransportConfig(
|
|
|
196
196
|
// Compile-time exhaustiveness: new TransportKind variants must add a case.
|
|
197
197
|
kind satisfies never;
|
|
198
198
|
throw new ConsoleDomainError(
|
|
199
|
-
"
|
|
199
|
+
"invalid_pool",
|
|
200
200
|
400,
|
|
201
201
|
`Unsupported transport kind: ${String(kind)}`,
|
|
202
|
+
{ field: "kind" },
|
|
202
203
|
);
|
|
203
204
|
}
|
|
204
205
|
}
|
|
@@ -494,10 +495,10 @@ export function validateEndpoint(
|
|
|
494
495
|
try {
|
|
495
496
|
u = new URL(candidate);
|
|
496
497
|
} catch {
|
|
497
|
-
throw new ConsoleDomainError("
|
|
498
|
+
throw new ConsoleDomainError("invalid_pool", 400, "Endpoint must be host:port", { field: "endpoint" });
|
|
498
499
|
}
|
|
499
500
|
if (!u.hostname || !u.port)
|
|
500
|
-
throw new ConsoleDomainError("
|
|
501
|
+
throw new ConsoleDomainError("invalid_pool", 400, "Endpoint must be host:port", { field: "endpoint" });
|
|
501
502
|
if (u.hostname === "localhost" || u.hostname.endsWith(".internal"))
|
|
502
503
|
throw new ConsoleDomainError("ssrf_rejected", 400, "Private endpoint rejected");
|
|
503
504
|
if (isIP(u.hostname) !== 0 && !isAddressAllowed(u.hostname, policy))
|
|
@@ -508,10 +509,10 @@ export function validateEndpoint(
|
|
|
508
509
|
try {
|
|
509
510
|
u = new URL(endpoint);
|
|
510
511
|
} catch {
|
|
511
|
-
throw new ConsoleDomainError("
|
|
512
|
+
throw new ConsoleDomainError("invalid_pool", 400, "Endpoint must be a valid http(s) URL", { field: "endpoint" });
|
|
512
513
|
}
|
|
513
514
|
if (!["http:", "https:"].includes(u.protocol)) {
|
|
514
|
-
throw new ConsoleDomainError("
|
|
515
|
+
throw new ConsoleDomainError("invalid_pool", 400, "Endpoint must be a valid http(s) URL", { field: "endpoint" });
|
|
515
516
|
}
|
|
516
517
|
if (u.hostname === "localhost" || u.hostname.endsWith(".internal")) {
|
|
517
518
|
throw new ConsoleDomainError("ssrf_rejected", 400, "Private endpoint rejected");
|
|
@@ -529,9 +530,10 @@ export function validatePoolLimits(maxInflight?: number, weight?: number): void
|
|
|
529
530
|
(!Number.isInteger(maxInflight) || maxInflight < 1 || maxInflight > MAX_POOL_INFLIGHT)
|
|
530
531
|
) {
|
|
531
532
|
throw new ConsoleDomainError(
|
|
532
|
-
"
|
|
533
|
+
"invalid_pool",
|
|
533
534
|
400,
|
|
534
535
|
"maxInflight must be an integer between 1 and 1000000",
|
|
536
|
+
{ field: "maxInflight" },
|
|
535
537
|
);
|
|
536
538
|
}
|
|
537
539
|
if (
|
|
@@ -539,9 +541,10 @@ export function validatePoolLimits(maxInflight?: number, weight?: number): void
|
|
|
539
541
|
(!Number.isInteger(weight) || weight < 1 || weight > MAX_POOL_WEIGHT)
|
|
540
542
|
) {
|
|
541
543
|
throw new ConsoleDomainError(
|
|
542
|
-
"
|
|
544
|
+
"invalid_pool",
|
|
543
545
|
400,
|
|
544
546
|
"weight must be an integer between 1 and 1000",
|
|
547
|
+
{ field: "weight" },
|
|
545
548
|
);
|
|
546
549
|
}
|
|
547
550
|
}
|
|
@@ -141,9 +141,10 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
|
|
|
141
141
|
const rotateCount = request.rotateCount ?? current.rotateCount;
|
|
142
142
|
if (!Number.isInteger(rotateCount) || rotateCount < 1 || rotateCount > 1000) {
|
|
143
143
|
throw new ConsoleDomainError(
|
|
144
|
-
"
|
|
144
|
+
"invalid_pool",
|
|
145
145
|
400,
|
|
146
146
|
"rotateCount must be an integer between 1 and 1000",
|
|
147
|
+
{ field: "rotateCount" },
|
|
147
148
|
);
|
|
148
149
|
}
|
|
149
150
|
const saved = await config.store.setStrategy(a.tenantId, { strategy, rotateCount });
|
|
@@ -447,6 +448,12 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
|
|
|
447
448
|
const a = requireTenantScope(access, "dashboard:write");
|
|
448
449
|
const recovered = await config.store.recover(a.tenantId, poolId);
|
|
449
450
|
if (!recovered) throw new ConsoleDomainError("pool_not_found", 404, `Pool ${poolId} not found`);
|
|
451
|
+
// Recover is the operator's "make this pool healthy again": besides
|
|
452
|
+
// resetting the pool-level state, drop every provider-rate-limit marker
|
|
453
|
+
// (volatile and durable) so a just-cleared pool is not still excluded
|
|
454
|
+
// by a stale 429 flag.
|
|
455
|
+
await config.poolSelector?.clearPoolCooldowns(poolId).catch(() => undefined);
|
|
456
|
+
await config.store.dropAllProviderCooldownMarkers?.(a.tenantId, poolId);
|
|
450
457
|
await config.poolAgentReleaser?.releasePool(poolId, a.tenantId);
|
|
451
458
|
await config.auditSink?.record({
|
|
452
459
|
access: a,
|
|
@@ -482,7 +489,7 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
|
|
|
482
489
|
): Promise<PoolSpeedTestResult> {
|
|
483
490
|
const a = requireTenantScope(access, "dashboard:read");
|
|
484
491
|
if (!config.store.speedTest) {
|
|
485
|
-
throw new ConsoleDomainError("
|
|
492
|
+
throw new ConsoleDomainError("capability_unsupported", 400, "Speed tests are unavailable");
|
|
486
493
|
}
|
|
487
494
|
if (!Number.isInteger(bytes) || bytes < SPEED_TEST_MIN_BYTES || bytes > SPEED_TEST_MAX_BYTES) {
|
|
488
495
|
throw new ConsoleDomainError(
|
|
@@ -507,7 +514,7 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
|
|
|
507
514
|
): Promise<PoolProviderProbeResult> {
|
|
508
515
|
const a = requireTenantScope(access, "dashboard:read");
|
|
509
516
|
if (!config.store.providerProbe) {
|
|
510
|
-
throw new ConsoleDomainError("
|
|
517
|
+
throw new ConsoleDomainError("capability_unsupported", 400, "Provider probing is unavailable");
|
|
511
518
|
}
|
|
512
519
|
const result = await config.store.providerProbe(a.tenantId, poolId, providerId);
|
|
513
520
|
if (!config.poolSelector) return result;
|
|
@@ -596,7 +603,7 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
|
|
|
596
603
|
);
|
|
597
604
|
}
|
|
598
605
|
if (!config.store.probeAdHocProvider) {
|
|
599
|
-
throw new ConsoleDomainError("
|
|
606
|
+
throw new ConsoleDomainError("capability_unsupported", 400, "Provider probing is unavailable");
|
|
600
607
|
}
|
|
601
608
|
const probe = config.store.probeAdHocProvider.bind(config.store);
|
|
602
609
|
const results: PoolBatchProviderProbeResult[] = new Array(requests.length);
|
|
@@ -632,7 +639,7 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
|
|
|
632
639
|
if (config.store.probeAdHoc) {
|
|
633
640
|
return config.store.probeAdHoc(a.tenantId, request);
|
|
634
641
|
}
|
|
635
|
-
throw new ConsoleDomainError("
|
|
642
|
+
throw new ConsoleDomainError("capability_unsupported", 400, "Ad-hoc pool probing is unavailable");
|
|
636
643
|
},
|
|
637
644
|
/**
|
|
638
645
|
* Probes many unsaved pool definitions in one call.
|
|
@@ -656,7 +663,7 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
|
|
|
656
663
|
);
|
|
657
664
|
}
|
|
658
665
|
if (!config.store.probeAdHoc) {
|
|
659
|
-
throw new ConsoleDomainError("
|
|
666
|
+
throw new ConsoleDomainError("capability_unsupported", 400, "Ad-hoc pool probing is unavailable");
|
|
660
667
|
}
|
|
661
668
|
const probe = config.store.probeAdHoc.bind(config.store);
|
|
662
669
|
const results: PoolBatchProbeResult[] = new Array(requests.length);
|
|
@@ -700,7 +707,7 @@ export function createNetworkPoolOperations(config: NetworkPoolConfig) {
|
|
|
700
707
|
): Promise<NetworkPoolResponse & { relayUrl: string }> {
|
|
701
708
|
const a = requireTenantScope(access, "dashboard:write");
|
|
702
709
|
if (!config.relayFetch)
|
|
703
|
-
throw new ConsoleDomainError("
|
|
710
|
+
throw new ConsoleDomainError("capability_unsupported", 503, "Relay deployment is unavailable");
|
|
704
711
|
if (!isRelayTarget(request.target))
|
|
705
712
|
throw new ConsoleDomainError("invalid_request", 422, `Unsupported relay target: ${String(request.target)}`);
|
|
706
713
|
const result = await deployRelayWorker(config.relayFetch, request);
|
|
@@ -15,8 +15,16 @@
|
|
|
15
15
|
* the address works (`allowed`, `throttled`) or says nothing about it
|
|
16
16
|
* (`unreachable`), and treating "says nothing" as "refused" would drop a usable
|
|
17
17
|
* pool for good.
|
|
18
|
+
*
|
|
19
|
+
* The 403-free-tier and 429 verdicts are the probe's reading of the same facts
|
|
20
|
+
* `classifyEgressFailure` (in `network/rate-limit-verdict`) reads on the
|
|
21
|
+
* dispatch path — for the probe they arrive as an HTTP status + body instead of
|
|
22
|
+
* a `GatewayError`, so the shared free-tier regex is borrowed from there to
|
|
23
|
+
* keep the two classifiers' boundaries honest.
|
|
18
24
|
*/
|
|
19
25
|
|
|
26
|
+
import { FREE_TIER_REFUSAL } from "../../../network/rate-limit-verdict";
|
|
27
|
+
|
|
20
28
|
export type ProbeVerdict = "allowed" | "blocked" | "throttled" | "unreachable";
|
|
21
29
|
|
|
22
30
|
export interface ProbeAnswer {
|
|
@@ -25,9 +33,6 @@ export interface ProbeAnswer {
|
|
|
25
33
|
readonly reason?: string;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
/** The upstream's own wording for "this address is retired". */
|
|
29
|
-
const FREE_TIER_REFUSAL = /freetier|free tier can only be used/i;
|
|
30
|
-
|
|
31
36
|
/**
|
|
32
37
|
* Classifies one upstream response.
|
|
33
38
|
*
|
|
@@ -151,7 +151,7 @@ async function deployCloudflare(
|
|
|
151
151
|
request: RelayDeployRequest,
|
|
152
152
|
): Promise<RelayDeployResult> {
|
|
153
153
|
const accountId = request.accountId?.trim();
|
|
154
|
-
if (!accountId) throw new GatewayError("invalid_request", 400, "Cloudflare account id is required");
|
|
154
|
+
if (!accountId) throw new GatewayError("invalid_request", 400, "Cloudflare account id is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
155
155
|
const projectName = request.projectName?.trim() || generateName("relay");
|
|
156
156
|
const scriptUrl = `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(projectName)}`;
|
|
157
157
|
const form = new FormData();
|
|
@@ -174,7 +174,7 @@ async function deployCloudflare(
|
|
|
174
174
|
body: form,
|
|
175
175
|
});
|
|
176
176
|
if (!upload.ok)
|
|
177
|
-
throw new GatewayError("platform_unavailable", upload.status, `Cloudflare deploy failed: ${await readError(upload)}
|
|
177
|
+
throw new GatewayError("platform_unavailable", upload.status, `Cloudflare deploy failed (request {requestId}): ${await readError(upload)}. Flip the switch back on when ready 🔌`);
|
|
178
178
|
// Enable the workers.dev route; a failure here is non-fatal because the
|
|
179
179
|
// subdomain lookup below decides the final URL and reports its own error.
|
|
180
180
|
await fetchImpl(`${scriptUrl}/subdomain`, {
|
|
@@ -190,7 +190,7 @@ async function deployCloudflare(
|
|
|
190
190
|
throw new GatewayError(
|
|
191
191
|
"platform_unavailable",
|
|
192
192
|
subdomainRes.status,
|
|
193
|
-
`Worker uploaded but its workers.dev subdomain could not be read: ${await readError(subdomainRes)}
|
|
193
|
+
`Worker uploaded but its workers.dev subdomain could not be read (request {requestId}): ${await readError(subdomainRes)}. Flip the switch back on when ready 🔌`,
|
|
194
194
|
);
|
|
195
195
|
const subdomainData = (await subdomainRes.json().catch(() => ({}))) as {
|
|
196
196
|
result?: { subdomain?: string };
|
|
@@ -200,7 +200,7 @@ async function deployCloudflare(
|
|
|
200
200
|
throw new GatewayError(
|
|
201
201
|
"platform_unavailable",
|
|
202
202
|
502,
|
|
203
|
-
"Worker uploaded but no workers.dev subdomain is configured for this Cloudflare account.",
|
|
203
|
+
"Worker uploaded but no workers.dev subdomain is configured for this Cloudflare account (request {requestId}). Flip the switch back on when ready 🔌",
|
|
204
204
|
);
|
|
205
205
|
return { target: "cloudflare", projectName, relayUrl: `https://${projectName}.${subdomain}.workers.dev` };
|
|
206
206
|
}
|
|
@@ -251,7 +251,7 @@ async function deployVercel(
|
|
|
251
251
|
}),
|
|
252
252
|
});
|
|
253
253
|
if (!deployRes.ok)
|
|
254
|
-
throw new GatewayError("platform_unavailable", deployRes.status, `Vercel deploy failed: ${await readError(deployRes)}
|
|
254
|
+
throw new GatewayError("platform_unavailable", deployRes.status, `Vercel deploy failed (request {requestId}): ${await readError(deployRes)}. Flip the switch back on when ready 🔌`);
|
|
255
255
|
// Pin the region again after the deploy created the project: the PATCH above
|
|
256
256
|
// is a no-op on first deploy (the project does not exist yet), and a redeploy
|
|
257
257
|
// is what Vercel requires for a region change to take effect anyway. The SSO
|
|
@@ -280,7 +280,7 @@ async function deployVercel(
|
|
|
280
280
|
}).catch(() => undefined);
|
|
281
281
|
const deployment = (await deployRes.json().catch(() => ({}))) as { url?: string };
|
|
282
282
|
if (!deployment.url)
|
|
283
|
-
throw new GatewayError("platform_unavailable", 502, "Vercel deploy returned no URL");
|
|
283
|
+
throw new GatewayError("platform_unavailable", 502, "Vercel deploy returned no URL (request {requestId}). Flip the switch back on when ready 🔌");
|
|
284
284
|
return {
|
|
285
285
|
target: "vercel",
|
|
286
286
|
projectName,
|
|
@@ -300,11 +300,11 @@ async function deployDeno(
|
|
|
300
300
|
body: JSON.stringify({ project: projectName, entryPoint: "main.ts", files: { "main.ts": RELAY_DENO_SOURCE } }),
|
|
301
301
|
});
|
|
302
302
|
if (!deployRes.ok)
|
|
303
|
-
throw new GatewayError("platform_unavailable", deployRes.status, `Deno deploy failed: ${await readError(deployRes)}
|
|
303
|
+
throw new GatewayError("platform_unavailable", deployRes.status, `Deno deploy failed (request {requestId}): ${await readError(deployRes)}. Flip the switch back on when ready 🔌`);
|
|
304
304
|
const deployment = (await deployRes.json().catch(() => ({}))) as { url?: string; domains?: string[] };
|
|
305
305
|
const url = deployment.url ?? deployment.domains?.[0];
|
|
306
306
|
if (!url)
|
|
307
|
-
throw new GatewayError("platform_unavailable", 502, "Deno deploy returned no URL");
|
|
307
|
+
throw new GatewayError("platform_unavailable", 502, "Deno deploy returned no URL (request {requestId}). Flip the switch back on when ready 🔌");
|
|
308
308
|
return {
|
|
309
309
|
target: "deno",
|
|
310
310
|
projectName,
|
|
@@ -333,6 +333,6 @@ export async function deployRelay(
|
|
|
333
333
|
request: RelayDeployRequest,
|
|
334
334
|
): Promise<RelayDeployResult> {
|
|
335
335
|
if (!request.token.trim())
|
|
336
|
-
throw new GatewayError("invalid_request", 400, `${request.target} API token is required
|
|
336
|
+
throw new GatewayError("invalid_request", 400, `${request.target} API token is required (request {requestId}). Wrong key, right door — check the credential 🔑`);
|
|
337
337
|
return DEPLOYERS[request.target](fetchImpl, request);
|
|
338
338
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Network-pool control-plane HTTP routes. The only module that touches Elysia.
|
|
2
|
-
import { Elysia, t } from "elysia";
|
|
2
|
+
import { Elysia, t, type AnyElysia } from "elysia";
|
|
3
3
|
import { consoleErrorHandler } from "../../shared/errors";
|
|
4
4
|
import { literalUnion } from "../../shared/elysia-schema";
|
|
5
5
|
import { RELAY_TARGETS, type RelayDeployRequest } from "./relay-deploy";
|
|
@@ -56,7 +56,7 @@ const importPoolsBody = t.Object({
|
|
|
56
56
|
family: t.Optional(t.Union([t.String(), t.Null()])),
|
|
57
57
|
labelPrefix: t.Optional(t.String()),
|
|
58
58
|
});
|
|
59
|
-
export function createNetworkPoolRoutes(config: NetworkPoolConfig):
|
|
59
|
+
export function createNetworkPoolRoutes(config: NetworkPoolConfig): AnyElysia {
|
|
60
60
|
const factory = createNetworkPoolOperations(config);
|
|
61
61
|
return new Elysia({ prefix: "/network/pools" })
|
|
62
62
|
.error(consoleErrorHandler("Network pool operation failed"))
|
|
@@ -207,5 +207,5 @@ export function createNetworkPoolRoutes(config: NetworkPoolConfig): Elysia {
|
|
|
207
207
|
body as Partial<PoolStrategySetting>,
|
|
208
208
|
);
|
|
209
209
|
},
|
|
210
|
-
)
|
|
210
|
+
);
|
|
211
211
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Drizzle-backed console persistence for network pools.
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { and, asc, eq, inArray, sql } from "drizzle-orm";
|
|
3
4
|
import type { CartethyiaDatabase } from "../../../persistence/db-handle";
|
|
4
|
-
import { networkPools, poolRoutingSettings } from "../../../persistence/schema";
|
|
5
|
+
import { networkPools, poolRoutingSettings, providerPoolAccounts } from "../../../persistence/schema";
|
|
6
|
+
import { providerRateLimitIsIpScoped } from "../../../providers/provider-metadata";
|
|
5
7
|
import {
|
|
6
8
|
DEFAULT_POOL_STRATEGY,
|
|
7
9
|
type CreateNetworkPoolRequest,
|
|
@@ -444,6 +446,24 @@ export class DrizzleNetworkPoolStore implements NetworkPoolStore {
|
|
|
444
446
|
...(record.family !== undefined ? { family: record.family } : {}),
|
|
445
447
|
}));
|
|
446
448
|
await this.db.insert(networkPools).values(values);
|
|
449
|
+
|
|
450
|
+
// Materialize pool-as-account rows for ip-scoped, credential-less
|
|
451
|
+
// providers (OpenCode Free today): the operator sees the egress in the
|
|
452
|
+
// provider's Accounts card, and routing excludes a spent egress like a
|
|
453
|
+
// cooling account. Every other provider gets no row.
|
|
454
|
+
if (providerRateLimitIsIpScoped("opencode")) {
|
|
455
|
+
const now = new Date();
|
|
456
|
+
const poolAccounts = values.map((value) => ({
|
|
457
|
+
id: randomUUID(),
|
|
458
|
+
tenantId: value.tenantId,
|
|
459
|
+
networkPoolId: value.id,
|
|
460
|
+
providerId: "opencode",
|
|
461
|
+
label: `Pool ${value.id.slice(0, 8)}`,
|
|
462
|
+
createdAt: now,
|
|
463
|
+
updatedAt: now,
|
|
464
|
+
}));
|
|
465
|
+
await this.db.insert(providerPoolAccounts).values(poolAccounts);
|
|
466
|
+
}
|
|
447
467
|
return values.length;
|
|
448
468
|
}
|
|
449
469
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Elysia } from "elysia";
|
|
1
|
+
import { Elysia, type AnyElysia } from "elysia";
|
|
2
2
|
import {
|
|
3
3
|
createRuntimeSettingsOperations,
|
|
4
4
|
runtimeUpdateBody,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type UpdateRuntimeSettingsRequest,
|
|
8
8
|
} from "./contracts";
|
|
9
9
|
|
|
10
|
-
export function createRuntimeSettingsRoutes(config: RuntimeSettingsConfig):
|
|
10
|
+
export function createRuntimeSettingsRoutes(config: RuntimeSettingsConfig): AnyElysia {
|
|
11
11
|
const factory = createRuntimeSettingsOperations(config);
|
|
12
12
|
return new Elysia({ prefix: "/settings/runtime" })
|
|
13
13
|
.get("/", async ({ request, set }) => {
|
|
@@ -26,5 +26,5 @@ export function createRuntimeSettingsRoutes(config: RuntimeSettingsConfig): Elys
|
|
|
26
26
|
} catch (e) {
|
|
27
27
|
return runtimeSettingsErrorResponse(e, set);
|
|
28
28
|
}
|
|
29
|
-
})
|
|
29
|
+
});
|
|
30
30
|
}
|
|
@@ -4,7 +4,7 @@ import { globalOrOwnedBy } from "../../persistence/tenant-scope";
|
|
|
4
4
|
// Share URLs reveal policy and allow one child key to be created per canonical
|
|
5
5
|
// client IP. The child bearer is returned only by the successful issue call.
|
|
6
6
|
|
|
7
|
-
import { Elysia } from "elysia";
|
|
7
|
+
import { Elysia, type AnyElysia } from "elysia";
|
|
8
8
|
import { and, eq, sql } from "drizzle-orm";
|
|
9
9
|
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
10
10
|
import { apiKeys, models, providers } from "../../persistence/schema";
|
|
@@ -34,12 +34,12 @@ const MIN_TOKEN_LENGTH = 20;
|
|
|
34
34
|
/**
|
|
35
35
|
* Cadence of the share stats SSE snapshot.
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* Roughly one tenth of the cache TTL (`SHARE_STATS_CACHE_TTL_SECONDS`, 300
|
|
38
|
+
* s), so nearly every tick is served from the cache and the cold-compute
|
|
39
|
+
* path runs at most once per five minutes per family. Short enough to still
|
|
40
|
+
* read as live.
|
|
41
41
|
*/
|
|
42
|
-
const SHARE_STATS_STREAM_INTERVAL_MS =
|
|
42
|
+
const SHARE_STATS_STREAM_INTERVAL_MS = 30_000;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Context and capabilities for one allowed model, sourced from the public
|
|
@@ -89,12 +89,12 @@ function json(body: unknown, status = 200): Response {
|
|
|
89
89
|
function notFound(refusal: ShareLinkRefusal = "not_found"): Response {
|
|
90
90
|
const message =
|
|
91
91
|
refusal === "expired"
|
|
92
|
-
? "This share link has expired"
|
|
92
|
+
? "This share link has expired (request {requestId}). Time for a fresh one 🌷"
|
|
93
93
|
: refusal === "revoked"
|
|
94
|
-
? "This share link's key has been revoked"
|
|
94
|
+
? "This share link's key has been revoked (request {requestId}). Ask for a new one 📩"
|
|
95
95
|
: refusal === "disabled"
|
|
96
|
-
? "This share link's key is paused"
|
|
97
|
-
: "Share link is unavailable";
|
|
96
|
+
? "This share link's key is paused (request {requestId}). Re-enable it when ready 🌱"
|
|
97
|
+
: "Share link is unavailable (request {requestId}). This link leads nowhere — check it again 🔗";
|
|
98
98
|
const error = new GatewayError(`link_${refusal}`, 404, message);
|
|
99
99
|
return json(publicGatewayErrorBody(error), error.status);
|
|
100
100
|
}
|
|
@@ -261,7 +261,7 @@ async function resolveFamilyStats(
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
/** Creates the public enrollment page and one-time shared-key issuance route. */
|
|
264
|
-
export function createShareRouter(options: ShareRouterOptions):
|
|
264
|
+
export function createShareRouter(options: ShareRouterOptions): AnyElysia {
|
|
265
265
|
const { db, shareStore } = options;
|
|
266
266
|
|
|
267
267
|
return new Elysia()
|
|
@@ -486,5 +486,5 @@ export function createShareRouter(options: ShareRouterOptions): Elysia {
|
|
|
486
486
|
};
|
|
487
487
|
}),
|
|
488
488
|
);
|
|
489
|
-
})
|
|
489
|
+
})
|
|
490
490
|
}
|