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,11 +1,15 @@
|
|
|
1
1
|
import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
|
|
2
2
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
3
|
-
import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
|
|
4
3
|
import { metrics } from "../../observability/metrics";
|
|
5
|
-
import {
|
|
6
|
-
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
7
|
-
import { repriceUsage, usageFromProvider } from "../../providers/provider-usage";
|
|
4
|
+
import { estimatedUsage, nativeCompletionBase, completeAttempt } from "./attempt-finalize";
|
|
8
5
|
import { runAttemptLoop } from "./attempt-loop";
|
|
6
|
+
import {
|
|
7
|
+
networkFetchContext,
|
|
8
|
+
requireRecordBody,
|
|
9
|
+
resolveAttemptCredential,
|
|
10
|
+
stripThinkingSuffix,
|
|
11
|
+
usageFromUpstreamClone,
|
|
12
|
+
} from "./native-shared";
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* The native System One route (`POST /v1/systemone`) and the dependency surface
|
|
@@ -20,7 +24,7 @@ import { runAttemptLoop } from "./attempt-loop";
|
|
|
20
24
|
*/
|
|
21
25
|
import type { GatewayHandlerDeps } from "./handler-deps";
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
type SystemoneHandlerDeps = GatewayHandlerDeps;
|
|
24
28
|
|
|
25
29
|
/** Adapter that can serve a System One decision request. */
|
|
26
30
|
type SystemoneAdapter = ProviderAdapter & {
|
|
@@ -31,36 +35,34 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
31
35
|
return async ({ request }: { request: Request }): Promise<Response> => {
|
|
32
36
|
const state = deps.stateStore.require(request);
|
|
33
37
|
const authorization = state.authorization;
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
const decision = requireRecordBody(
|
|
39
|
+
state,
|
|
40
|
+
"systemone request body must be a JSON object (request {requestId}). Check the shape of what you sent — then resend 🪞",
|
|
41
|
+
);
|
|
38
42
|
if (typeof decision.model !== "string" || decision.model.trim().length === 0)
|
|
39
|
-
throw new GatewayError("invalid_request", 400, "model is required");
|
|
43
|
+
throw new GatewayError("invalid_request", 400, "model is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
40
44
|
// A thinking suffix (`model(high)`) is stripped for the same reason the
|
|
41
45
|
// canonical preparer strips it: the route matches the model name against the
|
|
42
46
|
// allowlist and the route plan, and `(high)` is not part of any registered
|
|
43
47
|
// id. The level itself is dropped — a decision request carries no reasoning
|
|
44
48
|
// effort to shape, and the body is passed upstream verbatim.
|
|
45
|
-
const {
|
|
46
|
-
const decisionBody: Record<string, unknown> =
|
|
47
|
-
bareModel === decision.model ? decision : { ...decision, model: bareModel };
|
|
49
|
+
const { bareModel, body: decisionBody } = stripThinkingSuffix(decision.model, decision);
|
|
48
50
|
// Trust boundary only: the decision model's own question shapes are
|
|
49
51
|
// upstream's to validate. `state` and a `questions` object are what make a
|
|
50
52
|
// System One request a System One request at all.
|
|
51
53
|
if (decision.state === undefined || decision.state === null)
|
|
52
|
-
throw new GatewayError("invalid_request", 400, "state is required");
|
|
54
|
+
throw new GatewayError("invalid_request", 400, "state is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
53
55
|
if (
|
|
54
56
|
decision.questions === undefined ||
|
|
55
57
|
decision.questions === null ||
|
|
56
58
|
typeof decision.questions !== "object" ||
|
|
57
59
|
Array.isArray(decision.questions)
|
|
58
60
|
)
|
|
59
|
-
throw new GatewayError("invalid_request", 400, "questions must be an object");
|
|
61
|
+
throw new GatewayError("invalid_request", 400, "questions must be an object (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
60
62
|
const prepared = await deps.proxyPreparer.prepareNativeService({
|
|
61
63
|
model: bareModel,
|
|
62
64
|
serviceKind: "systemone",
|
|
63
|
-
authorization
|
|
65
|
+
authorization: authorization!,
|
|
64
66
|
...(state.abortController.signal ? { signal: state.abortController.signal } : {}),
|
|
65
67
|
...(state.clientUserAgent === undefined ? {} : { clientUserAgent: state.clientUserAgent }),
|
|
66
68
|
});
|
|
@@ -80,30 +82,18 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
80
82
|
// Lenient pool semantics: a missing slot tolerates direct egress.
|
|
81
83
|
strictPoolSelection: false,
|
|
82
84
|
exhaustedError: new GatewayError(
|
|
83
|
-
"
|
|
85
|
+
"route_unavailable",
|
|
84
86
|
503,
|
|
85
|
-
"no eligible System One route",
|
|
87
|
+
"no eligible System One route (request {requestId}). The bouncer stepped out — come back shortly 🚪",
|
|
86
88
|
),
|
|
87
89
|
prepare: async (candidate) => {
|
|
88
|
-
const credential = candidate
|
|
89
|
-
? await resolveCredentialForAccount(
|
|
90
|
-
deps.db,
|
|
91
|
-
candidate.provider_id,
|
|
92
|
-
candidate.provider_account_id,
|
|
93
|
-
deps.oauthRefreshService && deps.resolveOAuthRefresher
|
|
94
|
-
? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
|
|
95
|
-
: undefined,
|
|
96
|
-
)
|
|
97
|
-
: {
|
|
98
|
-
provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
|
|
99
|
-
credential_kind: "none" as const,
|
|
100
|
-
};
|
|
90
|
+
const credential = await resolveAttemptCredential(deps, candidate);
|
|
101
91
|
const adapter = (await deps.resolveProviderAdapter?.(candidate.provider_id)) as SystemoneAdapter | undefined;
|
|
102
92
|
if (!adapter || typeof adapter.systemone !== "function")
|
|
103
93
|
throw new GatewayError(
|
|
104
94
|
"capability_unsupported",
|
|
105
95
|
400,
|
|
106
|
-
"System One transport unavailable for this provider",
|
|
96
|
+
"System One transport unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰",
|
|
107
97
|
);
|
|
108
98
|
return { credential, adapter };
|
|
109
99
|
},
|
|
@@ -131,14 +121,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
131
121
|
deadline: state.deadlineMs,
|
|
132
122
|
abort_signal: state.abortController.signal,
|
|
133
123
|
...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
|
|
134
|
-
...(deps.
|
|
135
|
-
? {
|
|
136
|
-
outbound_fetch: deps.networkBindingFactory.fetch(
|
|
137
|
-
leases.proxySlot?.poolId,
|
|
138
|
-
authorization.tenantId,
|
|
139
|
-
),
|
|
140
|
-
}
|
|
141
|
-
: {}),
|
|
124
|
+
...networkFetchContext(deps, leases.proxySlot?.poolId, authorization!.tenantId),
|
|
142
125
|
});
|
|
143
126
|
if (!response.ok) {
|
|
144
127
|
// Surface the upstream decision error verbatim: it is the operator's
|
|
@@ -148,26 +131,15 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
|
|
|
148
131
|
throw new GatewayError(
|
|
149
132
|
response.status >= 500 ? "platform_unavailable" : "invalid_request",
|
|
150
133
|
response.status,
|
|
151
|
-
`System One upstream error: ${detail.slice(0, 240)}`,
|
|
134
|
+
`System One upstream error (request {requestId}): ${detail.slice(0, 240)}`,
|
|
152
135
|
);
|
|
153
136
|
}
|
|
154
137
|
// Prefer the usage the decision endpoint reported; fall back to the
|
|
155
138
|
// fixed native estimate. `response.clone()` leaves the body readable
|
|
156
139
|
// for the client.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
reportedUsage = usageFromProvider(
|
|
161
|
-
typeof parsed === "object" && parsed !== null
|
|
162
|
-
? (parsed as Record<string, unknown>)["usage"]
|
|
163
|
-
: undefined,
|
|
164
|
-
);
|
|
165
|
-
} catch {
|
|
166
|
-
reportedUsage = undefined;
|
|
167
|
-
}
|
|
168
|
-
const usage = repriceUsage(
|
|
169
|
-
reportedUsage ??
|
|
170
|
-
estimatedUsage(prepared.estimatedInputTokens, prepared.estimatedOutputTokens),
|
|
140
|
+
const usage = await usageFromUpstreamClone(
|
|
141
|
+
response,
|
|
142
|
+
estimatedUsage(prepared.estimatedInputTokens, prepared.estimatedOutputTokens),
|
|
171
143
|
candidate.provider_id,
|
|
172
144
|
candidate.model_id,
|
|
173
145
|
);
|
|
@@ -36,46 +36,48 @@
|
|
|
36
36
|
import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
|
|
37
37
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
38
38
|
import { isRecord } from "../../protocol/codecs";
|
|
39
|
-
import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
|
|
40
39
|
import { metrics } from "../../observability/metrics";
|
|
40
|
+
import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
|
|
41
41
|
import type { ProxyRequestState } from "../request/state";
|
|
42
42
|
import type { PreparedNativeRequest } from "../request/preparer";
|
|
43
43
|
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
44
|
+
import { finalizeRequestTelemetry } from "../guards/error-lifecycle";
|
|
44
45
|
import { runAttemptLoop } from "./attempt-loop";
|
|
46
|
+
import { hasScope, networkFetchContext, requireRecordBody, resolveAttemptCredential } from "./native-shared";
|
|
45
47
|
|
|
46
48
|
import type { GatewayHandlerDeps } from "./handler-deps";
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
type TokenCountHandlerDeps = GatewayHandlerDeps;
|
|
49
51
|
|
|
50
52
|
/** Builds the `POST /v1/messages/count_tokens` handler. */
|
|
51
53
|
export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
52
54
|
return async ({ request }: { request: Request }): Promise<Response> => {
|
|
53
55
|
const state = deps.stateStore.require(request);
|
|
54
56
|
const authorization = state.authorization;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
const countBody = requireRecordBody(
|
|
58
|
+
state,
|
|
59
|
+
"The count_tokens body must be a JSON object (request {requestId}), the same shape POST /v1/messages accepts. Check the shape of what you sent — then resend 🪞",
|
|
60
|
+
);
|
|
61
|
+
// `requireRecordBody` already refused a missing authorization; this guard
|
|
62
|
+
// exists only to narrow the type for the code below.
|
|
63
|
+
if (!authorization) {
|
|
64
|
+
throw new GatewayError("invalid_request", 400, "authentication required (request {requestId})");
|
|
62
65
|
}
|
|
63
|
-
const countBody = body as Record<string, unknown>;
|
|
64
66
|
|
|
65
67
|
// `model` names the route and `messages` is what gets counted; both are the
|
|
66
68
|
// upstream's own required fields, so the gateway does not restate its own
|
|
67
69
|
// narrower rule and risk rejecting a body the provider would accept.
|
|
68
70
|
if (typeof countBody.model !== "string" || countBody.model.trim().length === 0) {
|
|
69
|
-
throw new GatewayError("invalid_request", 400, "A model is required: it names the provider to count with.");
|
|
71
|
+
throw new GatewayError("invalid_request", 400, "A model is required (request {requestId}): it names the provider to count with. Check the shape of what you sent — then resend 🪞");
|
|
70
72
|
}
|
|
71
73
|
if (!Array.isArray(countBody.messages)) {
|
|
72
|
-
throw new GatewayError("invalid_request", 400, "messages must be an array — there is nothing to count without it.");
|
|
74
|
+
throw new GatewayError("invalid_request", 400, "messages must be an array (request {requestId}) — there is nothing to count without it. Check the shape of what you sent — then resend 🪞");
|
|
73
75
|
}
|
|
74
|
-
if (!authorization
|
|
76
|
+
if (!hasScope(authorization?.snapshot.scopes, "routing:invoke")) {
|
|
75
77
|
throw new GatewayError(
|
|
76
|
-
"
|
|
78
|
+
"policy_rejected",
|
|
77
79
|
403,
|
|
78
|
-
"token counting not allowed for this key — enable routing:invoke on the API key",
|
|
80
|
+
"token counting not allowed for this key (request {requestId}) — enable routing:invoke on the API key. Not allowed by policy — no hard feelings 🫂",
|
|
79
81
|
);
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -93,7 +95,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
93
95
|
// real 429/503 — a fabricated count would tell the client its context
|
|
94
96
|
// fits when nothing is there to serve the turn that follows.
|
|
95
97
|
if (!(error instanceof GatewayError) || error.code !== "capability_unsupported") throw error;
|
|
96
|
-
return estimatedCountResponse(state, countBody);
|
|
98
|
+
return estimatedCountResponse(state, countBody, deps.telemetryBuffer, deps.db);
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
return runAttemptLoop<Response, ProviderAdapter>({
|
|
@@ -111,27 +113,15 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
111
113
|
tenantId: prepared.authorization.tenantId,
|
|
112
114
|
strictPoolSelection: false,
|
|
113
115
|
exhaustedError: new GatewayError(
|
|
114
|
-
"
|
|
116
|
+
"route_unavailable",
|
|
115
117
|
503,
|
|
116
|
-
"no eligible route can count tokens for this model",
|
|
118
|
+
"no eligible route can count tokens for this model (request {requestId}). The bouncer stepped out — come back shortly 🚪",
|
|
117
119
|
),
|
|
118
120
|
prepare: async (candidate) => {
|
|
119
|
-
const credential = candidate
|
|
120
|
-
? await resolveCredentialForAccount(
|
|
121
|
-
deps.db,
|
|
122
|
-
candidate.provider_id,
|
|
123
|
-
candidate.provider_account_id,
|
|
124
|
-
deps.oauthRefreshService && deps.resolveOAuthRefresher
|
|
125
|
-
? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
|
|
126
|
-
: undefined,
|
|
127
|
-
)
|
|
128
|
-
: {
|
|
129
|
-
provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
|
|
130
|
-
credential_kind: "none" as const,
|
|
131
|
-
};
|
|
121
|
+
const credential = await resolveAttemptCredential(deps, candidate);
|
|
132
122
|
const adapter = await deps.resolveProviderAdapter?.(candidate.provider_id);
|
|
133
123
|
if (!adapter) {
|
|
134
|
-
throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider");
|
|
124
|
+
throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰");
|
|
135
125
|
}
|
|
136
126
|
return { credential, adapter };
|
|
137
127
|
},
|
|
@@ -146,7 +136,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
146
136
|
|
|
147
137
|
const adapter = context.adapter;
|
|
148
138
|
if (typeof adapter.dispatchNativeService !== "function") {
|
|
149
|
-
throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider");
|
|
139
|
+
throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰");
|
|
150
140
|
}
|
|
151
141
|
|
|
152
142
|
const target: ProviderDispatchTarget = {
|
|
@@ -167,14 +157,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
167
157
|
deadline: state.deadlineMs,
|
|
168
158
|
abort_signal: state.abortController.signal,
|
|
169
159
|
...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
|
|
170
|
-
...(deps.
|
|
171
|
-
? {
|
|
172
|
-
outbound_fetch: deps.networkBindingFactory.fetch(
|
|
173
|
-
leases.proxySlot?.poolId,
|
|
174
|
-
authorization.tenantId,
|
|
175
|
-
),
|
|
176
|
-
}
|
|
177
|
-
: {}),
|
|
160
|
+
...networkFetchContext(deps, leases.proxySlot?.poolId, authorization.tenantId),
|
|
178
161
|
});
|
|
179
162
|
|
|
180
163
|
// A count is free and generates nothing, so nothing is reserved or
|
|
@@ -221,20 +204,40 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
|
|
|
221
204
|
*
|
|
222
205
|
* No attempt is finalized: nothing was leased, nothing was sent upstream, and
|
|
223
206
|
* no provider produced this number, so recording it against an account would
|
|
224
|
-
* invent usage the account never served. The request still
|
|
225
|
-
*
|
|
226
|
-
*
|
|
207
|
+
* invent usage the account never served. The request is still finalized through
|
|
208
|
+
* `finalizeRequestTelemetry` (the early-rejection pattern) — without it the
|
|
209
|
+
* latency histogram, the `request_complete` console event, and the durable
|
|
210
|
+
* telemetry row all miss this request — and `state.completed` prevents the
|
|
211
|
+
* `afterResponse` fallback from finalizing it a second time.
|
|
227
212
|
*/
|
|
228
213
|
function estimatedCountResponse(
|
|
229
214
|
state: ProxyRequestState,
|
|
230
215
|
countBody: Record<string, unknown>,
|
|
216
|
+
telemetryBuffer: TelemetryBatchBuffer | undefined,
|
|
217
|
+
db: GatewayHandlerDeps["db"],
|
|
231
218
|
): Response {
|
|
232
219
|
const inputTokens = estimateMessagesInputTokens(countBody);
|
|
233
220
|
const payload = JSON.stringify({ input_tokens: inputTokens });
|
|
234
|
-
|
|
221
|
+
// The request still gets an outcome — without one the `afterResponse` hook
|
|
222
|
+
// would finalize it as a failure, and a count that answered 200 would show up
|
|
223
|
+
// in Console Log as an error. `usage` is a zero-priced estimate: nothing was
|
|
224
|
+
// leased and no provider produced this number, so recording a cost against an
|
|
225
|
+
// account would invent usage the account never served (`estimated_cost: null`).
|
|
226
|
+
state.outcome = {
|
|
227
|
+
status: "completed",
|
|
228
|
+
httpStatus: 200,
|
|
229
|
+
usage: estimatedUsage(inputTokens, 0),
|
|
230
|
+
};
|
|
235
231
|
state.completed = true;
|
|
236
232
|
state.clientResponseText = payload;
|
|
237
233
|
metrics.proxy_requests_total.inc(1, { status: "completed" });
|
|
234
|
+
// Finalize telemetry inline (the early-rejection pattern) instead of routing
|
|
235
|
+
// through `completeAttempt`: there was no lease and no provider — but the
|
|
236
|
+
// latency histogram, the `request_complete` console event, and the durable
|
|
237
|
+
// row must still happen, exactly once. `completed = true` also disables the
|
|
238
|
+
// `afterResponse` fallback, which is what used to make this request vanish
|
|
239
|
+
// from telemetry entirely.
|
|
240
|
+
if (telemetryBuffer) finalizeRequestTelemetry(state, telemetryBuffer, db);
|
|
238
241
|
return new Response(payload, {
|
|
239
242
|
headers: {
|
|
240
243
|
"content-type": "application/json",
|
|
@@ -255,7 +258,7 @@ function estimatedCountResponse(
|
|
|
255
258
|
* `/v1/messages?beta=true/count_tokens` — the counting segment lands inside the
|
|
256
259
|
* query, so the upstream sees the chat path with junk after it and never counts.
|
|
257
260
|
*/
|
|
258
|
-
|
|
261
|
+
function joinCountTokensPath(endpoint: string): string {
|
|
259
262
|
const queryAt = endpoint.indexOf("?");
|
|
260
263
|
const path = queryAt === -1 ? endpoint : endpoint.slice(0, queryAt);
|
|
261
264
|
const query = queryAt === -1 ? "" : endpoint.slice(queryAt);
|
|
@@ -16,57 +16,50 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
|
|
18
18
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
19
|
-
import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
|
|
20
19
|
import { metrics } from "../../observability/metrics";
|
|
21
|
-
import { parseThinkingSuffix } from "../../protocol/translation/thinking";
|
|
22
20
|
import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
|
|
23
21
|
import { runAttemptLoop } from "./attempt-loop";
|
|
22
|
+
import {
|
|
23
|
+
hasScope,
|
|
24
|
+
jsonResponse,
|
|
25
|
+
networkFetchContext,
|
|
26
|
+
requireRecordBody,
|
|
27
|
+
resolveAttemptCredential,
|
|
28
|
+
stripThinkingSuffix,
|
|
29
|
+
} from "./native-shared";
|
|
24
30
|
|
|
25
31
|
import type { GatewayHandlerDeps } from "./handler-deps";
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
type WebsearchHandlerDeps = GatewayHandlerDeps;
|
|
30
34
|
|
|
31
35
|
/** Adapter that can serve a web-search request. */
|
|
32
36
|
type WebsearchAdapter = ProviderAdapter & {
|
|
33
37
|
websearch: NonNullable<ProviderAdapter["websearch"]>;
|
|
34
38
|
};
|
|
35
39
|
|
|
36
|
-
function jsonResponse(payload: unknown, status: number, requestId: string): Response {
|
|
37
|
-
return new Response(JSON.stringify(payload), {
|
|
38
|
-
status,
|
|
39
|
-
headers: {
|
|
40
|
-
"content-type": "application/json",
|
|
41
|
-
"cache-control": "no-store",
|
|
42
|
-
"x-request-id": requestId,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
|
|
48
41
|
return async ({ request }: { request: Request }): Promise<Response> => {
|
|
49
42
|
const state = deps.stateStore.require(request);
|
|
50
43
|
const authorization = state.authorization;
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
const search = requireRecordBody(state, "The search body must be a JSON object (request {requestId}). Send one with at least a model and a query. Check the shape of what you sent — then resend 🪞");
|
|
45
|
+
// `requireRecordBody` already refused a missing authorization; this guard
|
|
46
|
+
// exists only to narrow the type for the code below.
|
|
47
|
+
if (!authorization) {
|
|
48
|
+
throw new GatewayError("invalid_request", 400, "authentication required (request {requestId})");
|
|
49
|
+
}
|
|
55
50
|
if (typeof search.model !== "string" || search.model.trim().length === 0)
|
|
56
|
-
throw new GatewayError("invalid_request", 400, "A model is required: it names the search provider to use.");
|
|
51
|
+
throw new GatewayError("invalid_request", 400, "A model is required (request {requestId}): it names the search provider to use. Check the shape of what you sent — then resend 🪞");
|
|
57
52
|
// The search "model" names the provider (or an alias/combo of one). A
|
|
58
53
|
// thinking suffix is stripped for the same reason the canonical preparer
|
|
59
54
|
// strips it: it is not part of any registered id.
|
|
60
|
-
const {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!authorization.snapshot.scopes?.includes("search:invoke" as never)) {
|
|
64
|
-
throw new GatewayError("invalid_request", 403, "search not allowed for this key — enable search:invoke on the API key");
|
|
55
|
+
const { bareModel, body: searchBody } = stripThinkingSuffix(search.model, search);
|
|
56
|
+
if (!hasScope(authorization?.snapshot.scopes, "search:invoke")) {
|
|
57
|
+
throw new GatewayError("policy_rejected", 403, "search not allowed for this key (request {requestId}) — enable search:invoke on the API key. Not allowed by policy — no hard feelings 🫂");
|
|
65
58
|
}
|
|
66
59
|
// Trust boundary only: the query must be a non-empty string. The adapter
|
|
67
60
|
// validates the remaining shape when it maps the body onto its provider.
|
|
68
61
|
if (typeof search.query !== "string" || search.query.trim().length === 0)
|
|
69
|
-
throw new GatewayError("invalid_request", 400, "query is required");
|
|
62
|
+
throw new GatewayError("invalid_request", 400, "query is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
|
|
70
63
|
|
|
71
64
|
const prepared = await deps.proxyPreparer.prepareNativeService({
|
|
72
65
|
model: bareModel,
|
|
@@ -89,27 +82,15 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
|
|
|
89
82
|
candidates: prepared.candidates,
|
|
90
83
|
tenantId: prepared.authorization.tenantId,
|
|
91
84
|
strictPoolSelection: false,
|
|
92
|
-
exhaustedError: new GatewayError("
|
|
85
|
+
exhaustedError: new GatewayError("route_unavailable", 503, "no eligible search route (request {requestId}). The bouncer stepped out — come back shortly 🚪"),
|
|
93
86
|
prepare: async (candidate) => {
|
|
94
|
-
const credential = candidate
|
|
95
|
-
? await resolveCredentialForAccount(
|
|
96
|
-
deps.db,
|
|
97
|
-
candidate.provider_id,
|
|
98
|
-
candidate.provider_account_id,
|
|
99
|
-
deps.oauthRefreshService && deps.resolveOAuthRefresher
|
|
100
|
-
? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
|
|
101
|
-
: undefined,
|
|
102
|
-
)
|
|
103
|
-
: {
|
|
104
|
-
provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
|
|
105
|
-
credential_kind: "none" as const,
|
|
106
|
-
};
|
|
87
|
+
const credential = await resolveAttemptCredential(deps, candidate);
|
|
107
88
|
const adapter = (await deps.resolveProviderAdapter?.(candidate.provider_id)) as WebsearchAdapter | undefined;
|
|
108
89
|
if (!adapter || typeof adapter.websearch !== "function")
|
|
109
90
|
throw new GatewayError(
|
|
110
91
|
"capability_unsupported",
|
|
111
92
|
400,
|
|
112
|
-
"web search is unavailable for this provider",
|
|
93
|
+
"web search is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰",
|
|
113
94
|
);
|
|
114
95
|
return { credential, adapter };
|
|
115
96
|
},
|
|
@@ -132,14 +113,7 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
|
|
|
132
113
|
deadline: state.deadlineMs,
|
|
133
114
|
abort_signal: state.abortController.signal,
|
|
134
115
|
...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
|
|
135
|
-
...(deps.
|
|
136
|
-
? {
|
|
137
|
-
outbound_fetch: deps.networkBindingFactory.fetch(
|
|
138
|
-
leases.proxySlot?.poolId,
|
|
139
|
-
authorization.tenantId,
|
|
140
|
-
),
|
|
141
|
-
}
|
|
142
|
-
: {}),
|
|
116
|
+
...networkFetchContext(deps, leases.proxySlot?.poolId, authorization.tenantId),
|
|
143
117
|
});
|
|
144
118
|
const payload = {
|
|
145
119
|
provider: candidate.provider_id,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { timingSafeEqual } from "node:crypto";
|
|
21
21
|
import { GatewayError } from "../protocol/gateway-error";
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
interface DrainEndpointDeps {
|
|
24
24
|
/** Shared secret; the route is registered only when this is set. */
|
|
25
25
|
readonly token: string;
|
|
26
26
|
/** Begins the graceful drain (the process's own shutdown path). */
|
|
@@ -55,9 +55,9 @@ export function createDrainHandler(
|
|
|
55
55
|
): (context: { request: Request }) => Promise<Response> {
|
|
56
56
|
return async ({ request }) => {
|
|
57
57
|
if (!isLoopbackPeer(deps.resolvePeerAddress(request)))
|
|
58
|
-
throw new GatewayError("
|
|
58
|
+
throw new GatewayError("policy_rejected", 403, "drain is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂");
|
|
59
59
|
if (!tokenMatches(request.headers.get("x-drain-token"), deps.token))
|
|
60
|
-
throw new GatewayError("
|
|
60
|
+
throw new GatewayError("authentication_failed", 401, "invalid drain token (request {requestId}). Wrong key, right door — check the token 🔑");
|
|
61
61
|
// Acknowledge first, then drain: the response must be on the wire before the
|
|
62
62
|
// shutdown path starts closing sockets, or the caller sees a dropped
|
|
63
63
|
// connection instead of a 202.
|
|
@@ -167,7 +167,7 @@ export function statusToGatewayErrorCode(status: number): GatewayErrorCode {
|
|
|
167
167
|
if (status === 413) return "request_too_large";
|
|
168
168
|
if (status === 415) return "unsupported_media_type";
|
|
169
169
|
if (status === 422) return "upstream_unprocessable";
|
|
170
|
-
if (status === 529) return "
|
|
170
|
+
if (status === 529) return "upstream_overloaded";
|
|
171
171
|
if (status >= 500 && status <= 599) return "platform_unavailable";
|
|
172
172
|
return "invalid_request";
|
|
173
173
|
}
|
|
@@ -274,7 +274,9 @@ export function classifyUpstreamFailure(error: unknown): UpstreamFailurePolicy {
|
|
|
274
274
|
error.code === "capability_unsupported" ||
|
|
275
275
|
error.code === "model_not_found" ||
|
|
276
276
|
error.code === "admission_unavailable" ||
|
|
277
|
+
error.code === "route_unavailable" ||
|
|
277
278
|
error.code === "capacity_exhausted" ||
|
|
279
|
+
error.code === "upstream_overloaded" ||
|
|
278
280
|
// A provider max-inflight rejection: the plan's next sibling account may
|
|
279
281
|
// have a free slot, exactly as with an API-key concurrency rejection.
|
|
280
282
|
error.code === "provider_capacity_exhausted" ||
|
|
@@ -286,6 +288,7 @@ export function classifyUpstreamFailure(error: unknown): UpstreamFailurePolicy {
|
|
|
286
288
|
error.code === "accounts_unavailable" ||
|
|
287
289
|
error.code === "accounts_rate_limited" ||
|
|
288
290
|
error.code === "quota_exceeded" ||
|
|
291
|
+
error.code === "rate_limited" ||
|
|
289
292
|
error.code === "authentication_failed" ||
|
|
290
293
|
error.code === "proxy_auth_required" ||
|
|
291
294
|
error.code === "deadline_exceeded" ||
|
|
@@ -427,10 +430,14 @@ const CATEGORY_HTTP_STATUS: Partial<Record<GatewayErrorCode | "unknown_error", n
|
|
|
427
430
|
// in this process.
|
|
428
431
|
transport_unavailable: 502,
|
|
429
432
|
platform_unavailable: 502,
|
|
433
|
+
upstream_overloaded: 529,
|
|
430
434
|
deadline_exceeded: 504,
|
|
431
435
|
transport_closed: 499,
|
|
432
436
|
shutting_down: 503,
|
|
433
437
|
overloaded: 529,
|
|
438
|
+
route_unavailable: 503,
|
|
439
|
+
model_not_allowed: 403,
|
|
440
|
+
rate_limited: 429,
|
|
434
441
|
unknown_error: 500,
|
|
435
442
|
};
|
|
436
443
|
|
|
@@ -624,7 +631,7 @@ export function upstreamQueuedError(
|
|
|
624
631
|
503,
|
|
625
632
|
detail.length > 0
|
|
626
633
|
? detail
|
|
627
|
-
: "The upstream has queued this request
|
|
634
|
+
: "The upstream has queued this request (request {requestId}); retry after the stated interval. You're in line — no cuts 💛",
|
|
628
635
|
{
|
|
629
636
|
providerId,
|
|
630
637
|
reason: "upstream-queued",
|
|
@@ -649,6 +656,12 @@ const QUOTA_ERROR_IDENTIFIERS: ReadonlySet<string> = new Set([
|
|
|
649
656
|
// it is a spend/limit verdict, never an auth one, so it must classify as
|
|
650
657
|
// quota (a cooldown) instead of `auth_invalidated`.
|
|
651
658
|
"110",
|
|
659
|
+
// Qoder also reports quota exhaustion as `{"code":116,"error":"quota
|
|
660
|
+
// exceeded"}` (seen on the IDE model-server surface with a 499/stream
|
|
661
|
+
// close). The 499 special-case below already treats qoder 499 as quota;
|
|
662
|
+
// naming the code here makes any status that carries it classify as quota
|
|
663
|
+
// instead of `invalid_request`/`authentication_failed`.
|
|
664
|
+
"116",
|
|
652
665
|
// InferHub publishes its own vocabulary in `error.code`. The two 403s below
|
|
653
666
|
// are deliberate absences: `account_blocked` means the account was blocked
|
|
654
667
|
// (an auth/entitlement verdict, not a spend limit) and
|
|
@@ -816,7 +829,7 @@ export function classifyUpstreamError(
|
|
|
816
829
|
}
|
|
817
830
|
if (normalized !== undefined && CAPACITY_ERROR_IDENTIFIERS.has(normalized))
|
|
818
831
|
return {
|
|
819
|
-
code: "
|
|
832
|
+
code: "upstream_overloaded",
|
|
820
833
|
status: status === 503 || status === 529 ? status : 529,
|
|
821
834
|
origin: "upstream",
|
|
822
835
|
};
|
|
@@ -969,7 +982,7 @@ export async function throwIfHtmlResponse(response: Response): Promise<void> {
|
|
|
969
982
|
throw new GatewayError(
|
|
970
983
|
"transport_unavailable",
|
|
971
984
|
502,
|
|
972
|
-
`Upstream returned an HTML error page: ${summary}
|
|
985
|
+
`Upstream returned an HTML error page: ${summary} (request {requestId}). The provider's edge is having a moment — hang tight ☕️`.slice(0, 320),
|
|
973
986
|
{ upstreamStatus: response.status, contentType, accountScope: true },
|
|
974
987
|
"upstream",
|
|
975
988
|
);
|
|
@@ -1011,11 +1024,11 @@ export function fallbackRetryDelayMs(attemptIndex: number): number {
|
|
|
1011
1024
|
|
|
1012
1025
|
/** Sleeps until the delay expires or the request is aborted. */
|
|
1013
1026
|
export function sleep(ms: number, signal: AbortSignal): Promise<void> {
|
|
1014
|
-
if (signal.aborted) return Promise.reject(new GatewayError("transport_closed", 499, "request was cancelled"));
|
|
1027
|
+
if (signal.aborted) return Promise.reject(new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃"));
|
|
1015
1028
|
if (ms <= 0) return Promise.resolve();
|
|
1016
1029
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
1017
1030
|
const timer = setTimeout(() => { signal.removeEventListener("abort", onAbort); resolve(); }, ms);
|
|
1018
|
-
const onAbort = () => { clearTimeout(timer); reject(new GatewayError("transport_closed", 499, "request was cancelled")); };
|
|
1031
|
+
const onAbort = () => { clearTimeout(timer); reject(new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃")); };
|
|
1019
1032
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1020
1033
|
return promise;
|
|
1021
1034
|
}
|
|
@@ -20,12 +20,6 @@ import { createAccessDecision } from "../../security/access-control";
|
|
|
20
20
|
export function createApiKeyAuthenticationMiddleware(deps: {
|
|
21
21
|
readonly db: CartethyiaDatabase;
|
|
22
22
|
readonly stateStore: ProxyRequestStateStore;
|
|
23
|
-
/**
|
|
24
|
-
* Model-abuse ban gate. A banned IP or key is refused here, before the
|
|
25
|
-
* canonical parse and route preparation, and before `state.authorization` is
|
|
26
|
-
* assigned — so a banned caller's attempts produce no telemetry row and no
|
|
27
|
-
* console error, which is exactly the noise this layer exists to stop.
|
|
28
|
-
*/
|
|
29
23
|
/**
|
|
30
24
|
* Tenant invoke kill switch. Checked after the key resolves, because the
|
|
31
25
|
* tenant is a property of the key: the same gateway serves several tenants
|
|
@@ -62,9 +56,9 @@ export function createApiKeyAuthenticationMiddleware(deps: {
|
|
|
62
56
|
safe.name = error instanceof Error ? error.name : "Error";
|
|
63
57
|
log.error("[auth] key lookup failed; treating as unavailable", safe);
|
|
64
58
|
throw new GatewayError(
|
|
65
|
-
"
|
|
59
|
+
"admission_unavailable",
|
|
66
60
|
503,
|
|
67
|
-
"The gateway could not verify this API key because its credential store is unavailable. Retry shortly.",
|
|
61
|
+
"The gateway could not verify this API key because its credential store is unavailable (request {requestId}). Retry shortly. The bouncer stepped out — come back shortly 🚪",
|
|
68
62
|
{},
|
|
69
63
|
"cartethyia",
|
|
70
64
|
);
|
|
@@ -76,7 +70,7 @@ export function createApiKeyAuthenticationMiddleware(deps: {
|
|
|
76
70
|
scopes: authorization.scopes,
|
|
77
71
|
});
|
|
78
72
|
if (!decision.scopes.includes("routing:invoke"))
|
|
79
|
-
throw new GatewayError("
|
|
73
|
+
throw new GatewayError("policy_rejected", 403, "This API key is not allowed to invoke routing (request {requestId}). Grant it the routing:invoke scope, or use a key that has it. Not allowed by policy — no hard feelings 🫂");
|
|
80
74
|
// Kill switch: refuse before any upstream is touched. Distinct from the
|
|
81
75
|
// scope check above, which is a property of the key — this is the tenant
|
|
82
76
|
// having stopped all traffic, and it applies to every key the tenant owns.
|
|
@@ -86,7 +80,8 @@ export function createApiKeyAuthenticationMiddleware(deps: {
|
|
|
86
80
|
throw new GatewayError(
|
|
87
81
|
"platform_unavailable",
|
|
88
82
|
503,
|
|
89
|
-
"This gateway has invoke switched off. Nothing upstream is being dialed while the switch is off; turn it back on from the dashboard overview to resume routing.",
|
|
83
|
+
"This gateway has invoke switched off (request {requestId}). Nothing upstream is being dialed while the switch is off; turn it back on from the dashboard overview to resume routing. Flip the switch back on when ready 🔌",
|
|
84
|
+
{ reason: "invoke_switched_off" },
|
|
90
85
|
);
|
|
91
86
|
// A key may refuse a downstream router it has been resold to. This is a
|
|
92
87
|
// policy check on the resolved key, so it belongs with the other
|
|
@@ -100,7 +95,7 @@ export function createApiKeyAuthenticationMiddleware(deps: {
|
|
|
100
95
|
throw new GatewayError(
|
|
101
96
|
"client_router_denied",
|
|
102
97
|
403,
|
|
103
|
-
"No API invocation access for this client.",
|
|
98
|
+
"No API invocation access for this client (request {requestId}). This router isn't on the guest list — denied, but politely 🤗",
|
|
104
99
|
{ reason: "client_router_denied", clientRouter: denied },
|
|
105
100
|
);
|
|
106
101
|
deps.stateStore.require(request).authorization = authorization;
|