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