@blockrun/clawrouter 0.11.13 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,55 +8,6 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
- // src/wallet.ts
12
- var wallet_exports = {};
13
- __export(wallet_exports, {
14
- deriveAllKeys: () => deriveAllKeys,
15
- deriveEvmKey: () => deriveEvmKey,
16
- deriveSolanaKeyBytes: () => deriveSolanaKeyBytes,
17
- generateWalletMnemonic: () => generateWalletMnemonic,
18
- isValidMnemonic: () => isValidMnemonic
19
- });
20
- import { HDKey } from "@scure/bip32";
21
- import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
22
- import { wordlist as english } from "@scure/bip39/wordlists/english";
23
- import { privateKeyToAccount } from "viem/accounts";
24
- function generateWalletMnemonic() {
25
- return generateMnemonic(english, 256);
26
- }
27
- function isValidMnemonic(mnemonic) {
28
- return validateMnemonic(mnemonic, english);
29
- }
30
- function deriveEvmKey(mnemonic) {
31
- const seed = mnemonicToSeedSync(mnemonic);
32
- const hdKey = HDKey.fromMasterSeed(seed);
33
- const derived = hdKey.derive(ETH_DERIVATION_PATH);
34
- if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
35
- const hex = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
36
- const account = privateKeyToAccount(hex);
37
- return { privateKey: hex, address: account.address };
38
- }
39
- function deriveSolanaKeyBytes(mnemonic) {
40
- const seed = mnemonicToSeedSync(mnemonic);
41
- const hdKey = HDKey.fromMasterSeed(seed);
42
- const derived = hdKey.derive(SOLANA_DERIVATION_PATH);
43
- if (!derived.privateKey) throw new Error("Failed to derive Solana private key");
44
- return new Uint8Array(derived.privateKey);
45
- }
46
- function deriveAllKeys(mnemonic) {
47
- const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);
48
- const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);
49
- return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };
50
- }
51
- var ETH_DERIVATION_PATH, SOLANA_DERIVATION_PATH;
52
- var init_wallet = __esm({
53
- "src/wallet.ts"() {
54
- "use strict";
55
- ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0";
56
- SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
57
- }
58
- });
59
-
60
11
  // src/solana-balance.ts
61
12
  var solana_balance_exports = {};
