@blockrun/clawrouter 0.12.272 → 0.12.274
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 +198 -44
- package/dist/index.d.ts +23 -17
- package/dist/index.js +198 -44
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -32378,6 +32378,40 @@ function registerSpendPolicyHook(x402, control) {
|
|
|
32378
32378
|
}
|
|
32379
32379
|
});
|
|
32380
32380
|
}
|
|
32381
|
+
function settledChargeUsd(response) {
|
|
32382
|
+
const raw = response.headers.get("x-blockrun-cost-usd");
|
|
32383
|
+
if (raw === null) return void 0;
|
|
32384
|
+
const trimmed = raw.trim();
|
|
32385
|
+
if (trimmed === "") return void 0;
|
|
32386
|
+
const value = Number(trimmed);
|
|
32387
|
+
return Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
32388
|
+
}
|
|
32389
|
+
function withSpendPolicy(inner, control, consumeEstimateUsd) {
|
|
32390
|
+
return async (input, init2) => {
|
|
32391
|
+
const published = consumeEstimateUsd();
|
|
32392
|
+
if (published === void 0 || !control.hasAmountLimits()) return inner(input, init2);
|
|
32393
|
+
const estimate = Number.isFinite(published) && published > 0 ? published : 0;
|
|
32394
|
+
const result = control.checkAmount(estimate);
|
|
32395
|
+
if (!result.allowed) {
|
|
32396
|
+
throw new SpendPolicyError(result.reason ?? "blocked by spend policy", {
|
|
32397
|
+
blockedBy: result.blockedBy
|
|
32398
|
+
});
|
|
32399
|
+
}
|
|
32400
|
+
const reservationId = control.hasAggregateLimits() ? control.reserve(estimate) : void 0;
|
|
32401
|
+
let response;
|
|
32402
|
+
try {
|
|
32403
|
+
response = await inner(input, init2);
|
|
32404
|
+
} catch (err) {
|
|
32405
|
+
if (reservationId !== void 0) control.releaseReservation(reservationId);
|
|
32406
|
+
throw err;
|
|
32407
|
+
}
|
|
32408
|
+
const settled = settledChargeUsd(response);
|
|
32409
|
+
const charged = response.ok ? settled ?? estimate : settled ?? 0;
|
|
32410
|
+
if (reservationId !== void 0) control.releaseReservation(reservationId);
|
|
32411
|
+
if (charged > 0) control.record(charged, { action: "BlockRun account charge" });
|
|
32412
|
+
return response;
|
|
32413
|
+
};
|
|
32414
|
+
}
|
|
32381
32415
|
function formatDuration(seconds) {
|
|
32382
32416
|
if (seconds < 60) {
|
|
32383
32417
|
return `${seconds}s`;
|
|
@@ -32707,6 +32741,29 @@ var init_spend_control = __esm({
|
|
|
32707
32741
|
};
|
|
32708
32742
|
}
|
|
32709
32743
|
}
|
|
32744
|
+
return this.checkAmount(estimatedCost2);
|
|
32745
|
+
}
|
|
32746
|
+
/**
|
|
32747
|
+
* The amount windows alone, with no counterparty to inspect.
|
|
32748
|
+
*
|
|
32749
|
+
* `perRequest` / `hourly` / `daily` / `session` are denominated in USD and
|
|
32750
|
+
* say nothing about how the money moves, so they are the part of the policy
|
|
32751
|
+
* that means the same thing on the API-key rail as on the wallet rail — a
|
|
32752
|
+
* daily cap is as sensible against account credit as against USDC, and both
|
|
32753
|
+
* are read from the same `spending.json` (#329). The counterparty lists are
|
|
32754
|
+
* the part that does NOT translate: `blockedPayees`, `allowedPayees`,
|
|
32755
|
+
* `blockedNetworks` and `allowedAssets` presuppose a payee, a network and an
|
|
32756
|
+
* asset, and on the key rail there is one counterparty and no on-chain asset.
|
|
32757
|
+
* They are vacuous there rather than missing, which is why `check()` layers
|
|
32758
|
+
* them on top of this instead of the other way round.
|
|
32759
|
+
*/
|
|
32760
|
+
checkAmount(estimatedCost2) {
|
|
32761
|
+
if (this.policyFileBroken !== void 0) {
|
|
32762
|
+
return {
|
|
32763
|
+
allowed: false,
|
|
32764
|
+
reason: `Spend policy is unreadable, refusing all payments: ${this.policyFileBroken}`
|
|
32765
|
+
};
|
|
32766
|
+
}
|
|
32710
32767
|
const now2 = this.now();
|
|
32711
32768
|
if (this.limits.perRequest !== void 0) {
|
|
32712
32769
|
if (estimatedCost2 > this.limits.perRequest) {
|
|
@@ -59882,6 +59939,28 @@ Then run: npx @blockrun/clawrouter`
|
|
|
59882
59939
|
solanaPrivateKeyBytes
|
|
59883
59940
|
};
|
|
59884
59941
|
}
|
|
59942
|
+
async function warnIfLegacyWalletDiffers(coreAddress) {
|
|
59943
|
+
if (legacyWalletDivergenceWarned) return;
|
|
59944
|
+
let legacyAddress;
|
|
59945
|
+
try {
|
|
59946
|
+
const saved = await loadSavedWallet();
|
|
59947
|
+
if (!saved) return;
|
|
59948
|
+
legacyAddress = privateKeyToAccount(saved).address;
|
|
59949
|
+
} catch {
|
|
59950
|
+
return;
|
|
59951
|
+
}
|
|
59952
|
+
if (legacyAddress === coreAddress) return;
|
|
59953
|
+
legacyWalletDivergenceWarned = true;
|
|
59954
|
+
console.warn(
|
|
59955
|
+
`[ClawRouter] \u26A0 Two different wallets are configured. Paying from the BlockRun Core wallet ${coreAddress}.`
|
|
59956
|
+
);
|
|
59957
|
+
console.warn(
|
|
59958
|
+
`[ClawRouter] ClawRouter's older wallet ${legacyAddress} (${WALLET_FILE}) is NOT being used \u2014 if that is the funded one, requests will fail on an empty balance.`
|
|
59959
|
+
);
|
|
59960
|
+
console.warn(
|
|
59961
|
+
`[ClawRouter] To keep paying from it: export BLOCKRUN_WALLET_KEY=$(cat ${WALLET_FILE})`
|
|
59962
|
+
);
|
|
59963
|
+
}
|
|
59885
59964
|
async function resolveExistingWalletKey() {
|
|
59886
59965
|
const coreSolanaKey = await loadCoreSolanaKeyForSelectedChain();
|
|
59887
59966
|
const envKey = process["env"].BLOCKRUN_WALLET_KEY;
|
|
@@ -59897,6 +59976,7 @@ async function resolveExistingWalletKey() {
|
|
|
59897
59976
|
const core = await loadCoreWallet();
|
|
59898
59977
|
if (core) {
|
|
59899
59978
|
const account = privateKeyToAccount(core);
|
|
59979
|
+
await warnIfLegacyWalletDiffers(account.address);
|
|
59900
59980
|
return {
|
|
59901
59981
|
key: core,
|
|
59902
59982
|
address: account.address,
|
|
@@ -60064,13 +60144,28 @@ async function savePaymentChain(chain3) {
|
|
|
60064
60144
|
}
|
|
60065
60145
|
async function loadPaymentChain() {
|
|
60066
60146
|
const core = await readOptional(CORE_CHAIN_FILE);
|
|
60067
|
-
if (core === "solana") return "solana";
|
|
60068
|
-
if (core === "base") return "base";
|
|
60069
60147
|
const legacy = await readOptional(CHAIN_FILE);
|
|
60148
|
+
if (core === "solana" || core === "base") {
|
|
60149
|
+
if ((legacy === "solana" || legacy === "base") && legacy !== core) {
|
|
60150
|
+
warnChainOverride(core, legacy);
|
|
60151
|
+
}
|
|
60152
|
+
return core;
|
|
60153
|
+
}
|
|
60070
60154
|
if (legacy === "solana") return "solana";
|
|
60071
60155
|
if (legacy === "base") return "base";
|
|
60072
60156
|
return await hasExistingWallet() ? "base" : "solana";
|
|
60073
60157
|
}
|
|
60158
|
+
function warnChainOverride(core, legacy) {
|
|
60159
|
+
if (chainOverrideWarned) return;
|
|
60160
|
+
chainOverrideWarned = true;
|
|
60161
|
+
console.warn(
|
|
60162
|
+
`[ClawRouter] \u26A0 Payment chain is ${core} (from ${CORE_CHAIN_FILE}), overriding this install's saved ${legacy}.`
|
|
60163
|
+
);
|
|
60164
|
+
console.warn(
|
|
60165
|
+
`[ClawRouter] A different chain means a different signer and gateway \u2014 funds on ${legacy} will not be spendable.`
|
|
60166
|
+
);
|
|
60167
|
+
console.warn(`[ClawRouter] To go back: npx @blockrun/clawrouter wallet ${legacy}`);
|
|
60168
|
+
}
|
|
60074
60169
|
async function hasExistingWallet() {
|
|
60075
60170
|
if (process["env"].BLOCKRUN_WALLET_KEY?.trim()) return true;
|
|
60076
60171
|
const [core, legacy] = await Promise.all([
|
|
@@ -60084,7 +60179,7 @@ async function resolvePaymentChain() {
|
|
|
60084
60179
|
if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "base") return "base";
|
|
60085
60180
|
return loadPaymentChain();
|
|
60086
60181
|
}
|
|
60087
|
-
var WALLET_DIR2, WALLET_FILE, MNEMONIC_FILE, CHAIN_FILE, CORE_WALLET_DIR, CORE_WALLET_FILE, CORE_SOLANA_WALLET_FILE, CORE_CHAIN_FILE;
|
|
60182
|
+
var WALLET_DIR2, WALLET_FILE, MNEMONIC_FILE, CHAIN_FILE, CORE_WALLET_DIR, CORE_WALLET_FILE, CORE_SOLANA_WALLET_FILE, CORE_CHAIN_FILE, legacyWalletDivergenceWarned, chainOverrideWarned;
|
|
60088
60183
|
var init_auth = __esm({
|
|
60089
60184
|
"src/auth.ts"() {
|
|
60090
60185
|
"use strict";
|
|
@@ -60100,6 +60195,8 @@ var init_auth = __esm({
|
|
|
60100
60195
|
CORE_WALLET_FILE = join7(CORE_WALLET_DIR, ".session");
|
|
60101
60196
|
CORE_SOLANA_WALLET_FILE = join7(CORE_WALLET_DIR, ".solana-session");
|
|
60102
60197
|
CORE_CHAIN_FILE = join7(CORE_WALLET_DIR, ".chain");
|
|
60198
|
+
legacyWalletDivergenceWarned = false;
|
|
60199
|
+
chainOverrideWarned = false;
|
|
60103
60200
|
}
|
|
60104
60201
|
});
|
|
60105
60202
|
|
|
@@ -91138,7 +91235,12 @@ async function checkExistingProxy(port) {
|
|
|
91138
91235
|
const data = await response.json();
|
|
91139
91236
|
const authMode = data.authMode === "api-key" ? "api-key" : "wallet";
|
|
91140
91237
|
if (data.status === "ok" && (data.wallet || authMode === "api-key")) {
|
|
91141
|
-
return {
|
|
91238
|
+
return {
|
|
91239
|
+
wallet: data.wallet ?? "",
|
|
91240
|
+
paymentChain: data.paymentChain,
|
|
91241
|
+
authMode,
|
|
91242
|
+
apiKeyLabel: typeof data.apiKey === "string" ? data.apiKey : void 0
|
|
91243
|
+
};
|
|
91142
91244
|
}
|
|
91143
91245
|
}
|
|
91144
91246
|
return void 0;
|
|
@@ -91603,6 +91705,10 @@ function settledMediaCostUsd(upstream, body, x402AmountUsd, estimate) {
|
|
|
91603
91705
|
if (typeof x402AmountUsd === "number" && x402AmountUsd > 0) return x402AmountUsd;
|
|
91604
91706
|
return estimate;
|
|
91605
91707
|
}
|
|
91708
|
+
function publishDispatchCost(usd2) {
|
|
91709
|
+
const store = paymentStore.getStore();
|
|
91710
|
+
if (store) store.estimatedUsd = Number.isFinite(usd2) && usd2 > 0 ? usd2 : 0;
|
|
91711
|
+
}
|
|
91606
91712
|
function noteRemainingCredit(response) {
|
|
91607
91713
|
const remaining = gatewayRemainingCreditUsd(response);
|
|
91608
91714
|
if (remaining === void 0) return;
|
|
@@ -91652,6 +91758,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91652
91758
|
res.on("close", () => {
|
|
91653
91759
|
if (!res.writableEnded) clientAbort.abort();
|
|
91654
91760
|
});
|
|
91761
|
+
publishDispatchCost(0);
|
|
91655
91762
|
const upstream = await payFetch(upstreamUrl, {
|
|
91656
91763
|
method: req.method ?? "POST",
|
|
91657
91764
|
headers,
|
|
@@ -91761,18 +91868,49 @@ function warnIfSpendLimitsUnenforced(spendControl) {
|
|
|
91761
91868
|
let configured;
|
|
91762
91869
|
try {
|
|
91763
91870
|
const limits = (spendControl ?? new SpendControl()).getStatus().limits;
|
|
91764
|
-
configured =
|
|
91871
|
+
configured = POLICY_LISTS.filter((list) => (limits[list]?.length ?? 0) > 0);
|
|
91765
91872
|
} catch {
|
|
91766
91873
|
return;
|
|
91767
91874
|
}
|
|
91768
91875
|
if (configured.length === 0) return;
|
|
91769
91876
|
console.warn(
|
|
91770
|
-
`[ClawRouter] \u26A0
|
|
91877
|
+
`[ClawRouter] \u26A0 Counterparty policy (${configured.join(", ")}) does not apply in API-key mode \u2014 there is one counterparty and no on-chain asset.`
|
|
91771
91878
|
);
|
|
91772
91879
|
console.warn(
|
|
91773
|
-
`[ClawRouter] Your
|
|
91880
|
+
`[ClawRouter] Your perRequest/hourly/daily/session limits DO apply here and are enforced per call.`
|
|
91774
91881
|
);
|
|
91775
91882
|
}
|
|
91883
|
+
function assertExistingProxyBillsUs(existing, requested) {
|
|
91884
|
+
const { listenPort } = requested;
|
|
91885
|
+
const describe = (mode) => mode === "api-key" ? "a BlockRun API key" : "a wallet";
|
|
91886
|
+
if (existing.authMode !== requested.authMode) {
|
|
91887
|
+
throw new Error(
|
|
91888
|
+
`Existing proxy on port ${listenPort} is authenticating with ${describe(existing.authMode)} but ${describe(requested.authMode)} was requested. Stop the existing proxy first or use a different port.`
|
|
91889
|
+
);
|
|
91890
|
+
}
|
|
91891
|
+
if (requested.authMode === "api-key") {
|
|
91892
|
+
if (existing.apiKeyLabel !== requested.apiKeyLabel) {
|
|
91893
|
+
throw new Error(
|
|
91894
|
+
`Existing proxy on port ${listenPort} is billing ${existing.apiKeyLabel ?? "an unidentified BlockRun account"}, but this process is configured for ${requested.apiKeyLabel}. Reusing it would charge the other account. Stop that proxy first, or use a different port.`
|
|
91895
|
+
);
|
|
91896
|
+
}
|
|
91897
|
+
return;
|
|
91898
|
+
}
|
|
91899
|
+
if (existing.paymentChain) {
|
|
91900
|
+
if (existing.paymentChain !== requested.paymentChain) {
|
|
91901
|
+
throw new Error(
|
|
91902
|
+
`Existing proxy on port ${listenPort} is using ${existing.paymentChain} but ${requested.paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91903
|
+
);
|
|
91904
|
+
}
|
|
91905
|
+
} else if (requested.paymentChain !== "base") {
|
|
91906
|
+
console.warn(
|
|
91907
|
+
`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`
|
|
91908
|
+
);
|
|
91909
|
+
throw new Error(
|
|
91910
|
+
`Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${requested.paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91911
|
+
);
|
|
91912
|
+
}
|
|
91913
|
+
}
|
|
91776
91914
|
async function startProxy(options) {
|
|
91777
91915
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91778
91916
|
if (upstreamProxy) {
|
|
@@ -91813,13 +91951,15 @@ async function startProxy(options) {
|
|
|
91813
91951
|
void loadGatewayCatalog(apiBase, apiKey);
|
|
91814
91952
|
const listenPort = options.port ?? getProxyPort();
|
|
91815
91953
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91954
|
+
const ourApiKeyLabel = apiKey ? maskApiKey(apiKey) : void 0;
|
|
91816
91955
|
if (existingProxy) {
|
|
91817
91956
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
91818
|
-
|
|
91819
|
-
|
|
91820
|
-
|
|
91821
|
-
|
|
91822
|
-
|
|
91957
|
+
assertExistingProxyBillsUs(existingProxy, {
|
|
91958
|
+
listenPort,
|
|
91959
|
+
authMode,
|
|
91960
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
91961
|
+
paymentChain
|
|
91962
|
+
});
|
|
91823
91963
|
if (authMode === "api-key") {
|
|
91824
91964
|
options.onReady?.(listenPort);
|
|
91825
91965
|
return {
|
|
@@ -91827,7 +91967,7 @@ async function startProxy(options) {
|
|
|
91827
91967
|
baseUrl: baseUrl2,
|
|
91828
91968
|
walletAddress: "",
|
|
91829
91969
|
authMode,
|
|
91830
|
-
apiKeyLabel:
|
|
91970
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
91831
91971
|
balanceMonitor: new ApiKeyBalanceMonitor(),
|
|
91832
91972
|
// No-op: we didn't start this proxy, so we shouldn't close it
|
|
91833
91973
|
close: async () => {
|
|
@@ -91840,20 +91980,6 @@ async function startProxy(options) {
|
|
|
91840
91980
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
91841
91981
|
);
|
|
91842
91982
|
}
|
|
91843
|
-
if (existingProxy.paymentChain) {
|
|
91844
|
-
if (existingProxy.paymentChain !== paymentChain) {
|
|
91845
|
-
throw new Error(
|
|
91846
|
-
`Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91847
|
-
);
|
|
91848
|
-
}
|
|
91849
|
-
} else if (paymentChain !== "base") {
|
|
91850
|
-
console.warn(
|
|
91851
|
-
`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`
|
|
91852
|
-
);
|
|
91853
|
-
throw new Error(
|
|
91854
|
-
`Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91855
|
-
);
|
|
91856
|
-
}
|
|
91857
91983
|
let reuseSolanaAddress;
|
|
91858
91984
|
if (solanaPrivateKeyBytes) {
|
|
91859
91985
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
@@ -91923,7 +92049,16 @@ async function startProxy(options) {
|
|
|
91923
92049
|
if (store) store.amountUsd = amountUsd;
|
|
91924
92050
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91925
92051
|
});
|
|
91926
|
-
const payFetch = authMode === "api-key" ?
|
|
92052
|
+
const payFetch = authMode === "api-key" ? withSpendPolicy(
|
|
92053
|
+
createApiKeyFetch(apiKey, fetch, apiBase),
|
|
92054
|
+
options.spendControl ?? getSharedSpendControl(),
|
|
92055
|
+
() => {
|
|
92056
|
+
const store = paymentStore.getStore();
|
|
92057
|
+
const published = store?.estimatedUsd;
|
|
92058
|
+
if (store) store.estimatedUsd = void 0;
|
|
92059
|
+
return published;
|
|
92060
|
+
}
|
|
92061
|
+
) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91927
92062
|
skipPreAuth: paymentChain === "solana",
|
|
91928
92063
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91929
92064
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -92306,6 +92441,7 @@ async function startProxy(options) {
|
|
|
92306
92441
|
} catch {
|
|
92307
92442
|
}
|
|
92308
92443
|
try {
|
|
92444
|
+
publishDispatchCost(imgCost);
|
|
92309
92445
|
const upstream = await payFetch(`${apiBase}/v1/images/generations`, {
|
|
92310
92446
|
method: "POST",
|
|
92311
92447
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92504,6 +92640,7 @@ async function startProxy(options) {
|
|
|
92504
92640
|
return;
|
|
92505
92641
|
}
|
|
92506
92642
|
try {
|
|
92643
|
+
publishDispatchCost(img2imgCost);
|
|
92507
92644
|
let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
92508
92645
|
method: "POST",
|
|
92509
92646
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92607,6 +92744,7 @@ async function startProxy(options) {
|
|
|
92607
92744
|
} catch {
|
|
92608
92745
|
}
|
|
92609
92746
|
try {
|
|
92747
|
+
publishDispatchCost(0.15);
|
|
92610
92748
|
let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
|
|
92611
92749
|
method: "POST",
|
|
92612
92750
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92706,6 +92844,7 @@ async function startProxy(options) {
|
|
|
92706
92844
|
} catch {
|
|
92707
92845
|
}
|
|
92708
92846
|
try {
|
|
92847
|
+
publishDispatchCost(estimateVideoCost(videoModel, videoDuration, videoHasImageInput));
|
|
92709
92848
|
const submitResp = await payFetch(`${apiBase}/v1/videos/generations`, {
|
|
92710
92849
|
method: "POST",
|
|
92711
92850
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92990,11 +93129,7 @@ async function startProxy(options) {
|
|
|
92990
93129
|
const existingProxy2 = await checkExistingProxy(listenPort);
|
|
92991
93130
|
if (existingProxy2) {
|
|
92992
93131
|
console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
|
|
92993
|
-
rejectAttempt({
|
|
92994
|
-
code: "REUSE_EXISTING",
|
|
92995
|
-
wallet: existingProxy2.wallet,
|
|
92996
|
-
existingChain: existingProxy2.paymentChain
|
|
92997
|
-
});
|
|
93132
|
+
rejectAttempt({ code: "REUSE_EXISTING", existing: existingProxy2 });
|
|
92998
93133
|
return;
|
|
92999
93134
|
}
|
|
93000
93135
|
if (attempt < PORT_RETRY_ATTEMPTS) {
|
|
@@ -93026,21 +93161,21 @@ async function startProxy(options) {
|
|
|
93026
93161
|
break;
|
|
93027
93162
|
} catch (err) {
|
|
93028
93163
|
const error = err;
|
|
93029
|
-
if (error.code === "REUSE_EXISTING" && error.
|
|
93030
|
-
|
|
93031
|
-
|
|
93032
|
-
|
|
93033
|
-
|
|
93034
|
-
|
|
93035
|
-
}
|
|
93164
|
+
if (error.code === "REUSE_EXISTING" && error.existing) {
|
|
93165
|
+
assertExistingProxyBillsUs(error.existing, {
|
|
93166
|
+
listenPort,
|
|
93167
|
+
authMode,
|
|
93168
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
93169
|
+
paymentChain
|
|
93170
|
+
});
|
|
93036
93171
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
93037
93172
|
options.onReady?.(listenPort);
|
|
93038
93173
|
return {
|
|
93039
93174
|
port: listenPort,
|
|
93040
93175
|
baseUrl: baseUrl2,
|
|
93041
|
-
walletAddress: error.wallet,
|
|
93176
|
+
walletAddress: error.existing.wallet,
|
|
93042
93177
|
authMode,
|
|
93043
|
-
...
|
|
93178
|
+
...ourApiKeyLabel ? { apiKeyLabel: ourApiKeyLabel } : {},
|
|
93044
93179
|
balanceMonitor,
|
|
93045
93180
|
close: async () => {
|
|
93046
93181
|
}
|
|
@@ -93505,6 +93640,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93505
93640
|
size: imageSize,
|
|
93506
93641
|
n: 1
|
|
93507
93642
|
});
|
|
93643
|
+
publishDispatchCost(estimateImageCost(imageModel, imageSize, 1));
|
|
93508
93644
|
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
93509
93645
|
method: "POST",
|
|
93510
93646
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -93786,6 +93922,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93786
93922
|
size: img2imgSize,
|
|
93787
93923
|
n: 1
|
|
93788
93924
|
});
|
|
93925
|
+
publishDispatchCost(estimateImageCost(img2imgModel, img2imgSize, 1));
|
|
93789
93926
|
const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
93790
93927
|
method: "POST",
|
|
93791
93928
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -94615,6 +94752,8 @@ data: [DONE]
|
|
|
94615
94752
|
throw abortError();
|
|
94616
94753
|
}
|
|
94617
94754
|
console.log(`[ClawRouter] Trying model ${i + 1}/${modelsToTry.length}: ${tryModel}`);
|
|
94755
|
+
const attemptEst = FREE_MODELS.has(tryModel) ? void 0 : estimateAmount(tryModel, body.length, maxTokens);
|
|
94756
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94618
94757
|
const perAttemptTimeoutMs = timeoutForModel(tryModel);
|
|
94619
94758
|
const modelController = new AbortController();
|
|
94620
94759
|
const modelTimeoutId = setTimeout(() => modelController.abort(), perAttemptTimeoutMs);
|
|
@@ -94714,6 +94853,7 @@ data: [DONE]
|
|
|
94714
94853
|
);
|
|
94715
94854
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
94716
94855
|
if (!globalController.signal.aborted) {
|
|
94856
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94717
94857
|
const retryController = new AbortController();
|
|
94718
94858
|
const retryTimeoutId = setTimeout(
|
|
94719
94859
|
() => retryController.abort(),
|
|
@@ -94776,6 +94916,7 @@ data: [DONE]
|
|
|
94776
94916
|
);
|
|
94777
94917
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
94778
94918
|
if (!globalController.signal.aborted) {
|
|
94919
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94779
94920
|
const retryController = new AbortController();
|
|
94780
94921
|
const retryTimeoutId = setTimeout(
|
|
94781
94922
|
() => retryController.abort(),
|
|
@@ -228554,6 +228695,19 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
228554
228695
|
const cfg = config ?? {};
|
|
228555
228696
|
return cfg.tools?.web?.search?.enabled === false;
|
|
228556
228697
|
}
|
|
228698
|
+
function legacyPackageIsBlockRun() {
|
|
228699
|
+
for (const dir of [
|
|
228700
|
+
join18(homedir15(), ".openclaw", "extensions", "clawrouter"),
|
|
228701
|
+
join18(homedir15(), ".openclaw", "npm", "node_modules", "@blockrun", "clawrouter")
|
|
228702
|
+
]) {
|
|
228703
|
+
try {
|
|
228704
|
+
const metadata = JSON.parse(readTextFileSync(join18(dir, "package.json")));
|
|
228705
|
+
if (metadata.name === "@blockrun/clawrouter") return true;
|
|
228706
|
+
} catch {
|
|
228707
|
+
}
|
|
228708
|
+
}
|
|
228709
|
+
return false;
|
|
228710
|
+
}
|
|
228557
228711
|
function injectModelsConfig(logger48, options = {}) {
|
|
228558
228712
|
const configDir = join18(homedir15(), ".openclaw");
|
|
228559
228713
|
const configPath = join18(configDir, "openclaw.json");
|
|
@@ -228717,9 +228871,9 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
228717
228871
|
const legacyEntry = config.plugins?.entries?.clawrouter;
|
|
228718
228872
|
const legacyEntryConfig = legacyEntry?.config;
|
|
228719
228873
|
const legacyBlockRunInstall = Boolean(
|
|
228720
|
-
legacyEntry && ["walletKey", "routing"].some(
|
|
228874
|
+
legacyEntry && (["walletKey", "routing"].some(
|
|
228721
228875
|
(key2) => key2 in legacyEntry || (legacyEntryConfig ? key2 in legacyEntryConfig : false)
|
|
228722
|
-
)
|
|
228876
|
+
) || legacyPackageIsBlockRun())
|
|
228723
228877
|
);
|
|
228724
228878
|
if (legacyBlockRunInstall && prepareBlockRunPluginConfig(config, { legacyBlockRunInstall })) {
|
|
228725
228879
|
needsWrite = true;
|
package/dist/index.d.ts
CHANGED
|
@@ -834,6 +834,21 @@ declare class SpendControl {
|
|
|
834
834
|
*/
|
|
835
835
|
reloadLimits(): void;
|
|
836
836
|
check(estimatedCost: number, counterparty?: CounterpartyInfo): CheckResult;
|
|
837
|
+
/**
|
|
838
|
+
* The amount windows alone, with no counterparty to inspect.
|
|
839
|
+
*
|
|
840
|
+
* `perRequest` / `hourly` / `daily` / `session` are denominated in USD and
|
|
841
|
+
* say nothing about how the money moves, so they are the part of the policy
|
|
842
|
+
* that means the same thing on the API-key rail as on the wallet rail — a
|
|
843
|
+
* daily cap is as sensible against account credit as against USDC, and both
|
|
844
|
+
* are read from the same `spending.json` (#329). The counterparty lists are
|
|
845
|
+
* the part that does NOT translate: `blockedPayees`, `allowedPayees`,
|
|
846
|
+
* `blockedNetworks` and `allowedAssets` presuppose a payee, a network and an
|
|
847
|
+
* asset, and on the key rail there is one counterparty and no on-chain asset.
|
|
848
|
+
* They are vacuous there rather than missing, which is why `check()` layers
|
|
849
|
+
* them on top of this instead of the other way round.
|
|
850
|
+
*/
|
|
851
|
+
checkAmount(estimatedCost: number): CheckResult;
|
|
837
852
|
record(amount: number, metadata?: {
|
|
838
853
|
model?: string;
|
|
839
854
|
action?: string;
|
|
@@ -1219,6 +1234,14 @@ type ProxyHandle = {
|
|
|
1219
1234
|
balanceMonitor: AnyBalanceMonitor;
|
|
1220
1235
|
close: () => Promise<void>;
|
|
1221
1236
|
};
|
|
1237
|
+
/**
|
|
1238
|
+
* Start the local x402 proxy server.
|
|
1239
|
+
*
|
|
1240
|
+
* If a proxy is already running on the target port, reuses it instead of failing.
|
|
1241
|
+
* Port can be configured via BLOCKRUN_PROXY_PORT environment variable.
|
|
1242
|
+
*
|
|
1243
|
+
* Returns a handle with the assigned port, base URL, and a close function.
|
|
1244
|
+
*/
|
|
1222
1245
|
declare function startProxy(options: ProxyOptions): Promise<ProxyHandle>;
|
|
1223
1246
|
|
|
1224
1247
|
/**
|
|
@@ -1778,23 +1801,6 @@ declare function buildPartnerTools(proxyBaseUrl: string): PartnerToolDefinition[
|
|
|
1778
1801
|
* opt-in.
|
|
1779
1802
|
*/
|
|
1780
1803
|
declare function isBlockrunWebSearchDisabled(config?: unknown): boolean;
|
|
1781
|
-
/**
|
|
1782
|
-
* Inject BlockRun models config into OpenClaw config file.
|
|
1783
|
-
* This is required because registerProvider() alone doesn't make models available.
|
|
1784
|
-
*
|
|
1785
|
-
* CRITICAL: This function must be idempotent and handle ALL edge cases:
|
|
1786
|
-
* - Config file doesn't exist (create it)
|
|
1787
|
-
* - Config file exists but is empty/invalid (reinitialize)
|
|
1788
|
-
* - blockrun provider exists but has undefined fields (fix them)
|
|
1789
|
-
* - Config exists but uses old port/models (update them)
|
|
1790
|
-
*
|
|
1791
|
-
* This function is called on EVERY plugin load to ensure config is always correct.
|
|
1792
|
-
*
|
|
1793
|
-
* Also strips any previously managed `mcp.servers.blockrun` entry we wrote in
|
|
1794
|
-
* older releases — ClawRouter no longer bundles the MCP bridge (the npx-spawned
|
|
1795
|
-
* grandchildren were leaking). The scrub only removes entries matching the
|
|
1796
|
-
* managed shape; user-defined `blockrun` MCP servers are left alone.
|
|
1797
|
-
*/
|
|
1798
1804
|
declare function injectModelsConfig(logger: {
|
|
1799
1805
|
info: (msg: string) => void;
|
|
1800
1806
|
}, options?: {
|
package/dist/index.js
CHANGED
|
@@ -34514,6 +34514,40 @@ function registerSpendPolicyHook(x402, control) {
|
|
|
34514
34514
|
}
|
|
34515
34515
|
});
|
|
34516
34516
|
}
|
|
34517
|
+
function settledChargeUsd(response) {
|
|
34518
|
+
const raw = response.headers.get("x-blockrun-cost-usd");
|
|
34519
|
+
if (raw === null) return void 0;
|
|
34520
|
+
const trimmed = raw.trim();
|
|
34521
|
+
if (trimmed === "") return void 0;
|
|
34522
|
+
const value = Number(trimmed);
|
|
34523
|
+
return Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
34524
|
+
}
|
|
34525
|
+
function withSpendPolicy(inner, control, consumeEstimateUsd) {
|
|
34526
|
+
return async (input, init2) => {
|
|
34527
|
+
const published = consumeEstimateUsd();
|
|
34528
|
+
if (published === void 0 || !control.hasAmountLimits()) return inner(input, init2);
|
|
34529
|
+
const estimate = Number.isFinite(published) && published > 0 ? published : 0;
|
|
34530
|
+
const result = control.checkAmount(estimate);
|
|
34531
|
+
if (!result.allowed) {
|
|
34532
|
+
throw new SpendPolicyError(result.reason ?? "blocked by spend policy", {
|
|
34533
|
+
blockedBy: result.blockedBy
|
|
34534
|
+
});
|
|
34535
|
+
}
|
|
34536
|
+
const reservationId = control.hasAggregateLimits() ? control.reserve(estimate) : void 0;
|
|
34537
|
+
let response;
|
|
34538
|
+
try {
|
|
34539
|
+
response = await inner(input, init2);
|
|
34540
|
+
} catch (err) {
|
|
34541
|
+
if (reservationId !== void 0) control.releaseReservation(reservationId);
|
|
34542
|
+
throw err;
|
|
34543
|
+
}
|
|
34544
|
+
const settled = settledChargeUsd(response);
|
|
34545
|
+
const charged = response.ok ? settled ?? estimate : settled ?? 0;
|
|
34546
|
+
if (reservationId !== void 0) control.releaseReservation(reservationId);
|
|
34547
|
+
if (charged > 0) control.record(charged, { action: "BlockRun account charge" });
|
|
34548
|
+
return response;
|
|
34549
|
+
};
|
|
34550
|
+
}
|
|
34517
34551
|
function formatDuration(seconds) {
|
|
34518
34552
|
if (seconds < 60) {
|
|
34519
34553
|
return `${seconds}s`;
|
|
@@ -34843,6 +34877,29 @@ var init_spend_control = __esm({
|
|
|
34843
34877
|
};
|
|
34844
34878
|
}
|
|
34845
34879
|
}
|
|
34880
|
+
return this.checkAmount(estimatedCost2);
|
|
34881
|
+
}
|
|
34882
|
+
/**
|
|
34883
|
+
* The amount windows alone, with no counterparty to inspect.
|
|
34884
|
+
*
|
|
34885
|
+
* `perRequest` / `hourly` / `daily` / `session` are denominated in USD and
|
|
34886
|
+
* say nothing about how the money moves, so they are the part of the policy
|
|
34887
|
+
* that means the same thing on the API-key rail as on the wallet rail — a
|
|
34888
|
+
* daily cap is as sensible against account credit as against USDC, and both
|
|
34889
|
+
* are read from the same `spending.json` (#329). The counterparty lists are
|
|
34890
|
+
* the part that does NOT translate: `blockedPayees`, `allowedPayees`,
|
|
34891
|
+
* `blockedNetworks` and `allowedAssets` presuppose a payee, a network and an
|
|
34892
|
+
* asset, and on the key rail there is one counterparty and no on-chain asset.
|
|
34893
|
+
* They are vacuous there rather than missing, which is why `check()` layers
|
|
34894
|
+
* them on top of this instead of the other way round.
|
|
34895
|
+
*/
|
|
34896
|
+
checkAmount(estimatedCost2) {
|
|
34897
|
+
if (this.policyFileBroken !== void 0) {
|
|
34898
|
+
return {
|
|
34899
|
+
allowed: false,
|
|
34900
|
+
reason: `Spend policy is unreadable, refusing all payments: ${this.policyFileBroken}`
|
|
34901
|
+
};
|
|
34902
|
+
}
|
|
34846
34903
|
const now2 = this.now();
|
|
34847
34904
|
if (this.limits.perRequest !== void 0) {
|
|
34848
34905
|
if (estimatedCost2 > this.limits.perRequest) {
|
|
@@ -59733,6 +59790,28 @@ Then run: npx @blockrun/clawrouter`
|
|
|
59733
59790
|
solanaPrivateKeyBytes
|
|
59734
59791
|
};
|
|
59735
59792
|
}
|
|
59793
|
+
async function warnIfLegacyWalletDiffers(coreAddress) {
|
|
59794
|
+
if (legacyWalletDivergenceWarned) return;
|
|
59795
|
+
let legacyAddress;
|
|
59796
|
+
try {
|
|
59797
|
+
const saved = await loadSavedWallet();
|
|
59798
|
+
if (!saved) return;
|
|
59799
|
+
legacyAddress = privateKeyToAccount(saved).address;
|
|
59800
|
+
} catch {
|
|
59801
|
+
return;
|
|
59802
|
+
}
|
|
59803
|
+
if (legacyAddress === coreAddress) return;
|
|
59804
|
+
legacyWalletDivergenceWarned = true;
|
|
59805
|
+
console.warn(
|
|
59806
|
+
`[ClawRouter] \u26A0 Two different wallets are configured. Paying from the BlockRun Core wallet ${coreAddress}.`
|
|
59807
|
+
);
|
|
59808
|
+
console.warn(
|
|
59809
|
+
`[ClawRouter] ClawRouter's older wallet ${legacyAddress} (${WALLET_FILE}) is NOT being used \u2014 if that is the funded one, requests will fail on an empty balance.`
|
|
59810
|
+
);
|
|
59811
|
+
console.warn(
|
|
59812
|
+
`[ClawRouter] To keep paying from it: export BLOCKRUN_WALLET_KEY=$(cat ${WALLET_FILE})`
|
|
59813
|
+
);
|
|
59814
|
+
}
|
|
59736
59815
|
async function resolveExistingWalletKey() {
|
|
59737
59816
|
const coreSolanaKey = await loadCoreSolanaKeyForSelectedChain();
|
|
59738
59817
|
const envKey = process["env"].BLOCKRUN_WALLET_KEY;
|
|
@@ -59748,6 +59827,7 @@ async function resolveExistingWalletKey() {
|
|
|
59748
59827
|
const core = await loadCoreWallet();
|
|
59749
59828
|
if (core) {
|
|
59750
59829
|
const account = privateKeyToAccount(core);
|
|
59830
|
+
await warnIfLegacyWalletDiffers(account.address);
|
|
59751
59831
|
return {
|
|
59752
59832
|
key: core,
|
|
59753
59833
|
address: account.address,
|
|
@@ -59871,13 +59951,28 @@ async function savePaymentChain(chain3) {
|
|
|
59871
59951
|
}
|
|
59872
59952
|
async function loadPaymentChain() {
|
|
59873
59953
|
const core = await readOptional(CORE_CHAIN_FILE);
|
|
59874
|
-
if (core === "solana") return "solana";
|
|
59875
|
-
if (core === "base") return "base";
|
|
59876
59954
|
const legacy = await readOptional(CHAIN_FILE);
|
|
59955
|
+
if (core === "solana" || core === "base") {
|
|
59956
|
+
if ((legacy === "solana" || legacy === "base") && legacy !== core) {
|
|
59957
|
+
warnChainOverride(core, legacy);
|
|
59958
|
+
}
|
|
59959
|
+
return core;
|
|
59960
|
+
}
|
|
59877
59961
|
if (legacy === "solana") return "solana";
|
|
59878
59962
|
if (legacy === "base") return "base";
|
|
59879
59963
|
return await hasExistingWallet() ? "base" : "solana";
|
|
59880
59964
|
}
|
|
59965
|
+
function warnChainOverride(core, legacy) {
|
|
59966
|
+
if (chainOverrideWarned) return;
|
|
59967
|
+
chainOverrideWarned = true;
|
|
59968
|
+
console.warn(
|
|
59969
|
+
`[ClawRouter] \u26A0 Payment chain is ${core} (from ${CORE_CHAIN_FILE}), overriding this install's saved ${legacy}.`
|
|
59970
|
+
);
|
|
59971
|
+
console.warn(
|
|
59972
|
+
`[ClawRouter] A different chain means a different signer and gateway \u2014 funds on ${legacy} will not be spendable.`
|
|
59973
|
+
);
|
|
59974
|
+
console.warn(`[ClawRouter] To go back: npx @blockrun/clawrouter wallet ${legacy}`);
|
|
59975
|
+
}
|
|
59881
59976
|
async function hasExistingWallet() {
|
|
59882
59977
|
if (process["env"].BLOCKRUN_WALLET_KEY?.trim()) return true;
|
|
59883
59978
|
const [core, legacy] = await Promise.all([
|
|
@@ -59891,7 +59986,7 @@ async function resolvePaymentChain() {
|
|
|
59891
59986
|
if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "base") return "base";
|
|
59892
59987
|
return loadPaymentChain();
|
|
59893
59988
|
}
|
|
59894
|
-
var WALLET_DIR2, WALLET_FILE, MNEMONIC_FILE, CHAIN_FILE, CORE_WALLET_DIR, CORE_WALLET_FILE, CORE_SOLANA_WALLET_FILE, CORE_CHAIN_FILE;
|
|
59989
|
+
var WALLET_DIR2, WALLET_FILE, MNEMONIC_FILE, CHAIN_FILE, CORE_WALLET_DIR, CORE_WALLET_FILE, CORE_SOLANA_WALLET_FILE, CORE_CHAIN_FILE, legacyWalletDivergenceWarned, chainOverrideWarned;
|
|
59895
59990
|
var init_auth = __esm({
|
|
59896
59991
|
"src/auth.ts"() {
|
|
59897
59992
|
"use strict";
|
|
@@ -59907,6 +60002,8 @@ var init_auth = __esm({
|
|
|
59907
60002
|
CORE_WALLET_FILE = join7(CORE_WALLET_DIR, ".session");
|
|
59908
60003
|
CORE_SOLANA_WALLET_FILE = join7(CORE_WALLET_DIR, ".solana-session");
|
|
59909
60004
|
CORE_CHAIN_FILE = join7(CORE_WALLET_DIR, ".chain");
|
|
60005
|
+
legacyWalletDivergenceWarned = false;
|
|
60006
|
+
chainOverrideWarned = false;
|
|
59910
60007
|
}
|
|
59911
60008
|
});
|
|
59912
60009
|
|
|
@@ -90891,7 +90988,12 @@ async function checkExistingProxy(port) {
|
|
|
90891
90988
|
const data = await response.json();
|
|
90892
90989
|
const authMode = data.authMode === "api-key" ? "api-key" : "wallet";
|
|
90893
90990
|
if (data.status === "ok" && (data.wallet || authMode === "api-key")) {
|
|
90894
|
-
return {
|
|
90991
|
+
return {
|
|
90992
|
+
wallet: data.wallet ?? "",
|
|
90993
|
+
paymentChain: data.paymentChain,
|
|
90994
|
+
authMode,
|
|
90995
|
+
apiKeyLabel: typeof data.apiKey === "string" ? data.apiKey : void 0
|
|
90996
|
+
};
|
|
90895
90997
|
}
|
|
90896
90998
|
}
|
|
90897
90999
|
return void 0;
|
|
@@ -91356,6 +91458,10 @@ function settledMediaCostUsd(upstream, body, x402AmountUsd, estimate) {
|
|
|
91356
91458
|
if (typeof x402AmountUsd === "number" && x402AmountUsd > 0) return x402AmountUsd;
|
|
91357
91459
|
return estimate;
|
|
91358
91460
|
}
|
|
91461
|
+
function publishDispatchCost(usd) {
|
|
91462
|
+
const store = paymentStore.getStore();
|
|
91463
|
+
if (store) store.estimatedUsd = Number.isFinite(usd) && usd > 0 ? usd : 0;
|
|
91464
|
+
}
|
|
91359
91465
|
function noteRemainingCredit(response) {
|
|
91360
91466
|
const remaining = gatewayRemainingCreditUsd(response);
|
|
91361
91467
|
if (remaining === void 0) return;
|
|
@@ -91405,6 +91511,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91405
91511
|
res.on("close", () => {
|
|
91406
91512
|
if (!res.writableEnded) clientAbort.abort();
|
|
91407
91513
|
});
|
|
91514
|
+
publishDispatchCost(0);
|
|
91408
91515
|
const upstream = await payFetch(upstreamUrl, {
|
|
91409
91516
|
method: req.method ?? "POST",
|
|
91410
91517
|
headers,
|
|
@@ -91514,18 +91621,49 @@ function warnIfSpendLimitsUnenforced(spendControl) {
|
|
|
91514
91621
|
let configured;
|
|
91515
91622
|
try {
|
|
91516
91623
|
const limits = (spendControl ?? new SpendControl()).getStatus().limits;
|
|
91517
|
-
configured =
|
|
91624
|
+
configured = POLICY_LISTS.filter((list) => (limits[list]?.length ?? 0) > 0);
|
|
91518
91625
|
} catch {
|
|
91519
91626
|
return;
|
|
91520
91627
|
}
|
|
91521
91628
|
if (configured.length === 0) return;
|
|
91522
91629
|
console.warn(
|
|
91523
|
-
`[ClawRouter] \u26A0
|
|
91630
|
+
`[ClawRouter] \u26A0 Counterparty policy (${configured.join(", ")}) does not apply in API-key mode \u2014 there is one counterparty and no on-chain asset.`
|
|
91524
91631
|
);
|
|
91525
91632
|
console.warn(
|
|
91526
|
-
`[ClawRouter] Your
|
|
91633
|
+
`[ClawRouter] Your perRequest/hourly/daily/session limits DO apply here and are enforced per call.`
|
|
91527
91634
|
);
|
|
91528
91635
|
}
|
|
91636
|
+
function assertExistingProxyBillsUs(existing, requested) {
|
|
91637
|
+
const { listenPort } = requested;
|
|
91638
|
+
const describe = (mode) => mode === "api-key" ? "a BlockRun API key" : "a wallet";
|
|
91639
|
+
if (existing.authMode !== requested.authMode) {
|
|
91640
|
+
throw new Error(
|
|
91641
|
+
`Existing proxy on port ${listenPort} is authenticating with ${describe(existing.authMode)} but ${describe(requested.authMode)} was requested. Stop the existing proxy first or use a different port.`
|
|
91642
|
+
);
|
|
91643
|
+
}
|
|
91644
|
+
if (requested.authMode === "api-key") {
|
|
91645
|
+
if (existing.apiKeyLabel !== requested.apiKeyLabel) {
|
|
91646
|
+
throw new Error(
|
|
91647
|
+
`Existing proxy on port ${listenPort} is billing ${existing.apiKeyLabel ?? "an unidentified BlockRun account"}, but this process is configured for ${requested.apiKeyLabel}. Reusing it would charge the other account. Stop that proxy first, or use a different port.`
|
|
91648
|
+
);
|
|
91649
|
+
}
|
|
91650
|
+
return;
|
|
91651
|
+
}
|
|
91652
|
+
if (existing.paymentChain) {
|
|
91653
|
+
if (existing.paymentChain !== requested.paymentChain) {
|
|
91654
|
+
throw new Error(
|
|
91655
|
+
`Existing proxy on port ${listenPort} is using ${existing.paymentChain} but ${requested.paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91656
|
+
);
|
|
91657
|
+
}
|
|
91658
|
+
} else if (requested.paymentChain !== "base") {
|
|
91659
|
+
console.warn(
|
|
91660
|
+
`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`
|
|
91661
|
+
);
|
|
91662
|
+
throw new Error(
|
|
91663
|
+
`Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${requested.paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91664
|
+
);
|
|
91665
|
+
}
|
|
91666
|
+
}
|
|
91529
91667
|
async function startProxy(options) {
|
|
91530
91668
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91531
91669
|
if (upstreamProxy) {
|
|
@@ -91566,13 +91704,15 @@ async function startProxy(options) {
|
|
|
91566
91704
|
void loadGatewayCatalog(apiBase, apiKey);
|
|
91567
91705
|
const listenPort = options.port ?? getProxyPort();
|
|
91568
91706
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91707
|
+
const ourApiKeyLabel = apiKey ? maskApiKey(apiKey) : void 0;
|
|
91569
91708
|
if (existingProxy) {
|
|
91570
91709
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
91571
|
-
|
|
91572
|
-
|
|
91573
|
-
|
|
91574
|
-
|
|
91575
|
-
|
|
91710
|
+
assertExistingProxyBillsUs(existingProxy, {
|
|
91711
|
+
listenPort,
|
|
91712
|
+
authMode,
|
|
91713
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
91714
|
+
paymentChain
|
|
91715
|
+
});
|
|
91576
91716
|
if (authMode === "api-key") {
|
|
91577
91717
|
options.onReady?.(listenPort);
|
|
91578
91718
|
return {
|
|
@@ -91580,7 +91720,7 @@ async function startProxy(options) {
|
|
|
91580
91720
|
baseUrl: baseUrl2,
|
|
91581
91721
|
walletAddress: "",
|
|
91582
91722
|
authMode,
|
|
91583
|
-
apiKeyLabel:
|
|
91723
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
91584
91724
|
balanceMonitor: new ApiKeyBalanceMonitor(),
|
|
91585
91725
|
// No-op: we didn't start this proxy, so we shouldn't close it
|
|
91586
91726
|
close: async () => {
|
|
@@ -91593,20 +91733,6 @@ async function startProxy(options) {
|
|
|
91593
91733
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
91594
91734
|
);
|
|
91595
91735
|
}
|
|
91596
|
-
if (existingProxy.paymentChain) {
|
|
91597
|
-
if (existingProxy.paymentChain !== paymentChain) {
|
|
91598
|
-
throw new Error(
|
|
91599
|
-
`Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91600
|
-
);
|
|
91601
|
-
}
|
|
91602
|
-
} else if (paymentChain !== "base") {
|
|
91603
|
-
console.warn(
|
|
91604
|
-
`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`
|
|
91605
|
-
);
|
|
91606
|
-
throw new Error(
|
|
91607
|
-
`Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91608
|
-
);
|
|
91609
|
-
}
|
|
91610
91736
|
let reuseSolanaAddress;
|
|
91611
91737
|
if (solanaPrivateKeyBytes) {
|
|
91612
91738
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
@@ -91676,7 +91802,16 @@ async function startProxy(options) {
|
|
|
91676
91802
|
if (store) store.amountUsd = amountUsd;
|
|
91677
91803
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91678
91804
|
});
|
|
91679
|
-
const payFetch = authMode === "api-key" ?
|
|
91805
|
+
const payFetch = authMode === "api-key" ? withSpendPolicy(
|
|
91806
|
+
createApiKeyFetch(apiKey, fetch, apiBase),
|
|
91807
|
+
options.spendControl ?? getSharedSpendControl(),
|
|
91808
|
+
() => {
|
|
91809
|
+
const store = paymentStore.getStore();
|
|
91810
|
+
const published = store?.estimatedUsd;
|
|
91811
|
+
if (store) store.estimatedUsd = void 0;
|
|
91812
|
+
return published;
|
|
91813
|
+
}
|
|
91814
|
+
) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91680
91815
|
skipPreAuth: paymentChain === "solana",
|
|
91681
91816
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91682
91817
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -92059,6 +92194,7 @@ async function startProxy(options) {
|
|
|
92059
92194
|
} catch {
|
|
92060
92195
|
}
|
|
92061
92196
|
try {
|
|
92197
|
+
publishDispatchCost(imgCost);
|
|
92062
92198
|
const upstream = await payFetch(`${apiBase}/v1/images/generations`, {
|
|
92063
92199
|
method: "POST",
|
|
92064
92200
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92257,6 +92393,7 @@ async function startProxy(options) {
|
|
|
92257
92393
|
return;
|
|
92258
92394
|
}
|
|
92259
92395
|
try {
|
|
92396
|
+
publishDispatchCost(img2imgCost);
|
|
92260
92397
|
let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
92261
92398
|
method: "POST",
|
|
92262
92399
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92360,6 +92497,7 @@ async function startProxy(options) {
|
|
|
92360
92497
|
} catch {
|
|
92361
92498
|
}
|
|
92362
92499
|
try {
|
|
92500
|
+
publishDispatchCost(0.15);
|
|
92363
92501
|
let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
|
|
92364
92502
|
method: "POST",
|
|
92365
92503
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92459,6 +92597,7 @@ async function startProxy(options) {
|
|
|
92459
92597
|
} catch {
|
|
92460
92598
|
}
|
|
92461
92599
|
try {
|
|
92600
|
+
publishDispatchCost(estimateVideoCost(videoModel, videoDuration, videoHasImageInput));
|
|
92462
92601
|
const submitResp = await payFetch(`${apiBase}/v1/videos/generations`, {
|
|
92463
92602
|
method: "POST",
|
|
92464
92603
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92743,11 +92882,7 @@ async function startProxy(options) {
|
|
|
92743
92882
|
const existingProxy2 = await checkExistingProxy(listenPort);
|
|
92744
92883
|
if (existingProxy2) {
|
|
92745
92884
|
console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
|
|
92746
|
-
rejectAttempt({
|
|
92747
|
-
code: "REUSE_EXISTING",
|
|
92748
|
-
wallet: existingProxy2.wallet,
|
|
92749
|
-
existingChain: existingProxy2.paymentChain
|
|
92750
|
-
});
|
|
92885
|
+
rejectAttempt({ code: "REUSE_EXISTING", existing: existingProxy2 });
|
|
92751
92886
|
return;
|
|
92752
92887
|
}
|
|
92753
92888
|
if (attempt < PORT_RETRY_ATTEMPTS) {
|
|
@@ -92779,21 +92914,21 @@ async function startProxy(options) {
|
|
|
92779
92914
|
break;
|
|
92780
92915
|
} catch (err) {
|
|
92781
92916
|
const error = err;
|
|
92782
|
-
if (error.code === "REUSE_EXISTING" && error.
|
|
92783
|
-
|
|
92784
|
-
|
|
92785
|
-
|
|
92786
|
-
|
|
92787
|
-
|
|
92788
|
-
}
|
|
92917
|
+
if (error.code === "REUSE_EXISTING" && error.existing) {
|
|
92918
|
+
assertExistingProxyBillsUs(error.existing, {
|
|
92919
|
+
listenPort,
|
|
92920
|
+
authMode,
|
|
92921
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
92922
|
+
paymentChain
|
|
92923
|
+
});
|
|
92789
92924
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
92790
92925
|
options.onReady?.(listenPort);
|
|
92791
92926
|
return {
|
|
92792
92927
|
port: listenPort,
|
|
92793
92928
|
baseUrl: baseUrl2,
|
|
92794
|
-
walletAddress: error.wallet,
|
|
92929
|
+
walletAddress: error.existing.wallet,
|
|
92795
92930
|
authMode,
|
|
92796
|
-
...
|
|
92931
|
+
...ourApiKeyLabel ? { apiKeyLabel: ourApiKeyLabel } : {},
|
|
92797
92932
|
balanceMonitor,
|
|
92798
92933
|
close: async () => {
|
|
92799
92934
|
}
|
|
@@ -93258,6 +93393,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93258
93393
|
size: imageSize,
|
|
93259
93394
|
n: 1
|
|
93260
93395
|
});
|
|
93396
|
+
publishDispatchCost(estimateImageCost(imageModel, imageSize, 1));
|
|
93261
93397
|
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
93262
93398
|
method: "POST",
|
|
93263
93399
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -93539,6 +93675,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93539
93675
|
size: img2imgSize,
|
|
93540
93676
|
n: 1
|
|
93541
93677
|
});
|
|
93678
|
+
publishDispatchCost(estimateImageCost(img2imgModel, img2imgSize, 1));
|
|
93542
93679
|
const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
93543
93680
|
method: "POST",
|
|
93544
93681
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -94368,6 +94505,8 @@ data: [DONE]
|
|
|
94368
94505
|
throw abortError();
|
|
94369
94506
|
}
|
|
94370
94507
|
console.log(`[ClawRouter] Trying model ${i + 1}/${modelsToTry.length}: ${tryModel}`);
|
|
94508
|
+
const attemptEst = FREE_MODELS.has(tryModel) ? void 0 : estimateAmount(tryModel, body.length, maxTokens);
|
|
94509
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94371
94510
|
const perAttemptTimeoutMs = timeoutForModel(tryModel);
|
|
94372
94511
|
const modelController = new AbortController();
|
|
94373
94512
|
const modelTimeoutId = setTimeout(() => modelController.abort(), perAttemptTimeoutMs);
|
|
@@ -94467,6 +94606,7 @@ data: [DONE]
|
|
|
94467
94606
|
);
|
|
94468
94607
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
94469
94608
|
if (!globalController.signal.aborted) {
|
|
94609
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94470
94610
|
const retryController = new AbortController();
|
|
94471
94611
|
const retryTimeoutId = setTimeout(
|
|
94472
94612
|
() => retryController.abort(),
|
|
@@ -94529,6 +94669,7 @@ data: [DONE]
|
|
|
94529
94669
|
);
|
|
94530
94670
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
94531
94671
|
if (!globalController.signal.aborted) {
|
|
94672
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94532
94673
|
const retryController = new AbortController();
|
|
94533
94674
|
const retryTimeoutId = setTimeout(
|
|
94534
94675
|
() => retryController.abort(),
|
|
@@ -228038,6 +228179,19 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
228038
228179
|
const cfg = config ?? {};
|
|
228039
228180
|
return cfg.tools?.web?.search?.enabled === false;
|
|
228040
228181
|
}
|
|
228182
|
+
function legacyPackageIsBlockRun() {
|
|
228183
|
+
for (const dir of [
|
|
228184
|
+
join17(homedir14(), ".openclaw", "extensions", "clawrouter"),
|
|
228185
|
+
join17(homedir14(), ".openclaw", "npm", "node_modules", "@blockrun", "clawrouter")
|
|
228186
|
+
]) {
|
|
228187
|
+
try {
|
|
228188
|
+
const metadata = JSON.parse(readTextFileSync(join17(dir, "package.json")));
|
|
228189
|
+
if (metadata.name === "@blockrun/clawrouter") return true;
|
|
228190
|
+
} catch {
|
|
228191
|
+
}
|
|
228192
|
+
}
|
|
228193
|
+
return false;
|
|
228194
|
+
}
|
|
228041
228195
|
function injectModelsConfig(logger48, options = {}) {
|
|
228042
228196
|
const configDir = join17(homedir14(), ".openclaw");
|
|
228043
228197
|
const configPath = join17(configDir, "openclaw.json");
|
|
@@ -228201,9 +228355,9 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
228201
228355
|
const legacyEntry = config.plugins?.entries?.clawrouter;
|
|
228202
228356
|
const legacyEntryConfig = legacyEntry?.config;
|
|
228203
228357
|
const legacyBlockRunInstall = Boolean(
|
|
228204
|
-
legacyEntry && ["walletKey", "routing"].some(
|
|
228358
|
+
legacyEntry && (["walletKey", "routing"].some(
|
|
228205
228359
|
(key2) => key2 in legacyEntry || (legacyEntryConfig ? key2 in legacyEntryConfig : false)
|
|
228206
|
-
)
|
|
228360
|
+
) || legacyPackageIsBlockRun())
|
|
228207
228361
|
);
|
|
228208
228362
|
if (legacyBlockRunInstall && prepareBlockRunPluginConfig(config, { legacyBlockRunInstall })) {
|
|
228209
228363
|
needsWrite = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/clawrouter",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.274",
|
|
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",
|