@antseed/cli 0.1.179 → 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 (666) hide show
  1. package/README.md +2 -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/buyer/identity.d.ts +3 -1
  7. package/dist/cli/commands/buyer/identity.d.ts.map +1 -1
  8. package/dist/cli/commands/buyer/identity.js +55 -8
  9. package/dist/cli/commands/buyer/identity.js.map +1 -1
  10. package/dist/cli/commands/buyer/identity.test.d.ts +2 -0
  11. package/dist/cli/commands/buyer/identity.test.d.ts.map +1 -0
  12. package/dist/cli/commands/buyer/identity.test.js +168 -0
  13. package/dist/cli/commands/buyer/identity.test.js.map +1 -0
  14. package/dist/cli/commands/buyer/set-authorized-wallet.d.ts +10 -0
  15. package/dist/cli/commands/buyer/set-authorized-wallet.d.ts.map +1 -1
  16. package/dist/cli/commands/buyer/set-authorized-wallet.js +26 -4
  17. package/dist/cli/commands/buyer/set-authorized-wallet.js.map +1 -1
  18. package/dist/cli/commands/buyer/set-authorized-wallet.test.js +37 -2
  19. package/dist/cli/commands/buyer/set-authorized-wallet.test.js.map +1 -1
  20. package/dist/cli/commands/buyer/start.d.ts +15 -0
  21. package/dist/cli/commands/buyer/start.d.ts.map +1 -1
  22. package/dist/cli/commands/buyer/start.js +158 -48
  23. package/dist/cli/commands/buyer/start.js.map +1 -1
  24. package/dist/cli/commands/buyer/start.test.js +16 -1
  25. package/dist/cli/commands/buyer/start.test.js.map +1 -1
  26. package/dist/cli/commands/chat.d.ts +3 -0
  27. package/dist/cli/commands/chat.d.ts.map +1 -0
  28. package/dist/cli/commands/chat.js +189 -0
  29. package/dist/cli/commands/chat.js.map +1 -0
  30. package/dist/cli/commands/gateway/activity.d.ts +15 -0
  31. package/dist/cli/commands/gateway/activity.d.ts.map +1 -0
  32. package/dist/cli/commands/gateway/activity.js +233 -0
  33. package/dist/cli/commands/gateway/activity.js.map +1 -0
  34. package/dist/cli/commands/gateway/admin-token.d.ts +3 -0
  35. package/dist/cli/commands/gateway/admin-token.d.ts.map +1 -0
  36. package/dist/cli/commands/gateway/admin-token.js +90 -0
  37. package/dist/cli/commands/gateway/admin-token.js.map +1 -0
  38. package/dist/cli/commands/gateway/console-link.d.ts +20 -0
  39. package/dist/cli/commands/gateway/console-link.d.ts.map +1 -0
  40. package/dist/cli/commands/gateway/console-link.js +92 -0
  41. package/dist/cli/commands/gateway/console-link.js.map +1 -0
  42. package/dist/cli/commands/gateway/gateway-commands.test.d.ts +2 -0
  43. package/dist/cli/commands/gateway/gateway-commands.test.d.ts.map +1 -0
  44. package/dist/cli/commands/gateway/gateway-commands.test.js +230 -0
  45. package/dist/cli/commands/gateway/gateway-commands.test.js.map +1 -0
  46. package/dist/cli/commands/gateway/gateway-console-commands.test.d.ts +2 -0
  47. package/dist/cli/commands/gateway/gateway-console-commands.test.d.ts.map +1 -0
  48. package/dist/cli/commands/gateway/gateway-console-commands.test.js +373 -0
  49. package/dist/cli/commands/gateway/gateway-console-commands.test.js.map +1 -0
  50. package/dist/cli/commands/gateway/index.d.ts +8 -0
  51. package/dist/cli/commands/gateway/index.d.ts.map +1 -1
  52. package/dist/cli/commands/gateway/index.js +93 -14
  53. package/dist/cli/commands/gateway/index.js.map +1 -1
  54. package/dist/cli/commands/gateway/key.d.ts +4 -0
  55. package/dist/cli/commands/gateway/key.d.ts.map +1 -1
  56. package/dist/cli/commands/gateway/key.js +297 -182
  57. package/dist/cli/commands/gateway/key.js.map +1 -1
  58. package/dist/cli/commands/gateway/member.d.ts +3 -0
  59. package/dist/cli/commands/gateway/member.d.ts.map +1 -0
  60. package/dist/cli/commands/gateway/member.js +302 -0
  61. package/dist/cli/commands/gateway/member.js.map +1 -0
  62. package/dist/cli/commands/gateway/migrate.d.ts +18 -0
  63. package/dist/cli/commands/gateway/migrate.d.ts.map +1 -0
  64. package/dist/cli/commands/gateway/migrate.js +158 -0
  65. package/dist/cli/commands/gateway/migrate.js.map +1 -0
  66. package/dist/cli/commands/gateway/operator.d.ts +11 -0
  67. package/dist/cli/commands/gateway/operator.d.ts.map +1 -0
  68. package/dist/cli/commands/gateway/operator.js +199 -0
  69. package/dist/cli/commands/gateway/operator.js.map +1 -0
  70. package/dist/cli/commands/gateway/operator.test.d.ts +2 -0
  71. package/dist/cli/commands/gateway/operator.test.d.ts.map +1 -0
  72. package/dist/cli/commands/gateway/operator.test.js +151 -0
  73. package/dist/cli/commands/gateway/operator.test.js.map +1 -0
  74. package/dist/cli/commands/gateway/peer-list.d.ts +3 -0
  75. package/dist/cli/commands/gateway/peer-list.d.ts.map +1 -0
  76. package/dist/cli/commands/gateway/peer-list.js +107 -0
  77. package/dist/cli/commands/gateway/peer-list.js.map +1 -0
  78. package/dist/cli/commands/gateway/policy-options.d.ts +23 -0
  79. package/dist/cli/commands/gateway/policy-options.d.ts.map +1 -0
  80. package/dist/cli/commands/gateway/policy-options.js +156 -0
  81. package/dist/cli/commands/gateway/policy-options.js.map +1 -0
  82. package/dist/cli/commands/gateway/preset.d.ts +3 -0
  83. package/dist/cli/commands/gateway/preset.d.ts.map +1 -0
  84. package/dist/cli/commands/gateway/preset.js +181 -0
  85. package/dist/cli/commands/gateway/preset.js.map +1 -0
  86. package/dist/cli/commands/gateway/routing.d.ts +3 -0
  87. package/dist/cli/commands/gateway/routing.d.ts.map +1 -0
  88. package/dist/cli/commands/gateway/routing.js +110 -0
  89. package/dist/cli/commands/gateway/routing.js.map +1 -0
  90. package/dist/cli/commands/gateway/settings.d.ts +4 -0
  91. package/dist/cli/commands/gateway/settings.d.ts.map +1 -0
  92. package/dist/cli/commands/gateway/settings.js +314 -0
  93. package/dist/cli/commands/gateway/settings.js.map +1 -0
  94. package/dist/cli/commands/gateway/shared.d.ts +55 -6
  95. package/dist/cli/commands/gateway/shared.d.ts.map +1 -1
  96. package/dist/cli/commands/gateway/shared.js +206 -26
  97. package/dist/cli/commands/gateway/shared.js.map +1 -1
  98. package/dist/cli/commands/gateway/workspace.d.ts +3 -0
  99. package/dist/cli/commands/gateway/workspace.d.ts.map +1 -0
  100. package/dist/cli/commands/gateway/workspace.js +250 -0
  101. package/dist/cli/commands/gateway/workspace.js.map +1 -0
  102. package/dist/cli/commands/tunnel/start.d.ts.map +1 -1
  103. package/dist/cli/commands/tunnel/start.js +23 -5
  104. package/dist/cli/commands/tunnel/start.js.map +1 -1
  105. package/dist/cli/index.js +12 -44
  106. package/dist/cli/index.js.map +1 -1
  107. package/dist/cli/program.d.ts +4 -0
  108. package/dist/cli/program.d.ts.map +1 -0
  109. package/dist/cli/program.js +49 -0
  110. package/dist/cli/program.js.map +1 -0
  111. package/dist/cli/program.test.d.ts +2 -0
  112. package/dist/cli/program.test.d.ts.map +1 -0
  113. package/dist/cli/program.test.js +38 -0
  114. package/dist/cli/program.test.js.map +1 -0
  115. package/dist/cli/prompt-hidden.d.ts +3 -0
  116. package/dist/cli/prompt-hidden.d.ts.map +1 -0
  117. package/dist/cli/prompt-hidden.js +38 -0
  118. package/dist/cli/prompt-hidden.js.map +1 -0
  119. package/dist/cli/wallet-passphrase.d.ts +48 -0
  120. package/dist/cli/wallet-passphrase.d.ts.map +1 -0
  121. package/dist/cli/wallet-passphrase.js +119 -0
  122. package/dist/cli/wallet-passphrase.js.map +1 -0
  123. package/dist/cli/wallet-passphrase.test.d.ts +2 -0
  124. package/dist/cli/wallet-passphrase.test.d.ts.map +1 -0
  125. package/dist/cli/wallet-passphrase.test.js +79 -0
  126. package/dist/cli/wallet-passphrase.test.js.map +1 -0
  127. package/dist/config/effective.d.ts +12 -0
  128. package/dist/config/effective.d.ts.map +1 -1
  129. package/dist/config/effective.js +39 -6
  130. package/dist/config/effective.js.map +1 -1
  131. package/dist/config/effective.test.js +9 -1
  132. package/dist/config/effective.test.js.map +1 -1
  133. package/dist/config/loader.d.ts +6 -0
  134. package/dist/config/loader.d.ts.map +1 -1
  135. package/dist/config/loader.js +12 -0
  136. package/dist/config/loader.js.map +1 -1
  137. package/dist/config/loader.test.js +7 -1
  138. package/dist/config/loader.test.js.map +1 -1
  139. package/dist/config/types.d.ts +14 -0
  140. package/dist/config/types.d.ts.map +1 -1
  141. package/dist/config/validation.d.ts.map +1 -1
  142. package/dist/config/validation.js +19 -0
  143. package/dist/config/validation.js.map +1 -1
  144. package/dist/gateway/accounting.d.ts +33 -3
  145. package/dist/gateway/accounting.d.ts.map +1 -1
  146. package/dist/gateway/accounting.js +86 -7
  147. package/dist/gateway/accounting.js.map +1 -1
  148. package/dist/gateway/admission-benchmark.test.d.ts +2 -0
  149. package/dist/gateway/admission-benchmark.test.d.ts.map +1 -0
  150. package/dist/gateway/admission-benchmark.test.js +75 -0
  151. package/dist/gateway/admission-benchmark.test.js.map +1 -0
  152. package/dist/gateway/auth/auth.test.d.ts +4 -0
  153. package/dist/gateway/auth/auth.test.d.ts.map +1 -0
  154. package/dist/gateway/auth/auth.test.js +337 -0
  155. package/dist/gateway/auth/auth.test.js.map +1 -0
  156. package/dist/gateway/auth/credentials.test.d.ts +2 -0
  157. package/dist/gateway/auth/credentials.test.d.ts.map +1 -0
  158. package/dist/gateway/auth/credentials.test.js +132 -0
  159. package/dist/gateway/auth/credentials.test.js.map +1 -0
  160. package/dist/gateway/auth/db.d.ts +156 -0
  161. package/dist/gateway/auth/db.d.ts.map +1 -0
  162. package/dist/gateway/auth/db.js +390 -0
  163. package/dist/gateway/auth/db.js.map +1 -0
  164. package/dist/gateway/auth/hardening.test.d.ts +2 -0
  165. package/dist/gateway/auth/hardening.test.d.ts.map +1 -0
  166. package/dist/gateway/auth/hardening.test.js +155 -0
  167. package/dist/gateway/auth/hardening.test.js.map +1 -0
  168. package/dist/gateway/auth/http.d.ts +53 -0
  169. package/dist/gateway/auth/http.d.ts.map +1 -0
  170. package/dist/gateway/auth/http.js +165 -0
  171. package/dist/gateway/auth/http.js.map +1 -0
  172. package/dist/gateway/auth/index.d.ts +26 -0
  173. package/dist/gateway/auth/index.d.ts.map +1 -0
  174. package/dist/gateway/auth/index.js +1014 -0
  175. package/dist/gateway/auth/index.js.map +1 -0
  176. package/dist/gateway/auth/oidc.d.ts +57 -0
  177. package/dist/gateway/auth/oidc.d.ts.map +1 -0
  178. package/dist/gateway/auth/oidc.js +159 -0
  179. package/dist/gateway/auth/oidc.js.map +1 -0
  180. package/dist/gateway/auth/oidc.test.d.ts +2 -0
  181. package/dist/gateway/auth/oidc.test.d.ts.map +1 -0
  182. package/dist/gateway/auth/oidc.test.js +223 -0
  183. package/dist/gateway/auth/oidc.test.js.map +1 -0
  184. package/dist/gateway/auth/reauth.test.d.ts +2 -0
  185. package/dist/gateway/auth/reauth.test.d.ts.map +1 -0
  186. package/dist/gateway/auth/reauth.test.js +108 -0
  187. package/dist/gateway/auth/reauth.test.js.map +1 -0
  188. package/dist/gateway/auth/store-integration.test.d.ts +2 -0
  189. package/dist/gateway/auth/store-integration.test.d.ts.map +1 -0
  190. package/dist/gateway/auth/store-integration.test.js +100 -0
  191. package/dist/gateway/auth/store-integration.test.js.map +1 -0
  192. package/dist/gateway/auth/test-helpers.d.ts +167 -0
  193. package/dist/gateway/auth/test-helpers.d.ts.map +1 -0
  194. package/dist/gateway/auth/test-helpers.js +368 -0
  195. package/dist/gateway/auth/test-helpers.js.map +1 -0
  196. package/dist/gateway/auth/types.d.ts +145 -0
  197. package/dist/gateway/auth/types.d.ts.map +1 -0
  198. package/dist/gateway/auth/types.js +2 -0
  199. package/dist/gateway/auth/types.js.map +1 -0
  200. package/dist/gateway/auth/wallet.d.ts +15 -0
  201. package/dist/gateway/auth/wallet.d.ts.map +1 -0
  202. package/dist/gateway/auth/wallet.js +43 -0
  203. package/dist/gateway/auth/wallet.js.map +1 -0
  204. package/dist/gateway/bundle.d.ts +127 -0
  205. package/dist/gateway/bundle.d.ts.map +1 -0
  206. package/dist/gateway/bundle.js +604 -0
  207. package/dist/gateway/bundle.js.map +1 -0
  208. package/dist/gateway/bundle.test.d.ts +2 -0
  209. package/dist/gateway/bundle.test.d.ts.map +1 -0
  210. package/dist/gateway/bundle.test.js +277 -0
  211. package/dist/gateway/bundle.test.js.map +1 -0
  212. package/dist/gateway/buyer-control.d.ts +33 -0
  213. package/dist/gateway/buyer-control.d.ts.map +1 -0
  214. package/dist/gateway/buyer-control.js +93 -0
  215. package/dist/gateway/buyer-control.js.map +1 -0
  216. package/dist/gateway/buyer-policy-probe.d.ts +41 -0
  217. package/dist/gateway/buyer-policy-probe.d.ts.map +1 -0
  218. package/dist/gateway/buyer-policy-probe.js +99 -0
  219. package/dist/gateway/buyer-policy-probe.js.map +1 -0
  220. package/dist/gateway/buyer-policy-probe.test.d.ts +2 -0
  221. package/dist/gateway/buyer-policy-probe.test.d.ts.map +1 -0
  222. package/dist/gateway/buyer-policy-probe.test.js +128 -0
  223. package/dist/gateway/buyer-policy-probe.test.js.map +1 -0
  224. package/dist/gateway/buyer-process.d.ts +30 -0
  225. package/dist/gateway/buyer-process.d.ts.map +1 -0
  226. package/dist/gateway/buyer-process.js +101 -0
  227. package/dist/gateway/buyer-process.js.map +1 -0
  228. package/dist/gateway/buyer-process.test.d.ts +2 -0
  229. package/dist/gateway/buyer-process.test.d.ts.map +1 -0
  230. package/dist/gateway/buyer-process.test.js +112 -0
  231. package/dist/gateway/buyer-process.test.js.map +1 -0
  232. package/dist/gateway/chat/routes.d.ts +45 -0
  233. package/dist/gateway/chat/routes.d.ts.map +1 -0
  234. package/dist/gateway/chat/routes.js +439 -0
  235. package/dist/gateway/chat/routes.js.map +1 -0
  236. package/dist/gateway/chat/routes.test.d.ts +2 -0
  237. package/dist/gateway/chat/routes.test.d.ts.map +1 -0
  238. package/dist/gateway/chat/routes.test.js +304 -0
  239. package/dist/gateway/chat/routes.test.js.map +1 -0
  240. package/dist/gateway/chat/tools.d.ts +35 -0
  241. package/dist/gateway/chat/tools.d.ts.map +1 -0
  242. package/dist/gateway/chat/tools.js +347 -0
  243. package/dist/gateway/chat/tools.js.map +1 -0
  244. package/dist/gateway/chat/tools.test.d.ts +2 -0
  245. package/dist/gateway/chat/tools.test.d.ts.map +1 -0
  246. package/dist/gateway/chat/tools.test.js +136 -0
  247. package/dist/gateway/chat/tools.test.js.map +1 -0
  248. package/dist/gateway/console-api/access.d.ts +52 -0
  249. package/dist/gateway/console-api/access.d.ts.map +1 -0
  250. package/dist/gateway/console-api/access.js +171 -0
  251. package/dist/gateway/console-api/access.js.map +1 -0
  252. package/dist/gateway/console-api/deps.d.ts +52 -0
  253. package/dist/gateway/console-api/deps.d.ts.map +1 -0
  254. package/dist/gateway/console-api/deps.js +2 -0
  255. package/dist/gateway/console-api/deps.js.map +1 -0
  256. package/dist/gateway/console-api/handlers/admin-tokens.d.ts +8 -0
  257. package/dist/gateway/console-api/handlers/admin-tokens.d.ts.map +1 -0
  258. package/dist/gateway/console-api/handlers/admin-tokens.js +48 -0
  259. package/dist/gateway/console-api/handlers/admin-tokens.js.map +1 -0
  260. package/dist/gateway/console-api/handlers/audit.d.ts +8 -0
  261. package/dist/gateway/console-api/handlers/audit.d.ts.map +1 -0
  262. package/dist/gateway/console-api/handlers/audit.js +19 -0
  263. package/dist/gateway/console-api/handlers/audit.js.map +1 -0
  264. package/dist/gateway/console-api/handlers/audit.test.d.ts +2 -0
  265. package/dist/gateway/console-api/handlers/audit.test.d.ts.map +1 -0
  266. package/dist/gateway/console-api/handlers/audit.test.js +241 -0
  267. package/dist/gateway/console-api/handlers/audit.test.js.map +1 -0
  268. package/dist/gateway/console-api/handlers/data-settings.test.d.ts +2 -0
  269. package/dist/gateway/console-api/handlers/data-settings.test.d.ts.map +1 -0
  270. package/dist/gateway/console-api/handlers/data-settings.test.js +264 -0
  271. package/dist/gateway/console-api/handlers/data-settings.test.js.map +1 -0
  272. package/dist/gateway/console-api/handlers/index.d.ts +4 -0
  273. package/dist/gateway/console-api/handlers/index.d.ts.map +1 -0
  274. package/dist/gateway/console-api/handlers/index.js +24 -0
  275. package/dist/gateway/console-api/handlers/index.js.map +1 -0
  276. package/dist/gateway/console-api/handlers/keys.d.ts +13 -0
  277. package/dist/gateway/console-api/handlers/keys.d.ts.map +1 -0
  278. package/dist/gateway/console-api/handlers/keys.js +128 -0
  279. package/dist/gateway/console-api/handlers/keys.js.map +1 -0
  280. package/dist/gateway/console-api/handlers/keys.test.d.ts +2 -0
  281. package/dist/gateway/console-api/handlers/keys.test.d.ts.map +1 -0
  282. package/dist/gateway/console-api/handlers/keys.test.js +166 -0
  283. package/dist/gateway/console-api/handlers/keys.test.js.map +1 -0
  284. package/dist/gateway/console-api/handlers/network-buyer.d.ts +22 -0
  285. package/dist/gateway/console-api/handlers/network-buyer.d.ts.map +1 -0
  286. package/dist/gateway/console-api/handlers/network-buyer.js +39 -0
  287. package/dist/gateway/console-api/handlers/network-buyer.js.map +1 -0
  288. package/dist/gateway/console-api/handlers/network-mapping.d.ts +33 -0
  289. package/dist/gateway/console-api/handlers/network-mapping.d.ts.map +1 -0
  290. package/dist/gateway/console-api/handlers/network-mapping.js +219 -0
  291. package/dist/gateway/console-api/handlers/network-mapping.js.map +1 -0
  292. package/dist/gateway/console-api/handlers/network-test-helpers.d.ts +35 -0
  293. package/dist/gateway/console-api/handlers/network-test-helpers.d.ts.map +1 -0
  294. package/dist/gateway/console-api/handlers/network-test-helpers.js +121 -0
  295. package/dist/gateway/console-api/handlers/network-test-helpers.js.map +1 -0
  296. package/dist/gateway/console-api/handlers/network.d.ts +15 -0
  297. package/dist/gateway/console-api/handlers/network.d.ts.map +1 -0
  298. package/dist/gateway/console-api/handlers/network.js +57 -0
  299. package/dist/gateway/console-api/handlers/network.js.map +1 -0
  300. package/dist/gateway/console-api/handlers/network.test.d.ts +2 -0
  301. package/dist/gateway/console-api/handlers/network.test.d.ts.map +1 -0
  302. package/dist/gateway/console-api/handlers/network.test.js +383 -0
  303. package/dist/gateway/console-api/handlers/network.test.js.map +1 -0
  304. package/dist/gateway/console-api/handlers/org.d.ts +10 -0
  305. package/dist/gateway/console-api/handlers/org.d.ts.map +1 -0
  306. package/dist/gateway/console-api/handlers/org.js +150 -0
  307. package/dist/gateway/console-api/handlers/org.js.map +1 -0
  308. package/dist/gateway/console-api/handlers/peer-lists.d.ts +5 -0
  309. package/dist/gateway/console-api/handlers/peer-lists.d.ts.map +1 -0
  310. package/dist/gateway/console-api/handlers/peer-lists.js +43 -0
  311. package/dist/gateway/console-api/handlers/peer-lists.js.map +1 -0
  312. package/dist/gateway/console-api/handlers/presets.d.ts +9 -0
  313. package/dist/gateway/console-api/handlers/presets.d.ts.map +1 -0
  314. package/dist/gateway/console-api/handlers/presets.js +79 -0
  315. package/dist/gateway/console-api/handlers/presets.js.map +1 -0
  316. package/dist/gateway/console-api/handlers/routing.d.ts +5 -0
  317. package/dist/gateway/console-api/handlers/routing.d.ts.map +1 -0
  318. package/dist/gateway/console-api/handlers/routing.js +20 -0
  319. package/dist/gateway/console-api/handlers/routing.js.map +1 -0
  320. package/dist/gateway/console-api/handlers/settings.d.ts +10 -0
  321. package/dist/gateway/console-api/handlers/settings.d.ts.map +1 -0
  322. package/dist/gateway/console-api/handlers/settings.js +38 -0
  323. package/dist/gateway/console-api/handlers/settings.js.map +1 -0
  324. package/dist/gateway/console-api/handlers/status.d.ts +9 -0
  325. package/dist/gateway/console-api/handlers/status.d.ts.map +1 -0
  326. package/dist/gateway/console-api/handlers/status.js +47 -0
  327. package/dist/gateway/console-api/handlers/status.js.map +1 -0
  328. package/dist/gateway/console-api/handlers/status.test.d.ts +2 -0
  329. package/dist/gateway/console-api/handlers/status.test.d.ts.map +1 -0
  330. package/dist/gateway/console-api/handlers/status.test.js +82 -0
  331. package/dist/gateway/console-api/handlers/status.test.js.map +1 -0
  332. package/dist/gateway/console-api/handlers/usage.d.ts +10 -0
  333. package/dist/gateway/console-api/handlers/usage.d.ts.map +1 -0
  334. package/dist/gateway/console-api/handlers/usage.js +68 -0
  335. package/dist/gateway/console-api/handlers/usage.js.map +1 -0
  336. package/dist/gateway/console-api/handlers/wallet-card-link.d.ts +29 -0
  337. package/dist/gateway/console-api/handlers/wallet-card-link.d.ts.map +1 -0
  338. package/dist/gateway/console-api/handlers/wallet-card-link.js +46 -0
  339. package/dist/gateway/console-api/handlers/wallet-card-link.js.map +1 -0
  340. package/dist/gateway/console-api/handlers/wallet-chain.d.ts +26 -0
  341. package/dist/gateway/console-api/handlers/wallet-chain.d.ts.map +1 -0
  342. package/dist/gateway/console-api/handlers/wallet-chain.js +80 -0
  343. package/dist/gateway/console-api/handlers/wallet-chain.js.map +1 -0
  344. package/dist/gateway/console-api/handlers/wallet-mapping.d.ts +45 -0
  345. package/dist/gateway/console-api/handlers/wallet-mapping.d.ts.map +1 -0
  346. package/dist/gateway/console-api/handlers/wallet-mapping.js +202 -0
  347. package/dist/gateway/console-api/handlers/wallet-mapping.js.map +1 -0
  348. package/dist/gateway/console-api/handlers/wallet-operator.d.ts +41 -0
  349. package/dist/gateway/console-api/handlers/wallet-operator.d.ts.map +1 -0
  350. package/dist/gateway/console-api/handlers/wallet-operator.js +198 -0
  351. package/dist/gateway/console-api/handlers/wallet-operator.js.map +1 -0
  352. package/dist/gateway/console-api/handlers/wallet-operator.test.d.ts +2 -0
  353. package/dist/gateway/console-api/handlers/wallet-operator.test.d.ts.map +1 -0
  354. package/dist/gateway/console-api/handlers/wallet-operator.test.js +195 -0
  355. package/dist/gateway/console-api/handlers/wallet-operator.test.js.map +1 -0
  356. package/dist/gateway/console-api/handlers/wallet.d.ts +61 -0
  357. package/dist/gateway/console-api/handlers/wallet.d.ts.map +1 -0
  358. package/dist/gateway/console-api/handlers/wallet.js +221 -0
  359. package/dist/gateway/console-api/handlers/wallet.js.map +1 -0
  360. package/dist/gateway/console-api/handlers/wallet.test.d.ts +2 -0
  361. package/dist/gateway/console-api/handlers/wallet.test.d.ts.map +1 -0
  362. package/dist/gateway/console-api/handlers/wallet.test.js +581 -0
  363. package/dist/gateway/console-api/handlers/wallet.test.js.map +1 -0
  364. package/dist/gateway/console-api/handlers/workspaces.d.ts +12 -0
  365. package/dist/gateway/console-api/handlers/workspaces.d.ts.map +1 -0
  366. package/dist/gateway/console-api/handlers/workspaces.js +96 -0
  367. package/dist/gateway/console-api/handlers/workspaces.js.map +1 -0
  368. package/dist/gateway/console-api/operator-types.d.ts +68 -0
  369. package/dist/gateway/console-api/operator-types.d.ts.map +1 -0
  370. package/dist/gateway/console-api/operator-types.js +21 -0
  371. package/dist/gateway/console-api/operator-types.js.map +1 -0
  372. package/dist/gateway/console-api/router.d.ts +97 -0
  373. package/dist/gateway/console-api/router.d.ts.map +1 -0
  374. package/dist/gateway/console-api/router.js +69 -0
  375. package/dist/gateway/console-api/router.js.map +1 -0
  376. package/dist/gateway/console-api/routes.test.d.ts +2 -0
  377. package/dist/gateway/console-api/routes.test.d.ts.map +1 -0
  378. package/dist/gateway/console-api/routes.test.js +331 -0
  379. package/dist/gateway/console-api/routes.test.js.map +1 -0
  380. package/dist/gateway/console-api/serialize.d.ts +33 -0
  381. package/dist/gateway/console-api/serialize.d.ts.map +1 -0
  382. package/dist/gateway/console-api/serialize.js +179 -0
  383. package/dist/gateway/console-api/serialize.js.map +1 -0
  384. package/dist/gateway/console-api/server.d.ts +38 -0
  385. package/dist/gateway/console-api/server.d.ts.map +1 -0
  386. package/dist/gateway/console-api/server.js +308 -0
  387. package/dist/gateway/console-api/server.js.map +1 -0
  388. package/dist/gateway/console-api/server.test.d.ts +2 -0
  389. package/dist/gateway/console-api/server.test.d.ts.map +1 -0
  390. package/dist/gateway/console-api/server.test.js +149 -0
  391. package/dist/gateway/console-api/server.test.js.map +1 -0
  392. package/dist/gateway/console-api/test-support.d.ts +59 -0
  393. package/dist/gateway/console-api/test-support.d.ts.map +1 -0
  394. package/dist/gateway/console-api/test-support.js +120 -0
  395. package/dist/gateway/console-api/test-support.js.map +1 -0
  396. package/dist/gateway/console-api/types.d.ts +779 -0
  397. package/dist/gateway/console-api/types.d.ts.map +1 -0
  398. package/dist/gateway/console-api/types.js +6 -0
  399. package/dist/gateway/console-api/types.js.map +1 -0
  400. package/dist/gateway/console-location.d.ts +21 -0
  401. package/dist/gateway/console-location.d.ts.map +1 -0
  402. package/dist/gateway/console-location.js +41 -0
  403. package/dist/gateway/console-location.js.map +1 -0
  404. package/dist/gateway/console-recovery.d.ts +34 -0
  405. package/dist/gateway/console-recovery.d.ts.map +1 -0
  406. package/dist/gateway/console-recovery.js +94 -0
  407. package/dist/gateway/console-recovery.js.map +1 -0
  408. package/dist/gateway/console-recovery.test.d.ts +2 -0
  409. package/dist/gateway/console-recovery.test.d.ts.map +1 -0
  410. package/dist/gateway/console-recovery.test.js +106 -0
  411. package/dist/gateway/console-recovery.test.js.map +1 -0
  412. package/dist/gateway/console.d.ts +18 -0
  413. package/dist/gateway/console.d.ts.map +1 -0
  414. package/dist/gateway/console.js +101 -0
  415. package/dist/gateway/console.js.map +1 -0
  416. package/dist/gateway/console.test.d.ts +2 -0
  417. package/dist/gateway/console.test.d.ts.map +1 -0
  418. package/dist/gateway/console.test.js +165 -0
  419. package/dist/gateway/console.test.js.map +1 -0
  420. package/dist/gateway/errors.d.ts +3 -0
  421. package/dist/gateway/errors.d.ts.map +1 -0
  422. package/dist/gateway/errors.js +5 -0
  423. package/dist/gateway/errors.js.map +1 -0
  424. package/dist/gateway/exposure.d.ts +33 -0
  425. package/dist/gateway/exposure.d.ts.map +1 -0
  426. package/dist/gateway/exposure.js +146 -0
  427. package/dist/gateway/exposure.js.map +1 -0
  428. package/dist/gateway/exposure.test.d.ts +2 -0
  429. package/dist/gateway/exposure.test.d.ts.map +1 -0
  430. package/dist/gateway/exposure.test.js +93 -0
  431. package/dist/gateway/exposure.test.js.map +1 -0
  432. package/dist/gateway/gateway-policy.test.d.ts +2 -0
  433. package/dist/gateway/gateway-policy.test.d.ts.map +1 -0
  434. package/dist/gateway/gateway-policy.test.js +404 -0
  435. package/dist/gateway/gateway-policy.test.js.map +1 -0
  436. package/dist/gateway/gateway.test.js +47 -25
  437. package/dist/gateway/gateway.test.js.map +1 -1
  438. package/dist/gateway/keys.d.ts +2 -0
  439. package/dist/gateway/keys.d.ts.map +1 -1
  440. package/dist/gateway/keys.js +2 -0
  441. package/dist/gateway/keys.js.map +1 -1
  442. package/dist/gateway/limits.d.ts +32 -8
  443. package/dist/gateway/limits.d.ts.map +1 -1
  444. package/dist/gateway/limits.js +27 -11
  445. package/dist/gateway/limits.js.map +1 -1
  446. package/dist/gateway/observability.d.ts +51 -0
  447. package/dist/gateway/observability.d.ts.map +1 -0
  448. package/dist/gateway/observability.js +136 -0
  449. package/dist/gateway/observability.js.map +1 -0
  450. package/dist/gateway/policy-resolver.d.ts +73 -0
  451. package/dist/gateway/policy-resolver.d.ts.map +1 -0
  452. package/dist/gateway/policy-resolver.js +142 -0
  453. package/dist/gateway/policy-resolver.js.map +1 -0
  454. package/dist/gateway/policy-resolver.test.d.ts +2 -0
  455. package/dist/gateway/policy-resolver.test.d.ts.map +1 -0
  456. package/dist/gateway/policy-resolver.test.js +273 -0
  457. package/dist/gateway/policy-resolver.test.js.map +1 -0
  458. package/dist/gateway/request-errors.test.d.ts +2 -0
  459. package/dist/gateway/request-errors.test.d.ts.map +1 -0
  460. package/dist/gateway/request-errors.test.js +108 -0
  461. package/dist/gateway/request-errors.test.js.map +1 -0
  462. package/dist/gateway/request-shaping.d.ts +25 -0
  463. package/dist/gateway/request-shaping.d.ts.map +1 -0
  464. package/dist/gateway/request-shaping.js +66 -0
  465. package/dist/gateway/request-shaping.js.map +1 -0
  466. package/dist/gateway/response-usage.d.ts +35 -0
  467. package/dist/gateway/response-usage.d.ts.map +1 -0
  468. package/dist/gateway/response-usage.js +167 -0
  469. package/dist/gateway/response-usage.js.map +1 -0
  470. package/dist/gateway/response-usage.test.d.ts +2 -0
  471. package/dist/gateway/response-usage.test.d.ts.map +1 -0
  472. package/dist/gateway/response-usage.test.js +78 -0
  473. package/dist/gateway/response-usage.test.js.map +1 -0
  474. package/dist/gateway/runtime.d.ts +25 -0
  475. package/dist/gateway/runtime.d.ts.map +1 -1
  476. package/dist/gateway/runtime.js +39 -19
  477. package/dist/gateway/runtime.js.map +1 -1
  478. package/dist/gateway/server.d.ts +68 -2
  479. package/dist/gateway/server.d.ts.map +1 -1
  480. package/dist/gateway/server.js +532 -116
  481. package/dist/gateway/server.js.map +1 -1
  482. package/dist/gateway/services/audit.d.ts +16 -0
  483. package/dist/gateway/services/audit.d.ts.map +1 -0
  484. package/dist/gateway/services/audit.js +18 -0
  485. package/dist/gateway/services/audit.js.map +1 -0
  486. package/dist/gateway/services/context.d.ts +66 -0
  487. package/dist/gateway/services/context.d.ts.map +1 -0
  488. package/dist/gateway/services/context.js +52 -0
  489. package/dist/gateway/services/context.js.map +1 -0
  490. package/dist/gateway/services/keys.d.ts +78 -0
  491. package/dist/gateway/services/keys.d.ts.map +1 -0
  492. package/dist/gateway/services/keys.js +232 -0
  493. package/dist/gateway/services/keys.js.map +1 -0
  494. package/dist/gateway/services/members.d.ts +72 -0
  495. package/dist/gateway/services/members.d.ts.map +1 -0
  496. package/dist/gateway/services/members.js +161 -0
  497. package/dist/gateway/services/members.js.map +1 -0
  498. package/dist/gateway/services/network.d.ts +36 -0
  499. package/dist/gateway/services/network.d.ts.map +1 -0
  500. package/dist/gateway/services/network.js +147 -0
  501. package/dist/gateway/services/network.js.map +1 -0
  502. package/dist/gateway/services/operator.d.ts +98 -0
  503. package/dist/gateway/services/operator.d.ts.map +1 -0
  504. package/dist/gateway/services/operator.js +170 -0
  505. package/dist/gateway/services/operator.js.map +1 -0
  506. package/dist/gateway/services/operator.test.d.ts +2 -0
  507. package/dist/gateway/services/operator.test.d.ts.map +1 -0
  508. package/dist/gateway/services/operator.test.js +184 -0
  509. package/dist/gateway/services/operator.test.js.map +1 -0
  510. package/dist/gateway/services/peer-lists.d.ts +20 -0
  511. package/dist/gateway/services/peer-lists.d.ts.map +1 -0
  512. package/dist/gateway/services/peer-lists.js +134 -0
  513. package/dist/gateway/services/peer-lists.js.map +1 -0
  514. package/dist/gateway/services/presets.d.ts +34 -0
  515. package/dist/gateway/services/presets.d.ts.map +1 -0
  516. package/dist/gateway/services/presets.js +134 -0
  517. package/dist/gateway/services/presets.js.map +1 -0
  518. package/dist/gateway/services/routing.d.ts +13 -0
  519. package/dist/gateway/services/routing.d.ts.map +1 -0
  520. package/dist/gateway/services/routing.js +18 -0
  521. package/dist/gateway/services/routing.js.map +1 -0
  522. package/dist/gateway/services/services.test.d.ts +2 -0
  523. package/dist/gateway/services/services.test.d.ts.map +1 -0
  524. package/dist/gateway/services/services.test.js +476 -0
  525. package/dist/gateway/services/services.test.js.map +1 -0
  526. package/dist/gateway/services/settings.d.ts +68 -0
  527. package/dist/gateway/services/settings.d.ts.map +1 -0
  528. package/dist/gateway/services/settings.js +580 -0
  529. package/dist/gateway/services/settings.js.map +1 -0
  530. package/dist/gateway/services/tokens.d.ts +29 -0
  531. package/dist/gateway/services/tokens.d.ts.map +1 -0
  532. package/dist/gateway/services/tokens.js +48 -0
  533. package/dist/gateway/services/tokens.js.map +1 -0
  534. package/dist/gateway/services/usage.d.ts +55 -0
  535. package/dist/gateway/services/usage.d.ts.map +1 -0
  536. package/dist/gateway/services/usage.js +225 -0
  537. package/dist/gateway/services/usage.js.map +1 -0
  538. package/dist/gateway/services/wallet-address-env.test.d.ts +2 -0
  539. package/dist/gateway/services/wallet-address-env.test.d.ts.map +1 -0
  540. package/dist/gateway/services/wallet-address-env.test.js +119 -0
  541. package/dist/gateway/services/wallet-address-env.test.js.map +1 -0
  542. package/dist/gateway/services/wallet-address.d.ts +40 -0
  543. package/dist/gateway/services/wallet-address.d.ts.map +1 -0
  544. package/dist/gateway/services/wallet-address.js +116 -0
  545. package/dist/gateway/services/wallet-address.js.map +1 -0
  546. package/dist/gateway/services/wallet-address.test.d.ts +2 -0
  547. package/dist/gateway/services/wallet-address.test.d.ts.map +1 -0
  548. package/dist/gateway/services/wallet-address.test.js +84 -0
  549. package/dist/gateway/services/wallet-address.test.js.map +1 -0
  550. package/dist/gateway/services/workspaces.d.ts +75 -0
  551. package/dist/gateway/services/workspaces.d.ts.map +1 -0
  552. package/dist/gateway/services/workspaces.js +228 -0
  553. package/dist/gateway/services/workspaces.js.map +1 -0
  554. package/dist/gateway/spend-durability.test.d.ts +2 -0
  555. package/dist/gateway/spend-durability.test.d.ts.map +1 -0
  556. package/dist/gateway/spend-durability.test.js +330 -0
  557. package/dist/gateway/spend-durability.test.js.map +1 -0
  558. package/dist/gateway/spend-feed.d.ts +11 -1
  559. package/dist/gateway/spend-feed.d.ts.map +1 -1
  560. package/dist/gateway/spend-feed.js +17 -4
  561. package/dist/gateway/spend-feed.js.map +1 -1
  562. package/dist/gateway/spend-feed.test.d.ts +2 -0
  563. package/dist/gateway/spend-feed.test.d.ts.map +1 -0
  564. package/dist/gateway/spend-feed.test.js +34 -0
  565. package/dist/gateway/spend-feed.test.js.map +1 -0
  566. package/dist/gateway/store-data.test.d.ts +2 -0
  567. package/dist/gateway/store-data.test.d.ts.map +1 -0
  568. package/dist/gateway/store-data.test.js +346 -0
  569. package/dist/gateway/store-data.test.js.map +1 -0
  570. package/dist/gateway/store-migrations.d.ts +6 -0
  571. package/dist/gateway/store-migrations.d.ts.map +1 -0
  572. package/dist/gateway/store-migrations.js +301 -0
  573. package/dist/gateway/store-migrations.js.map +1 -0
  574. package/dist/gateway/store.d.ts +595 -10
  575. package/dist/gateway/store.d.ts.map +1 -1
  576. package/dist/gateway/store.js +1242 -120
  577. package/dist/gateway/store.js.map +1 -1
  578. package/dist/gateway/store.test.d.ts +2 -0
  579. package/dist/gateway/store.test.d.ts.map +1 -0
  580. package/dist/gateway/store.test.js +185 -0
  581. package/dist/gateway/store.test.js.map +1 -0
  582. package/dist/proxy/buyer-chain-reader.d.ts +68 -0
  583. package/dist/proxy/buyer-chain-reader.d.ts.map +1 -0
  584. package/dist/proxy/buyer-chain-reader.js +152 -0
  585. package/dist/proxy/buyer-chain-reader.js.map +1 -0
  586. package/dist/proxy/buyer-chain-reader.test.d.ts +2 -0
  587. package/dist/proxy/buyer-chain-reader.test.d.ts.map +1 -0
  588. package/dist/proxy/buyer-chain-reader.test.js +102 -0
  589. package/dist/proxy/buyer-chain-reader.test.js.map +1 -0
  590. package/dist/proxy/buyer-proxy.d.ts +169 -3
  591. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  592. package/dist/proxy/buyer-proxy.js +873 -117
  593. package/dist/proxy/buyer-proxy.js.map +1 -1
  594. package/dist/proxy/buyer-proxy.test.js +31 -0
  595. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  596. package/dist/proxy/chain-read-cache.d.ts +47 -0
  597. package/dist/proxy/chain-read-cache.d.ts.map +1 -0
  598. package/dist/proxy/chain-read-cache.js +110 -0
  599. package/dist/proxy/chain-read-cache.js.map +1 -0
  600. package/dist/proxy/chain-read-cache.test.d.ts +2 -0
  601. package/dist/proxy/chain-read-cache.test.d.ts.map +1 -0
  602. package/dist/proxy/chain-read-cache.test.js +75 -0
  603. package/dist/proxy/chain-read-cache.test.js.map +1 -0
  604. package/dist/proxy/chain-rpc-test-support.d.ts +31 -0
  605. package/dist/proxy/chain-rpc-test-support.d.ts.map +1 -0
  606. package/dist/proxy/chain-rpc-test-support.js +136 -0
  607. package/dist/proxy/chain-rpc-test-support.js.map +1 -0
  608. package/dist/proxy/chain-rpc.d.ts +121 -0
  609. package/dist/proxy/chain-rpc.d.ts.map +1 -0
  610. package/dist/proxy/chain-rpc.js +420 -0
  611. package/dist/proxy/chain-rpc.js.map +1 -0
  612. package/dist/proxy/chain-rpc.test.d.ts +2 -0
  613. package/dist/proxy/chain-rpc.test.d.ts.map +1 -0
  614. package/dist/proxy/chain-rpc.test.js +180 -0
  615. package/dist/proxy/chain-rpc.test.js.map +1 -0
  616. package/dist/proxy/deposit-watch-hub.d.ts +47 -0
  617. package/dist/proxy/deposit-watch-hub.d.ts.map +1 -0
  618. package/dist/proxy/deposit-watch-hub.js +111 -0
  619. package/dist/proxy/deposit-watch-hub.js.map +1 -0
  620. package/dist/proxy/deposit-watch-hub.test.d.ts +2 -0
  621. package/dist/proxy/deposit-watch-hub.test.d.ts.map +1 -0
  622. package/dist/proxy/deposit-watch-hub.test.js +133 -0
  623. package/dist/proxy/deposit-watch-hub.test.js.map +1 -0
  624. package/dist/proxy/deposit-watcher.d.ts +44 -4
  625. package/dist/proxy/deposit-watcher.d.ts.map +1 -1
  626. package/dist/proxy/deposit-watcher.js +76 -11
  627. package/dist/proxy/deposit-watcher.js.map +1 -1
  628. package/dist/proxy/deposit-watcher.test.js +54 -1
  629. package/dist/proxy/deposit-watcher.test.js.map +1 -1
  630. package/dist/proxy/gateway-policy.test.d.ts +2 -0
  631. package/dist/proxy/gateway-policy.test.d.ts.map +1 -0
  632. package/dist/proxy/gateway-policy.test.js +576 -0
  633. package/dist/proxy/gateway-policy.test.js.map +1 -0
  634. package/dist/proxy/network-models.d.ts +2 -0
  635. package/dist/proxy/network-models.d.ts.map +1 -1
  636. package/dist/proxy/network-models.js +3 -1
  637. package/dist/proxy/network-models.js.map +1 -1
  638. package/dist/proxy/route-policy.d.ts +97 -0
  639. package/dist/proxy/route-policy.d.ts.map +1 -0
  640. package/dist/proxy/route-policy.js +230 -0
  641. package/dist/proxy/route-policy.js.map +1 -0
  642. package/dist/proxy/route-policy.test.d.ts +2 -0
  643. package/dist/proxy/route-policy.test.d.ts.map +1 -0
  644. package/dist/proxy/route-policy.test.js +120 -0
  645. package/dist/proxy/route-policy.test.js.map +1 -0
  646. package/dist/proxy/spend-attribution.d.ts +41 -10
  647. package/dist/proxy/spend-attribution.d.ts.map +1 -1
  648. package/dist/proxy/spend-attribution.js +133 -37
  649. package/dist/proxy/spend-attribution.js.map +1 -1
  650. package/dist/proxy/tee-control.d.ts +3 -1
  651. package/dist/proxy/tee-control.d.ts.map +1 -1
  652. package/dist/proxy/tee-control.js +5 -3
  653. package/dist/proxy/tee-control.js.map +1 -1
  654. package/dist/proxy/telemetry.d.ts +2 -1
  655. package/dist/proxy/telemetry.d.ts.map +1 -1
  656. package/dist/proxy/telemetry.js +10 -5
  657. package/dist/proxy/telemetry.js.map +1 -1
  658. package/dist/routing-policy/policy.d.ts +149 -0
  659. package/dist/routing-policy/policy.d.ts.map +1 -0
  660. package/dist/routing-policy/policy.js +450 -0
  661. package/dist/routing-policy/policy.js.map +1 -0
  662. package/dist/routing-policy/policy.test.d.ts +2 -0
  663. package/dist/routing-policy/policy.test.d.ts.map +1 -0
  664. package/dist/routing-policy/policy.test.js +134 -0
  665. package/dist/routing-policy/policy.test.js.map +1 -0
  666. package/package.json +15 -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,18 +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';
