@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/README.md +6 -6
- package/dist/cli.js +1540 -192
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +131 -32
- package/dist/index.js +1929 -393
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,13 +11,13 @@ import { x402Client } from "@x402/fetch";
|
|
|
11
11
|
// src/payment-preauth.ts
|
|
12
12
|
import { x402HTTPClient } from "@x402/fetch";
|
|
13
13
|
var DEFAULT_TTL_MS = 36e5;
|
|
14
|
-
function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS) {
|
|
14
|
+
function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, options) {
|
|
15
15
|
const httpClient = new x402HTTPClient(client);
|
|
16
16
|
const cache = /* @__PURE__ */ new Map();
|
|
17
17
|
return async (input, init) => {
|
|
18
18
|
const request = new Request(input, init);
|
|
19
19
|
const urlPath = new URL(request.url).pathname;
|
|
20
|
-
const cached = cache.get(urlPath);
|
|
20
|
+
const cached = !options?.skipPreAuth ? cache.get(urlPath) : void 0;
|
|
21
21
|
if (cached && Date.now() - cached.cachedAt < ttlMs) {
|
|
22
22
|
try {
|
|
23
23
|
const payload2 = await client.createPaymentPayload(cached.paymentRequired);
|
|
@@ -53,7 +53,8 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS) {
|
|
|
53
53
|
cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });
|
|
54
54
|
} catch (error) {
|
|
55
55
|
throw new Error(
|
|
56
|
-
`Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}
|
|
56
|
+
`Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
57
|
+
{ cause: error }
|
|
57
58
|
);
|
|
58
59
|
}
|
|
59
60
|
const payload = await client.createPaymentPayload(paymentRequired);
|
|
@@ -157,20 +158,18 @@ function scoreAgenticTask(text, keywords) {
|
|
|
157
158
|
};
|
|
158
159
|
}
|
|
159
160
|
function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
160
|
-
const text = `${systemPrompt ?? ""} ${prompt}`.toLowerCase();
|
|
161
161
|
const userText = prompt.toLowerCase();
|
|
162
162
|
const dimensions = [
|
|
163
|
-
//
|
|
163
|
+
// Token count uses total estimated tokens (system + user) — context size matters for model selection
|
|
164
164
|
scoreTokenCount(estimatedTokens, config.tokenCountThresholds),
|
|
165
165
|
scoreKeywordMatch(
|
|
166
|
-
|
|
166
|
+
userText,
|
|
167
167
|
config.codeKeywords,
|
|
168
168
|
"codePresence",
|
|
169
169
|
"code",
|
|
170
170
|
{ low: 1, high: 2 },
|
|
171
171
|
{ none: 0, low: 0.5, high: 1 }
|
|
172
172
|
),
|
|
173
|
-
// Reasoning markers use USER prompt only — system prompt "step by step" shouldn't trigger reasoning
|
|
174
173
|
scoreKeywordMatch(
|
|
175
174
|
userText,
|
|
176
175
|
config.reasoningKeywords,
|
|
@@ -180,7 +179,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
180
179
|
{ none: 0, low: 0.7, high: 1 }
|
|
181
180
|
),
|
|
182
181
|
scoreKeywordMatch(
|
|
183
|
-
|
|
182
|
+
userText,
|
|
184
183
|
config.technicalKeywords,
|
|
185
184
|
"technicalTerms",
|
|
186
185
|
"technical",
|
|
@@ -188,7 +187,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
188
187
|
{ none: 0, low: 0.5, high: 1 }
|
|
189
188
|
),
|
|
190
189
|
scoreKeywordMatch(
|
|
191
|
-
|
|
190
|
+
userText,
|
|
192
191
|
config.creativeKeywords,
|
|
193
192
|
"creativeMarkers",
|
|
194
193
|
"creative",
|
|
@@ -196,18 +195,18 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
196
195
|
{ none: 0, low: 0.5, high: 0.7 }
|
|
197
196
|
),
|
|
198
197
|
scoreKeywordMatch(
|
|
199
|
-
|
|
198
|
+
userText,
|
|
200
199
|
config.simpleKeywords,
|
|
201
200
|
"simpleIndicators",
|
|
202
201
|
"simple",
|
|
203
202
|
{ low: 1, high: 2 },
|
|
204
203
|
{ none: 0, low: -1, high: -1 }
|
|
205
204
|
),
|
|
206
|
-
scoreMultiStep(
|
|
205
|
+
scoreMultiStep(userText),
|
|
207
206
|
scoreQuestionComplexity(prompt),
|
|
208
207
|
// 6 new dimensions
|
|
209
208
|
scoreKeywordMatch(
|
|
210
|
-
|
|
209
|
+
userText,
|
|
211
210
|
config.imperativeVerbs,
|
|
212
211
|
"imperativeVerbs",
|
|
213
212
|
"imperative",
|
|
@@ -215,7 +214,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
215
214
|
{ none: 0, low: 0.3, high: 0.5 }
|
|
216
215
|
),
|
|
217
216
|
scoreKeywordMatch(
|
|
218
|
-
|
|
217
|
+
userText,
|
|
219
218
|
config.constraintIndicators,
|
|
220
219
|
"constraintCount",
|
|
221
220
|
"constraints",
|
|
@@ -223,7 +222,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
223
222
|
{ none: 0, low: 0.3, high: 0.7 }
|
|
224
223
|
),
|
|
225
224
|
scoreKeywordMatch(
|
|
226
|
-
|
|
225
|
+
userText,
|
|
227
226
|
config.outputFormatKeywords,
|
|
228
227
|
"outputFormat",
|
|
229
228
|
"format",
|
|
@@ -231,7 +230,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
231
230
|
{ none: 0, low: 0.4, high: 0.7 }
|
|
232
231
|
),
|
|
233
232
|
scoreKeywordMatch(
|
|
234
|
-
|
|
233
|
+
userText,
|
|
235
234
|
config.referenceKeywords,
|
|
236
235
|
"referenceComplexity",
|
|
237
236
|
"references",
|
|
@@ -239,7 +238,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
239
238
|
{ none: 0, low: 0.3, high: 0.5 }
|
|
240
239
|
),
|
|
241
240
|
scoreKeywordMatch(
|
|
242
|
-
|
|
241
|
+
userText,
|
|
243
242
|
config.negationKeywords,
|
|
244
243
|
"negationComplexity",
|
|
245
244
|
"negation",
|
|
@@ -247,7 +246,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
247
246
|
{ none: 0, low: 0.3, high: 0.5 }
|
|
248
247
|
),
|
|
249
248
|
scoreKeywordMatch(
|
|
250
|
-
|
|
249
|
+
userText,
|
|
251
250
|
config.domainSpecificKeywords,
|
|
252
251
|
"domainSpecificity",
|
|
253
252
|
"domain-specific",
|
|
@@ -279,7 +278,8 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
279
278
|
tier: "REASONING",
|
|
280
279
|
confidence: Math.max(confidence2, 0.85),
|
|
281
280
|
signals,
|
|
282
|
-
agenticScore
|
|
281
|
+
agenticScore,
|
|
282
|
+
dimensions
|
|
283
283
|
};
|
|
284
284
|
}
|
|
285
285
|
const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;
|
|
@@ -303,9 +303,9 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
303
303
|
}
|
|
304
304
|
const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);
|
|
305
305
|
if (confidence < config.confidenceThreshold) {
|
|
306
|
-
return { score: weightedScore, tier: null, confidence, signals, agenticScore };
|
|
306
|
+
return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };
|
|
307
307
|
}
|
|
308
|
-
return { score: weightedScore, tier, confidence, signals, agenticScore };
|
|
308
|
+
return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };
|
|
309
309
|
}
|
|
310
310
|
function calibrateConfidence(distance, steepness) {
|
|
311
311
|
return 1 / (1 + Math.exp(-steepness * distance));
|
|
@@ -313,7 +313,9 @@ function calibrateConfidence(distance, steepness) {
|
|
|
313
313
|
|
|
314
314
|
// src/router/selector.ts
|
|
315
315
|
var BASELINE_MODEL_ID = "anthropic/claude-opus-4.6";
|
|
316
|
-
|
|
316
|
+
var BASELINE_INPUT_PRICE = 5;
|
|
317
|
+
var BASELINE_OUTPUT_PRICE = 25;
|
|
318
|
+
function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) {
|
|
317
319
|
const tierConfig = tierConfigs[tier];
|
|
318
320
|
const model = tierConfig.primary;
|
|
319
321
|
const pricing = modelPricing.get(model);
|
|
@@ -323,8 +325,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
|
|
|
323
325
|
const outputCost = maxOutputTokens / 1e6 * outputPrice;
|
|
324
326
|
const costEstimate = inputCost + outputCost;
|
|
325
327
|
const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
|
|
326
|
-
const opusInputPrice = opusPricing?.inputPrice ??
|
|
327
|
-
const opusOutputPrice = opusPricing?.outputPrice ??
|
|
328
|
+
const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
|
|
329
|
+
const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
|
|
328
330
|
const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
|
|
329
331
|
const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
|
|
330
332
|
const baselineCost = baselineInput + baselineOutput;
|
|
@@ -337,7 +339,8 @@ function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPric
|
|
|
337
339
|
reasoning,
|
|
338
340
|
costEstimate,
|
|
339
341
|
baselineCost,
|
|
340
|
-
savings
|
|
342
|
+
savings,
|
|
343
|
+
...agenticScore !== void 0 && { agenticScore }
|
|
341
344
|
};
|
|
342
345
|
}
|
|
343
346
|
function getFallbackChain(tier, tierConfigs) {
|
|
@@ -352,14 +355,24 @@ function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutput
|
|
|
352
355
|
const outputCost = maxOutputTokens / 1e6 * outputPrice;
|
|
353
356
|
const costEstimate = inputCost + outputCost;
|
|
354
357
|
const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
|
|
355
|
-
const opusInputPrice = opusPricing?.inputPrice ??
|
|
356
|
-
const opusOutputPrice = opusPricing?.outputPrice ??
|
|
358
|
+
const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
|
|
359
|
+
const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
|
|
357
360
|
const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice;
|
|
358
361
|
const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice;
|
|
359
362
|
const baselineCost = baselineInput + baselineOutput;
|
|
360
363
|
const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0;
|
|
361
364
|
return { costEstimate, baselineCost, savings };
|
|
362
365
|
}
|
|
366
|
+
function filterByToolCalling(models, hasTools, supportsToolCalling2) {
|
|
367
|
+
if (!hasTools) return models;
|
|
368
|
+
const filtered = models.filter(supportsToolCalling2);
|
|
369
|
+
return filtered.length > 0 ? filtered : models;
|
|
370
|
+
}
|
|
371
|
+
function filterByVision(models, hasVision, supportsVision2) {
|
|
372
|
+
if (!hasVision) return models;
|
|
373
|
+
const filtered = models.filter(supportsVision2);
|
|
374
|
+
return filtered.length > 0 ? filtered : models;
|
|
375
|
+
}
|
|
363
376
|
function getFallbackChainFiltered(tier, tierConfigs, estimatedTotalTokens, getContextWindow) {
|
|
364
377
|
const fullChain = getFallbackChain(tier, tierConfigs);
|
|
365
378
|
const filtered = fullChain.filter((modelId) => {
|
|
@@ -1415,18 +1428,18 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1415
1428
|
]
|
|
1416
1429
|
},
|
|
1417
1430
|
MEDIUM: {
|
|
1418
|
-
primary: "
|
|
1419
|
-
//
|
|
1431
|
+
primary: "moonshot/kimi-k2.5",
|
|
1432
|
+
// $0.50/$2.40 - strong tool use, proper function call format
|
|
1420
1433
|
fallback: [
|
|
1434
|
+
"deepseek/deepseek-chat",
|
|
1421
1435
|
"google/gemini-2.5-flash-lite",
|
|
1422
1436
|
// 1M context, ultra cheap ($0.10/$0.40)
|
|
1423
|
-
"deepseek/deepseek-chat",
|
|
1424
1437
|
"xai/grok-4-1-fast-non-reasoning"
|
|
1425
1438
|
// Upgraded Grok 4.1
|
|
1426
1439
|
]
|
|
1427
1440
|
},
|
|
1428
1441
|
COMPLEX: {
|
|
1429
|
-
primary: "google/gemini-3.1-pro
|
|
1442
|
+
primary: "google/gemini-3.1-pro",
|
|
1430
1443
|
// Newest Gemini 3.1 - upgraded from 3.0
|
|
1431
1444
|
fallback: [
|
|
1432
1445
|
"google/gemini-2.5-flash-lite",
|
|
@@ -1486,7 +1499,7 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1486
1499
|
fallback: [
|
|
1487
1500
|
"anthropic/claude-haiku-4.5",
|
|
1488
1501
|
"google/gemini-2.5-flash-lite",
|
|
1489
|
-
"
|
|
1502
|
+
"deepseek/deepseek-chat"
|
|
1490
1503
|
]
|
|
1491
1504
|
},
|
|
1492
1505
|
MEDIUM: {
|
|
@@ -1506,7 +1519,7 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1506
1519
|
"openai/gpt-5.2-codex",
|
|
1507
1520
|
"anthropic/claude-opus-4.6",
|
|
1508
1521
|
"anthropic/claude-sonnet-4.6",
|
|
1509
|
-
"google/gemini-3.1-pro
|
|
1522
|
+
"google/gemini-3.1-pro",
|
|
1510
1523
|
// Newest Gemini
|
|
1511
1524
|
"google/gemini-3-pro-preview",
|
|
1512
1525
|
"moonshot/kimi-k2.5"
|
|
@@ -1537,9 +1550,13 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1537
1550
|
]
|
|
1538
1551
|
},
|
|
1539
1552
|
MEDIUM: {
|
|
1540
|
-
primary: "
|
|
1541
|
-
//
|
|
1542
|
-
fallback: [
|
|
1553
|
+
primary: "moonshot/kimi-k2.5",
|
|
1554
|
+
// $0.50/$2.40 - strong tool use, handles function calls correctly
|
|
1555
|
+
fallback: [
|
|
1556
|
+
"anthropic/claude-haiku-4.5",
|
|
1557
|
+
"deepseek/deepseek-chat",
|
|
1558
|
+
"xai/grok-4-1-fast-non-reasoning"
|
|
1559
|
+
]
|
|
1543
1560
|
},
|
|
1544
1561
|
COMPLEX: {
|
|
1545
1562
|
primary: "anthropic/claude-sonnet-4.6",
|
|
@@ -1547,7 +1564,7 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
1547
1564
|
"anthropic/claude-opus-4.6",
|
|
1548
1565
|
// Latest Opus - best agentic
|
|
1549
1566
|
"openai/gpt-5.2",
|
|
1550
|
-
"google/gemini-3.1-pro
|
|
1567
|
+
"google/gemini-3.1-pro",
|
|
1551
1568
|
// Newest Gemini
|
|
1552
1569
|
"google/gemini-3-pro-preview",
|
|
1553
1570
|
"xai/grok-4-0709"
|
|
@@ -1579,7 +1596,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
1579
1596
|
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
1580
1597
|
const { routingProfile } = options;
|
|
1581
1598
|
let tierConfigs;
|
|
1582
|
-
let profileSuffix
|
|
1599
|
+
let profileSuffix;
|
|
1583
1600
|
if (routingProfile === "eco" && config.ecoTiers) {
|
|
1584
1601
|
tierConfigs = config.ecoTiers;
|
|
1585
1602
|
profileSuffix = " | eco";
|
|
@@ -1594,6 +1611,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
1594
1611
|
tierConfigs = useAgenticTiers ? config.agenticTiers : config.tiers;
|
|
1595
1612
|
profileSuffix = useAgenticTiers ? " | agentic" : "";
|
|
1596
1613
|
}
|
|
1614
|
+
const agenticScoreValue = ruleResult.agenticScore;
|
|
1597
1615
|
if (estimatedTokens > config.overrides.maxTokensForceComplex) {
|
|
1598
1616
|
return selectModel(
|
|
1599
1617
|
"COMPLEX",
|
|
@@ -1604,7 +1622,8 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
1604
1622
|
modelPricing,
|
|
1605
1623
|
estimatedTokens,
|
|
1606
1624
|
maxOutputTokens,
|
|
1607
|
-
routingProfile
|
|
1625
|
+
routingProfile,
|
|
1626
|
+
agenticScoreValue
|
|
1608
1627
|
);
|
|
1609
1628
|
}
|
|
1610
1629
|
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
@@ -1638,7 +1657,8 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
1638
1657
|
modelPricing,
|
|
1639
1658
|
estimatedTokens,
|
|
1640
1659
|
maxOutputTokens,
|
|
1641
|
-
routingProfile
|
|
1660
|
+
routingProfile,
|
|
1661
|
+
agenticScoreValue
|
|
1642
1662
|
);
|
|
1643
1663
|
}
|
|
1644
1664
|
|
|
@@ -1686,6 +1706,8 @@ var MODEL_ALIASES = {
|
|
|
1686
1706
|
// Google
|
|
1687
1707
|
gemini: "google/gemini-2.5-pro",
|
|
1688
1708
|
flash: "google/gemini-2.5-flash",
|
|
1709
|
+
"gemini-3.1-pro-preview": "google/gemini-3.1-pro",
|
|
1710
|
+
"google/gemini-3.1-pro-preview": "google/gemini-3.1-pro",
|
|
1689
1711
|
// xAI
|
|
1690
1712
|
grok: "xai/grok-3",
|
|
1691
1713
|
"grok-fast": "xai/grok-4-fast-reasoning",
|
|
@@ -1759,7 +1781,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1759
1781
|
maxOutput: 128e3,
|
|
1760
1782
|
reasoning: true,
|
|
1761
1783
|
vision: true,
|
|
1762
|
-
agentic: true
|
|
1784
|
+
agentic: true,
|
|
1785
|
+
toolCalling: true
|
|
1763
1786
|
},
|
|
1764
1787
|
{
|
|
1765
1788
|
id: "openai/gpt-5-mini",
|
|
@@ -1768,7 +1791,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1768
1791
|
inputPrice: 0.25,
|
|
1769
1792
|
outputPrice: 2,
|
|
1770
1793
|
contextWindow: 2e5,
|
|
1771
|
-
maxOutput: 65536
|
|
1794
|
+
maxOutput: 65536,
|
|
1795
|
+
toolCalling: true
|
|
1772
1796
|
},
|
|
1773
1797
|
{
|
|
1774
1798
|
id: "openai/gpt-5-nano",
|
|
@@ -1777,7 +1801,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1777
1801
|
inputPrice: 0.05,
|
|
1778
1802
|
outputPrice: 0.4,
|
|
1779
1803
|
contextWindow: 128e3,
|
|
1780
|
-
maxOutput: 32768
|
|
1804
|
+
maxOutput: 32768,
|
|
1805
|
+
toolCalling: true
|
|
1781
1806
|
},
|
|
1782
1807
|
{
|
|
1783
1808
|
id: "openai/gpt-5.2-pro",
|
|
@@ -1787,7 +1812,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1787
1812
|
outputPrice: 168,
|
|
1788
1813
|
contextWindow: 4e5,
|
|
1789
1814
|
maxOutput: 128e3,
|
|
1790
|
-
reasoning: true
|
|
1815
|
+
reasoning: true,
|
|
1816
|
+
toolCalling: true
|
|
1791
1817
|
},
|
|
1792
1818
|
// OpenAI Codex Family
|
|
1793
1819
|
{
|
|
@@ -1798,7 +1824,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1798
1824
|
outputPrice: 14,
|
|
1799
1825
|
contextWindow: 128e3,
|
|
1800
1826
|
maxOutput: 32e3,
|
|
1801
|
-
agentic: true
|
|
1827
|
+
agentic: true,
|
|
1828
|
+
toolCalling: true
|
|
1802
1829
|
},
|
|
1803
1830
|
// OpenAI GPT-4 Family
|
|
1804
1831
|
{
|
|
@@ -1809,7 +1836,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1809
1836
|
outputPrice: 8,
|
|
1810
1837
|
contextWindow: 128e3,
|
|
1811
1838
|
maxOutput: 16384,
|
|
1812
|
-
vision: true
|
|
1839
|
+
vision: true,
|
|
1840
|
+
toolCalling: true
|
|
1813
1841
|
},
|
|
1814
1842
|
{
|
|
1815
1843
|
id: "openai/gpt-4.1-mini",
|
|
@@ -1818,7 +1846,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1818
1846
|
inputPrice: 0.4,
|
|
1819
1847
|
outputPrice: 1.6,
|
|
1820
1848
|
contextWindow: 128e3,
|
|
1821
|
-
maxOutput: 16384
|
|
1849
|
+
maxOutput: 16384,
|
|
1850
|
+
toolCalling: true
|
|
1822
1851
|
},
|
|
1823
1852
|
{
|
|
1824
1853
|
id: "openai/gpt-4.1-nano",
|
|
@@ -1827,7 +1856,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1827
1856
|
inputPrice: 0.1,
|
|
1828
1857
|
outputPrice: 0.4,
|
|
1829
1858
|
contextWindow: 128e3,
|
|
1830
|
-
maxOutput: 16384
|
|
1859
|
+
maxOutput: 16384,
|
|
1860
|
+
toolCalling: true
|
|
1831
1861
|
},
|
|
1832
1862
|
{
|
|
1833
1863
|
id: "openai/gpt-4o",
|
|
@@ -1838,7 +1868,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1838
1868
|
contextWindow: 128e3,
|
|
1839
1869
|
maxOutput: 16384,
|
|
1840
1870
|
vision: true,
|
|
1841
|
-
agentic: true
|
|
1871
|
+
agentic: true,
|
|
1872
|
+
toolCalling: true
|
|
1842
1873
|
},
|
|
1843
1874
|
{
|
|
1844
1875
|
id: "openai/gpt-4o-mini",
|
|
@@ -1847,7 +1878,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1847
1878
|
inputPrice: 0.15,
|
|
1848
1879
|
outputPrice: 0.6,
|
|
1849
1880
|
contextWindow: 128e3,
|
|
1850
|
-
maxOutput: 16384
|
|
1881
|
+
maxOutput: 16384,
|
|
1882
|
+
toolCalling: true
|
|
1851
1883
|
},
|
|
1852
1884
|
// OpenAI O-series (Reasoning)
|
|
1853
1885
|
{
|
|
@@ -1858,7 +1890,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1858
1890
|
outputPrice: 60,
|
|
1859
1891
|
contextWindow: 2e5,
|
|
1860
1892
|
maxOutput: 1e5,
|
|
1861
|
-
reasoning: true
|
|
1893
|
+
reasoning: true,
|
|
1894
|
+
toolCalling: true
|
|
1862
1895
|
},
|
|
1863
1896
|
{
|
|
1864
1897
|
id: "openai/o1-mini",
|
|
@@ -1868,7 +1901,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1868
1901
|
outputPrice: 4.4,
|
|
1869
1902
|
contextWindow: 128e3,
|
|
1870
1903
|
maxOutput: 65536,
|
|
1871
|
-
reasoning: true
|
|
1904
|
+
reasoning: true,
|
|
1905
|
+
toolCalling: true
|
|
1872
1906
|
},
|
|
1873
1907
|
{
|
|
1874
1908
|
id: "openai/o3",
|
|
@@ -1878,7 +1912,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1878
1912
|
outputPrice: 8,
|
|
1879
1913
|
contextWindow: 2e5,
|
|
1880
1914
|
maxOutput: 1e5,
|
|
1881
|
-
reasoning: true
|
|
1915
|
+
reasoning: true,
|
|
1916
|
+
toolCalling: true
|
|
1882
1917
|
},
|
|
1883
1918
|
{
|
|
1884
1919
|
id: "openai/o3-mini",
|
|
@@ -1888,7 +1923,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1888
1923
|
outputPrice: 4.4,
|
|
1889
1924
|
contextWindow: 128e3,
|
|
1890
1925
|
maxOutput: 65536,
|
|
1891
|
-
reasoning: true
|
|
1926
|
+
reasoning: true,
|
|
1927
|
+
toolCalling: true
|
|
1892
1928
|
},
|
|
1893
1929
|
{
|
|
1894
1930
|
id: "openai/o4-mini",
|
|
@@ -1898,7 +1934,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1898
1934
|
outputPrice: 4.4,
|
|
1899
1935
|
contextWindow: 128e3,
|
|
1900
1936
|
maxOutput: 65536,
|
|
1901
|
-
reasoning: true
|
|
1937
|
+
reasoning: true,
|
|
1938
|
+
toolCalling: true
|
|
1902
1939
|
},
|
|
1903
1940
|
// Anthropic - all Claude models excel at agentic workflows
|
|
1904
1941
|
// Use newest versions (4.6) with full provider prefix
|
|
@@ -1910,7 +1947,9 @@ var BLOCKRUN_MODELS = [
|
|
|
1910
1947
|
outputPrice: 5,
|
|
1911
1948
|
contextWindow: 2e5,
|
|
1912
1949
|
maxOutput: 8192,
|
|
1913
|
-
|
|
1950
|
+
vision: true,
|
|
1951
|
+
agentic: true,
|
|
1952
|
+
toolCalling: true
|
|
1914
1953
|
},
|
|
1915
1954
|
{
|
|
1916
1955
|
id: "anthropic/claude-sonnet-4.6",
|
|
@@ -1921,7 +1960,9 @@ var BLOCKRUN_MODELS = [
|
|
|
1921
1960
|
contextWindow: 2e5,
|
|
1922
1961
|
maxOutput: 64e3,
|
|
1923
1962
|
reasoning: true,
|
|
1924
|
-
|
|
1963
|
+
vision: true,
|
|
1964
|
+
agentic: true,
|
|
1965
|
+
toolCalling: true
|
|
1925
1966
|
},
|
|
1926
1967
|
{
|
|
1927
1968
|
id: "anthropic/claude-opus-4.6",
|
|
@@ -1932,19 +1973,22 @@ var BLOCKRUN_MODELS = [
|
|
|
1932
1973
|
contextWindow: 2e5,
|
|
1933
1974
|
maxOutput: 32e3,
|
|
1934
1975
|
reasoning: true,
|
|
1935
|
-
|
|
1976
|
+
vision: true,
|
|
1977
|
+
agentic: true,
|
|
1978
|
+
toolCalling: true
|
|
1936
1979
|
},
|
|
1937
1980
|
// Google
|
|
1938
1981
|
{
|
|
1939
|
-
id: "google/gemini-3.1-pro
|
|
1940
|
-
name: "Gemini 3.1 Pro
|
|
1982
|
+
id: "google/gemini-3.1-pro",
|
|
1983
|
+
name: "Gemini 3.1 Pro",
|
|
1941
1984
|
version: "3.1",
|
|
1942
1985
|
inputPrice: 2,
|
|
1943
1986
|
outputPrice: 12,
|
|
1944
1987
|
contextWindow: 105e4,
|
|
1945
1988
|
maxOutput: 65536,
|
|
1946
1989
|
reasoning: true,
|
|
1947
|
-
vision: true
|
|
1990
|
+
vision: true,
|
|
1991
|
+
toolCalling: true
|
|
1948
1992
|
},
|
|
1949
1993
|
{
|
|
1950
1994
|
id: "google/gemini-3-pro-preview",
|
|
@@ -1955,7 +1999,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1955
1999
|
contextWindow: 105e4,
|
|
1956
2000
|
maxOutput: 65536,
|
|
1957
2001
|
reasoning: true,
|
|
1958
|
-
vision: true
|
|
2002
|
+
vision: true,
|
|
2003
|
+
toolCalling: true
|
|
1959
2004
|
},
|
|
1960
2005
|
{
|
|
1961
2006
|
id: "google/gemini-3-flash-preview",
|
|
@@ -1965,7 +2010,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1965
2010
|
outputPrice: 3,
|
|
1966
2011
|
contextWindow: 1e6,
|
|
1967
2012
|
maxOutput: 65536,
|
|
1968
|
-
vision: true
|
|
2013
|
+
vision: true,
|
|
2014
|
+
toolCalling: true
|
|
1969
2015
|
},
|
|
1970
2016
|
{
|
|
1971
2017
|
id: "google/gemini-2.5-pro",
|
|
@@ -1976,7 +2022,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1976
2022
|
contextWindow: 105e4,
|
|
1977
2023
|
maxOutput: 65536,
|
|
1978
2024
|
reasoning: true,
|
|
1979
|
-
vision: true
|
|
2025
|
+
vision: true,
|
|
2026
|
+
toolCalling: true
|
|
1980
2027
|
},
|
|
1981
2028
|
{
|
|
1982
2029
|
id: "google/gemini-2.5-flash",
|
|
@@ -1985,7 +2032,9 @@ var BLOCKRUN_MODELS = [
|
|
|
1985
2032
|
inputPrice: 0.3,
|
|
1986
2033
|
outputPrice: 2.5,
|
|
1987
2034
|
contextWindow: 1e6,
|
|
1988
|
-
maxOutput: 65536
|
|
2035
|
+
maxOutput: 65536,
|
|
2036
|
+
vision: true,
|
|
2037
|
+
toolCalling: true
|
|
1989
2038
|
},
|
|
1990
2039
|
{
|
|
1991
2040
|
id: "google/gemini-2.5-flash-lite",
|
|
@@ -1994,7 +2043,8 @@ var BLOCKRUN_MODELS = [
|
|
|
1994
2043
|
inputPrice: 0.1,
|
|
1995
2044
|
outputPrice: 0.4,
|
|
1996
2045
|
contextWindow: 1e6,
|
|
1997
|
-
maxOutput: 65536
|
|
2046
|
+
maxOutput: 65536,
|
|
2047
|
+
toolCalling: true
|
|
1998
2048
|
},
|
|
1999
2049
|
// DeepSeek
|
|
2000
2050
|
{
|
|
@@ -2004,7 +2054,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2004
2054
|
inputPrice: 0.28,
|
|
2005
2055
|
outputPrice: 0.42,
|
|
2006
2056
|
contextWindow: 128e3,
|
|
2007
|
-
maxOutput: 8192
|
|
2057
|
+
maxOutput: 8192,
|
|
2058
|
+
toolCalling: true
|
|
2008
2059
|
},
|
|
2009
2060
|
{
|
|
2010
2061
|
id: "deepseek/deepseek-reasoner",
|
|
@@ -2014,7 +2065,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2014
2065
|
outputPrice: 0.42,
|
|
2015
2066
|
contextWindow: 128e3,
|
|
2016
2067
|
maxOutput: 8192,
|
|
2017
|
-
reasoning: true
|
|
2068
|
+
reasoning: true,
|
|
2069
|
+
toolCalling: true
|
|
2018
2070
|
},
|
|
2019
2071
|
// Moonshot / Kimi - optimized for agentic workflows
|
|
2020
2072
|
{
|
|
@@ -2027,7 +2079,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2027
2079
|
maxOutput: 8192,
|
|
2028
2080
|
reasoning: true,
|
|
2029
2081
|
vision: true,
|
|
2030
|
-
agentic: true
|
|
2082
|
+
agentic: true,
|
|
2083
|
+
toolCalling: true
|
|
2031
2084
|
},
|
|
2032
2085
|
// xAI / Grok
|
|
2033
2086
|
{
|
|
@@ -2038,7 +2091,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2038
2091
|
outputPrice: 15,
|
|
2039
2092
|
contextWindow: 131072,
|
|
2040
2093
|
maxOutput: 16384,
|
|
2041
|
-
reasoning: true
|
|
2094
|
+
reasoning: true,
|
|
2095
|
+
toolCalling: true
|
|
2042
2096
|
},
|
|
2043
2097
|
// grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead
|
|
2044
2098
|
{
|
|
@@ -2048,7 +2102,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2048
2102
|
inputPrice: 0.3,
|
|
2049
2103
|
outputPrice: 0.5,
|
|
2050
2104
|
contextWindow: 131072,
|
|
2051
|
-
maxOutput: 16384
|
|
2105
|
+
maxOutput: 16384,
|
|
2106
|
+
toolCalling: true
|
|
2052
2107
|
},
|
|
2053
2108
|
// xAI Grok 4 Family - Ultra-cheap fast models
|
|
2054
2109
|
{
|
|
@@ -2059,7 +2114,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2059
2114
|
outputPrice: 0.5,
|
|
2060
2115
|
contextWindow: 131072,
|
|
2061
2116
|
maxOutput: 16384,
|
|
2062
|
-
reasoning: true
|
|
2117
|
+
reasoning: true,
|
|
2118
|
+
toolCalling: true
|
|
2063
2119
|
},
|
|
2064
2120
|
{
|
|
2065
2121
|
id: "xai/grok-4-fast-non-reasoning",
|
|
@@ -2068,7 +2124,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2068
2124
|
inputPrice: 0.2,
|
|
2069
2125
|
outputPrice: 0.5,
|
|
2070
2126
|
contextWindow: 131072,
|
|
2071
|
-
maxOutput: 16384
|
|
2127
|
+
maxOutput: 16384,
|
|
2128
|
+
toolCalling: true
|
|
2072
2129
|
},
|
|
2073
2130
|
{
|
|
2074
2131
|
id: "xai/grok-4-1-fast-reasoning",
|
|
@@ -2078,7 +2135,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2078
2135
|
outputPrice: 0.5,
|
|
2079
2136
|
contextWindow: 131072,
|
|
2080
2137
|
maxOutput: 16384,
|
|
2081
|
-
reasoning: true
|
|
2138
|
+
reasoning: true,
|
|
2139
|
+
toolCalling: true
|
|
2082
2140
|
},
|
|
2083
2141
|
{
|
|
2084
2142
|
id: "xai/grok-4-1-fast-non-reasoning",
|
|
@@ -2087,7 +2145,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2087
2145
|
inputPrice: 0.2,
|
|
2088
2146
|
outputPrice: 0.5,
|
|
2089
2147
|
contextWindow: 131072,
|
|
2090
|
-
maxOutput: 16384
|
|
2148
|
+
maxOutput: 16384,
|
|
2149
|
+
toolCalling: true
|
|
2091
2150
|
},
|
|
2092
2151
|
{
|
|
2093
2152
|
id: "xai/grok-code-fast-1",
|
|
@@ -2096,9 +2155,10 @@ var BLOCKRUN_MODELS = [
|
|
|
2096
2155
|
inputPrice: 0.2,
|
|
2097
2156
|
outputPrice: 1.5,
|
|
2098
2157
|
contextWindow: 131072,
|
|
2099
|
-
maxOutput: 16384
|
|
2100
|
-
|
|
2101
|
-
//
|
|
2158
|
+
maxOutput: 16384
|
|
2159
|
+
// toolCalling intentionally omitted: outputs tool calls as plain text JSON,
|
|
2160
|
+
// not OpenAI-compatible structured function calls. Will be skipped when
|
|
2161
|
+
// request has tools to prevent the "talking to itself" bug.
|
|
2102
2162
|
},
|
|
2103
2163
|
{
|
|
2104
2164
|
id: "xai/grok-4-0709",
|
|
@@ -2108,7 +2168,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2108
2168
|
outputPrice: 1.5,
|
|
2109
2169
|
contextWindow: 131072,
|
|
2110
2170
|
maxOutput: 16384,
|
|
2111
|
-
reasoning: true
|
|
2171
|
+
reasoning: true,
|
|
2172
|
+
toolCalling: true
|
|
2112
2173
|
},
|
|
2113
2174
|
{
|
|
2114
2175
|
id: "xai/grok-2-vision",
|
|
@@ -2118,7 +2179,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2118
2179
|
outputPrice: 10,
|
|
2119
2180
|
contextWindow: 131072,
|
|
2120
2181
|
maxOutput: 16384,
|
|
2121
|
-
vision: true
|
|
2182
|
+
vision: true,
|
|
2183
|
+
toolCalling: true
|
|
2122
2184
|
},
|
|
2123
2185
|
// MiniMax
|
|
2124
2186
|
{
|
|
@@ -2130,7 +2192,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2130
2192
|
contextWindow: 204800,
|
|
2131
2193
|
maxOutput: 16384,
|
|
2132
2194
|
reasoning: true,
|
|
2133
|
-
agentic: true
|
|
2195
|
+
agentic: true,
|
|
2196
|
+
toolCalling: true
|
|
2134
2197
|
},
|
|
2135
2198
|
// NVIDIA - Free/cheap models
|
|
2136
2199
|
{
|
|
@@ -2141,6 +2204,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2141
2204
|
outputPrice: 0,
|
|
2142
2205
|
contextWindow: 128e3,
|
|
2143
2206
|
maxOutput: 16384
|
|
2207
|
+
// toolCalling intentionally omitted: free model, structured function
|
|
2208
|
+
// calling support unverified. Excluded from tool-heavy routing paths.
|
|
2144
2209
|
},
|
|
2145
2210
|
{
|
|
2146
2211
|
id: "nvidia/kimi-k2.5",
|
|
@@ -2149,7 +2214,8 @@ var BLOCKRUN_MODELS = [
|
|
|
2149
2214
|
inputPrice: 0.55,
|
|
2150
2215
|
outputPrice: 2.5,
|
|
2151
2216
|
contextWindow: 262144,
|
|
2152
|
-
maxOutput: 16384
|
|
2217
|
+
maxOutput: 16384,
|
|
2218
|
+
toolCalling: true
|
|
2153
2219
|
}
|
|
2154
2220
|
];
|
|
2155
2221
|
function toOpenClawModel(m) {
|
|
@@ -2178,6 +2244,16 @@ var OPENCLAW_MODELS = [
|
|
|
2178
2244
|
...BLOCKRUN_MODELS.map(toOpenClawModel),
|
|
2179
2245
|
...ALIAS_MODELS
|
|
2180
2246
|
];
|
|
2247
|
+
function supportsToolCalling(modelId) {
|
|
2248
|
+
const normalized = modelId.replace("blockrun/", "");
|
|
2249
|
+
const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
|
|
2250
|
+
return model?.toolCalling ?? false;
|
|
2251
|
+
}
|
|
2252
|
+
function supportsVision(modelId) {
|
|
2253
|
+
const normalized = modelId.replace("blockrun/", "");
|
|
2254
|
+
const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
|
|
2255
|
+
return model?.vision ?? false;
|
|
2256
|
+
}
|
|
2181
2257
|
function getModelContextWindow(modelId) {
|
|
2182
2258
|
const normalized = modelId.replace("blockrun/", "");
|
|
2183
2259
|
const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);
|
|
@@ -2828,6 +2904,105 @@ var BalanceMonitor = class {
|
|
|
2828
2904
|
}
|
|
2829
2905
|
};
|
|
2830
2906
|
|
|
2907
|
+
// src/solana-balance.ts
|
|
2908
|
+
import { address as solAddress, createSolanaRpc } from "@solana/kit";
|
|
2909
|
+
var SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
2910
|
+
var SOLANA_DEFAULT_RPC = "https://api.mainnet-beta.solana.com";
|
|
2911
|
+
var BALANCE_TIMEOUT_MS = 1e4;
|
|
2912
|
+
var CACHE_TTL_MS2 = 3e4;
|
|
2913
|
+
var SolanaBalanceMonitor = class {
|
|
2914
|
+
rpc;
|
|
2915
|
+
walletAddress;
|
|
2916
|
+
cachedBalance = null;
|
|
2917
|
+
cachedAt = 0;
|
|
2918
|
+
constructor(walletAddress, rpcUrl) {
|
|
2919
|
+
this.walletAddress = walletAddress;
|
|
2920
|
+
const url = rpcUrl || process["env"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC;
|
|
2921
|
+
this.rpc = createSolanaRpc(url);
|
|
2922
|
+
}
|
|
2923
|
+
async checkBalance() {
|
|
2924
|
+
const now = Date.now();
|
|
2925
|
+
if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS2) {
|
|
2926
|
+
return this.buildInfo(this.cachedBalance);
|
|
2927
|
+
}
|
|
2928
|
+
const balance = await this.fetchBalance();
|
|
2929
|
+
this.cachedBalance = balance;
|
|
2930
|
+
this.cachedAt = now;
|
|
2931
|
+
return this.buildInfo(balance);
|
|
2932
|
+
}
|
|
2933
|
+
deductEstimated(amountMicros) {
|
|
2934
|
+
if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
|
|
2935
|
+
this.cachedBalance -= amountMicros;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
invalidate() {
|
|
2939
|
+
this.cachedBalance = null;
|
|
2940
|
+
this.cachedAt = 0;
|
|
2941
|
+
}
|
|
2942
|
+
async refresh() {
|
|
2943
|
+
this.invalidate();
|
|
2944
|
+
return this.checkBalance();
|
|
2945
|
+
}
|
|
2946
|
+
/**
|
|
2947
|
+
* Check if balance is sufficient for an estimated cost.
|
|
2948
|
+
*/
|
|
2949
|
+
async checkSufficient(estimatedCostMicros) {
|
|
2950
|
+
const info = await this.checkBalance();
|
|
2951
|
+
if (info.balance >= estimatedCostMicros) {
|
|
2952
|
+
return { sufficient: true, info };
|
|
2953
|
+
}
|
|
2954
|
+
const shortfall = estimatedCostMicros - info.balance;
|
|
2955
|
+
return {
|
|
2956
|
+
sufficient: false,
|
|
2957
|
+
info,
|
|
2958
|
+
shortfall: this.formatUSDC(shortfall)
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Format USDC amount (in micros) as "$X.XX".
|
|
2963
|
+
*/
|
|
2964
|
+
formatUSDC(amountMicros) {
|
|
2965
|
+
const dollars = Number(amountMicros) / 1e6;
|
|
2966
|
+
return `$${dollars.toFixed(2)}`;
|
|
2967
|
+
}
|
|
2968
|
+
getWalletAddress() {
|
|
2969
|
+
return this.walletAddress;
|
|
2970
|
+
}
|
|
2971
|
+
async fetchBalance() {
|
|
2972
|
+
const owner = solAddress(this.walletAddress);
|
|
2973
|
+
const mint = solAddress(SOLANA_USDC_MINT);
|
|
2974
|
+
const controller = new AbortController();
|
|
2975
|
+
const timer = setTimeout(() => controller.abort(), BALANCE_TIMEOUT_MS);
|
|
2976
|
+
try {
|
|
2977
|
+
const response = await this.rpc.getTokenAccountsByOwner(owner, { mint }, { encoding: "jsonParsed" }).send({ abortSignal: controller.signal });
|
|
2978
|
+
if (response.value.length === 0) return 0n;
|
|
2979
|
+
let total = 0n;
|
|
2980
|
+
for (const account of response.value) {
|
|
2981
|
+
const parsed = account.account.data;
|
|
2982
|
+
total += BigInt(parsed.parsed.info.tokenAmount.amount);
|
|
2983
|
+
}
|
|
2984
|
+
return total;
|
|
2985
|
+
} catch (err) {
|
|
2986
|
+
throw new Error(
|
|
2987
|
+
`Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`,
|
|
2988
|
+
{ cause: err }
|
|
2989
|
+
);
|
|
2990
|
+
} finally {
|
|
2991
|
+
clearTimeout(timer);
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
buildInfo(balance) {
|
|
2995
|
+
const dollars = Number(balance) / 1e6;
|
|
2996
|
+
return {
|
|
2997
|
+
balance,
|
|
2998
|
+
balanceUSD: `$${dollars.toFixed(2)}`,
|
|
2999
|
+
isLow: balance < 1000000n,
|
|
3000
|
+
isEmpty: balance < 100n,
|
|
3001
|
+
walletAddress: this.walletAddress
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
};
|
|
3005
|
+
|
|
2831
3006
|
// src/auth.ts
|
|
2832
3007
|
import { writeFile, mkdir as mkdir2 } from "fs/promises";
|
|
2833
3008
|
import { join as join4 } from "path";
|
|
@@ -2838,9 +3013,507 @@ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
|
2838
3013
|
import { HDKey } from "@scure/bip32";
|
|
2839
3014
|
import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
|
|
2840
3015
|
import { wordlist as english } from "@scure/bip39/wordlists/english";
|
|
3016
|
+
|
|
3017
|
+
// node_modules/@noble/hashes/esm/utils.js
|
|
3018
|
+
function isBytes(a) {
|
|
3019
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
3020
|
+
}
|
|
3021
|
+
function anumber(n) {
|
|
3022
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
3023
|
+
throw new Error("positive integer expected, got " + n);
|
|
3024
|
+
}
|
|
3025
|
+
function abytes(b, ...lengths) {
|
|
3026
|
+
if (!isBytes(b))
|
|
3027
|
+
throw new Error("Uint8Array expected");
|
|
3028
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
3029
|
+
throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
3030
|
+
}
|
|
3031
|
+
function ahash(h) {
|
|
3032
|
+
if (typeof h !== "function" || typeof h.create !== "function")
|
|
3033
|
+
throw new Error("Hash should be wrapped by utils.createHasher");
|
|
3034
|
+
anumber(h.outputLen);
|
|
3035
|
+
anumber(h.blockLen);
|
|
3036
|
+
}
|
|
3037
|
+
function aexists(instance, checkFinished = true) {
|
|
3038
|
+
if (instance.destroyed)
|
|
3039
|
+
throw new Error("Hash instance has been destroyed");
|
|
3040
|
+
if (checkFinished && instance.finished)
|
|
3041
|
+
throw new Error("Hash#digest() has already been called");
|
|
3042
|
+
}
|
|
3043
|
+
function aoutput(out, instance) {
|
|
3044
|
+
abytes(out);
|
|
3045
|
+
const min = instance.outputLen;
|
|
3046
|
+
if (out.length < min) {
|
|
3047
|
+
throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
function clean(...arrays) {
|
|
3051
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
3052
|
+
arrays[i].fill(0);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
function createView(arr) {
|
|
3056
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
3057
|
+
}
|
|
3058
|
+
function utf8ToBytes(str) {
|
|
3059
|
+
if (typeof str !== "string")
|
|
3060
|
+
throw new Error("string expected");
|
|
3061
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
3062
|
+
}
|
|
3063
|
+
function toBytes(data) {
|
|
3064
|
+
if (typeof data === "string")
|
|
3065
|
+
data = utf8ToBytes(data);
|
|
3066
|
+
abytes(data);
|
|
3067
|
+
return data;
|
|
3068
|
+
}
|
|
3069
|
+
var Hash = class {
|
|
3070
|
+
};
|
|
3071
|
+
function createHasher(hashCons) {
|
|
3072
|
+
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
3073
|
+
const tmp = hashCons();
|
|
3074
|
+
hashC.outputLen = tmp.outputLen;
|
|
3075
|
+
hashC.blockLen = tmp.blockLen;
|
|
3076
|
+
hashC.create = () => hashCons();
|
|
3077
|
+
return hashC;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
// node_modules/@noble/hashes/esm/hmac.js
|
|
3081
|
+
var HMAC = class extends Hash {
|
|
3082
|
+
constructor(hash, _key) {
|
|
3083
|
+
super();
|
|
3084
|
+
this.finished = false;
|
|
3085
|
+
this.destroyed = false;
|
|
3086
|
+
ahash(hash);
|
|
3087
|
+
const key = toBytes(_key);
|
|
3088
|
+
this.iHash = hash.create();
|
|
3089
|
+
if (typeof this.iHash.update !== "function")
|
|
3090
|
+
throw new Error("Expected instance of class which extends utils.Hash");
|
|
3091
|
+
this.blockLen = this.iHash.blockLen;
|
|
3092
|
+
this.outputLen = this.iHash.outputLen;
|
|
3093
|
+
const blockLen = this.blockLen;
|
|
3094
|
+
const pad = new Uint8Array(blockLen);
|
|
3095
|
+
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
|
|
3096
|
+
for (let i = 0; i < pad.length; i++)
|
|
3097
|
+
pad[i] ^= 54;
|
|
3098
|
+
this.iHash.update(pad);
|
|
3099
|
+
this.oHash = hash.create();
|
|
3100
|
+
for (let i = 0; i < pad.length; i++)
|
|
3101
|
+
pad[i] ^= 54 ^ 92;
|
|
3102
|
+
this.oHash.update(pad);
|
|
3103
|
+
clean(pad);
|
|
3104
|
+
}
|
|
3105
|
+
update(buf) {
|
|
3106
|
+
aexists(this);
|
|
3107
|
+
this.iHash.update(buf);
|
|
3108
|
+
return this;
|
|
3109
|
+
}
|
|
3110
|
+
digestInto(out) {
|
|
3111
|
+
aexists(this);
|
|
3112
|
+
abytes(out, this.outputLen);
|
|
3113
|
+
this.finished = true;
|
|
3114
|
+
this.iHash.digestInto(out);
|
|
3115
|
+
this.oHash.update(out);
|
|
3116
|
+
this.oHash.digestInto(out);
|
|
3117
|
+
this.destroy();
|
|
3118
|
+
}
|
|
3119
|
+
digest() {
|
|
3120
|
+
const out = new Uint8Array(this.oHash.outputLen);
|
|
3121
|
+
this.digestInto(out);
|
|
3122
|
+
return out;
|
|
3123
|
+
}
|
|
3124
|
+
_cloneInto(to) {
|
|
3125
|
+
to || (to = Object.create(Object.getPrototypeOf(this), {}));
|
|
3126
|
+
const { oHash, iHash, finished: finished2, destroyed, blockLen, outputLen } = this;
|
|
3127
|
+
to = to;
|
|
3128
|
+
to.finished = finished2;
|
|
3129
|
+
to.destroyed = destroyed;
|
|
3130
|
+
to.blockLen = blockLen;
|
|
3131
|
+
to.outputLen = outputLen;
|
|
3132
|
+
to.oHash = oHash._cloneInto(to.oHash);
|
|
3133
|
+
to.iHash = iHash._cloneInto(to.iHash);
|
|
3134
|
+
return to;
|
|
3135
|
+
}
|
|
3136
|
+
clone() {
|
|
3137
|
+
return this._cloneInto();
|
|
3138
|
+
}
|
|
3139
|
+
destroy() {
|
|
3140
|
+
this.destroyed = true;
|
|
3141
|
+
this.oHash.destroy();
|
|
3142
|
+
this.iHash.destroy();
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3145
|
+
var hmac = (hash, key, message) => new HMAC(hash, key).update(message).digest();
|
|
3146
|
+
hmac.create = (hash, key) => new HMAC(hash, key);
|
|
3147
|
+
|
|
3148
|
+
// node_modules/@noble/hashes/esm/_md.js
|
|
3149
|
+
function setBigUint64(view, byteOffset, value, isLE) {
|
|
3150
|
+
if (typeof view.setBigUint64 === "function")
|
|
3151
|
+
return view.setBigUint64(byteOffset, value, isLE);
|
|
3152
|
+
const _32n2 = BigInt(32);
|
|
3153
|
+
const _u32_max = BigInt(4294967295);
|
|
3154
|
+
const wh = Number(value >> _32n2 & _u32_max);
|
|
3155
|
+
const wl = Number(value & _u32_max);
|
|
3156
|
+
const h = isLE ? 4 : 0;
|
|
3157
|
+
const l = isLE ? 0 : 4;
|
|
3158
|
+
view.setUint32(byteOffset + h, wh, isLE);
|
|
3159
|
+
view.setUint32(byteOffset + l, wl, isLE);
|
|
3160
|
+
}
|
|
3161
|
+
var HashMD = class extends Hash {
|
|
3162
|
+
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
3163
|
+
super();
|
|
3164
|
+
this.finished = false;
|
|
3165
|
+
this.length = 0;
|
|
3166
|
+
this.pos = 0;
|
|
3167
|
+
this.destroyed = false;
|
|
3168
|
+
this.blockLen = blockLen;
|
|
3169
|
+
this.outputLen = outputLen;
|
|
3170
|
+
this.padOffset = padOffset;
|
|
3171
|
+
this.isLE = isLE;
|
|
3172
|
+
this.buffer = new Uint8Array(blockLen);
|
|
3173
|
+
this.view = createView(this.buffer);
|
|
3174
|
+
}
|
|
3175
|
+
update(data) {
|
|
3176
|
+
aexists(this);
|
|
3177
|
+
data = toBytes(data);
|
|
3178
|
+
abytes(data);
|
|
3179
|
+
const { view, buffer, blockLen } = this;
|
|
3180
|
+
const len = data.length;
|
|
3181
|
+
for (let pos = 0; pos < len; ) {
|
|
3182
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
3183
|
+
if (take === blockLen) {
|
|
3184
|
+
const dataView = createView(data);
|
|
3185
|
+
for (; blockLen <= len - pos; pos += blockLen)
|
|
3186
|
+
this.process(dataView, pos);
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
3190
|
+
this.pos += take;
|
|
3191
|
+
pos += take;
|
|
3192
|
+
if (this.pos === blockLen) {
|
|
3193
|
+
this.process(view, 0);
|
|
3194
|
+
this.pos = 0;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
this.length += data.length;
|
|
3198
|
+
this.roundClean();
|
|
3199
|
+
return this;
|
|
3200
|
+
}
|
|
3201
|
+
digestInto(out) {
|
|
3202
|
+
aexists(this);
|
|
3203
|
+
aoutput(out, this);
|
|
3204
|
+
this.finished = true;
|
|
3205
|
+
const { buffer, view, blockLen, isLE } = this;
|
|
3206
|
+
let { pos } = this;
|
|
3207
|
+
buffer[pos++] = 128;
|
|
3208
|
+
clean(this.buffer.subarray(pos));
|
|
3209
|
+
if (this.padOffset > blockLen - pos) {
|
|
3210
|
+
this.process(view, 0);
|
|
3211
|
+
pos = 0;
|
|
3212
|
+
}
|
|
3213
|
+
for (let i = pos; i < blockLen; i++)
|
|
3214
|
+
buffer[i] = 0;
|
|
3215
|
+
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
|
|
3216
|
+
this.process(view, 0);
|
|
3217
|
+
const oview = createView(out);
|
|
3218
|
+
const len = this.outputLen;
|
|
3219
|
+
if (len % 4)
|
|
3220
|
+
throw new Error("_sha2: outputLen should be aligned to 32bit");
|
|
3221
|
+
const outLen = len / 4;
|
|
3222
|
+
const state = this.get();
|
|
3223
|
+
if (outLen > state.length)
|
|
3224
|
+
throw new Error("_sha2: outputLen bigger than state");
|
|
3225
|
+
for (let i = 0; i < outLen; i++)
|
|
3226
|
+
oview.setUint32(4 * i, state[i], isLE);
|
|
3227
|
+
}
|
|
3228
|
+
digest() {
|
|
3229
|
+
const { buffer, outputLen } = this;
|
|
3230
|
+
this.digestInto(buffer);
|
|
3231
|
+
const res = buffer.slice(0, outputLen);
|
|
3232
|
+
this.destroy();
|
|
3233
|
+
return res;
|
|
3234
|
+
}
|
|
3235
|
+
_cloneInto(to) {
|
|
3236
|
+
to || (to = new this.constructor());
|
|
3237
|
+
to.set(...this.get());
|
|
3238
|
+
const { blockLen, buffer, length, finished: finished2, destroyed, pos } = this;
|
|
3239
|
+
to.destroyed = destroyed;
|
|
3240
|
+
to.finished = finished2;
|
|
3241
|
+
to.length = length;
|
|
3242
|
+
to.pos = pos;
|
|
3243
|
+
if (length % blockLen)
|
|
3244
|
+
to.buffer.set(buffer);
|
|
3245
|
+
return to;
|
|
3246
|
+
}
|
|
3247
|
+
clone() {
|
|
3248
|
+
return this._cloneInto();
|
|
3249
|
+
}
|
|
3250
|
+
};
|
|
3251
|
+
var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
|
|
3252
|
+
1779033703,
|
|
3253
|
+
4089235720,
|
|
3254
|
+
3144134277,
|
|
3255
|
+
2227873595,
|
|
3256
|
+
1013904242,
|
|
3257
|
+
4271175723,
|
|
3258
|
+
2773480762,
|
|
3259
|
+
1595750129,
|
|
3260
|
+
1359893119,
|
|
3261
|
+
2917565137,
|
|
3262
|
+
2600822924,
|
|
3263
|
+
725511199,
|
|
3264
|
+
528734635,
|
|
3265
|
+
4215389547,
|
|
3266
|
+
1541459225,
|
|
3267
|
+
327033209
|
|
3268
|
+
]);
|
|
3269
|
+
|
|
3270
|
+
// node_modules/@noble/hashes/esm/_u64.js
|
|
3271
|
+
var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
3272
|
+
var _32n = /* @__PURE__ */ BigInt(32);
|
|
3273
|
+
function fromBig(n, le = false) {
|
|
3274
|
+
if (le)
|
|
3275
|
+
return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
|
|
3276
|
+
return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
|
3277
|
+
}
|
|
3278
|
+
function split(lst, le = false) {
|
|
3279
|
+
const len = lst.length;
|
|
3280
|
+
let Ah = new Uint32Array(len);
|
|
3281
|
+
let Al = new Uint32Array(len);
|
|
3282
|
+
for (let i = 0; i < len; i++) {
|
|
3283
|
+
const { h, l } = fromBig(lst[i], le);
|
|
3284
|
+
[Ah[i], Al[i]] = [h, l];
|
|
3285
|
+
}
|
|
3286
|
+
return [Ah, Al];
|
|
3287
|
+
}
|
|
3288
|
+
var shrSH = (h, _l, s) => h >>> s;
|
|
3289
|
+
var shrSL = (h, l, s) => h << 32 - s | l >>> s;
|
|
3290
|
+
var rotrSH = (h, l, s) => h >>> s | l << 32 - s;
|
|
3291
|
+
var rotrSL = (h, l, s) => h << 32 - s | l >>> s;
|
|
3292
|
+
var rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
|
|
3293
|
+
var rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
|
|
3294
|
+
function add(Ah, Al, Bh, Bl) {
|
|
3295
|
+
const l = (Al >>> 0) + (Bl >>> 0);
|
|
3296
|
+
return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 };
|
|
3297
|
+
}
|
|
3298
|
+
var add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
|
|
3299
|
+
var add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
|
|
3300
|
+
var add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
|
|
3301
|
+
var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
|
|
3302
|
+
var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
|
|
3303
|
+
var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
|
|
3304
|
+
|
|
3305
|
+
// node_modules/@noble/hashes/esm/sha2.js
|
|
3306
|
+
var K512 = /* @__PURE__ */ (() => split([
|
|
3307
|
+
"0x428a2f98d728ae22",
|
|
3308
|
+
"0x7137449123ef65cd",
|
|
3309
|
+
"0xb5c0fbcfec4d3b2f",
|
|
3310
|
+
"0xe9b5dba58189dbbc",
|
|
3311
|
+
"0x3956c25bf348b538",
|
|
3312
|
+
"0x59f111f1b605d019",
|
|
3313
|
+
"0x923f82a4af194f9b",
|
|
3314
|
+
"0xab1c5ed5da6d8118",
|
|
3315
|
+
"0xd807aa98a3030242",
|
|
3316
|
+
"0x12835b0145706fbe",
|
|
3317
|
+
"0x243185be4ee4b28c",
|
|
3318
|
+
"0x550c7dc3d5ffb4e2",
|
|
3319
|
+
"0x72be5d74f27b896f",
|
|
3320
|
+
"0x80deb1fe3b1696b1",
|
|
3321
|
+
"0x9bdc06a725c71235",
|
|
3322
|
+
"0xc19bf174cf692694",
|
|
3323
|
+
"0xe49b69c19ef14ad2",
|
|
3324
|
+
"0xefbe4786384f25e3",
|
|
3325
|
+
"0x0fc19dc68b8cd5b5",
|
|
3326
|
+
"0x240ca1cc77ac9c65",
|
|
3327
|
+
"0x2de92c6f592b0275",
|
|
3328
|
+
"0x4a7484aa6ea6e483",
|
|
3329
|
+
"0x5cb0a9dcbd41fbd4",
|
|
3330
|
+
"0x76f988da831153b5",
|
|
3331
|
+
"0x983e5152ee66dfab",
|
|
3332
|
+
"0xa831c66d2db43210",
|
|
3333
|
+
"0xb00327c898fb213f",
|
|
3334
|
+
"0xbf597fc7beef0ee4",
|
|
3335
|
+
"0xc6e00bf33da88fc2",
|
|
3336
|
+
"0xd5a79147930aa725",
|
|
3337
|
+
"0x06ca6351e003826f",
|
|
3338
|
+
"0x142929670a0e6e70",
|
|
3339
|
+
"0x27b70a8546d22ffc",
|
|
3340
|
+
"0x2e1b21385c26c926",
|
|
3341
|
+
"0x4d2c6dfc5ac42aed",
|
|
3342
|
+
"0x53380d139d95b3df",
|
|
3343
|
+
"0x650a73548baf63de",
|
|
3344
|
+
"0x766a0abb3c77b2a8",
|
|
3345
|
+
"0x81c2c92e47edaee6",
|
|
3346
|
+
"0x92722c851482353b",
|
|
3347
|
+
"0xa2bfe8a14cf10364",
|
|
3348
|
+
"0xa81a664bbc423001",
|
|
3349
|
+
"0xc24b8b70d0f89791",
|
|
3350
|
+
"0xc76c51a30654be30",
|
|
3351
|
+
"0xd192e819d6ef5218",
|
|
3352
|
+
"0xd69906245565a910",
|
|
3353
|
+
"0xf40e35855771202a",
|
|
3354
|
+
"0x106aa07032bbd1b8",
|
|
3355
|
+
"0x19a4c116b8d2d0c8",
|
|
3356
|
+
"0x1e376c085141ab53",
|
|
3357
|
+
"0x2748774cdf8eeb99",
|
|
3358
|
+
"0x34b0bcb5e19b48a8",
|
|
3359
|
+
"0x391c0cb3c5c95a63",
|
|
3360
|
+
"0x4ed8aa4ae3418acb",
|
|
3361
|
+
"0x5b9cca4f7763e373",
|
|
3362
|
+
"0x682e6ff3d6b2b8a3",
|
|
3363
|
+
"0x748f82ee5defb2fc",
|
|
3364
|
+
"0x78a5636f43172f60",
|
|
3365
|
+
"0x84c87814a1f0ab72",
|
|
3366
|
+
"0x8cc702081a6439ec",
|
|
3367
|
+
"0x90befffa23631e28",
|
|
3368
|
+
"0xa4506cebde82bde9",
|
|
3369
|
+
"0xbef9a3f7b2c67915",
|
|
3370
|
+
"0xc67178f2e372532b",
|
|
3371
|
+
"0xca273eceea26619c",
|
|
3372
|
+
"0xd186b8c721c0c207",
|
|
3373
|
+
"0xeada7dd6cde0eb1e",
|
|
3374
|
+
"0xf57d4f7fee6ed178",
|
|
3375
|
+
"0x06f067aa72176fba",
|
|
3376
|
+
"0x0a637dc5a2c898a6",
|
|
3377
|
+
"0x113f9804bef90dae",
|
|
3378
|
+
"0x1b710b35131c471b",
|
|
3379
|
+
"0x28db77f523047d84",
|
|
3380
|
+
"0x32caab7b40c72493",
|
|
3381
|
+
"0x3c9ebe0a15c9bebc",
|
|
3382
|
+
"0x431d67c49c100d4c",
|
|
3383
|
+
"0x4cc5d4becb3e42b6",
|
|
3384
|
+
"0x597f299cfc657e2a",
|
|
3385
|
+
"0x5fcb6fab3ad6faec",
|
|
3386
|
+
"0x6c44198c4a475817"
|
|
3387
|
+
].map((n) => BigInt(n))))();
|
|
3388
|
+
var SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
|
|
3389
|
+
var SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
|
|
3390
|
+
var SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
|
|
3391
|
+
var SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
|
|
3392
|
+
var SHA512 = class extends HashMD {
|
|
3393
|
+
constructor(outputLen = 64) {
|
|
3394
|
+
super(128, outputLen, 16, false);
|
|
3395
|
+
this.Ah = SHA512_IV[0] | 0;
|
|
3396
|
+
this.Al = SHA512_IV[1] | 0;
|
|
3397
|
+
this.Bh = SHA512_IV[2] | 0;
|
|
3398
|
+
this.Bl = SHA512_IV[3] | 0;
|
|
3399
|
+
this.Ch = SHA512_IV[4] | 0;
|
|
3400
|
+
this.Cl = SHA512_IV[5] | 0;
|
|
3401
|
+
this.Dh = SHA512_IV[6] | 0;
|
|
3402
|
+
this.Dl = SHA512_IV[7] | 0;
|
|
3403
|
+
this.Eh = SHA512_IV[8] | 0;
|
|
3404
|
+
this.El = SHA512_IV[9] | 0;
|
|
3405
|
+
this.Fh = SHA512_IV[10] | 0;
|
|
3406
|
+
this.Fl = SHA512_IV[11] | 0;
|
|
3407
|
+
this.Gh = SHA512_IV[12] | 0;
|
|
3408
|
+
this.Gl = SHA512_IV[13] | 0;
|
|
3409
|
+
this.Hh = SHA512_IV[14] | 0;
|
|
3410
|
+
this.Hl = SHA512_IV[15] | 0;
|
|
3411
|
+
}
|
|
3412
|
+
// prettier-ignore
|
|
3413
|
+
get() {
|
|
3414
|
+
const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
3415
|
+
return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
|
|
3416
|
+
}
|
|
3417
|
+
// prettier-ignore
|
|
3418
|
+
set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
|
|
3419
|
+
this.Ah = Ah | 0;
|
|
3420
|
+
this.Al = Al | 0;
|
|
3421
|
+
this.Bh = Bh | 0;
|
|
3422
|
+
this.Bl = Bl | 0;
|
|
3423
|
+
this.Ch = Ch | 0;
|
|
3424
|
+
this.Cl = Cl | 0;
|
|
3425
|
+
this.Dh = Dh | 0;
|
|
3426
|
+
this.Dl = Dl | 0;
|
|
3427
|
+
this.Eh = Eh | 0;
|
|
3428
|
+
this.El = El | 0;
|
|
3429
|
+
this.Fh = Fh | 0;
|
|
3430
|
+
this.Fl = Fl | 0;
|
|
3431
|
+
this.Gh = Gh | 0;
|
|
3432
|
+
this.Gl = Gl | 0;
|
|
3433
|
+
this.Hh = Hh | 0;
|
|
3434
|
+
this.Hl = Hl | 0;
|
|
3435
|
+
}
|
|
3436
|
+
process(view, offset) {
|
|
3437
|
+
for (let i = 0; i < 16; i++, offset += 4) {
|
|
3438
|
+
SHA512_W_H[i] = view.getUint32(offset);
|
|
3439
|
+
SHA512_W_L[i] = view.getUint32(offset += 4);
|
|
3440
|
+
}
|
|
3441
|
+
for (let i = 16; i < 80; i++) {
|
|
3442
|
+
const W15h = SHA512_W_H[i - 15] | 0;
|
|
3443
|
+
const W15l = SHA512_W_L[i - 15] | 0;
|
|
3444
|
+
const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
|
|
3445
|
+
const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
|
|
3446
|
+
const W2h = SHA512_W_H[i - 2] | 0;
|
|
3447
|
+
const W2l = SHA512_W_L[i - 2] | 0;
|
|
3448
|
+
const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
|
|
3449
|
+
const s1l = rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6);
|
|
3450
|
+
const SUMl = add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
|
|
3451
|
+
const SUMh = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);
|
|
3452
|
+
SHA512_W_H[i] = SUMh | 0;
|
|
3453
|
+
SHA512_W_L[i] = SUMl | 0;
|
|
3454
|
+
}
|
|
3455
|
+
let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
3456
|
+
for (let i = 0; i < 80; i++) {
|
|
3457
|
+
const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
|
|
3458
|
+
const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
|
|
3459
|
+
const CHIh = Eh & Fh ^ ~Eh & Gh;
|
|
3460
|
+
const CHIl = El & Fl ^ ~El & Gl;
|
|
3461
|
+
const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
|
|
3462
|
+
const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
|
|
3463
|
+
const T1l = T1ll | 0;
|
|
3464
|
+
const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
|
|
3465
|
+
const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
|
|
3466
|
+
const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
|
|
3467
|
+
const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
|
|
3468
|
+
Hh = Gh | 0;
|
|
3469
|
+
Hl = Gl | 0;
|
|
3470
|
+
Gh = Fh | 0;
|
|
3471
|
+
Gl = Fl | 0;
|
|
3472
|
+
Fh = Eh | 0;
|
|
3473
|
+
Fl = El | 0;
|
|
3474
|
+
({ h: Eh, l: El } = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
|
|
3475
|
+
Dh = Ch | 0;
|
|
3476
|
+
Dl = Cl | 0;
|
|
3477
|
+
Ch = Bh | 0;
|
|
3478
|
+
Cl = Bl | 0;
|
|
3479
|
+
Bh = Ah | 0;
|
|
3480
|
+
Bl = Al | 0;
|
|
3481
|
+
const All = add3L(T1l, sigma0l, MAJl);
|
|
3482
|
+
Ah = add3H(All, T1h, sigma0h, MAJh);
|
|
3483
|
+
Al = All | 0;
|
|
3484
|
+
}
|
|
3485
|
+
({ h: Ah, l: Al } = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
|
|
3486
|
+
({ h: Bh, l: Bl } = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
|
|
3487
|
+
({ h: Ch, l: Cl } = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
|
|
3488
|
+
({ h: Dh, l: Dl } = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
|
|
3489
|
+
({ h: Eh, l: El } = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
|
|
3490
|
+
({ h: Fh, l: Fl } = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
|
|
3491
|
+
({ h: Gh, l: Gl } = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
|
|
3492
|
+
({ h: Hh, l: Hl } = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
|
|
3493
|
+
this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
|
|
3494
|
+
}
|
|
3495
|
+
roundClean() {
|
|
3496
|
+
clean(SHA512_W_H, SHA512_W_L);
|
|
3497
|
+
}
|
|
3498
|
+
destroy() {
|
|
3499
|
+
clean(this.buffer);
|
|
3500
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
3501
|
+
}
|
|
3502
|
+
};
|
|
3503
|
+
var sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
|
|
3504
|
+
|
|
3505
|
+
// node_modules/@noble/hashes/esm/sha512.js
|
|
3506
|
+
var sha5122 = sha512;
|
|
3507
|
+
|
|
3508
|
+
// src/wallet.ts
|
|
2841
3509
|
import { privateKeyToAccount } from "viem/accounts";
|
|
2842
3510
|
var ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0";
|
|
2843
|
-
var
|
|
3511
|
+
var SOLANA_HARDENED_INDICES = [
|
|
3512
|
+
44 + 2147483648,
|
|
3513
|
+
501 + 2147483648,
|
|
3514
|
+
0 + 2147483648,
|
|
3515
|
+
0 + 2147483648
|
|
3516
|
+
];
|
|
2844
3517
|
function generateWalletMnemonic() {
|
|
2845
3518
|
return generateMnemonic(english, 256);
|
|
2846
3519
|
}
|
|
@@ -2857,10 +3530,29 @@ function deriveEvmKey(mnemonic) {
|
|
|
2857
3530
|
return { privateKey: hex, address: account.address };
|
|
2858
3531
|
}
|
|
2859
3532
|
function deriveSolanaKeyBytes(mnemonic) {
|
|
3533
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
3534
|
+
let I = hmac(sha5122, "ed25519 seed", seed);
|
|
3535
|
+
let key = I.slice(0, 32);
|
|
3536
|
+
let chainCode = I.slice(32);
|
|
3537
|
+
for (const index of SOLANA_HARDENED_INDICES) {
|
|
3538
|
+
const data = new Uint8Array(37);
|
|
3539
|
+
data[0] = 0;
|
|
3540
|
+
data.set(key, 1);
|
|
3541
|
+
data[33] = index >>> 24 & 255;
|
|
3542
|
+
data[34] = index >>> 16 & 255;
|
|
3543
|
+
data[35] = index >>> 8 & 255;
|
|
3544
|
+
data[36] = index & 255;
|
|
3545
|
+
I = hmac(sha5122, chainCode, data);
|
|
3546
|
+
key = I.slice(0, 32);
|
|
3547
|
+
chainCode = I.slice(32);
|
|
3548
|
+
}
|
|
3549
|
+
return new Uint8Array(key);
|
|
3550
|
+
}
|
|
3551
|
+
function deriveSolanaKeyBytesLegacy(mnemonic) {
|
|
2860
3552
|
const seed = mnemonicToSeedSync(mnemonic);
|
|
2861
3553
|
const hdKey = HDKey.fromMasterSeed(seed);
|
|
2862
|
-
const derived = hdKey.derive(
|
|
2863
|
-
if (!derived.privateKey) throw new Error("Failed to derive Solana private key");
|
|
3554
|
+
const derived = hdKey.derive("m/44'/501'/0'/0'");
|
|
3555
|
+
if (!derived.privateKey) throw new Error("Failed to derive legacy Solana private key");
|
|
2864
3556
|
return new Uint8Array(derived.privateKey);
|
|
2865
3557
|
}
|
|
2866
3558
|
function deriveAllKeys(mnemonic) {
|
|
@@ -2884,7 +3576,9 @@ async function loadSavedWallet() {
|
|
|
2884
3576
|
console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
|
|
2885
3577
|
console.error(`[ClawRouter] File: ${WALLET_FILE}`);
|
|
2886
3578
|
console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
|
|
2887
|
-
console.error(
|
|
3579
|
+
console.error(
|
|
3580
|
+
`[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
|
|
3581
|
+
);
|
|
2888
3582
|
throw new Error(
|
|
2889
3583
|
`Wallet file at ${WALLET_FILE} is corrupted or has wrong format. Refusing to auto-generate new wallet to protect existing funds. Restore your backup key or set BLOCKRUN_WALLET_KEY environment variable.`
|
|
2890
3584
|
);
|
|
@@ -2897,7 +3591,8 @@ async function loadSavedWallet() {
|
|
|
2897
3591
|
`[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
|
|
2898
3592
|
);
|
|
2899
3593
|
throw new Error(
|
|
2900
|
-
`Cannot read wallet file at ${WALLET_FILE}: ${err instanceof Error ? err.message : String(err)}. Refusing to auto-generate new wallet to protect existing funds. Fix file permissions or set BLOCKRUN_WALLET_KEY environment variable
|
|
3594
|
+
`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.`,
|
|
3595
|
+
{ cause: err }
|
|
2901
3596
|
);
|
|
2902
3597
|
}
|
|
2903
3598
|
}
|
|
@@ -2938,7 +3633,8 @@ async function generateAndSaveWallet() {
|
|
|
2938
3633
|
console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);
|
|
2939
3634
|
} catch (err) {
|
|
2940
3635
|
throw new Error(
|
|
2941
|
-
`Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}
|
|
3636
|
+
`Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`,
|
|
3637
|
+
{ cause: err }
|
|
2942
3638
|
);
|
|
2943
3639
|
}
|
|
2944
3640
|
console.log(`[ClawRouter]`);
|
|
@@ -2964,6 +3660,29 @@ async function generateAndSaveWallet() {
|
|
|
2964
3660
|
solanaPrivateKeyBytes: derived.solanaPrivateKeyBytes
|
|
2965
3661
|
};
|
|
2966
3662
|
}
|
|
3663
|
+
async function logMigrationWarning(legacyKeyBytes, newKeyBytes) {
|
|
3664
|
+
try {
|
|
3665
|
+
const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
|
|
3666
|
+
const [oldSigner, newSigner] = await Promise.all([
|
|
3667
|
+
createKeyPairSignerFromPrivateKeyBytes(legacyKeyBytes),
|
|
3668
|
+
createKeyPairSignerFromPrivateKeyBytes(newKeyBytes)
|
|
3669
|
+
]);
|
|
3670
|
+
console.log(`[ClawRouter]`);
|
|
3671
|
+
console.log(`[ClawRouter] \u26A0 SOLANA WALLET MIGRATION DETECTED`);
|
|
3672
|
+
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`);
|
|
3673
|
+
console.log(`[ClawRouter] Old address (secp256k1): ${oldSigner.address}`);
|
|
3674
|
+
console.log(`[ClawRouter] New address (SLIP-10): ${newSigner.address}`);
|
|
3675
|
+
console.log(`[ClawRouter]`);
|
|
3676
|
+
console.log(`[ClawRouter] Your Solana wallet derivation has been fixed to use`);
|
|
3677
|
+
console.log(`[ClawRouter] SLIP-10 Ed25519 (Phantom/Solflare compatible).`);
|
|
3678
|
+
console.log(`[ClawRouter]`);
|
|
3679
|
+
console.log(`[ClawRouter] If you had funds in the old wallet, run:`);
|
|
3680
|
+
console.log(`[ClawRouter] /wallet migrate-solana`);
|
|
3681
|
+
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`);
|
|
3682
|
+
console.log(`[ClawRouter]`);
|
|
3683
|
+
} catch {
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
2967
3686
|
async function resolveOrGenerateWalletKey() {
|
|
2968
3687
|
const saved = await loadSavedWallet();
|
|
2969
3688
|
if (saved) {
|
|
@@ -2971,13 +3690,19 @@ async function resolveOrGenerateWalletKey() {
|
|
|
2971
3690
|
const mnemonic = await loadMnemonic();
|
|
2972
3691
|
if (mnemonic) {
|
|
2973
3692
|
const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
|
|
2974
|
-
|
|
3693
|
+
const result2 = {
|
|
2975
3694
|
key: saved,
|
|
2976
3695
|
address: account.address,
|
|
2977
3696
|
source: "saved",
|
|
2978
3697
|
mnemonic,
|
|
2979
3698
|
solanaPrivateKeyBytes: solanaKeyBytes
|
|
2980
3699
|
};
|
|
3700
|
+
const legacyKeyBytes = deriveSolanaKeyBytesLegacy(mnemonic);
|
|
3701
|
+
if (Buffer.from(legacyKeyBytes).toString("hex") !== Buffer.from(solanaKeyBytes).toString("hex")) {
|
|
3702
|
+
result2.legacySolanaKeyBytes = legacyKeyBytes;
|
|
3703
|
+
await logMigrationWarning(legacyKeyBytes, solanaKeyBytes);
|
|
3704
|
+
}
|
|
3705
|
+
return result2;
|
|
2981
3706
|
}
|
|
2982
3707
|
return { key: saved, address: account.address, source: "saved" };
|
|
2983
3708
|
}
|
|
@@ -2987,13 +3712,19 @@ async function resolveOrGenerateWalletKey() {
|
|
|
2987
3712
|
const mnemonic = await loadMnemonic();
|
|
2988
3713
|
if (mnemonic) {
|
|
2989
3714
|
const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
|
|
2990
|
-
|
|
3715
|
+
const result2 = {
|
|
2991
3716
|
key: envKey,
|
|
2992
3717
|
address: account.address,
|
|
2993
3718
|
source: "env",
|
|
2994
3719
|
mnemonic,
|
|
2995
3720
|
solanaPrivateKeyBytes: solanaKeyBytes
|
|
2996
3721
|
};
|
|
3722
|
+
const legacyKeyBytes = deriveSolanaKeyBytesLegacy(mnemonic);
|
|
3723
|
+
if (Buffer.from(legacyKeyBytes).toString("hex") !== Buffer.from(solanaKeyBytes).toString("hex")) {
|
|
3724
|
+
result2.legacySolanaKeyBytes = legacyKeyBytes;
|
|
3725
|
+
await logMigrationWarning(legacyKeyBytes, solanaKeyBytes);
|
|
3726
|
+
}
|
|
3727
|
+
return result2;
|
|
2997
3728
|
}
|
|
2998
3729
|
return { key: envKey, address: account.address, source: "env" };
|
|
2999
3730
|
}
|
|
@@ -3767,8 +4498,9 @@ function shouldCompress(messages) {
|
|
|
3767
4498
|
}
|
|
3768
4499
|
|
|
3769
4500
|
// src/session.ts
|
|
4501
|
+
import { createHash as createHash3 } from "crypto";
|
|
3770
4502
|
var DEFAULT_SESSION_CONFIG = {
|
|
3771
|
-
enabled:
|
|
4503
|
+
enabled: true,
|
|
3772
4504
|
timeoutMs: 30 * 60 * 1e3,
|
|
3773
4505
|
// 30 minutes
|
|
3774
4506
|
headerName: "x-session-id"
|
|
@@ -3823,7 +4555,10 @@ var SessionStore = class {
|
|
|
3823
4555
|
tier,
|
|
3824
4556
|
createdAt: now,
|
|
3825
4557
|
lastUsedAt: now,
|
|
3826
|
-
requestCount: 1
|
|
4558
|
+
requestCount: 1,
|
|
4559
|
+
recentHashes: [],
|
|
4560
|
+
strikes: 0,
|
|
4561
|
+
escalated: false
|
|
3827
4562
|
});
|
|
3828
4563
|
}
|
|
3829
4564
|
}
|
|
@@ -3875,6 +4610,43 @@ var SessionStore = class {
|
|
|
3875
4610
|
}
|
|
3876
4611
|
}
|
|
3877
4612
|
}
|
|
4613
|
+
/**
|
|
4614
|
+
* Record a request content hash and detect repetitive patterns.
|
|
4615
|
+
* Returns true if escalation should be triggered (3+ consecutive similar requests).
|
|
4616
|
+
*/
|
|
4617
|
+
recordRequestHash(sessionId, hash) {
|
|
4618
|
+
const entry = this.sessions.get(sessionId);
|
|
4619
|
+
if (!entry) return false;
|
|
4620
|
+
const prev = entry.recentHashes;
|
|
4621
|
+
if (prev.length > 0 && prev[prev.length - 1] === hash) {
|
|
4622
|
+
entry.strikes++;
|
|
4623
|
+
} else {
|
|
4624
|
+
entry.strikes = 0;
|
|
4625
|
+
}
|
|
4626
|
+
entry.recentHashes.push(hash);
|
|
4627
|
+
if (entry.recentHashes.length > 3) {
|
|
4628
|
+
entry.recentHashes.shift();
|
|
4629
|
+
}
|
|
4630
|
+
return entry.strikes >= 2 && !entry.escalated;
|
|
4631
|
+
}
|
|
4632
|
+
/**
|
|
4633
|
+
* Escalate session to next tier. Returns the new model/tier or null if already at max.
|
|
4634
|
+
*/
|
|
4635
|
+
escalateSession(sessionId, tierConfigs) {
|
|
4636
|
+
const entry = this.sessions.get(sessionId);
|
|
4637
|
+
if (!entry) return null;
|
|
4638
|
+
const TIER_ORDER = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
|
|
4639
|
+
const currentIdx = TIER_ORDER.indexOf(entry.tier);
|
|
4640
|
+
if (currentIdx < 0 || currentIdx >= TIER_ORDER.length - 1) return null;
|
|
4641
|
+
const nextTier = TIER_ORDER[currentIdx + 1];
|
|
4642
|
+
const nextConfig = tierConfigs[nextTier];
|
|
4643
|
+
if (!nextConfig) return null;
|
|
4644
|
+
entry.model = nextConfig.primary;
|
|
4645
|
+
entry.tier = nextTier;
|
|
4646
|
+
entry.strikes = 0;
|
|
4647
|
+
entry.escalated = true;
|
|
4648
|
+
return { model: nextConfig.primary, tier: nextTier };
|
|
4649
|
+
}
|
|
3878
4650
|
/**
|
|
3879
4651
|
* Stop the cleanup interval.
|
|
3880
4652
|
*/
|
|
@@ -3895,6 +4667,17 @@ function getSessionId(headers, headerName = DEFAULT_SESSION_CONFIG.headerName) {
|
|
|
3895
4667
|
}
|
|
3896
4668
|
return void 0;
|
|
3897
4669
|
}
|
|
4670
|
+
function deriveSessionId(messages) {
|
|
4671
|
+
const firstUser = messages.find((m) => m.role === "user");
|
|
4672
|
+
if (!firstUser) return void 0;
|
|
4673
|
+
const content = typeof firstUser.content === "string" ? firstUser.content : JSON.stringify(firstUser.content);
|
|
4674
|
+
return createHash3("sha256").update(content).digest("hex").slice(0, 8);
|
|
4675
|
+
}
|
|
4676
|
+
function hashRequestContent(lastUserContent, toolCallNames) {
|
|
4677
|
+
const normalized = lastUserContent.replace(/\s+/g, " ").trim().slice(0, 500);
|
|
4678
|
+
const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(",")}` : "";
|
|
4679
|
+
return createHash3("sha256").update(normalized + toolSuffix).digest("hex").slice(0, 12);
|
|
4680
|
+
}
|
|
3898
4681
|
|
|
3899
4682
|
// src/updater.ts
|
|
3900
4683
|
var NPM_REGISTRY = "https://registry.npmjs.org/@blockrun/clawrouter/latest";
|
|
@@ -4652,13 +5435,36 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
|
|
|
4652
5435
|
}).catch(() => {
|
|
4653
5436
|
});
|
|
4654
5437
|
}
|
|
5438
|
+
async function uploadDataUriToHost(dataUri) {
|
|
5439
|
+
const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
5440
|
+
if (!match) throw new Error("Invalid data URI format");
|
|
5441
|
+
const [, mimeType, b64Data] = match;
|
|
5442
|
+
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
5443
|
+
const buffer = Buffer.from(b64Data, "base64");
|
|
5444
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
5445
|
+
const form = new FormData();
|
|
5446
|
+
form.append("reqtype", "fileupload");
|
|
5447
|
+
form.append("fileToUpload", blob, `image.${ext}`);
|
|
5448
|
+
const resp = await fetch("https://catbox.moe/user/api.php", {
|
|
5449
|
+
method: "POST",
|
|
5450
|
+
body: form
|
|
5451
|
+
});
|
|
5452
|
+
if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
|
|
5453
|
+
const result = await resp.text();
|
|
5454
|
+
if (result.startsWith("https://")) {
|
|
5455
|
+
return result.trim();
|
|
5456
|
+
}
|
|
5457
|
+
throw new Error(`catbox.moe upload failed: ${result}`);
|
|
5458
|
+
}
|
|
4655
5459
|
async function startProxy(options) {
|
|
4656
5460
|
const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
|
|
4657
5461
|
const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
|
|
4658
5462
|
const paymentChain = options.paymentChain ?? await resolvePaymentChain();
|
|
4659
5463
|
const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
4660
5464
|
if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
|
|
4661
|
-
console.warn(
|
|
5465
|
+
console.warn(
|
|
5466
|
+
`[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`
|
|
5467
|
+
);
|
|
4662
5468
|
} else if (paymentChain === "solana") {
|
|
4663
5469
|
console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
|
|
4664
5470
|
}
|
|
@@ -4666,7 +5472,6 @@ async function startProxy(options) {
|
|
|
4666
5472
|
const existingProxy = await checkExistingProxy(listenPort);
|
|
4667
5473
|
if (existingProxy) {
|
|
4668
5474
|
const account2 = privateKeyToAccount3(walletKey);
|
|
4669
|
-
const balanceMonitor2 = new BalanceMonitor(account2.address);
|
|
4670
5475
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
4671
5476
|
if (existingProxy.wallet !== account2.address) {
|
|
4672
5477
|
console.warn(
|
|
@@ -4680,7 +5485,9 @@ async function startProxy(options) {
|
|
|
4680
5485
|
);
|
|
4681
5486
|
}
|
|
4682
5487
|
} else if (paymentChain !== "base") {
|
|
4683
|
-
console.warn(
|
|
5488
|
+
console.warn(
|
|
5489
|
+
`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`
|
|
5490
|
+
);
|
|
4684
5491
|
throw new Error(
|
|
4685
5492
|
`Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
4686
5493
|
);
|
|
@@ -4691,6 +5498,7 @@ async function startProxy(options) {
|
|
|
4691
5498
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
|
|
4692
5499
|
reuseSolanaAddress = solanaSigner.address;
|
|
4693
5500
|
}
|
|
5501
|
+
const balanceMonitor2 = paymentChain === "solana" && reuseSolanaAddress ? new SolanaBalanceMonitor(reuseSolanaAddress) : new BalanceMonitor(account2.address);
|
|
4694
5502
|
options.onReady?.(listenPort);
|
|
4695
5503
|
return {
|
|
4696
5504
|
port: listenPort,
|
|
@@ -4721,8 +5529,10 @@ async function startProxy(options) {
|
|
|
4721
5529
|
const chain = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
|
|
4722
5530
|
console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);
|
|
4723
5531
|
});
|
|
4724
|
-
const payFetch = createPayFetchWithPreAuth(fetch, x402
|
|
4725
|
-
|
|
5532
|
+
const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
5533
|
+
skipPreAuth: paymentChain === "solana"
|
|
5534
|
+
});
|
|
5535
|
+
const balanceMonitor = paymentChain === "solana" && solanaAddress ? new SolanaBalanceMonitor(solanaAddress) : new BalanceMonitor(account.address);
|
|
4726
5536
|
const routingConfig = mergeRoutingConfig(options.routingConfig);
|
|
4727
5537
|
const modelPricing = buildModelPricing();
|
|
4728
5538
|
const routerOpts = {
|
|
@@ -4876,7 +5686,11 @@ async function startProxy(options) {
|
|
|
4876
5686
|
const existingProxy2 = await checkExistingProxy(listenPort);
|
|
4877
5687
|
if (existingProxy2) {
|
|
4878
5688
|
console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
|
|
4879
|
-
rejectAttempt({
|
|
5689
|
+
rejectAttempt({
|
|
5690
|
+
code: "REUSE_EXISTING",
|
|
5691
|
+
wallet: existingProxy2.wallet,
|
|
5692
|
+
existingChain: existingProxy2.paymentChain
|
|
5693
|
+
});
|
|
4880
5694
|
return;
|
|
4881
5695
|
}
|
|
4882
5696
|
if (attempt < PORT_RETRY_ATTEMPTS) {
|
|
@@ -4911,7 +5725,8 @@ async function startProxy(options) {
|
|
|
4911
5725
|
if (error.code === "REUSE_EXISTING" && error.wallet) {
|
|
4912
5726
|
if (error.existingChain && error.existingChain !== paymentChain) {
|
|
4913
5727
|
throw new Error(
|
|
4914
|
-
`Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port
|
|
5728
|
+
`Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`,
|
|
5729
|
+
{ cause: err }
|
|
4915
5730
|
);
|
|
4916
5731
|
}
|
|
4917
5732
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
@@ -5019,15 +5834,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
5019
5834
|
} catch {
|
|
5020
5835
|
}
|
|
5021
5836
|
try {
|
|
5022
|
-
const response = await payFetch(
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
signal
|
|
5029
|
-
}
|
|
5030
|
-
);
|
|
5837
|
+
const response = await payFetch(upstreamUrl, {
|
|
5838
|
+
method,
|
|
5839
|
+
headers,
|
|
5840
|
+
body: requestBody.length > 0 ? new Uint8Array(requestBody) : void 0,
|
|
5841
|
+
signal
|
|
5842
|
+
});
|
|
5031
5843
|
if (response.status !== 200) {
|
|
5032
5844
|
const errorBody = await response.text();
|
|
5033
5845
|
const isProviderErr = isProviderError(response.status, errorBody);
|
|
@@ -5075,14 +5887,19 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5075
5887
|
}
|
|
5076
5888
|
let body = Buffer.concat(bodyChunks);
|
|
5077
5889
|
const originalContextSizeKB = Math.ceil(body.length / 1024);
|
|
5890
|
+
const debugMode = req.headers["x-clawrouter-debug"] !== "false";
|
|
5078
5891
|
let routingDecision;
|
|
5892
|
+
let hasTools = false;
|
|
5893
|
+
let hasVision = false;
|
|
5079
5894
|
let isStreaming = false;
|
|
5080
5895
|
let modelId = "";
|
|
5081
5896
|
let maxTokens = 4096;
|
|
5082
5897
|
let routingProfile = null;
|
|
5083
5898
|
let accumulatedContent = "";
|
|
5899
|
+
let responseInputTokens;
|
|
5084
5900
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
5085
5901
|
const sessionId = getSessionId(req.headers);
|
|
5902
|
+
let effectiveSessionId = sessionId;
|
|
5086
5903
|
if (isChatCompletion && body.length > 0) {
|
|
5087
5904
|
try {
|
|
5088
5905
|
const parsed = JSON.parse(body.toString());
|
|
@@ -5090,10 +5907,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5090
5907
|
modelId = parsed.model || "";
|
|
5091
5908
|
maxTokens = parsed.max_tokens || 4096;
|
|
5092
5909
|
let bodyModified = false;
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5910
|
+
const parsedMessages = Array.isArray(parsed.messages) ? parsed.messages : [];
|
|
5911
|
+
const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === "user");
|
|
5912
|
+
const rawLastContent = lastUserMsg?.content;
|
|
5913
|
+
const lastContent = typeof rawLastContent === "string" ? rawLastContent : Array.isArray(rawLastContent) ? rawLastContent.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
|
|
5914
|
+
if (sessionId && parsedMessages.length > 0) {
|
|
5915
|
+
const messages = parsedMessages;
|
|
5097
5916
|
if (sessionJournal.needsContext(lastContent)) {
|
|
5098
5917
|
const journalText = sessionJournal.format(sessionId);
|
|
5099
5918
|
if (journalText) {
|
|
@@ -5114,6 +5933,303 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5114
5933
|
}
|
|
5115
5934
|
}
|
|
5116
5935
|
}
|
|
5936
|
+
if (lastContent.startsWith("/debug")) {
|
|
5937
|
+
const debugPrompt = lastContent.slice("/debug".length).trim() || "hello";
|
|
5938
|
+
const messages = parsed.messages;
|
|
5939
|
+
const systemMsg = messages?.find((m) => m.role === "system");
|
|
5940
|
+
const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
|
|
5941
|
+
const fullText = `${systemPrompt ?? ""} ${debugPrompt}`;
|
|
5942
|
+
const estimatedTokens = Math.ceil(fullText.length / 4);
|
|
5943
|
+
const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
|
|
5944
|
+
const profileName = normalizedModel2.replace("blockrun/", "");
|
|
5945
|
+
const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) ? profileName : "auto";
|
|
5946
|
+
const scoring = classifyByRules(
|
|
5947
|
+
debugPrompt,
|
|
5948
|
+
systemPrompt,
|
|
5949
|
+
estimatedTokens,
|
|
5950
|
+
DEFAULT_ROUTING_CONFIG.scoring
|
|
5951
|
+
);
|
|
5952
|
+
const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {
|
|
5953
|
+
...routerOpts,
|
|
5954
|
+
routingProfile: debugProfile
|
|
5955
|
+
});
|
|
5956
|
+
const dimLines = (scoring.dimensions ?? []).map((d) => {
|
|
5957
|
+
const nameStr = (d.name + ":").padEnd(24);
|
|
5958
|
+
const scoreStr = d.score.toFixed(2).padStart(6);
|
|
5959
|
+
const sigStr = d.signal ? ` [${d.signal}]` : "";
|
|
5960
|
+
return ` ${nameStr}${scoreStr}${sigStr}`;
|
|
5961
|
+
}).join("\n");
|
|
5962
|
+
const sess = sessionId ? sessionStore.getSession(sessionId) : void 0;
|
|
5963
|
+
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";
|
|
5964
|
+
const { simpleMedium, mediumComplex, complexReasoning } = DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;
|
|
5965
|
+
const debugText = [
|
|
5966
|
+
"ClawRouter Debug",
|
|
5967
|
+
"",
|
|
5968
|
+
`Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,
|
|
5969
|
+
`Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,
|
|
5970
|
+
`Reasoning: ${debugRouting.reasoning}`,
|
|
5971
|
+
"",
|
|
5972
|
+
`Scoring (weighted: ${scoring.score.toFixed(3)})`,
|
|
5973
|
+
dimLines,
|
|
5974
|
+
"",
|
|
5975
|
+
`Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,
|
|
5976
|
+
"",
|
|
5977
|
+
sessLine
|
|
5978
|
+
].join("\n");
|
|
5979
|
+
const completionId = `chatcmpl-debug-${Date.now()}`;
|
|
5980
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
5981
|
+
const syntheticResponse = {
|
|
5982
|
+
id: completionId,
|
|
5983
|
+
object: "chat.completion",
|
|
5984
|
+
created: timestamp,
|
|
5985
|
+
model: "clawrouter/debug",
|
|
5986
|
+
choices: [
|
|
5987
|
+
{
|
|
5988
|
+
index: 0,
|
|
5989
|
+
message: { role: "assistant", content: debugText },
|
|
5990
|
+
finish_reason: "stop"
|
|
5991
|
+
}
|
|
5992
|
+
],
|
|
5993
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
5994
|
+
};
|
|
5995
|
+
if (isStreaming) {
|
|
5996
|
+
res.writeHead(200, {
|
|
5997
|
+
"Content-Type": "text/event-stream",
|
|
5998
|
+
"Cache-Control": "no-cache",
|
|
5999
|
+
Connection: "keep-alive"
|
|
6000
|
+
});
|
|
6001
|
+
const sseChunk = {
|
|
6002
|
+
id: completionId,
|
|
6003
|
+
object: "chat.completion.chunk",
|
|
6004
|
+
created: timestamp,
|
|
6005
|
+
model: "clawrouter/debug",
|
|
6006
|
+
choices: [
|
|
6007
|
+
{
|
|
6008
|
+
index: 0,
|
|
6009
|
+
delta: { role: "assistant", content: debugText },
|
|
6010
|
+
finish_reason: null
|
|
6011
|
+
}
|
|
6012
|
+
]
|
|
6013
|
+
};
|
|
6014
|
+
const sseDone = {
|
|
6015
|
+
id: completionId,
|
|
6016
|
+
object: "chat.completion.chunk",
|
|
6017
|
+
created: timestamp,
|
|
6018
|
+
model: "clawrouter/debug",
|
|
6019
|
+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
|
|
6020
|
+
};
|
|
6021
|
+
res.write(`data: ${JSON.stringify(sseChunk)}
|
|
6022
|
+
|
|
6023
|
+
`);
|
|
6024
|
+
res.write(`data: ${JSON.stringify(sseDone)}
|
|
6025
|
+
|
|
6026
|
+
`);
|
|
6027
|
+
res.write("data: [DONE]\n\n");
|
|
6028
|
+
res.end();
|
|
6029
|
+
} else {
|
|
6030
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6031
|
+
res.end(JSON.stringify(syntheticResponse));
|
|
6032
|
+
}
|
|
6033
|
+
console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`);
|
|
6034
|
+
return;
|
|
6035
|
+
}
|
|
6036
|
+
if (lastContent.startsWith("/imagegen")) {
|
|
6037
|
+
const imageArgs = lastContent.slice("/imagegen".length).trim();
|
|
6038
|
+
let imageModel = "google/nano-banana";
|
|
6039
|
+
let imageSize = "1024x1024";
|
|
6040
|
+
let imagePrompt = imageArgs;
|
|
6041
|
+
const modelMatch = imageArgs.match(/--model\s+(\S+)/);
|
|
6042
|
+
if (modelMatch) {
|
|
6043
|
+
const raw = modelMatch[1];
|
|
6044
|
+
const IMAGE_MODEL_ALIASES = {
|
|
6045
|
+
"dall-e-3": "openai/dall-e-3",
|
|
6046
|
+
dalle3: "openai/dall-e-3",
|
|
6047
|
+
dalle: "openai/dall-e-3",
|
|
6048
|
+
"gpt-image": "openai/gpt-image-1",
|
|
6049
|
+
"gpt-image-1": "openai/gpt-image-1",
|
|
6050
|
+
flux: "black-forest/flux-1.1-pro",
|
|
6051
|
+
"flux-pro": "black-forest/flux-1.1-pro",
|
|
6052
|
+
banana: "google/nano-banana",
|
|
6053
|
+
"nano-banana": "google/nano-banana",
|
|
6054
|
+
"banana-pro": "google/nano-banana-pro",
|
|
6055
|
+
"nano-banana-pro": "google/nano-banana-pro"
|
|
6056
|
+
};
|
|
6057
|
+
imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;
|
|
6058
|
+
imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim();
|
|
6059
|
+
}
|
|
6060
|
+
const sizeMatch = imageArgs.match(/--size\s+(\d+x\d+)/);
|
|
6061
|
+
if (sizeMatch) {
|
|
6062
|
+
imageSize = sizeMatch[1];
|
|
6063
|
+
imagePrompt = imagePrompt.replace(/--size\s+\d+x\d+/, "").trim();
|
|
6064
|
+
}
|
|
6065
|
+
if (!imagePrompt) {
|
|
6066
|
+
const errorText = [
|
|
6067
|
+
"Usage: /imagegen <prompt>",
|
|
6068
|
+
"",
|
|
6069
|
+
"Options:",
|
|
6070
|
+
" --model <model> Model to use (default: nano-banana)",
|
|
6071
|
+
" --size <WxH> Image size (default: 1024x1024)",
|
|
6072
|
+
"",
|
|
6073
|
+
"Models:",
|
|
6074
|
+
" nano-banana Google Gemini Flash \u2014 $0.05/image",
|
|
6075
|
+
" banana-pro Google Gemini Pro \u2014 $0.10/image (up to 4K)",
|
|
6076
|
+
" dall-e-3 OpenAI DALL-E 3 \u2014 $0.04/image",
|
|
6077
|
+
" gpt-image OpenAI GPT Image 1 \u2014 $0.02/image",
|
|
6078
|
+
" flux Black Forest Flux 1.1 Pro \u2014 $0.04/image",
|
|
6079
|
+
"",
|
|
6080
|
+
"Examples:",
|
|
6081
|
+
" /imagegen a cat wearing sunglasses",
|
|
6082
|
+
" /imagegen --model dall-e-3 a futuristic city at sunset",
|
|
6083
|
+
" /imagegen --model banana-pro --size 2048x2048 mountain landscape"
|
|
6084
|
+
].join("\n");
|
|
6085
|
+
const completionId = `chatcmpl-image-${Date.now()}`;
|
|
6086
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
6087
|
+
if (isStreaming) {
|
|
6088
|
+
res.writeHead(200, {
|
|
6089
|
+
"Content-Type": "text/event-stream",
|
|
6090
|
+
"Cache-Control": "no-cache",
|
|
6091
|
+
Connection: "keep-alive"
|
|
6092
|
+
});
|
|
6093
|
+
res.write(
|
|
6094
|
+
`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 }] })}
|
|
6095
|
+
|
|
6096
|
+
`
|
|
6097
|
+
);
|
|
6098
|
+
res.write(
|
|
6099
|
+
`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
|
|
6100
|
+
|
|
6101
|
+
`
|
|
6102
|
+
);
|
|
6103
|
+
res.write("data: [DONE]\n\n");
|
|
6104
|
+
res.end();
|
|
6105
|
+
} else {
|
|
6106
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6107
|
+
res.end(
|
|
6108
|
+
JSON.stringify({
|
|
6109
|
+
id: completionId,
|
|
6110
|
+
object: "chat.completion",
|
|
6111
|
+
created: timestamp,
|
|
6112
|
+
model: "clawrouter/image",
|
|
6113
|
+
choices: [
|
|
6114
|
+
{
|
|
6115
|
+
index: 0,
|
|
6116
|
+
message: { role: "assistant", content: errorText },
|
|
6117
|
+
finish_reason: "stop"
|
|
6118
|
+
}
|
|
6119
|
+
],
|
|
6120
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
6121
|
+
})
|
|
6122
|
+
);
|
|
6123
|
+
}
|
|
6124
|
+
console.log(`[ClawRouter] /imagegen command \u2192 showing usage help`);
|
|
6125
|
+
return;
|
|
6126
|
+
}
|
|
6127
|
+
console.log(
|
|
6128
|
+
`[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`
|
|
6129
|
+
);
|
|
6130
|
+
try {
|
|
6131
|
+
const imageUpstreamUrl = `${apiBase}/v1/images/generations`;
|
|
6132
|
+
const imageBody = JSON.stringify({
|
|
6133
|
+
model: imageModel,
|
|
6134
|
+
prompt: imagePrompt,
|
|
6135
|
+
size: imageSize,
|
|
6136
|
+
n: 1
|
|
6137
|
+
});
|
|
6138
|
+
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
6139
|
+
method: "POST",
|
|
6140
|
+
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
6141
|
+
body: imageBody
|
|
6142
|
+
});
|
|
6143
|
+
const imageResult = await imageResponse.json();
|
|
6144
|
+
let responseText;
|
|
6145
|
+
if (!imageResponse.ok || imageResult.error) {
|
|
6146
|
+
const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`;
|
|
6147
|
+
responseText = `Image generation failed: ${errMsg}`;
|
|
6148
|
+
console.log(`[ClawRouter] /imagegen error: ${errMsg}`);
|
|
6149
|
+
} else {
|
|
6150
|
+
const images = imageResult.data ?? [];
|
|
6151
|
+
if (images.length === 0) {
|
|
6152
|
+
responseText = "Image generation returned no results.";
|
|
6153
|
+
} else {
|
|
6154
|
+
const lines = [];
|
|
6155
|
+
for (const img of images) {
|
|
6156
|
+
if (img.url) {
|
|
6157
|
+
if (img.url.startsWith("data:")) {
|
|
6158
|
+
try {
|
|
6159
|
+
const hostedUrl = await uploadDataUriToHost(img.url);
|
|
6160
|
+
lines.push(hostedUrl);
|
|
6161
|
+
} catch (uploadErr) {
|
|
6162
|
+
console.error(
|
|
6163
|
+
`[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
6164
|
+
);
|
|
6165
|
+
lines.push(
|
|
6166
|
+
"Image generated but upload failed. Try again or use --model dall-e-3."
|
|
6167
|
+
);
|
|
6168
|
+
}
|
|
6169
|
+
} else {
|
|
6170
|
+
lines.push(img.url);
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);
|
|
6174
|
+
}
|
|
6175
|
+
lines.push("", `Model: ${imageModel} | Size: ${imageSize}`);
|
|
6176
|
+
responseText = lines.join("\n");
|
|
6177
|
+
}
|
|
6178
|
+
console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
|
|
6179
|
+
}
|
|
6180
|
+
const completionId = `chatcmpl-image-${Date.now()}`;
|
|
6181
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
6182
|
+
if (isStreaming) {
|
|
6183
|
+
res.writeHead(200, {
|
|
6184
|
+
"Content-Type": "text/event-stream",
|
|
6185
|
+
"Cache-Control": "no-cache",
|
|
6186
|
+
Connection: "keep-alive"
|
|
6187
|
+
});
|
|
6188
|
+
res.write(
|
|
6189
|
+
`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 }] })}
|
|
6190
|
+
|
|
6191
|
+
`
|
|
6192
|
+
);
|
|
6193
|
+
res.write(
|
|
6194
|
+
`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
|
|
6195
|
+
|
|
6196
|
+
`
|
|
6197
|
+
);
|
|
6198
|
+
res.write("data: [DONE]\n\n");
|
|
6199
|
+
res.end();
|
|
6200
|
+
} else {
|
|
6201
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6202
|
+
res.end(
|
|
6203
|
+
JSON.stringify({
|
|
6204
|
+
id: completionId,
|
|
6205
|
+
object: "chat.completion",
|
|
6206
|
+
created: timestamp,
|
|
6207
|
+
model: "clawrouter/image",
|
|
6208
|
+
choices: [
|
|
6209
|
+
{
|
|
6210
|
+
index: 0,
|
|
6211
|
+
message: { role: "assistant", content: responseText },
|
|
6212
|
+
finish_reason: "stop"
|
|
6213
|
+
}
|
|
6214
|
+
],
|
|
6215
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
6216
|
+
})
|
|
6217
|
+
);
|
|
6218
|
+
}
|
|
6219
|
+
} catch (err) {
|
|
6220
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6221
|
+
console.error(`[ClawRouter] /imagegen error: ${errMsg}`);
|
|
6222
|
+
if (!res.headersSent) {
|
|
6223
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6224
|
+
res.end(
|
|
6225
|
+
JSON.stringify({
|
|
6226
|
+
error: { message: `Image generation failed: ${errMsg}`, type: "image_error" }
|
|
6227
|
+
})
|
|
6228
|
+
);
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
return;
|
|
6232
|
+
}
|
|
5117
6233
|
if (parsed.stream === true) {
|
|
5118
6234
|
parsed.stream = false;
|
|
5119
6235
|
bodyModified = true;
|
|
@@ -5154,54 +6270,118 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5154
6270
|
latencyMs: 0
|
|
5155
6271
|
});
|
|
5156
6272
|
} else {
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
const
|
|
6273
|
+
effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
|
|
6274
|
+
const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
|
|
6275
|
+
const rawPrompt = lastUserMsg?.content;
|
|
6276
|
+
const prompt = typeof rawPrompt === "string" ? rawPrompt : Array.isArray(rawPrompt) ? rawPrompt.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
|
|
6277
|
+
const systemMsg = parsedMessages.find((m) => m.role === "system");
|
|
6278
|
+
const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
|
|
6279
|
+
const tools = parsed.tools;
|
|
6280
|
+
hasTools = Array.isArray(tools) && tools.length > 0;
|
|
6281
|
+
if (hasTools && tools) {
|
|
6282
|
+
console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);
|
|
6283
|
+
}
|
|
6284
|
+
hasVision = parsedMessages.some((m) => {
|
|
6285
|
+
if (Array.isArray(m.content)) {
|
|
6286
|
+
return m.content.some((p) => p.type === "image_url");
|
|
6287
|
+
}
|
|
6288
|
+
return false;
|
|
6289
|
+
});
|
|
6290
|
+
if (hasVision) {
|
|
6291
|
+
console.log(`[ClawRouter] Vision content detected, filtering to vision-capable models`);
|
|
6292
|
+
}
|
|
6293
|
+
routingDecision = route(prompt, systemPrompt, maxTokens, {
|
|
6294
|
+
...routerOpts,
|
|
6295
|
+
routingProfile: routingProfile ?? void 0
|
|
6296
|
+
});
|
|
5161
6297
|
if (existingSession) {
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
6298
|
+
const tierRank = {
|
|
6299
|
+
SIMPLE: 0,
|
|
6300
|
+
MEDIUM: 1,
|
|
6301
|
+
COMPLEX: 2,
|
|
6302
|
+
REASONING: 3
|
|
6303
|
+
};
|
|
6304
|
+
const existingRank = tierRank[existingSession.tier] ?? 0;
|
|
6305
|
+
const newRank = tierRank[routingDecision.tier] ?? 0;
|
|
6306
|
+
if (newRank > existingRank) {
|
|
6307
|
+
console.log(
|
|
6308
|
+
`[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} \u2192 ${routingDecision.tier} (${routingDecision.model})`
|
|
6309
|
+
);
|
|
6310
|
+
parsed.model = routingDecision.model;
|
|
6311
|
+
modelId = routingDecision.model;
|
|
6312
|
+
bodyModified = true;
|
|
6313
|
+
if (effectiveSessionId) {
|
|
6314
|
+
sessionStore.setSession(
|
|
6315
|
+
effectiveSessionId,
|
|
6316
|
+
routingDecision.model,
|
|
6317
|
+
routingDecision.tier
|
|
6318
|
+
);
|
|
5178
6319
|
}
|
|
5179
|
-
}
|
|
5180
|
-
const systemMsg = messages?.find((m) => m.role === "system");
|
|
5181
|
-
const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
|
|
5182
|
-
const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
|
|
5183
|
-
const tools = parsed.tools;
|
|
5184
|
-
const hasTools = Array.isArray(tools) && tools.length > 0;
|
|
5185
|
-
if (hasTools && tools) {
|
|
6320
|
+
} else {
|
|
5186
6321
|
console.log(
|
|
5187
|
-
`[ClawRouter]
|
|
6322
|
+
`[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`
|
|
5188
6323
|
);
|
|
6324
|
+
parsed.model = existingSession.model;
|
|
6325
|
+
modelId = existingSession.model;
|
|
6326
|
+
bodyModified = true;
|
|
6327
|
+
sessionStore.touchSession(effectiveSessionId);
|
|
6328
|
+
routingDecision = {
|
|
6329
|
+
...routingDecision,
|
|
6330
|
+
model: existingSession.model,
|
|
6331
|
+
tier: existingSession.tier
|
|
6332
|
+
};
|
|
5189
6333
|
}
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
6334
|
+
const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
|
|
6335
|
+
const assistantToolCalls = lastAssistantMsg?.tool_calls;
|
|
6336
|
+
const toolCallNames = Array.isArray(assistantToolCalls) ? assistantToolCalls.map((tc) => tc.function?.name).filter((n) => Boolean(n)) : void 0;
|
|
6337
|
+
const contentHash = hashRequestContent(prompt, toolCallNames);
|
|
6338
|
+
const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
|
|
6339
|
+
if (shouldEscalate) {
|
|
6340
|
+
const activeTierConfigs = (() => {
|
|
6341
|
+
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
6342
|
+
return routerOpts.config.agenticTiers;
|
|
6343
|
+
}
|
|
6344
|
+
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
6345
|
+
return routerOpts.config.ecoTiers;
|
|
6346
|
+
}
|
|
6347
|
+
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
6348
|
+
return routerOpts.config.premiumTiers;
|
|
6349
|
+
}
|
|
6350
|
+
return routerOpts.config.tiers;
|
|
6351
|
+
})();
|
|
6352
|
+
const escalation = sessionStore.escalateSession(
|
|
6353
|
+
effectiveSessionId,
|
|
6354
|
+
activeTierConfigs
|
|
6355
|
+
);
|
|
6356
|
+
if (escalation) {
|
|
6357
|
+
console.log(
|
|
6358
|
+
`[ClawRouter] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`
|
|
6359
|
+
);
|
|
6360
|
+
parsed.model = escalation.model;
|
|
6361
|
+
modelId = escalation.model;
|
|
6362
|
+
routingDecision = {
|
|
6363
|
+
...routingDecision,
|
|
6364
|
+
model: escalation.model,
|
|
6365
|
+
tier: escalation.tier
|
|
6366
|
+
};
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
} else {
|
|
5194
6370
|
parsed.model = routingDecision.model;
|
|
5195
6371
|
modelId = routingDecision.model;
|
|
5196
6372
|
bodyModified = true;
|
|
5197
|
-
if (
|
|
5198
|
-
sessionStore.setSession(
|
|
6373
|
+
if (effectiveSessionId) {
|
|
6374
|
+
sessionStore.setSession(
|
|
6375
|
+
effectiveSessionId,
|
|
6376
|
+
routingDecision.model,
|
|
6377
|
+
routingDecision.tier
|
|
6378
|
+
);
|
|
5199
6379
|
console.log(
|
|
5200
|
-
`[ClawRouter] Session ${
|
|
6380
|
+
`[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`
|
|
5201
6381
|
);
|
|
5202
6382
|
}
|
|
5203
|
-
options.onRouted?.(routingDecision);
|
|
5204
6383
|
}
|
|
6384
|
+
options.onRouted?.(routingDecision);
|
|
5205
6385
|
}
|
|
5206
6386
|
}
|
|
5207
6387
|
if (bodyModified) {
|
|
@@ -5294,6 +6474,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5294
6474
|
}
|
|
5295
6475
|
deduplicator.markInflight(dedupKey);
|
|
5296
6476
|
let estimatedCostMicros;
|
|
6477
|
+
let balanceFallbackNotice;
|
|
5297
6478
|
const isFreeModel = modelId === FREE_MODEL;
|
|
5298
6479
|
if (modelId && !options.skipBalanceCheck && !isFreeModel) {
|
|
5299
6480
|
const estimated = estimateAmount(modelId, body.length, maxTokens);
|
|
@@ -5304,12 +6485,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5304
6485
|
if (sufficiency.info.isEmpty || !sufficiency.sufficient) {
|
|
5305
6486
|
const originalModel = modelId;
|
|
5306
6487
|
console.log(
|
|
5307
|
-
`[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (
|
|
6488
|
+
`[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
|
|
5308
6489
|
);
|
|
5309
6490
|
modelId = FREE_MODEL;
|
|
5310
6491
|
const parsed = JSON.parse(body.toString());
|
|
5311
6492
|
parsed.model = FREE_MODEL;
|
|
5312
6493
|
body = Buffer.from(JSON.stringify(parsed));
|
|
6494
|
+
balanceFallbackNotice = sufficiency.info.isEmpty ? `> **\u26A0\uFE0F Wallet empty** \u2014 using free model. Fund your wallet to use ${originalModel}.
|
|
6495
|
+
|
|
6496
|
+
` : `> **\u26A0\uFE0F Insufficient balance** (${sufficiency.info.balanceUSD}) \u2014 using free model instead of ${originalModel}.
|
|
6497
|
+
|
|
6498
|
+
`;
|
|
5313
6499
|
options.onLowBalance?.({
|
|
5314
6500
|
balanceUSD: sufficiency.info.balanceUSD,
|
|
5315
6501
|
walletAddress: sufficiency.info.walletAddress
|
|
@@ -5373,8 +6559,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5373
6559
|
if (routingDecision) {
|
|
5374
6560
|
const estimatedInputTokens = Math.ceil(body.length / 4);
|
|
5375
6561
|
const estimatedTotalTokens = estimatedInputTokens + maxTokens;
|
|
5376
|
-
const
|
|
5377
|
-
|
|
6562
|
+
const tierConfigs = (() => {
|
|
6563
|
+
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
6564
|
+
return routerOpts.config.agenticTiers;
|
|
6565
|
+
}
|
|
6566
|
+
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
6567
|
+
return routerOpts.config.ecoTiers;
|
|
6568
|
+
}
|
|
6569
|
+
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
6570
|
+
return routerOpts.config.premiumTiers;
|
|
6571
|
+
}
|
|
6572
|
+
return routerOpts.config.tiers;
|
|
6573
|
+
})();
|
|
5378
6574
|
const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);
|
|
5379
6575
|
const contextFiltered = getFallbackChainFiltered(
|
|
5380
6576
|
routingDecision.tier,
|
|
@@ -5388,14 +6584,27 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5388
6584
|
`[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`
|
|
5389
6585
|
);
|
|
5390
6586
|
}
|
|
5391
|
-
|
|
6587
|
+
const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);
|
|
6588
|
+
const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));
|
|
6589
|
+
if (toolExcluded.length > 0) {
|
|
6590
|
+
console.log(
|
|
6591
|
+
`[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`
|
|
6592
|
+
);
|
|
6593
|
+
}
|
|
6594
|
+
const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);
|
|
6595
|
+
const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));
|
|
6596
|
+
if (visionExcluded.length > 0) {
|
|
6597
|
+
console.log(
|
|
6598
|
+
`[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`
|
|
6599
|
+
);
|
|
6600
|
+
}
|
|
6601
|
+
modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);
|
|
5392
6602
|
modelsToTry = prioritizeNonRateLimited(modelsToTry);
|
|
5393
6603
|
} else {
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
}
|
|
6604
|
+
modelsToTry = modelId ? [modelId] : [];
|
|
6605
|
+
}
|
|
6606
|
+
if (!modelsToTry.includes(FREE_MODEL)) {
|
|
6607
|
+
modelsToTry.push(FREE_MODEL);
|
|
5399
6608
|
}
|
|
5400
6609
|
let upstream;
|
|
5401
6610
|
let lastError;
|
|
@@ -5429,6 +6638,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5429
6638
|
if (result.errorStatus === 429) {
|
|
5430
6639
|
markRateLimited(tryModel);
|
|
5431
6640
|
}
|
|
6641
|
+
const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
|
|
6642
|
+
result.errorBody || ""
|
|
6643
|
+
);
|
|
6644
|
+
if (isPaymentErr && tryModel !== FREE_MODEL) {
|
|
6645
|
+
const freeIdx = modelsToTry.indexOf(FREE_MODEL);
|
|
6646
|
+
if (freeIdx > i + 1) {
|
|
6647
|
+
console.log(`[ClawRouter] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
|
|
6648
|
+
i = freeIdx - 1;
|
|
6649
|
+
continue;
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
5432
6652
|
console.log(
|
|
5433
6653
|
`[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`
|
|
5434
6654
|
);
|
|
@@ -5446,6 +6666,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5446
6666
|
clearInterval(heartbeatInterval);
|
|
5447
6667
|
heartbeatInterval = void 0;
|
|
5448
6668
|
}
|
|
6669
|
+
if (debugMode && headersSentEarly && routingDecision) {
|
|
6670
|
+
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}
|
|
6671
|
+
|
|
6672
|
+
`;
|
|
6673
|
+
safeWrite(res, debugComment);
|
|
6674
|
+
}
|
|
5449
6675
|
if (routingDecision && actualModelUsed !== routingDecision.model) {
|
|
5450
6676
|
const estimatedInputTokens = Math.ceil(body.length / 4);
|
|
5451
6677
|
const newCosts = calculateModelCost(
|
|
@@ -5464,6 +6690,12 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5464
6690
|
savings: newCosts.savings
|
|
5465
6691
|
};
|
|
5466
6692
|
options.onRouted?.(routingDecision);
|
|
6693
|
+
if (effectiveSessionId) {
|
|
6694
|
+
sessionStore.setSession(effectiveSessionId, actualModelUsed, routingDecision.tier);
|
|
6695
|
+
console.log(
|
|
6696
|
+
`[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}`
|
|
6697
|
+
);
|
|
6698
|
+
}
|
|
5467
6699
|
}
|
|
5468
6700
|
if (!upstream) {
|
|
5469
6701
|
const rawErrBody = lastError?.body || "All models in fallback chain failed";
|
|
@@ -5526,6 +6758,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5526
6758
|
const jsonStr = jsonBody.toString();
|
|
5527
6759
|
try {
|
|
5528
6760
|
const rsp = JSON.parse(jsonStr);
|
|
6761
|
+
if (rsp.usage && typeof rsp.usage === "object") {
|
|
6762
|
+
const u = rsp.usage;
|
|
6763
|
+
if (typeof u.prompt_tokens === "number") responseInputTokens = u.prompt_tokens;
|
|
6764
|
+
}
|
|
5529
6765
|
const baseChunk = {
|
|
5530
6766
|
id: rsp.id ?? `chatcmpl-${Date.now()}`,
|
|
5531
6767
|
object: "chat.completion.chunk",
|
|
@@ -5551,6 +6787,25 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5551
6787
|
`;
|
|
5552
6788
|
safeWrite(res, roleData);
|
|
5553
6789
|
responseChunks.push(Buffer.from(roleData));
|
|
6790
|
+
if (balanceFallbackNotice) {
|
|
6791
|
+
const noticeChunk = {
|
|
6792
|
+
...baseChunk,
|
|
6793
|
+
choices: [
|
|
6794
|
+
{
|
|
6795
|
+
index,
|
|
6796
|
+
delta: { content: balanceFallbackNotice },
|
|
6797
|
+
logprobs: null,
|
|
6798
|
+
finish_reason: null
|
|
6799
|
+
}
|
|
6800
|
+
]
|
|
6801
|
+
};
|
|
6802
|
+
const noticeData = `data: ${JSON.stringify(noticeChunk)}
|
|
6803
|
+
|
|
6804
|
+
`;
|
|
6805
|
+
safeWrite(res, noticeData);
|
|
6806
|
+
responseChunks.push(Buffer.from(noticeData));
|
|
6807
|
+
balanceFallbackNotice = void 0;
|
|
6808
|
+
}
|
|
5554
6809
|
if (content) {
|
|
5555
6810
|
const contentChunk = {
|
|
5556
6811
|
...baseChunk,
|
|
@@ -5625,23 +6880,46 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5625
6880
|
});
|
|
5626
6881
|
responseHeaders["x-context-used-kb"] = String(originalContextSizeKB);
|
|
5627
6882
|
responseHeaders["x-context-limit-kb"] = String(CONTEXT_LIMIT_KB);
|
|
5628
|
-
|
|
6883
|
+
if (debugMode && routingDecision) {
|
|
6884
|
+
responseHeaders["x-clawrouter-profile"] = routingProfile ?? "auto";
|
|
6885
|
+
responseHeaders["x-clawrouter-tier"] = routingDecision.tier;
|
|
6886
|
+
responseHeaders["x-clawrouter-model"] = actualModelUsed;
|
|
6887
|
+
responseHeaders["x-clawrouter-confidence"] = routingDecision.confidence.toFixed(2);
|
|
6888
|
+
responseHeaders["x-clawrouter-reasoning"] = routingDecision.reasoning;
|
|
6889
|
+
if (routingDecision.agenticScore !== void 0) {
|
|
6890
|
+
responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
const bodyParts = [];
|
|
5629
6894
|
if (upstream.body) {
|
|
5630
6895
|
const reader = upstream.body.getReader();
|
|
5631
6896
|
try {
|
|
5632
6897
|
while (true) {
|
|
5633
6898
|
const { done, value } = await reader.read();
|
|
5634
6899
|
if (done) break;
|
|
5635
|
-
|
|
5636
|
-
safeWrite(res, chunk);
|
|
5637
|
-
responseChunks.push(chunk);
|
|
6900
|
+
bodyParts.push(Buffer.from(value));
|
|
5638
6901
|
}
|
|
5639
6902
|
} finally {
|
|
5640
6903
|
reader.releaseLock();
|
|
5641
6904
|
}
|
|
5642
6905
|
}
|
|
6906
|
+
let responseBody = Buffer.concat(bodyParts);
|
|
6907
|
+
if (balanceFallbackNotice && responseBody.length > 0) {
|
|
6908
|
+
try {
|
|
6909
|
+
const parsed = JSON.parse(responseBody.toString());
|
|
6910
|
+
if (parsed.choices?.[0]?.message?.content !== void 0) {
|
|
6911
|
+
parsed.choices[0].message.content = balanceFallbackNotice + parsed.choices[0].message.content;
|
|
6912
|
+
responseBody = Buffer.from(JSON.stringify(parsed));
|
|
6913
|
+
}
|
|
6914
|
+
} catch {
|
|
6915
|
+
}
|
|
6916
|
+
balanceFallbackNotice = void 0;
|
|
6917
|
+
}
|
|
6918
|
+
responseHeaders["content-length"] = String(responseBody.length);
|
|
6919
|
+
res.writeHead(upstream.status, responseHeaders);
|
|
6920
|
+
safeWrite(res, responseBody);
|
|
6921
|
+
responseChunks.push(responseBody);
|
|
5643
6922
|
res.end();
|
|
5644
|
-
const responseBody = Buffer.concat(responseChunks);
|
|
5645
6923
|
deduplicator.complete(dedupKey, {
|
|
5646
6924
|
status: upstream.status,
|
|
5647
6925
|
headers: responseHeaders,
|
|
@@ -5664,6 +6942,10 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5664
6942
|
if (rspJson.choices?.[0]?.message?.content) {
|
|
5665
6943
|
accumulatedContent = rspJson.choices[0].message.content;
|
|
5666
6944
|
}
|
|
6945
|
+
if (rspJson.usage && typeof rspJson.usage === "object") {
|
|
6946
|
+
if (typeof rspJson.usage.prompt_tokens === "number")
|
|
6947
|
+
responseInputTokens = rspJson.usage.prompt_tokens;
|
|
6948
|
+
}
|
|
5667
6949
|
} catch {
|
|
5668
6950
|
}
|
|
5669
6951
|
}
|
|
@@ -5689,7 +6971,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5689
6971
|
deduplicator.removeInflight(dedupKey);
|
|
5690
6972
|
balanceMonitor.invalidate();
|
|
5691
6973
|
if (err instanceof Error && err.name === "AbortError") {
|
|
5692
|
-
throw new Error(`Request timed out after ${timeoutMs}ms
|
|
6974
|
+
throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });
|
|
5693
6975
|
}
|
|
5694
6976
|
throw err;
|
|
5695
6977
|
}
|
|
@@ -5712,13 +6994,53 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5712
6994
|
cost: costWithBuffer,
|
|
5713
6995
|
baselineCost: baselineWithBuffer,
|
|
5714
6996
|
savings: accurateCosts.savings,
|
|
5715
|
-
latencyMs: Date.now() - startTime
|
|
6997
|
+
latencyMs: Date.now() - startTime,
|
|
6998
|
+
...responseInputTokens !== void 0 && { inputTokens: responseInputTokens }
|
|
5716
6999
|
};
|
|
5717
7000
|
logUsage(entry).catch(() => {
|
|
5718
7001
|
});
|
|
5719
7002
|
}
|
|
5720
7003
|
}
|
|
5721
7004
|
|
|
7005
|
+
// src/report.ts
|
|
7006
|
+
async function generateReport(period, json = false) {
|
|
7007
|
+
const days = period === "daily" ? 1 : period === "weekly" ? 7 : 30;
|
|
7008
|
+
const stats = await getStats(days);
|
|
7009
|
+
if (json) {
|
|
7010
|
+
return JSON.stringify(stats, null, 2);
|
|
7011
|
+
}
|
|
7012
|
+
return formatMarkdownReport(period, days, stats);
|
|
7013
|
+
}
|
|
7014
|
+
function formatMarkdownReport(period, days, stats) {
|
|
7015
|
+
const lines = [];
|
|
7016
|
+
lines.push(`# ClawRouter ${capitalize(period)} Report`);
|
|
7017
|
+
lines.push(`**Period:** Last ${days} day${days > 1 ? "s" : ""}`);
|
|
7018
|
+
lines.push(`**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
7019
|
+
lines.push("");
|
|
7020
|
+
lines.push("## \u{1F4CA} Usage Summary");
|
|
7021
|
+
lines.push("");
|
|
7022
|
+
lines.push(`| Metric | Value |`);
|
|
7023
|
+
lines.push(`|--------|-------|`);
|
|
7024
|
+
lines.push(`| Total Requests | ${stats.totalRequests} |`);
|
|
7025
|
+
lines.push(`| Total Cost | $${stats.totalCost.toFixed(4)} |`);
|
|
7026
|
+
lines.push(`| Baseline Cost | $${stats.totalBaselineCost.toFixed(4)} |`);
|
|
7027
|
+
lines.push(`| **Savings** | **$${stats.totalSavings.toFixed(4)}** |`);
|
|
7028
|
+
lines.push(`| Savings % | ${stats.savingsPercentage.toFixed(1)}% |`);
|
|
7029
|
+
lines.push(`| Avg Latency | ${stats.avgLatencyMs.toFixed(0)}ms |`);
|
|
7030
|
+
lines.push("");
|
|
7031
|
+
lines.push("## \u{1F916} Model Distribution");
|
|
7032
|
+
lines.push("");
|
|
7033
|
+
const sortedModels = Object.entries(stats.byModel).sort((a, b) => b[1].count - a[1].count).slice(0, 10);
|
|
7034
|
+
for (const [model, data] of sortedModels) {
|
|
7035
|
+
lines.push(`- ${model}: ${data.count} reqs, $${data.cost.toFixed(4)}`);
|
|
7036
|
+
}
|
|
7037
|
+
lines.push("");
|
|
7038
|
+
return lines.join("\n");
|
|
7039
|
+
}
|
|
7040
|
+
function capitalize(str) {
|
|
7041
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
7042
|
+
}
|
|
7043
|
+
|
|
5722
7044
|
// src/doctor.ts
|
|
5723
7045
|
import { platform, arch, freemem, totalmem } from "os";
|
|
5724
7046
|
import { createPublicClient as createPublicClient3, http as http3 } from "viem";
|
|
@@ -5811,7 +7133,6 @@ async function collectNetworkInfo() {
|
|
|
5811
7133
|
blockrunLatency = Date.now() - start;
|
|
5812
7134
|
blockrunReachable = response.ok || response.status === 402;
|
|
5813
7135
|
} catch {
|
|
5814
|
-
blockrunReachable = false;
|
|
5815
7136
|
}
|
|
5816
7137
|
let proxyRunning = false;
|
|
5817
7138
|
try {
|
|
@@ -5821,7 +7142,6 @@ async function collectNetworkInfo() {
|
|
|
5821
7142
|
});
|
|
5822
7143
|
proxyRunning = response.ok;
|
|
5823
7144
|
} catch {
|
|
5824
|
-
proxyRunning = false;
|
|
5825
7145
|
}
|
|
5826
7146
|
return {
|
|
5827
7147
|
blockrunApi: { reachable: blockrunReachable, latencyMs: blockrunLatency },
|
|
@@ -5946,28 +7266,26 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
|
|
|
5946
7266
|
const x402 = new x402Client2();
|
|
5947
7267
|
registerExactEvmScheme2(x402, { signer: evmSigner });
|
|
5948
7268
|
const paymentFetch = wrapFetchWithPayment(fetch, x402);
|
|
5949
|
-
const response = await paymentFetch(
|
|
5950
|
-
"
|
|
5951
|
-
{
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
role: "system",
|
|
5960
|
-
content: `You are a technical support expert for BlockRun and ClawRouter.
|
|
7269
|
+
const response = await paymentFetch("https://blockrun.ai/api/v1/chat/completions", {
|
|
7270
|
+
method: "POST",
|
|
7271
|
+
headers: { "Content-Type": "application/json" },
|
|
7272
|
+
body: JSON.stringify({
|
|
7273
|
+
model: modelConfig.id,
|
|
7274
|
+
stream: false,
|
|
7275
|
+
messages: [
|
|
7276
|
+
{
|
|
7277
|
+
role: "system",
|
|
7278
|
+
content: `You are a technical support expert for BlockRun and ClawRouter.
|
|
5961
7279
|
Analyze the diagnostics and:
|
|
5962
7280
|
1. Identify the root cause of any issues
|
|
5963
7281
|
2. Provide specific, actionable fix commands (bash)
|
|
5964
7282
|
3. Explain why the issue occurred briefly
|
|
5965
7283
|
4. Be concise but thorough
|
|
5966
7284
|
5. Format commands in code blocks`
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
7285
|
+
},
|
|
7286
|
+
{
|
|
7287
|
+
role: "user",
|
|
7288
|
+
content: userQuestion ? `Here are my system diagnostics:
|
|
5971
7289
|
|
|
5972
7290
|
${JSON.stringify(diagnostics, null, 2)}
|
|
5973
7291
|
|
|
@@ -5976,12 +7294,11 @@ User's question: ${userQuestion}` : `Here are my system diagnostics:
|
|
|
5976
7294
|
${JSON.stringify(diagnostics, null, 2)}
|
|
5977
7295
|
|
|
5978
7296
|
Please analyze and help me fix any issues.`
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
);
|
|
7297
|
+
}
|
|
7298
|
+
],
|
|
7299
|
+
max_tokens: 1e3
|
|
7300
|
+
})
|
|
7301
|
+
});
|
|
5985
7302
|
if (!response.ok) {
|
|
5986
7303
|
const text = await response.text();
|
|
5987
7304
|
console.log(`Error: ${response.status} - ${text}`);
|
|
@@ -6065,6 +7382,7 @@ Usage:
|
|
|
6065
7382
|
clawrouter [options]
|
|
6066
7383
|
clawrouter doctor [opus] [question]
|
|
6067
7384
|
clawrouter partners [test]
|
|
7385
|
+
clawrouter report [daily|weekly|monthly] [--json]
|
|
6068
7386
|
|
|
6069
7387
|
Options:
|
|
6070
7388
|
--version, -v Show version number
|
|
@@ -6107,6 +7425,9 @@ function parseArgs(args) {
|
|
|
6107
7425
|
doctor: false,
|
|
6108
7426
|
partners: false,
|
|
6109
7427
|
partnersTest: false,
|
|
7428
|
+
report: false,
|
|
7429
|
+
reportPeriod: "daily",
|
|
7430
|
+
reportJson: false,
|
|
6110
7431
|
port: void 0
|
|
6111
7432
|
};
|
|
6112
7433
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -6123,6 +7444,20 @@ function parseArgs(args) {
|
|
|
6123
7444
|
result.partnersTest = true;
|
|
6124
7445
|
i++;
|
|
6125
7446
|
}
|
|
7447
|
+
} else if (arg === "report") {
|
|
7448
|
+
result.report = true;
|
|
7449
|
+
const next = args[i + 1];
|
|
7450
|
+
if (next && ["daily", "weekly", "monthly"].includes(next)) {
|
|
7451
|
+
result.reportPeriod = next;
|
|
7452
|
+
i++;
|
|
7453
|
+
if (args[i + 1] === "--json") {
|
|
7454
|
+
result.reportJson = true;
|
|
7455
|
+
i++;
|
|
7456
|
+
}
|
|
7457
|
+
} else if (next === "--json") {
|
|
7458
|
+
result.reportJson = true;
|
|
7459
|
+
i++;
|
|
7460
|
+
}
|
|
6126
7461
|
} else if (arg === "--port" && args[i + 1]) {
|
|
6127
7462
|
result.port = parseInt(args[i + 1], 10);
|
|
6128
7463
|
i++;
|
|
@@ -6170,7 +7505,9 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
6170
7505
|
console.log(` ${svc.description}`);
|
|
6171
7506
|
console.log(` Tool: blockrun_${svc.id}`);
|
|
6172
7507
|
console.log(` Method: ${svc.method} /v1${svc.proxyPath}`);
|
|
6173
|
-
console.log(
|
|
7508
|
+
console.log(
|
|
7509
|
+
` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`
|
|
7510
|
+
);
|
|
6174
7511
|
console.log();
|
|
6175
7512
|
}
|
|
6176
7513
|
if (args.partnersTest) {
|
|
@@ -6191,6 +7528,11 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
6191
7528
|
}
|
|
6192
7529
|
process.exit(0);
|
|
6193
7530
|
}
|
|
7531
|
+
if (args.report) {
|
|
7532
|
+
const report = await generateReport(args.reportPeriod, args.reportJson);
|
|
7533
|
+
console.log(report);
|
|
7534
|
+
process.exit(0);
|
|
7535
|
+
}
|
|
6194
7536
|
const wallet = await resolveOrGenerateWalletKey();
|
|
6195
7537
|
if (wallet.source === "generated") {
|
|
6196
7538
|
console.log(`[ClawRouter] Generated new wallet: ${wallet.address}`);
|
|
@@ -6224,19 +7566,20 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
6224
7566
|
console.error(`[ClawRouter] Need help? Run: npx @blockrun/clawrouter doctor`);
|
|
6225
7567
|
}
|
|
6226
7568
|
});
|
|
6227
|
-
const
|
|
7569
|
+
const paymentChain = await resolvePaymentChain();
|
|
7570
|
+
const displayAddress = paymentChain === "solana" && proxy.solanaAddress ? proxy.solanaAddress : wallet.address;
|
|
6228
7571
|
try {
|
|
6229
|
-
const balance = await
|
|
7572
|
+
const balance = await proxy.balanceMonitor.checkBalance();
|
|
6230
7573
|
if (balance.isEmpty) {
|
|
6231
7574
|
console.log(`[ClawRouter] Wallet balance: $0.00 (using FREE model)`);
|
|
6232
|
-
console.log(`[ClawRouter] Fund wallet for premium models: ${
|
|
7575
|
+
console.log(`[ClawRouter] Fund wallet for premium models: ${displayAddress}`);
|
|
6233
7576
|
} else if (balance.isLow) {
|
|
6234
7577
|
console.log(`[ClawRouter] Wallet balance: ${balance.balanceUSD} (low)`);
|
|
6235
7578
|
} else {
|
|
6236
7579
|
console.log(`[ClawRouter] Wallet balance: ${balance.balanceUSD}`);
|
|
6237
7580
|
}
|
|
6238
7581
|
} catch {
|
|
6239
|
-
console.log(`[ClawRouter] Wallet: ${
|
|
7582
|
+
console.log(`[ClawRouter] Wallet: ${displayAddress} (balance check pending)`);
|
|
6240
7583
|
}
|
|
6241
7584
|
console.log(`[ClawRouter] Ready - Ctrl+C to stop`);
|
|
6242
7585
|
const shutdown = async (signal) => {
|
|
@@ -6261,4 +7604,9 @@ main().catch((err) => {
|
|
|
6261
7604
|
console.error(`[ClawRouter] Need help? Run: npx @blockrun/clawrouter doctor`);
|
|
6262
7605
|
process.exit(1);
|
|
6263
7606
|
});
|
|
7607
|
+
/*! Bundled license information:
|
|
7608
|
+
|
|
7609
|
+
@noble/hashes/esm/utils.js:
|
|
7610
|
+
(*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
7611
|
+
*/
|
|
6264
7612
|
//# sourceMappingURL=cli.js.map
|