@antseed/cli 0.1.178 → 0.1.180

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 (685) hide show
  1. package/README.md +5 -0
  2. package/dist/buyer-identities/store.d.ts +39 -2
  3. package/dist/buyer-identities/store.d.ts.map +1 -1
  4. package/dist/buyer-identities/store.js +99 -10
  5. package/dist/buyer-identities/store.js.map +1 -1
  6. package/dist/cli/commands/apps/index.d.ts +42 -0
  7. package/dist/cli/commands/apps/index.d.ts.map +1 -0
  8. package/dist/cli/commands/apps/index.js +224 -0
  9. package/dist/cli/commands/apps/index.js.map +1 -0
  10. package/dist/cli/commands/apps/index.test.d.ts +2 -0
  11. package/dist/cli/commands/apps/index.test.d.ts.map +1 -0
  12. package/dist/cli/commands/apps/index.test.js +206 -0
  13. package/dist/cli/commands/apps/index.test.js.map +1 -0
  14. package/dist/cli/commands/buyer/deposit.d.ts.map +1 -1
  15. package/dist/cli/commands/buyer/deposit.js +9 -1
  16. package/dist/cli/commands/buyer/deposit.js.map +1 -1
  17. package/dist/cli/commands/buyer/identity.d.ts +3 -1
  18. package/dist/cli/commands/buyer/identity.d.ts.map +1 -1
  19. package/dist/cli/commands/buyer/identity.js +55 -8
  20. package/dist/cli/commands/buyer/identity.js.map +1 -1
  21. package/dist/cli/commands/buyer/identity.test.d.ts +2 -0
  22. package/dist/cli/commands/buyer/identity.test.d.ts.map +1 -0
  23. package/dist/cli/commands/buyer/identity.test.js +168 -0
  24. package/dist/cli/commands/buyer/identity.test.js.map +1 -0
  25. package/dist/cli/commands/buyer/set-authorized-wallet.d.ts +10 -0
  26. package/dist/cli/commands/buyer/set-authorized-wallet.d.ts.map +1 -1
  27. package/dist/cli/commands/buyer/set-authorized-wallet.js +26 -4
  28. package/dist/cli/commands/buyer/set-authorized-wallet.js.map +1 -1
  29. package/dist/cli/commands/buyer/set-authorized-wallet.test.js +37 -2
  30. package/dist/cli/commands/buyer/set-authorized-wallet.test.js.map +1 -1
  31. package/dist/cli/commands/buyer/start.d.ts +15 -0
  32. package/dist/cli/commands/buyer/start.d.ts.map +1 -1
  33. package/dist/cli/commands/buyer/start.js +158 -48
  34. package/dist/cli/commands/buyer/start.js.map +1 -1
  35. package/dist/cli/commands/buyer/start.test.js +16 -1
  36. package/dist/cli/commands/buyer/start.test.js.map +1 -1
  37. package/dist/cli/commands/chat.d.ts +3 -0
  38. package/dist/cli/commands/chat.d.ts.map +1 -0
  39. package/dist/cli/commands/chat.js +189 -0
  40. package/dist/cli/commands/chat.js.map +1 -0
  41. package/dist/cli/commands/gateway/activity.d.ts +15 -0
  42. package/dist/cli/commands/gateway/activity.d.ts.map +1 -0
  43. package/dist/cli/commands/gateway/activity.js +233 -0
  44. package/dist/cli/commands/gateway/activity.js.map +1 -0
  45. package/dist/cli/commands/gateway/admin-token.d.ts +3 -0
  46. package/dist/cli/commands/gateway/admin-token.d.ts.map +1 -0
  47. package/dist/cli/commands/gateway/admin-token.js +90 -0
  48. package/dist/cli/commands/gateway/admin-token.js.map +1 -0
  49. package/dist/cli/commands/gateway/console-link.d.ts +20 -0
  50. package/dist/cli/commands/gateway/console-link.d.ts.map +1 -0
  51. package/dist/cli/commands/gateway/console-link.js +92 -0
  52. package/dist/cli/commands/gateway/console-link.js.map +1 -0
  53. package/dist/cli/commands/gateway/gateway-commands.test.d.ts +2 -0
  54. package/dist/cli/commands/gateway/gateway-commands.test.d.ts.map +1 -0
  55. package/dist/cli/commands/gateway/gateway-commands.test.js +230 -0
  56. package/dist/cli/commands/gateway/gateway-commands.test.js.map +1 -0
  57. package/dist/cli/commands/gateway/gateway-console-commands.test.d.ts +2 -0
  58. package/dist/cli/commands/gateway/gateway-console-commands.test.d.ts.map +1 -0
  59. package/dist/cli/commands/gateway/gateway-console-commands.test.js +373 -0
  60. package/dist/cli/commands/gateway/gateway-console-commands.test.js.map +1 -0
  61. package/dist/cli/commands/gateway/index.d.ts +8 -0
  62. package/dist/cli/commands/gateway/index.d.ts.map +1 -1
  63. package/dist/cli/commands/gateway/index.js +93 -14
  64. package/dist/cli/commands/gateway/index.js.map +1 -1
  65. package/dist/cli/commands/gateway/key.d.ts +4 -0
  66. package/dist/cli/commands/gateway/key.d.ts.map +1 -1
  67. package/dist/cli/commands/gateway/key.js +297 -182
  68. package/dist/cli/commands/gateway/key.js.map +1 -1
  69. package/dist/cli/commands/gateway/member.d.ts +3 -0
  70. package/dist/cli/commands/gateway/member.d.ts.map +1 -0
  71. package/dist/cli/commands/gateway/member.js +302 -0
  72. package/dist/cli/commands/gateway/member.js.map +1 -0
  73. package/dist/cli/commands/gateway/migrate.d.ts +18 -0
  74. package/dist/cli/commands/gateway/migrate.d.ts.map +1 -0
  75. package/dist/cli/commands/gateway/migrate.js +158 -0
  76. package/dist/cli/commands/gateway/migrate.js.map +1 -0
  77. package/dist/cli/commands/gateway/operator.d.ts +11 -0
  78. package/dist/cli/commands/gateway/operator.d.ts.map +1 -0
  79. package/dist/cli/commands/gateway/operator.js +199 -0
  80. package/dist/cli/commands/gateway/operator.js.map +1 -0
  81. package/dist/cli/commands/gateway/operator.test.d.ts +2 -0
  82. package/dist/cli/commands/gateway/operator.test.d.ts.map +1 -0
  83. package/dist/cli/commands/gateway/operator.test.js +151 -0
  84. package/dist/cli/commands/gateway/operator.test.js.map +1 -0
  85. package/dist/cli/commands/gateway/peer-list.d.ts +3 -0
  86. package/dist/cli/commands/gateway/peer-list.d.ts.map +1 -0
  87. package/dist/cli/commands/gateway/peer-list.js +107 -0
  88. package/dist/cli/commands/gateway/peer-list.js.map +1 -0
  89. package/dist/cli/commands/gateway/policy-options.d.ts +23 -0
  90. package/dist/cli/commands/gateway/policy-options.d.ts.map +1 -0
  91. package/dist/cli/commands/gateway/policy-options.js +156 -0
  92. package/dist/cli/commands/gateway/policy-options.js.map +1 -0
  93. package/dist/cli/commands/gateway/preset.d.ts +3 -0
  94. package/dist/cli/commands/gateway/preset.d.ts.map +1 -0
  95. package/dist/cli/commands/gateway/preset.js +181 -0
  96. package/dist/cli/commands/gateway/preset.js.map +1 -0
  97. package/dist/cli/commands/gateway/routing.d.ts +3 -0
  98. package/dist/cli/commands/gateway/routing.d.ts.map +1 -0
  99. package/dist/cli/commands/gateway/routing.js +110 -0
  100. package/dist/cli/commands/gateway/routing.js.map +1 -0
  101. package/dist/cli/commands/gateway/settings.d.ts +4 -0
  102. package/dist/cli/commands/gateway/settings.d.ts.map +1 -0
  103. package/dist/cli/commands/gateway/settings.js +314 -0
  104. package/dist/cli/commands/gateway/settings.js.map +1 -0
  105. package/dist/cli/commands/gateway/shared.d.ts +55 -6
  106. package/dist/cli/commands/gateway/shared.d.ts.map +1 -1
  107. package/dist/cli/commands/gateway/shared.js +206 -26
  108. package/dist/cli/commands/gateway/shared.js.map +1 -1
  109. package/dist/cli/commands/gateway/workspace.d.ts +3 -0
  110. package/dist/cli/commands/gateway/workspace.d.ts.map +1 -0
  111. package/dist/cli/commands/gateway/workspace.js +250 -0
  112. package/dist/cli/commands/gateway/workspace.js.map +1 -0
  113. package/dist/cli/commands/tunnel/start.d.ts.map +1 -1
  114. package/dist/cli/commands/tunnel/start.js +23 -5
  115. package/dist/cli/commands/tunnel/start.js.map +1 -1
  116. package/dist/cli/index.js +12 -42
  117. package/dist/cli/index.js.map +1 -1
  118. package/dist/cli/program.d.ts +4 -0
  119. package/dist/cli/program.d.ts.map +1 -0
  120. package/dist/cli/program.js +49 -0
  121. package/dist/cli/program.js.map +1 -0
  122. package/dist/cli/program.test.d.ts +2 -0
  123. package/dist/cli/program.test.d.ts.map +1 -0
  124. package/dist/cli/program.test.js +38 -0
  125. package/dist/cli/program.test.js.map +1 -0
  126. package/dist/cli/prompt-hidden.d.ts +3 -0
  127. package/dist/cli/prompt-hidden.d.ts.map +1 -0
  128. package/dist/cli/prompt-hidden.js +38 -0
  129. package/dist/cli/prompt-hidden.js.map +1 -0
  130. package/dist/cli/wallet-passphrase.d.ts +48 -0
  131. package/dist/cli/wallet-passphrase.d.ts.map +1 -0
  132. package/dist/cli/wallet-passphrase.js +119 -0
  133. package/dist/cli/wallet-passphrase.js.map +1 -0
  134. package/dist/cli/wallet-passphrase.test.d.ts +2 -0
  135. package/dist/cli/wallet-passphrase.test.d.ts.map +1 -0
  136. package/dist/cli/wallet-passphrase.test.js +79 -0
  137. package/dist/cli/wallet-passphrase.test.js.map +1 -0
  138. package/dist/config/effective.d.ts +12 -0
  139. package/dist/config/effective.d.ts.map +1 -1
  140. package/dist/config/effective.js +39 -6
  141. package/dist/config/effective.js.map +1 -1
  142. package/dist/config/effective.test.js +9 -1
  143. package/dist/config/effective.test.js.map +1 -1
  144. package/dist/config/loader.d.ts +6 -0
  145. package/dist/config/loader.d.ts.map +1 -1
  146. package/dist/config/loader.js +12 -0
  147. package/dist/config/loader.js.map +1 -1
  148. package/dist/config/loader.test.js +7 -1
  149. package/dist/config/loader.test.js.map +1 -1
  150. package/dist/config/types.d.ts +14 -0
  151. package/dist/config/types.d.ts.map +1 -1
  152. package/dist/config/validation.d.ts.map +1 -1
  153. package/dist/config/validation.js +19 -0
  154. package/dist/config/validation.js.map +1 -1
  155. package/dist/gateway/accounting.d.ts +33 -3
  156. package/dist/gateway/accounting.d.ts.map +1 -1
  157. package/dist/gateway/accounting.js +86 -7
  158. package/dist/gateway/accounting.js.map +1 -1
  159. package/dist/gateway/admission-benchmark.test.d.ts +2 -0
  160. package/dist/gateway/admission-benchmark.test.d.ts.map +1 -0
  161. package/dist/gateway/admission-benchmark.test.js +75 -0
  162. package/dist/gateway/admission-benchmark.test.js.map +1 -0
  163. package/dist/gateway/auth/auth.test.d.ts +4 -0
  164. package/dist/gateway/auth/auth.test.d.ts.map +1 -0
  165. package/dist/gateway/auth/auth.test.js +337 -0
  166. package/dist/gateway/auth/auth.test.js.map +1 -0
  167. package/dist/gateway/auth/credentials.test.d.ts +2 -0
  168. package/dist/gateway/auth/credentials.test.d.ts.map +1 -0
  169. package/dist/gateway/auth/credentials.test.js +132 -0
  170. package/dist/gateway/auth/credentials.test.js.map +1 -0
  171. package/dist/gateway/auth/db.d.ts +156 -0
  172. package/dist/gateway/auth/db.d.ts.map +1 -0
  173. package/dist/gateway/auth/db.js +390 -0
  174. package/dist/gateway/auth/db.js.map +1 -0
  175. package/dist/gateway/auth/hardening.test.d.ts +2 -0
  176. package/dist/gateway/auth/hardening.test.d.ts.map +1 -0
  177. package/dist/gateway/auth/hardening.test.js +155 -0
  178. package/dist/gateway/auth/hardening.test.js.map +1 -0
  179. package/dist/gateway/auth/http.d.ts +53 -0
  180. package/dist/gateway/auth/http.d.ts.map +1 -0
  181. package/dist/gateway/auth/http.js +165 -0
  182. package/dist/gateway/auth/http.js.map +1 -0
  183. package/dist/gateway/auth/index.d.ts +26 -0
  184. package/dist/gateway/auth/index.d.ts.map +1 -0
  185. package/dist/gateway/auth/index.js +1014 -0
  186. package/dist/gateway/auth/index.js.map +1 -0
  187. package/dist/gateway/auth/oidc.d.ts +57 -0
  188. package/dist/gateway/auth/oidc.d.ts.map +1 -0
  189. package/dist/gateway/auth/oidc.js +159 -0
  190. package/dist/gateway/auth/oidc.js.map +1 -0
  191. package/dist/gateway/auth/oidc.test.d.ts +2 -0
  192. package/dist/gateway/auth/oidc.test.d.ts.map +1 -0
  193. package/dist/gateway/auth/oidc.test.js +223 -0
  194. package/dist/gateway/auth/oidc.test.js.map +1 -0
  195. package/dist/gateway/auth/reauth.test.d.ts +2 -0
  196. package/dist/gateway/auth/reauth.test.d.ts.map +1 -0
  197. package/dist/gateway/auth/reauth.test.js +108 -0
  198. package/dist/gateway/auth/reauth.test.js.map +1 -0
  199. package/dist/gateway/auth/store-integration.test.d.ts +2 -0
  200. package/dist/gateway/auth/store-integration.test.d.ts.map +1 -0
  201. package/dist/gateway/auth/store-integration.test.js +100 -0
  202. package/dist/gateway/auth/store-integration.test.js.map +1 -0
  203. package/dist/gateway/auth/test-helpers.d.ts +167 -0
  204. package/dist/gateway/auth/test-helpers.d.ts.map +1 -0
  205. package/dist/gateway/auth/test-helpers.js +368 -0
  206. package/dist/gateway/auth/test-helpers.js.map +1 -0
  207. package/dist/gateway/auth/types.d.ts +145 -0
  208. package/dist/gateway/auth/types.d.ts.map +1 -0
  209. package/dist/gateway/auth/types.js +2 -0
  210. package/dist/gateway/auth/types.js.map +1 -0
  211. package/dist/gateway/auth/wallet.d.ts +15 -0
  212. package/dist/gateway/auth/wallet.d.ts.map +1 -0
  213. package/dist/gateway/auth/wallet.js +43 -0
  214. package/dist/gateway/auth/wallet.js.map +1 -0
  215. package/dist/gateway/bundle.d.ts +127 -0
  216. package/dist/gateway/bundle.d.ts.map +1 -0
  217. package/dist/gateway/bundle.js +604 -0
  218. package/dist/gateway/bundle.js.map +1 -0
  219. package/dist/gateway/bundle.test.d.ts +2 -0
  220. package/dist/gateway/bundle.test.d.ts.map +1 -0
  221. package/dist/gateway/bundle.test.js +277 -0
  222. package/dist/gateway/bundle.test.js.map +1 -0
  223. package/dist/gateway/buyer-control.d.ts +33 -0
  224. package/dist/gateway/buyer-control.d.ts.map +1 -0
  225. package/dist/gateway/buyer-control.js +93 -0
  226. package/dist/gateway/buyer-control.js.map +1 -0
  227. package/dist/gateway/buyer-policy-probe.d.ts +41 -0
  228. package/dist/gateway/buyer-policy-probe.d.ts.map +1 -0
  229. package/dist/gateway/buyer-policy-probe.js +99 -0
  230. package/dist/gateway/buyer-policy-probe.js.map +1 -0
  231. package/dist/gateway/buyer-policy-probe.test.d.ts +2 -0
  232. package/dist/gateway/buyer-policy-probe.test.d.ts.map +1 -0
  233. package/dist/gateway/buyer-policy-probe.test.js +128 -0
  234. package/dist/gateway/buyer-policy-probe.test.js.map +1 -0
  235. package/dist/gateway/buyer-process.d.ts +30 -0
  236. package/dist/gateway/buyer-process.d.ts.map +1 -0
  237. package/dist/gateway/buyer-process.js +101 -0
  238. package/dist/gateway/buyer-process.js.map +1 -0
  239. package/dist/gateway/buyer-process.test.d.ts +2 -0
  240. package/dist/gateway/buyer-process.test.d.ts.map +1 -0
  241. package/dist/gateway/buyer-process.test.js +112 -0
  242. package/dist/gateway/buyer-process.test.js.map +1 -0
  243. package/dist/gateway/chat/routes.d.ts +45 -0
  244. package/dist/gateway/chat/routes.d.ts.map +1 -0
  245. package/dist/gateway/chat/routes.js +439 -0
  246. package/dist/gateway/chat/routes.js.map +1 -0
  247. package/dist/gateway/chat/routes.test.d.ts +2 -0
  248. package/dist/gateway/chat/routes.test.d.ts.map +1 -0
  249. package/dist/gateway/chat/routes.test.js +304 -0
  250. package/dist/gateway/chat/routes.test.js.map +1 -0
  251. package/dist/gateway/chat/tools.d.ts +35 -0
  252. package/dist/gateway/chat/tools.d.ts.map +1 -0
  253. package/dist/gateway/chat/tools.js +347 -0
  254. package/dist/gateway/chat/tools.js.map +1 -0
  255. package/dist/gateway/chat/tools.test.d.ts +2 -0
  256. package/dist/gateway/chat/tools.test.d.ts.map +1 -0
  257. package/dist/gateway/chat/tools.test.js +136 -0
  258. package/dist/gateway/chat/tools.test.js.map +1 -0
  259. package/dist/gateway/console-api/access.d.ts +52 -0
  260. package/dist/gateway/console-api/access.d.ts.map +1 -0
  261. package/dist/gateway/console-api/access.js +171 -0
  262. package/dist/gateway/console-api/access.js.map +1 -0
  263. package/dist/gateway/console-api/deps.d.ts +52 -0
  264. package/dist/gateway/console-api/deps.d.ts.map +1 -0
  265. package/dist/gateway/console-api/deps.js +2 -0
  266. package/dist/gateway/console-api/deps.js.map +1 -0
  267. package/dist/gateway/console-api/handlers/admin-tokens.d.ts +8 -0
  268. package/dist/gateway/console-api/handlers/admin-tokens.d.ts.map +1 -0
  269. package/dist/gateway/console-api/handlers/admin-tokens.js +48 -0
  270. package/dist/gateway/console-api/handlers/admin-tokens.js.map +1 -0
  271. package/dist/gateway/console-api/handlers/audit.d.ts +8 -0
  272. package/dist/gateway/console-api/handlers/audit.d.ts.map +1 -0
  273. package/dist/gateway/console-api/handlers/audit.js +19 -0
  274. package/dist/gateway/console-api/handlers/audit.js.map +1 -0
  275. package/dist/gateway/console-api/handlers/audit.test.d.ts +2 -0
  276. package/dist/gateway/console-api/handlers/audit.test.d.ts.map +1 -0
  277. package/dist/gateway/console-api/handlers/audit.test.js +241 -0
  278. package/dist/gateway/console-api/handlers/audit.test.js.map +1 -0
  279. package/dist/gateway/console-api/handlers/data-settings.test.d.ts +2 -0
  280. package/dist/gateway/console-api/handlers/data-settings.test.d.ts.map +1 -0
  281. package/dist/gateway/console-api/handlers/data-settings.test.js +264 -0
  282. package/dist/gateway/console-api/handlers/data-settings.test.js.map +1 -0
  283. package/dist/gateway/console-api/handlers/index.d.ts +4 -0
  284. package/dist/gateway/console-api/handlers/index.d.ts.map +1 -0
  285. package/dist/gateway/console-api/handlers/index.js +24 -0
  286. package/dist/gateway/console-api/handlers/index.js.map +1 -0
  287. package/dist/gateway/console-api/handlers/keys.d.ts +13 -0
  288. package/dist/gateway/console-api/handlers/keys.d.ts.map +1 -0
  289. package/dist/gateway/console-api/handlers/keys.js +128 -0
  290. package/dist/gateway/console-api/handlers/keys.js.map +1 -0
  291. package/dist/gateway/console-api/handlers/keys.test.d.ts +2 -0
  292. package/dist/gateway/console-api/handlers/keys.test.d.ts.map +1 -0
  293. package/dist/gateway/console-api/handlers/keys.test.js +166 -0
  294. package/dist/gateway/console-api/handlers/keys.test.js.map +1 -0
  295. package/dist/gateway/console-api/handlers/network-buyer.d.ts +22 -0
  296. package/dist/gateway/console-api/handlers/network-buyer.d.ts.map +1 -0
  297. package/dist/gateway/console-api/handlers/network-buyer.js +39 -0
  298. package/dist/gateway/console-api/handlers/network-buyer.js.map +1 -0
  299. package/dist/gateway/console-api/handlers/network-mapping.d.ts +33 -0
  300. package/dist/gateway/console-api/handlers/network-mapping.d.ts.map +1 -0
  301. package/dist/gateway/console-api/handlers/network-mapping.js +219 -0
  302. package/dist/gateway/console-api/handlers/network-mapping.js.map +1 -0
  303. package/dist/gateway/console-api/handlers/network-test-helpers.d.ts +35 -0
  304. package/dist/gateway/console-api/handlers/network-test-helpers.d.ts.map +1 -0
  305. package/dist/gateway/console-api/handlers/network-test-helpers.js +121 -0
  306. package/dist/gateway/console-api/handlers/network-test-helpers.js.map +1 -0
  307. package/dist/gateway/console-api/handlers/network.d.ts +15 -0
  308. package/dist/gateway/console-api/handlers/network.d.ts.map +1 -0
  309. package/dist/gateway/console-api/handlers/network.js +57 -0
  310. package/dist/gateway/console-api/handlers/network.js.map +1 -0
  311. package/dist/gateway/console-api/handlers/network.test.d.ts +2 -0
  312. package/dist/gateway/console-api/handlers/network.test.d.ts.map +1 -0
  313. package/dist/gateway/console-api/handlers/network.test.js +383 -0
  314. package/dist/gateway/console-api/handlers/network.test.js.map +1 -0
  315. package/dist/gateway/console-api/handlers/org.d.ts +10 -0
  316. package/dist/gateway/console-api/handlers/org.d.ts.map +1 -0
  317. package/dist/gateway/console-api/handlers/org.js +150 -0
  318. package/dist/gateway/console-api/handlers/org.js.map +1 -0
  319. package/dist/gateway/console-api/handlers/peer-lists.d.ts +5 -0
  320. package/dist/gateway/console-api/handlers/peer-lists.d.ts.map +1 -0
  321. package/dist/gateway/console-api/handlers/peer-lists.js +43 -0
  322. package/dist/gateway/console-api/handlers/peer-lists.js.map +1 -0
  323. package/dist/gateway/console-api/handlers/presets.d.ts +9 -0
  324. package/dist/gateway/console-api/handlers/presets.d.ts.map +1 -0
  325. package/dist/gateway/console-api/handlers/presets.js +79 -0
  326. package/dist/gateway/console-api/handlers/presets.js.map +1 -0
  327. package/dist/gateway/console-api/handlers/routing.d.ts +5 -0
  328. package/dist/gateway/console-api/handlers/routing.d.ts.map +1 -0
  329. package/dist/gateway/console-api/handlers/routing.js +20 -0
  330. package/dist/gateway/console-api/handlers/routing.js.map +1 -0
  331. package/dist/gateway/console-api/handlers/settings.d.ts +10 -0
  332. package/dist/gateway/console-api/handlers/settings.d.ts.map +1 -0
  333. package/dist/gateway/console-api/handlers/settings.js +38 -0
  334. package/dist/gateway/console-api/handlers/settings.js.map +1 -0
  335. package/dist/gateway/console-api/handlers/status.d.ts +9 -0
  336. package/dist/gateway/console-api/handlers/status.d.ts.map +1 -0
  337. package/dist/gateway/console-api/handlers/status.js +47 -0
  338. package/dist/gateway/console-api/handlers/status.js.map +1 -0
  339. package/dist/gateway/console-api/handlers/status.test.d.ts +2 -0
  340. package/dist/gateway/console-api/handlers/status.test.d.ts.map +1 -0
  341. package/dist/gateway/console-api/handlers/status.test.js +82 -0
  342. package/dist/gateway/console-api/handlers/status.test.js.map +1 -0
  343. package/dist/gateway/console-api/handlers/usage.d.ts +10 -0
  344. package/dist/gateway/console-api/handlers/usage.d.ts.map +1 -0
  345. package/dist/gateway/console-api/handlers/usage.js +68 -0
  346. package/dist/gateway/console-api/handlers/usage.js.map +1 -0
  347. package/dist/gateway/console-api/handlers/wallet-card-link.d.ts +29 -0
  348. package/dist/gateway/console-api/handlers/wallet-card-link.d.ts.map +1 -0
  349. package/dist/gateway/console-api/handlers/wallet-card-link.js +46 -0
  350. package/dist/gateway/console-api/handlers/wallet-card-link.js.map +1 -0
  351. package/dist/gateway/console-api/handlers/wallet-chain.d.ts +26 -0
  352. package/dist/gateway/console-api/handlers/wallet-chain.d.ts.map +1 -0
  353. package/dist/gateway/console-api/handlers/wallet-chain.js +80 -0
  354. package/dist/gateway/console-api/handlers/wallet-chain.js.map +1 -0
  355. package/dist/gateway/console-api/handlers/wallet-mapping.d.ts +45 -0
  356. package/dist/gateway/console-api/handlers/wallet-mapping.d.ts.map +1 -0
  357. package/dist/gateway/console-api/handlers/wallet-mapping.js +202 -0
  358. package/dist/gateway/console-api/handlers/wallet-mapping.js.map +1 -0
  359. package/dist/gateway/console-api/handlers/wallet-operator.d.ts +41 -0
  360. package/dist/gateway/console-api/handlers/wallet-operator.d.ts.map +1 -0
  361. package/dist/gateway/console-api/handlers/wallet-operator.js +198 -0
  362. package/dist/gateway/console-api/handlers/wallet-operator.js.map +1 -0
  363. package/dist/gateway/console-api/handlers/wallet-operator.test.d.ts +2 -0
  364. package/dist/gateway/console-api/handlers/wallet-operator.test.d.ts.map +1 -0
  365. package/dist/gateway/console-api/handlers/wallet-operator.test.js +195 -0
  366. package/dist/gateway/console-api/handlers/wallet-operator.test.js.map +1 -0
  367. package/dist/gateway/console-api/handlers/wallet.d.ts +61 -0
  368. package/dist/gateway/console-api/handlers/wallet.d.ts.map +1 -0
  369. package/dist/gateway/console-api/handlers/wallet.js +221 -0
  370. package/dist/gateway/console-api/handlers/wallet.js.map +1 -0
  371. package/dist/gateway/console-api/handlers/wallet.test.d.ts +2 -0
  372. package/dist/gateway/console-api/handlers/wallet.test.d.ts.map +1 -0
  373. package/dist/gateway/console-api/handlers/wallet.test.js +581 -0
  374. package/dist/gateway/console-api/handlers/wallet.test.js.map +1 -0
  375. package/dist/gateway/console-api/handlers/workspaces.d.ts +12 -0
  376. package/dist/gateway/console-api/handlers/workspaces.d.ts.map +1 -0
  377. package/dist/gateway/console-api/handlers/workspaces.js +96 -0
  378. package/dist/gateway/console-api/handlers/workspaces.js.map +1 -0
  379. package/dist/gateway/console-api/operator-types.d.ts +68 -0
  380. package/dist/gateway/console-api/operator-types.d.ts.map +1 -0
  381. package/dist/gateway/console-api/operator-types.js +21 -0
  382. package/dist/gateway/console-api/operator-types.js.map +1 -0
  383. package/dist/gateway/console-api/router.d.ts +97 -0
  384. package/dist/gateway/console-api/router.d.ts.map +1 -0
  385. package/dist/gateway/console-api/router.js +69 -0
  386. package/dist/gateway/console-api/router.js.map +1 -0
  387. package/dist/gateway/console-api/routes.test.d.ts +2 -0
  388. package/dist/gateway/console-api/routes.test.d.ts.map +1 -0
  389. package/dist/gateway/console-api/routes.test.js +331 -0
  390. package/dist/gateway/console-api/routes.test.js.map +1 -0
  391. package/dist/gateway/console-api/serialize.d.ts +33 -0
  392. package/dist/gateway/console-api/serialize.d.ts.map +1 -0
  393. package/dist/gateway/console-api/serialize.js +179 -0
  394. package/dist/gateway/console-api/serialize.js.map +1 -0
  395. package/dist/gateway/console-api/server.d.ts +38 -0
  396. package/dist/gateway/console-api/server.d.ts.map +1 -0
  397. package/dist/gateway/console-api/server.js +308 -0
  398. package/dist/gateway/console-api/server.js.map +1 -0
  399. package/dist/gateway/console-api/server.test.d.ts +2 -0
  400. package/dist/gateway/console-api/server.test.d.ts.map +1 -0
  401. package/dist/gateway/console-api/server.test.js +149 -0
  402. package/dist/gateway/console-api/server.test.js.map +1 -0
  403. package/dist/gateway/console-api/test-support.d.ts +59 -0
  404. package/dist/gateway/console-api/test-support.d.ts.map +1 -0
  405. package/dist/gateway/console-api/test-support.js +120 -0
  406. package/dist/gateway/console-api/test-support.js.map +1 -0
  407. package/dist/gateway/console-api/types.d.ts +779 -0
  408. package/dist/gateway/console-api/types.d.ts.map +1 -0
  409. package/dist/gateway/console-api/types.js +6 -0
  410. package/dist/gateway/console-api/types.js.map +1 -0
  411. package/dist/gateway/console-location.d.ts +21 -0
  412. package/dist/gateway/console-location.d.ts.map +1 -0
  413. package/dist/gateway/console-location.js +41 -0
  414. package/dist/gateway/console-location.js.map +1 -0
  415. package/dist/gateway/console-recovery.d.ts +34 -0
  416. package/dist/gateway/console-recovery.d.ts.map +1 -0
  417. package/dist/gateway/console-recovery.js +94 -0
  418. package/dist/gateway/console-recovery.js.map +1 -0
  419. package/dist/gateway/console-recovery.test.d.ts +2 -0
  420. package/dist/gateway/console-recovery.test.d.ts.map +1 -0
  421. package/dist/gateway/console-recovery.test.js +106 -0
  422. package/dist/gateway/console-recovery.test.js.map +1 -0
  423. package/dist/gateway/console.d.ts +18 -0
  424. package/dist/gateway/console.d.ts.map +1 -0
  425. package/dist/gateway/console.js +101 -0
  426. package/dist/gateway/console.js.map +1 -0
  427. package/dist/gateway/console.test.d.ts +2 -0
  428. package/dist/gateway/console.test.d.ts.map +1 -0
  429. package/dist/gateway/console.test.js +165 -0
  430. package/dist/gateway/console.test.js.map +1 -0
  431. package/dist/gateway/errors.d.ts +3 -0
  432. package/dist/gateway/errors.d.ts.map +1 -0
  433. package/dist/gateway/errors.js +5 -0
  434. package/dist/gateway/errors.js.map +1 -0
  435. package/dist/gateway/exposure.d.ts +33 -0
  436. package/dist/gateway/exposure.d.ts.map +1 -0
  437. package/dist/gateway/exposure.js +146 -0
  438. package/dist/gateway/exposure.js.map +1 -0
  439. package/dist/gateway/exposure.test.d.ts +2 -0
  440. package/dist/gateway/exposure.test.d.ts.map +1 -0
  441. package/dist/gateway/exposure.test.js +93 -0
  442. package/dist/gateway/exposure.test.js.map +1 -0
  443. package/dist/gateway/gateway-policy.test.d.ts +2 -0
  444. package/dist/gateway/gateway-policy.test.d.ts.map +1 -0
  445. package/dist/gateway/gateway-policy.test.js +404 -0
  446. package/dist/gateway/gateway-policy.test.js.map +1 -0
  447. package/dist/gateway/gateway.test.js +77 -24
  448. package/dist/gateway/gateway.test.js.map +1 -1
  449. package/dist/gateway/keys.d.ts +2 -0
  450. package/dist/gateway/keys.d.ts.map +1 -1
  451. package/dist/gateway/keys.js +2 -0
  452. package/dist/gateway/keys.js.map +1 -1
  453. package/dist/gateway/limits.d.ts +32 -8
  454. package/dist/gateway/limits.d.ts.map +1 -1
  455. package/dist/gateway/limits.js +27 -11
  456. package/dist/gateway/limits.js.map +1 -1
  457. package/dist/gateway/observability.d.ts +51 -0
  458. package/dist/gateway/observability.d.ts.map +1 -0
  459. package/dist/gateway/observability.js +136 -0
  460. package/dist/gateway/observability.js.map +1 -0
  461. package/dist/gateway/policy-resolver.d.ts +73 -0
  462. package/dist/gateway/policy-resolver.d.ts.map +1 -0
  463. package/dist/gateway/policy-resolver.js +142 -0
  464. package/dist/gateway/policy-resolver.js.map +1 -0
  465. package/dist/gateway/policy-resolver.test.d.ts +2 -0
  466. package/dist/gateway/policy-resolver.test.d.ts.map +1 -0
  467. package/dist/gateway/policy-resolver.test.js +273 -0
  468. package/dist/gateway/policy-resolver.test.js.map +1 -0
  469. package/dist/gateway/request-errors.test.d.ts +2 -0
  470. package/dist/gateway/request-errors.test.d.ts.map +1 -0
  471. package/dist/gateway/request-errors.test.js +108 -0
  472. package/dist/gateway/request-errors.test.js.map +1 -0
  473. package/dist/gateway/request-shaping.d.ts +25 -0
  474. package/dist/gateway/request-shaping.d.ts.map +1 -0
  475. package/dist/gateway/request-shaping.js +66 -0
  476. package/dist/gateway/request-shaping.js.map +1 -0
  477. package/dist/gateway/response-usage.d.ts +35 -0
  478. package/dist/gateway/response-usage.d.ts.map +1 -0
  479. package/dist/gateway/response-usage.js +167 -0
  480. package/dist/gateway/response-usage.js.map +1 -0
  481. package/dist/gateway/response-usage.test.d.ts +2 -0
  482. package/dist/gateway/response-usage.test.d.ts.map +1 -0
  483. package/dist/gateway/response-usage.test.js +78 -0
  484. package/dist/gateway/response-usage.test.js.map +1 -0
  485. package/dist/gateway/runtime.d.ts +25 -0
  486. package/dist/gateway/runtime.d.ts.map +1 -1
  487. package/dist/gateway/runtime.js +39 -19
  488. package/dist/gateway/runtime.js.map +1 -1
  489. package/dist/gateway/server.d.ts +68 -2
  490. package/dist/gateway/server.d.ts.map +1 -1
  491. package/dist/gateway/server.js +533 -114
  492. package/dist/gateway/server.js.map +1 -1
  493. package/dist/gateway/services/audit.d.ts +16 -0
  494. package/dist/gateway/services/audit.d.ts.map +1 -0
  495. package/dist/gateway/services/audit.js +18 -0
  496. package/dist/gateway/services/audit.js.map +1 -0
  497. package/dist/gateway/services/context.d.ts +66 -0
  498. package/dist/gateway/services/context.d.ts.map +1 -0
  499. package/dist/gateway/services/context.js +52 -0
  500. package/dist/gateway/services/context.js.map +1 -0
  501. package/dist/gateway/services/keys.d.ts +78 -0
  502. package/dist/gateway/services/keys.d.ts.map +1 -0
  503. package/dist/gateway/services/keys.js +232 -0
  504. package/dist/gateway/services/keys.js.map +1 -0
  505. package/dist/gateway/services/members.d.ts +72 -0
  506. package/dist/gateway/services/members.d.ts.map +1 -0
  507. package/dist/gateway/services/members.js +161 -0
  508. package/dist/gateway/services/members.js.map +1 -0
  509. package/dist/gateway/services/network.d.ts +36 -0
  510. package/dist/gateway/services/network.d.ts.map +1 -0
  511. package/dist/gateway/services/network.js +147 -0
  512. package/dist/gateway/services/network.js.map +1 -0
  513. package/dist/gateway/services/operator.d.ts +98 -0
  514. package/dist/gateway/services/operator.d.ts.map +1 -0
  515. package/dist/gateway/services/operator.js +170 -0
  516. package/dist/gateway/services/operator.js.map +1 -0
  517. package/dist/gateway/services/operator.test.d.ts +2 -0
  518. package/dist/gateway/services/operator.test.d.ts.map +1 -0
  519. package/dist/gateway/services/operator.test.js +184 -0
  520. package/dist/gateway/services/operator.test.js.map +1 -0
  521. package/dist/gateway/services/peer-lists.d.ts +20 -0
  522. package/dist/gateway/services/peer-lists.d.ts.map +1 -0
  523. package/dist/gateway/services/peer-lists.js +134 -0
  524. package/dist/gateway/services/peer-lists.js.map +1 -0
  525. package/dist/gateway/services/presets.d.ts +34 -0
  526. package/dist/gateway/services/presets.d.ts.map +1 -0
  527. package/dist/gateway/services/presets.js +134 -0
  528. package/dist/gateway/services/presets.js.map +1 -0
  529. package/dist/gateway/services/routing.d.ts +13 -0
  530. package/dist/gateway/services/routing.d.ts.map +1 -0
  531. package/dist/gateway/services/routing.js +18 -0
  532. package/dist/gateway/services/routing.js.map +1 -0
  533. package/dist/gateway/services/services.test.d.ts +2 -0
  534. package/dist/gateway/services/services.test.d.ts.map +1 -0
  535. package/dist/gateway/services/services.test.js +476 -0
  536. package/dist/gateway/services/services.test.js.map +1 -0
  537. package/dist/gateway/services/settings.d.ts +68 -0
  538. package/dist/gateway/services/settings.d.ts.map +1 -0
  539. package/dist/gateway/services/settings.js +580 -0
  540. package/dist/gateway/services/settings.js.map +1 -0
  541. package/dist/gateway/services/tokens.d.ts +29 -0
  542. package/dist/gateway/services/tokens.d.ts.map +1 -0
  543. package/dist/gateway/services/tokens.js +48 -0
  544. package/dist/gateway/services/tokens.js.map +1 -0
  545. package/dist/gateway/services/usage.d.ts +55 -0
  546. package/dist/gateway/services/usage.d.ts.map +1 -0
  547. package/dist/gateway/services/usage.js +225 -0
  548. package/dist/gateway/services/usage.js.map +1 -0
  549. package/dist/gateway/services/wallet-address-env.test.d.ts +2 -0
  550. package/dist/gateway/services/wallet-address-env.test.d.ts.map +1 -0
  551. package/dist/gateway/services/wallet-address-env.test.js +119 -0
  552. package/dist/gateway/services/wallet-address-env.test.js.map +1 -0
  553. package/dist/gateway/services/wallet-address.d.ts +40 -0
  554. package/dist/gateway/services/wallet-address.d.ts.map +1 -0
  555. package/dist/gateway/services/wallet-address.js +116 -0
  556. package/dist/gateway/services/wallet-address.js.map +1 -0
  557. package/dist/gateway/services/wallet-address.test.d.ts +2 -0
  558. package/dist/gateway/services/wallet-address.test.d.ts.map +1 -0
  559. package/dist/gateway/services/wallet-address.test.js +84 -0
  560. package/dist/gateway/services/wallet-address.test.js.map +1 -0
  561. package/dist/gateway/services/workspaces.d.ts +75 -0
  562. package/dist/gateway/services/workspaces.d.ts.map +1 -0
  563. package/dist/gateway/services/workspaces.js +228 -0
  564. package/dist/gateway/services/workspaces.js.map +1 -0
  565. package/dist/gateway/spend-durability.test.d.ts +2 -0
  566. package/dist/gateway/spend-durability.test.d.ts.map +1 -0
  567. package/dist/gateway/spend-durability.test.js +330 -0
  568. package/dist/gateway/spend-durability.test.js.map +1 -0
  569. package/dist/gateway/spend-feed.d.ts +11 -1
  570. package/dist/gateway/spend-feed.d.ts.map +1 -1
  571. package/dist/gateway/spend-feed.js +17 -4
  572. package/dist/gateway/spend-feed.js.map +1 -1
  573. package/dist/gateway/spend-feed.test.d.ts +2 -0
  574. package/dist/gateway/spend-feed.test.d.ts.map +1 -0
  575. package/dist/gateway/spend-feed.test.js +34 -0
  576. package/dist/gateway/spend-feed.test.js.map +1 -0
  577. package/dist/gateway/store-data.test.d.ts +2 -0
  578. package/dist/gateway/store-data.test.d.ts.map +1 -0
  579. package/dist/gateway/store-data.test.js +346 -0
  580. package/dist/gateway/store-data.test.js.map +1 -0
  581. package/dist/gateway/store-migrations.d.ts +6 -0
  582. package/dist/gateway/store-migrations.d.ts.map +1 -0
  583. package/dist/gateway/store-migrations.js +301 -0
  584. package/dist/gateway/store-migrations.js.map +1 -0
  585. package/dist/gateway/store.d.ts +595 -10
  586. package/dist/gateway/store.d.ts.map +1 -1
  587. package/dist/gateway/store.js +1242 -120
  588. package/dist/gateway/store.js.map +1 -1
  589. package/dist/gateway/store.test.d.ts +2 -0
  590. package/dist/gateway/store.test.d.ts.map +1 -0
  591. package/dist/gateway/store.test.js +185 -0
  592. package/dist/gateway/store.test.js.map +1 -0
  593. package/dist/proxy/buyer-chain-reader.d.ts +68 -0
  594. package/dist/proxy/buyer-chain-reader.d.ts.map +1 -0
  595. package/dist/proxy/buyer-chain-reader.js +152 -0
  596. package/dist/proxy/buyer-chain-reader.js.map +1 -0
  597. package/dist/proxy/buyer-chain-reader.test.d.ts +2 -0
  598. package/dist/proxy/buyer-chain-reader.test.d.ts.map +1 -0
  599. package/dist/proxy/buyer-chain-reader.test.js +102 -0
  600. package/dist/proxy/buyer-chain-reader.test.js.map +1 -0
  601. package/dist/proxy/buyer-proxy.d.ts +169 -3
  602. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  603. package/dist/proxy/buyer-proxy.js +882 -118
  604. package/dist/proxy/buyer-proxy.js.map +1 -1
  605. package/dist/proxy/buyer-proxy.test.js +108 -0
  606. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  607. package/dist/proxy/chain-read-cache.d.ts +47 -0
  608. package/dist/proxy/chain-read-cache.d.ts.map +1 -0
  609. package/dist/proxy/chain-read-cache.js +110 -0
  610. package/dist/proxy/chain-read-cache.js.map +1 -0
  611. package/dist/proxy/chain-read-cache.test.d.ts +2 -0
  612. package/dist/proxy/chain-read-cache.test.d.ts.map +1 -0
  613. package/dist/proxy/chain-read-cache.test.js +75 -0
  614. package/dist/proxy/chain-read-cache.test.js.map +1 -0
  615. package/dist/proxy/chain-rpc-test-support.d.ts +31 -0
  616. package/dist/proxy/chain-rpc-test-support.d.ts.map +1 -0
  617. package/dist/proxy/chain-rpc-test-support.js +136 -0
  618. package/dist/proxy/chain-rpc-test-support.js.map +1 -0
  619. package/dist/proxy/chain-rpc.d.ts +121 -0
  620. package/dist/proxy/chain-rpc.d.ts.map +1 -0
  621. package/dist/proxy/chain-rpc.js +420 -0
  622. package/dist/proxy/chain-rpc.js.map +1 -0
  623. package/dist/proxy/chain-rpc.test.d.ts +2 -0
  624. package/dist/proxy/chain-rpc.test.d.ts.map +1 -0
  625. package/dist/proxy/chain-rpc.test.js +180 -0
  626. package/dist/proxy/chain-rpc.test.js.map +1 -0
  627. package/dist/proxy/credential-headers.d.ts +17 -0
  628. package/dist/proxy/credential-headers.d.ts.map +1 -0
  629. package/dist/proxy/credential-headers.js +29 -0
  630. package/dist/proxy/credential-headers.js.map +1 -0
  631. package/dist/proxy/deposit-watch-hub.d.ts +47 -0
  632. package/dist/proxy/deposit-watch-hub.d.ts.map +1 -0
  633. package/dist/proxy/deposit-watch-hub.js +111 -0
  634. package/dist/proxy/deposit-watch-hub.js.map +1 -0
  635. package/dist/proxy/deposit-watch-hub.test.d.ts +2 -0
  636. package/dist/proxy/deposit-watch-hub.test.d.ts.map +1 -0
  637. package/dist/proxy/deposit-watch-hub.test.js +133 -0
  638. package/dist/proxy/deposit-watch-hub.test.js.map +1 -0
  639. package/dist/proxy/deposit-watcher.d.ts +63 -4
  640. package/dist/proxy/deposit-watcher.d.ts.map +1 -1
  641. package/dist/proxy/deposit-watcher.js +120 -19
  642. package/dist/proxy/deposit-watcher.js.map +1 -1
  643. package/dist/proxy/deposit-watcher.test.js +81 -5
  644. package/dist/proxy/deposit-watcher.test.js.map +1 -1
  645. package/dist/proxy/gateway-policy.test.d.ts +2 -0
  646. package/dist/proxy/gateway-policy.test.d.ts.map +1 -0
  647. package/dist/proxy/gateway-policy.test.js +576 -0
  648. package/dist/proxy/gateway-policy.test.js.map +1 -0
  649. package/dist/proxy/network-models.d.ts +2 -0
  650. package/dist/proxy/network-models.d.ts.map +1 -1
  651. package/dist/proxy/network-models.js +3 -1
  652. package/dist/proxy/network-models.js.map +1 -1
  653. package/dist/proxy/route-policy.d.ts +97 -0
  654. package/dist/proxy/route-policy.d.ts.map +1 -0
  655. package/dist/proxy/route-policy.js +230 -0
  656. package/dist/proxy/route-policy.js.map +1 -0
  657. package/dist/proxy/route-policy.test.d.ts +2 -0
  658. package/dist/proxy/route-policy.test.d.ts.map +1 -0
  659. package/dist/proxy/route-policy.test.js +120 -0
  660. package/dist/proxy/route-policy.test.js.map +1 -0
  661. package/dist/proxy/spend-attribution.d.ts +41 -10
  662. package/dist/proxy/spend-attribution.d.ts.map +1 -1
  663. package/dist/proxy/spend-attribution.js +133 -37
  664. package/dist/proxy/spend-attribution.js.map +1 -1
  665. package/dist/proxy/tee-control.d.ts +3 -1
  666. package/dist/proxy/tee-control.d.ts.map +1 -1
  667. package/dist/proxy/tee-control.js +5 -3
  668. package/dist/proxy/tee-control.js.map +1 -1
  669. package/dist/proxy/telemetry.d.ts +2 -1
  670. package/dist/proxy/telemetry.d.ts.map +1 -1
  671. package/dist/proxy/telemetry.js +10 -5
  672. package/dist/proxy/telemetry.js.map +1 -1
  673. package/dist/routing-policy/policy.d.ts +149 -0
  674. package/dist/routing-policy/policy.d.ts.map +1 -0
  675. package/dist/routing-policy/policy.js +450 -0
  676. package/dist/routing-policy/policy.js.map +1 -0
  677. package/dist/routing-policy/policy.test.d.ts +2 -0
  678. package/dist/routing-policy/policy.test.d.ts.map +1 -0
  679. package/dist/routing-policy/policy.test.js +134 -0
  680. package/dist/routing-policy/policy.test.js.map +1 -0
  681. package/dist/system-proxy/proxy-server.js +2 -2
  682. package/dist/system-proxy/proxy-server.js.map +1 -1
  683. package/dist/system-proxy/proxy-server.test.js +40 -0
  684. package/dist/system-proxy/proxy-server.test.js.map +1 -1
  685. package/package.json +16 -7
