@agentcash/discovery 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  LEGACY_SUNSET_DATE: () => LEGACY_SUNSET_DATE,
25
25
  STRICT_ESCALATION_CODES: () => STRICT_ESCALATION_CODES,
26
26
  UPGRADE_WARNING_CODES: () => UPGRADE_WARNING_CODES,
27
+ VALIDATION_CODES: () => VALIDATION_CODES,
27
28
  auditContextHarness: () => auditContextHarness,
28
29
  buildContextHarnessReport: () => buildContextHarnessReport,
29
30
  computeUpgradeSignal: () => computeUpgradeSignal,
@@ -31,9 +32,11 @@ __export(index_exports, {
31
32
  discover: () => discover,
32
33
  discoverDetailed: () => discoverDetailed,
33
34
  discoverForMcp: () => discoverForMcp,
35
+ evaluateMetadataCompleteness: () => evaluateMetadataCompleteness,
34
36
  getHarnessClientProfile: () => getHarnessClientProfile,
35
37
  inspectEndpointForMcp: () => inspectEndpointForMcp,
36
- listHarnessClientProfiles: () => listHarnessClientProfiles
38
+ listHarnessClientProfiles: () => listHarnessClientProfiles,
39
+ validatePaymentRequiredDetailed: () => validatePaymentRequiredDetailed
37
40
  });
38
41
  module.exports = __toCommonJS(index_exports);
39
42
 
@@ -51,7 +54,7 @@ var STRICT_ESCALATION_CODES = [
51
54
  ];
52
55
 
53
56
  // src/mmm-enabled.ts
54
- var isMmmEnabled = () => "0.1.2".includes("-mmm");
57
+ var isMmmEnabled = () => "0.1.4".includes("-mmm");
55
58
 
56
59
  // src/core/constants.ts
57
60
  var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
@@ -221,7 +224,7 @@ function parseWellKnownPayload(payload, origin, sourceUrl) {
221
224
  "Using /.well-known/x402.instructions as compatibility guidance fallback. Prefer llms.txt.",
222
225
  {
223
226
  stage: "well-known/x402",
224
- hint: "Move guidance to llms.txt and reference via x-agentcash-guidance.llmsTxtUrl."
227
+ hint: "Move guidance to llms.txt and reference via x-discovery.llmsTxtUrl in OpenAPI."
225
228
  }
226
229
  )
227
230
  );
@@ -234,7 +237,7 @@ function parseWellKnownPayload(payload, origin, sourceUrl) {
234
237
  "Using /.well-known/x402.ownershipProofs compatibility field. Prefer OpenAPI provenance extension.",
235
238
  {
236
239
  stage: "well-known/x402",
237
- hint: "Move ownership proofs to x-agentcash-provenance.ownershipProofs in OpenAPI."
240
+ hint: "Move ownership proofs to x-discovery.ownershipProofs in OpenAPI."
238
241
  }
239
242
  )
240
243
  );
@@ -630,10 +633,31 @@ function estimateTokenCount(text) {
630
633
  return Math.ceil(text.length / 4);
631
634
  }
632
635
 
633
- // src/core/openapi.ts
636
+ // src/core/openapi-utils.ts
634
637
  function isRecord3(value) {
635
638
  return value !== null && typeof value === "object" && !Array.isArray(value);
636
639
  }
640
+ function hasSecurity(operation, scheme) {
641
+ const security = operation.security;
642
+ return Array.isArray(security) && security.some((s) => isRecord3(s) && scheme in s);
643
+ }
644
+ function has402Response(operation) {
645
+ const responses = operation.responses;
646
+ if (!isRecord3(responses)) return false;
647
+ return Boolean(responses["402"]);
648
+ }
649
+ function inferAuthMode(operation) {
650
+ if (isRecord3(operation["x-payment-info"])) return "paid";
651
+ if (has402Response(operation)) {
652
+ if (hasSecurity(operation, "siwx")) return "siwx";
653
+ return "paid";
654
+ }
655
+ if (hasSecurity(operation, "apiKey")) return "apiKey";
656
+ if (hasSecurity(operation, "siwx")) return "siwx";
657
+ return void 0;
658
+ }
659
+
660
+ // src/core/openapi.ts
637
661
  function asString(value) {
638
662
  return typeof value === "string" && value.length > 0 ? value : void 0;
639
663
  }
@@ -642,11 +666,6 @@ function parsePriceValue(value) {
642
666
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
643
667
  return void 0;
644
668
  }
645
- function require402Response(operation) {
646
- const responses = operation.responses;
647
- if (!isRecord3(responses)) return false;
648
- return Boolean(responses["402"]);
649
- }
650
669
  function parseProtocols(paymentInfo) {
651
670
  const protocols = paymentInfo.protocols;
652
671
  if (!Array.isArray(protocols)) return [];
@@ -654,15 +673,6 @@ function parseProtocols(paymentInfo) {
654
673
  (entry) => typeof entry === "string" && entry.length > 0
655
674
  );
656
675
  }
