@absol-labs/agent 0.1.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/dist/discovery/registry.d.ts +68 -0
  4. package/dist/discovery/registry.d.ts.map +1 -0
  5. package/dist/discovery/registry.js +148 -0
  6. package/dist/discovery/registry.js.map +1 -0
  7. package/dist/frameworks/agentkit.d.ts +158 -0
  8. package/dist/frameworks/agentkit.d.ts.map +1 -0
  9. package/dist/frameworks/agentkit.js +339 -0
  10. package/dist/frameworks/agentkit.js.map +1 -0
  11. package/dist/frameworks/crewai.d.ts +19 -0
  12. package/dist/frameworks/crewai.d.ts.map +1 -0
  13. package/dist/frameworks/crewai.js +73 -0
  14. package/dist/frameworks/crewai.js.map +1 -0
  15. package/dist/frameworks/eliza.d.ts +393 -0
  16. package/dist/frameworks/eliza.d.ts.map +1 -0
  17. package/dist/frameworks/eliza.js +542 -0
  18. package/dist/frameworks/eliza.js.map +1 -0
  19. package/dist/frameworks/langchain.d.ts +31 -0
  20. package/dist/frameworks/langchain.d.ts.map +1 -0
  21. package/dist/frameworks/langchain.js +206 -0
  22. package/dist/frameworks/langchain.js.map +1 -0
  23. package/dist/index.d.ts +14 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +16 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/mandates/env.d.ts +51 -0
  28. package/dist/mandates/env.d.ts.map +1 -0
  29. package/dist/mandates/env.js +148 -0
  30. package/dist/mandates/env.js.map +1 -0
  31. package/dist/mandates/mandate.d.ts +214 -0
  32. package/dist/mandates/mandate.d.ts.map +1 -0
  33. package/dist/mandates/mandate.js +172 -0
  34. package/dist/mandates/mandate.js.map +1 -0
  35. package/dist/mcp/http-server.d.ts +2 -0
  36. package/dist/mcp/http-server.d.ts.map +1 -0
  37. package/dist/mcp/http-server.js +32 -0
  38. package/dist/mcp/http-server.js.map +1 -0
  39. package/dist/mcp/http.d.ts +114 -0
  40. package/dist/mcp/http.d.ts.map +1 -0
  41. package/dist/mcp/http.js +428 -0
  42. package/dist/mcp/http.js.map +1 -0
  43. package/dist/mcp/server.d.ts +119 -0
  44. package/dist/mcp/server.d.ts.map +1 -0
  45. package/dist/mcp/server.js +679 -0
  46. package/dist/mcp/server.js.map +1 -0
  47. package/dist/mcp/stdio.d.ts +2 -0
  48. package/dist/mcp/stdio.d.ts.map +1 -0
  49. package/dist/mcp/stdio.js +6 -0
  50. package/dist/mcp/stdio.js.map +1 -0
  51. package/dist/sdk/client.d.ts +78 -0
  52. package/dist/sdk/client.d.ts.map +1 -0
  53. package/dist/sdk/client.js +99 -0
  54. package/dist/sdk/client.js.map +1 -0
  55. package/dist/wallet/provider.d.ts +51 -0
  56. package/dist/wallet/provider.d.ts.map +1 -0
  57. package/dist/wallet/provider.js +107 -0
  58. package/dist/wallet/provider.js.map +1 -0
  59. package/dist/x402/facilitator.d.ts +315 -0
  60. package/dist/x402/facilitator.d.ts.map +1 -0
  61. package/dist/x402/facilitator.js +194 -0
  62. package/dist/x402/facilitator.js.map +1 -0
  63. package/dist/zktls/reclaim.d.ts +137 -0
  64. package/dist/zktls/reclaim.d.ts.map +1 -0
  65. package/dist/zktls/reclaim.js +245 -0
  66. package/dist/zktls/reclaim.js.map +1 -0
  67. package/package.json +86 -0
  68. package/src/discovery/registry.ts +255 -0
  69. package/src/frameworks/agentkit.ts +537 -0
  70. package/src/frameworks/crewai.ts +102 -0
  71. package/src/frameworks/eliza.ts +776 -0
  72. package/src/frameworks/langchain.ts +325 -0
  73. package/src/index.ts +179 -0
  74. package/src/mandates/env.ts +222 -0
  75. package/src/mandates/mandate.ts +280 -0
  76. package/src/mcp/http-server.ts +38 -0
  77. package/src/mcp/http.ts +620 -0
  78. package/src/mcp/server.ts +1006 -0
  79. package/src/mcp/stdio.ts +8 -0
  80. package/src/sdk/client.ts +222 -0
  81. package/src/wallet/provider.ts +197 -0
  82. package/src/x402/facilitator.ts +336 -0
  83. package/src/zktls/reclaim.ts +424 -0
