agent402-openclaw 0.4.3 → 0.4.5
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 +23 -19
- package/cli.js +3 -3
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -2
- package/proxy.js +43 -1
package/README.md
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Part of [Agent402](https://agent402.tools), the open-source paid door for AI agents.
|
|
4
4
|
|
|
5
|
-
Agent402 as an [OpenClaw](https://openclaw.ai) model provider: auto-routed
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Agent402 as an [OpenClaw](https://openclaw.ai) model provider: the auto-routed
|
|
6
|
+
tier at a **flat per-call price** and explicit frontier models **priced per
|
|
7
|
+
request** from the body, paid in **USDC over x402** from a wallet or with a
|
|
8
|
+
prepaid credits key already issued (new credits are not on sale). The same key
|
|
9
|
+
and gateway reach Agent402's 500+ pay-per-call tools.
|
|
9
10
|
|
|
10
11
|
Guide: https://agent402.tools/guides/openclaw-model-provider
|
|
11
12
|
|
|
@@ -21,8 +22,7 @@ With no credits key and no `AGENT402_WALLET_KEY`, `setup` generates an EVM
|
|
|
21
22
|
wallet into `~/.openclaw/agent402/wallet.key` (0600, the only copy, never
|
|
22
23
|
printed) and prints its address: send it USDC on Base and every call is paid
|
|
23
24
|
from it over x402. `agent402-openclaw wallet` shows the address and balance;
|
|
24
|
-
`--no-wallet` skips the generation.
|
|
25
|
-
https://agent402.tools/credits and run
|
|
25
|
+
`--no-wallet` skips the generation. Holding a credits key already issued? Run
|
|
26
26
|
`AGENT402_CREDITS_KEY=a402_... npx agent402-openclaw setup --write` (key by env,
|
|
27
27
|
or `--credits-key -` on stdin).
|
|
28
28
|
|
|
@@ -57,28 +57,31 @@ wallet. The key never leaves the machine.
|
|
|
57
57
|
Two ways the wallet can pay a metered call:
|
|
58
58
|
|
|
59
59
|
- **exact** (default): the 402 quotes the call from its body (input + your
|
|
60
|
-
`max_tokens`
|
|
60
|
+
`max_tokens`) and the wallet pays that quote.
|
|
61
61
|
- **upto, actual usage**: run `agent402-openclaw permit2-approve` once (one USDC
|
|
62
62
|
approval transaction on Base; the wallet needs a little ETH for gas). From
|
|
63
63
|
then on the proxy authorizes the quote as a CEILING and the gateway settles
|
|
64
|
-
what the call actually
|
|
64
|
+
what the call actually used, so a short answer costs a fraction of the
|
|
65
65
|
quote. `agent402-openclaw doctor` says which mode the wallet is in.
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Credits-key buyers already pay actual usage on metered calls.
|
|
68
68
|
|
|
69
69
|
## Models and pricing
|
|
70
70
|
|
|
71
71
|
`auto` (routed per prompt, flat $0.01/call) plus every id from
|
|
72
72
|
`GET https://agent402.tools/v1/models`. Explicit models are **metered by default**:
|
|
73
73
|
the proxy sends them to the gateway's metered route, where each request is quoted
|
|
74
|
-
from its body (
|
|
75
|
-
|
|
74
|
+
from its body (input plus your `max_tokens`, from $0.001, capped at $2 per
|
|
75
|
+
call), so a short call costs a fraction
|
|
76
76
|
of a cent and a long one pays for what it asks. `--flat` (or
|
|
77
77
|
`AGENT402_PRICING=flat`) keeps every model on its flat per-call tier instead.
|
|
78
|
-
Either way OpenClaw's per-token cost fields stay zero; the price is per call. A model sent to
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
Either way OpenClaw's per-token cost fields stay zero; the price is per call. A model sent to another flat
|
|
79
|
+
tier's route is priced at its home tier: the 402 quotes that tier's price, and the paid call is served under
|
|
80
|
+
that tier's caps (the answer says so in `agent402_tier`). The router, grounded and stealth tiers still answer a
|
|
81
|
+
400 naming the right route; nothing is charged. A client-supplied `Idempotency-Key`
|
|
82
|
+
is passed through: on a credits key, a retry with the same key replays the paid
|
|
83
|
+
answer; on a wallet, each forwarded call signs a fresh payment, which the server
|
|
84
|
+
treats as a new purchase. Without a key, each call is its own payment.
|
|
82
85
|
|
|
83
86
|
The proxy answers native clients on loopback only: requests carrying a browser
|
|
84
87
|
`Origin` header or a non-loopback `Host` are refused, so a web page cannot spend
|
|
@@ -86,7 +89,7 @@ the key.
|
|
|
86
89
|
|
|
87
90
|
## Commands
|
|
88
91
|
|
|
89
|
-
- `agent402-openclaw setup [--credits-key K | --credits-key - (stdin) | AGENT402_CREDITS_KEY env] [--write] [--port N] [--flat]`
|
|
92
|
+
- `agent402-openclaw setup [--credits-key K | --credits-key - (stdin) | AGENT402_CREDITS_KEY env] [--no-wallet] [--write] [--port N] [--flat]`
|
|
90
93
|
- `agent402-openclaw proxy [--port N] [--upstream URL]`
|
|
91
94
|
- `agent402-openclaw doctor`
|
|
92
95
|
- `agent402-openclaw wallet [--rpc URL]` (address + USDC balance of the wallet the proxy pays from)
|
|
@@ -100,11 +103,12 @@ Anthropic Messages wires, embeddings, rerank, images, video, speech,
|
|
|
100
103
|
transcription, grounded answers with citations, 500+ tools over
|
|
101
104
|
MCP or HTTP, wallet-keyed memory, finished reports (dossiers, insider flow, 13F,
|
|
102
105
|
domain audits, token risk, deep research) and monitors, plus routing that buys
|
|
103
|
-
from
|
|
106
|
+
from external sellers on the agent's behalf.
|
|
104
107
|
|
|
105
108
|
Why pay here, with every claim linked to its proof: https://agent402.tools/why.
|
|
106
109
|
Usage is priced under a ceiling quoted before you pay, a failed call is not
|
|
107
|
-
charged and the receipt proves it, a keyed retry
|
|
108
|
-
published from outside
|
|
110
|
+
charged and the receipt proves it, a keyed retry on a credits key is not
|
|
111
|
+
debited twice, and uptime and transactions are published from outside
|
|
112
|
+
production.
|
|
109
113
|
|
|
110
114
|
Zero dependencies. MIT. Maintained by Havok Holdings LLC.
|
package/cli.js
CHANGED
|
@@ -90,7 +90,7 @@ async function main() {
|
|
|
90
90
|
// per-token router that prints a wallet at install; the card path
|
|
91
91
|
// (credits key) stays available. --no-wallet keeps the old behaviour.
|
|
92
92
|
if (has("--no-wallet")) {
|
|
93
|
-
out(`no payment method yet:
|
|
93
|
+
out(`no payment method yet: set AGENT402_WALLET_KEY, rerun without --no-wallet to generate a wallet, or rerun with --credits-key a402_... if you hold a credits key`);
|
|
94
94
|
} else {
|
|
95
95
|
const w = await generateWallet();
|
|
96
96
|
if (w) {
|
|
@@ -99,9 +99,9 @@ async function main() {
|
|
|
99
99
|
out(` key: ${WALLET_KEY_FILE()} (0600; back it up, it is the only copy)`);
|
|
100
100
|
out(`fund it with USDC on Base (any amount; a call costs from $0.001), then every call is paid from it over x402.`);
|
|
101
101
|
out(`optional: \`agent402-openclaw permit2-approve\` once (needs a little ETH on Base for gas) to pay actual usage instead of the per-request quote.`);
|
|
102
|
-
out(`
|
|
102
|
+
out(`holding a credits key already issued? rerun with --credits-key a402_... instead`);
|
|
103
103
|
} else {
|
|
104
|
-
out(`no payment method yet:
|
|
104
|
+
out(`no payment method yet: install viem + @x402/fetch + @x402/evm and rerun to generate a wallet, set AGENT402_WALLET_KEY, or rerun with --credits-key a402_... if you hold a credits key`);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "agent402",
|
|
3
3
|
"name": "Agent402",
|
|
4
|
-
"description": "Agent402 model provider: auto-routed
|
|
4
|
+
"description": "Agent402 model provider: an auto-routed tier at a flat per-call price and explicit frontier models priced per request, paid in USDC over x402 or with a prepaid credits key. Same key reaches 500+ pay-per-call tools.",
|
|
5
5
|
"activation": {
|
|
6
6
|
"onStartup": true
|
|
7
7
|
},
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"uiHints": {
|
|
50
50
|
"creditsKey": {
|
|
51
51
|
"label": "Credits key",
|
|
52
|
-
"help": "
|
|
52
|
+
"help": "Issued at https://agent402.tools/credits; that page says whether new keys are on sale",
|
|
53
53
|
"sensitive": true
|
|
54
54
|
},
|
|
55
55
|
"walletKey": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent402-openclaw",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "OpenClaw model provider for Agent402: auto-routed
|
|
3
|
+
"version": "0.4.5",
|
|
4
|
+
"description": "OpenClaw model provider for Agent402: an auto-routed tier at a flat per-call price and explicit LLM models priced per request, paid in USDC over x402 or with a prepaid credits key through a local proxy. Zero dependencies; x402 wallet payment is an optional peer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|
package/proxy.js
CHANGED
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
// own 402->pay retry), which does NOT make two client calls one payment.
|
|
21
21
|
// Streams pass through byte for byte.
|
|
22
22
|
//
|
|
23
|
+
// Refusals: a refused paid call's 402 from an x402 v2 seller carries the whole
|
|
24
|
+
// payment offer in its JSON body (Agent402 mirrors its PAYMENT-REQUIRED header
|
|
25
|
+
// there) beside the refusal's own reason, hint and retry. OpenClaw's model
|
|
26
|
+
// client builds its error message from `error.message`, else from the whole
|
|
27
|
+
// body, so relaying that body put kilobytes of accepts and schemas into the
|
|
28
|
+
// agent's error text in place of the one sentence that says what to fix. Such
|
|
29
|
+
// a body is answered in the OpenAI error shape instead (refusalAsOpenAIError);
|
|
30
|
+
// every other upstream body passes through byte for byte.
|
|
31
|
+
//
|
|
23
32
|
// Loopback only, and browser-hostile on purpose: any web page can POST to
|
|
24
33
|
// 127.0.0.1 with a "simple" no-cors request, and this proxy spends the
|
|
25
34
|
// user's key, so a request carrying an Origin header (browsers always send
|
|
@@ -49,6 +58,27 @@ function readBody(req, limit = MAX_BODY) {
|
|
|
49
58
|
});
|
|
50
59
|
}
|
|
51
60
|
|
|
61
|
+
// The x402 offer keys a refusal body may carry beside its own fields.
|
|
62
|
+
const OFFER_KEYS = ["x402Version", "resource", "accepts", "extensions"];
|
|
63
|
+
const text = (v) => (typeof v === "string" && v.trim() ? v : null);
|
|
64
|
+
|
|
65
|
+
/** An upstream error body that carries an x402 offer (a numeric x402Version),
|
|
66
|
+
* as an OpenAI-shaped error: the explanation (a problem's detail, else the
|
|
67
|
+
* hint, else the error sentence) as error.message, the retry class or reason as
|
|
68
|
+
* error.code, the refusal's own fields beside it, and no offer. Null for any
|
|
69
|
+
* other body, which the caller relays unchanged. */
|
|
70
|
+
export function refusalAsOpenAIError(status, doc) {
|
|
71
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc) || typeof doc.x402Version !== "number") return null;
|
|
72
|
+
const ours = { ...doc };
|
|
73
|
+
for (const k of OFFER_KEYS) delete ours[k];
|
|
74
|
+
const { error, ...fields } = ours;
|
|
75
|
+
const message = text(fields.detail) || text(fields.hint) || text(error) || text(fields.title) || `Agent402 answered HTTP ${status}`;
|
|
76
|
+
return {
|
|
77
|
+
error: { message, type: status === 402 ? "payment_required" : "upstream_error", code: text(fields.retry) || text(fields.reason) || null },
|
|
78
|
+
...fields,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
52
82
|
const json = (res, status, obj, headers = {}) => {
|
|
53
83
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", ...headers });
|
|
54
84
|
res.end(JSON.stringify(obj));
|
|
@@ -102,7 +132,7 @@ export async function startProxy({ upstream = DEFAULT_UPSTREAM, creditsKey = nul
|
|
|
102
132
|
return json(res, 400, { error: { message: `Unknown model "${requested}". Use "auto" or an id from GET /v1/models (${table.size} available).`, type: "invalid_request_error", code: "model_not_found" } });
|
|
103
133
|
}
|
|
104
134
|
if (!paid) {
|
|
105
|
-
return json(res, 402, { error: { message: "No payment method configured.
|
|
135
|
+
return json(res, 402, { error: { message: "No payment method configured. Run `agent402-openclaw setup` to generate an x402 wallet, set AGENT402_WALLET_KEY, or set AGENT402_CREDITS_KEY to a credits key already issued.", type: "payment_required", code: "agent402_unconfigured" }, topup: `${upstream}/credits`, priceUsd: route.priceUsd });
|
|
106
136
|
}
|
|
107
137
|
const outbound = { ...body };
|
|
108
138
|
if (requested === AUTO_ID) delete outbound.model; else outbound.model = route.id;
|
|
@@ -113,6 +143,18 @@ export async function startProxy({ upstream = DEFAULT_UPSTREAM, creditsKey = nul
|
|
|
113
143
|
stats.forwarded++;
|
|
114
144
|
const passthrough = {};
|
|
115
145
|
for (const h of ["content-type", "x-credits-balance", "payment-receipt", "x-cache", "cache-control"]) { const v = up.headers.get(h); if (v) passthrough[h] = v; }
|
|
146
|
+
if (!up.ok && /json/i.test(passthrough["content-type"] || "")) {
|
|
147
|
+
const raw = await up.text();
|
|
148
|
+
let doc = null;
|
|
149
|
+
try { doc = JSON.parse(raw); } catch { /* not JSON after all: relayed as sent */ }
|
|
150
|
+
const mapped = refusalAsOpenAIError(up.status, doc);
|
|
151
|
+
if (mapped) {
|
|
152
|
+
const { "content-type": _ct, ...rest } = passthrough;
|
|
153
|
+
return json(res, up.status, mapped, rest);
|
|
154
|
+
}
|
|
155
|
+
res.writeHead(up.status, passthrough);
|
|
156
|
+
return res.end(raw);
|
|
157
|
+
}
|
|
116
158
|
res.writeHead(up.status, passthrough);
|
|
117
159
|
if (!up.body) return res.end();
|
|
118
160
|
const reader = up.body.getReader();
|