@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/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
  }
@@ -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
- if (routingDecision) {
2780
- console.log(
2781
- `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} ($${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL}`
2782
- );
2783
- modelId = FREE_MODEL;
2784
- const parsed = JSON.parse(body.toString());
2785
- parsed.model = FREE_MODEL;
2786
- body = Buffer.from(JSON.stringify(parsed));
2787
- options.onLowBalance?.({
2788
- balanceUSD: sufficiency.info.balanceUSD,
2789
- walletAddress: sufficiency.info.walletAddress
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
- if (config.agents.defaults.models && typeof config.agents.defaults.models === "object") {
3293
- const allowlist = config.agents.defaults.models;
3294
- for (const model of OPENCLAW_MODELS) {
3295
- const fullId = `blockrun/${model.id}`;
3296
- if (!allowlist[fullId]) {
3297
- const alias = model.id.includes("/") ? model.id.split("/").pop() : model.id;
3298
- allowlist[fullId] = { alias };
3299
- needsWrite = true;
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("Set default model to blockrun/auto (smart routing enabled)");
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(`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
+ });
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
- 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) {
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;