@agentcash/discovery 0.1.3 → 1.0.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.
package/dist/index.js CHANGED
@@ -1,23 +1,104 @@
1
- // src/flags.ts
2
- var LEGACY_SUNSET_DATE = "2026-03-24";
3
- var DEFAULT_COMPAT_MODE = "on";
4
- var STRICT_ESCALATION_CODES = [
5
- "LEGACY_WELL_KNOWN_USED",
6
- "LEGACY_DNS_USED",
7
- "LEGACY_DNS_PLAIN_URL",
8
- "LEGACY_MISSING_METHOD",
9
- "LEGACY_INSTRUCTIONS_USED",
10
- "LEGACY_OWNERSHIP_PROOFS_USED",
11
- "INTEROP_MPP_USED"
12
- ];
1
+ // src/core/source/openapi/index.ts
2
+ import { okAsync, ResultAsync as ResultAsync2 } from "neverthrow";
13
3
 
14
- // src/mmm-enabled.ts
15
- var isMmmEnabled = () => "0.1.3".includes("-mmm");
4
+ // src/schemas.ts
5
+ import { z } from "zod";
6
+ var OpenApiPaymentInfoSchema = z.object({
7
+ pricingMode: z.enum(["fixed", "range", "quote"]),
8
+ price: z.string().optional(),
9
+ minPrice: z.string().optional(),
10
+ maxPrice: z.string().optional(),
11
+ protocols: z.array(z.string()).optional()
12
+ });
13
+ var OpenApiOperationSchema = z.object({
14
+ operationId: z.string().optional(),
15
+ summary: z.string().optional(),
16
+ description: z.string().optional(),
17
+ tags: z.array(z.string()).optional(),
18
+ security: z.array(z.record(z.string(), z.array(z.string()))).optional(),
19
+ parameters: z.array(
20
+ z.object({
21
+ in: z.string(),
22
+ name: z.string(),
23
+ schema: z.unknown().optional(),
24
+ required: z.boolean().optional()
25
+ })
26
+ ).optional(),
27
+ requestBody: z.object({
28
+ required: z.boolean().optional(),
29
+ content: z.record(z.string(), z.object({ schema: z.unknown().optional() }))
30
+ }).optional(),
31
+ responses: z.record(z.string(), z.unknown()).optional(),
32
+ "x-payment-info": OpenApiPaymentInfoSchema.optional()
33
+ });
34
+ var OpenApiPathItemSchema = z.object({
35
+ get: OpenApiOperationSchema.optional(),
36
+ post: OpenApiOperationSchema.optional(),
37
+ put: OpenApiOperationSchema.optional(),
38
+ delete: OpenApiOperationSchema.optional(),
39
+ patch: OpenApiOperationSchema.optional(),
40
+ head: OpenApiOperationSchema.optional(),
41
+ options: OpenApiOperationSchema.optional(),
42
+ trace: OpenApiOperationSchema.optional()
43
+ });
44
+ var OpenApiDocSchema = z.object({
45
+ // TODO(zdql): We should inherit a canonical OpenAPI schema and then extend with our types.
46
+ openapi: z.string(),
47
+ info: z.object({
48
+ title: z.string(),
49
+ version: z.string(),
50
+ description: z.string().optional(),
51
+ guidance: z.string().optional()
52
+ }),
53
+ servers: z.array(z.object({ url: z.string() })).optional(),
54
+ tags: z.array(z.object({ name: z.string() })).optional(),
55
+ components: z.object({ securitySchemes: z.record(z.string(), z.unknown()).optional() }).optional(),
56
+ "x-discovery": z.record(z.string(), z.unknown()).optional(),
57
+ paths: z.record(z.string(), OpenApiPathItemSchema)
58
+ });
59
+ var WellKnownDocSchema = z.object({
60
+ version: z.number().optional(),
61
+ resources: z.array(z.string()).default([]),
62
+ mppResources: z.array(z.string()).optional(),
63
+ // isMmmEnabled
64
+ description: z.string().optional(),
65
+ ownershipProofs: z.array(z.string()).optional(),
66
+ instructions: z.string().optional()
67
+ });
68
+ var WellKnownParsedSchema = z.object({
69
+ routes: z.array(
70
+ z.object({
71
+ path: z.string(),
72
+ method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"])
73
+ })
74
+ ),
75
+ instructions: z.string().optional()
76
+ });
77
+
78
+ // src/core/source/openapi/utils.ts
79
+ function isRecord(value) {
80
+ return value !== null && typeof value === "object" && !Array.isArray(value);
81
+ }
82
+ function hasSecurity(operation, scheme) {
83
+ return operation.security?.some((s) => scheme in s) ?? false;
84
+ }
85
+ function has402Response(operation) {
86
+ return Boolean(operation.responses?.["402"]);
87
+ }
88
+ function inferAuthMode(operation) {
89
+ const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
90
+ const hasPayment = hasXPaymentInfo || has402Response(operation);
91
+ const hasApiKey = hasSecurity(operation, "apiKey");
92
+ const hasSiwx = hasSecurity(operation, "siwx");
93
+ if (hasPayment && hasApiKey) return "apiKey+paid";
94
+ if (hasXPaymentInfo) return "paid";
95
+ if (hasPayment) return hasSiwx ? "siwx" : "paid";
96
+ if (hasApiKey) return "apiKey";
97
+ if (hasSiwx) return "siwx";
98
+ return void 0;
99
+ }
16
100
 
17
- // src/core/constants.ts
18
- var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
19
- var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
20
- var LLMS_TOKEN_WARNING_THRESHOLD = 2500;
101
+ // src/core/lib/constants.ts
21
102
  var HTTP_METHODS = /* @__PURE__ */ new Set([
22
103
  "GET",
23
104
  "POST",
@@ -28,10 +109,9 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
28
109
  "OPTIONS",
29
110
  "TRACE"
30
111
  ]);
31
- var DEFAULT_PROBE_METHODS = ["GET", "POST"];
32
112
  var DEFAULT_MISSING_METHOD = "POST";
33
113
 
