@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/cli.js CHANGED
@@ -45,7 +45,12 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
45
45
  const getHeader = (name) => response.headers.get(name);
46
46
  let body;
47
47
  try {
48
- const responseText = await response.text();
48
+ const responseText = await Promise.race([
49
+ response.text(),
50
+ new Promise(
51
+ (_, reject) => setTimeout(() => reject(new Error("Body read timeout")), 6e4)
52
+ )
53
+ ]);
49
54
  if (responseText) body = JSON.parse(responseText);
50
55
  } catch {
51
56
  }
@@ -4982,6 +4987,27 @@ var HEALTH_CHECK_TIMEOUT_MS = 2e3;
4982
4987
  var RATE_LIMIT_COOLDOWN_MS = 6e4;
4983
4988
  var PORT_RETRY_ATTEMPTS = 5;
4984
4989
  var PORT_RETRY_DELAY_MS = 1e3;
4990
+ var BODY_READ_TIMEOUT_MS = 6e4;
4991
+ async function readBodyWithTimeout(body, timeoutMs = BODY_READ_TIMEOUT_MS) {
4992
+ if (!body) return [];
4993
+ const reader = body.getReader();
4994
+ const chunks = [];
4995
+ try {
4996
+ while (true) {
4997
+ const result = await Promise.race([
4998
+ reader.read(),
4999
+ new Promise(
5000
+ (_, reject) => setTimeout(() => reject(new Error("Body read timeout")), timeoutMs)
5001
+ )
5002
+ ]);
5003
+ if (result.done) break;
5004
+ chunks.push(result.value);
5005
+ }
5006
+ } finally {
5007
+ reader.releaseLock();
5008
+ }
5009
+ return chunks;
5010
+ }
4985
5011
  function transformPaymentError(errorBody) {
4986
5012
  try {
4987
5013
  const parsed = JSON.parse(errorBody);
@@ -5021,9 +5047,21 @@ function transformPaymentError(errorBody) {
5021
5047
  }
5022
5048
  });
5023
5049
  }
5050
+ if (innerJson.invalidReason === "transaction_simulation_failed") {
5051
+ console.error(
5052
+ `[ClawRouter] Solana transaction simulation failed: ${innerJson.invalidMessage || "unknown"}`
5053
+ );
5054
+ return JSON.stringify({
5055
+ error: {
5056
+ message: "Solana payment simulation failed. Retrying with a different model.",
5057
+ type: "transaction_simulation_failed",
5058
+ help: "This is usually temporary. If it persists, check your Solana USDC balance or try: /model free"
5059
+ }
5060
+ });
5061
+ }
5024
5062
  }
5025
5063
  }