30
32
  import { isClientCredentialHeader } from './credential-headers.js';
31
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';
32
36
  // Re-export for backward compatibility (used by tests and other consumers)
33
37
  export { selectCandidatePeersForRouting } from './routing.js';
34
38
  export { parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, ROUTED_MODEL_ALIAS } from './request-utils.js';
@@ -36,7 +40,34 @@ const WATCHER_ABSENCE_ERRORS = {
36
40
  'payments-disabled': 'Deposit watcher unavailable — payments are disabled on this buyer.',
37
41
  'no-deposit-relay': 'Deposit watcher unavailable — this chain has no deposit relay.',
38
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.',
39
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
+ }
40
71
  // 401/403 are included: sellers relay upstream auth failures (revoked or
41
72
  // expired key, region/WAF block) that are specific to that seller's upstream
42
73
  // account, so another peer serving the same model can usually complete the
@@ -44,6 +75,24 @@ const WATCHER_ABSENCE_ERRORS = {
44
75
  // because model_not_found already has dedicated unadvertise handling while a
45
76
  // generic 404 would repeat identically on every peer.
46
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
+ }
47
96
  const MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER = 3;
48
97
  const MODEL_RATE_LIMIT_RETRY_DELAYS_MS = [250, 750];
49
98
  const MODEL_RATE_LIMIT_MAX_RETRY_AFTER_MS = 2_000;
