cartethyia 2.5.3 → 2.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -4
- package/README.md +2 -2
- package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
- package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
- package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
- package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
- package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
- package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
- package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
- package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
- package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
- package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
- package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
- package/dist/dashboard/assets/tokens-COzario9.js +1 -0
- package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
- package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -57
- package/migrations/README.md +52 -52
- package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
- package/package.json +96 -95
- package/scripts/cli/admin.ts +125 -0
- package/scripts/cli/autostart.ts +237 -0
- package/scripts/cli/{ui/background.ts → background.ts} +1 -1
- package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
- package/scripts/cli/doctor.ts +37 -0
- package/scripts/cli/{support/env.ts → env.ts} +99 -99
- package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
- package/scripts/cli/instance.ts +224 -0
- package/scripts/cli/main.ts +88 -18
- package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
- package/scripts/cli/port.ts +100 -0
- package/scripts/cli/{support/services.ts → services.ts} +88 -88
- package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
- package/scripts/cli/{command/start.ts → start.ts} +10 -5
- package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
- package/scripts/cli/tray/index.ts +62 -12
- package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
- package/scripts/cli/update.ts +40 -0
- package/scripts/cli/version.ts +67 -0
- package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
- package/src/boot/app.ts +11 -7
- package/src/boot/dependencies.ts +50 -19
- package/src/boot/timeout.ts +2 -13
- package/src/config/cooldowns.ts +186 -138
- package/src/config/network.ts +8 -14
- package/src/config/providers.ts +7 -10
- package/src/config/runtime.ts +76 -76
- package/src/config/security.ts +15 -0
- package/src/config/spec.ts +52 -7
- package/src/config/telemetry.ts +26 -0
- package/src/config/timeouts.ts +0 -6
- package/src/console/admin/model-abuse/contracts.ts +28 -6
- package/src/console/admin/model-abuse/routes.ts +56 -3
- package/src/console/api-keys/bucket-spend.ts +15 -4
- package/src/console/api-keys/contracts.ts +410 -414
- package/src/console/api-keys/routes.ts +881 -880
- package/src/console/auth/credentials.ts +3 -3
- package/src/console/auth/setup-service.ts +2 -2
- package/src/console/backup/contracts.ts +15 -2
- package/src/console/backup/export-endpoint.ts +6 -2
- package/src/console/backup/nine-router.ts +2 -5
- package/src/console/backup/service.ts +4 -0
- package/src/console/backup/store.ts +192 -29
- package/src/console/backup/validate.ts +3 -45
- package/src/console/cli-tools/fs-ops.ts +487 -487
- package/src/console/console-router-guards.ts +1 -1
- package/src/console/console-router.ts +8 -0
- package/src/console/data-management/contracts.ts +100 -0
- package/src/console/data-management/operations.ts +234 -0
- package/src/console/data-management/routes.ts +103 -0
- package/src/console/domain-registration.ts +45 -1
- package/src/console/observability/contracts.ts +524 -485
- package/src/console/observability/live.ts +22 -0
- package/src/console/observability/store.ts +1138 -1018
- package/src/console/observability/usage-dimensions.ts +4 -4
- package/src/console/providers/catalog/catalog-projections.ts +6 -7
- package/src/console/providers/catalog/contracts.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +5 -2
- package/src/console/providers/catalog/provider-operations.ts +24 -4
- package/src/console/providers/catalog/public-model-store.ts +8 -11
- package/src/console/providers/catalog/routes.ts +7 -0
- package/src/console/providers/catalog/store.ts +1553 -1532
- package/src/console/providers/oauth/routes.ts +8 -12
- package/src/console/quota/account-quota-shared.ts +52 -39
- package/src/console/quota/account-quota-tenant.ts +6 -6
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/quota/refresh.ts +441 -429
- package/src/console/remote-support/contracts.ts +61 -0
- package/src/console/remote-support/routes.ts +65 -0
- package/src/console/remote-support/service.ts +207 -0
- package/src/console/response-compression.ts +0 -3
- package/src/console/routing/pools/import-parse.ts +206 -0
- package/src/console/routing/pools/operations.ts +543 -501
- package/src/console/routing/pools/probe-result.ts +3 -2
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -164
- package/src/console/routing/pools/store.ts +8 -8
- package/src/console/settings/contracts.ts +95 -2
- package/src/console/settings/store.ts +81 -3
- package/src/console/shared/cursor.ts +8 -8
- package/src/console/shared/errors.ts +1 -3
- package/src/console/updates/routes.ts +16 -0
- package/src/main.ts +10 -0
- package/src/network/http2-bidi.ts +2 -4
- package/src/network/pool/agent.ts +478 -478
- package/src/network/pool/selector.ts +5 -4
- package/src/network/retry.ts +1 -2
- package/src/network/ssrf.ts +5 -4
- package/src/observability/event-compaction.ts +317 -0
- package/src/observability/event-day-index.ts +440 -0
- package/src/observability/event-log.ts +155 -6
- package/src/observability/logger.ts +6 -6
- package/src/observability/payload-capture.ts +158 -33
- package/src/observability/payload-store.ts +82 -61
- package/src/observability/redaction.ts +5 -6
- package/src/observability/request-notify.ts +43 -0
- package/src/observability/telemetry-status.ts +8 -9
- package/src/observability/token-speed.ts +5 -4
- package/src/persistence/db-mode.ts +7 -7
- package/src/persistence/health-event-flush.ts +36 -0
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/schema.ts +75 -29
- package/src/persistence/shared-cache.ts +1 -2
- package/src/persistence/stores/api-key-store.ts +18 -6
- package/src/persistence/stores/share-store.ts +4 -3
- package/src/persistence/stores/telemetry-store.ts +33 -11
- package/src/protocol/canonical-model.ts +11 -3
- package/src/protocol/codecs.ts +139 -6
- package/src/protocol/gateway-error.ts +6 -4
- package/src/protocol/image-clamp.ts +19 -1
- package/src/protocol/registry.ts +8 -0
- package/src/protocol/request/chat.ts +21 -1
- package/src/protocol/request/codex.ts +10 -1
- package/src/protocol/request/gemini.ts +9 -2
- package/src/protocol/request/messages.ts +18 -7
- package/src/protocol/request/responses.ts +9 -1
- package/src/protocol/response/chat.ts +34 -4
- package/src/protocol/response/codex.ts +7 -4
- package/src/protocol/response/dsml.ts +140 -28
- package/src/protocol/response/responses.ts +11 -5
- package/src/protocol/stream-error-frames.ts +4 -5
- package/src/providers/adapters/compatible-adapter.ts +3 -0
- package/src/providers/authentication/jwt-validator.ts +9 -10
- package/src/providers/authentication/oauth-refresh-service.ts +9 -11
- package/src/providers/bundled-discovery-providers.ts +122 -119
- package/src/providers/configured-openai-providers.ts +4 -4
- package/src/providers/default-registry.ts +5 -4
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/catalog/catalog-format.ts +3 -6
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/openai-model-discovery.ts +5 -0
- package/src/providers/discovery/openrouter-free-roster.ts +15 -17
- package/src/providers/discovery/probe-phases.ts +4 -4
- package/src/providers/discovery/probe-wire.ts +6 -13
- package/src/providers/discovery/probing-service.ts +1117 -1045
- package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
- package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
- package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
- package/src/providers/integrations/buddy/buddy-media.ts +2 -2
- package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
- package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
- package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
- package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
- package/src/providers/integrations/claude/claude.ts +608 -608
- package/src/providers/integrations/cline/cline-quota.ts +27 -20
- package/src/providers/integrations/cline/cline.ts +537 -539
- package/src/providers/integrations/codex/codex-headers.ts +25 -39
- package/src/providers/integrations/codex/codex-oauth.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +2 -29
- package/src/providers/integrations/commandcode/commandcode.ts +674 -675
- package/src/providers/integrations/commandcode/discovery.ts +117 -116
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
- package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
- package/src/providers/integrations/cursor/cursor-request.ts +616 -471
- package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
- package/src/providers/integrations/cursor/cursor.ts +1333 -960
- package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
- package/src/providers/integrations/factory/factory-models.ts +3 -5
- package/src/providers/integrations/factory/factory.ts +8 -1
- package/src/providers/integrations/gemini.ts +6 -1
- package/src/providers/integrations/grok/grok-install-id.ts +2 -3
- package/src/providers/integrations/grok/grok-quota.ts +279 -177
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
- package/src/providers/integrations/grok/grok.ts +6 -7
- package/src/providers/integrations/hermes.ts +14 -18
- package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
- package/src/providers/integrations/kiro/kiro.ts +1 -1
- package/src/providers/integrations/mistral.ts +6 -6
- package/src/providers/integrations/opencode/fingerprint.ts +3 -3
- package/src/providers/integrations/opencode/provider.ts +19 -20
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
- package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
- package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
- package/src/providers/integrations/qoder/qoder.ts +884 -865
- package/src/providers/integrations/shared/connect.ts +6 -6
- package/src/providers/integrations/trae/trae-catalog.ts +1 -2
- package/src/providers/integrations/trae/trae-quota.ts +384 -0
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/integrations/zcode/oauth.ts +6 -1
- package/src/providers/operations/account-health-service.ts +75 -27
- package/src/providers/operations/account-reset-service.ts +1 -1
- package/src/providers/operations/client-versions.ts +6 -6
- package/src/providers/operations/custom-cli-headers.ts +3 -4
- package/src/providers/operations/provider-catalog-seeder.ts +95 -95
- package/src/providers/operations/provider-catalog-service.ts +3 -4
- package/src/providers/provider-contracts.ts +7 -0
- package/src/providers/provider-metadata.ts +10 -8
- package/src/providers/provider-registry.ts +6 -7
- package/src/providers/provider-usage.ts +1 -1
- package/src/providers/quota/quota-support.ts +8 -0
- package/src/security/abuse.ts +15 -10
- package/src/security/access-control.ts +144 -146
- package/src/security/admission/contracts.ts +11 -0
- package/src/security/admission/in-memory-store.ts +4 -3
- package/src/security/admission/lease-sweep.ts +6 -6
- package/src/security/admission/reasons.ts +5 -1
- package/src/security/admission/redis-store.ts +211 -210
- package/src/security/admission/service.ts +4 -0
- package/src/security/api-key-auth.ts +27 -17
- package/src/security/ip-boundary.ts +155 -15
- package/src/security/model-abuse.ts +366 -94
- package/src/security/outbound-headers.ts +3 -4
- package/src/shared/sleep.ts +4 -0
- package/src/transport/dispatch/attempt-finalize.ts +47 -5
- package/src/transport/dispatch/attempt-loop.ts +531 -530
- package/src/transport/dispatch/generation.ts +6 -0
- package/src/transport/dispatch/proxy-request.ts +24 -1
- package/src/transport/dispatch/ratelimit-capture.ts +37 -0
- package/src/transport/dispatch/stream-bridge.ts +1 -3
- package/src/transport/dispatch/streaming/finalize.ts +1 -1
- package/src/transport/dispatch/streaming/stream.ts +19 -6
- package/src/transport/dispatch/upstream.ts +9 -0
- package/src/transport/failure-policy.ts +162 -8
- package/src/transport/guards/auth-guard.ts +7 -4
- package/src/transport/guards/concurrency-guard.ts +0 -5
- package/src/transport/guards/error-lifecycle.ts +13 -6
- package/src/transport/guards/pipeline.ts +15 -3
- package/src/transport/guards/request-context.ts +8 -2
- package/src/transport/request/inflight.ts +5 -6
- package/src/transport/request/preparer.ts +9 -8
- package/src/transport/request/state.ts +21 -8
- package/src/transport/request/tenant-preferences.ts +60 -0
- package/src/transport/routing/admission.ts +4 -0
- package/src/transport/routing/route-catalog.ts +65 -10
- package/src/transport/routing/route-model.ts +46 -14
- package/src/transport/routing/router.ts +21 -14
- package/src/transport/surface/completion.ts +2 -3
- package/src/transport/surface/dialects.ts +5 -6
- package/src/transport/surface/responses/encode.ts +38 -0
- package/src/transport/surface/responses/parse.ts +4 -0
- package/src/transport/translation/capabilities.ts +2 -3
- package/src/transport/translation/quirks.ts +3 -3
- package/src/workers/buddy-growth-tasks.ts +5 -10
- package/src/workers/daily-checkin.ts +17 -23
- package/src/workers/quota-refresh-sweep.ts +26 -26
- package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
- package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
- package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
- package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
- package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
- package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
- package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
- package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
- package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
- package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
- package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
- package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
- package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
- package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
- package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
- package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
- package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
- package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
- package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
- package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
- package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
- package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
- package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
- package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
- package/scripts/cli/command/admin.ts +0 -65
- package/scripts/cli/command/doctor.ts +0 -257
- package/scripts/cli/command/update.ts +0 -23
- package/scripts/cli/gateway/drain-token.ts +0 -78
- package/scripts/cli/gateway/instance-lock.ts +0 -105
- package/scripts/cli/gateway/stop.ts +0 -51
- package/scripts/cli/support/bun.ts +0 -27
- package/scripts/cli/support/version.ts +0 -37
- /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
- /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
- /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
package/src/protocol/codecs.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* `src/protocol/`. This is the single home for the generic guards, UTF-16
|
|
4
4
|
* well-formedness coercion, JSON/argument helpers, hash helpers, endpoint
|
|
5
5
|
* URL construction, image-source resolution, and custom-header validation
|
|
6
|
-
* that the per-wire request/response codecs build on
|
|
7
|
-
* across the Claude/Codex protocol modules and `providers/compatible-adapter`.
|
|
6
|
+
* that the per-wire request/response codecs build on.
|
|
8
7
|
*
|
|
9
8
|
* Provider-specific primitives that depend on provider identity (e.g. Codex
|
|
10
9
|
* identity headers) deliberately live with their provider, not here.
|
|
@@ -273,6 +272,46 @@ const SUPPORTED_SCHEMA_KEYS: Readonly<Record<string, true>> = Object.freeze({
|
|
|
273
272
|
maxProperties: true,
|
|
274
273
|
});
|
|
275
274
|
|
|
275
|
+
/**
|
|
276
|
+
* True when a schema string carries a backslash before an underscore.
|
|
277
|
+
*
|
|
278
|
+
* Some clients emit a property or pattern key containing a literal `\_` (an
|
|
279
|
+
* escaped underscore). Upstreams reject it, so the escape has to be stripped —
|
|
280
|
+
* and stripping it can collapse two previously distinct keys onto the same
|
|
281
|
+
* name, which is why {@link mergeCollidingKeys} exists below.
|
|
282
|
+
*/
|
|
283
|
+
function hasUnderscoreEscape(value: string): boolean {
|
|
284
|
+
return /\\+_/.test(value);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Merges map entries whose normalized keys collide, rather than overwriting.
|
|
289
|
+
*
|
|
290
|
+
* `allOf` accepts boolean schemas, so neither constraint is lost — a plain
|
|
291
|
+
* overwrite would silently drop one of two rules the caller actually sent.
|
|
292
|
+
*/
|
|
293
|
+
function mergeCollidingKeys(entries: ReadonlyArray<readonly [string, unknown]>): Record<string, unknown> {
|
|
294
|
+
const merged: Record<string, unknown> = {};
|
|
295
|
+
for (const [key, value] of entries) {
|
|
296
|
+
const existing = merged[key];
|
|
297
|
+
if (existing === undefined) {
|
|
298
|
+
merged[key] = value;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
merged[key] = { allOf: [existing, value] };
|
|
302
|
+
}
|
|
303
|
+
return merged;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** One schema map's keys, with `\_` escapes stripped and collisions merged. */
|
|
307
|
+
function normalizeSchemaMapKeys(map: Record<string, unknown>): Record<string, unknown> {
|
|
308
|
+
const entries = Object.entries(map);
|
|
309
|
+
if (!entries.some(([key]) => hasUnderscoreEscape(key))) return map;
|
|
310
|
+
return mergeCollidingKeys(
|
|
311
|
+
entries.map(([key, value]) => [key.replace(/\\+_/g, "_"), value] as const),
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
276
315
|
export function sanitizeSchemaForAnthropic(value: unknown): unknown {
|
|
277
316
|
return sanitizeSchemaWalk(value, ROOT_WALK);
|
|
278
317
|
}
|
|
@@ -292,10 +331,21 @@ function sanitizeSchemaWalk(value: unknown, walk: RewriteWalk): unknown {
|
|
|
292
331
|
// preserve them while sanitizing each property's schema recursively.
|
|
293
332
|
if (key === "properties" && isRecord(child)) {
|
|
294
333
|
out.properties = Object.fromEntries(
|
|
295
|
-
|
|
334
|
+
// Escaped-underscore keys are normalized first: two names differing only
|
|
335
|
+
// by an escape would otherwise survive as two properties and be rebuilt
|
|
336
|
+
// into a `required` list the upstream rejects.
|
|
337
|
+
Object.entries(normalizeSchemaMapKeys(child)).map(([name, schema]) => [
|
|
338
|
+
name,
|
|
339
|
+
sanitizeSchemaWalk(schema, inner),
|
|
340
|
+
]),
|
|
296
341
|
);
|
|
297
342
|
continue;
|
|
298
343
|
}
|
|
344
|
+
// `patternProperties` is deliberately NOT re-emitted: it is absent from
|
|
345
|
+
// `SUPPORTED_SCHEMA_KEYS` because the Anthropic wire does not accept it, and
|
|
346
|
+
// writing it here would be undone by the keyword filter a few lines down
|
|
347
|
+
// anyway. Its keys are still walked below, so nested schemas are sanitized
|
|
348
|
+
// and an escaped-underscore key inside one is still normalized.
|
|
299
349
|
// Drop wire-unsupported keywords so a strict upstream never 400s on
|
|
300
350
|
// them — but never invent caller text to say so. The dropped names are
|
|
301
351
|
// debug-logged by the caller-facing builder when that matters.
|
|
@@ -485,13 +535,12 @@ export function endpointUrl(
|
|
|
485
535
|
return url;
|
|
486
536
|
}
|
|
487
537
|
|
|
488
|
-
/** Session-scoped state (turn-state /
|
|
538
|
+
/** Session-scoped state (turn-state / session identity). */
|
|
489
539
|
export interface CodexSessionState {
|
|
490
540
|
sessionId: string;
|
|
491
541
|
threadId: string;
|
|
492
542
|
windowId: string;
|
|
493
543
|
turnState?: string;
|
|
494
|
-
modelsEtag?: string;
|
|
495
544
|
turnStartedAtUnixMs?: number;
|
|
496
545
|
}
|
|
497
546
|
|
|
@@ -677,12 +726,96 @@ export function canonicalTerminal(input: {
|
|
|
677
726
|
};
|
|
678
727
|
}
|
|
679
728
|
|
|
729
|
+
/**
|
|
730
|
+
* JSON Schema keywords. A parameter root containing any of these is already a
|
|
731
|
+
* schema object, not a legacy property-name → schema map.
|
|
732
|
+
*/
|
|
733
|
+
const SCHEMA_KEYWORDS: ReadonlySet<string> = new Set([
|
|
734
|
+
"$schema", "$id", "$ref", "$anchor", "$dynamicRef", "$dynamicAnchor", "$defs",
|
|
735
|
+
"$vocabulary", "$comment", "definitions", "type", "properties", "patternProperties",
|
|
736
|
+
"additionalProperties", "unevaluatedProperties", "propertyNames", "required",
|
|
737
|
+
"dependentRequired", "dependentSchemas", "dependencies", "minProperties", "maxProperties",
|
|
738
|
+
"allOf", "anyOf", "oneOf", "not", "if", "then", "else", "items", "prefixItems",
|
|
739
|
+
"additionalItems", "contains", "unevaluatedItems", "minItems", "maxItems", "uniqueItems",
|
|
740
|
+
"minContains", "maxContains", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum",
|
|
741
|
+
"multipleOf", "minLength", "maxLength", "pattern", "format", "contentEncoding",
|
|
742
|
+
"contentMediaType", "contentSchema", "enum", "const", "title", "description", "default",
|
|
743
|
+
"examples", "deprecated", "readOnly", "writeOnly",
|
|
744
|
+
]);
|
|
745
|
+
|
|
746
|
+
/** The JSON Schema primitive type names. */
|
|
747
|
+
const SCHEMA_TYPES: ReadonlySet<string> = new Set([
|
|
748
|
+
"string", "number", "integer", "boolean", "object", "array", "null",
|
|
749
|
+
]);
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Rewrites one legacy tool parameter root into an explicit object schema.
|
|
753
|
+
*
|
|
754
|
+
* Two shapes reach here and are both rejected upstream as-is:
|
|
755
|
+
*
|
|
756
|
+
* - **An empty root.** `{}` carries no `type`, and a schema with no type is not
|
|
757
|
+
* a schema the upstream will accept — it answers `invalid_function_parameters`
|
|
758
|
+
* (CodeBuddy `11129`).
|
|
759
|
+
* - **A legacy property-name → schema map.** Every field names a parameter and
|
|
760
|
+
* carries its own boolean `required` flag, with no `{"type":"object",
|
|
761
|
+
* "properties":…}` wrapper around them. Old clients in the wild still emit
|
|
762
|
+
* this; the upstream wants the wrapper and the `required` array.
|
|
763
|
+
*
|
|
764
|
+
* Anything ambiguous is returned **untouched**. A root that already holds schema
|
|
765
|
+
* keywords, or one whose fields do not all look like `{type: <primitive>}`
|
|
766
|
+
* entries, is left alone: guessing there would rewrite a valid schema into a
|
|
767
|
+
* wrong one, which is worse than the refusal it was trying to avoid.
|
|
768
|
+
*/
|
|
769
|
+
export function normalizeToolParameterRoot(value: unknown): unknown {
|
|
770
|
+
if (!isRecord(value)) return value;
|
|
771
|
+
if (Object.keys(value).length === 0) return { type: "object", properties: {} };
|
|
772
|
+
// Schema keywords (or an extension/annotation key) mean this is a real
|
|
773
|
+
// schema object already, not a property map.
|
|
774
|
+
if (SCHEMA_KEYWORDS.has(Object.keys(value)[0] ?? "")) return value;
|
|
775
|
+
if (Object.keys(value).some((name) => SCHEMA_KEYWORDS.has(name))) return value;
|
|
776
|
+
if (Object.keys(value).some((name) => name.startsWith("$") || name.startsWith("x-")))
|
|
777
|
+
return value;
|
|
778
|
+
const entries = Object.entries(value);
|
|
779
|
+
// A legacy map is identified by its per-field boolean `required`. Without at
|
|
780
|
+
// least one, this is not recognisably the legacy shape.
|
|
781
|
+
if (!entries.some(([, field]) => isRecord(field) && typeof field["required"] === "boolean"))
|
|
782
|
+
return value;
|
|
783
|
+
if (
|
|
784
|
+
!entries.every(
|
|
785
|
+
([, field]) =>
|
|
786
|
+
isRecord(field) &&
|
|
787
|
+
typeof field["type"] === "string" &&
|
|
788
|
+
SCHEMA_TYPES.has(field["type"]) &&
|
|
789
|
+
(!("required" in field) || typeof field["required"] === "boolean"),
|
|
790
|
+
)
|
|
791
|
+
)
|
|
792
|
+
return value;
|
|
793
|
+
|
|
794
|
+
const properties: Record<string, unknown> = {};
|
|
795
|
+
const required: string[] = [];
|
|
796
|
+
for (const [name, field] of entries) {
|
|
797
|
+
const copy = { ...(field as Record<string, unknown>) };
|
|
798
|
+
if (copy["required"] === true) required.push(name);
|
|
799
|
+
delete copy["required"];
|
|
800
|
+
properties[name] = copy;
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
type: "object",
|
|
804
|
+
properties,
|
|
805
|
+
...(required.length > 0 ? { required } : {}),
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
680
809
|
/**
|
|
681
810
|
* Defaults a tool schema's `required` list to all declared properties while
|
|
682
811
|
* preserving an explicit list, including optional properties.
|
|
812
|
+
*
|
|
813
|
+
* Also normalizes a legacy parameter root first (see
|
|
814
|
+
* {@link normalizeToolParameterRoot}), so a caller sending an empty root or a
|
|
815
|
+
* property-name → schema map still ships a schema the upstream accepts.
|
|
683
816
|
*/
|
|
684
817
|
export function completeRequiredSchema(value: unknown): unknown {
|
|
685
|
-
return completeRequiredWalk(value, ROOT_WALK);
|
|
818
|
+
return completeRequiredWalk(normalizeToolParameterRoot(value), ROOT_WALK);
|
|
686
819
|
}
|
|
687
820
|
|
|
688
821
|
function completeRequiredWalk(value: unknown, walk: RewriteWalk): unknown {
|
|
@@ -12,6 +12,8 @@ export type GatewayErrorCode =
|
|
|
12
12
|
| "accounts_unavailable"
|
|
13
13
|
| "accounts_rate_limited"
|
|
14
14
|
| "capacity_exhausted"
|
|
15
|
+
| "provider_capacity_exhausted"
|
|
16
|
+
| "upstream_queued"
|
|
15
17
|
| "proxy_pool_capacity_exceeded"
|
|
16
18
|
| "proxy_pool_provider_capacity_exceeded"
|
|
17
19
|
| "proxy_pool_blocked"
|
|
@@ -110,11 +112,11 @@ function stripLegacyOriginLabel(message: string): string {
|
|
|
110
112
|
* stamped into it.
|
|
111
113
|
*
|
|
112
114
|
* The code is not lost — it travels in the envelope's `error.code` field, and
|
|
113
|
-
* the HTTP status carries the class.
|
|
115
|
+
* the HTTP status carries the class. Stamping it into the text
|
|
114
116
|
* (`accounts_unavailable: Model 'x' has no available account (52 candidate(s)
|
|
115
|
-
* unusable: credit_floor_reached, model_cooldown)`)
|
|
116
|
-
* names in front of the operator's sentence and
|
|
117
|
-
* while adding nothing the field
|
|
117
|
+
* unusable: credit_floor_reached, model_cooldown)`) would put internal enum
|
|
118
|
+
* names in front of the operator's sentence and make the message unreadable
|
|
119
|
+
* while adding nothing the field does not already carry.
|
|
118
120
|
*
|
|
119
121
|
* Stripping is idempotent, and that is the point: a message that already
|
|
120
122
|
* carries a `code:` prefix — stamped by an older builder, or by a code path
|
|
@@ -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") {
|
|
@@ -353,7 +353,24 @@ export function canonicalToChatPayload(
|
|
|
353
353
|
// `display: "omitted"`, and dropping it stripped the replay the
|
|
354
354
|
// provider demands on the next request. `reasoning_content` is an
|
|
355
355
|
// assistant-only field, so it is emitted here and nowhere else.
|
|
356
|
-
|
|
356
|
+
//
|
|
357
|
+
// Load-bearing requires *content* in the replay: a non-empty summary
|
|
358
|
+
// or a non-empty serialised payload. Pushing `{content:"",
|
|
359
|
+
// reasoning_content:""}` is the opposite of load-bearing — the
|
|
360
|
+
// upstream reads it as "thinking mode with the reasoning stripped"
|
|
361
|
+
// and rejects the whole request (`WorkBuddy/CodeBuddy 11151 "history
|
|
362
|
+
// not found"`). The canonical form that reaches this branch with no
|
|
363
|
+
// trace is an input `reasoning` item whose `summary` was absent and
|
|
364
|
+
// whose `payload` was `null` — a signature-less thinking block
|
|
365
|
+
// emitted under `display: "omitted"` and already stripped by an
|
|
366
|
+
// earlier hop. There is nothing to replay, so the turn is dropped
|
|
367
|
+
// instead of being forwarded as an invalid stub.
|
|
368
|
+
// This non-empty echo is load-bearing for non-buddy
|
|
369
|
+
// OpenAI-compatible providers. Buddy providers deliberately drop
|
|
370
|
+
// the resulting bare stub later in `dropEmptyBuddyMessages`
|
|
371
|
+
// (`providers/integrations/buddy/buddy-chat-shared.ts`) because
|
|
372
|
+
// their upstream rejects that shape with 11151.
|
|
373
|
+
if (m.role === "assistant" && hasReasoning && reasoningContent.length > 0) {
|
|
357
374
|
messages.push({
|
|
358
375
|
role: "assistant",
|
|
359
376
|
content: "",
|
|
@@ -361,6 +378,9 @@ export function canonicalToChatPayload(
|
|
|
361
378
|
});
|
|
362
379
|
continue;
|
|
363
380
|
}
|
|
381
|
+
// No trace to carry: skip the turn entirely rather than emit an
|
|
382
|
+
// empty assistant message the upstream will reject.
|
|
383
|
+
if (m.role === "assistant" && hasReasoning) continue;
|
|
364
384
|
if (m.role !== "assistant" && hasReasoning && reasoningContent.length > 0) {
|
|
365
385
|
const last = messages[messages.length - 1];
|
|
366
386
|
if (last !== undefined && last["role"] === "tool") {
|
|
@@ -374,7 +374,16 @@ export function canonicalToCodexResponsesPayload(
|
|
|
374
374
|
summary: summaryArray,
|
|
375
375
|
encrypted_content: r.encrypted_content,
|
|
376
376
|
};
|
|
377
|
-
|
|
377
|
+
// The upstream binds encrypted reasoning to the item it was issued
|
|
378
|
+
// for and rejects a replay whose id does not match, so the id that
|
|
379
|
+
// came back with the payload is authoritative. A synthesized
|
|
380
|
+
// `rs_<hash>` never matched the real item and failed the whole
|
|
381
|
+
// request with "Encrypted content item_id did not match the target
|
|
382
|
+
// item id"; only fall back to it when no id survived, where a
|
|
383
|
+
// mismatch is unavoidable anyway.
|
|
384
|
+
if (typeof r.item_id === "string" && r.item_id.length > 0) {
|
|
385
|
+
item["id"] = r.item_id;
|
|
386
|
+
} else if (r.summary !== undefined || r.encrypted_content !== undefined) {
|
|
378
387
|
const idSource = `${r.summary ?? ""}|${r.encrypted_content ?? ""}`;
|
|
379
388
|
if (idSource.length > 1) {
|
|
380
389
|
item["id"] = `rs_${hashToBase36(idSource)}`;
|
|
@@ -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(
|
|
@@ -155,12 +157,11 @@ function applyImageClamp(
|
|
|
155
157
|
* the upstream rejects anything else. A canonical image part is an opaque
|
|
156
158
|
* origin payload, so it can arrive in a vocabulary that is *not* Anthropic's:
|
|
157
159
|
* a Responses part carries `type: "input_image"` with a top-level `image_url`
|
|
158
|
-
* string, and a Chat part carries a nested `image_url` object.
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* `source.type: "input_image"` — an invalid block the provider refused.
|
|
160
|
+
* string, and a Chat part carries a nested `image_url` object. Forwarding either
|
|
161
|
+
* with its own `type` intact (or with no `source` at all) sends a Claude model
|
|
162
|
+
* `source.type: "input_image"` — an invalid block the provider refuses.
|
|
162
163
|
*
|
|
163
|
-
* Every shape
|
|
164
|
+
* Every shape collapses to a valid source, preferring the cheapest
|
|
164
165
|
* transport: a Files API reference, then a URL, then base64 bytes (a `data:`
|
|
165
166
|
* URL is split rather than embedded, because Anthropic has no data-URL field).
|
|
166
167
|
*/
|
|
@@ -281,6 +282,7 @@ function partToClaudeBlock(
|
|
|
281
282
|
idMap: Map<string, string>;
|
|
282
283
|
isOAuth: boolean;
|
|
283
284
|
imageCount: number;
|
|
285
|
+
maxBytesOverride?: number;
|
|
284
286
|
},
|
|
285
287
|
): ClaudeWireObject | undefined {
|
|
286
288
|
switch (part.kind) {
|
|
@@ -291,6 +293,7 @@ function partToClaudeBlock(
|
|
|
291
293
|
normalizeImageBlock(part.payload),
|
|
292
294
|
ctx?.imageCount ?? 1,
|
|
293
295
|
part.payload,
|
|
296
|
+
ctx?.maxBytesOverride,
|
|
294
297
|
);
|
|
295
298
|
case "file":
|
|
296
299
|
return {
|
|
@@ -553,14 +556,22 @@ export function claudeFallbacksChain(
|
|
|
553
556
|
|
|
554
557
|
export function canonicalToClaudeMessagesPayload(
|
|
555
558
|
request: CanonicalRequest,
|
|
556
|
-
options: { isOAuth?: boolean } = {},
|
|
559
|
+
options: { isOAuth?: boolean; maxBytesOverride?: number } = {},
|
|
557
560
|
): ClaudeWireObject {
|
|
558
561
|
const seen = new Map<string, number>();
|
|
559
562
|
const idMap = new Map<string, string>();
|
|
560
563
|
// The clamp's per-image ceiling depends on how many images the whole request
|
|
561
564
|
// carries, so it is counted once over every part rather than per message.
|
|
562
565
|
const imageCount = allParts(request).filter((part) => part.kind === "image").length;
|
|
563
|
-
const ctx = {
|
|
566
|
+
const ctx = {
|
|
567
|
+
seen,
|
|
568
|
+
idMap,
|
|
569
|
+
isOAuth: options.isOAuth ?? false,
|
|
570
|
+
imageCount,
|
|
571
|
+
...(options.maxBytesOverride === undefined
|
|
572
|
+
? {}
|
|
573
|
+
: { maxBytesOverride: options.maxBytesOverride }),
|
|
574
|
+
};
|
|
564
575
|
const messages: ClaudeWireObject[] = [];
|
|
565
576
|
// Anthropic accepts system content only at the top level. Chat `developer`
|
|
566
577
|
// turns and Responses `instructions` have no `developer` role upstream, and
|
|
@@ -163,7 +163,15 @@ export function canonicalToResponsesPayload(
|
|
|
163
163
|
flushMessage();
|
|
164
164
|
// Replays prior encrypted reasoning so the model retains continuity
|
|
165
165
|
// across turns; never decrypted or reconstructed from summary text.
|
|
166
|
-
|
|
166
|
+
// The upstream binds encrypted reasoning to the item it was issued
|
|
167
|
+
// for and rejects a replay whose id does not match ("Encrypted content
|
|
168
|
+
// item_id did not match the target item id"), so the item carries the
|
|
169
|
+
// id it was parsed with. Without one there is nothing to match and the
|
|
170
|
+
// payload would be rejected anyway — it is dropped rather than sent
|
|
171
|
+
// under an id the upstream cannot verify.
|
|
172
|
+
if (p.item_id !== undefined) {
|
|
173
|
+
input.push({ type: "reasoning", id: p.item_id, encrypted_content: p.encrypted_content });
|
|
174
|
+
}
|
|
167
175
|
} else if (p.kind === "reasoning") {
|
|
168
176
|
// Plain reasoning replayed from another surface (a Messages `thinking`
|
|
169
177
|
// block, or Chat `reasoning_content`). It has no encrypted artifact, so
|
|
@@ -10,7 +10,7 @@ import { mergeResponsesUsage } from "./responses";
|
|
|
10
10
|
import { usageFromProvider, readReasoningText } from "../../providers/provider-usage";
|
|
11
11
|
import { createToolEmitLedger } from "../tool-identity";
|
|
12
12
|
import { MimoThinkSplitter, stripThinkingTags } from "../../providers/integrations/xiaomi-mimo/think-stream";
|
|
13
|
-
import { parseDsmlToolCalls } from "./dsml";
|
|
13
|
+
import { DsmlToolCallParser, parseDsmlToolCalls } from "./dsml";
|
|
14
14
|
import {
|
|
15
15
|
OmpToolCallMarkupParser,
|
|
16
16
|
parseOmpToolCallMarkup,
|
|
@@ -83,7 +83,7 @@ export function parseChatResponseToEvents(
|
|
|
83
83
|
const contentWithoutMarkup = recoveredMarkup?.text ?? content;
|
|
84
84
|
const recovered =
|
|
85
85
|
typeof contentWithoutMarkup === "string"
|
|
86
|
-
? parseDsmlToolCalls(contentWithoutMarkup)
|
|
86
|
+
? parseDsmlToolCalls(contentWithoutMarkup, declaredToolNames)
|
|
87
87
|
: undefined;
|
|
88
88
|
const visible = recovered === undefined ? contentWithoutMarkup : recovered.text;
|
|
89
89
|
if (typeof visible === "string" && visible.includes("<think")) {
|
|
@@ -144,6 +144,13 @@ export function parseChatResponseToEvents(
|
|
|
144
144
|
: undefined;
|
|
145
145
|
return callId && name !== undefined && name.length > 0;
|
|
146
146
|
});
|
|
147
|
+
// A request that declared no tools carries no allowlist to enforce, so a
|
|
148
|
+
// recovered block is honoured as-is exactly as before; a request that did
|
|
149
|
+
// declare them must never execute a name it did not offer.
|
|
150
|
+
const recoveredCalls =
|
|
151
|
+
declaredToolNames.size > 0
|
|
152
|
+
? (recovered?.calls ?? []).filter((call) => callableToolNames.has(call.name))
|
|
153
|
+
: (recovered?.calls ?? []);
|
|
147
154
|
const toolCalls = hasStructuredCalls
|
|
148
155
|
? structuredCalls
|
|
149
156
|
: recoveredMarkup?.calls.some((call) => callableToolNames.has(call.name))
|
|
@@ -153,7 +160,7 @@ export function parseChatResponseToEvents(
|
|
|
153
160
|
id: call.id,
|
|
154
161
|
function: { name: call.name, arguments: call.arguments },
|
|
155
162
|
}))
|
|
156
|
-
:
|
|
163
|
+
: recoveredCalls;
|
|
157
164
|
if (!hasStructuredCalls && recoveredMarkup !== undefined) {
|
|
158
165
|
recoveredToolCalls = recoveredMarkup.calls.filter((call) =>
|
|
159
166
|
callableToolNames.has(call.name),
|
|
@@ -257,6 +264,8 @@ export async function* decodeChatSseStream(
|
|
|
257
264
|
(request.tools?.length ?? 0) > 0 ? new OmpToolCallMarkupParser(declaredToolNames) : undefined;
|
|
258
265
|
const reasoningMarkup =
|
|
259
266
|
(request.tools?.length ?? 0) > 0 ? new OmpToolCallMarkupParser(declaredToolNames) : undefined;
|
|
267
|
+
const dsmlMarkup =
|
|
268
|
+
(request.tools?.length ?? 0) > 0 ? new DsmlToolCallParser(declaredToolNames) : undefined;
|
|
260
269
|
yield {
|
|
261
270
|
type: "response_start",
|
|
262
271
|
sequence_number: seq++,
|
|
@@ -302,7 +311,7 @@ export async function* decodeChatSseStream(
|
|
|
302
311
|
const raw = delta["content"] as string;
|
|
303
312
|
const markup = contentMarkup?.push(raw);
|
|
304
313
|
if (markup !== undefined) recoveredMarkupCalls.push(...markup.calls);
|
|
305
|
-
const recovered =
|
|
314
|
+
const recovered = dsmlMarkup?.push(markup?.text ?? raw);
|
|
306
315
|
const visible = recovered?.text ?? markup?.text ?? raw;
|
|
307
316
|
const split = think.push(visible);
|
|
308
317
|
if (split.reasoning.length > 0) {
|
|
@@ -478,6 +487,27 @@ export async function* decodeChatSseStream(
|
|
|
478
487
|
}
|
|
479
488
|
}
|
|
480
489
|
}
|
|
490
|
+
const dsmlMarkupTail = dsmlMarkup?.flush();
|
|
491
|
+
if (dsmlMarkupTail !== undefined) {
|
|
492
|
+
if (dsmlMarkupTail.text.length > 0) {
|
|
493
|
+
const split = think.push(dsmlMarkupTail.text);
|
|
494
|
+
if (split.reasoning.length > 0) {
|
|
495
|
+
yield {
|
|
496
|
+
type: "content_delta",
|
|
497
|
+
sequence_number: seq++,
|
|
498
|
+
content: { kind: "reasoning", payload: null, summary: split.reasoning },
|
|
499
|
+
} as CanonicalEvent;
|
|
500
|
+
}
|
|
501
|
+
if (split.text.length > 0) {
|
|
502
|
+
yield {
|
|
503
|
+
type: "content_delta",
|
|
504
|
+
sequence_number: seq++,
|
|
505
|
+
content: { kind: "text", text: split.text },
|
|
506
|
+
} as CanonicalEvent;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
for (const call of dsmlMarkupTail.calls) recoveredMarkupCalls.push(call);
|
|
510
|
+
}
|
|
481
511
|
const reasoningMarkupTail = reasoningMarkup?.flush();
|
|
482
512
|
if (reasoningMarkupTail?.text) {
|
|
483
513
|
const reasoningText = stripThinkingTags(reasoningMarkupTail.text);
|
|
@@ -84,7 +84,10 @@ export function reasoningEvent(
|
|
|
84
84
|
payload: null,
|
|
85
85
|
...(summary === undefined ? {} : { summary }),
|
|
86
86
|
...(typeof encrypted === "string"
|
|
87
|
-
? {
|
|
87
|
+
? {
|
|
88
|
+
encrypted_content: encrypted,
|
|
89
|
+
...(typeof item["id"] === "string" ? { item_id: item["id"] } : {}),
|
|
90
|
+
}
|
|
88
91
|
: {}),
|
|
89
92
|
...(summary === undefined && typeof encrypted === "string"
|
|
90
93
|
? { opaque: true as const }
|
|
@@ -235,9 +238,9 @@ export function parseCodexResponsesJsonToEvents(
|
|
|
235
238
|
| undefined);
|
|
236
239
|
// An incomplete response that produced a tool call is always a tool-use
|
|
237
240
|
// stop; every other incomplete response is a length stop. There is no
|
|
238
|
-
// separate executability gate —
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
+
// separate executability gate — scanning tool arguments for parseable JSON
|
|
242
|
+
// returns `tool_use` on both outcomes, so the scan and the
|
|
243
|
+
// `incomplete_details.reason` read could not change the result.
|
|
241
244
|
const stopReason = mapCodexStopReason(status, hasToolCall, endTurn);
|
|
242
245
|
let finalStopReason = stopReason;
|
|
243
246
|
if (endTurn === false && stopReason === "stop") {
|