@blockrun/clawrouter 0.8.19 → 0.8.21
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 +26 -19
- package/dist/cli.js +214 -72
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +217 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,17 +14,17 @@ One wallet, 30+ models, zero API keys.
|
|
|
14
14
|
[Docs](https://blockrun.ai/docs) · [Models](https://blockrun.ai/models) · [Configuration](docs/configuration.md) · [Features](docs/features.md) · [Windows](docs/windows-installation.md) · [Troubleshooting](docs/troubleshooting.md) · [Telegram](https://t.me/blockrunAI) · [X](https://x.com/BlockRunAI)
|
|
15
15
|
|
|
16
16
|
**Winner — Agentic Commerce Track** at the [USDC AI Agent Hackathon](https://x.com/USDC/status/2021625822294216977)<br>
|
|
17
|
-
|
|
17
|
+
_The world's first hackathon run entirely by AI agents, powered by USDC_
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
```
|
|
24
|
-
"What is 2+2?" →
|
|
25
|
-
"Summarize this article" →
|
|
26
|
-
"Build a React component" →
|
|
27
|
-
"Prove this theorem" →
|
|
24
|
+
"What is 2+2?" → NVIDIA Kimi $0.001/M saved ~100%
|
|
25
|
+
"Summarize this article" → Grok Code Fast $1.50/M saved 94%
|
|
26
|
+
"Build a React component" → Gemini 2.5 Pro $10.00/M best balance
|
|
27
|
+
"Prove this theorem" → Grok 4.1 Fast $0.50/M reasoning
|
|
28
28
|
"Run 50 parallel searches"→ Kimi K2.5 $2.40/M agentic swarm
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -112,18 +112,18 @@ Request → Weighted Scorer (15 dimensions)
|
|
|
112
112
|
└── Low confidence → Default to MEDIUM tier → Done
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
No external classifier calls. Ambiguous queries default to the MEDIUM tier (
|
|
115
|
+
No external classifier calls. Ambiguous queries default to the MEDIUM tier (Grok Code Fast) — fast, cheap, and good enough for most tasks.
|
|
116
116
|
|
|
117
117
|
**Deep dive:** [15-dimension scoring weights](docs/configuration.md#scoring-weights) | [Architecture](docs/architecture.md)
|
|
118
118
|
|
|
119
119
|
### Tier → Model Mapping
|
|
120
120
|
|
|
121
|
-
| Tier | Primary Model
|
|
122
|
-
| --------- |
|
|
123
|
-
| SIMPLE |
|
|
124
|
-
| MEDIUM | grok-code-fast-1
|
|
125
|
-
| COMPLEX | gemini-2.5-pro
|
|
126
|
-
| REASONING | grok-4-fast-reasoning | $0.50 | **
|
|
121
|
+
| Tier | Primary Model | Cost/M | Savings vs Opus |
|
|
122
|
+
| --------- | ----------------------- | ------ | --------------- |
|
|
123
|
+
| SIMPLE | nvidia/kimi-k2.5 | $0.001 | **~100%** |
|
|
124
|
+
| MEDIUM | grok-code-fast-1 | $1.50 | **94.0%** |
|
|
125
|
+
| COMPLEX | gemini-2.5-pro | $10.00 | **60.0%** |
|
|
126
|
+
| REASONING | grok-4-1-fast-reasoning | $0.50 | **98.0%** |
|
|
127
127
|
|
|
128
128
|
Special rule: 2+ reasoning markers → REASONING at 0.97 confidence.
|
|
129
129
|
|
|
@@ -137,6 +137,7 @@ ClawRouter v0.5+ includes intelligent features that work automatically:
|
|
|
137
137
|
- **Model aliases** — `/model free`, `/model sonnet`, `/model grok`
|
|
138
138
|
- **Session persistence** — pins model for multi-turn conversations
|
|
139
139
|
- **Free tier fallback** — keeps working when wallet is empty
|
|
140
|
+
- **Auto-update check** — notifies you when a new version is available
|
|
140
141
|
|
|
141
142
|
**Full details:** [docs/features.md](docs/features.md)
|
|
142
143
|
|
|
@@ -144,13 +145,13 @@ ClawRouter v0.5+ includes intelligent features that work automatically:
|
|
|
144
145
|
|
|
145
146
|
| Tier | % of Traffic | Cost/M |
|
|
146
147
|
| ------------------- | ------------ | ----------- |
|
|
147
|
-
| SIMPLE | ~45% | $0.
|
|
148
|
-
| MEDIUM | ~35% | $
|
|
149
|
-
| COMPLEX | ~15% | $
|
|
150
|
-
| REASONING | ~5% | $
|
|
151
|
-
| **Blended average** | | **$
|
|
148
|
+
| SIMPLE | ~45% | $0.001 |
|
|
149
|
+
| MEDIUM | ~35% | $1.50 |
|
|
150
|
+
| COMPLEX | ~15% | $10.00 |
|
|
151
|
+
| REASONING | ~5% | $0.50 |
|
|
152
|
+
| **Blended average** | | **$2.05/M** |
|
|
152
153
|
|
|
153
|
-
Compared to **$
|
|
154
|
+
Compared to **$25/M** for Claude Opus = **92% savings** on a typical workload.
|
|
154
155
|
|
|
155
156
|
---
|
|
156
157
|
|
|
@@ -334,13 +335,18 @@ Agents shouldn't need a human to paste API keys. They should generate a wallet,
|
|
|
334
335
|
Quick checklist:
|
|
335
336
|
|
|
336
337
|
```bash
|
|
337
|
-
# Check version (should be 0.
|
|
338
|
+
# Check version (should be 0.8.20+)
|
|
338
339
|
cat ~/.openclaw/extensions/clawrouter/package.json | grep version
|
|
339
340
|
|
|
340
341
|
# Check proxy running
|
|
341
342
|
curl http://localhost:8402/health
|
|
343
|
+
|
|
344
|
+
# Update to latest version
|
|
345
|
+
curl -fsSL https://blockrun.ai/ClawRouter-update | bash
|
|
342
346
|
```
|
|
343
347
|
|
|
348
|
+
ClawRouter automatically checks for updates on startup and shows a notification if a newer version is available.
|
|
349
|
+
|
|
344
350
|
**Full guide:** [docs/troubleshooting.md](docs/troubleshooting.md)
|
|
345
351
|
|
|
346
352
|
---
|
|
@@ -374,6 +380,7 @@ BLOCKRUN_WALLET_KEY=0x... npx tsx test-e2e.ts
|
|
|
374
380
|
- [x] Cost tracking — /stats command with savings dashboard
|
|
375
381
|
- [x] Model aliases — `/model free`, `/model sonnet`, `/model grok`, etc.
|
|
376
382
|
- [x] Free tier — gpt-oss-120b for $0 when wallet is empty
|
|
383
|
+
- [x] Auto-update — startup version check with one-command update
|
|
377
384
|
- [ ] Cascade routing — try cheap model first, escalate on low quality
|
|
378
385
|
- [ ] Spend controls — daily/monthly budgets
|
|
379
386
|
- [ ] Remote analytics — cost tracking at blockrun.ai
|
package/dist/cli.js
CHANGED
|
@@ -439,7 +439,7 @@ function calibrateConfidence(distance, steepness) {
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
// src/router/selector.ts
|
|
442
|
-
function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens) {
|
|
442
|
+
function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
|
|
443
443
|
const tierConfig = tierConfigs[tier];
|
|
444
444
|
const model = tierConfig.primary;
|
|
445
445
|
const pricing = modelPricing.get(model);
|
|
@@ -448,13 +448,13 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
|
|
|
448
448
|
const inputCost = estimatedInputTokens / 1e6 * inputPrice;
|
|
449
449
|
const outputCost = maxOutputTokens / 1e6 * outputPrice;
|
|
450
450
|
const costEstimate = inputCost + outputCost;
|
|
451
|
-
const opusPricing = modelPricing.get("anthropic/claude-opus-4");
|
|
451
|
+
const opusPricing = modelPricing.get("anthropic/claude-opus-4.5");
|
|
452
452
|
const opusInputPrice = opusPricing?.inputPrice ?? 0;
|
|
453
453
|
const opusOutputPrice = opusPricing?.outputPrice ?? 0;
|
|
454
454
|
const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
|
|
455
455
|
const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
|
|
456
456
|
const baselineCost = baselineInput + baselineOutput;
|
|
457
|
-
const savings = baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
457
|
+
const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
458
458
|
return {
|
|
459
459
|
model,
|
|
460
460
|
tier,
|
|
@@ -470,20 +470,20 @@ function getFallbackChain(tier, tierConfigs) {
|
|
|
470
470
|
const config = tierConfigs[tier];
|
|
471
471
|
return [config.primary, ...config.fallback];
|
|
472
472
|
}
|
|
473
|
-
function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutputTokens) {
|
|
473
|
+
function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
|
|
474
474
|
const pricing = modelPricing.get(model);
|
|
475
475
|
const inputPrice = pricing?.inputPrice ?? 0;
|
|
476
476
|
const outputPrice = pricing?.outputPrice ?? 0;
|
|
477
477
|
const inputCost = estimatedInputTokens / 1e6 * inputPrice;
|
|
478
478
|
const outputCost = maxOutputTokens / 1e6 * outputPrice;
|
|
479
479
|
const costEstimate = inputCost + outputCost;
|
|
480
|
-
const opusPricing = modelPricing.get("anthropic/claude-opus-4");
|
|
480
|
+
const opusPricing = modelPricing.get("anthropic/claude-opus-4.5");
|
|
481
481
|
const opusInputPrice = opusPricing?.inputPrice ?? 0;
|
|
482
482
|
const opusOutputPrice = opusPricing?.outputPrice ?? 0;
|
|
483
483
|
const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
|
|
484
484
|
const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
|
|
485
485
|
const baselineCost = baselineInput + baselineOutput;
|
|
486
|
-
const savings = baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
486
|
+
const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
487
487
|
return { costEstimate, baselineCost, savings };
|
|
488
488
|
}
|
|
489
489
|
function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
|
|
@@ -1112,15 +1112,17 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1112
1112
|
// Tier boundaries on weighted score axis
|
|
1113
1113
|
tierBoundaries: {
|
|
1114
1114
|
simpleMedium: 0,
|
|
1115
|
-
mediumComplex: 0.
|
|
1116
|
-
|
|
1117
|
-
|
|
1115
|
+
mediumComplex: 0.3,
|
|
1116
|
+
// Raised from 0.18 - prevent simple tasks from reaching expensive COMPLEX tier
|
|
1117
|
+
complexReasoning: 0.5
|
|
1118
|
+
// Raised from 0.4 - reserve for true reasoning tasks
|
|
1118
1119
|
},
|
|
1119
1120
|
// Sigmoid steepness for confidence calibration
|
|
1120
1121
|
confidenceSteepness: 12,
|
|
1121
1122
|
// Below this confidence → ambiguous (null tier)
|
|
1122
1123
|
confidenceThreshold: 0.7
|
|
1123
1124
|
},
|
|
1125
|
+
// Auto (balanced) tier configs - current default smart routing
|
|
1124
1126
|
tiers: {
|
|
1125
1127
|
SIMPLE: {
|
|
1126
1128
|
primary: "nvidia/kimi-k2.5",
|
|
@@ -1129,7 +1131,9 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1129
1131
|
"google/gemini-2.5-flash",
|
|
1130
1132
|
"nvidia/gpt-oss-120b",
|
|
1131
1133
|
"nvidia/gpt-oss-20b",
|
|
1132
|
-
"deepseek/deepseek-chat"
|
|
1134
|
+
"deepseek/deepseek-chat",
|
|
1135
|
+
"xai/grok-code-fast-1"
|
|
1136
|
+
// Added for better quality fallback
|
|
1133
1137
|
]
|
|
1134
1138
|
},
|
|
1135
1139
|
MEDIUM: {
|
|
@@ -1144,7 +1148,8 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1144
1148
|
},
|
|
1145
1149
|
COMPLEX: {
|
|
1146
1150
|
primary: "google/gemini-2.5-pro",
|
|
1147
|
-
fallback: ["
|
|
1151
|
+
fallback: ["xai/grok-4-0709", "openai/gpt-4o", "openai/gpt-5.2", "anthropic/claude-sonnet-4"]
|
|
1152
|
+
// Grok first for cost efficiency, Sonnet as last resort
|
|
1148
1153
|
},
|
|
1149
1154
|
REASONING: {
|
|
1150
1155
|
primary: "xai/grok-4-1-fast-reasoning",
|
|
@@ -1158,6 +1163,52 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1158
1163
|
]
|
|
1159
1164
|
}
|
|
1160
1165
|
},
|
|
1166
|
+
// Eco tier configs - ultra cost-optimized (blockrun/eco)
|
|
1167
|
+
ecoTiers: {
|
|
1168
|
+
SIMPLE: {
|
|
1169
|
+
primary: "nvidia/kimi-k2.5",
|
|
1170
|
+
// $0.001/$0.001
|
|
1171
|
+
fallback: ["deepseek/deepseek-chat", "nvidia/gpt-oss-120b", "nvidia/gpt-oss-20b"]
|
|
1172
|
+
},
|
|
1173
|
+
MEDIUM: {
|
|
1174
|
+
primary: "deepseek/deepseek-chat",
|
|
1175
|
+
// $0.14/$0.28
|
|
1176
|
+
fallback: ["xai/grok-code-fast-1", "google/gemini-2.5-flash", "nvidia/kimi-k2.5"]
|
|
1177
|
+
},
|
|
1178
|
+
COMPLEX: {
|
|
1179
|
+
primary: "xai/grok-4-0709",
|
|
1180
|
+
// $0.20/$1.50
|
|
1181
|
+
fallback: ["deepseek/deepseek-chat", "google/gemini-2.5-flash", "openai/gpt-4o-mini"]
|
|
1182
|
+
},
|
|
1183
|
+
REASONING: {
|
|
1184
|
+
primary: "deepseek/deepseek-reasoner",
|
|
1185
|
+
// $0.55/$2.19
|
|
1186
|
+
fallback: ["xai/grok-4-fast-reasoning", "moonshot/kimi-k2.5"]
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
// Premium tier configs - best quality (blockrun/premium)
|
|
1190
|
+
premiumTiers: {
|
|
1191
|
+
SIMPLE: {
|
|
1192
|
+
primary: "google/gemini-2.5-flash",
|
|
1193
|
+
// $0.075/$0.30
|
|
1194
|
+
fallback: ["openai/gpt-4o-mini", "anthropic/claude-haiku-4.5", "moonshot/kimi-k2.5"]
|
|
1195
|
+
},
|
|
1196
|
+
MEDIUM: {
|
|
1197
|
+
primary: "openai/gpt-4o",
|
|
1198
|
+
// $2.50/$10
|
|
1199
|
+
fallback: ["google/gemini-2.5-pro", "anthropic/claude-sonnet-4", "xai/grok-4-0709"]
|
|
1200
|
+
},
|
|
1201
|
+
COMPLEX: {
|
|
1202
|
+
primary: "anthropic/claude-opus-4.5",
|
|
1203
|
+
// $15/$75
|
|
1204
|
+
fallback: ["openai/gpt-5.2", "anthropic/claude-sonnet-4", "google/gemini-2.5-pro"]
|
|
1205
|
+
},
|
|
1206
|
+
REASONING: {
|
|
1207
|
+
primary: "openai/o3",
|
|
1208
|
+
// $10/$40
|
|
1209
|
+
fallback: ["anthropic/claude-opus-4.5", "openai/o1", "google/gemini-2.5-pro"]
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1161
1212
|
// Agentic tier configs - models that excel at multi-step autonomous tasks
|
|
1162
1213
|
agenticTiers: {
|
|
1163
1214
|
SIMPLE: {
|
|
@@ -1199,21 +1250,34 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
1199
1250
|
const fullText = `${systemPrompt ?? ""} ${prompt}`;
|
|
1200
1251
|
const estimatedTokens = Math.ceil(fullText.length / 4);
|
|
1201
1252
|
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1253
|
+
const { routingProfile } = options;
|
|
1254
|
+
let tierConfigs;
|
|
1255
|
+
let profileSuffix = "";
|
|
1256
|
+
if (routingProfile === "eco" && config.ecoTiers) {
|
|
1257
|
+
tierConfigs = config.ecoTiers;
|
|
1258
|
+
profileSuffix = " | eco";
|
|
1259
|
+
} else if (routingProfile === "premium" && config.premiumTiers) {
|
|
1260
|
+
tierConfigs = config.premiumTiers;
|
|
1261
|
+
profileSuffix = " | premium";
|
|
1262
|
+
} else {
|
|
1263
|
+
const agenticScore = ruleResult.agenticScore ?? 0;
|
|
1264
|
+
const isAutoAgentic = agenticScore >= 0.5;
|
|
1265
|
+
const isExplicitAgentic = config.overrides.agenticMode ?? false;
|
|
1266
|
+
const useAgenticTiers = (isAutoAgentic || isExplicitAgentic) && config.agenticTiers != null;
|
|
1267
|
+
tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
|
|
1268
|
+
profileSuffix = useAgenticTiers ? " | agentic" : "";
|
|
1269
|
+
}
|
|
1207
1270
|
if (estimatedTokens > config.overrides.maxTokensForceComplex) {
|
|
1208
1271
|
return selectModel(
|
|
1209
1272
|
"COMPLEX",
|
|
1210
1273
|
0.95,
|
|
1211
1274
|
"rules",
|
|
1212
|
-
`Input exceeds ${config.overrides.maxTokensForceComplex} tokens${
|
|
1275
|
+
`Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`,
|
|
1213
1276
|
tierConfigs,
|
|
1214
1277
|
modelPricing,
|
|
1215
1278
|
estimatedTokens,
|
|
1216
|
-
maxOutputTokens
|
|
1279
|
+
maxOutputTokens,
|
|
1280
|
+
routingProfile
|
|
1217
1281
|
);
|
|
1218
1282
|
}
|
|
1219
1283
|
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
@@ -1237,11 +1301,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
1237
1301
|
tier = minTier;
|
|
1238
1302
|
}
|
|
1239
1303
|
}
|
|
1240
|
-
|
|
1241
|
-
reasoning += " | auto-agentic";
|
|
1242
|
-
} else if (isExplicitAgentic) {
|
|
1243
|
-
reasoning += " | agentic";
|
|
1244
|
-
}
|
|
1304
|
+
reasoning += profileSuffix;
|
|
1245
1305
|
return selectModel(
|
|
1246
1306
|
tier,
|
|
1247
1307
|
confidence,
|
|
@@ -1250,7 +1310,8 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
1250
1310
|
tierConfigs,
|
|
1251
1311
|
modelPricing,
|
|
1252
1312
|
estimatedTokens,
|
|
1253
|
-
maxOutputTokens
|
|
1313
|
+
maxOutputTokens,
|
|
1314
|
+
routingProfile
|
|
1254
1315
|
);
|
|
1255
1316
|
}
|
|
1256
1317
|
|
|
@@ -1297,16 +1358,40 @@ function resolveModelAlias(model) {
|
|
|
1297
1358
|
return model;
|
|
1298
1359
|
}
|
|
1299
1360
|
var BLOCKRUN_MODELS = [
|
|
1300
|
-
// Smart routing meta-
|
|
1361
|
+
// Smart routing meta-models — proxy replaces with actual model
|
|
1301
1362
|
// NOTE: Model IDs are WITHOUT provider prefix (OpenClaw adds "blockrun/" automatically)
|
|
1302
1363
|
{
|
|
1303
1364
|
id: "auto",
|
|
1304
|
-
name: "
|
|
1365
|
+
name: "Auto (Smart Router - Balanced)",
|
|
1366
|
+
inputPrice: 0,
|
|
1367
|
+
outputPrice: 0,
|
|
1368
|
+
contextWindow: 105e4,
|
|
1369
|
+
maxOutput: 128e3
|
|
1370
|
+
},
|
|
1371
|
+
{
|
|
1372
|
+
id: "free",
|
|
1373
|
+
name: "Free (NVIDIA GPT-OSS-120B only)",
|
|
1374
|
+
inputPrice: 0,
|
|
1375
|
+
outputPrice: 0,
|
|
1376
|
+
contextWindow: 128e3,
|
|
1377
|
+
maxOutput: 4096
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
id: "eco",
|
|
1381
|
+
name: "Eco (Smart Router - Cost Optimized)",
|
|
1305
1382
|
inputPrice: 0,
|
|
1306
1383
|
outputPrice: 0,
|
|
1307
1384
|
contextWindow: 105e4,
|
|
1308
1385
|
maxOutput: 128e3
|
|
1309
1386
|
},
|
|
1387
|
+
{
|
|
1388
|
+
id: "premium",
|
|
1389
|
+
name: "Premium (Smart Router - Best Quality)",
|
|
1390
|
+
inputPrice: 0,
|
|
1391
|
+
outputPrice: 0,
|
|
1392
|
+
contextWindow: 2e6,
|
|
1393
|
+
maxOutput: 2e5
|
|
1394
|
+
},
|
|
1310
1395
|
// OpenAI GPT-5 Family
|
|
1311
1396
|
{
|
|
1312
1397
|
id: "openai/gpt-5.2",
|
|
@@ -1608,8 +1693,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1608
1693
|
{
|
|
1609
1694
|
id: "xai/grok-4-0709",
|
|
1610
1695
|
name: "Grok 4 (0709)",
|
|
1611
|
-
inputPrice:
|
|
1612
|
-
outputPrice:
|
|
1696
|
+
inputPrice: 0.2,
|
|
1697
|
+
outputPrice: 1.5,
|
|
1613
1698
|
contextWindow: 131072,
|
|
1614
1699
|
maxOutput: 16384,
|
|
1615
1700
|
reasoning: true
|
|
@@ -2272,7 +2357,16 @@ async function checkForUpdates() {
|
|
|
2272
2357
|
// src/proxy.ts
|
|
2273
2358
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
2274
2359
|
var AUTO_MODEL = "blockrun/auto";
|
|
2275
|
-
var
|
|
2360
|
+
var ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
2361
|
+
"blockrun/free",
|
|
2362
|
+
"free",
|
|
2363
|
+
"blockrun/eco",
|
|
2364
|
+
"eco",
|
|
2365
|
+
"blockrun/auto",
|
|
2366
|
+
"auto",
|
|
2367
|
+
"blockrun/premium",
|
|
2368
|
+
"premium"
|
|
2369
|
+
]);
|
|
2276
2370
|
var FREE_MODEL = "nvidia/gpt-oss-120b";
|
|
2277
2371
|
var HEARTBEAT_INTERVAL_MS = 2e3;
|
|
2278
2372
|
var DEFAULT_REQUEST_TIMEOUT_MS = 18e4;
|
|
@@ -2312,8 +2406,28 @@ function transformPaymentError(errorBody) {
|
|
|
2312
2406
|
});
|
|
2313
2407
|
}
|
|
2314
2408
|
}
|
|
2409
|
+
if (innerJson.invalidReason === "invalid_payload") {
|
|
2410
|
+
return JSON.stringify({
|
|
2411
|
+
error: {
|
|
2412
|
+
message: "Payment signature invalid. This may be a temporary issue.",
|
|
2413
|
+
type: "invalid_payload",
|
|
2414
|
+
help: "Try again. If this persists, reinstall ClawRouter: curl -fsSL https://blockrun.ai/ClawRouter-update | bash"
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2315
2418
|
}
|
|
2316
2419
|
}
|
|
2420
|
+
if (parsed.error === "Settlement failed" || parsed.details?.includes("Settlement failed")) {
|
|
2421
|
+
const details = parsed.details || "";
|
|
2422
|
+
const gasError = details.includes("unable to estimate gas");
|
|
2423
|
+
return JSON.stringify({
|
|
2424
|
+
error: {
|
|
2425
|
+
message: gasError ? "Payment failed: network congestion or gas issue. Try again." : "Payment settlement failed. Try again in a moment.",
|
|
2426
|
+
type: "settlement_failed",
|
|
2427
|
+
help: "This is usually temporary. If it persists, try: /model free"
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2317
2431
|
} catch {
|
|
2318
2432
|
}
|
|
2319
2433
|
return errorBody;
|
|
@@ -2934,6 +3048,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
2934
3048
|
let isStreaming = false;
|
|
2935
3049
|
let modelId = "";
|
|
2936
3050
|
let maxTokens = 4096;
|
|
3051
|
+
let routingProfile = null;
|
|
2937
3052
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
2938
3053
|
if (isChatCompletion && body.length > 0) {
|
|
2939
3054
|
try {
|
|
@@ -2949,58 +3064,83 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
2949
3064
|
const normalizedModel = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
|
|
2950
3065
|
const resolvedModel = resolveModelAlias(normalizedModel);
|
|
2951
3066
|
const wasAlias = resolvedModel !== normalizedModel;
|
|
2952
|
-
const
|
|
3067
|
+
const isRoutingProfile = ROUTING_PROFILES.has(normalizedModel);
|
|
3068
|
+
if (isRoutingProfile) {
|
|
3069
|
+
const profileName = normalizedModel.replace("blockrun/", "");
|
|
3070
|
+
routingProfile = profileName;
|
|
3071
|
+
}
|
|
2953
3072
|
console.log(
|
|
2954
|
-
`[ClawRouter] Received model: "${parsed.model}" -> normalized: "${normalizedModel}"${wasAlias ? ` -> alias: "${resolvedModel}"` : ""}
|
|
3073
|
+
`[ClawRouter] Received model: "${parsed.model}" -> normalized: "${normalizedModel}"${wasAlias ? ` -> alias: "${resolvedModel}"` : ""}${routingProfile ? `, profile: ${routingProfile}` : ""}`
|
|
2955
3074
|
);
|
|
2956
|
-
if (wasAlias && !
|
|
3075
|
+
if (wasAlias && !isRoutingProfile) {
|
|
2957
3076
|
parsed.model = resolvedModel;
|
|
2958
3077
|
modelId = resolvedModel;
|
|
2959
3078
|
bodyModified = true;
|
|
2960
3079
|
}
|
|
2961
|
-
if (
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
console.log(
|
|
2968
|
-
`[ClawRouter] Session ${sessionId?.slice(0, 8)}... using pinned model: ${existingSession.model}`
|
|
2969
|
-
);
|
|
2970
|
-
parsed.model = existingSession.model;
|
|
2971
|
-
modelId = existingSession.model;
|
|
3080
|
+
if (isRoutingProfile) {
|
|
3081
|
+
if (routingProfile === "free") {
|
|
3082
|
+
const freeModel = "nvidia/gpt-oss-120b";
|
|
3083
|
+
console.log(`[ClawRouter] Free profile - using ${freeModel} directly`);
|
|
3084
|
+
parsed.model = freeModel;
|
|
3085
|
+
modelId = freeModel;
|
|
2972
3086
|
bodyModified = true;
|
|
2973
|
-
|
|
3087
|
+
await logUsage({
|
|
3088
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3089
|
+
model: freeModel,
|
|
3090
|
+
tier: "SIMPLE",
|
|
3091
|
+
cost: 0,
|
|
3092
|
+
baselineCost: 0,
|
|
3093
|
+
savings: 1,
|
|
3094
|
+
// 100% savings
|
|
3095
|
+
latencyMs: 0
|
|
3096
|
+
});
|
|
2974
3097
|
} else {
|
|
2975
|
-
const
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
lastUserMsg = messages[i];
|
|
2981
|
-
break;
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
2984
|
-
}
|
|
2985
|
-
const systemMsg = messages?.find((m) => m.role === "system");
|
|
2986
|
-
const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
|
|
2987
|
-
const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
|
|
2988
|
-
const tools = parsed.tools;
|
|
2989
|
-
const hasTools = Array.isArray(tools) && tools.length > 0;
|
|
2990
|
-
if (hasTools) {
|
|
2991
|
-
console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);
|
|
2992
|
-
}
|
|
2993
|
-
routingDecision = route(prompt, systemPrompt, maxTokens, routerOpts);
|
|
2994
|
-
parsed.model = routingDecision.model;
|
|
2995
|
-
modelId = routingDecision.model;
|
|
2996
|
-
bodyModified = true;
|
|
2997
|
-
if (sessionId) {
|
|
2998
|
-
sessionStore.setSession(sessionId, routingDecision.model, routingDecision.tier);
|
|
3098
|
+
const sessionId = getSessionId(
|
|
3099
|
+
req.headers
|
|
3100
|
+
);
|
|
3101
|
+
const existingSession = sessionId ? sessionStore.getSession(sessionId) : void 0;
|
|
3102
|
+
if (existingSession) {
|
|
2999
3103
|
console.log(
|
|
3000
|
-
`[ClawRouter] Session ${sessionId
|
|
3104
|
+
`[ClawRouter] Session ${sessionId?.slice(0, 8)}... using pinned model: ${existingSession.model}`
|
|
3001
3105
|
);
|
|
3106
|
+
parsed.model = existingSession.model;
|
|
3107
|
+
modelId = existingSession.model;
|
|
3108
|
+
bodyModified = true;
|
|
3109
|
+
sessionStore.touchSession(sessionId);
|
|
3110
|
+
} else {
|
|
3111
|
+
const messages = parsed.messages;
|
|
3112
|
+
let lastUserMsg;
|
|
3113
|
+
if (messages) {
|
|
3114
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3115
|
+
if (messages[i].role === "user") {
|
|
3116
|
+
lastUserMsg = messages[i];
|
|
3117
|
+
break;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
const systemMsg = messages?.find((m) => m.role === "system");
|
|
3122
|
+
const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
|
|
3123
|
+
const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
|
|
3124
|
+
const tools = parsed.tools;
|
|
3125
|
+
const hasTools = Array.isArray(tools) && tools.length > 0;
|
|
3126
|
+
if (hasTools) {
|
|
3127
|
+
console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);
|
|
3128
|
+
}
|
|
3129
|
+
routingDecision = route(prompt, systemPrompt, maxTokens, {
|
|
3130
|
+
...routerOpts,
|
|
3131
|
+
routingProfile: routingProfile ?? void 0
|
|
3132
|
+
});
|
|
3133
|
+
parsed.model = routingDecision.model;
|
|
3134
|
+
modelId = routingDecision.model;
|
|
3135
|
+
bodyModified = true;
|
|
3136
|
+
if (sessionId) {
|
|
3137
|
+
sessionStore.setSession(sessionId, routingDecision.model, routingDecision.tier);
|
|
3138
|
+
console.log(
|
|
3139
|
+
`[ClawRouter] Session ${sessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`
|
|
3140
|
+
);
|
|
3141
|
+
}
|
|
3142
|
+
options.onRouted?.(routingDecision);
|
|
3002
3143
|
}
|
|
3003
|
-
options.onRouted?.(routingDecision);
|
|
3004
3144
|
}
|
|
3005
3145
|
}
|
|
3006
3146
|
if (bodyModified) {
|
|
@@ -3184,7 +3324,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
3184
3324
|
actualModelUsed,
|
|
3185
3325
|
routerOpts.modelPricing,
|
|
3186
3326
|
estimatedInputTokens,
|
|
3187
|
-
maxTokens
|
|
3327
|
+
maxTokens,
|
|
3328
|
+
routingProfile ?? void 0
|
|
3188
3329
|
);
|
|
3189
3330
|
routingDecision = {
|
|
3190
3331
|
...routingDecision,
|
|
@@ -3393,7 +3534,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
3393
3534
|
routingDecision.model,
|
|
3394
3535
|
routerOpts.modelPricing,
|
|
3395
3536
|
estimatedInputTokens,
|
|
3396
|
-
maxTokens
|
|
3537
|
+
maxTokens,
|
|
3538
|
+
routingProfile ?? void 0
|
|
3397
3539
|
);
|
|
3398
3540
|
const costWithBuffer = accurateCosts.costEstimate * 1.2;
|
|
3399
3541
|
const baselineWithBuffer = accurateCosts.baselineCost * 1.2;
|