@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,97 @@
|
|
|
1
|
+
[recipe]
|
|
2
|
+
name = "runtimes/agentcore"
|
|
3
|
+
description = "Runtime adapter for AWS Bedrock AgentCore. A project selects one or more public faces on one runtime and signer: A2A emits main.ts, MCP emits mcpMain.ts, and A2A+MCP emits the A2A-native dualMain.ts plus mcpMain.ts as its MCP server library. X402 is an optional sibling route on whichever entrypoint is selected. The chain tools (tools.ts) + model factory (model.ts) are shared by every mode."
|
|
4
|
+
status = "a2a|mcp|both"
|
|
5
|
+
|
|
6
|
+
# Emits the AgentCore serving layer for the chosen protocol:
|
|
7
|
+
# A2A — main.ts (express + @a2a-js/sdk on AGENT_PORT/9000 + /ping),
|
|
8
|
+
# executor.ts + sellerCore.ts (negotiate + notify_funded; signing stays
|
|
9
|
+
# fixed code in signing.ts, never an LLM tool), agentCard.ts.
|
|
10
|
+
# MCP — mcpMain.ts (stateful streamable-HTTP on 0.0.0.0:8000/mcp):
|
|
11
|
+
# negotiate (signed quote) + notify_funded (verify → work → submit,
|
|
12
|
+
# synchronously in-call) + read-only chain tools.
|
|
13
|
+
# BOTH — dualMain.ts uses A2A natively on :9000 and mounts the stateful MCP
|
|
14
|
+
# server from mcpMain.ts at /mcp for envelope-v1 tunneling.
|
|
15
|
+
# signing.ts comes from the `agent` recipe; tools.ts + model.ts are shared
|
|
16
|
+
# files here (always emitted, both protocols).
|
|
17
|
+
|
|
18
|
+
# NOTE (EX-6): `bag init` writes agentcore.json itself (runtimeVersion =
|
|
19
|
+
# "NODE_22", entrypoint = the built .js) — no native CLI is needed to
|
|
20
|
+
# scaffold. The descriptor names the deploy (resource-naming continuity) and
|
|
21
|
+
# drives `bag dev --container` (the one flow that still uses the npm
|
|
22
|
+
# `@aws/agentcore` CLI); `bag deploy --provider aws` delegates all cloud execution to
|
|
23
|
+
# the pinned @bnbagent/deploy-cli.
|
|
24
|
+
|
|
25
|
+
# Deps split by face. The scaffolder reads `[dependencies].node` (shared) plus
|
|
26
|
+
# one or both face groups, so single-face deploys stay minimal while the both
|
|
27
|
+
# mode receives their union.
|
|
28
|
+
[dependencies]
|
|
29
|
+
node = [
|
|
30
|
+
# loadRuntimeSecrets() in every entrypoint (main.ts / mcpMain.ts) pulls the
|
|
31
|
+
# runtime secret bundle from AWS Secrets Manager when
|
|
32
|
+
# BNBAGENT_RUNTIME_SECRET_ID is set (default secretsmanager mode + platform).
|
|
33
|
+
"@aws-sdk/client-secrets-manager@^3.600.0",
|
|
34
|
+
"@bnbagent/studio-runtime",
|
|
35
|
+
"@bnbagent/sdk@0.5.0-alpha.1",
|
|
36
|
+
# The LLM work hook (generateText + tools) and the model factory.
|
|
37
|
+
"ai@^7.0.29",
|
|
38
|
+
# Tool input schemas (AI SDK tools + MCP registerTool).
|
|
39
|
+
"zod@^3.25.0",
|
|
40
|
+
# Both protocol entrypoints serve over express (the @a2a-js/sdk server
|
|
41
|
+
# integration and the MCP streamable-HTTP wiring are express-based).
|
|
42
|
+
"express@^5.1.0",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[dependencies.a2a]
|
|
46
|
+
node = [
|
|
47
|
+
# The official A2A JS SDK hosts the A2A surface (agent card + JSON-RPC
|
|
48
|
+
# message/send). MUST be <1.0: 1.0.0-beta reshapes the server surface
|
|
49
|
+
# so 0.3.14 remains the spike-verified line (EX-3).
|
|
50
|
+
"@a2a-js/sdk@>=0.3.14 <1.0",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[dependencies.mcp]
|
|
54
|
+
node = [
|
|
55
|
+
# The official MCP TS SDK hosts the MCP surface on /mcp; AgentCore wraps
|
|
56
|
+
# the protocol. No @a2a-js/sdk needed.
|
|
57
|
+
"@modelcontextprotocol/sdk@^1.29.0",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# Modes are selected from public faces plus the packaging axis:
|
|
61
|
+
# protocol — a2a: main.ts (@a2a-js/sdk express) + executor.ts + sellerCore.ts + agentCard.ts
|
|
62
|
+
# mcp: mcpMain.ts (streamable-HTTP /mcp; negotiate + notify_funded sync-delivery + read tools)
|
|
63
|
+
# both: dualMain.ts (A2A native) + mcpMain.ts library + A2A support files
|
|
64
|
+
# deploy — codezip (evm-local) — managed NODE_22 runtime, no Dockerfile.
|
|
65
|
+
# container (twak) — custom image: the twak CLI must be
|
|
66
|
+
# installed globally (npm @trustwallet/cli)
|
|
67
|
+
# in a node:22 Dockerfile.
|
|
68
|
+
# A project picks one or more faces on ONE runtime and signer. signing.ts lives
|
|
69
|
+
# in the `agent` recipe (emitted in all modes). tools.ts + model.ts are shared
|
|
70
|
+
# files (unlisted below → always emitted). sellerCore.ts is the
|
|
71
|
+
# protocol-neutral seller core (negotiate + notify_funded + background
|
|
72
|
+
# delivery); the A2A executor inherits it.
|
|
73
|
+
#
|
|
74
|
+
# Provider `bnb` is UNIFIED with AWS packaging:
|
|
75
|
+
# it reuses the a2a-container / mcp-container modes (same entrypoint + fixed
|
|
76
|
+
# port), and the platform gateway routes to the protocol's native surface.
|
|
77
|
+
# There is no platform-specific recipe mode.
|
|
78
|
+
[modes]
|
|
79
|
+
default = "a2a-codezip"
|
|
80
|
+
|
|
81
|
+
[modes.a2a-codezip]
|
|
82
|
+
files = ["{{PKG}}/main.ts", "{{PKG}}/executor.ts", "{{PKG}}/sellerCore.ts", "{{PKG}}/agentCard.ts"]
|
|
83
|
+
|
|
84
|
+
[modes.a2a-container]
|
|
85
|
+
files = ["{{PKG}}/main.ts", "{{PKG}}/executor.ts", "{{PKG}}/sellerCore.ts", "{{PKG}}/agentCard.ts", "{{PKG}}/Dockerfile", "{{PKG}}/.dockerignore"]
|
|
86
|
+
|
|
87
|
+
[modes.mcp-codezip]
|
|
88
|
+
files = ["{{PKG}}/mcpMain.ts"]
|
|
89
|
+
|
|
90
|
+
[modes.mcp-container]
|
|
91
|
+
files = ["{{PKG}}/mcpMain.ts", "{{PKG}}/Dockerfile", "{{PKG}}/.dockerignore"]
|
|
92
|
+
|
|
93
|
+
[modes.both-codezip]
|
|
94
|
+
files = ["{{PKG}}/dualMain.ts", "{{PKG}}/mcpMain.ts", "{{PKG}}/executor.ts", "{{PKG}}/sellerCore.ts", "{{PKG}}/agentCard.ts"]
|
|
95
|
+
|
|
96
|
+
[modes.both-container]
|
|
97
|
+
files = ["{{PKG}}/dualMain.ts", "{{PKG}}/mcpMain.ts", "{{PKG}}/executor.ts", "{{PKG}}/sellerCore.ts", "{{PKG}}/agentCard.ts", "{{PKG}}/Dockerfile", "{{PKG}}/.dockerignore"]
|
|
@@ -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 Key Vault injection.)
|
|
4
|
+
node_modules/
|
|
5
|
+
dist/
|
|
6
|
+
.env.local
|
|
7
|
+
.studio/
|
|
8
|
+
*.tsbuildinfo
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Azure AI Foundry Hosted Agent image — SHIPPED BY `bag init`.
|
|
2
|
+
# NOTE: deliberately NO `# syntax=docker/dockerfile:1` directive AND no BuildKit-only
|
|
3
|
+
# instruction flags (`FROM --platform=…`, `RUN --mount=…`) — the file stays
|
|
4
|
+
# classic-builder-compatible so any builder (a "classic" builder rejects
|
|
5
|
+
# `FROM --platform=…` with "unable to understand line FROM --platform=…") can
|
|
6
|
+
# process it. The target arch is pinned OUTSIDE the Dockerfile instead: the
|
|
7
|
+
# pinned bnbagent-deploy (which `bag deploy --provider azure` delegates all cloud
|
|
8
|
+
# execution to) builds this Dockerfile LOCALLY with Docker for linux/amd64
|
|
9
|
+
# (`--platform` on the build command) and pushes the image to the
|
|
10
|
+
# auto-provisioned Azure Container Registry; Foundry Agent Service pulls +
|
|
11
|
+
# runs it, assigning a dedicated agent identity + endpoint.
|
|
12
|
+
#
|
|
13
|
+
# Why a custom image: azure-foundry deploys are container-only (the deploy
|
|
14
|
+
# provider rejects Node zip), and a twak wallet additionally shells out to the
|
|
15
|
+
# `twak` CLI (npm @trustwallet/cli) for every signing / on-chain intent, so
|
|
16
|
+
# the image must carry it globally.
|
|
17
|
+
FROM docker.io/library/node:22-slim
|
|
18
|
+
|
|
19
|
+
# The pinned twak CLI + pnpm.
|
|
20
|
+
#
|
|
21
|
+
# ARCH WARNING: `npm install` MUST run with this image's own node (as below).
|
|
22
|
+
# Installing on the host (or any other arch) and COPYing node_modules in
|
|
23
|
+
# breaks at runtime: twak's `@napi-rs/keyring` native binding is selected at
|
|
24
|
+
# install time per-arch (known npm optional-deps bug) — a cross-arch-installed
|
|
25
|
+
# tree crashes at runtime. Multi-arch images must install per-arch too.
|
|
26
|
+
RUN npm install -g @trustwallet/cli@{{TWAK_CLI_VERSION}} pnpm@{{PNPM_VERSION}} \
|
|
27
|
+
&& npm cache clean --force
|
|
28
|
+
|
|
29
|
+
WORKDIR /app
|
|
30
|
+
# Install deps first (layer cache), then build. The lockfile is copied when
|
|
31
|
+
# present; a project without one falls back to a plain install. (No BuildKit
|
|
32
|
+
# cache mounts here — see the classic-builder note above.)
|
|
33
|
+
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
|
|
34
|
+
COPY vendor ./vendor
|
|
35
|
+
RUN pnpm install
|
|
36
|
+
|
|
37
|
+
COPY . .
|
|
38
|
+
RUN pnpm build
|
|
39
|
+
|
|
40
|
+
# Runtime deps only in the final tree (drops typescript etc.).
|
|
41
|
+
RUN pnpm prune --prod
|
|
42
|
+
|
|
43
|
+
# {{CONTAINER_PORT}} / {{ENTRYPOINT}} are rendered by `bag init` from the
|
|
44
|
+
# provider contract (Foundry uses 8088 + dist/foundryMain.js).
|
|
45
|
+
ENV AGENT_PORT={{CONTAINER_PORT}}
|
|
46
|
+
EXPOSE {{CONTAINER_PORT}}
|
|
47
|
+
CMD ["node", "{{ENTRYPOINT}}"]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A AgentCard — the seller agent's outward, discoverable identity.
|
|
3
|
+
*
|
|
4
|
+
* Built by `main.ts` and served at `/.well-known/agent-card.json`. On
|
|
5
|
+
* AgentCore, `$AGENTCORE_RUNTIME_URL` overrides `card.url` at boot;
|
|
6
|
+
* azure-foundry/local runs keep the client-routable local placeholder.
|
|
7
|
+
*
|
|
8
|
+
* The card advertises exactly two skills — `negotiate` and `notify_funded` —
|
|
9
|
+
* and the OAuth2 (Cognito) security scheme buyers must satisfy when deployed
|
|
10
|
+
* behind AgentCore: A2A endpoints require an inbound OAuth2 bearer (there is
|
|
11
|
+
* no anonymous mode).
|
|
12
|
+
* The token URL + scope come from the Cognito user pool
|
|
13
|
+
* `bag deploy provision-cognito` creates (env `OAUTH_TOKEN_URL` /
|
|
14
|
+
* `OAUTH_SCOPE`, injected at deploy); the runtime's inbound JWT authorizer
|
|
15
|
+
* validates the same pool. Locally (no Cognito env) the card omits the scheme
|
|
16
|
+
* so `bag dev` is reachable without a token.
|
|
17
|
+
*
|
|
18
|
+
* You own this file — edit the skill descriptions / card metadata for your
|
|
19
|
+
* seller.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { AgentCard, AgentSkill, SecurityScheme } from "@a2a-js/sdk";
|
|
23
|
+
import { loadStudioToml } from "@bnbagent/studio-runtime/config";
|
|
24
|
+
|
|
25
|
+
const NEGOTIATE: AgentSkill = {
|
|
26
|
+
id: "negotiate",
|
|
27
|
+
name: "Negotiate an ERC-8183 job",
|
|
28
|
+
description:
|
|
29
|
+
'Send a data part {"skill": "negotiate", "task_description": "...", ' +
|
|
30
|
+
'"terms": {"deliverables": "...", "quality_standards": "..."}} (both ' +
|
|
31
|
+
"terms keys are REQUIRED) and receive a " +
|
|
32
|
+
"wallet-signed price quote (price, currency, negotiation_hash, provider_sig). " +
|
|
33
|
+
"Anchor the returned envelope on-chain via createJob + fund, then send the " +
|
|
34
|
+
"`notify_funded` skill with the job_id to request delivery.",
|
|
35
|
+
tags: ["erc8183", "negotiation", "bnb-chain"],
|
|
36
|
+
inputModes: ["application/json"],
|
|
37
|
+
outputModes: ["application/json"],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const NOTIFY_FUNDED: AgentSkill = {
|
|
41
|
+
id: "notify_funded",
|
|
42
|
+
name: "Notify the seller a job is funded (request delivery)",
|
|
43
|
+
description:
|
|
44
|
+
'After you fund the job on-chain, send {"skill": "notify_funded", ' +
|
|
45
|
+
'"job_id": <int>} to tell the seller "I funded job X — please deliver". ' +
|
|
46
|
+
"The seller verifies the funded job carries its signed quote and replies " +
|
|
47
|
+
'AT ONCE with {"status": "accepted"|"rejected", "job_id"}; delivery then ' +
|
|
48
|
+
"runs in the background (work takes time). Do NOT wait on this call for " +
|
|
49
|
+
"the result — read the deliverable back from the CHAIN once the job " +
|
|
50
|
+
"reaches SUBMITTED (the `submit` tx carries the deliverable_url; " +
|
|
51
|
+
"ERC-8183 `get_deliverable_url`). The agent serves no job-query endpoint.",
|
|
52
|
+
tags: ["erc8183", "delivery", "bnb-chain"],
|
|
53
|
+
inputModes: ["application/json"],
|
|
54
|
+
outputModes: ["application/json"],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Card name from studio.toml `[project].name` (best-effort). */
|
|
58
|
+
function agentName(): string {
|
|
59
|
+
let name = "";
|
|
60
|
+
try {
|
|
61
|
+
const cfg = loadStudioToml();
|
|
62
|
+
name = String(
|
|
63
|
+
((cfg.project ?? {}) as Record<string, unknown>).name ?? "",
|
|
64
|
+
);
|
|
65
|
+
} catch {
|
|
66
|
+
// a card label must never break boot
|
|
67
|
+
}
|
|
68
|
+
return name || "bnbagent-seller";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* OAuth2 (Cognito client-credentials) scheme from env, or null locally.
|
|
73
|
+
*
|
|
74
|
+
* `bag deploy provision-cognito` emits a Cognito user pool + app client and
|
|
75
|
+
* injects `OAUTH_TOKEN_URL` + `OAUTH_SCOPE`; the AgentCore runtime's inbound
|
|
76
|
+
* JWT authorizer is wired to the same pool. Absent (local `bag dev`) →
|
|
77
|
+
* return null so the card advertises no auth requirement.
|
|
78
|
+
*/
|
|
79
|
+
function oauth2Scheme(): SecurityScheme | null {
|
|
80
|
+
const tokenUrl = process.env.OAUTH_TOKEN_URL;
|
|
81
|
+
const scope = process.env.OAUTH_SCOPE;
|
|
82
|
+
if (!tokenUrl || !scope) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
type: "oauth2",
|
|
87
|
+
flows: {
|
|
88
|
+
clientCredentials: {
|
|
89
|
+
tokenUrl,
|
|
90
|
+
scopes: { [scope]: "Invoke the seller agent" },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Build the A2A AgentCard advertising the two seller skills (+ OAuth2 if set). */
|
|
97
|
+
export function buildAgentCard(): AgentCard {
|
|
98
|
+
const name = agentName();
|
|
99
|
+
const extra: Partial<AgentCard> = {};
|
|
100
|
+
const scheme = oauth2Scheme();
|
|
101
|
+
if (scheme !== null) {
|
|
102
|
+
const scope = process.env.OAUTH_SCOPE as string;
|
|
103
|
+
extra.securitySchemes = { oauth2: scheme };
|
|
104
|
+
extra.security = [{ oauth2: [scope] }];
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
name,
|
|
108
|
+
description: `ERC-8183 seller agent (${name}) — negotiate + notify_funded over A2A.`,
|
|
109
|
+
// $AGENTCORE_RUNTIME_URL wins when deployed behind AgentCore.
|
|
110
|
+
// Local-dev fallback: a client-routable localhost URL (not the 0.0.0.0
|
|
111
|
+
// bind address). Host via AGENT_HOST (default localhost); port via the
|
|
112
|
+
// same PORT → AGENT_PORT → 9000 resolution main.ts serves on.
|
|
113
|
+
url:
|
|
114
|
+
process.env.AGENTCORE_RUNTIME_URL ??
|
|
115
|
+
`http://${process.env.AGENT_HOST ?? "localhost"}:${process.env.PORT || process.env.AGENT_PORT || "9000"}/`,
|
|
116
|
+
version: "1.0.0",
|
|
117
|
+
protocolVersion: "0.3.0",
|
|
118
|
+
preferredTransport: "JSONRPC",
|
|
119
|
+
// Non-streaming: negotiate / notify_funded are request/response
|
|
120
|
+
// (message/send). Do NOT flip this on to satisfy the AgentCore
|
|
121
|
+
// inspector's chat box — that box can't drive a seller agent (it can
|
|
122
|
+
// only send plain text, never the {"skill": ...} DataPart these skills
|
|
123
|
+
// require, and its streaming view expects Task events). Test locally
|
|
124
|
+
// with curl / an A2A client sending a DataPart (see the operating skill).
|
|
125
|
+
capabilities: { streaming: false },
|
|
126
|
+
defaultInputModes: ["application/json"],
|
|
127
|
+
defaultOutputModes: ["application/json"],
|
|
128
|
+
skills: [NEGOTIATE, NOTIFY_FUNDED],
|
|
129
|
+
...extra,
|
|
130
|
+
};
|
|
131
|
+
}
|