@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/index.js
CHANGED
|
@@ -34678,6 +34678,11 @@ function getSharedSpendControl() {
|
|
|
34678
34678
|
host.__clawrouterSharedSpendControl ??= new SpendControl();
|
|
34679
34679
|
return host.__clawrouterSharedSpendControl;
|
|
34680
34680
|
}
|
|
34681
|
+
function parseQuotedAmountMicros(selected) {
|
|
34682
|
+
const raw = selected.amount ?? selected.maxAmountRequired;
|
|
34683
|
+
if (typeof raw !== "string" || !CANONICAL_AMOUNT.test(raw)) return void 0;
|
|
34684
|
+
return BigInt(raw);
|
|
34685
|
+
}
|
|
34681
34686
|
function parseQuotedAmountUsd(selected) {
|
|
34682
34687
|
const raw = selected.amount ?? selected.maxAmountRequired;
|
|
34683
34688
|
if (typeof raw !== "string" || !CANONICAL_AMOUNT.test(raw)) {
|
|
@@ -35443,6 +35448,16 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
35443
35448
|
return est === void 0 ? void 0 : Number(est);
|
|
35444
35449
|
};
|
|
35445
35450
|
const needMicros = estimateMicros();
|
|
35451
|
+
const notifyDeclinedPayment = () => {
|
|
35452
|
+
if (!options?.onPaymentDeclined) return;
|
|
35453
|
+
try {
|
|
35454
|
+
options.onPaymentDeclined();
|
|
35455
|
+
} catch (error) {
|
|
35456
|
+
console.error(
|
|
35457
|
+
`[ClawRouter] onPaymentDeclined callback failed: ${error instanceof Error ? error.message : String(error)}`
|
|
35458
|
+
);
|
|
35459
|
+
}
|
|
35460
|
+
};
|
|
35446
35461
|
const cached = !options?.skipPreAuth ? cache2.get(cacheKey2) : void 0;
|
|
35447
35462
|
const preAuthCovers = cached !== void 0 && Date.now() - cached.cachedAt < ttlMs && cached.coverMicros !== void 0 && needMicros !== void 0 && needMicros <= cached.coverMicros;
|
|
35448
35463
|
if (preAuthCovers) {
|
|
@@ -35461,6 +35476,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
35461
35476
|
return response2;
|
|
35462
35477
|
}
|
|
35463
35478
|
paymentInFlight = false;
|
|
35479
|
+
notifyDeclinedPayment();
|
|
35464
35480
|
cache2.delete(cacheKey2);
|
|
35465
35481
|
} catch (err) {
|
|
35466
35482
|
cache2.delete(cacheKey2);
|
|
@@ -35505,6 +35521,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
35505
35521
|
clonedRequest.headers.set(key3, value);
|
|
35506
35522
|
}
|
|
35507
35523
|
const paidResponse = await baseFetch(clonedRequest);
|
|
35524
|
+
if (paidResponse.status === 402) notifyDeclinedPayment();
|
|
35508
35525
|
notifyAcceptedPayment(paidResponse, payload);
|
|
35509
35526
|
return paidResponse;
|
|
35510
35527
|
};
|
|
@@ -64542,6 +64559,132 @@ var init_api_key = __esm({
|
|
|
64542
64559
|
}
|
|
64543
64560
|
});
|
|
64544
64561
|
|
|
64562
|
+
// src/session-spend.ts
|
|
64563
|
+
function parseMicros(raw) {
|
|
64564
|
+
if (raw === void 0 || !/^\d+$/.test(raw)) return void 0;
|
|
64565
|
+
return BigInt(raw);
|
|
64566
|
+
}
|
|
64567
|
+
function usdToMicros(usd) {
|
|
64568
|
+
return BigInt(Math.round(usd * 1e6));
|
|
64569
|
+
}
|
|
64570
|
+
function microsToUsd(micros) {
|
|
64571
|
+
return Number(micros) / 1e6;
|
|
64572
|
+
}
|
|
64573
|
+
var SessionSpend, CostCapError;
|
|
64574
|
+
var init_session_spend = __esm({
|
|
64575
|
+
"src/session-spend.ts"() {
|
|
64576
|
+
"use strict";
|
|
64577
|
+
init_spend_control();
|
|
64578
|
+
SessionSpend = class _SessionSpend {
|
|
64579
|
+
constructor(store, sessionId, capMicros, holdMicros) {
|
|
64580
|
+
this.store = store;
|
|
64581
|
+
this.sessionId = sessionId;
|
|
64582
|
+
this.capMicros = capMicros;
|
|
64583
|
+
this.heldMicros = holdMicros;
|
|
64584
|
+
if (holdMicros > 0n) store.adjustSessionHold(sessionId, holdMicros);
|
|
64585
|
+
}
|
|
64586
|
+
store;
|
|
64587
|
+
sessionId;
|
|
64588
|
+
capMicros;
|
|
64589
|
+
/** Budget this request still holds against the session (strict mode only). */
|
|
64590
|
+
heldMicros;
|
|
64591
|
+
/** Sum of payments signed by this request that still count. */
|
|
64592
|
+
signedMicros = 0n;
|
|
64593
|
+
/** The most recent signature not yet settled or voided — the one a decline undoes. */
|
|
64594
|
+
lastSignedMicros = 0n;
|
|
64595
|
+
released = false;
|
|
64596
|
+
/**
|
|
64597
|
+
* Strict mode: admit the request only if the session's spend, plus what its
|
|
64598
|
+
* other in-flight requests hold, plus this request's estimate fits the cap —
|
|
64599
|
+
* and hold the estimate in the same step.
|
|
64600
|
+
*/
|
|
64601
|
+
static admit(store, sessionId, capMicros, estimateMicros) {
|
|
64602
|
+
const spentMicros = store.getSessionCostMicros(sessionId);
|
|
64603
|
+
const heldMicros = store.getSessionHoldMicros(sessionId);
|
|
64604
|
+
if (spentMicros + heldMicros + estimateMicros > capMicros) {
|
|
64605
|
+
return { admitted: false, spentMicros, heldMicros, estimateMicros };
|
|
64606
|
+
}
|
|
64607
|
+
return {
|
|
64608
|
+
admitted: true,
|
|
64609
|
+
spend: new _SessionSpend(store, sessionId, capMicros, estimateMicros)
|
|
64610
|
+
};
|
|
64611
|
+
}
|
|
64612
|
+
/** Graceful mode: track signed spend, enforce nothing. */
|
|
64613
|
+
static track(store, sessionId) {
|
|
64614
|
+
return new _SessionSpend(store, sessionId, void 0, 0n);
|
|
64615
|
+
}
|
|
64616
|
+
get isStrict() {
|
|
64617
|
+
return this.capMicros !== void 0;
|
|
64618
|
+
}
|
|
64619
|
+
/**
|
|
64620
|
+
* Before a payment is created: may this request sign `quoteMicros`?
|
|
64621
|
+
* Spend already recorded (including this request's own earlier signatures)
|
|
64622
|
+
* plus what OTHER in-flight requests hold, plus the quote, must fit the cap.
|
|
64623
|
+
* Always true in graceful mode.
|
|
64624
|
+
*/
|
|
64625
|
+
coverPayment(quoteMicros) {
|
|
64626
|
+
if (this.capMicros === void 0) return true;
|
|
64627
|
+
const spent = this.store.getSessionCostMicros(this.sessionId);
|
|
64628
|
+
const heldByOthers = this.store.getSessionHoldMicros(this.sessionId) - this.heldMicros;
|
|
64629
|
+
if (spent + heldByOthers + quoteMicros > this.capMicros) return false;
|
|
64630
|
+
if (quoteMicros > this.heldMicros) {
|
|
64631
|
+
this.store.adjustSessionHold(this.sessionId, quoteMicros - this.heldMicros);
|
|
64632
|
+
this.heldMicros = quoteMicros;
|
|
64633
|
+
}
|
|
64634
|
+
return true;
|
|
64635
|
+
}
|
|
64636
|
+
/** A payment was signed: it is spend now, and replaces part of the hold. */
|
|
64637
|
+
recordSigned(amountMicros) {
|
|
64638
|
+
if (amountMicros <= 0n) return;
|
|
64639
|
+
this.signedMicros += amountMicros;
|
|
64640
|
+
this.lastSignedMicros = amountMicros;
|
|
64641
|
+
this.store.addSessionCost(this.sessionId, amountMicros);
|
|
64642
|
+
this.shrinkHold(amountMicros);
|
|
64643
|
+
}
|
|
64644
|
+
/**
|
|
64645
|
+
* The gateway answered the payment just signed with a 402: nothing settled.
|
|
64646
|
+
* Payments within one request are signed and sent one at a time, so the
|
|
64647
|
+
* decline always belongs to the latest signature. Undone at most once.
|
|
64648
|
+
*/
|
|
64649
|
+
voidLastSigned() {
|
|
64650
|
+
const undo = this.lastSignedMicros;
|
|
64651
|
+
this.lastSignedMicros = 0n;
|
|
64652
|
+
if (undo <= 0n) return;
|
|
64653
|
+
this.signedMicros -= undo;
|
|
64654
|
+
this.store.addSessionCost(this.sessionId, -undo);
|
|
64655
|
+
}
|
|
64656
|
+
/**
|
|
64657
|
+
* An attempt succeeded. When nothing was signed for this request (the API
|
|
64658
|
+
* key rail), the local estimate is the only record of the spend. When a
|
|
64659
|
+
* payment was signed, it is already recorded at its real amount.
|
|
64660
|
+
*/
|
|
64661
|
+
recordSuccess(estimateMicros) {
|
|
64662
|
+
if (this.signedMicros > 0n || estimateMicros <= 0n) return;
|
|
64663
|
+
this.store.addSessionCost(this.sessionId, estimateMicros);
|
|
64664
|
+
this.shrinkHold(estimateMicros);
|
|
64665
|
+
}
|
|
64666
|
+
/** The request is over: return whatever it still holds. Idempotent. */
|
|
64667
|
+
release() {
|
|
64668
|
+
if (this.released) return;
|
|
64669
|
+
this.released = true;
|
|
64670
|
+
this.shrinkHold(this.heldMicros);
|
|
64671
|
+
}
|
|
64672
|
+
shrinkHold(byMicros) {
|
|
64673
|
+
const drop = byMicros < this.heldMicros ? byMicros : this.heldMicros;
|
|
64674
|
+
if (drop <= 0n) return;
|
|
64675
|
+
this.heldMicros -= drop;
|
|
64676
|
+
this.store.adjustSessionHold(this.sessionId, -drop);
|
|
64677
|
+
}
|
|
64678
|
+
};
|
|
64679
|
+
CostCapError = class extends SpendPolicyError {
|
|
64680
|
+
constructor(reason) {
|
|
64681
|
+
super(reason);
|
|
64682
|
+
this.name = "CostCapError";
|
|
64683
|
+
}
|
|
64684
|
+
};
|
|
64685
|
+
}
|
|
64686
|
+
});
|
|
64687
|
+
|
|
64545
64688
|
// src/compression/types.ts
|
|
64546
64689
|
var DEFAULT_COMPRESSION_CONFIG;
|
|
64547
64690
|
var init_types2 = __esm({
|
|
@@ -65389,6 +65532,9 @@ var init_session = __esm({
|
|
|
65389
65532
|
};
|
|
65390
65533
|
SessionStore = class {
|
|
65391
65534
|
sessions = /* @__PURE__ */ new Map();
|
|
65535
|
+
// Kept apart from `sessions`: a hold belongs to requests still running, so it
|
|
65536
|
+
// must not vanish when the idle-session sweep drops an entry mid-request.
|
|
65537
|
+
sessionHolds = /* @__PURE__ */ new Map();
|
|
65392
65538
|
config;
|
|
65393
65539
|
cleanupInterval = null;
|
|
65394
65540
|
constructor(config = {}) {
|
|
@@ -65564,14 +65710,34 @@ var init_session = __esm({
|
|
|
65564
65710
|
this.sessions.set(sessionId, entry);
|
|
65565
65711
|
}
|
|
65566
65712
|
entry.sessionCostMicros += additionalMicros;
|
|
65713
|
+
if (entry.sessionCostMicros < 0n) entry.sessionCostMicros = 0n;
|
|
65567
65714
|
}
|
|
65568
65715
|
/**
|
|
65569
65716
|
* Get the total accumulated cost for a session in USD.
|
|
65570
65717
|
*/
|
|
65571
65718
|
getSessionCostUsd(sessionId) {
|
|
65572
|
-
|
|
65573
|
-
|
|
65574
|
-
|
|
65719
|
+
return Number(this.getSessionCostMicros(sessionId)) / 1e6;
|
|
65720
|
+
}
|
|
65721
|
+
/**
|
|
65722
|
+
* Get the total accumulated cost for a session in USDC micros.
|
|
65723
|
+
*/
|
|
65724
|
+
getSessionCostMicros(sessionId) {
|
|
65725
|
+
return this.sessions.get(sessionId)?.sessionCostMicros ?? 0n;
|
|
65726
|
+
}
|
|
65727
|
+
/**
|
|
65728
|
+
* Budget held by a session's in-flight requests (USDC micros) — admitted
|
|
65729
|
+
* against the strict cap but not yet spent. See session-spend.ts.
|
|
65730
|
+
*/
|
|
65731
|
+
getSessionHoldMicros(sessionId) {
|
|
65732
|
+
return this.sessionHolds.get(sessionId) ?? 0n;
|
|
65733
|
+
}
|
|
65734
|
+
/**
|
|
65735
|
+
* Grow (positive) or release (negative) a session's in-flight hold.
|
|
65736
|
+
*/
|
|
65737
|
+
adjustSessionHold(sessionId, deltaMicros) {
|
|
65738
|
+
const next = (this.sessionHolds.get(sessionId) ?? 0n) + deltaMicros;
|
|
65739
|
+
if (next > 0n) this.sessionHolds.set(sessionId, next);
|
|
65740
|
+
else this.sessionHolds.delete(sessionId);
|
|
65575
65741
|
}
|
|
65576
65742
|
/**
|
|
65577
65743
|
* Stop the cleanup interval.
|
|
@@ -65931,6 +66097,156 @@ ${lines.join("\n")}`;
|
|
|
65931
66097
|
}
|
|
65932
66098
|
});
|
|
65933
66099
|
|
|
66100
|
+
// src/local-guard.ts
|
|
66101
|
+
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2, realpathSync } from "fs";
|
|
66102
|
+
import { homedir as homedir7 } from "os";
|
|
66103
|
+
import { join as join10 } from "path";
|
|
66104
|
+
function isLocalHostname(hostname) {
|
|
66105
|
+
const h = hostname.toLowerCase().replace(/\.$/, "");
|
|
66106
|
+
return h === "localhost" || h === "127.0.0.1" || h === "[::1]" || h === "::1";
|
|
66107
|
+
}
|
|
66108
|
+
function untrustedRequestReason(req) {
|
|
66109
|
+
const host = req.headers.host;
|
|
66110
|
+
if (host) {
|
|
66111
|
+
let hostname;
|
|
66112
|
+
try {
|
|
66113
|
+
hostname = new URL(`http://${host}`).hostname;
|
|
66114
|
+
} catch {
|
|
66115
|
+
return "malformed Host header";
|
|
66116
|
+
}
|
|
66117
|
+
if (!isLocalHostname(hostname)) return `non-local Host header (${hostname})`;
|
|
66118
|
+
}
|
|
66119
|
+
const origin2 = req.headers.origin;
|
|
66120
|
+
if (Array.isArray(origin2)) return "multiple Origin headers";
|
|
66121
|
+
if (origin2 !== void 0) {
|
|
66122
|
+
if (origin2 === "null") return "opaque Origin";
|
|
66123
|
+
let o3;
|
|
66124
|
+
try {
|
|
66125
|
+
o3 = new URL(origin2);
|
|
66126
|
+
} catch {
|
|
66127
|
+
return "malformed Origin header";
|
|
66128
|
+
}
|
|
66129
|
+
if (o3.protocol !== "http:" && o3.protocol !== "https:" || !isLocalHostname(o3.hostname)) {
|
|
66130
|
+
return `cross-site Origin (${origin2})`;
|
|
66131
|
+
}
|
|
66132
|
+
}
|
|
66133
|
+
const site = req.headers["sec-fetch-site"];
|
|
66134
|
+
if (site === "cross-site") return "cross-site browser request";
|
|
66135
|
+
return null;
|
|
66136
|
+
}
|
|
66137
|
+
function v4FromHex(hi, lo) {
|
|
66138
|
+
return `${hi >>> 8 & 255}.${hi & 255}.${lo >>> 8 & 255}.${lo & 255}`;
|
|
66139
|
+
}
|
|
66140
|
+
function isBlockedSsrfHost(hostname) {
|
|
66141
|
+
const h = hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "").replace(/\.$/, "");
|
|
66142
|
+
if (!h) return true;
|
|
66143
|
+
if (h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local")) return true;
|
|
66144
|
+
if (h.endsWith(".internal")) return true;
|
|
66145
|
+
if (METADATA_HOSTS.has(h)) return true;
|
|
66146
|
+
if (h.includes(":")) {
|
|
66147
|
+
if (h === "::1" || h === "::") return true;
|
|
66148
|
+
if (h.startsWith("fe80:") || h.startsWith("fc") || h.startsWith("fd")) return true;
|
|
66149
|
+
const sixToFour = h.match(/^2002:([0-9a-f]{1,4}):([0-9a-f]{1,4}):/);
|
|
66150
|
+
if (sixToFour) {
|
|
66151
|
+
return isBlockedSsrfHost(v4FromHex(parseInt(sixToFour[1], 16), parseInt(sixToFour[2], 16)));
|
|
66152
|
+
}
|
|
66153
|
+
const embedded = h.match(/^64:ff9b::(.+)$/) ?? h.match(/::ffff:(.+)$/);
|
|
66154
|
+
if (embedded) {
|
|
66155
|
+
const tail = embedded[1];
|
|
66156
|
+
if (tail.includes(".")) return isBlockedSsrfHost(tail);
|
|
66157
|
+
const seg = tail.split(":");
|
|
66158
|
+
if (seg.length === 2)
|
|
66159
|
+
return isBlockedSsrfHost(v4FromHex(parseInt(seg[0], 16), parseInt(seg[1], 16)));
|
|
66160
|
+
}
|
|
66161
|
+
return false;
|
|
66162
|
+
}
|
|
66163
|
+
const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
66164
|
+
if (m) {
|
|
66165
|
+
const a = Number(m[1]);
|
|
66166
|
+
const b = Number(m[2]);
|
|
66167
|
+
const c = Number(m[3]);
|
|
66168
|
+
if (a === 127 || a === 0 || a === 10) return true;
|
|
66169
|
+
if (a === 169 && b === 254) return true;
|
|
66170
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
66171
|
+
if (a === 192 && b === 168) return true;
|
|
66172
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
66173
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
66174
|
+
}
|
|
66175
|
+
return false;
|
|
66176
|
+
}
|
|
66177
|
+
async function ssrfSafeFetch(url2, init2 = {}, maxHops = 5) {
|
|
66178
|
+
const { allowPrivate, ...fetchInit } = init2;
|
|
66179
|
+
let current = url2;
|
|
66180
|
+
for (let hop = 0; hop <= maxHops; hop++) {
|
|
66181
|
+
const u = new URL(current);
|
|
66182
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
66183
|
+
throw new Error(`refusing to fetch non-http(s) URL (${u.protocol})`);
|
|
66184
|
+
}
|
|
66185
|
+
if (!allowPrivate && isBlockedSsrfHost(u.hostname)) {
|
|
66186
|
+
throw new Error(`refusing to fetch a private/loopback/metadata address: ${u.hostname}`);
|
|
66187
|
+
}
|
|
66188
|
+
const res = await fetch(current, { ...fetchInit, redirect: "manual" });
|
|
66189
|
+
if (res.status >= 300 && res.status < 400) {
|
|
66190
|
+
const loc = res.headers.get("location");
|
|
66191
|
+
if (!loc) return res;
|
|
66192
|
+
current = new URL(loc, current).href;
|
|
66193
|
+
continue;
|
|
66194
|
+
}
|
|
66195
|
+
return res;
|
|
66196
|
+
}
|
|
66197
|
+
throw new Error("too many redirects");
|
|
66198
|
+
}
|
|
66199
|
+
function readLocalImageAsDataUri(filePath) {
|
|
66200
|
+
const expanded = filePath.startsWith("~/") ? join10(homedir7(), filePath.slice(2)) : filePath;
|
|
66201
|
+
const refuse = () => new Error(`Not a readable PNG, JPEG or WebP image: ${filePath}`);
|
|
66202
|
+
let fd;
|
|
66203
|
+
try {
|
|
66204
|
+
fd = openSync2(realpathSync(expanded), "r");
|
|
66205
|
+
} catch {
|
|
66206
|
+
throw refuse();
|
|
66207
|
+
}
|
|
66208
|
+
try {
|
|
66209
|
+
const st = fstatSync2(fd);
|
|
66210
|
+
if (!st.isFile() || st.size === 0 || st.size > MAX_LOCAL_IMAGE_BYTES) throw refuse();
|
|
66211
|
+
const head = Buffer.alloc(12);
|
|
66212
|
+
readSync2(fd, head, 0, 12, 0);
|
|
66213
|
+
const sig = IMAGE_SIGNATURES.find((s3) => s3.test(head));
|
|
66214
|
+
if (!sig) throw refuse();
|
|
66215
|
+
const data = Buffer.alloc(st.size);
|
|
66216
|
+
let off = 0;
|
|
66217
|
+
while (off < st.size) {
|
|
66218
|
+
const n = readSync2(fd, data, off, st.size - off, off);
|
|
66219
|
+
if (n === 0) break;
|
|
66220
|
+
off += n;
|
|
66221
|
+
}
|
|
66222
|
+
return `data:${sig.mime};base64,${data.subarray(0, off).toString("base64")}`;
|
|
66223
|
+
} finally {
|
|
66224
|
+
closeSync2(fd);
|
|
66225
|
+
}
|
|
66226
|
+
}
|
|
66227
|
+
var METADATA_HOSTS, MAX_LOCAL_IMAGE_BYTES, IMAGE_SIGNATURES;
|
|
66228
|
+
var init_local_guard = __esm({
|
|
66229
|
+
"src/local-guard.ts"() {
|
|
66230
|
+
"use strict";
|
|
66231
|
+
METADATA_HOSTS = /* @__PURE__ */ new Set(["metadata", "metadata.goog", "instance-data"]);
|
|
66232
|
+
MAX_LOCAL_IMAGE_BYTES = 25 * 1024 * 1024;
|
|
66233
|
+
IMAGE_SIGNATURES = [
|
|
66234
|
+
{
|
|
66235
|
+
mime: "image/png",
|
|
66236
|
+
test: (b) => b.length >= 8 && b.readUInt32BE(0) === 2303741511 && b.readUInt32BE(4) === 218765834
|
|
66237
|
+
},
|
|
66238
|
+
{
|
|
66239
|
+
mime: "image/jpeg",
|
|
66240
|
+
test: (b) => b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255
|
|
66241
|
+
},
|
|
66242
|
+
{
|
|
66243
|
+
mime: "image/webp",
|
|
66244
|
+
test: (b) => b.length >= 12 && b.toString("ascii", 0, 4) === "RIFF" && b.toString("ascii", 8, 12) === "WEBP"
|
|
66245
|
+
}
|
|
66246
|
+
];
|
|
66247
|
+
}
|
|
66248
|
+
});
|
|
66249
|
+
|
|
65934
66250
|
// node_modules/undici/lib/core/symbols.js
|
|
65935
66251
|
var require_symbols = __commonJS({
|
|
65936
66252
|
"node_modules/undici/lib/core/symbols.js"(exports, module) {
|
|
@@ -93324,8 +93640,8 @@ var init_textual_tool_calls = __esm({
|
|
|
93324
93640
|
|
|
93325
93641
|
// src/response-store.ts
|
|
93326
93642
|
import { appendFile as appendFile2, mkdir as mkdir5, readFile, readdir as readdir2 } from "fs/promises";
|
|
93327
|
-
import { homedir as
|
|
93328
|
-
import { join as
|
|
93643
|
+
import { homedir as homedir8 } from "os";
|
|
93644
|
+
import { join as join11 } from "path";
|
|
93329
93645
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
93330
93646
|
function isEnabled() {
|
|
93331
93647
|
const v = process.env.BLOCKRUN_RESPONSE_STORE;
|
|
@@ -93338,7 +93654,7 @@ async function ensureDir2() {
|
|
|
93338
93654
|
}
|
|
93339
93655
|
function dailyFile(date = /* @__PURE__ */ new Date()) {
|
|
93340
93656
|
const iso = date.toISOString().slice(0, 10);
|
|
93341
|
-
return
|
|
93657
|
+
return join11(STORE_DIR, `responses-${iso}.jsonl`);
|
|
93342
93658
|
}
|
|
93343
93659
|
function genId() {
|
|
93344
93660
|
return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
|
|
@@ -93388,7 +93704,7 @@ async function listRecent(limit = 20, daysBack = 7) {
|
|
|
93388
93704
|
d.setUTCDate(d.getUTCDate() - i);
|
|
93389
93705
|
const iso = d.toISOString().slice(0, 10);
|
|
93390
93706
|
const name = `responses-${iso}.jsonl`;
|
|
93391
|
-
if (files.includes(name)) candidateFiles.push(
|
|
93707
|
+
if (files.includes(name)) candidateFiles.push(join11(STORE_DIR, name));
|
|
93392
93708
|
}
|
|
93393
93709
|
const all3 = [];
|
|
93394
93710
|
for (const f of candidateFiles) {
|
|
@@ -93417,7 +93733,7 @@ var STORE_DIR, dirReady2;
|
|
|
93417
93733
|
var init_response_store = __esm({
|
|
93418
93734
|
"src/response-store.ts"() {
|
|
93419
93735
|
"use strict";
|
|
93420
|
-
STORE_DIR =
|
|
93736
|
+
STORE_DIR = join11(homedir8(), ".openclaw", "blockrun", "responses");
|
|
93421
93737
|
dirReady2 = false;
|
|
93422
93738
|
}
|
|
93423
93739
|
});
|
|
@@ -95054,10 +95370,10 @@ import { createHmac } from "crypto";
|
|
|
95054
95370
|
import { createServer } from "http";
|
|
95055
95371
|
import { finished, Readable } from "stream";
|
|
95056
95372
|
import { pipeline } from "stream/promises";
|
|
95057
|
-
import { homedir as
|
|
95058
|
-
import { join as
|
|
95373
|
+
import { homedir as homedir9 } from "os";
|
|
95374
|
+
import { join as join12 } from "path";
|
|
95059
95375
|
import { mkdir as mkdir6, writeFile as writeFile4, readFile as readFile2, stat as fsStat } from "fs/promises";
|
|
95060
|
-
import { readFileSync as readFileSync2
|
|
95376
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
95061
95377
|
async function loadGatewayCatalog(apiBase, apiKey) {
|
|
95062
95378
|
try {
|
|
95063
95379
|
const controller = new AbortController();
|
|
@@ -95951,22 +96267,6 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
95951
96267
|
}).catch(() => {
|
|
95952
96268
|
});
|
|
95953
96269
|
}
|
|
95954
|
-
function readImageFileAsDataUri(filePath) {
|
|
95955
|
-
const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
|
|
95956
|
-
if (!existsSync2(resolved)) {
|
|
95957
|
-
throw new Error(`Image file not found: ${resolved}`);
|
|
95958
|
-
}
|
|
95959
|
-
const ext = resolved.split(".").pop()?.toLowerCase() ?? "png";
|
|
95960
|
-
const mimeMap = {
|
|
95961
|
-
png: "image/png",
|
|
95962
|
-
jpg: "image/jpeg",
|
|
95963
|
-
jpeg: "image/jpeg",
|
|
95964
|
-
webp: "image/webp"
|
|
95965
|
-
};
|
|
95966
|
-
const mime = mimeMap[ext] ?? "image/png";
|
|
95967
|
-
const data = readFileSync2(resolved);
|
|
95968
|
-
return `data:${mime};base64,${data.toString("base64")}`;
|
|
95969
|
-
}
|
|
95970
96270
|
async function uploadDataUriToHost(dataUri) {
|
|
95971
96271
|
const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
95972
96272
|
if (!match) throw new Error("Invalid data URI format");
|
|
@@ -96140,6 +96440,19 @@ async function startProxy(options) {
|
|
|
96140
96440
|
}
|
|
96141
96441
|
const account = walletKey ? privateKeyToAccount(walletKey) : void 0;
|
|
96142
96442
|
const x402 = authMode === "wallet" ? new x402Client() : void 0;
|
|
96443
|
+
x402?.onBeforePaymentCreation(async (context) => {
|
|
96444
|
+
const spend = paymentStore.getStore()?.sessionSpend;
|
|
96445
|
+
if (!spend?.isStrict) return;
|
|
96446
|
+
const quote = parseQuotedAmountMicros(context.selectedRequirements);
|
|
96447
|
+
if (quote === void 0) {
|
|
96448
|
+
throw new CostCapError("ClawRouter cost cap: unreadable payment amount in the 402 quote");
|
|
96449
|
+
}
|
|
96450
|
+
if (!spend.coverPayment(quote)) {
|
|
96451
|
+
throw new CostCapError(
|
|
96452
|
+
`ClawRouter cost cap exceeded: signing $${microsToUsd(quote).toFixed(6)} would push this session past its maxCostPerRun limit`
|
|
96453
|
+
);
|
|
96454
|
+
}
|
|
96455
|
+
});
|
|
96143
96456
|
if (x402 && account) {
|
|
96144
96457
|
const evmPublicClient = createPublicClient({ chain: base, transport: http() });
|
|
96145
96458
|
const evmSigner = toClientEvmSigner(account, evmPublicClient);
|
|
@@ -96182,6 +96495,11 @@ async function startProxy(options) {
|
|
|
96182
96495
|
if (store) store.amountUsd = amountUsd;
|
|
96183
96496
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
96184
96497
|
});
|
|
96498
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
96499
|
+
const spend = paymentStore.getStore()?.sessionSpend;
|
|
96500
|
+
const amount = parseQuotedAmountMicros(context.selectedRequirements);
|
|
96501
|
+
if (spend && amount !== void 0) spend.recordSigned(amount);
|
|
96502
|
+
});
|
|
96185
96503
|
const payFetch = authMode === "api-key" ? withSpendPolicy(
|
|
96186
96504
|
createApiKeyFetch(apiKey, fetch, apiBase),
|
|
96187
96505
|
options.spendControl ?? getSharedSpendControl(),
|
|
@@ -96200,7 +96518,10 @@ async function startProxy(options) {
|
|
|
96200
96518
|
// Only the wallet rail settles per call. The API-key rail is billed
|
|
96201
96519
|
// server-side against account credit, so there is no per-request
|
|
96202
96520
|
// settlement for an observer to report.
|
|
96203
|
-
onPayment: options.onPayment
|
|
96521
|
+
onPayment: options.onPayment,
|
|
96522
|
+
// A 402 answer to a signed payment means the gateway declined it and
|
|
96523
|
+
// nothing settled — take it back off the session's budget.
|
|
96524
|
+
onPaymentDeclined: () => paymentStore.getStore()?.sessionSpend?.voidLastSigned()
|
|
96204
96525
|
});
|
|
96205
96526
|
let balanceMonitor;
|
|
96206
96527
|
if (options._balanceMonitorOverride) {
|
|
@@ -96243,6 +96564,13 @@ async function startProxy(options) {
|
|
|
96243
96564
|
console.error(`[ClawRouter] Request finished with error: ${err.message}`);
|
|
96244
96565
|
}
|
|
96245
96566
|
});
|
|
96567
|
+
const untrusted = untrustedRequestReason(req);
|
|
96568
|
+
if (untrusted) {
|
|
96569
|
+
console.warn(`[ClawRouter] Refused request to ${req.url}: ${untrusted}`);
|
|
96570
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
96571
|
+
res.end(JSON.stringify({ error: "forbidden", details: untrusted }));
|
|
96572
|
+
return;
|
|
96573
|
+
}
|
|
96246
96574
|
if (req.url === "/health" || req.url?.startsWith("/health?")) {
|
|
96247
96575
|
const url2 = new URL(req.url, "http://localhost");
|
|
96248
96576
|
const full = url2.searchParams.get("full") === "true";
|
|
@@ -96471,7 +96799,7 @@ async function startProxy(options) {
|
|
|
96471
96799
|
res.end("Bad request");
|
|
96472
96800
|
return;
|
|
96473
96801
|
}
|
|
96474
|
-
const filePath =
|
|
96802
|
+
const filePath = join12(IMAGE_DIR, filename);
|
|
96475
96803
|
try {
|
|
96476
96804
|
const s3 = await fsStat(filePath);
|
|
96477
96805
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96502,7 +96830,7 @@ async function startProxy(options) {
|
|
|
96502
96830
|
res.end("Bad request");
|
|
96503
96831
|
return;
|
|
96504
96832
|
}
|
|
96505
|
-
const filePath =
|
|
96833
|
+
const filePath = join12(AUDIO_DIR, filename);
|
|
96506
96834
|
try {
|
|
96507
96835
|
const s3 = await fsStat(filePath);
|
|
96508
96836
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96532,7 +96860,7 @@ async function startProxy(options) {
|
|
|
96532
96860
|
res.end("Bad request");
|
|
96533
96861
|
return;
|
|
96534
96862
|
}
|
|
96535
|
-
const filePath =
|
|
96863
|
+
const filePath = join12(VIDEO_DIR, filename);
|
|
96536
96864
|
try {
|
|
96537
96865
|
const s3 = await fsStat(filePath);
|
|
96538
96866
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96674,7 +97002,7 @@ async function startProxy(options) {
|
|
|
96674
97002
|
const [, mimeType, b64] = dataUriMatch;
|
|
96675
97003
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
96676
97004
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96677
|
-
await writeFile4(
|
|
97005
|
+
await writeFile4(join12(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
96678
97006
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96679
97007
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
96680
97008
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -96685,7 +97013,7 @@ async function startProxy(options) {
|
|
|
96685
97013
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
96686
97014
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96687
97015
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
96688
|
-
await writeFile4(
|
|
97016
|
+
await writeFile4(join12(IMAGE_DIR, filename), buf);
|
|
96689
97017
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96690
97018
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
96691
97019
|
}
|
|
@@ -96748,7 +97076,10 @@ async function startProxy(options) {
|
|
|
96748
97076
|
if (typeof val !== "string" || !val) continue;
|
|
96749
97077
|
if (val.startsWith("data:")) {
|
|
96750
97078
|
} else if (val.startsWith("https://") || val.startsWith("http://")) {
|
|
96751
|
-
const imgResp = await
|
|
97079
|
+
const imgResp = await ssrfSafeFetch(val, {
|
|
97080
|
+
signal: clientAbort.signal,
|
|
97081
|
+
allowPrivate: process.env.CLAWROUTER_ALLOW_PRIVATE_FETCH === "1"
|
|
97082
|
+
});
|
|
96752
97083
|
if (!imgResp.ok)
|
|
96753
97084
|
throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
|
|
96754
97085
|
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
@@ -96758,7 +97089,7 @@ async function startProxy(options) {
|
|
|
96758
97089
|
`[ClawRouter] img2img: downloaded ${field} URL \u2192 data URI (${buf.length} bytes)`
|
|
96759
97090
|
);
|
|
96760
97091
|
} else {
|
|
96761
|
-
parsed[field] =
|
|
97092
|
+
parsed[field] = readLocalImageAsDataUri(val);
|
|
96762
97093
|
console.log(`[ClawRouter] img2img: read ${field} file \u2192 data URI`);
|
|
96763
97094
|
}
|
|
96764
97095
|
}
|
|
@@ -96807,7 +97138,7 @@ async function startProxy(options) {
|
|
|
96807
97138
|
const [, mimeType, b64] = dataUriMatch;
|
|
96808
97139
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
96809
97140
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96810
|
-
await writeFile4(
|
|
97141
|
+
await writeFile4(join12(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
96811
97142
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96812
97143
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
96813
97144
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -96818,7 +97149,7 @@ async function startProxy(options) {
|
|
|
96818
97149
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
96819
97150
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96820
97151
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
96821
|
-
await writeFile4(
|
|
97152
|
+
await writeFile4(join12(IMAGE_DIR, filename), buf);
|
|
96822
97153
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96823
97154
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
96824
97155
|
}
|
|
@@ -96914,7 +97245,7 @@ async function startProxy(options) {
|
|
|
96914
97245
|
const ext = contentType.includes("wav") ? "wav" : "mp3";
|
|
96915
97246
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96916
97247
|
const buf = Buffer.from(await audioResp.arrayBuffer());
|
|
96917
|
-
await writeFile4(
|
|
97248
|
+
await writeFile4(join12(AUDIO_DIR, filename), buf);
|
|
96918
97249
|
track.url = `http://localhost:${port2}/audio/${filename}`;
|
|
96919
97250
|
console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
|
|
96920
97251
|
}
|
|
@@ -97081,7 +97412,7 @@ async function startProxy(options) {
|
|
|
97081
97412
|
const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
|
|
97082
97413
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
97083
97414
|
const buf = Buffer.from(await videoResp.arrayBuffer());
|
|
97084
|
-
await writeFile4(
|
|
97415
|
+
await writeFile4(join12(VIDEO_DIR, filename), buf);
|
|
97085
97416
|
clip.url = `http://localhost:${port2}/videos/${filename}`;
|
|
97086
97417
|
console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
|
|
97087
97418
|
}
|
|
@@ -97474,6 +97805,17 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
97474
97805
|
return { success: true, response };
|
|
97475
97806
|
} catch (err) {
|
|
97476
97807
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
97808
|
+
if (err instanceof CostCapError) {
|
|
97809
|
+
return {
|
|
97810
|
+
success: false,
|
|
97811
|
+
errorBody: JSON.stringify({
|
|
97812
|
+
error: { message: errorMsg, type: "cost_cap_exceeded", code: "cost_cap_exceeded" }
|
|
97813
|
+
}),
|
|
97814
|
+
errorStatus: 429,
|
|
97815
|
+
isProviderError: false,
|
|
97816
|
+
errorCategory: "payment_error"
|
|
97817
|
+
};
|
|
97818
|
+
}
|
|
97477
97819
|
if (err instanceof SpendPolicyError) {
|
|
97478
97820
|
return {
|
|
97479
97821
|
success: false,
|
|
@@ -98047,8 +98389,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98047
98389
|
let imageDataUri;
|
|
98048
98390
|
let maskDataUri;
|
|
98049
98391
|
try {
|
|
98050
|
-
imageDataUri =
|
|
98051
|
-
if (maskPath) maskDataUri =
|
|
98392
|
+
imageDataUri = readLocalImageAsDataUri(imagePath);
|
|
98393
|
+
if (maskPath) maskDataUri = readLocalImageAsDataUri(maskPath);
|
|
98052
98394
|
} catch (fileErr) {
|
|
98053
98395
|
const fileErrMsg = fileErr instanceof Error ? fileErr.message : String(fileErr);
|
|
98054
98396
|
sendImg2ImgText(`Failed to read image file: ${fileErrMsg}`);
|
|
@@ -98553,14 +98895,23 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98553
98895
|
}
|
|
98554
98896
|
}
|
|
98555
98897
|
}
|
|
98898
|
+
let sessionSpend;
|
|
98556
98899
|
if (options.maxCostPerRunUsd && effectiveSessionId && !isFreeModel && (options.maxCostPerRunMode ?? "graceful") === "strict") {
|
|
98557
|
-
const runCostUsd = sessionStore.getSessionCostUsd(effectiveSessionId);
|
|
98558
98900
|
const thisReqEstStr = estimatedCostMicros !== void 0 ? estimatedCostMicros.toString() : modelId ? estimateAmount(modelId, body.length, maxTokens) : void 0;
|
|
98559
|
-
const
|
|
98560
|
-
|
|
98561
|
-
|
|
98901
|
+
const admission = SessionSpend.admit(
|
|
98902
|
+
sessionStore,
|
|
98903
|
+
effectiveSessionId,
|
|
98904
|
+
usdToMicros(options.maxCostPerRunUsd),
|
|
98905
|
+
parseMicros(thisReqEstStr) ?? 0n
|
|
98906
|
+
);
|
|
98907
|
+
if (!admission.admitted) {
|
|
98908
|
+
const spentUsd = microsToUsd(admission.spentMicros);
|
|
98909
|
+
const heldUsd = microsToUsd(admission.heldMicros);
|
|
98910
|
+
const estUsd = microsToUsd(admission.estimateMicros);
|
|
98911
|
+
const projectedCostUsd = spentUsd + heldUsd + estUsd;
|
|
98912
|
+
const breakdown = `spent $${spentUsd.toFixed(4)}${heldUsd > 0 ? ` + in flight $${heldUsd.toFixed(4)}` : ""} + est $${estUsd.toFixed(4)}`;
|
|
98562
98913
|
console.log(
|
|
98563
|
-
`[ClawRouter] Cost cap exceeded for session ${effectiveSessionId.slice(0, 8)}...: projected $${projectedCostUsd.toFixed(4)} (
|
|
98914
|
+
`[ClawRouter] Cost cap exceeded for session ${effectiveSessionId.slice(0, 8)}...: projected $${projectedCostUsd.toFixed(4)} (${breakdown}) > $${options.maxCostPerRunUsd} limit`
|
|
98564
98915
|
);
|
|
98565
98916
|
res.writeHead(429, {
|
|
98566
98917
|
"Content-Type": "application/json",
|
|
@@ -98569,7 +98920,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98569
98920
|
res.end(
|
|
98570
98921
|
JSON.stringify({
|
|
98571
98922
|
error: {
|
|
98572
|
-
message: `ClawRouter cost cap exceeded: projected spend $${projectedCostUsd.toFixed(4)} (
|
|
98923
|
+
message: `ClawRouter cost cap exceeded: projected spend $${projectedCostUsd.toFixed(4)} (${breakdown}) would exceed limit $${options.maxCostPerRunUsd}`,
|
|
98573
98924
|
type: "cost_cap_exceeded",
|
|
98574
98925
|
code: "cost_cap_exceeded"
|
|
98575
98926
|
}
|
|
@@ -98578,7 +98929,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98578
98929
|
deduplicator.removeInflight(dedupKey);
|
|
98579
98930
|
return;
|
|
98580
98931
|
}
|
|
98932
|
+
sessionSpend = admission.spend;
|
|
98933
|
+
} else if (options.maxCostPerRunUsd && effectiveSessionId) {
|
|
98934
|
+
sessionSpend = SessionSpend.track(sessionStore, effectiveSessionId);
|
|
98581
98935
|
}
|
|
98936
|
+
const requestStore = paymentStore.getStore();
|
|
98937
|
+
if (requestStore) requestStore.sessionSpend = sessionSpend;
|
|
98582
98938
|
if (options.maxCostPerRunUsd && effectiveSessionId && !isFreeModel && (options.maxCostPerRunMode ?? "graceful") === "graceful") {
|
|
98583
98939
|
const runCostUsd = sessionStore.getSessionCostUsd(effectiveSessionId);
|
|
98584
98940
|
const remainingUsd = options.maxCostPerRunUsd - runCostUsd;
|
|
@@ -98974,11 +99330,9 @@ data: [DONE]
|
|
|
98974
99330
|
actualModelUsed = tryModel;
|
|
98975
99331
|
recordOutcome(tryModel, requestKind, true);
|
|
98976
99332
|
console.log(`[ClawRouter] Success with model: ${tryModel}`);
|
|
98977
|
-
if (
|
|
98978
|
-
const costEst = estimateAmount(tryModel, body.length, maxTokens);
|
|
98979
|
-
if (costEst)
|
|
98980
|
-
sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
|
|
98981
|
-
}
|
|
99333
|
+
if (sessionSpend && !FREE_MODELS.has(tryModel)) {
|
|
99334
|
+
const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
|
|
99335
|
+
if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
|
|
98982
99336
|
}
|
|
98983
99337
|
break;
|
|
98984
99338
|
}
|
|
@@ -99052,11 +99406,9 @@ data: [DONE]
|
|
|
99052
99406
|
upstream = retryResult.response;
|
|
99053
99407
|
actualModelUsed = tryModel;
|
|
99054
99408
|
console.log(`[ClawRouter] Explicit-pin retry succeeded for: ${tryModel}`);
|
|
99055
|
-
if (
|
|
99056
|
-
const costEst = estimateAmount(tryModel, body.length, maxTokens);
|
|
99057
|
-
if (costEst)
|
|
99058
|
-
sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
|
|
99059
|
-
}
|
|
99409
|
+
if (sessionSpend && !FREE_MODELS.has(tryModel)) {
|
|
99410
|
+
const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
|
|
99411
|
+
if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
|
|
99060
99412
|
}
|
|
99061
99413
|
break;
|
|
99062
99414
|
}
|
|
@@ -99123,11 +99475,9 @@ data: [DONE]
|
|
|
99123
99475
|
upstream = retryResult.response;
|
|
99124
99476
|
actualModelUsed = tryModel;
|
|
99125
99477
|
console.log(`[ClawRouter] Rate-limit retry succeeded for: ${tryModel}`);
|
|
99126
|
-
if (
|
|
99127
|
-
const costEst = estimateAmount(tryModel, body.length, maxTokens);
|
|
99128
|
-
if (costEst)
|
|
99129
|
-
sessionStore.addSessionCost(effectiveSessionId, BigInt(costEst));
|
|
99130
|
-
}
|
|
99478
|
+
if (sessionSpend && !FREE_MODELS.has(tryModel)) {
|
|
99479
|
+
const costEst = parseMicros(estimateAmount(tryModel, body.length, maxTokens));
|
|
99480
|
+
if (costEst !== void 0) sessionSpend.recordSuccess(costEst);
|
|
99131
99481
|
}
|
|
99132
99482
|
break;
|
|
99133
99483
|
}
|
|
@@ -99221,6 +99571,7 @@ data: [DONE]
|
|
|
99221
99571
|
);
|
|
99222
99572
|
const rawErrBody = lastError?.body || structuredMessage;
|
|
99223
99573
|
const errStatus = lastError?.status || 502;
|
|
99574
|
+
const costCapRefusal = errStatus === 429 && rawErrBody.includes('"cost_cap_exceeded"');
|
|
99224
99575
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
99225
99576
|
requestHadError = true;
|
|
99226
99577
|
if (headersSentEarly) {
|
|
@@ -99257,7 +99608,8 @@ data: [DONE]
|
|
|
99257
99608
|
"Content-Type": "application/json",
|
|
99258
99609
|
"x-context-used-kb": String(originalContextSizeKB),
|
|
99259
99610
|
"x-context-limit-kb": String(CONTEXT_LIMIT_KB),
|
|
99260
|
-
...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
|
|
99611
|
+
...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {},
|
|
99612
|
+
...costCapRefusal ? { "X-ClawRouter-Cost-Cap-Exceeded": "1" } : {}
|
|
99261
99613
|
});
|
|
99262
99614
|
res.end(transformedErr);
|
|
99263
99615
|
deduplicator.complete(dedupKey, {
|
|
@@ -99691,6 +100043,8 @@ data: [DONE]
|
|
|
99691
100043
|
throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });
|
|
99692
100044
|
}
|
|
99693
100045
|
throw err;
|
|
100046
|
+
} finally {
|
|
100047
|
+
sessionSpend?.release();
|
|
99694
100048
|
}
|
|
99695
100049
|
const logModel = routingDecision?.model ?? modelId;
|
|
99696
100050
|
if (logModel) {
|
|
@@ -99777,6 +100131,7 @@ var init_proxy = __esm({
|
|
|
99777
100131
|
init_auth();
|
|
99778
100132
|
init_api_key();
|
|
99779
100133
|
init_spend_control();
|
|
100134
|
+
init_session_spend();
|
|
99780
100135
|
init_compression();
|
|
99781
100136
|
init_errors5();
|
|
99782
100137
|
init_version4();
|
|
@@ -99786,6 +100141,7 @@ var init_proxy = __esm({
|
|
|
99786
100141
|
init_outcome_memory();
|
|
99787
100142
|
init_config();
|
|
99788
100143
|
init_journal();
|
|
100144
|
+
init_local_guard();
|
|
99789
100145
|
init_upstream_proxy();
|
|
99790
100146
|
init_textual_tool_calls();
|
|
99791
100147
|
init_response_store();
|
|
@@ -99794,10 +100150,10 @@ var init_proxy = __esm({
|
|
|
99794
100150
|
paymentStore = new AsyncLocalStorage();
|
|
99795
100151
|
BLOCKRUN_API = "https://blockrun.ai/api";
|
|
99796
100152
|
BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
99797
|
-
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ??
|
|
99798
|
-
IMAGE_DIR =
|
|
99799
|
-
AUDIO_DIR =
|
|
99800
|
-
VIDEO_DIR =
|
|
100153
|
+
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join12(homedir9(), ".clawrouter-desktop", "service-token");
|
|
100154
|
+
IMAGE_DIR = join12(homedir9(), ".openclaw", "blockrun", "images");
|
|
100155
|
+
AUDIO_DIR = join12(homedir9(), ".openclaw", "blockrun", "audio");
|
|
100156
|
+
VIDEO_DIR = join12(homedir9(), ".openclaw", "blockrun", "videos");
|
|
99801
100157
|
AUTO_MODEL = "blockrun/auto";
|
|
99802
100158
|
ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
99803
100159
|
"blockrun/eco",
|
|
@@ -101178,7 +101534,13 @@ function buildTool(service, proxyBaseUrl) {
|
|
|
101178
101534
|
if (service.proxyPath === "/pm/__dynamic__") {
|
|
101179
101535
|
const rawPath = typeof params.path === "string" ? params.path : "";
|
|
101180
101536
|
const normalized = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
|
101181
|
-
|
|
101537
|
+
let resolved;
|
|
101538
|
+
try {
|
|
101539
|
+
resolved = new URL(`/v1${normalized}`, "http://x").pathname;
|
|
101540
|
+
} catch {
|
|
101541
|
+
resolved = "";
|
|
101542
|
+
}
|
|
101543
|
+
if (!normalized.startsWith("/pm/") || normalized.includes("..") || /%2e|%2f|%5c|\\/i.test(normalized) || !resolved.startsWith("/v1/pm/")) {
|
|
101182
101544
|
throw new Error(
|
|
101183
101545
|
`predexon_endpoint_call: invalid path '${rawPath}' \u2014 must begin with '/pm/' and contain no '..'`
|
|
101184
101546
|
);
|
|
@@ -122018,8 +122380,8 @@ var init_constants3 = __esm({
|
|
|
122018
122380
|
|
|
122019
122381
|
// src/polymarket/wallet-adapter.ts
|
|
122020
122382
|
import fs2 from "fs";
|
|
122021
|
-
import { homedir as
|
|
122022
|
-
import { join as
|
|
122383
|
+
import { homedir as homedir10 } from "os";
|
|
122384
|
+
import { join as join13 } from "path";
|
|
122023
122385
|
function getOrCreateWalletKey() {
|
|
122024
122386
|
const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
|
|
122025
122387
|
if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
|
|
@@ -122067,7 +122429,7 @@ var init_wallet_adapter = __esm({
|
|
|
122067
122429
|
init_esm();
|
|
122068
122430
|
init_chains();
|
|
122069
122431
|
init_constants3();
|
|
122070
|
-
WALLET_FILE2 =
|
|
122432
|
+
WALLET_FILE2 = join13(homedir10(), ".openclaw", "blockrun", "wallet.key");
|
|
122071
122433
|
BASE_RPC_URLS = [
|
|
122072
122434
|
"https://mainnet.base.org",
|
|
122073
122435
|
"https://base.llamarpc.com",
|
|
@@ -122471,7 +122833,7 @@ var init_client5 = __esm({
|
|
|
122471
122833
|
function resolveSpendControl(deps) {
|
|
122472
122834
|
return deps?.spendControl ?? getSharedSpendControl();
|
|
122473
122835
|
}
|
|
122474
|
-
function
|
|
122836
|
+
function usdToMicros2(usd) {
|
|
122475
122837
|
return String(Math.ceil(usd * 1e6));
|
|
122476
122838
|
}
|
|
122477
122839
|
async function signUnderSpendPolicy(deps, quote, action, sign4) {
|
|
@@ -122737,7 +123099,7 @@ Re-call with confirm:true to sign and submit.`,
|
|
|
122737
123099
|
payTo: negRisk ? NEG_RISK_CTF_EXCHANGE_V2 : CTF_EXCHANGE_V2,
|
|
122738
123100
|
network: `eip155:${POLYGON_CHAIN_ID}`,
|
|
122739
123101
|
asset: PUSD_COLLATERAL,
|
|
122740
|
-
amount:
|
|
123102
|
+
amount: usdToMicros2(notional)
|
|
122741
123103
|
};
|
|
122742
123104
|
let r2;
|
|
122743
123105
|
try {
|
|
@@ -232583,7 +232945,7 @@ var init_retry = __esm({
|
|
|
232583
232945
|
// src/index.ts
|
|
232584
232946
|
import {
|
|
232585
232947
|
writeFileSync as writeFileSync3,
|
|
232586
|
-
existsSync as
|
|
232948
|
+
existsSync as existsSync2,
|
|
232587
232949
|
readdirSync,
|
|
232588
232950
|
mkdirSync as mkdirSync3,
|
|
232589
232951
|
copyFileSync,
|
|
@@ -232592,9 +232954,9 @@ import {
|
|
|
232592
232954
|
unlinkSync
|
|
232593
232955
|
} from "fs";
|
|
232594
232956
|
import { readFile as readFileAsync } from "fs/promises";
|
|
232595
|
-
import { homedir as
|
|
232957
|
+
import { homedir as homedir15 } from "os";
|
|
232596
232958
|
import { randomUUID } from "crypto";
|
|
232597
|
-
import { join as
|
|
232959
|
+
import { join as join18 } from "path";
|
|
232598
232960
|
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
232599
232961
|
const start = Date.now();
|
|
232600
232962
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -232614,12 +232976,12 @@ function writePrivateJsonSync(path5, value) {
|
|
|
232614
232976
|
renameSync2(temporary, path5);
|
|
232615
232977
|
chmodSync(path5, 384);
|
|
232616
232978
|
} finally {
|
|
232617
|
-
if (
|
|
232979
|
+
if (existsSync2(temporary)) unlinkSync(temporary);
|
|
232618
232980
|
}
|
|
232619
232981
|
}
|
|
232620
232982
|
function hasConfiguredWallet() {
|
|
232621
232983
|
return Boolean(
|
|
232622
|
-
process["env"].BLOCKRUN_WALLET_KEY ||
|
|
232984
|
+
process["env"].BLOCKRUN_WALLET_KEY || existsSync2(CORE_WALLET_FILE) || existsSync2(WALLET_FILE)
|
|
232623
232985
|
);
|
|
232624
232986
|
}
|
|
232625
232987
|
function isCompletionMode() {
|
|
@@ -232637,11 +232999,11 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
232637
232999
|
}
|
|
232638
233000
|
function legacyPackageIsBlockRun() {
|
|
232639
233001
|
for (const dir of [
|
|
232640
|
-
|
|
232641
|
-
|
|
233002
|
+
join18(homedir15(), ".openclaw", "extensions", "clawrouter"),
|
|
233003
|
+
join18(homedir15(), ".openclaw", "npm", "node_modules", "@blockrun", "clawrouter")
|
|
232642
233004
|
]) {
|
|
232643
233005
|
try {
|
|
232644
|
-
const metadata = JSON.parse(readTextFileSync(
|
|
233006
|
+
const metadata = JSON.parse(readTextFileSync(join18(dir, "package.json")));
|
|
232645
233007
|
if (metadata.name === "@blockrun/clawrouter") return true;
|
|
232646
233008
|
} catch {
|
|
232647
233009
|
}
|
|
@@ -232649,11 +233011,11 @@ function legacyPackageIsBlockRun() {
|
|
|
232649
233011
|
return false;
|
|
232650
233012
|
}
|
|
232651
233013
|
function injectModelsConfig(logger48, options = {}) {
|
|
232652
|
-
const configDir =
|
|
232653
|
-
const configPath =
|
|
233014
|
+
const configDir = join18(homedir15(), ".openclaw");
|
|
233015
|
+
const configPath = join18(configDir, "openclaw.json");
|
|
232654
233016
|
let config = {};
|
|
232655
233017
|
let needsWrite = false;
|
|
232656
|
-
if (!
|
|
233018
|
+
if (!existsSync2(configDir)) {
|
|
232657
233019
|
try {
|
|
232658
233020
|
mkdirSync3(configDir, { recursive: true });
|
|
232659
233021
|
logger48.info("Created OpenClaw config directory");
|
|
@@ -232664,7 +233026,7 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
232664
233026
|
return;
|
|
232665
233027
|
}
|
|
232666
233028
|
}
|
|
232667
|
-
if (
|
|
233029
|
+
if (existsSync2(configPath)) {
|
|
232668
233030
|
try {
|
|
232669
233031
|
const content = readTextFileSync(configPath).trim();
|
|
232670
233032
|
if (content) {
|
|
@@ -232871,8 +233233,8 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
232871
233233
|
}
|
|
232872
233234
|
function syncAgentModelCache(logger48, options = {}) {
|
|
232873
233235
|
if (!isGatewayMode() && !options.forceWrite) return;
|
|
232874
|
-
const agentsDir =
|
|
232875
|
-
if (!
|
|
233236
|
+
const agentsDir = join18(homedir15(), ".openclaw", "agents");
|
|
233237
|
+
if (!existsSync2(agentsDir)) return;
|
|
232876
233238
|
let agentDirs;
|
|
232877
233239
|
try {
|
|
232878
233240
|
agentDirs = readdirSync(agentsDir);
|
|
@@ -232881,8 +233243,8 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
232881
233243
|
}
|
|
232882
233244
|
const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
|
|
232883
233245
|
for (const agent of agentDirs) {
|
|
232884
|
-
const cachePath =
|
|
232885
|
-
if (!
|
|
233246
|
+
const cachePath = join18(agentsDir, agent, "agent", "models.json");
|
|
233247
|
+
if (!existsSync2(cachePath)) continue;
|
|
232886
233248
|
try {
|
|
232887
233249
|
const raw = readTextFileSync(cachePath).trim();
|
|
232888
233250
|
if (!raw) continue;
|
|
@@ -232910,8 +233272,8 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
232910
233272
|
}
|
|
232911
233273
|
}
|
|
232912
233274
|
function injectAuthProfile(logger48) {
|
|
232913
|
-
const agentsDir =
|
|
232914
|
-
if (!
|
|
233275
|
+
const agentsDir = join18(homedir15(), ".openclaw", "agents");
|
|
233276
|
+
if (!existsSync2(agentsDir)) {
|
|
232915
233277
|
try {
|
|
232916
233278
|
mkdirSync3(agentsDir, { recursive: true });
|
|
232917
233279
|
} catch (err) {
|
|
@@ -232927,10 +233289,10 @@ function injectAuthProfile(logger48) {
|
|
|
232927
233289
|
agents = ["main", ...agents];
|
|
232928
233290
|
}
|
|
232929
233291
|
for (const agentId of agents) {
|
|
232930
|
-
const authDir =
|
|
232931
|
-
const authPath =
|
|
232932
|
-
const sqlitePath =
|
|
232933
|
-
if (
|
|
233292
|
+
const authDir = join18(agentsDir, agentId, "agent");
|
|
233293
|
+
const authPath = join18(authDir, "auth-profiles.json");
|
|
233294
|
+
const sqlitePath = join18(authDir, "openclaw-agent.sqlite");
|
|
233295
|
+
if (existsSync2(sqlitePath)) {
|
|
232934
233296
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
232935
233297
|
continue;
|
|
232936
233298
|
}
|
|
@@ -232938,7 +233300,7 @@ function injectAuthProfile(logger48) {
|
|
|
232938
233300
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
232939
233301
|
continue;
|
|
232940
233302
|
}
|
|
232941
|
-
if (!
|
|
233303
|
+
if (!existsSync2(authDir)) {
|
|
232942
233304
|
try {
|
|
232943
233305
|
mkdirSync3(authDir, { recursive: true });
|
|
232944
233306
|
} catch {
|
|
@@ -232949,7 +233311,7 @@ function injectAuthProfile(logger48) {
|
|
|
232949
233311
|
version: 1,
|
|
232950
233312
|
profiles: {}
|
|
232951
233313
|
};
|
|
232952
|
-
if (
|
|
233314
|
+
if (existsSync2(authPath)) {
|
|
232953
233315
|
try {
|
|
232954
233316
|
const existing = JSON.parse(readTextFileSync(authPath));
|
|
232955
233317
|
if (existing.version && existing.profiles) {
|
|
@@ -232982,7 +233344,7 @@ function injectAuthProfile(logger48) {
|
|
|
232982
233344
|
}
|
|
232983
233345
|
function removeInjectedAuthPlaceholder(authPath, logger48, agentId) {
|
|
232984
233346
|
try {
|
|
232985
|
-
if (!
|
|
233347
|
+
if (!existsSync2(authPath)) return;
|
|
232986
233348
|
const parsed = JSON.parse(readTextFileSync(authPath));
|
|
232987
233349
|
const profiles = parsed?.profiles;
|
|
232988
233350
|
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) return;
|
|
@@ -233437,7 +233799,7 @@ function buildImageGenerationProvider() {
|
|
|
233437
233799
|
(result.data ?? []).map(async (img) => {
|
|
233438
233800
|
const filename = img.url?.split("/images/").pop();
|
|
233439
233801
|
if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
|
|
233440
|
-
const filePath =
|
|
233802
|
+
const filePath = join18(IMAGE_DIR2, filename);
|
|
233441
233803
|
const buffer2 = await readFileAsync(filePath);
|
|
233442
233804
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
233443
233805
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
@@ -233489,7 +233851,7 @@ function buildMusicGenerationProvider() {
|
|
|
233489
233851
|
(result.data ?? []).map(async (track) => {
|
|
233490
233852
|
const filename = track.url?.split("/audio/").pop();
|
|
233491
233853
|
if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
|
|
233492
|
-
const filePath =
|
|
233854
|
+
const filePath = join18(AUDIO_DIR2, filename);
|
|
233493
233855
|
const buffer2 = await readFileAsync(filePath);
|
|
233494
233856
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
|
|
233495
233857
|
const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
|
|
@@ -233565,7 +233927,7 @@ function buildVideoGenerationProvider() {
|
|
|
233565
233927
|
(result.data ?? []).map(async (clip) => {
|
|
233566
233928
|
const filename = clip.url?.split("/videos/").pop();
|
|
233567
233929
|
if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
|
|
233568
|
-
const filePath =
|
|
233930
|
+
const filePath = join18(VIDEO_DIR2, filename);
|
|
233569
233931
|
const buffer2 = await readFileAsync(filePath);
|
|
233570
233932
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
|
|
233571
233933
|
const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
|
|
@@ -233666,7 +234028,7 @@ function createWalletCommand(api) {
|
|
|
233666
234028
|
];
|
|
233667
234029
|
let hasMnemonic3 = false;
|
|
233668
234030
|
try {
|
|
233669
|
-
if (
|
|
234031
|
+
if (existsSync2(MNEMONIC_FILE)) {
|
|
233670
234032
|
const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
|
|
233671
234033
|
if (mnemonic) {
|
|
233672
234034
|
hasMnemonic3 = true;
|
|
@@ -233737,7 +234099,7 @@ function createWalletCommand(api) {
|
|
|
233737
234099
|
].join("\n")
|
|
233738
234100
|
};
|
|
233739
234101
|
}
|
|
233740
|
-
if (
|
|
234102
|
+
if (existsSync2(MNEMONIC_FILE)) {
|
|
233741
234103
|
const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
|
|
233742
234104
|
if (existingMnemonic) {
|
|
233743
234105
|
await savePaymentChain("solana");
|
|
@@ -233929,9 +234291,9 @@ var init_index = __esm({
|
|
|
233929
234291
|
init_partners();
|
|
233930
234292
|
activeProxyHandle = null;
|
|
233931
234293
|
pendingConfiguredStartupApi = null;
|
|
233932
|
-
IMAGE_DIR2 =
|
|
233933
|
-
AUDIO_DIR2 =
|
|
233934
|
-
VIDEO_DIR2 =
|
|
234294
|
+
IMAGE_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "images");
|
|
234295
|
+
AUDIO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "audio");
|
|
234296
|
+
VIDEO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "videos");
|
|
233935
234297
|
plugin = {
|
|
233936
234298
|
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
233937
234299
|
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
@@ -234071,7 +234433,8 @@ var init_index = __esm({
|
|
|
234071
234433
|
name: "cr-imagegen",
|
|
234072
234434
|
description: "Generate an image (BlockRun image models, paid via wallet)",
|
|
234073
234435
|
acceptsArgs: true,
|
|
234074
|
-
|
|
234436
|
+
// Paid from the wallet: only authorized senders.
|
|
234437
|
+
requireAuth: true,
|
|
234075
234438
|
handler: async (ctx) => {
|
|
234076
234439
|
const parsed = parseGenArgs(ctx.args ?? "");
|
|
234077
234440
|
if (!parsed.prompt) {
|
|
@@ -234123,7 +234486,8 @@ ${errText}`
|
|
|
234123
234486
|
name: "videogen",
|
|
234124
234487
|
description: "Generate a short video (Grok Imagine / Seedance, paid via wallet)",
|
|
234125
234488
|
acceptsArgs: true,
|
|
234126
|
-
|
|
234489
|
+
// Paid from the wallet: only authorized senders.
|
|
234490
|
+
requireAuth: true,
|
|
234127
234491
|
handler: async (ctx) => {
|
|
234128
234492
|
const parsed = parseGenArgs(ctx.args ?? "");
|
|
234129
234493
|
if (!parsed.prompt) {
|
|
@@ -234172,7 +234536,8 @@ ${errText}`
|
|
|
234172
234536
|
name: "cr-call",
|
|
234173
234537
|
description: "Place an AI voice call via BlockRun + Bland.ai (paid from wallet, $0.54/call)",
|
|
234174
234538
|
acceptsArgs: true,
|
|
234175
|
-
|
|
234539
|
+
// Paid from the wallet: only authorized senders.
|
|
234540
|
+
requireAuth: true,
|
|
234176
234541
|
handler: async (ctx) => {
|
|
234177
234542
|
const parsed = parseCallArgs(ctx.args ?? "");
|
|
234178
234543
|
if (!parsed.to || !parsed.task) {
|
|
@@ -234369,8 +234734,8 @@ ${errText}`
|
|
|
234369
234734
|
}
|
|
234370
234735
|
resetProxyStartupState();
|
|
234371
234736
|
try {
|
|
234372
|
-
const configPath =
|
|
234373
|
-
if (
|
|
234737
|
+
const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
|
|
234738
|
+
if (existsSync2(configPath)) {
|
|
234374
234739
|
const config = JSON.parse(readTextFileSync(configPath));
|
|
234375
234740
|
if (config.models?.providers?.blockrun) {
|
|
234376
234741
|
delete config.models.providers.blockrun;
|
|
@@ -234411,12 +234776,12 @@ ${errText}`
|
|
|
234411
234776
|
api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
234412
234777
|
}
|
|
234413
234778
|
try {
|
|
234414
|
-
const agentsDir =
|
|
234415
|
-
if (
|
|
234779
|
+
const agentsDir = join18(homedir15(), ".openclaw", "agents");
|
|
234780
|
+
if (existsSync2(agentsDir)) {
|
|
234416
234781
|
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
234417
234782
|
if (!entry.isDirectory()) continue;
|
|
234418
|
-
const authPath =
|
|
234419
|
-
if (!
|
|
234783
|
+
const authPath = join18(agentsDir, entry.name, "agent", "auth-profiles.json");
|
|
234784
|
+
if (!existsSync2(authPath)) continue;
|
|
234420
234785
|
try {
|
|
234421
234786
|
const store = JSON.parse(readTextFileSync(authPath));
|
|
234422
234787
|
if (store.profiles?.["blockrun:default"]) {
|