@agentcash/discovery 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -154
- package/dist/cli.cjs +87 -60
- package/dist/cli.js +77 -60
- package/dist/index.cjs +89 -62
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +79 -62
- package/dist/schemas.cjs +1 -0
- package/dist/schemas.d.cts +1 -0
- package/dist/schemas.d.ts +1 -0
- package/dist/schemas.js +1 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -50,6 +50,7 @@ var OpenApiDocSchema = z.object({
|
|
|
50
50
|
description: z.string().optional(),
|
|
51
51
|
guidance: z.string().optional()
|
|
52
52
|
}),
|
|
53
|
+
security: z.array(z.record(z.string(), z.array(z.string()))).optional(),
|
|
53
54
|
servers: z.array(z.object({ url: z.string() })).optional(),
|
|
54
55
|
tags: z.array(z.object({ name: z.string() })).optional(),
|
|
55
56
|
components: z.object({ securitySchemes: z.record(z.string(), z.unknown()).optional() }).optional(),
|
|
@@ -79,20 +80,31 @@ var WellKnownParsedSchema = z.object({
|
|
|
79
80
|
function isRecord(value) {
|
|
80
81
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
81
82
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
function resolveSecurityFlags(requirements, securitySchemes) {
|
|
84
|
+
let hasApiKey = false;
|
|
85
|
+
let hasSiwx = false;
|
|
86
|
+
for (const requirement of requirements) {
|
|
87
|
+
for (const schemeName of Object.keys(requirement)) {
|
|
88
|
+
if (schemeName === "siwx") {
|
|
89
|
+
hasSiwx = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (schemeName === "apiKey") {
|
|
93
|
+
hasApiKey = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const def = securitySchemes[schemeName];
|
|
97
|
+
if (isRecord(def) && def["type"] === "apiKey") hasApiKey = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { hasApiKey, hasSiwx };
|
|
87
101
|
}
|
|
88
|
-
function inferAuthMode(operation) {
|
|
102
|
+
function inferAuthMode(operation, globalSecurity, securitySchemes) {
|
|
89
103
|
const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
|
|
90
|
-
const
|
|
91
|
-
const hasApiKey =
|
|
92
|
-
|
|
93
|
-
if (hasPayment && hasApiKey) return "apiKey+paid";
|
|
104
|
+
const effectiveSecurity = operation.security !== void 0 && operation.security.length > 0 ? operation.security : globalSecurity ?? [];
|
|
105
|
+
const { hasApiKey, hasSiwx } = resolveSecurityFlags(effectiveSecurity, securitySchemes ?? {});
|
|
106
|
+
if (hasXPaymentInfo && hasApiKey) return "apiKey+paid";
|
|
94
107
|
if (hasXPaymentInfo) return "paid";
|
|
95
|
-
if (hasPayment) return hasSiwx ? "siwx" : "paid";
|
|
96
108
|
if (hasApiKey) return "apiKey";
|
|
97
109
|
if (hasSiwx) return "siwx";
|
|
98
110
|
return void 0;
|
|
@@ -112,10 +124,12 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
|
112
124
|
var DEFAULT_MISSING_METHOD = "POST";
|
|
113
125
|
|
|
114
126
|
// src/core/lib/url.ts
|
|
115
|
-
function
|
|
127
|
+
function ensureProtocol(target) {
|
|
116
128
|
const trimmed = target.trim();
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
130
|
+
}
|
|
131
|
+
function normalizeOrigin(target) {
|
|
132
|
+
const url = new URL(ensureProtocol(target));
|
|
119
133
|
url.pathname = "";
|
|
120
134
|
url.search = "";
|
|
121
135
|
url.hash = "";
|
|
@@ -154,7 +168,7 @@ function fetchSafe(url, init) {
|
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
// src/mmm-enabled.ts
|
|
157
|
-
var isMmmEnabled = () => "1.0
|
|
171
|
+
var isMmmEnabled = () => "1.1.0".includes("-mmm");
|
|
158
172
|
|
|
159
173
|
// src/core/source/openapi/index.ts
|
|
160
174
|
var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
|
|
@@ -163,15 +177,13 @@ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
|
|
|
163
177
|
for (const httpMethod of [...HTTP_METHODS]) {
|
|
164
178
|
const operation = pathItem[httpMethod.toLowerCase()];
|
|
165
179
|
if (!operation) continue;
|
|
166
|
-
const authMode = inferAuthMode(operation) ?? void 0;
|
|
180
|
+
const authMode = inferAuthMode(operation, doc.security, doc.components?.securitySchemes) ?? void 0;
|
|
167
181
|
if (!authMode) continue;
|
|
168
182
|
const p = operation["x-payment-info"];
|
|
169
183
|
const protocols = (p?.protocols ?? []).filter(
|
|
170
184
|
(proto) => proto !== "mpp" || isMmmEnabled()
|
|
171
185
|
);
|
|
172
|
-
|
|
173
|
-
if (authMode === "paid" && protocols.length === 0) continue;
|
|
174
|
-
const pricing = authMode === "paid" && p ? {
|
|
186
|
+
const pricing = (authMode === "paid" || authMode === "apiKey+paid") && p ? {
|
|
175
187
|
pricingMode: p.pricingMode,
|
|
176
188
|
...p.price ? { price: p.price } : {},
|
|
177
189
|
...p.minPrice ? { minPrice: p.minPrice } : {},
|
|
@@ -322,6 +334,7 @@ var AUDIT_CODES = {
|
|
|
322
334
|
L2_NO_ROUTES: "L2_NO_ROUTES",
|
|
323
335
|
L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH",
|
|
324
336
|
L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING",
|
|
337
|
+
L2_NO_PAID_ROUTES: "L2_NO_PAID_ROUTES",
|
|
325
338
|
L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID",
|
|
326
339
|
L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID",
|
|
327
340
|
// ─── L3 endpoint advisory checks ─────────────────────────────────────────────
|
|
@@ -383,6 +396,15 @@ function getWarningsForL2(l2) {
|
|
|
383
396
|
});
|
|
384
397
|
return warnings;
|
|
385
398
|
}
|
|
399
|
+
const hasPaidRoute = l2.routes.some((r) => r.authMode === "paid" || r.authMode === "apiKey+paid");
|
|
400
|
+
if (!hasPaidRoute) {
|
|
401
|
+
warnings.push({
|
|
402
|
+
code: AUDIT_CODES.L2_NO_PAID_ROUTES,
|
|
403
|
+
severity: "info",
|
|
404
|
+
message: "No endpoints are marked as paid or apiKey+paid.",
|
|
405
|
+
hint: "Add x-payment-info to operations that require payment so agents know which endpoints are monetized."
|
|
406
|
+
});
|
|
407
|
+
}
|
|
386
408
|
if (l2.routes.length > ROUTE_COUNT_HIGH) {
|
|
387
409
|
warnings.push({
|
|
388
410
|
code: AUDIT_CODES.L2_ROUTE_COUNT_HIGH,
|
|
@@ -469,8 +491,8 @@ function getWarningsForL4(l4) {
|
|
|
469
491
|
{
|
|
470
492
|
code: AUDIT_CODES.L4_GUIDANCE_MISSING,
|
|
471
493
|
severity: "info",
|
|
472
|
-
message: "No guidance text found (
|
|
473
|
-
hint: "Add an info.guidance field to your OpenAPI spec
|
|
494
|
+
message: "No guidance text found (OpenAPI info.guidance).",
|
|
495
|
+
hint: "Add an info.guidance field to your OpenAPI spec for agent-readable instructions."
|
|
474
496
|
}
|
|
475
497
|
];
|
|
476
498
|
}
|
|
@@ -779,54 +801,31 @@ function extractPaymentOptions4(wwwAuthenticate) {
|
|
|
779
801
|
return options;
|
|
780
802
|
}
|
|
781
803
|
|
|
782
|
-
// src/core/
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
if (!isRecord(entry)) continue;
|
|
788
|
-
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
789
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
790
|
-
if (regex.test(targetPath)) {
|
|
791
|
-
return { matchedPath: specPath, pathItem: entry };
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
return null;
|
|
795
|
-
}
|
|
796
|
-
function resolveRef(document, ref, seen) {
|
|
797
|
-
if (!ref.startsWith("#/")) return void 0;
|
|
798
|
-
if (seen.has(ref)) return { $circular: ref };
|
|
799
|
-
seen.add(ref);
|
|
800
|
-
const parts = ref.slice(2).split("/");
|
|
801
|
-
let current = document;
|
|
802
|
-
for (const part of parts) {
|
|
803
|
-
if (!isRecord(current)) return void 0;
|
|
804
|
-
current = current[part];
|
|
805
|
-
if (current === void 0) return void 0;
|
|
806
|
-
}
|
|
807
|
-
if (isRecord(current)) return resolveRefs(document, current, seen);
|
|
808
|
-
return current;
|
|
804
|
+
// src/core/lib/resolve-ref.ts
|
|
805
|
+
import pkg from "dereference-json-schema";
|
|
806
|
+
var { resolveRefSync } = pkg;
|
|
807
|
+
function isRecord2(value) {
|
|
808
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
809
809
|
}
|
|
810
|
-
function
|
|
811
|
-
if (depth > 4) return obj;
|
|
810
|
+
function deepResolveRefs(document, obj) {
|
|
812
811
|
const resolved = {};
|
|
813
812
|
for (const [key, value] of Object.entries(obj)) {
|
|
814
813
|
if (key === "$ref" && typeof value === "string") {
|
|
815
|
-
const deref =
|
|
816
|
-
if (
|
|
817
|
-
Object.assign(resolved, deref);
|
|
814
|
+
const deref = resolveRefSync(document, value);
|
|
815
|
+
if (isRecord2(deref)) {
|
|
816
|
+
Object.assign(resolved, deepResolveRefs(document, deref));
|
|
818
817
|
} else {
|
|
819
818
|
resolved[key] = value;
|
|
820
819
|
}
|
|
821
820
|
continue;
|
|
822
821
|
}
|
|
823
|
-
if (
|
|
824
|
-
resolved[key] =
|
|
822
|
+
if (isRecord2(value)) {
|
|
823
|
+
resolved[key] = deepResolveRefs(document, value);
|
|
825
824
|
continue;
|
|
826
825
|
}
|
|
827
826
|
if (Array.isArray(value)) {
|
|
828
827
|
resolved[key] = value.map(
|
|
829
|
-
(item) =>
|
|
828
|
+
(item) => isRecord2(item) ? deepResolveRefs(document, item) : item
|
|
830
829
|
);
|
|
831
830
|
continue;
|
|
832
831
|
}
|
|
@@ -834,6 +833,24 @@ function resolveRefs(document, obj, seen, depth = 0) {
|
|
|
834
833
|
}
|
|
835
834
|
return resolved;
|
|
836
835
|
}
|
|
836
|
+
function resolveRefs(obj, document) {
|
|
837
|
+
return deepResolveRefs(document, obj);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/core/layers/l3.ts
|
|
841
|
+
function findMatchingOpenApiPath(paths, targetPath) {
|
|
842
|
+
const exact = paths[targetPath];
|
|
843
|
+
if (isRecord(exact)) return { matchedPath: targetPath, pathItem: exact };
|
|
844
|
+
for (const [specPath, entry] of Object.entries(paths)) {
|
|
845
|
+
if (!isRecord(entry)) continue;
|
|
846
|
+
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
847
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
848
|
+
if (regex.test(targetPath)) {
|
|
849
|
+
return { matchedPath: specPath, pathItem: entry };
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
837
854
|
function extractRequestBodySchema(operationSchema) {
|
|
838
855
|
const requestBody = operationSchema.requestBody;
|
|
839
856
|
if (!isRecord(requestBody)) return void 0;
|
|
@@ -913,7 +930,7 @@ function getL3ForOpenAPI(openApi, path, method) {
|
|
|
913
930
|
if (!matched) return null;
|
|
914
931
|
const operation = matched.pathItem[method.toLowerCase()];
|
|
915
932
|
if (!isRecord(operation)) return null;
|
|
916
|
-
const resolvedOperation = resolveRefs(
|
|
933
|
+
const resolvedOperation = resolveRefs(operation, document);
|
|
917
934
|
const summary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
|
|
918
935
|
return {
|
|
919
936
|
source: "openapi",
|
|
@@ -959,12 +976,12 @@ function getAdvisoriesForProbe(probe, path) {
|
|
|
959
976
|
});
|
|
960
977
|
}
|
|
961
978
|
async function checkEndpointSchema(options) {
|
|
962
|
-
const endpoint = new URL(options.url);
|
|
979
|
+
const endpoint = new URL(ensureProtocol(options.url));
|
|
963
980
|
const origin = normalizeOrigin(endpoint.origin);
|
|
964
981
|
const path = normalizePath(endpoint.pathname || "/");
|
|
965
982
|
if (options.sampleInputBody !== void 0) {
|
|
966
983
|
const probeResult2 = await getProbe(
|
|
967
|
-
|
|
984
|
+
endpoint.href,
|
|
968
985
|
options.headers,
|
|
969
986
|
options.signal,
|
|
970
987
|
options.sampleInputBody
|
|
@@ -989,7 +1006,7 @@ async function checkEndpointSchema(options) {
|
|
|
989
1006
|
if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
|
|
990
1007
|
return { found: false, origin, path, cause: "not_found" };
|
|
991
1008
|
}
|
|
992
|
-
const probeResult = await getProbe(
|
|
1009
|
+
const probeResult = await getProbe(endpoint.href, options.headers, options.signal);
|
|
993
1010
|
if (probeResult.isErr()) {
|
|
994
1011
|
return {
|
|
995
1012
|
found: false,
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -97,6 +107,7 @@ var OpenApiDocSchema = import_zod.z.object({
|
|
|
97
107
|
description: import_zod.z.string().optional(),
|
|
98
108
|
guidance: import_zod.z.string().optional()
|
|
99
109
|
}),
|
|
110
|
+
security: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))).optional(),
|
|
100
111
|
servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string() })).optional(),
|
|
101
112
|
tags: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string() })).optional(),
|
|
102
113
|
components: import_zod.z.object({ securitySchemes: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional() }).optional(),
|
|
@@ -126,20 +137,31 @@ var WellKnownParsedSchema = import_zod.z.object({
|
|
|
126
137
|
function isRecord(value) {
|
|
127
138
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
128
139
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
function resolveSecurityFlags(requirements, securitySchemes) {
|
|
141
|
+
let hasApiKey = false;
|
|
142
|
+
let hasSiwx = false;
|
|
143
|
+
for (const requirement of requirements) {
|
|
144
|
+
for (const schemeName of Object.keys(requirement)) {
|
|
145
|
+
if (schemeName === "siwx") {
|
|
146
|
+
hasSiwx = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (schemeName === "apiKey") {
|
|
150
|
+
hasApiKey = true;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const def = securitySchemes[schemeName];
|
|
154
|
+
if (isRecord(def) && def["type"] === "apiKey") hasApiKey = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { hasApiKey, hasSiwx };
|
|
134
158
|
}
|
|
135
|
-
function inferAuthMode(operation) {
|
|
159
|
+
function inferAuthMode(operation, globalSecurity, securitySchemes) {
|
|
136
160
|
const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
|
|
137
|
-
const
|
|
138
|
-
const hasApiKey =
|
|
139
|
-
|
|
140
|
-
if (hasPayment && hasApiKey) return "apiKey+paid";
|
|
161
|
+
const effectiveSecurity = operation.security !== void 0 && operation.security.length > 0 ? operation.security : globalSecurity ?? [];
|
|
162
|
+
const { hasApiKey, hasSiwx } = resolveSecurityFlags(effectiveSecurity, securitySchemes ?? {});
|
|
163
|
+
if (hasXPaymentInfo && hasApiKey) return "apiKey+paid";
|
|
141
164
|
if (hasXPaymentInfo) return "paid";
|
|
142
|
-
if (hasPayment) return hasSiwx ? "siwx" : "paid";
|
|
143
165
|
if (hasApiKey) return "apiKey";
|
|
144
166
|
if (hasSiwx) return "siwx";
|
|
145
167
|
return void 0;
|
|
@@ -159,10 +181,12 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
|
159
181
|
var DEFAULT_MISSING_METHOD = "POST";
|
|
160
182
|
|
|
161
183
|
// src/core/lib/url.ts
|
|
162
|
-
function
|
|
184
|
+
function ensureProtocol(target) {
|
|
163
185
|
const trimmed = target.trim();
|
|
164
|
-
|
|
165
|
-
|
|
186
|
+
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
187
|
+
}
|
|
188
|
+
function normalizeOrigin(target) {
|
|
189
|
+
const url = new URL(ensureProtocol(target));
|
|
166
190
|
url.pathname = "";
|
|
167
191
|
url.search = "";
|
|
168
192
|
url.hash = "";
|
|
@@ -201,7 +225,7 @@ function fetchSafe(url, init) {
|
|
|
201
225
|
}
|
|
202
226
|
|
|
203
227
|
// src/mmm-enabled.ts
|
|
204
|
-
var isMmmEnabled = () => "1.0
|
|
228
|
+
var isMmmEnabled = () => "1.1.0".includes("-mmm");
|
|
205
229
|
|
|
206
230
|
// src/core/source/openapi/index.ts
|
|
207
231
|
var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
|
|
@@ -210,15 +234,13 @@ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
|
|
|
210
234
|
for (const httpMethod of [...HTTP_METHODS]) {
|
|
211
235
|
const operation = pathItem[httpMethod.toLowerCase()];
|
|
212
236
|
if (!operation) continue;
|
|
213
|
-
const authMode = inferAuthMode(operation) ?? void 0;
|
|
237
|
+
const authMode = inferAuthMode(operation, doc.security, doc.components?.securitySchemes) ?? void 0;
|
|
214
238
|
if (!authMode) continue;
|
|
215
239
|
const p = operation["x-payment-info"];
|
|
216
240
|
const protocols = (p?.protocols ?? []).filter(
|
|
217
241
|
(proto) => proto !== "mpp" || isMmmEnabled()
|
|
218
242
|
);
|
|
219
|
-
|
|
220
|
-
if (authMode === "paid" && protocols.length === 0) continue;
|
|
221
|
-
const pricing = authMode === "paid" && p ? {
|
|
243
|
+
const pricing = (authMode === "paid" || authMode === "apiKey+paid") && p ? {
|
|
222
244
|
pricingMode: p.pricingMode,
|
|
223
245
|
...p.price ? { price: p.price } : {},
|
|
224
246
|
...p.minPrice ? { minPrice: p.minPrice } : {},
|
|
@@ -978,54 +1000,31 @@ function extractPaymentOptions4(wwwAuthenticate) {
|
|
|
978
1000
|
return options;
|
|
979
1001
|
}
|
|
980
1002
|
|
|
981
|
-
// src/core/
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
if (!isRecord(entry)) continue;
|
|
987
|
-
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
988
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
989
|
-
if (regex.test(targetPath)) {
|
|
990
|
-
return { matchedPath: specPath, pathItem: entry };
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
function resolveRef(document, ref, seen) {
|
|
996
|
-
if (!ref.startsWith("#/")) return void 0;
|
|
997
|
-
if (seen.has(ref)) return { $circular: ref };
|
|
998
|
-
seen.add(ref);
|
|
999
|
-
const parts = ref.slice(2).split("/");
|
|
1000
|
-
let current = document;
|
|
1001
|
-
for (const part of parts) {
|
|
1002
|
-
if (!isRecord(current)) return void 0;
|
|
1003
|
-
current = current[part];
|
|
1004
|
-
if (current === void 0) return void 0;
|
|
1005
|
-
}
|
|
1006
|
-
if (isRecord(current)) return resolveRefs(document, current, seen);
|
|
1007
|
-
return current;
|
|
1003
|
+
// src/core/lib/resolve-ref.ts
|
|
1004
|
+
var import_dereference_json_schema = __toESM(require("dereference-json-schema"), 1);
|
|
1005
|
+
var { resolveRefSync } = import_dereference_json_schema.default;
|
|
1006
|
+
function isRecord2(value) {
|
|
1007
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1008
1008
|
}
|
|
1009
|
-
function
|
|
1010
|
-
if (depth > 4) return obj;
|
|
1009
|
+
function deepResolveRefs(document, obj) {
|
|
1011
1010
|
const resolved = {};
|
|
1012
1011
|
for (const [key, value] of Object.entries(obj)) {
|
|
1013
1012
|
if (key === "$ref" && typeof value === "string") {
|
|
1014
|
-
const deref =
|
|
1015
|
-
if (
|
|
1016
|
-
Object.assign(resolved, deref);
|
|
1013
|
+
const deref = resolveRefSync(document, value);
|
|
1014
|
+
if (isRecord2(deref)) {
|
|
1015
|
+
Object.assign(resolved, deepResolveRefs(document, deref));
|
|
1017
1016
|
} else {
|
|
1018
1017
|
resolved[key] = value;
|
|
1019
1018
|
}
|
|
1020
1019
|
continue;
|
|
1021
1020
|
}
|
|
1022
|
-
if (
|
|
1023
|
-
resolved[key] =
|
|
1021
|
+
if (isRecord2(value)) {
|
|
1022
|
+
resolved[key] = deepResolveRefs(document, value);
|
|
1024
1023
|
continue;
|
|
1025
1024
|
}
|
|
1026
1025
|
if (Array.isArray(value)) {
|
|
1027
1026
|
resolved[key] = value.map(
|
|
1028
|
-
(item) =>
|
|
1027
|
+
(item) => isRecord2(item) ? deepResolveRefs(document, item) : item
|
|
1029
1028
|
);
|
|
1030
1029
|
continue;
|
|
1031
1030
|
}
|
|
@@ -1033,6 +1032,24 @@ function resolveRefs(document, obj, seen, depth = 0) {
|
|
|
1033
1032
|
}
|
|
1034
1033
|
return resolved;
|
|
1035
1034
|
}
|
|
1035
|
+
function resolveRefs(obj, document) {
|
|
1036
|
+
return deepResolveRefs(document, obj);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/core/layers/l3.ts
|
|
1040
|
+
function findMatchingOpenApiPath(paths, targetPath) {
|
|
1041
|
+
const exact = paths[targetPath];
|
|
1042
|
+
if (isRecord(exact)) return { matchedPath: targetPath, pathItem: exact };
|
|
1043
|
+
for (const [specPath, entry] of Object.entries(paths)) {
|
|
1044
|
+
if (!isRecord(entry)) continue;
|
|
1045
|
+
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
1046
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
1047
|
+
if (regex.test(targetPath)) {
|
|
1048
|
+
return { matchedPath: specPath, pathItem: entry };
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1036
1053
|
function extractRequestBodySchema(operationSchema) {
|
|
1037
1054
|
const requestBody = operationSchema.requestBody;
|
|
1038
1055
|
if (!isRecord(requestBody)) return void 0;
|
|
@@ -1112,7 +1129,7 @@ function getL3ForOpenAPI(openApi, path, method) {
|
|
|
1112
1129
|
if (!matched) return null;
|
|
1113
1130
|
const operation = matched.pathItem[method.toLowerCase()];
|
|
1114
1131
|
if (!isRecord(operation)) return null;
|
|
1115
|
-
const resolvedOperation = resolveRefs(
|
|
1132
|
+
const resolvedOperation = resolveRefs(operation, document);
|
|
1116
1133
|
const summary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
|
|
1117
1134
|
return {
|
|
1118
1135
|
source: "openapi",
|
|
@@ -1162,12 +1179,12 @@ function getAdvisoriesForProbe(probe, path) {
|
|
|
1162
1179
|
});
|
|
1163
1180
|
}
|
|
1164
1181
|
async function checkEndpointSchema(options) {
|
|
1165
|
-
const endpoint = new URL(options.url);
|
|
1182
|
+
const endpoint = new URL(ensureProtocol(options.url));
|
|
1166
1183
|
const origin = normalizeOrigin(endpoint.origin);
|
|
1167
1184
|
const path = normalizePath(endpoint.pathname || "/");
|
|
1168
1185
|
if (options.sampleInputBody !== void 0) {
|
|
1169
1186
|
const probeResult2 = await getProbe(
|
|
1170
|
-
|
|
1187
|
+
endpoint.href,
|
|
1171
1188
|
options.headers,
|
|
1172
1189
|
options.signal,
|
|
1173
1190
|
options.sampleInputBody
|
|
@@ -1192,7 +1209,7 @@ async function checkEndpointSchema(options) {
|
|
|
1192
1209
|
if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
|
|
1193
1210
|
return { found: false, origin, path, cause: "not_found" };
|
|
1194
1211
|
}
|
|
1195
|
-
const probeResult = await getProbe(
|
|
1212
|
+
const probeResult = await getProbe(endpoint.href, options.headers, options.signal);
|
|
1196
1213
|
if (probeResult.isErr()) {
|
|
1197
1214
|
return {
|
|
1198
1215
|
found: false,
|
|
@@ -1297,7 +1314,7 @@ var import_schemas3 = require("@x402/core/schemas");
|
|
|
1297
1314
|
var DEFAULT_COMPAT_MODE = "on";
|
|
1298
1315
|
|
|
1299
1316
|
// src/x402scan-validation/payment-required.ts
|
|
1300
|
-
function
|
|
1317
|
+
function isRecord3(value) {
|
|
1301
1318
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1302
1319
|
}
|
|
1303
1320
|
function pushIssue2(issues, issue) {
|
|
@@ -1351,7 +1368,7 @@ function validatePaymentRequiredDetailed(payload, options = {}) {
|
|
|
1351
1368
|
const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
|
|
1352
1369
|
const requireInputSchema = options.requireInputSchema ?? true;
|
|
1353
1370
|
const requireOutputSchema = options.requireOutputSchema ?? true;
|
|
1354
|
-
if (!
|
|
1371
|
+
if (!isRecord3(payload)) {
|
|
1355
1372
|
pushIssue2(issues, {
|
|
1356
1373
|
code: VALIDATION_CODES.X402_NOT_OBJECT,
|
|
1357
1374
|
severity: "error",
|
|
@@ -1470,6 +1487,7 @@ var AUDIT_CODES = {
|
|
|
1470
1487
|
L2_NO_ROUTES: "L2_NO_ROUTES",
|
|
1471
1488
|
L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH",
|
|
1472
1489
|
L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING",
|
|
1490
|
+
L2_NO_PAID_ROUTES: "L2_NO_PAID_ROUTES",
|
|
1473
1491
|
L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID",
|
|
1474
1492
|
L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID",
|
|
1475
1493
|
// ─── L3 endpoint advisory checks ─────────────────────────────────────────────
|
|
@@ -1531,6 +1549,15 @@ function getWarningsForL2(l2) {
|
|
|
1531
1549
|
});
|
|
1532
1550
|
return warnings;
|
|
1533
1551
|
}
|
|
1552
|
+
const hasPaidRoute = l2.routes.some((r) => r.authMode === "paid" || r.authMode === "apiKey+paid");
|
|
1553
|
+
if (!hasPaidRoute) {
|
|
1554
|
+
warnings.push({
|
|
1555
|
+
code: AUDIT_CODES.L2_NO_PAID_ROUTES,
|
|
1556
|
+
severity: "info",
|
|
1557
|
+
message: "No endpoints are marked as paid or apiKey+paid.",
|
|
1558
|
+
hint: "Add x-payment-info to operations that require payment so agents know which endpoints are monetized."
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1534
1561
|
if (l2.routes.length > ROUTE_COUNT_HIGH) {
|
|
1535
1562
|
warnings.push({
|
|
1536
1563
|
code: AUDIT_CODES.L2_ROUTE_COUNT_HIGH,
|
|
@@ -1617,8 +1644,8 @@ function getWarningsForL4(l4) {
|
|
|
1617
1644
|
{
|
|
1618
1645
|
code: AUDIT_CODES.L4_GUIDANCE_MISSING,
|
|
1619
1646
|
severity: "info",
|
|
1620
|
-
message: "No guidance text found (
|
|
1621
|
-
hint: "Add an info.guidance field to your OpenAPI spec
|
|
1647
|
+
message: "No guidance text found (OpenAPI info.guidance).",
|
|
1648
|
+
hint: "Add an info.guidance field to your OpenAPI spec for agent-readable instructions."
|
|
1622
1649
|
}
|
|
1623
1650
|
];
|
|
1624
1651
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -325,6 +325,7 @@ declare const AUDIT_CODES: {
|
|
|
325
325
|
readonly L2_NO_ROUTES: "L2_NO_ROUTES";
|
|
326
326
|
readonly L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH";
|
|
327
327
|
readonly L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING";
|
|
328
|
+
readonly L2_NO_PAID_ROUTES: "L2_NO_PAID_ROUTES";
|
|
328
329
|
readonly L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID";
|
|
329
330
|
readonly L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID";
|
|
330
331
|
readonly L3_NOT_FOUND: "L3_NOT_FOUND";
|
package/dist/index.d.ts
CHANGED
|
@@ -325,6 +325,7 @@ declare const AUDIT_CODES: {
|
|
|
325
325
|
readonly L2_NO_ROUTES: "L2_NO_ROUTES";
|
|
326
326
|
readonly L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH";
|
|
327
327
|
readonly L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING";
|
|
328
|
+
readonly L2_NO_PAID_ROUTES: "L2_NO_PAID_ROUTES";
|
|
328
329
|
readonly L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID";
|
|
329
330
|
readonly L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID";
|
|
330
331
|
readonly L3_NOT_FOUND: "L3_NOT_FOUND";
|