@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/index.js
CHANGED
|
@@ -783,8 +783,8 @@ var init_models = __esm({
|
|
|
783
783
|
id: "openai/gpt-5.6-sol",
|
|
784
784
|
name: "GPT-5.6 Sol",
|
|
785
785
|
version: "5.6",
|
|
786
|
-
inputPrice:
|
|
787
|
-
outputPrice:
|
|
786
|
+
inputPrice: 4,
|
|
787
|
+
outputPrice: 20,
|
|
788
788
|
contextWindow: 105e4,
|
|
789
789
|
maxOutput: 128e3,
|
|
790
790
|
reasoning: true,
|
|
@@ -825,8 +825,8 @@ var init_models = __esm({
|
|
|
825
825
|
id: "openai/gpt-5.6-sol-pro",
|
|
826
826
|
name: "GPT-5.6 Sol Pro",
|
|
827
827
|
version: "5.6",
|
|
828
|
-
inputPrice:
|
|
829
|
-
outputPrice:
|
|
828
|
+
inputPrice: 4,
|
|
829
|
+
outputPrice: 20,
|
|
830
830
|
contextWindow: 105e4,
|
|
831
831
|
maxOutput: 128e3,
|
|
832
832
|
reasoning: true,
|
|
@@ -1264,8 +1264,8 @@ var init_models = __esm({
|
|
|
1264
1264
|
id: "google/gemini-3.6-flash",
|
|
1265
1265
|
name: "Gemini 3.6 Flash",
|
|
1266
1266
|
version: "3.6",
|
|
1267
|
-
inputPrice:
|
|
1268
|
-
outputPrice:
|
|
1267
|
+
inputPrice: 0.75,
|
|
1268
|
+
outputPrice: 3.75,
|
|
1269
1269
|
contextWindow: 1048576,
|
|
1270
1270
|
maxOutput: 65536,
|
|
1271
1271
|
reasoning: true,
|
|
@@ -1661,8 +1661,8 @@ var init_models = __esm({
|
|
|
1661
1661
|
id: "xai/grok-4.5",
|
|
1662
1662
|
name: "Grok 4.5",
|
|
1663
1663
|
version: "4.5",
|
|
1664
|
-
inputPrice: 2
|
|
1665
|
-
outputPrice:
|
|
1664
|
+
inputPrice: 2,
|
|
1665
|
+
outputPrice: 6,
|
|
1666
1666
|
contextWindow: 5e5,
|
|
1667
1667
|
maxOutput: 16384,
|
|
1668
1668
|
reasoning: true,
|
|
@@ -1676,8 +1676,8 @@ var init_models = __esm({
|
|
|
1676
1676
|
id: "xai/grok-4.3",
|
|
1677
1677
|
name: "Grok 4.3",
|
|
1678
1678
|
version: "4.3",
|
|
1679
|
-
inputPrice: 1.
|
|
1680
|
-
outputPrice:
|
|
1679
|
+
inputPrice: 1.25,
|
|
1680
|
+
outputPrice: 2.5,
|
|
1681
1681
|
contextWindow: 1e6,
|
|
1682
1682
|
maxOutput: 16384,
|
|
1683
1683
|
reasoning: true,
|
|
@@ -1689,8 +1689,8 @@ var init_models = __esm({
|
|
|
1689
1689
|
id: "xai/grok-build-0.1",
|
|
1690
1690
|
name: "Grok Build 0.1",
|
|
1691
1691
|
version: "0.1",
|
|
1692
|
-
inputPrice: 1
|
|
1693
|
-
outputPrice:
|
|
1692
|
+
inputPrice: 1,
|
|
1693
|
+
outputPrice: 2,
|
|
1694
1694
|
contextWindow: 256e3,
|
|
1695
1695
|
maxOutput: 16384,
|
|
1696
1696
|
agentic: true,
|
|
@@ -40581,6 +40581,18 @@ function isBalanceError(error) {
|
|
|
40581
40581
|
function isRpcError(error) {
|
|
40582
40582
|
return error instanceof Error && error.code === "RPC_ERROR";
|
|
40583
40583
|
}
|
|
40584
|
+
function describeFetchError(error) {
|
|
40585
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40586
|
+
const cause = error?.cause;
|
|
40587
|
+
if (!(cause instanceof Error)) return message;
|
|
40588
|
+
const detail = cause;
|
|
40589
|
+
const host = detail.hostname || detail.address;
|
|
40590
|
+
const where = host && detail.port ? `${host}:${detail.port}` : host;
|
|
40591
|
+
const parts = [detail.code, where, detail.code ? void 0 : detail.message].filter(
|
|
40592
|
+
(part) => typeof part === "string" && part !== ""
|
|
40593
|
+
);
|
|
40594
|
+
return parts.length > 0 ? `${message} (${parts.join(" ")})` : message;
|
|
40595
|
+
}
|
|
40584
40596
|
var InsufficientFundsError2, EmptyWalletError, RpcError2;
|
|
40585
40597
|
var init_errors5 = __esm({
|
|
40586
40598
|
"src/errors.ts"() {
|
|
@@ -59861,13 +59873,18 @@ async function loadPaymentChain() {
|
|
|
59861
59873
|
const core = await readOptional(CORE_CHAIN_FILE);
|
|
59862
59874
|
if (core === "solana") return "solana";
|
|
59863
59875
|
if (core === "base") return "base";
|
|
59864
|
-
|
|
59865
|
-
|
|
59866
|
-
|
|
59867
|
-
|
|
59868
|
-
|
|
59869
|
-
|
|
59870
|
-
|
|
59876
|
+
const legacy = await readOptional(CHAIN_FILE);
|
|
59877
|
+
if (legacy === "solana") return "solana";
|
|
59878
|
+
if (legacy === "base") return "base";
|
|
59879
|
+
return await hasExistingWallet() ? "base" : "solana";
|
|
59880
|
+
}
|
|
59881
|
+
async function hasExistingWallet() {
|
|
59882
|
+
if (process["env"].BLOCKRUN_WALLET_KEY?.trim()) return true;
|
|
59883
|
+
const [core, legacy] = await Promise.all([
|
|
59884
|
+
readOptional(CORE_WALLET_FILE),
|
|
59885
|
+
readOptional(WALLET_FILE)
|
|
59886
|
+
]);
|
|
59887
|
+
return Boolean(core || legacy);
|
|
59871
59888
|
}
|
|
59872
59889
|
async function resolvePaymentChain() {
|
|
59873
59890
|
if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "solana") return "solana";
|
|
@@ -59934,6 +59951,40 @@ async function resolveApiKey() {
|
|
|
59934
59951
|
}
|
|
59935
59952
|
return void 0;
|
|
59936
59953
|
}
|
|
59954
|
+
async function fetchCreditBalance(apiKey, timeoutMs = 5e3) {
|
|
59955
|
+
try {
|
|
59956
|
+
const controller = new AbortController();
|
|
59957
|
+
const timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
59958
|
+
const res = await fetch(`${BLOCKRUN_API_KEY_API}/v1/credits`, {
|
|
59959
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
59960
|
+
signal: controller.signal
|
|
59961
|
+
});
|
|
59962
|
+
clearTimeout(timer2);
|
|
59963
|
+
if (!res.ok) return void 0;
|
|
59964
|
+
const b = await res.json();
|
|
59965
|
+
const num2 = (v) => typeof v === "number" ? v : null;
|
|
59966
|
+
return {
|
|
59967
|
+
accountId: typeof b.account_id === "string" ? b.account_id : void 0,
|
|
59968
|
+
billingMode: typeof b.billing_mode === "string" ? b.billing_mode : void 0,
|
|
59969
|
+
currency: typeof b.currency === "string" ? b.currency : "USD",
|
|
59970
|
+
grantedUsd: num2(b.granted_usd),
|
|
59971
|
+
spentUsd: num2(b.spent_usd),
|
|
59972
|
+
remainingUsd: num2(b.remaining_usd),
|
|
59973
|
+
blocked: b.blocked === true,
|
|
59974
|
+
blockedReason: typeof b.blocked_reason === "string" ? b.blocked_reason : void 0
|
|
59975
|
+
};
|
|
59976
|
+
} catch {
|
|
59977
|
+
return void 0;
|
|
59978
|
+
}
|
|
59979
|
+
}
|
|
59980
|
+
function formatCreditBalance(b) {
|
|
59981
|
+
const money = (v) => v === null ? void 0 : `$${v.toFixed(v < 0.01 && v > 0 ? 6 : 2)}`;
|
|
59982
|
+
const remaining = money(b.remainingUsd);
|
|
59983
|
+
const spent = money(b.spentUsd);
|
|
59984
|
+
if (remaining) return `${remaining} remaining${spent ? ` (spent ${spent})` : ""}`;
|
|
59985
|
+
const mode = b.billingMode === "ungated" ? "no prepaid limit" : b.billingMode ?? "unknown";
|
|
59986
|
+
return `${spent ? `spent ${spent}` : "unknown"} \u2014 ${mode}`;
|
|
59987
|
+
}
|
|
59937
59988
|
function createApiKeyFetch(apiKey, baseFetch = fetch) {
|
|
59938
59989
|
return async (input, init2) => {
|
|
59939
59990
|
const headers = new Headers(init2?.headers);
|
|
@@ -59981,7 +60032,7 @@ var init_api_key = __esm({
|
|
|
59981
60032
|
HINTS = {
|
|
59982
60033
|
401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
|
|
59983
60034
|
402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
|
|
59984
|
-
404: `api.blockrun.ai does not serve this endpoint
|
|
60035
|
+
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".`
|
|
59985
60036
|
};
|
|
59986
60037
|
}
|
|
59987
60038
|
});
|
|
@@ -90455,6 +90506,24 @@ var init_client3 = __esm({
|
|
|
90455
90506
|
}
|
|
90456
90507
|
});
|
|
90457
90508
|
|
|
90509
|
+
// node_modules/@x402/svm/dist/esm/v1/index.mjs
|
|
90510
|
+
var v1_exports = {};
|
|
90511
|
+
__export(v1_exports, {
|
|
90512
|
+
ExactSvmSchemeV1: () => ExactSvmSchemeV1,
|
|
90513
|
+
NETWORKS: () => NETWORKS2
|
|
90514
|
+
});
|
|
90515
|
+
var init_v1 = __esm({
|
|
90516
|
+
"node_modules/@x402/svm/dist/esm/v1/index.mjs"() {
|
|
90517
|
+
"use strict";
|
|
90518
|
+
init_chunk_WWACQNRQ();
|
|
90519
|
+
init_chunk_MNNIECS4();
|
|
90520
|
+
init_chunk_WIPN332D();
|
|
90521
|
+
init_chunk_FVTMOTG6();
|
|
90522
|
+
init_chunk_FJ6REVHH();
|
|
90523
|
+
init_chunk_QEBTC3LJ();
|
|
90524
|
+
}
|
|
90525
|
+
});
|
|
90526
|
+
|
|
90458
90527
|
// src/proxy.ts
|
|
90459
90528
|
import { AsyncLocalStorage } from "async_hooks";
|
|
90460
90529
|
import { createHmac } from "crypto";
|
|
@@ -91209,6 +91278,23 @@ function estimateImageCost(model, size5, n = 1) {
|
|
|
91209
91278
|
const pricePerImage = sizePrice ?? pricing.default;
|
|
91210
91279
|
return pricePerImage * n * 1.05;
|
|
91211
91280
|
}
|
|
91281
|
+
function blockrunRequestId(response) {
|
|
91282
|
+
if (!response) return void 0;
|
|
91283
|
+
for (const name of ["x-blockrun-request-id", "x-request-id", "request-id"]) {
|
|
91284
|
+
const value = response.headers.get(name)?.trim();
|
|
91285
|
+
if (value) return value;
|
|
91286
|
+
}
|
|
91287
|
+
return void 0;
|
|
91288
|
+
}
|
|
91289
|
+
function gatewaySettledCostUsd(response) {
|
|
91290
|
+
const raw = response?.headers.get("x-blockrun-cost-usd");
|
|
91291
|
+
if (raw === null || raw === void 0) return void 0;
|
|
91292
|
+
const trimmed = raw.trim();
|
|
91293
|
+
if (trimmed === "") return void 0;
|
|
91294
|
+
const value = Number(trimmed);
|
|
91295
|
+
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
91296
|
+
return value;
|
|
91297
|
+
}
|
|
91212
91298
|
async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
|
|
91213
91299
|
const startTime = Date.now();
|
|
91214
91300
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
@@ -91258,8 +91344,9 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91258
91344
|
res.end();
|
|
91259
91345
|
const latencyMs = Date.now() - startTime;
|
|
91260
91346
|
console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
|
|
91261
|
-
const
|
|
91262
|
-
const
|
|
91347
|
+
const settledCostUsd = gatewaySettledCostUsd(upstream);
|
|
91348
|
+
const paidAmount = settledCostUsd ?? getActualPaymentUsd();
|
|
91349
|
+
const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
|
|
91263
91350
|
paidAmount,
|
|
91264
91351
|
isPhone,
|
|
91265
91352
|
upstreamStatus: upstream.status,
|
|
@@ -91268,6 +91355,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91268
91355
|
});
|
|
91269
91356
|
logUsage({
|
|
91270
91357
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
91358
|
+
...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
|
|
91271
91359
|
model: isBlockrunExa ? "blockrun-exa" : isModalSandbox ? "modal-sandbox" : isPhone ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : isSurf ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : "partner",
|
|
91272
91360
|
tier: isPhone ? "PHONE" : isSurf ? "SURF" : "PARTNER",
|
|
91273
91361
|
cost: requestCost,
|
|
@@ -91459,8 +91547,20 @@ async function startProxy(options) {
|
|
|
91459
91547
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91460
91548
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
91461
91549
|
solanaAddress = solanaSigner.address;
|
|
91550
|
+
const solanaRpcUrl = process["env"].CLAWROUTER_SOLANA_RPC_URL;
|
|
91462
91551
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91552
|
+
if (solanaRpcUrl) {
|
|
91553
|
+
const { ExactSvmScheme: ExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91554
|
+
const { ExactSvmSchemeV1: ExactSvmSchemeV12, NETWORKS: SVM_V1_NETWORKS } = await Promise.resolve().then(() => (init_v1(), v1_exports));
|
|
91555
|
+
x402.register("solana:*", new ExactSvmScheme2(solanaSigner, { rpcUrl: solanaRpcUrl }));
|
|
91556
|
+
for (const network of SVM_V1_NETWORKS) {
|
|
91557
|
+
x402.registerV1(network, new ExactSvmSchemeV12(solanaSigner, { rpcUrl: solanaRpcUrl }));
|
|
91558
|
+
}
|
|
91559
|
+
}
|
|
91463
91560
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91561
|
+
if (solanaRpcUrl) {
|
|
91562
|
+
console.log(`[ClawRouter] Solana RPC (payment signing + balance): ${solanaRpcUrl}`);
|
|
91563
|
+
}
|
|
91464
91564
|
}
|
|
91465
91565
|
x402?.onAfterPaymentCreation(async (context) => {
|
|
91466
91566
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
@@ -91560,7 +91660,15 @@ async function startProxy(options) {
|
|
|
91560
91660
|
)
|
|
91561
91661
|
]);
|
|
91562
91662
|
if (authMode === "api-key") {
|
|
91563
|
-
|
|
91663
|
+
const credit = await fetchCreditBalance(apiKey, BALANCE_CHECK_TIMEOUT_MS);
|
|
91664
|
+
response.balance = credit?.remainingUsd ?? null;
|
|
91665
|
+
response.creditSpent = credit?.spentUsd ?? null;
|
|
91666
|
+
response.creditGranted = credit?.grantedUsd ?? null;
|
|
91667
|
+
response.billingMode = credit?.billingMode ?? null;
|
|
91668
|
+
response.creditSummary = credit ? formatCreditBalance(credit) : null;
|
|
91669
|
+
response.blocked = credit?.blocked ?? false;
|
|
91670
|
+
if (credit?.blockedReason) response.blockedReason = credit.blockedReason;
|
|
91671
|
+
response.isEmpty = credit?.remainingUsd !== null && (credit?.remainingUsd ?? 1) <= 0;
|
|
91564
91672
|
response.billing = "BlockRun account credit";
|
|
91565
91673
|
response.topUpUrl = PORTAL_CREDITS_URL;
|
|
91566
91674
|
} else {
|
|
@@ -92365,7 +92473,8 @@ async function startProxy(options) {
|
|
|
92365
92473
|
}
|
|
92366
92474
|
return;
|
|
92367
92475
|
}
|
|
92368
|
-
|
|
92476
|
+
const isVerbatimGatewayPath = req.url === "/v1/messages" || (req.url?.startsWith("/v1/messages?") ?? false) || req.url === "/v1/audio/speech" || (req.url?.startsWith("/v1/audio/speech?") ?? false);
|
|
92477
|
+
if (isVerbatimGatewayPath || req.url?.match(
|
|
92369
92478
|
/^\/v1\/(?:partner|pm|exa|modal|stocks|usstock|crypto|fx|commodity|phone|voice|surf)\//
|
|
92370
92479
|
)) {
|
|
92371
92480
|
try {
|
|
@@ -92397,6 +92506,19 @@ async function startProxy(options) {
|
|
|
92397
92506
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
92398
92507
|
return;
|
|
92399
92508
|
}
|
|
92509
|
+
if (!req.url.includes("/chat/completions")) {
|
|
92510
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
92511
|
+
res.end(
|
|
92512
|
+
JSON.stringify({
|
|
92513
|
+
error: {
|
|
92514
|
+
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.`,
|
|
92515
|
+
type: "invalid_request_error",
|
|
92516
|
+
code: "unsupported_endpoint"
|
|
92517
|
+
}
|
|
92518
|
+
})
|
|
92519
|
+
);
|
|
92520
|
+
return;
|
|
92521
|
+
}
|
|
92400
92522
|
try {
|
|
92401
92523
|
await proxyRequest(
|
|
92402
92524
|
req,
|
|
@@ -92651,7 +92773,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92651
92773
|
errorBody,
|
|
92652
92774
|
errorStatus: response.status,
|
|
92653
92775
|
isProviderError: category !== null,
|
|
92654
|
-
errorCategory: category ?? void 0
|
|
92776
|
+
errorCategory: category ?? void 0,
|
|
92777
|
+
upstreamRequestId: blockrunRequestId(response)
|
|
92655
92778
|
};
|
|
92656
92779
|
}
|
|
92657
92780
|
const contentType = response.headers.get("content-type") || "";
|
|
@@ -92688,9 +92811,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92688
92811
|
errorCategory: "payment_error"
|
|
92689
92812
|
};
|
|
92690
92813
|
}
|
|
92814
|
+
const networkMsg = describeFetchError(err);
|
|
92815
|
+
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" : "";
|
|
92816
|
+
console.error(`[ClawRouter] ${modelId} network error: ${networkMsg}${solanaSigningHint}`);
|
|
92691
92817
|
return {
|
|
92692
92818
|
success: false,
|
|
92693
|
-
errorBody:
|
|
92819
|
+
errorBody: `${networkMsg}${solanaSigningHint}`,
|
|
92694
92820
|
errorStatus: 500,
|
|
92695
92821
|
isProviderError: true
|
|
92696
92822
|
// Network errors are retryable
|
|
@@ -92723,6 +92849,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
92723
92849
|
let accumulatedContent = "";
|
|
92724
92850
|
let responseInputTokens;
|
|
92725
92851
|
let responseOutputTokens;
|
|
92852
|
+
let upstreamRequestId;
|
|
92726
92853
|
let requestHadError = false;
|
|
92727
92854
|
let requestSummaryForStore = "";
|
|
92728
92855
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
@@ -94129,7 +94256,8 @@ data: [DONE]
|
|
|
94129
94256
|
}
|
|
94130
94257
|
lastError = {
|
|
94131
94258
|
body: result.errorBody || "Unknown error",
|
|
94132
|
-
status: result.errorStatus || 500
|
|
94259
|
+
status: result.errorStatus || 500,
|
|
94260
|
+
requestId: result.upstreamRequestId
|
|
94133
94261
|
};
|
|
94134
94262
|
failedAttempts.push({
|
|
94135
94263
|
model: tryModel,
|
|
@@ -94201,7 +94329,8 @@ data: [DONE]
|
|
|
94201
94329
|
}
|
|
94202
94330
|
lastError = {
|
|
94203
94331
|
body: retryResult.errorBody || lastError?.body || "Unknown error",
|
|
94204
|
-
status: retryResult.errorStatus || lastError?.status || 500
|
|
94332
|
+
status: retryResult.errorStatus || lastError?.status || 500,
|
|
94333
|
+
requestId: retryResult.upstreamRequestId ?? lastError?.requestId
|
|
94205
94334
|
};
|
|
94206
94335
|
failedAttempts.push({
|
|
94207
94336
|
model: tryModel,
|
|
@@ -94343,10 +94472,13 @@ data: [DONE]
|
|
|
94343
94472
|
}
|
|
94344
94473
|
}
|
|
94345
94474
|
}
|
|
94475
|
+
upstreamRequestId = blockrunRequestId(upstream);
|
|
94346
94476
|
if (!upstream) {
|
|
94347
94477
|
const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
|
|
94348
94478
|
const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
|
|
94349
|
-
console.log(
|
|
94479
|
+
console.log(
|
|
94480
|
+
`[ClawRouter] ${structuredMessage}${lastError?.requestId ? ` | gateway request id: ${lastError.requestId}` : ""}`
|
|
94481
|
+
);
|
|
94350
94482
|
const rawErrBody = lastError?.body || structuredMessage;
|
|
94351
94483
|
const errStatus = lastError?.status || 502;
|
|
94352
94484
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
@@ -94384,7 +94516,8 @@ data: [DONE]
|
|
|
94384
94516
|
res.writeHead(errStatus, {
|
|
94385
94517
|
"Content-Type": "application/json",
|
|
94386
94518
|
"x-context-used-kb": String(originalContextSizeKB),
|
|
94387
|
-
"x-context-limit-kb": String(CONTEXT_LIMIT_KB)
|
|
94519
|
+
"x-context-limit-kb": String(CONTEXT_LIMIT_KB),
|
|
94520
|
+
...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
|
|
94388
94521
|
});
|
|
94389
94522
|
res.end(transformedErr);
|
|
94390
94523
|
deduplicator.complete(dedupKey, {
|
|
@@ -94848,12 +94981,24 @@ data: [DONE]
|
|
|
94848
94981
|
maxTokens,
|
|
94849
94982
|
routingProfile ?? void 0
|
|
94850
94983
|
);
|
|
94851
|
-
|
|
94984
|
+
const apiKeyPricing = options.apiKey ? routerOpts.modelPricing.get(logModel) : void 0;
|
|
94985
|
+
if (FREE_MODELS.has(logModel)) {
|
|
94986
|
+
logCost = 0;
|
|
94987
|
+
logSavings = 1;
|
|
94988
|
+
} else if (apiKeyPricing) {
|
|
94989
|
+
const inTokens = responseInputTokens ?? chargedInputTokens;
|
|
94990
|
+
const outTokens = responseOutputTokens ?? 0;
|
|
94991
|
+
logCost = (inTokens * (apiKeyPricing.inputPrice ?? 0) + outTokens * (apiKeyPricing.outputPrice ?? 0)) / 1e6;
|
|
94992
|
+
logSavings = costs.baselineCost > 0 ? Math.max(0, (costs.baselineCost - logCost) / costs.baselineCost) : 0;
|
|
94993
|
+
} else {
|
|
94994
|
+
logCost = costs.costEstimate;
|
|
94995
|
+
logSavings = costs.savings;
|
|
94996
|
+
}
|
|
94852
94997
|
logBaseline = costs.baselineCost;
|
|
94853
|
-
logSavings = FREE_MODELS.has(logModel) ? 1 : costs.savings;
|
|
94854
94998
|
}
|
|
94855
94999
|
const entry = {
|
|
94856
95000
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95001
|
+
...upstreamRequestId ? { requestId: upstreamRequestId } : {},
|
|
94857
95002
|
model: logModel,
|
|
94858
95003
|
tier: routingDecision?.tier ?? "DIRECT",
|
|
94859
95004
|
cost: logCost,
|
|
@@ -94893,6 +95038,7 @@ var init_proxy = __esm({
|
|
|
94893
95038
|
init_api_key();
|
|
94894
95039
|
init_spend_control();
|
|
94895
95040
|
init_compression();
|
|
95041
|
+
init_errors5();
|
|
94896
95042
|
init_version4();
|
|
94897
95043
|
init_session();
|
|
94898
95044
|
init_updater();
|
|
@@ -229338,7 +229484,15 @@ ${errText}`
|
|
|
229338
229484
|
});
|
|
229339
229485
|
if (!isGatewayMode()) {
|
|
229340
229486
|
if (shouldLogRegistration) {
|
|
229341
|
-
|
|
229487
|
+
void (async () => {
|
|
229488
|
+
const keyResolution = await resolveApiKey().catch(() => void 0);
|
|
229489
|
+
if (keyResolution) {
|
|
229490
|
+
api.logger.info(
|
|
229491
|
+
`Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`
|
|
229492
|
+
);
|
|
229493
|
+
return;
|
|
229494
|
+
}
|
|
229495
|
+
const { address: address2, source } = await resolveOrGenerateWalletKey();
|
|
229342
229496
|
if (source === "generated") {
|
|
229343
229497
|
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`);
|
|
229344
229498
|
api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
|
|
@@ -229355,7 +229509,7 @@ ${errText}`
|
|
|
229355
229509
|
} else {
|
|
229356
229510
|
api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address2}`);
|
|
229357
229511
|
}
|
|
229358
|
-
}).catch((err) => {
|
|
229512
|
+
})().catch((err) => {
|
|
229359
229513
|
api.logger.warn(
|
|
229360
229514
|
`Failed to initialize wallet: ${err instanceof Error ? err.message : String(err)}`
|
|
229361
229515
|
);
|
package/docs/configuration.md
CHANGED
|
@@ -209,7 +209,8 @@ Per-request alternative: send `x-clawrouter-debug: false` on the request.
|
|
|
209
209
|
|
|
210
210
|
### CLAWROUTER_SOLANA_RPC_URL
|
|
211
211
|
|
|
212
|
-
Override the Solana RPC endpoint used for USDC balance checks
|
|
212
|
+
Override the Solana RPC endpoint used for USDC balance checks **and for signing
|
|
213
|
+
payments** (Solana chain only):
|
|
213
214
|
|
|
214
215
|
```bash
|
|
215
216
|
export CLAWROUTER_SOLANA_RPC_URL=https://your-rpc-provider.com
|
|
@@ -218,6 +219,21 @@ openclaw gateway restart
|
|
|
218
219
|
|
|
219
220
|
Public RPC may rate-limit on heavy usage. Use a dedicated RPC for production.
|
|
220
221
|
|
|
222
|
+
Unlike Base, where the EIP-3009 signature is produced entirely offline, signing
|
|
223
|
+
a Solana payment reads the payment asset's mint account over RPC. A host that
|
|
224
|
+
cannot reach `api.mainnet-beta.solana.com` therefore fails **every** paid Solana
|
|
225
|
+
call with `fetch failed` while `sol.blockrun.ai` itself answers normally. Check
|
|
226
|
+
egress before blaming the gateway:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
curl -s -m 10 -X POST https://api.mainnet-beta.solana.com \
|
|
230
|
+
-H 'content-type: application/json' \
|
|
231
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",{"encoding":"base64"}]}'
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
If that hangs or is refused, point this variable at an endpoint the host can
|
|
235
|
+
reach.
|
|
236
|
+
|
|
221
237
|
---
|
|
222
238
|
|
|
223
239
|
## Wallet Configuration
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/clawrouter",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.271",
|
|
4
4
|
"description": "Smart LLM router — save 84% on inference costs. 76 models (7 free). Pay by credit card (API key) or USDC (x402 wallet).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -53,7 +53,7 @@ metadata:
|
|
|
53
53
|
|
|
54
54
|
# ClawRouter
|
|
55
55
|
|
|
56
|
-
Hosted-gateway LLM router that saves <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% on inference costs by forwarding each request to the blockrun.ai gateway, which picks the cheapest model capable of handling it across <!-- br:models.chatVisible -->76<!-- /br:models.chatVisible --> models from 9 providers (<!-- br:models.free -->7<!-- /br:models.free --> free open-weight models). All billing flows through one USDC wallet
|
|
56
|
+
Hosted-gateway LLM router that saves <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% on inference costs by forwarding each request to the blockrun.ai gateway, which picks the cheapest model capable of handling it across <!-- br:models.chatVisible -->76<!-- /br:models.chatVisible --> models from 9 providers (<!-- br:models.free -->7<!-- /br:models.free --> free open-weight models). All billing flows through one BlockRun credential — either a USDC wallet paying x402 per call, or a BlockRun API key drawing on card-funded account credit. Either way you do not hold provider API keys.
|
|
57
57
|
|
|
58
58
|
**This is not a local-inference tool.** ClawRouter is a thin local proxy. Your prompts are sent over HTTPS to the blockrun.ai gateway for model execution. If your workload requires inference that never leaves your machine, use a local runtime like Ollama — ClawRouter is not the right tool for that use case.
|
|
59
59
|
|
|
@@ -69,7 +69,7 @@ Your app → localhost proxy (ClawRouter) → https://blockrun.ai/api (or sol.b
|
|
|
69
69
|
Response → back through proxy → your app
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
**Sent to blockrun.ai on every request:** the model name, the full prompt/messages body, sampling params (temperature, max_tokens, tools, etc.), and an `X-PAYMENT` header containing a signed x402 USDC micropayment.
|
|
72
|
+
**Sent to blockrun.ai on every request:** the model name, the full prompt/messages body, sampling params (temperature, max_tokens, tools, etc.), and a payment credential — an `X-PAYMENT` header containing a signed x402 USDC micropayment in wallet mode, or an `Authorization: Bearer brk_live_…` header in API-key mode.
|
|
73
73
|
|
|
74
74
|
**Not sent:** your wallet private key (only the detached payment signature is sent), any other local files, environment variables, or OpenClaw config beyond what's needed for this request.
|
|
75
75
|
|
|
@@ -81,12 +81,12 @@ ClawRouter does **not** collect or forward third-party provider API keys. You do
|
|
|
81
81
|
|
|
82
82
|
**What `models.providers.blockrun` stores (fully enumerated):**
|
|
83
83
|
|
|
84
|
-
| Field | Sensitive
|
|
85
|
-
| ----------- |
|
|
86
|
-
| `walletKey` |
|
|
87
|
-
| `solanaKey` | Yes
|
|
88
|
-
| `gateway` | No
|
|
89
|
-
| `routing` | No
|
|
84
|
+
| Field | Sensitive | Purpose |
|
|
85
|
+
| ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `walletKey` | Only in wallet mode | EVM private key used to sign USDC micropayments via x402. **Auto-generated locally on first run** — no user input required. Never transmitted over the network; only detached payment signatures are sent. Not used, read, or generated at all when a BlockRun API key is configured. |
|
|
87
|
+
| `solanaKey` | Yes | Solana keypair (BIP-44 `m/44'/501'/0'/0'`). Auto-derived from the same local mnemonic via `@scure/bip32` + `@scure/bip39`. |
|
|
88
|
+
| `gateway` | No | Gateway URL. Defaults: `https://sol.blockrun.ai/api` (Solana, default chain for new installs) · `https://blockrun.ai/api` (Base, default for pre-existing installs). |
|
|
89
|
+
| `routing` | No | Optional override of the router-core config (tier chains, `strategy: "rules"` rollback, `shadow` comparison, scorer keywords). |
|
|
90
90
|
|
|
91
91
|
**How and where keys are stored:**
|
|
92
92
|
|
package/skills/imagegen/SKILL.md
CHANGED
|
@@ -137,8 +137,8 @@ Response is identical to generation:
|
|
|
137
137
|
|
|
138
138
|
## Notes
|
|
139
139
|
|
|
140
|
-
- Payment is automatic
|
|
141
|
-
- If the call fails with a payment error,
|
|
140
|
+
- Payment is automatic, and which rail it uses depends on how ClawRouter was started: an x402 USDC micropayment from the user's wallet, or a draw on account credit if a BlockRun API key is configured. Either way the agent does nothing.
|
|
141
|
+
- If the call fails with a payment error, check `GET http://localhost:8402/health` and read `authMode` before telling the user how to fix it: `wallet` → fund the wallet at [blockrun.ai](https://blockrun.ai); `api-key` → top up account credit at [user.blockrun.ai/dashboard/credits](https://user.blockrun.ai/dashboard/credits). Naming the wrong one sends the user to a page that cannot fix their error.
|
|
142
142
|
- Google models may return base64 internally — ClawRouter uploads automatically and returns a hosted URL
|
|
143
143
|
- OpenAI image models enforce OpenAI content policy; use `nano-banana` or `grok-imagine` for more flexibility
|
|
144
144
|
- Image editing is only available with `gpt-image-1`; generation supports all listed models
|
package/skills/phone/SKILL.md
CHANGED
|
@@ -200,8 +200,9 @@ GET `http://localhost:8402/v1/voice/call/{call_id}`. Returns:
|
|
|
200
200
|
|
|
201
201
|
## Notes
|
|
202
202
|
|
|
203
|
-
- Payment is automatic
|
|
204
|
-
- If a call fails with a 402,
|
|
203
|
+
- Payment is automatic on every call — an x402 USDC micropayment from the user's wallet, or a draw on account credit if a BlockRun API key is configured.
|
|
204
|
+
- If a call fails with a 402, check `GET http://localhost:8402/health` and read `authMode` first: `wallet` → fund the wallet at [blockrun.ai](https://blockrun.ai); `api-key` → top up at [user.blockrun.ai/dashboard/credits](https://user.blockrun.ai/dashboard/credits).
|
|
205
|
+
- Number leases are bound to the **wallet's** payer address, so `phone numbers list/buy/renew/release` and the `from` parameter are wallet-mode features. In API-key mode the gateway answers those routes with a raw `402` x402 challenge (verified 2026-09-05) because there is no payer address to bind a lease to. Lookups, fraud checks and outbound calls all work on an API key; just let the server auto-pick `from`.
|
|
205
206
|
- Phone numbers are real, regulated resources — numbers bought are reachable from any phone within ~60 seconds of purchase
|
|
206
207
|
- Bland.ai's emergency-number blocklist is enforced server-side; ClawRouter does not duplicate it but trusts upstream
|
|
207
208
|
- Recordings and transcripts are retained by Bland.ai; ClawRouter does not download them locally (returns the upstream URL)
|
package/skills/predexon/SKILL.md
CHANGED
|
@@ -33,7 +33,7 @@ metadata: { "openclaw": { "emoji": "📊", "requires": { "config": ["models.prov
|
|
|
33
33
|
|
|
34
34
|
**IMPORTANT: Always use this skill for any Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, or prediction market request. Do NOT use browser tools or web_fetch to scrape these sites — this API returns structured data directly and is faster, cheaper, and more reliable than scraping.**
|
|
35
35
|
|
|
36
|
-
Real-time prediction market data (Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, dFlow, Binance, UMA oracle) via BlockRun's
|
|
36
|
+
Real-time prediction market data (Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, dFlow, Binance, UMA oracle) via BlockRun's gateway. Payment is automatic — an x402 USDC micropayment from the user's wallet, or a draw on BlockRun account credit if an API key is configured (both verified 2026-09-05).
|
|
37
37
|
|
|
38
38
|
**All responses are wrapped:** `{ "data": { ... } }` — always read from `response.data`.
|
|
39
39
|
|
package/skills/surf/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: surf
|
|
3
|
-
description: Use this skill — NOT browser or web_fetch — for ALL Surf crypto-data calls. 83 endpoints at localhost:8402/v1/surf/* covering CEX/DEX markets, on-chain SQL over 80+ ClickHouse tables (Ethereum, Base, Arbitrum, BSC, TRON, HyperEVM, Tempo), 100M+ labeled wallets, prediction markets (Polymarket + Kalshi), social/CT intelligence, news, project + DeFi metrics, token analytics, unified search, VC fund intelligence.
|
|
3
|
+
description: Use this skill — NOT browser or web_fetch — for ALL Surf crypto-data calls. 83 endpoints at localhost:8402/v1/surf/* covering CEX/DEX markets, on-chain SQL over 80+ ClickHouse tables (Ethereum, Base, Arbitrum, BSC, TRON, HyperEVM, Tempo), 100M+ labeled wallets, prediction markets (Polymarket + Kalshi), social/CT intelligence, news, project + DeFi metrics, token analytics, unified search, VC fund intelligence. Paid through ClawRouter — x402 USDC from the local wallet, or BlockRun account credit if an API key is configured. No Surf account or Surf API key required either way.
|
|
4
4
|
triggers:
|
|
5
5
|
- "blockrun surf"
|
|
6
6
|
- "surf crypto api"
|
|
@@ -24,19 +24,35 @@ license: MIT
|
|
|
24
24
|
|
|
25
25
|
# Surf — Unified Crypto Data API (via ClawRouter)
|
|
26
26
|
|
|
27
|
-
Surf bundles **83 endpoints across 12 domains** into one paid HTTP API. ClawRouter exposes them at `http://127.0.0.1:8402/v1/surf/*`, paid
|
|
27
|
+
Surf bundles **83 endpoints across 12 domains** into one paid HTTP API. ClawRouter exposes them at `http://127.0.0.1:8402/v1/surf/*`, paid the same way ClawRouter pays for LLM calls — an x402 USDC micropayment from the local wallet, or a draw on BlockRun account credit if an API key is configured (verified working on both, 2026-09-05). **No Surf account and no Surf API key either way.** Upstream lives at `api.asksurf.ai/gateway/v1` — ClawRouter forwards transparently.
|
|
28
28
|
|
|
29
|
-
**Pricing
|
|
29
|
+
**Pricing — flat per call, the same for every endpoint.**
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
Surf used to be tiered at $0.001 / $0.005 / $0.020. It is not any more: upstream
|
|
32
|
+
now prices all three tiers identically (`SURF_TIER_{1,2,3}_PRICE` are equal). The
|
|
33
|
+
`Tier` column in the catalog below still describes endpoint weight, but it no
|
|
34
|
+
longer affects what you pay. Anything quoting the old tier prices is stale.
|
|
35
|
+
|
|
36
|
+
| Rail | Price per call |
|
|
37
|
+
| --------------------------- | ------------------------------------------- |
|
|
38
|
+
| API key (`api.blockrun.ai`) | **$0.0075** — base rate, no transaction fee |
|
|
39
|
+
| Wallet, Solana | **$0.0075** |
|
|
40
|
+
| Wallet, Base | **$0.0085** |
|
|
41
|
+
|
|
42
|
+
Verified 2026-09-05 by measurement, not from a price page: the API-key figure by
|
|
43
|
+
diffing the account ledger across one call, the wallet figures by reading the
|
|
44
|
+
`amount` in the x402 402 challenge, which quotes the price before anything is
|
|
45
|
+
signed. Base and Solana genuinely differ for the same endpoint.
|
|
46
|
+
|
|
47
|
+
**The 402 quote is always authoritative.** On the wallet rail the price arrives
|
|
48
|
+
in the challenge before you pay, so if this table ever disagrees with it, believe
|
|
49
|
+
the challenge and treat the table as stale.
|
|
34
50
|
|
|
35
51
|
> The legacy **surf-1.5 chat** surface is intentionally NOT exposed yet — it's held until per-token settlement is wired. Trying `/v1/surf/chat/completions` returns 404 ("Unknown Surf endpoint"), no payment is taken.
|
|
36
52
|
|
|
37
|
-
All requests use GET unless the table below says otherwise. Path parameters that look like `?symbol=` are query params on a GET. POST endpoints take a JSON body. ClawRouter
|
|
53
|
+
All requests use GET unless the table below says otherwise. Path parameters that look like `?symbol=` are query params on a GET. POST endpoints take a JSON body. ClawRouter attaches payment transparently — an x402 header in wallet mode, a bearer token in API-key mode.
|
|
38
54
|
|
|
39
|
-
**Required-param pre-check.** 56 of the 83 endpoints have required query params (e.g. `pair`, `symbol`, `address`, `chain`, `q`, `interval`). The route validates them **before settlement** — call with missing params and you get `400 { missing_params, all_required, docs }` and
|
|
55
|
+
**Required-param pre-check.** 56 of the 83 endpoints have required query params (e.g. `pair`, `symbol`, `address`, `chain`, `q`, `interval`). The route validates them **before settlement** — call with missing params and you get `400 { missing_params, all_required, docs }` and you are NOT charged. Check each row in the catalog below for the correct param name (e.g. mindshare is `q` + `interval`, not `project` + `window`).
|
|
40
56
|
|
|
41
57
|
## When to use this skill
|
|
42
58
|
|