@blockrun/clawrouter 0.11.11 → 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/cli.js CHANGED
@@ -3,271 +3,72 @@
3
3
  // src/proxy.ts
4
4
  import { createServer } from "http";
5
5
  import { finished } from "stream";
6
- import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
7
-
8
- // src/x402.ts
9
- import { signTypedData, privateKeyToAccount } from "viem/accounts";
6
+ import { createPublicClient as createPublicClient2, http as http2 } from "viem";
7
+ import { base as base2 } from "viem/chains";
8
+ import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
9
+ import { x402Client } from "@x402/fetch";
10
10
 
11
- // src/payment-cache.ts
11
+ // src/payment-preauth.ts
12
+ import { x402HTTPClient } from "@x402/fetch";
12
13
  var DEFAULT_TTL_MS = 36e5;
13
- var PaymentCache = class {
14
- cache = /* @__PURE__ */ new Map();
15
- ttlMs;
16
- constructor(ttlMs = DEFAULT_TTL_MS) {
17
- this.ttlMs = ttlMs;
18
- }
19
- /** Get cached payment params for an endpoint path. */
20
- get(endpointPath) {
21
- const entry = this.cache.get(endpointPath);
22
- if (!entry) return void 0;
23
- if (Date.now() - entry.cachedAt > this.ttlMs) {
24
- this.cache.delete(endpointPath);
25
- return void 0;
26
- }
27
- return entry;
28
- }
29
- /** Cache payment params from a 402 response. */
30
- set(endpointPath, params) {
31
- this.cache.set(endpointPath, { ...params, cachedAt: Date.now() });
32
- }
33
- /** Invalidate cache for an endpoint (e.g., if payTo changed). */
34
- invalidate(endpointPath) {
35
- this.cache.delete(endpointPath);
36
- }
37
- };
38
-
39
- // src/x402.ts
40
- var BASE_CHAIN_ID = 8453;
41
- var BASE_SEPOLIA_CHAIN_ID = 84532;
42
- var DEFAULT_TOKEN_NAME = "USD Coin";
43
- var DEFAULT_TOKEN_VERSION = "2";
44
- var DEFAULT_NETWORK = "eip155:8453";
45
- var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
46
- var TRANSFER_TYPES = {
47
- TransferWithAuthorization: [
48
- { name: "from", type: "address" },
49
- { name: "to", type: "address" },
50
- { name: "value", type: "uint256" },
51
- { name: "validAfter", type: "uint256" },
52
- { name: "validBefore", type: "uint256" },
53
- { name: "nonce", type: "bytes32" }
54
- ]
55
- };
56
- function createNonce() {
57
- const bytes = new Uint8Array(32);
58
- crypto.getRandomValues(bytes);
59
- return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
60
- }
61
- function decodeBase64Json(value) {
62
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
63
- const padding = (4 - normalized.length % 4) % 4;
64
- const padded = normalized + "=".repeat(padding);
65
- const decoded = Buffer.from(padded, "base64").toString("utf8");
66
- return JSON.parse(decoded);
67
- }
68
- function encodeBase64Json(value) {
69
- return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
70
- }
71
- function parsePaymentRequired(headerValue) {
72
- return decodeBase64Json(headerValue);
73
- }
74
- function normalizeNetwork(network) {
75
- if (!network || network.trim().length === 0) {
76
- return DEFAULT_NETWORK;
77
- }
78
- return network.trim().toLowerCase();
79
- }
80
- function resolveChainId(network) {
81
- const eip155Match = network.match(/^eip155:(\d+)$/i);
82
- if (eip155Match) {
83
- const parsed = Number.parseInt(eip155Match[1], 10);
84
- if (Number.isFinite(parsed) && parsed > 0) {
85
- return parsed;
86
- }
87
- }
88
- if (network === "base") return BASE_CHAIN_ID;
89
- if (network === "base-sepolia") return BASE_SEPOLIA_CHAIN_ID;
90
- return BASE_CHAIN_ID;
91
- }
92
- function parseHexAddress(value) {
93
- if (!value) return void 0;
94
- const direct = value.match(/^0x[a-fA-F0-9]{40}$/);
95
- if (direct) {
96
- return direct[0];
97
- }
98
- const caipSuffix = value.match(/0x[a-fA-F0-9]{40}$/);
99
- if (caipSuffix) {
100
- return caipSuffix[0];
101
- }
102
- return void 0;
103
- }
104
- function requireHexAddress(value, field) {
105
- const parsed = parseHexAddress(value);
106
- if (!parsed) {
107
- throw new Error(`Invalid ${field} in payment requirements: ${String(value)}`);
108
- }
109
- return parsed;
110
- }
111
- function setPaymentHeaders(headers, payload) {
112
- headers.set("payment-signature", payload);
113
- headers.set("x-payment", payload);
114
- }
115
- async function createPaymentPayload(privateKey, fromAddress, option, amount, requestUrl, resource) {
116
- const network = normalizeNetwork(option.network);
117
- const chainId = resolveChainId(network);
118
- const recipient = requireHexAddress(option.payTo, "payTo");
119
- const verifyingContract = requireHexAddress(option.asset, "asset");
120
- const maxTimeoutSeconds = typeof option.maxTimeoutSeconds === "number" && option.maxTimeoutSeconds > 0 ? Math.floor(option.maxTimeoutSeconds) : DEFAULT_MAX_TIMEOUT_SECONDS;
121
- const now = Math.floor(Date.now() / 1e3);
122
- const validAfter = now - 600;
123
- const validBefore = now + maxTimeoutSeconds;
124
- const nonce = createNonce();
125
- const signature = await signTypedData({
126
- privateKey,
127
- domain: {
128
- name: option.extra?.name || DEFAULT_TOKEN_NAME,
129
- version: option.extra?.version || DEFAULT_TOKEN_VERSION,
130
- chainId,
131
- verifyingContract
132
- },
133
- types: TRANSFER_TYPES,
134
- primaryType: "TransferWithAuthorization",
135
- message: {
136
- from: fromAddress,
137
- to: recipient,
138
- value: BigInt(amount),
139
- validAfter: BigInt(validAfter),
140
- validBefore: BigInt(validBefore),
141
- nonce
142
- }
143
- });
144
- const paymentData = {
145
- x402Version: 2,
146
- resource: {
147
- url: resource?.url || requestUrl,
148
- description: resource?.description || "BlockRun AI API call",
149
- mimeType: "application/json"
150
- },
151
- accepted: {
152
- scheme: option.scheme,
153
- network,
154
- amount,
155
- asset: option.asset,
156
- payTo: option.payTo,
157
- maxTimeoutSeconds: option.maxTimeoutSeconds,
158
- extra: option.extra
159
- },
160
- payload: {
161
- signature,
162
- authorization: {
163
- from: fromAddress,
164
- to: recipient,
165
- value: amount,
166
- validAfter: validAfter.toString(),
167
- validBefore: validBefore.toString(),
168
- nonce
169
- }
170
- },
171
- extensions: {}
172
- };
173
- return encodeBase64Json(paymentData);
174
- }
175
- function createPaymentFetch(privateKey) {
176
- const account = privateKeyToAccount(privateKey);
177
- const walletAddress = account.address;
178
- const paymentCache = new PaymentCache();
179
- const payFetch = async (input, init, preAuth) => {
180
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
181
- const endpointPath = new URL(url).pathname;
182
- const cached = paymentCache.get(endpointPath);
183
- if (cached && preAuth?.estimatedAmount) {
184
- const paymentPayload = await createPaymentPayload(
185
- privateKey,
186
- walletAddress,
187
- {
188
- scheme: cached.scheme,
189
- network: cached.network,
190
- asset: cached.asset,
191
- payTo: cached.payTo,
192
- maxTimeoutSeconds: cached.maxTimeoutSeconds,
193
- extra: cached.extra
194
- },
195
- preAuth.estimatedAmount,
196
- url,
197
- {
198
- url: cached.resourceUrl,
199
- description: cached.resourceDescription
14
+ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS) {
15
+ const httpClient = new x402HTTPClient(client);
16
+ const cache = /* @__PURE__ */ new Map();
17
+ return async (input, init) => {
18
+ const request = new Request(input, init);
19
+ const urlPath = new URL(request.url).pathname;
20
+ const cached = cache.get(urlPath);
21
+ if (cached && Date.now() - cached.cachedAt < ttlMs) {
22
+ try {
23
+ const payload2 = await client.createPaymentPayload(cached.paymentRequired);
24
+ const headers = httpClient.encodePaymentSignatureHeader(payload2);
25
+ const preAuthRequest = request.clone();
26
+ for (const [key, value] of Object.entries(headers)) {
27
+ preAuthRequest.headers.set(key, value);
200
28
  }
201
- );
202
- const preAuthHeaders = new Headers(init?.headers);
203
- setPaymentHeaders(preAuthHeaders, paymentPayload);
204
- const response2 = await fetch(input, { ...init, headers: preAuthHeaders });
205
- if (response2.status !== 402) {
206
- return response2;
207
- }
208
- const paymentHeader2 = response2.headers.get("x-payment-required");
209
- if (paymentHeader2) {
210
- return handle402(input, init, url, endpointPath, paymentHeader2);
211
- }
212
- paymentCache.invalidate(endpointPath);
213
- const cleanResponse = await fetch(input, init);
214
- if (cleanResponse.status !== 402) {
215
- return cleanResponse;
216
- }
217
- const cleanHeader = cleanResponse.headers.get("x-payment-required");
218
- if (!cleanHeader) {
219
- throw new Error("402 response missing x-payment-required header");
29
+ const response2 = await baseFetch(preAuthRequest);
30
+ if (response2.status !== 402) {
31
+ return response2;
32
+ }
33
+ cache.delete(urlPath);
34
+ } catch {
35
+ cache.delete(urlPath);
220
36
  }
221
- return handle402(input, init, url, endpointPath, cleanHeader);
222
37
  }
223
- const response = await fetch(input, init);
38
+ const clonedRequest = request.clone();
39
+ const response = await baseFetch(request);
224
40
  if (response.status !== 402) {
225
41
  return response;
226
42
  }
227
- const paymentHeader = response.headers.get("x-payment-required");
228
- if (!paymentHeader) {
229
- throw new Error("402 response missing x-payment-required header");
43
+ let paymentRequired;
44
+ try {
45
+ const getHeader = (name) => response.headers.get(name);
46
+ let body;
47
+ try {
48
+ const responseText = await response.text();
49
+ if (responseText) body = JSON.parse(responseText);
50
+ } catch {
51
+ }
52
+ paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
53
+ cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });
54
+ } catch (error) {
55
+ throw new Error(
56
+ `Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`
57
+ );
58
+ }
59
+ const payload = await client.createPaymentPayload(paymentRequired);
60
+ const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
61
+ for (const [key, value] of Object.entries(paymentHeaders)) {
62
+ clonedRequest.headers.set(key, value);
230
63
  }
231
- return handle402(input, init, url, endpointPath, paymentHeader);
64
+ return baseFetch(clonedRequest);
232
65
  };
233
- async function handle402(input, init, url, endpointPath, paymentHeader) {
234
- const paymentRequired = parsePaymentRequired(paymentHeader);
235
- const option = paymentRequired.accepts?.[0];
236
- if (!option) {
237
- throw new Error("No payment options in 402 response");
238
- }
239
- const amount = option.amount || option.maxAmountRequired;
240
- if (!amount) {
241
- throw new Error("No amount in payment requirements");
242
- }
243
- paymentCache.set(endpointPath, {
244
- payTo: option.payTo,
245
- asset: option.asset,
246
- scheme: option.scheme,
247
- network: option.network,
248
- extra: option.extra,
249
- maxTimeoutSeconds: option.maxTimeoutSeconds,
250
- resourceUrl: paymentRequired.resource?.url,
251
- resourceDescription: paymentRequired.resource?.description
252
- });
253
- const paymentPayload = await createPaymentPayload(
254
- privateKey,
255
- walletAddress,
256
- option,
257
- amount,
258
- url,
259
- paymentRequired.resource
260
- );
261
- const retryHeaders = new Headers(init?.headers);
262
- setPaymentHeaders(retryHeaders, paymentPayload);
263
- return fetch(input, {
264
- ...init,
265
- headers: retryHeaders
266
- });
267
- }
268
- return { fetch: payFetch, cache: paymentCache };
269
66
  }
270
67
 
68
+ // src/proxy.ts
69
+ import { registerExactEvmScheme } from "@x402/evm/exact/client";
70
+ import { toClientEvmSigner } from "@x402/evm";
71
+
271
72
  // src/router/rules.ts
272
73
  function scoreTokenCount(estimatedTokens, thresholds) {
273
74
  if (estimatedTokens < thresholds.simple) {
@@ -356,18 +157,20 @@ function scoreAgenticTask(text, keywords) {
356
157
  };
357
158
  }
358
159
  function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
160
+ const text = `${systemPrompt ?? ""} ${prompt}`.toLowerCase();
359
161
  const userText = prompt.toLowerCase();
360
162
  const dimensions = [
361
- // Token count uses total estimated tokens (system + user) — context size matters for model selection
163
+ // Original 8 dimensions
362
164
  scoreTokenCount(estimatedTokens, config.tokenCountThresholds),
363
165
  scoreKeywordMatch(
364
- userText,
166
+ text,
365
167
  config.codeKeywords,
366
168
  "codePresence",
367
169
  "code",
368
170
  { low: 1, high: 2 },
369
171
  { none: 0, low: 0.5, high: 1 }
370
172
  ),
173
+ // Reasoning markers use USER prompt only — system prompt "step by step" shouldn't trigger reasoning
371
174
  scoreKeywordMatch(
372
175
  userText,
373
176
  config.reasoningKeywords,
@@ -377,7 +180,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
377
180
  { none: 0, low: 0.7, high: 1 }
378
181
  ),
379
182
  scoreKeywordMatch(
380
- userText,
183
+ text,
381
184
  config.technicalKeywords,
382
185
  "technicalTerms",
383
186
  "technical",
@@ -385,7 +188,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
385
188
  { none: 0, low: 0.5, high: 1 }
386
189
  ),
387
190
  scoreKeywordMatch(
388
- userText,
191
+ text,
389
192
  config.creativeKeywords,
390
193
  "creativeMarkers",
391
194
  "creative",
@@ -393,18 +196,18 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
393
196
  { none: 0, low: 0.5, high: 0.7 }
394
197
  ),
395
198
  scoreKeywordMatch(
396
- userText,
199
+ text,
397
200
  config.simpleKeywords,
398
201
  "simpleIndicators",
399
202
  "simple",
400
203
  { low: 1, high: 2 },
401
204
  { none: 0, low: -1, high: -1 }
402
205
  ),
403
- scoreMultiStep(userText),
206
+ scoreMultiStep(text),
404
207
  scoreQuestionComplexity(prompt),
405
208
  // 6 new dimensions
406
209
  scoreKeywordMatch(
407
- userText,
210
+ text,
408
211
  config.imperativeVerbs,
409
212
  "imperativeVerbs",
410
213
  "imperative",
@@ -412,7 +215,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
412
215
  { none: 0, low: 0.3, high: 0.5 }
413
216
  ),
414
217
  scoreKeywordMatch(
415
- userText,
218
+ text,
416
219
  config.constraintIndicators,
417
220
  "constraintCount",
418
221
  "constraints",
@@ -420,7 +223,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
420
223
  { none: 0, low: 0.3, high: 0.7 }
421
224
  ),
422
225
  scoreKeywordMatch(
423
- userText,
226
+ text,
424
227
  config.outputFormatKeywords,
425
228
  "outputFormat",
426
229
  "format",
@@ -428,7 +231,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
428
231
  { none: 0, low: 0.4, high: 0.7 }
429
232
  ),
430
233
  scoreKeywordMatch(
431
- userText,
234
+ text,
432
235
  config.referenceKeywords,
433
236
  "referenceComplexity",
434
237
  "references",
@@ -436,7 +239,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
436
239
  { none: 0, low: 0.3, high: 0.5 }
437
240
  ),
438
241
  scoreKeywordMatch(
439
- userText,
242
+ text,
440
243
  config.negationKeywords,
441
244
  "negationComplexity",
442
245
  "negation",
@@ -444,7 +247,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
444
247
  { none: 0, low: 0.3, high: 0.5 }
445
248
  ),
446
249
  scoreKeywordMatch(
447
- userText,
250
+ text,
448
251
  config.domainSpecificKeywords,
449
252
  "domainSpecificity",
450
253
  "domain-specific",
@@ -476,8 +279,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
476
279
  tier: "REASONING",
477
280
  confidence: Math.max(confidence2, 0.85),
478
281
  signals,
479
- agenticScore,
480
- dimensions
282
+ agenticScore
481
283
  };
482
284
  }
483
285
  const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
@@ -501,9 +303,9 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
501
303
  }
502
304
  const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
503
305
  if (confidence < config.confidenceThreshold) {
504
- return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };
306
+ return { score: weightedScore, tier: null, confidence, signals, agenticScore };
505
307
  }
506
- return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };
308
+ return { score: weightedScore, tier, confidence, signals, agenticScore };
507
309
  }
