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/config/providers.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Provider-specific knobs
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Provider-specific knobs: Kimi OAuth/quota hosts, the GitHub Enterprise domain
|
|
3
|
+
* for the Copilot device login, and the OAuth callback URL builders built on the
|
|
4
|
+
* public origin.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { CONFIG_SPEC, readInt,
|
|
7
|
+
import { CONFIG_SPEC, readInt, readRequired, readText } from "./spec";
|
|
8
8
|
|
|
9
9
|
/** Reads and normalizes the public OAuth origin. */
|
|
10
10
|
export function requirePublicOrigin(): string {
|
|
@@ -64,14 +64,11 @@ export function resolveOpenCodeUnavailableFree():
|
|
|
64
64
|
const raw = process.env.CARTETHYIA_OPENCODE_UNAVAILABLE_FREE?.trim();
|
|
65
65
|
if (raw === undefined || raw === "") return undefined;
|
|
66
66
|
if (raw.toLowerCase() === "none") return "none";
|
|
67
|
-
|
|
68
|
-
"CARTETHYIA_OPENCODE_UNAVAILABLE_FREE",
|
|
69
|
-
CONFIG_SPEC.CARTETHYIA_OPENCODE_UNAVAILABLE_FREE,
|
|
70
|
-
);
|
|
71
|
-
// Separators only: readList drops the empties and yields nothing. Treating
|
|
67
|
+
// Separators only: dropping empties and yielding nothing. Treating
|
|
72
68
|
// that as "trust everything" would un-block every refused model on a typo,
|
|
73
69
|
// so it falls back to the bundled list instead.
|
|
74
|
-
|
|
70
|
+
const entries = raw.split(",").map((v) => v.trim()).filter(Boolean);
|
|
71
|
+
return entries.length === 0 ? undefined : entries;
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
/**
|
package/src/config/runtime.ts
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP listener, dashboard runtime, and graceful-shutdown drain resolvers.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { CONFIG_SPEC, readInt, readOptionalText
|
|
6
|
-
import { resolvePackagePath } from "./package-paths";
|
|
7
|
-
|
|
8
|
-
/** Resolves the HTTP listener port from the process environment. */
|
|
9
|
-
export function resolvePort(): number {
|
|
10
|
-
return readInt("PORT", CONFIG_SPEC.PORT);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Resolves the dashboard static asset directory. */
|
|
14
|
-
export function resolveDashboardDist(): string {
|
|
15
|
-
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Whether Elysia precompiles route handlers at startup (AOT).
|
|
55
|
-
*
|
|
56
|
-
* Precompilation takes the eager validator branch, which calls TypeBox's
|
|
57
|
-
* `SchemaCompile(schema)` and then reads `.buildResult.external` off the
|
|
58
|
-
* result. In a source-run process that field is absent — the lazy
|
|
59
|
-
* `require("typebox/schema")` resolves to a namespace that does not carry it —
|
|
60
|
-
* and the process dies at the first schema-bearing route with
|
|
61
|
-
* `undefined is not an object (evaluating 'this.tb.buildResult.external')`.
|
|
62
|
-
* `scripts/
|
|
63
|
-
* the namespaces statically, so only the built artifact can actually take the
|
|
64
|
-
* eager path.
|
|
65
|
-
*
|
|
66
|
-
* So: precompilation is on for production *builds*, and off for a source run
|
|
67
|
-
* even when `NODE_ENV=production`. A deployment that runs
|
|
68
|
-
* `bun run src/main.ts` must not be pushed onto the eager branch.
|
|
69
|
-
*
|
|
70
|
-
* `CARTETHYIA_SOURCE_RUN` marks a source run explicitly. It is set by
|
|
71
|
-
* `scripts/deploy/*.sh`, which are the only supported way to run from source.
|
|
72
|
-
*/
|
|
73
|
-
export function resolveElysiaPrecompile(): boolean {
|
|
74
|
-
if (process.env.CARTETHYIA_SOURCE_RUN === "1") return false;
|
|
75
|
-
return process.env.NODE_ENV === "production";
|
|
76
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* HTTP listener, dashboard runtime, and graceful-shutdown drain resolvers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CONFIG_SPEC, readInt, readOptionalText } from "./spec";
|
|
6
|
+
import { resolvePackagePath } from "./package-paths";
|
|
7
|
+
|
|
8
|
+
/** Resolves the HTTP listener port from the process environment. */
|
|
9
|
+
export function resolvePort(): number {
|
|
10
|
+
return readInt("PORT", CONFIG_SPEC.PORT);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Resolves the dashboard static asset directory. */
|
|
14
|
+
export function resolveDashboardDist(): string {
|
|
15
|
+
const raw = process.env.DASHBOARD_DIST?.trim();
|
|
16
|
+
if (!raw) return resolvePackagePath("dist", "dashboard");
|
|
17
|
+
return raw;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolves the configured default gateway API key (`CARTETHYIA_API_KEY`), if
|
|
22
|
+
* one is set. Consumers that need the raw secret — first-boot seeding and the
|
|
23
|
+
* console's key recovery from existing installations — read it here instead of
|
|
24
|
+
* probing `process.env` directly.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveDefaultGatewayApiKey(): string | undefined {
|
|
27
|
+
return readOptionalText("CARTETHYIA_API_KEY", CONFIG_SPEC.CARTETHYIA_API_KEY);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Resolves the request-body ceiling shared by the Bun listener and ingress middleware. */
|
|
31
|
+
export function resolveMaxBodyBytes(): number {
|
|
32
|
+
return readInt("CARTETHYIA_SERVER_MAX_BODY_BYTES", CONFIG_SPEC.CARTETHYIA_SERVER_MAX_BODY_BYTES);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Resolves the listener idle-socket timeout (seconds). */
|
|
36
|
+
export function resolveIdleTimeout(): number {
|
|
37
|
+
return readInt("CARTETHYIA_SERVER_IDLE_TIMEOUT", CONFIG_SPEC.CARTETHYIA_SERVER_IDLE_TIMEOUT);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Resolves the graceful-shutdown drain window: time for in-flight requests to finish before abort. */
|
|
41
|
+
export function resolveShutdownDrainWindowMs(): number {
|
|
42
|
+
return readInt(
|
|
43
|
+
"CARTETHYIA_SHUTDOWN_DRAIN_WINDOW_MS",
|
|
44
|
+
CONFIG_SPEC.CARTETHYIA_SHUTDOWN_DRAIN_WINDOW_MS,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Resolves the operator drain token, or `undefined` when the drain route is disabled. */
|
|
49
|
+
export function resolveDrainToken(): string | undefined {
|
|
50
|
+
return readOptionalText("CARTETHYIA_DRAIN_TOKEN", CONFIG_SPEC.CARTETHYIA_DRAIN_TOKEN);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether Elysia precompiles route handlers at startup (AOT).
|
|
55
|
+
*
|
|
56
|
+
* Precompilation takes the eager validator branch, which calls TypeBox's
|
|
57
|
+
* `SchemaCompile(schema)` and then reads `.buildResult.external` off the
|
|
58
|
+
* result. In a source-run process that field is absent — the lazy
|
|
59
|
+
* `require("typebox/schema")` resolves to a namespace that does not carry it —
|
|
60
|
+
* and the process dies at the first schema-bearing route with
|
|
61
|
+
* `undefined is not an object (evaluating 'this.tb.buildResult.external')`.
|
|
62
|
+
* `scripts/build/aot.ts` avoids this by rewriting the call site and bundling
|
|
63
|
+
* the namespaces statically, so only the built artifact can actually take the
|
|
64
|
+
* eager path.
|
|
65
|
+
*
|
|
66
|
+
* So: precompilation is on for production *builds*, and off for a source run
|
|
67
|
+
* even when `NODE_ENV=production`. A deployment that runs
|
|
68
|
+
* `bun run src/main.ts` must not be pushed onto the eager branch.
|
|
69
|
+
*
|
|
70
|
+
* `CARTETHYIA_SOURCE_RUN` marks a source run explicitly. It is set by
|
|
71
|
+
* `scripts/deploy/*.sh`, which are the only supported way to run from source.
|
|
72
|
+
*/
|
|
73
|
+
export function resolveElysiaPrecompile(): boolean {
|
|
74
|
+
if (process.env.CARTETHYIA_SOURCE_RUN === "1") return false;
|
|
75
|
+
return process.env.NODE_ENV === "production";
|
|
76
|
+
}
|
package/src/config/security.ts
CHANGED
|
@@ -37,6 +37,21 @@ export function resolveModelBanTtlMs(): number {
|
|
|
37
37
|
return readInt("CARTETHYIA_MODEL_BAN_TTL_MS", CONFIG_SPEC.CARTETHYIA_MODEL_BAN_TTL_MS);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The longest model-abuse ban the console will record, however long a request
|
|
42
|
+
* asks for.
|
|
43
|
+
*
|
|
44
|
+
* A separate knob from the ban TTL because the two are different decisions:
|
|
45
|
+
* the TTL is what a graduated ban gets, this is the ceiling on what an
|
|
46
|
+
* operator may ask for by hand. Raising one was never meant to raise the other.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveModelBanMaxTtlMs(): number {
|
|
49
|
+
return readInt(
|
|
50
|
+
"CARTETHYIA_MODEL_BAN_MAX_TTL_MS",
|
|
51
|
+
CONFIG_SPEC.CARTETHYIA_MODEL_BAN_MAX_TTL_MS,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
/** Reads the application encryption key without caching it. */
|
|
41
56
|
export function requireEncryptionKeyEnv(): string {
|
|
42
57
|
return readRequired("CARTETHYIA_ENCRYPTION_KEY", CONFIG_SPEC.CARTETHYIA_ENCRYPTION_KEY);
|
package/src/config/spec.ts
CHANGED
|
@@ -148,7 +148,40 @@ export const CONFIG_SPEC = {
|
|
|
148
148
|
*/
|
|
149
149
|
CARTETHYIA_DISABLED_TASKS: { kind: "list" },
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* How long a day's telemetry is kept, in days.
|
|
153
|
+
*
|
|
154
|
+
* The retention unit is the whole day: a day's event file and its index are
|
|
155
|
+
* deleted together, and the current day is never touched. Past this age an
|
|
156
|
+
* individual request no longer exists anywhere, so its detail is gone —
|
|
157
|
+
* totals, breakdowns and spend are unaffected, because the index keeps
|
|
158
|
+
* running sums rather than the rows.
|
|
159
|
+
*
|
|
160
|
+
* This is the knob that decides how long the request list, per-request
|
|
161
|
+
* filters, and the detail drawer stay complete.
|
|
162
|
+
*/
|
|
151
163
|
CARTETHYIA_TELEMETRY_RETENTION_DAYS: { kind: "int", default: 30, min: 3, max: 365 },
|
|
164
|
+
/**
|
|
165
|
+
* Age at which a request's detail is described as no longer stored.
|
|
166
|
+
*
|
|
167
|
+
* Retention itself is `CARTETHYIA_TELEMETRY_RETENTION_DAYS`; this only words
|
|
168
|
+
* the operator-facing message shown when a request that old is asked for.
|
|
169
|
+
*/
|
|
170
|
+
CARTETHYIA_TELEMETRY_COMPACT_AFTER_DAYS: { kind: "int", default: 7, min: 1, max: 365 },
|
|
171
|
+
/**
|
|
172
|
+
* How often each day's index is brought up to date, in minutes.
|
|
173
|
+
*
|
|
174
|
+
* Bringing an index up to date means folding the bytes appended since the
|
|
175
|
+
* last pass — not re-reading the day. Reads fold before they answer too, so
|
|
176
|
+
* raising this does not make the numbers stale; it only leaves the on-disk
|
|
177
|
+
* index further behind, and the next read pays for the remainder.
|
|
178
|
+
*/
|
|
179
|
+
CARTETHYIA_TELEMETRY_COMPACT_INTERVAL_MINUTES: {
|
|
180
|
+
kind: "int",
|
|
181
|
+
default: 60,
|
|
182
|
+
min: 5,
|
|
183
|
+
max: 1440,
|
|
184
|
+
},
|
|
152
185
|
CARTETHYIA_TELEMETRY_PAYLOAD_MAX_BYTES: {
|
|
153
186
|
kind: "int",
|
|
154
187
|
default: 16 * 1024 * 1024,
|
|
@@ -191,10 +224,10 @@ export const CONFIG_SPEC = {
|
|
|
191
224
|
/**
|
|
192
225
|
* An unmapped upstream failure cools the account down for 15 minutes.
|
|
193
226
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
227
|
+
* A shorter window puts the account straight back into rotation and re-dials
|
|
228
|
+
* an upstream that has just answered something this gateway does not
|
|
229
|
+
* understand — so the same unknown error is retried every minute, and an
|
|
230
|
+
* account that is actually broken contributes a steady stream of failed
|
|
198
231
|
* attempts instead of one. Fifteen minutes is long enough for a transient
|
|
199
232
|
* provider condition to clear on its own and short enough that no operator
|
|
200
233
|
* action is needed to recover it. It is still a cooldown with a deadline,
|
|
@@ -224,6 +257,18 @@ export const CONFIG_SPEC = {
|
|
|
224
257
|
CARTETHYIA_MODEL_STRIKE_THRESHOLD: { kind: "int", default: 10, min: 1, max: 100 },
|
|
225
258
|
CARTETHYIA_MODEL_STRIKE_WINDOW_MS: { kind: "int", default: 1_800_000, min: 1_000, max: 86_400_000 },
|
|
226
259
|
CARTETHYIA_MODEL_BAN_TTL_MS: { kind: "int", default: 21_600_000, min: 1_000, max: 604_800_000 },
|
|
260
|
+
/**
|
|
261
|
+
* The longest ban the console will record, however long a request asks for.
|
|
262
|
+
* The "Permanent" choice is a very long TTL rather than an unbounded one,
|
|
263
|
+
* and this is what keeps a typo in a duration from parking a caller for a
|
|
264
|
+
* decade.
|
|
265
|
+
*/
|
|
266
|
+
CARTETHYIA_MODEL_BAN_MAX_TTL_MS: {
|
|
267
|
+
kind: "int",
|
|
268
|
+
default: 604_800_000,
|
|
269
|
+
min: 1_000,
|
|
270
|
+
max: 604_800_000,
|
|
271
|
+
},
|
|
227
272
|
CARTETHYIA_ENCRYPTION_KEY: {
|
|
228
273
|
kind: "required",
|
|
229
274
|
error:
|
|
@@ -240,9 +285,9 @@ export const CONFIG_SPEC = {
|
|
|
240
285
|
/**
|
|
241
286
|
* Loopback port the browser-OAuth callback listener binds.
|
|
242
287
|
*
|
|
243
|
-
* Configurable because the default is a fixed number
|
|
244
|
-
*
|
|
245
|
-
* it. Codex's authorization server still expects 59653, so changing this
|
|
288
|
+
* Configurable because the default is a fixed number: a busy port would strand
|
|
289
|
+
* the operator, since the login could not start and there would be no way to
|
|
290
|
+
* move it. Codex's authorization server still expects 59653, so changing this
|
|
246
291
|
* only helps the providers whose redirect URI is not allowlisted upstream.
|
|
247
292
|
*/
|
|
248
293
|
CARTETHYIA_OAUTH_CALLBACK_PORT: { kind: "int", default: 59_653, min: 1_024, max: 65_535 },
|
package/src/config/telemetry.ts
CHANGED
|
@@ -19,3 +19,29 @@ export function resolveTelemetryPayloadMaxBytes(): number {
|
|
|
19
19
|
CONFIG_SPEC.CARTETHYIA_TELEMETRY_PAYLOAD_MAX_BYTES,
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Age at which hourly telemetry files are compacted into a per-day summary.
|
|
25
|
+
*
|
|
26
|
+
* Clamped below the retention window: compacting at or past the delete age
|
|
27
|
+
* would fold a file and throw it away in the same pass, spending the read for
|
|
28
|
+
* totals that were about to be discarded.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveTelemetryCompactAfterDays(): number {
|
|
31
|
+
const compacted = readInt(
|
|
32
|
+
"CARTETHYIA_TELEMETRY_COMPACT_AFTER_DAYS",
|
|
33
|
+
CONFIG_SPEC.CARTETHYIA_TELEMETRY_COMPACT_AFTER_DAYS,
|
|
34
|
+
);
|
|
35
|
+
return Math.min(compacted, resolveTelemetryRetentionDays());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Minutes between compaction passes. */
|
|
39
|
+
export function resolveTelemetryCompactIntervalMs(): number {
|
|
40
|
+
const minutes = readInt(
|
|
41
|
+
"CARTETHYIA_TELEMETRY_COMPACT_INTERVAL_MINUTES",
|
|
42
|
+
CONFIG_SPEC.CARTETHYIA_TELEMETRY_COMPACT_INTERVAL_MINUTES,
|
|
43
|
+
);
|
|
44
|
+
return minutes * 60_000;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
package/src/config/timeouts.ts
CHANGED
|
@@ -51,12 +51,6 @@ let cachedUpstreamTimeoutMs: number | undefined;
|
|
|
51
51
|
let cachedStreamStallTimeoutMs: number | undefined;
|
|
52
52
|
let cachedInflightTtlSeconds: number | undefined;
|
|
53
53
|
|
|
54
|
-
/** Clears the memoized timeout cache; tests that mutate env mid-process only. */
|
|
55
|
-
export function resetTimeoutCacheForTests(): void {
|
|
56
|
-
cachedUpstreamTimeoutMs = undefined;
|
|
57
|
-
cachedStreamStallTimeoutMs = undefined;
|
|
58
|
-
cachedInflightTtlSeconds = undefined;
|
|
59
|
-
}
|
|
60
54
|
|
|
61
55
|
/**
|
|
62
56
|
* Crash-recovery TTL, in seconds, for a distributed inflight slot.
|
|
@@ -1,16 +1,38 @@
|
|
|
1
|
-
// Console surface for model-abuse bans: list active bans
|
|
1
|
+
// Console surface for model-abuse bans: list active bans, ban one, lift one.
|
|
2
2
|
//
|
|
3
|
-
// The strike layer (security/model-abuse.ts) bans a
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The strike layer (security/model-abuse.ts) bans a caller after repeated
|
|
4
|
+
// invalid-model requests. A ban lapses on its own after its TTL, and this is
|
|
5
|
+
// the escape hatch for lifting one early — a false positive (a shared NAT, a
|
|
6
|
+
// client that genuinely mistyped) must be fixable without SQL. It is a
|
|
7
7
|
// platform-admin surface: a ban is a security decision and the identity values
|
|
8
8
|
// are cross-tenant.
|
|
9
9
|
import type { AccessDecision } from "../../../security/access-control";
|
|
10
10
|
import type { ModelStrikeService } from "../../../security/model-abuse";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* The ban durations the console offers, in whole minutes. `null` is the
|
|
14
|
+
* "Permanent" choice.
|
|
15
|
+
*
|
|
16
|
+
* Expressed in minutes rather than milliseconds because that is what an
|
|
17
|
+
* operator picks and what the label says — a millisecond constant here drifts
|
|
18
|
+
* from the copy the moment either changes.
|
|
19
|
+
*/
|
|
20
|
+
export const BAN_DURATION_CHOICES = [
|
|
21
|
+
{ minutes: 60, label: "1 hour" },
|
|
22
|
+
{ minutes: 6 * 60, label: "6 hours" },
|
|
23
|
+
{ minutes: 12 * 60, label: "12 hours" },
|
|
24
|
+
{ minutes: 24 * 60, label: "1 day" },
|
|
25
|
+
{ minutes: 7 * 24 * 60, label: "7 days" },
|
|
26
|
+
{ minutes: null, label: "Permanent" },
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
/** The duration a bare "ban this user" click uses: the shortest one on offer. */
|
|
30
|
+
export const DEFAULT_BAN_MINUTES = 60;
|
|
31
|
+
|
|
32
|
+
/** Whole minutes the console accepts, or `null` for permanent. */
|
|
33
|
+
export type BanDurationMinutes = (typeof BAN_DURATION_CHOICES)[number]["minutes"];
|
|
34
|
+
|
|
12
35
|
export interface ModelAbuseRoutesConfig {
|
|
13
36
|
readonly strikes: Pick<ModelStrikeService, "listBans" | "unban" | "ban">;
|
|
14
37
|
readonly accessResolver: (request: Request) => AccessDecision | undefined;
|
|
15
38
|
}
|
|
16
|
-
|
|
@@ -1,16 +1,64 @@
|
|
|
1
1
|
import { Elysia, t } from "elysia";
|
|
2
|
-
import type
|
|
2
|
+
import { PERMANENT_BAN_TTL_MS, type ModelAbuseBan } from "../../../security/model-abuse";
|
|
3
3
|
import { ConsoleDomainError, errorResponse, requireGlobalAdmin } from "../../shared/errors";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
BAN_DURATION_CHOICES,
|
|
6
|
+
DEFAULT_BAN_MINUTES,
|
|
7
|
+
type ModelAbuseRoutesConfig,
|
|
8
|
+
} from "./contracts";
|
|
5
9
|
|
|
6
10
|
const unbanBody = t.Object({
|
|
11
|
+
/** The opaque member the listing reports, so a scoped ban lifts exactly it. */
|
|
7
12
|
identity: t.String({ minLength: 1 }),
|
|
8
13
|
});
|
|
9
14
|
|
|
10
15
|
const banBody = t.Object({
|
|
11
16
|
ip: t.String({ minLength: 1, maxLength: 45 }),
|
|
17
|
+
/**
|
|
18
|
+
* The key this ban is scoped to. Omitted or null is the blunt instrument:
|
|
19
|
+
* every key used from this address. That is a real choice an operator makes
|
|
20
|
+
* when the address is the problem, not a fallback for a missing value — so it
|
|
21
|
+
* is explicit in the body rather than inferred from an empty string.
|
|
22
|
+
*/
|
|
23
|
+
apiKeyId: t.Optional(t.Union([t.String({ minLength: 1 }), t.Null()])),
|
|
24
|
+
/** Whole minutes, or null for permanent. Default one hour. */
|
|
25
|
+
minutes: t.Optional(t.Union([t.Number({ minimum: 1 }), t.Null()])),
|
|
26
|
+
/**
|
|
27
|
+
* Bans the address outright, whatever `apiKeyId` says. Kept as its own flag
|
|
28
|
+
* so "no key supplied" cannot quietly widen into "every key" — the two read
|
|
29
|
+
* the same in a body that only had the one field.
|
|
30
|
+
*/
|
|
31
|
+
addressOnly: t.Optional(t.Boolean()),
|
|
12
32
|
});
|
|
13
33
|
|
|
34
|
+
/** The durations the route accepts, as whole minutes. `null` is permanent. */
|
|
35
|
+
const ALLOWED_MINUTES = new Set<number | null>(
|
|
36
|
+
BAN_DURATION_CHOICES.map((choice) => choice.minutes),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* How long a requested ban lasts, in milliseconds.
|
|
41
|
+
*
|
|
42
|
+
* `null` means permanent, which is a very long TTL rather than an unbounded
|
|
43
|
+
* ban: both stores lapse a ban by comparing an expiry to now, so a genuinely
|
|
44
|
+
* infinite ban would need a second mechanism and a second way to get it wrong.
|
|
45
|
+
*
|
|
46
|
+
* Anything not on the offered list is rejected rather than clamped. Clamping
|
|
47
|
+
* would answer a request for 90 days with a week and report success, and an
|
|
48
|
+
* operator who asked for the longer ban would believe they got it.
|
|
49
|
+
*/
|
|
50
|
+
function banTtlMs(minutes: number | null | undefined): number {
|
|
51
|
+
const chosen = minutes === undefined ? DEFAULT_BAN_MINUTES : minutes;
|
|
52
|
+
if (!ALLOWED_MINUTES.has(chosen)) {
|
|
53
|
+
throw new ConsoleDomainError(
|
|
54
|
+
"invalid_request",
|
|
55
|
+
400,
|
|
56
|
+
"Unsupported ban duration. Choose one of the offered durations.",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return chosen === null ? PERMANENT_BAN_TTL_MS : chosen * 60_000;
|
|
60
|
+
}
|
|
61
|
+
|
|
14
62
|
export function createModelAbuseRoutes(config: ModelAbuseRoutesConfig): Elysia {
|
|
15
63
|
return new Elysia()
|
|
16
64
|
.get("/model-bans", async ({ request, set }) => {
|
|
@@ -25,7 +73,12 @@ export function createModelAbuseRoutes(config: ModelAbuseRoutesConfig): Elysia {
|
|
|
25
73
|
.post("/model-bans", { body: banBody }, async ({ request, body, set }) => {
|
|
26
74
|
try {
|
|
27
75
|
requireGlobalAdmin(config.accessResolver(request));
|
|
28
|
-
const ban = await config.strikes.ban(
|
|
76
|
+
const ban = await config.strikes.ban({
|
|
77
|
+
ip: body.ip,
|
|
78
|
+
...(body.apiKeyId === undefined ? {} : { apiKeyId: body.apiKeyId }),
|
|
79
|
+
ttlMs: banTtlMs(body.minutes),
|
|
80
|
+
...(body.addressOnly === undefined ? {} : { addressOnly: body.addressOnly }),
|
|
81
|
+
});
|
|
29
82
|
return { ban };
|
|
30
83
|
} catch (e) {
|
|
31
84
|
return errorResponse(e, set, "Model ban failed");
|
|
@@ -2,7 +2,7 @@ import { eq } from "drizzle-orm";
|
|
|
2
2
|
|
|
3
3
|
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
4
4
|
import { apiKeys } from "../../persistence/schema";
|
|
5
|
-
import {
|
|
5
|
+
import { queryIndexedBuckets } from "../../observability/event-day-index";
|
|
6
6
|
import { utcDayStart, utcMonthStart } from "../shared/day";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -33,9 +33,20 @@ export async function familyBucketSpend(
|
|
|
33
33
|
.where(eq(apiKeys.parentKeyId, keyId));
|
|
34
34
|
const keyIds = [keyId, ...children.map((child) => child.id)];
|
|
35
35
|
const spentSince = async (start: Date): Promise<number> => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// Folded first, so the index covers every event written so far including the
|
|
37
|
+
// current day's — then read from the index alone.
|
|
38
|
+
//
|
|
39
|
+
// Deliberately not index *plus* events: they hold the same requests, and
|
|
40
|
+
// adding them doubled every bucket. Folding first is what makes the index
|
|
41
|
+
// sufficient on its own; the watermark is the point up to which it is
|
|
42
|
+
// complete.
|
|
43
|
+
const allowed = new Set(keyIds);
|
|
44
|
+
const indexed = await queryIndexedBuckets({ since: start, foldFirst: true }).catch(
|
|
45
|
+
() => [],
|
|
46
|
+
);
|
|
47
|
+
return indexed
|
|
48
|
+
.filter((bucket) => bucket.apiKeyId !== null && allowed.has(bucket.apiKeyId))
|
|
49
|
+
.reduce((sum, bucket) => sum + bucket.inputTokens + bucket.outputTokens, 0);
|
|
39
50
|
};
|
|
40
51
|
const [daily, monthly] = await Promise.all([
|
|
41
52
|
spentSince(utcDayStart(now)),
|