@blockrun/clawrouter 0.12.269 → 0.12.271
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/README.md +74 -51
- package/dist/cli.js +195 -34
- package/dist/index.d.ts +37 -4
- package/dist/index.js +188 -34
- package/docs/configuration.md +17 -1
- package/package.json +1 -1
- package/skills/clawrouter/SKILL.md +8 -8
- package/skills/imagegen/SKILL.md +2 -2
- package/skills/phone/SKILL.md +3 -2
- package/skills/predexon/SKILL.md +1 -1
- package/skills/surf/SKILL.md +24 -8
package/dist/cli.js
CHANGED
|
@@ -38541,8 +38541,8 @@ var init_models = __esm({
|
|
|
38541
38541
|
id: "openai/gpt-5.6-sol",
|
|
38542
38542
|
name: "GPT-5.6 Sol",
|
|
38543
38543
|
version: "5.6",
|
|
38544
|
-
inputPrice:
|
|
38545
|
-
outputPrice:
|
|
38544
|
+
inputPrice: 4,
|
|
38545
|
+
outputPrice: 20,
|
|
38546
38546
|
contextWindow: 105e4,
|
|
38547
38547
|
maxOutput: 128e3,
|
|
38548
38548
|
reasoning: true,
|
|
@@ -38583,8 +38583,8 @@ var init_models = __esm({
|
|
|
38583
38583
|
id: "openai/gpt-5.6-sol-pro",
|
|
38584
38584
|
name: "GPT-5.6 Sol Pro",
|
|
38585
38585
|
version: "5.6",
|
|
38586
|
-
inputPrice:
|
|
38587
|
-
outputPrice:
|
|
38586
|
+
inputPrice: 4,
|
|
38587
|
+
outputPrice: 20,
|
|
38588
38588
|
contextWindow: 105e4,
|
|
38589
38589
|
maxOutput: 128e3,
|
|
38590
38590
|
reasoning: true,
|
|
@@ -39022,8 +39022,8 @@ var init_models = __esm({
|
|
|
39022
39022
|
id: "google/gemini-3.6-flash",
|
|
39023
39023
|
name: "Gemini 3.6 Flash",
|
|
39024
39024
|
version: "3.6",
|
|
39025
|
-
inputPrice:
|
|
39026
|
-
outputPrice:
|
|
39025
|
+
inputPrice: 0.75,
|
|
39026
|
+
outputPrice: 3.75,
|
|
39027
39027
|
contextWindow: 1048576,
|
|
39028
39028
|
maxOutput: 65536,
|
|
39029
39029
|
reasoning: true,
|
|
@@ -39419,8 +39419,8 @@ var init_models = __esm({
|
|
|
39419
39419
|
id: "xai/grok-4.5",
|
|
39420
39420
|
name: "Grok 4.5",
|
|
39421
39421
|
version: "4.5",
|
|
39422
|
-
inputPrice: 2
|
|
39423
|
-
outputPrice:
|
|
39422
|
+
inputPrice: 2,
|
|
39423
|
+
outputPrice: 6,
|
|
39424
39424
|
contextWindow: 5e5,
|
|
39425
39425
|
maxOutput: 16384,
|
|
39426
39426
|
reasoning: true,
|
|
@@ -39434,8 +39434,8 @@ var init_models = __esm({
|
|
|
39434
39434
|
id: "xai/grok-4.3",
|
|
39435
39435
|
name: "Grok 4.3",
|
|
39436
39436
|
version: "4.3",
|
|
39437
|
-
inputPrice: 1.
|
|
39438
|
-
outputPrice:
|
|
39437
|
+
inputPrice: 1.25,
|
|
39438
|
+
outputPrice: 2.5,
|
|
39439
39439
|
contextWindow: 1e6,
|
|
39440
39440
|
maxOutput: 16384,
|
|
39441
39441
|
reasoning: true,
|
|
@@ -39447,8 +39447,8 @@ var init_models = __esm({
|
|
|
39447
39447
|
id: "xai/grok-build-0.1",
|
|
39448
39448
|
name: "Grok Build 0.1",
|
|
39449
39449
|
version: "0.1",
|
|
39450
|
-
inputPrice: 1
|
|
39451
|
-
outputPrice:
|
|
39450
|
+
inputPrice: 1,
|
|
39451
|
+
outputPrice: 2,
|
|
39452
39452
|
contextWindow: 256e3,
|
|
39453
39453
|
maxOutput: 16384,
|
|
39454
39454
|
agentic: true,
|
|
@@ -40713,6 +40713,18 @@ function isBalanceError(error) {
|
|
|
40713
40713
|
function isRpcError(error) {
|
|
40714
40714
|
return error instanceof Error && error.code === "RPC_ERROR";
|
|
40715
40715
|
}
|
|
40716
|
+
function describeFetchError(error) {
|
|
40717
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40718
|
+
const cause = error?.cause;
|
|
40719
|
+
if (!(cause instanceof Error)) return message;
|
|
40720
|
+
const detail = cause;
|
|
40721
|
+
const host = detail.hostname || detail.address;
|
|
40722
|
+
const where = host && detail.port ? `${host}:${detail.port}` : host;
|
|
40723
|
+
const parts = [detail.code, where, detail.code ? void 0 : detail.message].filter(
|
|
40724
|
+
(part) => typeof part === "string" && part !== ""
|
|
40725
|
+
);
|
|
40726
|
+
return parts.length > 0 ? `${message} (${parts.join(" ")})` : message;
|
|
40727
|
+
}
|
|
40716
40728
|
var InsufficientFundsError2, EmptyWalletError, RpcError2;
|
|
40717
40729
|
var init_errors5 = __esm({
|
|
40718
40730
|
"src/errors.ts"() {
|
|
@@ -60054,13 +60066,18 @@ async function loadPaymentChain() {
|
|
|
60054
60066
|
const core = await readOptional(CORE_CHAIN_FILE);
|
|
60055
60067
|
if (core === "solana") return "solana";
|
|
60056
60068
|
if (core === "base") return "base";
|
|
60057
|
-
|
|
60058
|
-
|
|
60059
|
-
|
|
60060
|
-
|
|
60061
|
-
|
|
60062
|
-
|
|
60063
|
-
|
|
60069
|
+
const legacy = await readOptional(CHAIN_FILE);
|
|
60070
|
+
if (legacy === "solana") return "solana";
|
|
60071
|
+
if (legacy === "base") return "base";
|
|
60072
|
+
return await hasExistingWallet() ? "base" : "solana";
|
|
60073
|
+
}
|
|
60074
|
+
async function hasExistingWallet() {
|
|
60075
|
+
if (process["env"].BLOCKRUN_WALLET_KEY?.trim()) return true;
|
|
60076
|
+
const [core, legacy] = await Promise.all([
|
|
60077
|
+
readOptional(CORE_WALLET_FILE),
|
|
60078
|
+
readOptional(WALLET_FILE)
|
|
60079
|
+
]);
|
|
60080
|
+
return Boolean(core || legacy);
|
|
60064
60081
|
}
|
|
60065
60082
|
async function resolvePaymentChain() {
|
|
60066
60083
|
if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "solana") return "solana";
|
|
@@ -60147,6 +60164,40 @@ async function clearApiKey() {
|
|
|
60147
60164
|
}
|
|
60148
60165
|
return { removed, envStillSet: Boolean(process["env"].BLOCKRUN_API_KEY?.trim()) };
|
|
60149
60166
|
}
|
|
60167
|
+
async function fetchCreditBalance(apiKey, timeoutMs = 5e3) {
|
|
60168
|
+
try {
|
|
60169
|
+
const controller = new AbortController();
|
|
60170
|
+
const timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
60171
|
+
const res = await fetch(`${BLOCKRUN_API_KEY_API}/v1/credits`, {
|
|
60172
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
60173
|
+
signal: controller.signal
|
|
60174
|
+
});
|
|
60175
|
+
clearTimeout(timer2);
|
|
60176
|
+
if (!res.ok) return void 0;
|
|
60177
|
+
const b = await res.json();
|
|
60178
|
+
const num2 = (v) => typeof v === "number" ? v : null;
|
|
60179
|
+
return {
|
|
60180
|
+
accountId: typeof b.account_id === "string" ? b.account_id : void 0,
|
|
60181
|
+
billingMode: typeof b.billing_mode === "string" ? b.billing_mode : void 0,
|
|
60182
|
+
currency: typeof b.currency === "string" ? b.currency : "USD",
|
|
60183
|
+
grantedUsd: num2(b.granted_usd),
|
|
60184
|
+
spentUsd: num2(b.spent_usd),
|
|
60185
|
+
remainingUsd: num2(b.remaining_usd),
|
|
60186
|
+
blocked: b.blocked === true,
|
|
60187
|
+
blockedReason: typeof b.blocked_reason === "string" ? b.blocked_reason : void 0
|
|
60188
|
+
};
|
|
60189
|
+
} catch {
|
|
60190
|
+
return void 0;
|
|
60191
|
+
}
|
|
60192
|
+
}
|
|
60193
|
+
function formatCreditBalance(b) {
|
|
60194
|
+
const money = (v) => v === null ? void 0 : `$${v.toFixed(v < 0.01 && v > 0 ? 6 : 2)}`;
|
|
60195
|
+
const remaining = money(b.remainingUsd);
|
|
60196
|
+
const spent = money(b.spentUsd);
|
|
60197
|
+
if (remaining) return `${remaining} remaining${spent ? ` (spent ${spent})` : ""}`;
|
|
60198
|
+
const mode = b.billingMode === "ungated" ? "no prepaid limit" : b.billingMode ?? "unknown";
|
|
60199
|
+
return `${spent ? `spent ${spent}` : "unknown"} \u2014 ${mode}`;
|
|
60200
|
+
}
|
|
60150
60201
|
function createApiKeyFetch(apiKey, baseFetch = fetch) {
|
|
60151
60202
|
return async (input, init2) => {
|
|
60152
60203
|
const headers = new Headers(init2?.headers);
|
|
@@ -60194,7 +60245,7 @@ var init_api_key = __esm({
|
|
|
60194
60245
|
HINTS = {
|
|
60195
60246
|
401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
|
|
60196
60247
|
402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
|
|
60197
|
-
404: `api.blockrun.ai does not serve this endpoint
|
|
60248
|
+
404: `api.blockrun.ai does not serve this endpoint. Chat, Anthropic-shaped messages, images, speech, video and the partner APIs (Surf, Exa, prediction markets, phone lookup) all work on an API key. The wallet-only exceptions are the routes that bind a lease or a position to a payer address \u2014 buying/renewing/releasing phone numbers, and Polymarket trading \u2014 which need a wallet to own the thing being bought. For those, unset BLOCKRUN_API_KEY and run "clawrouter logout".`
|
|
60198
60249
|
};
|
|
60199
60250
|
}
|
|
60200
60251
|
});
|
|
@@ -90668,6 +90719,24 @@ var init_client3 = __esm({
|
|
|
90668
90719
|
}
|
|
90669
90720
|
});
|
|
90670
90721
|
|
|
90722
|
+
// node_modules/@x402/svm/dist/esm/v1/index.mjs
|
|
90723
|
+
var v1_exports = {};
|
|
90724
|
+
__export(v1_exports, {
|
|
90725
|
+
ExactSvmSchemeV1: () => ExactSvmSchemeV1,
|
|
90726
|
+
NETWORKS: () => NETWORKS2
|
|
90727
|
+
});
|
|
90728
|
+
var init_v1 = __esm({
|
|
90729
|
+
"node_modules/@x402/svm/dist/esm/v1/index.mjs"() {
|
|
90730
|
+
"use strict";
|
|
90731
|
+
init_chunk_WWACQNRQ();
|
|
90732
|
+
init_chunk_MNNIECS4();
|
|
90733
|
+
init_chunk_WIPN332D();
|
|
90734
|
+
init_chunk_FVTMOTG6();
|
|
90735
|
+
init_chunk_FJ6REVHH();
|
|
90736
|
+
init_chunk_QEBTC3LJ();
|
|
90737
|
+
}
|
|
90738
|
+
});
|
|
90739
|
+
|
|
90671
90740
|
// src/proxy.ts
|
|
90672
90741
|
import { AsyncLocalStorage } from "async_hooks";
|
|
90673
90742
|
import { createHmac } from "crypto";
|
|
@@ -91422,6 +91491,23 @@ function estimateImageCost(model, size5, n = 1) {
|
|
|
91422
91491
|
const pricePerImage = sizePrice ?? pricing.default;
|
|
91423
91492
|
return pricePerImage * n * 1.05;
|
|
91424
91493
|
}
|
|
91494
|
+
function blockrunRequestId(response) {
|
|
91495
|
+
if (!response) return void 0;
|
|
91496
|
+
for (const name of ["x-blockrun-request-id", "x-request-id", "request-id"]) {
|
|
91497
|
+
const value = response.headers.get(name)?.trim();
|
|
91498
|
+
if (value) return value;
|
|
91499
|
+
}
|
|
91500
|
+
return void 0;
|
|
91501
|
+
}
|
|
91502
|
+
function gatewaySettledCostUsd(response) {
|
|
91503
|
+
const raw = response?.headers.get("x-blockrun-cost-usd");
|
|
91504
|
+
if (raw === null || raw === void 0) return void 0;
|
|
91505
|
+
const trimmed = raw.trim();
|
|
91506
|
+
if (trimmed === "") return void 0;
|
|
91507
|
+
const value = Number(trimmed);
|
|
91508
|
+
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
91509
|
+
return value;
|
|
91510
|
+
}
|
|
91425
91511
|
async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
|
|
91426
91512
|
const startTime = Date.now();
|
|
91427
91513
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
@@ -91471,8 +91557,9 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91471
91557
|
res.end();
|
|
91472
91558
|
const latencyMs = Date.now() - startTime;
|
|
91473
91559
|
console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
|
|
91474
|
-
const
|
|
91475
|
-
const
|
|
91560
|
+
const settledCostUsd = gatewaySettledCostUsd(upstream);
|
|
91561
|
+
const paidAmount = settledCostUsd ?? getActualPaymentUsd();
|
|
91562
|
+
const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
|
|
91476
91563
|
paidAmount,
|
|
91477
91564
|
isPhone,
|
|
91478
91565
|
upstreamStatus: upstream.status,
|
|
@@ -91481,6 +91568,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91481
91568
|
});
|
|
91482
91569
|
logUsage({
|
|
91483
91570
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
91571
|
+
...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
|
|
91484
91572
|
model: isBlockrunExa ? "blockrun-exa" : isModalSandbox ? "modal-sandbox" : isPhone ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : isSurf ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : "partner",
|
|
91485
91573
|
tier: isPhone ? "PHONE" : isSurf ? "SURF" : "PARTNER",
|
|
91486
91574
|
cost: requestCost,
|
|
@@ -91672,8 +91760,20 @@ async function startProxy(options) {
|
|
|
91672
91760
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91673
91761
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
91674
91762
|
solanaAddress = solanaSigner.address;
|
|
91763
|
+
const solanaRpcUrl = process["env"].CLAWROUTER_SOLANA_RPC_URL;
|
|
91675
91764
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91765
|
+
if (solanaRpcUrl) {
|
|
91766
|
+
const { ExactSvmScheme: ExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91767
|
+
const { ExactSvmSchemeV1: ExactSvmSchemeV12, NETWORKS: SVM_V1_NETWORKS } = await Promise.resolve().then(() => (init_v1(), v1_exports));
|
|
91768
|
+
x402.register("solana:*", new ExactSvmScheme2(solanaSigner, { rpcUrl: solanaRpcUrl }));
|
|
91769
|
+
for (const network of SVM_V1_NETWORKS) {
|
|
91770
|
+
x402.registerV1(network, new ExactSvmSchemeV12(solanaSigner, { rpcUrl: solanaRpcUrl }));
|
|
91771
|
+
}
|
|
91772
|
+
}
|
|
91676
91773
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91774
|
+
if (solanaRpcUrl) {
|
|
91775
|
+
console.log(`[ClawRouter] Solana RPC (payment signing + balance): ${solanaRpcUrl}`);
|
|
91776
|
+
}
|
|
91677
91777
|
}
|
|
91678
91778
|
x402?.onAfterPaymentCreation(async (context) => {
|
|
91679
91779
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
@@ -91773,7 +91873,15 @@ async function startProxy(options) {
|
|
|
91773
91873
|
)
|
|
91774
91874
|
]);
|
|
91775
91875
|
if (authMode === "api-key") {
|
|
91776
|
-
|
|
91876
|
+
const credit = await fetchCreditBalance(apiKey, BALANCE_CHECK_TIMEOUT_MS);
|
|
91877
|
+
response.balance = credit?.remainingUsd ?? null;
|
|
91878
|
+
response.creditSpent = credit?.spentUsd ?? null;
|
|
91879
|
+
response.creditGranted = credit?.grantedUsd ?? null;
|
|
91880
|
+
response.billingMode = credit?.billingMode ?? null;
|
|
91881
|
+
response.creditSummary = credit ? formatCreditBalance(credit) : null;
|
|
91882
|
+
response.blocked = credit?.blocked ?? false;
|
|
91883
|
+
if (credit?.blockedReason) response.blockedReason = credit.blockedReason;
|
|
91884
|
+
response.isEmpty = credit?.remainingUsd !== null && (credit?.remainingUsd ?? 1) <= 0;
|
|
91777
91885
|
response.billing = "BlockRun account credit";
|
|
91778
91886
|
response.topUpUrl = PORTAL_CREDITS_URL;
|
|
91779
91887
|
} else {
|
|
@@ -92578,7 +92686,8 @@ async function startProxy(options) {
|
|
|
92578
92686
|
}
|
|
92579
92687
|
return;
|
|
92580
92688
|
}
|
|
92581
|
-
|
|
92689
|
+
const isVerbatimGatewayPath = req.url === "/v1/messages" || (req.url?.startsWith("/v1/messages?") ?? false) || req.url === "/v1/audio/speech" || (req.url?.startsWith("/v1/audio/speech?") ?? false);
|
|
92690
|
+
if (isVerbatimGatewayPath || req.url?.match(
|
|
92582
92691
|
/^\/v1\/(?:partner|pm|exa|modal|stocks|usstock|crypto|fx|commodity|phone|voice|surf)\//
|
|
92583
92692
|
)) {
|
|
92584
92693
|
try {
|
|
@@ -92610,6 +92719,19 @@ async function startProxy(options) {
|
|
|
92610
92719
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
92611
92720
|
return;
|
|
92612
92721
|
}
|
|
92722
|
+
if (!req.url.includes("/chat/completions")) {
|
|
92723
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
92724
|
+
res.end(
|
|
92725
|
+
JSON.stringify({
|
|
92726
|
+
error: {
|
|
92727
|
+
message: `ClawRouter does not proxy ${req.url}. It serves /v1/chat/completions, /v1/messages, /v1/models, /v1/images/generations, /v1/images/image2image, /v1/videos/generations, /v1/audio/generations, /v1/audio/speech, and the partner prefixes /v1/{partner,pm,exa,modal,stocks,usstock,crypto,fx,commodity,phone,voice,surf}/. If BlockRun serves this endpoint, call it directly against the gateway.`,
|
|
92728
|
+
type: "invalid_request_error",
|
|
92729
|
+
code: "unsupported_endpoint"
|
|
92730
|
+
}
|
|
92731
|
+
})
|
|
92732
|
+
);
|
|
92733
|
+
return;
|
|
92734
|
+
}
|
|
92613
92735
|
try {
|
|
92614
92736
|
await proxyRequest(
|
|
92615
92737
|
req,
|
|
@@ -92864,7 +92986,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92864
92986
|
errorBody,
|
|
92865
92987
|
errorStatus: response.status,
|
|
92866
92988
|
isProviderError: category !== null,
|
|
92867
|
-
errorCategory: category ?? void 0
|
|
92989
|
+
errorCategory: category ?? void 0,
|
|
92990
|
+
upstreamRequestId: blockrunRequestId(response)
|
|
92868
92991
|
};
|
|
92869
92992
|
}
|
|
92870
92993
|
const contentType = response.headers.get("content-type") || "";
|
|
@@ -92901,9 +93024,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92901
93024
|
errorCategory: "payment_error"
|
|
92902
93025
|
};
|
|
92903
93026
|
}
|
|
93027
|
+
const networkMsg = describeFetchError(err);
|
|
93028
|
+
const solanaSigningHint = upstreamUrl.startsWith(BLOCKRUN_SOLANA_API) && networkMsg.startsWith("fetch failed") ? " \u2014 on the Solana rail this may be the RPC that signs the payment rather than the gateway; if api.mainnet-beta.solana.com is unreachable from this host, set CLAWROUTER_SOLANA_RPC_URL to your own endpoint" : "";
|
|
93029
|
+
console.error(`[ClawRouter] ${modelId} network error: ${networkMsg}${solanaSigningHint}`);
|
|
92904
93030
|
return {
|
|
92905
93031
|
success: false,
|
|
92906
|
-
errorBody:
|
|
93032
|
+
errorBody: `${networkMsg}${solanaSigningHint}`,
|
|
92907
93033
|
errorStatus: 500,
|
|
92908
93034
|
isProviderError: true
|
|
92909
93035
|
// Network errors are retryable
|
|
@@ -92936,6 +93062,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
92936
93062
|
let accumulatedContent = "";
|
|
92937
93063
|
let responseInputTokens;
|
|
92938
93064
|
let responseOutputTokens;
|
|
93065
|
+
let upstreamRequestId;
|
|
92939
93066
|
let requestHadError = false;
|
|
92940
93067
|
let requestSummaryForStore = "";
|
|
92941
93068
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
@@ -94342,7 +94469,8 @@ data: [DONE]
|
|
|
94342
94469
|
}
|
|
94343
94470
|
lastError = {
|
|
94344
94471
|
body: result.errorBody || "Unknown error",
|
|
94345
|
-
status: result.errorStatus || 500
|
|
94472
|
+
status: result.errorStatus || 500,
|
|
94473
|
+
requestId: result.upstreamRequestId
|
|
94346
94474
|
};
|
|
94347
94475
|
failedAttempts.push({
|
|
94348
94476
|
model: tryModel,
|
|
@@ -94414,7 +94542,8 @@ data: [DONE]
|
|
|
94414
94542
|
}
|
|
94415
94543
|
lastError = {
|
|
94416
94544
|
body: retryResult.errorBody || lastError?.body || "Unknown error",
|
|
94417
|
-
status: retryResult.errorStatus || lastError?.status || 500
|
|
94545
|
+
status: retryResult.errorStatus || lastError?.status || 500,
|
|
94546
|
+
requestId: retryResult.upstreamRequestId ?? lastError?.requestId
|
|
94418
94547
|
};
|
|
94419
94548
|
failedAttempts.push({
|
|
94420
94549
|
model: tryModel,
|
|
@@ -94556,10 +94685,13 @@ data: [DONE]
|
|
|
94556
94685
|
}
|
|
94557
94686
|
}
|
|
94558
94687
|
}
|
|
94688
|
+
upstreamRequestId = blockrunRequestId(upstream);
|
|
94559
94689
|
if (!upstream) {
|
|
94560
94690
|
const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
|
|
94561
94691
|
const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
|
|
94562
|
-
console.log(
|
|
94692
|
+
console.log(
|
|
94693
|
+
`[ClawRouter] ${structuredMessage}${lastError?.requestId ? ` | gateway request id: ${lastError.requestId}` : ""}`
|
|
94694
|
+
);
|
|
94563
94695
|
const rawErrBody = lastError?.body || structuredMessage;
|
|
94564
94696
|
const errStatus = lastError?.status || 502;
|
|
94565
94697
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
@@ -94597,7 +94729,8 @@ data: [DONE]
|
|
|
94597
94729
|
res.writeHead(errStatus, {
|
|
94598
94730
|
"Content-Type": "application/json",
|
|
94599
94731
|
"x-context-used-kb": String(originalContextSizeKB),
|
|
94600
|
-
"x-context-limit-kb": String(CONTEXT_LIMIT_KB)
|
|
94732
|
+
"x-context-limit-kb": String(CONTEXT_LIMIT_KB),
|
|
94733
|
+
...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
|
|
94601
94734
|
});
|
|
94602
94735
|
res.end(transformedErr);
|
|
94603
94736
|
deduplicator.complete(dedupKey, {
|
|
@@ -95061,12 +95194,24 @@ data: [DONE]
|
|
|
95061
95194
|
maxTokens,
|
|
95062
95195
|
routingProfile ?? void 0
|
|
95063
95196
|
);
|
|
95064
|
-
|
|
95197
|
+
const apiKeyPricing = options.apiKey ? routerOpts.modelPricing.get(logModel) : void 0;
|
|
95198
|
+
if (FREE_MODELS.has(logModel)) {
|
|
95199
|
+
logCost = 0;
|
|
95200
|
+
logSavings = 1;
|
|
95201
|
+
} else if (apiKeyPricing) {
|
|
95202
|
+
const inTokens = responseInputTokens ?? chargedInputTokens;
|
|
95203
|
+
const outTokens = responseOutputTokens ?? 0;
|
|
95204
|
+
logCost = (inTokens * (apiKeyPricing.inputPrice ?? 0) + outTokens * (apiKeyPricing.outputPrice ?? 0)) / 1e6;
|
|
95205
|
+
logSavings = costs.baselineCost > 0 ? Math.max(0, (costs.baselineCost - logCost) / costs.baselineCost) : 0;
|
|
95206
|
+
} else {
|
|
95207
|
+
logCost = costs.costEstimate;
|
|
95208
|
+
logSavings = costs.savings;
|
|
95209
|
+
}
|
|
95065
95210
|
logBaseline = costs.baselineCost;
|
|
95066
|
-
logSavings = FREE_MODELS.has(logModel) ? 1 : costs.savings;
|
|
95067
95211
|
}
|
|
95068
95212
|
const entry = {
|
|
95069
95213
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95214
|
+
...upstreamRequestId ? { requestId: upstreamRequestId } : {},
|
|
95070
95215
|
model: logModel,
|
|
95071
95216
|
tier: routingDecision?.tier ?? "DIRECT",
|
|
95072
95217
|
cost: logCost,
|
|
@@ -95106,6 +95251,7 @@ var init_proxy = __esm({
|
|
|
95106
95251
|
init_api_key();
|
|
95107
95252
|
init_spend_control();
|
|
95108
95253
|
init_compression();
|
|
95254
|
+
init_errors5();
|
|
95109
95255
|
init_version4();
|
|
95110
95256
|
init_session();
|
|
95111
95257
|
init_updater();
|
|
@@ -229633,7 +229779,15 @@ ${errText}`
|
|
|
229633
229779
|
});
|
|
229634
229780
|
if (!isGatewayMode()) {
|
|
229635
229781
|
if (shouldLogRegistration) {
|
|
229636
|
-
|
|
229782
|
+
void (async () => {
|
|
229783
|
+
const keyResolution = await resolveApiKey().catch(() => void 0);
|
|
229784
|
+
if (keyResolution) {
|
|
229785
|
+
api.logger.info(
|
|
229786
|
+
`Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`
|
|
229787
|
+
);
|
|
229788
|
+
return;
|
|
229789
|
+
}
|
|
229790
|
+
const { address: address2, source } = await resolveOrGenerateWalletKey();
|
|
229637
229791
|
if (source === "generated") {
|
|
229638
229792
|
api.logger.warn(`\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`);
|
|
229639
229793
|
api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
|
|
@@ -229650,7 +229804,7 @@ ${errText}`
|
|
|
229650
229804
|
} else {
|
|
229651
229805
|
api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address2}`);
|
|
229652
229806
|
}
|
|
229653
|
-
}).catch((err) => {
|
|
229807
|
+
})().catch((err) => {
|
|
229654
229808
|
api.logger.warn(
|
|
229655
229809
|
`Failed to initialize wallet: ${err instanceof Error ? err.message : String(err)}`
|
|
229656
229810
|
);
|
|
@@ -230582,6 +230736,12 @@ ClawRouter Status (port ${port})
|
|
|
230582
230736
|
console.log(` Auth: BlockRun API key ${data.apiKey}`);
|
|
230583
230737
|
console.log(` Gateway: ${data.gateway}`);
|
|
230584
230738
|
console.log(` Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
230739
|
+
if (data.creditSummary) console.log(` Credit: ${data.creditSummary}`);
|
|
230740
|
+
if (data.blocked) {
|
|
230741
|
+
console.log(` \u26A0 Account is blocked${data.blockedReason ? `: ${data.blockedReason}` : ""}`);
|
|
230742
|
+
} else if (data.isEmpty) {
|
|
230743
|
+
console.log(` \u26A0 Credit exhausted \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
230744
|
+
}
|
|
230585
230745
|
} else {
|
|
230586
230746
|
console.log(` Auth: wallet (x402)`);
|
|
230587
230747
|
console.log(` Payment Chain: ${data.paymentChain}`);
|
|
@@ -230609,6 +230769,7 @@ ClawRouter Account
|
|
|
230609
230769
|
console.log(` Auth: BlockRun API key ${data.apiKey}`);
|
|
230610
230770
|
console.log(` Gateway: ${data.gateway}`);
|
|
230611
230771
|
console.log(` Billing: account credit (no wallet, no chain)`);
|
|
230772
|
+
if (data.creditSummary) console.log(` Credit: ${data.creditSummary}`);
|
|
230612
230773
|
console.log(` Top up: ${PORTAL_CREDITS_URL}`);
|
|
230613
230774
|
console.log(` Usage: ${PORTAL_URL}/dashboard`);
|
|
230614
230775
|
console.log();
|
package/dist/index.d.ts
CHANGED
|
@@ -1248,13 +1248,29 @@ declare function setupSolana(): Promise<{
|
|
|
1248
1248
|
declare function savePaymentChain(chain: "base" | "solana"): Promise<void>;
|
|
1249
1249
|
/**
|
|
1250
1250
|
* Load the persisted payment chain selection from disk.
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1251
|
+
*
|
|
1252
|
+
* Solana is the preferred rail, and since v0.12.246 wallet generation persists
|
|
1253
|
+
* `solana` outright — so a fresh install answers "solana" from its own chain
|
|
1254
|
+
* file, not from a default. This function only decides the case where no chain
|
|
1255
|
+
* file exists at all, and there the answer turns on whether a wallet already
|
|
1256
|
+
* does:
|
|
1257
|
+
*
|
|
1258
|
+
* no chain file, no wallet → "solana" (nothing to strand; prefer Solana)
|
|
1259
|
+
* no chain file, wallet on disk → "base" (a pre-v0.12.246 install)
|
|
1260
|
+
*
|
|
1261
|
+
* That second line is the one that must not move. An install predating the
|
|
1262
|
+
* Solana default has USDC sitting in its Base wallet and no Solana balance;
|
|
1263
|
+
* flipping it would point every request at a gateway its money is not on and
|
|
1264
|
+
* fail them all with an empty balance. The same reasoning covers a wallet
|
|
1265
|
+
* supplied through BLOCKRUN_WALLET_KEY, and a generated wallet whose Solana
|
|
1266
|
+
* derivation failed (no chain file is written in that case, on purpose — see
|
|
1267
|
+
* resolveOrGenerateWalletKey — and the wallet it did write keeps us on Base,
|
|
1268
|
+
* which is the only chain that proxy can actually sign for).
|
|
1254
1269
|
*/
|
|
1255
1270
|
declare function loadPaymentChain(): Promise<"base" | "solana">;
|
|
1256
1271
|
/**
|
|
1257
|
-
* Resolve payment chain: env var first → persisted file second →
|
|
1272
|
+
* Resolve payment chain: env var first → persisted file second → the
|
|
1273
|
+
* fresh-install preference (Solana) last. See loadPaymentChain.
|
|
1258
1274
|
*/
|
|
1259
1275
|
declare function resolvePaymentChain(): Promise<"base" | "solana">;
|
|
1260
1276
|
|
|
@@ -1400,6 +1416,23 @@ type UsageEntry = {
|
|
|
1400
1416
|
partnerId?: string;
|
|
1401
1417
|
/** Partner service name (e.g., "BlockRun") — only set for partner API calls */
|
|
1402
1418
|
service?: string;
|
|
1419
|
+
/**
|
|
1420
|
+
* The gateway's own id for this request, from the `x-blockrun-request-id`
|
|
1421
|
+
* response header. Absent for requests that never reached the gateway (a
|
|
1422
|
+
* cache hit, a local refusal, an aborted call).
|
|
1423
|
+
*
|
|
1424
|
+
* This is the JOIN KEY for billing reconciliation. Every `cost` in this file
|
|
1425
|
+
* is a LOCAL estimate computed from our copy of the price table, so it drifts
|
|
1426
|
+
* whenever that copy goes stale — six prices were wrong until v0.12.270, and
|
|
1427
|
+
* `/stats` had been reporting the wrong money the whole time. Recording the
|
|
1428
|
+
* gateway's id is what will let us diff this journal line-by-line against a
|
|
1429
|
+
* server-side ledger and show what was actually charged instead of what we
|
|
1430
|
+
* guessed. It cannot be backfilled: a call whose id we did not record is
|
|
1431
|
+
* unreconcilable forever, which is why this is captured before the ledger API
|
|
1432
|
+
* that will consume it exists. It is also the id support needs to trace one
|
|
1433
|
+
* failed call.
|
|
1434
|
+
*/
|
|
1435
|
+
requestId?: string;
|
|
1403
1436
|
};
|
|
1404
1437
|
/**
|
|
1405
1438
|
* Log a usage entry as a JSON line.
|