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
@@ -0,0 +1,152 @@
1
+ import { asCoercedNumber, asRecord, asTrimmedString } from "../../../shared/coerce";
2
+ import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../quota/quota-contracts";
3
+
4
+ const BALANCE_URL = "https://zcode.z.ai/api/v1/zcode-plan/billing/balance?app_version=3.16.3";
5
+ const DEVICE_MID = "9f1b0d4e-3c2a-4e7b-9f1d-6a2c8b5e0d31";
6
+ const APP_VERSION = "3.16.3";
7
+
8
+ function instant(value: unknown): string | null {
9
+ const seconds = asCoercedNumber(value);
10
+ if (seconds === undefined || seconds <= 0) return null;
11
+ const date = new Date(seconds * 1000);
12
+ return Number.isFinite(date.getTime()) ? date.toISOString() : null;
13
+ }
14
+
15
+ function boundary(values: readonly unknown[], latest: boolean): string | null {
16
+ const instants = values.map(instant).filter((value): value is string => value !== null);
17
+ if (instants.length === 0) return null;
18
+ return instants.reduce((a, b) => (latest ? a > b : a < b) ? a : b);
19
+ }
20
+
21
+ // The upstream capabilities, not the display name, declare which models a grant serves.
22
+ function modelIds(value: unknown): readonly string[] {
23
+ if (!Array.isArray(value)) return [];
24
+ return [...new Set(value.flatMap((entry: unknown) => {
25
+ const capability = asTrimmedString(entry);
26
+ if (capability === undefined || (capability.includes(":") && !capability.startsWith("model:"))) return [];
27
+ const model = capability.replace(/^model:/, "").trim().toLowerCase();
28
+ return model ? [model] : [];
29
+ }))];
30
+ }
31
+
32
+ function periodLabel(period: string | undefined): string {
33
+ if (period === "daily_night") return "nightly";
34
+ if (period === "one_time") return "one-time";
35
+ return period ?? "grant";
36
+ }
37
+
38
+ function grantLabel(name: string): string {
39
+ return /^(?:zcode\s+)?start plan$/i.test(name) ? "Start" : name;
40
+ }
41
+
42
+ function percent(value: number | null, limit: number | null): number | null {
43
+ return value !== null && limit !== null && limit > 0
44
+ ? Math.min(100, Math.max(0, value / limit * 100))
45
+ : null;
46
+ }
47
+
48
+ function samePlan(bucket: Record<string, unknown>, plan: Record<string, unknown>): boolean {
49
+ const userPlan = asTrimmedString(bucket["user_plan_id"]);
50
+ const owner = asTrimmedString(plan["user_plan_id"]);
51
+ if (userPlan !== undefined && owner !== undefined) return userPlan === owner;
52
+ const id = asTrimmedString(bucket["plan_id"]);
53
+ return id !== undefined && id === asTrimmedString(plan["plan_id"]);
54
+ }
55
+
56
+ /** Billing shape verified against opencode-zcode-auth 0.1.42; see the provider guide. */
57
+ export async function fetchZcodeStartPlanQuota(
58
+ credential: string,
59
+ fetcher: FetchLike,
60
+ ): Promise<ProviderQuotaResult> {
61
+ const source = "zcode-start";
62
+ try {
63
+ const response = await fetcher(BALANCE_URL, {
64
+ headers: {
65
+ authorization: `Bearer ${credential}`,
66
+ accept: "application/json",
67
+ "x-device-mid": DEVICE_MID,
68
+ "x-zcode-app-version": APP_VERSION,
69
+ "x-client-language": "en-US",
70
+ },
71
+ signal: AbortSignal.timeout(15_000),
72
+ });
73
+ if (!response.ok) return { source, plan: null, windows: [], error: `plan billing answered ${response.status}` };
74
+ const payload = asRecord(await response.json());
75
+ const code = payload?.["code"];
76
+ if (!payload || payload["success"] === false ||
77
+ (code !== undefined && ![0, 200, "0", "200"].includes(code as string | number))) {
78
+ return { source, plan: null, windows: [], error: "plan billing rejected request" };
79
+ }
80
+ const data = asRecord(payload["data"]);
81
+ if (!data || !Array.isArray(data["balances"]) || !Array.isArray(data["plans"])) {
82
+ return { source, plan: null, windows: [], error: "plan billing returned an unreadable balance" };
83
+ }
84
+ const plans = data["plans"].flatMap((value: unknown) => {
85
+ const plan = asRecord(value);
86
+ return plan ? [plan] : [];
87
+ });
88
+ const windows: ProviderQuotaWindow[] = [];
89
+ const names = new Set<string>();
90
+ const represented = new Set<Record<string, unknown>>();
91
+ for (const raw of data["balances"]) {
92
+ const bucket = asRecord(raw);
93
+ if (!bucket) continue;
94
+ const owner = plans.find((plan) => samePlan(bucket, plan));
95
+ if (owner) represented.add(owner);
96
+ const name = asTrimmedString(owner?.["name"]) ?? "Start Plan";
97
+ names.add(name);
98
+ const entitlement = (Array.isArray(owner?.["entitlements"]) ? owner["entitlements"] : [])
99
+ .map(asRecord).find((entry) => entry?.["entitlement_id"] === bucket["entitlement_id"]);
100
+ const period = asTrimmedString(entitlement?.["period"]);
101
+ const limit = asCoercedNumber(bucket["total_units"]) ?? null;
102
+ const reportedUsed = asCoercedNumber(bucket["used_units"]);
103
+ const reportedRemaining = asCoercedNumber(bucket["remaining_units"]);
104
+ const used = reportedUsed ?? (limit !== null && reportedRemaining !== undefined ? Math.max(0, limit - reportedRemaining) : null);
105
+ const remaining = reportedRemaining ?? (limit !== null && used !== null ? Math.max(0, limit - used) : null);
106
+ const models = modelIds(bucket["capabilities"] ?? entitlement?.["capabilities"]);
107
+ const expired = asTrimmedString(owner?.["status"])?.toLowerCase() === "expired";
108
+ windows.push({
109
+ kind: period ?? "grant",
110
+ label: `${asTrimmedString(bucket["show_name"]) ?? (models.join(", ") || "GLM")} · ${grantLabel(name)} · ${periodLabel(period)}`,
111
+ accounting: "independent",
112
+ modelIds: models,
113
+ startsAt: boundary([owner?.["starts_at"], bucket["period_start"]], true),
114
+ resetsAt: boundary([bucket["expires_at"], bucket["period_end"], owner?.["ends_at"]], false),
115
+ usedPercent: expired ? 100 : percent(used, limit),
116
+ remainingPercent: expired ? 0 : percent(remaining, limit),
117
+ used,
118
+ limit,
119
+ remaining,
120
+ recurring: !expired && (period === "daily" || period === "daily_night"),
121
+ });
122
+ }
123
+ // The balance may list a future plan before it has materialized any buckets.
124
+ for (const plan of plans) {
125
+ const startsAt = instant(plan["starts_at"]);
126
+ if (represented.has(plan) || !startsAt || Date.parse(startsAt) <= Date.now() || plan["status"] === "expired") continue;
127
+ const name = asTrimmedString(plan["name"]) ?? "Start Plan";
128
+ names.add(name);
129
+ const entitlements = Array.isArray(plan["entitlements"]) ? plan["entitlements"] : [];
130
+ const pending = entitlements.length > 0 ? entitlements : [{}];
131
+ for (const raw of pending) {
132
+ const entitlement = asRecord(raw);
133
+ windows.push({
134
+ kind: "grant", label: `${grantLabel(name)} · ${asTrimmedString(entitlement?.["show_name"]) ?? "not started yet"}`,
135
+ accounting: "independent", modelIds: modelIds(entitlement?.["capabilities"]), startsAt,
136
+ resetsAt: instant(plan["ends_at"]), usedPercent: null, remainingPercent: null,
137
+ remaining: null, used: null, limit: asCoercedNumber(entitlement?.["grant_units"]) ?? null,
138
+ recurring: false,
139
+ });
140
+ }
141
+ }
142
+ return { source, plan: [...names].join(", ") || null, windows, error: null };
143
+ } catch (error) {
144
+ const name = error instanceof Error ? error.name : "";
145
+ const message = name === "TimeoutError" || name === "AbortError"
146
+ ? "plan billing operation timed out or was aborted"
147
+ : name === "SyntaxError"
148
+ ? "plan billing returned unreadable JSON"
149
+ : "plan billing network request failed";
150
+ return { source, plan: null, windows: [], error: message };
151
+ }
152
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "_source": "ZCode 3.14.5 client Messages prompt; preserve text with the pinned identity version",
3
+ "cliPrefix": "You are ZCode, an interactive coding agent",
4
+ "stableSections": [
5
+ "\nYou are an interactive ZCode agent that helps users with software engineering tasks.\n\nIMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.\n\n# Harness\n- Text you output outside of tool use is displayed to the user as Github-flavored markdown in a terminal.\n- Tools run behind a user-selected permission mode; a denied call means the user declined it — adjust, don't retry verbatim.\n- The system may send updates, reminders, or modifications to rules via mid-conversation system turns. These are system-controlled, unlike function results. Hooks may intercept tool calls; treat hook output as user feedback.\n- Prefer the dedicated file/search tools over shell commands when one fits. Independent tool calls can run in parallel in one response.\n- Reference code as `file_path:line_number` — it's clickable.",
6
+ "# ZCode Desktop Context\n\n### Files & URLs\n- Return local web URLs as Markdown links (e.g., [label](http://127.0.0.1:8080)).\n- File should be an absolute path or include the workspace folder segment so it can be resolved relative to the workspace.\n- Unless otherwise specified, return local file references as Markdown links (e.g., [name.md](/absolute/path/to/name.md)).\n\n### Inline Code Comments\n- Use the ::code-comment{...} directive when you need to attach feedback directly to specific code lines.\n- Emit one directive per inline comment; emit none when there are no actionable inline comments.\n- Required attributes: title (short label), body (one-paragraph explanation), file (path to the file).\n- Optional attributes: start, end (1-based line numbers), priority (0-3).\n- file should be an absolute path or include the workspace folder segment so it can be resolved relative to the workspace.\n- Keep line ranges tight; end defaults to start.\n- Example: ::code-comment{title=\"[P2] Off-by-one\" body=\"Loop iterates past the end when length is 0.\" file=\"/path/to/foo.ts\" start=10 end=11 priority=2}",
7
+ "# Working style\n\nWhen you have enough information to act, act. Do not re-derive facts already established in the conversation, re-litigate a decision the user has already made, or narrate options you will not pursue. If you are weighing a choice, give a recommendation, not an exhaustive survey. Prefer reading the actual file or running the actual command over reasoning about what it probably contains. When a signal pattern-matches to a known failure, check that the evidence actually supports that specific diagnosis before acting on it."
8
+ ],
9
+ "environmentTemplate": "# Environment\nYou have been invoked in the following environment:\n - Primary working directory: {cwd}\n - Platform: {platform}\n - Shell: {shell}\n - You are powered by the model named {provider}/{model}."
10
+ }
@@ -57,6 +57,26 @@ const UNCLASSIFIED_COOLDOWN_MS = resolveAccountUnclassifiedCooldownMs;
57
57
  * which let the account re-enter rotation inside the window.
