@blockrun/clawrouter 0.5.8 → 0.6.1
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 +42 -454
- package/dist/index.js +128 -72
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/scripts/reinstall.sh +177 -0
- package/scripts/uninstall.sh +135 -0
package/dist/index.js
CHANGED
|
@@ -1996,9 +1996,13 @@ var InsufficientFundsError = class extends Error {
|
|
|
1996
1996
|
requiredUSD;
|
|
1997
1997
|
walletAddress;
|
|
1998
1998
|
constructor(opts) {
|
|
1999
|
-
|
|
2000
|
-
`Insufficient
|
|
2001
|
-
|
|
1999
|
+
const msg = [
|
|
2000
|
+
`Insufficient balance. Current: ${opts.currentBalanceUSD}, Required: ${opts.requiredUSD}`,
|
|
2001
|
+
`Options:`,
|
|
2002
|
+
` 1. Fund wallet: ${opts.walletAddress}`,
|
|
2003
|
+
` 2. Use free model: /model free`
|
|
2004
|
+
].join("\n");
|
|
2005
|
+
super(msg);
|
|
2002
2006
|
this.name = "InsufficientFundsError";
|
|
2003
2007
|
this.currentBalanceUSD = opts.currentBalanceUSD;
|
|
2004
2008
|
this.requiredUSD = opts.requiredUSD;
|
|
@@ -2009,7 +2013,14 @@ var EmptyWalletError = class extends Error {
|
|
|
2009
2013
|
code = "EMPTY_WALLET";
|
|
2010
2014
|
walletAddress;
|
|
2011
2015
|
constructor(walletAddress) {
|
|
2012
|
-
|
|
2016
|
+
const msg = [
|
|
2017
|
+
`No USDC balance.`,
|
|
2018
|
+
`Options:`,
|
|
2019
|
+
` 1. Fund wallet: ${walletAddress}`,
|
|
2020
|
+
` 2. Use free model: /model free`,
|
|
2021
|
+
` 3. Uninstall: bash ~/.openclaw/extensions/clawrouter/scripts/uninstall.sh`
|
|
2022
|
+
].join("\n");
|
|
2023
|
+
super(msg);
|
|
2013
2024
|
this.name = "EmptyWalletError";
|
|
2014
2025
|
this.walletAddress = walletAddress;
|
|
2015
2026
|
}
|
|
@@ -2384,6 +2395,28 @@ function isProviderError(status, body) {
|
|
|
2384
2395
|
}
|
|
2385
2396
|
return PROVIDER_ERROR_PATTERNS.some((pattern) => pattern.test(body));
|
|
2386
2397
|
}
|
|
2398
|
+
var VALID_ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant", "tool", "function"]);
|
|
2399
|
+
var ROLE_MAPPINGS = {
|
|
2400
|
+
developer: "system",
|
|
2401
|
+
// OpenAI's newer API uses "developer" for system messages
|
|
2402
|
+
model: "assistant"
|
|
2403
|
+
// Some APIs use "model" instead of "assistant"
|
|
2404
|
+
};
|
|
2405
|
+
function normalizeMessageRoles(messages) {
|
|
2406
|
+
if (!messages || messages.length === 0) return messages;
|
|
2407
|
+
let hasChanges = false;
|
|
2408
|
+
const normalized = messages.map((msg) => {
|
|
2409
|
+
if (VALID_ROLES.has(msg.role)) return msg;
|
|
2410
|
+
const mappedRole = ROLE_MAPPINGS[msg.role];
|
|
2411
|
+
if (mappedRole) {
|
|
2412
|
+
hasChanges = true;
|
|
2413
|
+
return { ...msg, role: mappedRole };
|
|
2414
|
+
}
|
|
2415
|
+
hasChanges = true;
|
|
2416
|
+
return { ...msg, role: "user" };
|
|
2417
|
+
});
|
|
2418
|
+
return hasChanges ? normalized : messages;
|
|
2419
|
+
}
|
|
2387
2420
|
function normalizeMessagesForGoogle(messages) {
|
|
2388
2421
|
if (!messages || messages.length === 0) return messages;
|
|
2389
2422
|
let firstNonSystemIdx = -1;
|
|
@@ -2409,6 +2442,18 @@ function normalizeMessagesForGoogle(messages) {
|
|
|
2409
2442
|
function isGoogleModel(modelId) {
|
|
2410
2443
|
return modelId.startsWith("google/") || modelId.startsWith("gemini");
|
|
2411
2444
|
}
|
|
2445
|
+
function normalizeMessagesForThinking(messages) {
|
|
2446
|
+
if (!messages || messages.length === 0) return messages;
|
|
2447
|
+
let hasChanges = false;
|
|
2448
|
+
const normalized = messages.map((msg) => {
|
|
2449
|
+
if (msg.role === "assistant" && msg.tool_calls && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.reasoning_content === void 0) {
|
|
2450
|
+
hasChanges = true;
|
|
2451
|
+
return { ...msg, reasoning_content: "" };
|
|
2452
|
+
}
|
|
2453
|
+
return msg;
|
|
2454
|
+
});
|
|
2455
|
+
return hasChanges ? normalized : messages;
|
|
2456
|
+
}
|
|
2412
2457
|
var KIMI_BLOCK_RE = /<[||][^<>]*begin[^<>]*[||]>[\s\S]*?<[||][^<>]*end[^<>]*[||]>/gi;
|
|
2413
2458
|
var KIMI_TOKEN_RE = /<[||][^<>]*[||]>/g;
|
|
2414
2459
|
var THINKING_TAG_RE = /<\s*\/?\s*(?:think(?:ing)?|thought|antthinking)\b[^>]*>/gi;
|
|
@@ -2614,9 +2659,15 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
2614
2659
|
try {
|
|
2615
2660
|
const parsed = JSON.parse(body.toString());
|
|
2616
2661
|
parsed.model = modelId;
|
|
2662
|
+
if (Array.isArray(parsed.messages)) {
|
|
2663
|
+
parsed.messages = normalizeMessageRoles(parsed.messages);
|
|
2664
|
+
}
|
|
2617
2665
|
if (isGoogleModel(modelId) && Array.isArray(parsed.messages)) {
|
|
2618
2666
|
parsed.messages = normalizeMessagesForGoogle(parsed.messages);
|
|
2619
2667
|
}
|
|
2668
|
+
if (parsed.thinking && Array.isArray(parsed.messages)) {
|
|
2669
|
+
parsed.messages = normalizeMessagesForThinking(parsed.messages);
|
|
2670
|
+
}
|
|
2620
2671
|
requestBody = Buffer.from(JSON.stringify(parsed));
|
|
2621
2672
|
} catch {
|
|
2622
2673
|
}
|
|
@@ -2776,42 +2827,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
2776
2827
|
const bufferedCostMicros = estimatedCostMicros * BigInt(Math.ceil(BALANCE_CHECK_BUFFER * 100)) / 100n;
|
|
2777
2828
|
const sufficiency = await balanceMonitor.checkSufficient(bufferedCostMicros);
|
|
2778
2829
|
if (sufficiency.info.isEmpty || !sufficiency.sufficient) {
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
} else {
|
|
2792
|
-
deduplicator.removeInflight(dedupKey);
|
|
2793
|
-
if (sufficiency.info.isEmpty) {
|
|
2794
|
-
const error = new EmptyWalletError(sufficiency.info.walletAddress);
|
|
2795
|
-
options.onInsufficientFunds?.({
|
|
2796
|
-
balanceUSD: sufficiency.info.balanceUSD,
|
|
2797
|
-
requiredUSD: balanceMonitor.formatUSDC(bufferedCostMicros),
|
|
2798
|
-
walletAddress: sufficiency.info.walletAddress
|
|
2799
|
-
});
|
|
2800
|
-
throw error;
|
|
2801
|
-
} else {
|
|
2802
|
-
const error = new InsufficientFundsError({
|
|
2803
|
-
currentBalanceUSD: sufficiency.info.balanceUSD,
|
|
2804
|
-
requiredUSD: balanceMonitor.formatUSDC(bufferedCostMicros),
|
|
2805
|
-
walletAddress: sufficiency.info.walletAddress
|
|
2806
|
-
});
|
|
2807
|
-
options.onInsufficientFunds?.({
|
|
2808
|
-
balanceUSD: sufficiency.info.balanceUSD,
|
|
2809
|
-
requiredUSD: balanceMonitor.formatUSDC(bufferedCostMicros),
|
|
2810
|
-
walletAddress: sufficiency.info.walletAddress
|
|
2811
|
-
});
|
|
2812
|
-
throw error;
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2830
|
+
const originalModel = modelId;
|
|
2831
|
+
console.log(
|
|
2832
|
+
`[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} ($${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
|
|
2833
|
+
);
|
|
2834
|
+
modelId = FREE_MODEL;
|
|
2835
|
+
const parsed = JSON.parse(body.toString());
|
|
2836
|
+
parsed.model = FREE_MODEL;
|
|
2837
|
+
body = Buffer.from(JSON.stringify(parsed));
|
|
2838
|
+
options.onLowBalance?.({
|
|
2839
|
+
balanceUSD: sufficiency.info.balanceUSD,
|
|
2840
|
+
walletAddress: sufficiency.info.walletAddress
|
|
2841
|
+
});
|
|
2815
2842
|
} else if (sufficiency.info.isLow) {
|
|
2816
2843
|
options.onLowBalance?.({
|
|
2817
2844
|
balanceUSD: sufficiency.info.balanceUSD,
|
|
@@ -3240,6 +3267,18 @@ function isRetryable(errorOrResponse, config) {
|
|
|
3240
3267
|
}
|
|
3241
3268
|
|
|
3242
3269
|
// src/index.ts
|
|
3270
|
+
async function waitForProxyHealth(port, timeoutMs = 3e3) {
|
|
3271
|
+
const start = Date.now();
|
|
3272
|
+
while (Date.now() - start < timeoutMs) {
|
|
3273
|
+
try {
|
|
3274
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
3275
|
+
if (res.ok) return true;
|
|
3276
|
+
} catch {
|
|
3277
|
+
}
|
|
3278
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
3279
|
+
}
|
|
3280
|
+
return false;
|
|
3281
|
+
}
|
|
3243
3282
|
function isCompletionMode() {
|
|
3244
3283
|
const args = process.argv;
|
|
3245
3284
|
return args.some((arg, i) => arg === "completion" && i >= 1 && i <= 3);
|
|
@@ -3289,20 +3328,34 @@ function injectModelsConfig(logger) {
|
|
|
3289
3328
|
config.agents.defaults.model.primary = "blockrun/auto";
|
|
3290
3329
|
needsWrite = true;
|
|
3291
3330
|
}
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3331
|
+
const KEY_MODEL_ALIASES = [
|
|
3332
|
+
{ id: "auto", alias: "auto" },
|
|
3333
|
+
{ id: "free", alias: "free" },
|
|
3334
|
+
{ id: "sonnet", alias: "sonnet" },
|
|
3335
|
+
{ id: "opus", alias: "opus" },
|
|
3336
|
+
{ id: "haiku", alias: "haiku" },
|
|
3337
|
+
{ id: "grok", alias: "grok" },
|
|
3338
|
+
{ id: "deepseek", alias: "deepseek" },
|
|
3339
|
+
{ id: "kimi", alias: "kimi" },
|
|
3340
|
+
{ id: "gemini", alias: "gemini" },
|
|
3341
|
+
{ id: "flash", alias: "flash" },
|
|
3342
|
+
{ id: "gpt", alias: "gpt" },
|
|
3343
|
+
{ id: "reasoner", alias: "reasoner" }
|
|
3344
|
+
];
|
|
3345
|
+
if (!config.agents) config.agents = {};
|
|
3346
|
+
if (!config.agents.defaults) config.agents.defaults = {};
|
|
3347
|
+
if (!config.agents.defaults.models) config.agents.defaults.models = {};
|
|
3348
|
+
const allowlist = config.agents.defaults.models;
|
|
3349
|
+
for (const m of KEY_MODEL_ALIASES) {
|
|
3350
|
+
const fullId = `blockrun/${m.id}`;
|
|
3351
|
+
if (!allowlist[fullId]) {
|
|
3352
|
+
allowlist[fullId] = { alias: m.alias };
|
|
3353
|
+
needsWrite = true;
|
|
3301
3354
|
}
|
|
3302
3355
|
}
|
|
3303
3356
|
if (needsWrite) {
|
|
3304
3357
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3305
|
-
logger.info("
|
|
3358
|
+
logger.info("Smart routing enabled (blockrun/auto)");
|
|
3306
3359
|
}
|
|
3307
3360
|
} catch {
|
|
3308
3361
|
}
|
|
@@ -3374,29 +3427,11 @@ async function startProxyInBackground(api) {
|
|
|
3374
3427
|
const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
|
|
3375
3428
|
if (source === "generated") {
|
|
3376
3429
|
api.logger.info(`Generated new wallet: ${address}`);
|
|
3377
|
-
api.logger.info(`Fund with USDC on Base to start using ClawRouter.`);
|
|
3378
3430
|
} else if (source === "saved") {
|
|
3379
3431
|
api.logger.info(`Using saved wallet: ${address}`);
|
|
3380
3432
|
} else {
|
|
3381
3433
|
api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);
|
|
3382
3434
|
}
|
|
3383
|
-
const startupMonitor = new BalanceMonitor(address);
|
|
3384
|
-
try {
|
|
3385
|
-
const startupBalance = await startupMonitor.checkBalance();
|
|
3386
|
-
if (startupBalance.isEmpty) {
|
|
3387
|
-
api.logger.warn(`[!] No USDC balance. Fund wallet to use ClawRouter: ${address}`);
|
|
3388
|
-
} else if (startupBalance.isLow) {
|
|
3389
|
-
api.logger.warn(
|
|
3390
|
-
`[!] Low balance: ${startupBalance.balanceUSD} remaining. Fund wallet: ${address}`
|
|
3391
|
-
);
|
|
3392
|
-
} else {
|
|
3393
|
-
api.logger.info(`Wallet balance: ${startupBalance.balanceUSD}`);
|
|
3394
|
-
}
|
|
3395
|
-
} catch (err) {
|
|
3396
|
-
api.logger.warn(
|
|
3397
|
-
`Could not check wallet balance: ${err instanceof Error ? err.message : String(err)}`
|
|
3398
|
-
);
|
|
3399
|
-
}
|
|
3400
3435
|
const routingConfig = api.pluginConfig?.routing;
|
|
3401
3436
|
const proxy = await startProxy({
|
|
3402
3437
|
walletKey,
|
|
@@ -3425,7 +3460,21 @@ async function startProxyInBackground(api) {
|
|
|
3425
3460
|
});
|
|
3426
3461
|
setActiveProxy(proxy);
|
|
3427
3462
|
activeProxyHandle = proxy;
|
|
3428
|
-
api.logger.info(`
|
|
3463
|
+
api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
|
|
3464
|
+
api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
|
|
3465
|
+
const startupMonitor = new BalanceMonitor(address);
|
|
3466
|
+
startupMonitor.checkBalance().then((balance) => {
|
|
3467
|
+
if (balance.isEmpty) {
|
|
3468
|
+
api.logger.info(`Wallet: ${address} | Balance: $0.00`);
|
|
3469
|
+
api.logger.info(`Using FREE model. Fund wallet for premium models.`);
|
|
3470
|
+
} else if (balance.isLow) {
|
|
3471
|
+
api.logger.info(`Wallet: ${address} | Balance: ${balance.balanceUSD} (low)`);
|
|
3472
|
+
} else {
|
|
3473
|
+
api.logger.info(`Wallet: ${address} | Balance: ${balance.balanceUSD}`);
|
|
3474
|
+
}
|
|
3475
|
+
}).catch(() => {
|
|
3476
|
+
api.logger.info(`Wallet: ${address} | Balance: (checking...)`);
|
|
3477
|
+
});
|
|
3429
3478
|
}
|
|
3430
3479
|
async function createStatsCommand() {
|
|
3431
3480
|
return {
|
|
@@ -3531,7 +3580,7 @@ var plugin = {
|
|
|
3531
3580
|
name: "ClawRouter",
|
|
3532
3581
|
description: "Smart LLM router \u2014 30+ models, x402 micropayments, 78% cost savings",
|
|
3533
3582
|
version: VERSION,
|
|
3534
|
-
register(api) {
|
|
3583
|
+
async register(api) {
|
|
3535
3584
|
const isDisabled = process.env.CLAWROUTER_DISABLED === "true" || process.env.CLAWROUTER_DISABLED === "1";
|
|
3536
3585
|
if (isDisabled) {
|
|
3537
3586
|
api.logger.info("ClawRouter disabled (CLAWROUTER_DISABLED=true). Using default routing.");
|
|
@@ -3597,11 +3646,18 @@ var plugin = {
|
|
|
3597
3646
|
}
|
|
3598
3647
|
}
|
|
3599
3648
|
});
|
|
3600
|
-
|
|
3649
|
+
try {
|
|
3650
|
+
await startProxyInBackground(api);
|
|
3651
|
+
const port = getProxyPort();
|
|
3652
|
+
const healthy = await waitForProxyHealth(port);
|
|
3653
|
+
if (!healthy) {
|
|
3654
|
+
api.logger.warn(`Proxy health check timed out, commands may not work immediately`);
|
|
3655
|
+
}
|
|
3656
|
+
} catch (err) {
|
|
3601
3657
|
api.logger.error(
|
|
3602
3658
|
`Failed to start BlockRun proxy: ${err instanceof Error ? err.message : String(err)}`
|
|
3603
3659
|
);
|
|
3604
|
-
}
|
|
3660
|
+
}
|
|
3605
3661
|
}
|
|
3606
3662
|
};
|
|
3607
3663
|
var index_default = plugin;
|