agent402-openclaw 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/cli.js +64 -11
- package/index.js +17 -4
- package/openclaw.plugin.json +2 -2
- package/package.json +12 -2
- package/provider.js +10 -0
package/README.md
CHANGED
|
@@ -11,12 +11,21 @@ Guide: https://agent402.tools/guides/openclaw-model-provider
|
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
openclaw plugins install agent402-openclaw
|
|
14
|
-
|
|
14
|
+
npx agent402-openclaw setup --write # no key? it mints a wallet and prints the address to fund
|
|
15
15
|
openclaw gateway restart
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
With no credits key and no `AGENT402_WALLET_KEY`, `setup` generates an EVM
|
|
19
|
+
wallet into `~/.openclaw/agent402/wallet.key` (0600, the only copy, never
|
|
20
|
+
printed) and prints its address: send it USDC on Base and every call is paid
|
|
21
|
+
from it over x402. `agent402-openclaw wallet` shows the address and balance;
|
|
22
|
+
`--no-wallet` skips the generation. Prefer a card? Buy a pack at
|
|
23
|
+
https://agent402.tools/credits and run
|
|
24
|
+
`AGENT402_CREDITS_KEY=a402_... npx agent402-openclaw setup --write` (key by env,
|
|
25
|
+
or `--credits-key -` on stdin).
|
|
26
|
+
|
|
18
27
|
(`openclaw plugins install` copies the plugin into `~/.openclaw/extensions` and
|
|
19
|
-
does not link its CLI, hence `npx`.) `setup` stores
|
|
28
|
+
does not link its CLI, hence `npx`.) `setup` stores a credits key under
|
|
20
29
|
`~/.openclaw/agent402/credits.key` (0600) and prints the `openclaw.json` block;
|
|
21
30
|
add `--write` to merge it in. The primary model it writes is the cheapest
|
|
22
31
|
metered model that can hold OpenClaw's own prompt: OpenClaw sends roughly 70k
|
|
@@ -77,6 +86,7 @@ the key.
|
|
|
77
86
|
- `agent402-openclaw setup [--credits-key K | --credits-key - (stdin) | AGENT402_CREDITS_KEY env] [--write] [--port N] [--flat]`
|
|
78
87
|
- `agent402-openclaw proxy [--port N] [--upstream URL]`
|
|
79
88
|
- `agent402-openclaw doctor`
|
|
89
|
+
- `agent402-openclaw wallet [--rpc URL]` (address + USDC balance of the wallet the proxy pays from)
|
|
80
90
|
- `agent402-openclaw permit2-approve [--rpc URL]` (one-time USDC approval so the wallet pays actual usage over upto)
|
|
81
91
|
|
|
82
92
|
## What else the same key buys
|
|
@@ -84,7 +94,7 @@ the key.
|
|
|
84
94
|
The key or wallet that pays for chat also pays for the rest of the gateway and
|
|
85
95
|
the catalog, with one 402 shape and one receipt shape: OpenAI Responses and
|
|
86
96
|
Anthropic Messages wires, embeddings, rerank, images, video, speech,
|
|
87
|
-
transcription, grounded answers with citations, 500+
|
|
97
|
+
transcription, grounded answers with citations, 500+ tools over
|
|
88
98
|
MCP or HTTP, wallet-keyed memory, finished reports (dossiers, insider flow, 13F,
|
|
89
99
|
domain audits, token risk, deep research) and monitors, plus routing that buys
|
|
90
100
|
from proven external sellers on the agent's behalf.
|
package/cli.js
CHANGED
|
@@ -7,10 +7,10 @@ import { mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync, statSync
|
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
-
import { STATE_DIR, CREDITS_KEY_FILE, resolveCreditsKey, resolvePayFetch, USDC_BASE, DEFAULT_BASE_RPC, uptoReady } from "./index.js";
|
|
10
|
+
import { STATE_DIR, CREDITS_KEY_FILE, WALLET_KEY_FILE, resolveCreditsKey, resolveWalletKey, resolvePayFetch, USDC_BASE, DEFAULT_BASE_RPC, uptoReady } from "./index.js";
|
|
11
11
|
import { startProxy, loadRoutes, DEFAULT_UPSTREAM } from "./proxy.js";
|
|
12
12
|
import { providerModelsConfig, stripTrailingSlashes, defaultPrimary, AUTO_ID, OPENCLAW_MIN_INPUT_CHARS } from "./models.js";
|
|
13
|
-
import { DEFAULT_PORT, PROVIDER_ID } from "./provider.js";
|
|
13
|
+
import { DEFAULT_PORT, PLUGIN_ID, PROVIDER_ID } from "./provider.js";
|
|
14
14
|
|
|
15
15
|
const args = process.argv.slice(2);
|
|
16
16
|
const cmd = args[0] || "help";
|
|
@@ -38,14 +38,32 @@ function mergeInto(target, block, { port = DEFAULT_PORT } = {}) {
|
|
|
38
38
|
// on 8412 and OpenClaw dialing a port nobody listened on (real-install test).
|
|
39
39
|
if (port !== DEFAULT_PORT) {
|
|
40
40
|
t.plugins = t.plugins || {}; t.plugins.entries = t.plugins.entries || {};
|
|
41
|
-
t.plugins.entries[
|
|
41
|
+
t.plugins.entries[PLUGIN_ID] = { ...(t.plugins.entries[PLUGIN_ID] || {}), config: { ...(t.plugins.entries[PLUGIN_ID]?.config || {}), port } };
|
|
42
42
|
}
|
|
43
43
|
return t;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Mint a fresh EVM wallet into WALLET_KEY_FILE (0600). Needs viem (an optional
|
|
47
|
+
// peer, same as x402 payment itself); returns null when it is not installed so
|
|
48
|
+
// setup can fall back to the card path instead of failing.
|
|
49
|
+
async function generateWallet() {
|
|
50
|
+
try {
|
|
51
|
+
const [{ generatePrivateKey, privateKeyToAccount }] = await Promise.all([import("viem/accounts")]);
|
|
52
|
+
const pk = generatePrivateKey();
|
|
53
|
+
const account = privateKeyToAccount(pk);
|
|
54
|
+
mkdirSync(STATE_DIR(), { recursive: true });
|
|
55
|
+
writeFileSync(WALLET_KEY_FILE(), pk + "\n", { mode: 0o600, flag: "wx" }); // never overwrite an existing key
|
|
56
|
+
try { chmodSync(WALLET_KEY_FILE(), 0o600); } catch { /* best effort */ }
|
|
57
|
+
return { address: account.address };
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (e?.code === "EEXIST") { try { return { address: (await import("viem/accounts")).privateKeyToAccount(readFileSync(WALLET_KEY_FILE(), "utf8").trim()).address }; } catch { return null; } }
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
async function main() {
|
|
47
65
|
if (cmd === "help" || has("--help")) {
|
|
48
|
-
out("agent402-openclaw setup [--credits-key a402_...|-] [--write] [--flat] | proxy [--port N] [--upstream URL] [--flat] | doctor | permit2-approve [--rpc URL]");
|
|
66
|
+
out("agent402-openclaw setup [--credits-key a402_...|-] [--no-wallet] [--write] [--flat] | proxy [--port N] [--upstream URL] [--flat] | wallet [--rpc URL] | doctor | permit2-approve [--rpc URL]");
|
|
49
67
|
return 0;
|
|
50
68
|
}
|
|
51
69
|
const upstream = stripTrailingSlashes(opt("--upstream") || process.env.AGENT402_UPSTREAM || DEFAULT_UPSTREAM);
|
|
@@ -66,8 +84,26 @@ async function main() {
|
|
|
66
84
|
writeFileSync(CREDITS_KEY_FILE(), key + "\n", { mode: 0o600 });
|
|
67
85
|
try { chmodSync(CREDITS_KEY_FILE(), 0o600); } catch { /* best effort */ }
|
|
68
86
|
out(`stored credits key at ${CREDITS_KEY_FILE()} (0600)`);
|
|
69
|
-
} else if (!resolveCreditsKey()) {
|
|
70
|
-
|
|
87
|
+
} else if (!resolveCreditsKey() && !resolveWalletKey()) {
|
|
88
|
+
// No payment method at all: mint a wallet right here, print the address,
|
|
89
|
+
// and the user funds it with USDC on Base. Same first-run shape as a
|
|
90
|
+
// per-token router that prints a wallet at install; the card path
|
|
91
|
+
// (credits key) stays available. --no-wallet keeps the old behaviour.
|
|
92
|
+
if (has("--no-wallet")) {
|
|
93
|
+
out(`no payment method yet: buy a pack by card at ${upstream}/credits and rerun with --credits-key a402_..., or set AGENT402_WALLET_KEY`);
|
|
94
|
+
} else {
|
|
95
|
+
const w = await generateWallet();
|
|
96
|
+
if (w) {
|
|
97
|
+
out(`no credits key found, so a wallet was generated for you:`);
|
|
98
|
+
out(` address: ${w.address}`);
|
|
99
|
+
out(` key: ${WALLET_KEY_FILE()} (0600; back it up, it is the only copy)`);
|
|
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
|
+
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(`prefer a card? buy a pack at ${upstream}/credits and rerun with --credits-key a402_...`);
|
|
103
|
+
} else {
|
|
104
|
+
out(`no payment method yet: buy a pack by card at ${upstream}/credits and rerun with --credits-key a402_..., or install viem + @x402/fetch + @x402/evm and rerun to generate a wallet`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
71
107
|
}
|
|
72
108
|
let routes;
|
|
73
109
|
try { routes = await loadRoutes(upstream, fetch, { pricing }); } catch (e) { console.error(`could not read ${upstream}/v1/models: ${e?.message || e}`); return 2; }
|
|
@@ -111,8 +147,8 @@ async function main() {
|
|
|
111
147
|
// One-time USDC -> Permit2 approval on Base so the wallet can pay ACTUAL
|
|
112
148
|
// usage over `upto` instead of the per-request quote over `exact`.
|
|
113
149
|
if (cmd === "permit2-approve") {
|
|
114
|
-
const pk = (
|
|
115
|
-
if (
|
|
150
|
+
const pk = resolveWalletKey();
|
|
151
|
+
if (!pk) { console.error("no wallet: set AGENT402_WALLET_KEY (0x + 64 hex) or run `agent402-openclaw setup` to generate one"); return 2; }
|
|
116
152
|
try {
|
|
117
153
|
const [{ createPermit2ApprovalTx, getPermit2AllowanceReadParams }, { createWalletClient, createPublicClient, http }, { privateKeyToAccount }, { base }] = await Promise.all([import("@x402/evm/upto/client"), import("viem"), import("viem/accounts"), import("viem/chains")]);
|
|
118
154
|
const account = privateKeyToAccount(pk);
|
|
@@ -130,15 +166,32 @@ async function main() {
|
|
|
130
166
|
} catch (e) { console.error(`permit2-approve failed: ${String(e?.message || e).slice(0, 300)}`); return 1; }
|
|
131
167
|
}
|
|
132
168
|
|
|
169
|
+
// Address + USDC balance of the wallet the proxy pays from. Read-only.
|
|
170
|
+
if (cmd === "wallet") {
|
|
171
|
+
const pk = resolveWalletKey();
|
|
172
|
+
if (!pk) { console.error("no wallet: run `agent402-openclaw setup` to generate one, or set AGENT402_WALLET_KEY"); return 2; }
|
|
173
|
+
try {
|
|
174
|
+
const [{ createPublicClient, http, erc20Abi, formatUnits }, { privateKeyToAccount }, { base }] = await Promise.all([import("viem"), import("viem/accounts"), import("viem/chains")]);
|
|
175
|
+
const account = privateKeyToAccount(pk);
|
|
176
|
+
const pub = createPublicClient({ chain: base, transport: http((opt("--rpc") || process.env.AGENT402_BASE_RPC || DEFAULT_BASE_RPC).trim()) });
|
|
177
|
+
const bal = await pub.readContract({ address: USDC_BASE, abi: erc20Abi, functionName: "balanceOf", args: [account.address] });
|
|
178
|
+
out(`address: ${account.address}`);
|
|
179
|
+
out(`USDC on Base: $${formatUnits(bal, 6)}`);
|
|
180
|
+
if (bal === 0n) out(`fund it: send USDC on Base (eip155:8453) to ${account.address}`);
|
|
181
|
+
return 0;
|
|
182
|
+
} catch (e) { console.error(`wallet: ${String(e?.message || e).slice(0, 200)}`); return 1; }
|
|
183
|
+
}
|
|
184
|
+
|
|
133
185
|
if (cmd === "doctor") {
|
|
134
186
|
const key = resolveCreditsKey();
|
|
187
|
+
const walletPk = resolveWalletKey();
|
|
135
188
|
out(`upstream: ${upstream}`);
|
|
136
189
|
out(`credits key: ${key ? `present (${key.slice(0, 8)}…)` : "none"}`);
|
|
137
|
-
out(`wallet key: ${
|
|
138
|
-
if (
|
|
190
|
+
out(`wallet key: ${walletPk ? "present" : "none"}`);
|
|
191
|
+
if (walletPk) {
|
|
139
192
|
try {
|
|
140
193
|
const [{ getPermit2AllowanceReadParams }, { createPublicClient, http }, { privateKeyToAccount }, { base }] = await Promise.all([import("@x402/evm/upto/client"), import("viem"), import("viem/accounts"), import("viem/chains")]);
|
|
141
|
-
const account = privateKeyToAccount(
|
|
194
|
+
const account = privateKeyToAccount(walletPk);
|
|
142
195
|
const pub = createPublicClient({ chain: base, transport: http((process.env.AGENT402_BASE_RPC || DEFAULT_BASE_RPC).trim()) });
|
|
143
196
|
const allowance = await pub.readContract(getPermit2AllowanceReadParams({ tokenAddress: USDC_BASE, ownerAddress: account.address }));
|
|
144
197
|
out(`wallet ${account.address}: ${uptoReady(allowance) ? "pays actual usage (upto; Permit2 approved)" : "pays the per-request quote (exact) - run permit2-approve once to pay actual usage"}`);
|
package/index.js
CHANGED
|
@@ -5,12 +5,25 @@
|
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
-
import { buildProvider, setActiveProxy, setCachedRoutes, DEFAULT_PORT,
|
|
8
|
+
import { buildProvider, setActiveProxy, setCachedRoutes, DEFAULT_PORT, PLUGIN_ID } from "./provider.js";
|
|
9
9
|
import { startProxy, loadRoutes, DEFAULT_UPSTREAM } from "./proxy.js";
|
|
10
10
|
import { stripTrailingSlashes } from "./models.js";
|
|
11
11
|
|
|
12
12
|
export const STATE_DIR = () => join(process.env.AGENT402_OPENCLAW_HOME || join(homedir(), ".openclaw"), "agent402");
|
|
13
13
|
export const CREDITS_KEY_FILE = () => join(STATE_DIR(), "credits.key");
|
|
14
|
+
export const WALLET_KEY_FILE = () => join(STATE_DIR(), "wallet.key");
|
|
15
|
+
|
|
16
|
+
/** Wallet key precedence: plugin config > env > ~/.openclaw/agent402/wallet.key
|
|
17
|
+
* (the wallet `setup` generates when no payment method exists yet). Only a
|
|
18
|
+
* well-formed 0x + 64 hex key counts; anything else is "no wallet". */
|
|
19
|
+
export function resolveWalletKey(pluginConfig = {}) {
|
|
20
|
+
const cands = [
|
|
21
|
+
typeof pluginConfig.walletKey === "string" ? pluginConfig.walletKey.trim() : "",
|
|
22
|
+
(process.env.AGENT402_WALLET_KEY || "").trim(),
|
|
23
|
+
];
|
|
24
|
+
try { if (existsSync(WALLET_KEY_FILE())) cands.push(readFileSync(WALLET_KEY_FILE(), "utf8").trim()); } catch { /* unreadable */ }
|
|
25
|
+
return cands.find((k) => /^0x[0-9a-fA-F]{64}$/.test(k)) || null;
|
|
26
|
+
}
|
|
14
27
|
|
|
15
28
|
/** Credits key precedence: plugin config > env > ~/.openclaw/agent402/credits.key. */
|
|
16
29
|
export function resolveCreditsKey(pluginConfig = {}) {
|
|
@@ -42,8 +55,8 @@ export function selectAccept(accepts, { preferUpto = true } = {}) {
|
|
|
42
55
|
export const uptoReady = (allowance) => { try { return BigInt(allowance ?? 0) >= PERMIT2_READY_MIN; } catch { return false; } };
|
|
43
56
|
|
|
44
57
|
export async function resolvePayFetch(pluginConfig = {}, log = () => {}) {
|
|
45
|
-
const pk = (pluginConfig
|
|
46
|
-
if (
|
|
58
|
+
const pk = resolveWalletKey(pluginConfig);
|
|
59
|
+
if (!pk) return null;
|
|
47
60
|
try {
|
|
48
61
|
const [{ wrapFetchWithPayment, x402Client }, { privateKeyToAccount }, { toClientEvmSigner }, { registerExactEvmScheme }] = await Promise.all([
|
|
49
62
|
import("@x402/fetch"), import("viem/accounts"), import("@x402/evm"), import("@x402/evm/exact/client"),
|
|
@@ -81,7 +94,7 @@ let started = null;
|
|
|
81
94
|
let registeredOnce = false;
|
|
82
95
|
|
|
83
96
|
const plugin = {
|
|
84
|
-
id:
|
|
97
|
+
id: PLUGIN_ID,
|
|
85
98
|
name: "Agent402",
|
|
86
99
|
description: "Agent402 model provider: routed + explicit models at a flat per-call price, paid by card or USDC.",
|
|
87
100
|
register(api) {
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "agent402",
|
|
2
|
+
"id": "agent402-openclaw",
|
|
3
3
|
"name": "Agent402",
|
|
4
4
|
"description": "Agent402 model provider: auto-routed and explicit frontier models at a flat per-call price, paid by card (prepaid credits key) or in USDC over x402. Same key reaches 500+ pay-per-call tools.",
|
|
5
5
|
"activation": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"walletKey": {
|
|
21
21
|
"type": "string",
|
|
22
|
-
"description": "Optional EVM private key for x402 USDC payment (needs @x402/fetch + @x402/evm + viem installed)."
|
|
22
|
+
"description": "Optional EVM private key for x402 USDC payment (needs @x402/fetch + @x402/evm + viem installed). Precedence: this, then AGENT402_WALLET_KEY, then the wallet `setup` generates at ~/.openclaw/agent402/wallet.key."
|
|
23
23
|
},
|
|
24
24
|
"port": {
|
|
25
25
|
"type": "number",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent402-openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "OpenClaw model provider for Agent402: auto-routed and explicit LLM models at a flat per-call price, paid by card (prepaid credits key) or in USDC over x402 through a local proxy. Zero dependencies; x402 wallet payment is an optional peer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -24,7 +24,17 @@
|
|
|
24
24
|
"openclaw": {
|
|
25
25
|
"extensions": [
|
|
26
26
|
"./index.js"
|
|
27
|
-
]
|
|
27
|
+
],
|
|
28
|
+
"compat": {
|
|
29
|
+
"pluginApi": ">=2026.8.1"
|
|
30
|
+
},
|
|
31
|
+
"build": {
|
|
32
|
+
"openclawVersion": "2026.8.1"
|
|
33
|
+
},
|
|
34
|
+
"install": {
|
|
35
|
+
"minHostVersion": ">=2026.8.1",
|
|
36
|
+
"npmSpec": "agent402-openclaw"
|
|
37
|
+
}
|
|
28
38
|
},
|
|
29
39
|
"engines": {
|
|
30
40
|
"node": ">=18"
|
package/provider.js
CHANGED
|
@@ -4,7 +4,17 @@
|
|
|
4
4
|
// would 402 without the proxy's payment step in between).
|
|
5
5
|
import { providerModelsConfig, routesFromCatalog } from "./models.js";
|
|
6
6
|
|
|
7
|
+
// The MODEL PROVIDER id: what a user writes in openclaw.json ("agent402/auto").
|
|
8
|
+
// User-facing and stable - renaming it would break every existing config.
|
|
7
9
|
export const PROVIDER_ID = "agent402";
|
|
10
|
+
// The PLUGIN id, which is a different thing: OpenClaw keys plugins.entries by
|
|
11
|
+
// the MANIFEST id, and since 2026.8.1 it requires that id to equal the npm
|
|
12
|
+
// package name ("Plugin manifest id ... differs from npm package name ...").
|
|
13
|
+
// They were both "agent402", so nothing distinguished them until the host
|
|
14
|
+
// started checking. Writing config under the provider id while the host reads
|
|
15
|
+
// it under the manifest id is the same defect as the 2026-08-26 --port bug,
|
|
16
|
+
// where setup moved the baseUrl and the service kept reading the old key.
|
|
17
|
+
export const PLUGIN_ID = "agent402-openclaw";
|
|
8
18
|
export const DEFAULT_PORT = 8412;
|
|
9
19
|
|
|
10
20
|
let activeProxy = null;
|