cartethyia 2.7.4 → 2.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -0
- package/README.md +18 -6
- package/dist/dashboard/assets/BannedPage-B1N603cM.js +1 -0
- package/dist/dashboard/assets/BannedPage-B1N603cM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js +3 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js +1 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js +899 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js +1 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-C6WlpU5E.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-DxjvUzSD.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-DxjvUzSD.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js +1 -0
- package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js +1 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-DRPavCdU.js} +4 -4
- package/dist/dashboard/assets/StudioPage-DRPavCdU.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
- package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B7M7LvMM.js +56 -0
- package/dist/dashboard/assets/UsagePage-B7M7LvMM.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
- package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
- package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
- package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js +1 -0
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-BaX2GTrn.js} +1 -1
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DympBNq4.js → banner-DFH4Bkte.js} +1 -1
- package/dist/dashboard/assets/banner-DFH4Bkte.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-ibRWU_uT.js → card-CxDw0lK6.js} +1 -1
- package/dist/dashboard/assets/card-CxDw0lK6.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/circle-question-mark-Cz4un8sH.js +1 -0
- package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +1 -0
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-DfS1ntiq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-QqPul6sC.js} +1 -1
- package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js +4 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-u7JPB5BX.js} +1 -1
- package/dist/dashboard/assets/formatters-u7JPB5BX.js.gz +0 -0
- package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
- package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
- package/dist/dashboard/assets/{index-DBNPOfhV.js → index-Ko4dLRnV.js} +2 -2
- package/dist/dashboard/assets/index-Ko4dLRnV.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-CHJQwxLa.js} +1 -1
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BlmlizVV.js} +1 -1
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-Bz93I-yE.js} +1 -1
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Ml16KCfu.js} +1 -1
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js +1 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js +1 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js +1 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
- package/dist/dashboard/assets/providers/zai.webp +0 -0
- package/dist/dashboard/assets/providers/zcode.webp +0 -0
- package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
- package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js +1 -0
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BHqpd-bg.js} +2 -2
- package/dist/dashboard/assets/select-BHqpd-bg.js.gz +0 -0
- package/dist/dashboard/assets/settings-BxslsErn.js +1 -0
- package/dist/dashboard/assets/settings-BxslsErn.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-DfgnFDWY.js → stack-j7cXjgYX.js} +1 -1
- package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/{state-CkfdYWel.js → state-DHmEnLa2.js} +1 -1
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/{switch-SYm237HG.js → switch-DmZcyTIC.js} +1 -1
- package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-CbCP4w1o.js} +1 -1
- package/dist/dashboard/assets/useMutation-CbCP4w1o.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 +60 -59
- 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/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -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/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
- package/package.json +4 -6
- package/scripts/cli/update.ts +15 -41
- package/src/boot/app.ts +58 -2
- package/src/boot/dependencies.ts +794 -732
- package/src/boot/lifecycle.ts +11 -12
- package/src/config/cooldowns.ts +34 -55
- package/src/config/network.ts +1 -1
- package/src/config/package-paths.ts +1 -1
- package/src/config/providers.ts +19 -14
- package/src/config/resources.ts +0 -5
- package/src/config/runtime.ts +2 -5
- package/src/config/spec.ts +33 -6
- package/src/config/usage.ts +27 -0
- package/src/console/api-keys/ban-constants.ts +38 -0
- package/src/console/api-keys/contracts.ts +71 -62
- package/src/console/api-keys/routes.ts +56 -24
- package/src/console/auth/access.ts +4 -4
- package/src/console/auth/account-reset.ts +1 -1
- 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 +37 -31
- 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 +9 -9
- package/src/console/backup/export-endpoint.ts +5 -6
- package/src/console/backup/nine-router.ts +1 -7
- package/src/console/backup/routes.ts +29 -33
- 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 +43 -299
- package/src/console/cli-tools/fs-ops.ts +29 -0
- package/src/console/cli-tools/injectors/claude.ts +3 -3
- package/src/console/cli-tools/injectors/codex.ts +181 -181
- package/src/console/cli-tools/injectors/driver.ts +150 -247
- package/src/console/cli-tools/injectors/kilo.ts +146 -21
- package/src/console/cli-tools/injectors/opencode.ts +176 -153
- package/src/console/cli-tools/routes.ts +7 -1
- package/src/console/cli-tools/service.ts +8 -7
- package/src/console/cli-tools/store.ts +2 -2
- package/src/console/console-router.ts +32 -0
- package/src/console/dashboard-assets.ts +9 -9
- package/src/console/domain-registration.ts +16 -2
- 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 +7 -1
- package/src/console/observability/live.ts +31 -18
- package/src/console/observability/logs.ts +1 -1
- package/src/console/observability/sse.ts +62 -8
- package/src/console/observability/store.ts +23 -4
- package/src/console/providers/catalog/catalog-projections.ts +1 -1
- package/src/console/providers/catalog/contracts.ts +39 -0
- package/src/console/providers/catalog/model-operations.ts +1 -1
- package/src/console/providers/catalog/provider-operations.ts +95 -17
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +27 -3
- package/src/console/providers/catalog/store.ts +147 -8
- package/src/console/providers/detail/routes.ts +1 -1
- package/src/console/providers/oauth/callback-listener.ts +100 -18
- package/src/console/providers/oauth/routes.ts +92 -13
- 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 +259 -66
- 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/pools/contracts.ts +2 -2
- package/src/console/routing/pools/import-parse.ts +3 -3
- package/src/console/routing/pools/relay-deploy.ts +3 -3
- package/src/console/settings/contracts.ts +29 -2
- package/src/console/settings/store.ts +20 -0
- package/src/console/share/share-router.ts +100 -32
- package/src/console/share/share-stats.ts +11 -13
- 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 +4 -4
- 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 +252 -97
- 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 +1 -1
- package/src/network/pool/agent.ts +7 -6
- package/src/network/pool/byte-accounting.ts +1 -1
- package/src/network/pool/resolver.ts +16 -12
- package/src/network/pool/selector.ts +150 -136
- package/src/network/pool-health-machine.ts +1 -1
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/retry.ts +1 -1
- package/src/network/ssrf.ts +20 -71
- package/src/observability/event-log.ts +9 -2
- package/src/observability/event-query.ts +61 -291
- 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 +19 -3
- package/src/observability/telemetry-reconciliation.ts +3 -3
- 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 +3 -3
- package/src/persistence/dialect.ts +5 -1
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/redis.ts +1 -1
- package/src/persistence/schema.ts +121 -40
- package/src/persistence/shared-cache.ts +2 -2
- package/src/persistence/sort-order.ts +1 -1
- package/src/persistence/stores/api-key-store.ts +29 -2
- package/src/persistence/stores/share-store.ts +20 -8
- 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 +6 -10
- package/src/protocol/dsml.ts +8 -125
- package/src/protocol/image-clamp.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 +25 -10
- package/src/protocol/response/codex.ts +1 -1
- package/src/protocol/response/messages.ts +1 -1
- package/src/protocol/response/omp-tool-call-markup.ts +1 -1
- package/src/protocol/response/responses.ts +1 -1
- package/src/protocol/sse-decoder.ts +50 -1
- 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 +6 -6
- 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/providers/adapters/compatible-adapter.ts +71 -4
- 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/bundled-discovery-providers.ts +6 -30
- package/src/providers/configured-openai-providers.ts +82 -97
- package/src/providers/default-registry.ts +35 -37
- 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 +48 -13
- package/src/providers/discovery/probing-service.ts +6 -8
- package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
- package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +88 -28
- package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
- package/src/providers/integrations/antigravity/antigravity.ts +22 -13
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- 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-identity.ts +86 -0
- 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 +4 -15
- package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
- package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
- package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
- package/src/providers/integrations/claude/claude-cch.ts +148 -148
- package/src/providers/integrations/claude/fallback-credit.ts +3 -7
- package/src/providers/integrations/cline/cline.ts +1 -1
- package/src/providers/integrations/codex/codex-device-code.ts +101 -101
- package/src/providers/integrations/codex/codex-oauth.ts +0 -1
- package/src/providers/integrations/commandcode/commandcode.ts +75 -115
- package/src/providers/integrations/commandcode/quota.ts +131 -51
- package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
- 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 +35 -3
- 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 +9 -9
- package/src/providers/integrations/github/github-discovery.ts +70 -12
- package/src/providers/integrations/github/github-oauth.ts +184 -14
- package/src/providers/integrations/github/github.ts +134 -26
- 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 +86 -36
- package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
- 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 +80 -68
- package/src/providers/integrations/muse/muse-oauth.ts +0 -1
- package/src/providers/integrations/muse/muse-quota.ts +0 -4
- package/src/providers/integrations/opencode/fingerprint.ts +7 -5
- package/src/providers/integrations/opencode/provider.ts +17 -24
- package/src/providers/integrations/qoder/qoder-catalog.ts +94 -11
- 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 +10 -31
- package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
- package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
- package/src/providers/integrations/qoder/qoder.ts +422 -71
- 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/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 +51 -8
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- 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 +198 -133
- package/src/providers/operations/account-health-service.ts +31 -39
- package/src/providers/operations/account-reset-service.ts +58 -1
- package/src/providers/operations/byok-wire-profile.ts +19 -15
- 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 +18 -2
- package/src/providers/operations/provider-credential-service.ts +19 -2
- package/src/providers/provider-contracts.ts +18 -7
- 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-usage.ts +50 -19
- package/src/providers/quota/cache.ts +29 -0
- package/src/providers/quota/quota-contracts.ts +4 -0
- package/src/providers/quota/refresh.ts +27 -4
- package/src/providers/quota/snapshot.ts +94 -0
- package/src/providers/search/search-provider.ts +1 -3
- package/src/providers/search/search-providers.ts +2 -1
- package/src/security/abuse.ts +4 -4
- package/src/security/admission/service.ts +13 -6
- package/src/security/admission/ttl.ts +61 -61
- package/src/security/api-key-auth.ts +17 -3
- 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/invoke-guard.ts +47 -0
- package/src/transport/contracts.ts +13 -4
- package/src/transport/dispatch/attempt-finalize.ts +70 -22
- package/src/transport/dispatch/attempt-loop.ts +22 -0
- package/src/transport/dispatch/capture-policy.ts +34 -37
- package/src/transport/dispatch/empty-stream.ts +23 -20
- package/src/transport/dispatch/generation.ts +25 -45
- package/src/transport/dispatch/native-shared.ts +127 -0
- package/src/transport/dispatch/proxy-request.ts +3 -1
- package/src/transport/dispatch/responses-compact.ts +25 -46
- package/src/transport/dispatch/streaming/encoder.ts +18 -72
- package/src/transport/dispatch/streaming/finalize.ts +2 -2
- 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 +20 -48
- package/src/transport/dispatch/token-count.ts +41 -38
- package/src/transport/dispatch/websearch.ts +19 -45
- package/src/transport/drain-endpoint.ts +1 -1
- package/src/transport/failure-policy.ts +21 -1
- package/src/transport/guards/auth-guard.ts +0 -6
- package/src/transport/guards/body-policy.ts +3 -3
- package/src/transport/guards/error-lifecycle.ts +4 -0
- package/src/transport/guards/headroom-guard.ts +1 -1
- package/src/transport/guards/pipeline.ts +1 -1
- package/src/transport/guards/request-context.ts +12 -17
- package/src/transport/request/preparer.ts +286 -7
- 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 +7 -13
- 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 +17 -18
- package/src/transport/routing/route-model.ts +126 -33
- package/src/transport/routing/router.ts +159 -37
- package/src/transport/shutdown-notice.ts +2 -2
- 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 +2 -2
- package/src/transport/surface/content-parts.ts +1 -1
- package/src/transport/surface/dialects.ts +2 -2
- package/src/transport/surface/messages/parse.ts +3 -3
- package/src/transport/surface/reasoning-summary.ts +9 -14
- 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 +431 -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-ChG5svFb.js +0 -1
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js +0 -1
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.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-BSDbquc1.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js +0 -1
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
- package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
- package/dist/dashboard/assets/app-BayTTy01.js +0 -9
- package/dist/dashboard/assets/app-BayTTy01.js.gz +0 -0
- package/dist/dashboard/assets/app-a159k0ub.css +0 -2
- package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
- package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
- package/dist/dashboard/assets/dialog-8fiE6tec.js +0 -1
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSAsYsmV.js +0 -1
- package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
- package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
- package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-CPK47xQx.js +0 -1
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
- package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
- package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
- package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
- package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
- package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
- package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/state-CkfdYWel.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-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
- package/src/console/cli-tools/injectors/cline.ts +0 -110
- package/src/console/cli-tools/injectors/copilot.ts +0 -108
- package/src/console/cli-tools/injectors/cowork.ts +0 -64
- package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
- package/src/console/cli-tools/injectors/droid.ts +0 -96
- package/src/console/cli-tools/injectors/grok-build.ts +0 -70
- package/src/console/cli-tools/injectors/hermes.ts +0 -101
- package/src/console/cli-tools/injectors/jcode.ts +0 -115
- package/src/console/cli-tools/injectors/openclaw.ts +0 -161
- 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/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,14 +22,14 @@ 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
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 { qoderMachineOs, qoderMachineHostname } from "../../operations/cli-platform";
|
|
32
33
|
import { qoderIdeChatUrl, qoderInferenceCandidates, QODER_CHAT_PATH, QODER_REALMS, type QoderRealm } from "./qoder-realm";
|
|
33
34
|
import {
|
|
34
35
|
QODER_CN_MODEL_CONFIGS,
|
|
@@ -117,26 +118,6 @@ export const MODERN_PROFILE: QoderModeProfile = {
|
|
|
117
118
|
|
|
118
119
|
const QODER_PROVIDER_ID = "qoder" as const;
|
|
119
120
|
|
|
120
|
-
export const QODER_MODEL_CONFIGS: Record<
|
|
121
|
-
string,
|
|
122
|
-
{ id: string; display_name: string; max_input_tokens: number; max_output_tokens?: number; is_vl: boolean; is_reasoning: boolean; source?: string }
|
|
123
|
-
> = {
|
|
124
|
-
auto: { id: "auto", display_name: "Auto", max_input_tokens: 180000, is_vl: true, is_reasoning: false },
|
|
125
|
-
ultimate: { id: "ultimate", display_name: "Ultimate", max_input_tokens: 180000, is_vl: true, is_reasoning: true },
|
|
126
|
-
performance: { id: "performance", display_name: "Performance", max_input_tokens: 272000, is_vl: true, is_reasoning: false },
|
|
127
|
-
efficient: { id: "efficient", display_name: "Efficient", max_input_tokens: 180000, is_vl: true, is_reasoning: false },
|
|
128
|
-
lite: { id: "lite", display_name: "Lite", max_input_tokens: 180000, is_vl: false, is_reasoning: true },
|
|
129
|
-
qmodel: { id: "qmodel", display_name: "Qwen 3.6 Plus", max_input_tokens: 180000, is_vl: true, is_reasoning: false },
|
|
130
|
-
qmodel_latest: { id: "qmodel_latest", display_name: "Qwen 3.7 Max", max_input_tokens: 1000000, is_vl: true, is_reasoning: false },
|
|
131
|
-
qmodel_preview: { id: "qmodel_preview", display_name: "Qwen 3.8 Max", max_input_tokens: 1000000, is_vl: true, is_reasoning: false },
|
|
132
|
-
dmodel: { id: "dmodel", display_name: "DeepSeek V4 Pro", max_input_tokens: 180000, is_vl: true, is_reasoning: true },
|
|
133
|
-
dfmodel: { id: "dfmodel", display_name: "DeepSeek V4 Flash", max_input_tokens: 180000, is_vl: true, is_reasoning: true },
|
|
134
|
-
gm51model: { id: "gm51model", display_name: "GLM 5.1", max_input_tokens: 180000, is_vl: true, is_reasoning: true },
|
|
135
|
-
kmodel: { id: "kmodel", display_name: "Kimi K2.6", max_input_tokens: 256000, is_vl: true, is_reasoning: false },
|
|
136
|
-
mmodel: { id: "mmodel", display_name: "MiniMax M2.7", max_input_tokens: 180000, is_vl: true, is_reasoning: false },
|
|
137
|
-
kmodel_latest: { id: "kmodel_latest", display_name: "Kimi K2.7 Latest", max_input_tokens: 256000, is_vl: true, is_reasoning: false },
|
|
138
|
-
};
|
|
139
|
-
|
|
140
121
|
const QODER_APPCODE = "cosy";
|
|
141
122
|
const QODER_SIG_SECRET = "d2FyLCB3YXIgbmV2ZXIgY2hhbmdlcw==";
|
|
142
123
|
/**
|
|
@@ -182,7 +163,19 @@ interface QoderModelConfig {
|
|
|
182
163
|
max_output_tokens?: number;
|
|
183
164
|
is_vl: boolean;
|
|
184
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;
|
|
185
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;
|
|
186
179
|
}
|
|
187
180
|
|
|
188
181
|
function qoderMd5(value: string | Uint8Array): string {
|
|
@@ -456,12 +449,29 @@ function qoderEnvelopeMessage(rec: Record<string, unknown>): string {
|
|
|
456
449
|
return extractUpstreamMessage(rec);
|
|
457
450
|
}
|
|
458
451
|
|
|
459
|
-
function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data: string }> {
|
|
452
|
+
export function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data: string }> {
|
|
460
453
|
if (data === "[DONE]") return [{ event: null, data: "[DONE]" }];
|
|
461
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;
|
|
462
463
|
try {
|
|
463
464
|
envelope = JSON.parse(data);
|
|
464
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
|
+
}
|
|
465
475
|
}
|
|
466
476
|
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return [];
|
|
467
477
|
const rec = envelope as Record<string, unknown>;
|
|
@@ -505,11 +515,61 @@ function qoderEnvelopeToFrames(data: string): Array<{ event: string | null; data
|
|
|
505
515
|
if (body === "[DONE]") return [{ event: null, data: "[DONE]" }];
|
|
506
516
|
if (typeof body === "string") return [{ event: null, data: body.replace(/\r?\n/g, "") }];
|
|
507
517
|
// Direct OpenAI chunk (IDE surface): no envelope body, but a `choices` array
|
|
508
|
-
// is present. Pass the raw data through so the chunk parser sees
|
|
509
|
-
|
|
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 }];
|
|
510
521
|
return [];
|
|
511
522
|
}
|
|
512
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;
|
|
571
|
+
}
|
|
572
|
+
|
|
513
573
|
function qoderStableHash(prefix: string, ...parts: string[]): string {
|
|
514
574
|
const h = createHash("sha256");
|
|
515
575
|
h.update(prefix);
|
|
@@ -563,13 +623,23 @@ function qoderMaxContextFor(modelId: string, modelConfig: QoderModelConfig, real
|
|
|
563
623
|
return modelConfig.max_input_tokens;
|
|
564
624
|
}
|
|
565
625
|
|
|
566
|
-
function qoderRequestedMaxTokens(
|
|
626
|
+
function qoderRequestedMaxTokens(
|
|
627
|
+
request: CanonicalRequest,
|
|
628
|
+
modelConfig: QoderModelConfig,
|
|
629
|
+
realm: QoderRealm,
|
|
630
|
+
): number {
|
|
567
631
|
/**
|
|
568
632
|
* The ceiling is the roster's stated one. A fixed 32k fallback would clamp
|
|
569
633
|
* every model alike, so a request asking for more would be silently lowered
|
|
570
|
-
* 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.
|
|
571
638
|
*/
|
|
572
|
-
const configured =
|
|
639
|
+
const configured =
|
|
640
|
+
typeof modelConfig.max_output_tokens === "number"
|
|
641
|
+
? modelConfig.max_output_tokens
|
|
642
|
+
: qoderRosterMaxOutput(modelConfig.id, realm) ?? 65_536;
|
|
573
643
|
const controls = request.generation_controls as Record<string, unknown>;
|
|
574
644
|
const requested =
|
|
575
645
|
typeof controls["max_tokens"] === "number"
|
|
@@ -580,6 +650,17 @@ function qoderRequestedMaxTokens(request: CanonicalRequest, modelConfig: QoderMo
|
|
|
580
650
|
return typeof requested === "number" && requested > 0 ? Math.min(requested, configured) : configured;
|
|
581
651
|
}
|
|
582
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
|
+
|
|
583
664
|
/**
|
|
584
665
|
* Whether this model needs the prior turn's reasoning echoed back.
|
|
585
666
|
*
|
|
@@ -908,16 +989,40 @@ export function buildQoderRequest(
|
|
|
908
989
|
}
|
|
909
990
|
const latestUserMessage = [...qoderMessages].reverse().find((m) => m["role"] === "user");
|
|
910
991
|
const latestUserText = typeof latestUserMessage?.["content"] === "string" ? (latestUserMessage["content"] as string) : "";
|
|
911
|
-
const maxTokens = qoderRequestedMaxTokens(request, modelConfig);
|
|
992
|
+
const maxTokens = qoderRequestedMaxTokens(request, modelConfig, realm);
|
|
912
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
|
+
//
|
|
1002
|
+
// Only a reasoning-capable model carries the field. A non-reasoning model
|
|
1003
|
+
// (e.g. `lite`) cannot think, so an effort is meaningless there; sending it
|
|
1004
|
+
// would also assert a capability the model does not advertise. The upstream
|
|
1005
|
+
// tolerates it today, but declaring reasoning on a model that has none is a
|
|
1006
|
+
// latent contract violation, so the field is kept off non-reasoning models.
|
|
1007
|
+
const reasoningEffort =
|
|
1008
|
+
reasoning
|
|
1009
|
+
? (reasoningEffortFromIntent(request.reasoning) ?? modelConfig.default_reasoning_effort)
|
|
1010
|
+
: undefined;
|
|
913
1011
|
const sessionId = qoderStableHash("qoder-session", auth.userId, modelId);
|
|
914
1012
|
const recordId = qoderStableRecordId(modelId, allMessages, maxTokens);
|
|
915
1013
|
return {
|
|
1014
|
+
// 1:1 with the IDE worker's `FTn` legacy request builder, which always
|
|
1015
|
+
// sends a top-level `model` equal to the resolved roster key (falling
|
|
1016
|
+
// back to "auto" only when no key is known). The upstream honours
|
|
1017
|
+
// `model_config.key` for actual dispatch and echoes a fixed "auto" in
|
|
1018
|
+
// stream chunks, but the field is part of the wire shape the IDE sends.
|
|
1019
|
+
model: modelId,
|
|
916
1020
|
request_id: randomUUID(),
|
|
917
1021
|
request_set_id: recordId,
|
|
918
1022
|
chat_record_id: recordId,
|
|
919
1023
|
session_id: sessionId,
|
|
920
1024
|
stream: true,
|
|
1025
|
+
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
921
1026
|
...(MODERN_PROFILE.emptyAliyunUserType ? { aliyun_user_type: "" } : {}),
|
|
922
1027
|
chat_task: "FREE_INPUT",
|
|
923
1028
|
is_reply: true,
|
|
@@ -957,8 +1062,9 @@ export function buildQoderRequest(
|
|
|
957
1062
|
max_input_tokens: qoderMaxContextFor(modelId, modelConfig, realm),
|
|
958
1063
|
// Mirrors the roster's stated ceiling. A fixed 32k fallback would be sent
|
|
959
1064
|
// for every model alike, so the request would declare a cap the catalog
|
|
960
|
-
// does not publish.
|
|
961
|
-
|
|
1065
|
+
// does not publish. A discovered model's config carries no output limit,
|
|
1066
|
+
// so consult the roster for the key before the 65_536 last resort.
|
|
1067
|
+
max_output_tokens: modelConfig.max_output_tokens ?? qoderRosterMaxOutput(modelId, realm) ?? 65_536,
|
|
962
1068
|
source: modelConfig.source ?? "system",
|
|
963
1069
|
},
|
|
964
1070
|
messages: qoderMessages,
|
|
@@ -975,6 +1081,17 @@ export function buildQoderRequest(
|
|
|
975
1081
|
const choice = qoderToolChoice(request.tool_choice);
|
|
976
1082
|
return choice === undefined ? {} : { tool_choice: choice };
|
|
977
1083
|
})(),
|
|
1084
|
+
// Passthrough knobs the IDE surface reads from `parameters` (worker `vyl`):
|
|
1085
|
+
// parallel_tool_calls, tool_choice, top_p, seed, user, response_format, …
|
|
1086
|
+
// Only include what the caller actually set.
|
|
1087
|
+
parameters: (() => {
|
|
1088
|
+
const out: Record<string, unknown> = {};
|
|
1089
|
+
const choice = qoderToolChoice(request.tool_choice);
|
|
1090
|
+
if (choice !== undefined) out["tool_choice"] = choice;
|
|
1091
|
+
const parallel = request.generation_controls.parallel_tool_calls;
|
|
1092
|
+
if (parallel !== undefined) out["parallel_tool_calls"] = parallel;
|
|
1093
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
1094
|
+
})(),
|
|
978
1095
|
};
|
|
979
1096
|
}
|
|
980
1097
|
|
|
@@ -998,11 +1115,31 @@ export function buildQoderIdeRequest(
|
|
|
998
1115
|
): Record<string, unknown> {
|
|
999
1116
|
const legacy = buildQoderRequest(modelId, request, modelConfig, auth, realm);
|
|
1000
1117
|
const messages = legacy.messages as Array<Record<string, unknown>>;
|
|
1118
|
+
// The IDE surface (`model/v1/chat/completions`) must ship the same tool
|
|
1119
|
+
// declaration as the legacy builder. The unpacked desktop client (worker
|
|
1120
|
+
// `PFn`) injects `tools` directly when present — without it the upstream
|
|
1121
|
+
// sees no toolset and answers with a DSML text block (or claims no tool
|
|
1122
|
+
// exists) instead of a structured `tool_calls`. The remaining knobs
|
|
1123
|
+
// (`parallel_tool_calls`, `tool_choice`, …) ride the `parameters`
|
|
1124
|
+
// passthrough (worker `vyl`), matching the reference exactly.
|
|
1125
|
+
const tools = legacy.tools as Array<Record<string, unknown>> | undefined;
|
|
1001
1126
|
return {
|
|
1002
1127
|
model: modelId,
|
|
1003
1128
|
messages,
|
|
1004
1129
|
stream: true,
|
|
1005
1130
|
stream_options: { include_usage: true },
|
|
1131
|
+
...(tools !== undefined && tools.length > 0 ? { tools } : {}),
|
|
1132
|
+
...(legacy.parameters === undefined ? {} : { parameters: legacy.parameters }),
|
|
1133
|
+
// The same reasoning-effort field the legacy surface uses; without it the
|
|
1134
|
+
// IDE surface streams no thinking for models that need the push (Sonus,
|
|
1135
|
+
// Cantus). Undefined when the caller asked for none and the roster has no
|
|
1136
|
+
// default, matching the legacy builder.
|
|
1137
|
+
...(legacy.reasoningEffort === undefined ? {} : { reasoningEffort: legacy.reasoningEffort }),
|
|
1138
|
+
// The full wire config (model key, window, output ceiling, tier) the legacy
|
|
1139
|
+
// surface sends. The IDE surface reads the same block, so shipping it keeps
|
|
1140
|
+
// the request the same shape for every Qoder model — including lite's 1M
|
|
1141
|
+
// window and 64k output cap.
|
|
1142
|
+
...(legacy.model_config === undefined ? {} : { model_config: legacy.model_config }),
|
|
1006
1143
|
metadata: {
|
|
1007
1144
|
context: {
|
|
1008
1145
|
request_id: legacy.request_id,
|
|
@@ -1023,7 +1160,16 @@ export function buildQoderIdeRequest(
|
|
|
1023
1160
|
};
|
|
1024
1161
|
}
|
|
1025
1162
|
|
|
1026
|
-
/**
|
|
1163
|
+
/**
|
|
1164
|
+
* Identity headers for the modern model-server surface (plain bearer).
|
|
1165
|
+
*
|
|
1166
|
+
* Mirrors the desktop IDE's `openResponse` exactly: the IDE sends the five
|
|
1167
|
+
* OpenAI-surface headers (Authorization, Content-Type, Accept, X-Request-ID,
|
|
1168
|
+
* X-Session-ID) and the global client-identity interceptor adds Cosy-Version,
|
|
1169
|
+
* Cosy-ClientType, Cosy-MachineOS, and Cosy-MachineHostname (for infer-sse
|
|
1170
|
+
* requests). It does NOT send Cosy-MachineId / Cosy-MachineToken /
|
|
1171
|
+
* Cosy-MachineType on this surface, so neither do we.
|
|
1172
|
+
*/
|
|
1027
1173
|
function qoderIdeHeaders(auth: QoderAuth, version: string): Record<string, string> {
|
|
1028
1174
|
return {
|
|
1029
1175
|
Authorization: `Bearer ${auth.securityOauthToken}`,
|
|
@@ -1034,10 +1180,7 @@ function qoderIdeHeaders(auth: QoderAuth, version: string): Record<string, strin
|
|
|
1034
1180
|
"Cosy-Version": version,
|
|
1035
1181
|
"Cosy-ClientType": "5",
|
|
1036
1182
|
"Cosy-MachineOS": qoderMachineOs(),
|
|
1037
|
-
"Cosy-MachineHostname":
|
|
1038
|
-
"Cosy-MachineId": auth.machineId,
|
|
1039
|
-
"Cosy-MachineToken": auth.machineId,
|
|
1040
|
-
"Cosy-MachineType": "5",
|
|
1183
|
+
"Cosy-MachineHostname": qoderMachineHostname(),
|
|
1041
1184
|
};
|
|
1042
1185
|
}
|
|
1043
1186
|
|
|
@@ -1057,6 +1200,135 @@ async function callQoderIde(
|
|
|
1057
1200
|
});
|
|
1058
1201
|
}
|
|
1059
1202
|
|
|
1203
|
+
/**
|
|
1204
|
+
* True when the modern model-server surface refused the request on entitlement
|
|
1205
|
+
* grounds. This only detects the refusal — whether a legacy fallback is worth
|
|
1206
|
+
* trying is the caller's decision, and it is gated on the model being free-tier
|
|
1207
|
+
* (see `dispatch`).
|
|
1208
|
+
*
|
|
1209
|
+
* The upstream's refusal envelope is `{"code":116,"error":"quota exceeded"}`
|
|
1210
|
+
* on HTTP 402. The IDE's own error set classifies 116 as a quota/entitlement
|
|
1211
|
+
* failure (not an auth error), and the gRPC surface reports the same code as
|
|
1212
|
+
* status 13. For a free-tier model this is an entitlement refusal the legacy
|
|
1213
|
+
* COSY surface will serve with the same credential; for a billed model the
|
|
1214
|
+
* same code is genuine quota exhaustion (Qoder's daily exhaustion and cooldown
|
|
1215
|
+
* run on a 1-day cycle) and must surface as-is so the quota/cooldown path can
|
|
1216
|
+
* see it. A 401/403 or a model-level error is never fallback-class either way.
|
|
1217
|
+
*/
|
|
1218
|
+
function qoderIdeEntitlementRefused(response: Response): boolean {
|
|
1219
|
+
// Status alone is the signal; the body is left intact for
|
|
1220
|
+
// `mapUpstreamHttpError` to surface if the fallback also fails.
|
|
1221
|
+
return response.status === 402;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Qoder upstream streams are flaky: the IDE surface sometimes answers a full
|
|
1226
|
+
* normal stop with reasoning only and no output text/tool calls (observed ~1
|
|
1227
|
+
* in 4 runs with Sonus). That is not a valid completion — the lab would show a
|
|
1228
|
+
* thought with "No text came back". Replay such a stream before committing to
|
|
1229
|
+
* it. `sniffQoderStream` below inspects just enough of the body to make that
|
|
1230
|
+
* call without buffering the whole (possibly long) stream.
|
|
1231
|
+
*/
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Reads the upstream body just far enough to decide whether it carries real
|
|
1235
|
+
* output, then returns a replay stream that re-emits the already-read bytes
|
|
1236
|
+
* followed by the rest of the original body. If the body ends before any
|
|
1237
|
+
* meaningful output is seen, `meaningful` is false and the caller replays the
|
|
1238
|
+
* whole request (empty / thought-only recovery) without ever buffering a large
|
|
1239
|
+
* response: the original `drainQoderResponse` read the entire stream before
|
|
1240
|
+
* forwarding, which made every stream appear to complete in one burst at the
|
|
1241
|
+
* end.
|
|
1242
|
+
*/
|
|
1243
|
+
async function sniffQoderStream(
|
|
1244
|
+
response: Response,
|
|
1245
|
+
signal: AbortSignal,
|
|
1246
|
+
): Promise<{ meaningful: boolean; response: Response }> {
|
|
1247
|
+
const body = response.body;
|
|
1248
|
+
if (body === null) return { meaningful: true, response };
|
|
1249
|
+
const reader = body.getReader();
|
|
1250
|
+
const decoder = new TextDecoder();
|
|
1251
|
+
const pending: Uint8Array[] = [];
|
|
1252
|
+
let inspected = "";
|
|
1253
|
+
let meaningful = false;
|
|
1254
|
+
for (let i = 0; i < 32 && !meaningful; i += 1) {
|
|
1255
|
+
const { done, value } = await reader.read();
|
|
1256
|
+
if (signal.aborted) break;
|
|
1257
|
+
if (done) break;
|
|
1258
|
+
pending.push(value);
|
|
1259
|
+
inspected += decoder.decode(value, { stream: true });
|
|
1260
|
+
// Peek at completed SSE frames only: a content delta or a tool-call
|
|
1261
|
+
// fragment means real output is coming. Reasoning-only streams are NOT
|
|
1262
|
+
// meaningful (they are exactly what empty-stream recovery replays), and a
|
|
1263
|
+
// finish reason alone is not output either.
|
|
1264
|
+
if (/data:/.test(inspected)) {
|
|
1265
|
+
for (const frame of inspected.split(/\r?\n\r?\n/)) {
|
|
1266
|
+
if (!frame.includes("data:")) continue;
|
|
1267
|
+
if (frame.includes('"content":"') || frame.includes('"tool_calls"')) {
|
|
1268
|
+
meaningful = true;
|
|
1269
|
+
break;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (inspected.length > 65536) {
|
|
1274
|
+
meaningful = true;
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
// Replay: previously-read bytes first, then continue the original body.
|
|
1279
|
+
let offset = 0;
|
|
1280
|
+
const replay = new ReadableStream<Uint8Array>({
|
|
1281
|
+
start(controller) {
|
|
1282
|
+
for (; offset < pending.length; offset += 1) controller.enqueue(pending[offset]);
|
|
1283
|
+
},
|
|
1284
|
+
async pull(controller) {
|
|
1285
|
+
if (offset < pending.length) {
|
|
1286
|
+
controller.enqueue(pending[offset]);
|
|
1287
|
+
offset += 1;
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
const { done, value } = await reader.read();
|
|
1291
|
+
if (signal.aborted) {
|
|
1292
|
+
controller.close();
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
if (done) {
|
|
1296
|
+
controller.close();
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
controller.enqueue(value);
|
|
1300
|
+
},
|
|
1301
|
+
cancel(reason) {
|
|
1302
|
+
return reader.cancel(reason);
|
|
1303
|
+
},
|
|
1304
|
+
});
|
|
1305
|
+
return { meaningful, response: new Response(replay, response) };
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Runs the IDE request up to `1 + maxReplays` times, returning the first
|
|
1310
|
+
* response whose stream carried real output (text or tool calls). A 200 with
|
|
1311
|
+
* no output (empty or thought-only) is replayed rather than surfaced. The
|
|
1312
|
+
* stream is forwarded progressively (not buffered to completion).
|
|
1313
|
+
*/
|
|
1314
|
+
async function fetchQoderIdeWithEmptyRecovery(
|
|
1315
|
+
requestOnce: () => Promise<Response>,
|
|
1316
|
+
signal: AbortSignal,
|
|
1317
|
+
maxReplays = 2,
|
|
1318
|
+
): Promise<Response> {
|
|
1319
|
+
let last: Response | undefined;
|
|
1320
|
+
for (let attempt = 0; attempt <= maxReplays; attempt += 1) {
|
|
1321
|
+
const res = await requestOnce();
|
|
1322
|
+
if (!res.ok || !res.body) return res;
|
|
1323
|
+
const sniff = await sniffQoderStream(res, signal);
|
|
1324
|
+
last = sniff.response;
|
|
1325
|
+
if (sniff.meaningful) return last;
|
|
1326
|
+
}
|
|
1327
|
+
return last!;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
export { fetchQoderIdeWithEmptyRecovery };
|
|
1331
|
+
|
|
1060
1332
|
// Stream transformation — decode SSE, unwrap COSY envelope, map OpenAI chat chunks to CanonicalEvent
|
|
1061
1333
|
|
|
1062
1334
|
function mapQoderFinishReason(raw: unknown): CanonicalStopReason | undefined {
|
|
@@ -1073,7 +1345,7 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1073
1345
|
model: string,
|
|
1074
1346
|
): AsyncIterable<CanonicalEvent> {
|
|
1075
1347
|
let seq = 1;
|
|
1076
|
-
|
|
1348
|
+
let started = false;
|
|
1077
1349
|
let finishReason: unknown = undefined;
|
|
1078
1350
|
let rawUsage: Record<string, unknown> | undefined = undefined;
|
|
1079
1351
|
let hasTerminal = false;
|
|
@@ -1087,8 +1359,11 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1087
1359
|
// (recovered) or ordinary prose (released).
|
|
1088
1360
|
let leakBuf = "";
|
|
1089
1361
|
let leakFrames = 0;
|
|
1362
|
+
// Tool-call argument fragments, keyed by call id, reconciled at end of
|
|
1363
|
+
// stream (see the tool_calls handling in the loop).
|
|
1364
|
+
const toolCallBuffer = new Map<string, { index: number; name: string | undefined; arguments: string }>();
|
|
1090
1365
|
|
|
1091
|
-
for await (const sse of
|
|
1366
|
+
for await (const sse of decodeQoderSseEvents(body, signal)) {
|
|
1092
1367
|
const sseData = sse.data;
|
|
1093
1368
|
let frames: Array<{ event: string | null; data: string }>;
|
|
1094
1369
|
try {
|
|
@@ -1101,6 +1376,10 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1101
1376
|
if (!data) continue;
|
|
1102
1377
|
if (data === "[DONE]") {
|
|
1103
1378
|
hasTerminal = true;
|
|
1379
|
+
if (!started) {
|
|
1380
|
+
yield { type: "response_start", sequence_number: seq++, model } as CanonicalEvent;
|
|
1381
|
+
started = true;
|
|
1382
|
+
}
|
|
1104
1383
|
continue;
|
|
1105
1384
|
}
|
|
1106
1385
|
let json: Record<string, unknown>;
|
|
@@ -1109,6 +1388,18 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1109
1388
|
} catch {
|
|
1110
1389
|
continue;
|
|
1111
1390
|
}
|
|
1391
|
+
// The upstream names the real model in each chunk. Use the first one we
|
|
1392
|
+
// see for the opening event so the client sees the same model the
|
|
1393
|
+
// provider actually served, not the (virtual) id that was requested.
|
|
1394
|
+
if (!started && typeof json["model"] === "string") {
|
|
1395
|
+
const upstreamModel = (json["model"] as string).trim();
|
|
1396
|
+
yield {
|
|
1397
|
+
type: "response_start",
|
|
1398
|
+
sequence_number: seq++,
|
|
1399
|
+
model: upstreamModel.length > 0 ? upstreamModel : model,
|
|
1400
|
+
} as CanonicalEvent;
|
|
1401
|
+
started = true;
|
|
1402
|
+
}
|
|
1112
1403
|
// IDE surface error event: `{"code":"invalid_model_error","message":"..."}`
|
|
1113
1404
|
// carries no choices. Surface it as an upstream failure instead of an
|
|
1114
1405
|
// empty successful stream.
|
|
@@ -1227,7 +1518,14 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1227
1518
|
for (const tc of toolCalls) {
|
|
1228
1519
|
const fn = tc["function"] as Record<string, unknown> | undefined;
|
|
1229
1520
|
const index = typeof tc["index"] === "number" ? (tc["index"] as number) : undefined;
|
|
1230
|
-
|
|
1521
|
+
// `id` is only meaningful when non-empty: some Qoder slots echo
|
|
1522
|
+
// `id: ""` on continuation chunks, and treating that as a fresh
|
|
1523
|
+
// call id would orphan every argument fragment (the real call
|
|
1524
|
+
// keeps its first-seen id, the fragments pile up under "").
|
|
1525
|
+
const declaredId =
|
|
1526
|
+
typeof tc["id"] === "string" && (tc["id"] as string).length > 0
|
|
1527
|
+
? (tc["id"] as string)
|
|
1528
|
+
: undefined;
|
|
1231
1529
|
const fragName = typeof fn?.["name"] === "string" ? (fn["name"] as string) : undefined;
|
|
1232
1530
|
// OpenAI continuation chunks re-identify their call by wire
|
|
1233
1531
|
// `index` alone — the `id` appears only once, in the first chunk.
|
|
@@ -1255,14 +1553,27 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1255
1553
|
} else {
|
|
1256
1554
|
callId = `call_${String(index ?? 0)}`;
|
|
1257
1555
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1556
|
+
// Buffer tool-call argument fragments per call id until the end of
|
|
1557
|
+
// stream. Upstream streaming fragments occasionally drop or mangle
|
|
1558
|
+
// a byte mid-arguments; forwarding each fragment as an
|
|
1559
|
+
// `arguments_delta` would hand the client a malformed JSON blob to
|
|
1560
|
+
// stitch. Holding and reconciling here yields one complete,
|
|
1561
|
+
// parseable arguments string per call — flush happens at end of
|
|
1562
|
+
// stream (or when a terminal frame arrives).
|
|
1563
|
+
const existing = toolCallBuffer.get(callId);
|
|
1564
|
+
if (existing) {
|
|
1565
|
+
existing.index = index ?? existing.index;
|
|
1566
|
+
if (fragName !== undefined) existing.name = fragName;
|
|
1567
|
+
if (typeof fn?.["arguments"] === "string") {
|
|
1568
|
+
existing.arguments += (fn["arguments"] as string);
|
|
1569
|
+
}
|
|
1570
|
+
} else {
|
|
1571
|
+
toolCallBuffer.set(callId, {
|
|
1572
|
+
index: index ?? 0,
|
|
1573
|
+
name: fragName,
|
|
1574
|
+
arguments: typeof fn?.["arguments"] === "string" ? (fn["arguments"] as string) : "",
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1266
1577
|
}
|
|
1267
1578
|
}
|
|
1268
1579
|
}
|
|
@@ -1312,14 +1623,30 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1312
1623
|
}
|
|
1313
1624
|
}
|
|
1314
1625
|
// A `[DONE]` frame from the IDE surface may be the only terminal signal (the
|
|
1315
|
-
//
|
|
1626
|
+
// //-delta `finish_reason` is empty and `raw_usage` is not always sent), so
|
|
1316
1627
|
// a clean end-of-stream with no finish reason is a normal stop, not a
|
|
1317
1628
|
// truncation. If no terminal arrived at all, still synthesize one (prevents
|
|
1318
1629
|
// truncation error).
|
|
1630
|
+
if (!started) {
|
|
1631
|
+
yield { type: "response_start", sequence_number: seq++, model } as CanonicalEvent;
|
|
1632
|
+
}
|
|
1319
1633
|
if (hasTerminal && finishReason === undefined) finishReason = "stop";
|
|
1320
1634
|
const stopReason = mapQoderFinishReason(finishReason);
|
|
1321
1635
|
const _providerStop = typeof finishReason === "string" ? finishReason : undefined;
|
|
1322
1636
|
void hasTerminal;
|
|
1637
|
+
// Flush buffered tool-call fragments as complete, stitched calls — one
|
|
1638
|
+
// `arguments_delta` per call with the full JSON string, so the client never
|
|
1639
|
+
// has to repair upstream frame-loss.
|
|
1640
|
+
for (const [callId, buffered] of toolCallBuffer) {
|
|
1641
|
+
yield {
|
|
1642
|
+
type: "tool_call_delta",
|
|
1643
|
+
sequence_number: seq++,
|
|
1644
|
+
call_id: callId,
|
|
1645
|
+
index: buffered.index,
|
|
1646
|
+
name: buffered.name,
|
|
1647
|
+
arguments_delta: buffered.arguments,
|
|
1648
|
+
} as CanonicalEvent;
|
|
1649
|
+
}
|
|
1323
1650
|
yield {
|
|
1324
1651
|
type: "terminal",
|
|
1325
1652
|
sequence_number: seq++,
|
|
@@ -1330,17 +1657,13 @@ export async function* qoderBodyToCanonicalEvents(
|
|
|
1330
1657
|
} as CanonicalEvent;
|
|
1331
1658
|
}
|
|
1332
1659
|
|
|
1333
|
-
// Model catalog — maps
|
|
1660
|
+
// Model catalog — maps the realm's roster to Cartethyia ModelDefinition
|
|
1334
1661
|
|
|
1335
1662
|
/**
|
|
1336
1663
|
* The intl catalog this provider publishes.
|
|
1337
1664
|
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
* cosmetic: it is sent upstream as `model_config.max_input_tokens`, so a stale
|
|
1341
|
-
* low value tells Qoder the conversation window is small and makes it behave
|
|
1342
|
-
* that way. Both the published catalog and the dispatched request come from the
|
|
1343
|
-
* realm's roster, which carries the upstream's own numbers.
|
|
1665
|
+
* The published catalog and the dispatched request both come from the realm's
|
|
1666
|
+
* roster, which carries the upstream's own numbers.
|
|
1344
1667
|
*/
|
|
1345
1668
|
export const QODER_MODELS: readonly ModelDefinition[] = QODER_INTL_MODELS;
|
|
1346
1669
|
|
|
@@ -1356,10 +1679,20 @@ interface QoderAdapterOptions {
|
|
|
1356
1679
|
* Which chat surface to dispatch on.
|
|
1357
1680
|
*
|
|
1358
1681
|
* `ide` (default) uses the modern model-server surface
|
|
1359
|
-
* (`/model/v1/chat/completions
|
|
1360
|
-
*
|
|
1361
|
-
*
|
|
1362
|
-
*
|
|
1682
|
+
* (`/model/v1/chat/completions`) — the wire the IDE/QoderWake actually
|
|
1683
|
+
* speaks, echoes the real model key, and carries 1M context + xhigh
|
|
1684
|
+
* reasoning. When that surface answers an entitlement refusal (HTTP 402 /
|
|
1685
|
+
* `{"code":116,"error":"quota exceeded"}`, seen on free-tier accounts),
|
|
1686
|
+
* dispatch falls back to the legacy COSY surface
|
|
1687
|
+
* (`/algo/.../agent_chat_generation`) — but only for a free-tier model
|
|
1688
|
+
* row (`is_free: true`). A billed model answering 402/116 is genuine quota
|
|
1689
|
+
* exhaustion (Qoder's daily exhaustion / cooldown run on a 1-day cycle) and
|
|
1690
|
+
* surfaces as-is, so the exhausted state is never masked by a fallback.
|
|
1691
|
+
* Live verification (2026-11-02): the modern surface answers 116 for
|
|
1692
|
+
* free-tier accounts that stream fine over COSY, while entitled accounts
|
|
1693
|
+
* stream on the modern surface with the real model echoed.
|
|
1694
|
+
*
|
|
1695
|
+
* `cosy` forces only the legacy surface (the pre-fallback behavior).
|
|
1363
1696
|
*/
|
|
1364
1697
|
readonly surface?: "ide" | "cosy";
|
|
1365
1698
|
}
|
|
@@ -1448,19 +1781,37 @@ class QoderAdapter implements ProviderAdapter {
|
|
|
1448
1781
|
// a CN credential to `*.qoder.sh` is refused outright. The host order
|
|
1449
1782
|
// follows the token kind: job tokens (`jt-...`) belong on `api2`, which
|
|
1450
1783
|
// is the only intl host that serves them (`api3` answers "Login expired").
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
//
|
|
1459
|
-
//
|
|
1460
|
-
|
|
1784
|
+
|
|
1785
|
+
// The modern `ide` surface streams immediately, needs no COSY signing,
|
|
1786
|
+
// and echoes the real model key — it is the default. Fall back to the
|
|
1787
|
+
// legacy COSY surface only when BOTH hold: the modern surface refused on
|
|
1788
|
+
// entitlement grounds (HTTP 402 / code 116) AND the dispatched model is a
|
|
1789
|
+
// free-tier row (`is_free: true`). For a free-tier model this is the
|
|
1790
|
+
// entitlement refusal the legacy surface serves with the same credential;
|
|
1791
|
+
// for a billed model the same code is genuine quota exhaustion (Qoder's
|
|
1792
|
+
// daily exhaustion / cooldown run on a 1-day cycle), so it must surface
|
|
1793
|
+
// as-is — falling back would hide the exhausted state and starve the
|
|
1794
|
+
// quota/cooldown signal. A 401/403/model error never triggers fallback.
|
|
1795
|
+
const ideUrl = candidate.endpoint_path?.startsWith("http") ? candidate.endpoint_path : qoderIdeChatUrl(this.realm);
|
|
1796
|
+
const cosyUrl = candidate.endpoint_path?.startsWith("http")
|
|
1797
|
+
? candidate.endpoint_path
|
|
1798
|
+
: `${qoderInferenceCandidates(this.realm, auth.securityOauthToken)[0] ?? ""}${QODER_CHAT_PATH}`;
|
|
1799
|
+
const ideRequest = () =>
|
|
1800
|
+
callQoderIde(ideUrl, buildQoderIdeRequest(modelId, request, modelConfig, auth, this.realm, version), auth, lifecycle.signal, fetcher, version);
|
|
1801
|
+
const cosyRequest = () =>
|
|
1802
|
+
callQoder(cosyUrl, buildQoderRequest(modelId, request, modelConfig, auth, this.realm), modelId, auth, lifecycle.signal, fetcher, version);
|
|
1803
|
+
let response =
|
|
1461
1804
|
this.surface === "ide"
|
|
1462
|
-
? await
|
|
1463
|
-
: await
|
|
1805
|
+
? await fetchQoderIdeWithEmptyRecovery(ideRequest, lifecycle.signal)
|
|
1806
|
+
: await cosyRequest();
|
|
1807
|
+
if (
|
|
1808
|
+
!response.ok &&
|
|
1809
|
+
this.surface === "ide" &&
|
|
1810
|
+
qoderIdeEntitlementRefused(response) &&
|
|
1811
|
+
modelConfig.is_free === true
|
|
1812
|
+
) {
|
|
1813
|
+
response = await cosyRequest();
|
|
1814
|
+
}
|
|
1464
1815
|
if (!response.ok) throw await mapUpstreamHttpError(response, "qoder");
|
|
1465
1816
|
if (!response.body) throw new GatewayError("platform_unavailable", 502, "Qoder returned empty body (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
|
|
1466
1817
|
// Headers arrived: the pre-stream deadline has served its purpose.
|