@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
@@ -1,101 +1,417 @@
1
1
  import { DEFAULT_BUYER_IDENTITY } from '@antseed/node';
2
2
  import Database from 'better-sqlite3';
3
- import { mkdirSync } from 'node:fs';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { chmodSync, existsSync, mkdirSync } from 'node:fs';
4
5
  import { join } from 'node:path';
5
- import { generateApiKey, hashApiKey, apiKeyHint, newKeyId } from './keys.js';
6
- import { LIMIT_PERIODS, periodStart } from './limits.js';
7
- const MIGRATIONS = [
8
- `
9
- CREATE TABLE api_keys (
10
- id TEXT PRIMARY KEY,
11
- label TEXT NOT NULL,
12
- key_hash TEXT NOT NULL UNIQUE,
13
- key_hint TEXT NOT NULL,
14
- buyer_identity TEXT NOT NULL,
15
- source TEXT NOT NULL DEFAULT 'created',
16
- status TEXT NOT NULL DEFAULT 'active',
17
- daily_limit_usdc INTEGER,
18
- monthly_limit_usdc INTEGER,
19
- total_limit_usdc INTEGER,
20
- expires_at INTEGER,
21
- created_at INTEGER NOT NULL,
22
- revoked_at INTEGER,
23
- last_used_at INTEGER
24
- );
25
- CREATE TABLE gateway_requests (
26
- tag TEXT PRIMARY KEY,
27
- key_id TEXT NOT NULL,
28
- buyer_identity TEXT NOT NULL,
29
- method TEXT NOT NULL,
30
- path TEXT NOT NULL,
31
- model TEXT,
32
- status INTEGER,
33
- buyer_request_id TEXT,
34
- started_at INTEGER NOT NULL,
35
- finished_at INTEGER
36
- );
37
- CREATE INDEX gateway_requests_key_time ON gateway_requests(key_id, started_at);
38
- CREATE TABLE ledger_entries (
39
- id INTEGER PRIMARY KEY AUTOINCREMENT,
40
- kind TEXT NOT NULL CHECK (kind IN ('spend', 'credit')),
41
- key_id TEXT NOT NULL,
42
- buyer_identity TEXT NOT NULL,
43
- amount_usdc INTEGER NOT NULL,
44
- external_ref TEXT NOT NULL UNIQUE,
45
- request_tag TEXT,
46
- seller_peer_id TEXT,
47
- input_tokens INTEGER NOT NULL DEFAULT 0,
48
- cached_input_tokens INTEGER NOT NULL DEFAULT 0,
49
- output_tokens INTEGER NOT NULL DEFAULT 0,
50
- note TEXT,
51
- created_at INTEGER NOT NULL
52
- );
53
- CREATE INDEX ledger_entries_key_time ON ledger_entries(key_id, kind, created_at);
54
- `,
55
- `
56
- ALTER TABLE api_keys ADD COLUMN topup_enabled INTEGER NOT NULL DEFAULT 0;
57
- `,
58
- ];
6
+ import { isRoutingPolicy, normalizePolicy } from '../routing-policy/policy.js';
7
+ import { ADMIN_TOKEN_PREFIX, apiKeyHint, generateApiKey, hashApiKey, newKeyId } from './keys.js';
8
+ import { BUDGET_PERIODS, LIMIT_PERIODS, NO_BUDGET_LIMITS, fullLimits, periodStart, } from './limits.js';
9
+ import { MIGRATIONS } from './store-migrations.js';
10
+ export const DEFAULT_WORKSPACE_ID = 'ws_default';
11
+ function chatThreadSummary(row) {
12
+ return {
13
+ id: row.id,
14
+ workspaceId: row.workspace_id,
15
+ owner: { kind: row.owner_kind, id: row.owner_id },
16
+ title: row.title,
17
+ model: row.model,
18
+ peerId: row.peer_id,
19
+ createdAt: row.created_at,
20
+ updatedAt: row.updated_at,
21
+ };
22
+ }
23
+ /**
24
+ * End users are whatever a key holder sends, so a report by user keeps the
25
+ * largest ones and folds the rest into one row.
26
+ */
27
+ export const MAX_USER_GROUPS = 100;
28
+ export const OTHER_USERS_GROUP = '\u0000others';
29
+ /** Best effort: a file we do not own (or a filesystem without modes) keeps its mode. */
30
+ function restrictMode(path, mode) {
31
+ try {
32
+ if (existsSync(path))
33
+ chmodSync(path, mode);
34
+ }
35
+ catch {
36
+ // not ours to change
37
+ }
38
+ }
59
39
  export function gatewayDir(dataDir) {
60
40
  return join(dataDir, 'gateway');
61
41
  }
