@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/index.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 } : {},
|
|
@@ -931,54 +943,31 @@ function extractPaymentOptions4(wwwAuthenticate) {
|
|
|
931
943
|
return options;
|
|
932
944
|
}
|
|
933
945
|
|
|
934
|
-
// src/core/
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
if (!isRecord(entry)) continue;
|
|
940
|
-
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
941
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
942
|
-
if (regex.test(targetPath)) {
|
|
943
|
-
return { matchedPath: specPath, pathItem: entry };
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return null;
|
|
947
|
-
}
|
|
948
|
-
function resolveRef(document, ref, seen) {
|
|
949
|
-
if (!ref.startsWith("#/")) return void 0;
|
|
950
|
-
if (seen.has(ref)) return { $circular: ref };
|
|
951
|
-
seen.add(ref);
|
|
952
|
-
const parts = ref.slice(2).split("/");
|
|
953
|
-
let current = document;
|
|
954
|
-
for (const part of parts) {
|
|
955
|
-
if (!isRecord(current)) return void 0;
|
|
956
|
-
current = current[part];
|
|
957
|
-
if (current === void 0) return void 0;
|
|
958
|
-
}
|
|
959
|
-
if (isRecord(current)) return resolveRefs(document, current, seen);
|
|
960
|
-
return current;
|
|
946
|
+
// src/core/lib/resolve-ref.ts
|
|
947
|
+
import pkg from "dereference-json-schema";
|
|
948
|
+
var { resolveRefSync } = pkg;
|
|
949
|
+
function isRecord2(value) {
|
|
950
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
961
951
|
}
|
|
962
|
-
function
|
|
963
|
-
if (depth > 4) return obj;
|
|
952
|
+
function deepResolveRefs(document, obj) {
|
|
964
953
|
const resolved = {};
|
|
965
954
|
for (const [key, value] of Object.entries(obj)) {
|
|
966
955
|
if (key === "$ref" && typeof value === "string") {
|
|
967
|
-
const deref =
|
|
968
|
-
if (
|
|
969
|
-
Object.assign(resolved, deref);
|
|
956
|
+
const deref = resolveRefSync(document, value);
|
|
957
|
+
if (isRecord2(deref)) {
|
|
958
|
+
Object.assign(resolved, deepResolveRefs(document, deref));
|
|
970
959
|
} else {
|
|
971
960
|
resolved[key] = value;
|
|
972
961
|
}
|
|
973
962
|
continue;
|
|
974
963
|
}
|
|
975
|
-
if (
|
|
976
|
-
resolved[key] =
|
|
964
|
+
if (isRecord2(value)) {
|
|
965
|
+
resolved[key] = deepResolveRefs(document, value);
|
|
977
966
|
continue;
|
|
978
967
|
}
|
|
979
968
|
if (Array.isArray(value)) {
|
|
980
969
|
resolved[key] = value.map(
|
|
981
|
-
(item) =>
|
|
970
|
+
(item) => isRecord2(item) ? deepResolveRefs(document, item) : item
|
|
982
971
|
);
|
|
983
972
|
continue;
|
|
984
973
|
}
|
|
@@ -986,6 +975,24 @@ function resolveRefs(document, obj, seen, depth = 0) {
|
|
|
986
975
|
}
|
|
987
976
|
return resolved;
|
|
988
977
|
}
|
|
978
|
+
function resolveRefs(obj, document) {
|
|
979
|
+
return deepResolveRefs(document, obj);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/core/layers/l3.ts
|
|
983
|
+
function findMatchingOpenApiPath(paths, targetPath) {
|
|
984
|
+
const exact = paths[targetPath];
|
|
985
|
+
if (isRecord(exact)) return { matchedPath: targetPath, pathItem: exact };
|
|
986
|
+
for (const [specPath, entry] of Object.entries(paths)) {
|
|
987
|
+
if (!isRecord(entry)) continue;
|
|
988
|
+
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
989
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
990
|
+
if (regex.test(targetPath)) {
|
|
991
|
+
return { matchedPath: specPath, pathItem: entry };
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
989
996
|
function extractRequestBodySchema(operationSchema) {
|
|
990
997
|
const requestBody = operationSchema.requestBody;
|
|
991
998
|
if (!isRecord(requestBody)) return void 0;
|
|
@@ -1065,7 +1072,7 @@ function getL3ForOpenAPI(openApi, path, method) {
|
|
|
1065
1072
|
if (!matched) return null;
|
|
1066
1073
|
const operation = matched.pathItem[method.toLowerCase()];
|
|
1067
1074
|
if (!isRecord(operation)) return null;
|
|
1068
|
-
const resolvedOperation = resolveRefs(
|
|
1075
|
+
const resolvedOperation = resolveRefs(operation, document);
|
|
1069
1076
|
const summary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
|
|
1070
1077
|
return {
|
|
1071
1078
|
source: "openapi",
|
|
@@ -1115,12 +1122,12 @@ function getAdvisoriesForProbe(probe, path) {
|
|
|
1115
1122
|
});
|
|
1116
1123
|
}
|
|
1117
1124
|
async function checkEndpointSchema(options) {
|
|
1118
|
-
const endpoint = new URL(options.url);
|
|
1125
|
+
const endpoint = new URL(ensureProtocol(options.url));
|
|
1119
1126
|
const origin = normalizeOrigin(endpoint.origin);
|
|
1120
1127
|
const path = normalizePath(endpoint.pathname || "/");
|
|
1121
1128
|
if (options.sampleInputBody !== void 0) {
|
|
1122
1129
|
const probeResult2 = await getProbe(
|
|
1123
|
-
|
|
1130
|
+
endpoint.href,
|
|
1124
1131
|
options.headers,
|
|
1125
1132
|
options.signal,
|
|
1126
1133
|
options.sampleInputBody
|
|
@@ -1145,7 +1152,7 @@ async function checkEndpointSchema(options) {
|
|
|
1145
1152
|
if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
|
|
1146
1153
|
return { found: false, origin, path, cause: "not_found" };
|
|
1147
1154
|
}
|
|
1148
|
-
const probeResult = await getProbe(
|
|
1155
|
+
const probeResult = await getProbe(endpoint.href, options.headers, options.signal);
|
|
1149
1156
|
if (probeResult.isErr()) {
|
|
1150
1157
|
return {
|
|
1151
1158
|
found: false,
|
|
@@ -1250,7 +1257,7 @@ import { parsePaymentRequired } from "@x402/core/schemas";
|
|
|
1250
1257
|
var DEFAULT_COMPAT_MODE = "on";
|
|
1251
1258
|
|
|
1252
1259
|
// src/x402scan-validation/payment-required.ts
|
|
1253
|
-
function
|
|
1260
|
+
function isRecord3(value) {
|
|
1254
1261
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1255
1262
|
}
|
|
1256
1263
|
function pushIssue2(issues, issue) {
|
|
@@ -1304,7 +1311,7 @@ function validatePaymentRequiredDetailed(payload, options = {}) {
|
|
|
1304
1311
|
const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
|
|
1305
1312
|
const requireInputSchema = options.requireInputSchema ?? true;
|
|
1306
1313
|
const requireOutputSchema = options.requireOutputSchema ?? true;
|
|
1307
|
-
if (!
|
|
1314
|
+
if (!isRecord3(payload)) {
|
|
1308
1315
|
pushIssue2(issues, {
|
|
1309
1316
|
code: VALIDATION_CODES.X402_NOT_OBJECT,
|
|
1310
1317
|
severity: "error",
|
|
@@ -1423,6 +1430,7 @@ var AUDIT_CODES = {
|
|
|
1423
1430
|
L2_NO_ROUTES: "L2_NO_ROUTES",
|
|
1424
1431
|
L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH",
|
|
1425
1432
|
L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING",
|
|
1433
|
+
L2_NO_PAID_ROUTES: "L2_NO_PAID_ROUTES",
|
|
1426
1434
|
L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID",
|
|
1427
1435
|
L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID",
|
|
1428
1436
|
// ─── L3 endpoint advisory checks ─────────────────────────────────────────────
|
|
@@ -1484,6 +1492,15 @@ function getWarningsForL2(l2) {
|
|
|
1484
1492
|
});
|
|
1485
1493
|
return warnings;
|
|
1486
1494
|
}
|
|
1495
|
+
const hasPaidRoute = l2.routes.some((r) => r.authMode === "paid" || r.authMode === "apiKey+paid");
|
|
1496
|
+
if (!hasPaidRoute) {
|
|
1497
|
+
warnings.push({
|
|
1498
|
+
code: AUDIT_CODES.L2_NO_PAID_ROUTES,
|
|
1499
|
+
severity: "info",
|
|
1500
|
+
message: "No endpoints are marked as paid or apiKey+paid.",
|
|
1501
|
+
hint: "Add x-payment-info to operations that require payment so agents know which endpoints are monetized."
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1487
1504
|
if (l2.routes.length > ROUTE_COUNT_HIGH) {
|
|
1488
1505
|
warnings.push({
|
|
1489
1506
|
code: AUDIT_CODES.L2_ROUTE_COUNT_HIGH,
|
|
@@ -1570,8 +1587,8 @@ function getWarningsForL4(l4) {
|
|
|
1570
1587
|
{
|
|
1571
1588
|
code: AUDIT_CODES.L4_GUIDANCE_MISSING,
|
|
1572
1589
|
severity: "info",
|
|
1573
|
-
message: "No guidance text found (
|
|
1574
|
-
hint: "Add an info.guidance field to your OpenAPI spec
|
|
1590
|
+
message: "No guidance text found (OpenAPI info.guidance).",
|
|
1591
|
+
hint: "Add an info.guidance field to your OpenAPI spec for agent-readable instructions."
|
|
1575
1592
|
}
|
|
1576
1593
|
];
|
|
1577
1594
|
}
|
package/dist/schemas.cjs
CHANGED
|
@@ -76,6 +76,7 @@ var OpenApiDocSchema = import_zod.z.object({
|
|
|
76
76
|
description: import_zod.z.string().optional(),
|
|
77
77
|
guidance: import_zod.z.string().optional()
|
|
78
78
|
}),
|
|
79
|
+
security: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))).optional(),
|
|
79
80
|
servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string() })).optional(),
|
|
80
81
|
tags: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string() })).optional(),
|
|
81
82
|
components: import_zod.z.object({ securitySchemes: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional() }).optional(),
|
package/dist/schemas.d.cts
CHANGED
|
@@ -300,6 +300,7 @@ declare const OpenApiDocSchema: z.ZodObject<{
|
|
|
300
300
|
description: z.ZodOptional<z.ZodString>;
|
|
301
301
|
guidance: z.ZodOptional<z.ZodString>;
|
|
302
302
|
}, z.core.$strip>;
|
|
303
|
+
security: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
303
304
|
servers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
304
305
|
url: z.ZodString;
|
|
305
306
|
}, z.core.$strip>>>;
|
package/dist/schemas.d.ts
CHANGED
|
@@ -300,6 +300,7 @@ declare const OpenApiDocSchema: z.ZodObject<{
|
|
|
300
300
|
description: z.ZodOptional<z.ZodString>;
|
|
301
301
|
guidance: z.ZodOptional<z.ZodString>;
|
|
302
302
|
}, z.core.$strip>;
|
|
303
|
+
security: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
303
304
|
servers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
304
305
|
url: z.ZodString;
|
|
305
306
|
}, z.core.$strip>>>;
|
package/dist/schemas.js
CHANGED
|
@@ -47,6 +47,7 @@ var OpenApiDocSchema = z.object({
|
|
|
47
47
|
description: z.string().optional(),
|
|
48
48
|
guidance: z.string().optional()
|
|
49
49
|
}),
|
|
50
|
+
security: z.array(z.record(z.string(), z.array(z.string()))).optional(),
|
|
50
51
|
servers: z.array(z.object({ url: z.string() })).optional(),
|
|
51
52
|
tags: z.array(z.object({ name: z.string() })).optional(),
|
|
52
53
|
components: z.object({ securitySchemes: z.record(z.string(), z.unknown()).optional() }).optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/discovery",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Canonical OpenAPI-first discovery runtime for the agentcash ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@x402/core": "^2.5.0",
|
|
68
|
+
"dereference-json-schema": "^0.2.2",
|
|
68
69
|
"neverthrow": "^8.2.0",
|
|
69
70
|
"zod": "^4.0.0"
|
|
70
71
|
},
|