@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 +169 -8
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +80 -1
- package/dist/index.js +245 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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,
|
|
@@ -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(
|
|
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(
|
|
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(
|
|
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 = {
|
|
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}`);
|