cartethyia 2.5.9 → 2.6.0
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 +15 -42
- package/README.md +11 -10
- package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
- package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
- package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
- package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
- package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
- package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
- package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
- package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
- package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
- package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
- package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
- package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
- package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
- package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
- package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
- package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
- package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
- package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
- package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
- package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
- package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
- package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
- package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
- package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
- package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
- package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
- package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
- package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
- package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
- package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
- package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
- package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
- package/dist/dashboard/assets/useMutation-EDFRkn1i.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 +51 -51
- package/migrations/README.md +52 -52
- package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
- package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
- package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/package.json +96 -97
- package/scripts/cli/admin.ts +126 -125
- package/scripts/cli/autostart.ts +237 -237
- package/scripts/cli/background.ts +120 -110
- package/scripts/cli/bin/carte.cjs +8 -8
- package/scripts/cli/bin/cartethyia.cjs +167 -167
- package/scripts/cli/console-menu.ts +308 -275
- package/scripts/cli/controller.ts +145 -0
- package/scripts/cli/doctor.ts +1 -1
- package/scripts/cli/env.ts +99 -99
- package/scripts/cli/gateway.ts +264 -261
- package/scripts/{health.ts → cli/health.ts} +4 -4
- package/scripts/cli/instance.ts +234 -224
- package/scripts/cli/main.ts +188 -164
- package/scripts/cli/platform.ts +64 -64
- package/scripts/cli/prompt.ts +275 -107
- package/scripts/cli/services.ts +88 -88
- package/scripts/cli/setup.ts +445 -445
- package/scripts/cli/start.ts +85 -82
- package/scripts/cli/startup-signal.ts +23 -23
- package/scripts/cli/telemetry.ts +70 -0
- package/scripts/cli/tray/index.ts +263 -263
- package/scripts/cli/tray/windows.ps1 +107 -107
- package/scripts/cli/tray.ts +192 -208
- package/scripts/cli/update.ts +39 -39
- package/scripts/cli/version.ts +67 -67
- package/scripts/cli/wordmark.ts +58 -58
- package/src/boot/app.ts +16 -1
- package/src/boot/dependencies.ts +128 -25
- package/src/config/package-paths.ts +24 -0
- package/src/config/resources.ts +137 -0
- package/src/config/runtime.ts +133 -76
- package/src/config/spec.ts +30 -29
- package/src/config/telemetry.ts +0 -25
- package/src/console/api-keys/bucket-spend.ts +14 -17
- package/src/console/api-keys/contracts.ts +410 -410
- package/src/console/api-keys/routes.ts +12 -0
- package/src/console/audit/store.ts +2 -3
- package/src/console/auth/setup-service.ts +8 -1
- package/src/console/backup/routes.ts +7 -0
- package/src/console/backup/service.ts +463 -463
- package/src/console/backup/store.ts +16 -11
- package/src/console/backup/validate.ts +1 -4
- package/src/console/cli-tools/fs-ops.ts +18 -2
- package/src/console/data-management/contracts.ts +1 -8
- package/src/console/data-management/operations.ts +61 -48
- package/src/console/data-management/routes.ts +4 -2
- package/src/console/domain-registration.ts +25 -3
- package/src/console/inferhub/client.ts +139 -0
- package/src/console/inferhub/key-store.ts +129 -0
- package/src/console/inferhub/normalize.ts +347 -0
- package/src/console/inferhub/routes.ts +411 -0
- package/src/console/inferhub/store.ts +109 -0
- package/src/console/inferhub/types.ts +183 -0
- package/src/console/observability/contracts.ts +2 -41
- package/src/console/observability/store.ts +176 -371
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/store.ts +23 -36
- package/src/console/quota/account-quota-shared.ts +52 -52
- package/src/console/quota/account-quota-tenant.ts +16 -24
- package/src/console/quota/refresh.ts +441 -441
- package/src/console/remote-support/service.ts +270 -114
- package/src/console/routing/pools/import-parse.ts +206 -206
- package/src/console/routing/pools/operations.ts +543 -543
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -181
- package/src/console/routing/pools/store.ts +1 -1
- package/src/console/settings/contracts.ts +42 -40
- package/src/console/settings/store.ts +25 -25
- package/src/console/share/share-stats.ts +22 -4
- package/src/console/share/share-usage.ts +30 -4
- package/src/console/shared/cursor.ts +15 -0
- package/src/console/updates/repository-updates.ts +40 -39
- package/src/console/updates/routes.ts +12 -0
- package/src/main.ts +13 -5
- package/src/network/pool/resolver.ts +1104 -1104
- package/src/observability/client-name.ts +22 -0
- package/src/observability/event-log.ts +5 -29
- package/src/observability/event-query.ts +690 -0
- package/src/observability/log-ring.ts +26 -2
- package/src/observability/log-sanitize.ts +118 -0
- package/src/observability/logger.ts +17 -13
- package/src/observability/metrics.ts +35 -0
- package/src/observability/payload-capture.ts +114 -41
- package/src/observability/payload-store.ts +249 -24
- package/src/observability/raw-event-writer.ts +118 -0
- package/src/observability/runtime-metrics.ts +71 -1
- package/src/observability/telemetry-buffer.ts +100 -17
- package/src/observability/telemetry-reconciliation.ts +417 -0
- package/src/persistence/boot.ts +20 -9
- package/src/persistence/db-pg.ts +13 -0
- package/src/persistence/db-sqlite.ts +569 -497
- package/src/persistence/dialect.ts +3 -2
- package/src/persistence/migrate.ts +181 -181
- package/src/persistence/schema.ts +1496 -1406
- package/src/persistence/shared-cache.ts +5 -1
- package/src/persistence/stores/api-key-store.ts +581 -581
- package/src/persistence/stores/telemetry-store.ts +124 -63
- package/src/protocol/reasoning-text.ts +30 -0
- package/src/protocol/registry.ts +2 -0
- package/src/protocol/response/chat.ts +33 -7
- package/src/protocol/response/codex.ts +14 -2
- package/src/protocol/response/messages.ts +5 -2
- package/src/providers/authentication/oauth-flow-store.ts +3 -10
- package/src/providers/bundled-discovery-providers.ts +122 -122
- package/src/providers/default-registry.ts +14 -0
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +43 -7
- package/src/providers/discovery/probing-service.ts +70 -32
- package/src/providers/integrations/antigravity/antigravity.ts +9 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
- package/src/providers/integrations/buddy/capabilities.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
- package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
- package/src/providers/integrations/claude/claude-quota.ts +2 -2
- package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
- package/src/providers/integrations/claude/claude.ts +3 -4
- package/src/providers/integrations/cline/cline.ts +94 -8
- package/src/providers/integrations/codex/codex.ts +61 -37
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
- package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
- package/src/providers/integrations/cursor/cursor-request.ts +11 -0
- package/src/providers/integrations/cursor/cursor.ts +29 -17
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
- package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
- package/src/providers/integrations/github/github.ts +3 -1
- package/src/providers/integrations/grok/grok-quota.ts +279 -279
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
- package/src/providers/integrations/hermes.ts +7 -1
- package/src/providers/integrations/inferhub.ts +201 -73
- package/src/providers/integrations/kilo/kilo.ts +152 -152
- package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
- package/src/providers/integrations/opencode/provider.ts +2 -0
- package/src/providers/integrations/openrouter/provider.ts +129 -129
- package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
- package/src/providers/integrations/trae/trae-headers.ts +17 -48
- package/src/providers/integrations/trae/trae-oauth.ts +4 -26
- package/src/providers/integrations/trae/trae-quota.ts +389 -384
- package/src/providers/integrations/trae/trae-realm.ts +15 -2
- package/src/providers/integrations/trae/trae.ts +22 -24
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/account-health-service.ts +605 -279
- package/src/providers/provider-metadata.ts +12 -1
- package/src/providers/provider-model-definition.ts +373 -373
- package/src/providers/provider-probe.ts +3 -0
- package/src/providers/quota/quota-contracts.ts +3 -7
- package/src/providers/search/search-provider.ts +2 -6
- package/src/security/admission/contracts.ts +10 -0
- package/src/security/admission/in-memory-store.ts +89 -14
- package/src/security/admission/lua.ts +191 -182
- package/src/security/admission/redis-store.ts +30 -0
- package/src/security/admission/service.ts +18 -0
- package/src/security/api-key-auth-cache.ts +96 -0
- package/src/security/api-key-auth.ts +11 -100
- package/src/security/client-router-fingerprint.ts +3 -7
- package/src/security/ip-boundary.ts +7 -3
- package/src/shared/coerce.ts +67 -0
- package/src/shared/hash.ts +6 -0
- package/src/shared/time.ts +7 -0
- package/src/transport/dispatch/attempt-finalize.ts +26 -138
- package/src/transport/dispatch/attempt-loop.ts +103 -81
- package/src/transport/dispatch/capture-policy.ts +78 -0
- package/src/transport/dispatch/proxy-request.ts +4 -2
- package/src/transport/dispatch/streaming/encoder.ts +111 -5
- package/src/transport/dispatch/streaming/stream.ts +19 -2
- package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
- package/src/transport/failure-policy.ts +27 -0
- package/src/transport/guards/abuse-guard.ts +2 -2
- package/src/transport/guards/auth-guard.ts +17 -0
- package/src/transport/guards/body-policy.ts +3 -2
- package/src/transport/guards/concurrency-guard.ts +89 -13
- package/src/transport/guards/early-rejection-capture.ts +43 -3
- package/src/transport/guards/error-lifecycle.ts +10 -2
- package/src/transport/guards/guard-errors.ts +23 -0
- package/src/transport/guards/headroom-guard.ts +41 -7
- package/src/transport/guards/pipeline.ts +24 -0
- package/src/transport/guards/request-context.ts +2 -1
- package/src/transport/routing/admission.ts +51 -9
- package/src/transport/routing/bounded-lru.ts +49 -0
- package/src/transport/routing/candidate-index.ts +88 -0
- package/src/transport/routing/reservations.ts +121 -7
- package/src/transport/routing/round-robin.ts +10 -22
- package/src/transport/routing/route-catalog.ts +26 -13
- package/src/transport/routing/route-model.ts +22 -2
- package/src/transport/routing/router.ts +53 -31
- package/src/transport/surface/chat/encode.ts +2 -3
- package/src/transport/surface/chat/parse.ts +2 -1
- package/src/transport/surface/dialects.ts +11 -3
- package/src/transport/surface/messages/encode.ts +2 -1
- package/src/transport/surface/messages/parse.ts +3 -1
- package/src/transport/surface/messages/stream.ts +2 -6
- package/src/workers/maintenance-lease.ts +200 -0
- package/src/workers/scheduler.ts +101 -1
- package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
- package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
- package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
- package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/scripts/build/aot.ts +0 -143
- package/scripts/build/build.ts +0 -24
- package/scripts/build/compile.ts +0 -88
- package/scripts/build/restart.ts +0 -120
- package/scripts/build/start.ts +0 -25
- package/src/observability/event-compaction.ts +0 -317
- package/src/observability/event-day-index.ts +0 -440
package/scripts/build/restart.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* restart.ts — rebuild and restart the compiled gateway gracefully.
|
|
4
|
-
*
|
|
5
|
-
* Why this exists: a hard kill (`taskkill /F`, `kill -9`) truncates every
|
|
6
|
-
* in-flight response. On Linux the process handles SIGTERM/SIGINT and drains,
|
|
7
|
-
* but Windows has no deliverable catchable signal — `process.kill(pid,
|
|
8
|
-
* "SIGTERM")` of a console-less Bun process runs no JS handler (measured). So
|
|
9
|
-
* this script stops the running instance through the drain endpoint when
|
|
10
|
-
* `CARTETHYIA_DRAIN_TOKEN` is set (loopback + `x-drain-token`), and only falls
|
|
11
|
-
* back to a hard kill when no token is configured.
|
|
12
|
-
*
|
|
13
|
-
* Env:
|
|
14
|
-
* CARTETHYIA_DRAIN_TOKEN token for POST /admin/drain (required for graceful stop)
|
|
15
|
-
* PORT gateway port (default 12800)
|
|
16
|
-
* DRAIN_WAIT_MS how long to wait for the drain to finish (default 30000)
|
|
17
|
-
*/
|
|
18
|
-
import { compiledBinaryPath } from "./compile";
|
|
19
|
-
|
|
20
|
-
function intEnv(name: string, fallback: number): number {
|
|
21
|
-
const raw = process.env[name];
|
|
22
|
-
if (raw === undefined || raw.trim() === "") return fallback;
|
|
23
|
-
const value = Number(raw);
|
|
24
|
-
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : fallback;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const PORT = intEnv("PORT", 12800);
|
|
28
|
-
const DRAIN_WAIT_MS = intEnv("DRAIN_WAIT_MS", 30_000);
|
|
29
|
-
|
|
30
|
-
/** PIDs currently listening on `port` (best-effort, platform-specific). */
|
|
31
|
-
async function listeningPids(port: number): Promise<number[]> {
|
|
32
|
-
try {
|
|
33
|
-
if (process.platform === "win32") {
|
|
34
|
-
const proc = Bun.spawnSync(["netstat", "-ano", "-p", "TCP"]);
|
|
35
|
-
const out = new TextDecoder().decode(proc.stdout);
|
|
36
|
-
const pids = new Set<number>();
|
|
37
|
-
for (const line of out.split(/\r?\n/)) {
|
|
38
|
-
if (!line.includes(`:${port} `) || !line.includes("LISTENING")) continue;
|
|
39
|
-
const pid = Number(line.trim().split(/\s+/).pop());
|
|
40
|
-
if (Number.isInteger(pid) && pid > 0) pids.add(pid);
|
|
41
|
-
}
|
|
42
|
-
return [...pids];
|
|
43
|
-
}
|
|
44
|
-
const proc = Bun.spawnSync(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
|
|
45
|
-
return new TextDecoder()
|
|
46
|
-
.decode(proc.stdout)
|
|
47
|
-
.split(/\s+/)
|
|
48
|
-
.map(Number)
|
|
49
|
-
.filter((n) => Number.isInteger(n) && n > 0);
|
|
50
|
-
} catch {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function waitForPortFree(port: number, timeoutMs: number): Promise<boolean> {
|
|
56
|
-
const deadline = Date.now() + timeoutMs;
|
|
57
|
-
while (Date.now() < deadline) {
|
|
58
|
-
if ((await listeningPids(port)).length === 0) return true;
|
|
59
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
60
|
-
}
|
|
61
|
-
return (await listeningPids(port)).length === 0;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function stopGracefully(): Promise<boolean> {
|
|
65
|
-
const token = process.env.CARTETHYIA_DRAIN_TOKEN?.trim();
|
|
66
|
-
if (token === undefined || token.length === 0) {
|
|
67
|
-
console.warn("[restart] CARTETHYIA_DRAIN_TOKEN unset — cannot drain gracefully");
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
const response = await fetch(`http://127.0.0.1:${PORT}/admin/drain`, {
|
|
72
|
-
method: "POST",
|
|
73
|
-
headers: { "x-drain-token": token },
|
|
74
|
-
});
|
|
75
|
-
if (!response.ok) {
|
|
76
|
-
console.warn(`[restart] drain request returned HTTP ${response.status}`);
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
console.log("[restart] drain acknowledged; waiting for the process to exit");
|
|
80
|
-
return await waitForPortFree(PORT, DRAIN_WAIT_MS);
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.warn(`[restart] drain request failed: ${(error as Error).message}`);
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function hardKill(): Promise<void> {
|
|
88
|
-
const pids = await listeningPids(PORT);
|
|
89
|
-
if (pids.length === 0) return;
|
|
90
|
-
console.warn(`[restart] force-killing pid(s) ${pids.join(", ")} on :${PORT}`);
|
|
91
|
-
for (const pid of pids) {
|
|
92
|
-
try {
|
|
93
|
-
process.kill(pid, "SIGKILL");
|
|
94
|
-
} catch {
|
|
95
|
-
// Already gone.
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
await waitForPortFree(PORT, 5_000);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function main(): Promise<number> {
|
|
102
|
-
const graceful = await stopGracefully();
|
|
103
|
-
if (!graceful) await hardKill();
|
|
104
|
-
|
|
105
|
-
console.log("[restart] building");
|
|
106
|
-
const build = Bun.spawnSync(["bun", "run", "build"], { stdout: "inherit", stderr: "inherit" });
|
|
107
|
-
if (build.exitCode !== 0) {
|
|
108
|
-
console.error("[restart] build failed");
|
|
109
|
-
return build.exitCode ?? 1;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const binary = compiledBinaryPath();
|
|
113
|
-
console.log(`[restart] starting ${binary}`);
|
|
114
|
-
const child = Bun.spawn([binary], { stdin: "ignore", stdout: "inherit", stderr: "inherit" });
|
|
115
|
-
return await child.exited;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (import.meta.main) {
|
|
119
|
-
process.exit(await main());
|
|
120
|
-
}
|
package/scripts/build/start.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/** Runs the compiled binary from the repository root. */
|
|
2
|
-
import { compiledBinaryPath } from "./compile";
|
|
3
|
-
|
|
4
|
-
/** Runs the compiled binary with inherited stdio; resolves to its exit code. */
|
|
5
|
-
export async function startBinary(): Promise<number> {
|
|
6
|
-
const binary = compiledBinaryPath();
|
|
7
|
-
if (!(await Bun.file(binary).exists())) {
|
|
8
|
-
throw new Error(`${binary} is missing; run bun run build first`);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
process.env.NODE_ENV ??= "production";
|
|
12
|
-
process.env.CARTETHYIA_SERVER_MAX_BODY_BYTES ??= String(8 * 1024 * 1024 * 1024);
|
|
13
|
-
|
|
14
|
-
const child = Bun.spawn([binary], {
|
|
15
|
-
env: process.env,
|
|
16
|
-
stdin: "inherit",
|
|
17
|
-
stdout: "inherit",
|
|
18
|
-
stderr: "inherit",
|
|
19
|
-
});
|
|
20
|
-
return await child.exited;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (import.meta.main) {
|
|
24
|
-
process.exit(await startBinary());
|
|
25
|
-
}
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bucket primitives for the per-day telemetry index.
|
|
3
|
-
*
|
|
4
|
-
* Each day's events are folded into a `CompactedBucket` via `accumulate()` and
|
|
5
|
-
* stored as a `.index.json` file alongside the source `.events.jsonl`. Buckets
|
|
6
|
-
* are pure accumulators: every field is a running sum, min, or max — nothing
|
|
7
|
-
* is averaged or estimated away. `mergeBucketsInternal` adds two buckets so
|
|
8
|
-
* partial indexes can be merged without re-reading source files.
|
|
9
|
-
*/
|
|
10
|
-
import type { TelemetryEventRecord } from "./event-log";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Histogram bucket edges in milliseconds.
|
|
14
|
-
*
|
|
15
|
-
* Fixed rather than adaptive: a reader that wants P50/P95/P99 has to be able to
|
|
16
|
-
* interpret buckets it did not write, and an adaptive layout would have to be
|
|
17
|
-
* stored alongside every row. These follow the gateway's own latency
|
|
18
|
-
* histogram, so a compacted period and a live one read comparably.
|
|
19
|
-
*/
|
|
20
|
-
export const LATENCY_BUCKETS_MS = [
|
|
21
|
-
10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,
|
|
22
|
-
] as const;
|
|
23
|
-
|
|
24
|
-
export interface CompactedBucket {
|
|
25
|
-
readonly requests: number;
|
|
26
|
-
readonly inputTokens: number;
|
|
27
|
-
readonly cachedTokens: number;
|
|
28
|
-
readonly outputTokens: number;
|
|
29
|
-
readonly reasoningTokens: number;
|
|
30
|
-
readonly estimatedCostUsd: number;
|
|
31
|
-
readonly creditUsed: number;
|
|
32
|
-
readonly succeeded: number;
|
|
33
|
-
readonly failed: number;
|
|
34
|
-
readonly cancelled: number;
|
|
35
|
-
readonly truncated: number;
|
|
36
|
-
readonly latencySum: number;
|
|
37
|
-
readonly latencyCount: number;
|
|
38
|
-
readonly latencyMinMs: number | null;
|
|
39
|
-
readonly latencyMaxMs: number | null;
|
|
40
|
-
/** Counts per fixed latency bucket; `+Inf` is the last, overflow, bucket. */
|
|
41
|
-
readonly latencyHistogram: readonly number[];
|
|
42
|
-
readonly tokensPerSecSum: number;
|
|
43
|
-
readonly tokensPerSecCount: number;
|
|
44
|
-
/** Requests whose cost could not be priced. */
|
|
45
|
-
readonly unpriced: number;
|
|
46
|
-
/**
|
|
47
|
-
* Requests per breakdown dimension, kept so the Usage breakdown survives the
|
|
48
|
-
* source files being deleted.
|
|
49
|
-
*
|
|
50
|
-
* Only the *counts* are kept, never the events: a day's breakdown asks how
|
|
51
|
-
* many requests went to a model, not which ones. That is what makes this
|
|
52
|
-
* small enough to keep — a full event is ~1KB, a count is a few bytes.
|
|
53
|
-
*/
|
|
54
|
-
readonly byModel: Readonly<Record<string, number>>;
|
|
55
|
-
readonly byProvider: Readonly<Record<string, number>>;
|
|
56
|
-
readonly byClient: Readonly<Record<string, number>>;
|
|
57
|
-
readonly byClientIp: Readonly<Record<string, number>>;
|
|
58
|
-
/**
|
|
59
|
-
* Requests per HTTP status, keyed by the status as a decimal string.
|
|
60
|
-
*
|
|
61
|
-
* Kept as counts for the same reason the other dimensions are: the Usage
|
|
62
|
-
* summary's status chips and the request-list filters read these, and without
|
|
63
|
-
* them every chip reads zero once the day's events have been folded — the
|
|
64
|
-
* numbers were on the source events, which no longer exist.
|
|
65
|
-
*/
|
|
66
|
-
readonly byStatus: Readonly<Record<string, number>>;
|
|
67
|
-
/**
|
|
68
|
-
* Sum and count of gateway routing delay, so `avgResolveMs` survives
|
|
69
|
-
* compaction. Kept as a sum rather than an average of averages for the same
|
|
70
|
-
* reason the latency sum is: an average of per-file averages drifts as soon
|
|
71
|
-
* as the files hold different request counts.
|
|
72
|
-
*/
|
|
73
|
-
readonly resolveSumMs: number;
|
|
74
|
-
readonly resolveCount: number;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* A number from a value that may be a numeric string; 0 when unusable.
|
|
79
|
-
*
|
|
80
|
-
* Exported so readers of a compacted bucket coerce its fields the same way the
|
|
81
|
-
* fold does: a second local copy of "is this a number" is how one caller ends
|
|
82
|
-
* up summing `undefined` into NaN while another reads it as zero.
|
|
83
|
-
*/
|
|
84
|
-
export function numeric(value: unknown): number {
|
|
85
|
-
const asNumber = typeof value === "number" ? value : Number(value);
|
|
86
|
-
return Number.isFinite(asNumber) ? asNumber : 0;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* The two fields added after the first index format, read defensively.
|
|
91
|
-
*
|
|
92
|
-
* An index file written before they existed carries neither, and `undefined +
|
|
93
|
-
0` is `NaN` — which then propagates straight through every later fold and
|
|
94
|
-
* merge, so one old day turns the whole period's `avgResolveMs` into `NaN`.
|
|
95
|
-
* Reading them as 0/{} is the same answer an empty day gives, and a folded
|
|
96
|
-
* day that predates the field genuinely has no resolve measurement to report.
|
|
97
|
-
*/
|
|
98
|
-
function readResolveSum(bucket: CompactedBucket): number {
|
|
99
|
-
return numeric(bucket.resolveSumMs);
|
|
100
|
-
}
|
|
101
|
-
function readResolveCount(bucket: CompactedBucket): number {
|
|
102
|
-
return numeric(bucket.resolveCount);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** Adds one occurrence to a count map, ignoring an absent key. */
|
|
106
|
-
function bump(
|
|
107
|
-
counts: Readonly<Record<string, number>> | undefined,
|
|
108
|
-
key: string | null | undefined,
|
|
109
|
-
): Record<string, number> {
|
|
110
|
-
const out: Record<string, number> = { ...(counts ?? {}) };
|
|
111
|
-
const name = typeof key === "string" ? key.trim() : "";
|
|
112
|
-
if (name !== "") out[name] = (out[name] ?? 0) + 1;
|
|
113
|
-
return out;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Adds two count maps. */
|
|
117
|
-
function mergeCounts(
|
|
118
|
-
a: Readonly<Record<string, number>> | undefined,
|
|
119
|
-
b: Readonly<Record<string, number>> | undefined,
|
|
120
|
-
): Record<string, number> {
|
|
121
|
-
const out: Record<string, number> = { ...(a ?? {}) };
|
|
122
|
-
for (const [key, value] of Object.entries(b ?? {})) out[key] = (out[key] ?? 0) + value;
|
|
123
|
-
return out;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function emptyBucket(): CompactedBucket {
|
|
127
|
-
return {
|
|
128
|
-
byModel: {},
|
|
129
|
-
byProvider: {},
|
|
130
|
-
byClient: {},
|
|
131
|
-
byClientIp: {},
|
|
132
|
-
byStatus: {},
|
|
133
|
-
resolveSumMs: 0,
|
|
134
|
-
resolveCount: 0,
|
|
135
|
-
requests: 0,
|
|
136
|
-
inputTokens: 0,
|
|
137
|
-
cachedTokens: 0,
|
|
138
|
-
outputTokens: 0,
|
|
139
|
-
reasoningTokens: 0,
|
|
140
|
-
estimatedCostUsd: 0,
|
|
141
|
-
creditUsed: 0,
|
|
142
|
-
succeeded: 0,
|
|
143
|
-
failed: 0,
|
|
144
|
-
cancelled: 0,
|
|
145
|
-
truncated: 0,
|
|
146
|
-
latencySum: 0,
|
|
147
|
-
latencyCount: 0,
|
|
148
|
-
latencyMinMs: null,
|
|
149
|
-
latencyMaxMs: null,
|
|
150
|
-
latencyHistogram: new Array<number>(LATENCY_BUCKETS_MS.length + 1).fill(0),
|
|
151
|
-
tokensPerSecSum: 0,
|
|
152
|
-
tokensPerSecCount: 0,
|
|
153
|
-
unpriced: 0,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function bucketIndex(latencyMs: number): number {
|
|
158
|
-
for (let index = 0; index < LATENCY_BUCKETS_MS.length; index += 1) {
|
|
159
|
-
if (latencyMs <= LATENCY_BUCKETS_MS[index]!) return index;
|
|
160
|
-
}
|
|
161
|
-
return LATENCY_BUCKETS_MS.length;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Folds one request row into a bucket.
|
|
166
|
-
*
|
|
167
|
-
* Every field is a running sum or a min/max, never an average of averages: an
|
|
168
|
-
* average of per-file averages would drift from the true mean as soon as the
|
|
169
|
-
* files held different request counts, and the whole point of compaction is
|
|
170
|
-
* that the totals survive intact.
|
|
171
|
-
*/
|
|
172
|
-
export function accumulate(
|
|
173
|
-
into: CompactedBucket,
|
|
174
|
-
event: TelemetryEventRecord,
|
|
175
|
-
): CompactedBucket {
|
|
176
|
-
const histogram = [...into.latencyHistogram];
|
|
177
|
-
const latency = event.latencyMs;
|
|
178
|
-
if (latency !== null && Number.isFinite(latency)) {
|
|
179
|
-
histogram[bucketIndex(latency)] = (histogram[bucketIndex(latency)] ?? 0) + 1;
|
|
180
|
-
}
|
|
181
|
-
// A cost can arrive as a string: Postgres `numeric` columns come back that
|
|
182
|
-
// way, and so do restored rows. Adding one to a number concatenates instead of
|
|
183
|
-
// summing, which turned a cost of 0.000966 into "00.000966". Coerced here so
|
|
184
|
-
// every downstream sum is arithmetic.
|
|
185
|
-
const priced = numeric(event.estimatedCostUsd);
|
|
186
|
-
const resolve = event.resolveMs;
|
|
187
|
-
const hasResolve = resolve !== null && resolve !== undefined && Number.isFinite(resolve);
|
|
188
|
-
return {
|
|
189
|
-
byModel: bump(into.byModel, event.requestedModel ?? event.reportedModel),
|
|
190
|
-
byProvider: bump(into.byProvider, event.providerId),
|
|
191
|
-
byClient: bump(into.byClient, event.sourceSurface),
|
|
192
|
-
byClientIp: bump(into.byClientIp, event.clientIp),
|
|
193
|
-
// Keyed on the HTTP status, not the internal one: the chips and the filters
|
|
194
|
-
// are the status the client was given.
|
|
195
|
-
byStatus: bump(into.byStatus, event.httpStatus?.toString()),
|
|
196
|
-
resolveSumMs: readResolveSum(into) + (hasResolve ? resolve : 0),
|
|
197
|
-
resolveCount: readResolveCount(into) + (hasResolve ? 1 : 0),
|
|
198
|
-
requests: into.requests + 1,
|
|
199
|
-
inputTokens: into.inputTokens + (event.inputTokens ?? 0),
|
|
200
|
-
cachedTokens: into.cachedTokens + (event.cachedInputTokens ?? 0),
|
|
201
|
-
outputTokens: into.outputTokens + (event.outputTokens ?? 0),
|
|
202
|
-
reasoningTokens: into.reasoningTokens + (event.reasoningTokens ?? 0),
|
|
203
|
-
estimatedCostUsd: into.estimatedCostUsd + priced,
|
|
204
|
-
creditUsed: into.creditUsed + (event.creditUsed ?? 0),
|
|
205
|
-
succeeded: into.succeeded + (event.status === "completed" ? 1 : 0),
|
|
206
|
-
failed: into.failed + (event.status === "failed" ? 1 : 0),
|
|
207
|
-
cancelled: into.cancelled + (event.status === "cancelled" ? 1 : 0),
|
|
208
|
-
truncated: into.truncated + (event.status === "truncated" ? 1 : 0),
|
|
209
|
-
latencySum: into.latencySum + (latency ?? 0),
|
|
210
|
-
latencyCount: into.latencyCount + (latency === null ? 0 : 1),
|
|
211
|
-
latencyMinMs:
|
|
212
|
-
latency === null
|
|
213
|
-
? into.latencyMinMs
|
|
214
|
-
: into.latencyMinMs === null
|
|
215
|
-
? latency
|
|
216
|
-
: Math.min(into.latencyMinMs, latency),
|
|
217
|
-
latencyMaxMs:
|
|
218
|
-
latency === null
|
|
219
|
-
? into.latencyMaxMs
|
|
220
|
-
: into.latencyMaxMs === null
|
|
221
|
-
? latency
|
|
222
|
-
: Math.max(into.latencyMaxMs, latency),
|
|
223
|
-
latencyHistogram: histogram,
|
|
224
|
-
tokensPerSecSum: into.tokensPerSecSum + (event.tokensPerSec ?? 0),
|
|
225
|
-
tokensPerSecCount: into.tokensPerSecCount + (event.tokensPerSec === null ? 0 : 1),
|
|
226
|
-
// A completed request with no cost is unpriced, not free. Counting it keeps
|
|
227
|
-
// the dashboard's "partial cost" warning honest after compaction.
|
|
228
|
-
unpriced: into.unpriced + (event.status === "completed" && priced === 0 ? 1 : 0),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* An approximate percentile over a compacted histogram.
|
|
234
|
-
*
|
|
235
|
-
* Returns the upper edge of the bucket that contains it — never a value
|
|
236
|
-
* between edges, because a histogram cannot be more precise than the bucket
|
|
237
|
-
* that holds the sample. `null` when there are no samples.
|
|
238
|
-
*
|
|
239
|
-
* The overflow bucket is the last one and has no upper edge, so it returns
|
|
240
|
-
* {@link HISTOGRAM_OVERFLOW_MS} — the top edge of the last real bucket — rather
|
|
241
|
-
* than `null`. Returning `null` there was read as "no data" by every caller,
|
|
242
|
-
* which collapsed a P95 of 40 s to `0 ms`: the slowest, most interesting
|
|
243
|
-
* percentiles were the ones reported as zero. Callers that can do better pass
|
|
244
|
-
* the period's `latencyMaxMs` as `overflowMs` and get that instead.
|
|
245
|
-
*/
|
|
246
|
-
export function percentileFromHistogram(
|
|
247
|
-
histogram: readonly number[],
|
|
248
|
-
percentile: number,
|
|
249
|
-
overflowMs?: number | null,
|
|
250
|
-
): number | null {
|
|
251
|
-
const total = histogram.reduce((sum, count) => sum + count, 0);
|
|
252
|
-
if (total === 0) return null;
|
|
253
|
-
const target = Math.ceil((percentile / 100) * total);
|
|
254
|
-
let seen = 0;
|
|
255
|
-
for (let index = 0; index < histogram.length; index += 1) {
|
|
256
|
-
seen += histogram[index] ?? 0;
|
|
257
|
-
if (seen >= target) {
|
|
258
|
-
// The bucket's upper edge: a percentile from a histogram can never be
|
|
259
|
-
// more precise than the bucket that contains it.
|
|
260
|
-
if (index < LATENCY_BUCKETS_MS.length) return LATENCY_BUCKETS_MS[index]!;
|
|
261
|
-
// Above the last edge. `latencyMaxMs` is the honest answer when the
|
|
262
|
-
// caller has it — it is the real slowest request in the period, so a
|
|
263
|
-
// percentile past the last edge is at most that. Falling back to the
|
|
264
|
-
// last edge rather than 0 keeps the figure in the right order of
|
|
265
|
-
// magnitude even when no max was recorded.
|
|
266
|
-
return overflowMs ?? HISTOGRAM_OVERFLOW_MS;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/** Top edge of the last real latency bucket; the floor for an overflow answer. */
|
|
273
|
-
export const HISTOGRAM_OVERFLOW_MS = LATENCY_BUCKETS_MS[LATENCY_BUCKETS_MS.length - 1]!;
|
|
274
|
-
|
|
275
|
-
/** Newline count of a file, so a torn or unparsable line is detectable. */
|
|
276
|
-
/** Adds two buckets. Exported so the day index can merge without duplicating it. */
|
|
277
|
-
export function mergeBucketsInternal(a: CompactedBucket, b: CompactedBucket): CompactedBucket {
|
|
278
|
-
const histogram = a.latencyHistogram.map((count, index) => count + (b.latencyHistogram[index] ?? 0));
|
|
279
|
-
return {
|
|
280
|
-
byModel: mergeCounts(a.byModel, b.byModel),
|
|
281
|
-
byProvider: mergeCounts(a.byProvider, b.byProvider),
|
|
282
|
-
byClient: mergeCounts(a.byClient, b.byClient),
|
|
283
|
-
byClientIp: mergeCounts(a.byClientIp, b.byClientIp),
|
|
284
|
-
byStatus: mergeCounts(a.byStatus, b.byStatus),
|
|
285
|
-
resolveSumMs: readResolveSum(a) + readResolveSum(b),
|
|
286
|
-
resolveCount: readResolveCount(a) + readResolveCount(b),
|
|
287
|
-
requests: a.requests + b.requests,
|
|
288
|
-
inputTokens: a.inputTokens + b.inputTokens,
|
|
289
|
-
cachedTokens: a.cachedTokens + b.cachedTokens,
|
|
290
|
-
outputTokens: a.outputTokens + b.outputTokens,
|
|
291
|
-
reasoningTokens: a.reasoningTokens + b.reasoningTokens,
|
|
292
|
-
estimatedCostUsd: a.estimatedCostUsd + b.estimatedCostUsd,
|
|
293
|
-
creditUsed: a.creditUsed + b.creditUsed,
|
|
294
|
-
succeeded: a.succeeded + b.succeeded,
|
|
295
|
-
failed: a.failed + b.failed,
|
|
296
|
-
cancelled: a.cancelled + b.cancelled,
|
|
297
|
-
truncated: a.truncated + b.truncated,
|
|
298
|
-
latencySum: a.latencySum + b.latencySum,
|
|
299
|
-
latencyCount: a.latencyCount + b.latencyCount,
|
|
300
|
-
latencyMinMs:
|
|
301
|
-
a.latencyMinMs === null
|
|
302
|
-
? b.latencyMinMs
|
|
303
|
-
: b.latencyMinMs === null
|
|
304
|
-
? a.latencyMinMs
|
|
305
|
-
: Math.min(a.latencyMinMs, b.latencyMinMs),
|
|
306
|
-
latencyMaxMs:
|
|
307
|
-
a.latencyMaxMs === null
|
|
308
|
-
? b.latencyMaxMs
|
|
309
|
-
: b.latencyMaxMs === null
|
|
310
|
-
? a.latencyMaxMs
|
|
311
|
-
: Math.max(a.latencyMaxMs, b.latencyMaxMs),
|
|
312
|
-
latencyHistogram: histogram,
|
|
313
|
-
tokensPerSecSum: a.tokensPerSecSum + b.tokensPerSecSum,
|
|
314
|
-
tokensPerSecCount: a.tokensPerSecCount + b.tokensPerSecCount,
|
|
315
|
-
unpriced: a.unpriced + b.unpriced,
|
|
316
|
-
};
|
|
317
|
-
}
|