@blockrun/clawrouter 0.12.267 → 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 +664 -157
- package/dist/index.d.ts +58 -11
- package/dist/index.js +326 -90
- package/docs/configuration.md +102 -9
- package/openclaw.plugin.json +9 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -40559,7 +40559,7 @@ var init_errors5 = __esm({
|
|
|
40559
40559
|
});
|
|
40560
40560
|
|
|
40561
40561
|
// src/balance.ts
|
|
40562
|
-
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor;
|
|
40562
|
+
var USDC_BASE, CACHE_TTL_MS, BALANCE_THRESHOLDS, BalanceMonitor, ApiKeyBalanceMonitor;
|
|
40563
40563
|
var init_balance = __esm({
|
|
40564
40564
|
"src/balance.ts"() {
|
|
40565
40565
|
"use strict";
|
|
@@ -40688,6 +40688,38 @@ var init_balance = __esm({
|
|
|
40688
40688
|
};
|
|
40689
40689
|
}
|
|
40690
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
|
+
};
|
|
40691
40723
|
}
|
|
40692
40724
|
});
|
|
40693
40725
|
|
|
@@ -59787,6 +59819,99 @@ var init_auth = __esm({
|
|
|
59787
59819
|
}
|
|
59788
59820
|
});
|
|
59789
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
|
+
|
|
59790
59915
|
// src/compression/types.ts
|
|
59791
59916
|
var DEFAULT_COMPRESSION_CONFIG;
|
|
59792
59917
|
var init_types2 = __esm({
|
|
@@ -60876,8 +61001,8 @@ var init_updater = __esm({
|
|
|
60876
61001
|
|
|
60877
61002
|
// src/exclude-models.ts
|
|
60878
61003
|
import { readFileSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
60879
|
-
import { join as
|
|
60880
|
-
import { homedir as
|
|
61004
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
61005
|
+
import { homedir as homedir6 } from "os";
|
|
60881
61006
|
function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
|
|
60882
61007
|
try {
|
|
60883
61008
|
const mtimeMs = statSync(filePath).mtimeMs;
|
|
@@ -60926,7 +61051,7 @@ var init_exclude_models = __esm({
|
|
|
60926
61051
|
"src/exclude-models.ts"() {
|
|
60927
61052
|
"use strict";
|
|
60928
61053
|
init_models();
|
|
60929
|
-
DEFAULT_FILE_PATH =
|
|
61054
|
+
DEFAULT_FILE_PATH = join9(homedir6(), ".openclaw", "blockrun", "exclude-models.json");
|
|
60930
61055
|
loadCache = /* @__PURE__ */ new Map();
|
|
60931
61056
|
}
|
|
60932
61057
|
});
|
|
@@ -75935,7 +76060,7 @@ var require_snapshot_utils = __commonJS({
|
|
|
75935
76060
|
var require_snapshot_recorder = __commonJS({
|
|
75936
76061
|
"node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
75937
76062
|
"use strict";
|
|
75938
|
-
var { writeFile:
|
|
76063
|
+
var { writeFile: writeFile4, readFile: readFile3, mkdir: mkdir6 } = __require("fs/promises");
|
|
75939
76064
|
var { dirname: dirname4, resolve } = __require("path");
|
|
75940
76065
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
|
|
75941
76066
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
@@ -76182,12 +76307,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
76182
76307
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
76183
76308
|
}
|
|
76184
76309
|
const resolvedPath = resolve(path5);
|
|
76185
|
-
await
|
|
76310
|
+
await mkdir6(dirname4(resolvedPath), { recursive: true });
|
|
76186
76311
|
const data = Array.from(this.#snapshots.entries()).map(([hash5, snapshot]) => ({
|
|
76187
76312
|
hash: hash5,
|
|
76188
76313
|
snapshot
|
|
76189
76314
|
}));
|
|
76190
|
-
await
|
|
76315
|
+
await writeFile4(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
76191
76316
|
}
|
|
76192
76317
|
/**
|
|
76193
76318
|
* Clears all recorded snapshots
|
|
@@ -88516,9 +88641,9 @@ var init_textual_tool_calls = __esm({
|
|
|
88516
88641
|
});
|
|
88517
88642
|
|
|
88518
88643
|
// src/response-store.ts
|
|
88519
|
-
import { appendFile as appendFile2, mkdir as
|
|
88520
|
-
import { homedir as
|
|
88521
|
-
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";
|
|
88522
88647
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
88523
88648
|
function isEnabled() {
|
|
88524
88649
|
const v = process.env.BLOCKRUN_RESPONSE_STORE;
|
|
@@ -88526,12 +88651,12 @@ function isEnabled() {
|
|
|
88526
88651
|
}
|
|
88527
88652
|
async function ensureDir2() {
|
|
88528
88653
|
if (dirReady2) return;
|
|
88529
|
-
await
|
|
88654
|
+
await mkdir4(STORE_DIR, { recursive: true });
|
|
88530
88655
|
dirReady2 = true;
|
|
88531
88656
|
}
|
|
88532
88657
|
function dailyFile(date = /* @__PURE__ */ new Date()) {
|
|
88533
88658
|
const iso = date.toISOString().slice(0, 10);
|
|
88534
|
-
return
|
|
88659
|
+
return join10(STORE_DIR, `responses-${iso}.jsonl`);
|
|
88535
88660
|
}
|
|
88536
88661
|
function genId() {
|
|
88537
88662
|
return `resp_${Date.now()}_${randomBytes7(3).toString("hex")}`;
|
|
@@ -88581,7 +88706,7 @@ async function listRecent(limit = 20, daysBack = 7) {
|
|
|
88581
88706
|
d.setUTCDate(d.getUTCDate() - i);
|
|
88582
88707
|
const iso = d.toISOString().slice(0, 10);
|
|
88583
88708
|
const name = `responses-${iso}.jsonl`;
|
|
88584
|
-
if (files.includes(name)) candidateFiles.push(
|
|
88709
|
+
if (files.includes(name)) candidateFiles.push(join10(STORE_DIR, name));
|
|
88585
88710
|
}
|
|
88586
88711
|
const all3 = [];
|
|
88587
88712
|
for (const f of candidateFiles) {
|
|
@@ -88610,7 +88735,7 @@ var STORE_DIR, dirReady2;
|
|
|
88610
88735
|
var init_response_store = __esm({
|
|
88611
88736
|
"src/response-store.ts"() {
|
|
88612
88737
|
"use strict";
|
|
88613
|
-
STORE_DIR =
|
|
88738
|
+
STORE_DIR = join10(homedir7(), ".openclaw", "blockrun", "responses");
|
|
88614
88739
|
dirReady2 = false;
|
|
88615
88740
|
}
|
|
88616
88741
|
});
|
|
@@ -90261,15 +90386,18 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
90261
90386
|
import { createHmac } from "crypto";
|
|
90262
90387
|
import { createServer } from "http";
|
|
90263
90388
|
import { finished } from "stream";
|
|
90264
|
-
import { homedir as
|
|
90265
|
-
import { join as
|
|
90266
|
-
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";
|
|
90267
90392
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
90268
|
-
async function loadGatewayCatalog(apiBase) {
|
|
90393
|
+
async function loadGatewayCatalog(apiBase, apiKey) {
|
|
90269
90394
|
try {
|
|
90270
90395
|
const controller = new AbortController();
|
|
90271
90396
|
const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
|
|
90272
|
-
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
|
+
});
|
|
90273
90401
|
clearTimeout(timer2);
|
|
90274
90402
|
if (!res.ok) return;
|
|
90275
90403
|
const body = await res.json();
|
|
@@ -90568,8 +90696,9 @@ async function checkExistingProxy(port) {
|
|
|
90568
90696
|
clearTimeout(timeoutId);
|
|
90569
90697
|
if (response.ok) {
|
|
90570
90698
|
const data = await response.json();
|
|
90571
|
-
|
|
90572
|
-
|
|
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 };
|
|
90573
90702
|
}
|
|
90574
90703
|
}
|
|
90575
90704
|
return void 0;
|
|
@@ -91077,7 +91206,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91077
91206
|
});
|
|
91078
91207
|
}
|
|
91079
91208
|
function readImageFileAsDataUri(filePath) {
|
|
91080
|
-
const resolved = filePath.startsWith("~/") ?
|
|
91209
|
+
const resolved = filePath.startsWith("~/") ? join11(homedir8(), filePath.slice(2)) : filePath;
|
|
91081
91210
|
if (!existsSync2(resolved)) {
|
|
91082
91211
|
throw new Error(`Image file not found: ${resolved}`);
|
|
91083
91212
|
}
|
|
@@ -91120,16 +91249,48 @@ async function uploadDataUriToHost(dataUri) {
|
|
|
91120
91249
|
clearTimeout(uploadTimeout);
|
|
91121
91250
|
}
|
|
91122
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
|
+
}
|
|
91123
91268
|
async function startProxy(options) {
|
|
91124
91269
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91125
91270
|
if (upstreamProxy) {
|
|
91126
91271
|
console.log(`[ClawRouter] Upstream proxy: ${upstreamProxy}`);
|
|
91127
91272
|
}
|
|
91128
|
-
const
|
|
91129
|
-
|
|
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;
|
|
91130
91287
|
const paymentChain = options.paymentChain ?? await resolvePaymentChain();
|
|
91131
|
-
const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91132
|
-
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) {
|
|
91133
91294
|
console.warn(
|
|
91134
91295
|
`[ClawRouter] \u26A0 Payment chain is Solana but no mnemonic found \u2014 falling back to Base (EVM).`
|
|
91135
91296
|
);
|
|
@@ -91140,12 +91301,31 @@ async function startProxy(options) {
|
|
|
91140
91301
|
} else if (paymentChain === "solana") {
|
|
91141
91302
|
console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
|
|
91142
91303
|
}
|
|
91143
|
-
void loadGatewayCatalog(apiBase);
|
|
91304
|
+
void loadGatewayCatalog(apiBase, apiKey);
|
|
91144
91305
|
const listenPort = options.port ?? getProxyPort();
|
|
91145
91306
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91146
91307
|
if (existingProxy) {
|
|
91147
|
-
const account2 = privateKeyToAccount(walletKey);
|
|
91148
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);
|
|
91149
91329
|
if (existingProxy.wallet !== account2.address) {
|
|
91150
91330
|
console.warn(
|
|
91151
91331
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
@@ -91184,20 +91364,23 @@ async function startProxy(options) {
|
|
|
91184
91364
|
baseUrl: baseUrl2,
|
|
91185
91365
|
walletAddress: existingProxy.wallet,
|
|
91186
91366
|
solanaAddress: reuseSolanaAddress,
|
|
91367
|
+
authMode,
|
|
91187
91368
|
balanceMonitor: balanceMonitor2,
|
|
91188
91369
|
close: async () => {
|
|
91189
91370
|
}
|
|
91190
91371
|
};
|
|
91191
91372
|
}
|
|
91192
|
-
const account = privateKeyToAccount(walletKey);
|
|
91193
|
-
const
|
|
91194
|
-
|
|
91195
|
-
|
|
91196
|
-
|
|
91197
|
-
|
|
91198
|
-
|
|
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
|
+
}
|
|
91199
91382
|
let solanaAddress;
|
|
91200
|
-
if (solanaPrivateKeyBytes) {
|
|
91383
|
+
if (x402 && solanaPrivateKeyBytes) {
|
|
91201
91384
|
const { registerExactSvmScheme: registerExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91202
91385
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91203
91386
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
@@ -91205,12 +91388,12 @@ async function startProxy(options) {
|
|
|
91205
91388
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91206
91389
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91207
91390
|
}
|
|
91208
|
-
x402
|
|
91391
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91209
91392
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
91210
91393
|
const payload = context.paymentPayload;
|
|
91211
91394
|
payload.extensions = withBuilderCodeServiceCode(payload.extensions);
|
|
91212
91395
|
});
|
|
91213
|
-
x402
|
|
91396
|
+
x402?.onAfterPaymentCreation(async (context) => {
|
|
91214
91397
|
const network = context.selectedRequirements.network;
|
|
91215
91398
|
const chain3 = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
|
|
91216
91399
|
const amountMicros = parseInt(context.selectedRequirements.amount || "0", 10);
|
|
@@ -91219,7 +91402,7 @@ async function startProxy(options) {
|
|
|
91219
91402
|
if (store) store.amountUsd = amountUsd;
|
|
91220
91403
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91221
91404
|
});
|
|
91222
|
-
const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91405
|
+
const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91223
91406
|
skipPreAuth: paymentChain === "solana",
|
|
91224
91407
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91225
91408
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -91229,6 +91412,8 @@ async function startProxy(options) {
|
|
|
91229
91412
|
let balanceMonitor;
|
|
91230
91413
|
if (options._balanceMonitorOverride) {
|
|
91231
91414
|
balanceMonitor = options._balanceMonitorOverride;
|
|
91415
|
+
} else if (authMode === "api-key") {
|
|
91416
|
+
balanceMonitor = new ApiKeyBalanceMonitor();
|
|
91232
91417
|
} else if (paymentChain === "solana" && solanaAddress) {
|
|
91233
91418
|
const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
|
|
91234
91419
|
balanceMonitor = new SolanaBalanceMonitor2(solanaAddress);
|
|
@@ -91270,11 +91455,17 @@ async function startProxy(options) {
|
|
|
91270
91455
|
const full = url2.searchParams.get("full") === "true";
|
|
91271
91456
|
const response = {
|
|
91272
91457
|
status: "ok",
|
|
91273
|
-
|
|
91274
|
-
paymentChain
|
|
91458
|
+
authMode
|
|
91275
91459
|
};
|
|
91276
|
-
if (
|
|
91277
|
-
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
|
+
}
|
|
91278
91469
|
}
|
|
91279
91470
|
if (upstreamProxy) {
|
|
91280
91471
|
response.upstreamProxy = upstreamProxy;
|
|
@@ -91290,9 +91481,15 @@ async function startProxy(options) {
|
|
|
91290
91481
|
).unref()
|
|
91291
91482
|
)
|
|
91292
91483
|
]);
|
|
91293
|
-
|
|
91294
|
-
|
|
91295
|
-
|
|
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
|
+
}
|
|
91296
91493
|
} catch {
|
|
91297
91494
|
response.balanceError = "Could not fetch balance";
|
|
91298
91495
|
}
|
|
@@ -91472,7 +91669,7 @@ async function startProxy(options) {
|
|
|
91472
91669
|
res.end("Bad request");
|
|
91473
91670
|
return;
|
|
91474
91671
|
}
|
|
91475
|
-
const filePath =
|
|
91672
|
+
const filePath = join11(IMAGE_DIR, filename);
|
|
91476
91673
|
try {
|
|
91477
91674
|
const s3 = await fsStat(filePath);
|
|
91478
91675
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91503,7 +91700,7 @@ async function startProxy(options) {
|
|
|
91503
91700
|
res.end("Bad request");
|
|
91504
91701
|
return;
|
|
91505
91702
|
}
|
|
91506
|
-
const filePath =
|
|
91703
|
+
const filePath = join11(AUDIO_DIR, filename);
|
|
91507
91704
|
try {
|
|
91508
91705
|
const s3 = await fsStat(filePath);
|
|
91509
91706
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91533,7 +91730,7 @@ async function startProxy(options) {
|
|
|
91533
91730
|
res.end("Bad request");
|
|
91534
91731
|
return;
|
|
91535
91732
|
}
|
|
91536
|
-
const filePath =
|
|
91733
|
+
const filePath = join11(VIDEO_DIR, filename);
|
|
91537
91734
|
try {
|
|
91538
91735
|
const s3 = await fsStat(filePath);
|
|
91539
91736
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -91666,7 +91863,7 @@ async function startProxy(options) {
|
|
|
91666
91863
|
}
|
|
91667
91864
|
}
|
|
91668
91865
|
if (result.data?.length) {
|
|
91669
|
-
await
|
|
91866
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91670
91867
|
const port2 = server.address()?.port ?? 8402;
|
|
91671
91868
|
for (const img of result.data) {
|
|
91672
91869
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91674,7 +91871,7 @@ async function startProxy(options) {
|
|
|
91674
91871
|
const [, mimeType, b64] = dataUriMatch;
|
|
91675
91872
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91676
91873
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91677
|
-
await
|
|
91874
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91678
91875
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91679
91876
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91680
91877
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -91685,7 +91882,7 @@ async function startProxy(options) {
|
|
|
91685
91882
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
91686
91883
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91687
91884
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
91688
|
-
await
|
|
91885
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
91689
91886
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91690
91887
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
91691
91888
|
}
|
|
@@ -91789,7 +91986,7 @@ async function startProxy(options) {
|
|
|
91789
91986
|
return;
|
|
91790
91987
|
}
|
|
91791
91988
|
if (result.data?.length) {
|
|
91792
|
-
await
|
|
91989
|
+
await mkdir5(IMAGE_DIR, { recursive: true });
|
|
91793
91990
|
const port2 = server.address()?.port ?? 8402;
|
|
91794
91991
|
for (const img of result.data) {
|
|
91795
91992
|
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
@@ -91797,7 +91994,7 @@ async function startProxy(options) {
|
|
|
91797
91994
|
const [, mimeType, b64] = dataUriMatch;
|
|
91798
91995
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
91799
91996
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91800
|
-
await
|
|
91997
|
+
await writeFile3(join11(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
91801
91998
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91802
91999
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
91803
92000
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -91808,7 +92005,7 @@ async function startProxy(options) {
|
|
|
91808
92005
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
91809
92006
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91810
92007
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
91811
|
-
await
|
|
92008
|
+
await writeFile3(join11(IMAGE_DIR, filename), buf);
|
|
91812
92009
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
91813
92010
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
91814
92011
|
}
|
|
@@ -91883,7 +92080,7 @@ async function startProxy(options) {
|
|
|
91883
92080
|
return;
|
|
91884
92081
|
}
|
|
91885
92082
|
if (result.data?.length) {
|
|
91886
|
-
await
|
|
92083
|
+
await mkdir5(AUDIO_DIR, { recursive: true });
|
|
91887
92084
|
const port2 = server.address()?.port ?? 8402;
|
|
91888
92085
|
for (const track of result.data) {
|
|
91889
92086
|
if (track.url?.startsWith("https://") || track.url?.startsWith("http://")) {
|
|
@@ -91894,7 +92091,7 @@ async function startProxy(options) {
|
|
|
91894
92091
|
const ext = contentType.includes("wav") ? "wav" : "mp3";
|
|
91895
92092
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
91896
92093
|
const buf = Buffer.from(await audioResp.arrayBuffer());
|
|
91897
|
-
await
|
|
92094
|
+
await writeFile3(join11(AUDIO_DIR, filename), buf);
|
|
91898
92095
|
track.url = `http://localhost:${port2}/audio/${filename}`;
|
|
91899
92096
|
console.log(`[ClawRouter] Audio saved \u2192 ${track.url}`);
|
|
91900
92097
|
}
|
|
@@ -92043,7 +92240,7 @@ async function startProxy(options) {
|
|
|
92043
92240
|
}
|
|
92044
92241
|
}
|
|
92045
92242
|
if (finalResult.data?.length) {
|
|
92046
|
-
await
|
|
92243
|
+
await mkdir5(VIDEO_DIR, { recursive: true });
|
|
92047
92244
|
const port2 = server.address()?.port ?? 8402;
|
|
92048
92245
|
for (const clip of finalResult.data) {
|
|
92049
92246
|
if (clip.url?.startsWith("https://") || clip.url?.startsWith("http://")) {
|
|
@@ -92054,7 +92251,7 @@ async function startProxy(options) {
|
|
|
92054
92251
|
const ext = contentType.includes("webm") ? "webm" : contentType.includes("quicktime") ? "mov" : "mp4";
|
|
92055
92252
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
92056
92253
|
const buf = Buffer.from(await videoResp.arrayBuffer());
|
|
92057
|
-
await
|
|
92254
|
+
await writeFile3(join11(VIDEO_DIR, filename), buf);
|
|
92058
92255
|
clip.url = `http://localhost:${port2}/videos/${filename}`;
|
|
92059
92256
|
console.log(`[ClawRouter] Video saved \u2192 ${clip.url}`);
|
|
92060
92257
|
}
|
|
@@ -92237,6 +92434,8 @@ async function startProxy(options) {
|
|
|
92237
92434
|
port: listenPort,
|
|
92238
92435
|
baseUrl: baseUrl2,
|
|
92239
92436
|
walletAddress: error.wallet,
|
|
92437
|
+
authMode,
|
|
92438
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92240
92439
|
balanceMonitor,
|
|
92241
92440
|
close: async () => {
|
|
92242
92441
|
}
|
|
@@ -92287,8 +92486,10 @@ async function startProxy(options) {
|
|
|
92287
92486
|
return {
|
|
92288
92487
|
port,
|
|
92289
92488
|
baseUrl,
|
|
92290
|
-
walletAddress: account
|
|
92489
|
+
walletAddress: account?.address ?? "",
|
|
92291
92490
|
solanaAddress,
|
|
92491
|
+
authMode,
|
|
92492
|
+
...apiKey ? { apiKeyLabel: maskApiKey(apiKey) } : {},
|
|
92292
92493
|
balanceMonitor,
|
|
92293
92494
|
close: () => new Promise((res, rej) => {
|
|
92294
92495
|
const timeout = setTimeout(() => {
|
|
@@ -94611,6 +94812,7 @@ var init_proxy = __esm({
|
|
|
94611
94812
|
init_response_cache();
|
|
94612
94813
|
init_balance();
|
|
94613
94814
|
init_auth();
|
|
94815
|
+
init_api_key();
|
|
94614
94816
|
init_spend_control();
|
|
94615
94817
|
init_compression();
|
|
94616
94818
|
init_version4();
|
|
@@ -94627,10 +94829,10 @@ var init_proxy = __esm({
|
|
|
94627
94829
|
paymentStore = new AsyncLocalStorage();
|
|
94628
94830
|
BLOCKRUN_API = "https://blockrun.ai/api";
|
|
94629
94831
|
BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
94630
|
-
DESKTOP_SERVICE_TOKEN_FILE = process.env.CLAWROUTER_DESKTOP_TOKEN_FILE ??
|
|
94631
|
-
IMAGE_DIR =
|
|
94632
|
-
AUDIO_DIR =
|
|
94633
|
-
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");
|
|
94634
94836
|
AUTO_MODEL = "blockrun/auto";
|
|
94635
94837
|
ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
94636
94838
|
"blockrun/eco",
|
|
@@ -116822,8 +117024,8 @@ var init_constants3 = __esm({
|
|
|
116822
117024
|
|
|
116823
117025
|
// src/polymarket/wallet-adapter.ts
|
|
116824
117026
|
import fs2 from "fs";
|
|
116825
|
-
import { homedir as
|
|
116826
|
-
import { join as
|
|
117027
|
+
import { homedir as homedir9 } from "os";
|
|
117028
|
+
import { join as join12 } from "path";
|
|
116827
117029
|
function getOrCreateWalletKey() {
|
|
116828
117030
|
const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
|
|
116829
117031
|
if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
|
|
@@ -116871,7 +117073,7 @@ var init_wallet_adapter = __esm({
|
|
|
116871
117073
|
init_esm();
|
|
116872
117074
|
init_chains();
|
|
116873
117075
|
init_constants3();
|
|
116874
|
-
WALLET_FILE2 =
|
|
117076
|
+
WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
|
|
116875
117077
|
BASE_RPC_URLS = [
|
|
116876
117078
|
"https://mainnet.base.org",
|
|
116877
117079
|
"https://base.llamarpc.com",
|
|
@@ -227387,9 +227589,9 @@ import {
|
|
|
227387
227589
|
unlinkSync
|
|
227388
227590
|
} from "fs";
|
|
227389
227591
|
import { readFile as readFileAsync } from "fs/promises";
|
|
227390
|
-
import { homedir as
|
|
227592
|
+
import { homedir as homedir14 } from "os";
|
|
227391
227593
|
import { randomUUID } from "crypto";
|
|
227392
|
-
import { join as
|
|
227594
|
+
import { join as join17 } from "path";
|
|
227393
227595
|
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
227394
227596
|
const start = Date.now();
|
|
227395
227597
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -227431,8 +227633,8 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
227431
227633
|
return cfg.tools?.web?.search?.enabled === false;
|
|
227432
227634
|
}
|
|
227433
227635
|
function injectModelsConfig(logger48, options = {}) {
|
|
227434
|
-
const configDir =
|
|
227435
|
-
const configPath =
|
|
227636
|
+
const configDir = join17(homedir14(), ".openclaw");
|
|
227637
|
+
const configPath = join17(configDir, "openclaw.json");
|
|
227436
227638
|
let config = {};
|
|
227437
227639
|
let needsWrite = false;
|
|
227438
227640
|
if (!existsSync3(configDir)) {
|
|
@@ -227653,7 +227855,7 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
227653
227855
|
}
|
|
227654
227856
|
function syncAgentModelCache(logger48, options = {}) {
|
|
227655
227857
|
if (!isGatewayMode() && !options.forceWrite) return;
|
|
227656
|
-
const agentsDir =
|
|
227858
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227657
227859
|
if (!existsSync3(agentsDir)) return;
|
|
227658
227860
|
let agentDirs;
|
|
227659
227861
|
try {
|
|
@@ -227663,7 +227865,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227663
227865
|
}
|
|
227664
227866
|
const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
|
|
227665
227867
|
for (const agent of agentDirs) {
|
|
227666
|
-
const cachePath =
|
|
227868
|
+
const cachePath = join17(agentsDir, agent, "agent", "models.json");
|
|
227667
227869
|
if (!existsSync3(cachePath)) continue;
|
|
227668
227870
|
try {
|
|
227669
227871
|
const raw = readTextFileSync(cachePath).trim();
|
|
@@ -227692,7 +227894,7 @@ function syncAgentModelCache(logger48, options = {}) {
|
|
|
227692
227894
|
}
|
|
227693
227895
|
}
|
|
227694
227896
|
function injectAuthProfile(logger48) {
|
|
227695
|
-
const agentsDir =
|
|
227897
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
227696
227898
|
if (!existsSync3(agentsDir)) {
|
|
227697
227899
|
try {
|
|
227698
227900
|
mkdirSync3(agentsDir, { recursive: true });
|
|
@@ -227709,9 +227911,9 @@ function injectAuthProfile(logger48) {
|
|
|
227709
227911
|
agents = ["main", ...agents];
|
|
227710
227912
|
}
|
|
227711
227913
|
for (const agentId of agents) {
|
|
227712
|
-
const authDir =
|
|
227713
|
-
const authPath =
|
|
227714
|
-
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");
|
|
227715
227917
|
if (existsSync3(sqlitePath)) {
|
|
227716
227918
|
removeInjectedAuthPlaceholder(authPath, logger48, agentId);
|
|
227717
227919
|
continue;
|
|
@@ -227858,9 +228060,21 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
227858
228060
|
if (startupGeneration !== void 0 && isProxyStartupCurrent(startupGeneration, proc)) {
|
|
227859
228061
|
proc.__clawrouterStartupPhase = "starting";
|
|
227860
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;
|
|
227861
228071
|
const configKey = api.pluginConfig?.walletKey;
|
|
227862
228072
|
let wallet;
|
|
227863
|
-
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)) {
|
|
227864
228078
|
const account = privateKeyToAccount(configKey);
|
|
227865
228079
|
wallet = { key: configKey, address: account.address, source: "config" };
|
|
227866
228080
|
} else {
|
|
@@ -227871,7 +228085,8 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
227871
228085
|
}
|
|
227872
228086
|
wallet = await resolveOrGenerateWalletKey();
|
|
227873
228087
|
}
|
|
227874
|
-
if (wallet
|
|
228088
|
+
if (!wallet) {
|
|
228089
|
+
} else if (wallet.source === "generated") {
|
|
227875
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`);
|
|
227876
228091
|
api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
|
|
227877
228092
|
api.logger.warn(` Address : ${wallet.address}`);
|
|
@@ -227896,13 +228111,13 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
227896
228111
|
);
|
|
227897
228112
|
}
|
|
227898
228113
|
const proxy = await startProxy({
|
|
227899
|
-
wallet,
|
|
228114
|
+
...apiKey ? { apiKey } : { wallet },
|
|
227900
228115
|
routingConfig,
|
|
227901
228116
|
maxCostPerRunUsd,
|
|
227902
228117
|
maxCostPerRunMode,
|
|
227903
228118
|
spendControl: liveSpendControl = new SpendControl(),
|
|
227904
228119
|
onReady: (port) => {
|
|
227905
|
-
api.logger.info(`BlockRun x402 proxy listening on port ${port}`);
|
|
228120
|
+
api.logger.info(`BlockRun ${apiKey ? "API-key" : "x402"} proxy listening on port ${port}`);
|
|
227906
228121
|
},
|
|
227907
228122
|
onError: (error) => {
|
|
227908
228123
|
api.logger.error(`BlockRun proxy error: ${error.message}`);
|
|
@@ -227948,6 +228163,10 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
227948
228163
|
}
|
|
227949
228164
|
api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
|
|
227950
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
|
+
}
|
|
227951
228170
|
const currentChain = await resolvePaymentChain();
|
|
227952
228171
|
const displayAddress = currentChain === "solana" && proxy.solanaAddress ? proxy.solanaAddress : wallet.address;
|
|
227953
228172
|
const network = currentChain === "solana" ? "Solana" : "Base";
|
|
@@ -228191,7 +228410,7 @@ function buildImageGenerationProvider() {
|
|
|
228191
228410
|
(result.data ?? []).map(async (img) => {
|
|
228192
228411
|
const filename = img.url?.split("/images/").pop();
|
|
228193
228412
|
if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
|
|
228194
|
-
const filePath =
|
|
228413
|
+
const filePath = join17(IMAGE_DIR2, filename);
|
|
228195
228414
|
const buffer2 = await readFileAsync(filePath);
|
|
228196
228415
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
228197
228416
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
@@ -228243,7 +228462,7 @@ function buildMusicGenerationProvider() {
|
|
|
228243
228462
|
(result.data ?? []).map(async (track) => {
|
|
228244
228463
|
const filename = track.url?.split("/audio/").pop();
|
|
228245
228464
|
if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
|
|
228246
|
-
const filePath =
|
|
228465
|
+
const filePath = join17(AUDIO_DIR2, filename);
|
|
228247
228466
|
const buffer2 = await readFileAsync(filePath);
|
|
228248
228467
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
|
|
228249
228468
|
const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
|
|
@@ -228319,7 +228538,7 @@ function buildVideoGenerationProvider() {
|
|
|
228319
228538
|
(result.data ?? []).map(async (clip) => {
|
|
228320
228539
|
const filename = clip.url?.split("/videos/").pop();
|
|
228321
228540
|
if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
|
|
228322
|
-
const filePath =
|
|
228541
|
+
const filePath = join17(VIDEO_DIR2, filename);
|
|
228323
228542
|
const buffer2 = await readFileAsync(filePath);
|
|
228324
228543
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
|
|
228325
228544
|
const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
|
|
@@ -228372,6 +228591,22 @@ function createWalletCommand(api) {
|
|
|
228372
228591
|
requireAuth: true,
|
|
228373
228592
|
handler: async (ctx) => {
|
|
228374
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
|
+
}
|
|
228375
228610
|
let wallet;
|
|
228376
228611
|
try {
|
|
228377
228612
|
wallet = subcommand === "status" || subcommand === "export" ? await resolveExistingWalletKey() : await resolveOrGenerateWalletKey();
|
|
@@ -228628,6 +228863,7 @@ var init_index = __esm({
|
|
|
228628
228863
|
"src/index.ts"() {
|
|
228629
228864
|
init_provider();
|
|
228630
228865
|
init_proxy();
|
|
228866
|
+
init_api_key();
|
|
228631
228867
|
init_web_search_provider();
|
|
228632
228868
|
init_auth();
|
|
228633
228869
|
init_balance();
|
|
@@ -228667,9 +228903,9 @@ var init_index = __esm({
|
|
|
228667
228903
|
activeProxyHandle = null;
|
|
228668
228904
|
liveSpendControl = null;
|
|
228669
228905
|
pendingConfiguredStartupApi = null;
|
|
228670
|
-
IMAGE_DIR2 =
|
|
228671
|
-
AUDIO_DIR2 =
|
|
228672
|
-
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");
|
|
228673
228909
|
plugin = {
|
|
228674
228910
|
// NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
|
|
228675
228911
|
// 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
|
|
@@ -229086,7 +229322,7 @@ ${errText}`
|
|
|
229086
229322
|
}
|
|
229087
229323
|
resetProxyStartupState();
|
|
229088
229324
|
try {
|
|
229089
|
-
const configPath =
|
|
229325
|
+
const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
|
|
229090
229326
|
if (existsSync3(configPath)) {
|
|
229091
229327
|
const config = JSON.parse(readTextFileSync(configPath));
|
|
229092
229328
|
if (config.models?.providers?.blockrun) {
|
|
@@ -229128,11 +229364,11 @@ ${errText}`
|
|
|
229128
229364
|
api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
229129
229365
|
}
|
|
229130
229366
|
try {
|
|
229131
|
-
const agentsDir =
|
|
229367
|
+
const agentsDir = join17(homedir14(), ".openclaw", "agents");
|
|
229132
229368
|
if (existsSync3(agentsDir)) {
|
|
229133
229369
|
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
229134
229370
|
if (!entry.isDirectory()) continue;
|
|
229135
|
-
const authPath =
|
|
229371
|
+
const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
|
|
229136
229372
|
if (!existsSync3(authPath)) continue;
|
|
229137
229373
|
try {
|
|
229138
229374
|
const store = JSON.parse(readTextFileSync(authPath));
|