@blockrun/clawrouter 0.10.5 → 0.10.6

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
@@ -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,
@@ -5792,7 +5867,9 @@ function printDiagnostics(result) {
5792
5867
  console.log("System");
5793
5868
  console.log(` ${green(`OS: ${result.system.os}`)}`);
5794
5869
  console.log(` ${green(`Node: ${result.system.nodeVersion}`)}`);
5795
- console.log(` ${green(`Memory: ${result.system.memoryFree} free / ${result.system.memoryTotal}`)}`);
5870
+ console.log(
5871
+ ` ${green(`Memory: ${result.system.memoryFree} free / ${result.system.memoryTotal}`)}`
5872
+ );
5796
5873
  console.log("\nWallet");
5797
5874
  if (result.wallet.exists && result.wallet.valid) {
5798
5875
  console.log(` ${green(`Key: ${WALLET_FILE} (${result.wallet.source})`)}`);
@@ -5811,7 +5888,9 @@ function printDiagnostics(result) {
5811
5888
  }
5812
5889
  console.log("\nNetwork");
5813
5890
  if (result.network.blockrunApi.reachable) {
5814
- console.log(` ${green(`BlockRun API: reachable (${result.network.blockrunApi.latencyMs}ms)`)}`);
5891
+ console.log(
5892
+ ` ${green(`BlockRun API: reachable (${result.network.blockrunApi.latencyMs}ms)`)}`
5893
+ );
5815
5894
  } else {
5816
5895
  console.log(` ${red("BlockRun API: unreachable")}`);
5817
5896
  }
@@ -5821,7 +5900,9 @@ function printDiagnostics(result) {
5821
5900
  console.log(` ${red(`Local proxy: not running on :${result.network.localProxy.port}`)}`);
5822
5901
  }
5823
5902
  console.log("\nLogs");
5824
- console.log(` ${green(`Last 24h: ${result.logs.requestsLast24h} requests, ${result.logs.costLast24h} spent`)}`);
5903
+ console.log(
5904
+ ` ${green(`Last 24h: ${result.logs.requestsLast24h} requests, ${result.logs.costLast24h} spent`)}`
5905
+ );
5825
5906
  if (result.logs.errorsFound > 0) {
5826
5907
  console.log(` ${yellow(`${result.logs.errorsFound} errors found in logs`)}`);
5827
5908
  }
@@ -5940,6 +6021,36 @@ async function runDoctor(userQuestion, model = "sonnet") {
5940
6021
  await analyzeWithAI(result, userQuestion, model);
5941
6022
  }
5942
6023
 
6024
+ // src/partners/registry.ts
6025
+ var PARTNER_SERVICES = [
6026
+ {
6027
+ id: "x_users_lookup",
6028
+ name: "Twitter/X User Lookup",
6029
+ partner: "AttentionVC",
6030
+ description: "Look up Twitter/X user profiles by username. Returns follower counts, verification status, bio, and more. Accepts up to 100 usernames per request.",
6031
+ proxyPath: "/x/users/lookup",
6032
+ method: "POST",
6033
+ params: [
6034
+ {
6035
+ name: "usernames",
6036
+ type: "string[]",
6037
+ description: 'Array of Twitter/X usernames to look up (without @ prefix). Example: ["elonmusk", "naval"]',
6038
+ required: true
6039
+ }
6040
+ ],
6041
+ pricing: {
6042
+ perUnit: "$0.001",
6043
+ unit: "user",
6044
+ minimum: "$0.01 (10 users)",
6045
+ maximum: "$0.10 (100 users)"
6046
+ },
6047
+ example: {
6048
+ input: { usernames: ["elonmusk", "naval", "balaboris"] },
6049
+ description: "Look up 3 Twitter/X user profiles"
6050
+ }
6051
+ }
6052
+ ];
6053
+
5943
6054
  // src/cli.ts
5944
6055
  function printHelp() {
5945
6056
  console.log(`
@@ -5948,6 +6059,7 @@ ClawRouter v${VERSION} - Smart LLM Router
5948
6059
  Usage:
5949
6060
  clawrouter [options]
5950
6061
  clawrouter doctor [opus] [question]
6062
+ clawrouter partners [test]
5951
6063
 
5952
6064
  Options:
5953
6065
  --version, -v Show version number
@@ -5957,6 +6069,8 @@ Options:
5957
6069
  Commands:
5958
6070
  doctor AI-powered diagnostics (default: Sonnet ~$0.003)
5959
6071
  doctor opus Use Opus for deeper analysis (~$0.01)
6072
+ partners List available partner APIs with pricing
6073
+ partners test Test partner API endpoints (expect 402 = alive)
5960
6074
 
5961
6075
  Examples:
5962
6076
  # Start standalone proxy
@@ -5982,7 +6096,14 @@ For more info: https://github.com/BlockRunAI/ClawRouter
5982
6096
  `);
5983
6097
  }
5984
6098
  function parseArgs(args) {
5985
- const result = { version: false, help: false, doctor: false, port: void 0 };
6099
+ const result = {
6100
+ version: false,
6101
+ help: false,
6102
+ doctor: false,
6103
+ partners: false,
6104
+ partnersTest: false,
6105
+ port: void 0
6106
+ };
5986
6107
  for (let i = 0; i < args.length; i++) {
5987
6108
  const arg = args[i];
5988
6109
  if (arg === "--version" || arg === "-v") {
@@ -5991,6 +6112,12 @@ function parseArgs(args) {
5991
6112
  result.help = true;
5992
6113
  } else if (arg === "doctor" || arg === "--doctor") {
5993
6114
  result.doctor = true;
6115
+ } else if (arg === "partners") {
6116
+ result.partners = true;
6117
+ if (args[i + 1] === "test") {
6118
+ result.partnersTest = true;
6119
+ i++;
6120
+ }
5994
6121
  } else if (arg === "--port" && args[i + 1]) {
5995
6122
  result.port = parseInt(args[i + 1], 10);
5996
6123
  i++;
@@ -6025,6 +6152,40 @@ async function main() {
6025
6152
  await runDoctor(userQuestion, model);
6026
6153
  process.exit(0);
6027
6154
  }
6155
+ if (args.partners) {
6156
+ if (PARTNER_SERVICES.length === 0) {
6157
+ console.log("No partner APIs available.");
6158
+ process.exit(0);
6159
+ }
6160
+ console.log(`
6161
+ ClawRouter Partner APIs (v${VERSION})
6162
+ `);
6163
+ for (const svc of PARTNER_SERVICES) {
6164
+ console.log(` ${svc.name} (${svc.partner})`);
6165
+ console.log(` ${svc.description}`);
6166
+ console.log(` Tool: blockrun_${svc.id}`);
6167
+ console.log(` Method: ${svc.method} /v1${svc.proxyPath}`);
6168
+ console.log(` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`);
6169
+ console.log();
6170
+ }
6171
+ if (args.partnersTest) {
6172
+ console.log("Testing partner endpoints...\n");
6173
+ const apiBase = "https://blockrun.ai/api";
6174
+ for (const svc of PARTNER_SERVICES) {
6175
+ const url = `${apiBase}/v1${svc.proxyPath}`;
6176
+ try {
6177
+ const response = await fetch(url, { method: "GET" });
6178
+ const status = response.status;
6179
+ const ok = status === 402 ? "alive (402 = payment required)" : `status ${status}`;
6180
+ console.log(` ${svc.id}: ${ok}`);
6181
+ } catch (err) {
6182
+ console.log(` ${svc.id}: error - ${err instanceof Error ? err.message : String(err)}`);
6183
+ }
6184
+ }
6185
+ console.log();
6186
+ }
6187
+ process.exit(0);
6188
+ }
6028
6189
  const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
6029
6190
  if (source === "generated") {
6030
6191
  console.log(`[ClawRouter] Generated new wallet: ${address}`);