657
- function parseAuthMode(operation) {
658
- const auth = operation["x-agentcash-auth"];
659
- if (!isRecord3(auth)) return void 0;
660
- const mode = auth.mode;
661
- if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
662
- return mode;
663
- }
664
- return void 0;
665
- }
666
676
  async function runOpenApiStage(options) {
667
677
  const warnings = [];
668
678
  const resources = [];
@@ -734,10 +744,9 @@ async function runOpenApiStage(options) {
734
744
  };
735
745
  }
736
746
  const paths = document.paths;
737
- const provenance = isRecord3(document["x-agentcash-provenance"]) ? document["x-agentcash-provenance"] : void 0;
738
- const ownershipProofs = Array.isArray(provenance?.ownershipProofs) ? provenance.ownershipProofs.filter((entry) => typeof entry === "string") : [];
739
- const guidance = isRecord3(document["x-agentcash-guidance"]) ? document["x-agentcash-guidance"] : void 0;
740
- const llmsTxtUrl = asString(guidance?.llmsTxtUrl);
747
+ const discovery = isRecord3(document["x-discovery"]) ? document["x-discovery"] : void 0;
748
+ const ownershipProofs = Array.isArray(discovery?.ownershipProofs) ? discovery.ownershipProofs.filter((entry) => typeof entry === "string") : [];
749
+ const llmsTxtUrl = asString(discovery?.llmsTxtUrl);
741
750
  if (ownershipProofs.length > 0) {
742
751
  warnings.push(
743
752
  warning("OPENAPI_OWNERSHIP_PROOFS_PRESENT", "info", "OpenAPI ownership proofs detected", {
@@ -782,13 +791,13 @@ async function runOpenApiStage(options) {
782
791
  )
783
792
  );
784
793
  }
785
- const authMode = parseAuthMode(operation);
794
+ const authMode = inferAuthMode(operation);
786
795
  if (!authMode) {
787
796
  warnings.push(
788
797
  warning(
789
798
  "OPENAPI_AUTH_MODE_MISSING",
790
799
  "error",
791
- `${method} ${rawPath} missing x-agentcash-auth.mode`,
800
+ `${method} ${rawPath} missing auth mode (no x-payment-info, 402 response, or security scheme)`,
792
801
  {
793
802
  stage: "openapi"
794
803
  }
@@ -796,7 +805,7 @@ async function runOpenApiStage(options) {
796
805
  );
797
806
  continue;
798
807
  }
799
- if ((authMode === "paid" || authMode === "siwx") && !require402Response(operation)) {
808
+ if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) {
800
809
  warnings.push(
801
810
  warning(
802
811
  "OPENAPI_402_MISSING",
@@ -1375,11 +1384,6 @@ async function runDiscovery({
1375
1384
  }
1376
1385
  }
1377
1386
  }
1378
- if (!stageResult.valid) {
1379
- continue;
1380
- }
1381
- const mergeWarnings = mergeResources(merged, stageResult.resources, resourceWarnings);
1382
- warnings.push(...mergeWarnings);
1383
1387
  if (includeRaw && stageResult.raw !== void 0) {
1384
1388
  if (stageResult.stage === "openapi" || stageResult.stage === "override") {
1385
1389
  const rawObject = stageResult.raw;
@@ -1399,6 +1403,11 @@ async function runDiscovery({
1399
1403
  rawSources.interopMpp = stageResult.raw;
1400
1404
  }
1401
1405
  }
1406
+ if (!stageResult.valid) {
1407
+ continue;
1408
+ }
1409
+ const mergeWarnings = mergeResources(merged, stageResult.resources, resourceWarnings);
1410
+ warnings.push(...mergeWarnings);
1402
1411
  if (!detailed) {
1403
1412
  selectedStage = selectedStage ?? stageResult.stage;
1404
1413
  break;
@@ -1439,6 +1448,471 @@ async function runDiscovery({
1439
1448
  };
1440
1449
  }
1441
1450
 
1451
+ // src/validation/codes.ts
1452
+ var VALIDATION_CODES = {
1453
+ COINBASE_SCHEMA_INVALID: "COINBASE_SCHEMA_INVALID",
1454
+ X402_NOT_OBJECT: "X402_NOT_OBJECT",
1455
+ X402_VERSION_MISSING: "X402_VERSION_MISSING",
1456
+ X402_VERSION_UNSUPPORTED: "X402_VERSION_UNSUPPORTED",
1457
+ X402_ACCEPTS_MISSING: "X402_ACCEPTS_MISSING",
1458
+ X402_ACCEPTS_INVALID: "X402_ACCEPTS_INVALID",
1459
+ X402_ACCEPTS_EMPTY: "X402_ACCEPTS_EMPTY",
1460
+ X402_ACCEPT_ENTRY_INVALID: "X402_ACCEPT_ENTRY_INVALID",
1461
+ NETWORK_CAIP2_INVALID: "NETWORK_CAIP2_INVALID",
1462
+ NETWORK_EIP155_REFERENCE_INVALID: "NETWORK_EIP155_REFERENCE_INVALID",
1463
+ NETWORK_SOLANA_ALIAS_INVALID: "NETWORK_SOLANA_ALIAS_INVALID",
1464
+ NETWORK_SOLANA_ALIAS_COMPAT: "NETWORK_SOLANA_ALIAS_COMPAT",
1465
+ NETWORK_REFERENCE_UNKNOWN: "NETWORK_REFERENCE_UNKNOWN",
1466
+ SCHEMA_INPUT_MISSING: "SCHEMA_INPUT_MISSING",
1467
+ SCHEMA_OUTPUT_MISSING: "SCHEMA_OUTPUT_MISSING",
1468
+ METADATA_TITLE_MISSING: "METADATA_TITLE_MISSING",
1469
+ METADATA_DESCRIPTION_MISSING: "METADATA_DESCRIPTION_MISSING",
1470
+ METADATA_FAVICON_MISSING: "METADATA_FAVICON_MISSING",
1471
+ METADATA_OG_IMAGE_MISSING: "METADATA_OG_IMAGE_MISSING"
1472
+ };
1473
+
1474
+ // src/validation/metadata.ts
1475
+ function hasText(value) {
1476
+ return typeof value === "string" && value.trim().length > 0;
1477
+ }
1478
+ function hasOgImage(metadata) {
1479
+ const images = metadata.ogImages;
1480
+ if (!Array.isArray(images) || images.length === 0) return false;
1481
+ return images.some((entry) => {
1482
+ if (typeof entry === "string") {
1483
+ return entry.trim().length > 0;
1484
+ }
1485
+ if (!entry || typeof entry !== "object") return false;
1486
+ return hasText(entry.url);
1487
+ });
1488
+ }
1489
+ function evaluateMetadataCompleteness(metadata) {
1490
+ const issues = [];
1491
+ if (!hasText(metadata.title)) {
1492
+ issues.push({
1493
+ code: VALIDATION_CODES.METADATA_TITLE_MISSING,
1494
+ severity: "warn",
1495
+ message: "Metadata title is missing",
1496
+ hint: "Provide a page title to improve discovery quality and UX.",
1497
+ path: "metadata.title",
1498
+ stage: "metadata"
1499
+ });
1500
+ }
1501
+ if (!hasText(metadata.description)) {
1502
+ issues.push({
1503
+ code: VALIDATION_CODES.METADATA_DESCRIPTION_MISSING,
1504
+ severity: "warn",
1505
+ message: "Metadata description is missing",
1506
+ hint: "Provide a concise description for integrators and search previews.",
1507
+ path: "metadata.description",
1508
+ stage: "metadata"
1509
+ });
1510
+ }
1511
+ if (!hasText(metadata.favicon)) {
1512
+ issues.push({
1513
+ code: VALIDATION_CODES.METADATA_FAVICON_MISSING,
1514
+ severity: "warn",
1515
+ message: "Metadata favicon is missing",
1516
+ hint: "Expose a favicon URL for stronger brand attribution.",
1517
+ path: "metadata.favicon",
1518
+ stage: "metadata"
1519
+ });
1520
+ }
1521
+ if (!hasOgImage(metadata)) {
1522
+ issues.push({
1523
+ code: VALIDATION_CODES.METADATA_OG_IMAGE_MISSING,
1524
+ severity: "warn",
1525
+ message: "Metadata OG image is missing",
1526
+ hint: "Provide an Open Graph image to improve sharing previews.",
1527
+ path: "metadata.ogImages",
1528
+ stage: "metadata"
1529
+ });
1530
+ }
1531
+ return issues;
1532
+ }
1533
+
1534
+ // src/validation/payment-required.ts
1535
+ var import_schemas = require("@x402/core/schemas");
1536
+ var SOLANA_CANONICAL_BY_REF = {
1537
+ "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana",
1538
+ EtWTRABZaYq6iMfeYKouRu166VU2xqa1: "solana-devnet",
1539
+ "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "solana-testnet"
1540
+ };
1541
+ var SOLANA_ALIAS_BY_REF = {
1542
+ mainnet: "solana",
1543
+ devnet: "solana-devnet",
1544
+ testnet: "solana-testnet"
1545
+ };
1546
+ function isRecord4(value) {
1547
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1548
+ }
1549
+ function asNonEmptyString(value) {
1550
+ if (typeof value !== "string") return void 0;
1551
+ const trimmed = value.trim();
1552
+ return trimmed.length > 0 ? trimmed : void 0;
1553
+ }
1554
+ function asPositiveNumber(value) {
1555
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
1556
+ }
1557
+ function pushIssue(issues, issue) {
1558
+ issues.push({
1559
+ stage: issue.stage ?? "payment_required",
1560
+ ...issue
1561
+ });
1562
+ }
1563
+ function summarizeIssues(issues) {
1564
+ const summary = {
1565
+ errorCount: 0,
1566
+ warnCount: 0,
1567
+ infoCount: 0,
1568
+ byCode: {}
1569
+ };
1570
+ for (const issue of issues) {
1571
+ if (issue.severity === "error") summary.errorCount += 1;
1572
+ else if (issue.severity === "warn") summary.warnCount += 1;
1573
+ else summary.infoCount += 1;
1574
+ summary.byCode[issue.code] = (summary.byCode[issue.code] ?? 0) + 1;
1575
+ }
1576
+ return summary;
1577
+ }
1578
+ function outputSchemaMissingSeverity(compatMode) {
1579
+ return compatMode === "strict" ? "error" : "warn";
1580
+ }
1581
+ function zodPathToString(path) {
1582
+ if (path.length === 0) return "$";
1583
+ let out = "";
1584
+ for (const segment of path) {
1585
+ if (typeof segment === "number") out += `[${segment}]`;
1586
+ else out += out.length === 0 ? segment : `.${segment}`;
1587
+ }
1588
+ return out;
1589
+ }
1590
+ function parseWithCoinbaseStructuralGate(payload, issues) {
1591
+ const baseParsed = (0, import_schemas.parsePaymentRequired)(payload);
1592
+ if (baseParsed.success) {
1593
+ return baseParsed.data;
1594
+ }
1595
+ for (const issue of baseParsed.error.issues) {
1596
+ pushIssue(issues, {
1597
+ code: VALIDATION_CODES.COINBASE_SCHEMA_INVALID,
1598
+ severity: "error",
1599
+ message: `Coinbase schema validation failed: ${issue.message}`,
1600
+ path: zodPathToString(issue.path),
1601
+ actual: issue.code
1602
+ });
1603
+ }
1604
+ return void 0;
1605
+ }
1606
+ function validateV2Network(networkRaw, index, issues) {
1607
+ if (!/^[^:\s]+:[^:\s]+$/.test(networkRaw)) {
1608
+ pushIssue(issues, {
1609
+ code: VALIDATION_CODES.NETWORK_CAIP2_INVALID,
1610
+ severity: "error",
1611
+ message: `Accept at index ${index} has invalid CAIP-2 network format`,
1612
+ hint: "Expected '<namespace>:<reference>', e.g. 'eip155:8453' or 'solana:5eykt4...'.",
1613
+ path: `accepts[${index}].network`,
1614
+ expected: "<namespace>:<reference>",
1615
+ actual: networkRaw
1616
+ });
1617
+ return void 0;
1618
+ }
1619
+ const [namespace, reference] = networkRaw.split(":");
1620
+ if (namespace === "eip155") {
1621
+ if (!/^\d+$/.test(reference)) {
1622
+ pushIssue(issues, {
1623
+ code: VALIDATION_CODES.NETWORK_EIP155_REFERENCE_INVALID,
1624
+ severity: "error",
1625
+ message: `Accept at index ${index} has invalid eip155 reference`,
1626
+ hint: "Use a numeric chain id, e.g. 'eip155:8453'.",
1627
+ path: `accepts[${index}].network`,
1628
+ expected: "eip155:<numeric-chain-id>",
1629
+ actual: networkRaw
1630
+ });
1631
+ return void 0;
1632
+ }
1633
+ return networkRaw;
1634
+ }
1635
+ if (namespace === "solana") {
1636
+ const canonical = SOLANA_CANONICAL_BY_REF[reference];
1637
+ if (canonical) return canonical;
1638
+ const alias = SOLANA_ALIAS_BY_REF[reference];
1639
+ if (alias) {
1640
+ pushIssue(issues, {
1641
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_COMPAT,
1642
+ severity: "warn",
1643
+ message: `Accept at index ${index} uses compatibility Solana reference '${reference}'`,
1644
+ hint: "Use canonical Solana CAIP references for deterministic behavior.",
1645
+ path: `accepts[${index}].network`,
1646
+ actual: networkRaw
1647
+ });
1648
+ return alias;
1649
+ }
1650
+ pushIssue(issues, {
1651
+ code: VALIDATION_CODES.NETWORK_REFERENCE_UNKNOWN,
1652
+ severity: "error",
1653
+ message: `Accept at index ${index} uses unknown Solana reference '${reference}'`,
1654
+ hint: "Use canonical references for mainnet/devnet/testnet.",
1655
+ path: `accepts[${index}].network`,
1656
+ actual: networkRaw
1657
+ });
1658
+ return void 0;
1659
+ }
1660
+ return networkRaw;
1661
+ }
1662
+ function validateV1Network(networkRaw, index, issues) {
1663
+ if (networkRaw === "solana-mainnet-beta") {
1664
+ pushIssue(issues, {
1665
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
1666
+ severity: "error",
1667
+ message: `Accept at index ${index} uses invalid Solana network alias`,
1668
+ hint: "Use 'solana' (v1) or canonical Solana CAIP reference (v2).",
1669
+ path: `accepts[${index}].network`,
1670
+ expected: "solana",
1671
+ actual: networkRaw
1672
+ });
1673
+ return void 0;
1674
+ }
1675
+ if (networkRaw.startsWith("solana-") && networkRaw !== "solana-devnet") {
1676
+ pushIssue(issues, {
1677
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
1678
+ severity: "error",
1679
+ message: `Accept at index ${index} uses unsupported Solana network alias`,
1680
+ hint: "Use 'solana' or 'solana-devnet' for v1 payloads.",
1681
+ path: `accepts[${index}].network`,
1682
+ actual: networkRaw
1683
+ });
1684
+ return void 0;
1685
+ }
1686
+ if (networkRaw.includes(":")) {
1687
+ return validateV2Network(networkRaw, index, issues);
1688
+ }
1689
+ return networkRaw;
1690
+ }
1691
+ function validateAccept(acceptRaw, index, version, issues) {
1692
+ if (!isRecord4(acceptRaw)) {
1693
+ pushIssue(issues, {
1694
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1695
+ severity: "error",
1696
+ message: `Accept at index ${index} must be an object`,
1697
+ path: `accepts[${index}]`
1698
+ });
1699
+ return null;
1700
+ }
1701
+ const scheme = asNonEmptyString(acceptRaw.scheme);
1702
+ const networkRaw = asNonEmptyString(acceptRaw.network);
1703
+ const payTo = asNonEmptyString(acceptRaw.payTo);
1704
+ const asset = asNonEmptyString(acceptRaw.asset);
1705
+ const maxTimeoutSeconds = asPositiveNumber(acceptRaw.maxTimeoutSeconds);
1706
+ const amount = version === 2 ? asNonEmptyString(acceptRaw.amount) : asNonEmptyString(acceptRaw.maxAmountRequired);
1707
+ if (!scheme) {
1708
+ pushIssue(issues, {
1709
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1710
+ severity: "error",
1711
+ message: `Accept at index ${index} is missing scheme`,
1712
+ path: `accepts[${index}].scheme`
1713
+ });
1714
+ }
1715
+ if (!networkRaw) {
1716
+ pushIssue(issues, {
1717
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1718
+ severity: "error",
1719
+ message: `Accept at index ${index} is missing network`,
1720
+ path: `accepts[${index}].network`
1721
+ });
1722
+ }
1723
+ if (!amount) {
1724
+ pushIssue(issues, {
1725
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1726
+ severity: "error",
1727
+ message: `Accept at index ${index} is missing ${version === 2 ? "amount" : "maxAmountRequired"}`,
1728
+ path: `accepts[${index}].${version === 2 ? "amount" : "maxAmountRequired"}`
1729
+ });
1730
+ }
1731
+ if (!payTo) {
1732
+ pushIssue(issues, {
1733
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1734
+ severity: "error",
1735
+ message: `Accept at index ${index} is missing payTo`,
1736
+ path: `accepts[${index}].payTo`
1737
+ });
1738
+ }
1739
+ if (!asset) {
1740
+ pushIssue(issues, {
1741
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1742
+ severity: "error",
1743
+ message: `Accept at index ${index} is missing asset`,
1744
+ path: `accepts[${index}].asset`
1745
+ });
1746
+ }
1747
+ if (!maxTimeoutSeconds) {
1748
+ pushIssue(issues, {
1749
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1750
+ severity: "error",
1751
+ message: `Accept at index ${index} is missing or has invalid maxTimeoutSeconds`,
1752
+ path: `accepts[${index}].maxTimeoutSeconds`
1753
+ });
1754
+ }
1755
+ let normalizedNetwork;
1756
+ if (networkRaw) {
1757
+ normalizedNetwork = version === 2 ? validateV2Network(networkRaw, index, issues) : validateV1Network(networkRaw, index, issues);
1758
+ }
1759
+ return {
1760
+ index,
1761
+ network: normalizedNetwork ?? networkRaw ?? "unknown",
1762
+ networkRaw: networkRaw ?? "unknown",
1763
+ scheme,
1764
+ asset,
1765
+ payTo,
1766
+ amount,
1767
+ maxTimeoutSeconds
1768
+ };
1769
+ }
1770
+ function getSchemaPresence(payload, accepts, version) {
1771
+ if (version === 1) {
1772
+ const first = accepts[0];
1773
+ if (!isRecord4(first)) {
1774
+ return { hasInputSchema: false, hasOutputSchema: false };
1775
+ }
1776
+ const outputSchema = isRecord4(first.outputSchema) ? first.outputSchema : void 0;
1777
+ return {
1778
+ hasInputSchema: outputSchema?.input !== void 0 && outputSchema.input !== null,
1779
+ hasOutputSchema: outputSchema?.output !== void 0 && outputSchema.output !== null
1780
+ };
1781
+ }
1782
+ const extensions = isRecord4(payload.extensions) ? payload.extensions : void 0;
1783
+ const bazaar = extensions && isRecord4(extensions.bazaar) ? extensions.bazaar : void 0;
1784
+ const info = bazaar && isRecord4(bazaar.info) ? bazaar.info : void 0;
1785
+ return {
1786
+ hasInputSchema: info?.input !== void 0 && info.input !== null,
1787
+ hasOutputSchema: info?.output !== void 0 && info.output !== null
1788
+ };
1789
+ }
1790
+ function validatePaymentRequiredDetailed(payload, options = {}) {
1791
+ const issues = [];
1792
+ const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
1793
+ const requireInputSchema = options.requireInputSchema ?? true;
1794
+ const requireOutputSchema = options.requireOutputSchema ?? true;
1795
+ if (!isRecord4(payload)) {
1796
+ pushIssue(issues, {
1797
+ code: VALIDATION_CODES.X402_NOT_OBJECT,
1798
+ severity: "error",
1799
+ message: "Payment required payload must be a JSON object",
1800
+ path: "$"
1801
+ });
1802
+ if (options.metadata) {
1803
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1804
+ }
1805
+ return {
1806
+ valid: false,
1807
+ issues,
1808
+ summary: summarizeIssues(issues)
1809
+ };
1810
+ }
1811
+ const coinbaseParsed = parseWithCoinbaseStructuralGate(payload, issues);
1812
+ const versionRaw = payload.x402Version;
1813
+ let version;
1814
+ if (versionRaw === 1 || versionRaw === 2) {
1815
+ version = versionRaw;
1816
+ } else if (versionRaw === void 0) {
1817
+ pushIssue(issues, {
1818
+ code: VALIDATION_CODES.X402_VERSION_MISSING,
1819
+ severity: "error",
1820
+ message: "x402Version is required",
1821
+ path: "x402Version"
1822
+ });
1823
+ } else {
1824
+ pushIssue(issues, {
1825
+ code: VALIDATION_CODES.X402_VERSION_UNSUPPORTED,
1826
+ severity: "error",
1827
+ message: `Unsupported x402Version '${String(versionRaw)}'`,
1828
+ path: "x402Version",
1829
+ expected: "1 | 2",
1830
+ actual: String(versionRaw)
1831
+ });
1832
+ }
1833
+ const acceptsRaw = payload.accepts;
1834
+ let accepts = [];
1835
+ if (acceptsRaw === void 0) {
1836
+ pushIssue(issues, {
1837
+ code: VALIDATION_CODES.X402_ACCEPTS_MISSING,
1838
+ severity: "error",
1839
+ message: "accepts is required",
1840
+ path: "accepts"
1841
+ });
1842
+ } else if (!Array.isArray(acceptsRaw)) {
1843
+ pushIssue(issues, {
1844
+ code: VALIDATION_CODES.X402_ACCEPTS_INVALID,
1845
+ severity: "error",
1846
+ message: "accepts must be an array",
1847
+ path: "accepts"
1848
+ });
1849
+ } else {
1850
+ accepts = acceptsRaw;
1851
+ if (accepts.length === 0) {
1852
+ pushIssue(issues, {
1853
+ code: VALIDATION_CODES.X402_ACCEPTS_EMPTY,
1854
+ severity: "error",
1855
+ message: "accepts must contain at least one payment requirement",
1856
+ path: "accepts"
1857
+ });
1858
+ }
1859
+ }
1860
+ const normalizedAccepts = [];
1861
+ if (version && Array.isArray(accepts)) {
1862
+ accepts.forEach((accept, index) => {
1863
+ const normalized = validateAccept(accept, index, version, issues);
1864
+ if (normalized) normalizedAccepts.push(normalized);
1865
+ });
1866
+ const schemaPresence = getSchemaPresence(payload, accepts, version);
1867
+ if (requireInputSchema && !schemaPresence.hasInputSchema) {
1868
+ pushIssue(issues, {
1869
+ code: VALIDATION_CODES.SCHEMA_INPUT_MISSING,
1870
+ severity: "error",
1871
+ message: "Input schema is missing",
1872
+ hint: "Include input schema details so clients can invoke the endpoint correctly.",
1873
+ path: version === 1 ? "accepts[0].outputSchema.input" : "extensions.bazaar.info.input"
1874
+ });
1875
+ }
1876
+ if (requireOutputSchema && !schemaPresence.hasOutputSchema) {
1877
+ pushIssue(issues, {
1878
+ code: VALIDATION_CODES.SCHEMA_OUTPUT_MISSING,
1879
+ severity: outputSchemaMissingSeverity(compatMode),
1880
+ message: "Output schema is missing",
1881
+ hint: "Include output schema details so clients can validate responses.",
1882
+ path: version === 1 ? "accepts[0].outputSchema.output" : "extensions.bazaar.info.output"
1883
+ });
1884
+ }
1885
+ if (options.metadata) {
1886
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1887
+ }
1888
+ const summary2 = summarizeIssues(issues);
1889
+ return {
1890
+ valid: summary2.errorCount === 0,
1891
+ version,
1892
+ parsed: coinbaseParsed ?? payload,
1893
+ normalized: {
1894
+ version,
1895
+ accepts: normalizedAccepts,
1896
+ hasInputSchema: schemaPresence.hasInputSchema,
1897
+ hasOutputSchema: schemaPresence.hasOutputSchema
1898
+ },
1899
+ issues,
1900
+ summary: summary2
1901
+ };
1902
+ }
1903
+ if (options.metadata) {
1904
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1905
+ }
1906
+ const summary = summarizeIssues(issues);
1907
+ return {
1908
+ valid: summary.errorCount === 0,
1909
+ version,
1910
+ parsed: coinbaseParsed ?? payload,
1911
+ issues,
1912
+ summary
1913
+ };
1914
+ }
1915
+
1442
1916
  // src/adapters/mcp.ts
1443
1917
  var DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS = 1e3;
1444
1918
  var OPENAPI_METHODS = [
@@ -1451,9 +1925,6 @@ var OPENAPI_METHODS = [
1451
1925
  "OPTIONS",
1452
1926
  "TRACE"
1453
1927
  ];
1454
- function isRecord4(value) {
1455
- return value != null && typeof value === "object" && !Array.isArray(value);
1456
- }
1457
1928
  function toFiniteNumber(value) {
1458
1929
  if (typeof value === "number" && Number.isFinite(value)) return value;
1459
1930
  if (typeof value === "string" && value.trim().length > 0) {
@@ -1511,7 +1982,7 @@ function inferFailureCause(warnings) {
1511
1982
  return { cause: "not_found" };
1512
1983
  }
1513
1984
  function getOpenApiInfo(document) {
1514
- if (!isRecord4(document) || !isRecord4(document.info)) return void 0;
1985
+ if (!isRecord3(document) || !isRecord3(document.info)) return void 0;
1515
1986
  if (typeof document.info.title !== "string") return void 0;
1516
1987
  return {
1517
1988
  title: document.info.title,
@@ -1554,15 +2025,15 @@ async function resolveGuidance(options) {
1554
2025
  };
1555
2026
  }
1556
2027
  function extractPathsDocument(document) {
1557
- if (!isRecord4(document)) return void 0;
1558
- if (!isRecord4(document.paths)) return void 0;
2028
+ if (!isRecord3(document)) return void 0;
2029
+ if (!isRecord3(document.paths)) return void 0;
1559
2030
  return document.paths;
1560
2031
  }
1561
2032
  function findMatchingOpenApiPath(paths, targetPath) {
1562
2033
  const exact = paths[targetPath];
1563
- if (isRecord4(exact)) return { matchedPath: targetPath, pathItem: exact };
2034
+ if (isRecord3(exact)) return { matchedPath: targetPath, pathItem: exact };
1564
2035
  for (const [specPath, entry] of Object.entries(paths)) {
1565
- if (!isRecord4(entry)) continue;
2036
+ if (!isRecord3(entry)) continue;
1566
2037
  const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
1567
2038
  const regex = new RegExp(`^${pattern}$`);
1568
2039
  if (regex.test(targetPath)) {
@@ -1578,11 +2049,11 @@ function resolveRef(document, ref, seen) {
1578
2049
  const parts = ref.slice(2).split("/");
1579
2050
  let current = document;
1580
2051
  for (const part of parts) {
1581
- if (!isRecord4(current)) return void 0;
2052
+ if (!isRecord3(current)) return void 0;
1582
2053
  current = current[part];
1583
2054
  if (current === void 0) return void 0;
1584
2055
  }
1585
- if (isRecord4(current)) return resolveRefs(document, current, seen);
2056
+ if (isRecord3(current)) return resolveRefs(document, current, seen);
1586
2057
  return current;
1587
2058
  }
1588
2059
  function resolveRefs(document, obj, seen, depth = 0) {
@@ -1591,20 +2062,20 @@ function resolveRefs(document, obj, seen, depth = 0) {
1591
2062
  for (const [key, value] of Object.entries(obj)) {
1592
2063
  if (key === "$ref" && typeof value === "string") {
1593
2064
  const deref = resolveRef(document, value, seen);
1594
- if (isRecord4(deref)) {
2065
+ if (isRecord3(deref)) {
1595
2066
  Object.assign(resolved, deref);
1596
2067
  } else {
1597
2068
  resolved[key] = value;
1598
2069
  }
1599
2070
  continue;
1600
2071
  }
1601
- if (isRecord4(value)) {
2072
+ if (isRecord3(value)) {
1602
2073
  resolved[key] = resolveRefs(document, value, seen, depth + 1);
1603
2074
  continue;
1604
2075
  }
1605
2076
  if (Array.isArray(value)) {
1606
2077
  resolved[key] = value.map(
1607
- (item) => isRecord4(item) ? resolveRefs(document, item, seen, depth + 1) : item
2078
+ (item) => isRecord3(item) ? resolveRefs(document, item, seen, depth + 1) : item
1608
2079
  );
1609
2080
  continue;
1610
2081
  }
@@ -1614,15 +2085,15 @@ function resolveRefs(document, obj, seen, depth = 0) {
1614
2085
  }
1615
2086
  function extractRequestBodySchema(operationSchema) {
1616
2087
  const requestBody = operationSchema.requestBody;
1617
- if (!isRecord4(requestBody)) return void 0;
2088
+ if (!isRecord3(requestBody)) return void 0;
1618
2089
  const content = requestBody.content;
1619
- if (!isRecord4(content)) return void 0;
2090
+ if (!isRecord3(content)) return void 0;
1620
2091
  const jsonMediaType = content["application/json"];
1621
- if (isRecord4(jsonMediaType) && isRecord4(jsonMediaType.schema)) {
2092
+ if (isRecord3(jsonMediaType) && isRecord3(jsonMediaType.schema)) {
1622
2093
  return jsonMediaType.schema;
1623
2094
  }
1624
2095
  for (const mediaType of Object.values(content)) {
1625
- if (isRecord4(mediaType) && isRecord4(mediaType.schema)) {
2096
+ if (isRecord3(mediaType) && isRecord3(mediaType.schema)) {
1626
2097
  return mediaType.schema;
1627
2098
  }
1628
2099
  }
@@ -1631,7 +2102,7 @@ function extractRequestBodySchema(operationSchema) {
1631
2102
  function extractParameters(operationSchema) {
1632
2103
  const params = operationSchema.parameters;
1633
2104
  if (!Array.isArray(params)) return [];
1634
- return params.filter((value) => isRecord4(value));
2105
+ return params.filter((value) => isRecord3(value));
1635
2106
  }
1636
2107
  function extractInputSchema(operationSchema) {
1637
2108
  const requestBody = extractRequestBodySchema(operationSchema);
@@ -1645,7 +2116,7 @@ function extractInputSchema(operationSchema) {
1645
2116
  }
1646
2117
  function parseOperationPrice(operation) {
1647
2118
  const paymentInfo = operation["x-payment-info"];
1648
- if (!isRecord4(paymentInfo)) return void 0;
2119
+ if (!isRecord3(paymentInfo)) return void 0;
1649
2120
  const fixed = toFiniteNumber(paymentInfo.price);
1650
2121
  if (fixed != null) return `$${String(fixed)}`;
1651
2122
  const min = toFiniteNumber(paymentInfo.minPrice);
@@ -1655,23 +2126,12 @@ function parseOperationPrice(operation) {
1655
2126
  }
1656
2127
  function parseOperationProtocols(operation) {
1657
2128
  const paymentInfo = operation["x-payment-info"];
1658
- if (!isRecord4(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
2129
+ if (!isRecord3(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
1659
2130
  const protocols = paymentInfo.protocols.filter(
1660
2131
  (protocol) => typeof protocol === "string" && protocol.length > 0
1661
2132
  );
1662
2133
  return protocols.length > 0 ? protocols : void 0;
1663
2134
  }
1664
- function parseOperationAuthMode(operation) {
1665
- const authExtension = operation["x-agentcash-auth"];
1666
- if (isRecord4(authExtension)) {
1667
- const mode = authExtension.mode;
1668
- if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
1669
- return mode;
1670
- }
1671
- }
1672
- if (isRecord4(operation["x-payment-info"])) return "paid";
1673
- return void 0;
1674
- }
1675
2135
  function createResourceMap(result) {
1676
2136
  const map = /* @__PURE__ */ new Map();
1677
2137
  for (const resource of result.resources) {
@@ -1763,7 +2223,7 @@ async function inspectEndpointForMcp(options) {
1763
2223
  if (matched) {
1764
2224
  for (const candidate of OPENAPI_METHODS) {
1765
2225
  const operation = matched.pathItem[candidate.toLowerCase()];
1766
- if (!isRecord4(operation) || !isRecord4(document)) continue;
2226
+ if (!isRecord3(operation) || !isRecord3(document)) continue;
1767
2227
  specMethods.push(candidate);
1768
2228
  const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
1769
2229
  const resource = resourceMap.get(toResourceKey(origin, candidate, matched.matchedPath));
@@ -1771,7 +2231,7 @@ async function inspectEndpointForMcp(options) {
1771
2231
  const operationSummary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
1772
2232
  const operationPrice = parseOperationPrice(resolvedOperation);
1773
2233
  const operationProtocols = parseOperationProtocols(resolvedOperation);
1774
- const operationAuthMode = parseOperationAuthMode(resolvedOperation);
2234
+ const operationAuthMode = inferAuthMode(resolvedOperation) ?? "unprotected";
1775
2235
  const inputSchema = extractInputSchema(resolvedOperation);
1776
2236
  advisories[candidate] = {
1777
2237
  method: candidate,
@@ -2082,6 +2542,7 @@ async function discoverDetailed(options) {
2082
2542
  LEGACY_SUNSET_DATE,
2083
2543
  STRICT_ESCALATION_CODES,
2084
2544
  UPGRADE_WARNING_CODES,
2545
+ VALIDATION_CODES,
2085
2546
  auditContextHarness,
2086
2547
  buildContextHarnessReport,
2087
2548
  computeUpgradeSignal,
@@ -2089,7 +2550,9 @@ async function discoverDetailed(options) {
2089
2550
  discover,
2090
2551
  discoverDetailed,
2091
2552
  discoverForMcp,
2553
+ evaluateMetadataCompleteness,
2092
2554
  getHarnessClientProfile,
2093
2555
  inspectEndpointForMcp,
2094
- listHarnessClientProfiles
2556
+ listHarnessClientProfiles,
2557
+ validatePaymentRequiredDetailed
2095
2558
  });