@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,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only chain tools exposed to this agent's LLM (AI SDK `tool` wrap).
|
|
3
|
+
*
|
|
4
|
+
* Each entry in `LLM_READ_TOOLS` is a function from
|
|
5
|
+
* `@bnbagent/studio-runtime/tools` wrapped as a Vercel AI SDK tool. The LLM
|
|
6
|
+
* may call any tool in this set while producing the deliverable (the
|
|
7
|
+
* `notify_funded` work step); the `description` is what the LLM sees.
|
|
8
|
+
*
|
|
9
|
+
* You own this file — edit `LLM_READ_TOOLS` to control exactly what your
|
|
10
|
+
* agent can read on-chain. Entries for features your project doesn't use are
|
|
11
|
+
* commented out by default; uncomment after you've added the dependency to
|
|
12
|
+
* `studio.toml`.
|
|
13
|
+
*
|
|
14
|
+
* **All tools are read-only** by the studio definition: no on-chain state
|
|
15
|
+
* change, no transferable authority, no transaction signing, no EIP-712
|
|
16
|
+
* typed-data signing. The agent IS the sole on-chain signer, but ALL of its
|
|
17
|
+
* signing — quote-sign, submitResult, settle, plus the automatic
|
|
18
|
+
* budget-gated Pieverse LLM-credit auto-renew inside `buildModel()` — lives
|
|
19
|
+
* in `signing.ts` / `model.ts` as FIXED entrypoint code and is NEVER a tool
|
|
20
|
+
* the LLM can invoke. The LLM only produces work text after a job is
|
|
21
|
+
* verified funded; it can never price, sign, spend, or mutate chain state.
|
|
22
|
+
* Keep this set read-only.
|
|
23
|
+
*
|
|
24
|
+
* (`pieverseUsage` is the one exception in the underlying module: it does a
|
|
25
|
+
* SIWE EIP-191 personal_sign, domain-locked to llm.pieverse.io, no on-chain
|
|
26
|
+
* effect. It is commented out below.)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import * as cr from "@bnbagent/studio-runtime/tools";
|
|
30
|
+
import { loadStudioToml } from "@bnbagent/studio-runtime/config";
|
|
31
|
+
import { tool, type ToolSet } from "ai";
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The project-wide default network (`[network].default`) — tool calls that
|
|
36
|
+
* omit `network` fall back to it, never to a hardcoded name.
|
|
37
|
+
*/
|
|
38
|
+
function defaultNetwork(): string {
|
|
39
|
+
try {
|
|
40
|
+
const cfg = loadStudioToml();
|
|
41
|
+
return String(
|
|
42
|
+
((cfg.network ?? {}) as Record<string, unknown>).default ?? "bsc-testnet",
|
|
43
|
+
);
|
|
44
|
+
} catch {
|
|
45
|
+
return "bsc-testnet";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const networkArg = z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("studio network name (defaults to the project's [network].default)");
|
|
53
|
+
|
|
54
|
+
export const LLM_READ_TOOLS: ToolSet = {
|
|
55
|
+
// --- Wallet & chain basics ---
|
|
56
|
+
wallet_info: tool({
|
|
57
|
+
description:
|
|
58
|
+
"Describe the agent's active wallet (address, kind, key location).",
|
|
59
|
+
inputSchema: z.object({}),
|
|
60
|
+
execute: async () => cr.walletInfo(),
|
|
61
|
+
}),
|
|
62
|
+
balance_native: tool({
|
|
63
|
+
description:
|
|
64
|
+
"Native BNB balance of an address (defaults to the agent's own wallet).",
|
|
65
|
+
inputSchema: z.object({
|
|
66
|
+
address: z.string().optional().describe("0x address; omit for own wallet"),
|
|
67
|
+
network: networkArg,
|
|
68
|
+
}),
|
|
69
|
+
execute: async ({ address, network }) =>
|
|
70
|
+
cr.balanceNative(address ?? null, network ?? defaultNetwork()),
|
|
71
|
+
}),
|
|
72
|
+
balance_u: tool({
|
|
73
|
+
// requires [u_token] in studio.toml
|
|
74
|
+
description:
|
|
75
|
+
"$U (payment token) balance of an address (defaults to the agent's own wallet).",
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
address: z.string().optional().describe("0x address; omit for own wallet"),
|
|
78
|
+
network: networkArg,
|
|
79
|
+
}),
|
|
80
|
+
execute: async ({ address, network }) =>
|
|
81
|
+
cr.balanceU(address ?? null, network ?? defaultNetwork()),
|
|
82
|
+
}),
|
|
83
|
+
network_info: tool({
|
|
84
|
+
description: "Chain id / RPC / token info for a studio network.",
|
|
85
|
+
inputSchema: z.object({ network: networkArg }),
|
|
86
|
+
execute: async ({ network }) => cr.networkInfo(network ?? defaultNetwork()),
|
|
87
|
+
}),
|
|
88
|
+
tx_status: tool({
|
|
89
|
+
description: "Status + receipt summary of a transaction hash.",
|
|
90
|
+
inputSchema: z.object({
|
|
91
|
+
tx_hash: z.string().describe("0x transaction hash"),
|
|
92
|
+
network: networkArg,
|
|
93
|
+
}),
|
|
94
|
+
execute: async ({ tx_hash, network }) =>
|
|
95
|
+
cr.txStatus(tx_hash, network ?? defaultNetwork()),
|
|
96
|
+
}),
|
|
97
|
+
|
|
98
|
+
// --- LLM provider ---
|
|
99
|
+
// pieverse_usage: tool({
|
|
100
|
+
// // SIWE personal_sign; requires [llm.provider=pieverse-llm]
|
|
101
|
+
// description: "Pieverse LLM usage/credit summary for the last N days.",
|
|
102
|
+
// inputSchema: z.object({ days: z.number().int().optional() }),
|
|
103
|
+
// execute: async ({ days }) => cr.pieverseUsage(days ?? 7),
|
|
104
|
+
// }),
|
|
105
|
+
|
|
106
|
+
// --- ERC-8004 identity (read-only lookups the LLM may want for context) ---
|
|
107
|
+
agent_info: tool({
|
|
108
|
+
// requires [erc8004] in studio.toml
|
|
109
|
+
description: "ERC-8004 identity record for an agent id.",
|
|
110
|
+
inputSchema: z.object({
|
|
111
|
+
agent_id: z.number().int().describe("ERC-8004 agent id"),
|
|
112
|
+
network: networkArg,
|
|
113
|
+
}),
|
|
114
|
+
execute: async ({ agent_id, network }) =>
|
|
115
|
+
cr.agentInfo(agent_id, network ?? defaultNetwork()),
|
|
116
|
+
}),
|
|
117
|
+
agent_by_address: tool({
|
|
118
|
+
// requires [erc8004] in studio.toml
|
|
119
|
+
description: "Look up an ERC-8004 agent registration by wallet address.",
|
|
120
|
+
inputSchema: z.object({
|
|
121
|
+
address: z.string().describe("0x wallet address"),
|
|
122
|
+
network: networkArg,
|
|
123
|
+
}),
|
|
124
|
+
execute: async ({ address, network }) =>
|
|
125
|
+
cr.agentByAddress(address, network ?? defaultNetwork()),
|
|
126
|
+
}),
|
|
127
|
+
|
|
128
|
+
// --- ERC-8183 jobs (READ-ONLY status/list — writes live in signing.ts) ---
|
|
129
|
+
job_status: tool({
|
|
130
|
+
// requires [erc8183] in studio.toml
|
|
131
|
+
description: "Read-only ERC-8183 job summary (status, budget, deliverable URL).",
|
|
132
|
+
inputSchema: z.object({
|
|
133
|
+
job_id: z.number().int().describe("on-chain job id"),
|
|
134
|
+
network: networkArg,
|
|
135
|
+
}),
|
|
136
|
+
execute: async ({ job_id, network }) =>
|
|
137
|
+
cr.jobStatus(job_id, network ?? defaultNetwork()),
|
|
138
|
+
}),
|
|
139
|
+
job_list: tool({
|
|
140
|
+
// requires [erc8183] in studio.toml
|
|
141
|
+
description: "List recent ERC-8183 jobs (optionally only this agent's).",
|
|
142
|
+
inputSchema: z.object({
|
|
143
|
+
limit: z.number().int().optional(),
|
|
144
|
+
mine: z.boolean().optional().describe("only jobs assigned to this agent"),
|
|
145
|
+
network: networkArg,
|
|
146
|
+
}),
|
|
147
|
+
execute: async ({ limit, mine, network }) =>
|
|
148
|
+
cr.jobList({ limit, mine, network: network ?? defaultNetwork() }),
|
|
149
|
+
}),
|
|
150
|
+
// job_count: ... // network-wide stat — usually noise
|
|
151
|
+
|
|
152
|
+
// --- Advanced / footguns (commented by default) ---
|
|
153
|
+
// contract_call_view: ... // accepts any ABI — LLM-callable footgun
|
|
154
|
+
// block_info: ...
|
|
155
|
+
// wallet_list: ... // multi-wallet management — dev concern
|
|
156
|
+
// wallet_address: ... // alias of wallet_info
|
|
157
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
[recipe]
|
|
2
|
+
name = "runtimes/azure-foundry"
|
|
3
|
+
description = "Runtime adapter recipe for Azure AI Foundry Hosted Agents — the second deploy target. Selected via `bag init --runtime azure-foundry`. Declares the runtime's required node deps and contributes the cloud-neutral @a2a-js/sdk server for local/dev plus the Foundry deploy host: a pass-through Invocations skill router over the SHARED SellerAgentExecutor.dispatch, serving the documented :8088 + /readiness + /invocations contract. Azure deployment currently supports A2A scaffolds only; the MCP entrypoint remains local/AgentCore-oriented and is rejected before Foundry deploy. The chain tools (tools.ts) + model factory (model.ts) are shared by both protocols (decision D9). Deploys are container-only and fully delegated to the pinned @bnbagent/deploy-cli (SDK/REST + browser login; CustomKeys secret injection; no azd/az CLIs, no azure.yaml/infra in the scaffold)."
|
|
4
|
+
status = "v1"
|
|
5
|
+
|
|
6
|
+
# The recipe contributes the entrypoints plus the deps below; the deploy
|
|
7
|
+
# spec is generated per-invocation by `bag deploy --provider azure` and handed to the
|
|
8
|
+
# pinned bnbagent-deploy (all cloud execution is delegated — there is no
|
|
9
|
+
# azd bootstrap and no azure.yaml / infra/ in the scaffold).
|
|
10
|
+
#
|
|
11
|
+
# foundryMain.ts implements the documented framework-neutral container
|
|
12
|
+
# contract directly: plain HTTP on :8088, GET /readiness, and the declared
|
|
13
|
+
# POST /invocations pass-through route. This path was live E2E verified on
|
|
14
|
+
# 2026-07-20 through deploy, cold-start smoke, invoke, logs, and destroy.
|
|
15
|
+
|
|
16
|
+
# Base deps are shared by every mode. bnbagent-deploy injects provider/storage
|
|
17
|
+
# keys and wallet material through a Foundry CustomKeys connection before the
|
|
18
|
+
# process starts, so the agent needs no Azure credential or Key Vault SDK.
|
|
19
|
+
[dependencies]
|
|
20
|
+
node = [
|
|
21
|
+
"@bnbagent/studio-runtime",
|
|
22
|
+
"@bnbagent/sdk@0.5.0-alpha.1",
|
|
23
|
+
# The LLM work hook (generateText + tools) and the model factory; the
|
|
24
|
+
# Foundry host's OpenAI-compatible client comes from @ai-sdk/openai
|
|
25
|
+
# (built from the BNBAGENT_LLM_* env the deploy injects).
|
|
26
|
+
"ai@^7.0.29",
|
|
27
|
+
"@ai-sdk/openai@^4.0.15",
|
|
28
|
+
# Tool input schemas (AI SDK tools + MCP registerTool).
|
|
29
|
+
"zod@^3.25.0",
|
|
30
|
+
# Every entrypoint serves over express (the @a2a-js/sdk server
|
|
31
|
+
# integration, the MCP streamable-HTTP wiring, and the Invocations host).
|
|
32
|
+
"express@^5.1.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# A2A protocol surface (main.ts): the CLOUD-NEUTRAL serving stack — the
|
|
36
|
+
# official @a2a-js/sdk express app, NOT AWS's wrapper, so an azure-foundry
|
|
37
|
+
# seller carries no AWS serving dependency (commitments #4/#5). MUST be <1.0:
|
|
38
|
+
# 1.0.0-beta reshapes the server surface; 0.3.14 is the spike-verified line
|
|
39
|
+
# (EX-3).
|
|
40
|
+
[dependencies.a2a]
|
|
41
|
+
node = [
|
|
42
|
+
"@a2a-js/sdk@>=0.3.14 <1.0",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# MCP protocol surface (mcpMain.ts): the official MCP TS SDK.
|
|
46
|
+
[dependencies.mcp]
|
|
47
|
+
node = [
|
|
48
|
+
"@modelcontextprotocol/sdk@^1.29.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# Modes are the protocol × deploy cross-product `bag init` selects (mode=
|
|
52
|
+
# "{a2a|mcp}-{codezip|container}"), mirroring runtimes/agentcore:
|
|
53
|
+
# protocol — a2a: main.ts (neutral @a2a-js serving) + executor.ts +
|
|
54
|
+
# agentCard.ts + foundryMain.ts (the Foundry deploy host — it
|
|
55
|
+
# imports executor.ts, so it rides the a2a modes. Azure deploy
|
|
56
|
+
# rejects MCP because Foundry's declared invocations endpoint is
|
|
57
|
+
# not the native streamable-HTTP /mcp transport.)
|
|
58
|
+
# mcp: mcpMain.ts (streamable-HTTP /mcp; negotiate +
|
|
59
|
+
# notify_funded sync-delivery)
|
|
60
|
+
# deploy — container (the azure-foundry deploy shape): bnbagent-deploy's
|
|
61
|
+
# azure provider rejects Node zip, so every
|
|
62
|
+
# azure-foundry deploy builds this Dockerfile
|
|
63
|
+
# LOCALLY with Docker (linux/amd64) and pushes it to
|
|
64
|
+
# the auto-provisioned ACR (a twak wallet
|
|
65
|
+
# additionally needs the twak CLI inside the image).
|
|
66
|
+
# The container modes add {{PKG}}/Dockerfile +
|
|
67
|
+
# {{PKG}}/.dockerignore.
|
|
68
|
+
# codezip (historical): Foundry CodeZip source modes — never
|
|
69
|
+
# selected by `bag init` for azure-foundry today
|
|
70
|
+
# (kept for mode-shape parity with
|
|
71
|
+
# runtimes/agentcore); they ship no Dockerfile.
|
|
72
|
+
# tools.ts + model.ts are unlisted → SHARED, always emitted. signing.ts +
|
|
73
|
+
# the read-only tools live in the `agent` / tools recipes (emitted in all
|
|
74
|
+
# modes).
|
|
75
|
+
[modes]
|
|
76
|
+
default = "a2a-codezip"
|
|
77
|
+
|
|
78
|
+
[modes.a2a-codezip]
|
|
79
|
+
files = ["{{PKG}}/main.ts", "{{PKG}}/executor.ts", "{{PKG}}/agentCard.ts", "{{PKG}}/foundryMain.ts"]
|
|
80
|
+
|
|
81
|
+
[modes.a2a-container]
|
|
82
|
+
files = ["{{PKG}}/main.ts", "{{PKG}}/executor.ts", "{{PKG}}/agentCard.ts", "{{PKG}}/foundryMain.ts", "{{PKG}}/Dockerfile", "{{PKG}}/.dockerignore"]
|
|
83
|
+
|
|
84
|
+
[modes.mcp-codezip]
|
|
85
|
+
files = ["{{PKG}}/mcpMain.ts"]
|
|
86
|
+
|
|
87
|
+
[modes.mcp-container]
|
|
88
|
+
files = ["{{PKG}}/mcpMain.ts", "{{PKG}}/Dockerfile", "{{PKG}}/.dockerignore"]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain read-only tools exposed to this agent's LLM (AI SDK `tool` wrap).
|
|
3
|
+
*
|
|
4
|
+
* The full 15-tool read-only inventory from
|
|
5
|
+
* `@bnbagent/studio-runtime/tools`, re-exported as plain functions (for
|
|
6
|
+
* fixed-code use) AND wrapped as AI SDK tools in `CHAIN_READ_TOOLS` (for the
|
|
7
|
+
* LLM). Each tool's `description` is what the LLM sees. The curated set in
|
|
8
|
+
* `LLM_READ_TOOLS` (runtime recipe `tools.ts`) is the recommended default —
|
|
9
|
+
* this file is for agents that want the whole inventory or a custom cut.
|
|
10
|
+
*
|
|
11
|
+
* You own this file — edit `CHAIN_READ_TOOLS` to control exactly what your
|
|
12
|
+
* agent can read on-chain. Footgun entries are commented out by default;
|
|
13
|
+
* uncomment deliberately.
|
|
14
|
+
*
|
|
15
|
+
* All tools are **read-only** by the studio definition: no on-chain state
|
|
16
|
+
* change, no transferable authority. The only signing that happens is
|
|
17
|
+
* `pieverse_usage`'s SIWE personal_sign (domain-locked to llm.pieverse.io,
|
|
18
|
+
* no on-chain effect). See the `@bnbagent/studio-runtime/tools` module
|
|
19
|
+
* docstring for the full chain read-only LLM-tools decision.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { loadStudioToml } from "@bnbagent/studio-runtime/config";
|
|
23
|
+
import * as cr from "@bnbagent/studio-runtime/tools";
|
|
24
|
+
import { tool, type ToolSet } from "ai";
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
|
|
27
|
+
// Plain functions — call these from fixed code (no LLM in the loop).
|
|
28
|
+
export const {
|
|
29
|
+
agentByAddress,
|
|
30
|
+
agentInfo,
|
|
31
|
+
balanceNative,
|
|
32
|
+
balanceU,
|
|
33
|
+
blockInfo,
|
|
34
|
+
contractCallView,
|
|
35
|
+
jobCount,
|
|
36
|
+
jobList,
|
|
37
|
+
jobStatus,
|
|
38
|
+
networkInfo,
|
|
39
|
+
pieverseUsage,
|
|
40
|
+
txStatus,
|
|
41
|
+
walletAddress,
|
|
42
|
+
walletInfo,
|
|
43
|
+
walletList,
|
|
44
|
+
} = cr;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The project-wide default network (`[network].default`) — tool calls that
|
|
48
|
+
* omit `network` fall back to it, never to a hardcoded name.
|
|
49
|
+
*/
|
|
50
|
+
function defaultNetwork(): string {
|
|
51
|
+
try {
|
|
52
|
+
const cfg = loadStudioToml();
|
|
53
|
+
return String(
|
|
54
|
+
((cfg.network ?? {}) as Record<string, unknown>).default ?? "bsc-testnet",
|
|
55
|
+
);
|
|
56
|
+
} catch {
|
|
57
|
+
return "bsc-testnet";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const networkArg = z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("studio network name (defaults to the project's [network].default)");
|
|
65
|
+
|
|
66
|
+
export const CHAIN_READ_TOOLS: ToolSet = {
|
|
67
|
+
// --- Wallet & chain basics ---
|
|
68
|
+
wallet_info: tool({
|
|
69
|
+
description:
|
|
70
|
+
"Describe the agent's active wallet (address, kind, key location).",
|
|
71
|
+
inputSchema: z.object({}),
|
|
72
|
+
execute: async () => cr.walletInfo(),
|
|
73
|
+
}),
|
|
74
|
+
wallet_list: tool({
|
|
75
|
+
description: "List all local wallet addresses.",
|
|
76
|
+
inputSchema: z.object({}),
|
|
77
|
+
execute: async () => cr.walletList(),
|
|
78
|
+
}),
|
|
79
|
+
wallet_address: tool({
|
|
80
|
+
description: "The active wallet address.",
|
|
81
|
+
inputSchema: z.object({}),
|
|
82
|
+
execute: async () => ({ address: await cr.walletAddress() }),
|
|
83
|
+
}),
|
|
84
|
+
balance_native: tool({
|
|
85
|
+
description:
|
|
86
|
+
"Native BNB balance of an address (defaults to the agent's own wallet).",
|
|
87
|
+
inputSchema: z.object({
|
|
88
|
+
address: z.string().optional(),
|
|
89
|
+
network: networkArg,
|
|
90
|
+
}),
|
|
91
|
+
execute: async ({ address, network }) =>
|
|
92
|
+
cr.balanceNative(address ?? null, network ?? defaultNetwork()),
|
|
93
|
+
}),
|
|
94
|
+
balance_u: tool({
|
|
95
|
+
// requires [u_token] in studio.toml
|
|
96
|
+
description:
|
|
97
|
+
"$U (payment token) balance of an address (defaults to the agent's own wallet).",
|
|
98
|
+
inputSchema: z.object({
|
|
99
|
+
address: z.string().optional(),
|
|
100
|
+
network: networkArg,
|
|
101
|
+
}),
|
|
102
|
+
execute: async ({ address, network }) =>
|
|
103
|
+
cr.balanceU(address ?? null, network ?? defaultNetwork()),
|
|
104
|
+
}),
|
|
105
|
+
network_info: tool({
|
|
106
|
+
description: "Chain id / RPC / token info for a studio network.",
|
|
107
|
+
inputSchema: z.object({ network: networkArg }),
|
|
108
|
+
execute: async ({ network }) => cr.networkInfo(network ?? defaultNetwork()),
|
|
109
|
+
}),
|
|
110
|
+
tx_status: tool({
|
|
111
|
+
description: "Status + receipt summary of a transaction hash.",
|
|
112
|
+
inputSchema: z.object({ tx_hash: z.string(), network: networkArg }),
|
|
113
|
+
execute: async ({ tx_hash, network }) =>
|
|
114
|
+
cr.txStatus(tx_hash, network ?? defaultNetwork()),
|
|
115
|
+
}),
|
|
116
|
+
|
|
117
|
+
// --- LLM provider ---
|
|
118
|
+
// pieverse_usage: tool({
|
|
119
|
+
// // SIWE personal_sign; requires [llm.provider=pieverse-llm]
|
|
120
|
+
// description: "Pieverse LLM usage/credit summary for the last N days.",
|
|
121
|
+
// inputSchema: z.object({ days: z.number().int().optional() }),
|
|
122
|
+
// execute: async ({ days }) => cr.pieverseUsage(days ?? 7),
|
|
123
|
+
// }),
|
|
124
|
+
|
|
125
|
+
// --- ERC-8004 identity ---
|
|
126
|
+
agent_info: tool({
|
|
127
|
+
// requires [erc8004] in studio.toml
|
|
128
|
+
description: "ERC-8004 identity record for an agent id.",
|
|
129
|
+
inputSchema: z.object({ agent_id: z.number().int(), network: networkArg }),
|
|
130
|
+
execute: async ({ agent_id, network }) =>
|
|
131
|
+
cr.agentInfo(agent_id, network ?? defaultNetwork()),
|
|
132
|
+
}),
|
|
133
|
+
agent_by_address: tool({
|
|
134
|
+
// requires [erc8004] in studio.toml
|
|
135
|
+
description: "Look up an ERC-8004 agent registration by wallet address.",
|
|
136
|
+
inputSchema: z.object({ address: z.string(), network: networkArg }),
|
|
137
|
+
execute: async ({ address, network }) =>
|
|
138
|
+
cr.agentByAddress(address, network ?? defaultNetwork()),
|
|
139
|
+
}),
|
|
140
|
+
|
|
141
|
+
// --- ERC-8183 jobs ---
|
|
142
|
+
job_status: tool({
|
|
143
|
+
// requires [erc8183] in studio.toml
|
|
144
|
+
description:
|
|
145
|
+
"Read-only ERC-8183 job summary (status, budget, deliverable URL).",
|
|
146
|
+
inputSchema: z.object({ job_id: z.number().int(), network: networkArg }),
|
|
147
|
+
execute: async ({ job_id, network }) =>
|
|
148
|
+
cr.jobStatus(job_id, network ?? defaultNetwork()),
|
|
149
|
+
}),
|
|
150
|
+
job_list: tool({
|
|
151
|
+
// requires [erc8183] in studio.toml
|
|
152
|
+
description: "List recent ERC-8183 jobs (optionally only this agent's).",
|
|
153
|
+
inputSchema: z.object({
|
|
154
|
+
limit: z.number().int().optional(),
|
|
155
|
+
mine: z.boolean().optional(),
|
|
156
|
+
network: networkArg,
|
|
157
|
+
}),
|
|
158
|
+
execute: async ({ limit, mine, network }) =>
|
|
159
|
+
cr.jobList({ limit, mine, network: network ?? defaultNetwork() }),
|
|
160
|
+
}),
|
|
161
|
+
// job_count: ... // network-wide stat — usually noise
|
|
162
|
+
|
|
163
|
+
// --- Advanced / footguns (commented by default) ---
|
|
164
|
+
// contract_call_view: ... // accepts any ABI — LLM-callable footgun
|
|
165
|
+
// block_info: ...
|
|
166
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[recipe]
|
|
2
|
+
name = "tools-chain"
|
|
3
|
+
description = "Read-only chain tools from @bnbagent/studio-runtime/tools, exposed as plain functions plus AI SDK tool definitions the LLM can call (framework-neutral by decision D9)."
|
|
4
|
+
status = "v0.0.1"
|
|
5
|
+
|
|
6
|
+
[dependencies]
|
|
7
|
+
node = [
|
|
8
|
+
"@bnbagent/studio-runtime",
|
|
9
|
+
"ai@^7.0.29", # AI SDK `tool` wrappers
|
|
10
|
+
"zod@^3.25.0", # tool input schemas
|
|
11
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[recipe]
|
|
2
|
+
name = "wallet"
|
|
3
|
+
description = "Configures the agent's wallet: 'evm-local' (default encrypted keystore), 'twak' (Trust Wallet Agent Kit CLI), or 'altana' (encrypted admin keystore plus one bounded runtime session)."
|
|
4
|
+
|
|
5
|
+
[dependencies]
|
|
6
|
+
node = ["@bnbagent/studio-runtime"]
|
|
7
|
+
|
|
8
|
+
[dependencies.altana]
|
|
9
|
+
node = ["@altananetwork/sdk@0.5.1"]
|
|
10
|
+
|
|
11
|
+
[env]
|
|
12
|
+
# kind='twak': the twak CLI reads the unlock password from the environment —
|
|
13
|
+
# it never goes on a command line.
|
|
14
|
+
TWAK_WALLET_PASSWORD = "unlocks ~/.twak/wallet.json (twak kind); set in .studio/.env.local"
|
|
15
|
+
# kind='evm-local' (the `bag init` default) or kind='altana' admin operations:
|
|
16
|
+
WALLET_PASSWORD = "set to a strong password (>= 12 chars); encrypts .studio/wallets/<addr>.json (runtime never receives it for altana)"
|
|
17
|
+
|
|
18
|
+
[install]
|
|
19
|
+
# Secrets (keystore + .env.local) all live under the workspace-root .studio/.
|
|
20
|
+
gitignore = [".studio/"]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402 micropay buyer tools — emitted by `bag recipe code x402-buyer`.
|
|
3
|
+
*
|
|
4
|
+
* The agent's LLM can buy from any x402-protected URL whose host is on the
|
|
5
|
+
* effective allowlist: `[payments.x402].allowed_hosts` plus every merchant
|
|
6
|
+
* trusted via `bag x402 trust` (`[payments.x402.merchants.*]` — e.g. the
|
|
7
|
+
* Binance Bazaar shelf: CMC market data).
|
|
8
|
+
*
|
|
9
|
+
* Wire into the agent (main.ts / mcpMain.ts) — signing stays fixed code,
|
|
10
|
+
* these tools only *fetch with payment* inside the config fences:
|
|
11
|
+
*
|
|
12
|
+
* import { X402_BUYER_TOOLS } from "./x402Buyer.js";
|
|
13
|
+
* ...
|
|
14
|
+
* tools: { ...LLM_READ_TOOLS, ...X402_BUYER_TOOLS },
|
|
15
|
+
*
|
|
16
|
+
* (This file sits NEXT TO tools.ts on purpose so the emitted agent package
|
|
17
|
+
* stays a flat module set.)
|
|
18
|
+
*
|
|
19
|
+
* Security spine (all enforced in @bnbagent/studio-runtime, not here):
|
|
20
|
+
* - recipient: the pinned merchant `pay_to` (or `expected_recipient`) is
|
|
21
|
+
* byte-compared against the 402's payTo — a tampered 402 can never
|
|
22
|
+
* redirect funds;
|
|
23
|
+
* - per-call cap: `merchants.<name>.per_call_cap_usd` clamps every payment
|
|
24
|
+
* regardless of the `max_usd` argument below (the LLM can tighten it,
|
|
25
|
+
* never widen);
|
|
26
|
+
* - daily cap: `[budget].max_per_day_usd` across all paid calls.
|
|
27
|
+
*
|
|
28
|
+
* You own this file; edit freely.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { loadStudioToml } from "@bnbagent/studio-runtime/config";
|
|
32
|
+
import { getWallet } from "@bnbagent/studio-runtime/wallet";
|
|
33
|
+
import {
|
|
34
|
+
fetchWithPayment,
|
|
35
|
+
quoteUrl,
|
|
36
|
+
X402BuyerPolicy,
|
|
37
|
+
X402Error,
|
|
38
|
+
} from "@bnbagent/studio-runtime/x402";
|
|
39
|
+
import { tool, type ToolSet } from "ai";
|
|
40
|
+
import { z } from "zod";
|
|
41
|
+
|
|
42
|
+
function policy(): X402BuyerPolicy {
|
|
43
|
+
return X402BuyerPolicy.fromToml(loadStudioToml());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pay an x402-protected endpoint and return its response.
|
|
48
|
+
*
|
|
49
|
+
* NO API KEY IS REQUIRED. x402 merchants (CoinMarketCap x402 endpoints,
|
|
50
|
+
* Binance Bazaar merchants, etc.) authenticate the caller by the on-chain
|
|
51
|
+
* EIP-3009 payment signed with the agent's own wallet — there is NO
|
|
52
|
+
* `Authorization` / `X-API-KEY` header. If a task needs data from a URL on
|
|
53
|
+
* the allowlist (CMC prices, etc.), ALWAYS use this tool; never ask the user
|
|
54
|
+
* for an API key and never answer that the task can't be done for lack of
|
|
55
|
+
* one. Payment is fenced by studio.toml (merchant.per_call_cap_usd +
|
|
56
|
+
* [budget].max_per_day_usd), so calling it is always safe.
|
|
57
|
+
*
|
|
58
|
+
* URL host must be on the effective allowlist (allowed_hosts + trusted
|
|
59
|
+
* merchants). `maxUsd` is only a refusal threshold — the actual price comes
|
|
60
|
+
* from the endpoint's 402 challenge, and the config's per-call / daily caps
|
|
61
|
+
* still apply on top. Query-style endpoints (e.g. CMC) take GET with params
|
|
62
|
+
* in the URL; RPC-style ones take POST.
|
|
63
|
+
*/
|
|
64
|
+
export async function buyWithX402(
|
|
65
|
+
url: string,
|
|
66
|
+
maxUsd: number,
|
|
67
|
+
method = "GET",
|
|
68
|
+
): Promise<Record<string, unknown>> {
|
|
69
|
+
const pol = policy();
|
|
70
|
+
try {
|
|
71
|
+
const result = await fetchWithPayment(url, {
|
|
72
|
+
maxUsd: Math.min(maxUsd, pol.maxPerRequestUsd),
|
|
73
|
+
wallet: getWallet(),
|
|
74
|
+
method,
|
|
75
|
+
allowedHosts: [...pol.effectiveAllowedHosts],
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
status: result.statusCode,
|
|
80
|
+
json: result.json,
|
|
81
|
+
paid_usd: result.paidUsd,
|
|
82
|
+
};
|
|
83
|
+
} catch (exc) {
|
|
84
|
+
if (exc instanceof X402Error) {
|
|
85
|
+
return { ok: false, error: exc.constructor.name, message: exc.message };
|
|
86
|
+
}
|
|
87
|
+
throw exc;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Probe an x402 endpoint without paying — returns its 402 body for inspection. */
|
|
92
|
+
export async function quoteX402(
|
|
93
|
+
url: string,
|
|
94
|
+
method = "GET",
|
|
95
|
+
): Promise<Record<string, unknown>> {
|
|
96
|
+
try {
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
accepts: await quoteUrl(url, {
|
|
100
|
+
method,
|
|
101
|
+
allowedHosts: [...policy().effectiveAllowedHosts],
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
} catch (exc) {
|
|
105
|
+
if (exc instanceof X402Error) {
|
|
106
|
+
return { ok: false, error: exc.constructor.name, message: exc.message };
|
|
107
|
+
}
|
|
108
|
+
throw exc;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The AI SDK tool set — spread into the agent's `tools:` map. */
|
|
113
|
+
export const X402_BUYER_TOOLS: ToolSet = {
|
|
114
|
+
buy_with_x402: tool({
|
|
115
|
+
description:
|
|
116
|
+
"Pay an x402-protected endpoint with the agent's own wallet (EIP-3009; " +
|
|
117
|
+
"NO API key) and return its JSON response. Use it whenever a task needs " +
|
|
118
|
+
"data from an allowlisted paid endpoint (e.g. CoinMarketCap market " +
|
|
119
|
+
"data). max_usd is a refusal threshold; studio.toml per-call/daily caps " +
|
|
120
|
+
"always apply on top.",
|
|
121
|
+
inputSchema: z.object({
|
|
122
|
+
url: z.string().describe("the full x402-protected URL (query params inline)"),
|
|
123
|
+
max_usd: z.number().describe("refuse if the 402 price exceeds this (USD)"),
|
|
124
|
+
method: z.string().optional().describe("HTTP verb; default GET"),
|
|
125
|
+
}),
|
|
126
|
+
execute: async ({ url, max_usd, method }) =>
|
|
127
|
+
buyWithX402(url, max_usd, method ?? "GET"),
|
|
128
|
+
}),
|
|
129
|
+
quote_x402: tool({
|
|
130
|
+
description:
|
|
131
|
+
"Probe an x402 endpoint WITHOUT paying — returns the 402 challenge body " +
|
|
132
|
+
"(price, payTo, asset) for inspection.",
|
|
133
|
+
inputSchema: z.object({
|
|
134
|
+
url: z.string(),
|
|
135
|
+
method: z.string().optional().describe("HTTP verb; default GET"),
|
|
136
|
+
}),
|
|
137
|
+
execute: async ({ url, method }) => quoteX402(url, method ?? "GET"),
|
|
138
|
+
}),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ── Deterministic pre-fetch (recommended for a purpose-built seller) ──────────
|
|
142
|
+
// The tools above let the LLM *decide* to buy — fine for an agent that buys
|
|
143
|
+
// ad hoc from many sources. But if your seller ALWAYS needs the same paid
|
|
144
|
+
// data (e.g. it exists to report CMC prices), do NOT lean on the LLM to
|
|
145
|
+
// choose to call `buy_with_x402`: small models (gpt-4o-mini, haiku)
|
|
146
|
+
// routinely skip it and either excuse themselves ("missing API key") or
|
|
147
|
+
// answer from stale training priors — no amount of prompt wording reliably
|
|
148
|
+
// fixes this. Fetch in FIXED code, then hand the LLM the data to write up.
|
|
149
|
+
// This mirrors commitment #3: the money action is deterministic; the LLM
|
|
150
|
+
// only produces text.
|
|
151
|
+
//
|
|
152
|
+
// In `sellerCore.ts` `doWorkAndSubmit` (you own that file) — or the
|
|
153
|
+
// azure-foundry `executor.ts` equivalent — replace the generic prompt with a
|
|
154
|
+
// pre-fetch:
|
|
155
|
+
//
|
|
156
|
+
// import { buyWithX402 } from "./x402Buyer.js";
|
|
157
|
+
//
|
|
158
|
+
// const paid = await buyWithX402(
|
|
159
|
+
// "https://pro-api.coinmarketcap.com/x402/v3/cryptocurrency/quotes/latest" +
|
|
160
|
+
// "?symbol=BNB&convert=USD",
|
|
161
|
+
// 0.02,
|
|
162
|
+
// );
|
|
163
|
+
// if (!paid.ok) {
|
|
164
|
+
// throw new Error(`x402 fetch failed: ${paid.message}`);
|
|
165
|
+
// }
|
|
166
|
+
// const prompt =
|
|
167
|
+
// "You accepted and were paid for this job. The live data below was " +
|
|
168
|
+
// "already fetched and paid for on your behalf — use it verbatim; do " +
|
|
169
|
+
// "NOT claim you need an API key.\n\n" +
|
|
170
|
+
// `JOB CONTEXT:\n${task}\n\nFETCHED DATA (x402, paid $${paid.paid_usd}):\n` +
|
|
171
|
+
// JSON.stringify(paid.json);
|
|
172
|
+
// const work = await this.runWork(prompt, { sessionId: String(jobId) });
|
|
173
|
+
//
|
|
174
|
+
// Keep `buy_with_x402` in the LLM toolset too if some jobs need ad-hoc buys
|
|
175
|
+
// the LLM must decide — the deterministic pre-fetch and the tool compose.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[recipe]
|
|
2
|
+
name = "x402-buyer"
|
|
3
|
+
description = "x402 micropay buyer tool set for the agent's LLM (allowed_hosts + merchants trusted via `bag x402 trust`, e.g. CMC market data from the Binance Bazaar)."
|
|
4
|
+
status = "v0.0.x"
|
|
5
|
+
|
|
6
|
+
[dependencies]
|
|
7
|
+
node = [
|
|
8
|
+
"@bnbagent/studio-runtime",
|
|
9
|
+
"@bnbagent/sdk@0.5.0-alpha.1",
|
|
10
|
+
"ai@^7.0.29", # AI SDK `tool` wrappers around the buyer functions
|
|
11
|
+
"zod@^3.25.0", # tool input schemas
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[env]
|
|
15
|
+
WALLET_PASSWORD = "wallet keystore password (used to sign EIP-3009 transfers)"
|