58
58
  */
59
59
  const GROK_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
60
+ /**
61
+ * Trae answers `4013 "AI service is not available in the current region now."`
62
+ * when the upstream does not serve the account's region. A day because the
63
+ * verdict is durable: it reproduces on every request until the region opens,
64
+ * and the account must not be re-dialled every thirty seconds for the whole
65
+ * window.
66
+ */
67
+ const TRAE_REGION_COOLDOWN_MS = 24 * 60 * 60 * 1000;
68
+ /**
69
+ * Trae's free-tier quota (`4008` / "exceeded the quota") resets daily, so its
70
+ * exhaustion is a full-day model backoff — never the generic 1h fallback,
71
+ * which let the account re-enter rotation inside the window.
72
+ */
73
+ const TRAE_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
74
+ /**
75
+ * Qoder's quota verdicts (`110` billing-daily-count, the upstream-499 close)
76
+ * are per-model: one model's spend cap does not drain another. A day matches
77
+ * the daily billing window the provider names.
78
+ */
79
+ const QODER_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
60
80
  /**
61
81
  * The buddy family's policy/channel block (`11140`, `11128`) is a *base* backoff,
62
82
  * not a flat penalty — see {@link resolveAccountPolicyBlockCooldownMs}. It used
@@ -438,6 +458,26 @@ function banWording(lower: string): boolean {
438
458
  );
439
459
  }
440
460
 
461
+ /**
462
+ * Whether GitHub Copilot stated the account lacks a license — a ban-shaped
463
+ * verdict on the account, not a refreshable auth rejection.
464
+ *
465
+ * Observed live (2026-11-03): the health log showed
466
+ * `unauthorized: not licensed to use Copilot` and `forbidden` on
467
+ * `gpt-6-luna`. Copilot answers 401 for a bad credential and 403 for a
468
+ * license/entitlement refusal, so `forbidden`/`unauthorized` on this surface
469
+ * is the same verdict. No amount of waiting or refreshing changes it — the
470
+ * subscription cannot serve this credential.
471
+ */
472
+ function copilotNotLicensed(lower: string): boolean {
473
+ return (
474
+ lower.includes("not licensed") ||
475
+ lower.includes("forbidden") ||
476
+ lower.includes("unauthorized") ||
477
+ lower.includes("license")
478
+ );
479
+ }
480
+
441
481
  /**
442
482
  * Bounded to a day, matching `MAX_COOLDOWN_MS` in the failure policy: a stated
443
483
  * window longer than that is a monthly or plan-level limit, and parking an
@@ -586,20 +626,10 @@ interface AccountErrorSignals {
586
626
  readonly staticToken: boolean;
587
627
  readonly credentialKind: string | undefined;
588
628
  /** Buddy-family provider: the only family with provider-specific codes below. */