@@ -96,11 +145,34 @@ function peerAllowedByPolicy(policyRouter, request, peer) {
96
145
  return policyRouter.allowsPeerForPricing(request, peer);
97
146
  return true;
98
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
+ }
99
170
  function isControlPlaneServicesPath(path) {
100
171
  return path.toLowerCase().startsWith('/v1/models');
101
172
  }
102
173
  function isRouterSuccess(statusCode, path, retryableStatusCodes) {
103
- 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);
104
176
  }
105
177
  /**
106
178
  * Detect a "model not served" rejection: the seller's own pre-payment 400
@@ -610,9 +682,16 @@ export async function mergeJsonStateFile(stateDir, stateFile, patch) {
610
682
  throw err;
611
683
  }
612
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
+ }
613
693
  function sendUnknownBuyerIdentity(res) {
614
- res.writeHead(404, { 'content-type': 'application/json' });
615
- res.end(JSON.stringify({ ok: false, error: 'Unknown buyer identity' }));
694
+ sendJson(res, 404, { ok: false, error: 'Unknown buyer identity' });
616
695
  }
617
696
  export class BuyerProxy {
618
697
  _server;
@@ -681,6 +760,8 @@ export class BuyerProxy {
681
760
  _depositWatcher = null;
682
761
  /** Why no watcher is attached, so UIs can show the actual cause. */
