@blockrun/clawrouter 0.12.60 → 0.12.62

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.d.ts CHANGED
@@ -681,6 +681,8 @@ type ProxyOptions = {
681
681
  requestTimeoutMs?: number;
682
682
  /** Skip balance checks (for testing only). Default: false */
683
683
  skipBalanceCheck?: boolean;
684
+ /** Override the balance monitor with a mock (for testing only). */
685
+ _balanceMonitorOverride?: AnyBalanceMonitor;
684
686
  /**
685
687
  * Session persistence config. When enabled, maintains model selection
686
688
  * across requests within a session to prevent mid-task model switching.
@@ -717,6 +719,12 @@ type ProxyOptions = {
717
719
  * - 'strict': immediately return 429 once the session spend reaches the cap.
718
720
  */
719
721
  maxCostPerRunMode?: "graceful" | "strict";
722
+ /**
723
+ * Set of model IDs to exclude from routing.
724
+ * Excluded models are filtered out of fallback chains.
725
+ * Loaded from ~/.openclaw/blockrun/exclude-models.json
726
+ */
727
+ excludeModels?: Set<string>;
720
728
  onReady?: (port: number) => void;
721
729
  onError?: (error: Error) => void;
722
730
  onPayment?: (info: {
@@ -913,6 +921,8 @@ type UsageEntry = {
913
921
  latencyMs: number;
914
922
  /** Input (prompt) tokens reported by the provider */
915
923
  inputTokens?: number;
924
+ /** Output (completion) tokens reported by the provider */
925
+ outputTokens?: number;
916
926
  /** Partner service ID (e.g., "x_users_lookup") — only set for partner API calls */
917
927
  partnerId?: string;
918
928
  /** Partner service name (e.g., "AttentionVC") — only set for partner API calls */
package/dist/index.js CHANGED
@@ -32889,7 +32889,9 @@ var MODEL_ALIASES = {
32889
32889
  nvidia: "nvidia/gpt-oss-120b",
32890
32890
  "gpt-120b": "nvidia/gpt-oss-120b",
32891
32891
  // MiniMax
32892
- minimax: "minimax/minimax-m2.5",
32892
+ minimax: "minimax/minimax-m2.7",
32893
+ "minimax-m2.7": "minimax/minimax-m2.7",
32894
+ "minimax-m2.5": "minimax/minimax-m2.5",
32893
32895
  // Z.AI GLM-5
32894
32896
  glm: "zai/glm-5",
32895
32897
  "glm-5": "zai/glm-5",
@@ -33395,6 +33397,18 @@ var BLOCKRUN_MODELS = [
33395
33397
  toolCalling: true
33396
33398
  },
33397
33399
  // MiniMax
33400
+ {
33401
+ id: "minimax/minimax-m2.7",
33402
+ name: "MiniMax M2.7",
33403
+ version: "m2.7",
33404
+ inputPrice: 0.3,
33405
+ outputPrice: 1.2,
33406
+ contextWindow: 204800,
33407
+ maxOutput: 16384,
33408
+ reasoning: true,
33409
+ agentic: true,
33410
+ toolCalling: true
33411
+ },
33398
33412
  {
33399
33413
  id: "minimax/minimax-m2.5",
33400
33414
  name: "MiniMax M2.5",
@@ -33541,10 +33555,10 @@ var blockrunProvider = {
33541
33555
  // src/proxy.ts
33542
33556
  import { createServer } from "http";
33543
33557
  import { finished } from "stream";
33544
- import { homedir as homedir4 } from "os";
33545
- import { join as join7 } from "path";
33558
+ import { homedir as homedir5 } from "os";
33559
+ import { join as join8 } from "path";
33546
33560
  import { mkdir as mkdir3, writeFile as writeFile2, readFile, stat as fsStat } from "fs/promises";
33547
- import { readFileSync, existsSync } from "fs";
33561
+ import { readFileSync as readFileSync2, existsSync } from "fs";
33548
33562
 
33549
33563
  // node_modules/viem/_esm/utils/getAction.js
33550
33564
  function getAction(client, actionFn, name) {
@@ -43494,6 +43508,11 @@ function filterByVision(models, hasVision, supportsVision2) {
43494
43508
  const filtered = models.filter(supportsVision2);
43495
43509
  return filtered.length > 0 ? filtered : models;
43496
43510
  }
43511
+ function filterByExcludeList(models, excludeList) {
43512
+ if (excludeList.size === 0) return models;
43513
+ const filtered = models.filter((m) => !excludeList.has(m));
43514
+ return filtered.length > 0 ? filtered : models;
43515
+ }
43497
43516
  function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
43498
43517
  const fullChain = getFallbackChain(tier, tierConfigs);
43499
43518
  const filtered = fullChain.filter((modelId) => {
@@ -46839,6 +46858,54 @@ async function checkForUpdates() {
46839
46858
  }
46840
46859
  }
46841
46860
 
46861
+ // src/exclude-models.ts
46862
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
46863
+ import { join as join7, dirname as dirname2 } from "path";
46864
+ import { homedir as homedir4 } from "os";
46865
+ var DEFAULT_FILE_PATH = join7(
46866
+ homedir4(),
46867
+ ".openclaw",
46868
+ "blockrun",
46869
+ "exclude-models.json"
46870
+ );
46871
+ function loadExcludeList(filePath = DEFAULT_FILE_PATH) {
46872
+ try {
46873
+ const raw = readFileSync(filePath, "utf-8");
46874
+ const arr = JSON.parse(raw);
46875
+ if (Array.isArray(arr)) {
46876
+ return new Set(arr.filter((x) => typeof x === "string"));
46877
+ }
46878
+ return /* @__PURE__ */ new Set();
46879
+ } catch {
46880
+ return /* @__PURE__ */ new Set();
46881
+ }
46882
+ }
46883
+ function saveExcludeList(set, filePath) {
46884
+ const sorted = [...set].sort();
46885
+ const dir = dirname2(filePath);
46886
+ mkdirSync(dir, { recursive: true });
46887
+ writeFileSync(filePath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
46888
+ }
46889
+ function addExclusion(model, filePath = DEFAULT_FILE_PATH) {
46890
+ const resolved = resolveModelAlias(model);
46891
+ const set = loadExcludeList(filePath);
46892
+ set.add(resolved);
46893
+ saveExcludeList(set, filePath);
46894
+ return resolved;
46895
+ }
46896
+ function removeExclusion(model, filePath = DEFAULT_FILE_PATH) {
46897
+ const resolved = resolveModelAlias(model);
46898
+ const set = loadExcludeList(filePath);
46899
+ const had = set.delete(resolved);
46900
+ if (had) {
46901
+ saveExcludeList(set, filePath);
46902
+ }
46903
+ return had;
46904
+ }
46905
+ function clearExclusions(filePath = DEFAULT_FILE_PATH) {
46906
+ saveExcludeList(/* @__PURE__ */ new Set(), filePath);
46907
+ }
46908
+
46842
46909
  // src/config.ts
46843
46910
  var DEFAULT_PORT = 8402;
46844
46911
  var PROXY_PORT = (() => {
@@ -47023,7 +47090,7 @@ ${lines.join("\n")}`;
47023
47090
  // src/proxy.ts
47024
47091
  var BLOCKRUN_API = "https://blockrun.ai/api";
47025
47092
  var BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
47026
- var IMAGE_DIR = join7(homedir4(), ".openclaw", "blockrun", "images");
47093
+ var IMAGE_DIR = join8(homedir5(), ".openclaw", "blockrun", "images");
47027
47094
  var AUTO_MODEL = "blockrun/auto";
47028
47095
  var ROUTING_PROFILES = /* @__PURE__ */ new Set([
47029
47096
  "blockrun/free",
@@ -47691,7 +47758,7 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
47691
47758
  });
47692
47759
  }
47693
47760
  function readImageFileAsDataUri(filePath) {
47694
- const resolved = filePath.startsWith("~/") ? join7(homedir4(), filePath.slice(2)) : filePath;
47761
+ const resolved = filePath.startsWith("~/") ? join8(homedir5(), filePath.slice(2)) : filePath;
47695
47762
  if (!existsSync(resolved)) {
47696
47763
  throw new Error(`Image file not found: ${resolved}`);
47697
47764
  }
@@ -47703,7 +47770,7 @@ function readImageFileAsDataUri(filePath) {
47703
47770
  webp: "image/webp"
47704
47771
  };
47705
47772
  const mime = mimeMap[ext] ?? "image/png";
47706
- const data = readFileSync(resolved);
47773
+ const data = readFileSync2(resolved);
47707
47774
  return `data:${mime};base64,${data.toString("base64")}`;
47708
47775
  }
47709
47776
  async function uploadDataUriToHost(dataUri) {
@@ -47821,7 +47888,9 @@ async function startProxy(options) {
47821
47888
  skipPreAuth: paymentChain === "solana"
47822
47889
  });
47823
47890
  let balanceMonitor;
47824
- if (paymentChain === "solana" && solanaAddress) {
47891
+ if (options._balanceMonitorOverride) {
47892
+ balanceMonitor = options._balanceMonitorOverride;
47893
+ } else if (paymentChain === "solana" && solanaAddress) {
47825
47894
  const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
47826
47895
  balanceMonitor = new SolanaBalanceMonitor2(solanaAddress);
47827
47896
  } else {
@@ -47946,7 +48015,7 @@ async function startProxy(options) {
47946
48015
  res.end("Bad request");
47947
48016
  return;
47948
48017
  }
47949
- const filePath = join7(IMAGE_DIR, filename);
48018
+ const filePath = join8(IMAGE_DIR, filename);
47950
48019
  try {
47951
48020
  const s3 = await fsStat(filePath);
47952
48021
  if (!s3.isFile()) throw new Error("not a file");
@@ -48005,7 +48074,7 @@ async function startProxy(options) {
48005
48074
  const [, mimeType, b64] = dataUriMatch;
48006
48075
  const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
48007
48076
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
48008
- await writeFile2(join7(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
48077
+ await writeFile2(join8(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
48009
48078
  img.url = `http://localhost:${port2}/images/${filename}`;
48010
48079
  console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
48011
48080
  } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
@@ -48016,7 +48085,7 @@ async function startProxy(options) {
48016
48085
  const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
48017
48086
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
48018
48087
  const buf = Buffer.from(await imgResp.arrayBuffer());
48019
- await writeFile2(join7(IMAGE_DIR, filename), buf);
48088
+ await writeFile2(join8(IMAGE_DIR, filename), buf);
48020
48089
  img.url = `http://localhost:${port2}/images/${filename}`;
48021
48090
  console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
48022
48091
  }
@@ -48105,7 +48174,7 @@ async function startProxy(options) {
48105
48174
  const [, mimeType, b64] = dataUriMatch;
48106
48175
  const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
48107
48176
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
48108
- await writeFile2(join7(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
48177
+ await writeFile2(join8(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
48109
48178
  img.url = `http://localhost:${port2}/images/${filename}`;
48110
48179
  console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
48111
48180
  } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
@@ -48116,7 +48185,7 @@ async function startProxy(options) {
48116
48185
  const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
48117
48186
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
48118
48187
  const buf = Buffer.from(await imgResp.arrayBuffer());
48119
- await writeFile2(join7(IMAGE_DIR, filename), buf);
48188
+ await writeFile2(join8(IMAGE_DIR, filename), buf);
48120
48189
  img.url = `http://localhost:${port2}/images/${filename}`;
48121
48190
  console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
48122
48191
  }
@@ -48428,6 +48497,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
48428
48497
  let budgetDowngradeHeaderMode;
48429
48498
  let accumulatedContent = "";
48430
48499
  let responseInputTokens;
48500
+ let responseOutputTokens;
48431
48501
  const isChatCompletion = req.url?.includes("/chat/completions");
48432
48502
  const sessionId = getSessionId(req.headers);
48433
48503
  let effectiveSessionId = sessionId;
@@ -49361,6 +49431,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
49361
49431
  const timeoutId = setTimeout(() => globalController.abort(), timeoutMs);
49362
49432
  try {
49363
49433
  let modelsToTry;
49434
+ const excludeList = options.excludeModels ?? loadExcludeList();
49364
49435
  if (routingDecision) {
49365
49436
  const estimatedInputTokens = Math.ceil(body.length / 4);
49366
49437
  const estimatedTotalTokens = estimatedInputTokens + maxTokens;
@@ -49378,8 +49449,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
49378
49449
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
49379
49450
  );
49380
49451
  }
49381
- let toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);
49382
- const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));
49452
+ const excludeFiltered = filterByExcludeList(contextFiltered, excludeList);
49453
+ const excludeExcluded = contextFiltered.filter((m) => !excludeFiltered.includes(m));
49454
+ if (excludeExcluded.length > 0) {
49455
+ console.log(
49456
+ `[ClawRouter] Exclude filter: excluded ${excludeExcluded.join(", ")} (user preference)`
49457
+ );
49458
+ }
49459
+ let toolFiltered = filterByToolCalling(excludeFiltered, hasTools, supportsToolCalling);
49460
+ const toolExcluded = excludeFiltered.filter((m) => !toolFiltered.includes(m));
49383
49461
  if (toolExcluded.length > 0) {
49384
49462
  console.log(
49385
49463
  `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
@@ -49412,7 +49490,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
49412
49490
  } else {
49413
49491
  modelsToTry = modelId ? [modelId] : [];
49414
49492
  }
49415
- if (!hasTools && !modelsToTry.includes(FREE_MODEL)) {
49493
+ if (!hasTools && !modelsToTry.includes(FREE_MODEL) && !excludeList.has(FREE_MODEL)) {
49416
49494
  modelsToTry.push(FREE_MODEL);
49417
49495
  }
49418
49496
  if (options.maxCostPerRunUsd && effectiveSessionId && !isFreeModel && (options.maxCostPerRunMode ?? "graceful") === "graceful") {
@@ -49720,6 +49798,7 @@ data: [DONE]
49720
49798
  if (rsp.usage && typeof rsp.usage === "object") {
49721
49799
  const u = rsp.usage;
49722
49800
  if (typeof u.prompt_tokens === "number") responseInputTokens = u.prompt_tokens;
49801
+ if (typeof u.completion_tokens === "number") responseOutputTokens = u.completion_tokens;
49723
49802
  }
49724
49803
  const baseChunk = {
49725
49804
  id: rsp.id ?? `chatcmpl-${Date.now()}`,
@@ -49933,6 +50012,8 @@ data: [DONE]
49933
50012
  if (rspJson.usage && typeof rspJson.usage === "object") {
49934
50013
  if (typeof rspJson.usage.prompt_tokens === "number")
49935
50014
  responseInputTokens = rspJson.usage.prompt_tokens;
50015
+ if (typeof rspJson.usage.completion_tokens === "number")
50016
+ responseOutputTokens = rspJson.usage.completion_tokens;
49936
50017
  }
49937
50018
  } catch {
49938
50019
  }
@@ -49965,25 +50046,25 @@ data: [DONE]
49965
50046
  }
49966
50047
  const logModel = routingDecision?.model ?? modelId;
49967
50048
  if (logModel) {
49968
- const estimatedInputTokens = Math.ceil(body.length / 4);
50049
+ const actualInputTokens = responseInputTokens ?? Math.ceil(body.length / 4);
50050
+ const actualOutputTokens = responseOutputTokens ?? maxTokens;
49969
50051
  const accurateCosts = calculateModelCost(
49970
50052
  logModel,
49971
50053
  routerOpts.modelPricing,
49972
- estimatedInputTokens,
49973
- maxTokens,
50054
+ actualInputTokens,
50055
+ actualOutputTokens,
49974
50056
  routingProfile ?? void 0
49975
50057
  );
49976
- const costWithBuffer = accurateCosts.costEstimate * 1.2;
49977
- const baselineWithBuffer = accurateCosts.baselineCost * 1.2;
49978
50058
  const entry = {
49979
50059
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
49980
50060
  model: logModel,
49981
50061
  tier: routingDecision?.tier ?? "DIRECT",
49982
- cost: costWithBuffer,
49983
- baselineCost: baselineWithBuffer,
50062
+ cost: accurateCosts.costEstimate,
50063
+ baselineCost: accurateCosts.baselineCost,
49984
50064
  savings: accurateCosts.savings,
49985
50065
  latencyMs: Date.now() - startTime,
49986
- ...responseInputTokens !== void 0 && { inputTokens: responseInputTokens }
50066
+ ...responseInputTokens !== void 0 && { inputTokens: responseInputTokens },
50067
+ ...responseOutputTokens !== void 0 && { outputTokens: responseOutputTokens }
49987
50068
  };
49988
50069
  logUsage(entry).catch(() => {
49989
50070
  });
@@ -49992,15 +50073,15 @@ data: [DONE]
49992
50073
 
49993
50074
  // src/index.ts
49994
50075
  import {
49995
- writeFileSync as writeFileSync2,
50076
+ writeFileSync as writeFileSync3,
49996
50077
  existsSync as existsSync3,
49997
50078
  readdirSync,
49998
- mkdirSync as mkdirSync2,
50079
+ mkdirSync as mkdirSync3,
49999
50080
  copyFileSync,
50000
50081
  renameSync
50001
50082
  } from "fs";
50002
- import { homedir as homedir6 } from "os";
50003
- import { join as join9 } from "path";
50083
+ import { homedir as homedir7 } from "os";
50084
+ import { join as join10 } from "path";
50004
50085
  init_accounts();
50005
50086
 
50006
50087
  // src/partners/registry.ts
@@ -50104,8 +50185,8 @@ init_solana_balance();
50104
50185
  // src/spend-control.ts
50105
50186
  import * as fs from "fs";
50106
50187
  import * as path from "path";
50107
- import { homedir as homedir5 } from "os";
50108
- var WALLET_DIR2 = path.join(homedir5(), ".openclaw", "blockrun");
50188
+ import { homedir as homedir6 } from "os";
50189
+ var WALLET_DIR2 = path.join(homedir6(), ".openclaw", "blockrun");
50109
50190
  var HOUR_MS = 60 * 60 * 1e3;
50110
50191
  var DAY_MS = 24 * HOUR_MS;
50111
50192
  var FileSpendControlStorage = class {
@@ -50430,13 +50511,13 @@ function isGatewayMode() {
50430
50511
  return args.includes("gateway");
50431
50512
  }
50432
50513
  function injectModelsConfig(logger) {
50433
- const configDir = join9(homedir6(), ".openclaw");
50434
- const configPath = join9(configDir, "openclaw.json");
50514
+ const configDir = join10(homedir7(), ".openclaw");
50515
+ const configPath = join10(configDir, "openclaw.json");
50435
50516
  let config = {};
50436
50517
  let needsWrite = false;
50437
50518
  if (!existsSync3(configDir)) {
50438
50519
  try {
50439
- mkdirSync2(configDir, { recursive: true });
50520
+ mkdirSync3(configDir, { recursive: true });
50440
50521
  logger.info("Created OpenClaw config directory");
50441
50522
  } catch (err) {
50442
50523
  logger.info(
@@ -50597,7 +50678,7 @@ function injectModelsConfig(logger) {
50597
50678
  if (needsWrite) {
50598
50679
  try {
50599
50680
  const tmpPath = `${configPath}.tmp.${process.pid}`;
50600
- writeFileSync2(tmpPath, JSON.stringify(config, null, 2));
50681
+ writeFileSync3(tmpPath, JSON.stringify(config, null, 2));
50601
50682
  renameSync(tmpPath, configPath);
50602
50683
  logger.info("Smart routing enabled (blockrun/auto)");
50603
50684
  } catch (err) {
@@ -50606,10 +50687,10 @@ function injectModelsConfig(logger) {
50606
50687
  }
50607
50688
  }
50608
50689
  function injectAuthProfile(logger) {
50609
- const agentsDir = join9(homedir6(), ".openclaw", "agents");
50690
+ const agentsDir = join10(homedir7(), ".openclaw", "agents");
50610
50691
  if (!existsSync3(agentsDir)) {
50611
50692
  try {
50612
- mkdirSync2(agentsDir, { recursive: true });
50693
+ mkdirSync3(agentsDir, { recursive: true });
50613
50694
  } catch (err) {
50614
50695
  logger.info(
50615
50696
  `Could not create agents dir: ${err instanceof Error ? err.message : String(err)}`
@@ -50623,11 +50704,11 @@ function injectAuthProfile(logger) {
50623
50704
  agents = ["main", ...agents];
50624
50705
  }
50625
50706
  for (const agentId of agents) {
50626
- const authDir = join9(agentsDir, agentId, "agent");
50627
- const authPath = join9(authDir, "auth-profiles.json");
50707
+ const authDir = join10(agentsDir, agentId, "agent");
50708
+ const authPath = join10(authDir, "auth-profiles.json");
50628
50709
  if (!existsSync3(authDir)) {
50629
50710
  try {
50630
- mkdirSync2(authDir, { recursive: true });
50711
+ mkdirSync3(authDir, { recursive: true });
50631
50712
  } catch {
50632
50713
  continue;
50633
50714
  }
@@ -50655,7 +50736,7 @@ function injectAuthProfile(logger) {
50655
50736
  key: "x402-proxy-handles-auth"
50656
50737
  };
50657
50738
  try {
50658
- writeFileSync2(authPath, JSON.stringify(store, null, 2));
50739
+ writeFileSync3(authPath, JSON.stringify(store, null, 2));
50659
50740
  logger.info(`Injected BlockRun auth profile for agent: ${agentId}`);
50660
50741
  } catch (err) {
50661
50742
  logger.info(
@@ -50719,6 +50800,10 @@ async function startProxyInBackground(api) {
50719
50800
  });
50720
50801
  setActiveProxy(proxy);
50721
50802
  activeProxyHandle = proxy;
50803
+ const startupExclusions = loadExcludeList();
50804
+ if (startupExclusions.size > 0) {
50805
+ api.logger.info(`Model exclusions active (${startupExclusions.size}): ${[...startupExclusions].join(", ")}`);
50806
+ }
50722
50807
  api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
50723
50808
  api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
50724
50809
  const currentChain = await resolvePaymentChain();
@@ -50778,6 +50863,78 @@ async function createStatsCommand() {
50778
50863
  }
50779
50864
  };
50780
50865
  }
50866
+ async function createExcludeCommand() {
50867
+ return {
50868
+ name: "exclude",
50869
+ description: "Manage excluded models \u2014 /exclude add|remove|clear <model>",
50870
+ acceptsArgs: true,
50871
+ requireAuth: true,
50872
+ handler: async (ctx) => {
50873
+ const args = ctx.args?.trim() || "";
50874
+ const parts = args.split(/\s+/);
50875
+ const subcommand = parts[0]?.toLowerCase() || "";
50876
+ const modelArg = parts.slice(1).join(" ").trim();
50877
+ if (!subcommand) {
50878
+ const list = loadExcludeList();
50879
+ if (list.size === 0) {
50880
+ return {
50881
+ text: "No models excluded.\n\nUsage:\n /exclude add <model> \u2014 block a model\n /exclude remove <model> \u2014 unblock\n /exclude clear \u2014 remove all"
50882
+ };
50883
+ }
50884
+ const models = [...list].sort().map((m) => ` \u2022 ${m}`).join("\n");
50885
+ return {
50886
+ text: `Excluded models (${list.size}):
50887
+ ${models}
50888
+
50889
+ Use /exclude remove <model> to unblock.`
50890
+ };
50891
+ }
50892
+ if (subcommand === "add") {
50893
+ if (!modelArg) {
50894
+ return { text: "Usage: /exclude add <model>\nExample: /exclude add nvidia/gpt-oss-120b", isError: true };
50895
+ }
50896
+ const resolved = addExclusion(modelArg);
50897
+ const list = loadExcludeList();
50898
+ return {
50899
+ text: `Excluded: ${resolved}
50900
+
50901
+ Active exclusions (${list.size}):
50902
+ ${[...list].sort().map((m) => ` \u2022 ${m}`).join("\n")}`
50903
+ };
50904
+ }
50905
+ if (subcommand === "remove") {
50906
+ if (!modelArg) {
50907
+ return { text: "Usage: /exclude remove <model>", isError: true };
50908
+ }
50909
+ const removed = removeExclusion(modelArg);
50910
+ if (!removed) {
50911
+ return { text: `Model "${modelArg}" was not in the exclude list.` };
50912
+ }
50913
+ const list = loadExcludeList();
50914
+ return {
50915
+ text: `Unblocked: ${modelArg}
50916
+
50917
+ Active exclusions (${list.size}):
50918
+ ${list.size > 0 ? [...list].sort().map((m) => ` \u2022 ${m}`).join("\n") : " (none)"}`
50919
+ };
50920
+ }
50921
+ if (subcommand === "clear") {
50922
+ clearExclusions();
50923
+ return { text: "All model exclusions cleared." };
50924
+ }
50925
+ return {
50926
+ text: `Unknown subcommand: ${subcommand}
50927
+
50928
+ Usage:
50929
+ /exclude \u2014 show list
50930
+ /exclude add <model>
50931
+ /exclude remove <model>
50932
+ /exclude clear`,
50933
+ isError: true
50934
+ };
50935
+ }
50936
+ };
50937
+ }
50781
50938
  async function createWalletCommand() {
50782
50939
  return {
50783
50940
  name: "wallet",
@@ -51090,6 +51247,13 @@ var plugin = {
51090
51247
  `Failed to register /stats command: ${err instanceof Error ? err.message : String(err)}`
51091
51248
  );
51092
51249
  });
51250
+ createExcludeCommand().then((excludeCommand) => {
51251
+ api.registerCommand(excludeCommand);
51252
+ }).catch((err) => {
51253
+ api.logger.warn(
51254
+ `Failed to register /exclude command: ${err instanceof Error ? err.message : String(err)}`
51255
+ );
51256
+ });
51093
51257
  api.registerService({
51094
51258
  id: "clawrouter-proxy",
51095
51259
  start: () => {