agent402-client 0.6.11 → 0.8.0
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 +58 -18
- package/index.js +109 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
A tiny buyer-side client for [Agent402](https://agent402.tools) (and any Agent402
|
|
4
4
|
instance) - the buy side of [Agentic Finance](https://agent402.tools/agentic-finance),
|
|
5
|
-
agents paying per request over x402 or MPP. **Resolve a task to a tool, then call it
|
|
5
|
+
agents paying per request over x402 or MPP. **Resolve a task to a tool, then call it - with payment handled for
|
|
6
6
|
you.** Free pure-CPU tools settle with a built-in proof-of-work (no wallet, zero
|
|
7
|
-
dependencies); wallet-only tools settle via an x402-wrapped fetch you
|
|
8
|
-
|
|
9
|
-
never double-charges.
|
|
7
|
+
dependencies); wallet-only tools settle via an x402- or MPP-wrapped fetch you
|
|
8
|
+
provide, or by card through a prepaid credits key. Results are cached, and
|
|
9
|
+
retries reuse an `Idempotency-Key` so a lost response never double-charges.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install agent402-client
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Runnable copy of the free-tier quickstart below: [`examples/hello-agent402.js`](https://github.com/MikeyPetrillo/Agent402/blob/main/examples/hello-agent402.js)
|
|
15
|
+
Runnable copy of the free-tier quickstart below: [`examples/hello-agent402.js`](https://github.com/MikeyPetrillo/Agent402/blob/main/examples/hello-agent402.js) - discover a tool and call it in ~15 lines, no wallet.
|
|
16
16
|
|
|
17
17
|
## Free tier (proof-of-work, no wallet)
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ const a = new Agent402(); // → https://agent402.tools
|
|
|
25
25
|
const matches = await a.find("extract the article from a url");
|
|
26
26
|
// → [{ slug: "extract", route, price, inputSchema, example, … }]
|
|
27
27
|
|
|
28
|
-
// Call it
|
|
28
|
+
// Call it - proof-of-work is solved automatically for free tools.
|
|
29
29
|
const out = await a.call("hash", { text: "hello world", algo: "sha256" });
|
|
30
30
|
console.log(out.hex);
|
|
31
31
|
```
|
|
@@ -69,6 +69,26 @@ const a = new Agent402({ fetch: payFetch });
|
|
|
69
69
|
const article = await a.call("extract", { url: "https://example.com/article" });
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## Pay by card instead of a wallet (prepaid credits)
|
|
73
|
+
|
|
74
|
+
Buy a credits pack ($20 / $50 / $100) at https://agent402.tools/credits, claim the
|
|
75
|
+
`a402_...` key once, and pass it as `creditsKey`. The SDK then sends
|
|
76
|
+
`Authorization: Bearer a402_...` on wallet-only calls; the server authorizes
|
|
77
|
+
against the key's balance before the handler runs and debits the list price only
|
|
78
|
+
on a successful (200) response (the `X-Credits-Balance` header carries what is
|
|
79
|
+
left; credits never expire). A refused call throws with the balance and a top-up
|
|
80
|
+
link; nothing is debited.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
const client = new Agent402({ creditsKey: "a402_..." });
|
|
84
|
+
await client.call("whois", { domain: "example.com" }); // debited per successful call
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The same `maxPerCallUsd` / `dailyLimitUsd` / `maxPerHostUsd` caps apply (a
|
|
88
|
+
credits call reserves its price like a wallet call). A payment `fetch` still wins
|
|
89
|
+
when both are given, and free pure-CPU tools keep settling with proof-of-work.
|
|
90
|
+
The same Bearer header also reads the balance directly, e.g. `curl -H "Authorization: Bearer a402_..." https://agent402.tools/api/credits/balance`.
|
|
91
|
+
|
|
72
92
|
## Retries never double-charge
|
|
73
93
|
|
|
74
94
|
Every paid call the SDK makes carries an `Idempotency-Key`, so a retry of a
|
|
@@ -80,8 +100,8 @@ same id is a new payment).
|
|
|
80
100
|
|
|
81
101
|
## Workflows (skill packs)
|
|
82
102
|
|
|
83
|
-
For jobs that no single tool covers
|
|
84
|
-
brief"
|
|
103
|
+
For jobs that no single tool covers - e.g. "audit a domain", "build a stock
|
|
104
|
+
brief" - Agent402 ships curated multi-tool **skill packs**: 5–7 catalog tools
|
|
85
105
|
composed into a Claude-ready task template. Discover them the same way you'd
|
|
86
106
|
discover a tool:
|
|
87
107
|
|
|
@@ -96,7 +116,7 @@ const { messages } = await a.getWorkflowPrompt("security-audit", { domain: "stri
|
|
|
96
116
|
|
|
97
117
|
## Discover the live x402 economy
|
|
98
118
|
|
|
99
|
-
Want to see who's actually getting paid on x402 right now
|
|
119
|
+
Want to see who's actually getting paid on x402 right now - not just what tools
|
|
100
120
|
this service exposes? `topSellers()` returns the live leaderboard of sellers
|
|
101
121
|
settling USDC (primarily on Base) in the last ~24h, derived from on-chain transfers. Free
|
|
102
122
|
to call (no payment, no proof-of-work):
|
|
@@ -113,21 +133,41 @@ await a.topSellers({ sort: "calls", include: "all" });
|
|
|
113
133
|
|
|
114
134
|
| Method | What |
|
|
115
135
|
|---|---|
|
|
116
|
-
| `new Agent402({ baseUrl?, fetch?, cache?, fetchImpl?, maxPerCallUsd?, dailyLimitUsd?, maxPerHostUsd? })` | `fetch` is your x402-wrapped fetch for paid tools (optional); `cache` (default `true`) memoizes deterministic results; the three USD caps set optional spending limits (see below) |
|
|
136
|
+
| `new Agent402({ baseUrl?, fetch?, creditsKey?, cache?, fetchImpl?, maxPerCallUsd?, dailyLimitUsd?, maxPerHostUsd?, maxResponseBytes? })` | `fetch` is your x402- or MPP-wrapped fetch for paid tools (optional); `creditsKey` is a prepaid card-credits key (`a402_...`) used for paid tools when no `fetch` is given; `cache` (default `true`) memoizes deterministic results; the three USD caps set optional spending limits (see below); `maxResponseBytes` (default 32MB, `null` to disable) refuses an oversized response body before it is parsed |
|
|
117
137
|
| `await a.find(task, { k = 5 })` | Resolve a plain-language task to the best-matching tools (route, price, schema, example) |
|
|
118
138
|
| `await a.findWorkflows(task, { k = 2 })` | Resolve a task to matching multi-tool workflow templates (skill packs) |
|
|
119
139
|
| `await a.getWorkflowPrompt(slug, args)` | Fetch the rendered prompt messages for a skill pack with arguments substituted in |
|
|
120
140
|
| `await a.topSellers({ limit?, sort?, include? })` | Live x402 leaderboard: which sellers are settling the most USDC (primarily on Base) in the last ~24h (free, no payment) |
|
|
121
|
-
| `await a.call(slug, params, { idempotencyKey?, cache? })` | Call a tool; auto-pays (PoW for free tools
|
|
141
|
+
| `await a.call(slug, params, { idempotencyKey?, cache?, maxResponseBytes? })` | Call a tool; auto-pays (PoW for free tools; your payment `fetch` or the credits key for wallet-only); returns the JSON result |
|
|
122
142
|
| `Agent402.solvePow(pow)` | Solve a proof-of-work challenge object → an `X-Pow-Solution` value |
|
|
123
143
|
| `a.spendingSummary()` | Rolling-24h paid spend so far: `{ dailyUsd, calls, byHost, limits }` |
|
|
124
144
|
| `a.clearCache()` | Drop the in-memory result cache |
|
|
125
145
|
|
|
146
|
+
## Response size ceiling
|
|
147
|
+
|
|
148
|
+
You are calling strangers and paying them, and the seller chooses the response.
|
|
149
|
+
By the time `r.json()` resolves, a multi-gigabyte body is already in your
|
|
150
|
+
agent's memory, so this is one of the few checks that cannot be done after the
|
|
151
|
+
fact in your own code.
|
|
152
|
+
|
|
153
|
+
Every call is capped at 32MB by default. The declared `content-length` is
|
|
154
|
+
refused before a byte is read, and the stream is counted as it arrives, because
|
|
155
|
+
`content-length` is the seller's claim about their own body.
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
const a = new Agent402({ maxResponseBytes: 1_000_000 }); // 1MB everywhere
|
|
159
|
+
await a.call("hash", { text: "x" }, { maxResponseBytes: null }); // or per call
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
An oversized body throws `ResponseTooLargeError` carrying `size`, `cap`,
|
|
163
|
+
`source` and `paid`. Check `paid`: on a wallet-only tool the money moved before
|
|
164
|
+
the body arrived, so a refused response is still a spend you made.
|
|
165
|
+
|
|
126
166
|
## Spending caps (never overpay)
|
|
127
167
|
|
|
128
168
|
By default the client pays whatever a tool costs. Set optional hard ceilings and a
|
|
129
169
|
call that would exceed one is **refused before any payment is signed** (it throws
|
|
130
|
-
`SpendingLimitError`
|
|
170
|
+
`SpendingLimitError` - no funds move):
|
|
131
171
|
|
|
132
172
|
```js
|
|
133
173
|
import { Agent402, SpendingLimitError } from "agent402-client";
|
|
@@ -146,29 +186,29 @@ try {
|
|
|
146
186
|
}
|
|
147
187
|
```
|
|
148
188
|
|
|
149
|
-
Only **settled** paid calls count against the rolling window
|
|
189
|
+
Only **settled** paid calls count against the rolling window - a blocked or failed
|
|
150
190
|
call never consumes budget. Free proof-of-work calls are never counted. Omit a cap
|
|
151
191
|
(or leave it `null`) for no limit; with none set, behavior is unchanged.
|
|
152
192
|
|
|
153
193
|
**What the caps check.** When a cap is set, the client preflights the `402` and
|
|
154
194
|
checks the ceiling against the **larger** of the advertised price (from the
|
|
155
|
-
seller's `/api/pricing`) and the amount the `402` challenge actually quotes
|
|
195
|
+
seller's `/api/pricing`) and the amount the `402` challenge actually quotes - so a
|
|
156
196
|
server that under-advertises and then quotes more in the `402` is refused *before*
|
|
157
197
|
your wallet fetch signs anything. If the `402` can't be read (FREE_MODE, or an
|
|
158
198
|
unrecognized challenge shape) it falls back to the advertised price rather than
|
|
159
199
|
block a legitimate payment. Caps hold under **concurrency** too: each call reserves
|
|
160
200
|
its amount synchronously, so N simultaneous calls can't each pass against the same
|
|
161
|
-
pre-commit total. (The `402` amount is derived assuming stablecoin settlement
|
|
162
|
-
`atomic / 10^decimals ≈ USD`
|
|
201
|
+
pre-commit total. (The `402` amount is derived assuming stablecoin settlement -
|
|
202
|
+
`atomic / 10^decimals ≈ USD` - which matches x402's USDC/USDG rails.)
|
|
163
203
|
|
|
164
204
|
- **Zero dependencies** for the free/proof-of-work path (uses `node:crypto`).
|
|
165
|
-
- **Non-custodial:** paid settlement is your `@x402/fetch` + wallet; this client never sees
|
|
205
|
+
- **Non-custodial:** paid settlement is your `@x402/fetch` / `mppx` fetch + wallet (or a prepaid credits key you bought); this client never sees a private key.
|
|
166
206
|
- MIT licensed. Part of [Agent402](https://github.com/MikeyPetrillo/Agent402).
|
|
167
207
|
|
|
168
208
|
## Pick the settlement chain (`withNetworkPreference`)
|
|
169
209
|
|
|
170
210
|
Multi-chain sellers list Base first, so an unmodified x402 client effectively
|
|
171
|
-
always settles there. To pin a chain
|
|
211
|
+
always settles there. To pin a chain - e.g. **USDG on Robinhood Chain** -
|
|
172
212
|
wrap your client before building the fetch:
|
|
173
213
|
|
|
174
214
|
```js
|
package/index.js
CHANGED
|
@@ -18,8 +18,13 @@ import { createHash } from "node:crypto";
|
|
|
18
18
|
// `User-Agent: agent402-client/<version>` — a standard header, no extra
|
|
19
19
|
// network calls — so a seller can attribute traffic (and settled payments)
|
|
20
20
|
// to this SDK. Product token only; nothing about the caller rides along.
|
|
21
|
-
const VERSION = "0.
|
|
21
|
+
const VERSION = "0.7.0";
|
|
22
22
|
const USER_AGENT = `agent402-client/${VERSION}`;
|
|
23
|
+
// 32MB: about a hundred times any realistic response from this catalog (the
|
|
24
|
+
// largest is a base64 image at a few MB), so it cannot break a legitimate
|
|
25
|
+
// caller, while still stopping the class it exists for. A ceiling that is off
|
|
26
|
+
// by default protects nobody, so this one is on; pass null to disable it.
|
|
27
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 32 * 1024 * 1024;
|
|
23
28
|
|
|
24
29
|
const leadingZeroBits = (buf) => { let n = 0; for (const b of buf) { if (b === 0) { n += 8; continue; } n += Math.clz32(b) - 24; break; } return n; };
|
|
25
30
|
|
|
@@ -33,9 +38,15 @@ export class Agent402 {
|
|
|
33
38
|
* @param {number} [opts.maxPerCallUsd] hard ceiling on a single paid call (USD); over → SpendingLimitError before paying
|
|
34
39
|
* @param {number} [opts.dailyLimitUsd] hard ceiling on rolling-24h paid spend (USD)
|
|
35
40
|
* @param {number} [opts.maxPerHostUsd] hard ceiling on rolling-24h paid spend to one seller host (USD)
|
|
41
|
+
* @param {string} [opts.creditsKey] a prepaid card-credits key (a402_...) from agent402.tools/credits -
|
|
42
|
+
* pays wallet-only tools by card when no payFetch is given
|
|
43
|
+
* @param {number|null} [opts.maxResponseBytes=33554432]
|
|
44
|
+
* Hard ceiling on a response body, enforced BEFORE it is parsed. null disables it.
|
|
36
45
|
*/
|
|
37
46
|
constructor({ baseUrl = "https://agent402.tools", fetch: payFetch, cache = true, fetchImpl = globalThis.fetch,
|
|
38
|
-
maxPerCallUsd = null, dailyLimitUsd = null, maxPerHostUsd = null
|
|
47
|
+
maxPerCallUsd = null, dailyLimitUsd = null, maxPerHostUsd = null, creditsKey = null,
|
|
48
|
+
maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES } = {}) {
|
|
49
|
+
this.creditsKey = typeof creditsKey === "string" && /^a402_[A-Za-z0-9_-]{32,64}$/.test(creditsKey) ? creditsKey : null;
|
|
39
50
|
if (typeof fetchImpl !== "function") throw new Error("No fetch available — pass { fetchImpl } on Node < 18");
|
|
40
51
|
this.baseUrl = String(baseUrl).replace(/\/$/, "");
|
|
41
52
|
this.payFetch = payFetch || null;
|
|
@@ -56,6 +67,65 @@ export class Agent402 {
|
|
|
56
67
|
perHost: numOrNull(maxPerHostUsd),
|
|
57
68
|
log: [], // [{ ts, host, usd }] — settled paid calls in the last 24h
|
|
58
69
|
};
|
|
70
|
+
// A response-size ceiling, enforced before parsing. See _readJson.
|
|
71
|
+
this.maxResponseBytes = maxResponseBytes === null ? null : (numOrNull(maxResponseBytes) ?? DEFAULT_MAX_RESPONSE_BYTES);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Read a JSON body with a hard byte ceiling.
|
|
76
|
+
*
|
|
77
|
+
* WHY THIS LIVES IN THE SDK. Almost everything a buyer might want to check
|
|
78
|
+
* about a response, they can check themselves in their own code once they
|
|
79
|
+
* hold it. Not this one: by the time `r.json()` has resolved, a hostile or
|
|
80
|
+
* broken seller's multi-gigabyte body is already in the agent's memory. The
|
|
81
|
+
* SDK owns the fetch, so it is the only place the check can happen in time.
|
|
82
|
+
*
|
|
83
|
+
* That matters here more than in an ordinary HTTP client, because this SDK
|
|
84
|
+
* calls STRANGERS and pays them. The seller chooses the response.
|
|
85
|
+
*
|
|
86
|
+
* Two gates: the declared content-length is refused before a byte is read,
|
|
87
|
+
* and the actual stream is counted as it arrives, because content-length is
|
|
88
|
+
* the seller's claim about their own body and a missing or lying header must
|
|
89
|
+
* not be the thing standing between an agent and an OOM.
|
|
90
|
+
*/
|
|
91
|
+
async _readJson(r, { maxBytes, slug, paid = false } = {}) {
|
|
92
|
+
const cap = maxBytes === undefined ? this.maxResponseBytes : (maxBytes === null ? null : numOrNull(maxBytes));
|
|
93
|
+
if (cap == null) return r.json();
|
|
94
|
+
|
|
95
|
+
const declared = Number(r.headers?.get?.("content-length"));
|
|
96
|
+
if (Number.isFinite(declared) && declared > cap) {
|
|
97
|
+
throw new ResponseTooLargeError(declared, cap, { slug, paid, source: "content-length" });
|
|
98
|
+
}
|
|
99
|
+
// A response object we cannot measure at all - no stream and no text() -
|
|
100
|
+
// is a caller-supplied stub or an exotic runtime, never a hostile seller
|
|
101
|
+
// over a real fetch (a real Response has both). Degrade to an unbounded
|
|
102
|
+
// read rather than turning "I could not measure this" into a failure of a
|
|
103
|
+
// call the caller may have already paid for.
|
|
104
|
+
if ((!r.body || typeof r.body.getReader !== "function") && typeof r.text !== "function") return r.json();
|
|
105
|
+
// No readable stream but text() is available: bound the PARSE, even though
|
|
106
|
+
// the bytes have already arrived.
|
|
107
|
+
if (!r.body || typeof r.body.getReader !== "function") {
|
|
108
|
+
const text = await r.text();
|
|
109
|
+
const size = Buffer.byteLength(text, "utf8");
|
|
110
|
+
if (size > cap) throw new ResponseTooLargeError(size, cap, { slug, paid, source: "body" });
|
|
111
|
+
return JSON.parse(text);
|
|
112
|
+
}
|
|
113
|
+
const reader = r.body.getReader();
|
|
114
|
+
const chunks = [];
|
|
115
|
+
let received = 0;
|
|
116
|
+
for (;;) {
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done) break;
|
|
119
|
+
received += value.length;
|
|
120
|
+
if (received > cap) {
|
|
121
|
+
// Stop pulling immediately. Reading the rest to "see how big it is" is
|
|
122
|
+
// the failure this exists to prevent.
|
|
123
|
+
try { await reader.cancel(); } catch { /* already gone */ }
|
|
124
|
+
throw new ResponseTooLargeError(received, cap, { slug, paid, source: "stream" });
|
|
125
|
+
}
|
|
126
|
+
chunks.push(value);
|
|
127
|
+
}
|
|
128
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
59
129
|
}
|
|
60
130
|
|
|
61
131
|
async _loadCatalog() {
|
|
@@ -166,7 +236,7 @@ export class Agent402 {
|
|
|
166
236
|
* Call a tool by slug; pays automatically (PoW for free tools, x402 for
|
|
167
237
|
* wallet-only) and returns the parsed JSON result.
|
|
168
238
|
*/
|
|
169
|
-
async call(slug, params = {}, { idempotencyKey, cache = true } = {}) {
|
|
239
|
+
async call(slug, params = {}, { idempotencyKey, cache = true, maxResponseBytes } = {}) {
|
|
170
240
|
const cat = await this._loadCatalog();
|
|
171
241
|
const tool = cat.get(slug);
|
|
172
242
|
if (!tool) throw new Error(`unknown tool "${slug}" — use client.find(task) to discover one`);
|
|
@@ -221,15 +291,33 @@ export class Agent402 {
|
|
|
221
291
|
const r = await send({}, this.payFetch);
|
|
222
292
|
if (!r.ok) throw new Error(`call "${slug}" failed: HTTP ${r.status}`);
|
|
223
293
|
this._spendSettle(reservation); // confirm the reservation as settled spend
|
|
224
|
-
return this._store(cacheKey, await
|
|
294
|
+
return this._store(cacheKey, await this._readJson(r, { maxBytes: maxResponseBytes, slug, paid: true }), cache);
|
|
225
295
|
} catch (e) {
|
|
226
296
|
this._spendRelease(reservation); // roll back — nothing settled
|
|
227
297
|
throw e;
|
|
228
298
|
}
|
|
229
299
|
}
|
|
300
|
+
if (this.creditsKey) {
|
|
301
|
+
// Prepaid card credits: the server authorizes against the key's balance
|
|
302
|
+
// before the handler and debits only on a 200 (X-Credits-Balance tells
|
|
303
|
+
// what is left). The same spend caps apply as on the wallet path.
|
|
304
|
+
const host = hostOf(this.baseUrl);
|
|
305
|
+
const usd = parseUsd(tool.price);
|
|
306
|
+
const reservation = this._spendReserve(host, usd, slug);
|
|
307
|
+
try {
|
|
308
|
+
const r = await send({ Authorization: `Bearer ${this.creditsKey}` });
|
|
309
|
+
if (r.status === 402) {
|
|
310
|
+
const body = await r.json().catch(() => ({}));
|
|
311
|
+
throw new Error(`call "${slug}" refused by credits: ${body.error || "payment required"}${body.balanceUsd != null ? ` (balance $${body.balanceUsd})` : ""} - top up at ${body.topup || `${this.baseUrl}/credits`}`);
|
|
312
|
+
}
|
|
313
|
+
if (!r.ok) throw new Error(`call "${slug}" failed: HTTP ${r.status}`);
|
|
314
|
+
this._spendSettle(reservation);
|
|
315
|
+
return this._store(cacheKey, await this._readJson(r, { maxBytes: maxResponseBytes, slug, paid: true }), cache);
|
|
316
|
+
} catch (e) { this._spendRelease(reservation); throw e; }
|
|
317
|
+
}
|
|
230
318
|
const r = await send(); // no wallet — succeeds only on a FREE_MODE instance
|
|
231
|
-
if (r.ok) return this._store(cacheKey, await
|
|
232
|
-
throw new Error(`call "${slug}" failed: HTTP ${r.status} — wallet-only tool; construct with { fetch: payFetch } (an @x402/fetch-wrapped fetch)`);
|
|
319
|
+
if (r.ok) return this._store(cacheKey, await this._readJson(r, { maxBytes: maxResponseBytes, slug, paid: false }), cache);
|
|
320
|
+
throw new Error(`call "${slug}" failed: HTTP ${r.status} — wallet-only tool; construct with { fetch: payFetch } (an @x402/fetch-wrapped fetch) or { creditsKey } (prepaid card credits from ${this.baseUrl}/credits)`);
|
|
233
321
|
}
|
|
234
322
|
|
|
235
323
|
// Free (compute-payable) tool: succeeds plainly on a FREE_MODE instance,
|
|
@@ -241,7 +329,7 @@ export class Agent402 {
|
|
|
241
329
|
r = await send({ "X-Pow-Solution": Agent402.solvePow(chal) });
|
|
242
330
|
}
|
|
243
331
|
if (!r.ok) throw new Error(`call "${slug}" failed after proof-of-work: HTTP ${r.status}`);
|
|
244
|
-
return this._store(cacheKey, await
|
|
332
|
+
return this._store(cacheKey, await this._readJson(r, { maxBytes: maxResponseBytes, slug, paid: false }), cache);
|
|
245
333
|
}
|
|
246
334
|
|
|
247
335
|
async _powChallenge(slug) {
|
|
@@ -326,6 +414,20 @@ export class Agent402 {
|
|
|
326
414
|
|
|
327
415
|
/** Thrown when a paid call would exceed a configured spending ceiling. The call
|
|
328
416
|
* is refused BEFORE any payment is signed, so no funds move. */
|
|
417
|
+
/** A response body over the ceiling, refused BEFORE it was parsed.
|
|
418
|
+
*
|
|
419
|
+
* `paid` matters: on a wallet-only tool the money has already moved by the
|
|
420
|
+
* time the body arrives, so a caller must be able to tell "I was charged and
|
|
421
|
+
* the seller sent something unusable" from "nothing happened". Losing that
|
|
422
|
+
* distinction would turn a refused response into a silently forgotten spend. */
|
|
423
|
+
export class ResponseTooLargeError extends Error {
|
|
424
|
+
constructor(size, cap, { slug, paid, source } = {}) {
|
|
425
|
+
super(`response for "${slug}" is ${size} bytes, over the ${cap}-byte ceiling (${source}) - refused before parsing${paid ? "; this call WAS paid" : ""}`);
|
|
426
|
+
this.name = "ResponseTooLargeError";
|
|
427
|
+
Object.assign(this, { size, cap, slug, paid: Boolean(paid), source });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
329
431
|
export class SpendingLimitError extends Error {
|
|
330
432
|
constructor(message, details = {}) {
|
|
331
433
|
super(message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent402-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Agent402 buyer SDK: resolve a task to a tool across 500+ pay-per-call endpoints and call it with payment handled - free via proof-of-work, or over x402 or MPP (a stock mppx fetch works) with a wallet you keep; the buy side of Agentic Finance. Caching, idempotent retries (Idempotency-Key), non-custodial, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|