cartethyia 2.7.9 → 2.8.0

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 (375) hide show
  1. package/.env.example +3 -2
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Cc0vKpMG.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-CXsaOAO2.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BeLpJ4ma.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-DnV0uZ3m.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-CBxAjo63.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CgoPc5B9.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-D9bFQern.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-B_p4tL8k.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-D_0PiZtg.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  30. package/dist/dashboard/assets/{ProxyPage-BJ87Pp0U.js → ProxyPage-BwNC_-Cx.js} +1 -1
  31. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-CctTga2C.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage-febLU4r1.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-M6fvR5Wz.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-nyeuRqz9.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-BFOlvrJC.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  44. package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
  45. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-CqTLw5e7.js +1 -0
  47. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-C83EqLB0.js} +1 -1
  49. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-D1xn7pIe.js} +1 -1
  51. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-DU3Lq5Lm.js} +1 -1
  53. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-Dw1U_drK.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-CEL5Sg_J.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-BjFR5UHy.js} +1 -1
  59. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-HvBW0AjI.js} +1 -1
  61. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  62. package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
  63. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-CECVELyy.js} +1 -1
  65. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  66. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-DYYofQ7W.js} +1 -1
  67. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-DLW4i8zK.js} +1 -1
  69. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-1-fCqe64.js} +2 -2
  71. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-DGvU2oLK.js} +1 -1
  73. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-iNnO1X2K.js} +1 -1
  75. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-0q8UGypb.js} +1 -1
  77. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-C0w2bQjJ.js} +1 -1
  79. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-M5aJ_xCa.js} +1 -1
  81. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-BKeQGT3K.js} +1 -1
  83. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-CivAk3da.js} +1 -1
  85. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-CpzvI5ep.js} +1 -1
  87. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/zro.svg +3 -0
  89. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  90. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  91. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  92. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-BFQX2VBc.js} +1 -1
  93. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  94. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-CXbck0jG.js} +2 -2
  95. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  96. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-C_XvMHh9.js} +1 -1
  97. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  98. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-DggRiCxE.js} +1 -1
  99. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  100. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-ClFzfbYp.js} +1 -1
  101. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  102. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-CTIFxKqS.js} +1 -1
  103. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  104. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-BClG3yWj.js} +1 -1
  105. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/dashboard/index.html.gz +0 -0
  108. package/dist/dashboard/precompressed-assets.json +54 -53
  109. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  110. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  111. package/package.json +1 -1
  112. package/src/boot/app.ts +10 -13
  113. package/src/boot/dependencies.ts +1 -1
  114. package/src/boot/flush.ts +20 -0
  115. package/src/boot/lifecycle.ts +7 -5
  116. package/src/config/routing.ts +26 -0
  117. package/src/config/spec-metadata.ts +72 -0
  118. package/src/config/spec.ts +20 -0
  119. package/src/console/api-keys/contracts.ts +12 -1
  120. package/src/console/api-keys/routes.ts +40 -28
  121. package/src/console/auth/account-reset.ts +7 -9
  122. package/src/console/backup/contracts.ts +4 -0
  123. package/src/console/backup/export-endpoint.ts +7 -5
  124. package/src/console/cli-tools/service.ts +6 -12
  125. package/src/console/console-router-guards.ts +23 -16
  126. package/src/console/domain-registration.ts +0 -2
  127. package/src/console/inferhub/normalize.ts +24 -28
  128. package/src/console/inferhub/types.ts +3 -0
  129. package/src/console/ip-bans/routes.ts +7 -13
  130. package/src/console/observability/store.ts +0 -53
  131. package/src/console/providers/catalog/contracts.ts +2 -2
  132. package/src/console/providers/catalog/discover-constants.ts +6 -0
  133. package/src/console/providers/catalog/routes.ts +1 -0
  134. package/src/console/providers/catalog/store.ts +21 -16
  135. package/src/console/quota/account-quota-global.ts +14 -24
  136. package/src/console/quota/account-quota-shared.ts +49 -0
  137. package/src/console/quota/account-quota-tenant.ts +6 -18
  138. package/src/console/quota/quota-view.ts +19 -3
  139. package/src/console/routing/pools/operations.ts +31 -46
  140. package/src/console/shared/errors.ts +33 -6
  141. package/src/main.ts +2 -3
  142. package/src/network/pool/byte-accounting.ts +3 -2
  143. package/src/network/pool/selector.ts +182 -39
  144. package/src/network/pool-cooldown-store.ts +89 -14
  145. package/src/network/pool-health.ts +49 -61
  146. package/src/network/rate-limit-verdict.ts +1 -1
  147. package/src/network/ssrf.ts +6 -6
  148. package/src/observability/event-log.ts +7 -361
  149. package/src/observability/performance-metrics.ts +9 -0
  150. package/src/observability/runtime-metrics.ts +14 -9
  151. package/src/observability/telemetry-buffer.ts +15 -25
  152. package/src/observability/telemetry-reconciliation.ts +9 -37
  153. package/src/observability/telemetry-row.ts +125 -0
  154. package/src/persistence/db-mode.ts +4 -1
  155. package/src/persistence/dialect.ts +18 -0
  156. package/src/persistence/migrate.ts +6 -3
  157. package/src/persistence/schema.ts +8 -0
  158. package/src/persistence/ttl-cache.ts +20 -2
  159. package/src/protocol/codecs.ts +1 -1
  160. package/src/protocol/dsml.ts +34 -86
  161. package/src/protocol/markup-stream-parser.ts +195 -0
  162. package/src/protocol/reasoning-text.ts +19 -0
  163. package/src/protocol/request/chat.ts +10 -10
  164. package/src/protocol/request/gemini.ts +8 -2
  165. package/src/protocol/request/messages.ts +3 -4
  166. package/src/protocol/response/chat.ts +12 -20
  167. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  168. package/src/protocol/response/responses.ts +18 -2
  169. package/src/protocol/stop-reason.ts +23 -1
  170. package/src/protocol/think-split.ts +112 -0
  171. package/src/protocol/translation/dialect/qwen.ts +26 -58
  172. package/src/providers/adapters/compatible-adapter.ts +71 -12
  173. package/src/providers/authentication/oauth-client.ts +1 -1
  174. package/src/providers/authentication/oauth-device-flow.ts +98 -8
  175. package/src/providers/authentication/oauth-refresh-service.ts +30 -49
  176. package/src/providers/configured-provider.ts +148 -0
  177. package/src/providers/default-registry.ts +61 -36
  178. package/src/providers/discovery/probe-phases.ts +14 -4
  179. package/src/providers/discovery/probing-service.ts +15 -2
  180. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  181. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  182. package/src/providers/integrations/antigravity/antigravity-protocol.ts +5 -1
  183. package/src/providers/integrations/buddy/buddy-chat-shared.ts +173 -17
  184. package/src/providers/integrations/buddy/buddy-identity.ts +19 -6
  185. package/src/providers/integrations/buddy/capabilities.ts +2 -2
  186. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +3 -0
  187. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -0
  188. package/src/providers/integrations/buddy/codebuddy/shared.ts +37 -22
  189. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  190. package/src/providers/integrations/cline/cline.ts +2 -2
  191. package/src/providers/integrations/commandcode/quota.ts +22 -28
  192. package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
  193. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  194. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  195. package/src/providers/integrations/cursor/cursor-shared.ts +7 -4
  196. package/src/providers/integrations/cursor/cursor.ts +89 -40
  197. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  198. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  199. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  200. package/src/providers/integrations/factory/factory-oauth.ts +17 -44
  201. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  202. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  203. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  204. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  205. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  206. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  207. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  208. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  209. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  210. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  211. package/src/providers/integrations/grok/grok-oauth.ts +48 -45
  212. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  213. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  214. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +9 -9
  215. package/src/providers/integrations/hermes/index.ts +2 -0
  216. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  217. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  218. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  219. package/src/providers/integrations/kiro/kiro.ts +15 -19
  220. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  221. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  222. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  223. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  224. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  225. package/src/providers/integrations/perch/perch-protocol.ts +0 -3
  226. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  227. package/src/providers/integrations/perch/perch.ts +3 -13
  228. package/src/providers/integrations/qoder/claim-qoder-credits.ts +18 -59
  229. package/src/providers/integrations/qoder/qoder-adapter.ts +1 -12
  230. package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
  231. package/src/providers/integrations/qoder/qoder-catalog.ts +20 -5
  232. package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
  233. package/src/providers/integrations/qoder/qoder-checkin.ts +4 -4
  234. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -1
  235. package/src/providers/integrations/qoder/qoder-ide.ts +0 -107
  236. package/src/providers/integrations/qoder/qoder.ts +0 -1
  237. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  238. package/src/providers/integrations/trae/trae-quota.ts +6 -28
  239. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  240. package/src/providers/integrations/trae/trae.ts +7 -24
  241. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  242. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  243. package/src/providers/integrations/zro/zro.ts +204 -0
  244. package/src/providers/operations/account-error-classification.ts +199 -307
  245. package/src/providers/operations/account-health-service.ts +82 -1
  246. package/src/providers/operations/account-refresh-status.ts +22 -37
  247. package/src/providers/operations/account-tier.ts +15 -16
  248. package/src/providers/operations/client-version-resolver.ts +1 -1
  249. package/src/providers/operations/client-versions.ts +5 -51
  250. package/src/providers/operations/cooldown-rules.ts +11 -16
  251. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  252. package/src/providers/operations/provider-catalog-service.ts +1 -1
  253. package/src/providers/operations/provider-credential-service.ts +102 -32
  254. package/src/providers/operations/provider-rules/github.ts +48 -0
  255. package/src/providers/operations/provider-rules/grok.ts +33 -0
  256. package/src/providers/operations/provider-rules/inception.ts +49 -0
  257. package/src/providers/operations/provider-rules/index.ts +32 -0
  258. package/src/providers/operations/provider-rules/perch.ts +37 -0
  259. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  260. package/src/providers/operations/provider-rules/trae.ts +81 -0
  261. package/src/providers/provider-contracts.ts +4 -2
  262. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  263. package/src/providers/provider-metadata.ts +40 -10
  264. package/src/providers/provider-probe.ts +15 -1
  265. package/src/providers/provider-registry.ts +42 -33
  266. package/src/providers/quota/quota-limit-windows.ts +16 -5
  267. package/src/providers/quota/quota-support.ts +1 -1
  268. package/src/providers/quota/quota-window-parser.ts +90 -18
  269. package/src/providers/quota/refresh.ts +75 -18
  270. package/src/security/api-key-auth-cache.ts +1 -1
  271. package/src/security/api-key-auth.ts +6 -2
  272. package/src/transport/dispatch/attempt-finalize.ts +14 -13
  273. package/src/transport/dispatch/attempt-loop.ts +71 -17
  274. package/src/transport/dispatch/generation.ts +2 -8
  275. package/src/transport/dispatch/native-shared.ts +25 -0
  276. package/src/transport/dispatch/proxy-request.ts +8 -46
  277. package/src/transport/dispatch/responses-compact.ts +2 -8
  278. package/src/transport/dispatch/streaming/finalize.ts +9 -6
  279. package/src/transport/dispatch/streaming/stream.ts +8 -36
  280. package/src/transport/dispatch/systemone.ts +2 -8
  281. package/src/transport/dispatch/token-count.ts +2 -6
  282. package/src/transport/dispatch/upstream.ts +63 -0
  283. package/src/transport/drain-endpoint.ts +31 -4
  284. package/src/transport/failure-policy.ts +16 -30
  285. package/src/transport/guards/body-policy.ts +7 -1
  286. package/src/transport/guards/error-lifecycle.ts +13 -1
  287. package/src/transport/guards/request-context.ts +22 -14
  288. package/src/transport/request/preparer.ts +81 -71
  289. package/src/transport/routing/bounded-lru.ts +14 -0
  290. package/src/transport/routing/drain-reserve.ts +129 -0
  291. package/src/transport/routing/plan-gate.ts +7 -1
  292. package/src/transport/routing/round-robin.ts +60 -0
  293. package/src/transport/routing/route-catalog.ts +123 -111
  294. package/src/transport/routing/route-model.ts +51 -9
  295. package/src/transport/routing/router.ts +393 -32
  296. package/src/transport/surface/adapters.ts +11 -2
  297. package/src/transport/surface/chat/adapter.ts +2 -2
  298. package/src/transport/surface/chat/encode.ts +47 -4
  299. package/src/transport/surface/chat/parse.ts +2 -12
  300. package/src/transport/surface/completion.ts +44 -33
  301. package/src/transport/surface/content-parts.ts +21 -0
  302. package/src/transport/surface/messages/adapter.ts +7 -1
  303. package/src/transport/surface/messages/encode.ts +11 -3
  304. package/src/transport/surface/messages/parse.ts +2 -1
  305. package/src/transport/surface/messages/stream.ts +33 -17
  306. package/src/transport/surface/responses/encode.ts +22 -5
  307. package/src/transport/surface/responses/parse.ts +3 -11
  308. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  309. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  310. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  311. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  312. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  313. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  314. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  315. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  316. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  317. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  318. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  319. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  320. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  321. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  322. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  323. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  324. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  325. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  326. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  327. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  328. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  329. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  330. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  331. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  332. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  333. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  334. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  335. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  336. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  337. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  338. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  339. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  340. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  341. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  342. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  343. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  344. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  345. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  346. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  347. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  348. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  349. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  350. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  351. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  352. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  353. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  354. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  355. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  356. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  357. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  358. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  359. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  360. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  361. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  362. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  363. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  364. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  365. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  366. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  367. package/src/providers/bundled-discovery-providers.ts +0 -98
  368. package/src/providers/configured-openai-providers.ts +0 -120
  369. package/src/providers/operations/model-discovery-cache.ts +0 -34
  370. package/src/providers/operations/probe-fetch.ts +0 -15
  371. package/src/providers/operations/provider-version-cache.ts +0 -30
  372. package/src/providers/provider-capabilities.ts +0 -33
  373. package/src/providers/provider-credential-envelope.ts +0 -43
  374. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-DIXLjojg.js} +0 -0
  375. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-DIXLjojg.js.gz} +0 -0
