@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/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 hasSecurity(operation, scheme) {
83
- return operation.security?.some((s) => scheme in s) ?? false;
84
- }
85
- function has402Response(operation) {
86
- return Boolean(operation.responses?.["402"]);
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 hasPayment = hasXPaymentInfo || has402Response(operation);
91
- const hasApiKey = hasSecurity(operation, "apiKey");
92
- const hasSiwx = hasSecurity(operation, "siwx");
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 normalizeOrigin(target) {
127
+ function ensureProtocol(target) {
116
128
  const trimmed = target.trim();
117
- const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
118
- const url = new URL(withProtocol);
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.1".includes("-mmm");
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
- if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) continue;
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/layers/l3.ts
935
- function findMatchingOpenApiPath(paths, targetPath) {
936
- const exact = paths[targetPath];
937
- if (isRecord(exact)) return { matchedPath: targetPath, pathItem: exact };
938
- for (const [specPath, entry] of Object.entries(paths)) {
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 resolveRefs(document, obj, seen, depth = 0) {
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 = resolveRef(document, value, seen);
968
- if (isRecord(deref)) {
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 (isRecord(value)) {
976
- resolved[key] = resolveRefs(document, value, seen, depth + 1);
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) => isRecord(item) ? resolveRefs(document, item, seen, depth + 1) : 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(document, operation, /* @__PURE__ */ new Set());
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
- options.url,
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(options.url, options.headers, options.signal);
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 isRecord2(value) {
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 (!isRecord2(payload)) {
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 (llms.txt or OpenAPI info.guidance).",
1574
- hint: "Add an info.guidance field to your OpenAPI spec or expose /llms.txt for agent-readable instructions."
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(),
@@ -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.1",
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
  },