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
|
@@ -14,7 +14,7 @@ import { parseCookieValue, SESSION_COOKIE_NAME, isCsrfValid } from "../security/
|
|
|
14
14
|
/**
|
|
15
15
|
* Stateless double-submit CSRF guard for unsafe `/console/api/*` mutations.
|
|
16
16
|
* Compares the `x-csrf-token` header against the readable `csrf_token`
|
|
17
|
-
* cookie — zero database I/O, so dashboard writes
|
|
17
|
+
* cookie — zero database I/O, so dashboard writes pay no token
|
|
18
18
|
* SELECT + UPDATE on top of the session lookup, and there is no token
|
|
19
19
|
* store whose outage could masquerade as an auth failure.
|
|
20
20
|
*/
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Everything here is files on disk, except one deliberate row-level action.
|
|
6
6
|
* Reclaiming a file is safe because the data it held is already past the point
|
|
7
|
-
* where the gateway would read it: payload frames expire
|
|
8
|
-
*
|
|
9
|
-
* query's scan bound. Deleting rows is a different decision with a different
|
|
7
|
+
* where the gateway would read it: payload frames expire after the tenant's
|
|
8
|
+
* chosen retention, and telemetry files past the retention window are outside
|
|
9
|
+
* every query's scan bound. Deleting rows is a different decision with a different
|
|
10
10
|
* blast radius, and the only row data the console offers purging is the health
|
|
11
11
|
* & error log — purely an operator's diagnostic trail with no request-path
|
|
12
12
|
* reader, unlike everything else in this panel.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
/** One measured category of on-disk data. */
|
|
20
20
|
export interface DiskUsageBucket {
|
|
21
21
|
/** Stable id, so the dashboard can key rows without matching on copy. */
|
|
22
|
-
readonly id: "
|
|
22
|
+
readonly id: "telemetry_events" | "telemetry_index" | "payload_frames";
|
|
23
23
|
readonly label: string;
|
|
24
24
|
readonly bytes: number;
|
|
25
25
|
readonly files: number;
|
|
@@ -35,8 +35,15 @@ export interface DiskUsageResponse {
|
|
|
35
35
|
readonly totalBytes: number;
|
|
36
36
|
/** Retention in days; telemetry older than this is outside every scan. */
|
|
37
37
|
readonly retentionDays: number;
|
|
38
|
-
/** Payload frames live this long before they are reclaimable. */
|
|
38
|
+
/** Payload frames live this long by default before they are reclaimable. */
|
|
39
39
|
readonly payloadRetentionMs: number;
|
|
40
|
+
/**
|
|
41
|
+
* The longest retention a tenant may have chosen. A frame is only counted as
|
|
42
|
+
* reclaimable once it is older than *this*, because a tenant who raised the
|
|
43
|
+
* window has frames the default would call dead while their rows still
|
|
44
|
+
* address them.
|
|
45
|
+
*/
|
|
46
|
+
readonly payloadRetentionMaxMs: number;
|
|
40
47
|
/**
|
|
41
48
|
* Stored health & error rows, for the purge row's label. Absent when the
|
|
42
49
|
* composition does not wire the health-event store (a reduced console),
|
|
@@ -45,7 +52,7 @@ export interface DiskUsageResponse {
|
|
|
45
52
|
readonly healthEventCount?: number;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
export type ReclaimAction = "payload_frames" | "telemetry_retention"
|
|
55
|
+
export type ReclaimAction = "payload_frames" | "telemetry_retention";
|
|
49
56
|
|
|
50
57
|
export interface ReclaimResponse {
|
|
51
58
|
readonly action: ReclaimAction;
|
|
@@ -35,11 +35,12 @@ export interface DataManagementConfig {
|
|
|
35
35
|
readonly retentionDays: number;
|
|
36
36
|
readonly payloadRetentionMs: number;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* The longest payload retention a tenant may have chosen, and therefore the
|
|
39
|
+
* only cutoff a frame file can be judged dead against without reading it.
|
|
40
|
+
* Defaults to `payloadRetentionMs` for callers that predate per-tenant
|
|
41
|
+
* retention, which is then the true ceiling in that deployment.
|
|
41
42
|
*/
|
|
42
|
-
readonly
|
|
43
|
+
readonly payloadRetentionMaxMs?: number;
|
|
43
44
|
/**
|
|
44
45
|
* The health & error log store, backing the console's only row-level purge.
|
|
45
46
|
* Absent (a reduced console composition) means the purge is not offered
|
|
@@ -79,9 +80,8 @@ async function scan(
|
|
|
79
80
|
|
|
80
81
|
const isIndex = (name: string): boolean => name.endsWith(".index.json");
|
|
81
82
|
const isHourly = (name: string): boolean => name.endsWith(".events.jsonl");
|
|
82
|
-
// Both extensions: old `.jsonb` files are still on disk and still counted.
|
|
83
83
|
const isFrame = (name: string): boolean =>
|
|
84
|
-
name.endsWith(".jsonl")
|
|
84
|
+
name.endsWith(".jsonl");
|
|
85
85
|
|
|
86
86
|
/** The UTC day a telemetry file name starts with, compared to a cutoff. */
|
|
87
87
|
function dayBefore(name: string, cutoff: number): boolean {
|
|
@@ -91,10 +91,19 @@ function dayBefore(name: string, cutoff: number): boolean {
|
|
|
91
91
|
|
|
92
92
|
export function createDataManagementOperations(config: DataManagementConfig) {
|
|
93
93
|
const payloadDir = (): string => payloadDirectory();
|
|
94
|
+
/**
|
|
95
|
+
* The only cutoff a frame file can be judged dead against without parsing it.
|
|
96
|
+
*
|
|
97
|
+
* A file's mtime says when it was last written, not what its frames' tenants
|
|
98
|
+
* chose — so a file written an hour ago can hold a frame with a week to live.
|
|
99
|
+
* The ceiling is therefore the bound, and the default retention is only used
|
|
100
|
+
* when no ceiling was configured.
|
|
101
|
+
*/
|
|
102
|
+
const payloadMax = (): number => config.payloadRetentionMaxMs ?? config.payloadRetentionMs;
|
|
94
103
|
|
|
95
104
|
async function usage(): Promise<DiskUsageResponse> {
|
|
96
105
|
const retentionCutoff = Date.now() - config.retentionDays * 24 * 60 * 60_000;
|
|
97
|
-
const payloadCutoff = Date.now() -
|
|
106
|
+
const payloadCutoff = Date.now() - payloadMax();
|
|
98
107
|
|
|
99
108
|
const [hourly, compacted, staleCompacted, frames, staleFrames, healthEventCount] = await Promise.all([
|
|
100
109
|
scan(config.telemetryDir, isHourly),
|
|
@@ -107,8 +116,8 @@ export function createDataManagementOperations(config: DataManagementConfig) {
|
|
|
107
116
|
|
|
108
117
|
const buckets: DiskUsageBucket[] = [
|
|
109
118
|
{
|
|
110
|
-
id: "
|
|
111
|
-
label: "
|
|
119
|
+
id: "telemetry_events",
|
|
120
|
+
label: "Request event files",
|
|
112
121
|
bytes: hourly.bytes,
|
|
113
122
|
files: hourly.files,
|
|
114
123
|
note: "Feeds the request list and detail drawer.",
|
|
@@ -116,7 +125,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
|
|
|
116
125
|
reclaimableFiles: null,
|
|
117
126
|
},
|
|
118
127
|
{
|
|
119
|
-
id: "
|
|
128
|
+
id: "telemetry_index",
|
|
120
129
|
label: "Day indexes",
|
|
121
130
|
bytes: compacted.bytes,
|
|
122
131
|
files: compacted.files,
|
|
@@ -131,7 +140,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
|
|
|
131
140
|
files: frames.files,
|
|
132
141
|
note: `Captured bodies, expiring ${Math.round(
|
|
133
142
|
config.payloadRetentionMs / 60_000,
|
|
134
|
-
)} min after write.`,
|
|
143
|
+
)} min after write by default.`,
|
|
135
144
|
reclaimableBytes: staleFrames.bytes,
|
|
136
145
|
reclaimableFiles: staleFrames.files,
|
|
137
146
|
},
|
|
@@ -142,6 +151,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
|
|
|
142
151
|
totalBytes: buckets.reduce((sum, bucket) => sum + bucket.bytes, 0),
|
|
143
152
|
retentionDays: config.retentionDays,
|
|
144
153
|
payloadRetentionMs: config.payloadRetentionMs,
|
|
154
|
+
payloadRetentionMaxMs: payloadMax(),
|
|
145
155
|
...(healthEventCount === undefined ? {} : { healthEventCount }),
|
|
146
156
|
};
|
|
147
157
|
}
|
|
@@ -160,7 +170,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
|
|
|
160
170
|
const now = new Date();
|
|
161
171
|
const deletedFiles = await prunePayloadFrames(
|
|
162
172
|
now,
|
|
163
|
-
new Date(now.getTime() -
|
|
173
|
+
new Date(now.getTime() - payloadMax()),
|
|
164
174
|
);
|
|
165
175
|
const after = await scan(payloadDir(), isFrame);
|
|
166
176
|
return {
|
|
@@ -178,12 +188,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
|
|
|
178
188
|
(isIndex(name) || isHourly(name)) && dayBefore(name, cutoff),
|
|
179
189
|
);
|
|
180
190
|
}
|
|
181
|
-
|
|
182
|
-
throw new ReclaimNotAllowedError(
|
|
183
|
-
"Hourly files are the only copy of their requests until compaction folds them up, so the console will not delete them.",
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
return unlinkWhere(action, config.telemetryDir, isHourly);
|
|
191
|
+
throw new ReclaimNotAllowedError(`Unknown reclaim action: ${action}`);
|
|
187
192
|
}
|
|
188
193
|
|
|
189
194
|
/** The one row-level deletion the console offers. */
|
|
@@ -36,11 +36,14 @@ import { createModelAbuseRoutes } from "./admin/model-abuse/routes";
|
|
|
36
36
|
import { DrizzleAuditReadStore } from "./audit/store";
|
|
37
37
|
import { createPerformanceRoutes } from "./observability/performance";
|
|
38
38
|
import { createUpdateRoutes } from "./updates/routes";
|
|
39
|
+
import { createRemoteSupportRoutes } from "./remote-support/routes";
|
|
40
|
+
import { createRemoteSupportService } from "./remote-support/service";
|
|
41
|
+
import { resolvePort } from "../config/runtime";
|
|
39
42
|
import { createBackupRoutes } from "./backup/routes";
|
|
40
43
|
import { BackupService } from "./backup/service";
|
|
41
44
|
import type { ConsoleCredentialService } from "./auth/credentials";
|
|
42
45
|
import { resolveTelemetryRetentionDays } from "../config/telemetry";
|
|
43
|
-
import { payloadRetentionMs } from "../observability/payload-capture";
|
|
46
|
+
import { MAX_PAYLOAD_RETENTION_MS, payloadRetentionMs } from "../observability/payload-capture";
|
|
44
47
|
import { TELEMETRY_EVENT_DIR } from "../observability/event-log";
|
|
45
48
|
|
|
46
49
|
import { InMemoryOAuthFlowStore, OAuthFlowStore } from "../providers/authentication/oauth-flow-store";
|
|
@@ -241,6 +244,10 @@ export function registerConsoleDomains(
|
|
|
241
244
|
accessResolver: ctx.accessResolver,
|
|
242
245
|
observabilityStore,
|
|
243
246
|
}));
|
|
247
|
+
console.use(createRemoteSupportRoutes({
|
|
248
|
+
accessResolver: ctx.accessResolver,
|
|
249
|
+
service: createRemoteSupportService({ port: resolvePort() }),
|
|
250
|
+
}));
|
|
244
251
|
console.use(createAuditRoutes({ store: auditReadStore, accessResolver: ctx.accessResolver }));
|
|
245
252
|
if (ctx.modelStrikes) {
|
|
246
253
|
console.use(createModelAbuseRoutes({
|
|
@@ -281,15 +288,13 @@ export function registerConsoleDomains(
|
|
|
281
288
|
// same SSRF-validated fetch as every other egress.
|
|
282
289
|
relayFetch: createValidatedFetch({ policy: resolveSsrfPolicy() }),
|
|
283
290
|
}));
|
|
284
|
-
console.use(createRuntimeSettingsRoutes({ store: runtimeSettingsStore, accessResolver: ctx.accessResolver, auditSink: ctx.auditRecorder }));
|
|
291
|
+
console.use(createRuntimeSettingsRoutes({ store: runtimeSettingsStore, accessResolver: ctx.accessResolver, auditSink: ctx.auditRecorder, snapshotInvalidator: ctx.routeSnapshotService }));
|
|
285
292
|
console.use(
|
|
286
293
|
createDataManagementRoutes({
|
|
287
294
|
telemetryDir: TELEMETRY_EVENT_DIR,
|
|
288
295
|
retentionDays: resolveTelemetryRetentionDays(),
|
|
289
296
|
payloadRetentionMs: payloadRetentionMs(),
|
|
290
|
-
|
|
291
|
-
// folds them up, so the console does not delete them on request.
|
|
292
|
-
allowHourlyReclaim: false,
|
|
297
|
+
payloadRetentionMaxMs: MAX_PAYLOAD_RETENTION_MS,
|
|
293
298
|
accessResolver: ctx.accessResolver,
|
|
294
299
|
auditSink: ctx.auditRecorder,
|
|
295
300
|
flusher: ctx.flushBuffers,
|
|
@@ -32,6 +32,7 @@ import { networkPools } from "../../persistence/schema";
|
|
|
32
32
|
import { subscribePoolHealth } from "../../network/pool-health-machine";
|
|
33
33
|
import { poolByteSnapshot } from "../../network/pool/byte-accounting";
|
|
34
34
|
import { consoleSseResponse, createConsoleSseStream } from "./sse";
|
|
35
|
+
import { subscribeRequestCompleted } from "../../observability/request-notify";
|
|
35
36
|
|
|
36
37
|
export interface LiveConfig {
|
|
37
38
|
readonly accessResolver: ConsoleAccessResolver;
|
|
@@ -87,6 +88,27 @@ function withPoolBytes<T extends { poolId: string; currentInflight?: number }>(
|
|
|
87
88
|
|
|
88
89
|
export function createLiveRoutes(config: LiveConfig): Elysia {
|
|
89
90
|
return new Elysia()
|
|
91
|
+
.get("/live/requests/stream", ({ request, set }) => {
|
|
92
|
+
try {
|
|
93
|
+
const access = config.accessResolver(request);
|
|
94
|
+
requireScope(access, "dashboard:read");
|
|
95
|
+
const tenantId = access?.tenantId;
|
|
96
|
+
if (!tenantId) return errorResponse(new Error("Tenant required"), set, "Live operation failed");
|
|
97
|
+
set.headers["content-type"] = "text/event-stream";
|
|
98
|
+
set.headers["cache-control"] = "no-cache, no-transform";
|
|
99
|
+
set.headers["connection"] = "keep-alive";
|
|
100
|
+
set.headers["x-accel-buffering"] = "no";
|
|
101
|
+
return consoleSseResponse(
|
|
102
|
+
createConsoleSseStream(request.signal, ({ send }) => {
|
|
103
|
+
return subscribeRequestCompleted((notice) => {
|
|
104
|
+
if (notice.tenantId === tenantId) send("request_completed", notice);
|
|
105
|
+
});
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return errorResponse(e, set, "Live operation failed");
|
|
110
|
+
}
|
|
111
|
+
})
|
|
90
112
|
.get("/live/in-flight", ({ request, set }) => {
|
|
91
113
|
try {
|
|
92
114
|
requireScope(config.accessResolver(request), "dashboard:read");
|
|
@@ -11,6 +11,7 @@ import { isGatewayError } from "../../observability/telemetry-status";
|
|
|
11
11
|
import { queryIndexedBuckets, type IndexedBucket } from "../../observability/event-day-index";
|
|
12
12
|
import {
|
|
13
13
|
LATENCY_BUCKETS_MS,
|
|
14
|
+
numeric,
|
|
14
15
|
percentileFromHistogram,
|
|
15
16
|
} from "../../observability/event-compaction";
|
|
16
17
|
import { cachedReadWithEtag, type CachedResult } from "../shared/read-through-cache";
|
|
@@ -198,9 +199,9 @@ const HEALTH_WINDOW_MS = 24 * 3_600_000;
|
|
|
198
199
|
const TOP_N = 50;
|
|
199
200
|
/**
|
|
200
201
|
* How long a parsed window is shared. A dashboard poll fires ~8 usage
|
|
201
|
-
* endpoints at once, and a slow parse
|
|
202
|
-
* endpoints that
|
|
203
|
-
*
|
|
202
|
+
* endpoints at once, and a slow parse outlives a 1.5 s window — so the
|
|
203
|
+
* endpoints that miss it each re-read and re-parse the same files, which is how
|
|
204
|
+
* one page load becomes several full parses of the retention window.
|
|
204
205
|
*/
|
|
205
206
|
const PERIOD_MEMO_TTL_MS = 5_000;
|
|
206
207
|
|
|
@@ -264,10 +265,20 @@ export function summarizeBucketPerformance(
|
|
|
264
265
|
): UsagePerformanceSummary {
|
|
265
266
|
let latencySum = 0;
|
|
266
267
|
let latencyCount = 0;
|
|
268
|
+
let resolveSum = 0;
|
|
269
|
+
let resolveCount = 0;
|
|
270
|
+
let latencyMaxMs: number | null = null;
|
|
267
271
|
const histogram: number[] = [];
|
|
268
272
|
for (const bucket of buckets) {
|
|
269
273
|
latencySum += bucket.latencySum;
|
|
270
274
|
latencyCount += bucket.latencyCount;
|
|
275
|
+
// Read defensively: an index written before these fields existed carries
|
|
276
|
+
// neither, and summing `undefined` poisons the whole period with NaN —
|
|
277
|
+
// including the days that do have the data.
|
|
278
|
+
resolveSum += numeric(bucket.resolveSumMs);
|
|
279
|
+
resolveCount += numeric(bucket.resolveCount);
|
|
280
|
+
if (bucket.latencyMaxMs !== null)
|
|
281
|
+
latencyMaxMs = latencyMaxMs === null ? bucket.latencyMaxMs : Math.max(latencyMaxMs, bucket.latencyMaxMs);
|
|
271
282
|
for (let index = 0; index < bucket.latencyHistogram.length; index += 1) {
|
|
272
283
|
histogram[index] = (histogram[index] ?? 0) + (bucket.latencyHistogram[index] ?? 0);
|
|
273
284
|
}
|
|
@@ -277,13 +288,17 @@ export function summarizeBucketPerformance(
|
|
|
277
288
|
? histogram
|
|
278
289
|
: new Array<number>(LATENCY_BUCKETS_MS.length + 1).fill(0);
|
|
279
290
|
return {
|
|
280
|
-
avgResolveMs: 0,
|
|
291
|
+
avgResolveMs: resolveCount > 0 ? resolveSum / resolveCount : 0,
|
|
281
292
|
avgDurationMs: latencyCount > 0 ? latencySum / latencyCount : 0,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
293
|
+
// `latencyMaxMs` is passed as the overflow answer: a percentile past the
|
|
294
|
+
// histogram's last edge is at most the slowest request actually recorded.
|
|
295
|
+
// Without it an overflow returned `null`, and `?? 0` turned a P95 of 40 s
|
|
296
|
+
// into "0 ms" — the slow tail reported as nothing at all.
|
|
297
|
+
latencyP50Ms: percentileFromHistogram(fill, 50, latencyMaxMs) ?? 0,
|
|
298
|
+
latencyP95Ms: percentileFromHistogram(fill, 95, latencyMaxMs) ?? 0,
|
|
299
|
+
latencyP99Ms: percentileFromHistogram(fill, 99, latencyMaxMs) ?? 0,
|
|
285
300
|
latencySampleCount: latencyCount,
|
|
286
|
-
resolveSampleCount:
|
|
301
|
+
resolveSampleCount: resolveCount,
|
|
287
302
|
};
|
|
288
303
|
}
|
|
289
304
|
|
|
@@ -340,9 +355,8 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
340
355
|
) {}
|
|
341
356
|
|
|
342
357
|
async health(tenantId: string): Promise<CachedResult<SystemHealthResponse>> {
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
// page load. Same 10 s TTL as `/system/usage`.
|
|
358
|
+
// Cached like its siblings: reading this directly re-parses 24 hourly files
|
|
359
|
+
// every page load. Same 10 s TTL as `/system/usage`.
|
|
346
360
|
return this.readThroughWithEtag(
|
|
347
361
|
this.usageCacheKey(tenantId, "24h", "health"),
|
|
348
362
|
() => this.computeHealth(tenantId),
|
|
@@ -424,8 +438,8 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
424
438
|
p99 = percentile(latencies, 0.99);
|
|
425
439
|
// Cached share of the prompt: cached/input, the same honest definition
|
|
426
440
|
// usageSummary uses, so the health card and the Usage page cannot
|
|
427
|
-
// disagree. (
|
|
428
|
-
//
|
|
441
|
+
// disagree. (Cached share, not prompt share: input/(input+output) reads
|
|
442
|
+
// ~99% and mislabels it a cache hit rate.)
|
|
429
443
|
cacheHitRate = inputTokens > 0 ? Math.round((cachedTokens / inputTokens) * 10000) / 100 : 0;
|
|
430
444
|
avgTokensPerSec = tokensPerSecCount > 0 ? tokensPerSecSum / tokensPerSecCount : 0;
|
|
431
445
|
} catch {
|
|
@@ -596,6 +610,15 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
596
610
|
let tokensPerSecCount = 0;
|
|
597
611
|
const statuses = new Map<number, number>();
|
|
598
612
|
for (const row of buckets) {
|
|
613
|
+
// Folded from the index's own per-status counts. These used to be left
|
|
614
|
+
// empty: the loop below never filled `statuses`, so every status chip and
|
|
615
|
+
// the requests-list filters read zero for any day whose events had been
|
|
616
|
+
// folded — while `requests` beside them read the real total.
|
|
617
|
+
for (const [status, count] of Object.entries(row.byStatus ?? {})) {
|
|
618
|
+
const code = Number(status);
|
|
619
|
+
if (!Number.isFinite(code)) continue;
|
|
620
|
+
statuses.set(code, (statuses.get(code) ?? 0) + count);
|
|
621
|
+
}
|
|
599
622
|
requests += row.requests;
|
|
600
623
|
inputTokens += row.inputTokens;
|
|
601
624
|
outputTokens += row.outputTokens;
|
|
@@ -983,9 +1006,9 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
|
|
|
983
1006
|
): Promise<TelemetryEventListPage> {
|
|
984
1007
|
const before = decodeDatedCursor(cursor);
|
|
985
1008
|
// The cursor becomes an `until` bound inside the query, so `limit` applies
|
|
986
|
-
// to every page and not only the first. Filtering after the read
|
|
987
|
-
//
|
|
988
|
-
//
|
|
1009
|
+
// to every page and not only the first. Filtering after the read would force
|
|
1010
|
+
// a cursor page to fetch the whole window to know where it ended, dropping
|
|
1011
|
+
// `limit` the moment a cursor appears.
|
|
989
1012
|
//
|
|
990
1013
|
// `until` is inclusive while the cursor is exclusive, so rows at the exact
|
|
991
1014
|
// cursor timestamp are still compared by id here — the cheap part of the
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* dashboard offers one breakdown tab per member (kept in sync by hand).
|
|
8
8
|
*
|
|
9
9
|
* This module is intentionally free of Elysia / node:crypto so the dashboard
|
|
10
|
-
* can re-export the tuple as a value.
|
|
11
|
-
*
|
|
10
|
+
* can re-export the tuple as a value. Reaching it through `./contracts` would
|
|
11
|
+
* import Elysia and — through
|
|
12
12
|
* `console/shared/errors` → `protocol/codecs` → `security/outbound-headers`
|
|
13
|
-
* — `node:crypto`, so the browser bundle
|
|
14
|
-
*
|
|
13
|
+
* — `node:crypto`, so the browser bundle would externalize a Node builtin and
|
|
14
|
+
* carry a slice of the backend graph for one string list.
|
|
15
15
|
*/
|
|
16
16
|
export const USAGE_DIMENSIONS = ["model", "provider", "key", "client", "client_ip"] as const;
|
|
17
17
|
|
|
@@ -46,13 +46,12 @@ export function endpointPathForProviderModel(
|
|
|
46
46
|
// Operator profile > the provider's own static catalog > the generic
|
|
47
47
|
// per-wire default.
|
|
48
48
|
//
|
|
49
|
-
// The path is returned as declared
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
// into a different address that may or may not be the one they meant.
|
|
49
|
+
// The path is returned as declared, never rewritten against the base URL.
|
|
50
|
+
// Stripping a segment the base already carried would paper over the mistake
|
|
51
|
+
// instead of surfacing it: a provider whose base is already versioned has to
|
|
52
|
+
// declare its own path, exactly as `cline` and `qianfan` do. A wrong URL
|
|
53
|
+
// should reach the operator as the wrong URL, not be quietly corrected into
|
|
54
|
+
// a different address that may or may not be the one they meant.
|
|
56
55
|
const endpointPath =
|
|
57
56
|
profile?.endpoint_paths_by_wire_family?.[wireFamily] ??
|
|
58
57
|
staticEndpoints?.[wireFamily] ??
|
|
@@ -60,9 +60,12 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
60
60
|
},
|
|
61
61
|
async listFlatModels(access: AccessDecision | undefined): Promise<readonly FlatModelCatalogEntry[]> {
|
|
62
62
|
const a = requireTenantAnyScope(access, MODEL_READ_SCOPES);
|
|
63
|
-
const providers = await
|
|
63
|
+
const [providers, allAccounts] = await Promise.all([
|
|
64
|
+
config.store.list(a.tenantId),
|
|
65
|
+
config.store.listAllAccounts(a.tenantId),
|
|
66
|
+
]);
|
|
64
67
|
const accountsByProvider = new Map<string, number>();
|
|
65
|
-
for (const account of
|
|
68
|
+
for (const account of allAccounts) {
|
|
66
69
|
if (account.status === "active")
|
|
67
70
|
accountsByProvider.set(account.providerId, (accountsByProvider.get(account.providerId) ?? 0) + 1);
|
|
68
71
|
}
|
|
@@ -470,8 +470,8 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
470
470
|
* uses, so a batch cannot accept a shape the single create rejects. The
|
|
471
471
|
* expensive per-account bookkeeping — the audit row and the routing snapshot
|
|
472
472
|
* invalidation that forces a 10-table catalog rebuild — is done ONCE for the
|
|
473
|
-
* whole batch instead of once per account,
|
|
474
|
-
* client-side loop
|
|
473
|
+
* whole batch instead of once per account, so cost does not scale linearly
|
|
474
|
+
* with paste size the way a client-side loop over single creates would.
|
|
475
475
|
*
|
|
476
476
|
* Email/label enrichment is deliberately NOT done here. Persisting the
|
|
477
477
|
* credential is local work (encrypt + hash + insert) and needs no upstream;
|
|
@@ -516,8 +516,8 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
|
|
|
516
516
|
if (created.account !== undefined) queueNewAccountCheckin(providerId, created.account);
|
|
517
517
|
}
|
|
518
518
|
|
|
519
|
-
// One audit row per
|
|
520
|
-
//
|
|
519
|
+
// One audit row per batch, written after the batch lands rather than
|
|
520
|
+
// awaited per account inside the request path.
|
|
521
521
|
if (result.created > 0) {
|
|
522
522
|
await config.auditSink?.record({
|
|
523
523
|
access: a,
|
|
@@ -359,11 +359,9 @@ export class PublicModelCatalogStore {
|
|
|
359
359
|
/**
|
|
360
360
|
* Lists the models this key may see.
|
|
361
361
|
*
|
|
362
|
-
* `modelPrefix` is accepted and
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
* prefix could never add a row, and dropping it changes nothing. The
|
|
366
|
-
* parameter is kept so the call sites and the auth shape stay stable; if a
|
|
362
|
+
* `modelPrefix` is accepted and has NO effect on the result — a prefix
|
|
363
|
+
* disjunct fed into `isModelAllowed(...)` on its own could never add a row.
|
|
364
|
+
* The parameter is kept so the call sites and the auth shape stay stable; if a
|
|
367
365
|
* real prefix filter is wanted later it needs to be an AND, not the OR it
|
|
368
366
|
* was written as.
|
|
369
367
|
*/
|
|
@@ -449,12 +447,11 @@ export class PublicModelCatalogStore {
|
|
|
449
447
|
tenantId === null ? row.providerRequiresAccount === false : activeProviderIds.has(row.providerId);
|
|
450
448
|
if (!listed) continue;
|
|
451
449
|
const qualified = `${row.providerId}/${row.modelId}`;
|
|
452
|
-
// The prefix test is deliberately gone from this path:
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
// arrays they materialized.
|
|
450
|
+
// The prefix test is deliberately gone from this path: `(prefixMatch ||
|
|
451
|
+
// isAllowed)` followed by `isAllowed` reduces to `isAllowed` alone — the
|
|
452
|
+
// prefix disjunct could never change the outcome, only cost a second full
|
|
453
|
+
// pass over the catalog. Collapsing the four chained filters into one loop
|
|
454
|
+
// also drops the three intermediate arrays they materialized.
|
|
458
455
|
if (!isModelAllowed(snapshot, row.modelId, row.providerId)) continue;
|
|
459
456
|
if (shadowsAliasOrCombo(row, aliasTargets, comboNames, snapshot)) continue;
|
|
460
457
|
const capabilities = normalizeModalities(row.modalities);
|