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