@blockrun/clawrouter 0.11.13 → 0.11.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,55 +8,6 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
- // src/wallet.ts
12
- var wallet_exports = {};
13
- __export(wallet_exports, {
14
- deriveAllKeys: () => deriveAllKeys,
15
- deriveEvmKey: () => deriveEvmKey,
16
- deriveSolanaKeyBytes: () => deriveSolanaKeyBytes,
17
- generateWalletMnemonic: () => generateWalletMnemonic,
18
- isValidMnemonic: () => isValidMnemonic
19
- });
20
- import { HDKey } from "@scure/bip32";
21
- import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
22
- import { wordlist as english } from "@scure/bip39/wordlists/english";
23
- import { privateKeyToAccount } from "viem/accounts";
24
- function generateWalletMnemonic() {
25
- return generateMnemonic(english, 256);
26
- }
27
- function isValidMnemonic(mnemonic) {
28
- return validateMnemonic(mnemonic, english);
29
- }
30
- function deriveEvmKey(mnemonic) {
31
- const seed = mnemonicToSeedSync(mnemonic);
32
- const hdKey = HDKey.fromMasterSeed(seed);
33
- const derived = hdKey.derive(ETH_DERIVATION_PATH);
34
- if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
35
- const hex = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
36
- const account = privateKeyToAccount(hex);
37
- return { privateKey: hex, address: account.address };
38
- }
39
- function deriveSolanaKeyBytes(mnemonic) {
40
- const seed = mnemonicToSeedSync(mnemonic);
41
- const hdKey = HDKey.fromMasterSeed(seed);
42
- const derived = hdKey.derive(SOLANA_DERIVATION_PATH);
43
- if (!derived.privateKey) throw new Error("Failed to derive Solana private key");
44
- return new Uint8Array(derived.privateKey);
45
- }
46
- function deriveAllKeys(mnemonic) {
47
- const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);
48
- const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);
49
- return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };
50
- }
51
- var ETH_DERIVATION_PATH, SOLANA_DERIVATION_PATH;
52
- var init_wallet = __esm({
53
- "src/wallet.ts"() {
54
- "use strict";
55
- ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0";
56
- SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
57
- }
58
- });
59
-
60
11
  // src/solana-balance.ts
61
12
  var solana_balance_exports = {};