589
- readonly buddy: boolean;
590
- /**
591
- * The upstream named a restriction on this account, not a refusal of the
592
- * request. Only the `11140` shape carries `displayMsg` about the account
593
- * being restricted, so the policy arm can tell a ban from a bad request
594
- * instead of treating both as one cooldown.
595
- */
596
629
  readonly buddyBanned: boolean;
597
630
  readonly priceRefusal: boolean;
598
- readonly deploymentInfraError: boolean;
599
631
  readonly quotaSignal: boolean;
600
632
  readonly policyRejection: boolean;
601
- readonly requestScoped: boolean;
602
- readonly channelRejection: boolean;
603
633
  readonly hostedToolFailure: boolean;
604
634
  readonly modelEntitlementFailure: boolean;
605
635
  readonly queuedUpstream: boolean;
@@ -793,7 +823,20 @@ export function classifyAccountError(
793
823
  const modelBlockHits = Math.max(1, Math.floor(options?.modelBlockHits ?? 1));
794
824
  const staticToken = options?.staticToken === true;
795
825
  const credentialKind = options?.credentialKind;
796
- const buddy = BUDDY_PROVIDER_IDS.has(options?.providerId?.toLowerCase() ?? "");
826
+
827
+ // Start Plan is a separate provider: its named trial refusal rests only that model.
828
+ const providerId = options?.providerId ?? (error instanceof GatewayError ? error.details.providerId : undefined);
829
+ const refusedModel = statusCode === 400
830
+ ? /no active limited quota for ([\w.-]+)/i.exec(message)?.[1]
831
+ : statusCode === 429
832
+ ? /limited quota(?: for)? ([\w.-]+)(?: is)? exhausted/i.exec(message)?.[1]
833
+ : undefined;
834
+ if (providerId === "zcode-start" && modelId !== undefined && refusedModel !== undefined &&
835
+ refusedModel.toLowerCase().replace(/-trial$/, "") === modelId.toLowerCase().replace(/-trial$/, "")) {
836
+ const cooldownMs = statedCooldownMs ?? DEFAULT_QUOTA_COOLDOWN_MS();
837
+ return result("model_unreachable", "cooldown", cooldownMs, new Date(Date.now() + cooldownMs),
838
+ reason("Start Plan trial unavailable for this model"), origin === "upstream");
839
+ }
797
840
 
798
841
  // A price refusal is a verdict on the *request*, not on the account. inferhub
799
842
  // answers 402 with `no provider's ask matches your max-per-mtok bid`: the
@@ -869,6 +912,7 @@ export function classifyAccountError(
869
912
  // keeps serving its other models.
870
913
  providerCode === "112" ||
871
914
  providerCode === "110" ||
915
+ providerCode === "116" ||
872
916
  providerCode === "insufficient_quota" ||
873
917
  providerCode === "quota_exceeded" ||
874
918
  providerCode === "subscription:free-usage-exhausted" ||
@@ -1067,7 +1111,8 @@ export function classifyAccountError(
1067
1111
  // for all other models. Disabling the whole account breaks healthy models.
1068
1112
  const modelEntitlementFailure =
1069
1113
  statusCode === 403 &&
1070
- (lower.includes("not have a valid license") ||
1114
+ ((providerId === "commandcode" && lower.includes("model_not_in_plan")) ||
1115
+ lower.includes("not have a valid license") ||
1071
1116
  lower.includes("valid license of this product") ||
1072
1117
  lower.includes("model not supported") ||
1073
1118
  lower.includes("not entitled to") ||
@@ -1131,14 +1176,10 @@ export function classifyAccountError(
1131
1176
  modelBlockHits,
1132
1177
  staticToken,
1133
1178
  credentialKind,
1134
- buddy,
1135
1179
  buddyBanned,
1136
1180
  priceRefusal,
1137
- deploymentInfraError,
1138
1181
  quotaSignal,
1139
1182
  policyRejection,
1140
- requestScoped,
1141
- channelRejection,
1142
1183
  hostedToolFailure,
1143
1184
  modelEntitlementFailure,
1144
1185
  queuedUpstream,
@@ -1225,101 +1266,6 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1225
1266
  false,
1226
1267
  ),
1227
1268
  },
1228
- {
1229
- name: "deployment-infrastructure",
1230
- match: (s) => s.deploymentInfraError,
1231
- decide: (s) =>
1232
- s.result(
1233
- "server_error",
1234
- "cooldown",
1235
- 60_000,
1236
- null,
1237
- `Proxy deployment error: ${s.message.slice(0, 160)}`,
1238
- false,
1239
- ),
1240
- },
1241
- {
1242
- name: "model-frequency-window",
1243
- match: (s) => s.lower.includes("frequency limit") && s.lower.includes("switch to the other models"),
1244
- decide: (s) => {
1245
- const cooldownMs = s.statedCooldownMs ?? DEFAULT_QUOTA_COOLDOWN_MS();
1246
- return s.result(
1247
- "rate_limit_transient",
1248
- "cooldown",
1249
- cooldownMs,
1250
- new Date(Date.now() + cooldownMs),
1251
- s.reason("Provider frequency limit for this model"),
1252
- // Provider-scoped evidence still has to reach the writer: a stream frame
1253
- // carries no credential detail, so `canMutate` is false and without this
1254
- // the pair would be retried for the whole window it was closed for.
1255
- s.origin === "upstream" && s.scope === "provider",
1256
- );
1257
- },
1258
- },
1259
- {
1260
- name: "content-policy-block",
1261
- match: (s) => s.policyRejection && s.buddy,
1262
- decide: (s) => {
1263
- // A ban is a verdict on the credential, and cooldown cannot answer it:
1264
- // the account is refused, not throttled. Disabling is the fix, and
1265
- // disabling *immediately* is a feature — a client's retry budget is spent
1266
- // in seconds, so a banned account that merely cooled stayed in the plan
1267
- // and burned sibling accounts that were still healthy before the window
1268
- // expired.
1269
- //
1270
- // The two shapes are told apart by the body, not the code alone: `11140`
1271
- // names a restriction on the account, `11-128` refuses the request.
1272
- if (s.buddyBanned) {
1273
- return s.result(
1274
- "policy_blocked_banned",
1275
- "disabled",
1276
- 0,
1277
- null,
1278
- s.reason("Account banned by upstream"),
1279
- // A ban is evidence about the credential even at provider scope, for
1280
- // the same reason a named revocation is: the message is the evidence.
1281
- // Without this a stream frame's missing credential detail would leave
1282
- // a banned account serving.
1283
- true,
1284
- );
1285
- }
1286
- // Not a ban: the upstream refused this request. The account is fine, so
1287
- // the only correct move is to stop retrying it verbatim. Cooling the
1288
- // account here was the bug that made one malformed request look like a
1289
- // dead credential.
1290
- return s.result(
1291
- "policy_blocked_invalid_request",
1292
- "cooldown",
1293
- 0,
1294
- null,
1295
- s.reason("Upstream refused this request"),
1296
- // Never mutates: a malformed body fails identically on every account,
1297
- // so cooling would burn a healthy credential on the gateway's own fault.
1298
- false,
1299
- );
1300
- },
1301
- },
1302
- {
1303
- name: "request-scoped-rejection",
1304
- match: (s) => s.requestScoped && s.buddy,
1305
- decide: (s) =>
1306
- s.result("unknown", "cooldown", 0, null, s.reason("Request-scoped upstream rejection"), false),
1307
- },
1308
- {
1309
- name: "channel-rejection",
1310
- match: (s) => s.channelRejection && s.buddy,
1311
- decide: (s) => {
1312
- const channelBlockMs = resolveAccountPolicyBlockCooldownMs();
1313
- return s.result(
1314
- "policy_blocked",
1315
- "cooldown",
1316
- channelBlockMs,
1317
- new Date(Date.now() + channelBlockMs),
1318
- s.reason("Provider channel rejection"),
1319
- s.origin === "upstream",
1320
- );
1321
- },
1322
- },
1323
1269
  {
1324
1270
  name: "validation-required",
1325
1271
  match: (s) => s.validationRequired,
@@ -1337,6 +1283,33 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1337
1283
  true,
1338
1284
  ),
1339
1285
  },
1286
+ {
1287
+ // GitHub Copilot answers a license/entitlement refusal with 403 and
1288
+ // "not licensed to use Copilot" / "forbidden" / "unauthorized". That is a
1289
+ // verdict on the account: the subscription cannot serve this credential,
1290
+ // and no amount of waiting or refreshing changes it. Cooldown cycling
1291
+ // (auth_invalidated + 5m backoff) kept a dead account in rotation where a
1292
+ // client's retry budget burned sibling accounts — the same failure the
1293
+ // buddy ban exists to prevent. Disable immediately. Must run before
1294
+ // `credential-rejected`, which would claim the bare 403 as a refreshable
1295
+ // auth rejection.
1296
+ name: "github-copilot-not-licensed",
1297
+ match: (s) =>
1298
+ s.origin === "upstream" &&
1299
+ s.providerId === "github" &&
1300
+ copilotNotLicensed(s.lower),
1301
+ decide: (s) =>
1302
+ s.result(
1303
+ "policy_blocked_banned",
1304
+ "disabled",
1305
+ 0,
1306
+ null,
1307
+ s.reason("GitHub Copilot account not licensed"),
1308
+ // A license refusal is evidence about the account even at provider
1309
+ // scope, for the same reason a named revocation or a buddy ban is.
1310
+ true,
1311
+ ),
1312
+ },
1340
1313
  {
1341
1314
  name: "credential-rejected",
1342
1315
  match: (s) =>
@@ -1468,9 +1441,84 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1468
1441
  );
1469
1442
  },
1470
1443
  },
1444
+ {
1445
+ // Trae answers `4013 "AI service is not available in the current region
1446
+ // now."` when the upstream does not serve the account's region. That is a
1447
+ // durable verdict on the account, not a transient transport fault: it
1448
+ // reproduces on every request until the region opens, so it must cool hard
1449
+ // for a day rather than be re-dialled every thirty seconds. Must run
1450
+ // before `transport-fault`, which would read the 502 as a short
1451
+ // `server_error`.
1452
+ name: "trae-region-unavailable",
1453
+ match: (s) =>
1454
+ (s.providerId === "trae" || s.providerId === "traecn") &&
1455
+ (s.providerCode === "4013" || s.lower.includes("not available in the current region")),
1456
+ decide: (s) => {
1457
+ const cooldownMs = TRAE_REGION_COOLDOWN_MS;
1458
+ return s.result(
1459
+ "quota_exhausted",
1460
+ "cooldown",
1461
+ cooldownMs,
1462
+ new Date(Date.now() + cooldownMs),
1463
+ s.reason("Trae service unavailable in the current region"),
1464
+ // Account-scoped provider evidence still has to reach the writer: a
1465
+ // stream frame carries no credential detail, so `canMutate` is false
1466
+ // and without this the account stays in rotation to reproduce the
1467
+ // refusal for the whole window the region is closed for.
1468
+ true,
1469
+ );
1470
+ },
1471
+ },
1472
+ {
1473
+ // Trae's free-tier quota (`4008` / "exceeded the quota") resets daily.
1474
+ // Treating it like the generic quota cooldown (1h account-wide fallback)
1475
+ // let the account re-enter rotation inside the window and reproduce the
1476
+ // refusal every few minutes — the "cooldown not holding" report. The
1477
+ // verdict is a full-day backoff on *any* model (the account's quota is
1478
+ // spent, so every model on it shares the wall). Scoped to the model so
1479
+ // the account is not disabled wholesale; the 1-day window means a model
1480
+ // stays parked for the day instead of flapping. Must run before
1481
+ // `quota-exhausted`, which would apply the generic shorter fallback.
1482
+ name: "trae-quota-exhausted",
1483
+ match: (s) =>
1484
+ (s.providerId === "trae" || s.providerId === "traecn") &&
1485
+ (s.providerCode === "4008" || s.lower.includes("have exceeded the quota")),
1486
+ decide: (s) => {
1487
+ const cooldownMs = s.statedCooldownMs ?? TRAE_QUOTA_COOLDOWN_MS;
1488
+ return s.result(
1489
+ "model_unreachable",
1490
+ "cooldown",
1491
+ cooldownMs,
1492
+ new Date(Date.now() + cooldownMs),
1493
+ s.reason("Trae quota exhausted for this model"),
1494
+ s.origin === "upstream" && s.modelId !== undefined,
1495
+ );
1496
+ },
1497
+ },
1498
+ {
1499
+ // Qoder's quota verdicts — `110` "Billing daily count exceeded" and the
1500
+ // upstream-499 exhausted-quota close — are per-model: one model's spend
1501
+ // cap does not drain another. Scoping them to the model keeps the account
1502
+ // serving its other models. Must run before `quota-exhausted`, which
1503
+ // parks the whole account.
1504
+ name: "qoder-model-quota",
1505
+ match: (s) =>
1506
+ (s.providerId === "qoder" || s.providerId === "qodercn") && s.quotaSignal,
1507
+ decide: (s) => {
1508
+ const cooldownMs = s.statedCooldownMs ?? QODER_QUOTA_COOLDOWN_MS;
1509
+ return s.result(
1510
+ "model_unreachable",
1511
+ "cooldown",
1512
+ cooldownMs,
1513
+ new Date(Date.now() + cooldownMs),
1514
+ s.reason("Qoder quota exhausted for this model"),
1515
+ s.origin === "upstream" && s.modelId !== undefined,
1516
+ );
1517
+ },
1518
+ },
1471
1519
  {
1472
1520
  name: "quota-exhausted",
1473
- match: (s) => s.quotaSignal,
1521
+ match: (s) => s.origin === "upstream" && s.quotaSignal,
1474
1522
  decide: (s) => {
1475
1523
  // xAI Grok Build's free tier resets on a rolling window. Use a duration
1476
1524
  // stated by the provider when present; otherwise park for the conservative
@@ -1478,11 +1526,21 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1478
1526
  const grokFreeTier =
1479
1527
  s.providerCode === "subscription:free-usage-exhausted" ||
1480
1528
  s.providerCode.startsWith("subscription:") ||
1529
+ // A 402 spend/entitlement wall (`personal-team-blocked:spending-limit`,
1530
+ // "out of credits") is the same verdict: the account cannot serve until
1531
+ // the operator tops up or upgrades. The default 1h fallback re-dialled
1532
+ // it every hour inside a window that will not clear on its own.
1533
+ s.providerCode.includes("spending") ||
1534
+ s.providerCode.includes("credits") ||
1481
1535
  s.lower.includes("free-usage") ||
1482
1536
  s.lower.includes("free usage") ||
1483
1537
  s.lower.includes("included free usage") ||
1484
1538
  s.lower.includes("rolling 24-hour") ||
1485
- s.lower.includes("rolling 24 hour");
1539
+ s.lower.includes("rolling 24 hour") ||
1540
+ s.lower.includes("spending limit") ||
1541
+ s.lower.includes("out of credits") ||
1542
+ s.lower.includes("run out of credits") ||
1543
+ s.lower.includes("need a grok subscription");
1486
1544
  const buddyCreditsExhausted =
1487
1545
  s.providerCode === "14018" || s.lower.includes("credits exhausted");
1488
1546
  const cooldownMs = grokFreeTier
@@ -1514,6 +1572,7 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1514
1572
  // moves to the next account and the flagged one returns almost at once.
1515
1573
  name: "inception-per-minute-limit",
1516
1574
  match: (s) =>
1575
+ s.origin === "upstream" &&
1517
1576
  s.providerId === "inception" &&
1518
1577
  (s.statusCode === 429 ||
1519
1578
  s.providerCode === "rate_limit_reached" ||
@@ -1538,15 +1597,16 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1538
1597
  {
1539
1598
  name: "rate-limit",
1540
1599
  match: (s) =>
1541
- s.statusCode === 429 ||
1542
- s.providerCode === "rate_limit_exceeded" ||
1543
- s.lower.includes("rate_limit") ||
1544
- s.lower.includes("rate limit") ||
1545
- s.lower.includes("too many requests") ||
1546
- s.lower.includes("requests per minute") ||
1547
- s.lower.includes("tokens per minute") ||
1548
- s.lower.includes("tpm limit") ||
1549
- s.lower.includes("rpm limit"),
1600
+ s.origin === "upstream" &&
1601
+ (s.statusCode === 429 ||
1602
+ s.providerCode === "rate_limit_exceeded" ||
1603
+ s.lower.includes("rate_limit") ||
1604
+ s.lower.includes("rate limit") ||
1605
+ s.lower.includes("too many requests") ||
1606
+ s.lower.includes("requests per minute") ||
1607
+ s.lower.includes("tokens per minute") ||
1608
+ s.lower.includes("tpm limit") ||
1609
+ s.lower.includes("rpm limit")),
1550
1610
  decide: (s) => {
1551
1611
  const cooldownMs = s.statedCooldownMs ?? DEFAULT_RATE_LIMIT_COOLDOWN_MS();
1552
1612
  // Same per-account reasoning as quota above: the failing account (or
@@ -1565,13 +1625,14 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1565
1625
  {
1566
1626
  name: "model-capacity",
1567
1627
  match: (s) =>
1568
- s.statusCode === 503 ||
1569
- s.statusCode === 529 ||
1570
- s.providerCode === "model_capacity" ||
1571
- s.queuedUpstream ||
1572
- s.lower.includes("model overloaded") ||
1573
- s.lower.includes("capacity exceeded") ||
1574
- s.lower.includes("temporarily unavailable"),
1628
+ s.origin === "upstream" &&
1629
+ (s.statusCode === 503 ||
1630
+ s.statusCode === 529 ||
1631
+ s.providerCode === "model_capacity" ||
1632
+ s.queuedUpstream ||
1633
+ s.lower.includes("model overloaded") ||
1634
+ s.lower.includes("capacity exceeded") ||
1635
+ s.lower.includes("temporarily unavailable")),
1575
1636
  decide: (s) => {
1576
1637
  const cooldownMs = s.statedCooldownMs ?? MODEL_CAPACITY_COOLDOWN_MS();
1577
1638
  const modelScopedMutation = s.origin === "upstream" && s.modelId !== undefined;
@@ -1631,20 +1692,24 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1631
1692
  // verdict, not a transport fault. It must run before `transport-fault`,
1632
1693
  // which otherwise reads the status as a 5xx-class outage and cools the
1633
1694
  // account for the shorter transient window — or leaves it unmutated.
1695
+ //
1696
+ // The verdict is per-model, like Qoder's other quota signs: one model's
1697
+ // spend cap does not drain another, so it cools the pair, never the whole
1698
+ // account.
1634
1699
  name: "qoder-499-quota",
1635
1700
  match: (s) =>
1636
1701
  (s.providerId === "qoder" || s.providerId === "qodercn") &&
1637
1702
  s.statusCode === 499 &&
1638
1703
  s.origin === "upstream",
1639
1704
  decide: (s) => {
1640
- const cooldownMs = s.statedCooldownMs ?? DEFAULT_QUOTA_COOLDOWN_MS();
1705
+ const cooldownMs = s.statedCooldownMs ?? QODER_QUOTA_COOLDOWN_MS;
1641
1706
  return s.result(
1642
- "quota_exhausted",
1707
+ "model_unreachable",
1643
1708
  "cooldown",
1644
1709
  cooldownMs,
1645
1710
  new Date(Date.now() + cooldownMs),
1646
- s.reason("Qoder quota exhausted (upstream 499)"),
1647
- s.origin === "upstream",
1711
+ s.reason("Qoder quota exhausted for this model (upstream 499)"),
1712
+ s.origin === "upstream" && s.modelId !== undefined,
1648
1713
  );
1649
1714
  },
1650
1715
  },