@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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # ClawRouter
4
4
 
5
- **Save 63% on LLM costs. Automatically.**
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 94%
23
- "Summarize this article" → DeepSeek Chat $0.42/M saved 96%
24
- "Build a React component" → Claude Sonnet $15.00/M best quality
25
- "Prove this theorem" → o3 $8.00/M saved 20%
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 GPT-4o |
66
+ | Score | Tier | Primary Model | Output $/M | vs Opus |
67
67
  |-------|------|--------------|-----------|-----------|
68
- | ≤ 0 | SIMPLE | gemini-2.5-flash | $0.60 | **94% cheaper** |
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 | **96% cheaper** |
71
- | 5-6 | COMPLEX | claude-sonnet-4 | $15.00 | higher quality |
72
- | 7+ | REASONING | o3 | $8.00 | **20% cheaper** |
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% | $15.00 |
84
+ | COMPLEX | 20% | $75.00 |
85
85
  | REASONING | 10% | $8.00 |
86
- | **Weighted avg** | | **$3.67/M — 63% savings vs GPT-4o** |
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.0095 | Saved: 95.8%
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.20,
266
+ // savings: 0.893,
273
267
  // costEstimate: 0.032776,
274
- // baselineCost: 0.040970,
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, 94% savings):
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, 20% savings):
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=94.0%
341
- ✓ Greeting → google/gemini-2.5-flash (SIMPLE, rules) saved=94.0%
342
- ✓ Math proof → openai/o3 (REASONING, rules) saved=20.0%
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.040970
347
- ✓ Savings in range [0,1]: 0.9400
348
- ✓ Cost ($0.002458) <= Baseline ($0.040970)
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=94.0%
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.041** — that's **94% savings** on every simple query.
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 classifyByRules(prompt, systemPrompt, estimatedTokens, config, ambiguousZone) {
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
- let score = 0;
313
- const signals = [];
314
- if (estimatedTokens < config.tokenCountThresholds.simple) {
315
- score -= 2;
316
- signals.push(`short (${estimatedTokens} tokens)`);
317
- } else if (estimatedTokens > config.tokenCountThresholds.complex) {
318
- score += 2;
319
- signals.push(`long (${estimatedTokens} tokens)`);
320
- }
321
- const codeMatches = config.codeKeywords.filter((kw) => text.includes(kw.toLowerCase()));
322
- if (codeMatches.length >= 2) {
323
- score += 2;
324
- signals.push(`code (${codeMatches.slice(0, 3).join(", ")})`);
325
- } else if (codeMatches.length === 1) {
326
- score += 1;
327
- signals.push(`possible code (${codeMatches[0]})`);
328
- }
329
- const reasoningMatches = config.reasoningKeywords.filter((kw) => text.includes(kw.toLowerCase()));
330
- if (reasoningMatches.length > 0) {
331
- score += 3;
332
- signals.push(`reasoning (${reasoningMatches.slice(0, 3).join(", ")})`);
333
- }
334
- const techMatches = config.technicalKeywords.filter((kw) => text.includes(kw.toLowerCase()));
335
- if (techMatches.length >= 2) {
336
- score += Math.floor(techMatches.length / 2);
337
- signals.push(`technical (${techMatches.slice(0, 3).join(", ")})`);
338
- }
339
- const creativeMatches = config.creativeKeywords.filter((kw) => text.includes(kw.toLowerCase()));
340
- if (creativeMatches.length > 0) {
341
- score += 1;
342
- signals.push(`creative (${creativeMatches[0]})`);
343
- }
344
- const simpleMatches = config.simpleKeywords.filter((kw) => text.includes(kw.toLowerCase()));
345
- if (simpleMatches.length > 0) {
346
- score -= 2;
347
- signals.push(`simple (${simpleMatches.slice(0, 2).join(", ")})`);
348
- }
349
- const multiStepPatterns = [/first.*then/i, /step \d/i, /\d\.\s/];
350
- const multiStepHits = multiStepPatterns.filter((p) => p.test(text));
351
- if (multiStepHits.length > 0) {
352
- score += 1;
353
- signals.push("multi-step");
354
- }
355
- const questionCount = (prompt.match(/\?/g) || []).length;
356
- if (questionCount > 3) {
357
- score += 1;
358
- signals.push(`${questionCount} questions`);
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
- let tier;
361
- let confidence;
448
+ const reasoningMatches = config.reasoningKeywords.filter(
449
+ (kw) => text.includes(kw.toLowerCase())
450
+ );
362
451
  if (reasoningMatches.length >= 2) {
363
- tier = "REASONING";
364
- confidence = 0.9;
365
- } else if (score <= 0) {
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
- confidence = Math.min(0.95, 0.85 + Math.abs(score) * 0.02);
368
- } else if (score >= ambiguousZone[0] && score <= ambiguousZone[1]) {
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
- confidence = 0.75 + (score - 3) * 0.05;
374
- } else if (score >= 5 && score <= 6) {
472
+ distanceFromBoundary = Math.min(
473
+ weightedScore - simpleMedium,
474
+ mediumComplex - weightedScore
475
+ );
476
+ } else if (weightedScore < complexReasoning) {
375
477
  tier = "COMPLEX";
376
- confidence = 0.7 + (score - 5) * 0.075;
377
- } else if (score >= 7) {
378
- tier = "REASONING";
379
- confidence = 0.7 + Math.min(0.1, (score - 7) * 0.05);
478
+ distanceFromBoundary = Math.min(
479
+ weightedScore - mediumComplex,
480
+ complexReasoning - weightedScore
481
+ );
380
482
  } else {
381
- tier = null;
382
- confidence = 0.5;
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 gpt4oPricing = modelPricing.get("openai/gpt-4o");
469
- const baselineInput = gpt4oPricing ? estimatedInputTokens / 1e6 * gpt4oPricing.inputPrice : 0;
470
- const baselineOutput = gpt4oPricing ? maxOutputTokens / 1e6 * gpt4oPricing.outputPrice : 0;
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: "1.0",
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-sonnet-4",
566
- fallback: ["openai/gpt-4o", "google/gemini-2.5-pro"]
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"]}
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "claw-router",
2
+ "id": "clawrouter",
3
3
  "name": "ClawRouter",
4
4
  "description": "Smart LLM router — 30+ models, x402 micropayments, 63% cost savings",
5
5
  "configSchema": {
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@blockrun/clawrouter",
3
- "version": "0.1.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": ["./dist/index.js"]
9
+ "extensions": [
10
+ "./dist/index.js"
11
+ ]
10
12
  },
11
13
  "exports": {
12
14
  ".": {