@bnbagent/studio-cli 0.0.13-alpha.2 → 0.0.13-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -6
- package/dist/bag.js +1571 -335
- package/dist/{chunk-XCKI45TP.js → chunk-OFAD65VZ.js} +139 -43
- package/dist/{deployCli-HZNZU3CN.js → deployCli-NZAZDPKK.js} +1 -1
- package/package.json +4 -3
- package/recipes/agent/recipe.toml +1 -1
- package/recipes/mpp-buyer/code/{{PKG}}/mppBuyer.ts.tmpl +113 -0
- package/recipes/mpp-buyer/recipe.toml +15 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/dualMain.ts.tmpl +13 -12
- package/recipes/runtimes/agentcore/code/{{PKG}}/mcpMain.ts.tmpl +10 -9
- package/recipes/runtimes/agentcore/code/{{PKG}}/unifiedMain.ts.tmpl +16 -15
- package/recipes/runtimes/agentcore/recipe.toml +1 -1
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +10 -9
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/unifiedMain.ts.tmpl +16 -15
- package/recipes/runtimes/azure-foundry/recipe.toml +1 -1
- package/recipes/x402-buyer/recipe.toml +1 -1
- package/skills/bnbagent-studio.md +5 -4
- package/skills/references/bnbagent-studio-buying-via-8183.md +2 -2
- package/skills/references/bnbagent-studio-buying-via-mpp.md +44 -0
- package/skills/references/bnbagent-studio-scaffolding-agent.md +9 -5
- package/skills/references/bnbagent-studio-selling-via-8183.md +1 -1
- package/skills/references/bnbagent-studio-selling-via-b402.md +1 -1
- package/skills/references/bnbagent-studio-use-aws-agentcore.md +1 -1
- package/skills/references/bnbagent-studio-use-azure-foundry.md +44 -14
- package/skills/references/bnbagent-studio-use-bnb-trial.md +10 -3
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
import { build as esbuildBuild } from "esbuild";
|
|
34
34
|
|
|
35
35
|
// src/cli/utils/protocol.ts
|
|
36
|
-
var SUPPORTED_FACES = ["A2A", "MCP", "X402"];
|
|
36
|
+
var SUPPORTED_FACES = ["A2A", "MCP", "X402", "MPP"];
|
|
37
37
|
var LEGACY_ENTRY_STEMS = {
|
|
38
38
|
unifiedMain: ["main", "foundryMain"],
|
|
39
39
|
mcpMain: [],
|
|
@@ -57,7 +57,7 @@ function normalizeProtocolFaces(value) {
|
|
|
57
57
|
const face = item.trim().toUpperCase();
|
|
58
58
|
if (!SUPPORTED_FACES.includes(face)) {
|
|
59
59
|
throw new Error(
|
|
60
|
-
`unsupported protocol face '${item}'; expected A2A, MCP, or
|
|
60
|
+
`unsupported protocol face '${item}'; expected A2A, MCP, X402, or MPP`
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
if (seen.has(face)) {
|
|
@@ -65,6 +65,11 @@ function normalizeProtocolFaces(value) {
|
|
|
65
65
|
}
|
|
66
66
|
seen.add(face);
|
|
67
67
|
}
|
|
68
|
+
if (seen.has("X402") && seen.has("MPP")) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
"X402 and MPP are alternative B402 payment protocols; select exactly one"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
68
73
|
return SUPPORTED_FACES.filter((face) => seen.has(face));
|
|
69
74
|
}
|
|
70
75
|
function stackFaces(stack, payments = {}) {
|
|
@@ -77,7 +82,7 @@ function stackFaces(stack, payments = {}) {
|
|
|
77
82
|
const rawProtocol = typeof stack.protocol === "string" ? stack.protocol.trim().toUpperCase() : "A2A";
|
|
78
83
|
const protocol = rawProtocol === "MCP" || rawProtocol === "A2A" ? rawProtocol : "A2A";
|
|
79
84
|
const faces = [protocol];
|
|
80
|
-
if (table(payments.x402_seller).enabled === true) {
|
|
85
|
+
if (table(payments.b402_seller).enabled === true || table(payments.x402_seller).enabled === true) {
|
|
81
86
|
faces.push("X402");
|
|
82
87
|
}
|
|
83
88
|
return SUPPORTED_FACES.filter((face) => faces.includes(face));
|
|
@@ -91,6 +96,12 @@ function hasMcpFace(faces) {
|
|
|
91
96
|
function hasX402Face(faces) {
|
|
92
97
|
return faces.includes("X402");
|
|
93
98
|
}
|
|
99
|
+
function hasMppFace(faces) {
|
|
100
|
+
return faces.includes("MPP");
|
|
101
|
+
}
|
|
102
|
+
function hasB402PaymentFace(faces) {
|
|
103
|
+
return hasX402Face(faces) || hasMppFace(faces);
|
|
104
|
+
}
|
|
94
105
|
function nativeProtocolOf(faces) {
|
|
95
106
|
if (hasA2aFace(faces)) {
|
|
96
107
|
return "A2A";
|
|
@@ -98,7 +109,7 @@ function nativeProtocolOf(faces) {
|
|
|
98
109
|
if (hasMcpFace(faces)) {
|
|
99
110
|
return "MCP";
|
|
100
111
|
}
|
|
101
|
-
if (
|
|
112
|
+
if (hasB402PaymentFace(faces)) {
|
|
102
113
|
return "A2A";
|
|
103
114
|
}
|
|
104
115
|
throw new Error("at least one protocol face is required");
|
|
@@ -144,7 +155,11 @@ async function buildZip(agentDir, outZip, opts = {}) {
|
|
|
144
155
|
fs.mkdirSync(buildRoot, { recursive: true });
|
|
145
156
|
try {
|
|
146
157
|
const outfile = path.join(buildRoot, entrypoint);
|
|
147
|
-
|
|
158
|
+
if (opts.bundle) {
|
|
159
|
+
await opts.bundle(entry, outfile);
|
|
160
|
+
} else {
|
|
161
|
+
await bundleEntry(entry, outfile, opts.managedPlatform === true);
|
|
162
|
+
}
|
|
148
163
|
stageManifest(agentDir, buildRoot, entrypoint);
|
|
149
164
|
stageStudioToml(agentDir, buildRoot);
|
|
150
165
|
await stageAltanaSdk(agentDir, buildRoot, opts.bundle ?? bundleEntry);
|
|
@@ -202,13 +217,20 @@ function bundleFailure(exc) {
|
|
|
202
217
|
${msg.slice(0, 1e3)}`
|
|
203
218
|
);
|
|
204
219
|
}
|
|
205
|
-
async function bundleEntry(entry, outfile) {
|
|
220
|
+
async function bundleEntry(entry, outfile, managedPlatform = false) {
|
|
206
221
|
try {
|
|
207
222
|
await esbuildBuild({
|
|
208
223
|
...BUNDLE_OPTIONS,
|
|
209
224
|
entryPoints: [entry],
|
|
210
225
|
outfile,
|
|
211
|
-
minify: true
|
|
226
|
+
minify: true,
|
|
227
|
+
...managedPlatform ? {
|
|
228
|
+
external: [
|
|
229
|
+
"@aws-sdk/client-s3",
|
|
230
|
+
"@azure/identity",
|
|
231
|
+
"@azure/storage-blob"
|
|
232
|
+
]
|
|
233
|
+
} : {}
|
|
212
234
|
});
|
|
213
235
|
} catch (exc) {
|
|
214
236
|
throw bundleFailure(exc);
|
|
@@ -497,15 +519,28 @@ function isB402TestnetBaseUrl(value) {
|
|
|
497
519
|
return /sandbox|test/.test(value.toLowerCase());
|
|
498
520
|
}
|
|
499
521
|
function x402SellerUsesB402(cfg) {
|
|
500
|
-
const
|
|
501
|
-
const seller = table2(payments.x402_seller);
|
|
522
|
+
const seller = b402SellerConfig(cfg);
|
|
502
523
|
return seller.enabled === true && x402SellerPricingState(seller).kind === "paid";
|
|
503
524
|
}
|
|
504
525
|
function x402SellerIsFree(cfg) {
|
|
505
|
-
const
|
|
506
|
-
const seller = table2(payments.x402_seller);
|
|
526
|
+
const seller = b402SellerConfig(cfg);
|
|
507
527
|
return seller.enabled === true && x402SellerPricingState(seller).kind === "free";
|
|
508
528
|
}
|
|
529
|
+
function b402SellerConfig(cfg) {
|
|
530
|
+
const payments = table2(cfg.payments);
|
|
531
|
+
const canonical = table2(payments.b402_seller);
|
|
532
|
+
return Object.keys(canonical).length > 0 ? canonical : table2(payments.x402_seller);
|
|
533
|
+
}
|
|
534
|
+
function b402PaymentProtocol(cfg) {
|
|
535
|
+
const stack = table2(cfg.stack);
|
|
536
|
+
const faces = Array.isArray(stack.protocols) ? stack.protocols.map((face) => String(face).trim().toUpperCase()) : [];
|
|
537
|
+
if (faces.includes("X402") && faces.includes("MPP")) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
"X402 and MPP are alternative B402 payment protocols; select exactly one."
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return faces.includes("MPP") ? "mpp" : "x402";
|
|
543
|
+
}
|
|
509
544
|
function table2(value) {
|
|
510
545
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
511
546
|
}
|
|
@@ -756,8 +791,8 @@ function packageRoot() {
|
|
|
756
791
|
}
|
|
757
792
|
}
|
|
758
793
|
function studioCliVersion() {
|
|
759
|
-
if ("0.0.13-alpha.
|
|
760
|
-
return "0.0.13-alpha.
|
|
794
|
+
if ("0.0.13-alpha.4") {
|
|
795
|
+
return "0.0.13-alpha.4";
|
|
761
796
|
}
|
|
762
797
|
const file = path3.join(packageRoot(), "package.json");
|
|
763
798
|
const pkg = JSON.parse(fs3.readFileSync(file, "utf-8"));
|
|
@@ -859,6 +894,7 @@ var B402_RUNTIME_KEYS = [
|
|
|
859
894
|
"B402_PRIVATE_KEY",
|
|
860
895
|
"B402_PRIVATE_KEY_B64"
|
|
861
896
|
];
|
|
897
|
+
var MPP_RUNTIME_KEYS = ["MPP_SECRET_KEY", "MPP_REALM"];
|
|
862
898
|
var X402_CAPABLE_RUNTIMES = /* @__PURE__ */ new Set([
|
|
863
899
|
"agentcore",
|
|
864
900
|
"azure-foundry"
|
|
@@ -867,7 +903,9 @@ function commerceRails(cfg) {
|
|
|
867
903
|
const payments = table3(cfg.payments);
|
|
868
904
|
return {
|
|
869
905
|
erc8183: isTable(payments.erc8183),
|
|
870
|
-
|
|
906
|
+
// Historical property name retained for callers; this means the common
|
|
907
|
+
// B402 seller rail, whether its public adapter is x402 or MPP.
|
|
908
|
+
x402: b402SellerConfig(cfg).enabled === true
|
|
871
909
|
};
|
|
872
910
|
}
|
|
873
911
|
function b402Credentials(agentRoot) {
|
|
@@ -890,6 +928,22 @@ function b402Credentials(agentRoot) {
|
|
|
890
928
|
baseUrl: values.get("B402_BASE_URL") ?? null
|
|
891
929
|
};
|
|
892
930
|
}
|
|
931
|
+
function mppCredentials(agentRoot) {
|
|
932
|
+
const envPath = envLocalPath(agentRoot);
|
|
933
|
+
const secretKey = process.env.MPP_SECRET_KEY || getEnvVar(envPath, "MPP_SECRET_KEY");
|
|
934
|
+
const realm = process.env.MPP_REALM || getEnvVar(envPath, "MPP_REALM");
|
|
935
|
+
const secretKeyPresent = Boolean(secretKey);
|
|
936
|
+
const secretKeyValid = Boolean(
|
|
937
|
+
secretKey && Buffer.byteLength(secretKey, "utf8") >= 32
|
|
938
|
+
);
|
|
939
|
+
const realmPresent = Boolean(realm);
|
|
940
|
+
return {
|
|
941
|
+
secretKeyPresent,
|
|
942
|
+
secretKeyValid,
|
|
943
|
+
realmPresent,
|
|
944
|
+
complete: secretKeyValid && realmPresent
|
|
945
|
+
};
|
|
946
|
+
}
|
|
893
947
|
function loadDeployConfig(root) {
|
|
894
948
|
const agentRoot = findSubProjectRoot("agent", root) ?? root;
|
|
895
949
|
try {
|
|
@@ -912,20 +966,22 @@ function deploymentStatus(priceMode, credentialsComplete, runtime, destination)
|
|
|
912
966
|
function x402DeploymentSnapshot(root, destination) {
|
|
913
967
|
const { agentRoot, cfg } = loadDeployConfig(root);
|
|
914
968
|
if (!commerceRails(cfg).x402) return null;
|
|
915
|
-
const seller =
|
|
969
|
+
const seller = b402SellerConfig(cfg);
|
|
916
970
|
const pricing = x402SellerPricingState(seller);
|
|
917
971
|
const credentials = b402Credentials(agentRoot);
|
|
972
|
+
const protocol = b402PaymentProtocol(cfg);
|
|
973
|
+
const credentialsComplete = credentials.complete && (protocol !== "mpp" || mppCredentials(agentRoot).complete);
|
|
918
974
|
const priceMode = pricing.kind;
|
|
919
975
|
const runtime = String(table3(cfg.stack).runtime ?? "agentcore");
|
|
920
976
|
return {
|
|
921
977
|
status: deploymentStatus(
|
|
922
978
|
priceMode,
|
|
923
|
-
|
|
979
|
+
credentialsComplete,
|
|
924
980
|
runtime,
|
|
925
981
|
destination
|
|
926
982
|
),
|
|
927
983
|
priceMode,
|
|
928
|
-
credentialsComplete
|
|
984
|
+
credentialsComplete
|
|
929
985
|
};
|
|
930
986
|
}
|
|
931
987
|
function x402DeploySummary(root, destination, publicUrl) {
|
|
@@ -937,54 +993,62 @@ function x402DeploySummary(root, destination, publicUrl) {
|
|
|
937
993
|
snapshot,
|
|
938
994
|
runtime,
|
|
939
995
|
destination,
|
|
940
|
-
publicUrl
|
|
996
|
+
publicUrl,
|
|
997
|
+
b402PaymentProtocol(cfg)
|
|
941
998
|
);
|
|
942
999
|
}
|
|
943
|
-
function x402DeploySummaryFromSnapshot(snapshot, runtime, destination, publicUrl) {
|
|
944
|
-
const
|
|
1000
|
+
function x402DeploySummaryFromSnapshot(snapshot, runtime, destination, publicUrl, protocol = "x402") {
|
|
1001
|
+
const label = protocol === "mpp" ? "MPP" : "x402";
|
|
1002
|
+
const path7 = `/${protocol}`;
|
|
1003
|
+
const activation = `${B402_PAID_ONBOARDING_GUIDANCE} Then run the bnbagent-studio-selling-via-b402 skill, fill the four B402_* variables${protocol === "mpp" ? " plus MPP_SECRET_KEY and MPP_REALM in .studio/.env.local, inject a durable atomic replayStore for production," : " in .studio/.env.local,"} and redeploy.`;
|
|
945
1004
|
if (destination !== "platform" && !X402_CAPABLE_RUNTIMES.has(runtime)) {
|
|
946
|
-
return
|
|
1005
|
+
return `${protocol} rail is FORCED DORMANT: the ${runtime} runtime has no ${path7} path. Deploy to AgentCore or Azure Foundry (managed platform or self-hosted) to activate the rail.`;
|
|
947
1006
|
}
|
|
948
1007
|
const azure = runtime === "azure-foundry";
|
|
949
1008
|
const runtimeLabel = azure ? "Azure Foundry" : "AgentCore";
|
|
950
|
-
const tunnelLine = azure ?
|
|
951
|
-
const gatewayDocLine = azure ? `Gateway example: https://unpkg.com/@bnbagent/studio-cli@${studioCliVersion()}/skills/references/bnbagent-studio-use-azure-foundry.md#x402-external-gateway` : "Gateway example: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md";
|
|
1009
|
+
const tunnelLine = azure ? `The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"${path7}","headers":{...},"body":"<base64>"}, inside an Entra-authenticated Foundry invocation (POST \u2026/agents/<name>/endpoint/protocols/invocations with an https://ai.azure.com bearer). Limits: 1 MiB request, 5 MiB response, no streaming.` : `The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"${path7}","headers":{...},"body":"<base64>"}, inside an authenticated AgentCore invocation. The default Bag self-deploy uses Cognito OAuth over HTTPS; IAM runtimes may use SDK/SigV4. Limits: 1 MiB request, 5 MiB response, no streaming.`;
|
|
1010
|
+
const gatewayDocLine = azure ? protocol === "mpp" ? "Gateway contract: docs/guides/mpp-b402-selling.md" : `Gateway example: https://unpkg.com/@bnbagent/studio-cli@${studioCliVersion()}/skills/references/bnbagent-studio-use-azure-foundry.md#x402-external-gateway` : protocol === "mpp" ? "Gateway contract: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/mpp-b402-selling.md" : "Gateway example: https://github.com/bnb-chain/bnbagent-studio/blob/main/docs/guides/self-hosted-x402-gateway.md";
|
|
952
1011
|
if (snapshot.priceMode === "invalid") {
|
|
953
|
-
return
|
|
1012
|
+
return `${protocol} rail is DORMANT: price_usd is invalid; run \`bag doctor\`.`;
|
|
954
1013
|
}
|
|
955
1014
|
if (snapshot.priceMode === "free") {
|
|
956
1015
|
if (destination !== "platform") {
|
|
957
1016
|
return [
|
|
958
|
-
|
|
1017
|
+
`${protocol} rail is ACTIVE in FREE mode (self-hosted ${runtimeLabel}).`,
|
|
959
1018
|
"B402 verify/settle is bypassed; no credentials, token payment, or settlement audit is used.",
|
|
960
|
-
|
|
1019
|
+
`This target has no anonymous public URL; expose ${path7} with your own HTTP front.`,
|
|
961
1020
|
tunnelLine,
|
|
962
|
-
`You may invoke ${runtimeLabel} with envelope-v1 directly, but that is not a standard public
|
|
1021
|
+
`You may invoke ${runtimeLabel} with envelope-v1 directly, but that is not a standard public ${label} HTTP endpoint.`,
|
|
963
1022
|
gatewayDocLine
|
|
964
1023
|
].join("\n");
|
|
965
1024
|
}
|
|
966
1025
|
const url2 = publicUrl ?? "<available after the platform agentId is assigned>";
|
|
967
1026
|
return [
|
|
968
|
-
|
|
1027
|
+
`${protocol} rail is ACTIVE in FREE mode.`,
|
|
969
1028
|
`Anonymous FREE URL: ${url2}`,
|
|
970
1029
|
"B402 verify/settle is bypassed; no credentials, token payment, or settlement audit is used."
|
|
971
1030
|
].join("\n");
|
|
972
1031
|
}
|
|
973
1032
|
if (!snapshot.credentialsComplete) {
|
|
974
|
-
return
|
|
1033
|
+
return `${protocol} rail is DORMANT. To activate: ${activation}`;
|
|
975
1034
|
}
|
|
976
1035
|
const settlement = [
|
|
977
1036
|
"B402 settlement can add significant latency; buyer timeout must be at least 120 s.",
|
|
978
1037
|
"Settlement happens before work. If work later fails, the payment is retained and is not refunded automatically.",
|
|
979
|
-
"
|
|
1038
|
+
"B402 classifies an asynchronous pending response or other ambiguous settlement outcome as unknown; reconcile it and do not replay the paid request."
|
|
980
1039
|
];
|
|
1040
|
+
if (protocol === "mpp") {
|
|
1041
|
+
settlement.push(
|
|
1042
|
+
"MPP credentials are HMAC-bound; production replay protection requires a durable atomic replayStore shared by every runtime instance."
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
981
1045
|
if (destination !== "platform") {
|
|
982
1046
|
const egressLine = azure ? "PAID also needs fixed facilitator egress. Host the gateway on Azure Container Apps in a workload-profiles environment with a VNet + NAT Gateway static IP and allowlist that IP with B402 \u2014 no relay needed." : "PAID also needs fixed facilitator egress. Operate a restricted B402 Relay on a fixed-IP host such as a user-managed VPS, or use AgentCore VPC mode with a private subnet, NAT Gateway, and Elastic IP.";
|
|
983
1047
|
return [
|
|
984
|
-
|
|
985
|
-
|
|
1048
|
+
`${protocol} rail is ACTIVE (self-hosted ${runtimeLabel}).`,
|
|
1049
|
+
`This target has no anonymous public URL; expose ${path7} with your own HTTP front.`,
|
|
986
1050
|
tunnelLine,
|
|
987
|
-
`You may invoke ${runtimeLabel} with envelope-v1 directly, but the caller must unpack the inner HTTP response and the result is not a standard public
|
|
1051
|
+
`You may invoke ${runtimeLabel} with envelope-v1 directly, but the caller must unpack the inner HTTP response and the result is not a standard public ${label} endpoint.`,
|
|
988
1052
|
egressLine,
|
|
989
1053
|
gatewayDocLine.replace("Gateway example:", "Gateway and Relay examples:"),
|
|
990
1054
|
...settlement
|
|
@@ -992,9 +1056,9 @@ function x402DeploySummaryFromSnapshot(snapshot, runtime, destination, publicUrl
|
|
|
992
1056
|
}
|
|
993
1057
|
const url = publicUrl ?? "<available after the platform agentId is assigned>";
|
|
994
1058
|
return [
|
|
995
|
-
|
|
1059
|
+
`${protocol} rail is ACTIVE.`,
|
|
996
1060
|
`Anonymous paid URL: ${url}`,
|
|
997
|
-
|
|
1061
|
+
`The managed platform supplies the ${label} HTTP Gateway and the fixed-egress B402 Relay; no user-managed gateway, proxy, or VPC is required.`,
|
|
998
1062
|
`Payment policy and RSA signing remain inside the ${runtimeLabel} seller. The B402 Relay only forwards the signed request to its fixed facilitator upstream.`,
|
|
999
1063
|
...settlement
|
|
1000
1064
|
].join("\n");
|
|
@@ -1007,7 +1071,7 @@ function isTable(value) {
|
|
|
1007
1071
|
}
|
|
1008
1072
|
|
|
1009
1073
|
// src/cli/_deploy/deployCli.ts
|
|
1010
|
-
var DEPLOY_CLI_VERSION = "0.5.
|
|
1074
|
+
var DEPLOY_CLI_VERSION = "0.5.15";
|
|
1011
1075
|
var DEPLOY_CLI_PACKAGE = `@bnbagent/deploy-cli@${DEPLOY_CLI_VERSION}`;
|
|
1012
1076
|
var BNB_PLATFORM_API_URL = "https://bnbagent-api.bnbchain.world";
|
|
1013
1077
|
var BNB_PLATFORM_API_URL_ENV = "BNBAGENT_API_URL";
|
|
@@ -1034,11 +1098,14 @@ var RESERVED_AGENTCORE_KEYS = ["environmentVariables", "secretName"];
|
|
|
1034
1098
|
var FOUNDRY_PASSTHROUGH_KEYS = /* @__PURE__ */ new Set([
|
|
1035
1099
|
"account",
|
|
1036
1100
|
"cpu",
|
|
1101
|
+
"incomingA2a",
|
|
1102
|
+
"agentCard",
|
|
1037
1103
|
"location",
|
|
1038
1104
|
"memory",
|
|
1039
1105
|
"project",
|
|
1040
1106
|
"projectEndpoint",
|
|
1041
1107
|
"protocol",
|
|
1108
|
+
"rawInvocations",
|
|
1042
1109
|
"registry",
|
|
1043
1110
|
"subscriptionId"
|
|
1044
1111
|
]);
|
|
@@ -1151,7 +1218,27 @@ function buildDeploySpec(root, opts) {
|
|
|
1151
1218
|
}
|
|
1152
1219
|
Object.assign(foundry, passthrough);
|
|
1153
1220
|
if (!("protocol" in foundry)) {
|
|
1154
|
-
foundry.protocol = "
|
|
1221
|
+
foundry.protocol = "responses";
|
|
1222
|
+
}
|
|
1223
|
+
if (foundry.protocol === "responses" && !("incomingA2a" in foundry)) {
|
|
1224
|
+
foundry.incomingA2a = true;
|
|
1225
|
+
}
|
|
1226
|
+
if (foundry.protocol === "responses" && hasB402PaymentFace(faces)) {
|
|
1227
|
+
foundry.rawInvocations = true;
|
|
1228
|
+
}
|
|
1229
|
+
if (foundry.incomingA2a === true && !("agentCard" in foundry)) {
|
|
1230
|
+
foundry.agentCard = {
|
|
1231
|
+
name,
|
|
1232
|
+
description: `BNB Agent Studio seller ${name}`,
|
|
1233
|
+
version: "1.0",
|
|
1234
|
+
skills: [
|
|
1235
|
+
{
|
|
1236
|
+
id: "erc8183-seller",
|
|
1237
|
+
name: "ERC-8183 seller",
|
|
1238
|
+
description: "Negotiate and fulfil ERC-8183 jobs."
|
|
1239
|
+
}
|
|
1240
|
+
]
|
|
1241
|
+
};
|
|
1155
1242
|
}
|
|
1156
1243
|
const azure = tableOf(studio, "azure");
|
|
1157
1244
|
for (const [source, dest] of [
|
|
@@ -1182,6 +1269,9 @@ function buildDeploySpec(root, opts) {
|
|
|
1182
1269
|
}
|
|
1183
1270
|
deploy.protocol = protocol;
|
|
1184
1271
|
const stackRuntime = String(stack.runtime ?? "agentcore");
|
|
1272
|
+
doc.deliverables = {
|
|
1273
|
+
storage: stackRuntime === "azure-foundry" ? "azure-blob" : "s3"
|
|
1274
|
+
};
|
|
1185
1275
|
doc.bnb = stackRuntime === "azure-foundry" ? {
|
|
1186
1276
|
apiUrl: bnbPlatformApiUrl(),
|
|
1187
1277
|
backend: "azure",
|
|
@@ -1191,9 +1281,11 @@ function buildDeploySpec(root, opts) {
|
|
|
1191
1281
|
if (opts.inlineSecrets) {
|
|
1192
1282
|
Object.assign(env, opts.inlineSecrets);
|
|
1193
1283
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1284
|
+
const paymentProtocol = b402PaymentProtocol(studio);
|
|
1285
|
+
const hasSelectedPaymentFace = paymentProtocol === "mpp" ? hasMppFace(faces) : hasX402Face(faces);
|
|
1286
|
+
if (opts.target === "bnb/trial" && (!hasProtocolsArray || hasSelectedPaymentFace) && commerceRails(studio).x402 && (x402SellerIsFree(studio) || b402Credentials(agentRoot).complete && (paymentProtocol !== "mpp" || mppCredentials(agentRoot).complete))) {
|
|
1287
|
+
doc[paymentProtocol] = {
|
|
1288
|
+
publicPaths: [paymentProtocol === "mpp" ? "/mpp" : "/x402"],
|
|
1197
1289
|
tunnel: "http-envelope-v1"
|
|
1198
1290
|
};
|
|
1199
1291
|
}
|
|
@@ -1203,7 +1295,7 @@ function buildDeploySpec(root, opts) {
|
|
|
1203
1295
|
tunnel: "http-envelope-v1"
|
|
1204
1296
|
};
|
|
1205
1297
|
}
|
|
1206
|
-
if (opts.target === "bnb/trial" && hasProtocolsArray && faces.length === 1 &&
|
|
1298
|
+
if (opts.target === "bnb/trial" && hasProtocolsArray && faces.length === 1 && hasB402PaymentFace(faces)) {
|
|
1207
1299
|
doc.suppressProtocolFace = true;
|
|
1208
1300
|
}
|
|
1209
1301
|
doc.env = env;
|
|
@@ -1233,7 +1325,8 @@ async function withDeployFiles(root, opts, fn) {
|
|
|
1233
1325
|
const faces = stackFaces(stack, tableOf(studio, "payments"));
|
|
1234
1326
|
const outZip = path6.join(scratch, "code.zip");
|
|
1235
1327
|
const built = await (opts.buildZipFn ?? buildZip)(agentRoot, outZip, {
|
|
1236
|
-
protocols: faces
|
|
1328
|
+
protocols: faces,
|
|
1329
|
+
managedPlatform: opts.target === "bnb/trial"
|
|
1237
1330
|
});
|
|
1238
1331
|
zip = { path: outZip, entrypoint: built.entrypoint };
|
|
1239
1332
|
}
|
|
@@ -1412,13 +1505,14 @@ export {
|
|
|
1412
1505
|
hasA2aFace,
|
|
1413
1506
|
hasMcpFace,
|
|
1414
1507
|
hasX402Face,
|
|
1508
|
+
hasMppFace,
|
|
1509
|
+
hasB402PaymentFace,
|
|
1415
1510
|
nativeProtocolOf,
|
|
1416
1511
|
entryStemOf,
|
|
1417
1512
|
devPortOf,
|
|
1418
1513
|
recipeModeOf,
|
|
1419
1514
|
dryRunBundle,
|
|
1420
1515
|
DEFAULT_B402_PRICE_USD,
|
|
1421
|
-
DEFAULT_B402_TESTNET_BASE_URL,
|
|
1422
1516
|
B402_DEVELOPER_ACCOUNT_URL,
|
|
1423
1517
|
B402_PAID_ONBOARDING_GUIDANCE,
|
|
1424
1518
|
x402SellerPricingState,
|
|
@@ -1426,6 +1520,7 @@ export {
|
|
|
1426
1520
|
isB402TestnetBaseUrl,
|
|
1427
1521
|
x402SellerUsesB402,
|
|
1428
1522
|
x402SellerIsFree,
|
|
1523
|
+
b402PaymentProtocol,
|
|
1429
1524
|
whichBin,
|
|
1430
1525
|
agentcoreFlavor,
|
|
1431
1526
|
checkBunx,
|
|
@@ -1438,6 +1533,7 @@ export {
|
|
|
1438
1533
|
studioCliVersion,
|
|
1439
1534
|
pnpmVersion,
|
|
1440
1535
|
B402_RUNTIME_KEYS,
|
|
1536
|
+
MPP_RUNTIME_KEYS,
|
|
1441
1537
|
X402_CAPABLE_RUNTIMES,
|
|
1442
1538
|
commerceRails,
|
|
1443
1539
|
b402Credentials,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bnbagent/studio-cli",
|
|
3
|
-
"version": "0.0.13-alpha.
|
|
3
|
+
"version": "0.0.13-alpha.4",
|
|
4
4
|
"description": "Skills-first toolkit and bag CLI for BNB Chain seller agents: ERC-8004 identity, ERC-8183 escrowed commerce, and x402 payments.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bnb-chain",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"bag": "./dist/bag.js"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@bnbagent/sdk": "0.5.
|
|
45
|
+
"@bnbagent/sdk": "0.5.3",
|
|
46
46
|
"ai": "^7.0.29",
|
|
47
47
|
"archiver": "^8.0.0",
|
|
48
48
|
"commander": "^15.0.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"tar": "^7.4.0",
|
|
55
55
|
"viem": "^2.54.0",
|
|
56
56
|
"yaml": "^2.9.0",
|
|
57
|
-
"@bnbagent/studio-runtime": "0.0.13-alpha.
|
|
57
|
+
"@bnbagent/studio-runtime": "0.0.13-alpha.4"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@a2a-js/sdk": "^0.3.14",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "tsup",
|
|
76
|
+
"pretest": "pnpm --filter @bnbagent/studio-runtime build",
|
|
76
77
|
"test": "vitest run",
|
|
77
78
|
"lint": "biome check src tests",
|
|
78
79
|
"typecheck": "tsc --noEmit",
|
|
@@ -11,7 +11,7 @@ node = [
|
|
|
11
11
|
# neutral. The agent project's serving deps (@a2a-js/sdk / MCP SDK / ai)
|
|
12
12
|
# come from the runtimes/<R>/ recipe selected at `bag init` time.
|
|
13
13
|
"@bnbagent/studio-runtime",
|
|
14
|
-
"@bnbagent/sdk@0.5.
|
|
14
|
+
"@bnbagent/sdk@0.5.3",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
[env]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native MPP+B402 buyer tools — emitted by `bag recipe code mpp-buyer`.
|
|
3
|
+
*
|
|
4
|
+
* Trust a merchant first with `bag mpp trust <url>`. The runtime enforces
|
|
5
|
+
* exact host, realm, recipient, network, asset, per-call, and daily-budget
|
|
6
|
+
* constraints before signing. P0 supports evm-local + EIP-3009 only.
|
|
7
|
+
* x402 is a separate alternative protocol and is never used as fallback.
|
|
8
|
+
*
|
|
9
|
+
* Wire into an agent:
|
|
10
|
+
*
|
|
11
|
+
* import { MPP_BUYER_TOOLS } from "./mppBuyer.js";
|
|
12
|
+
* tools: { ...LLM_READ_TOOLS, ...MPP_BUYER_TOOLS },
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { loadStudioToml } from "@bnbagent/studio-runtime/config";
|
|
16
|
+
import {
|
|
17
|
+
MppBuyerPolicy,
|
|
18
|
+
MppError,
|
|
19
|
+
fetchMppWithPayment,
|
|
20
|
+
quoteMppUrl,
|
|
21
|
+
} from "@bnbagent/studio-runtime/mpp";
|
|
22
|
+
import { getWallet } from "@bnbagent/studio-runtime/wallet";
|
|
23
|
+
import { tool, type ToolSet } from "ai";
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
|
|
26
|
+
function policy(): MppBuyerPolicy {
|
|
27
|
+
return MppBuyerPolicy.fromToml(loadStudioToml());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function buyWithMpp(
|
|
31
|
+
url: string,
|
|
32
|
+
maxUsd: number,
|
|
33
|
+
method = "GET",
|
|
34
|
+
): Promise<Record<string, unknown>> {
|
|
35
|
+
const buyerPolicy = policy();
|
|
36
|
+
try {
|
|
37
|
+
const result = await fetchMppWithPayment(url, {
|
|
38
|
+
wallet: getWallet(),
|
|
39
|
+
maxUsd: Math.min(maxUsd, buyerPolicy.maxPerRequestUsd),
|
|
40
|
+
method,
|
|
41
|
+
policy: buyerPolicy,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
ok:
|
|
45
|
+
result.statusCode >= 200 &&
|
|
46
|
+
result.statusCode < 300 &&
|
|
47
|
+
result.paymentOutcome !== "unknown",
|
|
48
|
+
status: result.statusCode,
|
|
49
|
+
json: result.json,
|
|
50
|
+
paid_usd: result.paidUsd,
|
|
51
|
+
payment_outcome: result.paymentOutcome,
|
|
52
|
+
payment_reference: result.receipt?.reference ?? null,
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof MppError) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
error: error.constructor.name,
|
|
59
|
+
message: error.message,
|
|
60
|
+
payment_outcome:
|
|
61
|
+
error.constructor.name === "MppPaymentOutcomeUnknownError"
|
|
62
|
+
? "unknown"
|
|
63
|
+
: "not-dispatched",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function quoteMpp(
|
|
71
|
+
url: string,
|
|
72
|
+
method = "GET",
|
|
73
|
+
): Promise<Record<string, unknown>> {
|
|
74
|
+
const buyerPolicy = policy();
|
|
75
|
+
try {
|
|
76
|
+
const result = await quoteMppUrl(url, {
|
|
77
|
+
method,
|
|
78
|
+
policy: buyerPolicy,
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
ok: result.challenge !== null,
|
|
82
|
+
status: result.statusCode,
|
|
83
|
+
challenge: result.challenge,
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof MppError) {
|
|
87
|
+
return { ok: false, error: error.constructor.name, message: error.message };
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const MPP_BUYER_TOOLS: ToolSet = {
|
|
94
|
+
buy_with_mpp: tool({
|
|
95
|
+
description:
|
|
96
|
+
"Pay a trusted native MPP+B402 endpoint with EIP-3009. max_usd is a refusal threshold; Studio merchant and daily caps remain authoritative.",
|
|
97
|
+
inputSchema: z.object({
|
|
98
|
+
url: z.string(),
|
|
99
|
+
max_usd: z.number().positive(),
|
|
100
|
+
method: z.string().optional(),
|
|
101
|
+
}),
|
|
102
|
+
execute: async ({ url, max_usd, method }) =>
|
|
103
|
+
buyWithMpp(url, max_usd, method ?? "GET"),
|
|
104
|
+
}),
|
|
105
|
+
quote_mpp: tool({
|
|
106
|
+
description: "Probe a trusted native MPP+B402 endpoint without paying.",
|
|
107
|
+
inputSchema: z.object({
|
|
108
|
+
url: z.string(),
|
|
109
|
+
method: z.string().optional(),
|
|
110
|
+
}),
|
|
111
|
+
execute: async ({ url, method }) => quoteMpp(url, method ?? "GET"),
|
|
112
|
+
}),
|
|
113
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[recipe]
|
|
2
|
+
name = "mpp-buyer"
|
|
3
|
+
description = "Native MPP+B402 buyer tools guarded by trusted merchant recipient/realm pins and per-call/daily caps."
|
|
4
|
+
status = "v0.0.x"
|
|
5
|
+
|
|
6
|
+
[dependencies]
|
|
7
|
+
node = [
|
|
8
|
+
"@bnbagent/studio-runtime",
|
|
9
|
+
"@bnbagent/sdk@0.5.3",
|
|
10
|
+
"ai@^7.0.29",
|
|
11
|
+
"zod@^3.25.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[env]
|
|
15
|
+
WALLET_PASSWORD = "evm-local keystore password used for EIP-3009 signing"
|
|
@@ -67,11 +67,11 @@ import {
|
|
|
67
67
|
} from "@bnbagent/studio-runtime/wallet";
|
|
68
68
|
import {
|
|
69
69
|
createEnvelopeMiddleware,
|
|
70
|
-
|
|
71
|
-
type
|
|
72
|
-
type
|
|
73
|
-
|
|
74
|
-
} from "@bnbagent/studio-runtime/
|
|
70
|
+
b402SellPath,
|
|
71
|
+
type B402HttpRequest,
|
|
72
|
+
type B402RunWork,
|
|
73
|
+
B402Seller,
|
|
74
|
+
} from "@bnbagent/studio-runtime/b402";
|
|
75
75
|
import { generateText, stepCountIs } from "ai";
|
|
76
76
|
import express from "express";
|
|
77
77
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -236,8 +236,8 @@ function flatQuery(query: Record<string, unknown>): Record<string, string> {
|
|
|
236
236
|
return out;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
function
|
|
240
|
-
return ({ prompt }) => runWork(prompt, { sessionId: "
|
|
239
|
+
function b402Work(runWork: RunWork): B402RunWork {
|
|
240
|
+
return ({ prompt }) => runWork(prompt, { sessionId: "b402" });
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
// ── serving ───────────────────────────────────────────────────────────────────
|
|
@@ -266,6 +266,7 @@ export async function buildDualApp(): Promise<{
|
|
|
266
266
|
|
|
267
267
|
const cfg = loadStudioToml();
|
|
268
268
|
const rails = { erc8183: hasErc8183Rail(cfg) };
|
|
269
|
+
const sellPath = b402SellPath(cfg);
|
|
269
270
|
const port = Number(process.env.AGENT_PORT || "9000");
|
|
270
271
|
const runWork = buildRunWork();
|
|
271
272
|
|
|
@@ -279,13 +280,13 @@ export async function buildDualApp(): Promise<{
|
|
|
279
280
|
commerceSkills: rails.erc8183,
|
|
280
281
|
});
|
|
281
282
|
const agentCard = buildAgentCard({ commerceSkills: rails.erc8183 });
|
|
282
|
-
const seller = await
|
|
283
|
+
const seller = await B402Seller.create({
|
|
283
284
|
cfg,
|
|
284
|
-
runWork:
|
|
285
|
+
runWork: b402Work(runWork),
|
|
285
286
|
walletAddress: getWallet().address,
|
|
286
287
|
resourceUrl: `${
|
|
287
288
|
process.env.AGENTCORE_RUNTIME_URL ?? `http://localhost:${port}`
|
|
288
|
-
}${
|
|
289
|
+
}${sellPath}`,
|
|
289
290
|
});
|
|
290
291
|
|
|
291
292
|
const handler = new DefaultRequestHandler(
|
|
@@ -309,10 +310,10 @@ export async function buildDualApp(): Promise<{
|
|
|
309
310
|
|
|
310
311
|
if (seller.state !== "disabled") {
|
|
311
312
|
app.all(
|
|
312
|
-
|
|
313
|
+
sellPath,
|
|
313
314
|
express.text({ type: "*/*", limit: "1mb" }),
|
|
314
315
|
async (req, res) => {
|
|
315
|
-
const request:
|
|
316
|
+
const request: B402HttpRequest = {
|
|
316
317
|
method: req.method,
|
|
317
318
|
path: req.path,
|
|
318
319
|
query: flatQuery(req.query),
|