cartethyia 2.5.9 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +15 -42
- package/README.md +11 -10
- package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
- package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
- package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
- package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
- package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
- package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
- package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
- package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
- package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
- package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
- package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
- package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
- package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
- package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
- package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
- package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
- package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
- package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
- package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
- package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
- package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
- package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
- package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
- package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
- package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
- package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
- package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
- package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
- package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
- package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
- package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
- package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
- package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -51
- package/migrations/README.md +52 -52
- package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
- package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
- package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/package.json +96 -97
- package/scripts/cli/admin.ts +126 -125
- package/scripts/cli/autostart.ts +237 -237
- package/scripts/cli/background.ts +120 -110
- package/scripts/cli/bin/carte.cjs +8 -8
- package/scripts/cli/bin/cartethyia.cjs +167 -167
- package/scripts/cli/console-menu.ts +308 -275
- package/scripts/cli/controller.ts +145 -0
- package/scripts/cli/doctor.ts +1 -1
- package/scripts/cli/env.ts +99 -99
- package/scripts/cli/gateway.ts +264 -261
- package/scripts/{health.ts → cli/health.ts} +4 -4
- package/scripts/cli/instance.ts +234 -224
- package/scripts/cli/main.ts +188 -164
- package/scripts/cli/platform.ts +64 -64
- package/scripts/cli/prompt.ts +275 -107
- package/scripts/cli/services.ts +88 -88
- package/scripts/cli/setup.ts +445 -445
- package/scripts/cli/start.ts +85 -82
- package/scripts/cli/startup-signal.ts +23 -23
- package/scripts/cli/telemetry.ts +70 -0
- package/scripts/cli/tray/index.ts +263 -263
- package/scripts/cli/tray/windows.ps1 +107 -107
- package/scripts/cli/tray.ts +192 -208
- package/scripts/cli/update.ts +39 -39
- package/scripts/cli/version.ts +67 -67
- package/scripts/cli/wordmark.ts +58 -58
- package/src/boot/app.ts +16 -1
- package/src/boot/dependencies.ts +128 -25
- package/src/config/package-paths.ts +24 -0
- package/src/config/resources.ts +137 -0
- package/src/config/runtime.ts +133 -76
- package/src/config/spec.ts +30 -29
- package/src/config/telemetry.ts +0 -25
- package/src/console/api-keys/bucket-spend.ts +14 -17
- package/src/console/api-keys/contracts.ts +410 -410
- package/src/console/api-keys/routes.ts +12 -0
- package/src/console/audit/store.ts +2 -3
- package/src/console/auth/setup-service.ts +8 -1
- package/src/console/backup/routes.ts +7 -0
- package/src/console/backup/service.ts +463 -463
- package/src/console/backup/store.ts +16 -11
- package/src/console/backup/validate.ts +1 -4
- package/src/console/cli-tools/fs-ops.ts +18 -2
- package/src/console/data-management/contracts.ts +1 -8
- package/src/console/data-management/operations.ts +61 -48
- package/src/console/data-management/routes.ts +4 -2
- package/src/console/domain-registration.ts +25 -3
- package/src/console/inferhub/client.ts +139 -0
- package/src/console/inferhub/key-store.ts +129 -0
- package/src/console/inferhub/normalize.ts +347 -0
- package/src/console/inferhub/routes.ts +411 -0
- package/src/console/inferhub/store.ts +109 -0
- package/src/console/inferhub/types.ts +183 -0
- package/src/console/observability/contracts.ts +2 -41
- package/src/console/observability/store.ts +176 -371
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/store.ts +23 -36
- package/src/console/quota/account-quota-shared.ts +52 -52
- package/src/console/quota/account-quota-tenant.ts +16 -24
- package/src/console/quota/refresh.ts +441 -441
- package/src/console/remote-support/service.ts +270 -114
- package/src/console/routing/pools/import-parse.ts +206 -206
- package/src/console/routing/pools/operations.ts +543 -543
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -181
- package/src/console/routing/pools/store.ts +1 -1
- package/src/console/settings/contracts.ts +42 -40
- package/src/console/settings/store.ts +25 -25
- package/src/console/share/share-stats.ts +22 -4
- package/src/console/share/share-usage.ts +30 -4
- package/src/console/shared/cursor.ts +15 -0
- package/src/console/updates/repository-updates.ts +40 -39
- package/src/console/updates/routes.ts +12 -0
- package/src/main.ts +13 -5
- package/src/network/pool/resolver.ts +1104 -1104
- package/src/observability/client-name.ts +22 -0
- package/src/observability/event-log.ts +5 -29
- package/src/observability/event-query.ts +690 -0
- package/src/observability/log-ring.ts +26 -2
- package/src/observability/log-sanitize.ts +118 -0
- package/src/observability/logger.ts +17 -13
- package/src/observability/metrics.ts +35 -0
- package/src/observability/payload-capture.ts +114 -41
- package/src/observability/payload-store.ts +249 -24
- package/src/observability/raw-event-writer.ts +118 -0
- package/src/observability/runtime-metrics.ts +71 -1
- package/src/observability/telemetry-buffer.ts +100 -17
- package/src/observability/telemetry-reconciliation.ts +417 -0
- package/src/persistence/boot.ts +20 -9
- package/src/persistence/db-pg.ts +13 -0
- package/src/persistence/db-sqlite.ts +569 -497
- package/src/persistence/dialect.ts +3 -2
- package/src/persistence/migrate.ts +181 -181
- package/src/persistence/schema.ts +1496 -1406
- package/src/persistence/shared-cache.ts +5 -1
- package/src/persistence/stores/api-key-store.ts +581 -581
- package/src/persistence/stores/telemetry-store.ts +124 -63
- package/src/protocol/reasoning-text.ts +30 -0
- package/src/protocol/registry.ts +2 -0
- package/src/protocol/response/chat.ts +33 -7
- package/src/protocol/response/codex.ts +14 -2
- package/src/protocol/response/messages.ts +5 -2
- package/src/providers/authentication/oauth-flow-store.ts +3 -10
- package/src/providers/bundled-discovery-providers.ts +122 -122
- package/src/providers/default-registry.ts +14 -0
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +43 -7
- package/src/providers/discovery/probing-service.ts +70 -32
- package/src/providers/integrations/antigravity/antigravity.ts +9 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
- package/src/providers/integrations/buddy/capabilities.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
- package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
- package/src/providers/integrations/claude/claude-quota.ts +2 -2
- package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
- package/src/providers/integrations/claude/claude.ts +3 -4
- package/src/providers/integrations/cline/cline.ts +94 -8
- package/src/providers/integrations/codex/codex.ts +61 -37
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
- package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
- package/src/providers/integrations/cursor/cursor-request.ts +11 -0
- package/src/providers/integrations/cursor/cursor.ts +29 -17
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
- package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
- package/src/providers/integrations/github/github.ts +3 -1
- package/src/providers/integrations/grok/grok-quota.ts +279 -279
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
- package/src/providers/integrations/hermes.ts +7 -1
- package/src/providers/integrations/inferhub.ts +201 -73
- package/src/providers/integrations/kilo/kilo.ts +152 -152
- package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
- package/src/providers/integrations/opencode/provider.ts +2 -0
- package/src/providers/integrations/openrouter/provider.ts +129 -129
- package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
- package/src/providers/integrations/trae/trae-headers.ts +17 -48
- package/src/providers/integrations/trae/trae-oauth.ts +4 -26
- package/src/providers/integrations/trae/trae-quota.ts +389 -384
- package/src/providers/integrations/trae/trae-realm.ts +15 -2
- package/src/providers/integrations/trae/trae.ts +22 -24
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/account-health-service.ts +605 -279
- package/src/providers/provider-metadata.ts +12 -1
- package/src/providers/provider-model-definition.ts +373 -373
- package/src/providers/provider-probe.ts +3 -0
- package/src/providers/quota/quota-contracts.ts +3 -7
- package/src/providers/search/search-provider.ts +2 -6
- package/src/security/admission/contracts.ts +10 -0
- package/src/security/admission/in-memory-store.ts +89 -14
- package/src/security/admission/lua.ts +191 -182
- package/src/security/admission/redis-store.ts +30 -0
- package/src/security/admission/service.ts +18 -0
- package/src/security/api-key-auth-cache.ts +96 -0
- package/src/security/api-key-auth.ts +11 -100
- package/src/security/client-router-fingerprint.ts +3 -7
- package/src/security/ip-boundary.ts +7 -3
- package/src/shared/coerce.ts +67 -0
- package/src/shared/hash.ts +6 -0
- package/src/shared/time.ts +7 -0
- package/src/transport/dispatch/attempt-finalize.ts +26 -138
- package/src/transport/dispatch/attempt-loop.ts +103 -81
- package/src/transport/dispatch/capture-policy.ts +78 -0
- package/src/transport/dispatch/proxy-request.ts +4 -2
- package/src/transport/dispatch/streaming/encoder.ts +111 -5
- package/src/transport/dispatch/streaming/stream.ts +19 -2
- package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
- package/src/transport/failure-policy.ts +27 -0
- package/src/transport/guards/abuse-guard.ts +2 -2
- package/src/transport/guards/auth-guard.ts +17 -0
- package/src/transport/guards/body-policy.ts +3 -2
- package/src/transport/guards/concurrency-guard.ts +89 -13
- package/src/transport/guards/early-rejection-capture.ts +43 -3
- package/src/transport/guards/error-lifecycle.ts +10 -2
- package/src/transport/guards/guard-errors.ts +23 -0
- package/src/transport/guards/headroom-guard.ts +41 -7
- package/src/transport/guards/pipeline.ts +24 -0
- package/src/transport/guards/request-context.ts +2 -1
- package/src/transport/routing/admission.ts +51 -9
- package/src/transport/routing/bounded-lru.ts +49 -0
- package/src/transport/routing/candidate-index.ts +88 -0
- package/src/transport/routing/reservations.ts +121 -7
- package/src/transport/routing/round-robin.ts +10 -22
- package/src/transport/routing/route-catalog.ts +26 -13
- package/src/transport/routing/route-model.ts +22 -2
- package/src/transport/routing/router.ts +53 -31
- package/src/transport/surface/chat/encode.ts +2 -3
- package/src/transport/surface/chat/parse.ts +2 -1
- package/src/transport/surface/dialects.ts +11 -3
- package/src/transport/surface/messages/encode.ts +2 -1
- package/src/transport/surface/messages/parse.ts +3 -1
- package/src/transport/surface/messages/stream.ts +2 -6
- package/src/workers/maintenance-lease.ts +200 -0
- package/src/workers/scheduler.ts +101 -1
- package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
- package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
- package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
- package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/scripts/build/aot.ts +0 -143
- package/scripts/build/build.ts +0 -24
- package/scripts/build/compile.ts +0 -88
- package/scripts/build/restart.ts +0 -120
- package/scripts/build/start.ts +0 -25
- package/src/observability/event-compaction.ts +0 -317
- package/src/observability/event-day-index.ts +0 -440
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir, rm } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { sql } from "drizzle-orm";
|
|
4
4
|
import { affectedRows, currentTimestamp, laterOf } from "../dialect";
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
} from "../schema";
|
|
12
12
|
import { isGatewayError } from "../../observability/telemetry-status";
|
|
13
13
|
import { TELEMETRY_EVENT_DIR } from "../../observability/event-log";
|
|
14
|
-
import {
|
|
14
|
+
import { RawTelemetryEventWriter } from "../../observability/raw-event-writer";
|
|
15
|
+
import { hourStamp } from "../../shared/time";
|
|
15
16
|
|
|
16
17
|
interface UsageTotalDelta {
|
|
17
18
|
readonly tenantId: string;
|
|
@@ -71,94 +72,153 @@ function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[])
|
|
|
71
72
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
72
73
|
|
|
73
74
|
/**
|
|
74
|
-
* Deletes
|
|
75
|
-
*
|
|
75
|
+
* Deletes hourly event files whose whole hour is older than the retention
|
|
76
|
+
* cutoff.
|
|
76
77
|
*
|
|
77
|
-
* The
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
78
|
+
* The CURRENT and PREVIOUS hour are never touched. The current one is still
|
|
79
|
+
* being appended; the previous one is why retention cannot be an hour
|
|
80
|
+
* boundary: a batch flushed just after a boundary still carries events from
|
|
81
|
+
* the hour that just ended, and the writer files them under that hour. Under
|
|
82
|
+
* multi-process/multi-writer operation a file for the previous hour can also
|
|
83
|
+
* still be open, so deleting it would either fail (Windows) or unlink a file
|
|
84
|
+
* another writer has open (POSIX). Files are named `<hour>-<uuid>.jsonl` now,
|
|
85
|
+
* so an hour is complete only once it is two hours old.
|
|
85
86
|
*/
|
|
86
87
|
async function pruneEventFiles(before: Date): Promise<void> {
|
|
87
88
|
const files = await readdir(TELEMETRY_EVENT_DIR).catch(() => [] as string[]);
|
|
89
|
+
// Two hours back: the current hour is open, the previous one can be too.
|
|
90
|
+
const floor = new Date(Math.min(before.getTime(), Date.now() - 2 * 3_600_000));
|
|
91
|
+
const cutoff = hourStamp(floor);
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
if (!file.endsWith(".jsonl") || file.endsWith(".events.jsonl")) continue;
|
|
94
|
+
const hour = file.slice(0, 13);
|
|
95
|
+
if (hour < cutoff) await rm(join(TELEMETRY_EVENT_DIR, file), { force: true });
|
|
96
|
+
}
|
|
97
|
+
// Read-only legacy day logs are pruned only after their whole UTC day has
|
|
98
|
+
// aged out; index files are never consulted for usage anymore.
|
|
88
99
|
const cutoffDay = before.toISOString().slice(0, 10);
|
|
89
100
|
for (const file of files) {
|
|
90
|
-
|
|
91
|
-
if (day.length !== 10 || day >= cutoffDay) continue;
|
|
92
|
-
if (!file.endsWith(".events.jsonl") && !file.endsWith(".index.json")) continue;
|
|
101
|
+
if (!file.endsWith(".events.jsonl") || file.slice(0, 10) >= cutoffDay) continue;
|
|
93
102
|
await rm(join(TELEMETRY_EVENT_DIR, file), { force: true });
|
|
94
103
|
}
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
/**
|
|
98
|
-
* Appends one JSON line per event
|
|
99
|
-
*
|
|
100
|
-
* One file per UTC day, appended to and never rewritten while the day is open.
|
|
101
|
-
* A file per `windowMinutes` window — 96 a day at the default — would have to be
|
|
102
|
-
* found, opened and parsed one by one on every usage query, and which of those
|
|
103
|
-
* files was still being written could only be inferred from the name, which is
|
|
104
|
-
* what lets the open one be deleted mid-write.
|
|
105
|
-
*
|
|
106
|
-
* A single growing file has no such ambiguity, and the day index tracks how much
|
|
107
|
-
* of it has been counted by byte offset.
|
|
107
|
+
* Appends one JSON line per event. One file per hour, so retention is a file
|
|
108
|
+
* delete and no request is replaced by a compacted summary.
|
|
108
109
|
*/
|
|
109
|
-
async function appendEvents(rows: readonly (typeof telemetryEvents.$inferInsert)[]): Promise<void> {
|
|
110
|
-
await mkdir(TELEMETRY_EVENT_DIR, { recursive: true });
|
|
111
|
-
const file = join(TELEMETRY_EVENT_DIR, dayEventFileName(utcDay(new Date())));
|
|
112
|
-
const newline = String.fromCharCode(10);
|
|
113
|
-
const body = rows.map((row) => JSON.stringify(row)).join(newline) + newline;
|
|
114
|
-
await appendFile(file, body, "utf8");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
110
|
export class DrizzleTelemetryStore {
|
|
118
|
-
|
|
111
|
+
private readonly eventWriter: RawTelemetryEventWriter;
|
|
119
112
|
|
|
120
|
-
|
|
113
|
+
constructor(
|
|
114
|
+
private readonly db: CartethyiaDatabase,
|
|
115
|
+
eventWriter = new RawTelemetryEventWriter(TELEMETRY_EVENT_DIR),
|
|
116
|
+
) {
|
|
117
|
+
this.eventWriter = eventWriter;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Applies usage deltas inside an enclosing transaction.
|
|
122
|
+
*
|
|
123
|
+
* Takes the transaction handle, not this.db: `insertEvents` runs the event
|
|
124
|
+
* insert and the aggregate upsert in ONE transaction, so usage is charged
|
|
125
|
+
* from the rows actually inserted — never from rows that `ON CONFLICT DO
|
|
126
|
+
* NOTHING` skipped. The deltas are computed by the caller from the
|
|
127
|
+
* inserted-rows result for exactly that reason.
|
|
128
|
+
*/
|
|
129
|
+
private async applyUsageDeltas(
|
|
130
|
+
tx: Parameters<Parameters<CartethyiaDatabase["transaction"]>[0]>[0],
|
|
131
|
+
deltas: readonly UsageTotalDelta[],
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
if (deltas.length === 0) return;
|
|
134
|
+
// Deterministic order so two processes racing the same identities lock
|
|
135
|
+
// rows in the same order and cannot deadlock.
|
|
136
|
+
const ordered = [...deltas].sort((left, right) =>
|
|
137
|
+
left.tenantId.localeCompare(right.tenantId)
|
|
138
|
+
|| left.identityType.localeCompare(right.identityType)
|
|
139
|
+
|| left.entityId.localeCompare(right.entityId));
|
|
140
|
+
const now = new Date();
|
|
141
|
+
await tx
|
|
142
|
+
.insert(telemetryUsageTotals)
|
|
143
|
+
.values(ordered.map((delta) => ({
|
|
144
|
+
...delta,
|
|
145
|
+
requests: delta.requests,
|
|
146
|
+
errors: delta.errors,
|
|
147
|
+
inputTokens: delta.inputTokens,
|
|
148
|
+
outputTokens: delta.outputTokens,
|
|
149
|
+
lastUsedAt: now,
|
|
150
|
+
})))
|
|
151
|
+
.onConflictDoUpdate({
|
|
152
|
+
target: [
|
|
153
|
+
telemetryUsageTotals.tenantId,
|
|
154
|
+
telemetryUsageTotals.identityType,
|
|
155
|
+
telemetryUsageTotals.entityId,
|
|
156
|
+
],
|
|
157
|
+
set: {
|
|
158
|
+
requests: sql`${telemetryUsageTotals.requests} + excluded.requests`,
|
|
159
|
+
errors: sql`${telemetryUsageTotals.errors} + excluded.errors`,
|
|
160
|
+
inputTokens: sql`${telemetryUsageTotals.inputTokens} + excluded.input_tokens`,
|
|
161
|
+
outputTokens: sql`${telemetryUsageTotals.outputTokens} + excluded.output_tokens`,
|
|
162
|
+
lastUsedAt: laterOf(sql`${telemetryUsageTotals.lastUsedAt}`, sql`excluded.last_used_at`, this.db),
|
|
163
|
+
updatedAt: currentTimestamp(),
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Writes raw events and maintains their durable identity aggregates. */
|
|
121
169
|
async insertEvents(rows: Array<typeof telemetryEvents.$inferInsert>): Promise<void> {
|
|
122
170
|
if (rows.length === 0) return;
|
|
123
171
|
// The file accepts anything, so a malformed row would be written and then
|
|
124
172
|
// retried forever without ever failing. Rejecting it here keeps the buffer's
|
|
125
173
|
// drop-and-count contract: one bad row fails the batch, once.
|
|
174
|
+
const unique: Array<typeof telemetryEvents.$inferInsert> = [];
|
|
175
|
+
const ids = new Set<string>();
|
|
126
176
|
for (const row of rows) {
|
|
127
177
|
if (!UUID_RE.test(row.tenantId)) {
|
|
128
178
|
throw new Error("telemetry event rejected: tenantId is not a uuid");
|
|
129
179
|
}
|
|
180
|
+
// In-batch dedupe on the stable id: keep the first occurrence. A batch
|
|
181
|
+
// containing the same request twice (a buffer bug or a replayed
|
|
182
|
+
// enqueue) writes one row and charges once, instead of relying on the
|
|
183
|
+
// DB to skip the second insert silently.
|
|
184
|
+
if (row.id !== undefined) {
|
|
185
|
+
if (ids.has(row.id)) continue;
|
|
186
|
+
ids.add(row.id);
|
|
187
|
+
}
|
|
188
|
+
unique.push(row);
|
|
130
189
|
}
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
190
|
+
// The raw append happens FIRST: the file is the at-least-once mirror. A
|
|
191
|
+
// crash after the append but before the SQL commit is recovered by
|
|
192
|
+
// `carte telemetry reconcile` (the backfill skips rows the unique index
|
|
193
|
+
// already holds). A raw append failure rejects the whole store call so
|
|
194
|
+
// the buffer retries the batch; nothing is committed, nothing charged.
|
|
195
|
+
await this.eventWriter.append(unique);
|
|
196
|
+
// One transaction: insert (skip conflicts), then charge usage from the
|
|
197
|
+
// rows actually inserted. A retried drain or a cross-process race
|
|
198
|
+
// converges to one row and one charge; no compensation transactions.
|
|
136
199
|
await this.db.transaction(async (tx) => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
outputTokens: sql`${telemetryUsageTotals.outputTokens} + excluded.output_tokens`,
|
|
153
|
-
lastUsedAt: laterOf(sql`${telemetryUsageTotals.lastUsedAt}`, sql`excluded.last_used_at`),
|
|
154
|
-
updatedAt: currentTimestamp(),
|
|
155
|
-
},
|
|
200
|
+
const inserted = await tx
|
|
201
|
+
.insert(telemetryEvents)
|
|
202
|
+
.values(unique)
|
|
203
|
+
.onConflictDoNothing({
|
|
204
|
+
target: [telemetryEvents.tenantId, telemetryEvents.requestId],
|
|
205
|
+
})
|
|
206
|
+
.returning({
|
|
207
|
+
tenantId: telemetryEvents.tenantId,
|
|
208
|
+
requestId: telemetryEvents.requestId,
|
|
209
|
+
accountId: telemetryEvents.accountId,
|
|
210
|
+
apiKeyId: telemetryEvents.apiKeyId,
|
|
211
|
+
status: telemetryEvents.status,
|
|
212
|
+
httpStatus: telemetryEvents.httpStatus,
|
|
213
|
+
inputTokens: telemetryEvents.inputTokens,
|
|
214
|
+
outputTokens: telemetryEvents.outputTokens,
|
|
156
215
|
});
|
|
216
|
+
// Usage is charged only from what actually landed. `returning` on
|
|
217
|
+
// `ON CONFLICT DO NOTHING` yields exactly the inserted rows on both
|
|
218
|
+
// backends; skipped conflicts contribute nothing.
|
|
219
|
+
const deltas = aggregateUsage(inserted);
|
|
220
|
+
await this.applyUsageDeltas(tx, deltas);
|
|
157
221
|
});
|
|
158
|
-
// After the aggregate commits, so a rejected batch is retried once, not
|
|
159
|
-
// appended once per attempt. The file is the record of requests whose
|
|
160
|
-
// totals were actually counted.
|
|
161
|
-
await appendEvents(rows);
|
|
162
222
|
}
|
|
163
223
|
|
|
164
224
|
/** Inserts one redacted payload and returns its generated identity. */
|
|
@@ -225,6 +285,7 @@ export class DrizzleTelemetryStore {
|
|
|
225
285
|
* the payload sweeper's `cleanupExpired`.
|
|
226
286
|
*/
|
|
227
287
|
async pruneTelemetry(before: Date): Promise<void> {
|
|
288
|
+
await this.eventWriter.idle();
|
|
228
289
|
await pruneEventFiles(before);
|
|
229
290
|
const MAX_BATCHES_PER_SWEEP = 40;
|
|
230
291
|
for (let batch = 0; batch < MAX_BATCHES_PER_SWEEP; batch += 1) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The reasoning-shaped part this reads. Structural, so any reasoning part —
|
|
2
|
+
* including a `ContentPart` narrowed to `kind: "reasoning"` — is accepted. */
|
|
3
|
+
export interface ReasoningTextSource {
|
|
4
|
+
readonly payload?: unknown;
|
|
5
|
+
readonly summary?: string;
|
|
6
|
+
readonly opaque?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The one reader for visible reasoning text.
|
|
11
|
+
*
|
|
12
|
+
* A reasoning part carries two distinct fields: `payload` (the readable
|
|
13
|
+
* reasoning stream — Responses `reasoning_text`, Chat `reasoning_content`) and
|
|
14
|
+
* `summary` (Responses `reasoning_summary_text`, a digest of the same thought).
|
|
15
|
+
* Two call sites used to read them in opposite orders, so a part that populates
|
|
16
|
+
* both rendered different text per surface — and a part that populated both
|
|
17
|
+
* with the *same* string was emitted twice by surfaces that read each field.
|
|
18
|
+
*
|
|
19
|
+
* Precedence is `payload` → `summary`: `payload` is the stream, `summary` is a
|
|
20
|
+
* digest of it, and when only one is present it is the one to show. Opaque
|
|
21
|
+
* parts (redacted thinking, encrypted reasoning) have no readable text at all.
|
|
22
|
+
*/
|
|
23
|
+
export function reasoningTextOf(part: ReasoningTextSource): string | undefined {
|
|
24
|
+
if (part.opaque === true) return undefined;
|
|
25
|
+
const payload = part.payload;
|
|
26
|
+
if (typeof payload === "string" && payload.length > 0) return payload;
|
|
27
|
+
const summary = part.summary;
|
|
28
|
+
if (typeof summary === "string" && summary.length > 0) return summary;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
package/src/protocol/registry.ts
CHANGED
|
@@ -110,6 +110,8 @@ async function loadCodec(wireFamily: WireFamily): Promise<CodecModule> {
|
|
|
110
110
|
const cached = codecCache.get(wireFamily);
|
|
111
111
|
if (cached !== undefined) return cached;
|
|
112
112
|
const promise = (async (): Promise<CodecModule> => {
|
|
113
|
+
// dynamic-import: codec picked by wire family at request time; static
|
|
114
|
+
// imports here would couple every request path to every codec module.
|
|
113
115
|
switch (wireFamily) {
|
|
114
116
|
case "chat":
|
|
115
117
|
return (await import("./request/chat")).chatCodecModule;
|
|
@@ -258,6 +258,10 @@ export async function* decodeChatSseStream(
|
|
|
258
258
|
// Single emission ledger: one entry per logical call.
|
|
259
259
|
const ledger = createToolEmitLedger();
|
|
260
260
|
const think = new MimoThinkSplitter();
|
|
261
|
+
// Separate splitter for the reasoning channel: `reasoning_content` is its own
|
|
262
|
+
// stream, and interleaving it into the answer splitter would let a tag in one
|
|
263
|
+
// channel swallow the other's text.
|
|
264
|
+
const reasoningThink = new MimoThinkSplitter();
|
|
261
265
|
const declaredToolNames = declaredToolNamesForRequest(request);
|
|
262
266
|
const callableToolNames = callableToolNamesForRequest(request);
|
|
263
267
|
const contentMarkup =
|
|
@@ -344,13 +348,22 @@ export async function* decodeChatSseStream(
|
|
|
344
348
|
const reasoningMarkupResult =
|
|
345
349
|
typeof reasoningRaw === "string" ? reasoningMarkup?.push(reasoningRaw) : undefined;
|
|
346
350
|
// Strip stray `<think>` tags the upstream double-wrapped inside its own
|
|
347
|
-
// reasoning stream (buddy's hy4-preview-f leaks them per-delta).
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
+
// reasoning stream (buddy's hy4-preview-f leaks them per-delta). This
|
|
352
|
+
// is the splitter's job, not `stripThinkingTags`'s: a stateless strip
|
|
353
|
+
// cannot see a tag split across two deltas, so `"<thi"` was emitted
|
|
354
|
+
// verbatim and only the following `</think>` removed.
|
|
355
|
+
const reasoningSplit =
|
|
351
356
|
typeof reasoningRaw === "string"
|
|
352
|
-
?
|
|
353
|
-
:
|
|
357
|
+
? reasoningThink.push(reasoningMarkupResult?.text ?? reasoningRaw)
|
|
358
|
+
: undefined;
|
|
359
|
+
// Both sides are reasoning. The splitter routes text outside a
|
|
360
|
+
// `<think>` span to its `.text` side, but this field is the reasoning
|
|
361
|
+
// stream in its entirety: untagged prose here is still thought, not
|
|
362
|
+
// answer. Taking only `.reasoning` dropped every un-tagged delta.
|
|
363
|
+
const reasoningText =
|
|
364
|
+
reasoningSplit === undefined
|
|
365
|
+
? undefined
|
|
366
|
+
: `${reasoningSplit.reasoning}${reasoningSplit.text}`;
|
|
354
367
|
if (typeof reasoningText === "string" && reasoningText.length > 0) {
|
|
355
368
|
yield {
|
|
356
369
|
type: "content_delta",
|
|
@@ -510,7 +523,9 @@ export async function* decodeChatSseStream(
|
|
|
510
523
|
}
|
|
511
524
|
const reasoningMarkupTail = reasoningMarkup?.flush();
|
|
512
525
|
if (reasoningMarkupTail?.text) {
|
|
513
|
-
const
|
|
526
|
+
const split = reasoningThink.push(reasoningMarkupTail.text);
|
|
527
|
+
// Both sides are reasoning: see the per-delta branch above.
|
|
528
|
+
const reasoningText = `${split.reasoning}${split.text}`;
|
|
514
529
|
if (reasoningText.length > 0) {
|
|
515
530
|
yield {
|
|
516
531
|
type: "content_delta",
|
|
@@ -519,6 +534,17 @@ export async function* decodeChatSseStream(
|
|
|
519
534
|
} as CanonicalEvent;
|
|
520
535
|
}
|
|
521
536
|
}
|
|
537
|
+
// Release anything the reasoning splitter held for a tag that never
|
|
538
|
+
// completed, before `think.flush()` and the terminal.
|
|
539
|
+
const reasoningRest = reasoningThink.flush();
|
|
540
|
+
const reasoningTail = `${reasoningRest.reasoning}${reasoningRest.text}`;
|
|
541
|
+
if (reasoningTail.length > 0) {
|
|
542
|
+
yield {
|
|
543
|
+
type: "content_delta",
|
|
544
|
+
sequence_number: seq++,
|
|
545
|
+
content: { kind: "reasoning", payload: null, summary: reasoningTail },
|
|
546
|
+
} as CanonicalEvent;
|
|
547
|
+
}
|
|
522
548
|
const rest = think.flush();
|
|
523
549
|
if (rest.reasoning.length > 0) {
|
|
524
550
|
yield {
|
|
@@ -629,11 +629,23 @@ export class CodexStreamFrameProcessor {
|
|
|
629
629
|
if (streamError !== undefined) throw streamError;
|
|
630
630
|
this.terminalStatus = "failed";
|
|
631
631
|
} else if (typeof json["delta"] === "string" && json["delta"].length > 0) {
|
|
632
|
+
// An unrecognized frame carrying a bare `delta` used to be forwarded as
|
|
633
|
+
// answer text. Reasoning frames that fall through the chain above — the
|
|
634
|
+
// last `else if` is the catch-all for every type this decoder does not
|
|
635
|
+
// name — then leaked into the answer verbatim. Keeping the payload
|
|
636
|
+
// visible as an extension part is the unknown-event convention the
|
|
637
|
+
// Responses stream already uses (`responses:<type>`), so the frame stays
|
|
638
|
+
// in telemetry without ever reaching the answer. Dropping it instead
|
|
639
|
+
// would silently discard real content.
|
|
632
640
|
events.push({
|
|
633
641
|
type: "content_delta",
|
|
634
642
|
sequence_number: this.#seq++,
|
|
635
|
-
content: {
|
|
636
|
-
|
|
643
|
+
content: {
|
|
644
|
+
kind: "extension",
|
|
645
|
+
name: "codex:untyped_delta",
|
|
646
|
+
payload: json["delta"] as string,
|
|
647
|
+
},
|
|
648
|
+
} as CanonicalEvent);
|
|
637
649
|
} else {
|
|
638
650
|
const choices = json["choices"] as
|
|
639
651
|
| Array<Record<string, unknown>>
|
|
@@ -92,8 +92,12 @@ export function parseClaudeContent(value: unknown, isOAuth = false): ContentPart
|
|
|
92
92
|
const thinking = stringValue(value.thinking) ? value.thinking : "";
|
|
93
93
|
return {
|
|
94
94
|
kind: "reasoning",
|
|
95
|
+
// `thinking` is the readable reasoning stream, not a digest of itself.
|
|
96
|
+
// Putting it in both `payload` and `summary` made the Responses encoder
|
|
97
|
+
// emit the same text twice — once as `reasoning_text` and again as
|
|
98
|
+
// `reasoning_summary_text` — the same bug already fixed on the Codex
|
|
99
|
+
// surface (see `reasoningEvent` in `response/codex.ts`).
|
|
95
100
|
payload: thinking,
|
|
96
|
-
summary: thinking,
|
|
97
101
|
...(stringValue(value.signature) ? { signature: value.signature } : {}),
|
|
98
102
|
};
|
|
99
103
|
}
|
|
@@ -375,7 +379,6 @@ function streamEventToCanonical(
|
|
|
375
379
|
content: {
|
|
376
380
|
kind: "reasoning",
|
|
377
381
|
payload: delta.thinking,
|
|
378
|
-
summary: delta.thinking,
|
|
379
382
|
},
|
|
380
383
|
});
|
|
381
384
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
import { base64url, decodeJwt } from "jose";
|
|
5
|
+
import { asRecord as record, asTrimmedString as nonEmptyTrimmedString } from "../../shared/coerce";
|
|
5
6
|
import type { RedisClient } from "../../persistence/redis";
|
|
6
7
|
import { TtlCache } from "../../persistence/ttl-cache";
|
|
7
8
|
|
|
@@ -268,18 +269,10 @@ export function devicePollBackoff(
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
/** Trimmed non-empty string, or `undefined` for anything else. */
|
|
271
|
-
export
|
|
272
|
-
if (typeof value !== "string") return undefined;
|
|
273
|
-
const trimmed = value.trim();
|
|
274
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
275
|
-
}
|
|
272
|
+
export { nonEmptyTrimmedString };
|
|
276
273
|
|
|
277
274
|
/** Object (non-array) narrowing for untyped upstream JSON. */
|
|
278
|
-
export
|
|
279
|
-
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
280
|
-
? (value as Record<string, unknown>)
|
|
281
|
-
: undefined;
|
|
282
|
-
}
|
|
275
|
+
export { record };
|
|
283
276
|
|
|
284
277
|
|
|
285
278
|
interface PkcePair {
|