cartethyia 2.5.4 → 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-C2orBsI2.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-BV6pxVQo.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-CW-wPmJ6.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-BR5HHvbi.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-mUfcknqm.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.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-Js9jUPSb.js → ProvidersPage-uPiGVTTK.js} +1 -1
- 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-DDbMQsoo.js → SetupPage-UB690SE2.js} +1 -1
- package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-BLujdX7N.js} +1 -1
- package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-D95UCPHI.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-Chi7pCnt.js → assert-C9FD_JwJ.js} +1 -1
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D4pQapKj.js → badge-CR72dasT.js} +1 -1
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DH6cg-_B.js → banner-CI9_eSfO.js} +1 -1
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/{card-BKqlUOe4.js → card-DS7CQEsO.js} +1 -1
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-fTgljTGA.js → cli-tools-CNqzrfdb.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-D5Ys_sM0.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/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-CQBr6U9c.js → entry-B9Xvtnfj.js} +1 -1
- 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-BlptL8Uf.js → github-badge-sltk1qUi.js} +1 -1
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/{index-js6TJvdr.js → index-CmxZr14F.js} +2 -2
- package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DUWDSmDV.js → inline-BhY2sSnO.js} +1 -1
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/{input-LVjgZq4f.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-DupvLfDx.js → list-row-1zzZYM0B.js} +1 -1
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/{live-C4pEJUqI.js → live-BivGfuUs.js} +1 -1
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-BRiuufkB.js → model-bans-CmffjLuG.js} +1 -1
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-B4ROwk8L.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-gmjzC3RS.js → query-keys-BFhGpwR1.js} +1 -1
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/{routing-C7tBT5o-.js → routing-CAb4F37R.js} +1 -1
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/{select-WUCXorXw.js → select-BYR_5K2c.js} +1 -1
- package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
- package/dist/dashboard/assets/{settings-C_zKl1GS.js → settings-DNX9sydi.js} +1 -1
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/{stack-_3O0F8Xn.js → stack-BHTfXdz_.js} +1 -1
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/{state-BgTxHcJS.js → state-mPDRlQkl.js} +1 -1
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DEjTtOWf.js → switch-DOmJxg7H.js} +1 -1
- package/dist/dashboard/assets/switch-DOmJxg7H.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-BWSC0UAS.js → useMutation-Dem0lXQj.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -54
- 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/{ui/background.ts → background.ts} +1 -1
- package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -244
- 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 +61 -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/{command/update.ts → update.ts} +39 -39
- package/scripts/cli/version.ts +67 -0
- package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
- package/src/boot/app.ts +697 -693
- package/src/boot/dependencies.ts +647 -646
- 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 +20 -30
- package/src/config/telemetry.ts +0 -20
- 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/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/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/data-management/contracts.ts +13 -6
- package/src/console/data-management/operations.ts +23 -18
- package/src/console/data-management/routes.ts +0 -1
- package/src/console/domain-registration.ts +10 -5
- package/src/console/observability/live.ts +22 -0
- package/src/console/observability/store.ts +39 -16
- package/src/console/observability/usage-dimensions.ts +4 -4
- package/src/console/providers/catalog/catalog-projections.ts +6 -7
- package/src/console/providers/catalog/model-operations.ts +5 -2
- package/src/console/providers/catalog/provider-operations.ts +4 -4
- package/src/console/providers/catalog/public-model-store.ts +8 -11
- package/src/console/providers/catalog/store.ts +1553 -1553
- package/src/console/providers/oauth/routes.ts +8 -12
- package/src/console/quota/account-quota-tenant.ts +6 -6
- package/src/console/quota/quota-view.ts +1 -1
- 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/probe-result.ts +3 -2
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/store.ts +8 -8
- package/src/console/settings/contracts.ts +58 -2
- package/src/console/settings/store.ts +30 -3
- package/src/console/shared/cursor.ts +8 -8
- package/src/console/shared/errors.ts +1 -3
- 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 +80 -78
- package/src/observability/event-day-index.ts +13 -15
- package/src/observability/event-log.ts +4 -4
- package/src/observability/logger.ts +6 -6
- package/src/observability/payload-capture.ts +151 -33
- package/src/observability/payload-store.ts +397 -440
- 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/readiness.ts +1 -1
- package/src/persistence/schema.ts +1406 -1402
- 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 +236 -237
- package/src/protocol/canonical-model.ts +11 -3
- package/src/protocol/codecs.ts +138 -4
- package/src/protocol/gateway-error.ts +6 -4
- package/src/protocol/request/chat.ts +21 -1
- package/src/protocol/request/codex.ts +10 -1
- package/src/protocol/request/messages.ts +4 -5
- 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/authentication/jwt-validator.ts +9 -10
- package/src/providers/authentication/oauth-refresh-service.ts +9 -11
- package/src/providers/configured-openai-providers.ts +134 -136
- 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/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 +132 -132
- package/src/providers/integrations/cline/cline.ts +537 -539
- package/src/providers/integrations/codex/codex-oauth.ts +1 -1
- 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 -310
- package/src/providers/integrations/cursor/cursor.ts +11 -163
- package/src/providers/integrations/factory/factory-models.ts +3 -5
- package/src/providers/integrations/grok/grok-install-id.ts +2 -3
- 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 +8 -8
- 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.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.ts +2 -2
- package/src/providers/integrations/zcode/oauth.ts +6 -1
- package/src/providers/operations/account-health-service.ts +1528 -1523
- 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-metadata.ts +4 -8
- package/src/providers/provider-registry.ts +6 -7
- package/src/providers/provider-usage.ts +1 -1
- 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 +504 -504
- 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/stream-bridge.ts +1 -3
- package/src/transport/dispatch/streaming/finalize.ts +1 -1
- package/src/transport/dispatch/streaming/stream.ts +5 -6
- 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 +2 -2
- package/src/transport/request/state.ts +12 -8
- package/src/transport/routing/admission.ts +4 -0
- package/src/transport/routing/route-catalog.ts +8 -1
- 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-C2orBsI2.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Bsan4679.js +0 -1
- package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js +0 -1
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +0 -1
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +0 -7
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +0 -1
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js +0 -56
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js +0 -1
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
- package/dist/dashboard/assets/app-COlK95oi.css +0 -2
- package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
- package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
- package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
- package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
- package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BYj-8WuK.js +0 -1
- package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js +0 -1
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
- package/dist/dashboard/assets/entry-CBg5zeu8.js +0 -1
- package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
- package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
- package/dist/dashboard/assets/entry-CgdSAzEV.js +0 -4
- package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
- package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
- package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
- package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
- package/dist/dashboard/assets/layout-CPfxpsfy.js +0 -1
- package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
- package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
- package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
- package/dist/dashboard/assets/numbers-YNcHvRY7.js +0 -2
- package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-gmjzC3RS.js.gz +0 -0
- package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
- package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
- package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
- package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
- package/dist/dashboard/assets/switch-DEjTtOWf.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-BWSC0UAS.js.gz +0 -0
- package/scripts/cli/command/admin.ts +0 -65
- package/scripts/cli/command/doctor.ts +0 -257
- 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-DCQZRmHY.js → app-BIt446yE.js} +0 -0
- /package/dist/dashboard/assets/{app-DCQZRmHY.js.gz → app-BIt446yE.js.gz} +0 -0
- /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -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
|
})
|
|
@@ -1,237 +1,236 @@
|
|
|
1
|
-
import { appendFile, mkdir, readdir, rm } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { sql } from "drizzle-orm";
|
|
4
|
-
import { affectedRows, currentTimestamp, laterOf } from "../dialect";
|
|
5
|
-
import type { CartethyiaDatabase } from "../db-handle";
|
|
6
|
-
import {
|
|
7
|
-
telemetryEvents,
|
|
8
|
-
telemetryPayloads,
|
|
9
|
-
telemetryUsageTotals,
|
|
10
|
-
type TelemetryUsageIdentityType,
|
|
11
|
-
} from "../schema";
|
|
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";
|
|
15
|
-
|
|
16
|
-
interface UsageTotalDelta {
|
|
17
|
-
readonly tenantId: string;
|
|
18
|
-
readonly identityType: TelemetryUsageIdentityType;
|
|
19
|
-
readonly entityId: string;
|
|
20
|
-
requests: number;
|
|
21
|
-
errors: number;
|
|
22
|
-
inputTokens: number;
|
|
23
|
-
outputTokens: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function addUsageDelta(
|
|
27
|
-
deltas: Map<string, UsageTotalDelta>,
|
|
28
|
-
row: typeof telemetryEvents.$inferInsert,
|
|
29
|
-
identityType: TelemetryUsageIdentityType,
|
|
30
|
-
entityId: string | null | undefined,
|
|
31
|
-
): void {
|
|
32
|
-
if (entityId == null) return;
|
|
33
|
-
const key = `${row.tenantId}\0${identityType}\0${entityId}`;
|
|
34
|
-
let delta = deltas.get(key);
|
|
35
|
-
if (!delta) {
|
|
36
|
-
delta = {
|
|
37
|
-
tenantId: row.tenantId,
|
|
38
|
-
identityType,
|
|
39
|
-
entityId,
|
|
40
|
-
requests: 0,
|
|
41
|
-
errors: 0,
|
|
42
|
-
inputTokens: 0,
|
|
43
|
-
outputTokens: 0,
|
|
44
|
-
};
|
|
45
|
-
deltas.set(key, delta);
|
|
46
|
-
}
|
|
47
|
-
delta.requests += 1;
|
|
48
|
-
// Same definition as every read-side error count, including the HTTP-status
|
|
49
|
-
// exclusions (404/499/503/504): this rollup is durable, so a request counted here
|
|
50
|
-
// stays counted after its raw row is pruned and cannot be corrected later.
|
|
51
|
-
if (isGatewayError(row.status, row.httpStatus)) delta.errors += 1;
|
|
52
|
-
delta.inputTokens += row.inputTokens ?? 0;
|
|
53
|
-
delta.outputTokens += row.outputTokens ?? 0;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
|
|
57
|
-
const deltas = new Map<string, UsageTotalDelta>();
|
|
58
|
-
for (const row of rows) {
|
|
59
|
-
addUsageDelta(deltas, row, "account", row.accountId);
|
|
60
|
-
addUsageDelta(deltas, row, "api_key", row.apiKeyId);
|
|
61
|
-
}
|
|
62
|
-
return [...deltas.values()];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Database boundary for durable telemetry writes and bounded retention cleanup.
|
|
67
|
-
* The observability buffer owns queueing and retry policy; this store owns table
|
|
68
|
-
* operations, lifetime aggregates, and retention.
|
|
69
|
-
*/
|
|
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;
|
|
72
|
-
|
|
73
|
-
/**
|
|
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.
|
|
84
|
-
*
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
.
|
|
142
|
-
.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
telemetryUsageTotals.
|
|
146
|
-
telemetryUsageTotals.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
1
|
+
import { appendFile, mkdir, readdir, rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
4
|
+
import { affectedRows, currentTimestamp, laterOf } from "../dialect";
|
|
5
|
+
import type { CartethyiaDatabase } from "../db-handle";
|
|
6
|
+
import {
|
|
7
|
+
telemetryEvents,
|
|
8
|
+
telemetryPayloads,
|
|
9
|
+
telemetryUsageTotals,
|
|
10
|
+
type TelemetryUsageIdentityType,
|
|
11
|
+
} from "../schema";
|
|
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";
|
|
15
|
+
|
|
16
|
+
interface UsageTotalDelta {
|
|
17
|
+
readonly tenantId: string;
|
|
18
|
+
readonly identityType: TelemetryUsageIdentityType;
|
|
19
|
+
readonly entityId: string;
|
|
20
|
+
requests: number;
|
|
21
|
+
errors: number;
|
|
22
|
+
inputTokens: number;
|
|
23
|
+
outputTokens: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function addUsageDelta(
|
|
27
|
+
deltas: Map<string, UsageTotalDelta>,
|
|
28
|
+
row: typeof telemetryEvents.$inferInsert,
|
|
29
|
+
identityType: TelemetryUsageIdentityType,
|
|
30
|
+
entityId: string | null | undefined,
|
|
31
|
+
): void {
|
|
32
|
+
if (entityId == null) return;
|
|
33
|
+
const key = `${row.tenantId}\0${identityType}\0${entityId}`;
|
|
34
|
+
let delta = deltas.get(key);
|
|
35
|
+
if (!delta) {
|
|
36
|
+
delta = {
|
|
37
|
+
tenantId: row.tenantId,
|
|
38
|
+
identityType,
|
|
39
|
+
entityId,
|
|
40
|
+
requests: 0,
|
|
41
|
+
errors: 0,
|
|
42
|
+
inputTokens: 0,
|
|
43
|
+
outputTokens: 0,
|
|
44
|
+
};
|
|
45
|
+
deltas.set(key, delta);
|
|
46
|
+
}
|
|
47
|
+
delta.requests += 1;
|
|
48
|
+
// Same definition as every read-side error count, including the HTTP-status
|
|
49
|
+
// exclusions (404/499/503/504): this rollup is durable, so a request counted here
|
|
50
|
+
// stays counted after its raw row is pruned and cannot be corrected later.
|
|
51
|
+
if (isGatewayError(row.status, row.httpStatus)) delta.errors += 1;
|
|
52
|
+
delta.inputTokens += row.inputTokens ?? 0;
|
|
53
|
+
delta.outputTokens += row.outputTokens ?? 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
|
|
57
|
+
const deltas = new Map<string, UsageTotalDelta>();
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
addUsageDelta(deltas, row, "account", row.accountId);
|
|
60
|
+
addUsageDelta(deltas, row, "api_key", row.apiKeyId);
|
|
61
|
+
}
|
|
62
|
+
return [...deltas.values()];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Database boundary for durable telemetry writes and bounded retention cleanup.
|
|
67
|
+
* The observability buffer owns queueing and retry policy; this store owns table
|
|
68
|
+
* operations, lifetime aggregates, and retention.
|
|
69
|
+
*/
|
|
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;
|
|
72
|
+
|
|
73
|
+
/**
|
|
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.
|
|
85
|
+
*/
|
|
86
|
+
async function pruneEventFiles(before: Date): Promise<void> {
|
|
87
|
+
const files = await readdir(TELEMETRY_EVENT_DIR).catch(() => [] as string[]);
|
|
88
|
+
const cutoffDay = before.toISOString().slice(0, 10);
|
|
89
|
+
for (const file of files) {
|
|
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 });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
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
|
+
*/
|
|
109
|
+
async function appendEvents(rows: readonly (typeof telemetryEvents.$inferInsert)[]): Promise<void> {
|
|
110
|
+
await mkdir(TELEMETRY_EVENT_DIR, { recursive: true });
|
|
111
|
+
const file = join(TELEMETRY_EVENT_DIR, dayEventFileName(utcDay(new Date())));
|
|
112
|
+
const newline = String.fromCharCode(10);
|
|
113
|
+
const body = rows.map((row) => JSON.stringify(row)).join(newline) + newline;
|
|
114
|
+
await appendFile(file, body, "utf8");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class DrizzleTelemetryStore {
|
|
118
|
+
constructor(private readonly db: CartethyiaDatabase) {}
|
|
119
|
+
|
|
120
|
+
/** Inserts events and their durable identity aggregates atomically. */
|
|
121
|
+
async insertEvents(rows: Array<typeof telemetryEvents.$inferInsert>): Promise<void> {
|
|
122
|
+
if (rows.length === 0) return;
|
|
123
|
+
// The file accepts anything, so a malformed row would be written and then
|
|
124
|
+
// retried forever without ever failing. Rejecting it here keeps the buffer's
|
|
125
|
+
// drop-and-count contract: one bad row fails the batch, once.
|
|
126
|
+
for (const row of rows) {
|
|
127
|
+
if (!UUID_RE.test(row.tenantId)) {
|
|
128
|
+
throw new Error("telemetry event rejected: tenantId is not a uuid");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const deltas = aggregateUsage(rows);
|
|
132
|
+
// The per-request row goes to an append-only file, not to Postgres. The
|
|
133
|
+
// database keeps only the durable aggregate below, so a request never
|
|
134
|
+
// adds a row to the largest table. Readers of recent requests read the
|
|
135
|
+
// file; readers of long-term totals read the aggregate.
|
|
136
|
+
await this.db.transaction(async (tx) => {
|
|
137
|
+
if (deltas.length === 0) return;
|
|
138
|
+
const now = new Date();
|
|
139
|
+
await tx
|
|
140
|
+
.insert(telemetryUsageTotals)
|
|
141
|
+
.values(deltas.map((delta) => ({ ...delta, lastUsedAt: now })))
|
|
142
|
+
.onConflictDoUpdate({
|
|
143
|
+
target: [
|
|
144
|
+
telemetryUsageTotals.tenantId,
|
|
145
|
+
telemetryUsageTotals.identityType,
|
|
146
|
+
telemetryUsageTotals.entityId,
|
|
147
|
+
],
|
|
148
|
+
set: {
|
|
149
|
+
requests: sql`${telemetryUsageTotals.requests} + excluded.requests`,
|
|
150
|
+
errors: sql`${telemetryUsageTotals.errors} + excluded.errors`,
|
|
151
|
+
inputTokens: sql`${telemetryUsageTotals.inputTokens} + excluded.input_tokens`,
|
|
152
|
+
outputTokens: sql`${telemetryUsageTotals.outputTokens} + excluded.output_tokens`,
|
|
153
|
+
lastUsedAt: laterOf(sql`${telemetryUsageTotals.lastUsedAt}`, sql`excluded.last_used_at`),
|
|
154
|
+
updatedAt: currentTimestamp(),
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// After the aggregate commits, so a rejected batch is retried once, not
|
|
159
|
+
// appended once per attempt. The file is the record of requests whose
|
|
160
|
+
// totals were actually counted.
|
|
161
|
+
await appendEvents(rows);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Inserts one redacted payload and returns its generated identity. */
|
|
165
|
+
async insertPayload(
|
|
166
|
+
row: typeof telemetryPayloads.$inferInsert,
|
|
167
|
+
): Promise<{ id: string; expiresAt: Date }> {
|
|
168
|
+
const [inserted] = await this.db
|
|
169
|
+
.insert(telemetryPayloads)
|
|
170
|
+
.values(row)
|
|
171
|
+
.returning({ id: telemetryPayloads.id, expiresAt: telemetryPayloads.expiresAt });
|
|
172
|
+
if (!inserted) throw new Error("payload capture insert returned no row");
|
|
173
|
+
return inserted;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Deletes at most one bounded batch of expired payloads in a single
|
|
178
|
+
* statement, so concurrent sweeps atomically claim disjoint rows and the
|
|
179
|
+
* returned count matches the rows actually deleted.
|
|
180
|
+
*/
|
|
181
|
+
async deleteExpiredPayloads(batchSize = 5000, before = new Date()): Promise<number> {
|
|
182
|
+
const result = await this.db.execute(sql`
|
|
183
|
+
DELETE FROM ${telemetryPayloads}
|
|
184
|
+
WHERE ${telemetryPayloads.id} IN (
|
|
185
|
+
SELECT ${telemetryPayloads.id} FROM ${telemetryPayloads}
|
|
186
|
+
WHERE ${telemetryPayloads.expiresAt} < ${before}
|
|
187
|
+
ORDER BY ${telemetryPayloads.expiresAt}
|
|
188
|
+
LIMIT ${batchSize}
|
|
189
|
+
)
|
|
190
|
+
`);
|
|
191
|
+
return affectedRows(result);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Deletes at most one bounded batch of aged events in a single statement, so
|
|
196
|
+
* concurrent sweeps atomically claim disjoint rows and the returned count
|
|
197
|
+
* matches the rows actually deleted. Ordered by `created_at`, which
|
|
198
|
+
* `idx_telemetry_created_at` backs.
|
|
199
|
+
*/
|
|
200
|
+
async deleteExpiredEvents(batchSize = 5000, before: Date): Promise<number> {
|
|
201
|
+
const result = await this.db.execute(sql`
|
|
202
|
+
DELETE FROM ${telemetryEvents}
|
|
203
|
+
WHERE ${telemetryEvents.id} IN (
|
|
204
|
+
SELECT ${telemetryEvents.id} FROM ${telemetryEvents}
|
|
205
|
+
WHERE ${telemetryEvents.createdAt} < ${before}
|
|
206
|
+
ORDER BY ${telemetryEvents.createdAt}
|
|
207
|
+
LIMIT ${batchSize}
|
|
208
|
+
)
|
|
209
|
+
`);
|
|
210
|
+
return affectedRows(result);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Deletes telemetry older than the supplied cutoff. Payloads follow their
|
|
215
|
+
* `expires_at` contract (15 minutes per row) rather than capture time.
|
|
216
|
+
*
|
|
217
|
+
* Events are pruned in bounded batches rather than one `DELETE`, because
|
|
218
|
+
* `telemetry_events` is the highest-volume table and a single statement
|
|
219
|
+
* covering every aged row grows without limit: it holds locks and WAL for the
|
|
220
|
+
* whole sweep, and on a deployment that has run for months it can exceed the
|
|
221
|
+
* pool's `statement_timeout` and be cancelled, so the sweep never converges
|
|
222
|
+
* and retention silently stops working. Each batch commits independently, so
|
|
223
|
+
* progress survives a cancel or a restart. The loop is bounded so one pass
|
|
224
|
+
* cannot run unbounded — a backlog drains over successive sweeps — mirroring
|
|
225
|
+
* the payload sweeper's `cleanupExpired`.
|
|
226
|
+
*/
|
|
227
|
+
async pruneTelemetry(before: Date): Promise<void> {
|
|
228
|
+
await pruneEventFiles(before);
|
|
229
|
+
const MAX_BATCHES_PER_SWEEP = 40;
|
|
230
|
+
for (let batch = 0; batch < MAX_BATCHES_PER_SWEEP; batch += 1) {
|
|
231
|
+
const deleted = await this.deleteExpiredEvents(5000, before);
|
|
232
|
+
if (deleted < 5000) break;
|
|
233
|
+
}
|
|
234
|
+
await this.deleteExpiredPayloads(5000, before);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -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.
|