cartethyia 2.7.3 → 2.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (625) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-Cj15GsMp.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  5. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
  6. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-BIccZmeL.js} +1 -1
  10. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  11. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
  12. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
  14. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  15. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js +1 -0
  16. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-DnQPSIA8.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  31. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js +9 -0
  32. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-BfAb5-o_.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
  44. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  45. package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
  46. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C0YYuZJ4.js → assert-C0K-c4eo.js} +1 -1
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-BllT4LcF.js} +1 -1
  52. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  53. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  54. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  55. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  56. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  57. package/dist/dashboard/assets/{card-Be5Uzamx.js → card-CtkX66Wd.js} +1 -1
  58. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  59. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  60. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  61. package/dist/dashboard/assets/{cli-tools-muFRphDu.js → cli-tools-CQXVMn4P.js} +1 -1
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-BQqBQd2n.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  65. package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-BrTEZ8zu.js} +1 -1
  68. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
  70. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  71. package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-EBHJ3DrE.js} +1 -1
  72. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
  74. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  75. package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-YSCE4UC2.js} +1 -1
  78. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-cPrX5vn6.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-BHHX_Tmm.js} +1 -1
  84. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/{layout-C_7A6H55.js.gz → layout-CajrYUnD.js.gz} +0 -0
  87. package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-Bx2I7XUe.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  89. package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
  90. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  91. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
  92. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
  94. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  95. package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
  96. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  97. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  98. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  99. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  100. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  101. package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
  102. package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
  103. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  104. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  105. package/dist/dashboard/assets/{routing-BEH32QOC.js → routing-in0f_IfK.js} +1 -1
  106. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  107. package/dist/dashboard/assets/{select-B3iTetHN.js → select-BLULNqTp.js} +2 -2
  108. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  109. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  110. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  111. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  112. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  113. package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-D2lIR0tg.js} +1 -1
  114. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  115. package/dist/dashboard/assets/{state-DLS7IDMy.js → state-DTARjfiW.js} +1 -1
  116. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  117. package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-c0oPSnqW.js} +1 -1
  118. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  119. package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-m8WGNPrz.js} +1 -1
  120. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  121. package/dist/dashboard/console-backgrounds.jpg +0 -0
  122. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  123. package/dist/dashboard/index.html +2 -2
  124. package/dist/dashboard/index.html.gz +0 -0
  125. package/dist/dashboard/precompressed-assets.json +59 -57
  126. package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
  127. package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
  128. package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
  129. package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
  130. package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
  131. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  132. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  133. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  134. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  135. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  136. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  137. package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
  138. package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
  139. package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
  140. package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
  141. package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
  142. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  143. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  144. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  145. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  146. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  147. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  148. package/package.json +4 -5
  149. package/scripts/cli/update.ts +15 -41
  150. package/src/boot/app.ts +67 -10
  151. package/src/boot/dependencies.ts +786 -732
  152. package/src/boot/lifecycle.ts +11 -12
  153. package/src/config/cooldowns.ts +34 -55
  154. package/src/config/network.ts +8 -4
  155. package/src/config/package-paths.ts +1 -1
  156. package/src/config/providers.ts +13 -14
  157. package/src/config/resources.ts +0 -5
  158. package/src/config/runtime.ts +2 -5
  159. package/src/config/spec.ts +26 -6
  160. package/src/console/api-keys/ban-constants.ts +38 -0
  161. package/src/console/api-keys/contracts.ts +92 -31
  162. package/src/console/api-keys/routes.ts +131 -6
  163. package/src/console/audit/routes.ts +3 -3
  164. package/src/console/auth/access.ts +4 -4
  165. package/src/console/auth/account-reset.ts +5 -5
  166. package/src/console/auth/audit-recorder.ts +1 -1
  167. package/src/console/auth/lockout.ts +7 -3
  168. package/src/console/auth/row-coercion.ts +57 -12
  169. package/src/console/auth/session-resolver.ts +1 -1
  170. package/src/console/auth/session-service.ts +8 -2
  171. package/src/console/auth/session.ts +40 -34
  172. package/src/console/auth/setup-service.ts +4 -3
  173. package/src/console/auth/user-store.ts +1 -1
  174. package/src/console/backup/9router-map.ts +0 -3
  175. package/src/console/backup/contracts.ts +11 -9
  176. package/src/console/backup/export-endpoint.ts +11 -12
  177. package/src/console/backup/nine-router.ts +1 -7
  178. package/src/console/backup/routes.ts +32 -36
  179. package/src/console/backup/service.ts +468 -468
  180. package/src/console/backup/store.ts +47 -29
  181. package/src/console/backup/validate.ts +11 -1
  182. package/src/console/cli-tools/contracts.ts +7 -7
  183. package/src/console/cli-tools/injectors/cline.ts +110 -110
  184. package/src/console/cli-tools/injectors/codex.ts +181 -181
  185. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  186. package/src/console/cli-tools/injectors/driver.ts +247 -247
  187. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  188. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  189. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  190. package/src/console/cli-tools/routes.ts +4 -4
  191. package/src/console/cli-tools/service.ts +1 -1
  192. package/src/console/cli-tools/store.ts +2 -2
  193. package/src/console/console-router-guards.ts +2 -2
  194. package/src/console/console-router.ts +36 -4
  195. package/src/console/dashboard-assets.ts +9 -9
  196. package/src/console/data-management/routes.ts +3 -3
  197. package/src/console/domain-registration.ts +18 -4
  198. package/src/console/inferhub/client.ts +0 -7
  199. package/src/console/inferhub/key-store.ts +1 -1
  200. package/src/console/inferhub/normalize.ts +2 -2
  201. package/src/console/inferhub/routes.ts +1 -1
  202. package/src/console/inferhub/types.ts +1 -1
  203. package/src/console/observability/contracts.ts +5 -1
  204. package/src/console/observability/live.ts +34 -21
  205. package/src/console/observability/logs.ts +4 -4
  206. package/src/console/observability/performance.ts +3 -3
  207. package/src/console/observability/routes.ts +3 -3
  208. package/src/console/observability/sse.ts +62 -8
  209. package/src/console/observability/store.ts +36 -20
  210. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  211. package/src/console/providers/catalog/contracts.ts +82 -22
  212. package/src/console/providers/catalog/model-operations.ts +1 -1
  213. package/src/console/providers/catalog/provider-operations.ts +110 -17
  214. package/src/console/providers/catalog/public-model-store.ts +0 -0
  215. package/src/console/providers/catalog/routes.ts +30 -6
  216. package/src/console/providers/catalog/store-helpers.ts +349 -326
  217. package/src/console/providers/catalog/store.ts +1662 -1292
  218. package/src/console/providers/detail/routes.ts +4 -4
  219. package/src/console/providers/oauth/callback-listener.ts +100 -18
  220. package/src/console/providers/oauth/routes.ts +95 -16
  221. package/src/console/quota/account-quota-global.ts +41 -3
  222. package/src/console/quota/account-quota-shared.ts +10 -3
  223. package/src/console/quota/account-quota-tenant.ts +285 -67
  224. package/src/console/quota/account-test-egress.ts +4 -4
  225. package/src/console/quota/daily-growth-job.ts +308 -0
  226. package/src/console/quota/native-identity.ts +2 -2
  227. package/src/console/quota/quota-view.ts +1 -1
  228. package/src/console/remote-support/routes.ts +1 -1
  229. package/src/console/remote-support/service.ts +2 -2
  230. package/src/console/routing/model/contracts.ts +1 -1
  231. package/src/console/routing/model/routes.ts +3 -3
  232. package/src/console/routing/pools/contracts.ts +13 -10
  233. package/src/console/routing/pools/import-parse.ts +3 -3
  234. package/src/console/routing/pools/operations.ts +14 -7
  235. package/src/console/routing/pools/probe-verdict.ts +8 -3
  236. package/src/console/routing/pools/relay-deploy.ts +12 -12
  237. package/src/console/routing/pools/routes.ts +3 -3
  238. package/src/console/routing/pools/store.ts +21 -1
  239. package/src/console/settings/contracts.ts +29 -2
  240. package/src/console/settings/routes.ts +3 -3
  241. package/src/console/settings/store.ts +20 -0
  242. package/src/console/share/share-router.ts +112 -44
  243. package/src/console/share/share-stats.ts +109 -39
  244. package/src/console/share/share-usage.ts +1 -1
  245. package/src/console/shared/errors.ts +2 -2
  246. package/src/console/shared/object.ts +18 -0
  247. package/src/console/shared/read-through-cache.ts +42 -6
  248. package/src/console/shared/transaction.ts +14 -0
  249. package/src/console/system-prompt/contracts.ts +89 -12
  250. package/src/console/system-prompt/routes.ts +98 -45
  251. package/src/console/updates/apply-update.ts +244 -0
  252. package/src/console/updates/routes.ts +256 -98
  253. package/src/main.ts +11 -5
  254. package/src/network/http2-bidi.ts +1 -1
  255. package/src/network/ip-utils.ts +298 -0
  256. package/src/network/outbound-fetch.ts +76 -5
  257. package/src/network/pool/agent.ts +7 -6
  258. package/src/network/pool/byte-accounting.ts +1 -1
  259. package/src/network/pool/resolver.ts +55 -51
  260. package/src/network/pool/selector.ts +150 -136
  261. package/src/network/pool-cooldown-store.ts +71 -16
  262. package/src/network/pool-health-machine.ts +22 -2
  263. package/src/network/rate-limit-verdict.ts +68 -0
  264. package/src/network/retry.ts +1 -1
  265. package/src/network/ssrf.ts +24 -75
  266. package/src/observability/event-log.ts +2 -2
  267. package/src/observability/event-query.ts +286 -214
  268. package/src/observability/log-sanitize.ts +1 -1
  269. package/src/observability/metrics.ts +4 -4
  270. package/src/observability/payload-capture.ts +6 -5
  271. package/src/observability/payload-store.ts +1 -1
  272. package/src/observability/runtime-metrics.ts +1 -1
  273. package/src/observability/telemetry-buffer.ts +12 -3
  274. package/src/observability/telemetry-reconciliation.ts +3 -3
  275. package/src/observability/telemetry-status.ts +1 -1
  276. package/src/observability/token-speed.ts +1 -1
  277. package/src/persistence/connection-url.ts +1 -1
  278. package/src/persistence/db-handle.ts +1 -1
  279. package/src/persistence/db-pg.ts +2 -2
  280. package/src/persistence/db-sqlite.ts +10 -5
  281. package/src/persistence/dialect.ts +5 -1
  282. package/src/persistence/readiness.ts +19 -7
  283. package/src/persistence/redis.ts +1 -1
  284. package/src/persistence/schema.ts +177 -45
  285. package/src/persistence/shared-cache.ts +2 -2
  286. package/src/persistence/sort-order.ts +1 -1
  287. package/src/persistence/stores/api-key-store.ts +109 -4
  288. package/src/persistence/stores/share-store.ts +20 -8
  289. package/src/persistence/stores/telemetry-store.ts +1 -1
  290. package/src/persistence/system-prompt-store.ts +63 -18
  291. package/src/persistence/tenant-preferences.ts +1 -1
  292. package/src/persistence/ttl-cache.ts +3 -3
  293. package/src/persistence/usage-flush.ts +7 -2
  294. package/src/protocol/canonical-model.ts +1 -1
  295. package/src/protocol/codecs.ts +12 -16
  296. package/src/protocol/dsml.ts +3 -49
  297. package/src/protocol/gateway-error.ts +6 -3
  298. package/src/protocol/image-clamp.ts +2 -2
  299. package/src/protocol/image-generation.ts +2 -2
  300. package/src/protocol/reasoning-text.ts +1 -1
  301. package/src/protocol/reasoning.ts +7 -52
  302. package/src/protocol/registry.ts +50 -15
  303. package/src/protocol/request/chat.ts +46 -19
  304. package/src/protocol/request/codex.ts +1 -1
  305. package/src/protocol/request/messages.ts +46 -14
  306. package/src/protocol/request/responses.ts +19 -10
  307. package/src/protocol/response/chat.ts +27 -12
  308. package/src/protocol/response/codex.ts +2 -2
  309. package/src/protocol/response/gemini.ts +1 -1
  310. package/src/protocol/response/messages.ts +6 -6
  311. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  312. package/src/protocol/response/responses.ts +3 -3
  313. package/src/protocol/sse-decoder.ts +64 -4
  314. package/src/protocol/stop-reason.ts +2 -2
  315. package/src/protocol/think-split.ts +347 -0
  316. package/src/protocol/translation/attachment-integrity.ts +3 -3
  317. package/src/protocol/translation/cache-controls.ts +8 -20
  318. package/src/protocol/translation/capabilities.ts +8 -8
  319. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  320. package/src/protocol/translation/dialect/inband.ts +39 -0
  321. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  322. package/src/protocol/translation/dialect/qwen.ts +114 -0
  323. package/src/protocol/translation/dialect/resolver.ts +83 -0
  324. package/src/protocol/translation/dialect/rules.ts +93 -0
  325. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  326. package/src/protocol/translation/dialect/types.ts +58 -0
  327. package/src/protocol/translation/quirks.ts +18 -4
  328. package/src/protocol/translation/thinking.ts +12 -252
  329. package/src/protocol/translation/tool-id.ts +3 -3
  330. package/src/protocol/translation/tool-repair.ts +1 -1
  331. package/src/protocol/upstream/post-json.ts +6 -1
  332. package/src/protocol/upstream/send-claude-messages.ts +3 -3
  333. package/src/providers/adapters/base-adapter.ts +1 -1
  334. package/src/providers/adapters/compatible-adapter.ts +44 -5
  335. package/src/providers/authentication/jwt-validator.ts +0 -5
  336. package/src/providers/authentication/oauth-client.ts +254 -253
  337. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  338. package/src/providers/authentication/oauth-refresh-service.ts +2 -2
  339. package/src/providers/bundled-discovery-providers.ts +6 -30
  340. package/src/providers/configured-openai-providers.ts +82 -97
  341. package/src/providers/configured-provider.ts +1 -1
  342. package/src/providers/default-registry.ts +23 -35
  343. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  344. package/src/providers/discovery/discovery-types.ts +0 -1
  345. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  346. package/src/providers/discovery/probe-phases.ts +50 -15
  347. package/src/providers/discovery/probing-service.ts +18 -20
  348. package/src/providers/integrations/agentrouter/agentrouter.ts +14 -5
  349. package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
  350. package/src/providers/integrations/anthropic.ts +4 -11
  351. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  352. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  353. package/src/providers/integrations/antigravity/antigravity.ts +15 -26
  354. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +2 -2
  355. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  356. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  357. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
  358. package/src/providers/integrations/buddy/buddy-identity.ts +108 -1
  359. package/src/providers/integrations/buddy/buddy-media.ts +16 -16
  360. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  361. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  362. package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -16
  363. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  364. package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -10
  365. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  366. package/src/providers/integrations/claude/claude-betas.ts +1 -1
  367. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  368. package/src/providers/integrations/claude/claude-credentials.ts +1 -1
  369. package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
  370. package/src/providers/integrations/claude/claude.ts +3 -10
  371. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  372. package/src/providers/integrations/cline/cline.ts +2 -2
  373. package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
  374. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  375. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  376. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  377. package/src/providers/integrations/codex/codex.ts +14 -15
  378. package/src/providers/integrations/commandcode/commandcode.ts +76 -116
  379. package/src/providers/integrations/commandcode/quota.ts +132 -48
  380. package/src/providers/integrations/cursor/cursor-http2.ts +11 -14
  381. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  382. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  383. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  384. package/src/providers/integrations/cursor/cursor.ts +53 -20
  385. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  386. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  387. package/src/providers/integrations/factory/factory.ts +13 -13
  388. package/src/providers/integrations/gemini.ts +11 -11
  389. package/src/providers/integrations/github/github-discovery.ts +55 -10
  390. package/src/providers/integrations/github/github-oauth.ts +6 -5
  391. package/src/providers/integrations/github/github.ts +47 -20
  392. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  393. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  394. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  395. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  396. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  397. package/src/providers/integrations/grok/grok.ts +88 -42
  398. package/src/providers/integrations/inferhub.ts +4 -11
  399. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  400. package/src/providers/integrations/kimi/kimi.ts +4 -11
  401. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  402. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  403. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  404. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  405. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  406. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  407. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  408. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  409. package/src/providers/integrations/kiro/kiro.ts +91 -78
  410. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  411. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  412. package/src/providers/integrations/muse/muse.ts +6 -9
  413. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  414. package/src/providers/integrations/opencode/provider.ts +17 -24
  415. package/src/providers/integrations/perplexity.ts +2 -2
  416. package/src/providers/integrations/qoder/qoder-catalog.ts +132 -17
  417. package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
  418. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
  419. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  420. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  421. package/src/providers/integrations/qoder/qoder-realm.ts +22 -28
  422. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  423. package/src/providers/integrations/qoder/qoder-umid.ts +33 -38
  424. package/src/providers/integrations/qoder/qoder-virtual.ts +14 -2
  425. package/src/providers/integrations/qoder/qoder.ts +521 -63
  426. package/src/providers/integrations/shared/connect.ts +0 -54
  427. package/src/providers/integrations/shared/hash.ts +15 -0
  428. package/src/providers/integrations/shared/session-cache.ts +136 -0
  429. package/src/providers/integrations/tokenharbor.ts +2 -2
  430. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  431. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  432. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  433. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  434. package/src/providers/integrations/trae/trae.ts +59 -19
  435. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  436. package/src/providers/integrations/zcode/oauth.ts +1 -1
  437. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  438. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  439. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  440. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  441. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  442. package/src/providers/operations/account-error-classification.ts +131 -116
  443. package/src/providers/operations/account-health-service.ts +31 -39
  444. package/src/providers/operations/account-reset-service.ts +58 -1
  445. package/src/providers/operations/cli-platform.ts +31 -5
  446. package/src/providers/operations/client-version-resolver.ts +4 -6
  447. package/src/providers/operations/client-versions.ts +10 -7
  448. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  449. package/src/providers/operations/provider-catalog-service.ts +22 -6
  450. package/src/providers/operations/provider-credential-service.ts +35 -6
  451. package/src/providers/operations/upstream-deadline.ts +3 -3
  452. package/src/providers/provider-contracts.ts +65 -10
  453. package/src/providers/provider-credential-resolver.ts +1 -1
  454. package/src/providers/provider-metadata.ts +17 -2
  455. package/src/providers/provider-model-definition.ts +22 -1
  456. package/src/providers/provider-probe.ts +4 -2
  457. package/src/providers/provider-registry.ts +4 -4
  458. package/src/providers/provider-usage.ts +81 -16
  459. package/src/providers/quota/cache.ts +29 -0
  460. package/src/providers/quota/quota-contracts.ts +28 -10
  461. package/src/providers/quota/refresh.ts +29 -6
  462. package/src/providers/quota/snapshot.ts +94 -0
  463. package/src/providers/search/search-provider.ts +6 -8
  464. package/src/providers/search/search-providers.ts +2 -1
  465. package/src/security/abuse.ts +8 -8
  466. package/src/security/admission/reasons.ts +17 -17
  467. package/src/security/admission/service.ts +2 -2
  468. package/src/security/admission/ttl.ts +61 -61
  469. package/src/security/api-key-auth.ts +20 -8
  470. package/src/security/client-router-fingerprint.ts +4 -4
  471. package/src/security/credential-codec.ts +3 -3
  472. package/src/security/csrf.ts +2 -2
  473. package/src/security/ip-boundary.ts +16 -189
  474. package/src/security/model-access-rule.ts +1 -1
  475. package/src/security/outbound-headers.ts +18 -0
  476. package/src/shared/coerce.ts +19 -0
  477. package/src/shared/invoke-guard.ts +47 -0
  478. package/src/transport/contracts.ts +13 -4
  479. package/src/transport/dispatch/attempt-finalize.ts +5 -4
  480. package/src/transport/dispatch/attempt-loop.ts +10 -7
  481. package/src/transport/dispatch/capture-policy.ts +34 -37
  482. package/src/transport/dispatch/empty-stream.ts +28 -25
  483. package/src/transport/dispatch/generation.ts +33 -53
  484. package/src/transport/dispatch/leases.ts +8 -8
  485. package/src/transport/dispatch/native-shared.ts +127 -0
  486. package/src/transport/dispatch/proxy-request.ts +10 -8
  487. package/src/transport/dispatch/responses-compact.ts +35 -56
  488. package/src/transport/dispatch/retry-policy.ts +4 -67
  489. package/src/transport/dispatch/stream-bridge.ts +1 -1
  490. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  491. package/src/transport/dispatch/streaming/finalize.ts +11 -10
  492. package/src/transport/dispatch/streaming/stream.ts +7 -3
  493. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  494. package/src/transport/dispatch/systemone.ts +27 -55
  495. package/src/transport/dispatch/token-count.ts +49 -46
  496. package/src/transport/dispatch/websearch.ts +24 -50
  497. package/src/transport/drain-endpoint.ts +3 -3
  498. package/src/transport/failure-policy.ts +19 -6
  499. package/src/transport/guards/auth-guard.ts +6 -11
  500. package/src/transport/guards/body-policy.ts +7 -7
  501. package/src/transport/guards/error-lifecycle.ts +18 -3
  502. package/src/transport/guards/guard-errors.ts +7 -3
  503. package/src/transport/guards/headroom-guard.ts +2 -2
  504. package/src/transport/guards/pipeline.ts +7 -7
  505. package/src/transport/guards/readiness-guard.ts +16 -2
  506. package/src/transport/guards/request-context.ts +15 -20
  507. package/src/transport/request/preparer.ts +306 -27
  508. package/src/transport/request/rtk/compress-request.ts +3 -3
  509. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  510. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  511. package/src/transport/request/state.ts +5 -20
  512. package/src/transport/request/system-prompt-injection.ts +74 -11
  513. package/src/transport/request/tenant-preferences.ts +22 -0
  514. package/src/transport/routing/candidate-index.ts +2 -2
  515. package/src/transport/routing/round-robin.ts +1 -1
  516. package/src/transport/routing/route-catalog.ts +113 -19
  517. package/src/transport/routing/route-model.ts +131 -38
  518. package/src/transport/routing/router.ts +154 -39
  519. package/src/transport/shutdown-notice.ts +21 -9
  520. package/src/transport/surface/adapters.ts +3 -3
  521. package/src/transport/surface/chat/encode.ts +7 -7
  522. package/src/transport/surface/chat/parse.ts +7 -7
  523. package/src/transport/surface/completion.ts +3 -3
  524. package/src/transport/surface/content-parts.ts +1 -1
  525. package/src/transport/surface/dialects.ts +2 -2
  526. package/src/transport/surface/messages/encode.ts +1 -1
  527. package/src/transport/surface/messages/parse.ts +3 -3
  528. package/src/transport/surface/responses/adapter.ts +1 -1
  529. package/src/transport/surface/responses/errors.ts +0 -9
  530. package/src/transport/vision-handoff-cache.ts +58 -0
  531. package/src/transport/vision-handoff.ts +376 -0
  532. package/src/workers/account-identity-sweep.ts +2 -2
  533. package/src/workers/buddy-growth-tasks.ts +392 -346
  534. package/src/workers/daily-checkin.ts +102 -49
  535. package/src/workers/maintenance-lease.ts +2 -35
  536. package/src/workers/oauth-refresh-sweep.ts +8 -1
  537. package/src/workers/quota-refresh-sweep.ts +120 -82
  538. package/src/workers/scheduler.ts +67 -4
  539. package/src/workers/sweep-runner.ts +24 -5
  540. package/src/workers/zcode-gift-claim.ts +108 -0
  541. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  542. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  543. package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
  544. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
  545. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
  546. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js +0 -1
  547. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  548. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
  549. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
  550. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
  551. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
  552. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
  553. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
  554. package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
  555. package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
  556. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
  557. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
  558. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
  559. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
  560. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  561. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  562. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
  563. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
  564. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
  565. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
  566. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
  567. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  568. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
  569. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
  570. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
  571. package/dist/dashboard/assets/SortableList-BAL5npBC.js +0 -1
  572. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  573. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  574. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
  575. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
  576. package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
  577. package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
  578. package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
  579. package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
  580. package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
  581. package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
  582. package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
  583. package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
  584. package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
  585. package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
  586. package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
  587. package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
  588. package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
  589. package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
  590. package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
  591. package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
  592. package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
  593. package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
  594. package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
  595. package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
  596. package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
  597. package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
  598. package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
  599. package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
  600. package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
  601. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
  602. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
  603. package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
  604. package/dist/dashboard/assets/numbers-BNDZnsUE.js +0 -2
  605. package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
  606. package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
  607. package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
  608. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  609. package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
  610. package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
  611. package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
  612. package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
  613. package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
  614. package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
  615. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  616. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  617. package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
  618. package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
  619. package/src/network/response-headers.ts +0 -39
  620. package/src/protocol/response/dsml.ts +0 -38
  621. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  622. /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-Bgd3zANQ.js} +0 -0
  623. /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  624. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  625. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -9,6 +9,7 @@
