@blockrun/clawrouter 0.12.281 → 0.12.282
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/dist/cli.js +276 -122
- package/dist/index.js +241 -87
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -65931,6 +65931,156 @@ ${lines.join("\n")}`;
|
|
|
65931
65931
|
}
|
|
65932
65932
|
});
|
|
65933
65933
|
|
|
65934
|
+
// src/local-guard.ts
|
|
65935
|
+
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2, realpathSync } from "fs";
|
|
65936
|
+
import { homedir as homedir7 } from "os";
|
|
65937
|
+
import { join as join10 } from "path";
|
|
65938
|
+
function isLocalHostname(hostname) {
|
|
65939
|
+
const h = hostname.toLowerCase().replace(/\.$/, "");
|
|
65940
|
+
return h === "localhost" || h === "127.0.0.1" || h === "[::1]" || h === "::1";
|
|
65941
|
+
}
|
|
65942
|
+
function untrustedRequestReason(req) {
|
|
65943
|
+
const host = req.headers.host;
|
|
65944
|
+
if (host) {
|
|
65945
|
+
let hostname;
|
|
65946
|
+
try {
|
|
65947
|
+
hostname = new URL(`http://${host}`).hostname;
|
|
65948
|
+
} catch {
|
|
65949
|
+
return "malformed Host header";
|
|
65950
|
+
}
|
|
65951
|
+
if (!isLocalHostname(hostname)) return `non-local Host header (${hostname})`;
|
|
65952
|
+
}
|
|
65953
|
+
const origin2 = req.headers.origin;
|
|
65954
|
+
if (Array.isArray(origin2)) return "multiple Origin headers";
|
|
65955
|
+
if (origin2 !== void 0) {
|
|
65956
|
+
if (origin2 === "null") return "opaque Origin";
|
|
65957
|
+
let o3;
|
|
65958
|
+
try {
|
|
65959
|
+
o3 = new URL(origin2);
|
|
65960
|
+
} catch {
|
|
65961
|
+
return "malformed Origin header";
|
|
65962
|
+
}
|
|
65963
|
+
if (o3.protocol !== "http:" && o3.protocol !== "https:" || !isLocalHostname(o3.hostname)) {
|
|
65964
|
+
return `cross-site Origin (${origin2})`;
|
|
65965
|
+
}
|
|
65966
|
+
}
|
|
65967
|
+
const site = req.headers["sec-fetch-site"];
|
|
65968
|
+
if (site === "cross-site") return "cross-site browser request";
|
|
65969
|
+
return null;
|
|
65970
|
+
}
|
|
65971
|
+
function v4FromHex(hi, lo) {
|
|
65972
|
+
return `${hi >>> 8 & 255}.${hi & 255}.${lo >>> 8 & 255}.${lo & 255}`;
|
|
65973
|
+
}
|
|
65974
|
+
function isBlockedSsrfHost(hostname) {
|
|
65975
|
+
const h = hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "").replace(/\.$/, "");
|
|
65976
|
+
if (!h) return true;
|
|
65977
|
+
if (h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local")) return true;
|
|
65978
|
+
if (h.endsWith(".internal")) return true;
|
|
65979
|
+
if (METADATA_HOSTS.has(h)) return true;
|
|
65980
|
+
if (h.includes(":")) {
|
|
65981
|
+
if (h === "::1" || h === "::") return true;
|
|
65982
|
+
if (h.startsWith("fe80:") || h.startsWith("fc") || h.startsWith("fd")) return true;
|
|
65983
|
+
const sixToFour = h.match(/^2002:([0-9a-f]{1,4}):([0-9a-f]{1,4}):/);
|
|
65984
|
+
if (sixToFour) {
|
|
65985
|
+
return isBlockedSsrfHost(v4FromHex(parseInt(sixToFour[1], 16), parseInt(sixToFour[2], 16)));
|
|
65986
|
+
}
|
|
65987
|
+
const embedded = h.match(/^64:ff9b::(.+)$/) ?? h.match(/::ffff:(.+)$/);
|
|
65988
|
+
if (embedded) {
|
|
65989
|
+
const tail = embedded[1];
|
|
65990
|
+
if (tail.includes(".")) return isBlockedSsrfHost(tail);
|
|
65991
|
+
const seg = tail.split(":");
|
|
65992
|
+
if (seg.length === 2)
|
|
65993
|
+
return isBlockedSsrfHost(v4FromHex(parseInt(seg[0], 16), parseInt(seg[1], 16)));
|
|
65994
|
+
}
|
|
65995
|
+
return false;
|
|
65996
|
+
}
|
|
65997
|
+
const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
65998
|
+
if (m) {
|
|
65999
|
+
const a = Number(m[1]);
|
|
66000
|
+
const b = Number(m[2]);
|
|
66001
|
+
const c = Number(m[3]);
|
|
66002
|
+
if (a === 127 || a === 0 || a === 10) return true;
|
|
66003
|
+
if (a === 169 && b === 254) return true;
|
|
66004
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
66005
|
+
if (a === 192 && b === 168) return true;
|
|
66006
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
66007
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
66008
|
+
}
|
|
66009
|
+
return false;
|
|
66010
|
+
}
|
|
66011
|
+
async function ssrfSafeFetch(url2, init2 = {}, maxHops = 5) {
|
|
66012
|
+
const { allowPrivate, ...fetchInit } = init2;
|
|
66013
|
+
let current = url2;
|
|
66014
|
+
for (let hop = 0; hop <= maxHops; hop++) {
|
|
66015
|
+
const u = new URL(current);
|
|
66016
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
66017
|
+
throw new Error(`refusing to fetch non-http(s) URL (${u.protocol})`);
|
|
66018
|
+
}
|
|
66019
|
+
if (!allowPrivate && isBlockedSsrfHost(u.hostname)) {
|
|
66020
|
+
throw new Error(`refusing to fetch a private/loopback/metadata address: ${u.hostname}`);
|
|
66021
|
+
}
|
|
66022
|
+
const res = await fetch(current, { ...fetchInit, redirect: "manual" });
|
|
66023
|
+
if (res.status >= 300 && res.status < 400) {
|
|
66024
|
+
const loc = res.headers.get("location");
|
|
66025
|
+
if (!loc) return res;
|
|
66026
|
+
current = new URL(loc, current).href;
|
|
66027
|
+
continue;
|
|
66028
|
+
}
|
|
66029
|
+
return res;
|
|
66030
|
+
}
|
|
66031
|
+
throw new Error("too many redirects");
|
|
66032
|
+
}
|
|
66033
|
+
function readLocalImageAsDataUri(filePath) {
|
|
66034
|
+
const expanded = filePath.startsWith("~/") ? join10(homedir7(), filePath.slice(2)) : filePath;
|
|
66035
|
+
const refuse = () => new Error(`Not a readable PNG, JPEG or WebP image: ${filePath}`);
|
|
66036
|
+
let fd;
|
|
66037
|
+
try {
|
|
66038
|
+
fd = openSync2(realpathSync(expanded), "r");
|
|
66039
|
+
} catch {
|
|
66040
|
+
throw refuse();
|
|
66041
|
+
}
|
|
66042
|
+
try {
|
|
66043
|
+
const st = fstatSync2(fd);
|
|
66044
|
+
if (!st.isFile() || st.size === 0 || st.size > MAX_LOCAL_IMAGE_BYTES) throw refuse();
|
|
66045
|
+
const head = Buffer.alloc(12);
|
|
66046
|
+
readSync2(fd, head, 0, 12, 0);
|
|
66047
|
+
const sig = IMAGE_SIGNATURES.find((s3) => s3.test(head));
|
|
66048
|
+
if (!sig) throw refuse();
|
|
66049
|
+
const data = Buffer.alloc(st.size);
|
|
66050
|
+
let off = 0;
|
|
66051
|
+
while (off < st.size) {
|
|
66052
|
+
const n = readSync2(fd, data, off, st.size - off, off);
|
|
66053
|
+
if (n === 0) break;
|
|
66054
|
+
off += n;
|
|
66055
|
+
}
|
|
66056
|
+
return `data:${sig.mime};base64,${data.subarray(0, off).toString("base64")}`;
|
|
66057
|
+
} finally {
|
|
66058
|
+
closeSync2(fd);
|
|
66059
|
+
}
|
|
66060
|
+
}
|
|
66061
|
+
var METADATA_HOSTS, MAX_LOCAL_IMAGE_BYTES, IMAGE_SIGNATURES;
|
|
66062
|
+
var init_local_guard = __esm({
|
|
66063
|
+
"src/local-guard.ts"() {
|
|
66064
|
+
"use strict";
|
|
66065
|
+
METADATA_HOSTS = /* @__PURE__ */ new Set(["metadata", "metadata.goog", "instance-data"]);
|
|
66066
|
+
MAX_LOCAL_IMAGE_BYTES = 25 * 1024 * 1024;
|
|
66067
|
+
IMAGE_SIGNATURES = [
|
|
66068
|
+
{
|
|
66069
|
+
mime: "image/png",
|
|
66070
|
+
test: (b) => b.length >= 8 && b.readUInt32BE(0) === 2303741511 && b.readUInt32BE(4) === 218765834
|
|
66071
|
+
},
|
|
66072
|
+
{
|
|
66073
|
+
mime: "image/jpeg",
|
|
66074
|
+
test: (b) => b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255
|
|
66075
|
+
},
|
|
66076
|
+
{
|
|
66077
|
+
mime: "image/webp",
|
|
66078
|
+
test: (b) => b.length >= 12 && b.toString("ascii", 0, 4) === "RIFF" && b.toString("ascii", 8, 12) === "WEBP"
|
|
66079
|
+
}
|
|
66080
|
+
];
|
|
66081
|
+
}
|
|
66082
|
+
});
|
|
66083
|
+
|
|
65934
66084
|
// node_modules/undici/lib/core/symbols.js
|
|
65935
66085
|
var require_symbols = __commonJS({
|
|
65936
66086
|
"node_modules/undici/lib/core/symbols.js"(exports, module) {
|
|
@@ -93324,8 +93474,8 @@ var init_textual_tool_calls = __esm({
|
|
|
93324
93474
|
|
|
93325
93475
|
// src/response-store.ts
|
|
93326
93476
|
import { appendFile as appendFile2, mkdir as mkdir5, readFile, readdir as readdir2 } from "fs/promises";
|
|
93327
|
-
import { homedir as
|
|
93328
|
-
import { join as
|
|
93477
|
+
import { homedir as homedir8 } from "os";
|
|
93478
|
+
import { join as join11 } from "path";
|
|
93329
93479
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
93330
93480
|
function isEnabled() {
|
|
93331
93481
|
const v = process.env.BLOCKRUN_RESPONSE_STORE;
|
|
@@ -93338,7 +93488,7 @@ async function ensureDir2() {
|
|
|
93338
93488
|
}
|
|
93339
93489
|
function dailyFile(date = /* @__PURE__ */ new Date()) {
|
|
93340
93490
|
const iso = date.toISOString().slice(0, 10);
|
|
93341
|
-
return
|
|
93491
|
+
return join11(STORE_DIR, `responses-${iso}.jsonl`);
|
|
93342
93492
|
}
|
|
93343
93493
|
function genId() {
|
|
93344
93494
|
return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
|
|
@@ -93388,7 +93538,7 @@ async function listRecent(limit = 20, daysBack = 7) {
|
|
|
93388
93538
|
d.setUTCDate(d.getUTCDate() - i);
|
|
93389
93539
|
const iso = d.toISOString().slice(0, 10);
|
|
93390
93540
|
const name = `responses-${iso}.jsonl`;
|
|
93391
|
-
if (files.includes(name)) candidateFiles.push(
|
|
93541
|
+
if (files.includes(name)) candidateFiles.push(join11(STORE_DIR, name));
|
|
93392
93542
|
}
|
|
93393
93543
|
const all3 = [];
|
|
93394
93544
|
for (const f of candidateFiles) {
|
|
@@ -93417,7 +93567,7 @@ var STORE_DIR, dirReady2;
|
|
|
93417
93567
|
var init_response_store = __esm({
|
|
93418
93568
|
"src/response-store.ts"() {
|
|
93419
93569
|
"use strict";
|
|
93420
|
-
STORE_DIR =
|
|
93570
|
+
STORE_DIR = join11(homedir8(), ".openclaw", "blockrun", "responses");
|
|
93421
93571
|
dirReady2 = false;
|
|
93422
93572
|
}
|
|
93423
93573
|
});
|
|
@@ -95054,10 +95204,10 @@ import { createHmac } from "crypto";
|
|
|
95054
95204
|
import { createServer } from "http";
|
|
95055
95205
|
import { finished, Readable } from "stream";
|
|
95056
95206
|
import { pipeline } from "stream/promises";
|
|
95057
|
-
import { homedir as
|
|
95058
|
-
import { join as
|
|
95207
|
+
import { homedir as homedir9 } from "os";
|
|
95208
|
+
import { join as join12 } from "path";
|
|
95059
95209
|
import { mkdir as mkdir6, writeFile as writeFile4, readFile as readFile2, stat as fsStat } from "fs/promises";
|
|
95060
|
-
import { readFileSync as readFileSync2
|
|
95210
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
95061
95211
|
async function loadGatewayCatalog(apiBase, apiKey) {
|
|
95062
95212
|
try {
|
|
95063
95213
|
const controller = new AbortController();
|
|
@@ -95951,22 +96101,6 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
95951
96101
|
}).catch(() => {
|
|
95952
96102
|
});
|
|
95953
96103
|
}
|
|
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
96104
|
async function uploadDataUriToHost(dataUri) {
|
|
95971
96105
|
const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
95972
96106
|
if (!match) throw new Error("Invalid data URI format");
|
|
@@ -96243,6 +96377,13 @@ async function startProxy(options) {
|
|
|
96243
96377
|
console.error(`[ClawRouter] Request finished with error: ${err.message}`);
|
|
96244
96378
|
}
|
|
96245
96379
|
});
|
|
96380
|
+
const untrusted = untrustedRequestReason(req);
|
|
96381
|
+
if (untrusted) {
|
|
96382
|
+
console.warn(`[ClawRouter] Refused request to ${req.url}: ${untrusted}`);
|
|
96383
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
96384
|
+
res.end(JSON.stringify({ error: "forbidden", details: untrusted }));
|
|
96385
|
+
return;
|
|
96386
|
+
}
|
|
96246
96387
|
if (req.url === "/health" || req.url?.startsWith("/health?")) {
|
|
96247
96388
|
const url2 = new URL(req.url, "http://localhost");
|
|
96248
96389
|
const full = url2.searchParams.get("full") === "true";
|
|
@@ -96471,7 +96612,7 @@ async function startProxy(options) {
|
|
|
96471
96612
|
res.end("Bad request");
|
|
96472
96613
|
return;
|
|
96473
96614
|
}
|
|
96474
|
-
const filePath =
|
|
96615
|
+
const filePath = join12(IMAGE_DIR, filename);
|
|
96475
96616
|
try {
|
|
96476
96617
|
const s3 = await fsStat(filePath);
|
|
96477
96618
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96502,7 +96643,7 @@ async function startProxy(options) {
|
|
|
96502
96643
|
res.end("Bad request");
|
|
96503
96644
|
return;
|
|
96504
96645
|
}
|
|
96505
|
-
const filePath =
|
|
96646
|
+
const filePath = join12(AUDIO_DIR, filename);
|
|
96506
96647
|
try {
|
|
96507
96648
|
const s3 = await fsStat(filePath);
|
|
96508
96649
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96532,7 +96673,7 @@ async function startProxy(options) {
|
|
|
96532
96673
|
res.end("Bad request");
|
|
96533
96674
|
return;
|
|
96534
96675
|
}
|
|
96535
|
-
const filePath =
|
|
96676
|
+
const filePath = join12(VIDEO_DIR, filename);
|
|
96536
96677
|
try {
|
|
96537
96678
|
const s3 = await fsStat(filePath);
|
|
96538
96679
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -96674,7 +96815,7 @@ async function startProxy(options) {
|
|
|
96674
96815
|
const [, mimeType, b64] = dataUriMatch;
|
|
96675
96816
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
96676
96817
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96677
|
-
await writeFile4(
|
|
96818
|
+
await writeFile4(join12(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
96678
96819
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96679
96820
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
96680
96821
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -96685,7 +96826,7 @@ async function startProxy(options) {
|
|
|
96685
96826
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
96686
96827
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96687
96828
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
96688
|
-
await writeFile4(
|
|
96829
|
+
await writeFile4(join12(IMAGE_DIR, filename), buf);
|
|
96689
96830
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96690
96831
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
96691
96832
|
}
|
|
@@ -96748,7 +96889,10 @@ async function startProxy(options) {
|
|
|
96748
96889
|
if (typeof val !== "string" || !val) continue;
|
|
96749
96890
|
if (val.startsWith("data:")) {
|
|
96750
96891
|
} else if (val.startsWith("https://") || val.startsWith("http://")) {
|
|
96751
|
-
const imgResp = await
|
|
96892
|
+
const imgResp = await ssrfSafeFetch(val, {
|
|
96893
|
+
signal: clientAbort.signal,
|
|
96894
|
+
allowPrivate: process.env.CLAWROUTER_ALLOW_PRIVATE_FETCH === "1"
|
|
96895
|
+
});
|
|
96752
96896
|
if (!imgResp.ok)
|
|
96753
96897
|
throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
|
|
96754
96898
|
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
@@ -96758,7 +96902,7 @@ async function startProxy(options) {
|
|
|
96758
96902
|
`[ClawRouter] img2img: downloaded ${field} URL \u2192 data URI (${buf.length} bytes)`
|
|
96759
96903
|
);
|
|
96760
96904
|
} else {
|
|
96761
|
-
parsed[field] =
|
|
96905
|
+
parsed[field] = readLocalImageAsDataUri(val);
|
|
96762
96906
|
console.log(`[ClawRouter] img2img: read ${field} file \u2192 data URI`);
|
|
96763
96907
|
}
|
|
96764
96908
|
}
|
|
@@ -96807,7 +96951,7 @@ async function startProxy(options) {
|
|
|
96807
96951
|
const [, mimeType, b64] = dataUriMatch;
|
|
96808
96952
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
96809
96953
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96810
|
-
await writeFile4(
|
|
96954
|
+
await writeFile4(join12(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
96811
96955
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96812
96956
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
96813
96957
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -96818,7 +96962,7 @@ async function startProxy(options) {
|
|
|
96818
96962
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
96819
96963
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96820
96964
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
96821
|
-
await writeFile4(
|
|
96965
|
+
await writeFile4(join12(IMAGE_DIR, filename), buf);
|
|
96822
96966
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
96823
96967
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
96824
96968
|
}
|
|
@@ -96914,7 +97058,7 @@ async function startProxy(options) {
|
|
|
96914
97058
|
const ext = contentType.includes("wav") ? "wav" : "mp3";
|
|
96915
97059
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
96916
97060
|
const buf = Buffer.from(await audioResp.arrayBuffer());
|
|
96917
|
-
await writeFile4(
|
|
97061
|
+
await writeFile4(join12(AUDIO_DIR, filename), buf);
|
|
96918
97062
|
track.url = `http://localhost:${port2}/audio/${filename}`;
|
|
96919
97063
|
console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
|
|
96920
97064
|
}
|
|
@@ -97081,7 +97225,7 @@ async function startProxy(options) {
|
|
|
97081
97225
|
const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
|
|
97082
97226
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
97083
97227
|
const buf = Buffer.from(await videoResp.arrayBuffer());
|
|
97084
|
-
await writeFile4(
|
|
97228
|
+
await writeFile4(join12(VIDEO_DIR, filename), buf);
|
|
97085
97229
|
clip.url = `http://localhost:${port2}/videos/${filename}`;
|
|
97086
97230
|
console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
|
|
97087
97231
|
}
|
|
@@ -98047,8 +98191,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
98047
98191
|
let imageDataUri;
|
|
98048
98192
|
let maskDataUri;
|
|
98049
98193
|
try {
|
|
98050
|
-
imageDataUri =
|
|
98051
|
-
if (maskPath) maskDataUri =
|
|
98194
|
+
imageDataUri = readLocalImageAsDataUri(imagePath);
|
|
98195
|
+
if (maskPath) maskDataUri = readLocalImageAsDataUri(maskPath);
|
|
98052
98196
|
} catch (fileErr) {
|
|
98053
98197
|
const fileErrMsg = fileErr instanceof Error ? fileErr.message : String(fileErr);
|
|
98054
98198
|
sendImg2ImgText(`Failed to read image file: ${fileErrMsg}`);
|
|
@@ -99786,6 +99930,7 @@ var init_proxy = __esm({
|
|
|
99786
99930
|
init_outcome_memory();
|
|
99787
99931
|
init_config();
|
|
99788
99932
|
init_journal();
|
|
99933
|
+
init_local_guard();
|
|
99789
99934
|
init_upstream_proxy();
|
|
99790
99935
|
init_textual_tool_calls();
|
|
99791
99936
|
init_response_store();
|
|
@@ -99794,10 +99939,10 @@ var init_proxy = __esm({
|
|
|
99794
99939
|
paymentStore = new AsyncLocalStorage();
|
|
99795
99940
|
BLOCKRUN_API = "https://blockrun.ai/api";
|
|
99796
99941
|
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 =
|
|
99942
|
+
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join12(homedir9(), ".clawrouter-desktop", "service-token");
|
|
99943
|
+
IMAGE_DIR = join12(homedir9(), ".openclaw", "blockrun", "images");
|
|
99944
|
+
AUDIO_DIR = join12(homedir9(), ".openclaw", "blockrun", "audio");
|
|
99945
|
+
VIDEO_DIR = join12(homedir9(), ".openclaw", "blockrun", "videos");
|
|
99801
99946
|
AUTO_MODEL = "blockrun/auto";
|
|
99802
99947
|
ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
99803
99948
|
"blockrun/eco",
|
|
@@ -101178,7 +101323,13 @@ function buildTool(service, proxyBaseUrl) {
|
|
|
101178
101323
|
if (service.proxyPath === "/pm/__dynamic__") {
|
|
101179
101324
|
const rawPath = typeof params.path === "string" ? params.path : "";
|
|
101180
101325
|
const normalized = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
|
101181
|
-
|
|
101326
|
+
let resolved;
|
|
101327
|
+
try {
|
|
101328
|
+
resolved = new URL(`/v1${normalized}`, "http://x").pathname;
|
|
101329
|
+
} catch {
|
|
101330
|
+
resolved = "";
|
|
101331
|
+
}
|
|
101332
|
+
if (!normalized.startsWith("/pm/") || normalized.includes("..") || /%2e|%2f|%5c|\\/i.test(normalized) || !resolved.startsWith("/v1/pm/")) {
|
|
101182
101333
|
throw new Error(
|
|
101183
101334
|
`predexon_endpoint_call: invalid path '${rawPath}' \u2014 must begin with '/pm/' and contain no '..'`
|
|
101184
101335
|
);
|
|
@@ -122018,8 +122169,8 @@ var init_constants3 = __esm({
|
|
|
122018
122169
|
|
|
122019
122170
|
// src/polymarket/wallet-adapter.ts
|
|
122020
122171
|
import fs2 from "fs";
|
|
122021
|
-
import { homedir as
|
|
122022
|
-
import { join as
|
|
122172
|
+
import { homedir as homedir10 } from "os";
|
|
122173
|
+
import { join as join13 } from "path";
|
|
122023
122174
|
function getOrCreateWalletKey() {
|
|
122024
122175
|
const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
|
|
122025
122176
|
if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
|
|
@@ -122067,7 +122218,7 @@ var init_wallet_adapter = __esm({
|
|
|
122067
122218
|
init_esm();
|
|
122068
122219
|
init_chains();
|
|
122069
122220
|
init_constants3();
|
|
122070
|
-
WALLET_FILE2 =
|
|
122221
|
+
WALLET_FILE2 = join13(homedir10(), ".openclaw", "blockrun", "wallet.key");
|
|
122071
122222
|
BASE_RPC_URLS = [
|
|
122072
122223
|
"https://mainnet.base.org",
|
|
122073
122224
|
"https://base.llamarpc.com",
|
|
@@ -232583,7 +232734,7 @@ var init_retry = __esm({
|
|
|
232583
232734
|
// src/index.ts
|
|
232584
232735
|
import {
|
|
232585
232736
|
writeFileSync as writeFileSync3,
|
|
232586
|
-
existsSync as
|
|
232737
|
+
existsSync as existsSync2,
|
|
232587
232738
|
readdirSync,
|
|
232588
232739
|
mkdirSync as mkdirSync3,
|
|
232589
232740
|
copyFileSync,
|
|
@@ -232592,9 +232743,9 @@ import {
|
|
|
232592
232743
|
unlinkSync
|
|
232593
232744
|
} from "fs";
|
|
232594
232745
|
import { readFile as readFileAsync } from "fs/promises";
|
|
232595
|
-
import { homedir as
|
|
232746
|
+
import { homedir as homedir15 } from "os";
|
|
232596
232747
|
import { randomUUID } from "crypto";
|
|
232597
|
-
import { join as
|
|
232748
|
+
import { join as join18 } from "path";
|
|
232598
232749
|
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
232599
232750
|
const start = Date.now();
|
|
232600
232751
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -232614,12 +232765,12 @@ function writePrivateJsonSync(path5, value) {
|
|
|
232614
232765
|
renameSync2(temporary, path5);
|
|
232615
232766
|
chmodSync(path5, 384);
|
|
232616
232767
|
} finally {
|
|
232617
|
-
if (
|
|
232768
|
+
if (existsSync2(temporary)) unlinkSync(temporary);
|
|
232618
232769
|
}
|
|
232619
232770
|
}
|
|
232620
232771
|
function hasConfiguredWallet() {
|
|
232621
232772
|
return Boolean(
|
|
232622
|
-
process["env"].BLOCKRUN_WALLET_KEY ||
|
|
232773
|
+
process["env"].BLOCKRUN_WALLET_KEY || existsSync2(CORE_WALLET_FILE) || existsSync2(WALLET_FILE)
|
|
232623
232774
|
);
|
|
232624
232775
|
}
|
|
232625
232776
|
function isCompletionMode() {
|
|
@@ -232637,11 +232788,11 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
232637
232788
|
}
|
|
232638
232789
|
function legacyPackageIsBlockRun() {
|
|
232639
232790
|
for (const dir of [
|
|
232640
|
-
|
|
232641
|
-
|
|
232791
|
+
join18(homedir15(), ".openclaw", "extensions", "clawrouter"),
|
|
232792
|
+
join18(homedir15(), ".openclaw", "npm", "node_modules", "@blockrun", "clawrouter")
|
|
232642
232793
|
]) {
|
|
232643
232794
|
try {
|
|
232644
|
-
const metadata = JSON.parse(readTextFileSync(
|
|
232795
|
+
const metadata = JSON.parse(readTextFileSync(join18(dir, "package.json")));
|
|
232645
232796
|
if (metadata.name === "@blockrun/clawrouter") return true;
|
|
232646
232797
|
} catch {
|
|
232647
232798
|
}
|
|
@@ -232649,11 +232800,11 @@ function legacyPackageIsBlockRun() {
|
|
|
232649
232800
|
return false;
|
|
232650
232801
|
}
|
|
232651
232802
|
function injectModelsConfig(logger48, options = {}) {
|
|
232652
|
-
const configDir =
|
|
232653
|
-
const configPath =
|
|
232803
|
+
const configDir = join18(homedir15(), ".openclaw");
|
|
232804
|
+
const configPath = join18(configDir, "openclaw.json");
|
|
232654
232805
|
let config = {};
|
|
232655
232806
|
let needsWrite = false;
|
|
232656
|
-
if (!
|
|
232807
|
+
if (!existsSync2(configDir)) {
|
|
232657
232808
|
try {
|
|
232658
232809
|
mkdirSync3(configDir, { recursive: true });
|
|
232659
232810
|
logger48.info("Created OpenClaw config directory");
|
|
@@ -232664,7 +232815,7 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
232664
232815
|
return;
|
|
232665
232816
|
}
|
|
232666
232817
|
}
|
|
232667
|
-
if (
|
|
232818
|
+
if (existsSync2(configPath)) {
|
|
232668
232819
|
try {
|
|
232669
232820
|
const content = readTextFileSync(configPath).trim();
|
|
232670
232821
|
if (content) {
|
|
@@ -232871,8 +233022,8 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
232871
233022
|
}
|
|
232872
233023
|
function syncAgentModelCache(logger48, options = {}) {
|
|
232873
233024
|
if (!isGatewayMode() && !options.forceWrite) return;
|
|
232874
|
-
const agentsDir =
|
|
232875
|
-
if (!
|
|
233025
|
+
const agentsDir = join18(homedir15(), ".openclaw", "agents");
|
|
233026
|
+
if (!existsSync2(agentsDir)) return;
|
|
232876
233027
|
let agentDirs;
|
|
232877
233028
|
try {
|
|
232878
233029
|
agentDirs = readdirSync(agentsDir);
|
|
@@ -232881,8 +233032,8 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
232881
233032
|
}
|
|
232882
233033
|
const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
|
|
232883
233034
|
for (const agent of agentDirs) {
|
|
232884
|
-
const cachePath =
|
|
232885
|
-
if (!
|
|
233035
|
+
const cachePath = join18(agentsDir, agent, "agent", "models.json");
|
|
233036
|
+
if (!existsSync2(cachePath)) continue;
|
|
232886
233037
|
try {
|
|
232887
233038
|
const raw = readTextFileSync(cachePath).trim();
|
|
232888
233039
|
if (!raw) continue;
|
|
@@ -232910,8 +233061,8 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
232910
233061
|
}
|
|
232911
233062
|
}
|
|
232912
233063
|
function injectAuthProfile(logger48) {
|
|
232913
|
-
const agentsDir =
|
|
232914
|
-
if (!
|
|
233064
|
+
const agentsDir = join18(homedir15(), ".openclaw", "agents");
|
|
233065
|
+
if (!existsSync2(agentsDir)) {
|
|
232915
233066
|
try {
|
|
232916
233067
|
mkdirSync3(agentsDir, { recursive: true });
|
|
232917
233068
|
} catch (err) {
|
|
@@ -232927,10 +233078,10 @@ function injectAuthProfile(logger48) {
|
|
|
232927
233078
|
agents = ["main", ...agents];
|
|
232928
233079
|
}
|
|
232929
233080
|
for (const agentId of agents) {
|
|
232930
|
-
const authDir =
|
|
232931
|
-
const authPath =
|
|
232932
|
-
const sqlitePath =
|
|
232933
|
-
if (
|
|
233081
|
+
const authDir = join18(agentsDir, agentId, "agent");
|
|
233082
|
+
const authPath = join18(authDir, "auth-profiles.json");
|
|
233083
|
+
const sqlitePath = join18(authDir, "openclaw-agent.sqlite");
|
|
233084
|
+
if (existsSync2(sqlitePath)) {
|
|
232934
233085
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
232935
233086
|
continue;
|
|
232936
233087
|
}
|
|
@@ -232938,7 +233089,7 @@ function injectAuthProfile(logger48) {
|
|
|
232938
233089
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
232939
233090
|
continue;
|
|
232940
233091
|
}
|
|
232941
|
-
if (!
|
|
233092
|
+
if (!existsSync2(authDir)) {
|
|
232942
233093
|
try {
|
|
232943
233094
|
mkdirSync3(authDir, { recursive: true });
|
|
232944
233095
|
} catch {
|
|
@@ -232949,7 +233100,7 @@ function injectAuthProfile(logger48) {
|
|
|
232949
233100
|
version: 1,
|
|
232950
233101
|
profiles: {}
|
|
232951
233102
|
};
|
|
232952
|
-
if (
|
|
233103
|
+
if (existsSync2(authPath)) {
|
|
232953
233104
|
try {
|
|
232954
233105
|
const existing = JSON.parse(readTextFileSync(authPath));
|
|
232955
233106
|
if (existing.version && existing.profiles) {
|
|
@@ -232982,7 +233133,7 @@ function injectAuthProfile(logger48) {
|
|
|
232982
233133
|
}
|
|
232983
233134
|
function removeInjectedAuthPlaceholder(authPath, logger48, agentId) {
|
|
232984
233135
|
try {
|
|
232985
|
-
if (!
|
|
233136
|
+
if (!existsSync2(authPath)) return;
|
|
232986
233137
|
const parsed = JSON.parse(readTextFileSync(authPath));
|
|
232987
233138
|
const profiles = parsed?.profiles;
|
|
232988
233139
|
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) return;
|
|
@@ -233437,7 +233588,7 @@ function buildImageGenerationProvider() {
|
|
|
233437
233588
|
(result.data ?? []).map(async (img) => {
|
|
233438
233589
|
const filename = img.url?.split("/images/").pop();
|
|
233439
233590
|
if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
|
|
233440
|
-
const filePath =
|
|
233591
|
+
const filePath = join18(IMAGE_DIR2, filename);
|
|
233441
233592
|
const buffer2 = await readFileAsync(filePath);
|
|
233442
233593
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
233443
233594
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
@@ -233489,7 +233640,7 @@ function buildMusicGenerationProvider() {
|
|
|
233489
233640
|
(result.data ?? []).map(async (track) => {
|
|
233490
233641
|
const filename = track.url?.split("/audio/").pop();
|
|
233491
233642
|
if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
|
|
233492
|
-
const filePath =
|
|
233643
|
+
const filePath = join18(AUDIO_DIR2, filename);
|
|
233493
233644
|
const buffer2 = await readFileAsync(filePath);
|
|
233494
233645
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
|
|
233495
233646
|
const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
|
|
@@ -233565,7 +233716,7 @@ function buildVideoGenerationProvider() {
|
|
|
233565
233716
|
(result.data ?? []).map(async (clip) => {
|
|
233566
233717
|
const filename = clip.url?.split("/videos/").pop();
|
|
233567
233718
|
if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
|
|
233568
|
-
const filePath =
|
|
233719
|
+
const filePath = join18(VIDEO_DIR2, filename);
|
|
233569
233720
|
const buffer2 = await readFileAsync(filePath);
|
|
233570
233721
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
|
|
233571
233722
|
const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
|
|
@@ -233666,7 +233817,7 @@ function createWalletCommand(api) {
|
|
|
233666
233817
|
];
|
|
233667
233818
|
let hasMnemonic3 = false;
|
|
233668
233819
|
try {
|
|
233669
|
-
if (
|
|
233820
|
+
if (existsSync2(MNEMONIC_FILE)) {
|
|
233670
233821
|
const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
|
|
233671
233822
|
if (mnemonic) {
|
|
233672
233823
|
hasMnemonic3 = true;
|
|
@@ -233737,7 +233888,7 @@ function createWalletCommand(api) {
|
|
|
233737
233888
|
].join("\n")
|
|
233738
233889
|
};
|
|
233739
233890
|
}
|
|
233740
|
-
if (
|
|
233891
|
+
if (existsSync2(MNEMONIC_FILE)) {
|
|
233741
233892
|
const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
|
|
233742
233893
|
if (existingMnemonic) {
|
|
233743
233894
|
await savePaymentChain("solana");
|
|
@@ -233929,9 +234080,9 @@ var init_index = __esm({
|
|
|
233929
234080
|
init_partners();
|
|
233930
234081
|
activeProxyHandle = null;
|
|
233931
234082
|
pendingConfiguredStartupApi = null;
|
|
233932
|
-
IMAGE_DIR2 =
|
|
233933
|
-
AUDIO_DIR2 =
|
|
233934
|
-
VIDEO_DIR2 =
|
|
234083
|
+
IMAGE_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "images");
|
|
234084
|
+
AUDIO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "audio");
|
|
234085
|
+
VIDEO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "videos");
|
|
233935
234086
|
plugin = {
|
|
233936
234087
|
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
233937
234088
|
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
@@ -234071,7 +234222,8 @@ var init_index = __esm({
|
|
|
234071
234222
|
name: "cr-imagegen",
|
|
234072
234223
|
description: "Generate an image (BlockRun image models, paid via wallet)",
|
|
234073
234224
|
acceptsArgs: true,
|
|
234074
|
-
|
|
234225
|
+
// Paid from the wallet: only authorized senders.
|
|
234226
|
+
requireAuth: true,
|
|
234075
234227
|
handler: async (ctx) => {
|
|
234076
234228
|
const parsed = parseGenArgs(ctx.args ?? "");
|
|
234077
234229
|
if (!parsed.prompt) {
|
|
@@ -234123,7 +234275,8 @@ ${errText}`
|
|
|
234123
234275
|
name: "videogen",
|
|
234124
234276
|
description: "Generate a short video (Grok Imagine / Seedance, paid via wallet)",
|
|
234125
234277
|
acceptsArgs: true,
|
|
234126
|
-
|
|
234278
|
+
// Paid from the wallet: only authorized senders.
|
|
234279
|
+
requireAuth: true,
|
|
234127
234280
|
handler: async (ctx) => {
|
|
234128
234281
|
const parsed = parseGenArgs(ctx.args ?? "");
|
|
234129
234282
|
if (!parsed.prompt) {
|
|
@@ -234172,7 +234325,8 @@ ${errText}`
|
|
|
234172
234325
|
name: "cr-call",
|
|
234173
234326
|
description: "Place an AI voice call via BlockRun + Bland.ai (paid from wallet, $0.54/call)",
|
|
234174
234327
|
acceptsArgs: true,
|
|
234175
|
-
|
|
234328
|
+
// Paid from the wallet: only authorized senders.
|
|
234329
|
+
requireAuth: true,
|
|
234176
234330
|
handler: async (ctx) => {
|
|
234177
234331
|
const parsed = parseCallArgs(ctx.args ?? "");
|
|
234178
234332
|
if (!parsed.to || !parsed.task) {
|
|
@@ -234369,8 +234523,8 @@ ${errText}`
|
|
|
234369
234523
|
}
|
|
234370
234524
|
resetProxyStartupState();
|
|
234371
234525
|
try {
|
|
234372
|
-
const configPath =
|
|
234373
|
-
if (
|
|
234526
|
+
const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
|
|
234527
|
+
if (existsSync2(configPath)) {
|
|
234374
234528
|
const config = JSON.parse(readTextFileSync(configPath));
|
|
234375
234529
|
if (config.models?.providers?.blockrun) {
|
|
234376
234530
|
delete config.models.providers.blockrun;
|
|
@@ -234411,12 +234565,12 @@ ${errText}`
|
|
|
234411
234565
|
api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
234412
234566
|
}
|
|
234413
234567
|
try {
|
|
234414
|
-
const agentsDir =
|
|
234415
|
-
if (
|
|
234568
|
+
const agentsDir = join18(homedir15(), ".openclaw", "agents");
|
|
234569
|
+
if (existsSync2(agentsDir)) {
|
|
234416
234570
|
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
234417
234571
|
if (!entry.isDirectory()) continue;
|
|
234418
|
-
const authPath =
|
|
234419
|
-
if (!
|
|
234572
|
+
const authPath = join18(agentsDir, entry.name, "agent", "auth-profiles.json");
|
|
234573
|
+
if (!existsSync2(authPath)) continue;
|
|
234420
234574
|
try {
|
|
234421
234575
|
const store = JSON.parse(readTextFileSync(authPath));
|
|
234422
234576
|
if (store.profiles?.["blockrun:default"]) {
|