@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,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
+ }