cartethyia 2.7.4 → 2.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (595) hide show
  1. package/.env.example +5 -0
  2. package/README.md +18 -6
  3. package/dist/dashboard/assets/BannedPage-B1N603cM.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-B1N603cM.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js +3 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js.gz +0 -0
  7. package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js +1 -0
  8. package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-D762MIkp.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-D762MIkp.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js +899 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js.gz +0 -0
  19. package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js +1 -0
  20. package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-C6WlpU5E.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-DxjvUzSD.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-DxjvUzSD.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-OaHScjpX.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-OaHScjpX.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js.gz +0 -0
  37. package/dist/dashboard/assets/SetupPage-44BZrxFC.js +1 -0
  38. package/dist/dashboard/assets/SetupPage-44BZrxFC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-DRPavCdU.js} +4 -4
  40. package/dist/dashboard/assets/StudioPage-DRPavCdU.js.gz +0 -0
  41. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
  42. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
  43. package/dist/dashboard/assets/UsagePage-B7M7LvMM.js +56 -0
  44. package/dist/dashboard/assets/UsagePage-B7M7LvMM.js.gz +0 -0
  45. package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
  46. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  47. package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
  48. package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
  49. package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
  50. package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
  51. package/dist/dashboard/assets/assert-CidQVoNo.js +1 -0
  52. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  53. package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-BaX2GTrn.js} +1 -1
  54. package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
  55. package/dist/dashboard/assets/{banner-DympBNq4.js → banner-DFH4Bkte.js} +1 -1
  56. package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
  57. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  58. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  59. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  60. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  61. package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CxDw0lK6.js} +1 -1
  62. package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
  63. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  64. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  65. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js +1 -0
  66. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
  67. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +1 -0
  68. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  69. package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-DfS1ntiq.js} +1 -1
  70. package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
  71. package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
  72. package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
  73. package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-QqPul6sC.js} +1 -1
  74. package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
  75. package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
  76. package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-D6WvojeD.js +4 -0
  78. package/dist/dashboard/assets/entry-D6WvojeD.js.gz +0 -0
  79. package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
  80. package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
  81. package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-u7JPB5BX.js} +1 -1
  82. package/dist/dashboard/assets/formatters-u7JPB5BX.js.gz +0 -0
  83. package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
  84. package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
  85. package/dist/dashboard/assets/{index-DBNPOfhV.js → index-Ko4dLRnV.js} +2 -2
  86. package/dist/dashboard/assets/index-Ko4dLRnV.js.gz +0 -0
  87. package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-CHJQwxLa.js} +1 -1
  88. package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
  89. package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BlmlizVV.js} +1 -1
  90. package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
  91. package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-Bz93I-yE.js} +1 -1
  92. package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
  93. package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Ml16KCfu.js} +1 -1
  94. package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
  95. package/dist/dashboard/assets/live-7_QpnYGu.js +1 -0
  96. package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
  97. package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
  98. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  99. package/dist/dashboard/assets/network-CuMDaTzT.js +1 -0
  100. package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
  101. package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
  102. package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
  103. package/dist/dashboard/assets/pagination-B5aOoweZ.js +1 -0
  104. package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
  105. package/dist/dashboard/assets/providers/zai.webp +0 -0
  106. package/dist/dashboard/assets/providers/zcode.webp +0 -0
  107. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
  108. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
  109. package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
  110. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  111. package/dist/dashboard/assets/routing-fnCsxgNO.js +1 -0
  112. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  113. package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BHqpd-bg.js} +2 -2
  114. package/dist/dashboard/assets/select-BHqpd-bg.js.gz +0 -0
  115. package/dist/dashboard/assets/settings-BxslsErn.js +1 -0
  116. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  117. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  118. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  119. package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-j7cXjgYX.js} +1 -1
  120. package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
  121. package/dist/dashboard/assets/{state-CkfdYWel.js → state-DHmEnLa2.js} +1 -1
  122. package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
  123. package/dist/dashboard/assets/{switch-SYm237HG.js → switch-DmZcyTIC.js} +1 -1
  124. package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
  125. package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-CbCP4w1o.js} +1 -1
  126. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  127. package/dist/dashboard/console-backgrounds.jpg +0 -0
  128. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  129. package/dist/dashboard/index.html +2 -2
  130. package/dist/dashboard/index.html.gz +0 -0
  131. package/dist/dashboard/precompressed-assets.json +60 -59
  132. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  133. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  134. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  135. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  136. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  137. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  138. package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
  139. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  140. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  141. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  142. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  143. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  144. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  145. package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
  146. package/package.json +4 -6
  147. package/scripts/cli/update.ts +15 -41
  148. package/src/boot/app.ts +58 -2
  149. package/src/boot/dependencies.ts +794 -732
  150. package/src/boot/lifecycle.ts +11 -12
  151. package/src/config/cooldowns.ts +34 -55
  152. package/src/config/network.ts +1 -1
  153. package/src/config/package-paths.ts +1 -1
  154. package/src/config/providers.ts +19 -14
  155. package/src/config/resources.ts +0 -5
  156. package/src/config/runtime.ts +2 -5
  157. package/src/config/spec.ts +33 -6
  158. package/src/config/usage.ts +27 -0
  159. package/src/console/api-keys/ban-constants.ts +38 -0
  160. package/src/console/api-keys/contracts.ts +71 -62
  161. package/src/console/api-keys/routes.ts +56 -24
  162. package/src/console/auth/access.ts +4 -4
  163. package/src/console/auth/account-reset.ts +1 -1
  164. package/src/console/auth/audit-recorder.ts +1 -1
  165. package/src/console/auth/lockout.ts +7 -3
  166. package/src/console/auth/row-coercion.ts +57 -12
  167. package/src/console/auth/session-resolver.ts +1 -1
  168. package/src/console/auth/session-service.ts +8 -2
  169. package/src/console/auth/session.ts +37 -31
  170. package/src/console/auth/setup-service.ts +4 -3
  171. package/src/console/auth/user-store.ts +1 -1
  172. package/src/console/backup/9router-map.ts +0 -3
  173. package/src/console/backup/contracts.ts +9 -9
  174. package/src/console/backup/export-endpoint.ts +5 -6
  175. package/src/console/backup/nine-router.ts +1 -7
  176. package/src/console/backup/routes.ts +29 -33
  177. package/src/console/backup/service.ts +468 -468
  178. package/src/console/backup/store.ts +47 -29
  179. package/src/console/backup/validate.ts +11 -1
  180. package/src/console/cli-tools/contracts.ts +43 -299
  181. package/src/console/cli-tools/fs-ops.ts +29 -0
  182. package/src/console/cli-tools/injectors/claude.ts +3 -3
  183. package/src/console/cli-tools/injectors/codex.ts +181 -181
  184. package/src/console/cli-tools/injectors/driver.ts +150 -247
  185. package/src/console/cli-tools/injectors/kilo.ts +146 -21
  186. package/src/console/cli-tools/injectors/opencode.ts +176 -153
  187. package/src/console/cli-tools/routes.ts +7 -1
  188. package/src/console/cli-tools/service.ts +8 -7
  189. package/src/console/cli-tools/store.ts +2 -2
  190. package/src/console/console-router.ts +32 -0
  191. package/src/console/dashboard-assets.ts +9 -9
  192. package/src/console/domain-registration.ts +16 -2
  193. package/src/console/inferhub/client.ts +0 -7
  194. package/src/console/inferhub/key-store.ts +1 -1
  195. package/src/console/inferhub/normalize.ts +2 -2
  196. package/src/console/inferhub/routes.ts +1 -1
  197. package/src/console/inferhub/types.ts +1 -1
  198. package/src/console/observability/contracts.ts +7 -1
  199. package/src/console/observability/live.ts +31 -18
  200. package/src/console/observability/logs.ts +1 -1
  201. package/src/console/observability/sse.ts +62 -8
  202. package/src/console/observability/store.ts +23 -4
  203. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  204. package/src/console/providers/catalog/contracts.ts +39 -0
  205. package/src/console/providers/catalog/model-operations.ts +1 -1
  206. package/src/console/providers/catalog/provider-operations.ts +95 -17
  207. package/src/console/providers/catalog/public-model-store.ts +0 -0
  208. package/src/console/providers/catalog/routes.ts +27 -3
  209. package/src/console/providers/catalog/store.ts +147 -8
  210. package/src/console/providers/detail/routes.ts +1 -1
  211. package/src/console/providers/oauth/callback-listener.ts +100 -18
  212. package/src/console/providers/oauth/routes.ts +92 -13
  213. package/src/console/quota/account-quota-global.ts +41 -3
  214. package/src/console/quota/account-quota-shared.ts +10 -3
  215. package/src/console/quota/account-quota-tenant.ts +259 -66
  216. package/src/console/quota/daily-growth-job.ts +308 -0
  217. package/src/console/quota/native-identity.ts +2 -2
  218. package/src/console/quota/quota-view.ts +1 -1
  219. package/src/console/remote-support/routes.ts +1 -1
  220. package/src/console/remote-support/service.ts +2 -2
  221. package/src/console/routing/model/contracts.ts +1 -1
  222. package/src/console/routing/pools/contracts.ts +2 -2
  223. package/src/console/routing/pools/import-parse.ts +3 -3
  224. package/src/console/routing/pools/relay-deploy.ts +3 -3
  225. package/src/console/settings/contracts.ts +29 -2
  226. package/src/console/settings/store.ts +20 -0
  227. package/src/console/share/share-router.ts +100 -32
  228. package/src/console/share/share-stats.ts +11 -13
  229. package/src/console/share/share-usage.ts +1 -1
  230. package/src/console/shared/errors.ts +2 -2
  231. package/src/console/shared/object.ts +18 -0
  232. package/src/console/shared/read-through-cache.ts +4 -4
  233. package/src/console/shared/transaction.ts +14 -0
  234. package/src/console/system-prompt/contracts.ts +89 -12
  235. package/src/console/system-prompt/routes.ts +98 -45
  236. package/src/console/updates/apply-update.ts +244 -0
  237. package/src/console/updates/routes.ts +252 -97
  238. package/src/main.ts +11 -5
  239. package/src/network/http2-bidi.ts +1 -1
  240. package/src/network/ip-utils.ts +298 -0
  241. package/src/network/outbound-fetch.ts +1 -1
  242. package/src/network/pool/agent.ts +7 -6
  243. package/src/network/pool/byte-accounting.ts +1 -1
  244. package/src/network/pool/resolver.ts +16 -12
  245. package/src/network/pool/selector.ts +150 -136
  246. package/src/network/pool-health-machine.ts +1 -1
  247. package/src/network/rate-limit-verdict.ts +1 -1
  248. package/src/network/retry.ts +1 -1
  249. package/src/network/ssrf.ts +20 -71
  250. package/src/observability/event-log.ts +9 -2
  251. package/src/observability/event-query.ts +61 -291
  252. package/src/observability/log-sanitize.ts +1 -1
  253. package/src/observability/metrics.ts +4 -4
  254. package/src/observability/payload-capture.ts +6 -5
  255. package/src/observability/payload-store.ts +1 -1
  256. package/src/observability/runtime-metrics.ts +1 -1
  257. package/src/observability/telemetry-buffer.ts +19 -3
  258. package/src/observability/telemetry-reconciliation.ts +3 -3
  259. package/src/observability/token-speed.ts +1 -1
  260. package/src/persistence/connection-url.ts +1 -1
  261. package/src/persistence/db-handle.ts +1 -1
  262. package/src/persistence/db-pg.ts +2 -2
  263. package/src/persistence/db-sqlite.ts +3 -3
  264. package/src/persistence/dialect.ts +5 -1
  265. package/src/persistence/readiness.ts +1 -1
  266. package/src/persistence/redis.ts +1 -1
  267. package/src/persistence/schema.ts +121 -40
  268. package/src/persistence/shared-cache.ts +2 -2
  269. package/src/persistence/sort-order.ts +1 -1
  270. package/src/persistence/stores/api-key-store.ts +29 -2
  271. package/src/persistence/stores/share-store.ts +20 -8
  272. package/src/persistence/system-prompt-store.ts +63 -18
  273. package/src/persistence/tenant-preferences.ts +1 -1
  274. package/src/persistence/ttl-cache.ts +3 -3
  275. package/src/persistence/usage-flush.ts +7 -2
  276. package/src/protocol/canonical-model.ts +1 -1
  277. package/src/protocol/codecs.ts +6 -10
  278. package/src/protocol/dsml.ts +8 -125
  279. package/src/protocol/image-clamp.ts +2 -2
  280. package/src/protocol/reasoning-text.ts +1 -1
  281. package/src/protocol/reasoning.ts +7 -52
  282. package/src/protocol/registry.ts +50 -15
  283. package/src/protocol/request/chat.ts +46 -19
  284. package/src/protocol/request/codex.ts +1 -1
  285. package/src/protocol/request/messages.ts +46 -14
  286. package/src/protocol/request/responses.ts +19 -10
  287. package/src/protocol/response/chat.ts +25 -10
  288. package/src/protocol/response/codex.ts +1 -1
  289. package/src/protocol/response/messages.ts +1 -1
  290. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  291. package/src/protocol/response/responses.ts +1 -1
  292. package/src/protocol/sse-decoder.ts +50 -1
  293. package/src/protocol/stop-reason.ts +2 -2
  294. package/src/protocol/think-split.ts +347 -0
  295. package/src/protocol/translation/attachment-integrity.ts +3 -3
  296. package/src/protocol/translation/cache-controls.ts +8 -20
  297. package/src/protocol/translation/capabilities.ts +6 -6
  298. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  299. package/src/protocol/translation/dialect/inband.ts +39 -0
  300. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  301. package/src/protocol/translation/dialect/qwen.ts +114 -0
  302. package/src/protocol/translation/dialect/resolver.ts +83 -0
  303. package/src/protocol/translation/dialect/rules.ts +93 -0
  304. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  305. package/src/protocol/translation/dialect/types.ts +58 -0
  306. package/src/protocol/translation/quirks.ts +18 -4
  307. package/src/protocol/translation/thinking.ts +12 -252
  308. package/src/protocol/translation/tool-id.ts +3 -3
  309. package/src/protocol/translation/tool-repair.ts +1 -1
  310. package/src/protocol/upstream/post-json.ts +6 -1
  311. package/src/providers/adapters/compatible-adapter.ts +71 -4
  312. package/src/providers/authentication/jwt-validator.ts +0 -5
  313. package/src/providers/authentication/oauth-client.ts +254 -253
  314. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  315. package/src/providers/bundled-discovery-providers.ts +6 -30
  316. package/src/providers/configured-openai-providers.ts +82 -97
  317. package/src/providers/default-registry.ts +35 -37
  318. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  319. package/src/providers/discovery/discovery-types.ts +0 -1
  320. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  321. package/src/providers/discovery/probe-phases.ts +48 -13
  322. package/src/providers/discovery/probing-service.ts +6 -8
  323. package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
  324. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  325. package/src/providers/integrations/antigravity/antigravity-protocol.ts +88 -28
  326. package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
  327. package/src/providers/integrations/antigravity/antigravity.ts +22 -13
  328. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  329. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  330. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  331. package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
  332. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  333. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  334. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
  335. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  336. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
  337. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  338. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  339. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  340. package/src/providers/integrations/cline/cline.ts +1 -1
  341. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  342. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  343. package/src/providers/integrations/commandcode/commandcode.ts +75 -115
  344. package/src/providers/integrations/commandcode/quota.ts +131 -51
  345. package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
  346. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  347. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  348. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  349. package/src/providers/integrations/cursor/cursor.ts +35 -3
  350. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  351. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  352. package/src/providers/integrations/factory/factory.ts +9 -9
  353. package/src/providers/integrations/github/github-discovery.ts +70 -12
  354. package/src/providers/integrations/github/github-oauth.ts +184 -14
  355. package/src/providers/integrations/github/github.ts +134 -26
  356. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  357. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  358. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  359. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  360. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  361. package/src/providers/integrations/grok/grok.ts +86 -36
  362. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  363. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  364. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  365. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  366. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  367. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  368. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  369. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  370. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  371. package/src/providers/integrations/kiro/kiro.ts +80 -68
  372. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  373. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  374. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  375. package/src/providers/integrations/opencode/provider.ts +17 -24
  376. package/src/providers/integrations/qoder/qoder-catalog.ts +94 -11
  377. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  378. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  379. package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
  380. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  381. package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
  382. package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
  383. package/src/providers/integrations/qoder/qoder.ts +422 -71
  384. package/src/providers/integrations/shared/connect.ts +0 -54
  385. package/src/providers/integrations/shared/hash.ts +15 -0
  386. package/src/providers/integrations/shared/session-cache.ts +136 -0
  387. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  388. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  389. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  390. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  391. package/src/providers/integrations/trae/trae.ts +51 -8
  392. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  393. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  394. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  395. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  396. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  397. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  398. package/src/providers/operations/account-error-classification.ts +198 -133
  399. package/src/providers/operations/account-health-service.ts +31 -39
  400. package/src/providers/operations/account-reset-service.ts +58 -1
  401. package/src/providers/operations/byok-wire-profile.ts +19 -15
  402. package/src/providers/operations/cli-platform.ts +31 -5
  403. package/src/providers/operations/client-version-resolver.ts +4 -6
  404. package/src/providers/operations/client-versions.ts +10 -7
  405. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  406. package/src/providers/operations/provider-catalog-service.ts +18 -2
  407. package/src/providers/operations/provider-credential-service.ts +19 -2
  408. package/src/providers/provider-contracts.ts +18 -7
  409. package/src/providers/provider-metadata.ts +17 -2
  410. package/src/providers/provider-model-definition.ts +22 -1
  411. package/src/providers/provider-probe.ts +4 -2
  412. package/src/providers/provider-usage.ts +50 -19
  413. package/src/providers/quota/cache.ts +29 -0
  414. package/src/providers/quota/quota-contracts.ts +4 -0
  415. package/src/providers/quota/refresh.ts +27 -4
  416. package/src/providers/quota/snapshot.ts +94 -0
  417. package/src/providers/search/search-provider.ts +1 -3
  418. package/src/providers/search/search-providers.ts +2 -1
  419. package/src/security/abuse.ts +4 -4
  420. package/src/security/admission/service.ts +13 -6
  421. package/src/security/admission/ttl.ts +61 -61
  422. package/src/security/api-key-auth.ts +17 -3
  423. package/src/security/client-router-fingerprint.ts +4 -4
  424. package/src/security/credential-codec.ts +3 -3
  425. package/src/security/csrf.ts +2 -2
  426. package/src/security/ip-boundary.ts +16 -189
  427. package/src/security/model-access-rule.ts +1 -1
  428. package/src/security/outbound-headers.ts +18 -0
  429. package/src/shared/invoke-guard.ts +47 -0
  430. package/src/transport/contracts.ts +13 -4
  431. package/src/transport/dispatch/attempt-finalize.ts +70 -22
  432. package/src/transport/dispatch/attempt-loop.ts +22 -0
  433. package/src/transport/dispatch/capture-policy.ts +34 -37
  434. package/src/transport/dispatch/empty-stream.ts +23 -20
  435. package/src/transport/dispatch/generation.ts +25 -45
  436. package/src/transport/dispatch/native-shared.ts +127 -0
  437. package/src/transport/dispatch/proxy-request.ts +3 -1
  438. package/src/transport/dispatch/responses-compact.ts +25 -46
  439. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  440. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  441. package/src/transport/dispatch/streaming/stream.ts +7 -3
  442. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  443. package/src/transport/dispatch/systemone.ts +20 -48
  444. package/src/transport/dispatch/token-count.ts +41 -38
  445. package/src/transport/dispatch/websearch.ts +19 -45
  446. package/src/transport/drain-endpoint.ts +1 -1
  447. package/src/transport/failure-policy.ts +21 -1
  448. package/src/transport/guards/auth-guard.ts +0 -6
  449. package/src/transport/guards/body-policy.ts +3 -3
  450. package/src/transport/guards/error-lifecycle.ts +4 -0
  451. package/src/transport/guards/headroom-guard.ts +1 -1
  452. package/src/transport/guards/pipeline.ts +1 -1
  453. package/src/transport/guards/request-context.ts +12 -17
  454. package/src/transport/request/preparer.ts +286 -7
  455. package/src/transport/request/rtk/compress-request.ts +3 -3
  456. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  457. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  458. package/src/transport/request/state.ts +7 -13
  459. package/src/transport/request/system-prompt-injection.ts +74 -11
  460. package/src/transport/request/tenant-preferences.ts +22 -0
  461. package/src/transport/routing/candidate-index.ts +2 -2
  462. package/src/transport/routing/round-robin.ts +1 -1
  463. package/src/transport/routing/route-catalog.ts +17 -18
  464. package/src/transport/routing/route-model.ts +126 -33
  465. package/src/transport/routing/router.ts +159 -37
  466. package/src/transport/shutdown-notice.ts +2 -2
  467. package/src/transport/surface/adapters.ts +3 -3
  468. package/src/transport/surface/chat/encode.ts +7 -7
  469. package/src/transport/surface/chat/parse.ts +7 -7
  470. package/src/transport/surface/completion.ts +2 -2
  471. package/src/transport/surface/content-parts.ts +1 -1
  472. package/src/transport/surface/dialects.ts +2 -2
  473. package/src/transport/surface/messages/parse.ts +3 -3
  474. package/src/transport/surface/reasoning-summary.ts +9 -14
  475. package/src/transport/surface/responses/adapter.ts +1 -1
  476. package/src/transport/surface/responses/errors.ts +0 -9
  477. package/src/transport/vision-handoff-cache.ts +58 -0
  478. package/src/transport/vision-handoff.ts +431 -0
  479. package/src/workers/account-identity-sweep.ts +2 -2
  480. package/src/workers/buddy-growth-tasks.ts +392 -346
  481. package/src/workers/daily-checkin.ts +102 -49
  482. package/src/workers/maintenance-lease.ts +2 -35
  483. package/src/workers/oauth-refresh-sweep.ts +8 -1
  484. package/src/workers/quota-refresh-sweep.ts +120 -82
  485. package/src/workers/scheduler.ts +67 -4
  486. package/src/workers/sweep-runner.ts +24 -5
  487. package/src/workers/zcode-gift-claim.ts +108 -0
  488. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  489. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  490. package/dist/dashboard/assets/BannedPage-ChG5svFb.js +0 -1
  491. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  492. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js +0 -1
  493. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  494. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js +0 -1
  495. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  496. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
  497. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  498. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
  499. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  500. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +0 -1
  501. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  502. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
  503. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  504. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js +0 -1
  505. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  506. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  507. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
  508. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  509. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
  510. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  511. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  512. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  513. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
  514. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  515. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  516. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
  517. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  518. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
  519. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  520. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js +0 -1
  521. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  522. package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
  523. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  524. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  525. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
  526. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  527. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
  528. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  529. package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
  530. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  531. package/dist/dashboard/assets/app-BayTTy01.js +0 -9
  532. package/dist/dashboard/assets/app-BayTTy01.js.gz +0 -0
  533. package/dist/dashboard/assets/app-a159k0ub.css +0 -2
  534. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  535. package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
  536. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  537. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  538. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  539. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  540. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
  541. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  542. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  543. package/dist/dashboard/assets/dialog-8fiE6tec.js +0 -1
  544. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  545. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  546. package/dist/dashboard/assets/entry-BSAsYsmV.js +0 -1
  547. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  548. package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
  549. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  550. package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
  551. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  552. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  553. package/dist/dashboard/assets/github-badge-CPK47xQx.js +0 -1
  554. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  555. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  556. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  557. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  558. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  559. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  560. package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
  561. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  562. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  563. package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
  564. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  565. package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
  566. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  567. package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
  568. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  569. package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
  570. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  571. package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
  572. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  573. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  574. package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
  575. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  576. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  577. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  578. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  579. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  580. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  581. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  582. package/src/console/cli-tools/injectors/cline.ts +0 -110
  583. package/src/console/cli-tools/injectors/copilot.ts +0 -108
  584. package/src/console/cli-tools/injectors/cowork.ts +0 -64
  585. package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
  586. package/src/console/cli-tools/injectors/droid.ts +0 -96
  587. package/src/console/cli-tools/injectors/grok-build.ts +0 -70
  588. package/src/console/cli-tools/injectors/hermes.ts +0 -101
  589. package/src/console/cli-tools/injectors/jcode.ts +0 -115
  590. package/src/console/cli-tools/injectors/openclaw.ts +0 -161
  591. package/src/network/response-headers.ts +0 -39
  592. package/src/protocol/response/dsml.ts +0 -38
  593. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  594. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  595. /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,13 +120,19 @@ 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.
