@blockrun/clawrouter 0.12.64 → 0.12.66
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 +55 -55
- package/dist/cli.js +70 -25
- package/dist/cli.js.map +1 -1
- package/dist/index.js +77 -27
- package/dist/index.js.map +1 -1
- package/docs/anthropic-cost-savings.md +90 -85
- package/docs/architecture.md +12 -12
- package/docs/{blog-openclaw-cost-overruns.md → clawrouter-cuts-llm-api-costs-500x.md} +27 -27
- package/docs/clawrouter-vs-openrouter-llm-routing-comparison.md +280 -0
- package/docs/configuration.md +2 -2
- package/docs/image-generation.md +39 -39
- package/docs/{blog-benchmark-2026-03.md → llm-router-benchmark-46-models-sub-1ms-routing.md} +61 -64
- package/docs/routing-profiles.md +6 -6
- package/docs/{technical-routing-2026-03.md → smart-llm-router-14-dimension-classifier.md} +29 -28
- package/docs/worker-network.md +438 -347
- package/package.json +1 -1
- package/scripts/reinstall.sh +31 -6
- package/scripts/update.sh +6 -1
- package/docs/vs-openrouter.md +0 -157
package/dist/index.js
CHANGED
|
@@ -42464,7 +42464,19 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
42464
42464
|
return async (input, init) => {
|
|
42465
42465
|
const request = new Request(input, init);
|
|
42466
42466
|
const urlPath = new URL(request.url).pathname;
|
|
42467
|
-
|
|
42467
|
+
let requestModel = "";
|
|
42468
|
+
if (init?.body) {
|
|
42469
|
+
try {
|
|
42470
|
+
const bodyStr = init.body instanceof Uint8Array ? new TextDecoder().decode(init.body) : typeof init.body === "string" ? init.body : "";
|
|
42471
|
+
if (bodyStr) {
|
|
42472
|
+
const parsed = JSON.parse(bodyStr);
|
|
42473
|
+
requestModel = parsed.model ?? "";
|
|
42474
|
+
}
|
|
42475
|
+
} catch {
|
|
42476
|
+
}
|
|
42477
|
+
}
|
|
42478
|
+
const cacheKey2 = `${urlPath}:${requestModel}`;
|
|
42479
|
+
const cached = !options?.skipPreAuth ? cache2.get(cacheKey2) : void 0;
|
|
42468
42480
|
if (cached && Date.now() - cached.cachedAt < ttlMs) {
|
|
42469
42481
|
try {
|
|
42470
42482
|
const payload2 = await client.createPaymentPayload(cached.paymentRequired);
|
|
@@ -42477,9 +42489,9 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
42477
42489
|
if (response2.status !== 402) {
|
|
42478
42490
|
return response2;
|
|
42479
42491
|
}
|
|
42480
|
-
cache2.delete(
|
|
42492
|
+
cache2.delete(cacheKey2);
|
|
42481
42493
|
} catch {
|
|
42482
|
-
cache2.delete(
|
|
42494
|
+
cache2.delete(cacheKey2);
|
|
42483
42495
|
}
|
|
42484
42496
|
}
|
|
42485
42497
|
const clonedRequest = request.clone();
|
|
@@ -42502,7 +42514,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
42502
42514
|
} catch {
|
|
42503
42515
|
}
|
|
42504
42516
|
paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
|
|
42505
|
-
cache2.set(
|
|
42517
|
+
cache2.set(cacheKey2, { paymentRequired, cachedAt: Date.now() });
|
|
42506
42518
|
} catch (error) {
|
|
42507
42519
|
throw new Error(
|
|
42508
42520
|
`Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
@@ -46862,12 +46874,7 @@ async function checkForUpdates() {
|
|
|
46862
46874
|
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
46863
46875
|
import { join as join7, dirname as dirname2 } from "path";
|
|
46864
46876
|
import { homedir as homedir4 } from "os";
|
|
46865
|
-
var DEFAULT_FILE_PATH = join7(
|
|
46866
|
-
homedir4(),
|
|
46867
|
-
".openclaw",
|
|
46868
|
-
"blockrun",
|
|
46869
|
-
"exclude-models.json"
|
|
46870
|
-
);
|
|
46877
|
+
var DEFAULT_FILE_PATH = join7(homedir4(), ".openclaw", "blockrun", "exclude-models.json");
|
|
46871
46878
|
function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
|
|
46872
46879
|
try {
|
|
46873
46880
|
const raw = readFileSync(filePath, "utf-8");
|
|
@@ -47272,8 +47279,7 @@ function categorizeError(status, body) {
|
|
|
47272
47279
|
if (status === 401) return "auth_failure";
|
|
47273
47280
|
if (status === 402) return "payment_error";
|
|
47274
47281
|
if (status === 403) {
|
|
47275
|
-
if (/plan.*limit|quota.*exceeded|subscription|allowance/i.test(body))
|
|
47276
|
-
return "quota_exceeded";
|
|
47282
|
+
if (/plan.*limit|quota.*exceeded|subscription|allowance/i.test(body)) return "quota_exceeded";
|
|
47277
47283
|
return "auth_failure";
|
|
47278
47284
|
}
|
|
47279
47285
|
if (status === 429) return "rate_limited";
|
|
@@ -49560,6 +49566,7 @@ data: [DONE]
|
|
|
49560
49566
|
let upstream;
|
|
49561
49567
|
let lastError;
|
|
49562
49568
|
let actualModelUsed = modelId;
|
|
49569
|
+
const failedAttempts = [];
|
|
49563
49570
|
for (let i = 0; i < modelsToTry.length; i++) {
|
|
49564
49571
|
const tryModel = modelsToTry[i];
|
|
49565
49572
|
const isLastAttempt = i === modelsToTry.length - 1;
|
|
@@ -49608,6 +49615,31 @@ data: [DONE]
|
|
|
49608
49615
|
body: result.errorBody || "Unknown error",
|
|
49609
49616
|
status: result.errorStatus || 500
|
|
49610
49617
|
};
|
|
49618
|
+
failedAttempts.push({
|
|
49619
|
+
model: tryModel,
|
|
49620
|
+
reason: result.errorCategory || `HTTP ${result.errorStatus || 500}`,
|
|
49621
|
+
status: result.errorStatus || 500
|
|
49622
|
+
});
|
|
49623
|
+
const isPaymentErr = /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test(
|
|
49624
|
+
result.errorBody || ""
|
|
49625
|
+
);
|
|
49626
|
+
if (isPaymentErr && tryModel !== FREE_MODEL && !isLastAttempt) {
|
|
49627
|
+
failedAttempts.push({
|
|
49628
|
+
...failedAttempts[failedAttempts.length - 1],
|
|
49629
|
+
reason: "payment_error"
|
|
49630
|
+
});
|
|
49631
|
+
const freeIdx = modelsToTry.indexOf(FREE_MODEL);
|
|
49632
|
+
if (freeIdx > i + 1) {
|
|
49633
|
+
console.log(`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
|
|
49634
|
+
i = freeIdx - 1;
|
|
49635
|
+
continue;
|
|
49636
|
+
}
|
|
49637
|
+
if (freeIdx === -1) {
|
|
49638
|
+
modelsToTry.push(FREE_MODEL);
|
|
49639
|
+
console.log(`[ClawRouter] Payment error \u2014 appending free model: ${FREE_MODEL}`);
|
|
49640
|
+
continue;
|
|
49641
|
+
}
|
|
49642
|
+
}
|
|
49611
49643
|
if (result.isProviderError && !isLastAttempt) {
|
|
49612
49644
|
const isExplicitModelError = !routingDecision;
|
|
49613
49645
|
const isUnknownExplicitModel = isExplicitModelError && /unknown.*model|invalid.*model/i.test(result.errorBody || "");
|
|
@@ -49685,17 +49717,6 @@ data: [DONE]
|
|
|
49685
49717
|
`[ClawRouter] \u{1F511} ${errorCat === "auth_failure" ? "Auth failure" : "Quota exceeded"} for ${tryModel} \u2014 check provider config`
|
|
49686
49718
|
);
|
|
49687
49719
|
}
|
|
49688
|
-
const isPaymentErr = /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test(
|
|
49689
|
-
result.errorBody || ""
|
|
49690
|
-
);
|
|
49691
|
-
if (isPaymentErr && tryModel !== FREE_MODEL) {
|
|
49692
|
-
const freeIdx = modelsToTry.indexOf(FREE_MODEL);
|
|
49693
|
-
if (freeIdx > i + 1) {
|
|
49694
|
-
console.log(`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
|
|
49695
|
-
i = freeIdx - 1;
|
|
49696
|
-
continue;
|
|
49697
|
-
}
|
|
49698
|
-
}
|
|
49699
49720
|
console.log(
|
|
49700
49721
|
`[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`
|
|
49701
49722
|
);
|
|
@@ -49745,7 +49766,10 @@ data: [DONE]
|
|
|
49745
49766
|
}
|
|
49746
49767
|
}
|
|
49747
49768
|
if (!upstream) {
|
|
49748
|
-
const
|
|
49769
|
+
const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
|
|
49770
|
+
const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
|
|
49771
|
+
console.log(`[ClawRouter] ${structuredMessage}`);
|
|
49772
|
+
const rawErrBody = lastError?.body || structuredMessage;
|
|
49749
49773
|
const errStatus = lastError?.status || 502;
|
|
49750
49774
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
49751
49775
|
if (headersSentEarly) {
|
|
@@ -49804,7 +49828,7 @@ data: [DONE]
|
|
|
49804
49828
|
id: rsp.id ?? `chatcmpl-${Date.now()}`,
|
|
49805
49829
|
object: "chat.completion.chunk",
|
|
49806
49830
|
created: rsp.created ?? Math.floor(Date.now() / 1e3),
|
|
49807
|
-
model: rsp.model
|
|
49831
|
+
model: actualModelUsed || rsp.model || "unknown",
|
|
49808
49832
|
system_fingerprint: null
|
|
49809
49833
|
};
|
|
49810
49834
|
if (rsp.choices && Array.isArray(rsp.choices)) {
|
|
@@ -49919,6 +49943,13 @@ data: [DONE]
|
|
|
49919
49943
|
responseChunks.push(Buffer.from(sseData));
|
|
49920
49944
|
}
|
|
49921
49945
|
}
|
|
49946
|
+
if (routingDecision) {
|
|
49947
|
+
const costComment = `: cost=$${routingDecision.costEstimate.toFixed(4)} savings=${(routingDecision.savings * 100).toFixed(0)}% model=${actualModelUsed} tier=${routingDecision.tier}
|
|
49948
|
+
|
|
49949
|
+
`;
|
|
49950
|
+
safeWrite(res, costComment);
|
|
49951
|
+
responseChunks.push(Buffer.from(costComment));
|
|
49952
|
+
}
|
|
49922
49953
|
safeWrite(res, "data: [DONE]\n\n");
|
|
49923
49954
|
responseChunks.push(Buffer.from("data: [DONE]\n\n"));
|
|
49924
49955
|
res.end();
|
|
@@ -49947,6 +49978,10 @@ data: [DONE]
|
|
|
49947
49978
|
responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
|
|
49948
49979
|
}
|
|
49949
49980
|
}
|
|
49981
|
+
if (routingDecision) {
|
|
49982
|
+
responseHeaders["x-clawrouter-cost"] = routingDecision.costEstimate.toFixed(6);
|
|
49983
|
+
responseHeaders["x-clawrouter-savings"] = `${(routingDecision.savings * 100).toFixed(0)}%`;
|
|
49984
|
+
}
|
|
49950
49985
|
const bodyParts = [];
|
|
49951
49986
|
if (upstream.body) {
|
|
49952
49987
|
const chunks = await readBodyWithTimeout(upstream.body);
|
|
@@ -49977,6 +50012,16 @@ data: [DONE]
|
|
|
49977
50012
|
}
|
|
49978
50013
|
budgetDowngradeNotice = void 0;
|
|
49979
50014
|
}
|
|
50015
|
+
if (actualModelUsed && responseBody.length > 0) {
|
|
50016
|
+
try {
|
|
50017
|
+
const parsed = JSON.parse(responseBody.toString());
|
|
50018
|
+
if (parsed.model !== void 0) {
|
|
50019
|
+
parsed.model = actualModelUsed;
|
|
50020
|
+
responseBody = Buffer.from(JSON.stringify(parsed));
|
|
50021
|
+
}
|
|
50022
|
+
} catch {
|
|
50023
|
+
}
|
|
50024
|
+
}
|
|
49980
50025
|
if (budgetDowngradeHeaderMode) {
|
|
49981
50026
|
responseHeaders["x-clawrouter-budget-downgrade"] = "1";
|
|
49982
50027
|
responseHeaders["x-clawrouter-budget-mode"] = budgetDowngradeHeaderMode;
|
|
@@ -50802,7 +50847,9 @@ async function startProxyInBackground(api) {
|
|
|
50802
50847
|
activeProxyHandle = proxy;
|
|
50803
50848
|
const startupExclusions = loadExcludeList();
|
|
50804
50849
|
if (startupExclusions.size > 0) {
|
|
50805
|
-
api.logger.info(
|
|
50850
|
+
api.logger.info(
|
|
50851
|
+
`Model exclusions active (${startupExclusions.size}): ${[...startupExclusions].join(", ")}`
|
|
50852
|
+
);
|
|
50806
50853
|
}
|
|
50807
50854
|
api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
|
|
50808
50855
|
api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
|
|
@@ -50891,7 +50938,10 @@ Use /exclude remove <model> to unblock.`
|
|
|
50891
50938
|
}
|
|
50892
50939
|
if (subcommand === "add") {
|
|
50893
50940
|
if (!modelArg) {
|
|
50894
|
-
return {
|
|
50941
|
+
return {
|
|
50942
|
+
text: "Usage: /exclude add <model>\nExample: /exclude add nvidia/gpt-oss-120b",
|
|
50943
|
+
isError: true
|
|
50944
|
+
};
|
|
50895
50945
|
}
|
|
50896
50946
|
const resolved = addExclusion(modelArg);
|
|
50897
50947
|
const list = loadExcludeList();
|