9
9
  * back to the prebuilt base catalog; capability does not — see `defineModel`.
10
10
  */
11
11
  import type { ServiceKind, WireFamily } from "../protocol/canonical-model";
12
+ import type { DialectId } from "../protocol/translation/dialect/types";
12
13
  import { modelsDevCatalog } from "./discovery/models-dev-catalog";
13
14
 
14
15
  /** Billing metadata for one provider model. */
@@ -53,8 +54,22 @@ export interface ModelDefinition {
53
54
  readonly reasoning: boolean;
54
55
  /** Explicitly supported reasoning effort scale (e.g. `["minimal", "low", "medium", "high", "xhigh"]`). */
55
56
  readonly reasoningEfforts?: readonly string[];
57
+ /**
58
+ * Optional thinking/effort override for this row. When present, the ladder
59
+ * resolver prefers these efforts and transport mode over the rule table.
60
+ * Compile-time only — no DB column (see `thinking-ladder.ts`).
61
+ */
62
+ readonly thinking?: {
63
+ readonly efforts?: readonly string[];
64
+ readonly mode?: "effort" | "budget" | "anthropic-adaptive" | "google-level";
65
+ };
56
66
  readonly toolCall: boolean;
57
67
  readonly cost: ModelCostDefinition;
68
+ /**
69
+ * Explicit wire dialect for this row. Absent = resolver fallback (heuristic
70
+ * + family default) — see `src/protocol/translation/dialect/`.
71
+ */
72
+ readonly dialect?: DialectId;
58
73
  /**
59
74
  * Whether the provider serves this row on a free plan tier.
60
75
  *
@@ -85,7 +100,7 @@ export interface ModelDefinition {
85
100
  * on a free account while `gpt-5.6-luna` bills `x0.14` and is refused. The
86
101
  * only signal is the refusal itself: `11158 "model [X] is not allowed for
87
102
  * user type [personal_free_user]"`. See
88
- * `.skills/cartethyia-reverse-engineering/references/codebuddy-dispatch-headers.md`.
103
+ * `documentation/research/codebuddy/2026-10-19-codebuddy-chat-dispatch-headers.md`.
89
104
  *
90
105
  * Optional, and absence is "tier unknown" rather than "served on free". Read
91
106
  * it as `=== true`; a UI that shows a tier must treat undefined as unknown,
@@ -168,6 +183,8 @@ export interface ModelHelperRow {
168
183
  readonly output?: readonly ModelDefinition["modalities"]["output"][number][];
169
184
  /** Explicitly supported reasoning effort scale. */
170
185
  readonly reasoningEfforts?: readonly string[];
186
+ /** Optional thinking/effort override; see `ModelDefinition.thinking`. */
187
+ readonly thinking?: ModelDefinition["thinking"];
171
188
  readonly toolCall?: boolean;
172
189
  /**
173
190
  * Marks a row served by a free plan tier. Such rows bill zero per token
@@ -186,6 +203,8 @@ export interface ModelHelperRow {
186
203
  readonly freeTier?: boolean;
187
204
  /** Whether the provider refuses this row on a free plan. See `ModelDefinition.proOnly`. */
188
205
  readonly proOnly?: boolean;
206
+ /** Explicit wire dialect override for this row. See `ModelDefinition.dialect`. */
207
+ readonly dialect?: DialectId;
189
208
  }
