@blockrun/clawrouter 0.1.0 → 0.2.0
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 +25 -31
- package/dist/index.d.ts +18 -1
- package/dist/index.js +282 -77
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# ClawRouter
|
|
4
4
|
|
|
5
|
-
**Save
|
|
5
|
+
**Save 78% on LLM costs. Automatically.**
|
|
6
6
|
|
|
7
7
|
Route every request to the cheapest model that can handle it.
|
|
8
8
|
One wallet, 30+ models, zero API keys.
|
|
@@ -19,10 +19,10 @@ One wallet, 30+ models, zero API keys.
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
"What is 2+2?" → Gemini Flash $0.60/M saved
|
|
23
|
-
"Summarize this article" → DeepSeek Chat $0.42/M saved
|
|
24
|
-
"Build a React component" → Claude
|
|
25
|
-
"Prove this theorem" → o3 $8.00/M saved
|
|
22
|
+
"What is 2+2?" → Gemini Flash $0.60/M saved 99%
|
|
23
|
+
"Summarize this article" → DeepSeek Chat $0.42/M saved 99%
|
|
24
|
+
"Build a React component" → Claude Opus $75.00/M best quality
|
|
25
|
+
"Prove this theorem" → o3 $8.00/M saved 89%
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
ClawRouter is a smart LLM router for [OpenClaw](https://github.com/openclaw/openclaw). It classifies each request, picks the cheapest model that can handle it, and pays per-request via [x402](https://x402.org) USDC micropayments on Base. No account, no API key — your wallet signs each payment.
|
|
@@ -63,13 +63,13 @@ Request → Rule-based scorer (< 1ms, free)
|
|
|
63
63
|
|
|
64
64
|
Score maps to a tier:
|
|
65
65
|
|
|
66
|
-
| Score | Tier | Primary Model | Output $/M | vs
|
|
66
|
+
| Score | Tier | Primary Model | Output $/M | vs Opus |
|
|
67
67
|
|-------|------|--------------|-----------|-----------|
|
|
68
|
-
| ≤ 0 | SIMPLE | gemini-2.5-flash | $0.60 | **
|
|
68
|
+
| ≤ 0 | SIMPLE | gemini-2.5-flash | $0.60 | **99% cheaper** |
|
|
69
69
|
| 1-2 | *ambiguous* | → LLM classifier | — | — |
|
|
70
|
-
| 3-4 | MEDIUM | deepseek-chat | $0.42 | **
|
|
71
|
-
| 5-6 | COMPLEX | claude-
|
|
72
|
-
| 7+ | REASONING | o3 | $8.00 | **
|
|
70
|
+
| 3-4 | MEDIUM | deepseek-chat | $0.42 | **99% cheaper** |
|
|
71
|
+
| 5-6 | COMPLEX | claude-opus-4 | $75.00 | best quality |
|
|
72
|
+
| 7+ | REASONING | o3 | $8.00 | **89% cheaper** |
|
|
73
73
|
|
|
74
74
|
### LLM Classifier Fallback
|
|
75
75
|
|
|
@@ -81,15 +81,15 @@ When rules score in the ambiguous zone (1-2), ClawRouter sends the first 500 cha
|
|
|
81
81
|
|------|-------------|-----------|
|
|
82
82
|
| SIMPLE | 40% | $0.60 |
|
|
83
83
|
| MEDIUM | 30% | $0.42 |
|
|
84
|
-
| COMPLEX | 20% | $
|
|
84
|
+
| COMPLEX | 20% | $75.00 |
|
|
85
85
|
| REASONING | 10% | $8.00 |
|
|
86
|
-
| **Weighted avg** | | **$
|
|
86
|
+
| **Weighted avg** | | **$16.17/M — 78% savings vs Claude Opus** |
|
|
87
87
|
|
|
88
88
|
Every routed request logs its decision:
|
|
89
89
|
|
|
90
90
|
```
|
|
91
91
|
[ClawRouter] deepseek-chat (MEDIUM, rules, confidence=0.85)
|
|
92
|
-
Cost: $0.0004 | Baseline: $0.
|
|
92
|
+
Cost: $0.0004 | Baseline: $0.0713 | Saved: 99.4%
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
## Models
|
|
@@ -135,12 +135,6 @@ Request → 402 (price: $0.003) → wallet signs USDC → retry → response
|
|
|
135
135
|
|
|
136
136
|
USDC stays in your wallet until the moment each request is paid — non-custodial. The price is visible in the 402 response before your wallet signs.
|
|
137
137
|
|
|
138
|
-
**Pricing formula:**
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
Price = (input_tokens × input_rate) + (max_output_tokens × output_rate)
|
|
142
|
-
```
|
|
143
|
-
|
|
144
138
|
**Funding your wallet** — send USDC on Base to your wallet address:
|
|
145
139
|
- Coinbase — buy USDC, send to Base
|
|
146
140
|
- Any CEX — withdraw USDC to Base
|
|
@@ -269,9 +263,9 @@ console.log(decision);
|
|
|
269
263
|
// tier: "REASONING",
|
|
270
264
|
// confidence: 0.9,
|
|
271
265
|
// method: "rules",
|
|
272
|
-
// savings: 0.
|
|
266
|
+
// savings: 0.893,
|
|
273
267
|
// costEstimate: 0.032776,
|
|
274
|
-
// baselineCost: 0.
|
|
268
|
+
// baselineCost: 0.307500,
|
|
275
269
|
// }
|
|
276
270
|
```
|
|
277
271
|
|
|
@@ -325,32 +319,32 @@ Real output from `node test/dist/e2e.js` — routing decisions are made in <1ms:
|
|
|
325
319
|
```
|
|
326
320
|
═══ Routing Test Results ═══
|
|
327
321
|
|
|
328
|
-
Simple queries (→ Gemini Flash,
|
|
322
|
+
Simple queries (→ Gemini Flash, 99% savings):
|
|
329
323
|
✓ "What is the capital of France?" → SIMPLE
|
|
330
324
|
✓ "Hello" → SIMPLE
|
|
331
325
|
✓ "Define photosynthesis" → SIMPLE
|
|
332
326
|
✓ "Translate hello to Spanish" → SIMPLE
|
|
333
327
|
|
|
334
|
-
Reasoning queries (→ o3,
|
|
328
|
+
Reasoning queries (→ o3, 89% savings):
|
|
335
329
|
✓ "Prove sqrt(2) is irrational" → REASONING
|
|
336
330
|
✓ "Derive time complexity + prove optimal" → REASONING
|
|
337
331
|
|
|
338
332
|
═══ Full Router (rules-only path) ═══
|
|
339
333
|
|
|
340
|
-
✓ Simple factual → google/gemini-2.5-flash (SIMPLE, rules) saved=
|
|
341
|
-
✓ Greeting → google/gemini-2.5-flash (SIMPLE, rules) saved=
|
|
342
|
-
✓ Math proof → openai/o3 (REASONING, rules) saved=
|
|
334
|
+
✓ Simple factual → google/gemini-2.5-flash (SIMPLE, rules) saved=99.2%
|
|
335
|
+
✓ Greeting → google/gemini-2.5-flash (SIMPLE, rules) saved=99.2%
|
|
336
|
+
✓ Math proof → openai/o3 (REASONING, rules) saved=89.3%
|
|
343
337
|
|
|
344
338
|
Cost estimate sanity:
|
|
345
339
|
✓ Cost estimate > 0: $0.002458
|
|
346
|
-
✓ Baseline cost > 0: $0.
|
|
347
|
-
✓ Savings in range [0,1]: 0.
|
|
348
|
-
✓ Cost ($0.002458) <= Baseline ($0.
|
|
340
|
+
✓ Baseline cost > 0: $0.307500
|
|
341
|
+
✓ Savings in range [0,1]: 0.9920
|
|
342
|
+
✓ Cost ($0.002458) <= Baseline ($0.307500)
|
|
349
343
|
|
|
350
344
|
═══ Live Proxy Test ═══
|
|
351
345
|
|
|
352
346
|
✓ Health check: ok
|
|
353
|
-
[routed] google/gemini-2.5-flash (SIMPLE) saved=
|
|
347
|
+
[routed] google/gemini-2.5-flash (SIMPLE) saved=99.2%
|
|
354
348
|
✓ Response: 2+2 equals 4.
|
|
355
349
|
|
|
356
350
|
═══════════════════════════════════
|
|
@@ -358,7 +352,7 @@ Cost estimate sanity:
|
|
|
358
352
|
═══════════════════════════════════
|
|
359
353
|
```
|
|
360
354
|
|
|
361
|
-
**Bottom line:** A simple "What is 2+2?" costs **$0.002** instead of **$0.
|
|
355
|
+
**Bottom line:** A simple "What is 2+2?" costs **$0.002** instead of **$0.308** on Opus — that's **99% savings** on every simple query.
|
|
362
356
|
|
|
363
357
|
## License
|
|
364
358
|
|
package/dist/index.d.ts
CHANGED
|
@@ -128,6 +128,8 @@ type OpenClawPluginDefinition = {
|
|
|
128
128
|
* Four classification tiers — REASONING is distinct from COMPLEX because
|
|
129
129
|
* reasoning tasks need different models (o3, gemini-pro) than general
|
|
130
130
|
* complex tasks (gpt-4o, sonnet-4).
|
|
131
|
+
*
|
|
132
|
+
* Scoring uses weighted float dimensions with sigmoid confidence calibration.
|
|
131
133
|
*/
|
|
132
134
|
type Tier = "SIMPLE" | "MEDIUM" | "COMPLEX" | "REASONING";
|
|
133
135
|
type RoutingDecision = {
|
|
@@ -154,9 +156,22 @@ type ScoringConfig = {
|
|
|
154
156
|
simpleKeywords: string[];
|
|
155
157
|
technicalKeywords: string[];
|
|
156
158
|
creativeKeywords: string[];
|
|
159
|
+
imperativeVerbs: string[];
|
|
160
|
+
constraintIndicators: string[];
|
|
161
|
+
outputFormatKeywords: string[];
|
|
162
|
+
referenceKeywords: string[];
|
|
163
|
+
negationKeywords: string[];
|
|
164
|
+
domainSpecificKeywords: string[];
|
|
165
|
+
dimensionWeights: Record<string, number>;
|
|
166
|
+
tierBoundaries: {
|
|
167
|
+
simpleMedium: number;
|
|
168
|
+
mediumComplex: number;
|
|
169
|
+
complexReasoning: number;
|
|
170
|
+
};
|
|
171
|
+
confidenceSteepness: number;
|
|
172
|
+
confidenceThreshold: number;
|
|
157
173
|
};
|
|
158
174
|
type ClassifierConfig = {
|
|
159
|
-
ambiguousZone: [number, number];
|
|
160
175
|
llmModel: string;
|
|
161
176
|
llmMaxTokens: number;
|
|
162
177
|
llmTemperature: number;
|
|
@@ -192,6 +207,8 @@ type ModelPricing = {
|
|
|
192
207
|
*
|
|
193
208
|
* All routing parameters as a TypeScript constant.
|
|
194
209
|
* Operators override via openclaw.yaml plugin config.
|
|
210
|
+
*
|
|
211
|
+
* Scoring uses 14 weighted dimensions with sigmoid confidence calibration.
|
|
195
212
|
*/
|
|
196
213
|
|
|
197
214
|
declare const DEFAULT_ROUTING_CONFIG: RoutingConfig;
|
package/dist/index.js
CHANGED
|
@@ -307,81 +307,190 @@ function createPaymentFetch(privateKey) {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
// src/router/rules.ts
|
|
310
|
-
function
|
|
310
|
+
function scoreTokenCount(estimatedTokens, thresholds) {
|
|
311
|
+
if (estimatedTokens < thresholds.simple) {
|
|
312
|
+
return { name: "tokenCount", score: -1, signal: `short (${estimatedTokens} tokens)` };
|
|
313
|
+
}
|
|
314
|
+
if (estimatedTokens > thresholds.complex) {
|
|
315
|
+
return { name: "tokenCount", score: 1, signal: `long (${estimatedTokens} tokens)` };
|
|
316
|
+
}
|
|
317
|
+
return { name: "tokenCount", score: 0, signal: null };
|
|
318
|
+
}
|
|
319
|
+
function scoreKeywordMatch(text, keywords, name, signalLabel, thresholds, scores) {
|
|
320
|
+
const matches = keywords.filter((kw) => text.includes(kw.toLowerCase()));
|
|
321
|
+
if (matches.length >= thresholds.high) {
|
|
322
|
+
return { name, score: scores.high, signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})` };
|
|
323
|
+
}
|
|
324
|
+
if (matches.length >= thresholds.low) {
|
|
325
|
+
return { name, score: scores.low, signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})` };
|
|
326
|
+
}
|
|
327
|
+
return { name, score: scores.none, signal: null };
|
|
328
|
+
}
|
|
329
|
+
function scoreMultiStep(text) {
|
|
330
|
+
const patterns = [/first.*then/i, /step \d/i, /\d\.\s/];
|
|
331
|
+
const hits = patterns.filter((p) => p.test(text));
|
|
332
|
+
if (hits.length > 0) {
|
|
333
|
+
return { name: "multiStepPatterns", score: 0.5, signal: "multi-step" };
|
|
334
|
+
}
|
|
335
|
+
return { name: "multiStepPatterns", score: 0, signal: null };
|
|
336
|
+
}
|
|
337
|
+
function scoreQuestionComplexity(prompt) {
|
|
338
|
+
const count = (prompt.match(/\?/g) || []).length;
|
|
339
|
+
if (count > 3) {
|
|
340
|
+
return { name: "questionComplexity", score: 0.5, signal: `${count} questions` };
|
|
341
|
+
}
|
|
342
|
+
return { name: "questionComplexity", score: 0, signal: null };
|
|
343
|
+
}
|
|
344
|
+
function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
311
345
|
const text = `${systemPrompt ?? ""} ${prompt}`.toLowerCase();
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
346
|
+
const dimensions = [
|
|
347
|
+
// Original 8 dimensions
|
|
348
|
+
scoreTokenCount(estimatedTokens, config.tokenCountThresholds),
|
|
349
|
+
scoreKeywordMatch(
|
|
350
|
+
text,
|
|
351
|
+
config.codeKeywords,
|
|
352
|
+
"codePresence",
|
|
353
|
+
"code",
|
|
354
|
+
{ low: 1, high: 2 },
|
|
355
|
+
{ none: 0, low: 0.5, high: 1 }
|
|
356
|
+
),
|
|
357
|
+
scoreKeywordMatch(
|
|
358
|
+
text,
|
|
359
|
+
config.reasoningKeywords,
|
|
360
|
+
"reasoningMarkers",
|
|
361
|
+
"reasoning",
|
|
362
|
+
{ low: 1, high: 2 },
|
|
363
|
+
{ none: 0, low: 0.7, high: 1 }
|
|
364
|
+
),
|
|
365
|
+
scoreKeywordMatch(
|
|
366
|
+
text,
|
|
367
|
+
config.technicalKeywords,
|
|
368
|
+
"technicalTerms",
|
|
369
|
+
"technical",
|
|
370
|
+
{ low: 2, high: 4 },
|
|
371
|
+
{ none: 0, low: 0.5, high: 1 }
|
|
372
|
+
),
|
|
373
|
+
scoreKeywordMatch(
|
|
374
|
+
text,
|
|
375
|
+
config.creativeKeywords,
|
|
376
|
+
"creativeMarkers",
|
|
377
|
+
"creative",
|
|
378
|
+
{ low: 1, high: 2 },
|
|
379
|
+
{ none: 0, low: 0.5, high: 0.7 }
|
|
380
|
+
),
|
|
381
|
+
scoreKeywordMatch(
|
|
382
|
+
text,
|
|
383
|
+
config.simpleKeywords,
|
|
384
|
+
"simpleIndicators",
|
|
385
|
+
"simple",
|
|
386
|
+
{ low: 1, high: 2 },
|
|
387
|
+
{ none: 0, low: -1, high: -1 }
|
|
388
|
+
),
|
|
389
|
+
scoreMultiStep(text),
|
|
390
|
+
scoreQuestionComplexity(prompt),
|
|
391
|
+
// 6 new dimensions
|
|
392
|
+
scoreKeywordMatch(
|
|
393
|
+
text,
|
|
394
|
+
config.imperativeVerbs,
|
|
395
|
+
"imperativeVerbs",
|
|
396
|
+
"imperative",
|
|
397
|
+
{ low: 1, high: 2 },
|
|
398
|
+
{ none: 0, low: 0.3, high: 0.5 }
|
|
399
|
+
),
|
|
400
|
+
scoreKeywordMatch(
|
|
401
|
+
text,
|
|
402
|
+
config.constraintIndicators,
|
|
403
|
+
"constraintCount",
|
|
404
|
+
"constraints",
|
|
405
|
+
{ low: 1, high: 3 },
|
|
406
|
+
{ none: 0, low: 0.3, high: 0.7 }
|
|
407
|
+
),
|
|
408
|
+
scoreKeywordMatch(
|
|
409
|
+
text,
|
|
410
|
+
config.outputFormatKeywords,
|
|
411
|
+
"outputFormat",
|
|
412
|
+
"format",
|
|
413
|
+
{ low: 1, high: 2 },
|
|
414
|
+
{ none: 0, low: 0.4, high: 0.7 }
|
|
415
|
+
),
|
|
416
|
+
scoreKeywordMatch(
|
|
417
|
+
text,
|
|
418
|
+
config.referenceKeywords,
|
|
419
|
+
"referenceComplexity",
|
|
420
|
+
"references",
|
|
421
|
+
{ low: 1, high: 2 },
|
|
422
|
+
{ none: 0, low: 0.3, high: 0.5 }
|
|
423
|
+
),
|
|
424
|
+
scoreKeywordMatch(
|
|
425
|
+
text,
|
|
426
|
+
config.negationKeywords,
|
|
427
|
+
"negationComplexity",
|
|
428
|
+
"negation",
|
|
429
|
+
{ low: 2, high: 3 },
|
|
430
|
+
{ none: 0, low: 0.3, high: 0.5 }
|
|
431
|
+
),
|
|
432
|
+
scoreKeywordMatch(
|
|
433
|
+
text,
|
|
434
|
+
config.domainSpecificKeywords,
|
|
435
|
+
"domainSpecificity",
|
|
436
|
+
"domain-specific",
|
|
437
|
+
{ low: 1, high: 2 },
|
|
438
|
+
{ none: 0, low: 0.5, high: 0.8 }
|
|
439
|
+
)
|
|
440
|
+
];
|
|
441
|
+
const signals = dimensions.filter((d) => d.signal !== null).map((d) => d.signal);
|
|
442
|
+
const weights = config.dimensionWeights;
|
|
443
|
+
let weightedScore = 0;
|
|
444
|
+
for (const d of dimensions) {
|
|
445
|
+
const w = weights[d.name] ?? 0;
|
|
446
|
+
weightedScore += d.score * w;
|
|
359
447
|
}
|
|
360
|
-
|
|
361
|
-
|
|
448
|
+
const reasoningMatches = config.reasoningKeywords.filter(
|
|
449
|
+
(kw) => text.includes(kw.toLowerCase())
|
|
450
|
+
);
|
|
362
451
|
if (reasoningMatches.length >= 2) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
452
|
+
const confidence2 = calibrateConfidence(
|
|
453
|
+
Math.max(weightedScore, 0.3),
|
|
454
|
+
// ensure positive for confidence calc
|
|
455
|
+
config.confidenceSteepness
|
|
456
|
+
);
|
|
457
|
+
return {
|
|
458
|
+
score: weightedScore,
|
|
459
|
+
tier: "REASONING",
|
|
460
|
+
confidence: Math.max(confidence2, 0.85),
|
|
461
|
+
signals
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
|
|
465
|
+
let tier;
|
|
466
|
+
let distanceFromBoundary;
|
|
467
|
+
if (weightedScore < simpleMedium) {
|
|
366
468
|
tier = "SIMPLE";
|
|
367
|
-
|
|
368
|
-
} else if (
|
|
369
|
-
tier = null;
|
|
370
|
-
confidence = 0.5;
|
|
371
|
-
} else if (score >= 3 && score <= 4) {
|
|
469
|
+
distanceFromBoundary = simpleMedium - weightedScore;
|
|
470
|
+
} else if (weightedScore < mediumComplex) {
|
|
372
471
|
tier = "MEDIUM";
|
|
373
|
-
|
|
374
|
-
|
|
472
|
+
distanceFromBoundary = Math.min(
|
|
473
|
+
weightedScore - simpleMedium,
|
|
474
|
+
mediumComplex - weightedScore
|
|
475
|
+
);
|
|
476
|
+
} else if (weightedScore < complexReasoning) {
|
|
375
477
|
tier = "COMPLEX";
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
478
|
+
distanceFromBoundary = Math.min(
|
|
479
|
+
weightedScore - mediumComplex,
|
|
480
|
+
complexReasoning - weightedScore
|
|
481
|
+
);
|
|
380
482
|
} else {
|
|
381
|
-
tier =
|
|
382
|
-
|
|
483
|
+
tier = "REASONING";
|
|
484
|
+
distanceFromBoundary = weightedScore - complexReasoning;
|
|
485
|
+
}
|
|
486
|
+
const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
|
|
487
|
+
if (confidence < config.confidenceThreshold) {
|
|
488
|
+
return { score: weightedScore, tier: null, confidence, signals };
|
|
383
489
|
}
|
|
384
|
-
return { score, tier, confidence, signals };
|
|
490
|
+
return { score: weightedScore, tier, confidence, signals };
|
|
491
|
+
}
|
|
492
|
+
function calibrateConfidence(distance, steepness) {
|
|
493
|
+
return 1 / (1 + Math.exp(-steepness * distance));
|
|
385
494
|
}
|
|
386
495
|
|
|
387
496
|
// src/router/llm-classifier.ts
|
|
@@ -465,9 +574,9 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
|
|
|
465
574
|
const inputCost = pricing ? estimatedInputTokens / 1e6 * pricing.inputPrice : 0;
|
|
466
575
|
const outputCost = pricing ? maxOutputTokens / 1e6 * pricing.outputPrice : 0;
|
|
467
576
|
const costEstimate = inputCost + outputCost;
|
|
468
|
-
const
|
|
469
|
-
const baselineInput =
|
|
470
|
-
const baselineOutput =
|
|
577
|
+
const opusPricing = modelPricing.get("anthropic/claude-opus-4");
|
|
578
|
+
const baselineInput = opusPricing ? estimatedInputTokens / 1e6 * opusPricing.inputPrice : 0;
|
|
579
|
+
const baselineOutput = opusPricing ? maxOutputTokens / 1e6 * opusPricing.outputPrice : 0;
|
|
471
580
|
const baselineCost = baselineInput + baselineOutput;
|
|
472
581
|
const savings = baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
473
582
|
return {
|
|
@@ -484,9 +593,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
|
|
|
484
593
|
|
|
485
594
|
// src/router/config.ts
|
|
486
595
|
var DEFAULT_ROUTING_CONFIG = {
|
|
487
|
-
version: "
|
|
596
|
+
version: "2.0",
|
|
488
597
|
classifier: {
|
|
489
|
-
ambiguousZone: [1, 2],
|
|
490
598
|
llmModel: "google/gemini-2.5-flash",
|
|
491
599
|
llmMaxTokens: 10,
|
|
492
600
|
llmTemperature: 0,
|
|
@@ -550,7 +658,105 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
550
658
|
"creative",
|
|
551
659
|
"imagine",
|
|
552
660
|
"write a"
|
|
553
|
-
]
|
|
661
|
+
],
|
|
662
|
+
// New dimension keyword lists
|
|
663
|
+
imperativeVerbs: [
|
|
664
|
+
"build",
|
|
665
|
+
"create",
|
|
666
|
+
"implement",
|
|
667
|
+
"design",
|
|
668
|
+
"develop",
|
|
669
|
+
"construct",
|
|
670
|
+
"generate",
|
|
671
|
+
"deploy",
|
|
672
|
+
"configure",
|
|
673
|
+
"set up"
|
|
674
|
+
],
|
|
675
|
+
constraintIndicators: [
|
|
676
|
+
"under",
|
|
677
|
+
"at most",
|
|
678
|
+
"at least",
|
|
679
|
+
"within",
|
|
680
|
+
"no more than",
|
|
681
|
+
"o(",
|
|
682
|
+
"maximum",
|
|
683
|
+
"minimum",
|
|
684
|
+
"limit",
|
|
685
|
+
"budget"
|
|
686
|
+
],
|
|
687
|
+
outputFormatKeywords: [
|
|
688
|
+
"json",
|
|
689
|
+
"yaml",
|
|
690
|
+
"xml",
|
|
691
|
+
"table",
|
|
692
|
+
"csv",
|
|
693
|
+
"markdown",
|
|
694
|
+
"schema",
|
|
695
|
+
"format as",
|
|
696
|
+
"structured"
|
|
697
|
+
],
|
|
698
|
+
referenceKeywords: [
|
|
699
|
+
"above",
|
|
700
|
+
"below",
|
|
701
|
+
"previous",
|
|
702
|
+
"following",
|
|
703
|
+
"the docs",
|
|
704
|
+
"the api",
|
|
705
|
+
"the code",
|
|
706
|
+
"earlier",
|
|
707
|
+
"attached"
|
|
708
|
+
],
|
|
709
|
+
negationKeywords: [
|
|
710
|
+
"don't",
|
|
711
|
+
"do not",
|
|
712
|
+
"avoid",
|
|
713
|
+
"never",
|
|
714
|
+
"without",
|
|
715
|
+
"except",
|
|
716
|
+
"exclude",
|
|
717
|
+
"no longer"
|
|
718
|
+
],
|
|
719
|
+
domainSpecificKeywords: [
|
|
720
|
+
"quantum",
|
|
721
|
+
"fpga",
|
|
722
|
+
"vlsi",
|
|
723
|
+
"risc-v",
|
|
724
|
+
"asic",
|
|
725
|
+
"photonics",
|
|
726
|
+
"genomics",
|
|
727
|
+
"proteomics",
|
|
728
|
+
"topological",
|
|
729
|
+
"homomorphic",
|
|
730
|
+
"zero-knowledge",
|
|
731
|
+
"lattice-based"
|
|
732
|
+
],
|
|
733
|
+
// Dimension weights (sum to 1.0)
|
|
734
|
+
dimensionWeights: {
|
|
735
|
+
tokenCount: 0.08,
|
|
736
|
+
codePresence: 0.15,
|
|
737
|
+
reasoningMarkers: 0.18,
|
|
738
|
+
technicalTerms: 0.1,
|
|
739
|
+
creativeMarkers: 0.05,
|
|
740
|
+
simpleIndicators: 0.12,
|
|
741
|
+
multiStepPatterns: 0.12,
|
|
742
|
+
questionComplexity: 0.05,
|
|
743
|
+
imperativeVerbs: 0.03,
|
|
744
|
+
constraintCount: 0.04,
|
|
745
|
+
outputFormat: 0.03,
|
|
746
|
+
referenceComplexity: 0.02,
|
|
747
|
+
negationComplexity: 0.01,
|
|
748
|
+
domainSpecificity: 0.02
|
|
749
|
+
},
|
|
750
|
+
// Tier boundaries on weighted score axis
|
|
751
|
+
tierBoundaries: {
|
|
752
|
+
simpleMedium: 0,
|
|
753
|
+
mediumComplex: 0.15,
|
|
754
|
+
complexReasoning: 0.25
|
|
755
|
+
},
|
|
756
|
+
// Sigmoid steepness for confidence calibration
|
|
757
|
+
confidenceSteepness: 12,
|
|
758
|
+
// Below this confidence → ambiguous (null tier)
|
|
759
|
+
confidenceThreshold: 0.7
|
|
554
760
|
},
|
|
555
761
|
tiers: {
|
|
556
762
|
SIMPLE: {
|
|
@@ -562,8 +768,8 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
562
768
|
fallback: ["google/gemini-2.5-flash", "openai/gpt-4o-mini"]
|
|
563
769
|
},
|
|
564
770
|
COMPLEX: {
|
|
565
|
-
primary: "anthropic/claude-
|
|
566
|
-
fallback: ["
|
|
771
|
+
primary: "anthropic/claude-opus-4",
|
|
772
|
+
fallback: ["anthropic/claude-sonnet-4", "openai/gpt-4o"]
|
|
567
773
|
},
|
|
568
774
|
REASONING: {
|
|
569
775
|
primary: "openai/o3",
|
|
@@ -598,8 +804,7 @@ async function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
598
804
|
prompt,
|
|
599
805
|
systemPrompt,
|
|
600
806
|
estimatedTokens,
|
|
601
|
-
config.scoring
|
|
602
|
-
config.classifier.ambiguousZone
|
|
807
|
+
config.scoring
|
|
603
808
|
);
|
|
604
809
|
let tier;
|
|
605
810
|
let confidence;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth.ts","../src/models.ts","../src/provider.ts","../src/proxy.ts","../src/x402.ts","../src/router/rules.ts","../src/router/llm-classifier.ts","../src/router/selector.ts","../src/router/config.ts","../src/router/index.ts","../src/logger.ts","../src/index.ts"],"sourcesContent":["/**\n * BlockRun Auth Methods for OpenClaw\n *\n * Provides wallet-based authentication for the BlockRun provider.\n * Operators configure their wallet private key, which is used to\n * sign x402 micropayments for LLM inference.\n *\n * Three methods:\n * 1. Auto-generate — create a new wallet on first run, save to ~/.openclaw/blockrun/wallet.key\n * 2. Environment variable — read from BLOCKRUN_WALLET_KEY\n * 3. Manual input — operator enters private key via wizard\n */\n\nimport { writeFile, readFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { generatePrivateKey, privateKeyToAccount } from \"viem/accounts\";\nimport type { ProviderAuthMethod, ProviderAuthContext, ProviderAuthResult } from \"./types.js\";\n\nconst WALLET_DIR = join(homedir(), \".openclaw\", \"blockrun\");\nconst WALLET_FILE = join(WALLET_DIR, \"wallet.key\");\n\n/**\n * Try to load a previously auto-generated wallet key from disk.\n */\nasync function loadSavedWallet(): Promise<string | undefined> {\n try {\n const key = (await readFile(WALLET_FILE, \"utf-8\")).trim();\n if (key.startsWith(\"0x\") && key.length === 66) return key;\n } catch {\n // File doesn't exist yet\n }\n return undefined;\n}\n\n/**\n * Generate a new wallet, save to disk, return the private key.\n */\nasync function generateAndSaveWallet(): Promise<{ key: string; address: string }> {\n const key = generatePrivateKey();\n const account = privateKeyToAccount(key);\n await mkdir(WALLET_DIR, { recursive: true });\n await writeFile(WALLET_FILE, key + \"\\n\", { mode: 0o600 });\n return { key, address: account.address };\n}\n\n/**\n * Resolve wallet key: load saved → env var → auto-generate.\n * Called by index.ts before the auth wizard runs.\n */\nexport async function resolveOrGenerateWalletKey(): Promise<{ key: string; address: string; source: \"saved\" | \"env\" | \"generated\" }> {\n // 1. Previously saved wallet\n const saved = await loadSavedWallet();\n if (saved) {\n const account = privateKeyToAccount(saved as `0x${string}`);\n return { key: saved, address: account.address, source: \"saved\" };\n }\n\n // 2. Environment variable\n const envKey = process.env.BLOCKRUN_WALLET_KEY;\n if (typeof envKey === \"string\" && envKey.startsWith(\"0x\") && envKey.length === 66) {\n const account = privateKeyToAccount(envKey as `0x${string}`);\n return { key: envKey, address: account.address, source: \"env\" };\n }\n\n // 3. Auto-generate\n const { key, address } = await generateAndSaveWallet();\n return { key, address, source: \"generated\" };\n}\n\n/**\n * Auth method: operator enters their wallet private key directly.\n */\nexport const walletKeyAuth: ProviderAuthMethod = {\n id: \"wallet-key\",\n label: \"Wallet Private Key\",\n hint: \"Enter your EVM wallet private key (0x...) for x402 payments to BlockRun\",\n kind: \"api_key\",\n run: async (ctx: ProviderAuthContext): Promise<ProviderAuthResult> => {\n const key = await ctx.prompter.text({\n message: \"Enter your wallet private key (0x...)\",\n validate: (value: string) => {\n const trimmed = value.trim();\n if (!trimmed.startsWith(\"0x\")) return \"Key must start with 0x\";\n if (trimmed.length !== 66) return \"Key must be 66 characters (0x + 64 hex)\";\n if (!/^0x[0-9a-fA-F]{64}$/.test(trimmed)) return \"Key must be valid hex\";\n return undefined;\n },\n });\n\n if (!key || typeof key !== \"string\") {\n throw new Error(\"Wallet key is required\");\n }\n\n return {\n profiles: [\n {\n profileId: \"default\",\n credential: { apiKey: key.trim() },\n },\n ],\n notes: [\n \"Wallet key stored securely in OpenClaw credentials.\",\n \"Your wallet signs x402 USDC payments on Base for each LLM call.\",\n \"Fund your wallet with USDC on Base to start using BlockRun models.\",\n ],\n };\n },\n};\n\n/**\n * Auth method: read wallet key from BLOCKRUN_WALLET_KEY environment variable.\n */\nexport const envKeyAuth: ProviderAuthMethod = {\n id: \"env-key\",\n label: \"Environment Variable\",\n hint: \"Use BLOCKRUN_WALLET_KEY environment variable\",\n kind: \"api_key\",\n run: async (): Promise<ProviderAuthResult> => {\n const key = process.env.BLOCKRUN_WALLET_KEY;\n\n if (!key) {\n throw new Error(\n \"BLOCKRUN_WALLET_KEY environment variable is not set. \" +\n \"Set it to your EVM wallet private key (0x...).\",\n );\n }\n\n return {\n profiles: [\n {\n profileId: \"default\",\n credential: { apiKey: key.trim() },\n },\n ],\n notes: [\"Using wallet key from BLOCKRUN_WALLET_KEY environment variable.\"],\n };\n },\n};\n","/**\n * BlockRun Model Definitions for OpenClaw\n *\n * Maps BlockRun's 30+ AI models to OpenClaw's ModelDefinitionConfig format.\n * All models use the \"openai-completions\" API since BlockRun is OpenAI-compatible.\n *\n * Pricing is in USD per 1M tokens. Operators pay these rates via x402;\n * they set their own markup when reselling to end users (Phase 2).\n */\n\nimport type { ModelDefinitionConfig, ModelProviderConfig } from \"./types.js\";\n\ntype BlockRunModel = {\n id: string;\n name: string;\n inputPrice: number;\n outputPrice: number;\n contextWindow: number;\n maxOutput: number;\n reasoning?: boolean;\n vision?: boolean;\n};\n\nexport const BLOCKRUN_MODELS: BlockRunModel[] = [\n // Smart routing meta-model — proxy replaces with actual model\n { id: \"blockrun/auto\", name: \"BlockRun Smart Router\", inputPrice: 0, outputPrice: 0, contextWindow: 1_050_000, maxOutput: 128_000 },\n\n\n // OpenAI GPT-5 Family\n { id: \"openai/gpt-5.2\", name: \"GPT-5.2\", inputPrice: 1.75, outputPrice: 14.0, contextWindow: 400000, maxOutput: 128000, reasoning: true, vision: true },\n { id: \"openai/gpt-5-mini\", name: \"GPT-5 Mini\", inputPrice: 0.25, outputPrice: 2.0, contextWindow: 200000, maxOutput: 65536 },\n { id: \"openai/gpt-5-nano\", name: \"GPT-5 Nano\", inputPrice: 0.05, outputPrice: 0.4, contextWindow: 128000, maxOutput: 32768 },\n { id: \"openai/gpt-5.2-pro\", name: \"GPT-5.2 Pro\", inputPrice: 21.0, outputPrice: 168.0, contextWindow: 400000, maxOutput: 128000, reasoning: true },\n\n // OpenAI GPT-4 Family\n { id: \"openai/gpt-4.1\", name: \"GPT-4.1\", inputPrice: 2.0, outputPrice: 8.0, contextWindow: 128000, maxOutput: 16384, vision: true },\n { id: \"openai/gpt-4.1-mini\", name: \"GPT-4.1 Mini\", inputPrice: 0.4, outputPrice: 1.6, contextWindow: 128000, maxOutput: 16384 },\n { id: \"openai/gpt-4.1-nano\", name: \"GPT-4.1 Nano\", inputPrice: 0.1, outputPrice: 0.4, contextWindow: 128000, maxOutput: 16384 },\n { id: \"openai/gpt-4o\", name: \"GPT-4o\", inputPrice: 2.5, outputPrice: 10.0, contextWindow: 128000, maxOutput: 16384, vision: true },\n { id: \"openai/gpt-4o-mini\", name: \"GPT-4o Mini\", inputPrice: 0.15, outputPrice: 0.6, contextWindow: 128000, maxOutput: 16384 },\n\n // OpenAI O-series (Reasoning)\n { id: \"openai/o1\", name: \"o1\", inputPrice: 15.0, outputPrice: 60.0, contextWindow: 200000, maxOutput: 100000, reasoning: true },\n { id: \"openai/o1-mini\", name: \"o1-mini\", inputPrice: 1.1, outputPrice: 4.4, contextWindow: 128000, maxOutput: 65536, reasoning: true },\n { id: \"openai/o3\", name: \"o3\", inputPrice: 2.0, outputPrice: 8.0, contextWindow: 200000, maxOutput: 100000, reasoning: true },\n { id: \"openai/o3-mini\", name: \"o3-mini\", inputPrice: 1.1, outputPrice: 4.4, contextWindow: 128000, maxOutput: 65536, reasoning: true },\n { id: \"openai/o4-mini\", name: \"o4-mini\", inputPrice: 1.1, outputPrice: 4.4, contextWindow: 128000, maxOutput: 65536, reasoning: true },\n\n // Anthropic\n { id: \"anthropic/claude-haiku-4.5\", name: \"Claude Haiku 4.5\", inputPrice: 1.0, outputPrice: 5.0, contextWindow: 200000, maxOutput: 8192 },\n { id: \"anthropic/claude-sonnet-4\", name: \"Claude Sonnet 4\", inputPrice: 3.0, outputPrice: 15.0, contextWindow: 200000, maxOutput: 64000, reasoning: true },\n { id: \"anthropic/claude-opus-4\", name: \"Claude Opus 4\", inputPrice: 15.0, outputPrice: 75.0, contextWindow: 200000, maxOutput: 32000, reasoning: true },\n { id: \"anthropic/claude-opus-4.5\", name: \"Claude Opus 4.5\", inputPrice: 15.0, outputPrice: 75.0, contextWindow: 200000, maxOutput: 32000, reasoning: true },\n\n // Google\n { id: \"google/gemini-3-pro-preview\", name: \"Gemini 3 Pro Preview\", inputPrice: 2.0, outputPrice: 12.0, contextWindow: 1050000, maxOutput: 65536, reasoning: true, vision: true },\n { id: \"google/gemini-2.5-pro\", name: \"Gemini 2.5 Pro\", inputPrice: 1.25, outputPrice: 10.0, contextWindow: 1050000, maxOutput: 65536, reasoning: true, vision: true },\n { id: \"google/gemini-2.5-flash\", name: \"Gemini 2.5 Flash\", inputPrice: 0.15, outputPrice: 0.6, contextWindow: 1000000, maxOutput: 65536 },\n\n // DeepSeek\n { id: \"deepseek/deepseek-chat\", name: \"DeepSeek V3.2 Chat\", inputPrice: 0.28, outputPrice: 0.42, contextWindow: 128000, maxOutput: 8192 },\n { id: \"deepseek/deepseek-reasoner\", name: \"DeepSeek V3.2 Reasoner\", inputPrice: 0.28, outputPrice: 0.42, contextWindow: 128000, maxOutput: 8192, reasoning: true },\n\n // xAI / Grok\n { id: \"xai/grok-3\", name: \"Grok 3\", inputPrice: 3.0, outputPrice: 15.0, contextWindow: 131072, maxOutput: 16384, reasoning: true },\n { id: \"xai/grok-3-fast\", name: \"Grok 3 Fast\", inputPrice: 5.0, outputPrice: 25.0, contextWindow: 131072, maxOutput: 16384, reasoning: true },\n { id: \"xai/grok-3-mini\", name: \"Grok 3 Mini\", inputPrice: 0.3, outputPrice: 0.5, contextWindow: 131072, maxOutput: 16384 },\n];\n\n/**\n * Convert BlockRun model definitions to OpenClaw ModelDefinitionConfig format.\n */\nfunction toOpenClawModel(m: BlockRunModel): ModelDefinitionConfig {\n return {\n id: m.id,\n name: m.name,\n api: \"openai-completions\",\n reasoning: m.reasoning ?? false,\n input: m.vision ? [\"text\", \"image\"] : [\"text\"],\n cost: {\n input: m.inputPrice,\n output: m.outputPrice,\n cacheRead: 0,\n cacheWrite: 0,\n },\n contextWindow: m.contextWindow,\n maxTokens: m.maxOutput,\n };\n}\n\n/**\n * All BlockRun models in OpenClaw format.\n */\nexport const OPENCLAW_MODELS: ModelDefinitionConfig[] = BLOCKRUN_MODELS.map(toOpenClawModel);\n\n/**\n * Build a ModelProviderConfig for BlockRun.\n *\n * @param baseUrl - The proxy's local base URL (e.g., \"http://127.0.0.1:12345\")\n */\nexport function buildProviderModels(baseUrl: string): ModelProviderConfig {\n return {\n baseUrl: `${baseUrl}/v1`,\n api: \"openai-completions\",\n models: OPENCLAW_MODELS,\n };\n}\n","/**\n * BlockRun ProviderPlugin for OpenClaw\n *\n * Registers BlockRun as an LLM provider in OpenClaw.\n * Uses a local x402 proxy to handle micropayments transparently —\n * pi-ai sees a standard OpenAI-compatible API at localhost.\n */\n\nimport type { ProviderPlugin, AuthProfileCredential } from \"./types.js\";\nimport { walletKeyAuth, envKeyAuth } from \"./auth.js\";\nimport { buildProviderModels } from \"./models.js\";\nimport type { ProxyHandle } from \"./proxy.js\";\n\n/**\n * State for the running proxy (set when the plugin activates).\n */\nlet activeProxy: ProxyHandle | null = null;\n\n/**\n * Update the proxy handle (called from index.ts when the proxy starts).\n */\nexport function setActiveProxy(proxy: ProxyHandle): void {\n activeProxy = proxy;\n}\n\nexport function getActiveProxy(): ProxyHandle | null {\n return activeProxy;\n}\n\n/**\n * BlockRun provider plugin definition.\n */\nexport const blockrunProvider: ProviderPlugin = {\n id: \"blockrun\",\n label: \"BlockRun\",\n docsPath: \"https://blockrun.ai/docs\",\n aliases: [\"br\"],\n envVars: [\"BLOCKRUN_WALLET_KEY\"],\n\n // Model definitions — dynamically set to proxy URL\n get models() {\n if (!activeProxy) {\n // Fallback: point to BlockRun API directly (won't handle x402, but\n // allows config loading before proxy starts)\n return buildProviderModels(\"https://blockrun.ai/api\");\n }\n return buildProviderModels(activeProxy.baseUrl);\n },\n\n // Auth methods\n auth: [envKeyAuth, walletKeyAuth],\n\n // Format the stored credential as the wallet key\n formatApiKey: (cred: AuthProfileCredential): string => {\n if (\"apiKey\" in cred && typeof cred.apiKey === \"string\") {\n return cred.apiKey;\n }\n throw new Error(\"BlockRun credential must contain an apiKey (wallet private key)\");\n },\n};\n","/**\n * Local x402 Proxy Server\n *\n * Sits between OpenClaw's pi-ai (which makes standard OpenAI-format requests)\n * and BlockRun's API (which requires x402 micropayments).\n *\n * Flow:\n * pi-ai → http://localhost:{port}/v1/chat/completions\n * → proxy forwards to https://blockrun.ai/api/v1/chat/completions\n * → gets 402 → @x402/fetch signs payment → retries\n * → streams response back to pi-ai\n *\n * Phase 2 additions:\n * - Smart routing: when model is \"blockrun/auto\", classify query and pick cheapest model\n * - Usage logging: log every request as JSON line to ~/.openclaw/blockrun/logs/\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { createPaymentFetch } from \"./x402.js\";\nimport { route, getFallbackChain, DEFAULT_ROUTING_CONFIG, type RouterOptions, type RoutingDecision, type RoutingConfig, type ModelPricing } from \"./router/index.js\";\nimport { BLOCKRUN_MODELS } from \"./models.js\";\nimport { logUsage, type UsageEntry } from \"./logger.js\";\n\nconst BLOCKRUN_API = \"https://blockrun.ai/api\";\nconst AUTO_MODEL = \"blockrun/auto\";\nconst USER_AGENT = \"claw-router/0.1.0\";\n\nexport type ProxyOptions = {\n walletKey: string;\n apiBase?: string;\n port?: number;\n routingConfig?: Partial<RoutingConfig>;\n onReady?: (port: number) => void;\n onError?: (error: Error) => void;\n onPayment?: (info: { model: string; amount: string; network: string }) => void;\n onRouted?: (decision: RoutingDecision) => void;\n};\n\nexport type ProxyHandle = {\n port: number;\n baseUrl: string;\n close: () => Promise<void>;\n};\n\n/**\n * Build model pricing map from BLOCKRUN_MODELS.\n */\nfunction buildModelPricing(): Map<string, ModelPricing> {\n const map = new Map<string, ModelPricing>();\n for (const m of BLOCKRUN_MODELS) {\n if (m.id === AUTO_MODEL) continue; // skip meta-model\n map.set(m.id, { inputPrice: m.inputPrice, outputPrice: m.outputPrice });\n }\n return map;\n}\n\n/**\n * Merge partial routing config overrides with defaults.\n */\nfunction mergeRoutingConfig(overrides?: Partial<RoutingConfig>): RoutingConfig {\n if (!overrides) return DEFAULT_ROUTING_CONFIG;\n return {\n ...DEFAULT_ROUTING_CONFIG,\n ...overrides,\n classifier: { ...DEFAULT_ROUTING_CONFIG.classifier, ...overrides.classifier },\n scoring: { ...DEFAULT_ROUTING_CONFIG.scoring, ...overrides.scoring },\n tiers: { ...DEFAULT_ROUTING_CONFIG.tiers, ...overrides.tiers },\n overrides: { ...DEFAULT_ROUTING_CONFIG.overrides, ...overrides.overrides },\n };\n}\n\n/**\n * Start the local x402 proxy server.\n *\n * Returns a handle with the assigned port, base URL, and a close function.\n */\nexport async function startProxy(options: ProxyOptions): Promise<ProxyHandle> {\n const apiBase = options.apiBase ?? BLOCKRUN_API;\n\n // Create x402 payment-enabled fetch from wallet private key\n const account = privateKeyToAccount(options.walletKey as `0x${string}`);\n const payFetch = createPaymentFetch(options.walletKey as `0x${string}`);\n\n // Build router options\n const routingConfig = mergeRoutingConfig(options.routingConfig);\n const modelPricing = buildModelPricing();\n const routerOpts: RouterOptions = {\n config: routingConfig,\n modelPricing,\n payFetch,\n apiBase,\n };\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n // Health check\n if (req.url === \"/health\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"ok\", wallet: account.address }));\n return;\n }\n\n // Only proxy paths starting with /v1\n if (!req.url?.startsWith(\"/v1\")) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n return;\n }\n\n try {\n await proxyRequest(req, res, apiBase, payFetch, options, routerOpts);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n options.onError?.(error);\n\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({\n error: { message: `Proxy error: ${error.message}`, type: \"proxy_error\" },\n }));\n }\n }\n });\n\n // Listen on requested port (0 = random available port)\n const listenPort = options.port ?? 0;\n\n return new Promise<ProxyHandle>((resolve, reject) => {\n server.on(\"error\", reject);\n\n server.listen(listenPort, \"127.0.0.1\", () => {\n const addr = server.address() as AddressInfo;\n const port = addr.port;\n const baseUrl = `http://127.0.0.1:${port}`;\n\n options.onReady?.(port);\n\n resolve({\n port,\n baseUrl,\n close: () =>\n new Promise<void>((res, rej) => {\n server.close((err) => (err ? rej(err) : res()));\n }),\n });\n });\n });\n}\n\n/**\n * Proxy a single request through x402 payment flow to BlockRun API.\n *\n * When model is \"blockrun/auto\", runs the smart router to pick the\n * cheapest capable model before forwarding.\n */\nasync function proxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n apiBase: string,\n payFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>,\n options: ProxyOptions,\n routerOpts: RouterOptions,\n): Promise<void> {\n const startTime = Date.now();\n\n // Build upstream URL: /v1/chat/completions → https://blockrun.ai/api/v1/chat/completions\n const upstreamUrl = `${apiBase}${req.url}`;\n\n // Collect request body\n const bodyChunks: Buffer[] = [];\n for await (const chunk of req) {\n bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n let body = Buffer.concat(bodyChunks);\n\n // --- Smart routing ---\n let routingDecision: RoutingDecision | undefined;\n const isChatCompletion = req.url?.includes(\"/chat/completions\");\n\n if (isChatCompletion && body.length > 0) {\n try {\n const parsed = JSON.parse(body.toString()) as Record<string, unknown>;\n\n if (parsed.model === AUTO_MODEL) {\n // Extract prompt from messages\n type ChatMessage = { role: string; content: string };\n const messages = parsed.messages as ChatMessage[] | undefined;\n let lastUserMsg: ChatMessage | undefined;\n if (messages) {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") { lastUserMsg = messages[i]; break; }\n }\n }\n const systemMsg = messages?.find((m: ChatMessage) => m.role === \"system\");\n const prompt = typeof lastUserMsg?.content === \"string\" ? lastUserMsg.content : \"\";\n const systemPrompt = typeof systemMsg?.content === \"string\" ? systemMsg.content : undefined;\n const maxTokens = (parsed.max_tokens as number) || 4096;\n\n routingDecision = await route(prompt, systemPrompt, maxTokens, routerOpts);\n\n // Replace model in body\n parsed.model = routingDecision.model;\n body = Buffer.from(JSON.stringify(parsed));\n\n options.onRouted?.(routingDecision);\n }\n } catch {\n // JSON parse error — forward body as-is\n }\n }\n\n // Forward headers, stripping host, connection, and content-length\n // (content-length may be wrong after body modification for routing)\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (key === \"host\" || key === \"connection\" || key === \"transfer-encoding\" || key === \"content-length\") continue;\n if (typeof value === \"string\") {\n headers[key] = value;\n }\n }\n // Ensure content-type is set\n if (!headers[\"content-type\"]) {\n headers[\"content-type\"] = \"application/json\";\n }\n // Set User-Agent for BlockRun API tracking\n headers[\"user-agent\"] = USER_AGENT;\n\n // Make the request through x402-wrapped fetch\n // This handles: request → 402 → sign payment → retry with PAYMENT-SIGNATURE header\n const upstream = await payFetch(upstreamUrl, {\n method: req.method ?? \"POST\",\n headers,\n body: body.length > 0 ? body : undefined,\n });\n\n // Forward status and headers from upstream\n const responseHeaders: Record<string, string> = {};\n upstream.headers.forEach((value, key) => {\n // Skip hop-by-hop headers\n if (key === \"transfer-encoding\" || key === \"connection\") return;\n responseHeaders[key] = value;\n });\n\n res.writeHead(upstream.status, responseHeaders);\n\n // Stream the response body\n if (upstream.body) {\n const reader = upstream.body.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(value);\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n res.end();\n\n // --- Usage logging (fire-and-forget) ---\n if (routingDecision) {\n const entry: UsageEntry = {\n timestamp: new Date().toISOString(),\n model: routingDecision.model,\n cost: routingDecision.costEstimate,\n latencyMs: Date.now() - startTime,\n };\n logUsage(entry).catch(() => {});\n }\n}\n","/**\n * x402 Payment Implementation\n *\n * Based on BlockRun's proven implementation.\n * Handles 402 Payment Required responses with EIP-712 signed USDC transfers.\n */\n\nimport { signTypedData, privateKeyToAccount } from \"viem/accounts\";\n\nconst BASE_CHAIN_ID = 8453;\nconst USDC_BASE = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\" as const;\n\nconst USDC_DOMAIN = {\n name: \"USD Coin\",\n version: \"2\",\n chainId: BASE_CHAIN_ID,\n verifyingContract: USDC_BASE,\n} as const;\n\nconst TRANSFER_TYPES = {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n} as const;\n\nfunction createNonce(): `0x${string}` {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return `0x${Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`;\n}\n\ninterface PaymentOption {\n scheme: string;\n network: string;\n amount?: string;\n maxAmountRequired?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: { name?: string; version?: string };\n}\n\ninterface PaymentRequired {\n accepts: PaymentOption[];\n resource?: { url?: string; description?: string };\n}\n\nfunction parsePaymentRequired(headerValue: string): PaymentRequired {\n const decoded = atob(headerValue);\n return JSON.parse(decoded) as PaymentRequired;\n}\n\nasync function createPaymentPayload(\n privateKey: `0x${string}`,\n fromAddress: string,\n recipient: string,\n amount: string,\n resourceUrl: string\n): Promise<string> {\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - 600;\n const validBefore = now + 300;\n const nonce = createNonce();\n\n const signature = await signTypedData({\n privateKey,\n domain: USDC_DOMAIN,\n types: TRANSFER_TYPES,\n primaryType: \"TransferWithAuthorization\",\n message: {\n from: fromAddress as `0x${string}`,\n to: recipient as `0x${string}`,\n value: BigInt(amount),\n validAfter: BigInt(validAfter),\n validBefore: BigInt(validBefore),\n nonce,\n },\n });\n\n const paymentData = {\n x402Version: 2,\n resource: {\n url: resourceUrl,\n description: \"BlockRun AI API call\",\n mimeType: \"application/json\",\n },\n accepted: {\n scheme: \"exact\",\n network: \"eip155:8453\",\n amount,\n asset: USDC_BASE,\n payTo: recipient,\n maxTimeoutSeconds: 300,\n extra: { name: \"USD Coin\", version: \"2\" },\n },\n payload: {\n signature,\n authorization: {\n from: fromAddress,\n to: recipient,\n value: amount,\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n },\n },\n extensions: {},\n };\n\n return btoa(JSON.stringify(paymentData));\n}\n\n/**\n * Create a fetch wrapper that handles x402 payment automatically.\n */\nexport function createPaymentFetch(privateKey: `0x${string}`): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {\n const account = privateKeyToAccount(privateKey);\n const walletAddress = account.address;\n\n return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n // First request - may get 402\n const response = await fetch(input, init);\n\n if (response.status !== 402) {\n return response;\n }\n\n // Parse 402 payment requirements\n const paymentHeader = response.headers.get(\"x-payment-required\");\n if (!paymentHeader) {\n throw new Error(\"402 response missing x-payment-required header\");\n }\n\n const paymentRequired = parsePaymentRequired(paymentHeader);\n const option = paymentRequired.accepts?.[0];\n if (!option) {\n throw new Error(\"No payment options in 402 response\");\n }\n\n const amount = option.amount || option.maxAmountRequired;\n if (!amount) {\n throw new Error(\"No amount in payment requirements\");\n }\n\n // Create signed payment\n const paymentPayload = await createPaymentPayload(\n privateKey,\n walletAddress,\n option.payTo,\n amount,\n url\n );\n\n // Retry with payment\n const retryHeaders = new Headers(init?.headers);\n retryHeaders.set(\"payment-signature\", paymentPayload);\n\n return fetch(input, {\n ...init,\n headers: retryHeaders,\n });\n };\n}\n","/**\n * Rule-Based Classifier\n *\n * Scores a request across multiple dimensions (token count, code presence,\n * reasoning markers, etc.) and maps the aggregate score to a tier.\n * Returns null tier for ambiguous scores — triggers LLM classifier fallback.\n *\n * Handles 70-80% of requests in < 1ms with zero cost.\n */\n\nimport type { Tier, ScoringResult, ScoringConfig } from \"./types.js\";\n\nexport function classifyByRules(\n prompt: string,\n systemPrompt: string | undefined,\n estimatedTokens: number,\n config: ScoringConfig,\n ambiguousZone: [number, number],\n): ScoringResult {\n const text = `${systemPrompt ?? \"\"} ${prompt}`.toLowerCase();\n let score = 0;\n const signals: string[] = [];\n\n // 1. Token count\n if (estimatedTokens < config.tokenCountThresholds.simple) {\n score -= 2;\n signals.push(`short (${estimatedTokens} tokens)`);\n } else if (estimatedTokens > config.tokenCountThresholds.complex) {\n score += 2;\n signals.push(`long (${estimatedTokens} tokens)`);\n }\n\n // 2. Code presence\n const codeMatches = config.codeKeywords.filter((kw) => text.includes(kw.toLowerCase()));\n if (codeMatches.length >= 2) {\n score += 2;\n signals.push(`code (${codeMatches.slice(0, 3).join(\", \")})`);\n } else if (codeMatches.length === 1) {\n score += 1;\n signals.push(`possible code (${codeMatches[0]})`);\n }\n\n // 3. Reasoning markers — highest priority, can override to REASONING\n const reasoningMatches = config.reasoningKeywords.filter((kw) => text.includes(kw.toLowerCase()));\n if (reasoningMatches.length > 0) {\n score += 3;\n signals.push(`reasoning (${reasoningMatches.slice(0, 3).join(\", \")})`);\n }\n\n // 4. Technical terms\n const techMatches = config.technicalKeywords.filter((kw) => text.includes(kw.toLowerCase()));\n if (techMatches.length >= 2) {\n score += Math.floor(techMatches.length / 2);\n signals.push(`technical (${techMatches.slice(0, 3).join(\", \")})`);\n }\n\n // 5. Creative markers\n const creativeMatches = config.creativeKeywords.filter((kw) => text.includes(kw.toLowerCase()));\n if (creativeMatches.length > 0) {\n score += 1;\n signals.push(`creative (${creativeMatches[0]})`);\n }\n\n // 6. Simple indicators\n const simpleMatches = config.simpleKeywords.filter((kw) => text.includes(kw.toLowerCase()));\n if (simpleMatches.length > 0) {\n score -= 2;\n signals.push(`simple (${simpleMatches.slice(0, 2).join(\", \")})`);\n }\n\n // 7. Multi-step patterns\n const multiStepPatterns = [/first.*then/i, /step \\d/i, /\\d\\.\\s/];\n const multiStepHits = multiStepPatterns.filter((p) => p.test(text));\n if (multiStepHits.length > 0) {\n score += 1;\n signals.push(\"multi-step\");\n }\n\n // 8. Question count\n const questionCount = (prompt.match(/\\?/g) || []).length;\n if (questionCount > 3) {\n score += 1;\n signals.push(`${questionCount} questions`);\n }\n\n // --- Map score to tier ---\n\n let tier: Tier | null;\n let confidence: number;\n\n // Direct reasoning override: 2+ reasoning markers = high confidence REASONING\n if (reasoningMatches.length >= 2) {\n tier = \"REASONING\";\n confidence = 0.9;\n } else if (score <= 0) {\n tier = \"SIMPLE\";\n confidence = Math.min(0.95, 0.85 + Math.abs(score) * 0.02);\n } else if (score >= ambiguousZone[0] && score <= ambiguousZone[1]) {\n // Ambiguous zone — trigger LLM classifier\n tier = null;\n confidence = 0.5;\n } else if (score >= 3 && score <= 4) {\n tier = \"MEDIUM\";\n confidence = 0.75 + (score - 3) * 0.05;\n } else if (score >= 5 && score <= 6) {\n tier = \"COMPLEX\";\n confidence = 0.7 + (score - 5) * 0.075;\n } else if (score >= 7) {\n tier = \"REASONING\";\n confidence = 0.7 + Math.min(0.1, (score - 7) * 0.05);\n } else {\n tier = null;\n confidence = 0.5;\n }\n\n return { score, tier, confidence, signals };\n}\n","/**\n * LLM Classifier (Fallback)\n *\n * When the rule-based classifier returns ambiguous (score 1-2),\n * we send a classification request to the cheapest model.\n *\n * Cost per classification: ~$0.00003\n * Latency: ~200-400ms\n * Only triggered for ~20-30% of requests.\n */\n\nimport type { Tier } from \"./types.js\";\n\nconst CLASSIFIER_PROMPT = `You are a query complexity classifier. Classify the user's query into exactly one category.\n\nCategories:\n- SIMPLE: Factual Q&A, definitions, translations, short answers\n- MEDIUM: Summaries, explanations, moderate code generation\n- COMPLEX: Multi-step code, system design, creative writing, analysis\n- REASONING: Mathematical proofs, formal logic, step-by-step problem solving\n\nRespond with ONLY one word: SIMPLE, MEDIUM, COMPLEX, or REASONING.`;\n\n// In-memory cache: hash → { tier, expires }\nconst cache = new Map<string, { tier: Tier; expires: number }>();\n\nexport type LLMClassifierConfig = {\n model: string;\n maxTokens: number;\n temperature: number;\n truncationChars: number;\n cacheTtlMs: number;\n};\n\ntype PayFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Classify a prompt using a cheap LLM.\n * Returns tier and confidence. Defaults to MEDIUM on any failure.\n */\nexport async function classifyByLLM(\n prompt: string,\n config: LLMClassifierConfig,\n payFetch: PayFetch,\n apiBase: string,\n): Promise<{ tier: Tier; confidence: number }> {\n const truncated = prompt.slice(0, config.truncationChars);\n\n // Check cache\n const cacheKey = simpleHash(truncated);\n const cached = cache.get(cacheKey);\n if (cached && cached.expires > Date.now()) {\n return { tier: cached.tier, confidence: 0.75 };\n }\n\n try {\n const response = await payFetch(`${apiBase}/v1/chat/completions`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: config.model,\n messages: [\n { role: \"system\", content: CLASSIFIER_PROMPT },\n { role: \"user\", content: truncated },\n ],\n max_tokens: config.maxTokens,\n temperature: config.temperature,\n stream: false,\n }),\n });\n\n if (!response.ok) {\n return { tier: \"MEDIUM\", confidence: 0.5 };\n }\n\n const data = (await response.json()) as {\n choices?: Array<{ message?: { content?: string } }>;\n };\n\n const content = data.choices?.[0]?.message?.content?.trim().toUpperCase() ?? \"\";\n const tier = parseTier(content);\n\n // Cache result\n cache.set(cacheKey, { tier, expires: Date.now() + config.cacheTtlMs });\n\n // Prune if cache grows too large\n if (cache.size > 1000) {\n pruneCache();\n }\n\n return { tier, confidence: 0.75 };\n } catch {\n // Any error → safe default\n return { tier: \"MEDIUM\", confidence: 0.5 };\n }\n}\n\n/**\n * Parse tier from LLM response. Handles \"SIMPLE\", \"The query is SIMPLE\", etc.\n */\nfunction parseTier(text: string): Tier {\n if (/\\bREASONING\\b/.test(text)) return \"REASONING\";\n if (/\\bCOMPLEX\\b/.test(text)) return \"COMPLEX\";\n if (/\\bMEDIUM\\b/.test(text)) return \"MEDIUM\";\n if (/\\bSIMPLE\\b/.test(text)) return \"SIMPLE\";\n return \"MEDIUM\"; // safe default\n}\n\nfunction simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash |= 0;\n }\n return hash.toString(36);\n}\n\nfunction pruneCache(): void {\n const now = Date.now();\n for (const [key, value] of cache) {\n if (value.expires <= now) {\n cache.delete(key);\n }\n }\n}\n","/**\n * Tier → Model Selection\n *\n * Maps a classification tier to the cheapest capable model.\n * Builds RoutingDecision metadata with cost estimates and savings.\n */\n\nimport type { Tier, TierConfig, RoutingDecision } from \"./types.js\";\n\nexport type ModelPricing = {\n inputPrice: number; // per 1M tokens\n outputPrice: number; // per 1M tokens\n};\n\n/**\n * Select the primary model for a tier and build the RoutingDecision.\n */\nexport function selectModel(\n tier: Tier,\n confidence: number,\n method: \"rules\" | \"llm\",\n reasoning: string,\n tierConfigs: Record<Tier, TierConfig>,\n modelPricing: Map<string, ModelPricing>,\n estimatedInputTokens: number,\n maxOutputTokens: number,\n): RoutingDecision {\n const tierConfig = tierConfigs[tier];\n const model = tierConfig.primary;\n const pricing = modelPricing.get(model);\n\n const inputCost = pricing\n ? (estimatedInputTokens / 1_000_000) * pricing.inputPrice\n : 0;\n const outputCost = pricing\n ? (maxOutputTokens / 1_000_000) * pricing.outputPrice\n : 0;\n const costEstimate = inputCost + outputCost;\n\n // Baseline: what GPT-4o would cost\n const gpt4oPricing = modelPricing.get(\"openai/gpt-4o\");\n const baselineInput = gpt4oPricing\n ? (estimatedInputTokens / 1_000_000) * gpt4oPricing.inputPrice\n : 0;\n const baselineOutput = gpt4oPricing\n ? (maxOutputTokens / 1_000_000) * gpt4oPricing.outputPrice\n : 0;\n const baselineCost = baselineInput + baselineOutput;\n\n const savings =\n baselineCost > 0\n ? Math.max(0, (baselineCost - costEstimate) / baselineCost)\n : 0;\n\n return {\n model,\n tier,\n confidence,\n method,\n reasoning,\n costEstimate,\n baselineCost,\n savings,\n };\n}\n\n/**\n * Get the ordered fallback chain for a tier: [primary, ...fallbacks].\n */\nexport function getFallbackChain(\n tier: Tier,\n tierConfigs: Record<Tier, TierConfig>,\n): string[] {\n const config = tierConfigs[tier];\n return [config.primary, ...config.fallback];\n}\n","/**\n * Default Routing Config\n *\n * All routing parameters as a TypeScript constant.\n * Operators override via openclaw.yaml plugin config.\n */\n\nimport type { RoutingConfig } from \"./types.js\";\n\nexport const DEFAULT_ROUTING_CONFIG: RoutingConfig = {\n version: \"1.0\",\n\n classifier: {\n ambiguousZone: [1, 2],\n llmModel: \"google/gemini-2.5-flash\",\n llmMaxTokens: 10,\n llmTemperature: 0,\n promptTruncationChars: 500,\n cacheTtlMs: 3_600_000, // 1 hour\n },\n\n scoring: {\n tokenCountThresholds: { simple: 50, complex: 500 },\n codeKeywords: [\n \"function\", \"class\", \"import\", \"def\", \"SELECT\", \"async\", \"await\",\n \"const\", \"let\", \"var\", \"return\", \"```\",\n ],\n reasoningKeywords: [\n \"prove\", \"theorem\", \"derive\", \"step by step\", \"chain of thought\",\n \"formally\", \"mathematical\", \"proof\", \"logically\",\n ],\n simpleKeywords: [\n \"what is\", \"define\", \"translate\", \"hello\", \"yes or no\",\n \"capital of\", \"how old\", \"who is\", \"when was\",\n ],\n technicalKeywords: [\n \"algorithm\", \"optimize\", \"architecture\", \"distributed\",\n \"kubernetes\", \"microservice\", \"database\", \"infrastructure\",\n ],\n creativeKeywords: [\n \"story\", \"poem\", \"compose\", \"brainstorm\", \"creative\",\n \"imagine\", \"write a\",\n ],\n },\n\n tiers: {\n SIMPLE: {\n primary: \"google/gemini-2.5-flash\",\n fallback: [\"deepseek/deepseek-chat\", \"openai/gpt-4o-mini\"],\n },\n MEDIUM: {\n primary: \"deepseek/deepseek-chat\",\n fallback: [\"google/gemini-2.5-flash\", \"openai/gpt-4o-mini\"],\n },\n COMPLEX: {\n primary: \"anthropic/claude-sonnet-4\",\n fallback: [\"openai/gpt-4o\", \"google/gemini-2.5-pro\"],\n },\n REASONING: {\n primary: \"openai/o3\",\n fallback: [\"google/gemini-2.5-pro\", \"anthropic/claude-sonnet-4\"],\n },\n },\n\n overrides: {\n maxTokensForceComplex: 100_000,\n structuredOutputMinTier: \"MEDIUM\",\n },\n};\n","/**\n * Smart Router Entry Point\n *\n * Classifies requests and routes to the cheapest capable model.\n * Uses hybrid approach: rules first (< 1ms), LLM fallback for ambiguous cases.\n */\n\nimport type { Tier, RoutingDecision, RoutingConfig } from \"./types.js\";\nimport { classifyByRules } from \"./rules.js\";\nimport { classifyByLLM } from \"./llm-classifier.js\";\nimport { selectModel, getFallbackChain, type ModelPricing } from \"./selector.js\";\n\nexport type RouterOptions = {\n config: RoutingConfig;\n modelPricing: Map<string, ModelPricing>;\n payFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n apiBase: string;\n};\n\n/**\n * Route a request to the cheapest capable model.\n *\n * 1. Check overrides (large context, structured output)\n * 2. Run rule-based classifier\n * 3. If ambiguous, run LLM classifier\n * 4. Select model for tier\n * 5. Return RoutingDecision with metadata\n */\nexport async function route(\n prompt: string,\n systemPrompt: string | undefined,\n maxOutputTokens: number,\n options: RouterOptions,\n): Promise<RoutingDecision> {\n const { config, modelPricing, payFetch, apiBase } = options;\n\n // Estimate input tokens (~4 chars per token)\n const fullText = `${systemPrompt ?? \"\"} ${prompt}`;\n const estimatedTokens = Math.ceil(fullText.length / 4);\n\n // --- Override: large context → force COMPLEX ---\n if (estimatedTokens > config.overrides.maxTokensForceComplex) {\n return selectModel(\n \"COMPLEX\",\n 0.95,\n \"rules\",\n `Input exceeds ${config.overrides.maxTokensForceComplex} tokens`,\n config.tiers,\n modelPricing,\n estimatedTokens,\n maxOutputTokens,\n );\n }\n\n // Structured output detection\n const hasStructuredOutput = systemPrompt\n ? /json|structured|schema/i.test(systemPrompt)\n : false;\n\n // --- Rule-based classification ---\n const ruleResult = classifyByRules(\n prompt,\n systemPrompt,\n estimatedTokens,\n config.scoring,\n config.classifier.ambiguousZone,\n );\n\n let tier: Tier;\n let confidence: number;\n let method: \"rules\" | \"llm\" = \"rules\";\n let reasoning = `score=${ruleResult.score} | ${ruleResult.signals.join(\", \")}`;\n\n if (ruleResult.tier !== null) {\n tier = ruleResult.tier;\n confidence = ruleResult.confidence;\n } else {\n // Ambiguous — LLM classifier fallback\n const llmResult = await classifyByLLM(\n prompt,\n {\n model: config.classifier.llmModel,\n maxTokens: config.classifier.llmMaxTokens,\n temperature: config.classifier.llmTemperature,\n truncationChars: config.classifier.promptTruncationChars,\n cacheTtlMs: config.classifier.cacheTtlMs,\n },\n payFetch,\n apiBase,\n );\n\n tier = llmResult.tier;\n confidence = llmResult.confidence;\n method = \"llm\";\n reasoning += ` | ambiguous -> LLM: ${tier}`;\n }\n\n // Apply structured output minimum tier\n if (hasStructuredOutput) {\n const tierRank: Record<Tier, number> = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };\n const minTier = config.overrides.structuredOutputMinTier;\n if (tierRank[tier] < tierRank[minTier]) {\n reasoning += ` | upgraded to ${minTier} (structured output)`;\n tier = minTier;\n }\n }\n\n return selectModel(\n tier,\n confidence,\n method,\n reasoning,\n config.tiers,\n modelPricing,\n estimatedTokens,\n maxOutputTokens,\n );\n}\n\nexport { getFallbackChain } from \"./selector.js\";\nexport { DEFAULT_ROUTING_CONFIG } from \"./config.js\";\nexport type { RoutingDecision, Tier, RoutingConfig } from \"./types.js\";\nexport type { ModelPricing } from \"./selector.js\";\n","/**\n * Usage Logger\n *\n * Logs every LLM request as a JSON line to a daily log file.\n * Files: ~/.openclaw/blockrun/logs/usage-YYYY-MM-DD.jsonl\n *\n * MVP: append-only JSON lines. No rotation, no cleanup.\n * Logging never breaks the request flow — all errors are swallowed.\n */\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport type UsageEntry = {\n timestamp: string;\n model: string;\n cost: number;\n latencyMs: number;\n};\n\nconst LOG_DIR = join(homedir(), \".openclaw\", \"blockrun\", \"logs\");\nlet dirReady = false;\n\nasync function ensureDir(): Promise<void> {\n if (dirReady) return;\n await mkdir(LOG_DIR, { recursive: true });\n dirReady = true;\n}\n\n/**\n * Log a usage entry as a JSON line.\n */\nexport async function logUsage(entry: UsageEntry): Promise<void> {\n try {\n await ensureDir();\n const date = entry.timestamp.slice(0, 10); // YYYY-MM-DD\n const file = join(LOG_DIR, `usage-${date}.jsonl`);\n await appendFile(file, JSON.stringify(entry) + \"\\n\");\n } catch {\n // Never break the request flow\n }\n}\n","/**\n * @blockrun/openclaw-provider\n *\n * OpenClaw plugin that adds BlockRun as an LLM provider with 30+ AI models.\n * Payments are handled automatically via x402 USDC micropayments on Base.\n * Smart routing picks the cheapest capable model for each request.\n *\n * Usage:\n * # Install the plugin\n * openclaw plugin install @blockrun/openclaw-provider\n *\n * # Set wallet key\n * export BLOCKRUN_WALLET_KEY=0x...\n *\n * # Or configure via wizard\n * openclaw provider add blockrun\n *\n * # Use smart routing (auto-picks cheapest model)\n * openclaw config set model blockrun/auto\n *\n * # Or use any specific BlockRun model\n * openclaw config set model openai/gpt-5.2\n */\n\nimport type { OpenClawPluginDefinition, OpenClawPluginApi } from \"./types.js\";\nimport { blockrunProvider, setActiveProxy } from \"./provider.js\";\nimport { startProxy } from \"./proxy.js\";\nimport { resolveOrGenerateWalletKey } from \"./auth.js\";\nimport type { RoutingConfig } from \"./router/index.js\";\n\nconst plugin: OpenClawPluginDefinition = {\n id: \"claw-router\",\n name: \"ClawRouter\",\n description: \"Smart LLM router — 30+ models, x402 micropayments, 63% cost savings\",\n version: \"0.1.0\",\n\n register(api: OpenClawPluginApi) {\n // Register BlockRun as a provider\n api.registerProvider(blockrunProvider);\n\n api.logger.info(\"BlockRun provider registered (30+ models via x402)\");\n },\n\n async activate(api: OpenClawPluginApi) {\n // Resolve wallet key: saved file → env var → auto-generate\n const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();\n\n // Log wallet source\n if (source === \"generated\") {\n api.logger.info(`Generated new wallet: ${address}`);\n api.logger.info(`Fund with USDC on Base to start using ClawRouter.`);\n } else if (source === \"saved\") {\n api.logger.info(`Using saved wallet: ${address}`);\n } else {\n api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);\n }\n\n // Resolve routing config overrides from plugin config\n const routingConfig = api.pluginConfig?.routing as Partial<RoutingConfig> | undefined;\n\n // Start the local x402 proxy\n try {\n const proxy = await startProxy({\n walletKey,\n routingConfig,\n onReady: (port) => {\n api.logger.info(`BlockRun x402 proxy listening on port ${port}`);\n },\n onError: (error) => {\n api.logger.error(`BlockRun proxy error: ${error.message}`);\n },\n onRouted: (decision) => {\n const cost = decision.costEstimate.toFixed(4);\n const saved = (decision.savings * 100).toFixed(0);\n api.logger.info(`${decision.model} $${cost} (saved ${saved}%)`);\n },\n });\n\n setActiveProxy(proxy);\n api.logger.info(`BlockRun provider active — ${proxy.baseUrl}/v1 (smart routing enabled)`);\n } catch (err) {\n api.logger.error(\n `Failed to start BlockRun proxy: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n },\n};\n\n\nexport default plugin;\n\n// Re-export for programmatic use\nexport { startProxy } from \"./proxy.js\";\nexport { blockrunProvider } from \"./provider.js\";\nexport { OPENCLAW_MODELS, BLOCKRUN_MODELS, buildProviderModels } from \"./models.js\";\nexport { route, DEFAULT_ROUTING_CONFIG } from \"./router/index.js\";\nexport type { RoutingDecision, RoutingConfig, Tier } from \"./router/index.js\";\nexport { logUsage } from \"./logger.js\";\nexport type { UsageEntry } from \"./logger.js\";\n"],"mappings":"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","names":["privateKeyToAccount","privateKeyToAccount","mkdir","join","homedir","privateKeyToAccount"]}
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/models.ts","../src/provider.ts","../src/proxy.ts","../src/x402.ts","../src/router/rules.ts","../src/router/llm-classifier.ts","../src/router/selector.ts","../src/router/config.ts","../src/router/index.ts","../src/logger.ts","../src/index.ts"],"sourcesContent":["/**\n * BlockRun Auth Methods for OpenClaw\n *\n * Provides wallet-based authentication for the BlockRun provider.\n * Operators configure their wallet private key, which is used to\n * sign x402 micropayments for LLM inference.\n *\n * Three methods:\n * 1. Auto-generate — create a new wallet on first run, save to ~/.openclaw/blockrun/wallet.key\n * 2. Environment variable — read from BLOCKRUN_WALLET_KEY\n * 3. Manual input — operator enters private key via wizard\n */\n\nimport { writeFile, readFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { generatePrivateKey, privateKeyToAccount } from \"viem/accounts\";\nimport type { ProviderAuthMethod, ProviderAuthContext, ProviderAuthResult } from \"./types.js\";\n\nconst WALLET_DIR = join(homedir(), \".openclaw\", \"blockrun\");\nconst WALLET_FILE = join(WALLET_DIR, \"wallet.key\");\n\n/**\n * Try to load a previously auto-generated wallet key from disk.\n */\nasync function loadSavedWallet(): Promise<string | undefined> {\n try {\n const key = (await readFile(WALLET_FILE, \"utf-8\")).trim();\n if (key.startsWith(\"0x\") && key.length === 66) return key;\n } catch {\n // File doesn't exist yet\n }\n return undefined;\n}\n\n/**\n * Generate a new wallet, save to disk, return the private key.\n */\nasync function generateAndSaveWallet(): Promise<{ key: string; address: string }> {\n const key = generatePrivateKey();\n const account = privateKeyToAccount(key);\n await mkdir(WALLET_DIR, { recursive: true });\n await writeFile(WALLET_FILE, key + \"\\n\", { mode: 0o600 });\n return { key, address: account.address };\n}\n\n/**\n * Resolve wallet key: load saved → env var → auto-generate.\n * Called by index.ts before the auth wizard runs.\n */\nexport async function resolveOrGenerateWalletKey(): Promise<{ key: string; address: string; source: \"saved\" | \"env\" | \"generated\" }> {\n // 1. Previously saved wallet\n const saved = await loadSavedWallet();\n if (saved) {\n const account = privateKeyToAccount(saved as `0x${string}`);\n return { key: saved, address: account.address, source: \"saved\" };\n }\n\n // 2. Environment variable\n const envKey = process.env.BLOCKRUN_WALLET_KEY;\n if (typeof envKey === \"string\" && envKey.startsWith(\"0x\") && envKey.length === 66) {\n const account = privateKeyToAccount(envKey as `0x${string}`);\n return { key: envKey, address: account.address, source: \"env\" };\n }\n\n // 3. Auto-generate\n const { key, address } = await generateAndSaveWallet();\n return { key, address, source: \"generated\" };\n}\n\n/**\n * Auth method: operator enters their wallet private key directly.\n */\nexport const walletKeyAuth: ProviderAuthMethod = {\n id: \"wallet-key\",\n label: \"Wallet Private Key\",\n hint: \"Enter your EVM wallet private key (0x...) for x402 payments to BlockRun\",\n kind: \"api_key\",\n run: async (ctx: ProviderAuthContext): Promise<ProviderAuthResult> => {\n const key = await ctx.prompter.text({\n message: \"Enter your wallet private key (0x...)\",\n validate: (value: string) => {\n const trimmed = value.trim();\n if (!trimmed.startsWith(\"0x\")) return \"Key must start with 0x\";\n if (trimmed.length !== 66) return \"Key must be 66 characters (0x + 64 hex)\";\n if (!/^0x[0-9a-fA-F]{64}$/.test(trimmed)) return \"Key must be valid hex\";\n return undefined;\n },\n });\n\n if (!key || typeof key !== \"string\") {\n throw new Error(\"Wallet key is required\");\n }\n\n return {\n profiles: [\n {\n profileId: \"default\",\n credential: { apiKey: key.trim() },\n },\n ],\n notes: [\n \"Wallet key stored securely in OpenClaw credentials.\",\n \"Your wallet signs x402 USDC payments on Base for each LLM call.\",\n \"Fund your wallet with USDC on Base to start using BlockRun models.\",\n ],\n };\n },\n};\n\n/**\n * Auth method: read wallet key from BLOCKRUN_WALLET_KEY environment variable.\n */\nexport const envKeyAuth: ProviderAuthMethod = {\n id: \"env-key\",\n label: \"Environment Variable\",\n hint: \"Use BLOCKRUN_WALLET_KEY environment variable\",\n kind: \"api_key\",\n run: async (): Promise<ProviderAuthResult> => {\n const key = process.env.BLOCKRUN_WALLET_KEY;\n\n if (!key) {\n throw new Error(\n \"BLOCKRUN_WALLET_KEY environment variable is not set. \" +\n \"Set it to your EVM wallet private key (0x...).\",\n );\n }\n\n return {\n profiles: [\n {\n profileId: \"default\",\n credential: { apiKey: key.trim() },\n },\n ],\n notes: [\"Using wallet key from BLOCKRUN_WALLET_KEY environment variable.\"],\n };\n },\n};\n","/**\n * BlockRun Model Definitions for OpenClaw\n *\n * Maps BlockRun's 30+ AI models to OpenClaw's ModelDefinitionConfig format.\n * All models use the \"openai-completions\" API since BlockRun is OpenAI-compatible.\n *\n * Pricing is in USD per 1M tokens. Operators pay these rates via x402;\n * they set their own markup when reselling to end users (Phase 2).\n */\n\nimport type { ModelDefinitionConfig, ModelProviderConfig } from \"./types.js\";\n\ntype BlockRunModel = {\n id: string;\n name: string;\n inputPrice: number;\n outputPrice: number;\n contextWindow: number;\n maxOutput: number;\n reasoning?: boolean;\n vision?: boolean;\n};\n\nexport const BLOCKRUN_MODELS: BlockRunModel[] = [\n // Smart routing meta-model — proxy replaces with actual model\n { id: \"blockrun/auto\", name: \"BlockRun Smart Router\", inputPrice: 0, outputPrice: 0, contextWindow: 1_050_000, maxOutput: 128_000 },\n\n\n // OpenAI GPT-5 Family\n { id: \"openai/gpt-5.2\", name: \"GPT-5.2\", inputPrice: 1.75, outputPrice: 14.0, contextWindow: 400000, maxOutput: 128000, reasoning: true, vision: true },\n { id: \"openai/gpt-5-mini\", name: \"GPT-5 Mini\", inputPrice: 0.25, outputPrice: 2.0, contextWindow: 200000, maxOutput: 65536 },\n { id: \"openai/gpt-5-nano\", name: \"GPT-5 Nano\", inputPrice: 0.05, outputPrice: 0.4, contextWindow: 128000, maxOutput: 32768 },\n { id: \"openai/gpt-5.2-pro\", name: \"GPT-5.2 Pro\", inputPrice: 21.0, outputPrice: 168.0, contextWindow: 400000, maxOutput: 128000, reasoning: true },\n\n // OpenAI GPT-4 Family\n { id: \"openai/gpt-4.1\", name: \"GPT-4.1\", inputPrice: 2.0, outputPrice: 8.0, contextWindow: 128000, maxOutput: 16384, vision: true },\n { id: \"openai/gpt-4.1-mini\", name: \"GPT-4.1 Mini\", inputPrice: 0.4, outputPrice: 1.6, contextWindow: 128000, maxOutput: 16384 },\n { id: \"openai/gpt-4.1-nano\", name: \"GPT-4.1 Nano\", inputPrice: 0.1, outputPrice: 0.4, contextWindow: 128000, maxOutput: 16384 },\n { id: \"openai/gpt-4o\", name: \"GPT-4o\", inputPrice: 2.5, outputPrice: 10.0, contextWindow: 128000, maxOutput: 16384, vision: true },\n { id: \"openai/gpt-4o-mini\", name: \"GPT-4o Mini\", inputPrice: 0.15, outputPrice: 0.6, contextWindow: 128000, maxOutput: 16384 },\n\n // OpenAI O-series (Reasoning)\n { id: \"openai/o1\", name: \"o1\", inputPrice: 15.0, outputPrice: 60.0, contextWindow: 200000, maxOutput: 100000, reasoning: true },\n { id: \"openai/o1-mini\", name: \"o1-mini\", inputPrice: 1.1, outputPrice: 4.4, contextWindow: 128000, maxOutput: 65536, reasoning: true },\n { id: \"openai/o3\", name: \"o3\", inputPrice: 2.0, outputPrice: 8.0, contextWindow: 200000, maxOutput: 100000, reasoning: true },\n { id: \"openai/o3-mini\", name: \"o3-mini\", inputPrice: 1.1, outputPrice: 4.4, contextWindow: 128000, maxOutput: 65536, reasoning: true },\n { id: \"openai/o4-mini\", name: \"o4-mini\", inputPrice: 1.1, outputPrice: 4.4, contextWindow: 128000, maxOutput: 65536, reasoning: true },\n\n // Anthropic\n { id: \"anthropic/claude-haiku-4.5\", name: \"Claude Haiku 4.5\", inputPrice: 1.0, outputPrice: 5.0, contextWindow: 200000, maxOutput: 8192 },\n { id: \"anthropic/claude-sonnet-4\", name: \"Claude Sonnet 4\", inputPrice: 3.0, outputPrice: 15.0, contextWindow: 200000, maxOutput: 64000, reasoning: true },\n { id: \"anthropic/claude-opus-4\", name: \"Claude Opus 4\", inputPrice: 15.0, outputPrice: 75.0, contextWindow: 200000, maxOutput: 32000, reasoning: true },\n { id: \"anthropic/claude-opus-4.5\", name: \"Claude Opus 4.5\", inputPrice: 15.0, outputPrice: 75.0, contextWindow: 200000, maxOutput: 32000, reasoning: true },\n\n // Google\n { id: \"google/gemini-3-pro-preview\", name: \"Gemini 3 Pro Preview\", inputPrice: 2.0, outputPrice: 12.0, contextWindow: 1050000, maxOutput: 65536, reasoning: true, vision: true },\n { id: \"google/gemini-2.5-pro\", name: \"Gemini 2.5 Pro\", inputPrice: 1.25, outputPrice: 10.0, contextWindow: 1050000, maxOutput: 65536, reasoning: true, vision: true },\n { id: \"google/gemini-2.5-flash\", name: \"Gemini 2.5 Flash\", inputPrice: 0.15, outputPrice: 0.6, contextWindow: 1000000, maxOutput: 65536 },\n\n // DeepSeek\n { id: \"deepseek/deepseek-chat\", name: \"DeepSeek V3.2 Chat\", inputPrice: 0.28, outputPrice: 0.42, contextWindow: 128000, maxOutput: 8192 },\n { id: \"deepseek/deepseek-reasoner\", name: \"DeepSeek V3.2 Reasoner\", inputPrice: 0.28, outputPrice: 0.42, contextWindow: 128000, maxOutput: 8192, reasoning: true },\n\n // xAI / Grok\n { id: \"xai/grok-3\", name: \"Grok 3\", inputPrice: 3.0, outputPrice: 15.0, contextWindow: 131072, maxOutput: 16384, reasoning: true },\n { id: \"xai/grok-3-fast\", name: \"Grok 3 Fast\", inputPrice: 5.0, outputPrice: 25.0, contextWindow: 131072, maxOutput: 16384, reasoning: true },\n { id: \"xai/grok-3-mini\", name: \"Grok 3 Mini\", inputPrice: 0.3, outputPrice: 0.5, contextWindow: 131072, maxOutput: 16384 },\n];\n\n/**\n * Convert BlockRun model definitions to OpenClaw ModelDefinitionConfig format.\n */\nfunction toOpenClawModel(m: BlockRunModel): ModelDefinitionConfig {\n return {\n id: m.id,\n name: m.name,\n api: \"openai-completions\",\n reasoning: m.reasoning ?? false,\n input: m.vision ? [\"text\", \"image\"] : [\"text\"],\n cost: {\n input: m.inputPrice,\n output: m.outputPrice,\n cacheRead: 0,\n cacheWrite: 0,\n },\n contextWindow: m.contextWindow,\n maxTokens: m.maxOutput,\n };\n}\n\n/**\n * All BlockRun models in OpenClaw format.\n */\nexport const OPENCLAW_MODELS: ModelDefinitionConfig[] = BLOCKRUN_MODELS.map(toOpenClawModel);\n\n/**\n * Build a ModelProviderConfig for BlockRun.\n *\n * @param baseUrl - The proxy's local base URL (e.g., \"http://127.0.0.1:12345\")\n */\nexport function buildProviderModels(baseUrl: string): ModelProviderConfig {\n return {\n baseUrl: `${baseUrl}/v1`,\n api: \"openai-completions\",\n models: OPENCLAW_MODELS,\n };\n}\n","/**\n * BlockRun ProviderPlugin for OpenClaw\n *\n * Registers BlockRun as an LLM provider in OpenClaw.\n * Uses a local x402 proxy to handle micropayments transparently —\n * pi-ai sees a standard OpenAI-compatible API at localhost.\n */\n\nimport type { ProviderPlugin, AuthProfileCredential } from \"./types.js\";\nimport { walletKeyAuth, envKeyAuth } from \"./auth.js\";\nimport { buildProviderModels } from \"./models.js\";\nimport type { ProxyHandle } from \"./proxy.js\";\n\n/**\n * State for the running proxy (set when the plugin activates).\n */\nlet activeProxy: ProxyHandle | null = null;\n\n/**\n * Update the proxy handle (called from index.ts when the proxy starts).\n */\nexport function setActiveProxy(proxy: ProxyHandle): void {\n activeProxy = proxy;\n}\n\nexport function getActiveProxy(): ProxyHandle | null {\n return activeProxy;\n}\n\n/**\n * BlockRun provider plugin definition.\n */\nexport const blockrunProvider: ProviderPlugin = {\n id: \"blockrun\",\n label: \"BlockRun\",\n docsPath: \"https://blockrun.ai/docs\",\n aliases: [\"br\"],\n envVars: [\"BLOCKRUN_WALLET_KEY\"],\n\n // Model definitions — dynamically set to proxy URL\n get models() {\n if (!activeProxy) {\n // Fallback: point to BlockRun API directly (won't handle x402, but\n // allows config loading before proxy starts)\n return buildProviderModels(\"https://blockrun.ai/api\");\n }\n return buildProviderModels(activeProxy.baseUrl);\n },\n\n // Auth methods\n auth: [envKeyAuth, walletKeyAuth],\n\n // Format the stored credential as the wallet key\n formatApiKey: (cred: AuthProfileCredential): string => {\n if (\"apiKey\" in cred && typeof cred.apiKey === \"string\") {\n return cred.apiKey;\n }\n throw new Error(\"BlockRun credential must contain an apiKey (wallet private key)\");\n },\n};\n","/**\n * Local x402 Proxy Server\n *\n * Sits between OpenClaw's pi-ai (which makes standard OpenAI-format requests)\n * and BlockRun's API (which requires x402 micropayments).\n *\n * Flow:\n * pi-ai → http://localhost:{port}/v1/chat/completions\n * → proxy forwards to https://blockrun.ai/api/v1/chat/completions\n * → gets 402 → @x402/fetch signs payment → retries\n * → streams response back to pi-ai\n *\n * Phase 2 additions:\n * - Smart routing: when model is \"blockrun/auto\", classify query and pick cheapest model\n * - Usage logging: log every request as JSON line to ~/.openclaw/blockrun/logs/\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { createPaymentFetch } from \"./x402.js\";\nimport { route, getFallbackChain, DEFAULT_ROUTING_CONFIG, type RouterOptions, type RoutingDecision, type RoutingConfig, type ModelPricing } from \"./router/index.js\";\nimport { BLOCKRUN_MODELS } from \"./models.js\";\nimport { logUsage, type UsageEntry } from \"./logger.js\";\n\nconst BLOCKRUN_API = \"https://blockrun.ai/api\";\nconst AUTO_MODEL = \"blockrun/auto\";\nconst USER_AGENT = \"claw-router/0.1.0\";\n\nexport type ProxyOptions = {\n walletKey: string;\n apiBase?: string;\n port?: number;\n routingConfig?: Partial<RoutingConfig>;\n onReady?: (port: number) => void;\n onError?: (error: Error) => void;\n onPayment?: (info: { model: string; amount: string; network: string }) => void;\n onRouted?: (decision: RoutingDecision) => void;\n};\n\nexport type ProxyHandle = {\n port: number;\n baseUrl: string;\n close: () => Promise<void>;\n};\n\n/**\n * Build model pricing map from BLOCKRUN_MODELS.\n */\nfunction buildModelPricing(): Map<string, ModelPricing> {\n const map = new Map<string, ModelPricing>();\n for (const m of BLOCKRUN_MODELS) {\n if (m.id === AUTO_MODEL) continue; // skip meta-model\n map.set(m.id, { inputPrice: m.inputPrice, outputPrice: m.outputPrice });\n }\n return map;\n}\n\n/**\n * Merge partial routing config overrides with defaults.\n */\nfunction mergeRoutingConfig(overrides?: Partial<RoutingConfig>): RoutingConfig {\n if (!overrides) return DEFAULT_ROUTING_CONFIG;\n return {\n ...DEFAULT_ROUTING_CONFIG,\n ...overrides,\n classifier: { ...DEFAULT_ROUTING_CONFIG.classifier, ...overrides.classifier },\n scoring: { ...DEFAULT_ROUTING_CONFIG.scoring, ...overrides.scoring },\n tiers: { ...DEFAULT_ROUTING_CONFIG.tiers, ...overrides.tiers },\n overrides: { ...DEFAULT_ROUTING_CONFIG.overrides, ...overrides.overrides },\n };\n}\n\n/**\n * Start the local x402 proxy server.\n *\n * Returns a handle with the assigned port, base URL, and a close function.\n */\nexport async function startProxy(options: ProxyOptions): Promise<ProxyHandle> {\n const apiBase = options.apiBase ?? BLOCKRUN_API;\n\n // Create x402 payment-enabled fetch from wallet private key\n const account = privateKeyToAccount(options.walletKey as `0x${string}`);\n const payFetch = createPaymentFetch(options.walletKey as `0x${string}`);\n\n // Build router options\n const routingConfig = mergeRoutingConfig(options.routingConfig);\n const modelPricing = buildModelPricing();\n const routerOpts: RouterOptions = {\n config: routingConfig,\n modelPricing,\n payFetch,\n apiBase,\n };\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n // Health check\n if (req.url === \"/health\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"ok\", wallet: account.address }));\n return;\n }\n\n // Only proxy paths starting with /v1\n if (!req.url?.startsWith(\"/v1\")) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n return;\n }\n\n try {\n await proxyRequest(req, res, apiBase, payFetch, options, routerOpts);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n options.onError?.(error);\n\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({\n error: { message: `Proxy error: ${error.message}`, type: \"proxy_error\" },\n }));\n }\n }\n });\n\n // Listen on requested port (0 = random available port)\n const listenPort = options.port ?? 0;\n\n return new Promise<ProxyHandle>((resolve, reject) => {\n server.on(\"error\", reject);\n\n server.listen(listenPort, \"127.0.0.1\", () => {\n const addr = server.address() as AddressInfo;\n const port = addr.port;\n const baseUrl = `http://127.0.0.1:${port}`;\n\n options.onReady?.(port);\n\n resolve({\n port,\n baseUrl,\n close: () =>\n new Promise<void>((res, rej) => {\n server.close((err) => (err ? rej(err) : res()));\n }),\n });\n });\n });\n}\n\n/**\n * Proxy a single request through x402 payment flow to BlockRun API.\n *\n * When model is \"blockrun/auto\", runs the smart router to pick the\n * cheapest capable model before forwarding.\n */\nasync function proxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n apiBase: string,\n payFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>,\n options: ProxyOptions,\n routerOpts: RouterOptions,\n): Promise<void> {\n const startTime = Date.now();\n\n // Build upstream URL: /v1/chat/completions → https://blockrun.ai/api/v1/chat/completions\n const upstreamUrl = `${apiBase}${req.url}`;\n\n // Collect request body\n const bodyChunks: Buffer[] = [];\n for await (const chunk of req) {\n bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n let body = Buffer.concat(bodyChunks);\n\n // --- Smart routing ---\n let routingDecision: RoutingDecision | undefined;\n const isChatCompletion = req.url?.includes(\"/chat/completions\");\n\n if (isChatCompletion && body.length > 0) {\n try {\n const parsed = JSON.parse(body.toString()) as Record<string, unknown>;\n\n if (parsed.model === AUTO_MODEL) {\n // Extract prompt from messages\n type ChatMessage = { role: string; content: string };\n const messages = parsed.messages as ChatMessage[] | undefined;\n let lastUserMsg: ChatMessage | undefined;\n if (messages) {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"user\") { lastUserMsg = messages[i]; break; }\n }\n }\n const systemMsg = messages?.find((m: ChatMessage) => m.role === \"system\");\n const prompt = typeof lastUserMsg?.content === \"string\" ? lastUserMsg.content : \"\";\n const systemPrompt = typeof systemMsg?.content === \"string\" ? systemMsg.content : undefined;\n const maxTokens = (parsed.max_tokens as number) || 4096;\n\n routingDecision = await route(prompt, systemPrompt, maxTokens, routerOpts);\n\n // Replace model in body\n parsed.model = routingDecision.model;\n body = Buffer.from(JSON.stringify(parsed));\n\n options.onRouted?.(routingDecision);\n }\n } catch {\n // JSON parse error — forward body as-is\n }\n }\n\n // Forward headers, stripping host, connection, and content-length\n // (content-length may be wrong after body modification for routing)\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (key === \"host\" || key === \"connection\" || key === \"transfer-encoding\" || key === \"content-length\") continue;\n if (typeof value === \"string\") {\n headers[key] = value;\n }\n }\n // Ensure content-type is set\n if (!headers[\"content-type\"]) {\n headers[\"content-type\"] = \"application/json\";\n }\n // Set User-Agent for BlockRun API tracking\n headers[\"user-agent\"] = USER_AGENT;\n\n // Make the request through x402-wrapped fetch\n // This handles: request → 402 → sign payment → retry with PAYMENT-SIGNATURE header\n const upstream = await payFetch(upstreamUrl, {\n method: req.method ?? \"POST\",\n headers,\n body: body.length > 0 ? body : undefined,\n });\n\n // Forward status and headers from upstream\n const responseHeaders: Record<string, string> = {};\n upstream.headers.forEach((value, key) => {\n // Skip hop-by-hop headers\n if (key === \"transfer-encoding\" || key === \"connection\") return;\n responseHeaders[key] = value;\n });\n\n res.writeHead(upstream.status, responseHeaders);\n\n // Stream the response body\n if (upstream.body) {\n const reader = upstream.body.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(value);\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n res.end();\n\n // --- Usage logging (fire-and-forget) ---\n if (routingDecision) {\n const entry: UsageEntry = {\n timestamp: new Date().toISOString(),\n model: routingDecision.model,\n cost: routingDecision.costEstimate,\n latencyMs: Date.now() - startTime,\n };\n logUsage(entry).catch(() => {});\n }\n}\n","/**\n * x402 Payment Implementation\n *\n * Based on BlockRun's proven implementation.\n * Handles 402 Payment Required responses with EIP-712 signed USDC transfers.\n */\n\nimport { signTypedData, privateKeyToAccount } from \"viem/accounts\";\n\nconst BASE_CHAIN_ID = 8453;\nconst USDC_BASE = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\" as const;\n\nconst USDC_DOMAIN = {\n name: \"USD Coin\",\n version: \"2\",\n chainId: BASE_CHAIN_ID,\n verifyingContract: USDC_BASE,\n} as const;\n\nconst TRANSFER_TYPES = {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n} as const;\n\nfunction createNonce(): `0x${string}` {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return `0x${Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`;\n}\n\ninterface PaymentOption {\n scheme: string;\n network: string;\n amount?: string;\n maxAmountRequired?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: { name?: string; version?: string };\n}\n\ninterface PaymentRequired {\n accepts: PaymentOption[];\n resource?: { url?: string; description?: string };\n}\n\nfunction parsePaymentRequired(headerValue: string): PaymentRequired {\n const decoded = atob(headerValue);\n return JSON.parse(decoded) as PaymentRequired;\n}\n\nasync function createPaymentPayload(\n privateKey: `0x${string}`,\n fromAddress: string,\n recipient: string,\n amount: string,\n resourceUrl: string\n): Promise<string> {\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - 600;\n const validBefore = now + 300;\n const nonce = createNonce();\n\n const signature = await signTypedData({\n privateKey,\n domain: USDC_DOMAIN,\n types: TRANSFER_TYPES,\n primaryType: \"TransferWithAuthorization\",\n message: {\n from: fromAddress as `0x${string}`,\n to: recipient as `0x${string}`,\n value: BigInt(amount),\n validAfter: BigInt(validAfter),\n validBefore: BigInt(validBefore),\n nonce,\n },\n });\n\n const paymentData = {\n x402Version: 2,\n resource: {\n url: resourceUrl,\n description: \"BlockRun AI API call\",\n mimeType: \"application/json\",\n },\n accepted: {\n scheme: \"exact\",\n network: \"eip155:8453\",\n amount,\n asset: USDC_BASE,\n payTo: recipient,\n maxTimeoutSeconds: 300,\n extra: { name: \"USD Coin\", version: \"2\" },\n },\n payload: {\n signature,\n authorization: {\n from: fromAddress,\n to: recipient,\n value: amount,\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n },\n },\n extensions: {},\n };\n\n return btoa(JSON.stringify(paymentData));\n}\n\n/**\n * Create a fetch wrapper that handles x402 payment automatically.\n */\nexport function createPaymentFetch(privateKey: `0x${string}`): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {\n const account = privateKeyToAccount(privateKey);\n const walletAddress = account.address;\n\n return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n // First request - may get 402\n const response = await fetch(input, init);\n\n if (response.status !== 402) {\n return response;\n }\n\n // Parse 402 payment requirements\n const paymentHeader = response.headers.get(\"x-payment-required\");\n if (!paymentHeader) {\n throw new Error(\"402 response missing x-payment-required header\");\n }\n\n const paymentRequired = parsePaymentRequired(paymentHeader);\n const option = paymentRequired.accepts?.[0];\n if (!option) {\n throw new Error(\"No payment options in 402 response\");\n }\n\n const amount = option.amount || option.maxAmountRequired;\n if (!amount) {\n throw new Error(\"No amount in payment requirements\");\n }\n\n // Create signed payment\n const paymentPayload = await createPaymentPayload(\n privateKey,\n walletAddress,\n option.payTo,\n amount,\n url\n );\n\n // Retry with payment\n const retryHeaders = new Headers(init?.headers);\n retryHeaders.set(\"payment-signature\", paymentPayload);\n\n return fetch(input, {\n ...init,\n headers: retryHeaders,\n });\n };\n}\n","/**\n * Rule-Based Classifier (v2 — Weighted Scoring)\n *\n * Scores a request across 14 weighted dimensions and maps the aggregate\n * score to a tier using configurable boundaries. Confidence is calibrated\n * via sigmoid — low confidence triggers the fallback classifier.\n *\n * Handles 70-80% of requests in < 1ms with zero cost.\n */\n\nimport type { Tier, ScoringResult, ScoringConfig } from \"./types.js\";\n\ntype DimensionScore = { name: string; score: number; signal: string | null };\n\n// ─── Dimension Scorers ───\n// Each returns a score in [-1, 1] and an optional signal string.\n\nfunction scoreTokenCount(\n estimatedTokens: number,\n thresholds: { simple: number; complex: number },\n): DimensionScore {\n if (estimatedTokens < thresholds.simple) {\n return { name: \"tokenCount\", score: -1.0, signal: `short (${estimatedTokens} tokens)` };\n }\n if (estimatedTokens > thresholds.complex) {\n return { name: \"tokenCount\", score: 1.0, signal: `long (${estimatedTokens} tokens)` };\n }\n return { name: \"tokenCount\", score: 0, signal: null };\n}\n\nfunction scoreKeywordMatch(\n text: string,\n keywords: string[],\n name: string,\n signalLabel: string,\n thresholds: { low: number; high: number },\n scores: { none: number; low: number; high: number },\n): DimensionScore {\n const matches = keywords.filter((kw) => text.includes(kw.toLowerCase()));\n if (matches.length >= thresholds.high) {\n return { name, score: scores.high, signal: `${signalLabel} (${matches.slice(0, 3).join(\", \")})` };\n }\n if (matches.length >= thresholds.low) {\n return { name, score: scores.low, signal: `${signalLabel} (${matches.slice(0, 3).join(\", \")})` };\n }\n return { name, score: scores.none, signal: null };\n}\n\nfunction scoreMultiStep(text: string): DimensionScore {\n const patterns = [/first.*then/i, /step \\d/i, /\\d\\.\\s/];\n const hits = patterns.filter((p) => p.test(text));\n if (hits.length > 0) {\n return { name: \"multiStepPatterns\", score: 0.5, signal: \"multi-step\" };\n }\n return { name: \"multiStepPatterns\", score: 0, signal: null };\n}\n\nfunction scoreQuestionComplexity(prompt: string): DimensionScore {\n const count = (prompt.match(/\\?/g) || []).length;\n if (count > 3) {\n return { name: \"questionComplexity\", score: 0.5, signal: `${count} questions` };\n }\n return { name: \"questionComplexity\", score: 0, signal: null };\n}\n\n// ─── Main Classifier ───\n\nexport function classifyByRules(\n prompt: string,\n systemPrompt: string | undefined,\n estimatedTokens: number,\n config: ScoringConfig,\n): ScoringResult {\n const text = `${systemPrompt ?? \"\"} ${prompt}`.toLowerCase();\n\n // Score all 14 dimensions\n const dimensions: DimensionScore[] = [\n // Original 8 dimensions\n scoreTokenCount(estimatedTokens, config.tokenCountThresholds),\n scoreKeywordMatch(text, config.codeKeywords, \"codePresence\", \"code\",\n { low: 1, high: 2 }, { none: 0, low: 0.5, high: 1.0 }),\n scoreKeywordMatch(text, config.reasoningKeywords, \"reasoningMarkers\", \"reasoning\",\n { low: 1, high: 2 }, { none: 0, low: 0.7, high: 1.0 }),\n scoreKeywordMatch(text, config.technicalKeywords, \"technicalTerms\", \"technical\",\n { low: 2, high: 4 }, { none: 0, low: 0.5, high: 1.0 }),\n scoreKeywordMatch(text, config.creativeKeywords, \"creativeMarkers\", \"creative\",\n { low: 1, high: 2 }, { none: 0, low: 0.5, high: 0.7 }),\n scoreKeywordMatch(text, config.simpleKeywords, \"simpleIndicators\", \"simple\",\n { low: 1, high: 2 }, { none: 0, low: -1.0, high: -1.0 }),\n scoreMultiStep(text),\n scoreQuestionComplexity(prompt),\n\n // 6 new dimensions\n scoreKeywordMatch(text, config.imperativeVerbs, \"imperativeVerbs\", \"imperative\",\n { low: 1, high: 2 }, { none: 0, low: 0.3, high: 0.5 }),\n scoreKeywordMatch(text, config.constraintIndicators, \"constraintCount\", \"constraints\",\n { low: 1, high: 3 }, { none: 0, low: 0.3, high: 0.7 }),\n scoreKeywordMatch(text, config.outputFormatKeywords, \"outputFormat\", \"format\",\n { low: 1, high: 2 }, { none: 0, low: 0.4, high: 0.7 }),\n scoreKeywordMatch(text, config.referenceKeywords, \"referenceComplexity\", \"references\",\n { low: 1, high: 2 }, { none: 0, low: 0.3, high: 0.5 }),\n scoreKeywordMatch(text, config.negationKeywords, \"negationComplexity\", \"negation\",\n { low: 2, high: 3 }, { none: 0, low: 0.3, high: 0.5 }),\n scoreKeywordMatch(text, config.domainSpecificKeywords, \"domainSpecificity\", \"domain-specific\",\n { low: 1, high: 2 }, { none: 0, low: 0.5, high: 0.8 }),\n ];\n\n // Collect signals\n const signals = dimensions\n .filter((d) => d.signal !== null)\n .map((d) => d.signal!);\n\n // Compute weighted score\n const weights = config.dimensionWeights;\n let weightedScore = 0;\n for (const d of dimensions) {\n const w = weights[d.name] ?? 0;\n weightedScore += d.score * w;\n }\n\n // Count reasoning markers for override\n const reasoningMatches = config.reasoningKeywords.filter((kw) =>\n text.includes(kw.toLowerCase()),\n );\n\n // Direct reasoning override: 2+ reasoning markers = high confidence REASONING\n if (reasoningMatches.length >= 2) {\n const confidence = calibrateConfidence(\n Math.max(weightedScore, 0.3), // ensure positive for confidence calc\n config.confidenceSteepness,\n );\n return {\n score: weightedScore,\n tier: \"REASONING\",\n confidence: Math.max(confidence, 0.85),\n signals,\n };\n }\n\n // Map weighted score to tier using boundaries\n const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;\n let tier: Tier;\n let distanceFromBoundary: number;\n\n if (weightedScore < simpleMedium) {\n tier = \"SIMPLE\";\n distanceFromBoundary = simpleMedium - weightedScore;\n } else if (weightedScore < mediumComplex) {\n tier = \"MEDIUM\";\n distanceFromBoundary = Math.min(\n weightedScore - simpleMedium,\n mediumComplex - weightedScore,\n );\n } else if (weightedScore < complexReasoning) {\n tier = \"COMPLEX\";\n distanceFromBoundary = Math.min(\n weightedScore - mediumComplex,\n complexReasoning - weightedScore,\n );\n } else {\n tier = \"REASONING\";\n distanceFromBoundary = weightedScore - complexReasoning;\n }\n\n // Calibrate confidence via sigmoid of distance from nearest boundary\n const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);\n\n // If confidence is below threshold → ambiguous\n if (confidence < config.confidenceThreshold) {\n return { score: weightedScore, tier: null, confidence, signals };\n }\n\n return { score: weightedScore, tier, confidence, signals };\n}\n\n/**\n * Sigmoid confidence calibration.\n * Maps distance from tier boundary to [0.5, 1.0] confidence range.\n */\nfunction calibrateConfidence(distance: number, steepness: number): number {\n return 1 / (1 + Math.exp(-steepness * distance));\n}\n","/**\n * LLM Classifier (Fallback)\n *\n * When the rule-based classifier returns ambiguous (score 1-2),\n * we send a classification request to the cheapest model.\n *\n * Cost per classification: ~$0.00003\n * Latency: ~200-400ms\n * Only triggered for ~20-30% of requests.\n */\n\nimport type { Tier } from \"./types.js\";\n\nconst CLASSIFIER_PROMPT = `You are a query complexity classifier. Classify the user's query into exactly one category.\n\nCategories:\n- SIMPLE: Factual Q&A, definitions, translations, short answers\n- MEDIUM: Summaries, explanations, moderate code generation\n- COMPLEX: Multi-step code, system design, creative writing, analysis\n- REASONING: Mathematical proofs, formal logic, step-by-step problem solving\n\nRespond with ONLY one word: SIMPLE, MEDIUM, COMPLEX, or REASONING.`;\n\n// In-memory cache: hash → { tier, expires }\nconst cache = new Map<string, { tier: Tier; expires: number }>();\n\nexport type LLMClassifierConfig = {\n model: string;\n maxTokens: number;\n temperature: number;\n truncationChars: number;\n cacheTtlMs: number;\n};\n\ntype PayFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Classify a prompt using a cheap LLM.\n * Returns tier and confidence. Defaults to MEDIUM on any failure.\n */\nexport async function classifyByLLM(\n prompt: string,\n config: LLMClassifierConfig,\n payFetch: PayFetch,\n apiBase: string,\n): Promise<{ tier: Tier; confidence: number }> {\n const truncated = prompt.slice(0, config.truncationChars);\n\n // Check cache\n const cacheKey = simpleHash(truncated);\n const cached = cache.get(cacheKey);\n if (cached && cached.expires > Date.now()) {\n return { tier: cached.tier, confidence: 0.75 };\n }\n\n try {\n const response = await payFetch(`${apiBase}/v1/chat/completions`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: config.model,\n messages: [\n { role: \"system\", content: CLASSIFIER_PROMPT },\n { role: \"user\", content: truncated },\n ],\n max_tokens: config.maxTokens,\n temperature: config.temperature,\n stream: false,\n }),\n });\n\n if (!response.ok) {\n return { tier: \"MEDIUM\", confidence: 0.5 };\n }\n\n const data = (await response.json()) as {\n choices?: Array<{ message?: { content?: string } }>;\n };\n\n const content = data.choices?.[0]?.message?.content?.trim().toUpperCase() ?? \"\";\n const tier = parseTier(content);\n\n // Cache result\n cache.set(cacheKey, { tier, expires: Date.now() + config.cacheTtlMs });\n\n // Prune if cache grows too large\n if (cache.size > 1000) {\n pruneCache();\n }\n\n return { tier, confidence: 0.75 };\n } catch {\n // Any error → safe default\n return { tier: \"MEDIUM\", confidence: 0.5 };\n }\n}\n\n/**\n * Parse tier from LLM response. Handles \"SIMPLE\", \"The query is SIMPLE\", etc.\n */\nfunction parseTier(text: string): Tier {\n if (/\\bREASONING\\b/.test(text)) return \"REASONING\";\n if (/\\bCOMPLEX\\b/.test(text)) return \"COMPLEX\";\n if (/\\bMEDIUM\\b/.test(text)) return \"MEDIUM\";\n if (/\\bSIMPLE\\b/.test(text)) return \"SIMPLE\";\n return \"MEDIUM\"; // safe default\n}\n\nfunction simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash |= 0;\n }\n return hash.toString(36);\n}\n\nfunction pruneCache(): void {\n const now = Date.now();\n for (const [key, value] of cache) {\n if (value.expires <= now) {\n cache.delete(key);\n }\n }\n}\n","/**\n * Tier → Model Selection\n *\n * Maps a classification tier to the cheapest capable model.\n * Builds RoutingDecision metadata with cost estimates and savings.\n */\n\nimport type { Tier, TierConfig, RoutingDecision } from \"./types.js\";\n\nexport type ModelPricing = {\n inputPrice: number; // per 1M tokens\n outputPrice: number; // per 1M tokens\n};\n\n/**\n * Select the primary model for a tier and build the RoutingDecision.\n */\nexport function selectModel(\n tier: Tier,\n confidence: number,\n method: \"rules\" | \"llm\",\n reasoning: string,\n tierConfigs: Record<Tier, TierConfig>,\n modelPricing: Map<string, ModelPricing>,\n estimatedInputTokens: number,\n maxOutputTokens: number,\n): RoutingDecision {\n const tierConfig = tierConfigs[tier];\n const model = tierConfig.primary;\n const pricing = modelPricing.get(model);\n\n const inputCost = pricing\n ? (estimatedInputTokens / 1_000_000) * pricing.inputPrice\n : 0;\n const outputCost = pricing\n ? (maxOutputTokens / 1_000_000) * pricing.outputPrice\n : 0;\n const costEstimate = inputCost + outputCost;\n\n // Baseline: what Claude Opus would cost (the premium default)\n const opusPricing = modelPricing.get(\"anthropic/claude-opus-4\");\n const baselineInput = opusPricing\n ? (estimatedInputTokens / 1_000_000) * opusPricing.inputPrice\n : 0;\n const baselineOutput = opusPricing\n ? (maxOutputTokens / 1_000_000) * opusPricing.outputPrice\n : 0;\n const baselineCost = baselineInput + baselineOutput;\n\n const savings =\n baselineCost > 0\n ? Math.max(0, (baselineCost - costEstimate) / baselineCost)\n : 0;\n\n return {\n model,\n tier,\n confidence,\n method,\n reasoning,\n costEstimate,\n baselineCost,\n savings,\n };\n}\n\n/**\n * Get the ordered fallback chain for a tier: [primary, ...fallbacks].\n */\nexport function getFallbackChain(\n tier: Tier,\n tierConfigs: Record<Tier, TierConfig>,\n): string[] {\n const config = tierConfigs[tier];\n return [config.primary, ...config.fallback];\n}\n","/**\n * Default Routing Config\n *\n * All routing parameters as a TypeScript constant.\n * Operators override via openclaw.yaml plugin config.\n *\n * Scoring uses 14 weighted dimensions with sigmoid confidence calibration.\n */\n\nimport type { RoutingConfig } from \"./types.js\";\n\nexport const DEFAULT_ROUTING_CONFIG: RoutingConfig = {\n version: \"2.0\",\n\n classifier: {\n llmModel: \"google/gemini-2.5-flash\",\n llmMaxTokens: 10,\n llmTemperature: 0,\n promptTruncationChars: 500,\n cacheTtlMs: 3_600_000, // 1 hour\n },\n\n scoring: {\n tokenCountThresholds: { simple: 50, complex: 500 },\n codeKeywords: [\n \"function\", \"class\", \"import\", \"def\", \"SELECT\", \"async\", \"await\",\n \"const\", \"let\", \"var\", \"return\", \"```\",\n ],\n reasoningKeywords: [\n \"prove\", \"theorem\", \"derive\", \"step by step\", \"chain of thought\",\n \"formally\", \"mathematical\", \"proof\", \"logically\",\n ],\n simpleKeywords: [\n \"what is\", \"define\", \"translate\", \"hello\", \"yes or no\",\n \"capital of\", \"how old\", \"who is\", \"when was\",\n ],\n technicalKeywords: [\n \"algorithm\", \"optimize\", \"architecture\", \"distributed\",\n \"kubernetes\", \"microservice\", \"database\", \"infrastructure\",\n ],\n creativeKeywords: [\n \"story\", \"poem\", \"compose\", \"brainstorm\", \"creative\",\n \"imagine\", \"write a\",\n ],\n\n // New dimension keyword lists\n imperativeVerbs: [\n \"build\", \"create\", \"implement\", \"design\", \"develop\", \"construct\",\n \"generate\", \"deploy\", \"configure\", \"set up\",\n ],\n constraintIndicators: [\n \"under\", \"at most\", \"at least\", \"within\", \"no more than\",\n \"o(\", \"maximum\", \"minimum\", \"limit\", \"budget\",\n ],\n outputFormatKeywords: [\n \"json\", \"yaml\", \"xml\", \"table\", \"csv\", \"markdown\",\n \"schema\", \"format as\", \"structured\",\n ],\n referenceKeywords: [\n \"above\", \"below\", \"previous\", \"following\", \"the docs\",\n \"the api\", \"the code\", \"earlier\", \"attached\",\n ],\n negationKeywords: [\n \"don't\", \"do not\", \"avoid\", \"never\", \"without\",\n \"except\", \"exclude\", \"no longer\",\n ],\n domainSpecificKeywords: [\n \"quantum\", \"fpga\", \"vlsi\", \"risc-v\", \"asic\", \"photonics\",\n \"genomics\", \"proteomics\", \"topological\", \"homomorphic\",\n \"zero-knowledge\", \"lattice-based\",\n ],\n\n // Dimension weights (sum to 1.0)\n dimensionWeights: {\n tokenCount: 0.08,\n codePresence: 0.15,\n reasoningMarkers: 0.18,\n technicalTerms: 0.10,\n creativeMarkers: 0.05,\n simpleIndicators: 0.12,\n multiStepPatterns: 0.12,\n questionComplexity: 0.05,\n imperativeVerbs: 0.03,\n constraintCount: 0.04,\n outputFormat: 0.03,\n referenceComplexity: 0.02,\n negationComplexity: 0.01,\n domainSpecificity: 0.02,\n },\n\n // Tier boundaries on weighted score axis\n tierBoundaries: {\n simpleMedium: 0.0,\n mediumComplex: 0.15,\n complexReasoning: 0.25,\n },\n\n // Sigmoid steepness for confidence calibration\n confidenceSteepness: 12,\n // Below this confidence → ambiguous (null tier)\n confidenceThreshold: 0.70,\n },\n\n tiers: {\n SIMPLE: {\n primary: \"google/gemini-2.5-flash\",\n fallback: [\"deepseek/deepseek-chat\", \"openai/gpt-4o-mini\"],\n },\n MEDIUM: {\n primary: \"deepseek/deepseek-chat\",\n fallback: [\"google/gemini-2.5-flash\", \"openai/gpt-4o-mini\"],\n },\n COMPLEX: {\n primary: \"anthropic/claude-opus-4\",\n fallback: [\"anthropic/claude-sonnet-4\", \"openai/gpt-4o\"],\n },\n REASONING: {\n primary: \"openai/o3\",\n fallback: [\"google/gemini-2.5-pro\", \"anthropic/claude-sonnet-4\"],\n },\n },\n\n overrides: {\n maxTokensForceComplex: 100_000,\n structuredOutputMinTier: \"MEDIUM\",\n },\n};\n","/**\n * Smart Router Entry Point\n *\n * Classifies requests and routes to the cheapest capable model.\n * Uses hybrid approach: rules first (< 1ms), LLM fallback for ambiguous cases.\n */\n\nimport type { Tier, RoutingDecision, RoutingConfig } from \"./types.js\";\nimport { classifyByRules } from \"./rules.js\";\nimport { classifyByLLM } from \"./llm-classifier.js\";\nimport { selectModel, getFallbackChain, type ModelPricing } from \"./selector.js\";\n\nexport type RouterOptions = {\n config: RoutingConfig;\n modelPricing: Map<string, ModelPricing>;\n payFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n apiBase: string;\n};\n\n/**\n * Route a request to the cheapest capable model.\n *\n * 1. Check overrides (large context, structured output)\n * 2. Run rule-based classifier\n * 3. If ambiguous, run LLM classifier\n * 4. Select model for tier\n * 5. Return RoutingDecision with metadata\n */\nexport async function route(\n prompt: string,\n systemPrompt: string | undefined,\n maxOutputTokens: number,\n options: RouterOptions,\n): Promise<RoutingDecision> {\n const { config, modelPricing, payFetch, apiBase } = options;\n\n // Estimate input tokens (~4 chars per token)\n const fullText = `${systemPrompt ?? \"\"} ${prompt}`;\n const estimatedTokens = Math.ceil(fullText.length / 4);\n\n // --- Override: large context → force COMPLEX ---\n if (estimatedTokens > config.overrides.maxTokensForceComplex) {\n return selectModel(\n \"COMPLEX\",\n 0.95,\n \"rules\",\n `Input exceeds ${config.overrides.maxTokensForceComplex} tokens`,\n config.tiers,\n modelPricing,\n estimatedTokens,\n maxOutputTokens,\n );\n }\n\n // Structured output detection\n const hasStructuredOutput = systemPrompt\n ? /json|structured|schema/i.test(systemPrompt)\n : false;\n\n // --- Rule-based classification ---\n const ruleResult = classifyByRules(\n prompt,\n systemPrompt,\n estimatedTokens,\n config.scoring,\n );\n\n let tier: Tier;\n let confidence: number;\n let method: \"rules\" | \"llm\" = \"rules\";\n let reasoning = `score=${ruleResult.score} | ${ruleResult.signals.join(\", \")}`;\n\n if (ruleResult.tier !== null) {\n tier = ruleResult.tier;\n confidence = ruleResult.confidence;\n } else {\n // Ambiguous — LLM classifier fallback\n const llmResult = await classifyByLLM(\n prompt,\n {\n model: config.classifier.llmModel,\n maxTokens: config.classifier.llmMaxTokens,\n temperature: config.classifier.llmTemperature,\n truncationChars: config.classifier.promptTruncationChars,\n cacheTtlMs: config.classifier.cacheTtlMs,\n },\n payFetch,\n apiBase,\n );\n\n tier = llmResult.tier;\n confidence = llmResult.confidence;\n method = \"llm\";\n reasoning += ` | ambiguous -> LLM: ${tier}`;\n }\n\n // Apply structured output minimum tier\n if (hasStructuredOutput) {\n const tierRank: Record<Tier, number> = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };\n const minTier = config.overrides.structuredOutputMinTier;\n if (tierRank[tier] < tierRank[minTier]) {\n reasoning += ` | upgraded to ${minTier} (structured output)`;\n tier = minTier;\n }\n }\n\n return selectModel(\n tier,\n confidence,\n method,\n reasoning,\n config.tiers,\n modelPricing,\n estimatedTokens,\n maxOutputTokens,\n );\n}\n\nexport { getFallbackChain } from \"./selector.js\";\nexport { DEFAULT_ROUTING_CONFIG } from \"./config.js\";\nexport type { RoutingDecision, Tier, RoutingConfig } from \"./types.js\";\nexport type { ModelPricing } from \"./selector.js\";\n","/**\n * Usage Logger\n *\n * Logs every LLM request as a JSON line to a daily log file.\n * Files: ~/.openclaw/blockrun/logs/usage-YYYY-MM-DD.jsonl\n *\n * MVP: append-only JSON lines. No rotation, no cleanup.\n * Logging never breaks the request flow — all errors are swallowed.\n */\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport type UsageEntry = {\n timestamp: string;\n model: string;\n cost: number;\n latencyMs: number;\n};\n\nconst LOG_DIR = join(homedir(), \".openclaw\", \"blockrun\", \"logs\");\nlet dirReady = false;\n\nasync function ensureDir(): Promise<void> {\n if (dirReady) return;\n await mkdir(LOG_DIR, { recursive: true });\n dirReady = true;\n}\n\n/**\n * Log a usage entry as a JSON line.\n */\nexport async function logUsage(entry: UsageEntry): Promise<void> {\n try {\n await ensureDir();\n const date = entry.timestamp.slice(0, 10); // YYYY-MM-DD\n const file = join(LOG_DIR, `usage-${date}.jsonl`);\n await appendFile(file, JSON.stringify(entry) + \"\\n\");\n } catch {\n // Never break the request flow\n }\n}\n","/**\n * @blockrun/openclaw-provider\n *\n * OpenClaw plugin that adds BlockRun as an LLM provider with 30+ AI models.\n * Payments are handled automatically via x402 USDC micropayments on Base.\n * Smart routing picks the cheapest capable model for each request.\n *\n * Usage:\n * # Install the plugin\n * openclaw plugin install @blockrun/openclaw-provider\n *\n * # Set wallet key\n * export BLOCKRUN_WALLET_KEY=0x...\n *\n * # Or configure via wizard\n * openclaw provider add blockrun\n *\n * # Use smart routing (auto-picks cheapest model)\n * openclaw config set model blockrun/auto\n *\n * # Or use any specific BlockRun model\n * openclaw config set model openai/gpt-5.2\n */\n\nimport type { OpenClawPluginDefinition, OpenClawPluginApi } from \"./types.js\";\nimport { blockrunProvider, setActiveProxy } from \"./provider.js\";\nimport { startProxy } from \"./proxy.js\";\nimport { resolveOrGenerateWalletKey } from \"./auth.js\";\nimport type { RoutingConfig } from \"./router/index.js\";\n\nconst plugin: OpenClawPluginDefinition = {\n id: \"claw-router\",\n name: \"ClawRouter\",\n description: \"Smart LLM router — 30+ models, x402 micropayments, 63% cost savings\",\n version: \"0.1.0\",\n\n register(api: OpenClawPluginApi) {\n // Register BlockRun as a provider\n api.registerProvider(blockrunProvider);\n\n api.logger.info(\"BlockRun provider registered (30+ models via x402)\");\n },\n\n async activate(api: OpenClawPluginApi) {\n // Resolve wallet key: saved file → env var → auto-generate\n const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();\n\n // Log wallet source\n if (source === \"generated\") {\n api.logger.info(`Generated new wallet: ${address}`);\n api.logger.info(`Fund with USDC on Base to start using ClawRouter.`);\n } else if (source === \"saved\") {\n api.logger.info(`Using saved wallet: ${address}`);\n } else {\n api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);\n }\n\n // Resolve routing config overrides from plugin config\n const routingConfig = api.pluginConfig?.routing as Partial<RoutingConfig> | undefined;\n\n // Start the local x402 proxy\n try {\n const proxy = await startProxy({\n walletKey,\n routingConfig,\n onReady: (port) => {\n api.logger.info(`BlockRun x402 proxy listening on port ${port}`);\n },\n onError: (error) => {\n api.logger.error(`BlockRun proxy error: ${error.message}`);\n },\n onRouted: (decision) => {\n const cost = decision.costEstimate.toFixed(4);\n const saved = (decision.savings * 100).toFixed(0);\n api.logger.info(`${decision.model} $${cost} (saved ${saved}%)`);\n },\n });\n\n setActiveProxy(proxy);\n api.logger.info(`BlockRun provider active — ${proxy.baseUrl}/v1 (smart routing enabled)`);\n } catch (err) {\n api.logger.error(\n `Failed to start BlockRun proxy: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n },\n};\n\n\nexport default plugin;\n\n// Re-export for programmatic use\nexport { startProxy } from \"./proxy.js\";\nexport { blockrunProvider } from \"./provider.js\";\nexport { OPENCLAW_MODELS, BLOCKRUN_MODELS, buildProviderModels } from \"./models.js\";\nexport { route, DEFAULT_ROUTING_CONFIG } from \"./router/index.js\";\nexport type { RoutingDecision, RoutingConfig, Tier } from \"./router/index.js\";\nexport { logUsage } from \"./logger.js\";\nexport type { UsageEntry } from \"./logger.js\";\n"],"mappings":"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","names":["privateKeyToAccount","privateKeyToAccount","confidence","mkdir","join","homedir","privateKeyToAccount"]}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/clawrouter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Smart LLM router — save 63% on inference costs. 30+ models, one wallet, x402 micropayments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"openclaw": {
|
|
9
|
-
"extensions": [
|
|
9
|
+
"extensions": [
|
|
10
|
+
"./dist/index.js"
|
|
11
|
+
]
|
|
10
12
|
},
|
|
11
13
|
"exports": {
|
|
12
14
|
".": {
|