5026
- if (parsed.error === "Settlement failed" || parsed.details?.includes("Settlement failed")) {
5064
+ if (parsed.error === "Settlement failed" || parsed.error === "Payment settlement failed" || parsed.details?.includes("Settlement failed") || parsed.details?.includes("transaction_simulation_failed")) {
5027
5065
  const details = parsed.details || "";
5028
5066
  const gasError = details.includes("unable to estimate gas");
5029
5067
  return JSON.stringify({
@@ -5469,15 +5507,9 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
5469
5507
  });
5470
5508
  res.writeHead(upstream.status, responseHeaders);
5471
5509
  if (upstream.body) {
5472
- const reader = upstream.body.getReader();
5473
- try {
5474
- while (true) {
5475
- const { done, value } = await reader.read();
5476
- if (done) break;
5477
- safeWrite(res, Buffer.from(value));
5478
- }
5479
- } finally {
5480
- reader.releaseLock();
5510
+ const chunks = await readBodyWithTimeout(upstream.body);
5511
+ for (const chunk of chunks) {
5512
+ safeWrite(res, Buffer.from(chunk));
5481
5513
  }
5482
5514
  }
5483
5515
  res.end();
@@ -5507,16 +5539,23 @@ async function uploadDataUriToHost(dataUri) {
5507
5539
  const form = new FormData();
5508
5540
  form.append("reqtype", "fileupload");
5509
5541
  form.append("fileToUpload", blob, `image.${ext}`);
5510
- const resp = await fetch("https://catbox.moe/user/api.php", {
5511
- method: "POST",
5512
- body: form
5513
- });
5514
- if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
5515
- const result = await resp.text();
5516
- if (result.startsWith("https://")) {
5517
- return result.trim();
5542
+ const uploadController = new AbortController();
5543
+ const uploadTimeout = setTimeout(() => uploadController.abort(), 3e4);
5544
+ try {
5545
+ const resp = await fetch("https://catbox.moe/user/api.php", {
5546
+ method: "POST",
5547
+ body: form,
5548
+ signal: uploadController.signal
5549
+ });
5550
+ if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);
5551
+ const result = await resp.text();
5552
+ if (result.startsWith("https://")) {
5553
+ return result.trim();
5554
+ }
5555
+ throw new Error(`catbox.moe upload failed: ${result}`);
5556
+ } finally {
5557
+ clearTimeout(uploadTimeout);
5518
5558
  }
5519
- throw new Error(`catbox.moe upload failed: ${result}`);
5520
5559
  }
5521
5560
  async function startProxy(options) {
5522
5561
  const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
@@ -5918,7 +5957,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5918
5957
  signal
5919
5958
  });
5920
5959
  if (response.status !== 200) {
5921
- const errorBody = await response.text();
5960
+ const errorBodyChunks = await readBodyWithTimeout(response.body);
5961
+ const errorBody = Buffer.concat(errorBodyChunks).toString();
5922
5962
  const isProviderErr = isProviderError(response.status, errorBody);
5923
5963
  return {
5924
5964
  success: false,
@@ -5930,7 +5970,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5930
5970
  const contentType = response.headers.get("content-type") || "";
5931
5971
  if (contentType.includes("json") || contentType.includes("text")) {
5932
5972
  try {
5933
- const responseBody = await response.clone().text();
5973
+ const clonedChunks = await readBodyWithTimeout(response.clone().body);
5974
+ const responseBody = Buffer.concat(clonedChunks).toString();
5934
5975
  const degradedReason = detectDegradedSuccessResponse(responseBody);
5935
5976
  if (degradedReason) {
5936
5977
  return {
@@ -6730,8 +6771,22 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6730
6771
  if (result.isProviderError && !isLastAttempt) {
6731
6772
  if (result.errorStatus === 429) {
6732
6773
  markRateLimited(tryModel);
6774
+ try {
6775
+ const parsed = JSON.parse(result.errorBody || "{}");
6776
+ if (parsed.update_available) {
6777
+ console.log("");
6778
+ console.log(
6779
+ `\x1B[33m\u2B06\uFE0F ClawRouter ${parsed.update_available} available (you have ${VERSION})\x1B[0m`
6780
+ );
6781
+ console.log(
6782
+ ` Run: \x1B[36mcurl -fsSL ${parsed.update_url || "https://blockrun.ai/ClawRouter-update"} | bash\x1B[0m`
6783
+ );
6784
+ console.log("");
6785
+ }
6786
+ } catch {
6787
+ }
6733
6788
  }
6734
- const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
6789
+ const isPaymentErr = /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test(
6735
6790
  result.errorBody || ""
6736
6791
  );
6737
6792
  if (isPaymentErr && tryModel !== FREE_MODEL) {
@@ -6836,17 +6891,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6836
6891
  const responseChunks = [];
6837
6892
  if (headersSentEarly) {
6838
6893
  if (upstream.body) {
6839
- const reader = upstream.body.getReader();
6840
- const chunks = [];
6841
- try {
6842
- while (true) {
6843
- const { done, value } = await reader.read();
6844
- if (done) break;
6845
- chunks.push(value);
6846
- }
6847
- } finally {
6848
- reader.releaseLock();
6849
- }
6894
+ const chunks = await readBodyWithTimeout(upstream.body);
6850
6895
  const jsonBody = Buffer.concat(chunks);
6851
6896
  const jsonStr = jsonBody.toString();
6852
6897
  try {
@@ -6985,15 +7030,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6985
7030
  }
6986
7031
  const bodyParts = [];
6987
7032
  if (upstream.body) {
6988
- const reader = upstream.body.getReader();
6989
- try {
6990
- while (true) {
6991
- const { done, value } = await reader.read();
6992
- if (done) break;
6993
- bodyParts.push(Buffer.from(value));
6994
- }
6995
- } finally {
6996
- reader.releaseLock();
7033
+ const chunks = await readBodyWithTimeout(upstream.body);
7034
+ for (const chunk of chunks) {
7035
+ bodyParts.push(Buffer.from(chunk));
6997
7036
  }
6998
7037
  }
6999
7038
  let responseBody = Buffer.concat(bodyParts);