@agentcash/discovery 0.1.4 → 1.0.1

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