cartethyia 2.5.2 → 2.5.4
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/dist/dashboard/assets/{BannedPage-MsQaeQTs.js → BannedPage-C2orBsI2.js} +1 -1
- package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DfWHTJ7v.js → CliToolsPage-BV6pxVQo.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BrCBQDAc.js → CombosPage-Bsan4679.js} +1 -1
- package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-C6Uh33er.js → ConsoleLogPage-CW-wPmJ6.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-8aS9hyJu.js → CustomizationPage-0p8uX7vB.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-C8hKyXBe.js → LoginPage-BvzCx8Kr.js} +1 -1
- package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-CWkTUtpX.js → ModelPicker-BY1aKEkP.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +7 -0
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +1 -0
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-CXjUlfqr.js → SetupPage-DDbMQsoo.js} +1 -1
- package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-C-_oD3F2.js → SortableList-CB3Z7VTf.js} +1 -1
- package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-u9IxmYOI.js → StudioPage-D95UCPHI.js} +3 -3
- package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-BEVLVrwP.js → UsagePage-Cnae9SWA.js} +13 -13
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-uNywYlfv.js → api-keys-VY68Mhd1.js} +1 -1
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
- package/dist/dashboard/assets/app-COlK95oi.css +2 -0
- package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
- package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-Chi7pCnt.js} +1 -1
- package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
- package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-D4pQapKj.js} +1 -1
- package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
- package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-DH6cg-_B.js} +1 -1
- package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
- package/dist/dashboard/assets/{card--AtFedAq.js → card-BKqlUOe4.js} +1 -1
- package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-fTgljTGA.js} +1 -1
- package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-D5Ys_sM0.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.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-BtpsJU98.js → dialog-BYj-8WuK.js} +1 -1
- package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-WwwtzihF.js → drawer-CmXIZ6H1.js} +1 -1
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
- package/dist/dashboard/assets/entry-CBg5zeu8.js +1 -0
- package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
- package/dist/dashboard/assets/entry-CQBr6U9c.js +1 -0
- package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
- package/dist/dashboard/assets/entry-CgdSAzEV.js +4 -0
- package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-BlptL8Uf.js} +1 -1
- package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
- package/dist/dashboard/assets/{index-DCBtROoK.js → index-js6TJvdr.js} +2 -2
- package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-DUWDSmDV.js} +1 -1
- package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
- package/dist/dashboard/assets/{input-BG41et9G.js → input-LVjgZq4f.js} +1 -1
- package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
- package/dist/dashboard/assets/{layout-C8mer_Ak.js → layout-CPfxpsfy.js} +1 -1
- package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-DupvLfDx.js} +1 -1
- package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
- package/dist/dashboard/assets/{live-BJGYRBnR.js → live-C4pEJUqI.js} +1 -1
- package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-BRiuufkB.js +1 -0
- package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-B4ROwk8L.js} +1 -1
- package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
- package/dist/dashboard/assets/numbers-YNcHvRY7.js +2 -0
- package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-gmjzC3RS.js} +1 -1
- package/dist/dashboard/assets/{query-keys-D7wX2vM5.js.gz → query-keys-gmjzC3RS.js.gz} +0 -0
- package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-C7tBT5o-.js} +1 -1
- package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
- package/dist/dashboard/assets/{select-DsX5QT8K.js → select-WUCXorXw.js} +2 -2
- package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
- package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-C_zKl1GS.js} +1 -1
- package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-_3O0F8Xn.js} +1 -1
- package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
- package/dist/dashboard/assets/{state-DYdQmdAl.js → state-BgTxHcJS.js} +1 -1
- package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
- package/dist/dashboard/assets/switch-DEjTtOWf.js +1 -0
- package/dist/dashboard/assets/switch-DEjTtOWf.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/{useMutation-w4u4CVue.js → useMutation-BWSC0UAS.js} +1 -1
- package/dist/dashboard/assets/useMutation-BWSC0UAS.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 +54 -54
- package/package.json +1 -1
- package/scripts/cli/autostart.ts +237 -0
- package/scripts/cli/command/console-menu.ts +244 -246
- package/scripts/cli/command/tray.ts +208 -204
- package/scripts/cli/command/update.ts +27 -10
- package/scripts/cli/main.ts +28 -1
- package/src/boot/app.ts +693 -693
- package/src/boot/dependencies.ts +646 -616
- package/src/config/spec.ts +81 -2
- package/src/config/telemetry.ts +46 -0
- package/src/console/api-keys/bucket-spend.ts +15 -4
- package/src/console/backup/contracts.ts +15 -2
- package/src/console/backup/routes.ts +52 -1
- package/src/console/backup/service.ts +60 -4
- package/src/console/console-router.ts +8 -0
- package/src/console/data-management/contracts.ts +93 -0
- package/src/console/data-management/operations.ts +229 -0
- package/src/console/data-management/routes.ts +104 -0
- package/src/console/domain-registration.ts +39 -0
- package/src/console/observability/contracts.ts +524 -485
- package/src/console/observability/store.ts +1115 -1018
- package/src/console/providers/catalog/contracts.ts +6 -0
- package/src/console/providers/catalog/provider-operations.ts +20 -0
- package/src/console/providers/catalog/routes.ts +7 -0
- package/src/console/providers/catalog/store.ts +22 -1
- package/src/console/quota/account-quota-shared.ts +52 -39
- package/src/console/quota/refresh.ts +441 -429
- package/src/console/routing/pools/import-parse.ts +206 -0
- package/src/console/routing/pools/operations.ts +543 -501
- package/src/console/routing/pools/routes.ts +181 -164
- package/src/console/settings/contracts.ts +37 -0
- package/src/console/settings/store.ts +51 -0
- package/src/console/share/share-router.ts +6 -9
- package/src/console/updates/routes.ts +16 -0
- package/src/main.ts +10 -0
- package/src/observability/event-compaction.ts +315 -0
- package/src/observability/event-day-index.ts +442 -0
- package/src/observability/event-log.ts +152 -3
- package/src/observability/payload-capture.ts +373 -344
- package/src/observability/payload-store.ts +440 -366
- package/src/observability/telemetry-status.ts +28 -16
- package/src/persistence/health-event-flush.ts +36 -0
- package/src/persistence/schema.ts +1402 -1360
- package/src/persistence/stores/api-key-store.ts +6 -0
- package/src/persistence/stores/share-store.ts +583 -556
- package/src/persistence/stores/telemetry-store.ts +237 -214
- package/src/protocol/codecs.ts +1 -2
- package/src/protocol/image-clamp.ts +19 -1
- package/src/protocol/registry.ts +8 -0
- package/src/protocol/request/gemini.ts +9 -2
- package/src/protocol/request/messages.ts +14 -2
- package/src/protocol/response/gemini.ts +18 -4
- package/src/providers/adapters/compatible-adapter.ts +3 -0
- package/src/providers/bundled-discovery-providers.ts +122 -119
- package/src/providers/configured-openai-providers.ts +136 -134
- package/src/providers/default-registry.ts +5 -4
- package/src/providers/discovery/openai-model-discovery.ts +5 -0
- package/src/providers/discovery/openrouter-free-roster.ts +15 -17
- package/src/providers/integrations/antigravity/antigravity-quota.ts +11 -3
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +4 -4
- package/src/providers/integrations/buddy/workbuddy/shared.ts +201 -200
- package/src/providers/integrations/cline/cline-quota.ts +132 -128
- package/src/providers/integrations/codex/codex-headers.ts +25 -39
- package/src/providers/integrations/codex/codex.ts +2 -29
- package/src/providers/integrations/cursor/cursor-quota.ts +310 -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 +1485 -960
- package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
- package/src/providers/integrations/factory/factory.ts +8 -1
- package/src/providers/integrations/gemini.ts +6 -1
- 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/opencode/provider.ts +531 -532
- package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
- package/src/providers/integrations/tokenharbor.ts +2 -2
- package/src/providers/integrations/trae/trae-catalog.ts +5 -3
- package/src/providers/integrations/trae/trae-quota.ts +384 -0
- package/src/providers/operations/account-health-service.ts +1523 -1457
- package/src/providers/operations/client-versions.ts +9 -2
- package/src/providers/provider-contracts.ts +7 -0
- package/src/providers/provider-metadata.ts +13 -1
- package/src/providers/quota/quota-support.ts +8 -0
- package/src/security/api-key-auth.ts +504 -494
- package/src/transport/dispatch/attempt-finalize.ts +46 -5
- package/src/transport/dispatch/generation.ts +6 -0
- package/src/transport/dispatch/proxy-request.ts +33 -1
- package/src/transport/dispatch/ratelimit-capture.ts +37 -0
- package/src/transport/dispatch/streaming/stream.ts +30 -2
- package/src/transport/dispatch/upstream.ts +9 -0
- package/src/transport/request/preparer.ts +7 -6
- package/src/transport/request/state.ts +9 -0
- package/src/transport/request/tenant-preferences.ts +60 -0
- package/src/transport/routing/route-catalog.ts +854 -799
- package/dist/dashboard/assets/BannedPage-MsQaeQTs.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DfWHTJ7v.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BrCBQDAc.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-C6Uh33er.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-8aS9hyJu.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-C8hKyXBe.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js +0 -1
- package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js +0 -7
- package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BihkgQSq.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BihkgQSq.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js +0 -1
- package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CXjUlfqr.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-C-_oD3F2.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-u9IxmYOI.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BEVLVrwP.js.gz +0 -0
- 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.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.gz +0 -0
- package/dist/dashboard/assets/entry-Dj9AR0gY.js +0 -4
- package/dist/dashboard/assets/entry-Dj9AR0gY.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/github-badge-D_eM3IgG.js.gz +0 -0
- package/dist/dashboard/assets/index-DCBtROoK.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.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/routing-CcusqXA7.js.gz +0 -0
- package/dist/dashboard/assets/select-DsX5QT8K.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/useMutation-w4u4CVue.js.gz +0 -0
- /package/dist/dashboard/assets/{app-CafWtVXA.js → app-DCQZRmHY.js} +0 -0
- /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-DCQZRmHY.js.gz} +0 -0
|
@@ -39,6 +39,27 @@ export async function shouldCaptureProviderBody(
|
|
|
39
39
|
return payloadCaptureKeepsBodies(db, tenantId);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Per-stage cap for the provider request/response tee at one capture depth.
|
|
44
|
+
*
|
|
45
|
+
* The tee is what the drawer's "Provider Request" and "Provider Response"
|
|
46
|
+
* panels read, so its cap is the most a provider panel can ever show. It used
|
|
47
|
+
* to be a flat 256 KiB at every depth: on a large request the translated
|
|
48
|
+
* provider body was clipped to 256 KiB before the shared record budget ever
|
|
49
|
+
* ran, so a tenant who raised the depth to Maximum — 16 MiB — still saw a
|
|
50
|
+
* quarter-megabyte provider request and an empty provider response. Derived
|
|
51
|
+
* from the same depth tiers as the record budget, so raising the depth now
|
|
52
|
+
* raises both.
|
|
53
|
+
*
|
|
54
|
+
* Capped at a quarter of the depth's total budget: four panels share it, and
|
|
55
|
+
* one of them must never be able to spend the whole thing.
|
|
56
|
+
*/
|
|
57
|
+
export function providerCaptureMaxBytes(depth?: TelemetryPayloadDepth): number {
|
|
58
|
+
if (depth === "moderate") return 2 * 1024 * 1024;
|
|
59
|
+
if (depth === "maximum") return 4 * 1024 * 1024;
|
|
60
|
+
return 256 * 1024;
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
/**
|
|
43
64
|
* Same answer as {@link shouldCaptureProviderBody}, memoized on the request.
|
|
44
65
|
*
|
|
@@ -62,6 +83,28 @@ export async function shouldCaptureProviderBodyForRequest(
|
|
|
62
83
|
return resolved.ok === true && resolved.mode === "full";
|
|
63
84
|
}
|
|
64
85
|
|
|
86
|
+
/**
|
|
87
|
+
* The tenant's capture depth, memoized on the request like the mode above.
|
|
88
|
+
*
|
|
89
|
+
* Read *after* {@link shouldCaptureProviderBodyForRequest} has populated the
|
|
90
|
+
* memo, so the two answers come from one preference read. It sizes the
|
|
91
|
+
* provider tee: a tenant who raised the depth is asking for more of every
|
|
92
|
+
* panel, and a flat cap would answer with the same clipped body regardless.
|
|
93
|
+
*/
|
|
94
|
+
export async function resolveCaptureDepthForRequest(
|
|
95
|
+
db: CartethyiaDatabase,
|
|
96
|
+
tenantId: string | null,
|
|
97
|
+
state: { payloadCapturePreference?: unknown },
|
|
98
|
+
): Promise<TelemetryPayloadDepth> {
|
|
99
|
+
if (state.payloadCapturePreference === undefined) {
|
|
100
|
+
state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
|
|
101
|
+
}
|
|
102
|
+
const resolved = state.payloadCapturePreference as
|
|
103
|
+
| { readonly ok: true; readonly depth: TelemetryPayloadDepth }
|
|
104
|
+
| { readonly ok: false };
|
|
105
|
+
return resolved.ok === true ? resolved.depth : "minimum";
|
|
106
|
+
}
|
|
107
|
+
|
|
65
108
|
/**
|
|
66
109
|
* Combined body cap for one capture depth. Fixed size tiers: every depth
|
|
67
110
|
* stores all four drawer panels (client request, translated provider
|
|
@@ -103,7 +146,6 @@ export function parseCapturedBody(value: unknown): unknown {
|
|
|
103
146
|
}
|
|
104
147
|
}
|
|
105
148
|
|
|
106
|
-
const PROVIDER_CAPTURE_MAX_BYTES = 256 * 1024;
|
|
107
149
|
const PROVIDER_CAPTURE_AWAIT_MS = 2_000;
|
|
108
150
|
const CAPTURABLE_RESPONSE_TYPES = /^(text\/|application\/(json|x-ndjson|sse))/i;
|
|
109
151
|
|
|
@@ -258,6 +300,7 @@ export function captureProviderExchange(
|
|
|
258
300
|
inner: ValidatedOutboundFetch,
|
|
259
301
|
sink: ProviderExchangeCapture,
|
|
260
302
|
captureBody = true,
|
|
303
|
+
maxBytes = providerCaptureMaxBytes(),
|
|
261
304
|
): ValidatedOutboundFetch {
|
|
262
305
|
return async (input, init) => {
|
|
263
306
|
const url =
|
|
@@ -276,14 +319,12 @@ export function captureProviderExchange(
|
|
|
276
319
|
// unbounded translated request body per attempt — up to `maxAttempts` of
|
|
277
320
|
// them, all alive until the loop returns — costs more than it can tell
|
|
278
321
|
// an operator.
|
|
279
|
-
...(rawBody === null
|
|
280
|
-
? {}
|
|
281
|
-
: { body: parseCapturedBody(rawBody.slice(0, PROVIDER_CAPTURE_MAX_BYTES)) }),
|
|
322
|
+
...(rawBody === null ? {} : { body: parseCapturedBody(rawBody.slice(0, maxBytes)) }),
|
|
282
323
|
};
|
|
283
324
|
const response = await inner(input, init);
|
|
284
325
|
sink.response =
|
|
285
326
|
captureBody && shouldCaptureProviderResponse(response)
|
|
286
|
-
? readBoundedBody(response.clone(),
|
|
327
|
+
? readBoundedBody(response.clone(), maxBytes).then(parseCapturedBody)
|
|
287
328
|
: Promise.resolve(null);
|
|
288
329
|
// Read regardless of `captureBody`: this is metadata about the dispatch, not
|
|
289
330
|
// payload retention, so a tenant in `metadata` mode still records which
|
|
@@ -34,6 +34,7 @@ import { ProxyRequestStateStore } from "../request/state";
|
|
|
34
34
|
import { ProxyRequestPreparer } from "../request/preparer";
|
|
35
35
|
import { completeAttempt, estimatedUsage } from "./attempt-finalize";
|
|
36
36
|
import { runAttemptLoop } from "./attempt-loop";
|
|
37
|
+
import { captureProviderRateLimit } from "./ratelimit-capture";
|
|
37
38
|
|
|
38
39
|
export interface GenerationHandlerDeps {
|
|
39
40
|
readonly db: CartethyiaDatabase;
|
|
@@ -125,6 +126,11 @@ async function passThrough(
|
|
|
125
126
|
},
|
|
126
127
|
): Promise<Response> {
|
|
127
128
|
const usage = estimatedUsage(0, 0);
|
|
129
|
+
// The CLI bucket comes back on every upstream response, so this is the free
|
|
130
|
+
// place to learn it: the quota surface then reads the stored value instead of
|
|
131
|
+
// spending a request of its own. Both the manual account test and automatic
|
|
132
|
+
// routing land here, so a captured reading follows real traffic.
|
|
133
|
+
captureProviderRateLimit(args.candidate.provider_id, args.candidate.provider_account_id, upstream.headers);
|
|
128
134
|
await completeAttempt(state, {
|
|
129
135
|
status: "completed",
|
|
130
136
|
providerId: args.candidate.provider_id,
|
|
@@ -15,7 +15,11 @@ import { messagesAdapter } from "../surface/messages/adapter";
|
|
|
15
15
|
import { completionAdapter } from "../surface/completion";
|
|
16
16
|
import { forwardedRequestHeaders, proxySuccessHeaders, buildUpstreamDispatchContext, reportedModelFrom } from "./upstream";
|
|
17
17
|
import { resolvePromptCacheKey } from "../../providers/operations/session-resolution";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
applyTenantPreferences,
|
|
20
|
+
applyTenantDeadlineOverride,
|
|
21
|
+
resolveTenantImageClampMaxBytes,
|
|
22
|
+
} from "../request/tenant-preferences";
|
|
19
23
|
import { metrics } from "../../observability/metrics";
|
|
20
24
|
import type { NetworkPoolSelector } from "../../network/pool/selector";
|
|
21
25
|
import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
|
|
@@ -28,6 +32,8 @@ import {
|
|
|
28
32
|
completionContext,
|
|
29
33
|
estimatedUsage,
|
|
30
34
|
parseCapturedBody,
|
|
35
|
+
providerCaptureMaxBytes,
|
|
36
|
+
resolveCaptureDepthForRequest,
|
|
31
37
|
shouldCaptureProviderBodyForRequest,
|
|
32
38
|
terminalFailure,
|
|
33
39
|
} from "./attempt-finalize";
|
|
@@ -126,6 +132,22 @@ export async function handleProviderProxyRequest(
|
|
|
126
132
|
if (!prepared)
|
|
127
133
|
throw new GatewayError("admission_unavailable", 503, "The request was not prepared, so no route can be dialed. Resend the request.");
|
|
128
134
|
const canonicalRequest = await applyTenantPreferences(prepared, deps.db);
|
|
135
|
+
// Per-tenant timeout, applied as soon as the tenant is known. The deadline
|
|
136
|
+
// was armed at admission with the deployment default; this re-arms it.
|
|
137
|
+
await applyTenantDeadlineOverride(
|
|
138
|
+
prepared.authorization.snapshot.tenant_id,
|
|
139
|
+
deps.db,
|
|
140
|
+
(ms) => state.extendDeadline(ms),
|
|
141
|
+
);
|
|
142
|
+
// Memoized on the request: the ceiling is a tenant setting, so it cannot
|
|
143
|
+
// change between failover attempts, and each wire encoder that clamps an
|
|
144
|
+
// image reads it from here instead of paying for a preference read per
|
|
145
|
+
// attempt.
|
|
146
|
+
const imageClampMaxBytes = await resolveTenantImageClampMaxBytes(
|
|
147
|
+
deps.db,
|
|
148
|
+
prepared.authorization.snapshot.tenant_id,
|
|
149
|
+
);
|
|
150
|
+
if (imageClampMaxBytes !== undefined) state.imageClampMaxBytes = imageClampMaxBytes;
|
|
129
151
|
const candidates =
|
|
130
152
|
prepared.eligibleRouteCandidates.length > 0 ? prepared.eligibleRouteCandidates : [prepared.candidate];
|
|
131
153
|
const attemptCandidates = candidates;
|
|
@@ -224,6 +246,9 @@ export async function handleProviderProxyRequest(
|
|
|
224
246
|
headers: inboundHeaders,
|
|
225
247
|
requestId: state.requestId,
|
|
226
248
|
requestIdentity: state,
|
|
249
|
+
...(state.imageClampMaxBytes === undefined
|
|
250
|
+
? {}
|
|
251
|
+
: { imageClampMaxBytes: state.imageClampMaxBytes }),
|
|
227
252
|
...(conversationAffinity ? { conversationAffinity } : {}),
|
|
228
253
|
...(candidate.user_agent === undefined ? {} : { userAgent: candidate.user_agent }),
|
|
229
254
|
...(deps.networkBindingFactory
|
|
@@ -239,6 +264,13 @@ export async function handleProviderProxyRequest(
|
|
|
239
264
|
prepared.authorization.snapshot.tenant_id,
|
|
240
265
|
state,
|
|
241
266
|
),
|
|
267
|
+
providerCaptureMaxBytes(
|
|
268
|
+
await resolveCaptureDepthForRequest(
|
|
269
|
+
deps.db,
|
|
270
|
+
prepared.authorization.snapshot.tenant_id,
|
|
271
|
+
state,
|
|
272
|
+
),
|
|
273
|
+
),
|
|
242
274
|
),
|
|
243
275
|
}
|
|
244
276
|
: {}),
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Files a provider's rate-limit headers against the account that earned them.
|
|
2
|
+
//
|
|
3
|
+
// Providers answer real traffic with the bucket they are metering — Grok's CLI
|
|
4
|
+
// plane returns `x-ratelimit-*` on every response — and that is a reading the
|
|
5
|
+
// gateway already paid for. Capturing it here is what lets the quota surface
|
|
6
|
+
// show a balance without spending a request of its own.
|
|
7
|
+
//
|
|
8
|
+
// Kept provider-scoped and fail-open: a provider with no bucket parser is
|
|
9
|
+
// skipped, and any throw is swallowed, because a bookkeeping miss must never
|
|
10
|
+
// take down a request that already succeeded.
|
|
11
|
+
|
|
12
|
+
import { rememberGrokRateLimit } from "../../providers/integrations/grok/grok-ratelimit-store";
|
|
13
|
+
import { readGrokRateLimitHeaders } from "../../providers/integrations/grok/grok-ratelimit";
|
|
14
|
+
|
|
15
|
+
/** Providers whose response headers carry a readable bucket. */
|
|
16
|
+
const GROK_PROVIDER_IDS: readonly string[] = ["grok"];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Stores whatever bucket the response carried, keyed by account.
|
|
20
|
+
*
|
|
21
|
+
* No account id means no home for the reading: a bucket filed under the wrong
|
|
22
|
+
* account is worse than none, since it would report another account's balance.
|
|
23
|
+
*/
|
|
24
|
+
export function captureProviderRateLimit(
|
|
25
|
+
providerId: string,
|
|
26
|
+
accountId: string | undefined,
|
|
27
|
+
headers: { get(name: string): string | null },
|
|
28
|
+
): void {
|
|
29
|
+
if (!accountId) return;
|
|
30
|
+
const id = providerId.toLowerCase();
|
|
31
|
+
if (!GROK_PROVIDER_IDS.includes(id)) return;
|
|
32
|
+
try {
|
|
33
|
+
rememberGrokRateLimit(accountId, readGrokRateLimitHeaders(headers));
|
|
34
|
+
} catch {
|
|
35
|
+
// Swallowed: advisory bookkeeping on a request that already succeeded.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -43,7 +43,13 @@ import type { AdmissionLease } from "../../../security/admission/contracts";
|
|
|
43
43
|
import type { NetworkPoolSelector } from "../../../network/pool/selector";
|
|
44
44
|
import type { TelemetryBatchBuffer } from "../../../observability/telemetry-buffer";
|
|
45
45
|
import type { ProviderExchangeCapture } from "../attempt-finalize";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
captureProviderExchange,
|
|
48
|
+
providerCaptureMaxBytes,
|
|
49
|
+
resolveCaptureDepthForRequest,
|
|
50
|
+
shouldCaptureProviderBodyForRequest,
|
|
51
|
+
} from "../attempt-finalize";
|
|
52
|
+
import { resolveTenantImageClampMaxBytes } from "../../request/tenant-preferences";
|
|
47
53
|
import { proxySuccessHeaders, buildUpstreamDispatchContext } from "../upstream";
|
|
48
54
|
import type { AttemptLeaseOwnership } from "../leases";
|
|
49
55
|
import type { ProviderContinuationScope } from "../../../providers/provider-contracts";
|
|
@@ -165,13 +171,32 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
|
|
|
165
171
|
networkPoolId,
|
|
166
172
|
prepared.authorization.snapshot.tenant_id,
|
|
167
173
|
);
|
|
174
|
+
const captureBody = outboundFetch
|
|
175
|
+
? await shouldCaptureProviderBodyForRequest(deps.db, prepared.authorization.snapshot.tenant_id, state)
|
|
176
|
+
: false;
|
|
177
|
+
const captureDepth = await resolveCaptureDepthForRequest(
|
|
178
|
+
deps.db,
|
|
179
|
+
prepared.authorization.snapshot.tenant_id,
|
|
180
|
+
state,
|
|
181
|
+
);
|
|
168
182
|
const wrappedOutboundFetch = outboundFetch
|
|
169
183
|
? captureProviderExchange(
|
|
170
184
|
outboundFetch,
|
|
171
185
|
providerCapture,
|
|
172
|
-
|
|
186
|
+
captureBody,
|
|
187
|
+
providerCaptureMaxBytes(captureDepth),
|
|
173
188
|
)
|
|
174
189
|
: undefined;
|
|
190
|
+
// Same memoized ceiling the non-streaming path resolves, read before the
|
|
191
|
+
// dispatch context is built so a stream gets the same limit as a normal
|
|
192
|
+
// request rather than silently falling back to the deployment default.
|
|
193
|
+
if (state.imageClampMaxBytes === undefined) {
|
|
194
|
+
const clampMaxBytes = await resolveTenantImageClampMaxBytes(
|
|
195
|
+
deps.db,
|
|
196
|
+
prepared.authorization.snapshot.tenant_id,
|
|
197
|
+
);
|
|
198
|
+
if (clampMaxBytes !== undefined) state.imageClampMaxBytes = clampMaxBytes;
|
|
199
|
+
}
|
|
175
200
|
const dispatchContext = buildUpstreamDispatchContext({
|
|
176
201
|
credential,
|
|
177
202
|
deadline: state.deadlineMs,
|
|
@@ -179,6 +204,9 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
|
|
|
179
204
|
headers: inboundHeaders,
|
|
180
205
|
requestId: state.requestId,
|
|
181
206
|
requestIdentity: state,
|
|
207
|
+
...(state.imageClampMaxBytes === undefined
|
|
208
|
+
? {}
|
|
209
|
+
: { imageClampMaxBytes: state.imageClampMaxBytes }),
|
|
182
210
|
...(conversationAffinity ? { conversationAffinity } : {}),
|
|
183
211
|
...(candidate.user_agent === undefined ? {} : { userAgent: candidate.user_agent }),
|
|
184
212
|
...(wrappedOutboundFetch ? { outboundFetch: wrappedOutboundFetch } : {}),
|
|
@@ -56,6 +56,12 @@ export function buildUpstreamDispatchContext(input: {
|
|
|
56
56
|
readonly continuationScope?: ProviderContinuationScope;
|
|
57
57
|
/** Stable per-conversation cache affinity (preferred over random ids). */
|
|
58
58
|
readonly conversationAffinity?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Per-tenant inline-attachment ceiling in bytes, resolved once per request.
|
|
61
|
+
* Carried through dispatch because wire encoders clamp images but hold no
|
|
62
|
+
* database handle.
|
|
63
|
+
*/
|
|
64
|
+
readonly imageClampMaxBytes?: number;
|
|
59
65
|
/** Opaque identity shared by retries of one logical gateway request. */
|
|
60
66
|
readonly requestIdentity?: object;
|
|
61
67
|
}): Record<string, unknown> {
|
|
@@ -94,6 +100,9 @@ export function buildUpstreamDispatchContext(input: {
|
|
|
94
100
|
...(input.leaseOwnership ? { lease_ownership: input.leaseOwnership } : {}),
|
|
95
101
|
...(input.continuationScope ? { continuation_scope: input.continuationScope } : {}),
|
|
96
102
|
...(input.conversationAffinity ? { conversation_affinity: input.conversationAffinity } : {}),
|
|
103
|
+
...(input.imageClampMaxBytes === undefined
|
|
104
|
+
? {}
|
|
105
|
+
: { image_clamp_max_bytes: input.imageClampMaxBytes }),
|
|
97
106
|
...(input.requestIdentity ? { request_identity: input.requestIdentity } : {}),
|
|
98
107
|
} as Record<string, unknown>;
|
|
99
108
|
}
|
|
@@ -85,12 +85,13 @@ export function resolveContinuationAffinity(
|
|
|
85
85
|
// id replayed from another key or tenant resolves nothing.
|
|
86
86
|
const record = lookupPendingCall(callId, scope);
|
|
87
87
|
if (record === undefined) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
88
|
+
// A replayed conversation carries every earlier tool result, and results
|
|
89
|
+
// previous legs already answered resolve nothing here — they are history,
|
|
90
|
+
// not the flight being continued. The request is a continuation only
|
|
91
|
+
// through the ids that still resolve; when none do, the ordinary routing
|
|
92
|
+
// path decides (a Cursor adapter may rebase the full history into a
|
|
93
|
+
// fresh Run).
|
|
94
|
+
continue;
|
|
94
95
|
}
|
|
95
96
|
if (affinity !== undefined && affinity.providerAccountId !== record.providerAccountId) {
|
|
96
97
|
throw new GatewayError(
|
|
@@ -117,6 +117,15 @@ export interface ProxyRequestState {
|
|
|
117
117
|
payloadCapturePreference?:
|
|
118
118
|
| { readonly ok: true; readonly mode: TelemetryPayloadMode; readonly depth: TelemetryPayloadDepth }
|
|
119
119
|
| { readonly ok: false };
|
|
120
|
+
/**
|
|
121
|
+
* This request's resolved inline-attachment ceiling, memoized for the same
|
|
122
|
+
* reason the capture preference is: it is a tenant setting, so it cannot
|
|
123
|
+
* change between attempts, and reading it per attempt would pay for it
|
|
124
|
+
* several times on a request that fails over.
|
|
125
|
+
*
|
|
126
|
+
* `undefined` means the deployment default.
|
|
127
|
+
*/
|
|
128
|
+
imageClampMaxBytes?: number;
|
|
120
129
|
/**
|
|
121
130
|
* Set by the dispatch handler synchronously before returning a streaming
|
|
122
131
|
* response. `afterResponse` lifecycle hooks read it to skip finalization
|
|
@@ -13,6 +13,66 @@ import type { PreparedProxyRequest } from "./preparer";
|
|
|
13
13
|
import { normalizeThinkingConfig } from "../translation/thinking";
|
|
14
14
|
import { compressRequest } from "./rtk/compress-request";
|
|
15
15
|
import { preferencesReaderFor } from "../../persistence/tenant-preferences";
|
|
16
|
+
import { resolveUpstreamTimeoutMs } from "../../config/timeouts";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Re-arms the request deadline from the tenant's timeout override.
|
|
20
|
+
*
|
|
21
|
+
* The initial deadline is armed before authentication, when the tenant is not
|
|
22
|
+
* yet known, so a per-tenant timeout can only be applied here — once the
|
|
23
|
+
* snapshot says who is asking. `extendDeadline` keeps the timer, the state
|
|
24
|
+
* field, and the in-flight record in step, so the backstop sweep cannot
|
|
25
|
+
* force-release a request whose budget legitimately grew.
|
|
26
|
+
*
|
|
27
|
+
* Non-fatal: a settings read outage leaves the deployment default in place
|
|
28
|
+
* rather than failing a dispatchable request.
|
|
29
|
+
*/
|
|
30
|
+
export async function applyTenantDeadlineOverride(
|
|
31
|
+
tenantId: string,
|
|
32
|
+
db: CartethyiaDatabase,
|
|
33
|
+
extend: (ms: number) => void,
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
try {
|
|
36
|
+
const prefs = await preferencesReaderFor(db).readPreferences(tenantId);
|
|
37
|
+
const seconds = prefs?.upstreamTimeoutSeconds;
|
|
38
|
+
if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) return;
|
|
39
|
+
const wanted = Math.floor(seconds * 1000);
|
|
40
|
+
// Only ever shorten or leave alone a deadline that has already begun: an
|
|
41
|
+
// override longer than the default cannot give back time the request has
|
|
42
|
+
// already spent, and arming a longer one would silently extend a budget
|
|
43
|
+
// the caller's own client has likely abandoned.
|
|
44
|
+
const remaining = Math.min(wanted, resolveUpstreamTimeoutMs());
|
|
45
|
+
extend(remaining);
|
|
46
|
+
} catch {
|
|
47
|
+
// Non-fatal
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The tenant's inline-attachment byte ceiling, or `undefined` for the default.
|
|
53
|
+
*
|
|
54
|
+
* Read through the shared revision-keyed preference reader, so the cost is a
|
|
55
|
+
* memory hit rather than a Postgres round trip — this is consulted while
|
|
56
|
+
* building a request body, on the dispatch path.
|
|
57
|
+
*
|
|
58
|
+
* Non-fatal: a settings read outage falls back to the deployment default,
|
|
59
|
+
* which is the safe direction (the default refuses less, but it never admits
|
|
60
|
+
* something no upstream will take).
|
|
61
|
+
*/
|
|
62
|
+
export async function resolveTenantImageClampMaxBytes(
|
|
63
|
+
db: CartethyiaDatabase,
|
|
64
|
+
tenantId: string | undefined,
|
|
65
|
+
): Promise<number | undefined> {
|
|
66
|
+
if (tenantId === undefined) return undefined;
|
|
67
|
+
try {
|
|
68
|
+
const prefs = await preferencesReaderFor(db).readPreferences(tenantId);
|
|
69
|
+
const bytes = prefs?.imageClampMaxBytes;
|
|
70
|
+
if (typeof bytes !== "number" || !Number.isFinite(bytes) || bytes <= 0) return undefined;
|
|
71
|
+
return Math.floor(bytes);
|
|
72
|
+
} catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
16
76
|
|
|
17
77
|
/**
|
|
18
78
|
* Applies request shaping owned by tenant preferences and deployment config:
|