@blockrun/clawrouter 0.12.9 → 0.12.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +94 -44
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +108 -44
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1284,6 +1284,12 @@ declare function getStats(days?: number): Promise<AggregatedStats>;
|
|
|
1284
1284
|
* Format stats as ASCII table for terminal display.
|
|
1285
1285
|
*/
|
|
1286
1286
|
declare function formatStatsAscii(stats: AggregatedStats): string;
|
|
1287
|
+
/**
|
|
1288
|
+
* Delete all usage log files, resetting stats to zero.
|
|
1289
|
+
*/
|
|
1290
|
+
declare function clearStats(): Promise<{
|
|
1291
|
+
deletedFiles: number;
|
|
1292
|
+
}>;
|
|
1287
1293
|
|
|
1288
1294
|
/**
|
|
1289
1295
|
* Partner Service Registry
|
|
@@ -1381,4 +1387,4 @@ declare function buildPartnerTools(proxyBaseUrl: string): PartnerToolDefinition[
|
|
|
1381
1387
|
|
|
1382
1388
|
declare const plugin: OpenClawPluginDefinition;
|
|
1383
1389
|
|
|
1384
|
-
export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, type CachedLLMResponse, type CachedResponse, type CheckResult, DEFAULT_RETRY_CONFIG, DEFAULT_ROUTING_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, type DerivedKeys, EmptyWalletError, FileSpendControlStorage, InMemorySpendControlStorage, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, OPENCLAW_MODELS, PARTNER_SERVICES, type PartnerServiceDefinition, type PartnerToolDefinition, type PaymentChain, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, type RoutingConfig, type RoutingDecision, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SolanaBalanceInfo, SolanaBalanceMonitor, SpendControl, type SpendControlOptions, type SpendControlStorage, type SpendLimits, type SpendRecord, type SpendWindow, type SpendingStatus, type SufficiencyResult, type SweepError, type SweepResult, type Tier, type UsageEntry, type WalletConfig, type WalletResolution, blockrunProvider, buildPartnerTools, buildProviderModels, calculateModelCost, plugin as default, deriveAllKeys, deriveEvmKey, deriveSolanaKeyBytes, deriveSolanaKeyBytesLegacy, fetchWithRetry, formatDuration, formatStatsAscii, generateWalletMnemonic, getAgenticModels, getFallbackChain, getFallbackChainFiltered, getModelContextWindow, getPartnerService, getProxyPort, getSessionId, getStats, hashRequestContent, isAgenticModel, isBalanceError, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, isValidMnemonic, loadPaymentChain, logUsage, resolveModelAlias, resolvePaymentChain, route, savePaymentChain, setupSolana, startProxy, sweepSolanaWallet };
|
|
1390
|
+
export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, type CachedLLMResponse, type CachedResponse, type CheckResult, DEFAULT_RETRY_CONFIG, DEFAULT_ROUTING_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, type DerivedKeys, EmptyWalletError, FileSpendControlStorage, InMemorySpendControlStorage, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, OPENCLAW_MODELS, PARTNER_SERVICES, type PartnerServiceDefinition, type PartnerToolDefinition, type PaymentChain, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, type RoutingConfig, type RoutingDecision, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SolanaBalanceInfo, SolanaBalanceMonitor, SpendControl, type SpendControlOptions, type SpendControlStorage, type SpendLimits, type SpendRecord, type SpendWindow, type SpendingStatus, type SufficiencyResult, type SweepError, type SweepResult, type Tier, type UsageEntry, type WalletConfig, type WalletResolution, blockrunProvider, buildPartnerTools, buildProviderModels, calculateModelCost, clearStats, plugin as default, deriveAllKeys, deriveEvmKey, deriveSolanaKeyBytes, deriveSolanaKeyBytesLegacy, fetchWithRetry, formatDuration, formatStatsAscii, generateWalletMnemonic, getAgenticModels, getFallbackChain, getFallbackChainFiltered, getModelContextWindow, getPartnerService, getProxyPort, getSessionId, getStats, hashRequestContent, isAgenticModel, isBalanceError, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, isValidMnemonic, loadPaymentChain, logUsage, resolveModelAlias, resolvePaymentChain, route, savePaymentChain, setupSolana, startProxy, sweepSolanaWallet };
|
package/dist/index.js
CHANGED
|
@@ -1662,7 +1662,12 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
|
|
|
1662
1662
|
const getHeader = (name) => response.headers.get(name);
|
|
1663
1663
|
let body;
|
|
1664
1664
|
try {
|
|
1665
|
-
const responseText = await
|
|
1665
|
+
const responseText = await Promise.race([
|
|
1666
|
+
response.text(),
|
|
1667
|
+
new Promise(
|
|
1668
|
+
(_, reject) => setTimeout(() => reject(new Error("Body read timeout")), 6e4)
|
|
1669
|
+
)
|
|
1670
|
+
]);
|
|
1666
1671
|
if (responseText) body = JSON.parse(responseText);
|
|
1667
1672
|
} catch {
|
|
1668
1673
|
}
|
|
@@ -3305,7 +3310,7 @@ async function logUsage(entry) {
|
|
|
3305
3310
|
}
|
|
3306
3311
|
|
|
3307
3312
|
// src/stats.ts
|
|
3308
|
-
import { readdir } from "fs/promises";
|
|
3313
|
+
import { readdir, unlink } from "fs/promises";
|
|
3309
3314
|
|
|
3310
3315
|
// src/fs-read.ts
|
|
3311
3316
|
import { open } from "fs/promises";
|
|
@@ -3545,6 +3550,16 @@ function formatStatsAscii(stats) {
|
|
|
3545
3550
|
lines.push("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
3546
3551
|
return lines.join("\n");
|
|
3547
3552
|
}
|
|
3553
|
+
async function clearStats() {
|
|
3554
|
+
try {
|
|
3555
|
+
const files = await readdir(LOG_DIR2);
|
|
3556
|
+
const logFiles = files.filter((f) => f.startsWith("usage-") && f.endsWith(".jsonl"));
|
|
3557
|
+
await Promise.all(logFiles.map((f) => unlink(join3(LOG_DIR2, f))));
|
|
3558
|
+
return { deletedFiles: logFiles.length };
|
|
3559
|
+
} catch {
|
|
3560
|
+
return { deletedFiles: 0 };
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3548
3563
|
|
|
3549
3564
|
// src/dedup.ts
|
|
3550
3565
|
import { createHash } from "crypto";
|
|
@@ -5456,6 +5471,27 @@ var HEALTH_CHECK_TIMEOUT_MS = 2e3;
|
|
|
5456
5471
|
var RATE_LIMIT_COOLDOWN_MS = 6e4;
|
|
5457
5472
|
var PORT_RETRY_ATTEMPTS = 5;
|
|
5458
5473
|
var PORT_RETRY_DELAY_MS = 1e3;
|
|
5474
|
+
var BODY_READ_TIMEOUT_MS = 6e4;
|
|
5475
|
+
async function readBodyWithTimeout(body, timeoutMs = BODY_READ_TIMEOUT_MS) {
|
|
5476
|
+
if (!body) return [];
|
|
5477
|
+
const reader = body.getReader();
|
|
5478
|
+
const chunks = [];
|
|
5479
|
+
try {
|
|
5480
|
+
while (true) {
|
|
5481
|
+
const result = await Promise.race([
|
|
5482
|
+
reader.read(),
|
|
5483
|
+
new Promise(
|
|
5484
|
+
(_, reject) => setTimeout(() => reject(new Error("Body read timeout")), timeoutMs)
|
|
5485
|
+
)
|
|
5486
|
+
]);
|
|
5487
|
+
if (result.done) break;
|
|
5488
|
+
chunks.push(result.value);
|
|
5489
|
+
}
|
|
5490
|
+
} finally {
|
|
5491
|
+
reader.releaseLock();
|
|
5492
|
+
}
|
|
5493
|
+
return chunks;
|
|
5494
|
+
}
|
|
5459
5495
|
function transformPaymentError(errorBody) {
|
|
5460
5496
|
try {
|
|
5461
5497
|
const parsed = JSON.parse(errorBody);
|
|
@@ -5495,9 +5531,21 @@ function transformPaymentError(errorBody) {
|
|
|
5495
5531
|
}
|
|
5496
5532
|
});
|
|
5497
5533
|
}
|
|
5534
|
+
if (innerJson.invalidReason === "transaction_simulation_failed") {
|
|
5535
|
+
console.error(
|
|
5536
|
+
`[ClawRouter] Solana transaction simulation failed: ${innerJson.invalidMessage || "unknown"}`
|
|
5537
|
+
);
|
|
5538
|
+
return JSON.stringify({
|
|
5539
|
+
error: {
|
|
5540
|
+
message: "Solana payment simulation failed. Retrying with a different model.",
|
|
5541
|
+
type: "transaction_simulation_failed",
|
|
5542
|
+
help: "This is usually temporary. If it persists, check your Solana USDC balance or try: /model free"
|
|
5543
|
+
}
|
|
5544
|
+
});
|
|
5545
|
+
}
|
|
5498
5546
|
}
|
|
5499
5547
|
}
|
|
5500
|
-
if (parsed.error === "Settlement failed" || parsed.details?.includes("Settlement failed")) {
|
|
5548
|
+
if (parsed.error === "Settlement failed" || parsed.error === "Payment settlement failed" || parsed.details?.includes("Settlement failed") || parsed.details?.includes("transaction_simulation_failed")) {
|
|
5501
5549
|
const details = parsed.details || "";
|
|
5502
5550
|
const gasError = details.includes("unable to estimate gas");
|
|
5503
5551
|
return JSON.stringify({
|
|
@@ -5943,15 +5991,9 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
|
|
|
5943
5991
|
});
|
|
5944
5992
|
res.writeHead(upstream.status, responseHeaders);
|
|
5945
5993
|
if (upstream.body) {
|
|
5946
|
-
const
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
const { done, value } = await reader.read();
|
|
5950
|
-
if (done) break;
|
|
5951
|
-
safeWrite(res, Buffer.from(value));
|
|
5952
|
-
}
|
|
5953
|
-
} finally {
|
|
5954
|
-
reader.releaseLock();
|
|
5994
|
+
const chunks = await readBodyWithTimeout(upstream.body);
|
|
5995
|
+
for (const chunk of chunks) {
|
|
5996
|
+
safeWrite(res, Buffer.from(chunk));
|
|
5955
5997
|
}
|
|
5956
5998
|
}
|
|
5957
5999
|
res.end();
|
|
@@ -5981,16 +6023,23 @@ async function uploadDataUriToHost(dataUri) {
|
|
|
5981
6023
|
const form = new FormData();
|
|
5982
6024
|
form.append("reqtype", "fileupload");
|
|
5983
6025
|
form.append("fileToUpload", blob, `image.${ext}`);
|
|
5984
|
-
const
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
6026
|
+
const uploadController = new AbortController();
|
|
6027
|
+
const uploadTimeout = setTimeout(() => uploadController.abort(), 3e4);
|
|
6028
|
+
try {
|
|
6029
|
+
const resp = await fetch("https://catbox.moe/user/api.php", {
|
|
6030
|
+
method: "POST",
|
|
6031
|
+
body: form,
|
|
6032
|
+
signal: uploadController.signal
|
|
6033
|
+
});
|
|
6034
|
+
if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
|
|
6035
|
+
const result = await resp.text();
|
|
6036
|
+
if (result.startsWith("https://")) {
|
|
6037
|
+
return result.trim();
|
|
6038
|
+
}
|
|
6039
|
+
throw new Error(`catbox.moe upload failed: ${result}`);
|
|
6040
|
+
} finally {
|
|
6041
|
+
clearTimeout(uploadTimeout);
|
|
5992
6042
|
}
|
|
5993
|
-
throw new Error(`catbox.moe upload failed: ${result}`);
|
|
5994
6043
|
}
|
|
5995
6044
|
async function startProxy(options) {
|
|
5996
6045
|
const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
|
|
@@ -6131,6 +6180,21 @@ async function startProxy(options) {
|
|
|
6131
6180
|
res.end(JSON.stringify(stats, null, 2));
|
|
6132
6181
|
return;
|
|
6133
6182
|
}
|
|
6183
|
+
if (req.url === "/stats" && req.method === "DELETE") {
|
|
6184
|
+
try {
|
|
6185
|
+
const result = await clearStats();
|
|
6186
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6187
|
+
res.end(JSON.stringify({ cleared: true, deletedFiles: result.deletedFiles }));
|
|
6188
|
+
} catch (err) {
|
|
6189
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6190
|
+
res.end(
|
|
6191
|
+
JSON.stringify({
|
|
6192
|
+
error: `Failed to clear stats: ${err instanceof Error ? err.message : String(err)}`
|
|
6193
|
+
})
|
|
6194
|
+
);
|
|
6195
|
+
}
|
|
6196
|
+
return;
|
|
6197
|
+
}
|
|
6134
6198
|
if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
|
|
6135
6199
|
try {
|
|
6136
6200
|
const url = new URL(req.url, "http://localhost");
|
|
@@ -6377,7 +6441,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
6377
6441
|
signal
|
|
6378
6442
|
});
|
|
6379
6443
|
if (response.status !== 200) {
|
|
6380
|
-
const
|
|
6444
|
+
const errorBodyChunks = await readBodyWithTimeout(response.body);
|
|
6445
|
+
const errorBody = Buffer.concat(errorBodyChunks).toString();
|
|
6381
6446
|
const isProviderErr = isProviderError(response.status, errorBody);
|
|
6382
6447
|
return {
|
|
6383
6448
|
success: false,
|
|
@@ -6389,7 +6454,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
6389
6454
|
const contentType = response.headers.get("content-type") || "";
|
|
6390
6455
|
if (contentType.includes("json") || contentType.includes("text")) {
|
|
6391
6456
|
try {
|
|
6392
|
-
const
|
|
6457
|
+
const clonedChunks = await readBodyWithTimeout(response.clone().body);
|
|
6458
|
+
const responseBody = Buffer.concat(clonedChunks).toString();
|
|
6393
6459
|
const degradedReason = detectDegradedSuccessResponse(responseBody);
|
|
6394
6460
|
if (degradedReason) {
|
|
6395
6461
|
return {
|
|
@@ -7190,7 +7256,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7190
7256
|
if (result.errorStatus === 429) {
|
|
7191
7257
|
markRateLimited(tryModel);
|
|
7192
7258
|
}
|
|
7193
|
-
const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
|
|
7259
|
+
const isPaymentErr = /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test(
|
|
7194
7260
|
result.errorBody || ""
|
|
7195
7261
|
);
|
|
7196
7262
|
if (isPaymentErr && tryModel !== FREE_MODEL) {
|
|
@@ -7295,17 +7361,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7295
7361
|
const responseChunks = [];
|
|
7296
7362
|
if (headersSentEarly) {
|
|
7297
7363
|
if (upstream.body) {
|
|
7298
|
-
const
|
|
7299
|
-
const chunks = [];
|
|
7300
|
-
try {
|
|
7301
|
-
while (true) {
|
|
7302
|
-
const { done, value } = await reader.read();
|
|
7303
|
-
if (done) break;
|
|
7304
|
-
chunks.push(value);
|
|
7305
|
-
}
|
|
7306
|
-
} finally {
|
|
7307
|
-
reader.releaseLock();
|
|
7308
|
-
}
|
|
7364
|
+
const chunks = await readBodyWithTimeout(upstream.body);
|
|
7309
7365
|
const jsonBody = Buffer.concat(chunks);
|
|
7310
7366
|
const jsonStr = jsonBody.toString();
|
|
7311
7367
|
try {
|
|
@@ -7444,15 +7500,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
7444
7500
|
}
|
|
7445
7501
|
const bodyParts = [];
|
|
7446
7502
|
if (upstream.body) {
|
|
7447
|
-
const
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
const { done, value } = await reader.read();
|
|
7451
|
-
if (done) break;
|
|
7452
|
-
bodyParts.push(Buffer.from(value));
|
|
7453
|
-
}
|
|
7454
|
-
} finally {
|
|
7455
|
-
reader.releaseLock();
|
|
7503
|
+
const chunks = await readBodyWithTimeout(upstream.body);
|
|
7504
|
+
for (const chunk of chunks) {
|
|
7505
|
+
bodyParts.push(Buffer.from(chunk));
|
|
7456
7506
|
}
|
|
7457
7507
|
}
|
|
7458
7508
|
let responseBody = Buffer.concat(bodyParts);
|
|
@@ -8293,6 +8343,19 @@ async function createStatsCommand() {
|
|
|
8293
8343
|
requireAuth: false,
|
|
8294
8344
|
handler: async (ctx) => {
|
|
8295
8345
|
const arg = ctx.args?.trim().toLowerCase() || "7";
|
|
8346
|
+
if (arg === "clear" || arg === "reset") {
|
|
8347
|
+
try {
|
|
8348
|
+
const { deletedFiles } = await clearStats();
|
|
8349
|
+
return {
|
|
8350
|
+
text: `Stats cleared \u2014 ${deletedFiles} log file(s) deleted. Fresh start!`
|
|
8351
|
+
};
|
|
8352
|
+
} catch (err) {
|
|
8353
|
+
return {
|
|
8354
|
+
text: `Failed to clear stats: ${err instanceof Error ? err.message : String(err)}`,
|
|
8355
|
+
isError: true
|
|
8356
|
+
};
|
|
8357
|
+
}
|
|
8358
|
+
}
|
|
8296
8359
|
const days = parseInt(arg, 10) || 7;
|
|
8297
8360
|
try {
|
|
8298
8361
|
const stats = await getStats(Math.min(days, 30));
|
|
@@ -8761,6 +8824,7 @@ export {
|
|
|
8761
8824
|
buildPartnerTools,
|
|
8762
8825
|
buildProviderModels,
|
|
8763
8826
|
calculateModelCost,
|
|
8827
|
+
clearStats,
|
|
8764
8828
|
index_default as default,
|
|
8765
8829
|
deriveAllKeys,
|
|
8766
8830
|
deriveEvmKey,
|