508
310
  function calibrateConfidence(distance, steepness) {
509
311
  return 1 / (1 + Math.exp(-steepness * distance));
@@ -511,9 +313,7 @@ function calibrateConfidence(distance, steepness) {
511
313
 
512
314
  // src/router/selector.ts
513
315
  var BASELINE_MODEL_ID = "anthropic/claude-opus-4.6";
514
- var BASELINE_INPUT_PRICE = 5;
515
- var BASELINE_OUTPUT_PRICE = 25;
516
- function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
316
+ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
517
317
  const tierConfig = tierConfigs[tier];
518
318
  const model = tierConfig.primary;
519
319
  const pricing = modelPricing.get(model);
@@ -523,8 +323,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
523
323
  const outputCost = maxOutputTokens / 1e6 * outputPrice;
524
324
  const costEstimate = inputCost + outputCost;
525
325
  const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
526
- const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
527
- const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
326
+ const opusInputPrice = opusPricing?.inputPrice ?? 0;
327
+ const opusOutputPrice = opusPricing?.outputPrice ?? 0;
528
328
  const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
529
329
  const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
530
330
  const baselineCost = baselineInput + baselineOutput;
@@ -537,8 +337,7 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
537
337
  reasoning,
538
338
  costEstimate,
539
339
  baselineCost,
540
- savings,
541
- ...agenticScore !== void 0 && { agenticScore }
340
+ savings
542
341
  };
543
342
  }
544
343
  function getFallbackChain(tier, tierConfigs) {
@@ -553,24 +352,14 @@ function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutput
553
352
  const outputCost = maxOutputTokens / 1e6 * outputPrice;
554
353
  const costEstimate = inputCost + outputCost;
555
354
  const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
556
- const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
557
- const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
355
+ const opusInputPrice = opusPricing?.inputPrice ?? 0;
356
+ const opusOutputPrice = opusPricing?.outputPrice ?? 0;
558
357
  const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
559
358
  const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
560
359
  const baselineCost = baselineInput + baselineOutput;
561
360
  const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
562
361
  return { costEstimate, baselineCost, savings };
563
362
  }
564
- function filterByToolCalling(models, hasTools, supportsToolCalling2) {
565
- if (!hasTools) return models;
566
- const filtered = models.filter(supportsToolCalling2);
567
- return filtered.length > 0 ? filtered : models;
568
- }
569
- function filterByVision(models, hasVision, supportsVision2) {
570
- if (!hasVision) return models;
571
- const filtered = models.filter(supportsVision2);
572
- return filtered.length > 0 ? filtered : models;
573
- }
574
363
  function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
575
364
  const fullChain = getFallbackChain(tier, tierConfigs);