190
209
 
191
210
  function defaultEndpoint(wireFamily: ModelDefinition["wireFamily"]): string {
@@ -283,10 +302,12 @@ export function defineModel(row: ModelHelperRow): ModelDefinition {
283
302
  },
284
303
  reasoning: row.reasoning ?? false,
285
304
  ...(row.reasoningEfforts !== undefined ? { reasoningEfforts: row.reasoningEfforts } : {}),
305
+ ...(row.thinking === undefined ? {} : { thinking: row.thinking }),
286
306
  toolCall: row.toolCall ?? true,
287
307
  cost,
288
308
  ...(row.freeTier === undefined ? {} : { freeTier: row.freeTier }),
289
309
  ...(row.proOnly === undefined ? {} : { proOnly: row.proOnly }),
310
+ ...(row.dialect === undefined ? {} : { dialect: row.dialect }),
290
311
  ...(limitsEstimated ? { limitsEstimated: true } : {}),
291
312
  };
292
313
  }
@@ -12,6 +12,7 @@
12
12
  * report a pool as refused for a reason replacing the proxy cannot fix.
13
13
  */
14
14
  import { encodeWireRequest } from "../protocol/registry";
15
+ import { resolveDialect } from "../protocol/translation/dialect/resolver";
15
16
  import type { CanonicalRequest } from "../protocol/canonical-model";
