@blockrun/clawrouter 0.10.5 → 0.10.7

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/cli.js CHANGED
@@ -511,7 +511,7 @@ function calibrateConfidence(distance, steepness) {
511
511
  }
512
512
 
513
513
  // src/router/selector.ts
514
- var BASELINE_MODEL_ID = "anthropic/claude-opus-4-5";
514
+ var BASELINE_MODEL_ID = "anthropic/claude-opus-4.6";
515
515
  function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
516
516
  const tierConfig = tierConfigs[tier];
517
517
  const model = tierConfig.primary;
@@ -4595,6 +4595,63 @@ function estimateAmount(modelId, bodyLength, maxTokens) {
4595
4595
  const amountMicros = Math.max(100, Math.ceil(costUsd * 1.2 * 1e6));
4596
4596
  return amountMicros.toString();
4597
4597
  }
4598
+ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
4599
+ const startTime = Date.now();
4600
+ const upstreamUrl = `${apiBase}${req.url}`;
4601
+ const bodyChunks = [];
4602
+ for await (const chunk of req) {
4603
+ bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4604
+ }
4605
+ const body = Buffer.concat(bodyChunks);
4606
+ const headers = {};
4607
+ for (const [key, value] of Object.entries(req.headers)) {
4608
+ if (key === "host" || key === "connection" || key === "transfer-encoding" || key === "content-length")
4609
+ continue;
4610
+ if (typeof value === "string") headers[key] = value;
4611
+ }
4612
+ if (!headers["content-type"]) headers["content-type"] = "application/json";
4613
+ headers["user-agent"] = USER_AGENT;
4614
+ console.log(`[ClawRouter] Partner request: ${req.method} ${req.url}`);
4615
+ const upstream = await payFetch(upstreamUrl, {
4616
+ method: req.method ?? "POST",
4617
+ headers,
4618
+ body: body.length > 0 ? new Uint8Array(body) : void 0
4619
+ });
4620
+ const responseHeaders = {};
4621
+ upstream.headers.forEach((value, key) => {
4622
+ if (key === "transfer-encoding" || key === "connection" || key === "content-encoding") return;
4623
+ responseHeaders[key] = value;
4624
+ });
4625
+ res.writeHead(upstream.status, responseHeaders);
4626
+ if (upstream.body) {
4627
+ const reader = upstream.body.getReader();
4628
+ try {
4629
+ while (true) {
4630
+ const { done, value } = await reader.read();
4631
+ if (done) break;
4632
+ safeWrite(res, Buffer.from(value));
4633
+ }
4634
+ } finally {
4635
+ reader.releaseLock();
4636
+ }
4637
+ }
4638
+ res.end();
4639
+ const latencyMs = Date.now() - startTime;
4640
+ console.log(`[ClawRouter] Partner response: ${upstream.status} (${latencyMs}ms)`);
4641
+ logUsage({
4642
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4643
+ model: "partner",
4644
+ tier: "PARTNER",
4645
+ cost: 0,
4646
+ // Actual cost handled by x402 settlement
4647
+ baselineCost: 0,
4648
+ savings: 0,
4649
+ latencyMs,
4650
+ partnerId: (req.url?.split("?")[0] ?? "").replace(/^\/v1\//, "").replace(/\//g, "_") || "unknown",
4651
+ service: "partner"
4652
+ }).catch(() => {
4653
+ });
4654
+ }
4598
4655
  async function startProxy(options) {
4599
4656
  const apiBase = options.apiBase ?? BLOCKRUN_API;
4600
4657
  const listenPort = options.port ?? getProxyPort();
@@ -4705,6 +4762,23 @@ async function startProxy(options) {
4705
4762
  res.end(JSON.stringify({ object: "list", data: models }));
4706
4763
  return;
4707
4764
  }
4765
+ if (req.url?.match(/^\/v1\/(?:x|partner)\//)) {
4766
+ try {
4767
+ await proxyPartnerRequest(req, res, apiBase, payFetch);
4768
+ } catch (err) {
4769
+ const error = err instanceof Error ? err : new Error(String(err));
4770
+ options.onError?.(error);
4771
+ if (!res.headersSent) {
4772
+ res.writeHead(502, { "Content-Type": "application/json" });
4773
+ res.end(
4774
+ JSON.stringify({
4775
+ error: { message: `Partner proxy error: ${error.message}`, type: "partner_error" }
4776
+ })
4777
+ );
4778
+ }
4779
+ }
4780
+ return;
4781
+ }
4708
4782
  if (!req.url?.startsWith("/v1")) {
4709
4783
  res.writeHead(404, { "Content-Type": "application/json" });
4710
4784
  res.end(JSON.stringify({ error: "Not found" }));
@@ -5567,10 +5641,11 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5567
5641
  }
5568
5642
  throw err;
5569
5643
  }
5570
- if (routingDecision) {
5644
+ const logModel = routingDecision?.model ?? modelId;
5645
+ if (logModel) {
5571
5646
  const estimatedInputTokens = Math.ceil(body.length / 4);
5572
5647
  const accurateCosts = calculateModelCost(
5573
- routingDecision.model,
5648
+ logModel,
5574
5649
  routerOpts.modelPricing,
5575
5650
  estimatedInputTokens,
5576
5651
  maxTokens,
@@ -5580,8 +5655,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5580
5655
  const baselineWithBuffer = accurateCosts.baselineCost * 1.2;
5581
5656
  const entry = {
5582
5657
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5583
- model: routingDecision.model,
5584
- tier: routingDecision.tier,
5658
+ model: logModel,
5659
+ tier: routingDecision?.tier ?? "DIRECT",
5585
5660
  cost: costWithBuffer,
5586
5661
  baselineCost: baselineWithBuffer,
5587
5662
  savings: accurateCosts.savings,
@@ -5606,12 +5681,24 @@ async function loadSavedWallet() {
5606
5681
  console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
5607
5682
  return key;
5608
5683
  }
5609
- console.warn(`[ClawRouter] \u26A0 Wallet file exists but is invalid (wrong format)`);
5684
+ console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
5685
+ console.error(`[ClawRouter] File: ${WALLET_FILE}`);
5686
+ console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
5687
+ console.error(`[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`);
5688
+ throw new Error(
5689
+ `Wallet file at ${WALLET_FILE} is corrupted or has wrong format. Refusing to auto-generate new wallet to protect existing funds. Restore your backup key or set BLOCKRUN_WALLET_KEY environment variable.`
5690
+ );
5610
5691
  } catch (err) {
5611
5692
  if (err.code !== "ENOENT") {
5693
+ if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
5694
+ throw err;
5695
+ }
5612
5696
  console.error(
5613
5697
  `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
5614
5698
  );
5699
+ throw new Error(
5700
+ `Cannot read wallet file at ${WALLET_FILE}: ${err instanceof Error ? err.message : String(err)}. Refusing to auto-generate new wallet to protect existing funds. Fix file permissions or set BLOCKRUN_WALLET_KEY environment variable.`
5701
+ );
5615
5702
  }
5616
5703
  }
5617
5704
  return void 0;
@@ -5632,6 +5719,20 @@ async function generateAndSaveWallet() {
5632
5719
  `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
5633
5720
  );
5634
5721
  }
5722
+ console.log(`[ClawRouter]`);
5723
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
5724
+ console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
5725
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
5726
+ console.log(`[ClawRouter] Address : ${account.address}`);
5727
+ console.log(`[ClawRouter] Key file: ${WALLET_FILE}`);
5728
+ console.log(`[ClawRouter]`);
5729
+ console.log(`[ClawRouter] To back up, run in OpenClaw:`);
5730
+ console.log(`[ClawRouter] /wallet export`);
5731
+ console.log(`[ClawRouter]`);
5732
+ console.log(`[ClawRouter] To restore on another machine:`);
5733
+ console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
5734
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
5735
+ console.log(`[ClawRouter]`);
5635
5736
  return { key, address: account.address };
5636
5737
  }
5637
5738
  async function resolveOrGenerateWalletKey() {
@@ -5792,7 +5893,9 @@ function printDiagnostics(result) {
5792
5893
  console.log("System");
5793
5894
  console.log(` ${green(`OS: ${result.system.os}`)}`);
5794
5895
  console.log(` ${green(`Node: ${result.system.nodeVersion}`)}`);
5795
- console.log(` ${green(`Memory: ${result.system.memoryFree} free / ${result.system.memoryTotal}`)}`);
5896
+ console.log(
5897
+ ` ${green(`Memory: ${result.system.memoryFree} free / ${result.system.memoryTotal}`)}`
5898
+ );
5796
5899
  console.log("\nWallet");
5797
5900
  if (result.wallet.exists && result.wallet.valid) {
5798
5901
  console.log(` ${green(`Key: ${WALLET_FILE} (${result.wallet.source})`)}`);
@@ -5811,7 +5914,9 @@ function printDiagnostics(result) {
5811
5914
  }
5812
5915
  console.log("\nNetwork");
5813
5916
  if (result.network.blockrunApi.reachable) {
5814
- console.log(` ${green(`BlockRun API: reachable (${result.network.blockrunApi.latencyMs}ms)`)}`);
5917
+ console.log(
5918
+ ` ${green(`BlockRun API: reachable (${result.network.blockrunApi.latencyMs}ms)`)}`
5919
+ );
5815
5920
  } else {
5816
5921
  console.log(` ${red("BlockRun API: unreachable")}`);
5817
5922
  }
@@ -5821,7 +5926,9 @@ function printDiagnostics(result) {
5821
5926
  console.log(` ${red(`Local proxy: not running on :${result.network.localProxy.port}`)}`);
5822
5927
  }
5823
5928
  console.log("\nLogs");
5824
- console.log(` ${green(`Last 24h: ${result.logs.requestsLast24h} requests, ${result.logs.costLast24h} spent`)}`);
5929
+ console.log(
5930
+ ` ${green(`Last 24h: ${result.logs.requestsLast24h} requests, ${result.logs.costLast24h} spent`)}`
5931
+ );
5825
5932
  if (result.logs.errorsFound > 0) {
5826
5933
  console.log(` ${yellow(`${result.logs.errorsFound} errors found in logs`)}`);
5827
5934
  }
@@ -5940,6 +6047,36 @@ async function runDoctor(userQuestion, model = "sonnet") {
5940
6047
  await analyzeWithAI(result, userQuestion, model);
5941
6048
  }
5942
6049
 
6050
+ // src/partners/registry.ts
6051
+ var PARTNER_SERVICES = [
6052
+ {
6053
+ id: "x_users_lookup",
6054
+ name: "Twitter/X User Lookup",
6055
+ partner: "AttentionVC",
6056
+ description: "ALWAYS use this tool to look up real-time Twitter/X user profiles. Call this when the user asks about any Twitter/X account, username, handle, follower count, verification status, bio, or profile. Do NOT answer Twitter/X user questions from memory \u2014 always fetch live data with this tool. Returns: follower count, verification badge, bio, location, join date. Accepts up to 100 usernames per request (without @ prefix).",
6057
+ proxyPath: "/x/users/lookup",
6058
+ method: "POST",
6059
+ params: [
6060
+ {
6061
+ name: "usernames",
6062
+ type: "string[]",
6063
+ description: 'Array of Twitter/X usernames to look up (without @ prefix). Example: ["elonmusk", "naval"]',
6064
+ required: true
6065
+ }
6066
+ ],
6067
+ pricing: {
6068
+ perUnit: "$0.001",
6069
+ unit: "user",
6070
+ minimum: "$0.01 (10 users)",
6071
+ maximum: "$0.10 (100 users)"
6072
+ },
6073
+ example: {
6074
+ input: { usernames: ["elonmusk", "naval", "balaboris"] },
6075
+ description: "Look up 3 Twitter/X user profiles"
6076
+ }
6077
+ }
6078
+ ];
6079
+
5943
6080
  // src/cli.ts
5944
6081
  function printHelp() {
5945
6082
  console.log(`
@@ -5948,6 +6085,7 @@ ClawRouter v${VERSION} - Smart LLM Router
5948
6085
  Usage:
5949
6086
  clawrouter [options]
5950
6087
  clawrouter doctor [opus] [question]
6088
+ clawrouter partners [test]
5951
6089
 
5952
6090
  Options:
5953
6091
  --version, -v Show version number
@@ -5957,6 +6095,8 @@ Options:
5957
6095
  Commands:
5958
6096
  doctor AI-powered diagnostics (default: Sonnet ~$0.003)
5959
6097
  doctor opus Use Opus for deeper analysis (~$0.01)
6098
+ partners List available partner APIs with pricing
6099
+ partners test Test partner API endpoints (expect 402 = alive)
5960
6100
 
5961
6101
  Examples:
5962
6102
  # Start standalone proxy
@@ -5982,7 +6122,14 @@ For more info: https://github.com/BlockRunAI/ClawRouter
5982
6122
  `);
5983
6123
  }
5984
6124
  function parseArgs(args) {
5985
- const result = { version: false, help: false, doctor: false, port: void 0 };
6125
+ const result = {
6126
+ version: false,
6127
+ help: false,
6128
+ doctor: false,
6129
+ partners: false,
6130
+ partnersTest: false,
6131
+ port: void 0
6132
+ };
5986
6133
  for (let i = 0; i < args.length; i++) {
5987
6134
  const arg = args[i];
5988
6135
  if (arg === "--version" || arg === "-v") {
@@ -5991,6 +6138,12 @@ function parseArgs(args) {
5991
6138
  result.help = true;
5992
6139
  } else if (arg === "doctor" || arg === "--doctor") {
5993
6140
  result.doctor = true;
6141
+ } else if (arg === "partners") {
6142
+ result.partners = true;
6143
+ if (args[i + 1] === "test") {
6144
+ result.partnersTest = true;
6145
+ i++;
6146
+ }
5994
6147
  } else if (arg === "--port" && args[i + 1]) {
5995
6148
  result.port = parseInt(args[i + 1], 10);
5996
6149
  i++;
@@ -6025,6 +6178,40 @@ async function main() {
6025
6178
  await runDoctor(userQuestion, model);
6026
6179
  process.exit(0);
6027
6180
  }
6181
+ if (args.partners) {
6182
+ if (PARTNER_SERVICES.length === 0) {
6183
+ console.log("No partner APIs available.");
6184
+ process.exit(0);
6185
+ }
6186
+ console.log(`
6187
+ ClawRouter Partner APIs (v${VERSION})
6188
+ `);
6189
+ for (const svc of PARTNER_SERVICES) {
6190
+ console.log(` ${svc.name} (${svc.partner})`);
6191
+ console.log(` ${svc.description}`);
6192
+ console.log(` Tool: blockrun_${svc.id}`);
6193
+ console.log(` Method: ${svc.method} /v1${svc.proxyPath}`);
6194
+ console.log(` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`);
6195
+ console.log();
6196
+ }
6197
+ if (args.partnersTest) {
6198
+ console.log("Testing partner endpoints...\n");
6199
+ const apiBase = "https://blockrun.ai/api";
6200
+ for (const svc of PARTNER_SERVICES) {
6201
+ const url = `${apiBase}/v1${svc.proxyPath}`;
6202
+ try {
6203
+ const response = await fetch(url, { method: "GET" });
6204
+ const status = response.status;
6205
+ const ok = status === 402 ? "alive (402 = payment required)" : `status ${status}`;
6206
+ console.log(` ${svc.id}: ${ok}`);
6207
+ } catch (err) {
6208
+ console.log(` ${svc.id}: error - ${err instanceof Error ? err.message : String(err)}`);
6209
+ }
6210
+ }
6211
+ console.log();
6212
+ }
6213
+ process.exit(0);
6214
+ }
6028
6215
  const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
6029
6216
  if (source === "generated") {
6030
6217
  console.log(`[ClawRouter] Generated new wallet: ${address}`);