@agentcash/discovery 0.1.1 → 0.1.3

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
@@ -12,7 +12,7 @@ var STRICT_ESCALATION_CODES = [
12
12
  ];
13
13
 
14
14
  // src/mmm-enabled.ts
15
- var isMmmEnabled = () => "0.1.1".includes("-mmm");
15
+ var isMmmEnabled = () => "0.1.3".includes("-mmm");
16
16
 
17
17
  // src/core/constants.ts
18
18
  var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
@@ -1400,6 +1400,861 @@ async function runDiscovery({
1400
1400
  };
1401
1401
  }
1402
1402
 
1403
+ // src/validation/codes.ts
1404
+ var VALIDATION_CODES = {
1405
+ COINBASE_SCHEMA_INVALID: "COINBASE_SCHEMA_INVALID",
1406
+ X402_NOT_OBJECT: "X402_NOT_OBJECT",
1407
+ X402_VERSION_MISSING: "X402_VERSION_MISSING",
1408
+ X402_VERSION_UNSUPPORTED: "X402_VERSION_UNSUPPORTED",
1409
+ X402_ACCEPTS_MISSING: "X402_ACCEPTS_MISSING",
1410
+ X402_ACCEPTS_INVALID: "X402_ACCEPTS_INVALID",
1411
+ X402_ACCEPTS_EMPTY: "X402_ACCEPTS_EMPTY",
1412
+ X402_ACCEPT_ENTRY_INVALID: "X402_ACCEPT_ENTRY_INVALID",
1413
+ NETWORK_CAIP2_INVALID: "NETWORK_CAIP2_INVALID",
1414
+ NETWORK_EIP155_REFERENCE_INVALID: "NETWORK_EIP155_REFERENCE_INVALID",
1415
+ NETWORK_SOLANA_ALIAS_INVALID: "NETWORK_SOLANA_ALIAS_INVALID",
1416
+ NETWORK_SOLANA_ALIAS_COMPAT: "NETWORK_SOLANA_ALIAS_COMPAT",
1417
+ NETWORK_REFERENCE_UNKNOWN: "NETWORK_REFERENCE_UNKNOWN",
1418
+ SCHEMA_INPUT_MISSING: "SCHEMA_INPUT_MISSING",
1419
+ SCHEMA_OUTPUT_MISSING: "SCHEMA_OUTPUT_MISSING",
1420
+ METADATA_TITLE_MISSING: "METADATA_TITLE_MISSING",
1421
+ METADATA_DESCRIPTION_MISSING: "METADATA_DESCRIPTION_MISSING",
1422
+ METADATA_FAVICON_MISSING: "METADATA_FAVICON_MISSING",
1423
+ METADATA_OG_IMAGE_MISSING: "METADATA_OG_IMAGE_MISSING"
1424
+ };
1425
+
1426
+ // src/validation/metadata.ts
1427
+ function hasText(value) {
1428
+ return typeof value === "string" && value.trim().length > 0;
1429
+ }
1430
+ function hasOgImage(metadata) {
1431
+ const images = metadata.ogImages;
1432
+ if (!Array.isArray(images) || images.length === 0) return false;
1433
+ return images.some((entry) => {
1434
+ if (typeof entry === "string") {
1435
+ return entry.trim().length > 0;
1436
+ }
1437
+ if (!entry || typeof entry !== "object") return false;
1438
+ return hasText(entry.url);
1439
+ });
1440
+ }
1441
+ function evaluateMetadataCompleteness(metadata) {
1442
+ const issues = [];
1443
+ if (!hasText(metadata.title)) {
1444
+ issues.push({
1445
+ code: VALIDATION_CODES.METADATA_TITLE_MISSING,
1446
+ severity: "warn",
1447
+ message: "Metadata title is missing",
1448
+ hint: "Provide a page title to improve discovery quality and UX.",
1449
+ path: "metadata.title",
1450
+ stage: "metadata"
1451
+ });
1452
+ }
1453
+ if (!hasText(metadata.description)) {
1454
+ issues.push({
1455
+ code: VALIDATION_CODES.METADATA_DESCRIPTION_MISSING,
1456
+ severity: "warn",
1457
+ message: "Metadata description is missing",
1458
+ hint: "Provide a concise description for integrators and search previews.",
1459
+ path: "metadata.description",
1460
+ stage: "metadata"
1461
+ });
1462
+ }
1463
+ if (!hasText(metadata.favicon)) {
1464
+ issues.push({
1465
+ code: VALIDATION_CODES.METADATA_FAVICON_MISSING,
1466
+ severity: "warn",
1467
+ message: "Metadata favicon is missing",
1468
+ hint: "Expose a favicon URL for stronger brand attribution.",
1469
+ path: "metadata.favicon",
1470
+ stage: "metadata"
1471
+ });
1472
+ }
1473
+ if (!hasOgImage(metadata)) {
1474
+ issues.push({
1475
+ code: VALIDATION_CODES.METADATA_OG_IMAGE_MISSING,
1476
+ severity: "warn",
1477
+ message: "Metadata OG image is missing",
1478
+ hint: "Provide an Open Graph image to improve sharing previews.",
1479
+ path: "metadata.ogImages",
1480
+ stage: "metadata"
1481
+ });
1482
+ }
1483
+ return issues;
1484
+ }
1485
+
1486
+ // src/validation/payment-required.ts
1487
+ import { parsePaymentRequired } from "@x402/core/schemas";
1488
+ var SOLANA_CANONICAL_BY_REF = {
1489
+ "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana",
1490
+ EtWTRABZaYq6iMfeYKouRu166VU2xqa1: "solana-devnet",
1491
+ "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "solana-testnet"
1492
+ };
1493
+ var SOLANA_ALIAS_BY_REF = {
1494
+ mainnet: "solana",
1495
+ devnet: "solana-devnet",
1496
+ testnet: "solana-testnet"
1497
+ };
1498
+ function isRecord4(value) {
1499
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1500
+ }
1501
+ function asNonEmptyString(value) {
1502
+ if (typeof value !== "string") return void 0;
1503
+ const trimmed = value.trim();
1504
+ return trimmed.length > 0 ? trimmed : void 0;
1505
+ }
1506
+ function asPositiveNumber(value) {
1507
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
1508
+ }
1509
+ function pushIssue(issues, issue) {
1510
+ issues.push({
1511
+ stage: issue.stage ?? "payment_required",
1512
+ ...issue
1513
+ });
1514
+ }
1515
+ function summarizeIssues(issues) {
1516
+ const summary = {
1517
+ errorCount: 0,
1518
+ warnCount: 0,
1519
+ infoCount: 0,
1520
+ byCode: {}
1521
+ };
1522
+ for (const issue of issues) {
1523
+ if (issue.severity === "error") summary.errorCount += 1;
1524
+ else if (issue.severity === "warn") summary.warnCount += 1;
1525
+ else summary.infoCount += 1;
1526
+ summary.byCode[issue.code] = (summary.byCode[issue.code] ?? 0) + 1;
1527
+ }
1528
+ return summary;
1529
+ }
1530
+ function outputSchemaMissingSeverity(compatMode) {
1531
+ return compatMode === "strict" ? "error" : "warn";
1532
+ }
1533
+ function zodPathToString(path) {
1534
+ if (path.length === 0) return "$";
1535
+ let out = "";
1536
+ for (const segment of path) {
1537
+ if (typeof segment === "number") out += `[${segment}]`;
1538
+ else out += out.length === 0 ? segment : `.${segment}`;
1539
+ }
1540
+ return out;
1541
+ }
1542
+ function parseWithCoinbaseStructuralGate(payload, issues) {
1543
+ const baseParsed = parsePaymentRequired(payload);
1544
+ if (baseParsed.success) {
1545
+ return baseParsed.data;
1546
+ }
1547
+ for (const issue of baseParsed.error.issues) {
1548
+ pushIssue(issues, {
1549
+ code: VALIDATION_CODES.COINBASE_SCHEMA_INVALID,
1550
+ severity: "error",
1551
+ message: `Coinbase schema validation failed: ${issue.message}`,
1552
+ path: zodPathToString(issue.path),
1553
+ actual: issue.code
1554
+ });
1555
+ }
1556
+ return void 0;
1557
+ }
1558
+ function validateV2Network(networkRaw, index, issues) {
1559
+ if (!/^[^:\s]+:[^:\s]+$/.test(networkRaw)) {
1560
+ pushIssue(issues, {
1561
+ code: VALIDATION_CODES.NETWORK_CAIP2_INVALID,
1562
+ severity: "error",
1563
+ message: `Accept at index ${index} has invalid CAIP-2 network format`,
1564
+ hint: "Expected '<namespace>:<reference>', e.g. 'eip155:8453' or 'solana:5eykt4...'.",
1565
+ path: `accepts[${index}].network`,
1566
+ expected: "<namespace>:<reference>",
1567
+ actual: networkRaw
1568
+ });
1569
+ return void 0;
1570
+ }
1571
+ const [namespace, reference] = networkRaw.split(":");
1572
+ if (namespace === "eip155") {
1573
+ if (!/^\d+$/.test(reference)) {
1574
+ pushIssue(issues, {
1575
+ code: VALIDATION_CODES.NETWORK_EIP155_REFERENCE_INVALID,
1576
+ severity: "error",
1577
+ message: `Accept at index ${index} has invalid eip155 reference`,
1578
+ hint: "Use a numeric chain id, e.g. 'eip155:8453'.",
1579
+ path: `accepts[${index}].network`,
1580
+ expected: "eip155:<numeric-chain-id>",
1581
+ actual: networkRaw
1582
+ });
1583
+ return void 0;
1584
+ }
1585
+ return networkRaw;
1586
+ }
1587
+ if (namespace === "solana") {
1588
+ const canonical = SOLANA_CANONICAL_BY_REF[reference];
1589
+ if (canonical) return canonical;
1590
+ const alias = SOLANA_ALIAS_BY_REF[reference];
1591
+ if (alias) {
1592
+ pushIssue(issues, {
1593
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_COMPAT,
1594
+ severity: "warn",
1595
+ message: `Accept at index ${index} uses compatibility Solana reference '${reference}'`,
1596
+ hint: "Use canonical Solana CAIP references for deterministic behavior.",
1597
+ path: `accepts[${index}].network`,
1598
+ actual: networkRaw
1599
+ });
1600
+ return alias;
1601
+ }
1602
+ pushIssue(issues, {
1603
+ code: VALIDATION_CODES.NETWORK_REFERENCE_UNKNOWN,
1604
+ severity: "error",
1605
+ message: `Accept at index ${index} uses unknown Solana reference '${reference}'`,
1606
+ hint: "Use canonical references for mainnet/devnet/testnet.",
1607
+ path: `accepts[${index}].network`,
1608
+ actual: networkRaw
1609
+ });
1610
+ return void 0;
1611
+ }
1612
+ return networkRaw;
1613
+ }
1614
+ function validateV1Network(networkRaw, index, issues) {
1615
+ if (networkRaw === "solana-mainnet-beta") {
1616
+ pushIssue(issues, {
1617
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
1618
+ severity: "error",
1619
+ message: `Accept at index ${index} uses invalid Solana network alias`,
1620
+ hint: "Use 'solana' (v1) or canonical Solana CAIP reference (v2).",
1621
+ path: `accepts[${index}].network`,
1622
+ expected: "solana",
1623
+ actual: networkRaw
1624
+ });
1625
+ return void 0;
1626
+ }
1627
+ if (networkRaw.startsWith("solana-") && networkRaw !== "solana-devnet") {
1628
+ pushIssue(issues, {
1629
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
1630
+ severity: "error",
1631
+ message: `Accept at index ${index} uses unsupported Solana network alias`,
1632
+ hint: "Use 'solana' or 'solana-devnet' for v1 payloads.",
1633
+ path: `accepts[${index}].network`,
1634
+ actual: networkRaw
1635
+ });
1636
+ return void 0;
1637
+ }
1638
+ if (networkRaw.includes(":")) {
1639
+ return validateV2Network(networkRaw, index, issues);
1640
+ }
1641
+ return networkRaw;
1642
+ }
1643
+ function validateAccept(acceptRaw, index, version, issues) {
1644
+ if (!isRecord4(acceptRaw)) {
1645
+ pushIssue(issues, {
1646
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1647
+ severity: "error",
1648
+ message: `Accept at index ${index} must be an object`,
1649
+ path: `accepts[${index}]`
1650
+ });
1651
+ return null;
1652
+ }
1653
+ const scheme = asNonEmptyString(acceptRaw.scheme);
1654
+ const networkRaw = asNonEmptyString(acceptRaw.network);
1655
+ const payTo = asNonEmptyString(acceptRaw.payTo);
1656
+ const asset = asNonEmptyString(acceptRaw.asset);
1657
+ const maxTimeoutSeconds = asPositiveNumber(acceptRaw.maxTimeoutSeconds);
1658
+ const amount = version === 2 ? asNonEmptyString(acceptRaw.amount) : asNonEmptyString(acceptRaw.maxAmountRequired);
1659
+ if (!scheme) {
1660
+ pushIssue(issues, {
1661
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1662
+ severity: "error",
1663
+ message: `Accept at index ${index} is missing scheme`,
1664
+ path: `accepts[${index}].scheme`
1665
+ });
1666
+ }
1667
+ if (!networkRaw) {
1668
+ pushIssue(issues, {
1669
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1670
+ severity: "error",
1671
+ message: `Accept at index ${index} is missing network`,
1672
+ path: `accepts[${index}].network`
1673
+ });
1674
+ }
1675
+ if (!amount) {
1676
+ pushIssue(issues, {
1677
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1678
+ severity: "error",
1679
+ message: `Accept at index ${index} is missing ${version === 2 ? "amount" : "maxAmountRequired"}`,
1680
+ path: `accepts[${index}].${version === 2 ? "amount" : "maxAmountRequired"}`
1681
+ });
1682
+ }
1683
+ if (!payTo) {
1684
+ pushIssue(issues, {
1685
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1686
+ severity: "error",
1687
+ message: `Accept at index ${index} is missing payTo`,
1688
+ path: `accepts[${index}].payTo`
1689
+ });
1690
+ }
1691
+ if (!asset) {
1692
+ pushIssue(issues, {
1693
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1694
+ severity: "error",
1695
+ message: `Accept at index ${index} is missing asset`,
1696
+ path: `accepts[${index}].asset`
1697
+ });
1698
+ }
1699
+ if (!maxTimeoutSeconds) {
1700
+ pushIssue(issues, {
1701
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1702
+ severity: "error",
1703
+ message: `Accept at index ${index} is missing or has invalid maxTimeoutSeconds`,
1704
+ path: `accepts[${index}].maxTimeoutSeconds`
1705
+ });
1706
+ }
1707
+ let normalizedNetwork;
1708
+ if (networkRaw) {
1709
+ normalizedNetwork = version === 2 ? validateV2Network(networkRaw, index, issues) : validateV1Network(networkRaw, index, issues);
1710
+ }
1711
+ return {
1712
+ index,
1713
+ network: normalizedNetwork ?? networkRaw ?? "unknown",
1714
+ networkRaw: networkRaw ?? "unknown",
1715
+ scheme,
1716
+ asset,
1717
+ payTo,
1718
+ amount,
1719
+ maxTimeoutSeconds
1720
+ };
1721
+ }
1722
+ function getSchemaPresence(payload, accepts, version) {
1723
+ if (version === 1) {
1724
+ const first = accepts[0];
1725
+ if (!isRecord4(first)) {
1726
+ return { hasInputSchema: false, hasOutputSchema: false };
1727
+ }
1728
+ const outputSchema = isRecord4(first.outputSchema) ? first.outputSchema : void 0;
1729
+ return {
1730
+ hasInputSchema: outputSchema?.input !== void 0 && outputSchema.input !== null,
1731
+ hasOutputSchema: outputSchema?.output !== void 0 && outputSchema.output !== null
1732
+ };
1733
+ }
1734
+ const extensions = isRecord4(payload.extensions) ? payload.extensions : void 0;
1735
+ const bazaar = extensions && isRecord4(extensions.bazaar) ? extensions.bazaar : void 0;
1736
+ const info = bazaar && isRecord4(bazaar.info) ? bazaar.info : void 0;
1737
+ return {
1738
+ hasInputSchema: info?.input !== void 0 && info.input !== null,
1739
+ hasOutputSchema: info?.output !== void 0 && info.output !== null
1740
+ };
1741
+ }
1742
+ function validatePaymentRequiredDetailed(payload, options = {}) {
1743
+ const issues = [];
1744
+ const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
1745
+ const requireInputSchema = options.requireInputSchema ?? true;
1746
+ const requireOutputSchema = options.requireOutputSchema ?? true;
1747
+ if (!isRecord4(payload)) {
1748
+ pushIssue(issues, {
1749
+ code: VALIDATION_CODES.X402_NOT_OBJECT,
1750
+ severity: "error",
1751
+ message: "Payment required payload must be a JSON object",
1752
+ path: "$"
1753
+ });
1754
+ if (options.metadata) {
1755
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1756
+ }
1757
+ return {
1758
+ valid: false,
1759
+ issues,
1760
+ summary: summarizeIssues(issues)
1761
+ };
1762
+ }
1763
+ const coinbaseParsed = parseWithCoinbaseStructuralGate(payload, issues);
1764
+ const versionRaw = payload.x402Version;
1765
+ let version;
1766
+ if (versionRaw === 1 || versionRaw === 2) {
1767
+ version = versionRaw;
1768
+ } else if (versionRaw === void 0) {
1769
+ pushIssue(issues, {
1770
+ code: VALIDATION_CODES.X402_VERSION_MISSING,
1771
+ severity: "error",
1772
+ message: "x402Version is required",
1773
+ path: "x402Version"
1774
+ });
1775
+ } else {
1776
+ pushIssue(issues, {
1777
+ code: VALIDATION_CODES.X402_VERSION_UNSUPPORTED,
1778
+ severity: "error",
1779
+ message: `Unsupported x402Version '${String(versionRaw)}'`,
1780
+ path: "x402Version",
1781
+ expected: "1 | 2",
1782
+ actual: String(versionRaw)
1783
+ });
1784
+ }
1785
+ const acceptsRaw = payload.accepts;
1786
+ let accepts = [];
1787
+ if (acceptsRaw === void 0) {
1788
+ pushIssue(issues, {
1789
+ code: VALIDATION_CODES.X402_ACCEPTS_MISSING,
1790
+ severity: "error",
1791
+ message: "accepts is required",
1792
+ path: "accepts"
1793
+ });
1794
+ } else if (!Array.isArray(acceptsRaw)) {
1795
+ pushIssue(issues, {
1796
+ code: VALIDATION_CODES.X402_ACCEPTS_INVALID,
1797
+ severity: "error",
1798
+ message: "accepts must be an array",
1799
+ path: "accepts"
1800
+ });
1801
+ } else {
1802
+ accepts = acceptsRaw;
1803
+ if (accepts.length === 0) {
1804
+ pushIssue(issues, {
1805
+ code: VALIDATION_CODES.X402_ACCEPTS_EMPTY,
1806
+ severity: "error",
1807
+ message: "accepts must contain at least one payment requirement",
1808
+ path: "accepts"
1809
+ });
1810
+ }
1811
+ }
1812
+ const normalizedAccepts = [];
1813
+ if (version && Array.isArray(accepts)) {
1814
+ accepts.forEach((accept, index) => {
1815
+ const normalized = validateAccept(accept, index, version, issues);
1816
+ if (normalized) normalizedAccepts.push(normalized);
1817
+ });
1818
+ const schemaPresence = getSchemaPresence(payload, accepts, version);
1819
+ if (requireInputSchema && !schemaPresence.hasInputSchema) {
1820
+ pushIssue(issues, {
1821
+ code: VALIDATION_CODES.SCHEMA_INPUT_MISSING,
1822
+ severity: "error",
1823
+ message: "Input schema is missing",
1824
+ hint: "Include input schema details so clients can invoke the endpoint correctly.",
1825
+ path: version === 1 ? "accepts[0].outputSchema.input" : "extensions.bazaar.info.input"
1826
+ });
1827
+ }
1828
+ if (requireOutputSchema && !schemaPresence.hasOutputSchema) {
1829
+ pushIssue(issues, {
1830
+ code: VALIDATION_CODES.SCHEMA_OUTPUT_MISSING,
1831
+ severity: outputSchemaMissingSeverity(compatMode),
1832
+ message: "Output schema is missing",
1833
+ hint: "Include output schema details so clients can validate responses.",
1834
+ path: version === 1 ? "accepts[0].outputSchema.output" : "extensions.bazaar.info.output"
1835
+ });
1836
+ }
1837
+ if (options.metadata) {
1838
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1839
+ }
1840
+ const summary2 = summarizeIssues(issues);
1841
+ return {
1842
+ valid: summary2.errorCount === 0,
1843
+ version,
1844
+ parsed: coinbaseParsed ?? payload,
1845
+ normalized: {
1846
+ version,
1847
+ accepts: normalizedAccepts,
1848
+ hasInputSchema: schemaPresence.hasInputSchema,
1849
+ hasOutputSchema: schemaPresence.hasOutputSchema
1850
+ },
1851
+ issues,
1852
+ summary: summary2
1853
+ };
1854
+ }
1855
+ if (options.metadata) {
1856
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1857
+ }
1858
+ const summary = summarizeIssues(issues);
1859
+ return {
1860
+ valid: summary.errorCount === 0,
1861
+ version,
1862
+ parsed: coinbaseParsed ?? payload,
1863
+ issues,
1864
+ summary
1865
+ };
1866
+ }
1867
+
1868
+ // src/adapters/mcp.ts
1869
+ var DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS = 1e3;
1870
+ var OPENAPI_METHODS = [
1871
+ "GET",
1872
+ "POST",
1873
+ "PUT",
1874
+ "DELETE",
1875
+ "PATCH",
1876
+ "HEAD",
1877
+ "OPTIONS",
1878
+ "TRACE"
1879
+ ];
1880
+ function isRecord5(value) {
1881
+ return value != null && typeof value === "object" && !Array.isArray(value);
1882
+ }
1883
+ function toFiniteNumber(value) {
1884
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1885
+ if (typeof value === "string" && value.trim().length > 0) {
1886
+ const parsed = Number(value);
1887
+ if (Number.isFinite(parsed)) return parsed;
1888
+ }
1889
+ return void 0;
1890
+ }
1891
+ function formatPriceHintFromResource(resource) {
1892
+ const pricing = resource.pricing;
1893
+ if (pricing) {
1894
+ if (pricing.pricingMode === "fixed" && pricing.price) {
1895
+ return pricing.price.startsWith("$") ? pricing.price : `$${pricing.price}`;
1896
+ }
1897
+ if (pricing.pricingMode === "range" && pricing.minPrice && pricing.maxPrice) {
1898
+ const min = pricing.minPrice.startsWith("$") ? pricing.minPrice : `$${pricing.minPrice}`;
1899
+ const max = pricing.maxPrice.startsWith("$") ? pricing.maxPrice : `$${pricing.maxPrice}`;
1900
+ return `${min}-${max}`;
1901
+ }
1902
+ }
1903
+ if (!resource.priceHint) return void 0;
1904
+ const hint = resource.priceHint.trim();
1905
+ if (hint.length === 0) return void 0;
1906
+ if (hint.startsWith("$")) return hint;
1907
+ if (hint.includes("-")) {
1908
+ const [min, max] = hint.split("-", 2).map((part) => part.trim());
1909
+ if (min && max) return `$${min}-$${max}`;
1910
+ }
1911
+ return `$${hint}`;
1912
+ }
1913
+ function deriveMcpAuthMode(resource) {
1914
+ if (!resource) return void 0;
1915
+ const hint = resource.authHint;
1916
+ if (hint === "paid" || hint === "siwx" || hint === "apiKey" || hint === "unprotected") {
1917
+ return hint;
1918
+ }
1919
+ return void 0;
1920
+ }
1921
+ function inferFailureCause(warnings) {
1922
+ const openapiWarnings = warnings.filter((w) => w.stage === "openapi");
1923
+ const parseWarning = openapiWarnings.find(
1924
+ (w) => w.code === "PARSE_FAILED" || w.code === "OPENAPI_TOP_LEVEL_INVALID" || w.code === "OPENAPI_OPERATION_INVALID" || w.code === "OPENAPI_PRICING_INVALID"
1925
+ );
1926
+ if (parseWarning) {
1927
+ return { cause: "parse", message: parseWarning.message };
1928
+ }
1929
+ const fetchWarning = openapiWarnings.find((w) => w.code === "FETCH_FAILED");
1930
+ if (fetchWarning) {
1931
+ const msg = fetchWarning.message.toLowerCase();
1932
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("abort")) {
1933
+ return { cause: "timeout", message: fetchWarning.message };
1934
+ }
1935
+ return { cause: "network", message: fetchWarning.message };
1936
+ }
1937
+ return { cause: "not_found" };
1938
+ }
1939
+ function getOpenApiInfo(document) {
1940
+ if (!isRecord5(document) || !isRecord5(document.info)) return void 0;
1941
+ if (typeof document.info.title !== "string") return void 0;
1942
+ return {
1943
+ title: document.info.title,
1944
+ ...typeof document.info.version === "string" ? { version: document.info.version } : {},
1945
+ ...typeof document.info.description === "string" ? { description: document.info.description } : {}
1946
+ };
1947
+ }
1948
+ function getGuidanceUrl(result) {
1949
+ const fromTrace = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1950
+ if (fromTrace) return fromTrace;
1951
+ const fromResource = result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1952
+ if (fromResource) return fromResource;
1953
+ return void 0;
1954
+ }
1955
+ async function resolveGuidance(options) {
1956
+ let guidanceText = typeof options.result.rawSources?.llmsTxt === "string" ? options.result.rawSources.llmsTxt : void 0;
1957
+ if (!guidanceText) {
1958
+ const guidanceUrl = getGuidanceUrl(options.result) ?? `${options.result.origin}/llms.txt`;
1959
+ const fetcher = options.fetcher ?? fetch;
1960
+ try {
1961
+ const response = await fetcher(guidanceUrl, {
1962
+ method: "GET",
1963
+ headers: { Accept: "text/plain", ...options.headers },
1964
+ signal: options.signal
1965
+ });
1966
+ if (response.ok) guidanceText = await response.text();
1967
+ } catch {
1968
+ }
1969
+ }
1970
+ if (!guidanceText) {
1971
+ return { guidanceAvailable: false };
1972
+ }
1973
+ const guidanceTokens = estimateTokenCount(guidanceText);
1974
+ const threshold = options.guidanceAutoIncludeMaxTokens ?? DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS;
1975
+ const shouldInclude = options.includeGuidance === true || options.includeGuidance == null && guidanceTokens <= threshold;
1976
+ return {
1977
+ guidanceAvailable: true,
1978
+ guidanceTokens,
1979
+ ...shouldInclude ? { guidance: guidanceText } : {}
1980
+ };
1981
+ }
1982
+ function extractPathsDocument(document) {
1983
+ if (!isRecord5(document)) return void 0;
1984
+ if (!isRecord5(document.paths)) return void 0;
1985
+ return document.paths;
1986
+ }
1987
+ function findMatchingOpenApiPath(paths, targetPath) {
1988
+ const exact = paths[targetPath];
1989
+ if (isRecord5(exact)) return { matchedPath: targetPath, pathItem: exact };
1990
+ for (const [specPath, entry] of Object.entries(paths)) {
1991
+ if (!isRecord5(entry)) continue;
1992
+ const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
1993
+ const regex = new RegExp(`^${pattern}$`);
1994
+ if (regex.test(targetPath)) {
1995
+ return { matchedPath: specPath, pathItem: entry };
1996
+ }
1997
+ }
1998
+ return null;
1999
+ }
2000
+ function resolveRef(document, ref, seen) {
2001
+ if (!ref.startsWith("#/")) return void 0;
2002
+ if (seen.has(ref)) return { $circular: ref };
2003
+ seen.add(ref);
2004
+ const parts = ref.slice(2).split("/");
2005
+ let current = document;
2006
+ for (const part of parts) {
2007
+ if (!isRecord5(current)) return void 0;
2008
+ current = current[part];
2009
+ if (current === void 0) return void 0;
2010
+ }
2011
+ if (isRecord5(current)) return resolveRefs(document, current, seen);
2012
+ return current;
2013
+ }
2014
+ function resolveRefs(document, obj, seen, depth = 0) {
2015
+ if (depth > 4) return obj;
2016
+ const resolved = {};
2017
+ for (const [key, value] of Object.entries(obj)) {
2018
+ if (key === "$ref" && typeof value === "string") {
2019
+ const deref = resolveRef(document, value, seen);
2020
+ if (isRecord5(deref)) {
2021
+ Object.assign(resolved, deref);
2022
+ } else {
2023
+ resolved[key] = value;
2024
+ }
2025
+ continue;
2026
+ }
2027
+ if (isRecord5(value)) {
2028
+ resolved[key] = resolveRefs(document, value, seen, depth + 1);
2029
+ continue;
2030
+ }
2031
+ if (Array.isArray(value)) {
2032
+ resolved[key] = value.map(
2033
+ (item) => isRecord5(item) ? resolveRefs(document, item, seen, depth + 1) : item
2034
+ );
2035
+ continue;
2036
+ }
2037
+ resolved[key] = value;
2038
+ }
2039
+ return resolved;
2040
+ }
2041
+ function extractRequestBodySchema(operationSchema) {
2042
+ const requestBody = operationSchema.requestBody;
2043
+ if (!isRecord5(requestBody)) return void 0;
2044
+ const content = requestBody.content;
2045
+ if (!isRecord5(content)) return void 0;
2046
+ const jsonMediaType = content["application/json"];
2047
+ if (isRecord5(jsonMediaType) && isRecord5(jsonMediaType.schema)) {
2048
+ return jsonMediaType.schema;
2049
+ }
2050
+ for (const mediaType of Object.values(content)) {
2051
+ if (isRecord5(mediaType) && isRecord5(mediaType.schema)) {
2052
+ return mediaType.schema;
2053
+ }
2054
+ }
2055
+ return void 0;
2056
+ }
2057
+ function extractParameters(operationSchema) {
2058
+ const params = operationSchema.parameters;
2059
+ if (!Array.isArray(params)) return [];
2060
+ return params.filter((value) => isRecord5(value));
2061
+ }
2062
+ function extractInputSchema(operationSchema) {
2063
+ const requestBody = extractRequestBodySchema(operationSchema);
2064
+ const parameters = extractParameters(operationSchema);
2065
+ if (!requestBody && parameters.length === 0) return void 0;
2066
+ if (requestBody && parameters.length === 0) return requestBody;
2067
+ return {
2068
+ ...requestBody ? { requestBody } : {},
2069
+ ...parameters.length > 0 ? { parameters } : {}
2070
+ };
2071
+ }
2072
+ function parseOperationPrice(operation) {
2073
+ const paymentInfo = operation["x-payment-info"];
2074
+ if (!isRecord5(paymentInfo)) return void 0;
2075
+ const fixed = toFiniteNumber(paymentInfo.price);
2076
+ if (fixed != null) return `$${String(fixed)}`;
2077
+ const min = toFiniteNumber(paymentInfo.minPrice);
2078
+ const max = toFiniteNumber(paymentInfo.maxPrice);
2079
+ if (min != null && max != null) return `$${String(min)}-$${String(max)}`;
2080
+ return void 0;
2081
+ }
2082
+ function parseOperationProtocols(operation) {
2083
+ const paymentInfo = operation["x-payment-info"];
2084
+ if (!isRecord5(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
2085
+ const protocols = paymentInfo.protocols.filter(
2086
+ (protocol) => typeof protocol === "string" && protocol.length > 0
2087
+ );
2088
+ return protocols.length > 0 ? protocols : void 0;
2089
+ }
2090
+ function parseOperationAuthMode(operation) {
2091
+ const authExtension = operation["x-agentcash-auth"];
2092
+ if (isRecord5(authExtension)) {
2093
+ const mode = authExtension.mode;
2094
+ if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
2095
+ return mode;
2096
+ }
2097
+ }
2098
+ if (isRecord5(operation["x-payment-info"])) return "paid";
2099
+ return void 0;
2100
+ }
2101
+ function createResourceMap(result) {
2102
+ const map = /* @__PURE__ */ new Map();
2103
+ for (const resource of result.resources) {
2104
+ map.set(resource.resourceKey, resource);
2105
+ }
2106
+ return map;
2107
+ }
2108
+ function toMcpEndpointSummary(resource) {
2109
+ const method = parseMethod(resource.method);
2110
+ if (!method) return void 0;
2111
+ const price = formatPriceHintFromResource(resource);
2112
+ const authMode = deriveMcpAuthMode(resource);
2113
+ return {
2114
+ path: resource.path,
2115
+ method,
2116
+ summary: resource.summary ?? `${method} ${resource.path}`,
2117
+ ...price ? { price } : {},
2118
+ ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
2119
+ ...authMode ? { authMode } : {}
2120
+ };
2121
+ }
2122
+ async function discoverForMcp(options) {
2123
+ const result = await runDiscovery({
2124
+ detailed: true,
2125
+ options: {
2126
+ target: options.target,
2127
+ compatMode: options.compatMode ?? "off",
2128
+ rawView: "full",
2129
+ fetcher: options.fetcher,
2130
+ headers: options.headers,
2131
+ signal: options.signal
2132
+ }
2133
+ });
2134
+ if (result.resources.length === 0) {
2135
+ const failure = inferFailureCause(result.warnings);
2136
+ return {
2137
+ found: false,
2138
+ origin: result.origin,
2139
+ cause: failure.cause,
2140
+ ...failure.message ? { message: failure.message } : {},
2141
+ warnings: result.warnings
2142
+ };
2143
+ }
2144
+ const source = result.selectedStage ?? "openapi";
2145
+ const endpoints = result.resources.map(toMcpEndpointSummary).filter((endpoint) => endpoint != null).sort((a, b) => {
2146
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
2147
+ return a.method.localeCompare(b.method);
2148
+ });
2149
+ const guidance = await resolveGuidance({
2150
+ result,
2151
+ includeGuidance: options.includeGuidance,
2152
+ guidanceAutoIncludeMaxTokens: options.guidanceAutoIncludeMaxTokens,
2153
+ fetcher: options.fetcher,
2154
+ headers: options.headers,
2155
+ signal: options.signal
2156
+ });
2157
+ return {
2158
+ found: true,
2159
+ origin: result.origin,
2160
+ source,
2161
+ ...getOpenApiInfo(result.rawSources?.openapi) ? { info: getOpenApiInfo(result.rawSources?.openapi) } : {},
2162
+ ...guidance,
2163
+ endpoints,
2164
+ warnings: result.warnings
2165
+ };
2166
+ }
2167
+ async function inspectEndpointForMcp(options) {
2168
+ const endpoint = new URL(options.endpointUrl);
2169
+ const origin = normalizeOrigin(endpoint.origin);
2170
+ const path = normalizePath(endpoint.pathname || "/");
2171
+ const result = await runDiscovery({
2172
+ detailed: true,
2173
+ options: {
2174
+ target: options.target,
2175
+ compatMode: options.compatMode ?? "off",
2176
+ rawView: "full",
2177
+ fetcher: options.fetcher,
2178
+ headers: options.headers,
2179
+ signal: options.signal
2180
+ }
2181
+ });
2182
+ const document = result.rawSources?.openapi;
2183
+ const paths = extractPathsDocument(document);
2184
+ const resourceMap = createResourceMap(result);
2185
+ const advisories = {};
2186
+ const specMethods = [];
2187
+ if (paths) {
2188
+ const matched = findMatchingOpenApiPath(paths, path);
2189
+ if (matched) {
2190
+ for (const candidate of OPENAPI_METHODS) {
2191
+ const operation = matched.pathItem[candidate.toLowerCase()];
2192
+ if (!isRecord5(operation) || !isRecord5(document)) continue;
2193
+ specMethods.push(candidate);
2194
+ const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
2195
+ const resource = resourceMap.get(toResourceKey(origin, candidate, matched.matchedPath));
2196
+ const formattedPrice = resource ? formatPriceHintFromResource(resource) : void 0;
2197
+ const operationSummary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
2198
+ const operationPrice = parseOperationPrice(resolvedOperation);
2199
+ const operationProtocols = parseOperationProtocols(resolvedOperation);
2200
+ const operationAuthMode = parseOperationAuthMode(resolvedOperation);
2201
+ const inputSchema = extractInputSchema(resolvedOperation);
2202
+ advisories[candidate] = {
2203
+ method: candidate,
2204
+ ...resource?.summary || operationSummary ? {
2205
+ summary: resource?.summary ?? operationSummary
2206
+ } : {},
2207
+ ...formattedPrice || operationPrice ? {
2208
+ estimatedPrice: formattedPrice ?? operationPrice
2209
+ } : {},
2210
+ ...resource?.protocolHints?.length || operationProtocols ? {
2211
+ protocols: resource?.protocolHints ?? operationProtocols
2212
+ } : {},
2213
+ ...deriveMcpAuthMode(resource) || operationAuthMode ? {
2214
+ authMode: deriveMcpAuthMode(resource) ?? operationAuthMode
2215
+ } : {},
2216
+ ...inputSchema ? { inputSchema } : {},
2217
+ operationSchema: resolvedOperation
2218
+ };
2219
+ }
2220
+ }
2221
+ }
2222
+ if (specMethods.length === 0) {
2223
+ for (const method of OPENAPI_METHODS) {
2224
+ const resource = resourceMap.get(toResourceKey(origin, method, path));
2225
+ if (!resource) continue;
2226
+ specMethods.push(method);
2227
+ const formattedPrice = formatPriceHintFromResource(resource);
2228
+ advisories[method] = {
2229
+ method,
2230
+ ...resource.summary ? { summary: resource.summary } : {},
2231
+ ...formattedPrice ? { estimatedPrice: formattedPrice } : {},
2232
+ ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
2233
+ ...deriveMcpAuthMode(resource) ? { authMode: deriveMcpAuthMode(resource) } : {}
2234
+ };
2235
+ }
2236
+ }
2237
+ if (specMethods.length === 0) {
2238
+ const failure = inferFailureCause(result.warnings);
2239
+ return {
2240
+ found: false,
2241
+ origin,
2242
+ path,
2243
+ cause: failure.cause,
2244
+ ...failure.message ? { message: failure.message } : {},
2245
+ warnings: result.warnings
2246
+ };
2247
+ }
2248
+ return {
2249
+ found: true,
2250
+ origin,
2251
+ path,
2252
+ specMethods,
2253
+ advisories,
2254
+ warnings: result.warnings
2255
+ };
2256
+ }
2257
+
1403
2258
  // src/harness.ts
1404
2259
  var INTENT_TRIGGERS = ["x402", "mpp", "pay for", "micropayment", "agentic commerce"];
1405
2260
  var CLIENT_PROFILES = {
@@ -1652,12 +2507,17 @@ export {
1652
2507
  LEGACY_SUNSET_DATE,
1653
2508
  STRICT_ESCALATION_CODES,
1654
2509
  UPGRADE_WARNING_CODES,
2510
+ VALIDATION_CODES,
1655
2511
  auditContextHarness,
1656
2512
  buildContextHarnessReport,
1657
2513
  computeUpgradeSignal,
1658
2514
  defaultHarnessProbeCandidates,
1659
2515
  discover,
1660
2516
  discoverDetailed,
2517
+ discoverForMcp,
2518
+ evaluateMetadataCompleteness,
1661
2519
  getHarnessClientProfile,
1662
- listHarnessClientProfiles
2520
+ inspectEndpointForMcp,
2521
+ listHarnessClientProfiles,
2522
+ validatePaymentRequiredDetailed
1663
2523
  };