@blockrun/clawrouter 0.12.10 → 0.12.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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
  }
@@ -5466,6 +5471,27 @@ var HEALTH_CHECK_TIMEOUT_MS = 2e3;
5466
5471
  var RATE_LIMIT_COOLDOWN_MS = 6e4;
5467
5472
  var PORT_RETRY_ATTEMPTS = 5;
5468
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
+ }
5469
5495
  function transformPaymentError(errorBody) {
5470
5496
  try {
5471
5497
  const parsed = JSON.parse(errorBody);
@@ -5505,9 +5531,21 @@ function transformPaymentError(errorBody) {
5505
5531
  }
5506
5532
  });
5507
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
+ }
5508
5546
  }
5509
5547
  }
5510
- 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")) {
5511
5549
  const details = parsed.details || "";
5512
5550
  const gasError = details.includes("unable to estimate gas");
5513
5551
  return JSON.stringify({
@@ -5953,15 +5991,9 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
5953
5991
  });
5954
5992
  res.writeHead(upstream.status, responseHeaders);
5955
5993
  if (upstream.body) {
5956
- const reader = upstream.body.getReader();
5957
- try {
5958
- while (true) {
5959
- const { done, value } = await reader.read();
5960
- if (done) break;
5961
- safeWrite(res, Buffer.from(value));
5962
- }
5963
- } finally {
5964
- reader.releaseLock();
5994
+ const chunks = await readBodyWithTimeout(upstream.body);
5995
+ for (const chunk of chunks) {
5996
+ safeWrite(res, Buffer.from(chunk));
5965
5997
  }
5966
5998
  }
5967
5999
  res.end();
@@ -5991,16 +6023,23 @@ async function uploadDataUriToHost(dataUri) {
5991
6023
  const form = new FormData();
5992
6024
  form.append("reqtype", "fileupload");
5993
6025
  form.append("fileToUpload", blob, `image.${ext}`);
5994
- const resp = await fetch("https://catbox.moe/user/api.php", {
5995
- method: "POST",
5996
- body: form
5997
- });
5998
- if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
5999
- const result = await resp.text();
6000
- if (result.startsWith("https://")) {
6001
- 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);
6002
6042
  }
6003
- throw new Error(`catbox.moe upload failed: ${result}`);
6004
6043
  }
6005
6044
  async function startProxy(options) {
6006
6045
  const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
@@ -6402,7 +6441,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
6402
6441
  signal
6403
6442
  });
6404
6443
  if (response.status !== 200) {
6405
- const errorBody = await response.text();
6444
+ const errorBodyChunks = await readBodyWithTimeout(response.body);
6445
+ const errorBody = Buffer.concat(errorBodyChunks).toString();
6406
6446
  const isProviderErr = isProviderError(response.status, errorBody);
6407
6447
  return {
6408
6448
  success: false,
@@ -6414,7 +6454,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
6414
6454
  const contentType = response.headers.get("content-type") || "";
6415
6455
  if (contentType.includes("json") || contentType.includes("text")) {
6416
6456
  try {
6417
- const responseBody = await response.clone().text();
6457
+ const clonedChunks = await readBodyWithTimeout(response.clone().body);
6458
+ const responseBody = Buffer.concat(clonedChunks).toString();
6418
6459
  const degradedReason = detectDegradedSuccessResponse(responseBody);
6419
6460
  if (degradedReason) {
6420
6461
  return {
@@ -7214,8 +7255,22 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
7214
7255
  if (result.isProviderError && !isLastAttempt) {
7215
7256
  if (result.errorStatus === 429) {
7216
7257
  markRateLimited(tryModel);
7258
+ try {
7259
+ const parsed = JSON.parse(result.errorBody || "{}");
7260
+ if (parsed.update_available) {
7261
+ console.log("");
7262
+ console.log(
7263
+ `\x1B[33m\u2B06\uFE0F ClawRouter ${parsed.update_available} available (you have ${VERSION})\x1B[0m`
7264
+ );
7265
+ console.log(
7266
+ ` Run: \x1B[36mcurl -fsSL ${parsed.update_url || "https://blockrun.ai/ClawRouter-update"} | bash\x1B[0m`
7267
+ );
7268
+ console.log("");
7269
+ }
7270
+ } catch {
7271
+ }
7217
7272
  }
7218
- const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
7273
+ const isPaymentErr = /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test(
7219
7274
  result.errorBody || ""
7220
7275
  );
7221
7276
  if (isPaymentErr && tryModel !== FREE_MODEL) {
@@ -7320,17 +7375,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
7320
7375
  const responseChunks = [];
7321
7376
  if (headersSentEarly) {
7322
7377
  if (upstream.body) {
7323
- const reader = upstream.body.getReader();
7324
- const chunks = [];
7325
- try {
7326
- while (true) {
7327
- const { done, value } = await reader.read();
7328
- if (done) break;
7329
- chunks.push(value);
7330
- }
7331
- } finally {
7332
- reader.releaseLock();
7333
- }
7378
+ const chunks = await readBodyWithTimeout(upstream.body);
7334
7379
  const jsonBody = Buffer.concat(chunks);
7335
7380
  const jsonStr = jsonBody.toString();
7336
7381
  try {
@@ -7469,15 +7514,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
7469
7514
  }
7470
7515
  const bodyParts = [];
7471
7516
  if (upstream.body) {
7472
- const reader = upstream.body.getReader();
7473
- try {
7474
- while (true) {
7475
- const { done, value } = await reader.read();
7476
- if (done) break;
7477
- bodyParts.push(Buffer.from(value));
7478
- }
7479
- } finally {
7480
- reader.releaseLock();
7517
+ const chunks = await readBodyWithTimeout(upstream.body);
7518
+ for (const chunk of chunks) {
7519
+ bodyParts.push(Buffer.from(chunk));
7481
7520
  }
7482
7521
  }
7483
7522
  let responseBody = Buffer.concat(bodyParts);