@blockrun/clawrouter 0.12.60 → 0.12.62
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 +19 -0
- package/dist/cli.js +80 -24
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +204 -40
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/reinstall.sh +8 -4
- package/scripts/update.sh +8 -4
package/README.md
CHANGED
|
@@ -248,6 +248,10 @@ USDC stays in your wallet until spent — non-custodial. Price is visible in the
|
|
|
248
248
|
/chain solana # Alias for /wallet solana
|
|
249
249
|
/stats # View usage and savings
|
|
250
250
|
/stats clear # Reset usage statistics
|
|
251
|
+
/exclude # Show excluded models
|
|
252
|
+
/exclude add <model> # Block a model from routing (aliases work: "grok-4", "free")
|
|
253
|
+
/exclude remove <model> # Unblock a model
|
|
254
|
+
/exclude clear # Remove all exclusions
|
|
251
255
|
```
|
|
252
256
|
|
|
253
257
|
**Fund your wallet:**
|
|
@@ -289,6 +293,21 @@ For basic usage, no configuration needed. For advanced options:
|
|
|
289
293
|
|
|
290
294
|
**Full reference:** [docs/configuration.md](docs/configuration.md)
|
|
291
295
|
|
|
296
|
+
### Model Exclusion
|
|
297
|
+
|
|
298
|
+
Block specific models from being routed to. Useful if a model doesn't follow your agent instructions or you want to control costs.
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
/exclude add nvidia/gpt-oss-120b # Block the free model
|
|
302
|
+
/exclude add grok-4 # Aliases work — blocks all grok-4 variants
|
|
303
|
+
/exclude add gpt-5.4 # Skip expensive models
|
|
304
|
+
/exclude # Show current exclusions
|
|
305
|
+
/exclude remove grok-4 # Unblock a model
|
|
306
|
+
/exclude clear # Remove all exclusions
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Exclusions persist across restarts (`~/.openclaw/blockrun/exclude-models.json`). If all models in a tier are excluded, the safety net ignores the filter so routing never breaks.
|
|
310
|
+
|
|
292
311
|
---
|
|
293
312
|
|
|
294
313
|
## Troubleshooting
|
package/dist/cli.js
CHANGED
|
@@ -25522,10 +25522,10 @@ var init_client = __esm({
|
|
|
25522
25522
|
// src/proxy.ts
|
|
25523
25523
|
import { createServer } from "http";
|
|
25524
25524
|
import { finished } from "stream";
|
|
25525
|
-
import { homedir as
|
|
25526
|
-
import { join as
|
|
25525
|
+
import { homedir as homedir5 } from "os";
|
|
25526
|
+
import { join as join8 } from "path";
|
|
25527
25527
|
import { mkdir as mkdir3, writeFile as writeFile2, readFile, stat as fsStat } from "fs/promises";
|
|
25528
|
-
import { readFileSync, existsSync } from "fs";
|
|
25528
|
+
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
25529
25529
|
|
|
25530
25530
|
// node_modules/viem/_esm/utils/getAction.js
|
|
25531
25531
|
function getAction(client, actionFn, name) {
|
|
@@ -39019,6 +39019,11 @@ function filterByVision(models, hasVision, supportsVision2) {
|
|
|
39019
39019
|
const filtered = models.filter(supportsVision2);
|
|
39020
39020
|
return filtered.length > 0 ? filtered : models;
|
|
39021
39021
|
}
|
|
39022
|
+
function filterByExcludeList(models, excludeList) {
|
|
39023
|
+
if (excludeList.size === 0) return models;
|
|
39024
|
+
const filtered = models.filter((m) => !excludeList.has(m));
|
|
39025
|
+
return filtered.length > 0 ? filtered : models;
|
|
39026
|
+
}
|
|
39022
39027
|
function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
|
|
39023
39028
|
const fullChain = getFallbackChain(tier, tierConfigs);
|
|
39024
39029
|
const filtered = fullChain.filter((modelId) => {
|
|
@@ -40454,7 +40459,9 @@ var MODEL_ALIASES = {
|
|
|
40454
40459
|
nvidia: "nvidia/gpt-oss-120b",
|
|
40455
40460
|
"gpt-120b": "nvidia/gpt-oss-120b",
|
|
40456
40461
|
// MiniMax
|
|
40457
|
-
minimax: "minimax/minimax-m2.
|
|
40462
|
+
minimax: "minimax/minimax-m2.7",
|
|
40463
|
+
"minimax-m2.7": "minimax/minimax-m2.7",
|
|
40464
|
+
"minimax-m2.5": "minimax/minimax-m2.5",
|
|
40458
40465
|
// Z.AI GLM-5
|
|
40459
40466
|
glm: "zai/glm-5",
|
|
40460
40467
|
"glm-5": "zai/glm-5",
|
|
@@ -40960,6 +40967,18 @@ var BLOCKRUN_MODELS = [
|
|
|
40960
40967
|
toolCalling: true
|
|
40961
40968
|
},
|
|
40962
40969
|
// MiniMax
|
|
40970
|
+
{
|
|
40971
|
+
id: "minimax/minimax-m2.7",
|
|
40972
|
+
name: "MiniMax M2.7",
|
|
40973
|
+
version: "m2.7",
|
|
40974
|
+
inputPrice: 0.3,
|
|
40975
|
+
outputPrice: 1.2,
|
|
40976
|
+
contextWindow: 204800,
|
|
40977
|
+
maxOutput: 16384,
|
|
40978
|
+
reasoning: true,
|
|
40979
|
+
agentic: true,
|
|
40980
|
+
toolCalling: true
|
|
40981
|
+
},
|
|
40963
40982
|
{
|
|
40964
40983
|
id: "minimax/minimax-m2.5",
|
|
40965
40984
|
name: "MiniMax M2.5",
|
|
@@ -46344,6 +46363,29 @@ async function checkForUpdates() {
|
|
|
46344
46363
|
}
|
|
46345
46364
|
}
|
|
46346
46365
|
|
|
46366
|
+
// src/exclude-models.ts
|
|
46367
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
46368
|
+
import { join as join7, dirname as dirname2 } from "path";
|
|
46369
|
+
import { homedir as homedir4 } from "os";
|
|
46370
|
+
var DEFAULT_FILE_PATH = join7(
|
|
46371
|
+
homedir4(),
|
|
46372
|
+
".openclaw",
|
|
46373
|
+
"blockrun",
|
|
46374
|
+
"exclude-models.json"
|
|
46375
|
+
);
|
|
46376
|
+
function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
|
|
46377
|
+
try {
|
|
46378
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
46379
|
+
const arr = JSON.parse(raw);
|
|
46380
|
+
if (Array.isArray(arr)) {
|
|
46381
|
+
return new Set(arr.filter((x) => typeof x === "string"));
|
|
46382
|
+
}
|
|
46383
|
+
return /* @__PURE__ */ new Set();
|
|
46384
|
+
} catch {
|
|
46385
|
+
return /* @__PURE__ */ new Set();
|
|
46386
|
+
}
|
|
46387
|
+
}
|
|
46388
|
+
|
|
46347
46389
|
// src/config.ts
|
|
46348
46390
|
var DEFAULT_PORT = 8402;
|
|
46349
46391
|
var PROXY_PORT = (() => {
|
|
@@ -46528,7 +46570,7 @@ ${lines.join("\n")}`;
|
|
|
46528
46570
|
// src/proxy.ts
|
|
46529
46571
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
46530
46572
|
var BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
46531
|
-
var IMAGE_DIR =
|
|
46573
|
+
var IMAGE_DIR = join8(homedir5(), ".openclaw", "blockrun", "images");
|
|
46532
46574
|
var AUTO_MODEL = "blockrun/auto";
|
|
46533
46575
|
var ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
46534
46576
|
"blockrun/free",
|
|
@@ -47196,7 +47238,7 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
|
|
|
47196
47238
|
});
|
|
47197
47239
|
}
|
|
47198
47240
|
function readImageFileAsDataUri(filePath) {
|
|
47199
|
-
const resolved = filePath.startsWith("~/") ?
|
|
47241
|
+
const resolved = filePath.startsWith("~/") ? join8(homedir5(), filePath.slice(2)) : filePath;
|
|
47200
47242
|
if (!existsSync(resolved)) {
|
|
47201
47243
|
throw new Error(`Image file not found: ${resolved}`);
|
|
47202
47244
|
}
|
|
@@ -47208,7 +47250,7 @@ function readImageFileAsDataUri(filePath) {
|
|
|
47208
47250
|
webp: "image/webp"
|
|
47209
47251
|
};
|
|
47210
47252
|
const mime = mimeMap[ext] ?? "image/png";
|
|
47211
|
-
const data =
|
|
47253
|
+
const data = readFileSync2(resolved);
|
|
47212
47254
|
return `data:${mime};base64,${data.toString("base64")}`;
|
|
47213
47255
|
}
|
|
47214
47256
|
async function uploadDataUriToHost(dataUri) {
|
|
@@ -47326,7 +47368,9 @@ async function startProxy(options) {
|
|
|
47326
47368
|
skipPreAuth: paymentChain === "solana"
|
|
47327
47369
|
});
|
|
47328
47370
|
let balanceMonitor;
|
|
47329
|
-
if (
|
|
47371
|
+
if (options._balanceMonitorOverride) {
|
|
47372
|
+
balanceMonitor = options._balanceMonitorOverride;
|
|
47373
|
+
} else if (paymentChain === "solana" && solanaAddress) {
|
|
47330
47374
|
const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
|
|
47331
47375
|
balanceMonitor = new SolanaBalanceMonitor2(solanaAddress);
|
|
47332
47376
|
} else {
|
|
@@ -47451,7 +47495,7 @@ async function startProxy(options) {
|
|
|
47451
47495
|
res.end("Bad request");
|
|
47452
47496
|
return;
|
|
47453
47497
|
}
|
|
47454
|
-
const filePath =
|
|
47498
|
+
const filePath = join8(IMAGE_DIR, filename);
|
|
47455
47499
|
try {
|
|
47456
47500
|
const s3 = await fsStat(filePath);
|
|
47457
47501
|
if (!s3.isFile()) throw new Error("not a file");
|
|
@@ -47510,7 +47554,7 @@ async function startProxy(options) {
|
|
|
47510
47554
|
const [, mimeType, b64] = dataUriMatch;
|
|
47511
47555
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
47512
47556
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
47513
|
-
await writeFile2(
|
|
47557
|
+
await writeFile2(join8(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
47514
47558
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
47515
47559
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
47516
47560
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -47521,7 +47565,7 @@ async function startProxy(options) {
|
|
|
47521
47565
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
47522
47566
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
47523
47567
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
47524
|
-
await writeFile2(
|
|
47568
|
+
await writeFile2(join8(IMAGE_DIR, filename), buf);
|
|
47525
47569
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
47526
47570
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
47527
47571
|
}
|
|
@@ -47610,7 +47654,7 @@ async function startProxy(options) {
|
|
|
47610
47654
|
const [, mimeType, b64] = dataUriMatch;
|
|
47611
47655
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
47612
47656
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
47613
|
-
await writeFile2(
|
|
47657
|
+
await writeFile2(join8(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
47614
47658
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
47615
47659
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
47616
47660
|
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
@@ -47621,7 +47665,7 @@ async function startProxy(options) {
|
|
|
47621
47665
|
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
47622
47666
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
47623
47667
|
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
47624
|
-
await writeFile2(
|
|
47668
|
+
await writeFile2(join8(IMAGE_DIR, filename), buf);
|
|
47625
47669
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
47626
47670
|
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
47627
47671
|
}
|
|
@@ -47933,6 +47977,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
47933
47977
|
let budgetDowngradeHeaderMode;
|
|
47934
47978
|
let accumulatedContent = "";
|
|
47935
47979
|
let responseInputTokens;
|
|
47980
|
+
let responseOutputTokens;
|
|
47936
47981
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
47937
47982
|
const sessionId = getSessionId(req.headers);
|
|
47938
47983
|
let effectiveSessionId = sessionId;
|
|
@@ -48866,6 +48911,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
48866
48911
|
const timeoutId = setTimeout(() => globalController.abort(), timeoutMs);
|
|
48867
48912
|
try {
|
|
48868
48913
|
let modelsToTry;
|
|
48914
|
+
const excludeList = options.excludeModels ?? loadExcludeList();
|
|
48869
48915
|
if (routingDecision) {
|
|
48870
48916
|
const estimatedInputTokens = Math.ceil(body.length / 4);
|
|
48871
48917
|
const estimatedTotalTokens = estimatedInputTokens + maxTokens;
|
|
@@ -48883,8 +48929,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
48883
48929
|
`[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
|
|
48884
48930
|
);
|
|
48885
48931
|
}
|
|
48886
|
-
|
|
48887
|
-
const
|
|
48932
|
+
const excludeFiltered = filterByExcludeList(contextFiltered, excludeList);
|
|
48933
|
+
const excludeExcluded = contextFiltered.filter((m) => !excludeFiltered.includes(m));
|
|
48934
|
+
if (excludeExcluded.length > 0) {
|
|
48935
|
+
console.log(
|
|
48936
|
+
`[ClawRouter] Exclude filter: excluded ${excludeExcluded.join(", ")} (user preference)`
|
|
48937
|
+
);
|
|
48938
|
+
}
|
|
48939
|
+
let toolFiltered = filterByToolCalling(excludeFiltered, hasTools, supportsToolCalling);
|
|
48940
|
+
const toolExcluded = excludeFiltered.filter((m) => !toolFiltered.includes(m));
|
|
48888
48941
|
if (toolExcluded.length > 0) {
|
|
48889
48942
|
console.log(
|
|
48890
48943
|
`[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
|
|
@@ -48917,7 +48970,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
48917
48970
|
} else {
|
|
48918
48971
|
modelsToTry = modelId ? [modelId] : [];
|
|
48919
48972
|
}
|
|
48920
|
-
if (!hasTools && !modelsToTry.includes(FREE_MODEL)) {
|
|
48973
|
+
if (!hasTools && !modelsToTry.includes(FREE_MODEL) && !excludeList.has(FREE_MODEL)) {
|
|
48921
48974
|
modelsToTry.push(FREE_MODEL);
|
|
48922
48975
|
}
|
|
48923
48976
|
if (options.maxCostPerRunUsd && effectiveSessionId && !isFreeModel && (options.maxCostPerRunMode ?? "graceful") === "graceful") {
|
|
@@ -49225,6 +49278,7 @@ data: [DONE]
|
|
|
49225
49278
|
if (rsp.usage && typeof rsp.usage === "object") {
|
|
49226
49279
|
const u = rsp.usage;
|
|
49227
49280
|
if (typeof u.prompt_tokens === "number") responseInputTokens = u.prompt_tokens;
|
|
49281
|
+
if (typeof u.completion_tokens === "number") responseOutputTokens = u.completion_tokens;
|
|
49228
49282
|
}
|
|
49229
49283
|
const baseChunk = {
|
|
49230
49284
|
id: rsp.id ?? `chatcmpl-${Date.now()}`,
|
|
@@ -49438,6 +49492,8 @@ data: [DONE]
|
|
|
49438
49492
|
if (rspJson.usage && typeof rspJson.usage === "object") {
|
|
49439
49493
|
if (typeof rspJson.usage.prompt_tokens === "number")
|
|
49440
49494
|
responseInputTokens = rspJson.usage.prompt_tokens;
|
|
49495
|
+
if (typeof rspJson.usage.completion_tokens === "number")
|
|
49496
|
+
responseOutputTokens = rspJson.usage.completion_tokens;
|
|
49441
49497
|
}
|
|
49442
49498
|
} catch {
|
|
49443
49499
|
}
|
|
@@ -49470,25 +49526,25 @@ data: [DONE]
|
|
|
49470
49526
|
}
|
|
49471
49527
|
const logModel = routingDecision?.model ?? modelId;
|
|
49472
49528
|
if (logModel) {
|
|
49473
|
-
const
|
|
49529
|
+
const actualInputTokens = responseInputTokens ?? Math.ceil(body.length / 4);
|
|
49530
|
+
const actualOutputTokens = responseOutputTokens ?? maxTokens;
|
|
49474
49531
|
const accurateCosts = calculateModelCost(
|
|
49475
49532
|
logModel,
|
|
49476
49533
|
routerOpts.modelPricing,
|
|
49477
|
-
|
|
49478
|
-
|
|
49534
|
+
actualInputTokens,
|
|
49535
|
+
actualOutputTokens,
|
|
49479
49536
|
routingProfile ?? void 0
|
|
49480
49537
|
);
|
|
49481
|
-
const costWithBuffer = accurateCosts.costEstimate * 1.2;
|
|
49482
|
-
const baselineWithBuffer = accurateCosts.baselineCost * 1.2;
|
|
49483
49538
|
const entry = {
|
|
49484
49539
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
49485
49540
|
model: logModel,
|
|
49486
49541
|
tier: routingDecision?.tier ?? "DIRECT",
|
|
49487
|
-
cost:
|
|
49488
|
-
baselineCost:
|
|
49542
|
+
cost: accurateCosts.costEstimate,
|
|
49543
|
+
baselineCost: accurateCosts.baselineCost,
|
|
49489
49544
|
savings: accurateCosts.savings,
|
|
49490
49545
|
latencyMs: Date.now() - startTime,
|
|
49491
|
-
...responseInputTokens !== void 0 && { inputTokens: responseInputTokens }
|
|
49546
|
+
...responseInputTokens !== void 0 && { inputTokens: responseInputTokens },
|
|
49547
|
+
...responseOutputTokens !== void 0 && { outputTokens: responseOutputTokens }
|
|
49492
49548
|
};
|
|
49493
49549
|
logUsage(entry).catch(() => {
|
|
49494
49550
|
});
|