@srouterhq/server 0.0.0-stage → 0.9.0

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 (1027) hide show
  1. package/LICENSE +21 -0
  2. package/client/dist/assets/am-TQ7Jmdqe.js +1 -0
  3. package/client/dist/assets/ar-Bf5a7yfC.js +1 -0
  4. package/client/dist/assets/az-CrosE1xH.js +1 -0
  5. package/client/dist/assets/bg-BvWnmcUz.js +1 -0
  6. package/client/dist/assets/bn-ams5TsJp.js +1 -0
  7. package/client/dist/assets/cs-Dh67CaJ_.js +1 -0
  8. package/client/dist/assets/da-9WlT8Iq7.js +1 -0
  9. package/client/dist/assets/de-lWhJJzz7.js +1 -0
  10. package/client/dist/assets/el-Cu7_GhNq.js +1 -0
  11. package/client/dist/assets/es-BZuzsBcP.js +1 -0
  12. package/client/dist/assets/fa-Dwdn-jKS.js +1 -0
  13. package/client/dist/assets/fi-BFrFyOTy.js +1 -0
  14. package/client/dist/assets/fr-XhbtmPpj.js +1 -0
  15. package/client/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  16. package/client/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  17. package/client/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  18. package/client/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  19. package/client/dist/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
  20. package/client/dist/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
  21. package/client/dist/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
  22. package/client/dist/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
  23. package/client/dist/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
  24. package/client/dist/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
  25. package/client/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  26. package/client/dist/assets/gu-D0uhLUB8.js +1 -0
  27. package/client/dist/assets/ha-CVlm9pqp.js +1 -0
  28. package/client/dist/assets/he-CMIFeoWr.js +1 -0
  29. package/client/dist/assets/hi-ENTwhBpn.js +1 -0
  30. package/client/dist/assets/hr-fc03vMy_.js +1 -0
  31. package/client/dist/assets/hu-YBo2BIYt.js +1 -0
  32. package/client/dist/assets/id-BEWXYX69.js +1 -0
  33. package/client/dist/assets/ig-BKLZFKka.js +1 -0
  34. package/client/dist/assets/index-D9I4KtvR.js +145 -0
  35. package/client/dist/assets/index-txhEmCdb.css +2 -0
  36. package/client/dist/assets/it-Bzmei1Y2.js +1 -0
  37. package/client/dist/assets/ja-BfcpuqEl.js +1 -0
  38. package/client/dist/assets/ka-CnWjRgLo.js +1 -0
  39. package/client/dist/assets/km-BAjPR3Qj.js +1 -0
  40. package/client/dist/assets/kn-B15WKU5m.js +1 -0
  41. package/client/dist/assets/ko-C61fAB-w.js +1 -0
  42. package/client/dist/assets/lt-CYTr4_YW.js +1 -0
  43. package/client/dist/assets/ml-CtqxgyH3.js +1 -0
  44. package/client/dist/assets/mr-X5TMY7aR.js +1 -0
  45. package/client/dist/assets/ms-DSg8RqPS.js +1 -0
  46. package/client/dist/assets/my-D7RqR8uE.js +1 -0
  47. package/client/dist/assets/ne-DaflxkDj.js +1 -0
  48. package/client/dist/assets/nl-BOqqtsC8.js +1 -0
  49. package/client/dist/assets/no-BSHp_StB.js +1 -0
  50. package/client/dist/assets/or-BkLR9Vub.js +1 -0
  51. package/client/dist/assets/pa-CarJ2XgI.js +1 -0
  52. package/client/dist/assets/pl-C2n0V-0k.js +1 -0
  53. package/client/dist/assets/pt-BR-CqJdNhg2.js +1 -0
  54. package/client/dist/assets/pt-PT-C_HpMyKz.js +1 -0
  55. package/client/dist/assets/ro-DVRZqSAS.js +1 -0
  56. package/client/dist/assets/ru-DwAJG7VC.js +1 -0
  57. package/client/dist/assets/si-BxUezXm0.js +1 -0
  58. package/client/dist/assets/sk-eI1VcIfY.js +1 -0
  59. package/client/dist/assets/sr-CJF3auCr.js +1 -0
  60. package/client/dist/assets/srouter-logo-C6ZfjGIi.svg +14 -0
  61. package/client/dist/assets/sv-nqvwHAq_.js +1 -0
  62. package/client/dist/assets/sw-DWynE2pW.js +1 -0
  63. package/client/dist/assets/ta-BVM12knC.js +1 -0
  64. package/client/dist/assets/te-oyzEkF7_.js +1 -0
  65. package/client/dist/assets/th-COVeE0ZQ.js +1 -0
  66. package/client/dist/assets/tl-DsJANnQz.js +1 -0
  67. package/client/dist/assets/tr-B-Fl-urO.js +1 -0
  68. package/client/dist/assets/uk-C5qketAO.js +1 -0
  69. package/client/dist/assets/ur-QU9KugfV.js +1 -0
  70. package/client/dist/assets/uz-DL9cG_mY.js +1 -0
  71. package/client/dist/assets/vi-CQiVy3Pu.js +1 -0
  72. package/client/dist/assets/yo-DqazMdrZ.js +1 -0
  73. package/client/dist/assets/zh-CN-DnYL_Kio.js +1 -0
  74. package/client/dist/assets/zh-TW-5bdbosKj.js +1 -0
  75. package/client/dist/favicon.svg +14 -0
  76. package/client/dist/icons.svg +24 -0
  77. package/client/dist/index.html +37 -0
  78. package/dist/app.d.ts +4 -0
  79. package/dist/app.d.ts.map +1 -0
  80. package/dist/app.js +372 -0
  81. package/dist/app.js.map +1 -0
  82. package/dist/db/index.d.ts +49 -0
  83. package/dist/db/index.d.ts.map +1 -0
  84. package/dist/db/index.js +203 -0
  85. package/dist/db/index.js.map +1 -0
  86. package/dist/db/migrate/TEMPLATE.d.ts +4 -0
  87. package/dist/db/migrate/TEMPLATE.d.ts.map +1 -0
  88. package/dist/db/migrate/TEMPLATE.js +18 -0
  89. package/dist/db/migrate/TEMPLATE.js.map +1 -0
  90. package/dist/db/migrate/cli.d.ts +2 -0
  91. package/dist/db/migrate/cli.d.ts.map +1 -0
  92. package/dist/db/migrate/cli.js +177 -0
  93. package/dist/db/migrate/cli.js.map +1 -0
  94. package/dist/db/migrate/defaults.d.ts +52 -0
  95. package/dist/db/migrate/defaults.d.ts.map +1 -0
  96. package/dist/db/migrate/defaults.js +126 -0
  97. package/dist/db/migrate/defaults.js.map +1 -0
  98. package/dist/db/migrate/runner.d.ts +16 -0
  99. package/dist/db/migrate/runner.d.ts.map +1 -0
  100. package/dist/db/migrate/runner.js +178 -0
  101. package/dist/db/migrate/runner.js.map +1 -0
  102. package/dist/db/migrations/20260101_000000_legacy_baseline.d.ts +4 -0
  103. package/dist/db/migrations/20260101_000000_legacy_baseline.d.ts.map +1 -0
  104. package/dist/db/migrations/20260101_000000_legacy_baseline.js +2240 -0
  105. package/dist/db/migrations/20260101_000000_legacy_baseline.js.map +1 -0
  106. package/dist/db/migrations/20260627_000001_custom_provider_modalities.d.ts +4 -0
  107. package/dist/db/migrations/20260627_000001_custom_provider_modalities.d.ts.map +1 -0
  108. package/dist/db/migrations/20260627_000001_custom_provider_modalities.js +27 -0
  109. package/dist/db/migrations/20260627_000001_custom_provider_modalities.js.map +1 -0
  110. package/dist/db/migrations/20260627_000002_catalog_model_state.d.ts +4 -0
  111. package/dist/db/migrations/20260627_000002_catalog_model_state.d.ts.map +1 -0
  112. package/dist/db/migrations/20260627_000002_catalog_model_state.js +30 -0
  113. package/dist/db/migrations/20260627_000002_catalog_model_state.js.map +1 -0
  114. package/dist/db/migrations/20260628_120000_request_aggregates.d.ts +6 -0
  115. package/dist/db/migrations/20260628_120000_request_aggregates.d.ts.map +1 -0
  116. package/dist/db/migrations/20260628_120000_request_aggregates.js +104 -0
  117. package/dist/db/migrations/20260628_120000_request_aggregates.js.map +1 -0
  118. package/dist/db/migrations/20260630_000001_github_gpt41_context.d.ts +9 -0
  119. package/dist/db/migrations/20260630_000001_github_gpt41_context.d.ts.map +1 -0
  120. package/dist/db/migrations/20260630_000001_github_gpt41_context.js +22 -0
  121. package/dist/db/migrations/20260630_000001_github_gpt41_context.js.map +1 -0
  122. package/dist/db/migrations/20260706_000001_request_client_info.d.ts +4 -0
  123. package/dist/db/migrations/20260706_000001_request_client_info.d.ts.map +1 -0
  124. package/dist/db/migrations/20260706_000001_request_client_info.js +30 -0
  125. package/dist/db/migrations/20260706_000001_request_client_info.js.map +1 -0
  126. package/dist/db/migrations/20260706_000002_custom_model_tool_support.d.ts +17 -0
  127. package/dist/db/migrations/20260706_000002_custom_model_tool_support.d.ts.map +1 -0
  128. package/dist/db/migrations/20260706_000002_custom_model_tool_support.js +20 -0
  129. package/dist/db/migrations/20260706_000002_custom_model_tool_support.js.map +1 -0
  130. package/dist/db/migrations/20260714_000001_profile_chain_backfill.d.ts +12 -0
  131. package/dist/db/migrations/20260714_000001_profile_chain_backfill.d.ts.map +1 -0
  132. package/dist/db/migrations/20260714_000001_profile_chain_backfill.js +45 -0
  133. package/dist/db/migrations/20260714_000001_profile_chain_backfill.js.map +1 -0
  134. package/dist/db/migrations/20260720_000001_key_health_error.d.ts +5 -0
  135. package/dist/db/migrations/20260720_000001_key_health_error.d.ts.map +1 -0
  136. package/dist/db/migrations/20260720_000001_key_health_error.js +16 -0
  137. package/dist/db/migrations/20260720_000001_key_health_error.js.map +1 -0
  138. package/dist/db/migrations/20260726_000001_cooldown_probe_provenance.d.ts +4 -0
  139. package/dist/db/migrations/20260726_000001_cooldown_probe_provenance.d.ts.map +1 -0
  140. package/dist/db/migrations/20260726_000001_cooldown_probe_provenance.js +38 -0
  141. package/dist/db/migrations/20260726_000001_cooldown_probe_provenance.js.map +1 -0
  142. package/dist/db/migrations/20260726_000002_request_attempts.d.ts +4 -0
  143. package/dist/db/migrations/20260726_000002_request_attempts.d.ts.map +1 -0
  144. package/dist/db/migrations/20260726_000002_request_attempts.js +43 -0
  145. package/dist/db/migrations/20260726_000002_request_attempts.js.map +1 -0
  146. package/dist/db/migrations/20260726_000003_model_source_provenance.d.ts +4 -0
  147. package/dist/db/migrations/20260726_000003_model_source_provenance.d.ts.map +1 -0
  148. package/dist/db/migrations/20260726_000003_model_source_provenance.js +80 -0
  149. package/dist/db/migrations/20260726_000003_model_source_provenance.js.map +1 -0
  150. package/dist/db/migrations/20260726_000004_media_model_meta.d.ts +4 -0
  151. package/dist/db/migrations/20260726_000004_media_model_meta.d.ts.map +1 -0
  152. package/dist/db/migrations/20260726_000004_media_model_meta.js +34 -0
  153. package/dist/db/migrations/20260726_000004_media_model_meta.js.map +1 -0
  154. package/dist/db/migrations/20260726_000005_request_served_model.d.ts +4 -0
  155. package/dist/db/migrations/20260726_000005_request_served_model.d.ts.map +1 -0
  156. package/dist/db/migrations/20260726_000005_request_served_model.js +31 -0
  157. package/dist/db/migrations/20260726_000005_request_served_model.js.map +1 -0
  158. package/dist/db/migrations/20260726_000006_attempt_error_summary.d.ts +4 -0
  159. package/dist/db/migrations/20260726_000006_attempt_error_summary.d.ts.map +1 -0
  160. package/dist/db/migrations/20260726_000006_attempt_error_summary.js +29 -0
  161. package/dist/db/migrations/20260726_000006_attempt_error_summary.js.map +1 -0
  162. package/dist/db/migrations/20260727_000001_agent_compatibility.d.ts +4 -0
  163. package/dist/db/migrations/20260727_000001_agent_compatibility.d.ts.map +1 -0
  164. package/dist/db/migrations/20260727_000001_agent_compatibility.js +41 -0
  165. package/dist/db/migrations/20260727_000001_agent_compatibility.js.map +1 -0
  166. package/dist/db/migrations/20260728_000001_tombstone_provenance.d.ts +12 -0
  167. package/dist/db/migrations/20260728_000001_tombstone_provenance.d.ts.map +1 -0
  168. package/dist/db/migrations/20260728_000001_tombstone_provenance.js +37 -0
  169. package/dist/db/migrations/20260728_000001_tombstone_provenance.js.map +1 -0
  170. package/dist/db/migrations/20260729_000001_custom_model_endpoint_identity.d.ts +4 -0
  171. package/dist/db/migrations/20260729_000001_custom_model_endpoint_identity.d.ts.map +1 -0
  172. package/dist/db/migrations/20260729_000001_custom_model_endpoint_identity.js +145 -0
  173. package/dist/db/migrations/20260729_000001_custom_model_endpoint_identity.js.map +1 -0
  174. package/dist/db/migrations/20260802_000001_custom_endpoint_host_labels.d.ts +6 -0
  175. package/dist/db/migrations/20260802_000001_custom_endpoint_host_labels.d.ts.map +1 -0
  176. package/dist/db/migrations/20260802_000001_custom_endpoint_host_labels.js +52 -0
  177. package/dist/db/migrations/20260802_000001_custom_endpoint_host_labels.js.map +1 -0
  178. package/dist/db/migrations/20260805_000001_key_model_scope.d.ts +6 -0
  179. package/dist/db/migrations/20260805_000001_key_model_scope.d.ts.map +1 -0
  180. package/dist/db/migrations/20260805_000001_key_model_scope.js +17 -0
  181. package/dist/db/migrations/20260805_000001_key_model_scope.js.map +1 -0
  182. package/dist/db/migrations/20260805_000002_client_profiles.d.ts +4 -0
  183. package/dist/db/migrations/20260805_000002_client_profiles.d.ts.map +1 -0
  184. package/dist/db/migrations/20260805_000002_client_profiles.js +33 -0
  185. package/dist/db/migrations/20260805_000002_client_profiles.js.map +1 -0
  186. package/dist/db/migrations/20260810_000001_api_key_proxy.d.ts +4 -0
  187. package/dist/db/migrations/20260810_000001_api_key_proxy.d.ts.map +1 -0
  188. package/dist/db/migrations/20260810_000001_api_key_proxy.js +33 -0
  189. package/dist/db/migrations/20260810_000001_api_key_proxy.js.map +1 -0
  190. package/dist/db/migrations/20260819_000001_custom_model_tombstones.d.ts +4 -0
  191. package/dist/db/migrations/20260819_000001_custom_model_tombstones.d.ts.map +1 -0
  192. package/dist/db/migrations/20260819_000001_custom_model_tombstones.js +19 -0
  193. package/dist/db/migrations/20260819_000001_custom_model_tombstones.js.map +1 -0
  194. package/dist/db/migrations/20260820_000001_playground_conversations.d.ts +4 -0
  195. package/dist/db/migrations/20260820_000001_playground_conversations.d.ts.map +1 -0
  196. package/dist/db/migrations/20260820_000001_playground_conversations.js +47 -0
  197. package/dist/db/migrations/20260820_000001_playground_conversations.js.map +1 -0
  198. package/dist/db/migrations/20260823_000001_server_logs.d.ts +4 -0
  199. package/dist/db/migrations/20260823_000001_server_logs.d.ts.map +1 -0
  200. package/dist/db/migrations/20260823_000001_server_logs.js +53 -0
  201. package/dist/db/migrations/20260823_000001_server_logs.js.map +1 -0
  202. package/dist/db/migrations/20260823_000002_backups_table.d.ts +8 -0
  203. package/dist/db/migrations/20260823_000002_backups_table.d.ts.map +1 -0
  204. package/dist/db/migrations/20260823_000002_backups_table.js +22 -0
  205. package/dist/db/migrations/20260823_000002_backups_table.js.map +1 -0
  206. package/dist/db/migrations/20260823_000003_attempt_key_label.d.ts +19 -0
  207. package/dist/db/migrations/20260823_000003_attempt_key_label.d.ts.map +1 -0
  208. package/dist/db/migrations/20260823_000003_attempt_key_label.js +28 -0
  209. package/dist/db/migrations/20260823_000003_attempt_key_label.js.map +1 -0
  210. package/dist/db/migrations/20260823_000004_profile_auto_include.d.ts +14 -0
  211. package/dist/db/migrations/20260823_000004_profile_auto_include.d.ts.map +1 -0
  212. package/dist/db/migrations/20260823_000004_profile_auto_include.js +25 -0
  213. package/dist/db/migrations/20260823_000004_profile_auto_include.js.map +1 -0
  214. package/dist/db/migrations/20260901_000001_idempotency_claims.d.ts +4 -0
  215. package/dist/db/migrations/20260901_000001_idempotency_claims.d.ts.map +1 -0
  216. package/dist/db/migrations/20260901_000001_idempotency_claims.js +46 -0
  217. package/dist/db/migrations/20260901_000001_idempotency_claims.js.map +1 -0
  218. package/dist/db/migrations/20260901_000002_quota_observation_lookup.d.ts +4 -0
  219. package/dist/db/migrations/20260901_000002_quota_observation_lookup.d.ts.map +1 -0
  220. package/dist/db/migrations/20260901_000002_quota_observation_lookup.js +37 -0
  221. package/dist/db/migrations/20260901_000002_quota_observation_lookup.js.map +1 -0
  222. package/dist/db/migrations/20260901_000003_request_caller.d.ts +4 -0
  223. package/dist/db/migrations/20260901_000003_request_caller.d.ts.map +1 -0
  224. package/dist/db/migrations/20260901_000003_request_caller.js +27 -0
  225. package/dist/db/migrations/20260901_000003_request_caller.js.map +1 -0
  226. package/dist/db/migrations/20260902_000001_analytics_latency_percentile_index.d.ts +4 -0
  227. package/dist/db/migrations/20260902_000001_analytics_latency_percentile_index.d.ts.map +1 -0
  228. package/dist/db/migrations/20260902_000001_analytics_latency_percentile_index.js +35 -0
  229. package/dist/db/migrations/20260902_000001_analytics_latency_percentile_index.js.map +1 -0
  230. package/dist/db/migrations/20260903_000001_mcp_enabled_default.d.ts +4 -0
  231. package/dist/db/migrations/20260903_000001_mcp_enabled_default.d.ts.map +1 -0
  232. package/dist/db/migrations/20260903_000001_mcp_enabled_default.js +37 -0
  233. package/dist/db/migrations/20260903_000001_mcp_enabled_default.js.map +1 -0
  234. package/dist/db/migrations/20260903_000002_response_cache.d.ts +4 -0
  235. package/dist/db/migrations/20260903_000002_response_cache.d.ts.map +1 -0
  236. package/dist/db/migrations/20260903_000002_response_cache.js +53 -0
  237. package/dist/db/migrations/20260903_000002_response_cache.js.map +1 -0
  238. package/dist/db/migrations/20260904_000001_key_monthly_budget.d.ts +13 -0
  239. package/dist/db/migrations/20260904_000001_key_monthly_budget.d.ts.map +1 -0
  240. package/dist/db/migrations/20260904_000001_key_monthly_budget.js +35 -0
  241. package/dist/db/migrations/20260904_000001_key_monthly_budget.js.map +1 -0
  242. package/dist/db/migrations/20260913_000001_request_model_attribution.d.ts +4 -0
  243. package/dist/db/migrations/20260913_000001_request_model_attribution.d.ts.map +1 -0
  244. package/dist/db/migrations/20260913_000001_request_model_attribution.js +68 -0
  245. package/dist/db/migrations/20260913_000001_request_model_attribution.js.map +1 -0
  246. package/dist/db/migrations/20260914_000001_key_monthly_usage.d.ts +5 -0
  247. package/dist/db/migrations/20260914_000001_key_monthly_usage.d.ts.map +1 -0
  248. package/dist/db/migrations/20260914_000001_key_monthly_usage.js +37 -0
  249. package/dist/db/migrations/20260914_000001_key_monthly_usage.js.map +1 -0
  250. package/dist/db/migrations/20260915_000001_quota_snapshot_freshness.d.ts +4 -0
  251. package/dist/db/migrations/20260915_000001_quota_snapshot_freshness.d.ts.map +1 -0
  252. package/dist/db/migrations/20260915_000001_quota_snapshot_freshness.js +28 -0
  253. package/dist/db/migrations/20260915_000001_quota_snapshot_freshness.js.map +1 -0
  254. package/dist/db/migrations/20261004_000001_unified_key_sr_prefix.d.ts +4 -0
  255. package/dist/db/migrations/20261004_000001_unified_key_sr_prefix.d.ts.map +1 -0
  256. package/dist/db/migrations/20261004_000001_unified_key_sr_prefix.js +25 -0
  257. package/dist/db/migrations/20261004_000001_unified_key_sr_prefix.js.map +1 -0
  258. package/dist/db/migrations/20261005_000001_key_budget_period.d.ts +10 -0
  259. package/dist/db/migrations/20261005_000001_key_budget_period.d.ts.map +1 -0
  260. package/dist/db/migrations/20261005_000001_key_budget_period.js +78 -0
  261. package/dist/db/migrations/20261005_000001_key_budget_period.js.map +1 -0
  262. package/dist/db/migrations/20261005_000002_key_budget_period_auto.d.ts +21 -0
  263. package/dist/db/migrations/20261005_000002_key_budget_period_auto.d.ts.map +1 -0
  264. package/dist/db/migrations/20261005_000002_key_budget_period_auto.js +41 -0
  265. package/dist/db/migrations/20261005_000002_key_budget_period_auto.js.map +1 -0
  266. package/dist/db/model-pricing.d.ts +39 -0
  267. package/dist/db/model-pricing.d.ts.map +1 -0
  268. package/dist/db/model-pricing.js +274 -0
  269. package/dist/db/model-pricing.js.map +1 -0
  270. package/dist/db/node-sqlite.d.ts +9 -0
  271. package/dist/db/node-sqlite.d.ts.map +1 -0
  272. package/dist/db/node-sqlite.js +87 -0
  273. package/dist/db/node-sqlite.js.map +1 -0
  274. package/dist/db/types.d.ts +22 -0
  275. package/dist/db/types.d.ts.map +1 -0
  276. package/dist/db/types.js +2 -0
  277. package/dist/db/types.js.map +1 -0
  278. package/dist/docs/docs-page.d.ts +2 -0
  279. package/dist/docs/docs-page.d.ts.map +1 -0
  280. package/dist/docs/docs-page.js +319 -0
  281. package/dist/docs/docs-page.js.map +1 -0
  282. package/dist/docs/openapi.d.ts +1896 -0
  283. package/dist/docs/openapi.d.ts.map +1 -0
  284. package/dist/docs/openapi.js +1096 -0
  285. package/dist/docs/openapi.js.map +1 -0
  286. package/dist/env.d.ts +2 -0
  287. package/dist/env.d.ts.map +1 -0
  288. package/dist/env.js +9 -0
  289. package/dist/env.js.map +1 -0
  290. package/dist/index.d.ts +2 -0
  291. package/dist/index.d.ts.map +1 -0
  292. package/dist/index.js +144 -0
  293. package/dist/index.js.map +1 -0
  294. package/dist/lib/anthropic-documents.d.ts +41 -0
  295. package/dist/lib/anthropic-documents.d.ts.map +1 -0
  296. package/dist/lib/anthropic-documents.js +132 -0
  297. package/dist/lib/anthropic-documents.js.map +1 -0
  298. package/dist/lib/app-version.d.ts +5 -0
  299. package/dist/lib/app-version.d.ts.map +1 -0
  300. package/dist/lib/app-version.js +61 -0
  301. package/dist/lib/app-version.js.map +1 -0
  302. package/dist/lib/attempt-trace.d.ts +23 -0
  303. package/dist/lib/attempt-trace.d.ts.map +1 -0
  304. package/dist/lib/attempt-trace.js +32 -0
  305. package/dist/lib/attempt-trace.js.map +1 -0
  306. package/dist/lib/budget.d.ts +6 -0
  307. package/dist/lib/budget.d.ts.map +1 -0
  308. package/dist/lib/budget.js +40 -0
  309. package/dist/lib/budget.js.map +1 -0
  310. package/dist/lib/client-classifier.d.ts +10 -0
  311. package/dist/lib/client-classifier.d.ts.map +1 -0
  312. package/dist/lib/client-classifier.js +126 -0
  313. package/dist/lib/client-classifier.js.map +1 -0
  314. package/dist/lib/client-context.d.ts +10 -0
  315. package/dist/lib/client-context.d.ts.map +1 -0
  316. package/dist/lib/client-context.js +39 -0
  317. package/dist/lib/client-context.js.map +1 -0
  318. package/dist/lib/config.d.ts +33 -0
  319. package/dist/lib/config.d.ts.map +1 -0
  320. package/dist/lib/config.js +94 -0
  321. package/dist/lib/config.js.map +1 -0
  322. package/dist/lib/content.d.ts +33 -0
  323. package/dist/lib/content.d.ts.map +1 -0
  324. package/dist/lib/content.js +201 -0
  325. package/dist/lib/content.js.map +1 -0
  326. package/dist/lib/credential.d.ts +11 -0
  327. package/dist/lib/credential.d.ts.map +1 -0
  328. package/dist/lib/credential.js +18 -0
  329. package/dist/lib/credential.js.map +1 -0
  330. package/dist/lib/crypto.d.ts +31 -0
  331. package/dist/lib/crypto.d.ts.map +1 -0
  332. package/dist/lib/crypto.js +206 -0
  333. package/dist/lib/crypto.js.map +1 -0
  334. package/dist/lib/custom-provider-cleanup.d.ts +13 -0
  335. package/dist/lib/custom-provider-cleanup.d.ts.map +1 -0
  336. package/dist/lib/custom-provider-cleanup.js +43 -0
  337. package/dist/lib/custom-provider-cleanup.js.map +1 -0
  338. package/dist/lib/db-backup.d.ts +31 -0
  339. package/dist/lib/db-backup.d.ts.map +1 -0
  340. package/dist/lib/db-backup.js +255 -0
  341. package/dist/lib/db-backup.js.map +1 -0
  342. package/dist/lib/endpoint-scope.d.ts +42 -0
  343. package/dist/lib/endpoint-scope.d.ts.map +1 -0
  344. package/dist/lib/endpoint-scope.js +95 -0
  345. package/dist/lib/endpoint-scope.js.map +1 -0
  346. package/dist/lib/env-drift.d.ts +17 -0
  347. package/dist/lib/env-drift.d.ts.map +1 -0
  348. package/dist/lib/env-drift.js +108 -0
  349. package/dist/lib/env-drift.js.map +1 -0
  350. package/dist/lib/error-classify.d.ts +40 -0
  351. package/dist/lib/error-classify.d.ts.map +1 -0
  352. package/dist/lib/error-classify.js +687 -0
  353. package/dist/lib/error-classify.js.map +1 -0
  354. package/dist/lib/error-redaction.d.ts +8 -0
  355. package/dist/lib/error-redaction.d.ts.map +1 -0
  356. package/dist/lib/error-redaction.js +45 -0
  357. package/dist/lib/error-redaction.js.map +1 -0
  358. package/dist/lib/fallback-loop.d.ts +306 -0
  359. package/dist/lib/fallback-loop.d.ts.map +1 -0
  360. package/dist/lib/fallback-loop.js +1336 -0
  361. package/dist/lib/fallback-loop.js.map +1 -0
  362. package/dist/lib/file-permissions.d.ts +98 -0
  363. package/dist/lib/file-permissions.d.ts.map +1 -0
  364. package/dist/lib/file-permissions.js +159 -0
  365. package/dist/lib/file-permissions.js.map +1 -0
  366. package/dist/lib/gemini-wire.d.ts +76 -0
  367. package/dist/lib/gemini-wire.d.ts.map +1 -0
  368. package/dist/lib/gemini-wire.js +392 -0
  369. package/dist/lib/gemini-wire.js.map +1 -0
  370. package/dist/lib/guardrails.d.ts +37 -0
  371. package/dist/lib/guardrails.d.ts.map +1 -0
  372. package/dist/lib/guardrails.js +106 -0
  373. package/dist/lib/guardrails.js.map +1 -0
  374. package/dist/lib/header-value.d.ts +8 -0
  375. package/dist/lib/header-value.d.ts.map +1 -0
  376. package/dist/lib/header-value.js +55 -0
  377. package/dist/lib/header-value.js.map +1 -0
  378. package/dist/lib/image-normalize.d.ts +21 -0
  379. package/dist/lib/image-normalize.d.ts.map +1 -0
  380. package/dist/lib/image-normalize.js +218 -0
  381. package/dist/lib/image-normalize.js.map +1 -0
  382. package/dist/lib/inbound-chat.d.ts +48 -0
  383. package/dist/lib/inbound-chat.d.ts.map +1 -0
  384. package/dist/lib/inbound-chat.js +406 -0
  385. package/dist/lib/inbound-chat.js.map +1 -0
  386. package/dist/lib/key-parser.d.ts +79 -0
  387. package/dist/lib/key-parser.d.ts.map +1 -0
  388. package/dist/lib/key-parser.js +701 -0
  389. package/dist/lib/key-parser.js.map +1 -0
  390. package/dist/lib/key-proxy.d.ts +49 -0
  391. package/dist/lib/key-proxy.d.ts.map +1 -0
  392. package/dist/lib/key-proxy.js +92 -0
  393. package/dist/lib/key-proxy.js.map +1 -0
  394. package/dist/lib/log-redaction.d.ts +28 -0
  395. package/dist/lib/log-redaction.d.ts.map +1 -0
  396. package/dist/lib/log-redaction.js +166 -0
  397. package/dist/lib/log-redaction.js.map +1 -0
  398. package/dist/lib/model-scope.d.ts +9 -0
  399. package/dist/lib/model-scope.d.ts.map +1 -0
  400. package/dist/lib/model-scope.js +29 -0
  401. package/dist/lib/model-scope.js.map +1 -0
  402. package/dist/lib/one-time-code.d.ts +15 -0
  403. package/dist/lib/one-time-code.d.ts.map +1 -0
  404. package/dist/lib/one-time-code.js +41 -0
  405. package/dist/lib/one-time-code.js.map +1 -0
  406. package/dist/lib/output-cap.d.ts +24 -0
  407. package/dist/lib/output-cap.d.ts.map +1 -0
  408. package/dist/lib/output-cap.js +80 -0
  409. package/dist/lib/output-cap.js.map +1 -0
  410. package/dist/lib/password.d.ts +3 -0
  411. package/dist/lib/password.d.ts.map +1 -0
  412. package/dist/lib/password.js +27 -0
  413. package/dist/lib/password.js.map +1 -0
  414. package/dist/lib/process-safety-net.d.ts +32 -0
  415. package/dist/lib/process-safety-net.d.ts.map +1 -0
  416. package/dist/lib/process-safety-net.js +123 -0
  417. package/dist/lib/process-safety-net.js.map +1 -0
  418. package/dist/lib/provider-identity.d.ts +37 -0
  419. package/dist/lib/provider-identity.d.ts.map +1 -0
  420. package/dist/lib/provider-identity.js +110 -0
  421. package/dist/lib/provider-identity.js.map +1 -0
  422. package/dist/lib/provider-size-parser.d.ts +6 -0
  423. package/dist/lib/provider-size-parser.d.ts.map +1 -0
  424. package/dist/lib/provider-size-parser.js +72 -0
  425. package/dist/lib/provider-size-parser.js.map +1 -0
  426. package/dist/lib/provider-timeout.d.ts +17 -0
  427. package/dist/lib/provider-timeout.d.ts.map +1 -0
  428. package/dist/lib/provider-timeout.js +70 -0
  429. package/dist/lib/provider-timeout.js.map +1 -0
  430. package/dist/lib/proxy.d.ts +180 -0
  431. package/dist/lib/proxy.d.ts.map +1 -0
  432. package/dist/lib/proxy.js +1002 -0
  433. package/dist/lib/proxy.js.map +1 -0
  434. package/dist/lib/request-log.d.ts +4 -0
  435. package/dist/lib/request-log.d.ts.map +1 -0
  436. package/dist/lib/request-log.js +161 -0
  437. package/dist/lib/request-log.js.map +1 -0
  438. package/dist/lib/reset-code.d.ts +5 -0
  439. package/dist/lib/reset-code.d.ts.map +1 -0
  440. package/dist/lib/reset-code.js +39 -0
  441. package/dist/lib/reset-code.js.map +1 -0
  442. package/dist/lib/retry-hint.d.ts +14 -0
  443. package/dist/lib/retry-hint.d.ts.map +1 -0
  444. package/dist/lib/retry-hint.js +36 -0
  445. package/dist/lib/retry-hint.js.map +1 -0
  446. package/dist/lib/route-live.d.ts +29 -0
  447. package/dist/lib/route-live.d.ts.map +1 -0
  448. package/dist/lib/route-live.js +97 -0
  449. package/dist/lib/route-live.js.map +1 -0
  450. package/dist/lib/sampling-params.d.ts +183 -0
  451. package/dist/lib/sampling-params.d.ts.map +1 -0
  452. package/dist/lib/sampling-params.js +386 -0
  453. package/dist/lib/sampling-params.js.map +1 -0
  454. package/dist/lib/scheduler.d.ts +13 -0
  455. package/dist/lib/scheduler.d.ts.map +1 -0
  456. package/dist/lib/scheduler.js +11 -0
  457. package/dist/lib/scheduler.js.map +1 -0
  458. package/dist/lib/served-model.d.ts +16 -0
  459. package/dist/lib/served-model.d.ts.map +1 -0
  460. package/dist/lib/served-model.js +0 -0
  461. package/dist/lib/served-model.js.map +1 -0
  462. package/dist/lib/server-logs.d.ts +132 -0
  463. package/dist/lib/server-logs.d.ts.map +1 -0
  464. package/dist/lib/server-logs.js +473 -0
  465. package/dist/lib/server-logs.js.map +1 -0
  466. package/dist/lib/setup-code.d.ts +5 -0
  467. package/dist/lib/setup-code.d.ts.map +1 -0
  468. package/dist/lib/setup-code.js +35 -0
  469. package/dist/lib/setup-code.js.map +1 -0
  470. package/dist/lib/structured-output.d.ts +9 -0
  471. package/dist/lib/structured-output.d.ts.map +1 -0
  472. package/dist/lib/structured-output.js +72 -0
  473. package/dist/lib/structured-output.js.map +1 -0
  474. package/dist/lib/system-prompt.d.ts +30 -0
  475. package/dist/lib/system-prompt.d.ts.map +1 -0
  476. package/dist/lib/system-prompt.js +66 -0
  477. package/dist/lib/system-prompt.js.map +1 -0
  478. package/dist/lib/task-type.d.ts +18 -0
  479. package/dist/lib/task-type.d.ts.map +1 -0
  480. package/dist/lib/task-type.js +92 -0
  481. package/dist/lib/task-type.js.map +1 -0
  482. package/dist/lib/think-tags.d.ts +37 -0
  483. package/dist/lib/think-tags.d.ts.map +1 -0
  484. package/dist/lib/think-tags.js +203 -0
  485. package/dist/lib/think-tags.js.map +1 -0
  486. package/dist/lib/tool-args.d.ts +36 -0
  487. package/dist/lib/tool-args.d.ts.map +1 -0
  488. package/dist/lib/tool-args.js +190 -0
  489. package/dist/lib/tool-args.js.map +1 -0
  490. package/dist/lib/tool-call-rescue.d.ts +62 -0
  491. package/dist/lib/tool-call-rescue.d.ts.map +1 -0
  492. package/dist/lib/tool-call-rescue.js +258 -0
  493. package/dist/lib/tool-call-rescue.js.map +1 -0
  494. package/dist/lib/tool-capability.d.ts +13 -0
  495. package/dist/lib/tool-capability.d.ts.map +1 -0
  496. package/dist/lib/tool-capability.js +70 -0
  497. package/dist/lib/tool-capability.js.map +1 -0
  498. package/dist/lib/tool-validate.d.ts +44 -0
  499. package/dist/lib/tool-validate.d.ts.map +1 -0
  500. package/dist/lib/tool-validate.js +165 -0
  501. package/dist/lib/tool-validate.js.map +1 -0
  502. package/dist/lib/ttfb-budget.d.ts +13 -0
  503. package/dist/lib/ttfb-budget.d.ts.map +1 -0
  504. package/dist/lib/ttfb-budget.js +153 -0
  505. package/dist/lib/ttfb-budget.js.map +1 -0
  506. package/dist/lib/url-guard.d.ts +47 -0
  507. package/dist/lib/url-guard.d.ts.map +1 -0
  508. package/dist/lib/url-guard.js +229 -0
  509. package/dist/lib/url-guard.js.map +1 -0
  510. package/dist/lib/wake-detect.d.ts +12 -0
  511. package/dist/lib/wake-detect.d.ts.map +1 -0
  512. package/dist/lib/wake-detect.js +99 -0
  513. package/dist/lib/wake-detect.js.map +1 -0
  514. package/dist/middleware/errorHandler.d.ts +3 -0
  515. package/dist/middleware/errorHandler.d.ts.map +1 -0
  516. package/dist/middleware/errorHandler.js +45 -0
  517. package/dist/middleware/errorHandler.js.map +1 -0
  518. package/dist/middleware/rateLimit.d.ts +4 -0
  519. package/dist/middleware/rateLimit.d.ts.map +1 -0
  520. package/dist/middleware/rateLimit.js +125 -0
  521. package/dist/middleware/rateLimit.js.map +1 -0
  522. package/dist/middleware/requireAuth.d.ts +3 -0
  523. package/dist/middleware/requireAuth.d.ts.map +1 -0
  524. package/dist/middleware/requireAuth.js +17 -0
  525. package/dist/middleware/requireAuth.js.map +1 -0
  526. package/dist/providers/aclide.d.ts +18 -0
  527. package/dist/providers/aclide.d.ts.map +1 -0
  528. package/dist/providers/aclide.js +174 -0
  529. package/dist/providers/aclide.js.map +1 -0
  530. package/dist/providers/aihorde.d.ts +42 -0
  531. package/dist/providers/aihorde.d.ts.map +1 -0
  532. package/dist/providers/aihorde.js +192 -0
  533. package/dist/providers/aihorde.js.map +1 -0
  534. package/dist/providers/airforce.d.ts +11 -0
  535. package/dist/providers/airforce.d.ts.map +1 -0
  536. package/dist/providers/airforce.js +32 -0
  537. package/dist/providers/airforce.js.map +1 -0
  538. package/dist/providers/base.d.ts +177 -0
  539. package/dist/providers/base.d.ts.map +1 -0
  540. package/dist/providers/base.js +354 -0
  541. package/dist/providers/base.js.map +1 -0
  542. package/dist/providers/blaze.d.ts +11 -0
  543. package/dist/providers/blaze.d.ts.map +1 -0
  544. package/dist/providers/blaze.js +33 -0
  545. package/dist/providers/blaze.js.map +1 -0
  546. package/dist/providers/blockrun.d.ts +14 -0
  547. package/dist/providers/blockrun.d.ts.map +1 -0
  548. package/dist/providers/blockrun.js +36 -0
  549. package/dist/providers/blockrun.js.map +1 -0
  550. package/dist/providers/clod.d.ts +11 -0
  551. package/dist/providers/clod.d.ts.map +1 -0
  552. package/dist/providers/clod.js +39 -0
  553. package/dist/providers/clod.js.map +1 -0
  554. package/dist/providers/cloudflare.d.ts +15 -0
  555. package/dist/providers/cloudflare.d.ts.map +1 -0
  556. package/dist/providers/cloudflare.js +175 -0
  557. package/dist/providers/cloudflare.js.map +1 -0
  558. package/dist/providers/cohere.d.ts +11 -0
  559. package/dist/providers/cohere.d.ts.map +1 -0
  560. package/dist/providers/cohere.js +117 -0
  561. package/dist/providers/cohere.js.map +1 -0
  562. package/dist/providers/dreamprompting.d.ts +11 -0
  563. package/dist/providers/dreamprompting.d.ts.map +1 -0
  564. package/dist/providers/dreamprompting.js +41 -0
  565. package/dist/providers/dreamprompting.js.map +1 -0
  566. package/dist/providers/electronhub.d.ts +10 -0
  567. package/dist/providers/electronhub.d.ts.map +1 -0
  568. package/dist/providers/electronhub.js +56 -0
  569. package/dist/providers/electronhub.js.map +1 -0
  570. package/dist/providers/experiential.d.ts +10 -0
  571. package/dist/providers/experiential.d.ts.map +1 -0
  572. package/dist/providers/experiential.js +33 -0
  573. package/dist/providers/experiential.js.map +1 -0
  574. package/dist/providers/gizmo.d.ts +15 -0
  575. package/dist/providers/gizmo.d.ts.map +1 -0
  576. package/dist/providers/gizmo.js +54 -0
  577. package/dist/providers/gizmo.js.map +1 -0
  578. package/dist/providers/google.d.ts +30 -0
  579. package/dist/providers/google.d.ts.map +1 -0
  580. package/dist/providers/google.js +766 -0
  581. package/dist/providers/google.js.map +1 -0
  582. package/dist/providers/index.d.ts +13 -0
  583. package/dist/providers/index.d.ts.map +1 -0
  584. package/dist/providers/index.js +561 -0
  585. package/dist/providers/index.js.map +1 -0
  586. package/dist/providers/llmtr.d.ts +15 -0
  587. package/dist/providers/llmtr.d.ts.map +1 -0
  588. package/dist/providers/llmtr.js +51 -0
  589. package/dist/providers/llmtr.js.map +1 -0
  590. package/dist/providers/logfare.d.ts +11 -0
  591. package/dist/providers/logfare.d.ts.map +1 -0
  592. package/dist/providers/logfare.js +33 -0
  593. package/dist/providers/logfare.js.map +1 -0
  594. package/dist/providers/lucidity.d.ts +11 -0
  595. package/dist/providers/lucidity.d.ts.map +1 -0
  596. package/dist/providers/lucidity.js +37 -0
  597. package/dist/providers/lucidity.js.map +1 -0
  598. package/dist/providers/modelscope.d.ts +50 -0
  599. package/dist/providers/modelscope.d.ts.map +1 -0
  600. package/dist/providers/modelscope.js +143 -0
  601. package/dist/providers/modelscope.js.map +1 -0
  602. package/dist/providers/moondream.d.ts +17 -0
  603. package/dist/providers/moondream.d.ts.map +1 -0
  604. package/dist/providers/moondream.js +140 -0
  605. package/dist/providers/moondream.js.map +1 -0
  606. package/dist/providers/openai-compat.d.ts +140 -0
  607. package/dist/providers/openai-compat.d.ts.map +1 -0
  608. package/dist/providers/openai-compat.js +537 -0
  609. package/dist/providers/openai-compat.js.map +1 -0
  610. package/dist/providers/opencode-free.d.ts +24 -0
  611. package/dist/providers/opencode-free.d.ts.map +1 -0
  612. package/dist/providers/opencode-free.js +262 -0
  613. package/dist/providers/opencode-free.js.map +1 -0
  614. package/dist/providers/pollinations.d.ts +32 -0
  615. package/dist/providers/pollinations.d.ts.map +1 -0
  616. package/dist/providers/pollinations.js +65 -0
  617. package/dist/providers/pollinations.js.map +1 -0
  618. package/dist/providers/router9.d.ts +16 -0
  619. package/dist/providers/router9.d.ts.map +1 -0
  620. package/dist/providers/router9.js +42 -0
  621. package/dist/providers/router9.js.map +1 -0
  622. package/dist/providers/sail.d.ts +44 -0
  623. package/dist/providers/sail.d.ts.map +1 -0
  624. package/dist/providers/sail.js +302 -0
  625. package/dist/providers/sail.js.map +1 -0
  626. package/dist/providers/septor.d.ts +10 -0
  627. package/dist/providers/septor.d.ts.map +1 -0
  628. package/dist/providers/septor.js +36 -0
  629. package/dist/providers/septor.js.map +1 -0
  630. package/dist/providers/speechify.d.ts +14 -0
  631. package/dist/providers/speechify.d.ts.map +1 -0
  632. package/dist/providers/speechify.js +28 -0
  633. package/dist/providers/speechify.js.map +1 -0
  634. package/dist/providers/speka.d.ts +12 -0
  635. package/dist/providers/speka.d.ts.map +1 -0
  636. package/dist/providers/speka.js +37 -0
  637. package/dist/providers/speka.js.map +1 -0
  638. package/dist/providers/waterfall.d.ts +11 -0
  639. package/dist/providers/waterfall.d.ts.map +1 -0
  640. package/dist/providers/waterfall.js +33 -0
  641. package/dist/providers/waterfall.js.map +1 -0
  642. package/dist/providers/xkiro.d.ts +71 -0
  643. package/dist/providers/xkiro.d.ts.map +1 -0
  644. package/dist/providers/xkiro.js +119 -0
  645. package/dist/providers/xkiro.js.map +1 -0
  646. package/dist/providers/zhipu.d.ts +43 -0
  647. package/dist/providers/zhipu.d.ts.map +1 -0
  648. package/dist/providers/zhipu.js +95 -0
  649. package/dist/providers/zhipu.js.map +1 -0
  650. package/dist/routes/analytics.d.ts +2 -0
  651. package/dist/routes/analytics.d.ts.map +1 -0
  652. package/dist/routes/analytics.js +738 -0
  653. package/dist/routes/analytics.js.map +1 -0
  654. package/dist/routes/anthropic.d.ts +7 -0
  655. package/dist/routes/anthropic.d.ts.map +1 -0
  656. package/dist/routes/anthropic.js +1076 -0
  657. package/dist/routes/anthropic.js.map +1 -0
  658. package/dist/routes/auth.d.ts +2 -0
  659. package/dist/routes/auth.d.ts.map +1 -0
  660. package/dist/routes/auth.js +310 -0
  661. package/dist/routes/auth.js.map +1 -0
  662. package/dist/routes/backups.d.ts +2 -0
  663. package/dist/routes/backups.d.ts.map +1 -0
  664. package/dist/routes/backups.js +127 -0
  665. package/dist/routes/backups.js.map +1 -0
  666. package/dist/routes/cache.d.ts +2 -0
  667. package/dist/routes/cache.d.ts.map +1 -0
  668. package/dist/routes/cache.js +40 -0
  669. package/dist/routes/cache.js.map +1 -0
  670. package/dist/routes/client-profiles.d.ts +2 -0
  671. package/dist/routes/client-profiles.d.ts.map +1 -0
  672. package/dist/routes/client-profiles.js +128 -0
  673. package/dist/routes/client-profiles.js.map +1 -0
  674. package/dist/routes/compression.d.ts +2 -0
  675. package/dist/routes/compression.d.ts.map +1 -0
  676. package/dist/routes/compression.js +82 -0
  677. package/dist/routes/compression.js.map +1 -0
  678. package/dist/routes/conversations.d.ts +3 -0
  679. package/dist/routes/conversations.d.ts.map +1 -0
  680. package/dist/routes/conversations.js +215 -0
  681. package/dist/routes/conversations.js.map +1 -0
  682. package/dist/routes/docs.d.ts +2 -0
  683. package/dist/routes/docs.d.ts.map +1 -0
  684. package/dist/routes/docs.js +21 -0
  685. package/dist/routes/docs.js.map +1 -0
  686. package/dist/routes/embeddings.d.ts +2 -0
  687. package/dist/routes/embeddings.d.ts.map +1 -0
  688. package/dist/routes/embeddings.js +255 -0
  689. package/dist/routes/embeddings.js.map +1 -0
  690. package/dist/routes/fallback.d.ts +2 -0
  691. package/dist/routes/fallback.d.ts.map +1 -0
  692. package/dist/routes/fallback.js +631 -0
  693. package/dist/routes/fallback.js.map +1 -0
  694. package/dist/routes/free-tier.d.ts +22 -0
  695. package/dist/routes/free-tier.d.ts.map +1 -0
  696. package/dist/routes/free-tier.js +175 -0
  697. package/dist/routes/free-tier.js.map +1 -0
  698. package/dist/routes/gemini.d.ts +2 -0
  699. package/dist/routes/gemini.d.ts.map +1 -0
  700. package/dist/routes/gemini.js +218 -0
  701. package/dist/routes/gemini.js.map +1 -0
  702. package/dist/routes/health.d.ts +2 -0
  703. package/dist/routes/health.d.ts.map +1 -0
  704. package/dist/routes/health.js +72 -0
  705. package/dist/routes/health.js.map +1 -0
  706. package/dist/routes/keys.d.ts +17 -0
  707. package/dist/routes/keys.d.ts.map +1 -0
  708. package/dist/routes/keys.js +1787 -0
  709. package/dist/routes/keys.js.map +1 -0
  710. package/dist/routes/logs.d.ts +2 -0
  711. package/dist/routes/logs.d.ts.map +1 -0
  712. package/dist/routes/logs.js +88 -0
  713. package/dist/routes/logs.js.map +1 -0
  714. package/dist/routes/mcp.d.ts +4 -0
  715. package/dist/routes/mcp.d.ts.map +1 -0
  716. package/dist/routes/mcp.js +563 -0
  717. package/dist/routes/mcp.js.map +1 -0
  718. package/dist/routes/media.d.ts +2 -0
  719. package/dist/routes/media.d.ts.map +1 -0
  720. package/dist/routes/media.js +180 -0
  721. package/dist/routes/media.js.map +1 -0
  722. package/dist/routes/models.d.ts +2 -0
  723. package/dist/routes/models.d.ts.map +1 -0
  724. package/dist/routes/models.js +439 -0
  725. package/dist/routes/models.js.map +1 -0
  726. package/dist/routes/ollama.d.ts +7 -0
  727. package/dist/routes/ollama.d.ts.map +1 -0
  728. package/dist/routes/ollama.js +595 -0
  729. package/dist/routes/ollama.js.map +1 -0
  730. package/dist/routes/profiles.d.ts +3 -0
  731. package/dist/routes/profiles.d.ts.map +1 -0
  732. package/dist/routes/profiles.js +431 -0
  733. package/dist/routes/profiles.js.map +1 -0
  734. package/dist/routes/proxy.d.ts +32 -0
  735. package/dist/routes/proxy.d.ts.map +1 -0
  736. package/dist/routes/proxy.js +2793 -0
  737. package/dist/routes/proxy.js.map +1 -0
  738. package/dist/routes/responses.d.ts +1161 -0
  739. package/dist/routes/responses.d.ts.map +1 -0
  740. package/dist/routes/responses.js +1521 -0
  741. package/dist/routes/responses.js.map +1 -0
  742. package/dist/routes/settings.d.ts +2 -0
  743. package/dist/routes/settings.d.ts.map +1 -0
  744. package/dist/routes/settings.js +549 -0
  745. package/dist/routes/settings.js.map +1 -0
  746. package/dist/routes/status.d.ts +3 -0
  747. package/dist/routes/status.d.ts.map +1 -0
  748. package/dist/routes/status.js +214 -0
  749. package/dist/routes/status.js.map +1 -0
  750. package/dist/routes/update.d.ts +31 -0
  751. package/dist/routes/update.d.ts.map +1 -0
  752. package/dist/routes/update.js +536 -0
  753. package/dist/routes/update.js.map +1 -0
  754. package/dist/routes/url-tokens.d.ts +2 -0
  755. package/dist/routes/url-tokens.d.ts.map +1 -0
  756. package/dist/routes/url-tokens.js +31 -0
  757. package/dist/routes/url-tokens.js.map +1 -0
  758. package/dist/scripts/export-catalog.d.ts +2 -0
  759. package/dist/scripts/export-catalog.d.ts.map +1 -0
  760. package/dist/scripts/export-catalog.js +114 -0
  761. package/dist/scripts/export-catalog.js.map +1 -0
  762. package/dist/scripts/rotate-encryption-key.d.ts +81 -0
  763. package/dist/scripts/rotate-encryption-key.d.ts.map +1 -0
  764. package/dist/scripts/rotate-encryption-key.js +232 -0
  765. package/dist/scripts/rotate-encryption-key.js.map +1 -0
  766. package/dist/scripts/routing-sim.d.ts +2 -0
  767. package/dist/scripts/routing-sim.d.ts.map +1 -0
  768. package/dist/scripts/routing-sim.js +130 -0
  769. package/dist/scripts/routing-sim.js.map +1 -0
  770. package/dist/scripts/test-all-models.d.ts +2 -0
  771. package/dist/scripts/test-all-models.d.ts.map +1 -0
  772. package/dist/scripts/test-all-models.js +56 -0
  773. package/dist/scripts/test-all-models.js.map +1 -0
  774. package/dist/services/anthropic-map.d.ts +39 -0
  775. package/dist/services/anthropic-map.d.ts.map +1 -0
  776. package/dist/services/anthropic-map.js +138 -0
  777. package/dist/services/anthropic-map.js.map +1 -0
  778. package/dist/services/auth.d.ts +30 -0
  779. package/dist/services/auth.d.ts.map +1 -0
  780. package/dist/services/auth.js +124 -0
  781. package/dist/services/auth.js.map +1 -0
  782. package/dist/services/auto-discover.d.ts +77 -0
  783. package/dist/services/auto-discover.d.ts.map +1 -0
  784. package/dist/services/auto-discover.js +1047 -0
  785. package/dist/services/auto-discover.js.map +1 -0
  786. package/dist/services/backups.d.ts +67 -0
  787. package/dist/services/backups.d.ts.map +1 -0
  788. package/dist/services/backups.js +444 -0
  789. package/dist/services/backups.js.map +1 -0
  790. package/dist/services/builtin-model-discovery.d.ts +107 -0
  791. package/dist/services/builtin-model-discovery.d.ts.map +1 -0
  792. package/dist/services/builtin-model-discovery.js +296 -0
  793. package/dist/services/builtin-model-discovery.js.map +1 -0
  794. package/dist/services/cache.d.ts +190 -0
  795. package/dist/services/cache.d.ts.map +1 -0
  796. package/dist/services/cache.js +617 -0
  797. package/dist/services/cache.js.map +1 -0
  798. package/dist/services/catalog-sync.d.ts +257 -0
  799. package/dist/services/catalog-sync.d.ts.map +1 -0
  800. package/dist/services/catalog-sync.js +1041 -0
  801. package/dist/services/catalog-sync.js.map +1 -0
  802. package/dist/services/compression/config.d.ts +33 -0
  803. package/dist/services/compression/config.d.ts.map +1 -0
  804. package/dist/services/compression/config.js +158 -0
  805. package/dist/services/compression/config.js.map +1 -0
  806. package/dist/services/compression/engines/aging.d.ts +2 -0
  807. package/dist/services/compression/engines/aging.d.ts.map +1 -0
  808. package/dist/services/compression/engines/aging.js +53 -0
  809. package/dist/services/compression/engines/aging.js.map +1 -0
  810. package/dist/services/compression/engines/custom-filters.d.ts +4 -0
  811. package/dist/services/compression/engines/custom-filters.d.ts.map +1 -0
  812. package/dist/services/compression/engines/custom-filters.js +49 -0
  813. package/dist/services/compression/engines/custom-filters.js.map +1 -0
  814. package/dist/services/compression/engines/dedup.d.ts +2 -0
  815. package/dist/services/compression/engines/dedup.d.ts.map +1 -0
  816. package/dist/services/compression/engines/dedup.js +61 -0
  817. package/dist/services/compression/engines/dedup.js.map +1 -0
  818. package/dist/services/compression/engines/filter-definitions.d.ts +75 -0
  819. package/dist/services/compression/engines/filter-definitions.d.ts.map +1 -0
  820. package/dist/services/compression/engines/filter-definitions.js +45 -0
  821. package/dist/services/compression/engines/filter-definitions.js.map +1 -0
  822. package/dist/services/compression/engines/hard-budget.d.ts +2 -0
  823. package/dist/services/compression/engines/hard-budget.d.ts.map +1 -0
  824. package/dist/services/compression/engines/hard-budget.js +52 -0
  825. package/dist/services/compression/engines/hard-budget.js.map +1 -0
  826. package/dist/services/compression/engines/index.d.ts +9 -0
  827. package/dist/services/compression/engines/index.d.ts.map +1 -0
  828. package/dist/services/compression/engines/index.js +9 -0
  829. package/dist/services/compression/engines/index.js.map +1 -0
  830. package/dist/services/compression/engines/jsoncompact.d.ts +3 -0
  831. package/dist/services/compression/engines/jsoncompact.d.ts.map +1 -0
  832. package/dist/services/compression/engines/jsoncompact.js +145 -0
  833. package/dist/services/compression/engines/jsoncompact.js.map +1 -0
  834. package/dist/services/compression/engines/lite.d.ts +2 -0
  835. package/dist/services/compression/engines/lite.d.ts.map +1 -0
  836. package/dist/services/compression/engines/lite.js +34 -0
  837. package/dist/services/compression/engines/lite.js.map +1 -0
  838. package/dist/services/compression/engines/read-lifecycle.d.ts +2 -0
  839. package/dist/services/compression/engines/read-lifecycle.d.ts.map +1 -0
  840. package/dist/services/compression/engines/read-lifecycle.js +70 -0
  841. package/dist/services/compression/engines/read-lifecycle.js.map +1 -0
  842. package/dist/services/compression/engines/relevance.d.ts +2 -0
  843. package/dist/services/compression/engines/relevance.d.ts.map +1 -0
  844. package/dist/services/compression/engines/relevance.js +74 -0
  845. package/dist/services/compression/engines/relevance.js.map +1 -0
  846. package/dist/services/compression/engines/toolfilter.d.ts +2 -0
  847. package/dist/services/compression/engines/toolfilter.d.ts.map +1 -0
  848. package/dist/services/compression/engines/toolfilter.js +195 -0
  849. package/dist/services/compression/engines/toolfilter.js.map +1 -0
  850. package/dist/services/compression/fidelity-gate.d.ts +12 -0
  851. package/dist/services/compression/fidelity-gate.d.ts.map +1 -0
  852. package/dist/services/compression/fidelity-gate.js +90 -0
  853. package/dist/services/compression/fidelity-gate.js.map +1 -0
  854. package/dist/services/compression/helpers.d.ts +9 -0
  855. package/dist/services/compression/helpers.d.ts.map +1 -0
  856. package/dist/services/compression/helpers.js +52 -0
  857. package/dist/services/compression/helpers.js.map +1 -0
  858. package/dist/services/compression/pipeline.d.ts +7 -0
  859. package/dist/services/compression/pipeline.d.ts.map +1 -0
  860. package/dist/services/compression/pipeline.js +245 -0
  861. package/dist/services/compression/pipeline.js.map +1 -0
  862. package/dist/services/compression/preservation.d.ts +27 -0
  863. package/dist/services/compression/preservation.d.ts.map +1 -0
  864. package/dist/services/compression/preservation.js +109 -0
  865. package/dist/services/compression/preservation.js.map +1 -0
  866. package/dist/services/compression/registry.d.ts +6 -0
  867. package/dist/services/compression/registry.d.ts.map +1 -0
  868. package/dist/services/compression/registry.js +16 -0
  869. package/dist/services/compression/registry.js.map +1 -0
  870. package/dist/services/compression/stats.d.ts +29 -0
  871. package/dist/services/compression/stats.d.ts.map +1 -0
  872. package/dist/services/compression/stats.js +55 -0
  873. package/dist/services/compression/stats.js.map +1 -0
  874. package/dist/services/compression/types.d.ts +90 -0
  875. package/dist/services/compression/types.d.ts.map +1 -0
  876. package/dist/services/compression/types.js +2 -0
  877. package/dist/services/compression/types.js.map +1 -0
  878. package/dist/services/context-handoff.d.ts +22 -0
  879. package/dist/services/context-handoff.d.ts.map +1 -0
  880. package/dist/services/context-handoff.js +164 -0
  881. package/dist/services/context-handoff.js.map +1 -0
  882. package/dist/services/cooldown-probe.d.ts +24 -0
  883. package/dist/services/cooldown-probe.d.ts.map +1 -0
  884. package/dist/services/cooldown-probe.js +181 -0
  885. package/dist/services/cooldown-probe.js.map +1 -0
  886. package/dist/services/custom-endpoint.d.ts +58 -0
  887. package/dist/services/custom-endpoint.d.ts.map +1 -0
  888. package/dist/services/custom-endpoint.js +167 -0
  889. package/dist/services/custom-endpoint.js.map +1 -0
  890. package/dist/services/custom-media-register.d.ts +22 -0
  891. package/dist/services/custom-media-register.d.ts.map +1 -0
  892. package/dist/services/custom-media-register.js +45 -0
  893. package/dist/services/custom-media-register.js.map +1 -0
  894. package/dist/services/custom-model-register.d.ts +50 -0
  895. package/dist/services/custom-model-register.d.ts.map +1 -0
  896. package/dist/services/custom-model-register.js +103 -0
  897. package/dist/services/custom-model-register.js.map +1 -0
  898. package/dist/services/custom-model-seed.d.ts +18 -0
  899. package/dist/services/custom-model-seed.d.ts.map +1 -0
  900. package/dist/services/custom-model-seed.js +42 -0
  901. package/dist/services/custom-model-seed.js.map +1 -0
  902. package/dist/services/custom-model-sync.d.ts +37 -0
  903. package/dist/services/custom-model-sync.d.ts.map +1 -0
  904. package/dist/services/custom-model-sync.js +118 -0
  905. package/dist/services/custom-model-sync.js.map +1 -0
  906. package/dist/services/custom-model-tombstone.d.ts +5 -0
  907. package/dist/services/custom-model-tombstone.d.ts.map +1 -0
  908. package/dist/services/custom-model-tombstone.js +30 -0
  909. package/dist/services/custom-model-tombstone.js.map +1 -0
  910. package/dist/services/declarative-config.d.ts +356 -0
  911. package/dist/services/declarative-config.d.ts.map +1 -0
  912. package/dist/services/declarative-config.js +427 -0
  913. package/dist/services/declarative-config.js.map +1 -0
  914. package/dist/services/degradation.d.ts +40 -0
  915. package/dist/services/degradation.d.ts.map +1 -0
  916. package/dist/services/degradation.js +119 -0
  917. package/dist/services/degradation.js.map +1 -0
  918. package/dist/services/embeddings.d.ts +76 -0
  919. package/dist/services/embeddings.d.ts.map +1 -0
  920. package/dist/services/embeddings.js +319 -0
  921. package/dist/services/embeddings.js.map +1 -0
  922. package/dist/services/fusion.d.ts +162 -0
  923. package/dist/services/fusion.d.ts.map +1 -0
  924. package/dist/services/fusion.js +789 -0
  925. package/dist/services/fusion.js.map +1 -0
  926. package/dist/services/gemini-map.d.ts +14 -0
  927. package/dist/services/gemini-map.d.ts.map +1 -0
  928. package/dist/services/gemini-map.js +78 -0
  929. package/dist/services/gemini-map.js.map +1 -0
  930. package/dist/services/health.d.ts +52 -0
  931. package/dist/services/health.d.ts.map +1 -0
  932. package/dist/services/health.js +352 -0
  933. package/dist/services/health.js.map +1 -0
  934. package/dist/services/idempotency.d.ts +49 -0
  935. package/dist/services/idempotency.d.ts.map +1 -0
  936. package/dist/services/idempotency.js +140 -0
  937. package/dist/services/idempotency.js.map +1 -0
  938. package/dist/services/key-budget.d.ts +74 -0
  939. package/dist/services/key-budget.d.ts.map +1 -0
  940. package/dist/services/key-budget.js +200 -0
  941. package/dist/services/key-budget.js.map +1 -0
  942. package/dist/services/media.d.ts +123 -0
  943. package/dist/services/media.d.ts.map +1 -0
  944. package/dist/services/media.js +954 -0
  945. package/dist/services/media.js.map +1 -0
  946. package/dist/services/model-discovery.d.ts +126 -0
  947. package/dist/services/model-discovery.d.ts.map +1 -0
  948. package/dist/services/model-discovery.js +662 -0
  949. package/dist/services/model-discovery.js.map +1 -0
  950. package/dist/services/model-groups.d.ts +149 -0
  951. package/dist/services/model-groups.d.ts.map +1 -0
  952. package/dist/services/model-groups.js +314 -0
  953. package/dist/services/model-groups.js.map +1 -0
  954. package/dist/services/model-listing.d.ts +18 -0
  955. package/dist/services/model-listing.d.ts.map +1 -0
  956. package/dist/services/model-listing.js +94 -0
  957. package/dist/services/model-listing.js.map +1 -0
  958. package/dist/services/model-retirement.d.ts +29 -0
  959. package/dist/services/model-retirement.d.ts.map +1 -0
  960. package/dist/services/model-retirement.js +86 -0
  961. package/dist/services/model-retirement.js.map +1 -0
  962. package/dist/services/model-state.d.ts +90 -0
  963. package/dist/services/model-state.d.ts.map +1 -0
  964. package/dist/services/model-state.js +342 -0
  965. package/dist/services/model-state.js.map +1 -0
  966. package/dist/services/model-weight-overrides.d.ts +48 -0
  967. package/dist/services/model-weight-overrides.d.ts.map +1 -0
  968. package/dist/services/model-weight-overrides.js +127 -0
  969. package/dist/services/model-weight-overrides.js.map +1 -0
  970. package/dist/services/notification-emitter.d.ts +50 -0
  971. package/dist/services/notification-emitter.d.ts.map +1 -0
  972. package/dist/services/notification-emitter.js +177 -0
  973. package/dist/services/notification-emitter.js.map +1 -0
  974. package/dist/services/opencode-free-sync.d.ts +29 -0
  975. package/dist/services/opencode-free-sync.d.ts.map +1 -0
  976. package/dist/services/opencode-free-sync.js +206 -0
  977. package/dist/services/opencode-free-sync.js.map +1 -0
  978. package/dist/services/penalty-inspector.d.ts +56 -0
  979. package/dist/services/penalty-inspector.d.ts.map +1 -0
  980. package/dist/services/penalty-inspector.js +167 -0
  981. package/dist/services/penalty-inspector.js.map +1 -0
  982. package/dist/services/profile-models.d.ts +5 -0
  983. package/dist/services/profile-models.d.ts.map +1 -0
  984. package/dist/services/profile-models.js +62 -0
  985. package/dist/services/profile-models.js.map +1 -0
  986. package/dist/services/provider-credential.d.ts +19 -0
  987. package/dist/services/provider-credential.d.ts.map +1 -0
  988. package/dist/services/provider-credential.js +51 -0
  989. package/dist/services/provider-credential.js.map +1 -0
  990. package/dist/services/provider-quota.d.ts +64 -0
  991. package/dist/services/provider-quota.d.ts.map +1 -0
  992. package/dist/services/provider-quota.js +563 -0
  993. package/dist/services/provider-quota.js.map +1 -0
  994. package/dist/services/quirks.d.ts +0 -0
  995. package/dist/services/quirks.d.ts.map +1 -0
  996. package/dist/services/quirks.js +0 -0
  997. package/dist/services/quirks.js.map +1 -0
  998. package/dist/services/quota-forecast.d.ts +50 -0
  999. package/dist/services/quota-forecast.d.ts.map +1 -0
  1000. package/dist/services/quota-forecast.js +150 -0
  1001. package/dist/services/quota-forecast.js.map +1 -0
  1002. package/dist/services/quota-outlook.d.ts +4 -0
  1003. package/dist/services/quota-outlook.d.ts.map +1 -0
  1004. package/dist/services/quota-outlook.js +92 -0
  1005. package/dist/services/quota-outlook.js.map +1 -0
  1006. package/dist/services/ratelimit.d.ts +249 -0
  1007. package/dist/services/ratelimit.d.ts.map +1 -0
  1008. package/dist/services/ratelimit.js +1182 -0
  1009. package/dist/services/ratelimit.js.map +1 -0
  1010. package/dist/services/request-retention.d.ts +60 -0
  1011. package/dist/services/request-retention.d.ts.map +1 -0
  1012. package/dist/services/request-retention.js +229 -0
  1013. package/dist/services/request-retention.js.map +1 -0
  1014. package/dist/services/router.d.ts +384 -0
  1015. package/dist/services/router.d.ts.map +1 -0
  1016. package/dist/services/router.js +1986 -0
  1017. package/dist/services/router.js.map +1 -0
  1018. package/dist/services/scoring.d.ts +156 -0
  1019. package/dist/services/scoring.d.ts.map +1 -0
  1020. package/dist/services/scoring.js +435 -0
  1021. package/dist/services/scoring.js.map +1 -0
  1022. package/dist/services/url-tokens.d.ts +15 -0
  1023. package/dist/services/url-tokens.d.ts.map +1 -0
  1024. package/dist/services/url-tokens.js +55 -0
  1025. package/dist/services/url-tokens.js.map +1 -0
  1026. package/package.json +77 -4
  1027. package/README.md +0 -3
