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