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