cartethyia 2.5.3 → 2.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -4
- package/README.md +2 -2
- package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
- package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
- package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
- package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
- package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
- package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
- package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
- package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
- package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
- package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
- package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
- package/dist/dashboard/assets/tokens-COzario9.js +1 -0
- package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
- package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -57
- package/migrations/README.md +52 -52
- package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
- package/package.json +96 -95
- package/scripts/cli/admin.ts +125 -0
- package/scripts/cli/autostart.ts +237 -0
- package/scripts/cli/{ui/background.ts → background.ts} +1 -1
- package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
- package/scripts/cli/doctor.ts +37 -0
- package/scripts/cli/{support/env.ts → env.ts} +99 -99
- package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
- package/scripts/cli/instance.ts +224 -0
- package/scripts/cli/main.ts +88 -18
- package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
- package/scripts/cli/port.ts +100 -0
- package/scripts/cli/{support/services.ts → services.ts} +88 -88
- package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
- package/scripts/cli/{command/start.ts → start.ts} +10 -5
- package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
- package/scripts/cli/tray/index.ts +62 -12
- package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
- package/scripts/cli/update.ts +40 -0
- package/scripts/cli/version.ts +67 -0
- package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
- package/src/boot/app.ts +11 -7
- package/src/boot/dependencies.ts +50 -19
- package/src/boot/timeout.ts +2 -13
- package/src/config/cooldowns.ts +186 -138
- package/src/config/network.ts +8 -14
- package/src/config/providers.ts +7 -10
- package/src/config/runtime.ts +76 -76
- package/src/config/security.ts +15 -0
- package/src/config/spec.ts +52 -7
- package/src/config/telemetry.ts +26 -0
- package/src/config/timeouts.ts +0 -6
- package/src/console/admin/model-abuse/contracts.ts +28 -6
- package/src/console/admin/model-abuse/routes.ts +56 -3
- package/src/console/api-keys/bucket-spend.ts +15 -4
- package/src/console/api-keys/contracts.ts +410 -414
- package/src/console/api-keys/routes.ts +881 -880
- package/src/console/auth/credentials.ts +3 -3
- package/src/console/auth/setup-service.ts +2 -2
- package/src/console/backup/contracts.ts +15 -2
- package/src/console/backup/export-endpoint.ts +6 -2
- package/src/console/backup/nine-router.ts +2 -5
- package/src/console/backup/service.ts +4 -0
- package/src/console/backup/store.ts +192 -29
- package/src/console/backup/validate.ts +3 -45
- package/src/console/cli-tools/fs-ops.ts +487 -487
- package/src/console/console-router-guards.ts +1 -1
- package/src/console/console-router.ts +8 -0
- package/src/console/data-management/contracts.ts +100 -0
- package/src/console/data-management/operations.ts +234 -0
- package/src/console/data-management/routes.ts +103 -0
- package/src/console/domain-registration.ts +45 -1
- package/src/console/observability/contracts.ts +524 -485
- package/src/console/observability/live.ts +22 -0
- package/src/console/observability/store.ts +1138 -1018
- package/src/console/observability/usage-dimensions.ts +4 -4
- package/src/console/providers/catalog/catalog-projections.ts +6 -7
- package/src/console/providers/catalog/contracts.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +5 -2
- package/src/console/providers/catalog/provider-operations.ts +24 -4
- package/src/console/providers/catalog/public-model-store.ts +8 -11
- package/src/console/providers/catalog/routes.ts +7 -0
- package/src/console/providers/catalog/store.ts +1553 -1532
- package/src/console/providers/oauth/routes.ts +8 -12
- package/src/console/quota/account-quota-shared.ts +52 -39
- package/src/console/quota/account-quota-tenant.ts +6 -6
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/quota/refresh.ts +441 -429
- package/src/console/remote-support/contracts.ts +61 -0
- package/src/console/remote-support/routes.ts +65 -0
- package/src/console/remote-support/service.ts +207 -0
- package/src/console/response-compression.ts +0 -3
- package/src/console/routing/pools/import-parse.ts +206 -0
- package/src/console/routing/pools/operations.ts +543 -501
- package/src/console/routing/pools/probe-result.ts +3 -2
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -164
- package/src/console/routing/pools/store.ts +8 -8
- package/src/console/settings/contracts.ts +95 -2
- package/src/console/settings/store.ts +81 -3
- package/src/console/shared/cursor.ts +8 -8
- package/src/console/shared/errors.ts +1 -3
- package/src/console/updates/routes.ts +16 -0
- package/src/main.ts +10 -0
- package/src/network/http2-bidi.ts +2 -4
- package/src/network/pool/agent.ts +478 -478
- package/src/network/pool/selector.ts +5 -4
- package/src/network/retry.ts +1 -2
- package/src/network/ssrf.ts +5 -4
- package/src/observability/event-compaction.ts +317 -0
- package/src/observability/event-day-index.ts +440 -0
- package/src/observability/event-log.ts +155 -6
- package/src/observability/logger.ts +6 -6
- package/src/observability/payload-capture.ts +158 -33
- package/src/observability/payload-store.ts +82 -61
- package/src/observability/redaction.ts +5 -6
- package/src/observability/request-notify.ts +43 -0
- package/src/observability/telemetry-status.ts +8 -9
- package/src/observability/token-speed.ts +5 -4
- package/src/persistence/db-mode.ts +7 -7
- package/src/persistence/health-event-flush.ts +36 -0
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/schema.ts +75 -29
- package/src/persistence/shared-cache.ts +1 -2
- package/src/persistence/stores/api-key-store.ts +18 -6
- package/src/persistence/stores/share-store.ts +4 -3
- package/src/persistence/stores/telemetry-store.ts +33 -11
- package/src/protocol/canonical-model.ts +11 -3
- package/src/protocol/codecs.ts +139 -6
- package/src/protocol/gateway-error.ts +6 -4
- package/src/protocol/image-clamp.ts +19 -1
- package/src/protocol/registry.ts +8 -0
- package/src/protocol/request/chat.ts +21 -1
- package/src/protocol/request/codex.ts +10 -1
- package/src/protocol/request/gemini.ts +9 -2
- package/src/protocol/request/messages.ts +18 -7
- package/src/protocol/request/responses.ts +9 -1
- package/src/protocol/response/chat.ts +34 -4
- package/src/protocol/response/codex.ts +7 -4
- package/src/protocol/response/dsml.ts +140 -28
- package/src/protocol/response/responses.ts +11 -5
- package/src/protocol/stream-error-frames.ts +4 -5
- package/src/providers/adapters/compatible-adapter.ts +3 -0
- package/src/providers/authentication/jwt-validator.ts +9 -10
- package/src/providers/authentication/oauth-refresh-service.ts +9 -11
- package/src/providers/bundled-discovery-providers.ts +122 -119
- package/src/providers/configured-openai-providers.ts +4 -4
- package/src/providers/default-registry.ts +5 -4
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/catalog/catalog-format.ts +3 -6
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/openai-model-discovery.ts +5 -0
- package/src/providers/discovery/openrouter-free-roster.ts +15 -17
- package/src/providers/discovery/probe-phases.ts +4 -4
- package/src/providers/discovery/probe-wire.ts +6 -13
- package/src/providers/discovery/probing-service.ts +1117 -1045
- package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
- package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
- package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
- package/src/providers/integrations/buddy/buddy-media.ts +2 -2
- package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
- package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
- package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
- package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
- package/src/providers/integrations/claude/claude.ts +608 -608
- package/src/providers/integrations/cline/cline-quota.ts +27 -20
- package/src/providers/integrations/cline/cline.ts +537 -539
- package/src/providers/integrations/codex/codex-headers.ts +25 -39
- package/src/providers/integrations/codex/codex-oauth.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +2 -29
- package/src/providers/integrations/commandcode/commandcode.ts +674 -675
- package/src/providers/integrations/commandcode/discovery.ts +117 -116
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
- package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
- package/src/providers/integrations/cursor/cursor-request.ts +616 -471
- package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
- package/src/providers/integrations/cursor/cursor.ts +1333 -960
- package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
- package/src/providers/integrations/factory/factory-models.ts +3 -5
- package/src/providers/integrations/factory/factory.ts +8 -1
- package/src/providers/integrations/gemini.ts +6 -1
- package/src/providers/integrations/grok/grok-install-id.ts +2 -3
- package/src/providers/integrations/grok/grok-quota.ts +279 -177
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
- package/src/providers/integrations/grok/grok.ts +6 -7
- package/src/providers/integrations/hermes.ts +14 -18
- package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
- package/src/providers/integrations/kiro/kiro.ts +1 -1
- package/src/providers/integrations/mistral.ts +6 -6
- package/src/providers/integrations/opencode/fingerprint.ts +3 -3
- package/src/providers/integrations/opencode/provider.ts +19 -20
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
- package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
- package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
- package/src/providers/integrations/qoder/qoder.ts +884 -865
- package/src/providers/integrations/shared/connect.ts +6 -6
- package/src/providers/integrations/trae/trae-catalog.ts +1 -2
- package/src/providers/integrations/trae/trae-quota.ts +384 -0
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/integrations/zcode/oauth.ts +6 -1
- package/src/providers/operations/account-health-service.ts +75 -27
- package/src/providers/operations/account-reset-service.ts +1 -1
- package/src/providers/operations/client-versions.ts +6 -6
- package/src/providers/operations/custom-cli-headers.ts +3 -4
- package/src/providers/operations/provider-catalog-seeder.ts +95 -95
- package/src/providers/operations/provider-catalog-service.ts +3 -4
- package/src/providers/provider-contracts.ts +7 -0
- package/src/providers/provider-metadata.ts +10 -8
- package/src/providers/provider-registry.ts +6 -7
- package/src/providers/provider-usage.ts +1 -1
- package/src/providers/quota/quota-support.ts +8 -0
- package/src/security/abuse.ts +15 -10
- package/src/security/access-control.ts +144 -146
- package/src/security/admission/contracts.ts +11 -0
- package/src/security/admission/in-memory-store.ts +4 -3
- package/src/security/admission/lease-sweep.ts +6 -6
- package/src/security/admission/reasons.ts +5 -1
- package/src/security/admission/redis-store.ts +211 -210
- package/src/security/admission/service.ts +4 -0
- package/src/security/api-key-auth.ts +27 -17
- package/src/security/ip-boundary.ts +155 -15
- package/src/security/model-abuse.ts +366 -94
- package/src/security/outbound-headers.ts +3 -4
- package/src/shared/sleep.ts +4 -0
- package/src/transport/dispatch/attempt-finalize.ts +47 -5
- package/src/transport/dispatch/attempt-loop.ts +531 -530
- package/src/transport/dispatch/generation.ts +6 -0
- package/src/transport/dispatch/proxy-request.ts +24 -1
- package/src/transport/dispatch/ratelimit-capture.ts +37 -0
- package/src/transport/dispatch/stream-bridge.ts +1 -3
- package/src/transport/dispatch/streaming/finalize.ts +1 -1
- package/src/transport/dispatch/streaming/stream.ts +19 -6
- package/src/transport/dispatch/upstream.ts +9 -0
- package/src/transport/failure-policy.ts +162 -8
- package/src/transport/guards/auth-guard.ts +7 -4
- package/src/transport/guards/concurrency-guard.ts +0 -5
- package/src/transport/guards/error-lifecycle.ts +13 -6
- package/src/transport/guards/pipeline.ts +15 -3
- package/src/transport/guards/request-context.ts +8 -2
- package/src/transport/request/inflight.ts +5 -6
- package/src/transport/request/preparer.ts +9 -8
- package/src/transport/request/state.ts +21 -8
- package/src/transport/request/tenant-preferences.ts +60 -0
- package/src/transport/routing/admission.ts +4 -0
- package/src/transport/routing/route-catalog.ts +65 -10
- package/src/transport/routing/route-model.ts +46 -14
- package/src/transport/routing/router.ts +21 -14
- package/src/transport/surface/completion.ts +2 -3
- package/src/transport/surface/dialects.ts +5 -6
- package/src/transport/surface/responses/encode.ts +38 -0
- package/src/transport/surface/responses/parse.ts +4 -0
- package/src/transport/translation/capabilities.ts +2 -3
- package/src/transport/translation/quirks.ts +3 -3
- package/src/workers/buddy-growth-tasks.ts +5 -10
- package/src/workers/daily-checkin.ts +17 -23
- package/src/workers/quota-refresh-sweep.ts +26 -26
- package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
- package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
- package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
- package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
- package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
- package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
- package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
- package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
- package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
- package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
- package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
- package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
- package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
- package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
- package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
- package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
- package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
- package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
- package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
- package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
- package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
- package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
- package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
- package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
- package/scripts/cli/command/admin.ts +0 -65
- package/scripts/cli/command/doctor.ts +0 -257
- package/scripts/cli/command/update.ts +0 -23
- package/scripts/cli/gateway/drain-token.ts +0 -78
- package/scripts/cli/gateway/instance-lock.ts +0 -105
- package/scripts/cli/gateway/stop.ts +0 -51
- package/scripts/cli/support/bun.ts +0 -27
- package/scripts/cli/support/version.ts +0 -37
- /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
- /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
- /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
|
@@ -278,7 +278,7 @@ export class NetworkPoolSelector {
|
|
|
278
278
|
// One script, not `SET` then `SADD`: a crash between the two left the
|
|
279
279
|
// marker unindexed (invisible to the pool listing) or the index
|
|
280
280
|
// pointing at a marker that expired. The script also arms the index
|
|
281
|
-
// set's own TTL, which
|
|
281
|
+
// set's own TTL, which must be armed or the index grows without bound.
|
|
282
282
|
await redisEvalNumber(
|
|
283
283
|
this.redis,
|
|
284
284
|
COOLDOWN_FLAG_SCRIPT,
|
|
@@ -458,9 +458,10 @@ export class NetworkPoolSelector {
|
|
|
458
458
|
/**
|
|
459
459
|
* Removes one cooldown marker and its index entry in one atomic step.
|
|
460
460
|
*
|
|
461
|
-
* The two writes
|
|
462
|
-
*
|
|
463
|
-
* report a cooldown that no longer
|
|
461
|
+
* The two writes must not be separate `DEL` + `SREM`: a crash between them
|
|
462
|
+
* would leave the index listing a provider whose marker is gone — a pool
|
|
463
|
+
* would report a cooldown that no longer applies until the index entry
|
|
464
|
+
* expires.
|
|
464
465
|
*/
|
|
465
466
|
private async clearRedisCooldown(poolId: string, providerId: string): Promise<void> {
|
|
466
467
|
if (!this.redis) return;
|
package/src/network/retry.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { exponentialBackoff, type BackoffOptions } from "./backoff";
|
|
10
|
+
import { sleep as defaultSleep } from "../shared/sleep";
|
|
10
11
|
|
|
11
12
|
export interface RetryOptions {
|
|
12
13
|
readonly maxRetries?: number;
|
|
@@ -18,8 +19,6 @@ export interface RetryOptions {
|
|
|
18
19
|
readonly shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const defaultSleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
-
|
|
23
22
|
/**
|
|
24
23
|
* Runs `fn`, retrying on failure with exponential backoff (100 ms, 200 ms,
|
|
25
24
|
* 400 ms, …) capped at `maxDelayMs` and jittered by up to 25% to avoid a
|
package/src/network/ssrf.ts
CHANGED
|
@@ -73,10 +73,11 @@ function cidrContains(address: string, cidr: string): boolean {
|
|
|
73
73
|
const [network, prefixText] = cidr.split("/");
|
|
74
74
|
if (!network || prefixText === undefined) return false;
|
|
75
75
|
// A present-but-blank prefix (`"10.0.0.0/"`) must be refused, not coerced.
|
|
76
|
-
// `Number("")` is 0 and `""` is not `undefined`, so
|
|
77
|
-
//
|
|
78
|
-
// operator's typo therefore silently
|
|
79
|
-
// space, including the link-local metadata endpoint this guard exists to
|
|
76
|
+
// `Number("")` is 0 and `""` is not `undefined`, so a plain truthiness check
|
|
77
|
+
// here would leave `prefix = 0` — and a /0 admits every address of its family.
|
|
78
|
+
// An operator's typo would therefore silently widen the allowlist to the whole
|
|
79
|
+
// IPv4 space, including the link-local metadata endpoint this guard exists to
|
|
80
|
+
// refuse.
|
|
80
81
|
//
|
|
81
82
|
// Requiring a run of digits (after trimming) also refuses the exotic spellings
|
|
82
83
|
// `Number` accepts for zero — `-0`, `+0`, `0x0`, `0b0`, `0o0`, `0.0` — all of
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bucket primitives for the per-day telemetry index.
|
|
3
|
+
*
|
|
4
|
+
* Each day's events are folded into a `CompactedBucket` via `accumulate()` and
|
|
5
|
+
* stored as a `.index.json` file alongside the source `.events.jsonl`. Buckets
|
|
6
|
+
* are pure accumulators: every field is a running sum, min, or max — nothing
|
|
7
|
+
* is averaged or estimated away. `mergeBucketsInternal` adds two buckets so
|
|
8
|
+
* partial indexes can be merged without re-reading source files.
|
|
9
|
+
*/
|
|
10
|
+
import type { TelemetryEventRecord } from "./event-log";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Histogram bucket edges in milliseconds.
|
|
14
|
+
*
|
|
15
|
+
* Fixed rather than adaptive: a reader that wants P50/P95/P99 has to be able to
|
|
16
|
+
* interpret buckets it did not write, and an adaptive layout would have to be
|
|
17
|
+
* stored alongside every row. These follow the gateway's own latency
|
|
18
|
+
* histogram, so a compacted period and a live one read comparably.
|
|
19
|
+
*/
|
|
20
|
+
export const LATENCY_BUCKETS_MS = [
|
|
21
|
+
10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
export interface CompactedBucket {
|
|
25
|
+
readonly requests: number;
|
|
26
|
+
readonly inputTokens: number;
|
|
27
|
+
readonly cachedTokens: number;
|
|
28
|
+
readonly outputTokens: number;
|
|
29
|
+
readonly reasoningTokens: number;
|
|
30
|
+
readonly estimatedCostUsd: number;
|
|
31
|
+
readonly creditUsed: number;
|
|
32
|
+
readonly succeeded: number;
|
|
33
|
+
readonly failed: number;
|
|
34
|
+
readonly cancelled: number;
|
|
35
|
+
readonly truncated: number;
|
|
36
|
+
readonly latencySum: number;
|
|
37
|
+
readonly latencyCount: number;
|
|
38
|
+
readonly latencyMinMs: number | null;
|
|
39
|
+
readonly latencyMaxMs: number | null;
|
|
40
|
+
/** Counts per fixed latency bucket; `+Inf` is the last, overflow, bucket. */
|
|
41
|
+
readonly latencyHistogram: readonly number[];
|
|
42
|
+
readonly tokensPerSecSum: number;
|
|
43
|
+
readonly tokensPerSecCount: number;
|
|
44
|
+
/** Requests whose cost could not be priced. */
|
|
45
|
+
readonly unpriced: number;
|
|
46
|
+
/**
|
|
47
|
+
* Requests per breakdown dimension, kept so the Usage breakdown survives the
|
|
48
|
+
* source files being deleted.
|
|
49
|
+
*
|
|
50
|
+
* Only the *counts* are kept, never the events: a day's breakdown asks how
|
|
51
|
+
* many requests went to a model, not which ones. That is what makes this
|
|
52
|
+
* small enough to keep — a full event is ~1KB, a count is a few bytes.
|
|
53
|
+
*/
|
|
54
|
+
readonly byModel: Readonly<Record<string, number>>;
|
|
55
|
+
readonly byProvider: Readonly<Record<string, number>>;
|
|
56
|
+
readonly byClient: Readonly<Record<string, number>>;
|
|
57
|
+
readonly byClientIp: Readonly<Record<string, number>>;
|
|
58
|
+
/**
|
|
59
|
+
* Requests per HTTP status, keyed by the status as a decimal string.
|
|
60
|
+
*
|
|
61
|
+
* Kept as counts for the same reason the other dimensions are: the Usage
|
|
62
|
+
* summary's status chips and the request-list filters read these, and without
|
|
63
|
+
* them every chip reads zero once the day's events have been folded — the
|
|
64
|
+
* numbers were on the source events, which no longer exist.
|
|
65
|
+
*/
|
|
66
|
+
readonly byStatus: Readonly<Record<string, number>>;
|
|
67
|
+
/**
|
|
68
|
+
* Sum and count of gateway routing delay, so `avgResolveMs` survives
|
|
69
|
+
* compaction. Kept as a sum rather than an average of averages for the same
|
|
70
|
+
* reason the latency sum is: an average of per-file averages drifts as soon
|
|
71
|
+
* as the files hold different request counts.
|
|
72
|
+
*/
|
|
73
|
+
readonly resolveSumMs: number;
|
|
74
|
+
readonly resolveCount: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A number from a value that may be a numeric string; 0 when unusable.
|
|
79
|
+
*
|
|
80
|
+
* Exported so readers of a compacted bucket coerce its fields the same way the
|
|
81
|
+
* fold does: a second local copy of "is this a number" is how one caller ends
|
|
82
|
+
* up summing `undefined` into NaN while another reads it as zero.
|
|
83
|
+
*/
|
|
84
|
+
export function numeric(value: unknown): number {
|
|
85
|
+
const asNumber = typeof value === "number" ? value : Number(value);
|
|
86
|
+
return Number.isFinite(asNumber) ? asNumber : 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The two fields added after the first index format, read defensively.
|
|
91
|
+
*
|
|
92
|
+
* An index file written before they existed carries neither, and `undefined +
|
|
93
|
+
0` is `NaN` — which then propagates straight through every later fold and
|
|
94
|
+
* merge, so one old day turns the whole period's `avgResolveMs` into `NaN`.
|
|
95
|
+
* Reading them as 0/{} is the same answer an empty day gives, and a folded
|
|
96
|
+
* day that predates the field genuinely has no resolve measurement to report.
|
|
97
|
+
*/
|
|
98
|
+
function readResolveSum(bucket: CompactedBucket): number {
|
|
99
|
+
return numeric(bucket.resolveSumMs);
|
|
100
|
+
}
|
|
101
|
+
function readResolveCount(bucket: CompactedBucket): number {
|
|
102
|
+
return numeric(bucket.resolveCount);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Adds one occurrence to a count map, ignoring an absent key. */
|
|
106
|
+
function bump(
|
|
107
|
+
counts: Readonly<Record<string, number>> | undefined,
|
|
108
|
+
key: string | null | undefined,
|
|
109
|
+
): Record<string, number> {
|
|
110
|
+
const out: Record<string, number> = { ...(counts ?? {}) };
|
|
111
|
+
const name = typeof key === "string" ? key.trim() : "";
|
|
112
|
+
if (name !== "") out[name] = (out[name] ?? 0) + 1;
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Adds two count maps. */
|
|
117
|
+
function mergeCounts(
|
|
118
|
+
a: Readonly<Record<string, number>> | undefined,
|
|
119
|
+
b: Readonly<Record<string, number>> | undefined,
|
|
120
|
+
): Record<string, number> {
|
|
121
|
+
const out: Record<string, number> = { ...(a ?? {}) };
|
|
122
|
+
for (const [key, value] of Object.entries(b ?? {})) out[key] = (out[key] ?? 0) + value;
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function emptyBucket(): CompactedBucket {
|
|
127
|
+
return {
|
|
128
|
+
byModel: {},
|
|
129
|
+
byProvider: {},
|
|
130
|
+
byClient: {},
|
|
131
|
+
byClientIp: {},
|
|
132
|
+
byStatus: {},
|
|
133
|
+
resolveSumMs: 0,
|
|
134
|
+
resolveCount: 0,
|
|
135
|
+
requests: 0,
|
|
136
|
+
inputTokens: 0,
|
|
137
|
+
cachedTokens: 0,
|
|
138
|
+
outputTokens: 0,
|
|
139
|
+
reasoningTokens: 0,
|
|
140
|
+
estimatedCostUsd: 0,
|
|
141
|
+
creditUsed: 0,
|
|
142
|
+
succeeded: 0,
|
|
143
|
+
failed: 0,
|
|
144
|
+
cancelled: 0,
|
|
145
|
+
truncated: 0,
|
|
146
|
+
latencySum: 0,
|
|
147
|
+
latencyCount: 0,
|
|
148
|
+
latencyMinMs: null,
|
|
149
|
+
latencyMaxMs: null,
|
|
150
|
+
latencyHistogram: new Array<number>(LATENCY_BUCKETS_MS.length + 1).fill(0),
|
|
151
|
+
tokensPerSecSum: 0,
|
|
152
|
+
tokensPerSecCount: 0,
|
|
153
|
+
unpriced: 0,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function bucketIndex(latencyMs: number): number {
|
|
158
|
+
for (let index = 0; index < LATENCY_BUCKETS_MS.length; index += 1) {
|
|
159
|
+
if (latencyMs <= LATENCY_BUCKETS_MS[index]!) return index;
|
|
160
|
+
}
|
|
161
|
+
return LATENCY_BUCKETS_MS.length;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Folds one request row into a bucket.
|
|
166
|
+
*
|
|
167
|
+
* Every field is a running sum or a min/max, never an average of averages: an
|
|
168
|
+
* average of per-file averages would drift from the true mean as soon as the
|
|
169
|
+
* files held different request counts, and the whole point of compaction is
|
|
170
|
+
* that the totals survive intact.
|
|
171
|
+
*/
|
|
172
|
+
export function accumulate(
|
|
173
|
+
into: CompactedBucket,
|
|
174
|
+
event: TelemetryEventRecord,
|
|
175
|
+
): CompactedBucket {
|
|
176
|
+
const histogram = [...into.latencyHistogram];
|
|
177
|
+
const latency = event.latencyMs;
|
|
178
|
+
if (latency !== null && Number.isFinite(latency)) {
|
|
179
|
+
histogram[bucketIndex(latency)] = (histogram[bucketIndex(latency)] ?? 0) + 1;
|
|
180
|
+
}
|
|
181
|
+
// A cost can arrive as a string: Postgres `numeric` columns come back that
|
|
182
|
+
// way, and so do restored rows. Adding one to a number concatenates instead of
|
|
183
|
+
// summing, which turned a cost of 0.000966 into "00.000966". Coerced here so
|
|
184
|
+
// every downstream sum is arithmetic.
|
|
185
|
+
const priced = numeric(event.estimatedCostUsd);
|
|
186
|
+
const resolve = event.resolveMs;
|
|
187
|
+
const hasResolve = resolve !== null && resolve !== undefined && Number.isFinite(resolve);
|
|
188
|
+
return {
|
|
189
|
+
byModel: bump(into.byModel, event.requestedModel ?? event.reportedModel),
|
|
190
|
+
byProvider: bump(into.byProvider, event.providerId),
|
|
191
|
+
byClient: bump(into.byClient, event.sourceSurface),
|
|
192
|
+
byClientIp: bump(into.byClientIp, event.clientIp),
|
|
193
|
+
// Keyed on the HTTP status, not the internal one: the chips and the filters
|
|
194
|
+
// are the status the client was given.
|
|
195
|
+
byStatus: bump(into.byStatus, event.httpStatus?.toString()),
|
|
196
|
+
resolveSumMs: readResolveSum(into) + (hasResolve ? resolve : 0),
|
|
197
|
+
resolveCount: readResolveCount(into) + (hasResolve ? 1 : 0),
|
|
198
|
+
requests: into.requests + 1,
|
|
199
|
+
inputTokens: into.inputTokens + (event.inputTokens ?? 0),
|
|
200
|
+
cachedTokens: into.cachedTokens + (event.cachedInputTokens ?? 0),
|
|
201
|
+
outputTokens: into.outputTokens + (event.outputTokens ?? 0),
|
|
202
|
+
reasoningTokens: into.reasoningTokens + (event.reasoningTokens ?? 0),
|
|
203
|
+
estimatedCostUsd: into.estimatedCostUsd + priced,
|
|
204
|
+
creditUsed: into.creditUsed + (event.creditUsed ?? 0),
|
|
205
|
+
succeeded: into.succeeded + (event.status === "completed" ? 1 : 0),
|
|
206
|
+
failed: into.failed + (event.status === "failed" ? 1 : 0),
|
|
207
|
+
cancelled: into.cancelled + (event.status === "cancelled" ? 1 : 0),
|
|
208
|
+
truncated: into.truncated + (event.status === "truncated" ? 1 : 0),
|
|
209
|
+
latencySum: into.latencySum + (latency ?? 0),
|
|
210
|
+
latencyCount: into.latencyCount + (latency === null ? 0 : 1),
|
|
211
|
+
latencyMinMs:
|
|
212
|
+
latency === null
|
|
213
|
+
? into.latencyMinMs
|
|
214
|
+
: into.latencyMinMs === null
|
|
215
|
+
? latency
|
|
216
|
+
: Math.min(into.latencyMinMs, latency),
|
|
217
|
+
latencyMaxMs:
|
|
218
|
+
latency === null
|
|
219
|
+
? into.latencyMaxMs
|
|
220
|
+
: into.latencyMaxMs === null
|
|
221
|
+
? latency
|
|
222
|
+
: Math.max(into.latencyMaxMs, latency),
|
|
223
|
+
latencyHistogram: histogram,
|
|
224
|
+
tokensPerSecSum: into.tokensPerSecSum + (event.tokensPerSec ?? 0),
|
|
225
|
+
tokensPerSecCount: into.tokensPerSecCount + (event.tokensPerSec === null ? 0 : 1),
|
|
226
|
+
// A completed request with no cost is unpriced, not free. Counting it keeps
|
|
227
|
+
// the dashboard's "partial cost" warning honest after compaction.
|
|
228
|
+
unpriced: into.unpriced + (event.status === "completed" && priced === 0 ? 1 : 0),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* An approximate percentile over a compacted histogram.
|
|
234
|
+
*
|
|
235
|
+
* Returns the upper edge of the bucket that contains it — never a value
|
|
236
|
+
* between edges, because a histogram cannot be more precise than the bucket
|
|
237
|
+
* that holds the sample. `null` when there are no samples.
|
|
238
|
+
*
|
|
239
|
+
* The overflow bucket is the last one and has no upper edge, so it returns
|
|
240
|
+
* {@link HISTOGRAM_OVERFLOW_MS} — the top edge of the last real bucket — rather
|
|
241
|
+
* than `null`. Returning `null` there was read as "no data" by every caller,
|
|
242
|
+
* which collapsed a P95 of 40 s to `0 ms`: the slowest, most interesting
|
|
243
|
+
* percentiles were the ones reported as zero. Callers that can do better pass
|
|
244
|
+
* the period's `latencyMaxMs` as `overflowMs` and get that instead.
|
|
245
|
+
*/
|
|
246
|
+
export function percentileFromHistogram(
|
|
247
|
+
histogram: readonly number[],
|
|
248
|
+
percentile: number,
|
|
249
|
+
overflowMs?: number | null,
|
|
250
|
+
): number | null {
|
|
251
|
+
const total = histogram.reduce((sum, count) => sum + count, 0);
|
|
252
|
+
if (total === 0) return null;
|
|
253
|
+
const target = Math.ceil((percentile / 100) * total);
|
|
254
|
+
let seen = 0;
|
|
255
|
+
for (let index = 0; index < histogram.length; index += 1) {
|
|
256
|
+
seen += histogram[index] ?? 0;
|
|
257
|
+
if (seen >= target) {
|
|
258
|
+
// The bucket's upper edge: a percentile from a histogram can never be
|
|
259
|
+
// more precise than the bucket that contains it.
|
|
260
|
+
if (index < LATENCY_BUCKETS_MS.length) return LATENCY_BUCKETS_MS[index]!;
|
|
261
|
+
// Above the last edge. `latencyMaxMs` is the honest answer when the
|
|
262
|
+
// caller has it — it is the real slowest request in the period, so a
|
|
263
|
+
// percentile past the last edge is at most that. Falling back to the
|
|
264
|
+
// last edge rather than 0 keeps the figure in the right order of
|
|
265
|
+
// magnitude even when no max was recorded.
|
|
266
|
+
return overflowMs ?? HISTOGRAM_OVERFLOW_MS;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Top edge of the last real latency bucket; the floor for an overflow answer. */
|
|
273
|
+
export const HISTOGRAM_OVERFLOW_MS = LATENCY_BUCKETS_MS[LATENCY_BUCKETS_MS.length - 1]!;
|
|
274
|
+
|
|
275
|
+
/** Newline count of a file, so a torn or unparsable line is detectable. */
|
|
276
|
+
/** Adds two buckets. Exported so the day index can merge without duplicating it. */
|
|
277
|
+
export function mergeBucketsInternal(a: CompactedBucket, b: CompactedBucket): CompactedBucket {
|
|
278
|
+
const histogram = a.latencyHistogram.map((count, index) => count + (b.latencyHistogram[index] ?? 0));
|
|
279
|
+
return {
|
|
280
|
+
byModel: mergeCounts(a.byModel, b.byModel),
|
|
281
|
+
byProvider: mergeCounts(a.byProvider, b.byProvider),
|
|
282
|
+
byClient: mergeCounts(a.byClient, b.byClient),
|
|
283
|
+
byClientIp: mergeCounts(a.byClientIp, b.byClientIp),
|
|
284
|
+
byStatus: mergeCounts(a.byStatus, b.byStatus),
|
|
285
|
+
resolveSumMs: readResolveSum(a) + readResolveSum(b),
|
|
286
|
+
resolveCount: readResolveCount(a) + readResolveCount(b),
|
|
287
|
+
requests: a.requests + b.requests,
|
|
288
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
289
|
+
cachedTokens: a.cachedTokens + b.cachedTokens,
|
|
290
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
291
|
+
reasoningTokens: a.reasoningTokens + b.reasoningTokens,
|
|
292
|
+
estimatedCostUsd: a.estimatedCostUsd + b.estimatedCostUsd,
|
|
293
|
+
creditUsed: a.creditUsed + b.creditUsed,
|
|
294
|
+
succeeded: a.succeeded + b.succeeded,
|
|
295
|
+
failed: a.failed + b.failed,
|
|
296
|
+
cancelled: a.cancelled + b.cancelled,
|
|
297
|
+
truncated: a.truncated + b.truncated,
|
|
298
|
+
latencySum: a.latencySum + b.latencySum,
|
|
299
|
+
latencyCount: a.latencyCount + b.latencyCount,
|
|
300
|
+
latencyMinMs:
|
|
301
|
+
a.latencyMinMs === null
|
|
302
|
+
? b.latencyMinMs
|
|
303
|
+
: b.latencyMinMs === null
|
|
304
|
+
? a.latencyMinMs
|
|
305
|
+
: Math.min(a.latencyMinMs, b.latencyMinMs),
|
|
306
|
+
latencyMaxMs:
|
|
307
|
+
a.latencyMaxMs === null
|
|
308
|
+
? b.latencyMaxMs
|
|
309
|
+
: b.latencyMaxMs === null
|
|
310
|
+
? a.latencyMaxMs
|
|
311
|
+
: Math.max(a.latencyMaxMs, b.latencyMaxMs),
|
|
312
|
+
latencyHistogram: histogram,
|
|
313
|
+
tokensPerSecSum: a.tokensPerSecSum + b.tokensPerSecSum,
|
|
314
|
+
tokensPerSecCount: a.tokensPerSecCount + b.tokensPerSecCount,
|
|
315
|
+
unpriced: a.unpriced + b.unpriced,
|
|
316
|
+
};
|
|
317
|
+
}
|