@blockrun/clawrouter 0.8.10 → 0.8.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 +57 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +71 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1829,6 +1829,12 @@ async function getStats(days = 7) {
|
|
|
1829
1829
|
}
|
|
1830
1830
|
const totalSavings = totalBaselineCost - totalCost;
|
|
1831
1831
|
const savingsPercentage = totalBaselineCost > 0 ? totalSavings / totalBaselineCost * 100 : 0;
|
|
1832
|
+
let entriesWithBaseline = 0;
|
|
1833
|
+
for (const day of dailyBreakdown) {
|
|
1834
|
+
if (day.totalBaselineCost !== day.totalCost) {
|
|
1835
|
+
entriesWithBaseline += day.totalRequests;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1832
1838
|
return {
|
|
1833
1839
|
period: days === 1 ? "today" : `last ${days} days`,
|
|
1834
1840
|
totalRequests,
|
|
@@ -1840,8 +1846,10 @@ async function getStats(days = 7) {
|
|
|
1840
1846
|
avgCostPerRequest: totalRequests > 0 ? totalCost / totalRequests : 0,
|
|
1841
1847
|
byTier: byTierWithPercentage,
|
|
1842
1848
|
byModel: byModelWithPercentage,
|
|
1843
|
-
dailyBreakdown: dailyBreakdown.reverse()
|
|
1849
|
+
dailyBreakdown: dailyBreakdown.reverse(),
|
|
1844
1850
|
// Oldest first for charts
|
|
1851
|
+
entriesWithBaseline
|
|
1852
|
+
// How many entries have valid baseline tracking
|
|
1845
1853
|
};
|
|
1846
1854
|
}
|
|
1847
1855
|
function formatStatsAscii(stats) {
|
|
@@ -1853,20 +1861,27 @@ function formatStatsAscii(stats) {
|
|
|
1853
1861
|
lines.push(`\u2551 Total Requests: ${stats.totalRequests.toString().padEnd(41)}\u2551`);
|
|
1854
1862
|
lines.push(`\u2551 Total Cost: $${stats.totalCost.toFixed(4).padEnd(43)}\u2551`);
|
|
1855
1863
|
lines.push(`\u2551 Baseline Cost (Opus): $${stats.totalBaselineCost.toFixed(4).padEnd(33)}\u2551`);
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1864
|
+
const savingsLine = `\u2551 \u{1F4B0} Total Saved: $${stats.totalSavings.toFixed(4)} (${stats.savingsPercentage.toFixed(1)}%)`;
|
|
1865
|
+
if (stats.entriesWithBaseline < stats.totalRequests && stats.entriesWithBaseline > 0) {
|
|
1866
|
+
lines.push(savingsLine.padEnd(61) + "\u2551");
|
|
1867
|
+
const note = `\u2551 (based on ${stats.entriesWithBaseline}/${stats.totalRequests} tracked requests)`;
|
|
1868
|
+
lines.push(note.padEnd(61) + "\u2551");
|
|
1869
|
+
} else {
|
|
1870
|
+
lines.push(savingsLine.padEnd(61) + "\u2551");
|
|
1871
|
+
}
|
|
1861
1872
|
lines.push(`\u2551 Avg Latency: ${stats.avgLatencyMs.toFixed(0)}ms`.padEnd(61) + "\u2551");
|
|
1862
1873
|
lines.push("\u2560\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\u2563");
|
|
1863
1874
|
lines.push("\u2551 Routing by Tier: \u2551");
|
|
1864
|
-
const
|
|
1875
|
+
const knownTiers = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
|
|
1876
|
+
const allTiers = Object.keys(stats.byTier);
|
|
1877
|
+
const otherTiers = allTiers.filter((t) => !knownTiers.includes(t));
|
|
1878
|
+
const tierOrder = [...knownTiers.filter((t) => stats.byTier[t]), ...otherTiers];
|
|
1865
1879
|
for (const tier of tierOrder) {
|
|
1866
1880
|
const data = stats.byTier[tier];
|
|
1867
1881
|
if (data) {
|
|
1868
1882
|
const bar = "\u2588".repeat(Math.min(20, Math.round(data.percentage / 5)));
|
|
1869
|
-
const
|
|
1883
|
+
const displayTier = tier === "UNKNOWN" ? "OTHER" : tier;
|
|
1884
|
+
const line = `\u2551 ${displayTier.padEnd(10)} ${bar.padEnd(20)} ${data.percentage.toFixed(1).padStart(5)}% (${data.count})`;
|
|
1870
1885
|
lines.push(line.padEnd(61) + "\u2551");
|
|
1871
1886
|
}
|
|
1872
1887
|
}
|
|
@@ -2347,6 +2362,42 @@ var HEALTH_CHECK_TIMEOUT_MS = 2e3;
|
|
|
2347
2362
|
var RATE_LIMIT_COOLDOWN_MS = 6e4;
|
|
2348
2363
|
var PORT_RETRY_ATTEMPTS = 5;
|
|
2349
2364
|
var PORT_RETRY_DELAY_MS = 1e3;
|
|
2365
|
+
function transformPaymentError(errorBody) {
|
|
2366
|
+
try {
|
|
2367
|
+
const parsed = JSON.parse(errorBody);
|
|
2368
|
+
if (parsed.error === "Payment verification failed" && parsed.details) {
|
|
2369
|
+
const match = parsed.details.match(/Verification failed:\s*(\{.*\})/s);
|
|
2370
|
+
if (match) {
|
|
2371
|
+
const innerJson = JSON.parse(match[1]);
|
|
2372
|
+
if (innerJson.invalidReason === "insufficient_funds" && innerJson.invalidMessage) {
|
|
2373
|
+
const balanceMatch = innerJson.invalidMessage.match(
|
|
2374
|
+
/insufficient balance:\s*(\d+)\s*<\s*(\d+)/i
|
|
2375
|
+
);
|
|
2376
|
+
if (balanceMatch) {
|
|
2377
|
+
const currentMicros = parseInt(balanceMatch[1], 10);
|
|
2378
|
+
const requiredMicros = parseInt(balanceMatch[2], 10);
|
|
2379
|
+
const currentUSD = (currentMicros / 1e6).toFixed(6);
|
|
2380
|
+
const requiredUSD = (requiredMicros / 1e6).toFixed(6);
|
|
2381
|
+
const wallet = innerJson.payer || "unknown";
|
|
2382
|
+
const shortWallet = wallet.length > 12 ? `${wallet.slice(0, 6)}...${wallet.slice(-4)}` : wallet;
|
|
2383
|
+
return JSON.stringify({
|
|
2384
|
+
error: {
|
|
2385
|
+
message: `Insufficient USDC balance. Current: $${currentUSD}, Required: ~$${requiredUSD}`,
|
|
2386
|
+
type: "insufficient_funds",
|
|
2387
|
+
wallet,
|
|
2388
|
+
current_balance_usd: currentUSD,
|
|
2389
|
+
required_usd: requiredUSD,
|
|
2390
|
+
help: `Fund wallet ${shortWallet} with USDC on Base, or use free model: /model free`
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
} catch {
|
|
2398
|
+
}
|
|
2399
|
+
return errorBody;
|
|
2400
|
+
}
|
|
2350
2401
|
var rateLimitedModels = /* @__PURE__ */ new Map();
|
|
2351
2402
|
function isRateLimited(modelId) {
|
|
2352
2403
|
const hitTime = rateLimitedModels.get(modelId);
|
|
@@ -3215,10 +3266,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
3215
3266
|
options.onRouted?.(routingDecision);
|
|
3216
3267
|
}
|
|
3217
3268
|
if (!upstream) {
|
|
3218
|
-
const
|
|
3269
|
+
const rawErrBody = lastError?.body || "All models in fallback chain failed";
|
|
3219
3270
|
const errStatus = lastError?.status || 502;
|
|
3271
|
+
const transformedErr = transformPaymentError(rawErrBody);
|
|
3220
3272
|
if (headersSentEarly) {
|
|
3221
|
-
|
|
3273
|
+
let errPayload;
|
|
3274
|
+
try {
|
|
3275
|
+
const parsed = JSON.parse(transformedErr);
|
|
3276
|
+
errPayload = JSON.stringify(parsed);
|
|
3277
|
+
} catch {
|
|
3278
|
+
errPayload = JSON.stringify({ error: { message: rawErrBody, type: "provider_error", status: errStatus } });
|
|
3279
|
+
}
|
|
3280
|
+
const errEvent = `data: ${errPayload}
|
|
3222
3281
|
|
|
3223
3282
|
`;
|
|
3224
3283
|
safeWrite(res, errEvent);
|
|
@@ -3233,17 +3292,11 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
3233
3292
|
});
|
|
3234
3293
|
} else {
|
|
3235
3294
|
res.writeHead(errStatus, { "Content-Type": "application/json" });
|
|
3236
|
-
res.end(
|
|
3237
|
-
JSON.stringify({
|
|
3238
|
-
error: { message: errBody, type: "provider_error" }
|
|
3239
|
-
})
|
|
3240
|
-
);
|
|
3295
|
+
res.end(transformedErr);
|
|
3241
3296
|
deduplicator.complete(dedupKey, {
|
|
3242
3297
|
status: errStatus,
|
|
3243
3298
|
headers: { "content-type": "application/json" },
|
|
3244
|
-
body: Buffer.from(
|
|
3245
|
-
JSON.stringify({ error: { message: errBody, type: "provider_error" } })
|
|
3246
|
-
),
|
|
3299
|
+
body: Buffer.from(transformedErr),
|
|
3247
3300
|
completedAt: Date.now()
|
|
3248
3301
|
});
|
|
3249
3302
|
}
|