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
@@ -23,7 +23,7 @@ import {
23
23
  } from "../../observability/log-ring";
24
24
  import { consoleSseResponse, createConsoleSseStream } from "./sse";
25
25
 
26
- export interface LogsConfig {
26
+ interface LogsConfig {
27
27
  readonly accessResolver: ConsoleAccessResolver;
28
28
  readonly auditSink?: AuditSink;
29
29
  }
@@ -5,14 +5,14 @@
5
5
  * the shared `buildSseEventFrame` core so the two layers cannot drift.
6
6
  */
7
7
 
8
- export interface SseSender {
8
+ interface SseSender {
9
9
  send(event: string, data: unknown): void;
10
10
  }
11
11
 
12
12
  /** Register endpoint-specific SSE subscriptions; return their teardown callback. */
13
- export type SseSetup = (sender: SseSender) => () => void;
13
+ type SseSetup = (sender: SseSender) => () => void;
14
14
 
15
- import { buildSseEventFrame as formatSseFrame } from "../../protocol/sse-frame";
15
+ import { TEXT_ENCODER, buildSseEventFrame as formatSseFrame } from "../../protocol/sse-frame";
16
16
 
17
17
  export { formatSseFrame };
18
18
 
@@ -22,38 +22,82 @@ export { formatSseFrame };
22
22
  */
23
23
  const SSE_HEARTBEAT_MS = 25_000;
24
24
 
25
+ /**
26
+ * Hard cap on one stream's lifetime. A tab that is abandoned but never
27
+ * unloads (a backgrounded browser window, a parked laptop) would otherwise
28
+ * hold its subscription and timers forever; the client reconnects on close,
29
+ * so this only forces a refresh. 45 minutes is far longer than any live view
30
+ * needs in one stretch.
31
+ */
32
+ const SSE_MAX_LIFETIME_MS = 45 * 60_000;
33
+
34
+ /**
35
+ * Idle timeout: if no *data* frame is sent for this long, the stream sends a
36
+ * terminal `close` event and closes. Heartbeats deliberately do not count —
37
+ * they are there to keep proxies alive, not to prove someone is reading, so
38
+ * they must not defeat the abandoned-tab cutoff.
39
+ */
40
+ const SSE_IDLE_TIMEOUT_MS = 5 * 60_000;
41
+
42
+ /** Watchdog cadence; the two cutoffs above are checked on this tick. */
43
+ const SSE_WATCHDOG_MS = 30_000;
44
+
45
+ /** Frame sent when the server cuts the stream (idle or max lifetime). */
46
+ function closeFrame(reason: string): Uint8Array {
47
+ return TEXT_ENCODER.encode(formatSseFrame("close", JSON.stringify({ reason })));
48
+ }
49
+
25
50
  /**
26
51
  * Console SSE response stream with uniform JSON event encoding, heartbeat,
27
- * and abort/cancel cleanup. The setup callback runs synchronously at stream
28
- * start so the initial snapshot is enqueued before the first read.
52
+ * idle/max-lifetime cutoff, and abort/cancel cleanup. The setup callback runs
53
+ * synchronously at stream start so the initial snapshot is enqueued before the
54
+ * first read.
55
+ *
56
+ * The encoder is the module-level shared `TextEncoder` from the frame module:
57
+ * allocating one per stream (let alone per send) is unnecessary garbage for a
58
+ * hot path that encodes a frame every few hundred milliseconds per connection.
29
59
  */
30
60
  export function createConsoleSseStream(
31
61
  signal: AbortSignal,
32
62
  setup: SseSetup,
33
63
  ): ReadableStream<Uint8Array> {
34
- const encoder = new TextEncoder();
35
64
  let cleanupSubscription: (() => void) | undefined;
36
65
  let heartbeat: ReturnType<typeof setInterval> | undefined;
66
+ let watchdog: ReturnType<typeof setInterval> | undefined;
37
67
  let close: (() => void) | undefined;
38
68
 
39
69
  return new ReadableStream<Uint8Array>({
40
70
  start(controller) {
41
71
  let closed = false;
72
+ const startedAt = Date.now();
73
+ let lastDataAt = startedAt;
42
74
  const sender: SseSender = {
43
75
  send(event, data) {
44
76
  if (closed) return;
45
77
  try {
46
- controller.enqueue(encoder.encode(formatSseFrame(event, JSON.stringify(data))));
78
+ lastDataAt = Date.now();
79
+ controller.enqueue(TEXT_ENCODER.encode(formatSseFrame(event, JSON.stringify(data))));
47
80
  } catch {
48
81
  close?.();
49
82
  }
50
83
  },
51
84
  };
52
85
 
86
+ const terminate = (reason: string): void => {
87
+ if (closed) return;
88
+ try {
89
+ controller.enqueue(closeFrame(reason));
90
+ } catch {
91
+ // Fall through to close regardless.
92
+ }
93
+ close?.();
94
+ };
95
+
53
96
  close = () => {
54
97
  if (closed) return;
55
98
  closed = true;
56
99
  clearInterval(heartbeat);
100
+ clearInterval(watchdog);
57
101
  cleanupSubscription?.();
58
102
  try {
59
103
  controller.close();
@@ -70,12 +114,22 @@ export function createConsoleSseStream(
70
114
  heartbeat = setInterval(() => {
71
115
  if (closed) return;
72
116
  try {
73
- controller.enqueue(encoder.encode(": ping\n\n"));
117
+ controller.enqueue(TEXT_ENCODER.encode(": ping\n\n"));
74
118
  } catch {
75
119
  close?.();
76
120
  }
77
121
  }, SSE_HEARTBEAT_MS);
78
122
 
123
+ watchdog = setInterval(() => {
124
+ if (closed) return;
125
+ const now = Date.now();
126
+ if (now - lastDataAt > SSE_IDLE_TIMEOUT_MS) {
127
+ terminate("idle");
128
+ } else if (now - startedAt > SSE_MAX_LIFETIME_MS) {
129
+ terminate("max_lifetime");
130
+ }
131
+ }, SSE_WATCHDOG_MS);
132
+
79
133
  signal.addEventListener("abort", close, { once: true });
80
134
  },
81
135
  cancel() {
@@ -184,6 +184,14 @@ async function resolveProxyLabel(
184
184
  }
185
185
  const HEALTH_WINDOW_MS = 24 * 3_600_000;
186
186
  const TOP_N = 50;
187
+ /**
188
+ * TTL for the usage analytics read-through (summary, chart, breakdown, totals).
189
+ * The dashboard polls these every 5–10s; a 30s TTL means the heavy telemetry
190
+ * window scans run once per 30s and the polls in between are answered from
191
+ * Redis (or the in-process fallback) with a `304`. Health stays on the
192
+ * default 10s — it feeds the live status tile.
193
+ */
194
+ const USAGE_READ_THROUGH_TTL_SECONDS = 30;
187
195
 
188
196
  /**
189
197
  * Nearest-rank percentile over a sorted array — kept for the JS-side
@@ -197,7 +205,7 @@ function percentile(sorted: readonly number[], fraction: number): number {
197
205
  return Math.round(sorted[index] ?? 0);
198
206
  }
199
207
 
200
- export interface UsagePerformanceSummary {
208
+ interface UsagePerformanceSummary {
201
209
  avgResolveMs: number;
202
210
  avgDurationMs: number;
203
211
  latencyP50Ms: number;
@@ -389,6 +397,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
389
397
  async usage(tenantId: string, period: string): Promise<CachedResult<UsageResponse>> {
390
398
  return this.readThroughWithEtag(this.usageCacheKey(tenantId, period, "usage"), () =>
391
399
  this.computeUsage(tenantId, period),
400
+ USAGE_READ_THROUGH_TTL_SECONDS,
392
401
  );
393
402
  }
394
403
 
@@ -405,8 +414,12 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
405
414
  * configured — and returns the strong validator alongside it, so a repeated
406
415
  * poll can be answered with a `304` instead of the whole payload.
407
416
  */
408
- private readThroughWithEtag<T>(key: string, compute: () => Promise<T>): Promise<CachedResult<T>> {
409
- return cachedReadWithEtag(this.redis, key, compute);
417
+ private readThroughWithEtag<T>(
418
+ key: string,
419
+ compute: () => Promise<T>,
420
+ ttlSeconds?: number,
421
+ ): Promise<CachedResult<T>> {
422
+ return cachedReadWithEtag(this.redis, key, compute, ttlSeconds);
410
423
  }
411
424
 
412
425
  private async computeUsage(tenantId: string, period: string): Promise<UsageResponse> {
@@ -459,6 +472,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
459
472
  async usageSummary(tenantId: string, period: string): Promise<CachedResult<UsageSummaryResponse>> {
460
473
  return this.readThroughWithEtag(this.usageCacheKey(tenantId, period, "summary"), () =>
461
474
  this.computeSummary(tenantId, period),
475
+ USAGE_READ_THROUGH_TTL_SECONDS,
462
476
  );
463
477
  }
464
478
 
@@ -499,6 +513,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
499
513
  async usageChart(tenantId: string, period: string): Promise<CachedResult<UsageChartResponse>> {
500
514
  return this.readThroughWithEtag(this.usageCacheKey(tenantId, period, "chart"), () =>
501
515
  this.computeChart(tenantId, period),
516
+ USAGE_READ_THROUGH_TTL_SECONDS,
502
517
  );
503
518
  }
504
519
 
@@ -522,7 +537,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
522
537
  input: bucket.inputTokens,
523
538
  // `cached` is the cached share of the prompt: the SQL reader sums
524
539
  // cached_input_tokens, which is the same number the old JS loop used.
525
- cached: bucket.inputTokens,
540
+ cached: bucket.cachedInputTokens,
526
541
  output: bucket.outputTokens,
527
542
  });
528
543
  }
@@ -546,6 +561,7 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
546
561
  ): Promise<CachedResult<UsageByResponse>> {
547
562
  return this.readThroughWithEtag(this.usageCacheKey(tenantId, `${dimension}:${period}`, "by"), () =>
548
563
  this.computeBy(tenantId, dimension, period),
564
+ USAGE_READ_THROUGH_TTL_SECONDS,
549
565
  );
550
566
  }
551
567
 
@@ -23,7 +23,7 @@ import type { ProviderRecord } from "./contracts";
23
23
  * purpose: the projection should not be able to reach a loader, so the type
24
24
  * cannot drift into "call something expensive to answer a boolean".
25
25
  */
26
- export interface ProviderDiscoveryLookup {
26
+ interface ProviderDiscoveryLookup {
27
27
  hasModelDiscovery(providerId: string): boolean;
28
28
  }
29
29
 
@@ -508,6 +508,13 @@ export interface ProviderAccountResponse {
508
508
  label: string;
509
509
  credentialKind: CredentialKind;
510
510
  credentialMode?: CredentialMode;
511
+ /**
512
+ * Which sign-in family issued this credential, read from the account's
513
+ * `auth_state` (e.g. `social`/`oidc`/`api_key` for Kiro). Surfaced so the
514
+ * console can badge an account by how it logged in without shipping the
515
+ * whole auth state to the browser.
516
+ */
517
+ authMethod?: string;
511
518
  tokenExpiresAt?: string;
512
519
  /**
513
520
  * Who issued this credential, read from the JWT `iss` claim at registration
@@ -653,6 +660,18 @@ export interface ProviderResponse {
653
660
  isBuiltIn: boolean;
654
661
  /** True when this tenant has any persisted account for the provider. */
655
662
  configured?: boolean;
663
+ /**
664
+ * Number of provider accounts this tenant has for the provider. Present on
665
+ * list items (the Providers page overview); absent elsewhere.
666
+ */
667
+ accountCount?: number;
668
+ /**
669
+ * Accounts whose status is not disabled/revoked — an account parked by the
670
+ * health machine (`cooldown`) still counts. Present on list items.
671
+ */
672
+ activeAccountCount?: number;
673
+ /** Enabled model rows the tenant can see for the provider. Present on list items. */
674
+ modelCount?: number;
656
675
  /** `false` only for a genuinely credential-less provider (OpenCode Free) —
657
676
  * the dashboard hides account creation for it since none is usable. */
658
677
  requiresAccount: boolean;
@@ -800,6 +819,26 @@ export interface ProviderCatalogStore {
800
819
  syncModels(tenantId: string, providerId: string): Promise<{ synced: number }>;
801
820
  listAccounts(tenantId: string, providerId: string): Promise<readonly ProviderAccountResponse[]>;
802
821
  listAllAccounts(tenantId: string): Promise<readonly ProviderAccountResponse[]>;
822
+ /**
823
+ * Provider-id + status only, for every account the tenant can see. Much
824
+ * cheaper than `listAllAccounts` (which also fetches today/lifetime usage,
825
+ * inflight, pool rows): used by the provider list's "configured" marker and
826
+ * the flat model picker's active-account counts, neither of which reads
827
+ * usage.
828
+ */
829
+ listAllAccountSummaries(tenantId: string): Promise<readonly { providerId: string; status: string }[]>;
830
+ /**
831
+ * Per-provider account totals for the tenant, from one grouped read. Used by
832
+ * the provider list overview (`accountCount` / `activeAccountCount`).
833
+ */
834
+ providerAccountCounts(
835
+ tenantId: string,
836
+ ): Promise<readonly { providerId: string; total: number; active: number }[]>;
837
+ /**
838
+ * Per-provider enabled model-row counts for the tenant, from one grouped read.
839
+ * Used by the provider list overview (`modelCount`).
840
+ */
841
+ providerModelCounts(tenantId: string): Promise<readonly { providerId: string; count: number }[]>;
803
842
  /**
804
843
  * Non-secret upstream auth configuration for the given owned accounts,
805
844
  * keyed by account id. Used by the account-export route so a re-import can
@@ -62,7 +62,7 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
62
62
  const a = requireTenantAnyScope(access, MODEL_READ_SCOPES);
63
63
  const [providers, allAccounts] = await Promise.all([
64
64
  config.store.list(a.tenantId),
65
- config.store.listAllAccounts(a.tenantId),
65
+ config.store.listAllAccountSummaries(a.tenantId),
66
66
  ]);
67
67
  const accountsByProvider = new Map<string, number>();
68
68
  for (const account of allAccounts) {
@@ -16,6 +16,8 @@ import {
16
16
  requireGlobalAdmin,
17
17
  requireTenantAnyScope,
18
18
  } from "../../shared/errors";
19
+ import { cachedReadWithEtag, conditionalResponse } from "../../shared/read-through-cache";
20
+ import type { RedisClient } from "../../../persistence/redis";
19
21
  import type { AccessDecision } from "../../../security/access-control";
20
22
  import type { ProviderUsageDayRow } from "../../../observability/event-query";
21
23
  import { isBundledProviderId } from "../../../providers/provider-contracts";
@@ -25,7 +27,6 @@ import type { OAuthLoginField } from "../../../providers/authentication/oauth-fl
25
27
  import { providerCredentialHint, providerCredentialUrl, providerDisplayName, providerHasAdapterUserAgent, providerOperatorNotice, providerServiceKinds, type CompatibilityProfile } from "../../../providers/provider-metadata";
26
28
  import type { AccountHealthEventRecord } from "../../../providers/operations/account-health-service";
27
29
  import { healthEventBuffer } from "../../../persistence/health-event-flush";
28
- import { materializeBuddyDeviceBinding } from "../../../providers/integrations/buddy/buddy-identity";
29
30
  import { CREDENTIAL_MODES, PROVIDER_READ_SCOPES, PROVIDER_WRITE_SCOPES, type BuddyGrowthTaskLedgerEntry } from "./contracts";
30
31
  import { enqueueCheckin } from "../../../workers/daily-checkin";
31
32
  import {
@@ -129,7 +130,22 @@ export interface ProviderCatalogConfig {
129
130
  readonly auditSink?: AuditSink;
130
131
  /** Registry-backed OAuth login clients; used to compute `oauthFlows` on responses. */
131
132
  readonly providerRegistry: ProviderRegistry;
132
- readonly snapshotInvalidator?: { invalidate(): Promise<number> };
133
+ /**
134
+ * Shared Redis client for the provider-list read-through cache. Omitted in
135
+ * lite mode / tests, where `cachedReadWithEtag` falls back to its bounded
136
+ * in-process TTL store; the ETag/304 semantics are identical either way.
137
+ */
138
+ readonly redis?: RedisClient | undefined;
139
+ /**
140
+ * Drops the cached route snapshot after a catalog write. `currentRevision`
141
+ * (when the host exposes it) is folded into the provider-list cache key, so
142
+ * every mutation that already bumps the snapshot — provider, account, and
143
+ * model writes — rotates the cached overview without a per-path delete.
144
+ */
145
+ readonly snapshotInvalidator?: {
146
+ invalidate(): Promise<number>;
147
+ currentRevision?(): number;
148
+ };
133
149
  /**
134
150
  * Re-applies one provider's BYOK adapter registration after a create/update
135
151
  * so a custom provider is dispatchable without a restart. Optional: tests
@@ -233,23 +249,81 @@ function toProviderLoginField(field: OAuthLoginField): ProviderLoginField {
233
249
  : { options: field.options.map((option) => ({ value: option.value, label: option.label })) }),
234
250
  };
235
251
  }
252
+ /** Provider-list cache TTL: short enough that a 60s poll re-checks the DB
253
+ * occasionally, long enough that a warm page does zero per-card work. */
254
+ const PROVIDER_LIST_CACHE_TTL_SECONDS = 20;
255
+
256
+ /** Cache key namespace + shape version for the provider-list overview. */
257
+ const PROVIDER_LIST_CACHE_SHAPE = "providers:v1:list";
258
+
259
+ /** Version of the read-through cache backend, so a bump is a key rotation. */
260
+ const PROVIDER_LIST_CACHE_REVISION = 1;
261
+
262
+ /** The list projection shared by `listProviders` and its cached wrapper. */
263
+ async function computeProviderList(
264
+ config: ProviderCatalogConfig,
265
+ tenantId: string,
266
+ ): Promise<ProviderResponse[]> {
267
+ const [records, accountCounts, modelCounts] = await Promise.all([
268
+ config.store.list(tenantId),
269
+ config.store.providerAccountCounts(tenantId),
270
+ config.store.providerModelCounts(tenantId),
271
+ ]);
272
+ const accountsByProvider = new Map(accountCounts.map((row) => [row.providerId, row]));
273
+ const modelsByProvider = new Map(modelCounts.map((row) => [row.providerId, row.count]));
274
+ return Promise.all(
275
+ records.map((r) => {
276
+ const counts = accountsByProvider.get(r.providerId);
277
+ return attachProviderCapabilities(
278
+ {
279
+ ...sanitizeProviderResponse(r),
280
+ // A provider with no persisted accounts is not "configured"; the
281
+ // count fields are still present (zero) so the card renders the
282
+ // empty state instead of a loading placeholder.
283
+ configured: counts !== undefined,
284
+ accountCount: counts?.total ?? 0,
285
+ activeAccountCount: counts?.active ?? 0,
286
+ modelCount: modelsByProvider.get(r.providerId) ?? 0,
287
+ },
288
+ config.providerRegistry,
289
+ );
290
+ }),
291
+ );
292
+ }
293
+
236
294
  export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
237
295
  const operations = {
238
296
  async listProviders(access: AccessDecision | undefined): Promise<ProviderResponse[]> {
239
297
  const a = requireTenantAnyScope(access, PROVIDER_READ_SCOPES);
240
- const [records, accounts] = await Promise.all([
241
- config.store.list(a.tenantId),
242
- config.store.listAllAccounts(a.tenantId),
243
- ]);
244
- const configured = new Set(accounts.map((account) => account.providerId));
245
- return Promise.all(
246
- records.map((r) =>
247
- attachProviderCapabilities(
248
- { ...sanitizeProviderResponse(r), configured: configured.has(r.providerId) },
249
- config.providerRegistry,
250
- ),
251
- ),
298
+ return computeProviderList(config, a.tenantId);
299
+ },
300
+ /**
301
+ * The full provider list with overview counts, served through the
302
+ * read-through cache (P-04). A repeated poll answers `304` with the ETag
303
+ * instead of recomputing the two grouped count queries.
304
+ *
305
+ * Returning a `Response` rather than a plain array is what makes the ETag
306
+ * conditional branch possible: a `304` has no body to serialize. The cache
307
+ * key is tenant-scoped and carries the routing revision, so a provider /
308
+ * account / model mutation that bumps the snapshot rotates the key and the
309
+ * next read recomputes fresh counts.
310
+ */
311
+ async listProvidersCached(
312
+ access: AccessDecision | undefined,
313
+ ifNoneMatch: string | null,
314
+ ): Promise<Response> {
315
+ const a = requireTenantAnyScope(access, PROVIDER_READ_SCOPES);
316
+ const revision = config.snapshotInvalidator?.currentRevision?.() ?? 0;
317
+ const key =
318
+ `cartethyia:${PROVIDER_LIST_CACHE_SHAPE}:v${PROVIDER_LIST_CACHE_REVISION}:` +
319
+ `${a.tenantId}:${revision}`;
320
+ const result = await cachedReadWithEtag(
321
+ config.redis,
322
+ key,
323
+ () => computeProviderList(config, a.tenantId),
324
+ PROVIDER_LIST_CACHE_TTL_SECONDS,
252
325
  );
326
+ return conditionalResponse(ifNoneMatch, result, result.value);
253
327
  },
254
328
  async getProviderDetail(
255
329
  access: AccessDecision | undefined,
@@ -694,7 +768,7 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
694
768
  // Non-secret auth state is part of the credential export: without
695
769
  // it a re-import loses the upstream user id and region that Trae
696
770
  // refresh and dispatch are addressed by.
697
- ...(() => {
771
+ ...(await (async () => {
698
772
  const authState = authStates[account.id];
699
773
  // Buddy providers bind a per-account device identity
700
774
  // (qimei36) that is normally derived from the INTERNAL account
@@ -707,13 +781,17 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
707
781
  account.providerId === "cbcn" ||
708
782
  account.providerId === "workbuddy" ||
709
783
  account.providerId === "workbuddycn"
710
- ? materializeBuddyDeviceBinding(account.id, authState)
784
+ // dynamic-import: B-02 lazy graph — buddy device-binding materializer, buddy-family only.
785
+ // The buddy-identity graph is lazy-loaded: the device-binding
786
+ // materializer only runs for buddy-family accounts (B-02).
787
+ ? (await import("../../../providers/integrations/buddy/buddy-identity"))
788
+ .materializeBuddyDeviceBinding(account.id, authState)
711
789
  : undefined;
712
790
  const effectiveAuthState = materialized ?? authState;
713
791
  return effectiveAuthState !== undefined && Object.keys(effectiveAuthState).length > 0
714
792
  ? { authState: effectiveAuthState }
715
793
  : {};
716
- })(),
794
+ })()),
717
795
  ...(account.inflight === undefined ? {} : { inflight: account.inflight }),
718
796
  createdAt: account.createdAt,
719
797
  ...(account.cooldownUntil ? { cooldownUntil: account.cooldownUntil } : {}),
@@ -151,12 +151,36 @@ const usageMatrixQuery = t.Object({
151
151
  days: t.Optional(t.Integer({ minimum: 1, maximum: 366 })),
152
152
  });
153
153
  export function createProviderCatalogRoutes(config: ProviderCatalogConfig): AnyElysia {
154
- const factory = createProviderCatalogOperations(config);
155
- const modelFactory = createModelCatalogOperations(config);
154
+ // One provider-list cache revision shared by both operation factories. Every
155
+ // provider/account/model mutation already funnels through the snapshot
156
+ // invalidator; wrapping it here means any of those writes bumps the revision
157
+ // folded into the provider-list cache key, so the cached overview (P-04)
158
+ // recomputes on the next read without a per-path cache delete. The revision
159
+ // stays per route-group, so a mutation handled by a peer process still
160
+ // converges when its own entry's TTL lapses.
161
+ const underlyingInvalidator = config.snapshotInvalidator;
162
+ let providerListCacheRevision = 0;
163
+ const catalogSnapshotInvalidator = underlyingInvalidator
164
+ ? {
165
+ invalidate: async () => {
166
+ providerListCacheRevision += 1;
167
+ return underlyingInvalidator.invalidate();
168
+ },
169
+ currentRevision: () => providerListCacheRevision,
170
+ }
171
+ : undefined;
172
+ const effectiveConfig = catalogSnapshotInvalidator
173
+ ? { ...config, snapshotInvalidator: catalogSnapshotInvalidator }
174
+ : config;
175
+ const factory = createProviderCatalogOperations(effectiveConfig);
176
+ const modelFactory = createModelCatalogOperations(effectiveConfig);
156
177
  return new Elysia({ prefix: "/providers" })
157
178
  .error(consoleErrorHandler("Provider catalog operation failed"))
158
179
  .get("/", async ({ request }) => {
159
- return await factory.listProviders(config.accessResolver(request));
180
+ return await factory.listProvidersCached(
181
+ config.accessResolver(request),
182
+ request.headers.get("if-none-match"),
183
+ );
160
184
  })
161
185
  .patch(
162
186
  "/platform/global/:providerId",