@absol-labs/agent 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +107 -23
  2. package/dist/capability/invocation-capability.d.ts +18 -6
  3. package/dist/capability/invocation-capability.d.ts.map +1 -1
  4. package/dist/capability/invocation-capability.js.map +1 -1
  5. package/dist/cli/commands/claim.d.ts.map +1 -1
  6. package/dist/cli/commands/claim.js +20 -4
  7. package/dist/cli/commands/claim.js.map +1 -1
  8. package/dist/cli/commands/close.d.ts.map +1 -1
  9. package/dist/cli/commands/close.js +16 -4
  10. package/dist/cli/commands/close.js.map +1 -1
  11. package/dist/cli/commands/invoke.d.ts.map +1 -1
  12. package/dist/cli/commands/invoke.js +1 -0
  13. package/dist/cli/commands/invoke.js.map +1 -1
  14. package/dist/cli/commands/open.d.ts.map +1 -1
  15. package/dist/cli/commands/open.js +63 -11
  16. package/dist/cli/commands/open.js.map +1 -1
  17. package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -1
  18. package/dist/cli/commands/reclaim-unverified.js +12 -4
  19. package/dist/cli/commands/reclaim-unverified.js.map +1 -1
  20. package/dist/cli/commands/reclaim.d.ts.map +1 -1
  21. package/dist/cli/commands/reclaim.js +16 -5
  22. package/dist/cli/commands/reclaim.js.map +1 -1
  23. package/dist/cli/commands/streams.js +1 -1
  24. package/dist/cli/commands/streams.js.map +1 -1
  25. package/dist/cli/deps.d.ts +53 -7
  26. package/dist/cli/deps.d.ts.map +1 -1
  27. package/dist/cli/deps.js +65 -15
  28. package/dist/cli/deps.js.map +1 -1
  29. package/dist/cli/privy.d.ts +83 -0
  30. package/dist/cli/privy.d.ts.map +1 -0
  31. package/dist/cli/privy.js +230 -0
  32. package/dist/cli/privy.js.map +1 -0
  33. package/dist/cli/spend-policy.d.ts +60 -0
  34. package/dist/cli/spend-policy.d.ts.map +1 -0
  35. package/dist/cli/spend-policy.js +65 -0
  36. package/dist/cli/spend-policy.js.map +1 -0
  37. package/dist/cli/wallet.d.ts +108 -39
  38. package/dist/cli/wallet.d.ts.map +1 -1
  39. package/dist/cli/wallet.js +108 -43
  40. package/dist/cli/wallet.js.map +1 -1
  41. package/dist/gateway/caller-auth-gateway.d.ts +38 -3
  42. package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
  43. package/dist/gateway/caller-auth-gateway.js +25 -1
  44. package/dist/gateway/caller-auth-gateway.js.map +1 -1
  45. package/dist/gateway/http-server.d.ts +53 -0
  46. package/dist/gateway/http-server.d.ts.map +1 -1
  47. package/dist/gateway/http-server.js +189 -4
  48. package/dist/gateway/http-server.js.map +1 -1
  49. package/dist/gateway/x402-mode.d.ts +123 -0
  50. package/dist/gateway/x402-mode.d.ts.map +1 -0
  51. package/dist/gateway/x402-mode.js +212 -0
  52. package/dist/gateway/x402-mode.js.map +1 -0
  53. package/dist/index.d.ts +6 -4
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +8 -3
  56. package/dist/index.js.map +1 -1
  57. package/dist/mandates/env.d.ts +3 -2
  58. package/dist/mandates/env.d.ts.map +1 -1
  59. package/dist/mandates/env.js +3 -2
  60. package/dist/mandates/env.js.map +1 -1
  61. package/dist/mcp/server.d.ts +49 -0
  62. package/dist/mcp/server.d.ts.map +1 -1
  63. package/dist/mcp/server.js +513 -24
  64. package/dist/mcp/server.js.map +1 -1
  65. package/dist/mcp/service-invoker.d.ts +97 -0
  66. package/dist/mcp/service-invoker.d.ts.map +1 -0
  67. package/dist/mcp/service-invoker.js +167 -0
  68. package/dist/mcp/service-invoker.js.map +1 -0
  69. package/dist/sdk/invoke.d.ts +19 -4
  70. package/dist/sdk/invoke.d.ts.map +1 -1
  71. package/dist/sdk/invoke.js +69 -5
  72. package/dist/sdk/invoke.js.map +1 -1
  73. package/dist/x402/delivery-oracle.d.ts +108 -0
  74. package/dist/x402/delivery-oracle.d.ts.map +1 -0
  75. package/dist/x402/delivery-oracle.js +174 -0
  76. package/dist/x402/delivery-oracle.js.map +1 -0
  77. package/dist/x402/facilitator-server-entry.d.ts +3 -0
  78. package/dist/x402/facilitator-server-entry.d.ts.map +1 -0
  79. package/dist/x402/facilitator-server-entry.js +27 -0
  80. package/dist/x402/facilitator-server-entry.js.map +1 -0
  81. package/dist/x402/facilitator-server.d.ts +73 -0
  82. package/dist/x402/facilitator-server.d.ts.map +1 -0
  83. package/dist/x402/facilitator-server.js +305 -0
  84. package/dist/x402/facilitator-server.js.map +1 -0
  85. package/dist/x402/facilitator.d.ts +69 -17
  86. package/dist/x402/facilitator.d.ts.map +1 -1
  87. package/dist/x402/facilitator.js +76 -33
  88. package/dist/x402/facilitator.js.map +1 -1
  89. package/dist/x402/payment-requirements.d.ts +106 -0
  90. package/dist/x402/payment-requirements.d.ts.map +1 -0
  91. package/dist/x402/payment-requirements.js +138 -0
  92. package/dist/x402/payment-requirements.js.map +1 -0
  93. package/dist/x402/relayer.d.ts +41 -0
  94. package/dist/x402/relayer.d.ts.map +1 -0
  95. package/dist/x402/relayer.js +64 -0
  96. package/dist/x402/relayer.js.map +1 -0
  97. package/dist/x402/resource-index.d.ts +75 -0
  98. package/dist/x402/resource-index.d.ts.map +1 -0
  99. package/dist/x402/resource-index.js +126 -0
  100. package/dist/x402/resource-index.js.map +1 -0
  101. package/dist/x402/verified-delivery-facilitator.d.ts +285 -0
  102. package/dist/x402/verified-delivery-facilitator.d.ts.map +1 -0
  103. package/dist/x402/verified-delivery-facilitator.js +423 -0
  104. package/dist/x402/verified-delivery-facilitator.js.map +1 -0
  105. package/dist/zktls/t2-delivery-proof.d.ts +6 -6
  106. package/docs/agent-layer.md +12 -8
  107. package/docs/cli.md +158 -22
  108. package/docs/mcp-hosted.md +4 -3
  109. package/docs/mcp-reference.md +175 -0
  110. package/docs/quickstart.md +18 -4
  111. package/docs/threat-model.md +4 -3
  112. package/docs/x402.md +648 -0
  113. package/package.json +18 -7
  114. package/src/capability/invocation-capability.ts +17 -1
  115. package/src/cli/commands/claim.ts +30 -4
  116. package/src/cli/commands/close.ts +20 -4
  117. package/src/cli/commands/invoke.ts +1 -0
  118. package/src/cli/commands/open.ts +76 -10
  119. package/src/cli/commands/reclaim-unverified.ts +16 -4
  120. package/src/cli/commands/reclaim.ts +22 -5
  121. package/src/cli/commands/streams.ts +1 -1
  122. package/src/cli/deps.ts +115 -24
  123. package/src/cli/privy.ts +396 -0
  124. package/src/cli/spend-policy.ts +139 -0
  125. package/src/cli/wallet.ts +188 -57
  126. package/src/gateway/caller-auth-gateway.ts +77 -6
  127. package/src/gateway/http-server.ts +280 -6
  128. package/src/gateway/x402-mode.ts +350 -0
  129. package/src/index.ts +40 -5
  130. package/src/mandates/env.ts +3 -2
  131. package/src/mcp/server.ts +681 -29
  132. package/src/mcp/service-invoker.ts +285 -0
  133. package/src/sdk/invoke.ts +90 -12
  134. package/src/x402/delivery-oracle.ts +285 -0
  135. package/src/x402/facilitator-server-entry.ts +35 -0
  136. package/src/x402/facilitator-server.ts +445 -0
  137. package/src/x402/facilitator.ts +125 -46
  138. package/src/x402/payment-requirements.ts +231 -0
  139. package/src/x402/relayer.ts +122 -0
  140. package/src/x402/resource-index.ts +228 -0
  141. package/src/x402/verified-delivery-facilitator.ts +744 -0
