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
@@ -17,6 +17,10 @@ import { type TelemetryEventRecord } from "../../observability/event-log";
17
17
  import {
18
18
  queryTelemetryEvents as querySqlEvents,
19
19
  sumTelemetryTokens,
20
+ familyKpiTotals,
21
+ familyWindowRequests,
22
+ familyHourlyTelemetry,
23
+ familyIpBreakdown,
20
24
  } from "../../observability/event-query";
21
25
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
22
26
  import { apiKeys } from "../../persistence/schema";
@@ -26,7 +30,7 @@ import { cachedRead } from "../shared/read-through-cache";
26
30
  import { utcDayStart, utcMonthStart } from "../shared/day";
27
31
 
28
32
  /** Totals for the whole share family, all-time plus the windows the UI draws. */
29
- export interface ShareFamilyTotals {
33
+ interface ShareFamilyTotals {
30
34
  readonly requests: number;
31
35
  readonly errors: number;
32
36
  readonly inputTokens: number;
@@ -41,7 +45,7 @@ export interface ShareFamilyTotals {
41
45
  }
42
46
 
43
47
  /** One hour bucket of the last 24 hours, oldest first. */
44
- export interface ShareHourlyBucket {
48
+ interface ShareHourlyBucket {
45
49
  /** ISO instant at the top of the hour. */
46
50
  readonly hour: string;
47
51
  readonly requests: number;
@@ -49,7 +53,7 @@ export interface ShareHourlyBucket {
49
53
  readonly tokens: number;
50
54
  }
51
55
 
52
- export interface ShareTopModel {
56
+ interface ShareTopModel {
53
57
  readonly modelId: string;
54
58
  readonly requests: number;
55
59
  readonly tokens: number;
@@ -59,7 +63,7 @@ export interface ShareTopModel {
59
63
  readonly avgTtfbMs: number | null;
60
64
  }
61
65
 
62
- export interface ShareTopClientIp {
66
+ interface ShareTopClientIp {
63
67
  /** Always masked: this payload is served to the recipient, not the tenant. */
64
68
  readonly ip: string;
65
69
  readonly requests: number;
@@ -72,6 +76,12 @@ export interface ShareTopClientIp {
72
76
  * the tool rather than the machine.
73
77
  */
74
78
  readonly clientType: string | null;
79
+ /**
80
+ * True when this address made a request within {@link ACTIVE_IP_WINDOW_MS}.
81
+ * A recipient watching the link can tell which addresses are still using it
82
+ * versus ones that stopped days ago.
83
+ */
84
+ readonly active: boolean;
75
85
  }
76
86
 
77
87
  export interface ShareFamilyStats {
@@ -111,6 +121,24 @@ export interface ShareStatsPort {
111
121
 
112
122
  const HOUR_MS = 3_600_000;
113
123
 
124
+ /**
125
+ * How old an address's last request may be and still count as active on the
126
+ * share page. 24 hours matches the page's own "last 24 hours" framing: an IP
127
+ * that made a request since yesterday is "active", one that stopped before
128
+ * that window is "inactive".
129
+ */
130
+ const ACTIVE_IP_WINDOW_MS = 24 * HOUR_MS;
131
+
132
+ /**
133
+ * Recency window for the client-IP ranking's *work set*: the GROUP BY only
134
+ * scans this slice of telemetry instead of the whole retained window every
135
+ * tick. Within the window, addresses are ranked by request volume (highest
136
+ * first), and older addresses fall out of the ranking entirely. 7 days keeps
137
+ * the ranking useful while staying orders of magnitude smaller than the
138
+ * retained window.
139
+ */
140
+ const IP_WINDOW_MS = 7 * 24 * HOUR_MS;
141
+
114
142
  /**
115
143
  * Names the client behind a request from its User-Agent.
116
144
  *
@@ -154,7 +182,7 @@ interface FamilyRollup {
154
182
  totals: ShareFamilyTotals;
155
183
  byHour: Map<string, { requests: number; tokens: number }>;
156
184
  models: Map<string, { modelId: string; requests: number; tokens: number; rateSum: number; rateCount: number; ttfbSum: number; ttfbCount: number }>;
157
- ips: Map<string, { ip: string; requests: number; tokens: number; lastSeenAt: string | null; userAgent: string | null }>;
185
+ ips: Map<string, { ip: string; requests: number; tokens: number; lastSeenAt: string | null; userAgent: string | null; active: boolean }>;
158
186
  }
159
187
 
160
188
  /**
@@ -163,7 +191,7 @@ interface FamilyRollup {
163
191
  */
164
192
  function rollupFamily(
165
193
  events: readonly TelemetryEventRecord[],
166
- bounds: { dayStart: number; monthStart: number; lastHourStart: number; windowStart: number; allowed: ReadonlySet<string> | null },
194
+ bounds: { dayStart: number; monthStart: number; lastHourStart: number; windowStart: number; activeIpCutoff: number; allowed: ReadonlySet<string> | null },
167
195
  ): FamilyRollup {
168
196
  const totals = { requests: 0, errors: 0, inputTokens: 0, outputTokens: 0, totalTokens: 0, lastHourRequests: 0, todayTokens: 0, monthTokens: 0 };
169
197
  const byHour = new Map<string, { requests: number; tokens: number }>();
@@ -196,13 +224,14 @@ function rollupFamily(
196
224
  models.set(event.requestedModel, model);
197
225
  }
198
226
  if (event.clientIp !== null) {
199
- const ip = ips.get(event.clientIp) ?? { ip: event.clientIp, requests: 0, tokens: 0, lastSeenAt: null, userAgent: null };
227
+ const ip = ips.get(event.clientIp) ?? { ip: event.clientIp, requests: 0, tokens: 0, lastSeenAt: null, userAgent: null, active: false };
200
228
  ip.requests += 1;
201
229
  ip.tokens += tokens;
202
230
  // Events arrive newest first, so the first one seen is the most recent.
203
231
  if (ip.lastSeenAt === null) {
204
232
  ip.lastSeenAt = event.createdAt;
205
233
  ip.userAgent = event.userAgent;
234
+ ip.active = at >= bounds.activeIpCutoff;
206
235
  }
207
236
  ips.set(event.clientIp, ip);
208
237
  }
@@ -244,17 +273,18 @@ const HOURS_WINDOW = 24;
244
273
  /**
245
274
  * Cache lifetime for one family's rollup.
246
275
  *
247
- * Matched to the share page's SSE tick, which was the point of the old 2 s
248
- * value — but at 2 s the cache expired on every tick, so every single push was
249
- * a full parse of the retained telemetry window. A share page's numbers move
250
- * when a request does, not every 2 s; 10 s still reads as live, and cuts the
251
- * parse rate by five.
276
+ * Longer than the SSE tick (30s) so most ticks are served from cache. The
277
+ * cold-compute path reads the family's telemetry windows (24h chart, 7d IP
278
+ * ranking), which on a busy family is the single most expensive thing the
279
+ * share page does; a 5-minute TTL caps that to once per family per 5
280
+ * minutes while the page still reads as live — KPI numbers move with
281
+ * traffic, not on a per-minute cadence.
252
282
  *
253
283
  * Declared here rather than imported from the router so this port does not
254
- * depend on it. `SHARE_STATS_STREAM_INTERVAL_MS` must be raised with it, or
255
- * the two drift apart and the cache expires between ticks for no reason.
284
+ * depend on it. `SHARE_STATS_STREAM_INTERVAL_MS` must stay below it, or the
285
+ * tick and cache drift apart and every tick bypasses the cache.
256
286
  */
257
- export const SHARE_STATS_CACHE_TTL_SECONDS = 10;
287
+ export const SHARE_STATS_CACHE_TTL_SECONDS = 300;
258
288
 
259
289
  /**
260
290
  * Cache key for one family's rollup. The family is named by its template key
@@ -318,17 +348,23 @@ async function computeFamilyStatsBody(
318
348
  const hourStart = utcHourStart(now);
319
349
  const lastHourStart = new Date(now.getTime() - HOUR_MS);
320
350
  const windowStart = new Date(hourStart.getTime() - (HOURS_WINDOW - 1) * HOUR_MS);
351
+ // The IP breakdown is bounded to this recency window: a family's "who is
352
+ // using the link" answer only needs addresses that were active recently,
353
+ // and an unbounded GROUP BY over every retained event is the one remaining
354
+ // full-window scan on the share page.
355
+ const ipWindowStart = new Date(now.getTime() - IP_WINDOW_MS);
321
356
 
322
- // All-time and today's/month's spend come from SQL aggregates, not from the
323
- // bounded page below. The page is capped so a very large family cannot pull
324
- // the whole table, which means it cannot carry the all-time total. Lifetime,
325
- // daily, and monthly are therefore separate bounded sums over the retained
326
- // SQL window (each is a single `SUM` aggregate, not a row materialization).
327
- // The page's events feed only the hourly chart and the two ranked tables.
328
- const [totalTokens, todayTokens, monthTokens, template] = await Promise.all([
329
- sumTelemetryTokens(db, tenantId, { familyKeyId: templateKeyId, since: new Date(0) }),
357
+ // All-time family KPI comes from the durable rollup
358
+ // (`telemetry_usage_totals`, charged at write time), not from re-aggregating
359
+ // the entire retained telemetry window on every tick: the share page's tick
360
+ // would otherwise read every event a busy family ever produced just to draw
361
+ // four numbers. Today's/month's spend and the last-hour count are bounded
362
+ // window reads over the time index, which stay small.
363
+ const [kpi, todayTokens, monthTokens, lastHourRequests, template] = await Promise.all([
364
+ familyKpiTotals(db, tenantId, templateKeyId),
330
365
  sumTelemetryTokens(db, tenantId, { familyKeyId: templateKeyId, since: dayStart }),
331
366
  sumTelemetryTokens(db, tenantId, { familyKeyId: templateKeyId, since: monthStart }),
367
+ familyWindowRequests(db, tenantId, templateKeyId, lastHourStart),
332
368
  db
333
369
  .select({ lifetime: apiKeys.lifetimeTokensConsumed })
334
370
  .from(apiKeys)
@@ -342,8 +378,9 @@ async function computeFamilyStatsBody(
342
378
  // across the overview, the share dialog, and this page.
343
379
  const templateLifetime = Number(template[0]?.lifetime ?? 0);
344
380
 
345
- // The hourly chart covers the last 24 hours, so the page only needs events
346
- // in that window — not the whole retained history. Bounded and small.
381
+ // The ranked tables need real events, bounded to the 24-hour window. The
382
+ // totals and hourly chart are computed in SQL, so the page cap here only
383
+ // shapes the model/IP rankings — never the KPI numbers.
347
384
  const page = await querySqlEvents(db, {
348
385
  tenantId,
349
386
  familyApiKeyId: templateKeyId,
@@ -356,6 +393,7 @@ async function computeFamilyStatsBody(
356
393
  monthStart: monthStart.getTime(),
357
394
  lastHourStart: lastHourStart.getTime(),
358
395
  windowStart: windowStart.getTime(),
396
+ activeIpCutoff: now.getTime() - ACTIVE_IP_WINDOW_MS,
359
397
  allowed: allowedSet,
360
398
  });
361
399
  rollup.totals = {
@@ -363,14 +401,28 @@ async function computeFamilyStatsBody(
363
401
  // The template counter is the seed on the running total, not a distinct
364
402
  // bucket: daily/monthly windows are telemetry-only (the counter has no
365
403
  // time axis), so those keep their own sums.
366
- totalTokens: templateLifetime + totalTokens,
404
+ totalTokens: templateLifetime + kpi.tokens,
367
405
  todayTokens,
368
406
  monthTokens,
407
+ // Lifetime counts come from the durable rollup, not the page: a family
408
+ // busier than the page cap must not freeze at 500 requests/errors.
409
+ requests: kpi.requests,
410
+ errors: kpi.errors,
411
+ lastHourRequests,
369
412
  };
370
413
 
371
- // Fill the 24 buckets in JS: only hours that saw traffic are present, and
372
- // a chart drawn from sparse rows would silently compress the axis.
373
- const byHour = rollup.byHour;
414
+ // The hourly chart is a SQL aggregate over the whole family window, so a
415
+ // busy family's bars stay true. Buckets are filled in JS because only hours
416
+ // that saw traffic are present; a chart drawn from sparse rows would
417
+ // silently compress the axis.
418
+ const bucketSeconds = HOUR_MS / 1000;
419
+ const sqlHourly = await familyHourlyTelemetry(db, tenantId, templateKeyId, windowStart, bucketSeconds);
420
+ const byHour = new Map(
421
+ sqlHourly.map((bucket) => [
422
+ new Date(bucket.at).toISOString(),
423
+ { requests: bucket.requests, tokens: bucket.inputTokens + bucket.outputTokens },
424
+ ]),
425
+ );
374
426
  const hourly: ShareHourlyBucket[] = [];
375
427
  for (let index = 0; index < HOURS_WINDOW; index += 1) {
376
428
  const hour = new Date(windowStart.getTime() + index * HOUR_MS).toISOString();
@@ -378,6 +430,33 @@ async function computeFamilyStatsBody(
378
430
  hourly.push({ hour, requests: bucket?.requests ?? 0, tokens: bucket?.tokens ?? 0 });
379
431
  }
380
432
 
433
+ // Client IPs come from a SQL aggregate over a bounded recent window, ranked
434
+ // by request volume — so a family's "who is using the link most" answer
435
+ // stays cheap instead of scanning the whole retained telemetry on every
436
+ // tick. `clientType` is best-effort from the page's events (the most recent
437
+ // 24 hours), which carry the User-Agent.
438
+ const ipRows = await familyIpBreakdown(db, tenantId, templateKeyId, TOP_IPS_LIMIT, ipWindowStart);
439
+ const clientTypeByIp = new Map<string, string | null>();
440
+ for (const event of events) {
441
+ if (event.clientIp !== null && !clientTypeByIp.has(event.clientIp)) {
442
+ clientTypeByIp.set(event.clientIp, clientTypeFromUserAgent(event.userAgent));
443
+ }
444
+ }
445
+ const activeIpCutoff = now.getTime() - ACTIVE_IP_WINDOW_MS;
446
+ const clientIps = ipRows.map((row) => {
447
+ const lastSeenAt =
448
+ row.lastSeenAt instanceof Date ? row.lastSeenAt : new Date(String(row.lastSeenAt ?? ""));
449
+ const lastSeenMs = Number.isFinite(lastSeenAt.getTime()) ? lastSeenAt.getTime() : 0;
450
+ return {
451
+ ip: maskClientIp(row.ip),
452
+ requests: row.requests,
453
+ tokens: row.tokens,
454
+ lastSeenAt: lastSeenMs > 0 ? lastSeenAt.toISOString() : null,
455
+ clientType: clientTypeByIp.get(row.ip) ?? null,
456
+ active: lastSeenMs >= activeIpCutoff,
457
+ };
458
+ });
459
+
381
460
  return {
382
461
  totals: rollup.totals,
383
462
  hourly,
@@ -391,15 +470,6 @@ async function computeFamilyStatsBody(
391
470
  avgTokensPerSec: row.rateCount > 0 ? row.rateSum / row.rateCount : null,
392
471
  avgTtfbMs: row.ttfbCount > 0 ? Math.round(row.ttfbSum / row.ttfbCount) : null,
393
472
  })),
394
- clientIps: [...rollup.ips.values()]
395
- .sort((a, b) => b.requests - a.requests)
396
- .slice(0, TOP_IPS_LIMIT)
397
- .map((row) => ({
398
- ip: maskClientIp(row.ip),
399
- requests: row.requests,
400
- tokens: row.tokens,
401
- lastSeenAt: row.lastSeenAt,
402
- clientType: clientTypeFromUserAgent(row.userAgent),
403
- })),
473
+ clientIps,
404
474
  };
405
475
  }
@@ -13,7 +13,7 @@ import { apiKeys, telemetryUsageTotals } from "../../persistence/schema";
13
13
  import { shouldMaskClientIp } from "../shared/ip-privacy";
14
14
  import { utcDayStart } from "../shared/day";
15
15
 
16
- export interface SharedKeyTokenUsage {
16
+ interface SharedKeyTokenUsage {
17
17
  readonly requests: number;
18
18
  readonly errors: number;
19
19
  readonly inputTokens: number;
@@ -7,7 +7,7 @@ import { formatPublicErrorMessage } from "../../protocol/gateway-error";
7
7
  // (or the storage-layer BackupError, same code/status/message shape) and
8
8
  // shapes it through errorResponse; guards narrow to TenantAccessDecision.
9
9
 
10
- export interface ErrorResponseOptions {
10
+ interface ErrorResponseOptions {
11
11
  readonly detailsPolicy?: "omit" | "include-if-present" | "always-include";
12
12
  }
13
13
 
@@ -105,7 +105,7 @@ export function requireAnyScope(
105
105
  return access;
106
106
  }
107
107
 
108
- export type TenantAccessDecision = AccessDecision & { readonly tenantId: string };
108
+ type TenantAccessDecision = AccessDecision & { readonly tenantId: string };
109
109
 
110
110
  export function requireTenantScope(
111
111
  access: AccessDecision | undefined,
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Builds an object from `source`, omitting absent values.
3
+ *
4
+ * The console domains hand-roll the same `...(x === undefined ? {} : { x })`
5
+ * field-omission spread many times (key create/update patches, response
6
+ * sanitizers). This is the single helper for that pattern; `omitNull` extends
7
+ * it to also drop `null` values (the `== null` variant), matching call sites
8
+ * that treat null as "clear this field" rather than "set it to null".
9
+ */
10
+ export function pickDefined<T extends object>(source: T, omitNull = false): { [K in keyof T]?: Exclude<T[K], undefined> } {
11
+ const out: Record<string, unknown> = {};
12
+ for (const [key, value] of Object.entries(source)) {
13
+ if (value === undefined) continue;
14
+ if (omitNull && value === null) continue;
15
+ out[key] = value;
16
+ }
17
+ return out as { [K in keyof T]?: Exclude<T[K], undefined> };
18
+ }
@@ -31,7 +31,7 @@
31
31
  import { createHash } from "node:crypto";
32
32
 
33
33
  /** The slice of Redis this cache needs, so it can be tested without a server. */
34
- export interface ReadThroughCacheClient {
34
+ interface ReadThroughCacheClient {
35
35
  get(key: string): Promise<string | null>;
36
36
  set(key: string, value: string, mode: "EX", ttlSeconds: number): Promise<unknown>;
37
37
  }
@@ -41,7 +41,7 @@ export interface ReadThroughCacheClient {
41
41
  * more often pass a shorter TTL; this value suits the dashboard's polling
42
42
  * cadence and the append-only telemetry log.
43
43
  */
44
- export const READ_THROUGH_DEFAULT_TTL_SECONDS = 10;
44
+ const READ_THROUGH_DEFAULT_TTL_SECONDS = 10;
45
45
 
46
46
  /** The cached value plus the strong validator for the bytes it serializes to. */
47
47
  export interface CachedResult<T> {
@@ -59,10 +59,38 @@ function etagFor(bytes: string): string {
59
59
  }
60
60
 
61
61
  /** The sibling key holding the validator, so a cached body is never re-hashed. */
62
- export function etagKey(key: string): string {
62
+ function etagKey(key: string): string {
63
63
  return `${key}:etag`;
64
64
  }
65
65
 
66
+ /**
67
+ * In-memory fallback store used when no Redis client is configured (the
68
+ * embedded lite backend). Keyed by the same cache key, storing the value and
69
+ * its validator with a TTL. Bounded so a busy gateway cannot grow it without
70
+ * limit.
71
+ */
72
+ const LOCAL_CACHE_MAX_ENTRIES = 2000;
73
+ const localCache = new Map<string, { at: number; value: string; etag: string }>();
74
+
75
+ function localGet(key: string): { value: string; etag: string } | undefined {
76
+ const entry = localCache.get(key);
77
+ if (entry === undefined) return undefined;
78
+ if (Date.now() - entry.at >= READ_THROUGH_DEFAULT_TTL_SECONDS * 1000) {
79
+ localCache.delete(key);
80
+ return undefined;
81
+ }
82
+ return { value: entry.value, etag: entry.etag };
83
+ }
84
+
85
+ function localSet(key: string, value: string, etag: string): void {
86
+ if (localCache.size >= LOCAL_CACHE_MAX_ENTRIES) {
87
+ // Evict the oldest entry (Map iterates in insertion order).
88
+ const oldest = localCache.keys().next();
89
+ if (!oldest.done) localCache.delete(oldest.value);
90
+ }
91
+ localCache.set(key, { at: Date.now(), value, etag });
92
+ }
93
+
66
94
  /**
67
95
  * Reads through the cache and returns a strong validator alongside the value.
68
96
  *
@@ -74,7 +102,8 @@ export function etagKey(key: string): string {
74
102
  * With no cache configured (`client === undefined`) the value is computed and
75
103
  * the validator is still returned, so conditional requests keep working for
76
104
  * the length of the client's own polling loop; only the cross-instance sharing
77
- * is lost.
105
+ * is lost. A process-local Map serves the same TTL when Redis is absent, so a
106
+ * poller still gets cache hits.
78
107
  */
79
108
  export async function cachedReadWithEtag<T>(
80
109
  client: ReadThroughCacheClient | undefined,
@@ -83,8 +112,15 @@ export async function cachedReadWithEtag<T>(
83
112
  ttlSeconds: number = READ_THROUGH_DEFAULT_TTL_SECONDS,
84
113
  ): Promise<CachedResult<T>> {
85
114
  if (client === undefined) {
115
+ const entry = localGet(key);
116
+ if (entry !== undefined) {
117
+ return { value: JSON.parse(entry.value) as T, etag: entry.etag };
118
+ }
86
119
  const fresh = await compute();
87
- return { value: fresh, etag: etagFor(JSON.stringify(fresh) ?? "null") };
120
+ const bytes = JSON.stringify(fresh) ?? "null";
121
+ const etag = etagFor(bytes);
122
+ localSet(key, bytes, etag);
123
+ return { value: fresh, etag };
88
124
  }
89
125
  try {
90
126
  const hit = await client.get(key);
@@ -160,7 +196,7 @@ export function conditionalResponse<T>(
160
196
  }
161
197
 
162
198
  /** True when an `If-None-Match` header names this validator. */
163
- export function matchesEtag(header: string | null, etag: string): boolean {
199
+ function matchesEtag(header: string | null, etag: string): boolean {
164
200
  if (header === null) return false;
165
201
  const trimmed = header.trim();
166
202
  if (trimmed === "*") return true;
@@ -0,0 +1,14 @@
1
+ import type { CartethyiaDatabase } from "../../persistence/db-handle";
2
+
3
+ /**
4
+ * Casts a Drizzle nested-transaction handle to the shared database type.
5
+ *
6
+ * Drizzle types a nested transaction callback with the dialect's own
7
+ * transaction handle, which differs from `CartethyiaDatabase`. The cast is
8
+ * required (both drivers implement the surface stores use), but it is a type
9
+ * hole every time it appears raw — so it is centralized here rather than
10
+ * scattered across call sites.
11
+ */
12
+ export function asConsoleDb(tx: unknown): CartethyiaDatabase {
13
+ return tx as unknown as CartethyiaDatabase;
14
+ }
@@ -16,33 +16,70 @@ export interface PromptSetting {
16
16
  export const PROMPT_MODES = ["append", "override"] as const;
17
17
  export type PromptMode = (typeof PROMPT_MODES)[number];
18
18
 
19
- /** Per-provider entry: follow the tenant's global prompt, or carry a custom one. */
20
- export interface ProviderPromptEntry {
21
- readonly followGlobal: boolean;
22
- /** Present and used only when `followGlobal` is false. */
23
- readonly setting?: PromptSetting;
19
+ /**
20
+ * A named, reusable prompt. Scopes reference presets by id instead of each
21
+ * carrying its own text, so one tone/ruleset can be applied to many scopes.
22
+ */
23
+ export interface SystemPromptPreset {
24
+ readonly id: string;
25
+ readonly name: string;
26
+ readonly enabled: boolean;
27
+ readonly mode: PromptMode;
28
+ readonly text: string;
24
29
  }
25
30
 
31
+ /**
32
+ * How one scope decides what to inject. Modes:
33
+ *
34
+ * - `off` — explicitly inject nothing for this scope; scope-specific stop,
35
+ * does NOT fall through to the global prompt.
36
+ * - `global` — follow the tenant's global prompt (the default posture).
37
+ * - `preset` — use the referenced named preset (disabled preset = no inject).
38
+ * - `custom` — carry this scope's own prompt inline.
39
+ *
40
+ * A scope with no entry at all behaves as `global` (resolution falls through
41
+ * to the global prompt), so omitting a key is the same as an explicit
42
+ * `{ kind: "global" }` reference. The global scope itself never uses
43
+ * `{ kind: "global" }` — it is `null` (off), a preset, or a custom setting.
44
+ */
45
+ export type ScopePromptRef =
46
+ | { readonly kind: "off" }
47
+ | { readonly kind: "global" }
48
+ | { readonly kind: "preset"; readonly presetId: string }
49
+ | { readonly kind: "custom"; readonly setting: PromptSetting };
50
+
26
51
  /** The full per-tenant configuration, one row in `tenant_system_prompts`. */
27
52
  export interface TenantSystemPrompts {
28
- /** `null` = never configured — distinct from an explicit disabled setting. */
29
- readonly global: PromptSetting | null;
30
- /** providerId → entry */
31
- readonly providers: Readonly<Record<string, ProviderPromptEntry>>;
32
- /** `${providerId}::${modelId}` → setting */
33
- readonly models: Readonly<Record<string, PromptSetting>>;
53
+ /** `null` = no global injection — distinct from an explicit disabled setting. */
54
+ readonly global: ScopePromptRef | null;
55
+ /** preset id → preset */
56
+ readonly presets: Readonly<Record<string, SystemPromptPreset>>;
57
+ /** providerId → ref */
58
+ readonly providers: Readonly<Record<string, ScopePromptRef>>;
59
+ /** `${providerId}::${modelId}` → ref */
60
+ readonly models: Readonly<Record<string, ScopePromptRef>>;
61
+ /** combo name → ref */
62
+ readonly combos: Readonly<Record<string, ScopePromptRef>>;
63
+ /** alias name → ref */
64
+ readonly aliases: Readonly<Record<string, ScopePromptRef>>;
34
65
  }
35
66
 
36
67
  /** Empty configuration: nothing configured anywhere. */
37
68
  export const EMPTY_TENANT_SYSTEM_PROMPTS: TenantSystemPrompts = {
38
69
  global: null,
70
+ presets: {},
39
71
  providers: {},
40
72
  models: {},
73
+ combos: {},
74
+ aliases: {},
41
75
  };
42
76
 
43
77
  /** Maximum stored text per scope (5 MB). Enforced at the console API. */
44
78
  export const SYSTEM_PROMPT_TEXT_CAP_BYTES = 5_000_000;
45
79
 
80
+ /** Maximum stored preset-name length. Enforced at the console API. */
81
+ export const SYSTEM_PROMPT_PRESET_NAME_CAP = 200;
82
+
46
83
  /**
47
84
  * Marker wrapping tenant text injected into a provider wire whose upstream
48
85
  * validates the system prompt as a channel identity (the buddy family). The
@@ -52,12 +89,21 @@ export const SYSTEM_PROMPT_TEXT_CAP_BYTES = 5_000_000;
52
89
  export const SYSTEM_PROMPT_WRAPPER_PREFIX = "<cartethyia:system-prompt>";
53
90
  export const SYSTEM_PROMPT_WRAPPER_SUFFIX = "</cartethyia:system-prompt>";
54
91
 
92
+ /** Maximum number of presets stored for one tenant. */
93
+ export const SYSTEM_PROMPT_PRESET_LIMIT = 64;
94
+
55
95
  /** Maximum number of per-provider entries stored for one tenant. */
56
96
  export const SYSTEM_PROMPT_PROVIDER_LIMIT = 256;
57
97
 
58
98
  /** Maximum number of per-model entries stored for one tenant. */
59
99
  export const SYSTEM_PROMPT_MODEL_LIMIT = 512;
60
100
 
101
+ /** Maximum number of per-combo entries stored for one tenant. */
102
+ export const SYSTEM_PROMPT_COMBO_LIMIT = 512;
103
+
104
+ /** Maximum number of per-alias entries stored for one tenant. */
105
+ export const SYSTEM_PROMPT_ALIAS_LIMIT = 512;
106
+
61
107
  /**
62
108
  * Providers whose upstream validates the fixed system prompt as a channel
63
109
  * identity. Their system turn must keep the fixed prompt as the opener, so
@@ -66,7 +112,7 @@ export const SYSTEM_PROMPT_MODEL_LIMIT = 512;
66
112
  * `override` mode is not honoured for these providers — it would strip the
67
113
  * identity and the upstream would refuse the request.
68
114
  */
69
- export const WRAPPED_INJECTION_PROVIDERS: ReadonlySet<string> = new Set([
115
+ const WRAPPED_INJECTION_PROVIDERS: ReadonlySet<string> = new Set([
70
116
  "cb", // CodeBuddy International
71
117
  "cbcn", // CodeBuddy CN
72
118
  "workbuddy",
@@ -100,3 +146,34 @@ export function isPromptSetting(value: unknown): value is PromptSetting {
100
146
  typeof candidate.text === "string"
101
147
  );
102
148
  }
149
+
150
+ /** True when the value is a well-formed preset (shape only, not size). */
151
+ export function isSystemPromptPreset(value: unknown): value is SystemPromptPreset {
152
+ if (typeof value !== "object" || value === null) return false;
153
+ const candidate = value as Partial<SystemPromptPreset>;
154
+ return (
155
+ typeof candidate.id === "string" &&
156
+ candidate.id.length > 0 &&
157
+ typeof candidate.name === "string" &&
158
+ candidate.name.length > 0 &&
159
+ typeof candidate.enabled === "boolean" &&
160
+ typeof candidate.mode === "string" &&
161
+ (PROMPT_MODES as readonly string[]).includes(candidate.mode) &&
162
+ typeof candidate.text === "string"
163
+ );
164
+ }
165
+
166
+ /** True when the value is a well-formed scope reference (shape only). */
167
+ export function isScopePromptRef(value: unknown): value is ScopePromptRef {
168
+ if (typeof value !== "object" || value === null) return false;
169
+ const kind = (value as { kind?: unknown }).kind;
170
+ if (kind === "off" || kind === "global") return true;
171
+ if (kind === "preset") {
172
+ const presetId = (value as { presetId?: unknown }).presetId;
173
+ return typeof presetId === "string" && presetId.length > 0;
174
+ }
175
+ if (kind === "custom") {
176
+ return isPromptSetting((value as { setting?: unknown }).setting);
177
+ }
178
+ return false;
179
+ }