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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "../../../transport/failure-policy";
|
|
13
13
|
import { GatewayError } from "../../../protocol/gateway-error";
|
|
14
14
|
import { resolveImageSource } from "../../../protocol/codecs";
|
|
15
|
+
import { reasoningEffortFromIntent } from "../../../protocol/reasoning";
|
|
15
16
|
import type {
|
|
16
17
|
CanonicalEvent,
|
|
17
18
|
CanonicalRequest,
|
|
@@ -21,16 +22,21 @@ import type {
|
|
|
21
22
|
ToolDefinition,
|
|
22
23
|
} from "../../../protocol/canonical-model";
|
|
23
24
|
import { toolResultParts, toolCallParts } from "../../../protocol/canonical-model";
|
|
24
|
-
import {
|
|
25
|
+
import { decodeQoderSseEvents } from "./qoder-sse";
|
|
25
26
|
import { usageFromProvider, readReasoningText } from "../../provider-usage";
|
|
26
|
-
import { readCredentialSecret, type ProviderDispatchTarget, type ProviderAdapter, type ProviderDispatchContext, type ProviderId } from "../../provider-contracts";
|
|
27
|
+
import { readCredentialSecret, assertWireFamily, type ProviderDispatchTarget, type ProviderAdapter, type ProviderDispatchContext, type ProviderId } from "../../provider-contracts";
|
|
27
28
|
import type { ModelDefinition } from "../../provider-model-definition";
|
|
28
29
|
import { parseQoderOAuthState } from "./qoder-oauth";
|
|
29
30
|
import { getQoderVersion } from "../../operations/client-versions";
|
|
30
31
|
import { abortGatewayError, createUpstreamDeadlineLifecycle } from "../../operations/upstream-deadline";
|
|
31
|
-
import { qoderMachineOs } from "../../operations/cli-platform";
|
|
32
|
-
import { qoderInferenceCandidates, QODER_CHAT_PATH, QODER_REALMS, type QoderRealm } from "./qoder-realm";
|
|
33
|
-
import {
|
|
32
|
+
import { qoderMachineOs, qoderMachineHostname } from "../../operations/cli-platform";
|
|
33
|
+
import { qoderIdeChatUrl, qoderInferenceCandidates, QODER_CHAT_PATH, QODER_REALMS, type QoderRealm } from "./qoder-realm";
|
|
34
|
+
import {
|
|
35
|
+
QODER_CN_MODEL_CONFIGS,
|
|
36
|
+
QODER_INTL_MODEL_CONFIGS,
|
|
37
|
+
QODER_INTL_MODELS,
|
|
38
|
+
qoderDiscoveredMaxContexts,
|
|
39
|
+
} from "./qoder-catalog";
|
|
34
40
|
import { isQoderVirtualModel, qoderDispatchModelId } from "./qoder-virtual";
|
|
35
41
|
import {
|
|
36
42
|
LEAK_MARKER_ANY_RE,
|
|
@@ -46,7 +52,7 @@ const LEAK_HOLD_MAX_CHARS = 32_768;
|
|
|
46
52
|
const LEAK_HOLD_MAX_FRAMES = 200;
|
|
47
53
|
|
|
48
54
|
const BAD_ECHO_MESSAGE =
|
|
49
|
-
"Qoder returned an unparseable tool-call echo; no tools were emitted";
|
|
55
|
+
"Qoder returned an unparseable tool-call echo (request {requestId}); no tools were emitted. The upstream came back empty-handed — retry 🌙";
|
|
50
56
|
|
|
51
57
|
function badEchoError(): GatewayError {
|
|
52
58
|
return new GatewayError(
|
|
@@ -112,26 +118,6 @@ export const MODERN_PROFILE: QoderModeProfile = {
|
|
|
112
118
|
|
|
113
119
|
const QODER_PROVIDER_ID = "qoder" as const;
|
|
114
120
|
|
|
115
|
-
export const QODER_MODEL_CONFIGS: Record<
|
|
116
|
-
string,
|
|
117
|
-
{ id: string; display_name: string; max_input_tokens: number; max_output_tokens?: number; is_vl: boolean; is_reasoning: boolean; source?: string }
|
|
118
|
-
> = {
|
|
119
|
-
auto: { id: "auto", display_name: "Auto", max_input_tokens: 180000, is_vl: true, is_reasoning: false },
|
|
120
|
-
ultimate: { id: "ultimate", display_name: "Ultimate", max_input_tokens: 180000, is_vl: true, is_reasoning: true },
|
|
121
|
-
performance: { id: "performance", display_name: "Performance", max_input_tokens: 272000, is_vl: true, is_reasoning: false },
|
|
122
|
-
efficient: { id: "efficient", display_name: "Efficient", max_input_tokens: 180000, is_vl: true, is_reasoning: false },
|
|
123
|
-
lite: { id: "lite", display_name: "Lite", max_input_tokens: 180000, is_vl: false, is_reasoning: true },
|
|
124
|
-
qmodel: { id: "qmodel", display_name: "Qwen 3.6 Plus", max_input_tokens: 180000, is_vl: true, is_reasoning: false },
|
|
125
|
-
qmodel_latest: { id: "qmodel_latest", display_name: "Qwen 3.7 Max", max_input_tokens: 1000000, is_vl: true, is_reasoning: false },
|
|
126
|
-
qmodel_preview: { id: "qmodel_preview", display_name: "Qwen 3.8 Max", max_input_tokens: 1000000, is_vl: true, is_reasoning: false },
|
|
127
|
-
dmodel: { id: "dmodel", display_name: "DeepSeek V4 Pro", max_input_tokens: 180000, is_vl: true, is_reasoning: true },
|
|
128
|
-
dfmodel: { id: "dfmodel", display_name: "DeepSeek V4 Flash", max_input_tokens: 180000, is_vl: true, is_reasoning: true },
|
|
129
|
-
gm51model: { id: "gm51model", display_name: "GLM 5.1", max_input_tokens: 180000, is_vl: true, is_reasoning: true },
|
|
130
|
-
kmodel: { id: "kmodel", display_name: "Kimi K2.6", max_input_tokens: 256000, is_vl: true, is_reasoning: false },
|
|
131
|
-
mmodel: { id: "mmodel", display_name: "MiniMax M2.7", max_input_tokens: 180000, is_vl: true, is_reasoning: false },
|
|
132
|
-
kmodel_latest: { id: "kmodel_latest", display_name: "Kimi K2.7 Latest", max_input_tokens: 256000, is_vl: true, is_reasoning: false },
|
|
133
|
-
};
|
|
134
|
-
|
|
135
121
|
const QODER_APPCODE = "cosy";
|
|
136
122
|
const QODER_SIG_SECRET = "d2FyLCB3YXIgbmV2ZXIgY2hhbmdlcw==";
|
|
137
123
|
/**
|
|
@@ -177,7 +163,19 @@ interface QoderModelConfig {
|
|
|
177
163
|
max_output_tokens?: number;
|
|
178
164
|
is_vl: boolean;
|
|
179
165
|
is_reasoning: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Whether upstream publishes this row as free. Only an explicit `true`
|
|
168
|
+
* marks the model as free-tier; absent or `false` means billed/unknown, so
|
|
169
|
+
* a 402/116 there is genuine quota exhaustion, not a free-tier entitlement
|
|
170
|
+
* refusal, and must not trigger a legacy-surface fallback.
|
|
171
|
+
*/
|
|
172
|
+
is_free?: boolean;
|
|
180
173
|
source?: string;
|
|
174
|
+
/**
|
|
175
|
+
* Reasoning effort to send when the caller did not ask for one. Some models
|
|
176
|
+
* on the IDE surface stream no reasoning unless an effort is present.
|
|
177
|
+
*/
|
|
178
|
+
default_reasoning_effort?: string;
|
|
181
179
|
}
|
|
182
180
|
|
|
183
181
|
function qoderMd5(value: string | Uint8Array): string {
|
|
@@ -246,7 +244,7 @@ export function qoderOAuthAuth(
|
|
|
246
244
|
throw new GatewayError(
|
|
247
245
|
"authentication_failed",
|
|
248
246
|
401,
|
|
249
|
-
"Qoder OAuth account is missing its user identity (auth_state.userId/machineId absent or empty — re-login, or re-export with authState included); sign in again",
|
|
247
|
+
"Qoder OAuth account is missing its user identity (request {requestId}) (auth_state.userId/machineId absent or empty — re-login, or re-export with authState included); sign in again. Wrong key, right door — check the credential 🔑",
|
|
250
248
|
);
|
|
251
249
|
}
|
|
252
250
|
return {
|
|
@@ -387,14 +385,14 @@ async function exchangeQoderPat(
|
|
|
387
385
|
try {
|
|
388
386
|
responseBody = await response.json();
|
|
389
387
|
} catch {
|
|
390
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned invalid JSON", {}, "upstream");
|
|
388
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned invalid JSON (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
391
389
|
}
|
|
392
390
|
if (typeof responseBody !== "object" || responseBody === null || Array.isArray(responseBody)) {
|
|
393
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned unexpected response", {}, "upstream");
|
|
391
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned unexpected response (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
394
392
|
}
|
|
395
393
|
const rec = responseBody as Record<string, unknown>;
|
|
396
394
|
if (typeof rec["id"] !== "string" || typeof rec["securityOauthToken"] !== "string") {
|
|
397
|
-
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned incomplete credentials", {}, "upstream");
|
|
395
|
+
throw new GatewayError("platform_unavailable", 502, "Qoder PAT exchange returned incomplete credentials (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
398
396
|
}
|
|
399
397
|
return {
|
|
400
398
|
userId: rec["id"] as string,
|
|
@@ -451,12 +449,29 @@ function qoderEnvelopeMessage(rec: Record<string, unknown>): string {
|
|
|
451
449
|
return extractUpstreamMessage(rec);
|
|
452
450
|
}
|
|
453
451
|
|
|
454
|
-
function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data: string }> {
|
|
452
|
+
export function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data: string }> {
|
|
455
453
|
if (data === "[DONE]") return [{ event: null, data: "[DONE]" }];
|
|
456
454
|
let envelope: unknown;
|
|
455
|
+
// The IDE surface sometimes embeds literal newlines inside JSON string
|
|
456
|
+
// values (e.g. the raw_usage event carries keys like
|
|
457
|
+
// `"data_policy_preferre\n\nnd_count"`). Those bytes are not valid JSON, so
|
|
458
|
+
// the first parse fails; when it does, salvage the event by escaping the
|
|
459
|
+
// raw newlines inside string values and parse again. The repaired text is
|
|
460
|
+
// also what the direct-chunk branch hands to the chunk parser below, so its
|
|
461
|
+
// own `JSON.parse` succeeds too.
|
|
462
|
+
let repaired = data;
|
|
457
463
|
try {
|
|
458
464
|
envelope = JSON.parse(data);
|
|
459
465
|
} catch {
|
|
466
|
+
const attempted = escapeJsonRawNewlines(data);
|
|
467
|
+
if (attempted !== data) {
|
|
468
|
+
try {
|
|
469
|
+
envelope = JSON.parse(attempted);
|
|
470
|
+
repaired = attempted;
|
|
471
|
+
} catch {
|
|
472
|
+
envelope = undefined;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
460
475
|
}
|
|
461
476
|
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return [];
|
|
462
477
|
const rec = envelope as Record<string, unknown>;
|
|
@@ -481,7 +496,7 @@ function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data
|
|
|
481
496
|
throw new GatewayError(
|
|
482
497
|
classification.code,
|
|
483
498
|
classification.status,
|
|
484
|
-
detail.length > 0 ? detail : `Qoder stream envelope returned HTTP ${status}
|
|
499
|
+
detail.length > 0 ? `${detail} (request {requestId}). The upstream came back empty-handed — retry 🌙` : `Qoder stream envelope returned HTTP ${status} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
|
|
485
500
|
{
|
|
486
501
|
providerId: QODER_PROVIDER_ID,
|
|
487
502
|
providerStatus: status,
|
|
@@ -498,8 +513,61 @@ function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data
|
|
|
498
513
|
}
|
|
499
514
|
const body = rec["body"];
|
|
500
515
|
if (body === "[DONE]") return [{ event: null, data: "[DONE]" }];
|
|
501
|
-
if (typeof body
|
|
502
|
-
|
|
516
|
+
if (typeof body === "string") return [{ event: null, data: body.replace(/\r?\n/g, "") }];
|
|
517
|
+
// Direct OpenAI chunk (IDE surface): no envelope body, but a `choices` array
|
|
518
|
+
// is present. Pass the (repaired) raw data through so the chunk parser sees
|
|
519
|
+
// it.
|
|
520
|
+
if (Array.isArray(rec["choices"])) return [{ event: null, data: repaired }];
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Escapes raw newline bytes that appear inside JSON string values.
|
|
526
|
+
*
|
|
527
|
+
* The IDE surface emits `data:` events whose JSON payload contains literal
|
|
528
|
+
* newlines inside strings (the terminal `raw_usage` event carries keys like
|
|
529
|
+
* `"data_policy_preferre\n\nnd_count"`). `JSON.parse` rejects those bytes, so
|
|
530
|
+
* before parsing we rewrite each raw `\n` found inside a string to the two
|
|
531
|
+
* characters `\n`. Bytes outside strings (whitespace, structural separators)
|
|
532
|
+
* are left untouched.
|
|
533
|
+
*/
|
|
534
|
+
function escapeJsonRawNewlines(data: string): string {
|
|
535
|
+
let out = "";
|
|
536
|
+
let inString = false;
|
|
537
|
+
let escaped = false;
|
|
538
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
539
|
+
const ch = data[i]!;
|
|
540
|
+
if (inString) {
|
|
541
|
+
if (escaped) {
|
|
542
|
+
out += ch;
|
|
543
|
+
escaped = false;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (ch === "\\") {
|
|
547
|
+
out += ch;
|
|
548
|
+
escaped = true;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (ch === '"') {
|
|
552
|
+
inString = false;
|
|
553
|
+
out += ch;
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (ch === "\n" || ch === "\r") {
|
|
557
|
+
out += "\\n";
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
out += ch;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (ch === '"') {
|
|
564
|
+
inString = true;
|
|
565
|
+
out += ch;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
out += ch;
|
|
569
|
+
}
|
|
570
|
+
return out;
|
|
503
571
|
}
|
|
504
572
|
|
|
505
573
|
function qoderStableHash(prefix: string, ...parts: string[]): string {
|
|
@@ -540,13 +608,38 @@ function qoderStableRecordId(modelId: string, messages: CanonicalRequest["messag
|
|
|
540
608
|
return h.digest("hex").slice(0, 16);
|
|
541
609
|
}
|
|
542
610
|
|
|
543
|
-
|
|
611
|
+
/**
|
|
612
|
+
* The widest selectable context window for one model key.
|
|
613
|
+
*
|
|
614
|
+
* The live list's `context_config` is the authority when discovery has run:
|
|
615
|
+
* it names every window the account can switch to, and the widest one is what
|
|
616
|
+
* a dispatch should send (`model_config.max_input_tokens`) so a conversation is
|
|
617
|
+
* never capped at the *currently active* window (often 180k) when the account
|
|
618
|
+
* can use 1M. Falls back to the static roster's stated window otherwise.
|
|
619
|
+
*/
|
|
620
|
+
function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, realm: QoderRealm): number {
|
|
621
|
+
const discovered = qoderDiscoveredMaxContexts(realm).get(modelId);
|
|
622
|
+
if (discovered !== undefined) return discovered;
|
|
623
|
+
return modelConfig.max_input_tokens;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function qoderRequestedMaxTokens(
|
|
627
|
+
request: CanonicalRequest,
|
|
628
|
+
modelConfig: QoderModelConfig,
|
|
629
|
+
realm: QoderRealm,
|
|
630
|
+
): number {
|
|
544
631
|
/**
|
|
545
632
|
* The ceiling is the roster's stated one. A fixed 32k fallback would clamp
|
|
546
633
|
* every model alike, so a request asking for more would be silently lowered
|
|
547
|
-
* no matter which model it named.
|
|
634
|
+
* no matter which model it named. A discovered row carries no
|
|
635
|
+
* `max_output_tokens` (upstream's model list reports none), so consult the
|
|
636
|
+
* static roster for the key before the 65_536 last resort — and keep that
|
|
637
|
+
* last resort only when neither the caller nor the roster states a limit.
|
|
548
638
|
*/
|
|
549
|
-
const configured =
|
|
639
|
+
const configured =
|
|
640
|
+
typeof modelConfig.max_output_tokens === "number"
|
|
641
|
+
? modelConfig.max_output_tokens
|
|
642
|
+
: qoderRosterMaxOutput(modelConfig.id, realm) ?? 65_536;
|
|
550
643
|
const controls = request.generation_controls as Record<string, unknown>;
|
|
551
644
|
const requested =
|
|
552
645
|
typeof controls["max_tokens"] === "number"
|
|
@@ -557,6 +650,17 @@ function qoderRequestedMaxTokens(request: CanonicalRequest, modelConfig: QoderMo
|
|
|
557
650
|
return typeof requested === "number" && requested > 0 ? Math.min(requested, configured) : configured;
|
|
558
651
|
}
|
|
559
652
|
|
|
653
|
+
/**
|
|
654
|
+
* The static roster's stated output ceiling for one key, when the roster states
|
|
655
|
+
* one. Dispatch builds a live-discovered model's config without an output limit
|
|
656
|
+
* (upstream's model list reports none); this lookup restores the roster's
|
|
657
|
+
* ceiling so such a model is not capped at the 65_536 last resort.
|
|
658
|
+
*/
|
|
659
|
+
function qoderRosterMaxOutput(modelId: string, realm: QoderRealm): number | undefined {
|
|
660
|
+
const configs = realm === "cn" ? QODER_CN_MODEL_CONFIGS : QODER_INTL_MODEL_CONFIGS;
|
|
661
|
+
return configs[modelId]?.max_output_tokens;
|
|
662
|
+
}
|
|
663
|
+
|
|
560
664
|
/**
|
|
561
665
|
* Whether this model needs the prior turn's reasoning echoed back.
|
|
562
666
|
*
|
|
@@ -885,16 +989,32 @@ export function buildQoderRequest(
|
|
|
885
989
|
}
|
|
886
990
|
const latestUserMessage = [...qoderMessages].reverse().find((m) => m["role"] === "user");
|
|
887
991
|
const latestUserText = typeof latestUserMessage?.["content"] === "string" ? (latestUserMessage["content"] as string) : "";
|
|
888
|
-
const maxTokens = qoderRequestedMaxTokens(request, modelConfig);
|
|
992
|
+
const maxTokens = qoderRequestedMaxTokens(request, modelConfig, realm);
|
|
889
993
|
const reasoning = modelConfig.is_reasoning === true;
|
|
994
|
+
// 1:1 with the IDE worker's `FTn` legacy builder, which sends reasoning
|
|
995
|
+
// effort as a top-level camelCase field (`reasoningEffort`), derived from
|
|
996
|
+
// the canonical intent when one is present. Omitted entirely when the
|
|
997
|
+
// caller asked for no reasoning, matching the reference's conditional send.
|
|
998
|
+
// When the caller sends no intent but the model needs the push to think
|
|
999
|
+
// (verified live: Sonus/Cantus stream no reasoning without it), use the
|
|
1000
|
+
// roster's default effort.
|
|
1001
|
+
const reasoningEffort =
|
|
1002
|
+
reasoningEffortFromIntent(request.reasoning) ?? modelConfig.default_reasoning_effort;
|
|
890
1003
|
const sessionId = qoderStableHash("qoder-session", auth.userId, modelId);
|
|
891
1004
|
const recordId = qoderStableRecordId(modelId, allMessages, maxTokens);
|
|
892
1005
|
return {
|
|
1006
|
+
// 1:1 with the IDE worker's `FTn` legacy request builder, which always
|
|
1007
|
+
// sends a top-level `model` equal to the resolved roster key (falling
|
|
1008
|
+
// back to "auto" only when no key is known). The upstream honours
|
|
1009
|
+
// `model_config.key` for actual dispatch and echoes a fixed "auto" in
|
|
1010
|
+
// stream chunks, but the field is part of the wire shape the IDE sends.
|
|
1011
|
+
model: modelId,
|
|
893
1012
|
request_id: randomUUID(),
|
|
894
1013
|
request_set_id: recordId,
|
|
895
1014
|
chat_record_id: recordId,
|
|
896
1015
|
session_id: sessionId,
|
|
897
1016
|
stream: true,
|
|
1017
|
+
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
898
1018
|
...(MODERN_PROFILE.emptyAliyunUserType ? { aliyun_user_type: "" } : {}),
|
|
899
1019
|
chat_task: "FREE_INPUT",
|
|
900
1020
|
is_reply: true,
|
|
@@ -927,11 +1047,16 @@ export function buildQoderRequest(
|
|
|
927
1047
|
url: "",
|
|
928
1048
|
is_vl: modelConfig.is_vl,
|
|
929
1049
|
is_reasoning: modelConfig.is_reasoning,
|
|
930
|
-
|
|
1050
|
+
// Send the widest selectable window from the live list's `context_config`
|
|
1051
|
+
// (falling back to the static roster's stated window). Without this, the
|
|
1052
|
+
// request would declare the *currently active* window — often 180k — even
|
|
1053
|
+
// when the account can use 1M, capping the conversation the upstream sees.
|
|
1054
|
+
max_input_tokens: qoderMaxContextFor(modelId, modelConfig, realm),
|
|
931
1055
|
// Mirrors the roster's stated ceiling. A fixed 32k fallback would be sent
|
|
932
1056
|
// for every model alike, so the request would declare a cap the catalog
|
|
933
|
-
// does not publish.
|
|
934
|
-
|
|
1057
|
+
// does not publish. A discovered model's config carries no output limit,
|
|
1058
|
+
// so consult the roster for the key before the 65_536 last resort.
|
|
1059
|
+
max_output_tokens: modelConfig.max_output_tokens ?? qoderRosterMaxOutput(modelId, realm) ?? 65_536,
|
|
935
1060
|
source: modelConfig.source ?? "system",
|
|
936
1061
|
},
|
|
937
1062
|
messages: qoderMessages,
|
|
@@ -951,9 +1076,240 @@ export function buildQoderRequest(
|
|
|
951
1076
|
};
|
|
952
1077
|
}
|
|
953
1078
|
|
|
1079
|
+
/**
|
|
1080
|
+
* Builds the modern model-server (`/model/v1/chat/completions`) request body.
|
|
1081
|
+
*
|
|
1082
|
+
* It reuses the message translation from `buildQoderRequest` (tool turns,
|
|
1083
|
+
* reasoning echo, image parts) and wraps the result in the OpenAI-shaped body
|
|
1084
|
+
* the IDE surface expects: top-level `model`, `messages`, `stream_options`,
|
|
1085
|
+
* and a `metadata.context`/`metadata.business` block. `model` is the roster
|
|
1086
|
+
* key after virtual-name resolution, so the upstream never sees a virtual id
|
|
1087
|
+
* (which it answers by falling back to `auto`).
|
|
1088
|
+
*/
|
|
1089
|
+
export function buildQoderIdeRequest(
|
|
1090
|
+
modelId: string,
|
|
1091
|
+
request: CanonicalRequest,
|
|
1092
|
+
modelConfig: QoderModelConfig,
|
|
1093
|
+
auth: QoderAuth,
|
|
1094
|
+
realm: QoderRealm = "intl",
|
|
1095
|
+
version = getQoderVersion(),
|
|
1096
|
+
): Record<string, unknown> {
|
|
1097
|
+
const legacy = buildQoderRequest(modelId, request, modelConfig, auth, realm);
|
|
1098
|
+
const messages = legacy.messages as Array<Record<string, unknown>>;
|
|
1099
|
+
return {
|
|
1100
|
+
model: modelId,
|
|
1101
|
+
messages,
|
|
1102
|
+
stream: true,
|
|
1103
|
+
stream_options: { include_usage: true },
|
|
1104
|
+
// The same reasoning-effort field the legacy surface uses; without it the
|
|
1105
|
+
// IDE surface streams no thinking for models that need the push (Sonus,
|
|
1106
|
+
// Cantus). Undefined when the caller asked for none and the roster has no
|
|
1107
|
+
// default, matching the legacy builder.
|
|
1108
|
+
...(legacy.reasoningEffort === undefined ? {} : { reasoningEffort: legacy.reasoningEffort }),
|
|
1109
|
+
// The full wire config (model key, window, output ceiling, tier) the legacy
|
|
1110
|
+
// surface sends. The IDE surface reads the same block, so shipping it keeps
|
|
1111
|
+
// the request the same shape for every Qoder model — including lite's 1M
|
|
1112
|
+
// window and 64k output cap.
|
|
1113
|
+
...(legacy.model_config === undefined ? {} : { model_config: legacy.model_config }),
|
|
1114
|
+
metadata: {
|
|
1115
|
+
context: {
|
|
1116
|
+
request_id: legacy.request_id,
|
|
1117
|
+
request_set_id: legacy.request_set_id,
|
|
1118
|
+
session_id: legacy.session_id,
|
|
1119
|
+
source_session_id: "",
|
|
1120
|
+
task_id: "common",
|
|
1121
|
+
client_type: "5",
|
|
1122
|
+
},
|
|
1123
|
+
business: {
|
|
1124
|
+
business_product: "cli",
|
|
1125
|
+
business_type: "agent",
|
|
1126
|
+
business_version: version,
|
|
1127
|
+
scene: "assistant",
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
max_tokens: (legacy.max_tokens as number) ?? 65_536,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Identity headers for the modern model-server surface (plain bearer).
|
|
1136
|
+
*
|
|
1137
|
+
* Mirrors the desktop IDE's `openResponse` exactly: the IDE sends the five
|
|
1138
|
+
* OpenAI-surface headers (Authorization, Content-Type, Accept, X-Request-ID,
|
|
1139
|
+
* X-Session-ID) and the global client-identity interceptor adds Cosy-Version,
|
|
1140
|
+
* Cosy-ClientType, Cosy-MachineOS, and Cosy-MachineHostname (for infer-sse
|
|
1141
|
+
* requests). It does NOT send Cosy-MachineId / Cosy-MachineToken /
|
|
1142
|
+
* Cosy-MachineType on this surface, so neither do we.
|
|
1143
|
+
*/
|
|
1144
|
+
function qoderIdeHeaders(auth: QoderAuth, version: string): Record<string, string> {
|
|
1145
|
+
return {
|
|
1146
|
+
Authorization: `Bearer ${auth.securityOauthToken}`,
|
|
1147
|
+
"Content-Type": "application/json",
|
|
1148
|
+
Accept: "text/event-stream",
|
|
1149
|
+
"X-Request-ID": randomUUID(),
|
|
1150
|
+
"X-Session-ID": randomUUID(),
|
|
1151
|
+
"Cosy-Version": version,
|
|
1152
|
+
"Cosy-ClientType": "5",
|
|
1153
|
+
"Cosy-MachineOS": qoderMachineOs(),
|
|
1154
|
+
"Cosy-MachineHostname": qoderMachineHostname(),
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
async function callQoderIde(
|
|
1159
|
+
url: string,
|
|
1160
|
+
body: Record<string, unknown>,
|
|
1161
|
+
auth: QoderAuth,
|
|
1162
|
+
signal: AbortSignal,
|
|
1163
|
+
fetcher: QoderFetcher,
|
|
1164
|
+
version = getQoderVersion(),
|
|
1165
|
+
): Promise<Response> {
|
|
1166
|
+
return fetcher(url, {
|
|
1167
|
+
method: "POST",
|
|
1168
|
+
headers: qoderIdeHeaders(auth, version),
|
|
1169
|
+
body: JSON.stringify(body),
|
|
1170
|
+
signal,
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* True when the modern model-server surface refused the request on entitlement
|
|
1176
|
+
* grounds. This only detects the refusal — whether a legacy fallback is worth
|
|
1177
|
+
* trying is the caller's decision, and it is gated on the model being free-tier
|
|
1178
|
+
* (see `dispatch`).
|
|
1179
|
+
*
|
|
1180
|
+
* The upstream's refusal envelope is `{"code":116,"error":"quota exceeded"}`
|
|
1181
|
+
* on HTTP 402. The IDE's own error set classifies 116 as a quota/entitlement
|
|
1182
|
+
* failure (not an auth error), and the gRPC surface reports the same code as
|
|
1183
|
+
* status 13. For a free-tier model this is an entitlement refusal the legacy
|
|
1184
|
+
* COSY surface will serve with the same credential; for a billed model the
|
|
1185
|
+
* same code is genuine quota exhaustion (Qoder's daily exhaustion and cooldown
|
|
1186
|
+
* run on a 1-day cycle) and must surface as-is so the quota/cooldown path can
|
|
1187
|
+
* see it. A 401/403 or a model-level error is never fallback-class either way.
|
|
1188
|
+
*/
|
|
1189
|
+
function qoderIdeEntitlementRefused(response: Response): boolean {
|
|
1190
|
+
// Status alone is the signal; the body is left intact for
|
|
1191
|
+
// `mapUpstreamHttpError` to surface if the fallback also fails.
|
|
1192
|
+
return response.status === 402;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Qoder upstream streams are flaky: the IDE surface sometimes answers a full
|
|
1197
|
+
* normal stop with reasoning only and no output text/tool calls (observed ~1
|
|
1198
|
+
* in 4 runs with Sonus). That is not a valid completion — the lab would show a
|
|
1199
|
+
* thought with "No text came back". Replay such a stream before committing to
|
|
1200
|
+
* it. Buffering the body is required: the decision is only knowable after the
|
|
1201
|
+
* stream ends, and the bytes are replayed to the canonical parser.
|
|
1202
|
+
*/
|
|
1203
|
+
interface QoderStreamDrain {
|
|
1204
|
+
readonly bytes: Uint8Array;
|
|
1205
|
+
readonly meaningful: boolean;
|
|
1206
|
+
readonly thoughtOnly: boolean;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
async function drainQoderResponse(
|
|
1210
|
+
response: Response,
|
|
1211
|
+
signal: AbortSignal,
|
|
1212
|
+
): Promise<QoderStreamDrain> {
|
|
1213
|
+
const chunks: Uint8Array[] = [];
|
|
1214
|
+
let total = 0;
|
|
1215
|
+
const reader = response.body!.getReader();
|
|
1216
|
+
try {
|
|
1217
|
+
for (;;) {
|
|
1218
|
+
if (signal.aborted) break;
|
|
1219
|
+
const { done, value } = await reader.read();
|
|
1220
|
+
if (done) break;
|
|
1221
|
+
if (value) {
|
|
1222
|
+
chunks.push(value);
|
|
1223
|
+
total += value.byteLength;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
} finally {
|
|
1227
|
+
reader.releaseLock();
|
|
1228
|
+
}
|
|
1229
|
+
const bytes = new Uint8Array(total);
|
|
1230
|
+
let offset = 0;
|
|
1231
|
+
for (const chunk of chunks) {
|
|
1232
|
+
bytes.set(chunk, offset);
|
|
1233
|
+
offset += chunk.byteLength;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
let dataEvents = 0;
|
|
1237
|
+
let meaningful = false;
|
|
1238
|
+
let thoughtOnly = false;
|
|
1239
|
+
const decoder = new TextDecoder();
|
|
1240
|
+
const text = decoder.decode(bytes);
|
|
1241
|
+
for (const frame of text.split(/\r?\n\r?\n/)) {
|
|
1242
|
+
if (frame.trim().length === 0) continue;
|
|
1243
|
+
const dataLines: string[] = [];
|
|
1244
|
+
for (const line of frame.split(/\r?\n/)) {
|
|
1245
|
+
if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
|
|
1246
|
+
}
|
|
1247
|
+
const data = dataLines.join("\n").trim();
|
|
1248
|
+
if (data.length === 0 || data === "[DONE]") continue;
|
|
1249
|
+
dataEvents++;
|
|
1250
|
+
for (const { data: frameData } of qoderEnvelopeToFrames(data)) {
|
|
1251
|
+
if (frameData === "[DONE]") continue;
|
|
1252
|
+
let json: Record<string, unknown>;
|
|
1253
|
+
try {
|
|
1254
|
+
json = JSON.parse(frameData) as Record<string, unknown>;
|
|
1255
|
+
} catch {
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
const choices = json["choices"];
|
|
1259
|
+
if (!Array.isArray(choices)) continue;
|
|
1260
|
+
for (const choice of choices as Array<Record<string, unknown>>) {
|
|
1261
|
+
const delta = choice["delta"] as Record<string, unknown> | undefined;
|
|
1262
|
+
if (delta === undefined) continue;
|
|
1263
|
+
if (typeof delta["content"] === "string" && (delta["content"] as string).length > 0) {
|
|
1264
|
+
meaningful = true;
|
|
1265
|
+
return { bytes, meaningful, thoughtOnly };
|
|
1266
|
+
}
|
|
1267
|
+
if (Array.isArray(delta["tool_calls"]) && (delta["tool_calls"] as unknown[]).length > 0) {
|
|
1268
|
+
meaningful = true;
|
|
1269
|
+
return { bytes, meaningful, thoughtOnly };
|
|
1270
|
+
}
|
|
1271
|
+
if (
|
|
1272
|
+
typeof delta["reasoning_content"] === "string" &&
|
|
1273
|
+
(delta["reasoning_content"] as string).length > 0
|
|
1274
|
+
) {
|
|
1275
|
+
thoughtOnly = true;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return { bytes, meaningful, thoughtOnly };
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Runs the IDE request up to `1 + maxReplays` times, returning the first
|
|
1285
|
+
* response whose stream carried real output (text or tool calls). A 200 with
|
|
1286
|
+
* no output (empty or thought-only) is replayed rather than surfaced.
|
|
1287
|
+
*/
|
|
1288
|
+
async function fetchQoderIdeWithEmptyRecovery(
|
|
1289
|
+
requestOnce: () => Promise<Response>,
|
|
1290
|
+
signal: AbortSignal,
|
|
1291
|
+
maxReplays = 2,
|
|
1292
|
+
): Promise<Response> {
|
|
1293
|
+
let last: Response | undefined;
|
|
1294
|
+
for (let attempt = 0; attempt <= maxReplays; attempt += 1) {
|
|
1295
|
+
const res = await requestOnce();
|
|
1296
|
+
if (!res.ok || !res.body) return res;
|
|
1297
|
+
const drain = await drainQoderResponse(res, signal);
|
|
1298
|
+
last = new Response(drain.bytes as BodyInit, {
|
|
1299
|
+
status: 200,
|
|
1300
|
+
headers: { "content-type": "text/event-stream", "cache-control": "no-cache" },
|
|
1301
|
+
});
|
|
1302
|
+
if (drain.meaningful) return last;
|
|
1303
|
+
}
|
|
1304
|
+
return last!;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
export { fetchQoderIdeWithEmptyRecovery };
|
|
1308
|
+
|
|
954
1309
|
// Stream transformation — decode SSE, unwrap COSY envelope, map OpenAI chat chunks to CanonicalEvent
|
|
955
1310
|
|
|
956
1311
|
function mapQoderFinishReason(raw: unknown): CanonicalStopReason | undefined {
|
|
1312
|
+
if (raw === "stop") return "stop";
|
|
957
1313
|
if (raw === "length") return "length";
|
|
958
1314
|
if (raw === "tool_calls") return "tool_use";
|
|
959
1315
|
if (raw === "content_filter") return "content_filter";
|
|
@@ -966,7 +1322,7 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
966
1322
|
model: string,
|
|
967
1323
|
): AsyncIterable<CanonicalEvent> {
|
|
968
1324
|
let seq = 1;
|
|
969
|
-
|
|
1325
|
+
let started = false;
|
|
970
1326
|
let finishReason: unknown = undefined;
|
|
971
1327
|
let rawUsage: Record<string, unknown> | undefined = undefined;
|
|
972
1328
|
let hasTerminal = false;
|
|
@@ -981,7 +1337,7 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
981
1337
|
let leakBuf = "";
|
|
982
1338
|
let leakFrames = 0;
|
|
983
1339
|
|
|
984
|
-
for await (const sse of
|
|
1340
|
+
for await (const sse of decodeQoderSseEvents(body, signal)) {
|
|
985
1341
|
const sseData = sse.data;
|
|
986
1342
|
let frames: Array<{ event: string | null; data: string }>;
|
|
987
1343
|
try {
|
|
@@ -994,6 +1350,10 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
994
1350
|
if (!data) continue;
|
|
995
1351
|
if (data === "[DONE]") {
|
|
996
1352
|
hasTerminal = true;
|
|
1353
|
+
if (!started) {
|
|
1354
|
+
yield { type: "response_start", sequence_number: seq++, model } as CanonicalEvent;
|
|
1355
|
+
started = true;
|
|
1356
|
+
}
|
|
997
1357
|
continue;
|
|
998
1358
|
}
|
|
999
1359
|
let json: Record<string, unknown>;
|
|
@@ -1002,7 +1362,51 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1002
1362
|
} catch {
|
|
1003
1363
|
continue;
|
|
1004
1364
|
}
|
|
1365
|
+
// The upstream names the real model in each chunk. Use the first one we
|
|
1366
|
+
// see for the opening event so the client sees the same model the
|
|
1367
|
+
// provider actually served, not the (virtual) id that was requested.
|
|
1368
|
+
if (!started && typeof json["model"] === "string") {
|
|
1369
|
+
const upstreamModel = (json["model"] as string).trim();
|
|
1370
|
+
yield {
|
|
1371
|
+
type: "response_start",
|
|
1372
|
+
sequence_number: seq++,
|
|
1373
|
+
model: upstreamModel.length > 0 ? upstreamModel : model,
|
|
1374
|
+
} as CanonicalEvent;
|
|
1375
|
+
started = true;
|
|
1376
|
+
}
|
|
1377
|
+
// IDE surface error event: `{"code":"invalid_model_error","message":"..."}`
|
|
1378
|
+
// carries no choices. Surface it as an upstream failure instead of an
|
|
1379
|
+
// empty successful stream.
|
|
1380
|
+
if (json["error"] !== undefined && json["choices"] === undefined) {
|
|
1381
|
+
const error = json["error"] as Record<string, unknown> | string;
|
|
1382
|
+
const message =
|
|
1383
|
+
typeof error === "string"
|
|
1384
|
+
? error
|
|
1385
|
+
: typeof error?.["message"] === "string"
|
|
1386
|
+
? (error["message"] as string)
|
|
1387
|
+
: typeof json["message"] === "string"
|
|
1388
|
+
? (json["message"] as string)
|
|
1389
|
+
: "Qoder model server error";
|
|
1390
|
+
const code = typeof json["code"] === "string" ? (json["code"] as string) : undefined;
|
|
1391
|
+
throw new GatewayError(
|
|
1392
|
+
code === "invalid_model_error" ? "model_not_found" : "platform_unavailable",
|
|
1393
|
+
code === "invalid_model_error" ? 404 : 502,
|
|
1394
|
+
`${message} (request {requestId}). The upstream came back empty-handed — retry 🌙`,
|
|
1395
|
+
{ providerId: QODER_PROVIDER_ID, ...(code === undefined ? {} : { providerCode: code }) },
|
|
1396
|
+
"upstream",
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1005
1399
|
if (json["usage"] && typeof json["usage"] === "object") rawUsage = json["usage"] as Record<string, unknown>;
|
|
1400
|
+
// IDE surface terminal event: `choices:[]` + `raw_usage`. It carries the
|
|
1401
|
+
// final usage and confirms the turn ended, so record it and synthesize a
|
|
1402
|
+
// stop reason — the terminal event has no `choices[].finish_reason`.
|
|
1403
|
+
if (json["raw_usage"] && typeof json["raw_usage"] === "object") {
|
|
1404
|
+
const rawUsageObj = json["raw_usage"] as Record<string, unknown>;
|
|
1405
|
+
rawUsage = (rawUsageObj["data"] as Record<string, unknown> | undefined) ?? rawUsageObj;
|
|
1406
|
+
if (json["choices"] === undefined || (json["choices"] as unknown[]).length === 0) {
|
|
1407
|
+
finishReason = finishReason ?? "stop";
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1006
1410
|
const choices = (json["choices"] as Array<Record<string, unknown>>) ?? [];
|
|
1007
1411
|
for (const choice of choices) {
|
|
1008
1412
|
const delta = choice["delta"] as Record<string, unknown> | undefined;
|
|
@@ -1128,12 +1532,16 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1128
1532
|
}
|
|
1129
1533
|
}
|
|
1130
1534
|
const fr = choice["finish_reason"];
|
|
1131
|
-
|
|
1535
|
+
// The IDE surface sends an empty string (`""`) for the per-delta finish
|
|
1536
|
+
// reason, which is not a real stop signal — only a value that maps to a
|
|
1537
|
+
// canonical stop reason (or the terminal `[DONE]` frame) ends the turn.
|
|
1538
|
+
if (fr !== null && fr !== undefined && fr !== "") finishReason = fr;
|
|
1132
1539
|
// Some providers send usage in choices[0].usage or top-level
|
|
1133
1540
|
if (choice["usage"] && typeof choice["usage"] === "object") rawUsage = choice["usage"] as Record<string, unknown>;
|
|
1134
1541
|
}
|
|
1135
|
-
// Some Qoder chunks carry finish_reason at top level
|
|
1136
|
-
|
|
1542
|
+
// Some Qoder chunks carry finish_reason at top level (empty string means
|
|
1543
|
+
// no stop signal, same as the per-delta field).
|
|
1544
|
+
if (json["finish_reason"] !== undefined && json["finish_reason"] !== "") finishReason = json["finish_reason"];
|
|
1137
1545
|
}
|
|
1138
1546
|
}
|
|
1139
1547
|
// End of stream: recover a held-back marker+JSON echo. A valid echo becomes
|
|
@@ -1168,7 +1576,15 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1168
1576
|
}
|
|
1169
1577
|
}
|
|
1170
1578
|
}
|
|
1171
|
-
//
|
|
1579
|
+
// A `[DONE]` frame from the IDE surface may be the only terminal signal (the
|
|
1580
|
+
// //-delta `finish_reason` is empty and `raw_usage` is not always sent), so
|
|
1581
|
+
// a clean end-of-stream with no finish reason is a normal stop, not a
|
|
1582
|
+
// truncation. If no terminal arrived at all, still synthesize one (prevents
|
|
1583
|
+
// truncation error).
|
|
1584
|
+
if (!started) {
|
|
1585
|
+
yield { type: "response_start", sequence_number: seq++, model } as CanonicalEvent;
|
|
1586
|
+
}
|
|
1587
|
+
if (hasTerminal && finishReason === undefined) finishReason = "stop";
|
|
1172
1588
|
const stopReason = mapQoderFinishReason(finishReason);
|
|
1173
1589
|
const _providerStop = typeof finishReason === "string" ? finishReason : undefined;
|
|
1174
1590
|
void hasTerminal;
|
|
@@ -1182,17 +1598,13 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1182
1598
|
} as CanonicalEvent;
|
|
1183
1599
|
}
|
|
1184
1600
|
|
|
1185
|
-
// Model catalog — maps
|
|
1601
|
+
// Model catalog — maps the realm's roster to Cartethyia ModelDefinition
|
|
1186
1602
|
|
|
1187
1603
|
/**
|
|
1188
1604
|
* The intl catalog this provider publishes.
|
|
1189
1605
|
*
|
|
1190
|
-
*
|
|
1191
|
-
*
|
|
1192
|
-
* cosmetic: it is sent upstream as `model_config.max_input_tokens`, so a stale
|
|
1193
|
-
* low value tells Qoder the conversation window is small and makes it behave
|
|
1194
|
-
* that way. Both the published catalog and the dispatched request come from the
|
|
1195
|
-
* realm's roster, which carries the upstream's own numbers.
|
|
1606
|
+
* The published catalog and the dispatched request both come from the realm's
|
|
1607
|
+
* roster, which carries the upstream's own numbers.
|
|
1196
1608
|
*/
|
|
1197
1609
|
export const QODER_MODELS: readonly ModelDefinition[] = QODER_INTL_MODELS;
|
|
1198
1610
|
|
|
@@ -1204,16 +1616,38 @@ interface QoderAdapterOptions {
|
|
|
1204
1616
|
readonly realm?: QoderRealm;
|
|
1205
1617
|
/** Bundled provider id, so the adapter's own id matches the route's. */
|
|
1206
1618
|
readonly providerId?: string;
|
|
1619
|
+
/**
|
|
1620
|
+
* Which chat surface to dispatch on.
|
|
1621
|
+
*
|
|
1622
|
+
* `ide` (default) uses the modern model-server surface
|
|
1623
|
+
* (`/model/v1/chat/completions`) — the wire the IDE/QoderWake actually
|
|
1624
|
+
* speaks, echoes the real model key, and carries 1M context + xhigh
|
|
1625
|
+
* reasoning. When that surface answers an entitlement refusal (HTTP 402 /
|
|
1626
|
+
* `{"code":116,"error":"quota exceeded"}`, seen on free-tier accounts),
|
|
1627
|
+
* dispatch falls back to the legacy COSY surface
|
|
1628
|
+
* (`/algo/.../agent_chat_generation`) — but only for a free-tier model
|
|
1629
|
+
* row (`is_free: true`). A billed model answering 402/116 is genuine quota
|
|
1630
|
+
* exhaustion (Qoder's daily exhaustion / cooldown run on a 1-day cycle) and
|
|
1631
|
+
* surfaces as-is, so the exhausted state is never masked by a fallback.
|
|
1632
|
+
* Live verification (2026-11-02): the modern surface answers 116 for
|
|
1633
|
+
* free-tier accounts that stream fine over COSY, while entitled accounts
|
|
1634
|
+
* stream on the modern surface with the real model echoed.
|
|
1635
|
+
*
|
|
1636
|
+
* `cosy` forces only the legacy surface (the pre-fallback behavior).
|
|
1637
|
+
*/
|
|
1638
|
+
readonly surface?: "ide" | "cosy";
|
|
1207
1639
|
}
|
|
1208
1640
|
|
|
1209
1641
|
class QoderAdapter implements ProviderAdapter {
|
|
1210
1642
|
readonly provider_id: ProviderId;
|
|
1211
1643
|
private readonly realm: QoderRealm;
|
|
1644
|
+
private readonly surface: "ide" | "cosy";
|
|
1212
1645
|
private readonly fetchFn: typeof fetch;
|
|
1213
1646
|
|
|
1214
1647
|
constructor(options: QoderAdapterOptions = {}) {
|
|
1215
1648
|
this.provider_id = (options.providerId ?? QODER_PROVIDER_ID) as ProviderId;
|
|
1216
1649
|
this.realm = options.realm ?? "intl";
|
|
1650
|
+
this.surface = options.surface ?? "ide";
|
|
1217
1651
|
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
1218
1652
|
}
|
|
1219
1653
|
|
|
@@ -1236,7 +1670,7 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
1236
1670
|
context: ProviderDispatchContext,
|
|
1237
1671
|
): AsyncIterable<CanonicalEvent> {
|
|
1238
1672
|
if (candidate.wire_family !== "chat") {
|
|
1239
|
-
|
|
1673
|
+
assertWireFamily(candidate, "chat", "qoder");
|
|
1240
1674
|
}
|
|
1241
1675
|
const secret = readCredentialSecret(context.credential, "Qoder credential is required");
|
|
1242
1676
|
|
|
@@ -1253,7 +1687,7 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
1253
1687
|
throw new GatewayError(
|
|
1254
1688
|
"model_not_found",
|
|
1255
1689
|
404,
|
|
1256
|
-
`Qoder virtual model ${requestedModelId} resolves to a roster key this realm does not serve
|
|
1690
|
+
`Qoder virtual model ${requestedModelId} resolves to a roster key this realm does not serve (request {requestId}). Try another model — this one's off the menu 📚`,
|
|
1257
1691
|
{ virtual_model: requestedModelId, target: qoderDispatchModelId(requestedModelId) },
|
|
1258
1692
|
"upstream",
|
|
1259
1693
|
);
|
|
@@ -1283,20 +1717,44 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
1283
1717
|
const auth = isQoderPat(secret)
|
|
1284
1718
|
? await exchangeQoderPat(secret, lifecycle.signal, fetcher, version, this.realm)
|
|
1285
1719
|
: qoderOAuthAuth(secret, context.credential.auth_state, context.credential.account_id);
|
|
1286
|
-
const qoderBody = buildQoderRequest(modelId, request, modelConfig, auth, this.realm);
|
|
1287
1720
|
// A full URL from the route wins, but the fallback is this realm's
|
|
1288
1721
|
// gateway — not the intl host hardcoded into `MODERN_PROFILE`. Dispatching
|
|
1289
1722
|
// a CN credential to `*.qoder.sh` is refused outright. The host order
|
|
1290
1723
|
// follows the token kind: job tokens (`jt-...`) belong on `api2`, which
|
|
1291
1724
|
// is the only intl host that serves them (`api3` answers "Login expired").
|
|
1292
|
-
const url =
|
|
1293
|
-
candidate.endpoint_path && candidate.endpoint_path.startsWith("http")
|
|
1294
|
-
? candidate.endpoint_path
|
|
1295
|
-
: `${qoderInferenceCandidates(this.realm, auth.securityOauthToken)[0] ?? ""}${QODER_CHAT_PATH}`;
|
|
1296
1725
|
|
|
1297
|
-
|
|
1726
|
+
// The modern `ide` surface streams immediately, needs no COSY signing,
|
|
1727
|
+
// and echoes the real model key — it is the default. Fall back to the
|
|
1728
|
+
// legacy COSY surface only when BOTH hold: the modern surface refused on
|
|
1729
|
+
// entitlement grounds (HTTP 402 / code 116) AND the dispatched model is a
|
|
1730
|
+
// free-tier row (`is_free: true`). For a free-tier model this is the
|
|
1731
|
+
// entitlement refusal the legacy surface serves with the same credential;
|
|
1732
|
+
// for a billed model the same code is genuine quota exhaustion (Qoder's
|
|
1733
|
+
// daily exhaustion / cooldown run on a 1-day cycle), so it must surface
|
|
1734
|
+
// as-is — falling back would hide the exhausted state and starve the
|
|
1735
|
+
// quota/cooldown signal. A 401/403/model error never triggers fallback.
|
|
1736
|
+
const ideUrl = candidate.endpoint_path?.startsWith("http") ? candidate.endpoint_path : qoderIdeChatUrl(this.realm);
|
|
1737
|
+
const cosyUrl = candidate.endpoint_path?.startsWith("http")
|
|
1738
|
+
? candidate.endpoint_path
|
|
1739
|
+
: `${qoderInferenceCandidates(this.realm, auth.securityOauthToken)[0] ?? ""}${QODER_CHAT_PATH}`;
|
|
1740
|
+
const ideRequest = () =>
|
|
1741
|
+
callQoderIde(ideUrl, buildQoderIdeRequest(modelId, request, modelConfig, auth, this.realm, version), auth, lifecycle.signal, fetcher, version);
|
|
1742
|
+
const cosyRequest = () =>
|
|
1743
|
+
callQoder(cosyUrl, buildQoderRequest(modelId, request, modelConfig, auth, this.realm), modelId, auth, lifecycle.signal, fetcher, version);
|
|
1744
|
+
let response =
|
|
1745
|
+
this.surface === "ide"
|
|
1746
|
+
? await fetchQoderIdeWithEmptyRecovery(ideRequest, lifecycle.signal)
|
|
1747
|
+
: await cosyRequest();
|
|
1748
|
+
if (
|
|
1749
|
+
!response.ok &&
|
|
1750
|
+
this.surface === "ide" &&
|
|
1751
|
+
qoderIdeEntitlementRefused(response) &&
|
|
1752
|
+
modelConfig.is_free === true
|
|
1753
|
+
) {
|
|
1754
|
+
response = await cosyRequest();
|
|
1755
|
+
}
|
|
1298
1756
|
if (!response.ok) throw await mapUpstreamHttpError(response, "qoder");
|
|
1299
|
-
if (!response.body) throw new GatewayError("platform_unavailable", 502, "Qoder returned empty body", {}, "upstream");
|
|
1757
|
+
if (!response.body) throw new GatewayError("platform_unavailable", 502, "Qoder returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
1300
1758
|
// Headers arrived: the pre-stream deadline has served its purpose.
|
|
1301
1759
|
// From here the gateway stall/first-chunk watchdog (propagated via
|
|
1302
1760
|
// context.abort_signal) governs the body. Keeping this timer armed
|