agent402-openclaw 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +17 -3
  2. package/package.json +1 -1
  3. package/proxy.js +45 -2
package/README.md CHANGED
@@ -37,8 +37,22 @@ prompts but is never made primary (OpenClaw would refuse every turn as a
37
37
  context overflow). The plugin starts a loopback proxy (`127.0.0.1:8412`) that
38
38
  pays Agent402 and forwards; OpenClaw only ever sees a local OpenAI-compatible URL.
39
39
 
40
- Tested in CI against a real `openclaw@latest` install: plugin install, setup,
41
- model listing, gateway boot and one agent turn (`test-real-install.js`).
40
+ The plugin is also on ClawHub: `openclaw plugins install clawhub:agent402-openclaw`
41
+ installs the same package and shows its ClawHub security audit first.
42
+
43
+ OpenClaw 2026.10.1 and later reserve the verified-source badge for plugins in
44
+ OpenClaw's own official inventory, so `openclaw plugins doctor` and `plugins
45
+ inspect` list agent402 with "OpenClaw can't verify where this plugin came from
46
+ ... reason=provenance-invalid" whichever way it was installed (npm, ClawHub or a
47
+ local archive). That notice is OpenClaw's verdict on every community plugin. The
48
+ plugin loads, the gateway starts its proxy and turns are served as before: it
49
+ registers a model provider and a service, and uses no feature OpenClaw keeps for
50
+ trusted plugins.
51
+
52
+ Tested in CI against real OpenClaw installs (the newest proven release and the
53
+ one before it, plus a daily run against the newest published one): plugin
54
+ install from a packed archive and from the npm registry, doctor, setup, model
55
+ listing, gateway boot and one agent turn (`test-real-install.js`).
42
56
 
43
57
  No OpenClaw? `agent402-openclaw proxy` runs the proxy alone; point any OpenAI
44
58
  client at `http://127.0.0.1:8412/v1` with model `auto`.
@@ -115,7 +129,7 @@ MCP or HTTP, wallet-keyed memory, finished reports (dossiers, insider flow, 13F,
115
129
  domain audits, token risk, deep research) and monitors, plus routing that buys
116
130
  from external sellers and resells the result.
117
131
 
118
- Why pay here, with every claim linked to its proof: https://agent402.tools/why.
132
+ Why pay here, with every claim linked to its proof: https://agent402.tools/#why.
119
133
  Usage is priced under a ceiling quoted before you pay, a failed call is not
120
134
  charged and the receipt proves it, a keyed retry on a credits key is not
121
135
  debited twice, and uptime and transactions are published from outside
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent402-openclaw",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
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",
package/proxy.js CHANGED
@@ -47,9 +47,52 @@ export const DEFAULT_UPSTREAM = "https://agent402.tools";
47
47
  export const PKG_VERSION = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version;
48
48
  const MAX_BODY = 2 * 1024 * 1024;
49
49
 
50
+ // The model catalog read (GET /v1/models) settles nothing, so a 503 the
51
+ // server sheds under load is safe to repeat. It is retried only when it names
52
+ // its own delay (a Retry-After header or a retryAfterSeconds body field), at
53
+ // most DISCOVERY_ATTEMPTS tries in all, each wait capped at
54
+ // DISCOVERY_MAX_WAIT_MS. A paid call never goes through here: a repeat of
55
+ // one could settle twice.
56
+ const DISCOVERY_ATTEMPTS = 3;
57
+ const DISCOVERY_MAX_WAIT_MS = 5000;
58
+
59
+ async function shedRetryWaitMs(r) {
60
+ if (!r || r.status !== 503) return null;
61
+ const header = r.headers && typeof r.headers.get === "function" ? r.headers.get("retry-after") : null;
62
+ let seconds = header != null && String(header).trim() !== "" ? Number(header) : NaN;
63
+ if (!Number.isFinite(seconds)) {
64
+ try {
65
+ const body = await (typeof r.clone === "function" ? r.clone() : r).json();
66
+ seconds = Number(body && body.retryAfterSeconds);
67
+ } catch { seconds = NaN; }
68
+ }
69
+ if (!Number.isFinite(seconds) || seconds < 0) return null;
70
+ return Math.min(seconds * 1000, DISCOVERY_MAX_WAIT_MS);
71
+ }
72
+
73
+ // Resolves with the 2xx response, or throws `${label}: HTTP <status>` (plus
74
+ // "after N attempts" when it retried) with `.status` and `.attempts` set.
75
+ // `init` may be a function, so each attempt gets its own timeout signal.
76
+ async function discoveryFetch(fetchImpl, label, url, init) {
77
+ let r;
78
+ let attempts = 0;
79
+ for (;;) {
80
+ attempts++;
81
+ r = await fetchImpl(url, typeof init === "function" ? init() : init);
82
+ if (r.ok) return r;
83
+ if (attempts >= DISCOVERY_ATTEMPTS) break;
84
+ const wait = await shedRetryWaitMs(r);
85
+ if (wait == null) break;
86
+ await new Promise((resolve) => setTimeout(resolve, wait));
87
+ }
88
+ const err = new Error(`${label}: HTTP ${r.status}${attempts > 1 ? ` after ${attempts} attempts` : ""}`);
89
+ err.status = r.status;
90
+ err.attempts = attempts;
91
+ throw err;
92
+ }
93
+
50
94
  export async function loadRoutes(upstream, fetchImpl = fetch, { pricing = "metered" } = {}) {
51
- const r = await fetchImpl(`${upstream}/v1/models`, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(15_000) });
52
- if (!r.ok) throw new Error(`GET /v1/models -> HTTP ${r.status}`);
95
+ const r = await discoveryFetch(fetchImpl, "GET /v1/models", `${upstream}/v1/models`, () => ({ headers: { accept: "application/json" }, signal: AbortSignal.timeout(15_000) }));
53
96
  return routesFromCatalog(await r.json(), { pricing });
54
97
  }
55
98