@blockrun/clawrouter 0.12.266 → 0.12.268
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 +147 -21
- package/dist/cli.js +670 -157
- package/dist/index.d.ts +58 -11
- package/dist/index.js +332 -90
- package/docs/configuration.md +102 -9
- package/openclaw.plugin.json +9 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -35091,6 +35091,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
35091
35091
|
const cached = !options?.skipPreAuth ? cache2.get(cacheKey2) : void 0;
|
|
35092
35092
|
const preAuthCovers = cached !== void 0 && Date.now() - cached.cachedAt < ttlMs && cached.coverMicros !== void 0 && needMicros !== void 0 && needMicros <= cached.coverMicros;
|
|
35093
35093
|
if (preAuthCovers) {
|
|
35094
|
+
let paymentInFlight = false;
|
|
35094
35095
|
try {
|
|
35095
35096
|
const payload2 = await client.createPaymentPayload(cached.paymentRequired);
|
|
35096
35097
|
const headers = httpClient.encodePaymentSignatureHeader(payload2);
|
|
@@ -35098,16 +35099,21 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
35098
35099
|
for (const [key2, value] of Object.entries(headers)) {
|
|
35099
35100
|
preAuthRequest.headers.set(key2, value);
|
|
35100
35101
|
}
|
|
35102
|
+
paymentInFlight = true;
|
|
35101
35103
|
const response2 = await baseFetch(preAuthRequest);
|
|
35102
35104
|
if (response2.status !== 402) {
|
|
35103
35105
|
return response2;
|
|
35104
35106
|
}
|
|
35107
|
+
paymentInFlight = false;
|
|
35105
35108
|
cache2.delete(cacheKey2);
|
|
35106
35109
|
} catch (err) {
|
|
35107
35110
|
cache2.delete(cacheKey2);
|
|
35108
35111
|
if (err instanceof SpendPolicyError) {
|
|
35109
35112
|
throw err;
|
|
35110
35113
|
}
|
|
35114
|
+
if (paymentInFlight) {
|
|
35115
|
+
throw err;
|
|
35116
|
+
}
|
|
35111
35117
|
}
|
|
35112
35118
|
}
|
|
35113
35119
|
const clonedRequest = request2.clone();
|
|
@@ -40553,7 +40559,7 @@ var init_errors5 = __esm({
|
|
|
40553
40559
|
});
|
|
40554
40560
|
|
|
40555
40561
|
// src/balance.ts
|
|
40556
|
-
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor;
|
|
40562
|
+
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor, ApiKeyBalanceMonitor;
|
|
40557
40563
|
var init_balance = __esm({
|
|
40558
40564
|
"src/balance.ts"() {
|
|
40559
40565
|
"use strict";
|
|
@@ -40682,6 +40688,38 @@ var init_balance = __esm({
|
|
|
40682
40688
|
};
|
|
40683
40689
|
}
|
|
40684
40690
|
};
|
|
40691
|
+
ApiKeyBalanceMonitor = class _ApiKeyBalanceMonitor {
|
|
40692
|
+
/** Large enough that every `balance >= estimatedCost` comparison passes. */
|
|
40693
|
+
static UNMETERED = BigInt(Number.MAX_SAFE_INTEGER);
|
|
40694
|
+
async checkBalance() {
|
|
40695
|
+
return {
|
|
40696
|
+
balance: _ApiKeyBalanceMonitor.UNMETERED,
|
|
40697
|
+
// Never a dollar figure: no caller may print this as if it were one.
|
|
40698
|
+
balanceUSD: "account credit",
|
|
40699
|
+
isLow: false,
|
|
40700
|
+
isEmpty: false,
|
|
40701
|
+
walletAddress: ""
|
|
40702
|
+
};
|
|
40703
|
+
}
|
|
40704
|
+
async checkSufficient() {
|
|
40705
|
+
return { sufficient: true, info: await this.checkBalance() };
|
|
40706
|
+
}
|
|
40707
|
+
/** No local cache to adjust — the server keeps the books. */
|
|
40708
|
+
deductEstimated() {
|
|
40709
|
+
}
|
|
40710
|
+
invalidate() {
|
|
40711
|
+
}
|
|
40712
|
+
async refresh() {
|
|
40713
|
+
return this.checkBalance();
|
|
40714
|
+
}
|
|
40715
|
+
formatUSDC(amountMicros) {
|
|
40716
|
+
return `$${(Number(amountMicros) / 1e6).toFixed(2)}`;
|
|
40717
|
+
}
|
|
40718
|
+
/** No wallet in this mode; callers render the account instead. */
|
|
40719
|
+
getWalletAddress() {
|
|
40720
|
+
return "";
|
|
40721
|
+
}
|
|
40722
|
+
};
|
|
40685
40723
|
}
|
|
40686
40724
|
});
|
|
40687
40725
|
|
|
@@ -59781,6 +59819,99 @@ var init_auth = __esm({
|
|
|
59781
59819
|
}
|
|
59782
59820
|
});
|
|
59783
59821
|
|
|
59822
|
+
// src/api-key.ts
|
|
59823
|
+
import { writeFile as writeFile2, mkdir as mkdir3, rm } from "fs/promises";
|
|
59824
|
+
import { join as join8 } from "path";
|
|
59825
|
+
import { homedir as homedir5 } from "os";
|
|
59826
|
+
function isValidApiKey(value) {
|
|
59827
|
+
if (typeof value !== "string") return false;
|
|
59828
|
+
return /^brk_[A-Za-z0-9_-]{8,}$/.test(value.trim());
|
|
59829
|
+
}
|
|
59830
|
+
function maskApiKey(key2) {
|
|
59831
|
+
const trimmed = key2.trim();
|
|
59832
|
+
if (trimmed.length <= 18) return `${trimmed.slice(0, 8)}\u2026`;
|
|
59833
|
+
return `${trimmed.slice(0, 14)}\u2026${trimmed.slice(-4)}`;
|
|
59834
|
+
}
|
|
59835
|
+
async function readOptional2(path5) {
|
|
59836
|
+
try {
|
|
59837
|
+
return (await readTextFile(path5)).trim() || void 0;
|
|
59838
|
+
} catch {
|
|
59839
|
+
return void 0;
|
|
59840
|
+
}
|
|
59841
|
+
}
|
|
59842
|
+
async function resolveApiKey() {
|
|
59843
|
+
const envKey = process["env"].BLOCKRUN_API_KEY?.trim();
|
|
59844
|
+
if (envKey) {
|
|
59845
|
+
if (isValidApiKey(envKey)) return { key: envKey, source: "env" };
|
|
59846
|
+
console.warn(
|
|
59847
|
+
`[ClawRouter] \u26A0 BLOCKRUN_API_KEY is set but does not look like a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
|
|
59848
|
+
);
|
|
59849
|
+
}
|
|
59850
|
+
for (const [path5, source] of [
|
|
59851
|
+
[CORE_API_KEY_FILE, "core"],
|
|
59852
|
+
[API_KEY_FILE, "saved"]
|
|
59853
|
+
]) {
|
|
59854
|
+
const stored = await readOptional2(path5);
|
|
59855
|
+
if (stored === void 0) continue;
|
|
59856
|
+
if (isValidApiKey(stored)) return { key: stored, source };
|
|
59857
|
+
console.warn(
|
|
59858
|
+
`[ClawRouter] \u26A0 ${path5} does not contain a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
|
|
59859
|
+
);
|
|
59860
|
+
}
|
|
59861
|
+
return void 0;
|
|
59862
|
+
}
|
|
59863
|
+
function createApiKeyFetch(apiKey, baseFetch = fetch) {
|
|
59864
|
+
return async (input, init2) => {
|
|
59865
|
+
const headers = new Headers(init2?.headers);
|
|
59866
|
+
headers.set("authorization", `Bearer ${apiKey}`);
|
|
59867
|
+
headers.delete("x-payment");
|
|
59868
|
+
headers.delete("x-api-key");
|
|
59869
|
+
const response = await baseFetch(input, { ...init2, headers });
|
|
59870
|
+
return response.ok ? response : explainApiKeyFailure(response);
|
|
59871
|
+
};
|
|
59872
|
+
}
|
|
59873
|
+
async function explainApiKeyFailure(response) {
|
|
59874
|
+
const hint = HINTS[response.status];
|
|
59875
|
+
if (!hint) return response;
|
|
59876
|
+
if (!(response.headers.get("content-type") ?? "").includes("json")) return response;
|
|
59877
|
+
let parsed;
|
|
59878
|
+
try {
|
|
59879
|
+
parsed = await response.clone().json();
|
|
59880
|
+
} catch {
|
|
59881
|
+
return response;
|
|
59882
|
+
}
|
|
59883
|
+
if (!parsed?.error || typeof parsed.error.message !== "string") return response;
|
|
59884
|
+
if (response.status === 404 && !/unsupported endpoint/i.test(parsed.error.message)) {
|
|
59885
|
+
return response;
|
|
59886
|
+
}
|
|
59887
|
+
parsed.error.message = `${parsed.error.message} ${hint}`;
|
|
59888
|
+
const headers = new Headers(response.headers);
|
|
59889
|
+
headers.delete("content-length");
|
|
59890
|
+
return new Response(JSON.stringify(parsed), {
|
|
59891
|
+
status: response.status,
|
|
59892
|
+
statusText: response.statusText,
|
|
59893
|
+
headers
|
|
59894
|
+
});
|
|
59895
|
+
}
|
|
59896
|
+
var API_KEY_FILE, CORE_API_KEY_FILE, PORTAL_URL, PORTAL_KEYS_URL, PORTAL_CREDITS_URL, BLOCKRUN_API_KEY_API, HINTS;
|
|
59897
|
+
var init_api_key = __esm({
|
|
59898
|
+
"src/api-key.ts"() {
|
|
59899
|
+
"use strict";
|
|
59900
|
+
init_fs_read();
|
|
59901
|
+
API_KEY_FILE = join8(homedir5(), ".openclaw", "blockrun", "api-key");
|
|
59902
|
+
CORE_API_KEY_FILE = join8(homedir5(), ".blockrun", ".api-key");
|
|
59903
|
+
PORTAL_URL = "https://user.blockrun.ai";
|
|
59904
|
+
PORTAL_KEYS_URL = `${PORTAL_URL}/dashboard/keys`;
|
|
59905
|
+
PORTAL_CREDITS_URL = `${PORTAL_URL}/dashboard/credits`;
|
|
59906
|
+
BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "") || "https://api.blockrun.ai";
|
|
59907
|
+
HINTS = {
|
|
59908
|
+
401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
|
|
59909
|
+
402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
|
|
59910
|
+
404: `api.blockrun.ai does not serve this endpoint yet \u2014 it currently carries chat and text completions. For media and partner APIs, run ClawRouter in wallet mode (unset BLOCKRUN_API_KEY and run "clawrouter logout").`
|
|
59911
|
+
};
|
|
59912
|
+
}
|
|
59913
|
+
});
|
|
59914
|
+
|
|
59784
59915
|
// src/compression/types.ts
|
|
59785
59916
|
var DEFAULT_COMPRESSION_CONFIG;
|
|
59786
59917
|
var init_types2 = __esm({
|
|
@@ -60870,8 +61001,8 @@ var init_updater = __esm({
|
|
|
60870
61001
|
|
|
60871
61002
|
// src/exclude-models.ts
|
|
60872
61003
|
import { readFileSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
60873
|
-
import { join as
|
|
60874
|
-
import { homedir as
|
|
61004
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
61005
|
+
import { homedir as homedir6 } from "os";
|
|
60875
61006
|
function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
|
|
60876
61007
|
try {
|
|
60877
61008
|
const mtimeMs = statSync(filePath).mtimeMs;
|
|
@@ -60920,7 +61051,7 @@ var init_exclude_models = __esm({
|
|
|
60920
61051
|
"src/exclude-models.ts"() {
|
|
60921
61052
|
"use strict";
|
|
60922
61053
|
init_models();
|
|
60923
|
-
DEFAULT_FILE_PATH =
|
|
61054
|
+
DEFAULT_FILE_PATH = join9(homedir6(), ".openclaw", "blockrun", "exclude-models.json");
|
|
60924
61055
|
loadCache = /* @__PURE__ */ new Map();
|
|
60925
61056
|
}
|
|
60926
61057
|
});
|
|
@@ -75929,7 +76060,7 @@ var require_snapshot_utils = __commonJS({
|
|
|
75929
76060
|
var require_snapshot_recorder = __commonJS({
|
|
75930
76061
|
"node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
75931
76062
|
"use strict";
|
|
75932
|
-
var { writeFile:
|
|
76063
|
+
var { writeFile: writeFile4, readFile: readFile3, mkdir: mkdir6 } = __require("fs/promises");
|
|
75933
76064
|
var { dirname: dirname4, resolve } = __require("path");
|
|
75934
76065
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
|
|
75935
76066
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
@@ -76176,12 +76307,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
76176
76307
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
76177
76308
|
}
|
|
76178
76309
|
const resolvedPath = resolve(path5);
|
|
76179
|
-
await
|
|
76310
|
+
await mkdir6(dirname4(resolvedPath), { recursive: true });
|
|
76180
76311
|
const data = Array.from(this.#snapshots.entries()).map(([hash5, snapshot]) => ({
|
|
76181
76312
|
hash: hash5,
|
|
76182
76313
|
snapshot
|
|
76183
76314
|
}));
|
|
76184
|
-
await
|
|
76315
|
+
await writeFile4(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
76185
76316
|
}
|
|
76186
76317
|
/**
|
|
76187
76318
|
* Clears all recorded snapshots
|
|
@@ -88510,9 +88641,9 @@ var init_textual_tool_calls = __esm({
|
|
|
88510
88641
|
});
|
|
88511
88642
|
|
|
88512
88643
|
// src/response-store.ts
|
|
88513
|
-
import { appendFile as appendFile2, mkdir as
|
|
88514
|
-
import { homedir as
|
|
88515
|
-
import { join as
|
|
88644
|
+
import { appendFile as appendFile2, mkdir as mkdir4, readFile, readdir as readdir2 } from "fs/promises";
|
|
88645
|
+
import { homedir as homedir7 } from "os";
|
|
88646
|
+
import { join as join10 } from "path";
|
|
88516
88647
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
88517
88648
|
function isEnabled() {
|
|
88518
88649
|
const v = process.env.BLOCKRUN_RESPONSE_STORE;
|
|
@@ -88520,12 +88651,12 @@ function isEnabled() {
|
|
|
88520
88651
|
}
|
|
88521
88652
|
async function ensureDir2() {
|
|
88522
88653
|
if (dirReady2) return;
|
|
88523
|
-
await
|
|
88654
|
+
await mkdir4(STORE_DIR, { recursive: true });
|
|
88524
88655
|
dirReady2 = true;
|
|
88525
88656
|
}
|
|
88526
88657
|
function dailyFile(date = /* @__PURE__ */ new Date()) {
|
|
88527
88658
|
const iso = date.toISOString().slice(0, 10);
|
|
88528
|
-
return
|
|
88659
|
+
return join10(STORE_DIR, `responses-${iso}.jsonl`);
|
|
88529
88660
|
}
|
|
88530
88661
|
function genId() {
|
|
88531
88662
|
return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
|
|
@@ -88575,7 +88706,7 @@ async function listRecent(limit = 20, daysBack = 7) {
|
|
|
88575
88706
|
d.setUTCDate(d.getUTCDate() - i);
|
|
88576
88707
|
const iso = d.toISOString().slice(0, 10);
|
|
88577
88708
|
const name = `responses-${iso}.jsonl`;
|
|
88578
|
-
if (files.includes(name)) candidateFiles.push(
|
|
88709
|
+
if (files.includes(name)) candidateFiles.push(join10(STORE_DIR, name));
|
|
88579
88710
|
}
|
|
88580
88711
|
const all3 = [];
|
|
88581
88712
|
for (const f of candidateFiles) {
|
|
@@ -88604,7 +88735,7 @@ var STORE_DIR, dirReady2;
|
|
|
88604
88735
|
var init_response_store = __esm({
|
|
88605
88736
|
"src/response-store.ts"() {
|
|
88606
88737
|
"use strict";
|
|
88607
|
-
STORE_DIR =
|
|
88738
|
+
STORE_DIR = join10(homedir7(), ".openclaw", "blockrun", "responses");
|
|
88608
88739
|
dirReady2 = false;
|
|
88609
88740
|
}
|
|
88610
88741
|
});
|
|
@@ -90255,15 +90386,18 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
90255
90386
|
import { createHmac } from "crypto";
|
|
90256
90387
|
import { createServer } from "http";
|
|
90257
90388
|
import { finished } from "stream";
|
|
90258
|
-
import { homedir as
|
|
90259
|
-
import { join as
|
|
90260
|
-
import { mkdir as
|
|
90389
|
+
import { homedir as homedir8 } from "os";
|
|
90390
|
+
import { join as join11 } from "path";
|
|
90391
|
+
import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile2, stat as fsStat } from "fs/promises";
|
|
90261
90392
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
90262
|
-
async function loadGatewayCatalog(apiBase) {
|
|
90393
|
+
async function loadGatewayCatalog(apiBase, apiKey) {
|
|
90263
90394
|
try {
|
|
90264
90395
|
const controller = new AbortController();
|
|
90265
90396
|
const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
|
|
90266
|
-
const res = await fetch(`${apiBase}/v1/models`, {
|
|
90397
|
+
const res = await fetch(`${apiBase}/v1/models`, {
|
|
90398
|
+
signal: controller.signal,
|
|
90399
|
+
...apiKey ? { headers: { authorization: `Bearer ${apiKey}` } } : {}
|
|
90400
|
+
});
|
|
90267
90401
|
clearTimeout(timer2);
|
|
90268
90402
|
if (!res.ok) return;
|
|
90269
90403
|
const body = await res.json();
|
|
@@ -90562,8 +90696,9 @@ async function checkExistingProxy(port) {
|
|
|
90562
90696
|
clearTimeout(timeoutId);
|
|
90563
90697
|
if (response.ok) {
|
|
90564
90698
|
const data = await response.json();
|
|
90565
|
-
|
|
90566
|
-
|
|
90699
|
+
const authMode = data.authMode === "api-key" ? "api-key" : "wallet";
|
|
90700
|
+
if (data.status === "ok" && (data.wallet || authMode === "api-key")) {
|
|
90701
|
+
return { wallet: data.wallet ?? "", paymentChain: data.paymentChain, authMode };
|
|
90567
90702
|
}
|
|
90568
90703
|
}
|
|
90569
90704
|
return void 0;
|
|
@@ -91071,7 +91206,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91071
91206
|
});
|
|
91072
91207
|
}
|
|
91073
91208
|
function readImageFileAsDataUri(filePath) {
|
|
91074
|
-
const resolved = filePath.startsWith("~/") ?
|
|
91209
|
+
const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
|
|
91075
91210
|
if (!existsSync2(resolved)) {
|
|
91076
91211
|
throw new Error(`Image file not found: ${resolved}`);
|
|
91077
91212
|
}
|
|
@@ -91114,16 +91249,48 @@ async function uploadDataUriToHost(dataUri) {
|
|
|
91114
91249
|
clearTimeout(uploadTimeout);
|
|
91115
91250
|
}
|
|
91116
91251
|
}
|
|
91252
|
+
function warnIfSpendLimitsUnenforced(spendControl) {
|
|
91253
|
+
let configured;
|
|
91254
|
+
try {
|
|
91255
|
+
const limits = (spendControl ?? new SpendControl()).getStatus().limits;
|
|
91256
|
+
configured = ["perRequest", "hourly", "daily", "session"].filter((window2) => typeof limits[window2] === "number").map((window2) => `${window2}=$${limits[window2].toFixed(2)}`);
|
|
91257
|
+
} catch {
|
|
91258
|
+
return;
|
|
91259
|
+
}
|
|
91260
|
+
if (configured.length === 0) return;
|
|
91261
|
+
console.warn(
|
|
91262
|
+
`[ClawRouter] \u26A0 Spend limits (${configured.join(", ")}) are NOT enforced in API-key mode \u2014 they gate x402 signing, and nothing is signed here.`
|
|
91263
|
+
);
|
|
91264
|
+
console.warn(
|
|
91265
|
+
`[ClawRouter] Your BlockRun account balance is the cap instead. Use maxCostPerRun for a per-session limit that still applies.`
|
|
91266
|
+
);
|
|
91267
|
+
}
|
|
91117
91268
|
async function startProxy(options) {
|
|
91118
91269
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91119
91270
|
if (upstreamProxy) {
|
|
91120
91271
|
console.log(`[ClawRouter] Upstream proxy: ${upstreamProxy}`);
|
|
91121
91272
|
}
|
|
91122
|
-
const
|
|
91123
|
-
|
|
91273
|
+
const apiKey = options.apiKey?.trim() || void 0;
|
|
91274
|
+
if (apiKey && !isValidApiKey(apiKey)) {
|
|
91275
|
+
throw new Error(
|
|
91276
|
+
`BlockRun API key is malformed (expected it to start with "brk_"). Mint one at https://user.blockrun.ai/dashboard/keys`
|
|
91277
|
+
);
|
|
91278
|
+
}
|
|
91279
|
+
const authMode = apiKey ? "api-key" : "wallet";
|
|
91280
|
+
if (!apiKey && !options.wallet) {
|
|
91281
|
+
throw new Error(
|
|
91282
|
+
`startProxy needs a credential: pass either a wallet (x402) or an apiKey (brk_\u2026, from https://user.blockrun.ai/dashboard/keys).`
|
|
91283
|
+
);
|
|
91284
|
+
}
|
|
91285
|
+
const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
|
|
91286
|
+
const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
|
|
91124
91287
|
const paymentChain = options.paymentChain ?? await resolvePaymentChain();
|
|
91125
|
-
const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91126
|
-
if (
|
|
91288
|
+
const apiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91289
|
+
if (authMode === "api-key") {
|
|
91290
|
+
console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
|
|
91291
|
+
console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
91292
|
+
warnIfSpendLimitsUnenforced(options.spendControl);
|
|
91293
|
+
} else if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
|
|
91127
91294
|
console.warn(
|
|
91128
91295
|
`[ClawRouter] \u26A0 Payment chain is Solana but no mnemonic found \u2014 falling back to Base (EVM).`
|
|
91129
91296
|
);
|
|
@@ -91134,12 +91301,31 @@ async function startProxy(options) {
|
|
|
91134
91301
|
} else if (paymentChain === "solana") {
|
|
91135
91302
|
console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
|
|
91136
91303
|
}
|
|
91137
|
-
void loadGatewayCatalog(apiBase);
|
|
91304
|
+
void loadGatewayCatalog(apiBase, apiKey);
|
|
91138
91305
|
const listenPort = options.port ?? getProxyPort();
|
|
91139
91306
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91140
91307
|
if (existingProxy) {
|
|
91141
|
-
const account2 = privateKeyToAccount(walletKey);
|
|
91142
91308
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
91309
|
+
if (existingProxy.authMode !== authMode) {
|
|
91310
|
+
throw new Error(
|
|
91311
|
+
`Existing proxy on port ${listenPort} is authenticating with ${existingProxy.authMode === "api-key" ? "a BlockRun API key" : "a wallet"} but ${authMode === "api-key" ? "an API key" : "a wallet"} was requested. Stop the existing proxy first or use a different port.`
|
|
91312
|
+
);
|
|
91313
|
+
}
|
|
91314
|
+
if (authMode === "api-key") {
|
|
91315
|
+
options.onReady?.(listenPort);
|
|
91316
|
+
return {
|
|
91317
|
+
port: listenPort,
|
|
91318
|
+
baseUrl: baseUrl2,
|
|
91319
|
+
walletAddress: "",
|
|
91320
|
+
authMode,
|
|
91321
|
+
apiKeyLabel: maskApiKey(apiKey),
|
|
91322
|
+
balanceMonitor: new ApiKeyBalanceMonitor(),
|
|
91323
|
+
// No-op: we didn't start this proxy, so we shouldn't close it
|
|
91324
|
+
close: async () => {
|
|
91325
|
+
}
|
|
91326
|
+
};
|
|
91327
|
+
}
|
|
91328
|
+
const account2 = privateKeyToAccount(walletKey);
|
|
91143
91329
|
if (existingProxy.wallet !== account2.address) {
|
|
91144
91330
|
console.warn(
|
|
91145
91331
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
@@ -91178,20 +91364,23 @@ async function startProxy(options) {
|
|
|
91178
91364
|
baseUrl: baseUrl2,
|
|
91179
91365
|
walletAddress: existingProxy.wallet,
|
|
91180
91366
|
solanaAddress: reuseSolanaAddress,
|
|
91367
|
+
authMode,
|
|
91181
91368
|
balanceMonitor: balanceMonitor2,
|
|
91182
91369
|
close: async () => {
|
|
91183
91370
|
}
|
|
91184
91371
|
};
|
|
91185
91372
|
}
|
|
91186
|
-
const account = privateKeyToAccount(walletKey);
|
|
91187
|
-
const
|
|
91188
|
-
|
|
91189
|
-
|
|
91190
|
-
|
|
91191
|
-
|
|
91192
|
-
|
|
91373
|
+
const account = walletKey ? privateKeyToAccount(walletKey) : void 0;
|
|
91374
|
+
const x402 = authMode === "wallet" ? new x402Client() : void 0;
|
|
91375
|
+
if (x402 && account) {
|
|
91376
|
+
const evmPublicClient = createPublicClient({ chain: base, transport: http() });
|
|
91377
|
+
const evmSigner = toClientEvmSigner(account, evmPublicClient);
|
|
91378
|
+
const spendControl = options.spendControl ?? new SpendControl();
|
|
91379
|
+
registerSpendPolicyHook(x402, spendControl);
|
|
91380
|
+
registerExactEvmScheme(x402, { signer: evmSigner });
|
|
91381
|
+
}
|
|
91193
91382
|
let solanaAddress;
|
|
91194
|
-
if (solanaPrivateKeyBytes) {
|
|
91383
|
+
if (x402 && solanaPrivateKeyBytes) {
|
|
91195
91384
|
const { registerExactSvmScheme: registerExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91196
91385
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91197
91386
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
@@ -91199,12 +91388,12 @@ async function startProxy(options) {
|
|
|
91199
91388
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91200
91389
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91201
91390
|
}
|
|
91202
|
-
x402
|
|
91391
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91203
91392
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
91204
91393
|
const payload = context.paymentPayload;
|
|
91205
91394
|
payload.extensions = withBuilderCodeServiceCode(payload.extensions);
|
|
91206
91395
|
});
|
|
91207
|
-
x402
|
|
91396
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91208
91397
|
const network = context.selectedRequirements.network;
|
|
91209
91398
|
const chain3 = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
|
|
91210
91399
|
const amountMicros = parseInt(context.selectedRequirements.amount || "0", 10);
|
|
@@ -91213,7 +91402,7 @@ async function startProxy(options) {
|
|
|
91213
91402
|
if (store) store.amountUsd = amountUsd;
|
|
91214
91403
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91215
91404
|
});
|
|
91216
|
-
const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91405
|
+
const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91217
91406
|
skipPreAuth: paymentChain === "solana",
|
|
91218
91407
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91219
91408
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -91223,6 +91412,8 @@ async function startProxy(options) {
|
|
|
91223
91412
|
let balanceMonitor;
|
|
91224
91413
|
if (options._balanceMonitorOverride) {
|
|
91225
91414
|
balanceMonitor = options._balanceMonitorOverride;
|
|
91415
|
+
} else if (authMode === "api-key") {
|
|
91416
|
+
balanceMonitor = new ApiKeyBalanceMonitor();
|
|
91226
91417
|
} else if (paymentChain === "solana" && solanaAddress) {
|
|
91227
91418
|
const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
|
|
91228
91419
|
balanceMonitor = new SolanaBalanceMonitor2(solanaAddress);
|
|
@@ -91264,11 +91455,17 @@ async function startProxy(options) {
|
|
|
91264
91455
|
const full = url2.searchParams.get("full") === "true";
|
|
91265
91456
|
const response = {
|
|
91266
91457
|
status: "ok",
|
|
91267
|
-
|
|
91268
|
-
paymentChain
|
|
91458
|
+
authMode
|
|
91269
91459
|
};
|
|
91270
|
-
if (
|
|
91271
|
-
response.
|
|
91460
|
+
if (authMode === "api-key") {
|
|
91461
|
+
response.apiKey = maskApiKey(apiKey);
|
|
91462
|
+
response.gateway = apiBase;
|
|
91463
|
+
} else {
|
|
91464
|
+
response.wallet = account.address;
|
|
91465
|
+
response.paymentChain = paymentChain;
|
|
91466
|
+
if (solanaAddress) {
|
|
91467
|
+
response.solana = solanaAddress;
|
|
91468
|
+
}
|
|
91272
91469
|
}
|
|
91273
91470
|
if (upstreamProxy) {
|
|
91274
91471
|
response.upstreamProxy = upstreamProxy;
|
|
@@ -91284,9 +91481,15 @@ async function startProxy(options) {
|
|
|
91284
91481
|
).unref()
|
|
91285
91482
|
)
|
|
91286
91483
|
]);
|
|
91287
|
-
|
|
91288
|
-
|
|
91289
|
-
|
|
91484
|
+
if (authMode === "api-key") {
|
|
91485
|
+
response.balance = null;
|
|
91486
|
+
response.billing = "BlockRun account credit";
|
|
91487
|
+
response.topUpUrl = PORTAL_CREDITS_URL;
|
|
91488
|
+
} else {
|
|
91489
|
+
response.balance = balanceInfo.balanceUSD;
|
|
91490
|
+
response.isLow = balanceInfo.isLow;
|
|
91491
|
+
response.isEmpty = balanceInfo.isEmpty;
|
|
91492
|
+
}
|
|
91290
91493
|
} catch {
|
|
91291
91494
|
response.balanceError = "Could not fetch balance";
|
|
91292
91495
|
}
|
|
@@ -91466,7 +91669,7 @@ async function startProxy(options) {
|
|
|
91466
91669
|
res.end("Bad request");
|
|
91467
91670
|
return;
|
|
91468
91671
|
}
|
|
91469
|
-
const filePath =
|
|
91672
|
+
const filePath = join11(IMAGE_DIR, filename);
|
|
91470
91673
|
try {
|
|
91471
91674
|
const s3 = await fsStat(filePath);
|
|
91472
91675
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91497,7 +91700,7 @@ async function startProxy(options) {
|
|
|
91497
91700
|
res.end("Bad request");
|
|
91498
91701
|
return;
|
|
91499
91702
|
}
|
|
91500
|
-
const filePath =
|
|
91703
|
+
const filePath = join11(AUDIO_DIR, filename);
|
|
91501
91704
|
try {
|
|
91502
91705
|
const s3 = await fsStat(filePath);
|
|
91503
91706
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91527,7 +91730,7 @@ async function startProxy(options) {
|
|
|
91527
91730
|
res.end("Bad request");
|
|
91528
91731
|
return;
|
|
91529
91732
|
}
|
|
91530
|
-
const filePath =
|
|
91733
|
+
const filePath = join11(VIDEO_DIR, filename);
|
|
91531
91734
|
try {
|
|
91532
91735
|
const s3 = await fsStat(filePath);
|
|
91533
91736
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91660,7 +91863,7 @@ async function startProxy(options) {
|
|
|
91660
91863
|
}
|
|
91661
91864
|
}
|
|
91662
91865
|
if (result.data?.length) {
|
|
91663
|
-
await
|
|
91866
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91664
91867
|
const port2 = server.address()?.port ?? 8402;
|
|
91665
91868
|
for (const img of result.data) {
|
|
91666
91869
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91668,7 +91871,7 @@ async function startProxy(options) {
|
|
|
91668
91871
|
const [, mimeType, b64] = dataUriMatch;
|
|
91669
91872
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91670
91873
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91671
|
-
await
|
|
91874
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91672
91875
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91673
91876
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91674
91877
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -91679,7 +91882,7 @@ async function startProxy(options) {
|
|
|
91679
91882
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
91680
91883
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91681
91884
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
91682
|
-
await
|
|
91885
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
91683
91886
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91684
91887
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
91685
91888
|
}
|
|
@@ -91783,7 +91986,7 @@ async function startProxy(options) {
|
|
|
91783
91986
|
return;
|
|
91784
91987
|
}
|
|
91785
91988
|
if (result.data?.length) {
|
|
91786
|
-
await
|
|
91989
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91787
91990
|
const port2 = server.address()?.port ?? 8402;
|
|
91788
91991
|
for (const img of result.data) {
|
|
91789
91992
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91791,7 +91994,7 @@ async function startProxy(options) {
|
|
|
91791
91994
|
const [, mimeType, b64] = dataUriMatch;
|
|
91792
91995
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91793
91996
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91794
|
-
await
|
|
91997
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91795
91998
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91796
91999
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91797
92000
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -91802,7 +92005,7 @@ async function startProxy(options) {
|
|
|
91802
92005
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
91803
92006
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91804
92007
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
91805
|
-
await
|
|
92008
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
91806
92009
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91807
92010
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
91808
92011
|
}
|
|
@@ -91877,7 +92080,7 @@ async function startProxy(options) {
|
|
|
91877
92080
|
return;
|
|
91878
92081
|
}
|
|
91879
92082
|
if (result.data?.length) {
|
|
91880
|
-
await
|
|
92083
|
+
await mkdir5(AUDIO_DIR, { recursive: true });
|
|
91881
92084
|
const port2 = server.address()?.port ?? 8402;
|
|
91882
92085
|
for (const track of result.data) {
|
|
91883
92086
|
if (track.url?.startsWith("https://") || track.url?.startsWith("http://")) {
|
|
@@ -91888,7 +92091,7 @@ async function startProxy(options) {
|
|
|
91888
92091
|
const ext = contentType.includes("wav") ? "wav" : "mp3";
|
|
91889
92092
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91890
92093
|
const buf = Buffer.from(await audioResp.arrayBuffer());
|
|
91891
|
-
await
|
|
92094
|
+
await writeFile3(join11(AUDIO_DIR, filename), buf);
|
|
91892
92095
|
track.url = `http://localhost:${port2}/audio/${filename}`;
|
|
91893
92096
|
console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
|
|
91894
92097
|
}
|
|
@@ -92037,7 +92240,7 @@ async function startProxy(options) {
|
|
|
92037
92240
|
}
|
|
92038
92241
|
}
|
|
92039
92242
|
if (finalResult.data?.length) {
|
|
92040
|
-
await
|
|
92243
|
+
await mkdir5(VIDEO_DIR, { recursive: true });
|
|
92041
92244
|
const port2 = server.address()?.port ?? 8402;
|
|
92042
92245
|
for (const clip of finalResult.data) {
|
|
92043
92246
|
if (clip.url?.startsWith("https://") || clip.url?.startsWith("http://")) {
|
|
@@ -92048,7 +92251,7 @@ async function startProxy(options) {
|
|
|
92048
92251
|
const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
|
|
92049
92252
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92050
92253
|
const buf = Buffer.from(await videoResp.arrayBuffer());
|
|
92051
|
-
await
|
|
92254
|
+
await writeFile3(join11(VIDEO_DIR, filename), buf);
|
|
92052
92255
|
clip.url = `http://localhost:${port2}/videos/${filename}`;
|
|
92053
92256
|
console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
|
|
92054
92257
|
}
|
|
@@ -92231,6 +92434,8 @@ async function startProxy(options) {
|
|
|
92231
92434
|
port: listenPort,
|
|
92232
92435
|
baseUrl: baseUrl2,
|
|
92233
92436
|
walletAddress: error.wallet,
|
|
92437
|
+
authMode,
|
|
92438
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92234
92439
|
balanceMonitor,
|
|
92235
92440
|
close: async () => {
|
|
92236
92441
|
}
|
|
@@ -92281,8 +92486,10 @@ async function startProxy(options) {
|
|
|
92281
92486
|
return {
|
|
92282
92487
|
port,
|
|
92283
92488
|
baseUrl,
|
|
92284
|
-
walletAddress: account
|
|
92489
|
+
walletAddress: account?.address ?? "",
|
|
92285
92490
|
solanaAddress,
|
|
92491
|
+
authMode,
|
|
92492
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92286
92493
|
balanceMonitor,
|
|
92287
92494
|
close: () => new Promise((res, rej) => {
|
|
92288
92495
|
const timeout = setTimeout(() => {
|
|
@@ -94605,6 +94812,7 @@ var init_proxy = __esm({
|
|
|
94605
94812
|
init_response_cache();
|
|
94606
94813
|
init_balance();
|
|
94607
94814
|
init_auth();
|
|
94815
|
+
init_api_key();
|
|
94608
94816
|
init_spend_control();
|
|
94609
94817
|
init_compression();
|
|
94610
94818
|
init_version4();
|
|
@@ -94621,10 +94829,10 @@ var init_proxy = __esm({
|
|
|
94621
94829
|
paymentStore = new AsyncLocalStorage();
|
|
94622
94830
|
BLOCKRUN_API = "https://blockrun.ai/api";
|
|
94623
94831
|
BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
94624
|
-
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ??
|
|
94625
|
-
IMAGE_DIR =
|
|
94626
|
-
AUDIO_DIR =
|
|
94627
|
-
VIDEO_DIR =
|
|
94832
|
+
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ?? join11(homedir8(), ".clawrouter-desktop", "service-token");
|
|
94833
|
+
IMAGE_DIR = join11(homedir8(), ".openclaw", "blockrun", "images");
|
|
94834
|
+
AUDIO_DIR = join11(homedir8(), ".openclaw", "blockrun", "audio");
|
|
94835
|
+
VIDEO_DIR = join11(homedir8(), ".openclaw", "blockrun", "videos");
|
|
94628
94836
|
AUTO_MODEL = "blockrun/auto";
|
|
94629
94837
|
ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
94630
94838
|
"blockrun/eco",
|
|
@@ -116816,8 +117024,8 @@ var init_constants3 = __esm({
|
|
|
116816
117024
|
|
|
116817
117025
|
// src/polymarket/wallet-adapter.ts
|
|
116818
117026
|
import fs2 from "fs";
|
|
116819
|
-
import { homedir as
|
|
116820
|
-
import { join as
|
|
117027
|
+
import { homedir as homedir9 } from "os";
|
|
117028
|
+
import { join as join12 } from "path";
|
|
116821
117029
|
function getOrCreateWalletKey() {
|
|
116822
117030
|
const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
|
|
116823
117031
|
if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
|
|
@@ -116865,7 +117073,7 @@ var init_wallet_adapter = __esm({
|
|
|
116865
117073
|
init_esm();
|
|
116866
117074
|
init_chains();
|
|
116867
117075
|
init_constants3();
|
|
116868
|
-
WALLET_FILE2 =
|
|
117076
|
+
WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
|
|
116869
117077
|
BASE_RPC_URLS = [
|
|
116870
117078
|
"https://mainnet.base.org",
|
|
116871
117079
|
"https://base.llamarpc.com",
|
|
@@ -227381,9 +227589,9 @@ import {
|
|
|
227381
227589
|
unlinkSync
|
|
227382
227590
|
} from "fs";
|
|
227383
227591
|
import { readFile as readFileAsync } from "fs/promises";
|
|
227384
|
-
import { homedir as
|
|
227592
|
+
import { homedir as homedir14 } from "os";
|
|
227385
227593
|
import { randomUUID } from "crypto";
|
|
227386
|
-
import { join as
|
|
227594
|
+
import { join as join17 } from "path";
|
|
227387
227595
|
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
227388
227596
|
const start = Date.now();
|
|
227389
227597
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -227425,8 +227633,8 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
227425
227633
|
return cfg.tools?.web?.search?.enabled === false;
|
|
227426
227634
|
}
|
|
227427
227635
|
function injectModelsConfig(logger48, options = {}) {
|
|
227428
|
-
const configDir =
|
|
227429
|
-
const configPath =
|
|
227636
|
+
const configDir = join17(homedir14(), ".openclaw");
|
|
227637
|
+
const configPath = join17(configDir, "openclaw.json");
|
|
227430
227638
|
let config = {};
|
|
227431
227639
|
let needsWrite = false;
|
|
227432
227640
|
if (!existsSync3(configDir)) {
|
|
@@ -227647,7 +227855,7 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
227647
227855
|
}
|
|
227648
227856
|
function syncAgentModelCache(logger48, options = {}) {
|
|
227649
227857
|
if (!isGatewayMode() && !options.forceWrite) return;
|
|
227650
|
-
const agentsDir =
|
|
227858
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227651
227859
|
if (!existsSync3(agentsDir)) return;
|
|
227652
227860
|
let agentDirs;
|
|
227653
227861
|
try {
|
|
@@ -227657,7 +227865,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227657
227865
|
}
|
|
227658
227866
|
const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
|
|
227659
227867
|
for (const agent of agentDirs) {
|
|
227660
|
-
const cachePath =
|
|
227868
|
+
const cachePath = join17(agentsDir, agent, "agent", "models.json");
|
|
227661
227869
|
if (!existsSync3(cachePath)) continue;
|
|
227662
227870
|
try {
|
|
227663
227871
|
const raw = readTextFileSync(cachePath).trim();
|
|
@@ -227686,7 +227894,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227686
227894
|
}
|
|
227687
227895
|
}
|
|
227688
227896
|
function injectAuthProfile(logger48) {
|
|
227689
|
-
const agentsDir =
|
|
227897
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227690
227898
|
if (!existsSync3(agentsDir)) {
|
|
227691
227899
|
try {
|
|
227692
227900
|
mkdirSync3(agentsDir, { recursive: true });
|
|
@@ -227703,9 +227911,9 @@ function injectAuthProfile(logger48) {
|
|
|
227703
227911
|
agents = ["main", ...agents];
|
|
227704
227912
|
}
|
|
227705
227913
|
for (const agentId of agents) {
|
|
227706
|
-
const authDir =
|
|
227707
|
-
const authPath =
|
|
227708
|
-
const sqlitePath =
|
|
227914
|
+
const authDir = join17(agentsDir, agentId, "agent");
|
|
227915
|
+
const authPath = join17(authDir, "auth-profiles.json");
|
|
227916
|
+
const sqlitePath = join17(authDir, "openclaw-agent.sqlite");
|
|
227709
227917
|
if (existsSync3(sqlitePath)) {
|
|
227710
227918
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
227711
227919
|
continue;
|
|
@@ -227852,9 +228060,21 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
227852
228060
|
if (startupGeneration !== void 0 && isProxyStartupCurrent(startupGeneration, proc)) {
|
|
227853
228061
|
proc.__clawrouterStartupPhase = "starting";
|
|
227854
228062
|
}
|
|
228063
|
+
const pluginApiKey = api.pluginConfig?.apiKey;
|
|
228064
|
+
const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
|
|
228065
|
+
if (typeof pluginApiKey === "string" && !isValidApiKey(pluginApiKey)) {
|
|
228066
|
+
api.logger.warn(
|
|
228067
|
+
`pluginConfig.apiKey is set but invalid (expected brk_...) \u2014 ignoring it and looking elsewhere`
|
|
228068
|
+
);
|
|
228069
|
+
}
|
|
228070
|
+
const apiKey = resolvedApiKey?.key;
|
|
227855
228071
|
const configKey = api.pluginConfig?.walletKey;
|
|
227856
228072
|
let wallet;
|
|
227857
|
-
if (
|
|
228073
|
+
if (apiKey) {
|
|
228074
|
+
api.logger.info(
|
|
228075
|
+
`Using BlockRun API key ${maskApiKey(apiKey)} (from ${resolvedApiKey.source}) \u2014 billing account credit, no wallet`
|
|
228076
|
+
);
|
|
228077
|
+
} else if (typeof configKey === "string" && /^0x[0-9a-fA-F]{64}$/.test(configKey)) {
|
|
227858
228078
|
const account = privateKeyToAccount(configKey);
|
|
227859
228079
|
wallet = { key: configKey, address: account.address, source: "config" };
|
|
227860
228080
|
} else {
|
|
@@ -227865,7 +228085,8 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
227865
228085
|
}
|
|
227866
228086
|
wallet = await resolveOrGenerateWalletKey();
|
|
227867
228087
|
}
|
|
227868
|
-
if (wallet
|
|
228088
|
+
if (!wallet) {
|
|
228089
|
+
} else if (wallet.source === "generated") {
|
|
227869
228090
|
api.logger.warn(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
227870
228091
|
api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
|
|
227871
228092
|
api.logger.warn(` Address : ${wallet.address}`);
|
|
@@ -227890,13 +228111,13 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
227890
228111
|
);
|
|
227891
228112
|
}
|
|
227892
228113
|
const proxy = await startProxy({
|
|
227893
|
-
wallet,
|
|
228114
|
+
...apiKey ? { apiKey } : { wallet },
|
|
227894
228115
|
routingConfig,
|
|
227895
228116
|
maxCostPerRunUsd,
|
|
227896
228117
|
maxCostPerRunMode,
|
|
227897
228118
|
spendControl: liveSpendControl = new SpendControl(),
|
|
227898
228119
|
onReady: (port) => {
|
|
227899
|
-
api.logger.info(`BlockRun x402 proxy listening on port ${port}`);
|
|
228120
|
+
api.logger.info(`BlockRun ${apiKey ? "API-key" : "x402"} proxy listening on port ${port}`);
|
|
227900
228121
|
},
|
|
227901
228122
|
onError: (error) => {
|
|
227902
228123
|
api.logger.error(`BlockRun proxy error: ${error.message}`);
|
|
@@ -227942,6 +228163,10 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
227942
228163
|
}
|
|
227943
228164
|
api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
|
|
227944
228165
|
api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
|
|
228166
|
+
if (apiKey) {
|
|
228167
|
+
api.logger.info(`Billing: BlockRun account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
228168
|
+
return true;
|
|
228169
|
+
}
|
|
227945
228170
|
const currentChain = await resolvePaymentChain();
|
|
227946
228171
|
const displayAddress = currentChain === "solana" && proxy.solanaAddress ? proxy.solanaAddress : wallet.address;
|
|
227947
228172
|
const network = currentChain === "solana" ? "Solana" : "Base";
|
|
@@ -228185,7 +228410,7 @@ function buildImageGenerationProvider() {
|
|
|
228185
228410
|
(result.data ?? []).map(async (img) => {
|
|
228186
228411
|
const filename = img.url?.split("/images/").pop();
|
|
228187
228412
|
if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
|
|
228188
|
-
const filePath =
|
|
228413
|
+
const filePath = join17(IMAGE_DIR2, filename);
|
|
228189
228414
|
const buffer2 = await readFileAsync(filePath);
|
|
228190
228415
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
228191
228416
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
@@ -228237,7 +228462,7 @@ function buildMusicGenerationProvider() {
|
|
|
228237
228462
|
(result.data ?? []).map(async (track) => {
|
|
228238
228463
|
const filename = track.url?.split("/audio/").pop();
|
|
228239
228464
|
if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
|
|
228240
|
-
const filePath =
|
|
228465
|
+
const filePath = join17(AUDIO_DIR2, filename);
|
|
228241
228466
|
const buffer2 = await readFileAsync(filePath);
|
|
228242
228467
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
|
|
228243
228468
|
const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
|
|
@@ -228313,7 +228538,7 @@ function buildVideoGenerationProvider() {
|
|
|
228313
228538
|
(result.data ?? []).map(async (clip) => {
|
|
228314
228539
|
const filename = clip.url?.split("/videos/").pop();
|
|
228315
228540
|
if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
|
|
228316
|
-
const filePath =
|
|
228541
|
+
const filePath = join17(VIDEO_DIR2, filename);
|
|
228317
228542
|
const buffer2 = await readFileAsync(filePath);
|
|
228318
228543
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
|
|
228319
228544
|
const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
|
|
@@ -228366,6 +228591,22 @@ function createWalletCommand(api) {
|
|
|
228366
228591
|
requireAuth: true,
|
|
228367
228592
|
handler: async (ctx) => {
|
|
228368
228593
|
const subcommand = ctx.args?.trim().toLowerCase() || "status";
|
|
228594
|
+
const activeApiKey = (await resolveApiKey())?.key;
|
|
228595
|
+
if (activeApiKey) {
|
|
228596
|
+
return {
|
|
228597
|
+
text: [
|
|
228598
|
+
"**BlockRun account** (API key)",
|
|
228599
|
+
"",
|
|
228600
|
+
`**Key:** \`${maskApiKey(activeApiKey)}\``,
|
|
228601
|
+
"**Billing:** account credit \u2014 no wallet, no payment chain, no gas.",
|
|
228602
|
+
"",
|
|
228603
|
+
`**Add credit:** ${PORTAL_CREDITS_URL}`,
|
|
228604
|
+
`**Usage & keys:** ${PORTAL_KEYS_URL}`,
|
|
228605
|
+
"",
|
|
228606
|
+
"To pay with a USDC wallet over x402 instead, run `clawrouter logout` and restart."
|
|
228607
|
+
].join("\n")
|
|
228608
|
+
};
|
|
228609
|
+
}
|
|
228369
228610
|
let wallet;
|
|
228370
228611
|
try {
|
|
228371
228612
|
wallet = subcommand === "status" || subcommand === "export" ? await resolveExistingWalletKey() : await resolveOrGenerateWalletKey();
|
|
@@ -228622,6 +228863,7 @@ var init_index = __esm({
|
|
|
228622
228863
|
"src/index.ts"() {
|
|
228623
228864
|
init_provider();
|
|
228624
228865
|
init_proxy();
|
|
228866
|
+
init_api_key();
|
|
228625
228867
|
init_web_search_provider();
|
|
228626
228868
|
init_auth();
|
|
228627
228869
|
init_balance();
|
|
@@ -228661,9 +228903,9 @@ var init_index = __esm({
|
|
|
228661
228903
|
activeProxyHandle = null;
|
|
228662
228904
|
liveSpendControl = null;
|
|
228663
228905
|
pendingConfiguredStartupApi = null;
|
|
228664
|
-
IMAGE_DIR2 =
|
|
228665
|
-
AUDIO_DIR2 =
|
|
228666
|
-
VIDEO_DIR2 =
|
|
228906
|
+
IMAGE_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "images");
|
|
228907
|
+
AUDIO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "audio");
|
|
228908
|
+
VIDEO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "videos");
|
|
228667
228909
|
plugin = {
|
|
228668
228910
|
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
228669
228911
|
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
@@ -229080,7 +229322,7 @@ ${errText}`
|
|
|
229080
229322
|
}
|
|
229081
229323
|
resetProxyStartupState();
|
|
229082
229324
|
try {
|
|
229083
|
-
const configPath =
|
|
229325
|
+
const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
|
|
229084
229326
|
if (existsSync3(configPath)) {
|
|
229085
229327
|
const config = JSON.parse(readTextFileSync(configPath));
|
|
229086
229328
|
if (config.models?.providers?.blockrun) {
|
|
@@ -229122,11 +229364,11 @@ ${errText}`
|
|
|
229122
229364
|
api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
229123
229365
|
}
|
|
229124
229366
|
try {
|
|
229125
|
-
const agentsDir =
|
|
229367
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
229126
229368
|
if (existsSync3(agentsDir)) {
|
|
229127
229369
|
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
229128
229370
|
if (!entry.isDirectory()) continue;
|
|
229129
|
-
const authPath =
|
|
229371
|
+
const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
|
|
229130
229372
|
if (!existsSync3(authPath)) continue;
|
|
229131
229373
|
try {
|
|
229132
229374
|
const store = JSON.parse(readTextFileSync(authPath));
|