@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/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
|
|
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
|
}
|
|
@@ -2317,7 +2322,7 @@ async function logUsage(entry) {
|
|
|
2317
2322
|
}
|
|
2318
2323
|
|
|
2319
2324
|
// src/stats.ts
|
|
2320
|
-
import { readdir } from "fs/promises";
|
|
2325
|
+
import { readdir, unlink } from "fs/promises";
|
|
2321
2326
|
|
|
2322
2327
|
// src/fs-read.ts
|
|
2323
2328
|
import { open } from "fs/promises";
|
|
@@ -2486,6 +2491,16 @@ async function getStats(days = 7) {
|
|
|
2486
2491
|
// How many entries have valid baseline tracking
|
|
2487
2492
|
};
|
|
2488
2493
|
}
|
|
2494
|
+
async function clearStats() {
|
|
2495
|
+
try {
|
|
2496
|
+
const files = await readdir(LOG_DIR2);
|
|
2497
|
+
const logFiles = files.filter((f) => f.startsWith("usage-") && f.endsWith(".jsonl"));
|
|
2498
|
+
await Promise.all(logFiles.map((f) => unlink(join3(LOG_DIR2, f))));
|
|
2499
|
+
return { deletedFiles: logFiles.length };
|
|
2500
|
+
} catch {
|
|
2501
|
+
return { deletedFiles: 0 };
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2489
2504
|
|
|
2490
2505
|
// src/dedup.ts
|
|
2491
2506
|
import { createHash } from "crypto";
|
|
@@ -4972,6 +4987,27 @@ var HEALTH_CHECK_TIMEOUT_MS = 2e3;
|
|
|
4972
4987
|
var RATE_LIMIT_COOLDOWN_MS = 6e4;
|
|
4973
4988
|
var PORT_RETRY_ATTEMPTS = 5;
|
|
4974
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
|
+
}
|
|
4975
5011
|
function transformPaymentError(errorBody) {
|
|
4976
5012
|
try {
|
|
4977
5013
|
const parsed = JSON.parse(errorBody);
|
|
@@ -5011,9 +5047,21 @@ function transformPaymentError(errorBody) {
|
|
|
5011
5047
|
}
|
|
5012
5048
|
});
|
|
5013
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
|
+
}
|
|
5014
5062
|
}
|
|
5015
5063
|
}
|
|
5016
|
-
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")) {
|
|
5017
5065
|
const details = parsed.details || "";
|
|
5018
5066
|
const gasError = details.includes("unable to estimate gas");
|
|
5019
5067
|
return JSON.stringify({
|
|
@@ -5459,15 +5507,9 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
|
|
|
5459
5507
|
});
|
|
5460
5508
|
res.writeHead(upstream.status, responseHeaders);
|
|
5461
5509
|
if (upstream.body) {
|
|
5462
|
-
const
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
const { done, value } = await reader.read();
|
|
5466
|
-
if (done) break;
|
|
5467
|
-
safeWrite(res, Buffer.from(value));
|
|
5468
|
-
}
|
|
5469
|
-
} finally {
|
|
5470
|
-
reader.releaseLock();
|
|
5510
|
+
const chunks = await readBodyWithTimeout(upstream.body);
|
|
5511
|
+
for (const chunk of chunks) {
|
|
5512
|
+
safeWrite(res, Buffer.from(chunk));
|
|
5471
5513
|
}
|
|
5472
5514
|
}
|
|
5473
5515
|
res.end();
|
|
@@ -5497,16 +5539,23 @@ async function uploadDataUriToHost(dataUri) {
|
|
|
5497
5539
|
const form = new FormData();
|
|
5498
5540
|
form.append("reqtype", "fileupload");
|
|
5499
5541
|
form.append("fileToUpload", blob, `image.${ext}`);
|
|
5500
|
-
const
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
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);
|
|
5508
5558
|
}
|
|
5509
|
-
throw new Error(`catbox.moe upload failed: ${result}`);
|
|
5510
5559
|
}
|
|
5511
5560
|
async function startProxy(options) {
|
|
5512
5561
|
const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
|
|
@@ -5647,6 +5696,21 @@ async function startProxy(options) {
|
|
|
5647
5696
|
res.end(JSON.stringify(stats, null, 2));
|
|
5648
5697
|
return;
|
|
5649
5698
|
}
|
|
5699
|
+
if (req.url === "/stats" && req.method === "DELETE") {
|
|
5700
|
+
try {
|
|
5701
|
+
const result = await clearStats();
|
|
5702
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5703
|
+
res.end(JSON.stringify({ cleared: true, deletedFiles: result.deletedFiles }));
|
|
5704
|
+
} catch (err) {
|
|
5705
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
5706
|
+
res.end(
|
|
5707
|
+
JSON.stringify({
|
|
5708
|
+
error: `Failed to clear stats: ${err instanceof Error ? err.message : String(err)}`
|
|
5709
|
+
})
|
|
5710
|
+
);
|
|
5711
|
+
}
|
|
5712
|
+
return;
|
|
5713
|
+
}
|
|
5650
5714
|
if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
|
|
5651
5715
|
try {
|
|
5652
5716
|
const url = new URL(req.url, "http://localhost");
|
|
@@ -5893,7 +5957,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
5893
5957
|
signal
|
|
5894
5958
|
});
|
|
5895
5959
|
if (response.status !== 200) {
|
|
5896
|
-
const
|
|
5960
|
+
const errorBodyChunks = await readBodyWithTimeout(response.body);
|
|
5961
|
+
const errorBody = Buffer.concat(errorBodyChunks).toString();
|
|
5897
5962
|
const isProviderErr = isProviderError(response.status, errorBody);
|
|
5898
5963
|
return {
|
|
5899
5964
|
success: false,
|
|
@@ -5905,7 +5970,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
5905
5970
|
const contentType = response.headers.get("content-type") || "";
|
|
5906
5971
|
if (contentType.includes("json") || contentType.includes("text")) {
|
|
5907
5972
|
try {
|
|
5908
|
-
const
|
|
5973
|
+
const clonedChunks = await readBodyWithTimeout(response.clone().body);
|
|
5974
|
+
const responseBody = Buffer.concat(clonedChunks).toString();
|
|
5909
5975
|
const degradedReason = detectDegradedSuccessResponse(responseBody);
|
|
5910
5976
|
if (degradedReason) {
|
|
5911
5977
|
return {
|
|
@@ -6706,7 +6772,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
6706
6772
|
if (result.errorStatus === 429) {
|
|
6707
6773
|
markRateLimited(tryModel);
|
|
6708
6774
|
}
|
|
6709
|
-
const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(
|
|
6775
|
+
const isPaymentErr = /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test(
|
|
6710
6776
|
result.errorBody || ""
|
|
6711
6777
|
);
|
|
6712
6778
|
if (isPaymentErr && tryModel !== FREE_MODEL) {
|
|
@@ -6811,17 +6877,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
6811
6877
|
const responseChunks = [];
|
|
6812
6878
|
if (headersSentEarly) {
|
|
6813
6879
|
if (upstream.body) {
|
|
6814
|
-
const
|
|
6815
|
-
const chunks = [];
|
|
6816
|
-
try {
|
|
6817
|
-
while (true) {
|
|
6818
|
-
const { done, value } = await reader.read();
|
|
6819
|
-
if (done) break;
|
|
6820
|
-
chunks.push(value);
|
|
6821
|
-
}
|
|
6822
|
-
} finally {
|
|
6823
|
-
reader.releaseLock();
|
|
6824
|
-
}
|
|
6880
|
+
const chunks = await readBodyWithTimeout(upstream.body);
|
|
6825
6881
|
const jsonBody = Buffer.concat(chunks);
|
|
6826
6882
|
const jsonStr = jsonBody.toString();
|
|
6827
6883
|
try {
|
|
@@ -6960,15 +7016,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
6960
7016
|
}
|
|
6961
7017
|
const bodyParts = [];
|
|
6962
7018
|
if (upstream.body) {
|
|
6963
|
-
const
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
const { done, value } = await reader.read();
|
|
6967
|
-
if (done) break;
|
|
6968
|
-
bodyParts.push(Buffer.from(value));
|
|
6969
|
-
}
|
|
6970
|
-
} finally {
|
|
6971
|
-
reader.releaseLock();
|
|
7019
|
+
const chunks = await readBodyWithTimeout(upstream.body);
|
|
7020
|
+
for (const chunk of chunks) {
|
|
7021
|
+
bodyParts.push(Buffer.from(chunk));
|
|
6972
7022
|
}
|
|
6973
7023
|
}
|
|
6974
7024
|
let responseBody = Buffer.concat(bodyParts);
|