@bnbagent/studio-cli 0.0.13-alpha.1 → 0.0.13-alpha.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.
Files changed (26) hide show
  1. package/README.md +34 -6
  2. package/dist/bag.js +1571 -335
  3. package/dist/{chunk-RSM3RMDD.js → chunk-E4C75NSV.js} +139 -43
  4. package/dist/{deployCli-BY3ILBEP.js → deployCli-UOUCOIW5.js} +1 -1
  5. package/package.json +4 -3
  6. package/recipes/agent/recipe.toml +1 -1
  7. package/recipes/mpp-buyer/code/{{PKG}}/mppBuyer.ts.tmpl +113 -0
  8. package/recipes/mpp-buyer/recipe.toml +15 -0
  9. package/recipes/runtimes/agentcore/code/{{PKG}}/dualMain.ts.tmpl +13 -12
  10. package/recipes/runtimes/agentcore/code/{{PKG}}/mcpMain.ts.tmpl +10 -9
  11. package/recipes/runtimes/agentcore/code/{{PKG}}/unifiedMain.ts.tmpl +16 -15
  12. package/recipes/runtimes/agentcore/recipe.toml +1 -1
  13. package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +10 -9
  14. package/recipes/runtimes/azure-foundry/code/{{PKG}}/unifiedMain.ts.tmpl +16 -15
  15. package/recipes/runtimes/azure-foundry/recipe.toml +1 -1
  16. package/recipes/x402-buyer/recipe.toml +1 -1
  17. package/skills/bnbagent-studio.md +5 -4
  18. package/skills/references/bnbagent-studio-buying-via-8183.md +2 -2
  19. package/skills/references/bnbagent-studio-buying-via-mpp.md +44 -0
  20. package/skills/references/bnbagent-studio-scaffolding-agent.md +9 -5
  21. package/skills/references/bnbagent-studio-selling-via-8183.md +1 -1
  22. package/skills/references/bnbagent-studio-selling-via-b402.md +1 -1
  23. package/skills/references/bnbagent-studio-use-aws-agentcore.md +1 -1
  24. package/skills/references/bnbagent-studio-use-azure-foundry.md +44 -14
  25. package/skills/references/bnbagent-studio-use-bnb-trial.md +10 -3
  26. package/skills/references/bnbagent-studio-using-altana-wallet.md +1 -1
@@ -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 X402`
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 (hasX402Face(faces)) {
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
- await (opts.bundle ?? bundleEntry)(entry, outfile);
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 payments = table2(cfg.payments);
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 payments = table2(cfg.payments);
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.1") {
760
- return "0.0.13-alpha.1";
794
+ if ("0.0.13-alpha.3") {
795
+ return "0.0.13-alpha.3";
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
- x402: table3(payments.x402_seller).enabled === true
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 = table3(table3(cfg.payments).x402_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
- credentials.complete,
979
+ credentialsComplete,
924
980
  runtime,
925
981
  destination
926
982
  ),
927
983
  priceMode,
928
- credentialsComplete: credentials.complete
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 activation = `${B402_PAID_ONBOARDING_GUIDANCE} Then run the bnbagent-studio-selling-via-b402 skill, fill the four B402_* variables in .studio/.env.local, and redeploy.`;
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 `x402 rail is FORCED DORMANT: the ${runtime} runtime has no x402 path. Deploy to AgentCore or Azure Foundry (managed platform or self-hosted) to activate the rail.`;
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 ? 'The front wraps each request as envelope-v1 JSON, {"v":1,"method":"POST","path":"/x402","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":"/x402","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.';
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 "x402 rail is DORMANT: price_usd is invalid; run `bag doctor`.";
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
- `x402 rail is ACTIVE in FREE mode (self-hosted ${runtimeLabel}).`,
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
- "This target has no anonymous public URL; expose /x402 with your own HTTP front.",
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 x402 HTTP endpoint.`,
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
- "x402 rail is ACTIVE in FREE mode.",
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 `x402 rail is DORMANT. To activate: ${activation}`;
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
- "Compatibility note: current @bnb-chain/b402@0.1.0 makes one settle call and classifies an asynchronous pending response as outcome unknown; reconcile it and do not replay the paid request."
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
- `x402 rail is ACTIVE (self-hosted ${runtimeLabel}).`,
985
- "This target has no anonymous public URL; expose /x402 with your own HTTP front.",
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 x402 endpoint.`,
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
- "x402 rail is ACTIVE.",
1059
+ `${protocol} rail is ACTIVE.`,
996
1060
  `Anonymous paid URL: ${url}`,
997
- "The managed platform supplies the x402 HTTP Gateway and the fixed-egress B402 Relay; no user-managed gateway, proxy, or VPC is required.",
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.13";
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 = "invocations";
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
- if (opts.target === "bnb/trial" && (!hasProtocolsArray || hasX402Face(faces)) && commerceRails(studio).x402 && (x402SellerIsFree(studio) || b402Credentials(agentRoot).complete)) {
1195
- doc.x402 = {
1196
- publicPaths: ["/x402"],
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 && hasX402Face(faces)) {
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,
@@ -18,7 +18,7 @@ import {
18
18
  runPlatformAccountCommand,
19
19
  trialFromDeployCliJson,
20
20
  withDeployFiles
21
- } from "./chunk-RSM3RMDD.js";
21
+ } from "./chunk-E4C75NSV.js";
22
22
  import "./chunk-RO726HJG.js";
23
23
  export {
24
24
  BNB_PLATFORM_API_URL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bnbagent/studio-cli",
3
- "version": "0.0.13-alpha.1",
3
+ "version": "0.0.13-alpha.3",
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.2-alpha.1",
45
+ "@bnbagent/sdk": "0.5.2",
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.1"
57
+ "@bnbagent/studio-runtime": "0.0.13-alpha.3"
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.2-alpha.1",
14
+ "@bnbagent/sdk@0.5.2",
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.2",
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
- X402_SELL_PATH,
71
- type X402HttpRequest,
72
- type X402RunWork,
73
- X402Seller,
74
- } from "@bnbagent/studio-runtime/x402";
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 x402Work(runWork: RunWork): X402RunWork {
240
- return ({ prompt }) => runWork(prompt, { sessionId: "x402" });
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 X402Seller.create({
283
+ const seller = await B402Seller.create({
283
284
  cfg,
284
- runWork: x402Work(runWork),
285
+ runWork: b402Work(runWork),
285
286
  walletAddress: getWallet().address,
286
287
  resourceUrl: `${
287
288
  process.env.AGENTCORE_RUNTIME_URL ?? `http://localhost:${port}`
288
- }${X402_SELL_PATH}`,
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
- X402_SELL_PATH,
313
+ sellPath,
313
314
  express.text({ type: "*/*", limit: "1mb" }),
314
315
  async (req, res) => {
315
- const request: X402HttpRequest = {
316
+ const request: B402HttpRequest = {
316
317
  method: req.method,
317
318
  path: req.path,
318
319
  query: flatQuery(req.query),