@agentcash/discovery 0.1.2 → 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/README.md +20 -0
- package/dist/cli.cjs +4 -1
- package/dist/cli.js +4 -1
- package/dist/index.cjs +495 -24
- package/dist/index.d.cts +85 -1
- package/dist/index.d.ts +85 -1
- package/dist/index.js +491 -23
- package/package.json +2 -1
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.
|
|
57
|
+
var isMmmEnabled = () => "0.1.3".includes("-mmm");
|
|
55
58
|
|
|
56
59
|
// src/core/constants.ts
|
|
57
60
|
var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
|
|
@@ -1439,6 +1442,471 @@ async function runDiscovery({
|
|
|
1439
1442
|
};
|
|
1440
1443
|
}
|
|
1441
1444
|
|
|
1445
|
+
// src/validation/codes.ts
|
|
1446
|
+
var VALIDATION_CODES = {
|
|
1447
|
+
COINBASE_SCHEMA_INVALID: "COINBASE_SCHEMA_INVALID",
|
|
1448
|
+
X402_NOT_OBJECT: "X402_NOT_OBJECT",
|
|
1449
|
+
X402_VERSION_MISSING: "X402_VERSION_MISSING",
|
|
1450
|
+
X402_VERSION_UNSUPPORTED: "X402_VERSION_UNSUPPORTED",
|
|
1451
|
+
X402_ACCEPTS_MISSING: "X402_ACCEPTS_MISSING",
|
|
1452
|
+
X402_ACCEPTS_INVALID: "X402_ACCEPTS_INVALID",
|
|
1453
|
+
X402_ACCEPTS_EMPTY: "X402_ACCEPTS_EMPTY",
|
|
1454
|
+
X402_ACCEPT_ENTRY_INVALID: "X402_ACCEPT_ENTRY_INVALID",
|
|
1455
|
+
NETWORK_CAIP2_INVALID: "NETWORK_CAIP2_INVALID",
|
|
1456
|
+
NETWORK_EIP155_REFERENCE_INVALID: "NETWORK_EIP155_REFERENCE_INVALID",
|
|
1457
|
+
NETWORK_SOLANA_ALIAS_INVALID: "NETWORK_SOLANA_ALIAS_INVALID",
|
|
1458
|
+
NETWORK_SOLANA_ALIAS_COMPAT: "NETWORK_SOLANA_ALIAS_COMPAT",
|
|
1459
|
+
NETWORK_REFERENCE_UNKNOWN: "NETWORK_REFERENCE_UNKNOWN",
|
|
1460
|
+
SCHEMA_INPUT_MISSING: "SCHEMA_INPUT_MISSING",
|
|
1461
|
+
SCHEMA_OUTPUT_MISSING: "SCHEMA_OUTPUT_MISSING",
|
|
1462
|
+
METADATA_TITLE_MISSING: "METADATA_TITLE_MISSING",
|
|
1463
|
+
METADATA_DESCRIPTION_MISSING: "METADATA_DESCRIPTION_MISSING",
|
|
1464
|
+
METADATA_FAVICON_MISSING: "METADATA_FAVICON_MISSING",
|
|
1465
|
+
METADATA_OG_IMAGE_MISSING: "METADATA_OG_IMAGE_MISSING"
|
|
1466
|
+
};
|
|
1467
|
+
|
|
1468
|
+
// src/validation/metadata.ts
|
|
1469
|
+
function hasText(value) {
|
|
1470
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1471
|
+
}
|
|
1472
|
+
function hasOgImage(metadata) {
|
|
1473
|
+
const images = metadata.ogImages;
|
|
1474
|
+
if (!Array.isArray(images) || images.length === 0) return false;
|
|
1475
|
+
return images.some((entry) => {
|
|
1476
|
+
if (typeof entry === "string") {
|
|
1477
|
+
return entry.trim().length > 0;
|
|
1478
|
+
}
|
|
1479
|
+
if (!entry || typeof entry !== "object") return false;
|
|
1480
|
+
return hasText(entry.url);
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
function evaluateMetadataCompleteness(metadata) {
|
|
1484
|
+
const issues = [];
|
|
1485
|
+
if (!hasText(metadata.title)) {
|
|
1486
|
+
issues.push({
|
|
1487
|
+
code: VALIDATION_CODES.METADATA_TITLE_MISSING,
|
|
1488
|
+
severity: "warn",
|
|
1489
|
+
message: "Metadata title is missing",
|
|
1490
|
+
hint: "Provide a page title to improve discovery quality and UX.",
|
|
1491
|
+
path: "metadata.title",
|
|
1492
|
+
stage: "metadata"
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
if (!hasText(metadata.description)) {
|
|
1496
|
+
issues.push({
|
|
1497
|
+
code: VALIDATION_CODES.METADATA_DESCRIPTION_MISSING,
|
|
1498
|
+
severity: "warn",
|
|
1499
|
+
message: "Metadata description is missing",
|
|
1500
|
+
hint: "Provide a concise description for integrators and search previews.",
|
|
1501
|
+
path: "metadata.description",
|
|
1502
|
+
stage: "metadata"
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
if (!hasText(metadata.favicon)) {
|
|
1506
|
+
issues.push({
|
|
1507
|
+
code: VALIDATION_CODES.METADATA_FAVICON_MISSING,
|
|
1508
|
+
severity: "warn",
|
|
1509
|
+
message: "Metadata favicon is missing",
|
|
1510
|
+
hint: "Expose a favicon URL for stronger brand attribution.",
|
|
1511
|
+
path: "metadata.favicon",
|
|
1512
|
+
stage: "metadata"
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
if (!hasOgImage(metadata)) {
|
|
1516
|
+
issues.push({
|
|
1517
|
+
code: VALIDATION_CODES.METADATA_OG_IMAGE_MISSING,
|
|
1518
|
+
severity: "warn",
|
|
1519
|
+
message: "Metadata OG image is missing",
|
|
1520
|
+
hint: "Provide an Open Graph image to improve sharing previews.",
|
|
1521
|
+
path: "metadata.ogImages",
|
|
1522
|
+
stage: "metadata"
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
return issues;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// src/validation/payment-required.ts
|
|
1529
|
+
var import_schemas = require("@x402/core/schemas");
|
|
1530
|
+
var SOLANA_CANONICAL_BY_REF = {
|
|
1531
|
+
"5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana",
|
|
1532
|
+
EtWTRABZaYq6iMfeYKouRu166VU2xqa1: "solana-devnet",
|
|
1533
|
+
"4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "solana-testnet"
|
|
1534
|
+
};
|
|
1535
|
+
var SOLANA_ALIAS_BY_REF = {
|
|
1536
|
+
mainnet: "solana",
|
|
1537
|
+
devnet: "solana-devnet",
|
|
1538
|
+
testnet: "solana-testnet"
|
|
1539
|
+
};
|
|
1540
|
+
function isRecord4(value) {
|
|
1541
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1542
|
+
}
|
|
1543
|
+
function asNonEmptyString(value) {
|
|
1544
|
+
if (typeof value !== "string") return void 0;
|
|
1545
|
+
const trimmed = value.trim();
|
|
1546
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1547
|
+
}
|
|
1548
|
+
function asPositiveNumber(value) {
|
|
1549
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1550
|
+
}
|
|
1551
|
+
function pushIssue(issues, issue) {
|
|
1552
|
+
issues.push({
|
|
1553
|
+
stage: issue.stage ?? "payment_required",
|
|
1554
|
+
...issue
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
function summarizeIssues(issues) {
|
|
1558
|
+
const summary = {
|
|
1559
|
+
errorCount: 0,
|
|
1560
|
+
warnCount: 0,
|
|
1561
|
+
infoCount: 0,
|
|
1562
|
+
byCode: {}
|
|
1563
|
+
};
|
|
1564
|
+
for (const issue of issues) {
|
|
1565
|
+
if (issue.severity === "error") summary.errorCount += 1;
|
|
1566
|
+
else if (issue.severity === "warn") summary.warnCount += 1;
|
|
1567
|
+
else summary.infoCount += 1;
|
|
1568
|
+
summary.byCode[issue.code] = (summary.byCode[issue.code] ?? 0) + 1;
|
|
1569
|
+
}
|
|
1570
|
+
return summary;
|
|
1571
|
+
}
|
|
1572
|
+
function outputSchemaMissingSeverity(compatMode) {
|
|
1573
|
+
return compatMode === "strict" ? "error" : "warn";
|
|
1574
|
+
}
|
|
1575
|
+
function zodPathToString(path) {
|
|
1576
|
+
if (path.length === 0) return "$";
|
|
1577
|
+
let out = "";
|
|
1578
|
+
for (const segment of path) {
|
|
1579
|
+
if (typeof segment === "number") out += `[${segment}]`;
|
|
1580
|
+
else out += out.length === 0 ? segment : `.${segment}`;
|
|
1581
|
+
}
|
|
1582
|
+
return out;
|
|
1583
|
+
}
|
|
1584
|
+
function parseWithCoinbaseStructuralGate(payload, issues) {
|
|
1585
|
+
const baseParsed = (0, import_schemas.parsePaymentRequired)(payload);
|
|
1586
|
+
if (baseParsed.success) {
|
|
1587
|
+
return baseParsed.data;
|
|
1588
|
+
}
|
|
1589
|
+
for (const issue of baseParsed.error.issues) {
|
|
1590
|
+
pushIssue(issues, {
|
|
1591
|
+
code: VALIDATION_CODES.COINBASE_SCHEMA_INVALID,
|
|
1592
|
+
severity: "error",
|
|
1593
|
+
message: `Coinbase schema validation failed: ${issue.message}`,
|
|
1594
|
+
path: zodPathToString(issue.path),
|
|
1595
|
+
actual: issue.code
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
return void 0;
|
|
1599
|
+
}
|
|
1600
|
+
function validateV2Network(networkRaw, index, issues) {
|
|
1601
|
+
if (!/^[^:\s]+:[^:\s]+$/.test(networkRaw)) {
|
|
1602
|
+
pushIssue(issues, {
|
|
1603
|
+
code: VALIDATION_CODES.NETWORK_CAIP2_INVALID,
|
|
1604
|
+
severity: "error",
|
|
1605
|
+
message: `Accept at index ${index} has invalid CAIP-2 network format`,
|
|
1606
|
+
hint: "Expected '<namespace>:<reference>', e.g. 'eip155:8453' or 'solana:5eykt4...'.",
|
|
1607
|
+
path: `accepts[${index}].network`,
|
|
1608
|
+
expected: "<namespace>:<reference>",
|
|
1609
|
+
actual: networkRaw
|
|
1610
|
+
});
|
|
1611
|
+
return void 0;
|
|
1612
|
+
}
|
|
1613
|
+
const [namespace, reference] = networkRaw.split(":");
|
|
1614
|
+
if (namespace === "eip155") {
|
|
1615
|
+
if (!/^\d+$/.test(reference)) {
|
|
1616
|
+
pushIssue(issues, {
|
|
1617
|
+
code: VALIDATION_CODES.NETWORK_EIP155_REFERENCE_INVALID,
|
|
1618
|
+
severity: "error",
|
|
1619
|
+
message: `Accept at index ${index} has invalid eip155 reference`,
|
|
1620
|
+
hint: "Use a numeric chain id, e.g. 'eip155:8453'.",
|
|
1621
|
+
path: `accepts[${index}].network`,
|
|
1622
|
+
expected: "eip155:<numeric-chain-id>",
|
|
1623
|
+
actual: networkRaw
|
|
1624
|
+
});
|
|
1625
|
+
return void 0;
|
|
1626
|
+
}
|
|
1627
|
+
return networkRaw;
|
|
1628
|
+
}
|
|
1629
|
+
if (namespace === "solana") {
|
|
1630
|
+
const canonical = SOLANA_CANONICAL_BY_REF[reference];
|
|
1631
|
+
if (canonical) return canonical;
|
|
1632
|
+
const alias = SOLANA_ALIAS_BY_REF[reference];
|
|
1633
|
+
if (alias) {
|
|
1634
|
+
pushIssue(issues, {
|
|
1635
|
+
code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_COMPAT,
|
|
1636
|
+
severity: "warn",
|
|
1637
|
+
message: `Accept at index ${index} uses compatibility Solana reference '${reference}'`,
|
|
1638
|
+
hint: "Use canonical Solana CAIP references for deterministic behavior.",
|
|
1639
|
+
path: `accepts[${index}].network`,
|
|
1640
|
+
actual: networkRaw
|
|
1641
|
+
});
|
|
1642
|
+
return alias;
|
|
1643
|
+
}
|
|
1644
|
+
pushIssue(issues, {
|
|
1645
|
+
code: VALIDATION_CODES.NETWORK_REFERENCE_UNKNOWN,
|
|
1646
|
+
severity: "error",
|
|
1647
|
+
message: `Accept at index ${index} uses unknown Solana reference '${reference}'`,
|
|
1648
|
+
hint: "Use canonical references for mainnet/devnet/testnet.",
|
|
1649
|
+
path: `accepts[${index}].network`,
|
|
1650
|
+
actual: networkRaw
|
|
1651
|
+
});
|
|
1652
|
+
return void 0;
|
|
1653
|
+
}
|
|
1654
|
+
return networkRaw;
|
|
1655
|
+
}
|
|
1656
|
+
function validateV1Network(networkRaw, index, issues) {
|
|
1657
|
+
if (networkRaw === "solana-mainnet-beta") {
|
|
1658
|
+
pushIssue(issues, {
|
|
1659
|
+
code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
|
|
1660
|
+
severity: "error",
|
|
1661
|
+
message: `Accept at index ${index} uses invalid Solana network alias`,
|
|
1662
|
+
hint: "Use 'solana' (v1) or canonical Solana CAIP reference (v2).",
|
|
1663
|
+
path: `accepts[${index}].network`,
|
|
1664
|
+
expected: "solana",
|
|
1665
|
+
actual: networkRaw
|
|
1666
|
+
});
|
|
1667
|
+
return void 0;
|
|
1668
|
+
}
|
|
1669
|
+
if (networkRaw.startsWith("solana-") && networkRaw !== "solana-devnet") {
|
|
1670
|
+
pushIssue(issues, {
|
|
1671
|
+
code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
|
|
1672
|
+
severity: "error",
|
|
1673
|
+
message: `Accept at index ${index} uses unsupported Solana network alias`,
|
|
1674
|
+
hint: "Use 'solana' or 'solana-devnet' for v1 payloads.",
|
|
1675
|
+
path: `accepts[${index}].network`,
|
|
1676
|
+
actual: networkRaw
|
|
1677
|
+
});
|
|
1678
|
+
return void 0;
|
|
1679
|
+
}
|
|
1680
|
+
if (networkRaw.includes(":")) {
|
|
1681
|
+
return validateV2Network(networkRaw, index, issues);
|
|
1682
|
+
}
|
|
1683
|
+
return networkRaw;
|
|
1684
|
+
}
|
|
1685
|
+
function validateAccept(acceptRaw, index, version, issues) {
|
|
1686
|
+
if (!isRecord4(acceptRaw)) {
|
|
1687
|
+
pushIssue(issues, {
|
|
1688
|
+
code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
|
|
1689
|
+
severity: "error",
|
|
1690
|
+
message: `Accept at index ${index} must be an object`,
|
|
1691
|
+
path: `accepts[${index}]`
|
|
1692
|
+
});
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
const scheme = asNonEmptyString(acceptRaw.scheme);
|
|
1696
|
+
const networkRaw = asNonEmptyString(acceptRaw.network);
|
|
1697
|
+
const payTo = asNonEmptyString(acceptRaw.payTo);
|
|
1698
|
+
const asset = asNonEmptyString(acceptRaw.asset);
|
|
1699
|
+
const maxTimeoutSeconds = asPositiveNumber(acceptRaw.maxTimeoutSeconds);
|
|
1700
|
+
const amount = version === 2 ? asNonEmptyString(acceptRaw.amount) : asNonEmptyString(acceptRaw.maxAmountRequired);
|
|
1701
|
+
if (!scheme) {
|
|
1702
|
+
pushIssue(issues, {
|
|
1703
|
+
code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
|
|
1704
|
+
severity: "error",
|
|
1705
|
+
message: `Accept at index ${index} is missing scheme`,
|
|
1706
|
+
path: `accepts[${index}].scheme`
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
if (!networkRaw) {
|
|
1710
|
+
pushIssue(issues, {
|
|
1711
|
+
code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
|
|
1712
|
+
severity: "error",
|
|
1713
|
+
message: `Accept at index ${index} is missing network`,
|
|
1714
|
+
path: `accepts[${index}].network`
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
if (!amount) {
|
|
1718
|
+
pushIssue(issues, {
|
|
1719
|
+
code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
|
|
1720
|
+
severity: "error",
|
|
1721
|
+
message: `Accept at index ${index} is missing ${version === 2 ? "amount" : "maxAmountRequired"}`,
|
|
1722
|
+
path: `accepts[${index}].${version === 2 ? "amount" : "maxAmountRequired"}`
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
if (!payTo) {
|
|
1726
|
+
pushIssue(issues, {
|
|
1727
|
+
code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
|
|
1728
|
+
severity: "error",
|
|
1729
|
+
message: `Accept at index ${index} is missing payTo`,
|
|
1730
|
+
path: `accepts[${index}].payTo`
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
if (!asset) {
|
|
1734
|
+
pushIssue(issues, {
|
|
1735
|
+
code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
|
|
1736
|
+
severity: "error",
|
|
1737
|
+
message: `Accept at index ${index} is missing asset`,
|
|
1738
|
+
path: `accepts[${index}].asset`
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
if (!maxTimeoutSeconds) {
|
|
1742
|
+
pushIssue(issues, {
|
|
1743
|
+
code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
|
|
1744
|
+
severity: "error",
|
|
1745
|
+
message: `Accept at index ${index} is missing or has invalid maxTimeoutSeconds`,
|
|
1746
|
+
path: `accepts[${index}].maxTimeoutSeconds`
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
let normalizedNetwork;
|
|
1750
|
+
if (networkRaw) {
|
|
1751
|
+
normalizedNetwork = version === 2 ? validateV2Network(networkRaw, index, issues) : validateV1Network(networkRaw, index, issues);
|
|
1752
|
+
}
|
|
1753
|
+
return {
|
|
1754
|
+
index,
|
|
1755
|
+
network: normalizedNetwork ?? networkRaw ?? "unknown",
|
|
1756
|
+
networkRaw: networkRaw ?? "unknown",
|
|
1757
|
+
scheme,
|
|
1758
|
+
asset,
|
|
1759
|
+
payTo,
|
|
1760
|
+
amount,
|
|
1761
|
+
maxTimeoutSeconds
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
function getSchemaPresence(payload, accepts, version) {
|
|
1765
|
+
if (version === 1) {
|
|
1766
|
+
const first = accepts[0];
|
|
1767
|
+
if (!isRecord4(first)) {
|
|
1768
|
+
return { hasInputSchema: false, hasOutputSchema: false };
|
|
1769
|
+
}
|
|
1770
|
+
const outputSchema = isRecord4(first.outputSchema) ? first.outputSchema : void 0;
|
|
1771
|
+
return {
|
|
1772
|
+
hasInputSchema: outputSchema?.input !== void 0 && outputSchema.input !== null,
|
|
1773
|
+
hasOutputSchema: outputSchema?.output !== void 0 && outputSchema.output !== null
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
const extensions = isRecord4(payload.extensions) ? payload.extensions : void 0;
|
|
1777
|
+
const bazaar = extensions && isRecord4(extensions.bazaar) ? extensions.bazaar : void 0;
|
|
1778
|
+
const info = bazaar && isRecord4(bazaar.info) ? bazaar.info : void 0;
|
|
1779
|
+
return {
|
|
1780
|
+
hasInputSchema: info?.input !== void 0 && info.input !== null,
|
|
1781
|
+
hasOutputSchema: info?.output !== void 0 && info.output !== null
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
function validatePaymentRequiredDetailed(payload, options = {}) {
|
|
1785
|
+
const issues = [];
|
|
1786
|
+
const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
|
|
1787
|
+
const requireInputSchema = options.requireInputSchema ?? true;
|
|
1788
|
+
const requireOutputSchema = options.requireOutputSchema ?? true;
|
|
1789
|
+
if (!isRecord4(payload)) {
|
|
1790
|
+
pushIssue(issues, {
|
|
1791
|
+
code: VALIDATION_CODES.X402_NOT_OBJECT,
|
|
1792
|
+
severity: "error",
|
|
1793
|
+
message: "Payment required payload must be a JSON object",
|
|
1794
|
+
path: "$"
|
|
1795
|
+
});
|
|
1796
|
+
if (options.metadata) {
|
|
1797
|
+
issues.push(...evaluateMetadataCompleteness(options.metadata));
|
|
1798
|
+
}
|
|
1799
|
+
return {
|
|
1800
|
+
valid: false,
|
|
1801
|
+
issues,
|
|
1802
|
+
summary: summarizeIssues(issues)
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
const coinbaseParsed = parseWithCoinbaseStructuralGate(payload, issues);
|
|
1806
|
+
const versionRaw = payload.x402Version;
|
|
1807
|
+
let version;
|
|
1808
|
+
if (versionRaw === 1 || versionRaw === 2) {
|
|
1809
|
+
version = versionRaw;
|
|
1810
|
+
} else if (versionRaw === void 0) {
|
|
1811
|
+
pushIssue(issues, {
|
|
1812
|
+
code: VALIDATION_CODES.X402_VERSION_MISSING,
|
|
1813
|
+
severity: "error",
|
|
1814
|
+
message: "x402Version is required",
|
|
1815
|
+
path: "x402Version"
|
|
1816
|
+
});
|
|
1817
|
+
} else {
|
|
1818
|
+
pushIssue(issues, {
|
|
1819
|
+
code: VALIDATION_CODES.X402_VERSION_UNSUPPORTED,
|
|
1820
|
+
severity: "error",
|
|
1821
|
+
message: `Unsupported x402Version '${String(versionRaw)}'`,
|
|
1822
|
+
path: "x402Version",
|
|
1823
|
+
expected: "1 | 2",
|
|
1824
|
+
actual: String(versionRaw)
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
const acceptsRaw = payload.accepts;
|
|
1828
|
+
let accepts = [];
|
|
1829
|
+
if (acceptsRaw === void 0) {
|
|
1830
|
+
pushIssue(issues, {
|
|
1831
|
+
code: VALIDATION_CODES.X402_ACCEPTS_MISSING,
|
|
1832
|
+
severity: "error",
|
|
1833
|
+
message: "accepts is required",
|
|
1834
|
+
path: "accepts"
|
|
1835
|
+
});
|
|
1836
|
+
} else if (!Array.isArray(acceptsRaw)) {
|
|
1837
|
+
pushIssue(issues, {
|
|
1838
|
+
code: VALIDATION_CODES.X402_ACCEPTS_INVALID,
|
|
1839
|
+
severity: "error",
|
|
1840
|
+
message: "accepts must be an array",
|
|
1841
|
+
path: "accepts"
|
|
1842
|
+
});
|
|
1843
|
+
} else {
|
|
1844
|
+
accepts = acceptsRaw;
|
|
1845
|
+
if (accepts.length === 0) {
|
|
1846
|
+
pushIssue(issues, {
|
|
1847
|
+
code: VALIDATION_CODES.X402_ACCEPTS_EMPTY,
|
|
1848
|
+
severity: "error",
|
|
1849
|
+
message: "accepts must contain at least one payment requirement",
|
|
1850
|
+
path: "accepts"
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
const normalizedAccepts = [];
|
|
1855
|
+
if (version && Array.isArray(accepts)) {
|
|
1856
|
+
accepts.forEach((accept, index) => {
|
|
1857
|
+
const normalized = validateAccept(accept, index, version, issues);
|
|
1858
|
+
if (normalized) normalizedAccepts.push(normalized);
|
|
1859
|
+
});
|
|
1860
|
+
const schemaPresence = getSchemaPresence(payload, accepts, version);
|
|
1861
|
+
if (requireInputSchema && !schemaPresence.hasInputSchema) {
|
|
1862
|
+
pushIssue(issues, {
|
|
1863
|
+
code: VALIDATION_CODES.SCHEMA_INPUT_MISSING,
|
|
1864
|
+
severity: "error",
|
|
1865
|
+
message: "Input schema is missing",
|
|
1866
|
+
hint: "Include input schema details so clients can invoke the endpoint correctly.",
|
|
1867
|
+
path: version === 1 ? "accepts[0].outputSchema.input" : "extensions.bazaar.info.input"
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
if (requireOutputSchema && !schemaPresence.hasOutputSchema) {
|
|
1871
|
+
pushIssue(issues, {
|
|
1872
|
+
code: VALIDATION_CODES.SCHEMA_OUTPUT_MISSING,
|
|
1873
|
+
severity: outputSchemaMissingSeverity(compatMode),
|
|
1874
|
+
message: "Output schema is missing",
|
|
1875
|
+
hint: "Include output schema details so clients can validate responses.",
|
|
1876
|
+
path: version === 1 ? "accepts[0].outputSchema.output" : "extensions.bazaar.info.output"
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
if (options.metadata) {
|
|
1880
|
+
issues.push(...evaluateMetadataCompleteness(options.metadata));
|
|
1881
|
+
}
|
|
1882
|
+
const summary2 = summarizeIssues(issues);
|
|
1883
|
+
return {
|
|
1884
|
+
valid: summary2.errorCount === 0,
|
|
1885
|
+
version,
|
|
1886
|
+
parsed: coinbaseParsed ?? payload,
|
|
1887
|
+
normalized: {
|
|
1888
|
+
version,
|
|
1889
|
+
accepts: normalizedAccepts,
|
|
1890
|
+
hasInputSchema: schemaPresence.hasInputSchema,
|
|
1891
|
+
hasOutputSchema: schemaPresence.hasOutputSchema
|
|
1892
|
+
},
|
|
1893
|
+
issues,
|
|
1894
|
+
summary: summary2
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
if (options.metadata) {
|
|
1898
|
+
issues.push(...evaluateMetadataCompleteness(options.metadata));
|
|
1899
|
+
}
|
|
1900
|
+
const summary = summarizeIssues(issues);
|
|
1901
|
+
return {
|
|
1902
|
+
valid: summary.errorCount === 0,
|
|
1903
|
+
version,
|
|
1904
|
+
parsed: coinbaseParsed ?? payload,
|
|
1905
|
+
issues,
|
|
1906
|
+
summary
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1442
1910
|
// src/adapters/mcp.ts
|
|
1443
1911
|
var DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS = 1e3;
|
|
1444
1912
|
var OPENAPI_METHODS = [
|
|
@@ -1451,7 +1919,7 @@ var OPENAPI_METHODS = [
|
|
|
1451
1919
|
"OPTIONS",
|
|
1452
1920
|
"TRACE"
|
|
1453
1921
|
];
|
|
1454
|
-
function
|
|
1922
|
+
function isRecord5(value) {
|
|
1455
1923
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1456
1924
|
}
|
|
1457
1925
|
function toFiniteNumber(value) {
|
|
@@ -1511,7 +1979,7 @@ function inferFailureCause(warnings) {
|
|
|
1511
1979
|
return { cause: "not_found" };
|
|
1512
1980
|
}
|
|
1513
1981
|
function getOpenApiInfo(document) {
|
|
1514
|
-
if (!
|
|
1982
|
+
if (!isRecord5(document) || !isRecord5(document.info)) return void 0;
|
|
1515
1983
|
if (typeof document.info.title !== "string") return void 0;
|
|
1516
1984
|
return {
|
|
1517
1985
|
title: document.info.title,
|
|
@@ -1554,15 +2022,15 @@ async function resolveGuidance(options) {
|
|
|
1554
2022
|
};
|
|
1555
2023
|
}
|
|
1556
2024
|
function extractPathsDocument(document) {
|
|
1557
|
-
if (!
|
|
1558
|
-
if (!
|
|
2025
|
+
if (!isRecord5(document)) return void 0;
|
|
2026
|
+
if (!isRecord5(document.paths)) return void 0;
|
|
1559
2027
|
return document.paths;
|
|
1560
2028
|
}
|
|
1561
2029
|
function findMatchingOpenApiPath(paths, targetPath) {
|
|
1562
2030
|
const exact = paths[targetPath];
|
|
1563
|
-
if (
|
|
2031
|
+
if (isRecord5(exact)) return { matchedPath: targetPath, pathItem: exact };
|
|
1564
2032
|
for (const [specPath, entry] of Object.entries(paths)) {
|
|
1565
|
-
if (!
|
|
2033
|
+
if (!isRecord5(entry)) continue;
|
|
1566
2034
|
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
1567
2035
|
const regex = new RegExp(`^${pattern}$`);
|
|
1568
2036
|
if (regex.test(targetPath)) {
|
|
@@ -1578,11 +2046,11 @@ function resolveRef(document, ref, seen) {
|
|
|
1578
2046
|
const parts = ref.slice(2).split("/");
|
|
1579
2047
|
let current = document;
|
|
1580
2048
|
for (const part of parts) {
|
|
1581
|
-
if (!
|
|
2049
|
+
if (!isRecord5(current)) return void 0;
|
|
1582
2050
|
current = current[part];
|
|
1583
2051
|
if (current === void 0) return void 0;
|
|
1584
2052
|
}
|
|
1585
|
-
if (
|
|
2053
|
+
if (isRecord5(current)) return resolveRefs(document, current, seen);
|
|
1586
2054
|
return current;
|
|
1587
2055
|
}
|
|
1588
2056
|
function resolveRefs(document, obj, seen, depth = 0) {
|
|
@@ -1591,20 +2059,20 @@ function resolveRefs(document, obj, seen, depth = 0) {
|
|
|
1591
2059
|
for (const [key, value] of Object.entries(obj)) {
|
|
1592
2060
|
if (key === "$ref" && typeof value === "string") {
|
|
1593
2061
|
const deref = resolveRef(document, value, seen);
|
|
1594
|
-
if (
|
|
2062
|
+
if (isRecord5(deref)) {
|
|
1595
2063
|
Object.assign(resolved, deref);
|
|
1596
2064
|
} else {
|
|
1597
2065
|
resolved[key] = value;
|
|
1598
2066
|
}
|
|
1599
2067
|
continue;
|
|
1600
2068
|
}
|
|
1601
|
-
if (
|
|
2069
|
+
if (isRecord5(value)) {
|
|
1602
2070
|
resolved[key] = resolveRefs(document, value, seen, depth + 1);
|
|
1603
2071
|
continue;
|
|
1604
2072
|
}
|
|
1605
2073
|
if (Array.isArray(value)) {
|
|
1606
2074
|
resolved[key] = value.map(
|
|
1607
|
-
(item) =>
|
|
2075
|
+
(item) => isRecord5(item) ? resolveRefs(document, item, seen, depth + 1) : item
|
|
1608
2076
|
);
|
|
1609
2077
|
continue;
|
|
1610
2078
|
}
|
|
@@ -1614,15 +2082,15 @@ function resolveRefs(document, obj, seen, depth = 0) {
|
|
|
1614
2082
|
}
|
|
1615
2083
|
function extractRequestBodySchema(operationSchema) {
|
|
1616
2084
|
const requestBody = operationSchema.requestBody;
|
|
1617
|
-
if (!
|
|
2085
|
+
if (!isRecord5(requestBody)) return void 0;
|
|
1618
2086
|
const content = requestBody.content;
|
|
1619
|
-
if (!
|
|
2087
|
+
if (!isRecord5(content)) return void 0;
|
|
1620
2088
|
const jsonMediaType = content["application/json"];
|
|
1621
|
-
if (
|
|
2089
|
+
if (isRecord5(jsonMediaType) && isRecord5(jsonMediaType.schema)) {
|
|
1622
2090
|
return jsonMediaType.schema;
|
|
1623
2091
|
}
|
|
1624
2092
|
for (const mediaType of Object.values(content)) {
|
|
1625
|
-
if (
|
|
2093
|
+
if (isRecord5(mediaType) && isRecord5(mediaType.schema)) {
|
|
1626
2094
|
return mediaType.schema;
|
|
1627
2095
|
}
|
|
1628
2096
|
}
|
|
@@ -1631,7 +2099,7 @@ function extractRequestBodySchema(operationSchema) {
|
|
|
1631
2099
|
function extractParameters(operationSchema) {
|
|
1632
2100
|
const params = operationSchema.parameters;
|
|
1633
2101
|
if (!Array.isArray(params)) return [];
|
|
1634
|
-
return params.filter((value) =>
|
|
2102
|
+
return params.filter((value) => isRecord5(value));
|
|
1635
2103
|
}
|
|
1636
2104
|
function extractInputSchema(operationSchema) {
|
|
1637
2105
|
const requestBody = extractRequestBodySchema(operationSchema);
|
|
@@ -1645,7 +2113,7 @@ function extractInputSchema(operationSchema) {
|
|
|
1645
2113
|
}
|
|
1646
2114
|
function parseOperationPrice(operation) {
|
|
1647
2115
|
const paymentInfo = operation["x-payment-info"];
|
|
1648
|
-
if (!
|
|
2116
|
+
if (!isRecord5(paymentInfo)) return void 0;
|
|
1649
2117
|
const fixed = toFiniteNumber(paymentInfo.price);
|
|
1650
2118
|
if (fixed != null) return `$${String(fixed)}`;
|
|
1651
2119
|
const min = toFiniteNumber(paymentInfo.minPrice);
|
|
@@ -1655,7 +2123,7 @@ function parseOperationPrice(operation) {
|
|
|
1655
2123
|
}
|
|
1656
2124
|
function parseOperationProtocols(operation) {
|
|
1657
2125
|
const paymentInfo = operation["x-payment-info"];
|
|
1658
|
-
if (!
|
|
2126
|
+
if (!isRecord5(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
|
|
1659
2127
|
const protocols = paymentInfo.protocols.filter(
|
|
1660
2128
|
(protocol) => typeof protocol === "string" && protocol.length > 0
|
|
1661
2129
|
);
|
|
@@ -1663,13 +2131,13 @@ function parseOperationProtocols(operation) {
|
|
|
1663
2131
|
}
|
|
1664
2132
|
function parseOperationAuthMode(operation) {
|
|
1665
2133
|
const authExtension = operation["x-agentcash-auth"];
|
|
1666
|
-
if (
|
|
2134
|
+
if (isRecord5(authExtension)) {
|
|
1667
2135
|
const mode = authExtension.mode;
|
|
1668
2136
|
if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
|
|
1669
2137
|
return mode;
|
|
1670
2138
|
}
|
|
1671
2139
|
}
|
|
1672
|
-
if (
|
|
2140
|
+
if (isRecord5(operation["x-payment-info"])) return "paid";
|
|
1673
2141
|
return void 0;
|
|
1674
2142
|
}
|
|
1675
2143
|
function createResourceMap(result) {
|
|
@@ -1763,7 +2231,7 @@ async function inspectEndpointForMcp(options) {
|
|
|
1763
2231
|
if (matched) {
|
|
1764
2232
|
for (const candidate of OPENAPI_METHODS) {
|
|
1765
2233
|
const operation = matched.pathItem[candidate.toLowerCase()];
|
|
1766
|
-
if (!
|
|
2234
|
+
if (!isRecord5(operation) || !isRecord5(document)) continue;
|
|
1767
2235
|
specMethods.push(candidate);
|
|
1768
2236
|
const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
|
|
1769
2237
|
const resource = resourceMap.get(toResourceKey(origin, candidate, matched.matchedPath));
|
|
@@ -2082,6 +2550,7 @@ async function discoverDetailed(options) {
|
|
|
2082
2550
|
LEGACY_SUNSET_DATE,
|
|
2083
2551
|
STRICT_ESCALATION_CODES,
|
|
2084
2552
|
UPGRADE_WARNING_CODES,
|
|
2553
|
+
VALIDATION_CODES,
|
|
2085
2554
|
auditContextHarness,
|
|
2086
2555
|
buildContextHarnessReport,
|
|
2087
2556
|
computeUpgradeSignal,
|
|
@@ -2089,7 +2558,9 @@ async function discoverDetailed(options) {
|
|
|
2089
2558
|
discover,
|
|
2090
2559
|
discoverDetailed,
|
|
2091
2560
|
discoverForMcp,
|
|
2561
|
+
evaluateMetadataCompleteness,
|
|
2092
2562
|
getHarnessClientProfile,
|
|
2093
2563
|
inspectEndpointForMcp,
|
|
2094
|
-
listHarnessClientProfiles
|
|
2564
|
+
listHarnessClientProfiles,
|
|
2565
|
+
validatePaymentRequiredDetailed
|
|
2095
2566
|
});
|