@@ -0,0 +1,285 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * The Metrik delivery signal, as consumed by the x402 facilitator.
5
+ *
6
+ * ## Where the signal comes from
7
+ *
8
+ * The oracle publishes `GET /services` — one row per `serviceRef`, reduced from
9
+ * the oracle's own settlement-path verdicts for the active streams of that
10
+ * service (`streamproof-oracle/src/service-status.ts`). Its three states are
11
+ * already buyer-favouring at the source: a FRESH failing stream beats a fresh
12
+ * delivering sibling, and absent/stale evidence is reported as `unverified`
13
+ * rather than folded into "fine".
14
+ *
15
+ * ## Why this module re-checks freshness the oracle already checked
16
+ *
17
+ * The oracle's own bound is 5 minutes (`DEFAULT_SERVICE_STATUS_TTL_MS`), which
18
+ * is a sensible bound for a dashboard and far too loose for a payment gate. On
19
+ * top of that the HTTP response itself can be cached or served by a stale
20
+ * replica, and `lastCheckedAt` is the only field that dates the EVIDENCE rather
21
+ * than the response. So the age of `lastCheckedAt` is re-derived here against
22
+ * this module's own, much tighter window.
23
+ *
24
+ * ## The freshness window: 90 seconds, and why
25
+ *
26
+ * The oracle checkpoints every ~15–22 s, and a service's recorded status is
27
+ * refreshed on that cadence. A window has to be wide enough that ordinary
28
+ * jitter does not flap the gate, and narrow enough that "verified" still means
29
+ * "now":
30
+ *
31
+ * - 3 worst-case probe intervals (3 x 22 s = 66 s) absorbs two consecutive
32
+ * missed or slow ticks, which is normal operational noise, not evidence of
33
+ * a dead service.
34
+ * - plus the oracle's own record-and-serve latency and this process's fetch
35
+ * round trip, rounded up: **90 s**.
36
+ *
37
+ * Tighter (e.g. 30 s) and a single slow tick turns a healthy service's gate
38
+ * indeterminate. Looser (e.g. the oracle's own 300 s) and a service that died
39
+ * four minutes ago still reads `delivering`, which is exactly the claim this
40
+ * gate exists to avoid making.
41
+ *
42
+ * Note this window is strictly stricter than the oracle's: a verdict aged
43
+ * between 90 s and 300 s is `delivering` at the source and `indeterminate`
44
+ * here.
45
+ */
46
+ export const DEFAULT_DELIVERY_FRESHNESS_SECONDS = 90;
47
+
48
+ /** How long a fetched `/services` snapshot may be reused. See `verdictFor`. */
49
+ export const DEFAULT_DELIVERY_SNAPSHOT_TTL_MS = 10_000;
50
+
51
+ /** Request timeout for the oracle read. Short: this sits in a payment hot path. */
52
+ export const DEFAULT_DELIVERY_ORACLE_TIMEOUT_MS = 4_000;
53
+
54
+ export const DEFAULT_METRIK_ORACLE_URL = "https://oracle.metrik.live";
55
+
56
+ /**
57
+ * Three states, never two.
58
+ *
59
+ * `indeterminate` is deliberately NOT collapsed into either of the others: the
60
+ * facilitator's policy for "no fresh evidence" is a separate, configurable
61
+ * decision (see `./verified-delivery-facilitator.ts`), and folding it into
62
+ * `failing` would make Metrik refuse every payment whenever its own oracle is
63
+ * quiet or unreachable.
64
+ */
65
+ export type DeliveryVerdict = "delivering" | "failing" | "indeterminate";
66
+
67
+ /** Why the verdict is what it is — machine-readable, for logs and for tests. */
68
+ export type DeliveryVerdictReason =
69
+ | "fresh-delivering"
70
+ | "fresh-failing"
71
+ | "no-fresh-evidence"
72
+ | "verdict-stale"
73
+ | "service-not-listed"
74
+ | "oracle-unreachable"
75
+ | "oracle-malformed";
76
+
77
+ export interface DeliveryAssessment {
78
+ readonly serviceRef: string;
79
+ readonly verdict: DeliveryVerdict;
80
+ readonly reason: DeliveryVerdictReason;
81
+ /** Unix ms of the newest recorded check for this service, when known. */
82
+ readonly lastCheckedAt: number | null;
83
+ /** Age of `lastCheckedAt` in seconds, when known. */
84
+ readonly ageSeconds: number | null;
85
+ /** The freshness window applied, in seconds. */
86
+ readonly freshnessWindowSeconds: number;
87
+ }
88
+
89
+ export interface MetrikDeliveryOracle {
90
+ /** Never throws: an unreachable oracle is an `indeterminate` verdict, not an exception. */
91
+ verdictFor(serviceRef: string): Promise<DeliveryAssessment>;
92
+ }
93
+
94
+ /** The subset of the oracle's `/services` row this module relies on. */
95
+ const serviceStatusRowSchema = z
96
+ .object({
97
+ serviceRef: z.string(),
98
+ status: z.enum(["delivering", "failing", "unverified"]),
99
+ lastCheckedAt: z.number().nullable().optional(),
100
+ })
101
+ .passthrough();
102
+
103
+ const servicesResponseSchema = z.object({
104
+ services: z.array(serviceStatusRowSchema),
105
+ });
106
+
107
+ export type FetchLike = (
108
+ input: string,
109
+ init?: { signal?: AbortSignal; headers?: Record<string, string> },
110
+ ) => Promise<{
111
+ ok: boolean;
112
+ status: number;
113
+ json(): Promise<unknown>;
114
+ }>;
115
+
116
+ export interface OracleDeliveryOracleOptions {
117
+ /** Oracle base URL. Default {@link DEFAULT_METRIK_ORACLE_URL}. */
118
+ readonly oracleUrl?: string;
119
+ /** Default {@link DEFAULT_DELIVERY_FRESHNESS_SECONDS}. */
120
+ readonly freshnessWindowSeconds?: number;
121
+ /** Default {@link DEFAULT_DELIVERY_SNAPSHOT_TTL_MS}. */
122
+ readonly snapshotTtlMs?: number;
123
+ readonly timeoutMs?: number;
124
+ readonly fetch?: FetchLike;
125
+ /** Injectable clock (unix ms), for tests. */
126
+ readonly now?: () => number;
127
+ }
128
+
129
+ /**
130
+ * Reads `GET {oracleUrl}/services` and projects one row onto a
131
+ * {@link DeliveryAssessment}.
132
+ *
133
+ * The snapshot is cached for {@link DEFAULT_DELIVERY_SNAPSHOT_TTL_MS} so a burst
134
+ * of payments does not become a burst of oracle reads. The cache is SHORTER than
135
+ * the freshness window by design, and caching cannot make a verdict look fresher
136
+ * than it is: freshness is computed from the row's own `lastCheckedAt`, never
137
+ * from when we fetched it.
138
+ */
139
+ export function createOracleDeliveryOracle(
140
+ options: OracleDeliveryOracleOptions = {},
141
+ ): MetrikDeliveryOracle {
142
+ const oracleUrl = (options.oracleUrl ?? DEFAULT_METRIK_ORACLE_URL).replace(
143
+ /\/+$/,
144
+ "",
145
+ );
146
+ const freshnessWindowSeconds =
147
+ options.freshnessWindowSeconds ?? DEFAULT_DELIVERY_FRESHNESS_SECONDS;
148
+ if (!Number.isFinite(freshnessWindowSeconds) || freshnessWindowSeconds <= 0) {
149
+ throw new Error("freshnessWindowSeconds must be a positive number");
150
+ }
151
+ const snapshotTtlMs =
152
+ options.snapshotTtlMs ?? DEFAULT_DELIVERY_SNAPSHOT_TTL_MS;
153
+ const timeoutMs = options.timeoutMs ?? DEFAULT_DELIVERY_ORACLE_TIMEOUT_MS;
154
+ const doFetch = (options.fetch ??
155
+ ((input, init) => globalThis.fetch(input, init))) as FetchLike;
156
+ const now = options.now ?? (() => Date.now());
157
+
158
+ type Snapshot = {
159
+ readonly fetchedAtMs: number;
160
+ readonly rows: Map<string, z.infer<typeof serviceStatusRowSchema>> | null;
161
+ readonly failure: DeliveryVerdictReason | null;
162
+ };
163
+ let snapshot: Snapshot | undefined;
164
+ let inFlight: Promise<Snapshot> | undefined;
165
+
166
+ async function loadSnapshot(): Promise<Snapshot> {
167
+ const controller = new AbortController();
168
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
169
+ try {
170
+ const response = await doFetch(`${oracleUrl}/services`, {
171
+ signal: controller.signal,
172
+ headers: { accept: "application/json", "cache-control": "no-cache" },
173
+ });
174
+ if (!response.ok) {
175
+ return {
176
+ fetchedAtMs: now(),
177
+ rows: null,
178
+ failure: "oracle-unreachable",
179
+ };
180
+ }
181
+ const parsed = servicesResponseSchema.safeParse(await response.json());
182
+ if (!parsed.success) {
183
+ return { fetchedAtMs: now(), rows: null, failure: "oracle-malformed" };
184
+ }
185
+ const rows = new Map<string, z.infer<typeof serviceStatusRowSchema>>();
186
+ for (const row of parsed.data.services) {
187
+ rows.set(row.serviceRef.toLowerCase(), row);
188
+ }
189
+ return { fetchedAtMs: now(), rows, failure: null };
190
+ } catch {
191
+ return { fetchedAtMs: now(), rows: null, failure: "oracle-unreachable" };
192
+ } finally {
193
+ clearTimeout(timer);
194
+ }
195
+ }
196
+
197
+ async function currentSnapshot(): Promise<Snapshot> {
198
+ const cached = snapshot;
199
+ if (cached !== undefined && now() - cached.fetchedAtMs < snapshotTtlMs) {
200
+ return cached;
201
+ }
202
+ // Collapse concurrent misses onto one request.
203
+ inFlight ??= loadSnapshot().finally(() => {
204
+ inFlight = undefined;
205
+ });
206
+ const loaded = await inFlight;
207
+ snapshot = loaded;
208
+ return loaded;
209
+ }
210
+
211
+ return {
212
+ async verdictFor(serviceRef) {
213
+ const key = serviceRef.toLowerCase();
214
+ const loaded = await currentSnapshot();
215
+ if (loaded.rows === null) {
216
+ return indeterminate(
217
+ serviceRef,
218
+ loaded.failure ?? "oracle-unreachable",
219
+ null,
220
+ freshnessWindowSeconds,
221
+ );
222
+ }
223
+ const row = loaded.rows.get(key);
224
+ if (row === undefined) {
225
+ return indeterminate(
226
+ serviceRef,
227
+ "service-not-listed",
228
+ null,
229
+ freshnessWindowSeconds,
230
+ );
231
+ }
232
+ const lastCheckedAt = row.lastCheckedAt ?? null;
233
+ const ageSeconds =
234
+ lastCheckedAt === null
235
+ ? null
236
+ : Math.max(0, Math.round((now() - lastCheckedAt) / 1000));
237
+
238
+ if (row.status === "unverified") {
239
+ return indeterminate(
240
+ serviceRef,
241
+ "no-fresh-evidence",
242
+ lastCheckedAt,
243
+ freshnessWindowSeconds,
244
+ ageSeconds,
245
+ );
246
+ }
247
+ // The oracle dated its own verdict; if it cannot be shown to be recent it
248
+ // is not evidence of anything, in EITHER direction.
249
+ if (ageSeconds === null || ageSeconds > freshnessWindowSeconds) {
250
+ return indeterminate(
251
+ serviceRef,
252
+ "verdict-stale",
253
+ lastCheckedAt,
254
+ freshnessWindowSeconds,
255
+ ageSeconds,
256
+ );
257
+ }
258
+ return {
259
+ serviceRef,
260
+ verdict: row.status === "failing" ? "failing" : "delivering",
261
+ reason: row.status === "failing" ? "fresh-failing" : "fresh-delivering",
262
+ lastCheckedAt,
263
+ ageSeconds,
264
+ freshnessWindowSeconds,
265
+ };
266
+ },
267
+ };
268
+ }
269
+
270
+ function indeterminate(
271
+ serviceRef: string,
272
+ reason: DeliveryVerdictReason,
273
+ lastCheckedAt: number | null,
274
+ freshnessWindowSeconds: number,
275
+ ageSeconds: number | null = null,
276
+ ): DeliveryAssessment {
277
+ return {
278
+ serviceRef,
279
+ verdict: "indeterminate",
280
+ reason,
281
+ lastCheckedAt,
282
+ ageSeconds,
283
+ freshnessWindowSeconds,
284
+ };
285
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { startMetrikFacilitatorServerFromEnv } from "./facilitator-server.js";
3
+
4
+ /**
5
+ * Runnable entrypoint for Metrik's x402 facilitator (`metrik-x402-facilitator`).
6
+ * All configuration comes from the environment; see
7
+ * {@link parseMetrikFacilitatorEnvConfig}. Refuses to start without a
8
+ * settlement relayer unless `METRIK_X402_ALLOW_VERIFY_ONLY=true`.
9
+ */
10
+ async function main(): Promise<void> {
11
+ const server = await startMetrikFacilitatorServerFromEnv();
12
+ const host = process.env.METRIK_X402_HOST ?? "0.0.0.0";
13
+ console.error(
14
+ `[metrik-x402-facilitator] listening on http://${host}:${server.boundPort}`,
15
+ );
16
+
17
+ const shutdown = (signal: string) => {
18
+ console.error(
19
+ `[metrik-x402-facilitator] ${signal} received — shutting down`,
20
+ );
21
+ server
22
+ .close()
23
+ .then(() => process.exit(0))
24
+ .catch(() => process.exit(1));
25
+ };
26
+ process.on("SIGINT", () => shutdown("SIGINT"));
27
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
28
+ }
29
+
30
+ main().catch((error) => {
31
+ console.error(
32
+ error instanceof Error ? error.message : "failed to start x402 facilitator",
33
+ );
34
+ process.exit(1);
35
+ });