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
|
@@ -8,17 +8,17 @@ export type DbMode = "full" | "lite";
|
|
|
8
8
|
/**
|
|
9
9
|
* Reads `CARTETHYIA_DB_MODE`, which is required and has no default.
|
|
10
10
|
*
|
|
11
|
-
* It
|
|
12
|
-
* different answers
|
|
13
|
-
*
|
|
14
|
-
* The third one is the one that
|
|
15
|
-
* mode and then
|
|
16
|
-
*
|
|
11
|
+
* It must not default to `full`: that leaves the same key carrying three
|
|
12
|
+
* different answers, since `.env.example` ships `lite`, the `bun setup` prompt
|
|
13
|
+
* offers `lite`, and an environment that sets nothing at all would boot
|
|
14
|
+
* `full`. The third one is the one that bites — a clone without a `.env` comes
|
|
15
|
+
* up in Full mode and then fails on a missing `DATABASE_URL`, when the
|
|
16
|
+
* documented intent is the self-contained Lite path.
|
|
17
17
|
*
|
|
18
18
|
* Failing closed is the honest fix: the two modes are different databases with
|
|
19
19
|
* different migration sets (`migrations/lite-sqlite3/` vs
|
|
20
20
|
* `migrations/full-postgres/`), so guessing one silently picks which migrations
|
|
21
|
-
* an operator's data is read against. `bun setup`, `scripts/install
|
|
21
|
+
* an operator's data is read against. `bun setup`, `scripts/install.sh`,
|
|
22
22
|
* `.env.example`, and `docker-compose.yml` all set it explicitly,
|
|
23
23
|
* so requiring it costs an existing deployment nothing.
|
|
24
24
|
*/
|
|
@@ -67,6 +67,12 @@ export class HealthEventFlushBuffer {
|
|
|
67
67
|
private timer: ReturnType<typeof setInterval> | undefined;
|
|
68
68
|
private flushing = false;
|
|
69
69
|
private dropped = 0;
|
|
70
|
+
/**
|
|
71
|
+
* Settles once while an INSERT is in flight, so a purge that drops the
|
|
72
|
+
* queue can wait that write out instead of letting it land behind its own
|
|
73
|
+
* `DELETE` and resurrect the rows it just removed.
|
|
74
|
+
*/
|
|
75
|
+
private inflightDone: Promise<void> | undefined;
|
|
70
76
|
|
|
71
77
|
constructor(
|
|
72
78
|
private readonly db: CartethyiaDatabase,
|
|
@@ -97,10 +103,38 @@ export class HealthEventFlushBuffer {
|
|
|
97
103
|
return this.dropped;
|
|
98
104
|
}
|
|
99
105
|
|
|
106
|
+
/** Drops queued rows — everything, or one account's — without writing them. */
|
|
107
|
+
clear(accountId?: string): void {
|
|
108
|
+
if (accountId === undefined) {
|
|
109
|
+
this.pending.length = 0;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
for (let i = this.pending.length - 1; i >= 0; i -= 1) {
|
|
113
|
+
if (this.pending[i]?.accountId === accountId) this.pending.splice(i, 1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Drops queued rows and then blocks until a write already in flight lands.
|
|
119
|
+
*
|
|
120
|
+
* The barrier is what makes a purge sound: deleting only the rows visible at
|
|
121
|
+
* one instant is final, while an insert in flight for the same instant would
|
|
122
|
+
* re-add rows after the `DELETE` finished. Rows enqueued *after* the drop
|
|
123
|
+
* are new events, not resurrected ones, so they are left alone.
|
|
124
|
+
*/
|
|
125
|
+
async drain(accountId?: string): Promise<void> {
|
|
126
|
+
this.clear(accountId);
|
|
127
|
+
await this.inflightDone;
|
|
128
|
+
}
|
|
129
|
+
|
|
100
130
|
/** Writes everything accumulated so far. Safe to call concurrently. */
|
|
101
131
|
async flush(): Promise<void> {
|
|
102
132
|
if (this.flushing || this.pending.length === 0) return;
|
|
103
133
|
this.flushing = true;
|
|
134
|
+
let settle!: () => void;
|
|
135
|
+
this.inflightDone = new Promise<void>((resolve) => {
|
|
136
|
+
settle = resolve;
|
|
137
|
+
});
|
|
104
138
|
// Drained before the await: rows enqueued while the insert runs stay for
|
|
105
139
|
// the next pass rather than being lost if this one throws.
|
|
106
140
|
const batch = this.pending.splice(0, this.pending.length);
|
|
@@ -123,6 +157,8 @@ export class HealthEventFlushBuffer {
|
|
|
123
157
|
// are dropped; the durable status change was written synchronously.
|
|
124
158
|
} finally {
|
|
125
159
|
this.flushing = false;
|
|
160
|
+
this.inflightDone = undefined;
|
|
161
|
+
settle();
|
|
126
162
|
}
|
|
127
163
|
}
|
|
128
164
|
|
|
@@ -9,7 +9,7 @@ import { withTimeout } from "../boot/timeout";
|
|
|
9
9
|
/**
|
|
10
10
|
* Memoized readiness: the first caller runs the real `SELECT 1`/`PING`/
|
|
11
11
|
* migration checks, and every subsequent caller within the TTL window reuses
|
|
12
|
-
* that result — so a burst of concurrent requests
|
|
12
|
+
* that result — so a burst of concurrent requests does not each spend a
|
|
13
13
|
* Postgres pool checkout plus a Redis PING on readiness validation.
|
|
14
14
|
*/
|
|
15
15
|
|
|
@@ -182,7 +182,7 @@ export const providers = pgTable(
|
|
|
182
182
|
// `tenant_id` null means the account is shared pool-wide; populated means
|
|
183
183
|
// tenant-owned/BYOK. The concurrency ceiling lives in provider routing
|
|
184
184
|
// (`provider_routing_settings.max_inflight`) and network pools
|
|
185
|
-
// (`network_pools.max_inflight`);
|
|
185
|
+
// (`network_pools.max_inflight`); there is no per-account override.
|
|
186
186
|
export const providerAccounts = pgTable("provider_accounts", {
|
|
187
187
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
188
188
|
providerId: text("provider_id")
|
|
@@ -469,11 +469,11 @@ export const networkPools = pgTable("network_pools", {
|
|
|
469
469
|
* (`egress_ip`, `last_latency_ms`, the speed-test columns) rather than in the
|
|
470
470
|
* browser.
|
|
471
471
|
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
472
|
+
* Browser storage is not enough: a verdict held only there is visible to the
|
|
473
|
+
* operator who ran it and to nobody else — not to another machine, not to the
|
|
474
|
+
* gateway, and not to admission. A pool would read "OpenCode blocked" on
|
|
475
|
+
* screen while dispatch kept sending traffic through it, because nothing
|
|
476
|
+
* outside that one browser had heard the verdict.
|
|
477
477
|
*
|
|
478
478
|
* `null` means never probed. These describe the *address* recorded in
|
|
479
479
|
* `egress_ip` at the time, so a changed endpoint clears them.
|
|
@@ -527,7 +527,7 @@ export const healthEvents = pgTable(
|
|
|
527
527
|
index("health_events_account_id_created_at_idx").on(table.accountId, table.createdAt),
|
|
528
528
|
// Descending `created_at` is deliberate: health history is always read
|
|
529
529
|
// newest-first. The SQLite baseline drops the direction (see
|
|
530
|
-
// `scripts/generate
|
|
530
|
+
// `scripts/tools/generate-sqlite.ts` — SQLite scans an index either way),
|
|
531
531
|
// so this is a Postgres-only access-path choice, not a cross-backend
|
|
532
532
|
// disagreement.
|
|
533
533
|
index("health_events_account_model_created_idx").on(
|
|
@@ -544,8 +544,7 @@ export const healthEvents = pgTable(
|
|
|
544
544
|
);
|
|
545
545
|
|
|
546
546
|
|
|
547
|
-
// Model aliasing & combos
|
|
548
|
-
// `routing_policies` scaffolding. Aliases resolve a client-facing name to a
|
|
547
|
+
// Model aliasing & combos. Aliases resolve a client-facing name to a
|
|
549
548
|
// real model; combos back a client-facing name with >=1 real models,
|
|
550
549
|
// auto-selected via `fallback` or `round_robin`.
|
|
551
550
|
export const modelComboStrategy = pgEnum("model_combo_strategy", [
|
|
@@ -556,7 +555,7 @@ export const modelComboStrategy = pgEnum("model_combo_strategy", [
|
|
|
556
555
|
/** Canonical `ComboStrategy` union, derived from the enum above. Console
|
|
557
556
|
* domain and routing layers import this instead of hand-typing their own mirror.
|
|
558
557
|
* `ProviderRoutingStrategy` covers the same two values but persists on
|
|
559
|
-
* `provider_routing_settings` beside `rotateCount
|
|
558
|
+
* `provider_routing_settings` beside `rotateCount`. */
|
|
560
559
|
export type ComboStrategy = (typeof modelComboStrategy.enumValues)[number];
|
|
561
560
|
|
|
562
561
|
export const modelAliases = pgTable(
|
|
@@ -635,9 +634,8 @@ export const providerRoutingSettings = pgTable(
|
|
|
635
634
|
* provider; this one bounds one provider's share of one pool. All three
|
|
636
635
|
* are enforced together — none replaces another.
|
|
637
636
|
*
|
|
638
|
-
* `null` = unlimited, and that is the default:
|
|
639
|
-
*
|
|
640
|
-
* extra Redis key and no extra round trip.
|
|
637
|
+
* `null` = unlimited, and that is the default: an unconfigured provider
|
|
638
|
+
* costs no extra Redis key and no extra round trip.
|
|
641
639
|
*/
|
|
642
640
|
maxInflightPerProxy: integer("max_inflight_per_proxy"),
|
|
643
641
|
/**
|
|
@@ -752,7 +750,7 @@ export const apiKeys = pgTable(
|
|
|
752
750
|
/**
|
|
753
751
|
* `whitelist` = only the names in `modelList` may be used (empty = all);
|
|
754
752
|
* `blacklist` = the names in `modelList` are refused (empty = none). One
|
|
755
|
-
* list, one mode
|
|
753
|
+
* list, one mode.
|
|
756
754
|
*/
|
|
757
755
|
modelAccessMode: text("model_access_mode")
|
|
758
756
|
.$type<ApiKeyModelAccessMode>()
|
|
@@ -787,11 +785,9 @@ export const apiKeys = pgTable(
|
|
|
787
785
|
/**
|
|
788
786
|
* One *live* default key per tenant, enforced by the database.
|
|
789
787
|
*
|
|
790
|
-
*
|
|
791
|
-
* so the two descriptions disagreed and nothing noticed. It is the only
|
|
792
|
-
* database-level guarantee behind `isDefault`, and
|
|
788
|
+
* This is the only database-level guarantee behind `isDefault`, and
|
|
793
789
|
* `test/persistence/sqlite-baseline.test.ts` pins it by name — so the
|
|
794
|
-
* schema
|
|
790
|
+
* schema must keep declaring it to match the baseline.
|
|
795
791
|
*
|
|
796
792
|
* Partial on `revoked_at IS NULL` so a tenant can rotate its default key
|
|
797
793
|
* without the old, revoked row blocking the new one.
|
|
@@ -879,8 +875,7 @@ export const shareLinks = pgTable(
|
|
|
879
875
|
.references(() => apiKeys.id, { onDelete: "cascade" }),
|
|
880
876
|
tokenHash: text("token_hash").notNull(),
|
|
881
877
|
// Encrypted bearer token. Present so the console can re-display a stable
|
|
882
|
-
// link;
|
|
883
|
-
// cannot be shown again.
|
|
878
|
+
// link; nullable, and a NULL row simply cannot be shown again.
|
|
884
879
|
tokenEncrypted: bytea("token_encrypted"),
|
|
885
880
|
/**
|
|
886
881
|
* The public slug this page is reachable at, in the clear.
|
|
@@ -891,7 +886,7 @@ export const shareLinks = pgTable(
|
|
|
891
886
|
* unencrypted keeps every restored share page reachable under the address
|
|
892
887
|
* it was created with.
|
|
893
888
|
*
|
|
894
|
-
* Nullable:
|
|
889
|
+
* Nullable: a row without a slug has no address, and inventing one
|
|
895
890
|
* would publish an address the page never had.
|
|
896
891
|
*/
|
|
897
892
|
slug: text("slug"),
|
|
@@ -903,8 +898,8 @@ export const shareLinks = pgTable(
|
|
|
903
898
|
},
|
|
904
899
|
(table) => [
|
|
905
900
|
uniqueIndex("share_links_token_hash_idx").on(table.tokenHash),
|
|
906
|
-
// Partial: rows without a slug
|
|
907
|
-
//
|
|
901
|
+
// Partial: rows without a slug are not constrained, so backfilling is never
|
|
902
|
+
// blocked by a uniqueness collision.
|
|
908
903
|
uniqueIndex("share_links_slug_uidx").on(table.slug).where(sql`${table.slug} IS NOT NULL`),
|
|
909
904
|
index("share_links_api_key_idx").on(table.apiKeyId),
|
|
910
905
|
index("share_links_active_kind_expires_idx").on(table.active, table.kind, table.expiresAt),
|
|
@@ -1045,7 +1040,59 @@ export interface ConsoleSettingsPreferences {
|
|
|
1045
1040
|
* - `maximum` — 16 MiB combined cap, expect CPU/memory spikes while active
|
|
1046
1041
|
*/
|
|
1047
1042
|
telemetryPayloadDepth?: "minimum" | "moderate" | "maximum";
|
|
1043
|
+
/**
|
|
1044
|
+
* How long a captured payload frame is kept, in whole minutes.
|
|
1045
|
+
*
|
|
1046
|
+
* `null` or absent means the deployment default
|
|
1047
|
+
* (`CARTETHYIA_TELEMETRY_PAYLOAD_RETENTION_MS`, 15 minutes). Raising it is a
|
|
1048
|
+
* real trade: a frame is a gzipped copy of up to four request bodies, so
|
|
1049
|
+
* keeping them for a week is keeping a week of bodies on disk. The upper
|
|
1050
|
+
* bound is seven days, enforced by the capture path rather than trusted here.
|
|
1051
|
+
*/
|
|
1052
|
+
telemetryPayloadRetentionMinutes?: number | null;
|
|
1048
1053
|
privacyMode?: "masked" | "full";
|
|
1054
|
+
/**
|
|
1055
|
+
* Ignores an account-wide cooldown when building route candidates.
|
|
1056
|
+
*
|
|
1057
|
+
* Off by default, and deliberately dangerous: an account in cooldown was
|
|
1058
|
+
* parked because the upstream refused it, and dialing it anyway reproduces
|
|
1059
|
+
* that refusal on every attempt. Turning this on can exhaust the candidate
|
|
1060
|
+
* list and fail a request that would otherwise have been routed.
|
|
1061
|
+
*
|
|
1062
|
+
* The override is read-only — the cooldown is still recorded, still shown in
|
|
1063
|
+
* Health, and still recovered by the sweep. Nothing is written back, so the
|
|
1064
|
+
* toggle can be turned off again without cleaning anything up.
|
|
1065
|
+
*
|
|
1066
|
+
* Per-(account, model) cooldowns are NOT overridden. Those are usually a
|
|
1067
|
+
* named quota ("daily free limit reached, try again in 11h") where the
|
|
1068
|
+
* upstream stated a real reset; ignoring it only buys repeated refusals.
|
|
1069
|
+
*/
|
|
1070
|
+
forceDisableCooldown?: boolean;
|
|
1071
|
+
/**
|
|
1072
|
+
* Per-tenant upstream request timeout in seconds, overriding
|
|
1073
|
+
* `CARTETHYIA_UPSTREAM_TIMEOUT_MS`. `null` or absent means use the
|
|
1074
|
+
* deployment default.
|
|
1075
|
+
*/
|
|
1076
|
+
upstreamTimeoutSeconds?: number | null;
|
|
1077
|
+
/**
|
|
1078
|
+
* Per-tenant telemetry compaction age in days, overriding
|
|
1079
|
+
* `CARTETHYIA_TELEMETRY_COMPACT_AFTER_DAYS`. `null` or absent means use the
|
|
1080
|
+
* deployment default. Lower keeps less history but bounds the usage-list
|
|
1081
|
+
* scan; see the compaction notes in `event-compaction.ts`.
|
|
1082
|
+
*/
|
|
1083
|
+
telemetryWindowMinutes?: number | null;
|
|
1084
|
+
/**
|
|
1085
|
+
* Per-tenant ceiling for one inline attachment, in bytes, overriding the
|
|
1086
|
+
* deployment default of 10 MB.
|
|
1087
|
+
*
|
|
1088
|
+
* Raising this asserts that this tenant's upstreams accept larger
|
|
1089
|
+
* attachments. The clamp is a hard send gate precisely because an oversized
|
|
1090
|
+
* block poisons every retry and every replayed turn — the upstream rejects
|
|
1091
|
+
* the whole request rather than the one block — so an override larger than
|
|
1092
|
+
* the provider actually accepts trades that protection away. Lowering it is
|
|
1093
|
+
* the safer direction and the more common need.
|
|
1094
|
+
*/
|
|
1095
|
+
imageClampMaxBytes?: number | null;
|
|
1049
1096
|
}
|
|
1050
1097
|
|
|
1051
1098
|
export const consoleSettings = pgTable("console_settings", {
|
|
@@ -1247,9 +1294,8 @@ export const telemetryPayloads = pgTable(
|
|
|
1247
1294
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
1248
1295
|
/**
|
|
1249
1296
|
* Typed index into the on-disk frame store. Prompt/response bodies live
|
|
1250
|
-
* only in the frame file these columns name — never in Postgres
|
|
1251
|
-
*
|
|
1252
|
-
* written here by accident.
|
|
1297
|
+
* only in the frame file these columns name — never in Postgres, which is
|
|
1298
|
+
* why this table carries no body column to write one into by accident.
|
|
1253
1299
|
*/
|
|
1254
1300
|
storage: text("storage").notNull(),
|
|
1255
1301
|
file: text("file").notNull(),
|
|
@@ -1264,8 +1310,8 @@ export const telemetryPayloads = pgTable(
|
|
|
1264
1310
|
* reads it off the indexed row.
|
|
1265
1311
|
*
|
|
1266
1312
|
* Bits: 1=request, 2=provider_request, 4=provider_response,
|
|
1267
|
-
* 8=client_response. Nullable: NULL
|
|
1268
|
-
*
|
|
1313
|
+
* 8=client_response. Nullable: NULL renders as all icons greyed rather
|
|
1314
|
+
* than inventing presence.
|
|
1269
1315
|
*/
|
|
1270
1316
|
stages: integer("stages"),
|
|
1271
1317
|
},
|
|
@@ -1273,7 +1319,7 @@ export const telemetryPayloads = pgTable(
|
|
|
1273
1319
|
index("telemetry_payloads_request_id_idx").on(table.requestId),
|
|
1274
1320
|
index("telemetry_payloads_tenant_request_idx").on(table.tenantId, table.requestId),
|
|
1275
1321
|
index("telemetry_payloads_expires_idx").on(table.expiresAt),
|
|
1276
|
-
check("telemetry_payloads_storage_check", sql`${table.storage}
|
|
1322
|
+
check("telemetry_payloads_storage_check", sql`${table.storage} = 'jsonl-file'`),
|
|
1277
1323
|
check("telemetry_payloads_version_check", sql`${table.version} = 1`),
|
|
1278
1324
|
check("telemetry_payloads_offset_check", sql`${table.offset} >= 0`),
|
|
1279
1325
|
check("telemetry_payloads_length_check", sql`${table.length} > 0`),
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Cross-process cache coordination: a shared L2 in front of Postgres.
|
|
2
2
|
//
|
|
3
|
-
// Every hot read in the request path is cached in-process
|
|
4
|
-
// `documentation/research/iomemory/01-request-path-census.md`). That is correct
|
|
3
|
+
// Every hot read in the request path is cached in-process. That is correct
|
|
5
4
|
// for one process and wrong for several: `main.ts` sets `reusePort: true`, so N
|
|
6
5
|
// gateway processes each hold their own copy. With no shared layer, a key
|
|
7
6
|
// revoked through the console stays valid for up to a full TTL in every other
|
|
@@ -187,6 +187,7 @@ export interface ApiKeyStore {
|
|
|
187
187
|
reorder(tenantId: string, keyIds: readonly string[]): Promise<void>;
|
|
188
188
|
update(tenantId: string, keyId: string, patch: ApiKeyPatch): Promise<ApiKeyRecord | undefined>;
|
|
189
189
|
revoke(tenantId: string, keyId: string, revokedAt: Date): Promise<boolean>;
|
|
190
|
+
remove(tenantId: string, keyId: string): Promise<boolean>;
|
|
190
191
|
findActiveByHash?(hash: string): Promise<typeof apiKeys.$inferSelect | undefined>;
|
|
191
192
|
findActiveById?(keyId: string): Promise<typeof apiKeys.$inferSelect | undefined>;
|
|
192
193
|
sumChildrenConsumed?(parentKeyId: string): Promise<number>;
|
|
@@ -380,10 +381,6 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
380
381
|
.set({
|
|
381
382
|
...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}),
|
|
382
383
|
...(patch.label !== undefined ? { label: patch.label } : {}),
|
|
383
|
-
// Promotes a legacy default key: `isDefaultGatewayApiKey` recognizes
|
|
384
|
-
// it by its canonical label, but recognition was never written back,
|
|
385
|
-
// so the row stayed unflagged and a later rename — which changes the
|
|
386
|
-
// only field that identifies it — dropped the default entirely.
|
|
387
384
|
...(patch.isDefault !== undefined ? { isDefault: patch.isDefault } : {}),
|
|
388
385
|
...(patch.keyHash !== undefined ? { keyHash: patch.keyHash } : {}),
|
|
389
386
|
...(patch.keyMode !== undefined ? { keyMode: patch.keyMode } : {}),
|
|
@@ -413,8 +410,8 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
413
410
|
: {}),
|
|
414
411
|
// Scopes are validated once, in `validateApiKeyRequest`, against the
|
|
415
412
|
// tenant scope set; the store writes what it is handed. Omitting the
|
|
416
|
-
// field here
|
|
417
|
-
// silently dropped — a grant like `routing:cli_mapping`
|
|
413
|
+
// field here means an update is type-checked, accepted, and then
|
|
414
|
+
// silently dropped — a grant like `routing:cli_mapping` can be
|
|
418
415
|
// turned on and never stick.
|
|
419
416
|
...(patch.scopes === undefined ? {} : { scopes: patch.scopes }),
|
|
420
417
|
})
|
|
@@ -453,6 +450,21 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
|
|
|
453
450
|
return true;
|
|
454
451
|
});
|
|
455
452
|
}
|
|
453
|
+
async remove(tenantId: string, keyId: string): Promise<boolean> {
|
|
454
|
+
return this.db.transaction(async (tx) => {
|
|
455
|
+
const rows = await tx
|
|
456
|
+
.delete(apiKeys)
|
|
457
|
+
.where(
|
|
458
|
+
and(
|
|
459
|
+
eq(apiKeys.tenantId, tenantId),
|
|
460
|
+
eq(apiKeys.id, keyId),
|
|
461
|
+
eq(apiKeys.isDefault, false),
|
|
462
|
+
),
|
|
463
|
+
)
|
|
464
|
+
.returning({ id: apiKeys.id });
|
|
465
|
+
return rows.length > 0;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
456
468
|
async findActiveByHash(hash: string): Promise<typeof apiKeys.$inferSelect | undefined> {
|
|
457
469
|
// The SQLite backend holds this lookup prepared. The builder path rebuilds
|
|
458
470
|
// the query per call, which measured 60x the lookup itself, so the hot
|
|
@@ -308,9 +308,10 @@ export class DrizzleShareLinkStore implements ShareLinkStore {
|
|
|
308
308
|
.set({
|
|
309
309
|
tokenHash: input.tokenHash,
|
|
310
310
|
tokenEncrypted: input.tokenEncrypted,
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
|
|
311
|
+
// Rotating the slug changes the public URL, so the old link stops resolving.
|
|
312
|
+
// Callers always generate a fresh slug via generateShareSlug() before calling
|
|
313
|
+
// create() with rotate: true.
|
|
314
|
+
slug: input.slug,
|
|
314
315
|
expiresAt: input.expiresAt,
|
|
315
316
|
lastViewedAt: null,
|
|
316
317
|
})
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
type TelemetryUsageIdentityType,
|
|
11
11
|
} from "../schema";
|
|
12
12
|
import { isGatewayError } from "../../observability/telemetry-status";
|
|
13
|
+
import { TELEMETRY_EVENT_DIR } from "../../observability/event-log";
|
|
14
|
+
import { dayEventFileName, utcDay } from "../../observability/event-day-index";
|
|
13
15
|
|
|
14
16
|
interface UsageTotalDelta {
|
|
15
17
|
readonly tenantId: string;
|
|
@@ -66,27 +68,47 @@ function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[])
|
|
|
66
68
|
* operations, lifetime aggregates, and retention.
|
|
67
69
|
*/
|
|
68
70
|
|
|
69
|
-
const EVENT_DIR = "./data/telemetry-events";
|
|
70
71
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
|
-
* Deletes
|
|
74
|
-
*
|
|
74
|
+
* Deletes whole days past the retention cutoff: the event file and its index
|
|
75
|
+
* together.
|
|
76
|
+
*
|
|
77
|
+
* The day is the retention unit now, so both go at once. Removing one without
|
|
78
|
+
* the other is the failure this avoids — an index with no events reports totals
|
|
79
|
+
* for requests that no longer exist, and events with no index are invisible to
|
|
80
|
+
* every usage read.
|
|
81
|
+
*
|
|
82
|
+
* The cutoff is a whole day, so today's file — still being appended to — is
|
|
83
|
+
* never a candidate. Comparing filenames hour by hour would match a day file's
|
|
84
|
+
* `.events` suffix against an hour stamp and could drop the open file.
|
|
75
85
|
*/
|
|
76
86
|
async function pruneEventFiles(before: Date): Promise<void> {
|
|
77
|
-
const files = await readdir(
|
|
78
|
-
const
|
|
87
|
+
const files = await readdir(TELEMETRY_EVENT_DIR).catch(() => [] as string[]);
|
|
88
|
+
const cutoffDay = before.toISOString().slice(0, 10);
|
|
79
89
|
for (const file of files) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (
|
|
90
|
+
const day = file.slice(0, 10);
|
|
91
|
+
if (day.length !== 10 || day >= cutoffDay) continue;
|
|
92
|
+
if (!file.endsWith(".events.jsonl") && !file.endsWith(".index.json")) continue;
|
|
93
|
+
await rm(join(TELEMETRY_EVENT_DIR, file), { force: true });
|
|
83
94
|
}
|
|
84
95
|
}
|
|
85
96
|
|
|
86
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* Appends one JSON line per event to the day's event file.
|
|
99
|
+
*
|
|
100
|
+
* One file per UTC day, appended to and never rewritten while the day is open.
|
|
101
|
+
* A file per `windowMinutes` window — 96 a day at the default — would have to be
|
|
102
|
+
* found, opened and parsed one by one on every usage query, and which of those
|
|
103
|
+
* files was still being written could only be inferred from the name, which is
|
|
104
|
+
* what lets the open one be deleted mid-write.
|
|
105
|
+
*
|
|
106
|
+
* A single growing file has no such ambiguity, and the day index tracks how much
|
|
107
|
+
* of it has been counted by byte offset.
|
|
108
|
+
*/
|
|
87
109
|
async function appendEvents(rows: readonly (typeof telemetryEvents.$inferInsert)[]): Promise<void> {
|
|
88
|
-
await mkdir(
|
|
89
|
-
const file = join(
|
|
110
|
+
await mkdir(TELEMETRY_EVENT_DIR, { recursive: true });
|
|
111
|
+
const file = join(TELEMETRY_EVENT_DIR, dayEventFileName(utcDay(new Date())));
|
|
90
112
|
const newline = String.fromCharCode(10);
|
|
91
113
|
const body = rows.map((row) => JSON.stringify(row)).join(newline) + newline;
|
|
92
114
|
await appendFile(file, body, "utf8");
|
|
@@ -308,6 +308,13 @@ export type ContentPart =
|
|
|
308
308
|
/** Responses reasoning summary block index, preserved across surfaces. */
|
|
309
309
|
summary_index?: number;
|
|
310
310
|
signature?: string;
|
|
311
|
+
/**
|
|
312
|
+
* Responses item id the `encrypted_content` was issued for. The upstream
|
|
313
|
+
* binds encrypted reasoning to its item and rejects a replay whose item
|
|
314
|
+
* id does not match, so the id travels with the payload it belongs to
|
|
315
|
+
* rather than being re-derived at request time.
|
|
316
|
+
*/
|
|
317
|
+
item_id?: string;
|
|
311
318
|
encrypted_content?: string;
|
|
312
319
|
}
|
|
313
320
|
| { kind: "extension"; name: string; payload: unknown };
|
|
@@ -596,9 +603,10 @@ export function hasEncryptedReasoning(parts: readonly ContentPart[]): boolean {
|
|
|
596
603
|
* Single-pass summary of a request's content parts.
|
|
597
604
|
*
|
|
598
605
|
* `deriveRequiredCapabilities` + `assessTranslation` run on every candidate
|
|
599
|
-
* preflight and
|
|
600
|
-
*
|
|
601
|
-
* no intermediate array, and answers every predicate the
|
|
606
|
+
* preflight, and flattening the whole request (`allParts`) in each and then
|
|
607
|
+
* scanning the copy up to ~6 times is the cost this avoids. It walks the
|
|
608
|
+
* messages once, builds no intermediate array, and answers every predicate the
|
|
609
|
+
* pipeline needs.
|
|
602
610
|
* Results are memoized per request object in a `WeakMap`, so the preflight +
|
|
603
611
|
* translate passes over one request share a single summary and entries are
|
|
604
612
|
* collected with the request itself — no manual invalidation, no leak.
|