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
@@ -1,11 +1,11 @@
1
1
  // Persisted API-key store: the console API-key domain and gateway authentication both consume this boundary.
2
2
 
3
- import { and, asc, count, desc, eq, inArray, isNotNull, isNull, sql } from "drizzle-orm";
3
+ import { and, asc, count, desc, eq, inArray, isNotNull, isNull, lte, or, sql, type SQL } from "drizzle-orm";
4
4
  import { nextSortIndex, reorderSortIndex, type SortOrderDb } from "../sort-order";
5
5
  import { getDbHandle } from "../boot";
6
6
  import type { CartethyiaDatabase } from "../db-handle";
7
7
  import { lockRows } from "../dialect";
8
- import { apiKeys, shareLinks, sharePopups, telemetryUsageTotals, type ApiKeyMode, type ApiKeyModelAccessMode } from "../schema";
8
+ import { apiKeys, shareLinks, sharePopups, telemetryUsageTotals, type ApiKeyMode, type ApiKeyModelAccessMode, type ApiKeyConcurrencyScope } from "../schema";
9
9
  import type { AccessScope } from "../../security/access-control";
10
10
 
11
11
  /** Sortable columns for the paged share-child listing (api_keys columns only). */
@@ -34,6 +34,18 @@ export interface SharedChildrenPage {
34
34
  readonly consumed: number;
35
35
  }
36
36
 
37
+ /**
38
+ * SQL predicate: the key is not under an active ban. A ban is active while
39
+ * `banned_at` is set AND either `banned_until` is null (permanent) or still in
40
+ * the future — so a timed ban lapses on its own without a sweep.
41
+ */
42
+ function notBannedSql(): SQL {
43
+ return or(
44
+ isNull(apiKeys.bannedAt),
45
+ and(isNotNull(apiKeys.bannedUntil), lte(apiKeys.bannedUntil, new Date())),
46
+ ) as SQL;
47
+ }
48
+
37
49
  /** Persisted API key. Only its one-way hash authenticates; plaintext is never stored. */