@@ -4,10 +4,10 @@ import { prepareVideoRequest, recordVideoAcceptance } from './native-video-proxy
4
4
  import { downloadVideo } from './video-download.js';
5
5
  import { createServer } from 'node:http';
6
6
  import { randomUUID } from 'node:crypto';
7
- import { watchFile, unwatchFile } from 'node:fs';
7
+ import { readFileSync, watchFile, unwatchFile } from 'node:fs';
8
8
  import { readFile, writeFile, rename, mkdir, readdir, stat, unlink } from 'node:fs/promises';
9
9
  import { join } from 'node:path';
10
- import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, ANTSEED_ATTEST_PATH, adaptPeerFaultErrorResponse, computeTrustScore, decodeSweepRequest, faultAttributionOf, faultCodeOf, isModelRouteEligible, modelRouteTotalPrice, normalizedModelReputationScore, peerSupportsCooperativeClose, DEFAULT_BUYER_IDENTITY, rankModelRoutes, sanitizePeerDisplayName, } from '@antseed/node';
10
+ import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, ANTSEED_ATTEST_PATH, adaptPeerFaultErrorResponse, computeTrustScore, decodeSweepRequest, faultAttributionOf, faultCodeOf, isModelRouteCoolingDown, isModelRouteEligible, modelRouteReputationScore, modelRouteTotalPrice, normalizedModelReputationScore, peerSupportsCooperativeClose, DEFAULT_BUYER_IDENTITY, rankModelRoutes, sanitizePeerDisplayName, } from '@antseed/node';
11
11
  import { canonicalModelKey } from '@antseed/node/model-identity';
