cartethyia 2.7.9 → 2.8.1
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 +6 -5
- package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Dq530awD.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Dq530awD.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-DZzaS-7S.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZzaS-7S.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-DaZZ3cqP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DaZZ3cqP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BlC_xdkX.js} +1 -1
- package/dist/dashboard/assets/CombosPage-BlC_xdkX.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-D-rqeVtG.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-D-rqeVtG.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-7xT5yg2E.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-7xT5yg2E.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-Clq8E9cW.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-6Teoq9Se.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-6Teoq9Se.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CZuI0MAR.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CZuI0MAR.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-C1hNTlkr.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C1hNTlkr.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-0yfhSI1C.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-0yfhSI1C.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js +8 -0
- package/dist/dashboard/assets/ProviderDetailPage-xqTxdQ4c.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-Ddhm1InL.js} +1 -1
- package/dist/dashboard/assets/ProviderIcon-Ddhm1InL.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-C3fYuu6i.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js +9 -0
- package/dist/dashboard/assets/ProxyPage-CHtJtZ3u.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-D1RV_2BX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-BzZAvYSX.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BzZAvYSX.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage--s-EfEIL.js} +1 -1
- package/dist/dashboard/assets/SetupPage--s-EfEIL.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-BjXFGSK4.js} +1 -1
- package/dist/dashboard/assets/StudioPage-BjXFGSK4.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-CP8F8BN7.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CP8F8BN7.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-2hc12Uz2.js} +1 -1
- package/dist/dashboard/assets/api-keys-2hc12Uz2.js.gz +0 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css +2 -0
- package/dist/dashboard/assets/app-DGXbNfAR.css.gz +0 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js +1 -0
- package/dist/dashboard/assets/assert-BMcj1xZE.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-DkRUuVfz.js} +1 -1
- package/dist/dashboard/assets/badge-DkRUuVfz.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-CDjW841Q.js} +1 -1
- package/dist/dashboard/assets/banner-CDjW841Q.js.gz +0 -0
- package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-Bto5UlL9.js} +1 -1
- package/dist/dashboard/assets/card-Bto5UlL9.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-DaH6g9hL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-DaH6g9hL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-BYKBCzjk.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BYKBCzjk.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-DL0Zftwv.js} +1 -1
- package/dist/dashboard/assets/dialog-DL0Zftwv.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-Dpx_UXaS.js} +1 -1
- package/dist/dashboard/assets/drawer-Dpx_UXaS.js.gz +0 -0
- package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-BV7-1NoM.js} +1 -1
- package/dist/dashboard/assets/entry-BV7-1NoM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-CGvdNU6r.js} +1 -1
- package/dist/dashboard/assets/entry-CGvdNU6r.js.gz +0 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js +4 -0
- package/dist/dashboard/assets/entry-DScKYvZj.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-BS3DZlZj.js} +1 -1
- package/dist/dashboard/assets/formatters-BS3DZlZj.js.gz +0 -0
- package/dist/dashboard/assets/{index-BU64Lqvp.js → index-SQnvvTWX.js} +2 -2
- package/dist/dashboard/assets/index-SQnvvTWX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-Dl-cuLK0.js} +1 -1
- package/dist/dashboard/assets/inline-Dl-cuLK0.js.gz +0 -0
- package/dist/dashboard/assets/{input-BCVkdR9d.js → input-Bfx0EzRc.js} +1 -1
- package/dist/dashboard/assets/input-Bfx0EzRc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-BxhbQ_JS.js} +1 -1
- package/dist/dashboard/assets/layout-BxhbQ_JS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-D49Y4ptn.js} +1 -1
- package/dist/dashboard/assets/list-row-D49Y4ptn.js.gz +0 -0
- package/dist/dashboard/assets/{live-Cae01iu8.js → live-CbVTdX9V.js} +1 -1
- package/dist/dashboard/assets/live-CbVTdX9V.js.gz +0 -0
- package/dist/dashboard/assets/{network-CRib7H_2.js → network-JGvDveof.js} +1 -1
- package/dist/dashboard/assets/network-JGvDveof.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-Dw6kiew-.js} +1 -1
- package/dist/dashboard/assets/numbers-Dw6kiew-.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-iUoI8GeE.js} +1 -1
- package/dist/dashboard/assets/pagination-iUoI8GeE.js.gz +0 -0
- package/dist/dashboard/assets/providers/sensenova-intl.png +0 -0
- package/dist/dashboard/assets/providers/sensenova.png +0 -0
- package/dist/dashboard/assets/providers/zro.svg +3 -0
- package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
- package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-8jZr-3WE.js} +1 -1
- package/dist/dashboard/assets/routing-8jZr-3WE.js.gz +0 -0
- package/dist/dashboard/assets/{select-ChoFWyKz.js → select-oO1FxMVb.js} +2 -2
- package/dist/dashboard/assets/select-oO1FxMVb.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-CLr3kdIx.js} +1 -1
- package/dist/dashboard/assets/settings-CLr3kdIx.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-Y3pDteZr.js} +1 -1
- package/dist/dashboard/assets/stack-Y3pDteZr.js.gz +0 -0
- package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-CArX_Iak.js} +1 -1
- package/dist/dashboard/assets/state-CArX_Iak.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Db70BMix.js → switch-DwlwmDO-.js} +1 -1
- package/dist/dashboard/assets/switch-DwlwmDO-.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-DJ9GYEgf.js} +1 -1
- package/dist/dashboard/assets/useMutation-DJ9GYEgf.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -53
- package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
- package/migrations/full-postgres/0047_add_provider_device_flow_proxy.sql +17 -0
- package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
- package/migrations/lite-sqlite3/0047_add_provider_device_flow_proxy.sql +19 -0
- package/package.json +1 -1
- package/src/boot/app.ts +11 -14
- package/src/boot/dependencies.ts +5 -7
- package/src/boot/flush.ts +20 -0
- package/src/boot/lifecycle.ts +7 -5
- package/src/config/providers.ts +6 -5
- package/src/config/routing.ts +26 -0
- package/src/config/spec-metadata.ts +72 -0
- package/src/config/spec.ts +28 -8
- package/src/console/api-keys/contracts.ts +12 -1
- package/src/console/api-keys/routes.ts +40 -28
- package/src/console/auth/account-reset.ts +7 -9
- package/src/console/backup/contracts.ts +4 -0
- package/src/console/backup/export-endpoint.ts +7 -5
- package/src/console/backup/nine-router.ts +71 -10
- package/src/console/backup/service.ts +8 -1
- package/src/console/cli-tools/service.ts +6 -12
- package/src/console/console-router-guards.ts +23 -16
- package/src/console/console-router.ts +1 -1
- package/src/console/domain-registration.ts +7 -4
- package/src/console/inferhub/client.ts +1 -1
- package/src/console/inferhub/normalize.ts +24 -28
- package/src/console/inferhub/types.ts +3 -0
- package/src/console/ip-bans/routes.ts +7 -13
- package/src/console/observability/live.ts +2 -2
- package/src/console/observability/store.ts +0 -53
- package/src/console/providers/catalog/contracts.ts +9 -3
- package/src/console/providers/catalog/model-operations.ts +2 -3
- package/src/console/providers/catalog/provider-operations.ts +1 -1
- package/src/console/providers/catalog/routes.ts +3 -2
- package/src/console/providers/catalog/store.ts +1914 -1902
- package/src/console/providers/detail/routes.ts +1 -0
- package/src/console/providers/detail/store.ts +4 -0
- package/src/console/providers/oauth/callback-listener.ts +30 -11
- package/src/console/providers/oauth/device-egress.ts +123 -0
- package/src/console/providers/oauth/routes.ts +123 -16
- package/src/console/quota/account-quota-global.ts +14 -24
- package/src/console/quota/account-quota-shared.ts +49 -0
- package/src/console/quota/account-quota-tenant.ts +11 -23
- package/src/console/quota/account-test-egress.ts +2 -2
- package/src/console/quota/daily-growth-job.ts +1 -1
- package/src/console/quota/quota-view.ts +19 -3
- package/src/console/routing/pools/contracts.ts +1 -1
- package/src/console/routing/pools/operations.ts +32 -47
- package/src/console/routing/pools/probe-verdict.ts +2 -2
- package/src/console/routing/pools/relay-deploy.ts +1 -1
- package/src/console/routing/pools/store.ts +4 -4
- package/src/console/shared/errors.ts +33 -6
- package/src/main.ts +2 -3
- package/src/network/client.ts +67 -0
- package/src/network/egress/bidi-session.ts +322 -0
- package/src/network/egress/dial.ts +201 -0
- package/src/network/egress/factory.ts +191 -0
- package/src/network/{outbound-fetch.ts → egress/fetch.ts} +195 -29
- package/src/network/egress/tls-profile.ts +123 -0
- package/src/network/egress/ws.ts +175 -0
- package/src/network/pool/{byte-accounting.ts → accounting.ts} +3 -2
- package/src/network/{types.ts → pool/agent-config.ts} +1 -1
- package/src/network/pool/agent.ts +2 -2
- package/src/network/{pool-cooldown-store.ts → pool/cooldown-store.ts} +95 -20
- package/src/network/{pool-health-machine.ts → pool/health.ts} +205 -6
- package/src/network/pool/resolver.ts +10 -849
- package/src/network/pool/selector.ts +184 -41
- package/src/network/ssrf.ts +7 -7
- package/src/network/{retry.ts → util/retry.ts} +1 -1
- package/src/network/{rate-limit-verdict.ts → verdict.ts} +1 -1
- package/src/observability/event-log.ts +7 -361
- package/src/observability/performance-metrics.ts +9 -0
- package/src/observability/runtime-metrics.ts +14 -9
- package/src/observability/telemetry-buffer.ts +16 -26
- package/src/observability/telemetry-reconciliation.ts +9 -37
- package/src/observability/telemetry-row.ts +125 -0
- package/src/persistence/db-mode.ts +4 -1
- package/src/persistence/dialect.ts +18 -0
- package/src/persistence/migrate.ts +6 -3
- package/src/persistence/schema.ts +17 -0
- package/src/persistence/ttl-cache.ts +21 -3
- package/src/protocol/codecs.ts +59 -2
- package/src/protocol/dsml.ts +34 -86
- package/src/protocol/markup-stream-parser.ts +195 -0
- package/src/protocol/reasoning-text.ts +19 -0
- package/src/protocol/request/chat.ts +10 -10
- package/src/protocol/request/gemini.ts +8 -2
- package/src/protocol/request/messages.ts +34 -22
- package/src/protocol/response/chat.ts +12 -20
- package/src/protocol/response/messages.ts +34 -7
- package/src/protocol/response/omp-tool-call-markup.ts +43 -86
- package/src/protocol/response/responses.ts +18 -2
- package/src/protocol/stop-reason.ts +23 -1
- package/src/protocol/think-split.ts +112 -0
- package/src/protocol/translation/dialect/qwen.ts +26 -58
- package/src/protocol/translation/quirks.ts +15 -0
- package/src/protocol/upstream/send-claude-messages.ts +2 -2
- package/src/providers/adapters/compatible-adapter.ts +76 -12
- package/src/providers/authentication/oauth-client.ts +40 -6
- package/src/providers/authentication/oauth-device-flow.ts +105 -11
- package/src/providers/authentication/oauth-flow-store.ts +82 -1
- package/src/providers/authentication/oauth-refresh-service.ts +766 -758
- package/src/providers/configured-provider.ts +148 -0
- package/src/providers/default-registry.ts +137 -36
- package/src/providers/discovery/discovery-types.ts +7 -0
- package/src/providers/discovery/probe-phases.ts +20 -6
- package/src/providers/discovery/probing-service.ts +22 -2
- package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
- package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +16 -1
- package/src/providers/integrations/buddy/account.ts +969 -0
- package/src/providers/integrations/buddy/capabilities.ts +12 -12
- package/src/providers/integrations/buddy/{buddy-catalog-shared.ts → catalog.ts} +198 -136
- package/src/providers/integrations/buddy/{buddy-chat-shared.ts → chat.ts} +176 -20
- package/src/providers/integrations/buddy/codebuddy/index.ts +429 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +41 -26
- package/src/providers/integrations/buddy/headers.ts +311 -0
- package/src/providers/integrations/buddy/lifecycle.ts +1018 -0
- package/src/providers/integrations/buddy/{buddy-media.ts → media.ts} +1 -1
- package/src/providers/integrations/claude/claude-quota.ts +14 -17
- package/src/providers/integrations/cline/cline-oauth.ts +10 -5
- package/src/providers/integrations/cline/cline.ts +5 -6
- package/src/providers/integrations/clixad/clixad-oauth.ts +98 -0
- package/src/providers/integrations/clixad/clixad-quota.ts +132 -0
- package/src/providers/integrations/clixad/provider.ts +145 -0
- package/src/providers/integrations/codex/codex-oauth.ts +8 -5
- package/src/providers/integrations/commandcode/quota.ts +22 -28
- package/src/providers/integrations/cursor/cursor-auth.ts +1 -9
- package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
- package/src/providers/integrations/cursor/cursor-hardening.ts +279 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +605 -6
- package/src/providers/integrations/cursor/cursor-request.ts +26 -12
- package/src/providers/integrations/cursor/cursor-shared.ts +8 -5
- package/src/providers/integrations/cursor/cursor.ts +574 -110
- package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
- package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
- package/src/providers/integrations/deepai/deepai-catalog.ts +149 -0
- package/src/providers/integrations/deepai/deepai-common.ts +145 -0
- package/src/providers/integrations/deepai/deepai-oauth.ts +129 -0
- package/src/providers/integrations/deepai/deepai-quota.ts +97 -0
- package/src/providers/integrations/deepai/provider.ts +42 -0
- package/src/providers/integrations/devin/devin-auth.ts +7 -0
- package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
- package/src/providers/integrations/factory/factory-oauth.ts +33 -51
- package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
- package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
- package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
- package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
- package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
- package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
- package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
- package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
- package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
- package/src/providers/integrations/freebuff/freebuff.ts +682 -0
- package/src/providers/integrations/freepi/freepi-catalog.ts +5 -0
- package/src/providers/integrations/freepi/freepi-discovery.ts +2 -2
- package/src/providers/integrations/freepi/freepi-headers.ts +2 -2
- package/src/providers/integrations/freepi/freepi-protocol.ts +11 -2
- package/src/providers/integrations/freepi/freepi-realm.ts +0 -3
- package/src/providers/integrations/freepi/freepi-session.ts +158 -0
- package/src/providers/integrations/freepi/freepi.ts +80 -54
- package/src/providers/integrations/github/github-discovery.ts +30 -13
- package/src/providers/integrations/github/github-oauth.ts +217 -44
- package/src/providers/integrations/github/github-quota.ts +22 -5
- package/src/providers/integrations/github/github.ts +40 -32
- package/src/providers/integrations/grok/grok-oauth.ts +52 -49
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
- package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
- package/src/providers/integrations/hermes/hermes-oauth.ts +3 -3
- package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +42 -13
- package/src/providers/integrations/hermes/index.ts +2 -0
- package/src/providers/integrations/kilo/kilo-oauth.ts +4 -4
- package/src/providers/integrations/kilo/kilo.ts +1 -1
- package/src/providers/integrations/kimi/kimi-oauth.ts +3 -3
- package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
- package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
- package/src/providers/integrations/kiro/kiro-oauth.ts +2 -2
- package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
- package/src/providers/integrations/kiro/kiro.ts +15 -19
- package/src/providers/integrations/muse/muse-oauth.ts +5 -5
- package/src/providers/integrations/ollamacloud/index.ts +2 -0
- package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
- package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
- package/src/providers/integrations/opencode/fingerprint.ts +1 -1
- package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
- package/src/providers/integrations/openrouter/provider.ts +1 -1
- package/src/providers/integrations/perch/perch-credential.ts +25 -0
- package/src/providers/integrations/perch/perch-protocol.ts +0 -6
- package/src/providers/integrations/perch/perch-quota.ts +2 -16
- package/src/providers/integrations/perch/perch.ts +6 -16
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
- package/src/providers/integrations/qoder/qoder-adapter.ts +20 -125
- package/src/providers/integrations/qoder/qoder-auth.ts +109 -30
- package/src/providers/integrations/qoder/qoder-catalog.ts +21 -6
- package/src/providers/integrations/qoder/qoder-chat.ts +32 -29
- package/src/providers/integrations/qoder/qoder-checkin.ts +5 -5
- package/src/providers/integrations/qoder/qoder-cosy.ts +37 -9
- package/src/providers/integrations/qoder/qoder-oauth.ts +9 -2
- package/src/providers/integrations/qoder/qoder-realm.ts +13 -36
- package/src/providers/integrations/qoder/qoder-sse.ts +447 -68
- package/src/providers/integrations/qoder/qoder-types.ts +9 -0
- package/src/providers/integrations/qoder/qoder.ts +3 -26
- package/src/providers/integrations/sensenova/provider.ts +144 -0
- package/src/providers/integrations/trae/trae-protocol.ts +9 -5
- package/src/providers/integrations/trae/trae-quota.ts +21 -29
- package/src/providers/integrations/trae/trae-realm.ts +38 -0
- package/src/providers/integrations/trae/trae.ts +7 -24
- package/src/providers/integrations/vercel/provider.ts +117 -0
- package/src/providers/integrations/vercel/vercel-quota.ts +96 -0
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +1 -1
- package/src/providers/integrations/zcode-start-plan/oauth.ts +3 -3
- package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
- package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
- package/src/providers/integrations/zro/zro.ts +205 -0
- package/src/providers/operations/account-error-classification.ts +199 -307
- package/src/providers/operations/account-health-service.ts +82 -1
- package/src/providers/operations/account-refresh-status.ts +36 -37
- package/src/providers/operations/account-tier.ts +15 -16
- package/src/providers/operations/client-version-resolver.ts +1 -1
- package/src/providers/operations/client-versions.ts +100 -81
- package/src/providers/operations/cooldown-rules.ts +11 -16
- package/src/providers/operations/oauth-refresh-lead.ts +46 -22
- package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
- package/src/providers/operations/provider-catalog-service.ts +1 -1
- package/src/providers/operations/provider-credential-service.ts +118 -51
- package/src/providers/operations/provider-rules/github.ts +48 -0
- package/src/providers/operations/provider-rules/grok.ts +33 -0
- package/src/providers/operations/provider-rules/inception.ts +49 -0
- package/src/providers/operations/provider-rules/index.ts +32 -0
- package/src/providers/operations/provider-rules/perch.ts +37 -0
- package/src/providers/operations/provider-rules/qoder.ts +70 -0
- package/src/providers/operations/provider-rules/trae.ts +81 -0
- package/src/providers/operations/session-resolution.ts +17 -1
- package/src/providers/provider-contracts.ts +6 -4
- package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
- package/src/providers/provider-metadata.ts +70 -10
- package/src/providers/provider-probe.ts +15 -1
- package/src/providers/provider-registry.ts +42 -33
- package/src/providers/quota/quota-contracts.ts +2 -0
- package/src/providers/quota/quota-limit-windows.ts +16 -5
- package/src/providers/quota/quota-support.ts +1 -1
- package/src/providers/quota/quota-window-parser.ts +90 -18
- package/src/providers/quota/refresh.ts +100 -21
- package/src/security/api-key-auth-cache.ts +1 -1
- package/src/security/api-key-auth.ts +6 -2
- package/src/security/ip-boundary.ts +1 -1
- package/src/transport/dispatch/attempt-finalize.ts +15 -14
- package/src/transport/dispatch/attempt-loop.ts +73 -19
- package/src/transport/dispatch/generation.ts +2 -8
- package/src/transport/dispatch/handler-deps.ts +1 -1
- package/src/transport/dispatch/leases.ts +1 -1
- package/src/transport/dispatch/native-shared.ts +25 -0
- package/src/transport/dispatch/proxy-request.ts +8 -46
- package/src/transport/dispatch/responses-compact.ts +2 -8
- package/src/transport/dispatch/retry-policy.ts +1 -1
- package/src/transport/dispatch/streaming/finalize.ts +10 -7
- package/src/transport/dispatch/streaming/stream.ts +9 -37
- package/src/transport/dispatch/systemone.ts +2 -8
- package/src/transport/dispatch/token-count.ts +2 -6
- package/src/transport/dispatch/upstream.ts +63 -0
- package/src/transport/drain-endpoint.ts +31 -4
- package/src/transport/failure-policy.ts +16 -30
- package/src/transport/guards/body-policy.ts +7 -1
- package/src/transport/guards/error-lifecycle.ts +13 -1
- package/src/transport/guards/request-context.ts +22 -14
- package/src/transport/request/preparer.ts +81 -71
- package/src/transport/routing/bounded-lru.ts +14 -0
- package/src/transport/routing/drain-reserve.ts +129 -0
- package/src/transport/routing/plan-gate.ts +7 -1
- package/src/transport/routing/round-robin.ts +60 -0
- package/src/transport/routing/route-catalog.ts +123 -111
- package/src/transport/routing/route-model.ts +51 -9
- package/src/transport/routing/router.ts +393 -32
- package/src/transport/shutdown-notice.ts +1 -1
- package/src/transport/surface/adapters.ts +11 -2
- package/src/transport/surface/chat/adapter.ts +2 -2
- package/src/transport/surface/chat/encode.ts +47 -4
- package/src/transport/surface/chat/parse.ts +2 -12
- package/src/transport/surface/completion.ts +44 -33
- package/src/transport/surface/content-parts.ts +21 -0
- package/src/transport/surface/messages/adapter.ts +7 -1
- package/src/transport/surface/messages/encode.ts +11 -3
- package/src/transport/surface/messages/parse.ts +2 -1
- package/src/transport/surface/messages/stream.ts +33 -17
- package/src/transport/surface/responses/encode.ts +22 -5
- package/src/transport/surface/responses/parse.ts +3 -11
- package/src/workers/buddy-growth-tasks.ts +8 -8
- package/src/workers/daily-checkin.ts +4 -4
- package/src/workers/quota-refresh-sweep.ts +1 -1
- package/src/workers/zcode-gift-claim.ts +1 -1
- package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
- package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
- package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
- package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
- package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
- package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
- package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
- package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
- package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
- package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
- package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
- package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
- package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
- package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
- package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
- package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
- package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
- package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
- package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
- package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
- package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
- package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
- package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
- package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
- package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
- package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
- package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
- package/src/network/pool-health.ts +0 -210
- package/src/providers/bundled-discovery-providers.ts +0 -98
- package/src/providers/configured-openai-providers.ts +0 -120
- package/src/providers/integrations/buddy/buddy-checkin.ts +0 -521
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +0 -76
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -130
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +0 -234
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -496
- package/src/providers/integrations/buddy/buddy-identity.ts +0 -300
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +0 -341
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +0 -417
- package/src/providers/integrations/buddy/buddy-server-tools.ts +0 -240
- package/src/providers/integrations/buddy/buddy-usage-shared.ts +0 -221
- package/src/providers/integrations/buddy/buddy-usage-store.ts +0 -181
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +0 -37
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +0 -185
- package/src/providers/integrations/buddy/codebuddy/oauth.ts +0 -47
- package/src/providers/integrations/buddy/codebuddy/provider.ts +0 -175
- package/src/providers/integrations/buddy/codebuddy/quota.ts +0 -154
- package/src/providers/integrations/qoder/chat-proto.ts +0 -328
- package/src/providers/integrations/qoder/qoder-grpc.ts +0 -871
- package/src/providers/integrations/qoder/qoder-ide.ts +0 -588
- package/src/providers/operations/model-discovery-cache.ts +0 -34
- package/src/providers/operations/probe-fetch.ts +0 -15
- package/src/providers/operations/provider-version-cache.ts +0 -30
- package/src/providers/provider-capabilities.ts +0 -33
- package/src/providers/provider-credential-envelope.ts +0 -43
- /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-C0gTXEoN.js} +0 -0
- /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-C0gTXEoN.js.gz} +0 -0
- /package/src/network/{http2-bidi.ts → bidi.ts} +0 -0
- /package/src/network/pool/{pool-scripts.ts → scripts.ts} +0 -0
- /package/src/network/pool/{pool-state.ts → state.ts} +0 -0
- /package/src/network/{backoff.ts → util/backoff.ts} +0 -0
- /package/src/network/{deduplication.ts → util/dedupe.ts} +0 -0
- /package/src/network/{ip-utils.ts → util/ip.ts} +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { open, readdir } from "node:fs/promises";
|
|
2
1
|
import { join } from "node:path";
|
|
3
|
-
import { resolveTelemetryRetentionDays } from "../config/telemetry";
|
|
4
2
|
import { resolveDataDir } from "../persistence/db-mode";
|
|
5
|
-
import { hourStamp } from "../shared/time";
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
|
-
* Per-request telemetry
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
5
|
+
* Per-request telemetry is mirrored to hourly JSONL files and the durable
|
|
6
|
+
* `telemetry_events` table. This module is the shared home for the raw-file
|
|
7
|
+
* directory constant and the raw record type; the canonical reader is
|
|
8
|
+
* `event-query.ts` (SQL), and the reconciliation job (`telemetry-reconciliation.ts`)
|
|
9
|
+
* replays the raw mirror. The file-based reader that once lived here is gone —
|
|
10
|
+
* it was used only by tests and by this module itself.
|
|
12
11
|
*/
|
|
12
|
+
|
|
13
13
|
/**
|
|
14
14
|
* Where raw hourly event files live.
|
|
15
15
|
*
|
|
@@ -64,357 +64,3 @@ export interface TelemetryEventRecord {
|
|
|
64
64
|
*/
|
|
65
65
|
readonly degenerateStop?: boolean;
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
function asString(value: unknown): string | null {
|
|
69
|
-
return typeof value === "string" ? value : null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function asNumber(value: unknown): number | null {
|
|
73
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
74
|
-
if (typeof value === "string" && value.length > 0 && Number.isFinite(Number(value))) return Number(value);
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function normalize(raw: Record<string, unknown>): TelemetryEventRecord | undefined {
|
|
79
|
-
const tenantId = asString(raw["tenantId"]);
|
|
80
|
-
const requestId = asString(raw["requestId"]);
|
|
81
|
-
const createdAt = asString(raw["createdAt"]);
|
|
82
|
-
// No time means the row cannot be ordered or aged out. Inventing one made
|
|
83
|
-
// every undated row reappear as "just now" on each read.
|
|
84
|
-
if (!tenantId || !requestId || !createdAt) return undefined;
|
|
85
|
-
return {
|
|
86
|
-
id: asString(raw["id"]) ?? requestId,
|
|
87
|
-
createdAt,
|
|
88
|
-
tenantId,
|
|
89
|
-
requestId,
|
|
90
|
-
sourceSurface: asString(raw["sourceSurface"]),
|
|
91
|
-
requestedModel: asString(raw["requestedModel"]),
|
|
92
|
-
reportedModel: asString(raw["reportedModel"]),
|
|
93
|
-
requestedEffort: asString(raw["requestedEffort"]),
|
|
94
|
-
providerId: asString(raw["providerId"]),
|
|
95
|
-
accountId: asString(raw["accountId"]),
|
|
96
|
-
networkPoolId: asString(raw["networkPoolId"]),
|
|
97
|
-
endpoint: asString(raw["endpoint"]),
|
|
98
|
-
apiKeyId: asString(raw["apiKeyId"]),
|
|
99
|
-
userAgent: asString(raw["userAgent"]),
|
|
100
|
-
clientIp: asString(raw["clientIp"]),
|
|
101
|
-
latencyMs: asNumber(raw["latencyMs"]),
|
|
102
|
-
ttfbMs: asNumber(raw["ttfbMs"]),
|
|
103
|
-
stream: typeof raw["stream"] === "boolean" ? raw["stream"] : null,
|
|
104
|
-
status: asString(raw["status"]),
|
|
105
|
-
httpStatus: asNumber(raw["httpStatus"]),
|
|
106
|
-
errorOrigin: asString(raw["errorOrigin"]),
|
|
107
|
-
errorCategory: asString(raw["errorCategory"]),
|
|
108
|
-
inputTokens: asNumber(raw["inputTokens"]),
|
|
109
|
-
cachedInputTokens: asNumber(raw["cachedInputTokens"]),
|
|
110
|
-
outputTokens: asNumber(raw["outputTokens"]),
|
|
111
|
-
reasoningTokens: asNumber(raw["reasoningTokens"]),
|
|
112
|
-
estimatedCostUsd: asNumber(raw["estimatedCostUsd"]),
|
|
113
|
-
creditUsed: asNumber(raw["creditUsed"]),
|
|
114
|
-
resolveMs: asNumber(raw["resolveMs"]),
|
|
115
|
-
tokensPerSec: asNumber(raw["tokensPerSec"]),
|
|
116
|
-
firstContentDeltaAtMs: asNumber(raw["firstContentDeltaAtMs"]),
|
|
117
|
-
lastEventAtMs: asNumber(raw["lastEventAtMs"]),
|
|
118
|
-
...(raw["degenerateStop"] === true ? { degenerateStop: true } : {}),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* The comparable hour of an event file's name.
|
|
124
|
-
*
|
|
125
|
-
* A name is either `2026-10-22T05-15.jsonl` (an older window file) or
|
|
126
|
-
* `2026-10-22.events.jsonl` (a day file). Both sort against `hourStamp`: only
|
|
127
|
-
* the leading date and hour are taken, so the day file's `.events` suffix cannot
|
|
128
|
-
* make it compare as later or earlier than the window files beside it.
|
|
129
|
-
*/
|
|
130
|
-
function eventFileHour(file: string): string {
|
|
131
|
-
const stamp = file.endsWith(".events.jsonl") ? `${file.slice(0, 10)}T00` : file.slice(0, 13);
|
|
132
|
-
return stamp;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** Chunk size for the streaming scan: one line can be long, a file cannot. */
|
|
136
|
-
const READ_CHUNK_BYTES = 256 * 1024;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* The newest matching events in one file, read backwards from the end.
|
|
140
|
-
*
|
|
141
|
-
* Reads fixed blocks from the tail until the page is full, so a day file of any
|
|
142
|
-
* size costs one block per page rather than a full parse. A partial line at the
|
|
143
|
-
* very end is dropped: the writer may still be appending to it.
|
|
144
|
-
*/
|
|
145
|
-
async function* readEventFileTail(
|
|
146
|
-
file: string,
|
|
147
|
-
tenantId: string | undefined,
|
|
148
|
-
want: number,
|
|
149
|
-
since: number | undefined,
|
|
150
|
-
until: number | undefined,
|
|
151
|
-
): AsyncGenerator<TelemetryEventRecord> {
|
|
152
|
-
if (want <= 0) return;
|
|
153
|
-
const handle = await open(join(TELEMETRY_EVENT_DIR, file), "r").catch(() => undefined);
|
|
154
|
-
if (handle === undefined) return;
|
|
155
|
-
try {
|
|
156
|
-
const size = (await handle.stat().catch(() => undefined))?.size ?? 0;
|
|
157
|
-
if (size === 0) return;
|
|
158
|
-
const BLOCK = 64 * 1024;
|
|
159
|
-
let end = size;
|
|
160
|
-
let carry = "";
|
|
161
|
-
const found: TelemetryEventRecord[] = [];
|
|
162
|
-
while (end > 0 && found.length < want) {
|
|
163
|
-
const start = Math.max(0, end - BLOCK);
|
|
164
|
-
const length = end - start;
|
|
165
|
-
const buffer = Buffer.alloc(length);
|
|
166
|
-
await handle.read(buffer, 0, length, start);
|
|
167
|
-
const text = buffer.toString("utf8") + carry;
|
|
168
|
-
const lines = text.split("\n");
|
|
169
|
-
carry = lines.shift() ?? "";
|
|
170
|
-
// Newest first: the block was read from the end, so its lines run
|
|
171
|
-
// backwards through the file.
|
|
172
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
173
|
-
const line = lines[index] ?? "";
|
|
174
|
-
if (line.length === 0) continue;
|
|
175
|
-
if (tenantId !== undefined && !line.includes(tenantId)) continue;
|
|
176
|
-
let parsed: TelemetryEventRecord | undefined;
|
|
177
|
-
try {
|
|
178
|
-
const value: unknown = JSON.parse(line);
|
|
179
|
-
if (typeof value === "object" && value !== null) {
|
|
180
|
-
parsed = value as TelemetryEventRecord;
|
|
181
|
-
}
|
|
182
|
-
} catch {
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
if (parsed === undefined) continue;
|
|
186
|
-
if (parsed.tenantId !== tenantId) continue;
|
|
187
|
-
const at = Date.parse(parsed.createdAt);
|
|
188
|
-
if (Number.isFinite(at)) {
|
|
189
|
-
if (since !== undefined && at < since) continue;
|
|
190
|
-
if (until !== undefined && at > until) continue;
|
|
191
|
-
}
|
|
192
|
-
found.push(parsed);
|
|
193
|
-
if (found.length >= want) break;
|
|
194
|
-
}
|
|
195
|
-
end = start;
|
|
196
|
-
}
|
|
197
|
-
for (const event of found) yield event;
|
|
198
|
-
} finally {
|
|
199
|
-
await handle.close().catch(() => undefined);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async function* readEventFiles(
|
|
204
|
-
files: readonly string[],
|
|
205
|
-
allow?: (line: string) => boolean,
|
|
206
|
-
): AsyncGenerator<TelemetryEventRecord> {
|
|
207
|
-
for (const file of files) {
|
|
208
|
-
const handle = await open(join(TELEMETRY_EVENT_DIR, file), "r").catch(() => undefined);
|
|
209
|
-
if (handle === undefined) continue;
|
|
210
|
-
// Chunked, because `readFile` + `split` held the whole file as a string plus
|
|
211
|
-
// an array of one string per line — several times the file's size, per
|
|
212
|
-
// concurrent read, on the same process that serves every request. Peak
|
|
213
|
-
// memory is now one chunk and one line.
|
|
214
|
-
const decoder = new TextDecoder();
|
|
215
|
-
const buffer = Buffer.allocUnsafe(READ_CHUNK_BYTES);
|
|
216
|
-
let carry = "";
|
|
217
|
-
try {
|
|
218
|
-
for (;;) {
|
|
219
|
-
const { bytesRead } = await handle.read(buffer, 0, READ_CHUNK_BYTES);
|
|
220
|
-
if (bytesRead === 0) break;
|
|
221
|
-
const text = carry + decoder.decode(buffer.subarray(0, bytesRead), { stream: true });
|
|
222
|
-
const lines = text.split(String.fromCharCode(10));
|
|
223
|
-
// The tail is a partial line unless the chunk ended exactly on a newline.
|
|
224
|
-
carry = lines.pop() ?? "";
|
|
225
|
-
for (const line of lines) {
|
|
226
|
-
if (!line) continue;
|
|
227
|
-
// A line that cannot match is skipped before `JSON.parse`. The log is
|
|
228
|
-
// shared by every tenant, so most of its bytes belong to someone else —
|
|
229
|
-
// parsing them was the entire cost of a console read.
|
|
230
|
-
if (allow !== undefined && !allow(line)) continue;
|
|
231
|
-
try {
|
|
232
|
-
const event = normalize(JSON.parse(line) as Record<string, unknown>);
|
|
233
|
-
if (event) yield event;
|
|
234
|
-
} catch {
|
|
235
|
-
// A torn tail from a crash is skipped, not fatal. The next line stands alone.
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (carry.length > 0) {
|
|
240
|
-
const line = carry + decoder.decode();
|
|
241
|
-
if (allow === undefined || allow(line)) {
|
|
242
|
-
try {
|
|
243
|
-
const event = normalize(JSON.parse(line) as Record<string, unknown>);
|
|
244
|
-
if (event) yield event;
|
|
245
|
-
} catch {
|
|
246
|
-
// A torn tail from a crash is skipped, not fatal.
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
} finally {
|
|
251
|
-
await handle.close();
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/** Reads every retained event, newest first. A missing directory is an empty log. */
|
|
257
|
-
export async function readTelemetryEvents(): Promise<TelemetryEventRecord[]> {
|
|
258
|
-
const files = await readdir(TELEMETRY_EVENT_DIR).catch(() => [] as string[]);
|
|
259
|
-
const events: TelemetryEventRecord[] = [];
|
|
260
|
-
for await (const event of readEventFiles(files.filter((file) => file.endsWith(".jsonl")))) events.push(event);
|
|
261
|
-
events.sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0));
|
|
262
|
-
return events;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
interface EventQuery {
|
|
266
|
-
readonly tenantId?: string;
|
|
267
|
-
readonly since?: Date;
|
|
268
|
-
/**
|
|
269
|
-
* Exclusive upper bound on `createdAt`, used to push a pagination cursor into
|
|
270
|
-
* the query instead of filtering after the whole window was parsed. Without
|
|
271
|
-
* it a cursor page drops `limit` — the reader cannot know where the page
|
|
272
|
-
* ends — so only the first page of a list is bounded.
|
|
273
|
-
*/
|
|
274
|
-
readonly until?: Date;
|
|
275
|
-
readonly apiKeyIds?: readonly string[];
|
|
276
|
-
readonly accountIds?: readonly string[];
|
|
277
|
-
readonly requestId?: string;
|
|
278
|
-
/**
|
|
279
|
-
* Distinct from {@link requestId}: `id` defaults to the request id when a row
|
|
280
|
-
* does not carry one of its own (`event-log.ts:68`), so a row can match by
|
|
281
|
-
* either. A lookup by `id` short-circuits like `requestId` does, which is the
|
|
282
|
-
* whole point — without it a single-event detail read parses the retention
|
|
283
|
-
* window and then scans it in JS.
|
|
284
|
-
*/
|
|
285
|
-
readonly id?: string;
|
|
286
|
-
readonly httpStatus?: number;
|
|
287
|
-
readonly limit?: number;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function matches(event: TelemetryEventRecord, query: EventQuery, since: number | undefined, until: number | undefined, keys: ReadonlySet<string> | undefined, accounts: ReadonlySet<string> | undefined): boolean {
|
|
291
|
-
if (query.tenantId !== undefined && event.tenantId !== query.tenantId) return false;
|
|
292
|
-
const at = new Date(event.createdAt).getTime();
|
|
293
|
-
if (since !== undefined && at < since) return false;
|
|
294
|
-
if (until !== undefined && at > until) return false;
|
|
295
|
-
if (keys && (event.apiKeyId === null || !keys.has(event.apiKeyId))) return false;
|
|
296
|
-
if (accounts && (event.accountId === null || !accounts.has(event.accountId))) return false;
|
|
297
|
-
if (query.requestId !== undefined && event.requestId !== query.requestId) return false;
|
|
298
|
-
if (query.id !== undefined && event.id !== query.id) return false;
|
|
299
|
-
if (query.httpStatus !== undefined && event.httpStatus !== query.httpStatus) return false;
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/** The events matching a query, already newest first. */
|
|
304
|
-
export async function queryTelemetryEvents(query: EventQuery): Promise<TelemetryEventRecord[]> {
|
|
305
|
-
// Hourly request files only. A `.compacted.jsonl` also ends in `.jsonl`, and
|
|
306
|
-
// letting one through meant every summary row was parsed as if it were a
|
|
307
|
-
// request — a row with no `tenantId`/`createdAt` that then failed `matches`,
|
|
308
|
-
// but only after paying for the parse.
|
|
309
|
-
//
|
|
310
|
-
// The lower bound is the retention window, not just `since`: a caller that
|
|
311
|
-
// asks for all time still must not scan files that have already been aged
|
|
312
|
-
// out, and one that passes no `since` at all must not read every file ever
|
|
313
|
-
// written. Retention removes whole raw files; no summary is substituted.
|
|
314
|
-
const retentionFloor = hourStamp(
|
|
315
|
-
new Date(Date.now() - resolveTelemetryRetentionDays() * 24 * 60 * 60_000),
|
|
316
|
-
);
|
|
317
|
-
const sinceFloor =
|
|
318
|
-
query.since === undefined ? retentionFloor : hourStamp(query.since);
|
|
319
|
-
const floor = sinceFloor > retentionFloor ? sinceFloor : retentionFloor;
|
|
320
|
-
const files = (await readdir(TELEMETRY_EVENT_DIR).catch(() => [] as string[]))
|
|
321
|
-
.filter(
|
|
322
|
-
(file) =>
|
|
323
|
-
file.endsWith(".jsonl") &&
|
|
324
|
-
!file.endsWith(".compacted.jsonl") &&
|
|
325
|
-
eventFileHour(file) >= floor,
|
|
326
|
-
)
|
|
327
|
-
.sort((a, b) => (a < b ? 1 : a > b ? -1 : 0));
|
|
328
|
-
const since = query.since?.getTime();
|
|
329
|
-
const until = query.until?.getTime();
|
|
330
|
-
const keys = query.apiKeyIds ? new Set(query.apiKeyIds) : undefined;
|
|
331
|
-
const accounts = query.accountIds ? new Set(query.accountIds) : undefined;
|
|
332
|
-
const events: TelemetryEventRecord[] = [];
|
|
333
|
-
// The tenant and request ids are plain substrings of the stored line, so they
|
|
334
|
-
// are checked on the raw text: a UUID appearing anywhere in the row is that
|
|
335
|
-
// field, and a false negative is not possible while a false positive costs
|
|
336
|
-
// only the parse it would have paid anyway.
|
|
337
|
-
const allow =
|
|
338
|
-
query.tenantId === undefined && query.requestId === undefined && query.id === undefined
|
|
339
|
-
? undefined
|
|
340
|
-
: (line: string): boolean =>
|
|
341
|
-
(query.tenantId === undefined || line.includes(query.tenantId)) &&
|
|
342
|
-
(query.requestId === undefined || line.includes(query.requestId)) &&
|
|
343
|
-
(query.id === undefined || line.includes(query.id));
|
|
344
|
-
// A single-row lookup: stop at the first (newest) match instead of parsing
|
|
345
|
-
// the whole retention window.
|
|
346
|
-
const oneRow = query.requestId !== undefined || query.id !== undefined;
|
|
347
|
-
// With one file per hour, a plain page of the newest rows does not need to
|
|
348
|
-
// parse the whole retention window to return fifty lines. When nothing filters on
|
|
349
|
-
// content — no status, no key, no account — the newest rows are the last
|
|
350
|
-
// lines, so they are read backwards from the end and the scan stops once the
|
|
351
|
-
// page is full.
|
|
352
|
-
const tailOnly =
|
|
353
|
-
query.limit !== undefined &&
|
|
354
|
-
!oneRow &&
|
|
355
|
-
query.httpStatus === undefined &&
|
|
356
|
-
query.apiKeyIds === undefined &&
|
|
357
|
-
query.accountIds === undefined;
|
|
358
|
-
if (tailOnly) {
|
|
359
|
-
const newest: TelemetryEventRecord[] = [];
|
|
360
|
-
for (const file of files) {
|
|
361
|
-
for await (const event of readEventFileTail(file, query.tenantId, query.limit - newest.length, since, until)) {
|
|
362
|
-
newest.push(event);
|
|
363
|
-
}
|
|
364
|
-
if (newest.length >= query.limit) break;
|
|
365
|
-
}
|
|
366
|
-
return newest.slice(0, query.limit);
|
|
367
|
-
}
|
|
368
|
-
for (const file of files) {
|
|
369
|
-
const matched: TelemetryEventRecord[] = [];
|
|
370
|
-
for await (const event of readEventFiles([file], allow)) {
|
|
371
|
-
if (!matches(event, query, since, until, keys, accounts)) continue;
|
|
372
|
-
matched.push(event);
|
|
373
|
-
if (oneRow) return matched;
|
|
374
|
-
}
|
|
375
|
-
matched.sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0));
|
|
376
|
-
events.push(...matched);
|
|
377
|
-
if (query.limit !== undefined && events.length >= query.limit) return events.slice(0, query.limit);
|
|
378
|
-
}
|
|
379
|
-
return events;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
interface EventTotals {
|
|
383
|
-
requests: number;
|
|
384
|
-
succeeded: number;
|
|
385
|
-
failed: number;
|
|
386
|
-
cancelled: number;
|
|
387
|
-
truncated: number;
|
|
388
|
-
inputTokens: number;
|
|
389
|
-
cachedTokens: number;
|
|
390
|
-
outputTokens: number;
|
|
391
|
-
estimatedCost: number;
|
|
392
|
-
unpriced: number;
|
|
393
|
-
latencySum: number;
|
|
394
|
-
latencyCount: number;
|
|
395
|
-
tokensPerSecSum: number;
|
|
396
|
-
tokensPerSecCount: number;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
export function emptyTotals(): EventTotals {
|
|
400
|
-
return { requests: 0, succeeded: 0, failed: 0, cancelled: 0, truncated: 0, inputTokens: 0, cachedTokens: 0, outputTokens: 0, estimatedCost: 0, unpriced: 0, latencySum: 0, latencyCount: 0, tokensPerSecSum: 0, tokensPerSecCount: 0 };
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
|
|
405
|
-
/** Folds one event into a totals bucket. `failed` counts any non-completed terminal status. */
|
|
406
|
-
export function accumulate(totals: EventTotals, event: TelemetryEventRecord): void {
|
|
407
|
-
totals.requests += 1;
|
|
408
|
-
if (event.status === "completed") totals.succeeded += 1;
|
|
409
|
-
else if (event.status === "cancelled") totals.cancelled += 1;
|
|
410
|
-
else if (event.status === "truncated") totals.truncated += 1;
|
|
411
|
-
else if (event.status !== null) totals.failed += 1;
|
|
412
|
-
totals.inputTokens += event.inputTokens ?? 0;
|
|
413
|
-
totals.cachedTokens += event.cachedInputTokens ?? 0;
|
|
414
|
-
totals.outputTokens += event.outputTokens ?? 0;
|
|
415
|
-
if (event.estimatedCostUsd !== null) totals.estimatedCost += event.estimatedCostUsd;
|
|
416
|
-
else if (event.status === "completed") totals.unpriced += 1;
|
|
417
|
-
if (event.latencyMs !== null) { totals.latencySum += event.latencyMs; totals.latencyCount += 1; }
|
|
418
|
-
if (event.tokensPerSec !== null) { totals.tokensPerSecSum += event.tokensPerSec; totals.tokensPerSecCount += 1; }
|
|
419
|
-
}
|
|
420
|
-
|
|
@@ -62,6 +62,15 @@ export function trackMemoryUsage(component: string, bytes: number): void {
|
|
|
62
62
|
memoryUsage.record(component, bytes);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The recorded memory figures keyed by component name (`rss`, `heap_used`,
|
|
67
|
+
* `heap_total`, `external`). This series is the canonical memory view the
|
|
68
|
+
* Prometheus gauges are derived from, so the two registries cannot disagree.
|
|
69
|
+
*/
|
|
70
|
+
export function memoryUsageBytes(): Record<string, number> {
|
|
71
|
+
return memoryUsage.snapshot();
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
/** Returns the current snapshot for the console performance endpoint. */
|
|
66
75
|
export function performanceMetricsSnapshot(): PerformanceMetricsSnapshot {
|
|
67
76
|
return {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import { metrics } from "./metrics";
|
|
13
|
-
import { trackMemoryUsage } from "./performance-metrics";
|
|
13
|
+
import { memoryUsageBytes, trackMemoryUsage } from "./performance-metrics";
|
|
14
14
|
import { log } from "./logger";
|
|
15
15
|
import { resolveMemoryLimitOverrideBytes } from "../config/timeouts";
|
|
16
16
|
import { CONFIG_SPEC, readFlag } from "../config/spec";
|
|
@@ -190,19 +190,24 @@ export class RuntimeMetricsSampler {
|
|
|
190
190
|
|
|
191
191
|
sample(): void {
|
|
192
192
|
const memory = process.memoryUsage();
|
|
193
|
-
metrics
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
// The performance-metrics memory series is the single memory view: record
|
|
194
|
+
// the four figures once, then derive the Prometheus gauges from that same
|
|
195
|
+
// series. `external` has no gauge equivalent, so it lives only in the
|
|
196
|
+
// series (and in the `/system/performance` snapshot).
|
|
197
|
+
trackMemoryUsage("rss", memory.rss);
|
|
198
|
+
trackMemoryUsage("heap_used", memory.heapUsed);
|
|
199
|
+
trackMemoryUsage("heap_total", memory.heapTotal);
|
|
200
|
+
trackMemoryUsage("external", memory.external);
|
|
201
|
+
const recordedMemory = memoryUsageBytes();
|
|
202
|
+
metrics.cartethyia_memory_rss_bytes.set(recordedMemory["rss"] ?? 0);
|
|
203
|
+
metrics.cartethyia_memory_heap_used_bytes.set(recordedMemory["heap_used"] ?? 0);
|
|
204
|
+
metrics.cartethyia_memory_heap_total_bytes.set(recordedMemory["heap_total"] ?? 0);
|
|
196
205
|
metrics.cartethyia_memory_limit_bytes.set(this.limitBytes ?? 0);
|
|
197
206
|
this.snapshot = {
|
|
198
|
-
rssBytes:
|
|
207
|
+
rssBytes: recordedMemory["rss"] ?? 0,
|
|
199
208
|
limitBytes: this.limitBytes,
|
|
200
209
|
sampledAtMs: Date.now(),
|
|
201
210
|
};
|
|
202
|
-
trackMemoryUsage("rss", memory.rss);
|
|
203
|
-
trackMemoryUsage("heap_used", memory.heapUsed);
|
|
204
|
-
trackMemoryUsage("heap_total", memory.heapTotal);
|
|
205
|
-
trackMemoryUsage("external", memory.external);
|
|
206
211
|
|
|
207
212
|
const roundRobin = this.sources.routingRoundRobinEntries();
|
|
208
213
|
metrics.cartethyia_routing_roundrobin_entries.set(roundRobin.combo, { scope: "combo" });
|
|
@@ -6,7 +6,7 @@ import { DEFAULT_BOUNDS } from "../config/network";
|
|
|
6
6
|
import { type SourceSurface, type UsageRecord } from "../protocol/canonical-model";
|
|
7
7
|
import { log } from "./logger";
|
|
8
8
|
import { scrubSecrets } from "./redaction";
|
|
9
|
-
import {
|
|
9
|
+
import { telemetryInsertRow, tokenCountToInt, type TelemetryInsertRowSource } from "./telemetry-row";
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -73,10 +73,6 @@ export interface TelemetryEventInput {
|
|
|
73
73
|
readonly degenerateStop?: boolean;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function tokenCountToInt(value: number | "unavailable" | undefined): number | undefined {
|
|
77
|
-
return typeof value === "number" ? Math.round(value) : undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
76
|
/** Conservative byte estimate for queue accounting.
|
|
81
77
|
* Telemetry events contain only bounded scalar metadata; serializing every
|
|
82
78
|
* event just to measure queue size added a JSON allocation on every request.
|
|
@@ -104,23 +100,19 @@ function estimateTelemetryEventBytes(event: TelemetryEventInput): number {
|
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
/** Maps a durable event to its `telemetry_events` insert row for the
|
|
107
|
-
* batched multi-row writer (`TelemetryBatchBuffer.drain`).
|
|
103
|
+
* batched multi-row writer (`TelemetryBatchBuffer.drain`). Normalizes the
|
|
104
|
+
* `TelemetryEventInput` shape (nested `usage`, token-count coercion) into the
|
|
105
|
+
* shared record shape; the column spelling itself lives in
|
|
106
|
+
* {@link telemetryInsertRow}, which the reconciliation backfill shares. */
|
|
108
107
|
function telemetryEventRow(event: TelemetryEventInput) {
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
// Stable identity: the request's own id. Retries reuse the materialized
|
|
115
|
-
// row (the buffer retries the same rows across its five attempts), and
|
|
116
|
-
// the (tenant_id, request_id) unique boundary makes a re-drain or a
|
|
117
|
-
// cross-process race converge to one row via ON CONFLICT DO NOTHING
|
|
118
|
-
// instead of double-charging.
|
|
108
|
+
// Only the normalization lives here: collapse the nested `usage` record onto
|
|
109
|
+
// the flat shared shape, and coerce token counts to the integers the schema
|
|
110
|
+
// columns store. Every field below has a live producer; the column spelling
|
|
111
|
+
// and the client-name derivation are `telemetryInsertRow`'s.
|
|
112
|
+
const source: TelemetryInsertRowSource = {
|
|
119
113
|
id: event.requestId,
|
|
120
114
|
tenantId: event.tenantId,
|
|
121
115
|
requestId: event.requestId,
|
|
122
|
-
// Stamped here, not by the reader. A row without it has no real time, and a
|
|
123
|
-
// reader that invented one made every such row look like it happened just now.
|
|
124
116
|
createdAt: new Date(),
|
|
125
117
|
sourceSurface: event.sourceSurface,
|
|
126
118
|
requestedModel: event.requestedModel,
|
|
@@ -129,8 +121,6 @@ function telemetryEventRow(event: TelemetryEventInput) {
|
|
|
129
121
|
endpoint: event.endpoint ?? null,
|
|
130
122
|
apiKeyId: event.apiKeyId ?? null,
|
|
131
123
|
userAgent: event.userAgent ?? null,
|
|
132
|
-
// Canonical client grouping, stamped once so SQL breakdowns group by it.
|
|
133
|
-
clientName: clientNameFromUserAgent(event.userAgent),
|
|
134
124
|
clientIp: event.clientIp ?? null,
|
|
135
125
|
providerId: event.providerId ?? null,
|
|
136
126
|
accountId: event.accountId ?? null,
|
|
@@ -152,18 +142,18 @@ function telemetryEventRow(event: TelemetryEventInput) {
|
|
|
152
142
|
estimatedCostUsd:
|
|
153
143
|
event.usage?.estimated_cost === null || event.usage === undefined
|
|
154
144
|
? null
|
|
155
|
-
:
|
|
145
|
+
: event.usage.estimated_cost,
|
|
156
146
|
// Provider-billed credit (Tencent buddy-meter `credit`, Qoder `credits`);
|
|
157
147
|
// NULL when the upstream reported no credit field.
|
|
158
|
-
|
|
159
|
-
creditUsed: event.usage?.credit_used != null ? String(event.usage.credit_used) : null,
|
|
148
|
+
creditUsed: event.usage?.credit_used ?? null,
|
|
160
149
|
resolveMs: event.resolveMs ?? null,
|
|
161
|
-
tokensPerSec: event.tokensPerSec
|
|
150
|
+
tokensPerSec: event.tokensPerSec ?? null,
|
|
162
151
|
firstContentDeltaAtMs: event.firstContentDeltaAtMs ?? null,
|
|
163
152
|
lastEventAtMs: event.lastEventAtMs ?? null,
|
|
164
153
|
payloadCaptureState: event.payloadCaptureState ?? null,
|
|
165
|
-
degenerateStop: event.degenerateStop
|
|
154
|
+
...(event.degenerateStop !== undefined ? { degenerateStop: event.degenerateStop } : {}),
|
|
166
155
|
};
|
|
156
|
+
return telemetryInsertRow(source);
|
|
167
157
|
}
|
|
168
158
|
|
|
169
159
|
interface TelemetryBatchBufferOptions {
|
|
@@ -184,7 +174,7 @@ const DRAIN_MAX_ATTEMPTS = 5;
|
|
|
184
174
|
const DRAIN_RETRY_BASE_MS = 25;
|
|
185
175
|
const DRAIN_RETRY_MAX_MS = 1_000;
|
|
186
176
|
|
|
187
|
-
import { exponentialBackoff, type BackoffOptions } from "../network/backoff";
|
|
177
|
+
import { exponentialBackoff, type BackoffOptions } from "../network/util/backoff";
|
|
188
178
|
|
|
189
179
|
/** Exponential retry delay for the `attempt`-th retry, clamped to the ceiling. */
|
|
190
180
|
function drainRetryDelayMs(attempt: number, baseDelayMs = DRAIN_RETRY_BASE_MS): number {
|
|
@@ -33,7 +33,7 @@ import { join } from "node:path";
|
|
|
33
33
|
import type { CartethyiaDatabase } from "../persistence/db-handle";
|
|
34
34
|
import { telemetryEvents, telemetryReconciliationState } from "../persistence/schema";
|
|
35
35
|
import { TELEMETRY_EVENT_DIR } from "./event-log";
|
|
36
|
-
import {
|
|
36
|
+
import { telemetryInsertRow } from "./telemetry-row";
|
|
37
37
|
import type { TelemetryEventRecord } from "./event-log";
|
|
38
38
|
import { hourStamp } from "../shared/time";
|
|
39
39
|
|
|
@@ -143,43 +143,15 @@ function parseChunk(text: string): { readonly events: TelemetryEventRecord[]; re
|
|
|
143
143
|
return { events, carry };
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
/** The columns the backfill inserts.
|
|
146
|
+
/** The columns the backfill inserts. Shared with the write path via
|
|
147
|
+
* {@link telemetryInsertRow}, so a recovered row carries exactly the columns
|
|
148
|
+
* the request path persists — including creditUsed, resolveMs, tokensPerSec,
|
|
149
|
+
* firstContentDeltaAtMs, lastEventAtMs, payloadCaptureState, and degenerateStop,
|
|
150
|
+
* which a hand-maintained mirror used to drop. */
|
|
147
151
|
function toRow(event: TelemetryEventRecord): typeof telemetryEvents.$inferInsert {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
id: event.requestId,
|
|
152
|
-
tenantId: event.tenantId!,
|
|
153
|
-
requestId: event.requestId,
|
|
154
|
-
sourceSurface: event.sourceSurface as typeof telemetryEvents.$inferInsert.sourceSurface,
|
|
155
|
-
requestedModel: event.requestedModel,
|
|
156
|
-
reportedModel: event.reportedModel,
|
|
157
|
-
requestedEffort: event.requestedEffort,
|
|
158
|
-
providerId: event.providerId,
|
|
159
|
-
accountId: event.accountId,
|
|
160
|
-
networkPoolId: event.networkPoolId,
|
|
161
|
-
endpoint: event.endpoint,
|
|
162
|
-
apiKeyId: event.apiKeyId,
|
|
163
|
-
userAgent: event.userAgent,
|
|
164
|
-
// Derived the same way ingestion derives it, so a backfilled row groups
|
|
165
|
-
// with the rows the request path wrote instead of forming its own bucket.
|
|
166
|
-
clientName: clientNameFromUserAgent(event.userAgent),
|
|
167
|
-
clientIp: event.clientIp,
|
|
168
|
-
latencyMs: event.latencyMs,
|
|
169
|
-
ttfbMs: event.ttfbMs,
|
|
170
|
-
stream: event.stream,
|
|
171
|
-
httpStatus: event.httpStatus,
|
|
172
|
-
status: event.status as typeof telemetryEvents.$inferInsert.status,
|
|
173
|
-
errorCategory: event.errorCategory,
|
|
174
|
-
errorOrigin: event.errorOrigin,
|
|
175
|
-
inputTokens: event.inputTokens,
|
|
176
|
-
cachedInputTokens: event.cachedInputTokens,
|
|
177
|
-
outputTokens: event.outputTokens,
|
|
178
|
-
reasoningTokens: event.reasoningTokens,
|
|
179
|
-
estimatedCostUsd:
|
|
180
|
-
event.estimatedCostUsd === null ? null : String(event.estimatedCostUsd),
|
|
181
|
-
createdAt: new Date(event.createdAt),
|
|
182
|
-
};
|
|
152
|
+
// `parseChunk` guarantees `tenantId` is a string before a record reaches
|
|
153
|
+
// here; the type keeps it optional because the raw shape predates that gate.
|
|
154
|
+
return telemetryInsertRow({ ...event, tenantId: event.tenantId! });
|
|
183
155
|
}
|
|
184
156
|
|
|
185
157
|
/**
|