cartethyia 2.7.4 → 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 (553) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-ChG5svFb.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-BIcWQun7.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Bf2VelDe.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-ggeMSjte.js → CustomizationPage-BKj_r0rp.js} +1 -1
  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-U6AKaJcF.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.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-lsrx7NYg.js → ProxyPage-5SnUSy3j.js} +1 -1
  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-f0DE1h0-.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.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-C0K-c4eo.js +1 -0
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DympBNq4.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-ibRWU_uT.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-CQXVMn4P.js +1 -0
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.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-8fiE6tec.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-CqGYeOrd.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-BSAsYsmV.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-D-MTJxMr.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-CPK47xQx.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-DBNPOfhV.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-Da2ZViWg.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-BXNeLw6M.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
  87. package/dist/dashboard/assets/{list-row-BycOrrIV.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/network-C5UBACrJ.js +1 -0
  92. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  93. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  94. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  95. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  96. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  97. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  98. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  99. package/dist/dashboard/assets/routing-in0f_IfK.js +1 -0
  100. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  101. package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BLULNqTp.js} +1 -1
  102. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  103. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  104. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  105. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  106. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  107. package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-D2lIR0tg.js} +1 -1
  108. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  109. package/dist/dashboard/assets/{state-CkfdYWel.js → state-DTARjfiW.js} +1 -1
  110. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  111. package/dist/dashboard/assets/{switch-SYm237HG.js → switch-c0oPSnqW.js} +1 -1
  112. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  113. package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-m8WGNPrz.js} +1 -1
  114. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  115. package/dist/dashboard/console-backgrounds.jpg +0 -0
  116. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  117. package/dist/dashboard/index.html +2 -2
  118. package/dist/dashboard/index.html.gz +0 -0
  119. package/dist/dashboard/precompressed-assets.json +56 -55
  120. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  121. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  122. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  123. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  124. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  125. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  126. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  127. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  128. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  129. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  130. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  131. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  132. package/package.json +4 -6
  133. package/scripts/cli/update.ts +15 -41
  134. package/src/boot/app.ts +58 -2
  135. package/src/boot/dependencies.ts +786 -732
  136. package/src/boot/lifecycle.ts +11 -12
  137. package/src/config/cooldowns.ts +34 -55
  138. package/src/config/network.ts +1 -1
  139. package/src/config/package-paths.ts +1 -1
  140. package/src/config/providers.ts +13 -14
  141. package/src/config/resources.ts +0 -5
  142. package/src/config/runtime.ts +2 -5
  143. package/src/config/spec.ts +26 -6
  144. package/src/console/api-keys/ban-constants.ts +38 -0
  145. package/src/console/api-keys/contracts.ts +71 -62
  146. package/src/console/api-keys/routes.ts +48 -12
  147. package/src/console/auth/access.ts +4 -4
  148. package/src/console/auth/account-reset.ts +1 -1
  149. package/src/console/auth/audit-recorder.ts +1 -1
  150. package/src/console/auth/lockout.ts +7 -3
  151. package/src/console/auth/row-coercion.ts +57 -12
  152. package/src/console/auth/session-resolver.ts +1 -1
  153. package/src/console/auth/session-service.ts +8 -2
  154. package/src/console/auth/session.ts +37 -31
  155. package/src/console/auth/setup-service.ts +4 -3
  156. package/src/console/auth/user-store.ts +1 -1
  157. package/src/console/backup/9router-map.ts +0 -3
  158. package/src/console/backup/contracts.ts +9 -9
  159. package/src/console/backup/export-endpoint.ts +5 -6
  160. package/src/console/backup/nine-router.ts +1 -7
  161. package/src/console/backup/routes.ts +29 -33
  162. package/src/console/backup/service.ts +468 -468
  163. package/src/console/backup/store.ts +47 -29
  164. package/src/console/backup/validate.ts +11 -1
  165. package/src/console/cli-tools/contracts.ts +7 -7
  166. package/src/console/cli-tools/injectors/cline.ts +110 -110
  167. package/src/console/cli-tools/injectors/codex.ts +181 -181
  168. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  169. package/src/console/cli-tools/injectors/driver.ts +247 -247
  170. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  171. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  172. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  173. package/src/console/cli-tools/routes.ts +1 -1
  174. package/src/console/cli-tools/service.ts +1 -1
  175. package/src/console/cli-tools/store.ts +2 -2
  176. package/src/console/console-router.ts +32 -0
  177. package/src/console/dashboard-assets.ts +9 -9
  178. package/src/console/domain-registration.ts +16 -2
  179. package/src/console/inferhub/client.ts +0 -7
  180. package/src/console/inferhub/key-store.ts +1 -1
  181. package/src/console/inferhub/normalize.ts +2 -2
  182. package/src/console/inferhub/routes.ts +1 -1
  183. package/src/console/inferhub/types.ts +1 -1
  184. package/src/console/observability/contracts.ts +1 -1
  185. package/src/console/observability/live.ts +31 -18
  186. package/src/console/observability/logs.ts +1 -1
  187. package/src/console/observability/sse.ts +62 -8
  188. package/src/console/observability/store.ts +20 -4
  189. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  190. package/src/console/providers/catalog/contracts.ts +39 -0
  191. package/src/console/providers/catalog/model-operations.ts +1 -1
  192. package/src/console/providers/catalog/provider-operations.ts +95 -17
  193. package/src/console/providers/catalog/public-model-store.ts +0 -0
  194. package/src/console/providers/catalog/routes.ts +27 -3
  195. package/src/console/providers/catalog/store.ts +147 -8
  196. package/src/console/providers/detail/routes.ts +1 -1
  197. package/src/console/providers/oauth/callback-listener.ts +100 -18
  198. package/src/console/providers/oauth/routes.ts +92 -13
  199. package/src/console/quota/account-quota-global.ts +41 -3
  200. package/src/console/quota/account-quota-shared.ts +10 -3
  201. package/src/console/quota/account-quota-tenant.ts +259 -66
  202. package/src/console/quota/daily-growth-job.ts +308 -0
  203. package/src/console/quota/native-identity.ts +2 -2
  204. package/src/console/quota/quota-view.ts +1 -1
  205. package/src/console/remote-support/routes.ts +1 -1
  206. package/src/console/remote-support/service.ts +2 -2
  207. package/src/console/routing/model/contracts.ts +1 -1
  208. package/src/console/routing/pools/contracts.ts +2 -2
  209. package/src/console/routing/pools/import-parse.ts +3 -3
  210. package/src/console/routing/pools/relay-deploy.ts +3 -3
  211. package/src/console/settings/contracts.ts +29 -2
  212. package/src/console/settings/store.ts +20 -0
  213. package/src/console/share/share-router.ts +100 -32
  214. package/src/console/share/share-stats.ts +4 -4
  215. package/src/console/share/share-usage.ts +1 -1
  216. package/src/console/shared/errors.ts +2 -2
  217. package/src/console/shared/object.ts +18 -0
  218. package/src/console/shared/read-through-cache.ts +4 -4
  219. package/src/console/shared/transaction.ts +14 -0
  220. package/src/console/system-prompt/contracts.ts +89 -12
  221. package/src/console/system-prompt/routes.ts +98 -45
  222. package/src/console/updates/apply-update.ts +244 -0
  223. package/src/console/updates/routes.ts +252 -97
  224. package/src/main.ts +11 -5
  225. package/src/network/http2-bidi.ts +1 -1
  226. package/src/network/ip-utils.ts +298 -0
  227. package/src/network/outbound-fetch.ts +1 -1
  228. package/src/network/pool/agent.ts +7 -6
  229. package/src/network/pool/byte-accounting.ts +1 -1
  230. package/src/network/pool/resolver.ts +16 -12
  231. package/src/network/pool/selector.ts +150 -136
  232. package/src/network/pool-health-machine.ts +1 -1
  233. package/src/network/rate-limit-verdict.ts +1 -1
  234. package/src/network/retry.ts +1 -1
  235. package/src/network/ssrf.ts +20 -71
  236. package/src/observability/event-log.ts +2 -2
  237. package/src/observability/event-query.ts +59 -291
  238. package/src/observability/log-sanitize.ts +1 -1
  239. package/src/observability/metrics.ts +4 -4
  240. package/src/observability/payload-capture.ts +6 -5
  241. package/src/observability/payload-store.ts +1 -1
  242. package/src/observability/runtime-metrics.ts +1 -1
  243. package/src/observability/telemetry-buffer.ts +12 -3
  244. package/src/observability/telemetry-reconciliation.ts +3 -3
  245. package/src/observability/token-speed.ts +1 -1
  246. package/src/persistence/connection-url.ts +1 -1
  247. package/src/persistence/db-handle.ts +1 -1
  248. package/src/persistence/db-pg.ts +2 -2
  249. package/src/persistence/db-sqlite.ts +3 -3
  250. package/src/persistence/dialect.ts +5 -1
  251. package/src/persistence/readiness.ts +1 -1
  252. package/src/persistence/redis.ts +1 -1
  253. package/src/persistence/schema.ts +113 -40
  254. package/src/persistence/shared-cache.ts +2 -2
  255. package/src/persistence/sort-order.ts +1 -1
  256. package/src/persistence/stores/api-key-store.ts +10 -1
  257. package/src/persistence/stores/share-store.ts +20 -8
  258. package/src/persistence/system-prompt-store.ts +63 -18
  259. package/src/persistence/tenant-preferences.ts +1 -1
  260. package/src/persistence/ttl-cache.ts +3 -3
  261. package/src/persistence/usage-flush.ts +7 -2
  262. package/src/protocol/canonical-model.ts +1 -1
  263. package/src/protocol/codecs.ts +6 -10
  264. package/src/protocol/dsml.ts +3 -49
  265. package/src/protocol/image-clamp.ts +2 -2
  266. package/src/protocol/reasoning-text.ts +1 -1
  267. package/src/protocol/reasoning.ts +7 -52
  268. package/src/protocol/registry.ts +50 -15
  269. package/src/protocol/request/chat.ts +46 -19
  270. package/src/protocol/request/codex.ts +1 -1
  271. package/src/protocol/request/messages.ts +46 -14
  272. package/src/protocol/request/responses.ts +19 -10
  273. package/src/protocol/response/chat.ts +25 -10
  274. package/src/protocol/response/codex.ts +1 -1
  275. package/src/protocol/response/messages.ts +1 -1
  276. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  277. package/src/protocol/response/responses.ts +1 -1
  278. package/src/protocol/sse-decoder.ts +50 -1
  279. package/src/protocol/stop-reason.ts +2 -2
  280. package/src/protocol/think-split.ts +347 -0
  281. package/src/protocol/translation/attachment-integrity.ts +3 -3
  282. package/src/protocol/translation/cache-controls.ts +8 -20
  283. package/src/protocol/translation/capabilities.ts +6 -6
  284. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  285. package/src/protocol/translation/dialect/inband.ts +39 -0
  286. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  287. package/src/protocol/translation/dialect/qwen.ts +114 -0
  288. package/src/protocol/translation/dialect/resolver.ts +83 -0
  289. package/src/protocol/translation/dialect/rules.ts +93 -0
  290. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  291. package/src/protocol/translation/dialect/types.ts +58 -0
  292. package/src/protocol/translation/quirks.ts +18 -4
  293. package/src/protocol/translation/thinking.ts +12 -252
  294. package/src/protocol/translation/tool-id.ts +3 -3
  295. package/src/protocol/translation/tool-repair.ts +1 -1
  296. package/src/protocol/upstream/post-json.ts +6 -1
  297. package/src/providers/adapters/compatible-adapter.ts +42 -3
  298. package/src/providers/authentication/jwt-validator.ts +0 -5
  299. package/src/providers/authentication/oauth-client.ts +254 -253
  300. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  301. package/src/providers/bundled-discovery-providers.ts +6 -30
  302. package/src/providers/configured-openai-providers.ts +82 -97
  303. package/src/providers/default-registry.ts +23 -35
  304. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  305. package/src/providers/discovery/discovery-types.ts +0 -1
  306. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  307. package/src/providers/discovery/probe-phases.ts +48 -13
  308. package/src/providers/discovery/probing-service.ts +6 -8
  309. package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
  310. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  311. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  312. package/src/providers/integrations/antigravity/antigravity.ts +2 -2
  313. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  314. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  315. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  316. package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
  317. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  318. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  319. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
  320. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  321. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
  322. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  323. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  324. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  325. package/src/providers/integrations/cline/cline.ts +1 -1
  326. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  327. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  328. package/src/providers/integrations/commandcode/commandcode.ts +75 -115
  329. package/src/providers/integrations/commandcode/quota.ts +131 -51
  330. package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
  331. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  332. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  333. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  334. package/src/providers/integrations/cursor/cursor.ts +38 -5
  335. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  336. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  337. package/src/providers/integrations/factory/factory.ts +9 -9
  338. package/src/providers/integrations/github/github-discovery.ts +55 -10
  339. package/src/providers/integrations/github/github-oauth.ts +6 -5
  340. package/src/providers/integrations/github/github.ts +41 -14
  341. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  342. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  343. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  344. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  345. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  346. package/src/providers/integrations/grok/grok.ts +86 -36
  347. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  348. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  349. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  350. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  351. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  352. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  353. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  354. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  355. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  356. package/src/providers/integrations/kiro/kiro.ts +81 -68
  357. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  358. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  359. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  360. package/src/providers/integrations/opencode/provider.ts +17 -24
  361. package/src/providers/integrations/qoder/qoder-catalog.ts +85 -8
  362. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  363. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  364. package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
  365. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  366. package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
  367. package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
  368. package/src/providers/integrations/qoder/qoder.ts +354 -62
  369. package/src/providers/integrations/shared/connect.ts +0 -54
  370. package/src/providers/integrations/shared/hash.ts +15 -0
  371. package/src/providers/integrations/shared/session-cache.ts +136 -0
  372. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  373. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  374. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  375. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  376. package/src/providers/integrations/trae/trae.ts +53 -10
  377. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  378. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  379. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  380. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  381. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  382. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  383. package/src/providers/operations/account-error-classification.ts +131 -116
  384. package/src/providers/operations/account-health-service.ts +31 -39
  385. package/src/providers/operations/account-reset-service.ts +58 -1
  386. package/src/providers/operations/cli-platform.ts +31 -5
  387. package/src/providers/operations/client-version-resolver.ts +4 -6
  388. package/src/providers/operations/client-versions.ts +10 -7
  389. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  390. package/src/providers/operations/provider-catalog-service.ts +18 -2
  391. package/src/providers/operations/provider-credential-service.ts +19 -2
  392. package/src/providers/provider-contracts.ts +18 -7
  393. package/src/providers/provider-metadata.ts +17 -2
  394. package/src/providers/provider-model-definition.ts +22 -1
  395. package/src/providers/provider-probe.ts +4 -2
  396. package/src/providers/provider-usage.ts +81 -16
  397. package/src/providers/quota/cache.ts +29 -0
  398. package/src/providers/quota/quota-contracts.ts +4 -0
  399. package/src/providers/quota/refresh.ts +27 -4
  400. package/src/providers/quota/snapshot.ts +94 -0
  401. package/src/providers/search/search-provider.ts +1 -3
  402. package/src/providers/search/search-providers.ts +2 -1
  403. package/src/security/abuse.ts +4 -4
  404. package/src/security/admission/service.ts +2 -2
  405. package/src/security/admission/ttl.ts +61 -61
  406. package/src/security/api-key-auth.ts +16 -4
  407. package/src/security/client-router-fingerprint.ts +4 -4
  408. package/src/security/credential-codec.ts +3 -3
  409. package/src/security/csrf.ts +2 -2
  410. package/src/security/ip-boundary.ts +16 -189
  411. package/src/security/model-access-rule.ts +1 -1
  412. package/src/security/outbound-headers.ts +18 -0
  413. package/src/shared/invoke-guard.ts +47 -0
  414. package/src/transport/contracts.ts +13 -4
  415. package/src/transport/dispatch/attempt-finalize.ts +1 -1
  416. package/src/transport/dispatch/capture-policy.ts +34 -37
  417. package/src/transport/dispatch/empty-stream.ts +23 -20
  418. package/src/transport/dispatch/generation.ts +25 -45
  419. package/src/transport/dispatch/native-shared.ts +127 -0
  420. package/src/transport/dispatch/proxy-request.ts +3 -1
  421. package/src/transport/dispatch/responses-compact.ts +25 -46
  422. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  423. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  424. package/src/transport/dispatch/streaming/stream.ts +7 -3
  425. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  426. package/src/transport/dispatch/systemone.ts +20 -48
  427. package/src/transport/dispatch/token-count.ts +41 -38
  428. package/src/transport/dispatch/websearch.ts +19 -45
  429. package/src/transport/drain-endpoint.ts +1 -1
  430. package/src/transport/failure-policy.ts +6 -0
  431. package/src/transport/guards/auth-guard.ts +0 -6
  432. package/src/transport/guards/body-policy.ts +3 -3
  433. package/src/transport/guards/headroom-guard.ts +1 -1
  434. package/src/transport/guards/pipeline.ts +1 -1
  435. package/src/transport/guards/request-context.ts +12 -17
  436. package/src/transport/request/preparer.ts +286 -7
  437. package/src/transport/request/rtk/compress-request.ts +3 -3
  438. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  439. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  440. package/src/transport/request/state.ts +1 -13
  441. package/src/transport/request/system-prompt-injection.ts +74 -11
  442. package/src/transport/request/tenant-preferences.ts +22 -0
  443. package/src/transport/routing/candidate-index.ts +2 -2
  444. package/src/transport/routing/round-robin.ts +1 -1
  445. package/src/transport/routing/route-catalog.ts +17 -18
  446. package/src/transport/routing/route-model.ts +126 -33
  447. package/src/transport/routing/router.ts +152 -37
  448. package/src/transport/shutdown-notice.ts +2 -2
  449. package/src/transport/surface/adapters.ts +3 -3
  450. package/src/transport/surface/chat/encode.ts +7 -7
  451. package/src/transport/surface/chat/parse.ts +7 -7
  452. package/src/transport/surface/completion.ts +2 -2
  453. package/src/transport/surface/content-parts.ts +1 -1
  454. package/src/transport/surface/dialects.ts +2 -2
  455. package/src/transport/surface/messages/parse.ts +3 -3
  456. package/src/transport/surface/responses/adapter.ts +1 -1
  457. package/src/transport/surface/responses/errors.ts +0 -9
  458. package/src/transport/vision-handoff-cache.ts +58 -0
  459. package/src/transport/vision-handoff.ts +376 -0
  460. package/src/workers/account-identity-sweep.ts +2 -2
  461. package/src/workers/buddy-growth-tasks.ts +392 -346
  462. package/src/workers/daily-checkin.ts +102 -49
  463. package/src/workers/maintenance-lease.ts +2 -35
  464. package/src/workers/oauth-refresh-sweep.ts +8 -1
  465. package/src/workers/quota-refresh-sweep.ts +120 -82
  466. package/src/workers/scheduler.ts +67 -4
  467. package/src/workers/sweep-runner.ts +24 -5
  468. package/src/workers/zcode-gift-claim.ts +108 -0
  469. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  470. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  471. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  472. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  473. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  474. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
  475. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  476. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
  477. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  478. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  479. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
  480. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  481. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  482. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  483. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
  484. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  485. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
  486. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  487. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  488. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  489. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
  490. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  491. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  492. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
  493. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  494. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
  495. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  496. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  497. package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
  498. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  499. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  500. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
  501. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  502. package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
  503. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  504. package/dist/dashboard/assets/app-a159k0ub.css +0 -2
  505. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  506. package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
  507. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  508. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  509. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  510. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  511. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
  512. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  513. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  514. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  515. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  516. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  517. package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
  518. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  519. package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
  520. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  521. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  522. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  523. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  524. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  525. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  526. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  527. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  528. package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
  529. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  530. package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
  531. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  532. package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
  533. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  534. package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
  535. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  536. package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
  537. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  538. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  539. package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
  540. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  541. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  542. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  543. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  544. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  545. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  546. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  547. package/src/network/response-headers.ts +0 -39
  548. package/src/protocol/response/dsml.ts +0 -38
  549. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  550. /package/dist/dashboard/assets/{app-BayTTy01.js → app-Bgd3zANQ.js} +0 -0
  551. /package/dist/dashboard/assets/{app-BayTTy01.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  552. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  553. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -41,6 +41,77 @@ const MIN_TOKEN_LENGTH = 20;
41
41
  */
42
42
  const SHARE_STATS_STREAM_INTERVAL_MS = 30_000;
43
43
 
44
+ /**
45
+ * A subscriber of the process-wide share-stats sampler. A recipient is keyed
46
+ * by its family (`tenantId` + template key) so one sampler tick can push to
47
+ * many links without a per-connection timer.
48
+ */
49
+ type ShareStatsListener = (family: ShareFamilyStats) => void;
50
+
51
+ /**
52
+ * One sampler per gateway for share-stats SSE delivery. Previously each
53
+ * `/share/:token/stats/stream` connection ran its own 30s timer and re-read
54
+ * the family rollup; with N open tabs that is N timers and N rollups all
55
+ * reading the same data. Now a single process-level sampler ticks on the
56
+ * shared cadence and fans each computed rollup out to the subscribers whose
57
+ * family it matches. Timers run only while at least one stream is open.
58
+ */
59
+ function createShareStatsSampler(config: {
60
+ readonly db: CartethyiaDatabase;
61
+ readonly shareStore: ShareLinkStore;
62
+ readonly stats: ShareStatsPort;
63
+ }): { subscribe(token: string, listener: ShareStatsListener): () => void } {
64
+ const subscribers = new Map<string, Set<ShareStatsListener>>();
65
+ let timer: ReturnType<typeof setTimeout> | undefined;
66
+
67
+ const tick = async (): Promise<void> => {
68
+ // One resolve per family token: a recipient with several tabs shares one
69
+ // rollup, and a family with no open stream is never computed.
70
+ const tokens = new Set(subscribers.keys());
71
+ for (const token of tokens) {
72
+ const listeners = subscribers.get(token);
73
+ if (listeners === undefined || listeners.size === 0) continue;
74
+ try {
75
+ const family = await resolveFamilyStats(config.db, config.shareStore, config.stats, token);
76
+ if (family === null) continue;
77
+ for (const listener of listeners) listener(family);
78
+ } catch {
79
+ // A transient read must not interrupt the sampler.
80
+ }
81
+ }
82
+ };
83
+
84
+ const start = (): void => {
85
+ if (timer !== undefined) return;
86
+ timer = setTimeout(async function run() {
87
+ timer = setTimeout(run, SHARE_STATS_STREAM_INTERVAL_MS);
88
+ await tick();
89
+ }, SHARE_STATS_STREAM_INTERVAL_MS);
90
+ };
91
+
92
+ const stop = (): void => {
93
+ if (timer !== undefined) clearTimeout(timer);
94
+ timer = undefined;
95
+ };
96
+
97
+ return {
98
+ subscribe(token, listener) {
99
+ let listeners = subscribers.get(token);
100
+ if (listeners === undefined) {
101
+ listeners = new Set();
102
+ subscribers.set(token, listeners);
103
+ }
104
+ listeners.add(listener);
105
+ if (subscribers.size > 0) start();
106
+ return () => {
107
+ listeners.delete(listener);
108
+ if (listeners.size === 0) subscribers.delete(token);
109
+ if (subscribers.size === 0) stop();
110
+ };
111
+ },
112
+ };
113
+ }
114
+
44
115
  /**
45
116
  * Context and capabilities for one allowed model, sourced from the public
46
117
  * catalog. Every field is nullable/optional so the page can show what is known
@@ -56,7 +127,7 @@ export interface ShareModelInfo {
56
127
  readonly webSearch: boolean;
57
128
  }
58
129
 
59
- export interface ShareRouterOptions {
130
+ interface ShareRouterOptions {
60
131
  readonly db: CartethyiaDatabase;
61
132
  readonly shareStore: ShareLinkStore;
62
133
  /** Resolves the normalized client IP through the trusted-proxy boundary. */
@@ -263,6 +334,12 @@ async function resolveFamilyStats(
263
334
  /** Creates the public enrollment page and one-time shared-key issuance route. */
264
335
  export function createShareRouter(options: ShareRouterOptions): AnyElysia {
265
336
  const { db, shareStore } = options;
337
+ // The sampler is shared by every stats stream this router serves; it is a
338
+ // no-op when the composition omits `stats` (tests that do not exercise the
339
+ // stream), because `tick` never runs with no subscribers.
340
+ const statsSampler = options.stats === undefined
341
+ ? undefined
342
+ : createShareStatsSampler({ db, shareStore, stats: options.stats });
266
343
 
267
344
  return new Elysia()
268
345
  /**
@@ -296,6 +373,7 @@ export function createShareRouter(options: ShareRouterOptions): AnyElysia {
296
373
  oneTimeLimit: row.lifetimeTokenBudget,
297
374
  requestsPerMinute: row.requestsPerMinute,
298
375
  maxConcurrentRequests: row.maxConcurrentRequests,
376
+ concurrencyScope: row.concurrencyScope ?? "family",
299
377
  modelPrefix: row.modelPrefix,
300
378
  models,
301
379
  modelInfo,
@@ -442,48 +520,38 @@ export function createShareRouter(options: ShareRouterOptions): AnyElysia {
442
520
  /**
443
521
  * Live family activity for the share page's stats section, pushed over SSE.
444
522
  *
445
- * The stream sends one snapshot immediately on connect, then re-reads on a
446
- * short cadence so the recipient watches the shared quota move instead of
447
- * refreshing. Telemetry is buffered server-side and drained in under a
448
- * second, so a DB re-read on this cadence is as fresh as an event bus would
449
- * be. Same bearer token and same masked-IP rollup as the one-shot route; a
450
- * token that stops resolving closes the stream rather than reporting zeros.
523
+ * The stream sends one snapshot immediately on connect, then pushes on a
524
+ * shared sampler cadence so the recipient watches the shared quota move
525
+ * instead of refreshing. Telemetry is buffered server-side and drained in
526
+ * under a second, so the sampler's DB re-read is as fresh as an event bus
527
+ * would be. Same bearer token and same masked-IP rollup as the one-shot
528
+ * route; a token that stops resolving closes the stream rather than
529
+ * reporting zeros.
451
530
  */
452
531
  .get("/share/:token/stats/stream", async ({ params, request }) => {
453
532
  if (typeof params.token !== "string") return notFound();
454
533
  const token = params.token;
455
534
  if (token.length < MIN_TOKEN_LENGTH) return notFound();
456
535
  const stats = options.stats;
457
- if (!stats) return json({ error: { code: "stats_unavailable", message: "Stats are unavailable" } }, 503);
536
+ if (!stats || statsSampler === undefined) {
537
+ return json({ error: { code: "stats_unavailable", message: "Stats are unavailable" } }, 503);
538
+ }
539
+ const sampler = statsSampler;
458
540
  return consoleSseResponse(
459
541
  createConsoleSseStream(request.signal, (sender) => {
460
- let closed = false;
461
- let timer: ReturnType<typeof setTimeout> | undefined;
462
- const push = async () => {
463
- const family = await resolveFamilyStats(db, shareStore, stats, token);
464
- if (closed) return;
542
+ const unsubscribe = sampler.subscribe(token, (family) => sender.send("stats", family));
543
+ // Bootstrap: deliver the link's current rollup immediately, and close
544
+ // the stream if the token no longer resolves — the same terminal
545
+ // behavior the per-connection timer had on a dead link.
546
+ void resolveFamilyStats(db, shareStore, stats, token).then((family) => {
465
547
  if (family === null) {
466
548
  sender.send("error", { code: "link_not_found" });
467
- closed = true;
468
- return;
549
+ unsubscribe();
550
+ } else {
551
+ sender.send("stats", family);
469
552
  }
470
- sender.send("stats", family);
471
- };
472
- const schedule = () => {
473
- timer = setTimeout(() => {
474
- if (closed) return;
475
- void push().finally(() => {
476
- if (!closed) schedule();
477
- });
478
- }, SHARE_STATS_STREAM_INTERVAL_MS);
479
- };
480
- void push().finally(() => {
481
- if (!closed) schedule();
482
- });
483
- return () => {
484
- closed = true;
485
- clearTimeout(timer);
486
- };
553
+ }).catch(() => undefined);
554
+ return unsubscribe;
487
555
  }),
488
556
  );
489
557
  })
@@ -30,7 +30,7 @@ import { cachedRead } from "../shared/read-through-cache";
30
30
  import { utcDayStart, utcMonthStart } from "../shared/day";
31
31
 
32
32
  /** Totals for the whole share family, all-time plus the windows the UI draws. */
33
- export interface ShareFamilyTotals {
33
+ interface ShareFamilyTotals {
34
34
  readonly requests: number;
35
35
  readonly errors: number;
36
36
  readonly inputTokens: number;
@@ -45,7 +45,7 @@ export interface ShareFamilyTotals {
45
45
  }
46
46
 
47
47
  /** One hour bucket of the last 24 hours, oldest first. */
48
- export interface ShareHourlyBucket {
48
+ interface ShareHourlyBucket {
49
49
  /** ISO instant at the top of the hour. */
50
50
  readonly hour: string;
51
51
  readonly requests: number;
@@ -53,7 +53,7 @@ export interface ShareHourlyBucket {
53
53
  readonly tokens: number;
54
54
  }
55
55
 
56
- export interface ShareTopModel {
56
+ interface ShareTopModel {
57
57
  readonly modelId: string;
58
58
  readonly requests: number;
59
59
  readonly tokens: number;
@@ -63,7 +63,7 @@ export interface ShareTopModel {
63
63
  readonly avgTtfbMs: number | null;
64
64
  }
65
65
 
66
- export interface ShareTopClientIp {
66
+ interface ShareTopClientIp {
67
67
  /** Always masked: this payload is served to the recipient, not the tenant. */
68
68
  readonly ip: string;
69
69
  readonly requests: number;
@@ -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,7 +59,7 @@ 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
 
@@ -196,7 +196,7 @@ export function conditionalResponse<T>(
196
196
  }
197
197
 
198
198
  /** True when an `If-None-Match` header names this validator. */
199
- export function matchesEtag(header: string | null, etag: string): boolean {
199
+ function matchesEtag(header: string | null, etag: string): boolean {
200
200
  if (header === null) return false;
201
201
  const trimmed = header.trim();
202
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
+ }
@@ -21,18 +21,25 @@ import {
21
21
  import {
22
22
  EMPTY_TENANT_SYSTEM_PROMPTS,
23
23
  PROMPT_MODES,
24
+ SYSTEM_PROMPT_ALIAS_LIMIT,
25
+ SYSTEM_PROMPT_COMBO_LIMIT,
24
26
  SYSTEM_PROMPT_MODEL_LIMIT,
27
+ SYSTEM_PROMPT_PRESET_LIMIT,
28
+ SYSTEM_PROMPT_PRESET_NAME_CAP,
25
29
  SYSTEM_PROMPT_PROVIDER_LIMIT,
26
30
  SYSTEM_PROMPT_TEXT_CAP_BYTES,
27
31
  isPromptSetting,
32
+ isScopePromptRef,
33
+ isSystemPromptPreset,
28
34
  parseSystemPromptModelKey,
29
35
  type PromptSetting,
30
- type ProviderPromptEntry,
36
+ type ScopePromptRef,
37
+ type SystemPromptPreset,
31
38
  type TenantSystemPrompts,
32
39
  } from "./contracts";
33
40
 
34
41
  /** Route-group context injected by the composition root. */
35
- export interface SystemPromptRoutesConfig {
42
+ interface SystemPromptRoutesConfig {
36
43
  readonly db: CartethyiaDatabase;
37
44
  readonly accessResolver: ConsoleAccessResolver;
38
45
  readonly auditSink?: AuditSink;
@@ -53,6 +60,46 @@ function validateSetting(value: unknown, scopeLabel: string): PromptSetting {
53
60
  return value;
54
61
  }
55
62
 
63
+ /** Validates one preset. */
64
+ function validatePreset(value: unknown, scopeLabel: string): SystemPromptPreset {
65
+ if (!isSystemPromptPreset(value)) {
66
+ throw new ConsoleDomainError("invalid_request", 400, `${scopeLabel} is not a valid preset`);
67
+ }
68
+ if (value.name.length > SYSTEM_PROMPT_PRESET_NAME_CAP) {
69
+ throw new ConsoleDomainError(
70
+ "invalid_request",
71
+ 400,
72
+ `${scopeLabel} name exceeds the ${SYSTEM_PROMPT_PRESET_NAME_CAP} character limit`,
73
+ );
74
+ }
75
+ validateSetting(value, scopeLabel);
76
+ return value;
77
+ }
78
+
79
+ /** Validates one scope reference; a preset id that does not exist is
80
+ * neutralized to `off` rather than rejected, so deleting a preset does not
81
+ * block saving a configuration that still references it. */
82
+ function validateScopeRef(
83
+ value: unknown,
84
+ scopeLabel: string,
85
+ presetIds: ReadonlySet<string>,
86
+ ): ScopePromptRef {
87
+ if (!isScopePromptRef(value)) {
88
+ throw new ConsoleDomainError("invalid_request", 400, `${scopeLabel} is not a valid scope reference`);
89
+ }
90
+ if (value.kind === "preset") {
91
+ if (!presetIds.has(value.presetId)) {
92
+ return { kind: "off" };
93
+ }
94
+ return value;
95
+ }
96
+ if (value.kind === "custom") {
97
+ validateSetting(value.setting, scopeLabel);
98
+ return value;
99
+ }
100
+ return value;
101
+ }
102
+
56
103
  /** Validates the full PUT body into the stored shape. */
57
104
  function validateBody(value: unknown): TenantSystemPrompts {
58
105
  if (typeof value !== "object" || value === null) {
@@ -60,68 +107,71 @@ function validateBody(value: unknown): TenantSystemPrompts {
60
107
  }
61
108
  const body = value as {
62
109
  global?: unknown;
110
+ presets?: unknown;
63
111
  providers?: unknown;
64
112
  models?: unknown;
113
+ combos?: unknown;
114
+ aliases?: unknown;
65
115
  };
66
116
 
67
- const global =
68
- body.global === null || body.global === undefined
69
- ? null
70
- : validateSetting(body.global, "global");
71
-
72
- if (body.providers !== undefined && typeof body.providers !== "object") {
73
- throw new ConsoleDomainError("invalid_request", 400, "providers must be an object");
117
+ if (body.presets !== undefined && typeof body.presets !== "object") {
118
+ throw new ConsoleDomainError("invalid_request", 400, "presets must be an object");
74
119
  }
75
- const rawProviders = (body.providers ?? {}) as Record<string, unknown>;
76
- const providerIds = Object.keys(rawProviders);
77
- if (providerIds.length > SYSTEM_PROMPT_PROVIDER_LIMIT) {
120
+ const rawPresets = (body.presets ?? {}) as Record<string, unknown>;
121
+ const presetIds = Object.keys(rawPresets);
122
+ if (presetIds.length > SYSTEM_PROMPT_PRESET_LIMIT) {
78
123
  throw new ConsoleDomainError(
79
124
  "invalid_request",
80
125
  400,
81
- `providers exceeds the ${SYSTEM_PROMPT_PROVIDER_LIMIT} entry limit`,
126
+ `presets exceeds the ${SYSTEM_PROMPT_PRESET_LIMIT} entry limit`,
82
127
  );
83
128
  }
84
- const providers: Record<string, ProviderPromptEntry> = {};
85
- for (const providerId of providerIds) {
86
- const entry = rawProviders[providerId];
87
- if (typeof entry !== "object" || entry === null) {
88
- throw new ConsoleDomainError("invalid_request", 400, `provider ${providerId} entry must be an object`);
129
+ const presets: Record<string, SystemPromptPreset> = {};
130
+ for (const id of presetIds) {
131
+ presets[id] = validatePreset(rawPresets[id], `preset ${id}`);
132
+ }
133
+ const presetIdSet = new Set(presetIds);
134
+
135
+ const global =
136
+ body.global === null || body.global === undefined
137
+ ? null
138
+ : validateScopeRef(body.global, "global", presetIdSet);
139
+
140
+ const readRefMap = (
141
+ raw: unknown,
142
+ label: string,
143
+ limit: number,
144
+ ): Record<string, ScopePromptRef> => {
145
+ if (raw !== undefined && typeof raw !== "object") {
146
+ throw new ConsoleDomainError("invalid_request", 400, `${label} must be an object`);
89
147
  }
90
- const candidate = entry as Partial<ProviderPromptEntry>;
91
- if (typeof candidate.followGlobal !== "boolean") {
92
- throw new ConsoleDomainError("invalid_request", 400, `provider ${providerId} needs followGlobal`);
148
+ const entries = (raw ?? {}) as Record<string, unknown>;
149
+ const keys = Object.keys(entries);
150
+ if (keys.length > limit) {
151
+ throw new ConsoleDomainError(
152
+ "invalid_request",
153
+ 400,
154
+ `${label} exceeds the ${limit} entry limit`,
155
+ );
93
156
  }
94
- if (candidate.followGlobal) {
95
- providers[providerId] = { followGlobal: true };
96
- } else {
97
- providers[providerId] = {
98
- followGlobal: false,
99
- setting: validateSetting(candidate.setting, `provider ${providerId}`),
100
- };
157
+ const out: Record<string, ScopePromptRef> = {};
158
+ for (const key of keys) {
159
+ out[key] = validateScopeRef(entries[key], `${label} ${key}`, presetIdSet);
101
160
  }
102
- }
161
+ return out;
162
+ };
103
163
 
104
- if (body.models !== undefined && typeof body.models !== "object") {
105
- throw new ConsoleDomainError("invalid_request", 400, "models must be an object");
106
- }
107
- const rawModels = (body.models ?? {}) as Record<string, unknown>;
108
- const modelKeys = Object.keys(rawModels);
109
- if (modelKeys.length > SYSTEM_PROMPT_MODEL_LIMIT) {
110
- throw new ConsoleDomainError(
111
- "invalid_request",
112
- 400,
113
- `models exceeds the ${SYSTEM_PROMPT_MODEL_LIMIT} entry limit`,
114
- );
115
- }
116
- const models: Record<string, PromptSetting> = {};
117
- for (const key of modelKeys) {
164
+ const providers = readRefMap(body.providers, "providers", SYSTEM_PROMPT_PROVIDER_LIMIT);
165
+ const models = readRefMap(body.models, "models", SYSTEM_PROMPT_MODEL_LIMIT);
166
+ for (const key of Object.keys(models)) {
118
167
  if (parseSystemPromptModelKey(key) === null) {
119
168
  throw new ConsoleDomainError("invalid_request", 400, `model key ${key} is malformed`);
120
169
  }
121
- models[key] = validateSetting(rawModels[key], `model ${key}`);
122
170
  }
171
+ const combos = readRefMap(body.combos, "combos", SYSTEM_PROMPT_COMBO_LIMIT);
172
+ const aliases = readRefMap(body.aliases, "aliases", SYSTEM_PROMPT_ALIAS_LIMIT);
123
173
 
124
- return { global, providers, models };
174
+ return { global, presets, providers, models, combos, aliases };
125
175
  }
126
176
 
127
177
  /**
@@ -150,8 +200,11 @@ export function createSystemPromptRoutes(config: SystemPromptRoutesConfig) {
150
200
  target: "tenant_system_prompts",
151
201
  detail: {
152
202
  global: next.global !== null,
203
+ presets: Object.keys(next.presets).length,
153
204
  providers: Object.keys(next.providers).length,
154
205
  models: Object.keys(next.models).length,
206
+ combos: Object.keys(next.combos).length,
207
+ aliases: Object.keys(next.aliases).length,
155
208
  },
156
209
  });
157
210
  return next;