576
365
  const filtered = fullChain.filter((modelId) => {
@@ -1626,18 +1415,18 @@ var DEFAULT_ROUTING_CONFIG = {
1626
1415
  ]
1627
1416
  },
1628
1417
  MEDIUM: {
1629
- primary: "moonshot/kimi-k2.5",
1630
- // $0.50/$2.40 - strong tool use, proper function call format
1418
+ primary: "xai/grok-code-fast-1",
1419
+ // Code specialist, $0.20/$1.50
1631
1420
  fallback: [
1632
- "deepseek/deepseek-chat",
1633
1421
  "google/gemini-2.5-flash-lite",
1634
1422
  // 1M context, ultra cheap ($0.10/$0.40)
1423
+ "deepseek/deepseek-chat",
1635
1424
  "xai/grok-4-1-fast-non-reasoning"
1636
1425
  // Upgraded Grok 4.1
1637
1426
  ]
1638
1427
  },
1639
1428
  COMPLEX: {
1640
- primary: "google/gemini-3.1-pro",
1429
+ primary: "google/gemini-3.1-pro-preview",
1641
1430
  // Newest Gemini 3.1 - upgraded from 3.0
1642
1431
  fallback: [
1643
1432
  "google/gemini-2.5-flash-lite",
@@ -1697,7 +1486,7 @@ var DEFAULT_ROUTING_CONFIG = {
1697
1486
  fallback: [
1698
1487
  "anthropic/claude-haiku-4.5",
1699
1488
  "google/gemini-2.5-flash-lite",
1700
- "deepseek/deepseek-chat"
1489
+ "xai/grok-code-fast-1"
1701
1490
  ]
1702
1491
  },
1703
1492
  MEDIUM: {
@@ -1717,7 +1506,7 @@ var DEFAULT_ROUTING_CONFIG = {
1717
1506
  "openai/gpt-5.2-codex",
1718
1507
  "anthropic/claude-opus-4.6",
1719
1508
  "anthropic/claude-sonnet-4.6",
1720
- "google/gemini-3.1-pro",
1509
+ "google/gemini-3.1-pro-preview",
1721
1510
  // Newest Gemini
1722
1511
  "google/gemini-3-pro-preview",
1723
1512
  "moonshot/kimi-k2.5"
@@ -1748,13 +1537,9 @@ var DEFAULT_ROUTING_CONFIG = {
1748
1537
  ]
1749
1538
  },
1750
1539
  MEDIUM: {
1751
- primary: "moonshot/kimi-k2.5",
1752
- // $0.50/$2.40 - strong tool use, handles function calls correctly
1753
- fallback: [
1754
- "anthropic/claude-haiku-4.5",
1755
- "deepseek/deepseek-chat",
1756
- "xai/grok-4-1-fast-non-reasoning"
1757
- ]
1540
+ primary: "xai/grok-code-fast-1",
1541
+ // Code specialist for agentic coding
1542
+ fallback: ["moonshot/kimi-k2.5", "anthropic/claude-haiku-4.5", "claude-sonnet-4"]
1758
1543
  },
1759
1544
  COMPLEX: {
1760
1545
  primary: "anthropic/claude-sonnet-4.6",
@@ -1762,7 +1547,7 @@ var DEFAULT_ROUTING_CONFIG = {
1762
1547
  "anthropic/claude-opus-4.6",
1763
1548
  // Latest Opus - best agentic
1764
1549
  "openai/gpt-5.2",
1765
- "google/gemini-3.1-pro",
1550
+ "google/gemini-3.1-pro-preview",
1766
1551
  // Newest Gemini
1767
1552
  "google/gemini-3-pro-preview",
1768
1553
  "xai/grok-4-0709"
@@ -1809,7 +1594,6 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
1809
1594
  tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
1810
1595
  profileSuffix = useAgenticTiers ? " | agentic" : "";
1811
1596
  }
1812
- const agenticScoreValue = ruleResult.agenticScore;
1813
1597
  if (estimatedTokens > config.overrides.maxTokensForceComplex) {
1814
1598
  return selectModel(
1815
1599
  "COMPLEX",
@@ -1820,8 +1604,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
1820
1604
  modelPricing,
1821
1605
  estimatedTokens,
1822
1606
  maxOutputTokens,
1823
- routingProfile,
1824
- agenticScoreValue
1607
+ routingProfile
1825
1608
  );
1826
1609
  }
1827
1610
  const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
@@ -1855,8 +1638,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
1855
1638
  modelPricing,
1856
1639
  estimatedTokens,
1857
1640
  maxOutputTokens,
1858
- routingProfile,
1859
- agenticScoreValue
1641
+ routingProfile
1860
1642
  );
1861
1643
  }
1862
1644
 
@@ -1904,8 +1686,6 @@ var MODEL_ALIASES = {
1904
1686
  // Google
1905
1687
  gemini: "google/gemini-2.5-pro",
1906
1688
  flash: "google/gemini-2.5-flash",
1907
- "gemini-3.1-pro-preview": "google/gemini-3.1-pro",
1908
- "google/gemini-3.1-pro-preview": "google/gemini-3.1-pro",
1909
1689
  // xAI
1910
1690
  grok: "xai/grok-3",
1911
1691
  "grok-fast": "xai/grok-4-fast-reasoning",
@@ -1979,8 +1759,7 @@ var BLOCKRUN_MODELS = [
1979
1759
  maxOutput: 128e3,
1980
1760
  reasoning: true,
1981
1761
  vision: true,
1982
- agentic: true,
1983
- toolCalling: true
1762
+ agentic: true
1984
1763
  },
1985
1764
  {
1986
1765
  id: "openai/gpt-5-mini",
@@ -1989,8 +1768,7 @@ var BLOCKRUN_MODELS = [
1989
1768
  inputPrice: 0.25,
1990
1769
  outputPrice: 2,
1991
1770
  contextWindow: 2e5,
1992
- maxOutput: 65536,
1993
- toolCalling: true
1771
+ maxOutput: 65536
1994
1772
  },
1995
1773
  {
1996
1774
  id: "openai/gpt-5-nano",
@@ -1999,8 +1777,7 @@ var BLOCKRUN_MODELS = [
1999
1777
  inputPrice: 0.05,
2000
1778
  outputPrice: 0.4,
2001
1779
  contextWindow: 128e3,
2002
- maxOutput: 32768,
2003
- toolCalling: true
1780
+ maxOutput: 32768
2004
1781
  },
2005
1782
  {
2006
1783
  id: "openai/gpt-5.2-pro",
@@ -2010,8 +1787,7 @@ var BLOCKRUN_MODELS = [
2010
1787
  outputPrice: 168,
2011
1788
  contextWindow: 4e5,
2012
1789
  maxOutput: 128e3,
2013
- reasoning: true,
2014
- toolCalling: true
1790
+ reasoning: true
2015
1791
  },
2016
1792
  // OpenAI Codex Family
2017
1793
  {
@@ -2022,8 +1798,7 @@ var BLOCKRUN_MODELS = [
2022
1798
  outputPrice: 14,
2023
1799
  contextWindow: 128e3,
2024
1800
  maxOutput: 32e3,
2025
- agentic: true,
2026
- toolCalling: true
1801
+ agentic: true
2027
1802
  },
2028
1803
  // OpenAI GPT-4 Family
2029
1804
  {
@@ -2034,8 +1809,7 @@ var BLOCKRUN_MODELS = [
2034
1809
  outputPrice: 8,
2035
1810
  contextWindow: 128e3,
2036
1811
  maxOutput: 16384,
2037
- vision: true,
2038
- toolCalling: true
1812
+ vision: true
2039
1813
  },
2040
1814
  {
2041
1815
  id: "openai/gpt-4.1-mini",
@@ -2044,8 +1818,7 @@ var BLOCKRUN_MODELS = [
2044
1818
  inputPrice: 0.4,
2045
1819
  outputPrice: 1.6,
2046
1820
  contextWindow: 128e3,
2047
- maxOutput: 16384,
2048
- toolCalling: true
1821
+ maxOutput: 16384
2049
1822
  },
2050
1823
  {
2051
1824
  id: "openai/gpt-4.1-nano",
@@ -2054,8 +1827,7 @@ var BLOCKRUN_MODELS = [
2054
1827
  inputPrice: 0.1,
2055
1828
  outputPrice: 0.4,
2056
1829
  contextWindow: 128e3,
2057
- maxOutput: 16384,
2058
- toolCalling: true
1830
+ maxOutput: 16384
2059
1831
  },
2060
1832
  {
2061
1833
  id: "openai/gpt-4o",
@@ -2066,8 +1838,7 @@ var BLOCKRUN_MODELS = [
2066
1838
  contextWindow: 128e3,
2067
1839
  maxOutput: 16384,
2068
1840
  vision: true,
2069
- agentic: true,
2070
- toolCalling: true
1841
+ agentic: true
2071
1842
  },
2072
1843
  {
2073
1844
  id: "openai/gpt-4o-mini",
@@ -2076,8 +1847,7 @@ var BLOCKRUN_MODELS = [
2076
1847
  inputPrice: 0.15,
2077
1848
  outputPrice: 0.6,
2078
1849
  contextWindow: 128e3,
2079
- maxOutput: 16384,
2080
- toolCalling: true
1850
+ maxOutput: 16384
2081
1851
  },
2082
1852
  // OpenAI O-series (Reasoning)
2083
1853
  {
@@ -2088,8 +1858,7 @@ var BLOCKRUN_MODELS = [
2088
1858
  outputPrice: 60,
2089
1859
  contextWindow: 2e5,
2090
1860
  maxOutput: 1e5,
2091
- reasoning: true,
2092
- toolCalling: true
1861
+ reasoning: true
2093
1862
  },
2094
1863
  {
2095
1864
  id: "openai/o1-mini",
@@ -2099,8 +1868,7 @@ var BLOCKRUN_MODELS = [
2099
1868
  outputPrice: 4.4,
2100
1869
  contextWindow: 128e3,
2101
1870
  maxOutput: 65536,
2102
- reasoning: true,
2103
- toolCalling: true
1871
+ reasoning: true
2104
1872
  },
2105
1873
  {
2106
1874
  id: "openai/o3",
@@ -2110,8 +1878,7 @@ var BLOCKRUN_MODELS = [
2110
1878
  outputPrice: 8,
2111
1879
  contextWindow: 2e5,
2112
1880
  maxOutput: 1e5,
2113
- reasoning: true,
2114
- toolCalling: true
1881
+ reasoning: true
2115
1882
  },
2116
1883
  {
2117
1884
  id: "openai/o3-mini",
@@ -2121,8 +1888,7 @@ var BLOCKRUN_MODELS = [
2121
1888
  outputPrice: 4.4,
2122
1889
  contextWindow: 128e3,
2123
1890
  maxOutput: 65536,
2124
- reasoning: true,
2125
- toolCalling: true
1891
+ reasoning: true
2126
1892
  },
2127
1893
  {
2128
1894
  id: "openai/o4-mini",
@@ -2132,8 +1898,7 @@ var BLOCKRUN_MODELS = [
2132
1898
  outputPrice: 4.4,
2133
1899
  contextWindow: 128e3,
2134
1900
  maxOutput: 65536,
2135
- reasoning: true,
2136
- toolCalling: true
1901
+ reasoning: true
2137
1902
  },
2138
1903
  // Anthropic - all Claude models excel at agentic workflows
2139
1904
  // Use newest versions (4.6) with full provider prefix
@@ -2145,9 +1910,7 @@ var BLOCKRUN_MODELS = [
2145
1910
  outputPrice: 5,
2146
1911
  contextWindow: 2e5,
2147
1912
  maxOutput: 8192,
2148
- vision: true,
2149
- agentic: true,
2150
- toolCalling: true
1913
+ agentic: true
2151
1914
  },
2152
1915
  {
2153
1916
  id: "anthropic/claude-sonnet-4.6",
@@ -2158,9 +1921,7 @@ var BLOCKRUN_MODELS = [
2158
1921
  contextWindow: 2e5,
2159
1922
  maxOutput: 64e3,
2160
1923
  reasoning: true,
2161
- vision: true,
2162
- agentic: true,
2163
- toolCalling: true
1924
+ agentic: true
2164
1925
  },
2165
1926
  {
2166
1927
  id: "anthropic/claude-opus-4.6",
@@ -2171,22 +1932,19 @@ var BLOCKRUN_MODELS = [
2171
1932
  contextWindow: 2e5,
2172
1933
  maxOutput: 32e3,
2173
1934
  reasoning: true,
2174
- vision: true,
2175
- agentic: true,
2176
- toolCalling: true
1935
+ agentic: true
2177
1936
  },
2178
1937
  // Google
2179
1938
  {
2180
- id: "google/gemini-3.1-pro",
2181
- name: "Gemini 3.1 Pro",
1939
+ id: "google/gemini-3.1-pro-preview",
1940
+ name: "Gemini 3.1 Pro Preview",
2182
1941
  version: "3.1",
2183
1942
  inputPrice: 2,
2184
1943
  outputPrice: 12,
2185
1944
  contextWindow: 105e4,
2186
1945
  maxOutput: 65536,
2187
1946
  reasoning: true,
2188
- vision: true,
2189
- toolCalling: true
1947
+ vision: true
2190
1948
  },
2191
1949
  {
2192
1950
  id: "google/gemini-3-pro-preview",
@@ -2197,8 +1955,7 @@ var BLOCKRUN_MODELS = [
2197
1955
  contextWindow: 105e4,
2198
1956
  maxOutput: 65536,
2199
1957
  reasoning: true,
2200
- vision: true,
2201
- toolCalling: true
1958
+ vision: true
2202
1959
  },
2203
1960
  {
2204
1961
  id: "google/gemini-3-flash-preview",
@@ -2208,8 +1965,7 @@ var BLOCKRUN_MODELS = [
2208
1965
  outputPrice: 3,
2209
1966
  contextWindow: 1e6,
2210
1967
  maxOutput: 65536,
2211
- vision: true,
2212
- toolCalling: true
1968
+ vision: true
2213
1969
  },
2214
1970
  {
2215
1971
  id: "google/gemini-2.5-pro",
@@ -2220,8 +1976,7 @@ var BLOCKRUN_MODELS = [
2220
1976
  contextWindow: 105e4,
2221
1977
  maxOutput: 65536,
2222
1978
  reasoning: true,
2223
- vision: true,
2224
- toolCalling: true
1979
+ vision: true
2225
1980
  },
2226
1981
  {
2227
1982
  id: "google/gemini-2.5-flash",
@@ -2230,9 +1985,7 @@ var BLOCKRUN_MODELS = [
2230
1985
  inputPrice: 0.3,
2231
1986
  outputPrice: 2.5,
2232
1987
  contextWindow: 1e6,
2233
- maxOutput: 65536,
2234
- vision: true,
2235
- toolCalling: true
1988
+ maxOutput: 65536
2236
1989
  },
2237
1990
  {
2238
1991
  id: "google/gemini-2.5-flash-lite",
@@ -2241,8 +1994,7 @@ var BLOCKRUN_MODELS = [
2241
1994
  inputPrice: 0.1,
2242
1995
  outputPrice: 0.4,
2243
1996
  contextWindow: 1e6,
2244
- maxOutput: 65536,
2245
- toolCalling: true
1997
+ maxOutput: 65536
2246
1998
  },
2247
1999
  // DeepSeek
2248
2000
  {
@@ -2252,8 +2004,7 @@ var BLOCKRUN_MODELS = [
2252
2004
  inputPrice: 0.28,
2253
2005
  outputPrice: 0.42,
2254
2006
  contextWindow: 128e3,
2255
- maxOutput: 8192,
2256
- toolCalling: true
2007
+ maxOutput: 8192
2257
2008
  },
2258
2009
  {
2259
2010
  id: "deepseek/deepseek-reasoner",
@@ -2263,8 +2014,7 @@ var BLOCKRUN_MODELS = [
2263
2014
  outputPrice: 0.42,
2264
2015
  contextWindow: 128e3,
2265
2016
  maxOutput: 8192,
2266
- reasoning: true,
2267
- toolCalling: true
2017
+ reasoning: true
2268
2018
  },
2269
2019
  // Moonshot / Kimi - optimized for agentic workflows
2270
2020
  {
@@ -2277,8 +2027,7 @@ var BLOCKRUN_MODELS = [
2277
2027
  maxOutput: 8192,
2278
2028
  reasoning: true,
2279
2029
  vision: true,
2280
- agentic: true,
2281
- toolCalling: true
2030
+ agentic: true
2282
2031
  },
2283
2032
  // xAI / Grok
2284
2033
  {
@@ -2289,8 +2038,7 @@ var BLOCKRUN_MODELS = [
2289
2038
  outputPrice: 15,
2290
2039
  contextWindow: 131072,
2291
2040
  maxOutput: 16384,
2292
- reasoning: true,
2293
- toolCalling: true
2041
+ reasoning: true
2294
2042
  },
2295
2043
  // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead
2296
2044
  {
@@ -2300,8 +2048,7 @@ var BLOCKRUN_MODELS = [
2300
2048
  inputPrice: 0.3,
2301
2049
  outputPrice: 0.5,
2302
2050
  contextWindow: 131072,
2303
- maxOutput: 16384,
2304
- toolCalling: true
2051
+ maxOutput: 16384
2305
2052
  },
2306
2053
  // xAI Grok 4 Family - Ultra-cheap fast models
2307
2054
  {
@@ -2312,8 +2059,7 @@ var BLOCKRUN_MODELS = [
2312
2059
  outputPrice: 0.5,
2313
2060
  contextWindow: 131072,
2314
2061
  maxOutput: 16384,
2315
- reasoning: true,
2316
- toolCalling: true
2062
+ reasoning: true
2317
2063
  },
2318
2064
  {
2319
2065
  id: "xai/grok-4-fast-non-reasoning",
@@ -2322,8 +2068,7 @@ var BLOCKRUN_MODELS = [
2322
2068
  inputPrice: 0.2,
2323
2069
  outputPrice: 0.5,
2324
2070
  contextWindow: 131072,
2325
- maxOutput: 16384,
2326
- toolCalling: true
2071
+ maxOutput: 16384
2327
2072
  },
2328
2073
  {
2329
2074
  id: "xai/grok-4-1-fast-reasoning",
@@ -2333,8 +2078,7 @@ var BLOCKRUN_MODELS = [
2333
2078
  outputPrice: 0.5,
2334
2079
  contextWindow: 131072,
2335
2080
  maxOutput: 16384,
2336
- reasoning: true,
2337
- toolCalling: true
2081
+ reasoning: true
2338
2082
  },
2339
2083
  {
2340
2084
  id: "xai/grok-4-1-fast-non-reasoning",
@@ -2343,8 +2087,7 @@ var BLOCKRUN_MODELS = [
2343
2087
  inputPrice: 0.2,
2344
2088
  outputPrice: 0.5,
2345
2089
  contextWindow: 131072,
2346
- maxOutput: 16384,
2347
- toolCalling: true
2090
+ maxOutput: 16384
2348
2091
  },
2349
2092
  {
2350
2093
  id: "xai/grok-code-fast-1",
@@ -2353,10 +2096,9 @@ var BLOCKRUN_MODELS = [
2353
2096
  inputPrice: 0.2,
2354
2097
  outputPrice: 1.5,
2355
2098
  contextWindow: 131072,
2356
- maxOutput: 16384
2357
- // toolCalling intentionally omitted: outputs tool calls as plain text JSON,
2358
- // not OpenAI-compatible structured function calls. Will be skipped when
2359
- // request has tools to prevent the "talking to itself" bug.
2099
+ maxOutput: 16384,
2100
+ agentic: true
2101
+ // Good for coding tasks
2360
2102
  },
2361
2103
  {
2362
2104
  id: "xai/grok-4-0709",
@@ -2366,8 +2108,7 @@ var BLOCKRUN_MODELS = [
2366
2108
  outputPrice: 1.5,
2367
2109
  contextWindow: 131072,
2368
2110
  maxOutput: 16384,
2369
- reasoning: true,
2370
- toolCalling: true
2111
+ reasoning: true
2371
2112
  },
2372
2113
  {
2373
2114
  id: "xai/grok-2-vision",
@@ -2377,8 +2118,7 @@ var BLOCKRUN_MODELS = [
2377
2118
  outputPrice: 10,
2378
2119
  contextWindow: 131072,
2379
2120
  maxOutput: 16384,
2380
- vision: true,
2381
- toolCalling: true
2121
+ vision: true
2382
2122
  },
2383
2123
  // MiniMax
2384
2124
  {
@@ -2390,8 +2130,7 @@ var BLOCKRUN_MODELS = [
2390
2130
  contextWindow: 204800,
2391
2131
  maxOutput: 16384,
2392
2132
  reasoning: true,
2393
- agentic: true,
2394
- toolCalling: true
2133
+ agentic: true
2395
2134
  },
2396
2135
  // NVIDIA - Free/cheap models
2397
2136
  {
@@ -2402,8 +2141,6 @@ var BLOCKRUN_MODELS = [
2402
2141
  outputPrice: 0,
2403
2142
  contextWindow: 128e3,
2404
2143
  maxOutput: 16384
2405
- // toolCalling intentionally omitted: free model, structured function
2406
- // calling support unverified. Excluded from tool-heavy routing paths.
2407
2144
  },
2408
2145
  {
2409
2146
  id: "nvidia/kimi-k2.5",
@@ -2412,8 +2149,7 @@ var BLOCKRUN_MODELS = [
2412
2149
  inputPrice: 0.55,
2413
2150
  outputPrice: 2.5,
2414
2151
  contextWindow: 262144,
2415
- maxOutput: 16384,
2416
- toolCalling: true
2152
+ maxOutput: 16384
2417
2153
  }
2418
2154
  ];
2419
2155
  function toOpenClawModel(m) {
@@ -2442,16 +2178,6 @@ var OPENCLAW_MODELS = [
2442
2178
  ...BLOCKRUN_MODELS.map(toOpenClawModel),
2443
2179
  ...ALIAS_MODELS
2444
2180
  ];
2445
- function supportsToolCalling(modelId) {
2446
- const normalized = modelId.replace("blockrun/", "");
2447
- const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
2448
- return model?.toolCalling ?? false;
2449
- }
2450
- function supportsVision(modelId) {
2451
- const normalized = modelId.replace("blockrun/", "");
2452
- const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
2453
- return model?.vision ?? false;
2454
- }
2455
2181
  function getModelContextWindow(modelId) {
2456
2182
  const normalized = modelId.replace("blockrun/", "");
2457
2183
  const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
@@ -3102,6 +2828,199 @@ var BalanceMonitor = class {
3102
2828
  }
3103
2829
  };
3104
2830
 
2831
+ // src/auth.ts
2832
+ import { writeFile, mkdir as mkdir2 } from "fs/promises";
2833
+ import { join as join4 } from "path";
2834
+ import { homedir as homedir3 } from "os";
2835
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
2836
+
2837
+ // src/wallet.ts
2838
+ import { HDKey } from "@scure/bip32";
2839
+ import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
2840
+ import { wordlist as english } from "@scure/bip39/wordlists/english";
2841
+ import { privateKeyToAccount } from "viem/accounts";
2842
+ var ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0";
2843
+ var SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
2844
+ function generateWalletMnemonic() {
2845
+ return generateMnemonic(english, 256);
2846
+ }
2847
+ function isValidMnemonic(mnemonic) {
2848
+ return validateMnemonic(mnemonic, english);
2849
+ }
2850
+ function deriveEvmKey(mnemonic) {
2851
+ const seed = mnemonicToSeedSync(mnemonic);
2852
+ const hdKey = HDKey.fromMasterSeed(seed);
2853
+ const derived = hdKey.derive(ETH_DERIVATION_PATH);
2854
+ if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
2855
+ const hex = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
2856
+ const account = privateKeyToAccount(hex);
2857
+ return { privateKey: hex, address: account.address };
2858
+ }
2859
+ function deriveSolanaKeyBytes(mnemonic) {
2860
+ const seed = mnemonicToSeedSync(mnemonic);
2861
+ const hdKey = HDKey.fromMasterSeed(seed);
2862
+ const derived = hdKey.derive(SOLANA_DERIVATION_PATH);
2863
+ if (!derived.privateKey) throw new Error("Failed to derive Solana private key");
2864
+ return new Uint8Array(derived.privateKey);
2865
+ }
2866
+ function deriveAllKeys(mnemonic) {
2867
+ const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);
2868
+ const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);
2869
+ return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };
2870
+ }
2871
+
2872
+ // src/auth.ts
2873
+ var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
2874
+ var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
2875
+ var MNEMONIC_FILE = join4(WALLET_DIR, "mnemonic");
2876
+ var CHAIN_FILE = join4(WALLET_DIR, "payment-chain");
2877
+ async function loadSavedWallet() {
2878
+ try {
2879
+ const key = (await readTextFile(WALLET_FILE)).trim();
2880
+ if (key.startsWith("0x") && key.length === 66) {
2881
+ console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
2882
+ return key;
2883
+ }
2884
+ console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
2885
+ console.error(`[ClawRouter] File: ${WALLET_FILE}`);
2886
+ console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
2887
+ console.error(`[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`);
2888
+ throw new Error(
2889
+ `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.`
2890
+ );
2891
+ } catch (err) {
2892
+ if (err.code !== "ENOENT") {
2893
+ if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
2894
+ throw err;
2895
+ }
2896
+ console.error(
2897
+ `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
2898
+ );
2899
+ throw new Error(
2900
+ `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.`
2901
+ );
2902
+ }
2903
+ }
2904
+ return void 0;
2905
+ }
2906
+ async function loadMnemonic() {
2907
+ try {
2908
+ const mnemonic = (await readTextFile(MNEMONIC_FILE)).trim();
2909
+ if (mnemonic && isValidMnemonic(mnemonic)) {
2910
+ return mnemonic;
2911
+ }
2912
+ console.warn(`[ClawRouter] \u26A0 Mnemonic file exists but has invalid format \u2014 ignoring`);
2913
+ return void 0;
2914
+ } catch (err) {
2915
+ if (err.code !== "ENOENT") {
2916
+ console.warn(`[ClawRouter] \u26A0 Cannot read mnemonic file \u2014 ignoring`);
2917
+ }
2918
+ }
2919
+ return void 0;
2920
+ }
2921
+ async function generateAndSaveWallet() {
2922
+ const existingMnemonic = await loadMnemonic();
2923
+ if (existingMnemonic) {
2924
+ throw new Error(
2925
+ `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>`
2926
+ );
2927
+ }
2928
+ const mnemonic = generateWalletMnemonic();
2929
+ const derived = deriveAllKeys(mnemonic);
2930
+ await mkdir2(WALLET_DIR, { recursive: true });
2931
+ await writeFile(WALLET_FILE, derived.evmPrivateKey + "\n", { mode: 384 });
2932
+ await writeFile(MNEMONIC_FILE, mnemonic + "\n", { mode: 384 });
2933
+ try {
2934
+ const verification = (await readTextFile(WALLET_FILE)).trim();
2935
+ if (verification !== derived.evmPrivateKey) {
2936
+ throw new Error("Wallet file verification failed - content mismatch");
2937
+ }
2938
+ console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);
2939
+ } catch (err) {
2940
+ throw new Error(
2941
+ `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
2942
+ );
2943
+ }
2944
+ console.log(`[ClawRouter]`);
2945
+ 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`);
2946
+ console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
2947
+ 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`);
2948
+ console.log(`[ClawRouter] EVM Address : ${derived.evmAddress}`);
2949
+ console.log(`[ClawRouter] Key file : ${WALLET_FILE}`);
2950
+ console.log(`[ClawRouter] Mnemonic : ${MNEMONIC_FILE}`);
2951
+ console.log(`[ClawRouter]`);
2952
+ console.log(`[ClawRouter] Both EVM (Base) and Solana wallets are ready.`);
2953
+ console.log(`[ClawRouter] To back up, run in OpenClaw:`);
2954
+ console.log(`[ClawRouter] /wallet export`);
2955
+ console.log(`[ClawRouter]`);
2956
+ console.log(`[ClawRouter] To restore on another machine:`);
2957
+ console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
2958
+ 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`);
2959
+ console.log(`[ClawRouter]`);
2960
+ return {
2961
+ key: derived.evmPrivateKey,
2962
+ address: derived.evmAddress,
2963
+ mnemonic,
2964
+ solanaPrivateKeyBytes: derived.solanaPrivateKeyBytes
2965
+ };
2966
+ }
2967
+ async function resolveOrGenerateWalletKey() {
2968
+ const saved = await loadSavedWallet();
2969
+ if (saved) {
2970
+ const account = privateKeyToAccount2(saved);
2971
+ const mnemonic = await loadMnemonic();
2972
+ if (mnemonic) {
2973
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
2974
+ return {
2975
+ key: saved,
2976
+ address: account.address,
2977
+ source: "saved",
2978
+ mnemonic,
2979
+ solanaPrivateKeyBytes: solanaKeyBytes
2980
+ };
2981
+ }
2982
+ return { key: saved, address: account.address, source: "saved" };
2983
+ }
2984
+ const envKey = process["env"].BLOCKRUN_WALLET_KEY;
2985
+ if (typeof envKey === "string" && envKey.startsWith("0x") && envKey.length === 66) {
2986
+ const account = privateKeyToAccount2(envKey);
2987
+ const mnemonic = await loadMnemonic();
2988
+ if (mnemonic) {
2989
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
2990
+ return {
2991
+ key: envKey,
2992
+ address: account.address,
2993
+ source: "env",
2994
+ mnemonic,
2995
+ solanaPrivateKeyBytes: solanaKeyBytes
2996
+ };
2997
+ }
2998
+ return { key: envKey, address: account.address, source: "env" };
2999
+ }
3000
+ const result = await generateAndSaveWallet();
3001
+ return {
3002
+ key: result.key,
3003
+ address: result.address,
3004
+ source: "generated",
3005
+ mnemonic: result.mnemonic,
3006
+ solanaPrivateKeyBytes: result.solanaPrivateKeyBytes
3007
+ };
3008
+ }
3009
+ async function loadPaymentChain() {
3010
+ try {
3011
+ const content = (await readTextFile(CHAIN_FILE)).trim();
3012
+ if (content === "solana") return "solana";
3013
+ return "base";
3014
+ } catch {
3015
+ return "base";
3016
+ }
3017
+ }
3018
+ async function resolvePaymentChain() {
3019
+ if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "solana") return "solana";
3020
+ if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "base") return "base";
3021
+ return loadPaymentChain();
3022
+ }
3023
+
3105
3024
  // src/compression/types.ts
3106
3025
  var DEFAULT_COMPRESSION_CONFIG = {
3107
3026
  enabled: true,
@@ -3131,7 +3050,7 @@ var DEFAULT_COMPRESSION_CONFIG = {
3131
3050
  };
3132
3051
 
3133
3052
  // src/compression/layers/deduplication.ts
3134
- import crypto2 from "crypto";
3053
+ import crypto from "crypto";
3135
3054
  function hashMessage(message) {
3136
3055
  let contentStr = "";
3137
3056
  if (typeof message.content === "string") {
@@ -3151,7 +3070,7 @@ function hashMessage(message) {
3151
3070
  );
3152
3071
  }
3153
3072
  const content = parts.join("|");
3154
- return crypto2.createHash("md5").update(content).digest("hex");
3073
+ return crypto.createHash("md5").update(content).digest("hex");
3155
3074
  }
3156
3075
  function deduplicateMessages(messages) {
3157
3076
  const seen = /* @__PURE__ */ new Set();
@@ -3848,9 +3767,8 @@ function shouldCompress(messages) {
3848
3767
  }
3849
3768
 
3850
3769
  // src/session.ts
3851
- import { createHash as createHash3 } from "crypto";
3852
3770
  var DEFAULT_SESSION_CONFIG = {
3853
- enabled: true,
3771
+ enabled: false,
3854
3772
  timeoutMs: 30 * 60 * 1e3,
3855
3773
  // 30 minutes
3856
3774
  headerName: "x-session-id"
@@ -3905,10 +3823,7 @@ var SessionStore = class {
3905
3823
  tier,
3906
3824
  createdAt: now,
3907
3825
  lastUsedAt: now,
3908
- requestCount: 1,
3909
- recentHashes: [],
3910
- strikes: 0,
3911
- escalated: false
3826
+ requestCount: 1
3912
3827
  });
3913
3828
  }
3914
3829
  }
@@ -3960,43 +3875,6 @@ var SessionStore = class {
3960
3875
  }
3961
3876
  }
3962
3877
  }
3963
- /**
3964
- * Record a request content hash and detect repetitive patterns.
3965
- * Returns true if escalation should be triggered (3+ consecutive similar requests).
3966
- */
3967
- recordRequestHash(sessionId, hash) {
3968
- const entry = this.sessions.get(sessionId);
3969
- if (!entry) return false;
3970
- const prev = entry.recentHashes;
3971
- if (prev.length > 0 && prev[prev.length - 1] === hash) {
3972
- entry.strikes++;
3973
- } else {
3974
- entry.strikes = 0;
3975
- }
3976
- entry.recentHashes.push(hash);
3977
- if (entry.recentHashes.length > 3) {
3978
- entry.recentHashes.shift();
3979
- }
3980
- return entry.strikes >= 2 && !entry.escalated;
3981
- }
3982
- /**
3983
- * Escalate session to next tier. Returns the new model/tier or null if already at max.
3984
- */
3985
- escalateSession(sessionId, tierConfigs) {
3986
- const entry = this.sessions.get(sessionId);
3987
- if (!entry) return null;
3988
- const TIER_ORDER = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
3989
- const currentIdx = TIER_ORDER.indexOf(entry.tier);
3990
- if (currentIdx < 0 || currentIdx >= TIER_ORDER.length - 1) return null;
3991
- const nextTier = TIER_ORDER[currentIdx + 1];
3992
- const nextConfig = tierConfigs[nextTier];
3993
- if (!nextConfig) return null;
3994
- entry.model = nextConfig.primary;
3995
- entry.tier = nextTier;
3996
- entry.strikes = 0;
3997
- entry.escalated = true;
3998
- return { model: nextConfig.primary, tier: nextTier };
3999
- }
4000
3878
  /**
4001
3879
  * Stop the cleanup interval.
4002
3880
  */
@@ -4017,17 +3895,6 @@ function getSessionId(headers, headerName = DEFAULT_SESSION_CONFIG.headerName) {
4017
3895
  }
4018
3896
  return void 0;
4019
3897
  }
4020
- function deriveSessionId(messages) {
4021
- const firstUser = messages.find((m) => m.role === "user");
4022
- if (!firstUser) return void 0;
4023
- const content = typeof firstUser.content === "string" ? firstUser.content : JSON.stringify(firstUser.content);
4024
- return createHash3("sha256").update(content).digest("hex").slice(0, 8);
4025
- }
4026
- function hashRequestContent(lastUserContent, toolCallNames) {
4027
- const normalized = lastUserContent.replace(/\s+/g, " ").trim().slice(0, 500);
4028
- const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(",")}` : "";
4029
- return createHash3("sha256").update(normalized + toolSuffix).digest("hex").slice(0, 12);
4030
- }
4031
3898
 
4032
3899
  // src/updater.ts
4033
3900
  var NPM_REGISTRY = "https://registry.npmjs.org/@blockrun/clawrouter/latest";
@@ -4248,6 +4115,7 @@ ${lines.join("\n")}`;
4248
4115
 
4249
4116
  // src/proxy.ts
4250
4117
  var BLOCKRUN_API = "https://blockrun.ai/api";
4118
+ var BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
4251
4119
  var AUTO_MODEL = "blockrun/auto";
4252
4120
  var ROUTING_PROFILES = /* @__PURE__ */ new Set([
4253
4121
  "blockrun/free",
@@ -4376,7 +4244,7 @@ async function checkExistingProxy(port) {
4376
4244
  if (response.ok) {
4377
4245
  const data = await response.json();
4378
4246
  if (data.status === "ok" && data.wallet) {
4379
- return data.wallet;
4247
+ return { wallet: data.wallet, paymentChain: data.paymentChain };
4380
4248
  }
4381
4249
  }
4382
4250
  return void 0;
@@ -4784,52 +4652,76 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
4784
4652
  }).catch(() => {
4785
4653
  });
4786
4654
  }
4787
- async function uploadDataUriToHost(dataUri) {
4788
- const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
4789
- if (!match) throw new Error("Invalid data URI format");
4790
- const [, mimeType, b64Data] = match;
4791
- const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
4792
- const buffer = Buffer.from(b64Data, "base64");
4793
- const blob = new Blob([buffer], { type: mimeType });
4794
- const form = new FormData();
4795
- form.append("reqtype", "fileupload");
4796
- form.append("fileToUpload", blob, `image.${ext}`);
4797
- const resp = await fetch("https://catbox.moe/user/api.php", {
4798
- method: "POST",
4799
- body: form
4800
- });
4801
- if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
4802
- const result = await resp.text();
4803
- if (result.startsWith("https://")) {
4804
- return result.trim();
4805
- }
4806
- throw new Error(`catbox.moe upload failed: ${result}`);
4807
- }
4808
4655
  async function startProxy(options) {
4809
- const apiBase = options.apiBase ?? BLOCKRUN_API;
4656
+ const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
4657
+ const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
4658
+ const paymentChain = options.paymentChain ?? await resolvePaymentChain();
4659
+ const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
4660
+ if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
4661
+ console.warn(`[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`);
4662
+ } else if (paymentChain === "solana") {
4663
+ console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
4664
+ }
4810
4665
  const listenPort = options.port ?? getProxyPort();
4811
- const existingWallet = await checkExistingProxy(listenPort);
4812
- if (existingWallet) {
4813
- const account2 = privateKeyToAccount2(options.walletKey);
4666
+ const existingProxy = await checkExistingProxy(listenPort);
4667
+ if (existingProxy) {
4668
+ const account2 = privateKeyToAccount3(walletKey);
4814
4669
  const balanceMonitor2 = new BalanceMonitor(account2.address);
4815
4670
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
4816
- if (existingWallet !== account2.address) {
4671
+ if (existingProxy.wallet !== account2.address) {
4817
4672
  console.warn(
4818
- `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingWallet}, but current config uses ${account2.address}. Reusing existing proxy.`
4673
+ `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
4819
4674
  );
4820
4675
  }
4676
+ if (existingProxy.paymentChain) {
4677
+ if (existingProxy.paymentChain !== paymentChain) {
4678
+ throw new Error(
4679
+ `Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
4680
+ );
4681
+ }
4682
+ } else if (paymentChain !== "base") {
4683
+ console.warn(`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`);
4684
+ throw new Error(
4685
+ `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.`
4686
+ );
4687
+ }
4688
+ let reuseSolanaAddress;
4689
+ if (solanaPrivateKeyBytes) {
4690
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
4691
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
4692
+ reuseSolanaAddress = solanaSigner.address;
4693
+ }
4821
4694
  options.onReady?.(listenPort);
4822
4695
  return {
4823
4696
  port: listenPort,
4824
4697
  baseUrl: baseUrl2,
4825
- walletAddress: existingWallet,
4698
+ walletAddress: existingProxy.wallet,
4699
+ solanaAddress: reuseSolanaAddress,
4826
4700
  balanceMonitor: balanceMonitor2,
4827
4701
  close: async () => {
4828
4702
  }
4829
4703
  };
4830
4704
  }
4831
- const account = privateKeyToAccount2(options.walletKey);
4832
- const { fetch: payFetch } = createPaymentFetch(options.walletKey);
4705
+ const account = privateKeyToAccount3(walletKey);
4706
+ const evmPublicClient = createPublicClient2({ chain: base2, transport: http2() });
4707
+ const evmSigner = toClientEvmSigner(account, evmPublicClient);
4708
+ const x402 = new x402Client();
4709
+ registerExactEvmScheme(x402, { signer: evmSigner });
4710
+ let solanaAddress;
4711
+ if (solanaPrivateKeyBytes) {
4712
+ const { registerExactSvmScheme } = await import("@x402/svm/exact/client");
4713
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
4714
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
4715
+ solanaAddress = solanaSigner.address;
4716
+ registerExactSvmScheme(x402, { signer: solanaSigner });
4717
+ console.log(`[ClawRouter] Solana x402 scheme registered: ${solanaAddress}`);
4718
+ }
4719
+ x402.onAfterPaymentCreation(async (context) => {
4720
+ const network = context.selectedRequirements.network;
4721
+ const chain = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
4722
+ console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);
4723
+ });
4724
+ const payFetch = createPayFetchWithPreAuth(fetch, x402);
4833
4725
  const balanceMonitor = new BalanceMonitor(account.address);
4834
4726
  const routingConfig = mergeRoutingConfig(options.routingConfig);
4835
4727
  const modelPricing = buildModelPricing();
@@ -4864,8 +4756,12 @@ async function startProxy(options) {
4864
4756
  const full = url.searchParams.get("full") === "true";
4865
4757
  const response = {
4866
4758
  status: "ok",
4867
- wallet: account.address
4759
+ wallet: account.address,
4760
+ paymentChain
4868
4761
  };
4762
+ if (solanaAddress) {
4763
+ response.solana = solanaAddress;
4764
+ }
4869
4765
  if (full) {
4870
4766
  try {
4871
4767
  const balanceInfo = await balanceMonitor.checkBalance();
@@ -4977,10 +4873,10 @@ async function startProxy(options) {
4977
4873
  const onError = async (err) => {
4978
4874
  server.removeListener("error", onError);
4979
4875
  if (err.code === "EADDRINUSE") {
4980
- const existingWallet2 = await checkExistingProxy(listenPort);
4981
- if (existingWallet2) {
4876
+ const existingProxy2 = await checkExistingProxy(listenPort);
4877
+ if (existingProxy2) {
4982
4878
  console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
4983
- rejectAttempt({ code: "REUSE_EXISTING", wallet: existingWallet2 });
4879
+ rejectAttempt({ code: "REUSE_EXISTING", wallet: existingProxy2.wallet, existingChain: existingProxy2.paymentChain });
4984
4880
  return;
4985
4881
  }
4986
4882
  if (attempt < PORT_RETRY_ATTEMPTS) {
@@ -5013,6 +4909,11 @@ async function startProxy(options) {
5013
4909
  } catch (err) {
5014
4910
  const error = err;
5015
4911
  if (error.code === "REUSE_EXISTING" && error.wallet) {
4912
+ if (error.existingChain && error.existingChain !== paymentChain) {
4913
+ throw new Error(
4914
+ `Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
4915
+ );
4916
+ }
5016
4917
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
5017
4918
  options.onReady?.(listenPort);
5018
4919
  return {
@@ -5070,6 +4971,7 @@ async function startProxy(options) {
5070
4971
  port,
5071
4972
  baseUrl,
5072
4973
  walletAddress: account.address,
4974
+ solanaAddress,
5073
4975
  balanceMonitor,
5074
4976
  close: () => new Promise((res, rej) => {
5075
4977
  const timeout = setTimeout(() => {
@@ -5116,8 +5018,6 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5116
5018
  requestBody = Buffer.from(JSON.stringify(parsed));
5117
5019
  } catch {
5118
5020
  }
5119
- const estimated = estimateAmount(modelId, requestBody.length, maxTokens);
5120
- const preAuth = estimated ? { estimatedAmount: estimated } : void 0;
5121
5021
  try {
5122
5022
  const response = await payFetch(
5123
5023
  upstreamUrl,
@@ -5126,8 +5026,7 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5126
5026
  headers,
5127
5027
  body: requestBody.length > 0 ? new Uint8Array(requestBody) : void 0,
5128
5028
  signal
5129
- },
5130
- preAuth
5029
+ }
5131
5030
  );
5132
5031
  if (response.status !== 200) {
5133
5032
  const errorBody = await response.text();
@@ -5176,19 +5075,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5176
5075
  }
5177
5076
  let body = Buffer.concat(bodyChunks);
5178
5077
  const originalContextSizeKB = Math.ceil(body.length / 1024);
5179
- const debugMode = req.headers["x-clawrouter-debug"] !== "false";
5180
5078
  let routingDecision;
5181
- let hasTools = false;
5182
- let hasVision = false;
5183
5079
  let isStreaming = false;
5184
5080
  let modelId = "";
5185
5081
  let maxTokens = 4096;
5186
5082
  let routingProfile = null;
5187
5083
  let accumulatedContent = "";
5188
- let responseInputTokens;
5189
5084
  const isChatCompletion = req.url?.includes("/chat/completions");
5190
5085
  const sessionId = getSessionId(req.headers);
5191
- let effectiveSessionId = sessionId;
5192
5086
  if (isChatCompletion && body.length > 0) {
5193
5087
  try {
5194
5088
  const parsed = JSON.parse(body.toString());
@@ -5196,12 +5090,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5196
5090
  modelId = parsed.model || "";
5197
5091
  maxTokens = parsed.max_tokens || 4096;
5198
5092
  let bodyModified = false;
5199
- const parsedMessages = Array.isArray(parsed.messages) ? parsed.messages : [];
5200
- const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === "user");
5201
- const rawLastContent = lastUserMsg?.content;
5202
- const lastContent = typeof rawLastContent === "string" ? rawLastContent : Array.isArray(rawLastContent) ? rawLastContent.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
5203
- if (sessionId && parsedMessages.length > 0) {
5204
- const messages = parsedMessages;
5093
+ if (sessionId && Array.isArray(parsed.messages)) {
5094
+ const messages = parsed.messages;
5095
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
5096
+ const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5205
5097
  if (sessionJournal.needsContext(lastContent)) {
5206
5098
  const journalText = sessionJournal.format(sessionId);
5207
5099
  if (journalText) {
@@ -5222,303 +5114,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5222
5114
  }
5223
5115
  }
5224
5116
  }
5225
- if (lastContent.startsWith("/debug")) {
5226
- const debugPrompt = lastContent.slice("/debug".length).trim() || "hello";
5227
- const messages = parsed.messages;
5228
- const systemMsg = messages?.find((m) => m.role === "system");
5229
- const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5230
- const fullText = `${systemPrompt ?? ""} ${debugPrompt}`;
5231
- const estimatedTokens = Math.ceil(fullText.length / 4);
5232
- const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
5233
- const profileName = normalizedModel2.replace("blockrun/", "");
5234
- const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) ? profileName : "auto";
5235
- const scoring = classifyByRules(
5236
- debugPrompt,
5237
- systemPrompt,
5238
- estimatedTokens,
5239
- DEFAULT_ROUTING_CONFIG.scoring
5240
- );
5241
- const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {
5242
- ...routerOpts,
5243
- routingProfile: debugProfile
5244
- });
5245
- const dimLines = (scoring.dimensions ?? []).map((d) => {
5246
- const nameStr = (d.name + ":").padEnd(24);
5247
- const scoreStr = d.score.toFixed(2).padStart(6);
5248
- const sigStr = d.signal ? ` [${d.signal}]` : "";
5249
- return ` ${nameStr}${scoreStr}${sigStr}`;
5250
- }).join("\n");
5251
- const sess = sessionId ? sessionStore.getSession(sessionId) : void 0;
5252
- 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";
5253
- const { simpleMedium, mediumComplex, complexReasoning } = DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;
5254
- const debugText = [
5255
- "ClawRouter Debug",
5256
- "",
5257
- `Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,
5258
- `Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,
5259
- `Reasoning: ${debugRouting.reasoning}`,
5260
- "",
5261
- `Scoring (weighted: ${scoring.score.toFixed(3)})`,
5262
- dimLines,
5263
- "",
5264
- `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,
5265
- "",
5266
- sessLine
5267
- ].join("\n");
5268
- const completionId = `chatcmpl-debug-${Date.now()}`;
5269
- const timestamp = Math.floor(Date.now() / 1e3);
5270
- const syntheticResponse = {
5271
- id: completionId,
5272
- object: "chat.completion",
5273
- created: timestamp,
5274
- model: "clawrouter/debug",
5275
- choices: [
5276
- {
5277
- index: 0,
5278
- message: { role: "assistant", content: debugText },
5279
- finish_reason: "stop"
5280
- }
5281
- ],
5282
- usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5283
- };
5284
- if (isStreaming) {
5285
- res.writeHead(200, {
5286
- "Content-Type": "text/event-stream",
5287
- "Cache-Control": "no-cache",
5288
- Connection: "keep-alive"
5289
- });
5290
- const sseChunk = {
5291
- id: completionId,
5292
- object: "chat.completion.chunk",
5293
- created: timestamp,
5294
- model: "clawrouter/debug",
5295
- choices: [
5296
- {
5297
- index: 0,
5298
- delta: { role: "assistant", content: debugText },
5299
- finish_reason: null
5300
- }
5301
- ]
5302
- };
5303
- const sseDone = {
5304
- id: completionId,
5305
- object: "chat.completion.chunk",
5306
- created: timestamp,
5307
- model: "clawrouter/debug",
5308
- choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
5309
- };
5310
- res.write(`data: ${JSON.stringify(sseChunk)}
5311
-
5312
- `);
5313
- res.write(`data: ${JSON.stringify(sseDone)}
5314
-
5315
- `);
5316
- res.write("data: [DONE]\n\n");
5317
- res.end();
5318
- } else {
5319
- res.writeHead(200, { "Content-Type": "application/json" });
5320
- res.end(JSON.stringify(syntheticResponse));
5321
- }
5322
- console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`);
5323
- return;
5324
- }
5325
- if (lastContent.startsWith("/imagegen")) {
5326
- const imageArgs = lastContent.slice("/imagegen".length).trim();
5327
- let imageModel = "google/nano-banana";
5328
- let imageSize = "1024x1024";
5329
- let imagePrompt = imageArgs;
5330
- const modelMatch = imageArgs.match(/--model\s+(\S+)/);
5331
- if (modelMatch) {
5332
- const raw = modelMatch[1];
5333
- const IMAGE_MODEL_ALIASES = {
5334
- "dall-e-3": "openai/dall-e-3",
5335
- dalle3: "openai/dall-e-3",
5336
- dalle: "openai/dall-e-3",
5337
- "gpt-image": "openai/gpt-image-1",
5338
- "gpt-image-1": "openai/gpt-image-1",
5339
- flux: "black-forest/flux-1.1-pro",
5340
- "flux-pro": "black-forest/flux-1.1-pro",
5341
- banana: "google/nano-banana",
5342
- "nano-banana": "google/nano-banana",
5343
- "banana-pro": "google/nano-banana-pro",
5344
- "nano-banana-pro": "google/nano-banana-pro"
5345
- };
5346
- imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;
5347
- imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim();
5348
- }
5349
- const sizeMatch = imageArgs.match(/--size\s+(\d+x\d+)/);
5350
- if (sizeMatch) {
5351
- imageSize = sizeMatch[1];
5352
- imagePrompt = imagePrompt.replace(/--size\s+\d+x\d+/, "").trim();
5353
- }
5354
- if (!imagePrompt) {
5355
- const errorText = [
5356
- "Usage: /imagegen <prompt>",
5357
- "",
5358
- "Options:",
5359
- " --model <model> Model to use (default: nano-banana)",
5360
- " --size <WxH> Image size (default: 1024x1024)",
5361
- "",
5362
- "Models:",
5363
- " nano-banana Google Gemini Flash \u2014 $0.05/image",
5364
- " banana-pro Google Gemini Pro \u2014 $0.10/image (up to 4K)",
5365
- " dall-e-3 OpenAI DALL-E 3 \u2014 $0.04/image",
5366
- " gpt-image OpenAI GPT Image 1 \u2014 $0.02/image",
5367
- " flux Black Forest Flux 1.1 Pro \u2014 $0.04/image",
5368
- "",
5369
- "Examples:",
5370
- " /imagegen a cat wearing sunglasses",
5371
- " /imagegen --model dall-e-3 a futuristic city at sunset",
5372
- " /imagegen --model banana-pro --size 2048x2048 mountain landscape"
5373
- ].join("\n");
5374
- const completionId = `chatcmpl-image-${Date.now()}`;
5375
- const timestamp = Math.floor(Date.now() / 1e3);
5376
- if (isStreaming) {
5377
- res.writeHead(200, {
5378
- "Content-Type": "text/event-stream",
5379
- "Cache-Control": "no-cache",
5380
- Connection: "keep-alive"
5381
- });
5382
- res.write(
5383
- `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 }] })}
5384
-
5385
- `
5386
- );
5387
- res.write(
5388
- `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
5389
-
5390
- `
5391
- );
5392
- res.write("data: [DONE]\n\n");
5393
- res.end();
5394
- } else {
5395
- res.writeHead(200, { "Content-Type": "application/json" });
5396
- res.end(
5397
- JSON.stringify({
5398
- id: completionId,
5399
- object: "chat.completion",
5400
- created: timestamp,
5401
- model: "clawrouter/image",
5402
- choices: [
5403
- {
5404
- index: 0,
5405
- message: { role: "assistant", content: errorText },
5406
- finish_reason: "stop"
5407
- }
5408
- ],
5409
- usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5410
- })
5411
- );
5412
- }
5413
- console.log(`[ClawRouter] /imagegen command \u2192 showing usage help`);
5414
- return;
5415
- }
5416
- console.log(
5417
- `[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`
5418
- );
5419
- try {
5420
- const imageUpstreamUrl = `${apiBase}/v1/images/generations`;
5421
- const imageBody = JSON.stringify({
5422
- model: imageModel,
5423
- prompt: imagePrompt,
5424
- size: imageSize,
5425
- n: 1
5426
- });
5427
- const imageResponse = await payFetch(imageUpstreamUrl, {
5428
- method: "POST",
5429
- headers: { "content-type": "application/json", "user-agent": USER_AGENT },
5430
- body: imageBody
5431
- });
5432
- const imageResult = await imageResponse.json();
5433
- let responseText;
5434
- if (!imageResponse.ok || imageResult.error) {
5435
- const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`;
5436
- responseText = `Image generation failed: ${errMsg}`;
5437
- console.log(`[ClawRouter] /imagegen error: ${errMsg}`);
5438
- } else {
5439
- const images = imageResult.data ?? [];
5440
- if (images.length === 0) {
5441
- responseText = "Image generation returned no results.";
5442
- } else {
5443
- const lines = [];
5444
- for (const img of images) {
5445
- if (img.url) {
5446
- if (img.url.startsWith("data:")) {
5447
- try {
5448
- const hostedUrl = await uploadDataUriToHost(img.url);
5449
- lines.push(hostedUrl);
5450
- } catch (uploadErr) {
5451
- console.error(
5452
- `[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
5453
- );
5454
- lines.push(
5455
- "Image generated but upload failed. Try again or use --model dall-e-3."
5456
- );
5457
- }
5458
- } else {
5459
- lines.push(img.url);
5460
- }
5461
- }
5462
- if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);
5463
- }
5464
- lines.push("", `Model: ${imageModel} | Size: ${imageSize}`);
5465
- responseText = lines.join("\n");
5466
- }
5467
- console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
5468
- }
5469
- const completionId = `chatcmpl-image-${Date.now()}`;
5470
- const timestamp = Math.floor(Date.now() / 1e3);
5471
- if (isStreaming) {
5472
- res.writeHead(200, {
5473
- "Content-Type": "text/event-stream",
5474
- "Cache-Control": "no-cache",
5475
- Connection: "keep-alive"
5476
- });
5477
- res.write(
5478
- `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 }] })}
5479
-
5480
- `
5481
- );
5482
- res.write(
5483
- `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
5484
-
5485
- `
5486
- );
5487
- res.write("data: [DONE]\n\n");
5488
- res.end();
5489
- } else {
5490
- res.writeHead(200, { "Content-Type": "application/json" });
5491
- res.end(
5492
- JSON.stringify({
5493
- id: completionId,
5494
- object: "chat.completion",
5495
- created: timestamp,
5496
- model: "clawrouter/image",
5497
- choices: [
5498
- {
5499
- index: 0,
5500
- message: { role: "assistant", content: responseText },
5501
- finish_reason: "stop"
5502
- }
5503
- ],
5504
- usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
5505
- })
5506
- );
5507
- }
5508
- } catch (err) {
5509
- const errMsg = err instanceof Error ? err.message : String(err);
5510
- console.error(`[ClawRouter] /imagegen error: ${errMsg}`);
5511
- if (!res.headersSent) {
5512
- res.writeHead(500, { "Content-Type": "application/json" });
5513
- res.end(
5514
- JSON.stringify({
5515
- error: { message: `Image generation failed: ${errMsg}`, type: "image_error" }
5516
- })
5517
- );
5518
- }
5519
- }
5520
- return;
5521
- }
5522
5117
  if (parsed.stream === true) {
5523
5118
  parsed.stream = false;
5524
5119
  bodyModified = true;
@@ -5559,117 +5154,54 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5559
5154
  latencyMs: 0
5560
5155
  });
5561
5156
  } else {
5562
- effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
5563
- const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
5564
- const rawPrompt = lastUserMsg?.content;
5565
- const prompt = typeof rawPrompt === "string" ? rawPrompt : Array.isArray(rawPrompt) ? rawPrompt.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
5566
- const systemMsg = parsedMessages.find((m) => m.role === "system");
5567
- const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5568
- const tools = parsed.tools;
5569
- hasTools = Array.isArray(tools) && tools.length > 0;
5570
- if (hasTools && tools) {
5571
- console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);
5572
- }
5573
- hasVision = parsedMessages.some((m) => {
5574
- if (Array.isArray(m.content)) {
5575
- return m.content.some((p) => p.type === "image_url");
5576
- }
5577
- return false;
5578
- });
5579
- if (hasVision) {
5580
- console.log(`[ClawRouter] Vision content detected, filtering to vision-capable models`);
5581
- }
5582
- routingDecision = route(prompt, systemPrompt, maxTokens, {
5583
- ...routerOpts,
5584
- routingProfile: routingProfile ?? void 0
5585
- });
5157
+ const sessionId2 = getSessionId(
5158
+ req.headers
5159
+ );
5160
+ const existingSession = sessionId2 ? sessionStore.getSession(sessionId2) : void 0;
5586
5161
  if (existingSession) {
5587
- const tierRank = {
5588
- SIMPLE: 0,
5589
- MEDIUM: 1,
5590
- COMPLEX: 2,
5591
- REASONING: 3
5592
- };
5593
- const existingRank = tierRank[existingSession.tier] ?? 0;
5594
- const newRank = tierRank[routingDecision.tier] ?? 0;
5595
- if (newRank > existingRank) {
5596
- console.log(
5597
- `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} \u2192 ${routingDecision.tier} (${routingDecision.model})`
5598
- );
5599
- parsed.model = routingDecision.model;
5600
- modelId = routingDecision.model;
5601
- bodyModified = true;
5602
- if (effectiveSessionId) {
5603
- sessionStore.setSession(
5604
- effectiveSessionId,
5605
- routingDecision.model,
5606
- routingDecision.tier
5607
- );
5162
+ console.log(
5163
+ `[ClawRouter] Session ${sessionId2?.slice(0, 8)}... using pinned model: ${existingSession.model}`
5164
+ );
5165
+ parsed.model = existingSession.model;
5166
+ modelId = existingSession.model;
5167
+ bodyModified = true;
5168
+ sessionStore.touchSession(sessionId2);
5169
+ } else {
5170
+ const messages = parsed.messages;
5171
+ let lastUserMsg;
5172
+ if (messages) {
5173
+ for (let i = messages.length - 1; i >= 0; i--) {
5174
+ if (messages[i].role === "user") {
5175
+ lastUserMsg = messages[i];
5176
+ break;
5177
+ }
5608
5178
  }
5609
- } else {
5610
- console.log(
5611
- `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`
5612
- );
5613
- parsed.model = existingSession.model;
5614
- modelId = existingSession.model;
5615
- bodyModified = true;
5616
- sessionStore.touchSession(effectiveSessionId);
5617
- routingDecision = {
5618
- ...routingDecision,
5619
- model: existingSession.model,
5620
- tier: existingSession.tier
5621
- };
5622
5179
  }
5623
- const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
5624
- const toolCallNames = Array.isArray(lastAssistantMsg?.tool_calls) ? lastAssistantMsg.tool_calls.map((tc) => tc.function?.name).filter(Boolean) : void 0;
5625
- const contentHash = hashRequestContent(prompt, toolCallNames);
5626
- const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
5627
- if (shouldEscalate) {
5628
- const activeTierConfigs = (() => {
5629
- if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
5630
- return routerOpts.config.agenticTiers;
5631
- }
5632
- if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
5633
- return routerOpts.config.ecoTiers;
5634
- }
5635
- if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
5636
- return routerOpts.config.premiumTiers;
5637
- }
5638
- return routerOpts.config.tiers;
5639
- })();
5640
- const escalation = sessionStore.escalateSession(
5641
- effectiveSessionId,
5642
- activeTierConfigs
5180
+ const systemMsg = messages?.find((m) => m.role === "system");
5181
+ const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5182
+ const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5183
+ const tools = parsed.tools;
5184
+ const hasTools = Array.isArray(tools) && tools.length > 0;
5185
+ if (hasTools && tools) {
5186
+ console.log(
5187
+ `[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`
5643
5188
  );
5644
- if (escalation) {
5645
- console.log(
5646
- `[ClawRouter] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`
5647
- );
5648
- parsed.model = escalation.model;
5649
- modelId = escalation.model;
5650
- routingDecision = {
5651
- ...routingDecision,
5652
- model: escalation.model,
5653
- tier: escalation.tier
5654
- };
5655
- }
5656
5189
  }
5657
- } else {
5190
+ routingDecision = route(prompt, systemPrompt, maxTokens, {
5191
+ ...routerOpts,
5192
+ routingProfile: routingProfile ?? void 0
5193
+ });
5658
5194
  parsed.model = routingDecision.model;
5659
5195
  modelId = routingDecision.model;
5660
5196
  bodyModified = true;
5661
- if (effectiveSessionId) {
5662
- sessionStore.setSession(
5663
- effectiveSessionId,
5664
- routingDecision.model,
5665
- routingDecision.tier
5666
- );
5197
+ if (sessionId2) {
5198
+ sessionStore.setSession(sessionId2, routingDecision.model, routingDecision.tier);
5667
5199
  console.log(
5668
- `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`
5200
+ `[ClawRouter] Session ${sessionId2.slice(0, 8)}... pinned to model: ${routingDecision.model}`
5669
5201
  );
5670
5202
  }
5203
+ options.onRouted?.(routingDecision);
5671
5204
  }
5672
- options.onRouted?.(routingDecision);
5673
5205
  }
5674
5206
  }
5675
5207
  if (bodyModified) {
@@ -5841,18 +5373,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5841
5373
  if (routingDecision) {
5842
5374
  const estimatedInputTokens = Math.ceil(body.length / 4);
5843
5375
  const estimatedTotalTokens = estimatedInputTokens + maxTokens;
5844
- const tierConfigs = (() => {
5845
- if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
5846
- return routerOpts.config.agenticTiers;
5847
- }
5848
- if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
5849
- return routerOpts.config.ecoTiers;
5850
- }
5851
- if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
5852
- return routerOpts.config.premiumTiers;
5853
- }
5854
- return routerOpts.config.tiers;
5855
- })();
5376
+ const useAgenticTiers = routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers;
5377
+ const tierConfigs = useAgenticTiers ? routerOpts.config.agenticTiers : routerOpts.config.tiers;
5856
5378
  const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);
5857
5379
  const contextFiltered = getFallbackChainFiltered(
5858
5380
  routingDecision.tier,
@@ -5866,27 +5388,14 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5866
5388
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
5867
5389
  );
5868
5390
  }
5869
- const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);
5870
- const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));
5871
- if (toolExcluded.length > 0) {
5872
- console.log(
5873
- `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
5874
- );
5875
- }
5876
- const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);
5877
- const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));
5878
- if (visionExcluded.length > 0) {
5879
- console.log(
5880
- `[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`
5881
- );
5882
- }
5883
- modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5391
+ modelsToTry = contextFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5884
5392
  modelsToTry = prioritizeNonRateLimited(modelsToTry);
5885
5393
  } else {
5886
- modelsToTry = modelId ? [modelId] : [];
5887
- }
5888
- if (!modelsToTry.includes(FREE_MODEL)) {
5889
- modelsToTry.push(FREE_MODEL);
5394
+ if (modelId && modelId !== FREE_MODEL) {
5395
+ modelsToTry = [modelId, FREE_MODEL];
5396
+ } else {
5397
+ modelsToTry = modelId ? [modelId] : [];
5398
+ }
5890
5399
  }
5891
5400
  let upstream;
5892
5401
  let lastError;
@@ -5920,17 +5429,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5920
5429
  if (result.errorStatus === 429) {
5921
5430
  markRateLimited(tryModel);
5922
5431
  }
5923
- const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
5924
- result.errorBody || ""
5925
- );
5926
- if (isPaymentErr && tryModel !== FREE_MODEL) {
5927
- const freeIdx = modelsToTry.indexOf(FREE_MODEL);
5928
- if (freeIdx > i + 1) {
5929
- console.log(`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
5930
- i = freeIdx - 1;
5931
- continue;
5932
- }
5933
- }
5934
5432
  console.log(
5935
5433
  `[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`
5936
5434
  );
@@ -5948,12 +5446,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5948
5446
  clearInterval(heartbeatInterval);
5949
5447
  heartbeatInterval = void 0;
5950
5448
  }
5951
- if (debugMode && headersSentEarly && routingDecision) {
5952
- 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}
5953
-
5954
- `;
5955
- safeWrite(res, debugComment);
5956
- }
5957
5449
  if (routingDecision && actualModelUsed !== routingDecision.model) {
5958
5450
  const estimatedInputTokens = Math.ceil(body.length / 4);
5959
5451
  const newCosts = calculateModelCost(
@@ -5972,12 +5464,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5972
5464
  savings: newCosts.savings
5973
5465
  };
5974
5466
  options.onRouted?.(routingDecision);
5975
- if (effectiveSessionId) {
5976
- sessionStore.setSession(effectiveSessionId, actualModelUsed, routingDecision.tier);
5977
- console.log(
5978
- `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}`
5979
- );
5980
- }
5981
5467
  }
5982
5468
  if (!upstream) {
5983
5469
  const rawErrBody = lastError?.body || "All models in fallback chain failed";
@@ -6040,10 +5526,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6040
5526
  const jsonStr = jsonBody.toString();
6041
5527
  try {
6042
5528
  const rsp = JSON.parse(jsonStr);
6043
- if (rsp.usage && typeof rsp.usage === "object") {
6044
- const u = rsp.usage;
6045
- if (typeof u.prompt_tokens === "number") responseInputTokens = u.prompt_tokens;
6046
- }
6047
5529
  const baseChunk = {
6048
5530
  id: rsp.id ?? `chatcmpl-${Date.now()}`,
6049
5531
  object: "chat.completion.chunk",
@@ -6143,16 +5625,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6143
5625
  });
6144
5626
  responseHeaders["x-context-used-kb"] = String(originalContextSizeKB);
6145
5627
  responseHeaders["x-context-limit-kb"] = String(CONTEXT_LIMIT_KB);
6146
- if (debugMode && routingDecision) {
6147
- responseHeaders["x-clawrouter-profile"] = routingProfile ?? "auto";
6148
- responseHeaders["x-clawrouter-tier"] = routingDecision.tier;
6149
- responseHeaders["x-clawrouter-model"] = actualModelUsed;
6150
- responseHeaders["x-clawrouter-confidence"] = routingDecision.confidence.toFixed(2);
6151
- responseHeaders["x-clawrouter-reasoning"] = routingDecision.reasoning;
6152
- if (routingDecision.agenticScore !== void 0) {
6153
- responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
6154
- }
6155
- }
6156
5628
  res.writeHead(upstream.status, responseHeaders);
6157
5629
  if (upstream.body) {
6158
5630
  const reader = upstream.body.getReader();
@@ -6192,10 +5664,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6192
5664
  if (rspJson.choices?.[0]?.message?.content) {
6193
5665
  accumulatedContent = rspJson.choices[0].message.content;
6194
5666
  }
6195
- if (rspJson.usage && typeof rspJson.usage === "object") {
6196
- if (typeof rspJson.usage.prompt_tokens === "number")
6197
- responseInputTokens = rspJson.usage.prompt_tokens;
6198
- }
6199
5667
  } catch {
6200
5668
  }
6201
5669
  }
@@ -6244,140 +5712,21 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6244
5712
  cost: costWithBuffer,
6245
5713
  baselineCost: baselineWithBuffer,
6246
5714
  savings: accurateCosts.savings,
6247
- latencyMs: Date.now() - startTime,
6248
- ...responseInputTokens !== void 0 && { inputTokens: responseInputTokens }
5715
+ latencyMs: Date.now() - startTime
6249
5716
  };
6250
5717
  logUsage(entry).catch(() => {
6251
5718
  });
6252
5719
  }
6253
5720
  }
6254
5721
 
6255
- // src/auth.ts
6256
- import { writeFile, mkdir as mkdir2 } from "fs/promises";
6257
- import { join as join4 } from "path";
6258
- import { homedir as homedir3 } from "os";
6259
- import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
6260
- var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
6261
- var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
6262
- async function loadSavedWallet() {
6263
- try {
6264
- const key = (await readTextFile(WALLET_FILE)).trim();
6265
- if (key.startsWith("0x") && key.length === 66) {
6266
- console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
6267
- return key;
6268
- }
6269
- console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
6270
- console.error(`[ClawRouter] File: ${WALLET_FILE}`);
6271
- console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
6272
- console.error(
6273
- `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
6274
- );
6275
- throw new Error(
6276
- `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.`
6277
- );
6278
- } catch (err) {
6279
- if (err.code !== "ENOENT") {
6280
- if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
6281
- throw err;
6282
- }
6283
- console.error(
6284
- `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
6285
- );
6286
- throw new Error(
6287
- `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.`
6288
- );
6289
- }
6290
- }
6291
- return void 0;
6292
- }
6293
- async function generateAndSaveWallet() {
6294
- const key = generatePrivateKey();
6295
- const account = privateKeyToAccount3(key);
6296
- await mkdir2(WALLET_DIR, { recursive: true });
6297
- await writeFile(WALLET_FILE, key + "\n", { mode: 384 });
6298
- try {
6299
- const verification = (await readTextFile(WALLET_FILE)).trim();
6300
- if (verification !== key) {
6301
- throw new Error("Wallet file verification failed - content mismatch");
6302
- }
6303
- console.log(`[ClawRouter] \u2713 Wallet saved and verified at ${WALLET_FILE}`);
6304
- } catch (err) {
6305
- throw new Error(
6306
- `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
6307
- );
6308
- }
6309
- console.log(`[ClawRouter]`);
6310
- 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`);
6311
- console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
6312
- 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`);
6313
- console.log(`[ClawRouter] Address : ${account.address}`);
6314
- console.log(`[ClawRouter] Key file: ${WALLET_FILE}`);
6315
- console.log(`[ClawRouter]`);
6316
- console.log(`[ClawRouter] To back up, run in OpenClaw:`);
6317
- console.log(`[ClawRouter] /wallet export`);
6318
- console.log(`[ClawRouter]`);
6319
- console.log(`[ClawRouter] To restore on another machine:`);
6320
- console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
6321
- 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`);
6322
- console.log(`[ClawRouter]`);
6323
- return { key, address: account.address };
6324
- }
6325
- async function resolveOrGenerateWalletKey() {
6326
- const saved = await loadSavedWallet();
6327
- if (saved) {
6328
- const account = privateKeyToAccount3(saved);
6329
- return { key: saved, address: account.address, source: "saved" };
6330
- }
6331
- const envKey = process["env"].BLOCKRUN_WALLET_KEY;
6332
- if (typeof envKey === "string" && envKey.startsWith("0x") && envKey.length === 66) {
6333
- const account = privateKeyToAccount3(envKey);
6334
- return { key: envKey, address: account.address, source: "env" };
6335
- }
6336
- const { key, address } = await generateAndSaveWallet();
6337
- return { key, address, source: "generated" };
6338
- }
6339
-
6340
- // src/report.ts
6341
- async function generateReport(period, json = false) {
6342
- const days = period === "daily" ? 1 : period === "weekly" ? 7 : 30;
6343
- const stats = await getStats(days);
6344
- if (json) {
6345
- return JSON.stringify(stats, null, 2);
6346
- }
6347
- return formatMarkdownReport(period, days, stats);
6348
- }
6349
- function formatMarkdownReport(period, days, stats) {
6350
- const lines = [];
6351
- lines.push(`# ClawRouter ${capitalize(period)} Report`);
6352
- lines.push(`**Period:** Last ${days} day${days > 1 ? "s" : ""}`);
6353
- lines.push(`**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
6354
- lines.push("");
6355
- lines.push("## \u{1F4CA} Usage Summary");
6356
- lines.push("");
6357
- lines.push(`| Metric | Value |`);
6358
- lines.push(`|--------|-------|`);
6359
- lines.push(`| Total Requests | ${stats.totalRequests} |`);
6360
- lines.push(`| Total Cost | $${stats.totalCost.toFixed(4)} |`);
6361
- lines.push(`| Baseline Cost | $${stats.totalBaselineCost.toFixed(4)} |`);
6362
- lines.push(`| **Savings** | **$${stats.totalSavings.toFixed(4)}** |`);
6363
- lines.push(`| Savings % | ${stats.savingsPercentage.toFixed(1)}% |`);
6364
- lines.push(`| Avg Latency | ${stats.avgLatencyMs.toFixed(0)}ms |`);
6365
- lines.push("");
6366
- lines.push("## \u{1F916} Model Distribution");
6367
- lines.push("");
6368
- const sortedModels = Object.entries(stats.byModel).sort((a, b) => b[1].count - a[1].count).slice(0, 10);
6369
- for (const [model, data] of sortedModels) {
6370
- lines.push(`- ${model}: ${data.count} reqs, $${data.cost.toFixed(4)}`);
6371
- }
6372
- lines.push("");
6373
- return lines.join("\n");
6374
- }
6375
- function capitalize(str) {
6376
- return str.charAt(0).toUpperCase() + str.slice(1);
6377
- }
6378
-
6379
5722
  // src/doctor.ts
6380
5723
  import { platform, arch, freemem, totalmem } from "os";
5724
+ import { createPublicClient as createPublicClient3, http as http3 } from "viem";
5725
+ import { base as base3 } from "viem/chains";
5726
+ import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
5727
+ import { wrapFetchWithPayment, x402Client as x402Client2 } from "@x402/fetch";
5728
+ import { registerExactEvmScheme as registerExactEvmScheme2 } from "@x402/evm/exact/client";
5729
+ import { toClientEvmSigner as toClientEvmSigner2 } from "@x402/evm";
6381
5730
  function formatBytes(bytes) {
6382
5731
  const gb = bytes / (1024 * 1024 * 1024);
6383
5732
  return `${gb.toFixed(1)}GB`;
@@ -6591,7 +5940,12 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
6591
5940
  `);
6592
5941
  try {
6593
5942
  const { key } = await resolveOrGenerateWalletKey();
6594
- const { fetch: paymentFetch } = createPaymentFetch(key);
5943
+ const account = privateKeyToAccount4(key);
5944
+ const publicClient = createPublicClient3({ chain: base3, transport: http3() });
5945
+ const evmSigner = toClientEvmSigner2(account, publicClient);
5946
+ const x402 = new x402Client2();
5947
+ registerExactEvmScheme2(x402, { signer: evmSigner });
5948
+ const paymentFetch = wrapFetchWithPayment(fetch, x402);
6595
5949
  const response = await paymentFetch(
6596
5950
  "https://blockrun.ai/api/v1/chat/completions",
6597
5951
  {
@@ -6626,8 +5980,7 @@ Please analyze and help me fix any issues.`
6626
5980
  ],
6627
5981
  max_tokens: 1e3
6628
5982
  })
6629
- },
6630
- void 0
5983
+ }
6631
5984
  );
6632
5985
  if (!response.ok) {
6633
5986
  const text = await response.text();
@@ -6712,7 +6065,6 @@ Usage:
6712
6065
  clawrouter [options]
6713
6066
  clawrouter doctor [opus] [question]
6714
6067
  clawrouter partners [test]
6715
- clawrouter report [daily|weekly|monthly] [--json]
6716
6068
 
6717
6069
  Options:
6718
6070
  --version, -v Show version number
@@ -6755,9 +6107,6 @@ function parseArgs(args) {
6755
6107
  doctor: false,
6756
6108
  partners: false,
6757
6109
  partnersTest: false,
6758
- report: false,
6759
- reportPeriod: "daily",
6760
- reportJson: false,
6761
6110
  port: void 0
6762
6111
  };
6763
6112
  for (let i = 0; i < args.length; i++) {
@@ -6774,20 +6123,6 @@ function parseArgs(args) {
6774
6123
  result.partnersTest = true;
6775
6124
  i++;
6776
6125
  }
6777
- } else if (arg === "report") {
6778
- result.report = true;
6779
- const next = args[i + 1];
6780
- if (next && ["daily", "weekly", "monthly"].includes(next)) {
6781
- result.reportPeriod = next;
6782
- i++;
6783
- if (args[i + 1] === "--json") {
6784
- result.reportJson = true;
6785
- i++;
6786
- }
6787
- } else if (next === "--json") {
6788
- result.reportJson = true;
6789
- i++;
6790
- }
6791
6126
  } else if (arg === "--port" && args[i + 1]) {
6792
6127
  result.port = parseInt(args[i + 1], 10);
6793
6128
  i++;
@@ -6835,9 +6170,7 @@ ClawRouter Partner APIs (v${VERSION})
6835
6170
  console.log(` ${svc.description}`);
6836
6171
  console.log(` Tool: blockrun_${svc.id}`);
6837
6172
  console.log(` Method: ${svc.method} /v1${svc.proxyPath}`);
6838
- console.log(
6839
- ` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`
6840
- );
6173
+ console.log(` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`);
6841
6174
  console.log();
6842
6175
  }
6843
6176
  if (args.partnersTest) {
@@ -6858,21 +6191,16 @@ ClawRouter Partner APIs (v${VERSION})
6858
6191
  }
6859
6192
  process.exit(0);
6860
6193
  }
6861
- if (args.report) {
6862
- const report = await generateReport(args.reportPeriod, args.reportJson);
6863
- console.log(report);
6864
- process.exit(0);
6865
- }
6866
- const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
6867
- if (source === "generated") {
6868
- console.log(`[ClawRouter] Generated new wallet: ${address}`);
6869
- } else if (source === "saved") {
6870
- console.log(`[ClawRouter] Using saved wallet: ${address}`);
6194
+ const wallet = await resolveOrGenerateWalletKey();
6195
+ if (wallet.source === "generated") {
6196
+ console.log(`[ClawRouter] Generated new wallet: ${wallet.address}`);
6197
+ } else if (wallet.source === "saved") {
6198
+ console.log(`[ClawRouter] Using saved wallet: ${wallet.address}`);
6871
6199
  } else {
6872
- console.log(`[ClawRouter] Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);
6200
+ console.log(`[ClawRouter] Using wallet from BLOCKRUN_WALLET_KEY: ${wallet.address}`);
6873
6201
  }
6874
6202
  const proxy = await startProxy({
6875
- walletKey,
6203
+ wallet,
6876
6204
  port: args.port,
6877
6205
  onReady: (port) => {
6878
6206
  console.log(`[ClawRouter] Proxy listening on http://127.0.0.1:${port}`);
@@ -6896,19 +6224,19 @@ ClawRouter Partner APIs (v${VERSION})
6896
6224
  console.error(`[ClawRouter] Need help? Run: npx @blockrun/clawrouter doctor`);
6897
6225
  }
6898
6226
  });
6899
- const monitor = new BalanceMonitor(address);
6227
+ const monitor = new BalanceMonitor(wallet.address);
6900
6228
  try {
6901
6229
  const balance = await monitor.checkBalance();
6902
6230
  if (balance.isEmpty) {
6903
6231
  console.log(`[ClawRouter] Wallet balance: $0.00 (using FREE model)`);
6904
- console.log(`[ClawRouter] Fund wallet for premium models: ${address}`);
6232
+ console.log(`[ClawRouter] Fund wallet for premium models: ${wallet.address}`);
6905
6233
  } else if (balance.isLow) {
6906
6234
  console.log(`[ClawRouter] Wallet balance: ${balance.balanceUSD} (low)`);
6907
6235
  } else {
6908
6236
  console.log(`[ClawRouter] Wallet balance: ${balance.balanceUSD}`);
6909
6237
  }
6910
6238
  } catch {
6911
- console.log(`[ClawRouter] Wallet: ${address} (balance check pending)`);
6239
+ console.log(`[ClawRouter] Wallet: ${wallet.address} (balance check pending)`);
6912
6240
  }
6913
6241
  console.log(`[ClawRouter] Ready - Ctrl+C to stop`);
6914
6242
  const shutdown = async (signal) => {