@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/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 response.text();
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 reader = upstream.body.getReader();
5947
- try {
5948
- while (true) {
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 resp = await fetch("https://catbox.moe/user/api.php", {
5985
- method: "POST",
5986
- body: form
5987
- });
5988
- if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
5989
- const result = await resp.text();
5990
- if (result.startsWith("https://")) {
5991
- return result.trim();
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 errorBody = await response.text();
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 responseBody = await response.clone().text();
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 reader = upstream.body.getReader();
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 reader = upstream.body.getReader();
7448
- try {
7449
- while (true) {
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,