cartethyia 2.7.3 → 2.7.5
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 +2 -0
- package/README.md +17 -5
- package/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-Cj15GsMp.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-BIccZmeL.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-DkXb6dx-.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-DnQPSIA8.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js +9 -0
- package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-WTa0sktC.js} +1 -1
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-BfAb5-o_.js} +3 -3
- package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
- package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
- package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C0YYuZJ4.js → assert-C0K-c4eo.js} +1 -1
- package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-DfbhQU4k.js} +1 -1
- package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-BllT4LcF.js} +1 -1
- package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/{card-Be5Uzamx.js → card-CtkX66Wd.js} +1 -1
- package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-muFRphDu.js → cli-tools-CQXVMn4P.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-BQqBQd2n.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-BEK7KP0H.js} +1 -1
- package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-BrTEZ8zu.js} +1 -1
- package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
- package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-EBHJ3DrE.js} +1 -1
- package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-BAACA727.js} +1 -1
- package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-YSCE4UC2.js} +1 -1
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
- package/dist/dashboard/assets/{index-cPrX5vn6.js → index-FQlj0HUT.js} +2 -2
- package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
- package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-C4gU5iOL.js} +1 -1
- package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
- package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-BHHX_Tmm.js} +1 -1
- package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
- package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-CajrYUnD.js} +1 -1
- package/dist/dashboard/assets/{layout-C_7A6H55.js.gz → layout-CajrYUnD.js.gz} +0 -0
- package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-Bx2I7XUe.js} +1 -1
- package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
- package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
- package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
- package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
- package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
- package/dist/dashboard/assets/{routing-BEH32QOC.js → routing-in0f_IfK.js} +1 -1
- package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
- package/dist/dashboard/assets/{select-B3iTetHN.js → select-BLULNqTp.js} +2 -2
- package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
- package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-D2lIR0tg.js} +1 -1
- package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
- package/dist/dashboard/assets/{state-DLS7IDMy.js → state-DTARjfiW.js} +1 -1
- package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
- package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-c0oPSnqW.js} +1 -1
- package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-m8WGNPrz.js} +1 -1
- package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +59 -57
- package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
- package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
- package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
- package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
- package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
- package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
- package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
- package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
- package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
- package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
- package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
- package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
- package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
- package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
- package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
- package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
- package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
- package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
- package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
- package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
- package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
- package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
- package/package.json +4 -5
- package/scripts/cli/update.ts +15 -41
- package/src/boot/app.ts +67 -10
- package/src/boot/dependencies.ts +786 -732
- package/src/boot/lifecycle.ts +11 -12
- package/src/config/cooldowns.ts +34 -55
- package/src/config/network.ts +8 -4
- package/src/config/package-paths.ts +1 -1
- package/src/config/providers.ts +13 -14
- package/src/config/resources.ts +0 -5
- package/src/config/runtime.ts +2 -5
- package/src/config/spec.ts +26 -6
- package/src/console/api-keys/ban-constants.ts +38 -0
- package/src/console/api-keys/contracts.ts +92 -31
- package/src/console/api-keys/routes.ts +131 -6
- package/src/console/audit/routes.ts +3 -3
- package/src/console/auth/access.ts +4 -4
- package/src/console/auth/account-reset.ts +5 -5
- package/src/console/auth/audit-recorder.ts +1 -1
- package/src/console/auth/lockout.ts +7 -3
- package/src/console/auth/row-coercion.ts +57 -12
- package/src/console/auth/session-resolver.ts +1 -1
- package/src/console/auth/session-service.ts +8 -2
- package/src/console/auth/session.ts +40 -34
- package/src/console/auth/setup-service.ts +4 -3
- package/src/console/auth/user-store.ts +1 -1
- package/src/console/backup/9router-map.ts +0 -3
- package/src/console/backup/contracts.ts +11 -9
- package/src/console/backup/export-endpoint.ts +11 -12
- package/src/console/backup/nine-router.ts +1 -7
- package/src/console/backup/routes.ts +32 -36
- package/src/console/backup/service.ts +468 -468
- package/src/console/backup/store.ts +47 -29
- package/src/console/backup/validate.ts +11 -1
- package/src/console/cli-tools/contracts.ts +7 -7
- package/src/console/cli-tools/injectors/cline.ts +110 -110
- package/src/console/cli-tools/injectors/codex.ts +181 -181
- package/src/console/cli-tools/injectors/copilot.ts +108 -108
- package/src/console/cli-tools/injectors/driver.ts +247 -247
- package/src/console/cli-tools/injectors/jcode.ts +115 -115
- package/src/console/cli-tools/injectors/openclaw.ts +161 -161
- package/src/console/cli-tools/injectors/opencode.ts +153 -153
- package/src/console/cli-tools/routes.ts +4 -4
- package/src/console/cli-tools/service.ts +1 -1
- package/src/console/cli-tools/store.ts +2 -2
- package/src/console/console-router-guards.ts +2 -2
- package/src/console/console-router.ts +36 -4
- package/src/console/dashboard-assets.ts +9 -9
- package/src/console/data-management/routes.ts +3 -3
- package/src/console/domain-registration.ts +18 -4
- package/src/console/inferhub/client.ts +0 -7
- package/src/console/inferhub/key-store.ts +1 -1
- package/src/console/inferhub/normalize.ts +2 -2
- package/src/console/inferhub/routes.ts +1 -1
- package/src/console/inferhub/types.ts +1 -1
- package/src/console/observability/contracts.ts +5 -1
- package/src/console/observability/live.ts +34 -21
- package/src/console/observability/logs.ts +4 -4
- package/src/console/observability/performance.ts +3 -3
- package/src/console/observability/routes.ts +3 -3
- package/src/console/observability/sse.ts +62 -8
- package/src/console/observability/store.ts +36 -20
- package/src/console/providers/catalog/catalog-projections.ts +1 -1
- package/src/console/providers/catalog/contracts.ts +82 -22
- package/src/console/providers/catalog/model-operations.ts +1 -1
- package/src/console/providers/catalog/provider-operations.ts +110 -17
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +30 -6
- package/src/console/providers/catalog/store-helpers.ts +349 -326
- package/src/console/providers/catalog/store.ts +1662 -1292
- package/src/console/providers/detail/routes.ts +4 -4
- package/src/console/providers/oauth/callback-listener.ts +100 -18
- package/src/console/providers/oauth/routes.ts +95 -16
- package/src/console/quota/account-quota-global.ts +41 -3
- package/src/console/quota/account-quota-shared.ts +10 -3
- package/src/console/quota/account-quota-tenant.ts +285 -67
- package/src/console/quota/account-test-egress.ts +4 -4
- package/src/console/quota/daily-growth-job.ts +308 -0
- package/src/console/quota/native-identity.ts +2 -2
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/routes.ts +1 -1
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/routing/model/contracts.ts +1 -1
- package/src/console/routing/model/routes.ts +3 -3
- package/src/console/routing/pools/contracts.ts +13 -10
- package/src/console/routing/pools/import-parse.ts +3 -3
- package/src/console/routing/pools/operations.ts +14 -7
- package/src/console/routing/pools/probe-verdict.ts +8 -3
- package/src/console/routing/pools/relay-deploy.ts +12 -12
- package/src/console/routing/pools/routes.ts +3 -3
- package/src/console/routing/pools/store.ts +21 -1
- package/src/console/settings/contracts.ts +29 -2
- package/src/console/settings/routes.ts +3 -3
- package/src/console/settings/store.ts +20 -0
- package/src/console/share/share-router.ts +112 -44
- package/src/console/share/share-stats.ts +109 -39
- package/src/console/share/share-usage.ts +1 -1
- package/src/console/shared/errors.ts +2 -2
- package/src/console/shared/object.ts +18 -0
- package/src/console/shared/read-through-cache.ts +42 -6
- package/src/console/shared/transaction.ts +14 -0
- package/src/console/system-prompt/contracts.ts +89 -12
- package/src/console/system-prompt/routes.ts +98 -45
- package/src/console/updates/apply-update.ts +244 -0
- package/src/console/updates/routes.ts +256 -98
- package/src/main.ts +11 -5
- package/src/network/http2-bidi.ts +1 -1
- package/src/network/ip-utils.ts +298 -0
- package/src/network/outbound-fetch.ts +76 -5
- package/src/network/pool/agent.ts +7 -6
- package/src/network/pool/byte-accounting.ts +1 -1
- package/src/network/pool/resolver.ts +55 -51
- package/src/network/pool/selector.ts +150 -136
- package/src/network/pool-cooldown-store.ts +71 -16
- package/src/network/pool-health-machine.ts +22 -2
- package/src/network/rate-limit-verdict.ts +68 -0
- package/src/network/retry.ts +1 -1
- package/src/network/ssrf.ts +24 -75
- package/src/observability/event-log.ts +2 -2
- package/src/observability/event-query.ts +286 -214
- package/src/observability/log-sanitize.ts +1 -1
- package/src/observability/metrics.ts +4 -4
- package/src/observability/payload-capture.ts +6 -5
- package/src/observability/payload-store.ts +1 -1
- package/src/observability/runtime-metrics.ts +1 -1
- package/src/observability/telemetry-buffer.ts +12 -3
- package/src/observability/telemetry-reconciliation.ts +3 -3
- package/src/observability/telemetry-status.ts +1 -1
- package/src/observability/token-speed.ts +1 -1
- package/src/persistence/connection-url.ts +1 -1
- package/src/persistence/db-handle.ts +1 -1
- package/src/persistence/db-pg.ts +2 -2
- package/src/persistence/db-sqlite.ts +10 -5
- package/src/persistence/dialect.ts +5 -1
- package/src/persistence/readiness.ts +19 -7
- package/src/persistence/redis.ts +1 -1
- package/src/persistence/schema.ts +177 -45
- package/src/persistence/shared-cache.ts +2 -2
- package/src/persistence/sort-order.ts +1 -1
- package/src/persistence/stores/api-key-store.ts +109 -4
- package/src/persistence/stores/share-store.ts +20 -8
- package/src/persistence/stores/telemetry-store.ts +1 -1
- package/src/persistence/system-prompt-store.ts +63 -18
- package/src/persistence/tenant-preferences.ts +1 -1
- package/src/persistence/ttl-cache.ts +3 -3
- package/src/persistence/usage-flush.ts +7 -2
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/codecs.ts +12 -16
- package/src/protocol/dsml.ts +3 -49
- package/src/protocol/gateway-error.ts +6 -3
- package/src/protocol/image-clamp.ts +2 -2
- package/src/protocol/image-generation.ts +2 -2
- package/src/protocol/reasoning-text.ts +1 -1
- package/src/protocol/reasoning.ts +7 -52
- package/src/protocol/registry.ts +50 -15
- package/src/protocol/request/chat.ts +46 -19
- package/src/protocol/request/codex.ts +1 -1
- package/src/protocol/request/messages.ts +46 -14
- package/src/protocol/request/responses.ts +19 -10
- package/src/protocol/response/chat.ts +27 -12
- package/src/protocol/response/codex.ts +2 -2
- package/src/protocol/response/gemini.ts +1 -1
- package/src/protocol/response/messages.ts +6 -6
- package/src/protocol/response/omp-tool-call-markup.ts +1 -1
- package/src/protocol/response/responses.ts +3 -3
- package/src/protocol/sse-decoder.ts +64 -4
- package/src/protocol/stop-reason.ts +2 -2
- package/src/protocol/think-split.ts +347 -0
- package/src/protocol/translation/attachment-integrity.ts +3 -3
- package/src/protocol/translation/cache-controls.ts +8 -20
- package/src/protocol/translation/capabilities.ts +8 -8
- package/src/protocol/translation/dialect/deepseek.ts +10 -0
- package/src/protocol/translation/dialect/inband.ts +39 -0
- package/src/protocol/translation/dialect/model-thinking.ts +89 -0
- package/src/protocol/translation/dialect/qwen.ts +114 -0
- package/src/protocol/translation/dialect/resolver.ts +83 -0
- package/src/protocol/translation/dialect/rules.ts +93 -0
- package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
- package/src/protocol/translation/dialect/types.ts +58 -0
- package/src/protocol/translation/quirks.ts +18 -4
- package/src/protocol/translation/thinking.ts +12 -252
- package/src/protocol/translation/tool-id.ts +3 -3
- package/src/protocol/translation/tool-repair.ts +1 -1
- package/src/protocol/upstream/post-json.ts +6 -1
- package/src/protocol/upstream/send-claude-messages.ts +3 -3
- package/src/providers/adapters/base-adapter.ts +1 -1
- package/src/providers/adapters/compatible-adapter.ts +44 -5
- package/src/providers/authentication/jwt-validator.ts +0 -5
- package/src/providers/authentication/oauth-client.ts +254 -253
- package/src/providers/authentication/oauth-flow-store.ts +42 -7
- package/src/providers/authentication/oauth-refresh-service.ts +2 -2
- package/src/providers/bundled-discovery-providers.ts +6 -30
- package/src/providers/configured-openai-providers.ts +82 -97
- package/src/providers/configured-provider.ts +1 -1
- package/src/providers/default-registry.ts +23 -35
- package/src/providers/discovery/catalog/catalog-format.ts +13 -2
- package/src/providers/discovery/discovery-types.ts +0 -1
- package/src/providers/discovery/openai-model-discovery.ts +2 -2
- package/src/providers/discovery/probe-phases.ts +50 -15
- package/src/providers/discovery/probing-service.ts +18 -20
- package/src/providers/integrations/agentrouter/agentrouter.ts +14 -5
- package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
- package/src/providers/integrations/anthropic.ts +4 -11
- package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
- package/src/providers/integrations/antigravity/antigravity.ts +15 -26
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +2 -2
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
- package/src/providers/integrations/buddy/buddy-identity.ts +108 -1
- package/src/providers/integrations/buddy/buddy-media.ts +16 -16
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
- package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -16
- package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
- package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -10
- package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
- package/src/providers/integrations/claude/claude-betas.ts +1 -1
- package/src/providers/integrations/claude/claude-cch.ts +148 -148
- package/src/providers/integrations/claude/claude-credentials.ts +1 -1
- package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
- package/src/providers/integrations/claude/claude.ts +3 -10
- package/src/providers/integrations/claude/fallback-credit.ts +3 -7
- package/src/providers/integrations/cline/cline.ts +2 -2
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
- package/src/providers/integrations/codex/codex-device-code.ts +101 -101
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex-oauth.ts +0 -1
- package/src/providers/integrations/codex/codex.ts +14 -15
- package/src/providers/integrations/commandcode/commandcode.ts +76 -116
- package/src/providers/integrations/commandcode/quota.ts +132 -48
- package/src/providers/integrations/cursor/cursor-http2.ts +11 -14
- package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
- package/src/providers/integrations/cursor/cursor-request.ts +0 -87
- package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
- package/src/providers/integrations/cursor/cursor.ts +53 -20
- package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
- package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
- package/src/providers/integrations/factory/factory.ts +13 -13
- package/src/providers/integrations/gemini.ts +11 -11
- package/src/providers/integrations/github/github-discovery.ts +55 -10
- package/src/providers/integrations/github/github-oauth.ts +6 -5
- package/src/providers/integrations/github/github.ts +47 -20
- package/src/providers/integrations/grok/grok-oauth.ts +261 -261
- package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
- package/src/providers/integrations/grok/grok-quota.ts +16 -7
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
- package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
- package/src/providers/integrations/grok/grok.ts +88 -42
- package/src/providers/integrations/inferhub.ts +4 -11
- package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
- package/src/providers/integrations/kimi/kimi.ts +4 -11
- package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
- package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
- package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
- package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
- package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
- package/src/providers/integrations/kiro/kiro-request.ts +8 -11
- package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
- package/src/providers/integrations/kiro/kiro.ts +91 -78
- package/src/providers/integrations/muse/muse-oauth.ts +0 -1
- package/src/providers/integrations/muse/muse-quota.ts +0 -4
- package/src/providers/integrations/muse/muse.ts +6 -9
- package/src/providers/integrations/opencode/fingerprint.ts +7 -5
- package/src/providers/integrations/opencode/provider.ts +17 -24
- package/src/providers/integrations/perplexity.ts +2 -2
- package/src/providers/integrations/qoder/qoder-catalog.ts +132 -17
- package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
- package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
- package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
- package/src/providers/integrations/qoder/qoder-realm.ts +22 -28
- package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +33 -38
- package/src/providers/integrations/qoder/qoder-virtual.ts +14 -2
- package/src/providers/integrations/qoder/qoder.ts +521 -63
- package/src/providers/integrations/shared/connect.ts +0 -54
- package/src/providers/integrations/shared/hash.ts +15 -0
- package/src/providers/integrations/shared/session-cache.ts +136 -0
- package/src/providers/integrations/tokenharbor.ts +2 -2
- package/src/providers/integrations/trae/trae-cache.ts +7 -87
- package/src/providers/integrations/trae/trae-catalog.ts +1 -6
- package/src/providers/integrations/trae/trae-protocol.ts +17 -8
- package/src/providers/integrations/trae/trae-realm.ts +0 -7
- package/src/providers/integrations/trae/trae.ts +59 -19
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
- package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
- package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
- package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
- package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
- package/src/providers/operations/account-error-classification.ts +131 -116
- package/src/providers/operations/account-health-service.ts +31 -39
- package/src/providers/operations/account-reset-service.ts +58 -1
- package/src/providers/operations/cli-platform.ts +31 -5
- package/src/providers/operations/client-version-resolver.ts +4 -6
- package/src/providers/operations/client-versions.ts +10 -7
- package/src/providers/operations/provider-catalog-seeder.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +22 -6
- package/src/providers/operations/provider-credential-service.ts +35 -6
- package/src/providers/operations/upstream-deadline.ts +3 -3
- package/src/providers/provider-contracts.ts +65 -10
- package/src/providers/provider-credential-resolver.ts +1 -1
- package/src/providers/provider-metadata.ts +17 -2
- package/src/providers/provider-model-definition.ts +22 -1
- package/src/providers/provider-probe.ts +4 -2
- package/src/providers/provider-registry.ts +4 -4
- package/src/providers/provider-usage.ts +81 -16
- package/src/providers/quota/cache.ts +29 -0
- package/src/providers/quota/quota-contracts.ts +28 -10
- package/src/providers/quota/refresh.ts +29 -6
- package/src/providers/quota/snapshot.ts +94 -0
- package/src/providers/search/search-provider.ts +6 -8
- package/src/providers/search/search-providers.ts +2 -1
- package/src/security/abuse.ts +8 -8
- package/src/security/admission/reasons.ts +17 -17
- package/src/security/admission/service.ts +2 -2
- package/src/security/admission/ttl.ts +61 -61
- package/src/security/api-key-auth.ts +20 -8
- package/src/security/client-router-fingerprint.ts +4 -4
- package/src/security/credential-codec.ts +3 -3
- package/src/security/csrf.ts +2 -2
- package/src/security/ip-boundary.ts +16 -189
- package/src/security/model-access-rule.ts +1 -1
- package/src/security/outbound-headers.ts +18 -0
- package/src/shared/coerce.ts +19 -0
- package/src/shared/invoke-guard.ts +47 -0
- package/src/transport/contracts.ts +13 -4
- package/src/transport/dispatch/attempt-finalize.ts +5 -4
- package/src/transport/dispatch/attempt-loop.ts +10 -7
- package/src/transport/dispatch/capture-policy.ts +34 -37
- package/src/transport/dispatch/empty-stream.ts +28 -25
- package/src/transport/dispatch/generation.ts +33 -53
- package/src/transport/dispatch/leases.ts +8 -8
- package/src/transport/dispatch/native-shared.ts +127 -0
- package/src/transport/dispatch/proxy-request.ts +10 -8
- package/src/transport/dispatch/responses-compact.ts +35 -56
- package/src/transport/dispatch/retry-policy.ts +4 -67
- package/src/transport/dispatch/stream-bridge.ts +1 -1
- package/src/transport/dispatch/streaming/encoder.ts +18 -72
- package/src/transport/dispatch/streaming/finalize.ts +11 -10
- package/src/transport/dispatch/streaming/stream.ts +7 -3
- package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
- package/src/transport/dispatch/systemone.ts +27 -55
- package/src/transport/dispatch/token-count.ts +49 -46
- package/src/transport/dispatch/websearch.ts +24 -50
- package/src/transport/drain-endpoint.ts +3 -3
- package/src/transport/failure-policy.ts +19 -6
- package/src/transport/guards/auth-guard.ts +6 -11
- package/src/transport/guards/body-policy.ts +7 -7
- package/src/transport/guards/error-lifecycle.ts +18 -3
- package/src/transport/guards/guard-errors.ts +7 -3
- package/src/transport/guards/headroom-guard.ts +2 -2
- package/src/transport/guards/pipeline.ts +7 -7
- package/src/transport/guards/readiness-guard.ts +16 -2
- package/src/transport/guards/request-context.ts +15 -20
- package/src/transport/request/preparer.ts +306 -27
- package/src/transport/request/rtk/compress-request.ts +3 -3
- package/src/transport/request/rtk/rtk-constants.ts +2 -2
- package/src/transport/request/rtk/rtk-filters.ts +8 -8
- package/src/transport/request/state.ts +5 -20
- package/src/transport/request/system-prompt-injection.ts +74 -11
- package/src/transport/request/tenant-preferences.ts +22 -0
- package/src/transport/routing/candidate-index.ts +2 -2
- package/src/transport/routing/round-robin.ts +1 -1
- package/src/transport/routing/route-catalog.ts +113 -19
- package/src/transport/routing/route-model.ts +131 -38
- package/src/transport/routing/router.ts +154 -39
- package/src/transport/shutdown-notice.ts +21 -9
- package/src/transport/surface/adapters.ts +3 -3
- package/src/transport/surface/chat/encode.ts +7 -7
- package/src/transport/surface/chat/parse.ts +7 -7
- package/src/transport/surface/completion.ts +3 -3
- package/src/transport/surface/content-parts.ts +1 -1
- package/src/transport/surface/dialects.ts +2 -2
- package/src/transport/surface/messages/encode.ts +1 -1
- package/src/transport/surface/messages/parse.ts +3 -3
- package/src/transport/surface/responses/adapter.ts +1 -1
- package/src/transport/surface/responses/errors.ts +0 -9
- package/src/transport/vision-handoff-cache.ts +58 -0
- package/src/transport/vision-handoff.ts +376 -0
- package/src/workers/account-identity-sweep.ts +2 -2
- package/src/workers/buddy-growth-tasks.ts +392 -346
- package/src/workers/daily-checkin.ts +102 -49
- package/src/workers/maintenance-lease.ts +2 -35
- package/src/workers/oauth-refresh-sweep.ts +8 -1
- package/src/workers/quota-refresh-sweep.ts +120 -82
- package/src/workers/scheduler.ts +67 -4
- package/src/workers/sweep-runner.ts +24 -5
- package/src/workers/zcode-gift-claim.ts +108 -0
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-JEbYJFxw.js +0 -1
- package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-BAL5npBC.js +0 -1
- package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
- package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
- package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
- package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
- package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
- package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
- package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
- package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
- package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
- package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
- package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
- package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
- package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
- package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
- package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
- package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
- package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
- package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
- package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
- package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BNDZnsUE.js +0 -2
- package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
- package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
- package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
- package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
- package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
- package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
- package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
- package/src/network/response-headers.ts +0 -39
- package/src/protocol/response/dsml.ts +0 -38
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
- /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-Bgd3zANQ.js} +0 -0
- /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-Bgd3zANQ.js.gz} +0 -0
- /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
- /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
2
|
-
import type { CanonicalRequest, ServiceKind } from "../../protocol/canonical-model";
|
|
2
|
+
import type { CanonicalRequest, CanonicalMessage, ServiceKind, ContentPart } from "../../protocol/canonical-model";
|
|
3
3
|
import { toolResultParts } from "../../protocol/canonical-model";
|
|
4
4
|
import type { ApiKeyAdmissionService } from "../../security/admission/service";
|
|
5
|
+
import type { VisionHandoffCall, VisionHandoffResult } from "../vision-handoff";
|
|
6
|
+
import { isImageOrPlaceholderPart, lastImageCarryingUserTurnIndex } from "../vision-handoff";
|
|
5
7
|
import type { CallerIdentity } from "../../security/access-control";
|
|
6
8
|
import type { RouteCandidate as RouteCandidate, InMemoryRouteSnapshotService, RoutePlan } from "../routing/route-model";
|
|
7
9
|
import type { RouteContinuationAffinity } from "../routing/route-model";
|
|
8
10
|
import { resolveAliasTarget, type RoutingEngine } from "../routing/router";
|
|
9
|
-
import { projectForRoute, routeCapabilitiesFor } from "../../protocol/translation/capabilities";
|
|
11
|
+
import { projectForRoute, routeCapabilitiesFor, deriveRequiredCapabilities } from "../../protocol/translation/capabilities";
|
|
10
12
|
import type { RequiredCapability } from "../../protocol/translation/capabilities";
|
|
11
13
|
import { isModelAllowed, type ResolvedApiKey } from "../../security/api-key-auth";
|
|
12
14
|
import { allowsCliToolMappings } from "../../security/cli-client-fingerprint";
|
|
@@ -36,6 +38,128 @@ function requestNeedsToolRepair(request: CanonicalRequest): boolean {
|
|
|
36
38
|
return false;
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The text that replaces an image part after a successful vision handoff.
|
|
43
|
+
* The description is wrapped so the receiving model understands it is an
|
|
44
|
+
* automated description of an image the user attached — not prose the user
|
|
45
|
+
* typed — which is what made it answer as if it had no image at all.
|
|
46
|
+
*/
|
|
47
|
+
function visionDescriptionText(description: string): string {
|
|
48
|
+
return `[The user attached an image. A vision model described it for you: ${description}]`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Replaces every image part (or its `[image]` degradation placeholder) in the
|
|
53
|
+
* request with a single text part carrying the vision-handoff description.
|
|
54
|
+
* Returns `null` when the request has neither image parts nor placeholders.
|
|
55
|
+
* Only user-message parts are replaced; system/tool-result images are left
|
|
56
|
+
* untouched.
|
|
57
|
+
*/
|
|
58
|
+
function replaceImagePartsWithText(
|
|
59
|
+
request: CanonicalRequest,
|
|
60
|
+
description: string,
|
|
61
|
+
): CanonicalRequest | null {
|
|
62
|
+
const activeIndex = lastImageCarryingUserTurnIndex(request);
|
|
63
|
+
const strip = (parts: readonly ContentPart[]): readonly ContentPart[] => {
|
|
64
|
+
const hasImage = parts.some(isImageOrPlaceholderPart);
|
|
65
|
+
if (!hasImage) return parts;
|
|
66
|
+
return parts.flatMap((part): ContentPart[] =>
|
|
67
|
+
isImageOrPlaceholderPart(part)
|
|
68
|
+
? [{ kind: "text", text: visionDescriptionText(description) }]
|
|
69
|
+
: [part],
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
const nextMessages = request.messages.map((message, i) => {
|
|
73
|
+
if (message.role !== "user") return message;
|
|
74
|
+
// The active turn (the one the handoff actually described) gets the
|
|
75
|
+
// description; older image-carrying history turns are dropped — they are
|
|
76
|
+
// not part of the current context the vision model saw, and leaving a
|
|
77
|
+
// stale image on a text-only route would risk an upstream rejection.
|
|
78
|
+
if (i === activeIndex) {
|
|
79
|
+
return { ...message, content: strip(message.content) };
|
|
80
|
+
}
|
|
81
|
+
const hasImage = message.content.some(isImageOrPlaceholderPart);
|
|
82
|
+
if (!hasImage) return message;
|
|
83
|
+
return { ...message, content: message.content.filter((part) => !isImageOrPlaceholderPart(part)) };
|
|
84
|
+
});
|
|
85
|
+
const unchangedMessages = nextMessages.every(
|
|
86
|
+
(message, i) => message === request.messages[i],
|
|
87
|
+
);
|
|
88
|
+
if (unchangedMessages) return null;
|
|
89
|
+
return { ...request, messages: nextMessages };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* When a handoff request is a giant session (opencode CLI sessions routinely
|
|
94
|
+
* carry hundreds of tool-result turns), the text-only request that goes to the
|
|
95
|
+
* main model is still enormous: the handoff only replaced the image parts, so
|
|
96
|
+
* the full history — including 700+ tool transcripts — is forwarded verbatim.
|
|
97
|
+
* Upstreams reject or reset such payloads (502/ECONNRESET), which is the
|
|
98
|
+
* "broken on large sessions" report: a small fresh chat works, a session with
|
|
99
|
+
* a large context dies even though the handoff itself is just a description.
|
|
100
|
+
*
|
|
101
|
+
* The handoff describes *an image the user just attached* — it does not need
|
|
102
|
+
* the rest of the session. For a giant request this pass returns only the
|
|
103
|
+
* system prompt (when present) plus the active image-carrying turn; the whole
|
|
104
|
+
* history is dropped so the payload stays small and upstreams do not reset it.
|
|
105
|
+
* Small requests (< the thresholds) pass through untouched.
|
|
106
|
+
*/
|
|
107
|
+
export function condenseHandoffRequest(request: CanonicalRequest): CanonicalRequest {
|
|
108
|
+
const activeIndex = lastImageCarryingUserTurnIndex(request);
|
|
109
|
+
if (activeIndex < 0) return request;
|
|
110
|
+
|
|
111
|
+
const totalMessages = request.messages.length;
|
|
112
|
+
let totalChars = 0;
|
|
113
|
+
for (const message of request.messages) totalChars += messageCharSize(message);
|
|
114
|
+
if (totalMessages <= HANDOFF_CONDENSE_MESSAGE_THRESHOLD && totalChars <= HANDOFF_CONDENSE_CHARS_THRESHOLD) {
|
|
115
|
+
return request;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const kept: CanonicalMessage[] = [];
|
|
119
|
+
for (let i = 0; i < request.messages.length; i += 1) {
|
|
120
|
+
const message = request.messages[i]!;
|
|
121
|
+
if (message.role === "system") {
|
|
122
|
+
kept.push(message);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (i === activeIndex) {
|
|
126
|
+
kept.push(message);
|
|
127
|
+
// Anything after the active turn stays after it (rare, but preserved).
|
|
128
|
+
kept.push(...request.messages.slice(activeIndex + 1));
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Never return a request that lost the very turn we are answering.
|
|
133
|
+
if (!kept.includes(request.messages[activeIndex]!)) return request;
|
|
134
|
+
return { ...request, messages: kept };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const HANDOFF_CONDENSE_MESSAGE_THRESHOLD = 100;
|
|
138
|
+
const HANDOFF_CONDENSE_CHARS_THRESHOLD = 200_000;
|
|
139
|
+
|
|
140
|
+
function messageCharSize(message: CanonicalMessage): number {
|
|
141
|
+
let size = 0;
|
|
142
|
+
for (const part of message.content) {
|
|
143
|
+
if (part.kind === "text" || part.kind === "refusal") {
|
|
144
|
+
size += part.text.length;
|
|
145
|
+
} else if (part.kind === "toolCall") {
|
|
146
|
+
size += part.name.length;
|
|
147
|
+
try {
|
|
148
|
+
size += JSON.stringify(part.arguments).length;
|
|
149
|
+
} catch {
|
|
150
|
+
size += 64;
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
try {
|
|
154
|
+
size += JSON.stringify((part as { data?: unknown }).data ?? "").length;
|
|
155
|
+
} catch {
|
|
156
|
+
size += 64;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return size;
|
|
161
|
+
}
|
|
162
|
+
|
|
39
163
|
/** Prefix of a provider-continuation tool-call id the gateway itself minted. */
|
|
40
164
|
const CURSOR_CALL_PREFIX = "call_cursor_";
|
|
41
165
|
|
|
@@ -46,7 +170,7 @@ const CURSOR_CALL_PREFIX = "call_cursor_";
|
|
|
46
170
|
* Any other id belongs to a conversation the client owns; it is not a
|
|
47
171
|
* continuation and must not influence routing.
|
|
48
172
|
*/
|
|
49
|
-
|
|
173
|
+
function continuationCallIds(request: CanonicalRequest): readonly string[] {
|
|
50
174
|
const ids: string[] = [];
|
|
51
175
|
for (const message of request.messages) {
|
|
52
176
|
if (message.role !== "tool" && message.role !== "user") continue;
|
|
@@ -70,7 +194,7 @@ export function continuationCallIds(request: CanonicalRequest): readonly string[
|
|
|
70
194
|
* Returns `undefined` when the request carries no continuation ids — the
|
|
71
195
|
* ordinary routing path.
|
|
72
196
|
*/
|
|
73
|
-
|
|
197
|
+
function resolveContinuationAffinity(
|
|
74
198
|
request: CanonicalRequest,
|
|
75
199
|
authorization: ResolvedApiKey,
|
|
76
200
|
): RouteContinuationAffinity | undefined {
|
|
@@ -101,7 +225,7 @@ export function resolveContinuationAffinity(
|
|
|
101
225
|
throw new GatewayError(
|
|
102
226
|
"invalid_request",
|
|
103
227
|
400,
|
|
104
|
-
"These tool results belong to different Cursor accounts; resend them one account at a time.",
|
|
228
|
+
"These tool results belong to different Cursor accounts (request {requestId}); resend them one account at a time. The session went cold — reconnect or resend 🔌",
|
|
105
229
|
{ callIds: [...ids] },
|
|
106
230
|
);
|
|
107
231
|
}
|
|
@@ -122,6 +246,46 @@ export function resolveContinuationAffinity(
|
|
|
122
246
|
return undefined;
|
|
123
247
|
}
|
|
124
248
|
|
|
249
|
+
/**
|
|
250
|
+
* True when every continuation call id this request carries is answered by a
|
|
251
|
+
* matching `toolCall` block in the same conversation — i.e. the request is a
|
|
252
|
+
* complete replay, not a tail continuation still owed to a live Run.
|
|
253
|
+
*/
|
|
254
|
+
function isCompleteContinuationReplay(request: CanonicalRequest, ids: readonly string[]): boolean {
|
|
255
|
+
const calledIds = new Set<string>();
|
|
256
|
+
for (const message of request.messages) {
|
|
257
|
+
for (const part of message.content) {
|
|
258
|
+
if (part.kind === "toolCall") calledIds.add(part.call_id);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return ids.every((id) => calledIds.has(id));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Resolves a request's pending tool results to the one account that owns them,
|
|
266
|
+
* with rebaseability computed from the conversation shape.
|
|
267
|
+
*
|
|
268
|
+
* A hard pin is only safe when the request is still owed to a live Run. But an
|
|
269
|
+
* account that has since become unroutable (quota exhausted, cooling) must not
|
|
270
|
+
* turn a complete replay into a hard 400: the whole recorded conversation is
|
|
271
|
+
* present, so the router may fall through to normal routing and the Cursor
|
|
272
|
+
* adapter rebases it into a fresh Run on whichever account serves the model.
|
|
273
|
+
* That is what makes switching provider/model mid-conversation work.
|
|
274
|
+
*/
|
|
275
|
+
function resolveContinuationAffinityWithRebase(
|
|
276
|
+
request: CanonicalRequest,
|
|
277
|
+
authorization: ResolvedApiKey,
|
|
278
|
+
): RouteContinuationAffinity | undefined {
|
|
279
|
+
const affinity = resolveContinuationAffinity(request, authorization);
|
|
280
|
+
if (affinity === undefined) return undefined;
|
|
281
|
+
const ids = continuationCallIds(request);
|
|
282
|
+
if (affinity.rebaseable) return affinity;
|
|
283
|
+
return {
|
|
284
|
+
...affinity,
|
|
285
|
+
rebaseable: isCompleteContinuationReplay(request, ids),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
125
289
|
/** Looks up one pending call across every provider that records continuations. */
|
|
126
290
|
function lookupPendingCall(
|
|
127
291
|
callId: string,
|
|
@@ -170,15 +334,29 @@ export interface PreparedNativeRequest {
|
|
|
170
334
|
readonly admissionService: ApiKeyAdmissionService;
|
|
171
335
|
}
|
|
172
336
|
|
|
173
|
-
|
|
337
|
+
interface ProxyRequestPreparerDeps {
|
|
174
338
|
readonly snapshotService: InMemoryRouteSnapshotService;
|
|
175
339
|
readonly routingEngine: RoutingEngine;
|
|
176
340
|
readonly admissionService: ApiKeyAdmissionService;
|
|
177
341
|
}
|
|
178
342
|
|
|
179
343
|
export class ProxyRequestPreparer {
|
|
344
|
+
/**
|
|
345
|
+
* Vision-handoff runner, injected by boot once the request state store and
|
|
346
|
+
* the full dispatch dependency surface exist. Absent (or when the feature is
|
|
347
|
+
* not configured) means the legacy `[image]` placeholder path.
|
|
348
|
+
*/
|
|
349
|
+
#runVisionHandoff: ((input: VisionHandoffCall) => Promise<readonly VisionHandoffResult[]>) | undefined;
|
|
350
|
+
|
|
180
351
|
constructor(private readonly deps: ProxyRequestPreparerDeps) {}
|
|
181
352
|
|
|
353
|
+
/** Injects the vision-handoff runner (called once from boot wiring). */
|
|
354
|
+
bindVisionHandoff(
|
|
355
|
+
runner: (input: VisionHandoffCall) => Promise<readonly VisionHandoffResult[]>,
|
|
356
|
+
): void {
|
|
357
|
+
this.#runVisionHandoff = runner;
|
|
358
|
+
}
|
|
359
|
+
|
|
182
360
|
async prepare(input: {
|
|
183
361
|
readonly canonicalRequest: CanonicalRequest;
|
|
184
362
|
readonly authorization: ResolvedApiKey;
|
|
@@ -186,12 +364,14 @@ export class ProxyRequestPreparer {
|
|
|
186
364
|
readonly signal?: AbortSignal;
|
|
187
365
|
/** Inbound `User-Agent`; gates remote CLI remaps so short slots stay tool-local. */
|
|
188
366
|
readonly clientUserAgent?: string;
|
|
367
|
+
/** Suppresses nested vision handoff; internal sub-requests set this. */
|
|
368
|
+
readonly disableVisionHandoff?: boolean;
|
|
189
369
|
}): Promise<PreparedProxyRequest> {
|
|
190
370
|
const { canonicalRequest: initialRequest, authorization, signal } = input;
|
|
191
371
|
if (signal?.aborted)
|
|
192
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
372
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
193
373
|
if (!initialRequest.model || initialRequest.model.trim().length === 0) {
|
|
194
|
-
throw new GatewayError("invalid_request", 400, "Request requires a non-empty model identifier", {
|
|
374
|
+
throw new GatewayError("invalid_request", 400, "Request requires a non-empty model identifier (request {requestId}). Check the shape of what you sent — then resend 🪞", {
|
|
195
375
|
field: "model",
|
|
196
376
|
});
|
|
197
377
|
}
|
|
@@ -242,13 +422,13 @@ export class ProxyRequestPreparer {
|
|
|
242
422
|
!allowedForKey
|
|
243
423
|
)
|
|
244
424
|
throw new GatewayError(
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
`This key may only use models starting with '${authorization.modelPrefix}'. Ask for one inside that prefix, or have an operator widen the key
|
|
425
|
+
"model_not_allowed",
|
|
426
|
+
403,
|
|
427
|
+
`This key may only use models starting with '${authorization.modelPrefix}' (request {requestId}). Ask for one inside that prefix, or have an operator widen the key. The door's locked for this one — check the allowlist 🔒`,
|
|
248
428
|
{ model: request.model, required_prefix: authorization.modelPrefix },
|
|
249
429
|
);
|
|
250
430
|
if (!allowedForKey) {
|
|
251
|
-
throw new GatewayError("
|
|
431
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
252
432
|
model: request.model,
|
|
253
433
|
});
|
|
254
434
|
}
|
|
@@ -266,7 +446,7 @@ export class ProxyRequestPreparer {
|
|
|
266
446
|
// degradation could narrow the candidate set past it. This is also the
|
|
267
447
|
// point where a mismatched or expired call id becomes a 400 the caller can
|
|
268
448
|
// act on, instead of a dispatch that silently starts a new upstream turn.
|
|
269
|
-
const continuationAffinity =
|
|
449
|
+
const continuationAffinity = resolveContinuationAffinityWithRebase(requestWithAttachments, authorization);
|
|
270
450
|
// Capability-aware routing: derive requirements BEFORE planning so the
|
|
271
451
|
// router filters candidates against snapshot profiles. When nothing
|
|
272
452
|
// supports the full request, degrade (same greedy order) and re-plan
|
|
@@ -296,9 +476,108 @@ export class ProxyRequestPreparer {
|
|
|
296
476
|
continuationAffinity,
|
|
297
477
|
);
|
|
298
478
|
} catch (error) {
|
|
299
|
-
|
|
479
|
+
// `plan()` collapses "no candidate supports this capability" into
|
|
480
|
+
// `accounts_unavailable` (503), not `capability_unsupported` — a
|
|
481
|
+
// candidate that exists but cannot carry the required capability is
|
|
482
|
+
// evaluated ineligible, and an all-ineligible set reads as "no usable
|
|
483
|
+
// account". Both mean "try the next, more degraded variant" here.
|
|
484
|
+
// `accounts_unavailable` is swallowed on the pristine variant (so a
|
|
485
|
+
// genuinely unhealthy pool falls through to a degraded plan) and on any
|
|
486
|
+
// variant that still carries requirements (a capability the route
|
|
487
|
+
// lacks, not a dead pool). Only a fully degraded variant — nothing left
|
|
488
|
+
// to drop — that still refuses means the pool itself is unusable, and
|
|
489
|
+
// that 503 must surface.
|
|
490
|
+
if (
|
|
491
|
+
error instanceof GatewayError &&
|
|
492
|
+
(error.code === "capability_unsupported" ||
|
|
493
|
+
(error.code === "accounts_unavailable" &&
|
|
494
|
+
(variant.degraded.length === 0 || variant.required.length > 0)))
|
|
495
|
+
) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
300
498
|
throw error;
|
|
301
499
|
}
|
|
500
|
+
// Vision handoff: when the winning route cannot carry images (the image
|
|
501
|
+
// capability was degraded), try to describe the images with a configured
|
|
502
|
+
// vision model and plan the resulting text-only request instead. This
|
|
503
|
+
// only runs when the selected model is NOT image-capable — a vision route
|
|
504
|
+
// that plans without degradation keeps the real image. The handoff is
|
|
505
|
+
// best-effort; any failure keeps the legacy `[image]` placeholder plan.
|
|
506
|
+
if (
|
|
507
|
+
variant.degraded.includes("image") &&
|
|
508
|
+
input.disableVisionHandoff !== true &&
|
|
509
|
+
this.#runVisionHandoff !== undefined
|
|
510
|
+
) {
|
|
511
|
+
try {
|
|
512
|
+
const handedOff = await this.#runVisionHandoff({
|
|
513
|
+
canonicalRequest: requestWithAttachments,
|
|
514
|
+
authorization,
|
|
515
|
+
deadlineMs: input.deadlineMs,
|
|
516
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
517
|
+
...(input.clientUserAgent === undefined ? {} : { clientUserAgent: input.clientUserAgent }),
|
|
518
|
+
});
|
|
519
|
+
if (handedOff.length > 0) {
|
|
520
|
+
const description = handedOff.map((result) => result.description).join("\n");
|
|
521
|
+
// Replace on the already-degraded variant so earlier degradations
|
|
522
|
+
// (tools, reasoning, …) survive alongside the handoff — a chain
|
|
523
|
+
// variant carries them, and rewriting from the pristine request
|
|
524
|
+
// would reintroduce capabilities the winning route lacks.
|
|
525
|
+
// Condense first (while the active turn still carries its image, so
|
|
526
|
+
// it can be located): a session with hundreds of tool-result turns
|
|
527
|
+
// forwards a multi-megabyte body that upstreams reset or reject
|
|
528
|
+
// (502 / ECONNRESET) — the "broken on large sessions" report. The
|
|
529
|
+
// handoff only needs to describe the attached image, so the giant
|
|
530
|
+
// history is dropped entirely, leaving system + active turn.
|
|
531
|
+
const condensedBase = condenseHandoffRequest(variant.request);
|
|
532
|
+
const replaced = replaceImagePartsWithText(condensedBase, description);
|
|
533
|
+
if (replaced !== null) {
|
|
534
|
+
const requiredAfter = deriveRequiredCapabilities(replaced);
|
|
535
|
+
try {
|
|
536
|
+
const handoffPlan = await this.deps.routingEngine.plan(
|
|
537
|
+
request.model,
|
|
538
|
+
snapshot,
|
|
539
|
+
authorization.tenantId,
|
|
540
|
+
requiredAfter,
|
|
541
|
+
allowCliMappings,
|
|
542
|
+
authorization.cliMappingOwnerId ?? authorization.id,
|
|
543
|
+
continuationAffinity,
|
|
544
|
+
);
|
|
545
|
+
plan = handoffPlan;
|
|
546
|
+
variantRequest = replaced;
|
|
547
|
+
degraded = [
|
|
548
|
+
...degraded,
|
|
549
|
+
...variant.degraded.filter(
|
|
550
|
+
(cap) => cap !== "image" && !degraded.includes(cap),
|
|
551
|
+
),
|
|
552
|
+
];
|
|
553
|
+
break;
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (
|
|
556
|
+
error instanceof GatewayError &&
|
|
557
|
+
(error.code === "capability_unsupported" ||
|
|
558
|
+
error.code === "accounts_unavailable")
|
|
559
|
+
) {
|
|
560
|
+
// Re-plan failed without the image requirement; keep the
|
|
561
|
+
// degraded `[image]` plan captured above. `accounts_unavailable`
|
|
562
|
+
// is also swallowed: the text-only re-plan can be refused for
|
|
563
|
+
// the same health/pool reasons the original plan was, and the
|
|
564
|
+
// parent request must never fail because a best-effort
|
|
565
|
+
// handoff could not be re-planned.
|
|
566
|
+
} else {
|
|
567
|
+
throw error;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch (error) {
|
|
573
|
+
// Best-effort only: a handoff failure never fails the parent request.
|
|
574
|
+
log.warn("[vision-handoff] handoff failed; falling back to [image] placeholder", {
|
|
575
|
+
model: request.model,
|
|
576
|
+
tenantId: authorization.tenantId,
|
|
577
|
+
error: error instanceof Error ? error.message : String(error),
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
302
581
|
variantRequest = variant.request;
|
|
303
582
|
degraded = [...degraded, ...variant.degraded.filter((cap) => !degraded.includes(cap))];
|
|
304
583
|
break;
|
|
@@ -307,7 +586,7 @@ export class ProxyRequestPreparer {
|
|
|
307
586
|
throw new GatewayError(
|
|
308
587
|
"capability_unsupported",
|
|
309
588
|
400,
|
|
310
|
-
"no eligible route supports this request's capabilities",
|
|
589
|
+
"no eligible route supports this request's capabilities (request {requestId}). Drop the unsupported capability, or route to a model that accepts it. Know your tools — this one just isn't in the box 🧰",
|
|
311
590
|
{ model: request.model },
|
|
312
591
|
);
|
|
313
592
|
const policyCandidates = plan.candidates.filter((candidate) =>
|
|
@@ -319,7 +598,7 @@ export class ProxyRequestPreparer {
|
|
|
319
598
|
),
|
|
320
599
|
);
|
|
321
600
|
if (policyCandidates.length === 0)
|
|
322
|
-
throw new GatewayError("
|
|
601
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
323
602
|
model: request.model,
|
|
324
603
|
});
|
|
325
604
|
if (policyCandidates.length !== plan.candidates.length)
|
|
@@ -341,7 +620,7 @@ export class ProxyRequestPreparer {
|
|
|
341
620
|
throw new GatewayError(
|
|
342
621
|
"capability_unsupported",
|
|
343
622
|
400,
|
|
344
|
-
`this model is not served on a chat wire — it is a '${kind}' service model${route === undefined ? "" : `; call it at POST ${route}`}
|
|
623
|
+
`this model is not served on a chat wire — it is a '${kind}' service model${route === undefined ? "" : `; call it at POST ${route}`} (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
|
|
345
624
|
{ model: request.model, service_kind: kind, ...(route === undefined ? {} : { route }) },
|
|
346
625
|
);
|
|
347
626
|
}
|
|
@@ -366,7 +645,7 @@ export class ProxyRequestPreparer {
|
|
|
366
645
|
}
|
|
367
646
|
}
|
|
368
647
|
if (signal?.aborted)
|
|
369
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
648
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
370
649
|
const eligible = plan.candidates;
|
|
371
650
|
// The buddy gateway rejects partial tool rounds outright (`11148`) where
|
|
372
651
|
// the generic synthesis policy would insert a placeholder result, so a
|
|
@@ -401,7 +680,7 @@ export class ProxyRequestPreparer {
|
|
|
401
680
|
effectiveRequest = sanitizeRequestToolIds(effectiveRequest);
|
|
402
681
|
}
|
|
403
682
|
if (signal?.aborted)
|
|
404
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
683
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
405
684
|
// Project against the chosen candidate, not the intersection of every
|
|
406
685
|
// candidate in the plan. Still required for generation-control stripping
|
|
407
686
|
// even on the no-tools path — keep it, but it is cheap vs message clones.
|
|
@@ -448,7 +727,7 @@ export class ProxyRequestPreparer {
|
|
|
448
727
|
readonly emptyMessage: string;
|
|
449
728
|
}): Promise<PreparedNativeRequest> {
|
|
450
729
|
if (input.signal?.aborted)
|
|
451
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
730
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
452
731
|
const snapshot = await this.deps.snapshotService.getSnapshot();
|
|
453
732
|
const allowCliMappings = allowsCliToolMappings(input.authorization.scopes, {
|
|
454
733
|
...(input.clientUserAgent === undefined ? {} : { userAgent: input.clientUserAgent }),
|
|
@@ -473,13 +752,13 @@ export class ProxyRequestPreparer {
|
|
|
473
752
|
!allowedForKey
|
|
474
753
|
)
|
|
475
754
|
throw new GatewayError(
|
|
476
|
-
"
|
|
477
|
-
|
|
478
|
-
`This key may only use models starting with '${input.authorization.modelPrefix}'. Ask for one inside that prefix, or have an operator widen the key
|
|
755
|
+
"model_not_allowed",
|
|
756
|
+
403,
|
|
757
|
+
`This key may only use models starting with '${input.authorization.modelPrefix}' (request {requestId}). Ask for one inside that prefix, or have an operator widen the key. The door's locked for this one — check the allowlist 🔒`,
|
|
479
758
|
{ model: input.model, required_prefix: input.authorization.modelPrefix },
|
|
480
759
|
);
|
|
481
760
|
if (!allowedForKey)
|
|
482
|
-
throw new GatewayError("
|
|
761
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
483
762
|
model: input.model,
|
|
484
763
|
});
|
|
485
764
|
const plan = await this.deps.routingEngine.plan(
|
|
@@ -491,7 +770,7 @@ export class ProxyRequestPreparer {
|
|
|
491
770
|
input.authorization.cliMappingOwnerId ?? input.authorization.id,
|
|
492
771
|
);
|
|
493
772
|
if (input.signal?.aborted)
|
|
494
|
-
throw new GatewayError("transport_closed", 499, "request was cancelled");
|
|
773
|
+
throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
|
|
495
774
|
const policyCandidates = plan.candidates.filter((candidate) =>
|
|
496
775
|
isModelAllowed(
|
|
497
776
|
input.authorization.snapshot,
|
|
@@ -501,12 +780,12 @@ export class ProxyRequestPreparer {
|
|
|
501
780
|
),
|
|
502
781
|
);
|
|
503
782
|
if (policyCandidates.length === 0)
|
|
504
|
-
throw new GatewayError("
|
|
783
|
+
throw new GatewayError("model_not_allowed", 403, "model is not allowed for this API key (request {requestId}). The door's locked for this one — check the allowlist 🔒", {
|
|
505
784
|
model: input.model,
|
|
506
785
|
});
|
|
507
786
|
const candidates = policyCandidates.filter(input.eligible);
|
|
508
787
|
if (candidates.length === 0)
|
|
509
|
-
throw new GatewayError("capability_unsupported", 400, input.emptyMessage
|
|
788
|
+
throw new GatewayError("capability_unsupported", 400, `${input.emptyMessage} (request {requestId}). Know your tools — this one just isn't in the box 🧰`, {
|
|
510
789
|
model: input.model,
|
|
511
790
|
});
|
|
512
791
|
return {
|
|
@@ -26,7 +26,7 @@ import { autoDetectFilter, dedupLogFilter, safeApplyFilter, smartTruncateFilter
|
|
|
26
26
|
import { RTK_RAW_CAP, rtkProfile, type RtkLevel } from "./rtk-constants";
|
|
27
27
|
import { ponyTailPrompt, type PonyTailLevel } from "./ponytail-prompt";
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
interface CompressionOptions {
|
|
30
30
|
/** Compact bulky tool-result text in the message history. */
|
|
31
31
|
readonly rtkPrune?: boolean;
|
|
32
32
|
/** RTK strength; `full` when omitted. See {@link RtkLevel}. */
|
|
@@ -35,7 +35,7 @@ export interface CompressionOptions {
|
|
|
35
35
|
readonly ponyTail?: PonyTailLevel | null | undefined;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
interface CompressionStats {
|
|
39
39
|
/** Tool-result text parts actually shortened. */
|
|
40
40
|
readonly prunedParts: number;
|
|
41
41
|
/** Characters removed by RTK (>= 0). */
|
|
@@ -45,7 +45,7 @@ export interface CompressionStats {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/** Compresses one text blob with RTK at a strength level, or returns it unchanged. */
|
|
48
|
-
|
|
48
|
+
function compressToolText(text: string, level?: RtkLevel | null): string {
|
|
49
49
|
const profile = rtkProfile(level);
|
|
50
50
|
if (text.length < profile.minSize || text.length > RTK_RAW_CAP) return text;
|
|
51
51
|
const filter = autoDetectFilter(text, { genericFallback: profile.genericFallback });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// not env knobs: these are safety margins, not deployment settings.
|
|
3
3
|
|
|
4
4
|
/** Skip blobs smaller than this — compressing tiny tool results only adds noise. */
|
|
5
|
-
|
|
5
|
+
const RTK_MIN_COMPRESS_SIZE = 500;
|
|
6
6
|
/** Never touch a blob larger than this; a multi-megabyte dump is not a tool result to prune. */
|
|
7
7
|
export const RTK_RAW_CAP = 10 * 1024 * 1024;
|
|
8
8
|
/** How many leading characters autodetect inspects when classifying a blob. */
|
|
@@ -22,7 +22,7 @@ export const RTK_DETECT_WINDOW = 1024;
|
|
|
22
22
|
export const RTK_LEVELS = ["lite", "full", "ultra"] as const;
|
|
23
23
|
export type RtkLevel = (typeof RTK_LEVELS)[number];
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
interface RtkProfile {
|
|
26
26
|
/** Blobs smaller than this are left alone at this level. */
|
|
27
27
|
readonly minSize: number;
|
|
28
28
|
/** Whether the generic fallbacks (dedup-log, smart-truncate) may classify a blob. */
|
|
@@ -29,10 +29,10 @@ import {
|
|
|
29
29
|
TREE_MAX_LINES,
|
|
30
30
|
} from "./rtk-constants";
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
type RtkFilter = ((input: string) => string) & { filterName?: string };
|
|
33
33
|
|
|
34
34
|
/** `git diff` → per-file header, per-hunk truncation, +/- counts. */
|
|
35
|
-
|
|
35
|
+
const gitDiffFilter: RtkFilter = (diff) => {
|
|
36
36
|
const result: string[] = [];
|
|
37
37
|
let currentFile = "";
|
|
38
38
|
let added = 0;
|
|
@@ -83,7 +83,7 @@ export const gitDiffFilter: RtkFilter = (diff) => {
|
|
|
83
83
|
gitDiffFilter.filterName = "git-diff";
|
|
84
84
|
|
|
85
85
|
/** `git log` → one line per commit, capped. */
|
|
86
|
-
|
|
86
|
+
const gitLogFilter: RtkFilter = (input) => {
|
|
87
87
|
const lines = input.split("\n");
|
|
88
88
|
const out: string[] = [];
|
|
89
89
|
for (const line of lines) {
|
|
@@ -99,7 +99,7 @@ export const gitLogFilter: RtkFilter = (input) => {
|
|
|
99
99
|
gitLogFilter.filterName = "git-log";
|
|
100
100
|
|
|
101
101
|
/** `git status` → branch line, changed-file counts, file list capped. */
|
|
102
|
-
|
|
102
|
+
const gitStatusFilter: RtkFilter = (input) => {
|
|
103
103
|
const lines = input.split("\n");
|
|
104
104
|
const out: string[] = [];
|
|
105
105
|
let changed = 0;
|
|
@@ -119,7 +119,7 @@ export const gitStatusFilter: RtkFilter = (input) => {
|
|
|
119
119
|
gitStatusFilter.filterName = "git-status";
|
|
120
120
|
|
|
121
121
|
/** `grep`/`rg` dump (`file:line:content`) → grouped by file, capped per file. */
|
|
122
|
-
|
|
122
|
+
const grepFilter: RtkFilter = (input) => {
|
|
123
123
|
const byFile = new Map<string, Array<[string, string]>>();
|
|
124
124
|
let total = 0;
|
|
125
125
|
for (const line of input.split("\n")) {
|
|
@@ -151,7 +151,7 @@ export const grepFilter: RtkFilter = (input) => {
|
|
|
151
151
|
grepFilter.filterName = "grep";
|
|
152
152
|
|
|
153
153
|
/** `find` dump (path-per-line) → grouped by parent directory, capped. */
|
|
154
|
-
|
|
154
|
+
const findFilter: RtkFilter = (input) => {
|
|
155
155
|
const byDir = new Map<string, string[]>();
|
|
156
156
|
for (const line of input.split("\n")) {
|
|
157
157
|
const path = line.trim();
|
|
@@ -177,7 +177,7 @@ export const findFilter: RtkFilter = (input) => {
|
|
|
177
177
|
findFilter.filterName = "find";
|
|
178
178
|
|
|
179
179
|
/** `ls -la` dump → directories and files with human sizes, noise dirs dropped. */
|
|
180
|
-
|
|
180
|
+
const lsFilter: RtkFilter = (input) => {
|
|
181
181
|
const dirs: string[] = [];
|
|
182
182
|
const files: Array<[string, number]> = [];
|
|
183
183
|
const byExt = new Map<string, number>();
|
|
@@ -219,7 +219,7 @@ export const lsFilter: RtkFilter = (input) => {
|
|
|
219
219
|
lsFilter.filterName = "ls";
|
|
220
220
|
|
|
221
221
|
/** `tree` dump → collapsed, noise dirs dropped, capped. */
|
|
222
|
-
|
|
222
|
+
const treeFilter: RtkFilter = (input) => {
|
|
223
223
|
const out: string[] = [];
|
|
224
224
|
for (const line of input.split("\n")) {
|
|
225
225
|
const trimmed = line.replace(/[│├└─]/g, " ").trim();
|