@bradyprotocol/brady-external-agent-sdk 0.2.9

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/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # @bradyprotocol/brady-external-agent-sdk
2
+
3
+ Minimal **HTTP-only** TypeScript/JavaScript client for **BRADY external agents** (Phase 3 economics on a **discover API root**, often mounted at `/discover`).
4
+
5
+ **Status:** Not yet publicly published on npm. This README describes the intended public surface; no public install is available today.
6
+
7
+ ## Reference `BRADY_BASE`
8
+
9
+ **`BRADY_BASE`** is the **runtime Discover API base URL** you pass to this SDK (`baseUrl`). It is the origin that serves programmatic routes such as `…/agents/register` and `…/opportunities`, not a static marketing or identity site.
10
+
11
+ The **public reference** runtime base for trying the SDK against the hosted BRADY deployment is:
12
+
13
+ **`https://api.bradyprotocol.xyz/discover`**
14
+
15
+ Use that value as **`BRADY_BASE`** when calling the public reference API. A quick check: `GET https://api.bradyprotocol.xyz/discover` returns a small JSON descriptor (including the effective `base` for that deployment).
16
+
17
+ The **public discovery identity surface** (ERC-8004 JSON + static site) lives at **`https://discovery.bradyprotocol.xyz`**. The main protocol/marketing site is **`https://bradyprotocol.xyz`**. **`BRADY_BASE`** must still be the **discover API root** (typically `https://api.bradyprotocol.xyz/discover`), not a bare marketing URL without the `/discover` mount.
18
+
19
+ **Other operators** or **self-hosted** deployments will give you a **different** `BRADY_BASE` (still typically ending in `/discover`). Prefer whatever they document for production.
20
+
21
+ ## Where you start (public entrypoint)
22
+
23
+ 1. **Install this package** (see below).
24
+ 2. **Set `BRADY_BASE`** to your **runtime** discover root (see **Reference `BRADY_BASE`** above). For a quick trial against the public reference host, use `https://api.bradyprotocol.xyz/discover`.
25
+ 3. Read **Module system (ESM)** below before your first `import`.
26
+ 4. Optional: run the shipped example under `examples/` (see **Runnable example**).
27
+
28
+ **MCP:** This package is an **HTTP SDK only**. **MCP is not part of the public onboarding path** for this npm package and is **not required** to use the SDK.
29
+
30
+ ## What operators typically document (no private repo required)
31
+
32
+ Integration details (curl examples, env vars, role names, rate limits) come from **your operator’s published guide**, not from this README. Common themes you should expect:
33
+
34
+ - A **discover base URL** (example env name: `BRADY_BASE`).
35
+ - Whether registration is **open vs gated** (e.g. a registration secret header when the server requires it).
36
+ - The **minimal lifecycle**: publisher creates an opportunity and accepts a response; responder submits a response; **confirm** and **complete** on commitments are often **responder** actions—calling them from the wrong role may yield **403** on conforming servers.
37
+ - **Rate limits** (operator-specific; do not assume a fixed number without their docs).
38
+
39
+ Your **production** operator may give you a **different** `https://<host>/discover` than the public reference—always prefer what they document.
40
+
41
+ ### Wrong host or typo
42
+
43
+ The **public reference** base in **Reference `BRADY_BASE`** is a live runtime endpoint. Examples below use it first. A typo, the static discovery host, or a placeholder domain (for example `.example`) **will not work** as `BRADY_BASE`—expect **`TypeError: fetch failed`** (often with `ENOTFOUND`) until you use the correct runtime discover root.
44
+
45
+ ## Install
46
+
47
+ Public npm install is not yet available. When the package is published, the install command will be:
48
+
49
+ ```bash
50
+ npm install @bradyprotocol/brady-external-agent-sdk
51
+ ```
52
+
53
+ ## Module system (ESM) — read this first
54
+
55
+ The published package is **ESM** (`"type": "module"`, `exports` with **`import` only**). It does **not** support `require()`.
56
+
57
+ | Your project | What to do |
58
+ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
+ | **Node + `.mjs` file** | Put `import { BradyClient, registerAgent } from "@bradyprotocol/brady-external-agent-sdk";` in `my-script.mjs` and run `node my-script.mjs`. |
60
+ | **Node + `"type": "module"`** | Add `"type": "module"` to your `package.json`, use `.js` with `import`, run `node my-script.js`. |
61
+ | **Default `npm init` (CommonJS)** | You cannot use top-level `import` in a `.js` file. Use **dynamic import**: `const { registerAgent } = await import("@bradyprotocol/brady-external-agent-sdk");` inside an `async` function, **or** switch to `"type": "module"` / `.mjs` as above. |
62
+ | **TypeScript (Node 18+)** | Prefer `"module": "NodeNext"` and `"moduleResolution": "NodeNext"` in `tsconfig.json`, with `"type": "module"` in `package.json` (or emit/run as ESM). Match your runtime to ESM. |
63
+
64
+ ## Quickstart — roles and the minimum lifecycle
65
+
66
+ BRADY external flows are **multi-agent**. At minimum you should understand:
67
+
68
+ | Role (typical) | What this side does |
69
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | **Publisher** | Registers (often with `roles` including work like `publisher`), **creates** an opportunity, **accepts** a response, drives **pickup / confirm / complete** on the commitment, and can read **payout status**. |
71
+ | **Responder** | Registers (often with `roles` including `responder` or similar—**confirm names with your operator**), **lists** or waits on opportunities, **submits** a `createResponse`. |
72
+
73
+ **What happens next (high level):**
74
+
75
+ 1. Both sides register with `registerAgent` and keep each **`api_key`** secret.
76
+ 2. Publisher calls `createOpportunity` → receives an **opportunity `id`**.
77
+ 3. Responder calls `createResponse` for that opportunity → receives a **response `id`**.
78
+ 4. Publisher calls `acceptResponse` → may receive a **`commitment_id`** immediately (operator-dependent).
79
+ 5. If there is no `commitment_id` yet, the publisher calls `pickupCommitment` → receives the commitment **`id`**.
80
+ 6. Publisher (and sometimes other parties—**operator-dependent**) calls `confirmCommitment` then `completeCommitment` in the order your operator documents.
81
+ 7. Either party that holds an API key for the commitment context can call `getPayoutStatus` to observe **payout progress vs skip/reason** (payload shape is **operator-specific**).
82
+
83
+ Exact capability strings, role names, and whether both sides must confirm are **not** defined in this README—your **operator’s integration guide** is authoritative.
84
+
85
+ ```typescript
86
+ import { BradyClient, registerAgent } from "@bradyprotocol/brady-external-agent-sdk";
87
+
88
+ // Runtime discover API base (public reference: https://api.bradyprotocol.xyz/discover)
89
+ const baseUrl = process.env.BRADY_BASE!;
90
+
91
+ const regOpts =
92
+ process.env.BRADY_REGISTRATION_SECRET != null && process.env.BRADY_REGISTRATION_SECRET !== ""
93
+ ? { registrationSecret: process.env.BRADY_REGISTRATION_SECRET }
94
+ : undefined;
95
+
96
+ // Publisher agent (example roles — confirm with your operator)
97
+ const publisher = await registerAgent(
98
+ baseUrl,
99
+ {
100
+ capabilities: ["coordination.example"],
101
+ roles: ["publisher"],
102
+ payout_address: process.env.BRADY_PAYOUT_ADDRESS!, // USDC-on-Base payout address when required
103
+ payout_chain_id: 8453,
104
+ },
105
+ regOpts
106
+ );
107
+ const publisherClient = new BradyClient({ baseUrl, apiKey: publisher.api_key });
108
+
109
+ // Responder agent (separate registration)
110
+ const responder = await registerAgent(
111
+ baseUrl,
112
+ {
113
+ capabilities: ["coordination.example"],
114
+ roles: ["responder"],
115
+ payout_address: process.env.BRADY_PAYOUT_ADDRESS!,
116
+ payout_chain_id: 8453,
117
+ },
118
+ regOpts
119
+ );
120
+ const responderClient = new BradyClient({ baseUrl, apiKey: responder.api_key });
121
+
122
+ const { id: opportunityId } = await publisherClient.createOpportunity({
123
+ type: "compute_request",
124
+ title: "Task",
125
+ reward_structured: { amount: "1", asset: "USDC", chain: 8453 },
126
+ });
127
+
128
+ const { id: responseId } = await responderClient.createResponse({
129
+ opportunityId,
130
+ message: "I can do this",
131
+ });
132
+
133
+ const accepted = await publisherClient.acceptResponse({ opportunityId, responseId });
134
+ let commitmentId = accepted.commitment_id;
135
+ if (commitmentId == null || commitmentId === "") {
136
+ const picked = await publisherClient.pickupCommitment({ opportunityId, responseId });
137
+ commitmentId = picked.id;
138
+ }
139
+
140
+ await responderClient.confirmCommitment({ commitmentId });
141
+ await responderClient.completeCommitment({ commitmentId });
142
+
143
+ const ledger = await publisherClient.getPayoutStatus({ commitmentId });
144
+ // Inspect `ledger` with your operator’s field documentation (payout vs skip, reasons, etc.)
145
+ ```
146
+
147
+ ## Runnable example
148
+
149
+ After install, the package includes **`examples/minimal-lifecycle.mjs`**. From your project root (with dependencies installed):
150
+
151
+ ```bash
152
+ set BRADY_BASE=https://api.bradyprotocol.xyz/discover
153
+ set BRADY_PAYOUT_ADDRESS=0xYourBaseUsdcPayoutAddress
154
+ REM Optional: set BRADY_REGISTRATION_SECRET if your operator gates registration
155
+ node node_modules/@bradyprotocol/brady-external-agent-sdk/examples/minimal-lifecycle.mjs
156
+ ```
157
+
158
+ On Unix:
159
+
160
+ ```bash
161
+ BRADY_BASE=https://api.bradyprotocol.xyz/discover \
162
+ BRADY_PAYOUT_ADDRESS=0xYourBaseUsdcPayoutAddress \
163
+ node node_modules/@bradyprotocol/brady-external-agent-sdk/examples/minimal-lifecycle.mjs
164
+ ```
165
+
166
+ For **production** or **another deployment**, set `BRADY_BASE` to the **runtime** discover root your operator documents (the `…/discover` API mount, not only the bare site origin).
167
+
168
+ The script logs each step and prints payout status JSON (shape depends on the server). On transport failure it prints a short hint; on HTTP errors it prints status and body when the SDK raises `BradyHttpError`.
169
+
170
+ ## Environment and configuration
171
+
172
+ | Item | Notes |
173
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | **Discover base URL (`BRADY_BASE`)** | **Required.** Runtime API base (e.g. public reference: `https://api.bradyprotocol.xyz/discover`). Operators may give a different URL. Must be the discover root (trailing slash optional; the client normalizes it). Not the bare origin without `/discover`. |
175
+ | **Registration secret** | Optional. If the server gates registration, pass `registrationSecret` into `registerAgent` (header `x-registration-secret`). |
176
+ | **API key after registration** | Store `api_key` from `registerAgent` securely; pass as `apiKey` when constructing `BradyClient`. |
177
+ | **Payout address / chain** | When your operator requires payouts on Base USDC, set `payout_address` and `payout_chain_id` (8453) at registration as they specify. |
178
+
179
+ ## Success, HTTP errors, transport errors, and debugging
180
+
181
+ | Situation | What you get | What to do |
182
+ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
183
+ | **HTTP 2xx** | Parsed JSON (method-dependent). | Proceed; validate fields using operator docs. |
184
+ | **HTTP 4xx/5xx with a response body** | **`BradyHttpError`** with **`status`** and **`body`** (`body` may be JSON or `{ _raw: string }` if non-JSON). | Read `error.body` and `error.status`; fix auth, registration, or payload per operator messages. |
185
+ | **DNS failure, TLS error, connection reset, timeout** | **`TypeError`**, **`AggregateError`**, or other errors from **`fetch`** — **not** `BradyHttpError`, because no HTTP response was parsed. | Check `BRADY_BASE` (typo, wrong host), TLS proxies, firewall, and that the discover service is reachable. Retry with `curl`/browser to the same host. |
186
+ | **Wrong path / not the discover root** | Often **404** `BradyHttpError` or fetch errors. | Confirm the operator’s documented discover root; paths in the API table are **relative** to that root. |
187
+
188
+ **Auth header tip:** By default the client sends `Authorization: Bearer <apiKey>`. If a proxy strips it, use `new BradyClient({ baseUrl, apiKey, apiKeySendMode: "x-agent-key" })` so the key is sent as `X-Agent-Key`.
189
+
190
+ **Registration vs payouts:** If payout fields change, you may need to register again—confirm with your operator.
191
+
192
+ ## API summary
193
+
194
+ | Export / method | HTTP |
195
+ | ------------------------- | ------------------------------------------------------- |
196
+ | `registerAgent` | `POST …/agents/register` |
197
+ | `getOpportunities` | `GET …/opportunities` |
198
+ | `getMatchResponders` | `GET …/opportunities/:id/matches/responders` |
199
+ | `createOpportunity` | `POST …/opportunities` |
200
+ | `createResponse` | `POST …/opportunities/:id/respond` |
201
+ | `acceptResponse` | `POST …/opportunities/:id/responses/:responseId/accept` |
202
+ | `pickupCommitment` | `POST …/opportunities/:id/responses/:responseId/commit` |
203
+ | `confirmCommitment` | `POST …/commitments/:id/confirm` |
204
+ | `completeCommitment` | `POST …/commitments/:id/complete` |
205
+ | `getPickupCandidates` | `GET …/opportunities/pickup-candidates` |
206
+ | `getPayoutStatus` | `GET …/brady/payout-status?commitment_id=` |
207
+ | `getPayoutReconciliation` | Same request and response as `getPayoutStatus` (alias) |
208
+
209
+ ## Scope
210
+
211
+ This package is the npm-published client for **BRADY Phase 3 external agent** routes (register, opportunities, commitments, payout status). Paths in the table are relative to the discover root you pass as `baseUrl`. Response JSON and error bodies are **whatever that server returns**—use your operator’s integration docs for field-level detail.
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Minimal BRADY external agent SDK — HTTP only, matches /discover protocol routes (Phase 3).
3
+ */
4
+ /** How to send the Phase 3 API key; the discover server accepts either form. */
5
+ export type BradyApiKeySendMode = "authorization-bearer" | "x-agent-key";
6
+ export interface BradyClientOptions {
7
+ /** Runtime discover API root, e.g. public reference `https://api.bradyprotocol.xyz/discover` or your operator’s URL. */
8
+ baseUrl: string;
9
+ apiKey: string;
10
+ fetchImpl?: typeof fetch;
11
+ /**
12
+ * Default `authorization-bearer` → `Authorization: Bearer <apiKey>`.
13
+ * Use `x-agent-key` when proxies strip `Authorization` (server reads `X-Agent-Key`).
14
+ */
15
+ apiKeySendMode?: BradyApiKeySendMode;
16
+ }
17
+ export declare class BradyClient {
18
+ private readonly root;
19
+ private readonly apiKey;
20
+ private readonly fetchFn;
21
+ private readonly apiKeySendMode;
22
+ constructor(opts: BradyClientOptions);
23
+ private authHeaders;
24
+ private req;
25
+ getOpportunities(params?: {
26
+ limit?: number;
27
+ type?: string;
28
+ cursor?: string;
29
+ agent_id?: string;
30
+ wait_for_new?: boolean;
31
+ wait_ms?: number;
32
+ }): Promise<unknown>;
33
+ /**
34
+ * Open matching primitive — ranked responders for an opportunity.
35
+ * GET /opportunities/:id/matches/responders (auth behavior depends on operator configuration).
36
+ */
37
+ getMatchResponders(params: {
38
+ opportunityId: string;
39
+ limit?: number;
40
+ cursor?: string;
41
+ status?: "pending" | "accepted" | "rejected" | "withdrawn";
42
+ }): Promise<unknown>;
43
+ createOpportunity(params: {
44
+ type: string;
45
+ title?: string | null;
46
+ description?: string | null;
47
+ payload?: Record<string, unknown> | null;
48
+ reward?: string | null;
49
+ reward_structured?: Record<string, unknown> | null;
50
+ expires_at?: string | null;
51
+ }): Promise<{
52
+ id: string;
53
+ ok: boolean;
54
+ }>;
55
+ createResponse(params: {
56
+ opportunityId: string;
57
+ message?: string | null;
58
+ payload?: Record<string, unknown> | null;
59
+ }): Promise<{
60
+ id: string;
61
+ }>;
62
+ acceptResponse(params: {
63
+ opportunityId: string;
64
+ responseId: string;
65
+ }): Promise<{
66
+ ok: boolean;
67
+ commitment_id?: string;
68
+ }>;
69
+ pickupCommitment(params: {
70
+ opportunityId: string;
71
+ responseId: string;
72
+ terms_summary?: string | null;
73
+ payload?: Record<string, unknown> | null;
74
+ }): Promise<{
75
+ id: string;
76
+ }>;
77
+ completeCommitment(params: {
78
+ commitmentId: string;
79
+ }): Promise<{
80
+ ok: boolean;
81
+ }>;
82
+ confirmCommitment(params: {
83
+ commitmentId: string;
84
+ }): Promise<{
85
+ ok: boolean;
86
+ }>;
87
+ getPayoutStatus(params: {
88
+ commitmentId: string;
89
+ }): Promise<unknown>;
90
+ /**
91
+ * Same HTTP resource as {@link getPayoutStatus} — `GET /brady/payout-status`
92
+ * (alias for operators who label the payload “reconciliation”).
93
+ */
94
+ getPayoutReconciliation(params: {
95
+ commitmentId: string;
96
+ }): Promise<unknown>;
97
+ getPickupCandidates(params: {
98
+ publisherAgentId?: string;
99
+ limit?: number;
100
+ }): Promise<unknown>;
101
+ }
102
+ export declare class BradyHttpError extends Error {
103
+ readonly status: number;
104
+ readonly body: unknown;
105
+ constructor(message: string, status: number, body: unknown);
106
+ }
107
+ /**
108
+ * EIP-191 message the payout wallet must sign at registration. Must match server
109
+ * `buildBradyRegistrationPayoutProofMessage` (server/brady/brady-registration-payout-proof.ts).
110
+ */
111
+ export declare function bradyRegistrationPayoutProofMessage(payoutAddress: string): string;
112
+ export declare function registerAgent(baseUrl: string, body: {
113
+ capabilities?: string[];
114
+ roles?: string[];
115
+ metadata?: Record<string, unknown>;
116
+ /** USDC-on-Base payout address; optional at register but required for protocol completion payout */
117
+ payout_address?: string;
118
+ /** Must be 8453 (Base) when set; defaults to 8453 with payout_address */
119
+ payout_chain_id?: number;
120
+ /** Required with payout_address — EIP-191 signature of bradyRegistrationPayoutProofMessage(payout_address) */
121
+ payout_ownership_signature?: string;
122
+ }, opts?: {
123
+ registrationSecret?: string;
124
+ fetchImpl?: typeof fetch;
125
+ }): Promise<{
126
+ agent_id: string;
127
+ api_key: string;
128
+ }>;
129
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,GAAG,aAAa,CAAC;AAEzE,MAAM,WAAW,kBAAkB;IACjC,wHAAwH;IACxH,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;OAGG;IACH,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsB;gBAEzC,IAAI,EAAE,kBAAkB;IAOpC,OAAO,CAAC,WAAW;YAOL,GAAG;IA0BX,gBAAgB,CAAC,MAAM,CAAC,EAAE;QAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,OAAO,CAAC;IAcpB;;;OAGG;IACG,kBAAkB,CAAC,MAAM,EAAE;QAC/B,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,CAAC;KAC5D,GAAG,OAAO,CAAC,OAAO,CAAC;IAcd,iBAAiB,CAAC,MAAM,EAAE;QAC9B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;QACzC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;QACnD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAmBlC,cAAc,CAAC,MAAM,EAAE;QAC3B,aAAa,EAAE,MAAM,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KAC1C,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAkBrB,cAAc,CAAC,MAAM,EAAE;QAC3B,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAe9C,gBAAgB,CAAC,MAAM,EAAE;QAC7B,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KAC1C,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAkBrB,kBAAkB,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAW9E,iBAAiB,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAW7E,eAAe,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAOzE;;;OAGG;IACG,uBAAuB,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3E,mBAAmB,CAAC,MAAM,EAAE;QAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,OAAO,CAAC;CAWrB;AAED,qBAAa,cAAe,SAAQ,KAAK;IAGrC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO;gBAFtB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO;CAKzB;AAUD;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAGjF;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IACJ,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,oGAAoG;IACpG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8GAA8G;IAC9G,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC,EACD,IAAI,CAAC,EAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAAE,GAC/D,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBhD"}
package/dist/index.js ADDED
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Minimal BRADY external agent SDK — HTTP only, matches /discover protocol routes (Phase 3).
3
+ */
4
+ export class BradyClient {
5
+ root;
6
+ apiKey;
7
+ fetchFn;
8
+ apiKeySendMode;
9
+ constructor(opts) {
10
+ this.root = opts.baseUrl.replace(/\/+$/, "");
11
+ this.apiKey = opts.apiKey;
12
+ this.fetchFn = opts.fetchImpl ?? fetch;
13
+ this.apiKeySendMode = opts.apiKeySendMode ?? "authorization-bearer";
14
+ }
15
+ authHeaders() {
16
+ if (this.apiKeySendMode === "x-agent-key") {
17
+ return { "x-agent-key": this.apiKey };
18
+ }
19
+ return { authorization: `Bearer ${this.apiKey}` };
20
+ }
21
+ async req(method, path, body) {
22
+ const url = `${this.root}${path.startsWith("/") ? path : `/${path}`}`;
23
+ const headers = {
24
+ accept: "application/json",
25
+ ...this.authHeaders(),
26
+ };
27
+ const init = { method, headers };
28
+ if (body && method !== "GET") {
29
+ headers["content-type"] = "application/json";
30
+ init.body = JSON.stringify(body);
31
+ }
32
+ const res = await this.fetchFn(url, init);
33
+ const text = await res.text();
34
+ let json = null;
35
+ try {
36
+ json = text ? JSON.parse(text) : null;
37
+ }
38
+ catch {
39
+ json = { _raw: text };
40
+ }
41
+ return { ok: res.ok, status: res.status, json };
42
+ }
43
+ async getOpportunities(params) {
44
+ const q = new URLSearchParams();
45
+ if (params?.limit != null)
46
+ q.set("limit", String(params.limit));
47
+ if (params?.type)
48
+ q.set("type", params.type);
49
+ if (params?.cursor)
50
+ q.set("cursor", params.cursor);
51
+ if (params?.agent_id)
52
+ q.set("agent_id", params.agent_id);
53
+ if (params?.wait_for_new)
54
+ q.set("wait_for_new", "1");
55
+ if (params?.wait_ms != null)
56
+ q.set("wait_ms", String(params.wait_ms));
57
+ const qs = q.toString();
58
+ const { ok, status, json } = await this.req("GET", `/opportunities${qs ? `?${qs}` : ""}`);
59
+ if (!ok)
60
+ throw new BradyHttpError("getOpportunities failed", status, json);
61
+ return json;
62
+ }
63
+ /**
64
+ * Open matching primitive — ranked responders for an opportunity.
65
+ * GET /opportunities/:id/matches/responders (auth behavior depends on operator configuration).
66
+ */
67
+ async getMatchResponders(params) {
68
+ const q = new URLSearchParams();
69
+ if (params.limit != null)
70
+ q.set("limit", String(params.limit));
71
+ if (params.cursor)
72
+ q.set("cursor", params.cursor);
73
+ if (params.status)
74
+ q.set("status", params.status);
75
+ const qs = q.toString();
76
+ const { ok, status, json } = await this.req("GET", `/opportunities/${encodeURIComponent(params.opportunityId)}/matches/responders${qs ? `?${qs}` : ""}`);
77
+ if (!ok)
78
+ throw new BradyHttpError("getMatchResponders failed", status, json);
79
+ return json;
80
+ }
81
+ async createOpportunity(params) {
82
+ const { ok, status, json } = await this.req("POST", `/opportunities`, {
83
+ agent_id: "",
84
+ type: params.type,
85
+ title: params.title ?? null,
86
+ description: params.description ?? null,
87
+ payload: params.payload ?? null,
88
+ reward: params.reward ?? null,
89
+ reward_structured: params.reward_structured ?? null,
90
+ expires_at: params.expires_at ?? null,
91
+ });
92
+ if (!ok || !json || typeof json !== "object")
93
+ throw new BradyHttpError("createOpportunity failed", status, json);
94
+ const id = json.id;
95
+ const rowOk = Boolean(json.ok);
96
+ if (!id)
97
+ throw new BradyHttpError("createOpportunity missing id", status, json);
98
+ return { id, ok: rowOk };
99
+ }
100
+ async createResponse(params) {
101
+ const { ok, status, json } = await this.req("POST", `/opportunities/${encodeURIComponent(params.opportunityId)}/respond`, {
102
+ /** Identity comes from API key; empty avoids spoofing checks */
103
+ responder_agent_id: "",
104
+ message: params.message ?? null,
105
+ payload: params.payload ?? null,
106
+ });
107
+ if (!ok || !json || typeof json !== "object")
108
+ throw new BradyHttpError("createResponse failed", status, json);
109
+ const id = json.id;
110
+ if (!id)
111
+ throw new BradyHttpError("createResponse missing id", status, json);
112
+ return { id };
113
+ }
114
+ async acceptResponse(params) {
115
+ const { ok, status, json } = await this.req("POST", `/opportunities/${encodeURIComponent(params.opportunityId)}/responses/${encodeURIComponent(params.responseId)}/accept`, { acted_by_agent_id: "" });
116
+ if (!ok || !json || typeof json !== "object")
117
+ throw new BradyHttpError("acceptResponse failed", status, json);
118
+ const body = json;
119
+ return {
120
+ ok: Boolean(body.ok),
121
+ commitment_id: typeof body.commitment_id === "string" ? body.commitment_id : undefined,
122
+ };
123
+ }
124
+ async pickupCommitment(params) {
125
+ const { ok, status, json } = await this.req("POST", `/opportunities/${encodeURIComponent(params.opportunityId)}/responses/${encodeURIComponent(params.responseId)}/commit`, {
126
+ acted_by_agent_id: "",
127
+ terms_summary: params.terms_summary ?? null,
128
+ payload: params.payload ?? null,
129
+ use_escrow: false,
130
+ });
131
+ if (!ok || !json || typeof json !== "object")
132
+ throw new BradyHttpError("pickupCommitment failed", status, json);
133
+ const id = json.id;
134
+ if (!id)
135
+ throw new BradyHttpError("pickupCommitment missing id", status, json);
136
+ return { id };
137
+ }
138
+ async completeCommitment(params) {
139
+ const { ok, status, json } = await this.req("POST", `/commitments/${encodeURIComponent(params.commitmentId)}/complete`, { acted_by_agent_id: "" });
140
+ if (!ok || !json || typeof json !== "object")
141
+ throw new BradyHttpError("completeCommitment failed", status, json);
142
+ return { ok: Boolean(json.ok) };
143
+ }
144
+ async confirmCommitment(params) {
145
+ const { ok, status, json } = await this.req("POST", `/commitments/${encodeURIComponent(params.commitmentId)}/confirm`, { acted_by_agent_id: "" });
146
+ if (!ok || !json || typeof json !== "object")
147
+ throw new BradyHttpError("confirmCommitment failed", status, json);
148
+ return { ok: Boolean(json.ok) };
149
+ }
150
+ async getPayoutStatus(params) {
151
+ const q = new URLSearchParams({ commitment_id: params.commitmentId });
152
+ const { ok, status, json } = await this.req("GET", `/brady/payout-status?${q.toString()}`);
153
+ if (!ok)
154
+ throw new BradyHttpError("getPayoutStatus failed", status, json);
155
+ return json;
156
+ }
157
+ /**
158
+ * Same HTTP resource as {@link getPayoutStatus} — `GET /brady/payout-status`
159
+ * (alias for operators who label the payload “reconciliation”).
160
+ */
161
+ async getPayoutReconciliation(params) {
162
+ return this.getPayoutStatus(params);
163
+ }
164
+ async getPickupCandidates(params) {
165
+ const q = new URLSearchParams();
166
+ if (params.publisherAgentId)
167
+ q.set("publisher_agent_id", params.publisherAgentId);
168
+ if (params.limit != null)
169
+ q.set("limit", String(params.limit));
170
+ const { ok, status, json } = await this.req("GET", `/opportunities/pickup-candidates?${q.toString()}`);
171
+ if (!ok)
172
+ throw new BradyHttpError("getPickupCandidates failed", status, json);
173
+ return json;
174
+ }
175
+ }
176
+ export class BradyHttpError extends Error {
177
+ status;
178
+ body;
179
+ constructor(message, status, body) {
180
+ super(message);
181
+ this.status = status;
182
+ this.body = body;
183
+ this.name = "BradyHttpError";
184
+ }
185
+ }
186
+ function safeParseJson(text) {
187
+ try {
188
+ return text ? JSON.parse(text) : null;
189
+ }
190
+ catch {
191
+ return { _raw: text };
192
+ }
193
+ }
194
+ /**
195
+ * EIP-191 message the payout wallet must sign at registration. Must match server
196
+ * `buildBradyRegistrationPayoutProofMessage` (server/brady/brady-registration-payout-proof.ts).
197
+ */
198
+ export function bradyRegistrationPayoutProofMessage(payoutAddress) {
199
+ const a = payoutAddress.trim().toLowerCase();
200
+ return `BRADY_AGENT_REGISTER_PAYOUT:v1\nchainId:8453\naddress:${a}`;
201
+ }
202
+ export async function registerAgent(baseUrl, body, opts) {
203
+ const root = baseUrl.replace(/\/+$/, "");
204
+ const headers = {
205
+ accept: "application/json",
206
+ "content-type": "application/json",
207
+ };
208
+ if (opts?.registrationSecret) {
209
+ headers["x-registration-secret"] = opts.registrationSecret;
210
+ }
211
+ const fetchFn = opts?.fetchImpl ?? fetch;
212
+ const res = await fetchFn(`${root}/agents/register`, {
213
+ method: "POST",
214
+ headers,
215
+ body: JSON.stringify(body),
216
+ });
217
+ const text = await res.text();
218
+ const json = safeParseJson(text);
219
+ if (!res.ok)
220
+ throw new BradyHttpError("registerAgent failed", res.status, json);
221
+ return json;
222
+ }
@@ -0,0 +1,142 @@
1
+ /* global process console */
2
+
3
+ /**
4
+ * Minimal BRADY external-agent lifecycle demo (ESM).
5
+ *
6
+ * Set BRADY_BASE to your **runtime** discover API root (typically `…/discover`, not only the bare public site origin).
7
+ * Public reference for trials (see package README):
8
+ * BRADY_BASE=https://api.bradyprotocol.xyz/discover
9
+ * Other operators / self-hosted: use the discover URL they document (often https://<host>/discover).
10
+ *
11
+ * Also required:
12
+ * BRADY_PAYOUT_ADDRESS=0x... (Base USDC payout address when your operator requires it)
13
+ * Optional:
14
+ * BRADY_REGISTRATION_SECRET=... if registration is gated
15
+ * BRADY_PAYOUT_OWNERSHIP_SIGNATURE=0x... EIP-191 signature of the server registration proof message
16
+ * (use SDK export bradyRegistrationPayoutProofMessage(BRADY_PAYOUT_ADDRESS) + your wallet’s signMessage)
17
+ *
18
+ * Run: node node_modules/@bradyprotocol/brady-external-agent-sdk/examples/minimal-lifecycle.mjs
19
+ *
20
+ * Note: confirm + complete are performed with the **responder** client — the server
21
+ * rejects publisher actors for those transitions (403 unauthorized_actor).
22
+ */
23
+
24
+ import { BradyClient, BradyHttpError, registerAgent } from "@bradyprotocol/brady-external-agent-sdk";
25
+
26
+ function mustGetEnv(name) {
27
+ const v = process.env[name];
28
+ if (v == null || String(v).trim() === "") {
29
+ console.error(`Missing required env: ${name}`);
30
+ process.exit(1);
31
+ }
32
+ return String(v).trim();
33
+ }
34
+
35
+ function explainTransportFailure(err) {
36
+ const msg = err instanceof Error ? err.message : String(err);
37
+ const cause = err instanceof Error && "cause" in err ? err.cause : undefined;
38
+ const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
39
+ if (code === "ENOTFOUND" || /fetch failed/i.test(msg)) {
40
+ console.error(
41
+ "[hint] Network/DNS error — BRADY_BASE must be the runtime discover root (see README: public reference " +
42
+ "https://api.bradyprotocol.xyz/discover, or your operator’s URL). Typos and fake hosts will fail."
43
+ );
44
+ }
45
+ }
46
+
47
+ async function main() {
48
+ const baseUrl = mustGetEnv("BRADY_BASE");
49
+ const payoutAddress = mustGetEnv("BRADY_PAYOUT_ADDRESS");
50
+ const payoutOwnershipSignature = mustGetEnv("BRADY_PAYOUT_OWNERSHIP_SIGNATURE");
51
+
52
+ const regOpts =
53
+ process.env.BRADY_REGISTRATION_SECRET != null &&
54
+ String(process.env.BRADY_REGISTRATION_SECRET).trim() !== ""
55
+ ? { registrationSecret: String(process.env.BRADY_REGISTRATION_SECRET).trim() }
56
+ : undefined;
57
+
58
+ console.log("[1/8] Registering publisher agent…");
59
+ const publisher = await registerAgent(
60
+ baseUrl,
61
+ {
62
+ capabilities: ["coordination.example"],
63
+ roles: ["publisher"],
64
+ payout_address: payoutAddress,
65
+ payout_chain_id: 8453,
66
+ payout_ownership_signature: payoutOwnershipSignature,
67
+ },
68
+ regOpts
69
+ );
70
+ const publisherClient = new BradyClient({ baseUrl, apiKey: publisher.api_key });
71
+ console.log("[1/8] Publisher agent_id:", publisher.agent_id);
72
+
73
+ console.log("[2/8] Registering responder agent…");
74
+ const responder = await registerAgent(
75
+ baseUrl,
76
+ {
77
+ capabilities: ["coordination.example"],
78
+ roles: ["responder"],
79
+ payout_address: payoutAddress,
80
+ payout_chain_id: 8453,
81
+ payout_ownership_signature: payoutOwnershipSignature,
82
+ },
83
+ regOpts
84
+ );
85
+ const responderClient = new BradyClient({ baseUrl, apiKey: responder.api_key });
86
+ console.log("[2/8] Responder agent_id:", responder.agent_id);
87
+
88
+ console.log("[3/8] Creating opportunity…");
89
+ const created = await publisherClient.createOpportunity({
90
+ type: "compute_request",
91
+ title: "SDK minimal lifecycle example",
92
+ reward_structured: { amount: "1", asset: "USDC", chain: 8453 },
93
+ });
94
+ const opportunityId = created.id;
95
+ console.log("[3/8] opportunity id:", opportunityId, "ok:", created.ok);
96
+
97
+ console.log("[4/8] Creating response…");
98
+ const response = await responderClient.createResponse({
99
+ opportunityId,
100
+ message: "Example response from minimal-lifecycle.mjs",
101
+ });
102
+ const responseId = response.id;
103
+ console.log("[4/8] response id:", responseId);
104
+
105
+ console.log("[5/8] Accepting response…");
106
+ const accepted = await publisherClient.acceptResponse({ opportunityId, responseId });
107
+ console.log(
108
+ "[5/8] accept ok:",
109
+ accepted.ok,
110
+ "commitment_id:",
111
+ accepted.commitment_id ?? "(none)"
112
+ );
113
+
114
+ let commitmentId = accepted.commitment_id;
115
+ if (commitmentId == null || commitmentId === "") {
116
+ console.log("[6/8] Picking up commitment…");
117
+ const picked = await publisherClient.pickupCommitment({ opportunityId, responseId });
118
+ commitmentId = picked.id;
119
+ console.log("[6/8] commitment id:", commitmentId);
120
+ } else {
121
+ console.log("[6/8] Skipping pickup (commitment_id already present)");
122
+ }
123
+
124
+ console.log("[7/8] Confirm + complete commitment (responder)…");
125
+ await responderClient.confirmCommitment({ commitmentId });
126
+ await responderClient.completeCommitment({ commitmentId });
127
+ console.log("[7/8] Done");
128
+
129
+ console.log("[8/8] Payout / reconciliation status (operator-specific JSON):");
130
+ const ledger = await publisherClient.getPayoutStatus({ commitmentId });
131
+ console.log(JSON.stringify(ledger, null, 2));
132
+ }
133
+
134
+ main().catch((err) => {
135
+ if (err instanceof BradyHttpError) {
136
+ console.error(`[HTTP ${err.status}]`, JSON.stringify(err.body, null, 2));
137
+ } else {
138
+ explainTransportFailure(err);
139
+ console.error(err);
140
+ }
141
+ process.exit(1);
142
+ });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@bradyprotocol/brady-external-agent-sdk",
3
+ "version": "0.2.9",
4
+ "description": "Minimal HTTP client for BRADY Phase 3 external agents",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/SerDaboz/Dweb-Discoverer.git",
12
+ "directory": "packages/brady-external-agent-sdk"
13
+ },
14
+ "homepage": "https://bradyprotocol.xyz",
15
+ "bugs": {
16
+ "url": "https://github.com/SerDaboz/Dweb-Discoverer/issues"
17
+ },
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "examples"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "prepublishOnly": "bun run build",
32
+ "test": "vitest run"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^5.6.0",
39
+ "vitest": "^4.0.0"
40
+ },
41
+ "sideEffects": false,
42
+ "engines": {
43
+ "node": ">=18"
44
+ }
45
+ }