@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 +197 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +80 -1
- package/dist/index.js +286 -9
- package/dist/index.js.map +1 -1
- package/package.json +22 -22
- package/scripts/reinstall.sh +44 -0
- package/scripts/update.sh +168 -0
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
|
|
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
|
-
|
|
5644
|
+
const logModel = routingDecision?.model ?? modelId;
|
|
5645
|
+
if (logModel) {
|
|
5571
5646
|
const estimatedInputTokens = Math.ceil(body.length / 4);
|
|
5572
5647
|
const accurateCosts = calculateModelCost(
|
|
5573
|
-
|
|
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:
|
|
5584
|
-
tier: routingDecision
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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 = {
|
|
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}`);
|