119
127
  */
120
128
  export const QODER_INTL_MODEL_CONFIGS: Readonly<Record<string, QoderModelConfig>> = {
121
- auto: { id: "auto", display_name: "Auto", max_input_tokens: 200000, max_output_tokens: 64_000, is_vl: true, is_reasoning: false },
129
+ // Every row declares its *maximum selectable* window (1M), not the currently
130
+ // active one: Qoder publishes a `context_config` of selectable windows per
131
+ // row (1M/400K/200K/…), and the adapter dispatches the widest
132
+ // (`qoderMaxContextFor`). The live list's `max_input_tokens` only names the
133
+ // currently-selected window (often 180K/200K), which is not a ceiling —
134
+ // declaring that would cap a conversation a 1M-capable account could run.
135
+ auto: { id: "auto", display_name: "Auto", max_input_tokens: 1000000, max_output_tokens: 64_000, is_vl: true, is_reasoning: false },
122
136
  ultimate: { id: "ultimate", display_name: "Ultimate", max_input_tokens: 1000000, max_output_tokens: 128_000, is_vl: true, is_free: false, is_reasoning: true },
123
137
  // `fast` is what the live roster reports today; `performance` is the older
124
138
  // key for the same slot. Both are kept so a hand-added model id written
@@ -126,9 +140,12 @@ export const QODER_INTL_MODEL_CONFIGS: Readonly<Record<string, QoderModelConfig>
126
140
  // discovery supersedes it on the first successful fetch.
127
141
  fast: { id: "fast", display_name: "Fast", max_input_tokens: 1000000, max_output_tokens: 64_000, is_vl: true, is_reasoning: false },
128
142
  performance: { id: "performance", display_name: "Performance", max_input_tokens: 1000000, max_output_tokens: 64_000, is_vl: true, is_reasoning: false },
129
- 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 },
143
+ efficient: { id: "efficient", display_name: "Efficient", max_input_tokens: 1000000, max_output_tokens: 64_000, is_vl: true, is_reasoning: false },
144
+ // Sonus/Cantus need an explicit effort on the IDE surface; without it the
145
+ // upstream streams text only, no reasoning (verified live). DeepSeek-family
146
+ // models think by default and are left untouched.
147
+ 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" },
148
+ 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
149
  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
