cartethyia 2.4.6 → 2.5.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/README.md +45 -13
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DyNXefph.js → CliToolsPage-D3xqZbN-.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CATpAEOz.js → ConsoleLogPage-u6W1UXSc.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-CTTpCb25.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-C5XpWlvy.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-DHoB3Agn.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-Br3CXEau.js → ModelPicker-oQ-SDXp8.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-BHW4Zt06.js → SettingsPage-CvjZxHE4.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-D3nza7LH.js} +1 -1
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js +1 -0
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-CbK3hXvU.js} +4 -4
- package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BETfaMYZ.js → api-keys-BmDbsior.js} +1 -1
- package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
- package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
- package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BTyGk_DL.js → badge-DpXT4Ixt.js} +1 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/{banner-bujTWYM4.js → banner-CSAAWLJv.js} +1 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/{card-B0Bnx5CA.js → card-DEhRXXw4.js} +1 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-DE2ug812.js → cli-tools-B0wBq-RL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-R_rArH8S.js → clipboard-button-DOo6EU8O.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BWIP_EvW.js → dialog-BvQeBMfP.js} +1 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BKcgVugF.js → drawer-CMwPXwQB.js} +1 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CcWkGR4a.js → entry-D4O2229X.js} +1 -1
- package/dist/dashboard/assets/{entry-CcWkGR4a.js.gz → entry-D4O2229X.js.gz} +0 -0
- package/dist/dashboard/assets/{entry-kTiTFrvh.js → entry-DKvr5Q9K.js} +1 -1
- package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-Booc5mNe.js → github-badge-zliLvBwr.js} +1 -1
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/{index-D70pChfn.js → index-CXm3FIyt.js} +2 -2
- package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
- package/dist/dashboard/assets/{inline-D1TCqM2D.js → inline-D1IlZZ8H.js} +1 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/{input-B9oyU93y.js → input-BpVZdrbc.js} +1 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BArzXirQ.js → layout-BioLW7gN.js} +1 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BVZzaoHF.js → list-row-CwVcGAIc.js} +1 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/{live-EmuIayqp.js → live-yfEJFF9T.js} +1 -1
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CSPQ9xB0.js → model-bans-CT-TxSNX.js} +1 -1
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/providers/cursor.webp +0 -0
- package/dist/dashboard/assets/providers/trae.webp +0 -0
- package/dist/dashboard/assets/{routing-BExgxxBU.js → routing-Cs6jl49L.js} +1 -1
- package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
- package/dist/dashboard/assets/{stack-Ca5HxI5i.js → stack-C9_uUjaA.js} +1 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/{state-CzUPfLXo.js → state-DA0B95UQ.js} +1 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DOUIky_i.js → switch-Cittxy-x.js} +1 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/{toast-C76WrMhc.js → toast-CNt_jzO4.js} +1 -1
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-D87FZwH0.js → useMutation-BQKMcJ1H.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.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 -52
- package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
- package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
- package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
- package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
- package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
- package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
- package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
- package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
- package/package.json +3 -2
- package/scripts/cli/command/console-menu.ts +30 -11
- package/scripts/cli/command/start.ts +77 -13
- package/scripts/cli/command/update.ts +1 -1
- package/scripts/cli/gateway/instance-lock.ts +105 -0
- package/scripts/cli/gateway/spawn.ts +173 -101
- package/scripts/cli/ui/background.ts +110 -49
- package/src/boot/app.ts +20 -6
- package/src/boot/dependencies.ts +19 -0
- package/src/boot/elysia-typebox-bridge.ts +37 -0
- package/src/boot/lifecycle.ts +2 -0
- package/src/config/cooldowns.ts +138 -138
- package/src/config/providers.ts +5 -5
- package/src/config/runtime.ts +19 -3
- package/src/config/spec.ts +9 -0
- package/src/console/api-keys/contracts.ts +414 -391
- package/src/console/api-keys/routes.ts +880 -870
- package/src/console/auth/account-reset-store.ts +14 -0
- package/src/console/auth/account-reset.ts +3 -2
- package/src/console/auth/setup-service.ts +6 -3
- package/src/console/domain-registration.ts +9 -2
- package/src/console/observability/contracts.ts +475 -455
- package/src/console/observability/store.ts +1015 -952
- package/src/console/providers/catalog/contracts.ts +31 -0
- package/src/console/providers/catalog/provider-operations.ts +8 -0
- package/src/console/providers/catalog/store.ts +1532 -1525
- package/src/console/providers/oauth/callback-listener.ts +18 -3
- package/src/console/providers/oauth/routes.ts +38 -15
- package/src/console/quota/refresh.ts +19 -0
- package/src/console/updates/routes.ts +162 -4
- package/src/main.ts +3 -0
- package/src/network/http2-bidi.ts +85 -0
- package/src/network/outbound-fetch.ts +1 -26
- package/src/network/pool/agent.ts +478 -460
- package/src/network/pool/resolver.ts +1104 -544
- package/src/network/pool-health-machine.ts +12 -5
- package/src/observability/payload-capture.ts +344 -324
- package/src/persistence/db-pg.ts +9 -3
- package/src/persistence/db-sqlite.ts +497 -495
- package/src/persistence/health-event-flush.ts +137 -0
- package/src/persistence/migrate.ts +181 -129
- package/src/persistence/schema.ts +1360 -1295
- package/src/persistence/settings-revision.ts +25 -0
- package/src/persistence/shared-cache.ts +217 -0
- package/src/persistence/stores/share-store.ts +556 -528
- package/src/persistence/usage-flush.ts +61 -1
- package/src/protocol/canonical-model.ts +23 -1
- package/src/protocol/response/chat.ts +143 -10
- package/src/protocol/response/omp-tool-call-markup.ts +221 -0
- package/src/providers/default-registry.ts +173 -1
- package/src/providers/discovery/openai-model-discovery.ts +39 -3
- package/src/providers/discovery/openrouter-free-roster.ts +98 -0
- package/src/providers/discovery/probing-service.ts +1022 -1001
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +39 -0
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +18 -5
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
- package/src/providers/integrations/buddy/codebuddy/shared.ts +15 -6
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
- package/src/providers/integrations/buddy/workbuddy/shared.ts +3 -3
- package/src/providers/integrations/claude/claude.ts +606 -582
- package/src/providers/integrations/cline/cline-quota.ts +18 -16
- package/src/providers/integrations/cline/cline.ts +539 -468
- package/src/providers/integrations/commandcode/commandcode.ts +675 -541
- package/src/providers/integrations/commandcode/discovery.ts +116 -0
- package/src/providers/integrations/commandcode/quota.ts +160 -0
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
- package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
- package/src/providers/integrations/cursor/cursor-request.ts +471 -0
- package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
- package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
- package/src/providers/integrations/cursor/cursor.ts +960 -0
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
- package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
- package/src/providers/integrations/github/github.ts +4 -4
- package/src/providers/integrations/hermes.ts +79 -8
- package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
- package/src/providers/integrations/kilo/kilo.ts +153 -146
- package/src/providers/integrations/opencode/provider.ts +532 -538
- package/src/providers/integrations/openrouter/provider.ts +139 -281
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +310 -257
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
- package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
- package/src/providers/integrations/qoder/qoder.ts +865 -730
- package/src/providers/integrations/trae/trae-catalog.ts +190 -0
- package/src/providers/integrations/trae/trae-headers.ts +149 -0
- package/src/providers/integrations/trae/trae-oauth.ts +773 -0
- package/src/providers/integrations/trae/trae-protocol.ts +400 -0
- package/src/providers/integrations/trae/trae-realm.ts +220 -0
- package/src/providers/integrations/trae/trae.ts +458 -0
- package/src/providers/operations/account-health-service.ts +107 -10
- package/src/providers/operations/client-versions.ts +133 -6
- package/src/providers/operations/provider-catalog-seeder.ts +95 -86
- package/src/providers/operations/provider-credential-service.ts +46 -7
- package/src/providers/provider-contracts.ts +76 -0
- package/src/providers/provider-metadata.ts +78 -6
- package/src/providers/provider-model-definition.ts +373 -349
- package/src/security/api-key-auth.ts +126 -8
- package/src/transport/contracts.ts +3 -0
- package/src/transport/dispatch/attempt-finalize.ts +32 -1
- package/src/transport/dispatch/attempt-loop.ts +530 -515
- package/src/transport/dispatch/generation.ts +88 -21
- package/src/transport/dispatch/leases.ts +99 -2
- package/src/transport/dispatch/proxy-request.ts +60 -3
- package/src/transport/dispatch/streaming/finalize.ts +16 -1
- package/src/transport/dispatch/streaming/stream.ts +22 -2
- package/src/transport/dispatch/upstream.ts +19 -1
- package/src/transport/guards/auth-guard.ts +26 -1
- package/src/transport/guards/body-policy.ts +5 -4
- package/src/transport/request/preparer.ts +103 -0
- package/src/transport/request/state.ts +14 -0
- package/src/transport/routing/plan-gate.ts +45 -0
- package/src/transport/routing/route-catalog.ts +799 -777
- package/src/transport/routing/route-model.ts +52 -1
- package/src/transport/routing/router.ts +51 -0
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js +0 -1
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +0 -1
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js +0 -7
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js +0 -1
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js +0 -56
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
- package/dist/dashboard/assets/app-BLCvGdzb.css +0 -2
- package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
- package/dist/dashboard/assets/assert-7J6_LVD3.js +0 -1
- package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
- package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
- package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
- package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
- package/dist/dashboard/assets/entry-7dpgvpJ3.js +0 -4
- package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
- package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
- package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
- package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
- package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
- package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
- package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
- package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
- package/dist/dashboard/assets/select-C9mMoEpJ.js +0 -41
- package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
- package/dist/dashboard/assets/settings-C4_Gn_wq.js +0 -1
- package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
- package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
- package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
- package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
- package/src/network/tls/bun-client-hello.ts +0 -112
- /package/dist/dashboard/assets/{app-Cjq7nrwu.js → app-C-Y4oZT0.js} +0 -0
- /package/dist/dashboard/assets/{app-Cjq7nrwu.js.gz → app-C-Y4oZT0.js.gz} +0 -0
|
@@ -1,455 +1,475 @@
|
|
|
1
|
-
import type { ConsoleAccessResolver } from "../auth/access";
|
|
2
|
-
// Telemetry domain: system health, usage, and telemetry-event read API.
|
|
3
|
-
|
|
4
|
-
import { ConsoleDomainError, requireTenantScope } from "../shared/errors";
|
|
5
|
-
import { conditionalResponse, type CachedResult } from "../shared/read-through-cache";
|
|
6
|
-
import { parseQueryLimit } from "../shared/query";
|
|
7
|
-
import type { AccessDecision } from "../../security/access-control";
|
|
8
|
-
import { USAGE_DIMENSIONS, type UsageDimension } from "./usage-dimensions";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Whitelisted usage periods. Numeric tokens bound the aggregation window;
|
|
12
|
-
* `"all"` is the single explicit unbounded token. Anything else is rejected
|
|
13
|
-
* before any database query so a typo cannot trigger a full-table scan.
|
|
14
|
-
*/
|
|
15
|
-
export function isSupportedUsagePeriod(period: string): boolean {
|
|
16
|
-
if (period === "all") return true;
|
|
17
|
-
const match = /^(\d+)([hd])$/.exec(period);
|
|
18
|
-
if (!match) return false;
|
|
19
|
-
const amount = Number(match[1]);
|
|
20
|
-
const cap = match[2] === "h" ? 8760 : 365;
|
|
21
|
-
return Number.isSafeInteger(amount) && amount >= 1 && amount <= cap;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export { USAGE_PERIODS, type UsagePeriod } from "./usage-periods";
|
|
25
|
-
|
|
26
|
-
export interface SystemHealthResponse {
|
|
27
|
-
version: string;
|
|
28
|
-
status: "healthy" | "degraded" | "unhealthy";
|
|
29
|
-
uptime_seconds: number;
|
|
30
|
-
database_healthy: boolean;
|
|
31
|
-
redis_healthy: boolean;
|
|
32
|
-
/** False when no Redis client is configured and coordination stays in memory. */
|
|
33
|
-
redis_configured: boolean;
|
|
34
|
-
/** JavaScript heap used by this process. */
|
|
35
|
-
memory_heap_bytes: number;
|
|
36
|
-
/** Native buffers and malloc outside the JavaScript heap. */
|
|
37
|
-
memory_external_bytes: number;
|
|
38
|
-
memory_bytes: number;
|
|
39
|
-
/** RSS as a percentage of system memory, not of the JS heap. */
|
|
40
|
-
memory_percent: number;
|
|
41
|
-
/**
|
|
42
|
-
* True when the database runs inside this process (SQLite). Then RSS covers
|
|
43
|
-
* the gateway *and* a whole PostgreSQL build, so it is not comparable with a
|
|
44
|
-
* driver-only process and the console must say so rather than imply the
|
|
45
|
-
* gateway itself grew.
|
|
46
|
-
*/
|
|
47
|
-
database_in_process: boolean;
|
|
48
|
-
cpu_percent: number;
|
|
49
|
-
cpu_cores: number;
|
|
50
|
-
pid: number;
|
|
51
|
-
platform: string;
|
|
52
|
-
request_count: number;
|
|
53
|
-
error_count: number;
|
|
54
|
-
latency_avg_ms: number;
|
|
55
|
-
latency_p95_ms: number;
|
|
56
|
-
latency_p99_ms: number;
|
|
57
|
-
cache_hit_rate_percent: number;
|
|
58
|
-
avg_tokens_per_sec: number;
|
|
59
|
-
}
|
|
60
|
-
export interface UsageResponse {
|
|
61
|
-
tenantId: string;
|
|
62
|
-
period: string;
|
|
63
|
-
requestsTotal: number;
|
|
64
|
-
requestsSucceeded: number;
|
|
65
|
-
requestsFailed: number;
|
|
66
|
-
tokensUsed: number;
|
|
67
|
-
estimatedCost: number;
|
|
68
|
-
topModels: Array<{ modelId: string; count: number }>;
|
|
69
|
-
topProviders: Array<{ providerId: string; count: number }>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Period-bound usage summary mirroring the 20-beta analytics surface. */
|
|
73
|
-
export interface UsageSummaryTotals {
|
|
74
|
-
requests: number;
|
|
75
|
-
inputTokens: number;
|
|
76
|
-
cachedTokens: number;
|
|
77
|
-
outputTokens: number;
|
|
78
|
-
/**
|
|
79
|
-
* Failed and truncated requests. Client aborts (`cancelled`, rendered 499)
|
|
80
|
-
* are expected client behavior, never an error, and are tracked separately.
|
|
81
|
-
*/
|
|
82
|
-
errors: number;
|
|
83
|
-
/** Client-cancelled requests (rendered `499`) — a sub-count, never an error. */
|
|
84
|
-
cancelled: number;
|
|
85
|
-
/** Streams that ended early (`502`); also included in `errors`. */
|
|
86
|
-
truncated: number;
|
|
87
|
-
/** Counts by actual HTTP status for the clickable Requests filters. */
|
|
88
|
-
statusCounts: Array<{ status: number; count: number }>;
|
|
89
|
-
/** Average gateway routing and preparation delay before upstream dispatch. */
|
|
90
|
-
avgResolveMs: number;
|
|
91
|
-
avgDurationMs: number;
|
|
92
|
-
/** Nearest-rank latency percentiles over the selected usage period. */
|
|
93
|
-
latencyP50Ms: number;
|
|
94
|
-
latencyP95Ms: number;
|
|
95
|
-
latencyP99Ms: number;
|
|
96
|
-
/**
|
|
97
|
-
* How many events each latency series was computed from. A percentile over
|
|
98
|
-
* twenty requests and one over twenty thousand do not deserve the same
|
|
99
|
-
* confidence, and the count is the only thing that says which it is.
|
|
100
|
-
*/
|
|
101
|
-
latencySampleCount: number;
|
|
102
|
-
/** Events carrying a resolve measurement; differs when a row never dispatched. */
|
|
103
|
-
resolveSampleCount: number;
|
|
104
|
-
estimatedCostUsd: number;
|
|
105
|
-
/** True when completed rows without persisted cost exist in the window. */
|
|
106
|
-
partial: boolean;
|
|
107
|
-
/** Cache hit rate percentage (0-100). */
|
|
108
|
-
cacheHitRate: number;
|
|
109
|
-
/** Average tokens per second across the period. */
|
|
110
|
-
avgTokensPerSec: number;
|
|
111
|
-
}
|
|
112
|
-
export interface UsageSummaryResponse {
|
|
113
|
-
period: string;
|
|
114
|
-
totals: UsageSummaryTotals;
|
|
115
|
-
}
|
|
116
|
-
export interface UsageChartBucket {
|
|
117
|
-
/** Bucket start as ISO timestamp. */
|
|
118
|
-
t: string;
|
|
119
|
-
requests: number;
|
|
120
|
-
input: number;
|
|
121
|
-
cached: number;
|
|
122
|
-
output: number;
|
|
123
|
-
}
|
|
124
|
-
export interface UsageChartResponse {
|
|
125
|
-
buckets: UsageChartBucket[];
|
|
126
|
-
}
|
|
127
|
-
export interface UsageByRow {
|
|
128
|
-
name: string;
|
|
129
|
-
requests: number;
|
|
130
|
-
input: number;
|
|
131
|
-
output: number;
|
|
132
|
-
cached: number;
|
|
133
|
-
total: number;
|
|
134
|
-
errors: number;
|
|
135
|
-
/** Null when no row in the group carries persisted cost. */
|
|
136
|
-
costUsd: number | null;
|
|
137
|
-
/** Resolved API key label for the `key` dimension; absent otherwise. */
|
|
138
|
-
label?: string;
|
|
139
|
-
/** Cache hit rate percentage (0-100). */
|
|
140
|
-
cacheHitRate: number;
|
|
141
|
-
/** Average tokens per second for this dimension. */
|
|
142
|
-
avgTokensPerSec: number;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Breakdown dimensions the usage endpoint accepts. Declared in `./usage-dimensions`
|
|
146
|
-
* and re-exported here, because three layers read the same binding: the route
|
|
147
|
-
* table registers one `by-<dimension>` path per member, the operations validator
|
|
148
|
-
* rejects anything outside it, and the dashboard's `Dimension` union mirrors it
|
|
149
|
-
* (kept in sync by hand).
|
|
150
|
-
*/
|
|
151
|
-
export { USAGE_DIMENSIONS, type UsageDimension };
|
|
152
|
-
|
|
153
|
-
export interface UsageByResponse {
|
|
154
|
-
/** The dimension these rows were grouped by, so a stale response can never be read as another tab's data. */
|
|
155
|
-
dimension: UsageDimension;
|
|
156
|
-
rows: UsageByRow[];
|
|
157
|
-
}
|
|
158
|
-
export interface UsageCacheResponse {
|
|
159
|
-
period: string;
|
|
160
|
-
inputTokens: number;
|
|
161
|
-
cachedTokens: number;
|
|
162
|
-
/** Always zero: cache-write tokens are not tracked by the telemetry schema. */
|
|
163
|
-
cacheWriteTokens: number;
|
|
164
|
-
hitRate: number;
|
|
165
|
-
}
|
|
166
|
-
export type UsageRequestMode = "stream" | "non_stream";
|
|
167
|
-
export interface UsageRequestItem {
|
|
168
|
-
requestId: string;
|
|
169
|
-
surface?: string;
|
|
170
|
-
providerId?: string;
|
|
171
|
-
/** Provider account (credential) that served this request, if any. */
|
|
172
|
-
accountId?: string;
|
|
173
|
-
model?: string;
|
|
174
|
-
/**
|
|
175
|
-
* The model the upstream named in its own response, verbatim — not the one
|
|
176
|
-
* the client asked for. Absent when the response carried no `model`; never
|
|
177
|
-
* backfilled from `model`, because that would report the ask as the answer.
|
|
178
|
-
*/
|
|
179
|
-
reportedModel?: string;
|
|
180
|
-
endpoint?: string;
|
|
181
|
-
apiKeyId?: string;
|
|
182
|
-
userAgent?: string;
|
|
183
|
-
clientName?: string;
|
|
184
|
-
estimatedCost?: number;
|
|
185
|
-
/** Provider-billed credits; absent when the upstream reported no credit field. */
|
|
186
|
-
creditUsed?: number;
|
|
187
|
-
/** Gateway-side time from request start to upstream dispatch, in ms. */
|
|
188
|
-
resolveMs?: number;
|
|
189
|
-
/** Resolved API key label; absent when the key is gone or unknown. */
|
|
190
|
-
apiKeyLabel?: string;
|
|
191
|
-
/** HTTP status the client actually received; absent on historical rows. */
|
|
192
|
-
httpStatus?: number;
|
|
193
|
-
status: string;
|
|
194
|
-
errorKind?: string;
|
|
195
|
-
/**
|
|
196
|
-
* Which layer failed: `cartethyia` (the gateway), `upstream` (the provider),
|
|
197
|
-
* or `network`. Absent for rows written before the column existed.
|
|
198
|
-
*/
|
|
199
|
-
errorOrigin?: string;
|
|
200
|
-
mode: UsageRequestMode;
|
|
201
|
-
startedAt: string;
|
|
202
|
-
clientIp?: string;
|
|
203
|
-
durationMs?: number;
|
|
204
|
-
inputTokens?: number;
|
|
205
|
-
outputTokens?: number;
|
|
206
|
-
cachedTokens?: number;
|
|
207
|
-
reasoningTokens?: number;
|
|
208
|
-
totalTokens?: number;
|
|
209
|
-
ttfbMs?: number;
|
|
210
|
-
tokensPerSec?: number;
|
|
211
|
-
firstContentDeltaAtMs?: number;
|
|
212
|
-
lastEventAtMs?: number;
|
|
213
|
-
/**
|
|
214
|
-
* Why this request's drawer payloads are empty, or `captured` when they are
|
|
215
|
-
* not. Remote-diagnosis field: the four panels read blank for every reason
|
|
216
|
-
* alike, so without this an operator cannot tell "capture is off" from
|
|
217
|
-
* "capture is broken" without access to the server's logs. Absent on rows
|
|
218
|
-
* written before the column existed.
|
|
219
|
-
*/
|
|
220
|
-
payloadCaptureState?: string;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
): Promise<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
tenantId
|
|
437
|
-
operations.requirePeriod(period)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
1
|
+
import type { ConsoleAccessResolver } from "../auth/access";
|
|
2
|
+
// Telemetry domain: system health, usage, and telemetry-event read API.
|
|
3
|
+
|
|
4
|
+
import { ConsoleDomainError, requireTenantScope } from "../shared/errors";
|
|
5
|
+
import { conditionalResponse, type CachedResult } from "../shared/read-through-cache";
|
|
6
|
+
import { parseQueryLimit } from "../shared/query";
|
|
7
|
+
import type { AccessDecision } from "../../security/access-control";
|
|
8
|
+
import { USAGE_DIMENSIONS, type UsageDimension } from "./usage-dimensions";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Whitelisted usage periods. Numeric tokens bound the aggregation window;
|
|
12
|
+
* `"all"` is the single explicit unbounded token. Anything else is rejected
|
|
13
|
+
* before any database query so a typo cannot trigger a full-table scan.
|
|
14
|
+
*/
|
|
15
|
+
export function isSupportedUsagePeriod(period: string): boolean {
|
|
16
|
+
if (period === "all") return true;
|
|
17
|
+
const match = /^(\d+)([hd])$/.exec(period);
|
|
18
|
+
if (!match) return false;
|
|
19
|
+
const amount = Number(match[1]);
|
|
20
|
+
const cap = match[2] === "h" ? 8760 : 365;
|
|
21
|
+
return Number.isSafeInteger(amount) && amount >= 1 && amount <= cap;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { USAGE_PERIODS, type UsagePeriod } from "./usage-periods";
|
|
25
|
+
|
|
26
|
+
export interface SystemHealthResponse {
|
|
27
|
+
version: string;
|
|
28
|
+
status: "healthy" | "degraded" | "unhealthy";
|
|
29
|
+
uptime_seconds: number;
|
|
30
|
+
database_healthy: boolean;
|
|
31
|
+
redis_healthy: boolean;
|
|
32
|
+
/** False when no Redis client is configured and coordination stays in memory. */
|
|
33
|
+
redis_configured: boolean;
|
|
34
|
+
/** JavaScript heap used by this process. */
|
|
35
|
+
memory_heap_bytes: number;
|
|
36
|
+
/** Native buffers and malloc outside the JavaScript heap. */
|
|
37
|
+
memory_external_bytes: number;
|
|
38
|
+
memory_bytes: number;
|
|
39
|
+
/** RSS as a percentage of system memory, not of the JS heap. */
|
|
40
|
+
memory_percent: number;
|
|
41
|
+
/**
|
|
42
|
+
* True when the database runs inside this process (SQLite). Then RSS covers
|
|
43
|
+
* the gateway *and* a whole PostgreSQL build, so it is not comparable with a
|
|
44
|
+
* driver-only process and the console must say so rather than imply the
|
|
45
|
+
* gateway itself grew.
|
|
46
|
+
*/
|
|
47
|
+
database_in_process: boolean;
|
|
48
|
+
cpu_percent: number;
|
|
49
|
+
cpu_cores: number;
|
|
50
|
+
pid: number;
|
|
51
|
+
platform: string;
|
|
52
|
+
request_count: number;
|
|
53
|
+
error_count: number;
|
|
54
|
+
latency_avg_ms: number;
|
|
55
|
+
latency_p95_ms: number;
|
|
56
|
+
latency_p99_ms: number;
|
|
57
|
+
cache_hit_rate_percent: number;
|
|
58
|
+
avg_tokens_per_sec: number;
|
|
59
|
+
}
|
|
60
|
+
export interface UsageResponse {
|
|
61
|
+
tenantId: string;
|
|
62
|
+
period: string;
|
|
63
|
+
requestsTotal: number;
|
|
64
|
+
requestsSucceeded: number;
|
|
65
|
+
requestsFailed: number;
|
|
66
|
+
tokensUsed: number;
|
|
67
|
+
estimatedCost: number;
|
|
68
|
+
topModels: Array<{ modelId: string; count: number }>;
|
|
69
|
+
topProviders: Array<{ providerId: string; count: number }>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Period-bound usage summary mirroring the 20-beta analytics surface. */
|
|
73
|
+
export interface UsageSummaryTotals {
|
|
74
|
+
requests: number;
|
|
75
|
+
inputTokens: number;
|
|
76
|
+
cachedTokens: number;
|
|
77
|
+
outputTokens: number;
|
|
78
|
+
/**
|
|
79
|
+
* Failed and truncated requests. Client aborts (`cancelled`, rendered 499)
|
|
80
|
+
* are expected client behavior, never an error, and are tracked separately.
|
|
81
|
+
*/
|
|
82
|
+
errors: number;
|
|
83
|
+
/** Client-cancelled requests (rendered `499`) — a sub-count, never an error. */
|
|
84
|
+
cancelled: number;
|
|
85
|
+
/** Streams that ended early (`502`); also included in `errors`. */
|
|
86
|
+
truncated: number;
|
|
87
|
+
/** Counts by actual HTTP status for the clickable Requests filters. */
|
|
88
|
+
statusCounts: Array<{ status: number; count: number }>;
|
|
89
|
+
/** Average gateway routing and preparation delay before upstream dispatch. */
|
|
90
|
+
avgResolveMs: number;
|
|
91
|
+
avgDurationMs: number;
|
|
92
|
+
/** Nearest-rank latency percentiles over the selected usage period. */
|
|
93
|
+
latencyP50Ms: number;
|
|
94
|
+
latencyP95Ms: number;
|
|
95
|
+
latencyP99Ms: number;
|
|
96
|
+
/**
|
|
97
|
+
* How many events each latency series was computed from. A percentile over
|
|
98
|
+
* twenty requests and one over twenty thousand do not deserve the same
|
|
99
|
+
* confidence, and the count is the only thing that says which it is.
|
|
100
|
+
*/
|
|
101
|
+
latencySampleCount: number;
|
|
102
|
+
/** Events carrying a resolve measurement; differs when a row never dispatched. */
|
|
103
|
+
resolveSampleCount: number;
|
|
104
|
+
estimatedCostUsd: number;
|
|
105
|
+
/** True when completed rows without persisted cost exist in the window. */
|
|
106
|
+
partial: boolean;
|
|
107
|
+
/** Cache hit rate percentage (0-100). */
|
|
108
|
+
cacheHitRate: number;
|
|
109
|
+
/** Average tokens per second across the period. */
|
|
110
|
+
avgTokensPerSec: number;
|
|
111
|
+
}
|
|
112
|
+
export interface UsageSummaryResponse {
|
|
113
|
+
period: string;
|
|
114
|
+
totals: UsageSummaryTotals;
|
|
115
|
+
}
|
|
116
|
+
export interface UsageChartBucket {
|
|
117
|
+
/** Bucket start as ISO timestamp. */
|
|
118
|
+
t: string;
|
|
119
|
+
requests: number;
|
|
120
|
+
input: number;
|
|
121
|
+
cached: number;
|
|
122
|
+
output: number;
|
|
123
|
+
}
|
|
124
|
+
export interface UsageChartResponse {
|
|
125
|
+
buckets: UsageChartBucket[];
|
|
126
|
+
}
|
|
127
|
+
export interface UsageByRow {
|
|
128
|
+
name: string;
|
|
129
|
+
requests: number;
|
|
130
|
+
input: number;
|
|
131
|
+
output: number;
|
|
132
|
+
cached: number;
|
|
133
|
+
total: number;
|
|
134
|
+
errors: number;
|
|
135
|
+
/** Null when no row in the group carries persisted cost. */
|
|
136
|
+
costUsd: number | null;
|
|
137
|
+
/** Resolved API key label for the `key` dimension; absent otherwise. */
|
|
138
|
+
label?: string;
|
|
139
|
+
/** Cache hit rate percentage (0-100). */
|
|
140
|
+
cacheHitRate: number;
|
|
141
|
+
/** Average tokens per second for this dimension. */
|
|
142
|
+
avgTokensPerSec: number;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Breakdown dimensions the usage endpoint accepts. Declared in `./usage-dimensions`
|
|
146
|
+
* and re-exported here, because three layers read the same binding: the route
|
|
147
|
+
* table registers one `by-<dimension>` path per member, the operations validator
|
|
148
|
+
* rejects anything outside it, and the dashboard's `Dimension` union mirrors it
|
|
149
|
+
* (kept in sync by hand).
|
|
150
|
+
*/
|
|
151
|
+
export { USAGE_DIMENSIONS, type UsageDimension };
|
|
152
|
+
|
|
153
|
+
export interface UsageByResponse {
|
|
154
|
+
/** The dimension these rows were grouped by, so a stale response can never be read as another tab's data. */
|
|
155
|
+
dimension: UsageDimension;
|
|
156
|
+
rows: UsageByRow[];
|
|
157
|
+
}
|
|
158
|
+
export interface UsageCacheResponse {
|
|
159
|
+
period: string;
|
|
160
|
+
inputTokens: number;
|
|
161
|
+
cachedTokens: number;
|
|
162
|
+
/** Always zero: cache-write tokens are not tracked by the telemetry schema. */
|
|
163
|
+
cacheWriteTokens: number;
|
|
164
|
+
hitRate: number;
|
|
165
|
+
}
|
|
166
|
+
export type UsageRequestMode = "stream" | "non_stream";
|
|
167
|
+
export interface UsageRequestItem {
|
|
168
|
+
requestId: string;
|
|
169
|
+
surface?: string;
|
|
170
|
+
providerId?: string;
|
|
171
|
+
/** Provider account (credential) that served this request, if any. */
|
|
172
|
+
accountId?: string;
|
|
173
|
+
model?: string;
|
|
174
|
+
/**
|
|
175
|
+
* The model the upstream named in its own response, verbatim — not the one
|
|
176
|
+
* the client asked for. Absent when the response carried no `model`; never
|
|
177
|
+
* backfilled from `model`, because that would report the ask as the answer.
|
|
178
|
+
*/
|
|
179
|
+
reportedModel?: string;
|
|
180
|
+
endpoint?: string;
|
|
181
|
+
apiKeyId?: string;
|
|
182
|
+
userAgent?: string;
|
|
183
|
+
clientName?: string;
|
|
184
|
+
estimatedCost?: number;
|
|
185
|
+
/** Provider-billed credits; absent when the upstream reported no credit field. */
|
|
186
|
+
creditUsed?: number;
|
|
187
|
+
/** Gateway-side time from request start to upstream dispatch, in ms. */
|
|
188
|
+
resolveMs?: number;
|
|
189
|
+
/** Resolved API key label; absent when the key is gone or unknown. */
|
|
190
|
+
apiKeyLabel?: string;
|
|
191
|
+
/** HTTP status the client actually received; absent on historical rows. */
|
|
192
|
+
httpStatus?: number;
|
|
193
|
+
status: string;
|
|
194
|
+
errorKind?: string;
|
|
195
|
+
/**
|
|
196
|
+
* Which layer failed: `cartethyia` (the gateway), `upstream` (the provider),
|
|
197
|
+
* or `network`. Absent for rows written before the column existed.
|
|
198
|
+
*/
|
|
199
|
+
errorOrigin?: string;
|
|
200
|
+
mode: UsageRequestMode;
|
|
201
|
+
startedAt: string;
|
|
202
|
+
clientIp?: string;
|
|
203
|
+
durationMs?: number;
|
|
204
|
+
inputTokens?: number;
|
|
205
|
+
outputTokens?: number;
|
|
206
|
+
cachedTokens?: number;
|
|
207
|
+
reasoningTokens?: number;
|
|
208
|
+
totalTokens?: number;
|
|
209
|
+
ttfbMs?: number;
|
|
210
|
+
tokensPerSec?: number;
|
|
211
|
+
firstContentDeltaAtMs?: number;
|
|
212
|
+
lastEventAtMs?: number;
|
|
213
|
+
/**
|
|
214
|
+
* Why this request's drawer payloads are empty, or `captured` when they are
|
|
215
|
+
* not. Remote-diagnosis field: the four panels read blank for every reason
|
|
216
|
+
* alike, so without this an operator cannot tell "capture is off" from
|
|
217
|
+
* "capture is broken" without access to the server's logs. Absent on rows
|
|
218
|
+
* written before the column existed.
|
|
219
|
+
*/
|
|
220
|
+
payloadCaptureState?: string;
|
|
221
|
+
/**
|
|
222
|
+
* Bitmask of the payload stages captured for this request: 1 request,
|
|
223
|
+
* 2 provider request, 4 provider response, 8 client response. Read from the
|
|
224
|
+
* payload row so the list can mark each drawer panel without inflating a
|
|
225
|
+
* frame per row. Absent when nothing was captured for the request — the same
|
|
226
|
+
* state a pre-migration row reports, rendered as every stage uncaptured
|
|
227
|
+
* rather than as a measured zero.
|
|
228
|
+
*/
|
|
229
|
+
payloadStages?: number;
|
|
230
|
+
}
|
|
231
|
+
export interface UsageRequestsResponse {
|
|
232
|
+
items: UsageRequestItem[];
|
|
233
|
+
}
|
|
234
|
+
export interface UsageRequestDetail extends UsageRequestItem {
|
|
235
|
+
proxy?: string;
|
|
236
|
+
payloads?: {
|
|
237
|
+
request?: unknown;
|
|
238
|
+
response?: unknown;
|
|
239
|
+
/** Body Cartethyia returned to the client (post-translation). */
|
|
240
|
+
clientResponse?: unknown;
|
|
241
|
+
providerRequest?: unknown;
|
|
242
|
+
providerResponse?: unknown;
|
|
243
|
+
} | null;
|
|
244
|
+
payloadSignals?: {
|
|
245
|
+
toolCalls: number;
|
|
246
|
+
images: number;
|
|
247
|
+
attachments: number;
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
export interface TelemetryEventView {
|
|
251
|
+
id: string;
|
|
252
|
+
createdAt: string;
|
|
253
|
+
tenantId: string;
|
|
254
|
+
requestId: string;
|
|
255
|
+
sourceSurface?: string;
|
|
256
|
+
requestedModel?: string;
|
|
257
|
+
providerId?: string;
|
|
258
|
+
latencyMs?: number;
|
|
259
|
+
status?: string;
|
|
260
|
+
inputTokens?: number;
|
|
261
|
+
outputTokens?: number;
|
|
262
|
+
estimatedCost?: number;
|
|
263
|
+
firstContentDeltaAtMs?: number;
|
|
264
|
+
lastEventAtMs?: number;
|
|
265
|
+
}
|
|
266
|
+
export interface TelemetryEventDetail extends TelemetryEventView {
|
|
267
|
+
payload?: { requestBody?: unknown; responseBody?: unknown };
|
|
268
|
+
}
|
|
269
|
+
export interface TelemetryEventListPage {
|
|
270
|
+
entries: readonly TelemetryEventView[];
|
|
271
|
+
/** Opaque cursor for the next page; `undefined` when the tail is reached. */
|
|
272
|
+
nextCursor?: string;
|
|
273
|
+
}
|
|
274
|
+
export interface ObservabilityStore {
|
|
275
|
+
/**
|
|
276
|
+
* Every member below that is served from the read-through cache returns a
|
|
277
|
+
* {@link CachedResult}, carrying the strong validator for the bytes the
|
|
278
|
+
* client would otherwise receive. A repeated poll is then a `304` instead of
|
|
279
|
+
* a full payload. The reads that are per-row and already bounded — the event
|
|
280
|
+
* list and the detail views — have no cache entry and no validator.
|
|
281
|
+
*/
|
|
282
|
+
health(tenantId: string): Promise<CachedResult<SystemHealthResponse>>;
|
|
283
|
+
/**
|
|
284
|
+
* Process memory as it is right now, bypassing the read-through cache.
|
|
285
|
+
*
|
|
286
|
+
* `health` is cached for 10 s, which is correct for a dashboard tile and
|
|
287
|
+
* wrong for anything sampling a trend: a memory watcher polling on that same
|
|
288
|
+
* 10 s cadence reads the cached bytes often enough that consecutive samples
|
|
289
|
+
* can be identical, and a monotonic-growth test over identical samples is
|
|
290
|
+
* meaningless. The values are read in-process and cost nothing, so there is
|
|
291
|
+
* no reason to serve them stale.
|
|
292
|
+
*/
|
|
293
|
+
liveMemory(): Promise<{ memory_bytes: number; memory_percent: number }>;
|
|
294
|
+
usage(tenantId: string, period: string): Promise<CachedResult<UsageResponse>>;
|
|
295
|
+
listEvents(tenantId: string, limit: number, cursor?: string): Promise<TelemetryEventListPage>;
|
|
296
|
+
getEvent(
|
|
297
|
+
tenantId: string,
|
|
298
|
+
eventId: string,
|
|
299
|
+
includePayload: boolean,
|
|
300
|
+
createdAt?: string,
|
|
301
|
+
): Promise<TelemetryEventDetail | undefined>;
|
|
302
|
+
usageSummary(tenantId: string, period: string): Promise<CachedResult<UsageSummaryResponse>>;
|
|
303
|
+
usageChart(tenantId: string, period: string): Promise<CachedResult<UsageChartResponse>>;
|
|
304
|
+
usageBy(
|
|
305
|
+
tenantId: string,
|
|
306
|
+
dimension: UsageDimension,
|
|
307
|
+
period: string,
|
|
308
|
+
): Promise<CachedResult<UsageByResponse>>;
|
|
309
|
+
usageCache(tenantId: string, period: string): Promise<CachedResult<UsageCacheResponse>>;
|
|
310
|
+
usageRequests(
|
|
311
|
+
tenantId: string,
|
|
312
|
+
period: string,
|
|
313
|
+
limit: number,
|
|
314
|
+
httpStatus?: number,
|
|
315
|
+
): Promise<CachedResult<UsageRequestsResponse>>;
|
|
316
|
+
usageRequestDetail(tenantId: string, requestId: string): Promise<UsageRequestDetail | undefined>;
|
|
317
|
+
}
|
|
318
|
+
export interface ObservabilityConfig {
|
|
319
|
+
readonly store: ObservabilityStore;
|
|
320
|
+
readonly accessResolver: ConsoleAccessResolver;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function createObservabilityOperations(config: ObservabilityConfig) {
|
|
324
|
+
const operations = {
|
|
325
|
+
/**
|
|
326
|
+
* The cached reads below answer with a `Response` rather than a plain
|
|
327
|
+
* object, because a repeated poll is a `304` and a `304` has no body to
|
|
328
|
+
* serialize. `ifNoneMatch` is the client's `If-None-Match`; a match costs
|
|
329
|
+
* ~200 bytes instead of the whole payload.
|
|
330
|
+
*/
|
|
331
|
+
async getSystemHealth(
|
|
332
|
+
access: AccessDecision | undefined,
|
|
333
|
+
ifNoneMatch: string | null,
|
|
334
|
+
): Promise<Response> {
|
|
335
|
+
const a = requireTenantScope(access, "dashboard:read");
|
|
336
|
+
const result = await config.store.health(a.tenantId);
|
|
337
|
+
return conditionalResponse(ifNoneMatch, result, result.value);
|
|
338
|
+
},
|
|
339
|
+
async getTenantUsage(
|
|
340
|
+
access: AccessDecision | undefined,
|
|
341
|
+
period = "7d",
|
|
342
|
+
ifNoneMatch: string | null = null,
|
|
343
|
+
): Promise<Response> {
|
|
344
|
+
const a = requireTenantScope(access, "dashboard:read");
|
|
345
|
+
if (!isSupportedUsagePeriod(period)) {
|
|
346
|
+
throw new ConsoleDomainError(
|
|
347
|
+
"invalid_period",
|
|
348
|
+
400,
|
|
349
|
+
`Unsupported usage period: ${period}. Use "24h", "7d", "30d", or "all".`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const result = await config.store.usage(a.tenantId, period);
|
|
353
|
+
return conditionalResponse(ifNoneMatch, result, result.value);
|
|
354
|
+
},
|
|
355
|
+
async listEvents(
|
|
356
|
+
access: AccessDecision | undefined,
|
|
357
|
+
limit?: string,
|
|
358
|
+
cursor?: string,
|
|
359
|
+
): Promise<TelemetryEventListPage> {
|
|
360
|
+
const a = requireTenantScope(access, "dashboard:read");
|
|
361
|
+
const lim = parseQueryLimit(limit, 50, 100);
|
|
362
|
+
const page = await config.store.listEvents(a.tenantId, lim, cursor);
|
|
363
|
+
const entries = page.entries;
|
|
364
|
+
return page.nextCursor ? { entries, nextCursor: page.nextCursor } : { entries };
|
|
365
|
+
},
|
|
366
|
+
async getEventDetail(
|
|
367
|
+
access: AccessDecision | undefined,
|
|
368
|
+
eventId: string,
|
|
369
|
+
includePayload = false,
|
|
370
|
+
createdAt?: string,
|
|
371
|
+
): Promise<TelemetryEventDetail> {
|
|
372
|
+
const a = requireTenantScope(access, "dashboard:read");
|
|
373
|
+
const detail = await config.store.getEvent(
|
|
374
|
+
a.tenantId,
|
|
375
|
+
eventId,
|
|
376
|
+
includePayload,
|
|
377
|
+
createdAt,
|
|
378
|
+
);
|
|
379
|
+
if (!detail) throw new ConsoleDomainError("event_not_found", 404, `Event ${eventId} not found`);
|
|
380
|
+
return detail;
|
|
381
|
+
},
|
|
382
|
+
requirePeriod(period: string): string {
|
|
383
|
+
if (!isSupportedUsagePeriod(period)) {
|
|
384
|
+
throw new ConsoleDomainError(
|
|
385
|
+
"invalid_period",
|
|
386
|
+
400,
|
|
387
|
+
`Unsupported usage period: ${period}. Use "1h", "24h", "7d", "30d", or "all".`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
return period;
|
|
391
|
+
},
|
|
392
|
+
requireTenant(access: AccessDecision | undefined): string {
|
|
393
|
+
const a = requireTenantScope(access, "dashboard:read");
|
|
394
|
+
return a.tenantId;
|
|
395
|
+
},
|
|
396
|
+
async getUsageSummary(
|
|
397
|
+
access: AccessDecision | undefined,
|
|
398
|
+
period = "24h",
|
|
399
|
+
ifNoneMatch: string | null = null,
|
|
400
|
+
): Promise<Response> {
|
|
401
|
+
const tenantId = operations.requireTenant(access);
|
|
402
|
+
const result = await config.store.usageSummary(tenantId, operations.requirePeriod(period));
|
|
403
|
+
return conditionalResponse(ifNoneMatch, result, result.value);
|
|
404
|
+
},
|
|
405
|
+
async getUsageChart(
|
|
406
|
+
access: AccessDecision | undefined,
|
|
407
|
+
period = "24h",
|
|
408
|
+
ifNoneMatch: string | null = null,
|
|
409
|
+
): Promise<Response> {
|
|
410
|
+
const tenantId = operations.requireTenant(access);
|
|
411
|
+
const result = await config.store.usageChart(tenantId, operations.requirePeriod(period));
|
|
412
|
+
return conditionalResponse(ifNoneMatch, result, result.value);
|
|
413
|
+
},
|
|
414
|
+
async getUsageBy(
|
|
415
|
+
access: AccessDecision | undefined,
|
|
416
|
+
dimension: UsageDimension,
|
|
417
|
+
period = "24h",
|
|
418
|
+
ifNoneMatch: string | null = null,
|
|
419
|
+
): Promise<Response> {
|
|
420
|
+
const tenantId = operations.requireTenant(access);
|
|
421
|
+
if (!(USAGE_DIMENSIONS as readonly string[]).includes(dimension)) {
|
|
422
|
+
throw new ConsoleDomainError(
|
|
423
|
+
"invalid_dimension",
|
|
424
|
+
400,
|
|
425
|
+
`Unsupported usage dimension: ${dimension}. Use ${USAGE_DIMENSIONS.map((d) => `"${d}"`).join(", ")}.`,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
const result = await config.store.usageBy(tenantId, dimension, operations.requirePeriod(period));
|
|
429
|
+
return conditionalResponse(ifNoneMatch, result, result.value);
|
|
430
|
+
},
|
|
431
|
+
async getUsageCache(
|
|
432
|
+
access: AccessDecision | undefined,
|
|
433
|
+
period = "24h",
|
|
434
|
+
ifNoneMatch: string | null = null,
|
|
435
|
+
): Promise<Response> {
|
|
436
|
+
const tenantId = operations.requireTenant(access);
|
|
437
|
+
const result = await config.store.usageCache(tenantId, operations.requirePeriod(period));
|
|
438
|
+
return conditionalResponse(ifNoneMatch, result, result.value);
|
|
439
|
+
},
|
|
440
|
+
async listUsageRequests(
|
|
441
|
+
access: AccessDecision | undefined,
|
|
442
|
+
period = "24h",
|
|
443
|
+
limit?: string,
|
|
444
|
+
httpStatus?: string,
|
|
445
|
+
ifNoneMatch: string | null = null,
|
|
446
|
+
): Promise<Response> {
|
|
447
|
+
const tenantId = operations.requireTenant(access);
|
|
448
|
+
let parsedStatus: number | undefined;
|
|
449
|
+
if (httpStatus !== undefined) {
|
|
450
|
+
parsedStatus = Number(httpStatus);
|
|
451
|
+
if (!Number.isInteger(parsedStatus) || parsedStatus < 100 || parsedStatus > 599) {
|
|
452
|
+
throw new ConsoleDomainError("invalid_request", 400, "Invalid HTTP status filter");
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const result = await config.store.usageRequests(
|
|
456
|
+
tenantId,
|
|
457
|
+
operations.requirePeriod(period),
|
|
458
|
+
parseQueryLimit(limit, 50, 500),
|
|
459
|
+
parsedStatus,
|
|
460
|
+
);
|
|
461
|
+
return conditionalResponse(ifNoneMatch, result, result.value);
|
|
462
|
+
},
|
|
463
|
+
async getUsageRequestDetail(
|
|
464
|
+
access: AccessDecision | undefined,
|
|
465
|
+
requestId: string,
|
|
466
|
+
): Promise<UsageRequestDetail> {
|
|
467
|
+
const tenantId = operations.requireTenant(access);
|
|
468
|
+
const detail = await config.store.usageRequestDetail(tenantId, requestId);
|
|
469
|
+
if (!detail) throw new ConsoleDomainError("event_not_found", 404, `Request ${requestId} not found`);
|
|
470
|
+
const payloads = detail.payloads ?? null;
|
|
471
|
+
return { ...detail, payloads };
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
return operations;
|
|
475
|
+
}
|