16
17
  import type { ProviderDispatchContext } from "./provider-contracts";
17
18
  import { providerBaseUrl, providerRateLimitIsIpScoped } from "./provider-metadata";
@@ -60,7 +61,7 @@ function probeRequest(): CanonicalRequest {
60
61
  stream: true,
61
62
  source_surface: "chat",
62
63
  generation_controls: {},
63
- } as unknown as CanonicalRequest;
64
+ };
64
65
  }
65
66
 
66
67
  /**
@@ -98,6 +99,7 @@ export async function buildProviderProbeRequest(
98
99
  ...(spec.chatTokenLimitField === undefined
99
100
  ? {}
100
101
  : { chatTokenLimitField: spec.chatTokenLimitField }),
102
+ dialectPolicy: resolveDialect(PROBE_MODEL, providerId, "chat"),
101
103
  });
102
104
  // The adapter's own hooks build the wire body; without running them the
103
105
  // probe would omit the agent-tool fingerprint and be refused on a healthy
@@ -116,7 +118,7 @@ export async function buildProviderProbeRequest(
116
118
  );
117
119
  const headers = (await spec.buildExtraHeaders?.(probeContext(providerId), source)) ?? {};
118
120
  return {
119
- url: `${providerBaseUrl(providerId).replace(/\/+$/, "")}/inference/openai/v1/chat/completions`,
121
+ url: `${providerBaseUrl(providerId)}/inference/openai/v1/chat/completions`,
120
122
  headers: { "content-type": "application/json", ...headers },
121
123
  body: payload,
122
124
  };
@@ -89,7 +89,7 @@ function assertAdapterIdentity(expected: ProviderId, adapter: ProviderAdapter):
89
89
  // error: `invalid_request` would blame the caller and invite a byte-identical
90
90
  // retry. `internal_error` (500) says what is true — the gateway is
91
91
  // misconfigured and the request never had a chance.
92
- throw new GatewayError("internal_error", 500, "Loaded adapter ID does not match registration", {
92
+ throw new GatewayError("internal_error", 500, "Loaded adapter ID does not match registration (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
93
93
  provider_id: expected,
94
94
  adapter_id: adapter.provider_id,
95
95
  });
@@ -122,12 +122,12 @@ export class ProviderRegistry {
122
122
  register(registration: ProviderRegistration): void {
123
123
  const providerId = parseProviderId(registration.provider_id);
124
124
  if (this.#registrations.has(providerId)) {
125
- throw new GatewayError("invalid_request", 409, "Provider is already registered", {
125
+ throw new GatewayError("invalid_request", 409, "Provider is already registered (request {requestId}). This name's taken — pick another alias ✨", {
126
126
  provider_id: providerId,
127
127
  });
128
128
  }
129
129
  if (providerId !== registration.provider_id) {
130
- throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized", {
130
+ throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
131
131
  provider_id: registration.provider_id,
132
132
  });
133
133
  }
@@ -158,7 +158,7 @@ export class ProviderRegistry {
158
158
  upsert(registration: ProviderRegistration): boolean {
159
159
  const providerId = parseProviderId(registration.provider_id);
160
160
  if (providerId !== registration.provider_id) {
161
- throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized", {
161
+ throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
162
162
  provider_id: registration.provider_id,
163
163
  });
164
164
  }
@@ -11,6 +11,7 @@ export interface OpenAiUsage {
11
11
  };
12
12
  }
13
13
  import { modelsDevCatalog } from "./discovery/models-dev-catalog";
14
+ import { resolveCacheMode } from "../config/providers";
14
15
 
15
16
  /**
16
17
  * Extract cost multipliers from catalog model cost JSONB.
@@ -125,6 +126,36 @@ export function repriceUsage(
125
126
  : { ...usage, estimated_cost: estimatedCost };
126
127
  }
127
128
 
129
+ /**
130
+ * Simplified cache accounting: when the provider reported a cache hit, the
131
+ * whole prompt counts as cached — request input ber-cache = cache-eligible →
132
+ * input = cache. The cache-write volume is already inside the all-in input
133
+ * total on cache-aware shapes, so it stays separate and is not double-counted
134
+ * as a hit. A reported `0` or an absent report stay a cache miss. In `real`
135
+ * mode the provider's reported split is kept as-is.
136
+ *
137
+ * Exported so the hand-built record producers (Cursor, Kiro) apply the same
138
+ * rule as `normalizeUsage`.
139
+ */
140
+ export function applyCacheMode(usage: UsageRecord): UsageRecord {
141
+ if (resolveCacheMode() !== "simplified") return usage;
142
+ if (
143
+ typeof usage.cached_input_tokens === "number" &&
144
+ usage.cached_input_tokens > 0 &&
145
+ usage.input_tokens > 0
146
+ ) {
147
+ const writes =
148
+ typeof usage.cache_write_tokens === "number" ? Math.max(0, usage.cache_write_tokens) : 0;
149
+ const cached = Math.min(usage.input_tokens, Math.max(0, usage.input_tokens - writes));
150
+ return {
151
+ ...usage,
152
+ cached_input_tokens: cached,
153
+ uncached_input_tokens: 0,
154
+ };
155
+ }
156
+ return usage;
157
+ }
158
+
128
159
  /**
129
160
  * Normalize provider-specific usage data into canonical UsageRecord.
130
161
  * Handles Anthropic (cache_read_input_tokens, cache_creation_input_tokens, output_tokens_details.thinking_tokens)
@@ -250,6 +281,21 @@ export function normalizeUsage(input: Record<string, unknown>): UsageRecord {
250
281
  input_tokens += cache_write_tokens;
251
282
  }
252
283
 
284
+ // Simplified cache accounting: a reported hit makes the whole prompt count as
285
+ // cached — request input ber-cache = cache-eligible → input = cache. The
286
+ // cache-write volume is already inside the all-in input total on cache-aware
287
+ // shapes, so it stays separate and is not double-counted as a hit. Applied
288
+ // before pricing so the estimated cost reads the same simplified split as the
289
+ // record.
290
+ if (typeof cached_input_tokens === "number" && cached_input_tokens > 0 && input_tokens > 0) {
291
+ if (resolveCacheMode() === "simplified") {
292
+ const writes =
293
+ typeof cache_write_tokens === "number" ? Math.max(0, cache_write_tokens) : 0;
294
+ cached_input_tokens = Math.min(input_tokens, Math.max(0, input_tokens - writes));
295
+ uncached_input_tokens = 0;
296
+ }
297
+ }
298
+
253
299
  // Handle reasoning tokens: Anthropic and OpenAI both use output_tokens_details
254
300
  let reasoning_tokens: number | "unavailable" = "unavailable";
255
301
  const details: NonNullable<UsageRecord["details"]> = {};
@@ -436,28 +482,47 @@ export function usageToMessagesWire(usage: UsageRecord | undefined): Record<stri
436
482
  * and event shapes — only the key lookup lives here, so a new upstream key
437
483
  * is added once and every reader picks it up.
438
484
  */
