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
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
* the same endpoints as the upstream client.
|
|
9
9
|
*
|
|
10
10
|
* The issued token is used directly at dispatch: `dt-...` tokens skip the
|
|
11
|
-
* PAT→jobToken exchange.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `auth_state`, because the token itself carries neither and every
|
|
16
|
-
* needs both for COSY signing.
|
|
11
|
+
* PAT→jobToken exchange. Refresh uses the deviceToken refresh endpoint
|
|
12
|
+
* (`POST /api/v1/deviceToken/refresh` with `{refresh_token, machine_id,
|
|
13
|
+
* machine_token}`), verified live 2026-11-02 — it returns a fresh `dt-…`
|
|
14
|
+
* and a rotated `drt-…`. The account's Qoder user id and machine id travel
|
|
15
|
+
* in `auth_state`, because the token itself carries neither and every
|
|
16
|
+
* dispatch needs both for COSY signing.
|
|
17
17
|
*/
|
|
18
18
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
19
21
|
import { QODER_REALMS, type QoderRealm } from "./qoder-realm";
|
|
20
22
|
import {
|
|
21
23
|
base64UrlEncode,
|
|
@@ -28,7 +30,7 @@ import type {
|
|
|
28
30
|
OAuthDeviceStartResult,
|
|
29
31
|
OAuthExchangeResult,
|
|
30
32
|
} from "../../authentication/oauth-flow-store";
|
|
31
|
-
import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
|
|
33
|
+
import type { OAuthTokenRefreshResult, OAuthRefreshContext } from "../../authentication/oauth-refresh-service";
|
|
32
34
|
import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
|
|
33
35
|
|
|
34
36
|
/** One Qoder account profile, as much of it as the gateway uses. */
|
|
@@ -109,9 +111,7 @@ export function readQoderUserInfo(body: Record<string, unknown>): QoderUserInfo
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
/** Global/intl endpoints. Kept as the default realm's values, unchanged. */
|
|
112
|
-
export const QODER_LOGIN_URL = "https://qoder.com/device/selectAccounts" as const;
|
|
113
114
|
export const QODER_DEVICE_TOKEN_URL = "https://openapi.qoder.sh/api/v1/deviceToken/poll" as const;
|
|
114
|
-
export const QODER_USERINFO_URL = "https://openapi.qoder.sh/api/v1/userinfo" as const;
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Per-realm device endpoints, derived from `QODER_REALMS`.
|
|
@@ -221,6 +221,82 @@ function parseDeviceState(value: string | undefined): QoderDeviceState | undefin
|
|
|
221
221
|
return { verifier, machineId };
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Random PKCE verifier in the exact alphabet the desktop IDE uses.
|
|
226
|
+
*
|
|
227
|
+
* The IDE's `startDeviceFlow` draws the verifier from
|
|
228
|
+
* `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~`. Using
|
|
229
|
+
* the same alphabet keeps the login byte-for-byte identical to the desktop
|
|
230
|
+
* client, which the approval server may use to fingerprint the requesting
|
|
231
|
+
* client. Length is fixed at 64 — RFC 7636 §4.1 requires [43,128]; a fixed
|
|
232
|
+
* length avoids a non-cryptographic length while the content comes entirely
|
|
233
|
+
* from the CSPRNG (`randomBytes`) below.
|
|
234
|
+
*/
|
|
235
|
+
function qoderIdeVerifier(): string {
|
|
236
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
237
|
+
// RFC 7636 §4.1: verifier length must be within [43,128]; fixed here. Content
|
|
238
|
+
// comes from the CSPRNG (randomBytes) below. `67` (alphabet length) does not
|
|
239
|
+
// divide 256, so the `%` mapping has a negligible modulo bias (~21/256 per
|
|
240
|
+
// char) — irrelevant for a 64-char secret with ~388 bits of input entropy.
|
|
241
|
+
const length = 64;
|
|
242
|
+
const bytes = randomBytes(length);
|
|
243
|
+
let out = "";
|
|
244
|
+
for (let i = 0; i < length; i += 1) {
|
|
245
|
+
out += alphabet[bytes[i]! % alphabet.length];
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The hardware machine id the desktop IDE reports for the device flow.
|
|
252
|
+
*
|
|
253
|
+
* The IDE's `getMachineId` is platform-hardware-derived: Windows reads
|
|
254
|
+
* `MachineGuid` from `HKLM\SOFTWARE\Microsoft\Cryptography`, macOS reads the
|
|
255
|
+
* `IOPlatformUUID` from IORegistry, Linux reads `/etc/machine-id`. The device
|
|
256
|
+
* login sends it as `machine_id`, and the upstream correlates the approved
|
|
257
|
+
* device to the account. A random UUID instead registers a different device,
|
|
258
|
+
* which is the observable difference between an IDE login that chats and a
|
|
259
|
+
* gateway login that is refused with quota errors.
|
|
260
|
+
*
|
|
261
|
+
* Returns the hardware id when it can be read, else `""` so callers fall back
|
|
262
|
+
* to a random UUID exactly like the IDE does on an unreadable machine.
|
|
263
|
+
*/
|
|
264
|
+
export function qoderHardwareMachineId(): string {
|
|
265
|
+
try {
|
|
266
|
+
if (process.platform === "win32") {
|
|
267
|
+
const out = execSync('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid', {
|
|
268
|
+
encoding: "utf8",
|
|
269
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
270
|
+
timeout: 5000,
|
|
271
|
+
});
|
|
272
|
+
const m = /MachineGuid\s+REG_SZ\s+([0-9a-fA-F-]+)/.exec(out);
|
|
273
|
+
return m?.[1]?.trim() ?? "";
|
|
274
|
+
}
|
|
275
|
+
if (process.platform === "darwin") {
|
|
276
|
+
const out = execSync("ioreg -rd1 -c IOPlatformExpertDevice", {
|
|
277
|
+
encoding: "utf8",
|
|
278
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
279
|
+
timeout: 5000,
|
|
280
|
+
});
|
|
281
|
+
const m = /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out);
|
|
282
|
+
return m?.[1]?.trim() ?? "";
|
|
283
|
+
}
|
|
284
|
+
if (process.platform === "linux") {
|
|
285
|
+
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
286
|
+
try {
|
|
287
|
+
const v = readFileSync(p, "utf8").trim();
|
|
288
|
+
if (v) return v;
|
|
289
|
+
} catch {
|
|
290
|
+
// try the next candidate
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
// fall through to the caller's random fallback
|
|
296
|
+
}
|
|
297
|
+
return "";
|
|
298
|
+
}
|
|
299
|
+
|
|
224
300
|
export class QoderOAuthClient extends OAuthDeviceFlow {
|
|
225
301
|
override readonly supportsDeviceCode = true;
|
|
226
302
|
override readonly supportsBrowserCode = false;
|
|
@@ -254,25 +330,35 @@ export class QoderOAuthClient extends OAuthDeviceFlow {
|
|
|
254
330
|
override async startDeviceAuth(
|
|
255
331
|
_context?: OAuthDeviceFlowContext,
|
|
256
332
|
): Promise<OAuthDeviceStartResult> {
|
|
257
|
-
|
|
333
|
+
// 1:1 with the desktop IDE device flow (unpacked worker `startDeviceFlow`):
|
|
334
|
+
// the verifier is random from the IDE's own alphabet (`A-Za-z0-9-._~`,
|
|
335
|
+
// 43–128 chars), the challenge is PKCE S256, the nonce is a full UUID
|
|
336
|
+
// (dashes included), and the client id is ALWAYS sent — intl uses the
|
|
337
|
+
// same id the realm table carries, CN uses its own. Sending no client id
|
|
338
|
+
// (or stripping the UUID dashes) makes the approval screen classify the
|
|
339
|
+
// device differently and can produce an account the model server later
|
|
340
|
+
// refuses with quota/entitlement errors.
|
|
341
|
+
//
|
|
342
|
+
// The machine id defaults to the hardware id (same source the IDE reads),
|
|
343
|
+
// but an operator may pass `machine_id`/`machine_token` through
|
|
344
|
+
// `context.parameters` to reuse a device identity the IDE already holds
|
|
345
|
+
// (e.g. `aicoding.loginMachineIdentity.v1.qoder.local`) — the upstream
|
|
346
|
+
// correlates the approved device to the account, so matching the IDE's
|
|
347
|
+
// device is how a gateway login chats on the same account the IDE does.
|
|
348
|
+
const overrideMachineId = nonEmptyTrimmedString(_context?.parameters?.["machine_id"]);
|
|
349
|
+
const overrideMachineToken = nonEmptyTrimmedString(_context?.parameters?.["machine_token"]);
|
|
350
|
+
const verifier = qoderIdeVerifier();
|
|
258
351
|
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// has always sent. Both shapes are upstream fact from the donor, and only
|
|
262
|
-
// the CN branch is new, so the working intl URL is untouched.
|
|
263
|
-
const nonce = this.realm === "cn" ? randomUUID() : randomUUID().replaceAll("-", "");
|
|
264
|
-
const machineId = randomUUID();
|
|
352
|
+
const nonce = randomUUID();
|
|
353
|
+
const machineId = overrideMachineId ?? (qoderHardwareMachineId() || randomUUID());
|
|
265
354
|
const params = new URLSearchParams({
|
|
266
355
|
challenge,
|
|
267
356
|
challenge_method: "S256",
|
|
268
|
-
machine_id: machineId,
|
|
269
357
|
nonce,
|
|
358
|
+
machine_id: machineId,
|
|
359
|
+
client_id: QODER_REALMS[this.realm].clientId,
|
|
270
360
|
});
|
|
271
|
-
if (
|
|
272
|
-
const config = QODER_REALMS.cn;
|
|
273
|
-
params.set("client_id", config.clientId);
|
|
274
|
-
params.set("redirect_uri", config.redirectUri);
|
|
275
|
-
}
|
|
361
|
+
if (overrideMachineToken !== undefined) params.set("machine_token", overrideMachineToken);
|
|
276
362
|
return {
|
|
277
363
|
verificationUri: `${qoderOAuthUrls(this.realm).loginUrl}?${params.toString()}`,
|
|
278
364
|
// This flow shows no user code: approval happens against the
|
|
@@ -395,10 +481,64 @@ export class QoderOAuthClient extends OAuthDeviceFlow {
|
|
|
395
481
|
}
|
|
396
482
|
|
|
397
483
|
override async refresh(
|
|
398
|
-
|
|
399
|
-
|
|
484
|
+
refreshToken: string,
|
|
485
|
+
signal?: AbortSignal,
|
|
486
|
+
context?: OAuthRefreshContext,
|
|
400
487
|
): Promise<OAuthTokenRefreshResult> {
|
|
401
|
-
|
|
488
|
+
// The deviceToken refresh endpoint is the one working refresh grant for this
|
|
489
|
+
// flow (verified live 2026-11-02: POST /api/v1/deviceToken/refresh with
|
|
490
|
+
// `{refresh_token, machine_id, machine_token}` returns 200 with a fresh
|
|
491
|
+
// `device_token` + rotated `refresh_token`). The abstract OAuth refresh
|
|
492
|
+
// endpoint answers 403 for this flow, so we post to the deviceToken one.
|
|
493
|
+
const authState = record(context?.auth_state);
|
|
494
|
+
const machineId = authState === undefined ? undefined : nonEmptyTrimmedString(authState.machineId);
|
|
495
|
+
if (machineId === undefined) {
|
|
496
|
+
throw new Error("Qoder refresh requires auth_state.machineId");
|
|
497
|
+
}
|
|
498
|
+
const response = await this.fetchFn(
|
|
499
|
+
`${qoderOAuthUrls(this.realm).deviceTokenUrl}/refresh`,
|
|
500
|
+
{
|
|
501
|
+
method: "POST",
|
|
502
|
+
headers: {
|
|
503
|
+
"content-type": "application/json",
|
|
504
|
+
accept: "application/json",
|
|
505
|
+
"user-agent": "Go-http-client/2.0",
|
|
506
|
+
},
|
|
507
|
+
body: JSON.stringify({
|
|
508
|
+
refresh_token: refreshToken,
|
|
509
|
+
machine_id: machineId,
|
|
510
|
+
machine_token: machineId,
|
|
511
|
+
}),
|
|
512
|
+
...(signal === undefined ? {} : { signal }),
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
const raw = await response.text();
|
|
516
|
+
let payload: unknown;
|
|
517
|
+
try {
|
|
518
|
+
payload = JSON.parse(raw);
|
|
519
|
+
} catch {
|
|
520
|
+
throw new Error(`Qoder token refresh returned invalid JSON (${response.status})`);
|
|
521
|
+
}
|
|
522
|
+
if (!response.ok) {
|
|
523
|
+
const message = record(payload);
|
|
524
|
+
throw new Error(
|
|
525
|
+
nonEmptyTrimmedString(message?.message) ??
|
|
526
|
+
nonEmptyTrimmedString(message?.errorMessage) ??
|
|
527
|
+
`Qoder token refresh failed (${response.status})`,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
const body = record(payload);
|
|
531
|
+
const deviceToken = body === undefined ? undefined : nonEmptyTrimmedString(body.device_token);
|
|
532
|
+
if (deviceToken === undefined) {
|
|
533
|
+
throw new Error("Qoder token refresh response omitted device_token");
|
|
534
|
+
}
|
|
535
|
+
const nextRefresh = nonEmptyTrimmedString(body?.refresh_token);
|
|
536
|
+
const expiresAt = new Date(parseQoderExpiry(body?.expires_at, body?.expires_in));
|
|
537
|
+
return {
|
|
538
|
+
access: deviceToken,
|
|
539
|
+
...(nextRefresh === undefined ? {} : { refresh: nextRefresh }),
|
|
540
|
+
expiresAt,
|
|
541
|
+
};
|
|
402
542
|
}
|
|
403
543
|
}
|
|
404
544
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// Qoder quota.
|
|
2
2
|
//
|
|
3
|
-
// `GET /api/v2/quota/usage` on the **openapi** host, with a
|
|
4
|
-
//
|
|
5
|
-
// differs from inference.
|
|
3
|
+
// `GET /api/v2/quota/usage` on the **openapi** host, with a bearer token — no
|
|
4
|
+
// COSY signature on this surface, which is the one place the business API
|
|
5
|
+
// differs from inference. Device tokens (`dt-…`) are used directly; a PAT
|
|
6
|
+
// (`pt-…`) is exchanged for a job token (`jt-…`) first, because the business
|
|
7
|
+
// API rejects a raw PAT with 401 `TOKEN_EXPIRE`.
|
|
6
8
|
//
|
|
7
9
|
// The response carries two quota objects and they are not interchangeable:
|
|
8
10
|
//
|
|
@@ -36,6 +38,7 @@ import {
|
|
|
36
38
|
import type { QuotaCollectionContext } from "../../quota/quota-support";
|
|
37
39
|
import { log } from "../../../observability/logger";
|
|
38
40
|
import { QODER_REALMS, type QoderRealm } from "./qoder-realm";
|
|
41
|
+
import { isQoderPat, resolveQoderAuth } from "./qoder";
|
|
39
42
|
|
|
40
43
|
/** Paths on the realm's openapi host. */
|
|
41
44
|
const QUOTA_PATH = "/api/v2/quota/usage";
|
|
@@ -75,7 +78,29 @@ function creditWindow(
|
|
|
75
78
|
): ProviderQuotaWindow | null {
|
|
76
79
|
if (remaining === null && limit === null) return null;
|
|
77
80
|
const used = remaining !== null && limit !== null ? limit - remaining : null;
|
|
78
|
-
|
|
81
|
+
// Give pooled windows a percentage so the console can render a real bar and
|
|
82
|
+
// tone instead of an uncolored "quota-only" row (which is what a null
|
|
83
|
+
// percent produced: every Qoder account was grey, and "enable available
|
|
84
|
+
// accounts" could never match a window). A ceiling of 0 with nothing left is
|
|
85
|
+
// a spent balance — report 0% remaining rather than a null percent.
|
|
86
|
+
let usedPercent: number | null = null;
|
|
87
|
+
if (remaining !== null && limit !== null) {
|
|
88
|
+
if (limit > 0) usedPercent = ((limit - remaining) / limit) * 100;
|
|
89
|
+
else if (remaining <= 0) usedPercent = 100;
|
|
90
|
+
}
|
|
91
|
+
return percentWindow(kind, label, usedPercent, resetsAt, used, limit, "pooled");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The business API answers "never resets" with a far-future sentinel
|
|
96
|
+
* (`9999-12-31T00:00:00.000Z`). Keeping it on the wire makes the console show
|
|
97
|
+
* a 7978-year countdown. Any date at or beyond year 9999 means no reset.
|
|
98
|
+
*/
|
|
99
|
+
function normalizeQoderReset(resetsAt: string | null): string | null {
|
|
100
|
+
if (resetsAt === null) return null;
|
|
101
|
+
const date = new Date(resetsAt);
|
|
102
|
+
if (!Number.isFinite(date.getTime())) return null;
|
|
103
|
+
return date.getUTCFullYear() >= 9999 ? null : resetsAt;
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
/**
|
|
@@ -103,11 +128,28 @@ export async function fetchQoderQuota(
|
|
|
103
128
|
|
|
104
129
|
let payload: unknown;
|
|
105
130
|
try {
|
|
131
|
+
// A PAT (`pt-…`) is not an active bearer on the business API: it is a
|
|
132
|
+
// minted credential that must be exchanged for a short-lived job token
|
|
133
|
+
// (`jt-…`) first. Verified live 2026-11-02: a raw `pt-…` bearer answers
|
|
134
|
+
// 401 `TOKEN_EXPIRE` while the exchanged `jt-…` answers 200. Device
|
|
135
|
+
// tokens (`dt-…`) are used directly. A failed exchange — expired/revoked
|
|
136
|
+
// PAT — is an unreadable balance, not an error that cools the account.
|
|
137
|
+
let bearer = credential;
|
|
138
|
+
if (isQoderPat(credential)) {
|
|
139
|
+
const auth = await resolveQoderAuth({
|
|
140
|
+
secret: credential,
|
|
141
|
+
authState: raw,
|
|
142
|
+
signal: AbortSignal.timeout(15_000),
|
|
143
|
+
fetcher,
|
|
144
|
+
realm,
|
|
145
|
+
});
|
|
146
|
+
bearer = auth.securityOauthToken;
|
|
147
|
+
}
|
|
106
148
|
const response = await fetcher(url, {
|
|
107
149
|
method: "GET",
|
|
108
150
|
headers: {
|
|
109
151
|
accept: "application/json",
|
|
110
|
-
authorization: `Bearer ${
|
|
152
|
+
authorization: `Bearer ${bearer}`,
|
|
111
153
|
"user-agent": config.userAgent,
|
|
112
154
|
},
|
|
113
155
|
});
|
|
@@ -148,7 +190,7 @@ export async function fetchQoderQuota(
|
|
|
148
190
|
return { source: "qoder", plan: null, windows: [], error: null };
|
|
149
191
|
}
|
|
150
192
|
|
|
151
|
-
const resetsAt = isoDate(root["expiresAt"] ?? root["expireAt"] ?? root["expires_at"]);
|
|
193
|
+
const resetsAt = normalizeQoderReset(isoDate(root["expiresAt"] ?? root["expireAt"] ?? root["expires_at"]));
|
|
152
194
|
const windows: ProviderQuotaWindow[] = [];
|
|
153
195
|
const plan = creditWindow("qoder:plan", "Plan credits", number(userQuota?.["remaining"]), number(userQuota?.["total"]), resetsAt);
|
|
154
196
|
if (plan) windows.push(plan);
|
|
@@ -39,6 +39,25 @@ export const QODER_CHAT_PATH =
|
|
|
39
39
|
"/algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1";
|
|
40
40
|
export const QODER_MODELS_PATH = "/algo/api/v2/model/list?Encode=1";
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* The modern model-server surface used by the IDE / QoderWake (OpenAI-shaped
|
|
44
|
+
* chunks, plain bearer auth). This surface streams immediately and echoes the
|
|
45
|
+
* real model key — the legacy `/algo` chat endpoint answers
|
|
46
|
+
* `10605 isQueued` (parking requests in a server-side queue) and echoes a
|
|
47
|
+
* fixed `"auto"` model. Dispatch tries this surface first, and falls back to
|
|
48
|
+
* legacy COSY only when it refuses the account on entitlement grounds (HTTP
|
|
49
|
+
* 402 / `{"code":116}`) AND the dispatched model is a free-tier row — for a
|
|
50
|
+
* billed model the same code is genuine quota exhaustion and must surface.
|
|
51
|
+
*/
|
|
52
|
+
export const QODER_IDE_CHAT_PATH = "/model/v1/chat/completions";
|
|
53
|
+
|
|
54
|
+
/** The model-server host for one realm (intl: `api2-v2`, cn: gateway). */
|
|
55
|
+
export function qoderIdeChatUrl(realm: QoderRealm): string {
|
|
56
|
+
return realm === "cn"
|
|
57
|
+
? `https://gateway.qoder.com.cn${QODER_IDE_CHAT_PATH}`
|
|
58
|
+
: `https://api2-v2.qoder.sh${QODER_IDE_CHAT_PATH}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
/**
|
|
43
62
|
* Realm → deployment.
|
|
44
63
|
*
|
|
@@ -61,7 +80,9 @@ export const QODER_REALMS: Readonly<Record<QoderRealm, QoderRealmConfig>> = {
|
|
|
61
80
|
gatewayFallbacks: [],
|
|
62
81
|
openapi: "https://openapi.qoder.com.cn",
|
|
63
82
|
website: "https://qoder.com.cn",
|
|
64
|
-
|
|
83
|
+
// 1:1 with the desktop IDE's device flow (`Hfl` in the unpacked worker):
|
|
84
|
+
// the client id the IDE registers for the CN deployment.
|
|
85
|
+
clientId: "e93fe488-5778-4c35-a6fc-0f54ed7b3139",
|
|
65
86
|
redirectUri: "qoder-work-cn://",
|
|
66
87
|
userAgent: "QoderWork/1.1.64",
|
|
67
88
|
},
|
|
@@ -97,30 +118,3 @@ export function qoderInferenceCandidates(
|
|
|
97
118
|
}
|
|
98
119
|
return [config.gateway, ...config.gatewayFallbacks];
|
|
99
120
|
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Model keys that exist in exactly one realm.
|
|
103
|
-
*
|
|
104
|
-
* From the donor's `INTL_EXCLUSIVE` / `CN_EXCLUSIVE` tables. A request for one
|
|
105
|
-
* of these on the wrong realm does not degrade — it is refused — so the roster
|
|
106
|
-
* each realm publishes is the authority, not a union.
|
|
107
|
-
*/
|
|
108
|
-
export const QODER_INTL_EXCLUSIVE: ReadonlySet<string> = new Set([
|
|
109
|
-
"fast",
|
|
110
|
-
"efficient",
|
|
111
|
-
"cmodel",
|
|
112
|
-
"smodel",
|
|
113
|
-
"ultimate",
|
|
114
|
-
"performance",
|
|
115
|
-
]);
|
|
116
|
-
|
|
117
|
-
export const QODER_CN_EXCLUSIVE: ReadonlySet<string> = new Set([
|
|
118
|
-
"q37fmodel",
|
|
119
|
-
"gm51model",
|
|
120
|
-
]);
|
|
121
|
-
|
|
122
|
-
/** True when this model key is served only by the other realm. */
|
|
123
|
-
export function isForeignQoderModel(realm: QoderRealm, modelId: string): boolean {
|
|
124
|
-
const foreign = realm === "cn" ? QODER_INTL_EXCLUSIVE : QODER_CN_EXCLUSIVE;
|
|
125
|
-
return foreign.has(modelId.trim().toLowerCase());
|
|
126
|
-
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qoder-specific SSE decoding.
|
|
3
|
+
*
|
|
4
|
+
* The generic `decodeSseEvents` splits on newlines and treats a bare line
|
|
5
|
+
* (no `data:` prefix) with no open event as malformed. Qoder's IDE surface
|
|
6
|
+
* embeds literal newline bytes inside JSON string values (e.g. `raw_usage`
|
|
7
|
+
* keys like `"data_policy_preferre\n\nnd_count"`), which fragments one logical
|
|
8
|
+
* `data:` event into many physical lines — including blank lines that are not
|
|
9
|
+
* event boundaries. The generic decoder can throw `Malformed SSE line` on the
|
|
10
|
+
* trailing fragments of those giant events.
|
|
11
|
+
*
|
|
12
|
+
* This decoder is tolerant by construction: it accumulates one event's payload
|
|
13
|
+
* across `data:` lines, bare continuations, and blank lines until the payload
|
|
14
|
+
* is a complete JSON value or the `[DONE]` sentinel. It never throws on a
|
|
15
|
+
* bare fragment — payload completeness is the only way an event ends.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface QoderSseEvent {
|
|
19
|
+
readonly event: string | null;
|
|
20
|
+
readonly data: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DEFAULT_MAX_EVENT_BYTES = 8 * 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
/** True when the accumulated text is a complete JSON value (or non-JSON text). */
|
|
26
|
+
function isCompleteJsonValue(data: string): boolean {
|
|
27
|
+
if (data.length === 0) return true;
|
|
28
|
+
let depth = 0;
|
|
29
|
+
let inString = false;
|
|
30
|
+
let escaped = false;
|
|
31
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
32
|
+
const ch = data[i]!;
|
|
33
|
+
if (inString) {
|
|
34
|
+
if (escaped) {
|
|
35
|
+
escaped = false;
|
|
36
|
+
} else if (ch === "\\") {
|
|
37
|
+
escaped = true;
|
|
38
|
+
} else if (ch === '"') {
|
|
39
|
+
inString = false;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ch === '"') {
|
|
44
|
+
inString = true;
|
|
45
|
+
} else if (ch === "{" || ch === "[") {
|
|
46
|
+
depth += 1;
|
|
47
|
+
} else if (ch === "}" || ch === "]") {
|
|
48
|
+
depth -= 1;
|
|
49
|
+
if (depth < 0) return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return depth <= 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Decodes Qoder SSE into complete events.
|
|
57
|
+
*/
|
|
58
|
+
export async function* decodeQoderSseEvents(
|
|
59
|
+
body: ReadableStream<Uint8Array> | null,
|
|
60
|
+
signal?: AbortSignal,
|
|
61
|
+
maxEventBytes = DEFAULT_MAX_EVENT_BYTES,
|
|
62
|
+
): AsyncIterable<QoderSseEvent> {
|
|
63
|
+
if (body === null) return;
|
|
64
|
+
|
|
65
|
+
const reader = body.getReader();
|
|
66
|
+
const decoder = new TextDecoder();
|
|
67
|
+
let lineBuffer = "";
|
|
68
|
+
let current: string | null = null; // accumulated payload of the open event
|
|
69
|
+
let eventBytes = 0;
|
|
70
|
+
|
|
71
|
+
/** Returns the open payload when complete, else null (clearing it). */
|
|
72
|
+
const takeComplete = (): string | null => {
|
|
73
|
+
if (current === null) return null;
|
|
74
|
+
const trimmed = current.trim();
|
|
75
|
+
if (trimmed.length === 0) {
|
|
76
|
+
current = null;
|
|
77
|
+
eventBytes = 0;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (trimmed === "[DONE]" || isCompleteJsonValue(trimmed)) {
|
|
81
|
+
const data = current;
|
|
82
|
+
current = null;
|
|
83
|
+
eventBytes = 0;
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const appendPayload = (text: string): void => {
|
|
90
|
+
if (current === null) return;
|
|
91
|
+
current += text;
|
|
92
|
+
eventBytes += text.length;
|
|
93
|
+
if (eventBytes > maxEventBytes) throw new Error("SSE event exceeds size bound");
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** Emit a complete open payload, if any. Returns `true` when the stream ends. */
|
|
97
|
+
function* emitComplete(): Generator<QoderSseEvent, boolean, void> {
|
|
98
|
+
const data = takeComplete();
|
|
99
|
+
if (data === null) return false;
|
|
100
|
+
if (data.trim() === "[DONE]") return true;
|
|
101
|
+
yield { event: null, data };
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
for (;;) {
|
|
107
|
+
if (signal?.aborted) return;
|
|
108
|
+
const { done, value } = await reader.read();
|
|
109
|
+
if (done) break;
|
|
110
|
+
if (!value || value.byteLength === 0) continue;
|
|
111
|
+
lineBuffer += decoder.decode(value, { stream: true });
|
|
112
|
+
|
|
113
|
+
for (;;) {
|
|
114
|
+
const nl = lineBuffer.indexOf("\n");
|
|
115
|
+
if (nl < 0) break;
|
|
116
|
+
const line = lineBuffer.slice(0, nl);
|
|
117
|
+
lineBuffer = lineBuffer.slice(nl + 1);
|
|
118
|
+
const trimmed = line.trim();
|
|
119
|
+
|
|
120
|
+
// Comments, event names, and other fields carry no payload.
|
|
121
|
+
if (trimmed.startsWith(":") || trimmed.startsWith("event:")) continue;
|
|
122
|
+
|
|
123
|
+
if (trimmed.startsWith("data:")) {
|
|
124
|
+
// A fresh `data:` — flush a complete open event first, then start a
|
|
125
|
+
// new payload with the slice after the prefix.
|
|
126
|
+
if (yield* emitComplete()) return;
|
|
127
|
+
current = line.slice(5).trimStart();
|
|
128
|
+
eventBytes = current.length;
|
|
129
|
+
if (eventBytes > maxEventBytes) throw new Error("SSE event exceeds size bound");
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Other known fields (id:, retry:, vendor extensions) carry no payload.
|
|
134
|
+
if (/^[a-zA-Z0-9_-]+:/.test(trimmed)) continue;
|
|
135
|
+
|
|
136
|
+
if (trimmed.length === 0) {
|
|
137
|
+
// Blank line: an event boundary only when the open payload is
|
|
138
|
+
// complete; otherwise it is a literal newline inside the JSON string.
|
|
139
|
+
if (yield* emitComplete()) return;
|
|
140
|
+
if (current !== null) appendPayload("\n");
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Bare line: continuation of the open payload (Qoder fragments JSON
|
|
145
|
+
// across physical lines with literal newlines inside strings).
|
|
146
|
+
if (current !== null) {
|
|
147
|
+
appendPayload("\n" + line);
|
|
148
|
+
if (yield* emitComplete()) return;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// Bare line with no open payload: ignore (tolerant by design).
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
yield* emitComplete();
|
|
156
|
+
} finally {
|
|
157
|
+
reader.releaseLock();
|
|
158
|
+
}
|
|
159
|
+
}
|