@absol-labs/agent 0.10.0 → 0.12.0-rc.1

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 (331) hide show
  1. package/README.md +106 -30
  2. package/dist/capability/invocation-capability.d.ts +20 -8
  3. package/dist/capability/invocation-capability.d.ts.map +1 -1
  4. package/dist/capability/invocation-capability.js.map +1 -1
  5. package/dist/capability/probe-capability.d.ts +225 -0
  6. package/dist/capability/probe-capability.d.ts.map +1 -0
  7. package/dist/capability/probe-capability.js +248 -0
  8. package/dist/capability/probe-capability.js.map +1 -0
  9. package/dist/cli/args.d.ts +11 -0
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/args.js +23 -0
  12. package/dist/cli/args.js.map +1 -1
  13. package/dist/cli/commands/claim.d.ts.map +1 -1
  14. package/dist/cli/commands/claim.js +21 -5
  15. package/dist/cli/commands/claim.js.map +1 -1
  16. package/dist/cli/commands/close.d.ts.map +1 -1
  17. package/dist/cli/commands/close.js +51 -7
  18. package/dist/cli/commands/close.js.map +1 -1
  19. package/dist/cli/commands/invoke.d.ts +2 -0
  20. package/dist/cli/commands/invoke.d.ts.map +1 -1
  21. package/dist/cli/commands/invoke.js +3 -2
  22. package/dist/cli/commands/invoke.js.map +1 -1
  23. package/dist/cli/commands/mcp.d.ts.map +1 -1
  24. package/dist/cli/commands/mcp.js +15 -2
  25. package/dist/cli/commands/mcp.js.map +1 -1
  26. package/dist/cli/commands/open.d.ts.map +1 -1
  27. package/dist/cli/commands/open.js +121 -15
  28. package/dist/cli/commands/open.js.map +1 -1
  29. package/dist/cli/commands/pay.d.ts +19 -0
  30. package/dist/cli/commands/pay.d.ts.map +1 -0
  31. package/dist/cli/commands/pay.js +355 -0
  32. package/dist/cli/commands/pay.js.map +1 -0
  33. package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -1
  34. package/dist/cli/commands/reclaim-unverified.js +15 -6
  35. package/dist/cli/commands/reclaim-unverified.js.map +1 -1
  36. package/dist/cli/commands/reclaim.d.ts +23 -2
  37. package/dist/cli/commands/reclaim.d.ts.map +1 -1
  38. package/dist/cli/commands/reclaim.js +200 -21
  39. package/dist/cli/commands/reclaim.js.map +1 -1
  40. package/dist/cli/commands/refund.d.ts +13 -0
  41. package/dist/cli/commands/refund.d.ts.map +1 -0
  42. package/dist/cli/commands/refund.js +224 -0
  43. package/dist/cli/commands/refund.js.map +1 -0
  44. package/dist/cli/commands/status.d.ts.map +1 -1
  45. package/dist/cli/commands/status.js +10 -1
  46. package/dist/cli/commands/status.js.map +1 -1
  47. package/dist/cli/commands/streams.js +9 -13
  48. package/dist/cli/commands/streams.js.map +1 -1
  49. package/dist/cli/commands/watch.d.ts.map +1 -1
  50. package/dist/cli/commands/watch.js +2 -1
  51. package/dist/cli/commands/watch.js.map +1 -1
  52. package/dist/cli/config.d.ts +44 -26
  53. package/dist/cli/config.d.ts.map +1 -1
  54. package/dist/cli/config.js +88 -38
  55. package/dist/cli/config.js.map +1 -1
  56. package/dist/cli/deps.d.ts +86 -7
  57. package/dist/cli/deps.d.ts.map +1 -1
  58. package/dist/cli/deps.js +102 -16
  59. package/dist/cli/deps.js.map +1 -1
  60. package/dist/cli/errors.d.ts.map +1 -1
  61. package/dist/cli/errors.js +21 -1
  62. package/dist/cli/errors.js.map +1 -1
  63. package/dist/cli/escape-hatch.d.ts +6 -4
  64. package/dist/cli/escape-hatch.d.ts.map +1 -1
  65. package/dist/cli/escape-hatch.js +6 -2
  66. package/dist/cli/escape-hatch.js.map +1 -1
  67. package/dist/cli/listings.d.ts +18 -0
  68. package/dist/cli/listings.d.ts.map +1 -1
  69. package/dist/cli/listings.js +22 -12
  70. package/dist/cli/listings.js.map +1 -1
  71. package/dist/cli/preflight.d.ts +20 -14
  72. package/dist/cli/preflight.d.ts.map +1 -1
  73. package/dist/cli/preflight.js +34 -20
  74. package/dist/cli/preflight.js.map +1 -1
  75. package/dist/cli/privy.d.ts +83 -0
  76. package/dist/cli/privy.d.ts.map +1 -0
  77. package/dist/cli/privy.js +232 -0
  78. package/dist/cli/privy.js.map +1 -0
  79. package/dist/cli/reclaimability.d.ts +82 -0
  80. package/dist/cli/reclaimability.d.ts.map +1 -0
  81. package/dist/cli/reclaimability.js +80 -0
  82. package/dist/cli/reclaimability.js.map +1 -0
  83. package/dist/cli/run.d.ts +2 -1
  84. package/dist/cli/run.d.ts.map +1 -1
  85. package/dist/cli/run.js +40 -11
  86. package/dist/cli/run.js.map +1 -1
  87. package/dist/cli/scan-range.d.ts +2 -2
  88. package/dist/cli/scan-range.js +2 -2
  89. package/dist/cli/spend-policy.d.ts +60 -0
  90. package/dist/cli/spend-policy.d.ts.map +1 -0
  91. package/dist/cli/spend-policy.js +65 -0
  92. package/dist/cli/spend-policy.js.map +1 -0
  93. package/dist/cli/status.d.ts +3 -1
  94. package/dist/cli/status.d.ts.map +1 -1
  95. package/dist/cli/status.js +15 -3
  96. package/dist/cli/status.js.map +1 -1
  97. package/dist/cli/wallet.d.ts +109 -40
  98. package/dist/cli/wallet.d.ts.map +1 -1
  99. package/dist/cli/wallet.js +109 -44
  100. package/dist/cli/wallet.js.map +1 -1
  101. package/dist/discovery/rate-band.d.ts +74 -0
  102. package/dist/discovery/rate-band.d.ts.map +1 -0
  103. package/dist/discovery/rate-band.js +114 -0
  104. package/dist/discovery/rate-band.js.map +1 -0
  105. package/dist/escrow-generation.d.ts +81 -0
  106. package/dist/escrow-generation.d.ts.map +1 -0
  107. package/dist/escrow-generation.js +76 -0
  108. package/dist/escrow-generation.js.map +1 -0
  109. package/dist/frameworks/agentkit.d.ts +8 -4
  110. package/dist/frameworks/agentkit.d.ts.map +1 -1
  111. package/dist/frameworks/agentkit.js +27 -10
  112. package/dist/frameworks/agentkit.js.map +1 -1
  113. package/dist/frameworks/eliza.d.ts +3 -3
  114. package/dist/frameworks/eliza.d.ts.map +1 -1
  115. package/dist/frameworks/eliza.js +15 -6
  116. package/dist/frameworks/eliza.js.map +1 -1
  117. package/dist/frameworks/langchain.d.ts.map +1 -1
  118. package/dist/frameworks/langchain.js +8 -0
  119. package/dist/frameworks/langchain.js.map +1 -1
  120. package/dist/gateway/caller-auth-gateway.d.ts +91 -4
  121. package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
  122. package/dist/gateway/caller-auth-gateway.js +184 -7
  123. package/dist/gateway/caller-auth-gateway.js.map +1 -1
  124. package/dist/gateway/http-server.d.ts +121 -1
  125. package/dist/gateway/http-server.d.ts.map +1 -1
  126. package/dist/gateway/http-server.js +474 -5
  127. package/dist/gateway/http-server.js.map +1 -1
  128. package/dist/gateway/probe-auth.d.ts +112 -0
  129. package/dist/gateway/probe-auth.d.ts.map +1 -0
  130. package/dist/gateway/probe-auth.js +127 -0
  131. package/dist/gateway/probe-auth.js.map +1 -0
  132. package/dist/gateway/signed-rate-bounds.d.ts +51 -0
  133. package/dist/gateway/signed-rate-bounds.d.ts.map +1 -0
  134. package/dist/gateway/signed-rate-bounds.js +94 -0
  135. package/dist/gateway/signed-rate-bounds.js.map +1 -0
  136. package/dist/gateway/x402-opener-mode.d.ts +215 -0
  137. package/dist/gateway/x402-opener-mode.d.ts.map +1 -0
  138. package/dist/gateway/x402-opener-mode.js +639 -0
  139. package/dist/gateway/x402-opener-mode.js.map +1 -0
  140. package/dist/index.d.ts +11 -5
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +15 -5
  143. package/dist/index.js.map +1 -1
  144. package/dist/mandates/env.d.ts +3 -2
  145. package/dist/mandates/env.d.ts.map +1 -1
  146. package/dist/mandates/env.js +3 -2
  147. package/dist/mandates/env.js.map +1 -1
  148. package/dist/mcp/server.d.ts +100 -0
  149. package/dist/mcp/server.d.ts.map +1 -1
  150. package/dist/mcp/server.js +719 -39
  151. package/dist/mcp/server.js.map +1 -1
  152. package/dist/mcp/service-invoker.d.ts +97 -0
  153. package/dist/mcp/service-invoker.d.ts.map +1 -0
  154. package/dist/mcp/service-invoker.js +167 -0
  155. package/dist/mcp/service-invoker.js.map +1 -0
  156. package/dist/mcp/x402-tools.d.ts +104 -0
  157. package/dist/mcp/x402-tools.d.ts.map +1 -0
  158. package/dist/mcp/x402-tools.js +434 -0
  159. package/dist/mcp/x402-tools.js.map +1 -0
  160. package/dist/network.d.ts +119 -0
  161. package/dist/network.d.ts.map +1 -0
  162. package/dist/network.js +199 -0
  163. package/dist/network.js.map +1 -0
  164. package/dist/sdk/invoke.d.ts +23 -4
  165. package/dist/sdk/invoke.d.ts.map +1 -1
  166. package/dist/sdk/invoke.js +71 -10
  167. package/dist/sdk/invoke.js.map +1 -1
  168. package/dist/wallet/autonomous-wallet-broker.d.ts +3 -0
  169. package/dist/wallet/autonomous-wallet-broker.d.ts.map +1 -1
  170. package/dist/wallet/autonomous-wallet-broker.js +27 -23
  171. package/dist/wallet/autonomous-wallet-broker.js.map +1 -1
  172. package/dist/wallet/autonomous-wallet-protocol.d.ts +8 -2
  173. package/dist/wallet/autonomous-wallet-protocol.d.ts.map +1 -1
  174. package/dist/wallet/autonomous-wallet-protocol.js +9 -2
  175. package/dist/wallet/autonomous-wallet-protocol.js.map +1 -1
  176. package/dist/wallet/autonomous-wallet.d.ts.map +1 -1
  177. package/dist/wallet/autonomous-wallet.js +3 -2
  178. package/dist/wallet/autonomous-wallet.js.map +1 -1
  179. package/dist/wallet/broker-escrows.d.ts +222 -0
  180. package/dist/wallet/broker-escrows.d.ts.map +1 -0
  181. package/dist/wallet/broker-escrows.js +36 -0
  182. package/dist/wallet/broker-escrows.js.map +1 -0
  183. package/dist/wallet/lifecycle.d.ts +21 -5
  184. package/dist/wallet/lifecycle.d.ts.map +1 -1
  185. package/dist/wallet/lifecycle.js +32 -12
  186. package/dist/wallet/lifecycle.js.map +1 -1
  187. package/dist/wallet/privy-broker-server.d.ts.map +1 -1
  188. package/dist/wallet/privy-broker-server.js +31 -26
  189. package/dist/wallet/privy-broker-server.js.map +1 -1
  190. package/dist/wallet/privy-session-broker.d.ts +14 -5
  191. package/dist/wallet/privy-session-broker.d.ts.map +1 -1
  192. package/dist/wallet/privy-session-broker.js +29 -17
  193. package/dist/wallet/privy-session-broker.js.map +1 -1
  194. package/dist/wallet/privy-session-provider.d.ts +3 -1
  195. package/dist/wallet/privy-session-provider.d.ts.map +1 -1
  196. package/dist/wallet/privy-session-provider.js +5 -3
  197. package/dist/wallet/privy-session-provider.js.map +1 -1
  198. package/dist/wallet/provider.d.ts.map +1 -1
  199. package/dist/wallet/provider.js +15 -8
  200. package/dist/wallet/provider.js.map +1 -1
  201. package/dist/x402/facilitator.d.ts +42 -17
  202. package/dist/x402/facilitator.d.ts.map +1 -1
  203. package/dist/x402/facilitator.js +49 -33
  204. package/dist/x402/facilitator.js.map +1 -1
  205. package/dist/x402-buyer/guarded-fetch.d.ts +51 -0
  206. package/dist/x402-buyer/guarded-fetch.d.ts.map +1 -0
  207. package/dist/x402-buyer/guarded-fetch.js +147 -0
  208. package/dist/x402-buyer/guarded-fetch.js.map +1 -0
  209. package/dist/x402-buyer/index.d.ts +10 -0
  210. package/dist/x402-buyer/index.d.ts.map +1 -0
  211. package/dist/x402-buyer/index.js +10 -0
  212. package/dist/x402-buyer/index.js.map +1 -0
  213. package/dist/x402-buyer/pay.d.ts +135 -0
  214. package/dist/x402-buyer/pay.d.ts.map +1 -0
  215. package/dist/x402-buyer/pay.js +336 -0
  216. package/dist/x402-buyer/pay.js.map +1 -0
  217. package/dist/x402-opener/abi.d.ts +684 -0
  218. package/dist/x402-opener/abi.d.ts.map +1 -0
  219. package/dist/x402-opener/abi.js +880 -0
  220. package/dist/x402-opener/abi.js.map +1 -0
  221. package/dist/x402-opener/facilitator-entry.d.ts +3 -0
  222. package/dist/x402-opener/facilitator-entry.d.ts.map +1 -0
  223. package/dist/x402-opener/facilitator-entry.js +37 -0
  224. package/dist/x402-opener/facilitator-entry.js.map +1 -0
  225. package/dist/x402-opener/facilitator-server.d.ts +180 -0
  226. package/dist/x402-opener/facilitator-server.d.ts.map +1 -0
  227. package/dist/x402-opener/facilitator-server.js +696 -0
  228. package/dist/x402-opener/facilitator-server.js.map +1 -0
  229. package/dist/x402-opener/index.d.ts +14 -0
  230. package/dist/x402-opener/index.d.ts.map +1 -0
  231. package/dist/x402-opener/index.js +14 -0
  232. package/dist/x402-opener/index.js.map +1 -0
  233. package/dist/x402-opener/listing-resolver.d.ts +55 -0
  234. package/dist/x402-opener/listing-resolver.d.ts.map +1 -0
  235. package/dist/x402-opener/listing-resolver.js +125 -0
  236. package/dist/x402-opener/listing-resolver.js.map +1 -0
  237. package/dist/x402-opener/refund-relay.d.ts +240 -0
  238. package/dist/x402-opener/refund-relay.d.ts.map +1 -0
  239. package/dist/x402-opener/refund-relay.js +1240 -0
  240. package/dist/x402-opener/refund-relay.js.map +1 -0
  241. package/dist/x402-opener/relayer-queue.d.ts +14 -0
  242. package/dist/x402-opener/relayer-queue.d.ts.map +1 -0
  243. package/dist/x402-opener/relayer-queue.js +18 -0
  244. package/dist/x402-opener/relayer-queue.js.map +1 -0
  245. package/dist/x402-opener/scheme.d.ts +217 -0
  246. package/dist/x402-opener/scheme.d.ts.map +1 -0
  247. package/dist/x402-opener/scheme.js +749 -0
  248. package/dist/x402-opener/scheme.js.map +1 -0
  249. package/dist/x402-opener/terms.d.ts +105 -0
  250. package/dist/x402-opener/terms.d.ts.map +1 -0
  251. package/dist/x402-opener/terms.js +212 -0
  252. package/dist/x402-opener/terms.js.map +1 -0
  253. package/dist/zktls/reclaim.d.ts +2 -2
  254. package/docs/agent-layer.md +13 -8
  255. package/docs/autonomous-privy-wallet.md +188 -0
  256. package/docs/cli.md +342 -53
  257. package/docs/langchain.md +5 -0
  258. package/docs/mcp-hosted.md +4 -3
  259. package/docs/mcp-reference.md +245 -0
  260. package/docs/probe-capability.md +191 -0
  261. package/docs/quickstart.md +54 -5
  262. package/docs/threat-model.md +24 -3
  263. package/docs/x402.md +681 -0
  264. package/package.json +19 -6
  265. package/src/capability/invocation-capability.ts +17 -1
  266. package/src/capability/probe-capability.ts +355 -0
  267. package/src/cli/args.ts +29 -0
  268. package/src/cli/commands/claim.ts +31 -5
  269. package/src/cli/commands/close.ts +56 -7
  270. package/src/cli/commands/invoke.ts +6 -2
  271. package/src/cli/commands/mcp.ts +18 -2
  272. package/src/cli/commands/open.ts +151 -14
  273. package/src/cli/commands/pay.ts +442 -0
  274. package/src/cli/commands/reclaim-unverified.ts +24 -6
  275. package/src/cli/commands/reclaim.ts +274 -19
  276. package/src/cli/commands/refund.ts +277 -0
  277. package/src/cli/commands/status.ts +17 -1
  278. package/src/cli/commands/streams.ts +11 -15
  279. package/src/cli/commands/watch.ts +7 -1
  280. package/src/cli/config.ts +139 -52
  281. package/src/cli/deps.ts +189 -25
  282. package/src/cli/errors.ts +24 -1
  283. package/src/cli/escape-hatch.ts +19 -5
  284. package/src/cli/listings.ts +43 -10
  285. package/src/cli/preflight.ts +36 -21
  286. package/src/cli/privy.ts +398 -0
  287. package/src/cli/reclaimability.ts +154 -0
  288. package/src/cli/run.ts +54 -11
  289. package/src/cli/scan-range.ts +2 -2
  290. package/src/cli/spend-policy.ts +139 -0
  291. package/src/cli/status.ts +14 -1
  292. package/src/cli/wallet.ts +189 -58
  293. package/src/discovery/rate-band.ts +172 -0
  294. package/src/escrow-generation.ts +135 -0
  295. package/src/frameworks/agentkit.ts +45 -11
  296. package/src/frameworks/eliza.ts +27 -5
  297. package/src/frameworks/langchain.ts +14 -0
  298. package/src/gateway/caller-auth-gateway.ts +336 -13
  299. package/src/gateway/http-server.ts +690 -7
  300. package/src/gateway/probe-auth.ts +191 -0
  301. package/src/gateway/signed-rate-bounds.ts +164 -0
  302. package/src/gateway/x402-opener-mode.ts +974 -0
  303. package/src/index.ts +112 -14
  304. package/src/mandates/env.ts +3 -2
  305. package/src/mcp/server.ts +973 -44
  306. package/src/mcp/service-invoker.ts +285 -0
  307. package/src/mcp/x402-tools.ts +621 -0
  308. package/src/network.ts +302 -0
  309. package/src/sdk/invoke.ts +96 -17
  310. package/src/wallet/autonomous-wallet-broker.ts +46 -20
  311. package/src/wallet/autonomous-wallet-protocol.ts +11 -2
  312. package/src/wallet/autonomous-wallet.ts +4 -2
  313. package/src/wallet/broker-escrows.ts +66 -0
  314. package/src/wallet/lifecycle.ts +53 -18
  315. package/src/wallet/privy-broker-server.ts +35 -25
  316. package/src/wallet/privy-session-broker.ts +58 -25
  317. package/src/wallet/privy-session-provider.ts +7 -3
  318. package/src/wallet/provider.ts +20 -15
  319. package/src/x402/facilitator.ts +85 -46
  320. package/src/x402-buyer/guarded-fetch.ts +213 -0
  321. package/src/x402-buyer/index.ts +9 -0
  322. package/src/x402-buyer/pay.ts +517 -0
  323. package/src/x402-opener/abi.ts +881 -0
  324. package/src/x402-opener/facilitator-entry.ts +45 -0
  325. package/src/x402-opener/facilitator-server.ts +1014 -0
  326. package/src/x402-opener/index.ts +13 -0
  327. package/src/x402-opener/listing-resolver.ts +202 -0
  328. package/src/x402-opener/refund-relay.ts +1743 -0
  329. package/src/x402-opener/relayer-queue.ts +18 -0
  330. package/src/x402-opener/scheme.ts +1133 -0
  331. package/src/x402-opener/terms.ts +300 -0