150
  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
151
  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 },
@@ -141,7 +158,10 @@ export const QODER_INTL_MODEL_CONFIGS: Readonly<Record<string, QoderModelConfig>
141
158
  // Live roster (2026-11-01) reports dfmodel as billed (is_free false); it was
142
159
  // previously marked free. Display names are corrected from the same capture.
143
160
  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
- 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 },
161
+ 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: true },
162
+ // `lite` is the free-tier plan package: same 1M window and multimodal input
163
+ // as `qmodel` (Qwen3.7-Plus), capped at 64k output, free tier.
164
+ 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
165
  };
146
166
 
147
167
  /**
@@ -213,13 +233,24 @@ export function qoderVirtualModels(
213
233
  // substitutes the shared unfiled fallback for null — the same marker a
214
234
  // hand-added row gets.
215
235
  ctx: target.contextLimit ?? null,
216
- ...(target.outputLimit === null ? {} : { out: target.outputLimit }),
217
- vision: target.modalities.input.includes("image"),
236
+ // A virtual override wins; otherwise inherit the target's ceiling.
237
+ ...(virtual.outputLimit !== undefined
238
+ ? { out: virtual.outputLimit }
239
+ : target.outputLimit === null
240
+ ? {}
241
+ : { out: target.outputLimit }),
242
+ vision: virtual.vision ?? target.modalities.input.includes("image"),
218
243
  reasoning: target.reasoning ?? false,
219
244
  toolCall: target.toolCall ?? false,
220
245
  // 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 }),
246
+ // so the pill follows the roster row rather than being restated —
247
+ // unless the virtual card explicitly overrides it (`lite` is a free
248
+ // plan package over a billed slot).
249
+ ...(virtual.free !== undefined
250
+ ? { free: virtual.free, freeTier: virtual.free }
251
+ : target.freeTier === undefined
252
+ ? {}
253
+ : { free: target.freeTier, freeTier: target.freeTier }),
223
254
  }),
224
255
  );
225
256
  }
@@ -274,11 +305,27 @@ export function mapQoderCatalogResponse(raw: unknown): QoderCatalogEntry[] {
274
305
  return entries;
275
306
  }
276
307
 
308
+ /**
309
+ * The static roster's stated output ceiling for one slot, when the roster
310
+ * states one.
311
+ *
312
+ * Upstream's live `/model/list` rows report no output limit, so a discovered
313
+ * row cannot read one from the wire. Inheriting the roster's number keeps the
314
+ * discovered row's `outputLimit` honest — without it `defineModel` falls back
315
+ * to a base-catalog/majority vote that can sit below the roster's ceiling
316
+ * (e.g. `qwen3.7-plus` votes 64k while the roster says 131k).
317
+ */
318
+ function qoderRosterMaxOutputFor(providerId: string, key: string): number | undefined {
319
+ const configs = providerId === "qodercn" ? QODER_CN_MODEL_CONFIGS : QODER_INTL_MODEL_CONFIGS;
320
+ return configs[key]?.max_output_tokens;
321
+ }
322
+
277
323
  /** Projects one live catalog row onto a Cartethyia catalog row. */
