@blockrun/clawrouter 0.12.281 → 0.12.283
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 +20 -20
- package/dist/cli.js +515 -150
- package/dist/index.d.ts +14 -0
- package/dist/index.js +480 -115
- package/docs/9-free-ai-models-zero-cost-blockrun.md +5 -5
- package/docs/anthropic-cost-savings.md +1 -1
- package/docs/anthropic-third-party-harness-changes.md +3 -3
- package/docs/clawrouter-vs-openrouter-llm-routing-comparison.md +3 -3
- package/docs/configuration.md +6 -0
- package/package.json +2 -2
- package/skills/clawrouter/SKILL.md +3 -3
package/dist/cli.js
CHANGED
|
@@ -32315,6 +32315,11 @@ function getSharedSpendControl() {
|
|
|
32315
32315
|
host.__clawrouterSharedSpendControl ??= new SpendControl();
|
|
32316
32316
|
return host.__clawrouterSharedSpendControl;
|
|
32317
32317
|
}
|
|
32318
|
+
function parseQuotedAmountMicros(selected) {
|
|
32319
|
+
const raw = selected.amount ?? selected.maxAmountRequired;
|
|
32320
|
+
if (typeof raw !== "string" || !CANONICAL_AMOUNT.test(raw)) return void 0;
|
|
32321
|
+
return BigInt(raw);
|
|
32322
|
+
}
|
|
32318
32323
|
function parseQuotedAmountUsd(selected) {
|
|
32319
32324
|
const raw = selected.amount ?? selected.maxAmountRequired;
|
|
32320
32325
|
if (typeof raw !== "string" || !CANONICAL_AMOUNT.test(raw)) {
|
|
@@ -33080,6 +33085,16 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
33080
33085
|
return est === void 0 ? void 0 : Number(est);
|
|
33081
33086
|
};
|
|
33082
33087
|
const needMicros = estimateMicros();
|
|
33088
|
+
const notifyDeclinedPayment = () => {
|
|
33089
|
+
if (!options?.onPaymentDeclined) return;
|
|
33090
|
+
try {
|
|
33091
|
+
options.onPaymentDeclined();
|
|
33092
|
+
} catch (error) {
|
|
33093
|
+
console.error(
|
|
33094
|
+
`[ClawRouter] onPaymentDeclined callback failed: ${error instanceof Error ? error.message : String(error)}`
|
|
33095
|
+
);
|
|
33096
|
+
}
|
|
33097
|
+
};
|
|
33083
33098
|
const cached = !options?.skipPreAuth ? cache2.get(cacheKey2) : void 0;
|
|
33084
33099
|
const preAuthCovers = cached !== void 0 && Date.now() - cached.cachedAt < ttlMs && cached.coverMicros !== void 0 && needMicros !== void 0 && needMicros <= cached.coverMicros;
|
|
33085
33100
|
if (preAuthCovers) {
|
|
@@ -33098,6 +33113,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
33098
33113
|
return response2;
|
|
33099
33114
|
}
|
|
33100
33115
|
paymentInFlight = false;
|
|
33116
|
+
notifyDeclinedPayment();
|
|
33101
33117
|
cache2.delete(cacheKey2);
|
|
33102
33118
|
} catch (err) {
|
|
33103
33119
|
cache2.delete(cacheKey2);
|
|
@@ -33142,6 +33158,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
33142
33158
|
clonedRequest.headers.set(key3, value);
|
|
33143
33159
|
}
|
|
33144
33160
|
const paidResponse = await baseFetch(clonedRequest);
|
|
33161
|
+
if (paidResponse.status === 402) notifyDeclinedPayment();
|
|
33145
33162
|
notifyAcceptedPayment(paidResponse, payload);
|
|
33146
33163
|
return paidResponse;
|
|
33147
33164
|
};
|
|
@@ -64789,6 +64806,132 @@ var init_api_key = __esm({
|
|
|
64789
64806
|
}
|
|
64790
64807
|
});
|
|
64791
64808
|
|
|
64809
|
+
// src/session-spend.ts
|
|
64810
|
+
function parseMicros(raw) {
|
|
64811
|
+
if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
|
|
64812
|
+
return BigInt(raw);
|
|
64813
|
+
}
|
|
64814
|
+
function usdToMicros(usd2) {
|
|
64815
|
+
return BigInt(Math.round(usd2 * 1e6));
|
|
64816
|
+
}
|
|
64817
|
+
function microsToUsd(micros) {
|
|
64818
|
+
return Number(micros) / 1e6;
|
|
64819
|
+
}
|
|
64820
|
+
var SessionSpend, CostCapError;
|
|
64821
|
+
var init_session_spend = __esm({
|
|
64822
|
+
"src/session-spend.ts"() {
|
|
64823
|
+
"use strict";
|
|
64824
|
+
init_spend_control();
|
|
64825
|
+
SessionSpend = class _SessionSpend {
|
|
64826
|
+
constructor(store, sessionId, capMicros, holdMicros) {
|
|
64827
|
+
this.store = store;
|
|
64828
|
+
this.sessionId = sessionId;
|
|
64829
|
+
this.capMicros = capMicros;
|
|
64830
|
+
this.heldMicros = holdMicros;
|
|
64831
|
+
if (holdMicros > 0n) store.adjustSessionHold(sessionId, holdMicros);
|
|
64832
|
+
}
|
|
64833
|
+
store;
|
|
64834
|
+
sessionId;
|
|
64835
|
+
capMicros;
|
|
64836
|
+
/** Budget this request still holds against the session (strict mode only). */
|
|
64837
|
+
heldMicros;
|
|
64838
|
+
/** Sum of payments signed by this request that still count. */
|
|
64839
|
+
signedMicros = 0n;
|
|
64840
|
+
/** The most recent signature not yet settled or voided — the one a decline undoes. */
|
|
64841
|
+
lastSignedMicros = 0n;
|
|
64842
|
+
released = false;
|
|
64843
|
+
/**
|
|
64844
|
+
* Strict mode: admit the request only if the session's spend, plus what its
|
|
64845
|
+
* other in-flight requests hold, plus this request's estimate fits the cap —
|
|
64846
|
+
* and hold the estimate in the same step.
|
|
64847
|
+
*/
|
|
64848
|
+
static admit(store, sessionId, capMicros, estimateMicros) {
|
|
64849
|
+
const spentMicros = store.getSessionCostMicros(sessionId);
|
|
64850
|
+
const heldMicros = store.getSessionHoldMicros(sessionId);
|
|
64851
|
+
if (spentMicros + heldMicros + estimateMicros > capMicros) {
|
|
64852
|
+
return { admitted: false, spentMicros, heldMicros, estimateMicros };
|
|
64853
|
+
}
|
|
64854
|
+
return {
|
|
64855
|
+
admitted: true,
|
|
64856
|
+
spend: new _SessionSpend(store, sessionId, capMicros, estimateMicros)
|
|
64857
|
+
};
|
|
64858
|
+
}
|
|
64859
|
+
/** Graceful mode: track signed spend, enforce nothing. */
|
|
64860
|
+
static track(store, sessionId) {
|
|
64861
|
+
return new _SessionSpend(store, sessionId, void 0, 0n);
|
|
64862
|
+
}
|
|
64863
|
+
get isStrict() {
|
|
64864
|
+
return this.capMicros !== void 0;
|
|
64865
|
+
}
|
|
64866
|
+
/**
|
|
64867
|
+
* Before a payment is created: may this request sign `quoteMicros`?
|
|
64868
|
+
* Spend already recorded (including this request's own earlier signatures)
|
|
64869
|
+
* plus what OTHER in-flight requests hold, plus the quote, must fit the cap.
|
|
64870
|
+
* Always true in graceful mode.
|
|
64871
|
+
*/
|
|
64872
|
+
coverPayment(quoteMicros) {
|
|
64873
|
+
if (this.capMicros === void 0) return true;
|
|
64874
|
+
const spent = this.store.getSessionCostMicros(this.sessionId);
|
|
64875
|
+
const heldByOthers = this.store.getSessionHoldMicros(this.sessionId) - this.heldMicros;
|
|
64876
|
+
if (spent + heldByOthers + quoteMicros > this.capMicros) return false;
|
|
64877
|
+
if (quoteMicros > this.heldMicros) {
|
|
64878
|
+
this.store.adjustSessionHold(this.sessionId, quoteMicros - this.heldMicros);
|
|
64879
|
+
this.heldMicros = quoteMicros;
|
|
64880
|
+
}
|
|
64881
|
+
return true;
|
|
64882
|
+
}
|
|
64883
|
+
/** A payment was signed: it is spend now, and replaces part of the hold. */
|
|
64884
|
+
recordSigned(amountMicros) {
|
|
64885
|
+
if (amountMicros <= 0n) return;
|
|
64886
|
+
this.signedMicros += amountMicros;
|
|
64887
|
+
this.lastSignedMicros = amountMicros;
|
|
64888
|
+
this.store.addSessionCost(this.sessionId, amountMicros);
|
|
64889
|
+
this.shrinkHold(amountMicros);
|
|
64890
|
+
}
|
|
64891
|
+
/**
|
|
64892
|
+
* The gateway answered the payment just signed with a 402: nothing settled.
|
|
64893
|
+
* Payments within one request are signed and sent one at a time, so the
|
|
64894
|
+
* decline always belongs to the latest signature. Undone at most once.
|
|
64895
|
+
*/
|
|
64896
|
+
voidLastSigned() {
|
|
64897
|
+
const undo = this.lastSignedMicros;
|
|
64898
|
+
this.lastSignedMicros = 0n;
|
|
64899
|
+
if (undo <= 0n) return;
|
|
64900
|
+
this.signedMicros -= undo;
|
|
64901
|
+
this.store.addSessionCost(this.sessionId, -undo);
|
|
64902
|
+
}
|
|
64903
|
+
/**
|
|
64904
|
+
* An attempt succeeded. When nothing was signed for this request (the API
|
|
64905
|
+
* key rail), the local estimate is the only record of the spend. When a
|
|
64906
|
+
* payment was signed, it is already recorded at its real amount.
|
|
64907
|
+
*/
|
|
64908
|
+
recordSuccess(estimateMicros) {
|
|
64909
|
+
if (this.signedMicros > 0n || estimateMicros <= 0n) return;
|
|
64910
|
+
this.store.addSessionCost(this.sessionId, estimateMicros);
|
|
64911
|
+
this.shrinkHold(estimateMicros);
|
|
64912
|
+
}
|
|
64913
|
+
/** The request is over: return whatever it still holds. Idempotent. */
|
|
64914
|
+
release() {
|
|
64915
|
+
if (this.released) return;
|
|
64916
|
+
this.released = true;
|
|
64917
|
+
this.shrinkHold(this.heldMicros);
|
|
64918
|
+
}
|
|
64919
|
+
shrinkHold(byMicros) {
|
|
64920
|
+
const drop = byMicros < this.heldMicros ? byMicros : this.heldMicros;
|
|
64921
|
+
if (drop <= 0n) return;
|
|
64922
|
+
this.heldMicros -= drop;
|
|
64923
|
+
this.store.adjustSessionHold(this.sessionId, -drop);
|
|
64924
|
+
}
|
|
64925
|
+
};
|
|
64926
|
+
CostCapError = class extends SpendPolicyError {
|
|
64927
|
+
constructor(reason) {
|
|
64928
|
+
super(reason);
|
|
64929
|
+
this.name = "CostCapError";
|
|
64930
|
+
}
|
|
64931
|
+
};
|
|
64932
|
+
}
|
|
64933
|
+
});
|
|
64934
|
+
|
|
64792
64935
|
// src/compression/types.ts
|
|
64793
64936
|
var DEFAULT_COMPRESSION_CONFIG;
|
|
64794
64937
|
var init_types2 = __esm({
|
|
@@ -65636,6 +65779,9 @@ var init_session = __esm({
|
|
|
65636
65779
|
};
|
|
65637
65780
|
SessionStore = class {
|
|
65638
65781
|
sessions = /* @__PURE__ */ new Map();
|
|
65782
|
+
// Kept apart from `sessions`: a hold belongs to requests still running, so it
|
|
65783
|
+
// must not vanish when the idle-session sweep drops an entry mid-request.
|
|
65784
|
+
sessionHolds = /* @__PURE__ */ new Map();
|
|
65639
65785
|
config;
|
|
65640
65786
|
cleanupInterval = null;
|
|
65641
65787
|
constructor(config = {}) {
|
|
@@ -65811,14 +65957,34 @@ var init_session = __esm({
|
|
|
65811
65957
|
this.sessions.set(sessionId, entry);
|
|
65812
65958
|
}
|
|
65813
65959
|
entry.sessionCostMicros += additionalMicros;
|
|
65960
|
+
if (entry.sessionCostMicros < 0n) entry.sessionCostMicros = 0n;
|
|
65814
65961
|
}
|
|
65815
65962
|
/**
|
|
65816
65963
|
* Get the total accumulated cost for a session in USD.
|
|
65817
65964
|
*/
|
|
65818
65965
|
getSessionCostUsd(sessionId) {
|
|
65819
|
-
|
|
65820
|
-
|
|
65821
|
-
|
|
65966
|
+
return Number(this.getSessionCostMicros(sessionId)) / 1e6;
|
|
65967
|
+
}
|
|
65968
|
+
/**
|
|
65969
|
+
* Get the total accumulated cost for a session in USDC micros.
|
|
65970
|
+
*/
|
|
65971
|
+
getSessionCostMicros(sessionId) {
|
|
65972
|
+
return this.sessions.get(sessionId)?.sessionCostMicros ?? 0n;
|
|
65973
|
+
}
|
|
65974
|
+
/**
|
|
65975
|
+
* Budget held by a session's in-flight requests (USDC micros) — admitted
|
|
65976
|
+
* against the strict cap but not yet spent. See session-spend.ts.
|
|
65977
|
+
*/
|
|
65978
|
+
getSessionHoldMicros(sessionId) {
|
|
65979
|
+
return this.sessionHolds.get(sessionId) ?? 0n;
|
|
65980
|
+
}
|
|
65981
|
+
/**
|
|
65982
|
+
* Grow (positive) or release (negative) a session's in-flight hold.
|
|
65983
|
+
*/
|
|
65984
|
+
adjustSessionHold(sessionId, deltaMicros) {
|
|
65985
|
+
const next = (this.sessionHolds.get(sessionId) ?? 0n) + deltaMicros;
|
|
65986
|
+
if (next > 0n) this.sessionHolds.set(sessionId, next);
|
|
65987
|
+
else this.sessionHolds.delete(sessionId);
|
|
65822
65988
|
}
|
|
65823
65989
|
/**
|
|
65824
65990
|
* Stop the cleanup interval.
|
|
@@ -66178,6 +66344,156 @@ ${lines.join("\n")}`;
|
|
|
66178
66344
|
}
|
|
66179
66345
|
});
|
|
66180
66346
|
|
|
66347
|
+
// src/local-guard.ts
|
|
66348
|
+
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2, realpathSync } from "fs";
|
|
66349
|
+
import { homedir as homedir7 } from "os";
|
|
66350
|
+
import { join as join10 } from "path";
|
|
66351
|
+
function isLocalHostname(hostname) {
|
|
66352
|
+
const h = hostname.toLowerCase().replace(/\.$/, "");
|
|
66353
|
+
return h === "localhost" || h === "127.0.0.1" || h === "[::1]" || h === "::1";
|
|
66354
|
+
}
|
|
66355
|
+
function untrustedRequestReason(req) {
|
|
66356
|
+
const host = req.headers.host;
|
|
66357
|
+
if (host) {
|
|
66358
|
+
let hostname;
|
|
66359
|
+
try {
|
|
66360
|
+
hostname = new URL(`http://${host}`).hostname;
|
|
66361
|
+
} catch {
|
|
66362
|
+
return "malformed Host header";
|
|
66363
|
+
}
|
|
66364
|
+
if (!isLocalHostname(hostname)) return `non-local Host header (${hostname})`;
|
|
66365
|
+
}
|
|
66366
|
+
const origin2 = req.headers.origin;
|
|
66367
|
+
if (Array.isArray(origin2)) return "multiple Origin headers";
|
|
66368
|
+
if (origin2 !== void 0) {
|
|
66369
|
+
if (origin2 === "null") return "opaque Origin";
|
|
66370
|
+
let o3;
|
|
66371
|
+
try {
|
|
66372
|
+
o3 = new URL(origin2);
|
|
66373
|
+
} catch {
|
|
66374
|
+
return "malformed Origin header";
|
|
66375
|
+
}
|
|
66376
|
+
if (o3.protocol !== "http:" && o3.protocol !== "https:" || !isLocalHostname(o3.hostname)) {
|
|
66377
|
+
return `cross-site Origin (${origin2})`;
|
|
66378
|
+
}
|
|
66379
|
+
}
|
|
66380
|
+
const site = req.headers["sec-fetch-site"];
|
|
66381
|
+
if (site === "cross-site") return "cross-site browser request";
|
|
66382
|
+
return null;
|
|
66383
|
+
}
|
|
66384
|
+
function v4FromHex(hi, lo) {
|
|
66385
|
+
return `${hi >>> 8 & 255}.${hi & 255}.${lo >>> 8 & 255}.${lo & 255}`;
|
|
66386
|
+
}
|
|
66387
|
+
function isBlockedSsrfHost(hostname) {
|
|
66388
|
+
const h = hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "").replace(/\.$/, "");
|
|
66389
|
+
if (!h) return true;
|
|
66390
|
+
if (h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local")) return true;
|
|
66391
|
+
if (h.endsWith(".internal")) return true;
|
|
66392
|
+
if (METADATA_HOSTS.has(h)) return true;
|
|
66393
|
+
if (h.includes(":")) {
|
|
66394
|
+
if (h === "::1" || h === "::") return true;
|
|
66395
|
+
if (h.startsWith("fe80:") || h.startsWith("fc") || h.startsWith("fd")) return true;
|
|
66396
|
+
const sixToFour = h.match(/^2002:([0-9a-f]{1,4}):([0-9a-f]{1,4}):/);
|
|
66397
|
+
if (sixToFour) {
|
|
66398
|
+
return isBlockedSsrfHost(v4FromHex(parseInt(sixToFour[1], 16), parseInt(sixToFour[2], 16)));
|
|
66399
|
+
}
|
|
66400
|
+
const embedded = h.match(/^64:ff9b::(.+)$/) ?? h.match(/::ffff:(.+)$/);
|
|
66401
|
+
if (embedded) {
|
|
66402
|
+
const tail = embedded[1];
|
|
66403
|
+
if (tail.includes(".")) return isBlockedSsrfHost(tail);
|
|
66404
|
+
const seg = tail.split(":");
|
|
66405
|
+
if (seg.length === 2)
|
|
66406
|
+
return isBlockedSsrfHost(v4FromHex(parseInt(seg[0], 16), parseInt(seg[1], 16)));
|
|
66407
|
+
}
|
|
66408
|
+
return false;
|
|
66409
|
+
}
|
|
66410
|
+
const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
66411
|
+
if (m) {
|
|
66412
|
+
const a = Number(m[1]);
|
|
66413
|
+
const b = Number(m[2]);
|
|
66414
|
+
const c = Number(m[3]);
|
|
66415
|
+
if (a === 127 || a === 0 || a === 10) return true;
|
|
66416
|
+
if (a === 169 && b === 254) return true;
|
|
66417
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
66418
|
+
if (a === 192 && b === 168) return true;
|
|
66419
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
66420
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
66421
|
+
}
|
|
66422
|
+
return false;
|
|
66423
|
+
}
|
|
66424
|
+
async function ssrfSafeFetch(url2, init2 = {}, maxHops = 5) {
|
|
66425
|
+
const { allowPrivate, ...fetchInit } = init2;
|
|
66426
|
+
let current = url2;
|
|
66427
|
+
for (let hop = 0; hop <= maxHops; hop++) {
|
|
66428
|
+
const u = new URL(current);
|
|
66429
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
66430
|
+
throw new Error(`refusing to fetch non-http(s) URL (${u.protocol})`);
|
|
66431
|
+
}
|
|
66432
|
+
if (!allowPrivate && isBlockedSsrfHost(u.hostname)) {
|
|
66433
|
+
throw new Error(`refusing to fetch a private/loopback/metadata address: ${u.hostname}`);
|
|
66434
|
+
}
|
|
66435
|
+
const res = await fetch(current, { ...fetchInit, redirect: "manual" });
|
|
66436
|
+
if (res.status >= 300 && res.status < 400) {
|
|
66437
|
+
const loc = res.headers.get("location");
|
|
66438
|
+
if (!loc) return res;
|
|
66439
|
+
current = new URL(loc, current).href;
|
|
66440
|
+
continue;
|
|
66441
|
+
}
|
|
66442
|
+
return res;
|
|
66443
|
+
}
|
|
66444
|
+
throw new Error("too many redirects");
|
|
66445
|
+
}
|
|
66446
|
+
function readLocalImageAsDataUri(filePath) {
|
|
66447
|
+
const expanded = filePath.startsWith("~/") ? join10(homedir7(), filePath.slice(2)) : filePath;
|
|
66448
|
+
const refuse = () => new Error(`Not a readable PNG, JPEG or WebP image: ${filePath}`);
|
|
66449
|
+
let fd;
|
|
66450
|
+
try {
|
|
66451
|
+
fd = openSync2(realpathSync(expanded), "r");
|
|
66452
|
+
} catch {
|
|
66453
|
+
throw refuse();
|
|
66454
|
+
}
|
|
66455
|
+
try {
|
|
66456
|
+
const st = fstatSync2(fd);
|
|
66457
|
+
if (!st.isFile() || st.size === 0 || st.size > MAX_LOCAL_IMAGE_BYTES) throw refuse();
|
|
66458
|
+
const head = Buffer.alloc(12);
|
|
66459
|
+
readSync2(fd, head, 0, 12, 0);
|
|
66460
|
+
const sig = IMAGE_SIGNATURES.find((s3) => s3.test(head));
|
|
66461
|
+
if (!sig) throw refuse();
|
|
66462
|
+
const data = Buffer.alloc(st.size);
|
|
66463
|
+
let off = 0;
|
|
66464
|
+
while (off < st.size) {
|
|
66465
|
+
const n = readSync2(fd, data, off, st.size - off, off);
|
|
66466
|
+
if (n === 0) break;
|
|
66467
|
+
off += n;
|
|
66468
|
+
}
|
|
66469
|
+
return `data:${sig.mime};base64,${data.subarray(0, off).toString("base64")}`;
|
|
66470
|
+
} finally {
|
|
66471
|
+
closeSync2(fd);
|
|
66472
|
+
}
|
|
66473
|
+
}
|
|
66474
|
+
var METADATA_HOSTS, MAX_LOCAL_IMAGE_BYTES, IMAGE_SIGNATURES;
|
|
66475
|
+
var init_local_guard = __esm({
|
|
66476
|
+
"src/local-guard.ts"() {
|
|
66477
|
+
"use strict";
|
|
66478
|
+
METADATA_HOSTS = /* @__PURE__ */ new Set(["metadata", "metadata.goog", "instance-data"]);
|
|
66479
|
+
MAX_LOCAL_IMAGE_BYTES = 25 * 1024 * 1024;
|
|
66480
|
+
IMAGE_SIGNATURES = [
|
|
66481
|
+
{
|
|
66482
|
+
mime: "image/png",
|
|
66483
|
+
test: (b) => b.length >= 8 && b.readUInt32BE(0) === 2303741511 && b.readUInt32BE(4) === 218765834
|
|
66484
|
+
},
|
|
66485
|
+
{
|
|
66486
|
+
mime: "image/jpeg",
|
|
66487
|
+
test: (b) => b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255
|
|
66488
|
+
},
|
|
66489
|
+
{
|
|
66490
|
+
mime: "image/webp",
|
|
66491
|
+
test: (b) => b.length >= 12 && b.toString("ascii", 0, 4) === "RIFF" && b.toString("ascii", 8, 12) === "WEBP"
|
|
66492
|
+
}
|
|
66493
|
+
];
|
|
66494
|
+
}
|
|
66495
|
+
});
|
|
66496
|
+
|
|
66181
66497
|
// node_modules/undici/lib/core/symbols.js
|
|
66182
66498
|
var require_symbols = __commonJS({
|
|
66183
66499
|
"node_modules/undici/lib/core/symbols.js"(exports, module) {
|
|
@@ -93571,8 +93887,8 @@ var init_textual_tool_calls = __esm({
|
|
|
93571
93887
|
|
|
93572
93888
|
// src/response-store.ts
|
|
93573
93889
|
import { appendFile as appendFile2, mkdir as mkdir5, readFile, readdir as readdir2 } from "fs/promises";
|
|
93574
|
-
import { homedir as
|
|
93575
|
-
import { join as
|
|
93890
|
+
import { homedir as homedir8 } from "os";
|
|
93891
|
+
import { join as join11 } from "path";
|
|
93576
93892
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
93577
93893
|
function isEnabled() {
|
|
93578
93894
|
const v = process.env.BLOCKRUN_RESPONSE_STORE;
|
|
@@ -93585,7 +93901,7 @@ async function ensureDir2() {
|
|
|
93585
93901
|
}
|
|
93586
93902
|
function dailyFile(date = /* @__PURE__ */ new Date()) {
|
|
93587
93903
|
const iso = date.toISOString().slice(0, 10);
|
|
93588
|
-
return
|
|
93904
|
+
return join11(STORE_DIR, `responses-${iso}.jsonl`);
|
|
93589
93905
|
}
|
|
93590
93906
|
function genId() {
|
|
93591
93907
|
return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
|
|
@@ -93635,7 +93951,7 @@ async function listRecent(limit = 20, daysBack = 7) {
|
|
|
93635
93951
|
d.setUTCDate(d.getUTCDate() - i);
|
|
93636
93952
|
const iso = d.toISOString().slice(0, 10);
|
|
93637
93953
|
const name = `responses-${iso}.jsonl`;
|
|
93638
|
-
if (files.includes(name)) candidateFiles.push(
|
|
93954
|
+
if (files.includes(name)) candidateFiles.push(join11(STORE_DIR, name));
|
|
93639
93955
|
}
|
|
93640
93956
|
const all3 = [];
|
|
93641
93957
|
for (const f of candidateFiles) {
|
|
@@ -93664,7 +93980,7 @@ var STORE_DIR, dirReady2;
|
|
|
93664
93980
|
var init_response_store = __esm({
|
|
93665
93981
|
"src/response-store.ts"() {
|
|
93666
93982
|
"use strict";
|
|
93667
|
-
STORE_DIR =
|
|
93983
|
+
STORE_DIR = join11(homedir8(), ".openclaw", "blockrun", "responses");
|
|
93668
93984
|
dirReady2 = false;
|
|
93669
93985
|
}
|
|
93670
93986
|
});
|
|
@@ -95301,10 +95617,10 @@ import { createHmac } from "crypto";
|
|
|
95301
95617
|
import { createServer } from "http";
|
|
95302
95618
|
import { finished, Readable } from "stream";
|
|
95303
95619
|
import { pipeline } from "stream/promises";
|
|
95304
|
-
import { homedir as
|
|
95305
|
-
import { join as
|
|
95620
|
+
import { homedir as homedir9 } from "os";
|
|
95621
|
+
import { join as join12 } from "path";
|
|
95306
95622
|
import { mkdir as mkdir6, writeFile as writeFile4, readFile as readFile2, stat as fsStat } from "fs/promises";
|
|
95307
|
-
import { readFileSync as readFileSync2
|
|
95623
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
95308
95624
|
async function loadGatewayCatalog(apiBase, apiKey) {
|
|
95309
95625
|
try {
|
|
95310
95626
|
const controller = new AbortController();
|
|
@@ -96198,22 +96514,6 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
96198
96514
|
}).catch(() => {
|
|
96199
96515
|
});
|
|
96200
96516
|
}
|
|
96201
|
-
function readImageFileAsDataUri(filePath) {
|
|
96202
|
-
const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
|
|
96203
|
-
if (!existsSync2(resolved)) {
|
|
96204
|
-
throw new Error(`Image file not found: ${resolved}`);
|
|
96205
|
-
}
|
|
96206
|
-
const ext = resolved.split(".").pop()?.toLowerCase() ?? "png";
|
|
96207
|
-
const mimeMap = {
|
|
96208
|
-
png: "image/png",
|
|
96209
|
-
jpg: "image/jpeg",
|
|
96210
|
-
jpeg: "image/jpeg",
|
|
96211
|
-
webp: "image/webp"
|
|
96212
|
-
};
|
|
96213
|
-
const mime = mimeMap[ext] ?? "image/png";
|
|
96214
|
-
const data = readFileSync2(resolved);
|
|
96215
|
-
return `data:${mime};base64,${data.toString("base64")}`;
|
|
96216
|
-
}
|
|
96217
96517
|
async function uploadDataUriToHost(dataUri) {
|
|
96218
96518
|
const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
96219
96519
|
if (!match) throw new Error("Invalid data URI format");
|
|
@@ -96387,6 +96687,19 @@ async function startProxy(options) {
|
|
|
96387
96687
|
}
|
|
96388
96688
|
const account = walletKey ? privateKeyToAccount(walletKey) : void 0;
|
|
96389
96689
|
const x402 = authMode === "wallet" ? new x402Client() : void 0;
|
|
96690
|
+
x402?.onBeforePaymentCreation(async (context) => {
|
|
96691
|
+
const spend = paymentStore.getStore()?.sessionSpend;
|
|
96692
|
+
if (!spend?.isStrict) return;
|
|
96693
|
+
const quote = parseQuotedAmountMicros(context.selectedRequirements);
|
|
96694
|
+
if (quote === void 0) {
|
|
96695
|
+
throw new CostCapError("ClawRouter cost cap: unreadable payment amount in the 402 quote");
|
|
96696
|
+
}
|
|
96697
|
+
if (!spend.coverPayment(quote)) {
|
|
96698
|
+
throw new CostCapError(
|
|
96699
|
+
`ClawRouter cost cap exceeded: signing $${microsToUsd(quote).toFixed(6)} would push this session past its maxCostPerRun limit`
|
|
96700
|
+
);
|
|
96701
|
+
}
|
|
96702
|
+
});
|
|
96390
96703
|
if (x402 && account) {
|
|
96391
96704
|
const evmPublicClient = createPublicClient({ chain: base, transport: http() });
|
|
96392
96705
|
const evmSigner = toClientEvmSigner(account, evmPublicClient);
|
|
@@ -96429,6 +96742,11 @@ async function startProxy(options) {
|
|
|
96429
96742
|
if (store) store.amountUsd = amountUsd;
|
|
96430
96743
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
96431
96744
|
});
|
|
96745
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
96746
|
+
const spend = paymentStore.getStore()?.sessionSpend;
|
|
96747
|
+
const amount = parseQuotedAmountMicros(context.selectedRequirements);
|
|
96748
|
+
if (spend && amount !== void 0) spend.recordSigned(amount);
|
|
96749
|
+
});
|
|
96432
96750
|
const payFetch = authMode === "api-key" ? withSpendPolicy(
|
|
96433
96751
|
createApiKeyFetch(apiKey, fetch, apiBase),
|
|
96434
96752
|
options.spendControl ?? getSharedSpendControl(),
|
|
@@ -96447,7 +96765,10 @@ async function startProxy(options) {
|
|
|
96447
96765
|
// Only the wallet rail settles per call. The API-key rail is billed
|
|
96448
96766
|
// server-side against account credit, so there is no per-request
|
|
96449
96767
|
// settlement for an observer to report.
|
|
96450
|
-
onPayment: options.onPayment
|
|
96768
|
+
onPayment: options.onPayment,
|
|
96769
|
+
// A 402 answer to a signed payment means the gateway declined it and
|
|
96770
|
+
// nothing settled — take it back off the session's budget.
|
|
96771
|
+
onPaymentDeclined: () => paymentStore.getStore()?.sessionSpend?.voidLastSigned()
|
|
96451
96772
|
});
|
|
96452
96773
|
let balanceMonitor;
|
|
96453
96774
|
if (options._balanceMonitorOverride) {
|
|
@@ -96490,6 +96811,13 @@ async function startProxy(options) {
|
|
|
96490
96811
|
console.error(`[ClawRouter] Request finished with error: ${err.message}`);
|
|
96491
96812
|
}
|
|
96492
96813
|
});
|
|
96814
|
+
const untrusted = untrustedRequestReason(req);
|
|
96815
|
+
if (untrusted) {
|
|
96816
|
+
console.warn(`[ClawRouter] Refused request to ${req.url}: ${untrusted}`);
|
|
96817
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
96818
|
+
res.end(JSON.stringify({ error: "forbidden", details: untrusted }));
|
|
96819
|
+
return;
|
|
96820
|
+
}
|
|
96493
96821
|
if (req.url === "/health" || req.url?.startsWith("/health?")) {
|
|
96494
96822
|
const url2 = new URL(req.url, "http://localhost");
|
|
96495
96823
|
const full = url2.searchParams.get("full") === "true";
|
|
@@ -96718,7 +97046,7 @@ async function startProxy(options) {
|
|
|
96718
97046
|
res.end("Bad request");
|
|
96719
97047
|
return;
|
|
96720
97048
|
}
|
|
96721
|
-
const filePath =
|
|
97049
|
+
const filePath = join12(IMAGE_DIR, filename);
|
|
96722
97050
|
try {
|
|
96723
97051
|
const s3 = await fsStat(filePath);
|
|
96724
97052
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96749,7 +97077,7 @@ async function startProxy(options) {
|
|
|
96749
97077
|
res.end("Bad request");
|
|
96750
97078
|
return;
|
|
96751
97079
|
}
|
|
96752
|
-
const filePath =
|
|
97080
|
+
const filePath = join12(AUDIO_DIR, filename);
|
|
96753
97081
|
try {
|
|
96754
97082
|
const s3 = await fsStat(filePath);
|
|
96755
97083
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96779,7 +97107,7 @@ async function startProxy(options) {
|
|
|
96779
97107
|
res.end("Bad request");
|
|
96780
97108
|
return;
|
|
96781
97109
|
}
|
|
96782
|
-
const filePath =
|
|
97110
|
+
const filePath = join12(VIDEO_DIR, filename);
|
|
96783
97111
|
try {
|
|
96784
97112
|
const s3 = await fsStat(filePath);
|
|
96785
97113
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96921,7 +97249,7 @@ async function startProxy(options) {
|
|
|
96921
97249
|
const [, mimeType, b64] = dataUriMatch;
|
|
96922
97250
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
96923
97251
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96924
|
-
await writeFile4(
|
|
97252
|
+
await writeFile4(join12(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
96925
97253
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96926
97254
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
96927
97255
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -96932,7 +97260,7 @@ async function startProxy(options) {
|
|
|
96932
97260
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
96933
97261
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96934
97262
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
96935
|
-
await writeFile4(
|
|
97263
|
+
await writeFile4(join12(IMAGE_DIR, filename), buf);
|
|
96936
97264
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96937
97265
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
96938
97266
|
}
|
|
@@ -96995,7 +97323,10 @@ async function startProxy(options) {
|
|
|
96995
97323
|
if (typeof val !== "string" || !val) continue;
|
|
96996
97324
|
if (val.startsWith("data:")) {
|
|
96997
97325
|
} else if (val.startsWith("https://") || val.startsWith("http://")) {
|
|
96998
|
-
const imgResp = await
|
|
97326
|
+
const imgResp = await ssrfSafeFetch(val, {
|
|
97327
|
+
signal: clientAbort.signal,
|
|
97328
|
+
allowPrivate: process.env.CLAWROUTER_ALLOW_PRIVATE_FETCH === "1"
|
|
97329
|
+
});
|
|
96999
97330
|
if (!imgResp.ok)
|
|
97000
97331
|
throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
|
|
97001
97332
|
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
@@ -97005,7 +97336,7 @@ async function startProxy(options) {
|
|
|
97005
97336
|
`[ClawRouter] img2img: downloaded ${field} URL \u2192 data URI (${buf.length} bytes)`
|
|
97006
97337
|
);
|
|
97007
97338
|
} else {
|
|
97008
|
-
parsed[field] =
|
|
97339
|
+
parsed[field] = readLocalImageAsDataUri(val);
|
|
97009
97340
|
console.log(`[ClawRouter] img2img: read ${field} file \u2192 data URI`);
|
|
97010
97341
|
}
|
|
97011
97342
|
}
|
|
@@ -97054,7 +97385,7 @@ async function startProxy(options) {
|
|
|
97054
97385
|
const [, mimeType, b64] = dataUriMatch;
|
|
97055
97386
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
97056
97387
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
97057
|
-
await writeFile4(
|
|
97388
|
+
await writeFile4(join12(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
97058
97389
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
97059
97390
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
97060
97391
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -97065,7 +97396,7 @@ async function startProxy(options) {
|
|
|
97065
97396
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
97066
97397
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
97067
97398
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
97068
|
-
await writeFile4(
|
|
97399
|
+
await writeFile4(join12(IMAGE_DIR, filename), buf);
|
|
97069
97400
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
97070
97401
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
97071
97402
|
}
|
|
@@ -97161,7 +97492,7 @@ async function startProxy(options) {
|
|
|
97161
97492
|
const ext = contentType.includes("wav") ? "wav" : "mp3";
|
|
97162
97493
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
97163
97494
|
const buf = Buffer.from(await audioResp.arrayBuffer());
|
|
97164
|
-
await writeFile4(
|
|
97495
|
+
await writeFile4(join12(AUDIO_DIR, filename), buf);
|
|
97165
97496
|
track.url = `http://localhost:${port2}/audio/${filename}`;
|
|
97166
97497
|
console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
|
|
97167
97498
|
}
|
|
@@ -97328,7 +97659,7 @@ async function startProxy(options) {
|
|
|
97328
97659
|
const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
|
|
97329
97660
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
97330
97661
|
const buf = Buffer.from(await videoResp.arrayBuffer());
|
|
97331
|
-
await writeFile4(
|
|
97662
|
+
await writeFile4(join12(VIDEO_DIR, filename), buf);
|
|
97332
97663
|
clip.url = `http://localhost:${port2}/videos/${filename}`;
|
|
97333
97664
|
console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
|
|
97334
97665
|
}
|
|
@@ -97721,6 +98052,17 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
97721
98052
|
return { success: true, response };
|
|
97722
98053
|
} catch (err) {
|
|
97723
98054
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
98055
|
+
if (err instanceof CostCapError) {
|
|
98056
|
+
return {
|
|
98057
|
+
success: false,
|
|
98058
|
+
errorBody: JSON.stringify({
|
|
98059
|
+
error: { message: errorMsg, type: "cost_cap_exceeded", code: "cost_cap_exceeded" }
|
|
98060
|
+
}),
|
|
98061
|
+
errorStatus: 429,
|
|
98062
|
+
isProviderError: false,
|
|
98063
|
+
errorCategory: "payment_error"
|
|
98064
|
+
};
|
|
98065
|
+
}
|
|
97724
98066
|
if (err instanceof SpendPolicyError) {
|
|
97725
98067
|
return {
|
|
97726
98068
|
success: false,
|
|
@@ -98294,8 +98636,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98294
98636
|
let imageDataUri;
|
|
98295
98637
|
let maskDataUri;
|
|
98296
98638
|
try {
|
|
98297
|
-
imageDataUri =
|
|
98298
|
-
if (maskPath) maskDataUri =
|
|
98639
|
+
imageDataUri = readLocalImageAsDataUri(imagePath);
|
|
98640
|
+
if (maskPath) maskDataUri = readLocalImageAsDataUri(maskPath);
|
|
98299
98641
|
} catch (fileErr) {
|
|
98300
98642
|
const fileErrMsg = fileErr instanceof Error ? fileErr.message : String(fileErr);
|
|
98301
98643
|
sendImg2ImgText(`Failed to read image file: ${fileErrMsg}`);
|
|
@@ -98800,14 +99142,23 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98800
99142
|
}
|
|
98801
99143
|
}
|
|
98802
99144
|
}
|
|
99145
|
+
let sessionSpend;
|
|
98803
99146
|
if (options.maxCostPerRunUsd && effectiveSessionId && !isFreeModel && (options.maxCostPerRunMode ?? "graceful") === "strict") {
|
|
98804
|
-
const runCostUsd = sessionStore.getSessionCostUsd(effectiveSessionId);
|
|
98805
99147
|
const thisReqEstStr = estimatedCostMicros !== void 0 ? estimatedCostMicros.toString() : modelId ? estimateAmount(modelId, body.length, maxTokens) : void 0;
|
|
98806
|
-
const
|
|
98807
|
-
|
|
98808
|
-
|
|
99148
|
+
const admission = SessionSpend.admit(
|
|
99149
|
+
sessionStore,
|
|
99150
|
+
effectiveSessionId,
|
|
99151
|
+
usdToMicros(options.maxCostPerRunUsd),
|
|
99152
|
+
parseMicros(thisReqEstStr) ?? 0n
|
|
99153
|
+
);
|
|
99154
|
+
if (!admission.admitted) {
|
|
99155
|
+
const spentUsd = microsToUsd(admission.spentMicros);
|
|
99156
|
+
const heldUsd = microsToUsd(admission.heldMicros);
|
|
99157
|
+
const estUsd = microsToUsd(admission.estimateMicros);
|
|
99158
|
+
const projectedCostUsd = spentUsd + heldUsd + estUsd;
|
|
99159
|
+
const breakdown = `spent $${spentUsd.toFixed(4)}${heldUsd > 0 ? ` + in flight $${heldUsd.toFixed(4)}` : ""} + est $${estUsd.toFixed(4)}`;
|
|
98809
99160
|
console.log(
|
|
98810
|
-
`[ClawRouter] Cost cap exceeded for session ${effectiveSessionId.slice(0, 8)}...: projected $${projectedCostUsd.toFixed(4)} (
|
|
99161
|
+
`[ClawRouter] Cost cap exceeded for session ${effectiveSessionId.slice(0, 8)}...: projected $${projectedCostUsd.toFixed(4)} (${breakdown}) > $${options.maxCostPerRunUsd} limit`
|
|
98811
99162
|
);
|
|
98812
99163
|
res.writeHead(429, {
|
|
98813
99164
|
"Content-Type": "application/json",
|
|
@@ -98816,7 +99167,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98816
99167
|
res.end(
|
|
98817
99168
|
JSON.stringify({
|
|
98818
99169
|
error: {
|
|
98819
|
-
message: `ClawRouter cost cap exceeded: projected spend $${projectedCostUsd.toFixed(4)} (
|
|
99170
|
+
message: `ClawRouter cost cap exceeded: projected spend $${projectedCostUsd.toFixed(4)} (${breakdown}) would exceed limit $${options.maxCostPerRunUsd}`,
|
|
98820
99171
|
type: "cost_cap_exceeded",
|
|
98821
99172
|
code: "cost_cap_exceeded"
|
|
98822
99173
|
}
|
|
@@ -98825,7 +99176,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98825
99176
|
deduplicator.removeInflight(dedupKey);
|
|
98826
99177
|
return;
|
|
98827
99178
|
}
|
|
99179
|
+
sessionSpend = admission.spend;
|
|
99180
|
+
} else if (options.maxCostPerRunUsd && effectiveSessionId) {
|
|
99181
|
+
sessionSpend = SessionSpend.track(sessionStore, effectiveSessionId);
|
|
98828
99182
|
}
|
|
99183
|
+
const requestStore = paymentStore.getStore();
|
|
99184
|
+
if (requestStore) requestStore.sessionSpend = sessionSpend;
|
|
98829
99185
|
if (options.maxCostPerRunUsd && effectiveSessionId && !isFreeModel && (options.maxCostPerRunMode ?? "graceful") === "graceful") {
|
|
98830
99186
|
const runCostUsd = sessionStore.getSessionCostUsd(effectiveSessionId);
|
|
98831
99187
|
const remainingUsd = options.maxCostPerRunUsd - runCostUsd;
|
|
@@ -99221,11 +99577,9 @@ data: [DONE]
|
|
|
99221
99577
|
actualModelUsed = tryModel;
|
|
99222
99578
|
recordOutcome(tryModel, requestKind, true);
|
|
99223
99579
|
console.log(`[ClawRouter] Success with model: ${tryModel}`);
|
|
99224
|
-
if (
|
|
99225
|
-
const costEst = estimateAmount(tryModel, body.length, maxTokens);
|
|
99226
|
-
if (costEst)
|
|
99227
|
-
sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
|
|
99228
|
-
}
|
|
99580
|
+
if (sessionSpend && !FREE_MODELS.has(tryModel)) {
|
|
99581
|
+
const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
|
|
99582
|
+
if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
|
|
99229
99583
|
}
|
|
99230
99584
|
break;
|
|
99231
99585
|
}
|
|
@@ -99299,11 +99653,9 @@ data: [DONE]
|
|
|
99299
99653
|
upstream = retryResult.response;
|
|
99300
99654
|
actualModelUsed = tryModel;
|
|
99301
99655
|
console.log(`[ClawRouter] Explicit-pin retry succeeded for: ${tryModel}`);
|
|
99302
|
-
if (
|
|
99303
|
-
const costEst = estimateAmount(tryModel, body.length, maxTokens);
|
|
99304
|
-
if (costEst)
|
|
99305
|
-
sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
|
|
99306
|
-
}
|
|
99656
|
+
if (sessionSpend && !FREE_MODELS.has(tryModel)) {
|
|
99657
|
+
const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
|
|
99658
|
+
if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
|
|
99307
99659
|
}
|
|
99308
99660
|
break;
|
|
99309
99661
|
}
|
|
@@ -99370,11 +99722,9 @@ data: [DONE]
|
|
|
99370
99722
|
upstream = retryResult.response;
|
|
99371
99723
|
actualModelUsed = tryModel;
|
|
99372
99724
|
console.log(`[ClawRouter] Rate-limit retry succeeded for: ${tryModel}`);
|
|
99373
|
-
if (
|
|
99374
|
-
const costEst = estimateAmount(tryModel, body.length, maxTokens);
|
|
99375
|
-
if (costEst)
|
|
99376
|
-
sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
|
|
99377
|
-
}
|
|
99725
|
+
if (sessionSpend && !FREE_MODELS.has(tryModel)) {
|
|
99726
|
+
const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
|
|
99727
|
+
if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
|
|
99378
99728
|
}
|
|
99379
99729
|
break;
|
|
99380
99730
|
}
|
|
@@ -99468,6 +99818,7 @@ data: [DONE]
|
|
|
99468
99818
|
);
|
|
99469
99819
|
const rawErrBody = lastError?.body || structuredMessage;
|
|
99470
99820
|
const errStatus = lastError?.status || 502;
|
|
99821
|
+
const costCapRefusal = errStatus === 429 && rawErrBody.includes('"cost_cap_exceeded"');
|
|
99471
99822
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
99472
99823
|
requestHadError = true;
|
|
99473
99824
|
if (headersSentEarly) {
|
|
@@ -99504,7 +99855,8 @@ data: [DONE]
|
|
|
99504
99855
|
"Content-Type": "application/json",
|
|
99505
99856
|
"x-context-used-kb": String(originalContextSizeKB),
|
|
99506
99857
|
"x-context-limit-kb": String(CONTEXT_LIMIT_KB),
|
|
99507
|
-
...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
|
|
99858
|
+
...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {},
|
|
99859
|
+
...costCapRefusal ? { "X-ClawRouter-Cost-Cap-Exceeded": "1" } : {}
|
|
99508
99860
|
});
|
|
99509
99861
|
res.end(transformedErr);
|
|
99510
99862
|
deduplicator.complete(dedupKey, {
|
|
@@ -99938,6 +100290,8 @@ data: [DONE]
|
|
|
99938
100290
|
throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });
|
|
99939
100291
|
}
|
|
99940
100292
|
throw err;
|
|
100293
|
+
} finally {
|
|
100294
|
+
sessionSpend?.release();
|
|
99941
100295
|
}
|
|
99942
100296
|
const logModel = routingDecision?.model ?? modelId;
|
|
99943
100297
|
if (logModel) {
|
|
@@ -100024,6 +100378,7 @@ var init_proxy = __esm({
|
|
|
100024
100378
|
init_auth();
|
|
100025
100379
|
init_api_key();
|
|
100026
100380
|
init_spend_control();
|
|
100381
|
+
init_session_spend();
|
|
100027
100382
|
init_compression();
|
|
100028
100383
|
init_errors5();
|
|
100029
100384
|
init_version4();
|
|
@@ -100033,6 +100388,7 @@ var init_proxy = __esm({
|
|
|
100033
100388
|
init_outcome_memory();
|
|
100034
100389
|
init_config();
|
|
100035
100390
|
init_journal();
|
|
100391
|
+
init_local_guard();
|
|
100036
100392
|
init_upstream_proxy();
|
|
100037
100393
|
init_textual_tool_calls();
|
|
100038
100394
|
init_response_store();
|
|
@@ -100041,10 +100397,10 @@ var init_proxy = __esm({
|
|
|
100041
100397
|
paymentStore = new AsyncLocalStorage();
|
|
100042
100398
|
BLOCKRUN_API = "https://blockrun.ai/api";
|
|
100043
100399
|
BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
100044
|
-
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ??
|
|
100045
|
-
IMAGE_DIR =
|
|
100046
|
-
AUDIO_DIR =
|
|
100047
|
-
VIDEO_DIR =
|
|
100400
|
+
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join12(homedir9(), ".clawrouter-desktop", "service-token");
|
|
100401
|
+
IMAGE_DIR = join12(homedir9(), ".openclaw", "blockrun", "images");
|
|
100402
|
+
AUDIO_DIR = join12(homedir9(), ".openclaw", "blockrun", "audio");
|
|
100403
|
+
VIDEO_DIR = join12(homedir9(), ".openclaw", "blockrun", "videos");
|
|
100048
100404
|
AUTO_MODEL = "blockrun/auto";
|
|
100049
100405
|
ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
100050
100406
|
"blockrun/eco",
|
|
@@ -101154,7 +101510,13 @@ function buildTool(service, proxyBaseUrl) {
|
|
|
101154
101510
|
if (service.proxyPath === "/pm/__dynamic__") {
|
|
101155
101511
|
const rawPath = typeof params.path === "string" ? params.path : "";
|
|
101156
101512
|
const normalized = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
|
101157
|
-
|
|
101513
|
+
let resolved;
|
|
101514
|
+
try {
|
|
101515
|
+
resolved = new URL(`/v1${normalized}`, "http://x").pathname;
|
|
101516
|
+
} catch {
|
|
101517
|
+
resolved = "";
|
|
101518
|
+
}
|
|
101519
|
+
if (!normalized.startsWith("/pm/") || normalized.includes("..") || /%2e|%2f|%5c|\\/i.test(normalized) || !resolved.startsWith("/v1/pm/")) {
|
|
101158
101520
|
throw new Error(
|
|
101159
101521
|
`predexon_endpoint_call: invalid path '${rawPath}' \u2014 must begin with '/pm/' and contain no '..'`
|
|
101160
101522
|
);
|
|
@@ -101493,8 +101855,8 @@ __export(reconcile_exports, {
|
|
|
101493
101855
|
reconcile: () => reconcile
|
|
101494
101856
|
});
|
|
101495
101857
|
import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
|
|
101496
|
-
import { join as
|
|
101497
|
-
import { homedir as
|
|
101858
|
+
import { join as join13 } from "path";
|
|
101859
|
+
import { homedir as homedir10 } from "os";
|
|
101498
101860
|
async function loadLocalRows(since) {
|
|
101499
101861
|
let files;
|
|
101500
101862
|
try {
|
|
@@ -101507,7 +101869,7 @@ async function loadLocalRows(since) {
|
|
|
101507
101869
|
for (const file of files) {
|
|
101508
101870
|
let text;
|
|
101509
101871
|
try {
|
|
101510
|
-
text = await readFile3(
|
|
101872
|
+
text = await readFile3(join13(LOG_DIR3, file), "utf8");
|
|
101511
101873
|
} catch {
|
|
101512
101874
|
continue;
|
|
101513
101875
|
}
|
|
@@ -101665,7 +102027,7 @@ var init_reconcile = __esm({
|
|
|
101665
102027
|
"src/reconcile.ts"() {
|
|
101666
102028
|
"use strict";
|
|
101667
102029
|
init_api_key();
|
|
101668
|
-
LOG_DIR3 =
|
|
102030
|
+
LOG_DIR3 = join13(homedir10(), ".openclaw", "blockrun", "logs");
|
|
101669
102031
|
CENT_EPSILON = 1e-9;
|
|
101670
102032
|
usd = (n) => Math.abs(n) < 0.01 ? `$${n.toFixed(6)}` : `$${n.toFixed(2)}`;
|
|
101671
102033
|
}
|
|
@@ -122657,8 +123019,8 @@ var init_constants3 = __esm({
|
|
|
122657
123019
|
|
|
122658
123020
|
// src/polymarket/wallet-adapter.ts
|
|
122659
123021
|
import fs2 from "fs";
|
|
122660
|
-
import { homedir as
|
|
122661
|
-
import { join as
|
|
123022
|
+
import { homedir as homedir11 } from "os";
|
|
123023
|
+
import { join as join14 } from "path";
|
|
122662
123024
|
function getOrCreateWalletKey() {
|
|
122663
123025
|
const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
|
|
122664
123026
|
if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
|
|
@@ -122706,7 +123068,7 @@ var init_wallet_adapter = __esm({
|
|
|
122706
123068
|
init_esm();
|
|
122707
123069
|
init_chains();
|
|
122708
123070
|
init_constants3();
|
|
122709
|
-
WALLET_FILE2 =
|
|
123071
|
+
WALLET_FILE2 = join14(homedir11(), ".openclaw", "blockrun", "wallet.key");
|
|
122710
123072
|
BASE_RPC_URLS = [
|
|
122711
123073
|
"https://mainnet.base.org",
|
|
122712
123074
|
"https://base.llamarpc.com",
|
|
@@ -123110,7 +123472,7 @@ var init_client5 = __esm({
|
|
|
123110
123472
|
function resolveSpendControl(deps) {
|
|
123111
123473
|
return deps?.spendControl ?? getSharedSpendControl();
|
|
123112
123474
|
}
|
|
123113
|
-
function
|
|
123475
|
+
function usdToMicros2(usd2) {
|
|
123114
123476
|
return String(Math.ceil(usd2 * 1e6));
|
|
123115
123477
|
}
|
|
123116
123478
|
async function signUnderSpendPolicy(deps, quote, action, sign4) {
|
|
@@ -123376,7 +123738,7 @@ Re-call with confirm:true to sign and submit.`,
|
|
|
123376
123738
|
payTo: negRisk ? NEG_RISK_CTF_EXCHANGE_V2 : CTF_EXCHANGE_V2,
|
|
123377
123739
|
network: `eip155:${POLYGON_CHAIN_ID}`,
|
|
123378
123740
|
asset: PUSD_COLLATERAL,
|
|
123379
|
-
amount:
|
|
123741
|
+
amount: usdToMicros2(notional)
|
|
123380
123742
|
};
|
|
123381
123743
|
let r2;
|
|
123382
123744
|
try {
|
|
@@ -233099,7 +233461,7 @@ __export(index_exports, {
|
|
|
233099
233461
|
});
|
|
233100
233462
|
import {
|
|
233101
233463
|
writeFileSync as writeFileSync3,
|
|
233102
|
-
existsSync as
|
|
233464
|
+
existsSync as existsSync3,
|
|
233103
233465
|
readdirSync,
|
|
233104
233466
|
mkdirSync as mkdirSync3,
|
|
233105
233467
|
copyFileSync,
|
|
@@ -233108,9 +233470,9 @@ import {
|
|
|
233108
233470
|
unlinkSync
|
|
233109
233471
|
} from "fs";
|
|
233110
233472
|
import { readFile as readFileAsync } from "fs/promises";
|
|
233111
|
-
import { homedir as
|
|
233473
|
+
import { homedir as homedir16 } from "os";
|
|
233112
233474
|
import { randomUUID } from "crypto";
|
|
233113
|
-
import { join as
|
|
233475
|
+
import { join as join19 } from "path";
|
|
233114
233476
|
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
233115
233477
|
const start = Date.now();
|
|
233116
233478
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -233130,12 +233492,12 @@ function writePrivateJsonSync(path5, value) {
|
|
|
233130
233492
|
renameSync2(temporary, path5);
|
|
233131
233493
|
chmodSync(path5, 384);
|
|
233132
233494
|
} finally {
|
|
233133
|
-
if (
|
|
233495
|
+
if (existsSync3(temporary)) unlinkSync(temporary);
|
|
233134
233496
|
}
|
|
233135
233497
|
}
|
|
233136
233498
|
function hasConfiguredWallet() {
|
|
233137
233499
|
return Boolean(
|
|
233138
|
-
process["env"].BLOCKRUN_WALLET_KEY ||
|
|
233500
|
+
process["env"].BLOCKRUN_WALLET_KEY || existsSync3(CORE_WALLET_FILE) || existsSync3(WALLET_FILE)
|
|
233139
233501
|
);
|
|
233140
233502
|
}
|
|
233141
233503
|
function isCompletionMode() {
|
|
@@ -233153,11 +233515,11 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
233153
233515
|
}
|
|
233154
233516
|
function legacyPackageIsBlockRun() {
|
|
233155
233517
|
for (const dir of [
|
|
233156
|
-
|
|
233157
|
-
|
|
233518
|
+
join19(homedir16(), ".openclaw", "extensions", "clawrouter"),
|
|
233519
|
+
join19(homedir16(), ".openclaw", "npm", "node_modules", "@blockrun", "clawrouter")
|
|
233158
233520
|
]) {
|
|
233159
233521
|
try {
|
|
233160
|
-
const metadata = JSON.parse(readTextFileSync(
|
|
233522
|
+
const metadata = JSON.parse(readTextFileSync(join19(dir, "package.json")));
|
|
233161
233523
|
if (metadata.name === "@blockrun/clawrouter") return true;
|
|
233162
233524
|
} catch {
|
|
233163
233525
|
}
|
|
@@ -233165,11 +233527,11 @@ function legacyPackageIsBlockRun() {
|
|
|
233165
233527
|
return false;
|
|
233166
233528
|
}
|
|
233167
233529
|
function injectModelsConfig(logger48, options = {}) {
|
|
233168
|
-
const configDir =
|
|
233169
|
-
const configPath =
|
|
233530
|
+
const configDir = join19(homedir16(), ".openclaw");
|
|
233531
|
+
const configPath = join19(configDir, "openclaw.json");
|
|
233170
233532
|
let config = {};
|
|
233171
233533
|
let needsWrite = false;
|
|
233172
|
-
if (!
|
|
233534
|
+
if (!existsSync3(configDir)) {
|
|
233173
233535
|
try {
|
|
233174
233536
|
mkdirSync3(configDir, { recursive: true });
|
|
233175
233537
|
logger48.info("Created OpenClaw config directory");
|
|
@@ -233180,7 +233542,7 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
233180
233542
|
return;
|
|
233181
233543
|
}
|
|
233182
233544
|
}
|
|
233183
|
-
if (
|
|
233545
|
+
if (existsSync3(configPath)) {
|
|
233184
233546
|
try {
|
|
233185
233547
|
const content = readTextFileSync(configPath).trim();
|
|
233186
233548
|
if (content) {
|
|
@@ -233387,8 +233749,8 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
233387
233749
|
}
|
|
233388
233750
|
function syncAgentModelCache(logger48, options = {}) {
|
|
233389
233751
|
if (!isGatewayMode() && !options.forceWrite) return;
|
|
233390
|
-
const agentsDir =
|
|
233391
|
-
if (!
|
|
233752
|
+
const agentsDir = join19(homedir16(), ".openclaw", "agents");
|
|
233753
|
+
if (!existsSync3(agentsDir)) return;
|
|
233392
233754
|
let agentDirs;
|
|
233393
233755
|
try {
|
|
233394
233756
|
agentDirs = readdirSync(agentsDir);
|
|
@@ -233397,8 +233759,8 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
233397
233759
|
}
|
|
233398
233760
|
const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
|
|
233399
233761
|
for (const agent of agentDirs) {
|
|
233400
|
-
const cachePath =
|
|
233401
|
-
if (!
|
|
233762
|
+
const cachePath = join19(agentsDir, agent, "agent", "models.json");
|
|
233763
|
+
if (!existsSync3(cachePath)) continue;
|
|
233402
233764
|
try {
|
|
233403
233765
|
const raw = readTextFileSync(cachePath).trim();
|
|
233404
233766
|
if (!raw) continue;
|
|
@@ -233426,8 +233788,8 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
233426
233788
|
}
|
|
233427
233789
|
}
|
|
233428
233790
|
function injectAuthProfile(logger48) {
|
|
233429
|
-
const agentsDir =
|
|
233430
|
-
if (!
|
|
233791
|
+
const agentsDir = join19(homedir16(), ".openclaw", "agents");
|
|
233792
|
+
if (!existsSync3(agentsDir)) {
|
|
233431
233793
|
try {
|
|
233432
233794
|
mkdirSync3(agentsDir, { recursive: true });
|
|
233433
233795
|
} catch (err) {
|
|
@@ -233443,10 +233805,10 @@ function injectAuthProfile(logger48) {
|
|
|
233443
233805
|
agents = ["main", ...agents];
|
|
233444
233806
|
}
|
|
233445
233807
|
for (const agentId of agents) {
|
|
233446
|
-
const authDir =
|
|
233447
|
-
const authPath =
|
|
233448
|
-
const sqlitePath =
|
|
233449
|
-
if (
|
|
233808
|
+
const authDir = join19(agentsDir, agentId, "agent");
|
|
233809
|
+
const authPath = join19(authDir, "auth-profiles.json");
|
|
233810
|
+
const sqlitePath = join19(authDir, "openclaw-agent.sqlite");
|
|
233811
|
+
if (existsSync3(sqlitePath)) {
|
|
233450
233812
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
233451
233813
|
continue;
|
|
233452
233814
|
}
|
|
@@ -233454,7 +233816,7 @@ function injectAuthProfile(logger48) {
|
|
|
233454
233816
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
233455
233817
|
continue;
|
|
233456
233818
|
}
|
|
233457
|
-
if (!
|
|
233819
|
+
if (!existsSync3(authDir)) {
|
|
233458
233820
|
try {
|
|
233459
233821
|
mkdirSync3(authDir, { recursive: true });
|
|
233460
233822
|
} catch {
|
|
@@ -233465,7 +233827,7 @@ function injectAuthProfile(logger48) {
|
|
|
233465
233827
|
version: 1,
|
|
233466
233828
|
profiles: {}
|
|
233467
233829
|
};
|
|
233468
|
-
if (
|
|
233830
|
+
if (existsSync3(authPath)) {
|
|
233469
233831
|
try {
|
|
233470
233832
|
const existing = JSON.parse(readTextFileSync(authPath));
|
|
233471
233833
|
if (existing.version && existing.profiles) {
|
|
@@ -233498,7 +233860,7 @@ function injectAuthProfile(logger48) {
|
|
|
233498
233860
|
}
|
|
233499
233861
|
function removeInjectedAuthPlaceholder(authPath, logger48, agentId) {
|
|
233500
233862
|
try {
|
|
233501
|
-
if (!
|
|
233863
|
+
if (!existsSync3(authPath)) return;
|
|
233502
233864
|
const parsed = JSON.parse(readTextFileSync(authPath));
|
|
233503
233865
|
const profiles = parsed?.profiles;
|
|
233504
233866
|
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) return;
|
|
@@ -233953,7 +234315,7 @@ function buildImageGenerationProvider() {
|
|
|
233953
234315
|
(result.data ?? []).map(async (img) => {
|
|
233954
234316
|
const filename = img.url?.split("/images/").pop();
|
|
233955
234317
|
if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
|
|
233956
|
-
const filePath =
|
|
234318
|
+
const filePath = join19(IMAGE_DIR2, filename);
|
|
233957
234319
|
const buffer2 = await readFileAsync(filePath);
|
|
233958
234320
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
233959
234321
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
@@ -234005,7 +234367,7 @@ function buildMusicGenerationProvider() {
|
|
|
234005
234367
|
(result.data ?? []).map(async (track) => {
|
|
234006
234368
|
const filename = track.url?.split("/audio/").pop();
|
|
234007
234369
|
if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
|
|
234008
|
-
const filePath =
|
|
234370
|
+
const filePath = join19(AUDIO_DIR2, filename);
|
|
234009
234371
|
const buffer2 = await readFileAsync(filePath);
|
|
234010
234372
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
|
|
234011
234373
|
const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
|
|
@@ -234081,7 +234443,7 @@ function buildVideoGenerationProvider() {
|
|
|
234081
234443
|
(result.data ?? []).map(async (clip) => {
|
|
234082
234444
|
const filename = clip.url?.split("/videos/").pop();
|
|
234083
234445
|
if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
|
|
234084
|
-
const filePath =
|
|
234446
|
+
const filePath = join19(VIDEO_DIR2, filename);
|
|
234085
234447
|
const buffer2 = await readFileAsync(filePath);
|
|
234086
234448
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
|
|
234087
234449
|
const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
|
|
@@ -234182,7 +234544,7 @@ function createWalletCommand(api) {
|
|
|
234182
234544
|
];
|
|
234183
234545
|
let hasMnemonic3 = false;
|
|
234184
234546
|
try {
|
|
234185
|
-
if (
|
|
234547
|
+
if (existsSync3(MNEMONIC_FILE)) {
|
|
234186
234548
|
const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
|
|
234187
234549
|
if (mnemonic) {
|
|
234188
234550
|
hasMnemonic3 = true;
|
|
@@ -234253,7 +234615,7 @@ function createWalletCommand(api) {
|
|
|
234253
234615
|
].join("\n")
|
|
234254
234616
|
};
|
|
234255
234617
|
}
|
|
234256
|
-
if (
|
|
234618
|
+
if (existsSync3(MNEMONIC_FILE)) {
|
|
234257
234619
|
const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
|
|
234258
234620
|
if (existingMnemonic) {
|
|
234259
234621
|
await savePaymentChain("solana");
|
|
@@ -234446,9 +234808,9 @@ var init_index = __esm({
|
|
|
234446
234808
|
init_partners();
|
|
234447
234809
|
activeProxyHandle = null;
|
|
234448
234810
|
pendingConfiguredStartupApi = null;
|
|
234449
|
-
IMAGE_DIR2 =
|
|
234450
|
-
AUDIO_DIR2 =
|
|
234451
|
-
VIDEO_DIR2 =
|
|
234811
|
+
IMAGE_DIR2 = join19(homedir16(), ".openclaw", "blockrun", "images");
|
|
234812
|
+
AUDIO_DIR2 = join19(homedir16(), ".openclaw", "blockrun", "audio");
|
|
234813
|
+
VIDEO_DIR2 = join19(homedir16(), ".openclaw", "blockrun", "videos");
|
|
234452
234814
|
plugin = {
|
|
234453
234815
|
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
234454
234816
|
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
@@ -234588,7 +234950,8 @@ var init_index = __esm({
|
|
|
234588
234950
|
name: "cr-imagegen",
|
|
234589
234951
|
description: "Generate an image (BlockRun image models, paid via wallet)",
|
|
234590
234952
|
acceptsArgs: true,
|
|
234591
|
-
|
|
234953
|
+
// Paid from the wallet: only authorized senders.
|
|
234954
|
+
requireAuth: true,
|
|
234592
234955
|
handler: async (ctx) => {
|
|
234593
234956
|
const parsed = parseGenArgs(ctx.args ?? "");
|
|
234594
234957
|
if (!parsed.prompt) {
|
|
@@ -234640,7 +235003,8 @@ ${errText}`
|
|
|
234640
235003
|
name: "videogen",
|
|
234641
235004
|
description: "Generate a short video (Grok Imagine / Seedance, paid via wallet)",
|
|
234642
235005
|
acceptsArgs: true,
|
|
234643
|
-
|
|
235006
|
+
// Paid from the wallet: only authorized senders.
|
|
235007
|
+
requireAuth: true,
|
|
234644
235008
|
handler: async (ctx) => {
|
|
234645
235009
|
const parsed = parseGenArgs(ctx.args ?? "");
|
|
234646
235010
|
if (!parsed.prompt) {
|
|
@@ -234689,7 +235053,8 @@ ${errText}`
|
|
|
234689
235053
|
name: "cr-call",
|
|
234690
235054
|
description: "Place an AI voice call via BlockRun + Bland.ai (paid from wallet, $0.54/call)",
|
|
234691
235055
|
acceptsArgs: true,
|
|
234692
|
-
|
|
235056
|
+
// Paid from the wallet: only authorized senders.
|
|
235057
|
+
requireAuth: true,
|
|
234693
235058
|
handler: async (ctx) => {
|
|
234694
235059
|
const parsed = parseCallArgs(ctx.args ?? "");
|
|
234695
235060
|
if (!parsed.to || !parsed.task) {
|
|
@@ -234886,8 +235251,8 @@ ${errText}`
|
|
|
234886
235251
|
}
|
|
234887
235252
|
resetProxyStartupState();
|
|
234888
235253
|
try {
|
|
234889
|
-
const configPath =
|
|
234890
|
-
if (
|
|
235254
|
+
const configPath = join19(homedir16(), ".openclaw", "openclaw.json");
|
|
235255
|
+
if (existsSync3(configPath)) {
|
|
234891
235256
|
const config = JSON.parse(readTextFileSync(configPath));
|
|
234892
235257
|
if (config.models?.providers?.blockrun) {
|
|
234893
235258
|
delete config.models.providers.blockrun;
|
|
@@ -234928,12 +235293,12 @@ ${errText}`
|
|
|
234928
235293
|
api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
234929
235294
|
}
|
|
234930
235295
|
try {
|
|
234931
|
-
const agentsDir =
|
|
234932
|
-
if (
|
|
235296
|
+
const agentsDir = join19(homedir16(), ".openclaw", "agents");
|
|
235297
|
+
if (existsSync3(agentsDir)) {
|
|
234933
235298
|
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
234934
235299
|
if (!entry.isDirectory()) continue;
|
|
234935
|
-
const authPath =
|
|
234936
|
-
if (!
|
|
235300
|
+
const authPath = join19(agentsDir, entry.name, "agent", "auth-profiles.json");
|
|
235301
|
+
if (!existsSync3(authPath)) continue;
|
|
234937
235302
|
try {
|
|
234938
235303
|
const store = JSON.parse(readTextFileSync(authPath));
|
|
234939
235304
|
if (store.profiles?.["blockrun:default"]) {
|
|
@@ -235018,7 +235383,7 @@ init_proxy();
|
|
|
235018
235383
|
init_spend_control();
|
|
235019
235384
|
init_version4();
|
|
235020
235385
|
import { platform, arch, freemem, totalmem } from "os";
|
|
235021
|
-
import { existsSync as
|
|
235386
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
235022
235387
|
function formatBytes(bytes) {
|
|
235023
235388
|
const gb = bytes / (1024 * 1024 * 1024);
|
|
235024
235389
|
return `${gb.toFixed(1)}GB`;
|
|
@@ -235388,7 +235753,7 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
|
235388
235753
|
const paymentChain = diagnostics.wallet.paymentChain;
|
|
235389
235754
|
if (paymentChain === "solana") {
|
|
235390
235755
|
try {
|
|
235391
|
-
if (!
|
|
235756
|
+
if (!existsSync2(MNEMONIC_FILE)) {
|
|
235392
235757
|
throw new Error(`mnemonic file missing at ${MNEMONIC_FILE}`);
|
|
235393
235758
|
}
|
|
235394
235759
|
const mnemonic = readFileSync3(MNEMONIC_FILE, "utf8").trim();
|
|
@@ -236037,7 +236402,7 @@ async function cmdSetup() {
|
|
|
236037
236402
|
const {
|
|
236038
236403
|
copyFileSync: copyFileSync2,
|
|
236039
236404
|
cpSync,
|
|
236040
|
-
existsSync:
|
|
236405
|
+
existsSync: existsSync4,
|
|
236041
236406
|
mkdirSync: mkdirSync4,
|
|
236042
236407
|
readFileSync: readFileSync4,
|
|
236043
236408
|
readdirSync: readdirSync2,
|
|
@@ -236046,8 +236411,8 @@ async function cmdSetup() {
|
|
|
236046
236411
|
unlinkSync: unlinkSync2,
|
|
236047
236412
|
writeFileSync: writeFileSync4
|
|
236048
236413
|
} = await import("fs");
|
|
236049
|
-
const { dirname: dirname5, join:
|
|
236050
|
-
const { homedir:
|
|
236414
|
+
const { dirname: dirname5, join: join20 } = await import("path");
|
|
236415
|
+
const { homedir: homedir17 } = await import("os");
|
|
236051
236416
|
const { injectModelsConfig: injectModelsConfig2, injectAuthProfile: injectAuthProfile2, syncAgentModelCache: syncAgentModelCache2, VISIBLE_OPENCLAW_MODELS: VISIBLE_OPENCLAW_MODELS2 } = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
236052
236417
|
const { BLOCKRUN_PLUGIN_ID: BLOCKRUN_PLUGIN_ID2, prepareBlockRunPluginConfig: prepareBlockRunPluginConfig2 } = await Promise.resolve().then(() => (init_openclaw_plugin_config(), openclaw_plugin_config_exports));
|
|
236053
236418
|
console.log("\u{1F99E} ClawRouter setup\n");
|
|
@@ -236062,22 +236427,22 @@ async function cmdSetup() {
|
|
|
236062
236427
|
} catch {
|
|
236063
236428
|
}
|
|
236064
236429
|
if (process.env.npm_config_prefix) {
|
|
236065
|
-
candidates.push(
|
|
236430
|
+
candidates.push(join20(process.env.npm_config_prefix, "bin", "openclaw"));
|
|
236066
236431
|
}
|
|
236067
236432
|
try {
|
|
236068
236433
|
const npmRoot = execSync("npm root -g", {
|
|
236069
236434
|
encoding: "utf8",
|
|
236070
236435
|
stdio: ["ignore", "pipe", "ignore"]
|
|
236071
236436
|
}).trim();
|
|
236072
|
-
if (npmRoot) candidates.push(
|
|
236437
|
+
if (npmRoot) candidates.push(join20(dirname5(dirname5(npmRoot)), "bin", "openclaw"));
|
|
236073
236438
|
} catch {
|
|
236074
236439
|
}
|
|
236075
236440
|
candidates.push(
|
|
236076
|
-
|
|
236077
|
-
|
|
236441
|
+
join20(homedir17(), ".npm-global", "bin", "openclaw"),
|
|
236442
|
+
join20(homedir17(), ".local", "bin", "openclaw"),
|
|
236078
236443
|
"/usr/local/bin/openclaw"
|
|
236079
236444
|
);
|
|
236080
|
-
return candidates.find((candidate) =>
|
|
236445
|
+
return candidates.find((candidate) => existsSync4(candidate)) ?? "";
|
|
236081
236446
|
})();
|
|
236082
236447
|
if (!openclawPath) {
|
|
236083
236448
|
console.error(" \u2717 openclaw not found on PATH");
|
|
@@ -236086,31 +236451,31 @@ async function cmdSetup() {
|
|
|
236086
236451
|
process.exit(1);
|
|
236087
236452
|
}
|
|
236088
236453
|
console.log(` \u2713 Found openclaw at ${openclawPath}`);
|
|
236089
|
-
const configPath =
|
|
236090
|
-
const configInitiallyExisted =
|
|
236454
|
+
const configPath = join20(homedir17(), ".openclaw", "openclaw.json");
|
|
236455
|
+
const configInitiallyExisted = existsSync4(configPath);
|
|
236091
236456
|
const configRollbackBackup = configInitiallyExisted ? `${configPath}.before-${BLOCKRUN_PLUGIN_ID2}.${Date.now()}` : void 0;
|
|
236092
236457
|
if (configRollbackBackup) copyFileSync2(configPath, configRollbackBackup);
|
|
236093
236458
|
const ensureConfigRollbackBackup = () => {
|
|
236094
236459
|
return configRollbackBackup;
|
|
236095
236460
|
};
|
|
236096
236461
|
let installAttempted = false;
|
|
236097
|
-
const openclawRoot =
|
|
236462
|
+
const openclawRoot = join20(homedir17(), ".openclaw");
|
|
236098
236463
|
const findManagedBlockRunInstalls = () => {
|
|
236099
236464
|
const candidates = [
|
|
236100
|
-
|
|
236101
|
-
|
|
236102
|
-
|
|
236465
|
+
join20(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
|
|
236466
|
+
join20(openclawRoot, "extensions", "clawrouter"),
|
|
236467
|
+
join20(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
|
|
236103
236468
|
];
|
|
236104
|
-
const projectsDir =
|
|
236469
|
+
const projectsDir = join20(openclawRoot, "npm", "projects");
|
|
236105
236470
|
try {
|
|
236106
236471
|
for (const project of readdirSync2(projectsDir)) {
|
|
236107
|
-
candidates.push(
|
|
236472
|
+
candidates.push(join20(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
|
|
236108
236473
|
}
|
|
236109
236474
|
} catch {
|
|
236110
236475
|
}
|
|
236111
236476
|
return [...new Set(candidates)].filter((candidate) => {
|
|
236112
236477
|
try {
|
|
236113
|
-
const metadata = JSON.parse(readFileSync4(
|
|
236478
|
+
const metadata = JSON.parse(readFileSync4(join20(candidate, "package.json"), "utf8"));
|
|
236114
236479
|
return metadata.name === "@blockrun/clawrouter";
|
|
236115
236480
|
} catch {
|
|
236116
236481
|
return false;
|
|
@@ -236118,7 +236483,7 @@ async function cmdSetup() {
|
|
|
236118
236483
|
});
|
|
236119
236484
|
};
|
|
236120
236485
|
const initialPluginDirs = findManagedBlockRunInstalls();
|
|
236121
|
-
const setupRollbackRoot =
|
|
236486
|
+
const setupRollbackRoot = join20(
|
|
236122
236487
|
openclawRoot,
|
|
236123
236488
|
"blockrun",
|
|
236124
236489
|
"setup-rollbacks",
|
|
@@ -236128,16 +236493,16 @@ async function cmdSetup() {
|
|
|
236128
236493
|
if (initialPluginDirs.length > 0) {
|
|
236129
236494
|
mkdirSync4(setupRollbackRoot, { recursive: true, mode: 448 });
|
|
236130
236495
|
for (const [index2, pluginDir] of initialPluginDirs.entries()) {
|
|
236131
|
-
const backup =
|
|
236496
|
+
const backup = join20(setupRollbackRoot, String(index2));
|
|
236132
236497
|
cpSync(pluginDir, backup, { recursive: true });
|
|
236133
236498
|
pluginRollbackBackups.set(pluginDir, backup);
|
|
236134
236499
|
}
|
|
236135
236500
|
}
|
|
236136
236501
|
const restoreSetupConfig = () => {
|
|
236137
|
-
if (configRollbackBackup &&
|
|
236502
|
+
if (configRollbackBackup && existsSync4(configRollbackBackup)) {
|
|
236138
236503
|
copyFileSync2(configRollbackBackup, configPath);
|
|
236139
236504
|
console.error(` \u2713 Restored original OpenClaw config from ${configRollbackBackup}`);
|
|
236140
|
-
} else if (!configInitiallyExisted &&
|
|
236505
|
+
} else if (!configInitiallyExisted && existsSync4(configPath)) {
|
|
236141
236506
|
rmSync(configPath);
|
|
236142
236507
|
console.error(" \u2713 Removed the partially created OpenClaw config");
|
|
236143
236508
|
}
|
|
@@ -236159,7 +236524,7 @@ async function cmdSetup() {
|
|
|
236159
236524
|
}
|
|
236160
236525
|
}
|
|
236161
236526
|
for (const [pluginDir, backup] of pluginRollbackBackups) {
|
|
236162
|
-
if (!
|
|
236527
|
+
if (!existsSync4(backup)) continue;
|
|
236163
236528
|
rmSync(pluginDir, { recursive: true, force: true });
|
|
236164
236529
|
mkdirSync4(dirname5(pluginDir), { recursive: true });
|
|
236165
236530
|
cpSync(backup, pluginDir, { recursive: true });
|
|
@@ -236170,17 +236535,17 @@ async function cmdSetup() {
|
|
|
236170
236535
|
};
|
|
236171
236536
|
let legacyBlockRunInstall = false;
|
|
236172
236537
|
let stagedLegacyEntry;
|
|
236173
|
-
if (
|
|
236538
|
+
if (existsSync4(configPath)) {
|
|
236174
236539
|
try {
|
|
236175
236540
|
const config = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
236176
|
-
const legacyPackagePath =
|
|
236177
|
-
|
|
236541
|
+
const legacyPackagePath = join20(
|
|
236542
|
+
homedir17(),
|
|
236178
236543
|
".openclaw",
|
|
236179
236544
|
"extensions",
|
|
236180
236545
|
"clawrouter",
|
|
236181
236546
|
"package.json"
|
|
236182
236547
|
);
|
|
236183
|
-
if (
|
|
236548
|
+
if (existsSync4(legacyPackagePath)) {
|
|
236184
236549
|
try {
|
|
236185
236550
|
const legacyPackage = JSON.parse(readFileSync4(legacyPackagePath, "utf8"));
|
|
236186
236551
|
legacyBlockRunInstall = legacyPackage.name === "@blockrun/clawrouter";
|
|
@@ -236261,7 +236626,7 @@ async function cmdSetup() {
|
|
|
236261
236626
|
);
|
|
236262
236627
|
process.exit(1);
|
|
236263
236628
|
}
|
|
236264
|
-
if (
|
|
236629
|
+
if (existsSync4(configPath)) {
|
|
236265
236630
|
try {
|
|
236266
236631
|
const config = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
236267
236632
|
if (stagedLegacyEntry) {
|
|
@@ -236303,7 +236668,7 @@ async function cmdSetup() {
|
|
|
236303
236668
|
info: (msg) => console.log(` ${msg}`)
|
|
236304
236669
|
};
|
|
236305
236670
|
try {
|
|
236306
|
-
if (
|
|
236671
|
+
if (existsSync4(configPath)) ensureConfigRollbackBackup();
|
|
236307
236672
|
injectModelsConfig2(setupLogger, { forceWrite: true });
|
|
236308
236673
|
syncAgentModelCache2(setupLogger, { forceWrite: true });
|
|
236309
236674
|
injectAuthProfile2(setupLogger);
|
|
@@ -236314,7 +236679,7 @@ async function cmdSetup() {
|
|
|
236314
236679
|
}
|
|
236315
236680
|
console.log("\n\u2713 Setup complete.");
|
|
236316
236681
|
rmSync(setupRollbackRoot, { recursive: true, force: true });
|
|
236317
|
-
if (configRollbackBackup &&
|
|
236682
|
+
if (configRollbackBackup && existsSync4(configRollbackBackup)) {
|
|
236318
236683
|
unlinkSync2(configRollbackBackup);
|
|
236319
236684
|
}
|
|
236320
236685
|
console.log("\nNext: restart the gateway to load ClawRouter:");
|