439
- export const REASONING_TEXT_KEYS: readonly string[] = ["reasoning_content", "reasoning"];
485
+ export const REASONING_TEXT_KEYS: readonly string[] = [
486
+ "reasoning_content",
487
+ "reasoning",
488
+ "reasoning_summary",
489
+ "reasoning_summary_text",
490
+ "reasoning_text",
491
+ ];
492
+
493
+ /**
494
+ * Extracts readable text from one reasoning value. Accepts a plain string and
495
+ * the object shapes upstreams use for reasoning deltas: `{text}`,
496
+ * `{summary}`, `{delta}` (Responses-style), or an array of parts.
497
+ */
498
+ function reasoningTextValue(value: unknown): string | undefined {
499
+ if (typeof value === "string") return value;
500
+ if (Array.isArray(value)) {
501
+ let text = "";
502
+ for (const part of value) {
503
+ const piece = reasoningTextValue(part);
504
+ if (piece !== undefined) text += piece;
505
+ }
506
+ return text.length > 0 ? text : undefined;
507
+ }
508
+ if (typeof value !== "object" || value === null) return undefined;
509
+ const rec = value as Record<string, unknown>;
510
+ for (const key of ["text", "summary", "delta", "content"]) {
511
+ const piece = reasoningTextValue(rec[key]);
512
+ if (piece !== undefined) return piece;
513
+ }
514
+ return undefined;
515
+ }
440
516
 