38
50
  export interface ApiKeyRecord {
39
51
  readonly id: string;
@@ -66,6 +78,7 @@ export interface ApiKeyRecord {
66
78
  readonly monthlyTokenLimit?: number;
67
79
  readonly lifetimeTokenBudget?: number;
68
80
  readonly maxConcurrentRequests?: number;
81
+ readonly concurrencyScope?: ApiKeyConcurrencyScope;
69
82
  /** Share-template recipient slot cap; undefined = unlimited. */
70
83
  readonly maxChildren?: number;
71
84
  readonly modelPrefix?: string;
@@ -75,6 +88,11 @@ export interface ApiKeyRecord {
75
88
  readonly clientRouterDenylist?: readonly string[];
76
89
  readonly createdAt: Date;
77
90
  readonly revokedAt?: Date;
91
+ /** Ban state; see the schema column for the contract. */
92
+ readonly bannedAt?: Date;
93
+ readonly banReason?: string;
94
+ /** When the ban lifts on its own; null means permanent. */
95
+ readonly bannedUntil?: Date;
78
96
  readonly tokensConsumed: number;
79
97
  }
80
98
 
@@ -128,6 +146,9 @@ export interface ApiKeyPatch
128
146
  | "lifetimeTokenBudget"
129
147
  | "maxConcurrentRequests"
130
148
  | "maxChildren"
149
+ | "bannedAt"
150
+ | "banReason"
151
+ | "bannedUntil"
131
152
  > {
132
153
  readonly keyHash?: string | null;
133
154
  readonly keyEncrypted?: Buffer | null;
@@ -147,6 +168,11 @@ export interface ApiKeyPatch
147
168
  readonly lifetimeTokenBudget?: number | null;
148
169
  readonly maxConcurrentRequests?: number | null;
149
170
  readonly maxChildren?: number | null;
171
+ /** Ban state; a ban also disables the key, unban clears both columns entirely. */
172
+ readonly bannedAt?: Date | null;
173
+ readonly banReason?: string | null;
174
+ /** When the ban lifts on its own; null (explicit) means permanent. */
175
+ readonly bannedUntil?: Date | null;
150
176
  }
151
177
 
152
178
  interface PopupFields {
@@ -247,6 +273,20 @@ export interface ApiKeyStore {
247
273
  reorder(tenantId: string, keyIds: readonly string[]): Promise<void>;
248
274
  update(tenantId: string, keyId: string, patch: ApiKeyPatch): Promise<ApiKeyRecord | undefined>;
249
275
  revoke(tenantId: string, keyId: string, revokedAt: Date): Promise<boolean>;
276
+ /**
277
+ * Bans a key: records the operator's reason and (optionally) an expiry.
278
+ * `bannedUntil` null means permanent; absent defaults the the caller's
279
+ * choice. Returns false when the key is already revoked (a revoke is
280
+ * terminal).
281
+ */
282
+ ban(
283
+ tenantId: string,
284
+ keyId: string,
285
+ reason: string,
286
+ bannedUntil?: Date | null,
287
+ ): Promise<boolean>;
288
+ /** Unbans a key: clears the ban columns entirely — no metadata remains. */
289
+ unban(tenantId: string, keyId: string): Promise<boolean>;
250
290
  remove(tenantId: string, keyId: string): Promise<boolean>;
251
291
  findActiveByHash?(hash: string): Promise<typeof apiKeys.$inferSelect | undefined>;
252
292
  findActiveById?(keyId: string): Promise<typeof apiKeys.$inferSelect | undefined>;
@@ -305,6 +345,9 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
305
345
  ...(row.maxConcurrentRequests === null
306
346
  ? {}
307
347
  : { maxConcurrentRequests: row.maxConcurrentRequests }),
348
+ ...(row.concurrencyScope
349
+ ? { concurrencyScope: row.concurrencyScope as ApiKeyConcurrencyScope }
350
+ : {}),
308
351
  ...(row.maxChildren === null ? {} : { maxChildren: row.maxChildren }),
309
352
  ...(row.modelPrefix === null ? {} : { modelPrefix: row.modelPrefix }),
310
353
  modelAccessMode: row.modelAccessMode,
@@ -314,6 +357,9 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
314
357
  : { clientRouterDenylist: row.clientRouterDenylist as string[] }),
315
358
  createdAt: row.createdAt,
316
359
  ...(row.revokedAt ? { revokedAt: row.revokedAt } : {}),
360
+ ...(row.bannedAt ? { bannedAt: row.bannedAt } : {}),
361
+ ...(row.banReason ? { banReason: row.banReason } : {}),
362
+ ...(row.bannedUntil ? { bannedUntil: row.bannedUntil } : {}),
317
363
  tokensConsumed: row.lifetimeTokensConsumed,
318
364
  };
319
365
  }
@@ -505,6 +551,7 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
505
551
  monthlyTokenLimit: record.monthlyTokenLimit ?? null,
506
552
  lifetimeTokenBudget: record.lifetimeTokenBudget ?? null,
507
553
  maxConcurrentRequests: record.maxConcurrentRequests ?? null,
554
+ concurrencyScope: record.concurrencyScope ?? "family",
508
555
  maxChildren: record.maxChildren ?? null,
509
556
  modelPrefix: record.modelPrefix ?? null,
510
557
  modelAccessMode: record.modelAccessMode ?? "whitelist",
@@ -573,6 +620,9 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
573
620
  ...(patch.maxConcurrentRequests !== undefined
574
621
  ? { maxConcurrentRequests: patch.maxConcurrentRequests }
575
622
  : {}),
623
+ ...(patch.concurrencyScope !== undefined
624
+ ? { concurrencyScope: patch.concurrencyScope }
625
+ : {}),
576
626
  ...(patch.maxChildren !== undefined ? { maxChildren: patch.maxChildren } : {}),
577
627
  ...(patch.modelPrefix !== undefined ? { modelPrefix: patch.modelPrefix } : {}),
578
628
  ...(patch.modelAccessMode !== undefined ? { modelAccessMode: patch.modelAccessMode } : {}),
@@ -586,6 +636,8 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
586
636
  // silently dropped — a grant like `routing:cli_mapping` can be
587
637
  // turned on and never stick.
588
638
  ...(patch.scopes === undefined ? {} : { scopes: patch.scopes }),
639
+ ...(patch.bannedAt !== undefined ? { bannedAt: patch.bannedAt } : {}),
640
+ ...(patch.banReason !== undefined ? { banReason: patch.banReason } : {}),
589
641
  })
590
642
  .where(and(eq(apiKeys.tenantId, tenantId), eq(apiKeys.id, keyId)))
591
643
  .returning();
@@ -622,6 +674,55 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
622
674
  return true;
623
675
  });
624
676
  }
