@blockrun/clawrouter 0.11.12 → 0.11.13

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
@@ -1,3 +1,268 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
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
+ // src/solana-balance.ts
61
+ var solana_balance_exports = {};
62
+ __export(solana_balance_exports, {
63
+ SolanaBalanceMonitor: () => SolanaBalanceMonitor
64
+ });
65
+ import { address as solAddress, createSolanaRpc } from "@solana/kit";
66
+ var SOLANA_USDC_MINT, SOLANA_DEFAULT_RPC, BALANCE_TIMEOUT_MS, CACHE_TTL_MS2, SolanaBalanceMonitor;
67
+ var init_solana_balance = __esm({
68
+ "src/solana-balance.ts"() {
69
+ "use strict";
70
+ SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
71
+ SOLANA_DEFAULT_RPC = "https://api.mainnet-beta.solana.com";
72
+ BALANCE_TIMEOUT_MS = 1e4;
73
+ CACHE_TTL_MS2 = 3e4;
74
+ SolanaBalanceMonitor = class {
75
+ rpc;
76
+ walletAddress;
77
+ cachedBalance = null;
78
+ cachedAt = 0;
79
+ constructor(walletAddress, rpcUrl) {
80
+ this.walletAddress = walletAddress;
81
+ const url = rpcUrl || process["env"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC;
82
+ this.rpc = createSolanaRpc(url);
83
+ }
84
+ async checkBalance() {
85
+ const now = Date.now();
86
+ if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS2) {
87
+ return this.buildInfo(this.cachedBalance);
88
+ }
89
+ const balance = await this.fetchBalance();
90
+ this.cachedBalance = balance;
91
+ this.cachedAt = now;
92
+ return this.buildInfo(balance);
93
+ }
94
+ deductEstimated(amountMicros) {
95
+ if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
96
+ this.cachedBalance -= amountMicros;
97
+ }
98
+ }
99
+ invalidate() {
100
+ this.cachedBalance = null;
101
+ this.cachedAt = 0;
102
+ }
103
+ async refresh() {
104
+ this.invalidate();
105
+ return this.checkBalance();
106
+ }
107
+ getWalletAddress() {
108
+ return this.walletAddress;
109
+ }
110
+ async fetchBalance() {
111
+ const owner = solAddress(this.walletAddress);
112
+ const mint = solAddress(SOLANA_USDC_MINT);
113
+ const controller = new AbortController();
114
+ const timer = setTimeout(() => controller.abort(), BALANCE_TIMEOUT_MS);
115
+ try {
116
+ const response = await this.rpc.getTokenAccountsByOwner(owner, { mint }, { encoding: "jsonParsed" }).send({ abortSignal: controller.signal });
117
+ if (response.value.length === 0) return 0n;
118
+ let total = 0n;
119
+ for (const account of response.value) {
120
+ const parsed = account.account.data;
121
+ total += BigInt(parsed.parsed.info.tokenAmount.amount);
122
+ }
123
+ return total;
124
+ } catch (err) {
125
+ throw new Error(`Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`);
126
+ } finally {
127
+ clearTimeout(timer);
128
+ }
129
+ }
130
+ buildInfo(balance) {
131
+ const dollars = Number(balance) / 1e6;
132
+ return {
133
+ balance,
134
+ balanceUSD: `$${dollars.toFixed(2)}`,
135
+ isLow: balance < 1000000n,
136
+ isEmpty: balance < 100n,
137
+ walletAddress: this.walletAddress
138
+ };
139
+ }
140
+ };
141
+ }
142
+ });
143
+
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
+ }
181
+ });
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
+ };
240
+ }
241
+ function buildPartnerTools(proxyBaseUrl) {
242
+ return PARTNER_SERVICES.map((service) => buildTool(service, proxyBaseUrl));
243
+ }
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"() {
260
+ "use strict";
261
+ init_registry();
262
+ init_tools();
263
+ }
264
+ });
265
+
1
266
  // src/models.ts