441
517
  /** First string found under a known reasoning-text key, or undefined. */
442
518
  export function readReasoningText(source: Record<string, unknown>): string | undefined {
443
519
  for (const key of REASONING_TEXT_KEYS) {
444
- const value = source[key];
445
- if (typeof value === "string") return value;
520
+ const text = reasoningTextValue(source[key]);
521
+ if (text !== undefined) return text;
446
522
  }
447
523
  // Qwen/MiniMax-family `reasoning_details[]` parts carrying text fragments.
448
- const details = source["reasoning_details"];
449
- if (Array.isArray(details)) {
450
- const text = details
451
- .map((part) =>
452
- typeof part === "string"
453
- ? part
454
- : typeof (part as Record<string, unknown>)?.["text"] === "string"
455
- ? ((part as Record<string, unknown>)["text"] as string)
456
- : "",
457
- )
458
- .join("");
459
- if (text.length > 0) return text;
460
- }
524
+ const details = reasoningTextValue(source["reasoning_details"]);
525
+ if (details !== undefined) return details;
461
526
  return undefined;
462
527
  }
463
528
 
@@ -318,6 +318,35 @@ export async function invalidateQuotaCache(
318
318
  }
319
319
  }
320
320
 
321
+ /**
322
+ * Drops many accounts from one lens in a single pass.
323
+ *
324
+ * The batch-delete route clears several accounts' quota caches at once. The
325
+ * in-process tier is swept once (one `keys()` pass), and Redis is cleared with
326
+ * one pipeline, so the cost does not scale with per-account round trips.
327
+ */
328
+ export async function invalidateQuotaCaches(
329
+ lens: string,
330
+ accountIds: readonly string[],
331
+ redis: RedisClient | undefined,
332
+ ): Promise<void> {
333
+ if (accountIds.length === 0) return;
334
+ const keys = accountIds.map((accountId) => quotaKey(lens, accountId));
335
+ const keySet = new Set(keys);
336
+ for (const key of inMemoryQuotaCache.keys()) {
337
+ if (keySet.has(key)) inMemoryQuotaCache.delete(key);
338
+ }
339
+ if (redis === undefined) return;
340
+ try {
341
+ const pipeline = redis.pipeline();
342
+ for (const key of keys) pipeline.del(key);
343
+ await pipeline.exec().catch(() => undefined);
344
+ } catch (error) {
345
+ metrics.quota_cache_invalidate_failed.inc(1);
346
+ log.warn("quota cache batch invalidate failed", { lens, error: String(error) });
347
+ }
348
+ }
349
+
321
350
  /**
322
351
  * Drops one account from EVERY cache lens. Global accounts are shared, but a
323
352
  * release-N writer may have written the same account under a per-tenant lens,
@@ -18,6 +18,10 @@ export interface ProviderQuotaWindow {
18
18
  readonly remaining?: number | null;
19
19
  /** False means the timestamp is a hard expiry rather than a recurring reset. */
20
20
  readonly recurring?: boolean;
21
+ /** Exact routing model IDs this grant serves; absent means account-wide. */
22
+ readonly modelIds?: readonly string[];
23
+ /** Activation instant: future grants remain visible but cannot fund requests. */
24
+ readonly startsAt?: string | null;
21
25
  }
22
26
 