683
762
  _depositWatcherAbsence = null;
763
+ /** Watchers for named buyer identities (each workspace wallet), keyed by identity name. */
764
+ _identityDepositWatchers = new Map();
684
765
  /**
685
766
  * requestId -> the conversation that issued it, so the node's per-request
686
767
  * spend events can be attributed to a chat. Only the proxy knows this
@@ -691,8 +772,32 @@ export class BuyerProxy {
691
772
  */
692
773
  _requestConversations = new Map();
693
774
  /** Signed spend for requests a local gateway tagged, polled over the control plane. */
694
- _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;
695
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 ?? [])];
696
801
  this._node = config.node;
697
802
  this._verifier = config.verifier;
698
803
  this._buyerIdentities = config.buyerIdentities ?? new BuyerIdentityLoader(config.node, config.dataDir);
@@ -703,6 +808,11 @@ export class BuyerProxy {
703
808
  this._peerCacheTtlMs = Math.max(0, config.peerCacheTtlMs ?? Math.max(6 * 60_000, this._bgRefreshIntervalMs + 60_000));
704
809
  this._stateDir = config.dataDir;
705
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
+ });
706
816
  this._configPath = config.configPath ?? null;
707
817
  this._conversations = new ConversationStore(config.dataDir);
708
818
  this._pinnedPeer = config.pinnedPeerId?.toLowerCase() ?? null;
