cartethyia 2.5.3 → 2.5.6
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 +13 -4
- package/README.md +2 -2
- package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
- package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
- package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
- package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
- package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
- package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
- package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
- package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
- package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
- package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
- package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
- package/dist/dashboard/assets/tokens-COzario9.js +1 -0
- package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
- package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -57
- package/migrations/README.md +52 -52
- package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
- package/package.json +96 -95
- package/scripts/cli/admin.ts +125 -0
- package/scripts/cli/autostart.ts +237 -0
- package/scripts/cli/{ui/background.ts → background.ts} +1 -1
- package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
- package/scripts/cli/doctor.ts +37 -0
- package/scripts/cli/{support/env.ts → env.ts} +99 -99
- package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
- package/scripts/cli/instance.ts +224 -0
- package/scripts/cli/main.ts +88 -18
- package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
- package/scripts/cli/port.ts +100 -0
- package/scripts/cli/{support/services.ts → services.ts} +88 -88
- package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
- package/scripts/cli/{command/start.ts → start.ts} +10 -5
- package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
- package/scripts/cli/tray/index.ts +62 -12
- package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
- package/scripts/cli/update.ts +40 -0
- package/scripts/cli/version.ts +67 -0
- package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
- package/src/boot/app.ts +11 -7
- package/src/boot/dependencies.ts +50 -19
- package/src/boot/timeout.ts +2 -13
- package/src/config/cooldowns.ts +186 -138
- package/src/config/network.ts +8 -14
- package/src/config/providers.ts +7 -10
- package/src/config/runtime.ts +76 -76
- package/src/config/security.ts +15 -0
- package/src/config/spec.ts +52 -7
- package/src/config/telemetry.ts +26 -0
- package/src/config/timeouts.ts +0 -6
- package/src/console/admin/model-abuse/contracts.ts +28 -6
- package/src/console/admin/model-abuse/routes.ts +56 -3
- package/src/console/api-keys/bucket-spend.ts +15 -4
- package/src/console/api-keys/contracts.ts +410 -414
- package/src/console/api-keys/routes.ts +881 -880
- package/src/console/auth/credentials.ts +3 -3
- package/src/console/auth/setup-service.ts +2 -2
- package/src/console/backup/contracts.ts +15 -2
- package/src/console/backup/export-endpoint.ts +6 -2
- package/src/console/backup/nine-router.ts +2 -5
- package/src/console/backup/service.ts +4 -0
- package/src/console/backup/store.ts +192 -29
- package/src/console/backup/validate.ts +3 -45
- package/src/console/cli-tools/fs-ops.ts +487 -487
- package/src/console/console-router-guards.ts +1 -1
- package/src/console/console-router.ts +8 -0
- package/src/console/data-management/contracts.ts +100 -0
- package/src/console/data-management/operations.ts +234 -0
- package/src/console/data-management/routes.ts +103 -0
- package/src/console/domain-registration.ts +45 -1
- package/src/console/observability/contracts.ts +524 -485
- package/src/console/observability/live.ts +22 -0
- package/src/console/observability/store.ts +1138 -1018
- package/src/console/observability/usage-dimensions.ts +4 -4
- package/src/console/providers/catalog/catalog-projections.ts +6 -7
- package/src/console/providers/catalog/contracts.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +5 -2
- package/src/console/providers/catalog/provider-operations.ts +24 -4
- package/src/console/providers/catalog/public-model-store.ts +8 -11
- package/src/console/providers/catalog/routes.ts +7 -0
- package/src/console/providers/catalog/store.ts +1553 -1532
- package/src/console/providers/oauth/routes.ts +8 -12
- package/src/console/quota/account-quota-shared.ts +52 -39
- package/src/console/quota/account-quota-tenant.ts +6 -6
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/quota/refresh.ts +441 -429
- package/src/console/remote-support/contracts.ts +61 -0
- package/src/console/remote-support/routes.ts +65 -0
- package/src/console/remote-support/service.ts +207 -0
- package/src/console/response-compression.ts +0 -3
- package/src/console/routing/pools/import-parse.ts +206 -0
- package/src/console/routing/pools/operations.ts +543 -501
- package/src/console/routing/pools/probe-result.ts +3 -2
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -164
- package/src/console/routing/pools/store.ts +8 -8
- package/src/console/settings/contracts.ts +95 -2
- package/src/console/settings/store.ts +81 -3
- package/src/console/shared/cursor.ts +8 -8
- package/src/console/shared/errors.ts +1 -3
- package/src/console/updates/routes.ts +16 -0
- package/src/main.ts +10 -0
- package/src/network/http2-bidi.ts +2 -4
- package/src/network/pool/agent.ts +478 -478
- package/src/network/pool/selector.ts +5 -4
- package/src/network/retry.ts +1 -2
- package/src/network/ssrf.ts +5 -4
- package/src/observability/event-compaction.ts +317 -0
- package/src/observability/event-day-index.ts +440 -0
- package/src/observability/event-log.ts +155 -6
- package/src/observability/logger.ts +6 -6
- package/src/observability/payload-capture.ts +158 -33
- package/src/observability/payload-store.ts +82 -61
- package/src/observability/redaction.ts +5 -6
- package/src/observability/request-notify.ts +43 -0
- package/src/observability/telemetry-status.ts +8 -9
- package/src/observability/token-speed.ts +5 -4
- package/src/persistence/db-mode.ts +7 -7
- package/src/persistence/health-event-flush.ts +36 -0
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/schema.ts +75 -29
- package/src/persistence/shared-cache.ts +1 -2
- package/src/persistence/stores/api-key-store.ts +18 -6
- package/src/persistence/stores/share-store.ts +4 -3
- package/src/persistence/stores/telemetry-store.ts +33 -11
- package/src/protocol/canonical-model.ts +11 -3
- package/src/protocol/codecs.ts +139 -6
- package/src/protocol/gateway-error.ts +6 -4
- package/src/protocol/image-clamp.ts +19 -1
- package/src/protocol/registry.ts +8 -0
- package/src/protocol/request/chat.ts +21 -1
- package/src/protocol/request/codex.ts +10 -1
- package/src/protocol/request/gemini.ts +9 -2
- package/src/protocol/request/messages.ts +18 -7
- package/src/protocol/request/responses.ts +9 -1
- package/src/protocol/response/chat.ts +34 -4
- package/src/protocol/response/codex.ts +7 -4
- package/src/protocol/response/dsml.ts +140 -28
- package/src/protocol/response/responses.ts +11 -5
- package/src/protocol/stream-error-frames.ts +4 -5
- package/src/providers/adapters/compatible-adapter.ts +3 -0
- package/src/providers/authentication/jwt-validator.ts +9 -10
- package/src/providers/authentication/oauth-refresh-service.ts +9 -11
- package/src/providers/bundled-discovery-providers.ts +122 -119
- package/src/providers/configured-openai-providers.ts +4 -4
- package/src/providers/default-registry.ts +5 -4
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/catalog/catalog-format.ts +3 -6
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/openai-model-discovery.ts +5 -0
- package/src/providers/discovery/openrouter-free-roster.ts +15 -17
- package/src/providers/discovery/probe-phases.ts +4 -4
- package/src/providers/discovery/probe-wire.ts +6 -13
- package/src/providers/discovery/probing-service.ts +1117 -1045
- package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
- package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
- package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
- package/src/providers/integrations/buddy/buddy-media.ts +2 -2
- package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
- package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
- package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
- package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
- package/src/providers/integrations/claude/claude.ts +608 -608
- package/src/providers/integrations/cline/cline-quota.ts +27 -20
- package/src/providers/integrations/cline/cline.ts +537 -539
- package/src/providers/integrations/codex/codex-headers.ts +25 -39
- package/src/providers/integrations/codex/codex-oauth.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +2 -29
- package/src/providers/integrations/commandcode/commandcode.ts +674 -675
- package/src/providers/integrations/commandcode/discovery.ts +117 -116
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
- package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
- package/src/providers/integrations/cursor/cursor-request.ts +616 -471
- package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
- package/src/providers/integrations/cursor/cursor.ts +1333 -960
- package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
- package/src/providers/integrations/factory/factory-models.ts +3 -5
- package/src/providers/integrations/factory/factory.ts +8 -1
- package/src/providers/integrations/gemini.ts +6 -1
- package/src/providers/integrations/grok/grok-install-id.ts +2 -3
- package/src/providers/integrations/grok/grok-quota.ts +279 -177
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
- package/src/providers/integrations/grok/grok.ts +6 -7
- package/src/providers/integrations/hermes.ts +14 -18
- package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
- package/src/providers/integrations/kiro/kiro.ts +1 -1
- package/src/providers/integrations/mistral.ts +6 -6
- package/src/providers/integrations/opencode/fingerprint.ts +3 -3
- package/src/providers/integrations/opencode/provider.ts +19 -20
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
- package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
- package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
- package/src/providers/integrations/qoder/qoder.ts +884 -865
- package/src/providers/integrations/shared/connect.ts +6 -6
- package/src/providers/integrations/trae/trae-catalog.ts +1 -2
- package/src/providers/integrations/trae/trae-quota.ts +384 -0
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/integrations/zcode/oauth.ts +6 -1
- package/src/providers/operations/account-health-service.ts +75 -27
- package/src/providers/operations/account-reset-service.ts +1 -1
- package/src/providers/operations/client-versions.ts +6 -6
- package/src/providers/operations/custom-cli-headers.ts +3 -4
- package/src/providers/operations/provider-catalog-seeder.ts +95 -95
- package/src/providers/operations/provider-catalog-service.ts +3 -4
- package/src/providers/provider-contracts.ts +7 -0
- package/src/providers/provider-metadata.ts +10 -8
- package/src/providers/provider-registry.ts +6 -7
- package/src/providers/provider-usage.ts +1 -1
- package/src/providers/quota/quota-support.ts +8 -0
- package/src/security/abuse.ts +15 -10
- package/src/security/access-control.ts +144 -146
- package/src/security/admission/contracts.ts +11 -0
- package/src/security/admission/in-memory-store.ts +4 -3
- package/src/security/admission/lease-sweep.ts +6 -6
- package/src/security/admission/reasons.ts +5 -1
- package/src/security/admission/redis-store.ts +211 -210
- package/src/security/admission/service.ts +4 -0
- package/src/security/api-key-auth.ts +27 -17
- package/src/security/ip-boundary.ts +155 -15
- package/src/security/model-abuse.ts +366 -94
- package/src/security/outbound-headers.ts +3 -4
- package/src/shared/sleep.ts +4 -0
- package/src/transport/dispatch/attempt-finalize.ts +47 -5
- package/src/transport/dispatch/attempt-loop.ts +531 -530
- package/src/transport/dispatch/generation.ts +6 -0
- package/src/transport/dispatch/proxy-request.ts +24 -1
- package/src/transport/dispatch/ratelimit-capture.ts +37 -0
- package/src/transport/dispatch/stream-bridge.ts +1 -3
- package/src/transport/dispatch/streaming/finalize.ts +1 -1
- package/src/transport/dispatch/streaming/stream.ts +19 -6
- package/src/transport/dispatch/upstream.ts +9 -0
- package/src/transport/failure-policy.ts +162 -8
- package/src/transport/guards/auth-guard.ts +7 -4
- package/src/transport/guards/concurrency-guard.ts +0 -5
- package/src/transport/guards/error-lifecycle.ts +13 -6
- package/src/transport/guards/pipeline.ts +15 -3
- package/src/transport/guards/request-context.ts +8 -2
- package/src/transport/request/inflight.ts +5 -6
- package/src/transport/request/preparer.ts +9 -8
- package/src/transport/request/state.ts +21 -8
- package/src/transport/request/tenant-preferences.ts +60 -0
- package/src/transport/routing/admission.ts +4 -0
- package/src/transport/routing/route-catalog.ts +65 -10
- package/src/transport/routing/route-model.ts +46 -14
- package/src/transport/routing/router.ts +21 -14
- package/src/transport/surface/completion.ts +2 -3
- package/src/transport/surface/dialects.ts +5 -6
- package/src/transport/surface/responses/encode.ts +38 -0
- package/src/transport/surface/responses/parse.ts +4 -0
- package/src/transport/translation/capabilities.ts +2 -3
- package/src/transport/translation/quirks.ts +3 -3
- package/src/workers/buddy-growth-tasks.ts +5 -10
- package/src/workers/daily-checkin.ts +17 -23
- package/src/workers/quota-refresh-sweep.ts +26 -26
- package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
- package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
- package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
- package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
- package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
- package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
- package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
- package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
- package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
- package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
- package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
- package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
- package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
- package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
- package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
- package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
- package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
- package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
- package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
- package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
- package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
- package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
- package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
- package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
- package/scripts/cli/command/admin.ts +0 -65
- package/scripts/cli/command/doctor.ts +0 -257
- package/scripts/cli/command/update.ts +0 -23
- package/scripts/cli/gateway/drain-token.ts +0 -78
- package/scripts/cli/gateway/instance-lock.ts +0 -105
- package/scripts/cli/gateway/stop.ts +0 -51
- package/scripts/cli/support/bun.ts +0 -27
- package/scripts/cli/support/version.ts +0 -37
- /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
- /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
- /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
package/src/boot/app.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { GatewayError, publicGatewayErrorBody } from "../protocol/gateway-error"
|
|
|
15
15
|
import { shutdownError, shutdownNotice } from "../transport/shutdown-notice";
|
|
16
16
|
import type { CanonicalAdapter } from "../transport/contracts";
|
|
17
17
|
import type { ApiKeyAuthorizationSnapshot } from "../security/api-key-auth";
|
|
18
|
-
import {
|
|
18
|
+
import { modelsListApiKeyError } from "../security/api-key-auth";
|
|
19
19
|
import { getDbHandle } from "../persistence/boot";
|
|
20
20
|
import { isPgHandle } from "../persistence/db-pg";
|
|
21
21
|
import { resolveRedisClient } from "../persistence/redis";
|
|
@@ -80,7 +80,7 @@ import type { ShutdownDrainSource } from "../transport/contracts";
|
|
|
80
80
|
* discriminant narrows `deps` to `ProductionAppDeps`. Listing them here would
|
|
81
81
|
* let a caller pass a value the shell silently ignores.
|
|
82
82
|
*/
|
|
83
|
-
|
|
83
|
+
interface GatewayShellDeps {
|
|
84
84
|
readonly mode: "route-only";
|
|
85
85
|
readonly dashboardDist?: string;
|
|
86
86
|
readonly checkDb?: () => Promise<void>;
|
|
@@ -159,7 +159,7 @@ export interface ProductionAppDeps {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
/** Either app mode. The discriminant decides which builder path runs. */
|
|
162
|
-
|
|
162
|
+
type GatewayAppDeps = GatewayShellDeps | ProductionAppDeps;
|
|
163
163
|
|
|
164
164
|
function checkDatabaseConnection(): Promise<void> {
|
|
165
165
|
return getDbHandle()
|
|
@@ -409,7 +409,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
409
409
|
const handleModelsList = async ({ request }: { request: Request }): Promise<Response> => {
|
|
410
410
|
const state = requestStateStore.require(request);
|
|
411
411
|
const auth = state.authorization;
|
|
412
|
-
if (!auth) throw
|
|
412
|
+
if (!auth) throw modelsListApiKeyError();
|
|
413
413
|
const data = await queryAllowedModels(auth);
|
|
414
414
|
return new Response(JSON.stringify({ object: "list", data }), {
|
|
415
415
|
headers: {
|
|
@@ -432,7 +432,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
432
432
|
}): Promise<Response> => {
|
|
433
433
|
const state = requestStateStore.require(request);
|
|
434
434
|
const auth = state.authorization;
|
|
435
|
-
if (!auth) throw
|
|
435
|
+
if (!auth) throw modelsListApiKeyError();
|
|
436
436
|
const raw = params?.["*"] ?? params?.id ?? "";
|
|
437
437
|
const targetId = new URL(request.url).searchParams.get("id") ?? raw;
|
|
438
438
|
const found = await publicModelStore.getPublicModelDetail(
|
|
@@ -614,10 +614,14 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
614
614
|
const tasks = deps.scheduledTasks;
|
|
615
615
|
const token = deps.drainToken;
|
|
616
616
|
app.post("/admin/tasks/:name/run", async ({ request, params }) => {
|
|
617
|
+
// `policy_rejected`, not `invalid_request`: the request may be perfectly
|
|
618
|
+
// well-formed. What refused it is where it came from — the client asked
|
|
619
|
+
// for something only the local host may ask. `invalid_request` told an
|
|
620
|
+
// operator to fix the body, when the body was never the problem.
|
|
617
621
|
if (!isLoopbackPeer(peerAddresses.get(request) ?? null))
|
|
618
|
-
throw new GatewayError("
|
|
622
|
+
throw new GatewayError("policy_rejected", 403, "task trigger is only available from loopback");
|
|
619
623
|
if (!tokenMatches(request.headers.get("x-drain-token"), token))
|
|
620
|
-
throw new GatewayError("
|
|
624
|
+
throw new GatewayError("authentication_failed", 401, "invalid drain token");
|
|
621
625
|
const name = params.name;
|
|
622
626
|
if (!tasks.names().includes(name))
|
|
623
627
|
throw new GatewayError("invalid_request", 404, `no such task: ${name}`);
|
package/src/boot/dependencies.ts
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
import { NetworkPoolSelector } from "../network/pool/selector";
|
|
53
53
|
import { ScheduledTaskRegistry } from "../workers/scheduler";
|
|
54
54
|
import { quotaRefreshSweep } from "../workers/quota-refresh-sweep";
|
|
55
|
+
import { markProviderApiKeyCredential } from "../console/quota/account-quota-shared";
|
|
55
56
|
import { dailyCheckinEgressForPass } from "../workers/daily-checkin";
|
|
56
57
|
import {
|
|
57
58
|
buddyAdoptTask,
|
|
@@ -70,11 +71,14 @@ import { preferencesReaderFor } from "../persistence/tenant-preferences";
|
|
|
70
71
|
import { sweepExpiredCooldowns } from "../providers/operations/account-health-service";
|
|
71
72
|
import { DrizzleTelemetryStore } from "../persistence/stores/telemetry-store";
|
|
72
73
|
import { TelemetryPayloadCapture } from "../observability/payload-capture";
|
|
74
|
+
import { foldDayIndex, listEventDays } from "../observability/event-day-index";
|
|
75
|
+
import { resolveTelemetryCompactIntervalMs } from "../config/telemetry";
|
|
73
76
|
import { sweepExpiredPoolCooldowns } from "../network/pool-health-machine";
|
|
74
77
|
import { TelemetryBatchBuffer } from "../observability/telemetry-buffer";
|
|
75
78
|
import { RuntimeMetricsSampler } from "../observability/runtime-metrics";
|
|
76
79
|
import {
|
|
77
80
|
resolveIpRateLimit,
|
|
81
|
+
resolveModelBanMaxTtlMs,
|
|
78
82
|
resolveModelBanTtlMs,
|
|
79
83
|
resolveModelStrikeThreshold,
|
|
80
84
|
resolveModelStrikeWindowMs,
|
|
@@ -136,8 +140,8 @@ const OAUTH_REFRESH_INTER_ITEM_DELAY_MS = 1_500;
|
|
|
136
140
|
* Client identities are refreshed out of band so provider dispatch never waits
|
|
137
141
|
* on npm/provider version endpoints. Resolver TTLs deduplicate the sources.
|
|
138
142
|
*/
|
|
139
|
-
// Matched to the resolver's own 30 min cache TTL:
|
|
140
|
-
//
|
|
143
|
+
// Matched to the resolver's own 30 min cache TTL: a shorter interval makes
|
|
144
|
+
// every second pass a pure cache read that cannot discover anything new.
|
|
141
145
|
const CLIENT_VERSION_MONITOR_INTERVAL_MS = 30 * 60_000;
|
|
142
146
|
const CLIENT_VERSION_INITIAL_DELAY_MS = 10_000;
|
|
143
147
|
|
|
@@ -219,7 +223,7 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
219
223
|
// spent, worst exactly when the key is busiest.
|
|
220
224
|
setPendingUsageSource((apiKeyId) => usageFlush.pendingFor(apiKeyId));
|
|
221
225
|
// Health audit rows are append-only and read by nobody in dispatch, so they
|
|
222
|
-
//
|
|
226
|
+
// are buffered out of the request. The status write they accompany stays
|
|
223
227
|
// synchronous — routing reads it.
|
|
224
228
|
const healthEventFlush = new HealthEventFlushBuffer(db);
|
|
225
229
|
setHealthEventBuffer(healthEventFlush);
|
|
@@ -272,6 +276,7 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
272
276
|
threshold: resolveModelStrikeThreshold(),
|
|
273
277
|
windowMs: resolveModelStrikeWindowMs(),
|
|
274
278
|
banTtlMs: resolveModelBanTtlMs(),
|
|
279
|
+
maxBanTtlMs: resolveModelBanMaxTtlMs(),
|
|
275
280
|
});
|
|
276
281
|
const readiness = () => checkReadiness(db, redis, resolveRedisBackend(redis));
|
|
277
282
|
// One list covers every periodic task: a small deployment can cut the
|
|
@@ -303,6 +308,25 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
303
308
|
intervalMs: 15 * 60_000,
|
|
304
309
|
run: () => payloadCapture.cleanupExpired(),
|
|
305
310
|
});
|
|
311
|
+
scheduledTasks.register({
|
|
312
|
+
name: "telemetry-index-fold",
|
|
313
|
+
intervalMs: resolveTelemetryCompactIntervalMs(),
|
|
314
|
+
// Brings each day's index up to date with the bytes appended since the last
|
|
315
|
+
// pass, so a usage read is a small JSON read rather than a parse of the
|
|
316
|
+
// whole period. Folding is incremental and idempotent: a crash only leaves
|
|
317
|
+
// the watermark behind, and the next pass re-reads the tail.
|
|
318
|
+
run: async () => {
|
|
319
|
+
const days = await listEventDays();
|
|
320
|
+
let folded = 0;
|
|
321
|
+
for (const day of days) {
|
|
322
|
+
const result = await foldDayIndex(day).catch(() => ({ folded: 0 }));
|
|
323
|
+
folded += result.folded;
|
|
324
|
+
}
|
|
325
|
+
if (folded > 0) {
|
|
326
|
+
log.info(`[telemetry] indexed ${folded} event(s) across ${days.length} day(s)`);
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
});
|
|
306
330
|
scheduledTasks.register({
|
|
307
331
|
name: "console-session-sweep",
|
|
308
332
|
// Sessions live 48 h, so an hourly sweep deleted nothing 47 times out of
|
|
@@ -352,17 +376,17 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
352
376
|
});
|
|
353
377
|
scheduledTasks.register({
|
|
354
378
|
name: "pool-agent-sweep",
|
|
355
|
-
// `resolveAgent` already self-sweeps every 30 s, so a 1 min timer
|
|
356
|
-
// second pass that almost always
|
|
379
|
+
// `resolveAgent` already self-sweeps every 30 s, so a 1 min timer adds a
|
|
380
|
+
// second pass that almost always finds nothing to release. 5 min is well
|
|
357
381
|
// inside the idle timeout this exists to enforce, and outside the path
|
|
358
382
|
// that already runs.
|
|
359
383
|
intervalMs: 5 * 60_000,
|
|
360
384
|
run: () => poolAgentResolver.sweepIdleAgents(),
|
|
361
385
|
});
|
|
362
386
|
const oauthRefreshService = new OAuthRefreshService(db);
|
|
363
|
-
// Converge accounts
|
|
364
|
-
//
|
|
365
|
-
//
|
|
387
|
+
// Converge accounts parked on a dead refresh grant: an account whose stored
|
|
388
|
+
// access token is still usable becomes a static token (and returns to
|
|
389
|
+
// rotation) instead of staying disabled for re-auth.
|
|
366
390
|
await measureStartupPhase("static-token-reconciliation", () =>
|
|
367
391
|
reconcileStaticTokenAccounts(db),
|
|
368
392
|
);
|
|
@@ -373,10 +397,10 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
373
397
|
scheduledTasks.register({
|
|
374
398
|
name: "account-enrichment-sweep",
|
|
375
399
|
// Converges via `identity_checked_at`: once every stored credential has
|
|
376
|
-
// been parsed, the listing query returns nothing —
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
400
|
+
// been parsed, the listing query returns nothing — a 30 s timer would run
|
|
401
|
+
// that empty query forever, two million times a year. 10 min is still
|
|
402
|
+
// instant for the case that matters (a batch import, where the label and
|
|
403
|
+
// email show up on the next tick) and costs nothing when idle.
|
|
380
404
|
intervalMs: 10 * 60_000,
|
|
381
405
|
run: () => accountIdentitySweep({ db }),
|
|
382
406
|
});
|
|
@@ -432,11 +456,10 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
432
456
|
// the same pass (one attempt per account per day, guarded by a day
|
|
433
457
|
// marker), so no second timer duplicates the credential resolution.
|
|
434
458
|
//
|
|
435
|
-
// Registered in **every** mode, not only when Redis is present
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
// already accepts `RedisClient | undefined` and falls back to the
|
|
459
|
+
// Registered in **every** mode, not only when Redis is present: gating it on
|
|
460
|
+
// Redis would cost Lite proactive quota warming and the check-in ride-along
|
|
461
|
+
// above that claims the once-per-day WorkBuddy/CodeBuddy credit grant. The
|
|
462
|
+
// whole sweep chain accepts `RedisClient | undefined` and falls back to the
|
|
440
463
|
// in-process TTL cache and an in-process day marker, so Redis is an
|
|
441
464
|
// optimization here rather than a precondition — which is exactly what
|
|
442
465
|
// Lite is for.
|
|
@@ -453,6 +476,14 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
453
476
|
providerRegistry: registry,
|
|
454
477
|
resolveCredential: quotaResolveCredential,
|
|
455
478
|
snapshotInvalidator: snapshotService,
|
|
479
|
+
// Without this the sweep hands each collector the raw stored secret.
|
|
480
|
+
// For `cline` that means an `sk_...` key reaching the OAuth branch,
|
|
481
|
+
// which prepends `workos:` and is refused with "please make sure
|
|
482
|
+
// you're using the latest version of Cline and re-authenticate" — so
|
|
483
|
+
// every sweep cools a working account with `quota_fetch_failed` while
|
|
484
|
+
// the same account refreshes fine from the console, which binds the
|
|
485
|
+
// same marker through `createQuotaRefreshDeps`.
|
|
486
|
+
markApiKeyCredential: markProviderApiKeyCredential,
|
|
456
487
|
// Below the 60 s interval: a budget longer than the tick guarantees
|
|
457
488
|
// skip/burst under load instead of steady progress.
|
|
458
489
|
passBudgetMs: 45_000,
|
|
@@ -474,8 +505,8 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
|
|
|
474
505
|
// dispatch failure.
|
|
475
506
|
//
|
|
476
507
|
// Daily, not hourly: each claim is once per account per day and is
|
|
477
|
-
// ledger-guarded, so at an hourly cadence 23 of every 24 passes
|
|
478
|
-
// scan of active accounts plus a ledger read per account that
|
|
508
|
+
// ledger-guarded, so at an hourly cadence 23 of every 24 passes are a full
|
|
509
|
+
// scan of active accounts plus a ledger read per account that resolves to
|
|
479
510
|
// nothing. Once a day is the shortest interval where a pass can still find
|
|
480
511
|
// work, which is the only reason to run at all.
|
|
481
512
|
const buddyTaskDeps = {
|
package/src/boot/timeout.ts
CHANGED
|
@@ -24,19 +24,8 @@ export function withTimeout<T>(
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*/
|
|
30
|
-
export function timeoutAfter(ms: number, message: string): Promise<never> {
|
|
31
|
-
return new Promise<never>((_, reject) => {
|
|
32
|
-
const timer = setTimeout(() => reject(new Error(message)), ms);
|
|
33
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Cancellable variant of {@link timeoutAfter}: the loser timer is cleared
|
|
39
|
-
* when the raced operation wins, instead of firing later against nothing.
|
|
27
|
+
* The loser timer is cleared when the raced operation wins, instead of firing
|
|
28
|
+
* later against nothing.
|
|
40
29
|
*/
|
|
41
30
|
export function cancellableTimeout(ms: number, message: string): { promise: Promise<never>; cancel: () => void } {
|
|
42
31
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
package/src/config/cooldowns.ts
CHANGED
|
@@ -1,138 +1,186 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Account/pool health cooldown fallback resolvers.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { CONFIG_SPEC, readInt } from "./spec";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
CONFIG_SPEC.
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
*/
|
|
86
|
-
export function
|
|
87
|
-
|
|
88
|
-
"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* `
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Account/pool health cooldown fallback resolvers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CONFIG_SPEC, readInt } from "./spec";
|
|
6
|
+
|
|
7
|
+
let _rateLimitCooldown: number | undefined;
|
|
8
|
+
/** Fallback cooldown for a provider rate limit (default 15m). */
|
|
9
|
+
export function resolveAccountRateLimitCooldownMs(): number {
|
|
10
|
+
if (_rateLimitCooldown === undefined)
|
|
11
|
+
_rateLimitCooldown = readInt(
|
|
12
|
+
"CARTETHYIA_ACCOUNT_RATE_LIMIT_COOLDOWN_MS",
|
|
13
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_RATE_LIMIT_COOLDOWN_MS,
|
|
14
|
+
);
|
|
15
|
+
return _rateLimitCooldown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let _quotaCooldown: number | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Fallback cooldown for provider quota exhaustion when the error states no
|
|
21
|
+
* reset window (default 1h). A provider-specific rule (xAI Grok Build's
|
|
22
|
+
* rolling 24-hour free-tier window) overrides it inside the classifier.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveAccountQuotaCooldownMs(): number {
|
|
25
|
+
if (_quotaCooldown === undefined)
|
|
26
|
+
_quotaCooldown = readInt(
|
|
27
|
+
"CARTETHYIA_ACCOUNT_QUOTA_COOLDOWN_MS",
|
|
28
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_QUOTA_COOLDOWN_MS,
|
|
29
|
+
);
|
|
30
|
+
return _quotaCooldown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let _modelCapacityCooldown: number | undefined;
|
|
34
|
+
/** Fallback cooldown for model-capacity overload (default 2m). */
|
|
35
|
+
export function resolveAccountModelCapacityCooldownMs(): number {
|
|
36
|
+
if (_modelCapacityCooldown === undefined)
|
|
37
|
+
_modelCapacityCooldown = readInt(
|
|
38
|
+
"CARTETHYIA_ACCOUNT_MODEL_CAPACITY_COOLDOWN_MS",
|
|
39
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_MODEL_CAPACITY_COOLDOWN_MS,
|
|
40
|
+
);
|
|
41
|
+
return _modelCapacityCooldown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let _transientCooldown: number | undefined;
|
|
45
|
+
/** Backoff for a transient 5xx/network fault, recorded as `cooldown` (default 30s). */
|
|
46
|
+
export function resolveAccountTransientCooldownMs(): number {
|
|
47
|
+
if (_transientCooldown === undefined)
|
|
48
|
+
_transientCooldown = readInt(
|
|
49
|
+
"CARTETHYIA_ACCOUNT_TRANSIENT_COOLDOWN_MS",
|
|
50
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_TRANSIENT_COOLDOWN_MS,
|
|
51
|
+
);
|
|
52
|
+
return _transientCooldown;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let _unclassifiedCooldown: number | undefined;
|
|
56
|
+
/** Backoff for a failure no rule matched, recorded as `cooldown` (default 1m). */
|
|
57
|
+
export function resolveAccountUnclassifiedCooldownMs(): number {
|
|
58
|
+
if (_unclassifiedCooldown === undefined)
|
|
59
|
+
_unclassifiedCooldown = readInt(
|
|
60
|
+
"CARTETHYIA_ACCOUNT_UNCLASSIFIED_COOLDOWN_MS",
|
|
61
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_UNCLASSIFIED_COOLDOWN_MS,
|
|
62
|
+
);
|
|
63
|
+
return _unclassifiedCooldown;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Ceiling an escalated fallback cooldown may reach (default 1h).
|
|
68
|
+
*
|
|
69
|
+
* A fallback duration is one the provider never stated, so escalating it is our
|
|
70
|
+
* own estimate. The cap keeps that estimate from outliving a short transient
|
|
71
|
+
* window: an account that recovers on its own must not sit parked for a day
|
|
72
|
+
* because a burst of 5xx happened to arrive while it was already cooling.
|
|
73
|
+
*/
|
|
74
|
+
let _escalationMaxCooldown: number | undefined;
|
|
75
|
+
export function resolveAccountCooldownEscalationMaxMs(): number {
|
|
76
|
+
if (_escalationMaxCooldown === undefined)
|
|
77
|
+
_escalationMaxCooldown = readInt(
|
|
78
|
+
"CARTETHYIA_ACCOUNT_COOLDOWN_ESCALATION_MAX_MS",
|
|
79
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_COOLDOWN_ESCALATION_MAX_MS,
|
|
80
|
+
);
|
|
81
|
+
return _escalationMaxCooldown;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let _poolCooldown: number | undefined;
|
|
85
|
+
/** How long a proxy pool stays `cooldown` after a transport fault (default 2m). */
|
|
86
|
+
export function resolvePoolCooldownMs(): number {
|
|
87
|
+
if (_poolCooldown === undefined)
|
|
88
|
+
_poolCooldown = readInt("CARTETHYIA_POOL_COOLDOWN_MS", CONFIG_SPEC.CARTETHYIA_POOL_COOLDOWN_MS);
|
|
89
|
+
return _poolCooldown;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let _authRejectedCooldown: number | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Backoff for an auth rejection that names no revocation (default 5m).
|
|
95
|
+
*
|
|
96
|
+
* A bare `401`/`403` is weak evidence: it covers clock skew, a key rotated
|
|
97
|
+
* upstream a moment ago, an edge cache, and a load balancer that answers before
|
|
98
|
+
* the credential is loaded. None of those is the credential's death, and
|
|
99
|
+
* `disabled` is the one state no sweep recovers — the account would leave
|
|
100
|
+
* rotation until an operator noticed. A cooldown costs the same
|
|
101
|
+
* requests for five minutes and comes back on its own.
|
|
102
|
+
*
|
|
103
|
+
* Only a *named* revocation (`token revoked`, `invalidated oauth token`) still
|
|
104
|
+
* parks the account: the upstream stated the credential itself is dead, there is
|
|
105
|
+
* no expiry to wait out and no refresh to run, so a cooldown would only return
|
|
106
|
+
* it to rotation to fail again. Set this to `0` to return the account to
|
|
107
|
+
* rotation immediately, with no backoff to wait out.
|
|
108
|
+
*/
|
|
109
|
+
export function resolveAccountAuthRejectedCooldownMs(): number {
|
|
110
|
+
if (_authRejectedCooldown === undefined)
|
|
111
|
+
_authRejectedCooldown = readInt(
|
|
112
|
+
"CARTETHYIA_ACCOUNT_AUTH_REJECTED_COOLDOWN_MS",
|
|
113
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_AUTH_REJECTED_COOLDOWN_MS,
|
|
114
|
+
);
|
|
115
|
+
return _authRejectedCooldown;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let _policyBlockCooldown: number | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Base backoff for the buddy policy/channel block (default 5m).
|
|
121
|
+
*
|
|
122
|
+
* Deliberately a *base*, not the 24h flat penalty it replaced. `11140` has two
|
|
123
|
+
* documented causes our classifier cannot separate from the response alone: the
|
|
124
|
+
* user's prompt failed a safety review, **and** sending the wrong desktop
|
|
125
|
+
* `User-Agent` brand (`workbuddy/shared.ts`). A flat 24h meant that after we
|
|
126
|
+
* fixed a header regression every buddy account stayed dead for up to a further
|
|
127
|
+
* day, and one unsafe prompt cost a whole account a day.
|
|
128
|
+
*
|
|
129
|
+
* As a base it escalates through the consecutive-failure ladder instead, which
|
|
130
|
+
* distinguishes the two causes by what they do next: a genuinely blocked account
|
|
131
|
+
* is refused again on its very next request and parks itself within minutes,
|
|
132
|
+
* while a single bad prompt costs five minutes.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveAccountPolicyBlockCooldownMs(): number {
|
|
135
|
+
if (_policyBlockCooldown === undefined)
|
|
136
|
+
_policyBlockCooldown = readInt(
|
|
137
|
+
"CARTETHYIA_ACCOUNT_POLICY_BLOCK_COOLDOWN_MS",
|
|
138
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_POLICY_BLOCK_COOLDOWN_MS,
|
|
139
|
+
);
|
|
140
|
+
return _policyBlockCooldown;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let _disabledRecovery: number | undefined;
|
|
144
|
+
/**
|
|
145
|
+
* How long an **automatically** disabled account stays out of rotation (default 24h).
|
|
146
|
+
*
|
|
147
|
+
* A credential rejected once — a revoked key, a definitively dead refresh grant —
|
|
148
|
+
* would otherwise leave rotation until an operator noticed and clicked Recover by
|
|
149
|
+
* hand. That is the wrong default for a credential that may come back on its own:
|
|
150
|
+
* keys get re-issued, grants get re-authorized, and an upstream's notion of
|
|
151
|
+
* "invalid" can outrun ours.
|
|
152
|
+
*
|
|
153
|
+
* The deadline is what makes this safe to automate rather than a policy guess. A row
|
|
154
|
+
* this gateway disables **by itself** carries the deadline; a row an **operator**
|
|
155
|
+
* disables keeps `cooldownUntil` null, and the recovery sweep selects only rows whose
|
|
156
|
+
* deadline has elapsed. So an operator's decision still outranks the machine, with no
|
|
157
|
+
* extra column and no way to confuse the two.
|
|
158
|
+
*
|
|
159
|
+
* Long on purpose: the states that reach here are the ones where retrying sooner
|
|
160
|
+
* only reproduces the refusal. Set to `0` to make an automatic disable terminal.
|
|
161
|
+
*/
|
|
162
|
+
export function resolveAccountDisabledRecoveryMs(): number {
|
|
163
|
+
if (_disabledRecovery === undefined)
|
|
164
|
+
_disabledRecovery = readInt(
|
|
165
|
+
"CARTETHYIA_ACCOUNT_DISABLED_RECOVERY_MS",
|
|
166
|
+
CONFIG_SPEC.CARTETHYIA_ACCOUNT_DISABLED_RECOVERY_MS,
|
|
167
|
+
);
|
|
168
|
+
return _disabledRecovery;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Resets all cached cooldown values. Call this in tests that override
|
|
173
|
+
* cooldown env-vars between test cases.
|
|
174
|
+
*/
|
|
175
|
+
export function resetCooldownCacheForTests(): void {
|
|
176
|
+
_rateLimitCooldown = undefined;
|
|
177
|
+
_quotaCooldown = undefined;
|
|
178
|
+
_modelCapacityCooldown = undefined;
|
|
179
|
+
_transientCooldown = undefined;
|
|
180
|
+
_unclassifiedCooldown = undefined;
|
|
181
|
+
_escalationMaxCooldown = undefined;
|
|
182
|
+
_poolCooldown = undefined;
|
|
183
|
+
_authRejectedCooldown = undefined;
|
|
184
|
+
_policyBlockCooldown = undefined;
|
|
185
|
+
_disabledRecovery = undefined;
|
|
186
|
+
}
|
package/src/config/network.ts
CHANGED
|
@@ -83,20 +83,15 @@ export function resolveProxyMaxFreeSockets(): number {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Proxy pool egress is dialed in-process by `network/pool/agent.ts` (http,
|
|
86
|
-
// https, socks5).
|
|
87
|
-
// files, a binary path, a local SOCKS port range, and a uid/gid drop — was
|
|
88
|
-
// removed with that feature; do not reintroduce its resolvers without the
|
|
89
|
-
// daemon that consumed them.
|
|
86
|
+
// https, socks5). Add a resolver here only alongside the consumer it feeds.
|
|
90
87
|
|
|
91
88
|
// ─── Process-wide resource bounds ───────────────────────────────────────────
|
|
92
89
|
|
|
93
90
|
/**
|
|
94
91
|
* Live process-wide resource bounds. Only bounds with a production consumer
|
|
95
92
|
* live here: `maxPostgresPoolSize` feeds `poolMaxFromEnv()`, and
|
|
96
|
-
* `maxTelemetryQueueBytes` caps `TelemetryBatchBuffer`.
|
|
97
|
-
*
|
|
98
|
-
* catalog/replay bounds, `BoundedConfig` + its validators) had zero callers
|
|
99
|
-
* and was removed — re-add a bound only with its consumer in the same change.
|
|
93
|
+
* `maxTelemetryQueueBytes` caps `TelemetryBatchBuffer`. Re-add a bound only
|
|
94
|
+
* with its consumer in the same change.
|
|
100
95
|
*/
|
|
101
96
|
export const DEFAULT_BOUNDS = {
|
|
102
97
|
maxTelemetryQueueBytes: 10 * 1024 * 1024,
|
|
@@ -105,12 +100,11 @@ export const DEFAULT_BOUNDS = {
|
|
|
105
100
|
*
|
|
106
101
|
* This is a *ceiling*, not a reservation: the pool opens connections on
|
|
107
102
|
* demand and returns them to idle, so a value the workload never reaches
|
|
108
|
-
* costs nothing today — it only bounds the worst case. The
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* credential with a per-attempt read, health transitions write, and tenant
|
|
103
|
+
* costs nothing today — it only bounds the worst case. The proxy path is
|
|
104
|
+
* *mostly* database-free — routing comes from `InMemoryRouteSnapshotService`
|
|
105
|
+
* and admission counters live in Redis — but not entirely: a dispatch attempt
|
|
106
|
+
* resolves its credential with a per-attempt read, health transitions write,
|
|
107
|
+
* and tenant
|
|
114
108
|
* preferences are read behind a short cache. Those are small, bounded reads
|
|
115
109
|
* on the request's own attempt, not a per-request fan-out. The larger
|
|
116
110
|
* database consumers remain the console API, one telemetry flush at a time,
|