@bnbagent/studio-cli 0.0.6-alpha.1
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/DISCLAIMER.md +48 -0
- package/LICENSE +201 -0
- package/dist/_agentcoreName-DZDWEYD3.js +7 -0
- package/dist/_twak-5XQMOFUC.js +25 -0
- package/dist/bag.js +19358 -0
- package/dist/chunk-7RAKL4AS.js +172 -0
- package/dist/chunk-M3ODFCA7.js +1053 -0
- package/dist/chunk-U7IDQ3K5.js +14 -0
- package/dist/deployCli-N6TPN6XA.js +40 -0
- package/package.json +64 -0
- package/recipes/agent/code/{{PKG}}/signing.ts.tmpl +287 -0
- package/recipes/agent/recipe.toml +35 -0
- package/recipes/providers/pieverse-llm/recipe.toml +16 -0
- package/recipes/providers/pieverse-llm/skills/funding-pieverse-llm.md +203 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/.dockerignore.tmpl +8 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/Dockerfile.tmpl +50 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/agentCard.ts.tmpl +135 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/dualMain.ts.tmpl +402 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/executor.ts.tmpl +147 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/main.ts.tmpl +344 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/mcpMain.ts.tmpl +677 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/model.ts.tmpl +117 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/sellerCore.ts.tmpl +503 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/tools.ts.tmpl +157 -0
- package/recipes/runtimes/agentcore/recipe.toml +97 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/.dockerignore.tmpl +8 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/Dockerfile.tmpl +47 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/agentCard.ts.tmpl +131 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/executor.ts.tmpl +504 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/foundryMain.ts.tmpl +300 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/main.ts.tmpl +196 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +562 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/model.ts.tmpl +117 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/tools.ts.tmpl +157 -0
- package/recipes/runtimes/azure-foundry/recipe.toml +88 -0
- package/recipes/tools-chain/code/{{PKG}}/chainTools.ts.tmpl +166 -0
- package/recipes/tools-chain/recipe.toml +11 -0
- package/recipes/wallet/recipe.toml +20 -0
- package/recipes/x402-buyer/code/{{PKG}}/x402Buyer.ts.tmpl +175 -0
- package/recipes/x402-buyer/recipe.toml +15 -0
- package/skills/bnbagent-studio.md +107 -0
- package/skills/references/bnbagent-studio-adding-to-project.md +241 -0
- package/skills/references/bnbagent-studio-buying-from-bazaar.md +169 -0
- package/skills/references/bnbagent-studio-buying-via-8183.md +222 -0
- package/skills/references/bnbagent-studio-extending-signing.md +227 -0
- package/skills/references/bnbagent-studio-operating.md +211 -0
- package/skills/references/bnbagent-studio-scaffolding-agent.md +536 -0
- package/skills/references/bnbagent-studio-selling-via-8183.md +271 -0
- package/skills/references/bnbagent-studio-selling-via-b402.md +194 -0
- package/skills/references/bnbagent-studio-use-aws-agentcore.md +208 -0
- package/skills/references/bnbagent-studio-use-azure-foundry.md +164 -0
- package/skills/references/bnbagent-studio-use-bnb-trial.md +92 -0
- package/skills/references/bnbagent-studio-using-altana-wallet.md +68 -0
- package/skills/references/bnbagent-studio-using-twak-wallet.md +260 -0
- package/skills/references/bnbagent-studio-wiring-llm-tools.md +338 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/_agentcoreName.ts
|
|
4
|
+
var AGENTCORE_NAME_RE = /^[A-Za-z][A-Za-z0-9]{0,22}$/;
|
|
5
|
+
function validateAgentcoreName(name) {
|
|
6
|
+
if (AGENTCORE_NAME_RE.test(name)) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return `agentcore.json name '${name}' is invalid: the agentcore CLI requires a name that starts with a letter, contains only ASCII letters/digits (no '_', '-', '.'), and is at most 23 characters. The agentcore CLI's own error for this can be misleading ('No agentcore project found') \u2014 fix the 'name' field in agentcore/agentcore.json.`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
validateAgentcoreName
|
|
14
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BNB_PLATFORM_API_URL,
|
|
4
|
+
BNB_PLATFORM_API_URL_ENV,
|
|
5
|
+
DEPLOY_CLI_PACKAGE,
|
|
6
|
+
DEPLOY_CLI_VERSION,
|
|
7
|
+
LEGACY_BNB_PLATFORM_API_URL_ENV,
|
|
8
|
+
bnbEnv,
|
|
9
|
+
bnbPlatformApiUrl,
|
|
10
|
+
buildDeploySpec,
|
|
11
|
+
deployCommand,
|
|
12
|
+
deployEndpointValue,
|
|
13
|
+
providerPassthrough,
|
|
14
|
+
renderSecretEnvFile,
|
|
15
|
+
runDeployCliJson,
|
|
16
|
+
runDeployCliStream,
|
|
17
|
+
runPlatformAccountCommand,
|
|
18
|
+
trialFromDeployCliJson,
|
|
19
|
+
withDeployFiles
|
|
20
|
+
} from "./chunk-M3ODFCA7.js";
|
|
21
|
+
import "./chunk-7RAKL4AS.js";
|
|
22
|
+
export {
|
|
23
|
+
BNB_PLATFORM_API_URL,
|
|
24
|
+
BNB_PLATFORM_API_URL_ENV,
|
|
25
|
+
DEPLOY_CLI_PACKAGE,
|
|
26
|
+
DEPLOY_CLI_VERSION,
|
|
27
|
+
LEGACY_BNB_PLATFORM_API_URL_ENV,
|
|
28
|
+
bnbEnv,
|
|
29
|
+
bnbPlatformApiUrl,
|
|
30
|
+
buildDeploySpec,
|
|
31
|
+
deployCommand,
|
|
32
|
+
deployEndpointValue,
|
|
33
|
+
providerPassthrough,
|
|
34
|
+
renderSecretEnvFile,
|
|
35
|
+
runDeployCliJson,
|
|
36
|
+
runDeployCliStream,
|
|
37
|
+
runPlatformAccountCommand,
|
|
38
|
+
trialFromDeployCliJson,
|
|
39
|
+
withDeployFiles
|
|
40
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bnbagent/studio-cli",
|
|
3
|
+
"version": "0.0.6-alpha.1",
|
|
4
|
+
"description": "The `bag` CLI: scaffold, run, deploy, and monetize a single seller agent on BNB Chain (ERC-8004 identity, ERC-8183 commerce, x402 payments).",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/bnb-chain/bnbagent-studio.git",
|
|
9
|
+
"directory": "packages/studio-cli"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=22"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"skills",
|
|
21
|
+
"recipes",
|
|
22
|
+
"DISCLAIMER.md"
|
|
23
|
+
],
|
|
24
|
+
"bin": {
|
|
25
|
+
"bag": "./dist/bag.js"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@bnbagent/sdk": "0.5.0-alpha.1",
|
|
29
|
+
"ai": "^7.0.29",
|
|
30
|
+
"archiver": "^8.0.0",
|
|
31
|
+
"commander": "^15.0.0",
|
|
32
|
+
"esbuild": "^0.28.0",
|
|
33
|
+
"execa": "^9.5.0",
|
|
34
|
+
"picocolors": "^1.1.0",
|
|
35
|
+
"proper-lockfile": "^4.1.2",
|
|
36
|
+
"smol-toml": "^1.3.0",
|
|
37
|
+
"tar": "^7.4.0",
|
|
38
|
+
"viem": "^2.54.0",
|
|
39
|
+
"yaml": "^2.9.0",
|
|
40
|
+
"@bnbagent/studio-runtime": "0.0.6-alpha.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@a2a-js/sdk": "^0.3.14",
|
|
44
|
+
"@ai-sdk/openai": "^4.0.15",
|
|
45
|
+
"@aws-sdk/client-secrets-manager": "^3.600.0",
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
|
+
"@types/express": "^5.0.6",
|
|
48
|
+
"@types/node": "^22.0.0",
|
|
49
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
50
|
+
"express": "^5.2.1",
|
|
51
|
+
"tsup": "^8.0.0",
|
|
52
|
+
"tsx": "^4.19.0",
|
|
53
|
+
"typescript": "^5.5.0",
|
|
54
|
+
"vitest": "^2.0.0",
|
|
55
|
+
"zod": "^3.25.76"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"lint": "biome check src tests",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic signing — the Agent is the SOLE key-holder/signer.
|
|
3
|
+
*
|
|
4
|
+
* Every on-chain WRITE the Agent performs lives here as FIXED code:
|
|
5
|
+
*
|
|
6
|
+
* signQuote(...) EIP-191 sign the (clamped) negotiated offer
|
|
7
|
+
* submitResult(...) build manifest → upload → on-chain `submit`
|
|
8
|
+
* settle(...) claim payment after the dispute window
|
|
9
|
+
*
|
|
10
|
+
* These functions are NEVER registered as LLM-callable tools (`tools.ts` holds
|
|
11
|
+
* only read-only tools). The price is a FIXED list price from studio.toml
|
|
12
|
+
* (`listPrice()`, clamped by the entrypoint BEFORE it reaches here) — the LLM
|
|
13
|
+
* only produces the work text and never moves money or sets a price.
|
|
14
|
+
*
|
|
15
|
+
* The key is loaded by `@bnbagent/studio-runtime/wallet` `getWallet()` (local
|
|
16
|
+
* keystore, unlocked by `WALLET_PASSWORD`). It is injected into the AgentCore
|
|
17
|
+
* runtime via the secret store, never bundled into the code package.
|
|
18
|
+
*
|
|
19
|
+
* You own this file — edit the pricing clamp source / manifest shape if your
|
|
20
|
+
* domain needs it, but keep these ops OUT of the LLM tool list.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
JobDescription,
|
|
25
|
+
NegotiationHandler,
|
|
26
|
+
type NegotiationResult,
|
|
27
|
+
type QuoteSigner,
|
|
28
|
+
} from "@bnbagent/sdk/erc8183";
|
|
29
|
+
import { getAddress as deployedAddresses } from "@bnbagent/sdk/networks";
|
|
30
|
+
import {
|
|
31
|
+
loadStudioToml,
|
|
32
|
+
type TomlTable,
|
|
33
|
+
} from "@bnbagent/studio-runtime/config";
|
|
34
|
+
import {
|
|
35
|
+
get8183Client,
|
|
36
|
+
settleWorkflow,
|
|
37
|
+
type SubmitResult,
|
|
38
|
+
submitWorkflow,
|
|
39
|
+
type Verdict,
|
|
40
|
+
verifySignedJob as verifySignedJobCore,
|
|
41
|
+
} from "@bnbagent/studio-runtime/erc8183";
|
|
42
|
+
import { getNetwork } from "@bnbagent/studio-runtime/networks";
|
|
43
|
+
import { getWallet } from "@bnbagent/studio-runtime/wallet";
|
|
44
|
+
|
|
45
|
+
const MAX_UINT256 = (1n << 256n) - 1n;
|
|
46
|
+
|
|
47
|
+
// ── test seams (mirror the studio-runtime `_set*` convention) ────────────────
|
|
48
|
+
type StudioTomlLoader = () => TomlTable;
|
|
49
|
+
const defaultTomlLoader: StudioTomlLoader = () => loadStudioToml();
|
|
50
|
+
let tomlLoader: StudioTomlLoader = defaultTomlLoader;
|
|
51
|
+
|
|
52
|
+
/** Test seam: replace the studio.toml loader. Pass null to restore. */
|
|
53
|
+
export function _setStudioTomlLoader(loader: StudioTomlLoader | null): void {
|
|
54
|
+
tomlLoader = loader ?? defaultTomlLoader;
|
|
55
|
+
handler = null; // config feeds the cached handler — rebuild it
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The narrow NegotiationHandler surface signQuote drives (test-fakeable). */
|
|
59
|
+
export interface NegotiationHandlerLike {
|
|
60
|
+
negotiate(
|
|
61
|
+
request: Record<string, unknown>,
|
|
62
|
+
opts?: { price?: string; estimatedCompletionSeconds?: number },
|
|
63
|
+
): Promise<NegotiationResult> | NegotiationResult;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let handler: NegotiationHandlerLike | null = null;
|
|
67
|
+
|
|
68
|
+
type RuntimeWallet = ReturnType<typeof getWallet>;
|
|
69
|
+
type SessionQuoteWallet = RuntimeWallet & {
|
|
70
|
+
sessionQuoteSigner(): QuoteSigner;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Select the narrow quote-only authority when the wallet exposes one. */
|
|
74
|
+
export function negotiationSignerOptions(
|
|
75
|
+
wallet: RuntimeWallet,
|
|
76
|
+
): { quoteSigner: QuoteSigner } | { walletProvider: RuntimeWallet } {
|
|
77
|
+
const candidate = wallet as Partial<SessionQuoteWallet>;
|
|
78
|
+
return typeof candidate.sessionQuoteSigner === "function"
|
|
79
|
+
? { quoteSigner: candidate.sessionQuoteSigner.call(wallet) }
|
|
80
|
+
: { walletProvider: wallet };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Test seam: replace the cached NegotiationHandler. Pass null to restore. */
|
|
84
|
+
export function _setNegotiationHandler(h: NegotiationHandlerLike | null): void {
|
|
85
|
+
handler = h;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── config readers ────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/** Read `[payments.erc8183]` from studio.toml ({} when absent). */
|
|
91
|
+
function erc8183Cfg(): Record<string, unknown> {
|
|
92
|
+
let cfg: TomlTable;
|
|
93
|
+
try {
|
|
94
|
+
cfg = tomlLoader();
|
|
95
|
+
} catch {
|
|
96
|
+
// No studio.toml here; use the safe empty-config fallback.
|
|
97
|
+
cfg = {};
|
|
98
|
+
}
|
|
99
|
+
const payments = (cfg.payments ?? {}) as Record<string, unknown>;
|
|
100
|
+
return (payments.erc8183 ?? {}) as Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** studio.toml `[network].default` (best-effort; binds the quote signature). */
|
|
104
|
+
function defaultNetworkName(): string {
|
|
105
|
+
let cfg: TomlTable;
|
|
106
|
+
try {
|
|
107
|
+
cfg = tomlLoader();
|
|
108
|
+
} catch {
|
|
109
|
+
cfg = {};
|
|
110
|
+
}
|
|
111
|
+
return String(((cfg.network ?? {}) as TomlTable).default ?? "bsc-testnet");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Return `[minPrice, maxPrice]` in raw wei from studio.toml.
|
|
116
|
+
*
|
|
117
|
+
* These are the clamp bounds applied to the configured list price BEFORE
|
|
118
|
+
* signing. `min_price`/`max_price` are raw uint256 strings in
|
|
119
|
+
* `[payments.erc8183]`.
|
|
120
|
+
*/
|
|
121
|
+
export function priceBounds(): [bigint, bigint] {
|
|
122
|
+
const cfg = erc8183Cfg();
|
|
123
|
+
// TODO: if min/max are absent the bounds default to (0, +inf) — i.e. NO
|
|
124
|
+
// clamp. Set [payments.erc8183].min_price / max_price in studio.toml to
|
|
125
|
+
// enforce a real floor/ceiling (strongly recommended for production).
|
|
126
|
+
// The scaffold ships max_price = "" (an empty string, not absent), so treat
|
|
127
|
+
// empty/whitespace the same as missing → fall back to the default bound.
|
|
128
|
+
const raw = (key: string, dflt: bigint): bigint => {
|
|
129
|
+
const s = String(cfg[key] ?? "").trim();
|
|
130
|
+
return s ? BigInt(s) : dflt;
|
|
131
|
+
};
|
|
132
|
+
return [raw("min_price", 0n), raw("max_price", MAX_UINT256)];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Return the seller's list price in raw wei from studio.toml.
|
|
137
|
+
*
|
|
138
|
+
* Reads `[payments.erc8183].price` — the deterministic asking price every
|
|
139
|
+
* quote uses (rule-based pricing; no LLM in the quote path). Empty/absent → 0.
|
|
140
|
+
* Edit `price` in studio.toml to change what you charge. The value is still
|
|
141
|
+
* clamped to `[minPrice, maxPrice]` by {@link clampPrice} before signing.
|
|
142
|
+
*/
|
|
143
|
+
export function listPrice(): bigint {
|
|
144
|
+
const s = String(erc8183Cfg().price ?? "").trim();
|
|
145
|
+
return s ? BigInt(s) : 0n;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Clamp a proposed price into `[minPrice, maxPrice]`. */
|
|
149
|
+
export function clampPrice(proposedWei: bigint): bigint {
|
|
150
|
+
const [lo, hi] = priceBounds();
|
|
151
|
+
const capped = proposedWei < hi ? proposedWei : hi;
|
|
152
|
+
return capped > lo ? capped : lo;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Return the process-wide NegotiationHandler (lazy, cached).
|
|
157
|
+
*
|
|
158
|
+
* The handler's chainId + verifyingContract are stable per process, so we
|
|
159
|
+
* build it once. The per-request clamped price is passed via
|
|
160
|
+
* `negotiate(..., { price })` (see {@link signQuote}), so the
|
|
161
|
+
* construction-time `servicePrice` is a placeholder that is always
|
|
162
|
+
* overridden.
|
|
163
|
+
*
|
|
164
|
+
* chainId + verifyingContract bind provider_sig to this chain/contract
|
|
165
|
+
* (prevents cross-chain replay). The runtime client facade does not expose
|
|
166
|
+
* them, so
|
|
167
|
+
* they come from the SDK's on-chain address registry for the configured
|
|
168
|
+
* network — the same addresses the live client is constructed from.
|
|
169
|
+
*/
|
|
170
|
+
function getHandler(): NegotiationHandlerLike {
|
|
171
|
+
if (handler === null) {
|
|
172
|
+
const cfg = erc8183Cfg();
|
|
173
|
+
const currency = String(cfg.currency ?? ""); // the Agent owns the currency now
|
|
174
|
+
const ttl = Number(cfg.quote_ttl_seconds ?? 900);
|
|
175
|
+
const est = Number(cfg.default_estimated_completion_seconds ?? 600);
|
|
176
|
+
const network = getNetwork(defaultNetworkName());
|
|
177
|
+
const wallet = getWallet();
|
|
178
|
+
handler = new NegotiationHandler({
|
|
179
|
+
servicePrice: "0", // placeholder — overridden per call via price=
|
|
180
|
+
currency,
|
|
181
|
+
estimatedCompletionSeconds: est,
|
|
182
|
+
...negotiationSignerOptions(wallet),
|
|
183
|
+
quoteTtlSeconds: ttl,
|
|
184
|
+
chainId: network.chainId,
|
|
185
|
+
verifyingContract: deployedAddresses(network.chainId).commerceProxy,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return handler;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Negotiate + EIP-191-sign a quote at `clampedPriceWei`; return the SDK
|
|
193
|
+
* envelope.
|
|
194
|
+
*
|
|
195
|
+
* Reuses a process-wide NegotiationHandler (cached — its chainId +
|
|
196
|
+
* verifyingContract are stable per process) and overrides the price for this
|
|
197
|
+
* request via `negotiate(..., { price: String(clampedPriceWei) })`.
|
|
198
|
+
*
|
|
199
|
+
* Returns the SDK's `NegotiationResult.toDict()` envelope **verbatim** — the
|
|
200
|
+
* exact wire structure a buyer parses and feeds to `buildJobDescription` to
|
|
201
|
+
* anchor on-chain (see docs/design/erc8183-reference.md §2). On accept it
|
|
202
|
+
* carries `response.terms.price`/`currency`, `quote_expires_at`,
|
|
203
|
+
* `negotiation_hash`, `response_hash`, `provider_sig`, `chain_id`,
|
|
204
|
+
* `verifying_contract`; on reject it carries `response.reason_code` /
|
|
205
|
+
* `reason` (empty hash + sig). We do NOT invent a custom shape.
|
|
206
|
+
*/
|
|
207
|
+
export async function signQuote(
|
|
208
|
+
request: Record<string, unknown>,
|
|
209
|
+
clampedPriceWei: bigint,
|
|
210
|
+
): Promise<Record<string, unknown>> {
|
|
211
|
+
const cfg = erc8183Cfg();
|
|
212
|
+
const est = Number(cfg.default_estimated_completion_seconds ?? 600);
|
|
213
|
+
|
|
214
|
+
const result = await getHandler().negotiate(request, {
|
|
215
|
+
price: String(clampedPriceWei),
|
|
216
|
+
estimatedCompletionSeconds: est,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// NegotiationHandler signs non-fatally: if signMessage failed it returns an
|
|
220
|
+
// accepted result WITHOUT provider_sig. Never relay an unsigned "accepted".
|
|
221
|
+
if (result.accepted && (!result.negotiationHash || !result.providerSig)) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
"quote accepted but provider_sig is missing (wallet sign failed); " +
|
|
224
|
+
"refusing to relay an unsigned offer",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return result.toDict();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Verify funded `jobId` carries the quote THIS agent signed.
|
|
233
|
+
*
|
|
234
|
+
* Thin wrapper over `@bnbagent/studio-runtime/erc8183` `verifySignedJob` with
|
|
235
|
+
* `expectedSigner` = our own wallet address. Returns a `Verdict` `{ ok,
|
|
236
|
+
* reason, permanent }`: `ok` → safe to work; otherwise `permanent`
|
|
237
|
+
* distinguishes a job to skip-forever (record + tell the client) from a
|
|
238
|
+
* transient retry.
|
|
239
|
+
*/
|
|
240
|
+
export async function verifySignedJob(jobId: number): Promise<Verdict> {
|
|
241
|
+
return verifySignedJobCore(jobId, getWallet().address);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Return the on-chain `JobDescription` for `jobId` (`null` if unstructured).
|
|
246
|
+
*
|
|
247
|
+
* The task + terms the buyer ANCHORED ON-CHAIN — and that this agent's
|
|
248
|
+
* `provider_sig` covers — are the authoritative work spec. The work hook
|
|
249
|
+
* reads the task from HERE (the on-chain job description), so the Agent
|
|
250
|
+
* delivers exactly the deal it signed.
|
|
251
|
+
* Returns `null` for legacy/plain-text descriptions (caller falls back).
|
|
252
|
+
*/
|
|
253
|
+
export async function jobSpec(jobId: number): Promise<JobDescription | null> {
|
|
254
|
+
const client = await get8183Client();
|
|
255
|
+
const job = await client.getJob(BigInt(jobId));
|
|
256
|
+
return JobDescription.fromStr(job.description);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Sign + broadcast the on-chain `submit` for `jobId`.
|
|
261
|
+
*
|
|
262
|
+
* Delegates to `@bnbagent/studio-runtime/erc8183` `submitWorkflow`, which
|
|
263
|
+
* re-verifies the job is genuinely FUNDED + assigned to us (via the SDK's
|
|
264
|
+
* `ERC8183JobOps.verifyJob`), builds the `DeliverableManifest`, uploads it
|
|
265
|
+
* to storage, and calls on-chain `submit` — all `auditedOp`-wrapped.
|
|
266
|
+
* Returns the `SubmitResult` (`.submitTx` + `.deliverableUrl`);
|
|
267
|
+
* `deliverableUrl` is published on-chain by the submit, so the buyer fetches
|
|
268
|
+
* the canonical manifest from storage without an on-chain log scan.
|
|
269
|
+
*/
|
|
270
|
+
export async function submitResult(
|
|
271
|
+
jobId: number,
|
|
272
|
+
responseContent: string,
|
|
273
|
+
metadata?: Record<string, unknown> | null,
|
|
274
|
+
): Promise<SubmitResult> {
|
|
275
|
+
return submitWorkflow(jobId, responseContent, { metadata: metadata ?? null });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Sign + broadcast `settle` (claim payment) for `jobId`.
|
|
280
|
+
*
|
|
281
|
+
* Delegates to `@bnbagent/studio-runtime/erc8183` `settleWorkflow` with the
|
|
282
|
+
* default `approve` action → SDK `router.settle(jobId)`, `auditedOp`-wrapped.
|
|
283
|
+
* Returns the settle tx hash.
|
|
284
|
+
*/
|
|
285
|
+
export async function settle(jobId: number): Promise<string> {
|
|
286
|
+
return settleWorkflow(jobId, { action: "approve" });
|
|
287
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[recipe]
|
|
2
|
+
name = "agent"
|
|
3
|
+
description = "Agent — protocol-neutral signing entrypoints (signQuote / submitResult / settle). The Agent is the SOLE key-holder/signer; this recipe emits the fixed signing.ts glue that the LLM never calls. Runtime choice (AgentCore / Azure Foundry) lives in `runtimes/<runtime>/` (which also carries the chain tools + model factory — decision D9: no separate frameworks recipe in the TS line)."
|
|
4
|
+
status = "signing-only" # only signing.ts here; runtime/provider code lives in their own recipes
|
|
5
|
+
|
|
6
|
+
[dependencies]
|
|
7
|
+
node = [
|
|
8
|
+
# signing.ts imports only from @bnbagent/studio-runtime (erc8183 + wallet +
|
|
9
|
+
# config + networks subpaths) and the @bnbagent/sdk protocol layer
|
|
10
|
+
# (NegotiationHandler / JobDescription) — protocol-level glue, framework-
|
|
11
|
+
# neutral. The agent project's serving deps (@a2a-js/sdk / MCP SDK / ai)
|
|
12
|
+
# come from the runtimes/<R>/ recipe selected at `bag init` time.
|
|
13
|
+
"@bnbagent/studio-runtime",
|
|
14
|
+
"@bnbagent/sdk@0.5.0-alpha.1",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[env]
|
|
18
|
+
# WALLET_PASSWORD unlocks the local keystore. The agent is the SOLE signer:
|
|
19
|
+
# it uses the key for quote-sign + submit + settle (signing.ts, fixed code,
|
|
20
|
+
# NOT LLM tools) AND automatic budget-gated Pieverse LLM-credit auto-renew.
|
|
21
|
+
# The keystore is injected via the AgentCore secret store, NEVER bundled.
|
|
22
|
+
WALLET_PASSWORD = "wallet keystore password (the agent is the sole on-chain signer)"
|
|
23
|
+
PIEVERSE_LLM_API_KEY = "set by `bag llm activate` for the default Pieverse provider"
|
|
24
|
+
# OPENROUTER_API_KEY = "set if [llm].provider=openrouter (legacy)"
|
|
25
|
+
# STORAGE_API_URL / STORAGE_API_KEY: IPFS upload endpoint + write key — the
|
|
26
|
+
# Agent pins deliverables at submit ([storage].kind=ipfs). Any pinning service
|
|
27
|
+
# works (use its upload/pin endpoint + key/JWT), as does a self-hosted node's
|
|
28
|
+
# /api/v0/add (usually keyless). Agent-only secrets — inject into the AgentCore
|
|
29
|
+
# secret store at deploy, like WALLET_PASSWORD.
|
|
30
|
+
STORAGE_API_URL = "IPFS upload endpoint (required when [storage].kind=ipfs)"
|
|
31
|
+
STORAGE_API_KEY = "pinning-service write key/JWT (hosted services; self-hosted nodes usually need none)"
|
|
32
|
+
# RPC_URL: dedicated BSC RPC for the deployed Agent (SDK var). Set it to avoid
|
|
33
|
+
# the rate-limited public BSC RPC default; when set it is pushed with the
|
|
34
|
+
# runtime secrets on deploy. The STUDIO_BSC_RPC* vars are CLI-side only.
|
|
35
|
+
# RPC_URL = "https://your-dedicated-bsc-rpc"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[recipe]
|
|
2
|
+
name = "providers/pieverse-llm"
|
|
3
|
+
description = "Provider-specific assets for Pieverse LLM: emits `funding-pieverse-llm` skill into the project's `.claude/skills/` so Claude Code knows how to handle Pieverse credit / topup / paid-model decisions."
|
|
4
|
+
status = "v0.0.1"
|
|
5
|
+
|
|
6
|
+
# This is a "provider" recipe — non-code: it emits documentation/skill
|
|
7
|
+
# artifacts that travel with the chosen LLM provider. There is no `code/`
|
|
8
|
+
# directory; the init flow's provider-skill emitter reads `skills/*.md`
|
|
9
|
+
# and writes each to `<project>/.claude/skills/<stem>/SKILL.md`.
|
|
10
|
+
#
|
|
11
|
+
# Future on-chain providers (e.g. Bittensor, Aether) can follow the same
|
|
12
|
+
# layout: drop `skills/<name>.md` files here under their own provider dir
|
|
13
|
+
# and they'll be emitted at `bag init --llm-provider <name>` time.
|
|
14
|
+
|
|
15
|
+
[skills]
|
|
16
|
+
emit = ["funding-pieverse-llm"]
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: funding-pieverse-llm
|
|
3
|
+
description: When the user is dealing with Pieverse LLM funding — switching to a paid model, hitting InsufficientCreditsError / PieverseAccountBalanceExhaustedError, topping up the wallet for paid LLM usage, or asking "how do I pay for the LLM" / "why is my agent stuck on LLM". Covers the zero-deposit default + the paid-upgrade path.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# funding-pieverse-llm
|
|
7
|
+
|
|
8
|
+
Procedure for **Pieverse LLM funding decisions** in a bnbagent-studio project.
|
|
9
|
+
Audience: Claude Code helping a user who has already run `bag init` + `bag llm activate`.
|
|
10
|
+
|
|
11
|
+
**Different from**: `-operating` (general ops) — this skill is specifically for the
|
|
12
|
+
Pieverse credit lifecycle, paid-model upgrade, and topup troubleshooting.
|
|
13
|
+
|
|
14
|
+
## Mental model (zero-deposit default)
|
|
15
|
+
|
|
16
|
+
bnbagent-studio v0.0.x defaults to **zero-deposit Pieverse activation**:
|
|
17
|
+
|
|
18
|
+
- `bag init` + `bag llm activate` together create a Pieverse account + API key
|
|
19
|
+
with **$0 initial allocation**. No wallet funding required.
|
|
20
|
+
- Default model is **`auto/free`** (Pieverse's $0/token tier). The agent runs
|
|
21
|
+
unmetered against it.
|
|
22
|
+
- The user **only needs to fund their wallet** when they want to:
|
|
23
|
+
1. Switch to a **paid model** (e.g. `auto/paid`, `claude-haiku-4.5`,
|
|
24
|
+
`anthropic/claude-sonnet-4.6`, etc.)
|
|
25
|
+
2. Run `bag erc8004 register` (needs **testnet tBNB** for gas)
|
|
26
|
+
3. `bag deploy` to AgentCore
|
|
27
|
+
|
|
28
|
+
**Three independent balances**, on the same EOA address but different chains/assets:
|
|
29
|
+
|
|
30
|
+
| Balance | Chain | Purpose | When needed |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| testnet tBNB | BSC testnet (97) | gas for ERC-8004 / ERC-8183 / deploy txs | register / deploy |
|
|
33
|
+
| testnet U | BSC testnet (97) | ERC-8183 job settlement | when this agent buys/sells via 8183 |
|
|
34
|
+
| mainnet U | BSC mainnet (56) | Pieverse paid LLM credits | switching to paid LLM model |
|
|
35
|
+
|
|
36
|
+
Use `bag wallet balance --all` to see all three.
|
|
37
|
+
|
|
38
|
+
## Quick triage decision tree
|
|
39
|
+
|
|
40
|
+
| User said... | Run first |
|
|
41
|
+
|---|---|
|
|
42
|
+
| "how do I switch to claude/opus/sonnet/paid model" | Section A below |
|
|
43
|
+
| `InsufficientCreditsError` during a negotiate or job delivery | Section B below |
|
|
44
|
+
| `PieverseAccountBalanceExhaustedError` | Section B below |
|
|
45
|
+
| `PieverseColdStartTopupBlockedError` | Section C below |
|
|
46
|
+
| "I want auto-renew without being asked" / "turn auto-renew on/off" | Section D below (`bag llm auto-renew status`) |
|
|
47
|
+
| "how much U / credit do I have" | `bag llm usage` (no password) + `bag llm status` (key credit) + `bag wallet balance --all` |
|
|
48
|
+
| "why is gas failing" | testnet tBNB ≠ mainnet U; check `bag wallet balance --all` |
|
|
49
|
+
|
|
50
|
+
## A. Switch from `auto/free` to a paid model
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 1. fund the wallet (mainnet U)
|
|
54
|
+
# The wallet address is the same EOA that signs ERC-8004 txs; on mainnet
|
|
55
|
+
# only the U balance matters here.
|
|
56
|
+
bag wallet show
|
|
57
|
+
# Then either:
|
|
58
|
+
# - swap on PancakeSwap → https://pancakeswap.finance/swap
|
|
59
|
+
# ?outputCurrency=0xcE24439F2D9C6a2289F741120FE202248B666666
|
|
60
|
+
# - transfer U from another wallet to the address above
|
|
61
|
+
bag wallet balance --all # confirm mainnet U > 0
|
|
62
|
+
|
|
63
|
+
# 2. allocate paid credits into the API key
|
|
64
|
+
bag llm topup --amount 1 # start small ($1 covers many calls on auto/paid)
|
|
65
|
+
|
|
66
|
+
# 3. change the default model in studio.toml
|
|
67
|
+
bag config set llm.model auto/paid # or anthropic/claude-haiku-4.5 etc.
|
|
68
|
+
|
|
69
|
+
# 4. verify
|
|
70
|
+
bag llm status # should show non-zero key_credit
|
|
71
|
+
bag llm test # one-shot smoke check
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Do NOT** silently fallback to `auto/free` when a paid call fails — see the
|
|
75
|
+
zero-deposit hard-fail rule. The user explicitly chose a paid model; surface the failure.
|
|
76
|
+
|
|
77
|
+
## B. InsufficientCreditsError / PieverseAccountBalanceExhaustedError
|
|
78
|
+
|
|
79
|
+
The agent's API key ran out of credit (or never had any). Recovery:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Diagnose
|
|
83
|
+
bag llm status # key_credit (per-key balance); Account Balance has no API endpoint
|
|
84
|
+
bag llm usage --days 7 # see what was spent
|
|
85
|
+
|
|
86
|
+
# If account balance > 0 but key balance = 0: allocate from account → key
|
|
87
|
+
bag llm allocate --amount 1
|
|
88
|
+
|
|
89
|
+
# If account balance = 0: topup from mainnet U first
|
|
90
|
+
bag wallet balance --all # check mainnet U
|
|
91
|
+
bag llm topup --amount 1 # spends mainnet U via x402
|
|
92
|
+
bag llm allocate --amount 1 # then allocate
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Never edit `studio.toml [llm].model = auto/free` to "fix" a paid model's
|
|
96
|
+
funding error — that's silently changing agent behavior. Either fund or
|
|
97
|
+
explicitly downgrade, and tell the user which you did.
|
|
98
|
+
|
|
99
|
+
## C. PieverseColdStartTopupBlockedError
|
|
100
|
+
|
|
101
|
+
The auto-topup hook refuses to fund during the first 60 seconds after process
|
|
102
|
+
start. This is a **safety feature** (闸门 5) — it prevents boot-loop
|
|
103
|
+
scenarios from burning the user's monthly budget.
|
|
104
|
+
|
|
105
|
+
Recovery:
|
|
106
|
+
- Wait 60s, then retry the LLM call (auto-topup will engage).
|
|
107
|
+
- Or topup manually: `bag llm topup --amount N` (CLI is not subject to the
|
|
108
|
+
cold-start gate; only the in-process auto-topup hook is).
|
|
109
|
+
- **Never** advise the user to set `PIEVERSE_BUDGET_COLD_START_SECONDS=0` to
|
|
110
|
+
work around this — that env var is for CI/e2e only. If the user is hitting
|
|
111
|
+
this repeatedly, it indicates the agent is restarting too often; investigate
|
|
112
|
+
why.
|
|
113
|
+
|
|
114
|
+
## D. Auto-renew: two independent tiers + their switches
|
|
115
|
+
|
|
116
|
+
Auto-renew is a **two-tier ladder**, each with its own on/off switch. Inspect
|
|
117
|
+
both at once:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
bag llm auto-renew status # prints the llm tier AND the wallet tier
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Tier 1 — `llm` (allocate from Pieverse Account Balance; no wallet spend).**
|
|
124
|
+
Default **on**. Before each LLM call the agent tops the API key up from credit
|
|
125
|
+
already parked in the Account Balance when it dips below `min_balance_usd`.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
bag llm auto-renew llm off # disable: agent runs as a plain model, no credit hook
|
|
129
|
+
bag llm auto-renew llm on # re-enable (writes [llm.auto_renew].enabled)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Tier 2 — `wallet` (spend wallet U via x402 to refill the Account Balance).**
|
|
133
|
+
Default **off** (opt-in; the agent will not autonomously spend the wallet until
|
|
134
|
+
this is on). Turning it on records a consent stamp — required before any
|
|
135
|
+
autonomous spend.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
bag llm auto-renew wallet on # opt in (= bag budget enable; records acknowledged_at)
|
|
139
|
+
bag llm auto-renew wallet off # opt out (= bag budget disable)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`bag llm auto-renew wallet on` is exactly `bag budget enable` and accepts the
|
|
143
|
+
same cap overrides via that command:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
bag budget show # current state (default: disabled)
|
|
147
|
+
bag budget enable # default caps: $1/topup, $3/day, $15/month
|
|
148
|
+
# Or customize the caps:
|
|
149
|
+
bag budget enable \
|
|
150
|
+
--max-per-topup-usd 0.5 \
|
|
151
|
+
--max-per-day-usd 2 \
|
|
152
|
+
--max-per-month-usd 10
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Once enabled, the agent will:
|
|
156
|
+
- Wait at least 60s after process start before any topup (cold-start gate)
|
|
157
|
+
- Try to topup_x402 from mainnet U when the API key drops below
|
|
158
|
+
`min_balance_usd` (default $0.2)
|
|
159
|
+
- Stop and raise on cap-exceeded; not retry on failure (exponential backoff
|
|
160
|
+
applies — next LLM call may retry)
|
|
161
|
+
- Log every topup attempt to `~/.bnbagent-studio/<project>/logs/topup.jsonl`
|
|
162
|
+
and print a red-text notice to stdout
|
|
163
|
+
|
|
164
|
+
`WALLET_PASSWORD` env must be present in the runtime — without it the gate
|
|
165
|
+
falls back to allocate-only mode (no wallet spend, no error).
|
|
166
|
+
|
|
167
|
+
## E. Protocol-level details (for advanced cases)
|
|
168
|
+
|
|
169
|
+
When the user needs to understand the Pieverse protocol itself (raw SIWE,
|
|
170
|
+
x402 v2 envelope, deposit methods schema, signing typed data), point them
|
|
171
|
+
to Pieverse's official skill markdown — it is the authoritative reference:
|
|
172
|
+
|
|
173
|
+
- https://llm.pieverse.io/ai-gateway/skill.md (overview)
|
|
174
|
+
- https://llm.pieverse.io/ai-gateway/references/siwe-format.md (SIWE login)
|
|
175
|
+
- https://llm.pieverse.io/ai-gateway/references/x402-payment.md (topup mechanics)
|
|
176
|
+
- https://llm.pieverse.io/ai-gateway/references/api-reference.md (key/usage)
|
|
177
|
+
|
|
178
|
+
Do **not** re-implement what these documents specify. studio's `bag llm` /
|
|
179
|
+
`bag wallet` commands already wrap these flows.
|
|
180
|
+
|
|
181
|
+
## Read-only inspection (no password needed)
|
|
182
|
+
|
|
183
|
+
The CLI commands that only need the `sk-pv-...` API key (no wallet signature)
|
|
184
|
+
work without prompting for `WALLET_PASSWORD`:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
bag llm usage [--days 7] # per-key usage, no session
|
|
188
|
+
bag llm test --message "hi" # one-shot LLM smoke (uses key only)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Anything that needs the session_token (balance / deposits / key management)
|
|
192
|
+
will require `WALLET_PASSWORD` to be set in the environment or interactively.
|
|
193
|
+
|
|
194
|
+
## Hard rules
|
|
195
|
+
|
|
196
|
+
- Wallet private keys are never sent to Pieverse, never logged, and never put in
|
|
197
|
+
error messages. (Deploying to the managed platform is the one case a key leaves
|
|
198
|
+
your machine — it is sent to the operator to sign; self-deploy keeps it local.)
|
|
199
|
+
- `session_token` is memory-only per Pieverse spec — never persist to disk.
|
|
200
|
+
- When in doubt about a topup amount, suggest **smaller** values. Starting at
|
|
201
|
+
$0.5 – $1 is far better than $5+ for a user's first paid run.
|
|
202
|
+
- If the user has not opted into `bag budget enable`, never trigger an
|
|
203
|
+
auto-topup on their behalf — surface the error and let them decide.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Defense-in-depth: secrets and local state must never enter the image.
|
|
2
|
+
# (.env.local is also stashed out of the packaging window by `bag deploy`;
|
|
3
|
+
# wallet material reaches the runtime ONLY via Secrets Manager injection.)
|
|
4
|
+
node_modules/
|
|
5
|
+
dist/
|
|
6
|
+
.env.local
|
|
7
|
+
.studio/
|
|
8
|
+
*.tsbuildinfo
|