@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.
Files changed (55) hide show
  1. package/DISCLAIMER.md +48 -0
  2. package/LICENSE +201 -0
  3. package/dist/_agentcoreName-DZDWEYD3.js +7 -0
  4. package/dist/_twak-5XQMOFUC.js +25 -0
  5. package/dist/bag.js +19358 -0
  6. package/dist/chunk-7RAKL4AS.js +172 -0
  7. package/dist/chunk-M3ODFCA7.js +1053 -0
  8. package/dist/chunk-U7IDQ3K5.js +14 -0
  9. package/dist/deployCli-N6TPN6XA.js +40 -0
  10. package/package.json +64 -0
  11. package/recipes/agent/code/{{PKG}}/signing.ts.tmpl +287 -0
  12. package/recipes/agent/recipe.toml +35 -0
  13. package/recipes/providers/pieverse-llm/recipe.toml +16 -0
  14. package/recipes/providers/pieverse-llm/skills/funding-pieverse-llm.md +203 -0
  15. package/recipes/runtimes/agentcore/code/{{PKG}}/.dockerignore.tmpl +8 -0
  16. package/recipes/runtimes/agentcore/code/{{PKG}}/Dockerfile.tmpl +50 -0
  17. package/recipes/runtimes/agentcore/code/{{PKG}}/agentCard.ts.tmpl +135 -0
  18. package/recipes/runtimes/agentcore/code/{{PKG}}/dualMain.ts.tmpl +402 -0
  19. package/recipes/runtimes/agentcore/code/{{PKG}}/executor.ts.tmpl +147 -0
  20. package/recipes/runtimes/agentcore/code/{{PKG}}/main.ts.tmpl +344 -0
  21. package/recipes/runtimes/agentcore/code/{{PKG}}/mcpMain.ts.tmpl +677 -0
  22. package/recipes/runtimes/agentcore/code/{{PKG}}/model.ts.tmpl +117 -0
  23. package/recipes/runtimes/agentcore/code/{{PKG}}/sellerCore.ts.tmpl +503 -0
  24. package/recipes/runtimes/agentcore/code/{{PKG}}/tools.ts.tmpl +157 -0
  25. package/recipes/runtimes/agentcore/recipe.toml +97 -0
  26. package/recipes/runtimes/azure-foundry/code/{{PKG}}/.dockerignore.tmpl +8 -0
  27. package/recipes/runtimes/azure-foundry/code/{{PKG}}/Dockerfile.tmpl +47 -0
  28. package/recipes/runtimes/azure-foundry/code/{{PKG}}/agentCard.ts.tmpl +131 -0
  29. package/recipes/runtimes/azure-foundry/code/{{PKG}}/executor.ts.tmpl +504 -0
  30. package/recipes/runtimes/azure-foundry/code/{{PKG}}/foundryMain.ts.tmpl +300 -0
  31. package/recipes/runtimes/azure-foundry/code/{{PKG}}/main.ts.tmpl +196 -0
  32. package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +562 -0
  33. package/recipes/runtimes/azure-foundry/code/{{PKG}}/model.ts.tmpl +117 -0
  34. package/recipes/runtimes/azure-foundry/code/{{PKG}}/tools.ts.tmpl +157 -0
  35. package/recipes/runtimes/azure-foundry/recipe.toml +88 -0
  36. package/recipes/tools-chain/code/{{PKG}}/chainTools.ts.tmpl +166 -0
  37. package/recipes/tools-chain/recipe.toml +11 -0
  38. package/recipes/wallet/recipe.toml +20 -0
  39. package/recipes/x402-buyer/code/{{PKG}}/x402Buyer.ts.tmpl +175 -0
  40. package/recipes/x402-buyer/recipe.toml +15 -0
  41. package/skills/bnbagent-studio.md +107 -0
  42. package/skills/references/bnbagent-studio-adding-to-project.md +241 -0
  43. package/skills/references/bnbagent-studio-buying-from-bazaar.md +169 -0
  44. package/skills/references/bnbagent-studio-buying-via-8183.md +222 -0
  45. package/skills/references/bnbagent-studio-extending-signing.md +227 -0
  46. package/skills/references/bnbagent-studio-operating.md +211 -0
  47. package/skills/references/bnbagent-studio-scaffolding-agent.md +536 -0
  48. package/skills/references/bnbagent-studio-selling-via-8183.md +271 -0
  49. package/skills/references/bnbagent-studio-selling-via-b402.md +194 -0
  50. package/skills/references/bnbagent-studio-use-aws-agentcore.md +208 -0
  51. package/skills/references/bnbagent-studio-use-azure-foundry.md +164 -0
  52. package/skills/references/bnbagent-studio-use-bnb-trial.md +92 -0
  53. package/skills/references/bnbagent-studio-using-altana-wallet.md +68 -0
  54. package/skills/references/bnbagent-studio-using-twak-wallet.md +260 -0
  55. 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)"