34
- // src/core/url.ts
114
+ // src/core/lib/url.ts
35
115
  function normalizeOrigin(target) {
36
116
  const trimmed = target.trim();
37
117
  const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
@@ -49,9 +129,6 @@ function normalizePath(pathname) {
49
129
  const normalized = prefixed.replace(/\/+/g, "/");
50
130
  return normalized !== "/" ? normalized.replace(/\/$/, "") : "/";
51
131
  }
52
- function toResourceKey(origin, method, path) {
53
- return `${origin} ${method} ${normalizePath(path)}`;
54
- }
55
132
  function parseMethod(value) {
56
133
  if (!value) return void 0;
57
134
  const upper = value.toUpperCase();
@@ -66,1341 +143,1008 @@ function toAbsoluteUrl(origin, value) {
66
143
  }
67
144
  }
68
145
 
69
- // src/core/warnings.ts
70
- function warning(code, severity, message, options) {
71
- return {
72
- code,
73
- severity,
74
- message,
75
- ...options?.hint ? { hint: options.hint } : {},
76
- ...options?.stage ? { stage: options.stage } : {},
77
- ...options?.resourceKey ? { resourceKey: options.resourceKey } : {}
78
- };
79
- }
80
- function applyStrictEscalation(warnings, strict) {
81
- if (!strict) return warnings;
82
- return warnings.map((entry) => {
83
- if (!STRICT_ESCALATION_CODES.includes(entry.code)) {
84
- return entry;
85
- }
86
- if (entry.severity === "error") return entry;
87
- return {
88
- ...entry,
89
- severity: "error",
90
- message: `${entry.message} (strict mode escalated)`
91
- };
92
- });
146
+ // src/core/source/fetch.ts
147
+ import { ResultAsync } from "neverthrow";
148
+ function toFetchError(err) {
149
+ const cause = err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError") ? "timeout" : "network";
150
+ return { cause, message: String(err) };
93
151
  }
94
- function dedupeWarnings(warnings) {
95
- const seen = /* @__PURE__ */ new Set();
96
- const output = [];
97
- for (const item of warnings) {
98
- const key = `${item.code}|${item.severity}|${item.stage ?? ""}|${item.resourceKey ?? ""}|${item.message}`;
99
- if (seen.has(key)) continue;
100
- seen.add(key);
101
- output.push(item);
102
- }
103
- return output;
152
+ function fetchSafe(url, init) {
153
+ return ResultAsync.fromPromise(fetch(url, init), toFetchError);
104
154
  }
105
155
 
106
- // src/core/normalize.ts
107
- function createResource(input, confidence, trustTier) {
108
- const normalizedOrigin = normalizeOrigin(input.origin);
109
- const normalizedPath = normalizePath(input.path);
110
- return {
111
- resourceKey: toResourceKey(normalizedOrigin, input.method, normalizedPath),
112
- origin: normalizedOrigin,
113
- method: input.method,
114
- path: normalizedPath,
115
- source: input.source,
116
- verified: false,
117
- ...input.protocolHints?.length ? { protocolHints: [...new Set(input.protocolHints)] } : {},
118
- ...input.priceHint ? { priceHint: input.priceHint } : {},
119
- ...input.pricing ? { pricing: input.pricing } : {},
120
- ...input.authHint ? { authHint: input.authHint } : {},
121
- ...input.summary ? { summary: input.summary } : {},
122
- confidence,
123
- trustTier,
124
- ...input.links ? { links: input.links } : {}
125
- };
126
- }
156
+ // src/mmm-enabled.ts
157
+ var isMmmEnabled = () => "1.0.0".includes("-mmm");
127
158
 
128
- // src/compat/legacy-x402scan/wellKnown.ts
129
- function isRecord(value) {
130
- return value !== null && typeof value === "object" && !Array.isArray(value);
131
- }
132
- function parseLegacyResourceEntry(entry) {
133
- const trimmed = entry.trim();
134
- if (!trimmed) return null;
135
- const parts = trimmed.split(/\s+/);
136
- if (parts.length >= 2) {
137
- const maybeMethod = parseMethod(parts[0]);
138
- if (maybeMethod) {
139
- const target = parts.slice(1).join(" ");
140
- return { method: maybeMethod, target };
141
- }
142
- }
143
- if (trimmed.startsWith("/") || /^https?:\/\//i.test(trimmed)) {
144
- return { target: trimmed };
145
- }
146
- return null;
147
- }
148
- function parseWellKnownPayload(payload, origin, sourceUrl) {
149
- const warnings = [
150
- warning(
151
- "LEGACY_WELL_KNOWN_USED",
152
- "warn",
153
- "Using legacy /.well-known/x402 compatibility path. Migrate to OpenAPI-first.",
154
- {
155
- stage: "well-known/x402"
156
- }
157
- )
158
- ];
159
- if (!isRecord(payload)) {
160
- warnings.push(
161
- warning("PARSE_FAILED", "error", "Legacy well-known payload is not an object", {
162
- stage: "well-known/x402"
163
- })
164
- );
165
- return { resources: [], warnings, raw: payload };
166
- }
167
- const resourcesRaw = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
168
- if (resourcesRaw.length === 0) {
169
- warnings.push(
170
- warning("STAGE_EMPTY", "warn", "Legacy well-known has no valid resources array", {
171
- stage: "well-known/x402"
172
- })
173
- );
174
- }
175
- const instructions = typeof payload.instructions === "string" ? payload.instructions : void 0;
176
- const ownershipProofs = Array.isArray(payload.ownershipProofs) ? payload.ownershipProofs.filter((entry) => typeof entry === "string") : [];
177
- if (instructions) {
178
- warnings.push(
179
- warning(
180
- "LEGACY_INSTRUCTIONS_USED",
181
- "warn",
182
- "Using /.well-known/x402.instructions as compatibility guidance fallback. Prefer llms.txt.",
183
- {
184
- stage: "well-known/x402",
185
- hint: "Move guidance to llms.txt and reference via x-agentcash-guidance.llmsTxtUrl."
186
- }
187
- )
188
- );
189
- }
190
- if (ownershipProofs.length > 0) {
191
- warnings.push(
192
- warning(
193
- "LEGACY_OWNERSHIP_PROOFS_USED",
194
- "warn",
195
- "Using /.well-known/x402.ownershipProofs compatibility field. Prefer OpenAPI provenance extension.",
196
- {
197
- stage: "well-known/x402",
198
- hint: "Move ownership proofs to x-agentcash-provenance.ownershipProofs in OpenAPI."
199
- }
200
- )
201
- );
202
- }
203
- const resources = [];
204
- for (const rawEntry of resourcesRaw) {
205
- const parsed = parseLegacyResourceEntry(rawEntry);
206
- if (!parsed) {
207
- warnings.push(
208
- warning("PARSE_FAILED", "warn", `Invalid legacy resource entry: ${rawEntry}`, {
209
- stage: "well-known/x402"
210
- })
211
- );
212
- continue;
213
- }
214
- const absolute = toAbsoluteUrl(origin, parsed.target);
215
- if (!absolute) {
216
- warnings.push(
217
- warning("PARSE_FAILED", "warn", `Invalid legacy resource URL: ${rawEntry}`, {
218
- stage: "well-known/x402"
219
- })
220
- );
221
- continue;
222
- }
223
- const method = parsed.method ?? DEFAULT_MISSING_METHOD;
224
- if (!parsed.method) {
225
- warnings.push(
226
- warning(
227
- "LEGACY_MISSING_METHOD",
228
- "warn",
229
- `Legacy resource '${rawEntry}' missing method. Defaulting to ${DEFAULT_MISSING_METHOD}.`,
230
- {
231
- stage: "well-known/x402"
232
- }
233
- )
159
+ // src/core/source/openapi/index.ts
160
+ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
161
+ const routes = [];
162
+ for (const [rawPath, pathItem] of Object.entries(doc.paths)) {
163
+ for (const httpMethod of [...HTTP_METHODS]) {
164
+ const operation = pathItem[httpMethod.toLowerCase()];
165
+ if (!operation) continue;
166
+ const authMode = inferAuthMode(operation) ?? void 0;
167
+ if (!authMode) continue;
168
+ const p = operation["x-payment-info"];
169
+ const protocols = (p?.protocols ?? []).filter(
170
+ (proto) => proto !== "mpp" || isMmmEnabled()
234
171
  );
172
+ if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) continue;
173
+ if (authMode === "paid" && protocols.length === 0) continue;
174
+ const pricing = authMode === "paid" && p ? {
175
+ pricingMode: p.pricingMode,
176
+ ...p.price ? { price: p.price } : {},
177
+ ...p.minPrice ? { minPrice: p.minPrice } : {},
178
+ ...p.maxPrice ? { maxPrice: p.maxPrice } : {}
179
+ } : void 0;
180
+ const summary = operation.summary ?? operation.description;
181
+ routes.push({
182
+ path: normalizePath(rawPath),
183
+ method: httpMethod.toUpperCase(),
184
+ ...summary ? { summary } : {},
185
+ authMode,
186
+ ...protocols.length ? { protocols } : {},
187
+ ...pricing ? { pricing } : {}
188
+ });
235
189
  }
236
- const path = normalizePath(absolute.pathname);
237
- resources.push(
238
- createResource(
239
- {
240
- origin: absolute.origin,
241
- method,
242
- path,
243
- source: "well-known/x402",
244
- summary: `${method} ${path}`,
245
- authHint: "paid",
246
- protocolHints: ["x402"],
247
- links: {
248
- wellKnownUrl: sourceUrl,
249
- discoveryUrl: sourceUrl
250
- }
251
- },
252
- 0.65,
253
- ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
254
- )
255
- );
256
190
  }
257
191
  return {
258
- resources,
259
- warnings,
260
- raw: payload
192
+ info: {
193
+ title: doc.info.title,
194
+ ...doc.info.description ? { description: doc.info.description } : {},
195
+ version: doc.info.version
196
+ },
197
+ routes,
198
+ ...doc.info.guidance ? { guidance: doc.info.guidance } : {}
261
199
  };
262
- }
263
- async function runWellKnownX402Stage(options) {
264
- const stageUrl = options.url ?? `${options.origin}/.well-known/x402`;
200
+ });
201
+ async function parseBody(response, url) {
265
202
  try {
266
- const response = await options.fetcher(stageUrl, {
267
- method: "GET",
268
- headers: { Accept: "application/json", ...options.headers },
269
- signal: options.signal
270
- });
271
- if (!response.ok) {
272
- if (response.status === 404) {
273
- return {
274
- stage: "well-known/x402",
275
- valid: false,
276
- resources: [],
277
- warnings: [],
278
- links: { wellKnownUrl: stageUrl }
279
- };
280
- }
281
- return {
282
- stage: "well-known/x402",
283
- valid: false,
284
- resources: [],
285
- warnings: [
286
- warning("FETCH_FAILED", "warn", `Legacy well-known fetch failed (${response.status})`, {
287
- stage: "well-known/x402"
288
- })
289
- ],
290
- links: { wellKnownUrl: stageUrl }
291
- };
292
- }
293
203
  const payload = await response.json();
294
- const parsed = parseWellKnownPayload(payload, options.origin, stageUrl);
295
- return {
296
- stage: "well-known/x402",
297
- valid: parsed.resources.length > 0,
298
- resources: parsed.resources,
299
- warnings: parsed.warnings,
300
- links: { wellKnownUrl: stageUrl },
301
- ...options.includeRaw ? { raw: parsed.raw } : {}
302
- };
303
- } catch (error) {
304
- return {
305
- stage: "well-known/x402",
306
- valid: false,
307
- resources: [],
308
- warnings: [
309
- warning(
310
- "FETCH_FAILED",
311
- "warn",
312
- `Legacy well-known fetch exception: ${error instanceof Error ? error.message : String(error)}`,
313
- {
314
- stage: "well-known/x402"
315
- }
316
- )
317
- ],
318
- links: { wellKnownUrl: stageUrl }
319
- };
204
+ const parsed = OpenApiParsedSchema.safeParse(payload);
205
+ if (!parsed.success) return null;
206
+ return { raw: payload, ...parsed.data, fetchedUrl: url };
207
+ } catch {
208
+ return null;
320
209
  }
321
210
  }
322
-
323
- // src/compat/legacy-x402scan/dns.ts
324
- function parseDnsRecord(record) {
325
- const trimmed = record.trim();
326
- if (!trimmed) return null;
327
- if (/^https?:\/\//i.test(trimmed)) {
328
- return { url: trimmed, legacyPlainUrl: true };
329
- }
330
- const parts = trimmed.split(";").map((entry) => entry.trim()).filter(Boolean);
331
- const keyValues = /* @__PURE__ */ new Map();
332
- for (const part of parts) {
333
- const separator = part.indexOf("=");
334
- if (separator <= 0) continue;
335
- const key = part.slice(0, separator).trim().toLowerCase();
336
- const value = part.slice(separator + 1).trim();
337
- if (key && value) keyValues.set(key, value);
338
- }
339
- if (keyValues.get("v") !== "x4021") return null;
340
- const url = keyValues.get("url");
341
- if (!url || !/^https?:\/\//i.test(url)) return null;
342
- return { url, legacyPlainUrl: false };
211
+ function getOpenAPI(origin, headers, signal, specificationOverrideUrl) {
212
+ const url = specificationOverrideUrl ?? `${origin}/openapi.json`;
213
+ return fetchSafe(url, {
214
+ method: "GET",
215
+ headers: { Accept: "application/json", ...headers },
216
+ signal
217
+ }).andThen((response) => {
218
+ if (!response.ok) return okAsync(null);
219
+ return ResultAsync2.fromSafePromise(parseBody(response, url));
220
+ });
343
221
  }
344
- async function runDnsStage(options) {
345
- if (!options.txtResolver) {
346
- return {
347
- stage: "dns/_x402",
348
- valid: false,
349
- resources: [],
350
- warnings: []
351
- };
352
- }
353
- const origin = normalizeOrigin(options.origin);
354
- const hostname = new URL(origin).hostname;
355
- const fqdn = `_x402.${hostname}`;
356
- let records;
357
- try {
358
- records = await options.txtResolver(fqdn);
359
- } catch (error) {
360
- return {
361
- stage: "dns/_x402",
362
- valid: false,
363
- resources: [],
364
- warnings: [
365
- warning(
366
- "FETCH_FAILED",
367
- "warn",
368
- `DNS TXT lookup failed for ${fqdn}: ${error instanceof Error ? error.message : String(error)}`,
369
- { stage: "dns/_x402" }
370
- )
371
- ]
372
- };
373
- }
374
- if (records.length === 0) {
375
- return {
376
- stage: "dns/_x402",
377
- valid: false,
378
- resources: [],
379
- warnings: []
380
- };
381
- }
382
- const warnings = [
383
- warning(
384
- "LEGACY_DNS_USED",
385
- "warn",
386
- "Using DNS _x402 compatibility path. Migrate to OpenAPI-first discovery.",
222
+
223
+ // src/core/source/wellknown/index.ts
224
+ import { okAsync as okAsync2, ResultAsync as ResultAsync3 } from "neverthrow";
225
+ function toWellKnownParsed(origin, doc) {
226
+ const routes = doc.resources.flatMap((entry) => {
227
+ const trimmed = entry.trim();
228
+ if (!trimmed) return [];
229
+ const parts = trimmed.split(/\s+/);
230
+ const maybeMethod = parts.length >= 2 ? parseMethod(parts[0]) : void 0;
231
+ const target = maybeMethod ? parts.slice(1).join(" ") : trimmed;
232
+ const absolute = toAbsoluteUrl(origin, target);
233
+ if (!absolute) return [];
234
+ return [
387
235
  {
388
- stage: "dns/_x402"
236
+ path: normalizePath(absolute.pathname),
237
+ method: maybeMethod ?? DEFAULT_MISSING_METHOD
389
238
  }
390
- )
391
- ];
392
- const urls = [];
393
- for (const record of records) {
394
- const parsed = parseDnsRecord(record);
395
- if (!parsed) {
396
- warnings.push(
397
- warning("PARSE_FAILED", "warn", `Invalid DNS _x402 TXT record: ${record}`, {
398
- stage: "dns/_x402"
399
- })
400
- );
401
- continue;
402
- }
403
- urls.push(parsed.url);
404
- if (parsed.legacyPlainUrl) {
405
- warnings.push(
406
- warning("LEGACY_DNS_PLAIN_URL", "warn", `Legacy plain URL TXT format used: ${record}`, {
407
- stage: "dns/_x402",
408
- hint: "Use v=x4021;url=<https-url> format."
409
- })
410
- );
411
- }
412
- }
413
- if (urls.length === 0) {
414
- return {
415
- stage: "dns/_x402",
416
- valid: false,
417
- resources: [],
418
- warnings,
419
- ...options.includeRaw ? { raw: { dnsRecords: records } } : {}
420
- };
421
- }
422
- const mergedWarnings = [...warnings];
423
- const mergedResources = [];
424
- const rawDocuments = [];
425
- for (const url of urls) {
426
- const stageResult = await runWellKnownX402Stage({
427
- origin,
428
- url,
429
- fetcher: options.fetcher,
430
- headers: options.headers,
431
- signal: options.signal,
432
- includeRaw: options.includeRaw
433
- });
434
- mergedResources.push(...stageResult.resources);
435
- mergedWarnings.push(...stageResult.warnings);
436
- if (options.includeRaw && stageResult.raw !== void 0) {
437
- rawDocuments.push({ url, document: stageResult.raw });
438
- }
439
- }
440
- const deduped = /* @__PURE__ */ new Map();
441
- for (const resource of mergedResources) {
442
- if (!deduped.has(resource.resourceKey)) {
443
- deduped.set(resource.resourceKey, {
444
- ...resource,
445
- source: "dns/_x402",
446
- links: {
447
- ...resource.links,
448
- discoveryUrl: resource.links?.discoveryUrl
449
- }
450
- });
451
- }
239
+ ];
240
+ });
241
+ return { routes, ...doc.instructions ? { instructions: doc.instructions } : {} };
242
+ }
243
+ async function parseBody2(response, origin, url) {
244
+ try {
245
+ const payload = await response.json();
246
+ const doc = WellKnownDocSchema.safeParse(payload);
247
+ if (!doc.success) return null;
248
+ const parsed = WellKnownParsedSchema.safeParse(toWellKnownParsed(origin, doc.data));
249
+ if (!parsed.success) return null;
250
+ return { raw: payload, ...parsed.data, fetchedUrl: url };
251
+ } catch {
252
+ return null;
452
253
  }
254
+ }
255
+ function getWellKnown(origin, headers, signal) {
256
+ const url = `${origin}/.well-known/x402`;
257
+ return fetchSafe(url, {
258
+ method: "GET",
259
+ headers: { Accept: "application/json", ...headers },
260
+ signal
261
+ }).andThen((response) => {
262
+ if (!response.ok) return okAsync2(null);
263
+ return ResultAsync3.fromSafePromise(parseBody2(response, origin, url));
264
+ });
265
+ }
266
+
267
+ // src/core/layers/l2.ts
268
+ function formatPrice(pricing) {
269
+ if (pricing.pricingMode === "fixed") return `$${pricing.price}`;
270
+ if (pricing.pricingMode === "range") return `$${pricing.minPrice}-$${pricing.maxPrice}`;
271
+ return pricing.maxPrice ? `up to $${pricing.maxPrice}` : "quote";
272
+ }
273
+ function checkL2ForOpenAPI(openApi) {
274
+ const routes = openApi.routes.map((route) => ({
275
+ path: route.path,
276
+ method: route.method,
277
+ summary: route.summary ?? `${route.method} ${route.path}`,
278
+ ...route.authMode ? { authMode: route.authMode } : {},
279
+ ...route.pricing ? { price: formatPrice(route.pricing) } : {},
280
+ ...route.protocols?.length ? { protocols: route.protocols } : {}
281
+ }));
453
282
  return {
454
- stage: "dns/_x402",
455
- valid: deduped.size > 0,
456
- resources: [...deduped.values()],
457
- warnings: mergedWarnings,
458
- ...options.includeRaw ? { raw: { dnsRecords: records, documents: rawDocuments } } : {}
283
+ ...openApi.info.title ? { title: openApi.info.title } : {},
284
+ ...openApi.info.description ? { description: openApi.info.description } : {},
285
+ routes,
286
+ source: "openapi"
459
287
  };
460
288
  }
289
+ function checkL2ForWellknown(wellKnown) {
290
+ const routes = wellKnown.routes.map((route) => ({
291
+ path: route.path,
292
+ method: route.method,
293
+ summary: `${route.method} ${route.path}`,
294
+ authMode: "paid",
295
+ protocols: ["x402"]
296
+ }));
297
+ return { routes, source: "well-known/x402" };
298
+ }
461
299
 
462
- // src/compat/interop-mpp/wellKnownMpp.ts
463
- function isRecord2(value) {
464
- return value !== null && typeof value === "object" && !Array.isArray(value);
300
+ // src/core/layers/l4.ts
301
+ function checkL4ForOpenAPI(openApi) {
302
+ if (openApi.guidance) {
303
+ return { guidance: openApi.guidance, source: "openapi" };
304
+ }
305
+ return null;
465
306
  }
466
- async function runInteropMppStage(options) {
467
- const url = `${options.origin}${WELL_KNOWN_MPP_PATH}`;
468
- try {
469
- const response = await options.fetcher(url, {
470
- method: "GET",
471
- headers: { Accept: "application/json", ...options.headers },
472
- signal: options.signal
473
- });
474
- if (!response.ok) {
475
- return {
476
- stage: "interop/mpp",
477
- valid: false,
478
- resources: [],
479
- warnings: []
480
- };
481
- }
482
- const payload = await response.json();
483
- if (!isRecord2(payload)) {
484
- return {
485
- stage: "interop/mpp",
486
- valid: false,
487
- resources: [],
488
- warnings: [
489
- warning("PARSE_FAILED", "warn", "Interop /.well-known/mpp payload is not an object", {
490
- stage: "interop/mpp"
491
- })
492
- ],
493
- ...options.includeRaw ? { raw: payload } : {}
494
- };
495
- }
496
- const warnings = [
497
- warning(
498
- "INTEROP_MPP_USED",
499
- "info",
500
- "Using /.well-known/mpp interop adapter. This is additive and non-canonical.",
501
- {
502
- stage: "interop/mpp",
503
- hint: "Use for registry indexing only, not canonical runtime routing."
504
- }
505
- )
506
- ];
507
- const resources = [];
508
- const fromStringResources = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
509
- for (const entry of fromStringResources) {
510
- const parts = entry.trim().split(/\s+/);
511
- let method = parseMethod(parts[0]);
512
- let path = parts.join(" ");
513
- if (method) {
514
- path = parts.slice(1).join(" ");
515
- } else {
516
- method = "POST";
517
- }
518
- const normalizedPath = normalizePath(path);
519
- resources.push(
520
- createResource(
521
- {
522
- origin: options.origin,
523
- method,
524
- path: normalizedPath,
525
- source: "interop/mpp",
526
- summary: `${method} ${normalizedPath}`,
527
- authHint: "paid",
528
- protocolHints: ["mpp"],
529
- links: { discoveryUrl: url }
530
- },
531
- 0.45,
532
- "unverified"
533
- )
534
- );
535
- }
536
- const fromServiceObjects = Array.isArray(payload.services) ? payload.services.filter((entry) => isRecord2(entry)) : [];
537
- for (const service of fromServiceObjects) {
538
- const path = typeof service.path === "string" ? service.path : void 0;
539
- const method = parseMethod(typeof service.method === "string" ? service.method : void 0) ?? "POST";
540
- if (!path) continue;
541
- const normalizedPath = normalizePath(path);
542
- resources.push(
543
- createResource(
544
- {
545
- origin: options.origin,
546
- method,
547
- path: normalizedPath,
548
- source: "interop/mpp",
549
- summary: typeof service.summary === "string" ? service.summary : `${method} ${normalizedPath}`,
550
- authHint: "paid",
551
- protocolHints: ["mpp"],
552
- links: { discoveryUrl: url }
553
- },
554
- 0.45,
555
- "unverified"
556
- )
557
- );
558
- }
559
- const deduped = /* @__PURE__ */ new Map();
560
- for (const resource of resources) {
561
- if (!deduped.has(resource.resourceKey)) deduped.set(resource.resourceKey, resource);
562
- }
563
- return {
564
- stage: "interop/mpp",
565
- valid: deduped.size > 0,
566
- resources: [...deduped.values()],
567
- warnings,
568
- ...options.includeRaw ? { raw: payload } : {}
569
- };
570
- } catch (error) {
571
- return {
572
- stage: "interop/mpp",
573
- valid: false,
574
- resources: [],
575
- warnings: [
576
- warning(
577
- "FETCH_FAILED",
578
- "warn",
579
- `Interop /.well-known/mpp fetch failed: ${error instanceof Error ? error.message : String(error)}`,
580
- {
581
- stage: "interop/mpp"
582
- }
583
- )
584
- ]
585
- };
307
+ function checkL4ForWellknown(wellKnown) {
308
+ if (wellKnown.instructions) {
309
+ return { guidance: wellKnown.instructions, source: "well-known/x402" };
586
310
  }
311
+ return null;
587
312
  }
588
313
 
589
- // src/core/token.ts
314
+ // src/core/lib/tokens.ts
590
315
  function estimateTokenCount(text) {
591
316
  return Math.ceil(text.length / 4);
592
317
  }
593
318
 
594
- // src/core/openapi.ts
595
- function isRecord3(value) {
596
- return value !== null && typeof value === "object" && !Array.isArray(value);
597
- }
598
- function asString(value) {
599
- return typeof value === "string" && value.length > 0 ? value : void 0;
600
- }
601
- function parsePriceValue(value) {
602
- if (typeof value === "string" && value.length > 0) return value;
603
- if (typeof value === "number" && Number.isFinite(value)) return String(value);
604
- return void 0;
605
- }
606
- function require402Response(operation) {
607
- const responses = operation.responses;
608
- if (!isRecord3(responses)) return false;
609
- return Boolean(responses["402"]);
610
- }
611
- function parseProtocols(paymentInfo) {
612
- const protocols = paymentInfo.protocols;
613
- if (!Array.isArray(protocols)) return [];
614
- return protocols.filter(
615
- (entry) => typeof entry === "string" && entry.length > 0
319
+ // src/core/types/discover.ts
320
+ var GuidanceMode = /* @__PURE__ */ ((GuidanceMode2) => {
321
+ GuidanceMode2["Auto"] = "auto";
322
+ GuidanceMode2["Always"] = "always";
323
+ GuidanceMode2["Never"] = "never";
324
+ return GuidanceMode2;
325
+ })(GuidanceMode || {});
326
+
327
+ // src/runtime/discover.ts
328
+ var GUIDANCE_AUTO_INCLUDE_MAX_TOKENS_LENGTH = 1e3;
329
+ function withGuidance(base, l4, guidanceMode) {
330
+ if (guidanceMode === "never" /* Never */) return { ...base, guidanceAvailable: !!l4 };
331
+ if (!l4) return { ...base, guidanceAvailable: false };
332
+ const tokens = estimateTokenCount(l4.guidance);
333
+ if (guidanceMode === "always" /* Always */ || tokens <= GUIDANCE_AUTO_INCLUDE_MAX_TOKENS_LENGTH) {
334
+ return { ...base, guidanceAvailable: true, guidanceTokens: tokens, guidance: l4.guidance };
335
+ }
336
+ return { ...base, guidanceAvailable: true, guidanceTokens: tokens };
337
+ }
338
+ async function discoverOriginSchema(options) {
339
+ const origin = normalizeOrigin(options.target);
340
+ const guidanceMode = options.guidance ?? "auto" /* Auto */;
341
+ const openApiResult = await getOpenAPI(
342
+ origin,
343
+ options.headers,
344
+ options.signal,
345
+ options.specificationOverrideUrl
616
346
  );
617
- }
618
- function parseAuthMode(operation) {
619
- const auth = operation["x-agentcash-auth"];
620
- if (!isRecord3(auth)) return void 0;
621
- const mode = auth.mode;
622
- if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
623
- return mode;
624
- }
625
- return void 0;
626
- }
627
- async function runOpenApiStage(options) {
628
- const warnings = [];
629
- const resources = [];
630
- let fetchedUrl;
631
- let document;
632
- for (const path of OPENAPI_PATH_CANDIDATES) {
633
- const url = `${options.origin}${path}`;
634
- try {
635
- const response = await options.fetcher(url, {
636
- method: "GET",
637
- headers: { Accept: "application/json", ...options.headers },
638
- signal: options.signal
639
- });
640
- if (!response.ok) {
641
- if (response.status !== 404) {
642
- warnings.push(
643
- warning("FETCH_FAILED", "warn", `OpenAPI fetch failed (${response.status}) at ${url}`, {
644
- stage: "openapi"
645
- })
646
- );
647
- }
648
- continue;
649
- }
650
- const payload = await response.json();
651
- if (!isRecord3(payload)) {
652
- warnings.push(
653
- warning("PARSE_FAILED", "error", `OpenAPI payload at ${url} is not a JSON object`, {
654
- stage: "openapi"
655
- })
656
- );
657
- continue;
658
- }
659
- document = payload;
660
- fetchedUrl = url;
661
- break;
662
- } catch (error) {
663
- warnings.push(
664
- warning(
665
- "FETCH_FAILED",
666
- "warn",
667
- `OpenAPI fetch exception at ${url}: ${error instanceof Error ? error.message : String(error)}`,
668
- { stage: "openapi" }
669
- )
670
- );
671
- }
672
- }
673
- if (!document || !fetchedUrl) {
674
- return {
675
- stage: "openapi",
676
- valid: false,
677
- resources: [],
678
- warnings
347
+ const openApi = openApiResult.isOk() ? openApiResult.value : null;
348
+ if (openApi) {
349
+ const l22 = checkL2ForOpenAPI(openApi);
350
+ const l42 = checkL4ForOpenAPI(openApi);
351
+ const base2 = {
352
+ found: true,
353
+ origin,
354
+ source: "openapi",
355
+ ...l22.title ? { info: { title: l22.title, ...l22.description ? { description: l22.description } : {} } } : {},
356
+ endpoints: l22.routes
679
357
  };
358
+ return withGuidance(base2, l42, guidanceMode);
680
359
  }
681
- const hasTopLevel = typeof document.openapi === "string" && isRecord3(document.info) && typeof document.info.title === "string" && typeof document.info.version === "string" && isRecord3(document.paths);
682
- if (!hasTopLevel) {
683
- warnings.push(
684
- warning("OPENAPI_TOP_LEVEL_INVALID", "error", "OpenAPI required fields are missing", {
685
- stage: "openapi"
686
- })
687
- );
360
+ const wellKnownResult = await getWellKnown(origin, options.headers, options.signal);
361
+ if (wellKnownResult.isErr()) {
688
362
  return {
689
- stage: "openapi",
690
- valid: false,
691
- resources: [],
692
- warnings,
693
- links: { openapiUrl: fetchedUrl },
694
- ...options.includeRaw ? { raw: document } : {}
363
+ found: false,
364
+ origin,
365
+ cause: wellKnownResult.error.cause,
366
+ message: wellKnownResult.error.message
695
367
  };
696
368
  }
697
- const paths = document.paths;
698
- const provenance = isRecord3(document["x-agentcash-provenance"]) ? document["x-agentcash-provenance"] : void 0;
699
- const ownershipProofs = Array.isArray(provenance?.ownershipProofs) ? provenance.ownershipProofs.filter((entry) => typeof entry === "string") : [];
700
- const guidance = isRecord3(document["x-agentcash-guidance"]) ? document["x-agentcash-guidance"] : void 0;
701
- const llmsTxtUrl = asString(guidance?.llmsTxtUrl);
702
- if (ownershipProofs.length > 0) {
703
- warnings.push(
704
- warning("OPENAPI_OWNERSHIP_PROOFS_PRESENT", "info", "OpenAPI ownership proofs detected", {
705
- stage: "openapi"
706
- })
707
- );
708
- }
709
- for (const [rawPath, rawPathItem] of Object.entries(paths)) {
710
- if (!isRecord3(rawPathItem)) {
711
- warnings.push(
712
- warning("OPENAPI_OPERATION_INVALID", "warn", `Path item ${rawPath} is not an object`, {
713
- stage: "openapi"
714
- })
715
- );
716
- continue;
717
- }
718
- for (const [rawMethod, rawOperation] of Object.entries(rawPathItem)) {
719
- const method = parseMethod(rawMethod);
720
- if (!method) continue;
721
- if (!isRecord3(rawOperation)) {
722
- warnings.push(
723
- warning(
724
- "OPENAPI_OPERATION_INVALID",
725
- "warn",
726
- `${rawMethod.toUpperCase()} ${rawPath} is not an object`,
727
- {
728
- stage: "openapi"
729
- }
730
- )
731
- );
732
- continue;
733
- }
734
- const operation = rawOperation;
735
- const summary = asString(operation.summary) ?? asString(operation.description);
736
- if (!summary) {
737
- warnings.push(
738
- warning(
739
- "OPENAPI_SUMMARY_MISSING",
740
- "warn",
741
- `${method} ${rawPath} missing summary/description, using fallback summary`,
742
- { stage: "openapi" }
743
- )
744
- );
745
- }
746
- const authMode = parseAuthMode(operation);
747
- if (!authMode) {
748
- warnings.push(
749
- warning(
750
- "OPENAPI_AUTH_MODE_MISSING",
751
- "error",
752
- `${method} ${rawPath} missing x-agentcash-auth.mode`,
753
- {
754
- stage: "openapi"
755
- }
756
- )
757
- );
758
- continue;
759
- }
760
- if ((authMode === "paid" || authMode === "siwx") && !require402Response(operation)) {
761
- warnings.push(
762
- warning(
763
- "OPENAPI_402_MISSING",
764
- "error",
765
- `${method} ${rawPath} requires 402 response for authMode=${authMode}`,
766
- { stage: "openapi" }
767
- )
768
- );
769
- continue;
770
- }
771
- const paymentInfo = isRecord3(operation["x-payment-info"]) ? operation["x-payment-info"] : void 0;
772
- const protocols = paymentInfo ? parseProtocols(paymentInfo) : [];
773
- if (authMode === "paid" && protocols.length === 0) {
774
- warnings.push(
775
- warning(
776
- "OPENAPI_PAID_PROTOCOLS_MISSING",
777
- "error",
778
- `${method} ${rawPath} must define x-payment-info.protocols when authMode=paid`,
779
- { stage: "openapi" }
780
- )
781
- );
782
- continue;
783
- }
784
- let pricing;
785
- let priceHint;
786
- if (paymentInfo && authMode === "paid") {
787
- const pricingModeRaw = asString(paymentInfo.pricingMode);
788
- const price = parsePriceValue(paymentInfo.price);
789
- const minPrice = parsePriceValue(paymentInfo.minPrice);
790
- const maxPrice = parsePriceValue(paymentInfo.maxPrice);
791
- const inferredPricingMode = pricingModeRaw ?? (price ? "fixed" : minPrice && maxPrice ? "range" : "quote");
792
- if (inferredPricingMode !== "fixed" && inferredPricingMode !== "range" && inferredPricingMode !== "quote") {
793
- warnings.push(
794
- warning(
795
- "OPENAPI_PRICING_INVALID",
796
- "error",
797
- `${method} ${rawPath} has invalid pricingMode`,
798
- {
799
- stage: "openapi"
800
- }
801
- )
802
- );
803
- continue;
804
- }
805
- if (inferredPricingMode === "fixed" && !price) {
806
- warnings.push(
807
- warning(
808
- "OPENAPI_PRICING_INVALID",
809
- "error",
810
- `${method} ${rawPath} fixed pricing requires price`,
811
- {
812
- stage: "openapi"
813
- }
814
- )
815
- );
816
- continue;
817
- }
818
- if (inferredPricingMode === "range") {
819
- if (!minPrice || !maxPrice) {
820
- warnings.push(
821
- warning(
822
- "OPENAPI_PRICING_INVALID",
823
- "error",
824
- `${method} ${rawPath} range pricing requires minPrice and maxPrice`,
825
- { stage: "openapi" }
826
- )
827
- );
828
- continue;
829
- }
830
- const min = Number(minPrice);
831
- const max = Number(maxPrice);
832
- if (!Number.isFinite(min) || !Number.isFinite(max) || min > max) {
833
- warnings.push(
834
- warning(
835
- "OPENAPI_PRICING_INVALID",
836
- "error",
837
- `${method} ${rawPath} range pricing requires numeric minPrice <= maxPrice`,
838
- { stage: "openapi" }
839
- )
840
- );
841
- continue;
842
- }
843
- }
844
- pricing = {
845
- pricingMode: inferredPricingMode,
846
- ...price ? { price } : {},
847
- ...minPrice ? { minPrice } : {},
848
- ...maxPrice ? { maxPrice } : {}
849
- };
850
- priceHint = inferredPricingMode === "fixed" ? price : inferredPricingMode === "range" ? `${minPrice}-${maxPrice}` : maxPrice;
851
- }
852
- const resource = createResource(
853
- {
854
- origin: options.origin,
855
- method,
856
- path: normalizePath(rawPath),
857
- source: "openapi",
858
- summary: summary ?? `${method} ${normalizePath(rawPath)}`,
859
- authHint: authMode,
860
- protocolHints: protocols,
861
- ...priceHint ? { priceHint } : {},
862
- ...pricing ? { pricing } : {},
863
- links: {
864
- openapiUrl: fetchedUrl,
865
- ...llmsTxtUrl ? { llmsTxtUrl } : {}
866
- }
867
- },
868
- 0.95,
869
- ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
870
- );
871
- resources.push(resource);
872
- }
873
- }
874
- if (llmsTxtUrl) {
875
- try {
876
- const llmsResponse = await options.fetcher(llmsTxtUrl, {
877
- method: "GET",
878
- headers: { Accept: "text/plain", ...options.headers },
879
- signal: options.signal
880
- });
881
- if (llmsResponse.ok) {
882
- const llmsText = await llmsResponse.text();
883
- const tokenCount = estimateTokenCount(llmsText);
884
- if (tokenCount > LLMS_TOKEN_WARNING_THRESHOLD) {
885
- warnings.push(
886
- warning(
887
- "LLMSTXT_TOO_LARGE",
888
- "warn",
889
- `llms.txt estimated ${tokenCount} tokens (threshold ${LLMS_TOKEN_WARNING_THRESHOLD})`,
890
- {
891
- stage: "openapi",
892
- hint: "Keep llms.txt concise and domain-level only."
893
- }
894
- )
895
- );
896
- }
897
- if (options.includeRaw) {
898
- return {
899
- stage: "openapi",
900
- valid: resources.length > 0,
901
- resources,
902
- warnings,
903
- links: { openapiUrl: fetchedUrl, llmsTxtUrl },
904
- raw: {
905
- openapi: document,
906
- llmsTxt: llmsText
907
- }
908
- };
909
- }
910
- } else {
911
- warnings.push(
912
- warning(
913
- "LLMSTXT_FETCH_FAILED",
914
- "warn",
915
- `llms.txt fetch failed (${llmsResponse.status})`,
916
- {
917
- stage: "openapi"
918
- }
919
- )
920
- );
921
- }
922
- } catch (error) {
923
- warnings.push(
924
- warning(
925
- "LLMSTXT_FETCH_FAILED",
926
- "warn",
927
- `llms.txt fetch failed: ${error instanceof Error ? error.message : String(error)}`,
928
- { stage: "openapi" }
929
- )
930
- );
931
- }
932
- }
369
+ const wellKnown = wellKnownResult.value;
370
+ if (!wellKnown) return { found: false, origin, cause: "not_found" };
371
+ const l2 = checkL2ForWellknown(wellKnown);
372
+ const l4 = checkL4ForWellknown(wellKnown);
373
+ const base = {
374
+ found: true,
375
+ origin,
376
+ source: "well-known/x402",
377
+ endpoints: l2.routes
378
+ };
379
+ return withGuidance(base, l4, guidanceMode);
380
+ }
381
+
382
+ // src/core/source/probe/index.ts
383
+ import { ResultAsync as ResultAsync4 } from "neverthrow";
384
+
385
+ // src/core/protocols/x402/v1/schema.ts
386
+ function extractSchemas(accepts) {
387
+ const first = accepts[0];
388
+ if (!isRecord(first)) return {};
389
+ const outputSchema = isRecord(first.outputSchema) ? first.outputSchema : void 0;
933
390
  return {
934
- stage: "openapi",
935
- valid: resources.length > 0,
936
- resources,
937
- warnings,
938
- links: {
939
- openapiUrl: fetchedUrl,
940
- ...llmsTxtUrl ? { llmsTxtUrl } : {}
941
- },
942
- ...options.includeRaw ? { raw: { openapi: document } } : {}
391
+ ...outputSchema && isRecord(outputSchema.input) ? { inputSchema: outputSchema.input } : {},
392
+ ...outputSchema && isRecord(outputSchema.output) ? { outputSchema: outputSchema.output } : {}
943
393
  };
944
394
  }
945
395
 
946
- // src/core/probe.ts
947
- function detectAuthHintFrom402Payload(payload) {
948
- if (payload && typeof payload === "object") {
949
- const asRecord = payload;
950
- const extensions = asRecord.extensions;
951
- if (extensions && typeof extensions === "object") {
952
- const extRecord = extensions;
953
- if (extRecord["sign-in-with-x"]) return "siwx";
954
- }
955
- }
956
- return "paid";
396
+ // src/core/protocols/x402/shared.ts
397
+ function asNonEmptyString(value) {
398
+ if (typeof value !== "string") return void 0;
399
+ const trimmed = value.trim();
400
+ return trimmed.length > 0 ? trimmed : void 0;
957
401
  }
958
- function detectProtocols(response) {
959
- const protocols = /* @__PURE__ */ new Set();
960
- const directHeader = response.headers.get("x-payment-protocol");
961
- if (directHeader) {
962
- for (const part of directHeader.split(",")) {
963
- const protocol = part.trim().toLowerCase();
964
- if (protocol) protocols.add(protocol);
965
- }
966
- }
967
- const authHeader = response.headers.get("www-authenticate")?.toLowerCase() ?? "";
968
- if (authHeader.includes("x402")) protocols.add("x402");
969
- if (isMmmEnabled() && authHeader.includes("mpp")) protocols.add("mpp");
970
- return [...protocols];
402
+ function asPositiveNumber(value) {
403
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
971
404
  }
972
- async function runProbeStage(options) {
973
- const candidates = options.probeCandidates ?? [];
974
- if (candidates.length === 0) {
975
- return {
976
- stage: "probe",
977
- valid: false,
978
- resources: [],
979
- warnings: [
980
- warning(
981
- "PROBE_STAGE_SKIPPED",
982
- "info",
983
- "Probe stage skipped because no probe candidates were provided.",
984
- { stage: "probe" }
985
- )
986
- ]
987
- };
988
- }
989
- const resources = [];
990
- const warnings = [];
991
- for (const candidate of candidates) {
992
- const path = normalizePath(candidate.path);
993
- const methods = candidate.methods?.length ? candidate.methods : DEFAULT_PROBE_METHODS;
994
- for (const method of methods) {
995
- const url = `${options.origin}${path}`;
996
- try {
997
- const response = await options.fetcher(url, {
998
- method,
999
- headers: {
1000
- Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
1001
- ...options.headers
1002
- },
1003
- signal: options.signal
1004
- });
1005
- if (response.status !== 402 && (response.status < 200 || response.status >= 300)) {
1006
- continue;
1007
- }
405
+ function pushIssue(issues, issue) {
406
+ issues.push({ stage: "payment_required", ...issue });
407
+ }
408
+
409
+ // src/core/protocols/x402/v1/network.ts
410
+ function validateNetwork(networkRaw, index, issues) {
411
+ if (networkRaw === "solana-mainnet-beta") {
412
+ pushIssue(issues, {
413
+ code: "NETWORK_SOLANA_ALIAS_INVALID",
414
+ severity: "error",
415
+ message: `Accept at index ${index} uses invalid Solana network alias`,
416
+ hint: "Use 'solana' (v1) or canonical Solana CAIP reference (v2).",
417
+ path: `accepts[${index}].network`,
418
+ expected: "solana",
419
+ actual: networkRaw
420
+ });
421
+ return void 0;
422
+ }
423
+ if (networkRaw.startsWith("solana-") && networkRaw !== "solana-devnet") {
424
+ pushIssue(issues, {
425
+ code: "NETWORK_SOLANA_ALIAS_INVALID",
426
+ severity: "error",
427
+ message: `Accept at index ${index} uses unsupported Solana network alias`,
428
+ hint: "Use 'solana' or 'solana-devnet' for v1 payloads.",
429
+ path: `accepts[${index}].network`,
430
+ actual: networkRaw
431
+ });
432
+ return void 0;
433
+ }
434
+ return networkRaw;
435
+ }
436
+
437
+ // src/core/protocols/x402/v1/accepts.ts
438
+ function validateAccept(acceptRaw, index, issues) {
439
+ if (!isRecord(acceptRaw)) {
440
+ pushIssue(issues, {
441
+ code: "X402_ACCEPT_ENTRY_INVALID",
442
+ severity: "error",
443
+ message: `Accept at index ${index} must be an object`,
444
+ path: `accepts[${index}]`
445
+ });
446
+ return null;
447
+ }
448
+ const scheme = asNonEmptyString(acceptRaw.scheme);
449
+ const networkRaw = asNonEmptyString(acceptRaw.network);
450
+ const payTo = asNonEmptyString(acceptRaw.payTo);
451
+ const asset = asNonEmptyString(acceptRaw.asset);
452
+ const amount = asNonEmptyString(acceptRaw.maxAmountRequired);
453
+ const maxTimeoutSeconds = asPositiveNumber(acceptRaw.maxTimeoutSeconds);
454
+ if (!scheme)
455
+ pushIssue(issues, {
456
+ code: "X402_ACCEPT_ENTRY_INVALID",
457
+ severity: "error",
458
+ message: `Accept at index ${index} is missing scheme`,
459
+ path: `accepts[${index}].scheme`
460
+ });
461
+ if (!networkRaw)
462
+ pushIssue(issues, {
463
+ code: "X402_ACCEPT_ENTRY_INVALID",
464
+ severity: "error",
465
+ message: `Accept at index ${index} is missing network`,
466
+ path: `accepts[${index}].network`
467
+ });
468
+ if (!amount)
469
+ pushIssue(issues, {
470
+ code: "X402_ACCEPT_ENTRY_INVALID",
471
+ severity: "error",
472
+ message: `Accept at index ${index} is missing maxAmountRequired`,
473
+ path: `accepts[${index}].maxAmountRequired`
474
+ });
475
+ if (!payTo)
476
+ pushIssue(issues, {
477
+ code: "X402_ACCEPT_ENTRY_INVALID",
478
+ severity: "error",
479
+ message: `Accept at index ${index} is missing payTo`,
480
+ path: `accepts[${index}].payTo`
481
+ });
482
+ if (!asset)
483
+ pushIssue(issues, {
484
+ code: "X402_ACCEPT_ENTRY_INVALID",
485
+ severity: "error",
486
+ message: `Accept at index ${index} is missing asset`,
487
+ path: `accepts[${index}].asset`
488
+ });
489
+ if (!maxTimeoutSeconds)
490
+ pushIssue(issues, {
491
+ code: "X402_ACCEPT_ENTRY_INVALID",
492
+ severity: "error",
493
+ message: `Accept at index ${index} is missing or has invalid maxTimeoutSeconds`,
494
+ path: `accepts[${index}].maxTimeoutSeconds`
495
+ });
496
+ const normalizedNetwork = networkRaw ? validateNetwork(networkRaw, index, issues) : void 0;
497
+ return {
498
+ index,
499
+ network: normalizedNetwork ?? networkRaw ?? "unknown",
500
+ networkRaw: networkRaw ?? "unknown",
501
+ scheme,
502
+ asset,
503
+ payTo,
504
+ amount,
505
+ maxTimeoutSeconds
506
+ };
507
+ }
508
+ function validateAccepts(accepts, issues) {
509
+ return accepts.flatMap((accept, index) => {
510
+ const result = validateAccept(accept, index, issues);
511
+ return result ? [result] : [];
512
+ });
513
+ }
514
+
515
+ // src/core/protocols/x402/v1/payment-options.ts
516
+ function extractPaymentOptions(accepts) {
517
+ return accepts.flatMap((accept) => {
518
+ if (!isRecord(accept)) return [];
519
+ const network = asNonEmptyString(accept.network);
520
+ const asset = asNonEmptyString(accept.asset);
521
+ const maxAmountRequired = asNonEmptyString(accept.maxAmountRequired);
522
+ if (!network || !asset || !maxAmountRequired) return [];
523
+ const scheme = asNonEmptyString(accept.scheme);
524
+ const payTo = asNonEmptyString(accept.payTo);
525
+ const maxTimeoutSeconds = asPositiveNumber(accept.maxTimeoutSeconds);
526
+ return [
527
+ {
528
+ protocol: "x402",
529
+ version: 1,
530
+ network,
531
+ asset,
532
+ maxAmountRequired,
533
+ ...scheme ? { scheme } : {},
534
+ ...payTo ? { payTo } : {},
535
+ ...maxTimeoutSeconds ? { maxTimeoutSeconds } : {}
536
+ }
537
+ ];
538
+ });
539
+ }
540
+
541
+ // src/core/protocols/x402/v2/schema.ts
542
+ function extractSchemas2(payload) {
543
+ if (!isRecord(payload)) return {};
544
+ const extensions = isRecord(payload.extensions) ? payload.extensions : void 0;
545
+ const bazaar = extensions && isRecord(extensions.bazaar) ? extensions.bazaar : void 0;
546
+ const schema = bazaar && isRecord(bazaar.schema) ? bazaar.schema : void 0;
547
+ if (!schema) return {};
548
+ const schemaProps = isRecord(schema.properties) ? schema.properties : void 0;
549
+ if (!schemaProps) return {};
550
+ const inputProps = isRecord(schemaProps.input) ? schemaProps.input : void 0;
551
+ const inputProperties = inputProps && isRecord(inputProps.properties) ? inputProps.properties : void 0;
552
+ const inputSchema = (inputProperties && isRecord(inputProperties.body) ? inputProperties.body : void 0) ?? (inputProperties && isRecord(inputProperties.queryParams) ? inputProperties.queryParams : void 0);
553
+ const outputProps = isRecord(schemaProps.output) ? schemaProps.output : void 0;
554
+ const outputProperties = outputProps && isRecord(outputProps.properties) ? outputProps.properties : void 0;
555
+ const outputSchema = outputProperties && isRecord(outputProperties.example) ? outputProperties.example : void 0;
556
+ return {
557
+ ...inputSchema ? { inputSchema } : {},
558
+ ...outputSchema ? { outputSchema } : {}
559
+ };
560
+ }
561
+
562
+ // src/core/protocols/x402/v2/solana.ts
563
+ var CANONICAL_BY_REF = {
564
+ "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana",
565
+ EtWTRABZaYq6iMfeYKouRu166VU2xqa1: "solana-devnet",
566
+ "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "solana-testnet"
567
+ };
568
+ var ALIAS_BY_REF = {
569
+ mainnet: "solana",
570
+ devnet: "solana-devnet",
571
+ testnet: "solana-testnet"
572
+ };
573
+ function validateSolanaReference(reference, networkRaw, index, issues) {
574
+ const canonical = CANONICAL_BY_REF[reference];
575
+ if (canonical) return canonical;
576
+ const alias = ALIAS_BY_REF[reference];
577
+ if (alias) {
578
+ pushIssue(issues, {
579
+ code: "NETWORK_SOLANA_ALIAS_COMPAT",
580
+ severity: "warn",
581
+ message: `Accept at index ${index} uses compatibility Solana reference '${reference}'`,
582
+ hint: "Use canonical Solana CAIP references for deterministic behavior.",
583
+ path: `accepts[${index}].network`,
584
+ actual: networkRaw
585
+ });
586
+ return alias;
587
+ }
588
+ pushIssue(issues, {
589
+ code: "NETWORK_REFERENCE_UNKNOWN",
590
+ severity: "error",
591
+ message: `Accept at index ${index} uses unknown Solana reference '${reference}'`,
592
+ hint: "Use canonical references for mainnet/devnet/testnet.",
593
+ path: `accepts[${index}].network`,
594
+ actual: networkRaw
595
+ });
596
+ return void 0;
597
+ }
598
+
599
+ // src/core/protocols/x402/v2/network.ts
600
+ function validateNetwork2(networkRaw, index, issues) {
601
+ if (!/^[^:\s]+:[^:\s]+$/.test(networkRaw)) {
602
+ pushIssue(issues, {
603
+ code: "NETWORK_CAIP2_INVALID",
604
+ severity: "error",
605
+ message: `Accept at index ${index} has invalid CAIP-2 network format`,
606
+ hint: "Expected '<namespace>:<reference>', e.g. 'eip155:8453' or 'solana:5eykt4...'.",
607
+ path: `accepts[${index}].network`,
608
+ expected: "<namespace>:<reference>",
609
+ actual: networkRaw
610
+ });
611
+ return void 0;
612
+ }
613
+ const [namespace, reference] = networkRaw.split(":");
614
+ if (namespace === "eip155") {
615
+ if (!/^\d+$/.test(reference)) {
616
+ pushIssue(issues, {
617
+ code: "NETWORK_EIP155_REFERENCE_INVALID",
618
+ severity: "error",
619
+ message: `Accept at index ${index} has invalid eip155 reference`,
620
+ hint: "Use a numeric chain id, e.g. 'eip155:8453'.",
621
+ path: `accepts[${index}].network`,
622
+ expected: "eip155:<numeric-chain-id>",
623
+ actual: networkRaw
624
+ });
625
+ return void 0;
626
+ }
627
+ return networkRaw;
628
+ }
629
+ if (namespace === "solana") {
630
+ return validateSolanaReference(reference, networkRaw, index, issues);
631
+ }
632
+ return networkRaw;
633
+ }
634
+
635
+ // src/core/protocols/x402/v2/accepts.ts
636
+ function validateAccept2(acceptRaw, index, issues) {
637
+ if (!isRecord(acceptRaw)) {
638
+ pushIssue(issues, {
639
+ code: "X402_ACCEPT_ENTRY_INVALID",
640
+ severity: "error",
641
+ message: `Accept at index ${index} must be an object`,
642
+ path: `accepts[${index}]`
643
+ });
644
+ return null;
645
+ }
646
+ const scheme = asNonEmptyString(acceptRaw.scheme);
647
+ const networkRaw = asNonEmptyString(acceptRaw.network);
648
+ const payTo = asNonEmptyString(acceptRaw.payTo);
649
+ const asset = asNonEmptyString(acceptRaw.asset);
650
+ const amount = asNonEmptyString(acceptRaw.amount);
651
+ const maxTimeoutSeconds = asPositiveNumber(acceptRaw.maxTimeoutSeconds);
652
+ if (!scheme)
653
+ pushIssue(issues, {
654
+ code: "X402_ACCEPT_ENTRY_INVALID",
655
+ severity: "error",
656
+ message: `Accept at index ${index} is missing scheme`,
657
+ path: `accepts[${index}].scheme`
658
+ });
659
+ if (!networkRaw)
660
+ pushIssue(issues, {
661
+ code: "X402_ACCEPT_ENTRY_INVALID",
662
+ severity: "error",
663
+ message: `Accept at index ${index} is missing network`,
664
+ path: `accepts[${index}].network`
665
+ });
666
+ if (!amount)
667
+ pushIssue(issues, {
668
+ code: "X402_ACCEPT_ENTRY_INVALID",
669
+ severity: "error",
670
+ message: `Accept at index ${index} is missing amount`,
671
+ path: `accepts[${index}].amount`
672
+ });
673
+ if (!payTo)
674
+ pushIssue(issues, {
675
+ code: "X402_ACCEPT_ENTRY_INVALID",
676
+ severity: "error",
677
+ message: `Accept at index ${index} is missing payTo`,
678
+ path: `accepts[${index}].payTo`
679
+ });
680
+ if (!asset)
681
+ pushIssue(issues, {
682
+ code: "X402_ACCEPT_ENTRY_INVALID",
683
+ severity: "error",
684
+ message: `Accept at index ${index} is missing asset`,
685
+ path: `accepts[${index}].asset`
686
+ });
687
+ if (!maxTimeoutSeconds)
688
+ pushIssue(issues, {
689
+ code: "X402_ACCEPT_ENTRY_INVALID",
690
+ severity: "error",
691
+ message: `Accept at index ${index} is missing or has invalid maxTimeoutSeconds`,
692
+ path: `accepts[${index}].maxTimeoutSeconds`
693
+ });
694
+ const normalizedNetwork = networkRaw ? validateNetwork2(networkRaw, index, issues) : void 0;
695
+ return {
696
+ index,
697
+ network: normalizedNetwork ?? networkRaw ?? "unknown",
698
+ networkRaw: networkRaw ?? "unknown",
699
+ scheme,
700
+ asset,
701
+ payTo,
702
+ amount,
703
+ maxTimeoutSeconds
704
+ };
705
+ }
706
+ function validateAccepts2(accepts, issues) {
707
+ return accepts.flatMap((accept, index) => {
708
+ const result = validateAccept2(accept, index, issues);
709
+ return result ? [result] : [];
710
+ });
711
+ }
712
+
713
+ // src/core/protocols/x402/v2/payment-options.ts
714
+ function extractPaymentOptions2(accepts) {
715
+ return accepts.flatMap((accept) => {
716
+ if (!isRecord(accept)) return [];
717
+ const network = asNonEmptyString(accept.network);
718
+ const asset = asNonEmptyString(accept.asset);
719
+ const amount = asNonEmptyString(accept.amount);
720
+ if (!network || !asset || !amount) return [];
721
+ const scheme = asNonEmptyString(accept.scheme);
722
+ const payTo = asNonEmptyString(accept.payTo);
723
+ const maxTimeoutSeconds = asPositiveNumber(accept.maxTimeoutSeconds);
724
+ return [
725
+ {
726
+ protocol: "x402",
727
+ version: 2,
728
+ network,
729
+ asset,
730
+ amount,
731
+ ...scheme ? { scheme } : {},
732
+ ...payTo ? { payTo } : {},
733
+ ...maxTimeoutSeconds ? { maxTimeoutSeconds } : {}
734
+ }
735
+ ];
736
+ });
737
+ }
738
+
739
+ // src/core/protocols/x402/index.ts
740
+ function parseVersion(payload) {
741
+ if (!isRecord(payload)) return void 0;
742
+ const v = payload.x402Version;
743
+ if (v === 1 || v === 2) return v;
744
+ return void 0;
745
+ }
746
+ function detectAuthHint(payload) {
747
+ if (isRecord(payload) && isRecord(payload.extensions)) {
748
+ if (payload.extensions["api-key"]) return "apiKey+paid";
749
+ if (payload.extensions["sign-in-with-x"]) return "siwx";
750
+ }
751
+ return "paid";
752
+ }
753
+ function extractPaymentOptions3(payload) {
754
+ if (!isRecord(payload)) return [];
755
+ const version = parseVersion(payload);
756
+ const accepts = Array.isArray(payload.accepts) ? payload.accepts : [];
757
+ if (version === 1) return extractPaymentOptions(accepts);
758
+ if (version === 2) return extractPaymentOptions2(accepts);
759
+ return [];
760
+ }
761
+ function extractSchemas3(payload) {
762
+ if (!isRecord(payload)) return {};
763
+ const version = parseVersion(payload);
764
+ if (version === 2) return extractSchemas2(payload);
765
+ if (version === 1) {
766
+ const accepts = Array.isArray(payload.accepts) ? payload.accepts : [];
767
+ return extractSchemas(accepts);
768
+ }
769
+ return {};
770
+ }
771
+ function parseInputSchema(payload) {
772
+ return extractSchemas3(payload).inputSchema;
773
+ }
774
+ function parseOutputSchema(payload) {
775
+ return extractSchemas3(payload).outputSchema;
776
+ }
777
+
778
+ // src/core/protocols/x402/v1/parse-payment-required.ts
779
+ async function parsePaymentRequiredBody(response) {
780
+ const payload = await response.clone().json();
781
+ if (!isRecord(payload) || payload.x402Version !== 1) return null;
782
+ return payload;
783
+ }
784
+
785
+ // src/core/protocols/x402/v2/parse-payment-required.ts
786
+ function parsePaymentRequiredBody2(headerValue) {
787
+ const payload = JSON.parse(atob(headerValue));
788
+ if (!isRecord(payload) || payload.x402Version !== 2) return null;
789
+ return payload;
790
+ }
791
+
792
+ // src/core/source/probe/index.ts
793
+ function isUsableStatus(status) {
794
+ return status === 402 || status >= 200 && status < 300;
795
+ }
796
+ function detectProtocols(response) {
797
+ const protocols = /* @__PURE__ */ new Set();
798
+ const directHeader = response.headers.get("x-payment-protocol");
799
+ if (directHeader) {
800
+ for (const part of directHeader.split(",")) {
801
+ const protocol = part.trim().toLowerCase();
802
+ if (protocol) protocols.add(protocol);
803
+ }
804
+ }
805
+ const authHeader = response.headers.get("www-authenticate")?.toLowerCase() ?? "";
806
+ if (authHeader.includes("x402")) protocols.add("x402");
807
+ if (isMmmEnabled() && authHeader.includes("mpp")) protocols.add("mpp");
808
+ return [...protocols];
809
+ }
810
+ function buildProbeUrl(url, method, inputBody) {
811
+ if (!inputBody || method !== "GET" && method !== "HEAD") return url;
812
+ const u = new URL(url);
813
+ for (const [key, value] of Object.entries(inputBody)) {
814
+ u.searchParams.set(key, String(value));
815
+ }
816
+ return u.toString();
817
+ }
818
+ function probeMethod(url, method, path, headers, signal, inputBody) {
819
+ const hasBody = inputBody !== void 0 && method !== "GET" && method !== "HEAD";
820
+ const probeUrl = buildProbeUrl(url, method, inputBody);
821
+ return fetchSafe(probeUrl, {
822
+ method,
823
+ headers: {
824
+ Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
825
+ ...hasBody ? { "Content-Type": "application/json" } : {},
826
+ ...headers
827
+ },
828
+ ...hasBody ? { body: JSON.stringify(inputBody) } : {},
829
+ signal
830
+ }).andThen((response) => {
831
+ if (!isUsableStatus(response.status)) return ResultAsync4.fromSafePromise(Promise.resolve(null));
832
+ return ResultAsync4.fromSafePromise(
833
+ (async () => {
1008
834
  let authHint = response.status === 402 ? "paid" : "unprotected";
835
+ let paymentRequiredBody;
1009
836
  if (response.status === 402) {
1010
837
  try {
1011
- const payload = await response.clone().json();
1012
- authHint = detectAuthHintFrom402Payload(payload);
838
+ const headerValue = response.headers.get("payment-required");
839
+ const parsed = headerValue !== null ? parsePaymentRequiredBody2(headerValue) : await parsePaymentRequiredBody(response);
840
+ if (parsed !== null) {
841
+ paymentRequiredBody = parsed;
842
+ authHint = detectAuthHint(paymentRequiredBody);
843
+ }
1013
844
  } catch {
1014
845
  }
1015
846
  }
1016
- const protocolHints = detectProtocols(response);
1017
- resources.push(
1018
- createResource(
1019
- {
1020
- origin: options.origin,
1021
- method,
1022
- path,
1023
- source: "probe",
1024
- summary: `${method} ${path}`,
1025
- authHint,
1026
- ...protocolHints.length ? { protocolHints } : {}
1027
- },
1028
- 0.6,
1029
- "runtime_verified"
1030
- )
1031
- );
1032
- } catch (error) {
1033
- warnings.push(
1034
- warning(
1035
- "FETCH_FAILED",
1036
- "info",
1037
- `Probe ${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`,
1038
- { stage: "probe" }
1039
- )
1040
- );
1041
- }
847
+ const protocols = detectProtocols(response);
848
+ const wwwAuthenticate = response.headers.get("www-authenticate") ?? void 0;
849
+ return {
850
+ path,
851
+ method,
852
+ authHint,
853
+ ...protocols.length ? { protocols } : {},
854
+ ...paymentRequiredBody !== void 0 ? { paymentRequiredBody } : {},
855
+ ...wwwAuthenticate ? { wwwAuthenticate } : {}
856
+ };
857
+ })()
858
+ );
859
+ });
860
+ }
861
+ function getProbe(url, headers, signal, inputBody) {
862
+ const path = normalizePath(new URL(url).pathname || "/");
863
+ return ResultAsync4.fromSafePromise(
864
+ Promise.all(
865
+ [...HTTP_METHODS].map(
866
+ (method) => probeMethod(url, method, path, headers, signal, inputBody).match(
867
+ (result) => result,
868
+ () => null
869
+ )
870
+ )
871
+ ).then((results) => results.filter((r) => r !== null))
872
+ );
873
+ }
874
+
875
+ // src/core/protocols/mpp/index.ts
876
+ var TEMPO_DEFAULT_CHAIN_ID = 4217;
877
+ function parseAuthParams(segment) {
878
+ const params = {};
879
+ const re = /(\w+)=(?:"([^"]*)"|'([^']*)')/g;
880
+ let match;
881
+ while ((match = re.exec(segment)) !== null) {
882
+ params[match[1]] = match[2] ?? match[3] ?? "";
883
+ }
884
+ return params;
885
+ }
886
+ function extractPaymentOptions4(wwwAuthenticate) {
887
+ if (!isMmmEnabled() || !wwwAuthenticate) return [];
888
+ const options = [];
889
+ for (const segment of wwwAuthenticate.split(/,\s*(?=Payment\s)/i)) {
890
+ const stripped = segment.replace(/^Payment\s+/i, "").trim();
891
+ const params = parseAuthParams(stripped);
892
+ const paymentMethod = params["method"];
893
+ const intent = params["intent"];
894
+ const realm = params["realm"];
895
+ const description = params["description"];
896
+ const requestStr = params["request"];
897
+ if (!paymentMethod || !intent || !realm || !requestStr) continue;
898
+ let request;
899
+ try {
900
+ const parsed = JSON.parse(requestStr);
901
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
902
+ request = parsed;
903
+ } catch {
904
+ continue;
1042
905
  }
906
+ const asset = typeof request["currency"] === "string" ? request["currency"] : void 0;
907
+ const amountRaw = request["amount"];
908
+ const amount = typeof amountRaw === "string" ? amountRaw : typeof amountRaw === "number" ? String(amountRaw) : void 0;
909
+ const decimals = typeof request["decimals"] === "number" ? request["decimals"] : void 0;
910
+ const payTo = typeof request["recipient"] === "string" ? request["recipient"] : void 0;
911
+ const methodDetails = request["methodDetails"];
912
+ const chainId = methodDetails !== null && typeof methodDetails === "object" && !Array.isArray(methodDetails) && typeof methodDetails["chainId"] === "number" ? methodDetails["chainId"] : TEMPO_DEFAULT_CHAIN_ID;
913
+ if (!asset || !amount) continue;
914
+ options.push({
915
+ protocol: "mpp",
916
+ // isMmmEnabled
917
+ paymentMethod,
918
+ intent,
919
+ realm,
920
+ network: `tempo:${String(chainId)}`,
921
+ // isMmmEnabled
922
+ asset,
923
+ amount,
924
+ ...decimals != null ? { decimals } : {},
925
+ ...payTo ? { payTo } : {},
926
+ ...description ? { description } : {}
927
+ });
1043
928
  }
1044
- return {
1045
- stage: "probe",
1046
- valid: resources.length > 0,
1047
- resources,
1048
- warnings
1049
- };
929
+ return options;
1050
930
  }
1051
931
 
1052
- // src/core/upgrade.ts
1053
- var UPGRADE_WARNING_CODES = [
1054
- "LEGACY_WELL_KNOWN_USED",
1055
- "LEGACY_DNS_USED",
1056
- "LEGACY_DNS_PLAIN_URL",
1057
- "LEGACY_INSTRUCTIONS_USED",
1058
- "LEGACY_OWNERSHIP_PROOFS_USED",
1059
- "OPENAPI_AUTH_MODE_MISSING",
1060
- "OPENAPI_TOP_LEVEL_INVALID"
1061
- ];
1062
- var UPGRADE_WARNING_CODE_SET = new Set(UPGRADE_WARNING_CODES);
1063
- function computeUpgradeSignal(warnings) {
1064
- const reasons = warnings.map((entry) => entry.code).filter((code, index, list) => list.indexOf(code) === index).filter((code) => UPGRADE_WARNING_CODE_SET.has(code));
932
+ // src/core/layers/l3.ts
933
+ function findMatchingOpenApiPath(paths, targetPath) {
934
+ const exact = paths[targetPath];
935
+ if (isRecord(exact)) return { matchedPath: targetPath, pathItem: exact };
936
+ for (const [specPath, entry] of Object.entries(paths)) {
937
+ if (!isRecord(entry)) continue;
938
+ const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
939
+ const regex = new RegExp(`^${pattern}$`);
940
+ if (regex.test(targetPath)) {
941
+ return { matchedPath: specPath, pathItem: entry };
942
+ }
943
+ }
944
+ return null;
945
+ }
946
+ function resolveRef(document, ref, seen) {
947
+ if (!ref.startsWith("#/")) return void 0;
948
+ if (seen.has(ref)) return { $circular: ref };
949
+ seen.add(ref);
950
+ const parts = ref.slice(2).split("/");
951
+ let current = document;
952
+ for (const part of parts) {
953
+ if (!isRecord(current)) return void 0;
954
+ current = current[part];
955
+ if (current === void 0) return void 0;
956
+ }
957
+ if (isRecord(current)) return resolveRefs(document, current, seen);
958
+ return current;
959
+ }
960
+ function resolveRefs(document, obj, seen, depth = 0) {
961
+ if (depth > 4) return obj;
962
+ const resolved = {};
963
+ for (const [key, value] of Object.entries(obj)) {
964
+ if (key === "$ref" && typeof value === "string") {
965
+ const deref = resolveRef(document, value, seen);
966
+ if (isRecord(deref)) {
967
+ Object.assign(resolved, deref);
968
+ } else {
969
+ resolved[key] = value;
970
+ }
971
+ continue;
972
+ }
973
+ if (isRecord(value)) {
974
+ resolved[key] = resolveRefs(document, value, seen, depth + 1);
975
+ continue;
976
+ }
977
+ if (Array.isArray(value)) {
978
+ resolved[key] = value.map(
979
+ (item) => isRecord(item) ? resolveRefs(document, item, seen, depth + 1) : item
980
+ );
981
+ continue;
982
+ }
983
+ resolved[key] = value;
984
+ }
985
+ return resolved;
986
+ }
987
+ function extractRequestBodySchema(operationSchema) {
988
+ const requestBody = operationSchema.requestBody;
989
+ if (!isRecord(requestBody)) return void 0;
990
+ const content = requestBody.content;
991
+ if (!isRecord(content)) return void 0;
992
+ const jsonMediaType = content["application/json"];
993
+ if (isRecord(jsonMediaType) && isRecord(jsonMediaType.schema)) {
994
+ return jsonMediaType.schema;
995
+ }
996
+ for (const mediaType of Object.values(content)) {
997
+ if (isRecord(mediaType) && isRecord(mediaType.schema)) {
998
+ return mediaType.schema;
999
+ }
1000
+ }
1001
+ return void 0;
1002
+ }
1003
+ function extractParameters(operationSchema) {
1004
+ const params = operationSchema.parameters;
1005
+ if (!Array.isArray(params)) return [];
1006
+ return params.filter((value) => isRecord(value));
1007
+ }
1008
+ function extractInputSchema(operationSchema) {
1009
+ const requestBody = extractRequestBodySchema(operationSchema);
1010
+ const parameters = extractParameters(operationSchema);
1011
+ if (!requestBody && parameters.length === 0) return void 0;
1012
+ if (requestBody && parameters.length === 0) return requestBody;
1065
1013
  return {
1066
- upgradeSuggested: reasons.length > 0,
1067
- upgradeReasons: reasons
1014
+ ...requestBody ? { requestBody } : {},
1015
+ ...parameters.length > 0 ? { parameters } : {}
1068
1016
  };
1069
1017
  }
1070
-
1071
- // src/core/discovery.ts
1072
- function mergeResources(target, incoming, resourceWarnings) {
1073
- const warnings = [];
1074
- for (const resource of incoming) {
1075
- const existing = target.get(resource.resourceKey);
1076
- if (!existing) {
1077
- target.set(resource.resourceKey, resource);
1078
- continue;
1079
- }
1080
- const conflict = existing.authHint !== resource.authHint || existing.priceHint !== resource.priceHint || JSON.stringify(existing.protocolHints ?? []) !== JSON.stringify(resource.protocolHints ?? []);
1081
- if (conflict) {
1082
- const conflictWarning = warning(
1083
- "CROSS_SOURCE_CONFLICT",
1084
- "warn",
1085
- `Resource conflict for ${resource.resourceKey}; keeping higher-precedence source ${existing.source} over ${resource.source}.`,
1086
- {
1087
- resourceKey: resource.resourceKey
1088
- }
1089
- );
1090
- warnings.push(conflictWarning);
1091
- resourceWarnings[resource.resourceKey] = [
1092
- ...resourceWarnings[resource.resourceKey] ?? [],
1093
- conflictWarning
1094
- ];
1095
- continue;
1018
+ function parseOperationPrice(operation) {
1019
+ const paymentInfo = operation["x-payment-info"];
1020
+ if (!isRecord(paymentInfo)) return void 0;
1021
+ function toFiniteNumber(value) {
1022
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1023
+ if (typeof value === "string" && value.trim().length > 0) {
1024
+ const parsed = Number(value);
1025
+ if (Number.isFinite(parsed)) return parsed;
1096
1026
  }
1097
- target.set(resource.resourceKey, {
1098
- ...existing,
1099
- summary: existing.summary ?? resource.summary,
1100
- links: { ...resource.links, ...existing.links },
1101
- confidence: Math.max(existing.confidence ?? 0, resource.confidence ?? 0)
1102
- });
1027
+ return void 0;
1103
1028
  }
1104
- return warnings;
1029
+ const fixed = toFiniteNumber(paymentInfo.price);
1030
+ if (fixed != null) return `$${String(fixed)}`;
1031
+ const min = toFiniteNumber(paymentInfo.minPrice);
1032
+ const max = toFiniteNumber(paymentInfo.maxPrice);
1033
+ if (min != null && max != null) return `$${String(min)}-$${String(max)}`;
1034
+ return void 0;
1035
+ }
1036
+ function parseOperationProtocols(operation) {
1037
+ const paymentInfo = operation["x-payment-info"];
1038
+ if (!isRecord(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
1039
+ const protocols = paymentInfo.protocols.filter(
1040
+ (protocol) => typeof protocol === "string" && protocol.length > 0 && (protocol !== "mpp" || isMmmEnabled())
1041
+ );
1042
+ return protocols.length > 0 ? protocols : void 0;
1105
1043
  }
1106
- function toTrace(stageResult, startedAt) {
1044
+ function getL3ForOpenAPI(openApi, path, method) {
1045
+ const document = openApi.raw;
1046
+ const paths = isRecord(document.paths) ? document.paths : void 0;
1047
+ if (!paths) return null;
1048
+ const matched = findMatchingOpenApiPath(paths, path);
1049
+ if (!matched) return null;
1050
+ const operation = matched.pathItem[method.toLowerCase()];
1051
+ if (!isRecord(operation)) return null;
1052
+ const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
1053
+ const summary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
1107
1054
  return {
1108
- stage: stageResult.stage,
1109
- attempted: true,
1110
- valid: stageResult.valid,
1111
- resourceCount: stageResult.resources.length,
1112
- durationMs: Date.now() - startedAt,
1113
- warnings: stageResult.warnings,
1114
- ...stageResult.links ? { links: stageResult.links } : {}
1055
+ source: "openapi",
1056
+ ...summary ? { summary } : {},
1057
+ authMode: inferAuthMode(resolvedOperation) ?? void 0,
1058
+ estimatedPrice: parseOperationPrice(resolvedOperation),
1059
+ protocols: parseOperationProtocols(resolvedOperation),
1060
+ inputSchema: extractInputSchema(resolvedOperation),
1061
+ outputSchema: resolvedOperation
1115
1062
  };
1116
1063
  }
1117
- async function runOverrideStage(options) {
1118
- const warnings = [];
1119
- for (const overrideUrl of options.overrideUrls) {
1120
- const normalized = overrideUrl.trim();
1121
- if (!normalized) continue;
1122
- try {
1123
- const response = await options.fetcher(normalized, {
1124
- method: "GET",
1125
- headers: { Accept: "application/json, text/plain;q=0.9", ...options.headers },
1126
- signal: options.signal
1127
- });
1128
- if (!response.ok) {
1129
- warnings.push(
1130
- warning(
1131
- "FETCH_FAILED",
1132
- "warn",
1133
- `Override fetch failed (${response.status}) at ${normalized}`,
1134
- {
1135
- stage: "override"
1136
- }
1137
- )
1138
- );
1139
- continue;
1140
- }
1141
- const bodyText = await response.text();
1142
- let parsedJson;
1143
- try {
1144
- parsedJson = JSON.parse(bodyText);
1145
- } catch {
1146
- parsedJson = void 0;
1147
- }
1148
- if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson) && "openapi" in parsedJson && "paths" in parsedJson) {
1149
- const openapiResult = await runOpenApiStage({
1150
- origin: options.origin,
1151
- fetcher: async (input, init) => {
1152
- const inputString = String(input);
1153
- if (inputString === `${options.origin}/openapi.json` || inputString === `${options.origin}/.well-known/openapi.json`) {
1154
- return new Response(bodyText, {
1155
- status: 200,
1156
- headers: { "content-type": "application/json" }
1157
- });
1158
- }
1159
- return options.fetcher(input, init);
1160
- },
1161
- headers: options.headers,
1162
- signal: options.signal,
1163
- includeRaw: options.includeRaw
1164
- });
1165
- return {
1166
- ...openapiResult,
1167
- stage: "override",
1168
- warnings: [
1169
- warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1170
- stage: "override"
1171
- }),
1172
- ...openapiResult.warnings
1173
- ]
1174
- };
1175
- }
1176
- if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson)) {
1177
- const parsedWellKnown = parseWellKnownPayload(parsedJson, options.origin, normalized);
1178
- if (parsedWellKnown.resources.length > 0) {
1179
- return {
1180
- stage: "override",
1181
- valid: true,
1182
- resources: parsedWellKnown.resources,
1183
- warnings: [
1184
- warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1185
- stage: "override"
1186
- }),
1187
- ...parsedWellKnown.warnings
1188
- ],
1189
- links: { discoveryUrl: normalized },
1190
- ...options.includeRaw ? { raw: parsedWellKnown.raw } : {}
1191
- };
1192
- }
1193
- }
1194
- } catch (error) {
1195
- warnings.push(
1196
- warning(
1197
- "FETCH_FAILED",
1198
- "warn",
1199
- `Override fetch exception at ${normalized}: ${error instanceof Error ? error.message : String(error)}`,
1200
- { stage: "override" }
1201
- )
1202
- );
1203
- continue;
1204
- }
1205
- const fallbackWellKnownResult = await runWellKnownX402Stage({
1206
- origin: options.origin,
1207
- url: normalized,
1208
- fetcher: options.fetcher,
1209
- headers: options.headers,
1210
- signal: options.signal,
1211
- includeRaw: options.includeRaw
1212
- });
1213
- if (fallbackWellKnownResult.valid) {
1214
- return {
1215
- ...fallbackWellKnownResult,
1216
- stage: "override",
1217
- warnings: [
1218
- warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
1219
- stage: "override"
1220
- }),
1221
- ...fallbackWellKnownResult.warnings
1222
- ]
1223
- };
1224
- }
1225
- warnings.push(...fallbackWellKnownResult.warnings);
1226
- }
1064
+ function getL3ForProbe(probe, path, method) {
1065
+ const probeResult = probe.find((r) => r.path === path && r.method === method);
1066
+ if (!probeResult) return null;
1067
+ const inputSchema = probeResult.paymentRequiredBody ? parseInputSchema(probeResult.paymentRequiredBody) : void 0;
1068
+ const outputSchema = probeResult.paymentRequiredBody ? parseOutputSchema(probeResult.paymentRequiredBody) : void 0;
1069
+ const paymentOptions = [
1070
+ ...probeResult.paymentRequiredBody ? extractPaymentOptions3(probeResult.paymentRequiredBody) : [],
1071
+ ...isMmmEnabled() ? extractPaymentOptions4(probeResult.wwwAuthenticate) : []
1072
+ // isMmmEnabled
1073
+ ];
1227
1074
  return {
1228
- stage: "override",
1229
- valid: false,
1230
- resources: [],
1231
- warnings
1075
+ source: "probe",
1076
+ authMode: probeResult.authHint,
1077
+ ...probeResult.protocols?.length ? { protocols: probeResult.protocols } : {},
1078
+ ...inputSchema ? { inputSchema } : {},
1079
+ ...outputSchema ? { outputSchema } : {},
1080
+ ...paymentOptions.length ? { paymentOptions } : {}
1232
1081
  };
1233
1082
  }
1234
- async function runDiscovery({
1235
- detailed,
1236
- options
1237
- }) {
1238
- const fetcher = options.fetcher ?? fetch;
1239
- const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
1240
- const strictCompat = compatMode === "strict";
1241
- const rawView = detailed ? options.rawView ?? "none" : "none";
1242
- const includeRaw = rawView === "full";
1243
- const includeInteropMpp = detailed && Boolean(options.includeInteropMpp);
1244
- const origin = normalizeOrigin(options.target);
1245
- const warnings = [];
1246
- const trace = [];
1247
- const resourceWarnings = {};
1248
- const rawSources = {};
1249
- const merged = /* @__PURE__ */ new Map();
1250
- if (compatMode !== "off") {
1251
- warnings.push(
1252
- warning(
1253
- "COMPAT_MODE_ENABLED",
1254
- compatMode === "strict" ? "warn" : "info",
1255
- `Compatibility mode is '${compatMode}'. Legacy adapters are active.`
1256
- )
1257
- );
1258
- }
1259
- const stages = [];
1260
- if (options.overrideUrls && options.overrideUrls.length > 0) {
1261
- stages.push(
1262
- () => runOverrideStage({
1263
- origin,
1264
- overrideUrls: options.overrideUrls ?? [],
1265
- fetcher,
1266
- headers: options.headers,
1267
- signal: options.signal,
1268
- includeRaw
1269
- })
1270
- );
1271
- }
1272
- stages.push(
1273
- () => runOpenApiStage({
1274
- origin,
1275
- fetcher,
1276
- headers: options.headers,
1277
- signal: options.signal,
1278
- includeRaw
1279
- })
1280
- );
1281
- if (compatMode !== "off") {
1282
- stages.push(
1283
- () => runWellKnownX402Stage({
1284
- origin,
1285
- fetcher,
1286
- headers: options.headers,
1287
- signal: options.signal,
1288
- includeRaw
1289
- })
1083
+ function getL3(openApi, probe, path, method) {
1084
+ if (openApi) return getL3ForOpenAPI(openApi, path, method);
1085
+ return getL3ForProbe(probe, path, method);
1086
+ }
1087
+
1088
+ // src/runtime/check-endpoint.ts
1089
+ function getAdvisoriesForOpenAPI(openApi, path) {
1090
+ return [...HTTP_METHODS].flatMap((method) => {
1091
+ const l3 = getL3ForOpenAPI(openApi, path, method);
1092
+ return l3 ? [{ method, ...l3 }] : [];
1093
+ });
1094
+ }
1095
+ function getAdvisoriesForProbe(probe, path) {
1096
+ return [...HTTP_METHODS].flatMap((method) => {
1097
+ const l3 = getL3ForProbe(probe, path, method);
1098
+ return l3 ? [{ method, ...l3 }] : [];
1099
+ });
1100
+ }
1101
+ async function checkEndpointSchema(options) {
1102
+ const endpoint = new URL(options.url);
1103
+ const origin = normalizeOrigin(endpoint.origin);
1104
+ const path = normalizePath(endpoint.pathname || "/");
1105
+ if (options.sampleInputBody !== void 0) {
1106
+ const probeResult2 = await getProbe(
1107
+ options.url,
1108
+ options.headers,
1109
+ options.signal,
1110
+ options.sampleInputBody
1290
1111
  );
1291
- stages.push(
1292
- () => runDnsStage({
1112
+ if (probeResult2.isErr()) {
1113
+ return {
1114
+ found: false,
1293
1115
  origin,
1294
- fetcher,
1295
- txtResolver: options.txtResolver,
1296
- headers: options.headers,
1297
- signal: options.signal,
1298
- includeRaw
1299
- })
1300
- );
1301
- if (includeInteropMpp && isMmmEnabled()) {
1302
- stages.push(
1303
- () => runInteropMppStage({
1304
- origin,
1305
- fetcher,
1306
- headers: options.headers,
1307
- signal: options.signal,
1308
- includeRaw
1309
- })
1310
- );
1311
- }
1312
- }
1313
- stages.push(
1314
- () => runProbeStage({
1315
- origin,
1316
- fetcher,
1317
- headers: options.headers,
1318
- signal: options.signal,
1319
- probeCandidates: options.probeCandidates
1320
- })
1321
- );
1322
- let selectedStage;
1323
- for (const runStage of stages) {
1324
- const startedAt = Date.now();
1325
- const stageResult = await runStage();
1326
- stageResult.warnings = applyStrictEscalation(stageResult.warnings, strictCompat);
1327
- trace.push(toTrace(stageResult, startedAt));
1328
- warnings.push(...stageResult.warnings);
1329
- if (stageResult.resources.length > 0) {
1330
- for (const stageWarning of stageResult.warnings) {
1331
- if (stageWarning.resourceKey) {
1332
- resourceWarnings[stageWarning.resourceKey] = [
1333
- ...resourceWarnings[stageWarning.resourceKey] ?? [],
1334
- stageWarning
1335
- ];
1336
- }
1337
- }
1338
- }
1339
- if (!stageResult.valid) {
1340
- continue;
1341
- }
1342
- const mergeWarnings = mergeResources(merged, stageResult.resources, resourceWarnings);
1343
- warnings.push(...mergeWarnings);
1344
- if (includeRaw && stageResult.raw !== void 0) {
1345
- if (stageResult.stage === "openapi" || stageResult.stage === "override") {
1346
- const rawObject = stageResult.raw;
1347
- if (rawObject.openapi !== void 0) rawSources.openapi = rawObject.openapi;
1348
- if (typeof rawObject.llmsTxt === "string") rawSources.llmsTxt = rawObject.llmsTxt;
1349
- }
1350
- if (stageResult.stage === "well-known/x402" || stageResult.stage === "override") {
1351
- rawSources.wellKnownX402 = [...rawSources.wellKnownX402 ?? [], stageResult.raw];
1352
- }
1353
- if (stageResult.stage === "dns/_x402") {
1354
- const rawObject = stageResult.raw;
1355
- if (Array.isArray(rawObject.dnsRecords)) {
1356
- rawSources.dnsRecords = rawObject.dnsRecords;
1357
- }
1358
- }
1359
- if (stageResult.stage === "interop/mpp") {
1360
- rawSources.interopMpp = stageResult.raw;
1361
- }
1362
- }
1363
- if (!detailed) {
1364
- selectedStage = selectedStage ?? stageResult.stage;
1365
- break;
1116
+ path,
1117
+ cause: probeResult2.error.cause,
1118
+ message: probeResult2.error.message
1119
+ };
1366
1120
  }
1367
- }
1368
- if (merged.size === 0) {
1369
- warnings.push(
1370
- warning(
1371
- "NO_DISCOVERY_SOURCES",
1372
- "error",
1373
- "No discovery stage returned first-valid-non-empty results."
1374
- )
1375
- );
1376
- }
1377
- const dedupedWarnings = dedupeWarnings(warnings);
1378
- const upgradeSignal = computeUpgradeSignal(dedupedWarnings);
1379
- const resources = [...merged.values()];
1380
- if (!detailed) {
1121
+ const advisories2 = getAdvisoriesForProbe(probeResult2.value, path);
1122
+ if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
1123
+ return { found: false, origin, path, cause: "not_found" };
1124
+ }
1125
+ const openApiResult = await getOpenAPI(origin, options.headers, options.signal);
1126
+ const openApi = openApiResult.isOk() ? openApiResult.value : null;
1127
+ if (openApi) {
1128
+ const advisories2 = getAdvisoriesForOpenAPI(openApi, path);
1129
+ if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
1130
+ return { found: false, origin, path, cause: "not_found" };
1131
+ }
1132
+ const probeResult = await getProbe(options.url, options.headers, options.signal);
1133
+ if (probeResult.isErr()) {
1381
1134
  return {
1135
+ found: false,
1382
1136
  origin,
1383
- resources,
1384
- warnings: dedupedWarnings,
1385
- compatMode,
1386
- ...upgradeSignal,
1387
- ...selectedStage ? { selectedStage } : {}
1137
+ path,
1138
+ cause: probeResult.error.cause,
1139
+ message: probeResult.error.message
1388
1140
  };
1389
1141
  }
1390
- return {
1391
- origin,
1392
- resources,
1393
- warnings: dedupedWarnings,
1394
- compatMode,
1395
- ...upgradeSignal,
1396
- selectedStage: trace.find((entry) => entry.valid)?.stage,
1397
- trace,
1398
- resourceWarnings,
1399
- ...includeRaw ? { rawSources } : {}
1400
- };
1142
+ const advisories = getAdvisoriesForProbe(probeResult.value, path);
1143
+ if (advisories.length > 0) return { found: true, origin, path, advisories };
1144
+ return { found: false, origin, path, cause: "not_found" };
1401
1145
  }
1402
1146
 
1403
- // src/validation/codes.ts
1147
+ // src/x402scan-validation/codes.ts
1404
1148
  var VALIDATION_CODES = {
1405
1149
  COINBASE_SCHEMA_INVALID: "COINBASE_SCHEMA_INVALID",
1406
1150
  X402_NOT_OBJECT: "X402_NOT_OBJECT",
@@ -1423,7 +1167,7 @@ var VALIDATION_CODES = {
1423
1167
  METADATA_OG_IMAGE_MISSING: "METADATA_OG_IMAGE_MISSING"
1424
1168
  };
1425
1169
 
1426
- // src/validation/metadata.ts
1170
+ // src/x402scan-validation/metadata.ts
1427
1171
  function hasText(value) {
1428
1172
  return typeof value === "string" && value.trim().length > 0;
1429
1173
  }
@@ -1483,42 +1227,21 @@ function evaluateMetadataCompleteness(metadata) {
1483
1227
  return issues;
1484
1228
  }
1485
1229
 
1486
- // src/validation/payment-required.ts
1230
+ // src/x402scan-validation/payment-required.ts
1487
1231
  import { parsePaymentRequired } from "@x402/core/schemas";
1488
- var SOLANA_CANONICAL_BY_REF = {
1489
- "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana",
1490
- EtWTRABZaYq6iMfeYKouRu166VU2xqa1: "solana-devnet",
1491
- "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "solana-testnet"
1492
- };
1493
- var SOLANA_ALIAS_BY_REF = {
1494
- mainnet: "solana",
1495
- devnet: "solana-devnet",
1496
- testnet: "solana-testnet"
1497
- };
1498
- function isRecord4(value) {
1232
+
1233
+ // src/flags.ts
1234
+ var DEFAULT_COMPAT_MODE = "on";
1235
+
1236
+ // src/x402scan-validation/payment-required.ts
1237
+ function isRecord2(value) {
1499
1238
  return typeof value === "object" && value !== null && !Array.isArray(value);
1500
1239
  }
1501
- function asNonEmptyString(value) {
1502
- if (typeof value !== "string") return void 0;
1503
- const trimmed = value.trim();
1504
- return trimmed.length > 0 ? trimmed : void 0;
1505
- }
1506
- function asPositiveNumber(value) {
1507
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
1508
- }
1509
- function pushIssue(issues, issue) {
1510
- issues.push({
1511
- stage: issue.stage ?? "payment_required",
1512
- ...issue
1513
- });
1240
+ function pushIssue2(issues, issue) {
1241
+ issues.push({ stage: "payment_required", ...issue });
1514
1242
  }
1515
1243
  function summarizeIssues(issues) {
1516
- const summary = {
1517
- errorCount: 0,
1518
- warnCount: 0,
1519
- infoCount: 0,
1520
- byCode: {}
1521
- };
1244
+ const summary = { errorCount: 0, warnCount: 0, infoCount: 0, byCode: {} };
1522
1245
  for (const issue of issues) {
1523
1246
  if (issue.severity === "error") summary.errorCount += 1;
1524
1247
  else if (issue.severity === "warn") summary.warnCount += 1;
@@ -1541,202 +1264,23 @@ function zodPathToString(path) {
1541
1264
  }
1542
1265
  function parseWithCoinbaseStructuralGate(payload, issues) {
1543
1266
  const baseParsed = parsePaymentRequired(payload);
1544
- if (baseParsed.success) {
1545
- return baseParsed.data;
1546
- }
1267
+ if (baseParsed.success) return baseParsed.data;
1547
1268
  for (const issue of baseParsed.error.issues) {
1548
- pushIssue(issues, {
1269
+ pushIssue2(issues, {
1549
1270
  code: VALIDATION_CODES.COINBASE_SCHEMA_INVALID,
1550
1271
  severity: "error",
1551
1272
  message: `Coinbase schema validation failed: ${issue.message}`,
1552
1273
  path: zodPathToString(issue.path),
1553
- actual: issue.code
1554
- });
1555
- }
1556
- return void 0;
1557
- }
1558
- function validateV2Network(networkRaw, index, issues) {
1559
- if (!/^[^:\s]+:[^:\s]+$/.test(networkRaw)) {
1560
- pushIssue(issues, {
1561
- code: VALIDATION_CODES.NETWORK_CAIP2_INVALID,
1562
- severity: "error",
1563
- message: `Accept at index ${index} has invalid CAIP-2 network format`,
1564
- hint: "Expected '<namespace>:<reference>', e.g. 'eip155:8453' or 'solana:5eykt4...'.",
1565
- path: `accepts[${index}].network`,
1566
- expected: "<namespace>:<reference>",
1567
- actual: networkRaw
1568
- });
1569
- return void 0;
1570
- }
1571
- const [namespace, reference] = networkRaw.split(":");
1572
- if (namespace === "eip155") {
1573
- if (!/^\d+$/.test(reference)) {
1574
- pushIssue(issues, {
1575
- code: VALIDATION_CODES.NETWORK_EIP155_REFERENCE_INVALID,
1576
- severity: "error",
1577
- message: `Accept at index ${index} has invalid eip155 reference`,
1578
- hint: "Use a numeric chain id, e.g. 'eip155:8453'.",
1579
- path: `accepts[${index}].network`,
1580
- expected: "eip155:<numeric-chain-id>",
1581
- actual: networkRaw
1582
- });
1583
- return void 0;
1584
- }
1585
- return networkRaw;
1586
- }
1587
- if (namespace === "solana") {
1588
- const canonical = SOLANA_CANONICAL_BY_REF[reference];
1589
- if (canonical) return canonical;
1590
- const alias = SOLANA_ALIAS_BY_REF[reference];
1591
- if (alias) {
1592
- pushIssue(issues, {
1593
- code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_COMPAT,
1594
- severity: "warn",
1595
- message: `Accept at index ${index} uses compatibility Solana reference '${reference}'`,
1596
- hint: "Use canonical Solana CAIP references for deterministic behavior.",
1597
- path: `accepts[${index}].network`,
1598
- actual: networkRaw
1599
- });
1600
- return alias;
1601
- }
1602
- pushIssue(issues, {
1603
- code: VALIDATION_CODES.NETWORK_REFERENCE_UNKNOWN,
1604
- severity: "error",
1605
- message: `Accept at index ${index} uses unknown Solana reference '${reference}'`,
1606
- hint: "Use canonical references for mainnet/devnet/testnet.",
1607
- path: `accepts[${index}].network`,
1608
- actual: networkRaw
1609
- });
1610
- return void 0;
1611
- }
1612
- return networkRaw;
1613
- }
1614
- function validateV1Network(networkRaw, index, issues) {
1615
- if (networkRaw === "solana-mainnet-beta") {
1616
- pushIssue(issues, {
1617
- code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
1618
- severity: "error",
1619
- message: `Accept at index ${index} uses invalid Solana network alias`,
1620
- hint: "Use 'solana' (v1) or canonical Solana CAIP reference (v2).",
1621
- path: `accepts[${index}].network`,
1622
- expected: "solana",
1623
- actual: networkRaw
1624
- });
1625
- return void 0;
1626
- }
1627
- if (networkRaw.startsWith("solana-") && networkRaw !== "solana-devnet") {
1628
- pushIssue(issues, {
1629
- code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
1630
- severity: "error",
1631
- message: `Accept at index ${index} uses unsupported Solana network alias`,
1632
- hint: "Use 'solana' or 'solana-devnet' for v1 payloads.",
1633
- path: `accepts[${index}].network`,
1634
- actual: networkRaw
1635
- });
1636
- return void 0;
1637
- }
1638
- if (networkRaw.includes(":")) {
1639
- return validateV2Network(networkRaw, index, issues);
1640
- }
1641
- return networkRaw;
1642
- }
1643
- function validateAccept(acceptRaw, index, version, issues) {
1644
- if (!isRecord4(acceptRaw)) {
1645
- pushIssue(issues, {
1646
- code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1647
- severity: "error",
1648
- message: `Accept at index ${index} must be an object`,
1649
- path: `accepts[${index}]`
1650
- });
1651
- return null;
1652
- }
1653
- const scheme = asNonEmptyString(acceptRaw.scheme);
1654
- const networkRaw = asNonEmptyString(acceptRaw.network);
1655
- const payTo = asNonEmptyString(acceptRaw.payTo);
1656
- const asset = asNonEmptyString(acceptRaw.asset);
1657
- const maxTimeoutSeconds = asPositiveNumber(acceptRaw.maxTimeoutSeconds);
1658
- const amount = version === 2 ? asNonEmptyString(acceptRaw.amount) : asNonEmptyString(acceptRaw.maxAmountRequired);
1659
- if (!scheme) {
1660
- pushIssue(issues, {
1661
- code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1662
- severity: "error",
1663
- message: `Accept at index ${index} is missing scheme`,
1664
- path: `accepts[${index}].scheme`
1665
- });
1666
- }
1667
- if (!networkRaw) {
1668
- pushIssue(issues, {
1669
- code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1670
- severity: "error",
1671
- message: `Accept at index ${index} is missing network`,
1672
- path: `accepts[${index}].network`
1673
- });
1674
- }
1675
- if (!amount) {
1676
- pushIssue(issues, {
1677
- code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1678
- severity: "error",
1679
- message: `Accept at index ${index} is missing ${version === 2 ? "amount" : "maxAmountRequired"}`,
1680
- path: `accepts[${index}].${version === 2 ? "amount" : "maxAmountRequired"}`
1681
- });
1682
- }
1683
- if (!payTo) {
1684
- pushIssue(issues, {
1685
- code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1686
- severity: "error",
1687
- message: `Accept at index ${index} is missing payTo`,
1688
- path: `accepts[${index}].payTo`
1689
- });
1690
- }
1691
- if (!asset) {
1692
- pushIssue(issues, {
1693
- code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1694
- severity: "error",
1695
- message: `Accept at index ${index} is missing asset`,
1696
- path: `accepts[${index}].asset`
1697
- });
1698
- }
1699
- if (!maxTimeoutSeconds) {
1700
- pushIssue(issues, {
1701
- code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1702
- severity: "error",
1703
- message: `Accept at index ${index} is missing or has invalid maxTimeoutSeconds`,
1704
- path: `accepts[${index}].maxTimeoutSeconds`
1705
- });
1706
- }
1707
- let normalizedNetwork;
1708
- if (networkRaw) {
1709
- normalizedNetwork = version === 2 ? validateV2Network(networkRaw, index, issues) : validateV1Network(networkRaw, index, issues);
1710
- }
1711
- return {
1712
- index,
1713
- network: normalizedNetwork ?? networkRaw ?? "unknown",
1714
- networkRaw: networkRaw ?? "unknown",
1715
- scheme,
1716
- asset,
1717
- payTo,
1718
- amount,
1719
- maxTimeoutSeconds
1720
- };
1274
+ actual: issue.code
1275
+ });
1276
+ }
1277
+ return void 0;
1721
1278
  }
1722
1279
  function getSchemaPresence(payload, accepts, version) {
1723
- if (version === 1) {
1724
- const first = accepts[0];
1725
- if (!isRecord4(first)) {
1726
- return { hasInputSchema: false, hasOutputSchema: false };
1727
- }
1728
- const outputSchema = isRecord4(first.outputSchema) ? first.outputSchema : void 0;
1729
- return {
1730
- hasInputSchema: outputSchema?.input !== void 0 && outputSchema.input !== null,
1731
- hasOutputSchema: outputSchema?.output !== void 0 && outputSchema.output !== null
1732
- };
1733
- }
1734
- const extensions = isRecord4(payload.extensions) ? payload.extensions : void 0;
1735
- const bazaar = extensions && isRecord4(extensions.bazaar) ? extensions.bazaar : void 0;
1736
- const info = bazaar && isRecord4(bazaar.info) ? bazaar.info : void 0;
1280
+ const schemas = version === 1 ? extractSchemas(accepts) : extractSchemas2(payload);
1737
1281
  return {
1738
- hasInputSchema: info?.input !== void 0 && info.input !== null,
1739
- hasOutputSchema: info?.output !== void 0 && info.output !== null
1282
+ hasInputSchema: schemas.inputSchema !== void 0,
1283
+ hasOutputSchema: schemas.outputSchema !== void 0
1740
1284
  };
1741
1285
  }
1742
1286
  function validatePaymentRequiredDetailed(payload, options = {}) {
@@ -1744,21 +1288,15 @@ function validatePaymentRequiredDetailed(payload, options = {}) {
1744
1288
  const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
1745
1289
  const requireInputSchema = options.requireInputSchema ?? true;
1746
1290
  const requireOutputSchema = options.requireOutputSchema ?? true;
1747
- if (!isRecord4(payload)) {
1748
- pushIssue(issues, {
1291
+ if (!isRecord2(payload)) {
1292
+ pushIssue2(issues, {
1749
1293
  code: VALIDATION_CODES.X402_NOT_OBJECT,
1750
1294
  severity: "error",
1751
1295
  message: "Payment required payload must be a JSON object",
1752
1296
  path: "$"
1753
1297
  });
1754
- if (options.metadata) {
1755
- issues.push(...evaluateMetadataCompleteness(options.metadata));
1756
- }
1757
- return {
1758
- valid: false,
1759
- issues,
1760
- summary: summarizeIssues(issues)
1761
- };
1298
+ if (options.metadata) issues.push(...evaluateMetadataCompleteness(options.metadata));
1299
+ return { valid: false, issues, summary: summarizeIssues(issues) };
1762
1300
  }
1763
1301
  const coinbaseParsed = parseWithCoinbaseStructuralGate(payload, issues);
1764
1302
  const versionRaw = payload.x402Version;
@@ -1766,14 +1304,14 @@ function validatePaymentRequiredDetailed(payload, options = {}) {
1766
1304
  if (versionRaw === 1 || versionRaw === 2) {
1767
1305
  version = versionRaw;
1768
1306
  } else if (versionRaw === void 0) {
1769
- pushIssue(issues, {
1307
+ pushIssue2(issues, {
1770
1308
  code: VALIDATION_CODES.X402_VERSION_MISSING,
1771
1309
  severity: "error",
1772
1310
  message: "x402Version is required",
1773
1311
  path: "x402Version"
1774
1312
  });
1775
1313
  } else {
1776
- pushIssue(issues, {
1314
+ pushIssue2(issues, {
1777
1315
  code: VALIDATION_CODES.X402_VERSION_UNSUPPORTED,
1778
1316
  severity: "error",
1779
1317
  message: `Unsupported x402Version '${String(versionRaw)}'`,
@@ -1785,14 +1323,14 @@ function validatePaymentRequiredDetailed(payload, options = {}) {
1785
1323
  const acceptsRaw = payload.accepts;
1786
1324
  let accepts = [];
1787
1325
  if (acceptsRaw === void 0) {
1788
- pushIssue(issues, {
1326
+ pushIssue2(issues, {
1789
1327
  code: VALIDATION_CODES.X402_ACCEPTS_MISSING,
1790
1328
  severity: "error",
1791
1329
  message: "accepts is required",
1792
1330
  path: "accepts"
1793
1331
  });
1794
1332
  } else if (!Array.isArray(acceptsRaw)) {
1795
- pushIssue(issues, {
1333
+ pushIssue2(issues, {
1796
1334
  code: VALIDATION_CODES.X402_ACCEPTS_INVALID,
1797
1335
  severity: "error",
1798
1336
  message: "accepts must be an array",
@@ -1801,7 +1339,7 @@ function validatePaymentRequiredDetailed(payload, options = {}) {
1801
1339
  } else {
1802
1340
  accepts = acceptsRaw;
1803
1341
  if (accepts.length === 0) {
1804
- pushIssue(issues, {
1342
+ pushIssue2(issues, {
1805
1343
  code: VALIDATION_CODES.X402_ACCEPTS_EMPTY,
1806
1344
  severity: "error",
1807
1345
  message: "accepts must contain at least one payment requirement",
@@ -1809,52 +1347,45 @@ function validatePaymentRequiredDetailed(payload, options = {}) {
1809
1347
  });
1810
1348
  }
1811
1349
  }
1812
- const normalizedAccepts = [];
1813
1350
  if (version && Array.isArray(accepts)) {
1814
- accepts.forEach((accept, index) => {
1815
- const normalized = validateAccept(accept, index, version, issues);
1816
- if (normalized) normalizedAccepts.push(normalized);
1817
- });
1351
+ const normalizedAccepts = version === 1 ? validateAccepts(accepts, issues) : validateAccepts2(accepts, issues);
1818
1352
  const schemaPresence = getSchemaPresence(payload, accepts, version);
1819
1353
  if (requireInputSchema && !schemaPresence.hasInputSchema) {
1820
- pushIssue(issues, {
1354
+ pushIssue2(issues, {
1821
1355
  code: VALIDATION_CODES.SCHEMA_INPUT_MISSING,
1822
1356
  severity: "error",
1823
1357
  message: "Input schema is missing",
1824
1358
  hint: "Include input schema details so clients can invoke the endpoint correctly.",
1825
- path: version === 1 ? "accepts[0].outputSchema.input" : "extensions.bazaar.info.input"
1359
+ path: version === 1 ? "accepts[0].outputSchema.input" : "extensions.bazaar.schema.properties.input"
1826
1360
  });
1827
1361
  }
1828
1362
  if (requireOutputSchema && !schemaPresence.hasOutputSchema) {
1829
- pushIssue(issues, {
1363
+ pushIssue2(issues, {
1830
1364
  code: VALIDATION_CODES.SCHEMA_OUTPUT_MISSING,
1831
1365
  severity: outputSchemaMissingSeverity(compatMode),
1832
1366
  message: "Output schema is missing",
1833
1367
  hint: "Include output schema details so clients can validate responses.",
1834
- path: version === 1 ? "accepts[0].outputSchema.output" : "extensions.bazaar.info.output"
1368
+ path: version === 1 ? "accepts[0].outputSchema.output" : "extensions.bazaar.schema.properties.output"
1835
1369
  });
1836
1370
  }
1837
- if (options.metadata) {
1838
- issues.push(...evaluateMetadataCompleteness(options.metadata));
1839
- }
1371
+ if (options.metadata) issues.push(...evaluateMetadataCompleteness(options.metadata));
1840
1372
  const summary2 = summarizeIssues(issues);
1373
+ const normalized = {
1374
+ version,
1375
+ accepts: normalizedAccepts,
1376
+ hasInputSchema: schemaPresence.hasInputSchema,
1377
+ hasOutputSchema: schemaPresence.hasOutputSchema
1378
+ };
1841
1379
  return {
1842
1380
  valid: summary2.errorCount === 0,
1843
1381
  version,
1844
1382
  parsed: coinbaseParsed ?? payload,
1845
- normalized: {
1846
- version,
1847
- accepts: normalizedAccepts,
1848
- hasInputSchema: schemaPresence.hasInputSchema,
1849
- hasOutputSchema: schemaPresence.hasOutputSchema
1850
- },
1383
+ normalized,
1851
1384
  issues,
1852
1385
  summary: summary2
1853
1386
  };
1854
1387
  }
1855
- if (options.metadata) {
1856
- issues.push(...evaluateMetadataCompleteness(options.metadata));
1857
- }
1388
+ if (options.metadata) issues.push(...evaluateMetadataCompleteness(options.metadata));
1858
1389
  const summary = summarizeIssues(issues);
1859
1390
  return {
1860
1391
  valid: summary.errorCount === 0,
@@ -1865,659 +1396,202 @@ function validatePaymentRequiredDetailed(payload, options = {}) {
1865
1396
  };
1866
1397
  }
1867
1398
 
1868
- // src/adapters/mcp.ts
1869
- var DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS = 1e3;
1870
- var OPENAPI_METHODS = [
1871
- "GET",
1872
- "POST",
1873
- "PUT",
1874
- "DELETE",
1875
- "PATCH",
1876
- "HEAD",
1877
- "OPTIONS",
1878
- "TRACE"
1879
- ];
1880
- function isRecord5(value) {
1881
- return value != null && typeof value === "object" && !Array.isArray(value);
1882
- }
1883
- function toFiniteNumber(value) {
1884
- if (typeof value === "number" && Number.isFinite(value)) return value;
1885
- if (typeof value === "string" && value.trim().length > 0) {
1886
- const parsed = Number(value);
1887
- if (Number.isFinite(parsed)) return parsed;
1888
- }
1889
- return void 0;
1890
- }
1891
- function formatPriceHintFromResource(resource) {
1892
- const pricing = resource.pricing;
1893
- if (pricing) {
1894
- if (pricing.pricingMode === "fixed" && pricing.price) {
1895
- return pricing.price.startsWith("$") ? pricing.price : `$${pricing.price}`;
1896
- }
1897
- if (pricing.pricingMode === "range" && pricing.minPrice && pricing.maxPrice) {
1898
- const min = pricing.minPrice.startsWith("$") ? pricing.minPrice : `$${pricing.minPrice}`;
1899
- const max = pricing.maxPrice.startsWith("$") ? pricing.maxPrice : `$${pricing.maxPrice}`;
1900
- return `${min}-${max}`;
1901
- }
1399
+ // src/audit/codes.ts
1400
+ var AUDIT_CODES = {
1401
+ // ─── Source presence ─────────────────────────────────────────────────────────
1402
+ OPENAPI_NOT_FOUND: "OPENAPI_NOT_FOUND",
1403
+ WELLKNOWN_NOT_FOUND: "WELLKNOWN_NOT_FOUND",
1404
+ // ─── OpenAPI quality ─────────────────────────────────────────────────────────
1405
+ OPENAPI_NO_ROUTES: "OPENAPI_NO_ROUTES",
1406
+ // ─── L2 route-list checks ────────────────────────────────────────────────────
1407
+ L2_NO_ROUTES: "L2_NO_ROUTES",
1408
+ L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH",
1409
+ L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING",
1410
+ L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID",
1411
+ L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID",
1412
+ // ─── L3 endpoint advisory checks ─────────────────────────────────────────────
1413
+ L3_NOT_FOUND: "L3_NOT_FOUND",
1414
+ L3_INPUT_SCHEMA_MISSING: "L3_INPUT_SCHEMA_MISSING",
1415
+ L3_AUTH_MODE_MISSING: "L3_AUTH_MODE_MISSING",
1416
+ L3_PROTOCOLS_MISSING_ON_PAID: "L3_PROTOCOLS_MISSING_ON_PAID",
1417
+ // ─── L4 guidance checks ──────────────────────────────────────────────────────
1418
+ L4_GUIDANCE_MISSING: "L4_GUIDANCE_MISSING",
1419
+ L4_GUIDANCE_TOO_LONG: "L4_GUIDANCE_TOO_LONG"
1420
+ };
1421
+
1422
+ // src/audit/warnings.ts
1423
+ var GUIDANCE_TOO_LONG_CHARS = 4e3;
1424
+ var ROUTE_COUNT_HIGH = 40;
1425
+ function getWarningsForOpenAPI(openApi) {
1426
+ if (openApi === null) {
1427
+ return [
1428
+ {
1429
+ code: AUDIT_CODES.OPENAPI_NOT_FOUND,
1430
+ severity: "info",
1431
+ message: "No OpenAPI specification found at this origin.",
1432
+ hint: "Expose an OpenAPI spec (e.g. /openapi.json) for richer discovery."
1433
+ }
1434
+ ];
1902
1435
  }
1903
- if (!resource.priceHint) return void 0;
1904
- const hint = resource.priceHint.trim();
1905
- if (hint.length === 0) return void 0;
1906
- if (hint.startsWith("$")) return hint;
1907
- if (hint.includes("-")) {
1908
- const [min, max] = hint.split("-", 2).map((part) => part.trim());
1909
- if (min && max) return `$${min}-$${max}`;
1436
+ const warnings = [];
1437
+ if (openApi.routes.length === 0) {
1438
+ warnings.push({
1439
+ code: AUDIT_CODES.OPENAPI_NO_ROUTES,
1440
+ severity: "warn",
1441
+ message: "OpenAPI spec found but contains no route definitions.",
1442
+ hint: "Add paths to your OpenAPI spec so agents can discover endpoints.",
1443
+ path: "paths"
1444
+ });
1910
1445
  }
1911
- return `$${hint}`;
1446
+ return warnings;
1912
1447
  }
1913
- function deriveMcpAuthMode(resource) {
1914
- if (!resource) return void 0;
1915
- const hint = resource.authHint;
1916
- if (hint === "paid" || hint === "siwx" || hint === "apiKey" || hint === "unprotected") {
1917
- return hint;
1448
+ function getWarningsForWellKnown(wellKnown) {
1449
+ if (wellKnown === null) {
1450
+ return [
1451
+ {
1452
+ code: AUDIT_CODES.WELLKNOWN_NOT_FOUND,
1453
+ severity: "info",
1454
+ message: "No /.well-known/x402 resource found at this origin.",
1455
+ hint: "Expose /.well-known/x402 as a fallback for agents that cannot read OpenAPI."
1456
+ }
1457
+ ];
1918
1458
  }
1919
- return void 0;
1459
+ return [];
1920
1460
  }
1921
- function inferFailureCause(warnings) {
1922
- const openapiWarnings = warnings.filter((w) => w.stage === "openapi");
1923
- const parseWarning = openapiWarnings.find(
1924
- (w) => w.code === "PARSE_FAILED" || w.code === "OPENAPI_TOP_LEVEL_INVALID" || w.code === "OPENAPI_OPERATION_INVALID" || w.code === "OPENAPI_PRICING_INVALID"
1925
- );
1926
- if (parseWarning) {
1927
- return { cause: "parse", message: parseWarning.message };
1461
+ function getWarningsForL2(l2) {
1462
+ const warnings = [];
1463
+ if (l2.routes.length === 0) {
1464
+ warnings.push({
1465
+ code: AUDIT_CODES.L2_NO_ROUTES,
1466
+ severity: "warn",
1467
+ message: "No routes found from any discovery source."
1468
+ });
1469
+ return warnings;
1928
1470
  }
1929
- const fetchWarning = openapiWarnings.find((w) => w.code === "FETCH_FAILED");
1930
- if (fetchWarning) {
1931
- const msg = fetchWarning.message.toLowerCase();
1932
- if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("abort")) {
1933
- return { cause: "timeout", message: fetchWarning.message };
1934
- }
1935
- return { cause: "network", message: fetchWarning.message };
1471
+ if (l2.routes.length > ROUTE_COUNT_HIGH) {
1472
+ warnings.push({
1473
+ code: AUDIT_CODES.L2_ROUTE_COUNT_HIGH,
1474
+ severity: "warn",
1475
+ message: `High route count (${l2.routes.length}) may exceed agent token budgets for zero-hop injection.`,
1476
+ hint: "Consider grouping routes or reducing the advertised surface."
1477
+ });
1936
1478
  }
1937
- return { cause: "not_found" };
1938
- }
1939
- function getOpenApiInfo(document) {
1940
- if (!isRecord5(document) || !isRecord5(document.info)) return void 0;
1941
- if (typeof document.info.title !== "string") return void 0;
1942
- return {
1943
- title: document.info.title,
1944
- ...typeof document.info.version === "string" ? { version: document.info.version } : {},
1945
- ...typeof document.info.description === "string" ? { description: document.info.description } : {}
1946
- };
1947
- }
1948
- function getGuidanceUrl(result) {
1949
- const fromTrace = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1950
- if (fromTrace) return fromTrace;
1951
- const fromResource = result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1952
- if (fromResource) return fromResource;
1953
- return void 0;
1954
- }
1955
- async function resolveGuidance(options) {
1956
- let guidanceText = typeof options.result.rawSources?.llmsTxt === "string" ? options.result.rawSources.llmsTxt : void 0;
1957
- if (!guidanceText) {
1958
- const guidanceUrl = getGuidanceUrl(options.result) ?? `${options.result.origin}/llms.txt`;
1959
- const fetcher = options.fetcher ?? fetch;
1960
- try {
1961
- const response = await fetcher(guidanceUrl, {
1962
- method: "GET",
1963
- headers: { Accept: "text/plain", ...options.headers },
1964
- signal: options.signal
1479
+ for (const route of l2.routes) {
1480
+ const loc = `${route.method} ${route.path}`;
1481
+ if (!route.authMode) {
1482
+ warnings.push({
1483
+ code: AUDIT_CODES.L2_AUTH_MODE_MISSING,
1484
+ severity: "warn",
1485
+ message: `Route ${loc} is missing an auth mode declaration.`,
1486
+ hint: "Set x-payment-info.authMode (or securitySchemes) so agents know if payment is required.",
1487
+ path: route.path
1965
1488
  });
1966
- if (response.ok) guidanceText = await response.text();
1967
- } catch {
1968
- }
1969
- }
1970
- if (!guidanceText) {
1971
- return { guidanceAvailable: false };
1972
- }
1973
- const guidanceTokens = estimateTokenCount(guidanceText);
1974
- const threshold = options.guidanceAutoIncludeMaxTokens ?? DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS;
1975
- const shouldInclude = options.includeGuidance === true || options.includeGuidance == null && guidanceTokens <= threshold;
1976
- return {
1977
- guidanceAvailable: true,
1978
- guidanceTokens,
1979
- ...shouldInclude ? { guidance: guidanceText } : {}
1980
- };
1981
- }
1982
- function extractPathsDocument(document) {
1983
- if (!isRecord5(document)) return void 0;
1984
- if (!isRecord5(document.paths)) return void 0;
1985
- return document.paths;
1986
- }
1987
- function findMatchingOpenApiPath(paths, targetPath) {
1988
- const exact = paths[targetPath];
1989
- if (isRecord5(exact)) return { matchedPath: targetPath, pathItem: exact };
1990
- for (const [specPath, entry] of Object.entries(paths)) {
1991
- if (!isRecord5(entry)) continue;
1992
- const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
1993
- const regex = new RegExp(`^${pattern}$`);
1994
- if (regex.test(targetPath)) {
1995
- return { matchedPath: specPath, pathItem: entry };
1996
1489
  }
1997
- }
1998
- return null;
1999
- }
2000
- function resolveRef(document, ref, seen) {
2001
- if (!ref.startsWith("#/")) return void 0;
2002
- if (seen.has(ref)) return { $circular: ref };
2003
- seen.add(ref);
2004
- const parts = ref.slice(2).split("/");
2005
- let current = document;
2006
- for (const part of parts) {
2007
- if (!isRecord5(current)) return void 0;
2008
- current = current[part];
2009
- if (current === void 0) return void 0;
2010
- }
2011
- if (isRecord5(current)) return resolveRefs(document, current, seen);
2012
- return current;
2013
- }
2014
- function resolveRefs(document, obj, seen, depth = 0) {
2015
- if (depth > 4) return obj;
2016
- const resolved = {};
2017
- for (const [key, value] of Object.entries(obj)) {
2018
- if (key === "$ref" && typeof value === "string") {
2019
- const deref = resolveRef(document, value, seen);
2020
- if (isRecord5(deref)) {
2021
- Object.assign(resolved, deref);
2022
- } else {
2023
- resolved[key] = value;
1490
+ if (route.authMode === "paid") {
1491
+ if (!route.price) {
1492
+ warnings.push({
1493
+ code: AUDIT_CODES.L2_PRICE_MISSING_ON_PAID,
1494
+ severity: "warn",
1495
+ message: `Paid route ${loc} has no price hint.`,
1496
+ hint: "Add x-payment-info.price (or minPrice/maxPrice) so agents can budget before calling.",
1497
+ path: route.path
1498
+ });
1499
+ }
1500
+ if (!route.protocols?.length) {
1501
+ warnings.push({
1502
+ code: AUDIT_CODES.L2_PROTOCOLS_MISSING_ON_PAID,
1503
+ severity: "info",
1504
+ message: `Paid route ${loc} does not declare supported payment protocols.`,
1505
+ hint: "Add x-payment-info.protocols (e.g. ['x402']) to signal which payment flows are accepted.",
1506
+ path: route.path
1507
+ });
2024
1508
  }
2025
- continue;
2026
- }
2027
- if (isRecord5(value)) {
2028
- resolved[key] = resolveRefs(document, value, seen, depth + 1);
2029
- continue;
2030
- }
2031
- if (Array.isArray(value)) {
2032
- resolved[key] = value.map(
2033
- (item) => isRecord5(item) ? resolveRefs(document, item, seen, depth + 1) : item
2034
- );
2035
- continue;
2036
- }
2037
- resolved[key] = value;
2038
- }
2039
- return resolved;
2040
- }
2041
- function extractRequestBodySchema(operationSchema) {
2042
- const requestBody = operationSchema.requestBody;
2043
- if (!isRecord5(requestBody)) return void 0;
2044
- const content = requestBody.content;
2045
- if (!isRecord5(content)) return void 0;
2046
- const jsonMediaType = content["application/json"];
2047
- if (isRecord5(jsonMediaType) && isRecord5(jsonMediaType.schema)) {
2048
- return jsonMediaType.schema;
2049
- }
2050
- for (const mediaType of Object.values(content)) {
2051
- if (isRecord5(mediaType) && isRecord5(mediaType.schema)) {
2052
- return mediaType.schema;
2053
- }
2054
- }
2055
- return void 0;
2056
- }
2057
- function extractParameters(operationSchema) {
2058
- const params = operationSchema.parameters;
2059
- if (!Array.isArray(params)) return [];
2060
- return params.filter((value) => isRecord5(value));
2061
- }
2062
- function extractInputSchema(operationSchema) {
2063
- const requestBody = extractRequestBodySchema(operationSchema);
2064
- const parameters = extractParameters(operationSchema);
2065
- if (!requestBody && parameters.length === 0) return void 0;
2066
- if (requestBody && parameters.length === 0) return requestBody;
2067
- return {
2068
- ...requestBody ? { requestBody } : {},
2069
- ...parameters.length > 0 ? { parameters } : {}
2070
- };
2071
- }
2072
- function parseOperationPrice(operation) {
2073
- const paymentInfo = operation["x-payment-info"];
2074
- if (!isRecord5(paymentInfo)) return void 0;
2075
- const fixed = toFiniteNumber(paymentInfo.price);
2076
- if (fixed != null) return `$${String(fixed)}`;
2077
- const min = toFiniteNumber(paymentInfo.minPrice);
2078
- const max = toFiniteNumber(paymentInfo.maxPrice);
2079
- if (min != null && max != null) return `$${String(min)}-$${String(max)}`;
2080
- return void 0;
2081
- }
2082
- function parseOperationProtocols(operation) {
2083
- const paymentInfo = operation["x-payment-info"];
2084
- if (!isRecord5(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
2085
- const protocols = paymentInfo.protocols.filter(
2086
- (protocol) => typeof protocol === "string" && protocol.length > 0
2087
- );
2088
- return protocols.length > 0 ? protocols : void 0;
2089
- }
2090
- function parseOperationAuthMode(operation) {
2091
- const authExtension = operation["x-agentcash-auth"];
2092
- if (isRecord5(authExtension)) {
2093
- const mode = authExtension.mode;
2094
- if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
2095
- return mode;
2096
- }
2097
- }
2098
- if (isRecord5(operation["x-payment-info"])) return "paid";
2099
- return void 0;
2100
- }
2101
- function createResourceMap(result) {
2102
- const map = /* @__PURE__ */ new Map();
2103
- for (const resource of result.resources) {
2104
- map.set(resource.resourceKey, resource);
2105
- }
2106
- return map;
2107
- }
2108
- function toMcpEndpointSummary(resource) {
2109
- const method = parseMethod(resource.method);
2110
- if (!method) return void 0;
2111
- const price = formatPriceHintFromResource(resource);
2112
- const authMode = deriveMcpAuthMode(resource);
2113
- return {
2114
- path: resource.path,
2115
- method,
2116
- summary: resource.summary ?? `${method} ${resource.path}`,
2117
- ...price ? { price } : {},
2118
- ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
2119
- ...authMode ? { authMode } : {}
2120
- };
2121
- }
2122
- async function discoverForMcp(options) {
2123
- const result = await runDiscovery({
2124
- detailed: true,
2125
- options: {
2126
- target: options.target,
2127
- compatMode: options.compatMode ?? "off",
2128
- rawView: "full",
2129
- fetcher: options.fetcher,
2130
- headers: options.headers,
2131
- signal: options.signal
2132
1509
  }
2133
- });
2134
- if (result.resources.length === 0) {
2135
- const failure = inferFailureCause(result.warnings);
2136
- return {
2137
- found: false,
2138
- origin: result.origin,
2139
- cause: failure.cause,
2140
- ...failure.message ? { message: failure.message } : {},
2141
- warnings: result.warnings
2142
- };
2143
1510
  }
2144
- const source = result.selectedStage ?? "openapi";
2145
- const endpoints = result.resources.map(toMcpEndpointSummary).filter((endpoint) => endpoint != null).sort((a, b) => {
2146
- if (a.path !== b.path) return a.path.localeCompare(b.path);
2147
- return a.method.localeCompare(b.method);
2148
- });
2149
- const guidance = await resolveGuidance({
2150
- result,
2151
- includeGuidance: options.includeGuidance,
2152
- guidanceAutoIncludeMaxTokens: options.guidanceAutoIncludeMaxTokens,
2153
- fetcher: options.fetcher,
2154
- headers: options.headers,
2155
- signal: options.signal
2156
- });
2157
- return {
2158
- found: true,
2159
- origin: result.origin,
2160
- source,
2161
- ...getOpenApiInfo(result.rawSources?.openapi) ? { info: getOpenApiInfo(result.rawSources?.openapi) } : {},
2162
- ...guidance,
2163
- endpoints,
2164
- warnings: result.warnings
2165
- };
1511
+ return warnings;
2166
1512
  }
2167
- async function inspectEndpointForMcp(options) {
2168
- const endpoint = new URL(options.endpointUrl);
2169
- const origin = normalizeOrigin(endpoint.origin);
2170
- const path = normalizePath(endpoint.pathname || "/");
2171
- const result = await runDiscovery({
2172
- detailed: true,
2173
- options: {
2174
- target: options.target,
2175
- compatMode: options.compatMode ?? "off",
2176
- rawView: "full",
2177
- fetcher: options.fetcher,
2178
- headers: options.headers,
2179
- signal: options.signal
2180
- }
2181
- });
2182
- const document = result.rawSources?.openapi;
2183
- const paths = extractPathsDocument(document);
2184
- const resourceMap = createResourceMap(result);
2185
- const advisories = {};
2186
- const specMethods = [];
2187
- if (paths) {
2188
- const matched = findMatchingOpenApiPath(paths, path);
2189
- if (matched) {
2190
- for (const candidate of OPENAPI_METHODS) {
2191
- const operation = matched.pathItem[candidate.toLowerCase()];
2192
- if (!isRecord5(operation) || !isRecord5(document)) continue;
2193
- specMethods.push(candidate);
2194
- const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
2195
- const resource = resourceMap.get(toResourceKey(origin, candidate, matched.matchedPath));
2196
- const formattedPrice = resource ? formatPriceHintFromResource(resource) : void 0;
2197
- const operationSummary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
2198
- const operationPrice = parseOperationPrice(resolvedOperation);
2199
- const operationProtocols = parseOperationProtocols(resolvedOperation);
2200
- const operationAuthMode = parseOperationAuthMode(resolvedOperation);
2201
- const inputSchema = extractInputSchema(resolvedOperation);
2202
- advisories[candidate] = {
2203
- method: candidate,
2204
- ...resource?.summary || operationSummary ? {
2205
- summary: resource?.summary ?? operationSummary
2206
- } : {},
2207
- ...formattedPrice || operationPrice ? {
2208
- estimatedPrice: formattedPrice ?? operationPrice
2209
- } : {},
2210
- ...resource?.protocolHints?.length || operationProtocols ? {
2211
- protocols: resource?.protocolHints ?? operationProtocols
2212
- } : {},
2213
- ...deriveMcpAuthMode(resource) || operationAuthMode ? {
2214
- authMode: deriveMcpAuthMode(resource) ?? operationAuthMode
2215
- } : {},
2216
- ...inputSchema ? { inputSchema } : {},
2217
- operationSchema: resolvedOperation
2218
- };
1513
+ function getWarningsForL3(l3) {
1514
+ if (l3 === null) {
1515
+ return [
1516
+ {
1517
+ code: AUDIT_CODES.L3_NOT_FOUND,
1518
+ severity: "info",
1519
+ message: "No spec data found for this endpoint.",
1520
+ hint: "Ensure the path is defined in your OpenAPI spec or reachable via probe."
2219
1521
  }
2220
- }
2221
- }
2222
- if (specMethods.length === 0) {
2223
- for (const method of OPENAPI_METHODS) {
2224
- const resource = resourceMap.get(toResourceKey(origin, method, path));
2225
- if (!resource) continue;
2226
- specMethods.push(method);
2227
- const formattedPrice = formatPriceHintFromResource(resource);
2228
- advisories[method] = {
2229
- method,
2230
- ...resource.summary ? { summary: resource.summary } : {},
2231
- ...formattedPrice ? { estimatedPrice: formattedPrice } : {},
2232
- ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
2233
- ...deriveMcpAuthMode(resource) ? { authMode: deriveMcpAuthMode(resource) } : {}
2234
- };
2235
- }
2236
- }
2237
- if (specMethods.length === 0) {
2238
- const failure = inferFailureCause(result.warnings);
2239
- return {
2240
- found: false,
2241
- origin,
2242
- path,
2243
- cause: failure.cause,
2244
- ...failure.message ? { message: failure.message } : {},
2245
- warnings: result.warnings
2246
- };
1522
+ ];
2247
1523
  }
2248
- return {
2249
- found: true,
2250
- origin,
2251
- path,
2252
- specMethods,
2253
- advisories,
2254
- warnings: result.warnings
2255
- };
2256
- }
2257
-
2258
- // src/harness.ts
2259
- var INTENT_TRIGGERS = ["x402", "mpp", "pay for", "micropayment", "agentic commerce"];
2260
- var CLIENT_PROFILES = {
2261
- "claude-code": {
2262
- id: "claude-code",
2263
- label: "Claude Code MCP Harness",
2264
- surface: "mcp",
2265
- defaultContextWindowTokens: 2e5,
2266
- zeroHopBudgetPercent: 0.1,
2267
- notes: "Targets environments where MCP context can use roughly 10% of total context."
2268
- },
2269
- "skill-cli": {
2270
- id: "skill-cli",
2271
- label: "Skill + CLI Harness",
2272
- surface: "skill-cli",
2273
- defaultContextWindowTokens: 2e5,
2274
- zeroHopBudgetPercent: 0.02,
2275
- notes: "Targets constrained skill contexts with title/description-heavy routing."
2276
- },
2277
- "generic-mcp": {
2278
- id: "generic-mcp",
2279
- label: "Generic MCP Harness",
2280
- surface: "mcp",
2281
- defaultContextWindowTokens: 128e3,
2282
- zeroHopBudgetPercent: 0.05,
2283
- notes: "Conservative MCP profile when specific client budgets are unknown."
2284
- },
2285
- generic: {
2286
- id: "generic",
2287
- label: "Generic Agent Harness",
2288
- surface: "generic",
2289
- defaultContextWindowTokens: 128e3,
2290
- zeroHopBudgetPercent: 0.03,
2291
- notes: "Fallback profile for unknown clients."
1524
+ const warnings = [];
1525
+ if (!l3.authMode) {
1526
+ warnings.push({
1527
+ code: AUDIT_CODES.L3_AUTH_MODE_MISSING,
1528
+ severity: "warn",
1529
+ message: "Endpoint has no auth mode in the spec.",
1530
+ hint: "Declare auth mode via security schemes or x-payment-info so agents can plan payment."
1531
+ });
2292
1532
  }
2293
- };
2294
- function isFirstPartyDomain(hostname) {
2295
- const normalized = hostname.toLowerCase();
2296
- return normalized.endsWith(".dev") && normalized.startsWith("stable");
2297
- }
2298
- function safeHostname(origin) {
2299
- try {
2300
- return new URL(origin).hostname;
2301
- } catch {
2302
- return origin.replace(/^https?:\/\//, "").split("/")[0] ?? origin;
1533
+ if (l3.authMode === "paid" && !l3.inputSchema) {
1534
+ warnings.push({
1535
+ code: AUDIT_CODES.L3_INPUT_SCHEMA_MISSING,
1536
+ severity: "warn",
1537
+ message: "Paid endpoint is missing an input schema.",
1538
+ hint: "Add a requestBody or parameters schema so agents can construct valid payloads."
1539
+ });
2303
1540
  }
2304
- }
2305
- function previewText(text) {
2306
- if (!text) return null;
2307
- const compact = text.replace(/\s+/g, " ").trim();
2308
- if (!compact) return null;
2309
- return compact.length > 220 ? `${compact.slice(0, 217)}...` : compact;
2310
- }
2311
- function toAuthModeCount(resources) {
2312
- const counts = {
2313
- paid: 0,
2314
- siwx: 0,
2315
- apiKey: 0,
2316
- unprotected: 0,
2317
- unknown: 0
2318
- };
2319
- for (const resource of resources) {
2320
- const mode = resource.authHint;
2321
- if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
2322
- counts[mode] += 1;
2323
- } else {
2324
- counts.unknown += 1;
2325
- }
1541
+ if (l3.authMode === "paid" && !l3.protocols?.length) {
1542
+ warnings.push({
1543
+ code: AUDIT_CODES.L3_PROTOCOLS_MISSING_ON_PAID,
1544
+ severity: "info",
1545
+ message: "Paid endpoint does not declare supported payment protocols.",
1546
+ hint: "Add x-payment-info.protocols (e.g. ['x402']) to the operation."
1547
+ });
2326
1548
  }
2327
- return counts;
2328
- }
2329
- function toSourceCount(resources) {
2330
- return resources.reduce(
2331
- (acc, resource) => {
2332
- acc[resource.source] = (acc[resource.source] ?? 0) + 1;
2333
- return acc;
2334
- },
2335
- {}
2336
- );
2337
- }
2338
- function toL2Entries(resources) {
2339
- return [...resources].sort((a, b) => {
2340
- if (a.path !== b.path) return a.path.localeCompare(b.path);
2341
- if (a.method !== b.method) return a.method.localeCompare(b.method);
2342
- return a.resourceKey.localeCompare(b.resourceKey);
2343
- }).map((resource) => ({
2344
- resourceKey: resource.resourceKey,
2345
- method: resource.method,
2346
- path: resource.path,
2347
- source: resource.source,
2348
- authMode: resource.authHint ?? null
2349
- }));
1549
+ return warnings;
2350
1550
  }
2351
- function getLlmsTxtInfo(result) {
2352
- const llmsTxtUrl = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? null;
2353
- const llmsTxt = result.rawSources?.llmsTxt;
2354
- if (llmsTxt) {
2355
- return {
2356
- llmsTxtUrl,
2357
- llmsTxtTokenEstimate: estimateTokenCount(llmsTxt),
2358
- guidancePreview: previewText(llmsTxt),
2359
- guidanceStatus: "present"
2360
- };
2361
- }
2362
- if (llmsTxtUrl) {
2363
- const failed = result.warnings.some((warning2) => warning2.code === "LLMSTXT_FETCH_FAILED");
2364
- return {
2365
- llmsTxtUrl,
2366
- llmsTxtTokenEstimate: 0,
2367
- guidancePreview: null,
2368
- guidanceStatus: failed ? "advertised_but_unfetched" : "missing"
2369
- };
1551
+ function getWarningsForL4(l4) {
1552
+ if (l4 === null) {
1553
+ return [
1554
+ {
1555
+ code: AUDIT_CODES.L4_GUIDANCE_MISSING,
1556
+ severity: "info",
1557
+ message: "No guidance text found (llms.txt or OpenAPI info.guidance).",
1558
+ hint: "Add an info.guidance field to your OpenAPI spec or expose /llms.txt for agent-readable instructions."
1559
+ }
1560
+ ];
2370
1561
  }
2371
- return {
2372
- llmsTxtUrl: null,
2373
- llmsTxtTokenEstimate: 0,
2374
- guidancePreview: null,
2375
- guidanceStatus: "not_advertised"
2376
- };
2377
- }
2378
- function getHarnessClientProfile(client = "claude-code") {
2379
- return CLIENT_PROFILES[client] ?? CLIENT_PROFILES.generic;
2380
- }
2381
- function listHarnessClientProfiles() {
2382
- return Object.values(CLIENT_PROFILES);
2383
- }
2384
- function buildContextHarnessReport(options) {
2385
- const client = getHarnessClientProfile(options.client);
2386
- const contextWindowTokens = options.contextWindowTokens ?? client.defaultContextWindowTokens;
2387
- const zeroHopBudgetTokens = Math.floor(contextWindowTokens * client.zeroHopBudgetPercent);
2388
- const hostname = safeHostname(options.result.origin);
2389
- const domainClass = isFirstPartyDomain(hostname) ? "first-party" : "ugc";
2390
- const l0Summary = "Route payment-capable intents to agentcash and use install/discover commands for progressive disclosure.";
2391
- const l1Summary = "Expose installed domain routing hints and trigger fan-out into L2/L3 commands when user intent matches domain capabilities.";
2392
- const l0EstimatedTokens = estimateTokenCount(
2393
- `${INTENT_TRIGGERS.join(", ")} npx agentcash install npx agentcash install-ext`
2394
- );
2395
- const l1EstimatedTokens = estimateTokenCount(
2396
- `${hostname} ${l1Summary} npx agentcash discover ${hostname} npx agentcash discover ${hostname} --verbose`
2397
- );
2398
- const llmsInfo = getLlmsTxtInfo(options.result);
2399
- return {
2400
- target: options.target,
2401
- origin: options.result.origin,
2402
- client,
2403
- budget: {
2404
- contextWindowTokens,
2405
- zeroHopBudgetTokens,
2406
- estimatedZeroHopTokens: l0EstimatedTokens + l1EstimatedTokens,
2407
- withinBudget: l0EstimatedTokens + l1EstimatedTokens <= zeroHopBudgetTokens
2408
- },
2409
- levels: {
2410
- l0: {
2411
- layer: "L0",
2412
- intentTriggers: INTENT_TRIGGERS,
2413
- installCommand: "npx agentcash install",
2414
- deliverySurfaces: ["MCP", "Skill+CLI"],
2415
- summary: l0Summary,
2416
- estimatedTokens: l0EstimatedTokens
2417
- },
2418
- l1: {
2419
- layer: "L1",
2420
- domain: hostname,
2421
- domainClass,
2422
- selectedDiscoveryStage: options.result.selectedStage ?? null,
2423
- summary: l1Summary,
2424
- fanoutCommands: [
2425
- `npx agentcash discover ${hostname}`,
2426
- `npx agentcash discover ${hostname} --verbose`
2427
- ],
2428
- estimatedTokens: l1EstimatedTokens
2429
- },
2430
- l2: {
2431
- layer: "L2",
2432
- command: `npx agentcash discover ${hostname}`,
2433
- tokenLight: true,
2434
- resourceCount: options.result.resources.length,
2435
- resources: toL2Entries(options.result.resources)
2436
- },
2437
- l3: {
2438
- layer: "L3",
2439
- command: `npx agentcash discover ${hostname} --verbose`,
2440
- detailLevel: "endpoint-schema-and-metadata",
2441
- countsByAuthMode: toAuthModeCount(options.result.resources),
2442
- countsBySource: toSourceCount(options.result.resources),
2443
- pricedResourceCount: options.result.resources.filter(
2444
- (resource) => resource.authHint === "paid"
2445
- ).length
2446
- },
2447
- l4: {
2448
- layer: "L4",
2449
- guidanceSource: llmsInfo.llmsTxtUrl ? "llms.txt" : "none",
2450
- llmsTxtUrl: llmsInfo.llmsTxtUrl,
2451
- llmsTxtTokenEstimate: llmsInfo.llmsTxtTokenEstimate,
2452
- guidancePreview: llmsInfo.guidancePreview,
2453
- guidanceStatus: llmsInfo.guidanceStatus
2454
- },
2455
- l5: {
2456
- layer: "L5",
2457
- status: "out_of_scope",
2458
- note: "Cross-domain composition is intentionally out of scope for discovery v1."
1562
+ if (l4.guidance.length > GUIDANCE_TOO_LONG_CHARS) {
1563
+ return [
1564
+ {
1565
+ code: AUDIT_CODES.L4_GUIDANCE_TOO_LONG,
1566
+ severity: "warn",
1567
+ message: `Guidance text is ${l4.guidance.length} characters, which may exceed zero-hop token budgets.`,
1568
+ hint: "Trim guidance to under ~4000 characters for reliable zero-hop context injection."
2459
1569
  }
2460
- },
2461
- diagnostics: {
2462
- warningCount: options.result.warnings.length,
2463
- errorWarningCount: options.result.warnings.filter((warning2) => warning2.severity === "error").length,
2464
- selectedStage: options.result.selectedStage ?? null,
2465
- upgradeSuggested: options.result.upgradeSuggested,
2466
- upgradeReasons: options.result.upgradeReasons
2467
- }
2468
- };
2469
- }
2470
- async function auditContextHarness(options) {
2471
- const result = await runDiscovery({
2472
- detailed: true,
2473
- options: {
2474
- ...options,
2475
- rawView: "full"
2476
- }
2477
- });
2478
- return buildContextHarnessReport({
2479
- target: options.target,
2480
- result,
2481
- client: options.client,
2482
- contextWindowTokens: options.contextWindowTokens
2483
- });
2484
- }
2485
- function defaultHarnessProbeCandidates(report) {
2486
- const methodsByPath = /* @__PURE__ */ new Map();
2487
- for (const resource of report.levels.l2.resources) {
2488
- const methods = methodsByPath.get(resource.path) ?? /* @__PURE__ */ new Set();
2489
- methods.add(resource.method);
2490
- methodsByPath.set(resource.path, methods);
1570
+ ];
2491
1571
  }
2492
- return [...methodsByPath.entries()].map(([path, methods]) => ({
2493
- path,
2494
- methods: [...methods]
2495
- }));
2496
- }
2497
-
2498
- // src/index.ts
2499
- async function discover(options) {
2500
- return await runDiscovery({ detailed: false, options });
2501
- }
2502
- async function discoverDetailed(options) {
2503
- return await runDiscovery({ detailed: true, options });
1572
+ return [];
2504
1573
  }
2505
1574
  export {
2506
- DEFAULT_COMPAT_MODE,
2507
- LEGACY_SUNSET_DATE,
2508
- STRICT_ESCALATION_CODES,
2509
- UPGRADE_WARNING_CODES,
1575
+ AUDIT_CODES,
1576
+ GuidanceMode,
2510
1577
  VALIDATION_CODES,
2511
- auditContextHarness,
2512
- buildContextHarnessReport,
2513
- computeUpgradeSignal,
2514
- defaultHarnessProbeCandidates,
2515
- discover,
2516
- discoverDetailed,
2517
- discoverForMcp,
1578
+ checkEndpointSchema,
1579
+ checkL2ForOpenAPI,
1580
+ checkL2ForWellknown,
1581
+ checkL4ForOpenAPI,
1582
+ checkL4ForWellknown,
1583
+ discoverOriginSchema,
2518
1584
  evaluateMetadataCompleteness,
2519
- getHarnessClientProfile,
2520
- inspectEndpointForMcp,
2521
- listHarnessClientProfiles,
1585
+ getL3,
1586
+ getL3ForOpenAPI,
1587
+ getL3ForProbe,
1588
+ getOpenAPI,
1589
+ getProbe,
1590
+ getWarningsForL2,
1591
+ getWarningsForL3,
1592
+ getWarningsForL4,
1593
+ getWarningsForOpenAPI,
1594
+ getWarningsForWellKnown,
1595
+ getWellKnown,
2522
1596
  validatePaymentRequiredDetailed
2523
1597
  };