@@ -771,7 +881,7 @@ export class BuyerProxy {
771
881
  * (or can be) loaded, undefined when no stored identity has that name.
772
882
  */
773
883
  async _resolveBuyerIdentity(header) {
774
- const name = header?.trim().toLowerCase() ?? '';
884
+ const name = buyerIdentityName(header);
775
885
  if (!name || name === DEFAULT_BUYER_IDENTITY)
776
886
  return null;
777
887
  const loaded = await this._buyerIdentities.ensure(name).catch((err) => {
@@ -780,10 +890,531 @@ export class BuyerProxy {
780
890
  });
781
891
  return loaded ? name : undefined;
782
892
  }
783
- /** `?identity=` on control-plane reads; undefined when it names no stored identity. */
784
- async _controlPlaneIdentity(path) {
785
- const param = new URL(path, 'http://localhost').searchParams.get('identity');
786
- 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 }));
787
1418
  }
788
1419
  /**
789
1420
  * Remember which chat a request belongs to for the life of the request plus
@@ -807,6 +1438,22 @@ export class BuyerProxy {
807
1438
  this._depositWatcher = watcher;
808
1439
  this._depositWatcherAbsence = watcher ? null : absenceReason;
809
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
+ }
810
1457
  /** Latest relayer receipt for a sweep authNonce, if one has arrived. */