278
324
  export function qoderCatalogModel(
279
325
  entry: QoderCatalogEntry,
280
326
  providerId: string,
281
327
  ): ModelDefinition {
328
+ const rosterOutput = qoderRosterMaxOutputFor(providerId, entry.key);
282
329
  return defineModel({
283
330
  id: entry.key,
284
331
  providerId,
@@ -295,6 +342,9 @@ export function qoderCatalogModel(
295
342
  // a free row bills zero per token and belongs to the provider's free tier.
296
343
  free: entry.isFree,
297
344
  freeTier: entry.isFree,
345
+ // The live row carries no output ceiling, so inherit the static roster's
346
+ // stated one when it exists (see `qoderRosterMaxOutputFor`).
347
+ ...(rosterOutput === undefined ? {} : { out: rosterOutput }),
298
348
  });
299
349
  }
300
350
 
@@ -457,7 +507,40 @@ export async function discoverQoderModels(input: {
457
507
  lastError = new Error("Qoder model list returned no usable models");
458
508
  continue;
459
509
  }
460
- const models = enabled.map((entry) => qoderCatalogModel(entry, providerId));
510
+ const models = enabled.flatMap((entry) => {
511
+ // Publish caller-facing virtual ids (`sonus`, `lite-qwen-3.7-plus`, …)
512
+ // instead of the opaque roster keys (`smodel`, `qmodel`, …). A discovery
513
+ // row must look like what the UI offers and what a caller can type;
514
+ // keeping the raw key here made live-synced rows appear under names
515
+ // nobody can route to by display, and hid overridden cards like `lite`.
516
+ // More than one virtual may share a slot (`lite-qwen-3.7-plus` and
517
+ // `qwen3.7-plus` both dispatch to `qmodel`), so every match on the slot
518
+ // is published.
519
+ const virtuals = QODER_VIRTUAL_MODELS.filter((virtual) => virtual.target === entry.key);
520
+ if (virtuals.length === 0) return [qoderCatalogModel(entry, providerId)];
521
+ const rosterOutput = qoderRosterMaxOutputFor(providerId, entry.key);
522
+ return virtuals.map((virtual) =>
523
+ defineModel({
524
+ id: virtual.id,
525
+ providerId,
526
+ wireFamily: "chat",
527
+ endpoint: QODER_CHAT_PATH,
528
+ ctx: entry.maxInputTokens ?? null,
529
+ vision: virtual.vision ?? entry.isVl,
530
+ reasoning: entry.isReasoning,
531
+ // A virtual override wins; otherwise inherit the static roster's
532
+ // ceiling for the slot instead of a base-catalog/majority estimate
533
+ // (see `qoderRosterMaxOutputFor`).
534
+ ...(virtual.outputLimit !== undefined
535
+ ? { out: virtual.outputLimit }
536
+ : rosterOutput === undefined
537
+ ? {}
538
+ : { out: rosterOutput }),
539
+ free: virtual.free ?? entry.isFree,
540
+ freeTier: virtual.free ?? entry.isFree,
541
+ }),
542
+ );
543
+ });
461
544
  // Index the widest selectable window per key (from `context_config`,
462
545
  // already computed into `maxInputTokens` by `mapQoderCatalogResponse`) so
463
546
  // 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
- }