2
267
  var MODEL_ALIASES = {
3
268
  // Claude - use newest versions (4.6)
@@ -42,8 +307,6 @@ var MODEL_ALIASES = {
42
307
  // Google
43
308
  gemini: "google/gemini-2.5-pro",
44
309
  flash: "google/gemini-2.5-flash",
45
- "gemini-3.1-pro-preview": "google/gemini-3.1-pro",
46
- "google/gemini-3.1-pro-preview": "google/gemini-3.1-pro",
47
310
  // xAI
48
311
  grok: "xai/grok-3",
49
312
  "grok-fast": "xai/grok-4-fast-reasoning",
@@ -117,8 +380,7 @@ var BLOCKRUN_MODELS = [
117
380
  maxOutput: 128e3,
118
381
  reasoning: true,
119
382
  vision: true,
120
- agentic: true,
121
- toolCalling: true
383
+ agentic: true
122
384
  },
123
385
  {
124
386
  id: "openai/gpt-5-mini",
@@ -127,8 +389,7 @@ var BLOCKRUN_MODELS = [
127
389
  inputPrice: 0.25,
128
390
  outputPrice: 2,
129
391
  contextWindow: 2e5,
130
- maxOutput: 65536,
131
- toolCalling: true
392
+ maxOutput: 65536
132
393
  },
133
394
  {
134
395
  id: "openai/gpt-5-nano",
@@ -137,8 +398,7 @@ var BLOCKRUN_MODELS = [
137
398
  inputPrice: 0.05,
138
399
  outputPrice: 0.4,
139
400
  contextWindow: 128e3,
140
- maxOutput: 32768,
141
- toolCalling: true
401
+ maxOutput: 32768
142
402
  },
143
403
  {
144
404
  id: "openai/gpt-5.2-pro",
@@ -148,8 +408,7 @@ var BLOCKRUN_MODELS = [
148
408
  outputPrice: 168,
149
409
  contextWindow: 4e5,
150
410
  maxOutput: 128e3,
151
- reasoning: true,
152
- toolCalling: true
411
+ reasoning: true
153
412
  },
154
413
  // OpenAI Codex Family
155
414
  {
@@ -160,8 +419,7 @@ var BLOCKRUN_MODELS = [
160
419
  outputPrice: 14,
161
420
  contextWindow: 128e3,
162
421
  maxOutput: 32e3,
163
- agentic: true,
164
- toolCalling: true
422
+ agentic: true
165
423
  },
166
424
  // OpenAI GPT-4 Family
167
425
  {
@@ -172,8 +430,7 @@ var BLOCKRUN_MODELS = [
172
430
  outputPrice: 8,
173
431
  contextWindow: 128e3,
174
432
  maxOutput: 16384,
175
- vision: true,
176
- toolCalling: true
433
+ vision: true
177
434
  },
178
435
  {
179
436
  id: "openai/gpt-4.1-mini",
@@ -182,8 +439,7 @@ var BLOCKRUN_MODELS = [
182
439
  inputPrice: 0.4,
183
440
  outputPrice: 1.6,
184
441
  contextWindow: 128e3,
185
- maxOutput: 16384,
186
- toolCalling: true
442
+ maxOutput: 16384
187
443
  },
188
444
  {
189
445
  id: "openai/gpt-4.1-nano",
@@ -192,8 +448,7 @@ var BLOCKRUN_MODELS = [
192
448
  inputPrice: 0.1,
193
449
  outputPrice: 0.4,
194
450
  contextWindow: 128e3,
195
- maxOutput: 16384,
196
- toolCalling: true
451
+ maxOutput: 16384
197
452
  },
198
453
  {
199
454
  id: "openai/gpt-4o",
@@ -204,8 +459,7 @@ var BLOCKRUN_MODELS = [
204
459
  contextWindow: 128e3,
205
460
  maxOutput: 16384,
206
461
  vision: true,
207
- agentic: true,
208
- toolCalling: true
462
+ agentic: true
209
463
  },
210
464
  {
211
465
  id: "openai/gpt-4o-mini",
@@ -214,8 +468,7 @@ var BLOCKRUN_MODELS = [
214
468
  inputPrice: 0.15,
215
469
  outputPrice: 0.6,
216
470
  contextWindow: 128e3,
217
- maxOutput: 16384,
218
- toolCalling: true
471
+ maxOutput: 16384
219
472
  },
220
473
  // OpenAI O-series (Reasoning)
221
474
  {
@@ -226,8 +479,7 @@ var BLOCKRUN_MODELS = [
226
479
  outputPrice: 60,
227
480
  contextWindow: 2e5,
228
481
  maxOutput: 1e5,
229
- reasoning: true,
230
- toolCalling: true
482
+ reasoning: true
231
483
  },
232
484
  {
233
485
  id: "openai/o1-mini",
@@ -237,8 +489,7 @@ var BLOCKRUN_MODELS = [
237
489
  outputPrice: 4.4,
238
490
  contextWindow: 128e3,
239
491
  maxOutput: 65536,
240
- reasoning: true,
241
- toolCalling: true
492
+ reasoning: true
242
493
  },
243
494
  {
244
495
  id: "openai/o3",
@@ -248,8 +499,7 @@ var BLOCKRUN_MODELS = [
248
499
  outputPrice: 8,
249
500
  contextWindow: 2e5,
250
501
  maxOutput: 1e5,
251
- reasoning: true,
252
- toolCalling: true
502
+ reasoning: true
253
503
  },
254
504
  {
255
505
  id: "openai/o3-mini",
@@ -259,8 +509,7 @@ var BLOCKRUN_MODELS = [
259
509
  outputPrice: 4.4,
260
510
  contextWindow: 128e3,
261
511
  maxOutput: 65536,
262
- reasoning: true,
263
- toolCalling: true
512
+ reasoning: true
264
513
  },
265
514
  {
266
515
  id: "openai/o4-mini",
@@ -270,8 +519,7 @@ var BLOCKRUN_MODELS = [
270
519
  outputPrice: 4.4,
271
520
  contextWindow: 128e3,
272
521
  maxOutput: 65536,
273
- reasoning: true,
274
- toolCalling: true
522
+ reasoning: true
275
523
  },
276
524
  // Anthropic - all Claude models excel at agentic workflows
277
525
  // Use newest versions (4.6) with full provider prefix
@@ -283,9 +531,7 @@ var BLOCKRUN_MODELS = [
283
531
  outputPrice: 5,
284
532
  contextWindow: 2e5,
285
533
  maxOutput: 8192,
286
- vision: true,
287
- agentic: true,
288
- toolCalling: true
534
+ agentic: true
289
535
  },
290
536
  {
291
537
  id: "anthropic/claude-sonnet-4.6",
@@ -296,9 +542,7 @@ var BLOCKRUN_MODELS = [
296
542
  contextWindow: 2e5,
297
543
  maxOutput: 64e3,
298
544
  reasoning: true,
299
- vision: true,
300
- agentic: true,
301
- toolCalling: true
545
+ agentic: true
302
546
  },
303
547
  {
304
548
  id: "anthropic/claude-opus-4.6",
@@ -309,22 +553,19 @@ var BLOCKRUN_MODELS = [
309
553
  contextWindow: 2e5,
310
554
  maxOutput: 32e3,
311
555
  reasoning: true,
312
- vision: true,
313
- agentic: true,
314
- toolCalling: true
556
+ agentic: true
315
557
  },
316
558
  // Google
317
559
  {
318
- id: "google/gemini-3.1-pro",
319
- name: "Gemini 3.1 Pro",
560
+ id: "google/gemini-3.1-pro-preview",
561
+ name: "Gemini 3.1 Pro Preview",
320
562
  version: "3.1",
321
563
  inputPrice: 2,
322
564
  outputPrice: 12,
323
565
  contextWindow: 105e4,
324
566
  maxOutput: 65536,
325
567
  reasoning: true,
326
- vision: true,
327
- toolCalling: true
568
+ vision: true
328
569
  },
329
570
  {
330
571
  id: "google/gemini-3-pro-preview",
@@ -335,8 +576,7 @@ var BLOCKRUN_MODELS = [
335
576
  contextWindow: 105e4,
336
577
  maxOutput: 65536,
337
578
  reasoning: true,
338
- vision: true,
339
- toolCalling: true
579
+ vision: true
340
580
  },
341
581
  {
342
582
  id: "google/gemini-3-flash-preview",
@@ -346,8 +586,7 @@ var BLOCKRUN_MODELS = [
346
586
  outputPrice: 3,
347
587
  contextWindow: 1e6,
348
588
  maxOutput: 65536,
349
- vision: true,
350
- toolCalling: true
589
+ vision: true
351
590
  },
352
591
  {
353
592
  id: "google/gemini-2.5-pro",
@@ -358,8 +597,7 @@ var BLOCKRUN_MODELS = [
358
597
  contextWindow: 105e4,
359
598
  maxOutput: 65536,
360
599
  reasoning: true,
361
- vision: true,
362
- toolCalling: true
600
+ vision: true
363
601
  },
364
602
  {
365
603
  id: "google/gemini-2.5-flash",
@@ -368,9 +606,7 @@ var BLOCKRUN_MODELS = [
368
606
  inputPrice: 0.3,
369
607
  outputPrice: 2.5,
370
608
  contextWindow: 1e6,
371
- maxOutput: 65536,
372
- vision: true,
373
- toolCalling: true
609
+ maxOutput: 65536
374
610
  },
375
611
  {
376
612
  id: "google/gemini-2.5-flash-lite",
@@ -379,8 +615,7 @@ var BLOCKRUN_MODELS = [
379
615
  inputPrice: 0.1,
380
616
  outputPrice: 0.4,
381
617
  contextWindow: 1e6,
382
- maxOutput: 65536,
383
- toolCalling: true
618
+ maxOutput: 65536
384
619
  },
385
620
  // DeepSeek
386
621
  {
@@ -390,8 +625,7 @@ var BLOCKRUN_MODELS = [
390
625
  inputPrice: 0.28,
391
626
  outputPrice: 0.42,
392
627
  contextWindow: 128e3,
393
- maxOutput: 8192,
394
- toolCalling: true
628
+ maxOutput: 8192
395
629
  },
396
630
  {
397
631
  id: "deepseek/deepseek-reasoner",
@@ -401,8 +635,7 @@ var BLOCKRUN_MODELS = [
401
635
  outputPrice: 0.42,
402
636
  contextWindow: 128e3,
403
637
  maxOutput: 8192,
404
- reasoning: true,
405
- toolCalling: true
638
+ reasoning: true
406
639
  },
407
640
  // Moonshot / Kimi - optimized for agentic workflows
408
641
  {
@@ -415,8 +648,7 @@ var BLOCKRUN_MODELS = [
415
648
  maxOutput: 8192,
416
649
  reasoning: true,
417
650
  vision: true,
418
- agentic: true,
419
- toolCalling: true
651
+ agentic: true
420
652
  },
421
653
  // xAI / Grok
422
654
  {
@@ -427,8 +659,7 @@ var BLOCKRUN_MODELS = [
427
659
  outputPrice: 15,
428
660
  contextWindow: 131072,
429
661
  maxOutput: 16384,
430
- reasoning: true,
431
- toolCalling: true
662
+ reasoning: true
432
663
  },
433
664
  // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead
434
665
  {
@@ -438,8 +669,7 @@ var BLOCKRUN_MODELS = [
438
669
  inputPrice: 0.3,
439
670
  outputPrice: 0.5,
440
671
  contextWindow: 131072,
441
- maxOutput: 16384,
442
- toolCalling: true
672
+ maxOutput: 16384
443
673
  },
444
674
  // xAI Grok 4 Family - Ultra-cheap fast models
445
675
  {
@@ -450,8 +680,7 @@ var BLOCKRUN_MODELS = [
450
680
  outputPrice: 0.5,
451
681
  contextWindow: 131072,
452
682
  maxOutput: 16384,
453
- reasoning: true,
454
- toolCalling: true
683
+ reasoning: true
455
684
  },
456
685
  {
457
686
  id: "xai/grok-4-fast-non-reasoning",
@@ -460,8 +689,7 @@ var BLOCKRUN_MODELS = [
460
689
  inputPrice: 0.2,
461
690
  outputPrice: 0.5,
462
691
  contextWindow: 131072,
463
- maxOutput: 16384,
464
- toolCalling: true
692
+ maxOutput: 16384
465
693
  },
466
694
  {
467
695
  id: "xai/grok-4-1-fast-reasoning",
@@ -471,8 +699,7 @@ var BLOCKRUN_MODELS = [
471
699
  outputPrice: 0.5,
472
700
  contextWindow: 131072,
473
701
  maxOutput: 16384,
474
- reasoning: true,
475
- toolCalling: true
702
+ reasoning: true
476
703
  },
477
704
  {
478
705
  id: "xai/grok-4-1-fast-non-reasoning",
@@ -481,8 +708,7 @@ var BLOCKRUN_MODELS = [
481
708
  inputPrice: 0.2,
482
709
  outputPrice: 0.5,
483
710
  contextWindow: 131072,
484
- maxOutput: 16384,
485
- toolCalling: true
711
+ maxOutput: 16384
486
712
  },
487
713
  {
488
714
  id: "xai/grok-code-fast-1",
@@ -491,10 +717,9 @@ var BLOCKRUN_MODELS = [
491
717
  inputPrice: 0.2,
492
718
  outputPrice: 1.5,
493
719
  contextWindow: 131072,
494
- maxOutput: 16384
495
- // toolCalling intentionally omitted: outputs tool calls as plain text JSON,
496
- // not OpenAI-compatible structured function calls. Will be skipped when
497
- // request has tools to prevent the "talking to itself" bug.
720
+ maxOutput: 16384,
721
+ agentic: true
722
+ // Good for coding tasks
498
723
  },
499
724
  {
500
725
  id: "xai/grok-4-0709",
@@ -504,8 +729,7 @@ var BLOCKRUN_MODELS = [
504
729
  outputPrice: 1.5,
505
730
  contextWindow: 131072,
506
731
  maxOutput: 16384,
507
- reasoning: true,
508
- toolCalling: true
732
+ reasoning: true
509
733
  },
510
734
  {
511
735
  id: "xai/grok-2-vision",
@@ -515,8 +739,7 @@ var BLOCKRUN_MODELS = [
515
739
  outputPrice: 10,
516
740
  contextWindow: 131072,
517
741
  maxOutput: 16384,
518
- vision: true,
519
- toolCalling: true
742
+ vision: true
520
743
  },
521
744
  // MiniMax
522
745
  {
@@ -528,8 +751,7 @@ var BLOCKRUN_MODELS = [
528
751
  contextWindow: 204800,
529
752
  maxOutput: 16384,
530
753
  reasoning: true,
531
- agentic: true,
532
- toolCalling: true
754
+ agentic: true
533
755
  },
534
756
  // NVIDIA - Free/cheap models
535
757
  {
@@ -540,8 +762,6 @@ var BLOCKRUN_MODELS = [
540
762
  outputPrice: 0,
541
763
  contextWindow: 128e3,
542
764
  maxOutput: 16384
543
- // toolCalling intentionally omitted: free model, structured function
544
- // calling support unverified. Excluded from tool-heavy routing paths.
545
765
  },
546
766
  {
547
767
  id: "nvidia/kimi-k2.5",
@@ -550,8 +770,7 @@ var BLOCKRUN_MODELS = [
550
770
  inputPrice: 0.55,
551
771
  outputPrice: 2.5,
552
772
  contextWindow: 262144,
553
- maxOutput: 16384,
554
- toolCalling: true
773
+ maxOutput: 16384
555
774
  }
556
775
  ];
557
776
  function toOpenClawModel(m) {
@@ -596,16 +815,6 @@ function isAgenticModel(modelId) {
596
815
  function getAgenticModels() {
597
816
  return BLOCKRUN_MODELS.filter((m) => m.agentic).map((m) => m.id);
598
817
  }
599
- function supportsToolCalling(modelId) {
600
- const normalized = modelId.replace("blockrun/", "");
601
- const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
602
- return model?.toolCalling ?? false;
603
- }
604
- function supportsVision(modelId) {
605
- const normalized = modelId.replace("blockrun/", "");
606
- const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
607
- return model?.vision ?? false;
608
- }
609
818
  function getModelContextWindow(modelId) {
610
819
  const normalized = modelId.replace("blockrun/", "");
611
820
  const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
@@ -644,271 +853,72 @@ var blockrunProvider = {
644
853
  // src/proxy.ts
645
854
  import { createServer } from "http";
646
855
  import { finished } from "stream";
647
- import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
856
+ import { createPublicClient as createPublicClient2, http as http2 } from "viem";
857
+ import { base as base2 } from "viem/chains";
858
+ import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
859
+ import { x402Client } from "@x402/fetch";
648
860
 
649
- // src/x402.ts
650
- import { signTypedData, privateKeyToAccount } from "viem/accounts";
651
-
652
- // src/payment-cache.ts
861
+ // src/payment-preauth.ts
862
+ import { x402HTTPClient } from "@x402/fetch";
653
863
  var DEFAULT_TTL_MS = 36e5;
654
- var PaymentCache = class {
655
- cache = /* @__PURE__ */ new Map();
656
- ttlMs;
657
- constructor(ttlMs = DEFAULT_TTL_MS) {
658
- this.ttlMs = ttlMs;
659
- }
660
- /** Get cached payment params for an endpoint path. */
661
- get(endpointPath) {
662
- const entry = this.cache.get(endpointPath);
663
- if (!entry) return void 0;
664
- if (Date.now() - entry.cachedAt > this.ttlMs) {
665
- this.cache.delete(endpointPath);
666
- return void 0;
667
- }
668
- return entry;
669
- }
670
- /** Cache payment params from a 402 response. */
671
- set(endpointPath, params) {
672
- this.cache.set(endpointPath, { ...params, cachedAt: Date.now() });
673
- }
674
- /** Invalidate cache for an endpoint (e.g., if payTo changed). */
675
- invalidate(endpointPath) {
676
- this.cache.delete(endpointPath);
677
- }
678
- };
679
-
680
- // src/x402.ts
681
- var BASE_CHAIN_ID = 8453;
682
- var BASE_SEPOLIA_CHAIN_ID = 84532;
683
- var DEFAULT_TOKEN_NAME = "USD Coin";
684
- var DEFAULT_TOKEN_VERSION = "2";
685
- var DEFAULT_NETWORK = "eip155:8453";
686
- var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
687
- var TRANSFER_TYPES = {
688
- TransferWithAuthorization: [
689
- { name: "from", type: "address" },
690
- { name: "to", type: "address" },
691
- { name: "value", type: "uint256" },
692
- { name: "validAfter", type: "uint256" },
693
- { name: "validBefore", type: "uint256" },
694
- { name: "nonce", type: "bytes32" }
695
- ]
696
- };
697
- function createNonce() {
698
- const bytes = new Uint8Array(32);
699
- crypto.getRandomValues(bytes);
700
- return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
701
- }
702
- function decodeBase64Json(value) {
703
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
704
- const padding = (4 - normalized.length % 4) % 4;
705
- const padded = normalized + "=".repeat(padding);
706
- const decoded = Buffer.from(padded, "base64").toString("utf8");
707
- return JSON.parse(decoded);
708
- }
709
- function encodeBase64Json(value) {
710
- return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
711
- }
712
- function parsePaymentRequired(headerValue) {
713
- return decodeBase64Json(headerValue);
714
- }
715
- function normalizeNetwork(network) {
716
- if (!network || network.trim().length === 0) {
717
- return DEFAULT_NETWORK;
718
- }
719
- return network.trim().toLowerCase();
720
- }
721
- function resolveChainId(network) {
722
- const eip155Match = network.match(/^eip155:(\d+)$/i);
723
- if (eip155Match) {
724
- const parsed = Number.parseInt(eip155Match[1], 10);
725
- if (Number.isFinite(parsed) && parsed > 0) {
726
- return parsed;
727
- }
728
- }
729
- if (network === "base") return BASE_CHAIN_ID;
730
- if (network === "base-sepolia") return BASE_SEPOLIA_CHAIN_ID;
731
- return BASE_CHAIN_ID;
732
- }
733
- function parseHexAddress(value) {
734
- if (!value) return void 0;
735
- const direct = value.match(/^0x[a-fA-F0-9]{40}$/);
736
- if (direct) {
737
- return direct[0];
738
- }
739
- const caipSuffix = value.match(/0x[a-fA-F0-9]{40}$/);
740
- if (caipSuffix) {
741
- return caipSuffix[0];
742
- }
743
- return void 0;
744
- }
745
- function requireHexAddress(value, field) {
746
- const parsed = parseHexAddress(value);
747
- if (!parsed) {
748
- throw new Error(`Invalid ${field} in payment requirements: ${String(value)}`);
749
- }
750
- return parsed;
751
- }
752
- function setPaymentHeaders(headers, payload) {
753
- headers.set("payment-signature", payload);
754
- headers.set("x-payment", payload);
755
- }
756
- async function createPaymentPayload(privateKey, fromAddress, option, amount, requestUrl, resource) {
757
- const network = normalizeNetwork(option.network);
758
- const chainId = resolveChainId(network);
759
- const recipient = requireHexAddress(option.payTo, "payTo");
760
- const verifyingContract = requireHexAddress(option.asset, "asset");
761
- const maxTimeoutSeconds = typeof option.maxTimeoutSeconds === "number" && option.maxTimeoutSeconds > 0 ? Math.floor(option.maxTimeoutSeconds) : DEFAULT_MAX_TIMEOUT_SECONDS;
762
- const now = Math.floor(Date.now() / 1e3);
763
- const validAfter = now - 600;
764
- const validBefore = now + maxTimeoutSeconds;
765
- const nonce = createNonce();
766
- const signature = await signTypedData({
767
- privateKey,
768
- domain: {
769
- name: option.extra?.name || DEFAULT_TOKEN_NAME,
770
- version: option.extra?.version || DEFAULT_TOKEN_VERSION,
771
- chainId,
772
- verifyingContract
773
- },
774
- types: TRANSFER_TYPES,
775
- primaryType: "TransferWithAuthorization",
776
- message: {
777
- from: fromAddress,
778
- to: recipient,
779
- value: BigInt(amount),
780
- validAfter: BigInt(validAfter),
781
- validBefore: BigInt(validBefore),
782
- nonce
783
- }
784
- });
785
- const paymentData = {
786
- x402Version: 2,
787
- resource: {
788
- url: resource?.url || requestUrl,
789
- description: resource?.description || "BlockRun AI API call",
790
- mimeType: "application/json"
791
- },
792
- accepted: {
793
- scheme: option.scheme,
794
- network,
795
- amount,
796
- asset: option.asset,
797
- payTo: option.payTo,
798
- maxTimeoutSeconds: option.maxTimeoutSeconds,
799
- extra: option.extra
800
- },
801
- payload: {
802
- signature,
803
- authorization: {
804
- from: fromAddress,
805
- to: recipient,
806
- value: amount,
807
- validAfter: validAfter.toString(),
808
- validBefore: validBefore.toString(),
809
- nonce
810
- }
811
- },
812
- extensions: {}
813
- };
814
- return encodeBase64Json(paymentData);
815
- }
816
- function createPaymentFetch(privateKey) {
817
- const account = privateKeyToAccount(privateKey);
818
- const walletAddress = account.address;
819
- const paymentCache = new PaymentCache();
820
- const payFetch = async (input, init, preAuth) => {
821
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
822
- const endpointPath = new URL(url).pathname;
823
- const cached = paymentCache.get(endpointPath);
824
- if (cached && preAuth?.estimatedAmount) {
825
- const paymentPayload = await createPaymentPayload(
826
- privateKey,
827
- walletAddress,
828
- {
829
- scheme: cached.scheme,
830
- network: cached.network,
831
- asset: cached.asset,
832
- payTo: cached.payTo,
833
- maxTimeoutSeconds: cached.maxTimeoutSeconds,
834
- extra: cached.extra
835
- },
836
- preAuth.estimatedAmount,
837
- url,
838
- {
839
- url: cached.resourceUrl,
840
- description: cached.resourceDescription
864
+ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS) {
865
+ const httpClient = new x402HTTPClient(client);
866
+ const cache = /* @__PURE__ */ new Map();
867
+ return async (input, init) => {
868
+ const request = new Request(input, init);
869
+ const urlPath = new URL(request.url).pathname;
870
+ const cached = cache.get(urlPath);
871
+ if (cached && Date.now() - cached.cachedAt < ttlMs) {
872
+ try {
873
+ const payload2 = await client.createPaymentPayload(cached.paymentRequired);
874
+ const headers = httpClient.encodePaymentSignatureHeader(payload2);
875
+ const preAuthRequest = request.clone();
876
+ for (const [key, value] of Object.entries(headers)) {
877
+ preAuthRequest.headers.set(key, value);
841
878
  }
842
- );
843
- const preAuthHeaders = new Headers(init?.headers);
844
- setPaymentHeaders(preAuthHeaders, paymentPayload);
845
- const response2 = await fetch(input, { ...init, headers: preAuthHeaders });
846
- if (response2.status !== 402) {
847
- return response2;
848
- }
849
- const paymentHeader2 = response2.headers.get("x-payment-required");
850
- if (paymentHeader2) {
851
- return handle402(input, init, url, endpointPath, paymentHeader2);
852
- }
853
- paymentCache.invalidate(endpointPath);
854
- const cleanResponse = await fetch(input, init);
855
- if (cleanResponse.status !== 402) {
856
- return cleanResponse;
857
- }
858
- const cleanHeader = cleanResponse.headers.get("x-payment-required");
859
- if (!cleanHeader) {
860
- throw new Error("402 response missing x-payment-required header");
879
+ const response2 = await baseFetch(preAuthRequest);
880
+ if (response2.status !== 402) {
881
+ return response2;
882
+ }
883
+ cache.delete(urlPath);
884
+ } catch {
885
+ cache.delete(urlPath);
861
886
  }
862
- return handle402(input, init, url, endpointPath, cleanHeader);
863
887
  }
864
- const response = await fetch(input, init);
888
+ const clonedRequest = request.clone();
889
+ const response = await baseFetch(request);
865
890
  if (response.status !== 402) {
866
891
  return response;
867
892
  }
868
- const paymentHeader = response.headers.get("x-payment-required");
869
- if (!paymentHeader) {
870
- throw new Error("402 response missing x-payment-required header");
893
+ let paymentRequired;
894
+ try {
895
+ const getHeader = (name) => response.headers.get(name);
896
+ let body;
897
+ try {
898
+ const responseText = await response.text();
899
+ if (responseText) body = JSON.parse(responseText);
900
+ } catch {
901
+ }
902
+ paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
903
+ cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });
904
+ } catch (error) {
905
+ throw new Error(
906
+ `Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`
907
+ );
908
+ }
909
+ const payload = await client.createPaymentPayload(paymentRequired);
910
+ const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
911
+ for (const [key, value] of Object.entries(paymentHeaders)) {
912
+ clonedRequest.headers.set(key, value);
871
913
  }
872
- return handle402(input, init, url, endpointPath, paymentHeader);
914
+ return baseFetch(clonedRequest);
873
915
  };
874
- async function handle402(input, init, url, endpointPath, paymentHeader) {
875
- const paymentRequired = parsePaymentRequired(paymentHeader);
876
- const option = paymentRequired.accepts?.[0];
877
- if (!option) {
878
- throw new Error("No payment options in 402 response");
879
- }
880
- const amount = option.amount || option.maxAmountRequired;
881
- if (!amount) {
882
- throw new Error("No amount in payment requirements");
883
- }
884
- paymentCache.set(endpointPath, {
885
- payTo: option.payTo,
886
- asset: option.asset,
887
- scheme: option.scheme,
888
- network: option.network,
889
- extra: option.extra,
890
- maxTimeoutSeconds: option.maxTimeoutSeconds,
891
- resourceUrl: paymentRequired.resource?.url,
892
- resourceDescription: paymentRequired.resource?.description
893
- });
894
- const paymentPayload = await createPaymentPayload(
895
- privateKey,
896
- walletAddress,
897
- option,
898
- amount,
899
- url,
900
- paymentRequired.resource
901
- );
902
- const retryHeaders = new Headers(init?.headers);
903
- setPaymentHeaders(retryHeaders, paymentPayload);
904
- return fetch(input, {
905
- ...init,
906
- headers: retryHeaders
907
- });
908
- }
909
- return { fetch: payFetch, cache: paymentCache };
910
916
  }
911
917
 
918
+ // src/proxy.ts
919
+ import { registerExactEvmScheme } from "@x402/evm/exact/client";
920
+ import { toClientEvmSigner } from "@x402/evm";
921
+
912
922
  // src/router/rules.ts
913
923
  function scoreTokenCount(estimatedTokens, thresholds) {
914
924
  if (estimatedTokens < thresholds.simple) {
@@ -997,18 +1007,20 @@ function scoreAgenticTask(text, keywords) {
997
1007
  };
998
1008
  }
999
1009
  function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1010
+ const text = `${systemPrompt ?? ""} ${prompt}`.toLowerCase();
1000
1011
  const userText = prompt.toLowerCase();
1001
1012
  const dimensions = [
1002
- // Token count uses total estimated tokens (system + user) — context size matters for model selection
1013
+ // Original 8 dimensions
1003
1014
  scoreTokenCount(estimatedTokens, config.tokenCountThresholds),
1004
1015
  scoreKeywordMatch(
1005
- userText,
1016
+ text,
1006
1017
  config.codeKeywords,
1007
1018
  "codePresence",
1008
1019
  "code",
1009
1020
  { low: 1, high: 2 },
1010
1021
  { none: 0, low: 0.5, high: 1 }
1011
1022
  ),
1023
+ // Reasoning markers use USER prompt only — system prompt "step by step" shouldn't trigger reasoning
1012
1024
  scoreKeywordMatch(
1013
1025
  userText,
1014
1026
  config.reasoningKeywords,
@@ -1018,7 +1030,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1018
1030
  { none: 0, low: 0.7, high: 1 }
1019
1031
  ),
1020
1032
  scoreKeywordMatch(
1021
- userText,
1033
+ text,
1022
1034
  config.technicalKeywords,
1023
1035
  "technicalTerms",
1024
1036
  "technical",
@@ -1026,7 +1038,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1026
1038
  { none: 0, low: 0.5, high: 1 }
1027
1039
  ),
1028
1040
  scoreKeywordMatch(
1029
- userText,
1041
+ text,
1030
1042
  config.creativeKeywords,
1031
1043
  "creativeMarkers",
1032
1044
  "creative",
@@ -1034,18 +1046,18 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1034
1046
  { none: 0, low: 0.5, high: 0.7 }
1035
1047
  ),
1036
1048
  scoreKeywordMatch(
1037
- userText,
1049
+ text,
1038
1050
  config.simpleKeywords,
1039
1051
  "simpleIndicators",
1040
1052
  "simple",
1041
1053
  { low: 1, high: 2 },
1042
1054
  { none: 0, low: -1, high: -1 }
1043
1055
  ),
1044
- scoreMultiStep(userText),
1056
+ scoreMultiStep(text),
1045
1057
  scoreQuestionComplexity(prompt),
1046
1058
  // 6 new dimensions
1047
1059
  scoreKeywordMatch(
1048
- userText,
1060
+ text,
1049
1061
  config.imperativeVerbs,
1050
1062
  "imperativeVerbs",
1051
1063
  "imperative",
@@ -1053,7 +1065,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1053
1065
  { none: 0, low: 0.3, high: 0.5 }
1054
1066
  ),
1055
1067
  scoreKeywordMatch(
1056
- userText,
1068
+ text,
1057
1069
  config.constraintIndicators,
1058
1070
  "constraintCount",
1059
1071
  "constraints",
@@ -1061,7 +1073,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1061
1073
  { none: 0, low: 0.3, high: 0.7 }
1062
1074
  ),
1063
1075
  scoreKeywordMatch(
1064
- userText,
1076
+ text,
1065
1077
  config.outputFormatKeywords,
1066
1078
  "outputFormat",
1067
1079
  "format",
@@ -1069,7 +1081,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1069
1081
  { none: 0, low: 0.4, high: 0.7 }
1070
1082
  ),
1071
1083
  scoreKeywordMatch(
1072
- userText,
1084
+ text,
1073
1085
  config.referenceKeywords,
1074
1086
  "referenceComplexity",
1075
1087
  "references",
@@ -1077,7 +1089,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1077
1089
  { none: 0, low: 0.3, high: 0.5 }
1078
1090
  ),
1079
1091
  scoreKeywordMatch(
1080
- userText,
1092
+ text,
1081
1093
  config.negationKeywords,
1082
1094
  "negationComplexity",
1083
1095
  "negation",
@@ -1085,7 +1097,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1085
1097
  { none: 0, low: 0.3, high: 0.5 }
1086
1098
  ),
1087
1099
  scoreKeywordMatch(
1088
- userText,
1100
+ text,
1089
1101
  config.domainSpecificKeywords,
1090
1102
  "domainSpecificity",
1091
1103
  "domain-specific",
@@ -1117,8 +1129,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1117
1129
  tier: "REASONING",
1118
1130
  confidence: Math.max(confidence2, 0.85),
1119
1131
  signals,
1120
- agenticScore,
1121
- dimensions
1132
+ agenticScore
1122
1133
  };
1123
1134
  }
1124
1135
  const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
@@ -1142,9 +1153,9 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1142
1153
  }
1143
1154
  const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
1144
1155
  if (confidence < config.confidenceThreshold) {
1145
- return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };
1156
+ return { score: weightedScore, tier: null, confidence, signals, agenticScore };
1146
1157
  }
1147
- return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };
1158
+ return { score: weightedScore, tier, confidence, signals, agenticScore };
1148
1159
  }
1149
1160
  function calibrateConfidence(distance, steepness) {
1150
1161
  return 1 / (1 + Math.exp(-steepness * distance));
@@ -1152,9 +1163,7 @@ function calibrateConfidence(distance, steepness) {
1152
1163
 
1153
1164
  // src/router/selector.ts
1154
1165
  var BASELINE_MODEL_ID = "anthropic/claude-opus-4.6";
1155
- var BASELINE_INPUT_PRICE = 5;
1156
- var BASELINE_OUTPUT_PRICE = 25;
1157
- function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
1166
+ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
1158
1167
  const tierConfig = tierConfigs[tier];
1159
1168
  const model = tierConfig.primary;
1160
1169
  const pricing = modelPricing.get(model);
@@ -1164,8 +1173,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
1164
1173
  const outputCost = maxOutputTokens / 1e6 * outputPrice;
1165
1174
  const costEstimate = inputCost + outputCost;
1166
1175
  const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
1167
- const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
1168
- const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
1176
+ const opusInputPrice = opusPricing?.inputPrice ?? 0;
1177
+ const opusOutputPrice = opusPricing?.outputPrice ?? 0;
1169
1178
  const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
1170
1179
  const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
1171
1180
  const baselineCost = baselineInput + baselineOutput;
@@ -1178,8 +1187,7 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
1178
1187
  reasoning,
1179
1188
  costEstimate,
1180
1189
  baselineCost,
1181
- savings,
1182
- ...agenticScore !== void 0 && { agenticScore }
1190
+ savings
1183
1191
  };
1184
1192
  }
1185
1193
  function getFallbackChain(tier, tierConfigs) {
@@ -1194,24 +1202,14 @@ function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutput
1194
1202
  const outputCost = maxOutputTokens / 1e6 * outputPrice;
1195
1203
  const costEstimate = inputCost + outputCost;
1196
1204
  const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
1197
- const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
1198
- const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
1205
+ const opusInputPrice = opusPricing?.inputPrice ?? 0;
1206
+ const opusOutputPrice = opusPricing?.outputPrice ?? 0;
1199
1207
  const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
1200
1208
  const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
1201
1209
  const baselineCost = baselineInput + baselineOutput;
1202
1210
  const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
1203
1211
  return { costEstimate, baselineCost, savings };
1204
1212
  }
1205
- function filterByToolCalling(models, hasTools, supportsToolCalling2) {
1206
- if (!hasTools) return models;
1207
- const filtered = models.filter(supportsToolCalling2);
1208
- return filtered.length > 0 ? filtered : models;
1209
- }
1210
- function filterByVision(models, hasVision, supportsVision2) {
1211
- if (!hasVision) return models;
1212
- const filtered = models.filter(supportsVision2);
1213
- return filtered.length > 0 ? filtered : models;
1214
- }
1215
1213
  function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
1216
1214
  const fullChain = getFallbackChain(tier, tierConfigs);
1217
1215
  const filtered = fullChain.filter((modelId) => {
@@ -2267,18 +2265,18 @@ var DEFAULT_ROUTING_CONFIG = {
2267
2265
  ]
2268
2266
  },
2269
2267
  MEDIUM: {
2270
- primary: "moonshot/kimi-k2.5",
2271
- // $0.50/$2.40 - strong tool use, proper function call format
2268
+ primary: "xai/grok-code-fast-1",
2269
+ // Code specialist, $0.20/$1.50
2272
2270
  fallback: [
2273
- "deepseek/deepseek-chat",
2274
2271
  "google/gemini-2.5-flash-lite",
2275
2272
  // 1M context, ultra cheap ($0.10/$0.40)
2273
+ "deepseek/deepseek-chat",
2276
2274
  "xai/grok-4-1-fast-non-reasoning"
2277
2275
  // Upgraded Grok 4.1
2278
2276
  ]
2279
2277
  },
2280
2278
  COMPLEX: {
2281
- primary: "google/gemini-3.1-pro",
2279
+ primary: "google/gemini-3.1-pro-preview",
2282
2280
  // Newest Gemini 3.1 - upgraded from 3.0
2283
2281
  fallback: [
2284
2282
  "google/gemini-2.5-flash-lite",
@@ -2338,7 +2336,7 @@ var DEFAULT_ROUTING_CONFIG = {
2338
2336
  fallback: [
2339
2337
  "anthropic/claude-haiku-4.5",
2340
2338
  "google/gemini-2.5-flash-lite",
2341
- "deepseek/deepseek-chat"
2339
+ "xai/grok-code-fast-1"
2342
2340
  ]
2343
2341
  },
2344
2342
  MEDIUM: {
@@ -2358,7 +2356,7 @@ var DEFAULT_ROUTING_CONFIG = {
2358
2356
  "openai/gpt-5.2-codex",
2359
2357
  "anthropic/claude-opus-4.6",
2360
2358
  "anthropic/claude-sonnet-4.6",
2361
- "google/gemini-3.1-pro",
2359
+ "google/gemini-3.1-pro-preview",
2362
2360
  // Newest Gemini
2363
2361
  "google/gemini-3-pro-preview",
2364
2362
  "moonshot/kimi-k2.5"
@@ -2389,13 +2387,9 @@ var DEFAULT_ROUTING_CONFIG = {
2389
2387
  ]
2390
2388
  },
2391
2389
  MEDIUM: {
2392
- primary: "moonshot/kimi-k2.5",
2393
- // $0.50/$2.40 - strong tool use, handles function calls correctly
2394
- fallback: [
2395
- "anthropic/claude-haiku-4.5",
2396
- "deepseek/deepseek-chat",
2397
- "xai/grok-4-1-fast-non-reasoning"
2398
- ]
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"]
2399
2393
  },
2400
2394
  COMPLEX: {
2401
2395
  primary: "anthropic/claude-sonnet-4.6",
@@ -2403,7 +2397,7 @@ var DEFAULT_ROUTING_CONFIG = {
2403
2397
  "anthropic/claude-opus-4.6",
2404
2398
  // Latest Opus - best agentic
2405
2399
  "openai/gpt-5.2",
2406
- "google/gemini-3.1-pro",
2400
+ "google/gemini-3.1-pro-preview",
2407
2401
  // Newest Gemini
2408
2402
  "google/gemini-3-pro-preview",
2409
2403
  "xai/grok-4-0709"
@@ -2450,7 +2444,6 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2450
2444
  tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
2451
2445
  profileSuffix = useAgenticTiers ? " | agentic" : "";
2452
2446
  }
2453
- const agenticScoreValue = ruleResult.agenticScore;
2454
2447
  if (estimatedTokens > config.overrides.maxTokensForceComplex) {
2455
2448
  return selectModel(
2456
2449
  "COMPLEX",
@@ -2461,8 +2454,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2461
2454
  modelPricing,
2462
2455
  estimatedTokens,
2463
2456
  maxOutputTokens,
2464
- routingProfile,
2465
- agenticScoreValue
2457
+ routingProfile
2466
2458
  );
2467
2459
  }
2468
2460
  const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
@@ -2496,8 +2488,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2496
2488
  modelPricing,
2497
2489
  estimatedTokens,
2498
2490
  maxOutputTokens,
2499
- routingProfile,
2500
- agenticScoreValue
2491
+ routingProfile
2501
2492
  );
2502
2493
  }
2503
2494
 
@@ -3252,25 +3243,211 @@ var BalanceMonitor = class {
3252
3243
  }
3253
3244
  };
3254
3245
 
3255
- // src/compression/types.ts
3256
- var DEFAULT_COMPRESSION_CONFIG = {
3257
- enabled: true,
3258
- preserveRaw: true,
3259
- layers: {
3260
- deduplication: true,
3261
- // Safe: removes duplicate messages
3262
- whitespace: true,
3263
- // Safe: normalizes whitespace
3264
- dictionary: false,
3265
- // DISABLED: requires model to understand codebook
3266
- paths: false,
3267
- // DISABLED: requires model to understand path codes
3268
- jsonCompact: true,
3269
- // Safe: just removes JSON whitespace
3270
- observation: false,
3271
- // DISABLED: may lose important context
3272
- dynamicCodebook: false
3273
- // DISABLED: requires model to understand codes
3246
+ // src/auth.ts
3247
+ import { writeFile, mkdir as mkdir2 } from "fs/promises";
3248
+ init_wallet();
3249
+ import { join as join4 } from "path";
3250
+ import { homedir as homedir3 } from "os";
3251
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
3252
+ var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
3253
+ var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
3254
+ var MNEMONIC_FILE = join4(WALLET_DIR, "mnemonic");
3255
+ var CHAIN_FILE = join4(WALLET_DIR, "payment-chain");
3256
+ async function loadSavedWallet() {
3257
+ try {
3258
+ const key = (await readTextFile(WALLET_FILE)).trim();
3259
+ if (key.startsWith("0x") && key.length === 66) {
3260
+ console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
3261
+ return key;
3262
+ }
3263
+ console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
3264
+ console.error(`[ClawRouter] File: ${WALLET_FILE}`);
3265
+ 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`);
3267
+ throw new Error(
3268
+ `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
+ );
3270
+ } catch (err) {
3271
+ if (err.code !== "ENOENT") {
3272
+ if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
3273
+ throw err;
3274
+ }
3275
+ console.error(
3276
+ `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
3277
+ );
3278
+ 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.`
3280
+ );
3281
+ }
3282
+ }
3283
+ return void 0;
3284
+ }
3285
+ async function loadMnemonic() {
3286
+ try {
3287
+ const mnemonic = (await readTextFile(MNEMONIC_FILE)).trim();
3288
+ if (mnemonic && isValidMnemonic(mnemonic)) {
3289
+ return mnemonic;
3290
+ }
3291
+ console.warn(`[ClawRouter] \u26A0 Mnemonic file exists but has invalid format \u2014 ignoring`);
3292
+ return void 0;
3293
+ } catch (err) {
3294
+ if (err.code !== "ENOENT") {
3295
+ console.warn(`[ClawRouter] \u26A0 Cannot read mnemonic file \u2014 ignoring`);
3296
+ }
3297
+ }
3298
+ return void 0;
3299
+ }
3300
+ async function saveMnemonic(mnemonic) {
3301
+ await mkdir2(WALLET_DIR, { recursive: true });
3302
+ await writeFile(MNEMONIC_FILE, mnemonic + "\n", { mode: 384 });
3303
+ }
3304
+ async function generateAndSaveWallet() {
3305
+ const existingMnemonic = await loadMnemonic();
3306
+ if (existingMnemonic) {
3307
+ throw new Error(
3308
+ `Mnemonic file exists at ${MNEMONIC_FILE} but wallet.key is missing. This means a Solana wallet was derived from this mnemonic. Refusing to generate a new wallet to protect Solana funds. Restore your EVM key with: export BLOCKRUN_WALLET_KEY=<your_key>`
3309
+ );
3310
+ }
3311
+ const mnemonic = generateWalletMnemonic();
3312
+ const derived = deriveAllKeys(mnemonic);
3313
+ await mkdir2(WALLET_DIR, { recursive: true });
3314
+ await writeFile(WALLET_FILE, derived.evmPrivateKey + "\n", { mode: 384 });
3315
+ await writeFile(MNEMONIC_FILE, mnemonic + "\n", { mode: 384 });
3316
+ try {
3317
+ const verification = (await readTextFile(WALLET_FILE)).trim();
3318
+ if (verification !== derived.evmPrivateKey) {
3319
+ throw new Error("Wallet file verification failed - content mismatch");
3320
+ }
3321
+ console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);
3322
+ } catch (err) {
3323
+ throw new Error(
3324
+ `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
3325
+ );
3326
+ }
3327
+ console.log(`[ClawRouter]`);
3328
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
3329
+ console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
3330
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
3331
+ console.log(`[ClawRouter] EVM Address : ${derived.evmAddress}`);
3332
+ console.log(`[ClawRouter] Key file : ${WALLET_FILE}`);
3333
+ console.log(`[ClawRouter] Mnemonic : ${MNEMONIC_FILE}`);
3334
+ console.log(`[ClawRouter]`);
3335
+ console.log(`[ClawRouter] Both EVM (Base) and Solana wallets are ready.`);
3336
+ console.log(`[ClawRouter] To back up, run in OpenClaw:`);
3337
+ console.log(`[ClawRouter] /wallet export`);
3338
+ console.log(`[ClawRouter]`);
3339
+ console.log(`[ClawRouter] To restore on another machine:`);
3340
+ console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
3341
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
3342
+ console.log(`[ClawRouter]`);
3343
+ return {
3344
+ key: derived.evmPrivateKey,
3345
+ address: derived.evmAddress,
3346
+ mnemonic,
3347
+ solanaPrivateKeyBytes: derived.solanaPrivateKeyBytes
3348
+ };
3349
+ }
3350
+ async function resolveOrGenerateWalletKey() {
3351
+ const saved = await loadSavedWallet();
3352
+ if (saved) {
3353
+ const account = privateKeyToAccount2(saved);
3354
+ const mnemonic = await loadMnemonic();
3355
+ if (mnemonic) {
3356
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3357
+ return {
3358
+ key: saved,
3359
+ address: account.address,
3360
+ source: "saved",
3361
+ mnemonic,
3362
+ solanaPrivateKeyBytes: solanaKeyBytes
3363
+ };
3364
+ }
3365
+ return { key: saved, address: account.address, source: "saved" };
3366
+ }
3367
+ const envKey = process["env"].BLOCKRUN_WALLET_KEY;
3368
+ if (typeof envKey === "string" && envKey.startsWith("0x") && envKey.length === 66) {
3369
+ const account = privateKeyToAccount2(envKey);
3370
+ const mnemonic = await loadMnemonic();
3371
+ if (mnemonic) {
3372
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3373
+ return {
3374
+ key: envKey,
3375
+ address: account.address,
3376
+ source: "env",
3377
+ mnemonic,
3378
+ solanaPrivateKeyBytes: solanaKeyBytes
3379
+ };
3380
+ }
3381
+ return { key: envKey, address: account.address, source: "env" };
3382
+ }
3383
+ const result = await generateAndSaveWallet();
3384
+ return {
3385
+ key: result.key,
3386
+ address: result.address,
3387
+ source: "generated",
3388
+ mnemonic: result.mnemonic,
3389
+ solanaPrivateKeyBytes: result.solanaPrivateKeyBytes
3390
+ };
3391
+ }
3392
+ async function setupSolana() {
3393
+ const existing = await loadMnemonic();
3394
+ if (existing) {
3395
+ throw new Error(
3396
+ "Solana wallet already set up. Mnemonic file exists at " + MNEMONIC_FILE
3397
+ );
3398
+ }
3399
+ const savedKey = await loadSavedWallet();
3400
+ if (!savedKey) {
3401
+ throw new Error(
3402
+ "No EVM wallet found. Run ClawRouter first to generate a wallet before setting up Solana."
3403
+ );
3404
+ }
3405
+ const mnemonic = generateWalletMnemonic();
3406
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3407
+ await saveMnemonic(mnemonic);
3408
+ console.log(`[ClawRouter] Solana wallet set up successfully.`);
3409
+ console.log(`[ClawRouter] Mnemonic saved to ${MNEMONIC_FILE}`);
3410
+ console.log(`[ClawRouter] Existing EVM wallet unchanged.`);
3411
+ return { mnemonic, solanaPrivateKeyBytes: solanaKeyBytes };
3412
+ }
3413
+ async function savePaymentChain(chain) {
3414
+ await mkdir2(WALLET_DIR, { recursive: true });
3415
+ await writeFile(CHAIN_FILE, chain + "\n", { mode: 384 });
3416
+ }
3417
+ async function loadPaymentChain() {
3418
+ try {
3419
+ const content = (await readTextFile(CHAIN_FILE)).trim();
3420
+ if (content === "solana") return "solana";
3421
+ return "base";
3422
+ } catch {
3423
+ return "base";
3424
+ }
3425
+ }
3426
+ async function resolvePaymentChain() {
3427
+ if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "solana") return "solana";
3428
+ if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "base") return "base";
3429
+ return loadPaymentChain();
3430
+ }
3431
+
3432
+ // src/compression/types.ts
3433
+ var DEFAULT_COMPRESSION_CONFIG = {
3434
+ enabled: true,
3435
+ preserveRaw: true,
3436
+ layers: {
3437
+ deduplication: true,
3438
+ // Safe: removes duplicate messages
3439
+ whitespace: true,
3440
+ // Safe: normalizes whitespace
3441
+ dictionary: false,
3442
+ // DISABLED: requires model to understand codebook
3443
+ paths: false,
3444
+ // DISABLED: requires model to understand path codes
3445
+ jsonCompact: true,
3446
+ // Safe: just removes JSON whitespace
3447
+ observation: false,
3448
+ // DISABLED: may lose important context
3449
+ dynamicCodebook: false
3450
+ // DISABLED: requires model to understand codes
3274
3451
  },
3275
3452
  dictionary: {
3276
3453
  maxEntries: 50,
@@ -3281,7 +3458,7 @@ var DEFAULT_COMPRESSION_CONFIG = {
3281
3458
  };
3282
3459
 
3283
3460
  // src/compression/layers/deduplication.ts
3284
- import crypto2 from "crypto";
3461
+ import crypto from "crypto";
3285
3462
  function hashMessage(message) {
3286
3463
  let contentStr = "";
3287
3464
  if (typeof message.content === "string") {
@@ -3301,7 +3478,7 @@ function hashMessage(message) {
3301
3478
  );
3302
3479
  }
3303
3480
  const content = parts.join("|");
3304
- return crypto2.createHash("md5").update(content).digest("hex");
3481
+ return crypto.createHash("md5").update(content).digest("hex");
3305
3482
  }
3306
3483
  function deduplicateMessages(messages) {
3307
3484
  const seen = /* @__PURE__ */ new Set();
@@ -3454,7 +3631,7 @@ function generateCodebookHeader(usedCodes, pathMap = {}) {
3454
3631
  parts.push(`[Dict: ${codeEntries}]`);
3455
3632
  }
3456
3633
  if (Object.keys(pathMap).length > 0) {
3457
- const pathEntries = Object.entries(pathMap).map(([code, path]) => `${code}=${path}`).join(", ");
3634
+ const pathEntries = Object.entries(pathMap).map(([code, path2]) => `${code}=${path2}`).join(", ");
3458
3635
  parts.push(`[Paths: ${pathEntries}]`);
3459
3636
  }
3460
3637
  return parts.join("\n");
@@ -3528,8 +3705,8 @@ function extractPaths(messages) {
3528
3705
  }
3529
3706
  function findFrequentPrefixes(paths) {
3530
3707
  const prefixCounts = /* @__PURE__ */ new Map();
3531
- for (const path of paths) {
3532
- const parts = path.split("/").filter(Boolean);
3708
+ for (const path2 of paths) {
3709
+ const parts = path2.split("/").filter(Boolean);
3533
3710
  for (let i = 2; i < parts.length; i++) {
3534
3711
  const prefix = "/" + parts.slice(0, i).join("/") + "/";
3535
3712
  prefixCounts.set(prefix, (prefixCounts.get(prefix) || 0) + 1);
@@ -3998,9 +4175,8 @@ function shouldCompress(messages) {
3998
4175
  }
3999
4176
 
4000
4177
  // src/session.ts
4001
- import { createHash as createHash3 } from "crypto";
4002
4178
  var DEFAULT_SESSION_CONFIG = {
4003
- enabled: true,
4179
+ enabled: false,
4004
4180
  timeoutMs: 30 * 60 * 1e3,
4005
4181
  // 30 minutes
4006
4182
  headerName: "x-session-id"
@@ -4055,10 +4231,7 @@ var SessionStore = class {
4055
4231
  tier,
4056
4232
  createdAt: now,
4057
4233
  lastUsedAt: now,
4058
- requestCount: 1,
4059
- recentHashes: [],
4060
- strikes: 0,
4061
- escalated: false
4234
+ requestCount: 1
4062
4235
  });
4063
4236
  }
4064
4237
  }
@@ -4110,43 +4283,6 @@ var SessionStore = class {
4110
4283
  }
4111
4284
  }
4112
4285
  }
4113
- /**
4114
- * Record a request content hash and detect repetitive patterns.
4115
- * Returns true if escalation should be triggered (3+ consecutive similar requests).
4116
- */
4117
- recordRequestHash(sessionId, hash) {
4118
- const entry = this.sessions.get(sessionId);
4119
- if (!entry) return false;
4120
- const prev = entry.recentHashes;
4121
- if (prev.length > 0 && prev[prev.length - 1] === hash) {
4122
- entry.strikes++;
4123
- } else {
4124
- entry.strikes = 0;
4125
- }
4126
- entry.recentHashes.push(hash);
4127
- if (entry.recentHashes.length > 3) {
4128
- entry.recentHashes.shift();
4129
- }
4130
- return entry.strikes >= 2 && !entry.escalated;
4131
- }
4132
- /**
4133
- * Escalate session to next tier. Returns the new model/tier or null if already at max.
4134
- */
4135
- escalateSession(sessionId, tierConfigs) {
4136
- const entry = this.sessions.get(sessionId);
4137
- if (!entry) return null;
4138
- const TIER_ORDER = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
4139
- const currentIdx = TIER_ORDER.indexOf(entry.tier);
4140
- if (currentIdx < 0 || currentIdx >= TIER_ORDER.length - 1) return null;
4141
- const nextTier = TIER_ORDER[currentIdx + 1];
4142
- const nextConfig = tierConfigs[nextTier];
4143
- if (!nextConfig) return null;
4144
- entry.model = nextConfig.primary;
4145
- entry.tier = nextTier;
4146
- entry.strikes = 0;
4147
- entry.escalated = true;
4148
- return { model: nextConfig.primary, tier: nextTier };
4149
- }
4150
4286
  /**
4151
4287
  * Stop the cleanup interval.
4152
4288
  */
@@ -4167,17 +4303,6 @@ function getSessionId(headers, headerName = DEFAULT_SESSION_CONFIG.headerName) {
4167
4303
  }
4168
4304
  return void 0;
4169
4305
  }
4170
- function deriveSessionId(messages) {
4171
- const firstUser = messages.find((m) => m.role === "user");
4172
- if (!firstUser) return void 0;
4173
- const content = typeof firstUser.content === "string" ? firstUser.content : JSON.stringify(firstUser.content);
4174
- return createHash3("sha256").update(content).digest("hex").slice(0, 8);
4175
- }
4176
- function hashRequestContent(lastUserContent, toolCallNames) {
4177
- const normalized = lastUserContent.replace(/\s+/g, " ").trim().slice(0, 500);
4178
- const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(",")}` : "";
4179
- return createHash3("sha256").update(normalized + toolSuffix).digest("hex").slice(0, 12);
4180
- }
4181
4306
 
4182
4307
  // src/updater.ts
4183
4308
  var NPM_REGISTRY = "https://registry.npmjs.org/@blockrun/clawrouter/latest";
@@ -4398,6 +4523,7 @@ ${lines.join("\n")}`;
4398
4523
 
4399
4524
  // src/proxy.ts
4400
4525
  var BLOCKRUN_API = "https://blockrun.ai/api";
4526
+ var BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
4401
4527
  var AUTO_MODEL = "blockrun/auto";
4402
4528
  var ROUTING_PROFILES = /* @__PURE__ */ new Set([
4403
4529
  "blockrun/free",
@@ -4526,7 +4652,7 @@ async function checkExistingProxy(port) {
4526
4652
  if (response.ok) {
4527
4653
  const data = await response.json();
4528
4654
  if (data.status === "ok" && data.wallet) {
4529
- return data.wallet;
4655
+ return { wallet: data.wallet, paymentChain: data.paymentChain };
4530
4656
  }
4531
4657
  }
4532
4658
  return void 0;
@@ -4934,52 +5060,76 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
4934
5060
  }).catch(() => {
4935
5061
  });
4936
5062
  }
4937
- async function uploadDataUriToHost(dataUri) {
4938
- const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
4939
- if (!match) throw new Error("Invalid data URI format");
4940
- const [, mimeType, b64Data] = match;
4941
- const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
4942
- const buffer = Buffer.from(b64Data, "base64");
4943
- const blob = new Blob([buffer], { type: mimeType });
4944
- const form = new FormData();
4945
- form.append("reqtype", "fileupload");
4946
- form.append("fileToUpload", blob, `image.${ext}`);
4947
- const resp = await fetch("https://catbox.moe/user/api.php", {
4948
- method: "POST",
4949
- body: form
4950
- });
4951
- if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
4952
- const result = await resp.text();
4953
- if (result.startsWith("https://")) {
4954
- return result.trim();
4955
- }
4956
- throw new Error(`catbox.moe upload failed: ${result}`);
4957
- }
4958
5063
  async function startProxy(options) {
4959
- const apiBase = options.apiBase ?? BLOCKRUN_API;
5064
+ const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
5065
+ const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
5066
+ const paymentChain = options.paymentChain ?? await resolvePaymentChain();
5067
+ const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
5068
+ if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
5069
+ console.warn(`[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`);
5070
+ } else if (paymentChain === "solana") {
5071
+ console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
5072
+ }
4960
5073
  const listenPort = options.port ?? getProxyPort();
4961
- const existingWallet = await checkExistingProxy(listenPort);
4962
- if (existingWallet) {
4963
- const account2 = privateKeyToAccount2(options.walletKey);
5074
+ const existingProxy = await checkExistingProxy(listenPort);
5075
+ if (existingProxy) {
5076
+ const account2 = privateKeyToAccount3(walletKey);
4964
5077
  const balanceMonitor2 = new BalanceMonitor(account2.address);
4965
5078
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
4966
- if (existingWallet !== account2.address) {
5079
+ if (existingProxy.wallet !== account2.address) {
4967
5080
  console.warn(
4968
- `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingWallet}, but current config uses ${account2.address}. Reusing existing proxy.`
5081
+ `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
5082
+ );
5083
+ }
5084
+ if (existingProxy.paymentChain) {
5085
+ if (existingProxy.paymentChain !== paymentChain) {
5086
+ throw new Error(
5087
+ `Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
5088
+ );
5089
+ }
5090
+ } else if (paymentChain !== "base") {
5091
+ console.warn(`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`);
5092
+ throw new Error(
5093
+ `Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
4969
5094
  );
4970
5095
  }
5096
+ let reuseSolanaAddress;
5097
+ if (solanaPrivateKeyBytes) {
5098
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
5099
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
5100
+ reuseSolanaAddress = solanaSigner.address;
5101
+ }
4971
5102
  options.onReady?.(listenPort);
4972
5103
  return {
4973
5104
  port: listenPort,
4974
5105
  baseUrl: baseUrl2,
4975
- walletAddress: existingWallet,
5106
+ walletAddress: existingProxy.wallet,
5107
+ solanaAddress: reuseSolanaAddress,
4976
5108
  balanceMonitor: balanceMonitor2,
4977
5109
  close: async () => {
4978
5110
  }
4979
5111
  };
4980
5112
  }
4981
- const account = privateKeyToAccount2(options.walletKey);
4982
- const { fetch: payFetch } = createPaymentFetch(options.walletKey);
5113
+ const account = privateKeyToAccount3(walletKey);
5114
+ const evmPublicClient = createPublicClient2({ chain: base2, transport: http2() });
5115
+ const evmSigner = toClientEvmSigner(account, evmPublicClient);
5116
+ const x402 = new x402Client();
5117
+ registerExactEvmScheme(x402, { signer: evmSigner });
5118
+ let solanaAddress;
5119
+ if (solanaPrivateKeyBytes) {
5120
+ const { registerExactSvmScheme } = await import("@x402/svm/exact/client");
5121
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
5122
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
5123
+ solanaAddress = solanaSigner.address;
5124
+ registerExactSvmScheme(x402, { signer: solanaSigner });
5125
+ console.log(`[ClawRouter] Solana x402 scheme registered: ${solanaAddress}`);
5126
+ }
5127
+ x402.onAfterPaymentCreation(async (context) => {
5128
+ const network = context.selectedRequirements.network;
5129
+ const chain = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
5130
+ console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);
5131
+ });
5132
+ const payFetch = createPayFetchWithPreAuth(fetch, x402);
4983
5133
  const balanceMonitor = new BalanceMonitor(account.address);
4984
5134
  const routingConfig = mergeRoutingConfig(options.routingConfig);
4985
5135
  const modelPricing = buildModelPricing();
@@ -5014,8 +5164,12 @@ async function startProxy(options) {
5014
5164
  const full = url.searchParams.get("full") === "true";
5015
5165
  const response = {
5016
5166
  status: "ok",
5017
- wallet: account.address
5167
+ wallet: account.address,
5168
+ paymentChain
5018
5169
  };
5170
+ if (solanaAddress) {
5171
+ response.solana = solanaAddress;
5172
+ }
5019
5173
  if (full) {
5020
5174
  try {
5021
5175
  const balanceInfo = await balanceMonitor.checkBalance();
@@ -5127,10 +5281,10 @@ async function startProxy(options) {
5127
5281
  const onError = async (err) => {
5128
5282
  server.removeListener("error", onError);
5129
5283
  if (err.code === "EADDRINUSE") {
5130
- const existingWallet2 = await checkExistingProxy(listenPort);
5131
- if (existingWallet2) {
5284
+ const existingProxy2 = await checkExistingProxy(listenPort);
5285
+ if (existingProxy2) {
5132
5286
  console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
5133
- rejectAttempt({ code: "REUSE_EXISTING", wallet: existingWallet2 });
5287
+ rejectAttempt({ code: "REUSE_EXISTING", wallet: existingProxy2.wallet, existingChain: existingProxy2.paymentChain });
5134
5288
  return;
5135
5289
  }
5136
5290
  if (attempt < PORT_RETRY_ATTEMPTS) {
@@ -5163,6 +5317,11 @@ async function startProxy(options) {
5163
5317
  } catch (err) {
5164
5318
  const error = err;
5165
5319
  if (error.code === "REUSE_EXISTING" && error.wallet) {
5320
+ if (error.existingChain && error.existingChain !== paymentChain) {
5321
+ throw new Error(
5322
+ `Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
5323
+ );
5324
+ }
5166
5325
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
5167
5326
  options.onReady?.(listenPort);
5168
5327
  return {
@@ -5220,6 +5379,7 @@ async function startProxy(options) {
5220
5379
  port,
5221
5380
  baseUrl,
5222
5381
  walletAddress: account.address,
5382
+ solanaAddress,
5223
5383
  balanceMonitor,
5224
5384
  close: () => new Promise((res, rej) => {
5225
5385
  const timeout = setTimeout(() => {
@@ -5266,8 +5426,6 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5266
5426
  requestBody = Buffer.from(JSON.stringify(parsed));
5267
5427
  } catch {
5268
5428
  }
5269
- const estimated = estimateAmount(modelId, requestBody.length, maxTokens);
5270
- const preAuth = estimated ? { estimatedAmount: estimated } : void 0;
5271
5429
  try {
5272
5430
  const response = await payFetch(
5273
5431
  upstreamUrl,
@@ -5276,8 +5434,7 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5276
5434
  headers,
5277
5435
  body: requestBody.length > 0 ? new Uint8Array(requestBody) : void 0,
5278
5436
  signal
5279
- },
5280
- preAuth
5437
+ }
5281
5438
  );
5282
5439
  if (response.status !== 200) {
5283
5440
  const errorBody = await response.text();
@@ -5326,19 +5483,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5326
5483
  }
5327
5484
  let body = Buffer.concat(bodyChunks);
5328
5485
  const originalContextSizeKB = Math.ceil(body.length / 1024);
5329
- const debugMode = req.headers["x-clawrouter-debug"] !== "false";
5330
5486
  let routingDecision;
5331
- let hasTools = false;
5332
- let hasVision = false;
5333
5487
  let isStreaming = false;
5334
5488
  let modelId = "";
5335
5489
  let maxTokens = 4096;
5336
5490
  let routingProfile = null;
5337
5491
  let accumulatedContent = "";
5338
- let responseInputTokens;
5339
5492
  const isChatCompletion = req.url?.includes("/chat/completions");
5340
5493
  const sessionId = getSessionId(req.headers);
5341
- let effectiveSessionId = sessionId;
5342
5494
  if (isChatCompletion && body.length > 0) {
5343
5495
  try {
5344
5496
  const parsed = JSON.parse(body.toString());
@@ -5346,12 +5498,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5346
5498
  modelId = parsed.model || "";
5347
5499
  maxTokens = parsed.max_tokens || 4096;
5348
5500
  let bodyModified = false;
5349
- const parsedMessages = Array.isArray(parsed.messages) ? parsed.messages : [];
5350
- const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === "user");
5351
- const rawLastContent = lastUserMsg?.content;
5352
- const lastContent = typeof rawLastContent === "string" ? rawLastContent : Array.isArray(rawLastContent) ? rawLastContent.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
5353
- if (sessionId && parsedMessages.length > 0) {
5354
- const messages = parsedMessages;
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 : "";
5355
5505
  if (sessionJournal.needsContext(lastContent)) {
5356
5506
  const journalText = sessionJournal.format(sessionId);
5357
5507
  if (journalText) {
@@ -5372,303 +5522,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5372
5522
  }
5373
5523
  }
5374
5524
  }
5375
- if (lastContent.startsWith("/debug")) {
5376
- const debugPrompt = lastContent.slice("/debug".length).trim() || "hello";
5377
- const messages = parsed.messages;
5378
- const systemMsg = messages?.find((m) => m.role === "system");
5379
- const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5380
- const fullText = `${systemPrompt ?? ""} ${debugPrompt}`;
5381
- const estimatedTokens = Math.ceil(fullText.length / 4);
5382
- const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
5383
- const profileName = normalizedModel2.replace("blockrun/", "");
5384
- const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) ? profileName : "auto";
5385
- const scoring = classifyByRules(
5386
- debugPrompt,
5387
- systemPrompt,
5388
- estimatedTokens,
5389
- DEFAULT_ROUTING_CONFIG.scoring
5390
- );
5391
- const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {
5392
- ...routerOpts,
5393
- routingProfile: debugProfile
5394
- });
5395
- const dimLines = (scoring.dimensions ?? []).map((d) => {
5396
- const nameStr = (d.name + ":").padEnd(24);
5397
- const scoreStr = d.score.toFixed(2).padStart(6);
5398
- const sigStr = d.signal ? ` [${d.signal}]` : "";
5399
- return ` ${nameStr}${scoreStr}${sigStr}`;
5400
- }).join("\n");
5401
- const sess = sessionId ? sessionStore.getSession(sessionId) : void 0;
5402
- 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";
5403
- const { simpleMedium, mediumComplex, complexReasoning } = DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;
5404
- const debugText = [
5405
- "ClawRouter Debug",
5406
- "",
5407
- `Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,
5408
- `Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,
5409
- `Reasoning: ${debugRouting.reasoning}`,
5410
- "",
5411
- `Scoring (weighted: ${scoring.score.toFixed(3)})`,
5412
- dimLines,
5413
- "",
5414
- `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,
5415
- "",
5416
- sessLine
5417
- ].join("\n");
5418
- const completionId = `chatcmpl-debug-${Date.now()}`;
5419
- const timestamp = Math.floor(Date.now() / 1e3);
5420
- const syntheticResponse = {
5421
- id: completionId,
5422
- object: "chat.completion",
5423
- created: timestamp,
5424
- model: "clawrouter/debug",
5425
- choices: [
5426
- {
5427
- index: 0,
5428
- message: { role: "assistant", content: debugText },
5429
- finish_reason: "stop"
5430
- }
5431
- ],
5432
- usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5433
- };
5434
- if (isStreaming) {
5435
- res.writeHead(200, {
5436
- "Content-Type": "text/event-stream",
5437
- "Cache-Control": "no-cache",
5438
- Connection: "keep-alive"
5439
- });
5440
- const sseChunk = {
5441
- id: completionId,
5442
- object: "chat.completion.chunk",
5443
- created: timestamp,
5444
- model: "clawrouter/debug",
5445
- choices: [
5446
- {
5447
- index: 0,
5448
- delta: { role: "assistant", content: debugText },
5449
- finish_reason: null
5450
- }
5451
- ]
5452
- };
5453
- const sseDone = {
5454
- id: completionId,
5455
- object: "chat.completion.chunk",
5456
- created: timestamp,
5457
- model: "clawrouter/debug",
5458
- choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
5459
- };
5460
- res.write(`data: ${JSON.stringify(sseChunk)}
5461
-
5462
- `);
5463
- res.write(`data: ${JSON.stringify(sseDone)}
5464
-
5465
- `);
5466
- res.write("data: [DONE]\n\n");
5467
- res.end();
5468
- } else {
5469
- res.writeHead(200, { "Content-Type": "application/json" });
5470
- res.end(JSON.stringify(syntheticResponse));
5471
- }
5472
- console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`);
5473
- return;
5474
- }
5475
- if (lastContent.startsWith("/imagegen")) {
5476
- const imageArgs = lastContent.slice("/imagegen".length).trim();
5477
- let imageModel = "google/nano-banana";
5478
- let imageSize = "1024x1024";
5479
- let imagePrompt = imageArgs;
5480
- const modelMatch = imageArgs.match(/--model\s+(\S+)/);
5481
- if (modelMatch) {
5482
- const raw = modelMatch[1];
5483
- const IMAGE_MODEL_ALIASES = {
5484
- "dall-e-3": "openai/dall-e-3",
5485
- dalle3: "openai/dall-e-3",
5486
- dalle: "openai/dall-e-3",
5487
- "gpt-image": "openai/gpt-image-1",
5488
- "gpt-image-1": "openai/gpt-image-1",
5489
- flux: "black-forest/flux-1.1-pro",
5490
- "flux-pro": "black-forest/flux-1.1-pro",
5491
- banana: "google/nano-banana",
5492
- "nano-banana": "google/nano-banana",
5493
- "banana-pro": "google/nano-banana-pro",
5494
- "nano-banana-pro": "google/nano-banana-pro"
5495
- };
5496
- imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;
5497
- imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim();
5498
- }
5499
- const sizeMatch = imageArgs.match(/--size\s+(\d+x\d+)/);
5500
- if (sizeMatch) {
5501
- imageSize = sizeMatch[1];
5502
- imagePrompt = imagePrompt.replace(/--size\s+\d+x\d+/, "").trim();
5503
- }
5504
- if (!imagePrompt) {
5505
- const errorText = [
5506
- "Usage: /imagegen <prompt>",
5507
- "",
5508
- "Options:",
5509
- " --model <model> Model to use (default: nano-banana)",
5510
- " --size <WxH> Image size (default: 1024x1024)",
5511
- "",
5512
- "Models:",
5513
- " nano-banana Google Gemini Flash \u2014 $0.05/image",
5514
- " banana-pro Google Gemini Pro \u2014 $0.10/image (up to 4K)",
5515
- " dall-e-3 OpenAI DALL-E 3 \u2014 $0.04/image",
5516
- " gpt-image OpenAI GPT Image 1 \u2014 $0.02/image",
5517
- " flux Black Forest Flux 1.1 Pro \u2014 $0.04/image",
5518
- "",
5519
- "Examples:",
5520
- " /imagegen a cat wearing sunglasses",
5521
- " /imagegen --model dall-e-3 a futuristic city at sunset",
5522
- " /imagegen --model banana-pro --size 2048x2048 mountain landscape"
5523
- ].join("\n");
5524
- const completionId = `chatcmpl-image-${Date.now()}`;
5525
- const timestamp = Math.floor(Date.now() / 1e3);
5526
- if (isStreaming) {
5527
- res.writeHead(200, {
5528
- "Content-Type": "text/event-stream",
5529
- "Cache-Control": "no-cache",
5530
- Connection: "keep-alive"
5531
- });
5532
- res.write(
5533
- `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 }] })}
5534
-
5535
- `
5536
- );
5537
- res.write(
5538
- `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
5539
-
5540
- `
5541
- );
5542
- res.write("data: [DONE]\n\n");
5543
- res.end();
5544
- } else {
5545
- res.writeHead(200, { "Content-Type": "application/json" });
5546
- res.end(
5547
- JSON.stringify({
5548
- id: completionId,
5549
- object: "chat.completion",
5550
- created: timestamp,
5551
- model: "clawrouter/image",
5552
- choices: [
5553
- {
5554
- index: 0,
5555
- message: { role: "assistant", content: errorText },
5556
- finish_reason: "stop"
5557
- }
5558
- ],
5559
- usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5560
- })
5561
- );
5562
- }
5563
- console.log(`[ClawRouter] /imagegen command \u2192 showing usage help`);
5564
- return;
5565
- }
5566
- console.log(
5567
- `[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`
5568
- );
5569
- try {
5570
- const imageUpstreamUrl = `${apiBase}/v1/images/generations`;
5571
- const imageBody = JSON.stringify({
5572
- model: imageModel,
5573
- prompt: imagePrompt,
5574
- size: imageSize,
5575
- n: 1
5576
- });
5577
- const imageResponse = await payFetch(imageUpstreamUrl, {
5578
- method: "POST",
5579
- headers: { "content-type": "application/json", "user-agent": USER_AGENT },
5580
- body: imageBody
5581
- });
5582
- const imageResult = await imageResponse.json();
5583
- let responseText;
5584
- if (!imageResponse.ok || imageResult.error) {
5585
- const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`;
5586
- responseText = `Image generation failed: ${errMsg}`;
5587
- console.log(`[ClawRouter] /imagegen error: ${errMsg}`);
5588
- } else {
5589
- const images = imageResult.data ?? [];
5590
- if (images.length === 0) {
5591
- responseText = "Image generation returned no results.";
5592
- } else {
5593
- const lines = [];
5594
- for (const img of images) {
5595
- if (img.url) {
5596
- if (img.url.startsWith("data:")) {
5597
- try {
5598
- const hostedUrl = await uploadDataUriToHost(img.url);
5599
- lines.push(hostedUrl);
5600
- } catch (uploadErr) {
5601
- console.error(
5602
- `[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
5603
- );
5604
- lines.push(
5605
- "Image generated but upload failed. Try again or use --model dall-e-3."
5606
- );
5607
- }
5608
- } else {
5609
- lines.push(img.url);
5610
- }
5611
- }
5612
- if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);
5613
- }
5614
- lines.push("", `Model: ${imageModel} | Size: ${imageSize}`);
5615
- responseText = lines.join("\n");
5616
- }
5617
- console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
5618
- }
5619
- const completionId = `chatcmpl-image-${Date.now()}`;
5620
- const timestamp = Math.floor(Date.now() / 1e3);
5621
- if (isStreaming) {
5622
- res.writeHead(200, {
5623
- "Content-Type": "text/event-stream",
5624
- "Cache-Control": "no-cache",
5625
- Connection: "keep-alive"
5626
- });
5627
- res.write(
5628
- `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 }] })}
5629
-
5630
- `
5631
- );
5632
- res.write(
5633
- `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
5634
-
5635
- `
5636
- );
5637
- res.write("data: [DONE]\n\n");
5638
- res.end();
5639
- } else {
5640
- res.writeHead(200, { "Content-Type": "application/json" });
5641
- res.end(
5642
- JSON.stringify({
5643
- id: completionId,
5644
- object: "chat.completion",
5645
- created: timestamp,
5646
- model: "clawrouter/image",
5647
- choices: [
5648
- {
5649
- index: 0,
5650
- message: { role: "assistant", content: responseText },
5651
- finish_reason: "stop"
5652
- }
5653
- ],
5654
- usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5655
- })
5656
- );
5657
- }
5658
- } catch (err) {
5659
- const errMsg = err instanceof Error ? err.message : String(err);
5660
- console.error(`[ClawRouter] /imagegen error: ${errMsg}`);
5661
- if (!res.headersSent) {
5662
- res.writeHead(500, { "Content-Type": "application/json" });
5663
- res.end(
5664
- JSON.stringify({
5665
- error: { message: `Image generation failed: ${errMsg}`, type: "image_error" }
5666
- })
5667
- );
5668
- }
5669
- }
5670
- return;
5671
- }
5672
5525
  if (parsed.stream === true) {
5673
5526
  parsed.stream = false;
5674
5527
  bodyModified = true;
@@ -5709,118 +5562,54 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5709
5562
  latencyMs: 0
5710
5563
  });
5711
5564
  } else {
5712
- effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
5713
- const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
5714
- const rawPrompt = lastUserMsg?.content;
5715
- const prompt = typeof rawPrompt === "string" ? rawPrompt : Array.isArray(rawPrompt) ? rawPrompt.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
5716
- const systemMsg = parsedMessages.find((m) => m.role === "system");
5717
- const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5718
- const tools = parsed.tools;
5719
- hasTools = Array.isArray(tools) && tools.length > 0;
5720
- if (hasTools && tools) {
5721
- console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);
5722
- }
5723
- hasVision = parsedMessages.some((m) => {
5724
- if (Array.isArray(m.content)) {
5725
- return m.content.some((p) => p.type === "image_url");
5726
- }
5727
- return false;
5728
- });
5729
- if (hasVision) {
5730
- console.log(`[ClawRouter] Vision content detected, filtering to vision-capable models`);
5731
- }
5732
- routingDecision = route(prompt, systemPrompt, maxTokens, {
5733
- ...routerOpts,
5734
- routingProfile: routingProfile ?? void 0
5735
- });
5565
+ const sessionId2 = getSessionId(
5566
+ req.headers
5567
+ );
5568
+ const existingSession = sessionId2 ? sessionStore.getSession(sessionId2) : void 0;
5736
5569
  if (existingSession) {
5737
- const tierRank = {
5738
- SIMPLE: 0,
5739
- MEDIUM: 1,
5740
- COMPLEX: 2,
5741
- REASONING: 3
5742
- };
5743
- const existingRank = tierRank[existingSession.tier] ?? 0;
5744
- const newRank = tierRank[routingDecision.tier] ?? 0;
5745
- if (newRank > existingRank) {
5746
- console.log(
5747
- `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} \u2192 ${routingDecision.tier} (${routingDecision.model})`
5748
- );
5749
- parsed.model = routingDecision.model;
5750
- modelId = routingDecision.model;
5751
- bodyModified = true;
5752
- if (effectiveSessionId) {
5753
- sessionStore.setSession(
5754
- effectiveSessionId,
5755
- routingDecision.model,
5756
- routingDecision.tier
5757
- );
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
+ }
5758
5586
  }
5759
- } else {
5760
- console.log(
5761
- `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`
5762
- );
5763
- parsed.model = existingSession.model;
5764
- modelId = existingSession.model;
5765
- bodyModified = true;
5766
- sessionStore.touchSession(effectiveSessionId);
5767
- routingDecision = {
5768
- ...routingDecision,
5769
- model: existingSession.model,
5770
- tier: existingSession.tier
5771
- };
5772
5587
  }
5773
- const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
5774
- const assistantToolCalls = lastAssistantMsg?.tool_calls;
5775
- const toolCallNames = Array.isArray(assistantToolCalls) ? assistantToolCalls.map((tc) => tc.function?.name).filter((n) => Boolean(n)) : void 0;
5776
- const contentHash = hashRequestContent(prompt, toolCallNames);
5777
- const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
5778
- if (shouldEscalate) {
5779
- const activeTierConfigs = (() => {
5780
- if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
5781
- return routerOpts.config.agenticTiers;
5782
- }
5783
- if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
5784
- return routerOpts.config.ecoTiers;
5785
- }
5786
- if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
5787
- return routerOpts.config.premiumTiers;
5788
- }
5789
- return routerOpts.config.tiers;
5790
- })();
5791
- const escalation = sessionStore.escalateSession(
5792
- effectiveSessionId,
5793
- activeTierConfigs
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) {
5594
+ console.log(
5595
+ `[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`
5794
5596
  );
5795
- if (escalation) {
5796
- console.log(
5797
- `[ClawRouter] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`
5798
- );
5799
- parsed.model = escalation.model;
5800
- modelId = escalation.model;
5801
- routingDecision = {
5802
- ...routingDecision,
5803
- model: escalation.model,
5804
- tier: escalation.tier
5805
- };
5806
- }
5807
5597
  }
5808
- } else {
5598
+ routingDecision = route(prompt, systemPrompt, maxTokens, {
5599
+ ...routerOpts,
5600
+ routingProfile: routingProfile ?? void 0
5601
+ });
5809
5602
  parsed.model = routingDecision.model;
5810
5603
  modelId = routingDecision.model;
5811
5604
  bodyModified = true;
5812
- if (effectiveSessionId) {
5813
- sessionStore.setSession(
5814
- effectiveSessionId,
5815
- routingDecision.model,
5816
- routingDecision.tier
5817
- );
5605
+ if (sessionId2) {
5606
+ sessionStore.setSession(sessionId2, routingDecision.model, routingDecision.tier);
5818
5607
  console.log(
5819
- `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`
5608
+ `[ClawRouter] Session ${sessionId2.slice(0, 8)}... pinned to model: ${routingDecision.model}`
5820
5609
  );
5821
5610
  }
5611
+ options.onRouted?.(routingDecision);
5822
5612
  }
5823
- options.onRouted?.(routingDecision);
5824
5613
  }
5825
5614
  }
5826
5615
  if (bodyModified) {
@@ -5992,18 +5781,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5992
5781
  if (routingDecision) {
5993
5782
  const estimatedInputTokens = Math.ceil(body.length / 4);
5994
5783
  const estimatedTotalTokens = estimatedInputTokens + maxTokens;
5995
- const tierConfigs = (() => {
5996
- if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
5997
- return routerOpts.config.agenticTiers;
5998
- }
5999
- if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
6000
- return routerOpts.config.ecoTiers;
6001
- }
6002
- if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
6003
- return routerOpts.config.premiumTiers;
6004
- }
6005
- return routerOpts.config.tiers;
6006
- })();
5784
+ const useAgenticTiers = routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers;
5785
+ const tierConfigs = useAgenticTiers ? routerOpts.config.agenticTiers : routerOpts.config.tiers;
6007
5786
  const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);
6008
5787
  const contextFiltered = getFallbackChainFiltered(
6009
5788
  routingDecision.tier,
@@ -6017,27 +5796,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6017
5796
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
6018
5797
  );
6019
5798
  }
6020
- const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);
6021
- const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));
6022
- if (toolExcluded.length > 0) {
6023
- console.log(
6024
- `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
6025
- );
6026
- }
6027
- const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);
6028
- const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));
6029
- if (visionExcluded.length > 0) {
6030
- console.log(
6031
- `[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`
6032
- );
6033
- }
6034
- modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5799
+ modelsToTry = contextFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
6035
5800
  modelsToTry = prioritizeNonRateLimited(modelsToTry);
6036
5801
  } else {
6037
- modelsToTry = modelId ? [modelId] : [];
6038
- }
6039
- if (!modelsToTry.includes(FREE_MODEL)) {
6040
- modelsToTry.push(FREE_MODEL);
5802
+ if (modelId && modelId !== FREE_MODEL) {
5803
+ modelsToTry = [modelId, FREE_MODEL];
5804
+ } else {
5805
+ modelsToTry = modelId ? [modelId] : [];
5806
+ }
6041
5807
  }
6042
5808
  let upstream;
6043
5809
  let lastError;
@@ -6071,17 +5837,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6071
5837
  if (result.errorStatus === 429) {
6072
5838
  markRateLimited(tryModel);
6073
5839
  }
6074
- const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
6075
- result.errorBody || ""
6076
- );
6077
- if (isPaymentErr && tryModel !== FREE_MODEL) {
6078
- const freeIdx = modelsToTry.indexOf(FREE_MODEL);
6079
- if (freeIdx > i + 1) {
6080
- console.log(`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
6081
- i = freeIdx - 1;
6082
- continue;
6083
- }
6084
- }
6085
5840
  console.log(
6086
5841
  `[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`
6087
5842
  );
@@ -6099,12 +5854,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6099
5854
  clearInterval(heartbeatInterval);
6100
5855
  heartbeatInterval = void 0;
6101
5856
  }
6102
- if (debugMode && headersSentEarly && routingDecision) {
6103
- 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}
6104
-
6105
- `;
6106
- safeWrite(res, debugComment);
6107
- }
6108
5857
  if (routingDecision && actualModelUsed !== routingDecision.model) {
6109
5858
  const estimatedInputTokens = Math.ceil(body.length / 4);
6110
5859
  const newCosts = calculateModelCost(
@@ -6123,12 +5872,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6123
5872
  savings: newCosts.savings
6124
5873
  };
6125
5874
  options.onRouted?.(routingDecision);
6126
- if (effectiveSessionId) {
6127
- sessionStore.setSession(effectiveSessionId, actualModelUsed, routingDecision.tier);
6128
- console.log(
6129
- `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}`
6130
- );
6131
- }
6132
5875
  }
6133
5876
  if (!upstream) {
6134
5877
  const rawErrBody = lastError?.body || "All models in fallback chain failed";
@@ -6191,10 +5934,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6191
5934
  const jsonStr = jsonBody.toString();
6192
5935
  try {
6193
5936
  const rsp = JSON.parse(jsonStr);
6194
- if (rsp.usage && typeof rsp.usage === "object") {
6195
- const u = rsp.usage;
6196
- if (typeof u.prompt_tokens === "number") responseInputTokens = u.prompt_tokens;
6197
- }
6198
5937
  const baseChunk = {
6199
5938
  id: rsp.id ?? `chatcmpl-${Date.now()}`,
6200
5939
  object: "chat.completion.chunk",
@@ -6294,16 +6033,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6294
6033
  });
6295
6034
  responseHeaders["x-context-used-kb"] = String(originalContextSizeKB);
6296
6035
  responseHeaders["x-context-limit-kb"] = String(CONTEXT_LIMIT_KB);
6297
- if (debugMode && routingDecision) {
6298
- responseHeaders["x-clawrouter-profile"] = routingProfile ?? "auto";
6299
- responseHeaders["x-clawrouter-tier"] = routingDecision.tier;
6300
- responseHeaders["x-clawrouter-model"] = actualModelUsed;
6301
- responseHeaders["x-clawrouter-confidence"] = routingDecision.confidence.toFixed(2);
6302
- responseHeaders["x-clawrouter-reasoning"] = routingDecision.reasoning;
6303
- if (routingDecision.agenticScore !== void 0) {
6304
- responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
6305
- }
6306
- }
6307
6036
  res.writeHead(upstream.status, responseHeaders);
6308
6037
  if (upstream.body) {
6309
6038
  const reader = upstream.body.getReader();
@@ -6343,10 +6072,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6343
6072
  if (rspJson.choices?.[0]?.message?.content) {
6344
6073
  accumulatedContent = rspJson.choices[0].message.content;
6345
6074
  }
6346
- if (rspJson.usage && typeof rspJson.usage === "object") {
6347
- if (typeof rspJson.usage.prompt_tokens === "number")
6348
- responseInputTokens = rspJson.usage.prompt_tokens;
6349
- }
6350
6075
  } catch {
6351
6076
  }
6352
6077
  }
@@ -6395,207 +6120,278 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6395
6120
  cost: costWithBuffer,
6396
6121
  baselineCost: baselineWithBuffer,
6397
6122
  savings: accurateCosts.savings,
6398
- latencyMs: Date.now() - startTime,
6399
- ...responseInputTokens !== void 0 && { inputTokens: responseInputTokens }
6123
+ latencyMs: Date.now() - startTime
6400
6124
  };
6401
6125
  logUsage(entry).catch(() => {
6402
6126
  });
6403
6127
  }
6404
6128
  }
6405
6129
 
6406
- // src/auth.ts
6407
- import { writeFile, mkdir as mkdir2 } from "fs/promises";
6408
- import { join as join4 } from "path";
6409
- import { homedir as homedir3 } from "os";
6410
- import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
6411
- var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
6412
- var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
6413
- async function loadSavedWallet() {
6414
- try {
6415
- const key = (await readTextFile(WALLET_FILE)).trim();
6416
- if (key.startsWith("0x") && key.length === 66) {
6417
- console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
6418
- return key;
6419
- }
6420
- console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
6421
- console.error(`[ClawRouter] File: ${WALLET_FILE}`);
6422
- console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
6423
- console.error(
6424
- `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
6425
- );
6426
- throw new Error(
6427
- `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.`
6428
- );
6429
- } catch (err) {
6430
- if (err.code !== "ENOENT") {
6431
- if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
6432
- throw err;
6433
- }
6434
- console.error(
6435
- `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
6436
- );
6437
- throw new Error(
6438
- `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.`
6439
- );
6440
- }
6441
- }
6442
- return void 0;
6443
- }
6444
- async function generateAndSaveWallet() {
6445
- const key = generatePrivateKey();
6446
- const account = privateKeyToAccount3(key);
6447
- await mkdir2(WALLET_DIR, { recursive: true });
6448
- await writeFile(WALLET_FILE, key + "\n", { mode: 384 });
6449
- try {
6450
- const verification = (await readTextFile(WALLET_FILE)).trim();
6451
- if (verification !== key) {
6452
- throw new Error("Wallet file verification failed - content mismatch");
6453
- }
6454
- console.log(`[ClawRouter] \u2713 Wallet saved and verified at ${WALLET_FILE}`);
6455
- } catch (err) {
6456
- throw new Error(
6457
- `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
6458
- );
6459
- }
6460
- console.log(`[ClawRouter]`);
6461
- console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6462
- console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
6463
- console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6464
- console.log(`[ClawRouter] Address : ${account.address}`);
6465
- console.log(`[ClawRouter] Key file: ${WALLET_FILE}`);
6466
- console.log(`[ClawRouter]`);
6467
- console.log(`[ClawRouter] To back up, run in OpenClaw:`);
6468
- console.log(`[ClawRouter] /wallet export`);
6469
- console.log(`[ClawRouter]`);
6470
- console.log(`[ClawRouter] To restore on another machine:`);
6471
- console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
6472
- console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6473
- console.log(`[ClawRouter]`);
6474
- return { key, address: account.address };
6475
- }
6476
- async function resolveOrGenerateWalletKey() {
6477
- const saved = await loadSavedWallet();
6478
- if (saved) {
6479
- const account = privateKeyToAccount3(saved);
6480
- return { key: saved, address: account.address, source: "saved" };
6481
- }
6482
- const envKey = process["env"].BLOCKRUN_WALLET_KEY;
6483
- if (typeof envKey === "string" && envKey.startsWith("0x") && envKey.length === 66) {
6484
- const account = privateKeyToAccount3(envKey);
6485
- return { key: envKey, address: account.address, source: "env" };
6486
- }
6487
- const { key, address } = await generateAndSaveWallet();
6488
- return { key, address, source: "generated" };
6489
- }
6490
-
6491
6130
  // src/index.ts
6492
6131
  import {
6493
- writeFileSync,
6494
- existsSync,
6132
+ writeFileSync as writeFileSync2,
6133
+ existsSync as existsSync2,
6495
6134
  readdirSync,
6496
- mkdirSync,
6135
+ mkdirSync as mkdirSync2,
6497
6136
  copyFileSync,
6498
6137
  renameSync
6499
6138
  } from "fs";
6500
- import { homedir as homedir4 } from "os";
6501
- import { join as join5 } from "path";
6139
+ import { homedir as homedir5 } from "os";
6140
+ import { join as join6 } from "path";
6502
6141
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
6142
+ init_solana_balance();
6503
6143
 
6504
- // src/partners/registry.ts
6505
- var PARTNER_SERVICES = [
6506
- {
6507
- id: "x_users_lookup",
6508
- name: "Twitter/X User Lookup",
6509
- partner: "AttentionVC",
6510
- 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).",
6511
- proxyPath: "/x/users/lookup",
6512
- method: "POST",
6513
- params: [
6514
- {
6515
- name: "usernames",
6516
- type: "string[]",
6517
- description: 'Array of Twitter/X usernames to look up (without @ prefix). Example: ["elonmusk", "naval"]',
6518
- required: true
6144
+ // src/spend-control.ts
6145
+ import * as fs from "fs";
6146
+ import * as path from "path";
6147
+ import { homedir as homedir4 } from "os";
6148
+ var WALLET_DIR2 = path.join(homedir4(), ".openclaw", "blockrun");
6149
+ var HOUR_MS = 60 * 60 * 1e3;
6150
+ var DAY_MS = 24 * HOUR_MS;
6151
+ var FileSpendControlStorage = class {
6152
+ spendingFile;
6153
+ constructor() {
6154
+ this.spendingFile = path.join(WALLET_DIR2, "spending.json");
6155
+ }
6156
+ load() {
6157
+ try {
6158
+ if (fs.existsSync(this.spendingFile)) {
6159
+ const data = JSON.parse(readTextFileSync(this.spendingFile));
6160
+ const rawLimits = data.limits ?? {};
6161
+ const rawHistory = data.history ?? [];
6162
+ const limits = {};
6163
+ for (const key of ["perRequest", "hourly", "daily", "session"]) {
6164
+ const val = rawLimits[key];
6165
+ if (typeof val === "number" && val > 0 && Number.isFinite(val)) {
6166
+ limits[key] = val;
6167
+ }
6168
+ }
6169
+ const history = [];
6170
+ if (Array.isArray(rawHistory)) {
6171
+ for (const r of rawHistory) {
6172
+ if (typeof r?.timestamp === "number" && typeof r?.amount === "number" && Number.isFinite(r.timestamp) && Number.isFinite(r.amount) && r.amount >= 0) {
6173
+ history.push({
6174
+ timestamp: r.timestamp,
6175
+ amount: r.amount,
6176
+ model: typeof r.model === "string" ? r.model : void 0,
6177
+ action: typeof r.action === "string" ? r.action : void 0
6178
+ });
6179
+ }
6180
+ }
6181
+ }
6182
+ return { limits, history };
6519
6183
  }
6520
- ],
6521
- pricing: {
6522
- perUnit: "$0.001",
6523
- unit: "user",
6524
- minimum: "$0.01 (10 users)",
6525
- maximum: "$0.10 (100 users)"
6526
- },
6527
- example: {
6528
- input: { usernames: ["elonmusk", "naval", "balaboris"] },
6529
- description: "Look up 3 Twitter/X user profiles"
6184
+ } catch (err) {
6185
+ console.error(`[ClawRouter] Failed to load spending data, starting fresh: ${err}`);
6530
6186
  }
6187
+ return null;
6531
6188
  }
6532
- ];
6533
- function getPartnerService(id) {
6534
- return PARTNER_SERVICES.find((s) => s.id === id);
6535
- }
6536
-
6537
- // src/partners/tools.ts
6538
- function buildTool(service, proxyBaseUrl) {
6539
- const properties = {};
6540
- const required = [];
6541
- for (const param of service.params) {
6542
- const prop = {
6543
- description: param.description
6189
+ save(data) {
6190
+ try {
6191
+ if (!fs.existsSync(WALLET_DIR2)) {
6192
+ fs.mkdirSync(WALLET_DIR2, { recursive: true, mode: 448 });
6193
+ }
6194
+ fs.writeFileSync(this.spendingFile, JSON.stringify(data, null, 2), {
6195
+ mode: 384
6196
+ });
6197
+ } catch (err) {
6198
+ console.error(`[ClawRouter] Failed to save spending data: ${err}`);
6199
+ }
6200
+ }
6201
+ };
6202
+ var InMemorySpendControlStorage = class {
6203
+ data = null;
6204
+ load() {
6205
+ return this.data ? {
6206
+ limits: { ...this.data.limits },
6207
+ history: this.data.history.map((r) => ({ ...r }))
6208
+ } : null;
6209
+ }
6210
+ save(data) {
6211
+ this.data = {
6212
+ limits: { ...data.limits },
6213
+ history: data.history.map((r) => ({ ...r }))
6544
6214
  };
6545
- if (param.type === "string[]") {
6546
- prop.type = "array";
6547
- prop.items = { type: "string" };
6548
- } else {
6549
- prop.type = param.type;
6215
+ }
6216
+ };
6217
+ var SpendControl = class {
6218
+ limits = {};
6219
+ history = [];
6220
+ sessionSpent = 0;
6221
+ sessionCalls = 0;
6222
+ storage;
6223
+ now;
6224
+ constructor(options) {
6225
+ this.storage = options?.storage ?? new FileSpendControlStorage();
6226
+ this.now = options?.now ?? (() => Date.now());
6227
+ this.load();
6228
+ }
6229
+ setLimit(window, amount) {
6230
+ if (!Number.isFinite(amount) || amount <= 0) {
6231
+ throw new Error("Limit must be a finite positive number");
6232
+ }
6233
+ this.limits[window] = amount;
6234
+ this.save();
6235
+ }
6236
+ clearLimit(window) {
6237
+ delete this.limits[window];
6238
+ this.save();
6239
+ }
6240
+ getLimits() {
6241
+ return { ...this.limits };
6242
+ }
6243
+ check(estimatedCost) {
6244
+ const now = this.now();
6245
+ if (this.limits.perRequest !== void 0) {
6246
+ if (estimatedCost > this.limits.perRequest) {
6247
+ return {
6248
+ allowed: false,
6249
+ blockedBy: "perRequest",
6250
+ remaining: this.limits.perRequest,
6251
+ reason: `Per-request limit exceeded: $${estimatedCost.toFixed(4)} > $${this.limits.perRequest.toFixed(2)} max`
6252
+ };
6253
+ }
6550
6254
  }
6551
- properties[param.name] = prop;
6552
- if (param.required) {
6553
- required.push(param.name);
6255
+ if (this.limits.hourly !== void 0) {
6256
+ const hourlySpent = this.getSpendingInWindow(now - HOUR_MS, now);
6257
+ const remaining = this.limits.hourly - hourlySpent;
6258
+ if (estimatedCost > remaining) {
6259
+ const oldestInWindow = this.history.find((r) => r.timestamp >= now - HOUR_MS);
6260
+ const resetIn = oldestInWindow ? Math.ceil((oldestInWindow.timestamp + HOUR_MS - now) / 1e3) : 0;
6261
+ return {
6262
+ allowed: false,
6263
+ blockedBy: "hourly",
6264
+ remaining,
6265
+ reason: `Hourly limit exceeded: $${(hourlySpent + estimatedCost).toFixed(2)} > $${this.limits.hourly.toFixed(2)} max`,
6266
+ resetIn
6267
+ };
6268
+ }
6554
6269
  }
6555
- }
6556
- return {
6557
- name: `blockrun_${service.id}`,
6558
- description: [
6559
- service.description,
6560
- "",
6561
- `Partner: ${service.partner}`,
6562
- `Pricing: ${service.pricing.perUnit} per ${service.pricing.unit} (min: ${service.pricing.minimum}, max: ${service.pricing.maximum})`
6563
- ].join("\n"),
6564
- parameters: {
6565
- type: "object",
6566
- properties,
6567
- required
6568
- },
6569
- execute: async (_toolCallId, params) => {
6570
- const url = `${proxyBaseUrl}/v1${service.proxyPath}`;
6571
- const response = await fetch(url, {
6572
- method: service.method,
6573
- headers: { "Content-Type": "application/json" },
6574
- body: JSON.stringify(params)
6575
- });
6576
- if (!response.ok) {
6577
- const errText = await response.text().catch(() => "");
6578
- throw new Error(
6579
- `Partner API error (${response.status}): ${errText || response.statusText}`
6580
- );
6270
+ if (this.limits.daily !== void 0) {
6271
+ const dailySpent = this.getSpendingInWindow(now - DAY_MS, now);
6272
+ const remaining = this.limits.daily - dailySpent;
6273
+ if (estimatedCost > remaining) {
6274
+ const oldestInWindow = this.history.find((r) => r.timestamp >= now - DAY_MS);
6275
+ const resetIn = oldestInWindow ? Math.ceil((oldestInWindow.timestamp + DAY_MS - now) / 1e3) : 0;
6276
+ return {
6277
+ allowed: false,
6278
+ blockedBy: "daily",
6279
+ remaining,
6280
+ reason: `Daily limit exceeded: $${(dailySpent + estimatedCost).toFixed(2)} > $${this.limits.daily.toFixed(2)} max`,
6281
+ resetIn
6282
+ };
6581
6283
  }
6582
- const data = await response.json();
6583
- return {
6584
- content: [
6585
- {
6586
- type: "text",
6587
- text: JSON.stringify(data, null, 2)
6588
- }
6589
- ],
6590
- details: data
6591
- };
6592
6284
  }
6593
- };
6594
- }
6595
- function buildPartnerTools(proxyBaseUrl) {
6596
- return PARTNER_SERVICES.map((service) => buildTool(service, proxyBaseUrl));
6285
+ if (this.limits.session !== void 0) {
6286
+ const remaining = this.limits.session - this.sessionSpent;
6287
+ if (estimatedCost > remaining) {
6288
+ return {
6289
+ allowed: false,
6290
+ blockedBy: "session",
6291
+ remaining,
6292
+ reason: `Session limit exceeded: $${(this.sessionSpent + estimatedCost).toFixed(2)} > $${this.limits.session.toFixed(2)} max`
6293
+ };
6294
+ }
6295
+ }
6296
+ return { allowed: true };
6297
+ }
6298
+ record(amount, metadata) {
6299
+ if (!Number.isFinite(amount) || amount < 0) {
6300
+ throw new Error("Record amount must be a non-negative finite number");
6301
+ }
6302
+ const record = {
6303
+ timestamp: this.now(),
6304
+ amount,
6305
+ model: metadata?.model,
6306
+ action: metadata?.action
6307
+ };
6308
+ this.history.push(record);
6309
+ this.sessionSpent += amount;
6310
+ this.sessionCalls += 1;
6311
+ this.cleanup();
6312
+ this.save();
6313
+ }
6314
+ getSpendingInWindow(from, to) {
6315
+ return this.history.filter((r) => r.timestamp >= from && r.timestamp <= to).reduce((sum, r) => sum + r.amount, 0);
6316
+ }
6317
+ getSpending(window) {
6318
+ const now = this.now();
6319
+ switch (window) {
6320
+ case "hourly":
6321
+ return this.getSpendingInWindow(now - HOUR_MS, now);
6322
+ case "daily":
6323
+ return this.getSpendingInWindow(now - DAY_MS, now);
6324
+ case "session":
6325
+ return this.sessionSpent;
6326
+ }
6327
+ }
6328
+ getRemaining(window) {
6329
+ const limit = this.limits[window];
6330
+ if (limit === void 0) return null;
6331
+ return Math.max(0, limit - this.getSpending(window));
6332
+ }
6333
+ getStatus() {
6334
+ const now = this.now();
6335
+ const hourlySpent = this.getSpendingInWindow(now - HOUR_MS, now);
6336
+ const dailySpent = this.getSpendingInWindow(now - DAY_MS, now);
6337
+ return {
6338
+ limits: { ...this.limits },
6339
+ spending: {
6340
+ hourly: hourlySpent,
6341
+ daily: dailySpent,
6342
+ session: this.sessionSpent
6343
+ },
6344
+ remaining: {
6345
+ hourly: this.limits.hourly !== void 0 ? this.limits.hourly - hourlySpent : null,
6346
+ daily: this.limits.daily !== void 0 ? this.limits.daily - dailySpent : null,
6347
+ session: this.limits.session !== void 0 ? this.limits.session - this.sessionSpent : null
6348
+ },
6349
+ calls: this.sessionCalls
6350
+ };
6351
+ }
6352
+ getHistory(limit) {
6353
+ const records = [...this.history].reverse();
6354
+ return limit ? records.slice(0, limit) : records;
6355
+ }
6356
+ resetSession() {
6357
+ this.sessionSpent = 0;
6358
+ this.sessionCalls = 0;
6359
+ }
6360
+ cleanup() {
6361
+ const cutoff = this.now() - DAY_MS;
6362
+ this.history = this.history.filter((r) => r.timestamp >= cutoff);
6363
+ }
6364
+ save() {
6365
+ this.storage.save({
6366
+ limits: { ...this.limits },
6367
+ history: [...this.history]
6368
+ });
6369
+ }
6370
+ load() {
6371
+ const data = this.storage.load();
6372
+ if (data) {
6373
+ this.limits = data.limits;
6374
+ this.history = data.history;
6375
+ this.cleanup();
6376
+ }
6377
+ }
6378
+ };
6379
+ function formatDuration(seconds) {
6380
+ if (seconds < 60) {
6381
+ return `${seconds}s`;
6382
+ } else if (seconds < 3600) {
6383
+ const mins = Math.ceil(seconds / 60);
6384
+ return `${mins} min`;
6385
+ } else {
6386
+ const hours = Math.floor(seconds / 3600);
6387
+ const mins = Math.ceil(seconds % 3600 / 60);
6388
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
6389
+ }
6597
6390
  }
6598
6391
 
6392
+ // src/index.ts
6393
+ init_wallet();
6394
+
6599
6395
  // src/retry.ts
6600
6396
  var DEFAULT_RETRY_CONFIG = {
6601
6397
  maxRetries: 2,
@@ -6653,6 +6449,7 @@ function isRetryable(errorOrResponse, config) {
6653
6449
  }
6654
6450
 
6655
6451
  // src/index.ts
6452
+ init_partners();
6656
6453
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
6657
6454
  const start = Date.now();
6658
6455
  while (Date.now() - start < timeoutMs) {
@@ -6674,13 +6471,13 @@ function isGatewayMode() {
6674
6471
  return args.includes("gateway");
6675
6472
  }
6676
6473
  function injectModelsConfig(logger) {
6677
- const configDir = join5(homedir4(), ".openclaw");
6678
- const configPath = join5(configDir, "openclaw.json");
6474
+ const configDir = join6(homedir5(), ".openclaw");
6475
+ const configPath = join6(configDir, "openclaw.json");
6679
6476
  let config = {};
6680
6477
  let needsWrite = false;
6681
- if (!existsSync(configDir)) {
6478
+ if (!existsSync2(configDir)) {
6682
6479
  try {
6683
- mkdirSync(configDir, { recursive: true });
6480
+ mkdirSync2(configDir, { recursive: true });
6684
6481
  logger.info("Created OpenClaw config directory");
6685
6482
  } catch (err) {
6686
6483
  logger.info(
@@ -6689,7 +6486,7 @@ function injectModelsConfig(logger) {
6689
6486
  return;
6690
6487
  }
6691
6488
  }
6692
- if (existsSync(configPath)) {
6489
+ if (existsSync2(configPath)) {
6693
6490
  try {
6694
6491
  const content = readTextFileSync(configPath).trim();
6695
6492
  if (content) {
@@ -6789,52 +6586,60 @@ function injectModelsConfig(logger) {
6789
6586
  logger.info("Set default model to blockrun/auto (first install)");
6790
6587
  needsWrite = true;
6791
6588
  }
6792
- const TOP_MODELS = [
6793
- "auto",
6794
- "free",
6795
- "eco",
6796
- "premium",
6797
- "anthropic/claude-sonnet-4.6",
6798
- "anthropic/claude-opus-4.6",
6799
- "anthropic/claude-haiku-4.5",
6800
- "openai/gpt-5.2",
6801
- "openai/gpt-4o",
6802
- "openai/o3",
6803
- "google/gemini-3.1-pro",
6804
- "google/gemini-3-flash-preview",
6805
- "deepseek/deepseek-chat",
6806
- "moonshot/kimi-k2.5",
6807
- "xai/grok-3",
6808
- "minimax/minimax-m2.5"
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" }
6809
6606
  ];
6810
- if (!defaults.models || typeof defaults.models !== "object" || Array.isArray(defaults.models)) {
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
6615
+ ];
6616
+ if (!defaults.models) {
6811
6617
  defaults.models = {};
6812
6618
  needsWrite = true;
6813
6619
  }
6814
6620
  const allowlist = defaults.models;
6815
- const topSet = new Set(TOP_MODELS.map((id) => `blockrun/${id}`));
6816
- for (const key of Object.keys(allowlist)) {
6817
- if (key.startsWith("blockrun/") && !topSet.has(key)) {
6818
- delete allowlist[key];
6621
+ for (const deprecated of DEPRECATED_ALIASES) {
6622
+ if (allowlist[deprecated]) {
6623
+ delete allowlist[deprecated];
6624
+ logger.info(`Removed deprecated model alias: ${deprecated}`);
6819
6625
  needsWrite = true;
6820
6626
  }
6821
6627
  }
6822
- let addedCount = 0;
6823
- for (const id of TOP_MODELS) {
6824
- const key = `blockrun/${id}`;
6825
- if (!allowlist[key]) {
6826
- allowlist[key] = {};
6827
- addedCount++;
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;
6828
6637
  }
6829
6638
  }
6830
- if (addedCount > 0) {
6831
- needsWrite = true;
6832
- logger.info(`Added ${addedCount} models to allowlist (${TOP_MODELS.length} total)`);
6833
- }
6834
6639
  if (needsWrite) {
6835
6640
  try {
6836
6641
  const tmpPath = `${configPath}.tmp.${process.pid}`;
6837
- writeFileSync(tmpPath, JSON.stringify(config, null, 2));
6642
+ writeFileSync2(tmpPath, JSON.stringify(config, null, 2));
6838
6643
  renameSync(tmpPath, configPath);
6839
6644
  logger.info("Smart routing enabled (blockrun/auto)");
6840
6645
  } catch (err) {
@@ -6843,10 +6648,10 @@ function injectModelsConfig(logger) {
6843
6648
  }
6844
6649
  }
6845
6650
  function injectAuthProfile(logger) {
6846
- const agentsDir = join5(homedir4(), ".openclaw", "agents");
6847
- if (!existsSync(agentsDir)) {
6651
+ const agentsDir = join6(homedir5(), ".openclaw", "agents");
6652
+ if (!existsSync2(agentsDir)) {
6848
6653
  try {
6849
- mkdirSync(agentsDir, { recursive: true });
6654
+ mkdirSync2(agentsDir, { recursive: true });
6850
6655
  } catch (err) {
6851
6656
  logger.info(
6852
6657
  `Could not create agents dir: ${err instanceof Error ? err.message : String(err)}`
@@ -6860,11 +6665,11 @@ function injectAuthProfile(logger) {
6860
6665
  agents = ["main", ...agents];
6861
6666
  }
6862
6667
  for (const agentId of agents) {
6863
- const authDir = join5(agentsDir, agentId, "agent");
6864
- const authPath = join5(authDir, "auth-profiles.json");
6865
- if (!existsSync(authDir)) {
6668
+ const authDir = join6(agentsDir, agentId, "agent");
6669
+ const authPath = join6(authDir, "auth-profiles.json");
6670
+ if (!existsSync2(authDir)) {
6866
6671
  try {
6867
- mkdirSync(authDir, { recursive: true });
6672
+ mkdirSync2(authDir, { recursive: true });
6868
6673
  } catch {
6869
6674
  continue;
6870
6675
  }
@@ -6873,7 +6678,7 @@ function injectAuthProfile(logger) {
6873
6678
  version: 1,
6874
6679
  profiles: {}
6875
6680
  };
6876
- if (existsSync(authPath)) {
6681
+ if (existsSync2(authPath)) {
6877
6682
  try {
6878
6683
  const existing = JSON.parse(readTextFileSync(authPath));
6879
6684
  if (existing.version && existing.profiles) {
@@ -6892,7 +6697,7 @@ function injectAuthProfile(logger) {
6892
6697
  key: "x402-proxy-handles-auth"
6893
6698
  };
6894
6699
  try {
6895
- writeFileSync(authPath, JSON.stringify(store, null, 2));
6700
+ writeFileSync2(authPath, JSON.stringify(store, null, 2));
6896
6701
  logger.info(`Injected BlockRun auth profile for agent: ${agentId}`);
6897
6702
  } catch (err) {
6898
6703
  logger.info(
@@ -6906,22 +6711,22 @@ function injectAuthProfile(logger) {
6906
6711
  }
6907
6712
  var activeProxyHandle = null;
6908
6713
  async function startProxyInBackground(api) {
6909
- const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
6910
- if (source === "generated") {
6714
+ const wallet = await resolveOrGenerateWalletKey();
6715
+ if (wallet.source === "generated") {
6911
6716
  api.logger.warn(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6912
6717
  api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
6913
- api.logger.warn(` Address : ${address}`);
6718
+ api.logger.warn(` Address : ${wallet.address}`);
6914
6719
  api.logger.warn(` Run /wallet export to get your private key`);
6915
6720
  api.logger.warn(` Losing this key = losing your USDC funds`);
6916
6721
  api.logger.warn(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6917
- } else if (source === "saved") {
6918
- api.logger.info(`Using saved wallet: ${address}`);
6722
+ } else if (wallet.source === "saved") {
6723
+ api.logger.info(`Using saved wallet: ${wallet.address}`);
6919
6724
  } else {
6920
- api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);
6725
+ api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${wallet.address}`);
6921
6726
  }
6922
6727
  const routingConfig = api.pluginConfig?.routing;
6923
6728
  const proxy = await startProxy({
6924
- walletKey,
6729
+ wallet,
6925
6730
  routingConfig,
6926
6731
  onReady: (port) => {
6927
6732
  api.logger.info(`BlockRun x402 proxy listening on port ${port}`);
@@ -6949,18 +6754,18 @@ async function startProxyInBackground(api) {
6949
6754
  activeProxyHandle = proxy;
6950
6755
  api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
6951
6756
  api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
6952
- const startupMonitor = new BalanceMonitor(address);
6757
+ const startupMonitor = new BalanceMonitor(wallet.address);
6953
6758
  startupMonitor.checkBalance().then((balance) => {
6954
6759
  if (balance.isEmpty) {
6955
- api.logger.info(`Wallet: ${address} | Balance: $0.00`);
6760
+ api.logger.info(`Wallet: ${wallet.address} | Balance: $0.00`);
6956
6761
  api.logger.info(`Using FREE model. Fund wallet for premium models.`);
6957
6762
  } else if (balance.isLow) {
6958
- api.logger.info(`Wallet: ${address} | Balance: ${balance.balanceUSD} (low)`);
6763
+ api.logger.info(`Wallet: ${wallet.address} | Balance: ${balance.balanceUSD} (low)`);
6959
6764
  } else {
6960
- api.logger.info(`Wallet: ${address} | Balance: ${balance.balanceUSD}`);
6765
+ api.logger.info(`Wallet: ${wallet.address} | Balance: ${balance.balanceUSD}`);
6961
6766
  }
6962
6767
  }).catch(() => {
6963
- api.logger.info(`Wallet: ${address} | Balance: (checking...)`);
6768
+ api.logger.info(`Wallet: ${wallet.address} | Balance: (checking...)`);
6964
6769
  });
6965
6770
  }
6966
6771
  async function createStatsCommand() {
@@ -6998,7 +6803,7 @@ async function createWalletCommand() {
6998
6803
  let walletKey;
6999
6804
  let address;
7000
6805
  try {
7001
- if (existsSync(WALLET_FILE)) {
6806
+ if (existsSync2(WALLET_FILE)) {
7002
6807
  walletKey = readTextFileSync(WALLET_FILE).trim();
7003
6808
  if (walletKey.startsWith("0x") && walletKey.length === 66) {
7004
6809
  const account = privateKeyToAccount4(walletKey);
@@ -7016,48 +6821,176 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
7016
6821
  };
7017
6822
  }
7018
6823
  if (subcommand === "export") {
7019
- return {
7020
- text: [
7021
- "\u{1F510} **ClawRouter Wallet Export**",
7022
- "",
7023
- "\u26A0\uFE0F **SECURITY WARNING**: Your private key controls your wallet funds.",
7024
- "Never share this key. Anyone with this key can spend your USDC.",
7025
- "",
7026
- `**Address:** \`${address}\``,
7027
- "",
7028
- `**Private Key:**`,
7029
- `\`${walletKey}\``,
7030
- "",
7031
- "**To restore on a new machine:**",
7032
- "1. Set the environment variable before running OpenClaw:",
7033
- ` \`export BLOCKRUN_WALLET_KEY=${walletKey}\``,
7034
- "2. Or save to file:",
7035
- ` \`mkdir -p ~/.openclaw/blockrun && echo "${walletKey}" > ~/.openclaw/blockrun/wallet.key && chmod 600 ~/.openclaw/blockrun/wallet.key\``
7036
- ].join("\n")
7037
- };
6824
+ const lines = [
6825
+ "**ClawRouter Wallet Export**",
6826
+ "",
6827
+ "**SECURITY WARNING**: Your private key and mnemonic control your wallet funds.",
6828
+ "Never share these. Anyone with them can spend your USDC.",
6829
+ "",
6830
+ "**EVM (Base):**",
6831
+ ` Address: \`${address}\``,
6832
+ ` Private Key: \`${walletKey}\``
6833
+ ];
6834
+ let hasMnemonic = false;
6835
+ try {
6836
+ if (existsSync2(MNEMONIC_FILE)) {
6837
+ const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
6838
+ if (mnemonic) {
6839
+ hasMnemonic = true;
6840
+ const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
6841
+ const solKeyBytes = deriveSolanaKeyBytes2(mnemonic);
6842
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
6843
+ const signer = await createKeyPairSignerFromPrivateKeyBytes(solKeyBytes);
6844
+ lines.push(
6845
+ "",
6846
+ "**Solana:**",
6847
+ ` Address: \`${signer.address}\``,
6848
+ ` (Derived from mnemonic below)`,
6849
+ "",
6850
+ "**Mnemonic (24 words):**",
6851
+ `\`${mnemonic}\``,
6852
+ "",
6853
+ "CRITICAL: Back up this mnemonic. It is the ONLY way to recover your Solana wallet."
6854
+ );
6855
+ }
6856
+ }
6857
+ } catch {
6858
+ }
6859
+ lines.push(
6860
+ "",
6861
+ "**To restore on a new machine:**",
6862
+ "1. Set the environment variable before running OpenClaw:",
6863
+ ` \`export BLOCKRUN_WALLET_KEY=${walletKey}\``,
6864
+ "2. Or save to file:",
6865
+ ` \`mkdir -p ~/.openclaw/blockrun && echo "${walletKey}" > ~/.openclaw/blockrun/wallet.key && chmod 600 ~/.openclaw/blockrun/wallet.key\``
6866
+ );
6867
+ if (hasMnemonic) {
6868
+ lines.push(
6869
+ "3. Restore the mnemonic for Solana:",
6870
+ ` \`echo "<your mnemonic>" > ~/.openclaw/blockrun/mnemonic && chmod 600 ~/.openclaw/blockrun/mnemonic\``
6871
+ );
6872
+ }
6873
+ return { text: lines.join("\n") };
6874
+ }
6875
+ if (subcommand === "solana") {
6876
+ try {
6877
+ let solanaAddr;
6878
+ if (existsSync2(MNEMONIC_FILE)) {
6879
+ const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
6880
+ if (existingMnemonic) {
6881
+ await savePaymentChain("solana");
6882
+ const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
6883
+ const solKeyBytes = deriveSolanaKeyBytes2(existingMnemonic);
6884
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
6885
+ const signer2 = await createKeyPairSignerFromPrivateKeyBytes2(solKeyBytes);
6886
+ solanaAddr = signer2.address;
6887
+ return {
6888
+ text: [
6889
+ "Payment chain set to Solana. Restart the gateway to apply.",
6890
+ "",
6891
+ `**Solana Address:** \`${solanaAddr}\``,
6892
+ `**Fund with USDC on Solana:** https://solscan.io/account/${solanaAddr}`
6893
+ ].join("\n")
6894
+ };
6895
+ }
6896
+ }
6897
+ const { solanaPrivateKeyBytes } = await setupSolana();
6898
+ await savePaymentChain("solana");
6899
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
6900
+ const signer = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
6901
+ return {
6902
+ text: [
6903
+ "**Solana Wallet Set Up**",
6904
+ "",
6905
+ `**Solana Address:** \`${signer.address}\``,
6906
+ `**Mnemonic File:** \`${MNEMONIC_FILE}\``,
6907
+ "",
6908
+ "Your existing EVM wallet is unchanged.",
6909
+ "Payment chain set to Solana. Restart the gateway to apply.",
6910
+ "",
6911
+ `**Fund with USDC on Solana:** https://solscan.io/account/${signer.address}`
6912
+ ].join("\n")
6913
+ };
6914
+ } catch (err) {
6915
+ return {
6916
+ text: `Failed to set up Solana: ${err instanceof Error ? err.message : String(err)}`,
6917
+ isError: true
6918
+ };
6919
+ }
6920
+ }
6921
+ if (subcommand === "base") {
6922
+ try {
6923
+ await savePaymentChain("base");
6924
+ return {
6925
+ text: "Payment chain set to Base (EVM). Restart the gateway to apply."
6926
+ };
6927
+ } catch (err) {
6928
+ return {
6929
+ text: `Failed to set payment chain: ${err instanceof Error ? err.message : String(err)}`,
6930
+ isError: true
6931
+ };
6932
+ }
7038
6933
  }
7039
- let balanceText = "Balance: (checking...)";
6934
+ let evmBalanceText = "Balance: (checking...)";
7040
6935
  try {
7041
6936
  const monitor = new BalanceMonitor(address);
7042
6937
  const balance = await monitor.checkBalance();
7043
- balanceText = `Balance: ${balance.balanceUSD}`;
6938
+ evmBalanceText = `Balance: ${balance.balanceUSD}`;
6939
+ } catch {
6940
+ evmBalanceText = "Balance: (could not check)";
6941
+ }
6942
+ let solanaSection = "";
6943
+ try {
6944
+ if (existsSync2(MNEMONIC_FILE)) {
6945
+ const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
6946
+ const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
6947
+ if (mnemonic) {
6948
+ const solKeyBytes = deriveSolanaKeyBytes2(mnemonic);
6949
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
6950
+ const signer = await createKeyPairSignerFromPrivateKeyBytes(solKeyBytes);
6951
+ const solAddr = signer.address;
6952
+ let solBalanceText = "Balance: (checking...)";
6953
+ try {
6954
+ const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
6955
+ const solMonitor = new SolanaBalanceMonitor2(solAddr);
6956
+ const solBalance = await solMonitor.checkBalance();
6957
+ solBalanceText = `Balance: ${solBalance.balanceUSD}`;
6958
+ } catch {
6959
+ solBalanceText = "Balance: (could not check)";
6960
+ }
6961
+ solanaSection = [
6962
+ "",
6963
+ "**Solana:**",
6964
+ ` Address: \`${solAddr}\``,
6965
+ ` ${solBalanceText}`,
6966
+ ` Fund: https://solscan.io/account/${solAddr}`
6967
+ ].join("\n");
6968
+ }
6969
+ }
7044
6970
  } catch {
7045
- balanceText = "Balance: (could not check)";
7046
6971
  }
6972
+ const currentChain = await resolvePaymentChain();
7047
6973
  return {
7048
6974
  text: [
7049
- "\u{1F99E} **ClawRouter Wallet**",
6975
+ "**ClawRouter Wallet**",
6976
+ "",
6977
+ `**Payment Chain:** ${currentChain === "solana" ? "Solana" : "Base (EVM)"}`,
6978
+ "",
6979
+ "**Base (EVM):**",
6980
+ ` Address: \`${address}\``,
6981
+ ` ${evmBalanceText}`,
6982
+ ` Fund: https://basescan.org/address/${address}`,
6983
+ solanaSection,
7050
6984
  "",
7051
- `**Address:** \`${address}\``,
7052
- `**${balanceText}**`,
7053
6985
  `**Key File:** \`${WALLET_FILE}\``,
7054
6986
  "",
7055
6987
  "**Commands:**",
7056
6988
  "\u2022 `/wallet` - Show this status",
7057
6989
  "\u2022 `/wallet export` - Export private key for backup",
7058
- "",
7059
- `**Fund with USDC on Base:** https://basescan.org/address/${address}`
7060
- ].join("\n")
6990
+ !solanaSection ? "\u2022 `/wallet solana` - Enable Solana payments" : "",
6991
+ solanaSection ? "\u2022 `/wallet base` - Switch to Base (EVM)" : "",
6992
+ solanaSection ? "\u2022 `/wallet solana` - Switch to Solana" : ""
6993
+ ].filter(Boolean).join("\n")
7061
6994
  };
7062
6995
  }
7063
6996
  };
@@ -7067,7 +7000,7 @@ var plugin = {
7067
7000
  name: "ClawRouter",
7068
7001
  description: "Smart LLM router \u2014 30+ models, x402 micropayments, 78% cost savings",
7069
7002
  version: VERSION,
7070
- register(api) {
7003
+ async register(api) {
7071
7004
  const isDisabled = process["env"].CLAWROUTER_DISABLED === "true" || process["env"].CLAWROUTER_DISABLED === "1";
7072
7005
  if (isDisabled) {
7073
7006
  api.logger.info("ClawRouter disabled (CLAWROUTER_DISABLED=true). Using default routing.");
@@ -7096,15 +7029,14 @@ var plugin = {
7096
7029
  };
7097
7030
  api.logger.info("BlockRun provider registered (30+ models via x402)");
7098
7031
  try {
7032
+ const { buildPartnerTools: buildPartnerTools2, PARTNER_SERVICES: PARTNER_SERVICES2 } = await Promise.resolve().then(() => (init_partners(), partners_exports));
7099
7033
  const proxyBaseUrl = `http://127.0.0.1:${runtimePort}`;
7100
- const partnerTools = buildPartnerTools(proxyBaseUrl);
7034
+ const partnerTools = buildPartnerTools2(proxyBaseUrl);
7101
7035
  for (const tool of partnerTools) {
7102
7036
  api.registerTool(tool);
7103
7037
  }
7104
7038
  if (partnerTools.length > 0) {
7105
- api.logger.info(
7106
- `Registered ${partnerTools.length} partner tool(s): ${partnerTools.map((t) => t.name).join(", ")}`
7107
- );
7039
+ api.logger.info(`Registered ${partnerTools.length} partner tool(s): ${partnerTools.map((t) => t.name).join(", ")}`);
7108
7040
  }
7109
7041
  api.registerCommand({
7110
7042
  name: "partners",
@@ -7112,20 +7044,19 @@ var plugin = {
7112
7044
  acceptsArgs: false,
7113
7045
  requireAuth: false,
7114
7046
  handler: async () => {
7115
- if (PARTNER_SERVICES.length === 0) {
7047
+ if (PARTNER_SERVICES2.length === 0) {
7116
7048
  return { text: "No partner APIs available." };
7117
7049
  }
7118
- const lines = ["**Partner APIs** (paid via your ClawRouter wallet)", ""];
7119
- for (const svc of PARTNER_SERVICES) {
7050
+ const lines = [
7051
+ "**Partner APIs** (paid via your ClawRouter wallet)",
7052
+ ""
7053
+ ];
7054
+ for (const svc of PARTNER_SERVICES2) {
7120
7055
  lines.push(`**${svc.name}** (${svc.partner})`);
7121
7056
  lines.push(` ${svc.description}`);
7122
7057
  lines.push(` Tool: \`${`blockrun_${svc.id}`}\``);
7123
- lines.push(
7124
- ` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`
7125
- );
7126
- lines.push(
7127
- ` **How to use:** Ask "Look up Twitter user @elonmusk" or "Get info on these X accounts: @naval, @balajis"`
7128
- );
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"`);
7129
7060
  lines.push("");
7130
7061
  }
7131
7062
  return { text: lines.join("\n") };
@@ -7212,23 +7143,30 @@ export {
7212
7143
  DEFAULT_ROUTING_CONFIG,
7213
7144
  DEFAULT_SESSION_CONFIG,
7214
7145
  EmptyWalletError,
7146
+ FileSpendControlStorage,
7147
+ InMemorySpendControlStorage,
7215
7148
  InsufficientFundsError,
7216
7149
  MODEL_ALIASES,
7217
7150
  OPENCLAW_MODELS,
7218
7151
  PARTNER_SERVICES,
7219
- PaymentCache,
7220
7152
  RequestDeduplicator,
7221
7153
  ResponseCache,
7222
7154
  RpcError,
7223
7155
  SessionStore,
7156
+ SolanaBalanceMonitor,
7157
+ SpendControl,
7224
7158
  blockrunProvider,
7225
7159
  buildPartnerTools,
7226
7160
  buildProviderModels,
7227
7161
  calculateModelCost,
7228
- createPaymentFetch,
7229
7162
  index_default as default,
7163
+ deriveAllKeys,
7164
+ deriveEvmKey,
7165
+ deriveSolanaKeyBytes,
7230
7166
  fetchWithRetry,
7167
+ formatDuration,
7231
7168
  formatStatsAscii,
7169
+ generateWalletMnemonic,
7232
7170
  getAgenticModels,
7233
7171
  getFallbackChain,
7234
7172
  getFallbackChainFiltered,
@@ -7237,16 +7175,20 @@ export {
7237
7175
  getProxyPort,
7238
7176
  getSessionId,
7239
7177
  getStats,
7240
- hashRequestContent,
7241
7178
  isAgenticModel,
7242
7179
  isBalanceError,
7243
7180
  isEmptyWalletError,
7244
7181
  isInsufficientFundsError,
7245
7182
  isRetryable,
7246
7183
  isRpcError,
7184
+ isValidMnemonic,
7185
+ loadPaymentChain,
7247
7186
  logUsage,
7248
7187
  resolveModelAlias,
7188
+ resolvePaymentChain,
7249
7189
  route,
7190
+ savePaymentChain,
7191
+ setupSolana,
7250
7192
  startProxy
7251
7193
  };
7252
7194
  //# sourceMappingURL=index.js.map