12
12
  import { createStreamingAdapter, detectRequestServiceApiProtocol, transformRequest, transformResponse, } from './service-api-adapter.js';
13
13
  import { DEBUG, log, extractRequestedService, summarizeRequestShape, summarizeErrorResponse, requestWantsStreaming, parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, overrideRoutedModelInBody, ROUTED_MODEL_ALIAS, SYSTEM_PROXY_SOURCE_HEADER, SYSTEM_ROUTED_MODEL_HEADER, BUYER_IDENTITY_HEADER, normalizePeerId, } from './request-utils.js';
@@ -17,17 +17,22 @@ import { findMissingRequiredParameters, findUnannouncedRequestParameters, findAd
17
17
  import { computeResponseTelemetry, attachAntseedTelemetryHeaders, attachStreamingAntseedHeaders, } from './telemetry.js';
18
18
  import { DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS } from '../config/defaults.js';
19
19
  import { extractConversationIdentity, extractFirstUserSnippet, isCompletionRequestPath, isTitleGenerationRequest, parseRequestBodyObject, } from './conversation-identity.js';
20
- import { SPEND_ATTRIBUTION_HEADER, SpendAttributionFeed, parseSpendAttributionTag } from './spend-attribution.js';
20
+ import { SPEND_ATTRIBUTION_DB_FILE, SPEND_ATTRIBUTION_HEADER, SpendAttributionFeed, parseSpendAttributionTag } from './spend-attribution.js';
21
21
  import { ConversationStore } from './conversation-store.js';
22
+ import { DEPOSIT_WATCH_ACTIVE_TTL_MS } from './deposit-watcher.js';
22
23
  import { recordPeerFailureEntry, clearPeerHealthEntry, isCoolingDown, parsePersistedPeerHealth, prunePeerHealth, serializePeerHealth, reasonEscalates, } from './peer-health.js';
23
24
  import { PeerAttributionTracker, HEARTBEAT_MS } from './peer-attribution.js';
24
25
  import { estimateAnthropicPromptTokens, isCountTokensPath } from './count-tokens.js';
25
- import { runVerifier, verifierSupportFingerprint } from '../plugins/verifier.js';
26
+ import { runVerifier, selectVerifier, verifierSupportFingerprint } from '../plugins/verifier.js';
26
27
  import { TEE_VERIFIER_ID } from '@antseed/node/tee-status';
27
28
  import { parseVerifierCapabilities } from '@antseed/node/verifier-capabilities';
28
29
  import { TeeVerification } from './tee-verification.js';
29
30
  import { TeeControl } from './tee-control.js';
31
+ import { ChainReadCache } from './chain-read-cache.js';
32
+ import { isClientCredentialHeader } from './credential-headers.js';
30
33
  import { loadConfig } from '../config/loader.js';
34
+ import { GATEWAY_CONTROL_HEADER, GATEWAY_CONTROL_SECRET_ENV, GATEWAY_CONTROL_SECRET_FILE, ROUTING_POLICY_HEADER, decodePolicyHeader, findModelRoute, meaningfulPolicy, narrowPolicy, normalizePeerId as normalizePolicyPeerId, policyAllowsModel, policyModelKey, } from '../routing-policy/policy.js';
35
+ import { buyerConfigReason, buyerHardPolicy, buyerPreferencePolicy, orderByPolicy, pinnedPeerExclusionReasons, policyExclusionReasons, preferenceExclusionReasons, rankingPreferences, secretsMatch, } from './route-policy.js';
31
36
  // Re-export for backward compatibility (used by tests and other consumers)
32
37
  export { selectCandidatePeersForRouting } from './routing.js';
33
38
  export { parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, ROUTED_MODEL_ALIAS } from './request-utils.js';
@@ -35,7 +40,34 @@ const WATCHER_ABSENCE_ERRORS = {
35
40
  'payments-disabled': 'Deposit watcher unavailable — payments are disabled on this buyer.',
36
41
  'no-deposit-relay': 'Deposit watcher unavailable — this chain has no deposit relay.',
37
42
  'external-daemon': 'Deposit watcher unavailable — another daemon owns the proxy port and runs the watcher.',
43
+ 'identity-not-watched': 'Deposit watcher unavailable — this buyer identity has no deposit watcher.',
38
44
  };
45
+ /** Exit code `POST /_antseed/restart` ends the process with, so a supervisor restarts it. */
46
+ export const BUYER_RESTART_EXIT_CODE = 75;
47
+ const MIN_RESTART_INTERVAL_MS = 30_000;
48
+ const BALANCE_CACHE_TTL_MS = 15_000;
49
+ /** A balance up to this much past its TTL is answered at once while it refreshes. */
50
+ const BALANCE_REVALIDATE_MS = 45_000;
51
+ /** `fresh=1` re-reads the chain at most this often per address. */
52
+ const BALANCE_FRESH_FLOOR_MS = 5_000;
53
+ const CONTROL_SECRET_RECHECK_MS = 1_000;
54
+ /** Floor between forced re-reads on a secret mismatch, so a stream of bad secrets can't hammer the disk. */
55
+ const CONTROL_SECRET_FORCED_RECHECK_MS = 100;
56
+ const LATENCY_EMA_ALPHA = 0.3;
57
+ const MAX_LATENCY_ENTRIES = 4096;
58
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
59
+ /** Exact USDC decimal string from base units, e.g. 1500000n -> "1.500000". */
60
+ function formatUsdcBaseUnits(amount) {
61
+ const sign = amount < 0n ? '-' : '';
62
+ const abs = amount < 0n ? -amount : amount;
63
+ return `${sign}${abs / 1000000n}.${(abs % 1000000n).toString().padStart(6, '0')}`;
64
+ }
65
+ function isLoopbackAddress(address) {
66
+ // A destroyed socket has no address: never treat it as local.
67
+ if (address === undefined)
68
+ return false;
69
+ return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1' || address.startsWith('127.');
70
+ }
39
71
  // 401/403 are included: sellers relay upstream auth failures (revoked or
40
72
  // expired key, region/WAF block) that are specific to that seller's upstream
41
73
  // account, so another peer serving the same model can usually complete the
@@ -43,6 +75,24 @@ const WATCHER_ABSENCE_ERRORS = {
43
75
  // because model_not_found already has dedicated unadvertise handling while a
44
76
  // generic 404 would repeat identically on every peer.
45
77
  const RETRYABLE_STATUS_CODES = new Set([401, 403, 408, 429, 500, 502, 503, 504]);
78
+ /**
79
+ * Gateway-policy requests on automatic routing also move on after a 402: the
80
+ * buyer could not fund that seller (no deposits for its reserve), but a free
81
+ * seller needs none. Other requests keep the 402, as they always have.
82
+ */
83
+ const GATEWAY_AUTO_ROUTE_RETRYABLE_STATUS_CODES = new Set([...RETRYABLE_STATUS_CODES, 402]);
84
+ function retryableStatusCodesFor(gatewayAutoRoute) {
85
+ return gatewayAutoRoute ? GATEWAY_AUTO_ROUTE_RETRYABLE_STATUS_CODES : RETRYABLE_STATUS_CODES;
86
+ }
87
+ /**
88
+ * Whether the routing loop should try the next seller. On automatic routing a
89
+ * seller's model_not_found also moves on: its cached catalog was stale (the
90
+ * seller stopped serving the model), and another seller may still serve it.
91
+ * A pinned request keeps the pinned seller's answer.
92
+ */
93
+ function shouldTryNextPeer(statusCode, modelNotFound, pinned, gatewayAutoRoute) {
94
+ return retryableStatusCodesFor(gatewayAutoRoute).has(statusCode) || (modelNotFound && !pinned);
95
+ }
46
96
  const MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER = 3;
47
97
  const MODEL_RATE_LIMIT_RETRY_DELAYS_MS = [250, 750];
48
98
  const MODEL_RATE_LIMIT_MAX_RETRY_AFTER_MS = 2_000;
@@ -95,11 +145,34 @@ function peerAllowedByPolicy(policyRouter, request, peer) {
95
145
  return policyRouter.allowsPeerForPricing(request, peer);
96
146
  return true;
97
147
  }
148
+ /**
149
+ * Appends the buyer-level exclusion reasons, labelled as buyer config, that
150
+ * the gateway level did not already report (in either form).
151
+ */
152
+ function addBuyerConfigReasons(reasons, buyerReasons) {
153
+ for (const reason of buyerReasons) {
154
+ const labelled = buyerConfigReason(reason);
155
+ if (!reasons.includes(reason) && !reasons.includes(labelled))
156
+ reasons.push(labelled);
157
+ }
158
+ }
159
+ /**
160
+ * Why a route candidate's effective trust is below its seller's score: the
161
+ * only penalty (`effectiveModelReputationScore`) halves a paid seller that
162
+ * lists no cached-input price for a model other sellers price cache reads on.
163
+ */
164
+ function trustPenalty(candidate) {
165
+ const effective = candidate.effectiveReputationScore;
166
+ if (candidate.reputation < 0 || effective === null || effective >= candidate.reputation)
167
+ return null;
168
+ return `halved from ${Math.round(candidate.reputation)}: no cached-input price listed`;
169
+ }
98
170
  function isControlPlaneServicesPath(path) {
99
171
  return path.toLowerCase().startsWith('/v1/models');
100
172
  }
101
173
  function isRouterSuccess(statusCode, path, retryableStatusCodes) {
102
- return isControlPlaneServicesPath(path) || !retryableStatusCodes.has(statusCode);
174
+ // A 402 is the buyer's own funding gap, never the seller's failure.
175
+ return isControlPlaneServicesPath(path) || statusCode === 402 || !retryableStatusCodes.has(statusCode);
103
176
  }
104
177
  /**
105
178
  * Detect a "model not served" rejection: the seller's own pre-payment 400
@@ -609,9 +682,16 @@ export async function mergeJsonStateFile(stateDir, stateFile, patch) {
609
682
  throw err;
610
683
  }
611
684
  }
685
+ function sendJson(res, status, body) {
686
+ res.writeHead(status, { 'content-type': 'application/json' });
687
+ res.end(JSON.stringify(body));
688
+ }
689
+ /** A requested buyer identity name as stored ('' when none is named). */
690
+ function buyerIdentityName(value) {
691
+ return value?.trim().toLowerCase() ?? '';
692
+ }
612
693
  function sendUnknownBuyerIdentity(res) {
613
- res.writeHead(404, { 'content-type': 'application/json' });
614
- res.end(JSON.stringify({ ok: false, error: 'Unknown buyer identity' }));
694
+ sendJson(res, 404, { ok: false, error: 'Unknown buyer identity' });
615
695
  }
616
696
  export class BuyerProxy {
617
697
  _server;
@@ -680,6 +760,8 @@ export class BuyerProxy {
680
760
  _depositWatcher = null;
681
761
  /** Why no watcher is attached, so UIs can show the actual cause. */
682
762
  _depositWatcherAbsence = null;
763
+ /** Watchers for named buyer identities (each workspace wallet), keyed by identity name. */
764
+ _identityDepositWatchers = new Map();
683
765
  /**
684
766
  * requestId -> the conversation that issued it, so the node's per-request
685
767
  * spend events can be attributed to a chat. Only the proxy knows this
@@ -690,8 +772,32 @@ export class BuyerProxy {
690
772
  */
691
773
  _requestConversations = new Map();
692
774
  /** Signed spend for requests a local gateway tagged, polled over the control plane. */
693
- _spendAttribution = new SpendAttributionFeed();
775
+ _spendAttribution;
776
+ _minPeerReputation;
777
+ /** Gateway control secret, read lazily (the gateway may create it after the buyer starts). */
778
+ _controlSecret = null;
779
+ _controlSecretCheckedAt = 0;
780
+ _controlSecretForcedAt = 0;
781
+ _loggedUnauthenticatedPolicy = false;
782
+ /**
783
+ * Per-seller latency EMA (ms) measured by this proxy: time to the first
784
+ * response frame for streamed requests, full round trip otherwise. Feeds
785
+ * `sort: 'latency'`; sellers without a measurement rank after measured ones.
786
+ */
787
+ _peerLatencyMs = new Map();
788
+ _balanceReader;
789
+ _balanceCache = new ChainReadCache();
790
+ _restartHandler;
791
+ _settingOverrides;
792
+ _restartRequested = false;
793
+ /** A restarted buyer is a new process, so its start is the last accepted restart. */
794
+ _restartAllowedAt;
694
795
  constructor(config) {
796
+ this._minPeerReputation = config.minPeerReputation ?? null;
797
+ this._balanceReader = config.balanceReader ?? null;
798
+ this._restartHandler = config.onRestartRequested ?? null;
799
+ this._restartAllowedAt = Date.now() + (config.minRestartIntervalMs ?? MIN_RESTART_INTERVAL_MS);
800
+ this._settingOverrides = [...(config.settingOverrides ?? [])];
695
801
  this._node = config.node;
696
802
  this._verifier = config.verifier;
697
803
  this._buyerIdentities = config.buyerIdentities ?? new BuyerIdentityLoader(config.node, config.dataDir);
@@ -702,6 +808,11 @@ export class BuyerProxy {
702
808
  this._peerCacheTtlMs = Math.max(0, config.peerCacheTtlMs ?? Math.max(6 * 60_000, this._bgRefreshIntervalMs + 60_000));
703
809
  this._stateDir = config.dataDir;
704
810
  this._stateFile = join(config.dataDir, 'buyer.state.json');
811
+ // Kept on disk so spend signed just before a restart still reaches the gateway.
812
+ this._spendAttribution = new SpendAttributionFeed(undefined, {
813
+ path: join(config.dataDir, SPEND_ATTRIBUTION_DB_FILE),
814
+ onLog: (message) => console.warn(`[proxy] ${message}`),
815
+ });
705
816
  this._configPath = config.configPath ?? null;
706
817
  this._conversations = new ConversationStore(config.dataDir);
707
818
  this._pinnedPeer = config.pinnedPeerId?.toLowerCase() ?? null;
@@ -770,7 +881,7 @@ export class BuyerProxy {
770
881
  * (or can be) loaded, undefined when no stored identity has that name.
771
882
  */
772
883
  async _resolveBuyerIdentity(header) {
773
- const name = header?.trim().toLowerCase() ?? '';
884
+ const name = buyerIdentityName(header);
774
885
  if (!name || name === DEFAULT_BUYER_IDENTITY)
775
886
  return null;
776
887
  const loaded = await this._buyerIdentities.ensure(name).catch((err) => {
@@ -779,10 +890,531 @@ export class BuyerProxy {
779
890
  });
780
891
  return loaded ? name : undefined;
781
892
  }
782
- /** `?identity=` on control-plane reads; undefined when it names no stored identity. */
783
- async _controlPlaneIdentity(path) {
784
- const param = new URL(path, 'http://localhost').searchParams.get('identity');
785
- return this._resolveBuyerIdentity(param ?? undefined);
893
+ /**
894
+ * Whether a request may act as this buyer identity. The default wallet is
895
+ * always allowed; any other is a workspace wallet, so once a control secret
896
+ * exists only the gateway may name it (loopback callers otherwise, as for
897
+ * the attributed-spend feed).
898
+ */
899
+ _mayUseBuyerIdentity(req, value) {
900
+ const name = buyerIdentityName(value);
901
+ if (!name || name === DEFAULT_BUYER_IDENTITY)
902
+ return true;
903
+ if (!isLoopbackAddress(req.socket?.remoteAddress))
904
+ return false;
905
+ return this._readControlSecret() === null || this._isGatewayAuthenticated(req.headers);
906
+ }
907
+ /**
908
+ * `?identity=` on control-plane requests (a body's `identity`, when given,
909
+ * wins); undefined when it names no stored identity.
910
+ */
911
+ async _controlPlaneIdentity(path, bodyIdentity) {
912
+ const param = bodyIdentity ?? new URL(path, 'http://localhost').searchParams.get('identity') ?? undefined;
913
+ return this._resolveBuyerIdentity(param);
914
+ }
915
+ /**
916
+ * The gateway↔buyer control secret: env var first, else the file the
917
+ * gateway creates under the data dir. A missing file is re-checked at most
918
+ * once a second so a gateway started after the buyer is picked up. A
919
+ * mismatch forces an immediate re-read (bypassing that throttle, at most
920
+ * every 100 ms) in case the secret was just created or rotated.
921
+ */
922
+ _readControlSecret(forceReread = false) {
923
+ const fromEnv = process.env[GATEWAY_CONTROL_SECRET_ENV]?.trim();
924
+ if (fromEnv)
925
+ return fromEnv;
926
+ const now = Date.now();
927
+ if (forceReread) {
928
+ if (now - this._controlSecretForcedAt < CONTROL_SECRET_FORCED_RECHECK_MS)
929
+ return this._controlSecret;
930
+ this._controlSecretForcedAt = now;
931
+ }
932
+ else {
933
+ if (this._controlSecret)
934
+ return this._controlSecret;
935
+ if (now - this._controlSecretCheckedAt < CONTROL_SECRET_RECHECK_MS)
936
+ return this._controlSecret;
937
+ }
938
+ this._controlSecretCheckedAt = now;
939
+ try {
940
+ const value = readFileSync(join(this._stateDir, GATEWAY_CONTROL_SECRET_FILE), 'utf8').trim();
941
+ this._controlSecret = value.length > 0 ? value : null;
942
+ }
943
+ catch {
944
+ // Not created yet (no gateway): keep whatever we had.
945
+ }
946
+ return this._controlSecret;
947
+ }
948
+ /** Whether the request carries the local gateway's control secret. */
949
+ _isGatewayAuthenticated(headers) {
950
+ const raw = headers[GATEWAY_CONTROL_HEADER];
951
+ const provided = Array.isArray(raw) ? raw[0] : raw;
952
+ if (!provided)
953
+ return false;
954
+ const secret = this._readControlSecret();
955
+ if (secret && secretsMatch(provided, secret))
956
+ return true;
957
+ const reread = this._readControlSecret(true);
958
+ return reread !== null && reread !== secret && secretsMatch(provided, reread);
959
+ }
960
+ /**
961
+ * Reads and strips the gateway routing-policy headers. A control header
962
+ * that is present but wrong fails closed (the gateway meant to restrict
963
+ * this request); a policy sent with no control header at all is dropped
964
+ * and ignored. An authenticated but malformed policy is an error, never
965
+ * unrestricted routing.
966
+ */
967
+ _takeRequestPolicy(headers) {
968
+ const hasAuth = headers[GATEWAY_CONTROL_HEADER] !== undefined;
969
+ const encoded = headers[ROUTING_POLICY_HEADER];
970
+ const authenticated = hasAuth && this._isGatewayAuthenticated(headers);
971
+ delete headers[GATEWAY_CONTROL_HEADER];
972
+ delete headers[ROUTING_POLICY_HEADER];
973
+ if (hasAuth && !authenticated)
974
+ return { kind: 'auth_invalid' };
975
+ if (encoded === undefined)
976
+ return { kind: 'none' };
977
+ if (!authenticated) {
978
+ if (!this._loggedUnauthenticatedPolicy) {
979
+ this._loggedUnauthenticatedPolicy = true;
980
+ console.warn(`[proxy] Ignoring ${ROUTING_POLICY_HEADER} without a valid ${GATEWAY_CONTROL_HEADER} (logged once).`);
981
+ }
982
+ return { kind: 'none' };
983
+ }
984
+ const policy = decodePolicyHeader(encoded);
985
+ if (!policy)
986
+ return { kind: 'invalid' };
987
+ // A policy that changes nothing routes exactly like no policy (as on a
988
+ // buyer without a gateway), down to the error messages.
989
+ const meaningful = meaningfulPolicy(policy);
990
+ return Object.keys(meaningful).length > 0 ? { kind: 'policy', policy: meaningful } : { kind: 'none' };
991
+ }
992
+ /**
993
+ * The buyer's hard limits (min reputation, required verifier), which judge
994
+ * every request, pinned ones included.
995
+ */
996
+ _buyerHardPolicy() {
997
+ return buyerHardPolicy({
998
+ minPeerReputation: this._minPeerReputation,
999
+ verifierRequired: this._verifier?.require === true,
1000
+ });
1001
+ }
1002
+ /** `buyer.routingPreferences` as a policy level (empty without preferences). */
1003
+ _buyerPreferencePolicy() {
1004
+ return this._routingPreferences ? buyerPreferencePolicy(this._routingPreferences) : {};
1005
+ }
1006
+ /**
1007
+ * The buyer's whole config as the top policy level for automatic routing:
1008
+ * hard limits plus routing preferences. Reported to the gateway as the
1009
+ * route preview's `buyer` level.
1010
+ */
1011
+ _buyerPolicy() {
1012
+ return narrowPolicy(this._buyerHardPolicy(), this._buyerPreferencePolicy());
1013
+ }
1014
+ /** A gateway policy narrowed under the buyer's hard limits; null when the request carries none. Judges pins. */
1015
+ _effectivePolicy(requestPolicy) {
1016
+ if (!requestPolicy)
1017
+ return null;
1018
+ return narrowPolicy(this._buyerHardPolicy(), requestPolicy);
1019
+ }
1020
+ /** Null when the seller can be verified with this buyer's verifier, else why not. */
1021
+ _unverifiedReason(peer) {
1022
+ if (!this._verifier)
1023
+ return 'not verified (verification disabled on this buyer)';
1024
+ const supported = parseVerifierCapabilities(peer.capabilities);
1025
+ return selectVerifier({ ...this._verifier, require: true }, supported) === null
1026
+ ? 'not verified (no supported verifier)'
1027
+ : null;
1028
+ }
1029
+ _policyFacts(peer, offer) {
1030
+ const score = normalizedModelReputationScore(peer);
1031
+ return {
1032
+ peerId: peer.peerId,
1033
+ trustScore: score,
1034
+ reputation: score,
1035
+ inputUsdPerMillion: offer?.inputUsdPerMillion ?? null,
1036
+ outputUsdPerMillion: offer?.outputUsdPerMillion ?? null,
1037
+ cachedInputUsdPerMillion: offer?.cachedInputUsdPerMillion ?? null,
1038
+ imageUsdPerImage: offer?.maxImageUsdPerImage ?? offer?.minImageUsdPerImage ?? null,
1039
+ unverifiedReason: this._unverifiedReason(peer),
1040
+ teeCapable: parseVerifierCapabilities(peer.capabilities).supported.includes(TEE_VERIFIER_ID),
1041
+ };
1042
+ }
1043
+ /** Latency is tracked per seller and model: one seller can serve a small model fast and a large one slowly. */
1044
+ _latencyKey(peerId, serviceId) {
1045
+ return `${peerId.toLowerCase()}|${serviceId ? policyModelKey(serviceId) : ''}`;
1046
+ }
1047
+ _peerLatency(peerId, serviceId) {
1048
+ return this._peerLatencyMs.get(this._latencyKey(peerId, serviceId)) ?? null;
1049
+ }
1050
+ _recordPeerLatency(peerId, serviceId, latencyMs) {
1051
+ if (!Number.isFinite(latencyMs) || latencyMs < 0)
1052
+ return;
1053
+ const key = this._latencyKey(peerId, serviceId);
1054
+ const previous = this._peerLatencyMs.get(key);
1055
+ // Re-inserted so the map's order is least recently measured first.
1056
+ this._peerLatencyMs.delete(key);
1057
+ this._peerLatencyMs.set(key, previous === undefined
1058
+ ? latencyMs
1059
+ : previous + LATENCY_EMA_ALPHA * (latencyMs - previous));
1060
+ if (this._peerLatencyMs.size > MAX_LATENCY_ENTRIES) {
1061
+ const oldest = this._peerLatencyMs.keys().next().value;
1062
+ if (oldest !== undefined)
1063
+ this._peerLatencyMs.delete(oldest);
1064
+ }
1065
+ }
1066
+ /**
1067
+ * Candidate selection and ranking for a model-only request. The real
1068
+ * routing path and `GET /_antseed/route-preview` both call this, so the
1069
+ * preview shows exactly what routing would do. `verdicts` lists every
1070
+ * seller advertising the model with why it is (in)eligible; `candidates`
1071
+ * are the eligible ones in dispatch order.
1072
+ */
1073
+ _planModelRoutes(input) {
1074
+ const { modelPeers, modelPlans, request, requestProtocol, requestedService, explicitProvider, requiredParameters } = input;
1075
+ const policyRouter = this._node.router;
1076
+ const requestPolicy = input.requestPolicy;
1077
+ // Under a gateway policy the buyer's config is the top level of the
1078
+ // chain: the gateway narrows it, never replaces it.
1079
+ const effectivePolicy = requestPolicy ? narrowPolicy(this._buyerPolicy(), requestPolicy) : null;
1080
+ const verdicts = new Map();
1081
+ const routeCandidates = [];
1082
+ for (const peer of modelPeers) {
1083
+ const plan = modelPlans.get(peer.peerId)
1084
+ ?? resolvePeerRoutePlan(peer, requestProtocol, requestedService, explicitProvider, 'strict');
1085
+ if (!plan?.serviceId)
1086
+ continue;
1087
+ const offer = findAdvertisedServiceOffer(peer, plan.provider, plan.serviceId);
1088
+ if (!offer)
1089
+ continue;
1090
+ const verdict = { peer, serviceId: plan.serviceId, offer, eligible: false, rank: null, reasons: [] };
1091
+ verdicts.set(peer.peerId, verdict);
1092
+ const missingRequired = plan.selection?.requiresTransform
1093
+ ? [...requiredParameters]
1094
+ : findMissingRequiredParameters(peer, plan.provider, plan.serviceId, requiredParameters);
1095
+ if (missingRequired.length > 0) {
1096
+ log(`Capability filter: peer ${peer.peerId.slice(0, 12)}... service="${plan.serviceId}" `
1097
+ + `missing required parameter(s): ${missingRequired.join(', ')}`);
1098
+ verdict.reasons.push(`missing required parameter(s): ${missingRequired.join(', ')}`);
1099
+ continue;
1100
+ }
1101
+ const requestForPolicy = withRoutedModel(request, plan.serviceId);
1102
+ if (!peerAllowedByPolicy(policyRouter, requestForPolicy, peer)) {
1103
+ verdict.reasons.push(buyerConfigReason(policyRouter?.explainPolicyRejection?.(requestForPolicy, peer) ?? 'outside buyer pricing/reputation limits'));
1104
+ continue;
1105
+ }
1106
+ routeCandidates.push({
1107
+ offer,
1108
+ peer,
1109
+ peerId: peer.peerId,
1110
+ serviceId: plan.serviceId,
1111
+ request: requestForPolicy,
1112
+ reputation: normalizedModelReputationScore(peer) ?? -1,
1113
+ hasCachedInputPricing: offer.cachedInputUsdPerMillion !== undefined,
1114
+ inputUsdPerMillion: offer.inputUsdPerMillion ?? null,
1115
+ outputUsdPerMillion: offer.outputUsdPerMillion ?? null,
1116
+ minImageUsdPerImage: offer.minImageUsdPerImage ?? null,
1117
+ });
1118
+ }
1119
+ const now = this._now();
1120
+ const preferCachedPricing = routeCandidates.some((candidate) => candidate.hasCachedInputPricing);
1121
+ const ranked = routeCandidates.map((candidate) => {
1122
+ const health = this._peerHealth.get(candidate.peer.peerId);
1123
+ return {
1124
+ ...candidate,
1125
+ effectiveReputationScore: effectiveModelReputationScore(candidate.reputation >= 0 ? candidate.reputation : null, candidate.hasCachedInputPricing, preferCachedPricing, modelRouteTotalPrice(candidate) === 0),
1126
+ peerCooldownUntil: health?.cooldownUntil ?? null,
1127
+ peerFailureStreak: health?.failureStreak ?? 0,
1128
+ };
1129
+ });
1130
+ let candidates;
1131
+ const routingPreferences = this._routingPreferences;
1132
+ if (requestPolicy && effectivePolicy) {
1133
+ // Exclusions from the merged chain, reported per level: the gateway's
1134
+ // policy on raw trust, the buyer's preferences on the effective trust
1135
+ // its own automatic routing uses. (The buyer's hard limits were
1136
+ // already applied by the router plugin above.) Excluded by the merged
1137
+ // policy exactly when excluded by either level.
1138
+ const buyerPreferences = this._buyerPreferencePolicy();
1139
+ const allowed = ranked.filter((candidate) => {
1140
+ const facts = this._policyFacts(candidate.peer, candidate.offer);
1141
+ const reasons = policyExclusionReasons(requestPolicy, facts);
1142
+ addBuyerConfigReasons(reasons, policyExclusionReasons(buyerPreferences, {
1143
+ ...facts, trustScore: modelRouteReputationScore(candidate), trustPenalty: trustPenalty(candidate),
1144
+ }));
1145
+ if (reasons.length === 0)
1146
+ return true;
1147
+ verdicts.get(candidate.peerId)?.reasons.push(...reasons);
1148
+ return false;
1149
+ });
1150
+ candidates = routingPreferences
1151
+ ? rankModelRoutes(allowed, rankingPreferences(routingPreferences, effectivePolicy), now)
1152
+ : this._readyFirst(allowed, verdicts, now);
1153
+ }
1154
+ else if (routingPreferences) {
1155
+ candidates = [];
1156
+ for (const candidate of rankModelRoutes(ranked, routingPreferences, now)) {
1157
+ if (isModelRouteEligible(candidate, routingPreferences)) {
1158
+ candidates.push(candidate);
1159
+ }
1160
+ else {
1161
+ verdicts.get(candidate.peerId)?.reasons.push(...preferenceExclusionReasons(routingPreferences, candidate.peerId, modelRouteReputationScore(candidate), trustPenalty(candidate)));
1162
+ }
1163
+ }
1164
+ }
1165
+ else {
1166
+ candidates = this._readyFirst(ranked, verdicts, now);
1167
+ }
1168
+ const isCooling = (candidate) => isModelRouteCoolingDown(candidate, now);
1169
+ let chainPosition = null;
1170
+ if (effectivePolicy) {
1171
+ const ordering = orderByPolicy(candidates, effectivePolicy, requestedService, {
1172
+ peerId: (candidate) => candidate.peerId,
1173
+ totalPrice: (candidate) => modelRouteTotalPrice(candidate),
1174
+ trustScore: (candidate) => (candidate.reputation >= 0 ? candidate.reputation : null),
1175
+ latencyMs: (candidate) => this._peerLatency(candidate.peerId, candidate.serviceId),
1176
+ coolingDown: isCooling,
1177
+ });
1178
+ for (const candidate of candidates) {
1179
+ const reason = ordering.excluded.get(normalizePolicyPeerId(candidate.peerId));
1180
+ if (reason)
1181
+ verdicts.get(candidate.peerId)?.reasons.push(reason);
1182
+ }
1183
+ candidates = ordering.ordered;
1184
+ chainPosition = ordering.chainPosition;
1185
+ }
1186
+ // Conversation affinity is a soft preference: it only reorders sellers the
1187
+ // policy already allows, and yields to an explicit per-model route.
1188
+ const preferredPeerId = input.preferredPeerId;
1189
+ if (preferredPeerId && !findModelRoute(effectivePolicy, requestedService)) {
1190
+ const preferredIndex = candidates.findIndex((candidate) => (candidate.peer.peerId.toLowerCase() === preferredPeerId
1191
+ && !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now)));
1192
+ if (preferredIndex > 0) {
1193
+ const [preferred] = candidates.splice(preferredIndex, 1);
1194
+ if (preferred)
1195
+ candidates.unshift(preferred);
1196
+ }
1197
+ }
1198
+ candidates.forEach((candidate, index) => {
1199
+ const verdict = verdicts.get(candidate.peerId);
1200
+ if (!verdict)
1201
+ return;
1202
+ verdict.eligible = true;
1203
+ verdict.rank = index + 1;
1204
+ const position = chainPosition?.get(normalizePolicyPeerId(candidate.peerId));
1205
+ if (position !== undefined)
1206
+ verdict.reasons.push(`route chain #${position + 1}`);
1207
+ if (isCooling(candidate))
1208
+ verdict.reasons.push('cooling down (tried last)');
1209
+ if (modelRouteTotalPrice(candidate) === 0)
1210
+ verdict.reasons.push('free');
1211
+ const latency = this._peerLatency(candidate.peerId, candidate.serviceId);
1212
+ if (latency !== null)
1213
+ verdict.reasons.push(`latency ~${Math.round(latency)}ms`);
1214
+ });
1215
+ return { candidates, verdicts: [...verdicts.values()], effectivePolicy };
1216
+ }
1217
+ /**
1218
+ * Without routing preferences: best effective reputation first, and only
1219
+ * sellers not cooling down while any are ready.
1220
+ */
1221
+ _readyFirst(ranked, verdicts, now) {
1222
+ ranked.sort((a, b) => (b.effectiveReputationScore ?? -1) - (a.effectiveReputationScore ?? -1)
1223
+ || a.peer.peerId.localeCompare(b.peer.peerId));
1224
+ const ready = [];
1225
+ const cooling = [];
1226
+ for (const candidate of ranked) {
1227
+ if (isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now))
1228
+ cooling.push(candidate);
1229
+ else
1230
+ ready.push(candidate);
1231
+ }
1232
+ if (ready.length === 0)
1233
+ return ranked;
1234
+ for (const candidate of cooling)
1235
+ verdicts.get(candidate.peerId)?.reasons.push('cooling down');
1236
+ return ready;
1237
+ }
1238
+ /** `GET /_antseed/route-preview`: what routing would do for a model-only request, ineligible sellers included. */
1239
+ async _routePreview(model, requestPolicy) {
1240
+ const peers = await this._getPeers();
1241
+ const request = {
1242
+ requestId: randomUUID(),
1243
+ method: 'POST',
1244
+ path: '/v1/chat/completions',
1245
+ headers: { 'content-type': 'application/json' },
1246
+ body: new TextEncoder().encode(JSON.stringify({ model })),
1247
+ };
1248
+ const { candidatePeers, routePlanByPeerId } = selectCandidatePeersForRouting(peers, null, model, null, 'strict');
1249
+ const plan = this._planModelRoutes({
1250
+ modelPeers: candidatePeers,
1251
+ modelPlans: routePlanByPeerId,
1252
+ request,
1253
+ requestProtocol: null,
1254
+ requestedService: model,
1255
+ explicitProvider: null,
1256
+ requiredParameters: [],
1257
+ requestPolicy,
1258
+ preferredPeerId: null,
1259
+ });
1260
+ const effective = plan.effectivePolicy;
1261
+ const modelAllowed = policyAllowsModel(effective, model);
1262
+ return plan.verdicts
1263
+ .map((verdict) => ({
1264
+ peerId: verdict.peer.peerId,
1265
+ displayName: verdict.peer.displayName ?? null,
1266
+ rank: modelAllowed ? verdict.rank : null,
1267
+ eligible: modelAllowed && verdict.eligible,
1268
+ reasons: modelAllowed ? verdict.reasons : ['model not allowed', ...verdict.reasons],
1269
+ inputUsdPerMillion: verdict.offer.inputUsdPerMillion ?? null,
1270
+ outputUsdPerMillion: verdict.offer.outputUsdPerMillion ?? null,
1271
+ trustScore: normalizedModelReputationScore(verdict.peer),
1272
+ }))
1273
+ .sort((a, b) => (a.rank ?? Number.POSITIVE_INFINITY) - (b.rank ?? Number.POSITIVE_INFINITY)
1274
+ || a.peerId.localeCompare(b.peerId));
1275
+ }
1276
+ /** Forget cached balances of an address, e.g. once a deposit sweep is credited. */
1277
+ invalidateBalances(address) {
1278
+ this._balanceCache.invalidate(address.toLowerCase());
1279
+ this._balanceReader?.invalidate?.(address);
1280
+ }
1281
+ /**
1282
+ * On-chain balances for a buyer identity, cached ~15 s per address and
1283
+ * answered from the cache (refreshing in the background) for a while
1284
+ * after that. When the chain cannot be read the last values come back
1285
+ * with `stale: true`. `fresh` re-reads, at most every few seconds.
1286
+ */
1287
+ async _readBalances(address, fresh = false) {
1288
+ const reader = this._balanceReader;
1289
+ const key = address.toLowerCase();
1290
+ const cached = this._balanceCache.peek(key);
1291
+ const force = fresh && (!cached || Date.now() - cached.fetchedAt >= BALANCE_FRESH_FLOOR_MS);
1292
+ if (force)
1293
+ reader.invalidate?.(address);
1294
+ const result = await this._balanceCache.read(key, { ttlMs: BALANCE_CACHE_TTL_MS, staleWhileRevalidateMs: BALANCE_REVALIDATE_MS, force }, async () => {
1295
+ const [balance, wallet, creditLimit, operator] = await Promise.allSettled([
1296
+ reader.getBuyerBalance(address),
1297
+ reader.getUSDCBalance(address),
1298
+ reader.getBuyerCreditLimit(address),
1299
+ reader.getOperator(address),
1300
+ ]);
1301
+ if (balance.status === 'rejected')
1302
+ throw balance.reason;
1303
+ return {
1304
+ address,
1305
+ available: formatUsdcBaseUnits(balance.value.available),
1306
+ reserved: formatUsdcBaseUnits(balance.value.reserved),
1307
+ walletUsdc: wallet.status === 'fulfilled' ? formatUsdcBaseUnits(wallet.value) : null,
1308
+ creditLimit: creditLimit.status === 'fulfilled' ? formatUsdcBaseUnits(creditLimit.value) : null,
1309
+ operator: operator.status === 'fulfilled' && operator.value && operator.value.toLowerCase() !== ZERO_ADDRESS
1310
+ ? operator.value
1311
+ : null,
1312
+ ...(reader.isStale?.(address) ? { stale: true } : {}),
1313
+ };
1314
+ });
1315
+ return result.stale ? { ...result.value, stale: true } : result.value;
1316
+ }
1317
+ /**
1318
+ * Endpoints the local gateway calls with the control secret. Returns false
1319
+ * when the path is not one of them, so the regular control plane handles it.
1320
+ */
1321
+ async _handleGatewayControl(req, res, method, path) {
1322
+ const url = new URL(path, 'http://localhost');
1323
+ const handlers = {
1324
+ 'GET /_antseed/route-preview': () => this._handleRoutePreview(req, res, url),
1325
+ 'GET /_antseed/balances': () => this._handleBalances(res, url),
1326
+ // Routing preferences are reloaded from the config file, so they are never overridden.
1327
+ 'GET /_antseed/setting-overrides': () => sendJson(res, 200, { overrides: this._settingOverrides }),
1328
+ 'POST /_antseed/restart': () => this._handleRestart(res),
1329
+ };
1330
+ const handler = handlers[`${method} ${url.pathname}`];
1331
+ if (!handler)
1332
+ return false;
1333
+ if (!isLoopbackAddress(req.socket?.remoteAddress) || !this._isGatewayAuthenticated(req.headers)) {
1334
+ sendJson(res, 401, { ok: false, error: 'gateway_auth_required' });
1335
+ return true;
1336
+ }
1337
+ await handler();
1338
+ return true;
1339
+ }
1340
+ /** `GET /_antseed/route-preview?model=<id>`, optionally under a policy header. */
1341
+ async _handleRoutePreview(req, res, url) {
1342
+ const model = url.searchParams.get('model')?.trim() ?? '';
1343
+ if (!model) {
1344
+ sendJson(res, 400, { ok: false, error: 'model is required' });
1345
+ return;
1346
+ }
1347
+ const encoded = req.headers[ROUTING_POLICY_HEADER];
1348
+ const raw = Array.isArray(encoded) ? encoded[0] : encoded;
1349
+ let policy = null;
1350
+ if (raw !== undefined) {
1351
+ policy = decodePolicyHeader(raw);
1352
+ if (!policy) {
1353
+ sendJson(res, 400, { ok: false, error: 'invalid_routing_policy' });
1354
+ return;
1355
+ }
1356
+ if (Object.keys(meaningfulPolicy(policy)).length === 0)
1357
+ policy = null;
1358
+ }
1359
+ // `buyer`: the top level of the chain (hard limits plus routing
1360
+ // preferences), so the console can show it (max pricing lives in the
1361
+ // router plugin's config).
1362
+ sendJson(res, 200, { model, candidates: await this._routePreview(model, policy), buyer: this._buyerPolicy() });
1363
+ }
1364
+ /** `GET /_antseed/balances?identity=<name>`: on-chain balances of a buyer identity. */
1365
+ async _handleBalances(res, url) {
1366
+ const identity = await this._resolveBuyerIdentity(url.searchParams.get('identity') ?? undefined);
1367
+ if (identity === undefined) {
1368
+ sendUnknownBuyerIdentity(res);
1369
+ return;
1370
+ }
1371
+ if (!this._balanceReader) {
1372
+ sendJson(res, 503, { ok: false, error: 'payments_disabled' });
1373
+ return;
1374
+ }
1375
+ const name = identity ?? DEFAULT_BUYER_IDENTITY;
1376
+ const address = this._node.buyerIdentities?.().find((entry) => entry.name === name)?.address;
1377
+ if (!address) {
1378
+ sendUnknownBuyerIdentity(res);
1379
+ return;
1380
+ }
1381
+ try {
1382
+ sendJson(res, 200, await this._readBalances(address, url.searchParams.get('fresh') === '1'));
1383
+ }
1384
+ catch (err) {
1385
+ sendJson(res, 502, { ok: false, error: `Balance read failed: ${err instanceof Error ? err.message : String(err)}` });
1386
+ }
1387
+ }
1388
+ /**
1389
+ * `POST /_antseed/restart`. Only offered under a supervisor that brings the
1390
+ * buyer back (see `buyer start`); otherwise the console asks the operator
1391
+ * to restart it.
1392
+ */
1393
+ _handleRestart(res) {
1394
+ const restartHandler = this._restartHandler;
1395
+ if (!restartHandler) {
1396
+ sendJson(res, 503, { ok: false, error: 'restart_unsupported' });
1397
+ return;
1398
+ }
1399
+ const waitMs = this._restartAllowedAt - Date.now();
1400
+ if (!this._restartRequested && waitMs > 0) {
1401
+ res.setHeader('retry-after', String(Math.ceil(waitMs / 1000)));
1402
+ sendJson(res, 429, { ok: false, error: 'restart_too_soon' });
1403
+ return;
1404
+ }
1405
+ if (!this._restartRequested) {
1406
+ this._restartRequested = true;
1407
+ log('Restart requested by the local gateway; shutting down gracefully.');
1408
+ res.once('finish', () => {
1409
+ setImmediate(() => {
1410
+ void Promise.resolve().then(restartHandler).catch((err) => {
1411
+ console.error('[proxy] restart failed:', err instanceof Error ? err.message : String(err));
1412
+ });
1413
+ });
1414
+ });
1415
+ }
1416
+ res.writeHead(202, { 'content-type': 'application/json', connection: 'close' });
1417
+ res.end(JSON.stringify({ ok: true, restarting: true }));
786
1418
  }
787
1419
  /**
788
1420
  * Remember which chat a request belongs to for the life of the request plus
@@ -806,6 +1438,22 @@ export class BuyerProxy {
806
1438
  this._depositWatcher = watcher;
807
1439
  this._depositWatcherAbsence = watcher ? null : absenceReason;
808
1440
  }
1441
+ /** Attach (or detach with null) the deposit watcher of a named buyer identity. */
1442
+ setIdentityDepositWatcher(identity, watcher) {
1443
+ if (watcher)
1444
+ this._identityDepositWatchers.set(identity, watcher);
1445
+ else
1446
+ this._identityDepositWatchers.delete(identity);
1447
+ }
1448
+ /** Watcher for an identity (null = default) and, when there is none, why. */
1449
+ _depositWatcherFor(identity) {
1450
+ const watcher = identity ? this._identityDepositWatchers.get(identity) ?? null : this._depositWatcher;
1451
+ if (watcher)
1452
+ return { watcher, reason: null };
1453
+ // A named identity without a watcher shares the default's cause; if the
1454
+ // default has one, the identity simply has not been given one yet.
1455
+ return { watcher: null, reason: this._depositWatcherAbsence ?? (identity ? 'identity-not-watched' : null) };
1456
+ }
809
1457
  /** Latest relayer receipt for a sweep authNonce, if one has arrived. */
810
1458
  getSweepReceipt(authNonce) {
811
1459
  return this._sweepReceipts.get(authNonce.toLowerCase()) ?? null;
@@ -988,12 +1636,18 @@ export class BuyerProxy {
988
1636
  return;
989
1637
  try {
990
1638
  const config = await loadConfig(this._configPath);
1639
+ // Read straight from the file: no CLI flag or env var overrides routing
1640
+ // preferences, and resolving the full effective buyer config would let
1641
+ // an unrelated invalid field block this reload.
991
1642
  const next = config.buyer.routingPreferences;
992
1643
  this._routingPreferences = {
993
1644
  ...next,
994
1645
  allowedPeerIds: [...next.allowedPeerIds],
995
1646
  blockedPeerIds: [...next.blockedPeerIds],
996
1647
  };
1648
+ // `minPeerReputation` (like max pricing) is not reloaded: the router
1649
+ // plugin enforces it with the value it started with, so it applies on
1650
+ // restart only, and the proxy keeps the same value to stay in step.
997
1651
  log(`Routing preferences reloaded: minTrust=${next.minTrustScore} maxInput=${next.maxInputUsdPerMillion} `
998
1652
  + `preferFree=${next.preferFreePeers} allow=${next.allowedPeerIds.length} block=${next.blockedPeerIds.length}`);
999
1653
  }
@@ -1433,9 +2087,11 @@ export class BuyerProxy {
1433
2087
  const outcome = await this._teeVerification.verifyForDisplay(peer, () => runVerifier({ require: false, prefer: [TEE_VERIFIER_ID] }, peer.peerId, peer.capabilities, (chosen) => makeVerifierReach(this._node, peer, chosen, signal), signal));
1434
2088
  if (outcome.code === 'busy')
1435
2089
  throw new Error(outcome.reason);
1436
- });
2090
+ }, this._isGatewayAuthenticated(req.headers));
1437
2091
  return;
1438
2092
  }
2093
+ if (await this._handleGatewayControl(req, res, method, path))
2094
+ return;
1439
2095
  const origin = req.headers.origin ?? '';
1440
2096
  const isLocal = origin.startsWith('http://127.0.0.1') || origin.startsWith('http://localhost') || origin === 'file://';
1441
2097
  if (isLocal)
@@ -1498,6 +2154,12 @@ export class BuyerProxy {
1498
2154
  onChainUsageLastEpochUsdcMicros: p.onChainUsageLastEpochUsdcMicros ?? null,
1499
2155
  onChainWashFlagged: p.onChainWashFlagged ?? null,
1500
2156
  onChainWashShareBps: p.onChainWashShareBps ?? null,
2157
+ maxConcurrency: p.maxConcurrency ?? null,
2158
+ currentLoad: p.currentLoad ?? null,
2159
+ onChainChannelCount: p.onChainChannelCount ?? null,
2160
+ onChainTotalVolumeUsdcMicros: p.onChainTotalVolumeUsdcMicros ?? null,
2161
+ onChainLastSettledAtSec: p.onChainLastSettledAtSec ?? null,
2162
+ onChainSybilRisk: p.onChainSybilRisk ?? null,
1501
2163
  verificationResults: p.verificationResults,
1502
2164
  lastSeen: p.lastSeen,
1503
2165
  }));
@@ -1740,8 +2402,7 @@ export class BuyerProxy {
1740
2402
  }
1741
2403
  if (path.startsWith('/_antseed/buyer-identities') && method === 'GET') {
1742
2404
  await this._buyerIdentities.loadAll();
1743
- res.writeHead(200, { 'content-type': 'application/json' });
1744
- res.end(JSON.stringify({ ok: true, identities: this._node.buyerIdentities() }));
2405
+ sendJson(res, 200, { ok: true, identities: this._node.buyerIdentities() });
1745
2406
  return;
1746
2407
  }
1747
2408
  if (path.startsWith('/_antseed/channels') && method === 'GET') {
@@ -1766,12 +2427,24 @@ export class BuyerProxy {
1766
2427
  res.end(JSON.stringify({ ok: true, channels: channelsWithCapabilities }));
1767
2428
  return;
1768
2429
  }
1769
- // Unauthenticated like every /_antseed route: it relies on the buyer
1770
- // listening on 127.0.0.1 only, since it exposes per-seller spend and tags.
2430
+ // Gateway only: the feed holds up to 7 days of per-seller spend and tags.
2431
+ // Once a control secret exists, reading it takes that secret and every
2432
+ // read acknowledges the events before its cursor (lets them be pruned).
2433
+ // Without one (no gateway on this data dir, e.g. a gateway started with
2434
+ // its own --data-dir), loopback callers read it unacknowledged, as before
2435
+ // the secret existed, so such a gateway keeps tracking spend.
1771
2436
  if (path.startsWith('/_antseed/attributed-spend') && method === 'GET') {
2437
+ if (!isLoopbackAddress(req.socket?.remoteAddress)) {
2438
+ sendJson(res, 401, { ok: false, error: 'gateway_auth_required' });
2439
+ return;
2440
+ }
2441
+ const hasSecret = this._readControlSecret() !== null;
2442
+ if (hasSecret && !this._isGatewayAuthenticated(req.headers)) {
2443
+ sendJson(res, 401, { ok: false, error: 'gateway_auth_required' });
2444
+ return;
2445
+ }
1772
2446
  const after = Number(new URL(path, 'http://localhost').searchParams.get('after') ?? '0');
1773
- res.writeHead(200, { 'content-type': 'application/json' });
1774
- res.end(JSON.stringify({ ok: true, ...this._spendAttribution.page(Number.isFinite(after) ? after : 0) }));
2447
+ sendJson(res, 200, { ok: true, ...this._spendAttribution.page(Number.isFinite(after) ? after : 0, { ack: hasSecret }) });
1775
2448
  return;
1776
2449
  }
1777
2450
  if (path.startsWith('/_antseed/buyer-usage') && method === 'GET') {
@@ -1862,6 +2535,11 @@ export class BuyerProxy {
1862
2535
  res.end(JSON.stringify({ ok: false, error: 'Missing peerId' }));
1863
2536
  return;
1864
2537
  }
2538
+ // Closing hands the seller the identity's latest spending authorization.
2539
+ if (!this._mayUseBuyerIdentity(req, identityParam)) {
2540
+ sendJson(res, 401, { ok: false, error: 'gateway_auth_required' });
2541
+ return;
2542
+ }
1865
2543
  try {
1866
2544
  const identity = await this._resolveBuyerIdentity(identityParam);
1867
2545
  if (identity === undefined)
@@ -1882,23 +2560,29 @@ export class BuyerProxy {
1882
2560
  // Hot-wallet deposit watcher (auto-sweep). The desktop and `antseed
1883
2561
  // deposit` drive the daemon's single watcher through these instead of
1884
2562
  // running a second signer against the same wallet.
1885
- if (path === '/_antseed/deposits/status' && method === 'GET') {
2563
+ const controlPathname = path.split('?')[0];
2564
+ if (controlPathname === '/_antseed/deposits/status' && method === 'GET') {
2565
+ const identity = await this._controlPlaneIdentity(path);
2566
+ if (identity === undefined)
2567
+ return sendUnknownBuyerIdentity(res);
2568
+ const { watcher, reason } = this._depositWatcherFor(identity);
1886
2569
  res.writeHead(200, { 'content-type': 'application/json' });
1887
2570
  res.end(JSON.stringify({
1888
2571
  ok: true,
1889
- watcher: this._depositWatcher !== null,
2572
+ ...(identity ? { identity } : {}),
2573
+ watcher: watcher !== null,
1890
2574
  // Why no watcher runs (null while one is attached) — lets UIs say
1891
2575
  // "payments are disabled" vs "this chain has no deposit relay".
1892
- reason: this._depositWatcher ? null : this._depositWatcherAbsence,
2576
+ reason,
1893
2577
  // Live payments health: configured/active flags + chain-RPC
1894
2578
  // reachability from the node's background monitor. Optional call —
1895
2579
  // tolerate an older @antseed/node without it.
1896
2580
  payments: this._node.getPaymentsStatus?.() ?? null,
1897
- status: this._depositWatcher?.status() ?? null,
2581
+ status: watcher?.status() ?? null,
1898
2582
  }));
1899
2583
  return;
1900
2584
  }
1901
- if (path === '/_antseed/deposits/watch' && method === 'POST') {
2585
+ if (controlPathname === '/_antseed/deposits/watch' && method === 'POST') {
1902
2586
  const chunks = [];
1903
2587
  let totalSize = 0;
1904
2588
  for await (const chunk of req) {
@@ -1911,9 +2595,12 @@ export class BuyerProxy {
1911
2595
  chunks.push(chunk);
1912
2596
  }
1913
2597
  let mode;
2598
+ let bodyIdentity;
1914
2599
  try {
1915
2600
  const body = JSON.parse(Buffer.concat(chunks).toString() || '{}');
1916
2601
  mode = String(body.mode ?? 'active');
2602
+ if (typeof body.identity === 'string')
2603
+ bodyIdentity = body.identity;
1917
2604
  }
1918
2605
  catch {
1919
2606
  res.writeHead(400, { 'content-type': 'application/json' });
@@ -1925,18 +2612,26 @@ export class BuyerProxy {
1925
2612
  res.end(JSON.stringify({ ok: false, error: 'mode must be "active" or "background"' }));
1926
2613
  return;
1927
2614
  }
1928
- const watcher = this._depositWatcher;
2615
+ const identity = await this._controlPlaneIdentity(path, bodyIdentity);
2616
+ if (identity === undefined)
2617
+ return sendUnknownBuyerIdentity(res);
2618
+ const { watcher, reason } = this._depositWatcherFor(identity);
1929
2619
  if (!watcher) {
1930
- const reason = this._depositWatcherAbsence;
1931
2620
  const error = (reason && WATCHER_ABSENCE_ERRORS[reason]) ?? 'Deposit watcher unavailable.';
1932
2621
  res.writeHead(503, { 'content-type': 'application/json' });
1933
2622
  res.end(JSON.stringify({ ok: false, reason, error }));
1934
2623
  return;
1935
2624
  }
1936
- if (mode === 'active')
1937
- watcher.promote();
1938
- else
2625
+ // Only the gateway console's watch lapses on its own (it re-requests it
2626
+ // while its page is open); the desktop and `antseed deposit` demote
2627
+ // explicitly, as they always have.
2628
+ if (mode === 'active') {
2629
+ const fromGateway = isLoopbackAddress(req.socket?.remoteAddress) && this._isGatewayAuthenticated(req.headers);
2630
+ watcher.promote(fromGateway ? DEPOSIT_WATCH_ACTIVE_TTL_MS : null);
2631
+ }
2632
+ else {
1939
2633
  watcher.demote();
2634
+ }
1940
2635
  res.writeHead(200, { 'content-type': 'application/json' });
1941
2636
  res.end(JSON.stringify({ ok: true, status: watcher.status() }));
1942
2637
  return;
@@ -1957,13 +2652,17 @@ export class BuyerProxy {
1957
2652
  * `x-antseed-request-id` response header keeps the port-reuse probe in
1958
2653
  * `buyer start` recognizing this as an AntSeed proxy.
1959
2654
  */
1960
- async _handleNetworkModels(res, rawPath) {
2655
+ async _handleNetworkModels(res, rawPath, requestPolicy) {
1961
2656
  const url = new URL(rawPath, 'http://localhost');
1962
2657
  const responseHeaders = { 'content-type': 'application/json', 'x-antseed-request-id': randomUUID() };
1963
2658
  const peers = await this._getPeers();
2659
+ const peerById = new Map(peers.map((peer) => [peer.peerId, peer]));
1964
2660
  const models = buildNetworkModels(peers, this._now(), {
1965
2661
  routingPreferences: this._routingPreferences,
1966
2662
  peerHealth: this._peerHealth,
2663
+ ...(requestPolicy
2664
+ ? { offerFilter: (offer) => this._policyAllowsOffer(requestPolicy, peerById.get(offer.peerId), offer) }
2665
+ : {}),
1967
2666
  });
1968
2667
  const modelIdRaw = url.pathname.replace(/^\/v1\/models\/?/i, '');
1969
2668
  if (modelIdRaw.length > 0) {
@@ -2008,6 +2707,18 @@ export class BuyerProxy {
2008
2707
  res.writeHead(200, responseHeaders);
2009
2708
  res.end(JSON.stringify({ object: 'list', data }));
2010
2709
  }
2710
+ /**
2711
+ * Whether a request under the gateway's policy could use this offer, by
2712
+ * the checks that judge a pinned `peer@model`: the model allow list, the
2713
+ * seller-level policy, a strict model route and the buyer's hard limits.
2714
+ */
2715
+ _policyAllowsOffer(requestPolicy, peer, offer) {
2716
+ if (!peer || !policyAllowsModel(this._effectivePolicy(requestPolicy), offer.serviceId))
2717
+ return false;
2718
+ const facts = this._policyFacts(peer, offer);
2719
+ return pinnedPeerExclusionReasons(requestPolicy, facts, offer.serviceId).length === 0
2720
+ && policyExclusionReasons(this._buyerHardPolicy(), facts).length === 0;
2721
+ }
2011
2722
  async _handleRequest(req, res) {
2012
2723
  const method = req.method ?? 'GET';
2013
2724
  const path = req.url ?? '/';
@@ -2032,13 +2743,6 @@ export class BuyerProxy {
2032
2743
  res.end(JSON.stringify({ error: { message: 'Not found', type: 'invalid_request_error' } }));
2033
2744
  return;
2034
2745
  }
2035
- // `/v1/models` is answered locally from the discovered-peer cache: one
2036
- // entry per model across the whole network, with the peers serving it.
2037
- // Routed to a single pinned seller it would only cover that seller's
2038
- // services — and would require a pin just to browse the network.
2039
- if (method === 'GET' && normalizedPath.startsWith('/v1/models')) {
2040
- return this._handleNetworkModels(res, path);
2041
- }
2042
2746
  // Collect request body
2043
2747
  const chunks = [];
2044
2748
  for await (const chunk of req) {
@@ -2056,9 +2760,13 @@ export class BuyerProxy {
2056
2760
  res.end(JSON.stringify({ input_tokens: inputTokens }));
2057
2761
  return;
2058
2762
  }
2059
- // Build serialized request
2763
+ // Build serialized request. Client credentials (provider keys, cookies)
2764
+ // never leave the machine — see credential-headers.ts for why no seller
2765
+ // needs them.
2060
2766
  const headers = {};
2061
2767
  for (const [key, value] of Object.entries(req.headers)) {
2768
+ if (isClientCredentialHeader(key))
2769
+ continue;
2062
2770
  if (typeof value === 'string') {
2063
2771
  headers[key] = value;
2064
2772
  }
@@ -2068,6 +2776,44 @@ export class BuyerProxy {
2068
2776
  }
2069
2777
  // Remove host header (points to localhost, not the seller)
2070
2778
  delete headers['host'];
2779
+ // Routing policy from the local gateway. Both gateway headers are always
2780
+ // stripped here so they never reach a seller.
2781
+ const requestPolicyResult = this._takeRequestPolicy(headers);
2782
+ if (requestPolicyResult.kind === 'auth_invalid') {
2783
+ log('Request rejected: invalid gateway control secret');
2784
+ sendJson(res, 401, {
2785
+ error: {
2786
+ type: 'authentication_error',
2787
+ code: 'gateway_auth_invalid',
2788
+ message: `${GATEWAY_CONTROL_HEADER} does not match this buyer's gateway control secret.`,
2789
+ param: GATEWAY_CONTROL_HEADER,
2790
+ },
2791
+ });
2792
+ return;
2793
+ }
2794
+ if (requestPolicyResult.kind === 'invalid') {
2795
+ sendJson(res, 400, {
2796
+ error: {
2797
+ type: 'invalid_request_error',
2798
+ code: 'invalid_routing_policy',
2799
+ message: `${ROUTING_POLICY_HEADER} is not a valid routing policy.`,
2800
+ param: ROUTING_POLICY_HEADER,
2801
+ },
2802
+ });
2803
+ return;
2804
+ }
2805
+ const requestPolicy = requestPolicyResult.kind === 'policy' ? requestPolicyResult.policy : null;
2806
+ // `/v1/models` is answered locally from the discovered-peer cache: one
2807
+ // entry per model across the whole network, with the peers serving it.
2808
+ // Routed to a single pinned seller it would only cover that seller's
2809
+ // services — and would require a pin just to browse the network. Under a
2810
+ // gateway policy it lists only what the policy lets the caller use.
2811
+ if (method === 'GET' && normalizedPath.startsWith('/v1/models')) {
2812
+ return this._handleNetworkModels(res, path, requestPolicy);
2813
+ }
2814
+ // Node already answered curl's `Expect: 100-continue` for large bodies;
2815
+ // forwarded, it makes the seller's fetch fail.
2816
+ delete headers['expect'];
2071
2817
  // Internal marker from the system proxy: the body's model was assigned
2072
2818
  // by the proxy's route rewrite, not chosen by the tool. Stripped here so
2073
2819
  // it never reaches a seller.
@@ -2079,6 +2825,18 @@ export class BuyerProxy {
2079
2825
  delete headers[SPEND_ATTRIBUTION_HEADER];
2080
2826
  // The paying identity rides on the internal headers through routing, like
2081
2827
  // a peer pin, and is stripped when the request is built for the seller.
2828
+ if (!this._mayUseBuyerIdentity(req, headers[BUYER_IDENTITY_HEADER])) {
2829
+ log('Request rejected: buyer identity named without the gateway control secret');
2830
+ sendJson(res, 401, {
2831
+ error: {
2832
+ type: 'authentication_error',
2833
+ code: 'gateway_auth_required',
2834
+ message: `Paying as a buyer identity other than the default requires ${GATEWAY_CONTROL_HEADER}.`,
2835
+ param: GATEWAY_CONTROL_HEADER,
2836
+ },
2837
+ });
2838
+ return;
2839
+ }
2082
2840
  const buyerIdentity = await this._resolveBuyerIdentity(headers[BUYER_IDENTITY_HEADER]);
2083
2841
  if (buyerIdentity === undefined) {
2084
2842
  res.writeHead(400, { 'content-type': 'application/json' });
@@ -2302,6 +3060,17 @@ export class BuyerProxy {
2302
3060
  }));
2303
3061
  return;
2304
3062
  }
3063
+ if (requestPolicy && !policyAllowsModel(this._effectivePolicy(requestPolicy), requestedService)) {
3064
+ sendJson(res, 403, {
3065
+ error: {
3066
+ type: 'permission_error',
3067
+ code: 'model_not_allowed',
3068
+ message: `Model "${requestedService ?? 'unknown'}" is not allowed by the routing policy.`,
3069
+ param: 'model',
3070
+ },
3071
+ });
3072
+ return;
3073
+ }
2305
3074
  // Discover peers
2306
3075
  const peers = await this._getPeers();
2307
3076
  if (peers.length === 0) {
@@ -2320,72 +3089,18 @@ export class BuyerProxy {
2320
3089
  ({ candidatePeers: modelPeers, routePlanByPeerId: modelPlans } = selectModelPeers(discoveredPeers));
2321
3090
  }
2322
3091
  const router = this._node.router;
2323
- const policyRouter = router;
2324
- const routeCandidates = modelPeers
2325
- .map((peer) => {
2326
- const plan = modelPlans.get(peer.peerId)
2327
- ?? resolvePeerRoutePlan(peer, requestProtocol, requestedService, explicitProvider, 'strict');
2328
- if (!plan?.serviceId)
2329
- return null;
2330
- const offer = findAdvertisedServiceOffer(peer, plan.provider, plan.serviceId);
2331
- if (!offer)
2332
- return null;
2333
- const missingRequired = plan.selection?.requiresTransform
2334
- ? requiredParameters
2335
- : findMissingRequiredParameters(peer, plan.provider, plan.serviceId, requiredParameters);
2336
- if (missingRequired.length > 0) {
2337
- log(`Capability filter: peer ${peer.peerId.slice(0, 12)}... service="${plan.serviceId}" `
2338
- + `missing required parameter(s): ${missingRequired.join(', ')}`);
2339
- return null;
2340
- }
2341
- const requestForPolicy = withRoutedModel(serializedReq, plan.serviceId);
2342
- if (!peerAllowedByPolicy(policyRouter, requestForPolicy, peer))
2343
- return null;
2344
- return {
2345
- peer,
2346
- peerId: peer.peerId,
2347
- serviceId: plan.serviceId,
2348
- request: requestForPolicy,
2349
- reputation: normalizedModelReputationScore(peer) ?? -1,
2350
- hasCachedInputPricing: offer.cachedInputUsdPerMillion !== undefined,
2351
- inputUsdPerMillion: offer.inputUsdPerMillion ?? null,
2352
- outputUsdPerMillion: offer.outputUsdPerMillion ?? null,
2353
- minImageUsdPerImage: offer.minImageUsdPerImage ?? null,
2354
- };
2355
- })
2356
- .filter((candidate) => candidate !== null);
2357
- const now = this._now();
2358
- const preferCachedPricing = routeCandidates.some((candidate) => candidate.hasCachedInputPricing);
2359
- const ranked = routeCandidates.map((candidate) => {
2360
- const health = this._peerHealth.get(candidate.peer.peerId);
2361
- return {
2362
- ...candidate,
2363
- effectiveReputationScore: effectiveModelReputationScore(candidate.reputation >= 0 ? candidate.reputation : null, candidate.hasCachedInputPricing, preferCachedPricing, modelRouteTotalPrice(candidate) === 0),
2364
- peerCooldownUntil: health?.cooldownUntil ?? null,
2365
- peerFailureStreak: health?.failureStreak ?? 0,
2366
- };
3092
+ const { candidates, effectivePolicy } = this._planModelRoutes({
3093
+ modelPeers,
3094
+ modelPlans,
3095
+ request: serializedReq,
3096
+ requestProtocol,
3097
+ requestedService,
3098
+ explicitProvider,
3099
+ requiredParameters,
3100
+ requestPolicy,
3101
+ preferredPeerId: preferredConversationPeerId,
2367
3102
  });
2368
- let candidates;
2369
- const routingPreferences = this._routingPreferences;
2370
- if (routingPreferences) {
2371
- candidates = rankModelRoutes(ranked, routingPreferences, now)
2372
- .filter((candidate) => isModelRouteEligible(candidate, routingPreferences));
2373
- }
2374
- else {
2375
- ranked.sort((a, b) => (b.effectiveReputationScore ?? -1) - (a.effectiveReputationScore ?? -1)
2376
- || a.peer.peerId.localeCompare(b.peer.peerId));
2377
- const ready = ranked.filter((candidate) => !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now));
2378
- candidates = ready.length > 0 ? ready : ranked;
2379
- }
2380
- if (preferredConversationPeerId) {
2381
- const preferredIndex = candidates.findIndex((candidate) => (candidate.peer.peerId.toLowerCase() === preferredConversationPeerId
2382
- && !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now)));
2383
- if (preferredIndex > 0) {
2384
- const [preferred] = candidates.splice(preferredIndex, 1);
2385
- if (preferred)
2386
- candidates.unshift(preferred);
2387
- }
2388
- }
3103
+ const requireVerified = effectivePolicy?.requireVerified === true;
2389
3104
  if (candidates.length === 0) {
2390
3105
  const capabilityRequired = requiredParameters.length > 0;
2391
3106
  // The model exists on the network but only behind an API this
@@ -2429,10 +3144,16 @@ export class BuyerProxy {
2429
3144
  }
2430
3145
  let lastRetry = null;
2431
3146
  let lastVerificationError = null;
3147
+ // Set once a seller refused for lack of deposits: every other paid
3148
+ // seller would too, so only free ones are still worth trying.
3149
+ let unfunded = false;
3150
+ const isFree = (candidate) => modelRouteTotalPrice(candidate) === 0;
2432
3151
  for (const [index, selected] of candidates.entries()) {
3152
+ if (unfunded && !isFree(selected))
3153
+ continue;
2433
3154
  if (this._verifier) {
2434
3155
  const makeReach = (chosenId) => makeVerifierReach(this._node, selected.peer, chosenId, clientAbortController.signal);
2435
- const outcome = await this._verifyPeer(selected.peer, makeReach, clientAbortController.signal);
3156
+ const outcome = await this._verifyPeer(selected.peer, makeReach, clientAbortController.signal, requireVerified);
2436
3157
  if (!outcome.ok) {
2437
3158
  lastVerificationError = `Peer ${selected.peer.peerId.slice(0, 12)}... failed required verification (${outcome.reason ?? 'failed'}).`;
2438
3159
  log(`${lastVerificationError} Trying the next model peer.`);
@@ -2444,9 +3165,11 @@ export class BuyerProxy {
2444
3165
  + `service="${selected.serviceId}" reputation=${selected.reputation} `
2445
3166
  + `effective=${selected.effectiveReputationScore ?? 'unknown'} peer=${index + 1}/${candidates.length} `
2446
3167
  + `attempt=${peerAttempt + 1}/${MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER}`);
2447
- const result = await this._dispatchToPeer(res, selected.request, selected.peer, modelPlans, requestProtocol, selected.serviceId, explicitProvider, router, RETRYABLE_STATUS_CODES, false, clientAbortController.signal);
3168
+ const result = await this._dispatchToPeer(res, selected.request, selected.peer, modelPlans, requestProtocol, selected.serviceId, explicitProvider, router, false, clientAbortController.signal, requestPolicy !== null);
2448
3169
  if (result.done) {
2449
- if (trackedConversationId) {
3170
+ // Only a seller that answered becomes the chat's route: an error
3171
+ // must not pin later turns to it.
3172
+ if (trackedConversationId && res.statusCode < 400) {
2450
3173
  this._conversations.recordRoutedModel(trackedConversationId, `${selected.peer.peerId}@${selected.serviceId}`, conversationPinAtRouting);
2451
3174
  }
2452
3175
  return;
@@ -2460,6 +3183,12 @@ export class BuyerProxy {
2460
3183
  }
2461
3184
  lastRetry = result;
2462
3185
  if (result.responseHeaders[ANTSEED_FAULT_ATTRIBUTION_HEADER]?.toLowerCase() === 'buyer') {
3186
+ const freeLeft = candidates.slice(index + 1).some(isFree);
3187
+ if (result.statusCode === 402 && freeLeft) {
3188
+ unfunded = true;
3189
+ log(`No deposits to pay peer ${selected.peer.peerId.slice(0, 12)}...; trying free sellers only.`);
3190
+ break;
3191
+ }
2463
3192
  res.writeHead(result.statusCode, result.responseHeaders);
2464
3193
  res.end(result.responseBody);
2465
3194
  return;
@@ -2606,6 +3335,31 @@ export class BuyerProxy {
2606
3335
  return;
2607
3336
  }
2608
3337
  const pinnedRequest = pinnedServiceId ? withRoutedModel(serializedReq, pinnedServiceId) : serializedReq;
3338
+ // A hard pin cannot escape the gateway's policy: an excluded seller is
3339
+ // refused rather than silently swapped for another. Only the gateway
3340
+ // policy and the buyer's hard limits judge a pin, never the buyer's
3341
+ // routing preferences (they are automatic-routing defaults, as without
3342
+ // a gateway).
3343
+ const pinnedPolicy = this._effectivePolicy(requestPolicy);
3344
+ if (requestPolicy) {
3345
+ const offer = selectedPlan && pinnedServiceId
3346
+ ? findAdvertisedServiceOffer(selectedPeer, selectedPlan.provider, pinnedServiceId)
3347
+ : null;
3348
+ const facts = this._policyFacts(selectedPeer, offer);
3349
+ const reasons = pinnedPeerExclusionReasons(requestPolicy, facts, requestedService);
3350
+ addBuyerConfigReasons(reasons, policyExclusionReasons(this._buyerHardPolicy(), facts));
3351
+ if (reasons.length > 0) {
3352
+ log(`Pinned peer ${selectedPeer.peerId.slice(0, 12)}... refused by routing policy: ${reasons.join('; ')}`);
3353
+ sendJson(res, 403, {
3354
+ error: {
3355
+ type: 'permission_error',
3356
+ code: 'peer_not_allowed',
3357
+ message: `Seller ${selectedPeer.peerId.slice(0, 12)}... is not allowed by the routing policy (${reasons.join('; ')}).`,
3358
+ },
3359
+ });
3360
+ return;
3361
+ }
3362
+ }
2609
3363
  if (!peerAllowedByPolicy(policyRouter, pinnedRequest, selectedPeer)) {
2610
3364
  log(`Pinned peer ${selectedPeer.peerId.slice(0, 12)}... filtered out by buyer routing policy`);
2611
3365
  res.writeHead(502, { 'content-type': 'text/plain' });
@@ -2615,7 +3369,7 @@ export class BuyerProxy {
2615
3369
  }
2616
3370
  if (this._verifier) {
2617
3371
  const makeReach = (chosenId) => makeVerifierReach(this._node, selectedPeer, chosenId, clientAbortController.signal);
2618
- const outcome = await this._verifyPeer(selectedPeer, makeReach, clientAbortController.signal);
3372
+ const outcome = await this._verifyPeer(selectedPeer, makeReach, clientAbortController.signal, pinnedPolicy?.requireVerified === true);
2619
3373
  const short = selectedPeer.peerId.slice(0, 12);
2620
3374
  if (outcome.verified) {
2621
3375
  log(`Verified ${short}... via ${outcome.sdk}`);
@@ -2631,7 +3385,7 @@ export class BuyerProxy {
2631
3385
  }
2632
3386
  }
2633
3387
  log(`Using pinned peer ${selectedPeer.peerId.slice(0, 12)}...`);
2634
- const result = await this._dispatchToPeer(res, pinnedRequest, selectedPeer, routingPlans, requestProtocol, requestedService, explicitProvider, router, RETRYABLE_STATUS_CODES, true, clientAbortController.signal);
3388
+ const result = await this._dispatchToPeer(res, pinnedRequest, selectedPeer, routingPlans, requestProtocol, requestedService, explicitProvider, router, true, clientAbortController.signal);
2635
3389
  if (result.done && trackedConversationId && pinnedServiceId) {
2636
3390
  this._conversations.recordRoutedModel(trackedConversationId, `${selectedPeer.peerId}@${pinnedServiceId}`, conversationPinAtRouting);
2637
3391
  }
@@ -2642,10 +3396,12 @@ export class BuyerProxy {
2642
3396
  res.end(result.responseBody);
2643
3397
  }
2644
3398
  }
2645
- async _verifyPeer(peer, makeReach, signal) {
2646
- const policy = this._verifier;
2647
- if (!policy)
3399
+ async _verifyPeer(peer, makeReach, signal, requireVerified = false) {
3400
+ const configured = this._verifier;
3401
+ if (!configured)
2648
3402
  return { ok: true, verified: false };
3403
+ // A routing policy's `requireVerified` makes the buyer's verifier mandatory for this request.
3404
+ const policy = requireVerified && !configured.require ? { ...configured, require: true } : configured;
2649
3405
  const fingerprint = verifierSupportFingerprint(peer.capabilities);
2650
3406
  const outcome = await this._teeVerification.verify(peer, policy, () => runVerifier(policy, peer.peerId, peer.capabilities, makeReach, signal));
2651
3407
  const current = this._cachedPeers.find((candidate) => candidate.peerId === peer.peerId);
@@ -2723,7 +3479,8 @@ export class BuyerProxy {
2723
3479
  * was sent to the client (success or non-retryable error), or retry info if the
2724
3480
  * caller should try another peer.
2725
3481
  */
2726
- async _dispatchToPeer(res, serializedReq, selectedPeer, routePlanByPeerId, requestProtocol, requestedService, explicitProvider, router, retryableStatusCodes, pinned, requestSignal) {
3482
+ async _dispatchToPeer(res, serializedReq, selectedPeer, routePlanByPeerId, requestProtocol, requestedService, explicitProvider, router, pinned, requestSignal, gatewayRouted = false) {
3483
+ const gatewayAutoRoute = gatewayRouted && !pinned;
2727
3484
  const selectedRoutePlan = routePlanByPeerId.get(selectedPeer.peerId)
2728
3485
  ?? resolvePeerRoutePlan(selectedPeer, requestProtocol, requestedService, explicitProvider, 'lenient');
2729
3486
  if (!selectedRoutePlan) {
@@ -2749,6 +3506,7 @@ export class BuyerProxy {
2749
3506
  ...(requestedService ? { 'x-antseed-service': requestedService } : {}),
2750
3507
  },
2751
3508
  };
3509
+ const latencyServiceId = selectedRoutePlan.serviceId ?? requestedService;
2752
3510
  if (selectedRoutePlan.serviceId) {
2753
3511
  requestForPeer = withRoutedModel(requestForPeer, selectedRoutePlan.serviceId);
2754
3512
  }
@@ -2824,10 +3582,12 @@ export class BuyerProxy {
2824
3582
  if (!metadata.streaming)
2825
3583
  return;
2826
3584
  streamed = true;
3585
+ if (startResponse.statusCode < 400)
3586
+ this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, Date.now() - startTime);
2827
3587
  const adaptedStartResponse = streamResponseAdapter
2828
3588
  ? streamResponseAdapter.adaptStart(startResponse)
2829
3589
  : startResponse;
2830
- const streamingHeaders = attachStreamingAntseedHeaders(adaptedStartResponse.headers, selectedPeer, requestForPeer.requestId);
3590
+ const streamingHeaders = attachStreamingAntseedHeaders(adaptedStartResponse.headers, selectedPeer, requestForPeer);
2831
3591
  // Ensure content-type is set for SSE — some upstream APIs (e.g. Codex)
2832
3592
  // omit it, which can cause the client's fetch body reader to not
2833
3593
  // detect end-of-stream properly.
@@ -2866,6 +3626,8 @@ export class BuyerProxy {
2866
3626
  responseForClient = inject402PeerId(responseForClient, selectedPeer.peerId);
2867
3627
  }
2868
3628
  const latencyMs = Date.now() - startTime;
3629
+ if (!streamed && responseForClient.statusCode < 400)
3630
+ this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, latencyMs);
2869
3631
  log(`Response: ${responseForClient.statusCode} (${latencyMs}ms, ${responseForClient.body.length} bytes)`);
2870
3632
  if (responseForClient.statusCode >= 400) {
2871
3633
  const prefix = adaptResponse && !streamed ? 'Upstream adapted error detail' : 'Upstream error detail';
@@ -2882,7 +3644,7 @@ export class BuyerProxy {
2882
3644
  if (router && responseFault !== 'buyer') {
2883
3645
  router.onResult(selectedPeer, {
2884
3646
  success: !modelNotFound
2885
- && isRouterSuccess(responseForClient.statusCode, requestForPeer.path, retryableStatusCodes),
3647
+ && isRouterSuccess(responseForClient.statusCode, requestForPeer.path, retryableStatusCodesFor(gatewayAutoRoute)),
2886
3648
  latencyMs,
2887
3649
  tokens: telemetry.usage.totalTokens,
2888
3650
  });
@@ -2902,7 +3664,7 @@ export class BuyerProxy {
2902
3664
  }
2903
3665
  // Non-streamed response — check if retryable
2904
3666
  const responseHeaders = attachAntseedTelemetryHeaders(responseForClient.headers, selectedPeer, telemetry, requestForPeer.requestId, latencyMs);
2905
- if (retryableStatusCodes.has(responseForClient.statusCode)) {
3667
+ if (shouldTryNextPeer(responseForClient.statusCode, modelNotFound, pinned, gatewayAutoRoute)) {
2906
3668
  return {
2907
3669
  done: false,
2908
3670
  statusCode: responseForClient.statusCode,
@@ -2943,6 +3705,8 @@ export class BuyerProxy {
2943
3705
  }
2944
3706
  const latencyMs = Date.now() - startTime;
2945
3707
  this._markModelActivity();
3708
+ if (response.statusCode < 400)
3709
+ this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, latencyMs);
2946
3710
  log(`Response: ${response.statusCode} (${latencyMs}ms, ${response.body.length} bytes)`);
2947
3711
  if (response.statusCode >= 400) {
2948
3712
  const prefix = adaptResponse ? 'Upstream adapted error detail' : 'Upstream error detail';
@@ -2960,7 +3724,7 @@ export class BuyerProxy {
2960
3724
  if (router && responseFault !== 'buyer') {
2961
3725
  router.onResult(selectedPeer, {
2962
3726
  success: !modelNotFound
2963
- && isRouterSuccess(response.statusCode, requestForPeer.path, retryableStatusCodes),
3727
+ && isRouterSuccess(response.statusCode, requestForPeer.path, retryableStatusCodesFor(gatewayAutoRoute)),
2964
3728
  latencyMs,
2965
3729
  tokens: telemetry.usage.totalTokens,
2966
3730
  });
@@ -2972,7 +3736,7 @@ export class BuyerProxy {
2972
3736
  this._recordPeerResponseHealth(selectedPeer.peerId, response.statusCode, requestForPeer.path);
2973
3737
  }
2974
3738
  // Check if retryable
2975
- if (retryableStatusCodes.has(response.statusCode)) {
3739
+ if (shouldTryNextPeer(response.statusCode, modelNotFound, pinned, gatewayAutoRoute)) {
2976
3740
  return { done: false, statusCode: response.statusCode, responseBody: Buffer.from(response.body), responseHeaders, errorMessage: null };
2977
3741
  }
2978
3742
  // Forward response headers and body to the HTTP client