@blockrun/clawrouter 0.11.4 → 0.11.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/README.md +24 -0
- package/dist/cli.js +135 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.js +73 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/reinstall.sh +14 -12
- package/scripts/update.sh +6 -0
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ One wallet, 41+ models, zero API keys.
|
|
|
37
37
|
| ----------------------------------------- | ------------------------------- |
|
|
38
38
|
| [Quick Start](#-quick-start) | Install in 2 minutes |
|
|
39
39
|
| [Routing Profiles](#-routing-profiles) | eco / auto / premium / free |
|
|
40
|
+
| [Image Generation](#-image-generation) | /imagegen with 5 models |
|
|
40
41
|
| [How It Works](#-how-it-works) | 15-dimension local routing |
|
|
41
42
|
| [Models & Pricing](#-models--pricing) | 41+ models, full price list |
|
|
42
43
|
| [Screenshots](#-screenshots) | See it in action |
|
|
@@ -78,6 +79,28 @@ Choose your routing strategy with `/model <profile>`:
|
|
|
78
79
|
|
|
79
80
|
---
|
|
80
81
|
|
|
82
|
+
## 🎨 Image Generation
|
|
83
|
+
|
|
84
|
+
Generate images directly from chat with `/imagegen`:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
/imagegen a dog dancing on the beach
|
|
88
|
+
/imagegen --model dall-e-3 a futuristic city at sunset
|
|
89
|
+
/imagegen --model banana-pro --size 2048x2048 mountain landscape
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Model | Provider | Price | Max Size |
|
|
93
|
+
| ------------- | --------------------- | ----------- | --------- |
|
|
94
|
+
| `nano-banana` | Google Gemini Flash | $0.05/image | 1024x1024 |
|
|
95
|
+
| `banana-pro` | Google Gemini Pro | $0.10/image | 4096x4096 |
|
|
96
|
+
| `dall-e-3` | OpenAI DALL-E 3 | $0.04/image | 1792x1024 |
|
|
97
|
+
| `gpt-image` | OpenAI GPT Image 1 | $0.02/image | 1536x1024 |
|
|
98
|
+
| `flux` | Black Forest Flux 1.1 | $0.04/image | 1024x1024 |
|
|
99
|
+
|
|
100
|
+
Default model: `nano-banana`. Images are returned as hosted URLs for compatibility with Telegram, Discord, and other clients.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
81
104
|
## ⚡ How It Works
|
|
82
105
|
|
|
83
106
|
**100% local routing. <1ms latency. Zero external API calls.**
|
|
@@ -188,6 +211,7 @@ USDC stays in your wallet until spent — non-custodial. Price is visible in the
|
|
|
188
211
|
- **Coinbase:** Buy USDC, send to Base
|
|
189
212
|
- **Bridge:** Move USDC from any chain to Base
|
|
190
213
|
- **CEX:** Withdraw USDC to Base network
|
|
214
|
+
- **Credit card:** Don't have USDC? Reach out to [@bc1max on Telegram](https://t.me/bc1max) — we accept credit card payments
|
|
191
215
|
|
|
192
216
|
---
|
|
193
217
|
|
package/dist/cli.js
CHANGED
|
@@ -5331,13 +5331,13 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5331
5331
|
const raw = modelMatch[1];
|
|
5332
5332
|
const IMAGE_MODEL_ALIASES = {
|
|
5333
5333
|
"dall-e-3": "openai/dall-e-3",
|
|
5334
|
-
|
|
5335
|
-
|
|
5334
|
+
dalle3: "openai/dall-e-3",
|
|
5335
|
+
dalle: "openai/dall-e-3",
|
|
5336
5336
|
"gpt-image": "openai/gpt-image-1",
|
|
5337
5337
|
"gpt-image-1": "openai/gpt-image-1",
|
|
5338
|
-
|
|
5338
|
+
flux: "black-forest/flux-1.1-pro",
|
|
5339
5339
|
"flux-pro": "black-forest/flux-1.1-pro",
|
|
5340
|
-
|
|
5340
|
+
banana: "google/nano-banana",
|
|
5341
5341
|
"nano-banana": "google/nano-banana",
|
|
5342
5342
|
"banana-pro": "google/nano-banana-pro",
|
|
5343
5343
|
"nano-banana-pro": "google/nano-banana-pro"
|
|
@@ -5378,32 +5378,51 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5378
5378
|
"Cache-Control": "no-cache",
|
|
5379
5379
|
Connection: "keep-alive"
|
|
5380
5380
|
});
|
|
5381
|
-
res.write(
|
|
5381
|
+
res.write(
|
|
5382
|
+
`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: { role: "assistant", content: errorText }, finish_reason: null }] })}
|
|
5382
5383
|
|
|
5383
|
-
`
|
|
5384
|
-
|
|
5384
|
+
`
|
|
5385
|
+
);
|
|
5386
|
+
res.write(
|
|
5387
|
+
`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
|
|
5385
5388
|
|
|
5386
|
-
`
|
|
5389
|
+
`
|
|
5390
|
+
);
|
|
5387
5391
|
res.write("data: [DONE]\n\n");
|
|
5388
5392
|
res.end();
|
|
5389
5393
|
} else {
|
|
5390
5394
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5391
|
-
res.end(
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5395
|
+
res.end(
|
|
5396
|
+
JSON.stringify({
|
|
5397
|
+
id: completionId,
|
|
5398
|
+
object: "chat.completion",
|
|
5399
|
+
created: timestamp,
|
|
5400
|
+
model: "clawrouter/image",
|
|
5401
|
+
choices: [
|
|
5402
|
+
{
|
|
5403
|
+
index: 0,
|
|
5404
|
+
message: { role: "assistant", content: errorText },
|
|
5405
|
+
finish_reason: "stop"
|
|
5406
|
+
}
|
|
5407
|
+
],
|
|
5408
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
5409
|
+
})
|
|
5410
|
+
);
|
|
5399
5411
|
}
|
|
5400
5412
|
console.log(`[ClawRouter] /imagegen command \u2192 showing usage help`);
|
|
5401
5413
|
return;
|
|
5402
5414
|
}
|
|
5403
|
-
console.log(
|
|
5415
|
+
console.log(
|
|
5416
|
+
`[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`
|
|
5417
|
+
);
|
|
5404
5418
|
try {
|
|
5405
5419
|
const imageUpstreamUrl = `${apiBase}/v1/images/generations`;
|
|
5406
|
-
const imageBody = JSON.stringify({
|
|
5420
|
+
const imageBody = JSON.stringify({
|
|
5421
|
+
model: imageModel,
|
|
5422
|
+
prompt: imagePrompt,
|
|
5423
|
+
size: imageSize,
|
|
5424
|
+
n: 1
|
|
5425
|
+
});
|
|
5407
5426
|
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
5408
5427
|
method: "POST",
|
|
5409
5428
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -5428,8 +5447,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5428
5447
|
const hostedUrl = await uploadDataUriToHost(img.url);
|
|
5429
5448
|
lines.push(hostedUrl);
|
|
5430
5449
|
} catch (uploadErr) {
|
|
5431
|
-
console.error(
|
|
5432
|
-
|
|
5450
|
+
console.error(
|
|
5451
|
+
`[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
5452
|
+
);
|
|
5453
|
+
lines.push(
|
|
5454
|
+
"Image generated but upload failed. Try again or use --model dall-e-3."
|
|
5455
|
+
);
|
|
5433
5456
|
}
|
|
5434
5457
|
} else {
|
|
5435
5458
|
lines.push(img.url);
|
|
@@ -5450,33 +5473,47 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5450
5473
|
"Cache-Control": "no-cache",
|
|
5451
5474
|
Connection: "keep-alive"
|
|
5452
5475
|
});
|
|
5453
|
-
res.write(
|
|
5476
|
+
res.write(
|
|
5477
|
+
`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: { role: "assistant", content: responseText }, finish_reason: null }] })}
|
|
5454
5478
|
|
|
5455
|
-
`
|
|
5456
|
-
|
|
5479
|
+
`
|
|
5480
|
+
);
|
|
5481
|
+
res.write(
|
|
5482
|
+
`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
|
|
5457
5483
|
|
|
5458
|
-
`
|
|
5484
|
+
`
|
|
5485
|
+
);
|
|
5459
5486
|
res.write("data: [DONE]\n\n");
|
|
5460
5487
|
res.end();
|
|
5461
5488
|
} else {
|
|
5462
5489
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5463
|
-
res.end(
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5490
|
+
res.end(
|
|
5491
|
+
JSON.stringify({
|
|
5492
|
+
id: completionId,
|
|
5493
|
+
object: "chat.completion",
|
|
5494
|
+
created: timestamp,
|
|
5495
|
+
model: "clawrouter/image",
|
|
5496
|
+
choices: [
|
|
5497
|
+
{
|
|
5498
|
+
index: 0,
|
|
5499
|
+
message: { role: "assistant", content: responseText },
|
|
5500
|
+
finish_reason: "stop"
|
|
5501
|
+
}
|
|
5502
|
+
],
|
|
5503
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
5504
|
+
})
|
|
5505
|
+
);
|
|
5471
5506
|
}
|
|
5472
5507
|
} catch (err) {
|
|
5473
5508
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5474
5509
|
console.error(`[ClawRouter] /imagegen error: ${errMsg}`);
|
|
5475
5510
|
if (!res.headersSent) {
|
|
5476
5511
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
5477
|
-
res.end(
|
|
5478
|
-
|
|
5479
|
-
|
|
5512
|
+
res.end(
|
|
5513
|
+
JSON.stringify({
|
|
5514
|
+
error: { message: `Image generation failed: ${errMsg}`, type: "image_error" }
|
|
5515
|
+
})
|
|
5516
|
+
);
|
|
5480
5517
|
}
|
|
5481
5518
|
}
|
|
5482
5519
|
return;
|
|
@@ -5585,10 +5622,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5585
5622
|
const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
|
|
5586
5623
|
const toolCallNames = Array.isArray(lastAssistantMsg?.tool_calls) ? lastAssistantMsg.tool_calls.map((tc) => tc.function?.name).filter(Boolean) : void 0;
|
|
5587
5624
|
const contentHash = hashRequestContent(prompt, toolCallNames);
|
|
5588
|
-
const shouldEscalate = sessionStore.recordRequestHash(
|
|
5589
|
-
effectiveSessionId,
|
|
5590
|
-
contentHash
|
|
5591
|
-
);
|
|
5625
|
+
const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
|
|
5592
5626
|
if (shouldEscalate) {
|
|
5593
5627
|
const activeTierConfigs = (() => {
|
|
5594
5628
|
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
@@ -6293,6 +6327,45 @@ async function resolveOrGenerateWalletKey() {
|
|
|
6293
6327
|
return { key, address, source: "generated" };
|
|
6294
6328
|
}
|
|
6295
6329
|
|
|
6330
|
+
// src/report.ts
|
|
6331
|
+
async function generateReport(period, json = false) {
|
|
6332
|
+
const days = period === "daily" ? 1 : period === "weekly" ? 7 : 30;
|
|
6333
|
+
const stats = await getStats(days);
|
|
6334
|
+
if (json) {
|
|
6335
|
+
return JSON.stringify(stats, null, 2);
|
|
6336
|
+
}
|
|
6337
|
+
return formatMarkdownReport(period, days, stats);
|
|
6338
|
+
}
|
|
6339
|
+
function formatMarkdownReport(period, days, stats) {
|
|
6340
|
+
const lines = [];
|
|
6341
|
+
lines.push(`# ClawRouter ${capitalize(period)} Report`);
|
|
6342
|
+
lines.push(`**Period:** Last ${days} day${days > 1 ? "s" : ""}`);
|
|
6343
|
+
lines.push(`**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
6344
|
+
lines.push("");
|
|
6345
|
+
lines.push("## \u{1F4CA} Usage Summary");
|
|
6346
|
+
lines.push("");
|
|
6347
|
+
lines.push(`| Metric | Value |`);
|
|
6348
|
+
lines.push(`|--------|-------|`);
|
|
6349
|
+
lines.push(`| Total Requests | ${stats.totalRequests} |`);
|
|
6350
|
+
lines.push(`| Total Cost | $${stats.totalCost.toFixed(4)} |`);
|
|
6351
|
+
lines.push(`| Baseline Cost | $${stats.totalBaselineCost.toFixed(4)} |`);
|
|
6352
|
+
lines.push(`| **Savings** | **$${stats.totalSavings.toFixed(4)}** |`);
|
|
6353
|
+
lines.push(`| Savings % | ${stats.savingsPercentage.toFixed(1)}% |`);
|
|
6354
|
+
lines.push(`| Avg Latency | ${stats.avgLatencyMs.toFixed(0)}ms |`);
|
|
6355
|
+
lines.push("");
|
|
6356
|
+
lines.push("## \u{1F916} Model Distribution");
|
|
6357
|
+
lines.push("");
|
|
6358
|
+
const sortedModels = Object.entries(stats.byModel).sort((a, b) => b[1].count - a[1].count).slice(0, 10);
|
|
6359
|
+
for (const [model, data] of sortedModels) {
|
|
6360
|
+
lines.push(`- ${model}: ${data.count} reqs, $${data.cost.toFixed(4)}`);
|
|
6361
|
+
}
|
|
6362
|
+
lines.push("");
|
|
6363
|
+
return lines.join("\n");
|
|
6364
|
+
}
|
|
6365
|
+
function capitalize(str) {
|
|
6366
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
6367
|
+
}
|
|
6368
|
+
|
|
6296
6369
|
// src/doctor.ts
|
|
6297
6370
|
import { platform, arch, freemem, totalmem } from "os";
|
|
6298
6371
|
function formatBytes(bytes) {
|
|
@@ -6629,6 +6702,7 @@ Usage:
|
|
|
6629
6702
|
clawrouter [options]
|
|
6630
6703
|
clawrouter doctor [opus] [question]
|
|
6631
6704
|
clawrouter partners [test]
|
|
6705
|
+
clawrouter report [daily|weekly|monthly] [--json]
|
|
6632
6706
|
|
|
6633
6707
|
Options:
|
|
6634
6708
|
--version, -v Show version number
|
|
@@ -6671,6 +6745,9 @@ function parseArgs(args) {
|
|
|
6671
6745
|
doctor: false,
|
|
6672
6746
|
partners: false,
|
|
6673
6747
|
partnersTest: false,
|
|
6748
|
+
report: false,
|
|
6749
|
+
reportPeriod: "daily",
|
|
6750
|
+
reportJson: false,
|
|
6674
6751
|
port: void 0
|
|
6675
6752
|
};
|
|
6676
6753
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -6687,6 +6764,20 @@ function parseArgs(args) {
|
|
|
6687
6764
|
result.partnersTest = true;
|
|
6688
6765
|
i++;
|
|
6689
6766
|
}
|
|
6767
|
+
} else if (arg === "report") {
|
|
6768
|
+
result.report = true;
|
|
6769
|
+
const next = args[i + 1];
|
|
6770
|
+
if (next && ["daily", "weekly", "monthly"].includes(next)) {
|
|
6771
|
+
result.reportPeriod = next;
|
|
6772
|
+
i++;
|
|
6773
|
+
if (args[i + 1] === "--json") {
|
|
6774
|
+
result.reportJson = true;
|
|
6775
|
+
i++;
|
|
6776
|
+
}
|
|
6777
|
+
} else if (next === "--json") {
|
|
6778
|
+
result.reportJson = true;
|
|
6779
|
+
i++;
|
|
6780
|
+
}
|
|
6690
6781
|
} else if (arg === "--port" && args[i + 1]) {
|
|
6691
6782
|
result.port = parseInt(args[i + 1], 10);
|
|
6692
6783
|
i++;
|
|
@@ -6757,6 +6848,11 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
6757
6848
|
}
|
|
6758
6849
|
process.exit(0);
|
|
6759
6850
|
}
|
|
6851
|
+
if (args.report) {
|
|
6852
|
+
const report = await generateReport(args.reportPeriod, args.reportJson);
|
|
6853
|
+
console.log(report);
|
|
6854
|
+
process.exit(0);
|
|
6855
|
+
}
|
|
6760
6856
|
const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
|
|
6761
6857
|
if (source === "generated") {
|
|
6762
6858
|
console.log(`[ClawRouter] Generated new wallet: ${address}`);
|