@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
package/dist/bag.js
CHANGED
|
@@ -5,12 +5,13 @@ import {
|
|
|
5
5
|
B402_RUNTIME_KEYS,
|
|
6
6
|
CliExit,
|
|
7
7
|
DEFAULT_B402_PRICE_USD,
|
|
8
|
-
DEFAULT_B402_TESTNET_BASE_URL,
|
|
9
8
|
LEGACY_ENTRY_STEMS,
|
|
9
|
+
MPP_RUNTIME_KEYS,
|
|
10
10
|
X402_CAPABLE_RUNTIMES,
|
|
11
11
|
act,
|
|
12
12
|
agentcoreFlavor,
|
|
13
13
|
b402Credentials,
|
|
14
|
+
b402PaymentProtocol,
|
|
14
15
|
bnbEnv,
|
|
15
16
|
bnbPlatformApiUrl,
|
|
16
17
|
checkBunx,
|
|
@@ -24,7 +25,9 @@ import {
|
|
|
24
25
|
entryStemOf,
|
|
25
26
|
getEnvVar,
|
|
26
27
|
hasA2aFace,
|
|
28
|
+
hasB402PaymentFace,
|
|
27
29
|
hasMcpFace,
|
|
30
|
+
hasMppFace,
|
|
28
31
|
hasX402Face,
|
|
29
32
|
isB402TestnetBaseUrl,
|
|
30
33
|
nativeProtocolOf,
|
|
@@ -52,7 +55,7 @@ import {
|
|
|
52
55
|
x402SellerIsFree,
|
|
53
56
|
x402SellerPricingState,
|
|
54
57
|
x402SellerUsesB402
|
|
55
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-OFAD65VZ.js";
|
|
56
59
|
import {
|
|
57
60
|
TWAK_CLI_MIN_VERSION,
|
|
58
61
|
TWAK_CLI_VERSION,
|
|
@@ -230,7 +233,7 @@ import {
|
|
|
230
233
|
var CAMPAIGN_DOC_URL = "https://www.bnbchain.org/en/blog/bnb-agent-studio-is-live-on-bnb-chain-ai-agents-from-one-prompt";
|
|
231
234
|
var CAMPAIGN_CHECK_TIMEOUT_MS = 6e3;
|
|
232
235
|
async function fetchCampaignActive() {
|
|
233
|
-
const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-
|
|
236
|
+
const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-NZAZDPKK.js");
|
|
234
237
|
const controller = new AbortController();
|
|
235
238
|
const timer = setTimeout(() => controller.abort(), CAMPAIGN_CHECK_TIMEOUT_MS);
|
|
236
239
|
try {
|
|
@@ -1862,11 +1865,15 @@ ${result.stderr.trim()}` : ""}`;
|
|
|
1862
1865
|
}
|
|
1863
1866
|
return vendored;
|
|
1864
1867
|
}
|
|
1865
|
-
async function provisionInstall(workspaceRoot) {
|
|
1868
|
+
async function provisionInstall(workspaceRoot, opts = {}) {
|
|
1866
1869
|
process.stdout.write(
|
|
1867
1870
|
" \xB7 installing dependencies with `pnpm install` (may take a minute)\u2026\n"
|
|
1868
1871
|
);
|
|
1869
|
-
const
|
|
1872
|
+
const args = [
|
|
1873
|
+
"install",
|
|
1874
|
+
...opts.force ? ["--force", "--no-frozen-lockfile"] : []
|
|
1875
|
+
];
|
|
1876
|
+
const result = await runCapture("pnpm", args, {
|
|
1870
1877
|
cwd: workspaceRoot,
|
|
1871
1878
|
timeoutMs: 6e5
|
|
1872
1879
|
});
|
|
@@ -2856,8 +2863,11 @@ async function readUBalanceRaw(address, networkName) {
|
|
|
2856
2863
|
if (uToken === void 0) {
|
|
2857
2864
|
return null;
|
|
2858
2865
|
}
|
|
2866
|
+
return readTokenBalanceRaw(address, uToken.address, networkName);
|
|
2867
|
+
}
|
|
2868
|
+
async function readTokenBalanceRaw(address, tokenAddress, networkName) {
|
|
2859
2869
|
const raw = await client(networkName).readContract({
|
|
2860
|
-
address: getAddress(
|
|
2870
|
+
address: getAddress(tokenAddress),
|
|
2861
2871
|
abi: ERC20_BALANCE_ABI,
|
|
2862
2872
|
functionName: "balanceOf",
|
|
2863
2873
|
args: [getAddress(address)]
|
|
@@ -3086,7 +3096,7 @@ function storageLocalNotDeployableCheck(root) {
|
|
|
3086
3096
|
return {
|
|
3087
3097
|
level: Level.CRITICAL,
|
|
3088
3098
|
name: "storage_kind_not_deployable",
|
|
3089
|
-
message: "[storage].kind = 'local' is not deployable \u2014 a deployed seller needs durable public storage. Switch to 'ipfs' and
|
|
3099
|
+
message: "[storage].kind = 'local' is not deployable \u2014 a deployed seller needs durable public storage. Switch to 'ipfs', 's3', or 'azure-blob' and configure its user-owned write credentials before deploying.",
|
|
3090
3100
|
fixCmd: "bag config set storage.kind ipfs",
|
|
3091
3101
|
details: { storage_kind: "local" }
|
|
3092
3102
|
};
|
|
@@ -3106,10 +3116,86 @@ function storageIpfsEndpointRequiredCheck(root, opts = {}) {
|
|
|
3106
3116
|
details: { storage_kind: "ipfs", missing: ["STORAGE_API_URL"] }
|
|
3107
3117
|
};
|
|
3108
3118
|
}
|
|
3119
|
+
function publicBaseIssue(value) {
|
|
3120
|
+
const text2 = String(value ?? "").trim();
|
|
3121
|
+
if (!text2) return null;
|
|
3122
|
+
try {
|
|
3123
|
+
const url = new URL(text2);
|
|
3124
|
+
if (url.protocol !== "https:") return "must use https";
|
|
3125
|
+
if (url.username || url.password) return "must not contain credentials";
|
|
3126
|
+
if (url.search || url.hash) return "must not contain a query or fragment";
|
|
3127
|
+
return null;
|
|
3128
|
+
} catch {
|
|
3129
|
+
return "is not a valid URL";
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
function storageObjectProviderRequiredCheck(root) {
|
|
3133
|
+
const cfg = loadAgentToml(root);
|
|
3134
|
+
const storage = tableOf2(cfg, "storage");
|
|
3135
|
+
const kind = String(storage.kind ?? "").trim().toLowerCase();
|
|
3136
|
+
if (kind !== "s3" && kind !== "azure-blob") return null;
|
|
3137
|
+
const credentialMode = String(storage.credential_mode ?? "env").trim().toLowerCase();
|
|
3138
|
+
const missing = [];
|
|
3139
|
+
const invalid = [];
|
|
3140
|
+
if (credentialMode !== "env" && credentialMode !== "ambient") {
|
|
3141
|
+
invalid.push("[storage].credential_mode (expected 'env' or 'ambient')");
|
|
3142
|
+
}
|
|
3143
|
+
const baseIssue = publicBaseIssue(storage.public_base_url);
|
|
3144
|
+
if (baseIssue) invalid.push(`[storage].public_base_url ${baseIssue}`);
|
|
3145
|
+
if (kind === "s3") {
|
|
3146
|
+
for (const key of ["bucket", "region"]) {
|
|
3147
|
+
if (!String(storage[key] ?? "").trim()) missing.push(`[storage].${key}`);
|
|
3148
|
+
}
|
|
3149
|
+
if (credentialMode === "env") {
|
|
3150
|
+
for (const key of [
|
|
3151
|
+
"DELIVERABLE_S3_ACCESS_KEY_ID",
|
|
3152
|
+
"DELIVERABLE_S3_SECRET_ACCESS_KEY"
|
|
3153
|
+
]) {
|
|
3154
|
+
if (!runtimeEnvResolvable(root, key)) missing.push(key);
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
} else {
|
|
3158
|
+
for (const key of ["account_url", "container"]) {
|
|
3159
|
+
if (!String(storage[key] ?? "").trim()) missing.push(`[storage].${key}`);
|
|
3160
|
+
}
|
|
3161
|
+
if (credentialMode === "env") {
|
|
3162
|
+
const hasSas = runtimeEnvResolvable(root, "DELIVERABLE_AZURE_SAS_TOKEN");
|
|
3163
|
+
const spKeys = [
|
|
3164
|
+
"DELIVERABLE_AZURE_TENANT_ID",
|
|
3165
|
+
"DELIVERABLE_AZURE_CLIENT_ID",
|
|
3166
|
+
"DELIVERABLE_AZURE_CLIENT_SECRET"
|
|
3167
|
+
];
|
|
3168
|
+
const presentSpKeys = spKeys.filter(
|
|
3169
|
+
(key) => runtimeEnvResolvable(root, key)
|
|
3170
|
+
);
|
|
3171
|
+
if (!hasSas && presentSpKeys.length === 0) {
|
|
3172
|
+
missing.push(
|
|
3173
|
+
"DELIVERABLE_AZURE_SAS_TOKEN (or a complete service-principal set)"
|
|
3174
|
+
);
|
|
3175
|
+
} else if (!hasSas) {
|
|
3176
|
+
missing.push(
|
|
3177
|
+
...spKeys.filter((key) => !runtimeEnvResolvable(root, key))
|
|
3178
|
+
);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
if (missing.length === 0 && invalid.length === 0) return null;
|
|
3183
|
+
return {
|
|
3184
|
+
level: Level.CRITICAL,
|
|
3185
|
+
name: `storage_${kind.replace("-", "_")}_required`,
|
|
3186
|
+
message: `[storage].kind = '${kind}' is not deploy-ready. ${[
|
|
3187
|
+
missing.length > 0 ? `Missing: ${missing.join(", ")}.` : "",
|
|
3188
|
+
invalid.length > 0 ? `Invalid: ${invalid.join(", ")}.` : ""
|
|
3189
|
+
].filter(Boolean).join(" ")}`,
|
|
3190
|
+
fixCmd: "# update app/agent/studio.toml and .studio/.env.local",
|
|
3191
|
+
details: { storage_kind: kind, missing, invalid }
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3109
3194
|
function fatalStorageDeployChecks(root) {
|
|
3110
3195
|
return [
|
|
3111
3196
|
storageLocalNotDeployableCheck(root),
|
|
3112
|
-
storageIpfsEndpointRequiredCheck(root)
|
|
3197
|
+
storageIpfsEndpointRequiredCheck(root),
|
|
3198
|
+
storageObjectProviderRequiredCheck(root)
|
|
3113
3199
|
].filter((c2) => c2 !== null);
|
|
3114
3200
|
}
|
|
3115
3201
|
function hasIpfsEndpointFinding(result) {
|
|
@@ -3466,17 +3552,27 @@ function discoverRecordedDeployments(root) {
|
|
|
3466
3552
|
});
|
|
3467
3553
|
}
|
|
3468
3554
|
const azureEndpoint = text(azure.agent_endpoint);
|
|
3555
|
+
const azureHttpEndpoint = text(azure.http_endpoint);
|
|
3556
|
+
const azureInvocationsEndpoint = text(azure.invocations_endpoint);
|
|
3557
|
+
const azureA2aEndpoint = text(azure.a2a_endpoint);
|
|
3558
|
+
const azureAgentCardEndpoint = text(azure.agent_card_endpoint);
|
|
3469
3559
|
const azureDeploymentId = text(azure.deployment_id);
|
|
3470
3560
|
const azureFaces = protocolFaces(azure.deployed_faces);
|
|
3471
3561
|
const azureX402 = recordedX402Snapshot(azure);
|
|
3472
3562
|
const azureX402Carrier = recordedX402Carrier(azure);
|
|
3473
|
-
if (azureEndpoint || azureDeploymentId || text(azure.last_deploy_at)) {
|
|
3563
|
+
if (azureEndpoint || azureHttpEndpoint || azureInvocationsEndpoint || azureA2aEndpoint || azureDeploymentId || text(azure.last_deploy_at)) {
|
|
3474
3564
|
found.push({
|
|
3475
3565
|
provider: "azure",
|
|
3476
3566
|
target: PROVIDER_TARGETS.azure,
|
|
3477
3567
|
name: text(azure.agent_name) ?? projectName2,
|
|
3478
3568
|
deploymentId: azureDeploymentId,
|
|
3479
|
-
endpoint: azureEndpoint,
|
|
3569
|
+
endpoint: azureEndpoint ?? azureA2aEndpoint ?? azureHttpEndpoint,
|
|
3570
|
+
endpoints: {
|
|
3571
|
+
http: azureHttpEndpoint,
|
|
3572
|
+
invocations: azureInvocationsEndpoint,
|
|
3573
|
+
a2a: azureA2aEndpoint,
|
|
3574
|
+
agentCard: azureAgentCardEndpoint
|
|
3575
|
+
},
|
|
3480
3576
|
...azureFaces ? { faces: azureFaces } : {},
|
|
3481
3577
|
...azureX402 ? { x402: azureX402 } : {},
|
|
3482
3578
|
...azureX402Carrier ? { x402Carrier: azureX402Carrier } : {},
|
|
@@ -3495,7 +3591,7 @@ function unavailableProvidersForProject(root) {
|
|
|
3495
3591
|
const cfg = loadStudioToml6(path12.join(agentRoot2, "studio.toml"));
|
|
3496
3592
|
runtime = text(tableOf3(cfg, "stack").runtime) ?? "agentcore";
|
|
3497
3593
|
const faces = stackFaces(tableOf3(cfg, "stack"), tableOf3(cfg, "payments"));
|
|
3498
|
-
azureProtocolUnsupported = hasMcpFace(faces) || !hasA2aFace(faces) && !
|
|
3594
|
+
azureProtocolUnsupported = hasMcpFace(faces) || !hasA2aFace(faces) && !hasB402PaymentFace(faces);
|
|
3499
3595
|
} catch {
|
|
3500
3596
|
return {};
|
|
3501
3597
|
}
|
|
@@ -4227,12 +4323,12 @@ function inspectAzureX402Carrier(agentRoot2) {
|
|
|
4227
4323
|
};
|
|
4228
4324
|
}
|
|
4229
4325
|
const source = fs17.readFileSync(entryFile, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
|
|
4230
|
-
const hasSeller = /\
|
|
4326
|
+
const hasSeller = /\b(?:B402|X402)Seller\s*\.\s*create\s*\(/m.test(source);
|
|
4231
4327
|
const hasCarrier = /\bcreateEnvelopeMiddleware\s*\(/m.test(source);
|
|
4232
4328
|
if (!hasSeller || !hasCarrier) {
|
|
4233
4329
|
const missing = [
|
|
4234
4330
|
...hasCarrier ? [] : ["createEnvelopeMiddleware"],
|
|
4235
|
-
...hasSeller ? [] : ["
|
|
4331
|
+
...hasSeller ? [] : ["B402Seller.create"]
|
|
4236
4332
|
];
|
|
4237
4333
|
return {
|
|
4238
4334
|
status: "incompatible-scaffold",
|
|
@@ -4243,7 +4339,7 @@ function inspectAzureX402Carrier(agentRoot2) {
|
|
|
4243
4339
|
return {
|
|
4244
4340
|
status: "http-envelope-v1",
|
|
4245
4341
|
entrypoint,
|
|
4246
|
-
detail: `${entrypoint} mounts the
|
|
4342
|
+
detail: `${entrypoint} mounts the B402 seller and envelope carrier`
|
|
4247
4343
|
};
|
|
4248
4344
|
}
|
|
4249
4345
|
function tableOf4(cfg, key) {
|
|
@@ -4268,6 +4364,26 @@ function loadStudioCfg(workspaceRoot) {
|
|
|
4268
4364
|
return {};
|
|
4269
4365
|
}
|
|
4270
4366
|
}
|
|
4367
|
+
function foundryA2aSiblingEndpoints(value) {
|
|
4368
|
+
if (!value) return null;
|
|
4369
|
+
let url;
|
|
4370
|
+
try {
|
|
4371
|
+
url = new URL(value);
|
|
4372
|
+
} catch {
|
|
4373
|
+
return null;
|
|
4374
|
+
}
|
|
4375
|
+
const suffix = "/endpoint/protocols/a2a";
|
|
4376
|
+
const path49 = url.pathname.replace(/\/+$/, "");
|
|
4377
|
+
if (!path49.endsWith(suffix)) return null;
|
|
4378
|
+
const root = `${url.origin}${path49.slice(0, -suffix.length)}`;
|
|
4379
|
+
const a2a = `${root}${suffix}`;
|
|
4380
|
+
return {
|
|
4381
|
+
http: `${root}/endpoint/protocols/openai/responses?api-version=v1`,
|
|
4382
|
+
invocations: `${root}/endpoint/protocols/invocations?api-version=v1`,
|
|
4383
|
+
a2a,
|
|
4384
|
+
agentCard: `${a2a}/agentCard/v1.0`
|
|
4385
|
+
};
|
|
4386
|
+
}
|
|
4271
4387
|
function recordAzureDeployment(root, data, endpoint) {
|
|
4272
4388
|
const agentRoot2 = findSubProjectRoot7("agent", deployWorkspaceRoot(root));
|
|
4273
4389
|
if (agentRoot2 === null) return;
|
|
@@ -4278,7 +4394,7 @@ function recordAzureDeployment(root, data, endpoint) {
|
|
|
4278
4394
|
try {
|
|
4279
4395
|
const cfg = loadStudioCfg(deployWorkspaceRoot(root));
|
|
4280
4396
|
deployedFaces = stackFaces(tableOf4(cfg, "stack"), tableOf4(cfg, "payments"));
|
|
4281
|
-
if (
|
|
4397
|
+
if (hasB402PaymentFace(deployedFaces)) {
|
|
4282
4398
|
deployedX402 = x402DeploymentSnapshot(agentRoot2, "self");
|
|
4283
4399
|
deployedX402Carrier = inspectAzureX402Carrier(agentRoot2).status;
|
|
4284
4400
|
}
|
|
@@ -4290,9 +4406,87 @@ function recordAzureDeployment(root, data, endpoint) {
|
|
|
4290
4406
|
patchTomlKv(tomlPath, "azure", "agent_name", name);
|
|
4291
4407
|
patchTomlKv(tomlPath, "azure", "deployment_id", name);
|
|
4292
4408
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4409
|
+
const endpoints = tableOf4(data, "endpoints");
|
|
4410
|
+
const recordEndpoints = tableOf4(tableOf4(data, "record"), "endpoints");
|
|
4411
|
+
const reportedHttpEndpoint = deployEndpointValue(
|
|
4412
|
+
endpoints.http,
|
|
4413
|
+
recordEndpoints.http,
|
|
4414
|
+
tableOf4(data, "record").invokeUrl,
|
|
4415
|
+
data.invokeUrl
|
|
4416
|
+
);
|
|
4417
|
+
const reportedInvocationsEndpoint = deployEndpointValue(
|
|
4418
|
+
endpoints.invocations,
|
|
4419
|
+
recordEndpoints.invocations
|
|
4420
|
+
);
|
|
4421
|
+
const reportedA2aEndpoint = deployEndpointValue(
|
|
4422
|
+
endpoints.a2a,
|
|
4423
|
+
recordEndpoints.a2a
|
|
4424
|
+
);
|
|
4425
|
+
const reportedAgentCardEndpoint = deployEndpointValue(
|
|
4426
|
+
endpoints.agentCard,
|
|
4427
|
+
recordEndpoints.agentCard
|
|
4428
|
+
);
|
|
4429
|
+
const siblings = foundryA2aSiblingEndpoints(
|
|
4430
|
+
reportedA2aEndpoint ?? endpoint
|
|
4431
|
+
);
|
|
4432
|
+
const httpEndpoint = reportedHttpEndpoint ?? siblings?.http ?? null;
|
|
4433
|
+
const config = loadStudioToml7(tomlPath);
|
|
4434
|
+
const faces = stackFaces(
|
|
4435
|
+
tableOf4(config, "stack"),
|
|
4436
|
+
tableOf4(config, "payments")
|
|
4437
|
+
);
|
|
4438
|
+
const invocationsEndpoint = reportedInvocationsEndpoint ?? (hasB402PaymentFace(faces) ? siblings?.invocations ?? null : null);
|
|
4439
|
+
const a2aEndpoint = reportedA2aEndpoint ?? siblings?.a2a ?? null;
|
|
4440
|
+
const agentCardEndpoint = reportedAgentCardEndpoint ?? siblings?.agentCard ?? null;
|
|
4441
|
+
const primaryEndpoint = a2aEndpoint ?? endpoint ?? httpEndpoint;
|
|
4442
|
+
if (primaryEndpoint) {
|
|
4443
|
+
patchTomlKv(tomlPath, "azure", "agent_endpoint", primaryEndpoint);
|
|
4444
|
+
patchTomlKv(
|
|
4445
|
+
tomlPath,
|
|
4446
|
+
"identity",
|
|
4447
|
+
"endpoint",
|
|
4448
|
+
agentCardEndpoint ?? primaryEndpoint
|
|
4449
|
+
);
|
|
4450
|
+
}
|
|
4451
|
+
if (httpEndpoint)
|
|
4452
|
+
patchTomlKv(tomlPath, "azure", "http_endpoint", httpEndpoint);
|
|
4453
|
+
if (invocationsEndpoint)
|
|
4454
|
+
patchTomlKv(
|
|
4455
|
+
tomlPath,
|
|
4456
|
+
"azure",
|
|
4457
|
+
"invocations_endpoint",
|
|
4458
|
+
invocationsEndpoint
|
|
4459
|
+
);
|
|
4460
|
+
if (a2aEndpoint)
|
|
4461
|
+
patchTomlKv(tomlPath, "azure", "a2a_endpoint", a2aEndpoint);
|
|
4462
|
+
if (agentCardEndpoint)
|
|
4463
|
+
patchTomlKv(tomlPath, "azure", "agent_card_endpoint", agentCardEndpoint);
|
|
4464
|
+
if (deployedFaces) {
|
|
4465
|
+
patchTomlKv(tomlPath, "azure", "deployed_faces", deployedFaces);
|
|
4466
|
+
patchTomlKv(
|
|
4467
|
+
tomlPath,
|
|
4468
|
+
"azure",
|
|
4469
|
+
"deployed_x402_status",
|
|
4470
|
+
deployedX402?.status ?? ""
|
|
4471
|
+
);
|
|
4472
|
+
patchTomlKv(
|
|
4473
|
+
tomlPath,
|
|
4474
|
+
"azure",
|
|
4475
|
+
"deployed_x402_price_mode",
|
|
4476
|
+
deployedX402?.priceMode ?? ""
|
|
4477
|
+
);
|
|
4478
|
+
patchTomlKv(
|
|
4479
|
+
tomlPath,
|
|
4480
|
+
"azure",
|
|
4481
|
+
"deployed_x402_credentials_complete",
|
|
4482
|
+
deployedX402?.credentialsComplete ?? false
|
|
4483
|
+
);
|
|
4484
|
+
patchTomlKv(
|
|
4485
|
+
tomlPath,
|
|
4486
|
+
"azure",
|
|
4487
|
+
"deployed_x402_carrier",
|
|
4488
|
+
deployedX402Carrier ?? ""
|
|
4489
|
+
);
|
|
4296
4490
|
}
|
|
4297
4491
|
if (deployedFaces) {
|
|
4298
4492
|
patchTomlKv(tomlPath, "azure", "deployed_faces", deployedFaces);
|
|
@@ -4332,6 +4526,10 @@ function clearAzureEndpoint(root) {
|
|
|
4332
4526
|
try {
|
|
4333
4527
|
for (const key of [
|
|
4334
4528
|
"agent_endpoint",
|
|
4529
|
+
"http_endpoint",
|
|
4530
|
+
"invocations_endpoint",
|
|
4531
|
+
"a2a_endpoint",
|
|
4532
|
+
"agent_card_endpoint",
|
|
4335
4533
|
"agent_name",
|
|
4336
4534
|
"deployment_id",
|
|
4337
4535
|
"last_deploy_at"
|
|
@@ -4419,10 +4617,17 @@ async function deployAzureFoundry(opts) {
|
|
|
4419
4617
|
}
|
|
4420
4618
|
);
|
|
4421
4619
|
const record = recordOf(data);
|
|
4620
|
+
const endpoints = tableOf4(data, "endpoints");
|
|
4621
|
+
const recordEndpoints = tableOf4(record, "endpoints");
|
|
4422
4622
|
const endpoint = deployEndpointValue(
|
|
4423
|
-
|
|
4623
|
+
endpoints.a2a,
|
|
4624
|
+
recordEndpoints.a2a,
|
|
4424
4625
|
record.endpoint,
|
|
4425
|
-
data.endpoint
|
|
4626
|
+
data.endpoint,
|
|
4627
|
+
endpoints.http,
|
|
4628
|
+
recordEndpoints.http,
|
|
4629
|
+
record.invokeUrl,
|
|
4630
|
+
data.invokeUrl
|
|
4426
4631
|
);
|
|
4427
4632
|
if (code !== 0) {
|
|
4428
4633
|
if (endpoint) {
|
|
@@ -4635,12 +4840,12 @@ var checkEntrypointAndDockerfile = async (root, _target) => {
|
|
|
4635
4840
|
const findings = [];
|
|
4636
4841
|
const cfg = loadStudioCfg(deployWorkspaceRoot(root));
|
|
4637
4842
|
const faces = stackFaces(tableOf4(cfg, "stack"), tableOf4(cfg, "payments"));
|
|
4638
|
-
if (hasMcpFace(faces) || !hasA2aFace(faces) && !
|
|
4843
|
+
if (hasMcpFace(faces) || !hasA2aFace(faces) && !hasB402PaymentFace(faces)) {
|
|
4639
4844
|
return [
|
|
4640
4845
|
{
|
|
4641
4846
|
level: "CRITICAL",
|
|
4642
4847
|
name: "azure_protocol_supported",
|
|
4643
|
-
message: "azure-foundry deploy supports the A2A carrier, including
|
|
4848
|
+
message: "azure-foundry deploy supports the A2A carrier, including payment-only X402 or MPP seller configs. The delegated Foundry version declares the invocations contract (:8088, /readiness, /invocations), not native MCP /mcp. Re-scaffold with A2A and at most one B402 payment face; or use AgentCore for MCP.",
|
|
4644
4849
|
details: { faces }
|
|
4645
4850
|
}
|
|
4646
4851
|
];
|
|
@@ -4655,13 +4860,13 @@ var checkEntrypointAndDockerfile = async (root, _target) => {
|
|
|
4655
4860
|
details: { entrypoint: entry }
|
|
4656
4861
|
});
|
|
4657
4862
|
}
|
|
4658
|
-
if (
|
|
4863
|
+
if (hasB402PaymentFace(faces)) {
|
|
4659
4864
|
const carrier = inspectAzureX402Carrier(agentRoot2);
|
|
4660
4865
|
if (carrier.status !== "http-envelope-v1") {
|
|
4661
4866
|
findings.push({
|
|
4662
4867
|
level: "CRITICAL",
|
|
4663
4868
|
name: "azure_x402_entrypoint_compatible",
|
|
4664
|
-
message: `${carrier.detail}. This scaffold cannot serve
|
|
4869
|
+
message: `${carrier.detail}. This scaffold cannot serve the selected B402 payment protocol even though studio.toml enables it. In a clean parent directory, run \`bag init azureb402migration --runtime azure-foundry --protocols ${faces.join(",")} --no-install --no-onboard\`, then review and merge its src/unifiedMain.ts, Dockerfile, and package scripts before redeploying. Studio will not overwrite user-owned agent code.`,
|
|
4665
4870
|
details: {
|
|
4666
4871
|
entrypoint: carrier.entrypoint,
|
|
4667
4872
|
carrier: carrier.status
|
|
@@ -4854,9 +5059,9 @@ function writeEnvLocal(key, value, envPath = ".env.local") {
|
|
|
4854
5059
|
return setEnvVar(envPath, key, value);
|
|
4855
5060
|
}
|
|
4856
5061
|
function ensureSiweCapable(wallet) {
|
|
4857
|
-
const
|
|
4858
|
-
if (typeof
|
|
4859
|
-
if (
|
|
5062
|
+
const probe2 = wallet.supports;
|
|
5063
|
+
if (typeof probe2 !== "function") return;
|
|
5064
|
+
if (probe2.call(wallet, "sign.message")) return;
|
|
4860
5065
|
const kind = wallet.kind;
|
|
4861
5066
|
throw new PieverseCliError(
|
|
4862
5067
|
`the ${kind ? `'${kind}' ` : ""}wallet cannot log in to Pieverse: SIWE requires an EIP-191 personal-sign ('sign.message'), which this wallet kind never supports. Pieverse LLM is not usable with this wallet \u2014 set [llm].provider in studio.toml to 'openrouter', 'openai', or 'anthropic' (with the matching API key in .studio/.env.local), or use an 'evm-local' or 'twak' wallet.`
|
|
@@ -6650,6 +6855,7 @@ import {
|
|
|
6650
6855
|
import { auditedOp as auditedOp2 } from "@bnbagent/studio-runtime/audit";
|
|
6651
6856
|
import { loadStudioToml as loadStudioToml9 } from "@bnbagent/studio-runtime/config";
|
|
6652
6857
|
import {
|
|
6858
|
+
fromRaw as fromRaw3,
|
|
6653
6859
|
getNetwork as getNetwork5,
|
|
6654
6860
|
tbnbFaucetHint,
|
|
6655
6861
|
toRaw as toRaw2
|
|
@@ -6735,6 +6941,48 @@ function requirePassword() {
|
|
|
6735
6941
|
}
|
|
6736
6942
|
return password;
|
|
6737
6943
|
}
|
|
6944
|
+
var RELAY_GAS_FLOOR_WEI = 10n ** 16n;
|
|
6945
|
+
function isBareRelayExecutionError(error) {
|
|
6946
|
+
return error instanceof Error && error.message.includes("An error occurred while executing calls") && /Reason: 0x(?![0-9a-fA-F])/.test(error.message);
|
|
6947
|
+
}
|
|
6948
|
+
async function diagnoseRelayError(ctx, error) {
|
|
6949
|
+
if (!isBareRelayExecutionError(error)) return error;
|
|
6950
|
+
let balance;
|
|
6951
|
+
try {
|
|
6952
|
+
balance = await readNativeBalanceRaw(ctx.address, ctx.networkName);
|
|
6953
|
+
} catch {
|
|
6954
|
+
return error;
|
|
6955
|
+
}
|
|
6956
|
+
if (balance >= RELAY_GAS_FLOOR_WEI) return error;
|
|
6957
|
+
const symbol = ctx.networkName === "bsc-testnet" ? "tBNB" : "BNB";
|
|
6958
|
+
const funding = ctx.networkName === "bsc-testnet" ? ` ${tbnbFaucetHint(ctx.address)}` : "";
|
|
6959
|
+
return new Error(
|
|
6960
|
+
`${error.message}
|
|
6961
|
+
Likely cause: admin wallet ${ctx.address} holds ${fromRaw3(balance, 18)} ${symbol} on ${ctx.networkName} \u2014 not enough to repay the relay-fronted gas (the relay recovers its fee from the wallet in the same transaction; an unpayable fee reverts with empty revert data). Fund it to at least 0.01 ${symbol} and retry.${funding}`,
|
|
6962
|
+
{ cause: error }
|
|
6963
|
+
);
|
|
6964
|
+
}
|
|
6965
|
+
function withRelayGasDiagnosis(ctx, admin) {
|
|
6966
|
+
const guard = (fn) => async (...args) => {
|
|
6967
|
+
try {
|
|
6968
|
+
return await fn(...args);
|
|
6969
|
+
} catch (error) {
|
|
6970
|
+
throw await diagnoseRelayError(ctx, error);
|
|
6971
|
+
}
|
|
6972
|
+
};
|
|
6973
|
+
return {
|
|
6974
|
+
address: admin.address,
|
|
6975
|
+
grantSession: guard(admin.grantSession.bind(admin)),
|
|
6976
|
+
revokeSession: guard(admin.revokeSession.bind(admin)),
|
|
6977
|
+
approveQuoteSignatureChecker: guard(
|
|
6978
|
+
admin.approveQuoteSignatureChecker.bind(admin)
|
|
6979
|
+
),
|
|
6980
|
+
approveX402SignatureChecker: guard(
|
|
6981
|
+
admin.approveX402SignatureChecker.bind(admin)
|
|
6982
|
+
),
|
|
6983
|
+
setPermit2Allowance: guard(admin.setPermit2Allowance.bind(admin))
|
|
6984
|
+
};
|
|
6985
|
+
}
|
|
6738
6986
|
function buildAdmin(ctx) {
|
|
6739
6987
|
const admin = adminFactory({
|
|
6740
6988
|
password: requirePassword(),
|
|
@@ -6747,7 +6995,7 @@ function buildAdmin(ctx) {
|
|
|
6747
6995
|
`Altana admin address ${admin.address} does not match [wallet].address ${ctx.address}.`
|
|
6748
6996
|
);
|
|
6749
6997
|
}
|
|
6750
|
-
return admin;
|
|
6998
|
+
return withRelayGasDiagnosis(ctx, admin);
|
|
6751
6999
|
}
|
|
6752
7000
|
function parseSessionEnvelope(serialized) {
|
|
6753
7001
|
let parsed;
|
|
@@ -7874,7 +8122,12 @@ var SUPPORTED_PROVIDERS = [
|
|
|
7874
8122
|
"pieverse-llm"
|
|
7875
8123
|
];
|
|
7876
8124
|
var SUPPORTED_NETWORKS = ["bsc-testnet", "bsc-mainnet"];
|
|
7877
|
-
var SUPPORTED_STORAGE_PROVIDERS = [
|
|
8125
|
+
var SUPPORTED_STORAGE_PROVIDERS = [
|
|
8126
|
+
"ipfs",
|
|
8127
|
+
"s3",
|
|
8128
|
+
"azure-blob",
|
|
8129
|
+
"local"
|
|
8130
|
+
];
|
|
7878
8131
|
var SUPPORTED_DESTINATIONS = ["self", "platform"];
|
|
7879
8132
|
var RUNTIMES = {
|
|
7880
8133
|
agentcore: { label: "AWS Bedrock AgentCore", hidden: false },
|
|
@@ -8112,7 +8365,7 @@ function registerInit(program) {
|
|
|
8112
8365
|
acceptChoices(
|
|
8113
8366
|
new Option2(
|
|
8114
8367
|
"--runtime <name>",
|
|
8115
|
-
`Runtime
|
|
8368
|
+
`Runtime scaffold (${visibleRuntimes.join(", ")}); Azure: bag deploy --provider azure.`
|
|
8116
8369
|
).default("agentcore"),
|
|
8117
8370
|
SUPPORTED_RUNTIMES,
|
|
8118
8371
|
visibleRuntimes
|
|
@@ -8140,8 +8393,13 @@ function registerInit(program) {
|
|
|
8140
8393
|
).addOption(
|
|
8141
8394
|
new Option2(
|
|
8142
8395
|
"--protocols <faces>",
|
|
8143
|
-
"Comma-separated public faces to expose; recorded in studio.toml [stack].protocols. Any non-empty combination of A2A, MCP
|
|
8396
|
+
"Comma-separated public faces to expose; recorded in studio.toml [stack].protocols. Any non-empty combination of A2A, MCP and one B402 payment face (X402 or MPP; default: A2A,X402)."
|
|
8144
8397
|
)
|
|
8398
|
+
).addOption(
|
|
8399
|
+
new Option2(
|
|
8400
|
+
"--payment-protocol <protocol>",
|
|
8401
|
+
"B402 HTTP protocol adapter: x402 or mpp (default: x402). The two are alternatives."
|
|
8402
|
+
).choices(["x402", "mpp"])
|
|
8145
8403
|
).addOption(
|
|
8146
8404
|
new Option2(
|
|
8147
8405
|
"--protocol <protocol>",
|
|
@@ -8157,7 +8415,7 @@ function registerInit(program) {
|
|
|
8157
8415
|
"ERC-8183 list price as a non-negative integer string in token base units. Use 0 for FREE. Default: 100000000000000000 (0.1 U)."
|
|
8158
8416
|
).option(
|
|
8159
8417
|
"--b402-price <usd>",
|
|
8160
|
-
"B402
|
|
8418
|
+
"B402 per-request price for x402 or MPP as a non-negative decimal USD string. Use 0 for FREE passthrough (no B402 verify/settle). Default: 0.01."
|
|
8161
8419
|
).addOption(
|
|
8162
8420
|
new Option2(
|
|
8163
8421
|
"--llm-provider <provider>",
|
|
@@ -8182,7 +8440,7 @@ function registerInit(program) {
|
|
|
8182
8440
|
).addOption(
|
|
8183
8441
|
new Option2(
|
|
8184
8442
|
"--storage-provider <kind>",
|
|
8185
|
-
"Where job deliverables are stored: 'local' (default; offline dev) or 'ipfs'
|
|
8443
|
+
"Where job deliverables are stored: 'local' (default; offline dev) or durable self-hosted BYOS via 'ipfs', 's3', or 'azure-blob'. Self-hosted S3/Blob need the user's storage config and namespaced runtime credentials; Platform ignores this deploy-time choice and injects scoped access to managed S3 or Azure Blob. Written to studio.toml [storage].kind."
|
|
8186
8444
|
).default("local").choices([...SUPPORTED_STORAGE_PROVIDERS])
|
|
8187
8445
|
).option(
|
|
8188
8446
|
"--ipfs-key <key>",
|
|
@@ -8303,10 +8561,11 @@ async function cmdInitFlow(nameArg, opts) {
|
|
|
8303
8561
|
return 2;
|
|
8304
8562
|
}
|
|
8305
8563
|
const facesExplicit = opts.protocols !== void 0 || opts.protocol !== void 0;
|
|
8564
|
+
const paymentFace = opts.paymentProtocol === "mpp" ? "MPP" : "X402";
|
|
8306
8565
|
let faces;
|
|
8307
8566
|
try {
|
|
8308
8567
|
faces = normalizeProtocolFaces(
|
|
8309
|
-
opts.protocols ?? opts.protocol ??
|
|
8568
|
+
opts.protocols ?? opts.protocol ?? `A2A,${paymentFace}`
|
|
8310
8569
|
);
|
|
8311
8570
|
if (opts.protocol !== void 0 && faces.length !== 1) {
|
|
8312
8571
|
throw new Error("--protocol is a single-face alias; use --protocols");
|
|
@@ -8317,6 +8576,12 @@ async function cmdInitFlow(nameArg, opts) {
|
|
|
8317
8576
|
);
|
|
8318
8577
|
return 2;
|
|
8319
8578
|
}
|
|
8579
|
+
if (opts.paymentProtocol !== void 0 && facesExplicit && (opts.paymentProtocol === "mpp" && !hasMppFace(faces) || opts.paymentProtocol === "x402" && !hasX402Face(faces))) {
|
|
8580
|
+
printErr(
|
|
8581
|
+
"error: --payment-protocol must match the X402 or MPP face selected by --protocols."
|
|
8582
|
+
);
|
|
8583
|
+
return 2;
|
|
8584
|
+
}
|
|
8320
8585
|
if (opts.twakHome && opts.walletKind !== "twak") {
|
|
8321
8586
|
printErr("error: --twak-home is only valid with --wallet-kind twak.");
|
|
8322
8587
|
return 2;
|
|
@@ -8343,20 +8608,20 @@ async function cmdInitFlow(nameArg, opts) {
|
|
|
8343
8608
|
if (llmProvider === null) {
|
|
8344
8609
|
return 2;
|
|
8345
8610
|
}
|
|
8346
|
-
const inferredRails = opts.rails ?? (facesExplicit ?
|
|
8611
|
+
const inferredRails = opts.rails ?? (facesExplicit ? hasB402PaymentFace(faces) ? "both" : "8183" : void 0);
|
|
8347
8612
|
let rails = await resolveRails(inferredRails, isTty);
|
|
8348
8613
|
if (!facesExplicit) {
|
|
8349
8614
|
faces = normalizeProtocolFaces(
|
|
8350
|
-
rails === "8183" ? ["A2A"] : ["A2A",
|
|
8615
|
+
rails === "8183" ? ["A2A"] : ["A2A", paymentFace]
|
|
8351
8616
|
);
|
|
8352
8617
|
}
|
|
8353
|
-
if (
|
|
8618
|
+
if (hasB402PaymentFace(faces) && rails === "8183") {
|
|
8354
8619
|
rails = "both";
|
|
8355
|
-
} else if (
|
|
8620
|
+
} else if (hasB402PaymentFace(faces) && rails !== "both") {
|
|
8356
8621
|
rails = "b402";
|
|
8357
8622
|
}
|
|
8358
|
-
if ((rails === "b402" || rails === "both") && !
|
|
8359
|
-
faces = normalizeProtocolFaces([...faces,
|
|
8623
|
+
if ((rails === "b402" || rails === "both") && !hasB402PaymentFace(faces)) {
|
|
8624
|
+
faces = normalizeProtocolFaces([...faces, paymentFace]);
|
|
8360
8625
|
}
|
|
8361
8626
|
if (opts.erc8183Price !== void 0 && rails === "b402") {
|
|
8362
8627
|
printErr(
|
|
@@ -8423,9 +8688,9 @@ async function cmdInitFlow(nameArg, opts) {
|
|
|
8423
8688
|
destination = "self";
|
|
8424
8689
|
runtime = "agentcore";
|
|
8425
8690
|
}
|
|
8426
|
-
if (destination === "self" && runtime === "azure-foundry" && (hasMcpFace(faces) || !hasA2aFace(faces) && !
|
|
8691
|
+
if (destination === "self" && runtime === "azure-foundry" && (hasMcpFace(faces) || !hasA2aFace(faces) && !hasB402PaymentFace(faces))) {
|
|
8427
8692
|
printErr(
|
|
8428
|
-
"error: azure-foundry supports the A2A carrier (including
|
|
8693
|
+
"error: azure-foundry supports the A2A carrier (including payment-only seller configs) and does not support an MCP face. Foundry is delegated through its invocations container contract (:8088, /readiness, /invocations), not the native MCP /mcp transport. Use A2A with one X402/MPP payment face, or use --runtime agentcore for MCP."
|
|
8429
8694
|
);
|
|
8430
8695
|
return 2;
|
|
8431
8696
|
}
|
|
@@ -8595,24 +8860,27 @@ async function cmdInitFlow(nameArg, opts) {
|
|
|
8595
8860
|
);
|
|
8596
8861
|
}
|
|
8597
8862
|
if (opts.storageProvider === "local") {
|
|
8598
|
-
printLocalStorageWarning();
|
|
8863
|
+
printLocalStorageWarning(destination, runtime);
|
|
8599
8864
|
}
|
|
8600
8865
|
if (hasMcpFace(faces) && destination === "platform") {
|
|
8601
8866
|
printPlatformMcpSyncWarning(faces);
|
|
8602
8867
|
}
|
|
8603
|
-
if (faces.length === 1 &&
|
|
8868
|
+
if (faces.length === 1 && hasB402PaymentFace(faces) && destination === "platform") {
|
|
8869
|
+
const paymentPath = hasMppFace(faces) ? "/mpp" : "/x402";
|
|
8604
8870
|
printErr(
|
|
8605
|
-
|
|
8871
|
+
`info: payment-only publishes only the ${paymentPath} gateway face; the native AgentCore A2A protocol declaration is suppressed from discovery.`
|
|
8606
8872
|
);
|
|
8607
|
-
} else if (faces.length === 1 &&
|
|
8873
|
+
} else if (faces.length === 1 && hasB402PaymentFace(faces) && destination === "self") {
|
|
8874
|
+
const paymentName = hasMppFace(faces) ? "MPP" : "X402";
|
|
8875
|
+
const paymentPath = hasMppFace(faces) ? "/mpp" : "/x402";
|
|
8608
8876
|
const free = x402SellerPricingState({ price_usd: b402Price }).kind === "free";
|
|
8609
8877
|
if (runtime === "azure-foundry") {
|
|
8610
8878
|
printErr(
|
|
8611
|
-
free ?
|
|
8879
|
+
free ? `warning: ${paymentName}-only FREE mode on Azure Foundry exposes no public A2A face or anonymous ${paymentPath} URL; run an external HTTPS gateway that carries http-envelope-v1 over the Entra-authenticated Invocations endpoint.` : `warning: ${paymentName}-only on Azure Foundry exposes no public A2A face or anonymous ${paymentPath} URL; the seller stays dormant until its credentials are filled, then an external HTTPS gateway must carry http-envelope-v1 over the Entra-authenticated Invocations endpoint.`
|
|
8612
8880
|
);
|
|
8613
8881
|
} else {
|
|
8614
8882
|
printErr(
|
|
8615
|
-
free ?
|
|
8883
|
+
free ? `warning: ${paymentName}-only FREE mode uses an A2A-native AgentCore process internally, but exposes no public A2A face; a self-hosted target still needs your own HTTP front for ${paymentPath}.` : `warning: ${paymentName}-only uses an A2A-native AgentCore process internally, but exposes no public A2A face; the seller stays dormant until its credentials are filled, and a self-hosted target needs your own HTTP front for ${paymentPath}.`
|
|
8616
8884
|
);
|
|
8617
8885
|
}
|
|
8618
8886
|
}
|
|
@@ -8739,7 +9007,7 @@ function scaffold(target, name, o) {
|
|
|
8739
9007
|
o.walletKind,
|
|
8740
9008
|
o.rails,
|
|
8741
9009
|
o.b402Price,
|
|
8742
|
-
o.
|
|
9010
|
+
hasMppFace(o.faces) ? "mpp" : "x402"
|
|
8743
9011
|
)
|
|
8744
9012
|
);
|
|
8745
9013
|
fs23.writeFileSync(path22.join(agentRoot2, ".gitignore"), renderAgentGitignore());
|
|
@@ -8974,6 +9242,12 @@ function renderAgentStorageSection(storageProvider) {
|
|
|
8974
9242
|
if (storageProvider === "local") {
|
|
8975
9243
|
return '[storage]\n# Deliverables written to local disk (file://). Offline-dev only \u2014\n# does NOT survive deploy. Path from STORAGE_LOCAL_PATH.\nkind = "local"\n';
|
|
8976
9244
|
}
|
|
9245
|
+
if (storageProvider === "s3") {
|
|
9246
|
+
return '[storage]\n# User-owned S3 deliverable storage. Objects are immutable and keyed\n# as deliverables/sha256/<content-hash>.json. Set credentials in\n# .studio/.env.local, never here. For an IAM role, set\n# credential_mode = "ambient". public_base_url is optional.\nkind = "s3"\nbucket = ""\nregion = ""\nkey_prefix = "deliverables"\ncredential_mode = "env"\n# public_base_url = "https://artifacts.customer.com"\n';
|
|
9247
|
+
}
|
|
9248
|
+
if (storageProvider === "azure-blob") {
|
|
9249
|
+
return '[storage]\n# User-owned Azure Blob deliverable storage. Objects are immutable and\n# keyed as deliverables/sha256/<content-hash>.json. Set a write SAS or\n# service-principal credentials in .studio/.env.local, never here. For\n# Managed Identity, set credential_mode = "ambient".\nkind = "azure-blob"\naccount_url = ""\ncontainer = ""\nkey_prefix = "deliverables"\ncredential_mode = "env"\n# public_base_url = "https://artifacts.customer.com"\n';
|
|
9250
|
+
}
|
|
8977
9251
|
return '[storage]\n# Deliverables pinned to IPFS. upload() returns ipfs://CID, published\n# on-chain verbatim \u2014 durable, public, deploy-ready. Works with any\n# pinning service or self-hosted node: the upload endpoint is\n# STORAGE_API_URL and the write key/JWT is STORAGE_API_KEY, both in\n# .studio/.env.local.\nkind = "ipfs"\n';
|
|
8978
9252
|
}
|
|
8979
9253
|
function renderWalletSection(walletKind2, twakHome) {
|
|
@@ -9074,10 +9348,11 @@ expected_recipient = ""
|
|
|
9074
9348
|
allowed_hosts = ["llm.pieverse.io"]
|
|
9075
9349
|
`;
|
|
9076
9350
|
}
|
|
9077
|
-
function
|
|
9351
|
+
function renderB402SellerSection(priceUsd, protocol) {
|
|
9352
|
+
const path49 = protocol === "mpp" ? "/mpp" : "/x402";
|
|
9078
9353
|
return `
|
|
9079
|
-
[payments.
|
|
9080
|
-
# Public seller rail mounted at
|
|
9354
|
+
[payments.b402_seller]
|
|
9355
|
+
# Public ${protocol.toUpperCase()} seller rail mounted at ${path49}. Credentials stay in
|
|
9081
9356
|
# <workspace>/.studio/.env.local and are issued per B402 environment.
|
|
9082
9357
|
enabled = true
|
|
9083
9358
|
# Decimal USD string; "0" is explicit FREE passthrough and bypasses B402.
|
|
@@ -9089,7 +9364,7 @@ pay_to = ""
|
|
|
9089
9364
|
# Payment settles before work; this bounds only the post-settlement work.
|
|
9090
9365
|
work_timeout_seconds = 60
|
|
9091
9366
|
# Optional Bazaar metadata can be added later as scalar JSON fields under
|
|
9092
|
-
# [payments.
|
|
9367
|
+
# [payments.b402_seller.bazaar]: info_json, schema_json, description,
|
|
9093
9368
|
# and route_template.
|
|
9094
9369
|
`;
|
|
9095
9370
|
}
|
|
@@ -9133,13 +9408,20 @@ project_name = "${projectName2}"
|
|
|
9133
9408
|
# captured at \`bag deploy --provider azure\` (deploys are delegated to bnbagent-deploy:
|
|
9134
9409
|
# explicit saved browser sign-in or ambient CI credentials; runtime secrets ride as Foundry CustomKeys).
|
|
9135
9410
|
agent_endpoint = ""
|
|
9411
|
+
http_endpoint = ""
|
|
9412
|
+
invocations_endpoint = ""
|
|
9413
|
+
a2a_endpoint = ""
|
|
9414
|
+
agent_card_endpoint = ""
|
|
9136
9415
|
`;
|
|
9137
9416
|
}
|
|
9138
9417
|
function renderAgentStudioToml(name, o) {
|
|
9139
9418
|
const model = o.model ?? PROVIDER_DEFAULT_MODEL[o.provider] ?? "";
|
|
9140
9419
|
const pieverseSection = o.provider === "pieverse-llm" ? renderPieverseSections() : "";
|
|
9141
9420
|
const x402Section = o.provider === "pieverse-llm" && o.budgetChoice === "enable" ? renderX402Section() : "";
|
|
9142
|
-
const x402SellerSection = o.rails === "b402" || o.rails === "both" ||
|
|
9421
|
+
const x402SellerSection = o.rails === "b402" || o.rails === "both" || hasB402PaymentFace(o.faces) ? renderB402SellerSection(
|
|
9422
|
+
o.b402Price,
|
|
9423
|
+
hasMppFace(o.faces) ? "mpp" : "x402"
|
|
9424
|
+
) : "";
|
|
9143
9425
|
const budgetSection = renderBudgetSection(o.budgetChoice);
|
|
9144
9426
|
const storageSection = renderAgentStorageSection(o.storageProvider);
|
|
9145
9427
|
const azureSection = o.runtime === "azure-foundry" ? renderAzureSection(name, o.azureAccount, o.azureProject) : "";
|
|
@@ -9174,9 +9456,9 @@ runtime = "${o.runtime}"
|
|
|
9174
9456
|
# MCP \u2014 streamable-HTTP /mcp: negotiate (signed quote) + notify_funded
|
|
9175
9457
|
# (verify \u2192 deliver \u2192 submit, synchronously within the tool call) +
|
|
9176
9458
|
# read-only chain tools.
|
|
9177
|
-
# X402 \u2014 publish
|
|
9178
|
-
#
|
|
9179
|
-
#
|
|
9459
|
+
# X402 or MPP \u2014 publish exactly one sibling B402 seller route (/x402 or
|
|
9460
|
+
# /mpp). Payment-only still uses A2A as the cloud carrier but hides
|
|
9461
|
+
# that native face at the public gateway.
|
|
9180
9462
|
protocols = ${JSON.stringify(o.faces)}
|
|
9181
9463
|
|
|
9182
9464
|
[network]
|
|
@@ -9191,7 +9473,7 @@ ${pieverseSection}${erc8183Section}
|
|
|
9191
9473
|
${storageSection}
|
|
9192
9474
|
${x402Section}${x402SellerSection}${budgetSection}${azureSection}`;
|
|
9193
9475
|
}
|
|
9194
|
-
function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402Price,
|
|
9476
|
+
function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402Price, paymentProtocol) {
|
|
9195
9477
|
const keyEnv = PROVIDER_KEY_ENV[provider];
|
|
9196
9478
|
const lines = [
|
|
9197
9479
|
"# \u26A0\uFE0F SECRETS \u2014 do NOT paste this file into issue trackers, logs, or AI chats.",
|
|
@@ -9255,7 +9537,7 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
|
|
|
9255
9537
|
"# bedrock uses AWS credentials chain \u2014 no API key env var needed."
|
|
9256
9538
|
);
|
|
9257
9539
|
}
|
|
9258
|
-
if (storageProvider
|
|
9540
|
+
if (storageProvider === "ipfs") {
|
|
9259
9541
|
lines.push(
|
|
9260
9542
|
"",
|
|
9261
9543
|
"# IPFS upload endpoint \u2014 the Agent pins deliverables at submit. Any",
|
|
@@ -9269,10 +9551,29 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
|
|
|
9269
9551
|
"# Public read gateway (default: https://ipfs.io/ipfs/):",
|
|
9270
9552
|
"# STORAGE_GATEWAY_URL=https://ipfs.io/ipfs/"
|
|
9271
9553
|
);
|
|
9554
|
+
} else if (storageProvider === "s3") {
|
|
9555
|
+
lines.push(
|
|
9556
|
+
"",
|
|
9557
|
+
"# User-owned S3 upload credentials. Prefer credential_mode='ambient'",
|
|
9558
|
+
"# on a self-deployed runtime with a scoped IAM role.",
|
|
9559
|
+
"DELIVERABLE_S3_ACCESS_KEY_ID=",
|
|
9560
|
+
"DELIVERABLE_S3_SECRET_ACCESS_KEY=",
|
|
9561
|
+
"# DELIVERABLE_S3_SESSION_TOKEN="
|
|
9562
|
+
);
|
|
9563
|
+
} else if (storageProvider === "azure-blob") {
|
|
9564
|
+
lines.push(
|
|
9565
|
+
"",
|
|
9566
|
+
"# User-owned Blob upload credential. A write SAS is the simplest path.",
|
|
9567
|
+
"# It is never included in the public deliverable URL.",
|
|
9568
|
+
"DELIVERABLE_AZURE_SAS_TOKEN=",
|
|
9569
|
+
"# Or use a complete service-principal set:",
|
|
9570
|
+
"# DELIVERABLE_AZURE_TENANT_ID=",
|
|
9571
|
+
"# DELIVERABLE_AZURE_CLIENT_ID=",
|
|
9572
|
+
"# DELIVERABLE_AZURE_CLIENT_SECRET="
|
|
9573
|
+
);
|
|
9272
9574
|
}
|
|
9273
9575
|
if (rails === "b402" || rails === "both") {
|
|
9274
9576
|
const free = x402SellerPricingState({ price_usd: b402Price }).kind === "free";
|
|
9275
|
-
const baseUrl = !free && network === "bsc-testnet" ? DEFAULT_B402_TESTNET_BASE_URL : "";
|
|
9276
9577
|
lines.push(
|
|
9277
9578
|
"",
|
|
9278
9579
|
"# B402 merchant credentials (issued per environment after manual approval).",
|
|
@@ -9281,14 +9582,25 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402
|
|
|
9281
9582
|
"# for that exact wallet and environment. Never reuse merchant credentials",
|
|
9282
9583
|
"# across wallets.",
|
|
9283
9584
|
`# Apply: ${B402_DEVELOPER_ACCOUNT_URL}`,
|
|
9284
|
-
"# PAID requires all four; all absent
|
|
9585
|
+
"# PAID requires all four; all absent or partial = dormant."
|
|
9285
9586
|
],
|
|
9286
|
-
|
|
9587
|
+
"B402_BASE_URL=",
|
|
9287
9588
|
"B402_CLIENT_ID=",
|
|
9288
9589
|
"B402_ACCESS_TOKEN=",
|
|
9289
9590
|
"B402_PRIVATE_KEY=",
|
|
9290
9591
|
"# (alternative one-line PKCS#8 DER: B402_PRIVATE_KEY_B64=)"
|
|
9291
9592
|
);
|
|
9593
|
+
if (paymentProtocol === "mpp") {
|
|
9594
|
+
lines.push(
|
|
9595
|
+
"",
|
|
9596
|
+
"# MPP challenge authenticity. Generate at least 32 random bytes.",
|
|
9597
|
+
"MPP_SECRET_KEY=",
|
|
9598
|
+
"# Stable public realm/hostname used in MPP challenges.",
|
|
9599
|
+
"MPP_REALM=",
|
|
9600
|
+
"# Production must inject a durable atomic replayStore in runtime code.",
|
|
9601
|
+
"# Managed single-process test sandboxes may set B402_REPLAY_STORE_MODE=memory."
|
|
9602
|
+
);
|
|
9603
|
+
}
|
|
9292
9604
|
}
|
|
9293
9605
|
if (rails === "8183" || rails === "both") {
|
|
9294
9606
|
lines.push(
|
|
@@ -9308,8 +9620,9 @@ function renderAgentReadme(name, faces, destination, walletKind2) {
|
|
|
9308
9620
|
const mode = recipeModeOf(faces);
|
|
9309
9621
|
const entry = `src/${entryStemOf(faces)}.ts`;
|
|
9310
9622
|
const port = String(devPortOf(faces));
|
|
9311
|
-
const
|
|
9312
|
-
const
|
|
9623
|
+
const paymentOnly = faces.length === 1 && hasB402PaymentFace(faces);
|
|
9624
|
+
const paymentPath = hasMppFace(faces) ? "/mpp" : "/x402";
|
|
9625
|
+
const surface = mode === "both" ? "A2A natively on `0.0.0.0:9000` (agent card + JSON-RPC `message/send`) and buffered streamable-HTTP MCP on `/mcp` through the platform envelope tunnel" : mode === "mcp" ? "streamable-HTTP MCP on `0.0.0.0:8000/mcp` (negotiate + notify_funded sync-delivery + read-only chain tools)" : paymentOnly ? `only the public \`${paymentPath}\` seller face; AgentCore uses an internal A2A-native process on port 9000 but the gateway suppresses A2A discovery` : "`serveA2a`: the agent card at `/.well-known/agent-card.json` + JSON-RPC `message/send` on `0.0.0.0:9000`";
|
|
9313
9626
|
const filesA2a = `- \`src/unifiedMain.ts\` \u2014 unified serving entrypoint (A2A on port 9000 + Foundry invocations on 8088).
|
|
9314
9627
|
- \`src/executor.ts\` \u2014 the SellerAgentExecutor: the negotiate + notify_funded A2A skills.
|
|
9315
9628
|
- \`src/agentCard.ts\` \u2014 the discoverable AgentCard (+ OAuth2/Cognito scheme).`;
|
|
@@ -9808,7 +10121,7 @@ async function runOnboarding(o) {
|
|
|
9808
10121
|
process.env.WALLET_PASSWORD = savedPw;
|
|
9809
10122
|
}
|
|
9810
10123
|
}
|
|
9811
|
-
if (o.storageProvider
|
|
10124
|
+
if (o.storageProvider === "ipfs") {
|
|
9812
10125
|
await onboardIpfsKey(envLocalPath20(agentRoot2), o.ipfsKey);
|
|
9813
10126
|
}
|
|
9814
10127
|
if (address) {
|
|
@@ -9942,7 +10255,7 @@ Guide: the bnbagent-studio-using-twak-wallet.md reference (installed by \`bag sk
|
|
|
9942
10255
|
} finally {
|
|
9943
10256
|
process.chdir(savedCwd);
|
|
9944
10257
|
}
|
|
9945
|
-
if (o.storageProvider
|
|
10258
|
+
if (o.storageProvider === "ipfs") {
|
|
9946
10259
|
const { envLocalPath: envLocalPath20 } = await import("@bnbagent/studio-runtime/config");
|
|
9947
10260
|
await onboardIpfsKey(envLocalPath20(agentRoot2), o.ipfsKey);
|
|
9948
10261
|
}
|
|
@@ -10187,15 +10500,46 @@ NOTE \u2014 destination = "platform" (48h TESTNET TRIAL on the BNB Chain managed
|
|
|
10187
10500
|
address on mainnet is later soft-warned.`
|
|
10188
10501
|
);
|
|
10189
10502
|
}
|
|
10190
|
-
function printLocalStorageWarning() {
|
|
10503
|
+
function printLocalStorageWarning(destination, runtime) {
|
|
10504
|
+
if (destination === "platform") {
|
|
10505
|
+
const managedStorage = runtime === "azure-foundry" ? "Azure Blob" : "S3";
|
|
10506
|
+
printErr(
|
|
10507
|
+
`
|
|
10508
|
+
\u26A0 WARNING \u2014 storage.kind = "local" (DEV-ONLY):
|
|
10509
|
+
\xB7 Local runs write deliverables to disk (file://).
|
|
10510
|
+
\xB7 Platform deploy does not use this local path and does not require BYOS:
|
|
10511
|
+
the API injects an agent-scoped upload token and stores deliverables in
|
|
10512
|
+
managed ${managedStorage} behind the bnbagent-api URL.
|
|
10513
|
+
\xB7 Deliverables share the Agent lifecycle; after the Agent is deleted,
|
|
10514
|
+
their API URLs return 404.`
|
|
10515
|
+
);
|
|
10516
|
+
return;
|
|
10517
|
+
}
|
|
10518
|
+
if (runtime === "azure-foundry") {
|
|
10519
|
+
printErr(
|
|
10520
|
+
`
|
|
10521
|
+
\u26A0 WARNING \u2014 storage.kind = "local" (DEV-ONLY):
|
|
10522
|
+
\xB7 Local runs write deliverables to disk (file://).
|
|
10523
|
+
\xB7 Managed Platform deploy (\`bag deploy --provider bnb --backend azure\`)
|
|
10524
|
+
does not use this local path and does not require BYOS: the API injects an
|
|
10525
|
+
agent-scoped upload token and stores deliverables in managed Azure Blob.
|
|
10526
|
+
Deliverables share the Agent lifecycle and their API URLs return 404 after
|
|
10527
|
+
Agent deletion.
|
|
10528
|
+
\xB7 Self-hosted Azure deploy (\`bag deploy --provider azure\`) does require
|
|
10529
|
+
durable BYOS via IPFS, S3, or Azure Blob; its credentials, public URL,
|
|
10530
|
+
cost, and retention remain your responsibility.`
|
|
10531
|
+
);
|
|
10532
|
+
return;
|
|
10533
|
+
}
|
|
10191
10534
|
printErr(
|
|
10192
10535
|
`
|
|
10193
10536
|
\u26A0 WARNING \u2014 storage.kind = "local" (DEV-ONLY):
|
|
10194
10537
|
\xB7 Deliverables are written to local disk (file://) and served from this
|
|
10195
10538
|
machine \u2014 works for \`bag dev\`, but does NOT survive \`bag deploy\` (the file
|
|
10196
10539
|
isn't in the deployed runtime \u2192 the buyer gets a 404).
|
|
10197
|
-
\xB7 Before \`bag deploy\`:
|
|
10198
|
-
|
|
10540
|
+
\xB7 Before \`bag deploy\`: configure durable BYOS with IPFS, S3, or Azure Blob.
|
|
10541
|
+
The storage account, public URL/CDN, credentials, cost, and retention remain
|
|
10542
|
+
your responsibility for self-hosted deployment.
|
|
10199
10543
|
(\`bag deploy prepare\` will re-flag this as CRITICAL until you do.)`
|
|
10200
10544
|
);
|
|
10201
10545
|
}
|
|
@@ -10223,8 +10567,8 @@ function entrypointHint(faces) {
|
|
|
10223
10567
|
if (hasMcpFace(faces)) {
|
|
10224
10568
|
return "app/agent/src/mcpMain.ts = MCP entrypoint (build your value here)";
|
|
10225
10569
|
}
|
|
10226
|
-
if (faces.length === 1 &&
|
|
10227
|
-
return
|
|
10570
|
+
if (faces.length === 1 && hasB402PaymentFace(faces)) {
|
|
10571
|
+
return `app/agent/src/unifiedMain.ts = ${hasMppFace(faces) ? "MPP" : "X402"}-only runtime entrypoint (build your value here)`;
|
|
10228
10572
|
}
|
|
10229
10573
|
return "app/agent/src/unifiedMain.ts + executor.ts = A2A entrypoint (build your value here)";
|
|
10230
10574
|
}
|
|
@@ -10523,11 +10867,14 @@ function printConfigSummary(agentRoot2) {
|
|
|
10523
10867
|
return cur;
|
|
10524
10868
|
};
|
|
10525
10869
|
const shown = (v, unset = "<unset \u2014 fill before going live>") => v === void 0 || v === null || v === "" ? unset : String(v);
|
|
10526
|
-
const
|
|
10527
|
-
const faces = stackFaces(
|
|
10528
|
-
const payments =
|
|
10529
|
-
const erc8183 =
|
|
10530
|
-
const
|
|
10870
|
+
const table2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
10871
|
+
const faces = stackFaces(table2(agent.stack), table2(agent.payments));
|
|
10872
|
+
const payments = table2(agent.payments);
|
|
10873
|
+
const erc8183 = table2(payments.erc8183);
|
|
10874
|
+
const canonicalSeller = table2(payments.b402_seller);
|
|
10875
|
+
const x402Seller = Object.keys(canonicalSeller).length > 0 ? canonicalSeller : table2(payments.x402_seller);
|
|
10876
|
+
const paymentProtocol = hasMppFace(faces) ? "MPP+B402" : "x402";
|
|
10877
|
+
const paymentPath = hasMppFace(faces) ? "/mpp" : "/x402";
|
|
10531
10878
|
const hasErc8183 = Object.keys(erc8183).length > 0;
|
|
10532
10879
|
const hasX402Seller = x402Seller.enabled === true;
|
|
10533
10880
|
const x402Pricing = x402SellerPricingState(x402Seller);
|
|
@@ -10547,8 +10894,10 @@ function printConfigSummary(agentRoot2) {
|
|
|
10547
10894
|
const contractOverrides = erc8183ContractOverrideState(
|
|
10548
10895
|
erc8183ContractEnvForProject(agentRoot2)
|
|
10549
10896
|
);
|
|
10897
|
+
const destination = shown(g(agent, "deploy", "destination"), "self");
|
|
10898
|
+
const runtime = shown(g(agent, "stack", "runtime"), "?");
|
|
10550
10899
|
const storageKind2 = shown(g(agent, "storage", "kind"), "?");
|
|
10551
|
-
const storageDesc = storageKind2 === "ipfs" ? "IPFS (durable, public)" : "local disk (offline dev only)";
|
|
10900
|
+
const storageDesc = storageKind2 === "ipfs" ? "IPFS (durable, public)" : storageKind2 === "s3" ? "user-owned S3 (durable BYOS)" : storageKind2 === "azure-blob" ? "user-owned Azure Blob (durable BYOS)" : destination === "platform" ? "local disk for dev; Platform injects managed object storage" : runtime === "azure-foundry" ? "local disk for dev; Platform injects Azure Blob; self-hosted needs BYOS" : "local disk (offline dev only)";
|
|
10552
10901
|
const walletKind2 = shown(g(agent, "wallet", "kind"), "evm-local");
|
|
10553
10902
|
const walletAddr = g(agent, "wallet", "address");
|
|
10554
10903
|
const budgetOn = g(agent, "budget", "enabled");
|
|
@@ -10560,11 +10909,11 @@ function printConfigSummary(agentRoot2) {
|
|
|
10560
10909
|
printOut(bar);
|
|
10561
10910
|
printOut("");
|
|
10562
10911
|
printOut(
|
|
10563
|
-
onlyX402 && isFreeX402 ?
|
|
10912
|
+
onlyX402 && isFreeX402 ? `Per request \u2014 how this FREE ${paymentProtocol} seller delivers work:` : onlyX402 ? `Per request \u2014 how this ${paymentProtocol} seller earns $U:` : isFreePrice ? "Per task \u2014 how this FREE seller delivers work:" : "Per task \u2014 how this seller earns $U:"
|
|
10564
10913
|
);
|
|
10565
10914
|
if (onlyX402 && isFreeX402) {
|
|
10566
10915
|
printOut(
|
|
10567
|
-
|
|
10916
|
+
` anonymous request reaches ${paymentPath} \u2192 B402 verify/settle is bypassed \u2192`
|
|
10568
10917
|
);
|
|
10569
10918
|
printOut(
|
|
10570
10919
|
" the Agent runs the work and returns the result with no payment."
|
|
@@ -10670,11 +11019,11 @@ function printConfigSummary(agentRoot2) {
|
|
|
10670
11019
|
}
|
|
10671
11020
|
if (hasX402Seller) {
|
|
10672
11021
|
printOut(
|
|
10673
|
-
isFreeX402 ?
|
|
11022
|
+
isFreeX402 ? ` ${paymentProtocol.padEnd(10)}: FREE \u2014 anonymous passthrough; B402 verify/settle is bypassed` : ` ${paymentProtocol.padEnd(10)}: PAID \u2014 $${shown(x402Seller.price_usd, DEFAULT_B402_PRICE_USD)} per request through B402`
|
|
10674
11023
|
);
|
|
10675
11024
|
}
|
|
10676
11025
|
printOut(
|
|
10677
|
-
` storage : ${storageDesc} (options: local | ipfs
|
|
11026
|
+
` storage : ${storageDesc} (options: local | ipfs | s3 | azure-blob)`
|
|
10678
11027
|
);
|
|
10679
11028
|
if (budgetOn !== void 0) {
|
|
10680
11029
|
const bt = budgetOn ? "ENABLED \u2014 wallet may auto-refill within your caps" : "disabled \u2014 wallet is NEVER auto-charged (opt in: `bag budget enable`)";
|
|
@@ -10694,14 +11043,21 @@ function printConfigSummary(agentRoot2) {
|
|
|
10694
11043
|
);
|
|
10695
11044
|
}
|
|
10696
11045
|
if (hasX402Seller && !isFreeX402) {
|
|
10697
|
-
todo.push(
|
|
11046
|
+
todo.push(
|
|
11047
|
+
`PAID ${paymentPath} onboarding \u2014 ${B402_PAID_ONBOARDING_GUIDANCE}`
|
|
11048
|
+
);
|
|
11049
|
+
if (hasMppFace(faces)) {
|
|
11050
|
+
todo.push(
|
|
11051
|
+
"set MPP_SECRET_KEY / MPP_REALM and inject a durable production replayStore"
|
|
11052
|
+
);
|
|
11053
|
+
}
|
|
10698
11054
|
todo.push(
|
|
10699
11055
|
"then set B402_BASE_URL / CLIENT_ID / ACCESS_TOKEN / private key"
|
|
10700
11056
|
);
|
|
10701
11057
|
}
|
|
10702
|
-
if (storageKind2 === "local") {
|
|
11058
|
+
if (storageKind2 === "local" && destination !== "platform" && runtime !== "azure-foundry") {
|
|
10703
11059
|
todo.push(
|
|
10704
|
-
"[storage].kind \u2014 switch to
|
|
11060
|
+
"[storage].kind \u2014 switch to durable BYOS (ipfs, s3, or azure-blob) before deploy; local disk is NOT deployable"
|
|
10705
11061
|
);
|
|
10706
11062
|
}
|
|
10707
11063
|
if (todo.length > 0) {
|
|
@@ -10774,6 +11130,34 @@ function runtimeEnvKeysCore(cfg, resolvable) {
|
|
|
10774
11130
|
if (resolvable("STORAGE_API_KEY")) {
|
|
10775
11131
|
keys.push("STORAGE_API_KEY");
|
|
10776
11132
|
}
|
|
11133
|
+
} else if (storageKind2 === "s3") {
|
|
11134
|
+
const storage = tableOf8(cfg, "storage");
|
|
11135
|
+
const credentialMode = String(storage.credential_mode ?? "env");
|
|
11136
|
+
if (credentialMode !== "ambient") {
|
|
11137
|
+
keys.push(
|
|
11138
|
+
"DELIVERABLE_S3_ACCESS_KEY_ID",
|
|
11139
|
+
"DELIVERABLE_S3_SECRET_ACCESS_KEY"
|
|
11140
|
+
);
|
|
11141
|
+
if (resolvable("DELIVERABLE_S3_SESSION_TOKEN")) {
|
|
11142
|
+
keys.push("DELIVERABLE_S3_SESSION_TOKEN");
|
|
11143
|
+
}
|
|
11144
|
+
}
|
|
11145
|
+
} else if (storageKind2 === "azure-blob") {
|
|
11146
|
+
const storage = tableOf8(cfg, "storage");
|
|
11147
|
+
const credentialMode = String(storage.credential_mode ?? "env");
|
|
11148
|
+
if (credentialMode !== "ambient") {
|
|
11149
|
+
const servicePrincipalKeys = [
|
|
11150
|
+
"DELIVERABLE_AZURE_TENANT_ID",
|
|
11151
|
+
"DELIVERABLE_AZURE_CLIENT_ID",
|
|
11152
|
+
"DELIVERABLE_AZURE_CLIENT_SECRET"
|
|
11153
|
+
];
|
|
11154
|
+
const usesServicePrincipal = servicePrincipalKeys.some(resolvable);
|
|
11155
|
+
if (resolvable("DELIVERABLE_AZURE_SAS_TOKEN") || !usesServicePrincipal) {
|
|
11156
|
+
keys.push("DELIVERABLE_AZURE_SAS_TOKEN");
|
|
11157
|
+
} else {
|
|
11158
|
+
keys.push(...servicePrincipalKeys);
|
|
11159
|
+
}
|
|
11160
|
+
}
|
|
10777
11161
|
}
|
|
10778
11162
|
if (resolvable("RPC_URL")) {
|
|
10779
11163
|
keys.push("RPC_URL");
|
|
@@ -10792,8 +11176,16 @@ function runtimeEnvKeysCore(cfg, resolvable) {
|
|
|
10792
11176
|
}
|
|
10793
11177
|
}
|
|
10794
11178
|
if (X402_CAPABLE_RUNTIMES.has(runtimeName) && x402SellerUsesB402(cfg)) {
|
|
10795
|
-
|
|
10796
|
-
|
|
11179
|
+
const b402Complete = resolvable("B402_BASE_URL") && resolvable("B402_CLIENT_ID") && resolvable("B402_ACCESS_TOKEN") && (resolvable("B402_PRIVATE_KEY") || resolvable("B402_PRIVATE_KEY_B64"));
|
|
11180
|
+
if (b402Complete) {
|
|
11181
|
+
for (const key of B402_RUNTIME_KEYS) {
|
|
11182
|
+
if (resolvable(key)) keys.push(key);
|
|
11183
|
+
}
|
|
11184
|
+
if (b402PaymentProtocol(cfg) === "mpp") {
|
|
11185
|
+
for (const key of MPP_RUNTIME_KEYS) {
|
|
11186
|
+
if (resolvable(key)) keys.push(key);
|
|
11187
|
+
}
|
|
11188
|
+
}
|
|
10797
11189
|
}
|
|
10798
11190
|
}
|
|
10799
11191
|
return keys;
|
|
@@ -11451,12 +11843,12 @@ Switch to Node \u226522 so it wins on PATH, then reopen the shell and retry. Ver
|
|
|
11451
11843
|
`bag dev: test with curl / an A2A client sending a {"skill":...} DataPart \u2014 NOT the :8081 chat box (it can't build a seller's structured skills).`
|
|
11452
11844
|
);
|
|
11453
11845
|
}
|
|
11454
|
-
if (
|
|
11455
|
-
const
|
|
11456
|
-
|
|
11457
|
-
);
|
|
11846
|
+
if (hasB402PaymentFace(faces)) {
|
|
11847
|
+
const cfg = loadStudioToml11(path23.join(agentDir, "studio.toml"));
|
|
11848
|
+
const free = x402SellerIsFree(cfg);
|
|
11849
|
+
const paymentProtocol = b402PaymentProtocol(cfg);
|
|
11458
11850
|
printOut(
|
|
11459
|
-
free ? `bag dev:
|
|
11851
|
+
free ? `bag dev: ${paymentProtocol} seller at http://localhost:${agentPort}/${paymentProtocol} (FREE \u2014 anonymous passthrough; B402 bypassed)` : `bag dev: ${paymentProtocol} seller at http://localhost:${agentPort}/${paymentProtocol} (PAID \u2014 active when B402${paymentProtocol === "mpp" ? " and MPP" : ""} credentials are configured)`
|
|
11460
11852
|
);
|
|
11461
11853
|
}
|
|
11462
11854
|
await launch();
|
|
@@ -12001,9 +12393,11 @@ function dockerUsesNode22OrNewer(source) {
|
|
|
12001
12393
|
}
|
|
12002
12394
|
function x402SellerRailChecks(agentRoot2, cfg, target) {
|
|
12003
12395
|
const payments = tableOf2(cfg, "payments");
|
|
12004
|
-
const rawSeller = payments.x402_seller;
|
|
12396
|
+
const rawSeller = payments.b402_seller !== void 0 ? payments.b402_seller : payments.x402_seller;
|
|
12005
12397
|
const sellerPresent = rawSeller !== null && typeof rawSeller === "object" && !Array.isArray(rawSeller);
|
|
12006
12398
|
const seller = sellerPresent ? rawSeller : {};
|
|
12399
|
+
const paymentProtocol = b402PaymentProtocol(cfg);
|
|
12400
|
+
const configSection = payments.b402_seller !== void 0 ? "payments.b402_seller" : "payments.x402_seller";
|
|
12007
12401
|
const rails = commerceRails(cfg);
|
|
12008
12402
|
const credentials = b402Credentials(agentRoot2);
|
|
12009
12403
|
const out = [];
|
|
@@ -12015,14 +12409,14 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
|
|
|
12015
12409
|
out.push({
|
|
12016
12410
|
level: Level.CRITICAL,
|
|
12017
12411
|
name: "x402_price_invalid",
|
|
12018
|
-
message:
|
|
12412
|
+
message: `[${configSection}].price_usd must be a non-negative decimal string; "0" is valid and enables the free passthrough.`,
|
|
12019
12413
|
details: {}
|
|
12020
12414
|
});
|
|
12021
12415
|
} else if (free) {
|
|
12022
12416
|
out.push({
|
|
12023
12417
|
level: Level.INFO,
|
|
12024
12418
|
name: "x402_zero_price_enabled",
|
|
12025
|
-
message:
|
|
12419
|
+
message: `The ${paymentProtocol.toUpperCase()} seller is explicitly FREE. /${paymentProtocol} is anonymous, and B402 verify/settle, token payment, and settlement audit are bypassed.`,
|
|
12026
12420
|
details: { price_usd: pricing.priceUsd, facilitator_bypassed: true }
|
|
12027
12421
|
});
|
|
12028
12422
|
}
|
|
@@ -12031,7 +12425,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
|
|
|
12031
12425
|
out.push({
|
|
12032
12426
|
level: Level.CRITICAL,
|
|
12033
12427
|
name: "x402_assets_unsupported",
|
|
12034
|
-
message:
|
|
12428
|
+
message: `[${configSection}].assets must be exactly ["U"] in v1.`,
|
|
12035
12429
|
details: {}
|
|
12036
12430
|
});
|
|
12037
12431
|
}
|
|
@@ -12040,7 +12434,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
|
|
|
12040
12434
|
out.push({
|
|
12041
12435
|
level: Level.CRITICAL,
|
|
12042
12436
|
name: "x402_work_timeout_invalid",
|
|
12043
|
-
message:
|
|
12437
|
+
message: `[${configSection}].work_timeout_seconds must be a positive integer.`,
|
|
12044
12438
|
details: {}
|
|
12045
12439
|
});
|
|
12046
12440
|
}
|
|
@@ -12078,6 +12472,35 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
|
|
|
12078
12472
|
details: {}
|
|
12079
12473
|
});
|
|
12080
12474
|
}
|
|
12475
|
+
if (paid && paymentProtocol === "mpp") {
|
|
12476
|
+
const envPath = envLocalPath12(agentRoot2);
|
|
12477
|
+
const secretKey = process.env.MPP_SECRET_KEY || getEnvVar(envPath, "MPP_SECRET_KEY");
|
|
12478
|
+
const realm = process.env.MPP_REALM || getEnvVar(envPath, "MPP_REALM");
|
|
12479
|
+
if (!secretKey || Buffer.byteLength(secretKey, "utf8") < 32) {
|
|
12480
|
+
out.push({
|
|
12481
|
+
level: Level.CRITICAL,
|
|
12482
|
+
name: "mpp_secret_key_missing",
|
|
12483
|
+
message: "MPP+B402 paid mode requires MPP_SECRET_KEY with at least 32 UTF-8 bytes.",
|
|
12484
|
+
details: {}
|
|
12485
|
+
});
|
|
12486
|
+
}
|
|
12487
|
+
if (!realm) {
|
|
12488
|
+
out.push({
|
|
12489
|
+
level: Level.CRITICAL,
|
|
12490
|
+
name: "mpp_realm_missing",
|
|
12491
|
+
message: "MPP+B402 paid mode requires a stable MPP_REALM hostname.",
|
|
12492
|
+
details: {}
|
|
12493
|
+
});
|
|
12494
|
+
}
|
|
12495
|
+
if (!target.startsWith("platform")) {
|
|
12496
|
+
out.push({
|
|
12497
|
+
level: Level.WARNING,
|
|
12498
|
+
name: "mpp_replay_store_external",
|
|
12499
|
+
message: "Production MPP+B402 must inject a durable atomic replayStore shared by every runtime instance; Store.memory is development/single-process only.",
|
|
12500
|
+
details: {}
|
|
12501
|
+
});
|
|
12502
|
+
}
|
|
12503
|
+
}
|
|
12081
12504
|
if ((!rails.x402 || free) && credentials.any) {
|
|
12082
12505
|
out.push({
|
|
12083
12506
|
level: Level.WARNING,
|
|
@@ -12152,7 +12575,7 @@ async function checkCommerceReady(root, target) {
|
|
|
12152
12575
|
out.push({
|
|
12153
12576
|
level: Level.CRITICAL,
|
|
12154
12577
|
name: "commerce_no_rail",
|
|
12155
|
-
message: "No commerce rail is configured. Enable [payments.erc8183] or [payments.
|
|
12578
|
+
message: "No commerce rail is configured. Enable [payments.erc8183] or [payments.b402_seller] before deploy.",
|
|
12156
12579
|
details: {}
|
|
12157
12580
|
});
|
|
12158
12581
|
}
|
|
@@ -13237,8 +13660,8 @@ function agentTable(agentRoot2, key) {
|
|
|
13237
13660
|
} catch {
|
|
13238
13661
|
return {};
|
|
13239
13662
|
}
|
|
13240
|
-
const
|
|
13241
|
-
return
|
|
13663
|
+
const table2 = cfg[key];
|
|
13664
|
+
return table2 !== null && typeof table2 === "object" && !Array.isArray(table2) ? table2 : {};
|
|
13242
13665
|
}
|
|
13243
13666
|
function deployTable(agentRoot2) {
|
|
13244
13667
|
return agentTable(agentRoot2, "deploy");
|
|
@@ -13281,16 +13704,22 @@ function checkPackagingDrift(root, destination, walletKind2, force) {
|
|
|
13281
13704
|
return null;
|
|
13282
13705
|
}
|
|
13283
13706
|
if (packagingChannel(persisted) !== packagingChannel(derived)) {
|
|
13284
|
-
|
|
13707
|
+
const historicalManagedAzureZipLock = stackRuntime(agentRoot2) === "azure-foundry" && packagingChannel(persisted) === "zip" && packagingChannel(derived) === "container";
|
|
13708
|
+
if (historicalManagedAzureZipLock) {
|
|
13709
|
+
printErr(
|
|
13710
|
+
"note: repairing the historical managed-Azure packaging lock from 'zip' to 'container' after this successful deploy."
|
|
13711
|
+
);
|
|
13712
|
+
} else if (force) {
|
|
13285
13713
|
printErr(
|
|
13286
13714
|
`warning: packaging lock '${persisted}' differs from the derived '${derived}' (wallet.kind/destination changed); proceeding under --force without re-stamping the lock.`
|
|
13287
13715
|
);
|
|
13288
13716
|
return null;
|
|
13717
|
+
} else {
|
|
13718
|
+
printErr(
|
|
13719
|
+
`error: packaging is locked to '${persisted}' but this deploy derives '${derived}' (wallet.kind/destination changed). In-place packaging switch is provisionally disallowed pending live-verify; run 'bag deploy destroy' then redeploy, or pass --force to override.`
|
|
13720
|
+
);
|
|
13721
|
+
return 1;
|
|
13289
13722
|
}
|
|
13290
|
-
printErr(
|
|
13291
|
-
`error: packaging is locked to '${persisted}' but this deploy derives '${derived}' (wallet.kind/destination changed). In-place packaging switch is provisionally disallowed pending live-verify; run 'bag deploy destroy' then redeploy, or pass --force to override.`
|
|
13292
|
-
);
|
|
13293
|
-
return 1;
|
|
13294
13723
|
}
|
|
13295
13724
|
const dockerfile = path29.join(agentRoot2, "Dockerfile");
|
|
13296
13725
|
let dockerfilePresent = false;
|
|
@@ -14028,13 +14457,13 @@ var checkPlatformDockerAvailable = async (root, target) => {
|
|
|
14028
14457
|
}
|
|
14029
14458
|
];
|
|
14030
14459
|
};
|
|
14031
|
-
var checkPlatformStorageEndpoint = async (
|
|
14032
|
-
|
|
14033
|
-
|
|
14034
|
-
|
|
14035
|
-
|
|
14036
|
-
|
|
14037
|
-
|
|
14460
|
+
var checkPlatformStorageEndpoint = async (_root, _target) => [
|
|
14461
|
+
{
|
|
14462
|
+
level: Level.INFO,
|
|
14463
|
+
name: "platform_managed_deliverable_storage",
|
|
14464
|
+
message: "the managed platform injects an agent-scoped deliverable upload token; self-hosted IPFS/S3/Blob credentials are not sent to the platform."
|
|
14465
|
+
}
|
|
14466
|
+
];
|
|
14038
14467
|
var checkPlatformMcpSyncTimeout = async (root, _target) => {
|
|
14039
14468
|
const data = loadAgent(root);
|
|
14040
14469
|
const faces = stackFaces(tableOf11(data, "stack"), tableOf11(data, "payments"));
|
|
@@ -14105,7 +14534,6 @@ var criticalChecks = [
|
|
|
14105
14534
|
checkPlatformLoggedIn,
|
|
14106
14535
|
checkDeployCliRunnable,
|
|
14107
14536
|
checkPlatformDockerAvailable,
|
|
14108
|
-
checkPlatformStorageEndpoint,
|
|
14109
14537
|
checkTwakCustomContractsUnsupported,
|
|
14110
14538
|
checkAltanaCustomContractsUnsupported,
|
|
14111
14539
|
checkLocalKeystoreExists,
|
|
@@ -14127,6 +14555,7 @@ var warningChecks = [
|
|
|
14127
14555
|
var infoChecks = [
|
|
14128
14556
|
checkPlatformTrialStatus,
|
|
14129
14557
|
checkWalletBalanceSummary,
|
|
14558
|
+
checkPlatformStorageEndpoint,
|
|
14130
14559
|
checkPlatformFundingHint
|
|
14131
14560
|
];
|
|
14132
14561
|
|
|
@@ -14407,15 +14836,15 @@ async function runVerify(opts) {
|
|
|
14407
14836
|
exitCode: 1
|
|
14408
14837
|
});
|
|
14409
14838
|
}
|
|
14410
|
-
const
|
|
14839
|
+
const tableOf18 = (key) => {
|
|
14411
14840
|
const v = data[key];
|
|
14412
14841
|
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
14413
14842
|
};
|
|
14414
|
-
const network = opts.network || String(
|
|
14843
|
+
const network = opts.network || String(tableOf18("network").default ?? "bsc-testnet");
|
|
14415
14844
|
const protocol = nativeProtocolOf(
|
|
14416
|
-
stackFaces(
|
|
14845
|
+
stackFaces(tableOf18("stack"), tableOf18("payments"))
|
|
14417
14846
|
);
|
|
14418
|
-
const identity =
|
|
14847
|
+
const identity = tableOf18("identity");
|
|
14419
14848
|
const existingAgentId = identity.agent_id;
|
|
14420
14849
|
const accessDescription = accessSummaryForAgentcore(root, { protocol });
|
|
14421
14850
|
let agentId = null;
|
|
@@ -14576,6 +15005,13 @@ var WALLET_MATERIAL_KEYS = [
|
|
|
14576
15005
|
TWAK_WALLET_JSON_ENV2,
|
|
14577
15006
|
ALTANA_SESSION_ENV2
|
|
14578
15007
|
];
|
|
15008
|
+
function withoutPlatformByosStorageSecrets(secrets) {
|
|
15009
|
+
return Object.fromEntries(
|
|
15010
|
+
Object.entries(secrets).filter(
|
|
15011
|
+
([key]) => !key.startsWith("STORAGE_") && !key.startsWith("DELIVERABLE_S3_") && !key.startsWith("DELIVERABLE_AZURE_")
|
|
15012
|
+
)
|
|
15013
|
+
);
|
|
15014
|
+
}
|
|
14579
15015
|
var RETIRED_AGENT_STATES = /* @__PURE__ */ new Set([
|
|
14580
15016
|
"deleted",
|
|
14581
15017
|
"retired",
|
|
@@ -14804,6 +15240,7 @@ function buyerAccess(root) {
|
|
|
14804
15240
|
let a2aUrl = null;
|
|
14805
15241
|
let mcpUrl = null;
|
|
14806
15242
|
let x402Url = null;
|
|
15243
|
+
let mppUrl = null;
|
|
14807
15244
|
let scope = null;
|
|
14808
15245
|
if (agentId) {
|
|
14809
15246
|
const rt = `${base}/v1/rt/${agentId}`;
|
|
@@ -14815,7 +15252,10 @@ function buyerAccess(root) {
|
|
|
14815
15252
|
mcpUrl = `${rt}/mcp`;
|
|
14816
15253
|
buyerUrl ??= mcpUrl;
|
|
14817
15254
|
}
|
|
14818
|
-
if (
|
|
15255
|
+
if (hasMppFace(faces)) {
|
|
15256
|
+
mppUrl = `${rt}/mpp`;
|
|
15257
|
+
buyerUrl ??= mppUrl;
|
|
15258
|
+
} else if (hasB402PaymentFace(faces)) {
|
|
14819
15259
|
x402Url = `${rt}/x402`;
|
|
14820
15260
|
buyerUrl ??= x402Url;
|
|
14821
15261
|
}
|
|
@@ -14832,7 +15272,8 @@ function buyerAccess(root) {
|
|
|
14832
15272
|
buyerUrl,
|
|
14833
15273
|
a2aUrl,
|
|
14834
15274
|
mcpUrl,
|
|
14835
|
-
x402Url
|
|
15275
|
+
x402Url,
|
|
15276
|
+
mppUrl
|
|
14836
15277
|
};
|
|
14837
15278
|
}
|
|
14838
15279
|
function resolveSlug(root) {
|
|
@@ -14874,28 +15315,31 @@ async function platformAgent(opts) {
|
|
|
14874
15315
|
`note: deploying to the BNB Chain managed platform (backend=${backend}) \u2014 a 48h TESTNET trial sandbox (network is forced to bsc-testnet; the runtime is auto-reclaimed at 48h). Not a production seller.`
|
|
14875
15316
|
);
|
|
14876
15317
|
const platformCmd = opts.runPlatformCommand ?? runPlatformAccountCommand;
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
|
|
15318
|
+
const cfg = loadAgentCfg(root);
|
|
15319
|
+
const faces = stackFaces(tableOf12(cfg, "stack"), tableOf12(cfg, "payments"));
|
|
15320
|
+
const paymentProtocol = b402PaymentProtocol(cfg);
|
|
15321
|
+
const paymentFace = hasB402PaymentFace(faces);
|
|
15322
|
+
if (backend === "azure" || paymentFace && paymentProtocol === "mpp") {
|
|
15323
|
+
const protocol2 = nativeProtocolOf(faces);
|
|
14881
15324
|
const runtimeProfile = protocol2 === "A2A" ? "foundry-responses-v1" : "foundry-invocations-v1";
|
|
14882
15325
|
const capability = await platformCmd(["capabilities"], { json: true });
|
|
14883
15326
|
const platform = isJsonObject(capability.data.platform) ? capability.data.platform : {};
|
|
14884
15327
|
const backends = isJsonObject(platform.backends) ? platform.backends : {};
|
|
14885
|
-
const
|
|
15328
|
+
const backendCapability = isJsonObject(backends[backend]) ? backends[backend] : {};
|
|
15329
|
+
const azure = backend === "azure" ? backendCapability : {};
|
|
14886
15330
|
const profiles = Array.isArray(azure.runtimeProfiles) ? azure.runtimeProfiles.map(String) : [];
|
|
14887
15331
|
const protocols = Array.isArray(azure.protocols) ? azure.protocols.map(String) : [];
|
|
14888
|
-
if (capability.code !== 0 || azure.enabled !== true || !profiles.includes(runtimeProfile) || !protocols.includes(protocol2)) {
|
|
15332
|
+
if (backend === "azure" && (capability.code !== 0 || azure.enabled !== true || !profiles.includes(runtimeProfile) || !protocols.includes(protocol2))) {
|
|
14889
15333
|
warn(
|
|
14890
15334
|
`error: the managed platform does not currently accept Azure ${runtimeProfile}/${protocol2}. No build or upload was started.`
|
|
14891
15335
|
);
|
|
14892
15336
|
return capability.code || 1;
|
|
14893
15337
|
}
|
|
14894
|
-
if (
|
|
15338
|
+
if (paymentFace && (capability.code !== 0 || backendCapability[paymentProtocol] !== true)) {
|
|
14895
15339
|
warn(
|
|
14896
|
-
|
|
15340
|
+
`error: this platform deployment does not accept ${paymentProtocol} publication on the managed ${backend === "azure" ? "Azure" : "AWS"} backend yet. No build or upload was started. Deploy without the ${paymentProtocol.toUpperCase()} face, or self-host the ${paymentProtocol} gateway.`
|
|
14897
15341
|
);
|
|
14898
|
-
return 1;
|
|
15342
|
+
return capability.code || 1;
|
|
14899
15343
|
}
|
|
14900
15344
|
}
|
|
14901
15345
|
const campaignProbe = opts.campaignActive ?? (async () => null);
|
|
@@ -14933,17 +15377,19 @@ async function platformAgent(opts) {
|
|
|
14933
15377
|
}
|
|
14934
15378
|
const packaging = backend === "azure" ? "container" : platformPackaging(root);
|
|
14935
15379
|
{
|
|
14936
|
-
const
|
|
15380
|
+
const confirm2 = opts.confirmPackaging ?? ((r, p) => confirmPackagingDefault(r, p, {
|
|
14937
15381
|
acceptRisk: opts.acceptRisk ?? false,
|
|
14938
15382
|
isTty,
|
|
14939
15383
|
promptUser: ask2
|
|
14940
15384
|
}));
|
|
14941
|
-
const rc = await
|
|
15385
|
+
const rc = await confirm2(root, packaging);
|
|
14942
15386
|
if (rc !== null && rc !== 0) {
|
|
14943
15387
|
return rc;
|
|
14944
15388
|
}
|
|
14945
15389
|
}
|
|
14946
|
-
const secrets =
|
|
15390
|
+
const secrets = withoutPlatformByosStorageSecrets(
|
|
15391
|
+
await (opts.collectRuntimeSecrets ?? collectRuntimeSecrets)(root)
|
|
15392
|
+
);
|
|
14947
15393
|
walletWarning(root, secrets);
|
|
14948
15394
|
if (Object.keys(secrets).length > 0) {
|
|
14949
15395
|
warn(
|
|
@@ -14979,8 +15425,6 @@ then re-run the deploy.`
|
|
|
14979
15425
|
);
|
|
14980
15426
|
return 1;
|
|
14981
15427
|
}
|
|
14982
|
-
const cfg = loadAgentCfg(root);
|
|
14983
|
-
const faces = stackFaces(tableOf12(cfg, "stack"), tableOf12(cfg, "payments"));
|
|
14984
15428
|
const protocol = nativeProtocolOf(faces);
|
|
14985
15429
|
const runDeploy = opts.runDelegatedDeploy ?? (async (o) => withDeployFiles(
|
|
14986
15430
|
o.root,
|
|
@@ -15040,13 +15484,14 @@ then re-run the deploy.`
|
|
|
15040
15484
|
});
|
|
15041
15485
|
const clockStarted = startedNow ? ` \u23F1 your 48h trial clock just started${trial.expiresAt ? ` \u2014 expires ${trial.expiresAt}` : ""}.
|
|
15042
15486
|
` : "";
|
|
15487
|
+
const managedStorage = backend === "azure" ? "Azure Blob" : "S3";
|
|
15043
15488
|
emit(
|
|
15044
15489
|
`
|
|
15045
15490
|
\u2713 deployed to the platform (backend=${backend}, slug=${slug}).
|
|
15046
15491
|
deployment: ${dep.deploymentId}
|
|
15047
15492
|
invoke URL: ${dep.invokeUrl || "<pending \u2014 see `bag deploy status`>"}
|
|
15048
15493
|
${trialLine(trial)}
|
|
15049
|
-
${clockStarted} At trial expiry the
|
|
15494
|
+
${clockStarted} At trial expiry the runtime, secrets, and private deployment data are deleted (NOT recoverable). The API injects a scoped upload token; deliverables use managed ${managedStorage} and public URLs stay on bnbagent-api. Deliverables share the Agent lifecycle: after Agent deletion their URLs return 404.
|
|
15050
15495
|
Activity details: ${CAMPAIGN_DOC_URL}
|
|
15051
15496
|
Register on-chain so buyers can discover it: bag deploy verify --endpoint ${dep.invokeUrl || "<your-invoke-url>"}`
|
|
15052
15497
|
);
|
|
@@ -15156,11 +15601,12 @@ function recordDeployState(root, slug, dep, backend = "aws") {
|
|
|
15156
15601
|
}
|
|
15157
15602
|
}
|
|
15158
15603
|
const agentRoot2 = findSubProjectRoot15("agent", root);
|
|
15159
|
-
|
|
15604
|
+
const recordedPackaging = backend === "azure" ? "container" : platformPackaging(root);
|
|
15605
|
+
if (agentRoot2 !== null && (persistedPackaging2(agentRoot2) === null || backend === "azure" && persistedPackaging2(agentRoot2) !== "container")) {
|
|
15160
15606
|
const tomlPath = agentTomlPath2(root);
|
|
15161
15607
|
if (tomlPath !== null) {
|
|
15162
15608
|
try {
|
|
15163
|
-
patchTomlKv2(tomlPath, "deploy", "packaging",
|
|
15609
|
+
patchTomlKv2(tomlPath, "deploy", "packaging", recordedPackaging);
|
|
15164
15610
|
} catch {
|
|
15165
15611
|
}
|
|
15166
15612
|
}
|
|
@@ -15178,8 +15624,12 @@ function recordShippedWallet(root, secrets) {
|
|
|
15178
15624
|
function printAccessNextSteps(root, _invokeUrl) {
|
|
15179
15625
|
const agentRoot2 = findSubProjectRoot15("agent", root) ?? root;
|
|
15180
15626
|
const access = buyerAccess(agentRoot2);
|
|
15181
|
-
const x402Only = access.faces.length === 1 &&
|
|
15182
|
-
const
|
|
15627
|
+
const x402Only = access.faces.length === 1 && hasB402PaymentFace(access.faces);
|
|
15628
|
+
const agentCfg = loadAgentCfg(agentRoot2);
|
|
15629
|
+
const paymentProtocol = b402PaymentProtocol(agentCfg);
|
|
15630
|
+
const paymentPath = paymentProtocol === "mpp" ? "/mpp" : "/x402";
|
|
15631
|
+
const paymentUrl = access.mppUrl ?? access.x402Url;
|
|
15632
|
+
const x402Free = x402SellerIsFree(agentCfg);
|
|
15183
15633
|
const x402RouteLabel = x402Free ? "anonymous FREE route" : "anonymous paid route";
|
|
15184
15634
|
emit("\nAccess your agent \u2014");
|
|
15185
15635
|
if (access.agentId) {
|
|
@@ -15197,15 +15647,19 @@ function printAccessNextSteps(root, _invokeUrl) {
|
|
|
15197
15647
|
" MCP gateway support is rolling out \u2014 the A2A face is the stable face."
|
|
15198
15648
|
);
|
|
15199
15649
|
}
|
|
15200
|
-
if (
|
|
15201
|
-
emit(
|
|
15650
|
+
if (hasB402PaymentFace(access.faces)) {
|
|
15651
|
+
emit(
|
|
15652
|
+
` ${paymentProtocol} endpoint: ${paymentUrl} (${x402RouteLabel})`
|
|
15653
|
+
);
|
|
15202
15654
|
}
|
|
15203
15655
|
if (!x402Only) {
|
|
15204
15656
|
emit(
|
|
15205
15657
|
" mint buyer creds: bag platform invoke-client new (prints client_id + client_secret to POST to the client_credentials token endpoint above \u2014 credentials go in the form BODY; HTTP Basic auth is not accepted)"
|
|
15206
15658
|
);
|
|
15207
15659
|
} else {
|
|
15208
|
-
emit(
|
|
15660
|
+
emit(
|
|
15661
|
+
" protocol discovery is suppressed for this payment-only deployment."
|
|
15662
|
+
);
|
|
15209
15663
|
}
|
|
15210
15664
|
} else {
|
|
15211
15665
|
const rtTmpl = `${access.base}/v1/rt/<agentId>`;
|
|
@@ -15222,11 +15676,15 @@ function printAccessNextSteps(root, _invokeUrl) {
|
|
|
15222
15676
|
" MCP gateway support is rolling out \u2014 the A2A face is the stable face."
|
|
15223
15677
|
);
|
|
15224
15678
|
}
|
|
15225
|
-
if (
|
|
15226
|
-
emit(
|
|
15679
|
+
if (hasB402PaymentFace(access.faces)) {
|
|
15680
|
+
emit(
|
|
15681
|
+
` ${paymentProtocol} endpoint: ${rtTmpl}${paymentPath} (${x402RouteLabel})`
|
|
15682
|
+
);
|
|
15227
15683
|
}
|
|
15228
15684
|
if (x402Only) {
|
|
15229
|
-
emit(
|
|
15685
|
+
emit(
|
|
15686
|
+
" protocol discovery is suppressed for this payment-only deployment."
|
|
15687
|
+
);
|
|
15230
15688
|
} else {
|
|
15231
15689
|
emit(
|
|
15232
15690
|
" run `bag platform invoke-client new` to mint buyer creds and capture your agentId."
|
|
@@ -15257,10 +15715,13 @@ function printAgentClientPrompt(root, invokeUrl, status) {
|
|
|
15257
15715
|
this endpoint does NOT accept HTTP Basic auth (\`curl -u\` fails with oauth.invalid_request).
|
|
15258
15716
|
3) send \`Authorization: Bearer <token>\`.`;
|
|
15259
15717
|
const agentCfg = loadAgentCfg(agentRoot2);
|
|
15718
|
+
const paymentProtocol = b402PaymentProtocol(agentCfg);
|
|
15719
|
+
const paymentPath = paymentProtocol === "mpp" ? "/mpp" : "/x402";
|
|
15720
|
+
const paymentUrl = access.mppUrl ?? access.x402Url;
|
|
15260
15721
|
const rails = commerceRails(agentCfg);
|
|
15261
15722
|
const x402Free = x402SellerIsFree(agentCfg);
|
|
15262
|
-
const x402Only = faces.length === 1 &&
|
|
15263
|
-
const x402Summary = x402DeploySummary(agentRoot2, "platform",
|
|
15723
|
+
const x402Only = faces.length === 1 && hasB402PaymentFace(faces) || rails.x402 && !rails.erc8183;
|
|
15724
|
+
const x402Summary = x402DeploySummary(agentRoot2, "platform", paymentUrl);
|
|
15264
15725
|
const endpoints = [];
|
|
15265
15726
|
if (hasA2aFace(faces)) {
|
|
15266
15727
|
endpoints.push(
|
|
@@ -15279,7 +15740,7 @@ function printAgentClientPrompt(root, invokeUrl, status) {
|
|
|
15279
15740
|
}
|
|
15280
15741
|
if (x402Only) {
|
|
15281
15742
|
endpoints.push(
|
|
15282
|
-
` [
|
|
15743
|
+
` [${paymentProtocol.toUpperCase()}] Anonymous ${x402Free ? "FREE" : "paid"} endpoint: ${paymentUrl ?? `${rt}${paymentPath}`}
|
|
15283
15744
|
Protocol discovery is suppressed.`
|
|
15284
15745
|
);
|
|
15285
15746
|
}
|
|
@@ -15299,7 +15760,7 @@ function printAgentClientPrompt(root, invokeUrl, status) {
|
|
|
15299
15760
|
"\u{1F4CB} COPY EVERYTHING BELOW THIS LINE INTO CLAUDE CODE OR CURSOR",
|
|
15300
15761
|
" \u2014 it will build and run a client that tests your deployed agent",
|
|
15301
15762
|
CLIENT_PROMPT_RULE,
|
|
15302
|
-
x402Only ?
|
|
15763
|
+
x402Only ? `I deployed a BNB Chain ${paymentProtocol.toUpperCase()}+B402 seller agent. Build a small TypeScript (fetch) client that follows the ${paymentProtocol.toUpperCase()} SELLER instructions below.` : "I deployed a BNB Chain seller agent. Build a small TypeScript (fetch) client that authenticates, calls the agent, and prints the responses.",
|
|
15303
15764
|
"",
|
|
15304
15765
|
"WHAT WAS DEPLOYED",
|
|
15305
15766
|
`- Protocol: ${proto}`,
|
|
@@ -15314,7 +15775,11 @@ function printAgentClientPrompt(root, invokeUrl, status) {
|
|
|
15314
15775
|
] : [],
|
|
15315
15776
|
" 4) poll the CHAIN for the job's SUBMITTED state to read the deliverable_url (the agent serves no job-query endpoint)."
|
|
15316
15777
|
] : [],
|
|
15317
|
-
...x402Summary ? [
|
|
15778
|
+
...x402Summary ? [
|
|
15779
|
+
"",
|
|
15780
|
+
`${paymentProtocol.toUpperCase()} SELLER`,
|
|
15781
|
+
...x402Summary.split("\n")
|
|
15782
|
+
] : [],
|
|
15318
15783
|
"",
|
|
15319
15784
|
"ENDPOINT",
|
|
15320
15785
|
endpointBlock,
|
|
@@ -15327,14 +15792,14 @@ function printAgentClientPrompt(root, invokeUrl, status) {
|
|
|
15327
15792
|
"",
|
|
15328
15793
|
...commerceShapeLines
|
|
15329
15794
|
] : [
|
|
15330
|
-
|
|
15795
|
+
`- The anonymous ${paymentProtocol} route does not use the OAuth2 token flow shown for ERC-8183.`
|
|
15331
15796
|
],
|
|
15332
15797
|
...!x402Only && hasA2aFace(faces) ? [
|
|
15333
15798
|
'- A2A callers: send the payload as a DATA part, NOT text \u2014 parts:[{"kind":"data","data":{ ...payload above... }}]. The managed gateway adapts this canonical buyer contract to the selected backend.'
|
|
15334
15799
|
] : [],
|
|
15335
15800
|
"",
|
|
15336
15801
|
"YOUR TASK",
|
|
15337
|
-
x402Only ? x402Free ?
|
|
15802
|
+
x402Only ? x402Free ? `Call the anonymous FREE ${paymentPath} endpoint directly. Do not create a payment or call the OAuth2 ERC-8183 endpoint.` : `Follow the ${paymentProtocol.toUpperCase()} SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint.` : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
|
|
15338
15803
|
CLIENT_PROMPT_RULE
|
|
15339
15804
|
];
|
|
15340
15805
|
const text2 = lines.join("\n");
|
|
@@ -15391,6 +15856,7 @@ async function platformInfo(opts) {
|
|
|
15391
15856
|
a2a_url: access.a2aUrl,
|
|
15392
15857
|
mcp_url: access.mcpUrl,
|
|
15393
15858
|
x402_url: access.x402Url,
|
|
15859
|
+
mpp_url: access.mppUrl,
|
|
15394
15860
|
token_url: access.tokenUrl,
|
|
15395
15861
|
scope: access.scope
|
|
15396
15862
|
},
|
|
@@ -15419,10 +15885,13 @@ async function platformInfo(opts) {
|
|
|
15419
15885
|
" MCP gateway support is rolling out \u2014 the A2A face is the stable face."
|
|
15420
15886
|
);
|
|
15421
15887
|
}
|
|
15422
|
-
if (
|
|
15423
|
-
|
|
15888
|
+
if (hasB402PaymentFace(access.faces)) {
|
|
15889
|
+
const paymentProtocol = hasMppFace(access.faces) ? "mpp" : "x402";
|
|
15890
|
+
emit(
|
|
15891
|
+
` ${`${paymentProtocol} endpoint:`.padEnd(15)} ${access.mppUrl ?? access.x402Url ?? none}`
|
|
15892
|
+
);
|
|
15424
15893
|
}
|
|
15425
|
-
if (!(access.faces.length === 1 &&
|
|
15894
|
+
if (!(access.faces.length === 1 && hasB402PaymentFace(access.faces))) {
|
|
15426
15895
|
emit(
|
|
15427
15896
|
` ${"token endpoint:".padEnd(15)} ${access.tokenUrl} (client_credentials)`
|
|
15428
15897
|
);
|
|
@@ -15450,7 +15919,7 @@ async function platformDestroy(opts) {
|
|
|
15450
15919
|
);
|
|
15451
15920
|
emit(`deployment: ${recorded.deployment_id || "<none recorded>"}`);
|
|
15452
15921
|
emit(
|
|
15453
|
-
"\nTeardown deletes the platform agent + its deployment via DELETE /v1/agents/:slug. The operator removes the transmitted runtime secret as part of that delete. The slug is permanently retired; a later redeploy must use a new slug."
|
|
15922
|
+
"\nTeardown deletes the platform agent + its deployment via DELETE /v1/agents/:slug. The operator removes the transmitted runtime secret and managed deliverables as part of that delete. Deliverable URLs eventually return 404. The slug is permanently retired; a later redeploy must use a new slug."
|
|
15454
15923
|
);
|
|
15455
15924
|
emit(
|
|
15456
15925
|
"\n--- always note ---\n - your local keystore (.studio/wallets/) is left UNTOUCHED.\n - the on-chain ERC-8004 identity CANNOT be deleted; its endpoint goes stale.\n - the 48h trial sandbox is auto-reclaimed regardless."
|
|
@@ -15490,7 +15959,9 @@ async function platformDestroy(opts) {
|
|
|
15490
15959
|
root,
|
|
15491
15960
|
oldInvokeUrl ? String(oldInvokeUrl) : null
|
|
15492
15961
|
);
|
|
15493
|
-
emit(
|
|
15962
|
+
emit(
|
|
15963
|
+
`\u2713 deleted platform agent ${slug} (deployment + transmitted secret + managed deliverables). Deliverable URLs will return 404 after asynchronous cleanup.`
|
|
15964
|
+
);
|
|
15494
15965
|
emit(
|
|
15495
15966
|
`note: the on-chain ERC-8004 identity is unchanged; its endpoint will go stale. Destroyed slugs are permanently retired, so the local slug was advanced to '${nextSlug}' \u2014 the next deploy uses it automatically. Your local keystore (.studio/wallets/) was left untouched.`
|
|
15496
15967
|
);
|
|
@@ -15612,7 +16083,12 @@ function registerDeploy(program) {
|
|
|
15612
16083
|
"--allow-multiple",
|
|
15613
16084
|
"Allow creating a deployment while another provider remains active."
|
|
15614
16085
|
).option("--skip-smoke", "Skip the delegated post-deploy smoke check.").action(act((opts) => cmdDeploy(opts, [])));
|
|
15615
|
-
p.command("prepare").description("Run the deploy-readiness sweep on this project.").addOption(
|
|
16086
|
+
p.command("prepare").description("Run the deploy-readiness sweep on this project.").addOption(providerOption()).addOption(
|
|
16087
|
+
new Option4(
|
|
16088
|
+
"--backend <backend>",
|
|
16089
|
+
"Managed backend for --provider bnb (aws or azure). Selects the Platform readiness contract without deploying."
|
|
16090
|
+
).choices(["aws", "azure"])
|
|
16091
|
+
).addOption(runtimeOption()).addOption(targetAlias()).option("--project-root <path>", "Override project root.").option("--json", "Emit JSON instead of a table.").option(
|
|
15616
16092
|
"--ignore-warnings",
|
|
15617
16093
|
"Exit 0 even if WARNING-level checks fire (still fails on BLOCKED/CRITICAL)."
|
|
15618
16094
|
).option("--no-info", "Skip INFO-level checks (CI-friendly).").option(
|
|
@@ -15825,7 +16301,7 @@ function resolveTarget(opts, root, destination = agentDeployDestination(root)) {
|
|
|
15825
16301
|
return readStackRuntime(root) ?? "agentcore";
|
|
15826
16302
|
}
|
|
15827
16303
|
function validateRuntimeAgainstStack(target, force, root) {
|
|
15828
|
-
if (target === "platform") {
|
|
16304
|
+
if (target === "platform" || target === "platform-azure") {
|
|
15829
16305
|
return null;
|
|
15830
16306
|
}
|
|
15831
16307
|
const stackRuntime2 = readStackRuntime(root);
|
|
@@ -15857,7 +16333,49 @@ async function cmdPrepare(opts) {
|
|
|
15857
16333
|
const cwd = path37.resolve(process.cwd());
|
|
15858
16334
|
root = findStudioWorkspaceRoot3(cwd) ?? cwd;
|
|
15859
16335
|
}
|
|
15860
|
-
|
|
16336
|
+
let target;
|
|
16337
|
+
if (opts.provider !== void 0) {
|
|
16338
|
+
if (opts.runtime !== void 0 || opts.target !== void 0) {
|
|
16339
|
+
printErr(
|
|
16340
|
+
"error: --provider and --runtime select different readiness axes; pass only --provider (and --backend for bnb)."
|
|
16341
|
+
);
|
|
16342
|
+
return 2;
|
|
16343
|
+
}
|
|
16344
|
+
if (opts.provider === "bnb") {
|
|
16345
|
+
const stackRuntime2 = readStackRuntime(root);
|
|
16346
|
+
const expectedBackend = stackRuntime2 === "azure-foundry" ? "azure" : "aws";
|
|
16347
|
+
if (opts.backend !== void 0 && opts.backend !== expectedBackend) {
|
|
16348
|
+
printErr(
|
|
16349
|
+
`error: --backend=${opts.backend} conflicts with [stack].runtime='${stackRuntime2}', which requires backend=${expectedBackend}.`
|
|
16350
|
+
);
|
|
16351
|
+
return 2;
|
|
16352
|
+
}
|
|
16353
|
+
const backend = opts.backend ?? expectedBackend;
|
|
16354
|
+
target = backend === "azure" ? "platform-azure" : "platform";
|
|
16355
|
+
} else {
|
|
16356
|
+
if (opts.backend !== void 0) {
|
|
16357
|
+
printErr(
|
|
16358
|
+
`error: --backend is accepted only with --provider bnb; --provider ${opts.provider} selects a user-owned cloud directly.`
|
|
16359
|
+
);
|
|
16360
|
+
return 2;
|
|
16361
|
+
}
|
|
16362
|
+
target = opts.provider === "azure" ? "azure-foundry" : "agentcore";
|
|
16363
|
+
}
|
|
16364
|
+
} else {
|
|
16365
|
+
if (opts.backend !== void 0) {
|
|
16366
|
+
printErr("error: --backend requires --provider bnb.");
|
|
16367
|
+
return 2;
|
|
16368
|
+
}
|
|
16369
|
+
const destination = agentDeployDestination(root);
|
|
16370
|
+
const implicitAzureTarget = opts.runtime === void 0 && opts.target === void 0 && destination !== "platform" && readStackRuntime(root) === "azure-foundry";
|
|
16371
|
+
if (implicitAzureTarget) {
|
|
16372
|
+
printErr(
|
|
16373
|
+
"error: Azure prepare target is ambiguous. Choose the intended deployment explicitly: `bag deploy prepare --provider azure` for a user-owned Foundry deployment, or `bag deploy prepare --provider bnb --backend azure` for managed Platform Azure."
|
|
16374
|
+
);
|
|
16375
|
+
return 2;
|
|
16376
|
+
}
|
|
16377
|
+
target = resolveTarget(opts, root);
|
|
16378
|
+
}
|
|
15861
16379
|
const rc = validateRuntimeAgainstStack(target, Boolean(opts.force), root);
|
|
15862
16380
|
if (rc !== null) {
|
|
15863
16381
|
return rc;
|
|
@@ -15910,7 +16428,9 @@ function renderPrepareHuman(result, ignoreWarnings) {
|
|
|
15910
16428
|
if (result.hasBlockers) {
|
|
15911
16429
|
printOut("Deploy is BLOCKED. Fix the BLOCKED check(s) above first.");
|
|
15912
16430
|
} else if (result.hasCriticals) {
|
|
15913
|
-
const fixHint = result.checks.find(
|
|
16431
|
+
const fixHint = result.checks.find(
|
|
16432
|
+
(c2) => c2.level === "CRITICAL" && c2.fixCmd !== void 0 && c2.fixCmd !== null && !c2.fixCmd.trimStart().startsWith("#")
|
|
16433
|
+
)?.fixCmd;
|
|
15914
16434
|
if (fixHint) {
|
|
15915
16435
|
printOut(`Run ${fixHint} to auto-fix the first CRITICAL, then re-run.`);
|
|
15916
16436
|
} else {
|
|
@@ -15927,14 +16447,17 @@ function renderPrepareHuman(result, ignoreWarnings) {
|
|
|
15927
16447
|
}
|
|
15928
16448
|
}
|
|
15929
16449
|
function applyStorageGuard(result, root) {
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
if (localCheck !== null && !checks.some((c2) => c2.name === localCheck.name)) {
|
|
15933
|
-
checks.push(localCheck);
|
|
16450
|
+
if (result.target.startsWith("platform")) {
|
|
16451
|
+
return result;
|
|
15934
16452
|
}
|
|
15935
|
-
const
|
|
15936
|
-
|
|
15937
|
-
|
|
16453
|
+
const checks = [...result.checks];
|
|
16454
|
+
for (const check of fatalStorageDeployChecks(root)) {
|
|
16455
|
+
if (check.name === "storage_ipfs_endpoint_required" && hasIpfsEndpointFinding(result)) {
|
|
16456
|
+
continue;
|
|
16457
|
+
}
|
|
16458
|
+
if (!checks.some((existing) => existing.name === check.name)) {
|
|
16459
|
+
checks.push(check);
|
|
16460
|
+
}
|
|
15938
16461
|
}
|
|
15939
16462
|
if (checks.length === result.checks.length) {
|
|
15940
16463
|
return result;
|
|
@@ -15950,6 +16473,9 @@ function warnBrokenStorageBypass(checks) {
|
|
|
15950
16473
|
}
|
|
15951
16474
|
}
|
|
15952
16475
|
function enforceSkipPrepareStorageGate(opts, root, target) {
|
|
16476
|
+
if (target.startsWith("platform")) {
|
|
16477
|
+
return null;
|
|
16478
|
+
}
|
|
15953
16479
|
if (!opts.skipPrepare) {
|
|
15954
16480
|
return null;
|
|
15955
16481
|
}
|
|
@@ -16293,10 +16819,16 @@ async function refreshLocalDeployDependencies(root) {
|
|
|
16293
16819
|
try {
|
|
16294
16820
|
const vendored = await vendorLocalTarballs(agentRoot2, {
|
|
16295
16821
|
force: true,
|
|
16296
|
-
requiredPackages: ["@bnbagent/sdk"]
|
|
16822
|
+
requiredPackages: ["@bnbagent/sdk", "@bnbagent/studio-runtime"]
|
|
16297
16823
|
});
|
|
16298
16824
|
if (vendored.length > 0) {
|
|
16299
16825
|
applyWorkspaceVendorOverrides(workspaceRoot, agentRoot2);
|
|
16826
|
+
if (!await provisionInstall(workspaceRoot, { force: true })) {
|
|
16827
|
+
printErr(
|
|
16828
|
+
"error: refreshed vendored dependencies could not be installed; refusing to package stale node_modules."
|
|
16829
|
+
);
|
|
16830
|
+
return 1;
|
|
16831
|
+
}
|
|
16300
16832
|
printErr(
|
|
16301
16833
|
`note: refreshed vendored local tarball(s) in app/agent/vendor/: ${vendored.join(", ")}`
|
|
16302
16834
|
);
|
|
@@ -16681,6 +17213,7 @@ async function printAccessNextSteps2(root, runtime, opts = {}) {
|
|
|
16681
17213
|
const proto = nativeProtocolOf(faces);
|
|
16682
17214
|
printOut("\nAccess your agent \u2014");
|
|
16683
17215
|
if (runtime === "agentcore") {
|
|
17216
|
+
const paymentProtocol = hasMppFace(faces) ? "mpp" : "x402";
|
|
16684
17217
|
const summary = accessSummaryForAgentcore(agentRoot2, { protocol: proto });
|
|
16685
17218
|
if (opts.invokeUrl) {
|
|
16686
17219
|
printOut(` endpoint: ${opts.invokeUrl}`);
|
|
@@ -16696,9 +17229,9 @@ async function printAccessNextSteps2(root, runtime, opts = {}) {
|
|
|
16696
17229
|
" call: POST a JSON-RPC message/send to .../invocations with an OAuth2 Bearer token and an X-Amzn-Bedrock-AgentCore-Runtime-Session-Id header (>=33 chars)."
|
|
16697
17230
|
);
|
|
16698
17231
|
}
|
|
16699
|
-
if (hasA2aFace(faces) && hasMcpFace(faces) ||
|
|
17232
|
+
if (hasA2aFace(faces) && hasMcpFace(faces) || hasB402PaymentFace(faces)) {
|
|
16700
17233
|
printOut(
|
|
16701
|
-
|
|
17234
|
+
` note: /mcp and /${paymentProtocol} have no raw URL on self-managed AgentCore; reach them by wrapping the request as envelope-v1 JSON inside the InvokeAgentRuntime payload (see docs/guides/${paymentProtocol === "mpp" ? "mpp-b402-selling.md" : "x402-selling.md"}).`
|
|
16702
17235
|
);
|
|
16703
17236
|
}
|
|
16704
17237
|
printOut(
|
|
@@ -16718,16 +17251,19 @@ async function printAccessNextSteps2(root, runtime, opts = {}) {
|
|
|
16718
17251
|
printOut(
|
|
16719
17252
|
` Azure Foundry access uses a Microsoft Entra bearer token. Run \`bag deploy info --provider azure --with-curl\` for the exact endpoint, token resource, and Invocations request body; see ${FOUNDRY_GUIDE_URL}.`
|
|
16720
17253
|
);
|
|
16721
|
-
if (
|
|
17254
|
+
if (hasB402PaymentFace(faces)) {
|
|
17255
|
+
const paymentProtocol = hasMppFace(faces) ? "mpp" : "x402";
|
|
16722
17256
|
const summary = x402DeploySummary(agentRoot2, "self", null);
|
|
16723
|
-
if (summary.startsWith(
|
|
17257
|
+
if (summary.startsWith(`${paymentProtocol} rail is ACTIVE`)) {
|
|
17258
|
+
printOut(
|
|
17259
|
+
` ${paymentProtocol} access: requires-external-gateway \u2014 Foundry exposes no raw anonymous /${paymentProtocol} URL. An operator-run HTTPS gateway must carry each request as http-envelope-v1 over the Entra-authenticated Invocations endpoint.`
|
|
17260
|
+
);
|
|
16724
17261
|
printOut(
|
|
16725
|
-
|
|
17262
|
+
` ${paymentProtocol} gateway guide: ${paymentProtocol === "mpp" ? "docs/guides/mpp-b402-selling.md" : foundryX402GatewayGuideUrl()}`
|
|
16726
17263
|
);
|
|
16727
|
-
printOut(` x402 gateway guide: ${foundryX402GatewayGuideUrl()}`);
|
|
16728
17264
|
} else {
|
|
16729
17265
|
printOut(
|
|
16730
|
-
`
|
|
17266
|
+
` ${paymentProtocol} access: dormant \u2014 ${summary || `redeploy after enabling the ${paymentProtocol} seller rail.`}`
|
|
16731
17267
|
);
|
|
16732
17268
|
}
|
|
16733
17269
|
}
|
|
@@ -16755,6 +17291,9 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
|
|
|
16755
17291
|
2) POST client_credentials -> access_token.
|
|
16756
17292
|
3) send \`Authorization: Bearer <token>\` AND header \`X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: <stable id >= 33 chars>\`.`;
|
|
16757
17293
|
const agentCfg = loadAgentCfg2(agentRoot2) ?? {};
|
|
17294
|
+
const paymentProtocol = b402PaymentProtocol(agentCfg);
|
|
17295
|
+
const paymentLabel = paymentProtocol.toUpperCase();
|
|
17296
|
+
const paymentPath = `/${paymentProtocol}`;
|
|
16758
17297
|
const rails = commerceRails(agentCfg);
|
|
16759
17298
|
const x402Free = x402SellerIsFree(agentCfg);
|
|
16760
17299
|
const x402Only = rails.x402 && !rails.erc8183;
|
|
@@ -16762,9 +17301,9 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
|
|
|
16762
17301
|
const faces = agentFacesOf(agentRoot2);
|
|
16763
17302
|
const x402OnlyFace = !hasA2aFace(faces) && !hasMcpFace(faces);
|
|
16764
17303
|
const a2aPayloadPhrase = x402Only ? "POST JSON-RPC payloads" : "POST the negotiate /\n notify_funded payloads";
|
|
16765
|
-
const endpointBlock = proto === "MCP" ? ` [MCP] MCP endpoint (streamable HTTP): ${mcpUrl}` : x402OnlyFace ? ` [
|
|
17304
|
+
const endpointBlock = proto === "MCP" ? ` [MCP] MCP endpoint (streamable HTTP): ${mcpUrl}` : x402OnlyFace ? ` [${paymentLabel}] Invoke URL (POST-only): ${a2aInvokeUrl}
|
|
16766
17305
|
NOTE: there is NO public A2A face and NO GET-able agent card.
|
|
16767
|
-
Wrap each request as envelope-v1 JSON (see
|
|
17306
|
+
Wrap each request as envelope-v1 JSON (see ${paymentLabel} SELLER below)
|
|
16768
17307
|
and POST it to the invoke URL above.` : ` [A2A] Invoke (JSON-RPC message/send, POST-only): ${a2aInvokeUrl}
|
|
16769
17308
|
NOTE: this AgentCore endpoint is POST-only and serves NO
|
|
16770
17309
|
GET-able agent card. Skip A2A discovery (do NOT fetch
|
|
@@ -16800,7 +17339,7 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
|
|
|
16800
17339
|
"\u{1F4CB} COPY EVERYTHING BELOW THIS LINE INTO CLAUDE CODE OR CURSOR",
|
|
16801
17340
|
" \u2014 it will build and run a client that tests your deployed agent",
|
|
16802
17341
|
CLIENT_PROMPT_RULE2,
|
|
16803
|
-
x402Only ?
|
|
17342
|
+
x402Only ? `I deployed a BNB Chain ${paymentProtocol} seller agent. Build a small TypeScript (fetch) client that follows the ${paymentLabel} SELLER instructions below.` : "I deployed a BNB Chain seller agent. Build a small TypeScript (fetch) client that authenticates, calls the agent, and prints the responses.",
|
|
16804
17343
|
"",
|
|
16805
17344
|
"WHAT WAS DEPLOYED",
|
|
16806
17345
|
`- Protocol: ${proto}`,
|
|
@@ -16812,7 +17351,7 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
|
|
|
16812
17351
|
' 3) notify_funded \u2014 send that funded job_id; the agent verifies the on-chain job, produces the deliverable, and submits it on-chain. Returns {status:"accepted", job_id}. Delivery is ASYNC.',
|
|
16813
17352
|
" 4) poll the CHAIN for the job's SUBMITTED state to read the deliverable_url (the agent serves no job-query endpoint)."
|
|
16814
17353
|
] : [],
|
|
16815
|
-
...x402Summary ? ["",
|
|
17354
|
+
...x402Summary ? ["", `${paymentLabel} SELLER`, ...x402Summary.split("\n")] : [],
|
|
16816
17355
|
"",
|
|
16817
17356
|
"ENDPOINT",
|
|
16818
17357
|
endpointBlock,
|
|
@@ -16826,14 +17365,14 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
|
|
|
16826
17365
|
...commerceShapeLines,
|
|
16827
17366
|
...mcpSessionLines
|
|
16828
17367
|
] : [
|
|
16829
|
-
|
|
17368
|
+
`- The anonymous ${paymentProtocol} route does not use the OAuth2 token flow shown for ERC-8183.`
|
|
16830
17369
|
],
|
|
16831
17370
|
...!x402Only && proto !== "MCP" ? [
|
|
16832
17371
|
'- A2A callers: send the payload as a DATA part, NOT text \u2014 parts:[{"kind":"data","data":{ ...payload above... }}]. A JSON string in a "text" part is rejected (the runtime only reads data parts, so no skill is parsed).'
|
|
16833
17372
|
] : [],
|
|
16834
17373
|
"",
|
|
16835
17374
|
"YOUR TASK",
|
|
16836
|
-
x402Only ? x402Free ?
|
|
17375
|
+
x402Only ? x402Free ? `Call the anonymous FREE ${paymentPath} endpoint directly. Do not create a payment or call the OAuth2 ERC-8183 endpoint.` : `Follow the ${paymentLabel} SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint.` : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
|
|
16837
17376
|
CLIENT_PROMPT_RULE2
|
|
16838
17377
|
];
|
|
16839
17378
|
const text2 = lines.join("\n");
|
|
@@ -16850,11 +17389,16 @@ async function printAzureAgentClientPrompt(root) {
|
|
|
16850
17389
|
const deployment = discoverRecordedDeployments(
|
|
16851
17390
|
deployWorkspaceRoot2(root)
|
|
16852
17391
|
).find((candidate) => candidate.provider === "azure");
|
|
16853
|
-
const
|
|
17392
|
+
const legacyEndpoint = deployment?.endpoint ?? recordedEndpoint(root) ?? "<run `bag deploy info --provider azure` to recover the endpoint>";
|
|
16854
17393
|
const faces = deployment?.faces ?? agentFacesOf(agentRoot2);
|
|
16855
|
-
const hasX402 =
|
|
17394
|
+
const hasX402 = hasB402PaymentFace(faces);
|
|
17395
|
+
const paymentProtocol = hasMppFace(faces) ? "mpp" : "x402";
|
|
17396
|
+
const paymentLabel = paymentProtocol.toUpperCase();
|
|
16856
17397
|
const hasA2a = hasA2aFace(faces);
|
|
16857
|
-
const
|
|
17398
|
+
const a2aEndpoint = deployment?.endpoints?.a2a ?? deployment?.endpoints?.http ?? legacyEndpoint;
|
|
17399
|
+
const invocationsEndpoint = deployment?.endpoints?.invocations ?? legacyEndpoint;
|
|
17400
|
+
const endpoint = hasA2a ? a2aEndpoint : invocationsEndpoint;
|
|
17401
|
+
const request = hasA2a ? JSON.stringify(azureA2aQuickVerifyBody()) : hasX402 ? JSON.stringify(azureX402CarrierBody(paymentProtocol)) : JSON.stringify(azureA2aQuickVerifyBody());
|
|
16858
17402
|
const lines = [
|
|
16859
17403
|
"",
|
|
16860
17404
|
CLIENT_PROMPT_RULE2,
|
|
@@ -16865,21 +17409,24 @@ async function printAzureAgentClientPrompt(root) {
|
|
|
16865
17409
|
"",
|
|
16866
17410
|
"WHAT WAS DEPLOYED",
|
|
16867
17411
|
`- Faces: ${faces.join(", ") || "unknown"}`,
|
|
16868
|
-
`- Entra-authenticated Invocations endpoint: ${endpoint}`,
|
|
17412
|
+
`- Entra-authenticated ${hasA2a ? "A2A" : "Invocations"} endpoint: ${endpoint}`,
|
|
17413
|
+
...hasA2a && hasX402 ? [
|
|
17414
|
+
`- Entra-authenticated ${paymentLabel} Invocations endpoint: ${invocationsEndpoint}`
|
|
17415
|
+
] : [],
|
|
16869
17416
|
`- Token scope: ${AZURE_FOUNDRY_TOKEN_SCOPE}`,
|
|
16870
|
-
|
|
17417
|
+
`- This is not an anonymous buyer URL. A public ${paymentProtocol} buyer requires an external HTTPS gateway in front of Foundry.`,
|
|
16871
17418
|
"",
|
|
16872
17419
|
"REQUEST",
|
|
16873
17420
|
`- POST application/json: ${request}`,
|
|
16874
|
-
...hasA2a ? ["-
|
|
17421
|
+
...hasA2a ? ["- Send `A2A-Version: 1.0` with the native JSON-RPC A2A request."] : [],
|
|
16875
17422
|
...hasX402 ? [
|
|
16876
17423
|
...hasA2a ? [
|
|
16877
|
-
`-
|
|
17424
|
+
`- ${paymentLabel} carrier alternative: ${JSON.stringify(azureX402CarrierBody(paymentProtocol))}`
|
|
16878
17425
|
] : [],
|
|
16879
17426
|
"- The outer object is http-envelope-v1. Build it with:",
|
|
16880
17427
|
' const inner = { prompt: "Say hi in one short sentence." };',
|
|
16881
17428
|
' const body = Buffer.from(JSON.stringify(inner)).toString("base64");',
|
|
16882
|
-
|
|
17429
|
+
`- The outer response is another HTTP envelope. Decode response.body from base64 before parsing the inner ${paymentProtocol} response.`
|
|
16883
17430
|
] : [],
|
|
16884
17431
|
"",
|
|
16885
17432
|
"AUTHENTICATION AND LOGS",
|
|
@@ -17154,11 +17701,12 @@ function printVerifyCurl(access) {
|
|
|
17154
17701
|
const scope = access.scope;
|
|
17155
17702
|
const tokenUrl = access.tokenUrl;
|
|
17156
17703
|
const invokeUrl = `${access.base}/v1/rt/${agentId}`;
|
|
17157
|
-
if (access.faces.length === 1 &&
|
|
17704
|
+
if (access.faces.length === 1 && hasB402PaymentFace(access.faces)) {
|
|
17705
|
+
const paymentProtocol = hasMppFace(access.faces) ? "mpp" : "x402";
|
|
17158
17706
|
printOut(
|
|
17159
17707
|
`
|
|
17160
|
-
Quick-verify
|
|
17161
|
-
curl -i ${invokeUrl}
|
|
17708
|
+
Quick-verify ${paymentProtocol} endpoint:
|
|
17709
|
+
curl -i ${invokeUrl}/${paymentProtocol} # expect payment requirements until an ${paymentProtocol} client pays`
|
|
17162
17710
|
);
|
|
17163
17711
|
return;
|
|
17164
17712
|
}
|
|
@@ -17234,11 +17782,32 @@ function azureNegotiateQuickVerifyBody() {
|
|
|
17234
17782
|
})
|
|
17235
17783
|
};
|
|
17236
17784
|
}
|
|
17237
|
-
function
|
|
17785
|
+
function azureA2aQuickVerifyBody() {
|
|
17786
|
+
return {
|
|
17787
|
+
jsonrpc: "2.0",
|
|
17788
|
+
id: "bag-quick-verify",
|
|
17789
|
+
method: "message/send",
|
|
17790
|
+
params: {
|
|
17791
|
+
message: {
|
|
17792
|
+
messageId: "bag-quick-verify",
|
|
17793
|
+
role: "user",
|
|
17794
|
+
parts: [
|
|
17795
|
+
{
|
|
17796
|
+
kind: "text",
|
|
17797
|
+
text: JSON.stringify(
|
|
17798
|
+
JSON.parse(azureNegotiateQuickVerifyBody().input)
|
|
17799
|
+
)
|
|
17800
|
+
}
|
|
17801
|
+
]
|
|
17802
|
+
}
|
|
17803
|
+
}
|
|
17804
|
+
};
|
|
17805
|
+
}
|
|
17806
|
+
function azureX402CarrierBody(protocol = "x402") {
|
|
17238
17807
|
return {
|
|
17239
17808
|
v: 1,
|
|
17240
17809
|
method: "POST",
|
|
17241
|
-
path: "/x402",
|
|
17810
|
+
path: protocol === "mpp" ? "/mpp" : "/x402",
|
|
17242
17811
|
query: {},
|
|
17243
17812
|
headers: { "content-type": "application/json" },
|
|
17244
17813
|
body: Buffer.from(
|
|
@@ -17257,8 +17826,12 @@ function sameX402Snapshot(a, b) {
|
|
|
17257
17826
|
return a.status === b.status && a.priceMode === b.priceMode && a.credentialsComplete === b.credentialsComplete;
|
|
17258
17827
|
}
|
|
17259
17828
|
function printAzureInfo(root, deployment, opts) {
|
|
17260
|
-
const
|
|
17261
|
-
|
|
17829
|
+
const legacyEndpoint = deployment.endpoint;
|
|
17830
|
+
const httpEndpoint = deployment.endpoints?.http ?? legacyEndpoint;
|
|
17831
|
+
const invocationsEndpoint = deployment.endpoints?.invocations ?? null;
|
|
17832
|
+
const a2aEndpoint = deployment.endpoints?.a2a ?? null;
|
|
17833
|
+
const agentCardEndpoint = deployment.endpoints?.agentCard ?? null;
|
|
17834
|
+
if (!legacyEndpoint && !httpEndpoint && !invocationsEndpoint && !a2aEndpoint) {
|
|
17262
17835
|
if (opts.json) {
|
|
17263
17836
|
printOut(
|
|
17264
17837
|
JSON.stringify(
|
|
@@ -17266,7 +17839,12 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17266
17839
|
provider: "azure",
|
|
17267
17840
|
target: deployment.target,
|
|
17268
17841
|
name: deployment.name,
|
|
17269
|
-
|
|
17842
|
+
endpoints: {
|
|
17843
|
+
http: null,
|
|
17844
|
+
invocations: null,
|
|
17845
|
+
a2a: null,
|
|
17846
|
+
agent_card: null
|
|
17847
|
+
},
|
|
17270
17848
|
error: "No Azure invocation endpoint is recorded."
|
|
17271
17849
|
},
|
|
17272
17850
|
null,
|
|
@@ -17283,15 +17861,19 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17283
17861
|
const agentRoot2 = findSubProjectRoot16("agent", root) ?? root;
|
|
17284
17862
|
const currentFaces = agentFacesOf(agentRoot2);
|
|
17285
17863
|
const faces = deployment.faces ?? currentFaces;
|
|
17864
|
+
const paymentProtocol = hasMppFace(faces) ? "mpp" : "x402";
|
|
17865
|
+
const paymentLabel = paymentProtocol.toUpperCase();
|
|
17286
17866
|
const facesSource = deployment.faces ? "deployment-record" : "current-scaffold";
|
|
17287
17867
|
const facesDrift = deployment.faces !== void 0 && !sameFaces(deployment.faces, currentFaces);
|
|
17288
|
-
const hasX402 =
|
|
17868
|
+
const hasX402 = hasB402PaymentFace(faces);
|
|
17289
17869
|
const hasA2a = hasA2aFace(faces);
|
|
17290
|
-
const
|
|
17870
|
+
const endpoint = hasA2a ? a2aEndpoint ?? httpEndpoint ?? legacyEndpoint ?? invocationsEndpoint : hasX402 ? invocationsEndpoint ?? legacyEndpoint ?? httpEndpoint ?? a2aEndpoint : legacyEndpoint ?? httpEndpoint ?? a2aEndpoint ?? invocationsEndpoint;
|
|
17871
|
+
const x402Endpoint = invocationsEndpoint ?? legacyEndpoint;
|
|
17872
|
+
const currentX402 = hasB402PaymentFace(currentFaces) ? x402DeploymentSnapshot(agentRoot2, "self") : null;
|
|
17291
17873
|
const x402State = deployment.x402 ?? currentX402;
|
|
17292
17874
|
const x402StateSource = deployment.x402 ? "deployment-record" : "current-scaffold";
|
|
17293
17875
|
const x402Drift = deployment.x402 !== void 0 && !sameX402Snapshot(deployment.x402, currentX402);
|
|
17294
|
-
const currentCarrier =
|
|
17876
|
+
const currentCarrier = hasB402PaymentFace(currentFaces) ? inspectAzureX402Carrier(agentRoot2).status : null;
|
|
17295
17877
|
const carrierDrift = deployment.x402Carrier !== void 0 && deployment.x402Carrier !== currentCarrier;
|
|
17296
17878
|
const configurationDrift = facesDrift || x402Drift || carrierDrift;
|
|
17297
17879
|
const carrierVerified = deployment.x402Carrier === "http-envelope-v1";
|
|
@@ -17299,11 +17881,12 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17299
17881
|
deployment.x402,
|
|
17300
17882
|
"azure-foundry",
|
|
17301
17883
|
"self",
|
|
17302
|
-
null
|
|
17303
|
-
|
|
17304
|
-
|
|
17305
|
-
const
|
|
17306
|
-
const
|
|
17884
|
+
null,
|
|
17885
|
+
paymentProtocol
|
|
17886
|
+
) : x402DeploySummary(agentRoot2, "self", null) : deployment.x402Carrier === "incompatible-scaffold" ? "The deployed Azure entrypoint did not mount createEnvelopeMiddleware and B402Seller. Re-scaffold the unified entrypoint, review the diff, and redeploy." : `This deployment record predates ${paymentLabel} carrier verification, so the deployed artifact cannot be treated as ${paymentLabel}-capable. Verify or migrate the unified entrypoint and redeploy.` : "";
|
|
17887
|
+
const x402Status = carrierVerified ? x402State?.status ?? (x402Summary.includes("ACTIVE in FREE mode") ? "free" : x402Summary.startsWith(`${paymentProtocol} rail is ACTIVE`) ? "active" : "dormant") : "incompatible-scaffold";
|
|
17888
|
+
const x402Body = hasX402 && carrierVerified ? azureX402CarrierBody(paymentProtocol) : void 0;
|
|
17889
|
+
const body = hasA2a ? azureA2aQuickVerifyBody() : x402Body;
|
|
17307
17890
|
const x402Info = hasX402 ? carrierVerified ? {
|
|
17308
17891
|
status: x402Status,
|
|
17309
17892
|
access: x402Status === "dormant" ? "dormant" : "requires-external-gateway",
|
|
@@ -17314,6 +17897,7 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17314
17897
|
state_source: x402StateSource,
|
|
17315
17898
|
...x402Status === "dormant" ? {} : {
|
|
17316
17899
|
request: {
|
|
17900
|
+
endpoint: x402Endpoint,
|
|
17317
17901
|
method: "POST",
|
|
17318
17902
|
content_type: "application/json",
|
|
17319
17903
|
body: x402Body,
|
|
@@ -17336,6 +17920,12 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17336
17920
|
name: deployment.name,
|
|
17337
17921
|
deployment_id: deployment.deploymentId,
|
|
17338
17922
|
endpoint,
|
|
17923
|
+
endpoints: {
|
|
17924
|
+
http: httpEndpoint,
|
|
17925
|
+
invocations: invocationsEndpoint,
|
|
17926
|
+
a2a: a2aEndpoint,
|
|
17927
|
+
agent_card: agentCardEndpoint
|
|
17928
|
+
},
|
|
17339
17929
|
faces,
|
|
17340
17930
|
faces_source: facesSource,
|
|
17341
17931
|
configuration_drift: configurationDrift,
|
|
@@ -17349,7 +17939,7 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17349
17939
|
content_type: "application/json",
|
|
17350
17940
|
body
|
|
17351
17941
|
} : null,
|
|
17352
|
-
...x402Info ? {
|
|
17942
|
+
...x402Info ? { [paymentProtocol]: x402Info } : {}
|
|
17353
17943
|
},
|
|
17354
17944
|
null,
|
|
17355
17945
|
2
|
|
@@ -17358,7 +17948,17 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17358
17948
|
return 0;
|
|
17359
17949
|
}
|
|
17360
17950
|
printOut("bag deploy info \u2014 Azure Foundry self-deploy");
|
|
17361
|
-
printOut(` endpoint: ${
|
|
17951
|
+
if (httpEndpoint) printOut(` HTTP endpoint: ${httpEndpoint}`);
|
|
17952
|
+
if (invocationsEndpoint) {
|
|
17953
|
+
printOut(` Invocations tunnel endpoint: ${invocationsEndpoint}`);
|
|
17954
|
+
}
|
|
17955
|
+
if (a2aEndpoint) printOut(` A2A endpoint: ${a2aEndpoint}`);
|
|
17956
|
+
if (agentCardEndpoint) {
|
|
17957
|
+
printOut(` Agent Card endpoint: ${agentCardEndpoint}`);
|
|
17958
|
+
}
|
|
17959
|
+
if (!httpEndpoint && !invocationsEndpoint && !a2aEndpoint && endpoint) {
|
|
17960
|
+
printOut(` endpoint: ${endpoint}`);
|
|
17961
|
+
}
|
|
17362
17962
|
printOut(` deployed faces: ${faces.join(", ")} (${facesSource})`);
|
|
17363
17963
|
if (configurationDrift) {
|
|
17364
17964
|
printErr(
|
|
@@ -17373,31 +17973,38 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17373
17973
|
printOut(` body: ${JSON.stringify(body)}`);
|
|
17374
17974
|
} else {
|
|
17375
17975
|
printOut(
|
|
17376
|
-
|
|
17976
|
+
` request: unavailable until the ${paymentLabel} scaffold is migrated and redeployed`
|
|
17377
17977
|
);
|
|
17378
17978
|
}
|
|
17379
17979
|
if (x402Info) {
|
|
17380
17980
|
if (x402Info.access === "incompatible-scaffold") {
|
|
17381
|
-
printOut(
|
|
17981
|
+
printOut(
|
|
17982
|
+
` ${paymentProtocol} access: incompatible-scaffold \u2014 ${x402Info.summary}`
|
|
17983
|
+
);
|
|
17382
17984
|
} else if (x402Info.access === "dormant") {
|
|
17383
|
-
printOut(`
|
|
17985
|
+
printOut(` ${paymentProtocol} access: dormant \u2014 ${x402Info.summary}`);
|
|
17384
17986
|
} else {
|
|
17385
17987
|
printOut(
|
|
17386
|
-
|
|
17988
|
+
` ${paymentProtocol} access: requires-external-gateway (the Foundry endpoint has no raw anonymous /${paymentProtocol} URL)`
|
|
17387
17989
|
);
|
|
17388
17990
|
printOut(
|
|
17389
|
-
|
|
17991
|
+
` ${paymentProtocol} carrier: http-envelope-v1 over this Entra-authenticated Invocations endpoint`
|
|
17390
17992
|
);
|
|
17391
17993
|
if (hasA2a && x402Body) {
|
|
17392
17994
|
printOut(
|
|
17393
|
-
`
|
|
17995
|
+
` ${paymentProtocol} request example: ${JSON.stringify(x402Body)} (expect work in FREE mode or 402 before payment in PAID mode)`
|
|
17394
17996
|
);
|
|
17395
17997
|
}
|
|
17396
|
-
printOut(
|
|
17998
|
+
printOut(
|
|
17999
|
+
` ${paymentProtocol} gateway guide: ${paymentProtocol === "mpp" ? "docs/guides/mpp-b402-selling.md" : foundryX402GatewayGuideUrl()}`
|
|
18000
|
+
);
|
|
17397
18001
|
}
|
|
17398
18002
|
}
|
|
17399
18003
|
if (opts.withCurl && body) {
|
|
17400
|
-
printOut(
|
|
18004
|
+
printOut(
|
|
18005
|
+
hasA2a ? "\nQuick-verify curl:" : `
|
|
18006
|
+
${paymentLabel} request curl:`
|
|
18007
|
+
);
|
|
17401
18008
|
printOut(
|
|
17402
18009
|
` AZURE_TOKEN=$(az account get-access-token --resource ${AZURE_FOUNDRY_TOKEN_RESOURCE} --query accessToken -o tsv)`
|
|
17403
18010
|
);
|
|
@@ -17406,7 +18013,7 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17406
18013
|
printOut(
|
|
17407
18014
|
` AZURE_RESPONSE=$(curl -sS -D "$AZURE_HEADERS" -X POST "$AZURE_ENDPOINT" \\
|
|
17408
18015
|
-H "Authorization: Bearer $AZURE_TOKEN" \\
|
|
17409
|
-
-H "Content-Type: application/json" \\
|
|
18016
|
+
-H "Content-Type: application/json"${hasA2a ? ' \\\n -H "A2A-Version: 1.0"' : ""} \\
|
|
17410
18017
|
-d '${JSON.stringify(body)}')`
|
|
17411
18018
|
);
|
|
17412
18019
|
printOut(' printf "%s\\n" "$AZURE_RESPONSE"');
|
|
@@ -17417,7 +18024,8 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17417
18024
|
printOut(' bag deploy logs --provider azure --session "$AZURE_SESSION"');
|
|
17418
18025
|
if (!hasA2a && x402Body) {
|
|
17419
18026
|
printOut(
|
|
17420
|
-
|
|
18027
|
+
`
|
|
18028
|
+
Decode response.body from base64 to read the inner ${paymentProtocol} response.`
|
|
17421
18029
|
);
|
|
17422
18030
|
printOut(
|
|
17423
18031
|
` node -e 'const outer=JSON.parse(process.argv[1]); console.log(Buffer.from(outer.body,"base64").toString("utf8"))' "$AZURE_RESPONSE"`
|
|
@@ -17428,7 +18036,7 @@ function printAzureInfo(root, deployment, opts) {
|
|
|
17428
18036
|
);
|
|
17429
18037
|
} else if (opts.withCurl) {
|
|
17430
18038
|
printErr(
|
|
17431
|
-
|
|
18039
|
+
`warning: no ${paymentLabel} curl was emitted because the recorded Azure deployment does not prove http-envelope-v1 support; migrate and redeploy first.`
|
|
17432
18040
|
);
|
|
17433
18041
|
}
|
|
17434
18042
|
return 0;
|
|
@@ -17697,7 +18305,7 @@ import {
|
|
|
17697
18305
|
BSC_TESTNET_MIN_U,
|
|
17698
18306
|
BSC_TESTNET_U_FAUCET_URL as BSC_TESTNET_U_FAUCET_URL3,
|
|
17699
18307
|
ERC8183_SUBMIT_GAS_RESERVE_BNB,
|
|
17700
|
-
fromRaw as
|
|
18308
|
+
fromRaw as fromRaw4,
|
|
17701
18309
|
getNetwork as getNetwork9,
|
|
17702
18310
|
tbnbFaucetHint as tbnbFaucetHint3
|
|
17703
18311
|
} from "@bnbagent/studio-runtime/networks";
|
|
@@ -17720,6 +18328,13 @@ var SECRET_ENV_KEYS = [
|
|
|
17720
18328
|
"WALLET_PASSWORD",
|
|
17721
18329
|
"PIEVERSE_LLM_API_KEY",
|
|
17722
18330
|
"STORAGE_API_KEY",
|
|
18331
|
+
"DELIVERABLE_S3_ACCESS_KEY_ID",
|
|
18332
|
+
"DELIVERABLE_S3_SECRET_ACCESS_KEY",
|
|
18333
|
+
"DELIVERABLE_S3_SESSION_TOKEN",
|
|
18334
|
+
"DELIVERABLE_AZURE_SAS_TOKEN",
|
|
18335
|
+
"DELIVERABLE_AZURE_TENANT_ID",
|
|
18336
|
+
"DELIVERABLE_AZURE_CLIENT_ID",
|
|
18337
|
+
"DELIVERABLE_AZURE_CLIENT_SECRET",
|
|
17723
18338
|
"TWAK_ACCESS_ID",
|
|
17724
18339
|
"TWAK_HMAC_SECRET",
|
|
17725
18340
|
"TURNKEY_API_PRIVATE_KEY"
|
|
@@ -17799,11 +18414,11 @@ async function cmdDoctor(opts) {
|
|
|
17799
18414
|
);
|
|
17800
18415
|
checks.push(...checkPriceBounds(data));
|
|
17801
18416
|
checks.push(...checkX402Seller(data, projectRoot));
|
|
17802
|
-
|
|
18417
|
+
const destination = String(tableOf14(data, "deploy").destination ?? "self").trim().toLowerCase();
|
|
18418
|
+
checks.push(...checkStorage(data, destination));
|
|
17803
18419
|
checks.push(...await checkBalances(projectRoot, data, opts.network));
|
|
17804
18420
|
checks.push(...await checkPieverseMainnetU(projectRoot, data));
|
|
17805
18421
|
checks.push(...await check8004(projectRoot, data, opts.network));
|
|
17806
|
-
const destination = String(tableOf14(data, "deploy").destination ?? "self").trim().toLowerCase();
|
|
17807
18422
|
checks.push(...await checkDeployTools(data, destination));
|
|
17808
18423
|
checks.push(
|
|
17809
18424
|
...await checkPlatformZipBundle(data, destination, projectRoot)
|
|
@@ -18185,7 +18800,7 @@ async function checkWalletAltana(walletCfg, projectRoot, data) {
|
|
|
18185
18800
|
allowanceRaw > 0n ? {
|
|
18186
18801
|
name: "[wallet] Altana x402 buying",
|
|
18187
18802
|
status: PASS,
|
|
18188
|
-
detail: `U\u2192Permit2 allowance ${
|
|
18803
|
+
detail: `U\u2192Permit2 allowance ${fromRaw4(allowanceRaw, uToken.decimals)} U on ${networkName} (b402 permit2 rail armed)`
|
|
18189
18804
|
} : {
|
|
18190
18805
|
name: "[wallet] Altana x402 buying",
|
|
18191
18806
|
status: WARN,
|
|
@@ -18621,19 +19236,21 @@ function checkPriceBounds(data) {
|
|
|
18621
19236
|
}
|
|
18622
19237
|
function checkX402Seller(data, agentRoot2) {
|
|
18623
19238
|
const payments = tableOf14(data, "payments");
|
|
18624
|
-
const sellerValue = payments.x402_seller;
|
|
19239
|
+
const sellerValue = payments.b402_seller !== void 0 ? payments.b402_seller : payments.x402_seller;
|
|
18625
19240
|
if (sellerValue === null || typeof sellerValue !== "object" || Array.isArray(sellerValue)) {
|
|
18626
19241
|
return [];
|
|
18627
19242
|
}
|
|
18628
19243
|
const seller = sellerValue;
|
|
18629
19244
|
if (seller.enabled !== true) return [];
|
|
19245
|
+
const paymentProtocol = b402PaymentProtocol(data);
|
|
19246
|
+
const paymentPath = paymentProtocol === "mpp" ? "/mpp" : "/x402";
|
|
18630
19247
|
const pricing = x402SellerPricingState(seller);
|
|
18631
19248
|
if (pricing.kind === "invalid") {
|
|
18632
19249
|
return [
|
|
18633
19250
|
{
|
|
18634
19251
|
name: "x402 pricing",
|
|
18635
19252
|
status: FAIL,
|
|
18636
|
-
detail: "[payments.
|
|
19253
|
+
detail: "[payments.b402_seller].price_usd must be a non-negative decimal string."
|
|
18637
19254
|
}
|
|
18638
19255
|
];
|
|
18639
19256
|
}
|
|
@@ -18645,7 +19262,7 @@ function checkX402Seller(data, agentRoot2) {
|
|
|
18645
19262
|
{
|
|
18646
19263
|
name: "x402 pricing",
|
|
18647
19264
|
status: PASS,
|
|
18648
|
-
detail:
|
|
19265
|
+
detail: `FREE \u2014 ${paymentPath} is anonymous; B402 verify/settle, token payment, and settlement audit are bypassed.`
|
|
18649
19266
|
}
|
|
18650
19267
|
];
|
|
18651
19268
|
const credentials2 = b402Credentials(agentRoot2);
|
|
@@ -18660,7 +19277,13 @@ function checkX402Seller(data, agentRoot2) {
|
|
|
18660
19277
|
out2.push({
|
|
18661
19278
|
name: "x402 runtime",
|
|
18662
19279
|
status: fallbackStatus,
|
|
18663
|
-
detail: `${runtime} has no
|
|
19280
|
+
detail: `${runtime} has no ${paymentPath} route; FREE pricing does not remove that runtime limitation.`
|
|
19281
|
+
});
|
|
19282
|
+
} else if (runtime === "azure-foundry") {
|
|
19283
|
+
out2.push({
|
|
19284
|
+
name: "x402 ingress",
|
|
19285
|
+
status: INFO,
|
|
19286
|
+
detail: `Azure Foundry runs ${paymentPath} through http-envelope-v1; anonymous access requires an operator-run external gateway.`
|
|
18664
19287
|
});
|
|
18665
19288
|
} else if (runtime === "azure-foundry") {
|
|
18666
19289
|
out2.push({
|
|
@@ -18694,11 +19317,32 @@ function checkX402Seller(data, agentRoot2) {
|
|
|
18694
19317
|
detail: credentials.any ? `B402 credentials are partial; set BASE_URL, CLIENT_ID, ACCESS_TOKEN, and exactly one private-key form. Confirm they were issued for this exact agent wallet and environment; never reuse them across wallets. Apply: ${B402_DEVELOPER_ACCOUNT_URL}` : `PAID mode needs the four B402 merchant credentials; without them the rail starts dormant. ${B402_PAID_ONBOARDING_GUIDANCE}`
|
|
18695
19318
|
});
|
|
18696
19319
|
}
|
|
19320
|
+
if (paymentProtocol === "mpp") {
|
|
19321
|
+
const envPath = envLocalPath18(agentRoot2);
|
|
19322
|
+
const secretKey = process.env.MPP_SECRET_KEY || getEnvVar(envPath, "MPP_SECRET_KEY");
|
|
19323
|
+
const realm = process.env.MPP_REALM || getEnvVar(envPath, "MPP_REALM");
|
|
19324
|
+
out.push({
|
|
19325
|
+
name: "MPP challenge key",
|
|
19326
|
+
status: secretKey && Buffer.byteLength(secretKey, "utf8") >= 32 ? PASS : fallbackStatus,
|
|
19327
|
+
detail: secretKey && Buffer.byteLength(secretKey, "utf8") >= 32 ? "MPP_SECRET_KEY has at least 32 bytes." : "PAID MPP+B402 requires MPP_SECRET_KEY with at least 32 UTF-8 bytes."
|
|
19328
|
+
});
|
|
19329
|
+
out.push({
|
|
19330
|
+
name: "MPP realm",
|
|
19331
|
+
status: realm ? PASS : fallbackStatus,
|
|
19332
|
+
detail: realm ? `MPP_REALM=${realm}` : "PAID MPP+B402 requires a stable MPP_REALM hostname."
|
|
19333
|
+
});
|
|
19334
|
+
}
|
|
18697
19335
|
if (!X402_CAPABLE_RUNTIMES.has(runtime)) {
|
|
18698
19336
|
out.push({
|
|
18699
19337
|
name: "x402 runtime",
|
|
18700
19338
|
status: fallbackStatus,
|
|
18701
|
-
detail: `${runtime} has no
|
|
19339
|
+
detail: `${runtime} has no ${paymentPath} route; deploy this rail to AgentCore.`
|
|
19340
|
+
});
|
|
19341
|
+
} else if (runtime === "azure-foundry") {
|
|
19342
|
+
out.push({
|
|
19343
|
+
name: "x402 ingress",
|
|
19344
|
+
status: INFO,
|
|
19345
|
+
detail: `Azure Foundry runs ${paymentPath} through http-envelope-v1; anonymous access requires an operator-run external gateway.`
|
|
18702
19346
|
});
|
|
18703
19347
|
} else if (runtime === "azure-foundry") {
|
|
18704
19348
|
out.push({
|
|
@@ -18709,8 +19353,18 @@ function checkX402Seller(data, agentRoot2) {
|
|
|
18709
19353
|
}
|
|
18710
19354
|
return out;
|
|
18711
19355
|
}
|
|
18712
|
-
function checkStorage(data) {
|
|
18713
|
-
const
|
|
19356
|
+
function checkStorage(data, destination = "self") {
|
|
19357
|
+
const storage = tableOf14(data, "storage");
|
|
19358
|
+
const kind = String(storage.kind ?? "local").toLowerCase();
|
|
19359
|
+
if (destination === "platform") {
|
|
19360
|
+
return [
|
|
19361
|
+
{
|
|
19362
|
+
name: "storage (managed platform)",
|
|
19363
|
+
status: INFO,
|
|
19364
|
+
detail: "deliverable storage is managed by the Platform; user-owned S3/Azure configuration and credentials are not required."
|
|
19365
|
+
}
|
|
19366
|
+
];
|
|
19367
|
+
}
|
|
18714
19368
|
if (kind === "ipfs") {
|
|
18715
19369
|
if (!process.env.STORAGE_API_URL) {
|
|
18716
19370
|
return [
|
|
@@ -18738,6 +19392,68 @@ function checkStorage(data) {
|
|
|
18738
19392
|
}
|
|
18739
19393
|
];
|
|
18740
19394
|
}
|
|
19395
|
+
if (kind === "s3") {
|
|
19396
|
+
const missingConfig = ["bucket", "region"].filter(
|
|
19397
|
+
(key) => !String(storage[key] ?? "").trim()
|
|
19398
|
+
);
|
|
19399
|
+
const ambient = String(storage.credential_mode ?? "env") === "ambient";
|
|
19400
|
+
const missingSecrets = ambient ? [] : [
|
|
19401
|
+
"DELIVERABLE_S3_ACCESS_KEY_ID",
|
|
19402
|
+
"DELIVERABLE_S3_SECRET_ACCESS_KEY"
|
|
19403
|
+
].filter((key) => !process.env[key]);
|
|
19404
|
+
if (missingConfig.length > 0 || missingSecrets.length > 0) {
|
|
19405
|
+
return [
|
|
19406
|
+
{
|
|
19407
|
+
name: "storage (s3)",
|
|
19408
|
+
status: WARN,
|
|
19409
|
+
detail: `kind=s3 is incomplete; missing ${[
|
|
19410
|
+
...missingConfig.map((key) => `[storage].${key}`),
|
|
19411
|
+
...missingSecrets
|
|
19412
|
+
].join(
|
|
19413
|
+
", "
|
|
19414
|
+
)}. Use namespaced credentials or set credential_mode='ambient' for a user-owned runtime identity.`
|
|
19415
|
+
}
|
|
19416
|
+
];
|
|
19417
|
+
}
|
|
19418
|
+
return [
|
|
19419
|
+
{
|
|
19420
|
+
name: "storage (s3)",
|
|
19421
|
+
status: PASS,
|
|
19422
|
+
detail: `kind=s3; bucket/region configured; credentials=${ambient ? "ambient identity" : "injected secret"}; public URL=${storage.public_base_url ? "custom base" : "native S3"}`
|
|
19423
|
+
}
|
|
19424
|
+
];
|
|
19425
|
+
}
|
|
19426
|
+
if (kind === "azure-blob") {
|
|
19427
|
+
const missingConfig = ["account_url", "container"].filter(
|
|
19428
|
+
(key) => !String(storage[key] ?? "").trim()
|
|
19429
|
+
);
|
|
19430
|
+
const ambient = String(storage.credential_mode ?? "env") === "ambient";
|
|
19431
|
+
const hasSas = Boolean(process.env.DELIVERABLE_AZURE_SAS_TOKEN);
|
|
19432
|
+
const servicePrincipalKeys = [
|
|
19433
|
+
"DELIVERABLE_AZURE_TENANT_ID",
|
|
19434
|
+
"DELIVERABLE_AZURE_CLIENT_ID",
|
|
19435
|
+
"DELIVERABLE_AZURE_CLIENT_SECRET"
|
|
19436
|
+
];
|
|
19437
|
+
const hasServicePrincipal = servicePrincipalKeys.every(
|
|
19438
|
+
(key) => process.env[key]
|
|
19439
|
+
);
|
|
19440
|
+
if (missingConfig.length > 0 || !ambient && !hasSas && !hasServicePrincipal) {
|
|
19441
|
+
return [
|
|
19442
|
+
{
|
|
19443
|
+
name: "storage (azure-blob)",
|
|
19444
|
+
status: WARN,
|
|
19445
|
+
detail: `kind=azure-blob is incomplete${missingConfig.length > 0 ? `; missing ${missingConfig.map((key) => `[storage].${key}`).join(", ")}` : ""}. Configure DELIVERABLE_AZURE_SAS_TOKEN, a complete namespaced service-principal credential set, or credential_mode='ambient'.`
|
|
19446
|
+
}
|
|
19447
|
+
];
|
|
19448
|
+
}
|
|
19449
|
+
return [
|
|
19450
|
+
{
|
|
19451
|
+
name: "storage (azure-blob)",
|
|
19452
|
+
status: PASS,
|
|
19453
|
+
detail: `kind=azure-blob; account/container configured; credentials=${ambient ? "ambient identity" : hasSas ? "SAS secret" : "service principal"}; public URL=${storage.public_base_url ? "custom base" : "native Blob"}`
|
|
19454
|
+
}
|
|
19455
|
+
];
|
|
19456
|
+
}
|
|
18741
19457
|
if (kind === "local") {
|
|
18742
19458
|
return [
|
|
18743
19459
|
{
|
|
@@ -18751,7 +19467,7 @@ function checkStorage(data) {
|
|
|
18751
19467
|
{
|
|
18752
19468
|
name: "storage",
|
|
18753
19469
|
status: WARN,
|
|
18754
|
-
detail: `unknown [storage].kind "${kind}" \u2014 falls back to local storage. Set kind = "ipfs"
|
|
19470
|
+
detail: `unknown [storage].kind "${kind}" \u2014 falls back to local storage. Set kind = "ipfs", "s3", "azure-blob", or "local".`
|
|
18755
19471
|
}
|
|
18756
19472
|
];
|
|
18757
19473
|
}
|
|
@@ -18897,7 +19613,7 @@ async function checkPieverseMainnetU(projectRoot, data) {
|
|
|
18897
19613
|
return [];
|
|
18898
19614
|
}
|
|
18899
19615
|
const raw = await readUBalanceRaw(addr, pvNetwork) ?? 0n;
|
|
18900
|
-
const human = Number.parseFloat(
|
|
19616
|
+
const human = Number.parseFloat(fromRaw4(raw, uToken.decimals));
|
|
18901
19617
|
if (human < threshold) {
|
|
18902
19618
|
return [
|
|
18903
19619
|
{
|
|
@@ -19274,7 +19990,7 @@ import {
|
|
|
19274
19990
|
updateServiceEndpoint as updateServiceEndpoint2
|
|
19275
19991
|
} from "@bnbagent/studio-runtime/erc8004";
|
|
19276
19992
|
import {
|
|
19277
|
-
fromRaw as
|
|
19993
|
+
fromRaw as fromRaw5,
|
|
19278
19994
|
getNetwork as getNetwork10,
|
|
19279
19995
|
tbnbFaucetHint as tbnbFaucetHint4
|
|
19280
19996
|
} from "@bnbagent/studio-runtime/networks";
|
|
@@ -19401,9 +20117,9 @@ function resolveProtocol(override) {
|
|
|
19401
20117
|
return "A2A";
|
|
19402
20118
|
}
|
|
19403
20119
|
const faces = stackFaces(tableOf15(cfg, "stack"), tableOf15(cfg, "payments"));
|
|
19404
|
-
if (faces.length === 1 &&
|
|
20120
|
+
if (faces.length === 1 && hasB402PaymentFace(faces)) {
|
|
19405
20121
|
printErr(
|
|
19406
|
-
|
|
20122
|
+
`warning: this ${faces[0]}-only project has no public ERC-8004 protocol face; A2A is only the internal AgentCore protocol declaration.`
|
|
19407
20123
|
);
|
|
19408
20124
|
}
|
|
19409
20125
|
return nativeProtocolOf(faces);
|
|
@@ -19428,7 +20144,7 @@ async function precheckRegisterGas(wallet, network) {
|
|
|
19428
20144
|
let balance;
|
|
19429
20145
|
try {
|
|
19430
20146
|
const raw = await readNativeBalanceRaw(wallet.address, network);
|
|
19431
|
-
balance = Number.parseFloat(
|
|
20147
|
+
balance = Number.parseFloat(fromRaw5(raw, 18));
|
|
19432
20148
|
} catch (exc) {
|
|
19433
20149
|
printErr(
|
|
19434
20150
|
`warning: could not read ${wallet.address} balance to pre-check gas (${errMsg4(exc)}); proceeding.`
|
|
@@ -20592,12 +21308,517 @@ function registerUninstall(program) {
|
|
|
20592
21308
|
);
|
|
20593
21309
|
}
|
|
20594
21310
|
|
|
20595
|
-
// src/cli/
|
|
21311
|
+
// src/cli/mpp.ts
|
|
21312
|
+
import * as fs42 from "fs";
|
|
20596
21313
|
import * as path43 from "path";
|
|
21314
|
+
import { maskUrlSecrets, recordChainOp } from "@bnbagent/studio-runtime/audit";
|
|
21315
|
+
import {
|
|
21316
|
+
findProjectRoot as findProjectRoot6,
|
|
21317
|
+
loadStudioToml as loadStudioToml23
|
|
21318
|
+
} from "@bnbagent/studio-runtime/config";
|
|
21319
|
+
import {
|
|
21320
|
+
MppBuyerPolicy,
|
|
21321
|
+
fetchMppWithPayment,
|
|
21322
|
+
quoteMppUrl
|
|
21323
|
+
} from "@bnbagent/studio-runtime/mpp";
|
|
21324
|
+
import { getNetwork as getNetwork11 } from "@bnbagent/studio-runtime/networks";
|
|
21325
|
+
import * as walletRt8 from "@bnbagent/studio-runtime/wallet";
|
|
21326
|
+
import {
|
|
21327
|
+
SELLER_TOKENS,
|
|
21328
|
+
caip2,
|
|
21329
|
+
usdToAtomic
|
|
21330
|
+
} from "@bnbagent/studio-runtime/x402";
|
|
21331
|
+
import { getAddress as getAddress3, isAddress } from "viem";
|
|
21332
|
+
|
|
21333
|
+
// src/cli/_signingTrust.ts
|
|
21334
|
+
import { getAddress as getAddress2 } from "viem";
|
|
21335
|
+
function table(value) {
|
|
21336
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
21337
|
+
}
|
|
21338
|
+
function trustEip712Domain(text2, cfg, network, asset) {
|
|
21339
|
+
const wallet = table(cfg.wallet);
|
|
21340
|
+
const walletKind2 = String(wallet.kind ?? "evm-local");
|
|
21341
|
+
if (walletKind2 !== "evm-local") return text2;
|
|
21342
|
+
const match = /^eip155:(\d+)$/.exec(network);
|
|
21343
|
+
if (match === null) {
|
|
21344
|
+
throw new Error(`cannot trust a non-EVM signing domain: ${network}`);
|
|
21345
|
+
}
|
|
21346
|
+
const chainId = Number(match[1]);
|
|
21347
|
+
if (!Number.isSafeInteger(chainId) || chainId <= 0) {
|
|
21348
|
+
throw new Error(`cannot trust invalid EIP-155 chain id: ${network}`);
|
|
21349
|
+
}
|
|
21350
|
+
const address = getAddress2(asset);
|
|
21351
|
+
const signing = table(wallet.signing);
|
|
21352
|
+
const rawDomains = signing.extra_domains ?? [];
|
|
21353
|
+
if (!Array.isArray(rawDomains)) {
|
|
21354
|
+
throw new Error("[wallet.signing].extra_domains must be an array");
|
|
21355
|
+
}
|
|
21356
|
+
const domains = rawDomains.map((entry, index) => {
|
|
21357
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
21358
|
+
throw new Error(
|
|
21359
|
+
`[wallet.signing].extra_domains[${index}] must be a [chain_id, address] pair`
|
|
21360
|
+
);
|
|
21361
|
+
}
|
|
21362
|
+
const existingChainId = Number(entry[0]);
|
|
21363
|
+
const existingAddress = getAddress2(String(entry[1]));
|
|
21364
|
+
return [existingChainId, existingAddress];
|
|
21365
|
+
});
|
|
21366
|
+
const alreadyTrusted = domains.some(
|
|
21367
|
+
(entry) => Array.isArray(entry) && Number(entry[0]) === chainId && String(entry[1]).toLowerCase() === address.toLowerCase()
|
|
21368
|
+
);
|
|
21369
|
+
if (alreadyTrusted) return text2;
|
|
21370
|
+
return updateSection(text2, "wallet.signing", {
|
|
21371
|
+
extra_domains: [...domains, [chainId, address]]
|
|
21372
|
+
});
|
|
21373
|
+
}
|
|
21374
|
+
|
|
21375
|
+
// src/cli/mpp.ts
|
|
21376
|
+
function registerMpp(program) {
|
|
21377
|
+
const mpp = program.command("mpp").description("Native MPP+B402 payment client helpers.");
|
|
21378
|
+
mpp.action(
|
|
21379
|
+
act(() => {
|
|
21380
|
+
printOut(
|
|
21381
|
+
"bag mpp: pick a sub-command. Try `bag mpp quote --help`, `bag mpp buy --help`, or `bag mpp trust --help`."
|
|
21382
|
+
);
|
|
21383
|
+
return 0;
|
|
21384
|
+
})
|
|
21385
|
+
);
|
|
21386
|
+
mpp.command("quote").description("Probe a native MPP+B402 URL without paying.").argument("<url>", "Absolute URL of the MPP-protected resource.").option("--network <name>", "Override [network].default.").option("--method <verb>", "HTTP verb (default: GET).", "GET").option("--json <body>", "JSON object body.").option("--local-dev", "DEV ONLY: permit an untrusted loopback target.").action(
|
|
21387
|
+
act(
|
|
21388
|
+
(url, opts) => cmdQuote(url, opts)
|
|
21389
|
+
)
|
|
21390
|
+
);
|
|
21391
|
+
mpp.command("buy").description("Pay a native MPP+B402 charge with $U via EIP-3009.").argument("<url>", "Absolute URL of the MPP-protected resource.").requiredOption(
|
|
21392
|
+
"--max-usd <n>",
|
|
21393
|
+
"Hard per-request USD cap.",
|
|
21394
|
+
Number.parseFloat
|
|
21395
|
+
).option("--network <name>", "Override [network].default.").option("--method <verb>", "HTTP verb (default: GET).", "GET").option("--json <body>", "JSON object body.").option(
|
|
21396
|
+
"--local-dev",
|
|
21397
|
+
"DEV ONLY: permit loopback and pin recipient/realm from its live challenge."
|
|
21398
|
+
).action(
|
|
21399
|
+
act(
|
|
21400
|
+
(url, opts) => cmdBuy2(url, opts)
|
|
21401
|
+
)
|
|
21402
|
+
);
|
|
21403
|
+
mpp.command("trust").description(
|
|
21404
|
+
"Probe and pin an MPP merchant's domain, realm, recipient, asset, and per-call cap."
|
|
21405
|
+
).argument("<url>", "Full https:// MPP-protected URL.").option(
|
|
21406
|
+
"--cap <usd>",
|
|
21407
|
+
"Per-call cap (default: live price).",
|
|
21408
|
+
Number.parseFloat
|
|
21409
|
+
).option("--name <key>", "Merchant config key (default: derived from host).").option(
|
|
21410
|
+
"--pay-to <addr>",
|
|
21411
|
+
"Expected recipient; must match the live challenge."
|
|
21412
|
+
).option(
|
|
21413
|
+
"--realm <realm>",
|
|
21414
|
+
"Expected MPP realm; must match the live challenge."
|
|
21415
|
+
).option(
|
|
21416
|
+
"--network <name>",
|
|
21417
|
+
"Settlement network (default: project network)."
|
|
21418
|
+
).option("--method <verb>", "Probe this HTTP verb first.").option("--yes", "Write without interactive confirmation.").action(
|
|
21419
|
+
act(
|
|
21420
|
+
(url, opts) => cmdTrust(url, opts)
|
|
21421
|
+
)
|
|
21422
|
+
);
|
|
21423
|
+
}
|
|
21424
|
+
function errorMessage(error) {
|
|
21425
|
+
return error instanceof Error ? error.message : String(error);
|
|
21426
|
+
}
|
|
21427
|
+
function printMppError(error) {
|
|
21428
|
+
const name = error instanceof Error ? error.name : "";
|
|
21429
|
+
printErr(
|
|
21430
|
+
name.startsWith("Mpp") ? `error: ${name}: ${errorMessage(error)}` : `error: ${errorMessage(error)}`
|
|
21431
|
+
);
|
|
21432
|
+
}
|
|
21433
|
+
function tableOf16(cfg, key) {
|
|
21434
|
+
const value = cfg[key];
|
|
21435
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
21436
|
+
}
|
|
21437
|
+
function loadPolicy2() {
|
|
21438
|
+
const root = findProjectRoot6();
|
|
21439
|
+
if (root === null) {
|
|
21440
|
+
printErr("error: no studio.toml found in cwd or any parent.");
|
|
21441
|
+
return null;
|
|
21442
|
+
}
|
|
21443
|
+
try {
|
|
21444
|
+
const cfg = loadStudioToml23(path43.join(root, "studio.toml"));
|
|
21445
|
+
return { root, cfg, policy: MppBuyerPolicy.fromToml(cfg) };
|
|
21446
|
+
} catch (error) {
|
|
21447
|
+
printErr(`error: failed to load studio.toml: ${errorMessage(error)}`);
|
|
21448
|
+
return null;
|
|
21449
|
+
}
|
|
21450
|
+
}
|
|
21451
|
+
function resolveNetwork3(cfg, override) {
|
|
21452
|
+
return override ?? String(tableOf16(cfg, "network").default ?? "bsc-mainnet");
|
|
21453
|
+
}
|
|
21454
|
+
function hostOf(url) {
|
|
21455
|
+
try {
|
|
21456
|
+
return new URL(url).hostname.toLowerCase();
|
|
21457
|
+
} catch {
|
|
21458
|
+
return "";
|
|
21459
|
+
}
|
|
21460
|
+
}
|
|
21461
|
+
function loopback(host) {
|
|
21462
|
+
return ["localhost", "127.0.0.1", "::1"].includes(
|
|
21463
|
+
host.replace(/^\[|\]$/g, "").toLowerCase()
|
|
21464
|
+
);
|
|
21465
|
+
}
|
|
21466
|
+
function hostAllowed(url, policy) {
|
|
21467
|
+
const host = hostOf(url);
|
|
21468
|
+
return policy.effectiveAllowedHosts.some(
|
|
21469
|
+
(allowed) => allowed.toLowerCase() === host
|
|
21470
|
+
);
|
|
21471
|
+
}
|
|
21472
|
+
function parseJson(raw) {
|
|
21473
|
+
if (raw === void 0) return null;
|
|
21474
|
+
const value = JSON.parse(raw);
|
|
21475
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
21476
|
+
throw new Error("--json must decode to a JSON object");
|
|
21477
|
+
}
|
|
21478
|
+
return value;
|
|
21479
|
+
}
|
|
21480
|
+
function assertLocalDev(url, enabled) {
|
|
21481
|
+
if (enabled && !loopback(hostOf(url))) {
|
|
21482
|
+
printErr("error: --local-dev applies to loopback targets only.");
|
|
21483
|
+
return false;
|
|
21484
|
+
}
|
|
21485
|
+
return true;
|
|
21486
|
+
}
|
|
21487
|
+
async function cmdQuote(url, opts) {
|
|
21488
|
+
const loaded = loadPolicy2();
|
|
21489
|
+
if (loaded === null) return 1;
|
|
21490
|
+
const localDev = opts.localDev ?? false;
|
|
21491
|
+
if (!assertLocalDev(url, localDev)) return 2;
|
|
21492
|
+
if (!localDev && !hostAllowed(url, loaded.policy)) {
|
|
21493
|
+
printErr(
|
|
21494
|
+
`error: MPP host '${hostOf(url) || url}' is not trusted; run \`bag mpp trust <url>\` first.`
|
|
21495
|
+
);
|
|
21496
|
+
return 1;
|
|
21497
|
+
}
|
|
21498
|
+
try {
|
|
21499
|
+
const quote = await quoteMppUrl(url, {
|
|
21500
|
+
method: opts.method,
|
|
21501
|
+
jsonBody: parseJson(opts.json),
|
|
21502
|
+
networkName: resolveNetwork3(loaded.cfg, opts.network),
|
|
21503
|
+
policy: loaded.policy,
|
|
21504
|
+
allowedHosts: localDev ? null : void 0,
|
|
21505
|
+
allowInsecureLoopback: localDev
|
|
21506
|
+
});
|
|
21507
|
+
printOut(JSON.stringify(quote, jsonReplacer, 2));
|
|
21508
|
+
return quote.challenge === null && quote.statusCode === 402 ? 1 : 0;
|
|
21509
|
+
} catch (error) {
|
|
21510
|
+
printMppError(error);
|
|
21511
|
+
return error instanceof SyntaxError ? 2 : 1;
|
|
21512
|
+
}
|
|
21513
|
+
}
|
|
21514
|
+
async function cmdBuy2(url, opts) {
|
|
21515
|
+
const loaded = loadPolicy2();
|
|
21516
|
+
if (loaded === null) return 1;
|
|
21517
|
+
if (!Number.isFinite(opts.maxUsd) || opts.maxUsd <= 0) {
|
|
21518
|
+
printErr("error: --max-usd must be a positive number.");
|
|
21519
|
+
return 2;
|
|
21520
|
+
}
|
|
21521
|
+
const localDev = opts.localDev ?? false;
|
|
21522
|
+
if (!assertLocalDev(url, localDev)) return 2;
|
|
21523
|
+
if (!localDev && !hostAllowed(url, loaded.policy)) {
|
|
21524
|
+
printErr(
|
|
21525
|
+
`error: MPP host '${hostOf(url) || url}' is not trusted; run \`bag mpp trust <url>\` first.`
|
|
21526
|
+
);
|
|
21527
|
+
return 1;
|
|
21528
|
+
}
|
|
21529
|
+
let jsonBody;
|
|
21530
|
+
try {
|
|
21531
|
+
jsonBody = parseJson(opts.json);
|
|
21532
|
+
} catch (error) {
|
|
21533
|
+
printErr(`error: ${errorMessage(error)}`);
|
|
21534
|
+
return 2;
|
|
21535
|
+
}
|
|
21536
|
+
const networkName = resolveNetwork3(loaded.cfg, opts.network);
|
|
21537
|
+
let expectedRecipient = null;
|
|
21538
|
+
let expectedRealm = null;
|
|
21539
|
+
if (localDev) {
|
|
21540
|
+
try {
|
|
21541
|
+
const quote = await quoteMppUrl(url, {
|
|
21542
|
+
method: opts.method,
|
|
21543
|
+
jsonBody,
|
|
21544
|
+
networkName,
|
|
21545
|
+
policy: loaded.policy,
|
|
21546
|
+
allowedHosts: null,
|
|
21547
|
+
allowInsecureLoopback: true
|
|
21548
|
+
});
|
|
21549
|
+
if (quote.challenge === null) {
|
|
21550
|
+
printErr(
|
|
21551
|
+
"error: loopback target returned no payable MPP+B402 challenge."
|
|
21552
|
+
);
|
|
21553
|
+
return 1;
|
|
21554
|
+
}
|
|
21555
|
+
expectedRecipient = quote.challenge.recipient;
|
|
21556
|
+
expectedRealm = quote.challenge.realm;
|
|
21557
|
+
printOut(
|
|
21558
|
+
`\u26A0 DEV-ONLY: accepting live recipient ${expectedRecipient} and realm '${expectedRealm}'. Never use this outside your own machine.`
|
|
21559
|
+
);
|
|
21560
|
+
} catch (error) {
|
|
21561
|
+
printMppError(error);
|
|
21562
|
+
return 1;
|
|
21563
|
+
}
|
|
21564
|
+
}
|
|
21565
|
+
let wallet;
|
|
21566
|
+
try {
|
|
21567
|
+
wallet = walletRt8.getWallet();
|
|
21568
|
+
} catch (error) {
|
|
21569
|
+
printErr(`error: wallet unavailable: ${errorMessage(error)}`);
|
|
21570
|
+
return 1;
|
|
21571
|
+
}
|
|
21572
|
+
if (typeof wallet.supports === "function" && !wallet.supports("sign.typed_data")) {
|
|
21573
|
+
printErr(
|
|
21574
|
+
"error: MPP+B402 buying requires wallet capability sign.typed_data; use wallet.kind = 'evm-local'."
|
|
21575
|
+
);
|
|
21576
|
+
return 1;
|
|
21577
|
+
}
|
|
21578
|
+
const network = getNetwork11(networkName);
|
|
21579
|
+
const token = SELLER_TOKENS[caip2(network.chainId)];
|
|
21580
|
+
if (!token) {
|
|
21581
|
+
printErr(`error: MPP+B402 has no trusted buyer token on ${networkName}.`);
|
|
21582
|
+
return 1;
|
|
21583
|
+
}
|
|
21584
|
+
try {
|
|
21585
|
+
const current = await readTokenBalanceRaw(
|
|
21586
|
+
wallet.address,
|
|
21587
|
+
token.address,
|
|
21588
|
+
networkName
|
|
21589
|
+
);
|
|
21590
|
+
const needed = BigInt(
|
|
21591
|
+
usdToAtomic(decimalText(opts.maxUsd, token.decimals), token.decimals)
|
|
21592
|
+
);
|
|
21593
|
+
if (current < needed) {
|
|
21594
|
+
printErr(
|
|
21595
|
+
`error: insufficient B402 $U on ${networkName}; current=${current} atomic, needed=${needed} atomic.`
|
|
21596
|
+
);
|
|
21597
|
+
return 1;
|
|
21598
|
+
}
|
|
21599
|
+
} catch (error) {
|
|
21600
|
+
printErr(`error: B402 $U balance check failed: ${errorMessage(error)}`);
|
|
21601
|
+
return 1;
|
|
21602
|
+
}
|
|
21603
|
+
const commonAudit = {
|
|
21604
|
+
chainId: network.chainId,
|
|
21605
|
+
asset: "U",
|
|
21606
|
+
agentAddr: wallet.address,
|
|
21607
|
+
projectRoot: loaded.root
|
|
21608
|
+
};
|
|
21609
|
+
const auditUrl = maskUrlSecrets(url);
|
|
21610
|
+
recordChainOp("mpp_buy", {
|
|
21611
|
+
...commonAudit,
|
|
21612
|
+
amount: String(opts.maxUsd),
|
|
21613
|
+
txHash: null,
|
|
21614
|
+
status: "submitted",
|
|
21615
|
+
context: { url: auditUrl, method: opts.method }
|
|
21616
|
+
});
|
|
21617
|
+
let result;
|
|
21618
|
+
try {
|
|
21619
|
+
result = await fetchMppWithPayment(url, {
|
|
21620
|
+
wallet,
|
|
21621
|
+
maxUsd: opts.maxUsd,
|
|
21622
|
+
networkName,
|
|
21623
|
+
method: opts.method,
|
|
21624
|
+
jsonBody,
|
|
21625
|
+
policy: loaded.policy,
|
|
21626
|
+
allowedHosts: localDev ? null : void 0,
|
|
21627
|
+
allowInsecureLoopback: localDev,
|
|
21628
|
+
expectedRecipient,
|
|
21629
|
+
expectedRealm
|
|
21630
|
+
});
|
|
21631
|
+
} catch (error) {
|
|
21632
|
+
const unknown = error instanceof Error && error.name === "MppPaymentOutcomeUnknownError";
|
|
21633
|
+
const amount = unknown && typeof error.amountUsd === "number" ? String(error.amountUsd) : String(opts.maxUsd);
|
|
21634
|
+
recordChainOp("mpp_buy", {
|
|
21635
|
+
...commonAudit,
|
|
21636
|
+
amount,
|
|
21637
|
+
txHash: null,
|
|
21638
|
+
status: unknown ? "unknown" : "failed",
|
|
21639
|
+
context: {
|
|
21640
|
+
url: auditUrl,
|
|
21641
|
+
method: opts.method,
|
|
21642
|
+
payment_outcome: unknown ? "unknown" : "not-dispatched",
|
|
21643
|
+
error: errorMessage(error).slice(0, 200)
|
|
21644
|
+
}
|
|
21645
|
+
});
|
|
21646
|
+
printMppError(error);
|
|
21647
|
+
if (unknown) printErr("Do not retry blindly; reconcile the payment first.");
|
|
21648
|
+
return 1;
|
|
21649
|
+
}
|
|
21650
|
+
const txHash = transactionReference(result.receipt?.reference);
|
|
21651
|
+
recordChainOp("mpp_buy", {
|
|
21652
|
+
...commonAudit,
|
|
21653
|
+
amount: String(result.paidUsd ?? 0),
|
|
21654
|
+
txHash,
|
|
21655
|
+
status: result.paymentOutcome === "unknown" ? "unknown" : "confirmed",
|
|
21656
|
+
counterparty: result.challenge?.recipient ?? null,
|
|
21657
|
+
context: {
|
|
21658
|
+
url: auditUrl,
|
|
21659
|
+
method: opts.method,
|
|
21660
|
+
payment_outcome: result.paymentOutcome,
|
|
21661
|
+
...result.receipt?.reference ? { payment_reference: result.receipt.reference } : {}
|
|
21662
|
+
}
|
|
21663
|
+
});
|
|
21664
|
+
printResult(result);
|
|
21665
|
+
const ok = result.statusCode >= 200 && result.statusCode < 300;
|
|
21666
|
+
return ok && result.paymentOutcome !== "unknown" ? 0 : 1;
|
|
21667
|
+
}
|
|
21668
|
+
async function cmdTrust(url, opts) {
|
|
21669
|
+
const loaded = loadPolicy2();
|
|
21670
|
+
if (loaded === null) return 1;
|
|
21671
|
+
const host = hostOf(url);
|
|
21672
|
+
const localHttp = url.startsWith("http://") && loopback(host);
|
|
21673
|
+
if (!url.startsWith("https://") && !localHttp || !host) {
|
|
21674
|
+
printErr(
|
|
21675
|
+
"error: trust requires an absolute https:// URL; http:// is allowed only for loopback development."
|
|
21676
|
+
);
|
|
21677
|
+
return 1;
|
|
21678
|
+
}
|
|
21679
|
+
const networkName = resolveNetwork3(loaded.cfg, opts.network);
|
|
21680
|
+
const probed = await probe(url, networkName, opts.method, localHttp);
|
|
21681
|
+
if (probed === null) return 1;
|
|
21682
|
+
const { offer, method } = probed;
|
|
21683
|
+
if (opts.payTo && (!isAddress(opts.payTo) || getAddress3(opts.payTo) !== offer.recipient)) {
|
|
21684
|
+
printErr(
|
|
21685
|
+
`error: live recipient ${offer.recipient} does not match --pay-to ${opts.payTo}.`
|
|
21686
|
+
);
|
|
21687
|
+
return 1;
|
|
21688
|
+
}
|
|
21689
|
+
if (opts.realm && opts.realm.trim().toLowerCase() !== offer.realm) {
|
|
21690
|
+
printErr(
|
|
21691
|
+
`error: live realm '${offer.realm}' does not match --realm '${opts.realm}'.`
|
|
21692
|
+
);
|
|
21693
|
+
return 1;
|
|
21694
|
+
}
|
|
21695
|
+
const cap = opts.cap ?? offer.amountUsd;
|
|
21696
|
+
if (!Number.isFinite(cap) || cap < offer.amountUsd) {
|
|
21697
|
+
printErr(
|
|
21698
|
+
`error: cap must be at least the live price $${offer.amountUsd.toFixed(6)}.`
|
|
21699
|
+
);
|
|
21700
|
+
return 1;
|
|
21701
|
+
}
|
|
21702
|
+
const key = opts.name ?? host.replaceAll(".", "-");
|
|
21703
|
+
printOut(`Merchant: ${host} (UNREVIEWED)`);
|
|
21704
|
+
printOut(` Realm: ${offer.realm}`);
|
|
21705
|
+
printOut(` payTo: ${offer.recipient}`);
|
|
21706
|
+
printOut(` Asset: ${offer.asset} on ${offer.network}`);
|
|
21707
|
+
printOut(` Method: b402.charge / ${offer.transferMethod}`);
|
|
21708
|
+
printOut(` Price: $${offer.amountUsd.toFixed(6)}`);
|
|
21709
|
+
printOut(` Cap: $${cap.toFixed(6)}`);
|
|
21710
|
+
printOut(
|
|
21711
|
+
" \u26A0 The endpoint is not Studio-reviewed. Verify realm and recipient out-of-band before trusting."
|
|
21712
|
+
);
|
|
21713
|
+
if (!await confirm(opts.yes ?? false)) {
|
|
21714
|
+
printOut("aborted \u2014 nothing written.");
|
|
21715
|
+
return 1;
|
|
21716
|
+
}
|
|
21717
|
+
try {
|
|
21718
|
+
const tomlPath = path43.join(loaded.root, "studio.toml");
|
|
21719
|
+
const text2 = fs42.readFileSync(tomlPath, "utf8");
|
|
21720
|
+
const fields = {
|
|
21721
|
+
domain: host,
|
|
21722
|
+
realm: offer.realm,
|
|
21723
|
+
pay_to: offer.recipient,
|
|
21724
|
+
network: offer.network,
|
|
21725
|
+
asset: offer.asset,
|
|
21726
|
+
per_call_cap_usd: cap,
|
|
21727
|
+
verified: false
|
|
21728
|
+
};
|
|
21729
|
+
const withMerchant = updateSection(
|
|
21730
|
+
text2,
|
|
21731
|
+
`payments.mpp.merchants.${key}`,
|
|
21732
|
+
fields
|
|
21733
|
+
);
|
|
21734
|
+
fs42.writeFileSync(
|
|
21735
|
+
tomlPath,
|
|
21736
|
+
trustEip712Domain(withMerchant, loaded.cfg, offer.network, offer.asset),
|
|
21737
|
+
"utf8"
|
|
21738
|
+
);
|
|
21739
|
+
printOut(`\u2713 Wrote [payments.mpp.merchants.${key}] to ${tomlPath}`);
|
|
21740
|
+
const methodFlag = method === "GET" ? "" : ` --method ${method}`;
|
|
21741
|
+
printOut(` bag mpp quote ${url}${methodFlag}`);
|
|
21742
|
+
printOut(` bag mpp buy ${url} --max-usd ${cap}${methodFlag}`);
|
|
21743
|
+
printOut(" bag recipe code mpp-buyer");
|
|
21744
|
+
return 0;
|
|
21745
|
+
} catch (error) {
|
|
21746
|
+
printErr(`error: failed to write studio.toml: ${errorMessage(error)}`);
|
|
21747
|
+
return 1;
|
|
21748
|
+
}
|
|
21749
|
+
}
|
|
21750
|
+
async function probe(url, networkName, methodHint, allowInsecureLoopback = false) {
|
|
21751
|
+
const methods = [
|
|
21752
|
+
...new Set([methodHint, "GET", "POST"].filter(Boolean))
|
|
21753
|
+
];
|
|
21754
|
+
let lastError = null;
|
|
21755
|
+
for (const method of methods) {
|
|
21756
|
+
try {
|
|
21757
|
+
const quote = await quoteMppUrl(url, {
|
|
21758
|
+
method,
|
|
21759
|
+
networkName,
|
|
21760
|
+
policy: null,
|
|
21761
|
+
allowedHosts: null,
|
|
21762
|
+
allowInsecureLoopback
|
|
21763
|
+
});
|
|
21764
|
+
if (quote.challenge) return { offer: quote.challenge, method };
|
|
21765
|
+
lastError = new Error(
|
|
21766
|
+
`HTTP ${quote.statusCode} returned no b402.charge challenge`
|
|
21767
|
+
);
|
|
21768
|
+
} catch (error) {
|
|
21769
|
+
lastError = error;
|
|
21770
|
+
}
|
|
21771
|
+
}
|
|
21772
|
+
printErr(
|
|
21773
|
+
`error: could not obtain an MPP+B402 challenge: ${errorMessage(lastError)}`
|
|
21774
|
+
);
|
|
21775
|
+
return null;
|
|
21776
|
+
}
|
|
21777
|
+
async function confirm(yes) {
|
|
21778
|
+
if (yes) return true;
|
|
21779
|
+
if (!stdinIsTty()) {
|
|
21780
|
+
printErr("error: non-interactive shell; re-run with --yes after review.");
|
|
21781
|
+
return false;
|
|
21782
|
+
}
|
|
21783
|
+
const answer = (await promptUser("Trust this MPP merchant? [y/N] ")).trim().toLowerCase();
|
|
21784
|
+
return answer === "y" || answer === "yes";
|
|
21785
|
+
}
|
|
21786
|
+
function printResult(result) {
|
|
21787
|
+
if (result.paymentOutcome === "confirmed") {
|
|
21788
|
+
printOut(`\u2713 Paid: $${(result.paidUsd ?? 0).toFixed(6)} via MPP+B402`);
|
|
21789
|
+
} else if (result.paymentOutcome === "unknown") {
|
|
21790
|
+
printErr(
|
|
21791
|
+
"error: payment credential was dispatched but no successful B402 receipt was returned; outcome unknown. Do not retry blindly."
|
|
21792
|
+
);
|
|
21793
|
+
} else {
|
|
21794
|
+
printOut(`\u2713 No payment required (HTTP ${result.statusCode}).`);
|
|
21795
|
+
}
|
|
21796
|
+
printOut(` URL: ${result.url}`);
|
|
21797
|
+
printOut(` Status: ${result.statusCode}`);
|
|
21798
|
+
if (result.receipt?.reference) {
|
|
21799
|
+
printOut(` Reference: ${result.receipt.reference}`);
|
|
21800
|
+
}
|
|
21801
|
+
const body = result.json ? JSON.stringify(result.json, null, 2) : result.content.toString("utf8");
|
|
21802
|
+
printOut(
|
|
21803
|
+
` Response: ${body.slice(0, 200).replaceAll("\n", "\n ")}`
|
|
21804
|
+
);
|
|
21805
|
+
}
|
|
21806
|
+
function transactionReference(value) {
|
|
21807
|
+
return value && /^0x[0-9a-fA-F]{64}$/.test(value) ? value : null;
|
|
21808
|
+
}
|
|
21809
|
+
function decimalText(value, decimals) {
|
|
21810
|
+
return value.toFixed(decimals).replace(/(?:\.0+|(?:(\.\d*?)0+))$/, "$1");
|
|
21811
|
+
}
|
|
21812
|
+
function jsonReplacer(_key, value) {
|
|
21813
|
+
return Buffer.isBuffer(value) ? value.toString("utf8") : value;
|
|
21814
|
+
}
|
|
21815
|
+
|
|
21816
|
+
// src/cli/platform.ts
|
|
21817
|
+
import * as path44 from "path";
|
|
20597
21818
|
import {
|
|
20598
21819
|
STUDIO_TOML as STUDIO_TOML2,
|
|
20599
21820
|
findSubProjectRoot as findSubProjectRoot18,
|
|
20600
|
-
loadStudioToml as
|
|
21821
|
+
loadStudioToml as loadStudioToml24
|
|
20601
21822
|
} from "@bnbagent/studio-runtime/config";
|
|
20602
21823
|
var ACCOUNT_SLUG = "studio-platform";
|
|
20603
21824
|
var PlatformUsageError = class extends Error {
|
|
@@ -20796,10 +22017,10 @@ function localPlatformCfg() {
|
|
|
20796
22017
|
if (agentRoot2 === null) {
|
|
20797
22018
|
return [null, {}];
|
|
20798
22019
|
}
|
|
20799
|
-
const tomlPath =
|
|
22020
|
+
const tomlPath = path44.join(agentRoot2, STUDIO_TOML2);
|
|
20800
22021
|
let cfg;
|
|
20801
22022
|
try {
|
|
20802
|
-
cfg =
|
|
22023
|
+
cfg = loadStudioToml24(tomlPath);
|
|
20803
22024
|
} catch {
|
|
20804
22025
|
return [null, {}];
|
|
20805
22026
|
}
|
|
@@ -20830,14 +22051,14 @@ function findAgentId(data) {
|
|
|
20830
22051
|
tables.push(nested);
|
|
20831
22052
|
}
|
|
20832
22053
|
}
|
|
20833
|
-
for (const
|
|
22054
|
+
for (const table2 of tables) {
|
|
20834
22055
|
for (const key of ["agent_id", "agentId"]) {
|
|
20835
|
-
const value =
|
|
22056
|
+
const value = table2[key];
|
|
20836
22057
|
if (typeof value === "string" && value) {
|
|
20837
22058
|
return value;
|
|
20838
22059
|
}
|
|
20839
22060
|
}
|
|
20840
|
-
const scope =
|
|
22061
|
+
const scope = table2.scope;
|
|
20841
22062
|
if (typeof scope === "string" && scope.includes("invoke:")) {
|
|
20842
22063
|
const id = scope.split("invoke:").pop() ?? "";
|
|
20843
22064
|
if (id) {
|
|
@@ -20869,8 +22090,8 @@ function formatRemaining(seconds) {
|
|
|
20869
22090
|
}
|
|
20870
22091
|
|
|
20871
22092
|
// src/cli/recipe.ts
|
|
20872
|
-
import * as
|
|
20873
|
-
import * as
|
|
22093
|
+
import * as fs43 from "fs";
|
|
22094
|
+
import * as path45 from "path";
|
|
20874
22095
|
import { parse as parse6 } from "smol-toml";
|
|
20875
22096
|
function registerRecipe(program) {
|
|
20876
22097
|
const p = program.command("recipe").description("Print recipe code/files (agent / wallet / 8004).");
|
|
@@ -21000,7 +22221,7 @@ function resolvePkgContext(explicit) {
|
|
|
21000
22221
|
let data;
|
|
21001
22222
|
try {
|
|
21002
22223
|
data = parse6(
|
|
21003
|
-
|
|
22224
|
+
fs43.readFileSync(path45.join(projectRoot, "studio.toml"), "utf-8")
|
|
21004
22225
|
);
|
|
21005
22226
|
} catch {
|
|
21006
22227
|
return [null, null];
|
|
@@ -21018,8 +22239,8 @@ function resolvePkgContext(explicit) {
|
|
|
21018
22239
|
}
|
|
21019
22240
|
|
|
21020
22241
|
// src/cli/scan.ts
|
|
21021
|
-
import * as
|
|
21022
|
-
import * as
|
|
22242
|
+
import * as fs44 from "fs";
|
|
22243
|
+
import * as path46 from "path";
|
|
21023
22244
|
import { Option as Option8 } from "commander";
|
|
21024
22245
|
import { parse as parse7 } from "smol-toml";
|
|
21025
22246
|
function registerScan(program) {
|
|
@@ -21036,7 +22257,7 @@ function registerScan(program) {
|
|
|
21036
22257
|
"note: `--json` is deprecated (JSON is the default output); ignoring."
|
|
21037
22258
|
);
|
|
21038
22259
|
}
|
|
21039
|
-
const root =
|
|
22260
|
+
const root = path46.resolve(opts.path ?? process.cwd());
|
|
21040
22261
|
const report = scanProject(root);
|
|
21041
22262
|
if (opts.text) {
|
|
21042
22263
|
process.stdout.write(formatText(report));
|
|
@@ -21050,24 +22271,24 @@ function registerScan(program) {
|
|
|
21050
22271
|
}
|
|
21051
22272
|
function isFile19(p) {
|
|
21052
22273
|
try {
|
|
21053
|
-
return
|
|
22274
|
+
return fs44.statSync(p).isFile();
|
|
21054
22275
|
} catch {
|
|
21055
22276
|
return false;
|
|
21056
22277
|
}
|
|
21057
22278
|
}
|
|
21058
22279
|
function isDir10(p) {
|
|
21059
22280
|
try {
|
|
21060
|
-
return
|
|
22281
|
+
return fs44.statSync(p).isDirectory();
|
|
21061
22282
|
} catch {
|
|
21062
22283
|
return false;
|
|
21063
22284
|
}
|
|
21064
22285
|
}
|
|
21065
22286
|
function layerBase(root) {
|
|
21066
|
-
return isDir10(
|
|
22287
|
+
return isDir10(path46.join(root, "app", "agent")) ? path46.join(root, "app") : root;
|
|
21067
22288
|
}
|
|
21068
22289
|
function safeReadJson(p) {
|
|
21069
22290
|
try {
|
|
21070
|
-
const data = JSON.parse(
|
|
22291
|
+
const data = JSON.parse(fs44.readFileSync(p, "utf-8"));
|
|
21071
22292
|
return data !== null && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
21072
22293
|
} catch {
|
|
21073
22294
|
return null;
|
|
@@ -21075,7 +22296,7 @@ function safeReadJson(p) {
|
|
|
21075
22296
|
}
|
|
21076
22297
|
function safeReadToml(p) {
|
|
21077
22298
|
try {
|
|
21078
|
-
return parse7(
|
|
22299
|
+
return parse7(fs44.readFileSync(p, "utf-8"));
|
|
21079
22300
|
} catch {
|
|
21080
22301
|
return null;
|
|
21081
22302
|
}
|
|
@@ -21105,33 +22326,33 @@ function resolvePackageName(projectName2, root) {
|
|
|
21105
22326
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(candidate)) {
|
|
21106
22327
|
return null;
|
|
21107
22328
|
}
|
|
21108
|
-
if (!isDir10(
|
|
22329
|
+
if (!isDir10(path46.join(root, candidate))) {
|
|
21109
22330
|
return null;
|
|
21110
22331
|
}
|
|
21111
22332
|
return candidate;
|
|
21112
22333
|
}
|
|
21113
22334
|
function inferLayout(root) {
|
|
21114
|
-
const agentDir =
|
|
21115
|
-
const hasAgent = isFile19(
|
|
22335
|
+
const agentDir = path46.join(layerBase(root), "agent");
|
|
22336
|
+
const hasAgent = isFile19(path46.join(agentDir, "main.ts")) || isFile19(path46.join(agentDir, "main.py"));
|
|
21116
22337
|
return {
|
|
21117
22338
|
has_agent: hasAgent,
|
|
21118
22339
|
has_agent_descriptor: isFile19(
|
|
21119
|
-
|
|
22340
|
+
path46.join(root, "agentcore", "agentcore.json")
|
|
21120
22341
|
),
|
|
21121
22342
|
role: hasAgent ? "agent" : "none"
|
|
21122
22343
|
};
|
|
21123
22344
|
}
|
|
21124
22345
|
function inferPackageManager(root, hasPackageJson) {
|
|
21125
|
-
if (isFile19(
|
|
22346
|
+
if (isFile19(path46.join(root, "pnpm-lock.yaml"))) {
|
|
21126
22347
|
return "pnpm";
|
|
21127
22348
|
}
|
|
21128
|
-
if (isFile19(
|
|
22349
|
+
if (isFile19(path46.join(root, "yarn.lock"))) {
|
|
21129
22350
|
return "yarn";
|
|
21130
22351
|
}
|
|
21131
|
-
if (isFile19(
|
|
22352
|
+
if (isFile19(path46.join(root, "bun.lockb"))) {
|
|
21132
22353
|
return "bun";
|
|
21133
22354
|
}
|
|
21134
|
-
if (isFile19(
|
|
22355
|
+
if (isFile19(path46.join(root, "package-lock.json"))) {
|
|
21135
22356
|
return "npm";
|
|
21136
22357
|
}
|
|
21137
22358
|
if (hasPackageJson) {
|
|
@@ -21144,7 +22365,7 @@ function inferRecipes(root, studioData, layout) {
|
|
|
21144
22365
|
if (layout.has_agent) {
|
|
21145
22366
|
recipes.push("agent");
|
|
21146
22367
|
}
|
|
21147
|
-
const walletsDir =
|
|
22368
|
+
const walletsDir = path46.join(root, ".studio", "wallets");
|
|
21148
22369
|
const walletSection = studioData?.wallet;
|
|
21149
22370
|
const hasWalletSection = walletSection !== null && typeof walletSection === "object" && !Array.isArray(walletSection);
|
|
21150
22371
|
if (isDir10(walletsDir) && hasWalletSection) {
|
|
@@ -21153,13 +22374,13 @@ function inferRecipes(root, studioData, layout) {
|
|
|
21153
22374
|
return recipes;
|
|
21154
22375
|
}
|
|
21155
22376
|
function scanProject(root) {
|
|
21156
|
-
const studioToml =
|
|
21157
|
-
const packageJson =
|
|
21158
|
-
const agentcoreJson =
|
|
22377
|
+
const studioToml = path46.join(root, "studio.toml");
|
|
22378
|
+
const packageJson = path46.join(root, "package.json");
|
|
22379
|
+
const agentcoreJson = path46.join(root, "agentcore", "agentcore.json");
|
|
21159
22380
|
const hasStudioToml = isFile19(studioToml);
|
|
21160
22381
|
const hasPackageJson = isFile19(packageJson);
|
|
21161
22382
|
const hasAgentcoreJson = isFile19(agentcoreJson);
|
|
21162
|
-
const hasDotStudioDir = isDir10(
|
|
22383
|
+
const hasDotStudioDir = isDir10(path46.join(root, ".studio"));
|
|
21163
22384
|
const pkgData = hasPackageJson ? safeReadJson(packageJson) : null;
|
|
21164
22385
|
const studioData = hasStudioToml ? safeReadToml(studioToml) : null;
|
|
21165
22386
|
const rawName = pkgData?.name;
|
|
@@ -21216,23 +22437,23 @@ function formatText(report) {
|
|
|
21216
22437
|
}
|
|
21217
22438
|
|
|
21218
22439
|
// src/cli/x402.ts
|
|
21219
|
-
import * as
|
|
21220
|
-
import * as
|
|
22440
|
+
import * as fs45 from "fs";
|
|
22441
|
+
import * as path47 from "path";
|
|
21221
22442
|
import { auditedOp as auditedOp4 } from "@bnbagent/studio-runtime/audit";
|
|
21222
22443
|
import {
|
|
21223
22444
|
envLocalPath as envLocalPath19,
|
|
21224
|
-
findProjectRoot as
|
|
21225
|
-
loadStudioToml as
|
|
22445
|
+
findProjectRoot as findProjectRoot7,
|
|
22446
|
+
loadStudioToml as loadStudioToml25
|
|
21226
22447
|
} from "@bnbagent/studio-runtime/config";
|
|
21227
|
-
import { fromRaw as
|
|
21228
|
-
import * as
|
|
22448
|
+
import { fromRaw as fromRaw6, getNetwork as getNetwork12 } from "@bnbagent/studio-runtime/networks";
|
|
22449
|
+
import * as walletRt9 from "@bnbagent/studio-runtime/wallet";
|
|
21229
22450
|
import {
|
|
21230
|
-
SELLER_TOKENS,
|
|
22451
|
+
SELLER_TOKENS as SELLER_TOKENS2,
|
|
21231
22452
|
X402BuyerPolicy,
|
|
21232
22453
|
X402SellerPolicy,
|
|
21233
22454
|
_parseAccepted,
|
|
21234
22455
|
_probeB402Supported,
|
|
21235
|
-
caip2,
|
|
22456
|
+
caip2 as caip22,
|
|
21236
22457
|
fetchWithPayment,
|
|
21237
22458
|
quoteUrl
|
|
21238
22459
|
} from "@bnbagent/studio-runtime/x402";
|
|
@@ -21305,7 +22526,7 @@ function registerX402(program) {
|
|
|
21305
22526
|
"DEV ONLY: skip the host allowlist for a localhost/127.0.0.1 target."
|
|
21306
22527
|
).action(
|
|
21307
22528
|
act(
|
|
21308
|
-
(url, opts) =>
|
|
22529
|
+
(url, opts) => cmdQuote2(url, opts)
|
|
21309
22530
|
)
|
|
21310
22531
|
);
|
|
21311
22532
|
p.command("buy").description("Pay an x402-protected URL with $U via EIP-3009.").argument("<url>", "Absolute URL of the x402-protected resource.").requiredOption(
|
|
@@ -21323,7 +22544,7 @@ function registerX402(program) {
|
|
|
21323
22544
|
"DEV ONLY: for a localhost/127.0.0.1 target, skip the host allowlist and accept the challenge's payTo as the recipient (printed loudly)."
|
|
21324
22545
|
).action(
|
|
21325
22546
|
act(
|
|
21326
|
-
(url, opts) =>
|
|
22547
|
+
(url, opts) => cmdBuy3(url, opts)
|
|
21327
22548
|
)
|
|
21328
22549
|
);
|
|
21329
22550
|
p.command("trust").description(
|
|
@@ -21349,7 +22570,7 @@ function registerX402(program) {
|
|
|
21349
22570
|
"Skip the interactive confirmation (required in non-interactive shells)."
|
|
21350
22571
|
).action(
|
|
21351
22572
|
act(
|
|
21352
|
-
(merchant, opts) =>
|
|
22573
|
+
(merchant, opts) => cmdTrust2(merchant, opts)
|
|
21353
22574
|
)
|
|
21354
22575
|
);
|
|
21355
22576
|
const sell = p.command("sell").description("Configure and inspect the B402-backed x402 seller rail.");
|
|
@@ -21365,7 +22586,7 @@ function registerX402(program) {
|
|
|
21365
22586
|
).action(act((opts) => cmdSellInit(opts.priceUsd)));
|
|
21366
22587
|
sell.command("status").description("Show x402 seller config and B402 capability status.").option("--no-probe", "Skip the authenticated B402 /supported probe.").action(act((opts) => cmdSellStatus(opts.probe)));
|
|
21367
22588
|
}
|
|
21368
|
-
function
|
|
22589
|
+
function hostOf2(url) {
|
|
21369
22590
|
try {
|
|
21370
22591
|
return (new URL(url).hostname || "").toLowerCase();
|
|
21371
22592
|
} catch {
|
|
@@ -21390,13 +22611,13 @@ function printLocalDevHint(url) {
|
|
|
21390
22611
|
);
|
|
21391
22612
|
}
|
|
21392
22613
|
function checkHostAllowed(url, allowedHosts) {
|
|
21393
|
-
const host =
|
|
22614
|
+
const host = hostOf2(url);
|
|
21394
22615
|
const allowedLower = new Set(
|
|
21395
22616
|
allowedHosts.map((h) => String(h).toLowerCase())
|
|
21396
22617
|
);
|
|
21397
22618
|
return allowedLower.has(host);
|
|
21398
22619
|
}
|
|
21399
|
-
function
|
|
22620
|
+
function tableOf17(cfg, key) {
|
|
21400
22621
|
const v = cfg[key];
|
|
21401
22622
|
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
21402
22623
|
}
|
|
@@ -21404,14 +22625,14 @@ function effectiveAllowedHosts(cfg) {
|
|
|
21404
22625
|
return [...X402BuyerPolicy.fromToml(cfg).effectiveAllowedHosts];
|
|
21405
22626
|
}
|
|
21406
22627
|
function loadX402Policy() {
|
|
21407
|
-
const root =
|
|
22628
|
+
const root = findProjectRoot7();
|
|
21408
22629
|
if (root === null) {
|
|
21409
22630
|
printErr("error: no studio.toml found in cwd or any parent.");
|
|
21410
22631
|
return null;
|
|
21411
22632
|
}
|
|
21412
22633
|
let cfg;
|
|
21413
22634
|
try {
|
|
21414
|
-
cfg =
|
|
22635
|
+
cfg = loadStudioToml25(path47.join(root, "studio.toml"));
|
|
21415
22636
|
} catch (exc) {
|
|
21416
22637
|
printErr(`error: failed to parse studio.toml: ${errMsg6(exc)}`);
|
|
21417
22638
|
return null;
|
|
@@ -21419,23 +22640,23 @@ function loadX402Policy() {
|
|
|
21419
22640
|
return [root, cfg, effectiveAllowedHosts(cfg)];
|
|
21420
22641
|
}
|
|
21421
22642
|
function loadSellerConfig() {
|
|
21422
|
-
const root =
|
|
22643
|
+
const root = findProjectRoot7();
|
|
21423
22644
|
if (root === null) {
|
|
21424
22645
|
printErr("error: no studio.toml found in cwd or any parent.");
|
|
21425
22646
|
return null;
|
|
21426
22647
|
}
|
|
21427
22648
|
try {
|
|
21428
|
-
return [root,
|
|
22649
|
+
return [root, loadStudioToml25(path47.join(root, "studio.toml"))];
|
|
21429
22650
|
} catch (exc) {
|
|
21430
22651
|
printErr(`error: failed to parse studio.toml: ${errMsg6(exc)}`);
|
|
21431
22652
|
return null;
|
|
21432
22653
|
}
|
|
21433
22654
|
}
|
|
21434
|
-
function
|
|
22655
|
+
function resolveNetwork4(cfg, override) {
|
|
21435
22656
|
if (override) {
|
|
21436
22657
|
return override;
|
|
21437
22658
|
}
|
|
21438
|
-
const net2 =
|
|
22659
|
+
const net2 = tableOf17(cfg, "network").default;
|
|
21439
22660
|
return net2 ? String(net2) : "bsc-mainnet";
|
|
21440
22661
|
}
|
|
21441
22662
|
function parseJsonBody(raw) {
|
|
@@ -21455,7 +22676,7 @@ function parseJsonBody(raw) {
|
|
|
21455
22676
|
}
|
|
21456
22677
|
function resolveWallet2() {
|
|
21457
22678
|
try {
|
|
21458
|
-
return
|
|
22679
|
+
return walletRt9.getWallet();
|
|
21459
22680
|
} catch (exc) {
|
|
21460
22681
|
printErr(`error: wallet unavailable: ${errMsg6(exc)}`);
|
|
21461
22682
|
return null;
|
|
@@ -21471,9 +22692,13 @@ async function cmdSellInit(priceFlag) {
|
|
|
21471
22692
|
const loaded = loadSellerConfig();
|
|
21472
22693
|
if (loaded === null) return 1;
|
|
21473
22694
|
const [root, cfg] = loaded;
|
|
21474
|
-
const tomlPath =
|
|
21475
|
-
const original =
|
|
21476
|
-
const
|
|
22695
|
+
const tomlPath = path47.join(root, "studio.toml");
|
|
22696
|
+
const original = fs45.readFileSync(tomlPath, "utf8");
|
|
22697
|
+
const canonicalExists = hasSection(original, "payments.b402_seller");
|
|
22698
|
+
const legacyExists = hasSection(original, "payments.x402_seller");
|
|
22699
|
+
const sellerExists = canonicalExists || legacyExists;
|
|
22700
|
+
const sellerSection = legacyExists ? "payments.x402_seller" : "payments.b402_seller";
|
|
22701
|
+
const paymentProtocol = b402PaymentProtocol(cfg);
|
|
21477
22702
|
let priceUsd;
|
|
21478
22703
|
try {
|
|
21479
22704
|
priceUsd = priceFlag !== void 0 ? normalizeB402PriceUsd(priceFlag) : sellerExists ? X402SellerPolicy.fromToml(cfg).priceUsd : await resolveSellInitPrice(stdinIsTty());
|
|
@@ -21482,7 +22707,7 @@ async function cmdSellInit(priceFlag) {
|
|
|
21482
22707
|
return 2;
|
|
21483
22708
|
}
|
|
21484
22709
|
const free = x402SellerPricingState({ price_usd: priceUsd }).kind === "free";
|
|
21485
|
-
const walletKind2 = String(
|
|
22710
|
+
const walletKind2 = String(tableOf17(cfg, "wallet").kind ?? "evm-local");
|
|
21486
22711
|
if (!free && !["evm-local", "twak", "turnkey", "altana"].includes(walletKind2)) {
|
|
21487
22712
|
printErr(
|
|
21488
22713
|
`error: the b402 seller rail supports wallet.kind evm-local, twak, turnkey or altana only; '${walletKind2}' cannot act as the b402 payout wallet.`
|
|
@@ -21490,9 +22715,9 @@ async function cmdSellInit(priceFlag) {
|
|
|
21490
22715
|
return 1;
|
|
21491
22716
|
}
|
|
21492
22717
|
if (!sellerExists) {
|
|
21493
|
-
|
|
22718
|
+
fs45.writeFileSync(
|
|
21494
22719
|
tomlPath,
|
|
21495
|
-
updateSection(original,
|
|
22720
|
+
updateSection(original, sellerSection, {
|
|
21496
22721
|
enabled: true,
|
|
21497
22722
|
price_usd: priceUsd,
|
|
21498
22723
|
assets: ["U"],
|
|
@@ -21502,9 +22727,9 @@ async function cmdSellInit(priceFlag) {
|
|
|
21502
22727
|
"utf8"
|
|
21503
22728
|
);
|
|
21504
22729
|
} else if (priceFlag !== void 0) {
|
|
21505
|
-
|
|
22730
|
+
fs45.writeFileSync(
|
|
21506
22731
|
tomlPath,
|
|
21507
|
-
updateSection(original,
|
|
22732
|
+
updateSection(original, sellerSection, {
|
|
21508
22733
|
price_usd: priceUsd
|
|
21509
22734
|
}),
|
|
21510
22735
|
"utf8"
|
|
@@ -21513,21 +22738,24 @@ async function cmdSellInit(priceFlag) {
|
|
|
21513
22738
|
const envPath = envLocalPath19(root);
|
|
21514
22739
|
for (const key of B402_ENV_KEYS) {
|
|
21515
22740
|
const existing = getEnvVar(envPath, key);
|
|
21516
|
-
if (
|
|
21517
|
-
setEnvVar(envPath, key, DEFAULT_B402_TESTNET_BASE_URL);
|
|
21518
|
-
} else if (existing === null) {
|
|
22741
|
+
if (existing === null) {
|
|
21519
22742
|
setEnvVar(envPath, key, "");
|
|
21520
22743
|
}
|
|
21521
22744
|
}
|
|
22745
|
+
if (paymentProtocol === "mpp") {
|
|
22746
|
+
for (const key of ["MPP_SECRET_KEY", "MPP_REALM"]) {
|
|
22747
|
+
if (getEnvVar(envPath, key) === null) setEnvVar(envPath, key, "");
|
|
22748
|
+
}
|
|
22749
|
+
}
|
|
21522
22750
|
printOut(
|
|
21523
|
-
free ?
|
|
22751
|
+
free ? `\u2713 ${paymentProtocol.toUpperCase()} seller config is ready in FREE mode; B402 credentials are not required.` : `\u2713 ${paymentProtocol.toUpperCase()} seller config and credential placeholders are ready for PAID mode.`
|
|
21524
22752
|
);
|
|
21525
22753
|
if (!free) {
|
|
21526
22754
|
printOut(`Next: ${B402_PAID_ONBOARDING_GUIDANCE}`);
|
|
21527
22755
|
}
|
|
21528
22756
|
if (free) {
|
|
21529
22757
|
printErr(
|
|
21530
|
-
|
|
22758
|
+
`warning: /${paymentProtocol} is an anonymous FREE endpoint; B402 verify/settle and payment audit are bypassed.`
|
|
21531
22759
|
);
|
|
21532
22760
|
}
|
|
21533
22761
|
return 0;
|
|
@@ -21552,7 +22780,7 @@ async function resolveSellInitPrice(isTty) {
|
|
|
21552
22780
|
}
|
|
21553
22781
|
return DEFAULT_B402_PRICE_USD;
|
|
21554
22782
|
}
|
|
21555
|
-
async function cmdSellStatus(
|
|
22783
|
+
async function cmdSellStatus(probe2) {
|
|
21556
22784
|
const loaded = loadSellerConfig();
|
|
21557
22785
|
if (loaded === null) return 1;
|
|
21558
22786
|
const [, cfg] = loaded;
|
|
@@ -21560,7 +22788,7 @@ async function cmdSellStatus(probe) {
|
|
|
21560
22788
|
try {
|
|
21561
22789
|
policy = X402SellerPolicy.fromToml(cfg);
|
|
21562
22790
|
} catch (exc) {
|
|
21563
|
-
printErr(`error: invalid
|
|
22791
|
+
printErr(`error: invalid B402 seller config: ${errMsg6(exc)}`);
|
|
21564
22792
|
return 1;
|
|
21565
22793
|
}
|
|
21566
22794
|
const presence = {
|
|
@@ -21590,7 +22818,7 @@ async function cmdSellStatus(probe) {
|
|
|
21590
22818
|
if (policy.enabled && !free && !credentialsComplete) {
|
|
21591
22819
|
printOut(`Onboarding: ${B402_PAID_ONBOARDING_GUIDANCE}`);
|
|
21592
22820
|
}
|
|
21593
|
-
if (!
|
|
22821
|
+
if (!probe2) {
|
|
21594
22822
|
printOut("B402 probe: skipped (--no-probe)");
|
|
21595
22823
|
return 0;
|
|
21596
22824
|
}
|
|
@@ -21602,9 +22830,9 @@ async function cmdSellStatus(probe) {
|
|
|
21602
22830
|
printOut("B402 probe: skipped (rail disabled or credentials incomplete)");
|
|
21603
22831
|
return 0;
|
|
21604
22832
|
}
|
|
21605
|
-
const networkName =
|
|
21606
|
-
const network =
|
|
21607
|
-
const token =
|
|
22833
|
+
const networkName = resolveNetwork4(cfg);
|
|
22834
|
+
const network = caip22(getNetwork12(networkName).chainId);
|
|
22835
|
+
const token = SELLER_TOKENS2[network];
|
|
21608
22836
|
if (!token) {
|
|
21609
22837
|
printOut(`B402 probe: mismatch for ${network}; no pinned U token`);
|
|
21610
22838
|
return 0;
|
|
@@ -21634,7 +22862,7 @@ async function cmdSellStatus(probe) {
|
|
|
21634
22862
|
}
|
|
21635
22863
|
return 0;
|
|
21636
22864
|
}
|
|
21637
|
-
async function
|
|
22865
|
+
async function cmdQuote2(url, opts) {
|
|
21638
22866
|
const loaded = loadX402Policy();
|
|
21639
22867
|
if (loaded === null) {
|
|
21640
22868
|
return 1;
|
|
@@ -21647,12 +22875,12 @@ async function cmdQuote(url, opts) {
|
|
|
21647
22875
|
printErr(`error: ${errMsg6(exc)}`);
|
|
21648
22876
|
return 2;
|
|
21649
22877
|
}
|
|
21650
|
-
if (opts.localDev && !isLoopbackHost(
|
|
22878
|
+
if (opts.localDev && !isLoopbackHost(hostOf2(url))) {
|
|
21651
22879
|
printErr("error: --local-dev applies to localhost/127.0.0.1 targets only.");
|
|
21652
22880
|
return 2;
|
|
21653
22881
|
}
|
|
21654
22882
|
if (!opts.localDev && !checkHostAllowed(url, hosts)) {
|
|
21655
|
-
printScopeError(
|
|
22883
|
+
printScopeError(hostOf2(url) || url, url);
|
|
21656
22884
|
return 1;
|
|
21657
22885
|
}
|
|
21658
22886
|
let body;
|
|
@@ -21688,18 +22916,18 @@ async function cmdQuote(url, opts) {
|
|
|
21688
22916
|
}
|
|
21689
22917
|
return 0;
|
|
21690
22918
|
}
|
|
21691
|
-
async function
|
|
22919
|
+
async function cmdBuy3(url, opts) {
|
|
21692
22920
|
const loaded = loadX402Policy();
|
|
21693
22921
|
if (loaded === null) {
|
|
21694
22922
|
return 1;
|
|
21695
22923
|
}
|
|
21696
22924
|
const [, cfg, hosts] = loaded;
|
|
21697
|
-
if (opts.localDev && !isLoopbackHost(
|
|
22925
|
+
if (opts.localDev && !isLoopbackHost(hostOf2(url))) {
|
|
21698
22926
|
printErr("error: --local-dev applies to localhost/127.0.0.1 targets only.");
|
|
21699
22927
|
return 2;
|
|
21700
22928
|
}
|
|
21701
22929
|
if (!opts.localDev && !checkHostAllowed(url, hosts)) {
|
|
21702
|
-
printScopeError(
|
|
22930
|
+
printScopeError(hostOf2(url) || url, url);
|
|
21703
22931
|
return 1;
|
|
21704
22932
|
}
|
|
21705
22933
|
let jsonBody;
|
|
@@ -21709,7 +22937,7 @@ async function cmdBuy2(url, opts) {
|
|
|
21709
22937
|
printErr(`error: ${errMsg6(exc)}`);
|
|
21710
22938
|
return 2;
|
|
21711
22939
|
}
|
|
21712
|
-
const networkName =
|
|
22940
|
+
const networkName = resolveNetwork4(cfg, opts.network);
|
|
21713
22941
|
let localDevExpectedTo = null;
|
|
21714
22942
|
if (opts.localDev) {
|
|
21715
22943
|
let challenge;
|
|
@@ -21754,7 +22982,7 @@ async function cmdBuy2(url, opts) {
|
|
|
21754
22982
|
);
|
|
21755
22983
|
return 1;
|
|
21756
22984
|
}
|
|
21757
|
-
const buyWalletKind = String(
|
|
22985
|
+
const buyWalletKind = String(tableOf17(cfg, "wallet").kind ?? "evm-local");
|
|
21758
22986
|
if (buyWalletKind === "altana") {
|
|
21759
22987
|
let allowanceRaw;
|
|
21760
22988
|
try {
|
|
@@ -21768,9 +22996,9 @@ async function cmdBuy2(url, opts) {
|
|
|
21768
22996
|
return 1;
|
|
21769
22997
|
}
|
|
21770
22998
|
if (allowanceRaw !== null && allowanceRaw < neededRaw) {
|
|
21771
|
-
const uToken =
|
|
22999
|
+
const uToken = getNetwork12(networkName).token("U");
|
|
21772
23000
|
printErr(
|
|
21773
|
-
`error: U\u2192Permit2 allowance ${
|
|
23001
|
+
`error: U\u2192Permit2 allowance ${fromRaw6(allowanceRaw, uToken.decimals)} U cannot cover max_usd $${opts.maxUsd.toFixed(2)}: b402 settles Altana payments by pulling through Permit2 (${PERMIT2_ADDRESS}).`
|
|
21774
23002
|
);
|
|
21775
23003
|
printErr(
|
|
21776
23004
|
"fix: bag wallet session x402-setup --allowance-u <amount> (admin key; approves the x402 signature checker and sets the bounded Permit2 allowance)"
|
|
@@ -21783,7 +23011,7 @@ async function cmdBuy2(url, opts) {
|
|
|
21783
23011
|
result = await auditedOp4(
|
|
21784
23012
|
"x402_buy",
|
|
21785
23013
|
{
|
|
21786
|
-
chainId:
|
|
23014
|
+
chainId: getNetwork12(networkName).chainId,
|
|
21787
23015
|
asset: "U",
|
|
21788
23016
|
amount: String(opts.maxUsd),
|
|
21789
23017
|
agentAddr: wallet.address,
|
|
@@ -21810,7 +23038,7 @@ async function cmdBuy2(url, opts) {
|
|
|
21810
23038
|
);
|
|
21811
23039
|
} catch (exc) {
|
|
21812
23040
|
printX402Error(exc);
|
|
21813
|
-
if (errName4(exc) === "X402RecipientRequiredError" && isLoopbackHost(
|
|
23041
|
+
if (errName4(exc) === "X402RecipientRequiredError" && isLoopbackHost(hostOf2(url))) {
|
|
21814
23042
|
printLocalDevHint(url);
|
|
21815
23043
|
}
|
|
21816
23044
|
return 1;
|
|
@@ -21880,7 +23108,7 @@ async function probeChallenge(url, methodHint) {
|
|
|
21880
23108
|
return null;
|
|
21881
23109
|
}
|
|
21882
23110
|
function selectPayable(challenge, networkName) {
|
|
21883
|
-
const network =
|
|
23111
|
+
const network = getNetwork12(networkName);
|
|
21884
23112
|
const uToken = network.token("U");
|
|
21885
23113
|
let matched;
|
|
21886
23114
|
try {
|
|
@@ -21910,21 +23138,26 @@ async function confirmOrAbort(prompt, assumeYes) {
|
|
|
21910
23138
|
const answer = (await promptUser(`${prompt} [y/N] `)).trim().toLowerCase();
|
|
21911
23139
|
return answer === "y" || answer === "yes";
|
|
21912
23140
|
}
|
|
21913
|
-
function writeMerchantEntry(tomlPath, fields) {
|
|
21914
|
-
const text2 =
|
|
23141
|
+
function writeMerchantEntry(tomlPath, cfg, fields) {
|
|
23142
|
+
const text2 = fs45.readFileSync(tomlPath, "utf-8");
|
|
21915
23143
|
const entries = {
|
|
21916
23144
|
domain: fields.domain,
|
|
21917
23145
|
pay_to: fields.payTo,
|
|
21918
23146
|
per_call_cap_usd: fields.capUsd,
|
|
21919
23147
|
verified: fields.verified
|
|
21920
23148
|
};
|
|
21921
|
-
|
|
23149
|
+
const withMerchant = updateSection(
|
|
23150
|
+
text2,
|
|
23151
|
+
`payments.x402.merchants.${fields.key}`,
|
|
23152
|
+
entries
|
|
23153
|
+
);
|
|
23154
|
+
fs45.writeFileSync(
|
|
21922
23155
|
tomlPath,
|
|
21923
|
-
|
|
23156
|
+
trustEip712Domain(withMerchant, cfg, fields.network, fields.asset),
|
|
21924
23157
|
"utf-8"
|
|
21925
23158
|
);
|
|
21926
23159
|
}
|
|
21927
|
-
async function
|
|
23160
|
+
async function cmdTrust2(merchant, opts) {
|
|
21928
23161
|
const loaded = loadX402Policy();
|
|
21929
23162
|
if (loaded === null) {
|
|
21930
23163
|
return 1;
|
|
@@ -21936,7 +23169,7 @@ async function cmdTrust(merchant, opts) {
|
|
|
21936
23169
|
let methodHint;
|
|
21937
23170
|
if (merchant.includes("://")) {
|
|
21938
23171
|
probeUrl = merchant;
|
|
21939
|
-
host =
|
|
23172
|
+
host = hostOf2(probeUrl);
|
|
21940
23173
|
const plainHttp = probeUrl.toLowerCase().startsWith("http://");
|
|
21941
23174
|
const localDevHttp = plainHttp && isLoopbackHost(host);
|
|
21942
23175
|
if (!probeUrl.toLowerCase().startsWith("https://") && !localDevHttp || !host) {
|
|
@@ -21964,7 +23197,7 @@ async function cmdTrust(merchant, opts) {
|
|
|
21964
23197
|
host = rec.domain;
|
|
21965
23198
|
methodHint = rec.probeMethod;
|
|
21966
23199
|
}
|
|
21967
|
-
const networkName = opts.network || (rec ? rec.network :
|
|
23200
|
+
const networkName = opts.network || (rec ? rec.network : resolveNetwork4(cfg, null));
|
|
21968
23201
|
const probed = await probeChallenge(probeUrl, methodHint);
|
|
21969
23202
|
if (probed === null) {
|
|
21970
23203
|
return 1;
|
|
@@ -21977,7 +23210,7 @@ async function cmdTrust(merchant, opts) {
|
|
|
21977
23210
|
const [accepted, uToken] = selected2;
|
|
21978
23211
|
const livePayTo = String(accepted.payTo);
|
|
21979
23212
|
const priceUsd = Number.parseFloat(
|
|
21980
|
-
|
|
23213
|
+
fromRaw6(BigInt(String(accepted.amount)), uToken.decimals)
|
|
21981
23214
|
);
|
|
21982
23215
|
let pinned;
|
|
21983
23216
|
if (opts.payTo) {
|
|
@@ -22029,7 +23262,7 @@ The merchant may have rotated its address (or the response was tampered with). U
|
|
|
22029
23262
|
" \u26A0 MAINNET: calls spend real $U from the agent wallet. The cap above and [budget].max_per_day_usd bound the exposure."
|
|
22030
23263
|
);
|
|
22031
23264
|
}
|
|
22032
|
-
const projectDefault =
|
|
23265
|
+
const projectDefault = tableOf17(cfg, "network").default;
|
|
22033
23266
|
if (projectDefault && String(projectDefault) !== networkName) {
|
|
22034
23267
|
printOut(
|
|
22035
23268
|
` note: your project default is ${projectDefault}; this paid capability settles on ${networkName} independently of it.`
|
|
@@ -22047,9 +23280,11 @@ The merchant may have rotated its address (or the response was tampered with). U
|
|
|
22047
23280
|
printOut("aborted \u2014 nothing written.");
|
|
22048
23281
|
return 1;
|
|
22049
23282
|
}
|
|
22050
|
-
const tomlPath =
|
|
23283
|
+
const tomlPath = path47.join(root, "studio.toml");
|
|
22051
23284
|
try {
|
|
22052
|
-
writeMerchantEntry(tomlPath, {
|
|
23285
|
+
writeMerchantEntry(tomlPath, cfg, {
|
|
23286
|
+
network: String(accepted.network),
|
|
23287
|
+
asset: String(accepted.asset),
|
|
22053
23288
|
key,
|
|
22054
23289
|
domain: host,
|
|
22055
23290
|
payTo: pinned,
|
|
@@ -22103,6 +23338,7 @@ function buildProgram() {
|
|
|
22103
23338
|
registerErc8004(program);
|
|
22104
23339
|
registerErc8183(program);
|
|
22105
23340
|
registerX402(program);
|
|
23341
|
+
registerMpp(program);
|
|
22106
23342
|
registerLlm(program);
|
|
22107
23343
|
registerDoctor(program);
|
|
22108
23344
|
registerPlatform(program);
|
|
@@ -22114,19 +23350,19 @@ function cliVersion() {
|
|
|
22114
23350
|
}
|
|
22115
23351
|
|
|
22116
23352
|
// src/cli/updateCheck.ts
|
|
22117
|
-
import * as
|
|
23353
|
+
import * as fs46 from "fs";
|
|
22118
23354
|
import * as os7 from "os";
|
|
22119
|
-
import * as
|
|
23355
|
+
import * as path48 from "path";
|
|
22120
23356
|
var PACKAGE = "@bnbagent/studio-cli";
|
|
22121
23357
|
var REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE}/latest`;
|
|
22122
23358
|
var CHECK_INTERVAL_SECONDS = 24 * 3600;
|
|
22123
23359
|
var TIMEOUT_MS = 2e3;
|
|
22124
23360
|
function cachePath(home) {
|
|
22125
|
-
return
|
|
23361
|
+
return path48.join(home, ".bag", "update-check.json");
|
|
22126
23362
|
}
|
|
22127
23363
|
function loadCache(home) {
|
|
22128
23364
|
try {
|
|
22129
|
-
const data = JSON.parse(
|
|
23365
|
+
const data = JSON.parse(fs46.readFileSync(cachePath(home), "utf-8"));
|
|
22130
23366
|
return data !== null && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
22131
23367
|
} catch {
|
|
22132
23368
|
return {};
|
|
@@ -22135,8 +23371,8 @@ function loadCache(home) {
|
|
|
22135
23371
|
function saveCache(state, home) {
|
|
22136
23372
|
try {
|
|
22137
23373
|
const p = cachePath(home);
|
|
22138
|
-
|
|
22139
|
-
|
|
23374
|
+
fs46.mkdirSync(path48.dirname(p), { recursive: true });
|
|
23375
|
+
fs46.writeFileSync(p, `${JSON.stringify(state, null, 2)}
|
|
22140
23376
|
`);
|
|
22141
23377
|
} catch {
|
|
22142
23378
|
}
|