agent402-openclaw 0.5.4 → 0.5.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/package.json +1 -1
- package/proxy.js +17 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent402-openclaw",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
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 already issued, 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
|
@@ -140,6 +140,12 @@ export function refusalAsAnthropicError(status, doc) {
|
|
|
140
140
|
// body, any model id the gateway lists (dated Claude ids resolve to the live one).
|
|
141
141
|
export const MESSAGES_UPSTREAM_PATH = "/v1/metered/messages";
|
|
142
142
|
|
|
143
|
+
// Each client wire's own error shapes: mapRefusal turns an upstream refusal
|
|
144
|
+
// that carries an offer into the wire's error; error(type, message) is an
|
|
145
|
+
// error the proxy makes itself (a refused redirect, an unreachable upstream).
|
|
146
|
+
const OPENAI_WIRE = { mapRefusal: refusalAsOpenAIError, error: (type, message) => ({ error: { message, type } }) };
|
|
147
|
+
const ANTHROPIC_WIRE = { mapRefusal: refusalAsAnthropicError, error: (type, message) => ({ type: "error", error: { type, message } }) };
|
|
148
|
+
|
|
143
149
|
const json = (res, status, obj, headers = {}) => {
|
|
144
150
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", ...headers });
|
|
145
151
|
res.end(JSON.stringify(obj));
|
|
@@ -168,8 +174,8 @@ export async function startProxy({ upstream = DEFAULT_UPSTREAM, creditsKey = nul
|
|
|
168
174
|
|
|
169
175
|
// Pays and forwards one call. Streams pass through byte for byte; a refused
|
|
170
176
|
// paid call's offer-carrying JSON body is answered in the client wire's own
|
|
171
|
-
// error shape (mapRefusal); every other upstream body is relayed as sent.
|
|
172
|
-
async function forward(req, res, endpoint, outbound, extraHeaders,
|
|
177
|
+
// error shape (wire.mapRefusal); every other upstream body is relayed as sent.
|
|
178
|
+
async function forward(req, res, endpoint, outbound, extraHeaders, wire) {
|
|
173
179
|
const clientIdem = typeof req.headers["idempotency-key"] === "string" && /^[\w.:-]{8,128}$/.test(req.headers["idempotency-key"]) ? req.headers["idempotency-key"] : null;
|
|
174
180
|
const headers = { "content-type": "application/json", accept: req.headers.accept || "application/json", "idempotency-key": clientIdem || randomUUID(), "user-agent": `agent402-openclaw/${PKG_VERSION}`, ...extraHeaders };
|
|
175
181
|
if (key) headers.authorization = `Bearer ${key}`;
|
|
@@ -180,7 +186,7 @@ export async function startProxy({ upstream = DEFAULT_UPSTREAM, creditsKey = nul
|
|
|
180
186
|
stats.errors++;
|
|
181
187
|
const loc = up.headers.get("location");
|
|
182
188
|
const to = loc ? new URL(loc, `${upstream}${endpoint}`).href : null;
|
|
183
|
-
return json(res, 502,
|
|
189
|
+
return json(res, 502, wire.error("upstream_redirect_refused", `refused to follow a redirect on a paid call: ${upstream}${endpoint} answered HTTP ${up.status || "redirect"}${to ? ` pointing to ${to}` : ""}. Paid requests never follow redirects${to ? `; if that is the address you meant, set the upstream to ${new URL(to).origin}` : ""}.`));
|
|
184
190
|
}
|
|
185
191
|
stats.forwarded++;
|
|
186
192
|
const passthrough = {};
|
|
@@ -191,7 +197,7 @@ export async function startProxy({ upstream = DEFAULT_UPSTREAM, creditsKey = nul
|
|
|
191
197
|
const raw = await up.text();
|
|
192
198
|
let doc = null;
|
|
193
199
|
try { doc = JSON.parse(raw); } catch { /* not JSON after all: relayed as sent */ }
|
|
194
|
-
const mapped = mapRefusal(up.status, doc);
|
|
200
|
+
const mapped = wire.mapRefusal(up.status, doc);
|
|
195
201
|
if (mapped) {
|
|
196
202
|
const { "content-type": _ct, ...rest } = passthrough;
|
|
197
203
|
return json(res, up.status, mapped, rest);
|
|
@@ -207,6 +213,9 @@ export async function startProxy({ upstream = DEFAULT_UPSTREAM, creditsKey = nul
|
|
|
207
213
|
return res.end();
|
|
208
214
|
}
|
|
209
215
|
|
|
216
|
+
// forward() is awaited inside the try below, so a payment fetch that throws
|
|
217
|
+
// (an unreachable upstream, a refused signature) answers a 502 in the wire's
|
|
218
|
+
// own shape instead of escaping as an unhandled rejection.
|
|
210
219
|
const server = createServer(async (req, res) => {
|
|
211
220
|
stats.requests++;
|
|
212
221
|
const url = new URL(req.url, "http://localhost");
|
|
@@ -238,7 +247,7 @@ export async function startProxy({ upstream = DEFAULT_UPSTREAM, creditsKey = nul
|
|
|
238
247
|
}
|
|
239
248
|
const outbound = { ...body };
|
|
240
249
|
if (requested === AUTO_ID) delete outbound.model; else outbound.model = route.id;
|
|
241
|
-
return forward(req, res, route.endpoint, outbound, {},
|
|
250
|
+
return await forward(req, res, route.endpoint, outbound, {}, OPENAI_WIRE);
|
|
242
251
|
}
|
|
243
252
|
if (req.method === "POST" && url.pathname === "/v1/messages") {
|
|
244
253
|
const raw = await readBody(req);
|
|
@@ -253,13 +262,14 @@ export async function startProxy({ upstream = DEFAULT_UPSTREAM, creditsKey = nul
|
|
|
253
262
|
const outbound = { ...body, model: body.model.trim().replace(/^agent402\//, "") };
|
|
254
263
|
const extra = {};
|
|
255
264
|
for (const h of ["anthropic-version", "anthropic-beta"]) { const v = req.headers[h]; if (typeof v === "string" && v.length <= 512) extra[h] = v; }
|
|
256
|
-
return forward(req, res, MESSAGES_UPSTREAM_PATH, outbound, extra,
|
|
265
|
+
return await forward(req, res, MESSAGES_UPSTREAM_PATH, outbound, extra, ANTHROPIC_WIRE);
|
|
257
266
|
}
|
|
258
267
|
return json(res, 404, { error: { message: `No route for ${req.method} ${url.pathname}`, type: "invalid_request_error" } });
|
|
259
268
|
} catch (e) {
|
|
260
269
|
stats.errors++;
|
|
261
270
|
log(`[agent402-openclaw] ${req.method} ${url.pathname}: ${e?.message || e}`);
|
|
262
|
-
|
|
271
|
+
const wire = url.pathname === "/v1/messages" ? ANTHROPIC_WIRE : OPENAI_WIRE;
|
|
272
|
+
if (!res.headersSent) return json(res, 502, wire.error("upstream_error", `Upstream error: ${String(e?.message || e).slice(0, 200)}`));
|
|
263
273
|
try { res.end(); } catch { /* ignore */ }
|
|
264
274
|
}
|
|
265
275
|
});
|