@@ -0,0 +1,285 @@
1
+ import {
2
+ RegistryUnavailableError,
3
+ type DiscoverServicesOptions,
4
+ type ServiceListing,
5
+ } from "../discovery/registry.js";
6
+ import {
7
+ invoke as defaultInvoke,
8
+ type InvokeStreamReader,
9
+ } from "../sdk/invoke.js";
10
+ import type {
11
+ HttpMethod,
12
+ InvocationCapabilityDomainInput,
13
+ InvocationCapabilitySigner,
14
+ } from "../capability/invocation-capability.js";
15
+
16
+ /**
17
+ * The "use" half of the MCP tool surface.
18
+ *
19
+ * The MCP server could open a stream and settle it but could not CALL the
20
+ * service the stream paid for — an agent that hired an endpoint through this
21
+ * server had no way to reach it, which made every other tool a payment rail
22
+ * with no product on the other end. This module is the same path
23
+ * `metrik invoke` takes (`../cli/commands/invoke.ts`): resolve the stream's
24
+ * VERIFIED listing, sign a single-use `InvocationCapability` as the stream's
25
+ * buyer, and call the operator's gateway-fronted origin.
26
+ *
27
+ * It moves no funds and signs no transaction. Billing is a function of elapsed
28
+ * verified time, not of calls (see {@link METERING_MODEL} in `./server.ts`), so
29
+ * an invocation is authorized by the stream alone and is deliberately NOT
30
+ * gated by the spend mandate — there is no spend to bound.
31
+ */
32
+
33
+ /**
34
+ * Cap on the response body handed back to the MCP caller.
35
+ *
36
+ * An MCP tool result is pasted into a model's context. A seller can return a
37
+ * body of any size, and an unbounded one would blow the context (or the host's
38
+ * message limit) on a single call. Truncation is reported explicitly rather
39
+ * than silently, so an agent can tell a short answer from a clipped one.
40
+ */
41
+ export const MAX_INVOCATION_RESPONSE_BYTES = 64 * 1024;
42
+
43
+ /** Raised when no verified listing in the registry matches the stream's `serviceRef`. */
44
+ export class ServiceListingNotFoundError extends Error {
45
+ readonly serviceRef: `0x${string}`;
46
+
47
+ constructor(serviceRef: `0x${string}`) {
48
+ super(`no verified listing matches serviceRef ${serviceRef}`);
49
+ this.name = "ServiceListingNotFoundError";
50
+ this.serviceRef = serviceRef;
51
+ }
52
+ }
53
+
54
+ export interface ServiceInvocationRequest {
55
+ readonly streamId: `0x${string}`;
56
+ readonly method: HttpMethod;
57
+ /** Origin-relative path, e.g. `/price?symbol=BTC`. */
58
+ readonly path: string;
59
+ readonly headers?: Readonly<Record<string, string>>;
60
+ readonly body?: string;
61
+ readonly ttlSeconds?: number;
62
+ }
63
+
64
+ export interface ServiceInvocationResult {
65
+ readonly streamId: `0x${string}`;
66
+ readonly method: HttpMethod;
67
+ readonly path: string;
68
+ readonly status: number;
69
+ readonly ok: boolean;
70
+ readonly headers: Record<string, string>;
71
+ readonly body: string;
72
+ readonly bodyTruncated: boolean;
73
+ /** The single-use capability this call was authorized by. */
74
+ readonly capability: {
75
+ readonly nonce: `0x${string}`;
76
+ readonly expiry: number;
77
+ };
78
+ readonly routing: {
79
+ readonly serviceRef: `0x${string}`;
80
+ readonly operator: `0x${string}`;
81
+ readonly access: "public" | "gated";
82
+ /** The signed origin the call went to — never an unsigned caller-supplied URL. */
83
+ readonly origin: string;
84
+ };
85
+ /**
86
+ * The caller-auth gateway's machine-readable rejection reason when it refused
87
+ * the capability, or `null`. Distinguishes "the seller's app returned 403"
88
+ * from "the gateway would not let the call through, and here is why".
89
+ */
90
+ readonly gatewayDenialReason: string | null;
91
+ }
92
+
93
+ export interface ServiceInvoker {
94
+ invokeService(
95
+ request: ServiceInvocationRequest,
96
+ ): Promise<ServiceInvocationResult>;
97
+ }
98
+
99
+ /** Discovery driver — same shape as the server's, kept local to avoid an import cycle. */
100
+ type DiscoverServicesFn = (
101
+ opts?: DiscoverServicesOptions,
102
+ ) => Promise<ServiceListing[]>;
103
+
104
+ export interface CreateServiceInvokerOptions {
105
+ /** Reads the stream's buyer/serviceRef/status/expiry. */
106
+ readonly streamReader: InvokeStreamReader;
107
+ /** The server's own wallet. Must BE the stream's buyer or the call fails closed. */
108
+ readonly buyer: InvocationCapabilitySigner;
109
+ /** `{chainId, verifyingContract}` of the escrow the stream settles on. */
110
+ readonly domain: InvocationCapabilityDomainInput;
111
+ readonly discoverServices: DiscoverServicesFn;
112
+ /** Registry read options (URL/apikey). Passed through to discovery. */
113
+ readonly discoverOptions?: DiscoverServicesOptions;
114
+ /**
115
+ * Clock for the capability's expiry and the stream's liveness check.
116
+ * Defaults to the wall clock; injectable so a test can pin it.
117
+ */
118
+ readonly now?: () => number;
119
+ /** Injectable for tests. Defaults to the real `invoke()`. */
120
+ readonly invokeImpl?: typeof defaultInvoke;
121
+ readonly maxResponseBytes?: number;
122
+ }
123
+
124
+ export function createServiceInvoker(
125
+ options: CreateServiceInvokerOptions,
126
+ ): ServiceInvoker {
127
+ const invokeImpl = options.invokeImpl ?? defaultInvoke;
128
+ const maxBytes = options.maxResponseBytes ?? MAX_INVOCATION_RESPONSE_BYTES;
129
+
130
+ return {
131
+ async invokeService(request) {
132
+ // ONE read, used twice. The listing lookup needs the stream's serviceRef
133
+ // and `invoke()` re-reads the stream to authorize the call; memoizing
134
+ // makes both decisions come from the SAME snapshot (no window where the
135
+ // listing is resolved from one state and the capability signed against
136
+ // another) and costs one RPC round trip instead of two.
137
+ const reader = memoizeStreamReader(options.streamReader);
138
+ const stream = await reader.getStreamV2(request.streamId);
139
+ const listing = await resolveVerifiedListing(
140
+ stream.serviceRef as `0x${string}`,
141
+ options,
142
+ );
143
+
144
+ const result = await invokeImpl(
145
+ request.streamId,
146
+ {
147
+ method: request.method,
148
+ path: request.path,
149
+ ...(request.headers === undefined
150
+ ? {}
151
+ : { headers: request.headers }),
152
+ ...(request.body === undefined ? {} : { body: request.body }),
153
+ },
154
+ {
155
+ streamReader: reader,
156
+ buyer: options.buyer,
157
+ domain: options.domain,
158
+ listing,
159
+ ...(request.ttlSeconds === undefined
160
+ ? {}
161
+ : { ttlSeconds: request.ttlSeconds }),
162
+ ...(options.now === undefined ? {} : { nowSeconds: options.now() }),
163
+ },
164
+ );
165
+
166
+ const raw = await result.response.text();
167
+ const truncated = Buffer.byteLength(raw, "utf8") > maxBytes;
168
+ const body = truncated
169
+ ? Buffer.from(raw, "utf8").subarray(0, maxBytes).toString("utf8")
170
+ : raw;
171
+
172
+ return {
173
+ streamId: request.streamId,
174
+ method: request.method,
175
+ path: request.path,
176
+ status: result.response.status,
177
+ ok: result.response.ok,
178
+ headers: Object.fromEntries(result.response.headers.entries()),
179
+ body,
180
+ bodyTruncated: truncated,
181
+ capability: {
182
+ nonce: result.capability.nonce as `0x${string}`,
183
+ expiry: result.capability.expiry,
184
+ },
185
+ routing: {
186
+ serviceRef: listing.serviceRef,
187
+ operator: listing.operator,
188
+ access: listing.access,
189
+ origin: safeOrigin(listing.accessUrl),
190
+ },
191
+ gatewayDenialReason: readGatewayDenialReason(
192
+ result.response.status,
193
+ raw,
194
+ ),
195
+ };
196
+ },
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Finds the operator-signed listing for a `serviceRef`.
202
+ *
203
+ * The registry is an untrusted transport: `discoverServices` re-derives every
204
+ * `serviceRef` from the signed record and recovers the operator's signature, so
205
+ * a compromised registry can neither invent an operator nor redirect an
206
+ * invocation to an origin its operator never signed. That verification is why
207
+ * this tool routes through discovery instead of accepting a caller-supplied URL
208
+ * — an LLM-chosen base URL is exactly the injection this design refuses.
209
+ */
210
+ async function resolveVerifiedListing(
211
+ serviceRef: `0x${string}`,
212
+ options: CreateServiceInvokerOptions,
213
+ ): Promise<ServiceListing> {
214
+ const listings = await options.discoverServices(options.discoverOptions);
215
+ const wanted = serviceRef.toLowerCase();
216
+ const match = listings.find(
217
+ (listing) => listing.serviceRef.toLowerCase() === wanted,
218
+ );
219
+ if (match === undefined) {
220
+ throw new ServiceListingNotFoundError(serviceRef);
221
+ }
222
+ return match;
223
+ }
224
+
225
+ /** Wraps a reader so repeated reads of the SAME stream id reuse one result. */
226
+ function memoizeStreamReader(reader: InvokeStreamReader): InvokeStreamReader {
227
+ const cache = new Map<
228
+ string,
229
+ ReturnType<InvokeStreamReader["getStreamV2"]>
230
+ >();
231
+ return {
232
+ getStreamV2(streamId) {
233
+ const key = streamId.toLowerCase();
234
+ const cached = cache.get(key);
235
+ if (cached !== undefined) {
236
+ return cached;
237
+ }
238
+ const pending = reader.getStreamV2(streamId);
239
+ cache.set(key, pending);
240
+ return pending;
241
+ },
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Extracts the caller-auth gateway's `reason` from a 402/403 body.
247
+ *
248
+ * The gateway answers a refused capability with
249
+ * `{"authorized":false,"reason":"<closed-set reason>"}`
250
+ * (`../gateway/http-server.ts`). Surfacing that verbatim turns "403" into
251
+ * something an agent can act on — `stream-expired` and `nonce-replayed` call
252
+ * for opposite responses.
253
+ */
254
+ function readGatewayDenialReason(status: number, body: string): string | null {
255
+ if (status !== 402 && status !== 403) {
256
+ return null;
257
+ }
258
+ try {
259
+ const parsed = JSON.parse(body) as {
260
+ readonly authorized?: unknown;
261
+ readonly reason?: unknown;
262
+ } | null;
263
+ if (
264
+ typeof parsed === "object" &&
265
+ parsed !== null &&
266
+ parsed.authorized === false &&
267
+ typeof parsed.reason === "string"
268
+ ) {
269
+ return parsed.reason;
270
+ }
271
+ } catch {
272
+ // Not the gateway's JSON shape — a plain upstream 403. Nothing to add.
273
+ }
274
+ return null;
275
+ }
276
+
277
+ function safeOrigin(url: string): string {
278
+ try {
279
+ return new URL(url).origin;
280
+ } catch {
281
+ return url;
282
+ }
283
+ }
284
+
285
+ export { RegistryUnavailableError };