cartethyia 2.7.4 → 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.
Files changed (553) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-ChG5svFb.js → BannedPage-Cj15GsMp.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  5. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
  6. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolDetailPage-BIcWQun7.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Bf2VelDe.js → CliToolsPage-BIccZmeL.js} +1 -1
  10. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  11. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
  12. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
  14. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  15. package/dist/dashboard/assets/{CustomizationPage-ggeMSjte.js → CustomizationPage-BKj_r0rp.js} +1 -1
  16. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-U6AKaJcF.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-DnQPSIA8.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-5SnUSy3j.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-f0DE1h0-.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-BfAb5-o_.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
  44. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  45. package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
  46. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  47. package/dist/dashboard/assets/assert-C0K-c4eo.js +1 -0
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DympBNq4.js → banner-BllT4LcF.js} +1 -1
  52. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  53. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  54. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  55. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  56. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  57. package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CtkX66Wd.js} +1 -1
  58. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  59. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  60. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  61. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js +1 -0
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-BQqBQd2n.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  65. package/dist/dashboard/assets/{dialog-8fiE6tec.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-BrTEZ8zu.js} +1 -1
  68. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
  70. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  71. package/dist/dashboard/assets/{entry-BSAsYsmV.js → entry-EBHJ3DrE.js} +1 -1
  72. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
  74. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  75. package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-CPK47xQx.js → github-badge-YSCE4UC2.js} +1 -1
  78. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-DBNPOfhV.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BHHX_Tmm.js} +1 -1
  84. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
  87. package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Bx2I7XUe.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  89. package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
  90. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  91. package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
  92. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  93. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  94. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  95. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  96. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  97. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  98. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  99. package/dist/dashboard/assets/routing-in0f_IfK.js +1 -0
  100. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  101. package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BLULNqTp.js} +1 -1
  102. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  103. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  104. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  105. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  106. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  107. package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-D2lIR0tg.js} +1 -1
  108. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  109. package/dist/dashboard/assets/{state-CkfdYWel.js → state-DTARjfiW.js} +1 -1
  110. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  111. package/dist/dashboard/assets/{switch-SYm237HG.js → switch-c0oPSnqW.js} +1 -1
  112. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  113. package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-m8WGNPrz.js} +1 -1
  114. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  115. package/dist/dashboard/console-backgrounds.jpg +0 -0
  116. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  117. package/dist/dashboard/index.html +2 -2
  118. package/dist/dashboard/index.html.gz +0 -0
  119. package/dist/dashboard/precompressed-assets.json +56 -55
  120. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  121. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  122. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  123. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  124. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  125. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  126. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  127. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  128. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  129. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  130. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  131. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  132. package/package.json +4 -6
  133. package/scripts/cli/update.ts +15 -41
  134. package/src/boot/app.ts +58 -2
  135. package/src/boot/dependencies.ts +786 -732
  136. package/src/boot/lifecycle.ts +11 -12
  137. package/src/config/cooldowns.ts +34 -55
  138. package/src/config/network.ts +1 -1
  139. package/src/config/package-paths.ts +1 -1
  140. package/src/config/providers.ts +13 -14
  141. package/src/config/resources.ts +0 -5
  142. package/src/config/runtime.ts +2 -5
  143. package/src/config/spec.ts +26 -6
  144. package/src/console/api-keys/ban-constants.ts +38 -0
  145. package/src/console/api-keys/contracts.ts +71 -62
  146. package/src/console/api-keys/routes.ts +48 -12
  147. package/src/console/auth/access.ts +4 -4
  148. package/src/console/auth/account-reset.ts +1 -1
  149. package/src/console/auth/audit-recorder.ts +1 -1
  150. package/src/console/auth/lockout.ts +7 -3
  151. package/src/console/auth/row-coercion.ts +57 -12
  152. package/src/console/auth/session-resolver.ts +1 -1
  153. package/src/console/auth/session-service.ts +8 -2
  154. package/src/console/auth/session.ts +37 -31
  155. package/src/console/auth/setup-service.ts +4 -3
  156. package/src/console/auth/user-store.ts +1 -1
  157. package/src/console/backup/9router-map.ts +0 -3
  158. package/src/console/backup/contracts.ts +9 -9
  159. package/src/console/backup/export-endpoint.ts +5 -6
  160. package/src/console/backup/nine-router.ts +1 -7
  161. package/src/console/backup/routes.ts +29 -33
  162. package/src/console/backup/service.ts +468 -468
  163. package/src/console/backup/store.ts +47 -29
  164. package/src/console/backup/validate.ts +11 -1
  165. package/src/console/cli-tools/contracts.ts +7 -7
  166. package/src/console/cli-tools/injectors/cline.ts +110 -110
  167. package/src/console/cli-tools/injectors/codex.ts +181 -181
  168. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  169. package/src/console/cli-tools/injectors/driver.ts +247 -247
  170. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  171. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  172. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  173. package/src/console/cli-tools/routes.ts +1 -1
  174. package/src/console/cli-tools/service.ts +1 -1
  175. package/src/console/cli-tools/store.ts +2 -2
  176. package/src/console/console-router.ts +32 -0
  177. package/src/console/dashboard-assets.ts +9 -9
  178. package/src/console/domain-registration.ts +16 -2
  179. package/src/console/inferhub/client.ts +0 -7
  180. package/src/console/inferhub/key-store.ts +1 -1
  181. package/src/console/inferhub/normalize.ts +2 -2
  182. package/src/console/inferhub/routes.ts +1 -1
  183. package/src/console/inferhub/types.ts +1 -1
  184. package/src/console/observability/contracts.ts +1 -1
  185. package/src/console/observability/live.ts +31 -18
  186. package/src/console/observability/logs.ts +1 -1
  187. package/src/console/observability/sse.ts +62 -8
  188. package/src/console/observability/store.ts +20 -4
  189. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  190. package/src/console/providers/catalog/contracts.ts +39 -0
  191. package/src/console/providers/catalog/model-operations.ts +1 -1
  192. package/src/console/providers/catalog/provider-operations.ts +95 -17
  193. package/src/console/providers/catalog/public-model-store.ts +0 -0
  194. package/src/console/providers/catalog/routes.ts +27 -3
  195. package/src/console/providers/catalog/store.ts +147 -8
  196. package/src/console/providers/detail/routes.ts +1 -1
  197. package/src/console/providers/oauth/callback-listener.ts +100 -18
  198. package/src/console/providers/oauth/routes.ts +92 -13
  199. package/src/console/quota/account-quota-global.ts +41 -3
  200. package/src/console/quota/account-quota-shared.ts +10 -3
  201. package/src/console/quota/account-quota-tenant.ts +259 -66
  202. package/src/console/quota/daily-growth-job.ts +308 -0
  203. package/src/console/quota/native-identity.ts +2 -2
  204. package/src/console/quota/quota-view.ts +1 -1
  205. package/src/console/remote-support/routes.ts +1 -1
  206. package/src/console/remote-support/service.ts +2 -2
  207. package/src/console/routing/model/contracts.ts +1 -1
  208. package/src/console/routing/pools/contracts.ts +2 -2
  209. package/src/console/routing/pools/import-parse.ts +3 -3
  210. package/src/console/routing/pools/relay-deploy.ts +3 -3
  211. package/src/console/settings/contracts.ts +29 -2
  212. package/src/console/settings/store.ts +20 -0
  213. package/src/console/share/share-router.ts +100 -32
  214. package/src/console/share/share-stats.ts +4 -4
  215. package/src/console/share/share-usage.ts +1 -1
  216. package/src/console/shared/errors.ts +2 -2
  217. package/src/console/shared/object.ts +18 -0
  218. package/src/console/shared/read-through-cache.ts +4 -4
  219. package/src/console/shared/transaction.ts +14 -0
  220. package/src/console/system-prompt/contracts.ts +89 -12
  221. package/src/console/system-prompt/routes.ts +98 -45
  222. package/src/console/updates/apply-update.ts +244 -0
  223. package/src/console/updates/routes.ts +252 -97
  224. package/src/main.ts +11 -5
  225. package/src/network/http2-bidi.ts +1 -1
  226. package/src/network/ip-utils.ts +298 -0
  227. package/src/network/outbound-fetch.ts +1 -1
  228. package/src/network/pool/agent.ts +7 -6
  229. package/src/network/pool/byte-accounting.ts +1 -1
  230. package/src/network/pool/resolver.ts +16 -12
  231. package/src/network/pool/selector.ts +150 -136
  232. package/src/network/pool-health-machine.ts +1 -1
  233. package/src/network/rate-limit-verdict.ts +1 -1
  234. package/src/network/retry.ts +1 -1
  235. package/src/network/ssrf.ts +20 -71
  236. package/src/observability/event-log.ts +2 -2
  237. package/src/observability/event-query.ts +59 -291
  238. package/src/observability/log-sanitize.ts +1 -1
  239. package/src/observability/metrics.ts +4 -4
  240. package/src/observability/payload-capture.ts +6 -5
  241. package/src/observability/payload-store.ts +1 -1
  242. package/src/observability/runtime-metrics.ts +1 -1
  243. package/src/observability/telemetry-buffer.ts +12 -3
  244. package/src/observability/telemetry-reconciliation.ts +3 -3
  245. package/src/observability/token-speed.ts +1 -1
  246. package/src/persistence/connection-url.ts +1 -1
  247. package/src/persistence/db-handle.ts +1 -1
  248. package/src/persistence/db-pg.ts +2 -2
  249. package/src/persistence/db-sqlite.ts +3 -3
  250. package/src/persistence/dialect.ts +5 -1
  251. package/src/persistence/readiness.ts +1 -1
  252. package/src/persistence/redis.ts +1 -1
  253. package/src/persistence/schema.ts +113 -40
  254. package/src/persistence/shared-cache.ts +2 -2
  255. package/src/persistence/sort-order.ts +1 -1
  256. package/src/persistence/stores/api-key-store.ts +10 -1
  257. package/src/persistence/stores/share-store.ts +20 -8
  258. package/src/persistence/system-prompt-store.ts +63 -18
  259. package/src/persistence/tenant-preferences.ts +1 -1
  260. package/src/persistence/ttl-cache.ts +3 -3
  261. package/src/persistence/usage-flush.ts +7 -2
  262. package/src/protocol/canonical-model.ts +1 -1
  263. package/src/protocol/codecs.ts +6 -10
  264. package/src/protocol/dsml.ts +3 -49
  265. package/src/protocol/image-clamp.ts +2 -2
  266. package/src/protocol/reasoning-text.ts +1 -1
  267. package/src/protocol/reasoning.ts +7 -52
  268. package/src/protocol/registry.ts +50 -15
  269. package/src/protocol/request/chat.ts +46 -19
  270. package/src/protocol/request/codex.ts +1 -1
  271. package/src/protocol/request/messages.ts +46 -14
  272. package/src/protocol/request/responses.ts +19 -10
  273. package/src/protocol/response/chat.ts +25 -10
  274. package/src/protocol/response/codex.ts +1 -1
  275. package/src/protocol/response/messages.ts +1 -1
  276. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  277. package/src/protocol/response/responses.ts +1 -1
  278. package/src/protocol/sse-decoder.ts +50 -1
  279. package/src/protocol/stop-reason.ts +2 -2
  280. package/src/protocol/think-split.ts +347 -0
  281. package/src/protocol/translation/attachment-integrity.ts +3 -3
  282. package/src/protocol/translation/cache-controls.ts +8 -20
  283. package/src/protocol/translation/capabilities.ts +6 -6
  284. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  285. package/src/protocol/translation/dialect/inband.ts +39 -0
  286. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  287. package/src/protocol/translation/dialect/qwen.ts +114 -0
  288. package/src/protocol/translation/dialect/resolver.ts +83 -0
  289. package/src/protocol/translation/dialect/rules.ts +93 -0
  290. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  291. package/src/protocol/translation/dialect/types.ts +58 -0
  292. package/src/protocol/translation/quirks.ts +18 -4
  293. package/src/protocol/translation/thinking.ts +12 -252
  294. package/src/protocol/translation/tool-id.ts +3 -3
  295. package/src/protocol/translation/tool-repair.ts +1 -1
  296. package/src/protocol/upstream/post-json.ts +6 -1
  297. package/src/providers/adapters/compatible-adapter.ts +42 -3
  298. package/src/providers/authentication/jwt-validator.ts +0 -5
  299. package/src/providers/authentication/oauth-client.ts +254 -253
  300. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  301. package/src/providers/bundled-discovery-providers.ts +6 -30
  302. package/src/providers/configured-openai-providers.ts +82 -97
  303. package/src/providers/default-registry.ts +23 -35
  304. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  305. package/src/providers/discovery/discovery-types.ts +0 -1
  306. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  307. package/src/providers/discovery/probe-phases.ts +48 -13
  308. package/src/providers/discovery/probing-service.ts +6 -8
  309. package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
  310. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  311. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  312. package/src/providers/integrations/antigravity/antigravity.ts +2 -2
  313. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  314. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  315. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  316. package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
  317. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  318. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  319. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
  320. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  321. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
  322. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  323. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  324. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  325. package/src/providers/integrations/cline/cline.ts +1 -1
  326. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  327. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  328. package/src/providers/integrations/commandcode/commandcode.ts +75 -115
  329. package/src/providers/integrations/commandcode/quota.ts +131 -51
  330. package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
  331. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  332. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  333. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  334. package/src/providers/integrations/cursor/cursor.ts +38 -5
  335. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  336. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  337. package/src/providers/integrations/factory/factory.ts +9 -9
  338. package/src/providers/integrations/github/github-discovery.ts +55 -10
  339. package/src/providers/integrations/github/github-oauth.ts +6 -5
  340. package/src/providers/integrations/github/github.ts +41 -14
  341. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  342. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  343. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  344. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  345. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  346. package/src/providers/integrations/grok/grok.ts +86 -36
  347. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  348. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  349. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  350. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  351. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  352. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  353. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  354. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  355. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  356. package/src/providers/integrations/kiro/kiro.ts +81 -68
  357. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  358. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  359. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  360. package/src/providers/integrations/opencode/provider.ts +17 -24
  361. package/src/providers/integrations/qoder/qoder-catalog.ts +85 -8
  362. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  363. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  364. package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
  365. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  366. package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
  367. package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
  368. package/src/providers/integrations/qoder/qoder.ts +354 -62
  369. package/src/providers/integrations/shared/connect.ts +0 -54
  370. package/src/providers/integrations/shared/hash.ts +15 -0
  371. package/src/providers/integrations/shared/session-cache.ts +136 -0
  372. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  373. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  374. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  375. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  376. package/src/providers/integrations/trae/trae.ts +53 -10
  377. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  378. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  379. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  380. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  381. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  382. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  383. package/src/providers/operations/account-error-classification.ts +131 -116
  384. package/src/providers/operations/account-health-service.ts +31 -39
  385. package/src/providers/operations/account-reset-service.ts +58 -1
  386. package/src/providers/operations/cli-platform.ts +31 -5
  387. package/src/providers/operations/client-version-resolver.ts +4 -6
  388. package/src/providers/operations/client-versions.ts +10 -7
  389. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  390. package/src/providers/operations/provider-catalog-service.ts +18 -2
  391. package/src/providers/operations/provider-credential-service.ts +19 -2
  392. package/src/providers/provider-contracts.ts +18 -7
  393. package/src/providers/provider-metadata.ts +17 -2
  394. package/src/providers/provider-model-definition.ts +22 -1
  395. package/src/providers/provider-probe.ts +4 -2
  396. package/src/providers/provider-usage.ts +81 -16
  397. package/src/providers/quota/cache.ts +29 -0
  398. package/src/providers/quota/quota-contracts.ts +4 -0
  399. package/src/providers/quota/refresh.ts +27 -4
  400. package/src/providers/quota/snapshot.ts +94 -0
  401. package/src/providers/search/search-provider.ts +1 -3
  402. package/src/providers/search/search-providers.ts +2 -1
  403. package/src/security/abuse.ts +4 -4
  404. package/src/security/admission/service.ts +2 -2
  405. package/src/security/admission/ttl.ts +61 -61
  406. package/src/security/api-key-auth.ts +16 -4
  407. package/src/security/client-router-fingerprint.ts +4 -4
  408. package/src/security/credential-codec.ts +3 -3
  409. package/src/security/csrf.ts +2 -2
  410. package/src/security/ip-boundary.ts +16 -189
  411. package/src/security/model-access-rule.ts +1 -1
  412. package/src/security/outbound-headers.ts +18 -0
  413. package/src/shared/invoke-guard.ts +47 -0
  414. package/src/transport/contracts.ts +13 -4
  415. package/src/transport/dispatch/attempt-finalize.ts +1 -1
  416. package/src/transport/dispatch/capture-policy.ts +34 -37
  417. package/src/transport/dispatch/empty-stream.ts +23 -20
  418. package/src/transport/dispatch/generation.ts +25 -45
  419. package/src/transport/dispatch/native-shared.ts +127 -0
  420. package/src/transport/dispatch/proxy-request.ts +3 -1
  421. package/src/transport/dispatch/responses-compact.ts +25 -46
  422. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  423. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  424. package/src/transport/dispatch/streaming/stream.ts +7 -3
  425. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  426. package/src/transport/dispatch/systemone.ts +20 -48
  427. package/src/transport/dispatch/token-count.ts +41 -38
  428. package/src/transport/dispatch/websearch.ts +19 -45
  429. package/src/transport/drain-endpoint.ts +1 -1
  430. package/src/transport/failure-policy.ts +6 -0
  431. package/src/transport/guards/auth-guard.ts +0 -6
  432. package/src/transport/guards/body-policy.ts +3 -3
  433. package/src/transport/guards/headroom-guard.ts +1 -1
  434. package/src/transport/guards/pipeline.ts +1 -1
  435. package/src/transport/guards/request-context.ts +12 -17
  436. package/src/transport/request/preparer.ts +286 -7
  437. package/src/transport/request/rtk/compress-request.ts +3 -3
  438. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  439. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  440. package/src/transport/request/state.ts +1 -13
  441. package/src/transport/request/system-prompt-injection.ts +74 -11
  442. package/src/transport/request/tenant-preferences.ts +22 -0
  443. package/src/transport/routing/candidate-index.ts +2 -2
  444. package/src/transport/routing/round-robin.ts +1 -1
  445. package/src/transport/routing/route-catalog.ts +17 -18
  446. package/src/transport/routing/route-model.ts +126 -33
  447. package/src/transport/routing/router.ts +152 -37
  448. package/src/transport/shutdown-notice.ts +2 -2
  449. package/src/transport/surface/adapters.ts +3 -3
  450. package/src/transport/surface/chat/encode.ts +7 -7
  451. package/src/transport/surface/chat/parse.ts +7 -7
  452. package/src/transport/surface/completion.ts +2 -2
  453. package/src/transport/surface/content-parts.ts +1 -1
  454. package/src/transport/surface/dialects.ts +2 -2
  455. package/src/transport/surface/messages/parse.ts +3 -3
  456. package/src/transport/surface/responses/adapter.ts +1 -1
  457. package/src/transport/surface/responses/errors.ts +0 -9
  458. package/src/transport/vision-handoff-cache.ts +58 -0
  459. package/src/transport/vision-handoff.ts +376 -0
  460. package/src/workers/account-identity-sweep.ts +2 -2
  461. package/src/workers/buddy-growth-tasks.ts +392 -346
  462. package/src/workers/daily-checkin.ts +102 -49
  463. package/src/workers/maintenance-lease.ts +2 -35
  464. package/src/workers/oauth-refresh-sweep.ts +8 -1
  465. package/src/workers/quota-refresh-sweep.ts +120 -82
  466. package/src/workers/scheduler.ts +67 -4
  467. package/src/workers/sweep-runner.ts +24 -5
  468. package/src/workers/zcode-gift-claim.ts +108 -0
  469. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  470. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  471. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  472. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  473. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  474. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
  475. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  476. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
  477. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  478. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  479. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
  480. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  481. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  482. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  483. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
  484. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  485. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
  486. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  487. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  488. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  489. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
  490. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  491. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  492. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
  493. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  494. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
  495. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  496. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  497. package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
  498. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  499. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  500. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
  501. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  502. package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
  503. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  504. package/dist/dashboard/assets/app-a159k0ub.css +0 -2
  505. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  506. package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
  507. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  508. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  509. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  510. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  511. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
  512. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  513. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  514. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  515. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  516. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  517. package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
  518. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  519. package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
  520. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  521. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  522. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  523. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  524. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  525. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  526. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  527. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  528. package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
  529. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  530. package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
  531. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  532. package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
  533. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  534. package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
  535. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  536. package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
  537. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  538. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  539. package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
  540. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  541. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  542. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  543. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  544. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  545. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  546. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  547. package/src/network/response-headers.ts +0 -39
  548. package/src/protocol/response/dsml.ts +0 -38
  549. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  550. /package/dist/dashboard/assets/{app-BayTTy01.js → app-Bgd3zANQ.js} +0 -0
  551. /package/dist/dashboard/assets/{app-BayTTy01.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  552. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  553. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -86,6 +86,14 @@ interface QoderModelConfig {
86
86
  * `false` marks the model as billed.
87
87
  */
88
88
  readonly is_free?: boolean;
89
+ /**
90
+ * The reasoning effort to send when the caller did not ask for one. The IDE
91
+ * surface follows the `reasoningEffort` field: a model marked `is_reasoning`
92
+ * may still stream no reasoning unless an effort is present (verified live:
93
+ * Sonus streams thinking only when effort is sent; DeepSeek thinks by
94
+ * default). Set only for models that need the push.
95
+ */
96
+ readonly default_reasoning_effort?: string;
89
97
  }
90
98
 
91
99
  /**
@@ -112,7 +120,7 @@ export const QODER_CN_MODEL_CONFIGS: Readonly<Record<string, QoderModelConfig>>
112
120
  };
113
121
 
114
122
  /**
115
- * Global/intl roster (17 keys), from the donor's `qoder_catalog_intl.json`.
123
+ * Global/intl roster (18 keys), from the donor's `qoder_catalog_intl.json`.
116
124
  *
117
125
  * Note how the shared keys differ from CN — `auto` is non-reasoning here and
118
126
  * `qmodel` carries a 1M window. That is why the rosters are not merged.
@@ -127,8 +135,11 @@ export const QODER_INTL_MODEL_CONFIGS: Readonly<Record<string, QoderModelConfig>
127
135
  fast: { id: "fast", display_name: "Fast", max_input_tokens: 1000000, max_output_tokens: 64_000, is_vl: true, is_reasoning: false },
128
136
  performance: { id: "performance", display_name: "Performance", max_input_tokens: 1000000, max_output_tokens: 64_000, is_vl: true, is_reasoning: false },
129
137
  efficient: { id: "efficient", display_name: "Efficient", max_input_tokens: 200000, max_output_tokens: 64_000, is_vl: true, is_reasoning: false },
130
- smodel: { id: "smodel", display_name: "Sonus", max_input_tokens: 1000000, max_output_tokens: 128_000, is_vl: true, is_free: false, is_reasoning: true },
131
- cmodel: { id: "cmodel", display_name: "Cantus", max_input_tokens: 1000000, max_output_tokens: 128_000, is_vl: true, is_free: false, is_reasoning: true },
138
+ // Sonus/Cantus need an explicit effort on the IDE surface; without it the
139
+ // upstream streams text only, no reasoning (verified live). DeepSeek-family
140
+ // models think by default and are left untouched.
141
+ smodel: { id: "smodel", display_name: "Sonus", max_input_tokens: 1000000, max_output_tokens: 128_000, is_vl: true, is_free: false, is_reasoning: true, default_reasoning_effort: "high" },
142
+ cmodel: { id: "cmodel", display_name: "Cantus", max_input_tokens: 1000000, max_output_tokens: 128_000, is_vl: true, is_free: false, is_reasoning: true, default_reasoning_effort: "high" },
132
143
  qmodel_38max: { id: "qmodel_38max", display_name: "Qwen3.8-Max", max_input_tokens: 1000000, max_output_tokens: 131_072, is_vl: true, is_reasoning: true },
133
144
  qfmodel: { id: "qfmodel", display_name: "Qwen3.8-Flash", max_input_tokens: 1000000, max_output_tokens: 131_072, is_vl: true, is_free: true, is_reasoning: true },
134
145
  qmodel_latest: { id: "qmodel_latest", display_name: "Qwen3.7-Max", max_input_tokens: 1000000, max_output_tokens: 131_072, is_vl: true, is_reasoning: false },
@@ -142,6 +153,9 @@ export const QODER_INTL_MODEL_CONFIGS: Readonly<Record<string, QoderModelConfig>
142
153
  // previously marked free. Display names are corrected from the same capture.
143
154
  dfmodel: { id: "dfmodel", display_name: "DeepSeek-Flash", max_input_tokens: 1000000, max_output_tokens: 384_000, is_vl: true, is_free: false, is_reasoning: true },
144
155
  mmodel: { id: "mmodel", display_name: "MiniMax-M3.1-Flash", max_input_tokens: 1000000, max_output_tokens: 512_000, is_vl: true, is_free: false, is_reasoning: false },
156
+ // `lite` is the free-tier plan package: same 1M window and multimodal input
157
+ // as `qmodel` (Qwen3.7-Plus), capped at 64k output, free tier.
158
+ lite: { id: "lite", display_name: "Lite-Qwen-3.7-Plus", max_input_tokens: 1000000, max_output_tokens: 65_536, is_vl: true, is_free: true, is_reasoning: false },
145
159
  };
146
160
 
147
161
  /**
@@ -213,13 +227,24 @@ export function qoderVirtualModels(
213
227
  // substitutes the shared unfiled fallback for null — the same marker a
214
228
  // hand-added row gets.
215
229
  ctx: target.contextLimit ?? null,
216
- ...(target.outputLimit === null ? {} : { out: target.outputLimit }),
217
- vision: target.modalities.input.includes("image"),
230
+ // A virtual override wins; otherwise inherit the target's ceiling.
231
+ ...(virtual.outputLimit !== undefined
232
+ ? { out: virtual.outputLimit }
233
+ : target.outputLimit === null
234
+ ? {}
235
+ : { out: target.outputLimit }),
236
+ vision: virtual.vision ?? target.modalities.input.includes("image"),
218
237
  reasoning: target.reasoning ?? false,
219
238
  toolCall: target.toolCall ?? false,
220
239
  // The same model under its caller-facing name belongs to the same tier,
221
- // so the pill follows the roster row rather than being restated.
222
- ...(target.freeTier === undefined ? {} : { free: target.freeTier, freeTier: target.freeTier }),
240
+ // so the pill follows the roster row rather than being restated —
241
+ // unless the virtual card explicitly overrides it (`lite` is a free
242
+ // plan package over a billed slot).
243
+ ...(virtual.free !== undefined
244
+ ? { free: virtual.free, freeTier: virtual.free }
245
+ : target.freeTier === undefined
246
+ ? {}
247
+ : { free: target.freeTier, freeTier: target.freeTier }),
223
248
  }),
224
249
  );
225
250
  }
@@ -274,11 +299,27 @@ export function mapQoderCatalogResponse(raw: unknown): QoderCatalogEntry[] {
274
299
  return entries;
275
300
  }
276
301
 
302
+ /**
303
+ * The static roster's stated output ceiling for one slot, when the roster
304
+ * states one.
305
+ *
306
+ * Upstream's live `/model/list` rows report no output limit, so a discovered
307
+ * row cannot read one from the wire. Inheriting the roster's number keeps the
308
+ * discovered row's `outputLimit` honest — without it `defineModel` falls back
309
+ * to a base-catalog/majority vote that can sit below the roster's ceiling
310
+ * (e.g. `qwen3.7-plus` votes 64k while the roster says 131k).
311
+ */
312
+ function qoderRosterMaxOutputFor(providerId: string, key: string): number | undefined {
313
+ const configs = providerId === "qodercn" ? QODER_CN_MODEL_CONFIGS : QODER_INTL_MODEL_CONFIGS;
314
+ return configs[key]?.max_output_tokens;
315
+ }
316
+
277
317
  /** Projects one live catalog row onto a Cartethyia catalog row. */
278
318
  export function qoderCatalogModel(
279
319
  entry: QoderCatalogEntry,
280
320
  providerId: string,
281
321
  ): ModelDefinition {
322
+ const rosterOutput = qoderRosterMaxOutputFor(providerId, entry.key);
282
323
  return defineModel({
283
324
  id: entry.key,
284
325
  providerId,
@@ -295,6 +336,9 @@ export function qoderCatalogModel(
295
336
  // a free row bills zero per token and belongs to the provider's free tier.
296
337
  free: entry.isFree,
297
338
  freeTier: entry.isFree,
339
+ // The live row carries no output ceiling, so inherit the static roster's
340
+ // stated one when it exists (see `qoderRosterMaxOutputFor`).
341
+ ...(rosterOutput === undefined ? {} : { out: rosterOutput }),
298
342
  });
299
343
  }
300
344
 
@@ -457,7 +501,40 @@ export async function discoverQoderModels(input: {
457
501
  lastError = new Error("Qoder model list returned no usable models");
458
502
  continue;
459
503
  }
460
- const models = enabled.map((entry) => qoderCatalogModel(entry, providerId));
504
+ const models = enabled.flatMap((entry) => {
505
+ // Publish caller-facing virtual ids (`sonus`, `lite-qwen-3.7-plus`, …)
506
+ // instead of the opaque roster keys (`smodel`, `qmodel`, …). A discovery
507
+ // row must look like what the UI offers and what a caller can type;
508
+ // keeping the raw key here made live-synced rows appear under names
509
+ // nobody can route to by display, and hid overridden cards like `lite`.
510
+ // More than one virtual may share a slot (`lite-qwen-3.7-plus` and
511
+ // `qwen3.7-plus` both dispatch to `qmodel`), so every match on the slot
512
+ // is published.
513
+ const virtuals = QODER_VIRTUAL_MODELS.filter((virtual) => virtual.target === entry.key);
514
+ if (virtuals.length === 0) return [qoderCatalogModel(entry, providerId)];
515
+ const rosterOutput = qoderRosterMaxOutputFor(providerId, entry.key);
516
+ return virtuals.map((virtual) =>
517
+ defineModel({
518
+ id: virtual.id,
519
+ providerId,
520
+ wireFamily: "chat",
521
+ endpoint: QODER_CHAT_PATH,
522
+ ctx: entry.maxInputTokens ?? null,
523
+ vision: virtual.vision ?? entry.isVl,
524
+ reasoning: entry.isReasoning,
525
+ // A virtual override wins; otherwise inherit the static roster's
526
+ // ceiling for the slot instead of a base-catalog/majority estimate
527
+ // (see `qoderRosterMaxOutputFor`).
528
+ ...(virtual.outputLimit !== undefined
529
+ ? { out: virtual.outputLimit }
530
+ : rosterOutput === undefined
531
+ ? {}
532
+ : { out: rosterOutput }),
533
+ free: virtual.free ?? entry.isFree,
534
+ freeTier: virtual.free ?? entry.isFree,
535
+ }),
536
+ );
537
+ });
461
538
  // Index the widest selectable window per key (from `context_config`,
462
539
  // already computed into `maxInputTokens` by `mapQoderCatalogResponse`) so
463
540
  // dispatch can send the maximum rather than the currently active one.
@@ -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. There is no working refresh grant —
12
- * the upstream refresh endpoint answers 403 for this flow — so no refresher
13
- * is registered and the token is used exactly as issued until the operator
14
- * re-runs login. The account's Qoder user id and machine id travel in
15
- * `auth_state`, because the token itself carries neither and every dispatch
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
- const verifier = base64UrlEncode(randomBytes(32));
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
- // CN carries a dashed nonce and additionally states the client id and
260
- // native redirect; intl stays on the 32-hex nonce and the two params it
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 (this.realm === "cn") {
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
- _refreshToken: string,
399
- _signal?: AbortSignal,
484
+ refreshToken: string,
485
+ signal?: AbortSignal,
486
+ context?: OAuthRefreshContext,
400
487
  ): Promise<OAuthTokenRefreshResult> {
401
- throw new Error("Qoder does not support token refresh; sign in again");
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 plain bearer token
4
- // — no COSY signature on this surface, which is the one place the business API
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
- return percentWindow(kind, label, null, resetsAt, used, limit, "pooled");
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 ${credential}`,
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);
@@ -41,9 +41,13 @@ export const QODER_MODELS_PATH = "/algo/api/v2/model/list?Encode=1";
41
41
 
42
42
  /**
43
43
  * The modern model-server surface used by the IDE / QoderWake (OpenAI-shaped
44
- * chunks, plain bearer auth). This surface streams immediately — the legacy
45
- * `/algo` chat endpoint answers `10605 isQueued` and parks requests in a
46
- * server-side queue, so dispatch prefers this surface when it is available.
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.
47
51
  */
48
52
  export const QODER_IDE_CHAT_PATH = "/model/v1/chat/completions";
49
53
 
@@ -76,7 +80,9 @@ export const QODER_REALMS: Readonly<Record<QoderRealm, QoderRealmConfig>> = {
76
80
  gatewayFallbacks: [],
77
81
  openapi: "https://openapi.qoder.com.cn",
78
82
  website: "https://qoder.com.cn",
79
- clientId: "1c5e33e1-364d-4ce6-b02c-acaa81274a5c",
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",
80
86
  redirectUri: "qoder-work-cn://",
81
87
  userAgent: "QoderWork/1.1.64",
82
88
  },
@@ -112,30 +118,3 @@ export function qoderInferenceCandidates(
112
118
  }
113
119
  return [config.gateway, ...config.gatewayFallbacks];
114
120
  }
115
-
116
- /**
117
- * Model keys that exist in exactly one realm.
118
- *
119
- * From the donor's `INTL_EXCLUSIVE` / `CN_EXCLUSIVE` tables. A request for one
120
- * of these on the wrong realm does not degrade — it is refused — so the roster
121
- * each realm publishes is the authority, not a union.
122
- */
123
- export const QODER_INTL_EXCLUSIVE: ReadonlySet<string> = new Set([
124
- "fast",
125
- "efficient",
126
- "cmodel",
127
- "smodel",
128
- "ultimate",
129
- "performance",
130
- ]);
131
-
132
- export const QODER_CN_EXCLUSIVE: ReadonlySet<string> = new Set([
133
- "q37fmodel",
134
- "gm51model",
135
- ]);
136
-
137
- /** True when this model key is served only by the other realm. */
138
- export function isForeignQoderModel(realm: QoderRealm, modelId: string): boolean {
139
- const foreign = realm === "cn" ? QODER_INTL_EXCLUSIVE : QODER_CN_EXCLUSIVE;
140
- return foreign.has(modelId.trim().toLowerCase());
141
- }