@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.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.
|
|
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,471 @@ 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
|
+
|
|
1403
1868
|
// src/adapters/mcp.ts
|
|
1404
1869
|
var DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS = 1e3;
|
|
1405
1870
|
var OPENAPI_METHODS = [
|
|
@@ -1412,7 +1877,7 @@ var OPENAPI_METHODS = [
|
|
|
1412
1877
|
"OPTIONS",
|
|
1413
1878
|
"TRACE"
|
|
1414
1879
|
];
|
|
1415
|
-
function
|
|
1880
|
+
function isRecord5(value) {
|
|
1416
1881
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1417
1882
|
}
|
|
1418
1883
|
function toFiniteNumber(value) {
|
|
@@ -1472,7 +1937,7 @@ function inferFailureCause(warnings) {
|
|
|
1472
1937
|
return { cause: "not_found" };
|
|
1473
1938
|
}
|
|
1474
1939
|
function getOpenApiInfo(document) {
|
|
1475
|
-
if (!
|
|
1940
|
+
if (!isRecord5(document) || !isRecord5(document.info)) return void 0;
|
|
1476
1941
|
if (typeof document.info.title !== "string") return void 0;
|
|
1477
1942
|
return {
|
|
1478
1943
|
title: document.info.title,
|
|
@@ -1515,15 +1980,15 @@ async function resolveGuidance(options) {
|
|
|
1515
1980
|
};
|
|
1516
1981
|
}
|
|
1517
1982
|
function extractPathsDocument(document) {
|
|
1518
|
-
if (!
|
|
1519
|
-
if (!
|
|
1983
|
+
if (!isRecord5(document)) return void 0;
|
|
1984
|
+
if (!isRecord5(document.paths)) return void 0;
|
|
1520
1985
|
return document.paths;
|
|
1521
1986
|
}
|
|
1522
1987
|
function findMatchingOpenApiPath(paths, targetPath) {
|
|
1523
1988
|
const exact = paths[targetPath];
|
|
1524
|
-
if (
|
|
1989
|
+
if (isRecord5(exact)) return { matchedPath: targetPath, pathItem: exact };
|
|
1525
1990
|
for (const [specPath, entry] of Object.entries(paths)) {
|
|
1526
|
-
if (!
|
|
1991
|
+
if (!isRecord5(entry)) continue;
|
|
1527
1992
|
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
1528
1993
|
const regex = new RegExp(`^${pattern}$`);
|
|
1529
1994
|
if (regex.test(targetPath)) {
|
|
@@ -1539,11 +2004,11 @@ function resolveRef(document, ref, seen) {
|
|
|
1539
2004
|
const parts = ref.slice(2).split("/");
|
|
1540
2005
|
let current = document;
|
|
1541
2006
|
for (const part of parts) {
|
|
1542
|
-
if (!
|
|
2007
|
+
if (!isRecord5(current)) return void 0;
|
|
1543
2008
|
current = current[part];
|
|
1544
2009
|
if (current === void 0) return void 0;
|
|
1545
2010
|
}
|
|
1546
|
-
if (
|
|
2011
|
+
if (isRecord5(current)) return resolveRefs(document, current, seen);
|
|
1547
2012
|
return current;
|
|
1548
2013
|
}
|
|
1549
2014
|
function resolveRefs(document, obj, seen, depth = 0) {
|
|
@@ -1552,20 +2017,20 @@ function resolveRefs(document, obj, seen, depth = 0) {
|
|
|
1552
2017
|
for (const [key, value] of Object.entries(obj)) {
|
|
1553
2018
|
if (key === "$ref" && typeof value === "string") {
|
|
1554
2019
|
const deref = resolveRef(document, value, seen);
|
|
1555
|
-
if (
|
|
2020
|
+
if (isRecord5(deref)) {
|
|
1556
2021
|
Object.assign(resolved, deref);
|
|
1557
2022
|
} else {
|
|
1558
2023
|
resolved[key] = value;
|
|
1559
2024
|
}
|
|
1560
2025
|
continue;
|
|
1561
2026
|
}
|
|
1562
|
-
if (
|
|
2027
|
+
if (isRecord5(value)) {
|
|
1563
2028
|
resolved[key] = resolveRefs(document, value, seen, depth + 1);
|
|
1564
2029
|
continue;
|
|
1565
2030
|
}
|
|
1566
2031
|
if (Array.isArray(value)) {
|
|
1567
2032
|
resolved[key] = value.map(
|
|
1568
|
-
(item) =>
|
|
2033
|
+
(item) => isRecord5(item) ? resolveRefs(document, item, seen, depth + 1) : item
|
|
1569
2034
|
);
|
|
1570
2035
|
continue;
|
|
1571
2036
|
}
|
|
@@ -1575,15 +2040,15 @@ function resolveRefs(document, obj, seen, depth = 0) {
|
|
|
1575
2040
|
}
|
|
1576
2041
|
function extractRequestBodySchema(operationSchema) {
|
|
1577
2042
|
const requestBody = operationSchema.requestBody;
|
|
1578
|
-
if (!
|
|
2043
|
+
if (!isRecord5(requestBody)) return void 0;
|
|
1579
2044
|
const content = requestBody.content;
|
|
1580
|
-
if (!
|
|
2045
|
+
if (!isRecord5(content)) return void 0;
|
|
1581
2046
|
const jsonMediaType = content["application/json"];
|
|
1582
|
-
if (
|
|
2047
|
+
if (isRecord5(jsonMediaType) && isRecord5(jsonMediaType.schema)) {
|
|
1583
2048
|
return jsonMediaType.schema;
|
|
1584
2049
|
}
|
|
1585
2050
|
for (const mediaType of Object.values(content)) {
|
|
1586
|
-
if (
|
|
2051
|
+
if (isRecord5(mediaType) && isRecord5(mediaType.schema)) {
|
|
1587
2052
|
return mediaType.schema;
|
|
1588
2053
|
}
|
|
1589
2054
|
}
|
|
@@ -1592,7 +2057,7 @@ function extractRequestBodySchema(operationSchema) {
|
|
|
1592
2057
|
function extractParameters(operationSchema) {
|
|
1593
2058
|
const params = operationSchema.parameters;
|
|
1594
2059
|
if (!Array.isArray(params)) return [];
|
|
1595
|
-
return params.filter((value) =>
|
|
2060
|
+
return params.filter((value) => isRecord5(value));
|
|
1596
2061
|
}
|
|
1597
2062
|
function extractInputSchema(operationSchema) {
|
|
1598
2063
|
const requestBody = extractRequestBodySchema(operationSchema);
|
|
@@ -1606,7 +2071,7 @@ function extractInputSchema(operationSchema) {
|
|
|
1606
2071
|
}
|
|
1607
2072
|
function parseOperationPrice(operation) {
|
|
1608
2073
|
const paymentInfo = operation["x-payment-info"];
|
|
1609
|
-
if (!
|
|
2074
|
+
if (!isRecord5(paymentInfo)) return void 0;
|
|
1610
2075
|
const fixed = toFiniteNumber(paymentInfo.price);
|
|
1611
2076
|
if (fixed != null) return `$${String(fixed)}`;
|
|
1612
2077
|
const min = toFiniteNumber(paymentInfo.minPrice);
|
|
@@ -1616,7 +2081,7 @@ function parseOperationPrice(operation) {
|
|
|
1616
2081
|
}
|
|
1617
2082
|
function parseOperationProtocols(operation) {
|
|
1618
2083
|
const paymentInfo = operation["x-payment-info"];
|
|
1619
|
-
if (!
|
|
2084
|
+
if (!isRecord5(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
|
|
1620
2085
|
const protocols = paymentInfo.protocols.filter(
|
|
1621
2086
|
(protocol) => typeof protocol === "string" && protocol.length > 0
|
|
1622
2087
|
);
|
|
@@ -1624,13 +2089,13 @@ function parseOperationProtocols(operation) {
|
|
|
1624
2089
|
}
|
|
1625
2090
|
function parseOperationAuthMode(operation) {
|
|
1626
2091
|
const authExtension = operation["x-agentcash-auth"];
|
|
1627
|
-
if (
|
|
2092
|
+
if (isRecord5(authExtension)) {
|
|
1628
2093
|
const mode = authExtension.mode;
|
|
1629
2094
|
if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
|
|
1630
2095
|
return mode;
|
|
1631
2096
|
}
|
|
1632
2097
|
}
|
|
1633
|
-
if (
|
|
2098
|
+
if (isRecord5(operation["x-payment-info"])) return "paid";
|
|
1634
2099
|
return void 0;
|
|
1635
2100
|
}
|
|
1636
2101
|
function createResourceMap(result) {
|
|
@@ -1724,7 +2189,7 @@ async function inspectEndpointForMcp(options) {
|
|
|
1724
2189
|
if (matched) {
|
|
1725
2190
|
for (const candidate of OPENAPI_METHODS) {
|
|
1726
2191
|
const operation = matched.pathItem[candidate.toLowerCase()];
|
|
1727
|
-
if (!
|
|
2192
|
+
if (!isRecord5(operation) || !isRecord5(document)) continue;
|
|
1728
2193
|
specMethods.push(candidate);
|
|
1729
2194
|
const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
|
|
1730
2195
|
const resource = resourceMap.get(toResourceKey(origin, candidate, matched.matchedPath));
|
|
@@ -2042,6 +2507,7 @@ export {
|
|
|
2042
2507
|
LEGACY_SUNSET_DATE,
|
|
2043
2508
|
STRICT_ESCALATION_CODES,
|
|
2044
2509
|
UPGRADE_WARNING_CODES,
|
|
2510
|
+
VALIDATION_CODES,
|
|
2045
2511
|
auditContextHarness,
|
|
2046
2512
|
buildContextHarnessReport,
|
|
2047
2513
|
computeUpgradeSignal,
|
|
@@ -2049,7 +2515,9 @@ export {
|
|
|
2049
2515
|
discover,
|
|
2050
2516
|
discoverDetailed,
|
|
2051
2517
|
discoverForMcp,
|
|
2518
|
+
evaluateMetadataCompleteness,
|
|
2052
2519
|
getHarnessClientProfile,
|
|
2053
2520
|
inspectEndpointForMcp,
|
|
2054
|
-
listHarnessClientProfiles
|
|
2521
|
+
listHarnessClientProfiles,
|
|
2522
|
+
validatePaymentRequiredDetailed
|
|
2055
2523
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/discovery",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Canonical OpenAPI-first discovery runtime for the agentcash ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"access": "public"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
+
"@x402/core": "^2.5.0",
|
|
67
68
|
"table": "^6.9.0",
|
|
68
69
|
"zod": "^4.1.13"
|
|
69
70
|
},
|