62
13
  __export(solana_balance_exports, {
@@ -104,6 +55,28 @@ var init_solana_balance = __esm({
104
55
  this.invalidate();
105
56
  return this.checkBalance();
106
57
  }
58
+ /**
59
+ * Check if balance is sufficient for an estimated cost.
60
+ */
61
+ async checkSufficient(estimatedCostMicros) {
62
+ const info = await this.checkBalance();
63
+ if (info.balance >= estimatedCostMicros) {
64
+ return { sufficient: true, info };
65
+ }
66
+ const shortfall = estimatedCostMicros - info.balance;
67
+ return {
68
+ sufficient: false,
69
+ info,
70
+ shortfall: this.formatUSDC(shortfall)
71
+ };
72
+ }
73
+ /**
74
+ * Format USDC amount (in micros) as "$X.XX".
75
+ */
76
+ formatUSDC(amountMicros) {
77
+ const dollars = Number(amountMicros) / 1e6;
78
+ return `$${dollars.toFixed(2)}`;
79
+ }
107
80
  getWalletAddress() {
108
81
  return this.walletAddress;
109
82
  }
@@ -122,7 +95,10 @@ var init_solana_balance = __esm({
122
95
  }
123
96
  return total;
124
97
  } catch (err) {
125
- throw new Error(`Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`);
98
+ throw new Error(
99
+ `Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`,
100
+ { cause: err }
101
+ );
126
102
  } finally {
127
103
  clearTimeout(timer);
128
104
  }
@@ -141,125 +117,834 @@ var init_solana_balance = __esm({
141
117
  }
142
118
  });
143
119
 
144
- // src/partners/registry.ts
145
- function getPartnerService(id) {
146
- return PARTNER_SERVICES.find((s) => s.id === id);
120
+ // node_modules/@noble/hashes/esm/utils.js
121
+ function isBytes(a) {
122
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
123
+ }
124
+ function anumber(n) {
125
+ if (!Number.isSafeInteger(n) || n < 0)
126
+ throw new Error("positive integer expected, got " + n);
127
+ }
128
+ function abytes(b, ...lengths) {
129
+ if (!isBytes(b))
130
+ throw new Error("Uint8Array expected");
131
+ if (lengths.length > 0 && !lengths.includes(b.length))
132
+ throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
133
+ }
134
+ function ahash(h) {
135
+ if (typeof h !== "function" || typeof h.create !== "function")
136
+ throw new Error("Hash should be wrapped by utils.createHasher");
137
+ anumber(h.outputLen);
138
+ anumber(h.blockLen);
139
+ }
140
+ function aexists(instance, checkFinished = true) {
141
+ if (instance.destroyed)
142
+ throw new Error("Hash instance has been destroyed");
143
+ if (checkFinished && instance.finished)
144
+ throw new Error("Hash#digest() has already been called");
145
+ }
146
+ function aoutput(out, instance) {
147
+ abytes(out);
148
+ const min = instance.outputLen;
149
+ if (out.length < min) {
150
+ throw new Error("digestInto() expects output buffer of length at least " + min);
151
+ }
152
+ }
153
+ function clean(...arrays) {
154
+ for (let i = 0; i < arrays.length; i++) {
155
+ arrays[i].fill(0);
156
+ }
157
+ }
158
+ function createView(arr) {
159
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
147
160
  }
148
- var PARTNER_SERVICES;
149
- var init_registry = __esm({
150
- "src/partners/registry.ts"() {
161
+ function utf8ToBytes(str) {
162
+ if (typeof str !== "string")
163
+ throw new Error("string expected");
164
+ return new Uint8Array(new TextEncoder().encode(str));
165
+ }
166
+ function toBytes(data) {
167
+ if (typeof data === "string")
168
+ data = utf8ToBytes(data);
169
+ abytes(data);
170
+ return data;
171
+ }
172
+ function createHasher(hashCons) {
173
+ const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
174
+ const tmp = hashCons();
175
+ hashC.outputLen = tmp.outputLen;
176
+ hashC.blockLen = tmp.blockLen;
177
+ hashC.create = () => hashCons();
178
+ return hashC;
179
+ }
180
+ var Hash;
181
+ var init_utils = __esm({
182
+ "node_modules/@noble/hashes/esm/utils.js"() {
151
183
  "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
184
+ Hash = class {
185
+ };
186
+ }
187
+ });
188
+
189
+ // node_modules/@noble/hashes/esm/hmac.js
190
+ var HMAC, hmac;
191
+ var init_hmac = __esm({
192
+ "node_modules/@noble/hashes/esm/hmac.js"() {
193
+ "use strict";
194
+ init_utils();
195
+ HMAC = class extends Hash {
196
+ constructor(hash, _key) {
197
+ super();
198
+ this.finished = false;
199
+ this.destroyed = false;
200
+ ahash(hash);
201
+ const key = toBytes(_key);
202
+ this.iHash = hash.create();
203
+ if (typeof this.iHash.update !== "function")
204
+ throw new Error("Expected instance of class which extends utils.Hash");
205
+ this.blockLen = this.iHash.blockLen;
206
+ this.outputLen = this.iHash.outputLen;
207
+ const blockLen = this.blockLen;
208
+ const pad = new Uint8Array(blockLen);
209
+ pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
210
+ for (let i = 0; i < pad.length; i++)
211
+ pad[i] ^= 54;
212
+ this.iHash.update(pad);
213
+ this.oHash = hash.create();
214
+ for (let i = 0; i < pad.length; i++)
215
+ pad[i] ^= 54 ^ 92;
216
+ this.oHash.update(pad);
217
+ clean(pad);
218
+ }
219
+ update(buf) {
220
+ aexists(this);
221
+ this.iHash.update(buf);
222
+ return this;
223
+ }
224
+ digestInto(out) {
225
+ aexists(this);
226
+ abytes(out, this.outputLen);
227
+ this.finished = true;
228
+ this.iHash.digestInto(out);
229
+ this.oHash.update(out);
230
+ this.oHash.digestInto(out);
231
+ this.destroy();
232
+ }
233
+ digest() {
234
+ const out = new Uint8Array(this.oHash.outputLen);
235
+ this.digestInto(out);
236
+ return out;
237
+ }
238
+ _cloneInto(to) {
239
+ to || (to = Object.create(Object.getPrototypeOf(this), {}));
240
+ const { oHash, iHash, finished: finished2, destroyed, blockLen, outputLen } = this;
241
+ to = to;
242
+ to.finished = finished2;
243
+ to.destroyed = destroyed;
244
+ to.blockLen = blockLen;
245
+ to.outputLen = outputLen;
246
+ to.oHash = oHash._cloneInto(to.oHash);
247
+ to.iHash = iHash._cloneInto(to.iHash);
248
+ return to;
249
+ }
250
+ clone() {
251
+ return this._cloneInto();
252
+ }
253
+ destroy() {
254
+ this.destroyed = true;
255
+ this.oHash.destroy();
256
+ this.iHash.destroy();
257
+ }
258
+ };
259
+ hmac = (hash, key, message) => new HMAC(hash, key).update(message).digest();
260
+ hmac.create = (hash, key) => new HMAC(hash, key);
261
+ }
262
+ });
263
+
264
+ // node_modules/@noble/hashes/esm/_md.js
265
+ function setBigUint64(view, byteOffset, value, isLE) {
266
+ if (typeof view.setBigUint64 === "function")
267
+ return view.setBigUint64(byteOffset, value, isLE);
268
+ const _32n2 = BigInt(32);
269
+ const _u32_max = BigInt(4294967295);
270
+ const wh = Number(value >> _32n2 & _u32_max);
271
+ const wl = Number(value & _u32_max);
272
+ const h = isLE ? 4 : 0;
273
+ const l = isLE ? 0 : 4;
274
+ view.setUint32(byteOffset + h, wh, isLE);
275
+ view.setUint32(byteOffset + l, wl, isLE);
276
+ }
277
+ var HashMD, SHA512_IV;
278
+ var init_md = __esm({
279
+ "node_modules/@noble/hashes/esm/_md.js"() {
280
+ "use strict";
281
+ init_utils();
282
+ HashMD = class extends Hash {
283
+ constructor(blockLen, outputLen, padOffset, isLE) {
284
+ super();
285
+ this.finished = false;
286
+ this.length = 0;
287
+ this.pos = 0;
288
+ this.destroyed = false;
289
+ this.blockLen = blockLen;
290
+ this.outputLen = outputLen;
291
+ this.padOffset = padOffset;
292
+ this.isLE = isLE;
293
+ this.buffer = new Uint8Array(blockLen);
294
+ this.view = createView(this.buffer);
295
+ }
296
+ update(data) {
297
+ aexists(this);
298
+ data = toBytes(data);
299
+ abytes(data);
300
+ const { view, buffer, blockLen } = this;
301
+ const len = data.length;
302
+ for (let pos = 0; pos < len; ) {
303
+ const take = Math.min(blockLen - this.pos, len - pos);
304
+ if (take === blockLen) {
305
+ const dataView = createView(data);
306
+ for (; blockLen <= len - pos; pos += blockLen)
307
+ this.process(dataView, pos);
308
+ continue;
309
+ }
310
+ buffer.set(data.subarray(pos, pos + take), this.pos);
311
+ this.pos += take;
312
+ pos += take;
313
+ if (this.pos === blockLen) {
314
+ this.process(view, 0);
315
+ this.pos = 0;
166
316
  }
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
317
  }
318
+ this.length += data.length;
319
+ this.roundClean();
320
+ return this;
178
321
  }
179
- ];
322
+ digestInto(out) {
323
+ aexists(this);
324
+ aoutput(out, this);
325
+ this.finished = true;
326
+ const { buffer, view, blockLen, isLE } = this;
327
+ let { pos } = this;
328
+ buffer[pos++] = 128;
329
+ clean(this.buffer.subarray(pos));
330
+ if (this.padOffset > blockLen - pos) {
331
+ this.process(view, 0);
332
+ pos = 0;
333
+ }
334
+ for (let i = pos; i < blockLen; i++)
335
+ buffer[i] = 0;
336
+ setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
337
+ this.process(view, 0);
338
+ const oview = createView(out);
339
+ const len = this.outputLen;
340
+ if (len % 4)
341
+ throw new Error("_sha2: outputLen should be aligned to 32bit");
342
+ const outLen = len / 4;
343
+ const state = this.get();
344
+ if (outLen > state.length)
345
+ throw new Error("_sha2: outputLen bigger than state");
346
+ for (let i = 0; i < outLen; i++)
347
+ oview.setUint32(4 * i, state[i], isLE);
348
+ }
349
+ digest() {
350
+ const { buffer, outputLen } = this;
351
+ this.digestInto(buffer);
352
+ const res = buffer.slice(0, outputLen);
353
+ this.destroy();
354
+ return res;
355
+ }
356
+ _cloneInto(to) {
357
+ to || (to = new this.constructor());
358
+ to.set(...this.get());
359
+ const { blockLen, buffer, length, finished: finished2, destroyed, pos } = this;
360
+ to.destroyed = destroyed;
361
+ to.finished = finished2;
362
+ to.length = length;
363
+ to.pos = pos;
364
+ if (length % blockLen)
365
+ to.buffer.set(buffer);
366
+ return to;
367
+ }
368
+ clone() {
369
+ return this._cloneInto();
370
+ }
371
+ };
372
+ SHA512_IV = /* @__PURE__ */ Uint32Array.from([
373
+ 1779033703,
374
+ 4089235720,
375
+ 3144134277,
376
+ 2227873595,
377
+ 1013904242,
378
+ 4271175723,
379
+ 2773480762,
380
+ 1595750129,
381
+ 1359893119,
382
+ 2917565137,
383
+ 2600822924,
384
+ 725511199,
385
+ 528734635,
386
+ 4215389547,
387
+ 1541459225,
388
+ 327033209
389
+ ]);
180
390
  }
181
391
  });
182
392
 
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
- }
393
+ // node_modules/@noble/hashes/esm/_u64.js
394
+ function fromBig(n, le = false) {
395
+ if (le)
396
+ return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
397
+ return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
398
+ }
399
+ function split(lst, le = false) {
400
+ const len = lst.length;
401
+ let Ah = new Uint32Array(len);
402
+ let Al = new Uint32Array(len);
403
+ for (let i = 0; i < len; i++) {
404
+ const { h, l } = fromBig(lst[i], le);
405
+ [Ah[i], Al[i]] = [h, l];
406
+ }
407
+ return [Ah, Al];
408
+ }
409
+ function add(Ah, Al, Bh, Bl) {
410
+ const l = (Al >>> 0) + (Bl >>> 0);
411
+ return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 };
412
+ }
413
+ var U32_MASK64, _32n, shrSH, shrSL, rotrSH, rotrSL, rotrBH, rotrBL, add3L, add3H, add4L, add4H, add5L, add5H;
414
+ var init_u64 = __esm({
415
+ "node_modules/@noble/hashes/esm/_u64.js"() {
416
+ "use strict";
417
+ U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
418
+ _32n = /* @__PURE__ */ BigInt(32);
419
+ shrSH = (h, _l, s) => h >>> s;
420
+ shrSL = (h, l, s) => h << 32 - s | l >>> s;
421
+ rotrSH = (h, l, s) => h >>> s | l << 32 - s;
422
+ rotrSL = (h, l, s) => h << 32 - s | l >>> s;
423
+ rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
424
+ rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
425
+ add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
426
+ add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
427
+ add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
428
+ add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
429
+ add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
430
+ add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
201
431
  }
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
- );
432
+ });
433
+
434
+ // node_modules/@noble/hashes/esm/sha2.js
435
+ var K512, SHA512_Kh, SHA512_Kl, SHA512_W_H, SHA512_W_L, SHA512, sha512;
436
+ var init_sha2 = __esm({
437
+ "node_modules/@noble/hashes/esm/sha2.js"() {
438
+ "use strict";
439
+ init_md();
440
+ init_u64();
441
+ init_utils();
442
+ K512 = /* @__PURE__ */ (() => split([
443
+ "0x428a2f98d728ae22",
444
+ "0x7137449123ef65cd",
445
+ "0xb5c0fbcfec4d3b2f",
446
+ "0xe9b5dba58189dbbc",
447
+ "0x3956c25bf348b538",
448
+ "0x59f111f1b605d019",
449
+ "0x923f82a4af194f9b",
450
+ "0xab1c5ed5da6d8118",
451
+ "0xd807aa98a3030242",
452
+ "0x12835b0145706fbe",
453
+ "0x243185be4ee4b28c",
454
+ "0x550c7dc3d5ffb4e2",
455
+ "0x72be5d74f27b896f",
456
+ "0x80deb1fe3b1696b1",
457
+ "0x9bdc06a725c71235",
458
+ "0xc19bf174cf692694",
459
+ "0xe49b69c19ef14ad2",
460
+ "0xefbe4786384f25e3",
461
+ "0x0fc19dc68b8cd5b5",
462
+ "0x240ca1cc77ac9c65",
463
+ "0x2de92c6f592b0275",
464
+ "0x4a7484aa6ea6e483",
465
+ "0x5cb0a9dcbd41fbd4",
466
+ "0x76f988da831153b5",
467
+ "0x983e5152ee66dfab",
468
+ "0xa831c66d2db43210",
469
+ "0xb00327c898fb213f",
470
+ "0xbf597fc7beef0ee4",
471
+ "0xc6e00bf33da88fc2",
472
+ "0xd5a79147930aa725",
473
+ "0x06ca6351e003826f",
474
+ "0x142929670a0e6e70",
475
+ "0x27b70a8546d22ffc",
476
+ "0x2e1b21385c26c926",
477
+ "0x4d2c6dfc5ac42aed",
478
+ "0x53380d139d95b3df",
479
+ "0x650a73548baf63de",
480
+ "0x766a0abb3c77b2a8",
481
+ "0x81c2c92e47edaee6",
482
+ "0x92722c851482353b",
483
+ "0xa2bfe8a14cf10364",
484
+ "0xa81a664bbc423001",
485
+ "0xc24b8b70d0f89791",
486
+ "0xc76c51a30654be30",
487
+ "0xd192e819d6ef5218",
488
+ "0xd69906245565a910",
489
+ "0xf40e35855771202a",
490
+ "0x106aa07032bbd1b8",
491
+ "0x19a4c116b8d2d0c8",
492
+ "0x1e376c085141ab53",
493
+ "0x2748774cdf8eeb99",
494
+ "0x34b0bcb5e19b48a8",
495
+ "0x391c0cb3c5c95a63",
496
+ "0x4ed8aa4ae3418acb",
497
+ "0x5b9cca4f7763e373",
498
+ "0x682e6ff3d6b2b8a3",
499
+ "0x748f82ee5defb2fc",
500
+ "0x78a5636f43172f60",
501
+ "0x84c87814a1f0ab72",
502
+ "0x8cc702081a6439ec",
503
+ "0x90befffa23631e28",
504
+ "0xa4506cebde82bde9",
505
+ "0xbef9a3f7b2c67915",
506
+ "0xc67178f2e372532b",
507
+ "0xca273eceea26619c",
508
+ "0xd186b8c721c0c207",
509
+ "0xeada7dd6cde0eb1e",
510
+ "0xf57d4f7fee6ed178",
511
+ "0x06f067aa72176fba",
512
+ "0x0a637dc5a2c898a6",
513
+ "0x113f9804bef90dae",
514
+ "0x1b710b35131c471b",
515
+ "0x28db77f523047d84",
516
+ "0x32caab7b40c72493",
517
+ "0x3c9ebe0a15c9bebc",
518
+ "0x431d67c49c100d4c",
519
+ "0x4cc5d4becb3e42b6",
520
+ "0x597f299cfc657e2a",
521
+ "0x5fcb6fab3ad6faec",
522
+ "0x6c44198c4a475817"
523
+ ].map((n) => BigInt(n))))();
524
+ SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
525
+ SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
526
+ SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
527
+ SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
528
+ SHA512 = class extends HashMD {
529
+ constructor(outputLen = 64) {
530
+ super(128, outputLen, 16, false);
531
+ this.Ah = SHA512_IV[0] | 0;
532
+ this.Al = SHA512_IV[1] | 0;
533
+ this.Bh = SHA512_IV[2] | 0;
534
+ this.Bl = SHA512_IV[3] | 0;
535
+ this.Ch = SHA512_IV[4] | 0;
536
+ this.Cl = SHA512_IV[5] | 0;
537
+ this.Dh = SHA512_IV[6] | 0;
538
+ this.Dl = SHA512_IV[7] | 0;
539
+ this.Eh = SHA512_IV[8] | 0;
540
+ this.El = SHA512_IV[9] | 0;
541
+ this.Fh = SHA512_IV[10] | 0;
542
+ this.Fl = SHA512_IV[11] | 0;
543
+ this.Gh = SHA512_IV[12] | 0;
544
+ this.Gl = SHA512_IV[13] | 0;
545
+ this.Hh = SHA512_IV[14] | 0;
546
+ this.Hl = SHA512_IV[15] | 0;
227
547
  }
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
- };
548
+ // prettier-ignore
549
+ get() {
550
+ const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
551
+ return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
552
+ }
553
+ // prettier-ignore
554
+ set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
555
+ this.Ah = Ah | 0;
556
+ this.Al = Al | 0;
557
+ this.Bh = Bh | 0;
558
+ this.Bl = Bl | 0;
559
+ this.Ch = Ch | 0;
560
+ this.Cl = Cl | 0;
561
+ this.Dh = Dh | 0;
562
+ this.Dl = Dl | 0;
563
+ this.Eh = Eh | 0;
564
+ this.El = El | 0;
565
+ this.Fh = Fh | 0;
566
+ this.Fl = Fl | 0;
567
+ this.Gh = Gh | 0;
568
+ this.Gl = Gl | 0;
569
+ this.Hh = Hh | 0;
570
+ this.Hl = Hl | 0;
571
+ }
572
+ process(view, offset) {
573
+ for (let i = 0; i < 16; i++, offset += 4) {
574
+ SHA512_W_H[i] = view.getUint32(offset);
575
+ SHA512_W_L[i] = view.getUint32(offset += 4);
576
+ }
577
+ for (let i = 16; i < 80; i++) {
578
+ const W15h = SHA512_W_H[i - 15] | 0;
579
+ const W15l = SHA512_W_L[i - 15] | 0;
580
+ const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
581
+ const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
582
+ const W2h = SHA512_W_H[i - 2] | 0;
583
+ const W2l = SHA512_W_L[i - 2] | 0;
584
+ const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
585
+ const s1l = rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6);
586
+ const SUMl = add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
587
+ const SUMh = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);
588
+ SHA512_W_H[i] = SUMh | 0;
589
+ SHA512_W_L[i] = SUMl | 0;
590
+ }
591
+ let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
592
+ for (let i = 0; i < 80; i++) {
593
+ const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
594
+ const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
595
+ const CHIh = Eh & Fh ^ ~Eh & Gh;
596
+ const CHIl = El & Fl ^ ~El & Gl;
597
+ const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
598
+ const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
599
+ const T1l = T1ll | 0;
600
+ const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
601
+ const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
602
+ const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
603
+ const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
604
+ Hh = Gh | 0;
605
+ Hl = Gl | 0;
606
+ Gh = Fh | 0;
607
+ Gl = Fl | 0;
608
+ Fh = Eh | 0;
609
+ Fl = El | 0;
610
+ ({ h: Eh, l: El } = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
611
+ Dh = Ch | 0;
612
+ Dl = Cl | 0;
613
+ Ch = Bh | 0;
614
+ Cl = Bl | 0;
615
+ Bh = Ah | 0;
616
+ Bl = Al | 0;
617
+ const All = add3L(T1l, sigma0l, MAJl);
618
+ Ah = add3H(All, T1h, sigma0h, MAJh);
619
+ Al = All | 0;
620
+ }
621
+ ({ h: Ah, l: Al } = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
622
+ ({ h: Bh, l: Bl } = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
623
+ ({ h: Ch, l: Cl } = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
624
+ ({ h: Dh, l: Dl } = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
625
+ ({ h: Eh, l: El } = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
626
+ ({ h: Fh, l: Fl } = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
627
+ ({ h: Gh, l: Gl } = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
628
+ ({ h: Hh, l: Hl } = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
629
+ this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
630
+ }
631
+ roundClean() {
632
+ clean(SHA512_W_H, SHA512_W_L);
633
+ }
634
+ destroy() {
635
+ clean(this.buffer);
636
+ this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
637
+ }
638
+ };
639
+ sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
640
+ }
641
+ });
642
+
643
+ // node_modules/@noble/hashes/esm/sha512.js
644
+ var sha5122;
645
+ var init_sha512 = __esm({
646
+ "node_modules/@noble/hashes/esm/sha512.js"() {
647
+ "use strict";
648
+ init_sha2();
649
+ sha5122 = sha512;
650
+ }
651
+ });
652
+
653
+ // src/wallet.ts
654
+ var wallet_exports = {};
655
+ __export(wallet_exports, {
656
+ deriveAllKeys: () => deriveAllKeys,
657
+ deriveEvmKey: () => deriveEvmKey,
658
+ deriveSolanaKeyBytes: () => deriveSolanaKeyBytes,
659
+ deriveSolanaKeyBytesLegacy: () => deriveSolanaKeyBytesLegacy,
660
+ generateWalletMnemonic: () => generateWalletMnemonic,
661
+ isValidMnemonic: () => isValidMnemonic
662
+ });
663
+ import { HDKey } from "@scure/bip32";
664
+ import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
665
+ import { wordlist as english } from "@scure/bip39/wordlists/english";
666
+ import { privateKeyToAccount } from "viem/accounts";
667
+ function generateWalletMnemonic() {
668
+ return generateMnemonic(english, 256);
240
669
  }
241
- function buildPartnerTools(proxyBaseUrl) {
242
- return PARTNER_SERVICES.map((service) => buildTool(service, proxyBaseUrl));
670
+ function isValidMnemonic(mnemonic) {
671
+ return validateMnemonic(mnemonic, english);
672
+ }
673
+ function deriveEvmKey(mnemonic) {
674
+ const seed = mnemonicToSeedSync(mnemonic);
675
+ const hdKey = HDKey.fromMasterSeed(seed);
676
+ const derived = hdKey.derive(ETH_DERIVATION_PATH);
677
+ if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
678
+ const hex = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
679
+ const account = privateKeyToAccount(hex);
680
+ return { privateKey: hex, address: account.address };
243
681
  }
244
- var init_tools = __esm({
245
- "src/partners/tools.ts"() {
682
+ function deriveSolanaKeyBytes(mnemonic) {
683
+ const seed = mnemonicToSeedSync(mnemonic);
684
+ let I = hmac(sha5122, "ed25519 seed", seed);
685
+ let key = I.slice(0, 32);
686
+ let chainCode = I.slice(32);
687
+ for (const index of SOLANA_HARDENED_INDICES) {
688
+ const data = new Uint8Array(37);
689
+ data[0] = 0;
690
+ data.set(key, 1);
691
+ data[33] = index >>> 24 & 255;
692
+ data[34] = index >>> 16 & 255;
693
+ data[35] = index >>> 8 & 255;
694
+ data[36] = index & 255;
695
+ I = hmac(sha5122, chainCode, data);
696
+ key = I.slice(0, 32);
697
+ chainCode = I.slice(32);
698
+ }
699
+ return new Uint8Array(key);
700
+ }
701
+ function deriveSolanaKeyBytesLegacy(mnemonic) {
702
+ const seed = mnemonicToSeedSync(mnemonic);
703
+ const hdKey = HDKey.fromMasterSeed(seed);
704
+ const derived = hdKey.derive("m/44'/501'/0'/0'");
705
+ if (!derived.privateKey) throw new Error("Failed to derive legacy Solana private key");
706
+ return new Uint8Array(derived.privateKey);
707
+ }
708
+ function deriveAllKeys(mnemonic) {
709
+ const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);
710
+ const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);
711
+ return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };
712
+ }
713
+ var ETH_DERIVATION_PATH, SOLANA_HARDENED_INDICES;
714
+ var init_wallet = __esm({
715
+ "src/wallet.ts"() {
246
716
  "use strict";
247
- init_registry();
717
+ init_hmac();
718
+ init_sha512();
719
+ ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0";
720
+ SOLANA_HARDENED_INDICES = [
721
+ 44 + 2147483648,
722
+ 501 + 2147483648,
723
+ 0 + 2147483648,
724
+ 0 + 2147483648
725
+ ];
248
726
  }
249
727
  });
250
728
 
251
- // src/partners/index.ts
252
- var partners_exports = {};
253
- __export(partners_exports, {
254
- PARTNER_SERVICES: () => PARTNER_SERVICES,
255
- buildPartnerTools: () => buildPartnerTools,
256
- getPartnerService: () => getPartnerService
729
+ // src/solana-sweep.ts
730
+ var solana_sweep_exports = {};
731
+ __export(solana_sweep_exports, {
732
+ sweepSolanaWallet: () => sweepSolanaWallet
257
733
  });
258
- var init_partners = __esm({
259
- "src/partners/index.ts"() {
734
+ import {
735
+ address as solAddress2,
736
+ createSolanaRpc as createSolanaRpc2,
737
+ createSolanaRpcSubscriptions,
738
+ createKeyPairSignerFromPrivateKeyBytes,
739
+ pipe,
740
+ createTransactionMessage,
741
+ setTransactionMessageFeePayer,
742
+ setTransactionMessageLifetimeUsingBlockhash,
743
+ appendTransactionMessageInstructions,
744
+ signTransactionMessageWithSigners,
745
+ getSignatureFromTransaction,
746
+ sendAndConfirmTransactionFactory,
747
+ getProgramDerivedAddress,
748
+ getAddressEncoder
749
+ } from "@solana/kit";
750
+ async function getAssociatedTokenAddress(owner, mint) {
751
+ const encoder = getAddressEncoder();
752
+ const [ata] = await getProgramDerivedAddress({
753
+ programAddress: ASSOCIATED_TOKEN_PROGRAM,
754
+ seeds: [encoder.encode(owner), encoder.encode(TOKEN_PROGRAM), encoder.encode(mint)]
755
+ });
756
+ return ata;
757
+ }
758
+ function buildCreateAtaIdempotentInstruction(payer, ata, owner, mint) {
759
+ return {
760
+ programAddress: ASSOCIATED_TOKEN_PROGRAM,
761
+ accounts: [
762
+ {
763
+ address: payer,
764
+ role: 3
765
+ /* writable signer */
766
+ },
767
+ {
768
+ address: ata,
769
+ role: 1
770
+ /* writable */
771
+ },
772
+ {
773
+ address: owner,
774
+ role: 0
775
+ /* readonly */
776
+ },
777
+ {
778
+ address: mint,
779
+ role: 0
780
+ /* readonly */
781
+ },
782
+ {
783
+ address: SYSTEM_PROGRAM,
784
+ role: 0
785
+ /* readonly */
786
+ },
787
+ {
788
+ address: TOKEN_PROGRAM,
789
+ role: 0
790
+ /* readonly */
791
+ }
792
+ ],
793
+ data: new Uint8Array([1])
794
+ // instruction index 1 = CreateIdempotent
795
+ };
796
+ }
797
+ function buildTokenTransferInstruction(source, destination, authority, amount) {
798
+ const data = new Uint8Array(9);
799
+ data[0] = 3;
800
+ const view = new DataView(data.buffer, data.byteOffset);
801
+ view.setBigUint64(1, amount, true);
802
+ return {
803
+ programAddress: TOKEN_PROGRAM,
804
+ accounts: [
805
+ {
806
+ address: source,
807
+ role: 1
808
+ /* writable */
809
+ },
810
+ {
811
+ address: destination,
812
+ role: 1
813
+ /* writable */
814
+ },
815
+ {
816
+ address: authority,
817
+ role: 2
818
+ /* signer */
819
+ }
820
+ ],
821
+ data
822
+ };
823
+ }
824
+ async function sweepSolanaWallet(oldKeyBytes, newAddress, rpcUrl) {
825
+ const url = rpcUrl || process["env"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC2;
826
+ const rpc = createSolanaRpc2(url);
827
+ const oldSigner = await createKeyPairSignerFromPrivateKeyBytes(oldKeyBytes);
828
+ const oldAddress = oldSigner.address;
829
+ const mint = solAddress2(SOLANA_USDC_MINT2);
830
+ const newOwner = solAddress2(newAddress);
831
+ let solBalance;
832
+ try {
833
+ const solResp = await rpc.getBalance(solAddress2(oldAddress)).send();
834
+ solBalance = solResp.value;
835
+ } catch (err) {
836
+ return {
837
+ error: `Failed to check SOL balance: ${err instanceof Error ? err.message : String(err)}`,
838
+ oldAddress,
839
+ newAddress
840
+ };
841
+ }
842
+ let usdcBalance = 0n;
843
+ let oldTokenAccount;
844
+ try {
845
+ const response = await rpc.getTokenAccountsByOwner(
846
+ solAddress2(oldAddress),
847
+ { mint },
848
+ { encoding: "jsonParsed" }
849
+ ).send();
850
+ if (response.value.length > 0) {
851
+ for (const account of response.value) {
852
+ const parsed = account.account.data;
853
+ const amount = BigInt(parsed.parsed.info.tokenAmount.amount);
854
+ if (amount > 0n) {
855
+ usdcBalance += amount;
856
+ oldTokenAccount = account.pubkey;
857
+ }
858
+ }
859
+ }
860
+ } catch (err) {
861
+ return {
862
+ error: `Failed to check USDC balance: ${err instanceof Error ? err.message : String(err)}`,
863
+ oldAddress,
864
+ newAddress
865
+ };
866
+ }
867
+ if (usdcBalance === 0n) {
868
+ return {
869
+ error: "No USDC found in old wallet. Nothing to sweep.",
870
+ oldAddress,
871
+ newAddress,
872
+ solBalance,
873
+ usdcBalance: 0n
874
+ };
875
+ }
876
+ const MIN_SOL_FOR_GAS = 5000000n;
877
+ if (solBalance < MIN_SOL_FOR_GAS) {
878
+ const needed = Number(MIN_SOL_FOR_GAS - solBalance) / 1e9;
879
+ return {
880
+ error: `Insufficient SOL for transaction fees. Send ~${needed.toFixed(4)} SOL to ${oldAddress} to cover gas. Current SOL balance: ${(Number(solBalance) / 1e9).toFixed(6)} SOL`,
881
+ oldAddress,
882
+ newAddress,
883
+ solBalance,
884
+ usdcBalance
885
+ };
886
+ }
887
+ if (!oldTokenAccount) {
888
+ return {
889
+ error: "Could not find USDC token account in old wallet.",
890
+ oldAddress,
891
+ newAddress
892
+ };
893
+ }
894
+ try {
895
+ const newAta = await getAssociatedTokenAddress(newOwner, mint);
896
+ const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
897
+ const createAtaIx = buildCreateAtaIdempotentInstruction(
898
+ oldSigner.address,
899
+ newAta,
900
+ newOwner,
901
+ mint
902
+ );
903
+ const transferIx = buildTokenTransferInstruction(
904
+ solAddress2(oldTokenAccount),
905
+ newAta,
906
+ oldSigner.address,
907
+ usdcBalance
908
+ );
909
+ const txMessage = pipe(
910
+ createTransactionMessage({ version: 0 }),
911
+ (msg) => setTransactionMessageFeePayer(oldSigner.address, msg),
912
+ (msg) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, msg),
913
+ (msg) => appendTransactionMessageInstructions([createAtaIx, transferIx], msg)
914
+ );
915
+ const signedTx = await signTransactionMessageWithSigners(txMessage);
916
+ const txSignature = getSignatureFromTransaction(signedTx);
917
+ const wsUrl = url.replace("https://", "wss://").replace("http://", "ws://");
918
+ const rpcSubscriptions = createSolanaRpcSubscriptions(wsUrl);
919
+ const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
920
+ await sendAndConfirm(signedTx, { commitment: "confirmed" });
921
+ const dollars = Number(usdcBalance) / 1e6;
922
+ return {
923
+ transferred: `$${dollars.toFixed(2)}`,
924
+ transferredMicros: usdcBalance,
925
+ txSignature,
926
+ oldAddress,
927
+ newAddress
928
+ };
929
+ } catch (err) {
930
+ return {
931
+ error: `Transaction failed: ${err instanceof Error ? err.message : String(err)}`,
932
+ oldAddress,
933
+ newAddress,
934
+ solBalance,
935
+ usdcBalance
936
+ };
937
+ }
938
+ }
939
+ var SOLANA_USDC_MINT2, SOLANA_DEFAULT_RPC2, TOKEN_PROGRAM, ASSOCIATED_TOKEN_PROGRAM, SYSTEM_PROGRAM;
940
+ var init_solana_sweep = __esm({
941
+ "src/solana-sweep.ts"() {
260
942
  "use strict";
261
- init_registry();
262
- init_tools();
943
+ SOLANA_USDC_MINT2 = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
944
+ SOLANA_DEFAULT_RPC2 = "https://api.mainnet-beta.solana.com";
945
+ TOKEN_PROGRAM = "TokenkegQfeN4jV6bme4LphiJbfPe2VopRsimuVSoZ5K";
946
+ ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
947
+ SYSTEM_PROGRAM = "11111111111111111111111111111111";
263
948
  }
264
949
  });
265
950
 
@@ -307,6 +992,8 @@ var MODEL_ALIASES = {
307
992
  // Google
308
993
  gemini: "google/gemini-2.5-pro",
309
994
  flash: "google/gemini-2.5-flash",
995
+ "gemini-3.1-pro-preview": "google/gemini-3.1-pro",
996
+ "google/gemini-3.1-pro-preview": "google/gemini-3.1-pro",
310
997
  // xAI
311
998
  grok: "xai/grok-3",
312
999
  "grok-fast": "xai/grok-4-fast-reasoning",
@@ -380,7 +1067,8 @@ var BLOCKRUN_MODELS = [
380
1067
  maxOutput: 128e3,
381
1068
  reasoning: true,
382
1069
  vision: true,
383
- agentic: true
1070
+ agentic: true,
1071
+ toolCalling: true
384
1072
  },
385
1073
  {
386
1074
  id: "openai/gpt-5-mini",
@@ -389,7 +1077,8 @@ var BLOCKRUN_MODELS = [
389
1077
  inputPrice: 0.25,
390
1078
  outputPrice: 2,
391
1079
  contextWindow: 2e5,
392
- maxOutput: 65536
1080
+ maxOutput: 65536,
1081
+ toolCalling: true
393
1082
  },
394
1083
  {
395
1084
  id: "openai/gpt-5-nano",
@@ -398,7 +1087,8 @@ var BLOCKRUN_MODELS = [
398
1087
  inputPrice: 0.05,
399
1088
  outputPrice: 0.4,
400
1089
  contextWindow: 128e3,
401
- maxOutput: 32768
1090
+ maxOutput: 32768,
1091
+ toolCalling: true
402
1092
  },
403
1093
  {
404
1094
  id: "openai/gpt-5.2-pro",
@@ -408,7 +1098,8 @@ var BLOCKRUN_MODELS = [
408
1098
  outputPrice: 168,
409
1099
  contextWindow: 4e5,
410
1100
  maxOutput: 128e3,
411
- reasoning: true
1101
+ reasoning: true,
1102
+ toolCalling: true
412
1103
  },
413
1104
  // OpenAI Codex Family
414
1105
  {
@@ -419,7 +1110,8 @@ var BLOCKRUN_MODELS = [
419
1110
  outputPrice: 14,
420
1111
  contextWindow: 128e3,
421
1112
  maxOutput: 32e3,
422
- agentic: true
1113
+ agentic: true,
1114
+ toolCalling: true
423
1115
  },
424
1116
  // OpenAI GPT-4 Family
425
1117
  {
@@ -430,7 +1122,8 @@ var BLOCKRUN_MODELS = [
430
1122
  outputPrice: 8,
431
1123
  contextWindow: 128e3,
432
1124
  maxOutput: 16384,
433
- vision: true
1125
+ vision: true,
1126
+ toolCalling: true
434
1127
  },
435
1128
  {
436
1129
  id: "openai/gpt-4.1-mini",
@@ -439,7 +1132,8 @@ var BLOCKRUN_MODELS = [
439
1132
  inputPrice: 0.4,
440
1133
  outputPrice: 1.6,
441
1134
  contextWindow: 128e3,
442
- maxOutput: 16384
1135
+ maxOutput: 16384,
1136
+ toolCalling: true
443
1137
  },
444
1138
  {
445
1139
  id: "openai/gpt-4.1-nano",
@@ -448,7 +1142,8 @@ var BLOCKRUN_MODELS = [
448
1142
  inputPrice: 0.1,
449
1143
  outputPrice: 0.4,
450
1144
  contextWindow: 128e3,
451
- maxOutput: 16384
1145
+ maxOutput: 16384,
1146
+ toolCalling: true
452
1147
  },
453
1148
  {
454
1149
  id: "openai/gpt-4o",
@@ -459,7 +1154,8 @@ var BLOCKRUN_MODELS = [
459
1154
  contextWindow: 128e3,
460
1155
  maxOutput: 16384,
461
1156
  vision: true,
462
- agentic: true
1157
+ agentic: true,
1158
+ toolCalling: true
463
1159
  },
464
1160
  {
465
1161
  id: "openai/gpt-4o-mini",
@@ -468,7 +1164,8 @@ var BLOCKRUN_MODELS = [
468
1164
  inputPrice: 0.15,
469
1165
  outputPrice: 0.6,
470
1166
  contextWindow: 128e3,
471
- maxOutput: 16384
1167
+ maxOutput: 16384,
1168
+ toolCalling: true
472
1169
  },
473
1170
  // OpenAI O-series (Reasoning)
474
1171
  {
@@ -479,7 +1176,8 @@ var BLOCKRUN_MODELS = [
479
1176
  outputPrice: 60,
480
1177
  contextWindow: 2e5,
481
1178
  maxOutput: 1e5,
482
- reasoning: true
1179
+ reasoning: true,
1180
+ toolCalling: true
483
1181
  },
484
1182
  {
485
1183
  id: "openai/o1-mini",
@@ -489,7 +1187,8 @@ var BLOCKRUN_MODELS = [
489
1187
  outputPrice: 4.4,
490
1188
  contextWindow: 128e3,
491
1189
  maxOutput: 65536,
492
- reasoning: true
1190
+ reasoning: true,
1191
+ toolCalling: true
493
1192
  },
494
1193
  {
495
1194
  id: "openai/o3",
@@ -499,7 +1198,8 @@ var BLOCKRUN_MODELS = [
499
1198
  outputPrice: 8,
500
1199
  contextWindow: 2e5,
501
1200
  maxOutput: 1e5,
502
- reasoning: true
1201
+ reasoning: true,
1202
+ toolCalling: true
503
1203
  },
504
1204
  {
505
1205
  id: "openai/o3-mini",
@@ -509,7 +1209,8 @@ var BLOCKRUN_MODELS = [
509
1209
  outputPrice: 4.4,
510
1210
  contextWindow: 128e3,
511
1211
  maxOutput: 65536,
512
- reasoning: true
1212
+ reasoning: true,
1213
+ toolCalling: true
513
1214
  },
514
1215
  {
515
1216
  id: "openai/o4-mini",
@@ -519,7 +1220,8 @@ var BLOCKRUN_MODELS = [
519
1220
  outputPrice: 4.4,
520
1221
  contextWindow: 128e3,
521
1222
  maxOutput: 65536,
522
- reasoning: true
1223
+ reasoning: true,
1224
+ toolCalling: true
523
1225
  },
524
1226
  // Anthropic - all Claude models excel at agentic workflows
525
1227
  // Use newest versions (4.6) with full provider prefix
@@ -531,7 +1233,9 @@ var BLOCKRUN_MODELS = [
531
1233
  outputPrice: 5,
532
1234
  contextWindow: 2e5,
533
1235
  maxOutput: 8192,
534
- agentic: true
1236
+ vision: true,
1237
+ agentic: true,
1238
+ toolCalling: true
535
1239
  },
536
1240
  {
537
1241
  id: "anthropic/claude-sonnet-4.6",
@@ -542,7 +1246,9 @@ var BLOCKRUN_MODELS = [
542
1246
  contextWindow: 2e5,
543
1247
  maxOutput: 64e3,
544
1248
  reasoning: true,
545
- agentic: true
1249
+ vision: true,
1250
+ agentic: true,
1251
+ toolCalling: true
546
1252
  },
547
1253
  {
548
1254
  id: "anthropic/claude-opus-4.6",
@@ -553,19 +1259,22 @@ var BLOCKRUN_MODELS = [
553
1259
  contextWindow: 2e5,
554
1260
  maxOutput: 32e3,
555
1261
  reasoning: true,
556
- agentic: true
1262
+ vision: true,
1263
+ agentic: true,
1264
+ toolCalling: true
557
1265
  },
558
1266
  // Google
559
1267
  {
560
- id: "google/gemini-3.1-pro-preview",
561
- name: "Gemini 3.1 Pro Preview",
1268
+ id: "google/gemini-3.1-pro",
1269
+ name: "Gemini 3.1 Pro",
562
1270
  version: "3.1",
563
1271
  inputPrice: 2,
564
1272
  outputPrice: 12,
565
1273
  contextWindow: 105e4,
566
1274
  maxOutput: 65536,
567
1275
  reasoning: true,
568
- vision: true
1276
+ vision: true,
1277
+ toolCalling: true
569
1278
  },
570
1279
  {
571
1280
  id: "google/gemini-3-pro-preview",
@@ -576,7 +1285,8 @@ var BLOCKRUN_MODELS = [
576
1285
  contextWindow: 105e4,
577
1286
  maxOutput: 65536,
578
1287
  reasoning: true,
579
- vision: true
1288
+ vision: true,
1289
+ toolCalling: true
580
1290
  },
581
1291
  {
582
1292
  id: "google/gemini-3-flash-preview",
@@ -586,7 +1296,8 @@ var BLOCKRUN_MODELS = [
586
1296
  outputPrice: 3,
587
1297
  contextWindow: 1e6,
588
1298
  maxOutput: 65536,
589
- vision: true
1299
+ vision: true,
1300
+ toolCalling: true
590
1301
  },
591
1302
  {
592
1303
  id: "google/gemini-2.5-pro",
@@ -597,7 +1308,8 @@ var BLOCKRUN_MODELS = [
597
1308
  contextWindow: 105e4,
598
1309
  maxOutput: 65536,
599
1310
  reasoning: true,
600
- vision: true
1311
+ vision: true,
1312
+ toolCalling: true
601
1313
  },
602
1314
  {
603
1315
  id: "google/gemini-2.5-flash",
@@ -606,7 +1318,9 @@ var BLOCKRUN_MODELS = [
606
1318
  inputPrice: 0.3,
607
1319
  outputPrice: 2.5,
608
1320
  contextWindow: 1e6,
609
- maxOutput: 65536
1321
+ maxOutput: 65536,
1322
+ vision: true,
1323
+ toolCalling: true
610
1324
  },
611
1325
  {
612
1326
  id: "google/gemini-2.5-flash-lite",
@@ -615,7 +1329,8 @@ var BLOCKRUN_MODELS = [
615
1329
  inputPrice: 0.1,
616
1330
  outputPrice: 0.4,
617
1331
  contextWindow: 1e6,
618
- maxOutput: 65536
1332
+ maxOutput: 65536,
1333
+ toolCalling: true
619
1334
  },
620
1335
  // DeepSeek
621
1336
  {
@@ -625,7 +1340,8 @@ var BLOCKRUN_MODELS = [
625
1340
  inputPrice: 0.28,
626
1341
  outputPrice: 0.42,
627
1342
  contextWindow: 128e3,
628
- maxOutput: 8192
1343
+ maxOutput: 8192,
1344
+ toolCalling: true
629
1345
  },
630
1346
  {
631
1347
  id: "deepseek/deepseek-reasoner",
@@ -635,7 +1351,8 @@ var BLOCKRUN_MODELS = [
635
1351
  outputPrice: 0.42,
636
1352
  contextWindow: 128e3,
637
1353
  maxOutput: 8192,
638
- reasoning: true
1354
+ reasoning: true,
1355
+ toolCalling: true
639
1356
  },
640
1357
  // Moonshot / Kimi - optimized for agentic workflows
641
1358
  {
@@ -648,7 +1365,8 @@ var BLOCKRUN_MODELS = [
648
1365
  maxOutput: 8192,
649
1366
  reasoning: true,
650
1367
  vision: true,
651
- agentic: true
1368
+ agentic: true,
1369
+ toolCalling: true
652
1370
  },
653
1371
  // xAI / Grok
654
1372
  {
@@ -659,7 +1377,8 @@ var BLOCKRUN_MODELS = [
659
1377
  outputPrice: 15,
660
1378
  contextWindow: 131072,
661
1379
  maxOutput: 16384,
662
- reasoning: true
1380
+ reasoning: true,
1381
+ toolCalling: true
663
1382
  },
664
1383
  // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead
665
1384
  {
@@ -669,7 +1388,8 @@ var BLOCKRUN_MODELS = [
669
1388
  inputPrice: 0.3,
670
1389
  outputPrice: 0.5,
671
1390
  contextWindow: 131072,
672
- maxOutput: 16384
1391
+ maxOutput: 16384,
1392
+ toolCalling: true
673
1393
  },
674
1394
  // xAI Grok 4 Family - Ultra-cheap fast models
675
1395
  {
@@ -680,7 +1400,8 @@ var BLOCKRUN_MODELS = [
680
1400
  outputPrice: 0.5,
681
1401
  contextWindow: 131072,
682
1402
  maxOutput: 16384,
683
- reasoning: true
1403
+ reasoning: true,
1404
+ toolCalling: true
684
1405
  },
685
1406
  {
686
1407
  id: "xai/grok-4-fast-non-reasoning",
@@ -689,7 +1410,8 @@ var BLOCKRUN_MODELS = [
689
1410
  inputPrice: 0.2,
690
1411
  outputPrice: 0.5,
691
1412
  contextWindow: 131072,
692
- maxOutput: 16384
1413
+ maxOutput: 16384,
1414
+ toolCalling: true
693
1415
  },
694
1416
  {
695
1417
  id: "xai/grok-4-1-fast-reasoning",
@@ -699,7 +1421,8 @@ var BLOCKRUN_MODELS = [
699
1421
  outputPrice: 0.5,
700
1422
  contextWindow: 131072,
701
1423
  maxOutput: 16384,
702
- reasoning: true
1424
+ reasoning: true,
1425
+ toolCalling: true
703
1426
  },
704
1427
  {
705
1428
  id: "xai/grok-4-1-fast-non-reasoning",
@@ -708,7 +1431,8 @@ var BLOCKRUN_MODELS = [
708
1431
  inputPrice: 0.2,
709
1432
  outputPrice: 0.5,
710
1433
  contextWindow: 131072,
711
- maxOutput: 16384
1434
+ maxOutput: 16384,
1435
+ toolCalling: true
712
1436
  },
713
1437
  {
714
1438
  id: "xai/grok-code-fast-1",
@@ -717,9 +1441,10 @@ var BLOCKRUN_MODELS = [
717
1441
  inputPrice: 0.2,
718
1442
  outputPrice: 1.5,
719
1443
  contextWindow: 131072,
720
- maxOutput: 16384,
721
- agentic: true
722
- // Good for coding tasks
1444
+ maxOutput: 16384
1445
+ // toolCalling intentionally omitted: outputs tool calls as plain text JSON,
1446
+ // not OpenAI-compatible structured function calls. Will be skipped when
1447
+ // request has tools to prevent the "talking to itself" bug.
723
1448
  },
724
1449
  {
725
1450
  id: "xai/grok-4-0709",
@@ -729,7 +1454,8 @@ var BLOCKRUN_MODELS = [
729
1454
  outputPrice: 1.5,
730
1455
  contextWindow: 131072,
731
1456
  maxOutput: 16384,
732
- reasoning: true
1457
+ reasoning: true,
1458
+ toolCalling: true
733
1459
  },
734
1460
  {
735
1461
  id: "xai/grok-2-vision",
@@ -739,7 +1465,8 @@ var BLOCKRUN_MODELS = [
739
1465
  outputPrice: 10,
740
1466
  contextWindow: 131072,
741
1467
  maxOutput: 16384,
742
- vision: true
1468
+ vision: true,
1469
+ toolCalling: true
743
1470
  },
744
1471
  // MiniMax
745
1472
  {
@@ -751,7 +1478,8 @@ var BLOCKRUN_MODELS = [
751
1478
  contextWindow: 204800,
752
1479
  maxOutput: 16384,
753
1480
  reasoning: true,
754
- agentic: true
1481
+ agentic: true,
1482
+ toolCalling: true
755
1483
  },
756
1484
  // NVIDIA - Free/cheap models
757
1485
  {
@@ -762,6 +1490,8 @@ var BLOCKRUN_MODELS = [
762
1490
  outputPrice: 0,
763
1491
  contextWindow: 128e3,
764
1492
  maxOutput: 16384
1493
+ // toolCalling intentionally omitted: free model, structured function
1494
+ // calling support unverified. Excluded from tool-heavy routing paths.
765
1495
  },
766
1496
  {
767
1497
  id: "nvidia/kimi-k2.5",
@@ -770,7 +1500,8 @@ var BLOCKRUN_MODELS = [
770
1500
  inputPrice: 0.55,
771
1501
  outputPrice: 2.5,
772
1502
  contextWindow: 262144,
773
- maxOutput: 16384
1503
+ maxOutput: 16384,
1504
+ toolCalling: true
774
1505
  }
775
1506
  ];
776
1507
  function toOpenClawModel(m) {
@@ -815,6 +1546,16 @@ function isAgenticModel(modelId) {
815
1546
  function getAgenticModels() {
816
1547
  return BLOCKRUN_MODELS.filter((m) => m.agentic).map((m) => m.id);
817
1548
  }
1549
+ function supportsToolCalling(modelId) {
1550
+ const normalized = modelId.replace("blockrun/", "");
1551
+ const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
1552
+ return model?.toolCalling ?? false;
1553
+ }
1554
+ function supportsVision(modelId) {
1555
+ const normalized = modelId.replace("blockrun/", "");
1556
+ const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
1557
+ return model?.vision ?? false;
1558
+ }
818
1559
  function getModelContextWindow(modelId) {
819
1560
  const normalized = modelId.replace("blockrun/", "");
820
1561
  const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
@@ -861,13 +1602,13 @@ import { x402Client } from "@x402/fetch";
861
1602
  // src/payment-preauth.ts
862
1603
  import { x402HTTPClient } from "@x402/fetch";
863
1604
  var DEFAULT_TTL_MS = 36e5;
864
- function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS) {
1605
+ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, options) {
865
1606
  const httpClient = new x402HTTPClient(client);
866
1607
  const cache = /* @__PURE__ */ new Map();
867
1608
  return async (input, init) => {
868
1609
  const request = new Request(input, init);
869
1610
  const urlPath = new URL(request.url).pathname;
870
- const cached = cache.get(urlPath);
1611
+ const cached = !options?.skipPreAuth ? cache.get(urlPath) : void 0;
871
1612
  if (cached && Date.now() - cached.cachedAt < ttlMs) {
872
1613
  try {
873
1614
  const payload2 = await client.createPaymentPayload(cached.paymentRequired);
@@ -903,7 +1644,8 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS) {
903
1644
  cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });
904
1645
  } catch (error) {
905
1646
  throw new Error(
906
- `Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`
1647
+ `Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`,
1648
+ { cause: error }
907
1649
  );
908
1650
  }
909
1651
  const payload = await client.createPaymentPayload(paymentRequired);
@@ -1007,20 +1749,18 @@ function scoreAgenticTask(text, keywords) {
1007
1749
  };
1008
1750
  }
1009
1751
  function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1010
- const text = `${systemPrompt ?? ""} ${prompt}`.toLowerCase();
1011
1752
  const userText = prompt.toLowerCase();
1012
1753
  const dimensions = [
1013
- // Original 8 dimensions
1754
+ // Token count uses total estimated tokens (system + user) — context size matters for model selection
1014
1755
  scoreTokenCount(estimatedTokens, config.tokenCountThresholds),
1015
1756
  scoreKeywordMatch(
1016
- text,
1757
+ userText,
1017
1758
  config.codeKeywords,
1018
1759
  "codePresence",
1019
1760
  "code",
1020
1761
  { low: 1, high: 2 },
1021
1762
  { none: 0, low: 0.5, high: 1 }
1022
1763
  ),
1023
- // Reasoning markers use USER prompt only — system prompt "step by step" shouldn't trigger reasoning
1024
1764
  scoreKeywordMatch(
1025
1765
  userText,
1026
1766
  config.reasoningKeywords,
@@ -1030,7 +1770,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1030
1770
  { none: 0, low: 0.7, high: 1 }
1031
1771
  ),
1032
1772
  scoreKeywordMatch(
1033
- text,
1773
+ userText,
1034
1774
  config.technicalKeywords,
1035
1775
  "technicalTerms",
1036
1776
  "technical",
@@ -1038,7 +1778,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1038
1778
  { none: 0, low: 0.5, high: 1 }
1039
1779
  ),
1040
1780
  scoreKeywordMatch(
1041
- text,
1781
+ userText,
1042
1782
  config.creativeKeywords,
1043
1783
  "creativeMarkers",
1044
1784
  "creative",
@@ -1046,18 +1786,18 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1046
1786
  { none: 0, low: 0.5, high: 0.7 }
1047
1787
  ),
1048
1788
  scoreKeywordMatch(
1049
- text,
1789
+ userText,
1050
1790
  config.simpleKeywords,
1051
1791
  "simpleIndicators",
1052
1792
  "simple",
1053
1793
  { low: 1, high: 2 },
1054
1794
  { none: 0, low: -1, high: -1 }
1055
1795
  ),
1056
- scoreMultiStep(text),
1796
+ scoreMultiStep(userText),
1057
1797
  scoreQuestionComplexity(prompt),
1058
1798
  // 6 new dimensions
1059
1799
  scoreKeywordMatch(
1060
- text,
1800
+ userText,
1061
1801
  config.imperativeVerbs,
1062
1802
  "imperativeVerbs",
1063
1803
  "imperative",
@@ -1065,7 +1805,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1065
1805
  { none: 0, low: 0.3, high: 0.5 }
1066
1806
  ),
1067
1807
  scoreKeywordMatch(
1068
- text,
1808
+ userText,
1069
1809
  config.constraintIndicators,
1070
1810
  "constraintCount",
1071
1811
  "constraints",
@@ -1073,7 +1813,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1073
1813
  { none: 0, low: 0.3, high: 0.7 }
1074
1814
  ),
1075
1815
  scoreKeywordMatch(
1076
- text,
1816
+ userText,
1077
1817
  config.outputFormatKeywords,
1078
1818
  "outputFormat",
1079
1819
  "format",
@@ -1081,7 +1821,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1081
1821
  { none: 0, low: 0.4, high: 0.7 }
1082
1822
  ),
1083
1823
  scoreKeywordMatch(
1084
- text,
1824
+ userText,
1085
1825
  config.referenceKeywords,
1086
1826
  "referenceComplexity",
1087
1827
  "references",
@@ -1089,7 +1829,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1089
1829
  { none: 0, low: 0.3, high: 0.5 }
1090
1830
  ),
1091
1831
  scoreKeywordMatch(
1092
- text,
1832
+ userText,
1093
1833
  config.negationKeywords,
1094
1834
  "negationComplexity",
1095
1835
  "negation",
@@ -1097,7 +1837,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1097
1837
  { none: 0, low: 0.3, high: 0.5 }
1098
1838
  ),
1099
1839
  scoreKeywordMatch(
1100
- text,
1840
+ userText,
1101
1841
  config.domainSpecificKeywords,
1102
1842
  "domainSpecificity",
1103
1843
  "domain-specific",
@@ -1129,7 +1869,8 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1129
1869
  tier: "REASONING",
1130
1870
  confidence: Math.max(confidence2, 0.85),
1131
1871
  signals,
1132
- agenticScore
1872
+ agenticScore,
1873
+ dimensions
1133
1874
  };
1134
1875
  }
1135
1876
  const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
@@ -1153,9 +1894,9 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
1153
1894
  }
1154
1895
  const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
1155
1896
  if (confidence < config.confidenceThreshold) {
1156
- return { score: weightedScore, tier: null, confidence, signals, agenticScore };
1897
+ return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };
1157
1898
  }
1158
- return { score: weightedScore, tier, confidence, signals, agenticScore };
1899
+ return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };
1159
1900
  }
1160
1901
  function calibrateConfidence(distance, steepness) {
1161
1902
  return 1 / (1 + Math.exp(-steepness * distance));
@@ -1163,7 +1904,9 @@ function calibrateConfidence(distance, steepness) {
1163
1904
 
1164
1905
  // src/router/selector.ts
1165
1906
  var BASELINE_MODEL_ID = "anthropic/claude-opus-4.6";
1166
- function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
1907
+ var BASELINE_INPUT_PRICE = 5;
1908
+ var BASELINE_OUTPUT_PRICE = 25;
1909
+ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
1167
1910
  const tierConfig = tierConfigs[tier];
1168
1911
  const model = tierConfig.primary;
1169
1912
  const pricing = modelPricing.get(model);
@@ -1173,8 +1916,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
1173
1916
  const outputCost = maxOutputTokens / 1e6 * outputPrice;
1174
1917
  const costEstimate = inputCost + outputCost;
1175
1918
  const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
1176
- const opusInputPrice = opusPricing?.inputPrice ?? 0;
1177
- const opusOutputPrice = opusPricing?.outputPrice ?? 0;
1919
+ const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
1920
+ const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
1178
1921
  const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
1179
1922
  const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
1180
1923
  const baselineCost = baselineInput + baselineOutput;
@@ -1187,7 +1930,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
1187
1930
  reasoning,
1188
1931
  costEstimate,
1189
1932
  baselineCost,
1190
- savings
1933
+ savings,
1934
+ ...agenticScore !== void 0 && { agenticScore }
1191
1935
  };
1192
1936
  }
1193
1937
  function getFallbackChain(tier, tierConfigs) {
@@ -1202,14 +1946,24 @@ function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutput
1202
1946
  const outputCost = maxOutputTokens / 1e6 * outputPrice;
1203
1947
  const costEstimate = inputCost + outputCost;
1204
1948
  const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
1205
- const opusInputPrice = opusPricing?.inputPrice ?? 0;
1206
- const opusOutputPrice = opusPricing?.outputPrice ?? 0;
1949
+ const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
1950
+ const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
1207
1951
  const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
1208
1952
  const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
1209
1953
  const baselineCost = baselineInput + baselineOutput;
1210
1954
  const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
1211
1955
  return { costEstimate, baselineCost, savings };
1212
1956
  }
1957
+ function filterByToolCalling(models, hasTools, supportsToolCalling2) {
1958
+ if (!hasTools) return models;
1959
+ const filtered = models.filter(supportsToolCalling2);
1960
+ return filtered.length > 0 ? filtered : models;
1961
+ }
1962
+ function filterByVision(models, hasVision, supportsVision2) {
1963
+ if (!hasVision) return models;
1964
+ const filtered = models.filter(supportsVision2);
1965
+ return filtered.length > 0 ? filtered : models;
1966
+ }
1213
1967
  function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
1214
1968
  const fullChain = getFallbackChain(tier, tierConfigs);
1215
1969
  const filtered = fullChain.filter((modelId) => {
@@ -2265,18 +3019,18 @@ var DEFAULT_ROUTING_CONFIG = {
2265
3019
  ]
2266
3020
  },
2267
3021
  MEDIUM: {
2268
- primary: "xai/grok-code-fast-1",
2269
- // Code specialist, $0.20/$1.50
3022
+ primary: "moonshot/kimi-k2.5",
3023
+ // $0.50/$2.40 - strong tool use, proper function call format
2270
3024
  fallback: [
3025
+ "deepseek/deepseek-chat",
2271
3026
  "google/gemini-2.5-flash-lite",
2272
3027
  // 1M context, ultra cheap ($0.10/$0.40)
2273
- "deepseek/deepseek-chat",
2274
3028
  "xai/grok-4-1-fast-non-reasoning"
2275
3029
  // Upgraded Grok 4.1
2276
3030
  ]
2277
3031
  },
2278
3032
  COMPLEX: {
2279
- primary: "google/gemini-3.1-pro-preview",
3033
+ primary: "google/gemini-3.1-pro",
2280
3034
  // Newest Gemini 3.1 - upgraded from 3.0
2281
3035
  fallback: [
2282
3036
  "google/gemini-2.5-flash-lite",
@@ -2336,7 +3090,7 @@ var DEFAULT_ROUTING_CONFIG = {
2336
3090
  fallback: [
2337
3091
  "anthropic/claude-haiku-4.5",
2338
3092
  "google/gemini-2.5-flash-lite",
2339
- "xai/grok-code-fast-1"
3093
+ "deepseek/deepseek-chat"
2340
3094
  ]
2341
3095
  },
2342
3096
  MEDIUM: {
@@ -2356,7 +3110,7 @@ var DEFAULT_ROUTING_CONFIG = {
2356
3110
  "openai/gpt-5.2-codex",
2357
3111
  "anthropic/claude-opus-4.6",
2358
3112
  "anthropic/claude-sonnet-4.6",
2359
- "google/gemini-3.1-pro-preview",
3113
+ "google/gemini-3.1-pro",
2360
3114
  // Newest Gemini
2361
3115
  "google/gemini-3-pro-preview",
2362
3116
  "moonshot/kimi-k2.5"
@@ -2387,9 +3141,13 @@ var DEFAULT_ROUTING_CONFIG = {
2387
3141
  ]
2388
3142
  },
2389
3143
  MEDIUM: {
2390
- primary: "xai/grok-code-fast-1",
2391
- // Code specialist for agentic coding
2392
- fallback: ["moonshot/kimi-k2.5", "anthropic/claude-haiku-4.5", "claude-sonnet-4"]
3144
+ primary: "moonshot/kimi-k2.5",
3145
+ // $0.50/$2.40 - strong tool use, handles function calls correctly
3146
+ fallback: [
3147
+ "anthropic/claude-haiku-4.5",
3148
+ "deepseek/deepseek-chat",
3149
+ "xai/grok-4-1-fast-non-reasoning"
3150
+ ]
2393
3151
  },
2394
3152
  COMPLEX: {
2395
3153
  primary: "anthropic/claude-sonnet-4.6",
@@ -2397,7 +3155,7 @@ var DEFAULT_ROUTING_CONFIG = {
2397
3155
  "anthropic/claude-opus-4.6",
2398
3156
  // Latest Opus - best agentic
2399
3157
  "openai/gpt-5.2",
2400
- "google/gemini-3.1-pro-preview",
3158
+ "google/gemini-3.1-pro",
2401
3159
  // Newest Gemini
2402
3160
  "google/gemini-3-pro-preview",
2403
3161
  "xai/grok-4-0709"
@@ -2429,7 +3187,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2429
3187
  const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
2430
3188
  const { routingProfile } = options;
2431
3189
  let tierConfigs;
2432
- let profileSuffix = "";
3190
+ let profileSuffix;
2433
3191
  if (routingProfile === "eco" && config.ecoTiers) {
2434
3192
  tierConfigs = config.ecoTiers;
2435
3193
  profileSuffix = " | eco";
@@ -2444,6 +3202,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2444
3202
  tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
2445
3203
  profileSuffix = useAgenticTiers ? " | agentic" : "";
2446
3204
  }
3205
+ const agenticScoreValue = ruleResult.agenticScore;
2447
3206
  if (estimatedTokens > config.overrides.maxTokensForceComplex) {
2448
3207
  return selectModel(
2449
3208
  "COMPLEX",
@@ -2454,7 +3213,8 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2454
3213
  modelPricing,
2455
3214
  estimatedTokens,
2456
3215
  maxOutputTokens,
2457
- routingProfile
3216
+ routingProfile,
3217
+ agenticScoreValue
2458
3218
  );
2459
3219
  }
2460
3220
  const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
@@ -2488,7 +3248,8 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2488
3248
  modelPricing,
2489
3249
  estimatedTokens,
2490
3250
  maxOutputTokens,
2491
- routingProfile
3251
+ routingProfile,
3252
+ agenticScoreValue
2492
3253
  );
2493
3254
  }
2494
3255
 
@@ -3243,6 +4004,9 @@ var BalanceMonitor = class {
3243
4004
  }
3244
4005
  };
3245
4006
 
4007
+ // src/proxy.ts
4008
+ init_solana_balance();
4009
+
3246
4010
  // src/auth.ts
3247
4011
  import { writeFile, mkdir as mkdir2 } from "fs/promises";
3248
4012
  init_wallet();
@@ -3263,7 +4027,9 @@ async function loadSavedWallet() {
3263
4027
  console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
3264
4028
  console.error(`[ClawRouter] File: ${WALLET_FILE}`);
3265
4029
  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`);
4030
+ console.error(
4031
+ `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
4032
+ );
3267
4033
  throw new Error(
3268
4034
  `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
4035
  );
@@ -3276,7 +4042,8 @@ async function loadSavedWallet() {
3276
4042
  `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
3277
4043
  );
3278
4044
  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.`
4045
+ `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.`,
4046
+ { cause: err }
3280
4047
  );
3281
4048
  }
3282
4049
  }
@@ -3321,7 +4088,8 @@ async function generateAndSaveWallet() {
3321
4088
  console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);
3322
4089
  } catch (err) {
3323
4090
  throw new Error(
3324
- `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
4091
+ `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`,
4092
+ { cause: err }
3325
4093
  );
3326
4094
  }
3327
4095
  console.log(`[ClawRouter]`);
@@ -3347,6 +4115,29 @@ async function generateAndSaveWallet() {
3347
4115
  solanaPrivateKeyBytes: derived.solanaPrivateKeyBytes
3348
4116
  };
3349
4117
  }
4118
+ async function logMigrationWarning(legacyKeyBytes, newKeyBytes) {
4119
+ try {
4120
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
4121
+ const [oldSigner, newSigner] = await Promise.all([
4122
+ createKeyPairSignerFromPrivateKeyBytes2(legacyKeyBytes),
4123
+ createKeyPairSignerFromPrivateKeyBytes2(newKeyBytes)
4124
+ ]);
4125
+ console.log(`[ClawRouter]`);
4126
+ console.log(`[ClawRouter] \u26A0 SOLANA WALLET MIGRATION DETECTED`);
4127
+ 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`);
4128
+ console.log(`[ClawRouter] Old address (secp256k1): ${oldSigner.address}`);
4129
+ console.log(`[ClawRouter] New address (SLIP-10): ${newSigner.address}`);
4130
+ console.log(`[ClawRouter]`);
4131
+ console.log(`[ClawRouter] Your Solana wallet derivation has been fixed to use`);
4132
+ console.log(`[ClawRouter] SLIP-10 Ed25519 (Phantom/Solflare compatible).`);
4133
+ console.log(`[ClawRouter]`);
4134
+ console.log(`[ClawRouter] If you had funds in the old wallet, run:`);
4135
+ console.log(`[ClawRouter] /wallet migrate-solana`);
4136
+ 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`);
4137
+ console.log(`[ClawRouter]`);
4138
+ } catch {
4139
+ }
4140
+ }
3350
4141
  async function resolveOrGenerateWalletKey() {
3351
4142
  const saved = await loadSavedWallet();
3352
4143
  if (saved) {
@@ -3354,13 +4145,19 @@ async function resolveOrGenerateWalletKey() {
3354
4145
  const mnemonic = await loadMnemonic();
3355
4146
  if (mnemonic) {
3356
4147
  const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3357
- return {
4148
+ const result2 = {
3358
4149
  key: saved,
3359
4150
  address: account.address,
3360
4151
  source: "saved",
3361
4152
  mnemonic,
3362
4153
  solanaPrivateKeyBytes: solanaKeyBytes
3363
4154
  };
4155
+ const legacyKeyBytes = deriveSolanaKeyBytesLegacy(mnemonic);
4156
+ if (Buffer.from(legacyKeyBytes).toString("hex") !== Buffer.from(solanaKeyBytes).toString("hex")) {
4157
+ result2.legacySolanaKeyBytes = legacyKeyBytes;
4158
+ await logMigrationWarning(legacyKeyBytes, solanaKeyBytes);
4159
+ }
4160
+ return result2;
3364
4161
  }
3365
4162
  return { key: saved, address: account.address, source: "saved" };
3366
4163
  }
@@ -3370,13 +4167,19 @@ async function resolveOrGenerateWalletKey() {
3370
4167
  const mnemonic = await loadMnemonic();
3371
4168
  if (mnemonic) {
3372
4169
  const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3373
- return {
4170
+ const result2 = {
3374
4171
  key: envKey,
3375
4172
  address: account.address,
3376
4173
  source: "env",
3377
4174
  mnemonic,
3378
4175
  solanaPrivateKeyBytes: solanaKeyBytes
3379
4176
  };
4177
+ const legacyKeyBytes = deriveSolanaKeyBytesLegacy(mnemonic);
4178
+ if (Buffer.from(legacyKeyBytes).toString("hex") !== Buffer.from(solanaKeyBytes).toString("hex")) {
4179
+ result2.legacySolanaKeyBytes = legacyKeyBytes;
4180
+ await logMigrationWarning(legacyKeyBytes, solanaKeyBytes);
4181
+ }
4182
+ return result2;
3380
4183
  }
3381
4184
  return { key: envKey, address: account.address, source: "env" };
3382
4185
  }
@@ -3392,9 +4195,7 @@ async function resolveOrGenerateWalletKey() {
3392
4195
  async function setupSolana() {
3393
4196
  const existing = await loadMnemonic();
3394
4197
  if (existing) {
3395
- throw new Error(
3396
- "Solana wallet already set up. Mnemonic file exists at " + MNEMONIC_FILE
3397
- );
4198
+ throw new Error("Solana wallet already set up. Mnemonic file exists at " + MNEMONIC_FILE);
3398
4199
  }
3399
4200
  const savedKey = await loadSavedWallet();
3400
4201
  if (!savedKey) {
@@ -4175,8 +4976,9 @@ function shouldCompress(messages) {
4175
4976
  }
4176
4977
 
4177
4978
  // src/session.ts
4979
+ import { createHash as createHash3 } from "crypto";
4178
4980
  var DEFAULT_SESSION_CONFIG = {
4179
- enabled: false,
4981
+ enabled: true,
4180
4982
  timeoutMs: 30 * 60 * 1e3,
4181
4983
  // 30 minutes
4182
4984
  headerName: "x-session-id"
@@ -4231,7 +5033,10 @@ var SessionStore = class {
4231
5033
  tier,
4232
5034
  createdAt: now,
4233
5035
  lastUsedAt: now,
4234
- requestCount: 1
5036
+ requestCount: 1,
5037
+ recentHashes: [],
5038
+ strikes: 0,
5039
+ escalated: false
4235
5040
  });
4236
5041
  }
4237
5042
  }
@@ -4283,6 +5088,43 @@ var SessionStore = class {
4283
5088
  }
4284
5089
  }
4285
5090
  }
5091
+ /**
5092
+ * Record a request content hash and detect repetitive patterns.
5093
+ * Returns true if escalation should be triggered (3+ consecutive similar requests).
5094
+ */
5095
+ recordRequestHash(sessionId, hash) {
5096
+ const entry = this.sessions.get(sessionId);
5097
+ if (!entry) return false;
5098
+ const prev = entry.recentHashes;
5099
+ if (prev.length > 0 && prev[prev.length - 1] === hash) {
5100
+ entry.strikes++;
5101
+ } else {
5102
+ entry.strikes = 0;
5103
+ }
5104
+ entry.recentHashes.push(hash);
5105
+ if (entry.recentHashes.length > 3) {
5106
+ entry.recentHashes.shift();
5107
+ }
5108
+ return entry.strikes >= 2 && !entry.escalated;
5109
+ }
5110
+ /**
5111
+ * Escalate session to next tier. Returns the new model/tier or null if already at max.
5112
+ */
5113
+ escalateSession(sessionId, tierConfigs) {
5114
+ const entry = this.sessions.get(sessionId);
5115
+ if (!entry) return null;
5116
+ const TIER_ORDER = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
5117
+ const currentIdx = TIER_ORDER.indexOf(entry.tier);
5118
+ if (currentIdx < 0 || currentIdx >= TIER_ORDER.length - 1) return null;
5119
+ const nextTier = TIER_ORDER[currentIdx + 1];
5120
+ const nextConfig = tierConfigs[nextTier];
5121
+ if (!nextConfig) return null;
5122
+ entry.model = nextConfig.primary;
5123
+ entry.tier = nextTier;
5124
+ entry.strikes = 0;
5125
+ entry.escalated = true;
5126
+ return { model: nextConfig.primary, tier: nextTier };
5127
+ }
4286
5128
  /**
4287
5129
  * Stop the cleanup interval.
4288
5130
  */
@@ -4303,6 +5145,17 @@ function getSessionId(headers, headerName = DEFAULT_SESSION_CONFIG.headerName) {
4303
5145
  }
4304
5146
  return void 0;
4305
5147
  }
5148
+ function deriveSessionId(messages) {
5149
+ const firstUser = messages.find((m) => m.role === "user");
5150
+ if (!firstUser) return void 0;
5151
+ const content = typeof firstUser.content === "string" ? firstUser.content : JSON.stringify(firstUser.content);
5152
+ return createHash3("sha256").update(content).digest("hex").slice(0, 8);
5153
+ }
5154
+ function hashRequestContent(lastUserContent, toolCallNames) {
5155
+ const normalized = lastUserContent.replace(/\s+/g, " ").trim().slice(0, 500);
5156
+ const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(",")}` : "";
5157
+ return createHash3("sha256").update(normalized + toolSuffix).digest("hex").slice(0, 12);
5158
+ }
4306
5159
 
4307
5160
  // src/updater.ts
4308
5161
  var NPM_REGISTRY = "https://registry.npmjs.org/@blockrun/clawrouter/latest";
@@ -5060,13 +5913,36 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
5060
5913
  }).catch(() => {
5061
5914
  });
5062
5915
  }
5916
+ async function uploadDataUriToHost(dataUri) {
5917
+ const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
5918
+ if (!match) throw new Error("Invalid data URI format");
5919
+ const [, mimeType, b64Data] = match;
5920
+ const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
5921
+ const buffer = Buffer.from(b64Data, "base64");
5922
+ const blob = new Blob([buffer], { type: mimeType });
5923
+ const form = new FormData();
5924
+ form.append("reqtype", "fileupload");
5925
+ form.append("fileToUpload", blob, `image.${ext}`);
5926
+ const resp = await fetch("https://catbox.moe/user/api.php", {
5927
+ method: "POST",
5928
+ body: form
5929
+ });
5930
+ if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
5931
+ const result = await resp.text();
5932
+ if (result.startsWith("https://")) {
5933
+ return result.trim();
5934
+ }
5935
+ throw new Error(`catbox.moe upload failed: ${result}`);
5936
+ }
5063
5937
  async function startProxy(options) {
5064
5938
  const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
5065
5939
  const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
5066
5940
  const paymentChain = options.paymentChain ?? await resolvePaymentChain();
5067
5941
  const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
5068
5942
  if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
5069
- console.warn(`[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`);
5943
+ console.warn(
5944
+ `[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`
5945
+ );
5070
5946
  } else if (paymentChain === "solana") {
5071
5947
  console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
5072
5948
  }
@@ -5074,7 +5950,6 @@ async function startProxy(options) {
5074
5950
  const existingProxy = await checkExistingProxy(listenPort);
5075
5951
  if (existingProxy) {
5076
5952
  const account2 = privateKeyToAccount3(walletKey);
5077
- const balanceMonitor2 = new BalanceMonitor(account2.address);
5078
5953
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
5079
5954
  if (existingProxy.wallet !== account2.address) {
5080
5955
  console.warn(
@@ -5088,17 +5963,20 @@ async function startProxy(options) {
5088
5963
  );
5089
5964
  }
5090
5965
  } else if (paymentChain !== "base") {
5091
- console.warn(`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`);
5966
+ console.warn(
5967
+ `[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`
5968
+ );
5092
5969
  throw new Error(
5093
5970
  `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.`
5094
5971
  );
5095
5972
  }
5096
5973
  let reuseSolanaAddress;
5097
5974
  if (solanaPrivateKeyBytes) {
5098
- const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
5099
- const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
5975
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
5976
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
5100
5977
  reuseSolanaAddress = solanaSigner.address;
5101
5978
  }
5979
+ const balanceMonitor2 = paymentChain === "solana" && reuseSolanaAddress ? new SolanaBalanceMonitor(reuseSolanaAddress) : new BalanceMonitor(account2.address);
5102
5980
  options.onReady?.(listenPort);
5103
5981
  return {
5104
5982
  port: listenPort,
@@ -5118,8 +5996,8 @@ async function startProxy(options) {
5118
5996
  let solanaAddress;
5119
5997
  if (solanaPrivateKeyBytes) {
5120
5998
  const { registerExactSvmScheme } = await import("@x402/svm/exact/client");
5121
- const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
5122
- const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
5999
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
6000
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
5123
6001
  solanaAddress = solanaSigner.address;
5124
6002
  registerExactSvmScheme(x402, { signer: solanaSigner });
5125
6003
  console.log(`[ClawRouter] Solana x402 scheme registered: ${solanaAddress}`);
@@ -5129,8 +6007,10 @@ async function startProxy(options) {
5129
6007
  const chain = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
5130
6008
  console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);
5131
6009
  });
5132
- const payFetch = createPayFetchWithPreAuth(fetch, x402);
5133
- const balanceMonitor = new BalanceMonitor(account.address);
6010
+ const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
6011
+ skipPreAuth: paymentChain === "solana"
6012
+ });
6013
+ const balanceMonitor = paymentChain === "solana" && solanaAddress ? new SolanaBalanceMonitor(solanaAddress) : new BalanceMonitor(account.address);
5134
6014
  const routingConfig = mergeRoutingConfig(options.routingConfig);
5135
6015
  const modelPricing = buildModelPricing();
5136
6016
  const routerOpts = {
@@ -5284,7 +6164,11 @@ async function startProxy(options) {
5284
6164
  const existingProxy2 = await checkExistingProxy(listenPort);
5285
6165
  if (existingProxy2) {
5286
6166
  console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
5287
- rejectAttempt({ code: "REUSE_EXISTING", wallet: existingProxy2.wallet, existingChain: existingProxy2.paymentChain });
6167
+ rejectAttempt({
6168
+ code: "REUSE_EXISTING",
6169
+ wallet: existingProxy2.wallet,
6170
+ existingChain: existingProxy2.paymentChain
6171
+ });
5288
6172
  return;
5289
6173
  }
5290
6174
  if (attempt < PORT_RETRY_ATTEMPTS) {
@@ -5319,7 +6203,8 @@ async function startProxy(options) {
5319
6203
  if (error.code === "REUSE_EXISTING" && error.wallet) {
5320
6204
  if (error.existingChain && error.existingChain !== paymentChain) {
5321
6205
  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.`
6206
+ `Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`,
6207
+ { cause: err }
5323
6208
  );
5324
6209
  }
5325
6210
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
@@ -5427,15 +6312,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5427
6312
  } catch {
5428
6313
  }
5429
6314
  try {
5430
- const response = await payFetch(
5431
- upstreamUrl,
5432
- {
5433
- method,
5434
- headers,
5435
- body: requestBody.length > 0 ? new Uint8Array(requestBody) : void 0,
5436
- signal
5437
- }
5438
- );
6315
+ const response = await payFetch(upstreamUrl, {
6316
+ method,
6317
+ headers,
6318
+ body: requestBody.length > 0 ? new Uint8Array(requestBody) : void 0,
6319
+ signal
6320
+ });
5439
6321
  if (response.status !== 200) {
5440
6322
  const errorBody = await response.text();
5441
6323
  const isProviderErr = isProviderError(response.status, errorBody);
@@ -5483,14 +6365,19 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5483
6365
  }
5484
6366
  let body = Buffer.concat(bodyChunks);
5485
6367
  const originalContextSizeKB = Math.ceil(body.length / 1024);
6368
+ const debugMode = req.headers["x-clawrouter-debug"] !== "false";
5486
6369
  let routingDecision;
6370
+ let hasTools = false;
6371
+ let hasVision = false;
5487
6372
  let isStreaming = false;
5488
6373
  let modelId = "";
5489
6374
  let maxTokens = 4096;
5490
6375
  let routingProfile = null;
5491
6376
  let accumulatedContent = "";
6377
+ let responseInputTokens;
5492
6378
  const isChatCompletion = req.url?.includes("/chat/completions");
5493
6379
  const sessionId = getSessionId(req.headers);
6380
+ let effectiveSessionId = sessionId;
5494
6381
  if (isChatCompletion && body.length > 0) {
5495
6382
  try {
5496
6383
  const parsed = JSON.parse(body.toString());
@@ -5498,10 +6385,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5498
6385
  modelId = parsed.model || "";
5499
6386
  maxTokens = parsed.max_tokens || 4096;
5500
6387
  let bodyModified = false;
5501
- if (sessionId && Array.isArray(parsed.messages)) {
5502
- const messages = parsed.messages;
5503
- const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
5504
- const lastContent = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
6388
+ const parsedMessages = Array.isArray(parsed.messages) ? parsed.messages : [];
6389
+ const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === "user");
6390
+ const rawLastContent = lastUserMsg?.content;
6391
+ const lastContent = typeof rawLastContent === "string" ? rawLastContent : Array.isArray(rawLastContent) ? rawLastContent.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
6392
+ if (sessionId && parsedMessages.length > 0) {
6393
+ const messages = parsedMessages;
5505
6394
  if (sessionJournal.needsContext(lastContent)) {
5506
6395
  const journalText = sessionJournal.format(sessionId);
5507
6396
  if (journalText) {
@@ -5512,15 +6401,312 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5512
6401
  content: journalText + "\n\n" + messages[sysIdx].content
5513
6402
  };
5514
6403
  } else {
5515
- messages.unshift({ role: "system", content: journalText });
6404
+ messages.unshift({ role: "system", content: journalText });
6405
+ }
6406
+ parsed.messages = messages;
6407
+ bodyModified = true;
6408
+ console.log(
6409
+ `[ClawRouter] Injected session journal (${journalText.length} chars) for session ${sessionId.slice(0, 8)}...`
6410
+ );
6411
+ }
6412
+ }
6413
+ }
6414
+ if (lastContent.startsWith("/debug")) {
6415
+ const debugPrompt = lastContent.slice("/debug".length).trim() || "hello";
6416
+ const messages = parsed.messages;
6417
+ const systemMsg = messages?.find((m) => m.role === "system");
6418
+ const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
6419
+ const fullText = `${systemPrompt ?? ""} ${debugPrompt}`;
6420
+ const estimatedTokens = Math.ceil(fullText.length / 4);
6421
+ const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
6422
+ const profileName = normalizedModel2.replace("blockrun/", "");
6423
+ const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) ? profileName : "auto";
6424
+ const scoring = classifyByRules(
6425
+ debugPrompt,
6426
+ systemPrompt,
6427
+ estimatedTokens,
6428
+ DEFAULT_ROUTING_CONFIG.scoring
6429
+ );
6430
+ const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {
6431
+ ...routerOpts,
6432
+ routingProfile: debugProfile
6433
+ });
6434
+ const dimLines = (scoring.dimensions ?? []).map((d) => {
6435
+ const nameStr = (d.name + ":").padEnd(24);
6436
+ const scoreStr = d.score.toFixed(2).padStart(6);
6437
+ const sigStr = d.signal ? ` [${d.signal}]` : "";
6438
+ return ` ${nameStr}${scoreStr}${sigStr}`;
6439
+ }).join("\n");
6440
+ const sess = sessionId ? sessionStore.getSession(sessionId) : void 0;
6441
+ 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";
6442
+ const { simpleMedium, mediumComplex, complexReasoning } = DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;
6443
+ const debugText = [
6444
+ "ClawRouter Debug",
6445
+ "",
6446
+ `Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,
6447
+ `Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,
6448
+ `Reasoning: ${debugRouting.reasoning}`,
6449
+ "",
6450
+ `Scoring (weighted: ${scoring.score.toFixed(3)})`,
6451
+ dimLines,
6452
+ "",
6453
+ `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,
6454
+ "",
6455
+ sessLine
6456
+ ].join("\n");
6457
+ const completionId = `chatcmpl-debug-${Date.now()}`;
6458
+ const timestamp = Math.floor(Date.now() / 1e3);
6459
+ const syntheticResponse = {
6460
+ id: completionId,
6461
+ object: "chat.completion",
6462
+ created: timestamp,
6463
+ model: "clawrouter/debug",
6464
+ choices: [
6465
+ {
6466
+ index: 0,
6467
+ message: { role: "assistant", content: debugText },
6468
+ finish_reason: "stop"
6469
+ }
6470
+ ],
6471
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
6472
+ };
6473
+ if (isStreaming) {
6474
+ res.writeHead(200, {
6475
+ "Content-Type": "text/event-stream",
6476
+ "Cache-Control": "no-cache",
6477
+ Connection: "keep-alive"
6478
+ });
6479
+ const sseChunk = {
6480
+ id: completionId,
6481
+ object: "chat.completion.chunk",
6482
+ created: timestamp,
6483
+ model: "clawrouter/debug",
6484
+ choices: [
6485
+ {
6486
+ index: 0,
6487
+ delta: { role: "assistant", content: debugText },
6488
+ finish_reason: null
6489
+ }
6490
+ ]
6491
+ };
6492
+ const sseDone = {
6493
+ id: completionId,
6494
+ object: "chat.completion.chunk",
6495
+ created: timestamp,
6496
+ model: "clawrouter/debug",
6497
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
6498
+ };
6499
+ res.write(`data: ${JSON.stringify(sseChunk)}
6500
+
6501
+ `);
6502
+ res.write(`data: ${JSON.stringify(sseDone)}
6503
+
6504
+ `);
6505
+ res.write("data: [DONE]\n\n");
6506
+ res.end();
6507
+ } else {
6508
+ res.writeHead(200, { "Content-Type": "application/json" });
6509
+ res.end(JSON.stringify(syntheticResponse));
6510
+ }
6511
+ console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`);
6512
+ return;
6513
+ }
6514
+ if (lastContent.startsWith("/imagegen")) {
6515
+ const imageArgs = lastContent.slice("/imagegen".length).trim();
6516
+ let imageModel = "google/nano-banana";
6517
+ let imageSize = "1024x1024";
6518
+ let imagePrompt = imageArgs;
6519
+ const modelMatch = imageArgs.match(/--model\s+(\S+)/);
6520
+ if (modelMatch) {
6521
+ const raw = modelMatch[1];
6522
+ const IMAGE_MODEL_ALIASES = {
6523
+ "dall-e-3": "openai/dall-e-3",
6524
+ dalle3: "openai/dall-e-3",
6525
+ dalle: "openai/dall-e-3",
6526
+ "gpt-image": "openai/gpt-image-1",
6527
+ "gpt-image-1": "openai/gpt-image-1",
6528
+ flux: "black-forest/flux-1.1-pro",
6529
+ "flux-pro": "black-forest/flux-1.1-pro",
6530
+ banana: "google/nano-banana",
6531
+ "nano-banana": "google/nano-banana",
6532
+ "banana-pro": "google/nano-banana-pro",
6533
+ "nano-banana-pro": "google/nano-banana-pro"
6534
+ };
6535
+ imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;
6536
+ imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim();
6537
+ }
6538
+ const sizeMatch = imageArgs.match(/--size\s+(\d+x\d+)/);
6539
+ if (sizeMatch) {
6540
+ imageSize = sizeMatch[1];
6541
+ imagePrompt = imagePrompt.replace(/--size\s+\d+x\d+/, "").trim();
6542
+ }
6543
+ if (!imagePrompt) {
6544
+ const errorText = [
6545
+ "Usage: /imagegen <prompt>",
6546
+ "",
6547
+ "Options:",
6548
+ " --model <model> Model to use (default: nano-banana)",
6549
+ " --size <WxH> Image size (default: 1024x1024)",
6550
+ "",
6551
+ "Models:",
6552
+ " nano-banana Google Gemini Flash \u2014 $0.05/image",
6553
+ " banana-pro Google Gemini Pro \u2014 $0.10/image (up to 4K)",
6554
+ " dall-e-3 OpenAI DALL-E 3 \u2014 $0.04/image",
6555
+ " gpt-image OpenAI GPT Image 1 \u2014 $0.02/image",
6556
+ " flux Black Forest Flux 1.1 Pro \u2014 $0.04/image",
6557
+ "",
6558
+ "Examples:",
6559
+ " /imagegen a cat wearing sunglasses",
6560
+ " /imagegen --model dall-e-3 a futuristic city at sunset",
6561
+ " /imagegen --model banana-pro --size 2048x2048 mountain landscape"
6562
+ ].join("\n");
6563
+ const completionId = `chatcmpl-image-${Date.now()}`;
6564
+ const timestamp = Math.floor(Date.now() / 1e3);
6565
+ if (isStreaming) {
6566
+ res.writeHead(200, {
6567
+ "Content-Type": "text/event-stream",
6568
+ "Cache-Control": "no-cache",
6569
+ Connection: "keep-alive"
6570
+ });
6571
+ res.write(
6572
+ `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 }] })}
6573
+
6574
+ `
6575
+ );
6576
+ res.write(
6577
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
6578
+
6579
+ `
6580
+ );
6581
+ res.write("data: [DONE]\n\n");
6582
+ res.end();
6583
+ } else {
6584
+ res.writeHead(200, { "Content-Type": "application/json" });
6585
+ res.end(
6586
+ JSON.stringify({
6587
+ id: completionId,
6588
+ object: "chat.completion",
6589
+ created: timestamp,
6590
+ model: "clawrouter/image",
6591
+ choices: [
6592
+ {
6593
+ index: 0,
6594
+ message: { role: "assistant", content: errorText },
6595
+ finish_reason: "stop"
6596
+ }
6597
+ ],
6598
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
6599
+ })
6600
+ );
6601
+ }
6602
+ console.log(`[ClawRouter] /imagegen command \u2192 showing usage help`);
6603
+ return;
6604
+ }
6605
+ console.log(
6606
+ `[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`
6607
+ );
6608
+ try {
6609
+ const imageUpstreamUrl = `${apiBase}/v1/images/generations`;
6610
+ const imageBody = JSON.stringify({
6611
+ model: imageModel,
6612
+ prompt: imagePrompt,
6613
+ size: imageSize,
6614
+ n: 1
6615
+ });
6616
+ const imageResponse = await payFetch(imageUpstreamUrl, {
6617
+ method: "POST",
6618
+ headers: { "content-type": "application/json", "user-agent": USER_AGENT },
6619
+ body: imageBody
6620
+ });
6621
+ const imageResult = await imageResponse.json();
6622
+ let responseText;
6623
+ if (!imageResponse.ok || imageResult.error) {
6624
+ const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`;
6625
+ responseText = `Image generation failed: ${errMsg}`;
6626
+ console.log(`[ClawRouter] /imagegen error: ${errMsg}`);
6627
+ } else {
6628
+ const images = imageResult.data ?? [];
6629
+ if (images.length === 0) {
6630
+ responseText = "Image generation returned no results.";
6631
+ } else {
6632
+ const lines = [];
6633
+ for (const img of images) {
6634
+ if (img.url) {
6635
+ if (img.url.startsWith("data:")) {
6636
+ try {
6637
+ const hostedUrl = await uploadDataUriToHost(img.url);
6638
+ lines.push(hostedUrl);
6639
+ } catch (uploadErr) {
6640
+ console.error(
6641
+ `[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
6642
+ );
6643
+ lines.push(
6644
+ "Image generated but upload failed. Try again or use --model dall-e-3."
6645
+ );
6646
+ }
6647
+ } else {
6648
+ lines.push(img.url);
6649
+ }
6650
+ }
6651
+ if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);
6652
+ }
6653
+ lines.push("", `Model: ${imageModel} | Size: ${imageSize}`);
6654
+ responseText = lines.join("\n");
5516
6655
  }
5517
- parsed.messages = messages;
5518
- bodyModified = true;
5519
- console.log(
5520
- `[ClawRouter] Injected session journal (${journalText.length} chars) for session ${sessionId.slice(0, 8)}...`
6656
+ console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
6657
+ }
6658
+ const completionId = `chatcmpl-image-${Date.now()}`;
6659
+ const timestamp = Math.floor(Date.now() / 1e3);
6660
+ if (isStreaming) {
6661
+ res.writeHead(200, {
6662
+ "Content-Type": "text/event-stream",
6663
+ "Cache-Control": "no-cache",
6664
+ Connection: "keep-alive"
6665
+ });
6666
+ res.write(
6667
+ `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 }] })}
6668
+
6669
+ `
6670
+ );
6671
+ res.write(
6672
+ `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
6673
+
6674
+ `
6675
+ );
6676
+ res.write("data: [DONE]\n\n");
6677
+ res.end();
6678
+ } else {
6679
+ res.writeHead(200, { "Content-Type": "application/json" });
6680
+ res.end(
6681
+ JSON.stringify({
6682
+ id: completionId,
6683
+ object: "chat.completion",
6684
+ created: timestamp,
6685
+ model: "clawrouter/image",
6686
+ choices: [
6687
+ {
6688
+ index: 0,
6689
+ message: { role: "assistant", content: responseText },
6690
+ finish_reason: "stop"
6691
+ }
6692
+ ],
6693
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
6694
+ })
6695
+ );
6696
+ }
6697
+ } catch (err) {
6698
+ const errMsg = err instanceof Error ? err.message : String(err);
6699
+ console.error(`[ClawRouter] /imagegen error: ${errMsg}`);
6700
+ if (!res.headersSent) {
6701
+ res.writeHead(500, { "Content-Type": "application/json" });
6702
+ res.end(
6703
+ JSON.stringify({
6704
+ error: { message: `Image generation failed: ${errMsg}`, type: "image_error" }
6705
+ })
5521
6706
  );
5522
6707
  }
5523
6708
  }
6709
+ return;
5524
6710
  }
5525
6711
  if (parsed.stream === true) {
5526
6712
  parsed.stream = false;
@@ -5562,54 +6748,118 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5562
6748
  latencyMs: 0
5563
6749
  });
5564
6750
  } else {
5565
- const sessionId2 = getSessionId(
5566
- req.headers
5567
- );
5568
- const existingSession = sessionId2 ? sessionStore.getSession(sessionId2) : void 0;
6751
+ effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
6752
+ const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
6753
+ const rawPrompt = lastUserMsg?.content;
6754
+ const prompt = typeof rawPrompt === "string" ? rawPrompt : Array.isArray(rawPrompt) ? rawPrompt.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
6755
+ const systemMsg = parsedMessages.find((m) => m.role === "system");
6756
+ const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
6757
+ const tools = parsed.tools;
6758
+ hasTools = Array.isArray(tools) && tools.length > 0;
6759
+ if (hasTools && tools) {
6760
+ console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);
6761
+ }
6762
+ hasVision = parsedMessages.some((m) => {
6763
+ if (Array.isArray(m.content)) {
6764
+ return m.content.some((p) => p.type === "image_url");
6765
+ }
6766
+ return false;
6767
+ });
6768
+ if (hasVision) {
6769
+ console.log(`[ClawRouter] Vision content detected, filtering to vision-capable models`);
6770
+ }
6771
+ routingDecision = route(prompt, systemPrompt, maxTokens, {
6772
+ ...routerOpts,
6773
+ routingProfile: routingProfile ?? void 0
6774
+ });
5569
6775
  if (existingSession) {
5570
- console.log(
5571
- `[ClawRouter] Session ${sessionId2?.slice(0, 8)}... using pinned model: ${existingSession.model}`
5572
- );
5573
- parsed.model = existingSession.model;
5574
- modelId = existingSession.model;
5575
- bodyModified = true;
5576
- sessionStore.touchSession(sessionId2);
5577
- } else {
5578
- const messages = parsed.messages;
5579
- let lastUserMsg;
5580
- if (messages) {
5581
- for (let i = messages.length - 1; i >= 0; i--) {
5582
- if (messages[i].role === "user") {
5583
- lastUserMsg = messages[i];
5584
- break;
5585
- }
6776
+ const tierRank = {
6777
+ SIMPLE: 0,
6778
+ MEDIUM: 1,
6779
+ COMPLEX: 2,
6780
+ REASONING: 3
6781
+ };
6782
+ const existingRank = tierRank[existingSession.tier] ?? 0;
6783
+ const newRank = tierRank[routingDecision.tier] ?? 0;
6784
+ if (newRank > existingRank) {
6785
+ console.log(
6786
+ `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} \u2192 ${routingDecision.tier} (${routingDecision.model})`
6787
+ );
6788
+ parsed.model = routingDecision.model;
6789
+ modelId = routingDecision.model;
6790
+ bodyModified = true;
6791
+ if (effectiveSessionId) {
6792
+ sessionStore.setSession(
6793
+ effectiveSessionId,
6794
+ routingDecision.model,
6795
+ routingDecision.tier
6796
+ );
5586
6797
  }
5587
- }
5588
- const systemMsg = messages?.find((m) => m.role === "system");
5589
- const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
5590
- const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
5591
- const tools = parsed.tools;
5592
- const hasTools = Array.isArray(tools) && tools.length > 0;
5593
- if (hasTools && tools) {
6798
+ } else {
5594
6799
  console.log(
5595
- `[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`
6800
+ `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`
6801
+ );
6802
+ parsed.model = existingSession.model;
6803
+ modelId = existingSession.model;
6804
+ bodyModified = true;
6805
+ sessionStore.touchSession(effectiveSessionId);
6806
+ routingDecision = {
6807
+ ...routingDecision,
6808
+ model: existingSession.model,
6809
+ tier: existingSession.tier
6810
+ };
6811
+ }
6812
+ const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
6813
+ const assistantToolCalls = lastAssistantMsg?.tool_calls;
6814
+ const toolCallNames = Array.isArray(assistantToolCalls) ? assistantToolCalls.map((tc) => tc.function?.name).filter((n) => Boolean(n)) : void 0;
6815
+ const contentHash = hashRequestContent(prompt, toolCallNames);
6816
+ const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
6817
+ if (shouldEscalate) {
6818
+ const activeTierConfigs = (() => {
6819
+ if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
6820
+ return routerOpts.config.agenticTiers;
6821
+ }
6822
+ if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
6823
+ return routerOpts.config.ecoTiers;
6824
+ }
6825
+ if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
6826
+ return routerOpts.config.premiumTiers;
6827
+ }
6828
+ return routerOpts.config.tiers;
6829
+ })();
6830
+ const escalation = sessionStore.escalateSession(
6831
+ effectiveSessionId,
6832
+ activeTierConfigs
5596
6833
  );
6834
+ if (escalation) {
6835
+ console.log(
6836
+ `[ClawRouter] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`
6837
+ );
6838
+ parsed.model = escalation.model;
6839
+ modelId = escalation.model;
6840
+ routingDecision = {
6841
+ ...routingDecision,
6842
+ model: escalation.model,
6843
+ tier: escalation.tier
6844
+ };
6845
+ }
5597
6846
  }
5598
- routingDecision = route(prompt, systemPrompt, maxTokens, {
5599
- ...routerOpts,
5600
- routingProfile: routingProfile ?? void 0
5601
- });
6847
+ } else {
5602
6848
  parsed.model = routingDecision.model;
5603
6849
  modelId = routingDecision.model;
5604
6850
  bodyModified = true;
5605
- if (sessionId2) {
5606
- sessionStore.setSession(sessionId2, routingDecision.model, routingDecision.tier);
6851
+ if (effectiveSessionId) {
6852
+ sessionStore.setSession(
6853
+ effectiveSessionId,
6854
+ routingDecision.model,
6855
+ routingDecision.tier
6856
+ );
5607
6857
  console.log(
5608
- `[ClawRouter] Session ${sessionId2.slice(0, 8)}... pinned to model: ${routingDecision.model}`
6858
+ `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`
5609
6859
  );
5610
6860
  }
5611
- options.onRouted?.(routingDecision);
5612
6861
  }
6862
+ options.onRouted?.(routingDecision);
5613
6863
  }
5614
6864
  }
5615
6865
  if (bodyModified) {
@@ -5702,6 +6952,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5702
6952
  }
5703
6953
  deduplicator.markInflight(dedupKey);
5704
6954
  let estimatedCostMicros;
6955
+ let balanceFallbackNotice;
5705
6956
  const isFreeModel = modelId === FREE_MODEL;
5706
6957
  if (modelId && !options.skipBalanceCheck && !isFreeModel) {
5707
6958
  const estimated = estimateAmount(modelId, body.length, maxTokens);
@@ -5712,12 +6963,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5712
6963
  if (sufficiency.info.isEmpty || !sufficiency.sufficient) {
5713
6964
  const originalModel = modelId;
5714
6965
  console.log(
5715
- `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} ($${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
6966
+ `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
5716
6967
  );
5717
6968
  modelId = FREE_MODEL;
5718
6969
  const parsed = JSON.parse(body.toString());
5719
6970
  parsed.model = FREE_MODEL;
5720
6971
  body = Buffer.from(JSON.stringify(parsed));
6972
+ balanceFallbackNotice = sufficiency.info.isEmpty ? `> **\u26A0\uFE0F Wallet empty** \u2014 using free model. Fund your wallet to use ${originalModel}.
6973
+
6974
+ ` : `> **\u26A0\uFE0F Insufficient balance** (${sufficiency.info.balanceUSD}) \u2014 using free model instead of ${originalModel}.
6975
+
6976
+ `;
5721
6977
  options.onLowBalance?.({
5722
6978
  balanceUSD: sufficiency.info.balanceUSD,
5723
6979
  walletAddress: sufficiency.info.walletAddress
@@ -5781,8 +7037,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5781
7037
  if (routingDecision) {
5782
7038
  const estimatedInputTokens = Math.ceil(body.length / 4);
5783
7039
  const estimatedTotalTokens = estimatedInputTokens + maxTokens;
5784
- const useAgenticTiers = routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers;
5785
- const tierConfigs = useAgenticTiers ? routerOpts.config.agenticTiers : routerOpts.config.tiers;
7040
+ const tierConfigs = (() => {
7041
+ if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
7042
+ return routerOpts.config.agenticTiers;
7043
+ }
7044
+ if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
7045
+ return routerOpts.config.ecoTiers;
7046
+ }
7047
+ if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
7048
+ return routerOpts.config.premiumTiers;
7049
+ }
7050
+ return routerOpts.config.tiers;
7051
+ })();
5786
7052
  const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);
5787
7053
  const contextFiltered = getFallbackChainFiltered(
5788
7054
  routingDecision.tier,
@@ -5796,14 +7062,27 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5796
7062
  `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
5797
7063
  );
5798
7064
  }
5799
- modelsToTry = contextFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
7065
+ const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);
7066
+ const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));
7067
+ if (toolExcluded.length > 0) {
7068
+ console.log(
7069
+ `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
7070
+ );
7071
+ }
7072
+ const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);
7073
+ const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));
7074
+ if (visionExcluded.length > 0) {
7075
+ console.log(
7076
+ `[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`
7077
+ );
7078
+ }
7079
+ modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
5800
7080
  modelsToTry = prioritizeNonRateLimited(modelsToTry);
5801
7081
  } else {
5802
- if (modelId && modelId !== FREE_MODEL) {
5803
- modelsToTry = [modelId, FREE_MODEL];
5804
- } else {
5805
- modelsToTry = modelId ? [modelId] : [];
5806
- }
7082
+ modelsToTry = modelId ? [modelId] : [];
7083
+ }
7084
+ if (!modelsToTry.includes(FREE_MODEL)) {
7085
+ modelsToTry.push(FREE_MODEL);
5807
7086
  }
5808
7087
  let upstream;
5809
7088
  let lastError;
@@ -5837,6 +7116,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5837
7116
  if (result.errorStatus === 429) {
5838
7117
  markRateLimited(tryModel);
5839
7118
  }
7119
+ const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
7120
+ result.errorBody || ""
7121
+ );
7122
+ if (isPaymentErr && tryModel !== FREE_MODEL) {
7123
+ const freeIdx = modelsToTry.indexOf(FREE_MODEL);
7124
+ if (freeIdx > i + 1) {
7125
+ console.log(`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
7126
+ i = freeIdx - 1;
7127
+ continue;
7128
+ }
7129
+ }
5840
7130
  console.log(
5841
7131
  `[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`
5842
7132
  );
@@ -5854,6 +7144,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5854
7144
  clearInterval(heartbeatInterval);
5855
7145
  heartbeatInterval = void 0;
5856
7146
  }
7147
+ if (debugMode && headersSentEarly && routingDecision) {
7148
+ 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}
7149
+
7150
+ `;
7151
+ safeWrite(res, debugComment);
7152
+ }
5857
7153
  if (routingDecision && actualModelUsed !== routingDecision.model) {
5858
7154
  const estimatedInputTokens = Math.ceil(body.length / 4);
5859
7155
  const newCosts = calculateModelCost(
@@ -5872,6 +7168,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5872
7168
  savings: newCosts.savings
5873
7169
  };
5874
7170
  options.onRouted?.(routingDecision);
7171
+ if (effectiveSessionId) {
7172
+ sessionStore.setSession(effectiveSessionId, actualModelUsed, routingDecision.tier);
7173
+ console.log(
7174
+ `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}`
7175
+ );
7176
+ }
5875
7177
  }
5876
7178
  if (!upstream) {
5877
7179
  const rawErrBody = lastError?.body || "All models in fallback chain failed";
@@ -5934,6 +7236,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5934
7236
  const jsonStr = jsonBody.toString();
5935
7237
  try {
5936
7238
  const rsp = JSON.parse(jsonStr);
7239
+ if (rsp.usage && typeof rsp.usage === "object") {
7240
+ const u = rsp.usage;
7241
+ if (typeof u.prompt_tokens === "number") responseInputTokens = u.prompt_tokens;
7242
+ }
5937
7243
  const baseChunk = {
5938
7244
  id: rsp.id ?? `chatcmpl-${Date.now()}`,
5939
7245
  object: "chat.completion.chunk",
@@ -5959,6 +7265,25 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5959
7265
  `;
5960
7266
  safeWrite(res, roleData);
5961
7267
  responseChunks.push(Buffer.from(roleData));
7268
+ if (balanceFallbackNotice) {
7269
+ const noticeChunk = {
7270
+ ...baseChunk,
7271
+ choices: [
7272
+ {
7273
+ index,
7274
+ delta: { content: balanceFallbackNotice },
7275
+ logprobs: null,
7276
+ finish_reason: null
7277
+ }
7278
+ ]
7279
+ };
7280
+ const noticeData = `data: ${JSON.stringify(noticeChunk)}
7281
+
7282
+ `;
7283
+ safeWrite(res, noticeData);
7284
+ responseChunks.push(Buffer.from(noticeData));
7285
+ balanceFallbackNotice = void 0;
7286
+ }
5962
7287
  if (content) {
5963
7288
  const contentChunk = {
5964
7289
  ...baseChunk,
@@ -6033,23 +7358,46 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6033
7358
  });
6034
7359
  responseHeaders["x-context-used-kb"] = String(originalContextSizeKB);
6035
7360
  responseHeaders["x-context-limit-kb"] = String(CONTEXT_LIMIT_KB);
6036
- res.writeHead(upstream.status, responseHeaders);
7361
+ if (debugMode && routingDecision) {
7362
+ responseHeaders["x-clawrouter-profile"] = routingProfile ?? "auto";
7363
+ responseHeaders["x-clawrouter-tier"] = routingDecision.tier;
7364
+ responseHeaders["x-clawrouter-model"] = actualModelUsed;
7365
+ responseHeaders["x-clawrouter-confidence"] = routingDecision.confidence.toFixed(2);
7366
+ responseHeaders["x-clawrouter-reasoning"] = routingDecision.reasoning;
7367
+ if (routingDecision.agenticScore !== void 0) {
7368
+ responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
7369
+ }
7370
+ }
7371
+ const bodyParts = [];
6037
7372
  if (upstream.body) {
6038
7373
  const reader = upstream.body.getReader();
6039
7374
  try {
6040
7375
  while (true) {
6041
7376
  const { done, value } = await reader.read();
6042
7377
  if (done) break;
6043
- const chunk = Buffer.from(value);
6044
- safeWrite(res, chunk);
6045
- responseChunks.push(chunk);
7378
+ bodyParts.push(Buffer.from(value));
6046
7379
  }
6047
7380
  } finally {
6048
7381
  reader.releaseLock();
6049
7382
  }
6050
7383
  }
7384
+ let responseBody = Buffer.concat(bodyParts);
7385
+ if (balanceFallbackNotice && responseBody.length > 0) {
7386
+ try {
7387
+ const parsed = JSON.parse(responseBody.toString());
7388
+ if (parsed.choices?.[0]?.message?.content !== void 0) {
7389
+ parsed.choices[0].message.content = balanceFallbackNotice + parsed.choices[0].message.content;
7390
+ responseBody = Buffer.from(JSON.stringify(parsed));
7391
+ }
7392
+ } catch {
7393
+ }
7394
+ balanceFallbackNotice = void 0;
7395
+ }
7396
+ responseHeaders["content-length"] = String(responseBody.length);
7397
+ res.writeHead(upstream.status, responseHeaders);
7398
+ safeWrite(res, responseBody);
7399
+ responseChunks.push(responseBody);
6051
7400
  res.end();
6052
- const responseBody = Buffer.concat(responseChunks);
6053
7401
  deduplicator.complete(dedupKey, {
6054
7402
  status: upstream.status,
6055
7403
  headers: responseHeaders,
@@ -6072,6 +7420,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6072
7420
  if (rspJson.choices?.[0]?.message?.content) {
6073
7421
  accumulatedContent = rspJson.choices[0].message.content;
6074
7422
  }
7423
+ if (rspJson.usage && typeof rspJson.usage === "object") {
7424
+ if (typeof rspJson.usage.prompt_tokens === "number")
7425
+ responseInputTokens = rspJson.usage.prompt_tokens;
7426
+ }
6075
7427
  } catch {
6076
7428
  }
6077
7429
  }
@@ -6097,7 +7449,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6097
7449
  deduplicator.removeInflight(dedupKey);
6098
7450
  balanceMonitor.invalidate();
6099
7451
  if (err instanceof Error && err.name === "AbortError") {
6100
- throw new Error(`Request timed out after ${timeoutMs}ms`);
7452
+ throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });
6101
7453
  }
6102
7454
  throw err;
6103
7455
  }
@@ -6120,7 +7472,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6120
7472
  cost: costWithBuffer,
6121
7473
  baselineCost: baselineWithBuffer,
6122
7474
  savings: accurateCosts.savings,
6123
- latencyMs: Date.now() - startTime
7475
+ latencyMs: Date.now() - startTime,
7476
+ ...responseInputTokens !== void 0 && { inputTokens: responseInputTokens }
6124
7477
  };
6125
7478
  logUsage(entry).catch(() => {
6126
7479
  });
@@ -6139,6 +7492,103 @@ import {
6139
7492
  import { homedir as homedir5 } from "os";
6140
7493
  import { join as join6 } from "path";
6141
7494
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
7495
+
7496
+ // src/partners/registry.ts
7497
+ var PARTNER_SERVICES = [
7498
+ {
7499
+ id: "x_users_lookup",
7500
+ name: "Twitter/X User Lookup",
7501
+ partner: "AttentionVC",
7502
+ 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).",
7503
+ proxyPath: "/x/users/lookup",
7504
+ method: "POST",
7505
+ params: [
7506
+ {
7507
+ name: "usernames",
7508
+ type: "string[]",
7509
+ description: 'Array of Twitter/X usernames to look up (without @ prefix). Example: ["elonmusk", "naval"]',
7510
+ required: true
7511
+ }
7512
+ ],
7513
+ pricing: {
7514
+ perUnit: "$0.001",
7515
+ unit: "user",
7516
+ minimum: "$0.01 (10 users)",
7517
+ maximum: "$0.10 (100 users)"
7518
+ },
7519
+ example: {
7520
+ input: { usernames: ["elonmusk", "naval", "balaboris"] },
7521
+ description: "Look up 3 Twitter/X user profiles"
7522
+ }
7523
+ }
7524
+ ];
7525
+ function getPartnerService(id) {
7526
+ return PARTNER_SERVICES.find((s) => s.id === id);
7527
+ }
7528
+
7529
+ // src/partners/tools.ts
7530
+ function buildTool(service, proxyBaseUrl) {
7531
+ const properties = {};
7532
+ const required = [];
7533
+ for (const param of service.params) {
7534
+ const prop = {
7535
+ description: param.description
7536
+ };
7537
+ if (param.type === "string[]") {
7538
+ prop.type = "array";
7539
+ prop.items = { type: "string" };
7540
+ } else {
7541
+ prop.type = param.type;
7542
+ }
7543
+ properties[param.name] = prop;
7544
+ if (param.required) {
7545
+ required.push(param.name);
7546
+ }
7547
+ }
7548
+ return {
7549
+ name: `blockrun_${service.id}`,
7550
+ description: [
7551
+ service.description,
7552
+ "",
7553
+ `Partner: ${service.partner}`,
7554
+ `Pricing: ${service.pricing.perUnit} per ${service.pricing.unit} (min: ${service.pricing.minimum}, max: ${service.pricing.maximum})`
7555
+ ].join("\n"),
7556
+ parameters: {
7557
+ type: "object",
7558
+ properties,
7559
+ required
7560
+ },
7561
+ execute: async (_toolCallId, params) => {
7562
+ const url = `${proxyBaseUrl}/v1${service.proxyPath}`;
7563
+ const response = await fetch(url, {
7564
+ method: service.method,
7565
+ headers: { "Content-Type": "application/json" },
7566
+ body: JSON.stringify(params)
7567
+ });
7568
+ if (!response.ok) {
7569
+ const errText = await response.text().catch(() => "");
7570
+ throw new Error(
7571
+ `Partner API error (${response.status}): ${errText || response.statusText}`
7572
+ );
7573
+ }
7574
+ const data = await response.json();
7575
+ return {
7576
+ content: [
7577
+ {
7578
+ type: "text",
7579
+ text: JSON.stringify(data, null, 2)
7580
+ }
7581
+ ],
7582
+ details: data
7583
+ };
7584
+ }
7585
+ };
7586
+ }
7587
+ function buildPartnerTools(proxyBaseUrl) {
7588
+ return PARTNER_SERVICES.map((service) => buildTool(service, proxyBaseUrl));
7589
+ }
7590
+
7591
+ // src/index.ts
6142
7592
  init_solana_balance();
6143
7593
 
6144
7594
  // src/spend-control.ts
@@ -6391,6 +7841,7 @@ function formatDuration(seconds) {
6391
7841
 
6392
7842
  // src/index.ts
6393
7843
  init_wallet();
7844
+ init_solana_sweep();
6394
7845
 
6395
7846
  // src/retry.ts
6396
7847
  var DEFAULT_RETRY_CONFIG = {
@@ -6449,7 +7900,6 @@ function isRetryable(errorOrResponse, config) {
6449
7900
  }
6450
7901
 
6451
7902
  // src/index.ts
6452
- init_partners();
6453
7903
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
6454
7904
  const start = Date.now();
6455
7905
  while (Date.now() - start < timeoutMs) {
@@ -6586,56 +8036,48 @@ function injectModelsConfig(logger) {
6586
8036
  logger.info("Set default model to blockrun/auto (first install)");
6587
8037
  needsWrite = true;
6588
8038
  }
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" }
8039
+ const TOP_MODELS = [
8040
+ "auto",
8041
+ "free",
8042
+ "eco",
8043
+ "premium",
8044
+ "anthropic/claude-sonnet-4.6",
8045
+ "anthropic/claude-opus-4.6",
8046
+ "anthropic/claude-haiku-4.5",
8047
+ "openai/gpt-5.2",
8048
+ "openai/gpt-4o",
8049
+ "openai/o3",
8050
+ "google/gemini-3.1-pro",
8051
+ "google/gemini-3-flash-preview",
8052
+ "deepseek/deepseek-chat",
8053
+ "moonshot/kimi-k2.5",
8054
+ "xai/grok-3",
8055
+ "minimax/minimax-m2.5"
6606
8056
  ];
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) {
8057
+ if (!defaults.models || typeof defaults.models !== "object" || Array.isArray(defaults.models)) {
6617
8058
  defaults.models = {};
6618
8059
  needsWrite = true;
6619
8060
  }
6620
8061
  const allowlist = defaults.models;
6621
- for (const deprecated of DEPRECATED_ALIASES) {
6622
- if (allowlist[deprecated]) {
6623
- delete allowlist[deprecated];
6624
- logger.info(`Removed deprecated model alias: ${deprecated}`);
8062
+ const topSet = new Set(TOP_MODELS.map((id) => `blockrun/${id}`));
8063
+ for (const key of Object.keys(allowlist)) {
8064
+ if (key.startsWith("blockrun/") && !topSet.has(key)) {
8065
+ delete allowlist[key];
6625
8066
  needsWrite = true;
6626
8067
  }
6627
8068
  }
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;
8069
+ let addedCount = 0;
8070
+ for (const id of TOP_MODELS) {
8071
+ const key = `blockrun/${id}`;
8072
+ if (!allowlist[key]) {
8073
+ allowlist[key] = {};
8074
+ addedCount++;
6637
8075
  }
6638
8076
  }
8077
+ if (addedCount > 0) {
8078
+ needsWrite = true;
8079
+ logger.info(`Added ${addedCount} models to allowlist (${TOP_MODELS.length} total)`);
8080
+ }
6639
8081
  if (needsWrite) {
6640
8082
  try {
6641
8083
  const tmpPath = `${configPath}.tmp.${process.pid}`;
@@ -6754,18 +8196,19 @@ async function startProxyInBackground(api) {
6754
8196
  activeProxyHandle = proxy;
6755
8197
  api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
6756
8198
  api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
6757
- const startupMonitor = new BalanceMonitor(wallet.address);
6758
- startupMonitor.checkBalance().then((balance) => {
8199
+ const currentChain = await resolvePaymentChain();
8200
+ const displayAddress = currentChain === "solana" && proxy.solanaAddress ? proxy.solanaAddress : wallet.address;
8201
+ proxy.balanceMonitor.checkBalance().then((balance) => {
6759
8202
  if (balance.isEmpty) {
6760
- api.logger.info(`Wallet: ${wallet.address} | Balance: $0.00`);
8203
+ api.logger.info(`Wallet: ${displayAddress} | Balance: $0.00`);
6761
8204
  api.logger.info(`Using FREE model. Fund wallet for premium models.`);
6762
8205
  } else if (balance.isLow) {
6763
- api.logger.info(`Wallet: ${wallet.address} | Balance: ${balance.balanceUSD} (low)`);
8206
+ api.logger.info(`Wallet: ${displayAddress} | Balance: ${balance.balanceUSD} (low)`);
6764
8207
  } else {
6765
- api.logger.info(`Wallet: ${wallet.address} | Balance: ${balance.balanceUSD}`);
8208
+ api.logger.info(`Wallet: ${displayAddress} | Balance: ${balance.balanceUSD}`);
6766
8209
  }
6767
8210
  }).catch(() => {
6768
- api.logger.info(`Wallet: ${wallet.address} | Balance: (checking...)`);
8211
+ api.logger.info(`Wallet: ${displayAddress} | Balance: (checking...)`);
6769
8212
  });
6770
8213
  }
6771
8214
  async function createStatsCommand() {
@@ -6839,8 +8282,8 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
6839
8282
  hasMnemonic = true;
6840
8283
  const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
6841
8284
  const solKeyBytes = deriveSolanaKeyBytes2(mnemonic);
6842
- const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
6843
- const signer = await createKeyPairSignerFromPrivateKeyBytes(solKeyBytes);
8285
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
8286
+ const signer = await createKeyPairSignerFromPrivateKeyBytes2(solKeyBytes);
6844
8287
  lines.push(
6845
8288
  "",
6846
8289
  "**Solana:**",
@@ -6881,8 +8324,8 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
6881
8324
  await savePaymentChain("solana");
6882
8325
  const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
6883
8326
  const solKeyBytes = deriveSolanaKeyBytes2(existingMnemonic);
6884
- const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
6885
- const signer2 = await createKeyPairSignerFromPrivateKeyBytes2(solKeyBytes);
8327
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes3 } = await import("@solana/kit");
8328
+ const signer2 = await createKeyPairSignerFromPrivateKeyBytes3(solKeyBytes);
6886
8329
  solanaAddr = signer2.address;
6887
8330
  return {
6888
8331
  text: [
@@ -6896,8 +8339,8 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
6896
8339
  }
6897
8340
  const { solanaPrivateKeyBytes } = await setupSolana();
6898
8341
  await savePaymentChain("solana");
6899
- const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
6900
- const signer = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
8342
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
8343
+ const signer = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
6901
8344
  return {
6902
8345
  text: [
6903
8346
  "**Solana Wallet Set Up**",
@@ -6918,6 +8361,88 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
6918
8361
  };
6919
8362
  }
6920
8363
  }
8364
+ if (subcommand === "migrate-solana") {
8365
+ try {
8366
+ if (!existsSync2(MNEMONIC_FILE)) {
8367
+ return {
8368
+ text: "No mnemonic file found. Solana wallet not set up \u2014 nothing to migrate.",
8369
+ isError: true
8370
+ };
8371
+ }
8372
+ const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
8373
+ if (!mnemonic) {
8374
+ return { text: "Mnemonic file is empty.", isError: true };
8375
+ }
8376
+ const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2, deriveSolanaKeyBytesLegacy: deriveSolanaKeyBytesLegacy2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
8377
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
8378
+ const legacyKeyBytes = deriveSolanaKeyBytesLegacy2(mnemonic);
8379
+ const newKeyBytes = deriveSolanaKeyBytes2(mnemonic);
8380
+ const [oldSigner, newSigner] = await Promise.all([
8381
+ createKeyPairSignerFromPrivateKeyBytes2(legacyKeyBytes),
8382
+ createKeyPairSignerFromPrivateKeyBytes2(newKeyBytes)
8383
+ ]);
8384
+ if (oldSigner.address === newSigner.address) {
8385
+ return { text: "Legacy and new Solana addresses are the same. No migration needed." };
8386
+ }
8387
+ let oldUsdcText = "unknown";
8388
+ try {
8389
+ const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
8390
+ const monitor = new SolanaBalanceMonitor2(oldSigner.address);
8391
+ const balance = await monitor.checkBalance();
8392
+ oldUsdcText = balance.balanceUSD;
8393
+ if (balance.isEmpty) {
8394
+ return {
8395
+ text: [
8396
+ "**Solana Migration Status**",
8397
+ "",
8398
+ `Old wallet (secp256k1): \`${oldSigner.address}\``,
8399
+ ` USDC: $0.00`,
8400
+ "",
8401
+ `New wallet (SLIP-10): \`${newSigner.address}\``,
8402
+ "",
8403
+ "No USDC in old wallet. Nothing to sweep.",
8404
+ "Your new SLIP-10 address is Phantom/Solflare compatible."
8405
+ ].join("\n")
8406
+ };
8407
+ }
8408
+ } catch {
8409
+ }
8410
+ const { sweepSolanaWallet: sweepSolanaWallet2 } = await Promise.resolve().then(() => (init_solana_sweep(), solana_sweep_exports));
8411
+ const result = await sweepSolanaWallet2(legacyKeyBytes, newSigner.address);
8412
+ if ("error" in result) {
8413
+ return {
8414
+ text: [
8415
+ "**Solana Migration Failed**",
8416
+ "",
8417
+ `Old wallet: \`${result.oldAddress}\` (USDC: ${oldUsdcText})`,
8418
+ `New wallet: \`${result.newAddress || newSigner.address}\``,
8419
+ "",
8420
+ `Error: ${result.error}`
8421
+ ].join("\n"),
8422
+ isError: true
8423
+ };
8424
+ }
8425
+ return {
8426
+ text: [
8427
+ "**Solana Migration Complete**",
8428
+ "",
8429
+ `Swept **${result.transferred}** USDC from old to new wallet.`,
8430
+ "",
8431
+ `Old wallet: \`${result.oldAddress}\``,
8432
+ `New wallet: \`${result.newAddress}\``,
8433
+ `TX: https://solscan.io/tx/${result.txSignature}`,
8434
+ "",
8435
+ "Your new SLIP-10 address is Phantom/Solflare compatible.",
8436
+ "You can recover it from your 24-word mnemonic in any standard wallet."
8437
+ ].join("\n")
8438
+ };
8439
+ } catch (err) {
8440
+ return {
8441
+ text: `Migration failed: ${err instanceof Error ? err.message : String(err)}`,
8442
+ isError: true
8443
+ };
8444
+ }
8445
+ }
6921
8446
  if (subcommand === "base") {
6922
8447
  try {
6923
8448
  await savePaymentChain("base");
@@ -6931,7 +8456,7 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
6931
8456
  };
6932
8457
  }
6933
8458
  }
6934
- let evmBalanceText = "Balance: (checking...)";
8459
+ let evmBalanceText;
6935
8460
  try {
6936
8461
  const monitor = new BalanceMonitor(address);
6937
8462
  const balance = await monitor.checkBalance();
@@ -6946,8 +8471,8 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
6946
8471
  const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
6947
8472
  if (mnemonic) {
6948
8473
  const solKeyBytes = deriveSolanaKeyBytes2(mnemonic);
6949
- const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
6950
- const signer = await createKeyPairSignerFromPrivateKeyBytes(solKeyBytes);
8474
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
8475
+ const signer = await createKeyPairSignerFromPrivateKeyBytes2(solKeyBytes);
6951
8476
  const solAddr = signer.address;
6952
8477
  let solBalanceText = "Balance: (checking...)";
6953
8478
  try {
@@ -6989,7 +8514,8 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
6989
8514
  "\u2022 `/wallet export` - Export private key for backup",
6990
8515
  !solanaSection ? "\u2022 `/wallet solana` - Enable Solana payments" : "",
6991
8516
  solanaSection ? "\u2022 `/wallet base` - Switch to Base (EVM)" : "",
6992
- solanaSection ? "\u2022 `/wallet solana` - Switch to Solana" : ""
8517
+ solanaSection ? "\u2022 `/wallet solana` - Switch to Solana" : "",
8518
+ solanaSection ? "\u2022 `/wallet migrate-solana` - Sweep funds from old Solana wallet" : ""
6993
8519
  ].filter(Boolean).join("\n")
6994
8520
  };
6995
8521
  }
@@ -7000,7 +8526,7 @@ var plugin = {
7000
8526
  name: "ClawRouter",
7001
8527
  description: "Smart LLM router \u2014 30+ models, x402 micropayments, 78% cost savings",
7002
8528
  version: VERSION,
7003
- async register(api) {
8529
+ register(api) {
7004
8530
  const isDisabled = process["env"].CLAWROUTER_DISABLED === "true" || process["env"].CLAWROUTER_DISABLED === "1";
7005
8531
  if (isDisabled) {
7006
8532
  api.logger.info("ClawRouter disabled (CLAWROUTER_DISABLED=true). Using default routing.");
@@ -7029,14 +8555,15 @@ var plugin = {
7029
8555
  };
7030
8556
  api.logger.info("BlockRun provider registered (30+ models via x402)");
7031
8557
  try {
7032
- const { buildPartnerTools: buildPartnerTools2, PARTNER_SERVICES: PARTNER_SERVICES2 } = await Promise.resolve().then(() => (init_partners(), partners_exports));
7033
8558
  const proxyBaseUrl = `http://127.0.0.1:${runtimePort}`;
7034
- const partnerTools = buildPartnerTools2(proxyBaseUrl);
8559
+ const partnerTools = buildPartnerTools(proxyBaseUrl);
7035
8560
  for (const tool of partnerTools) {
7036
8561
  api.registerTool(tool);
7037
8562
  }
7038
8563
  if (partnerTools.length > 0) {
7039
- api.logger.info(`Registered ${partnerTools.length} partner tool(s): ${partnerTools.map((t) => t.name).join(", ")}`);
8564
+ api.logger.info(
8565
+ `Registered ${partnerTools.length} partner tool(s): ${partnerTools.map((t) => t.name).join(", ")}`
8566
+ );
7040
8567
  }
7041
8568
  api.registerCommand({
7042
8569
  name: "partners",
@@ -7044,19 +8571,20 @@ var plugin = {
7044
8571
  acceptsArgs: false,
7045
8572
  requireAuth: false,
7046
8573
  handler: async () => {
7047
- if (PARTNER_SERVICES2.length === 0) {
8574
+ if (PARTNER_SERVICES.length === 0) {
7048
8575
  return { text: "No partner APIs available." };
7049
8576
  }
7050
- const lines = [
7051
- "**Partner APIs** (paid via your ClawRouter wallet)",
7052
- ""
7053
- ];
7054
- for (const svc of PARTNER_SERVICES2) {
8577
+ const lines = ["**Partner APIs** (paid via your ClawRouter wallet)", ""];
8578
+ for (const svc of PARTNER_SERVICES) {
7055
8579
  lines.push(`**${svc.name}** (${svc.partner})`);
7056
8580
  lines.push(` ${svc.description}`);
7057
8581
  lines.push(` Tool: \`${`blockrun_${svc.id}`}\``);
7058
- lines.push(` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`);
7059
- lines.push(` **How to use:** Ask "Look up Twitter user @elonmusk" or "Get info on these X accounts: @naval, @balajis"`);
8582
+ lines.push(
8583
+ ` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`
8584
+ );
8585
+ lines.push(
8586
+ ` **How to use:** Ask "Look up Twitter user @elonmusk" or "Get info on these X accounts: @naval, @balajis"`
8587
+ );
7060
8588
  lines.push("");
7061
8589
  }
7062
8590
  return { text: lines.join("\n") };
@@ -7163,6 +8691,7 @@ export {
7163
8691
  deriveAllKeys,
7164
8692
  deriveEvmKey,
7165
8693
  deriveSolanaKeyBytes,
8694
+ deriveSolanaKeyBytesLegacy,
7166
8695
  fetchWithRetry,
7167
8696
  formatDuration,
7168
8697
  formatStatsAscii,
@@ -7175,6 +8704,7 @@ export {
7175
8704
  getProxyPort,
7176
8705
  getSessionId,
7177
8706
  getStats,
8707
+ hashRequestContent,
7178
8708
  isAgenticModel,
7179
8709
  isBalanceError,
7180
8710
  isEmptyWalletError,
@@ -7189,6 +8719,12 @@ export {
7189
8719
  route,
7190
8720
  savePaymentChain,
7191
8721
  setupSolana,
7192
- startProxy
8722
+ startProxy,
8723
+ sweepSolanaWallet
7193
8724
  };
8725
+ /*! Bundled license information:
8726
+
8727
+ @noble/hashes/esm/utils.js:
8728
+ (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
8729
+ */
7194
8730
  //# sourceMappingURL=index.js.map