677
+
678
+ async ban(
679
+ tenantId: string,
680
+ keyId: string,
681
+ reason: string,
682
+ bannedUntil?: Date | null,
683
+ ): Promise<boolean> {
684
+ return this.db.transaction(async (tx) => {
685
+ const rows = await tx
686
+ .update(apiKeys)
687
+ .set({
688
+ bannedAt: new Date(),
689
+ banReason: reason,
690
+ // `banned_until` null = permanent. The key stays `enabled` as-is:
691
+ // the auth path refuses a banned key while `banned_at` is set and
692
+ // `banned_until` is null or future, so a timed ban lapses on its own
693
+ // without a sweep flipping `enabled` back.
694
+ ...(bannedUntil === undefined ? {} : { bannedUntil }),
695
+ })
696
+ .where(
697
+ and(
698
+ eq(apiKeys.tenantId, tenantId),
699
+ eq(apiKeys.id, keyId),
700
+ isNull(apiKeys.revokedAt),
701
+ ),
702
+ )
703
+ .returning({ id: apiKeys.id });
704
+ return rows.length > 0;
705
+ });
706
+ }
707
+
708
+ async unban(tenantId: string, keyId: string): Promise<boolean> {
709
+ return this.db.transaction(async (tx) => {
710
+ // Unban clears all three ban columns entirely — no tombstone, no leftover
711
+ // reason, no stale expiry. The key returns to its prior enabled state.
712
+ const rows = await tx
713
+ .update(apiKeys)
714
+ .set({ bannedAt: null, banReason: null, bannedUntil: null, enabled: true })
715
+ .where(
716
+ and(
717
+ eq(apiKeys.tenantId, tenantId),
718
+ eq(apiKeys.id, keyId),
719
+ isNull(apiKeys.revokedAt),
720
+ ),
721
+ )
722
+ .returning({ id: apiKeys.id });
723
+ return rows.length > 0;
724
+ });
725
+ }
625
726
  async remove(tenantId: string, keyId: string): Promise<boolean> {
626
727
  return this.db.transaction(async (tx) => {
627
728
  const rows = await tx
@@ -647,7 +748,7 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
647
748
  const rows = await this.db
648
749
  .select()
649
750
  .from(apiKeys)
650
- .where(and(eq(apiKeys.keyHash, hash), eq(apiKeys.enabled, true), isNull(apiKeys.revokedAt)))
751
+ .where(and(eq(apiKeys.keyHash, hash), eq(apiKeys.enabled, true), isNull(apiKeys.revokedAt), notBannedSql()))
651
752
  .limit(1);
652
753
  return rows[0];
653
754
  }
@@ -658,7 +759,7 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
658
759
  const rows = await this.db
659
760
  .select()
660
761
  .from(apiKeys)
661
- .where(and(eq(apiKeys.id, keyId), eq(apiKeys.enabled, true), isNull(apiKeys.revokedAt)))
762
+ .where(and(eq(apiKeys.id, keyId), eq(apiKeys.enabled, true), isNull(apiKeys.revokedAt), notBannedSql()))
662
763
  .limit(1);
663
764
  return rows[0];
664
765
  }
@@ -709,6 +810,7 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
709
810
  lifetimeTokenBudget: integer("lifetime_token_budget"),
710
811
  lifetimeTokensConsumed: integer("lifetime_tokens_consumed") ?? 0,
711
812
  maxConcurrentRequests: integer("max_concurrent_requests"),
813
+ concurrencyScope: (text("concurrency_scope") ?? "family") as ApiKeyConcurrencyScope,
712
814
  modelAccessMode: (text("model_access_mode") ?? "whitelist") as ApiKeyModelAccessMode,
713
815
  modelList: json("model_list"),
714
816
  clientRouterDenylist: json("client_router_denylist"),
@@ -716,6 +818,9 @@ export class DrizzleApiKeyStore implements ApiKeyStore {
716
818
  createdAt: date("created_at") ?? new Date(0),
717
819
  enabled: flag("enabled"),
718
820
  revokedAt: date("revoked_at"),
821
+ bannedAt: date("banned_at"),
822
+ banReason: text("ban_reason"),
823
+ bannedUntil: date("banned_until"),
719
824
  keyPrefix: text("key_prefix"),
720
825
  keyEncrypted: raw["key_encrypted"] ?? null,
721
826
  notesTitle: text("notes_title"),
@@ -7,7 +7,7 @@ import { createHash, randomBytes, randomInt } from "node:crypto";
7
7
  import { and, desc, eq, isNull, or, sql, type SQL } from "drizzle-orm";
8
8
  import type { CartethyiaDatabase } from "../db-handle";
9
9
  import { lockRows } from "../dialect";
10
- import { apiKeys, shareLinks, sharePopups, type ShareLinkKind, type ApiKeyModelAccessMode } from "../schema";
10
+ import { apiKeys, shareLinks, sharePopups, type ShareLinkKind, type ApiKeyModelAccessMode, type ApiKeyConcurrencyScope } from "../schema";
11
11
 
12
12
  /** Hashes a share bearer token for storage and lookup. */
13
13
  /**
@@ -38,7 +38,7 @@ function addressMatches(address: string): SQL {
38
38
  }
39
39
 
40
40
  /** Newly created share-link metadata. */
41
- export interface ShareLinkRecord {
41
+ interface ShareLinkRecord {
42
42
  readonly id: string;
43
43
  readonly apiKeyId: string;
44
44
  readonly kind: ShareLinkKind;
@@ -61,7 +61,7 @@ export interface ShareLinkSummary {
61
61
  }
62
62
 
63
63
  /** A stable link's encrypted token, or null when it predates token retention. */
64
- export interface ShareLinkToken {
64
+ interface ShareLinkToken {
65
65
  readonly id: string;
66
66
  readonly apiKeyId: string;
67
67
  readonly kind: ShareLinkKind;
@@ -86,6 +86,7 @@ export interface ShareLinkPolicy {
86
86
  readonly monthlyTokenLimit: number | null;
87
87
  readonly lifetimeTokenBudget: number | null;
88
88
  readonly maxConcurrentRequests: number | null;
89
+ readonly concurrencyScope: ApiKeyConcurrencyScope;
89
90
  readonly modelAccessMode: ApiKeyModelAccessMode | null;
90
91
  readonly modelList: readonly string[] | null;
91
92
  readonly modelPrefix: string | null;
@@ -102,13 +103,13 @@ export interface ShareLinkPolicy {
102
103
  }
103
104
 
104
105
  /** Public template fields authorized for a valid enrollment-link lookup. */
105
- export interface ShareApiKeyRow extends ShareLinkPolicy {
106
+ interface ShareApiKeyRow extends ShareLinkPolicy {
106
107
  readonly active: boolean;
107
108
  readonly createdAt: string;
108
109
  }
109
110
 
110
111
  /** A personal key revealed by its handoff link. */
111
- export interface ShareHandoffRow extends ShareLinkPolicy {
112
+ interface ShareHandoffRow extends ShareLinkPolicy {
112
113
  /** Encrypted personal credential; null when the row predates secret storage. */
113
114
  readonly keyEncrypted: Buffer | null;
114
115
  }
@@ -118,7 +119,7 @@ export interface ShareHandoffRow extends ShareLinkPolicy {
118
119
  * public lookup answers with a discriminated result instead of the caller
119
120
  * guessing which endpoint to try.
120
121
  */
121
- export type ShareLinkResolution =
122
+ type ShareLinkResolution =
122
123
  | { readonly kind: "enroll"; readonly key: ShareApiKeyRow }
123
124
  | { readonly kind: "handoff"; readonly key: ShareHandoffRow };
124
125
 
@@ -136,7 +137,7 @@ export type ShareLinkOutcome =
136
137
  | { readonly ok: true; readonly resolution: ShareLinkResolution }
137
138
  | { readonly ok: false; readonly refusal: ShareLinkRefusal };
138
139
 
139
- export interface SharedApiKeyMaterial {
140
+ interface SharedApiKeyMaterial {
140
141
  readonly keyHash: string;
141
142
  readonly keyPrefix: string;
142
143
  readonly clientIp: string;
@@ -145,7 +146,7 @@ export interface SharedApiKeyMaterial {
145
146
  readonly nameHint?: string;
146
147
  }
147
148
 
148
- export type SharedApiKeyIssueResult =
149
+ type SharedApiKeyIssueResult =
149
150
  | {
150
151
  readonly kind: "issued";
151
152
  readonly apiKeyId: string;
@@ -185,6 +186,7 @@ function mapShareRow(key: ApiKeyRow, link: ShareLinkRow, popup?: PopupRow): Shar
185
186
  monthlyTokenLimit: key.monthlyTokenLimit,
186
187
  lifetimeTokenBudget: key.lifetimeTokenBudget,
187
188
  maxConcurrentRequests: key.maxConcurrentRequests,
189
+ concurrencyScope: key.concurrencyScope ?? "family",
188
190
  modelAccessMode: key.modelAccessMode as ApiKeyModelAccessMode | null,
189
191
  modelList: key.modelList as readonly string[] | null,
190
192
  modelPrefix: key.modelPrefix,
@@ -213,6 +215,7 @@ function mapHandoffRow(key: ApiKeyRow, link: ShareLinkRow, popup?: PopupRow): Sh
213
215
  monthlyTokenLimit: key.monthlyTokenLimit,
214
216
  lifetimeTokenBudget: key.lifetimeTokenBudget,
215
217
  maxConcurrentRequests: key.maxConcurrentRequests,
218
+ concurrencyScope: key.concurrencyScope ?? "family",
216
219
  modelAccessMode: key.modelAccessMode as ApiKeyModelAccessMode | null,
217
220
  modelList: key.modelList as readonly string[] | null,
218
221
  modelPrefix: key.modelPrefix,
@@ -557,6 +560,12 @@ export class DrizzleShareLinkStore implements ShareLinkStore {
557
560
  // Nullable policy columns stay NULL; `model_access_mode` takes its
558
561
  // NOT NULL default ('whitelist'). Keep in sync with migration 0029 and
559
562
  // `normalizeShareChildPolicy` in `src/console/backup/store.ts`.
563
+ //
564
+ // Exception: a template with `concurrencyScope: "per_key"` lets each
565
+ // recipient carry its own concurrency cap, so the cap is copied to the
566
+ // child at issue (the child's admission identity is its own id, not the
567
+ // parent's). The `family` default keeps child caps NULL — they inherit
568
+ // the parent's cap through auth.
560
569
  const [child] = await tx
561
570
  .insert(apiKeys)
562
571
  .values({
@@ -571,6 +580,9 @@ export class DrizzleShareLinkStore implements ShareLinkStore {
571
580
  keyPrefix: material.keyPrefix,
572
581
  keyEncrypted: null,
573
582
  lifetimeTokensConsumed: 0,
583
+ ...(parent.concurrencyScope === "per_key"
584
+ ? { concurrencyScope: "per_key" as const, maxConcurrentRequests: parent.maxConcurrentRequests }
585
+ : {}),
574
586
  })
575
587
  .returning({ id: apiKeys.id, tenantId: apiKeys.tenantId, createdAt: apiKeys.createdAt });
576
588
  if (!child) throw new Error("shared API-key insert returned no row");
@@ -55,7 +55,7 @@ function addUsageDelta(
55
55
  delta.outputTokens += row.outputTokens ?? 0;
56
56
  }
57
57
 
58
- function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
58
+ export function aggregateUsage(rows: readonly (typeof telemetryEvents.$inferInsert)[]) {
59
59
  const deltas = new Map<string, UsageTotalDelta>();
60
60
  for (const row of rows) {
61
61
  addUsageDelta(deltas, row, "account", row.accountId);
@@ -7,15 +7,20 @@ import { eq } from "drizzle-orm";
7
7
  import type { CartethyiaDatabase } from "./db-handle";
8
8
  import { tenantSystemPrompts } from "./schema";
9
9
  import type {
10
- ProviderPromptEntry,
11
- PromptSetting,
10
+ ScopePromptRef,
11
+ SystemPromptPreset,
12
12
  TenantSystemPrompts,
13
13
  } from "../console/system-prompt/contracts";
14
14
  import {
15
15
  EMPTY_TENANT_SYSTEM_PROMPTS,
16
+ SYSTEM_PROMPT_ALIAS_LIMIT,
17
+ SYSTEM_PROMPT_COMBO_LIMIT,
16
18
  SYSTEM_PROMPT_MODEL_LIMIT,
19
+ SYSTEM_PROMPT_PRESET_LIMIT,
17
20
  SYSTEM_PROMPT_PROVIDER_LIMIT,
18
21
  isPromptSetting,
22
+ isScopePromptRef,
23
+ isSystemPromptPreset,
19
24
  parseSystemPromptModelKey,
20
25
  } from "../console/system-prompt/contracts";
21
26
  import { bumpSettingsRevision } from "./settings-revision";
@@ -23,6 +28,24 @@ import { bumpSettingsRevision } from "./settings-revision";
23
28
  /** The stored row, normalized. Absent row reads as the empty configuration. */
24
29
  export type StoredTenantSystemPrompts = TenantSystemPrompts;
25
30
 
31
+ /**
32
+ * Normalizes one scope reference value. Accepts the new `{ kind, ... }` shape
33
+ * and the legacy per-provider entry shape (`{ followGlobal, setting? }`) so a
34
+ * hand-edited or pre-migration row stays valid. Invalid values become
35
+ * `undefined` (the caller drops the key).
36
+ */
37
+ function normalizeScopeRef(value: unknown, legacyProviderEntry?: boolean): ScopePromptRef | undefined {
38
+ if (isScopePromptRef(value)) return value;
39
+ if (legacyProviderEntry && typeof value === "object" && value !== null) {
40
+ const candidate = value as { followGlobal?: unknown; setting?: unknown };
41
+ if (candidate.followGlobal === true) return { kind: "global" };
42
+ if (candidate.followGlobal === false && isPromptSetting(candidate.setting)) {
43
+ return { kind: "custom", setting: candidate.setting };
44
+ }
45
+ }
46
+ return undefined;
47
+ }
48
+
26
49
  /**
27
50
  * Reads the tenant's stored configuration, defensively normalized: a
28
51
  * hand-edited or legacy row must not poison the dispatch path, so malformed
@@ -35,8 +58,11 @@ export async function readTenantSystemPrompts(
35
58
  const rows = await db
36
59
  .select({
37
60
  global: tenantSystemPrompts.global,
61
+ presets: tenantSystemPrompts.presets,
38
62
  providers: tenantSystemPrompts.providers,
39
63
  models: tenantSystemPrompts.models,
64
+ combos: tenantSystemPrompts.combos,
65
+ aliases: tenantSystemPrompts.aliases,
40
66
  })
41
67
  .from(tenantSystemPrompts)
42
68
  .where(eq(tenantSystemPrompts.tenantId, tenantId))
@@ -44,30 +70,43 @@ export async function readTenantSystemPrompts(
44
70
  const row = rows[0];
45
71
  if (row === undefined) return EMPTY_TENANT_SYSTEM_PROMPTS;
46
72
 
47
- const providers: Record<string, ProviderPromptEntry> = {};
48
- for (const [providerId, entry] of Object.entries(row.providers ?? {})) {
49
- if (typeof entry !== "object" || entry === null) continue;
50
- const candidate = entry as Partial<ProviderPromptEntry>;
51
- if (typeof candidate.followGlobal !== "boolean") continue;
52
- if (candidate.followGlobal === false) {
53
- if (!isPromptSetting(candidate.setting)) continue;
54
- providers[providerId] = { followGlobal: false, setting: candidate.setting };
55
- } else {
56
- providers[providerId] = { followGlobal: true };
57
- }
73
+ const presets: Record<string, SystemPromptPreset> = {};
74
+ for (const [id, preset] of Object.entries(row.presets ?? {})) {
75
+ if (isSystemPromptPreset(preset) && preset.id === id) presets[id] = preset;
58
76
  }
59
77
 
60
- const models: Record<string, PromptSetting> = {};
61
- for (const [key, setting] of Object.entries(row.models ?? {})) {
78
+ const providers: Record<string, ScopePromptRef> = {};
79
+ for (const [providerId, value] of Object.entries(row.providers ?? {})) {
80
+ const ref = normalizeScopeRef(value, true);
81
+ if (ref !== undefined) providers[providerId] = ref;
82
+ }
83
+
84
+ const models: Record<string, ScopePromptRef> = {};
85
+ for (const [key, value] of Object.entries(row.models ?? {})) {
62
86
  if (parseSystemPromptModelKey(key) === null) continue;
63
- if (!isPromptSetting(setting)) continue;
64
- models[key] = setting;
87
+ const ref = normalizeScopeRef(value);
88
+ if (ref !== undefined) models[key] = ref;
89
+ }
90
+
91
+ const combos: Record<string, ScopePromptRef> = {};
92
+ for (const [name, value] of Object.entries(row.combos ?? {})) {
93
+ const ref = normalizeScopeRef(value);
94
+ if (ref !== undefined) combos[name] = ref;
95
+ }
96
+
97
+ const aliases: Record<string, ScopePromptRef> = {};
98
+ for (const [name, value] of Object.entries(row.aliases ?? {})) {
99
+ const ref = normalizeScopeRef(value);
100
+ if (ref !== undefined) aliases[name] = ref;
65
101
  }
66
102
 
67
103
  return {
68
- global: row.global !== null && isPromptSetting(row.global) ? row.global : null,
104
+ global: row.global !== null && isScopePromptRef(row.global) ? row.global : null,
105
+ presets,
69
106
  providers,
70
107
  models,
108
+ combos,
109
+ aliases,
71
110
  };
72
111
  }
73
112
 
@@ -79,12 +118,18 @@ export async function writeTenantSystemPrompts(
79
118
  ): Promise<void> {
80
119
  // Keep the row bounded even though the API already enforces the caps: a
81
120
  // direct store caller must not be able to plant an unbounded map.
121
+ const presets = Object.entries(next.presets).slice(0, SYSTEM_PROMPT_PRESET_LIMIT);
82
122
  const providers = Object.entries(next.providers).slice(0, SYSTEM_PROMPT_PROVIDER_LIMIT);
83
123
  const models = Object.entries(next.models).slice(0, SYSTEM_PROMPT_MODEL_LIMIT);
124
+ const combos = Object.entries(next.combos).slice(0, SYSTEM_PROMPT_COMBO_LIMIT);
125
+ const aliases = Object.entries(next.aliases).slice(0, SYSTEM_PROMPT_ALIAS_LIMIT);
84
126
  const values = {
85
127
  global: next.global,
128
+ presets: Object.fromEntries(presets),
86
129
  providers: Object.fromEntries(providers),
87
130
  models: Object.fromEntries(models),
131
+ combos: Object.fromEntries(combos),
132
+ aliases: Object.fromEntries(aliases),
88
133
  updatedAt: new Date(),
89
134
  };
90
135
  await db
@@ -29,7 +29,7 @@ export class DrizzlePreferencesReader implements PreferencesReader {
29
29
  }
30
30
  }
31
31
 
32
- export interface CachedPreferencesReaderOptions {
32
+ interface CachedPreferencesReaderOptions {
33
33
  readonly ttlMs?: number;
34
34
  readonly maxEntries?: number;
35
35
  readonly revision?: () => number;
@@ -7,13 +7,13 @@ import { dedupeRequest } from "../network/deduplication";
7
7
  * - Revision-aware key invalidation
8
8
  */
9
9
 
10
- export interface TtlCacheOptions {
10
+ interface TtlCacheOptions {
11
11
  readonly ttlMs?: number;
12
12
  readonly maxEntries?: number;
13
13
  readonly now?: () => number;
14
14
  }
15
15
 
16
- export interface TtlCacheEntry<T> {
16
+ interface TtlCacheEntry<T> {
17
17
  readonly value: T;
18
18
  readonly at: number;
19
19
  }
@@ -134,7 +134,7 @@ export class TtlCache<T> {
134
134
  }
135
135
 
136
136
  /** One TTL-sized cache inside a {@link TtlCacheFamily}. */
137
- export interface TtlCacheFamilyOptions {
137
+ interface TtlCacheFamilyOptions {
138
138
  /** TTL applied when a caller does not pass its own. */
139
139
  readonly defaultTtlMs: number;
140
140
  /** Entry cap per TTL-sized cache. */
@@ -65,14 +65,19 @@ async function writeLifetimeDeltas(
65
65
  // text and the join against a uuid key fails. SQLite stores the key as text
66
66
  // and rejects `UPDATE ... FROM (VALUES ...)` outright, so the same rows are
67
67
  // a SELECT with named columns instead.
68
- const source = sqliteDialect()
68
+ const sqlite = sqliteDialect();
69
+ const source = sqlite
69
70
  ? `(${rows.map((row) => `SELECT '${row.apiKeyId}' AS id, ${row.delta} AS delta`).join(" UNION ALL ")})`
70
71
  : `(VALUES ${rows.map((row) => `('${row.apiKeyId}'::uuid, ${row.delta}::bigint)`).join(", ")}) AS v(id, delta)`;
72
+ // PostgreSQL already carries its alias in `source`; only the bare SQLite
73
+ // subquery needs the trailing alias appended. Emitting both (`AS v(id, delta) v`)
74
+ // is a syntax error on Postgres, so the flush silently never ran.
75
+ const from = sqlite ? `${source} v` : source;
71
76
  await db.execute(sql.raw(`
72
77
  UPDATE "api_keys"
73
78
  SET "lifetime_tokens_consumed" =
74
79
  COALESCE("api_keys"."lifetime_tokens_consumed", 0) + v.delta
75
- FROM ${source} v
80
+ FROM ${from}
76
81
  WHERE "api_keys"."id" = v.id
77
82
  `));
78
83
  }
@@ -614,7 +614,7 @@ export function hasEncryptedReasoning(parts: readonly ContentPart[]): boolean {
614
614
  * concurrent preflights would be an aliasing hazard; the memoized summary and
615
615
  * its extension set are treated as read-only.)
616
616
  */
617
- export interface PartSummary {
617
+ interface PartSummary {
618
618
  readonly hasImage: boolean;
619
619
  readonly hasDocument: boolean;
620
620
  readonly hasAudio: boolean;
@@ -89,7 +89,7 @@ export function hash32(input: string): number {
89
89
  return hash;
90
90
  }
91
91
 
92
- export const CLAUDE_TOOL_PREFIX = "_";
92
+ const CLAUDE_TOOL_PREFIX = "_";
93
93
 
94
94
  export type ClaudeWireObject = Record<string, unknown>;
95
95
 
@@ -408,13 +408,9 @@ export function isClaudeBillingHeaderText(text: string): boolean {
408
408
  // Well-formed UTF-16 coercion
409
409
 
410
410
  export function toWellFormedString(value: string): string {
411
- const maybe = value as unknown as { toWellFormed?: () => string };
412
- if (typeof maybe.toWellFormed === "function") return maybe.toWellFormed();
413
- // Fallback for runtimes without String.prototype.toWellFormed (replace lone surrogates)
414
- return value.replace(
415
- /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,
416
- "\uFFFD",
417
- );
411
+ // String.prototype.toWellFormed is ES2024; guaranteed by Bun ≥1.4, the only
412
+ // supported runtime.
413
+ return value.toWellFormed();
418
414
  }
419
415
 
420
416
  export function toWellFormedDeep(value: unknown): unknown {
@@ -480,7 +476,7 @@ export function assertBoundedJsonDepth(value: unknown): void {
480
476
 
481
477
  function assertBoundedDepthWalk(value: unknown, depth: number): void {
482
478
  if (depth > MAX_JSON_DEPTH)
483
- throw new GatewayError("invalid_request", 400, `The request body is nested deeper than this endpoint accepts (${MAX_JSON_DEPTH} levels). Flatten the payload.`);
479
+ throw new GatewayError("invalid_request", 400, `The request body is nested deeper than this endpoint accepts (${MAX_JSON_DEPTH} levels) (request {requestId}). Flatten the payload. Check the shape of what you sent — then resend 🪞`);
484
480
  if (Array.isArray(value)) {
485
481
  for (const item of value) assertBoundedDepthWalk(item, depth + 1);
486
482
  return;
@@ -873,7 +869,7 @@ export const BARE_ROOT_ENDPOINTS: Partial<Record<WireFamily, string>> = {
873
869
  };
874
870
 
875
871
  /** An image part's provider-neutral source, resolved from any supported origin shape. */
876
- export interface ResolvedImageSource {
872
+ interface ResolvedImageSource {
877
873
  /** URL or `data:` URI. Mutually usable with `fileId`. */
878
874
  url?: string;
879
875
  /** Files API reference (Chat/Responses `file_id`). */
@@ -883,7 +879,7 @@ export interface ResolvedImageSource {
883
879
  }
884
880
 
885
881
  /** An RFC 2397 base64 `data:` URL split into the media type and bytes it carries. */
886
- export interface DataUrlPayload {
882
+ interface DataUrlPayload {
887
883
  /** The declared media type (the `image/png` of `data:image/png;base64,…`). */
888
884
  readonly mediaType: string;
889
885
  /** The base64 payload, with the `data:` prefix and media type removed. */
@@ -1038,7 +1034,7 @@ export function filterProviderCustomHeaders(
1038
1034
  for (const [rawName, rawValue] of Object.entries(input)) {
1039
1035
  const name = rawName.toLowerCase();
1040
1036
  if (!HEADER_TOKEN.test(name)) {
1041
- throw new GatewayError("invalid_request", 400, `invalid custom header name: ${rawName}`, {
1037
+ throw new GatewayError("invalid_request", 400, `invalid custom header name: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`, {
1042
1038
  header: rawName,
1043
1039
  });
1044
1040
  }
@@ -1047,7 +1043,7 @@ export function filterProviderCustomHeaders(
1047
1043
  extraReserved.has(name) ||
1048
1044
  name.startsWith("x-forwarded-")
1049
1045
  ) {
1050
- throw new GatewayError("invalid_request", 400, `custom header is protected: ${rawName}`, {
1046
+ throw new GatewayError("invalid_request", 400, `custom header is protected: ${rawName} (request {requestId}). Not allowed by policy — no hard feelings 🫂`, {
1051
1047
  header: rawName,
1052
1048
  });
1053
1049
  }
@@ -1055,7 +1051,7 @@ export function filterProviderCustomHeaders(
1055
1051
  throw new GatewayError(
1056
1052
  "invalid_request",
1057
1053
  400,
1058
- `custom header value must be a string: ${rawName}`,
1054
+ `custom header value must be a string: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
1059
1055
  { header: rawName },
1060
1056
  );
1061
1057
  }
@@ -1063,7 +1059,7 @@ export function filterProviderCustomHeaders(
1063
1059
  throw new GatewayError(
1064
1060
  "invalid_request",
1065
1061
  400,
1066
- `custom header value exceeds 4KiB: ${rawName}`,
1062
+ `custom header value exceeds 4KiB: ${rawName} (request {requestId}). We love long reads, but not that long 📚`,
1067
1063
  { header: rawName },
1068
1064
  );
1069
1065
  }
@@ -1071,7 +1067,7 @@ export function filterProviderCustomHeaders(
1071
1067
  throw new GatewayError(
1072
1068
  "invalid_request",
1073
1069
  400,
1074
- `custom header value contains control characters: ${rawName}`,
1070
+ `custom header value contains control characters: ${rawName} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
1075
1071
  { header: rawName },
1076
1072
  );
1077
1073
  }