@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,1076 @@
1
+ import crypto from 'crypto';
2
+ import { Router } from 'express';
3
+ import { z } from 'zod';
4
+ import { routeRequest, resolveModelGroupCandidates, resolveRoutingChain, resolveStickyPreference, routingReserveTokens } from '../services/router.js';
5
+ import { getSetting, getUnifiedApiKey } from '../db/index.js';
6
+ import { contentToString, estimateInputTokens } from '../lib/content.js';
7
+ import { routeOutputBudget } from '../lib/output-cap.js';
8
+ import { resolveTaskType } from '../lib/task-type.js';
9
+ import { repairToolArguments, toolSchemaMap } from '../lib/tool-args.js';
10
+ import { invalidToolArgumentsError, invalidToolCallReasons, isToolArgumentValidationEnabled } from '../lib/tool-validate.js';
11
+ import { rescueInlineToolCalls, startsWithDialectMarker, couldBecomeDialectMarker, containsDialectMarker } from '../lib/tool-call-rescue.js';
12
+ import { sanitizeProviderErrorMessage } from '../lib/error-redaction.js';
13
+ import { convertDocumentBlock, documentRejectionMessage } from '../lib/anthropic-documents.js';
14
+ import { isClientAbortError, newClientAbortError, newHedgeAbortError, isUpstreamClassificationOutput } from '../lib/error-classify.js';
15
+ import { logRequest } from '../lib/request-log.js';
16
+ import { extractApiToken, timingSafeStringEqual, getStickyModel, setStickyModel } from './proxy.js';
17
+ import { runFallbackLoop, newFallbackState, fallbackRoutingTokens, recordUpstreamSuccess, setFallbackHeaders, setExhaustionHeaders } from '../lib/fallback-loop.js';
18
+ import { routedViaValue } from '../lib/header-value.js';
19
+ import { applyTokenBudget, tokenBudgetMessage } from '../lib/guardrails.js';
20
+ import { resolveAnthropicModel, claudeFamilyDiscoveryEntries } from '../services/anthropic-map.js';
21
+ import { isUnifyEnabled, getModelGroups, resolveRequestedIdForDispatch } from '../services/model-groups.js';
22
+ import { buildModelListing } from '../services/model-listing.js';
23
+ import { compressRequest, formatCompressionHeader } from '../services/compression/pipeline.js';
24
+ import { normalizeMessageImages } from '../lib/image-normalize.js';
25
+ // Anthropic-compatible Messages API (`POST /v1/messages`). This is a thin
26
+ // translation layer over the SAME router/fallback/analytics machinery the
27
+ // OpenAI `/v1/chat/completions` route uses — it converts the Anthropic wire
28
+ // format to our internal (OpenAI-shaped) ChatMessage form on the way in, runs
29
+ // the normal routing loop, and converts the result back to Anthropic shape on
30
+ // the way out. The OpenAI route is left untouched.
31
+ //
32
+ // The point is Claude Code (and anything else that speaks the Anthropic SDK):
33
+ // point `ANTHROPIC_BASE_URL` at this server, set the API key to the unified
34
+ // key, and every `claude-*` request transparently routes to whatever free
35
+ // model the chain picks. Auth accepts Anthropic's native `x-api-key` header
36
+ // (already handled by extractApiToken) as well as a bearer token.
37
+ export const anthropicRouter = Router();
38
+ const AUTO_MODEL_ID = 'auto';
39
+ function isAutoModel(modelId) {
40
+ if (!modelId)
41
+ return true;
42
+ const lower = modelId.toLowerCase();
43
+ return lower === AUTO_MODEL_ID || lower.startsWith(`${AUTO_MODEL_ID}:`);
44
+ }
45
+ const MAX_RETRIES = 20;
46
+ // Anthropic requires `max_tokens`; mirror the OpenAI route's routing-budget
47
+ // default when a client somehow omits it.
48
+ const DEFAULT_MAX_TOKENS = 1024;
49
+ const IMAGE_TOKEN_ESTIMATE = 1000;
50
+ // ── Request schema ──────────────────────────────────────────────────────────
51
+ // Permissive on purpose: the Anthropic content-block vocabulary grows over
52
+ // time (thinking, document, server-tool blocks…) and Claude Code sends blocks
53
+ // with extra fields like `cache_control`. We validate the envelope and handle
54
+ // the block types we understand by `type`, ignoring the rest — same tolerance
55
+ // philosophy as the OpenAI route (#200).
56
+ const contentBlockSchema = z.object({ type: z.string() }).passthrough();
57
+ const anthropicMessageSchema = z.object({
58
+ // Anthropic's own API only allows user/assistant here, but real clients
59
+ // (Claude Code, routers) sometimes inline a `system` turn in the messages
60
+ // array. Accept it and fold it into the system context rather than 400-ing —
61
+ // same tolerance philosophy as the OpenAI route's developer/function roles.
62
+ role: z.enum(['user', 'assistant', 'system']),
63
+ content: z.union([z.string(), z.array(contentBlockSchema)]),
64
+ }).passthrough();
65
+ const anthropicToolSchema = z.object({
66
+ name: z.string().min(1),
67
+ description: z.string().optional(),
68
+ input_schema: z.record(z.string(), z.unknown()).optional(),
69
+ }).passthrough();
70
+ const anthropicToolChoiceSchema = z.object({
71
+ type: z.enum(['auto', 'any', 'tool', 'none']),
72
+ name: z.string().optional(),
73
+ }).passthrough();
74
+ const messagesSchema = z.object({
75
+ model: z.string().optional(),
76
+ // Anthropic mandates max_tokens; accept omission and clamp non-positive
77
+ // values to the default rather than 400-ing (some clients send 0).
78
+ max_tokens: z.number().int().optional(),
79
+ messages: z.array(anthropicMessageSchema).min(1),
80
+ system: z.union([z.string(), z.array(contentBlockSchema)]).optional(),
81
+ temperature: z.number().min(0).max(2).optional(),
82
+ top_p: z.number().min(0).max(1).optional(),
83
+ // Anthropic's native top_k — forwarded to providers that support it via the
84
+ // platform policy in lib/sampling-params.ts.
85
+ top_k: z.number().int().min(1).nullable().optional(),
86
+ stream: z.boolean().optional(),
87
+ stop_sequences: z.array(z.string()).optional(),
88
+ tools: z.array(anthropicToolSchema).optional(),
89
+ tool_choice: anthropicToolChoiceSchema.optional(),
90
+ // Anthropic's native extended-thinking knob. Mapped onto the internal
91
+ // reasoning_effort (see effortFromAnthropicThinking) so providers with
92
+ // request-side reasoning control receive it; platforms without support have
93
+ // it stripped by the policy in lib/sampling-params.ts.
94
+ // `type` is a free string, not the enabled/disabled enum: Anthropic keeps
95
+ // adding modes (Claude Code sends 'adaptive') and validating the enum here
96
+ // turned a knob we only use as a hint into a hard 400 (#632).
97
+ thinking: z.object({
98
+ type: z.string().optional(),
99
+ budget_tokens: z.number().int().optional(),
100
+ }).passthrough().nullable().optional(),
101
+ }).passthrough();
102
+ // Anthropic expresses reasoning control as a token budget; our internal knob
103
+ // is OpenAI's coarse effort scale. Thresholds follow Anthropic's own guidance
104
+ // bands (minimum budget 1024; multi-10k budgets for hard problems).
105
+ export function effortFromAnthropicThinking(thinking) {
106
+ if (!thinking)
107
+ return undefined;
108
+ const type = thinking.type?.trim().toLowerCase();
109
+ if (type === 'disabled' || type === 'off' || type === 'none')
110
+ return 'none';
111
+ const budget = thinking.budget_tokens;
112
+ // Model-managed modes ('adaptive', 'auto') without an explicit budget mean
113
+ // "you decide how much" — forward no knob at all and let the provider
114
+ // default stand, same as a -1 Gemini thinking budget.
115
+ if (budget == null && (type === 'adaptive' || type === 'auto'))
116
+ return undefined;
117
+ if (budget == null)
118
+ return 'medium';
119
+ if (budget < 4096)
120
+ return 'low';
121
+ if (budget < 16384)
122
+ return 'medium';
123
+ return 'high';
124
+ }
125
+ function sendError(res, status, errorType, message) {
126
+ res.status(status).json({ type: 'error', error: { type: errorType, message } });
127
+ }
128
+ // The shared exhaustion body's `type` strings are chosen to be valid on the
129
+ // OpenAI-shaped surfaces; several of them are not Anthropic error types. Remap
130
+ // them onto Anthropic's vocabulary via the body's coarse `kind` so this
131
+ // surface stays wire-correct: auth/upstream failures → 'api_error', pool
132
+ // unavailability → 'overloaded_error', context exhaustion → Anthropic's 413
133
+ // 'request_too_large', model-gone → 'not_found_error'.
134
+ function anthropicErrorType(body) {
135
+ if (body.kind === 'auth' || body.kind === 'upstream')
136
+ return 'api_error';
137
+ if (body.kind === 'unavailable')
138
+ return 'overloaded_error';
139
+ if (body.kind === 'context_too_large')
140
+ return 'request_too_large';
141
+ if (body.kind === 'model_not_found')
142
+ return 'not_found_error';
143
+ return body.type;
144
+ }
145
+ // Render an exhaustion body on the Anthropic wire shape, carrying the
146
+ // machine-readable extras (`code`, `retryAtMs`) alongside Anthropic's standard
147
+ // type/message and stamping the matching Retry-After header. Extra keys on the
148
+ // error object are ignored by Anthropic SDKs, so this stays wire-compatible.
149
+ function sendExhaustion(res, body) {
150
+ setExhaustionHeaders(res, body);
151
+ res.status(body.status).json({
152
+ type: 'error',
153
+ error: {
154
+ type: anthropicErrorType(body),
155
+ message: body.message,
156
+ ...(body.code ? { code: body.code } : {}),
157
+ ...(body.retryAtMs != null ? { retryAtMs: body.retryAtMs } : {}),
158
+ },
159
+ });
160
+ }
161
+ function newMessageId() {
162
+ return `msg_${crypto.randomBytes(12).toString('hex')}`;
163
+ }
164
+ // ── Auth (shared with the OpenAI route) ─────────────────────────────────────
165
+ function authenticate(req, res) {
166
+ const token = extractApiToken(req);
167
+ const unifiedKey = getUnifiedApiKey();
168
+ if (!token || !timingSafeStringEqual(token, unifiedKey)) {
169
+ sendError(res, 401, 'authentication_error', 'Invalid API key');
170
+ return false;
171
+ }
172
+ return true;
173
+ }
174
+ // ── Request translation: Anthropic → internal (OpenAI-shaped) ───────────────
175
+ function flattenSystem(system) {
176
+ if (!system)
177
+ return '';
178
+ if (typeof system === 'string')
179
+ return system;
180
+ return system
181
+ .map(block => (typeof block.text === 'string' ? block.text : ''))
182
+ .filter(Boolean)
183
+ .join('\n');
184
+ }
185
+ // An Anthropic tool_result's content is a string or an array of blocks; flatten
186
+ // to text for the internal `tool` message (we don't forward tool images).
187
+ function flattenToolResult(content) {
188
+ if (content == null)
189
+ return '';
190
+ if (typeof content === 'string')
191
+ return content;
192
+ if (!Array.isArray(content))
193
+ return JSON.stringify(content);
194
+ return content
195
+ .map(block => {
196
+ if (typeof block === 'string')
197
+ return block;
198
+ if (block && typeof block === 'object' && typeof block.text === 'string')
199
+ return block.text;
200
+ return '';
201
+ })
202
+ .filter(Boolean)
203
+ .join('\n');
204
+ }
205
+ // Anthropic image block → OpenAI `image_url` block, so vision models keep
206
+ // working through the same routing path the OpenAI route uses.
207
+ function imageBlockToUrl(block) {
208
+ const src = block?.source;
209
+ if (!src || typeof src !== 'object')
210
+ return null;
211
+ if (src.type === 'base64' && src.media_type && src.data) {
212
+ return `data:${src.media_type};base64,${src.data}`;
213
+ }
214
+ if (src.type === 'url' && typeof src.url === 'string')
215
+ return src.url;
216
+ return null;
217
+ }
218
+ function convertToolChoice(choice) {
219
+ if (!choice)
220
+ return undefined;
221
+ switch (choice.type) {
222
+ case 'auto': return 'auto';
223
+ case 'none': return 'none';
224
+ case 'any': return 'required'; // Anthropic "any" == OpenAI "required"
225
+ case 'tool': return choice.name ? { type: 'function', function: { name: choice.name } } : 'required';
226
+ default: return undefined;
227
+ }
228
+ }
229
+ function convertRequest(input) {
230
+ const messages = [];
231
+ const documentRejections = [];
232
+ let hasImage = false;
233
+ const system = flattenSystem(input.system);
234
+ if (system)
235
+ messages.push({ role: 'system', content: system });
236
+ for (const message of input.messages) {
237
+ // A `system` turn inlined in the messages array → fold into system context
238
+ // (flatten text blocks). Anthropic clients occasionally send this; the real
239
+ // API rejects it, but we'd rather route the request than 400.
240
+ if (message.role === 'system') {
241
+ const sysText = typeof message.content === 'string'
242
+ ? message.content
243
+ : message.content.map(b => (typeof b.text === 'string' ? b.text : '')).filter(Boolean).join('\n');
244
+ if (sysText)
245
+ messages.push({ role: 'system', content: sysText });
246
+ continue;
247
+ }
248
+ if (typeof message.content === 'string') {
249
+ messages.push({ role: message.role, content: message.content });
250
+ continue;
251
+ }
252
+ const textParts = [];
253
+ const imageBlocks = [];
254
+ const toolCalls = [];
255
+ const toolResults = [];
256
+ for (const block of message.content) {
257
+ const type = block.type;
258
+ if (type === 'text') {
259
+ if (typeof block.text === 'string' && block.text.length > 0) {
260
+ textParts.push(block.text);
261
+ }
262
+ }
263
+ else if (type === 'image') {
264
+ const url = imageBlockToUrl(block);
265
+ if (url) {
266
+ imageBlocks.push({ type: 'image_url', image_url: { url } });
267
+ hasImage = true;
268
+ }
269
+ }
270
+ else if (type === 'tool_use') {
271
+ const thoughtSignature = block.thought_signature ?? block.thoughtSignature;
272
+ toolCalls.push({
273
+ id: String(block.id ?? ''),
274
+ type: 'function',
275
+ function: { name: String(block.name ?? ''), arguments: JSON.stringify(block.input ?? {}) },
276
+ ...(typeof thoughtSignature === 'string' && thoughtSignature.length > 0 ? { thought_signature: thoughtSignature } : {}),
277
+ });
278
+ }
279
+ else if (type === 'tool_result') {
280
+ toolResults.push({
281
+ role: 'tool',
282
+ tool_call_id: String(block.tool_use_id ?? ''),
283
+ content: flattenToolResult(block.content),
284
+ });
285
+ }
286
+ else if (type === 'document') {
287
+ // A document that is already text costs nothing to inline. One that
288
+ // needs decoding cannot be served by any provider here, and dropping
289
+ // it would answer confidently about a document the model never saw.
290
+ const result = convertDocumentBlock(block);
291
+ if (result.ok)
292
+ textParts.push(result.text);
293
+ else
294
+ documentRejections.push(result.reason);
295
+ }
296
+ // Other unknown block types (thinking, …) are intentionally dropped.
297
+ }
298
+ const text = textParts.join('\n');
299
+ if (message.role === 'assistant') {
300
+ messages.push({
301
+ role: 'assistant',
302
+ content: text.length > 0 ? text : null,
303
+ ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
304
+ });
305
+ }
306
+ else {
307
+ // User turn. Anthropic carries tool results inside a user message; split
308
+ // them into the `tool` messages OpenAI expects (which must directly
309
+ // follow the assistant's tool_calls), then any fresh user text/images.
310
+ messages.push(...toolResults);
311
+ if (imageBlocks.length > 0) {
312
+ const blocks = [];
313
+ if (text.length > 0)
314
+ blocks.push({ type: 'text', text });
315
+ blocks.push(...imageBlocks);
316
+ messages.push({ role: 'user', content: blocks });
317
+ }
318
+ else if (text.length > 0) {
319
+ messages.push({ role: 'user', content: text });
320
+ }
321
+ }
322
+ }
323
+ const tools = input.tools?.map(t => ({
324
+ type: 'function',
325
+ function: { name: t.name, description: t.description, parameters: t.input_schema ?? { type: 'object', properties: {} } },
326
+ }));
327
+ return {
328
+ messages,
329
+ tools,
330
+ tool_choice: convertToolChoice(input.tool_choice),
331
+ hasImage,
332
+ wantsTools: (tools?.length ?? 0) > 0,
333
+ documentRejections,
334
+ };
335
+ }
336
+ // Model resolution (Claude family → auto | pinned catalog model) lives in
337
+ // services/anthropic-map.ts so the dashboard mapping editor and this route
338
+ // share one source of truth. Claude Code keeps its built-in `claude-*` names;
339
+ // by default every family maps to "auto" (the router picks a free model).
340
+ // A stable created_at for the Anthropic /v1/models entries; clients only use it
341
+ // for display ordering, so a constant avoids needless churn.
342
+ const MODEL_CREATED_AT = '2026-01-01T00:00:00Z';
343
+ // ── Response translation: internal → Anthropic ──────────────────────────────
344
+ function mapStopReason(finishReason, hadToolCalls) {
345
+ if (hadToolCalls || finishReason === 'tool_calls')
346
+ return 'tool_use';
347
+ if (finishReason === 'length')
348
+ return 'max_tokens';
349
+ if (finishReason === 'stop' || finishReason === 'content_filter' || finishReason == null)
350
+ return 'end_turn';
351
+ return 'end_turn';
352
+ }
353
+ function parseToolInput(raw) {
354
+ try {
355
+ return JSON.parse(raw);
356
+ }
357
+ catch {
358
+ return {};
359
+ }
360
+ }
361
+ function toAnthropicContent(message) {
362
+ const blocks = [];
363
+ // Reasoning output (native provider reasoning_content, or extracted from an
364
+ // inline <think> block by lib/think-tags.ts) → Anthropic thinking block,
365
+ // first per Anthropic convention.
366
+ const reasoning = message?.reasoning_content;
367
+ if (typeof reasoning === 'string' && reasoning.length > 0) {
368
+ blocks.push({ type: 'thinking', thinking: reasoning, signature: '' });
369
+ }
370
+ const text = contentToString(message?.content ?? '');
371
+ if (text.length > 0)
372
+ blocks.push({ type: 'text', text });
373
+ for (const call of message?.tool_calls ?? []) {
374
+ const block = {
375
+ type: 'tool_use',
376
+ id: call.id,
377
+ name: call.function.name,
378
+ input: parseToolInput(call.function.arguments),
379
+ };
380
+ if (call.thought_signature)
381
+ block.thought_signature = call.thought_signature;
382
+ blocks.push(block);
383
+ }
384
+ return blocks;
385
+ }
386
+ // ── SSE helpers ─────────────────────────────────────────────────────────────
387
+ function writeSse(res, event, data) {
388
+ res.write(`event: ${event}\n`);
389
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
390
+ }
391
+ // Convert rescued inline tool calls (parsed out of a model that serialized its
392
+ // tool call as TEXT) into OpenAI-shaped tool_calls, so toAnthropicContent /
393
+ // the streaming tool_use emitter render them as Anthropic tool_use blocks —
394
+ // the same rescue /chat/completions and /v1/responses already apply (#231).
395
+ function rescuedToToolCalls(calls, schemas) {
396
+ return calls.map((c, i) => ({
397
+ id: `toolu_${crypto.randomBytes(8).toString('hex')}_rescued_${i + 1}`,
398
+ type: 'function',
399
+ function: { name: c.name, arguments: repairToolArguments(c.arguments, schemas.get(c.name)) },
400
+ }));
401
+ }
402
+ anthropicRouter.post('/messages', async (req, res) => {
403
+ const start = Date.now();
404
+ if (!authenticate(req, res))
405
+ return;
406
+ const parsed = messagesSchema.safeParse(req.body);
407
+ if (!parsed.success) {
408
+ const detail = parsed.error.errors
409
+ .map(e => (e.path.length ? `${e.path.join('.')}: ${e.message}` : e.message))
410
+ .slice(0, 5).join(', ');
411
+ console.warn(`[anthropic] 400 invalid /v1/messages request: ${detail}`);
412
+ sendError(res, 400, 'invalid_request_error', `Invalid request: ${detail}`);
413
+ return;
414
+ }
415
+ const body = parsed.data;
416
+ const requestedModel = body.model ?? 'auto';
417
+ const routedModel = body.model?.startsWith('claude/')
418
+ ? body.model.slice('claude/'.length)
419
+ : body.model;
420
+ // The Anthropic wire format requires max_tokens, but OUR default injection
421
+ // must not change the budget gate's verdict: gating AFTER defaulting made a
422
+ // no-max_tokens request 413 here (input + 1024 over budget) where the chat
423
+ // surface would cap it and serve. Gate on what the client actually sent,
424
+ // then resolve the default against the budget remainder.
425
+ const clientMaxTokens = body.max_tokens != null && body.max_tokens > 0 ? body.max_tokens : undefined;
426
+ const { temperature, top_p, stream } = body;
427
+ const converted = convertRequest(body);
428
+ // Rejected before routing, and before compression: this is our verdict, not
429
+ // a provider's. Entering the failover loop would spend quota on a request
430
+ // every candidate would fail identically, and book it as provider failure.
431
+ if (converted.documentRejections.length > 0) {
432
+ const message = documentRejectionMessage(converted.documentRejections);
433
+ console.warn(`[anthropic] 400 unsupported document block: ${converted.documentRejections.join('; ')}`);
434
+ sendError(res, 400, 'invalid_request_error', message);
435
+ return;
436
+ }
437
+ let { messages } = converted;
438
+ const { tools, tool_choice, hasImage, wantsTools } = converted;
439
+ // Downscale over-threshold inline images before compression/estimation so
440
+ // the budget, routing, and upstream transfer all see the shrunk bytes
441
+ // (see lib/image-normalize.ts). The cache-control detection below reads the
442
+ // RAW wire body, and normalization mutates urls in place keeping the blocks
443
+ // (and any cache_control on them) intact — neither is disturbed.
444
+ await normalizeMessageImages(messages);
445
+ const systemHasCacheControl = Array.isArray(body.system)
446
+ && body.system.some(block => block && typeof block === 'object' && 'cache_control' in block);
447
+ const messageHasCacheControl = body.messages.some(message => Array.isArray(message.content)
448
+ && message.content.some(block => block && typeof block === 'object' && 'cache_control' in block));
449
+ const compressionResult = compressRequest(messages, {
450
+ header: req.headers['x-freellm-compress'],
451
+ tools,
452
+ // Claude Code normally marks the system prefix. If a later native message
453
+ // carries a breakpoint, conservatively cap the translated prefix at
454
+ // lossless rather than risk invalidating upstream prompt-cache semantics.
455
+ cacheControlPrefixLength: messageHasCacheControl ? messages.length : (systemHasCacheControl ? 1 : 0),
456
+ });
457
+ messages = compressionResult.messages;
458
+ res.setHeader('X-FreeLLM-Compress', formatCompressionHeader(compressionResult));
459
+ const estimatedInputTokens = estimateInputTokens(messages, tools);
460
+ const imageCount = messages.reduce((n, m) => n + (Array.isArray(m.content) ? m.content.filter(b => b?.type === 'image_url').length : 0), 0);
461
+ // Guardrail: per-request token budget (request_max_tokens_budget, default
462
+ // off). A client-set max_tokens can only pass or reject; an omitted one is
463
+ // capped to the budget remainder, mirroring /v1/chat/completions.
464
+ const budgetCheck = applyTokenBudget(estimatedInputTokens + imageCount * IMAGE_TOKEN_ESTIMATE, clientMaxTokens);
465
+ if (budgetCheck.rejection) {
466
+ sendError(res, 413, 'invalid_request_error', tokenBudgetMessage(budgetCheck.rejection));
467
+ return;
468
+ }
469
+ const max_tokens = clientMaxTokens
470
+ ?? (budgetCheck.maxTokens != null ? Math.min(DEFAULT_MAX_TOKENS, budgetCheck.maxTokens) : DEFAULT_MAX_TOKENS);
471
+ const completionOptions = {
472
+ temperature, max_tokens, top_p, top_k: body.top_k ?? undefined, tools, tool_choice,
473
+ // Native `thinking: {type, budget_tokens}` → the same internal knob the
474
+ // OpenAI surface's reasoning_effort feeds; absent when the client sent none.
475
+ reasoning_effort: effortFromAnthropicThinking(body.thinking),
476
+ };
477
+ // Capped output reserve so a large max_tokens can't falsely exclude the model
478
+ // pool (#470); input + images count in full. Threaded to the router
479
+ // separately: it is exact and must not be inflated by the context-window
480
+ // safety margin (#956 review).
481
+ const outputReserve = routingReserveTokens(max_tokens);
482
+ const estimatedTotal = estimatedInputTokens + imageCount * IMAGE_TOKEN_ESTIMATE + outputReserve;
483
+ // Resolve the model through the operator's Claude-family map (opus/sonnet/
484
+ // haiku/default → auto | a pinned catalog model). A concrete catalog id pins
485
+ // directly. `pinned` drives the analytics requested-model label.
486
+ const resolved = resolveAnthropicModel(routedModel);
487
+ const pinnedModelId = resolved.pinned ? (body.model ?? null) : null;
488
+ // Named `auto:<profile>` chains (and plain `auto`) must go through the same
489
+ // resolver as /v1/chat/completions. resolveAnthropicModel treats unknown ids
490
+ // as unpinned auto-route, which used to silently walk the active pool.
491
+ let resolvedChain;
492
+ let strategyKey;
493
+ const autoModelString = isAutoModel(requestedModel)
494
+ ? requestedModel
495
+ : (isAutoModel(routedModel) ? routedModel : undefined);
496
+ if (!resolved.pinned && autoModelString !== undefined) {
497
+ try {
498
+ resolvedChain = resolveRoutingChain(autoModelString);
499
+ // Named auto:<profile> / auto:smart get their own sticky bucket. Plain
500
+ // `auto` stays unscoped so Claude Code session affinity is unchanged.
501
+ strategyKey = resolvedChain.strategyKey === 'auto' ? undefined : resolvedChain.strategyKey;
502
+ }
503
+ catch (err) {
504
+ if (err?.status === 400) {
505
+ sendError(res, 400, 'invalid_request_error', err.message);
506
+ return;
507
+ }
508
+ throw err;
509
+ }
510
+ }
511
+ // Session affinity: Claude Code stamps every request in a session with
512
+ // X-Claude-Code-Session-Id. When auto-routing, stick the whole session to one
513
+ // model so it doesn't flap between free providers mid-conversation.
514
+ const rawSession = req.headers['x-claude-code-session-id'] ?? req.headers['x-session-id'];
515
+ const sessionId = Array.isArray(rawSession) ? rawSession[0] : rawSession;
516
+ // Same-model cross-provider failover (#932). A pin resolves to ONE catalog
517
+ // row, and until now that row was the only thing the request preferred: when
518
+ // its provider failed, the loop walked the rest of the catalog and answered
519
+ // with a completely different model. The OpenAI, Responses and inbound-chat
520
+ // surfaces already avoid that by routing a pinned id over its unified model
521
+ // group as a STRICT chain (#335) — every provider serving the same logical
522
+ // model, and nothing else. Build the identical chain here so /v1/messages
523
+ // behaves the same.
524
+ //
525
+ // Only the pinned paths change. A Claude family alias mapped to 'auto' (the
526
+ // default for opus/sonnet/haiku/default) resolves to no model at all and
527
+ // keeps auto-routing over the full chain exactly as before.
528
+ let groupChain;
529
+ // Sticky scope for a group-pinned request: bucket by the pinned catalog id so
530
+ // the session prefers its last successful provider of THIS model without
531
+ // leaking affinity across groups. Undefined for auto (the global scope).
532
+ let stickyScope;
533
+ if (resolved.modelId) {
534
+ const dispatch = isUnifyEnabled() ? resolveRequestedIdForDispatch(resolved.modelId, getModelGroups()) : null;
535
+ const members = dispatch?.memberDbIds ?? null;
536
+ if (members && members.length > 0) {
537
+ const chain = resolveModelGroupCandidates(members, dispatch.demotedDbIds);
538
+ // An empty chain would mean the pinned row is no longer routable at all;
539
+ // this surface is lenient, so leave the legacy single-row pin in place
540
+ // rather than failing the request.
541
+ if (chain.length > 0) {
542
+ groupChain = chain;
543
+ stickyScope = resolved.modelId;
544
+ }
545
+ }
546
+ }
547
+ let preferredModel = resolved.preferredModelDbId;
548
+ if (groupChain) {
549
+ // The strict chain already fixes the model; the only preference left is
550
+ // which member served this session last. Passing a non-member would make
551
+ // routeRequest splice an off-group model into the chain and break the pin.
552
+ const sticky = getStickyModel(messages, sessionId, stickyScope);
553
+ preferredModel = (sticky != null && groupChain.some(r => r.model_db_id === sticky)) ? sticky : undefined;
554
+ }
555
+ if (preferredModel == null && !groupChain) {
556
+ preferredModel = resolveStickyPreference(getStickyModel(messages, sessionId, strategyKey), resolvedChain?.chain);
557
+ }
558
+ // Thin adapter over the shared fallback loop (lib/fallback-loop.ts): the
559
+ // cooldown/skip/penalty/exhaustion machinery is shared, only the Anthropic
560
+ // request/stream translation lives here. This converged three drifts on this
561
+ // surface: it now honors a provider Retry-After and day-benches a 403 (via the
562
+ // shared cooldown), returns a shared exhaustion body (a 400 invalid_request
563
+ // when every provider rejected the request, not always a 429), and applies the
564
+ // inline tool-call dialect rescue that the OpenAI/Responses surfaces carry.
565
+ const state = newFallbackState();
566
+ // Lets the failover loop learn which models reject tool calls (#1230).
567
+ state.wantsTools = wantsTools;
568
+ const attemptLog = [];
569
+ // Client-disconnect fan-out: the flag stops the loop before the NEXT
570
+ // attempt; the AbortController (threaded to the provider as
571
+ // CompletionOptions.signal) additionally cancels the IN-FLIGHT upstream
572
+ // fetch and any body/stream read, so tokens stop burning and the in-flight
573
+ // lease frees immediately. 'close' also fires on normal completion —
574
+ // writableEnded distinguishes a real disconnect.
575
+ let clientGone = false;
576
+ const clientAbort = new AbortController();
577
+ // Fallback-v2 hedging: the loop aborts this controller (via abortInFlight)
578
+ // when the wall-clock retry budget expires mid-attempt, canceling the
579
+ // in-flight upstream instead of waiting for a stalled attempt to time out.
580
+ const hedgeAbort = new AbortController();
581
+ res.on('close', () => {
582
+ if (!res.writableEnded) {
583
+ clientGone = true;
584
+ clientAbort.abort(newClientAbortError());
585
+ }
586
+ });
587
+ const dispatchOptions = { ...completionOptions, signal: AbortSignal.any([clientAbort.signal, hedgeAbort.signal]) };
588
+ await runFallbackLoop({
589
+ maxRetries: MAX_RETRIES,
590
+ state,
591
+ attemptLog,
592
+ logIdentity: { surface: 'anthropic messages', requestedModel },
593
+ clientGone: () => clientGone,
594
+ abortInFlight: () => hedgeAbort.abort(newHedgeAbortError()),
595
+ route: () => {
596
+ // Task-type routing (#1127): same header/derivation as /chat/completions.
597
+ const taskType = resolveTaskType(req, tools, messages);
598
+ // #507: inflate the routing estimate from any provider-reported REQUESTED
599
+ // size latched onto state so the existing size gates in router.ts skip
600
+ // low-TPM / small-context models on retry.
601
+ const routingTotal = fallbackRoutingTokens(state, estimatedTotal, outputReserve);
602
+ return routeRequest(routingTotal, state.skipKeys.size > 0 ? state.skipKeys : undefined, preferredModel, hasImage, wantsTools, state.skipModels.size > 0 ? state.skipModels : undefined, groupChain ?? resolvedChain?.chain, false, state.skipPlatforms.size > 0 ? state.skipPlatforms : undefined, outputReserve, taskType);
603
+ },
604
+ dispatch: async (route, attempt, dispatchCtx) => {
605
+ const contextBudget = routeOutputBudget(route, estimatedInputTokens);
606
+ const routeOptions = { ...dispatchOptions, contextBudget };
607
+ if (stream) {
608
+ try {
609
+ await streamCompletion(res, route, messages, routeOptions, {
610
+ start, attempt, attemptLog, clientGone: () => clientGone, requestedModel, estimatedInputTokens, tools, pinnedModelId,
611
+ sessionId, pinned: resolved.pinned, stickyScope, strategyKey, disarmHedge: dispatchCtx.disarmHedge, state,
612
+ });
613
+ return 'done';
614
+ }
615
+ catch (err) {
616
+ // The stream already committed (message_start sent) and surfaced its
617
+ // own error event; stop without failover or a second response.
618
+ if (err instanceof StreamAlreadyStarted)
619
+ return 'committed';
620
+ throw err;
621
+ }
622
+ }
623
+ const result = await route.provider.chatCompletion(route.apiKey, messages, route.modelId, routeOptions);
624
+ const respMsg = result.choices?.[0]?.message;
625
+ const respText = contentToString(respMsg?.content ?? '');
626
+ let respToolCalls = respMsg?.tool_calls ?? [];
627
+ // Empty completion → fail over via the shared loop, exactly like the
628
+ // OpenAI route; finish_reason 'length' (whole output budget spent on
629
+ // hidden reasoning) skips the cooldown/penalty.
630
+ if (!respText && respToolCalls.length === 0) {
631
+ throw Object.assign(new Error(`empty completion from ${route.displayName}`), result.choices?.[0]?.finish_reason === 'length' ? { skipBench: true } : {});
632
+ }
633
+ // #809: bare "safe"/"unsafe" classification output from a relay is an
634
+ // upstream filter, not the requested model — fail over like an empty
635
+ // completion.
636
+ if (isUpstreamClassificationOutput(respText, route.platform) && respToolCalls.length === 0) {
637
+ throw Object.assign(new Error(`empty completion from ${route.displayName} (upstream classification output)`), result.choices?.[0]?.finish_reason === 'length' ? { skipBench: true } : {});
638
+ }
639
+ // Inline tool-call dialect rescue (#231): a tool-bearing request answered
640
+ // with the call serialized as TEXT → re-parse it into structured tool_use
641
+ // so Claude Code's agent loop keeps working; a detected-but-unparseable
642
+ // dialect is a dead turn and fails over like an empty completion. Same
643
+ // rescue /chat/completions and /v1/responses already apply.
644
+ if (wantsTools && respMsg && respToolCalls.length === 0 && respText) {
645
+ const rescue = rescueInlineToolCalls(respText, new Set((tools ?? []).map(t => t.function.name)));
646
+ if (rescue.detected) {
647
+ if (!rescue.calls) {
648
+ throw new Error(`unparseable inline tool-call dialect from ${route.displayName}: ${respText.slice(0, 120)}`);
649
+ }
650
+ const schemas = toolSchemaMap(tools);
651
+ respMsg.tool_calls = rescuedToToolCalls(rescue.calls, schemas);
652
+ respMsg.content = rescue.cleanText.length > 0 ? rescue.cleanText : null;
653
+ if (result.choices?.[0])
654
+ result.choices[0].finish_reason = 'tool_calls';
655
+ respToolCalls = respMsg.tool_calls;
656
+ console.log(`[Anthropic] Rescued ${rescue.calls.length} inline tool call(s) from ${route.displayName} into tool_use`);
657
+ }
658
+ }
659
+ // Repair double-encoded tool arguments against the request schemas, so
660
+ // strict Anthropic clients (Claude Code) get clean JSON inputs.
661
+ if (respToolCalls.length) {
662
+ const schemas = toolSchemaMap(tools);
663
+ for (const tc of respToolCalls) {
664
+ if (tc?.function?.arguments != null) {
665
+ tc.function.arguments = repairToolArguments(tc.function.arguments, schemas.get(tc.function.name));
666
+ }
667
+ }
668
+ // Opt-in schema verdict on what the repair could not fix. This surface
669
+ // is where the silent failure was worst: parseToolInput turns
670
+ // unparseable arguments into `input: {}`, so the client sees a tool_use
671
+ // block with nothing in it and no indication anything went wrong.
672
+ if (isToolArgumentValidationEnabled()) {
673
+ const invalid = invalidToolCallReasons(respToolCalls, schemas);
674
+ if (invalid.length > 0)
675
+ throw invalidToolArgumentsError(route.displayName, invalid);
676
+ }
677
+ }
678
+ const promptTokens = result.usage?.prompt_tokens ?? estimatedInputTokens;
679
+ const completionTokens = result.usage?.completion_tokens ?? Math.ceil((respText.length + respToolCalls.reduce((n, c) => n + c.function.arguments.length, 0)) / 4);
680
+ recordUpstreamSuccess(route, result.usage?.total_tokens ?? promptTokens + completionTokens, state);
681
+ // Remember this model for the rest of the auto-routed session. A pin used
682
+ // to make this a no-op (the pin fixed the model); a group pin still has a
683
+ // provider choice to remember, recorded under the group's own scope.
684
+ if (!resolved.pinned || stickyScope)
685
+ setStickyModel(messages, route.modelDbId, sessionId, stickyScope ?? strategyKey);
686
+ const anthropicResponse = {
687
+ id: newMessageId(),
688
+ type: 'message',
689
+ role: 'assistant',
690
+ model: requestedModel,
691
+ content: toAnthropicContent(respMsg),
692
+ stop_reason: mapStopReason(result.choices?.[0]?.finish_reason ?? null, respToolCalls.length > 0),
693
+ stop_sequence: null,
694
+ usage: { input_tokens: promptTokens, output_tokens: completionTokens },
695
+ };
696
+ res.setHeader('X-Routed-Via', routedViaValue(route.platform, route.modelId));
697
+ setFallbackHeaders(res, attempt, attemptLog);
698
+ logRequest(route.platform, route.modelId, route.keyId, 'success', promptTokens, completionTokens, Date.now() - start, null, null, pinnedModelId, null, 'http');
699
+ res.json(anthropicResponse);
700
+ return 'done';
701
+ },
702
+ logFailure: (route, err) => {
703
+ logRequest(route.platform, route.modelId, route.keyId, 'error', estimatedInputTokens, 0, Date.now() - start, sanitizeProviderErrorMessage(err.message), null, pinnedModelId, null, 'http');
704
+ },
705
+ onFatal: (route, err, attempt) => {
706
+ setFallbackHeaders(res, attempt, attemptLog);
707
+ sendError(res, 502, 'api_error', `Provider error (${route.displayName}): ${sanitizeProviderErrorMessage(err.message)}`);
708
+ },
709
+ onRoutingExhausted: (lastError, routeErr, exhaustion, info) => {
710
+ setFallbackHeaders(res, info.attempts.length, info.attempts);
711
+ sendExhaustion(res, exhaustion);
712
+ },
713
+ onExhausted: (exhaustion, info) => {
714
+ setFallbackHeaders(res, info.attempts.length, info.attempts);
715
+ sendExhaustion(res, exhaustion);
716
+ },
717
+ });
718
+ });
719
+ // Thrown by streamCompletion once the SSE response is underway, so the outer
720
+ // loop knows the request is finished (honestly errored mid-stream) and must
721
+ // not fail over or send a second response.
722
+ class StreamAlreadyStarted extends Error {
723
+ }
724
+ // Consume the provider's OpenAI-style stream and re-emit it as the Anthropic
725
+ // SSE event sequence Claude Code expects:
726
+ // message_start → (content_block_start → content_block_delta* →
727
+ // content_block_stop)* → message_delta → message_stop
728
+ // Text is streamed incrementally as text_delta; tool calls are buffered across
729
+ // deltas and emitted as tool_use blocks (input_json_delta) once complete — the
730
+ // same buffering the OpenAI route does, since partial tool-call JSON isn't
731
+ // safely forwardable mid-flight.
732
+ async function streamCompletion(res, route, messages, options, ctx) {
733
+ let messageStarted = false; // doubles as "headers + message_start sent" = committed
734
+ let textBlockOpen = false;
735
+ let textBlockIndex = -1;
736
+ let nextIndex = 0;
737
+ let outputChars = 0;
738
+ let upstreamFinish = null;
739
+ const toolAcc = new Map();
740
+ // Inline-dialect hold window (#231): the first text is held until it either
741
+ // matches a tool-call dialect marker (held to the end and rescued into
742
+ // tool_use blocks) or provably cannot (flushed and streamed normally). This is
743
+ // also the commit point convergence — message_start is not sent until the
744
+ // first MEANINGFUL content, so a stream that opens with a dialect marker and
745
+ // then turns out unparseable fails over invisibly (previously this surface
746
+ // committed on the first non-empty text and had no dialect rescue at all).
747
+ let dialectMode = 'undecided';
748
+ let heldText = '';
749
+ const ensureMessageStart = () => {
750
+ if (messageStarted)
751
+ return;
752
+ res.setHeader('Content-Type', 'text/event-stream');
753
+ res.setHeader('Cache-Control', 'no-cache');
754
+ res.setHeader('Connection', 'keep-alive');
755
+ res.setHeader('X-Routed-Via', routedViaValue(route.platform, route.modelId));
756
+ setFallbackHeaders(res, ctx.attempt, ctx.attemptLog);
757
+ // Committed: the answer is on its way, so the retry budget must no longer
758
+ // cancel this attempt (it could not fail over now anyway).
759
+ ctx.disarmHedge();
760
+ writeSse(res, 'message_start', {
761
+ type: 'message_start',
762
+ message: {
763
+ id: newMessageId(), type: 'message', role: 'assistant', model: ctx.requestedModel,
764
+ content: [], stop_reason: null, stop_sequence: null,
765
+ usage: { input_tokens: ctx.estimatedInputTokens, output_tokens: 0 },
766
+ },
767
+ });
768
+ messageStarted = true;
769
+ };
770
+ // Reasoning output (delta.reasoning_content from providers / the <think>
771
+ // extractor, delta.reasoning from Ollama-style upstreams) is buffered and
772
+ // emitted as one complete thinking block right before the first text/tool
773
+ // block. Buffering (not live thinking_delta streaming) keeps the commit
774
+ // point where it was: a stream that produces ONLY reasoning and dies still
775
+ // fails over invisibly, exactly like the OpenAI surface's preamble hold.
776
+ let reasoningBuf = '';
777
+ const flushThinking = () => {
778
+ if (reasoningBuf.length === 0)
779
+ return;
780
+ const idx = nextIndex++;
781
+ writeSse(res, 'content_block_start', { type: 'content_block_start', index: idx, content_block: { type: 'thinking', thinking: '' } });
782
+ writeSse(res, 'content_block_delta', { type: 'content_block_delta', index: idx, delta: { type: 'thinking_delta', thinking: reasoningBuf } });
783
+ writeSse(res, 'content_block_stop', { type: 'content_block_stop', index: idx });
784
+ outputChars += reasoningBuf.length;
785
+ reasoningBuf = '';
786
+ };
787
+ // Commit (if needed), open the text block (if needed), and stream `text`.
788
+ const emitText = (text) => {
789
+ ensureMessageStart();
790
+ if (!textBlockOpen) {
791
+ flushThinking(); // thinking block precedes the text block
792
+ textBlockIndex = nextIndex++;
793
+ writeSse(res, 'content_block_start', { type: 'content_block_start', index: textBlockIndex, content_block: { type: 'text', text: '' } });
794
+ textBlockOpen = true;
795
+ }
796
+ writeSse(res, 'content_block_delta', { type: 'content_block_delta', index: textBlockIndex, delta: { type: 'text_delta', text } });
797
+ outputChars += text.length;
798
+ };
799
+ try {
800
+ const gen = route.provider.streamChatCompletion(route.apiKey, messages, route.modelId, options);
801
+ for await (const chunk of gen) {
802
+ if (ctx.clientGone())
803
+ break; // client hung up: stop pulling; reader.cancel() aborts upstream
804
+ const anyChunk = chunk;
805
+ // In-band provider error frame (e.g. Groq emits {"error":…} inside a 200
806
+ // stream). Before message_start: retryable → fail over invisibly. After:
807
+ // surface an Anthropic `error` event and stop.
808
+ if (anyChunk.error && !anyChunk.choices) {
809
+ const msg = anyChunk.error.message ?? JSON.stringify(anyChunk.error).slice(0, 200);
810
+ if (!messageStarted)
811
+ throw new Error(`in-band provider error from ${route.displayName}: ${msg}`);
812
+ writeSse(res, 'error', { type: 'error', error: { type: 'api_error', message: `Provider error (${route.displayName}): ${sanitizeProviderErrorMessage(String(msg))}` } });
813
+ res.end();
814
+ logRequest(route.platform, route.modelId, route.keyId, 'error', ctx.estimatedInputTokens, outputChars, Date.now() - ctx.start, `in-band error frame: ${sanitizeProviderErrorMessage(String(msg))}`, null, ctx.pinnedModelId, null, 'http');
815
+ throw new StreamAlreadyStarted();
816
+ }
817
+ const choice = anyChunk.choices?.[0];
818
+ if (!choice)
819
+ continue;
820
+ if (choice.finish_reason)
821
+ upstreamFinish = choice.finish_reason;
822
+ for (const tc of choice.delta?.tool_calls ?? []) {
823
+ const idx = tc.index ?? 0;
824
+ if (!toolAcc.has(idx))
825
+ toolAcc.set(idx, { id: undefined, name: '', args: '' });
826
+ const acc = toolAcc.get(idx);
827
+ if (tc.id && !acc.id)
828
+ acc.id = tc.id;
829
+ if (tc.function?.name)
830
+ acc.name += tc.function.name;
831
+ if (tc.function?.arguments)
832
+ acc.args += tc.function.arguments;
833
+ const thoughtSignature = tc.thought_signature ?? tc.thoughtSignature;
834
+ if (typeof thoughtSignature === 'string' && thoughtSignature.length > 0 && !acc.thought_signature) {
835
+ acc.thought_signature = thoughtSignature;
836
+ }
837
+ }
838
+ // Reasoning deltas: providers emit reasoning_content (or Ollama-style
839
+ // `reasoning`); the shared <think> extractor emits reasoning_content.
840
+ const reasoningDelta = choice.delta?.reasoning_content ?? choice.delta?.reasoning;
841
+ if (typeof reasoningDelta === 'string' && reasoningDelta.length > 0)
842
+ reasoningBuf += reasoningDelta;
843
+ const text = typeof choice.delta?.content === 'string' ? choice.delta.content : '';
844
+ if (text.length === 0)
845
+ continue;
846
+ if (dialectMode === 'passthrough') {
847
+ emitText(text);
848
+ }
849
+ else {
850
+ heldText += text;
851
+ if (dialectMode === 'undecided') {
852
+ const probe = heldText.trimStart();
853
+ if (startsWithDialectMarker(probe)) {
854
+ dialectMode = 'dialect';
855
+ }
856
+ else if (!couldBecomeDialectMarker(probe) || heldText.length > 256) {
857
+ dialectMode = 'passthrough';
858
+ emitText(heldText);
859
+ heldText = '';
860
+ }
861
+ }
862
+ // else: still a strict prefix of a marker — keep holding.
863
+ }
864
+ }
865
+ // Assemble buffered tool calls: synthesize missing ids, repair args against
866
+ // the request schemas, drop any that still aren't valid JSON.
867
+ const schemas = toolSchemaMap(ctx.tools);
868
+ let synthetic = 0;
869
+ const completedCalls = [...toolAcc.entries()]
870
+ .sort((a, b) => a[0] - b[0])
871
+ .map(([, acc]) => ({
872
+ id: acc.id && acc.id.length > 0 ? acc.id : `toolu_${crypto.randomBytes(8).toString('hex')}_${++synthetic}`,
873
+ name: acc.name,
874
+ arguments: repairToolArguments(acc.args || '{}', schemas.get(acc.name)),
875
+ ...(acc.thought_signature ? { thought_signature: acc.thought_signature } : {}),
876
+ }))
877
+ .filter(c => { try {
878
+ JSON.parse(c.arguments);
879
+ return c.name.length > 0;
880
+ }
881
+ catch {
882
+ return false;
883
+ } });
884
+ // Resolve the dialect hold window now the full text is known. Held text was
885
+ // never emitted, so a dead dialect turn can still fail over (nothing has been
886
+ // committed). A rescued dialect becomes tool_use blocks; leftover clean text
887
+ // is emitted as a text block first.
888
+ if (heldText.length > 0) {
889
+ const rescue = ((ctx.tools?.length ?? 0) > 0 && (dialectMode === 'dialect' || containsDialectMarker(heldText)))
890
+ ? rescueInlineToolCalls(heldText, new Set((ctx.tools ?? []).map(t => t.function.name)))
891
+ : { detected: false, calls: null, cleanText: heldText };
892
+ if (rescue.detected && !rescue.calls) {
893
+ throw new Error(`unparseable inline tool-call dialect from ${route.displayName}: ${heldText.slice(0, 120)}`);
894
+ }
895
+ if (rescue.detected && rescue.calls) {
896
+ if (rescue.cleanText.length > 0)
897
+ emitText(rescue.cleanText);
898
+ for (const c of rescue.calls) {
899
+ const repaired = repairToolArguments(c.arguments, schemas.get(c.name));
900
+ if (c.name.length === 0)
901
+ continue;
902
+ try {
903
+ JSON.parse(repaired);
904
+ }
905
+ catch {
906
+ continue;
907
+ }
908
+ completedCalls.push({ id: `toolu_${crypto.randomBytes(8).toString('hex')}_rescued_${++synthetic}`, name: c.name, arguments: repaired });
909
+ }
910
+ console.log(`[Anthropic] Rescued ${rescue.calls.length} inline tool call(s) from ${route.displayName} into tool_use`);
911
+ }
912
+ else {
913
+ emitText(heldText);
914
+ }
915
+ heldText = '';
916
+ }
917
+ // Opt-in schema verdict, same rule as the non-streaming surface above and
918
+ // as /chat/completions: this is the surface the silent failure hurt most,
919
+ // and Claude Code streams. Placed after the dialect rescue so a rescued
920
+ // call is judged too, and gated on `!messageStarted` — once message_start
921
+ // has gone out there is no failing over, and tearing the SSE stream down
922
+ // would be worse for the client than a tool_use the schema dislikes.
923
+ if (isToolArgumentValidationEnabled() && !messageStarted && completedCalls.length > 0) {
924
+ const invalid = invalidToolCallReasons(completedCalls.map(c => ({ function: { name: c.name, arguments: c.arguments } })), schemas);
925
+ if (invalid.length > 0)
926
+ throw invalidToolArgumentsError(route.displayName, invalid);
927
+ }
928
+ // Nothing usable came out — fail over (message_start was never sent, so the
929
+ // client never saw this attempt).
930
+ if (!messageStarted && completedCalls.length === 0) {
931
+ // Disconnect before the commit point: the pump loop broke with nothing
932
+ // accumulated because the CLIENT left, not because the provider failed —
933
+ // benching a healthy model+key for it was wrong. StreamAlreadyStarted
934
+ // maps to 'committed' in the dispatcher: stop, no failover, no
935
+ // exhaustion render (the socket is gone anyway).
936
+ if (ctx.clientGone()) {
937
+ console.log(`[Anthropic] client disconnected before first token from ${route.displayName} — dropping attempt without benching`);
938
+ throw new StreamAlreadyStarted();
939
+ }
940
+ // finish_reason 'length' = the model spent the whole output budget on
941
+ // hidden reasoning before any visible text: fail over, but skip the
942
+ // cooldown/penalty (not a provider-health signal).
943
+ throw Object.assign(new Error(`empty completion from ${route.displayName} (stream produced no content and no tool calls)`), upstreamFinish === 'length' ? { skipBench: true } : {});
944
+ }
945
+ ensureMessageStart();
946
+ if (textBlockOpen)
947
+ writeSse(res, 'content_block_stop', { type: 'content_block_stop', index: textBlockIndex });
948
+ // Residual reasoning: tool-only turns (no text block ever opened) and
949
+ // reasoning that arrived after the text block was already streaming.
950
+ flushThinking();
951
+ for (const call of completedCalls) {
952
+ const idx = nextIndex++;
953
+ writeSse(res, 'content_block_start', {
954
+ type: 'content_block_start',
955
+ index: idx,
956
+ content_block: {
957
+ type: 'tool_use',
958
+ id: call.id,
959
+ name: call.name,
960
+ input: {},
961
+ ...(call.thought_signature ? { thought_signature: call.thought_signature } : {}),
962
+ },
963
+ });
964
+ writeSse(res, 'content_block_delta', { type: 'content_block_delta', index: idx, delta: { type: 'input_json_delta', partial_json: call.arguments } });
965
+ writeSse(res, 'content_block_stop', { type: 'content_block_stop', index: idx });
966
+ outputChars += call.arguments.length;
967
+ }
968
+ const stopReason = mapStopReason(upstreamFinish, completedCalls.length > 0);
969
+ const outputTokens = Math.ceil(outputChars / 4);
970
+ writeSse(res, 'message_delta', { type: 'message_delta', delta: { stop_reason: stopReason, stop_sequence: null }, usage: { output_tokens: outputTokens } });
971
+ writeSse(res, 'message_stop', { type: 'message_stop' });
972
+ res.end();
973
+ recordUpstreamSuccess(route, ctx.estimatedInputTokens + outputTokens, ctx.state);
974
+ if (!ctx.pinned || ctx.stickyScope)
975
+ setStickyModel(messages, route.modelDbId, ctx.sessionId, ctx.stickyScope ?? ctx.strategyKey);
976
+ logRequest(route.platform, route.modelId, route.keyId, 'success', ctx.estimatedInputTokens, outputTokens, Date.now() - ctx.start, null, null, ctx.pinnedModelId, null, 'http');
977
+ }
978
+ catch (err) {
979
+ if (err instanceof StreamAlreadyStarted)
980
+ throw err;
981
+ // Client abort mid-stream: the pump's own `if (ctx.clientGone()) break`
982
+ // can lose the race against the fetch-signal rejection, so the abort may
983
+ // surface here instead. Rethrow — the shared loop's client-abort branch
984
+ // stops the ladder without benching or an error log row (the socket is
985
+ // gone; nothing to render).
986
+ if (isClientAbortError(err))
987
+ throw err;
988
+ if (messageStarted) {
989
+ // Real payload already reached the client — finish the SSE response
990
+ // honestly instead of leaving Claude Code hanging, and stop the retry loop.
991
+ writeSse(res, 'error', { type: 'error', error: { type: 'api_error', message: `Provider error (${route.displayName}): stream interrupted` } });
992
+ try {
993
+ res.end();
994
+ }
995
+ catch { /* socket gone */ }
996
+ logRequest(route.platform, route.modelId, route.keyId, 'error', ctx.estimatedInputTokens, outputChars, Date.now() - ctx.start, sanitizeProviderErrorMessage(err.message), null, ctx.pinnedModelId, null, 'http');
997
+ throw new StreamAlreadyStarted();
998
+ }
999
+ // Headers never sent — bubble to the outer loop for failover.
1000
+ throw err;
1001
+ }
1002
+ }
1003
+ // Anthropic token-counting endpoint. Claude Code calls this to size context
1004
+ // windows; we return a heuristic estimate (the proxy doesn't run a tokenizer).
1005
+ anthropicRouter.post('/messages/count_tokens', (req, res) => {
1006
+ if (!authenticate(req, res))
1007
+ return;
1008
+ const parsed = messagesSchema.safeParse(req.body);
1009
+ if (!parsed.success) {
1010
+ sendError(res, 400, 'invalid_request_error', 'Invalid request');
1011
+ return;
1012
+ }
1013
+ const converted = convertRequest(parsed.data);
1014
+ // Same verdict as POST /messages, so a client sizing a context window learns
1015
+ // the document is unusable here rather than getting a count for a prompt we
1016
+ // would go on to refuse.
1017
+ if (converted.documentRejections.length > 0) {
1018
+ sendError(res, 400, 'invalid_request_error', documentRejectionMessage(converted.documentRejections));
1019
+ return;
1020
+ }
1021
+ const { messages, tools } = converted;
1022
+ const compressionResult = compressRequest(messages, {
1023
+ header: req.headers['x-freellm-compress'],
1024
+ tools,
1025
+ recordStats: false,
1026
+ });
1027
+ res.setHeader('X-FreeLLM-Compress', formatCompressionHeader(compressionResult));
1028
+ res.json({ input_tokens: estimateInputTokens(compressionResult.messages, tools) });
1029
+ });
1030
+ // Anthropic-compatible GET /v1/models. Content-negotiated: only answers when
1031
+ // the caller speaks Anthropic (sends an `anthropic-version` header, as Claude
1032
+ // Code does) — otherwise it calls next() and the OpenAI-shaped handler in
1033
+ // proxyRouter serves the same path. Lists the SAME catalog as the OpenAI
1034
+ // endpoint (real free models that can serve a request right now, plus "auto").
1035
+ // Nothing here is hosted Claude, and no entry claims to be.
1036
+ //
1037
+ // One entry per Claude family (claude-sonnet-4-5 and friends) rides along so
1038
+ // clients that only accept Claude-shaped ids can discover anything at all; see
1039
+ // CLAUDE_FAMILY_ALIASES for why, and note /messages already routed those ids
1040
+ // long before they were listed here.
1041
+ //
1042
+ // Optional `claude/<real-id>` aliases let Claude Code's gateway picker discover
1043
+ // the full catalog; /messages strips the synthetic prefix before routing.
1044
+ anthropicRouter.get('/models', (req, res, next) => {
1045
+ if (!req.headers['anthropic-version'])
1046
+ return next(); // OpenAI client → proxyRouter
1047
+ if (!authenticate(req, res))
1048
+ return;
1049
+ const { models } = buildModelListing();
1050
+ const available = models.filter(m => m.available === 1);
1051
+ const aliasesEnabled = getSetting('expose_cc_discovery_aliases') === '1';
1052
+ const data = [
1053
+ { type: 'model', id: 'auto', display_name: 'Auto (router picks the best available model)', created_at: MODEL_CREATED_AT },
1054
+ // Only when something can actually serve them: advertising a Sonnet slot
1055
+ // backed by an empty pool would trade "0 models" for a model that 503s.
1056
+ ...(available.length > 0
1057
+ ? claudeFamilyDiscoveryEntries().map(a => ({
1058
+ type: 'model',
1059
+ id: a.id,
1060
+ display_name: a.displayName,
1061
+ created_at: MODEL_CREATED_AT,
1062
+ }))
1063
+ : []),
1064
+ ...available.map(m => ({ type: 'model', id: m.id, display_name: m.name, created_at: MODEL_CREATED_AT })),
1065
+ ...(aliasesEnabled
1066
+ ? available.map(m => ({
1067
+ type: 'model',
1068
+ id: `claude/${m.id}`,
1069
+ display_name: `${m.name} (Claude Code)`,
1070
+ created_at: MODEL_CREATED_AT,
1071
+ }))
1072
+ : []),
1073
+ ];
1074
+ res.json({ data, has_more: false, first_id: data[0]?.id ?? null, last_id: data[data.length - 1]?.id ?? null });
1075
+ });
1076
+ //# sourceMappingURL=anthropic.js.map