@blockrun/clawrouter 0.12.63 → 0.12.65
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 +50 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.js +57 -16
- 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 +3 -2
- package/scripts/reinstall.sh +31 -6
- package/scripts/update.sh +6 -1
- package/docs/assets/blockrun-248-day-cost-overrun-problem.png +0 -0
- package/docs/assets/blockrun-clawrouter-7-layer-token-compression-openclaw.png +0 -0
- package/docs/assets/blockrun-clawrouter-observation-compression-97-percent-token-savings.png +0 -0
- package/docs/assets/blockrun-clawrouter-openclaw-agentic-proxy-architecture.png +0 -0
- package/docs/assets/blockrun-clawrouter-openclaw-automatic-tier-routing-model-selection.png +0 -0
- package/docs/assets/blockrun-clawrouter-openclaw-error-classification-retry-storm-prevention.png +0 -0
- package/docs/assets/blockrun-clawrouter-openclaw-session-memory-journaling-vs-context-compounding.png +0 -0
- package/docs/assets/blockrun-clawrouter-vs-openclaw-standalone-comparison-production-safety.png +0 -0
- package/docs/assets/blockrun-clawrouter-x402-usdc-micropayment-wallet-budget-control.png +0 -0
- package/docs/assets/blockrun-openclaw-inference-layer-blind-spots.png +0 -0
- package/docs/plans/2026-02-03-smart-routing-design.md +0 -267
- package/docs/plans/2026-02-13-e2e-docker-deployment.md +0 -1260
- package/docs/plans/2026-02-28-worker-network.md +0 -947
- package/docs/plans/2026-03-18-error-classification.md +0 -574
- package/docs/plans/2026-03-19-exclude-models.md +0 -538
- 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,11 @@ 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
|
+
});
|
|
49611
49623
|
if (result.isProviderError && !isLastAttempt) {
|
|
49612
49624
|
const isExplicitModelError = !routingDecision;
|
|
49613
49625
|
const isUnknownExplicitModel = isExplicitModelError && /unknown.*model|invalid.*model/i.test(result.errorBody || "");
|
|
@@ -49745,7 +49757,10 @@ data: [DONE]
|
|
|
49745
49757
|
}
|
|
49746
49758
|
}
|
|
49747
49759
|
if (!upstream) {
|
|
49748
|
-
const
|
|
49760
|
+
const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
|
|
49761
|
+
const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
|
|
49762
|
+
console.log(`[ClawRouter] ${structuredMessage}`);
|
|
49763
|
+
const rawErrBody = lastError?.body || structuredMessage;
|
|
49749
49764
|
const errStatus = lastError?.status || 502;
|
|
49750
49765
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
49751
49766
|
if (headersSentEarly) {
|
|
@@ -49804,7 +49819,7 @@ data: [DONE]
|
|
|
49804
49819
|
id: rsp.id ?? `chatcmpl-${Date.now()}`,
|
|
49805
49820
|
object: "chat.completion.chunk",
|
|
49806
49821
|
created: rsp.created ?? Math.floor(Date.now() / 1e3),
|
|
49807
|
-
model: rsp.model
|
|
49822
|
+
model: actualModelUsed || rsp.model || "unknown",
|
|
49808
49823
|
system_fingerprint: null
|
|
49809
49824
|
};
|
|
49810
49825
|
if (rsp.choices && Array.isArray(rsp.choices)) {
|
|
@@ -49919,6 +49934,13 @@ data: [DONE]
|
|
|
49919
49934
|
responseChunks.push(Buffer.from(sseData));
|
|
49920
49935
|
}
|
|
49921
49936
|
}
|
|
49937
|
+
if (routingDecision) {
|
|
49938
|
+
const costComment = `: cost=$${routingDecision.costEstimate.toFixed(4)} savings=${(routingDecision.savings * 100).toFixed(0)}% model=${actualModelUsed} tier=${routingDecision.tier}
|
|
49939
|
+
|
|
49940
|
+
`;
|
|
49941
|
+
safeWrite(res, costComment);
|
|
49942
|
+
responseChunks.push(Buffer.from(costComment));
|
|
49943
|
+
}
|
|
49922
49944
|
safeWrite(res, "data: [DONE]\n\n");
|
|
49923
49945
|
responseChunks.push(Buffer.from("data: [DONE]\n\n"));
|
|
49924
49946
|
res.end();
|
|
@@ -49947,6 +49969,10 @@ data: [DONE]
|
|
|
49947
49969
|
responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
|
|
49948
49970
|
}
|
|
49949
49971
|
}
|
|
49972
|
+
if (routingDecision) {
|
|
49973
|
+
responseHeaders["x-clawrouter-cost"] = routingDecision.costEstimate.toFixed(6);
|
|
49974
|
+
responseHeaders["x-clawrouter-savings"] = `${(routingDecision.savings * 100).toFixed(0)}%`;
|
|
49975
|
+
}
|
|
49950
49976
|
const bodyParts = [];
|
|
49951
49977
|
if (upstream.body) {
|
|
49952
49978
|
const chunks = await readBodyWithTimeout(upstream.body);
|
|
@@ -49977,6 +50003,16 @@ data: [DONE]
|
|
|
49977
50003
|
}
|
|
49978
50004
|
budgetDowngradeNotice = void 0;
|
|
49979
50005
|
}
|
|
50006
|
+
if (actualModelUsed && responseBody.length > 0) {
|
|
50007
|
+
try {
|
|
50008
|
+
const parsed = JSON.parse(responseBody.toString());
|
|
50009
|
+
if (parsed.model !== void 0) {
|
|
50010
|
+
parsed.model = actualModelUsed;
|
|
50011
|
+
responseBody = Buffer.from(JSON.stringify(parsed));
|
|
50012
|
+
}
|
|
50013
|
+
} catch {
|
|
50014
|
+
}
|
|
50015
|
+
}
|
|
49980
50016
|
if (budgetDowngradeHeaderMode) {
|
|
49981
50017
|
responseHeaders["x-clawrouter-budget-downgrade"] = "1";
|
|
49982
50018
|
responseHeaders["x-clawrouter-budget-mode"] = budgetDowngradeHeaderMode;
|
|
@@ -50802,7 +50838,9 @@ async function startProxyInBackground(api) {
|
|
|
50802
50838
|
activeProxyHandle = proxy;
|
|
50803
50839
|
const startupExclusions = loadExcludeList();
|
|
50804
50840
|
if (startupExclusions.size > 0) {
|
|
50805
|
-
api.logger.info(
|
|
50841
|
+
api.logger.info(
|
|
50842
|
+
`Model exclusions active (${startupExclusions.size}): ${[...startupExclusions].join(", ")}`
|
|
50843
|
+
);
|
|
50806
50844
|
}
|
|
50807
50845
|
api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
|
|
50808
50846
|
api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
|
|
@@ -50891,7 +50929,10 @@ Use /exclude remove <model> to unblock.`
|
|
|
50891
50929
|
}
|
|
50892
50930
|
if (subcommand === "add") {
|
|
50893
50931
|
if (!modelArg) {
|
|
50894
|
-
return {
|
|
50932
|
+
return {
|
|
50933
|
+
text: "Usage: /exclude add <model>\nExample: /exclude add nvidia/gpt-oss-120b",
|
|
50934
|
+
isError: true
|
|
50935
|
+
};
|
|
50895
50936
|
}
|
|
50896
50937
|
const resolved = addExclusion(modelArg);
|
|
50897
50938
|
const list = loadExcludeList();
|