@@ -0,0 +1,245 @@
1
+ import { getHttpProviderClaimParamsFromProof, getProviderHashRequirementsFromSpec, verifyProof, } from "@reclaimprotocol/js-sdk";
2
+ import { z } from "zod";
3
+ const addressSchema = z.string().regex(/^0x[0-9a-fA-F]{40}$/);
4
+ const bytes32Schema = z.string().regex(/^0x[0-9a-fA-F]{64}$/);
5
+ const httpMethodSchema = z.enum(["GET", "POST", "PUT"]);
6
+ const responseMatchSchema = z.object({
7
+ type: z.enum(["regex", "contains"]),
8
+ value: z.string().min(1, "response match value is required"),
9
+ invert: z.boolean().optional(),
10
+ isOptional: z.boolean().optional(),
11
+ });
12
+ const responseRedactionSchema = z
13
+ .object({
14
+ regex: z.string().optional(),
15
+ jsonPath: z.string().optional(),
16
+ xPath: z.string().optional(),
17
+ hash: z.enum(["oprf", "oprf-raw", "oprf-mpc"]).optional(),
18
+ })
19
+ .refine((value) => value.regex !== undefined ||
20
+ value.jsonPath !== undefined ||
21
+ value.xPath !== undefined, "response redaction needs regex, jsonPath, or xPath");
22
+ export class ResponseProofUnavailableError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "ResponseProofUnavailableError";
26
+ }
27
+ }
28
+ export class ResponseProofVerificationError extends Error {
29
+ constructor(message) {
30
+ super(message);
31
+ this.name = "ResponseProofVerificationError";
32
+ }
33
+ }
34
+ const reclaimProofInputSchema = z.object({
35
+ url: z.string().url(),
36
+ method: httpMethodSchema.optional(),
37
+ headers: z.record(z.string()).optional(),
38
+ body: z.string().optional(),
39
+ secretHeaders: z.record(z.string()).optional(),
40
+ responseMatches: z
41
+ .array(responseMatchSchema)
42
+ .min(1, "at least one response match is required"),
43
+ responseRedactions: z.array(responseRedactionSchema).optional(),
44
+ cookie: z.string().optional(),
45
+ secretParamValues: z.record(z.string()).optional(),
46
+ geoLocation: z.string().optional(),
47
+ useTee: z.boolean().optional(),
48
+ zkEngine: z.enum(["snarkjs", "stwo"]).optional(),
49
+ delivery: z.object({
50
+ owner: addressSchema,
51
+ streamId: bytes32Schema.optional(),
52
+ operator: addressSchema.optional(),
53
+ serviceRef: bytes32Schema.optional(),
54
+ mandateId: bytes32Schema.optional(),
55
+ }),
56
+ });
57
+ export class ReclaimConsumerProofService {
58
+ client;
59
+ config;
60
+ verifyProofImpl;
61
+ constructor(config, options = {}) {
62
+ this.config = {
63
+ ...config,
64
+ logs: config.logs ?? false,
65
+ };
66
+ this.client = (options.createClient ?? defaultReclaimClientFactory)(this.config.applicationId, this.config.applicationSecret, this.config.logs ?? false);
67
+ this.verifyProofImpl = options.verifyProof ?? verifyProof;
68
+ }
69
+ async proveConsumedHttpsResponse(input) {
70
+ const parsed = reclaimProofInputSchema.parse(input);
71
+ const method = parsed.method ?? "GET";
72
+ const proof = await this.client
73
+ .zkFetch(parsed.url, {
74
+ method,
75
+ ...(parsed.body === undefined ? {} : { body: parsed.body }),
76
+ ...(parsed.headers === undefined ? {} : { headers: parsed.headers }),
77
+ ...(parsed.geoLocation === undefined
78
+ ? {}
79
+ : { geoLocation: parsed.geoLocation }),
80
+ context: buildRequestContext(parsed.delivery),
81
+ ...(parsed.useTee === undefined ? {} : { useTee: parsed.useTee }),
82
+ ...(parsed.zkEngine === undefined
83
+ ? {}
84
+ : { zkEngine: parsed.zkEngine }),
85
+ }, {
86
+ ...(parsed.secretHeaders === undefined
87
+ ? {}
88
+ : { headers: parsed.secretHeaders }),
89
+ responseMatches: parsed.responseMatches,
90
+ ...(parsed.responseRedactions === undefined
91
+ ? {}
92
+ : { responseRedactions: parsed.responseRedactions }),
93
+ ...(parsed.cookie === undefined ? {} : { cookieStr: parsed.cookie }),
94
+ ...(parsed.secretParamValues === undefined
95
+ ? {}
96
+ : { paramValues: parsed.secretParamValues }),
97
+ })
98
+ .catch((error) => {
99
+ throw new ResponseProofUnavailableError(`failed to generate zkTLS response proof: ${asErrorMessage(error)}`);
100
+ });
101
+ if (proof === undefined) {
102
+ throw new ResponseProofUnavailableError("reclaim returned no zkTLS response proof");
103
+ }
104
+ const verification = await this.verifyProofImpl(proof, {
105
+ ...getProviderHashRequirementsFromSpec({
106
+ requests: [toRequestSpec(parsed, method)],
107
+ }),
108
+ ...(parsed.useTee === true
109
+ ? {
110
+ teeAttestation: {
111
+ appSecret: this.config.applicationSecret,
112
+ },
113
+ }
114
+ : {}),
115
+ });
116
+ if (!verification.isVerified) {
117
+ throw new ResponseProofVerificationError(`zkTLS response proof verification failed: ${asErrorMessage(verification.error)}`);
118
+ }
119
+ const trusted = verification.data[0];
120
+ if (trusted === undefined) {
121
+ throw new ResponseProofVerificationError("zkTLS response proof verification returned no trusted data");
122
+ }
123
+ const request = getHttpProviderClaimParamsFromProof(proof);
124
+ return {
125
+ proof,
126
+ request: {
127
+ url: request.url,
128
+ method: request.method,
129
+ body: request.body === "" ? null : (request.body ?? null),
130
+ responseMatches: request.responseMatches.map((match) => ({
131
+ value: match.value,
132
+ type: match.type,
133
+ invert: match.invert,
134
+ isOptional: match.isOptional,
135
+ })),
136
+ responseRedactions: request.responseRedactions.map((redaction) => ({
137
+ regex: redaction.regex,
138
+ jsonPath: redaction.jsonPath,
139
+ xPath: redaction.xPath,
140
+ hash: redaction.hash,
141
+ })),
142
+ },
143
+ verification: {
144
+ context: trusted.context,
145
+ extractedParameters: trusted.extractedParameters,
146
+ publicData: verification.publicData[0] ?? null,
147
+ isTeeAttestationVerified: verification.isTeeAttestationVerified ?? false,
148
+ isAttestorTeeAttestationVerified: verification.isAttestorTeeAttestationVerified ?? false,
149
+ },
150
+ };
151
+ }
152
+ }
153
+ export function parseReclaimProofEnv(env = process.env) {
154
+ const applicationId = env.RECLAIM_APP_ID;
155
+ const applicationSecret = env.RECLAIM_APP_SECRET;
156
+ if (applicationId === undefined && applicationSecret === undefined) {
157
+ return null;
158
+ }
159
+ if (applicationId === undefined ||
160
+ applicationId.trim() === "" ||
161
+ applicationSecret === undefined ||
162
+ applicationSecret.trim() === "") {
163
+ throw new Error("RECLAIM_APP_ID and RECLAIM_APP_SECRET must both be set");
164
+ }
165
+ return {
166
+ applicationId,
167
+ applicationSecret,
168
+ logs: env.METRIK_AGENT_RECLAIM_LOGS === "true",
169
+ };
170
+ }
171
+ export function createReclaimConsumerProofServiceFromEnv(env = process.env) {
172
+ const parsed = parseReclaimProofEnv(env);
173
+ if (parsed === null) {
174
+ return null;
175
+ }
176
+ return new ReclaimConsumerProofService(parsed);
177
+ }
178
+ function defaultReclaimClientFactory(applicationId, applicationSecret, logs) {
179
+ return new LazyReclaimClient(applicationId, applicationSecret, logs);
180
+ }
181
+ function buildRequestContext(binding) {
182
+ return {
183
+ contextAddress: binding.owner,
184
+ contextMessage: JSON.stringify({
185
+ kind: "metrik-l4-http-response/v1",
186
+ streamId: binding.streamId ?? null,
187
+ operator: binding.operator ?? null,
188
+ serviceRef: binding.serviceRef ?? null,
189
+ mandateId: binding.mandateId ?? null,
190
+ }),
191
+ };
192
+ }
193
+ function toRequestSpec(input, method) {
194
+ return {
195
+ url: input.url,
196
+ urlType: "full",
197
+ method,
198
+ bodySniff: {
199
+ enabled: input.body !== undefined,
200
+ template: input.body ?? "",
201
+ },
202
+ responseMatches: input.responseMatches.map((match) => ({
203
+ value: match.value,
204
+ type: match.type,
205
+ invert: match.invert,
206
+ isOptional: match.isOptional,
207
+ })),
208
+ responseRedactions: (input.responseRedactions ?? []).map((redaction) => ({
209
+ regex: redaction.regex ?? "",
210
+ jsonPath: redaction.jsonPath ?? "",
211
+ xPath: redaction.xPath ?? "",
212
+ ...(redaction.hash === undefined ? {} : { hash: redaction.hash }),
213
+ })),
214
+ required: true,
215
+ multiple: false,
216
+ };
217
+ }
218
+ function asErrorMessage(error) {
219
+ return error instanceof Error ? error.message : String(error);
220
+ }
221
+ class LazyReclaimClient {
222
+ applicationId;
223
+ applicationSecret;
224
+ logs;
225
+ clientPromise;
226
+ constructor(applicationId, applicationSecret, logs) {
227
+ this.applicationId = applicationId;
228
+ this.applicationSecret = applicationSecret;
229
+ this.logs = logs;
230
+ }
231
+ async zkFetch(url, options, secretOptions) {
232
+ const client = await this.getClient();
233
+ return await client.zkFetch(url, options, secretOptions);
234
+ }
235
+ async getClient() {
236
+ if (this.clientPromise === undefined) {
237
+ this.clientPromise = import("@reclaimprotocol/zk-fetch").then((module) => {
238
+ const client = new module.ReclaimClient(this.applicationId, this.applicationSecret, this.logs);
239
+ return client;
240
+ });
241
+ }
242
+ return await this.clientPromise;
243
+ }
244
+ }
245
+ //# sourceMappingURL=reclaim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reclaim.js","sourceRoot":"","sources":["../../src/zktls/reclaim.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@absol-labs/agent",
3
+ "version": "0.1.0",
4
+ "description": "Metrik agent layer: x402 verified-streaming payments, an MCP server, framework tools, and spend mandates so AI agents can hire and pay verified services safely.",
5
+ "license": "MIT",
6
+ "author": "Absol Labs",
7
+ "keywords": [
8
+ "metrik",
9
+ "x402",
10
+ "mcp",
11
+ "ai-agents",
12
+ "spend-mandate",
13
+ "base",
14
+ "usdc"
15
+ ],
16
+ "private": false,
17
+ "type": "module",
18
+ "main": "dist/index.js",
19
+ "types": "dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./package.json": "./package.json",
26
+ "./*": {
27
+ "types": "./dist/*.d.ts",
28
+ "import": "./dist/*.js"
29
+ }
30
+ },
31
+ "sideEffects": false,
32
+ "files": [
33
+ "dist",
34
+ "src"
35
+ ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/Absol-Labs/metrik-agent.git"
39
+ },
40
+ "homepage": "https://github.com/Absol-Labs/metrik-agent#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/Absol-Labs/metrik-agent/issues"
43
+ },
44
+ "publishConfig": {
45
+ "registry": "https://registry.npmjs.org",
46
+ "access": "public"
47
+ },
48
+ "engines": {
49
+ "node": ">=20 <21",
50
+ "pnpm": "9.15.x"
51
+ },
52
+ "dependencies": {
53
+ "@absol-labs/sdk": "^0.4.0",
54
+ "@absol-labs/shared": "^0.7.1",
55
+ "@coinbase/agentkit": "^0.10.4",
56
+ "@coinbase/cdp-sdk": "^1.51.2",
57
+ "@elizaos/core": "^1.7.2",
58
+ "@langchain/core": "^1.2.0",
59
+ "@modelcontextprotocol/sdk": "^1.29.0",
60
+ "@reclaimprotocol/js-sdk": "^5.6.0",
61
+ "@reclaimprotocol/zk-fetch": "^1.0.0",
62
+ "reflect-metadata": "^0.2.2",
63
+ "viem": "^2.21.0",
64
+ "zod": "^3.24.0"
65
+ },
66
+ "devDependencies": {
67
+ "@swc/core": "^1.10.0",
68
+ "@types/node": "^20.17.0",
69
+ "prettier": "^3.4.0",
70
+ "tsx": "^4.19.0",
71
+ "typescript": "^5.7.0",
72
+ "vitest": "^2.1.0"
73
+ },
74
+ "scripts": {
75
+ "build": "tsc -p tsconfig.json",
76
+ "build:e2e": "tsc -p tsconfig.e2e.build.json",
77
+ "mcp:stdio": "tsx src/mcp/stdio.ts",
78
+ "mcp:http": "tsx src/mcp/http-server.ts",
79
+ "e2e:cdp": "node dist-e2e/scripts/e2e-cdp.js",
80
+ "test": "vitest run",
81
+ "typecheck": "tsc -p tsconfig.json --noEmit",
82
+ "typecheck:e2e": "tsc -p tsconfig.e2e.json --noEmit",
83
+ "lint": "tsc -p tsconfig.json --noEmit",
84
+ "format": "prettier --write ."
85
+ }
86
+ }
@@ -0,0 +1,255 @@
1
+ import {
2
+ deriveServiceRef,
3
+ recoverServiceBindingSigner,
4
+ serviceBindingSchema,
5
+ } from "@absol-labs/shared";
6
+ import { isHex } from "viem";
7
+ import { z } from "zod";
8
+
9
+ /**
10
+ * Service discovery for the Metrik verified-service marketplace.
11
+ *
12
+ * Before this module every agent surface (SDK, MCP, framework tools) assumed the
13
+ * caller already knew a seller's `operator` + `serviceRef`. There was no way to
14
+ * DISCOVER a service. {@link discoverServices} closes that gap: it reads the same
15
+ * Supabase `listings` registry the oracle reads and returns typed, VERIFIED
16
+ * listings an agent can hire directly.
17
+ *
18
+ * ## Fail-closed trust model
19
+ * The security-critical envelope lives entirely inside each row's `signed_binding`
20
+ * (`{ binding, signature, serviceRef, targetMetadata }`). The flat `service_ref` /
21
+ * `operator` / `public_url` columns are UNSIGNED denormalised copies for querying
22
+ * only — never trusted. A row is DROPPED (and logged) unless BOTH hold:
23
+ * 1. its EIP-712 signature recovers to `binding.operator`; and
24
+ * 2. `deriveServiceRef(binding)` equals the envelope's own `serviceRef`.
25
+ * `discoverServices` therefore NEVER returns an unverifiable listing.
26
+ *
27
+ * The unsigned `category` column is carried through as informational metadata only
28
+ * (used for the optional client-side category filter); it is never part of the
29
+ * trust decision.
30
+ */
31
+
32
+ /** A verified, hireable service listing. `serviceRef`/`operator`/`publicUrl` are
33
+ * derived from the SIGNED binding, not the unsigned flat columns. */
34
+ export interface ServiceListing {
35
+ readonly serviceRef: `0x${string}`;
36
+ readonly operator: `0x${string}`;
37
+ readonly publicUrl: string;
38
+ /** Unsigned informational category from the listings row (may be absent). */
39
+ readonly category: string | null;
40
+ /** Optional signed operator-supplied target metadata (e.g. resolver hints). */
41
+ readonly targetMetadata?: Record<string, string>;
42
+ }
43
+
44
+ export interface DiscoverServicesOptions {
45
+ /** Registry endpoint. Defaults to {@link DEFAULT_METRIK_REGISTRY_URL} (env override:
46
+ * `METRIK_AGENT_REGISTRY_URL`). Pass the full PostgREST `?select=...&order=...` URL. */
47
+ readonly registryUrl?: string;
48
+ /** PostgREST apikey (Supabase anon key). Env override: `METRIK_AGENT_REGISTRY_APIKEY`.
49
+ * Sent as both the `apikey` header and `Authorization: Bearer`. */
50
+ readonly apiKey?: string;
51
+ /** Optional client-side filter on the unsigned `category` column. */
52
+ readonly category?: string;
53
+ /** Optional cap on the number of verified listings returned. */
54
+ readonly limit?: number;
55
+ /** Injected fetch (tests / custom transports). Defaults to `globalThis.fetch`. */
56
+ readonly fetch?: typeof fetch;
57
+ /** Request timeout in milliseconds. Defaults to 5000. */
58
+ readonly timeoutMs?: number;
59
+ /** Warning sink for dropped rows / registry failures. Defaults to `console`. */
60
+ readonly logger?: { warn: (...args: unknown[]) => void };
61
+ }
62
+
63
+ /**
64
+ * Default Metrik listings registry (Supabase PostgREST). Selects the columns the
65
+ * oracle and the agent both need and orders newest-first.
66
+ */
67
+ export const DEFAULT_METRIK_REGISTRY_URL =
68
+ "https://pzvnacjolaipeobmptsb.supabase.co/rest/v1/listings?select=service_ref,operator,public_url,category,signed_binding&order=created_at.desc";
69
+
70
+ const DEFAULT_TIMEOUT_MS = 5_000;
71
+
72
+ const signature65HexSchema = z
73
+ .string()
74
+ .refine(
75
+ (value) => isHex(value, { strict: true }) && value.length === 132,
76
+ "must be a 65-byte hex signature",
77
+ );
78
+
79
+ const bytes32HexSchema = z
80
+ .string()
81
+ .refine(
82
+ (value) => isHex(value, { strict: true }) && value.length === 66,
83
+ "must be bytes32 hex",
84
+ );
85
+
86
+ /** The signed envelope inside a listings row. Mirrors the oracle's ingest schema
87
+ * (includes the optional operator-supplied `targetMetadata`). */
88
+ const signedBindingRecordSchema = z.object({
89
+ serviceRef: bytes32HexSchema,
90
+ binding: serviceBindingSchema,
91
+ signature: signature65HexSchema,
92
+ targetMetadata: z.record(z.string()).optional(),
93
+ });
94
+
95
+ /** A Supabase `listings` row. Only `signed_binding` is trusted; `category` is
96
+ * carried through as unsigned informational metadata. Unknown columns are ignored. */
97
+ const listingRowSchema = z
98
+ .object({
99
+ category: z.string().nullish(),
100
+ signed_binding: signedBindingRecordSchema,
101
+ })
102
+ .passthrough();
103
+
104
+ /**
105
+ * Discover verified services from the Metrik listings registry.
106
+ *
107
+ * Fail-closed: any row whose signed binding is malformed, whose signature does not
108
+ * recover to its operator, or whose `serviceRef` does not match `deriveServiceRef`
109
+ * is dropped and logged. Degrades gracefully: any network/registry failure returns
110
+ * `[]` (with a logged warning) rather than throwing to the caller.
111
+ */
112
+ export async function discoverServices(
113
+ opts: DiscoverServicesOptions = {},
114
+ ): Promise<ServiceListing[]> {
115
+ const logger = opts.logger ?? console;
116
+ const registryUrl =
117
+ opts.registryUrl ??
118
+ process.env.METRIK_AGENT_REGISTRY_URL ??
119
+ DEFAULT_METRIK_REGISTRY_URL;
120
+ const apiKey = opts.apiKey ?? process.env.METRIK_AGENT_REGISTRY_APIKEY;
121
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
122
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
123
+
124
+ if (typeof fetchImpl !== "function") {
125
+ logger.warn(
126
+ "metrik discovery: no fetch implementation available; returning no listings",
127
+ );
128
+ return [];
129
+ }
130
+
131
+ let rows: unknown[];
132
+ try {
133
+ rows = await fetchListingRows(fetchImpl, registryUrl, apiKey, timeoutMs);
134
+ } catch (error) {
135
+ // Fail-safe / buyer-favouring: a down or unreachable registry must never throw
136
+ // into the agent's hire flow — it degrades to "no services discovered".
137
+ logger.warn(
138
+ `metrik discovery: listings registry unreachable (${errorMessage(error)}); returning no listings`,
139
+ );
140
+ return [];
141
+ }
142
+
143
+ const listings: ServiceListing[] = [];
144
+ for (const [index, row] of rows.entries()) {
145
+ const listing = await verifyRow(row, index, logger);
146
+ if (listing === null) {
147
+ continue;
148
+ }
149
+ if (opts.category !== undefined && listing.category !== opts.category) {
150
+ continue;
151
+ }
152
+ listings.push(listing);
153
+ if (opts.limit !== undefined && listings.length >= opts.limit) {
154
+ break;
155
+ }
156
+ }
157
+ return listings;
158
+ }
159
+
160
+ async function fetchListingRows(
161
+ fetchImpl: typeof fetch,
162
+ url: string,
163
+ apiKey: string | undefined,
164
+ timeoutMs: number,
165
+ ): Promise<unknown[]> {
166
+ const controller = new AbortController();
167
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
168
+ try {
169
+ const headers: Record<string, string> = {};
170
+ if (apiKey !== undefined && apiKey.length > 0) {
171
+ headers.apikey = apiKey;
172
+ headers.Authorization = `Bearer ${apiKey}`;
173
+ }
174
+ const response = await fetchImpl(url, {
175
+ signal: controller.signal,
176
+ ...(Object.keys(headers).length === 0 ? {} : { headers }),
177
+ });
178
+ if (!response.ok) {
179
+ throw new Error(
180
+ `listings registry responded with status ${response.status}`,
181
+ );
182
+ }
183
+ const body: unknown = await response.json();
184
+ if (!Array.isArray(body)) {
185
+ throw new Error("listings registry response was not a JSON array");
186
+ }
187
+ return body;
188
+ } finally {
189
+ clearTimeout(timeout);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Verify one raw registry row into a {@link ServiceListing}, or `null` if it fails
195
+ * the fail-closed checks. Never throws.
196
+ */
197
+ async function verifyRow(
198
+ row: unknown,
199
+ index: number,
200
+ logger: { warn: (...args: unknown[]) => void },
201
+ ): Promise<ServiceListing | null> {
202
+ const parsed = listingRowSchema.safeParse(row);
203
+ if (!parsed.success) {
204
+ logger.warn(
205
+ `metrik discovery: row ${index} dropped — invalid listing envelope (${parsed.error.issues[0]?.message ?? "schema error"})`,
206
+ );
207
+ return null;
208
+ }
209
+ const { signed_binding: record, category } = parsed.data;
210
+
211
+ let signer: `0x${string}` | null;
212
+ try {
213
+ signer = (await recoverServiceBindingSigner(
214
+ record.binding,
215
+ record.signature as `0x${string}`,
216
+ )) as `0x${string}` | null;
217
+ } catch (error) {
218
+ logger.warn(
219
+ `metrik discovery: row ${index} dropped — signature recovery threw (${errorMessage(error)})`,
220
+ );
221
+ return null;
222
+ }
223
+ if (signer === null || !eqHex(signer, record.binding.operator)) {
224
+ logger.warn(
225
+ `metrik discovery: row ${index} dropped — signature does not recover to operator (recovered ${signer ?? "null"})`,
226
+ );
227
+ return null;
228
+ }
229
+
230
+ const derivedRef = deriveServiceRef(record.binding);
231
+ if (!eqHex(derivedRef, record.serviceRef)) {
232
+ logger.warn(
233
+ `metrik discovery: row ${index} dropped — serviceRef ${record.serviceRef} does not match derived ${derivedRef}`,
234
+ );
235
+ return null;
236
+ }
237
+
238
+ return {
239
+ serviceRef: derivedRef,
240
+ operator: record.binding.operator as `0x${string}`,
241
+ publicUrl: record.binding.publicUrl,
242
+ category: category ?? null,
243
+ ...(record.targetMetadata === undefined
244
+ ? {}
245
+ : { targetMetadata: record.targetMetadata }),
246
+ };
247
+ }
248
+
249
+ function eqHex(a: string, b: string): boolean {
250
+ return a.toLowerCase() === b.toLowerCase();
251
+ }
252
+
253
+ function errorMessage(error: unknown): string {
254
+ return error instanceof Error ? error.message : String(error);
255
+ }