cartethyia 2.5.3 → 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-hfCzi-Ux.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-DEDyKBlC.js → CliToolsPage-BV6pxVQo.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-CfuYiQ8Z.js → CombosPage-Bsan4679.js} +1 -1
- package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-CW-wPmJ6.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.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-DDVIfZGW.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-D6pmQ86L.js → SetupPage-DDbMQsoo.js} +1 -1
- package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-CB3Z7VTf.js} +1 -1
- package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D95UCPHI.js} +3 -3
- package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-B47uOydr.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-BydyKaDz.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-BO6h8yNv.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 +4 -8
- 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 +55 -0
- 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/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/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 +8 -1
- package/src/observability/payload-store.ts +440 -376
- package/src/persistence/health-event-flush.ts +36 -0
- package/src/persistence/schema.ts +1402 -1360
- 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/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/cline/cline-quota.ts +132 -125
- 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 +12 -13
- package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
- package/src/providers/integrations/trae/trae-quota.ts +384 -0
- package/src/providers/operations/account-health-service.ts +1523 -1480
- package/src/providers/provider-contracts.ts +7 -0
- package/src/providers/provider-metadata.ts +6 -0
- package/src/providers/quota/quota-support.ts +8 -0
- package/src/security/api-key-auth.ts +504 -494
- 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/streaming/stream.ts +14 -0
- 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 +58 -10
- 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.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.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.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-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/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.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-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/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
|
@@ -1,214 +1,237 @@
|
|
|
1
|
-
import { appendFile, mkdir, readdir, rm } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { sql } from "drizzle-orm";
|
|
4
|
-
import { affectedRows, currentTimestamp, laterOf } from "../dialect";
|
|
5
|
-
import type { CartethyiaDatabase } from "../db-handle";
|
|
6
|
-
import {
|
|
7
|
-
telemetryEvents,
|
|
8
|
-
telemetryPayloads,
|
|
9
|
-
telemetryUsageTotals,
|
|
10
|
-
type TelemetryUsageIdentityType,
|
|
11
|
-
} from "../schema";
|
|
12
|
-
import { isGatewayError } from "../../observability/telemetry-status";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
readonly
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
async function
|
|
88
|
-
await
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
1
|
+
import { appendFile, mkdir, readdir, rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
4
|
+
import { affectedRows, currentTimestamp, laterOf } from "../dialect";
|
|
5
|
+
import type { CartethyiaDatabase } from "../db-handle";
|
|
6
|
+
import {
|
|
7
|
+
telemetryEvents,
|
|
8
|
+
telemetryPayloads,
|
|
9
|
+
telemetryUsageTotals,
|
|
10
|
+
type TelemetryUsageIdentityType,
|
|
11
|
+
} from "../schema";
|
|
12
|
+
import { isGatewayError } from "../../observability/telemetry-status";
|
|
13
|
+
import { TELEMETRY_EVENT_DIR } from "../../observability/event-log";
|
|
14
|
+
import { dayEventFileName, utcDay } from "../../observability/event-day-index";
|
|
15
|
+
|
|
16
|
+
interface UsageTotalDelta {
|
|
17
|
+
readonly tenantId: string;
|
|
18
|
+
readonly identityType: TelemetryUsageIdentityType;
|
|
19
|
+
readonly entityId: string;
|
|
20
|
+
requests: number;
|
|
21
|
+
errors: number;
|
|
22
|
+
inputTokens: number;
|
|
23
|
+
outputTokens: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function addUsageDelta(
|
|
27
|
+
deltas: Map<string, UsageTotalDelta>,
|
|
28
|
+
row: typeof telemetryEvents.$inferInsert,
|
|
29
|
+
identityType: TelemetryUsageIdentityType,
|
|
30
|
+
entityId: string | null | undefined,
|
|
31
|
+
): void {
|
|
32
|
+
if (entityId == null) return;
|
|
33
|
+
const key = `${row.tenantId}\0${identityType}\0${entityId}`;
|
|
34
|
+
let delta = deltas.get(key);
|
|
35
|
+
if (!delta) {
|
|
36
|
+
delta = {
|
|
37
|
+
tenantId: row.tenantId,
|
|
38
|
+
identityType,
|
|
39
|
+
entityId,
|
|
40
|
+
requests: 0,
|
|
41
|
+
errors: 0,
|
|
42
|
+
inputTokens: 0,
|
|
43
|
+
outputTokens: 0,
|
|
44
|
+
};
|
|
45
|
+
deltas.set(key, delta);
|
|
46
|
+
}
|
|
47
|
+
delta.requests += 1;
|
|
48
|
+
// Same definition as every read-side error count, including the HTTP-status
|
|
49
|
+
// exclusions (404/499/503/504): this rollup is durable, so a request counted here
|
|
50
|
+
// stays counted after its raw row is pruned and cannot be corrected later.
|
|
51
|
+
if (isGatewayError(row.status, row.httpStatus)) delta.errors += 1;
|
|
52
|
+
delta.inputTokens += row.inputTokens ?? 0;
|
|
53
|
+
delta.outputTokens += row.outputTokens ?? 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
|
|
57
|
+
const deltas = new Map<string, UsageTotalDelta>();
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
addUsageDelta(deltas, row, "account", row.accountId);
|
|
60
|
+
addUsageDelta(deltas, row, "api_key", row.apiKeyId);
|
|
61
|
+
}
|
|
62
|
+
return [...deltas.values()];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Database boundary for durable telemetry writes and bounded retention cleanup.
|
|
67
|
+
* The observability buffer owns queueing and retry policy; this store owns table
|
|
68
|
+
* operations, lifetime aggregates, and retention.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
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
|
+
/**
|
|
74
|
+
* Deletes whole days past the retention cutoff: the event file and its index
|
|
75
|
+
* together.
|
|
76
|
+
*
|
|
77
|
+
* The day is the retention unit now, so both go at once. Removing one without
|
|
78
|
+
* the other is the failure this avoids — an index with no events reports totals
|
|
79
|
+
* for requests that no longer exist, and events with no index are invisible to
|
|
80
|
+
* every usage read.
|
|
81
|
+
*
|
|
82
|
+
* The cutoff is a whole day, so today's file — still being appended to — is
|
|
83
|
+
* never a candidate. It used to compare filenames hour by hour, which for a day
|
|
84
|
+
* file compared the `.events` suffix against an hour stamp and could drop the
|
|
85
|
+
* open file.
|
|
86
|
+
*/
|
|
87
|
+
async function pruneEventFiles(before: Date): Promise<void> {
|
|
88
|
+
const files = await readdir(TELEMETRY_EVENT_DIR).catch(() => [] as string[]);
|
|
89
|
+
const cutoffDay = before.toISOString().slice(0, 10);
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
const day = file.slice(0, 10);
|
|
92
|
+
if (day.length !== 10 || day >= cutoffDay) continue;
|
|
93
|
+
if (!file.endsWith(".events.jsonl") && !file.endsWith(".index.json")) continue;
|
|
94
|
+
await rm(join(TELEMETRY_EVENT_DIR, file), { force: true });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Appends one JSON line per event to the day's event file.
|
|
100
|
+
*
|
|
101
|
+
* One file per UTC day, appended to and never rewritten while the day is open.
|
|
102
|
+
* It used to be one file per `windowMinutes` window — 96 a day at the default —
|
|
103
|
+
* which the reader then had to find, open and parse one by one on every usage
|
|
104
|
+
* query. Which of those files was still being written had to be inferred from
|
|
105
|
+
* the name, and that inference is what let the open one be deleted mid-write.
|
|
106
|
+
*
|
|
107
|
+
* A single growing file has no such ambiguity, and the day index tracks how much
|
|
108
|
+
* of it has been counted by byte offset.
|
|
109
|
+
*/
|
|
110
|
+
async function appendEvents(rows: readonly (typeof telemetryEvents.$inferInsert)[]): Promise<void> {
|
|
111
|
+
await mkdir(TELEMETRY_EVENT_DIR, { recursive: true });
|
|
112
|
+
const file = join(TELEMETRY_EVENT_DIR, dayEventFileName(utcDay(new Date())));
|
|
113
|
+
const newline = String.fromCharCode(10);
|
|
114
|
+
const body = rows.map((row) => JSON.stringify(row)).join(newline) + newline;
|
|
115
|
+
await appendFile(file, body, "utf8");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class DrizzleTelemetryStore {
|
|
119
|
+
constructor(private readonly db: CartethyiaDatabase) {}
|
|
120
|
+
|
|
121
|
+
/** Inserts events and their durable identity aggregates atomically. */
|
|
122
|
+
async insertEvents(rows: Array<typeof telemetryEvents.$inferInsert>): Promise<void> {
|
|
123
|
+
if (rows.length === 0) return;
|
|
124
|
+
// The file accepts anything, so a malformed row would be written and then
|
|
125
|
+
// retried forever without ever failing. Rejecting it here keeps the buffer's
|
|
126
|
+
// drop-and-count contract: one bad row fails the batch, once.
|
|
127
|
+
for (const row of rows) {
|
|
128
|
+
if (!UUID_RE.test(row.tenantId)) {
|
|
129
|
+
throw new Error("telemetry event rejected: tenantId is not a uuid");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const deltas = aggregateUsage(rows);
|
|
133
|
+
// The per-request row goes to an append-only file, not to Postgres. The
|
|
134
|
+
// database keeps only the durable aggregate below, so a request never
|
|
135
|
+
// adds a row to the largest table. Readers of recent requests read the
|
|
136
|
+
// file; readers of long-term totals read the aggregate.
|
|
137
|
+
await this.db.transaction(async (tx) => {
|
|
138
|
+
if (deltas.length === 0) return;
|
|
139
|
+
const now = new Date();
|
|
140
|
+
await tx
|
|
141
|
+
.insert(telemetryUsageTotals)
|
|
142
|
+
.values(deltas.map((delta) => ({ ...delta, lastUsedAt: now })))
|
|
143
|
+
.onConflictDoUpdate({
|
|
144
|
+
target: [
|
|
145
|
+
telemetryUsageTotals.tenantId,
|
|
146
|
+
telemetryUsageTotals.identityType,
|
|
147
|
+
telemetryUsageTotals.entityId,
|
|
148
|
+
],
|
|
149
|
+
set: {
|
|
150
|
+
requests: sql`${telemetryUsageTotals.requests} + excluded.requests`,
|
|
151
|
+
errors: sql`${telemetryUsageTotals.errors} + excluded.errors`,
|
|
152
|
+
inputTokens: sql`${telemetryUsageTotals.inputTokens} + excluded.input_tokens`,
|
|
153
|
+
outputTokens: sql`${telemetryUsageTotals.outputTokens} + excluded.output_tokens`,
|
|
154
|
+
lastUsedAt: laterOf(sql`${telemetryUsageTotals.lastUsedAt}`, sql`excluded.last_used_at`),
|
|
155
|
+
updatedAt: currentTimestamp(),
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
// After the aggregate commits, so a rejected batch is retried once, not
|
|
160
|
+
// appended once per attempt. The file is the record of requests whose
|
|
161
|
+
// totals were actually counted.
|
|
162
|
+
await appendEvents(rows);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Inserts one redacted payload and returns its generated identity. */
|
|
166
|
+
async insertPayload(
|
|
167
|
+
row: typeof telemetryPayloads.$inferInsert,
|
|
168
|
+
): Promise<{ id: string; expiresAt: Date }> {
|
|
169
|
+
const [inserted] = await this.db
|
|
170
|
+
.insert(telemetryPayloads)
|
|
171
|
+
.values(row)
|
|
172
|
+
.returning({ id: telemetryPayloads.id, expiresAt: telemetryPayloads.expiresAt });
|
|
173
|
+
if (!inserted) throw new Error("payload capture insert returned no row");
|
|
174
|
+
return inserted;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Deletes at most one bounded batch of expired payloads in a single
|
|
179
|
+
* statement, so concurrent sweeps atomically claim disjoint rows and the
|
|
180
|
+
* returned count matches the rows actually deleted.
|
|
181
|
+
*/
|
|
182
|
+
async deleteExpiredPayloads(batchSize = 5000, before = new Date()): Promise<number> {
|
|
183
|
+
const result = await this.db.execute(sql`
|
|
184
|
+
DELETE FROM ${telemetryPayloads}
|
|
185
|
+
WHERE ${telemetryPayloads.id} IN (
|
|
186
|
+
SELECT ${telemetryPayloads.id} FROM ${telemetryPayloads}
|
|
187
|
+
WHERE ${telemetryPayloads.expiresAt} < ${before}
|
|
188
|
+
ORDER BY ${telemetryPayloads.expiresAt}
|
|
189
|
+
LIMIT ${batchSize}
|
|
190
|
+
)
|
|
191
|
+
`);
|
|
192
|
+
return affectedRows(result);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Deletes at most one bounded batch of aged events in a single statement, so
|
|
197
|
+
* concurrent sweeps atomically claim disjoint rows and the returned count
|
|
198
|
+
* matches the rows actually deleted. Ordered by `created_at`, which
|
|
199
|
+
* `idx_telemetry_created_at` backs.
|
|
200
|
+
*/
|
|
201
|
+
async deleteExpiredEvents(batchSize = 5000, before: Date): Promise<number> {
|
|
202
|
+
const result = await this.db.execute(sql`
|
|
203
|
+
DELETE FROM ${telemetryEvents}
|
|
204
|
+
WHERE ${telemetryEvents.id} IN (
|
|
205
|
+
SELECT ${telemetryEvents.id} FROM ${telemetryEvents}
|
|
206
|
+
WHERE ${telemetryEvents.createdAt} < ${before}
|
|
207
|
+
ORDER BY ${telemetryEvents.createdAt}
|
|
208
|
+
LIMIT ${batchSize}
|
|
209
|
+
)
|
|
210
|
+
`);
|
|
211
|
+
return affectedRows(result);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Deletes telemetry older than the supplied cutoff. Payloads follow their
|
|
216
|
+
* `expires_at` contract (15 minutes per row) rather than capture time.
|
|
217
|
+
*
|
|
218
|
+
* Events are pruned in bounded batches rather than one `DELETE`, because
|
|
219
|
+
* `telemetry_events` is the highest-volume table and a single statement
|
|
220
|
+
* covering every aged row grows without limit: it holds locks and WAL for the
|
|
221
|
+
* whole sweep, and on a deployment that has run for months it can exceed the
|
|
222
|
+
* pool's `statement_timeout` and be cancelled, so the sweep never converges
|
|
223
|
+
* and retention silently stops working. Each batch commits independently, so
|
|
224
|
+
* progress survives a cancel or a restart. The loop is bounded so one pass
|
|
225
|
+
* cannot run unbounded — a backlog drains over successive sweeps — mirroring
|
|
226
|
+
* the payload sweeper's `cleanupExpired`.
|
|
227
|
+
*/
|
|
228
|
+
async pruneTelemetry(before: Date): Promise<void> {
|
|
229
|
+
await pruneEventFiles(before);
|
|
230
|
+
const MAX_BATCHES_PER_SWEEP = 40;
|
|
231
|
+
for (let batch = 0; batch < MAX_BATCHES_PER_SWEEP; batch += 1) {
|
|
232
|
+
const deleted = await this.deleteExpiredEvents(5000, before);
|
|
233
|
+
if (deleted < 5000) break;
|
|
234
|
+
}
|
|
235
|
+
await this.deleteExpiredPayloads(5000, before);
|
|
236
|
+
}
|
|
237
|
+
}
|
package/src/protocol/codecs.ts
CHANGED
|
@@ -485,13 +485,12 @@ export function endpointUrl(
|
|
|
485
485
|
return url;
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
-
/** Session-scoped state (turn-state /
|
|
488
|
+
/** Session-scoped state (turn-state / session identity). */
|
|
489
489
|
export interface CodexSessionState {
|
|
490
490
|
sessionId: string;
|
|
491
491
|
threadId: string;
|
|
492
492
|
windowId: string;
|
|
493
493
|
turnState?: string;
|
|
494
|
-
modelsEtag?: string;
|
|
495
494
|
turnStartedAtUnixMs?: number;
|
|
496
495
|
}
|
|
497
496
|
|
|
@@ -56,6 +56,17 @@ export interface ImageClampInput {
|
|
|
56
56
|
readonly height?: number;
|
|
57
57
|
/** Images in the whole request, including this one. Defaults to this one alone. */
|
|
58
58
|
readonly imageCount?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Per-tenant byte ceiling override, in bytes.
|
|
61
|
+
*
|
|
62
|
+
* Absent means the deployment default (`IMAGE_CLAMP_MAX_BYTES`). A tenant
|
|
63
|
+
* raising this asserts that its upstreams accept larger attachments: the
|
|
64
|
+
* clamp exists so an oversized block cannot poison every retry and every
|
|
65
|
+
* replayed turn with a request the upstream rejects whole. An override
|
|
66
|
+
* promising more than the provider accepts turns that protection back off,
|
|
67
|
+
* which is why it is a deliberate per-tenant choice and not a global knob.
|
|
68
|
+
*/
|
|
69
|
+
readonly maxBytesOverride?: number;
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
export interface ImageClampDecision {
|
|
@@ -108,7 +119,14 @@ export function imageClampMaxDimensionPx(imageCount: number): number {
|
|
|
108
119
|
export function clampImage(input: ImageClampInput): ImageClampDecision {
|
|
109
120
|
const imageCount = input.imageCount ?? 1;
|
|
110
121
|
const maxDimensionPx = imageClampMaxDimensionPx(imageCount);
|
|
111
|
-
|
|
122
|
+
// A non-finite or non-positive override is not a ceiling, it is a mistake:
|
|
123
|
+
// falling back to the default keeps a malformed preference from silently
|
|
124
|
+
// admitting (or rejecting) every image.
|
|
125
|
+
const override = input.maxBytesOverride;
|
|
126
|
+
const maxBytes =
|
|
127
|
+
typeof override === "number" && Number.isFinite(override) && override > 0
|
|
128
|
+
? Math.floor(override)
|
|
129
|
+
: IMAGE_CLAMP_MAX_BYTES;
|
|
112
130
|
const longestEdge = Math.max(input.width ?? 0, input.height ?? 0);
|
|
113
131
|
if (longestEdge > maxDimensionPx) {
|
|
114
132
|
return {
|
package/src/protocol/registry.ts
CHANGED
|
@@ -8,6 +8,11 @@ export interface CodecContext {
|
|
|
8
8
|
readonly maxOutputTokens?: number;
|
|
9
9
|
readonly promptCacheKey?: string;
|
|
10
10
|
readonly signal?: AbortSignal;
|
|
11
|
+
/**
|
|
12
|
+
* Per-tenant ceiling for one inline attachment, in bytes. Absent means the
|
|
13
|
+
* deployment default; only wire encoders that clamp images read it.
|
|
14
|
+
*/
|
|
15
|
+
readonly maxBytesOverride?: number;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
/**
|
|
@@ -138,6 +143,9 @@ export async function encodeWireRequest(
|
|
|
138
143
|
if (codec.family === "messages") {
|
|
139
144
|
return codec.encodeWireRequest(request, {
|
|
140
145
|
...(context?.isOAuth === undefined ? {} : { isOAuth: context.isOAuth }),
|
|
146
|
+
...(context?.maxBytesOverride === undefined
|
|
147
|
+
? {}
|
|
148
|
+
: { maxBytesOverride: context.maxBytesOverride }),
|
|
141
149
|
});
|
|
142
150
|
}
|
|
143
151
|
if (codec.family === "chat") {
|
|
@@ -147,6 +147,7 @@ function geminiMediaPart(part: {
|
|
|
147
147
|
function toGeminiPart(
|
|
148
148
|
part: CanonicalRequest["messages"][number]["content"][number],
|
|
149
149
|
toolNames: ReadonlyMap<string, string>,
|
|
150
|
+
maxBytesOverride?: number,
|
|
150
151
|
): Record<string, unknown>[] {
|
|
151
152
|
if (part.kind === "text") return [{ text: part.text }];
|
|
152
153
|
if (part.kind === "reasoning") {
|
|
@@ -173,6 +174,7 @@ function toGeminiPart(
|
|
|
173
174
|
// whose attachment silently vanished.
|
|
174
175
|
const decision = clampImage({
|
|
175
176
|
bytes: base64DecodedByteLength(split.data),
|
|
177
|
+
...(maxBytesOverride === undefined ? {} : { maxBytesOverride }),
|
|
176
178
|
});
|
|
177
179
|
if (decision.action === "reject") {
|
|
178
180
|
// Only the byte ceiling refuses a block. A `downscale` decision is
|
|
@@ -255,7 +257,10 @@ function parseJsonObject(value: string): Record<string, unknown> {
|
|
|
255
257
|
}
|
|
256
258
|
}
|
|
257
259
|
|
|
258
|
-
export function buildGeminiPayload(
|
|
260
|
+
export function buildGeminiPayload(
|
|
261
|
+
request: CanonicalRequest,
|
|
262
|
+
options: { maxBytesOverride?: number } = {},
|
|
263
|
+
): Record<string, unknown> {
|
|
259
264
|
const systemParts: Array<{ text: string }> = [];
|
|
260
265
|
const pushSystemText = (text: string): void => {
|
|
261
266
|
if (!isClaudeBillingHeaderText(text)) systemParts.push({ text });
|
|
@@ -275,7 +280,9 @@ export function buildGeminiPayload(request: CanonicalRequest): Record<string, un
|
|
|
275
280
|
.map((message) => {
|
|
276
281
|
// Gemini roles: user or model (assistant -> model)
|
|
277
282
|
const role = message.role === "assistant" ? "model" : message.role === "tool" ? "user" : "user";
|
|
278
|
-
const parts = message.content.flatMap((block) =>
|
|
283
|
+
const parts = message.content.flatMap((block) =>
|
|
284
|
+
toGeminiPart(block, toolNames, options.maxBytesOverride),
|
|
285
|
+
);
|
|
279
286
|
return { role, parts: parts.length ? parts : [{ text: "" }] };
|
|
280
287
|
});
|
|
281
288
|
const payload: Record<string, unknown> = { contents };
|
|
@@ -121,6 +121,7 @@ function applyImageClamp(
|
|
|
121
121
|
block: ClaudeWireObject,
|
|
122
122
|
imageCount: number,
|
|
123
123
|
payload: unknown,
|
|
124
|
+
maxBytesOverride?: number,
|
|
124
125
|
): ClaudeWireObject {
|
|
125
126
|
const source = block["source"];
|
|
126
127
|
const data = isRecord(source) ? source["data"] : undefined;
|
|
@@ -131,6 +132,7 @@ function applyImageClamp(
|
|
|
131
132
|
...(width === undefined ? {} : { width }),
|
|
132
133
|
...(height === undefined ? {} : { height }),
|
|
133
134
|
imageCount,
|
|
135
|
+
...(maxBytesOverride === undefined ? {} : { maxBytesOverride }),
|
|
134
136
|
});
|
|
135
137
|
if (decision.action === "pass") return block;
|
|
136
138
|
log.warn(
|
|
@@ -281,6 +283,7 @@ function partToClaudeBlock(
|
|
|
281
283
|
idMap: Map<string, string>;
|
|
282
284
|
isOAuth: boolean;
|
|
283
285
|
imageCount: number;
|
|
286
|
+
maxBytesOverride?: number;
|
|
284
287
|
},
|
|
285
288
|
): ClaudeWireObject | undefined {
|
|
286
289
|
switch (part.kind) {
|
|
@@ -291,6 +294,7 @@ function partToClaudeBlock(
|
|
|
291
294
|
normalizeImageBlock(part.payload),
|
|
292
295
|
ctx?.imageCount ?? 1,
|
|
293
296
|
part.payload,
|
|
297
|
+
ctx?.maxBytesOverride,
|
|
294
298
|
);
|
|
295
299
|
case "file":
|
|
296
300
|
return {
|
|
@@ -553,14 +557,22 @@ export function claudeFallbacksChain(
|
|
|
553
557
|
|
|
554
558
|
export function canonicalToClaudeMessagesPayload(
|
|
555
559
|
request: CanonicalRequest,
|
|
556
|
-
options: { isOAuth?: boolean } = {},
|
|
560
|
+
options: { isOAuth?: boolean; maxBytesOverride?: number } = {},
|
|
557
561
|
): ClaudeWireObject {
|
|
558
562
|
const seen = new Map<string, number>();
|
|
559
563
|
const idMap = new Map<string, string>();
|
|
560
564
|
// The clamp's per-image ceiling depends on how many images the whole request
|
|
561
565
|
// carries, so it is counted once over every part rather than per message.
|
|
562
566
|
const imageCount = allParts(request).filter((part) => part.kind === "image").length;
|
|
563
|
-
const ctx = {
|
|
567
|
+
const ctx = {
|
|
568
|
+
seen,
|
|
569
|
+
idMap,
|
|
570
|
+
isOAuth: options.isOAuth ?? false,
|
|
571
|
+
imageCount,
|
|
572
|
+
...(options.maxBytesOverride === undefined
|
|
573
|
+
? {}
|
|
574
|
+
: { maxBytesOverride: options.maxBytesOverride }),
|
|
575
|
+
};
|
|
564
576
|
const messages: ClaudeWireObject[] = [];
|
|
565
577
|
// Anthropic accepts system content only at the top level. Chat `developer`
|
|
566
578
|
// turns and Responses `instructions` have no `developer` role upstream, and
|
|
@@ -207,6 +207,9 @@ export class OpenAICompatibleAdapter extends BaseProviderAdapter {
|
|
|
207
207
|
...(this.config.chat_token_limit_field === undefined
|
|
208
208
|
? {}
|
|
209
209
|
: { chatTokenLimitField: this.config.chat_token_limit_field }),
|
|
210
|
+
...(context.image_clamp_max_bytes === undefined
|
|
211
|
+
? {}
|
|
212
|
+
: { maxBytesOverride: context.image_clamp_max_bytes }),
|
|
210
213
|
});
|
|
211
214
|
if (
|
|
212
215
|
wireFamily !== "messages" &&
|