@@ -40,10 +40,9 @@ import { log } from "../../../observability/logger";
40
40
  import {
41
41
  TRAE_ENT_USAGE_PATH,
42
42
  TRAE_ENT_USAGE_PATH_V2,
43
- TRAE_REGION_CORE_HOSTS,
44
- TRAE_REGION_PAY_HOSTS,
45
- TRAE_REALMS,
46
43
  TRAE_SESSION_USAGE_PATH,
44
+ traeCoreForAccount,
45
+ traePayHostForAccount,
47
46
  type TraeRealm,
48
47
  } from "./trae-realm";
49
48
 
@@ -326,27 +325,6 @@ function logTraeQuotaMiss(errors: readonly string[]): void {
326
325
  * whose pay host is unreachable still has a readable meter, and reporting
327
326
  * "no quota" for it would park an account that can still serve.
328
327
  */
329
- /**
330
- * The core host for this account.
331
- *
332
- * A provisioned region overrides the realm default: an `sg` account sent to the
333
- * US core is answered 401 code 1001, which looks exactly like a dead token.
334
- */
335
- function coreHost(region: string, config: (typeof TRAE_REALMS)[TraeRealm]): string {
336
- return (region !== "" ? TRAE_REGION_CORE_HOSTS[region] : undefined) ?? config.core;
337
- }
338
-
339
- /**
340
- * The pay host for this account.
341
- *
342
- * The pay surface correlates the credential against its region, too: an `sg`
343
- * account sent to the US pay host is answered 401 code 1001, while the SG pay
344
- * host accepts the same token (verified live 2026-11-06).
345
- */
346
- function payHost(region: string, config: (typeof TRAE_REALMS)[TraeRealm]): string {
347
- return (region !== "" ? TRAE_REGION_PAY_HOSTS[region] : undefined) ?? config.pay;
348
- }
349
-
350
328
  export async function fetchTraeQuota(
351
329
  credential: string,
352
330
  fetcher: FetchLike,
@@ -412,13 +390,13 @@ export async function fetchTraeQuota(
412
390
  * realm is tried: one of them accepts, and that is the answer.
413
391
  */
414
392
  const tryRealms = async (
415
- path: (config: (typeof TRAE_REALMS)[TraeRealm]) => string,
393
+ path: (realm: TraeRealm) => string,
416
394
  surface: string,
417
395
  parse: (payload: unknown) => { readonly windows: readonly ProviderQuotaWindow[]; readonly plan: string | null },
418
396
  ): Promise<{ readonly windows: readonly ProviderQuotaWindow[]; readonly plan: string | null } | null> => {
419
397
  for (const candidate of realm === "cn" ? (["cn", "intl"] as const) : (["intl", "cn"] as const)) {
420
398
  try {
421
- const payload = await call(candidate, path(TRAE_REALMS[candidate]), surface);
399
+ const payload = await call(candidate, path(candidate), surface);
422
400
  return parse(payload);
423
401
  } catch (error) {
424
402
  errors.push(`${candidate} ${surface}: ${cleanError(error)}`);
@@ -427,11 +405,11 @@ export async function fetchTraeQuota(
427
405
  return null;
428
406
  };
429
407
 
430
- const session = await tryRealms((config) => coreHost(region, config) + TRAE_SESSION_USAGE_PATH, "session usage", parseSessionUsage);
408
+ const session = await tryRealms((r) => traeCoreForAccount(r, region) + TRAE_SESSION_USAGE_PATH, "session usage", parseSessionUsage);
431
409
  // The entitlement path differs by realm: the CN realm answers v2 (v1 lacks
432
410
  // `usage_summary` there), the intl realm answers v1 (v2 is a 404).
433
411
  const entPath = realm === "cn" ? TRAE_ENT_USAGE_PATH_V2 : TRAE_ENT_USAGE_PATH;
434
- const entitlement = await tryRealms((config) => payHost(region, config) + entPath, "entitlement", parseEntitlement);
412
+ const entitlement = await tryRealms((r) => traePayHostForAccount(r, region) + entPath, "entitlement", parseEntitlement);
435
413
 
436
414
  const windows = [...(session?.windows ?? []), ...(entitlement?.windows ?? [])];
437
415
  if (!session && !entitlement) {
@@ -12,6 +12,9 @@
12
12
  /** One Trae deployment. */
13
13
  export type TraeRealm = "intl" | "cn";
14
14
 
15
+ /** Provider ids served by the Trae integration (intl + CN). */
16
+ export const TRAE_PROVIDER_IDS: ReadonlySet<string> = new Set(["trae", "traecn"]);
17
+
15
18
  /** Per-realm upstream addresses, client identity, and auth scheme. */
16
19
  export interface TraeRealmConfig {
17
20
  /** Data-plane host: chat + model list (`/api/ide/v1/*`). */
@@ -245,3 +248,38 @@ export function traeAccountCandidates(realm: TraeRealm): readonly string[] {
245
248
  const config = TRAE_REALMS[realm];
246
249
  return [config.account, ...config.accountFallbacks];
247
250
  }
251
+
252
+ /**
253
+ * The core host for this account.
254
+ *
255
+ * A provisioned region overrides the realm default: an `sg` account sent to the
256
+ * US core is answered 401 code 1001 (which looks exactly like a dead token), so
257
+ * the region lookup must win when it is known. Falling back to the realm keeps
258
+ * older accounts (stored before the region was captured) working exactly as
259
+ * before.
260
+ */
261
+ export function traeCoreForAccount(
262
+ realm: TraeRealm,
263
+ region: string,
264
+ ): string {
265
+ const regional = TRAE_REGION_CORE_HOSTS[region];
266
+ if (regional !== undefined) return regional;
267
+ return traeCoreCandidates(realm)[0] ?? "";
268
+ }
269
+
270
+ /**
271
+ * The pay host for this account.
272
+ *
273
+ * The pay surface correlates the credential against its region, too: an `sg`
274
+ * account sent to the US pay host is answered 401 code 1001, while the SG pay
275
+ * host accepts the same token (verified live 2026-11-06). Falls back to the
276
+ * realm default host when no region is provisioned.
277
+ */
278
+ export function traePayHostForAccount(
279
+ realm: TraeRealm,
280
+ region: string,
281
+ ): string {
282
+ const regional = TRAE_REGION_PAY_HOSTS[region];
283
+ if (regional !== undefined) return regional;
284
+ return TRAE_REALMS[realm].pay;
285
+ }
@@ -31,7 +31,7 @@ import {
31
31
  TRAE_AGENT_ENDPOINT,
32
32
  TRAE_CHAT_ENDPOINT,
33
33
  } from "./trae-protocol";
34
- import { TRAE_REGION_CORE_HOSTS, traeCoreCandidates, TRAE_MODEL_LIST_PATH, TRAE_AGENT_FUNCTION, type TraeRealm } from "./trae-realm";
34
+ import { traeCoreForAccount, TRAE_MODEL_LIST_PATH, TRAE_AGENT_FUNCTION, type TraeRealm } from "./trae-realm";
35
35
 
36
36
  export interface TraeAdapterOptions {
37
37
  readonly providerId?: string;
@@ -269,7 +269,11 @@ class TraeAdapter implements ProviderAdapter {
269
269
  trafficType: agentSurface ? "agent" : "chat",
270
270
  });
271
271
 
272
- const primary = traeCoreForAccount(this.realm, context.credential.auth_state);
272
+ const region =
273
+ typeof context.credential.auth_state?.["region"] === "string"
274
+ ? context.credential.auth_state["region"].toLowerCase()
275
+ : "";
276
+ const primary = traeCoreForAccount(this.realm, region);
273
277
  const path = agentSurface ? TRAE_AGENT_ENDPOINT : TRAE_CHAT_ENDPOINT;
274
278
  const url =
275
279
  candidate.endpoint_path && candidate.endpoint_path.startsWith("http")
@@ -572,10 +576,7 @@ export async function discoverTraeModels(input: {
572
576
  uid: input.uid,
573
577
  appVersion: getTraeVersion(),
574
578
  });
575
- const core = traeCoreForAccount(
576
- input.realm,
577
- input.region === undefined ? undefined : { region: input.region },
578
- );
579
+ const core = traeCoreForAccount(input.realm, (input.region ?? "").toLowerCase());
579
580
 
580
581
  if (input.realm === "intl") {
581
582
  // The agent picker: the only surface that lists the intl roster's configs.
@@ -649,21 +650,3 @@ export async function discoverTraeModels(input: {
649
650
  }
650
651
 
651
652
  export { TRAE_CN_MODELS, TRAE_INTL_MODELS };
652
-
653
- /**
654
- * Core host for one account.
655
- *
656
- * An intl account is provisioned into a region and its token only authenticates
657
- * against that region's host, so the account's own `region` wins over the
658
- * realm default. Falling back to the realm keeps older accounts (stored before
659
- * the region was captured) working exactly as before.
660
- */
661
- function traeCoreForAccount(
662
- realm: TraeRealm,
663
- authState: Readonly<Record<string, unknown>> | undefined,
664
- ): string {
665
- const region = typeof authState?.["region"] === "string" ? authState["region"].toLowerCase() : "";
666
- const regional = TRAE_REGION_CORE_HOSTS[region];
667
- if (regional !== undefined) return regional;
668
- return traeCoreCandidates(realm)[0] ?? "";
669
- }
@@ -1,16 +1,41 @@
1
1
  /**
2
- * ZCode Start Plan uses the client's three-block Messages prompt fingerprint.
3
- * Caller instructions remain inside the third block; a fourth block is refused.
4
- * The bundled prompt/version pair is kept together to preserve upstream bytes.
5
- * Reference provenance and bounded live acceptance evidence: provider guide.
2
+ * ZCode Start Plan provider integration.
3
+ *
4
+ * The plan lane is an Anthropic Messages endpoint that only accepts the
5
+ * dressed ZCode client request (see `dress`): the gateway content-inspects the
6
+ * `system` field and refuses a request without the exact three-block identity
7
+ * fingerprint with `405 {code:3012}`. The credential is a ZCode login JWT —
8
+ * pasted, or minted by the sign-in flow (`oauth.ts`).
9
+ *
10
+ * Wire facts mirrored from the donor gateway (verified live 2026-10-30):
11
+ * - `anthropic-beta: mid-conversation-system-2026-04-07` rides every request.
12
+ * - `metadata.user_id` is a JSON string `{"device_id","account_uuid","session_id"}`
13
+ * the gateway reads for attribution; it lives in the body, not a header.
14
+ * - The LLM identity face carries per-request correlation ids: `x-session-id`,
15
+ * `x-request-id`, `x-zcode-trace-id`, `x-query-id`, `x-zcode-query-source`.
16
+ * - Errors inside an HTTP 200 response come as a `{code,msg,logid}` envelope
17
+ * (`1113` grant spent, `3009` model concurrency, `3012` identity gate) and
18
+ * are mapped through the shared `classifyUpstreamError`.
6
19
  */
7
20
  import { randomUUID } from "node:crypto";
8
21
  import { release as osRelease } from "node:os";
9
22
 
10
- import type { ApiKeyProviderSpec } from "../../configured-provider";
11
23
  import { defineModel } from "../../provider-model-definition";
12
24
  import type { ModelDefinition } from "../../provider-model-definition";
13
25
  import { getOrCreateInstallId } from "../shared/install-id";
26
+ import { resolveInboundSessionId } from "../../operations/session-resolution";
27
+ import { assertCredentialKind, assertWireFamily } from "../../provider-contracts";
28
+ import type { ProviderAdapter, ProviderDispatchContext, ProviderDispatchTarget } from "../../provider-contracts";
29
+ import { GatewayError } from "../../../protocol/gateway-error";
30
+ import { applyParamQuirks } from "../../../protocol/translation/quirks";
31
+ import { buildClaudeMessagesRequest, filterClaudeCustomHeaders } from "../shared/claude-messages";
32
+ import { mapClaudeHttpError } from "../../../protocol/messages-errors";
33
+ import { isRecord } from "../../../protocol/codecs";
34
+ import { classifyUpstreamError, upstreamErrorIdentifier, upstreamMessageAsIs } from "../../../transport/failure-policy";
35
+ import { parseClaudeSseStream, claudeResponseToEvents } from "../../../protocol/response/messages";
36
+ import { createUpstreamDeadlineLifecycle } from "../../operations/upstream-deadline";
37
+ import type { CanonicalEvent, CanonicalRequest } from "../../../protocol/canonical-model";
38
+ import { parseJwt } from "../../authentication/jwt-validator";
14
39
  import prompt from "./zcode_prompt.json";
15
40
 
16
41
  const CLI_PREFIX: string = prompt.cliPrefix;
@@ -21,8 +46,18 @@ export const ZCODE_START_PLAN_PROVIDER_ID = "zcode-start" as const;
21
46
  export const ZCODE_START_PLAN_ENDPOINT_PATH = "/v1/messages" as const;
22
47
  export const ZCODE_START_PLAN_BASE_URL = "https://zcode.z.ai/api/v1/zcode-plan/anthropic" as const;
23
48
 
24
- /** Client version the plan endpoint expects in the identity headers. */
49
+ /**
50
+ * Client version the plan endpoint expects in the identity headers. This is
51
+ * kept in step with the bundled prompt asset (`zcode_prompt.json`, 3.14.5):
52
+ * the gateway content-inspects the system blocks, and a version/prompt split
53
+ * (e.g. 3.16.3 headers over 3.14.5 blocks) risks the 3012 identity gate. Bump
54
+ * both together only after re-capturing the live blocks and re-verifying.
55
+ */
25
56
  const APP_VERSION = "3.14.5";
57
+ /** Anthropic beta the desktop client sends on the plan lane (captured live). */
58
+ const ANTHROPIC_BETA = "mid-conversation-system-2026-04-07";
59
+ /** The plan gateway enforces a serial per-account in-flight limit; 8192 is the desktop default. */
60
+ const DEFAULT_MAX_TOKENS = 8192;
26
61
 
27
62
  type Loose = Record<string, unknown>;
28
63
  type TextBlock = { type: "text"; text: string; cache_control?: { type: "ephemeral" } };
@@ -40,26 +75,47 @@ function environmentBlock(provider: string, model: string): string {
40
75
  .replace("{model}", model);
41
76
  }
42
77
 
78
+ /** The account uuid carried by the plan JWT, for metadata attribution. */
79
+ function jwtUserId(credential: { secret?: Uint8Array | undefined } | undefined): string {
80
+ if (!credential?.secret || credential.secret.length === 0) return "";
81
+ try {
82
+ const token = new TextDecoder().decode(credential.secret);
83
+ const parsed = parseJwt(token);
84
+ const userId = parsed?.payload?.["user_id"];
85
+ return typeof userId === "string" ? userId : "";
86
+ } catch {
87
+ return "";
88
+ }
89
+ }
90
+
43
91
  /** Identity headers every dispatch carries; the adapter adds Authorization. */
44
- async function identityHeaders(): Promise<Record<string, string>> {
92
+ async function identityHeaders(context: ProviderDispatchContext): Promise<Record<string, string>> {
93
+ const sessionId = resolveInboundSessionId(context) ?? `session_${randomUUID().replace(/-/g, "").slice(0, 32)}`;
94
+ const requestId = randomUUID();
95
+ const traceId = randomUUID();
96
+ const queryId = `01a1${requestId.slice(4).replace(/-/g, "").slice(0, 28)}`;
97
+ const deviceMid = await getOrCreateInstallId("zcode-start-plan");
45
98
  return {
46
99
  "user-agent": `ZCode/${APP_VERSION} ai-sdk/anthropic/3.0.81`,
47
100
  "http-referer": "https://zcode.z.ai",
48
- "x-title": "Z Code@cli",
101
+ "x-title": "Z Code@electron",
49
102
  "x-zcode-app-version": APP_VERSION,
50
103
  "x-zcode-agent": "glm",
51
104
  "x-release-channel": "production",
52
105
  "x-client-language": "en-US",
53
106
  "x-client-timezone": "Asia/Jakarta",
54
- "x-platform": "linux",
107
+ "x-platform": "linux-x64",
55
108
  "x-os-category": "linux",
56
109
  "x-os-version": osRelease(),
57
- "x-request-id": randomUUID(),
110
+ "x-request-id": requestId,
111
+ "x-query-id": queryId,
112
+ "x-zcode-trace-id": traceId,
58
113
  "x-zcode-session-type": "main",
59
- "x-zcode-trace-id": randomUUID(),
60
- // Keep the persisted install-id filename stable across the provider-ID rename.
61
- "x-device-mid": await getOrCreateInstallId("zcode-start-plan"),
114
+ "x-zcode-query-source": "main_turn",
115
+ "x-session-id": sessionId,
116
+ "x-device-mid": deviceMid,
62
117
  "anthropic-version": "2023-06-01",
118
+ "anthropic-beta": ANTHROPIC_BETA,
63
119
  };
64
120
  }
65
121
 
@@ -119,13 +175,160 @@ export const ZCODE_START_PLAN_MODELS: readonly ModelDefinition[] = [
119
175
  }),
120
176
  ];
121
177
 
122
- /** Start Plan adapter spec: direct upstream, prompt fingerprint, no sidecar. */
123
- export const ZCODE_START_PLAN_SPEC: ApiKeyProviderSpec = {
124
- provider_id: ZCODE_START_PLAN_PROVIDER_ID,
125
- base_url: ZCODE_START_PLAN_BASE_URL,
126
- endpoint_paths_by_wire_family: { messages: ZCODE_START_PLAN_ENDPOINT_PATH },
127
- buildExtraHeaders: () => identityHeaders(),
128
- prePayload: (payload) => {
129
- dress(payload as Loose);
130
- },
131
- };
178
+ /** True when the body is the ZCode `{code,msg,logid}` business-error envelope. */
179
+ function isZcodeEnvelopeError(body: Record<string, unknown>): boolean {
180
+ if (body["success"] === false) return true;
181
+ const code = body["code"];
182
+ return code !== undefined && ![0, 200, "0", "200"].includes(code as string | number);
183
+ }
184
+
185
+ /**
186
+ * Plan-lane Messages adapter. Unlike the plain Anthropic adapter it must (a)
187
+ * dress the three-block system fingerprint, (b) inject `metadata.user_id`, and
188
+ * (c) treat a `{code,msg}` envelope inside an HTTP 200 as a refusal — the
189
+ * gateway reports business errors on success statuses.
190
+ */
191
+ export class ZcodeStartPlanAdapter implements ProviderAdapter {
192
+ readonly provider_id = ZCODE_START_PLAN_PROVIDER_ID;
193
+ private readonly fetchFn: typeof fetch;
194
+ private readonly baseUrl: string;
195
+
196
+ constructor(fetchImpl?: typeof fetch, baseUrl?: string) {
197
+ this.fetchFn = fetchImpl ?? globalThis.fetch;
198
+ this.baseUrl = (baseUrl ?? ZCODE_START_PLAN_BASE_URL).replace(/\/+$/, "");
199
+ }
200
+
201
+ async *dispatch(
202
+ request: CanonicalRequest,
203
+ candidate: ProviderDispatchTarget,
204
+ context: ProviderDispatchContext,
205
+ ): AsyncIterable<CanonicalEvent> {
206
+ if (candidate.wire_family !== "messages") {
207
+ assertWireFamily(candidate, "messages", "zcode-start messages leg");
208
+ }
209
+ if (context.credential.credential_kind !== "api_key") {
210
+ assertCredentialKind(context.credential, "api_key", "zcode-start");
211
+ }
212
+ if (context.credential.secret === undefined || context.credential.secret.length === 0) {
213
+ throw new GatewayError(
214
+ "invalid_request",
215
+ 400,
216
+ "zcode-start: plan JWT is missing a secret value (request {requestId}). Wrong key, right door — check the credential 🔑",
217
+ );
218
+ }
219
+ const planJwt = new TextDecoder().decode(context.credential.secret);
220
+ const sessionId = resolveInboundSessionId(context, request) ?? `session_${randomUUID().replace(/-/g, "").slice(0, 32)}`;
221
+ const deviceMid = await getOrCreateInstallId("zcode-start-plan");
222
+ const accountUuid = jwtUserId(context.credential);
223
+ const customHeaders = filterClaudeCustomHeaders({
224
+ ...context.credential.custom_headers,
225
+ });
226
+ const outbound = buildClaudeMessagesRequest({
227
+ request: applyParamQuirks(request, candidate.provider_id),
228
+ authHeader: "bearer",
229
+ credential: { secret: planJwt, customHeaders },
230
+ baseUrl: this.baseUrl,
231
+ endpointPath: candidate.endpoint_path,
232
+ mutatePayload: (payload) => {
233
+ dress(payload);
234
+ const metadata = (isRecord(payload["metadata"]) ? payload["metadata"] : {}) as Loose;
235
+ metadata["user_id"] = JSON.stringify({
236
+ device_id: deviceMid,
237
+ account_uuid: accountUuid,
238
+ session_id: sessionId,
239
+ });
240
+ payload.metadata = metadata;
241
+ if (payload["max_tokens"] == null) payload["max_tokens"] = DEFAULT_MAX_TOKENS;
242
+ return payload;
243
+ },
244
+ });
245
+ Object.assign(outbound.headers, await identityHeaders(context));
246
+
247
+ const outboundFetch = context.outbound_fetch ?? this.fetchFn;
248
+ const lifecycle = createUpstreamDeadlineLifecycle(context);
249
+ let response: Response;
250
+ try {
251
+ response = await outboundFetch(outbound.url, {
252
+ method: "POST",
253
+ headers: outbound.headers,
254
+ body: outbound.body,
255
+ signal: lifecycle.signal,
256
+ });
257
+ } catch (error: unknown) {
258
+ lifecycle.release();
259
+ if (error instanceof GatewayError) throw error;
260
+ throw new GatewayError(
261
+ "transport_unavailable",
262
+ 502,
263
+ `zcode-start upstream fetch failed: ${(error as Error)?.message ?? String(error)}`,
264
+ {},
265
+ "network",
266
+ );
267
+ }
268
+ lifecycle.release();
269
+
270
+ if (!response.ok) {
271
+ const bodyText = await response.text().catch(() => "");
272
+ throw mapClaudeHttpError(response.status, bodyText, response.headers);
273
+ }
274
+ const contentType = response.headers.get("content-type") ?? "";
275
+ if (contentType.toLowerCase().includes("text/event-stream") || request.stream) {
276
+ if (response.body === null) {
277
+ throw new GatewayError(
278
+ "platform_unavailable",
279
+ 502,
280
+ "zcode-start returned no stream body (request {requestId}). The upstream came back empty-handed — retry 🌙",
281
+ {},
282
+ "upstream",
283
+ );
284
+ }
285
+ yield* parseClaudeSseStream(response.body, context.abort_signal, false);
286
+ return;
287
+ }
288
+ const bodyText = await response.text().catch(() => "");
289
+ let json: unknown;
290
+ try {
291
+ json = JSON.parse(bodyText) as unknown;
292
+ } catch {
293
+ const upstreamText = upstreamMessageAsIs(bodyText, "");
294
+ throw new GatewayError(
295
+ "platform_unavailable",
296
+ 502,
297
+ upstreamText.message || "zcode-start response is not valid JSON (request {requestId}). The upstream came back empty-handed — retry 🌙",
298
+ { raw: bodyText.slice(0, 4096) },
299
+ "upstream",
300
+ );
301
+ }
302
+ if (!isRecord(json)) {
303
+ throw new GatewayError(
304
+ "platform_unavailable",
305
+ 502,
306
+ "zcode-start response must be an object (request {requestId}). The upstream came back empty-handed — retry 🌙",
307
+ {},
308
+ "upstream",
309
+ );
310
+ }
311
+ if (isZcodeEnvelopeError(json)) {
312
+ const identifier = upstreamErrorIdentifier(json);
313
+ const classification = classifyUpstreamError(undefined, identifier);
314
+ const upstreamText = upstreamMessageAsIs(json, "");
315
+ throw new GatewayError(
316
+ classification.code,
317
+ classification.status,
318
+ upstreamText.message || "zcode-start reported an upstream error",
319
+ {
320
+ providerId: this.provider_id,
321
+ ...(identifier === undefined ? {} : { providerCode: identifier }),
322
+ ...(upstreamText.hadUpstreamText ? { upstreamMessageAsIs: true } : {}),
323
+ raw: bodyText.slice(0, 4096),
324
+ },
325
+ classification.origin,
326
+ );
327
+ }
328
+ yield* claudeResponseToEvents(json, request, false);
329
+ }
330
+ }
331
+
332
+ export function createZcodeStartPlanAdapter(fetchImpl?: typeof fetch): ProviderAdapter {
333
+ return new ZcodeStartPlanAdapter(fetchImpl);
334
+ }
@@ -1,9 +1,23 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { asCoercedNumber, asRecord, asTrimmedString } from "../../../shared/coerce";
2
3
  import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../quota/quota-contracts";
3
4
 
4
5
  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
6
  const APP_VERSION = "3.16.3";
7
+ const PLATFORM = "linux-x64";
8
+
9
+ /**
10
+ * Stable per-account device mid. The gateway validates `X-Device-Mid` as a
11
+ * UUID (billing answers `3001 parameter error` without it) and attributes
12
+ * usage to it; a hardcoded shared UUID would lump every account into one
13
+ * billing identity. Derivation matches the gift-claim device id, so quota and
14
+ * claim present the same device per account.
15
+ */
16
+ function deviceMid(accountId: string): string {
17
+ const hash = createHash("sha256").update(`zcode-device\0${accountId}`).digest("hex");
18
+ const variant = ((parseInt(hash[16] as string, 16) & 3) | 8).toString(16);
19
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-${variant}${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
20
+ }
7
21
 
8
22
  function instant(value: unknown): string | null {
9
23
  const seconds = asCoercedNumber(value);
@@ -57,16 +71,19 @@ function samePlan(bucket: Record<string, unknown>, plan: Record<string, unknown>
57
71
  export async function fetchZcodeStartPlanQuota(
58
72
  credential: string,
59
73
  fetcher: FetchLike,
74
+ context?: { accountId?: string },
60
75
  ): Promise<ProviderQuotaResult> {
61
76
  const source = "zcode-start";
62
77
  try {
78
+ const accountId = context?.accountId ?? credential.trim();
63
79
  const response = await fetcher(BALANCE_URL, {
64
80
  headers: {
65
81
  authorization: `Bearer ${credential}`,
66
82
  accept: "application/json",
67
- "x-device-mid": DEVICE_MID,
83
+ "x-device-mid": deviceMid(accountId),
68
84
  "x-zcode-app-version": APP_VERSION,
69
85
  "x-client-language": "en-US",
86
+ "x-platform": PLATFORM,
70
87
  },
71
88
  signal: AbortSignal.timeout(15_000),
72
89
  });