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
@@ -12,6 +12,7 @@ import {
12
12
  type ProviderRoutingSetting,
13
13
  type Reservation,
14
14
  type RouteCandidate,
15
+ type RoutedCandidate,
15
16
  type RoutePlan,
16
17
  type EligibilityDecision,
17
18
  type RouteContinuationAffinity,
@@ -34,22 +35,100 @@ import {
34
35
  } from "./candidate-index";
35
36
  import { totalRemainingCredit, totalRemainingPercent, type ProviderQuotaWindow } from "../../providers/quota/quota-contracts";
36
37
  import { getCachedQuotaEntry, quotaLens } from "../../providers/quota/cache";
38
+ import { readQuotaSnapshot } from "../../providers/quota/snapshot";
39
+ import type { CartethyiaDatabase } from "../../persistence/db-handle";
37
40
  import type { RedisClient } from "../../persistence/redis";
38
41
  import { sharedClaudeSlowLanes } from "../../providers/integrations/claude/claude-slow-lane";
39
42
 
43
+ /**
44
+ * Filters quota windows to those relevant to the candidate model.
45
+ * Providers like Antigravity report separate quotas for distinct model families
46
+ * (Gemini vs Claude/GPT), and Grok reports per-product buckets.
47
+ * Evaluating a Claude exhaustion against a Gemini request would incorrectly
48
+ * mark the account exhausted for Gemini when Gemini capacity is still available.
49
+ */
50
+ export function filterCandidateQuotaWindows(
51
+ windows: readonly ProviderQuotaWindow[],
52
+ candidate: { readonly provider_id?: string; readonly model_id?: string },
53
+ ): readonly ProviderQuotaWindow[] {
54
+ if (windows.length === 0) return windows;
55
+ const providerId = candidate.provider_id?.toLowerCase() ?? "";
56
+ const modelId = candidate.model_id?.toLowerCase() ?? "";
57
+ windows = windows.filter((window) => window.modelIds === undefined || window.modelIds.includes(modelId));
58
+
59
+ if (providerId === "antigravity") {
60
+ const isGemini = /gemini|tab_flash/i.test(modelId);
61
+ const isClaude = /claude|opus|sonnet/i.test(modelId);
62
+ if (isGemini) {
63
+ return windows.filter((w) => {
64
+ const text = `${w.kind} ${w.label}`.toLowerCase();
65
+ if (text.includes("gemini") || text.includes("google")) return true;
66
+ return !text.includes("claude") && !text.includes("gpt");
67
+ });
68
+ }
69
+ if (isClaude) {
70
+ return windows.filter((w) => {
71
+ const text = `${w.kind} ${w.label}`.toLowerCase();
72
+ if (text.includes("claude") || text.includes("gpt")) return true;
73
+ return !text.includes("gemini") && !text.includes("google");
74
+ });
75
+ }
76
+ }
77
+
78
+ if (providerId === "grok") {
79
+ return windows.filter((w) => {
80
+ if (w.kind.startsWith("product:")) {
81
+ const product = w.kind.split(":")[1]?.toLowerCase();
82
+ return product ? modelId.includes(product) : true;
83
+ }
84
+ return true;
85
+ });
86
+ }
87
+
88
+ return windows;
89
+ }
90
+
40
91
  /**
41
92
  * True when the account's remaining allowance is at or below its provider's
42
93
  * floor. Credit mode sums the absolute remaining credit; percent mode uses the
43
94
  * aggregate credit percentage and the lowest independent percentage window.
44
95
  * No reading means unknown, which is not below the floor.
45
96
  */
46
- function belowUsageFloor(state: {
47
- readonly usage_floor?: number;
48
- readonly usage_floor_unit?: "credit" | "percent";
49
- readonly quota_windows?: readonly unknown[] | undefined;
50
- }): boolean {
51
- const windows = state.quota_windows as readonly ProviderQuotaWindow[] | undefined;
52
- if (windows === undefined || windows.length === 0) return false;
97
+ function belowUsageFloor(state: RoutedCandidate): boolean {
98
+ const rawWindows = state.quota_windows;
99
+ // A successful Start Plan balance (`quota_entitlements_known`) authoritatively
100
+ // lists its model grants: when the provider is zcode-start and no window
101
+ // matches the requested model, the account is below the floor even with an
102
+ // empty/absent window list — there is no grant to fund the model.
103
+ if (state.provider_id === "zcode-start" && state.quota_entitlements_known === true) {
104
+ if (rawWindows === undefined || rawWindows.length === 0) return true;
105
+ const knownGrants = rawWindows.some((window) => window.modelIds !== undefined);
106
+ if (!knownGrants) return true;
107
+ }
108
+ if (rawWindows === undefined || rawWindows.length === 0) return false;
109
+ const relevant = filterCandidateQuotaWindows(rawWindows, state);
110
+ // Model-scoped grants (Start Plan) are checked separately: a grant that has
111
+ // not started, has expired, or is exhausted cannot fund the requested model,
112
+ // so it must not count as credit.
113
+ const now = Date.now();
114
+ const grants = relevant.filter((window) => window.modelIds !== undefined);
115
+ if (state.provider_id === "zcode-start" && grants.length === 0 && rawWindows.some((window) => window.modelIds !== undefined)) {
116
+ return true;
117
+ }
118
+ if (
119
+ grants.length > 0 &&
120
+ !grants.some((window) => {
121
+ if (window.startsAt && Date.parse(window.startsAt) > now) return false;
122
+ if (window.resetsAt && Date.parse(window.resetsAt) <= now) return false;
123
+ if (typeof window.remaining === "number" && window.remaining <= 0) return false;
124
+ if (typeof window.remainingPercent === "number" && window.remainingPercent <= 0) return false;
125
+ const remaining = state.usage_floor_unit === "credit" ? window.remaining : window.remainingPercent;
126
+ return state.usage_floor === undefined || remaining == null || remaining > state.usage_floor;
127
+ })
128
+ ) {
129
+ return true;
130
+ }
131
+ const windows = relevant.filter((window) => window.modelIds === undefined);
53
132
  const credit = totalRemainingCredit(windows);
54
133
  const percent = totalRemainingPercent(windows);
55
134
  const exhaustedIndependentWindow = windows.some(
@@ -63,10 +142,32 @@ function belowUsageFloor(state: {
63
142
  if (credit === null && percent !== null && percent <= 0) return true;
64
143
  const floor = state.usage_floor;
65
144
  if (floor === undefined) return false;
145
+ // Free-tier models are not credit-funded, so the operator's configured floor
146
+ // threshold does not apply to them — a free model may use any remaining
147
+ // balance. This mirrors `creditPriority` (free tier skips credit preference).
148
+ // It deliberately comes AFTER the exhausted-window checks above: a free-tier
149
+ // account whose own quota window is spent (0 left) is still excluded, because
150
+ // that is the provider's own limit, not the credit floor.
151
+ if (state.free_tier === true) return false;
66
152
  const remaining = state.usage_floor_unit === "credit" ? credit : percent;
67
153
  return remaining !== null && remaining <= floor;
68
154
  }
69
155
 
156
+ /**
157
+ * Routing priority for an account's remaining allowance when two candidates sit
158
+ * in the same rank group: positive remaining credit first, then unknown, then
159
+ * known-zero last. Free-tier models skip the preference entirely — a free model
160
+ * routes to any account, as the provider's own directory stated it is free.
161
+ */
162
+ function creditPriority(candidate: RoutedCandidate): number {
163
+ if (candidate.free_tier === true) return 0;
164
+ const windows = candidate.quota_windows;
165
+ if (windows === undefined || windows.length === 0) return 0;
166
+ const credit = totalRemainingCredit(windows);
167
+ if (credit === null) return 0;
168
+ return credit > 0 ? 1 : -1;
169
+ }
170
+
70
171
  /**
71
172
  * Normalizes a requested model name before alias lookup. Coding-agent CLIs
72
173
  * version their model ids with variant suffixes (`claude-opus-5[1m]`,
@@ -233,10 +334,17 @@ function spreadDeadline(cooldownUntil: number | undefined): { cooldown_until?: n
233
334
  function earliestWindowResetMs(
234
335
  windows: readonly ProviderQuotaWindow[] | undefined,
235
336
  nowMs: number,
337
+ candidate: RouteCandidate,
236
338
  ): number | undefined {
237
339
  if (windows === undefined) return undefined;
340
+ windows = filterCandidateQuotaWindows(windows, candidate);
238
341
  let earliest: number | undefined;
239
342
  for (const window of windows) {
343
+ if (window.startsAt && Date.parse(window.startsAt) > nowMs) {
344
+ const at = Date.parse(window.startsAt);
345
+ if (earliest === undefined || at < earliest) earliest = at;
346
+ continue;
347
+ }
240
348
  if (typeof window.resetsAt !== "string" || window.resetsAt.length === 0) continue;
241
349
  const at = Date.parse(window.resetsAt);
242
350
  if (!Number.isFinite(at) || at <= nowMs) continue;
@@ -249,7 +357,7 @@ function earliestWindowResetMs(
249
357
  * The earliest instant any cooling candidate recovers, in epoch ms.
250
358
  *
251
359
  * `undefined` when no decision carries a deadline — which is the honest answer
252
- * for a set whose exclusions have no reset (`disabled`, `locked`), and keeps
360
+ * for a set whose exclusions have no reset (`disabled`), and keeps
253
361
  * `accountsRateLimitedError` from reporting a number nobody measured. The
254
362
  * minimum, not the maximum: the client can be served as soon as the *first*
255
363
  * account comes back, so quoting the slowest would hold it off longer than it
@@ -268,36 +376,22 @@ function earliestResetMs(
268
376
  }
269
377
 
270
378
  export class EligibilityEvaluator {
271
- evaluate(candidate: RouteCandidate): EligibilityDecision {
272
- const state = candidate as RouteCandidate & {
273
- readonly health_status?: string;
274
- readonly cooldown_kind?: "hard" | "soft";
275
- readonly cooldown_until?: number;
276
- readonly account_locked?: boolean;
277
- readonly locked?: boolean;
278
- readonly usage_floor?: number;
279
- readonly usage_floor_unit?: "credit" | "percent";
280
- readonly quota_windows?: readonly ProviderQuotaWindow[] | undefined;
281
- /** Slow-lane stamp applied at plan time; see `applySlowLane`. */
282
- readonly lane?: "slow";
283
- };
284
- if (state.account_locked || state.locked)
285
- return { eligible: false, reason: "locked", candidate };
286
- if (state.health_status === "disabled")
379
+ evaluate(candidate: RoutedCandidate): EligibilityDecision {
380
+ if (candidate.health_status === "disabled")
287
381
  return { eligible: false, reason: "disabled", candidate };
288
- if (state.health_status === "model_cooldown")
289
- return { eligible: false, reason: "model_cooldown", candidate, ...spreadDeadline(state.cooldown_until) };
290
- if (state.health_status === "cooldown" && state.cooldown_kind === "hard")
291
- return { eligible: false, reason: "cooldown_hard", candidate, ...spreadDeadline(state.cooldown_until) };
382
+ if (candidate.health_status === "model_cooldown")
383
+ return { eligible: false, reason: "model_cooldown", candidate, ...spreadDeadline(candidate.cooldown_until) };
384
+ if (candidate.health_status === "cooldown" && candidate.cooldown_kind === "hard")
385
+ return { eligible: false, reason: "cooldown_hard", candidate, ...spreadDeadline(candidate.cooldown_until) };
292
386
  // The floor is checked here, not after selection: an account below its
293
387
  // floor must not win the route. An account with no reading is left
294
388
  // eligible — a missing quota fetch is not proof the allowance is gone.
295
- if (belowUsageFloor(state))
389
+ if (belowUsageFloor(candidate))
296
390
  return {
297
391
  eligible: false,
298
392
  reason: "quota_exhausted",
299
393
  candidate,
300
- ...spreadDeadline(earliestWindowResetMs(state.quota_windows, Date.now())),
394
+ ...spreadDeadline(earliestWindowResetMs(candidate.quota_windows, Date.now(), candidate)),
301
395
  };
302
396
  // A slow lane is orthogonal to cooldown, never a member of it: the account
303
397
  // still serves on spare capacity when the request carries the slow usage
@@ -308,10 +402,10 @@ export class EligibilityEvaluator {
308
402
  // exclusion, not that it overrides one. An account with no usable allowance
309
403
  // is not rescued by the lane — the lane is a latency budget against spare
310
404
  // capacity, and an account below its floor has none.
311
- if (state.lane === "slow")
405
+ if (candidate.lane === "slow")
312
406
  return { eligible: true, reason: "slow", candidate };
313
- if (state.health_status === "cooldown")
314
- return { eligible: true, reason: "cooldown", candidate, ...spreadDeadline(state.cooldown_until) };
407
+ if (candidate.health_status === "cooldown")
408
+ return { eligible: true, reason: "cooldown", candidate, ...spreadDeadline(candidate.cooldown_until) };
315
409
  return { eligible: true, reason: "healthy", candidate };
316
410
  }
317
411
 
@@ -332,6 +426,7 @@ export class RoutingEngine {
332
426
  constructor(
333
427
  private readonly admission: AdmissionController = new InMemoryAdmissionController(),
334
428
  private readonly redis?: RedisClient,
429
+ private readonly db?: CartethyiaDatabase,
335
430
  ) {}
336
431
 
337
432
  private getRoundRobin(key: string): RoundRobinState {
@@ -467,13 +562,32 @@ export class RoutingEngine {
467
562
  accountId,
468
563
  this.redis,
469
564
  ).catch(() => undefined);
470
- const quotaWindows = cached?.quota.windows ?? [];
565
+ // The live cache is authoritative when warm; when it is cold (e.g. right
566
+ // after a restart, before the sweep re-reads accounts), fall back to the
567
+ // persisted quota snapshot — the durable source of truth — so routing
568
+ // still prefers accounts that actually have remaining credit. The snapshot
569
+ // is never fresher than the cache, but a stale balance beats an unknown
570
+ // one for ordering, and the sweep refreshes it on schedule.
571
+ let quota = cached?.quota;
572
+ if (quota === undefined && this.db !== undefined) {
573
+ const stored = await readQuotaSnapshot(this.db, accountId).catch(() => null);
574
+ quota = stored?.quota;
575
+ }
576
+ const rawWindows = quota?.windows;
577
+ // The cache is authoritative when it has a reading; when it does not,
578
+ // keep whatever the candidate already carried (the credit ordering reads
579
+ // these windows, and a miss must not erase a stamped value).
580
+ const quotaWindows =
581
+ rawWindows === undefined
582
+ ? undefined
583
+ : candidate.provider_id === "zcode-start" ? rawWindows : filterCandidateQuotaWindows(rawWindows, candidate);
471
584
  return {
472
585
  ...candidate,
473
586
  ...(setting?.usageFloor == null
474
587
  ? {}
475
588
  : { usage_floor: setting.usageFloor, usage_floor_unit: setting.usageFloorUnit }),
476
- quota_windows: quotaWindows,
589
+ ...(quotaWindows === undefined ? {} : { quota_windows: quotaWindows }),
590
+ ...(quota?.source === "zcode-start" && quota.error === null ? { quota_entitlements_known: true } : {}),
477
591
  };
478
592
  }
479
593
 
@@ -641,6 +755,11 @@ export class RoutingEngine {
641
755
  const pinnedDecisions = pinned.map((candidate) => this.eligibility.evaluate(candidate));
642
756
  const blocked = pinnedDecisions.find((decision) => !decision.eligible);
643
757
  if (blocked !== undefined) {
758
+ // A rebaseable continuation (complete conversation replay) is not owed
759
+ // to this account: the recorded exchange may travel as history to a
760
+ // fresh Run on an eligible account. Cooling/blocked pinned account must
761
+ // not turn that into a hard refusal — fall through to normal routing.
762
+ if (affinity.rebaseable) return null;
644
763
  const reason = blocked.reason;
645
764
  if (
646
765
  reason === "cooldown_hard" ||
@@ -810,11 +929,14 @@ export class RoutingEngine {
810
929
  );
811
930
  }
812
931
 
813
- // Single sort by rank (fresh → slow → cooling → saturated), stable.
932
+ // Single sort by rank (fresh → slow → cooling → saturated), stable. Within
933
+ // the same rank, accounts with remaining credit come first unless the model
934
+ // is free tier, which routes to any account.
814
935
  eligible = [...eligible].sort((left, right) => {
815
936
  const leftRank = decisions.get(admissionKey(left))?.rank ?? ROUTING_RANK.FRESH;
816
937
  const rightRank = decisions.get(admissionKey(right))?.rank ?? ROUTING_RANK.FRESH;
817
- return leftRank - rightRank;
938
+ if (leftRank !== rightRank) return leftRank - rightRank;
939
+ return creditPriority(right) - creditPriority(left);
818
940
  });
819
941
 
820
942
  // Provider routing rotates accounts within the same rank group only; it can
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { GatewayError, type GatewayErrorCode } from "../protocol/gateway-error";
17
17
 
18
- export interface ShutdownNotice {
18
+ interface ShutdownNotice {
19
19
  readonly code: Extract<GatewayErrorCode, "shutting_down">;
20
20
  readonly message: string;
21
21
  readonly reason?: string;
@@ -27,7 +27,7 @@ export interface ShutdownNotice {
27
27
  * with it before swapping the image so its callers are told the process is
28
28
  * coming straight back.
29
29
  */
30
- export const UPDATE_SHUTDOWN_REASON = "update";
30
+ const UPDATE_SHUTDOWN_REASON = "update";
31
31
 
32
32
  export function shutdownNotice(reason: string | undefined): ShutdownNotice {
33
33
  if (reason === UPDATE_SHUTDOWN_REASON) {
@@ -26,7 +26,7 @@ export function eventText(event: CanonicalEvent): string | undefined {
26
26
  // The endpoint path is authoritative: user-agent sniffing and prompt-text
27
27
  // marker scanning never override it, so routing cannot depend on client
28
28
  // identity strings or prompt content.
29
- export type WinningSource =
29
+ type WinningSource =
30
30
  "explicit_marker" | "body_shape" | "endpoint_path";
31
31
 
32
32
  export interface SurfaceInput {
@@ -35,7 +35,7 @@ export interface SurfaceInput {
35
35
  path: string;
36
36
  }
37
37
 
38
- export interface SurfaceDetection {
38
+ interface SurfaceDetection {
39
39
  surface: SourceSurface;
40
40
  winning_source: WinningSource;
41
41
  /**
@@ -64,7 +64,7 @@ export interface SurfaceAdapter {
64
64
  * it explicitly. Both detection lookups read this table, so adding a surface
65
65
  * is one row here plus its adapter.
66
66
  */
67
- export interface SurfaceDescriptor {
67
+ interface SurfaceDescriptor {
68
68
  readonly surface: SourceSurface;
69
69
  readonly pathPrefix: string;
70
70
  }
@@ -37,8 +37,8 @@ import type { ChatEncodeOptions, ChunkBase, JsonObject } from "./parse";
37
37
  */
38
38
  export function repairToolArguments(raw: string): string {
39
39
  if (raw.length === 0) return "{}";
40
- const repaired = repairToolArgumentText(raw);
41
40
  try {
41
+ const repaired = repairToolArgumentText(raw);
42
42
  const parsed: unknown = JSON.parse(repaired);
43
43
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return raw;
44
44
  return repaired;
@@ -52,7 +52,7 @@ function repairToolArgumentText(json: string): string {
52
52
  return jsonrepair(json);
53
53
  }
54
54
 
55
- export function bytesToString(value: string | Uint8Array): string {
55
+ function bytesToString(value: string | Uint8Array): string {
56
56
  return typeof value === "string" ? value : new TextDecoder().decode(value);
57
57
  }
58
58
 
@@ -84,7 +84,7 @@ export function getModel(options: ChatEncodeOptions, events: CanonicalEvent[]):
84
84
  * placeholder — passing the provider's own bytes through is the whole point of
85
85
  * the extension — but never an explicit configuration.
86
86
  */
87
- export function resolveObfuscation(options: ChatEncodeOptions): {
87
+ function resolveObfuscation(options: ChatEncodeOptions): {
88
88
  value: string | undefined;
89
89
  pinned: boolean;
90
90
  } {
@@ -96,7 +96,7 @@ export function resolveObfuscation(options: ChatEncodeOptions): {
96
96
  return { value: undefined, pinned: false };
97
97
  }
98
98
 
99
- export function eventObfuscation(event: CanonicalEvent): string | undefined {
99
+ function eventObfuscation(event: CanonicalEvent): string | undefined {
100
100
  if (event.type !== "content_delta" || event.content.kind !== "extension") return undefined;
101
101
  if (!event.content.name.includes("obfuscat")) return undefined;
102
102
  const payload = event.content.payload;
@@ -106,13 +106,13 @@ export function eventObfuscation(event: CanonicalEvent): string | undefined {
106
106
  }
107
107
 
108
108
  /** Audio output payload carried as an `audio` extension content part. */
109
- export function eventAudio(event: CanonicalEvent): Record<string, unknown> | undefined {
109
+ function eventAudio(event: CanonicalEvent): Record<string, unknown> | undefined {
110
110
  if (event.type !== "content_delta" || event.content.kind !== "extension") return undefined;
111
111
  if (event.content.name !== "audio") return undefined;
112
112
  return isRecord(event.content.payload) ? event.content.payload : undefined;
113
113
  }
114
114
 
115
- export function eventReasoningText(event: CanonicalEvent): string | undefined {
115
+ function eventReasoningText(event: CanonicalEvent): string | undefined {
116
116
  if (event.type !== "content_delta" || event.content.kind !== "reasoning") return undefined;
117
117
  return reasoningTextOf(event.content);
118
118
  }
@@ -144,7 +144,7 @@ function mergedReasoningText(events: readonly CanonicalEvent[]): string {
144
144
  export function finishReason(stopReason: CanonicalStopReason | undefined): string {
145
145
  return chatFinishReason(stopReason);
146
146
  }
147
- export function eventToolCall(
147
+ function eventToolCall(
148
148
  event: CanonicalEvent,
149
149
  ): { state: ToolCallState; hasArguments: boolean } | undefined {
150
150
  if (event.type === "tool_call_delta") {
@@ -32,7 +32,7 @@ export interface JsonObject {
32
32
  [key: string]: unknown;
33
33
  }
34
34
 
35
- export interface ParsedToolCall {
35
+ interface ParsedToolCall {
36
36
  id: string;
37
37
  name: string;
38
38
  arguments: string;
@@ -57,7 +57,7 @@ export interface ChunkBase {
57
57
 
58
58
 
59
59
 
60
- export function isSurfaceInput(value: unknown): value is SurfaceInput {
60
+ function isSurfaceInput(value: unknown): value is SurfaceInput {
61
61
  return isRecord(value) && "body" in value && "path" in value && typeof value.path === "string";
62
62
  }
63
63
 
@@ -75,7 +75,7 @@ export function requestBody(input: unknown): unknown {
75
75
  return decodeBody(isSurfaceInput(input) ? input.body : input);
76
76
  }
77
77
 
78
- export function nonNegativeNumber(value: unknown): number | undefined {
78
+ function nonNegativeNumber(value: unknown): number | undefined {
79
79
  const number = asNumber(value);
80
80
  return number !== undefined && number >= 0 ? number : undefined;
81
81
  }
@@ -87,13 +87,13 @@ const AUDIO_FORMAT_MEDIA_TYPE: Readonly<Record<string, string>> = {
87
87
  mpeg: "audio/mpeg",
88
88
  };
89
89
 
90
- export function audioFormatToMediaType(format: string | undefined): string {
90
+ function audioFormatToMediaType(format: string | undefined): string {
91
91
  if (format === undefined || format.length === 0) return "audio/mpeg";
92
92
  if (format.includes("/")) return format;
93
93
  return AUDIO_FORMAT_MEDIA_TYPE[format.toLowerCase()] ?? "audio/mpeg";
94
94
  }
95
95
 
96
- export function parseContentPart(value: unknown): ContentPart {
96
+ function parseContentPart(value: unknown): ContentPart {
97
97
  if (typeof value === "string") return textPart(value);
98
98
  if (!isRecord(value)) {
99
99
  throw new Error("Malformed content part: expected string or object");
@@ -173,7 +173,7 @@ export function parseContentPart(value: unknown): ContentPart {
173
173
  throw new Error(`Malformed content part: unsupported type "${type ?? "unknown"}"`);
174
174
  }
175
175
 
176
- export function parseContent(value: unknown): ContentPart[] {
176
+ function parseContent(value: unknown): ContentPart[] {
177
177
  if (typeof value === "string") return [textPart(value)];
178
178
  if (!Array.isArray(value)) return [];
179
179
  return value.map((part) => parseContentPart(part));
@@ -271,7 +271,7 @@ export function parseMessage(value: unknown): CanonicalMessage {
271
271
  }
272
272
 
273
273
 
274
- export function addExtension(
274
+ function addExtension(
275
275
  controls: GenerationControls,
276
276
  key: `extension:${string}`,
277
277
  value: unknown,
@@ -9,7 +9,7 @@ import { usageToChatWire, type OpenAiUsage } from "../../providers/provider-usag
9
9
  import { TEXT_ENCODER } from "../../protocol/sse-frame";
10
10
  import { SurfaceStreamEncoder } from "./stream-frame";
11
11
 
12
- export interface CompletionEncodingOptions {
12
+ interface CompletionEncodingOptions {
13
13
  response_id?: string;
14
14
  model?: string;
15
15
  created?: number;
@@ -150,7 +150,7 @@ export class CompletionStreamEncoder extends SurfaceStreamEncoder<CanonicalEvent
150
150
  }
151
151
  }
152
152
 
153
- export class CompletionAdapter implements SurfaceAdapter {
153
+ class CompletionAdapter implements SurfaceAdapter {
154
154
  readonly surface = "completion" as const;
155
155
 
156
156
  matchesBodyShape(body: unknown): boolean {
@@ -12,7 +12,7 @@ export function textPart(text: string): ContentPart {
12
12
  * `document.source`). Each parser reads its own shape and hands the extracted
13
13
  * values here so the data-URI split happens once.
14
14
  */
15
- export interface RawFileBlock {
15
+ interface RawFileBlock {
16
16
  /** Inline payload — may be raw base64 or a whole `data:` URI. */
17
17
  readonly data?: unknown;
18
18
  readonly media_type?: string | undefined;
@@ -6,7 +6,7 @@ import { completeRequiredSchema } from "../../protocol/codecs";
6
6
  import type { ToolDefinition } from "../../protocol/canonical-model";
7
7
 
8
8
  /** Tool-definition dialects preserve each surface's distinct wire fields. */
9
- export type ToolDialect = "chat" | "responses" | "messages";
9
+ type ToolDialect = "chat" | "responses" | "messages";
10
10
 
11
11
  /**
12
12
  * Parses a tool definition from one wire dialect.
@@ -165,7 +165,7 @@ function classifyNativeToolType(type: string): Exclude<ToolDefinition["tool_type
165
165
 
166
166
  export const CANONICAL_REASONING_EFFORTS: ReadonlySet<string> = new Set(REASONING_EFFORTS);
167
167
 
168
- export function isReasoningEffort(value: unknown): value is NonNullable<ReasoningIntent["effort"]> {
168
+ function isReasoningEffort(value: unknown): value is NonNullable<ReasoningIntent["effort"]> {
169
169
  return typeof value === "string" && CANONICAL_REASONING_EFFORTS.has(value as NonNullable<ReasoningIntent["effort"]>);
170
170
  }
171
171
 
@@ -53,7 +53,7 @@ export interface MessagesStreamEvent extends MessagesWireObject {
53
53
  | "message_stop";
54
54
  }
55
55
 
56
- export function contentArray(value: unknown, label: string): unknown[] {
56
+ function contentArray(value: unknown, label: string): unknown[] {
57
57
  if (stringValue(value)) return [value];
58
58
  if (!Array.isArray(value)) throw new Error(`Messages ${label} must be a string or block array`);
59
59
  return value;
@@ -165,7 +165,7 @@ export function parseBlock(value: unknown): ContentPart {
165
165
  };
166
166
  }
167
167
 
168
- export function parseBlocks(value: unknown, label: string): ContentPart[] {
168
+ function parseBlocks(value: unknown, label: string): ContentPart[] {
169
169
  return contentArray(value, label).map(parseBlock);
170
170
  }
171
171
 
@@ -207,7 +207,7 @@ export function parseTools(value: unknown): ToolDefinition[] | undefined {
207
207
 
208
208
 
209
209
  /** Reads `thinking.block_binding.prefix_mismatch_behavior` when present. */
210
- export function prefixMismatchBehavior(value: unknown): "drop_block" | "error" | undefined {
210
+ function prefixMismatchBehavior(value: unknown): "drop_block" | "error" | undefined {
211
211
  if (!isRecord(value)) return undefined;
212
212
  const behavior = value["prefix_mismatch_behavior"];
213
213
  if (behavior === "drop_block" || behavior === "error") return behavior;
@@ -51,12 +51,10 @@ export function reasoningSummaryHasBoundary(
51
51
  * ends one item is the first half of a word the next item finishes. Inserting
52
52
  * a break there painted `Gil` in the thinking pane and `iran` in the answer.
53
53
  *
54
- * The part that decides is the one that *opens*: a part starting with a
55
- * capital, or with the markdown marker a summary block begins with, is a new
56
- * thought; a part starting mid-word is a continuation. Requiring the previous
57
- * part to end a sentence as well dropped the break between two headers that
58
- * carry no terminal punctuation (`**Crafting a brief reply**`), which glued
59
- * separate thoughts into one line.
54
+ * A capital by itself is not a boundary: a sentence can continue in the next
55
+ * item, and inserting a paragraph there makes a captured chat look split. A
56
+ * sentence terminator or an explicit Markdown block marker is stronger
57
+ * evidence; otherwise a capitalized continuation gets only a space.
60
58
  */
61
59
  export function reasoningSummarySeparator(
62
60
  previous: ReasoningSummaryIdentity | undefined,
@@ -66,13 +64,10 @@ export function reasoningSummarySeparator(
66
64
  ): string {
67
65
  if (!reasoningSummaryHasBoundary(previous, current)) return "";
68
66
  if (/\s$/.test(previousText ?? "") || /^\s/.test(currentText)) return "";
69
- // The part that opens decides: a capital, or the markdown marker a summary
70
- // block starts with, begins a thought, so the break is a paragraph.
71
- if (/^[A-ZÀ-ÖØ-Þ*#\-]/.test(currentText)) return "\n\n";
72
- // A part opening mid-word is a continuation, not a new thought — that is the
73
- // `Gil` + `iran` case, and it gets nothing. But when the previous part closed
74
- // a sentence, the next one is a different artifact (the raw reasoning after a
75
- // summary), and gluing them produced `file.raw private reasoning`.
76
- if (/[.!?][)"'\]]*$/.test(previousText ?? "")) return " ";
67
+ if (/^[*#\-]/.test(currentText)) return "\n\n";
68
+ const previousEndedSentence = /[.!?][)"'\]]*$/.test(previousText ?? "");
69
+ if (previousEndedSentence && /^[A-ZÀ-ÖØ-Þ]/.test(currentText)) return "\n\n";
70
+ if (/^[A-ZÀ-ÖØ-Þ]/.test(currentText)) return " ";
71
+ if (previousEndedSentence) return " ";
77
72
  return "";
78
73
  }
@@ -7,7 +7,7 @@ import { hasOwn, parseResponsesRequest, unwrapBody } from "./parse";
7
7
  import { encodeResponsesEvents, encodeResponsesWireEvents } from "./encode";
8
8
 
9
9
  /** The OpenAI Responses public surface adapter. */
10
- export class ResponsesAdapter implements SurfaceAdapter {
10
+ class ResponsesAdapter implements SurfaceAdapter {
11
11
  readonly surface = "responses" as const;
12
12
 
13
13
  /** Check only top-level Responses shape markers, never arbitrary prompt text. */
@@ -1,15 +1,6 @@
1
1
  import { GatewayError } from "../../../protocol/gateway-error";
2
2
 
3
3
  /** Typed failures for the OpenAI Responses surface. */
4
- export class ResponsesReasoningError extends GatewayError {
5
- readonly field: "include" | "store" | "reasoning.encrypted_content";
6
-
7
- constructor(field: "include" | "store" | "reasoning.encrypted_content", message: string) {
8
- super("capability_unsupported", 400, message, { field });
9
- this.name = "ResponsesReasoningError";
10
- this.field = field;
11
- }
12
- }
13
4
 
14
5
  /** Stable typed failure for malformed canonical sequence numbers. */
15
6
  export class ResponsesSequenceError extends GatewayError {