23
27
  export interface ProviderQuotaResult {
@@ -58,20 +62,27 @@ export function totalRemainingCredit(
58
62
  * independent quota windows, or `null` when neither is reported.
59
63
  *
60
64
  * Pooled credits are summed before converting to percent, so one exhausted
61
- * bonus pack cannot hide balance in another. Independent windows keep their
62
- * own remaining percent and the lowest one gates the account.
65
+ * bonus pack cannot hide balance in another. A pooled window without a ceiling
66
+ * (Command Code credits) contributes its absolute remaining to the credit sum
67
+ * but cannot produce a percent, so it is not counted in the percent ratio.
68
+ * Independent windows keep their own remaining percent and the lowest one
69
+ * gates the account.
63
70
  */
64
71
  export function totalRemainingPercent(
65
72
  windows: readonly ProviderQuotaWindow[],
66
73
  ): number | null {
67
74
  let creditLimit = 0;
68
75
  let creditRemaining = 0;
76
+ let hasPooledLimit = false;
69
77
  let independentPercent: number | null = null;
70
78
  for (const window of windows) {
71
79
  if (window.accounting === "pooled") {
72
80
  const balance = creditWindowBalance(window);
73
81
  if (balance !== null) {
74
- creditLimit += balance.limit;
82
+ if (balance.limit !== null) {
83
+ creditLimit += balance.limit;
84
+ hasPooledLimit = true;
85
+ }
75
86
  creditRemaining += balance.remaining;
76
87
  }
77
88
  continue;
@@ -85,8 +96,13 @@ export function totalRemainingPercent(
85
96
  independentPercent =
86
97
  independentPercent === null ? clamped : Math.min(independentPercent, clamped);
87
98
  }
88
- const creditPercent =
89
- creditLimit > 0 ? Math.min(100, Math.max(0, (creditRemaining / creditLimit) * 100)) : null;
99
+ // A pooled window with a reported ceiling (including a 0 ceiling — a spent
100
+ // balance) always defines a percent: 0 ceiling means 0% remaining.
101
+ const creditPercent = hasPooledLimit
102
+ ? creditLimit > 0
103
+ ? Math.min(100, Math.max(0, (creditRemaining / creditLimit) * 100))
104
+ : 0
105
+ : null;
90
106
  if (creditPercent === null) return independentPercent;
91
107
  if (independentPercent === null) return creditPercent;
92
108
  return Math.min(creditPercent, independentPercent);
@@ -94,20 +110,22 @@ export function totalRemainingPercent(
94
110
 
95
111
  function creditWindowBalance(
96
112
  window: ProviderQuotaWindow,
97
- ): { readonly limit: number; readonly remaining: number } | null {
113
+ ): { readonly limit: number | null; readonly remaining: number } | null {
114
+ // A ceiling of 0 is a spent balance, not an absence of data; only a window
115
+ // with no remaining figure at all is skipped. `limit` may be null (Command
116
+ // Code reports credits without a ceiling) — the remaining total still counts.
98
117
  const limit =
99
- typeof window.limit === "number" && Number.isFinite(window.limit) && window.limit > 0
118
+ typeof window.limit === "number" && Number.isFinite(window.limit) && window.limit >= 0
100
119
  ? window.limit
101
120
  : null;
102
- if (limit === null) return null;
103
121
  let remaining: number | null = null;
104
122
  if (typeof window.remaining === "number" && Number.isFinite(window.remaining)) {
105
123
  remaining = window.remaining;
106
- } else if (typeof window.used === "number" && Number.isFinite(window.used)) {
124
+ } else if (typeof window.used === "number" && Number.isFinite(window.used) && limit !== null) {
107
125
  remaining = limit - window.used;
108
126
  }
109
127
  if (remaining === null) return null;
110
- return { limit, remaining: Math.min(limit, Math.max(0, remaining)) };
128
+ return { limit, remaining: Math.max(0, remaining) };
111
129
  }
112
130
 
113
131
  const TIMEOUT_MS = 15_000;
@@ -15,8 +15,21 @@ import { fetchProviderQuota } from "./quota-support";
15
15
  import { type FetchLike, type ProviderQuotaResult } from "./quota-contracts";
16
16
  import type { ProviderId } from "../provider-contracts";
17
17
  import type { ProviderRegistry } from "../provider-registry";
18
- import { GLOBAL_QUOTA_LENS, setCachedQuota } from "./cache";
19
- import { record } from "../authentication/oauth-flow-store";
18
+ import { quotaLens, setCachedQuota } from "./cache";
19
+ import { persistQuotaSnapshot } from "./snapshot";
20
+ import { asJsonbRecord } from "../../shared/coerce";
21
+ import { KIRO_DEFAULT_LABELS } from "../integrations/kiro/kiro-oauth";
22
+
23
+ /**
24
+ * The stock Kiro fallback labels as a SQL literal list. Built once from the
25
+ * canonical set (`KIRO_DEFAULT_LABELS`) and escaped for the single-quote SQL
26
+ * literal; the values are compile-time constants, so this fragment is static.
27
+ */
28
+ const KIRO_DEFAULT_LABEL_IN = sql.raw(
29
+ [...KIRO_DEFAULT_LABELS]
30
+ .map((label) => `'${label.replace(/'/g, "''")}'`)
31
+ .join(", "),
32
+ );
20
33
 
21
34
  /** Why an account quota refresh failed; `null` means the fetch succeeded. */
22
35
  export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed";
@@ -52,7 +65,7 @@ export interface QuotaRefreshDeps {
52
65
  */
53
66
  readonly resolveCredential: (providerId: ProviderId, accountId: string) => Promise<string>;
54
67
  /** Marks a stored api_key credential with the upstream-distinguishable envelope. */
55
- readonly markApiKeyCredential?: (providerId: string, credential: string) => string;
68
+ readonly markApiKeyCredential?: (providerId: string, credential: string) => Promise<string>;
56
69
  /** Invalidates the route snapshot when a stamped figure may exclude an account. */
57
70
  readonly snapshotInvalidator?: { invalidate(): unknown } | undefined;
58
71
  }
@@ -219,7 +232,7 @@ export interface QuotaRefreshTarget {
219
232
 
220
233
  /** Cache lens an account's quota belongs under. */
221
234
  export function targetLens(target: QuotaRefreshTarget): string {
222
- return target.tenantId ?? GLOBAL_QUOTA_LENS;
235
+ return quotaLens(target.tenantId);
223
236
  }
224
237
 
225
238
  const TARGET_COLUMNS = {
@@ -352,10 +365,10 @@ async function runQuotaRefresh(
352
365
  // collector to the key-only probe, and OAuth credentials carry no key.
353
366
  const marked =
354
367
  target.credentialKind === "api_key"
355
- ? (deps.markApiKeyCredential?.(target.providerId, credential) ?? credential)
368
+ ? (await deps.markApiKeyCredential?.(target.providerId, credential) ?? credential)
356
369
  : credential;
357
370
  try {
358
- const authState = record(target.authState);
371
+ const authState = asJsonbRecord(target.authState);
359
372
  quota = await fetchProviderQuota(deps.providerRegistry, target.providerId, marked, fetcher, {
360
373
  // Carried so a collector can reuse a reading captured from real
361
374
  // traffic rather than spending a request to rediscover it.
@@ -399,6 +412,15 @@ async function runQuotaRefresh(
399
412
  log.warn(`[quota] failed to record plan for account=${target.accountId}`, error as Error);
400
413
  }
401
414
  }
415
+ // Persist the whole successful read, not just the plan. The row survives a
416
+ // restart that empties the in-process cache, so a cold console open renders
417
+ // the last known balance instead of nothing (and the sweep refreshes it). A
418
+ // failed fetch leaves the previous snapshot in place — same reasoning as the
419
+ // plan column: failure is evidence about the network/credential, not that
420
+ // the account's balance changed.
421
+ if (quota.error === null) {
422
+ await persistQuotaSnapshot(deps.db, target.accountId, quota);
423
+ }
402
424
  // A quota surface that names the account adopts the identity — but only over
403
425
  // a default label. An operator's custom rename is never overwritten, and the
404
426
  // CASE keeps the read and the write in one statement so a concurrent rename
@@ -416,6 +438,7 @@ async function runQuotaRefresh(
416
438
  label: sql`CASE
417
439
  WHEN lower(trim(${providerAccounts.label})) = lower(trim(${providerAccounts.providerId})) THEN ${quota.accountLabel}
418
440
  WHEN ${providerAccounts.label} LIKE '%|%' THEN ${quota.accountLabel}
441
+ WHEN ${providerAccounts.label} IN (${KIRO_DEFAULT_LABEL_IN}) THEN ${quota.accountLabel}
419
442
  ELSE ${providerAccounts.label}
420
443
  END`,
421
444
  })
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Persisted quota snapshots.
3
+ *
4
+ * The in-process quota cache is a short TTL and empty on a cold start, so
5
+ * right after a restart the console would render no balance for every account
6
+ * until the sweep re-reads them — while Buddy's usage snapshots survived via a
7
+ * table. This is the generic counterpart: every provider's last successful
8
+ * quota read is persisted on the account row, and the console falls back to it
9
+ * when the cache is cold. A failed fetch leaves the previous snapshot in place,
10
+ * mirroring `quota_plan`.
11
+ */
12
+ import { eq, inArray } from "drizzle-orm";
13
+ import type { CartethyiaDatabase } from "../../persistence/db-handle";
14
+ import { providerAccounts, type ProviderQuotaResultSnapshot } from "../../persistence/schema";
15
+ import type { ProviderQuotaResult } from "./quota-contracts";
16
+
17
+ /**
18
+ * Parses the `quota_snapshot` column. Postgres' jsonb arrives as an object;
19
+ * SQLite's text-backed column arrives as a JSON string — accept both.
20
+ */
21
+ function parseSnapshot(value: unknown): ProviderQuotaResultSnapshot | null {
22
+ if (value === null || value === undefined) return null;
23
+ let root: unknown = value;
24
+ if (typeof value === "string") {
25
+ try {
26
+ root = JSON.parse(value);
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ if (typeof root !== "object" || root === null || Array.isArray(root)) return null;
32
+ const rec = root as Record<string, unknown>;
33
+ const { quota, fetchedAt } = rec;
34
+ if (typeof quota !== "object" || quota === null || typeof fetchedAt !== "string") return null;
35
+ return { quota: quota as ProviderQuotaResult, fetchedAt };
36
+ }
37
+
38
+ /** Stores one successful quota read on the account row. Never throws. */
39
+ export async function persistQuotaSnapshot(
40
+ db: CartethyiaDatabase,
41
+ accountId: string,
42
+ quota: ProviderQuotaResult,
43
+ fetchedAt: Date = new Date(),
44
+ ): Promise<void> {
45
+ try {
46
+ await db
47
+ .update(providerAccounts)
48
+ .set({ quotaSnapshot: { quota, fetchedAt: fetchedAt.toISOString() } })
49
+ .where(eq(providerAccounts.id, accountId));
50
+ } catch {
51
+ // A snapshot that cannot be stored is a stale display, not an error the
52
+ // caller must handle.
53
+ }
54
+ }
55
+
56
+ /** Reads one account's last successful quota snapshot, if any. */
57
+ export async function readQuotaSnapshot(
58
+ db: CartethyiaDatabase,
59
+ accountId: string,
60
+ ): Promise<ProviderQuotaResultSnapshot | null> {
61
+ try {
62
+ const rows = await db
63
+ .select({ quotaSnapshot: providerAccounts.quotaSnapshot })
64
+ .from(providerAccounts)
65
+ .where(eq(providerAccounts.id, accountId))
66
+ .limit(1);
67
+ return parseSnapshot(rows[0]?.quotaSnapshot);
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ /** Batch read for one page render: one query for every account. */
74
+ export async function readQuotaSnapshots(
75
+ db: CartethyiaDatabase,
76
+ accountIds: readonly string[],
77
+ ): Promise<Map<string, ProviderQuotaResultSnapshot>> {
78
+ const found = new Map<string, ProviderQuotaResultSnapshot>();
79
+ if (accountIds.length === 0) return found;
80
+ try {
81
+ const rows = await db
82
+ .select({ id: providerAccounts.id, quotaSnapshot: providerAccounts.quotaSnapshot })
83
+ .from(providerAccounts)
84
+ .where(inArray(providerAccounts.id, [...accountIds]));
85
+ for (const row of rows) {
86
+ const snapshot = parseSnapshot(row.quotaSnapshot);
87
+ if (snapshot) found.set(row.id, snapshot);
88
+ }
89
+ } catch {
90
+ // A read failure yields a partial map; callers already treat a missing
91
+ // entry as "no snapshot".
92
+ }
93
+ return found;
94
+ }
@@ -1,5 +1,3 @@
1
- import { asRecord } from "../../shared/coerce";
2
-
3
1
  /**
4
2
  * Declarative web-search provider adapters.
5
3
  *
@@ -53,7 +51,7 @@ function stringField(record: Record<string, unknown> | undefined, key: string):
53
51
  export function searchParamsFromBody(body: Record<string, unknown>): SearchParams {
54
52
  const query = typeof body.query === "string" ? body.query.trim() : "";
55
53
  if (query.length === 0)
56
- throw new GatewayError("invalid_request", 400, "search query is required");
54
+ throw new GatewayError("invalid_request", 400, "search query is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
57
55
  const rawMax = body.max_results;
58
56
  const maxResults =
59
57
  typeof rawMax === "number" && Number.isFinite(rawMax)
@@ -90,7 +88,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
90
88
  throw new GatewayError(
91
89
  "capability_unsupported",
92
90
  400,
93
- `${spec.provider_id} serves web search, not a chat wire`,
91
+ `${spec.provider_id} serves web search, not a chat wire (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
94
92
  );
95
93
  },
96
94
  async websearch(
@@ -113,7 +111,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
113
111
  throw new GatewayError(
114
112
  "platform_unavailable",
115
113
  502,
116
- `${spec.provider_id} search unreachable: ${error instanceof Error ? error.message : String(error)}`,
114
+ `${spec.provider_id} search unreachable (request {requestId}): ${error instanceof Error ? error.message : String(error)}. The connection ghosted us — come back in a moment 👻`,
117
115
  );
118
116
  }
119
117
  if (!res.ok) {
@@ -121,7 +119,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
121
119
  throw new GatewayError(
122
120
  res.status >= 500 ? "platform_unavailable" : "invalid_request",
123
121
  res.status,
124
- `${spec.provider_id} search error: ${detail.slice(0, 240)}`,
122
+ `${spec.provider_id} search error (request {requestId}): ${detail.slice(0, 240)}. The upstream came back empty-handed — retry 🌙`,
125
123
  );
126
124
  }
127
125
  const raw = await res.text();
@@ -135,7 +133,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
135
133
  throw new GatewayError(
136
134
  "platform_unavailable",
137
135
  502,
138
- `${spec.provider_id} search returned an unparseable body`,
136
+ `${spec.provider_id} search returned an unparseable body (request {requestId}). The upstream came back empty-handed — retry 🌙`,
139
137
  );
140
138
  }
141
139
  const results = spec.normalize(data);
@@ -161,4 +159,4 @@ function jsonFromEventStream(raw: string): unknown {
161
159
 
162
160
  }
163
161
 
164
- export { asRecord, stringField };
162
+ export { stringField };
@@ -9,7 +9,8 @@
9
9
  * normalization, which is what makes a provider's wire local to one spec.
10
10
  */
11
11
  import type { SearchParams, SearchProviderSpec, SearchRequestInit } from "./search-provider";
12
- import { asRecord, stringField } from "./search-provider";
12
+ import { stringField } from "./search-provider";
13
+ import { asRecord } from "../../shared/coerce";
13
14
 
14
15
  function domainFilterParams(filter: readonly string[] | undefined): {
15
16
  includes: string[];