811
1458
  getSweepReceipt(authNonce) {
812
1459
  return this._sweepReceipts.get(authNonce.toLowerCase()) ?? null;
@@ -989,12 +1636,18 @@ export class BuyerProxy {
989
1636
  return;
990
1637
  try {
991
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.
992
1642
  const next = config.buyer.routingPreferences;
993
1643
  this._routingPreferences = {
994
1644
  ...next,
995
1645
  allowedPeerIds: [...next.allowedPeerIds],
996
1646
  blockedPeerIds: [...next.blockedPeerIds],
997
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.
998
1651
  log(`Routing preferences reloaded: minTrust=${next.minTrustScore} maxInput=${next.maxInputUsdPerMillion} `
999
1652
  + `preferFree=${next.preferFreePeers} allow=${next.allowedPeerIds.length} block=${next.blockedPeerIds.length}`);
1000
1653
  }
@@ -1434,9 +2087,11 @@ export class BuyerProxy {
1434
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));
1435
2088
  if (outcome.code === 'busy')
1436
2089
  throw new Error(outcome.reason);
1437
- });
2090
+ }, this._isGatewayAuthenticated(req.headers));
1438
2091
  return;
1439
2092
  }
2093
+ if (await this._handleGatewayControl(req, res, method, path))
2094
+ return;
1440
2095
  const origin = req.headers.origin ?? '';
1441
2096
  const isLocal = origin.startsWith('http://127.0.0.1') || origin.startsWith('http://localhost') || origin === 'file://';
1442
2097
  if (isLocal)
@@ -1499,6 +2154,12 @@ export class BuyerProxy {
1499
2154
  onChainUsageLastEpochUsdcMicros: p.onChainUsageLastEpochUsdcMicros ?? null,
1500
2155
  onChainWashFlagged: p.onChainWashFlagged ?? null,
1501
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,
1502
2163
  verificationResults: p.verificationResults,
1503
2164
  lastSeen: p.lastSeen,
1504
2165
  }));
@@ -1741,8 +2402,7 @@ export class BuyerProxy {
1741
2402
  }
1742
2403
  if (path.startsWith('/_antseed/buyer-identities') && method === 'GET') {
1743
2404
  await this._buyerIdentities.loadAll();
1744
- res.writeHead(200, { 'content-type': 'application/json' });
1745
- res.end(JSON.stringify({ ok: true, identities: this._node.buyerIdentities() }));
2405
+ sendJson(res, 200, { ok: true, identities: this._node.buyerIdentities() });
1746
2406
  return;
1747
2407
  }
1748
2408
  if (path.startsWith('/_antseed/channels') && method === 'GET') {
@@ -1767,12 +2427,24 @@ export class BuyerProxy {
1767
2427
  res.end(JSON.stringify({ ok: true, channels: channelsWithCapabilities }));
1768
2428
  return;
1769
2429
  }
1770
- // Unauthenticated like every /_antseed route: it relies on the buyer
1771
- // 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.
1772
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
+ }
1773
2446
  const after = Number(new URL(path, 'http://localhost').searchParams.get('after') ?? '0');
1774
- res.writeHead(200, { 'content-type': 'application/json' });
1775
- 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 }) });
1776
2448
  return;
1777
2449
  }
