cartethyia 2.7.3 → 2.7.5
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 +2 -0
- package/README.md +17 -5
- package/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-Cj15GsMp.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-BIccZmeL.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-DkXb6dx-.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-DnQPSIA8.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js +9 -0
- package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-WTa0sktC.js} +1 -1
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-BfAb5-o_.js} +3 -3
- package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
- package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
- package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C0YYuZJ4.js → assert-C0K-c4eo.js} +1 -1
- package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-DfbhQU4k.js} +1 -1
- package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-BllT4LcF.js} +1 -1
- package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/{card-Be5Uzamx.js → card-CtkX66Wd.js} +1 -1
- package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-muFRphDu.js → cli-tools-CQXVMn4P.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-BQqBQd2n.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-BEK7KP0H.js} +1 -1
- package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-BrTEZ8zu.js} +1 -1
- package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
- package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-EBHJ3DrE.js} +1 -1
- package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-BAACA727.js} +1 -1
- package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-YSCE4UC2.js} +1 -1
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
- package/dist/dashboard/assets/{index-cPrX5vn6.js → index-FQlj0HUT.js} +2 -2
- package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
- package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-C4gU5iOL.js} +1 -1
- package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
- package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-BHHX_Tmm.js} +1 -1
- package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
- package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-CajrYUnD.js} +1 -1
- package/dist/dashboard/assets/{layout-C_7A6H55.js.gz → layout-CajrYUnD.js.gz} +0 -0
- package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-Bx2I7XUe.js} +1 -1
- package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
- package/dist/dashboard/assets/live-DNNqRbj2.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-C5UBACrJ.js +1 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.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-DvQbD_sr.js +1 -0
- package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
- package/dist/dashboard/assets/{routing-BEH32QOC.js → routing-in0f_IfK.js} +1 -1
- package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
- package/dist/dashboard/assets/{select-B3iTetHN.js → select-BLULNqTp.js} +2 -2
- package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
- package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-D2lIR0tg.js} +1 -1
- package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
- package/dist/dashboard/assets/{state-DLS7IDMy.js → state-DTARjfiW.js} +1 -1
- package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
- package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-c0oPSnqW.js} +1 -1
- package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-m8WGNPrz.js} +1 -1
- package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +59 -57
- 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/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
- package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
- package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
- package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
- package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
- package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -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/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
- package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
- package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
- package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
- package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
- package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
- package/package.json +4 -5
- package/scripts/cli/update.ts +15 -41
- package/src/boot/app.ts +67 -10
- package/src/boot/dependencies.ts +786 -732
- package/src/boot/lifecycle.ts +11 -12
- package/src/config/cooldowns.ts +34 -55
- package/src/config/network.ts +8 -4
- package/src/config/package-paths.ts +1 -1
- package/src/config/providers.ts +13 -14
- package/src/config/resources.ts +0 -5
- package/src/config/runtime.ts +2 -5
- package/src/config/spec.ts +26 -6
- package/src/console/api-keys/ban-constants.ts +38 -0
- package/src/console/api-keys/contracts.ts +92 -31
- package/src/console/api-keys/routes.ts +131 -6
- package/src/console/audit/routes.ts +3 -3
- package/src/console/auth/access.ts +4 -4
- package/src/console/auth/account-reset.ts +5 -5
- package/src/console/auth/audit-recorder.ts +1 -1
- package/src/console/auth/lockout.ts +7 -3
- package/src/console/auth/row-coercion.ts +57 -12
- package/src/console/auth/session-resolver.ts +1 -1
- package/src/console/auth/session-service.ts +8 -2
- package/src/console/auth/session.ts +40 -34
- package/src/console/auth/setup-service.ts +4 -3
- package/src/console/auth/user-store.ts +1 -1
- package/src/console/backup/9router-map.ts +0 -3
- package/src/console/backup/contracts.ts +11 -9
- package/src/console/backup/export-endpoint.ts +11 -12
- package/src/console/backup/nine-router.ts +1 -7
- package/src/console/backup/routes.ts +32 -36
- package/src/console/backup/service.ts +468 -468
- package/src/console/backup/store.ts +47 -29
- package/src/console/backup/validate.ts +11 -1
- package/src/console/cli-tools/contracts.ts +7 -7
- package/src/console/cli-tools/injectors/cline.ts +110 -110
- package/src/console/cli-tools/injectors/codex.ts +181 -181
- package/src/console/cli-tools/injectors/copilot.ts +108 -108
- package/src/console/cli-tools/injectors/driver.ts +247 -247
- package/src/console/cli-tools/injectors/jcode.ts +115 -115
- package/src/console/cli-tools/injectors/openclaw.ts +161 -161
- package/src/console/cli-tools/injectors/opencode.ts +153 -153
- package/src/console/cli-tools/routes.ts +4 -4
- package/src/console/cli-tools/service.ts +1 -1
- package/src/console/cli-tools/store.ts +2 -2
- package/src/console/console-router-guards.ts +2 -2
- package/src/console/console-router.ts +36 -4
- package/src/console/dashboard-assets.ts +9 -9
- package/src/console/data-management/routes.ts +3 -3
- package/src/console/domain-registration.ts +18 -4
- package/src/console/inferhub/client.ts +0 -7
- package/src/console/inferhub/key-store.ts +1 -1
- package/src/console/inferhub/normalize.ts +2 -2
- package/src/console/inferhub/routes.ts +1 -1
- package/src/console/inferhub/types.ts +1 -1
- package/src/console/observability/contracts.ts +5 -1
- package/src/console/observability/live.ts +34 -21
- package/src/console/observability/logs.ts +4 -4
- package/src/console/observability/performance.ts +3 -3
- package/src/console/observability/routes.ts +3 -3
- package/src/console/observability/sse.ts +62 -8
- package/src/console/observability/store.ts +36 -20
- package/src/console/providers/catalog/catalog-projections.ts +1 -1
- package/src/console/providers/catalog/contracts.ts +82 -22
- package/src/console/providers/catalog/model-operations.ts +1 -1
- package/src/console/providers/catalog/provider-operations.ts +110 -17
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +30 -6
- package/src/console/providers/catalog/store-helpers.ts +349 -326
- package/src/console/providers/catalog/store.ts +1662 -1292
- package/src/console/providers/detail/routes.ts +4 -4
- package/src/console/providers/oauth/callback-listener.ts +100 -18
- package/src/console/providers/oauth/routes.ts +95 -16
- package/src/console/quota/account-quota-global.ts +41 -3
- package/src/console/quota/account-quota-shared.ts +10 -3
- package/src/console/quota/account-quota-tenant.ts +285 -67
- package/src/console/quota/account-test-egress.ts +4 -4
- package/src/console/quota/daily-growth-job.ts +308 -0
- package/src/console/quota/native-identity.ts +2 -2
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/routes.ts +1 -1
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/routing/model/contracts.ts +1 -1
- package/src/console/routing/model/routes.ts +3 -3
- package/src/console/routing/pools/contracts.ts +13 -10
- package/src/console/routing/pools/import-parse.ts +3 -3
- 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 +12 -12
- package/src/console/routing/pools/routes.ts +3 -3
- package/src/console/routing/pools/store.ts +21 -1
- package/src/console/settings/contracts.ts +29 -2
- package/src/console/settings/routes.ts +3 -3
- package/src/console/settings/store.ts +20 -0
- package/src/console/share/share-router.ts +112 -44
- package/src/console/share/share-stats.ts +109 -39
- package/src/console/share/share-usage.ts +1 -1
- package/src/console/shared/errors.ts +2 -2
- package/src/console/shared/object.ts +18 -0
- package/src/console/shared/read-through-cache.ts +42 -6
- package/src/console/shared/transaction.ts +14 -0
- package/src/console/system-prompt/contracts.ts +89 -12
- package/src/console/system-prompt/routes.ts +98 -45
- package/src/console/updates/apply-update.ts +244 -0
- package/src/console/updates/routes.ts +256 -98
- package/src/main.ts +11 -5
- package/src/network/http2-bidi.ts +1 -1
- package/src/network/ip-utils.ts +298 -0
- package/src/network/outbound-fetch.ts +76 -5
- package/src/network/pool/agent.ts +7 -6
- package/src/network/pool/byte-accounting.ts +1 -1
- package/src/network/pool/resolver.ts +55 -51
- package/src/network/pool/selector.ts +150 -136
- package/src/network/pool-cooldown-store.ts +71 -16
- package/src/network/pool-health-machine.ts +22 -2
- package/src/network/rate-limit-verdict.ts +68 -0
- package/src/network/retry.ts +1 -1
- package/src/network/ssrf.ts +24 -75
- package/src/observability/event-log.ts +2 -2
- package/src/observability/event-query.ts +286 -214
- package/src/observability/log-sanitize.ts +1 -1
- package/src/observability/metrics.ts +4 -4
- package/src/observability/payload-capture.ts +6 -5
- package/src/observability/payload-store.ts +1 -1
- package/src/observability/runtime-metrics.ts +1 -1
- package/src/observability/telemetry-buffer.ts +12 -3
- package/src/observability/telemetry-reconciliation.ts +3 -3
- package/src/observability/telemetry-status.ts +1 -1
- package/src/observability/token-speed.ts +1 -1
- package/src/persistence/connection-url.ts +1 -1
- package/src/persistence/db-handle.ts +1 -1
- package/src/persistence/db-pg.ts +2 -2
- package/src/persistence/db-sqlite.ts +10 -5
- package/src/persistence/dialect.ts +5 -1
- package/src/persistence/readiness.ts +19 -7
- package/src/persistence/redis.ts +1 -1
- package/src/persistence/schema.ts +177 -45
- package/src/persistence/shared-cache.ts +2 -2
- package/src/persistence/sort-order.ts +1 -1
- package/src/persistence/stores/api-key-store.ts +109 -4
- package/src/persistence/stores/share-store.ts +20 -8
- package/src/persistence/stores/telemetry-store.ts +1 -1
- package/src/persistence/system-prompt-store.ts +63 -18
- package/src/persistence/tenant-preferences.ts +1 -1
- package/src/persistence/ttl-cache.ts +3 -3
- package/src/persistence/usage-flush.ts +7 -2
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/codecs.ts +12 -16
- package/src/protocol/dsml.ts +3 -49
- package/src/protocol/gateway-error.ts +6 -3
- package/src/protocol/image-clamp.ts +2 -2
- package/src/protocol/image-generation.ts +2 -2
- package/src/protocol/reasoning-text.ts +1 -1
- package/src/protocol/reasoning.ts +7 -52
- package/src/protocol/registry.ts +50 -15
- package/src/protocol/request/chat.ts +46 -19
- package/src/protocol/request/codex.ts +1 -1
- package/src/protocol/request/messages.ts +46 -14
- package/src/protocol/request/responses.ts +19 -10
- package/src/protocol/response/chat.ts +27 -12
- package/src/protocol/response/codex.ts +2 -2
- package/src/protocol/response/gemini.ts +1 -1
- package/src/protocol/response/messages.ts +6 -6
- package/src/protocol/response/omp-tool-call-markup.ts +1 -1
- package/src/protocol/response/responses.ts +3 -3
- package/src/protocol/sse-decoder.ts +64 -4
- package/src/protocol/stop-reason.ts +2 -2
- package/src/protocol/think-split.ts +347 -0
- package/src/protocol/translation/attachment-integrity.ts +3 -3
- package/src/protocol/translation/cache-controls.ts +8 -20
- package/src/protocol/translation/capabilities.ts +8 -8
- package/src/protocol/translation/dialect/deepseek.ts +10 -0
- package/src/protocol/translation/dialect/inband.ts +39 -0
- package/src/protocol/translation/dialect/model-thinking.ts +89 -0
- package/src/protocol/translation/dialect/qwen.ts +114 -0
- package/src/protocol/translation/dialect/resolver.ts +83 -0
- package/src/protocol/translation/dialect/rules.ts +93 -0
- package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
- package/src/protocol/translation/dialect/types.ts +58 -0
- package/src/protocol/translation/quirks.ts +18 -4
- package/src/protocol/translation/thinking.ts +12 -252
- package/src/protocol/translation/tool-id.ts +3 -3
- package/src/protocol/translation/tool-repair.ts +1 -1
- package/src/protocol/upstream/post-json.ts +6 -1
- 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 +44 -5
- package/src/providers/authentication/jwt-validator.ts +0 -5
- package/src/providers/authentication/oauth-client.ts +254 -253
- package/src/providers/authentication/oauth-flow-store.ts +42 -7
- package/src/providers/authentication/oauth-refresh-service.ts +2 -2
- package/src/providers/bundled-discovery-providers.ts +6 -30
- package/src/providers/configured-openai-providers.ts +82 -97
- package/src/providers/configured-provider.ts +1 -1
- package/src/providers/default-registry.ts +23 -35
- package/src/providers/discovery/catalog/catalog-format.ts +13 -2
- package/src/providers/discovery/discovery-types.ts +0 -1
- package/src/providers/discovery/openai-model-discovery.ts +2 -2
- package/src/providers/discovery/probe-phases.ts +50 -15
- package/src/providers/discovery/probing-service.ts +18 -20
- package/src/providers/integrations/agentrouter/agentrouter.ts +14 -5
- package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
- package/src/providers/integrations/anthropic.ts +4 -11
- package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
- package/src/providers/integrations/antigravity/antigravity.ts +15 -26
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
- package/src/providers/integrations/buddy/buddy-identity.ts +108 -1
- package/src/providers/integrations/buddy/buddy-media.ts +16 -16
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
- package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -16
- package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
- package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -10
- package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
- package/src/providers/integrations/claude/claude-betas.ts +1 -1
- package/src/providers/integrations/claude/claude-cch.ts +148 -148
- 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/claude/fallback-credit.ts +3 -7
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
- package/src/providers/integrations/codex/codex-device-code.ts +101 -101
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex-oauth.ts +0 -1
- package/src/providers/integrations/codex/codex.ts +14 -15
- package/src/providers/integrations/commandcode/commandcode.ts +76 -116
- package/src/providers/integrations/commandcode/quota.ts +132 -48
- package/src/providers/integrations/cursor/cursor-http2.ts +11 -14
- package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
- package/src/providers/integrations/cursor/cursor-request.ts +0 -87
- package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
- package/src/providers/integrations/cursor/cursor.ts +53 -20
- package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
- package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
- package/src/providers/integrations/factory/factory.ts +13 -13
- package/src/providers/integrations/gemini.ts +11 -11
- package/src/providers/integrations/github/github-discovery.ts +55 -10
- package/src/providers/integrations/github/github-oauth.ts +6 -5
- package/src/providers/integrations/github/github.ts +47 -20
- package/src/providers/integrations/grok/grok-oauth.ts +261 -261
- package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
- package/src/providers/integrations/grok/grok-quota.ts +16 -7
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
- package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
- package/src/providers/integrations/grok/grok.ts +88 -42
- package/src/providers/integrations/inferhub.ts +4 -11
- package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
- package/src/providers/integrations/kimi/kimi.ts +4 -11
- package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
- package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
- package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
- package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
- package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
- package/src/providers/integrations/kiro/kiro-request.ts +8 -11
- package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
- package/src/providers/integrations/kiro/kiro.ts +91 -78
- package/src/providers/integrations/muse/muse-oauth.ts +0 -1
- package/src/providers/integrations/muse/muse-quota.ts +0 -4
- package/src/providers/integrations/muse/muse.ts +6 -9
- package/src/providers/integrations/opencode/fingerprint.ts +7 -5
- package/src/providers/integrations/opencode/provider.ts +17 -24
- package/src/providers/integrations/perplexity.ts +2 -2
- package/src/providers/integrations/qoder/qoder-catalog.ts +132 -17
- 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-oauth.ts +165 -25
- package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
- package/src/providers/integrations/qoder/qoder-realm.ts +22 -28
- package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +33 -38
- package/src/providers/integrations/qoder/qoder-virtual.ts +14 -2
- package/src/providers/integrations/qoder/qoder.ts +521 -63
- package/src/providers/integrations/shared/connect.ts +0 -54
- package/src/providers/integrations/shared/hash.ts +15 -0
- package/src/providers/integrations/shared/session-cache.ts +136 -0
- package/src/providers/integrations/tokenharbor.ts +2 -2
- package/src/providers/integrations/trae/trae-cache.ts +7 -87
- package/src/providers/integrations/trae/trae-catalog.ts +1 -6
- package/src/providers/integrations/trae/trae-protocol.ts +17 -8
- package/src/providers/integrations/trae/trae-realm.ts +0 -7
- package/src/providers/integrations/trae/trae.ts +59 -19
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
- package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
- package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
- package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
- package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
- package/src/providers/operations/account-error-classification.ts +131 -116
- package/src/providers/operations/account-health-service.ts +31 -39
- package/src/providers/operations/account-reset-service.ts +58 -1
- package/src/providers/operations/cli-platform.ts +31 -5
- package/src/providers/operations/client-version-resolver.ts +4 -6
- package/src/providers/operations/client-versions.ts +10 -7
- package/src/providers/operations/provider-catalog-seeder.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +22 -6
- package/src/providers/operations/provider-credential-service.ts +35 -6
- package/src/providers/operations/upstream-deadline.ts +3 -3
- package/src/providers/provider-contracts.ts +65 -10
- package/src/providers/provider-credential-resolver.ts +1 -1
- package/src/providers/provider-metadata.ts +17 -2
- package/src/providers/provider-model-definition.ts +22 -1
- package/src/providers/provider-probe.ts +4 -2
- package/src/providers/provider-registry.ts +4 -4
- package/src/providers/provider-usage.ts +81 -16
- package/src/providers/quota/cache.ts +29 -0
- package/src/providers/quota/quota-contracts.ts +28 -10
- package/src/providers/quota/refresh.ts +29 -6
- package/src/providers/quota/snapshot.ts +94 -0
- package/src/providers/search/search-provider.ts +6 -8
- package/src/providers/search/search-providers.ts +2 -1
- package/src/security/abuse.ts +8 -8
- package/src/security/admission/reasons.ts +17 -17
- package/src/security/admission/service.ts +2 -2
- package/src/security/admission/ttl.ts +61 -61
- package/src/security/api-key-auth.ts +20 -8
- package/src/security/client-router-fingerprint.ts +4 -4
- package/src/security/credential-codec.ts +3 -3
- package/src/security/csrf.ts +2 -2
- package/src/security/ip-boundary.ts +16 -189
- package/src/security/model-access-rule.ts +1 -1
- package/src/security/outbound-headers.ts +18 -0
- package/src/shared/coerce.ts +19 -0
- package/src/shared/invoke-guard.ts +47 -0
- package/src/transport/contracts.ts +13 -4
- package/src/transport/dispatch/attempt-finalize.ts +5 -4
- package/src/transport/dispatch/attempt-loop.ts +10 -7
- package/src/transport/dispatch/capture-policy.ts +34 -37
- package/src/transport/dispatch/empty-stream.ts +28 -25
- package/src/transport/dispatch/generation.ts +33 -53
- package/src/transport/dispatch/leases.ts +8 -8
- package/src/transport/dispatch/native-shared.ts +127 -0
- package/src/transport/dispatch/proxy-request.ts +10 -8
- package/src/transport/dispatch/responses-compact.ts +35 -56
- package/src/transport/dispatch/retry-policy.ts +4 -67
- package/src/transport/dispatch/stream-bridge.ts +1 -1
- package/src/transport/dispatch/streaming/encoder.ts +18 -72
- package/src/transport/dispatch/streaming/finalize.ts +11 -10
- package/src/transport/dispatch/streaming/stream.ts +7 -3
- package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
- package/src/transport/dispatch/systemone.ts +27 -55
- package/src/transport/dispatch/token-count.ts +49 -46
- package/src/transport/dispatch/websearch.ts +24 -50
- package/src/transport/drain-endpoint.ts +3 -3
- package/src/transport/failure-policy.ts +19 -6
- package/src/transport/guards/auth-guard.ts +6 -11
- package/src/transport/guards/body-policy.ts +7 -7
- 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 +2 -2
- package/src/transport/guards/pipeline.ts +7 -7
- package/src/transport/guards/readiness-guard.ts +16 -2
- package/src/transport/guards/request-context.ts +15 -20
- package/src/transport/request/preparer.ts +306 -27
- package/src/transport/request/rtk/compress-request.ts +3 -3
- package/src/transport/request/rtk/rtk-constants.ts +2 -2
- package/src/transport/request/rtk/rtk-filters.ts +8 -8
- package/src/transport/request/state.ts +5 -20
- package/src/transport/request/system-prompt-injection.ts +74 -11
- package/src/transport/request/tenant-preferences.ts +22 -0
- package/src/transport/routing/candidate-index.ts +2 -2
- package/src/transport/routing/round-robin.ts +1 -1
- package/src/transport/routing/route-catalog.ts +113 -19
- package/src/transport/routing/route-model.ts +131 -38
- package/src/transport/routing/router.ts +154 -39
- package/src/transport/shutdown-notice.ts +21 -9
- package/src/transport/surface/adapters.ts +3 -3
- package/src/transport/surface/chat/encode.ts +7 -7
- package/src/transport/surface/chat/parse.ts +7 -7
- package/src/transport/surface/completion.ts +3 -3
- package/src/transport/surface/content-parts.ts +1 -1
- package/src/transport/surface/dialects.ts +2 -2
- package/src/transport/surface/messages/encode.ts +1 -1
- package/src/transport/surface/messages/parse.ts +3 -3
- package/src/transport/surface/responses/adapter.ts +1 -1
- package/src/transport/surface/responses/errors.ts +0 -9
- package/src/transport/vision-handoff-cache.ts +58 -0
- package/src/transport/vision-handoff.ts +376 -0
- package/src/workers/account-identity-sweep.ts +2 -2
- package/src/workers/buddy-growth-tasks.ts +392 -346
- package/src/workers/daily-checkin.ts +102 -49
- package/src/workers/maintenance-lease.ts +2 -35
- package/src/workers/oauth-refresh-sweep.ts +8 -1
- package/src/workers/quota-refresh-sweep.ts +120 -82
- package/src/workers/scheduler.ts +67 -4
- package/src/workers/sweep-runner.ts +24 -5
- package/src/workers/zcode-gift-claim.ts +108 -0
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- 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 +0 -1
- 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/ProviderIcon-FB6yS4v1.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.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 +0 -1
- 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.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.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/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 +0 -2
- 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.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/stream-retry-5QnVuSSo.js +0 -1
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.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/src/network/response-headers.ts +0 -39
- package/src/protocol/response/dsml.ts +0 -38
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
- /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-Bgd3zANQ.js} +0 -0
- /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-Bgd3zANQ.js.gz} +0 -0
- /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
- /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background execution for the manual daily-growth batch (`POST /quota/daily-growth`).
|
|
3
|
+
*
|
|
4
|
+
* The route used to run the whole batch synchronously — chunked 3-parallel,
|
|
5
|
+
* up to two upstream HTTP calls per account, no global timeout — so a large
|
|
6
|
+
* roster held the console request open and blocked the event loop. This module
|
|
7
|
+
* moves the orchestration to an in-process background job: the route enqueues
|
|
8
|
+
* and returns `{ jobId }` immediately, and a status route reads the job's
|
|
9
|
+
* progress.
|
|
10
|
+
*
|
|
11
|
+
* The per-account runner (report then claim, egress rotation, per-leg timeout)
|
|
12
|
+
* is injected by the route — the job owns only the orchestration: the
|
|
13
|
+
* bounded-concurrency worker loop, the single-result aggregation, and the
|
|
14
|
+
* deduplication guards. This keeps the module free of the route's imports and
|
|
15
|
+
* avoids a cycle between the two quota modules.
|
|
16
|
+
*
|
|
17
|
+
* One job per (tenant, provider) key is allowed at a time. The key is the
|
|
18
|
+
* double-claim guard between two manual triggers: enqueuing again while a job
|
|
19
|
+
* for the same tenant+provider is still queued/running returns the existing
|
|
20
|
+
* job id instead of stamping the same accounts twice. In parallel with the
|
|
21
|
+
* daily sweep, the ledger is the guard — the runner stamps
|
|
22
|
+
* `markCheckinAttempted` after a successful claim, and this runner skips
|
|
23
|
+
* accounts the sweep (or an earlier run) already settled today, so the same
|
|
24
|
+
* account is never claimed twice in one window.
|
|
25
|
+
*
|
|
26
|
+
* The registry is intentionally process-local: the manual trigger is an
|
|
27
|
+
* operator action on a single gateway, so a cross-process queue would buy
|
|
28
|
+
* nothing here.
|
|
29
|
+
*/
|
|
30
|
+
import { randomUUID } from "node:crypto";
|
|
31
|
+
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
32
|
+
import type { RedisClient } from "../../persistence/redis";
|
|
33
|
+
import { checkinAttemptedToday } from "../../workers/daily-checkin";
|
|
34
|
+
import type { ValidatedFetch } from "../../network/outbound-fetch";
|
|
35
|
+
|
|
36
|
+
/** Lifecycle states of a daily-growth job. */
|
|
37
|
+
export type DailyGrowthJobState = "queued" | "running" | "done" | "failed";
|
|
38
|
+
|
|
39
|
+
/** One account the job failed to settle, with the reason. */
|
|
40
|
+
export interface DailyGrowthJobError {
|
|
41
|
+
readonly accountId: string;
|
|
42
|
+
readonly error: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Aggregate outcome of one job. */
|
|
46
|
+
export interface DailyGrowthJobResult {
|
|
47
|
+
/** Accounts the job actually attempted (excludes skipped/already-settled). */
|
|
48
|
+
readonly total: number;
|
|
49
|
+
readonly succeeded: number;
|
|
50
|
+
readonly failed: number;
|
|
51
|
+
readonly errors: readonly DailyGrowthJobError[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Public view of a job, as the status route returns it. */
|
|
55
|
+
export interface DailyGrowthJobView {
|
|
56
|
+
readonly jobId: string;
|
|
57
|
+
readonly state: DailyGrowthJobState;
|
|
58
|
+
readonly providerId: string;
|
|
59
|
+
readonly createdAt: string;
|
|
60
|
+
readonly finishedAt: string | null;
|
|
61
|
+
readonly total: number;
|
|
62
|
+
readonly succeeded: number;
|
|
63
|
+
readonly failed: number;
|
|
64
|
+
readonly errors: readonly DailyGrowthJobError[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One account the job will run the daily-growth pass over. */
|
|
68
|
+
export interface DailyGrowthJobAccount {
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly tenantId: string | null;
|
|
71
|
+
readonly label: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Outcome the route's per-account runner reports. */
|
|
75
|
+
export interface DailyGrowthAccountOutcome {
|
|
76
|
+
readonly ok: boolean;
|
|
77
|
+
/** Present when `ok` is false: the reason the account was not settled. */
|
|
78
|
+
readonly error?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Everything the background runner needs to execute one job. */
|
|
82
|
+
export interface DailyGrowthJobDeps {
|
|
83
|
+
readonly db: CartethyiaDatabase;
|
|
84
|
+
readonly redis: RedisClient | undefined;
|
|
85
|
+
/** Runs one account's daily-growth pass (report then claim). */
|
|
86
|
+
readonly runAccount: (account: DailyGrowthJobAccount) => Promise<DailyGrowthAccountOutcome>;
|
|
87
|
+
/** Invalidated once when the job finishes, so the page re-reads the ledger. */
|
|
88
|
+
readonly snapshotInvalidator?: { invalidate(): Promise<number> };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Bound on accounts processed at once. Mirrors the old synchronous chunk size. */
|
|
92
|
+
const DAILY_GROWTH_CONCURRENCY = 3;
|
|
93
|
+
/**
|
|
94
|
+
* Per-account leg budget. Each individual upstream call already carries its own
|
|
95
|
+
* 15s abort timeout; this bounds the whole leg (credential resolve + report +
|
|
96
|
+
* check-in) so a single stuck account cannot hold a job worker open forever.
|
|
97
|
+
*/
|
|
98
|
+
export const DAILY_GROWTH_LEG_TIMEOUT_MS = 60_000;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Wraps a fetcher so every request also carries `budget`, even when the callee
|
|
102
|
+
* sets its own per-request timeout. The buddy/qoder billing helpers pass an
|
|
103
|
+
* `AbortSignal.timeout(...)` of their own, which would override a plain
|
|
104
|
+
* `signalFetch`-style injection; combining signals intersects both bounds.
|
|
105
|
+
*/
|
|
106
|
+
export function withLegBudget(
|
|
107
|
+
fetcher: ValidatedFetch | typeof fetch,
|
|
108
|
+
budget: AbortSignal,
|
|
109
|
+
): typeof fetch {
|
|
110
|
+
const wrapped = (
|
|
111
|
+
input: Parameters<typeof fetch>[0],
|
|
112
|
+
init?: Parameters<typeof fetch>[1],
|
|
113
|
+
): Promise<Response> =>
|
|
114
|
+
fetcher(input, {
|
|
115
|
+
...init,
|
|
116
|
+
signal: init?.signal ? AbortSignal.any([budget, init.signal]) : budget,
|
|
117
|
+
});
|
|
118
|
+
return Object.assign(wrapped, {
|
|
119
|
+
preconnect: (fetcher as typeof fetch).preconnect,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Registry cap; oldest finished jobs are evicted so the map cannot grow unbounded. */
|
|
124
|
+
const DAILY_GROWTH_JOB_MAX = 64;
|
|
125
|
+
|
|
126
|
+
interface DailyGrowthJobRecord {
|
|
127
|
+
readonly id: string;
|
|
128
|
+
readonly key: string;
|
|
129
|
+
readonly tenantId: string;
|
|
130
|
+
readonly providerId: string;
|
|
131
|
+
readonly createdAt: number;
|
|
132
|
+
state: DailyGrowthJobState;
|
|
133
|
+
finishedAt: number | null;
|
|
134
|
+
readonly accounts: readonly DailyGrowthJobAccount[];
|
|
135
|
+
result: DailyGrowthJobResult;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const jobs = new Map<string, DailyGrowthJobRecord>();
|
|
139
|
+
/** tenant+provider key → job id; the double-trigger guard. */
|
|
140
|
+
const jobsByKey = new Map<string, string>();
|
|
141
|
+
/**
|
|
142
|
+
* Accounts currently being worked by any job. Global accounts (`tenant_id IS
|
|
143
|
+
* NULL`) are visible to every tenant, so two tenants can enqueue jobs over the
|
|
144
|
+
* same global account; this set keeps them from stamping it twice in the same
|
|
145
|
+
* window. Tenant-owned accounts cannot collide this way (they are unique to
|
|
146
|
+
* their tenant), but the set is uniform and cheap.
|
|
147
|
+
*/
|
|
148
|
+
const inFlightAccounts = new Set<string>();
|
|
149
|
+
|
|
150
|
+
function jobView(job: DailyGrowthJobRecord): DailyGrowthJobView {
|
|
151
|
+
return {
|
|
152
|
+
jobId: job.id,
|
|
153
|
+
state: job.state,
|
|
154
|
+
providerId: job.providerId,
|
|
155
|
+
createdAt: new Date(job.createdAt).toISOString(),
|
|
156
|
+
finishedAt: job.finishedAt === null ? null : new Date(job.finishedAt).toISOString(),
|
|
157
|
+
total: job.result.total,
|
|
158
|
+
succeeded: job.result.succeeded,
|
|
159
|
+
failed: job.result.failed,
|
|
160
|
+
errors: job.result.errors,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Evicts the oldest finished jobs once the registry exceeds its cap. */
|
|
165
|
+
function evictFinishedJobs(): void {
|
|
166
|
+
if (jobs.size <= DAILY_GROWTH_JOB_MAX) return;
|
|
167
|
+
const finished: DailyGrowthJobRecord[] = [];
|
|
168
|
+
for (const job of jobs.values()) {
|
|
169
|
+
if (job.state === "done" || job.state === "failed") finished.push(job);
|
|
170
|
+
}
|
|
171
|
+
finished.sort((left, right) => left.createdAt - right.createdAt);
|
|
172
|
+
let excess = jobs.size - DAILY_GROWTH_JOB_MAX;
|
|
173
|
+
for (const job of finished) {
|
|
174
|
+
if (excess <= 0) break;
|
|
175
|
+
jobs.delete(job.id);
|
|
176
|
+
if (jobsByKey.get(job.key) === job.id) jobsByKey.delete(job.key);
|
|
177
|
+
excess -= 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Runs one job to completion. Never rejects: every failure is collected into
|
|
183
|
+
* the job's result, so the status route always has an answer.
|
|
184
|
+
*/
|
|
185
|
+
async function runJob(record: DailyGrowthJobRecord, deps: DailyGrowthJobDeps): Promise<void> {
|
|
186
|
+
record.state = "running";
|
|
187
|
+
const succeeded: string[] = [];
|
|
188
|
+
const errors: DailyGrowthJobError[] = [];
|
|
189
|
+
const attempted: string[] = [];
|
|
190
|
+
let cursor = 0;
|
|
191
|
+
const accounts = record.accounts;
|
|
192
|
+
|
|
193
|
+
const runOne = async (account: DailyGrowthJobAccount): Promise<void> => {
|
|
194
|
+
// Double-claim guard with the daily sweep (and any other job): the ledger
|
|
195
|
+
// is shared, so an account the sweep already settled today is skipped
|
|
196
|
+
// rather than claimed again — the same `markCheckinAttempted` semantics
|
|
197
|
+
// the button uses.
|
|
198
|
+
if (inFlightAccounts.has(account.id)) return;
|
|
199
|
+
try {
|
|
200
|
+
if (await checkinAttemptedToday({ db: deps.db, redis: deps.redis, accountId: account.id })) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
// An unreadable ledger reads as "not attempted" — the safe direction for
|
|
205
|
+
// a manual trigger, which asserts "try now".
|
|
206
|
+
}
|
|
207
|
+
inFlightAccounts.add(account.id);
|
|
208
|
+
attempted.push(account.id);
|
|
209
|
+
try {
|
|
210
|
+
const outcome = await deps.runAccount(account);
|
|
211
|
+
if (outcome.ok) {
|
|
212
|
+
succeeded.push(account.id);
|
|
213
|
+
} else {
|
|
214
|
+
errors.push({ accountId: account.id, error: outcome.error ?? "Daily growth failed" });
|
|
215
|
+
}
|
|
216
|
+
} finally {
|
|
217
|
+
inFlightAccounts.delete(account.id);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
await Promise.all(
|
|
222
|
+
Array.from({ length: Math.min(DAILY_GROWTH_CONCURRENCY, accounts.length) }, async () => {
|
|
223
|
+
for (;;) {
|
|
224
|
+
const index = cursor;
|
|
225
|
+
cursor += 1;
|
|
226
|
+
if (index >= accounts.length) return;
|
|
227
|
+
await runOne(accounts[index]!);
|
|
228
|
+
}
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
record.result = {
|
|
233
|
+
total: attempted.length,
|
|
234
|
+
succeeded: succeeded.length,
|
|
235
|
+
failed: errors.length,
|
|
236
|
+
errors,
|
|
237
|
+
};
|
|
238
|
+
record.finishedAt = Date.now();
|
|
239
|
+
record.state = "done";
|
|
240
|
+
if (deps.snapshotInvalidator) {
|
|
241
|
+
try {
|
|
242
|
+
await deps.snapshotInvalidator.invalidate();
|
|
243
|
+
} catch {
|
|
244
|
+
// Best-effort: a failed snapshot invalidate must not fail the job.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Enqueues a daily-growth job and returns its id immediately. When a job for
|
|
251
|
+
* the same (tenant, provider) is already queued or running, the existing job
|
|
252
|
+
* id is returned instead — the operator pressed the button twice, and running
|
|
253
|
+
* the same accounts twice would double-claim.
|
|
254
|
+
*/
|
|
255
|
+
export function enqueueDailyGrowthJob(args: {
|
|
256
|
+
readonly deps: DailyGrowthJobDeps;
|
|
257
|
+
readonly tenantId: string;
|
|
258
|
+
readonly providerId: string;
|
|
259
|
+
readonly accounts: readonly DailyGrowthJobAccount[];
|
|
260
|
+
}): string {
|
|
261
|
+
const key = `${args.tenantId}:${args.providerId}`;
|
|
262
|
+
const existing = jobsByKey.get(key);
|
|
263
|
+
if (existing !== undefined) {
|
|
264
|
+
const job = jobs.get(existing);
|
|
265
|
+
if (job !== undefined && (job.state === "queued" || job.state === "running")) {
|
|
266
|
+
return job.id;
|
|
267
|
+
}
|
|
268
|
+
jobsByKey.delete(key);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const id = randomUUID();
|
|
272
|
+
const record: DailyGrowthJobRecord = {
|
|
273
|
+
id,
|
|
274
|
+
key,
|
|
275
|
+
tenantId: args.tenantId,
|
|
276
|
+
providerId: args.providerId,
|
|
277
|
+
createdAt: Date.now(),
|
|
278
|
+
state: "queued",
|
|
279
|
+
finishedAt: null,
|
|
280
|
+
accounts: args.accounts,
|
|
281
|
+
result: { total: 0, succeeded: 0, failed: 0, errors: [] },
|
|
282
|
+
};
|
|
283
|
+
jobs.set(id, record);
|
|
284
|
+
jobsByKey.set(key, id);
|
|
285
|
+
evictFinishedJobs();
|
|
286
|
+
void runJob(record, args.deps).catch(() => {
|
|
287
|
+
record.state = "failed";
|
|
288
|
+
record.finishedAt = Date.now();
|
|
289
|
+
});
|
|
290
|
+
return id;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Reads one job's status. Scoped to the requesting tenant: a job enqueued by
|
|
295
|
+
* another tenant is not visible, so one tenant cannot probe another's work.
|
|
296
|
+
*/
|
|
297
|
+
export function getDailyGrowthJob(jobId: string, tenantId: string): DailyGrowthJobView | null {
|
|
298
|
+
const job = jobs.get(jobId);
|
|
299
|
+
if (job === undefined || job.tenantId !== tenantId) return null;
|
|
300
|
+
return jobView(job);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Test seam: clears the registry so suites do not leak state across runs. */
|
|
304
|
+
export function clearDailyGrowthJobsForTests(): void {
|
|
305
|
+
jobs.clear();
|
|
306
|
+
jobsByKey.clear();
|
|
307
|
+
inFlightAccounts.clear();
|
|
308
|
+
}
|
|
@@ -20,7 +20,7 @@ import { ConsoleDomainError, requireGlobalAdmin, requireTenantScope } from "../s
|
|
|
20
20
|
import type { ConsoleAccessResolver } from "../auth/access";
|
|
21
21
|
|
|
22
22
|
/** True when `token` looks like a plausible machine token. */
|
|
23
|
-
|
|
23
|
+
function isValidNativeIdentityToken(token: string): boolean {
|
|
24
24
|
return typeof token === "string" && token.length >= 8 && token.length <= 512;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -120,7 +120,7 @@ export function createNativeIdentityOps(
|
|
|
120
120
|
* (`tenant_id IS NULL`) need platform admin; tenant-owned accounts need the
|
|
121
121
|
* tenant scope for the owning tenant.
|
|
122
122
|
*/
|
|
123
|
-
|
|
123
|
+
interface NativeIdentityScope {
|
|
124
124
|
readonly tenantId: string | null;
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "../../providers/quota/refresh";
|
|
12
12
|
|
|
13
13
|
/** Shape returned for quota data on dashboard account responses. */
|
|
14
|
-
|
|
14
|
+
interface QuotaRefreshView {
|
|
15
15
|
readonly source: string;
|
|
16
16
|
readonly status: "ready" | "error" | "refreshing";
|
|
17
17
|
readonly plan: string | null;
|
|
@@ -14,7 +14,7 @@ import { consoleErrorHandler, requireTenantScope } from "../shared/errors";
|
|
|
14
14
|
import { REMOTE_SUPPORT_TOOLS } from "./contracts";
|
|
15
15
|
import type { RemoteSupportService } from "./service";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
interface RemoteSupportRoutesConfig {
|
|
18
18
|
readonly accessResolver: ConsoleAccessResolver;
|
|
19
19
|
readonly service: RemoteSupportService;
|
|
20
20
|
}
|
|
@@ -33,7 +33,7 @@ const URL_POLL_MS = 500;
|
|
|
33
33
|
/** Cached install detection so we don't spawn `where`/`which` on every poll. */
|
|
34
34
|
const installedCache = new Map<RemoteSupportTool, boolean>();
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
interface RemoteSupportServiceConfig {
|
|
37
37
|
/** Defaults to the gateway's configured port. */
|
|
38
38
|
readonly port: number;
|
|
39
39
|
/** Overridable for tests. Defaults to a subdir of the user config dir. */
|
|
@@ -245,7 +245,7 @@ function tailscaleDnsName(): string | null {
|
|
|
245
245
|
* nothing about this: stopping the tunnel leaves the node up and empties the
|
|
246
246
|
* status, which is why an online node alone was read as "still active".
|
|
247
247
|
*/
|
|
248
|
-
|
|
248
|
+
function tailscaleServing(): boolean {
|
|
249
249
|
const result = spawnSync("tailscale", ["serve", "status", "--json"], { encoding: "utf8", windowsHide: true });
|
|
250
250
|
if (result.status !== 0) return false;
|
|
251
251
|
let parsed: unknown;
|
|
@@ -102,7 +102,7 @@ export interface ModelRoutingStore {
|
|
|
102
102
|
|
|
103
103
|
/** Structurally satisfied by `InMemoryRouteSnapshotService` as-is — no import
|
|
104
104
|
* of routing internals into the console domain layer. */
|
|
105
|
-
|
|
105
|
+
interface ModelRoutingSnapshotInvalidator {
|
|
106
106
|
invalidate(): Promise<number>;
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -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
|
}
|
|
@@ -222,7 +223,7 @@ export type PoolStatus = (typeof POOL_STATUSES)[number];
|
|
|
222
223
|
* stay aligned with the `pool_routing_strategy` enum in `schema.ts`.
|
|
223
224
|
*/
|
|
224
225
|
export const POOL_ROUTING_STRATEGIES = ["least_loaded", "round_robin", "random"] as const;
|
|
225
|
-
|
|
226
|
+
type PoolRoutingStrategyValue = (typeof POOL_ROUTING_STRATEGIES)[number];
|
|
226
227
|
|
|
227
228
|
export interface PoolStrategySetting {
|
|
228
229
|
strategy: PoolRoutingStrategyValue;
|
|
@@ -455,7 +456,7 @@ export const MAX_BATCH_PROBE_TARGETS = 100;
|
|
|
455
456
|
*/
|
|
456
457
|
export const PROVIDER_PROBE_BATCH_CONCURRENCY = 4;
|
|
457
458
|
|
|
458
|
-
|
|
459
|
+
interface NetworkPoolAgentReleaser {
|
|
459
460
|
releasePool(poolId: string, tenantId: string): Promise<void>;
|
|
460
461
|
}
|
|
461
462
|
export interface NetworkPoolConfig {
|
|
@@ -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
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { POOL_KINDS, POOL_STATUSES } from "./contracts";
|
|
19
19
|
|
|
20
20
|
/** One row that parsed, ready to become a pool. */
|
|
21
|
-
|
|
21
|
+
interface ParsedProxyRow {
|
|
22
22
|
readonly kind: (typeof POOL_KINDS)[number];
|
|
23
23
|
readonly endpoint: string;
|
|
24
24
|
readonly credential: string | null;
|
|
@@ -26,12 +26,12 @@ export interface ParsedProxyRow {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** One row that did not parse, with the reason. */
|
|
29
|
-
|
|
29
|
+
interface SkippedProxyRow {
|
|
30
30
|
readonly line: number;
|
|
31
31
|
readonly reason: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
interface ParsedProxyFile {
|
|
35
35
|
readonly rows: readonly ParsedProxyRow[];
|
|
36
36
|
readonly skipped: readonly SkippedProxyRow[];
|
|
37
37
|
}
|
|
@@ -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
|
*
|