@blockrun/clawrouter 0.5.9 → 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/dist/index.js CHANGED
@@ -1996,9 +1996,13 @@ var InsufficientFundsError = class extends Error {
1996
1996
  requiredUSD;
1997
1997
  walletAddress;
1998
1998
  constructor(opts) {
1999
- super(
2000
- `Insufficient USDC balance. Current: ${opts.currentBalanceUSD}, Required: ${opts.requiredUSD}. Fund wallet: ${opts.walletAddress}`
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
- super(`No USDC balance. Fund wallet to use ClawRouter: ${walletAddress}`);
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
  }
@@ -2431,6 +2442,18 @@ function normalizeMessagesForGoogle(messages) {
2431
2442
  function isGoogleModel(modelId) {
2432
2443
  return modelId.startsWith("google/") || modelId.startsWith("gemini");
2433
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
+ }
2434
2457
  var KIMI_BLOCK_RE = /<[||][^<>]*begin[^<>]*[||]>[\s\S]*?<[||][^<>]*end[^<>]*[||]>/gi;
2435
2458
  var KIMI_TOKEN_RE = /<[||][^<>]*[||]>/g;
2436
2459
  var THINKING_TAG_RE = /<\s*\/?\s*(?:think(?:ing)?|thought|antthinking)\b[^>]*>/gi;
@@ -2642,6 +2665,9 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
2642
2665
  if (isGoogleModel(modelId) && Array.isArray(parsed.messages)) {
2643
2666
  parsed.messages = normalizeMessagesForGoogle(parsed.messages);
2644
2667
  }
2668
+ if (parsed.thinking && Array.isArray(parsed.messages)) {
2669
+ parsed.messages = normalizeMessagesForThinking(parsed.messages);
2670
+ }
2645
2671
  requestBody = Buffer.from(JSON.stringify(parsed));
2646
2672
  } catch {
2647
2673
  }
@@ -2801,42 +2827,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
2801
2827
  const bufferedCostMicros = estimatedCostMicros * BigInt(Math.ceil(BALANCE_CHECK_BUFFER * 100)) / 100n;
2802
2828
  const sufficiency = await balanceMonitor.checkSufficient(bufferedCostMicros);
2803
2829
  if (sufficiency.info.isEmpty || !sufficiency.sufficient) {
2804
- if (routingDecision) {
2805
- console.log(
2806
- `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} ($${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL}`
2807
- );
2808
- modelId = FREE_MODEL;
2809
- const parsed = JSON.parse(body.toString());
2810
- parsed.model = FREE_MODEL;
2811
- body = Buffer.from(JSON.stringify(parsed));
2812
- options.onLowBalance?.({
2813
- balanceUSD: sufficiency.info.balanceUSD,
2814
- walletAddress: sufficiency.info.walletAddress
2815
- });
2816
- } else {
2817
- deduplicator.removeInflight(dedupKey);
2818
- if (sufficiency.info.isEmpty) {
2819
- const error = new EmptyWalletError(sufficiency.info.walletAddress);
2820
- options.onInsufficientFunds?.({
2821
- balanceUSD: sufficiency.info.balanceUSD,
2822
- requiredUSD: balanceMonitor.formatUSDC(bufferedCostMicros),
2823
- walletAddress: sufficiency.info.walletAddress
2824
- });
2825
- throw error;
2826
- } else {
2827
- const error = new InsufficientFundsError({
2828
- currentBalanceUSD: sufficiency.info.balanceUSD,
2829
- requiredUSD: balanceMonitor.formatUSDC(bufferedCostMicros),
2830
- walletAddress: sufficiency.info.walletAddress
2831
- });
2832
- options.onInsufficientFunds?.({
2833
- balanceUSD: sufficiency.info.balanceUSD,
2834
- requiredUSD: balanceMonitor.formatUSDC(bufferedCostMicros),
2835
- walletAddress: sufficiency.info.walletAddress
2836
- });
2837
- throw error;
2838
- }
2839
- }
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
+ });
2840
2842
  } else if (sufficiency.info.isLow) {
2841
2843
  options.onLowBalance?.({
2842
2844
  balanceUSD: sufficiency.info.balanceUSD,
@@ -3265,6 +3267,18 @@ function isRetryable(errorOrResponse, config) {
3265
3267
  }
3266
3268
 
3267
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
+ }
3268
3282
  function isCompletionMode() {
3269
3283
  const args = process.argv;
3270
3284
  return args.some((arg, i) => arg === "completion" && i >= 1 && i <= 3);
@@ -3314,20 +3328,34 @@ function injectModelsConfig(logger) {
3314
3328
  config.agents.defaults.model.primary = "blockrun/auto";
3315
3329
  needsWrite = true;
3316
3330
  }
3317
- if (config.agents.defaults.models && typeof config.agents.defaults.models === "object") {
3318
- const allowlist = config.agents.defaults.models;
3319
- for (const model of OPENCLAW_MODELS) {
3320
- const fullId = `blockrun/${model.id}`;
3321
- if (!allowlist[fullId]) {
3322
- const alias = model.id.includes("/") ? model.id.split("/").pop() : model.id;
3323
- allowlist[fullId] = { alias };
3324
- needsWrite = true;
3325
- }
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;
3326
3354
  }
3327
3355
  }
3328
3356
  if (needsWrite) {
3329
3357
  writeFileSync(configPath, JSON.stringify(config, null, 2));
3330
- logger.info("Set default model to blockrun/auto (smart routing enabled)");
3358
+ logger.info("Smart routing enabled (blockrun/auto)");
3331
3359
  }
3332
3360
  } catch {
3333
3361
  }
@@ -3399,29 +3427,11 @@ async function startProxyInBackground(api) {
3399
3427
  const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
3400
3428
  if (source === "generated") {
3401
3429
  api.logger.info(`Generated new wallet: ${address}`);
3402
- api.logger.info(`Fund with USDC on Base to start using ClawRouter.`);
3403
3430
  } else if (source === "saved") {
3404
3431
  api.logger.info(`Using saved wallet: ${address}`);
3405
3432
  } else {
3406
3433
  api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);
3407
3434
  }
3408
- const startupMonitor = new BalanceMonitor(address);
3409
- try {
3410
- const startupBalance = await startupMonitor.checkBalance();
3411
- if (startupBalance.isEmpty) {
3412
- api.logger.warn(`[!] No USDC balance. Fund wallet to use ClawRouter: ${address}`);
3413
- } else if (startupBalance.isLow) {
3414
- api.logger.warn(
3415
- `[!] Low balance: ${startupBalance.balanceUSD} remaining. Fund wallet: ${address}`
3416
- );
3417
- } else {
3418
- api.logger.info(`Wallet balance: ${startupBalance.balanceUSD}`);
3419
- }
3420
- } catch (err) {
3421
- api.logger.warn(
3422
- `Could not check wallet balance: ${err instanceof Error ? err.message : String(err)}`
3423
- );
3424
- }
3425
3435
  const routingConfig = api.pluginConfig?.routing;
3426
3436
  const proxy = await startProxy({
3427
3437
  walletKey,
@@ -3450,7 +3460,21 @@ async function startProxyInBackground(api) {
3450
3460
  });
3451
3461
  setActiveProxy(proxy);
3452
3462
  activeProxyHandle = proxy;
3453
- api.logger.info(`BlockRun provider active \u2014 ${proxy.baseUrl}/v1 (smart routing enabled)`);
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
+ });
3454
3478
  }
3455
3479
  async function createStatsCommand() {
3456
3480
  return {
@@ -3556,7 +3580,7 @@ var plugin = {
3556
3580
  name: "ClawRouter",
3557
3581
  description: "Smart LLM router \u2014 30+ models, x402 micropayments, 78% cost savings",
3558
3582
  version: VERSION,
3559
- register(api) {
3583
+ async register(api) {
3560
3584
  const isDisabled = process.env.CLAWROUTER_DISABLED === "true" || process.env.CLAWROUTER_DISABLED === "1";
3561
3585
  if (isDisabled) {
3562
3586
  api.logger.info("ClawRouter disabled (CLAWROUTER_DISABLED=true). Using default routing.");
@@ -3622,11 +3646,18 @@ var plugin = {
3622
3646
  }
3623
3647
  }
3624
3648
  });
3625
- startProxyInBackground(api).catch((err) => {
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) {
3626
3657
  api.logger.error(
3627
3658
  `Failed to start BlockRun proxy: ${err instanceof Error ? err.message : String(err)}`
3628
3659
  );
3629
- });
3660
+ }
3630
3661
  }
3631
3662
  };
3632
3663
  var index_default = plugin;