62
13
  __export(solana_balance_exports, {
@@ -104,6 +55,28 @@ var init_solana_balance = __esm({
104
55
  this.invalidate();
105
56
  return this.checkBalance();
106
57
  }
58
+ /**
59
+ * Check if balance is sufficient for an estimated cost.
60
+ */
61
+ async checkSufficient(estimatedCostMicros) {
62
+ const info = await this.checkBalance();
63
+ if (info.balance >= estimatedCostMicros) {
64
+ return { sufficient: true, info };
65
+ }
66
+ const shortfall = estimatedCostMicros - info.balance;
67
+ return {
68
+ sufficient: false,
69
+ info,
70
+ shortfall: this.formatUSDC(shortfall)
71
+ };
72
+ }
73
+ /**
74
+ * Format USDC amount (in micros) as "$X.XX".
75
+ */
76
+ formatUSDC(amountMicros) {
77
+ const dollars = Number(amountMicros) / 1e6;
78
+ return `$${dollars.toFixed(2)}`;
79
+ }
107
80
  getWalletAddress() {
108
81
  return this.walletAddress;
109
82
  }
@@ -141,125 +114,52 @@ var init_solana_balance = __esm({
141
114
  }
142
115
  });
143
116
 
144
- // src/partners/registry.ts
145
- function getPartnerService(id) {
146
- return PARTNER_SERVICES.find((s) => s.id === id);
147
- }
148
- var PARTNER_SERVICES;
149
- var init_registry = __esm({
150
- "src/partners/registry.ts"() {
151
- "use strict";
152
- PARTNER_SERVICES = [
153
- {
154
- id: "x_users_lookup",
155
- name: "Twitter/X User Lookup",
156
- partner: "AttentionVC",
157
- description: "ALWAYS use this tool to look up real-time Twitter/X user profiles. Call this when the user asks about any Twitter/X account, username, handle, follower count, verification status, bio, or profile. Do NOT answer Twitter/X user questions from memory \u2014 always fetch live data with this tool. Returns: follower count, verification badge, bio, location, join date. Accepts up to 100 usernames per request (without @ prefix).",
158
- proxyPath: "/x/users/lookup",
159
- method: "POST",
160
- params: [
161
- {
162
- name: "usernames",
163
- type: "string[]",
164
- description: 'Array of Twitter/X usernames to look up (without @ prefix). Example: ["elonmusk", "naval"]',
165
- required: true
166
- }
167
- ],
168
- pricing: {
169
- perUnit: "$0.001",
170
- unit: "user",
171
- minimum: "$0.01 (10 users)",
172
- maximum: "$0.10 (100 users)"
173
- },
174
- example: {
175
- input: { usernames: ["elonmusk", "naval", "balaboris"] },
176
- description: "Look up 3 Twitter/X user profiles"
177
- }
178
- }
179
- ];
180
- }
117
+ // src/wallet.ts
118
+ var wallet_exports = {};
119
+ __export(wallet_exports, {
120
+ deriveAllKeys: () => deriveAllKeys,
121
+ deriveEvmKey: () => deriveEvmKey,
122
+ deriveSolanaKeyBytes: () => deriveSolanaKeyBytes,
123
+ generateWalletMnemonic: () => generateWalletMnemonic,
124
+ isValidMnemonic: () => isValidMnemonic
181
125
  });
182
-
183
- // src/partners/tools.ts
184
- function buildTool(service, proxyBaseUrl) {
185
- const properties = {};
186
- const required = [];
187
- for (const param of service.params) {
188
- const prop = {
189
- description: param.description
190
- };
191
- if (param.type === "string[]") {
192
- prop.type = "array";
193
- prop.items = { type: "string" };
194
- } else {
195
- prop.type = param.type;
196
- }
197
- properties[param.name] = prop;
198
- if (param.required) {
199
- required.push(param.name);
200
- }
201
- }
202
- return {
203
- name: `blockrun_${service.id}`,
204
- description: [
205
- service.description,
206
- "",
207
- `Partner: ${service.partner}`,
208
- `Pricing: ${service.pricing.perUnit} per ${service.pricing.unit} (min: ${service.pricing.minimum}, max: ${service.pricing.maximum})`
209
- ].join("\n"),
210
- parameters: {
211
- type: "object",
212
- properties,
213
- required
214
- },
215
- execute: async (_toolCallId, params) => {
216
- const url = `${proxyBaseUrl}/v1${service.proxyPath}`;
217
- const response = await fetch(url, {
218
- method: service.method,
219
- headers: { "Content-Type": "application/json" },
220
- body: JSON.stringify(params)
221
- });
222
- if (!response.ok) {
223
- const errText = await response.text().catch(() => "");
224
- throw new Error(
225
- `Partner API error (${response.status}): ${errText || response.statusText}`
226
- );
227
- }
228
- const data = await response.json();
229
- return {
230
- content: [
231
- {
232
- type: "text",
233
- text: JSON.stringify(data, null, 2)
234
- }
235
- ],
236
- details: data
237
- };
238
- }
239
- };
126
+ import { HDKey } from "@scure/bip32";
127
+ import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
128
+ import { wordlist as english } from "@scure/bip39/wordlists/english";
129
+ import { privateKeyToAccount } from "viem/accounts";
130
+ function generateWalletMnemonic() {
131
+ return generateMnemonic(english, 256);
240
132
  }
241
- function buildPartnerTools(proxyBaseUrl) {
242
- return PARTNER_SERVICES.map((service) => buildTool(service, proxyBaseUrl));
133
+ function isValidMnemonic(mnemonic) {
134
+ return validateMnemonic(mnemonic, english);
243
135
  }
244
- var init_tools = __esm({
245
- "src/partners/tools.ts"() {
246
- "use strict";
247
- init_registry();
248
- }
249
- });
250
-
251
- // src/partners/index.ts
252
- var partners_exports = {};
253
- __export(partners_exports, {
254
- PARTNER_SERVICES: () => PARTNER_SERVICES,
255
- buildPartnerTools: () => buildPartnerTools,
256
- getPartnerService: () => getPartnerService
257
- });
258
- var init_partners = __esm({
259
- "src/partners/index.ts"() {
136
+ function deriveEvmKey(mnemonic) {
137
+ const seed = mnemonicToSeedSync(mnemonic);
138
+ const hdKey = HDKey.fromMasterSeed(seed);
139
+ const derived = hdKey.derive(ETH_DERIVATION_PATH);
140
+ if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
141
+ const hex = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
142
+ const account = privateKeyToAccount(hex);
143
+ return { privateKey: hex, address: account.address };
144
+ }
145
+ function deriveSolanaKeyBytes(mnemonic) {
146
+ const seed = mnemonicToSeedSync(mnemonic);
147
+ const hdKey = HDKey.fromMasterSeed(seed);
148
+ const derived = hdKey.derive(SOLANA_DERIVATION_PATH);
149
+ if (!derived.privateKey) throw new Error("Failed to derive Solana private key");
150
+ return new Uint8Array(derived.privateKey);
151
+ }
152
+ function deriveAllKeys(mnemonic) {
153
+ const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);
154
+ const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);
155
+ return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };
156
+ }
157
+ var ETH_DERIVATION_PATH, SOLANA_DERIVATION_PATH;
158
+ var init_wallet = __esm({
159
+ "src/wallet.ts"() {
260
160
  "use strict";
261
- init_registry();
262
- init_tools();
161
+ ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0";
162
+ SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
263
163
  }
264
164
  });
265
165
 
@@ -307,6 +207,8 @@ var MODEL_ALIASES = {
307
207
  // Google
308
208
  gemini: "google/gemini-2.5-pro",
309
209
  flash: "google/gemini-2.5-flash",
210
+ "gemini-3.1-pro-preview": "google/gemini-3.1-pro",
211
+ "google/gemini-3.1-pro-preview": "google/gemini-3.1-pro",
310
212
  // xAI
311
213
  grok: "xai/grok-3",
312
214
  "grok-fast": "xai/grok-4-fast-reasoning",
@@ -380,7 +282,8 @@ var BLOCKRUN_MODELS = [
380
282
  maxOutput: 128e3,
381
283
  reasoning: true,
382
284
  vision: true,
383
- agentic: true
285
+ agentic: true,
286
+ toolCalling: true
384
287
  },
385
288
  {
386
289
  id: "openai/gpt-5-mini",
@@ -389,7 +292,8 @@ var BLOCKRUN_MODELS = [
389
292
  inputPrice: 0.25,
390
293
  outputPrice: 2,
391
294
  contextWindow: 2e5,
392
- maxOutput: 65536
295
+ maxOutput: 65536,
296
+ toolCalling: true
393
297
  },
394
298
  {
395
299
  id: "openai/gpt-5-nano",
@@ -398,7 +302,8 @@ var BLOCKRUN_MODELS = [
398
302
  inputPrice: 0.05,
399
303
  outputPrice: 0.4,
400
304
  contextWindow: 128e3,
401
- maxOutput: 32768
305
+ maxOutput: 32768,
306
+ toolCalling: true
402
307
  },
403
308
  {
404
309
  id: "openai/gpt-5.2-pro",
@@ -408,7 +313,8 @@ var BLOCKRUN_MODELS = [
408
313
  outputPrice: 168,
409
314
  contextWindow: 4e5,
410
315
  maxOutput: 128e3,
411
- reasoning: true
316
+ reasoning: true,
317
+ toolCalling: true
412
318
  },
413
319
  // OpenAI Codex Family
414
320
  {
@@ -419,7 +325,8 @@ var BLOCKRUN_MODELS = [
419
325
  outputPrice: 14,
420
326
  contextWindow: 128e3,
421
327
  maxOutput: 32e3,
422
- agentic: true
328
+ agentic: true,
329
+ toolCalling: true
423
330
  },
424
331
  // OpenAI GPT-4 Family
425
332
  {
@@ -430,7 +337,8 @@ var BLOCKRUN_MODELS = [
430
337
  outputPrice: 8,
431
338
  contextWindow: 128e3,
432
339
  maxOutput: 16384,
433
- vision: true
340
+ vision: true,
341
+ toolCalling: true
434
342
  },
435
343
  {
436
344
  id: "openai/gpt-4.1-mini",
@@ -439,7 +347,8 @@ var BLOCKRUN_MODELS = [
439
347
  inputPrice: 0.4,
440
348
  outputPrice: 1.6,
441
349
  contextWindow: 128e3,
442
- maxOutput: 16384
350
+ maxOutput: 16384,
351
+ toolCalling: true
443
352
  },
444
353
  {
445
354
  id: "openai/gpt-4.1-nano",
@@ -448,7 +357,8 @@ var BLOCKRUN_MODELS = [
448
357
  inputPrice: 0.1,
449
358
  outputPrice: 0.4,
450
359
  contextWindow: 128e3,
451
- maxOutput: 16384
360
+ maxOutput: 16384,
361
+ toolCalling: true
452
362
  },
453
363
  {
454
364
  id: "openai/gpt-4o",
@@ -459,7 +369,8 @@ var BLOCKRUN_MODELS = [
459
369
  contextWindow: 128e3,
460
370
  maxOutput: 16384,
461
371
  vision: true,
462
- agentic: true
372
+ agentic: true,
373
+ toolCalling: true
463
374
  },
464
375
  {
465
376
  id: "openai/gpt-4o-mini",
@@ -468,7 +379,8 @@ var BLOCKRUN_MODELS = [
468
379
  inputPrice: 0.15,
469
380
  outputPrice: 0.6,
470
381
  contextWindow: 128e3,
471
- maxOutput: 16384
382
+ maxOutput: 16384,
383
+ toolCalling: true
472
384
  },
473
385
  // OpenAI O-series (Reasoning)
474
386
  {
@@ -479,7 +391,8 @@ var BLOCKRUN_MODELS = [
479
391
  outputPrice: 60,
480
392
  contextWindow: 2e5,
481
393
  maxOutput: 1e5,
482
- reasoning: true
394
+ reasoning: true,
395
+ toolCalling: true
483
396
  },
484
397
  {
485
398
  id: "openai/o1-mini",
@@ -489,7 +402,8 @@ var BLOCKRUN_MODELS = [
489
402
  outputPrice: 4.4,
490
403
  contextWindow: 128e3,
491
404
  maxOutput: 65536,
492
- reasoning: true
405
+ reasoning: true,
406
+ toolCalling: true
493
407
  },
494
408
  {
495
409
  id: "openai/o3",
@@ -499,7 +413,8 @@ var BLOCKRUN_MODELS = [
499
413
  outputPrice: 8,
500
414
  contextWindow: 2e5,
501
415
  maxOutput: 1e5,
502
- reasoning: true
416
+ reasoning: true,
417
+ toolCalling: true
503
418
  },
504
419
  {
505
420
  id: "openai/o3-mini",
@@ -509,7 +424,8 @@ var BLOCKRUN_MODELS = [
509
424
  outputPrice: 4.4,
510
425
  contextWindow: 128e3,
511
426
  maxOutput: 65536,
512
- reasoning: true
427
+ reasoning: true,
428
+ toolCalling: true
513
429
  },
514
430
  {
515
431
  id: "openai/o4-mini",
@@ -519,7 +435,8 @@ var BLOCKRUN_MODELS = [
519
435
  outputPrice: 4.4,
520
436
  contextWindow: 128e3,
521
437
  maxOutput: 65536,
522
- reasoning: true
438
+ reasoning: true,
439
+ toolCalling: true
523
440
  },
524
441
  // Anthropic - all Claude models excel at agentic workflows
525
442
  // Use newest versions (4.6) with full provider prefix
@@ -531,7 +448,9 @@ var BLOCKRUN_MODELS = [
531
448
  outputPrice: 5,
532
449
  contextWindow: 2e5,
533
450
  maxOutput: 8192,
534
- agentic: true
451
+ vision: true,
452
+ agentic: true,
453
+ toolCalling: true
535
454
  },
536
455
  {
537
456
  id: "anthropic/claude-sonnet-4.6",
@@ -542,7 +461,9 @@ var BLOCKRUN_MODELS = [
542
461
  contextWindow: 2e5,
543
462
  maxOutput: 64e3,
544
463
  reasoning: true,
545
- agentic: true
464
+ vision: true,
465
+ agentic: true,
466
+ toolCalling: true
546
467
  },
547
468
  {
548
469
  id: "anthropic/claude-opus-4.6",
@@ -553,19 +474,22 @@ var BLOCKRUN_MODELS = [
553
474
  contextWindow: 2e5,
554
475
  maxOutput: 32e3,
555
476
  reasoning: true,
556
- agentic: true
477
+ vision: true,
478
+ agentic: true,
479
+ toolCalling: true
557
480
  },
558
481
  // Google
559
482
  {
560
- id: "google/gemini-3.1-pro-preview",
561
- name: "Gemini 3.1 Pro Preview",
483
+ id: "google/gemini-3.1-pro",
484
+ name: "Gemini 3.1 Pro",
562
485
  version: "3.1",
563
486
  inputPrice: 2,
564
487
  outputPrice: 12,
565
488
  contextWindow: 105e4,
566
489
  maxOutput: 65536,
567
490
  reasoning: true,
568
- vision: true
491
+ vision: true,
492
+ toolCalling: true
569
493
  },
570
494
  {
571
495
  id: "google/gemini-3-pro-preview",
@@ -576,7 +500,8 @@ var BLOCKRUN_MODELS = [
576
500
  contextWindow: 105e4,
577
501
  maxOutput: 65536,
578
502
  reasoning: true,
579
- vision: true
503
+ vision: true,
504
+ toolCalling: true
580
505
  },
581
506
  {
582
507
  id: "google/gemini-3-flash-preview",
@@ -586,7 +511,8 @@ var BLOCKRUN_MODELS = [
586
511
  outputPrice: 3,
587
512
  contextWindow: 1e6,
588
513
  maxOutput: 65536,
589
- vision: true
514
+ vision: true,
515
+ toolCalling: true
590
516
  },
591
517
  {
592
518
  id: "google/gemini-2.5-pro",
@@ -597,7 +523,8 @@ var BLOCKRUN_MODELS = [
597
523
  contextWindow: 105e4,
598
524
  maxOutput: 65536,
599
525
  reasoning: true,
600
- vision: true
526
+ vision: true,
527
+ toolCalling: true
601
528
  },
602
529
  {
603
530
  id: "google/gemini-2.5-flash",
@@ -606,7 +533,9 @@ var BLOCKRUN_MODELS = [
606
533
  inputPrice: 0.3,
607
534
  outputPrice: 2.5,
608
535
  contextWindow: 1e6,
609
- maxOutput: 65536
536
+ maxOutput: 65536,
537
+ vision: true,
538
+ toolCalling: true
610
539
  },
611
540
  {
612
541
  id: "google/gemini-2.5-flash-lite",
@@ -615,7 +544,8 @@ var BLOCKRUN_MODELS = [
615
544
  inputPrice: 0.1,
616
545
  outputPrice: 0.4,
617
546
  contextWindow: 1e6,
618
- maxOutput: 65536
547
+ maxOutput: 65536,
548
+ toolCalling: true
619
549
  },
620
550
  // DeepSeek
621
551
  {
@@ -625,7 +555,8 @@ var BLOCKRUN_MODELS = [
625
555
  inputPrice: 0.28,
626
556
  outputPrice: 0.42,
627
557
  contextWindow: 128e3,
628
- maxOutput: 8192
558
+ maxOutput: 8192,
559
+ toolCalling: true
629
560
  },
630
561
  {
631
562
  id: "deepseek/deepseek-reasoner",
@@ -635,7 +566,8 @@ var BLOCKRUN_MODELS = [
635
566
  outputPrice: 0.42,
636
567
  contextWindow: 128e3,
637
568
  maxOutput: 8192,
638
- reasoning: true
569
+ reasoning: true,
570
+ toolCalling: true
639
571
  },
640
572
  // Moonshot / Kimi - optimized for agentic workflows
641
573
  {
@@ -648,7 +580,8 @@ var BLOCKRUN_MODELS = [
648
580
  maxOutput: 8192,
649
581
  reasoning: true,
650
582
  vision: true,
651
- agentic: true
583
+ agentic: true,
584
+ toolCalling: true
652
585
  },
653
586
  // xAI / Grok
654
587
  {
@@ -659,7 +592,8 @@ var BLOCKRUN_MODELS = [
659
592
  outputPrice: 15,
660
593
  contextWindow: 131072,
661
594
  maxOutput: 16384,
662
- reasoning: true
595
+ reasoning: true,
596
+ toolCalling: true
663
597
  },
664
598
  // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead
665
599
  {
@@ -669,7 +603,8 @@ var BLOCKRUN_MODELS = [
669
603
  inputPrice: 0.3,
670
604
  outputPrice: 0.5,
671
605
  contextWindow: 131072,
672
- maxOutput: 16384
606
+ maxOutput: 16384,
607
+ toolCalling: true
673
608
  },
674
609
  // xAI Grok 4 Family - Ultra-cheap fast models
675
610
  {
@@ -680,7 +615,8 @@ var BLOCKRUN_MODELS = [
680
615
  outputPrice: 0.5,
681
616
  contextWindow: 131072,
682
617
  maxOutput: 16384,
683
- reasoning: true
618
+ reasoning: true,
619
+ toolCalling: true
684
620
  },
685
621
  {
686
622
  id: "xai/grok-4-fast-non-reasoning",
@@ -689,7 +625,8 @@ var BLOCKRUN_MODELS = [
689
625
  inputPrice: 0.2,
690
626
  outputPrice: 0.5,
691
627
  contextWindow: 131072,
692
- maxOutput: 16384
628
+ maxOutput: 16384,
629
+ toolCalling: true
693
630
  },
694
631
  {
695
632
  id: "xai/grok-4-1-fast-reasoning",
@@ -699,7 +636,8 @@ var BLOCKRUN_MODELS = [
699
636
  outputPrice: 0.5,
700
637
  contextWindow: 131072,
701
638
  maxOutput: 16384,
702
- reasoning: true
639
+ reasoning: true,
640
+ toolCalling: true
703
641
  },
704
642
  {
705
643
  id: "xai/grok-4-1-fast-non-reasoning",
@@ -708,7 +646,8 @@ var BLOCKRUN_MODELS = [
708
646
  inputPrice: 0.2,
709
647
  outputPrice: 0.5,
710
648
  contextWindow: 131072,
711
- maxOutput: 16384
649
+ maxOutput: 16384,
650
+ toolCalling: true
712
651
  },
713
652
  {
714
653
  id: "xai/grok-code-fast-1",
@@ -717,9 +656,10 @@ var BLOCKRUN_MODELS = [
717
656
  inputPrice: 0.2,
718
657
  outputPrice: 1.5,
719
658
  contextWindow: 131072,
720
- maxOutput: 16384,
721
- agentic: true
722
- // Good for coding tasks
659
+ maxOutput: 16384
660
+ // toolCalling intentionally omitted: outputs tool calls as plain text JSON,
661
+ // not OpenAI-compatible structured function calls. Will be skipped when
662
+ // request has tools to prevent the "talking to itself" bug.
723
663
  },
724
664
  {
725
665
  id: "xai/grok-4-0709",
@@ -729,7 +669,8 @@ var BLOCKRUN_MODELS = [
729
669
  outputPrice: 1.5,
730
670
  contextWindow: 131072,
731
671
  maxOutput: 16384,
732
- reasoning: true
672
+ reasoning: true,
673
+ toolCalling: true
733
674
  },
734
675
  {
735
676
  id: "xai/grok-2-vision",
@@ -739,7 +680,8 @@ var BLOCKRUN_MODELS = [
739
680
  outputPrice: 10,
740
681
  contextWindow: 131072,
741
682
  maxOutput: 16384,
742
- vision: true
683
+ vision: true,
684
+ toolCalling: true
743
685
  },
744
686
  // MiniMax
745
687
  {
@@ -751,7 +693,8 @@ var BLOCKRUN_MODELS = [
751
693
  contextWindow: 204800,
752
694
  maxOutput: 16384,
753
695
  reasoning: true,
754
- agentic: true
696
+ agentic: true,
697
+ toolCalling: true
755
698
  },
756
699
  // NVIDIA - Free/cheap models
757
700
  {
@@ -762,6 +705,8 @@ var BLOCKRUN_MODELS = [
762
705
  outputPrice: 0,
763
706
  contextWindow: 128e3,
764
707
  maxOutput: 16384
708
+ // toolCalling intentionally omitted: free model, structured function
709
+ // calling support unverified. Excluded from tool-heavy routing paths.
765
710
  },
766
711
  {
767
712
  id: "nvidia/kimi-k2.5",
@@ -770,7 +715,8 @@ var BLOCKRUN_MODELS = [
770
715
  inputPrice: 0.55,
771
716
  outputPrice: 2.5,
772
717
  contextWindow: 262144,
773
- maxOutput: 16384
718
+ maxOutput: 16384,
719
+ toolCalling: true
774
720
  }
775
721
  ];
776
722
  function toOpenClawModel(m) {
@@ -815,6 +761,16 @@ function isAgenticModel(modelId) {
815
761
  function getAgenticModels() {
816
762
  return BLOCKRUN_MODELS.filter((m) => m.agentic).map((m) => m.id);
817
763
  }
764
+ function supportsToolCalling(modelId) {
765
+ const normalized = modelId.replace("blockrun/", "");
766
+ const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
767
+ return model?.toolCalling ?? false;
768
+ }
769
+ function supportsVision(modelId) {
770
+ const normalized = modelId.replace("blockrun/", "");
771
+ const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
772
+ return model?.vision ?? false;
773
+ }
818
774
  function getModelContextWindow(modelId) {
819
775
  const normalized = modelId.replace("blockrun/", "");
820
776
  const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
@@ -861,13 +817,13 @@ import { x402Client } from "@x402/fetch";
861
817
  // src/payment-preauth.ts
862
818
  import { x402HTTPClient } from "@x402/fetch";
863
819
  var DEFAULT_TTL_MS = 36e5;
864
- function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS) {
820
+ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, options) {
865
821
  const httpClient = new x402HTTPClient(client);
866
822
  const cache = /* @__PURE__ */ new Map();
867
823
  return async (input, init) => {
868
824
  const request = new Request(input, init);
869
825
  const urlPath = new URL(request.url).pathname;
870
- const cached = cache.get(urlPath);
826
+ const cached = !options?.skipPreAuth ? cache.get(urlPath) : void 0;
871
827
  if (cached && Date.now() - cached.cachedAt < ttlMs) {
872
828
  try {
873
829
  const payload2 = await client.createPaymentPayload(cached.paymentRequired);
@@ -1007,20 +963,18 @@ function scoreAgenticTask(text, keywords) {
1007
963
  };
1008
964
  }
1009
965
  function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1010
- const text = `${systemPrompt ?? ""} ${prompt}`.toLowerCase();
1011
966
  const userText = prompt.toLowerCase();
1012
967
  const dimensions = [
1013
- // Original 8 dimensions
968
+ // Token count uses total estimated tokens (system + user) — context size matters for model selection
1014
969
  scoreTokenCount(estimatedTokens, config.tokenCountThresholds),
1015
970
  scoreKeywordMatch(
1016
- text,
971
+ userText,
1017
972
  config.codeKeywords,
1018
973
  "codePresence",
1019
974
  "code",
1020
975
  { low: 1, high: 2 },
1021
976
  { none: 0, low: 0.5, high: 1 }
1022
977
  ),
1023
- // Reasoning markers use USER prompt only — system prompt "step by step" shouldn't trigger reasoning
1024
978
  scoreKeywordMatch(
1025
979
  userText,
1026
980
  config.reasoningKeywords,
@@ -1030,7 +984,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1030
984
  { none: 0, low: 0.7, high: 1 }
1031
985
  ),
1032
986
  scoreKeywordMatch(
1033
- text,
987
+ userText,
1034
988
  config.technicalKeywords,
1035
989
  "technicalTerms",
1036
990
  "technical",
@@ -1038,7 +992,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1038
992
  { none: 0, low: 0.5, high: 1 }
1039
993
  ),
1040
994
  scoreKeywordMatch(
1041
- text,
995
+ userText,
1042
996
  config.creativeKeywords,
1043
997
  "creativeMarkers",
1044
998
  "creative",
@@ -1046,18 +1000,18 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1046
1000
  { none: 0, low: 0.5, high: 0.7 }
1047
1001
  ),
1048
1002
  scoreKeywordMatch(
1049
- text,
1003
+ userText,
1050
1004
  config.simpleKeywords,
1051
1005
  "simpleIndicators",
1052
1006
  "simple",
1053
1007
  { low: 1, high: 2 },
1054
1008
  { none: 0, low: -1, high: -1 }
1055
1009
  ),
1056
- scoreMultiStep(text),
1010
+ scoreMultiStep(userText),
1057
1011
  scoreQuestionComplexity(prompt),
1058
1012
  // 6 new dimensions
1059
1013
  scoreKeywordMatch(
1060
- text,
1014
+ userText,
1061
1015
  config.imperativeVerbs,
1062
1016
  "imperativeVerbs",
1063
1017
  "imperative",
@@ -1065,7 +1019,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1065
1019
  { none: 0, low: 0.3, high: 0.5 }
1066
1020
  ),
1067
1021
  scoreKeywordMatch(
1068
- text,
1022
+ userText,
1069
1023
  config.constraintIndicators,
1070
1024
  "constraintCount",
1071
1025
  "constraints",
@@ -1073,7 +1027,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1073
1027
  { none: 0, low: 0.3, high: 0.7 }
1074
1028
  ),
1075
1029
  scoreKeywordMatch(
1076
- text,
1030
+ userText,
1077
1031
  config.outputFormatKeywords,
1078
1032
  "outputFormat",
1079
1033
  "format",
@@ -1081,7 +1035,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1081
1035
  { none: 0, low: 0.4, high: 0.7 }
1082
1036
  ),
1083
1037
  scoreKeywordMatch(
1084
- text,
1038
+ userText,
1085
1039
  config.referenceKeywords,
1086
1040
  "referenceComplexity",
1087
1041
  "references",
@@ -1089,7 +1043,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1089
1043
  { none: 0, low: 0.3, high: 0.5 }
1090
1044
  ),
1091
1045
  scoreKeywordMatch(
1092
- text,
1046
+ userText,
1093
1047
  config.negationKeywords,
1094
1048
  "negationComplexity",
1095
1049
  "negation",
@@ -1097,7 +1051,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1097
1051
  { none: 0, low: 0.3, high: 0.5 }
1098
1052
  ),
1099
1053
  scoreKeywordMatch(
1100
- text,
1054
+ userText,
1101
1055
  config.domainSpecificKeywords,
1102
1056
  "domainSpecificity",
1103
1057
  "domain-specific",
@@ -1129,7 +1083,8 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1129
1083
  tier: "REASONING",
1130
1084
  confidence: Math.max(confidence2, 0.85),
1131
1085
  signals,
1132
- agenticScore
1086
+ agenticScore,
1087
+ dimensions
1133
1088
  };
1134
1089
  }
1135
1090
  const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
@@ -1153,9 +1108,9 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1153
1108
  }
1154
1109
  const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
1155
1110
  if (confidence < config.confidenceThreshold) {
1156
- return { score: weightedScore, tier: null, confidence, signals, agenticScore };
1111
+ return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };
1157
1112
  }
1158
- return { score: weightedScore, tier, confidence, signals, agenticScore };
1113
+ return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };
1159
1114
  }
1160
1115
  function calibrateConfidence(distance, steepness) {
1161
1116
  return 1 / (1 + Math.exp(-steepness * distance));
@@ -1163,7 +1118,9 @@ function calibrateConfidence(distance, steepness) {
1163
1118
 
1164
1119
  // src/router/selector.ts
1165
1120
  var BASELINE_MODEL_ID = "anthropic/claude-opus-4.6";
1166
- function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
1121
+ var BASELINE_INPUT_PRICE = 5;
1122
+ var BASELINE_OUTPUT_PRICE = 25;
1123
+ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
1167
1124
  const tierConfig = tierConfigs[tier];
1168
1125
  const model = tierConfig.primary;
1169
1126
  const pricing = modelPricing.get(model);
@@ -1173,8 +1130,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
1173
1130
  const outputCost = maxOutputTokens / 1e6 * outputPrice;
1174
1131
  const costEstimate = inputCost + outputCost;
1175
1132
  const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
1176
- const opusInputPrice = opusPricing?.inputPrice ?? 0;
1177
- const opusOutputPrice = opusPricing?.outputPrice ?? 0;
1133
+ const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
1134
+ const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
1178
1135
  const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
1179
1136
  const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
1180
1137
  const baselineCost = baselineInput + baselineOutput;
@@ -1187,7 +1144,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
1187
1144
  reasoning,
1188
1145
  costEstimate,
1189
1146
  baselineCost,
1190
- savings
1147
+ savings,
1148
+ ...agenticScore !== void 0 && { agenticScore }
1191
1149
  };
1192
1150
  }
1193
1151
  function getFallbackChain(tier, tierConfigs) {
@@ -1202,14 +1160,24 @@ function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutput
1202
1160
  const outputCost = maxOutputTokens / 1e6 * outputPrice;
1203
1161
  const costEstimate = inputCost + outputCost;
1204
1162
  const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
1205
- const opusInputPrice = opusPricing?.inputPrice ?? 0;
1206
- const opusOutputPrice = opusPricing?.outputPrice ?? 0;
1163
+ const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
1164
+ const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
1207
1165
  const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
1208
1166
  const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
1209
1167
  const baselineCost = baselineInput + baselineOutput;
1210
1168
  const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
1211
1169
  return { costEstimate, baselineCost, savings };
1212
1170
  }
1171
+ function filterByToolCalling(models, hasTools, supportsToolCalling2) {
1172
+ if (!hasTools) return models;
1173
+ const filtered = models.filter(supportsToolCalling2);
1174
+ return filtered.length > 0 ? filtered : models;
1175
+ }
1176
+ function filterByVision(models, hasVision, supportsVision2) {
1177
+ if (!hasVision) return models;
1178
+ const filtered = models.filter(supportsVision2);
1179
+ return filtered.length > 0 ? filtered : models;
1180
+ }
1213
1181
  function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
1214
1182
  const fullChain = getFallbackChain(tier, tierConfigs);
1215
1183
  const filtered = fullChain.filter((modelId) => {
@@ -2265,18 +2233,18 @@ var DEFAULT_ROUTING_CONFIG = {
2265
2233
  ]
2266
2234
  },
2267
2235
  MEDIUM: {
2268
- primary: "xai/grok-code-fast-1",
2269
- // Code specialist, $0.20/$1.50
2236
+ primary: "moonshot/kimi-k2.5",
2237
+ // $0.50/$2.40 - strong tool use, proper function call format
2270
2238
  fallback: [
2239
+ "deepseek/deepseek-chat",
2271
2240
  "google/gemini-2.5-flash-lite",
2272
2241
  // 1M context, ultra cheap ($0.10/$0.40)
2273
- "deepseek/deepseek-chat",
2274
2242
  "xai/grok-4-1-fast-non-reasoning"
2275
2243
  // Upgraded Grok 4.1
2276
2244
  ]
2277
2245
  },
2278
2246
  COMPLEX: {
2279
- primary: "google/gemini-3.1-pro-preview",
2247
+ primary: "google/gemini-3.1-pro",
2280
2248
  // Newest Gemini 3.1 - upgraded from 3.0
2281
2249
  fallback: [
2282
2250
  "google/gemini-2.5-flash-lite",
@@ -2336,7 +2304,7 @@ var DEFAULT_ROUTING_CONFIG = {
2336
2304
  fallback: [
2337
2305
  "anthropic/claude-haiku-4.5",
2338
2306
  "google/gemini-2.5-flash-lite",
2339
- "xai/grok-code-fast-1"
2307
+ "deepseek/deepseek-chat"
2340
2308
  ]
2341
2309
  },
2342
2310
  MEDIUM: {
@@ -2356,7 +2324,7 @@ var DEFAULT_ROUTING_CONFIG = {
2356
2324
  "openai/gpt-5.2-codex",
2357
2325
  "anthropic/claude-opus-4.6",
2358
2326
  "anthropic/claude-sonnet-4.6",
2359
- "google/gemini-3.1-pro-preview",
2327
+ "google/gemini-3.1-pro",
2360
2328
  // Newest Gemini
2361
2329
  "google/gemini-3-pro-preview",
2362
2330
  "moonshot/kimi-k2.5"
@@ -2387,9 +2355,13 @@ var DEFAULT_ROUTING_CONFIG = {
2387
2355
  ]
2388
2356
  },
2389
2357
  MEDIUM: {
2390
- primary: "xai/grok-code-fast-1",
2391
- // Code specialist for agentic coding
2392
- fallback: ["moonshot/kimi-k2.5", "anthropic/claude-haiku-4.5", "claude-sonnet-4"]
2358
+ primary: "moonshot/kimi-k2.5",
2359
+ // $0.50/$2.40 - strong tool use, handles function calls correctly
2360
+ fallback: [
2361
+ "anthropic/claude-haiku-4.5",
2362
+ "deepseek/deepseek-chat",
2363
+ "xai/grok-4-1-fast-non-reasoning"
2364
+ ]
2393
2365
  },
2394
2366
  COMPLEX: {
2395
2367
  primary: "anthropic/claude-sonnet-4.6",
@@ -2397,7 +2369,7 @@ var DEFAULT_ROUTING_CONFIG = {
2397
2369
  "anthropic/claude-opus-4.6",
2398
2370
  // Latest Opus - best agentic
2399
2371
  "openai/gpt-5.2",
2400
- "google/gemini-3.1-pro-preview",
2372
+ "google/gemini-3.1-pro",
2401
2373
  // Newest Gemini
2402
2374
  "google/gemini-3-pro-preview",
2403
2375
  "xai/grok-4-0709"
@@ -2429,7 +2401,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2429
2401
  const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
2430
2402
  const { routingProfile } = options;
2431
2403
  let tierConfigs;
2432
- let profileSuffix = "";
2404
+ let profileSuffix;
2433
2405
  if (routingProfile === "eco" && config.ecoTiers) {
2434
2406
  tierConfigs = config.ecoTiers;
2435
2407
  profileSuffix = " | eco";
@@ -2444,6 +2416,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2444
2416
  tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
2445
2417
  profileSuffix = useAgenticTiers ? " | agentic" : "";
2446
2418
  }
2419
+ const agenticScoreValue = ruleResult.agenticScore;
2447
2420
  if (estimatedTokens > config.overrides.maxTokensForceComplex) {
2448
2421
  return selectModel(
2449
2422
  "COMPLEX",
@@ -2454,7 +2427,8 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2454
2427
  modelPricing,
2455
2428
  estimatedTokens,
2456
2429
  maxOutputTokens,
2457
- routingProfile
2430
+ routingProfile,
2431
+ agenticScoreValue
2458
2432
  );
2459
2433
  }
2460
2434
  const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
@@ -2488,7 +2462,8 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2488
2462
  modelPricing,
2489
2463
  estimatedTokens,
2490
2464
  maxOutputTokens,
2491
- routingProfile
2465
+ routingProfile,
2466
+ agenticScoreValue
2492
2467
  );
2493
2468
  }
2494
2469
 
@@ -3243,6 +3218,9 @@ var BalanceMonitor = class {
3243
3218
  }
3244
3219
  };
3245
3220
 
3221
+ // src/proxy.ts
3222
+ init_solana_balance();
3223
+
3246
3224
  // src/auth.ts
3247
3225
  import { writeFile, mkdir as mkdir2 } from "fs/promises";
3248
3226
  init_wallet();
@@ -3263,7 +3241,9 @@ async function loadSavedWallet() {
3263
3241
  console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
3264
3242
  console.error(`[ClawRouter] File: ${WALLET_FILE}`);
3265
3243
  console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
3266
- console.error(`[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`);
3244
+ console.error(
3245
+ `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
3246
+ );
3267
3247
  throw new Error(
3268
3248
  `Wallet file at ${WALLET_FILE} is corrupted or has wrong format. Refusing to auto-generate new wallet to protect existing funds. Restore your backup key or set BLOCKRUN_WALLET_KEY environment variable.`
3269
3249
  );
@@ -3276,7 +3256,8 @@ async function loadSavedWallet() {
3276
3256
  `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
3277
3257
  );
3278
3258
  throw new Error(
3279
- `Cannot read wallet file at ${WALLET_FILE}: ${err instanceof Error ? err.message : String(err)}. Refusing to auto-generate new wallet to protect existing funds. Fix file permissions or set BLOCKRUN_WALLET_KEY environment variable.`
3259
+ `Cannot read wallet file at ${WALLET_FILE}: ${err instanceof Error ? err.message : String(err)}. Refusing to auto-generate new wallet to protect existing funds. Fix file permissions or set BLOCKRUN_WALLET_KEY environment variable.`,
3260
+ { cause: err }
3280
3261
  );
3281
3262
  }
3282
3263
  }
@@ -3321,7 +3302,8 @@ async function generateAndSaveWallet() {
3321
3302
  console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);
3322
3303
  } catch (err) {
3323
3304
  throw new Error(
3324
- `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
3305
+ `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`,
3306
+ { cause: err }
3325
3307
  );
3326
3308
  }
3327
3309
  console.log(`[ClawRouter]`);
@@ -4175,8 +4157,9 @@ function shouldCompress(messages) {
4175
4157
  }
4176
4158
 
4177
4159
  // src/session.ts
4160
+ import { createHash as createHash3 } from "crypto";
4178
4161
  var DEFAULT_SESSION_CONFIG = {
4179
- enabled: false,
4162
+ enabled: true,
4180
4163
  timeoutMs: 30 * 60 * 1e3,
4181
4164
  // 30 minutes
4182
4165
  headerName: "x-session-id"
@@ -4231,7 +4214,10 @@ var SessionStore = class {
4231
4214
  tier,
4232
4215
  createdAt: now,
4233
4216
  lastUsedAt: now,
4234
- requestCount: 1
4217
+ requestCount: 1,
4218
+ recentHashes: [],
4219
+ strikes: 0,
4220
+ escalated: false
4235
4221
  });
4236
4222
  }
4237
4223
  }
@@ -4283,6 +4269,43 @@ var SessionStore = class {
4283
4269
  }
4284
4270
  }
4285
4271
  }
4272
+ /**
4273
+ * Record a request content hash and detect repetitive patterns.
4274
+ * Returns true if escalation should be triggered (3+ consecutive similar requests).
4275
+ */
4276
+ recordRequestHash(sessionId, hash) {
4277
+ const entry = this.sessions.get(sessionId);
4278
+ if (!entry) return false;
4279
+ const prev = entry.recentHashes;
4280
+ if (prev.length > 0 && prev[prev.length - 1] === hash) {
4281
+ entry.strikes++;
4282
+ } else {
4283
+ entry.strikes = 0;
4284
+ }
4285
+ entry.recentHashes.push(hash);
4286
+ if (entry.recentHashes.length > 3) {
4287
+ entry.recentHashes.shift();
4288
+ }
4289
+ return entry.strikes >= 2 && !entry.escalated;
4290
+ }
4291
+ /**
4292
+ * Escalate session to next tier. Returns the new model/tier or null if already at max.
4293
+ */
4294
+ escalateSession(sessionId, tierConfigs) {
4295
+ const entry = this.sessions.get(sessionId);
4296
+ if (!entry) return null;
4297
+ const TIER_ORDER = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
4298
+ const currentIdx = TIER_ORDER.indexOf(entry.tier);
4299
+ if (currentIdx < 0 || currentIdx >= TIER_ORDER.length - 1) return null;
4300
+ const nextTier = TIER_ORDER[currentIdx + 1];
4301
+ const nextConfig = tierConfigs[nextTier];
4302
+ if (!nextConfig) return null;
4303
+ entry.model = nextConfig.primary;
4304
+ entry.tier = nextTier;
4305
+ entry.strikes = 0;
4306
+ entry.escalated = true;
4307
+ return { model: nextConfig.primary, tier: nextTier };
4308
+ }
4286
4309
  /**
4287
4310
  * Stop the cleanup interval.
4288
4311
  */
@@ -4303,6 +4326,17 @@ function getSessionId(headers, headerName = DEFAULT_SESSION_CONFIG.headerName) {
4303
4326
  }
4304
4327
  return void 0;
4305
4328
  }
4329
+ function deriveSessionId(messages) {
4330
+ const firstUser = messages.find((m) => m.role === "user");
4331
+ if (!firstUser) return void 0;
4332
+ const content = typeof firstUser.content === "string" ? firstUser.content : JSON.stringify(firstUser.content);
4333
+ return createHash3("sha256").update(content).digest("hex").slice(0, 8);
4334
+ }
4335
+ function hashRequestContent(lastUserContent, toolCallNames) {
4336
+ const normalized = lastUserContent.replace(/\s+/g, " ").trim().slice(0, 500);
4337
+ const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(",")}` : "";
4338
+ return createHash3("sha256").update(normalized + toolSuffix).digest("hex").slice(0, 12);
4339
+ }
4306
4340
 
4307
4341
  // src/updater.ts
4308
4342
  var NPM_REGISTRY = "https://registry.npmjs.org/@blockrun/clawrouter/latest";
@@ -5060,6 +5094,27 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
5060
5094
  }).catch(() => {
5061
5095
  });
5062
5096
  }
5097
+ async function uploadDataUriToHost(dataUri) {
5098
+ const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
5099
+ if (!match) throw new Error("Invalid data URI format");
5100
+ const [, mimeType, b64Data] = match;
5101
+ const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
5102
+ const buffer = Buffer.from(b64Data, "base64");
5103
+ const blob = new Blob([buffer], { type: mimeType });
5104
+ const form = new FormData();
5105
+ form.append("reqtype", "fileupload");
5106
+ form.append("fileToUpload", blob, `image.${ext}`);
5107
+ const resp = await fetch("https://catbox.moe/user/api.php", {
5108
+ method: "POST",
5109
+ body: form
5110
+ });
5111
+ if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
5112
+ const result = await resp.text();
5113
+ if (result.startsWith("https://")) {
5114
+ return result.trim();
5115
+ }
5116
+ throw new Error(`catbox.moe upload failed: ${result}`);
5117
+ }
5063
5118
  async function startProxy(options) {
5064
5119
  const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
5065
5120
  const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
@@ -5074,7 +5129,6 @@ async function startProxy(options) {
5074
5129
  const existingProxy = await checkExistingProxy(listenPort);
5075
5130
  if (existingProxy) {
5076
5131
  const account2 = privateKeyToAccount3(walletKey);
5077
- const balanceMonitor2 = new BalanceMonitor(account2.address);
5078
5132
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
5079
5133
  if (existingProxy.wallet !== account2.address) {
5080
5134
  console.warn(
@@ -5099,6 +5153,7 @@ async function startProxy(options) {
5099
5153
  const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
5100
5154
  reuseSolanaAddress = solanaSigner.address;
5101
5155
  }
5156
+ const balanceMonitor2 = paymentChain === "solana" && reuseSolanaAddress ? new SolanaBalanceMonitor(reuseSolanaAddress) : new BalanceMonitor(account2.address);
5102
5157
  options.onReady?.(listenPort);
5103
5158
  return {
5104
5159
  port: listenPort,
@@ -5129,8 +5184,10 @@ async function startProxy(options) {
5129
5184
  const chain = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
5130
5185
  console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);
5131
5186
  });
5132
- const payFetch = createPayFetchWithPreAuth(fetch, x402);
5133
- const balanceMonitor = new BalanceMonitor(account.address);
5187
+ const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
5188
+ skipPreAuth: paymentChain === "solana"
5189
+ });
5190
+ const balanceMonitor = paymentChain === "solana" && solanaAddress ? new SolanaBalanceMonitor(solanaAddress) : new BalanceMonitor(account.address);
5134
5191
  const routingConfig = mergeRoutingConfig(options.routingConfig);
5135
5192
  const modelPricing = buildModelPricing();
5136
5193
  const routerOpts = {
@@ -5483,14 +5540,19 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5483
5540
  }
5484
5541
  let body = Buffer.concat(bodyChunks);
5485
5542
  const originalContextSizeKB = Math.ceil(body.length / 1024);
5543
+ const debugMode = req.headers["x-clawrouter-debug"] !== "false";
5486
5544
  let routingDecision;
5545
+ let hasTools = false;
5546
+ let hasVision = false;
5487
5547
  let isStreaming = false;
5488
5548
  let modelId = "";
5489
5549
  let maxTokens = 4096;
5490
5550
  let routingProfile = null;
5491
5551
  let accumulatedContent = "";
5552
+ let responseInputTokens;
5492
5553
  const isChatCompletion = req.url?.includes("/chat/completions");
5493
5554
  const sessionId = getSessionId(req.headers);
5555
+ let effectiveSessionId = sessionId;
5494
5556
  if (isChatCompletion && body.length > 0) {
5495
5557
  try {
5496
5558
  const parsed = JSON.parse(body.toString());
@@ -5498,10 +5560,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5498
5560
  modelId = parsed.model || "";
5499
5561
  maxTokens = parsed.max_tokens || 4096;
5500
5562
  let bodyModified = false;
5501
- if (sessionId && Array.isArray(parsed.messages)) {
5502
- const messages = parsed.messages;
5503
- const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
5504
- const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5563
+ const parsedMessages = Array.isArray(parsed.messages) ? parsed.messages : [];
5564
+ const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === "user");
5565
+ const rawLastContent = lastUserMsg?.content;
5566
+ const lastContent = typeof rawLastContent === "string" ? rawLastContent : Array.isArray(rawLastContent) ? rawLastContent.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
5567
+ if (sessionId && parsedMessages.length > 0) {
5568
+ const messages = parsedMessages;
5505
5569
  if (sessionJournal.needsContext(lastContent)) {
5506
5570
  const journalText = sessionJournal.format(sessionId);
5507
5571
  if (journalText) {
@@ -5522,6 +5586,303 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5522
5586
  }
5523
5587
  }
5524
5588
  }
5589
+ if (lastContent.startsWith("/debug")) {
5590
+ const debugPrompt = lastContent.slice("/debug".length).trim() || "hello";
5591
+ const messages = parsed.messages;
5592
+ const systemMsg = messages?.find((m) => m.role === "system");
5593
+ const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5594
+ const fullText = `${systemPrompt ?? ""} ${debugPrompt}`;
5595
+ const estimatedTokens = Math.ceil(fullText.length / 4);
5596
+ const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
5597
+ const profileName = normalizedModel2.replace("blockrun/", "");
5598
+ const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) ? profileName : "auto";
5599
+ const scoring = classifyByRules(
5600
+ debugPrompt,
5601
+ systemPrompt,
5602
+ estimatedTokens,
5603
+ DEFAULT_ROUTING_CONFIG.scoring
5604
+ );
5605
+ const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {
5606
+ ...routerOpts,
5607
+ routingProfile: debugProfile
5608
+ });
5609
+ const dimLines = (scoring.dimensions ?? []).map((d) => {
5610
+ const nameStr = (d.name + ":").padEnd(24);
5611
+ const scoreStr = d.score.toFixed(2).padStart(6);
5612
+ const sigStr = d.signal ? ` [${d.signal}]` : "";
5613
+ return ` ${nameStr}${scoreStr}${sigStr}`;
5614
+ }).join("\n");
5615
+ const sess = sessionId ? sessionStore.getSession(sessionId) : void 0;
5616
+ const sessLine = sess ? `Session: ${sessionId.slice(0, 8)}... \u2192 pinned: ${sess.model} (${sess.requestCount} requests)` : sessionId ? `Session: ${sessionId.slice(0, 8)}... \u2192 no pinned model` : "Session: none";
5617
+ const { simpleMedium, mediumComplex, complexReasoning } = DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;
5618
+ const debugText = [
5619
+ "ClawRouter Debug",
5620
+ "",
5621
+ `Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,
5622
+ `Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,
5623
+ `Reasoning: ${debugRouting.reasoning}`,
5624
+ "",
5625
+ `Scoring (weighted: ${scoring.score.toFixed(3)})`,
5626
+ dimLines,
5627
+ "",
5628
+ `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,
5629
+ "",
5630
+ sessLine
5631
+ ].join("\n");
5632
+ const completionId = `chatcmpl-debug-${Date.now()}`;
5633
+ const timestamp = Math.floor(Date.now() / 1e3);
5634
+ const syntheticResponse = {
5635
+ id: completionId,
5636
+ object: "chat.completion",
5637
+ created: timestamp,
5638
+ model: "clawrouter/debug",
5639
+ choices: [
5640
+ {
5641
+ index: 0,
5642
+ message: { role: "assistant", content: debugText },
5643
+ finish_reason: "stop"
5644
+ }
5645
+ ],
5646
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5647
+ };
5648
+ if (isStreaming) {
5649
+ res.writeHead(200, {
5650
+ "Content-Type": "text/event-stream",
5651
+ "Cache-Control": "no-cache",
5652
+ Connection: "keep-alive"
5653
+ });
5654
+ const sseChunk = {
5655
+ id: completionId,
5656
+ object: "chat.completion.chunk",
5657
+ created: timestamp,
5658
+ model: "clawrouter/debug",
5659
+ choices: [
5660
+ {
5661
+ index: 0,
5662
+ delta: { role: "assistant", content: debugText },
5663
+ finish_reason: null
5664
+ }
5665
+ ]
5666
+ };
5667
+ const sseDone = {
5668
+ id: completionId,
5669
+ object: "chat.completion.chunk",
5670
+ created: timestamp,
5671
+ model: "clawrouter/debug",
5672
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
5673
+ };
5674
+ res.write(`data: ${JSON.stringify(sseChunk)}
5675
+
5676
+ `);
5677
+ res.write(`data: ${JSON.stringify(sseDone)}
5678
+
5679
+ `);
5680
+ res.write("data: [DONE]\n\n");
5681
+ res.end();
5682
+ } else {
5683
+ res.writeHead(200, { "Content-Type": "application/json" });
5684
+ res.end(JSON.stringify(syntheticResponse));
5685
+ }
5686
+ console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`);
5687
+ return;
5688
+ }
5689
+ if (lastContent.startsWith("/imagegen")) {
5690
+ const imageArgs = lastContent.slice("/imagegen".length).trim();
5691
+ let imageModel = "google/nano-banana";
5692
+ let imageSize = "1024x1024";
5693
+ let imagePrompt = imageArgs;
5694
+ const modelMatch = imageArgs.match(/--model\s+(\S+)/);
5695
+ if (modelMatch) {
5696
+ const raw = modelMatch[1];
5697
+ const IMAGE_MODEL_ALIASES = {
5698
+ "dall-e-3": "openai/dall-e-3",
5699
+ dalle3: "openai/dall-e-3",
5700
+ dalle: "openai/dall-e-3",
5701
+ "gpt-image": "openai/gpt-image-1",
5702
+ "gpt-image-1": "openai/gpt-image-1",
5703
+ flux: "black-forest/flux-1.1-pro",
5704
+ "flux-pro": "black-forest/flux-1.1-pro",
5705
+ banana: "google/nano-banana",
5706
+ "nano-banana": "google/nano-banana",
5707
+ "banana-pro": "google/nano-banana-pro",
5708
+ "nano-banana-pro": "google/nano-banana-pro"
5709
+ };
5710
+ imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;
5711
+ imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim();
5712
+ }
5713
+ const sizeMatch = imageArgs.match(/--size\s+(\d+x\d+)/);
5714
+ if (sizeMatch) {
5715
+ imageSize = sizeMatch[1];
5716
+ imagePrompt = imagePrompt.replace(/--size\s+\d+x\d+/, "").trim();
5717
+ }
5718
+ if (!imagePrompt) {
5719
+ const errorText = [
5720
+ "Usage: /imagegen <prompt>",
5721
+ "",
5722
+ "Options:",
5723
+ " --model <model> Model to use (default: nano-banana)",
5724
+ " --size <WxH> Image size (default: 1024x1024)",
5725
+ "",
5726
+ "Models:",
5727
+ " nano-banana Google Gemini Flash \u2014 $0.05/image",
5728
+ " banana-pro Google Gemini Pro \u2014 $0.10/image (up to 4K)",
5729
+ " dall-e-3 OpenAI DALL-E 3 \u2014 $0.04/image",
5730
+ " gpt-image OpenAI GPT Image 1 \u2014 $0.02/image",
5731
+ " flux Black Forest Flux 1.1 Pro \u2014 $0.04/image",
5732
+ "",
5733
+ "Examples:",
5734
+ " /imagegen a cat wearing sunglasses",
5735
+ " /imagegen --model dall-e-3 a futuristic city at sunset",
5736
+ " /imagegen --model banana-pro --size 2048x2048 mountain landscape"
5737
+ ].join("\n");
5738
+ const completionId = `chatcmpl-image-${Date.now()}`;
5739
+ const timestamp = Math.floor(Date.now() / 1e3);
5740
+ if (isStreaming) {
5741
+ res.writeHead(200, {
5742
+ "Content-Type": "text/event-stream",
5743
+ "Cache-Control": "no-cache",
5744
+ Connection: "keep-alive"
5745
+ });
5746
+ res.write(
5747
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: { role: "assistant", content: errorText }, finish_reason: null }] })}
5748
+
5749
+ `
5750
+ );
5751
+ res.write(
5752
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
5753
+
5754
+ `
5755
+ );
5756
+ res.write("data: [DONE]\n\n");
5757
+ res.end();
5758
+ } else {
5759
+ res.writeHead(200, { "Content-Type": "application/json" });
5760
+ res.end(
5761
+ JSON.stringify({
5762
+ id: completionId,
5763
+ object: "chat.completion",
5764
+ created: timestamp,
5765
+ model: "clawrouter/image",
5766
+ choices: [
5767
+ {
5768
+ index: 0,
5769
+ message: { role: "assistant", content: errorText },
5770
+ finish_reason: "stop"
5771
+ }
5772
+ ],
5773
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5774
+ })
5775
+ );
5776
+ }
5777
+ console.log(`[ClawRouter] /imagegen command \u2192 showing usage help`);
5778
+ return;
5779
+ }
5780
+ console.log(
5781
+ `[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`
5782
+ );
5783
+ try {
5784
+ const imageUpstreamUrl = `${apiBase}/v1/images/generations`;
5785
+ const imageBody = JSON.stringify({
5786
+ model: imageModel,
5787
+ prompt: imagePrompt,
5788
+ size: imageSize,
5789
+ n: 1
5790
+ });
5791
+ const imageResponse = await payFetch(imageUpstreamUrl, {
5792
+ method: "POST",
5793
+ headers: { "content-type": "application/json", "user-agent": USER_AGENT },
5794
+ body: imageBody
5795
+ });
5796
+ const imageResult = await imageResponse.json();
5797
+ let responseText;
5798
+ if (!imageResponse.ok || imageResult.error) {
5799
+ const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`;
5800
+ responseText = `Image generation failed: ${errMsg}`;
5801
+ console.log(`[ClawRouter] /imagegen error: ${errMsg}`);
5802
+ } else {
5803
+ const images = imageResult.data ?? [];
5804
+ if (images.length === 0) {
5805
+ responseText = "Image generation returned no results.";
5806
+ } else {
5807
+ const lines = [];
5808
+ for (const img of images) {
5809
+ if (img.url) {
5810
+ if (img.url.startsWith("data:")) {
5811
+ try {
5812
+ const hostedUrl = await uploadDataUriToHost(img.url);
5813
+ lines.push(hostedUrl);
5814
+ } catch (uploadErr) {
5815
+ console.error(
5816
+ `[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
5817
+ );
5818
+ lines.push(
5819
+ "Image generated but upload failed. Try again or use --model dall-e-3."
5820
+ );
5821
+ }
5822
+ } else {
5823
+ lines.push(img.url);
5824
+ }
5825
+ }
5826
+ if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);
5827
+ }
5828
+ lines.push("", `Model: ${imageModel} | Size: ${imageSize}`);
5829
+ responseText = lines.join("\n");
5830
+ }
5831
+ console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
5832
+ }
5833
+ const completionId = `chatcmpl-image-${Date.now()}`;
5834
+ const timestamp = Math.floor(Date.now() / 1e3);
5835
+ if (isStreaming) {
5836
+ res.writeHead(200, {
5837
+ "Content-Type": "text/event-stream",
5838
+ "Cache-Control": "no-cache",
5839
+ Connection: "keep-alive"
5840
+ });
5841
+ res.write(
5842
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: { role: "assistant", content: responseText }, finish_reason: null }] })}
5843
+
5844
+ `
5845
+ );
5846
+ res.write(
5847
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
5848
+
5849
+ `
5850
+ );
5851
+ res.write("data: [DONE]\n\n");
5852
+ res.end();
5853
+ } else {
5854
+ res.writeHead(200, { "Content-Type": "application/json" });
5855
+ res.end(
5856
+ JSON.stringify({
5857
+ id: completionId,
5858
+ object: "chat.completion",
5859
+ created: timestamp,
5860
+ model: "clawrouter/image",
5861
+ choices: [
5862
+ {
5863
+ index: 0,
5864
+ message: { role: "assistant", content: responseText },
5865
+ finish_reason: "stop"
5866
+ }
5867
+ ],
5868
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5869
+ })
5870
+ );
5871
+ }
5872
+ } catch (err) {
5873
+ const errMsg = err instanceof Error ? err.message : String(err);
5874
+ console.error(`[ClawRouter] /imagegen error: ${errMsg}`);
5875
+ if (!res.headersSent) {
5876
+ res.writeHead(500, { "Content-Type": "application/json" });
5877
+ res.end(
5878
+ JSON.stringify({
5879
+ error: { message: `Image generation failed: ${errMsg}`, type: "image_error" }
5880
+ })
5881
+ );
5882
+ }
5883
+ }
5884
+ return;
5885
+ }
5525
5886
  if (parsed.stream === true) {
5526
5887
  parsed.stream = false;
5527
5888
  bodyModified = true;
@@ -5562,54 +5923,118 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5562
5923
  latencyMs: 0
5563
5924
  });
5564
5925
  } else {
5565
- const sessionId2 = getSessionId(
5566
- req.headers
5567
- );
5568
- const existingSession = sessionId2 ? sessionStore.getSession(sessionId2) : void 0;
5926
+ effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
5927
+ const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
5928
+ const rawPrompt = lastUserMsg?.content;
5929
+ const prompt = typeof rawPrompt === "string" ? rawPrompt : Array.isArray(rawPrompt) ? rawPrompt.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
5930
+ const systemMsg = parsedMessages.find((m) => m.role === "system");
5931
+ const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5932
+ const tools = parsed.tools;
5933
+ hasTools = Array.isArray(tools) && tools.length > 0;
5934
+ if (hasTools && tools) {
5935
+ console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);
5936
+ }
5937
+ hasVision = parsedMessages.some((m) => {
5938
+ if (Array.isArray(m.content)) {
5939
+ return m.content.some((p) => p.type === "image_url");
5940
+ }
5941
+ return false;
5942
+ });
5943
+ if (hasVision) {
5944
+ console.log(`[ClawRouter] Vision content detected, filtering to vision-capable models`);
5945
+ }
5946
+ routingDecision = route(prompt, systemPrompt, maxTokens, {
5947
+ ...routerOpts,
5948
+ routingProfile: routingProfile ?? void 0
5949
+ });
5569
5950
  if (existingSession) {
5570
- console.log(
5571
- `[ClawRouter] Session ${sessionId2?.slice(0, 8)}... using pinned model: ${existingSession.model}`
5572
- );
5573
- parsed.model = existingSession.model;
5574
- modelId = existingSession.model;
5575
- bodyModified = true;
5576
- sessionStore.touchSession(sessionId2);
5577
- } else {
5578
- const messages = parsed.messages;
5579
- let lastUserMsg;
5580
- if (messages) {
5581
- for (let i = messages.length - 1; i >= 0; i--) {
5582
- if (messages[i].role === "user") {
5583
- lastUserMsg = messages[i];
5584
- break;
5585
- }
5951
+ const tierRank = {
5952
+ SIMPLE: 0,
5953
+ MEDIUM: 1,
5954
+ COMPLEX: 2,
5955
+ REASONING: 3
5956
+ };
5957
+ const existingRank = tierRank[existingSession.tier] ?? 0;
5958
+ const newRank = tierRank[routingDecision.tier] ?? 0;
5959
+ if (newRank > existingRank) {
5960
+ console.log(
5961
+ `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} \u2192 ${routingDecision.tier} (${routingDecision.model})`
5962
+ );
5963
+ parsed.model = routingDecision.model;
5964
+ modelId = routingDecision.model;
5965
+ bodyModified = true;
5966
+ if (effectiveSessionId) {
5967
+ sessionStore.setSession(
5968
+ effectiveSessionId,
5969
+ routingDecision.model,
5970
+ routingDecision.tier
5971
+ );
5586
5972
  }
5587
- }
5588
- const systemMsg = messages?.find((m) => m.role === "system");
5589
- const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5590
- const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5591
- const tools = parsed.tools;
5592
- const hasTools = Array.isArray(tools) && tools.length > 0;
5593
- if (hasTools && tools) {
5973
+ } else {
5594
5974
  console.log(
5595
- `[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`
5975
+ `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`
5596
5976
  );
5977
+ parsed.model = existingSession.model;
5978
+ modelId = existingSession.model;
5979
+ bodyModified = true;
5980
+ sessionStore.touchSession(effectiveSessionId);
5981
+ routingDecision = {
5982
+ ...routingDecision,
5983
+ model: existingSession.model,
5984
+ tier: existingSession.tier
5985
+ };
5597
5986
  }
5598
- routingDecision = route(prompt, systemPrompt, maxTokens, {
5599
- ...routerOpts,
5600
- routingProfile: routingProfile ?? void 0
5601
- });
5987
+ const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
5988
+ const assistantToolCalls = lastAssistantMsg?.tool_calls;
5989
+ const toolCallNames = Array.isArray(assistantToolCalls) ? assistantToolCalls.map((tc) => tc.function?.name).filter((n) => Boolean(n)) : void 0;
5990
+ const contentHash = hashRequestContent(prompt, toolCallNames);
5991
+ const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
5992
+ if (shouldEscalate) {
5993
+ const activeTierConfigs = (() => {
5994
+ if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
5995
+ return routerOpts.config.agenticTiers;
5996
+ }
5997
+ if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
5998
+ return routerOpts.config.ecoTiers;
5999
+ }
6000
+ if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
6001
+ return routerOpts.config.premiumTiers;
6002
+ }
6003
+ return routerOpts.config.tiers;
6004
+ })();
6005
+ const escalation = sessionStore.escalateSession(
6006
+ effectiveSessionId,
6007
+ activeTierConfigs
6008
+ );
6009
+ if (escalation) {
6010
+ console.log(
6011
+ `[ClawRouter] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`
6012
+ );
6013
+ parsed.model = escalation.model;
6014
+ modelId = escalation.model;
6015
+ routingDecision = {
6016
+ ...routingDecision,
6017
+ model: escalation.model,
6018
+ tier: escalation.tier
6019
+ };
6020
+ }
6021
+ }
6022
+ } else {
5602
6023
  parsed.model = routingDecision.model;
5603
6024
  modelId = routingDecision.model;
5604
6025
  bodyModified = true;
5605
- if (sessionId2) {
5606
- sessionStore.setSession(sessionId2, routingDecision.model, routingDecision.tier);
6026
+ if (effectiveSessionId) {
6027
+ sessionStore.setSession(
6028
+ effectiveSessionId,
6029
+ routingDecision.model,
6030
+ routingDecision.tier
6031
+ );
5607
6032
  console.log(
5608
- `[ClawRouter] Session ${sessionId2.slice(0, 8)}... pinned to model: ${routingDecision.model}`
6033
+ `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`
5609
6034
  );
5610
6035
  }
5611
- options.onRouted?.(routingDecision);
5612
6036
  }
6037
+ options.onRouted?.(routingDecision);
5613
6038
  }
5614
6039
  }
5615
6040
  if (bodyModified) {
@@ -5702,6 +6127,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5702
6127
  }
5703
6128
  deduplicator.markInflight(dedupKey);
5704
6129
  let estimatedCostMicros;
6130
+ let balanceFallbackNotice;
5705
6131
  const isFreeModel = modelId === FREE_MODEL;
5706
6132
  if (modelId && !options.skipBalanceCheck && !isFreeModel) {
5707
6133
  const estimated = estimateAmount(modelId, body.length, maxTokens);
@@ -5712,12 +6138,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5712
6138
  if (sufficiency.info.isEmpty || !sufficiency.sufficient) {
5713
6139
  const originalModel = modelId;
5714
6140
  console.log(
5715
- `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} ($${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
6141
+ `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
5716
6142
  );
5717
6143
  modelId = FREE_MODEL;
5718
6144
  const parsed = JSON.parse(body.toString());
5719
6145
  parsed.model = FREE_MODEL;
5720
6146
  body = Buffer.from(JSON.stringify(parsed));
6147
+ balanceFallbackNotice = sufficiency.info.isEmpty ? `> **\u26A0\uFE0F Wallet empty** \u2014 using free model. Fund your wallet to use ${originalModel}.
6148
+
6149
+ ` : `> **\u26A0\uFE0F Insufficient balance** (${sufficiency.info.balanceUSD}) \u2014 using free model instead of ${originalModel}.
6150
+
6151
+ `;
5721
6152
  options.onLowBalance?.({
5722
6153
  balanceUSD: sufficiency.info.balanceUSD,
5723
6154
  walletAddress: sufficiency.info.walletAddress
@@ -5781,8 +6212,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5781
6212
  if (routingDecision) {
5782
6213
  const estimatedInputTokens = Math.ceil(body.length / 4);
5783
6214
  const estimatedTotalTokens = estimatedInputTokens + maxTokens;
5784
- const useAgenticTiers = routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers;
5785
- const tierConfigs = useAgenticTiers ? routerOpts.config.agenticTiers : routerOpts.config.tiers;
6215
+ const tierConfigs = (() => {
6216
+ if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
6217
+ return routerOpts.config.agenticTiers;
6218
+ }
6219
+ if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
6220
+ return routerOpts.config.ecoTiers;
6221
+ }
6222
+ if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
6223
+ return routerOpts.config.premiumTiers;
6224
+ }
6225
+ return routerOpts.config.tiers;
6226
+ })();
5786
6227
  const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);
5787
6228
  const contextFiltered = getFallbackChainFiltered(
5788
6229
  routingDecision.tier,
@@ -5796,14 +6237,27 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5796
6237
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
5797
6238
  );
5798
6239
  }
5799
- modelsToTry = contextFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
6240
+ const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);
6241
+ const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));
6242
+ if (toolExcluded.length > 0) {
6243
+ console.log(
6244
+ `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
6245
+ );
6246
+ }
6247
+ const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);
6248
+ const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));
6249
+ if (visionExcluded.length > 0) {
6250
+ console.log(
6251
+ `[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`
6252
+ );
6253
+ }
6254
+ modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5800
6255
  modelsToTry = prioritizeNonRateLimited(modelsToTry);
5801
6256
  } else {
5802
- if (modelId && modelId !== FREE_MODEL) {
5803
- modelsToTry = [modelId, FREE_MODEL];
5804
- } else {
5805
- modelsToTry = modelId ? [modelId] : [];
5806
- }
6257
+ modelsToTry = modelId ? [modelId] : [];
6258
+ }
6259
+ if (!modelsToTry.includes(FREE_MODEL)) {
6260
+ modelsToTry.push(FREE_MODEL);
5807
6261
  }
5808
6262
  let upstream;
5809
6263
  let lastError;
@@ -5837,6 +6291,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5837
6291
  if (result.errorStatus === 429) {
5838
6292
  markRateLimited(tryModel);
5839
6293
  }
6294
+ const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
6295
+ result.errorBody || ""
6296
+ );
6297
+ if (isPaymentErr && tryModel !== FREE_MODEL) {
6298
+ const freeIdx = modelsToTry.indexOf(FREE_MODEL);
6299
+ if (freeIdx > i + 1) {
6300
+ console.log(`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
6301
+ i = freeIdx - 1;
6302
+ continue;
6303
+ }
6304
+ }
5840
6305
  console.log(
5841
6306
  `[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`
5842
6307
  );
@@ -5854,6 +6319,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5854
6319
  clearInterval(heartbeatInterval);
5855
6320
  heartbeatInterval = void 0;
5856
6321
  }
6322
+ if (debugMode && headersSentEarly && routingDecision) {
6323
+ const debugComment = `: x-clawrouter-debug profile=${routingProfile ?? "auto"} tier=${routingDecision.tier} model=${actualModelUsed} agentic=${routingDecision.agenticScore?.toFixed(2) ?? "n/a"} confidence=${routingDecision.confidence.toFixed(2)} reasoning=${routingDecision.reasoning}
6324
+
6325
+ `;
6326
+ safeWrite(res, debugComment);
6327
+ }
5857
6328
  if (routingDecision && actualModelUsed !== routingDecision.model) {
5858
6329
  const estimatedInputTokens = Math.ceil(body.length / 4);
5859
6330
  const newCosts = calculateModelCost(
@@ -5872,6 +6343,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5872
6343
  savings: newCosts.savings
5873
6344
  };
5874
6345
  options.onRouted?.(routingDecision);
6346
+ if (effectiveSessionId) {
6347
+ sessionStore.setSession(effectiveSessionId, actualModelUsed, routingDecision.tier);
6348
+ console.log(
6349
+ `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}`
6350
+ );
6351
+ }
5875
6352
  }
5876
6353
  if (!upstream) {
5877
6354
  const rawErrBody = lastError?.body || "All models in fallback chain failed";
@@ -5934,6 +6411,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5934
6411
  const jsonStr = jsonBody.toString();
5935
6412
  try {
5936
6413
  const rsp = JSON.parse(jsonStr);
6414
+ if (rsp.usage && typeof rsp.usage === "object") {
6415
+ const u = rsp.usage;
6416
+ if (typeof u.prompt_tokens === "number") responseInputTokens = u.prompt_tokens;
6417
+ }
5937
6418
  const baseChunk = {
5938
6419
  id: rsp.id ?? `chatcmpl-${Date.now()}`,
5939
6420
  object: "chat.completion.chunk",
@@ -5959,6 +6440,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5959
6440
  `;
5960
6441
  safeWrite(res, roleData);
5961
6442
  responseChunks.push(Buffer.from(roleData));
6443
+ if (balanceFallbackNotice) {
6444
+ const noticeChunk = {
6445
+ ...baseChunk,
6446
+ choices: [{ index, delta: { content: balanceFallbackNotice }, logprobs: null, finish_reason: null }]
6447
+ };
6448
+ const noticeData = `data: ${JSON.stringify(noticeChunk)}
6449
+
6450
+ `;
6451
+ safeWrite(res, noticeData);
6452
+ responseChunks.push(Buffer.from(noticeData));
6453
+ balanceFallbackNotice = void 0;
6454
+ }
5962
6455
  if (content) {
5963
6456
  const contentChunk = {
5964
6457
  ...baseChunk,
@@ -6033,23 +6526,46 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6033
6526
  });
6034
6527
  responseHeaders["x-context-used-kb"] = String(originalContextSizeKB);
6035
6528
  responseHeaders["x-context-limit-kb"] = String(CONTEXT_LIMIT_KB);
6036
- res.writeHead(upstream.status, responseHeaders);
6529
+ if (debugMode && routingDecision) {
6530
+ responseHeaders["x-clawrouter-profile"] = routingProfile ?? "auto";
6531
+ responseHeaders["x-clawrouter-tier"] = routingDecision.tier;
6532
+ responseHeaders["x-clawrouter-model"] = actualModelUsed;
6533
+ responseHeaders["x-clawrouter-confidence"] = routingDecision.confidence.toFixed(2);
6534
+ responseHeaders["x-clawrouter-reasoning"] = routingDecision.reasoning;
6535
+ if (routingDecision.agenticScore !== void 0) {
6536
+ responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
6537
+ }
6538
+ }
6539
+ const bodyParts = [];
6037
6540
  if (upstream.body) {
6038
6541
  const reader = upstream.body.getReader();
6039
6542
  try {
6040
6543
  while (true) {
6041
6544
  const { done, value } = await reader.read();
6042
6545
  if (done) break;
6043
- const chunk = Buffer.from(value);
6044
- safeWrite(res, chunk);
6045
- responseChunks.push(chunk);
6546
+ bodyParts.push(Buffer.from(value));
6046
6547
  }
6047
6548
  } finally {
6048
6549
  reader.releaseLock();
6049
6550
  }
6050
6551
  }
6552
+ let responseBody = Buffer.concat(bodyParts);
6553
+ if (balanceFallbackNotice && responseBody.length > 0) {
6554
+ try {
6555
+ const parsed = JSON.parse(responseBody.toString());
6556
+ if (parsed.choices?.[0]?.message?.content !== void 0) {
6557
+ parsed.choices[0].message.content = balanceFallbackNotice + parsed.choices[0].message.content;
6558
+ responseBody = Buffer.from(JSON.stringify(parsed));
6559
+ }
6560
+ } catch {
6561
+ }
6562
+ balanceFallbackNotice = void 0;
6563
+ }
6564
+ responseHeaders["content-length"] = String(responseBody.length);
6565
+ res.writeHead(upstream.status, responseHeaders);
6566
+ safeWrite(res, responseBody);
6567
+ responseChunks.push(responseBody);
6051
6568
  res.end();
6052
- const responseBody = Buffer.concat(responseChunks);
6053
6569
  deduplicator.complete(dedupKey, {
6054
6570
  status: upstream.status,
6055
6571
  headers: responseHeaders,
@@ -6072,6 +6588,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6072
6588
  if (rspJson.choices?.[0]?.message?.content) {
6073
6589
  accumulatedContent = rspJson.choices[0].message.content;
6074
6590
  }
6591
+ if (rspJson.usage && typeof rspJson.usage === "object") {
6592
+ if (typeof rspJson.usage.prompt_tokens === "number")
6593
+ responseInputTokens = rspJson.usage.prompt_tokens;
6594
+ }
6075
6595
  } catch {
6076
6596
  }
6077
6597
  }
@@ -6097,7 +6617,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6097
6617
  deduplicator.removeInflight(dedupKey);
6098
6618
  balanceMonitor.invalidate();
6099
6619
  if (err instanceof Error && err.name === "AbortError") {
6100
- throw new Error(`Request timed out after ${timeoutMs}ms`);
6620
+ throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });
6101
6621
  }
6102
6622
  throw err;
6103
6623
  }
@@ -6120,7 +6640,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6120
6640
  cost: costWithBuffer,
6121
6641
  baselineCost: baselineWithBuffer,
6122
6642
  savings: accurateCosts.savings,
6123
- latencyMs: Date.now() - startTime
6643
+ latencyMs: Date.now() - startTime,
6644
+ ...responseInputTokens !== void 0 && { inputTokens: responseInputTokens }
6124
6645
  };
6125
6646
  logUsage(entry).catch(() => {
6126
6647
  });
@@ -6139,6 +6660,103 @@ import {
6139
6660
  import { homedir as homedir5 } from "os";
6140
6661
  import { join as join6 } from "path";
6141
6662
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
6663
+
6664
+ // src/partners/registry.ts
6665
+ var PARTNER_SERVICES = [
6666
+ {
6667
+ id: "x_users_lookup",
6668
+ name: "Twitter/X User Lookup",
6669
+ partner: "AttentionVC",
6670
+ description: "ALWAYS use this tool to look up real-time Twitter/X user profiles. Call this when the user asks about any Twitter/X account, username, handle, follower count, verification status, bio, or profile. Do NOT answer Twitter/X user questions from memory \u2014 always fetch live data with this tool. Returns: follower count, verification badge, bio, location, join date. Accepts up to 100 usernames per request (without @ prefix).",
6671
+ proxyPath: "/x/users/lookup",
6672
+ method: "POST",
6673
+ params: [
6674
+ {
6675
+ name: "usernames",
6676
+ type: "string[]",
6677
+ description: 'Array of Twitter/X usernames to look up (without @ prefix). Example: ["elonmusk", "naval"]',
6678
+ required: true
6679
+ }
6680
+ ],
6681
+ pricing: {
6682
+ perUnit: "$0.001",
6683
+ unit: "user",
6684
+ minimum: "$0.01 (10 users)",
6685
+ maximum: "$0.10 (100 users)"
6686
+ },
6687
+ example: {
6688
+ input: { usernames: ["elonmusk", "naval", "balaboris"] },
6689
+ description: "Look up 3 Twitter/X user profiles"
6690
+ }
6691
+ }
6692
+ ];
6693
+ function getPartnerService(id) {
6694
+ return PARTNER_SERVICES.find((s) => s.id === id);
6695
+ }
6696
+
6697
+ // src/partners/tools.ts
6698
+ function buildTool(service, proxyBaseUrl) {
6699
+ const properties = {};
6700
+ const required = [];
6701
+ for (const param of service.params) {
6702
+ const prop = {
6703
+ description: param.description
6704
+ };
6705
+ if (param.type === "string[]") {
6706
+ prop.type = "array";
6707
+ prop.items = { type: "string" };
6708
+ } else {
6709
+ prop.type = param.type;
6710
+ }
6711
+ properties[param.name] = prop;
6712
+ if (param.required) {
6713
+ required.push(param.name);
6714
+ }
6715
+ }
6716
+ return {
6717
+ name: `blockrun_${service.id}`,
6718
+ description: [
6719
+ service.description,
6720
+ "",
6721
+ `Partner: ${service.partner}`,
6722
+ `Pricing: ${service.pricing.perUnit} per ${service.pricing.unit} (min: ${service.pricing.minimum}, max: ${service.pricing.maximum})`
6723
+ ].join("\n"),
6724
+ parameters: {
6725
+ type: "object",
6726
+ properties,
6727
+ required
6728
+ },
6729
+ execute: async (_toolCallId, params) => {
6730
+ const url = `${proxyBaseUrl}/v1${service.proxyPath}`;
6731
+ const response = await fetch(url, {
6732
+ method: service.method,
6733
+ headers: { "Content-Type": "application/json" },
6734
+ body: JSON.stringify(params)
6735
+ });
6736
+ if (!response.ok) {
6737
+ const errText = await response.text().catch(() => "");
6738
+ throw new Error(
6739
+ `Partner API error (${response.status}): ${errText || response.statusText}`
6740
+ );
6741
+ }
6742
+ const data = await response.json();
6743
+ return {
6744
+ content: [
6745
+ {
6746
+ type: "text",
6747
+ text: JSON.stringify(data, null, 2)
6748
+ }
6749
+ ],
6750
+ details: data
6751
+ };
6752
+ }
6753
+ };
6754
+ }
6755
+ function buildPartnerTools(proxyBaseUrl) {
6756
+ return PARTNER_SERVICES.map((service) => buildTool(service, proxyBaseUrl));
6757
+ }
6758
+
6759
+ // src/index.ts
6142
6760
  init_solana_balance();
6143
6761
 
6144
6762
  // src/spend-control.ts
@@ -6449,7 +7067,6 @@ function isRetryable(errorOrResponse, config) {
6449
7067
  }
6450
7068
 
6451
7069
  // src/index.ts
6452
- init_partners();
6453
7070
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
6454
7071
  const start = Date.now();
6455
7072
  while (Date.now() - start < timeoutMs) {
@@ -6586,56 +7203,48 @@ function injectModelsConfig(logger) {
6586
7203
  logger.info("Set default model to blockrun/auto (first install)");
6587
7204
  needsWrite = true;
6588
7205
  }
6589
- const KEY_MODEL_ALIASES = [
6590
- { id: "auto", alias: "auto" },
6591
- { id: "eco", alias: "eco" },
6592
- { id: "premium", alias: "premium" },
6593
- { id: "free", alias: "free" },
6594
- { id: "sonnet", alias: "sonnet-4.6" },
6595
- { id: "opus", alias: "opus" },
6596
- { id: "haiku", alias: "haiku" },
6597
- { id: "gpt5", alias: "gpt5" },
6598
- { id: "codex", alias: "codex" },
6599
- { id: "grok-fast", alias: "grok-fast" },
6600
- { id: "grok-code", alias: "grok-code" },
6601
- { id: "deepseek", alias: "deepseek" },
6602
- { id: "reasoner", alias: "reasoner" },
6603
- { id: "kimi", alias: "kimi" },
6604
- { id: "minimax", alias: "minimax" },
6605
- { id: "gemini", alias: "gemini" }
6606
- ];
6607
- const DEPRECATED_ALIASES = [
6608
- "blockrun/nvidia",
6609
- "blockrun/gpt",
6610
- "blockrun/o3",
6611
- "blockrun/grok",
6612
- "blockrun/mini",
6613
- "blockrun/flash"
6614
- // removed from picker - use gemini instead
7206
+ const TOP_MODELS = [
7207
+ "auto",
7208
+ "free",
7209
+ "eco",
7210
+ "premium",
7211
+ "anthropic/claude-sonnet-4.6",
7212
+ "anthropic/claude-opus-4.6",
7213
+ "anthropic/claude-haiku-4.5",
7214
+ "openai/gpt-5.2",
7215
+ "openai/gpt-4o",
7216
+ "openai/o3",
7217
+ "google/gemini-3.1-pro",
7218
+ "google/gemini-3-flash-preview",
7219
+ "deepseek/deepseek-chat",
7220
+ "moonshot/kimi-k2.5",
7221
+ "xai/grok-3",
7222
+ "minimax/minimax-m2.5"
6615
7223
  ];
6616
- if (!defaults.models) {
7224
+ if (!defaults.models || typeof defaults.models !== "object" || Array.isArray(defaults.models)) {
6617
7225
  defaults.models = {};
6618
7226
  needsWrite = true;
6619
7227
  }
6620
7228
  const allowlist = defaults.models;
6621
- for (const deprecated of DEPRECATED_ALIASES) {
6622
- if (allowlist[deprecated]) {
6623
- delete allowlist[deprecated];
6624
- logger.info(`Removed deprecated model alias: ${deprecated}`);
7229
+ const topSet = new Set(TOP_MODELS.map((id) => `blockrun/${id}`));
7230
+ for (const key of Object.keys(allowlist)) {
7231
+ if (key.startsWith("blockrun/") && !topSet.has(key)) {
7232
+ delete allowlist[key];
6625
7233
  needsWrite = true;
6626
7234
  }
6627
7235
  }
6628
- for (const m of KEY_MODEL_ALIASES) {
6629
- const fullId = `blockrun/${m.id}`;
6630
- const existing = allowlist[fullId];
6631
- if (!existing) {
6632
- allowlist[fullId] = { alias: m.alias };
6633
- needsWrite = true;
6634
- } else if (existing.alias !== m.alias) {
6635
- existing.alias = m.alias;
6636
- needsWrite = true;
7236
+ let addedCount = 0;
7237
+ for (const id of TOP_MODELS) {
7238
+ const key = `blockrun/${id}`;
7239
+ if (!allowlist[key]) {
7240
+ allowlist[key] = {};
7241
+ addedCount++;
6637
7242
  }
6638
7243
  }
7244
+ if (addedCount > 0) {
7245
+ needsWrite = true;
7246
+ logger.info(`Added ${addedCount} models to allowlist (${TOP_MODELS.length} total)`);
7247
+ }
6639
7248
  if (needsWrite) {
6640
7249
  try {
6641
7250
  const tmpPath = `${configPath}.tmp.${process.pid}`;
@@ -6754,18 +7363,18 @@ async function startProxyInBackground(api) {
6754
7363
  activeProxyHandle = proxy;
6755
7364
  api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
6756
7365
  api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
6757
- const startupMonitor = new BalanceMonitor(wallet.address);
6758
- startupMonitor.checkBalance().then((balance) => {
7366
+ const displayAddress = proxy.solanaAddress ?? wallet.address;
7367
+ proxy.balanceMonitor.checkBalance().then((balance) => {
6759
7368
  if (balance.isEmpty) {
6760
- api.logger.info(`Wallet: ${wallet.address} | Balance: $0.00`);
7369
+ api.logger.info(`Wallet: ${displayAddress} | Balance: $0.00`);
6761
7370
  api.logger.info(`Using FREE model. Fund wallet for premium models.`);
6762
7371
  } else if (balance.isLow) {
6763
- api.logger.info(`Wallet: ${wallet.address} | Balance: ${balance.balanceUSD} (low)`);
7372
+ api.logger.info(`Wallet: ${displayAddress} | Balance: ${balance.balanceUSD} (low)`);
6764
7373
  } else {
6765
- api.logger.info(`Wallet: ${wallet.address} | Balance: ${balance.balanceUSD}`);
7374
+ api.logger.info(`Wallet: ${displayAddress} | Balance: ${balance.balanceUSD}`);
6766
7375
  }
6767
7376
  }).catch(() => {
6768
- api.logger.info(`Wallet: ${wallet.address} | Balance: (checking...)`);
7377
+ api.logger.info(`Wallet: ${displayAddress} | Balance: (checking...)`);
6769
7378
  });
6770
7379
  }
6771
7380
  async function createStatsCommand() {
@@ -7000,7 +7609,7 @@ var plugin = {
7000
7609
  name: "ClawRouter",
7001
7610
  description: "Smart LLM router \u2014 30+ models, x402 micropayments, 78% cost savings",
7002
7611
  version: VERSION,
7003
- async register(api) {
7612
+ register(api) {
7004
7613
  const isDisabled = process["env"].CLAWROUTER_DISABLED === "true" || process["env"].CLAWROUTER_DISABLED === "1";
7005
7614
  if (isDisabled) {
7006
7615
  api.logger.info("ClawRouter disabled (CLAWROUTER_DISABLED=true). Using default routing.");
@@ -7029,14 +7638,15 @@ var plugin = {
7029
7638
  };
7030
7639
  api.logger.info("BlockRun provider registered (30+ models via x402)");
7031
7640
  try {
7032
- const { buildPartnerTools: buildPartnerTools2, PARTNER_SERVICES: PARTNER_SERVICES2 } = await Promise.resolve().then(() => (init_partners(), partners_exports));
7033
7641
  const proxyBaseUrl = `http://127.0.0.1:${runtimePort}`;
7034
- const partnerTools = buildPartnerTools2(proxyBaseUrl);
7642
+ const partnerTools = buildPartnerTools(proxyBaseUrl);
7035
7643
  for (const tool of partnerTools) {
7036
7644
  api.registerTool(tool);
7037
7645
  }
7038
7646
  if (partnerTools.length > 0) {
7039
- api.logger.info(`Registered ${partnerTools.length} partner tool(s): ${partnerTools.map((t) => t.name).join(", ")}`);
7647
+ api.logger.info(
7648
+ `Registered ${partnerTools.length} partner tool(s): ${partnerTools.map((t) => t.name).join(", ")}`
7649
+ );
7040
7650
  }
7041
7651
  api.registerCommand({
7042
7652
  name: "partners",
@@ -7044,19 +7654,20 @@ var plugin = {
7044
7654
  acceptsArgs: false,
7045
7655
  requireAuth: false,
7046
7656
  handler: async () => {
7047
- if (PARTNER_SERVICES2.length === 0) {
7657
+ if (PARTNER_SERVICES.length === 0) {
7048
7658
  return { text: "No partner APIs available." };
7049
7659
  }
7050
- const lines = [
7051
- "**Partner APIs** (paid via your ClawRouter wallet)",
7052
- ""
7053
- ];
7054
- for (const svc of PARTNER_SERVICES2) {
7660
+ const lines = ["**Partner APIs** (paid via your ClawRouter wallet)", ""];
7661
+ for (const svc of PARTNER_SERVICES) {
7055
7662
  lines.push(`**${svc.name}** (${svc.partner})`);
7056
7663
  lines.push(` ${svc.description}`);
7057
7664
  lines.push(` Tool: \`${`blockrun_${svc.id}`}\``);
7058
- lines.push(` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`);
7059
- lines.push(` **How to use:** Ask "Look up Twitter user @elonmusk" or "Get info on these X accounts: @naval, @balajis"`);
7665
+ lines.push(
7666
+ ` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`
7667
+ );
7668
+ lines.push(
7669
+ ` **How to use:** Ask "Look up Twitter user @elonmusk" or "Get info on these X accounts: @naval, @balajis"`
7670
+ );
7060
7671
  lines.push("");
7061
7672
  }
7062
7673
  return { text: lines.join("\n") };
@@ -7175,6 +7786,7 @@ export {
7175
7786
  getProxyPort,
7176
7787
  getSessionId,
7177
7788
  getStats,
7789
+ hashRequestContent,
7178
7790
  isAgenticModel,
7179
7791
  isBalanceError,
7180
7792
  isEmptyWalletError,