42
+ function newId(prefix) {
43
+ return `${prefix}_${randomBytes(6).toString('hex')}`;
44
+ }
45
+ function parsePolicy(raw) {
46
+ if (!raw)
47
+ return null;
48
+ try {
49
+ const parsed = JSON.parse(raw);
50
+ return isRoutingPolicy(parsed) ? normalizePolicy(parsed) : null;
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ function policyJson(policy) {
57
+ return policy ? JSON.stringify(normalizePolicy(policy)) : null;
58
+ }
59
+ function parseJson(raw, fallback) {
60
+ if (!raw)
61
+ return fallback;
62
+ try {
63
+ return JSON.parse(raw);
64
+ }
65
+ catch {
66
+ return fallback;
67
+ }
68
+ }
69
+ function rowLimits(row, prefix = '') {
70
+ const limit = (period) => row[`${prefix}${period}_limit_usdc`];
71
+ return {
72
+ daily: limit('daily'),
73
+ weekly: limit('weekly'),
74
+ monthly: limit('monthly'),
75
+ total: limit('total'),
76
+ };
77
+ }
62
78
  /**
63
- * Durable state for the API-key gateway: keys, request log and the per-key
64
- * ledger. SQLite in WAL mode so `antseed gateway key …`
65
- * commands can change keys while a gateway process is serving them.
79
+ * Durable state for the API-key gateway and its console: keys, request log,
80
+ * the per-key ledger, and the organization around them (workspaces, members,
81
+ * invites, presets, peer lists, management tokens, settings). SQLite in WAL
82
+ * mode so `antseed gateway key …` commands can change keys while a gateway
83
+ * process is serving them.
66
84
  */
67
85
  export class GatewayStore {
68
86
  _now;
69
87
  _db;
88
+ _statements = new Map();
89
+ _settings = new Map();
90
+ _touched = new Map();
70
91
  constructor(dataDir, _now = () => Date.now()) {
71
92
  this._now = _now;
72
- mkdirSync(gatewayDir(dataDir), { recursive: true });
73
- this._db = new Database(join(gatewayDir(dataDir), 'gateway.db'));
74
- this._db.pragma('journal_mode = WAL');
93
+ const dir = gatewayDir(dataDir);
94
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
95
+ // Key hashes, sessions and request bodies live here: owner-only.
96
+ restrictMode(dir, 0o700);
97
+ const file = join(dir, 'gateway.db');
98
+ this._db = new Database(file, { timeout: 5000 });
75
99
  this._db.pragma('busy_timeout = 5000');
100
+ this._enableWal();
76
101
  this._db.pragma('foreign_keys = ON');
102
+ for (const path of [file, `${file}-wal`, `${file}-shm`])
103
+ restrictMode(path, 0o600);
77
104
  this._migrate();
78
105
  }
106
+ /**
107
+ * Switching a database to WAL needs it to itself, and SQLite answers
108
+ * SQLITE_BUSY at once (no busy handler) while other processes have it
109
+ * open, e.g. several opening an old database together: retry for a while.
110
+ */
111
+ _enableWal() {
112
+ const deadline = Date.now() + 5_000;
113
+ for (;;) {
114
+ try {
115
+ this._db.pragma('journal_mode = WAL');
116
+ return;
117
+ }
118
+ catch (error) {
119
+ if (error.code !== 'SQLITE_BUSY' || Date.now() > deadline)
120
+ throw error;
121
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10 + Math.floor(Math.random() * 20));
122
+ }
123
+ }
124
+ }
125
+ /** The underlying database; the console's auth module keeps its own tables in it. */
126
+ get database() {
127
+ return this._db;
128
+ }
79
129
  close() {
130
+ this._statements.clear();
80
131
  this._db.close();
81
132
  }
133
+ _count(sql, ...params) {
134
+ return this._stmt(sql).get(...params).count;
135
+ }
136
+ /** A prepared statement, cached by its SQL (hot paths run the same few statements on every request). */
137
+ _stmt(sql) {
138
+ let statement = this._statements.get(sql);
139
+ if (!statement) {
140
+ if (this._statements.size >= MAX_CACHED_STATEMENTS)
141
+ this._statements.clear();
142
+ statement = this._db.prepare(sql);
143
+ this._statements.set(sql, statement);
144
+ }
145
+ return statement;
146
+ }
147
+ /**
148
+ * Several processes (the gateway, `antseed gateway key …`) may open an old
149
+ * database at once: the version is re-read inside an IMMEDIATE transaction,
150
+ * which holds the write lock, so only the first one applies a migration.
151
+ */
82
152
  _migrate() {
83
- const version = this._db.pragma('user_version', { simple: true });
84
- for (let index = version; index < MIGRATIONS.length; index += 1) {
85
- this._db.transaction(() => {
153
+ if (this._db.pragma('user_version', { simple: true }) >= MIGRATIONS.length)
154
+ return;
155
+ this._db.transaction(() => {
156
+ const version = this._db.pragma('user_version', { simple: true });
157
+ for (let index = version; index < MIGRATIONS.length; index += 1) {
86
158
  this._db.exec(MIGRATIONS[index]);
87
159
  this._db.pragma(`user_version = ${index + 1}`);
88
- })();
89
- }
160
+ }
161
+ }).immediate();
162
+ }
163
+ // ── Workspaces ──────────────────────────────────────────────────────────
164
+ getWorkspace(id) {
165
+ const row = this._stmt('SELECT * FROM workspaces WHERE id = ? AND deleted_at IS NULL').get(id);
166
+ return row ? toWorkspace(row) : null;
167
+ }
168
+ listWorkspaces() {
169
+ const rows = this._stmt('SELECT * FROM workspaces WHERE deleted_at IS NULL ORDER BY is_default DESC, created_at, id').all();
170
+ return rows.map(toWorkspace);
171
+ }
172
+ defaultWorkspace() {
173
+ return this.getWorkspace(DEFAULT_WORKSPACE_ID);
174
+ }
175
+ workspaceForKey(keyId) {
176
+ const row = this._stmt('SELECT workspace_id FROM api_keys WHERE id = ?').get(keyId);
177
+ return row?.workspace_id ? this.getWorkspace(row.workspace_id) : null;
178
+ }
179
+ createWorkspace(input) {
180
+ const id = newId('ws');
181
+ const limits = input.limits ?? NO_BUDGET_LIMITS;
182
+ this._stmt(`
183
+ INSERT INTO workspaces (id, name, buyer_identity, is_default, wallet_address, daily_limit_usdc, weekly_limit_usdc, monthly_limit_usdc, total_limit_usdc, routing_policy, org_routing_policy, created_at)
184
+ VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?)
185
+ `).run(id, input.name, input.buyerIdentity, input.walletAddress ?? null, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(input.routingPolicy), policyJson(input.orgRoutingPolicy), this._now());
186
+ return this.getWorkspace(id);
187
+ }
188
+ /** Caches the wallet address of every workspace paying with this identity. */
189
+ setWalletAddress(buyerIdentity, address) {
190
+ this._stmt('UPDATE workspaces SET wallet_address = ? WHERE buyer_identity = ?').run(address, buyerIdentity);
191
+ }
192
+ updateWorkspace(id, patch) {
193
+ const current = this.getWorkspace(id);
194
+ if (!current)
195
+ throw new Error(`Unknown workspace "${id}".`);
196
+ const limits = { ...current.limits, ...(patch.limits ?? {}) };
197
+ const policy = patch.routingPolicy === undefined ? current.routingPolicy : patch.routingPolicy;
198
+ const orgPolicy = patch.orgRoutingPolicy === undefined ? current.orgRoutingPolicy : patch.orgRoutingPolicy;
199
+ this._stmt(`
200
+ UPDATE workspaces SET name = ?, daily_limit_usdc = ?, weekly_limit_usdc = ?, monthly_limit_usdc = ?, total_limit_usdc = ?, routing_policy = ?,
201
+ org_routing_policy = ?
202
+ WHERE id = ?
203
+ `).run(patch.name ?? current.name, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(policy), policyJson(orgPolicy), id);
204
+ return this.getWorkspace(id);
205
+ }
206
+ /** Soft delete: history keeps pointing at it. The default workspace cannot be deleted. */
207
+ deleteWorkspace(id) {
208
+ if (id === DEFAULT_WORKSPACE_ID)
209
+ throw new Error('The Default workspace cannot be deleted.');
210
+ this._db.transaction(() => {
211
+ this._stmt('UPDATE workspaces SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL').run(this._now(), id);
212
+ this._stmt('DELETE FROM workspace_members WHERE workspace_id = ?').run(id);
213
+ })();
214
+ }
215
+ /**
216
+ * The workspace that pays with this identity, created (named after the
217
+ * identity) when there is none. Keeps `antseed gateway key create
218
+ * --identity/--new-identity` working.
219
+ */
220
+ ensureWorkspaceForIdentity(buyerIdentity) {
221
+ if (buyerIdentity === DEFAULT_BUYER_IDENTITY)
222
+ return this.defaultWorkspace();
223
+ const row = this._stmt('SELECT * FROM workspaces WHERE buyer_identity = ? AND deleted_at IS NULL ORDER BY created_at, id LIMIT 1')
224
+ .get(buyerIdentity);
225
+ return row ? toWorkspace(row) : this.createWorkspace({ name: buyerIdentity, buyerIdentity });
226
+ }
227
+ countWorkspaceKeys(workspaceId, activeOnly = true) {
228
+ return this._count(`SELECT COUNT(*) AS count FROM api_keys WHERE workspace_id = ?${activeOnly ? " AND status = 'active'" : ''}`, workspaceId);
229
+ }
230
+ countWorkspaceMembers(workspaceId) {
231
+ return this._count('SELECT COUNT(*) AS count FROM workspace_members WHERE workspace_id = ?', workspaceId);
232
+ }
233
+ workspaceMembers(workspaceId) {
234
+ const rows = this._stmt(`
235
+ SELECT m.*, wm.role AS ws_role FROM workspace_members wm JOIN members m ON m.id = wm.member_id
236
+ WHERE wm.workspace_id = ? ORDER BY m.created_at, m.id
237
+ `).all(workspaceId);
238
+ return rows.map((row) => ({ member: toMember(row), role: row.ws_role }));
239
+ }
240
+ setWorkspaceMember(workspaceId, memberId, role) {
241
+ this._stmt(`
242
+ INSERT INTO workspace_members (workspace_id, member_id, role) VALUES (?, ?, ?)
243
+ ON CONFLICT (workspace_id, member_id) DO UPDATE SET role = excluded.role
244
+ `).run(workspaceId, memberId, role);
245
+ }
246
+ removeWorkspaceMember(workspaceId, memberId) {
247
+ return this._stmt('DELETE FROM workspace_members WHERE workspace_id = ? AND member_id = ?').run(workspaceId, memberId).changes > 0;
248
+ }
249
+ // ── Members ─────────────────────────────────────────────────────────────
250
+ getMember(id) {
251
+ const row = this._stmt('SELECT * FROM members WHERE id = ?').get(id);
252
+ return row ? toMember(row) : null;
253
+ }
254
+ findMemberByEmail(email) {
255
+ const row = this._stmt('SELECT * FROM members WHERE lower(email) = lower(?)').get(email.trim());
256
+ return row ? toMember(row) : null;
257
+ }
258
+ listMembers() {
259
+ return this._stmt('SELECT * FROM members ORDER BY created_at, id').all().map(toMember);
260
+ }
261
+ /** Workspaces (not deleted) the member belongs to, with their role. */
262
+ memberWorkspaceRoles(memberId) {
263
+ const rows = this._stmt(`
264
+ SELECT wm.workspace_id, wm.role FROM workspace_members wm JOIN workspaces w ON w.id = wm.workspace_id
265
+ WHERE wm.member_id = ? AND w.deleted_at IS NULL
266
+ `).all(memberId);
267
+ return new Map(rows.map((row) => [row.workspace_id, row.role]));
268
+ }
269
+ /** True once an active owner exists. */
270
+ isSetupComplete() {
271
+ return this._stmt("SELECT 1 FROM members WHERE org_role = 'owner' AND status = 'active' LIMIT 1").get() !== undefined;
272
+ }
273
+ countActiveOwners() {
274
+ return this._count("SELECT COUNT(*) AS count FROM members WHERE org_role = 'owner' AND status = 'active'");
275
+ }
276
+ /** The first owner, created when the console is claimed; admin of the Default workspace. */
277
+ createOwner(input) {
278
+ return this._db.transaction(() => {
279
+ if (this.isSetupComplete())
280
+ throw new Error('The console already has an owner.');
281
+ const member = this._insertMember({ label: input.label, email: input.email, orgRole: 'owner', status: 'active' });
282
+ this.setWorkspaceMember(DEFAULT_WORKSPACE_ID, member.id, 'admin');
283
+ return member;
284
+ })();
285
+ }
286
+ _insertMember(input) {
287
+ const id = newId('mem');
288
+ this._stmt('INSERT INTO members (id, label, email, org_role, status, created_at) VALUES (?, ?, ?, ?, ?, ?)')
289
+ .run(id, input.label, input.email?.trim() || null, input.orgRole, input.status, this._now());
290
+ return this.getMember(id);
291
+ }
292
+ updateMember(id, patch) {
293
+ const current = this.getMember(id);
294
+ if (!current)
295
+ throw new Error(`Unknown member "${id}".`);
296
+ const limits = { ...current.limits, ...(patch.limits ?? {}) };
297
+ this._stmt(`
298
+ UPDATE members SET label = ?, email = ?, org_role = ?, daily_limit_usdc = ?, weekly_limit_usdc = ?, monthly_limit_usdc = ?,
299
+ total_limit_usdc = ?, routing_policy = ?, max_keys = ? WHERE id = ?
300
+ `).run(patch.label ?? current.label, patch.email === undefined ? current.email : (patch.email?.trim() || null), patch.orgRole ?? current.orgRole, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(patch.routingPolicy === undefined ? current.routingPolicy : patch.routingPolicy), patch.maxKeys === undefined ? current.maxKeys : patch.maxKeys, id);
301
+ return this.getMember(id);
302
+ }
303
+ setMemberStatus(id, status) {
304
+ const result = this._stmt('UPDATE members SET status = ? WHERE id = ?').run(status, id);
305
+ if (result.changes === 0)
306
+ throw new Error(`Unknown member "${id}".`);
307
+ return this.getMember(id);
308
+ }
309
+ // ── Invites ─────────────────────────────────────────────────────────────
310
+ /**
311
+ * Creates the invited member (status 'invited', already in its workspaces)
312
+ * and a single-use link token, of which only the SHA-256 is stored.
313
+ */
314
+ createInvite(input) {
315
+ const token = randomBytes(32).toString('base64url');
316
+ const invite = this._db.transaction(() => {
317
+ const member = this._insertMember({ label: input.label, email: input.email, orgRole: input.orgRole, status: 'invited' });
318
+ for (const entry of input.workspaces)
319
+ this.setWorkspaceMember(entry.workspaceId, member.id, entry.role);
320
+ const id = newId('inv');
321
+ this._stmt(`
322
+ INSERT INTO invites (id, token_hash, member_id, label, email, org_role, workspace_roles, created_by, expires_at, created_at)
323
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
324
+ `).run(id, hashApiKey(token), member.id, input.label, member.email, input.orgRole, JSON.stringify(input.workspaces), input.createdBy, input.expiresAt, this._now());
325
+ return this.getInvite(id);
326
+ })();
327
+ return { invite, token };
328
+ }
329
+ getInvite(id) {
330
+ const row = this._stmt('SELECT * FROM invites WHERE id = ?').get(id);
331
+ return row ? toInvite(row) : null;
332
+ }
333
+ /** Invites not yet used (expired ones included, so they can be cleaned up). */
334
+ listInvites() {
335
+ return this._stmt('SELECT * FROM invites WHERE used_at IS NULL ORDER BY created_at DESC').all().map(toInvite);
336
+ }
337
+ /** Deletes an unused invite and the member it created, if that member never joined. */
338
+ deleteInvite(id) {
339
+ return this._db.transaction(() => {
340
+ const invite = this.getInvite(id);
341
+ if (!invite || invite.usedAt !== null)
342
+ return false;
343
+ this._stmt('DELETE FROM invites WHERE id = ?').run(id);
344
+ const member = this.getMember(invite.memberId);
345
+ if (member?.status === 'invited') {
346
+ this._stmt('DELETE FROM workspace_members WHERE member_id = ?').run(member.id);
347
+ this._stmt('DELETE FROM members WHERE id = ?').run(member.id);
348
+ }
349
+ return true;
350
+ })();
351
+ }
352
+ /** The invite behind a token hash while it is usable, without consuming it. */
353
+ peekInvite(tokenHash, now) {
354
+ const row = this._stmt('SELECT * FROM invites WHERE token_hash = ? AND used_at IS NULL AND expires_at > ?').get(tokenHash, now);
355
+ if (!row)
356
+ return null;
357
+ const member = this.getMember(row.member_id);
358
+ return member && member.status === 'invited' ? { invite: toInvite(row), member } : null;
359
+ }
360
+ /** Token hash of a member's newest live (unused, unexpired) invite, for SSO activation by email. */
361
+ liveInviteHash(memberId, now) {
362
+ const row = this._stmt('SELECT token_hash FROM invites WHERE member_id = ? AND used_at IS NULL AND expires_at > ? ORDER BY created_at DESC LIMIT 1')
363
+ .get(memberId, now);
364
+ return row?.token_hash ?? null;
365
+ }
366
+ /** Why an invite token is not usable: already used, expired, or unknown (also for a live one). */
367
+ inviteState(tokenHash, now) {
368
+ const row = this._stmt('SELECT used_at, expires_at FROM invites WHERE token_hash = ?').get(tokenHash);
369
+ if (!row)
370
+ return 'unknown';
371
+ if (row.used_at !== null)
372
+ return 'used';
373
+ return row.expires_at <= now ? 'expired' : 'unknown';
374
+ }
375
+ /** Marks the invite used and its member active; null when the token is unknown, used or expired. */
376
+ consumeInvite(tokenHash, now) {
377
+ return this._db.transaction(() => {
378
+ const found = this.peekInvite(tokenHash, now);
379
+ if (!found)
380
+ return null;
381
+ this._stmt('UPDATE invites SET used_at = ? WHERE id = ?').run(now, found.invite.id);
382
+ return this.setMemberStatus(found.member.id, 'active');
383
+ })();
90
384
  }
91
385
  // ── Keys ────────────────────────────────────────────────────────────────
386
+ /**
387
+ * Creates a key in `workspaceId`, or, for callers that only name an
388
+ * identity (the CLI), in the workspace paying with that identity.
389
+ */
92
390
  createKey(input) {
391
+ let workspace;
392
+ if (input.workspaceId) {
393
+ const found = this.getWorkspace(input.workspaceId);
394
+ if (!found)
395
+ throw new Error(`Unknown workspace "${input.workspaceId}".`);
396
+ if (input.buyerIdentity && input.buyerIdentity !== found.buyerIdentity) {
397
+ throw new Error(`Workspace "${found.name}" pays with identity "${found.buyerIdentity}", not "${input.buyerIdentity}".`);
398
+ }
399
+ workspace = found;
400
+ }
401
+ else {
402
+ workspace = this.ensureWorkspaceForIdentity(input.buyerIdentity ?? DEFAULT_BUYER_IDENTITY);
403
+ }
93
404
  const generated = generateApiKey();
94
405
  const id = newKeyId();
95
- this._db.prepare(`
96
- INSERT INTO api_keys (id, label, key_hash, key_hint, buyer_identity, source, daily_limit_usdc, monthly_limit_usdc, total_limit_usdc, topup_enabled, expires_at, created_at)
97
- VALUES (?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?)
98
- `).run(id, input.label, generated.hash, generated.hint, input.buyerIdentity, input.limits.daily, input.limits.monthly, input.limits.total, input.topupEnabled ? 1 : 0, input.expiresAt, this._now());
406
+ const limits = fullLimits(input.limits);
407
+ const ownerLimits = input.ownerLimits ?? NO_BUDGET_LIMITS;
408
+ this._stmt(`
409
+ INSERT INTO api_keys (id, label, key_hash, key_hint, buyer_identity, workspace_id, owner_member_id, source,
410
+ daily_limit_usdc, weekly_limit_usdc, monthly_limit_usdc, total_limit_usdc, routing_policy,
411
+ owner_daily_limit_usdc, owner_weekly_limit_usdc, owner_monthly_limit_usdc, owner_total_limit_usdc, owner_routing_policy,
412
+ topup_enabled, expires_at, created_at)
413
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
414
+ `).run(id, input.label, generated.hash, generated.hint, workspace.buyerIdentity, workspace.id, input.ownerMemberId ?? null, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(input.routingPolicy), ownerLimits.daily, ownerLimits.weekly, ownerLimits.monthly, ownerLimits.total, policyJson(input.ownerRoutingPolicy), input.topupEnabled ? 1 : 0, input.expiresAt, this._now());
99
415
  return { key: this.getKey(id), secret: generated.secret };
100
416
  }
101
417
  /**
@@ -105,17 +421,17 @@ export class GatewayStore {
105
421
  */
106
422
  syncEnvironmentKey(secret) {
107
423
  const hash = hashApiKey(secret);
108
- const existing = this._db.prepare("SELECT id FROM api_keys WHERE source = 'tunnel-env'").get();
424
+ const existing = this._stmt("SELECT id FROM api_keys WHERE source = 'tunnel-env'").get();
109
425
  if (existing) {
110
- this._db.prepare("UPDATE api_keys SET key_hash = ?, key_hint = ?, status = 'active', revoked_at = NULL WHERE id = ?")
426
+ this._stmt("UPDATE api_keys SET key_hash = ?, key_hint = ?, status = 'active', revoked_at = NULL WHERE id = ?")
111
427
  .run(hash, apiKeyHint(secret), existing.id);
112
428
  return this.getKey(existing.id);
113
429
  }
114
430
  const id = newKeyId();
115
- this._db.prepare(`
116
- INSERT INTO api_keys (id, label, key_hash, key_hint, buyer_identity, source, created_at)
117
- VALUES (?, 'Tunnel key', ?, ?, ?, 'tunnel-env', ?)
118
- `).run(id, hash, apiKeyHint(secret), DEFAULT_BUYER_IDENTITY, this._now());
431
+ this._stmt(`
432
+ INSERT INTO api_keys (id, label, key_hash, key_hint, buyer_identity, workspace_id, source, created_at)
433
+ VALUES (?, 'Tunnel key', ?, ?, ?, ?, 'tunnel-env', ?)
434
+ `).run(id, hash, apiKeyHint(secret), DEFAULT_BUYER_IDENTITY, DEFAULT_WORKSPACE_ID, this._now());
119
435
  return this.getKey(id);
120
436
  }
121
437
  /**
@@ -124,111 +440,829 @@ export class GatewayStore {
124
440
  * revoked key, or null when there was no active one.
125
441
  */
126
442
  retireEnvironmentKey() {
127
- const existing = this._db.prepare("SELECT id FROM api_keys WHERE source = 'tunnel-env' AND status = 'active'").get();
443
+ const existing = this._stmt("SELECT id FROM api_keys WHERE source = 'tunnel-env' AND status = 'active'").get();
128
444
  return existing ? this.revokeKey(existing.id) : null;
129
445
  }
130
446
  findKeyBySecret(secret) {
131
- const row = this._db.prepare('SELECT * FROM api_keys WHERE key_hash = ?').get(hashApiKey(secret));
447
+ const row = this._stmt('SELECT * FROM api_keys WHERE key_hash = ?').get(hashApiKey(secret));
132
448
  return row ? toKey(row) : null;
133
449
  }
134
450
  getKey(id) {
135
- const row = this._db.prepare('SELECT * FROM api_keys WHERE id = ?').get(id);
451
+ const row = this._stmt('SELECT * FROM api_keys WHERE id = ?').get(id);
136
452
  return row ? toKey(row) : null;
137
453
  }
138
- listKeys() {
139
- const rows = this._db.prepare('SELECT * FROM api_keys ORDER BY created_at, id').all();
140
- return rows.map(toKey);
454
+ listKeys(filter = {}) {
455
+ const where = [];
456
+ const params = [];
457
+ if (filter.workspaceId) {
458
+ where.push('workspace_id = ?');
459
+ params.push(filter.workspaceId);
460
+ }
461
+ if (filter.ownerMemberId) {
462
+ where.push('owner_member_id = ?');
463
+ params.push(filter.ownerMemberId);
464
+ }
465
+ const sql = `SELECT * FROM api_keys ${whereClause(where)} ORDER BY created_at, id`;
466
+ return this._stmt(sql).all(...params).map(toKey);
141
467
  }
142
468
  countActiveKeys() {
143
- return this._db.prepare("SELECT COUNT(*) AS count FROM api_keys WHERE status = 'active'").get().count;
469
+ return this._count("SELECT COUNT(*) AS count FROM api_keys WHERE status = 'active'");
470
+ }
471
+ countActiveKeysForMember(memberId) {
472
+ return this._count("SELECT COUNT(*) AS count FROM api_keys WHERE status = 'active' AND owner_member_id = ?", memberId);
144
473
  }
145
474
  setLimits(id, limits) {
146
475
  const key = this.getKey(id);
147
476
  if (!key)
148
477
  throw new Error(`Unknown key "${id}".`);
149
478
  const next = { ...key.limits, ...limits };
150
- this._db.prepare('UPDATE api_keys SET daily_limit_usdc = ?, monthly_limit_usdc = ?, total_limit_usdc = ? WHERE id = ?')
151
- .run(next.daily, next.monthly, next.total, id);
479
+ this._stmt('UPDATE api_keys SET daily_limit_usdc = ?, weekly_limit_usdc = ?, monthly_limit_usdc = ?, total_limit_usdc = ? WHERE id = ?')
480
+ .run(next.daily, next.weekly ?? null, next.monthly, next.total, id);
152
481
  return this.getKey(id);
153
482
  }
154
483
  setTopupEnabled(id, enabled) {
155
- const result = this._db.prepare('UPDATE api_keys SET topup_enabled = ? WHERE id = ?').run(enabled ? 1 : 0, id);
484
+ const result = this._stmt('UPDATE api_keys SET topup_enabled = ? WHERE id = ?').run(enabled ? 1 : 0, id);
156
485
  if (result.changes === 0)
157
486
  throw new Error(`Unknown key "${id}".`);
158
487
  return this.getKey(id);
159
488
  }
489
+ updateKey(id, patch) {
490
+ const key = this.getKey(id);
491
+ if (!key)
492
+ throw new Error(`Unknown key "${id}".`);
493
+ const limits = { ...key.limits, ...(patch.limits ?? {}) };
494
+ const ownerLimits = { ...key.ownerLimits, ...(patch.ownerLimits ?? {}) };
495
+ this._stmt(`
496
+ UPDATE api_keys SET label = ?, daily_limit_usdc = ?, weekly_limit_usdc = ?, monthly_limit_usdc = ?, total_limit_usdc = ?,
497
+ routing_policy = ?, owner_daily_limit_usdc = ?, owner_weekly_limit_usdc = ?, owner_monthly_limit_usdc = ?, owner_total_limit_usdc = ?,
498
+ owner_routing_policy = ?, topup_enabled = ?, expires_at = ?, owner_member_id = ? WHERE id = ?
499
+ `).run(patch.label ?? key.label, limits.daily, limits.weekly, limits.monthly, limits.total, policyJson(patch.routingPolicy === undefined ? key.routingPolicy : patch.routingPolicy), ownerLimits.daily, ownerLimits.weekly, ownerLimits.monthly, ownerLimits.total, policyJson(patch.ownerRoutingPolicy === undefined ? key.ownerRoutingPolicy : patch.ownerRoutingPolicy), (patch.topupEnabled ?? key.topupEnabled) ? 1 : 0, patch.expiresAt === undefined ? key.expiresAt : patch.expiresAt, patch.ownerMemberId === undefined ? key.ownerMemberId : patch.ownerMemberId, id);
500
+ return this.getKey(id);
501
+ }
502
+ /** New secret for the same key id; usage history, limits and policy stay. */
503
+ rotateKey(id) {
504
+ const generated = generateApiKey();
505
+ const result = this._stmt("UPDATE api_keys SET key_hash = ?, key_hint = ? WHERE id = ? AND status = 'active'")
506
+ .run(generated.hash, generated.hint, id);
507
+ if (result.changes === 0)
508
+ throw new Error(this.getKey(id) ? `Key "${id}" is revoked.` : `Unknown key "${id}".`);
509
+ return { key: this.getKey(id), secret: generated.secret };
510
+ }
160
511
  revokeKey(id) {
161
- const result = this._db.prepare("UPDATE api_keys SET status = 'revoked', revoked_at = ? WHERE id = ? AND status = 'active'")
512
+ const result = this._stmt("UPDATE api_keys SET status = 'revoked', revoked_at = ? WHERE id = ? AND status = 'active'")
162
513
  .run(this._now(), id);
163
514
  if (result.changes === 0 && !this.getKey(id))
164
515
  throw new Error(`Unknown key "${id}".`);
165
516
  return this.getKey(id);
166
517
  }
518
+ /** Revokes every active key a member owns; returns their ids. */
519
+ revokeMemberKeys(memberId) {
520
+ const ids = this._stmt("SELECT id FROM api_keys WHERE owner_member_id = ? AND status = 'active'").all(memberId)
521
+ .map((row) => row.id);
522
+ for (const id of ids)
523
+ this.revokeKey(id);
524
+ return ids;
525
+ }
526
+ /** Records use at most once a minute per key (per process), so requests do not each write. */
167
527
  touchKey(id) {
168
- this._db.prepare('UPDATE api_keys SET last_used_at = ? WHERE id = ?').run(this._now(), id);
528
+ const now = this._now();
529
+ if (!this._shouldTouch(`key:${id}`, now))
530
+ return;
531
+ this._stmt('UPDATE api_keys SET last_used_at = ? WHERE id = ?').run(now, id);
532
+ }
533
+ _shouldTouch(id, now) {
534
+ const last = this._touched.get(id);
535
+ if (last !== undefined && now - last < TOUCH_INTERVAL_MS && now >= last)
536
+ return false;
537
+ if (this._touched.size >= MAX_TRACKED_TOUCHES)
538
+ this._touched.clear();
539
+ this._touched.set(id, now);
540
+ return true;
169
541
  }
170
542
  // ── Requests ────────────────────────────────────────────────────────────
171
543
  startRequest(input) {
172
- this._db.prepare(`
173
- INSERT INTO gateway_requests (tag, key_id, buyer_identity, method, path, model, started_at)
174
- VALUES (?, ?, ?, ?, ?, ?, ?)
175
- `).run(input.tag, input.keyId, input.buyerIdentity, input.method, input.path, input.model, input.startedAt);
544
+ this._stmt(`
545
+ INSERT INTO gateway_requests (tag, key_id, buyer_identity, method, path, model, started_at, workspace_id, member_id, end_user)
546
+ VALUES (?, ?, ?, ?, ?, ?, ?,
547
+ COALESCE(?, (SELECT workspace_id FROM api_keys WHERE id = ?)),
548
+ COALESCE(?, (SELECT owner_member_id FROM api_keys WHERE id = ?)),
549
+ ?)
550
+ `).run(input.tag, input.keyId, input.buyerIdentity, input.method, input.path, input.model, input.startedAt, input.workspaceId ?? null, input.keyId, input.memberId ?? null, input.keyId, input.endUser ?? null);
176
551
  }
552
+ /** `error` is kept for failed requests (code ≤ 100, message ≤ 500 characters). */
177
553
  finishRequest(tag, result) {
178
- this._db.prepare('UPDATE gateway_requests SET status = ?, buyer_request_id = ?, finished_at = ? WHERE tag = ?')
179
- .run(result.status, result.buyerRequestId, this._now(), tag);
554
+ this._stmt(`
555
+ UPDATE gateway_requests SET status = ?, buyer_request_id = ?, finished_at = ?, error_code = ?, error_message = ? WHERE tag = ?
556
+ `).run(result.status, result.buyerRequestId, this._now(), result.error?.code?.slice(0, MAX_ERROR_CODE) || null, result.error?.message?.slice(0, MAX_ERROR_MESSAGE) || null, tag);
557
+ }
558
+ /** Seller, latency and end user of a request; null fields leave what is stored. */
559
+ recordRequestOutcome(tag, outcome) {
560
+ this._stmt(`
561
+ UPDATE gateway_requests SET seller_peer_id = COALESCE(?, seller_peer_id), latency_ms = COALESCE(?, latency_ms),
562
+ end_user = COALESCE(?, end_user), served_free = MAX(served_free, ?) WHERE tag = ?
563
+ `).run(outcome.sellerPeerId ?? null, outcome.latencyMs ?? null, outcome.endUser ?? null, outcome.servedFree ? 1 : 0, tag);
564
+ }
565
+ /**
566
+ * The token counts the response itself reported, read by the gateway as it
567
+ * streamed past. Usage falls back to them while (or if ever) the request
568
+ * has no spend with token counts in the ledger, so they never add up twice.
569
+ */
570
+ recordResponseUsage(tag, usage) {
571
+ this._stmt(`
572
+ UPDATE gateway_requests SET usage_input_tokens = ?, usage_cached_input_tokens = ?, usage_output_tokens = ? WHERE tag = ?
573
+ `).run(usage.inputTokens, usage.cachedInputTokens, usage.outputTokens, tag);
574
+ }
575
+ /** Only called while content logging is on. */
576
+ recordRequestContent(tag, content) {
577
+ this._stmt(`
578
+ UPDATE gateway_requests SET request_body = COALESCE(?, request_body), response_body = COALESCE(?, response_body) WHERE tag = ?
579
+ `).run(content.requestBody ?? null, content.responseBody ?? null, tag);
580
+ }
581
+ /** Forgets every stored request and response body; for when content logging is turned off. */
582
+ clearRequestContent() {
583
+ return this._stmt(`
584
+ UPDATE gateway_requests SET request_body = NULL, response_body = NULL WHERE request_body IS NOT NULL OR response_body IS NOT NULL
585
+ `).run().changes;
180
586
  }
587
+ /**
588
+ * Drops request-log rows started before `before`, `chunkSize` rows per
589
+ * transaction so a large backlog never holds the write lock for long. Their
590
+ * request counts move into `request_rollups` (per hour, key, workspace,
591
+ * member, model, seller and end user) so usage reports keep counting them;
592
+ * spend and tokens live in the ledger, which is never pruned. Pruned
593
+ * periods are therefore counted at hour granularity. Yields to the event
594
+ * loop between chunks, so requests keep flowing while a backlog drains.
595
+ */
596
+ async pruneRequests(before, chunkSize = 5_000) {
597
+ const select = this._stmt('SELECT rowid FROM gateway_requests WHERE started_at < ? ORDER BY started_at LIMIT ?').pluck();
598
+ const rollup = this._stmt(`
599
+ INSERT INTO request_rollups (hour_start, key_id, workspace_id, member_id, model, seller_peer_id, end_user, requests, failed_requests)
600
+ SELECT (r.started_at / ${HOUR_MS}) * ${HOUR_MS}, r.key_id, r.workspace_id, r.member_id, r.model, r.seller_peer_id, r.end_user,
601
+ COUNT(*), SUM(${FAILED_SQL})
602
+ FROM gateway_requests r WHERE r.rowid IN (SELECT value FROM json_each(?))
603
+ GROUP BY 1, 2, 3, 4, 5, 6, 7
604
+ `);
605
+ const remove = this._stmt('DELETE FROM gateway_requests WHERE rowid IN (SELECT value FROM json_each(?))');
606
+ const pruneChunk = this._db.transaction(() => {
607
+ const ids = select.all(before, chunkSize);
608
+ if (ids.length === 0)
609
+ return 0;
610
+ const json = JSON.stringify(ids);
611
+ rollup.run(json);
612
+ remove.run(json);
613
+ return ids.length;
614
+ });
615
+ let removed = 0;
616
+ for (;;) {
617
+ const count = pruneChunk.immediate();
618
+ removed += count;
619
+ if (count < chunkSize)
620
+ return removed;
621
+ await new Promise((resolve) => setImmediate(resolve));
622
+ }
623
+ }
624
+ /** Who a request was attributed to when it started. */
181
625
  findRequest(tag) {
182
- const row = this._db.prepare('SELECT key_id, buyer_identity FROM gateway_requests WHERE tag = ?').get(tag);
183
- return row ? { keyId: row.key_id, buyerIdentity: row.buyer_identity } : null;
626
+ const row = this._stmt('SELECT key_id, buyer_identity, workspace_id, member_id, model, end_user FROM gateway_requests WHERE tag = ?').get(tag);
627
+ return row
628
+ ? { keyId: row.key_id, buyerIdentity: row.buyer_identity, workspaceId: row.workspace_id, memberId: row.member_id, model: row.model, endUser: row.end_user }
629
+ : null;
630
+ }
631
+ /** Requests per seller since a time, with the median latency this gateway measured. */
632
+ peerStats(sinceMs) {
633
+ const rows = this._stmt(`
634
+ SELECT seller_peer_id AS peer, latency_ms AS latency FROM gateway_requests
635
+ WHERE started_at >= ? AND seller_peer_id IS NOT NULL
636
+ `).all(sinceMs);
637
+ const byPeer = new Map();
638
+ for (const row of rows) {
639
+ const entry = byPeer.get(row.peer) ?? { requests: 0, latencies: [] };
640
+ entry.requests += 1;
641
+ if (row.latency !== null)
642
+ entry.latencies.push(row.latency);
643
+ byPeer.set(row.peer, entry);
644
+ }
645
+ return [...byPeer].map(([peerId, entry]) => {
646
+ const sorted = entry.latencies.sort((a, b) => a - b);
647
+ return { peerId, requests: entry.requests, latencyMsP50: sorted.length ? sorted[Math.floor((sorted.length - 1) / 2)] : null };
648
+ });
649
+ }
650
+ /** Newest first, ordered by (startedAt, tag); pass the last row as `before` for the next page. Bodies are not read. */
651
+ listRequests(filter) {
652
+ const { where, params } = scopeSql(filter, REQUEST_COLUMNS);
653
+ if (filter.status === 'ok')
654
+ where.push('r.status IS NOT NULL AND r.status < 400');
655
+ else if (filter.status === 'error')
656
+ where.push(FAILED_SQL);
657
+ else if (typeof filter.status === 'number') {
658
+ where.push('r.status = ?');
659
+ params.push(filter.status);
660
+ }
661
+ const q = filter.q?.trim();
662
+ if (q) {
663
+ const pattern = `%${escapeLike(q)}%`;
664
+ const columns = ['r.model', 'k.label', 'r.end_user', 'r.tag', 'r.seller_peer_id', 'r.path', 'r.error_code', 'r.error_message'];
665
+ where.push(`(${columns.map((c) => `${c} LIKE ? ESCAPE '\\'`).join(' OR ')})`);
666
+ params.push(...columns.map(() => pattern));
667
+ }
668
+ if (filter.before) {
669
+ where.push('(r.started_at < ? OR (r.started_at = ? AND r.tag < ?))');
670
+ params.push(filter.before.startedAt, filter.before.startedAt, filter.before.tag);
671
+ }
672
+ const limit = Math.max(1, Math.min(filter.limit ?? 50, 100_000));
673
+ return this._requestRows(where, params, limit, false);
674
+ }
675
+ /** One request with its bodies; `keyIds` limits it to what the caller may see. */
676
+ getRequest(tag, keyIds = null) {
677
+ const { where, params } = scopeSql({ keyIds }, REQUEST_COLUMNS);
678
+ where.push('r.tag = ?');
679
+ params.push(tag);
680
+ return this._requestRows(where, params, 1, true)[0] ?? null;
681
+ }
682
+ /** The page of requests first, then one grouped ledger read for just those tags. */
683
+ _requestRows(where, params, limit, withBodies) {
684
+ const rows = this._stmt(`
685
+ WITH page AS (
686
+ SELECT r.tag, r.started_at, r.finished_at, r.key_id, k.label AS key_label, COALESCE(r.workspace_id, k.workspace_id) AS ws_id,
687
+ r.member_id, r.end_user, r.method, r.path, r.model, r.status, r.seller_peer_id, r.latency_ms, r.error_code, r.error_message,
688
+ r.served_free, r.usage_input_tokens, r.usage_cached_input_tokens, r.usage_output_tokens
689
+ ${withBodies ? ', r.request_body, r.response_body' : ''}
690
+ FROM gateway_requests r LEFT JOIN api_keys k ON k.id = r.key_id
691
+ ${whereClause(where)}
692
+ ORDER BY r.started_at DESC, r.tag DESC LIMIT ?
693
+ ),
694
+ spend AS (
695
+ SELECT l.request_tag AS tag, -SUM(l.amount_usdc) AS spent, SUM(l.input_tokens) AS in_tokens,
696
+ SUM(l.cached_input_tokens) AS cached_tokens, SUM(l.output_tokens) AS out_tokens
697
+ FROM ledger_entries l WHERE l.kind = 'spend' AND l.request_tag IN (SELECT tag FROM page) GROUP BY l.request_tag
698
+ )
699
+ SELECT page.*, spend.spent, COALESCE(spend.in_tokens, 0) AS in_tokens, COALESCE(spend.cached_tokens, 0) AS cached_tokens,
700
+ COALESCE(spend.out_tokens, 0) AS out_tokens
701
+ FROM page LEFT JOIN spend ON spend.tag = page.tag
702
+ ORDER BY page.started_at DESC, page.tag DESC
703
+ `).all(...params, limit);
704
+ return rows.map((row) => {
705
+ // Ledger tokens when the spend reported any, else what the response said.
706
+ const ledgerTokens = row['in_tokens'] + row['cached_tokens'] + row['out_tokens'] > 0;
707
+ const status = row['status'];
708
+ const servedFree = row['served_free'] === 1;
709
+ return {
710
+ tag: row['tag'],
711
+ startedAt: row['started_at'],
712
+ finishedAt: row['finished_at'],
713
+ keyId: row['key_id'],
714
+ keyLabel: row['key_label'] ?? row['key_id'],
715
+ workspaceId: row['ws_id'] ?? DEFAULT_WORKSPACE_ID,
716
+ memberId: row['member_id'],
717
+ endUser: row['end_user'],
718
+ method: row['method'],
719
+ path: row['path'],
720
+ model: row['model'],
721
+ status: row['status'],
722
+ sellerPeerId: row['seller_peer_id'],
723
+ latencyMs: row['latency_ms'],
724
+ spentUsdc: row['spent'],
725
+ inputTokens: ledgerTokens ? row['in_tokens'] : row['usage_input_tokens'] ?? 0,
726
+ cachedInputTokens: ledgerTokens ? row['cached_tokens'] : row['usage_cached_input_tokens'] ?? 0,
727
+ outputTokens: ledgerTokens ? row['out_tokens'] : row['usage_output_tokens'] ?? 0,
728
+ errorCode: row['error_code'],
729
+ errorMessage: row['error_message'],
730
+ servedFree,
731
+ costPending: row['spent'] === null && !servedFree && row['model'] !== null && (status === null || status < 400),
732
+ ...(withBodies ? { requestBody: row['request_body'], responseBody: row['response_body'] } : {}),
733
+ };
734
+ });
735
+ }
736
+ /**
737
+ * Totals plus one row per group (each split by `splitBy` when given),
738
+ * attributed as at request time. Request counts come from the request log
739
+ * plus the rollups of pruned rows; spend and tokens from the ledger, and
740
+ * the tokens of requests whose spend carries none (not reported yet, or
741
+ * never) from the usage their responses reported.
742
+ */
743
+ usageReport(filter, groupBy, splitBy = null) {
744
+ const select = (columns) => `${groupExpression(groupBy, columns)} AS g, ${splitBy ? groupExpression(splitBy, columns) : 'NULL'} AS s`;
745
+ const req = scopeSql(filter, REQUEST_COLUMNS);
746
+ const requestRows = this._stmt(`
747
+ SELECT ${select(REQUEST_COLUMNS)}, COUNT(*) AS requests, COALESCE(SUM(${FAILED_SQL}), 0) AS failed
748
+ FROM gateway_requests r ${whereClause(req.where)} GROUP BY g, s
749
+ `).all(...req.params);
750
+ const roll = scopeSql(filter, ROLLUP_COLUMNS);
751
+ const rollupRows = this._stmt(`
752
+ SELECT ${select(ROLLUP_COLUMNS)}, SUM(u.requests) AS requests, SUM(u.failed_requests) AS failed
753
+ FROM request_rollups u ${whereClause(roll.where)} GROUP BY g, s
754
+ `).all(...roll.params);
755
+ const led = scopeSql(filter, LEDGER_COLUMNS);
756
+ led.where.push("l.kind = 'spend'");
757
+ const ledgerRows = this._stmt(`
758
+ SELECT ${select(LEDGER_COLUMNS)}, COALESCE(-SUM(l.amount_usdc), 0) AS spent, COALESCE(SUM(l.input_tokens), 0) AS input_tokens,
759
+ COALESCE(SUM(l.cached_input_tokens), 0) AS cached_input_tokens, COALESCE(SUM(l.output_tokens), 0) AS output_tokens
760
+ FROM ledger_entries l ${whereClause(led.where)} GROUP BY g, s
761
+ `).all(...led.params);
762
+ const fallback = scopeSql(filter, REQUEST_COLUMNS);
763
+ fallback.where.push(REPORTED_TOKENS_ONLY_SQL);
764
+ const responseRows = this._stmt(`
765
+ SELECT ${select(REQUEST_COLUMNS)}, SUM(r.usage_input_tokens) AS input_tokens, COALESCE(SUM(r.usage_cached_input_tokens), 0) AS cached_input_tokens,
766
+ COALESCE(SUM(r.usage_output_tokens), 0) AS output_tokens
767
+ FROM gateway_requests r ${whereClause(fallback.where)} GROUP BY g, s
768
+ `).all(...fallback.params);
769
+ const groups = new Map();
770
+ const entries = (group, split) => {
771
+ let found = groups.get(group);
772
+ if (!found) {
773
+ found = { totals: emptyUsageTotals(), splits: new Map() };
774
+ groups.set(group, found);
775
+ }
776
+ if (!splitBy)
777
+ return [found.totals];
778
+ let part = found.splits.get(split);
779
+ if (!part) {
780
+ part = emptyUsageTotals();
781
+ found.splits.set(split, part);
782
+ }
783
+ return [found.totals, part];
784
+ };
785
+ for (const row of [...requestRows, ...rollupRows]) {
786
+ for (const target of entries(row.g, row.s)) {
787
+ target.requests += row.requests;
788
+ target.failedRequests += row.failed;
789
+ }
790
+ }
791
+ const tokenRows = [...ledgerRows, ...responseRows];
792
+ for (const row of tokenRows) {
793
+ for (const target of entries(row.g, row.s)) {
794
+ target.spentUsdc += row.spent ?? 0;
795
+ target.inputTokens += row.input_tokens;
796
+ target.cachedInputTokens += row.cached_input_tokens;
797
+ target.outputTokens += row.output_tokens;
798
+ }
799
+ }
800
+ const totals = emptyUsageTotals();
801
+ for (const { totals: value } of groups.values()) {
802
+ for (const field of Object.keys(totals))
803
+ totals[field] += value[field];
804
+ }
805
+ if (!groupBy)
806
+ return { totals, groups: [] };
807
+ const bySize = (a, b) => b.spentUsdc - a.spentUsdc || b.requests - a.requests;
808
+ const list = [...groups].map(([group, entry]) => ({
809
+ group,
810
+ ...entry.totals,
811
+ ...(splitBy ? { splits: capUserGroups(splitBy, [...entry.splits].map(([split, part]) => ({ group: split, ...part })).sort(bySize)) } : {}),
812
+ }));
813
+ if (isTimeGrouping(groupBy))
814
+ list.sort((a, b) => String(a.group).localeCompare(String(b.group)));
815
+ else
816
+ list.sort(bySize);
817
+ return { totals, groups: capUserGroups(groupBy, list) };
184
818
  }
185
819
  // ── Ledger ──────────────────────────────────────────────────────────────
186
- /** Returns false when the entry was already recorded (same externalRef). */
820
+ /**
821
+ * Returns false when the entry was already recorded (same externalRef).
822
+ * The entry is attributed to the workspace, member, model and end user of
823
+ * its request (or, without one, of its key as it is now) unless given.
824
+ */
187
825
  recordLedgerEntry(entry) {
188
826
  if (!Number.isSafeInteger(entry.amountUsdc) || entry.amountUsdc < 0) {
189
827
  throw new Error('Ledger amounts are non-negative integer USDC base units; the kind sets the sign.');
190
828
  }
191
829
  const signed = entry.kind === 'spend' ? -entry.amountUsdc : entry.amountUsdc;
192
- const result = this._db.prepare(`
830
+ const request = entry.requestTag ? this.findRequest(entry.requestTag) : null;
831
+ const key = request ? null : this.getKey(entry.keyId);
832
+ const workspaceId = entry.workspaceId ?? request?.workspaceId ?? key?.workspaceId ?? null;
833
+ let memberId = entry.memberId;
834
+ if (memberId === undefined)
835
+ memberId = request ? request.memberId : key?.ownerMemberId ?? null;
836
+ const result = this._stmt(`
193
837
  INSERT OR IGNORE INTO ledger_entries
194
838
  (kind, key_id, buyer_identity, amount_usdc, external_ref, request_tag, seller_peer_id,
195
- input_tokens, cached_input_tokens, output_tokens, note, created_at)
196
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
197
- `).run(entry.kind, entry.keyId, entry.buyerIdentity, signed, entry.externalRef, entry.requestTag ?? null, entry.sellerPeerId ?? null, entry.inputTokens ?? 0, entry.cachedInputTokens ?? 0, entry.outputTokens ?? 0, entry.note ?? null, entry.createdAt);
839
+ input_tokens, cached_input_tokens, output_tokens, note, created_at, workspace_id, member_id, model, end_user)
840
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
841
+ `).run(entry.kind, entry.keyId, entry.buyerIdentity, signed, entry.externalRef, entry.requestTag ?? null, entry.sellerPeerId ?? null, entry.inputTokens ?? 0, entry.cachedInputTokens ?? 0, entry.outputTokens ?? 0, entry.note ?? null, entry.createdAt, workspaceId, memberId, entry.model ?? request?.model ?? null, entry.endUser ?? request?.endUser ?? null);
198
842
  return result.changes > 0;
199
843
  }
844
+ /** A key's spend for the periods `antseed gateway key` shows. */
200
845
  periodSpend(keyId, now = this._now()) {
201
- const statement = this._db.prepare("SELECT COALESCE(-SUM(amount_usdc), 0) AS spent FROM ledger_entries WHERE key_id = ? AND kind = 'spend' AND created_at >= ?");
202
- const spend = {};
203
- for (const period of LIMIT_PERIODS) {
204
- spend[period] = statement.get(keyId, periodStart(period, now)).spent;
846
+ const all = this.spendByPeriod({ keyId }, now);
847
+ return Object.fromEntries(LIMIT_PERIODS.map((period) => [period, all[period]]));
848
+ }
849
+ /**
850
+ * Settled spend per budget period of a key, member or workspace, as
851
+ * attributed when it was spent. Only `periods` are summed (the rest are 0),
852
+ * in one indexed query.
853
+ */
854
+ spendByPeriod(scope, now = this._now(), periods = BUDGET_PERIODS) {
855
+ const spend = { daily: 0, weekly: 0, monthly: 0, total: 0 };
856
+ if (periods.length === 0)
857
+ return spend;
858
+ // Read from the rollups the ledger trigger keeps (see migration v3):
859
+ // the lifetime total is one row, a dated period at most 31 daily rows,
860
+ // however long the ledger grows. Periods start at UTC midnight, so they
861
+ // align with the daily rows.
862
+ const { kind, id } = budgetScopeRow(scope);
863
+ if (periods.includes('total')) {
864
+ const row = this._stmt('SELECT total_usdc FROM spend_totals WHERE scope_kind = ? AND scope_id = ?').get(kind, id);
865
+ spend.total = row?.total_usdc ?? 0;
866
+ }
867
+ const dated = periods.filter((period) => period !== 'total');
868
+ if (dated.length > 0) {
869
+ const starts = dated.map((period) => periodStart(period, now));
870
+ const row = this._stmt(`
871
+ SELECT ${dated.map((_, index) => `COALESCE(SUM(CASE WHEN day_start >= ? THEN amount_usdc ELSE 0 END), 0) AS p${index}`).join(', ')}
872
+ FROM spend_daily WHERE scope_kind = ? AND scope_id = ? AND day_start >= ?
873
+ `).get(...starts, kind, id, Math.min(...starts));
874
+ dated.forEach((period, index) => { spend[period] = row[`p${index}`] ?? 0; });
205
875
  }
206
876
  return spend;
207
877
  }
208
878
  usageStats(keyId, since = 0) {
209
- const requests = this._db.prepare(`
210
- SELECT COUNT(*) AS requests,
211
- COALESCE(SUM(CASE WHEN status IS NULL OR status >= 400 THEN 1 ELSE 0 END), 0) AS failed
212
- FROM gateway_requests WHERE key_id = ? AND started_at >= ?
879
+ const requests = this._stmt(`
880
+ SELECT COUNT(*) AS requests, COALESCE(SUM(${FAILED_SQL}), 0) AS failed
881
+ FROM gateway_requests r WHERE r.key_id = ? AND r.started_at >= ?
213
882
  `).get(keyId, since);
214
- const ledger = this._db.prepare(`
883
+ const rolled = this._stmt(`
884
+ SELECT COALESCE(SUM(requests), 0) AS requests, COALESCE(SUM(failed_requests), 0) AS failed
885
+ FROM request_rollups WHERE key_id = ? AND hour_start >= ?
886
+ `).get(keyId, since);
887
+ const ledger = this._stmt(`
215
888
  SELECT COALESCE(-SUM(CASE WHEN kind = 'spend' THEN amount_usdc ELSE 0 END), 0) AS spent,
216
889
  COALESCE(SUM(CASE WHEN kind = 'credit' THEN amount_usdc ELSE 0 END), 0) AS credited,
217
890
  COALESCE(SUM(input_tokens), 0) AS input_tokens,
218
891
  COALESCE(SUM(cached_input_tokens), 0) AS cached_input_tokens,
219
892
  COALESCE(SUM(output_tokens), 0) AS output_tokens
220
893
  FROM ledger_entries WHERE key_id = ? AND created_at >= ?
894
+ `).get(keyId, since);
895
+ // As in the usage report, requests without metered tokens count the response's own.
896
+ const reported = this._stmt(`
897
+ SELECT COALESCE(SUM(r.usage_input_tokens), 0) AS input_tokens,
898
+ COALESCE(SUM(r.usage_cached_input_tokens), 0) AS cached_input_tokens,
899
+ COALESCE(SUM(r.usage_output_tokens), 0) AS output_tokens
900
+ FROM gateway_requests r
901
+ WHERE r.key_id = ? AND r.started_at >= ? AND ${REPORTED_TOKENS_ONLY_SQL}
221
902
  `).get(keyId, since);
222
903
  return {
223
- requests: requests.requests,
224
- failedRequests: requests.failed,
904
+ requests: requests.requests + rolled.requests,
905
+ failedRequests: requests.failed + rolled.failed,
225
906
  spentUsdc: ledger.spent,
226
907
  creditedUsdc: ledger.credited,
227
- inputTokens: ledger.input_tokens,
228
- cachedInputTokens: ledger.cached_input_tokens,
229
- outputTokens: ledger.output_tokens,
908
+ inputTokens: ledger.input_tokens + reported.input_tokens,
909
+ cachedInputTokens: ledger.cached_input_tokens + reported.cached_input_tokens,
910
+ outputTokens: ledger.output_tokens + reported.output_tokens,
230
911
  };
231
912
  }
913
+ /**
914
+ * Lifetime requests and spend, plus spend since `since`, for many keys in
915
+ * one grouped query per table (rather than `usageStats` per key). Every
916
+ * id asked for gets an entry.
917
+ */
918
+ keyUsageSummaries(keyIds, since) {
919
+ const summaries = new Map(keyIds.map((id) => [id, { requests: 0, spentUsdc: 0, spentSinceUsdc: 0 }]));
920
+ if (keyIds.length === 0)
921
+ return summaries;
922
+ const ids = JSON.stringify(keyIds);
923
+ const requests = this._stmt(`
924
+ SELECT key_id, COUNT(*) AS requests FROM gateway_requests
925
+ WHERE key_id IN (SELECT value FROM json_each(?)) GROUP BY key_id
926
+ `).all(ids);
927
+ const rolled = this._stmt(`
928
+ SELECT key_id, SUM(requests) AS requests FROM request_rollups
929
+ WHERE key_id IN (SELECT value FROM json_each(?)) GROUP BY key_id
930
+ `).all(ids);
931
+ for (const row of [...requests, ...rolled]) {
932
+ const summary = summaries.get(row.key_id);
933
+ if (summary)
934
+ summary.requests += row.requests;
935
+ }
936
+ const spend = this._stmt(`
937
+ SELECT key_id, -SUM(amount_usdc) AS spent, -SUM(CASE WHEN created_at >= ? THEN amount_usdc ELSE 0 END) AS spent_since
938
+ FROM ledger_entries WHERE key_id IN (SELECT value FROM json_each(?)) AND kind = 'spend' GROUP BY key_id
939
+ `).all(since, ids);
940
+ for (const row of spend) {
941
+ const summary = summaries.get(row.key_id);
942
+ if (!summary)
943
+ continue;
944
+ summary.spentUsdc = row.spent;
945
+ summary.spentSinceUsdc = row.spent_since;
946
+ }
947
+ return summaries;
948
+ }
949
+ // ── Peer lists ──────────────────────────────────────────────────────────
950
+ listPeerLists() {
951
+ return this._stmt('SELECT * FROM peer_lists ORDER BY created_at, id').all().map(toPeerList);
952
+ }
953
+ getPeerList(id) {
954
+ const row = this._stmt('SELECT * FROM peer_lists WHERE id = ?').get(id);
955
+ return row ? toPeerList(row) : null;
956
+ }
957
+ createPeerList(input) {
958
+ const id = newId('pl');
959
+ this._stmt('INSERT INTO peer_lists (id, name, description, peer_ids, created_at) VALUES (?, ?, ?, ?, ?)')
960
+ .run(id, input.name, input.description, JSON.stringify(input.peerIds), this._now());
961
+ return this.getPeerList(id);
962
+ }
963
+ updatePeerList(id, patch) {
964
+ const current = this.getPeerList(id);
965
+ if (!current)
966
+ throw new Error(`Unknown peer list "${id}".`);
967
+ this._stmt('UPDATE peer_lists SET name = ?, description = ?, peer_ids = ? WHERE id = ?').run(patch.name ?? current.name, patch.description === undefined ? current.description : patch.description, JSON.stringify(patch.peerIds ?? current.peerIds), id);
968
+ return this.getPeerList(id);
969
+ }
970
+ deletePeerList(id) {
971
+ return this._stmt('DELETE FROM peer_lists WHERE id = ?').run(id).changes > 0;
972
+ }
973
+ // ── Presets ─────────────────────────────────────────────────────────────
974
+ listPresets() {
975
+ return this._stmt('SELECT * FROM presets ORDER BY created_at, id').all().map(toPreset);
976
+ }
977
+ getPreset(id) {
978
+ const row = this._stmt('SELECT * FROM presets WHERE id = ?').get(id);
979
+ return row ? toPreset(row) : null;
980
+ }
981
+ /**
982
+ * An org-wide preset wins over a workspace's own with the same slug, so a
983
+ * workspace admin cannot replace an org-mandated preset (and its system
984
+ * prompt) for their workspace.
985
+ */
986
+ findPresetBySlug(slug, workspaceId) {
987
+ const row = this._stmt(`
988
+ SELECT * FROM presets WHERE slug = ? AND (workspace_id IS NULL OR workspace_id = ?)
989
+ ORDER BY workspace_id IS NOT NULL LIMIT 1
990
+ `).get(slug, workspaceId);
991
+ return row ? toPreset(row) : null;
992
+ }
993
+ createPreset(input) {
994
+ const id = newId('pre');
995
+ this._stmt(`
996
+ INSERT INTO presets (id, slug, name, workspace_id, model, routing_policy, system_prompt, params, created_at)
997
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
998
+ `).run(id, input.slug, input.name, input.workspaceId, input.model, policyJson(input.routingPolicy), input.systemPrompt, JSON.stringify(input.params ?? {}), this._now());
999
+ return this.getPreset(id);
1000
+ }
1001
+ updatePreset(id, patch) {
1002
+ const current = this.getPreset(id);
1003
+ if (!current)
1004
+ throw new Error(`Unknown preset "${id}".`);
1005
+ const next = { ...current, ...patch };
1006
+ this._stmt(`
1007
+ UPDATE presets SET slug = ?, name = ?, model = ?, routing_policy = ?, system_prompt = ?, params = ? WHERE id = ?
1008
+ `).run(next.slug, next.name, next.model, policyJson(next.routingPolicy), next.systemPrompt, JSON.stringify(next.params ?? {}), id);
1009
+ return this.getPreset(id);
1010
+ }
1011
+ deletePreset(id) {
1012
+ return this._stmt('DELETE FROM presets WHERE id = ?').run(id).changes > 0;
1013
+ }
1014
+ // ── Management tokens ───────────────────────────────────────────────────
1015
+ /**
1016
+ * `createdBy` is the creating member's id (null from the CLI); the token
1017
+ * stops working when that member is no longer an active admin or owner.
1018
+ * `expiresAt` null never expires.
1019
+ */
1020
+ createAdminToken(input) {
1021
+ const secret = `${ADMIN_TOKEN_PREFIX}${randomBytes(32).toString('base64url')}`;
1022
+ const id = newId('tok');
1023
+ this._stmt('INSERT INTO admin_tokens (id, label, token_hash, hint, scope, created_by, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)')
1024
+ .run(id, input.label, hashApiKey(secret), apiKeyHint(secret), input.scope, input.createdBy, input.expiresAt ?? null, this._now());
1025
+ return { token: this.getAdminToken(id), secret };
1026
+ }
1027
+ getAdminToken(id) {
1028
+ const row = this._stmt('SELECT * FROM admin_tokens WHERE id = ?').get(id);
1029
+ return row ? toAdminToken(row) : null;
1030
+ }
1031
+ listAdminTokens() {
1032
+ return this._stmt('SELECT * FROM admin_tokens WHERE revoked_at IS NULL ORDER BY created_at, id').all()
1033
+ .map(toAdminToken);
1034
+ }
1035
+ /** Active token for a presented secret (hash lookup). */
1036
+ findAdminTokenBySecret(secret) {
1037
+ const row = this._stmt('SELECT * FROM admin_tokens WHERE token_hash = ? AND revoked_at IS NULL').get(hashApiKey(secret));
1038
+ return row ? toAdminToken(row) : null;
1039
+ }
1040
+ /** Like `touchKey`: at most once a minute per token. */
1041
+ touchAdminToken(id) {
1042
+ const now = this._now();
1043
+ if (!this._shouldTouch(`token:${id}`, now))
1044
+ return;
1045
+ this._stmt('UPDATE admin_tokens SET last_used_at = ? WHERE id = ?').run(now, id);
1046
+ }
1047
+ revokeAdminToken(id) {
1048
+ return this._stmt('UPDATE admin_tokens SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL').run(this._now(), id).changes > 0;
1049
+ }
1050
+ /** Revokes every token a member created (when they are disabled or lose admin); returns their ids. */
1051
+ revokeAdminTokensCreatedBy(memberId) {
1052
+ const rows = this._stmt('SELECT id FROM admin_tokens WHERE created_by = ? AND revoked_at IS NULL').all(memberId);
1053
+ const now = this._now();
1054
+ const revoke = this._stmt('UPDATE admin_tokens SET revoked_at = ? WHERE id = ?');
1055
+ this._db.transaction(() => { for (const row of rows)
1056
+ revoke.run(now, row.id); })();
1057
+ return rows.map((row) => row.id);
1058
+ }
1059
+ // ── Audit log ───────────────────────────────────────────────────────────
1060
+ /**
1061
+ * Appends one audit entry. Every mutating console action calls this:
1062
+ *
1063
+ * deps.store.recordAudit({ actor, action, target, details, ip })
1064
+ *
1065
+ * `actor` comes from `auditActor(store, principal)` in console-api/access.ts
1066
+ * (`{ kind: 'cli', id: null }` from CLI commands), `ip` from
1067
+ * `clientIp(request)`. `details` must never contain secrets.
1068
+ */
1069
+ recordAudit(input) {
1070
+ const id = newId('aud');
1071
+ const at = this._now();
1072
+ this._stmt(`
1073
+ INSERT INTO audit_log (id, at, actor_kind, actor_id, actor_label, action, target_kind, target_id, target_label, details, ip)
1074
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1075
+ `).run(id, at, input.actor.kind, input.actor.id, input.actor.label ?? null, input.action, input.target?.kind ?? null, input.target?.id ?? null, input.target?.label ?? null, JSON.stringify(input.details ?? {}), input.ip ?? null);
1076
+ return {
1077
+ id,
1078
+ at,
1079
+ actor: { kind: input.actor.kind, id: input.actor.id, label: input.actor.label ?? null },
1080
+ action: input.action,
1081
+ target: input.target ? { kind: input.target.kind, id: input.target.id, label: input.target.label ?? null } : null,
1082
+ details: input.details ?? {},
1083
+ ip: input.ip ?? null,
1084
+ };
1085
+ }
1086
+ /** Newest first. `before` is the opaque cursor returned as `nextBefore`. */
1087
+ listAudit(filter = {}) {
1088
+ const limit = Math.max(1, Math.min(500, Math.floor(filter.limit ?? 100)));
1089
+ const where = [];
1090
+ const params = [];
1091
+ const before = filter.before ? Number.parseInt(filter.before, 10) : NaN;
1092
+ if (Number.isFinite(before)) {
1093
+ where.push('seq < ?');
1094
+ params.push(before);
1095
+ }
1096
+ if (filter.actorId) {
1097
+ where.push('actor_id = ?');
1098
+ params.push(filter.actorId);
1099
+ }
1100
+ if (filter.action) {
1101
+ // "key" matches "key" and "key.*"
1102
+ where.push("(action = ? OR action LIKE ? ESCAPE '\\')");
1103
+ params.push(filter.action, `${escapeLike(filter.action)}.%`);
1104
+ }
1105
+ const rows = this._stmt(`SELECT * FROM audit_log ${whereClause(where)} ORDER BY seq DESC LIMIT ?`)
1106
+ .all(...params, limit + 1);
1107
+ const page = rows.slice(0, limit);
1108
+ return {
1109
+ entries: page.map(toAudit),
1110
+ nextBefore: rows.length > limit ? String(page[page.length - 1]['seq']) : null,
1111
+ };
1112
+ }
1113
+ // ── Chat history ────────────────────────────────────────────────────────
1114
+ /** A member's or a key's conversations in a workspace, newest first (without their messages). */
1115
+ listChatThreads(owner, limit = 200) {
1116
+ return this._stmt(`
1117
+ SELECT id, workspace_id, owner_kind, owner_id, title, model, peer_id, created_at, updated_at FROM chat_threads
1118
+ WHERE owner_kind = ? AND owner_id = ? AND workspace_id = ? ORDER BY updated_at DESC LIMIT ?
1119
+ `).all(owner.kind, owner.id, owner.workspaceId, limit).map(chatThreadSummary);
1120
+ }
1121
+ getChatThread(id) {
1122
+ const row = this._stmt('SELECT * FROM chat_threads WHERE id = ?').get(id);
1123
+ return row ? { ...chatThreadSummary(row), messages: parseJson(row.messages, []) } : null;
1124
+ }
1125
+ /** Creates or replaces a conversation; the owner and creation time of an existing one never change. */
1126
+ saveChatThread(input) {
1127
+ const now = this._now();
1128
+ this._stmt(`
1129
+ INSERT INTO chat_threads (id, workspace_id, owner_kind, owner_id, title, model, peer_id, messages, created_at, updated_at)
1130
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1131
+ ON CONFLICT (id) DO UPDATE SET title = excluded.title, model = excluded.model, peer_id = excluded.peer_id, messages = excluded.messages, updated_at = excluded.updated_at
1132
+ `).run(input.id, input.owner.workspaceId, input.owner.kind, input.owner.id, input.title, input.model, input.peerId, JSON.stringify(input.messages), now, now);
1133
+ }
1134
+ deleteChatThread(id) {
1135
+ return this._stmt('DELETE FROM chat_threads WHERE id = ?').run(id).changes > 0;
1136
+ }
1137
+ // ── Settings ────────────────────────────────────────────────────────────
1138
+ /**
1139
+ * Settings are read on every request, so values are cached for a few
1140
+ * seconds. A write through this store clears its cache at once; a write by
1141
+ * another process (a CLI command) is seen within `SETTINGS_TTL_MS`.
1142
+ */
1143
+ getSetting(key) {
1144
+ const now = Date.now();
1145
+ let cached = this._settings.get(key);
1146
+ if (!cached || now - cached.at >= SETTINGS_TTL_MS || now < cached.at) {
1147
+ const row = this._stmt('SELECT value FROM settings WHERE key = ?').get(key);
1148
+ cached = { raw: row?.value ?? null, at: now };
1149
+ this._settings.set(key, cached);
1150
+ }
1151
+ // Parsed per call so callers never share (and mutate) one object.
1152
+ return cached.raw === null ? null : parseJson(cached.raw, null);
1153
+ }
1154
+ setSetting(key, value) {
1155
+ this._settings.delete(key);
1156
+ if (value === null || value === undefined) {
1157
+ this._stmt('DELETE FROM settings WHERE key = ?').run(key);
1158
+ return;
1159
+ }
1160
+ this._stmt('INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value')
1161
+ .run(key, JSON.stringify(value));
1162
+ }
1163
+ }
1164
+ const REQUEST_COLUMNS = {
1165
+ key: 'r.key_id', workspace: 'r.workspace_id', member: 'r.member_id', model: 'r.model', peer: 'r.seller_peer_id', user: 'r.end_user', time: 'r.started_at',
1166
+ };
1167
+ const LEDGER_COLUMNS = {
1168
+ key: 'l.key_id', workspace: 'l.workspace_id', member: 'l.member_id', model: 'l.model', peer: 'l.seller_peer_id', user: 'l.end_user', time: 'l.created_at',
1169
+ };
1170
+ const ROLLUP_COLUMNS = {
1171
+ key: 'u.key_id', workspace: 'u.workspace_id', member: 'u.member_id', model: 'u.model', peer: 'u.seller_peer_id', user: 'u.end_user', time: 'u.hour_start',
1172
+ };
1173
+ const HOUR_MS = 60 * 60 * 1000;
1174
+ const TOUCH_INTERVAL_MS = 60 * 1000;
1175
+ const MAX_TRACKED_TOUCHES = 10_000;
1176
+ const SETTINGS_TTL_MS = 5_000;
1177
+ const MAX_CACHED_STATEMENTS = 500;
1178
+ const MAX_ERROR_CODE = 100;
1179
+ const MAX_ERROR_MESSAGE = 500;
1180
+ /** A failed request: finished with an error status. One still in flight is not a failure. */
1181
+ const FAILED_SQL = '(r.finished_at IS NOT NULL AND (r.status IS NULL OR r.status >= 400))';
1182
+ /**
1183
+ * A request whose tokens come only from its response: its spend recorded no
1184
+ * token counts (free sellers record none), so the response's own count is used.
1185
+ */
1186
+ const REPORTED_TOKENS_ONLY_SQL = `(r.usage_input_tokens IS NOT NULL AND NOT EXISTS (
1187
+ SELECT 1 FROM ledger_entries t WHERE t.request_tag = r.tag AND t.kind = 'spend' AND t.input_tokens + t.cached_input_tokens + t.output_tokens > 0
1188
+ ))`;
1189
+ /** WHERE clauses shared by request-log, rollup and ledger queries. */
1190
+ function scopeSql(filter, columns) {
1191
+ const where = [];
1192
+ const params = [];
1193
+ if (filter.keyIds) {
1194
+ where.push(`${columns.key} IN (SELECT value FROM json_each(?))`);
1195
+ params.push(JSON.stringify(filter.keyIds));
1196
+ }
1197
+ if (filter.keyId) {
1198
+ where.push(`${columns.key} = ?`);
1199
+ params.push(filter.keyId);
1200
+ }
1201
+ if (filter.workspaceId) {
1202
+ where.push(`${columns.workspace} = ?`);
1203
+ params.push(filter.workspaceId);
1204
+ }
1205
+ if (filter.memberId) {
1206
+ where.push(`${columns.member} = ?`);
1207
+ params.push(filter.memberId);
1208
+ }
1209
+ if (filter.model) {
1210
+ where.push(`${columns.model} = ?`);
1211
+ params.push(filter.model);
1212
+ }
1213
+ if (filter.from != null) {
1214
+ where.push(`${columns.time} >= ?`);
1215
+ params.push(filter.from);
1216
+ }
1217
+ if (filter.to != null) {
1218
+ where.push(`${columns.time} < ?`);
1219
+ params.push(filter.to);
1220
+ }
1221
+ return { where, params };
1222
+ }
1223
+ function whereClause(where) {
1224
+ return where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
1225
+ }
1226
+ /** Escapes LIKE wildcards; the queries declare backslash as the escape character. */
1227
+ function escapeLike(value) {
1228
+ return value.replace(/[\\%_]/g, (c) => `\\${c}`);
1229
+ }
1230
+ /** The SQL a usage report groups by; one group ('all') without a grouping. */
1231
+ function groupExpression(groupBy, columns) {
1232
+ if (!groupBy)
1233
+ return "'all'";
1234
+ if (groupBy === 'day')
1235
+ return `strftime('%Y-%m-%d', ${columns.time} / 1000, 'unixepoch')`;
1236
+ if (groupBy === 'hour')
1237
+ return `strftime('%Y-%m-%dT%H', ${columns.time} / 1000, 'unixepoch')`;
1238
+ return columns[groupBy];
1239
+ }
1240
+ /** Groupings whose groups are UTC time buckets (listed oldest first). */
1241
+ export function isTimeGrouping(groupBy) {
1242
+ return groupBy === 'day' || groupBy === 'hour';
1243
+ }
1244
+ /** Groups by end user past the largest `MAX_USER_GROUPS` (rows come sorted largest first), folded into one. */
1245
+ function capUserGroups(groupBy, rows) {
1246
+ if (groupBy !== 'user' || rows.length <= MAX_USER_GROUPS)
1247
+ return rows;
1248
+ const rest = rows.slice(MAX_USER_GROUPS);
1249
+ const other = { group: OTHER_USERS_GROUP, others: rest.length, ...emptyUsageTotals() };
1250
+ for (const row of rest) {
1251
+ for (const field of Object.keys(emptyUsageTotals()))
1252
+ other[field] += row[field];
1253
+ }
1254
+ return [...rows.slice(0, MAX_USER_GROUPS), other];
1255
+ }
1256
+ function emptyUsageTotals() {
1257
+ return { requests: 0, failedRequests: 0, spentUsdc: 0, inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
1258
+ }
1259
+ /** The `spend_totals` / `spend_daily` row key of a budget scope. */
1260
+ function budgetScopeRow(scope) {
1261
+ if ('keyId' in scope)
1262
+ return { kind: 'key', id: scope.keyId };
1263
+ if ('memberId' in scope)
1264
+ return { kind: 'member', id: scope.memberId };
1265
+ return { kind: 'workspace', id: scope.workspaceId };
232
1266
  }
233
1267
  function toKey(row) {
234
1268
  return {
@@ -236,13 +1270,14 @@ function toKey(row) {
236
1270
  label: row.label,
237
1271
  hint: row.key_hint,
238
1272
  buyerIdentity: row.buyer_identity,
1273
+ workspaceId: row.workspace_id ?? DEFAULT_WORKSPACE_ID,
1274
+ ownerMemberId: row.owner_member_id,
239
1275
  source: row.source,
240
1276
  status: row.status,
241
- limits: {
242
- daily: row.daily_limit_usdc,
243
- monthly: row.monthly_limit_usdc,
244
- total: row.total_limit_usdc,
245
- },
1277
+ limits: rowLimits(row),
1278
+ routingPolicy: parsePolicy(row.routing_policy),
1279
+ ownerLimits: rowLimits(row, 'owner_'),
1280
+ ownerRoutingPolicy: parsePolicy(row.owner_routing_policy),
246
1281
  topupEnabled: row.topup_enabled === 1,
247
1282
  expiresAt: row.expires_at,
248
1283
  createdAt: row.created_at,
@@ -250,4 +1285,91 @@ function toKey(row) {
250
1285
  lastUsedAt: row.last_used_at,
251
1286
  };
252
1287
  }
1288
+ function toWorkspace(row) {
1289
+ return {
1290
+ id: row.id,
1291
+ name: row.name,
1292
+ buyerIdentity: row.buyer_identity,
1293
+ isDefault: row.is_default === 1,
1294
+ walletAddress: row.wallet_address,
1295
+ limits: rowLimits(row),
1296
+ routingPolicy: parsePolicy(row.routing_policy),
1297
+ orgRoutingPolicy: parsePolicy(row.org_routing_policy),
1298
+ createdAt: row.created_at,
1299
+ };
1300
+ }
1301
+ function toMember(row) {
1302
+ return {
1303
+ id: row.id,
1304
+ label: row.label,
1305
+ email: row.email,
1306
+ orgRole: row.org_role,
1307
+ status: row.status,
1308
+ limits: rowLimits(row),
1309
+ routingPolicy: parsePolicy(row.routing_policy),
1310
+ maxKeys: row.max_keys,
1311
+ createdAt: row.created_at,
1312
+ };
1313
+ }
1314
+ function toInvite(row) {
1315
+ return {
1316
+ id: row.id,
1317
+ memberId: row.member_id,
1318
+ label: row.label,
1319
+ email: row.email,
1320
+ orgRole: row.org_role,
1321
+ workspaces: parseJson(row.workspace_roles, []),
1322
+ createdBy: row.created_by,
1323
+ expiresAt: row.expires_at,
1324
+ usedAt: row.used_at,
1325
+ createdAt: row.created_at,
1326
+ };
1327
+ }
1328
+ function toPeerList(row) {
1329
+ return {
1330
+ id: row['id'],
1331
+ name: row['name'],
1332
+ description: row['description'],
1333
+ peerIds: parseJson(row['peer_ids'], []),
1334
+ createdAt: row['created_at'],
1335
+ };
1336
+ }
1337
+ function toPreset(row) {
1338
+ return {
1339
+ id: row['id'],
1340
+ slug: row['slug'],
1341
+ name: row['name'],
1342
+ workspaceId: row['workspace_id'],
1343
+ model: row['model'],
1344
+ routingPolicy: parsePolicy(row['routing_policy']),
1345
+ systemPrompt: row['system_prompt'],
1346
+ params: parseJson(row['params'], {}),
1347
+ createdAt: row['created_at'],
1348
+ };
1349
+ }
1350
+ function toAdminToken(row) {
1351
+ return {
1352
+ id: row['id'],
1353
+ label: row['label'],
1354
+ hint: row['hint'],
1355
+ scope: row['scope'],
1356
+ createdBy: row['created_by'],
1357
+ expiresAt: row['expires_at'] ?? null,
1358
+ createdAt: row['created_at'],
1359
+ lastUsedAt: row['last_used_at'],
1360
+ revokedAt: row['revoked_at'],
1361
+ };
1362
+ }
1363
+ function toAudit(row) {
1364
+ const targetKind = row['target_kind'];
1365
+ return {
1366
+ id: row['id'],
1367
+ at: row['at'],
1368
+ actor: { kind: row['actor_kind'], id: row['actor_id'], label: row['actor_label'] },
1369
+ action: row['action'],
1370
+ target: targetKind ? { kind: targetKind, id: row['target_id'], label: row['target_label'] } : null,
1371
+ details: parseJson(row['details'], {}),
1372
+ ip: row['ip'],
1373
+ };
1374
+ }
253
1375
  //# sourceMappingURL=store.js.map