@@ -0,0 +1,738 @@
1
+ import { Router } from 'express';
2
+ import { getDb } from '../db/index.js';
3
+ import { FALLBACK_INPUT_PER_M, FALLBACK_OUTPUT_PER_M } from '../db/model-pricing.js';
4
+ import { providerIdFor, providerDisplayName } from '../lib/provider-identity.js';
5
+ import { getLiveRoutes } from '../lib/route-live.js';
6
+ import { normalizeBaseUrl } from '../lib/endpoint-scope.js';
7
+ export const analyticsRouter = Router();
8
+ // The dashboard's live routing strip: what the router is serving right now and
9
+ // what it finished most recently. Backed by the in-memory tracker in
10
+ // lib/route-live (fed by traceRouteEvent), so a poll costs no SQL and the
11
+ // state resets honestly with the process.
12
+ analyticsRouter.get('/live', (_req, res) => {
13
+ res.json(getLiveRoutes());
14
+ });
15
+ // The endpoint identity of a request, in SQL: the serving key's base_url, ''
16
+ // when the key is gone or carries none (every catalog key). Custom endpoints
17
+ // all share the platform id 'custom' (services/custom-endpoint.ts), so any
18
+ // view that groups by `platform` alone collapses every relay into one row and
19
+ // the operator cannot tell which endpoint did what (#889) — this expression is
20
+ // the second half of the grouping key everywhere that matters.
21
+ //
22
+ // rtrim/trim mirror lib/endpoint-scope.normalizeBaseUrl so the SQL side agrees
23
+ // with the ids providerIdFor() builds: keys.ts normalizes base_url on write,
24
+ // but rows stored before it did would otherwise split one endpoint in two.
25
+ // Requires the query to LEFT JOIN api_keys as `k`.
26
+ const ENDPOINT_ID_SQL = "COALESCE(rtrim(trim(k.base_url), '/'), '')";
27
+ // Format UTC timestamps the same way SQLite stores created_at text values.
28
+ const toSqliteDateTime = (timestamp) => new Date(timestamp).toISOString().slice(0, 19).replace('T', ' ');
29
+ // Return the rolling cutoff timestamp for the selected analytics range.
30
+ function getSinceTimestamp(range) {
31
+ const now = Date.now();
32
+ switch (range) {
33
+ case '24h':
34
+ return toSqliteDateTime(now - 24 * 60 * 60 * 1000);
35
+ case '30d':
36
+ return toSqliteDateTime(now - 30 * 24 * 60 * 60 * 1000);
37
+ case '90d':
38
+ return toSqliteDateTime(now - 90 * 24 * 60 * 60 * 1000);
39
+ case '7d':
40
+ default:
41
+ return toSqliteDateTime(now - 7 * 24 * 60 * 60 * 1000);
42
+ }
43
+ }
44
+ // Range-based window read from the durable `request_hourly` aggregate. The raw
45
+ // `requests` table is pruned by REQUEST_ANALYTICS_MAX_ROWS, so any analytics
46
+ // count that depends on a >=7d window must read from the hourly table to stay
47
+ // accurate. Hourly resolution is fine for any UI range the dashboard exposes.
48
+ function readAggregateSince(since) {
49
+ const db = getDb();
50
+ // Hour keys are created_at truncated to the hour, so they share SQLite's
51
+ // canonical 'YYYY-MM-DD HH:00:00' text (space separator). The range cutoff is
52
+ // already in that format — floor it to the hour and compare the strings
53
+ // directly. No separator conversion: the writer (logRequest) and the timeline
54
+ // reader both compare on the space form, so this must too.
55
+ const aggregateSince = since.slice(0, 13) + ':00:00';
56
+ const rows = db.prepare(`
57
+ SELECT
58
+ COALESCE(SUM(total_requests), 0) as total_requests,
59
+ COALESCE(SUM(success_count), 0) as success_count,
60
+ COALESCE(SUM(error_count), 0) as error_count,
61
+ COALESCE(SUM(input_tokens), 0) as total_input_tokens,
62
+ COALESCE(SUM(output_tokens), 0) as total_output_tokens,
63
+ MIN(hour) as first_request_at
64
+ FROM request_hourly
65
+ WHERE hour >= ?
66
+ `).get(aggregateSince);
67
+ return rows;
68
+ }
69
+ function readLifetimeSettings() {
70
+ const db = getDb();
71
+ const row = db.prepare(`
72
+ SELECT value FROM settings WHERE key = 'first_request_at'
73
+ `).get();
74
+ return row?.value ?? null;
75
+ }
76
+ // Summary stats
77
+ analyticsRouter.get('/summary', (req, res) => {
78
+ const range = req.query.range ?? '7d';
79
+ const since = getSinceTimestamp(range);
80
+ const db = getDb();
81
+ // Totals (request count, token sums, success rate, lifetime first_request_at)
82
+ // come from the durable `request_hourly` aggregate so they stay accurate even
83
+ // after the raw `requests` table is pruned. Per-model pin-honor rate and
84
+ // estimated savings still need the raw table because they're broken down by
85
+ // (platform, model_id); for those we fall back to the raw rows but they're
86
+ // only reported for ranges where recent activity still exists. The aggregate
87
+ // is the source of truth for headline numbers.
88
+ const aggregate = readAggregateSince(since);
89
+ const totalRequests = aggregate.total_requests ?? 0;
90
+ // Success rate over success+error only: a 'canceled' request (#752 — client
91
+ // hung up) still counts in the totals but is neither a success nor a
92
+ // failure, so it must not dilute the rate.
93
+ const decidedRequests = (aggregate.success_count ?? 0) + (aggregate.error_count ?? 0);
94
+ const successRate = decidedRequests > 0 ? (aggregate.success_count / decidedRequests) * 100 : 0;
95
+ // Avg latency is only meaningful at the raw row level; the hourly bucket
96
+ // doesn't preserve it. Fall back to a 0/null when no recent raw rows exist.
97
+ const latencyRow = db.prepare(`
98
+ SELECT AVG(latency_ms) as avg_latency_ms FROM requests WHERE created_at >= ?
99
+ `).get(since);
100
+ // Estimated savings is a per-request priced value, so it lives on the raw
101
+ // rows. For ranges where the raw table is empty we report 0 (no recent
102
+ // activity to price).
103
+ const savings = db.prepare(`
104
+ SELECT COALESCE(SUM(
105
+ CASE WHEN r.status = 'success' THEN
106
+ r.input_tokens * COALESCE(m.paid_input_per_m, ?) / 1000000.0 +
107
+ r.output_tokens * COALESCE(m.paid_output_per_m, ?) / 1000000.0
108
+ ELSE 0 END
109
+ ), 0) as est_savings
110
+ FROM requests r
111
+ LEFT JOIN models m ON m.platform = r.platform AND m.model_id = r.model_id
112
+ WHERE r.created_at >= ?
113
+ `).get(FALLBACK_INPUT_PER_M, FALLBACK_OUTPUT_PER_M, since);
114
+ // Pin-honor stats are also raw-row scoped. We still report them when present
115
+ // (typically 24h/7d) and gracefully drop them when the raw window is empty.
116
+ const pinRow = db.prepare(`
117
+ SELECT
118
+ SUM(CASE WHEN requested_model IS NOT NULL THEN 1 ELSE 0 END) as pinned_count,
119
+ SUM(CASE WHEN requested_model = model_id THEN 1 ELSE 0 END) as pin_honored_count
120
+ FROM requests WHERE created_at >= ?
121
+ `).get(since);
122
+ // Latency percentiles, time-to-first-token, and the chat/embedding split all
123
+ // live on the raw rows (the hourly aggregate keeps neither latency nor a
124
+ // per-type breakdown). When the raw window is empty (older than the prune
125
+ // horizon) we report null, not 0, so the UI can render a placeholder instead
126
+ // of a misleading zero. Percentiles use nearest-rank via ORDER BY/OFFSET.
127
+ // Only rows that actually recorded a latency participate in the percentile.
128
+ // The IS NOT NULL guard must be on BOTH the offset-denominator count and the
129
+ // ordered selection so they range over the same set: a NULL sorts first under
130
+ // ORDER BY latency_ms ASC, so if it were counted but not filtered the offset
131
+ // math would shift and a NULL could be selected (rendered as 0).
132
+ const rawCount = db.prepare(`SELECT COUNT(*) as c FROM requests WHERE created_at >= ? AND latency_ms IS NOT NULL`).get(since).c;
133
+ const percentileAt = (fraction) => {
134
+ if (rawCount === 0)
135
+ return null;
136
+ const offset = Math.floor((rawCount - 1) * fraction);
137
+ const row = db.prepare(`
138
+ SELECT latency_ms FROM requests
139
+ WHERE created_at >= ? AND latency_ms IS NOT NULL
140
+ ORDER BY latency_ms ASC
141
+ LIMIT 1 OFFSET ?
142
+ `).get(since, offset);
143
+ return row ? Math.round(row.latency_ms) : null;
144
+ };
145
+ const p50LatencyMs = percentileAt(0.5);
146
+ const p95LatencyMs = percentileAt(0.95);
147
+ const ttfbRow = db.prepare(`
148
+ SELECT AVG(ttfb_ms) as avg_ttfb_ms FROM requests
149
+ WHERE created_at >= ? AND ttfb_ms IS NOT NULL
150
+ `).get(since);
151
+ const avgTtfbMs = ttfbRow?.avg_ttfb_ms != null ? Math.round(ttfbRow.avg_ttfb_ms) : null;
152
+ const typeRows = db.prepare(`
153
+ SELECT request_type, COUNT(*) as count FROM requests
154
+ WHERE created_at >= ?
155
+ GROUP BY request_type
156
+ `).all(since);
157
+ const requestTypeCounts = { chat: 0, embedding: 0 };
158
+ for (const row of typeRows) {
159
+ if (row.request_type === 'embedding')
160
+ requestTypeCounts.embedding = row.count;
161
+ else if (row.request_type === 'chat')
162
+ requestTypeCounts.chat = row.count;
163
+ }
164
+ const lifetimeFirst = readLifetimeSettings();
165
+ res.json({
166
+ totalRequests,
167
+ successRate: Math.round(successRate * 10) / 10,
168
+ totalInputTokens: aggregate.total_input_tokens ?? 0,
169
+ totalOutputTokens: aggregate.total_output_tokens ?? 0,
170
+ avgLatencyMs: Math.round(latencyRow?.avg_latency_ms ?? 0),
171
+ // Latency spread (raw rows): p50 typical, p95 tail. Null when the raw
172
+ // window is empty.
173
+ p50LatencyMs,
174
+ p95LatencyMs,
175
+ // Average streaming time-to-first-token over rows that recorded it; null
176
+ // when none did (non-streaming traffic or pruned window).
177
+ avgTtfbMs,
178
+ // Chat vs embedding request split for the selected window.
179
+ requestTypeCounts,
180
+ estimatedCostSavings: Math.round((savings.est_savings ?? 0) * 100) / 100,
181
+ // Pinned = requests where the client named a specific model (not 'auto').
182
+ // Honored = the pinned model actually served it; the difference is
183
+ // failovers that overrode the pin.
184
+ pinnedRequests: pinRow.pinned_count ?? 0,
185
+ pinHonoredRequests: pinRow.pin_honored_count ?? 0,
186
+ // First-ever request timestamp (lifetime, never pruned). Falls back to
187
+ // the oldest hour in the current window when lifetime is not yet seeded.
188
+ firstRequestAt: lifetimeFirst ?? aggregate.first_request_at ?? null,
189
+ // Lifetime total since install — useful when the user wants to see "all
190
+ // time" alongside the selected range window. Sourced from settings so it
191
+ // survives the raw-row prune entirely.
192
+ lifetimeTotalRequests: Number(db.prepare(`SELECT value FROM settings WHERE key='total_requests'`).get()?.value ?? 0) || 0,
193
+ });
194
+ });
195
+ // Stats grouped by model.
196
+ //
197
+ // The grouping key is (platform, endpoint, model_id), not (platform, model_id):
198
+ // the same model id served by two different custom relays is two different
199
+ // things — different latency, different failure modes — and merging them into
200
+ // one row labelled "custom" is the #889 collision in its most misleading form,
201
+ // because the merged row's numbers describe neither endpoint.
202
+ //
203
+ // The models join is endpoint-scoped for the same reason. `models` is unique on
204
+ // (platform, model_id, endpoint_scope) since #651, so joining on
205
+ // (platform, model_id) alone matches ONE row per relay that registered the
206
+ // model and multiplies every request row by that count. Adding endpoint_scope
207
+ // to the ON clause picks the row belonging to the endpoint that actually served
208
+ // the request — the only one whose display name and pricing apply.
209
+ analyticsRouter.get('/by-model', (req, res) => {
210
+ const range = req.query.range ?? '7d';
211
+ const since = getSinceTimestamp(range);
212
+ const db = getDb();
213
+ const rows = db.prepare(`
214
+ SELECT
215
+ r.platform,
216
+ ${ENDPOINT_ID_SQL} as base_url,
217
+ r.model_id,
218
+ m.display_name,
219
+ COUNT(*) as requests,
220
+ -- Rate over success+error only: 'canceled' (#752) is neither.
221
+ SUM(CASE WHEN r.status = 'success' THEN 1 ELSE 0 END) * 100.0 / NULLIF(SUM(CASE WHEN r.status <> 'canceled' THEN 1 ELSE 0 END), 0) as success_rate,
222
+ AVG(r.latency_ms) as avg_latency_ms,
223
+ SUM(r.input_tokens) as total_input_tokens,
224
+ SUM(r.output_tokens) as total_output_tokens,
225
+ SUM(CASE WHEN r.requested_model = r.model_id THEN 1 ELSE 0 END) as pinned_requests,
226
+ SUM(CASE WHEN r.status = 'success' THEN
227
+ r.input_tokens * COALESCE(m.paid_input_per_m, ?) / 1000000.0 +
228
+ r.output_tokens * COALESCE(m.paid_output_per_m, ?) / 1000000.0
229
+ ELSE 0 END) as est_cost
230
+ FROM requests r
231
+ LEFT JOIN api_keys k ON k.id = r.key_id
232
+ LEFT JOIN models m
233
+ ON m.platform = r.platform AND m.model_id = r.model_id
234
+ AND m.endpoint_scope = ${ENDPOINT_ID_SQL}
235
+ WHERE r.created_at >= ?
236
+ GROUP BY r.platform, ${ENDPOINT_ID_SQL}, r.model_id
237
+ ORDER BY requests DESC
238
+ `).all(FALLBACK_INPUT_PER_M, FALLBACK_OUTPUT_PER_M, since);
239
+ res.json(rows.map(r => ({
240
+ platform: r.platform,
241
+ // Same row identity as /by-platform, so a model row and a provider row for
242
+ // one endpoint carry the same id and the same operator-readable name.
243
+ providerId: providerIdFor(r.platform, r.base_url || null),
244
+ endpoint: providerDisplayName(r.platform, r.base_url || null),
245
+ modelId: r.model_id,
246
+ displayName: r.display_name ?? r.model_id,
247
+ requests: r.requests,
248
+ // success_rate is NULL when every row in the group was canceled.
249
+ successRate: Math.round((r.success_rate ?? 0) * 10) / 10,
250
+ avgLatencyMs: Math.round(r.avg_latency_ms),
251
+ totalInputTokens: r.total_input_tokens ?? 0,
252
+ totalOutputTokens: r.total_output_tokens ?? 0,
253
+ // Requests this model served because the client pinned it by name.
254
+ pinnedRequests: r.pinned_requests ?? 0,
255
+ estimatedCost: Math.round((r.est_cost ?? 0) * 100) / 100,
256
+ })));
257
+ });
258
+ // Stats grouped by platform.
259
+ //
260
+ // Custom endpoints all share the platform id 'custom' (services/custom-
261
+ // endpoint.ts), so grouping by `platform` alone would collapse every custom
262
+ // relay into one row and the operator could not tell which endpoint did what
263
+ // (#889). We therefore also group by the serving key's base_url — the canonical
264
+ // endpoint identity (custom-endpoint.ts pools credentials by base_url, and the
265
+ // router treats every key sharing a base_url as one endpoint). Non-custom keys
266
+ // carry no base_url, so COALESCE(base_url,'') keeps each of them in a single
267
+ // per-platform group exactly as before.
268
+ analyticsRouter.get('/by-platform', (req, res) => {
269
+ const range = req.query.range ?? '7d';
270
+ const since = getSinceTimestamp(range);
271
+ const db = getDb();
272
+ const rows = db.prepare(`
273
+ SELECT
274
+ r.platform,
275
+ ${ENDPOINT_ID_SQL} as base_url,
276
+ COUNT(*) as requests,
277
+ COUNT(r.latency_ms) as latency_count,
278
+ SUM(CASE WHEN r.status = 'success' THEN 1 ELSE 0 END) * 100.0 / NULLIF(SUM(CASE WHEN r.status <> 'canceled' THEN 1 ELSE 0 END), 0) as success_rate,
279
+ AVG(r.latency_ms) as avg_latency_ms,
280
+ AVG(r.ttfb_ms) as avg_ttfb_ms,
281
+ SUM(CASE WHEN r.status = 'error' THEN 1 ELSE 0 END) as error_count,
282
+ AVG(CASE WHEN r.output_tokens > 0 AND r.latency_ms > 0
283
+ THEN r.output_tokens / (r.latency_ms / 1000.0) ELSE NULL END) as avg_tokens_per_second,
284
+ SUM(r.input_tokens) as total_input_tokens,
285
+ SUM(r.output_tokens) as total_output_tokens
286
+ FROM requests r
287
+ LEFT JOIN api_keys k ON k.id = r.key_id
288
+ WHERE r.created_at >= ?
289
+ GROUP BY r.platform, ${ENDPOINT_ID_SQL}
290
+ ORDER BY requests DESC
291
+ `).all(since);
292
+ // P95 latency is a per-group percentile; SQLite has no native percentile
293
+ // aggregate, so we take the nearest-rank value per group with a small
294
+ // ORDER BY/OFFSET query. The group count is tiny (one row per provider /
295
+ // custom endpoint), so the extra round-trips are negligible and keep the SQL
296
+ // readable. The WHERE must match the grouping exactly — platform AND the
297
+ // endpoint's base_url — or a custom endpoint's p95 would bleed in latency
298
+ // from every other custom endpoint.
299
+ const p95Stmt = db.prepare(`
300
+ SELECT r.latency_ms FROM requests r
301
+ LEFT JOIN api_keys k ON k.id = r.key_id
302
+ WHERE r.created_at >= ? AND r.platform = ? AND ${ENDPOINT_ID_SQL} = ? AND r.latency_ms IS NOT NULL
303
+ ORDER BY r.latency_ms ASC
304
+ LIMIT 1 OFFSET ?
305
+ `);
306
+ res.json(rows.map(r => {
307
+ // Offset math and the ordered selection both range over the non-null
308
+ // latency rows (latency_count), so a NULL can neither be counted into the
309
+ // denominator nor selected as the p95 value.
310
+ const latencyCount = r.latency_count ?? 0;
311
+ const baseUrl = r.base_url || null;
312
+ const p95Row = latencyCount > 0
313
+ ? p95Stmt.get(since, r.platform, r.base_url, Math.floor((latencyCount - 1) * 0.95))
314
+ : undefined;
315
+ return {
316
+ platform: r.platform,
317
+ // Stable, unique id for this row: the platform slug for catalog providers,
318
+ // 'custom:<base_url>' for custom endpoints (falls back to 'custom' when
319
+ // the key is gone). The client uses this as the chart key and the
320
+ // recent-calls filter value.
321
+ providerId: providerIdFor(r.platform, baseUrl),
322
+ // The short identifier the operator actually reads: the endpoint host for
323
+ // custom rows, the platform slug otherwise.
324
+ endpoint: providerDisplayName(r.platform, baseUrl),
325
+ requests: r.requests,
326
+ successRate: Math.round((r.success_rate ?? 0) * 10) / 10,
327
+ avgLatencyMs: Math.round(r.avg_latency_ms),
328
+ p95LatencyMs: p95Row ? Math.round(p95Row.latency_ms) : null,
329
+ avgTtfbMs: r.avg_ttfb_ms != null ? Math.round(r.avg_ttfb_ms) : null,
330
+ errorCount: r.error_count ?? 0,
331
+ avgTokensPerSecond: r.avg_tokens_per_second != null
332
+ ? Math.round(r.avg_tokens_per_second * 10) / 10
333
+ : null,
334
+ totalInputTokens: r.total_input_tokens ?? 0,
335
+ totalOutputTokens: r.total_output_tokens ?? 0,
336
+ };
337
+ }));
338
+ });
339
+ analyticsRouter.get('/by-client', (req, res) => {
340
+ const range = req.query.range ?? '7d';
341
+ const since = getSinceTimestamp(range);
342
+ const rows = getDb().prepare(`
343
+ SELECT
344
+ COALESCE(client_agent, 'unknown') AS client_agent,
345
+ COUNT(*) AS requests,
346
+ SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) * 100.0 / NULLIF(SUM(CASE WHEN status <> 'canceled' THEN 1 ELSE 0 END), 0) AS success_rate,
347
+ AVG(latency_ms) AS avg_latency_ms,
348
+ SUM(input_tokens) AS total_input_tokens,
349
+ SUM(output_tokens) AS total_output_tokens,
350
+ MAX(strftime('%Y-%m-%dT%H:%M:%SZ', created_at)) AS last_seen_at
351
+ FROM requests
352
+ WHERE created_at >= ?
353
+ GROUP BY client_agent
354
+ ORDER BY requests DESC
355
+ `).all(since);
356
+ res.json(rows.map(row => ({
357
+ clientAgent: row.client_agent,
358
+ requests: row.requests,
359
+ successRate: Math.round((row.success_rate ?? 0) * 10) / 10,
360
+ avgLatencyMs: Math.round(row.avg_latency_ms ?? 0),
361
+ totalInputTokens: row.total_input_tokens ?? 0,
362
+ totalOutputTokens: row.total_output_tokens ?? 0,
363
+ lastSeenAt: row.last_seen_at,
364
+ })));
365
+ });
366
+ // Stats grouped by API key. Raw-row scoped (the hourly aggregate has no key
367
+ // dimension), LEFT JOINed to api_keys so a request whose key was later deleted
368
+ // still shows up with a null label — the keyId is always returned.
369
+ analyticsRouter.get('/by-key', (req, res) => {
370
+ const range = req.query.range ?? '7d';
371
+ const since = getSinceTimestamp(range);
372
+ const db = getDb();
373
+ const rows = db.prepare(`
374
+ SELECT
375
+ r.key_id as key_id,
376
+ k.label as label,
377
+ k.platform as platform,
378
+ COUNT(*) as requests,
379
+ SUM(CASE WHEN r.status = 'success' THEN 1 ELSE 0 END) * 100.0 / NULLIF(SUM(CASE WHEN r.status <> 'canceled' THEN 1 ELSE 0 END), 0) as success_rate,
380
+ AVG(r.latency_ms) as avg_latency_ms,
381
+ SUM(r.input_tokens) as total_input_tokens,
382
+ SUM(r.output_tokens) as total_output_tokens
383
+ FROM requests r
384
+ LEFT JOIN api_keys k ON k.id = r.key_id
385
+ WHERE r.key_id IS NOT NULL AND r.created_at >= ?
386
+ GROUP BY r.key_id
387
+ ORDER BY requests DESC
388
+ LIMIT 50
389
+ `).all(since);
390
+ res.json(rows.map(r => ({
391
+ keyId: r.key_id,
392
+ // Null when the key row was deleted, or the empty string when the key
393
+ // exists but was never labelled; the client falls back to "Key #<id>".
394
+ label: r.label ?? null,
395
+ platform: r.platform ?? null,
396
+ requests: r.requests,
397
+ successRate: Math.round((r.success_rate ?? 0) * 10) / 10,
398
+ avgLatencyMs: Math.round(r.avg_latency_ms),
399
+ totalInputTokens: r.total_input_tokens ?? 0,
400
+ totalOutputTokens: r.total_output_tokens ?? 0,
401
+ })));
402
+ });
403
+ // Timeline data
404
+ analyticsRouter.get('/timeline', (req, res) => {
405
+ const range = req.query.range ?? '7d';
406
+ const interval = req.query.interval ?? (range === '24h' ? 'hour' : 'day');
407
+ const since = getSinceTimestamp(range);
408
+ const db = getDb();
409
+ // dateFormat is a hardcoded whitelist — never user-controlled.
410
+ const dateFormat = interval === 'hour' ? '%Y-%m-%dT%H:00:00' : '%Y-%m-%d';
411
+ // tzOffset: viewer's local offset from UTC in minutes (480 = UTC+8), sent by
412
+ // the browser so hour/day bucket boundaries follow the viewer's wall clock
413
+ // instead of UTC. Whitelisted to a sane integer range; bound as a parameter,
414
+ // never interpolated into SQL.
415
+ const rawOffset = Number(req.query.tzOffset);
416
+ const tzOffset = Number.isInteger(rawOffset) && rawOffset >= -720 && rawOffset <= 840 ? rawOffset : 0;
417
+ // The single current offset applies to the whole range (SQLite has no tz
418
+ // database), so buckets on the far side of a DST transition sit 1h off.
419
+ const tzModifier = `${tzOffset >= 0 ? '+' : '-'}${Math.abs(tzOffset)} minutes`;
420
+ // Read from request_hourly (hour-bucketed) for both 'hour' and 'day'
421
+ // intervals. Day buckets are rolled up via strftime on the hour column,
422
+ // which keeps the timeline accurate past the raw-row prune window.
423
+ const rows = db.prepare(`
424
+ SELECT
425
+ strftime(?, hour, ?) as timestamp,
426
+ SUM(total_requests) as requests,
427
+ SUM(success_count) as success_count,
428
+ SUM(error_count) as failure_count,
429
+ SUM(input_tokens) as input_tokens,
430
+ SUM(output_tokens) as output_tokens
431
+ FROM request_hourly
432
+ WHERE hour >= ?
433
+ GROUP BY timestamp
434
+ ORDER BY timestamp ASC
435
+ `).all(dateFormat, tzModifier, since);
436
+ res.json(rows.map(r => ({
437
+ timestamp: r.timestamp,
438
+ requests: r.requests,
439
+ successCount: r.success_count,
440
+ failureCount: r.failure_count,
441
+ inputTokens: r.input_tokens ?? 0,
442
+ outputTokens: r.output_tokens ?? 0,
443
+ })));
444
+ });
445
+ // Error distribution (grouped by error type and platform)
446
+ analyticsRouter.get('/error-distribution', (req, res) => {
447
+ const range = req.query.range ?? '7d';
448
+ const since = getSinceTimestamp(range);
449
+ const db = getDb();
450
+ // Group errors by category (extract the key part of the error message).
451
+ // LOWER() first: LIKE is case-sensitive in SQLite, and the error strings we
452
+ // persist are dominated by capitalized upstream messages ("Invalid API key",
453
+ // "Too Many Requests", "Timeout"), which all landed in 'Other' before. The
454
+ // old '%invalid.*key%' pattern was regex syntax pasted into LIKE, where '.'
455
+ // and '*' are literal characters, so it matched nothing real.
456
+ const rows = db.prepare(`
457
+ SELECT
458
+ platform,
459
+ model_id,
460
+ CASE
461
+ WHEN lower(error) LIKE '%429%' OR lower(error) LIKE '%rate limit%' OR lower(error) LIKE '%too many%' OR lower(error) LIKE '%quota%' THEN 'Rate Limited (429)'
462
+ WHEN lower(error) LIKE '%401%' OR lower(error) LIKE '%unauthorized%' OR lower(error) LIKE '%invalid api key%' OR lower(error) LIKE '%invalid_api_key%' OR lower(error) LIKE '%incorrect api key%' THEN 'Auth Error (401)'
463
+ WHEN lower(error) LIKE '%403%' OR lower(error) LIKE '%forbidden%' THEN 'Forbidden (403)'
464
+ WHEN lower(error) LIKE '%404%' OR lower(error) LIKE '%not found%' THEN 'Not Found (404)'
465
+ WHEN lower(error) LIKE '%timeout%' OR lower(error) LIKE '%timed out%' OR lower(error) LIKE '%etimedout%' OR lower(error) LIKE '%econnrefused%' THEN 'Timeout/Connection'
466
+ WHEN lower(error) LIKE '%500%' OR lower(error) LIKE '%internal server%' THEN 'Server Error (500)'
467
+ WHEN lower(error) LIKE '%503%' OR lower(error) LIKE '%unavailable%' THEN 'Unavailable (503)'
468
+ ELSE 'Other'
469
+ END as error_category,
470
+ COUNT(*) as count
471
+ FROM requests
472
+ WHERE status = 'error' AND created_at >= ?
473
+ GROUP BY platform, error_category
474
+ ORDER BY count DESC
475
+ `).all(since);
476
+ // Also get totals by category
477
+ const byCategory = db.prepare(`
478
+ SELECT
479
+ CASE
480
+ WHEN lower(error) LIKE '%429%' OR lower(error) LIKE '%rate limit%' OR lower(error) LIKE '%too many%' OR lower(error) LIKE '%quota%' THEN 'Rate Limited (429)'
481
+ WHEN lower(error) LIKE '%401%' OR lower(error) LIKE '%unauthorized%' OR lower(error) LIKE '%invalid api key%' OR lower(error) LIKE '%invalid_api_key%' OR lower(error) LIKE '%incorrect api key%' THEN 'Auth Error (401)'
482
+ WHEN lower(error) LIKE '%403%' OR lower(error) LIKE '%forbidden%' THEN 'Forbidden (403)'
483
+ WHEN lower(error) LIKE '%404%' OR lower(error) LIKE '%not found%' THEN 'Not Found (404)'
484
+ WHEN lower(error) LIKE '%timeout%' OR lower(error) LIKE '%timed out%' OR lower(error) LIKE '%etimedout%' OR lower(error) LIKE '%econnrefused%' THEN 'Timeout/Connection'
485
+ WHEN lower(error) LIKE '%500%' OR lower(error) LIKE '%internal server%' THEN 'Server Error (500)'
486
+ WHEN lower(error) LIKE '%503%' OR lower(error) LIKE '%unavailable%' THEN 'Unavailable (503)'
487
+ ELSE 'Other'
488
+ END as category,
489
+ COUNT(*) as count
490
+ FROM requests
491
+ WHERE status = 'error' AND created_at >= ?
492
+ GROUP BY category
493
+ ORDER BY count DESC
494
+ `).all(since);
495
+ // Errors by provider. Endpoint-scoped like /by-platform: one bar per custom
496
+ // relay, not one bar pooling every relay's failures under "custom" (#889) —
497
+ // a bar the operator cannot act on, because it never says which endpoint is
498
+ // the one failing.
499
+ const byPlatformRows = db.prepare(`
500
+ SELECT r.platform, ${ENDPOINT_ID_SQL} as base_url, COUNT(*) as count
501
+ FROM requests r
502
+ LEFT JOIN api_keys k ON k.id = r.key_id
503
+ WHERE r.status = 'error' AND r.created_at >= ?
504
+ GROUP BY r.platform, ${ENDPOINT_ID_SQL}
505
+ ORDER BY count DESC
506
+ `).all(since);
507
+ const byPlatform = byPlatformRows.map(r => {
508
+ const baseUrl = r.base_url || null;
509
+ return {
510
+ // `platform` stays the raw slug: it is what the chart colors by.
511
+ platform: r.platform,
512
+ providerId: providerIdFor(r.platform, baseUrl),
513
+ endpoint: providerDisplayName(r.platform, baseUrl),
514
+ count: r.count,
515
+ };
516
+ });
517
+ res.json({
518
+ byCategory,
519
+ byPlatform,
520
+ detailed: rows,
521
+ });
522
+ });
523
+ // Recent errors
524
+ analyticsRouter.get('/errors', (req, res) => {
525
+ const range = req.query.range ?? '7d';
526
+ const since = getSinceTimestamp(range);
527
+ const db = getDb();
528
+ // Joined to the serving key so each error names the endpoint it came from.
529
+ // Without it every custom relay's failures read as a bare "custom" in the
530
+ // panel (#889) and the operator has to guess which one broke.
531
+ const rows = db.prepare(`
532
+ SELECT r.id, r.platform, ${ENDPOINT_ID_SQL} as base_url, r.model_id, r.error,
533
+ r.latency_ms, r.created_at
534
+ FROM requests r
535
+ LEFT JOIN api_keys k ON k.id = r.key_id
536
+ WHERE r.status = 'error' AND r.created_at >= ?
537
+ ORDER BY r.created_at DESC
538
+ LIMIT 50
539
+ `).all(since);
540
+ res.json(rows.map(r => {
541
+ const baseUrl = r.base_url || null;
542
+ return {
543
+ id: r.id,
544
+ platform: r.platform,
545
+ providerId: providerIdFor(r.platform, baseUrl),
546
+ endpoint: providerDisplayName(r.platform, baseUrl),
547
+ modelId: r.model_id,
548
+ error: r.error,
549
+ latencyMs: r.latency_ms,
550
+ createdAt: r.created_at,
551
+ };
552
+ }));
553
+ });
554
+ // Recent calls — one row per proxied request, newest first, with the caller's
555
+ // IP and User-Agent (all local clients share the unified key, so client_ip is
556
+ // the only per-caller discriminator; UA disambiguates tunneled loopback calls).
557
+ // Reads the raw `requests` table, so history is bounded by the retention prune.
558
+ analyticsRouter.get('/requests', (req, res) => {
559
+ const range = req.query.range ?? '7d';
560
+ const since = getSinceTimestamp(range);
561
+ const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 100, 1), 500);
562
+ const offset = Math.max(parseInt(req.query.offset, 10) || 0, 0);
563
+ // Optional filters. All are validated (whitelist / shape) and applied as
564
+ // bound parameters; absent filters keep the default behavior identical.
565
+ const status = req.query.status;
566
+ if (status !== undefined && status !== 'success' && status !== 'error' && status !== 'canceled') {
567
+ res.status(400).json({ error: "invalid status filter (expected 'success', 'error' or 'canceled')" });
568
+ return;
569
+ }
570
+ // Provider filter. The `provider` param carries the stable row id returned by
571
+ // /by-platform: a platform slug ('groq') or 'custom:<base_url>' for a custom
572
+ // endpoint (#889 — every custom relay shares the 'custom' platform id, so the
573
+ // endpoint's base_url is what actually selects one). The legacy `platform`
574
+ // param still works for old clients. Both are bound parameters, never
575
+ // interpolated, so the only validation needed is shape.
576
+ const provider = req.query.provider;
577
+ const platform = req.query.platform;
578
+ let providerFilterSql = '';
579
+ const providerFilterParams = [];
580
+ if (provider !== undefined) {
581
+ if (provider.length > 256 || /[\r\n]/.test(provider)) {
582
+ res.status(400).json({ error: 'invalid provider filter' });
583
+ return;
584
+ }
585
+ if (provider === 'custom') {
586
+ // The BARE 'custom' id is the orphan bucket, not "all custom traffic":
587
+ // /by-platform emits it only for rows whose endpoint is unknown (the key
588
+ // was deleted, or never carried a base_url). Falling through to the slug
589
+ // branch below would filter on `platform = 'custom'` and return every
590
+ // relay's traffic — a list that contradicts the very row the user
591
+ // clicked, which counted the orphans alone. Match what that row counted.
592
+ providerFilterSql = ` AND r.platform = 'custom' AND ${ENDPOINT_ID_SQL} = ''`;
593
+ }
594
+ else if (provider.startsWith('custom:')) {
595
+ // Select one custom endpoint by its base_url. Normalized on the way in
596
+ // for the same reason ENDPOINT_ID_SQL normalizes on the way out.
597
+ providerFilterSql = ` AND r.platform = 'custom' AND ${ENDPOINT_ID_SQL} = ?`;
598
+ providerFilterParams.push(normalizeBaseUrl(provider.slice('custom:'.length)));
599
+ }
600
+ else if (/^[A-Za-z0-9_-]{1,64}$/.test(provider)) {
601
+ providerFilterSql = ' AND r.platform = ?';
602
+ providerFilterParams.push(provider);
603
+ }
604
+ else {
605
+ res.status(400).json({ error: 'invalid provider filter' });
606
+ return;
607
+ }
608
+ }
609
+ else if (platform !== undefined) {
610
+ // The legacy param keeps its pre-#889 meaning: `platform=custom` is every
611
+ // custom relay's traffic. Only the `provider` ids are endpoint-scoped.
612
+ // Platform ids are short slugs ('groq', 'pt-custom_1'); anything else is a
613
+ // client bug, not a filter.
614
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(platform)) {
615
+ res.status(400).json({ error: 'invalid platform filter' });
616
+ return;
617
+ }
618
+ providerFilterSql = ' AND r.platform = ?';
619
+ providerFilterParams.push(platform);
620
+ }
621
+ const db = getDb();
622
+ const filterSql = (status !== undefined ? ' AND r.status = ?' : '') +
623
+ providerFilterSql;
624
+ const filterParams = [
625
+ ...(status !== undefined ? [status] : []),
626
+ ...providerFilterParams,
627
+ ];
628
+ const total = db.prepare(`SELECT COUNT(*) as c FROM requests r
629
+ LEFT JOIN api_keys k ON k.id = r.key_id
630
+ WHERE r.created_at >= ?${filterSql}`).get(since, ...filterParams).c;
631
+ const rows = db.prepare(`
632
+ SELECT r.id, r.platform, r.model_id, r.requested_model, r.request_type, r.status,
633
+ r.input_tokens, r.output_tokens, r.latency_ms, r.error,
634
+ r.client_ip, r.client_user_agent, r.client_agent,
635
+ strftime('%Y-%m-%dT%H:%M:%SZ', r.created_at) as created_at_iso,
636
+ (SELECT COUNT(*) FROM request_attempts a WHERE a.request_id = r.id) as attempt_count,
637
+ k.label as key_label
638
+ FROM requests r
639
+ LEFT JOIN api_keys k ON k.id = r.key_id
640
+ WHERE r.created_at >= ?${filterSql}
641
+ ORDER BY r.created_at DESC, r.id DESC
642
+ LIMIT ? OFFSET ?
643
+ `).all(since, ...filterParams, limit, offset);
644
+ res.json({
645
+ total,
646
+ rows: rows.map(r => ({
647
+ id: r.id,
648
+ platform: r.platform,
649
+ modelId: r.model_id,
650
+ requestedModel: r.requested_model,
651
+ requestType: r.request_type,
652
+ status: r.status,
653
+ inputTokens: r.input_tokens,
654
+ outputTokens: r.output_tokens,
655
+ latencyMs: r.latency_ms,
656
+ error: r.error,
657
+ clientIp: r.client_ip,
658
+ clientUserAgent: r.client_user_agent,
659
+ clientAgent: r.client_agent,
660
+ createdAt: r.created_at_iso,
661
+ // #785: custom endpoints all share the generic 'custom' platform id, so
662
+ // the user's key label ("Ollama box") rides along to name the real
663
+ // provider in the recent-calls list. Null when the key was deleted or
664
+ // never labelled.
665
+ keyLabel: r.key_label ?? null,
666
+ // Failover-ladder length for this row. Attempts hang off the TERMINAL
667
+ // row of a proxied request; mid-ladder failure rows report 0.
668
+ attemptCount: r.attempt_count,
669
+ })),
670
+ });
671
+ });
672
+ // Per-request detail: the row plus its durable failover ladder — one entry per
673
+ // dispatched attempt (including the successful final one), ordinal-ordered,
674
+ // with the failure class and timing of each hop. keyOrdinal is the per-request
675
+ // key ordinal (key1, key2…), same anonymization as X-Fallback-Trail — internal
676
+ // key ids are never exposed. Attempts are keyed to the ladder's terminal row
677
+ // (the success row, or the last failure row when it exhausted), so mid-ladder
678
+ // error rows legitimately return an empty attempts array.
679
+ analyticsRouter.get('/requests/:id', (req, res) => {
680
+ const id = Number.parseInt(req.params.id, 10);
681
+ if (!Number.isInteger(id) || id <= 0) {
682
+ res.status(400).json({ error: 'invalid request id' });
683
+ return;
684
+ }
685
+ const db = getDb();
686
+ const r = db.prepare(`
687
+ SELECT id, platform, model_id, requested_model, served_model, request_type, status,
688
+ input_tokens, output_tokens, latency_ms, ttfb_ms, error,
689
+ client_ip, client_user_agent, client_agent,
690
+ strftime('%Y-%m-%dT%H:%M:%SZ', created_at) as created_at_iso
691
+ FROM requests
692
+ WHERE id = ?
693
+ `).get(id);
694
+ if (!r) {
695
+ res.status(404).json({ error: 'request not found' });
696
+ return;
697
+ }
698
+ const attempts = db.prepare(`
699
+ SELECT ordinal, platform, model_id, key_ordinal, key_label, outcome, start_offset_ms, duration_ms, error_summary
700
+ FROM request_attempts
701
+ WHERE request_id = ?
702
+ ORDER BY ordinal ASC
703
+ `).all(id);
704
+ res.json({
705
+ id: r.id,
706
+ platform: r.platform,
707
+ modelId: r.model_id,
708
+ requestedModel: r.requested_model,
709
+ // Upstream-reported model when it genuinely differed from the routed
710
+ // model_id (#534 served-model drift guard); null in the healthy case.
711
+ servedModel: r.served_model,
712
+ requestType: r.request_type,
713
+ status: r.status,
714
+ inputTokens: r.input_tokens,
715
+ outputTokens: r.output_tokens,
716
+ latencyMs: r.latency_ms,
717
+ ttfbMs: r.ttfb_ms,
718
+ error: r.error,
719
+ clientIp: r.client_ip,
720
+ clientUserAgent: r.client_user_agent,
721
+ clientAgent: r.client_agent,
722
+ createdAt: r.created_at_iso,
723
+ attempts: attempts.map(a => ({
724
+ ordinal: a.ordinal,
725
+ platform: a.platform,
726
+ modelId: a.model_id,
727
+ keyOrdinal: a.key_ordinal,
728
+ keyLabel: a.key_label ?? null,
729
+ outcome: a.outcome,
730
+ startOffsetMs: a.start_offset_ms,
731
+ durationMs: a.duration_ms,
732
+ // Short, redacted per-hop error text (null for successful hops and for
733
+ // rows written before the error_summary migration).
734
+ errorSummary: a.error_summary ?? null,
735
+ })),
736
+ });
737
+ });
738
+ //# sourceMappingURL=analytics.js.map