1778
2450
  if (path.startsWith('/_antseed/buyer-usage') && method === 'GET') {
@@ -1863,6 +2535,11 @@ export class BuyerProxy {
1863
2535
  res.end(JSON.stringify({ ok: false, error: 'Missing peerId' }));
1864
2536
  return;
1865
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
+ }
1866
2543
  try {
1867
2544
  const identity = await this._resolveBuyerIdentity(identityParam);
1868
2545
  if (identity === undefined)
@@ -1883,23 +2560,29 @@ export class BuyerProxy {
1883
2560
  // Hot-wallet deposit watcher (auto-sweep). The desktop and `antseed
1884
2561
  // deposit` drive the daemon's single watcher through these instead of
1885
2562
  // running a second signer against the same wallet.
1886
- 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);
1887
2569
  res.writeHead(200, { 'content-type': 'application/json' });
1888
2570
  res.end(JSON.stringify({
1889
2571
  ok: true,
1890
- watcher: this._depositWatcher !== null,
2572
+ ...(identity ? { identity } : {}),
2573
+ watcher: watcher !== null,
1891
2574
  // Why no watcher runs (null while one is attached) — lets UIs say
1892
2575
  // "payments are disabled" vs "this chain has no deposit relay".
1893
- reason: this._depositWatcher ? null : this._depositWatcherAbsence,
2576
+ reason,
1894
2577
  // Live payments health: configured/active flags + chain-RPC
1895
2578
  // reachability from the node's background monitor. Optional call —
1896
2579
  // tolerate an older @antseed/node without it.
1897
2580
  payments: this._node.getPaymentsStatus?.() ?? null,
1898
- status: this._depositWatcher?.status() ?? null,
2581
+ status: watcher?.status() ?? null,
1899
2582
  }));
1900
2583
  return;
1901
2584
  }
1902
- if (path === '/_antseed/deposits/watch' && method === 'POST') {
2585
+ if (controlPathname === '/_antseed/deposits/watch' && method === 'POST') {
1903
2586
  const chunks = [];
1904
2587
  let totalSize = 0;
1905
2588
  for await (const chunk of req) {
@@ -1912,9 +2595,12 @@ export class BuyerProxy {
1912
2595
  chunks.push(chunk);
1913
2596
  }
1914
2597
  let mode;
2598
+ let bodyIdentity;
1915
2599
  try {
1916
2600
  const body = JSON.parse(Buffer.concat(chunks).toString() || '{}');
1917
2601
  mode = String(body.mode ?? 'active');
2602
+ if (typeof body.identity === 'string')
2603
+ bodyIdentity = body.identity;
1918
2604
  }
1919
2605
  catch {
1920
2606
  res.writeHead(400, { 'content-type': 'application/json' });
@@ -1926,18 +2612,26 @@ export class BuyerProxy {
1926
2612
  res.end(JSON.stringify({ ok: false, error: 'mode must be "active" or "background"' }));
1927
2613
  return;
1928
2614
  }
1929
- 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);
1930
2619
  if (!watcher) {
1931
- const reason = this._depositWatcherAbsence;
1932
2620
  const error = (reason && WATCHER_ABSENCE_ERRORS[reason]) ?? 'Deposit watcher unavailable.';
1933
2621
  res.writeHead(503, { 'content-type': 'application/json' });
1934
2622
  res.end(JSON.stringify({ ok: false, reason, error }));
1935
2623
  return;
1936
2624
  }
1937
- if (mode === 'active')
1938
- watcher.promote();
1939
- 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 {
1940
2633
  watcher.demote();
2634
+ }
1941
2635
  res.writeHead(200, { 'content-type': 'application/json' });
1942
2636
  res.end(JSON.stringify({ ok: true, status: watcher.status() }));
1943
2637
  return;
@@ -1958,13 +2652,17 @@ export class BuyerProxy {
1958
2652
  * `x-antseed-request-id` response header keeps the port-reuse probe in
1959
2653
  * `buyer start` recognizing this as an AntSeed proxy.
1960
2654
  */
1961
- async _handleNetworkModels(res, rawPath) {
2655
+ async _handleNetworkModels(res, rawPath, requestPolicy) {
1962
2656
  const url = new URL(rawPath, 'http://localhost');
1963
2657
  const responseHeaders = { 'content-type': 'application/json', 'x-antseed-request-id': randomUUID() };
1964
2658
  const peers = await this._getPeers();
2659
+ const peerById = new Map(peers.map((peer) => [peer.peerId, peer]));
1965
2660
  const models = buildNetworkModels(peers, this._now(), {
1966
2661
  routingPreferences: this._routingPreferences,
1967
2662
  peerHealth: this._peerHealth,
2663
+ ...(requestPolicy
2664
+ ? { offerFilter: (offer) => this._policyAllowsOffer(requestPolicy, peerById.get(offer.peerId), offer) }
2665
+ : {}),
1968
2666
  });
1969
2667
  const modelIdRaw = url.pathname.replace(/^\/v1\/models\/?/i, '');
1970
2668
  if (modelIdRaw.length > 0) {
@@ -2009,6 +2707,18 @@ export class BuyerProxy {
2009
2707
  res.writeHead(200, responseHeaders);
2010
2708
  res.end(JSON.stringify({ object: 'list', data }));
2011
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
+ }
2012
2722
  async _handleRequest(req, res) {
2013
2723
  const method = req.method ?? 'GET';
2014
2724
  const path = req.url ?? '/';
@@ -2033,13 +2743,6 @@ export class BuyerProxy {
2033
2743
  res.end(JSON.stringify({ error: { message: 'Not found', type: 'invalid_request_error' } }));
2034
2744
  return;
2035
2745
  }
2036
- // `/v1/models` is answered locally from the discovered-peer cache: one
2037
- // entry per model across the whole network, with the peers serving it.
2038
- // Routed to a single pinned seller it would only cover that seller's
2039
- // services — and would require a pin just to browse the network.
2040
- if (method === 'GET' && normalizedPath.startsWith('/v1/models')) {
2041
- return this._handleNetworkModels(res, path);
2042
- }
2043
2746
  // Collect request body
2044
2747
  const chunks = [];
2045
2748
  for await (const chunk of req) {
@@ -2073,6 +2776,41 @@ export class BuyerProxy {
2073
2776
  }
2074
2777
  // Remove host header (points to localhost, not the seller)
2075
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
+ }
2076
2814
  // Node already answered curl's `Expect: 100-continue` for large bodies;
2077
2815
  // forwarded, it makes the seller's fetch fail.
2078
2816
  delete headers['expect'];
@@ -2087,6 +2825,18 @@ export class BuyerProxy {
2087
2825
  delete headers[SPEND_ATTRIBUTION_HEADER];
2088
2826
  // The paying identity rides on the internal headers through routing, like
2089
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
+ }
2090
2840
  const buyerIdentity = await this._resolveBuyerIdentity(headers[BUYER_IDENTITY_HEADER]);
2091
2841
  if (buyerIdentity === undefined) {
2092
2842
  res.writeHead(400, { 'content-type': 'application/json' });
@@ -2310,6 +3060,17 @@ export class BuyerProxy {
2310
3060
  }));
2311
3061
  return;
2312
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
+ }
2313
3074
  // Discover peers
2314
3075
  const peers = await this._getPeers();
2315
3076
  if (peers.length === 0) {
@@ -2328,72 +3089,18 @@ export class BuyerProxy {
2328
3089
  ({ candidatePeers: modelPeers, routePlanByPeerId: modelPlans } = selectModelPeers(discoveredPeers));
2329
3090
  }
2330
3091
  const router = this._node.router;
2331
- const policyRouter = router;
2332
- const routeCandidates = modelPeers
2333
- .map((peer) => {
2334
- const plan = modelPlans.get(peer.peerId)
2335
- ?? resolvePeerRoutePlan(peer, requestProtocol, requestedService, explicitProvider, 'strict');
2336
- if (!plan?.serviceId)
2337
- return null;
2338
- const offer = findAdvertisedServiceOffer(peer, plan.provider, plan.serviceId);
2339
- if (!offer)
2340
- return null;
2341
- const missingRequired = plan.selection?.requiresTransform
2342
- ? requiredParameters
2343
- : findMissingRequiredParameters(peer, plan.provider, plan.serviceId, requiredParameters);
2344
- if (missingRequired.length > 0) {
2345
- log(`Capability filter: peer ${peer.peerId.slice(0, 12)}... service="${plan.serviceId}" `
2346
- + `missing required parameter(s): ${missingRequired.join(', ')}`);
2347
- return null;
2348
- }
2349
- const requestForPolicy = withRoutedModel(serializedReq, plan.serviceId);
2350
- if (!peerAllowedByPolicy(policyRouter, requestForPolicy, peer))
2351
- return null;
2352
- return {
2353
- peer,
2354
- peerId: peer.peerId,
2355
- serviceId: plan.serviceId,
2356
- request: requestForPolicy,
2357
- reputation: normalizedModelReputationScore(peer) ?? -1,
2358
- hasCachedInputPricing: offer.cachedInputUsdPerMillion !== undefined,
2359
- inputUsdPerMillion: offer.inputUsdPerMillion ?? null,
2360
- outputUsdPerMillion: offer.outputUsdPerMillion ?? null,
2361
- minImageUsdPerImage: offer.minImageUsdPerImage ?? null,
2362
- };
2363
- })
2364
- .filter((candidate) => candidate !== null);
2365
- const now = this._now();
2366
- const preferCachedPricing = routeCandidates.some((candidate) => candidate.hasCachedInputPricing);
2367
- const ranked = routeCandidates.map((candidate) => {
2368
- const health = this._peerHealth.get(candidate.peer.peerId);
2369
- return {
2370
- ...candidate,
2371
- effectiveReputationScore: effectiveModelReputationScore(candidate.reputation >= 0 ? candidate.reputation : null, candidate.hasCachedInputPricing, preferCachedPricing, modelRouteTotalPrice(candidate) === 0),
2372
- peerCooldownUntil: health?.cooldownUntil ?? null,
2373
- peerFailureStreak: health?.failureStreak ?? 0,
2374
- };
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,
2375
3102
  });
2376
- let candidates;
2377
- const routingPreferences = this._routingPreferences;
2378
- if (routingPreferences) {
2379
- candidates = rankModelRoutes(ranked, routingPreferences, now)
2380
- .filter((candidate) => isModelRouteEligible(candidate, routingPreferences));
2381
- }
2382
- else {
2383
- ranked.sort((a, b) => (b.effectiveReputationScore ?? -1) - (a.effectiveReputationScore ?? -1)
2384
- || a.peer.peerId.localeCompare(b.peer.peerId));
2385
- const ready = ranked.filter((candidate) => !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now));
2386
- candidates = ready.length > 0 ? ready : ranked;
2387
- }
2388
- if (preferredConversationPeerId) {
2389
- const preferredIndex = candidates.findIndex((candidate) => (candidate.peer.peerId.toLowerCase() === preferredConversationPeerId
2390
- && !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now)));
2391
- if (preferredIndex > 0) {
2392
- const [preferred] = candidates.splice(preferredIndex, 1);
2393
- if (preferred)
2394
- candidates.unshift(preferred);
2395
- }
2396
- }
3103
+ const requireVerified = effectivePolicy?.requireVerified === true;
2397
3104
  if (candidates.length === 0) {
2398
3105
  const capabilityRequired = requiredParameters.length > 0;
2399
3106
  // The model exists on the network but only behind an API this
@@ -2437,10 +3144,16 @@ export class BuyerProxy {
2437
3144
  }
2438
3145
  let lastRetry = null;
2439
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;
2440
3151
  for (const [index, selected] of candidates.entries()) {
3152
+ if (unfunded && !isFree(selected))
3153
+ continue;
2441
3154
  if (this._verifier) {
2442
3155
  const makeReach = (chosenId) => makeVerifierReach(this._node, selected.peer, chosenId, clientAbortController.signal);
2443
- const outcome = await this._verifyPeer(selected.peer, makeReach, clientAbortController.signal);
3156
+ const outcome = await this._verifyPeer(selected.peer, makeReach, clientAbortController.signal, requireVerified);
2444
3157
  if (!outcome.ok) {
2445
3158
  lastVerificationError = `Peer ${selected.peer.peerId.slice(0, 12)}... failed required verification (${outcome.reason ?? 'failed'}).`;
2446
3159
  log(`${lastVerificationError} Trying the next model peer.`);
@@ -2452,9 +3165,11 @@ export class BuyerProxy {
2452
3165
  + `service="${selected.serviceId}" reputation=${selected.reputation} `
2453
3166
  + `effective=${selected.effectiveReputationScore ?? 'unknown'} peer=${index + 1}/${candidates.length} `
2454
3167
  + `attempt=${peerAttempt + 1}/${MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER}`);
2455
- 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);
2456
3169
  if (result.done) {
2457
- 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) {
2458
3173
  this._conversations.recordRoutedModel(trackedConversationId, `${selected.peer.peerId}@${selected.serviceId}`, conversationPinAtRouting);
2459
3174
  }
2460
3175
  return;
@@ -2468,6 +3183,12 @@ export class BuyerProxy {
2468
3183
  }
2469
3184
  lastRetry = result;
2470
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
+ }
2471
3192
  res.writeHead(result.statusCode, result.responseHeaders);
2472
3193
  res.end(result.responseBody);
2473
3194
  return;
@@ -2614,6 +3335,31 @@ export class BuyerProxy {
2614
3335
  return;
2615
3336
  }
2616
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
+ }
2617
3363
  if (!peerAllowedByPolicy(policyRouter, pinnedRequest, selectedPeer)) {
2618
3364
  log(`Pinned peer ${selectedPeer.peerId.slice(0, 12)}... filtered out by buyer routing policy`);
2619
3365
  res.writeHead(502, { 'content-type': 'text/plain' });
@@ -2623,7 +3369,7 @@ export class BuyerProxy {
2623
3369
  }
2624
3370
  if (this._verifier) {
2625
3371
  const makeReach = (chosenId) => makeVerifierReach(this._node, selectedPeer, chosenId, clientAbortController.signal);
2626
- const outcome = await this._verifyPeer(selectedPeer, makeReach, clientAbortController.signal);
3372
+ const outcome = await this._verifyPeer(selectedPeer, makeReach, clientAbortController.signal, pinnedPolicy?.requireVerified === true);
2627
3373
  const short = selectedPeer.peerId.slice(0, 12);
2628
3374
  if (outcome.verified) {
2629
3375
  log(`Verified ${short}... via ${outcome.sdk}`);
@@ -2639,7 +3385,7 @@ export class BuyerProxy {
2639
3385
  }
2640
3386
  }
2641
3387
  log(`Using pinned peer ${selectedPeer.peerId.slice(0, 12)}...`);
2642
- 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);
2643
3389
  if (result.done && trackedConversationId && pinnedServiceId) {
2644
3390
  this._conversations.recordRoutedModel(trackedConversationId, `${selectedPeer.peerId}@${pinnedServiceId}`, conversationPinAtRouting);
2645
3391
  }
@@ -2650,10 +3396,12 @@ export class BuyerProxy {
2650
3396
  res.end(result.responseBody);
2651
3397
  }
2652
3398
  }
2653
- async _verifyPeer(peer, makeReach, signal) {
2654
- const policy = this._verifier;
2655
- if (!policy)
3399
+ async _verifyPeer(peer, makeReach, signal, requireVerified = false) {
3400
+ const configured = this._verifier;
3401
+ if (!configured)
2656
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;
2657
3405
  const fingerprint = verifierSupportFingerprint(peer.capabilities);
2658
3406
  const outcome = await this._teeVerification.verify(peer, policy, () => runVerifier(policy, peer.peerId, peer.capabilities, makeReach, signal));
2659
3407
  const current = this._cachedPeers.find((candidate) => candidate.peerId === peer.peerId);
@@ -2731,7 +3479,8 @@ export class BuyerProxy {
2731
3479
  * was sent to the client (success or non-retryable error), or retry info if the
2732
3480
  * caller should try another peer.
2733
3481
  */
2734
- 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;
2735
3484
  const selectedRoutePlan = routePlanByPeerId.get(selectedPeer.peerId)
2736
3485
  ?? resolvePeerRoutePlan(selectedPeer, requestProtocol, requestedService, explicitProvider, 'lenient');
2737
3486
  if (!selectedRoutePlan) {
@@ -2757,6 +3506,7 @@ export class BuyerProxy {
2757
3506
  ...(requestedService ? { 'x-antseed-service': requestedService } : {}),
2758
3507
  },
2759
3508
  };
3509
+ const latencyServiceId = selectedRoutePlan.serviceId ?? requestedService;
2760
3510
  if (selectedRoutePlan.serviceId) {
2761
3511
  requestForPeer = withRoutedModel(requestForPeer, selectedRoutePlan.serviceId);
2762
3512
  }
@@ -2832,10 +3582,12 @@ export class BuyerProxy {
2832
3582
  if (!metadata.streaming)
2833
3583
  return;
2834
3584
  streamed = true;
3585
+ if (startResponse.statusCode < 400)
3586
+ this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, Date.now() - startTime);
2835
3587
  const adaptedStartResponse = streamResponseAdapter
2836
3588
  ? streamResponseAdapter.adaptStart(startResponse)
2837
3589
  : startResponse;
2838
- const streamingHeaders = attachStreamingAntseedHeaders(adaptedStartResponse.headers, selectedPeer, requestForPeer.requestId);
3590
+ const streamingHeaders = attachStreamingAntseedHeaders(adaptedStartResponse.headers, selectedPeer, requestForPeer);
2839
3591
  // Ensure content-type is set for SSE — some upstream APIs (e.g. Codex)
2840
3592
  // omit it, which can cause the client's fetch body reader to not
2841
3593
  // detect end-of-stream properly.
@@ -2874,6 +3626,8 @@ export class BuyerProxy {
2874
3626
  responseForClient = inject402PeerId(responseForClient, selectedPeer.peerId);
2875
3627
  }
2876
3628
  const latencyMs = Date.now() - startTime;
3629
+ if (!streamed && responseForClient.statusCode < 400)
3630
+ this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, latencyMs);
2877
3631
  log(`Response: ${responseForClient.statusCode} (${latencyMs}ms, ${responseForClient.body.length} bytes)`);
2878
3632
  if (responseForClient.statusCode >= 400) {
2879
3633
  const prefix = adaptResponse && !streamed ? 'Upstream adapted error detail' : 'Upstream error detail';
@@ -2890,7 +3644,7 @@ export class BuyerProxy {
2890
3644
  if (router && responseFault !== 'buyer') {
2891
3645
  router.onResult(selectedPeer, {
2892
3646
  success: !modelNotFound
2893
- && isRouterSuccess(responseForClient.statusCode, requestForPeer.path, retryableStatusCodes),
3647
+ && isRouterSuccess(responseForClient.statusCode, requestForPeer.path, retryableStatusCodesFor(gatewayAutoRoute)),
2894
3648
  latencyMs,
2895
3649
  tokens: telemetry.usage.totalTokens,
2896
3650
  });
@@ -2910,7 +3664,7 @@ export class BuyerProxy {
2910
3664
  }
2911
3665
  // Non-streamed response — check if retryable
2912
3666
  const responseHeaders = attachAntseedTelemetryHeaders(responseForClient.headers, selectedPeer, telemetry, requestForPeer.requestId, latencyMs);
2913
- if (retryableStatusCodes.has(responseForClient.statusCode)) {
3667
+ if (shouldTryNextPeer(responseForClient.statusCode, modelNotFound, pinned, gatewayAutoRoute)) {
2914
3668
  return {
2915
3669
  done: false,
2916
3670
  statusCode: responseForClient.statusCode,
@@ -2951,6 +3705,8 @@ export class BuyerProxy {
2951
3705
  }
2952
3706
  const latencyMs = Date.now() - startTime;
2953
3707
  this._markModelActivity();
3708
+ if (response.statusCode < 400)
3709
+ this._recordPeerLatency(selectedPeer.peerId, latencyServiceId, latencyMs);
2954
3710
  log(`Response: ${response.statusCode} (${latencyMs}ms, ${response.body.length} bytes)`);
2955
3711
  if (response.statusCode >= 400) {
2956
3712
  const prefix = adaptResponse ? 'Upstream adapted error detail' : 'Upstream error detail';
@@ -2968,7 +3724,7 @@ export class BuyerProxy {
2968
3724
  if (router && responseFault !== 'buyer') {
2969
3725
  router.onResult(selectedPeer, {
2970
3726
  success: !modelNotFound
2971
- && isRouterSuccess(response.statusCode, requestForPeer.path, retryableStatusCodes),
3727
+ && isRouterSuccess(response.statusCode, requestForPeer.path, retryableStatusCodesFor(gatewayAutoRoute)),
2972
3728
  latencyMs,
2973
3729
  tokens: telemetry.usage.totalTokens,
2974
3730
  });
@@ -2980,7 +3736,7 @@ export class BuyerProxy {
2980
3736
  this._recordPeerResponseHealth(selectedPeer.peerId, response.statusCode, requestForPeer.path);
2981
3737
  }
2982
3738
  // Check if retryable
2983
- if (retryableStatusCodes.has(response.statusCode)) {
3739
+ if (shouldTryNextPeer(response.statusCode, modelNotFound, pinned, gatewayAutoRoute)) {
2984
3740
  return { done: false, statusCode: response.statusCode, responseBody: Buffer.from(response.body), responseHeaders, errorMessage: null };
2985
3741
  }
2986
3742
  // Forward response headers and body to the HTTP client