@devlab.group/agent-commerce 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -126
- package/dist/ap2.d.ts +5 -5
- package/dist/ap2.js +19 -11
- package/dist/ap2.js.map +1 -1
- package/dist/{authorization-CPDcpJVa.d.ts → authorization-DA7mjy_O.d.ts} +4 -4
- package/dist/{chunk-ZDVJ7EVL.js → chunk-5GEWKNIH.js} +5 -4
- package/dist/chunk-5GEWKNIH.js.map +1 -0
- package/dist/chunk-6D2EZ5ME.js +27 -0
- package/dist/chunk-6D2EZ5ME.js.map +1 -0
- package/dist/chunk-NKNXVDHN.js +701 -0
- package/dist/chunk-NKNXVDHN.js.map +1 -0
- package/dist/{chunk-IUR3Q7DE.js → chunk-PBYN7EK2.js} +33 -31
- package/dist/chunk-PBYN7EK2.js.map +1 -0
- package/dist/{chunk-7NKWFGBY.js → chunk-US622BKF.js} +3 -3
- package/dist/{chunk-7NKWFGBY.js.map → chunk-US622BKF.js.map} +1 -1
- package/dist/cli/index.js +339 -107
- package/dist/cli/index.js.map +1 -1
- package/dist/constants-6mypC60h.d.ts +50 -0
- package/dist/index.d.ts +44 -13
- package/dist/index.js +358 -113
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +4 -1
- package/dist/mcp.js.map +1 -1
- package/dist/mpp.d.ts +50 -0
- package/dist/mpp.js +351 -0
- package/dist/mpp.js.map +1 -0
- package/dist/{protocol-adapter-CaMZj6Wm.d.ts → protocol-adapter-APRjs1E2.d.ts} +25 -11
- package/dist/{runtime-CKq3d_3K.d.ts → runtime-DC5XQ9Cc.d.ts} +24 -13
- package/dist/x402.d.ts +1 -1
- package/dist/x402.js +7 -678
- package/dist/x402.js.map +1 -1
- package/package.json +22 -4
- package/dist/chunk-IUR3Q7DE.js.map +0 -1
- package/dist/chunk-ZDVJ7EVL.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import picocolors from 'picocolors';
|
|
|
9
9
|
import { randomUUID } from 'crypto';
|
|
10
10
|
import { createRequire } from 'module';
|
|
11
11
|
import Database from 'better-sqlite3';
|
|
12
|
-
import { writeFile, stat,
|
|
12
|
+
import { writeFile, stat, rename, rm, open } from 'fs/promises';
|
|
13
13
|
import { validate } from '@scalar/openapi-parser';
|
|
14
14
|
import * as clack from '@clack/prompts';
|
|
15
15
|
|
|
@@ -32,10 +32,11 @@ var init_filename = __esm({
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
// src/core/domain/common.ts
|
|
35
|
-
var PROTOCOL_NAMES;
|
|
35
|
+
var PROTOCOL_NAMES, PAYMENT_METHOD_NAMES;
|
|
36
36
|
var init_common = __esm({
|
|
37
37
|
"src/core/domain/common.ts"() {
|
|
38
38
|
PROTOCOL_NAMES = ["http", "mcp", "a2a", "acp"];
|
|
39
|
+
PAYMENT_METHOD_NAMES = ["x402", "mpp"];
|
|
39
40
|
}
|
|
40
41
|
});
|
|
41
42
|
|
|
@@ -312,6 +313,34 @@ var init_execution = __esm({
|
|
|
312
313
|
}
|
|
313
314
|
});
|
|
314
315
|
|
|
316
|
+
// src/payments/mpp/constants.ts
|
|
317
|
+
function isMppNetwork(value) {
|
|
318
|
+
return MPP_NETWORKS.includes(value);
|
|
319
|
+
}
|
|
320
|
+
var MPP_SPEC_COMMIT, MPP_SPEC_DRAFTS, MPP_MIN_CHALLENGE_SECRET_LENGTH, MPPX_VERSION, MPP_PROFILE, MPP_NETWORKS, MPP_DEFAULT_NETWORK;
|
|
321
|
+
var init_constants2 = __esm({
|
|
322
|
+
"src/payments/mpp/constants.ts"() {
|
|
323
|
+
MPP_SPEC_COMMIT = "806fdb8b8c92cda5c84b8660558921b6bbeef7e0";
|
|
324
|
+
MPP_SPEC_DRAFTS = {
|
|
325
|
+
core: "draft-httpauth-payment-00",
|
|
326
|
+
intent: "draft-payment-intent-charge-00",
|
|
327
|
+
method: "draft-evm-charge-00"
|
|
328
|
+
};
|
|
329
|
+
MPP_MIN_CHALLENGE_SECRET_LENGTH = 32;
|
|
330
|
+
MPPX_VERSION = "0.10.1";
|
|
331
|
+
MPP_PROFILE = {
|
|
332
|
+
intent: "charge",
|
|
333
|
+
method: "evm",
|
|
334
|
+
credentialType: "authorization",
|
|
335
|
+
assetSymbol: "USDC",
|
|
336
|
+
assetDecimals: 6
|
|
337
|
+
};
|
|
338
|
+
MPP_NETWORKS = ["eip155:84532", "eip155:8453"];
|
|
339
|
+
MPP_DEFAULT_NETWORK = "eip155:84532";
|
|
340
|
+
`mpp/${MPP_SPEC_DRAFTS.core} intent=${MPP_PROFILE.intent} method=${MPP_PROFILE.method}`;
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
315
344
|
// src/payments/x402/networks.ts
|
|
316
345
|
function findNetworkProfile(id) {
|
|
317
346
|
return BY_ID.get(id);
|
|
@@ -372,7 +401,7 @@ var init_networks = __esm({
|
|
|
372
401
|
|
|
373
402
|
// src/protocols/acp/constants.ts
|
|
374
403
|
var ACP_SPEC_VERSION, ACP_API_VERSION, ACP_WELL_KNOWN_PATH, ACP_CHECKOUT_OPERATIONS, ACP_OPERATION_INPUT_KEYS;
|
|
375
|
-
var
|
|
404
|
+
var init_constants3 = __esm({
|
|
376
405
|
"src/protocols/acp/constants.ts"() {
|
|
377
406
|
ACP_SPEC_VERSION = "2026-04-17";
|
|
378
407
|
ACP_API_VERSION = "2026-04-17";
|
|
@@ -524,93 +553,95 @@ function invalid(message, path2) {
|
|
|
524
553
|
return new CommerceError("CONFIG_INVALID", message, { details: { path: path2 } });
|
|
525
554
|
}
|
|
526
555
|
function resolveX402Deployment(input) {
|
|
527
|
-
const
|
|
556
|
+
const at = input.configPath ?? "payments.x402";
|
|
557
|
+
const payTo = input.payToField ?? "payTo";
|
|
558
|
+
const profile = requireNetworkProfile(input.network, `${at}.network`);
|
|
528
559
|
const mode = resolveDeploymentMode(profile, input.facilitator.mode);
|
|
529
560
|
if (profile.kind === "mainnet" && input.facilitator.mode === "local") {
|
|
530
561
|
throw invalid(
|
|
531
|
-
|
|
532
|
-
|
|
562
|
+
`${at}: network "${profile.id}" (${profile.displayName}) is a mainnet and cannot be served by facilitator.mode "local". A mainnet deployment must settle through a remote facilitator.`,
|
|
563
|
+
`${at}.facilitator.mode`
|
|
533
564
|
);
|
|
534
565
|
}
|
|
535
566
|
if (mode === "mainnet" && input.allowMainnet !== true) {
|
|
536
567
|
throw invalid(
|
|
537
|
-
|
|
538
|
-
|
|
568
|
+
`${at}: network "${profile.id}" (${profile.displayName}) settles real funds. Set ${at}.allowMainnet: true to acknowledge this explicitly - it is never the default.`,
|
|
569
|
+
`${at}.allowMainnet`
|
|
539
570
|
);
|
|
540
571
|
}
|
|
541
572
|
if (input.facilitator.mode === "remote") {
|
|
542
|
-
assertFacilitatorUrlIsSafe(input.facilitator.url, mode);
|
|
573
|
+
assertFacilitatorUrlIsSafe(input.facilitator.url, mode, at);
|
|
543
574
|
if (mode === "mainnet" && input.facilitator.auth.type === "none" && input.allowUnauthenticatedFacilitator !== true) {
|
|
544
575
|
throw invalid(
|
|
545
|
-
|
|
546
|
-
|
|
576
|
+
`${at}: facilitator ${describeOrigin(input.facilitator.url)} takes no credential, and this is a mainnet deployment. It will see every payment authorisation you handle, with no account, terms or support behind it. Set ${at}.allowUnauthenticatedFacilitator: true to accept that, or configure facilitator.auth.`,
|
|
577
|
+
`${at}.allowUnauthenticatedFacilitator`
|
|
547
578
|
);
|
|
548
579
|
}
|
|
549
580
|
for (const [field, value] of credentialFields(input.facilitator.auth)) {
|
|
550
581
|
if (value.trim() === "") {
|
|
551
582
|
throw invalid(
|
|
552
|
-
|
|
553
|
-
|
|
583
|
+
`${at}: facilitator.auth.type is "${input.facilitator.auth.type}" but ${field} is empty. An empty credential is refused rather than sent.`,
|
|
584
|
+
`${at}.facilitator.auth.${field}`
|
|
554
585
|
);
|
|
555
586
|
}
|
|
556
587
|
}
|
|
557
588
|
}
|
|
558
589
|
if (/^0x0{40}$/i.test(input.payTo)) {
|
|
559
590
|
throw invalid(
|
|
560
|
-
|
|
561
|
-
|
|
591
|
+
`${at}: "${payTo}" is the zero address. Every payment settled there is destroyed.`,
|
|
592
|
+
`${at}.${payTo}`
|
|
562
593
|
);
|
|
563
594
|
}
|
|
564
595
|
if (mode !== "local" && isWellKnownDevAddress(input.payTo)) {
|
|
565
596
|
throw invalid(
|
|
566
|
-
|
|
567
|
-
|
|
597
|
+
`${at}: "${payTo}" (${input.payTo}) is a well-known Anvil development address and this is a ${mode} deployment. Anyone can spend what settles there. Set ${payTo} to your own merchant wallet.`,
|
|
598
|
+
`${at}.${payTo}`
|
|
568
599
|
);
|
|
569
600
|
}
|
|
570
601
|
const canonical = profile.canonicalAsset;
|
|
571
602
|
if (mode === "mainnet" && canonical) {
|
|
572
603
|
if (!sameAddress(input.asset, canonical.address)) {
|
|
573
604
|
throw invalid(
|
|
574
|
-
|
|
575
|
-
|
|
605
|
+
`${at}: asset ${input.asset} is not ${canonical.symbol} on ${profile.displayName} (expected ${canonical.address}). Settling a mainnet payment in an unintended token is refused.`,
|
|
606
|
+
`${at}.asset`
|
|
576
607
|
);
|
|
577
608
|
}
|
|
578
609
|
if (input.assetName !== void 0 && input.assetName !== canonical.name) {
|
|
579
610
|
throw invalid(
|
|
580
|
-
|
|
581
|
-
|
|
611
|
+
`${at}: assetName "${input.assetName}" is not the EIP-712 domain name ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.name}"). Every payment would be refused after the buyer signed.`,
|
|
612
|
+
`${at}.assetName`
|
|
582
613
|
);
|
|
583
614
|
}
|
|
584
615
|
if (input.assetVersion !== void 0 && input.assetVersion !== canonical.version) {
|
|
585
616
|
throw invalid(
|
|
586
|
-
|
|
587
|
-
|
|
617
|
+
`${at}: assetVersion "${input.assetVersion}" is not the EIP-712 domain version ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.version}").`,
|
|
618
|
+
`${at}.assetVersion`
|
|
588
619
|
);
|
|
589
620
|
}
|
|
590
621
|
}
|
|
591
622
|
return { profile, mode };
|
|
592
623
|
}
|
|
593
|
-
function assertFacilitatorUrlIsSafe(url, mode) {
|
|
624
|
+
function assertFacilitatorUrlIsSafe(url, mode, at) {
|
|
594
625
|
let parsed;
|
|
595
626
|
try {
|
|
596
627
|
parsed = new URL(url);
|
|
597
628
|
} catch (cause) {
|
|
598
|
-
throw new CommerceError("CONFIG_INVALID",
|
|
629
|
+
throw new CommerceError("CONFIG_INVALID", `${at}: facilitator.url is not a valid URL`, {
|
|
599
630
|
cause,
|
|
600
|
-
details: { path:
|
|
631
|
+
details: { path: `${at}.facilitator.url` }
|
|
601
632
|
});
|
|
602
633
|
}
|
|
603
634
|
if (parsed.protocol === "https:") return;
|
|
604
635
|
if (parsed.protocol !== "http:") {
|
|
605
636
|
throw invalid(
|
|
606
|
-
|
|
607
|
-
|
|
637
|
+
`${at}: facilitator ${describeOrigin(url)} must be reached over https (or http on a local/private host); got "${parsed.protocol}//"`,
|
|
638
|
+
`${at}.facilitator.url`
|
|
608
639
|
);
|
|
609
640
|
}
|
|
610
641
|
if (mode !== "mainnet" && isLikelyLocalOrPrivateHost(parsed.hostname)) return;
|
|
611
642
|
throw invalid(
|
|
612
|
-
|
|
613
|
-
|
|
643
|
+
`${at}: facilitator ${describeOrigin(url)} is reached over plain HTTP. Payment authorisations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,
|
|
644
|
+
`${at}.facilitator.url`
|
|
614
645
|
);
|
|
615
646
|
}
|
|
616
647
|
function describeOrigin(url) {
|
|
@@ -755,12 +786,7 @@ function normalise(raw) {
|
|
|
755
786
|
};
|
|
756
787
|
validateMountPaths(protocols);
|
|
757
788
|
const x402Raw = raw.payments.x402;
|
|
758
|
-
const
|
|
759
|
-
mode: "remote",
|
|
760
|
-
url: x402Raw.facilitator.url,
|
|
761
|
-
auth: x402Raw.facilitator.auth ?? { type: "none" }
|
|
762
|
-
};
|
|
763
|
-
const x402 = x402Raw && facilitator ? {
|
|
789
|
+
const x402 = x402Raw !== void 0 ? {
|
|
764
790
|
enabled: toBoolean(x402Raw.enabled, "payments.x402.enabled"),
|
|
765
791
|
network: x402Raw.network,
|
|
766
792
|
rpcUrl: x402Raw.rpcUrl,
|
|
@@ -779,7 +805,7 @@ function normalise(raw) {
|
|
|
779
805
|
min: 1
|
|
780
806
|
}
|
|
781
807
|
),
|
|
782
|
-
facilitator,
|
|
808
|
+
facilitator: normaliseFacilitator(x402Raw.facilitator),
|
|
783
809
|
...x402Raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(x402Raw.allowMainnet, "payments.x402.allowMainnet") } : {},
|
|
784
810
|
...x402Raw.allowUnauthenticatedFacilitator !== void 0 ? {
|
|
785
811
|
allowUnauthenticatedFacilitator: toBoolean(
|
|
@@ -802,12 +828,13 @@ function normalise(raw) {
|
|
|
802
828
|
...x402.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: x402.allowUnauthenticatedFacilitator } : {}
|
|
803
829
|
});
|
|
804
830
|
}
|
|
831
|
+
const mpp = normaliseMpp(raw.payments.mpp);
|
|
805
832
|
const ap2 = normaliseAp2(raw.authorization?.ap2);
|
|
806
833
|
if (ap2?.enabled) {
|
|
807
834
|
validateReplayStoreIsolated(ap2.replay.path, raw.storage.receipts.path, protocols.acp);
|
|
808
835
|
}
|
|
809
836
|
const resources = Object.entries(raw.resources).map(
|
|
810
|
-
([id, entry]) => normaliseResource(id, entry, protocols, x402, ap2)
|
|
837
|
+
([id, entry]) => normaliseResource(id, entry, protocols, x402, mpp, ap2)
|
|
811
838
|
);
|
|
812
839
|
if (protocols.acp.enabled) validateAcpCheckoutMapping(protocols.acp, resources);
|
|
813
840
|
return {
|
|
@@ -829,7 +856,8 @@ function normalise(raw) {
|
|
|
829
856
|
protocols,
|
|
830
857
|
resources,
|
|
831
858
|
payments: {
|
|
832
|
-
...x402 !== void 0 ? { x402 } : {}
|
|
859
|
+
...x402 !== void 0 ? { x402 } : {},
|
|
860
|
+
...mpp !== void 0 ? { mpp } : {}
|
|
833
861
|
},
|
|
834
862
|
...ap2 !== void 0 ? { authorization: { ap2 } } : {}
|
|
835
863
|
};
|
|
@@ -1181,7 +1209,74 @@ function validateAcpOperationInput(path2, operation, resource) {
|
|
|
1181
1209
|
}
|
|
1182
1210
|
}
|
|
1183
1211
|
}
|
|
1184
|
-
function
|
|
1212
|
+
function normaliseFacilitator(raw) {
|
|
1213
|
+
return raw.mode === "local" ? { mode: "local", signerPrivateKey: raw.signerPrivateKey } : { mode: "remote", url: raw.url, auth: raw.auth ?? { type: "none" } };
|
|
1214
|
+
}
|
|
1215
|
+
function normaliseMpp(raw) {
|
|
1216
|
+
if (raw === void 0) return void 0;
|
|
1217
|
+
const network = raw.network ?? MPP_DEFAULT_NETWORK;
|
|
1218
|
+
if (!isMppNetwork(network)) {
|
|
1219
|
+
throw new CommerceError(
|
|
1220
|
+
"CONFIG_INVALID",
|
|
1221
|
+
`payments.mpp.network must be one of ${MPP_NETWORKS.join(", ")}`,
|
|
1222
|
+
{ details: { path: "payments.mpp.network" } }
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
const mpp = {
|
|
1226
|
+
enabled: toBoolean(raw.enabled, "payments.mpp.enabled"),
|
|
1227
|
+
network,
|
|
1228
|
+
rpcUrl: raw.rpcUrl,
|
|
1229
|
+
asset: raw.asset,
|
|
1230
|
+
assetName: raw.assetName,
|
|
1231
|
+
assetVersion: raw.assetVersion,
|
|
1232
|
+
recipient: raw.recipient,
|
|
1233
|
+
realm: raw.realm,
|
|
1234
|
+
challengeSecret: raw.challengeSecret,
|
|
1235
|
+
...raw.challengeTtlSeconds !== void 0 ? {
|
|
1236
|
+
challengeTtlSeconds: toNumber(
|
|
1237
|
+
raw.challengeTtlSeconds,
|
|
1238
|
+
"payments.mpp.challengeTtlSeconds",
|
|
1239
|
+
{ min: 1 }
|
|
1240
|
+
)
|
|
1241
|
+
} : {},
|
|
1242
|
+
facilitator: normaliseFacilitator(raw.facilitator),
|
|
1243
|
+
...raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(raw.allowMainnet, "payments.mpp.allowMainnet") } : {},
|
|
1244
|
+
...raw.allowUnauthenticatedFacilitator !== void 0 ? {
|
|
1245
|
+
allowUnauthenticatedFacilitator: toBoolean(
|
|
1246
|
+
raw.allowUnauthenticatedFacilitator,
|
|
1247
|
+
"payments.mpp.allowUnauthenticatedFacilitator"
|
|
1248
|
+
)
|
|
1249
|
+
} : {}
|
|
1250
|
+
};
|
|
1251
|
+
validateAddress("payments.mpp.recipient", mpp.recipient);
|
|
1252
|
+
validateAddress("payments.mpp.asset", mpp.asset);
|
|
1253
|
+
if (/[\r\n]/.test(mpp.realm)) {
|
|
1254
|
+
throw new CommerceError("CONFIG_INVALID", "payments.mpp.realm must be a single line", {
|
|
1255
|
+
details: { path: "payments.mpp.realm" }
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
if (mpp.challengeSecret.length < MPP_MIN_CHALLENGE_SECRET_LENGTH) {
|
|
1259
|
+
throw new CommerceError(
|
|
1260
|
+
"CONFIG_INVALID",
|
|
1261
|
+
`payments.mpp.challengeSecret must have length at least ${MPP_MIN_CHALLENGE_SECRET_LENGTH}`,
|
|
1262
|
+
{ details: { path: "payments.mpp.challengeSecret" } }
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
resolveX402Deployment({
|
|
1266
|
+
network: mpp.network,
|
|
1267
|
+
payTo: mpp.recipient,
|
|
1268
|
+
asset: mpp.asset,
|
|
1269
|
+
assetName: mpp.assetName,
|
|
1270
|
+
assetVersion: mpp.assetVersion,
|
|
1271
|
+
facilitator: mpp.facilitator,
|
|
1272
|
+
...mpp.allowMainnet !== void 0 ? { allowMainnet: mpp.allowMainnet } : {},
|
|
1273
|
+
...mpp.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: mpp.allowUnauthenticatedFacilitator } : {},
|
|
1274
|
+
configPath: "payments.mpp",
|
|
1275
|
+
payToField: "recipient"
|
|
1276
|
+
});
|
|
1277
|
+
return mpp;
|
|
1278
|
+
}
|
|
1279
|
+
function normaliseResource(id, entry, protocols, x402, mpp, ap2) {
|
|
1185
1280
|
if (entry.input !== void 0) validateResourceSchemaKeywords(id, "input", entry.input);
|
|
1186
1281
|
const inputProperties = entry.input?.["properties"];
|
|
1187
1282
|
if (inputProperties && typeof inputProperties === "object") {
|
|
@@ -1257,7 +1352,7 @@ function normaliseResource(id, entry, protocols, x402, ap2) {
|
|
|
1257
1352
|
);
|
|
1258
1353
|
}
|
|
1259
1354
|
if (entry.pricing.type === "fixed") {
|
|
1260
|
-
validatePricingAmount(id, entry.pricing.amount, x402);
|
|
1355
|
+
validatePricingAmount(id, entry.pricing.amount, x402?.assetDecimals);
|
|
1261
1356
|
}
|
|
1262
1357
|
const paymentMethods = entry.payments ?? [];
|
|
1263
1358
|
if (entry.pricing.type === "fixed") {
|
|
@@ -1272,17 +1367,28 @@ function normaliseResource(id, entry, protocols, x402, ap2) {
|
|
|
1272
1367
|
if (!SUPPORTED_PAYMENT_METHODS.has(method)) {
|
|
1273
1368
|
throw new CommerceError(
|
|
1274
1369
|
"CONFIG_INVALID",
|
|
1275
|
-
`Resource "${id}" names unsupported payment method "${method}". Supported:
|
|
1370
|
+
`Resource "${id}" names unsupported payment method "${method}". Supported: ${PAYMENT_METHOD_NAMES.join(", ")}.`,
|
|
1276
1371
|
{ details: { path: `resources.${id}.payments`, resourceId: id, method } }
|
|
1277
1372
|
);
|
|
1278
1373
|
}
|
|
1279
|
-
|
|
1374
|
+
}
|
|
1375
|
+
const enabledRails = [...x402?.enabled ? ["x402"] : [], ...mpp?.enabled ? ["mpp"] : []];
|
|
1376
|
+
if (!paymentMethods.some((method) => enabledRails.includes(method))) {
|
|
1377
|
+
throw new CommerceError(
|
|
1378
|
+
"CONFIG_INVALID",
|
|
1379
|
+
`Resource "${id}" names payment method(s) "${paymentMethods.join(", ")}", none of which is configured and enabled under "payments"`,
|
|
1380
|
+
{ details: { path: `resources.${id}.payments`, resourceId: id, paymentMethods } }
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
if (mpp?.enabled && paymentMethods.includes("mpp")) {
|
|
1384
|
+
if (entry.pricing.currency !== MPP_PROFILE.assetSymbol) {
|
|
1280
1385
|
throw new CommerceError(
|
|
1281
1386
|
"CONFIG_INVALID",
|
|
1282
|
-
`Resource "${id}"
|
|
1283
|
-
{ details: { path: `resources.${id}.
|
|
1387
|
+
`Resource "${id}" is priced in ${entry.pricing.currency}, but MPP charges ${MPP_PROFILE.assetSymbol}`,
|
|
1388
|
+
{ details: { path: `resources.${id}.pricing.currency`, resourceId: id } }
|
|
1284
1389
|
);
|
|
1285
1390
|
}
|
|
1391
|
+
validatePricingAmount(id, entry.pricing.amount, MPP_PROFILE.assetDecimals);
|
|
1286
1392
|
}
|
|
1287
1393
|
}
|
|
1288
1394
|
const pricing = entry.pricing.type === "free" ? { type: "free" } : { type: "fixed", amount: entry.pricing.amount, currency: entry.pricing.currency };
|
|
@@ -1396,7 +1502,7 @@ function validateResourceSchemaKeywords(id, path2, schema) {
|
|
|
1396
1502
|
);
|
|
1397
1503
|
}
|
|
1398
1504
|
}
|
|
1399
|
-
function validatePricingAmount(id, amount,
|
|
1505
|
+
function validatePricingAmount(id, amount, decimals) {
|
|
1400
1506
|
const path2 = `resources.${id}.pricing.amount`;
|
|
1401
1507
|
if (!PRICING_AMOUNT_PATTERN.test(amount)) {
|
|
1402
1508
|
throw new CommerceError(
|
|
@@ -1412,12 +1518,12 @@ function validatePricingAmount(id, amount, x402) {
|
|
|
1412
1518
|
{ details: { path: path2, resourceId: id } }
|
|
1413
1519
|
);
|
|
1414
1520
|
}
|
|
1415
|
-
if (
|
|
1521
|
+
if (decimals !== void 0) {
|
|
1416
1522
|
const fractionalDigits = amount.includes(".") ? amount.split(".")[1]?.length ?? 0 : 0;
|
|
1417
|
-
if (fractionalDigits >
|
|
1523
|
+
if (fractionalDigits > decimals) {
|
|
1418
1524
|
throw new CommerceError(
|
|
1419
1525
|
"CONFIG_INVALID",
|
|
1420
|
-
`Resource "${id}" has pricing.amount "${amount}" with more precision (${fractionalDigits} fractional digits) than the configured asset supports (${
|
|
1526
|
+
`Resource "${id}" has pricing.amount "${amount}" with more precision (${fractionalDigits} fractional digits) than the configured asset supports (${decimals} decimals)`,
|
|
1421
1527
|
{ details: { path: path2, resourceId: id } }
|
|
1422
1528
|
);
|
|
1423
1529
|
}
|
|
@@ -1588,14 +1694,15 @@ function validateAddress(path2, value) {
|
|
|
1588
1694
|
);
|
|
1589
1695
|
}
|
|
1590
1696
|
}
|
|
1591
|
-
var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, RESERVED_GATEWAY_PATHS, MountPathSchema, AcpAuthSchema, AcpIdempotencySchema, AcpCheckoutSchema, AcpDiscoverySchema, ProtocolsSchema, DEFAULT_A2A_MOUNT_PATH, DEFAULT_ACP_MOUNT_PATH, ACP_RETENTION_HOURS, BackendMethodSchema, BackendInputBindingsSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorAuthSchema, FacilitatorSchema, X402Schema, PaymentsSchema, Ap2KeySchema, Ap2IssuerSchema, Ap2Schema, AuthorizationSchema, RawConfigSchema, SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, SUPPORTED_AUTHORIZATION_METHODS, EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
|
|
1697
|
+
var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, RESERVED_GATEWAY_PATHS, MountPathSchema, AcpAuthSchema, AcpIdempotencySchema, AcpCheckoutSchema, AcpDiscoverySchema, ProtocolsSchema, DEFAULT_A2A_MOUNT_PATH, DEFAULT_ACP_MOUNT_PATH, ACP_RETENTION_HOURS, BackendMethodSchema, BackendInputBindingsSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorAuthSchema, FacilitatorSchema, X402Schema, MppSchema, PaymentsSchema, Ap2KeySchema, Ap2IssuerSchema, Ap2Schema, AuthorizationSchema, RawConfigSchema, SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, SUPPORTED_AUTHORIZATION_METHODS, EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
|
|
1592
1698
|
var init_schema = __esm({
|
|
1593
1699
|
"src/config/schema.ts"() {
|
|
1594
1700
|
init_constants();
|
|
1595
1701
|
init_execution();
|
|
1596
1702
|
init_core();
|
|
1597
|
-
init_guardrails();
|
|
1598
1703
|
init_constants2();
|
|
1704
|
+
init_guardrails();
|
|
1705
|
+
init_constants3();
|
|
1599
1706
|
init_env();
|
|
1600
1707
|
SUPPORTED_CONFIG_VERSION = 1;
|
|
1601
1708
|
ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
|
|
@@ -1781,8 +1888,26 @@ var init_schema = __esm({
|
|
|
1781
1888
|
/** Accepts a mainnet facilitator that takes no credential. Never defaulted. */
|
|
1782
1889
|
allowUnauthenticatedFacilitator: BooleanOrString.optional()
|
|
1783
1890
|
}).strict();
|
|
1891
|
+
MppSchema = z.object({
|
|
1892
|
+
enabled: BooleanOrString,
|
|
1893
|
+
network: z.string().min(1).optional(),
|
|
1894
|
+
rpcUrl: z.string().min(1),
|
|
1895
|
+
asset: z.string().min(1),
|
|
1896
|
+
assetName: z.string().min(1),
|
|
1897
|
+
assetVersion: z.string().min(1),
|
|
1898
|
+
recipient: z.string().min(1),
|
|
1899
|
+
realm: z.string().min(1),
|
|
1900
|
+
challengeSecret: z.string().min(1),
|
|
1901
|
+
challengeTtlSeconds: NumberOrString.optional(),
|
|
1902
|
+
facilitator: FacilitatorSchema,
|
|
1903
|
+
// Real funds. Never defaulted - see src/payments/x402/guardrails.ts
|
|
1904
|
+
allowMainnet: BooleanOrString.optional(),
|
|
1905
|
+
// Accepts a mainnet facilitator that takes no credential. Never defaulted
|
|
1906
|
+
allowUnauthenticatedFacilitator: BooleanOrString.optional()
|
|
1907
|
+
}).strict();
|
|
1784
1908
|
PaymentsSchema = z.object({
|
|
1785
|
-
x402: X402Schema.optional()
|
|
1909
|
+
x402: X402Schema.optional(),
|
|
1910
|
+
mpp: MppSchema.optional()
|
|
1786
1911
|
}).strict();
|
|
1787
1912
|
Ap2KeySchema = z.object({
|
|
1788
1913
|
kid: z.string().min(1),
|
|
@@ -1821,7 +1946,7 @@ var init_schema = __esm({
|
|
|
1821
1946
|
authorization: AuthorizationSchema.optional()
|
|
1822
1947
|
}).strict();
|
|
1823
1948
|
SUPPORTED_PROTOCOLS = new Set(PROTOCOL_NAMES);
|
|
1824
|
-
SUPPORTED_PAYMENT_METHODS =
|
|
1949
|
+
SUPPORTED_PAYMENT_METHODS = new Set(PAYMENT_METHOD_NAMES);
|
|
1825
1950
|
SUPPORTED_AUTHORIZATION_METHODS = /* @__PURE__ */ new Set(["ap2"]);
|
|
1826
1951
|
EMPTY_CLOSED_OBJECT_SCHEMA = {
|
|
1827
1952
|
type: "object",
|
|
@@ -2029,13 +2154,13 @@ var AP2_UNSUPPORTED = [
|
|
|
2029
2154
|
"digest algorithms other than sha-256",
|
|
2030
2155
|
// Roles this gateway does not play
|
|
2031
2156
|
"mandate issuance",
|
|
2032
|
-
"merchant checkout JWT issuance",
|
|
2033
2157
|
"AP2 over the ACP checkout adapter"
|
|
2034
2158
|
];
|
|
2035
2159
|
|
|
2036
2160
|
// src/cli/commands/doctor.ts
|
|
2037
2161
|
init_execution();
|
|
2038
2162
|
init_core();
|
|
2163
|
+
init_constants2();
|
|
2039
2164
|
init_networks();
|
|
2040
2165
|
|
|
2041
2166
|
// src/protocols/a2a/constants.ts
|
|
@@ -2076,10 +2201,10 @@ var A2A_UNSUPPORTED = [
|
|
|
2076
2201
|
];
|
|
2077
2202
|
|
|
2078
2203
|
// src/cli/commands/doctor.ts
|
|
2079
|
-
|
|
2204
|
+
init_constants3();
|
|
2080
2205
|
|
|
2081
2206
|
// src/protocols/acp/descriptor.ts
|
|
2082
|
-
|
|
2207
|
+
init_constants3();
|
|
2083
2208
|
[
|
|
2084
2209
|
"rest transport",
|
|
2085
2210
|
"well-known discovery",
|
|
@@ -2113,7 +2238,7 @@ var ACP_UNSUPPORTED = [
|
|
|
2113
2238
|
|
|
2114
2239
|
// src/storage/receipts/store.ts
|
|
2115
2240
|
init_core();
|
|
2116
|
-
var PACKAGE_VERSION = "1.
|
|
2241
|
+
var PACKAGE_VERSION = "1.5.0" ;
|
|
2117
2242
|
|
|
2118
2243
|
// src/storage/sqlite.ts
|
|
2119
2244
|
init_core();
|
|
@@ -2884,7 +3009,7 @@ function maskMiddle(value, keep = 6) {
|
|
|
2884
3009
|
}
|
|
2885
3010
|
createRequire(import.meta.url);
|
|
2886
3011
|
function readVersionReport() {
|
|
2887
|
-
const injected = '{"cliVersion":"1.
|
|
3012
|
+
const injected = '{"cliVersion":"1.5.0","pinned":[{"name":"@coinbase/x402","version":"2.1.0","via":"@devlab.group/agent-commerce"},{"name":"@modelcontextprotocol/sdk","version":"1.30.0","via":"@devlab.group/agent-commerce"},{"name":"@x402/core","version":"2.23.0","via":"@devlab.group/agent-commerce"},{"name":"@x402/evm","version":"2.23.0","via":"@devlab.group/agent-commerce"},{"name":"viem","version":"2.55.18","via":"@devlab.group/agent-commerce"},{"name":"better-sqlite3","version":"13.0.3","via":"@devlab.group/agent-commerce"},{"name":"fastify","version":"5.12.0","via":"@devlab.group/agent-commerce"},{"name":"pino","version":"10.3.1","via":"@devlab.group/agent-commerce"},{"name":"zod","version":"3.25.76","via":"@devlab.group/agent-commerce"}]}' ;
|
|
2888
3013
|
{
|
|
2889
3014
|
return JSON.parse(injected);
|
|
2890
3015
|
}
|
|
@@ -2909,15 +3034,16 @@ function substitutePathParams(url) {
|
|
|
2909
3034
|
}
|
|
2910
3035
|
return filled;
|
|
2911
3036
|
}
|
|
2912
|
-
function extractWellKnownX402(body) {
|
|
3037
|
+
function extractWellKnownX402(body, rail = "x402") {
|
|
2913
3038
|
const payments = body?.["payments"];
|
|
2914
3039
|
if (typeof payments !== "object" || payments === null) return void 0;
|
|
2915
|
-
const
|
|
2916
|
-
if (typeof
|
|
2917
|
-
const rec =
|
|
3040
|
+
const block = payments[rail];
|
|
3041
|
+
if (typeof block !== "object" || block === null) return void 0;
|
|
3042
|
+
const rec = block;
|
|
2918
3043
|
if (rec["enabled"] === false) return "disabled";
|
|
2919
3044
|
if (rec["enabled"] !== true) return void 0;
|
|
2920
|
-
const { asset, network
|
|
3045
|
+
const { asset, network } = rec;
|
|
3046
|
+
const payTo = rec[rail === "mpp" ? "recipient" : "payTo"];
|
|
2921
3047
|
if (typeof asset !== "string" || typeof network !== "string" || typeof payTo !== "string") {
|
|
2922
3048
|
return void 0;
|
|
2923
3049
|
}
|
|
@@ -2926,7 +3052,7 @@ function extractWellKnownX402(body) {
|
|
|
2926
3052
|
function sameAddress2(a, b) {
|
|
2927
3053
|
return a.toLowerCase() === b.toLowerCase();
|
|
2928
3054
|
}
|
|
2929
|
-
function findX402Mismatch(configured, live) {
|
|
3055
|
+
function findX402Mismatch(configured, live, payToField = "payTo") {
|
|
2930
3056
|
const diffs = [];
|
|
2931
3057
|
if (!sameAddress2(configured.asset, live.asset)) {
|
|
2932
3058
|
diffs.push(
|
|
@@ -2940,11 +3066,11 @@ function findX402Mismatch(configured, live) {
|
|
|
2940
3066
|
}
|
|
2941
3067
|
if (!sameAddress2(configured.payTo, live.payTo)) {
|
|
2942
3068
|
diffs.push(
|
|
2943
|
-
|
|
3069
|
+
`${payToField}: gateway is using ${live.payTo} but local config resolves to ${configured.payTo}`
|
|
2944
3070
|
);
|
|
2945
3071
|
}
|
|
2946
3072
|
if (diffs.length === 0) return void 0;
|
|
2947
|
-
return `${diffs.join("; ")}
|
|
3073
|
+
return `${diffs.join("; ")}; the gateway may be running against an older deployment - restart it or re-run chain:deploy`;
|
|
2948
3074
|
}
|
|
2949
3075
|
function describeIssuers(label, issuers) {
|
|
2950
3076
|
const described = issuers.map(
|
|
@@ -3250,11 +3376,36 @@ async function runDoctor(options, deps = {}) {
|
|
|
3250
3376
|
});
|
|
3251
3377
|
}
|
|
3252
3378
|
}
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
status: "INFO",
|
|
3256
|
-
|
|
3257
|
-
|
|
3379
|
+
const mpp = config?.payments.mpp;
|
|
3380
|
+
if (mpp === void 0 || !mpp.enabled) {
|
|
3381
|
+
checks.push({ name: "Payments (MPP)", status: "INFO", detail: "MPP not configured" });
|
|
3382
|
+
} else {
|
|
3383
|
+
const profile = findNetworkProfile(mpp.network);
|
|
3384
|
+
const mode = profile ? resolveDeploymentMode(profile, mpp.facilitator.mode) : void 0;
|
|
3385
|
+
const where = profile === void 0 || mode === void 0 ? `unknown network ${mpp.network}` : mode === "local" ? `LOCAL dev chain (${mpp.network}, chain id shared with ${profile.displayName})` : `${describeDeploymentMode(mode)} on ${profile.displayName} (${mpp.network})`;
|
|
3386
|
+
const facilitatorDetail = mpp.facilitator.mode === "local" ? "local" : `remote (auth=${mpp.facilitator.auth.type})`;
|
|
3387
|
+
const summary = `MPP ${MPP_PROFILE.intent}/${MPP_PROFILE.method}/${MPP_PROFILE.credentialType} enabled - ${where}, asset=${MPP_PROFILE.assetSymbol} ${maskMiddle(mpp.asset)}, recipient=${maskMiddle(mpp.recipient)}, facilitator=${facilitatorDetail}, spec ${MPP_SPEC_DRAFTS.core}@${MPP_SPEC_COMMIT.slice(0, 7)}, mppx ${MPPX_VERSION}`;
|
|
3388
|
+
const configured = { asset: mpp.asset, network: mpp.network, payTo: mpp.recipient };
|
|
3389
|
+
const live = wellKnown?.ok ? extractWellKnownX402(wellKnown.body, "mpp") : void 0;
|
|
3390
|
+
if (live === "disabled") {
|
|
3391
|
+
checks.push({
|
|
3392
|
+
name: "Payments (MPP)",
|
|
3393
|
+
status: "FAIL",
|
|
3394
|
+
detail: `Local config enables MPP, but ${gatewayUrl} reports it disabled`
|
|
3395
|
+
});
|
|
3396
|
+
} else if (live === void 0) {
|
|
3397
|
+
checks.push({
|
|
3398
|
+
name: "Payments (MPP)",
|
|
3399
|
+
status: "INFO",
|
|
3400
|
+
detail: `${summary} (live MPP settlement config unavailable for comparison)`
|
|
3401
|
+
});
|
|
3402
|
+
} else {
|
|
3403
|
+
const mismatch = findX402Mismatch(configured, live, "recipient");
|
|
3404
|
+
checks.push(
|
|
3405
|
+
mismatch === void 0 ? { name: "Payments (MPP)", status: "PASS", detail: summary } : { name: "Payments (MPP)", status: "FAIL", detail: mismatch }
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3258
3409
|
if (config === void 0) {
|
|
3259
3410
|
checks.push({ name: "Storage", status: "WARN", detail: "skipped \u2014 config invalid" });
|
|
3260
3411
|
} else if (
|
|
@@ -3463,16 +3614,24 @@ function discoverOperations(loaded, options = {}) {
|
|
|
3463
3614
|
}
|
|
3464
3615
|
const pathItem = dereference(document, rawPathItem).value;
|
|
3465
3616
|
if (!isRecord(pathItem)) continue;
|
|
3617
|
+
const unsupportedMethod = (name) => {
|
|
3618
|
+
diagnostics.push({
|
|
3619
|
+
severity: "warning",
|
|
3620
|
+
code: "unsupported-method",
|
|
3621
|
+
operation: `${name.toUpperCase()} ${path2}`,
|
|
3622
|
+
message: `Skipped ${name.toUpperCase()} ${path2}: only GET, POST, PUT, PATCH and DELETE are supported`
|
|
3623
|
+
});
|
|
3624
|
+
};
|
|
3466
3625
|
for (const [key, rawOperation] of Object.entries(pathItem)) {
|
|
3467
3626
|
if (NON_OPERATION_KEYS.has(key) || key.startsWith("x-")) continue;
|
|
3627
|
+
if (key === "additionalOperations") {
|
|
3628
|
+
if (isRecord(rawOperation))
|
|
3629
|
+
for (const name of Object.keys(rawOperation)) unsupportedMethod(name);
|
|
3630
|
+
continue;
|
|
3631
|
+
}
|
|
3468
3632
|
const method = SUPPORTED_METHODS[key.toLowerCase()];
|
|
3469
3633
|
if (method === void 0) {
|
|
3470
|
-
|
|
3471
|
-
severity: "warning",
|
|
3472
|
-
code: "unsupported-method",
|
|
3473
|
-
operation: `${key.toUpperCase()} ${path2}`,
|
|
3474
|
-
message: `Skipped ${key.toUpperCase()} ${path2}: only GET, POST, PUT, PATCH and DELETE are supported`
|
|
3475
|
-
});
|
|
3634
|
+
unsupportedMethod(key);
|
|
3476
3635
|
continue;
|
|
3477
3636
|
}
|
|
3478
3637
|
const operation = dereference(document, rawOperation).value;
|
|
@@ -3552,12 +3711,13 @@ function selectServer(document, candidates, context, options, diagnostics) {
|
|
|
3552
3711
|
if (typeof url !== "string" || url === "") continue;
|
|
3553
3712
|
const substituted = substituteServerVariables(url, first["variables"], context, diagnostics);
|
|
3554
3713
|
if (substituted === void 0) return void 0;
|
|
3555
|
-
|
|
3714
|
+
const issue = describeServerUrlIssue(substituted);
|
|
3715
|
+
if (issue !== void 0) {
|
|
3556
3716
|
diagnostics.push({
|
|
3557
3717
|
severity: "error",
|
|
3558
|
-
code:
|
|
3718
|
+
code: issue.code,
|
|
3559
3719
|
operation: context.resourceId,
|
|
3560
|
-
message: `Skipped ${context.where}: server URL "${substituted}"
|
|
3720
|
+
message: `Skipped ${context.where}: server URL "${substituted}" ${issue.reason}. Pass --base-url`
|
|
3561
3721
|
});
|
|
3562
3722
|
return void 0;
|
|
3563
3723
|
}
|
|
@@ -3606,12 +3766,26 @@ function substituteServerVariables(url, variables, context, diagnostics) {
|
|
|
3606
3766
|
return substituted;
|
|
3607
3767
|
}
|
|
3608
3768
|
function isAbsoluteHttpUrl(value) {
|
|
3769
|
+
return describeServerUrlIssue(value) === void 0;
|
|
3770
|
+
}
|
|
3771
|
+
function describeServerUrlIssue(value) {
|
|
3609
3772
|
try {
|
|
3610
3773
|
const parsed = new URL(value);
|
|
3611
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
3612
|
-
|
|
3774
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
3775
|
+
return { code: "invalid-server-url", reason: "must use http:// or https://" };
|
|
3776
|
+
}
|
|
3777
|
+
if (parsed.search !== "" || parsed.hash !== "") {
|
|
3778
|
+
return {
|
|
3779
|
+
code: "invalid-server-url",
|
|
3780
|
+
reason: "must not contain a query string or fragment"
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
return void 0;
|
|
3613
3784
|
} catch {
|
|
3614
|
-
return
|
|
3785
|
+
return {
|
|
3786
|
+
code: "relative-server-url",
|
|
3787
|
+
reason: "is relative or malformed, so no backend host is known"
|
|
3788
|
+
};
|
|
3615
3789
|
}
|
|
3616
3790
|
}
|
|
3617
3791
|
function assertAbsoluteHttpUrl(value, label) {
|
|
@@ -3765,6 +3939,7 @@ function mergeAllOf(document, schema, stack, dropped) {
|
|
|
3765
3939
|
const merged = { type: "object", properties: {}, required: [] };
|
|
3766
3940
|
const properties = merged["properties"];
|
|
3767
3941
|
const required = /* @__PURE__ */ new Set();
|
|
3942
|
+
let open2 = false;
|
|
3768
3943
|
const parts = [...branches, ...Object.keys(siblings).length > 0 ? [siblings] : []];
|
|
3769
3944
|
for (const branch of parts) {
|
|
3770
3945
|
const converted = convertNode(document, branch, stack, dropped);
|
|
@@ -3793,13 +3968,22 @@ function mergeAllOf(document, schema, stack, dropped) {
|
|
|
3793
3968
|
throw new UnsupportedSchema(`allOf branch uses "${keyword}", which cannot be merged`);
|
|
3794
3969
|
}
|
|
3795
3970
|
}
|
|
3796
|
-
|
|
3797
|
-
merged
|
|
3971
|
+
for (const keyword of METADATA_KEYWORDS) {
|
|
3972
|
+
if (Object.hasOwn(converted, keyword) && !Object.hasOwn(merged, keyword)) {
|
|
3973
|
+
merged[keyword] = converted[keyword];
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
const additional = converted["additionalProperties"];
|
|
3977
|
+
if (additional === true) open2 = true;
|
|
3978
|
+
else if (isRecord2(additional)) {
|
|
3979
|
+
throw new UnsupportedSchema(
|
|
3980
|
+
"allOf branch constrains additionalProperties, which cannot be merged"
|
|
3981
|
+
);
|
|
3798
3982
|
}
|
|
3799
3983
|
}
|
|
3800
3984
|
if (required.size > 0) merged["required"] = [...required];
|
|
3801
3985
|
else delete merged["required"];
|
|
3802
|
-
merged["additionalProperties"] =
|
|
3986
|
+
merged["additionalProperties"] = open2;
|
|
3803
3987
|
return merged;
|
|
3804
3988
|
}
|
|
3805
3989
|
function isRecord2(value) {
|
|
@@ -3963,11 +4147,11 @@ function describeUnsupportedParameter(document, parameter, location) {
|
|
|
3963
4147
|
}
|
|
3964
4148
|
function resolveBody(document, candidate) {
|
|
3965
4149
|
if (candidate.requestBody === void 0) return { kind: "none" };
|
|
3966
|
-
const
|
|
3967
|
-
if (typeof
|
|
4150
|
+
const resolvedBody = dereference(document, candidate.requestBody);
|
|
4151
|
+
if (typeof resolvedBody.value !== "object" || resolvedBody.value === null || Array.isArray(resolvedBody.value)) {
|
|
3968
4152
|
return { kind: "none" };
|
|
3969
4153
|
}
|
|
3970
|
-
const requestBody =
|
|
4154
|
+
const requestBody = resolvedBody.value;
|
|
3971
4155
|
const required = requestBody["required"] === true;
|
|
3972
4156
|
if (candidate.method === "GET" || candidate.method === "DELETE") {
|
|
3973
4157
|
return {
|
|
@@ -3986,7 +4170,7 @@ function resolveBody(document, candidate) {
|
|
|
3986
4170
|
reason: `no JSON request body content type (found: ${Object.keys(content).join(", ") || "none"}). Only application/json and application/*+json are supported - multipart and form data are never serialized as JSON`
|
|
3987
4171
|
};
|
|
3988
4172
|
}
|
|
3989
|
-
const media = content[mediaType];
|
|
4173
|
+
const media = dereference(document, content[mediaType], resolvedBody.stack).value;
|
|
3990
4174
|
const schemaNode = isRecord3(media) ? media["schema"] : void 0;
|
|
3991
4175
|
if (schemaNode === void 0) {
|
|
3992
4176
|
return {
|
|
@@ -4031,11 +4215,12 @@ function buildResourceDrafts(loaded, options = {}) {
|
|
|
4031
4215
|
loaded,
|
|
4032
4216
|
options.baseUrl !== void 0 ? { baseUrl: options.baseUrl } : {}
|
|
4033
4217
|
);
|
|
4034
|
-
const diagnostics = [
|
|
4218
|
+
const diagnostics = [];
|
|
4035
4219
|
const drafts = [];
|
|
4036
4220
|
const wanted = options.include?.operationIds;
|
|
4037
4221
|
const wantedTags = options.include?.tags;
|
|
4038
4222
|
const matched = /* @__PURE__ */ new Set();
|
|
4223
|
+
const selected = /* @__PURE__ */ new Set();
|
|
4039
4224
|
for (const candidate of discovery.operations) {
|
|
4040
4225
|
if (wanted !== void 0 && !selects(wanted, candidate)) continue;
|
|
4041
4226
|
if (wantedTags !== void 0 && !candidate.tags.some((tag) => wantedTags.includes(tag))) {
|
|
@@ -4044,6 +4229,7 @@ function buildResourceDrafts(loaded, options = {}) {
|
|
|
4044
4229
|
if (wanted !== void 0) {
|
|
4045
4230
|
for (const id of wanted) if (selects([id], candidate)) matched.add(id);
|
|
4046
4231
|
}
|
|
4232
|
+
selected.add(candidate.resourceId);
|
|
4047
4233
|
const mapping = mapRequest(loaded, candidate);
|
|
4048
4234
|
diagnostics.push(...mapping.diagnostics);
|
|
4049
4235
|
if (!mapping.supported) continue;
|
|
@@ -4073,8 +4259,8 @@ function buildResourceDrafts(loaded, options = {}) {
|
|
|
4073
4259
|
if (options.policy?.pricing === void 0 || options.policy.expose === void 0) {
|
|
4074
4260
|
review.push(
|
|
4075
4261
|
"REVIEW: pricing and exposure are not inferred from OpenAPI. Add e.g.",
|
|
4076
|
-
' pricing: { type: free } # or { type: fixed, amount: "0.01", currency: USDC }',
|
|
4077
|
-
" expose: [http] # http | mcp | a2a"
|
|
4262
|
+
' pricing: { type: free } # or { type: fixed, amount: "0.01", currency: USDC } with payments: [x402]',
|
|
4263
|
+
" expose: [http] # http | mcp | a2a | acp"
|
|
4078
4264
|
);
|
|
4079
4265
|
}
|
|
4080
4266
|
drafts.push({
|
|
@@ -4108,10 +4294,19 @@ function buildResourceDrafts(loaded, options = {}) {
|
|
|
4108
4294
|
version: loaded.version,
|
|
4109
4295
|
sourcePath: loaded.sourcePath,
|
|
4110
4296
|
drafts,
|
|
4111
|
-
diagnostics
|
|
4297
|
+
diagnostics: [
|
|
4298
|
+
...selectDiscoveryDiagnostics(discovery.diagnostics, options.include, selected),
|
|
4299
|
+
...diagnostics
|
|
4300
|
+
],
|
|
4112
4301
|
unmatchedOperationIds: (wanted ?? []).filter((id) => !matched.has(id))
|
|
4113
4302
|
};
|
|
4114
4303
|
}
|
|
4304
|
+
function selectDiscoveryDiagnostics(found, include, selected) {
|
|
4305
|
+
if (include?.operationIds === void 0 && include?.tags === void 0) return found;
|
|
4306
|
+
return found.filter(
|
|
4307
|
+
({ operation }) => operation === void 0 || selected.has(operation) || include.operationIds?.includes(operation) === true
|
|
4308
|
+
);
|
|
4309
|
+
}
|
|
4115
4310
|
function selects(ids, candidate) {
|
|
4116
4311
|
return ids.includes(candidate.resourceId) || candidate.operationId !== void 0 && ids.includes(candidate.operationId);
|
|
4117
4312
|
}
|
|
@@ -4125,19 +4320,28 @@ function selectOutputSchema(document, candidate, diagnostics, dropped) {
|
|
|
4125
4320
|
if (!isRecord4(responses)) return void 0;
|
|
4126
4321
|
const successes = Object.keys(responses).filter((status) => /^2\d\d$/.test(status)).sort((a, b) => rank(a) - rank(b));
|
|
4127
4322
|
if (successes.length === 0) return void 0;
|
|
4128
|
-
const withBody = successes.
|
|
4129
|
-
const
|
|
4130
|
-
|
|
4131
|
-
|
|
4323
|
+
const withBody = successes.flatMap((status) => {
|
|
4324
|
+
const response = dereference(document, responses[status]);
|
|
4325
|
+
if (!isRecord4(response.value)) return [];
|
|
4326
|
+
const content = response.value["content"];
|
|
4327
|
+
if (!isRecord4(content)) return [];
|
|
4328
|
+
const mediaType = pickJsonMediaType(Object.keys(content));
|
|
4329
|
+
if (mediaType === void 0) return [];
|
|
4330
|
+
const media = dereference(document, content[mediaType], response.stack).value;
|
|
4331
|
+
return isRecord4(media) ? [{ status, media }] : [];
|
|
4132
4332
|
});
|
|
4133
4333
|
const chosen = withBody[0];
|
|
4134
4334
|
if (chosen === void 0) return void 0;
|
|
4135
|
-
const
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4335
|
+
const schemaNode = chosen.media["schema"];
|
|
4336
|
+
if (schemaNode === void 0) {
|
|
4337
|
+
diagnostics.push({
|
|
4338
|
+
severity: "warning",
|
|
4339
|
+
code: "missing-output-schema",
|
|
4340
|
+
operation: candidate.resourceId,
|
|
4341
|
+
message: `${candidate.resourceId}: response ${chosen.status} has JSON content without a schema; omitted the output schema`
|
|
4342
|
+
});
|
|
4343
|
+
return void 0;
|
|
4344
|
+
}
|
|
4141
4345
|
const converted = convertSchema(document, schemaNode);
|
|
4142
4346
|
if (!converted.supported) {
|
|
4143
4347
|
diagnostics.push({
|
|
@@ -4154,7 +4358,7 @@ function selectOutputSchema(document, candidate, diagnostics, dropped) {
|
|
|
4154
4358
|
severity: "warning",
|
|
4155
4359
|
code: "multiple-success-responses",
|
|
4156
4360
|
operation: candidate.resourceId,
|
|
4157
|
-
message: `${candidate.resourceId}: responses ${withBody.join(", ")} all carry a JSON body; used ${chosen}`
|
|
4361
|
+
message: `${candidate.resourceId}: responses ${withBody.map(({ status }) => status).join(", ")} all carry a JSON body; used ${chosen.status}`
|
|
4158
4362
|
});
|
|
4159
4363
|
}
|
|
4160
4364
|
return converted.schema;
|
|
@@ -4200,6 +4404,31 @@ function invalid2(message, details, cause) {
|
|
|
4200
4404
|
...cause !== void 0 ? { cause } : {}
|
|
4201
4405
|
});
|
|
4202
4406
|
}
|
|
4407
|
+
async function readCapped(sourcePath, details) {
|
|
4408
|
+
const buffer = Buffer.alloc(MAX_SOURCE_BYTES + 1);
|
|
4409
|
+
let length = 0;
|
|
4410
|
+
try {
|
|
4411
|
+
const handle = await open(sourcePath, "r");
|
|
4412
|
+
try {
|
|
4413
|
+
while (length < buffer.length) {
|
|
4414
|
+
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
|
|
4415
|
+
if (bytesRead === 0) break;
|
|
4416
|
+
length += bytesRead;
|
|
4417
|
+
}
|
|
4418
|
+
} finally {
|
|
4419
|
+
await handle.close();
|
|
4420
|
+
}
|
|
4421
|
+
} catch (error) {
|
|
4422
|
+
throw invalid2(`OpenAPI document "${sourcePath}" could not be read`, details, error);
|
|
4423
|
+
}
|
|
4424
|
+
if (length > MAX_SOURCE_BYTES) {
|
|
4425
|
+
throw invalid2(`OpenAPI document "${sourcePath}" is over the ${MAX_SOURCE_BYTES}-byte limit`, {
|
|
4426
|
+
...details,
|
|
4427
|
+
maxBytes: MAX_SOURCE_BYTES
|
|
4428
|
+
});
|
|
4429
|
+
}
|
|
4430
|
+
return buffer.toString("utf8", 0, length);
|
|
4431
|
+
}
|
|
4203
4432
|
async function loadOpenApiDocument(sourcePath) {
|
|
4204
4433
|
const details = { sourcePath };
|
|
4205
4434
|
let stats;
|
|
@@ -4224,7 +4453,7 @@ async function loadOpenApiDocument(sourcePath) {
|
|
|
4224
4453
|
details
|
|
4225
4454
|
);
|
|
4226
4455
|
}
|
|
4227
|
-
const source = await
|
|
4456
|
+
const source = await readCapped(sourcePath, details);
|
|
4228
4457
|
if (source.trim() === "") {
|
|
4229
4458
|
throw invalid2(`OpenAPI document "${sourcePath}" is empty`, details);
|
|
4230
4459
|
}
|
|
@@ -4702,7 +4931,10 @@ function buildProgram(io = processIo) {
|
|
|
4702
4931
|
process.exitCode = code;
|
|
4703
4932
|
});
|
|
4704
4933
|
const importCommand = program.command("import").description("Generate Agent Commerce resource drafts from an API description.");
|
|
4705
|
-
importCommand.command("openapi").description("Convert a local OpenAPI 3.0/3.1/3.2 document into resource drafts.").argument("<source>", "path to a local .yaml, .yml or .json OpenAPI document").option(
|
|
4934
|
+
importCommand.command("openapi").description("Convert a local OpenAPI 3.0/3.1/3.2 document into resource drafts.").argument("<source>", "path to a local .yaml, .yml or .json OpenAPI document").option(
|
|
4935
|
+
"--output <path>",
|
|
4936
|
+
"output path (default: <source name without extension>.agent-commerce.yaml in the current directory)"
|
|
4937
|
+
).option("--force", "overwrite an existing output file", false).option("--base-url <url>", "backend base URL, overriding the document servers").option(
|
|
4706
4938
|
"--operation <operationId>",
|
|
4707
4939
|
"import only this operation (repeatable)",
|
|
4708
4940
|
collect,
|