@blockrun/clawrouter 0.12.273 → 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 +192 -50
- package/dist/index.d.ts +23 -17
- package/dist/index.js +192 -50
- 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
|
|
|
@@ -91608,6 +91705,10 @@ function settledMediaCostUsd(upstream, body, x402AmountUsd, estimate) {
|
|
|
91608
91705
|
if (typeof x402AmountUsd === "number" && x402AmountUsd > 0) return x402AmountUsd;
|
|
91609
91706
|
return estimate;
|
|
91610
91707
|
}
|
|
91708
|
+
function publishDispatchCost(usd2) {
|
|
91709
|
+
const store = paymentStore.getStore();
|
|
91710
|
+
if (store) store.estimatedUsd = Number.isFinite(usd2) && usd2 > 0 ? usd2 : 0;
|
|
91711
|
+
}
|
|
91611
91712
|
function noteRemainingCredit(response) {
|
|
91612
91713
|
const remaining = gatewayRemainingCreditUsd(response);
|
|
91613
91714
|
if (remaining === void 0) return;
|
|
@@ -91657,6 +91758,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91657
91758
|
res.on("close", () => {
|
|
91658
91759
|
if (!res.writableEnded) clientAbort.abort();
|
|
91659
91760
|
});
|
|
91761
|
+
publishDispatchCost(0);
|
|
91660
91762
|
const upstream = await payFetch(upstreamUrl, {
|
|
91661
91763
|
method: req.method ?? "POST",
|
|
91662
91764
|
headers,
|
|
@@ -91766,18 +91868,49 @@ function warnIfSpendLimitsUnenforced(spendControl) {
|
|
|
91766
91868
|
let configured;
|
|
91767
91869
|
try {
|
|
91768
91870
|
const limits = (spendControl ?? new SpendControl()).getStatus().limits;
|
|
91769
|
-
configured =
|
|
91871
|
+
configured = POLICY_LISTS.filter((list) => (limits[list]?.length ?? 0) > 0);
|
|
91770
91872
|
} catch {
|
|
91771
91873
|
return;
|
|
91772
91874
|
}
|
|
91773
91875
|
if (configured.length === 0) return;
|
|
91774
91876
|
console.warn(
|
|
91775
|
-
`[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.`
|
|
91776
91878
|
);
|
|
91777
91879
|
console.warn(
|
|
91778
|
-
`[ClawRouter] Your
|
|
91880
|
+
`[ClawRouter] Your perRequest/hourly/daily/session limits DO apply here and are enforced per call.`
|
|
91779
91881
|
);
|
|
91780
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
|
+
}
|
|
91781
91914
|
async function startProxy(options) {
|
|
91782
91915
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91783
91916
|
if (upstreamProxy) {
|
|
@@ -91818,28 +91951,23 @@ async function startProxy(options) {
|
|
|
91818
91951
|
void loadGatewayCatalog(apiBase, apiKey);
|
|
91819
91952
|
const listenPort = options.port ?? getProxyPort();
|
|
91820
91953
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91954
|
+
const ourApiKeyLabel = apiKey ? maskApiKey(apiKey) : void 0;
|
|
91821
91955
|
if (existingProxy) {
|
|
91822
91956
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
91823
|
-
|
|
91824
|
-
|
|
91825
|
-
|
|
91826
|
-
|
|
91827
|
-
|
|
91957
|
+
assertExistingProxyBillsUs(existingProxy, {
|
|
91958
|
+
listenPort,
|
|
91959
|
+
authMode,
|
|
91960
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
91961
|
+
paymentChain
|
|
91962
|
+
});
|
|
91828
91963
|
if (authMode === "api-key") {
|
|
91829
|
-
const runningLabel = existingProxy.apiKeyLabel;
|
|
91830
|
-
const ourLabel = maskApiKey(apiKey);
|
|
91831
|
-
if (runningLabel !== ourLabel) {
|
|
91832
|
-
throw new Error(
|
|
91833
|
-
`Existing proxy on port ${listenPort} is billing ${runningLabel ?? "an unidentified BlockRun account"}, but this process is configured for ${ourLabel}. Reusing it would charge the other account. Stop that proxy first, or use a different port.`
|
|
91834
|
-
);
|
|
91835
|
-
}
|
|
91836
91964
|
options.onReady?.(listenPort);
|
|
91837
91965
|
return {
|
|
91838
91966
|
port: listenPort,
|
|
91839
91967
|
baseUrl: baseUrl2,
|
|
91840
91968
|
walletAddress: "",
|
|
91841
91969
|
authMode,
|
|
91842
|
-
apiKeyLabel:
|
|
91970
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
91843
91971
|
balanceMonitor: new ApiKeyBalanceMonitor(),
|
|
91844
91972
|
// No-op: we didn't start this proxy, so we shouldn't close it
|
|
91845
91973
|
close: async () => {
|
|
@@ -91852,20 +91980,6 @@ async function startProxy(options) {
|
|
|
91852
91980
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
91853
91981
|
);
|
|
91854
91982
|
}
|
|
91855
|
-
if (existingProxy.paymentChain) {
|
|
91856
|
-
if (existingProxy.paymentChain !== paymentChain) {
|
|
91857
|
-
throw new Error(
|
|
91858
|
-
`Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91859
|
-
);
|
|
91860
|
-
}
|
|
91861
|
-
} else if (paymentChain !== "base") {
|
|
91862
|
-
console.warn(
|
|
91863
|
-
`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`
|
|
91864
|
-
);
|
|
91865
|
-
throw new Error(
|
|
91866
|
-
`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.`
|
|
91867
|
-
);
|
|
91868
|
-
}
|
|
91869
91983
|
let reuseSolanaAddress;
|
|
91870
91984
|
if (solanaPrivateKeyBytes) {
|
|
91871
91985
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
@@ -91935,7 +92049,16 @@ async function startProxy(options) {
|
|
|
91935
92049
|
if (store) store.amountUsd = amountUsd;
|
|
91936
92050
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91937
92051
|
});
|
|
91938
|
-
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, {
|
|
91939
92062
|
skipPreAuth: paymentChain === "solana",
|
|
91940
92063
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91941
92064
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -92318,6 +92441,7 @@ async function startProxy(options) {
|
|
|
92318
92441
|
} catch {
|
|
92319
92442
|
}
|
|
92320
92443
|
try {
|
|
92444
|
+
publishDispatchCost(imgCost);
|
|
92321
92445
|
const upstream = await payFetch(`${apiBase}/v1/images/generations`, {
|
|
92322
92446
|
method: "POST",
|
|
92323
92447
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92516,6 +92640,7 @@ async function startProxy(options) {
|
|
|
92516
92640
|
return;
|
|
92517
92641
|
}
|
|
92518
92642
|
try {
|
|
92643
|
+
publishDispatchCost(img2imgCost);
|
|
92519
92644
|
let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
92520
92645
|
method: "POST",
|
|
92521
92646
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92619,6 +92744,7 @@ async function startProxy(options) {
|
|
|
92619
92744
|
} catch {
|
|
92620
92745
|
}
|
|
92621
92746
|
try {
|
|
92747
|
+
publishDispatchCost(0.15);
|
|
92622
92748
|
let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
|
|
92623
92749
|
method: "POST",
|
|
92624
92750
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92718,6 +92844,7 @@ async function startProxy(options) {
|
|
|
92718
92844
|
} catch {
|
|
92719
92845
|
}
|
|
92720
92846
|
try {
|
|
92847
|
+
publishDispatchCost(estimateVideoCost(videoModel, videoDuration, videoHasImageInput));
|
|
92721
92848
|
const submitResp = await payFetch(`${apiBase}/v1/videos/generations`, {
|
|
92722
92849
|
method: "POST",
|
|
92723
92850
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -93002,11 +93129,7 @@ async function startProxy(options) {
|
|
|
93002
93129
|
const existingProxy2 = await checkExistingProxy(listenPort);
|
|
93003
93130
|
if (existingProxy2) {
|
|
93004
93131
|
console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
|
|
93005
|
-
rejectAttempt({
|
|
93006
|
-
code: "REUSE_EXISTING",
|
|
93007
|
-
wallet: existingProxy2.wallet,
|
|
93008
|
-
existingChain: existingProxy2.paymentChain
|
|
93009
|
-
});
|
|
93132
|
+
rejectAttempt({ code: "REUSE_EXISTING", existing: existingProxy2 });
|
|
93010
93133
|
return;
|
|
93011
93134
|
}
|
|
93012
93135
|
if (attempt < PORT_RETRY_ATTEMPTS) {
|
|
@@ -93038,21 +93161,21 @@ async function startProxy(options) {
|
|
|
93038
93161
|
break;
|
|
93039
93162
|
} catch (err) {
|
|
93040
93163
|
const error = err;
|
|
93041
|
-
if (error.code === "REUSE_EXISTING" && error.
|
|
93042
|
-
|
|
93043
|
-
|
|
93044
|
-
|
|
93045
|
-
|
|
93046
|
-
|
|
93047
|
-
}
|
|
93164
|
+
if (error.code === "REUSE_EXISTING" && error.existing) {
|
|
93165
|
+
assertExistingProxyBillsUs(error.existing, {
|
|
93166
|
+
listenPort,
|
|
93167
|
+
authMode,
|
|
93168
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
93169
|
+
paymentChain
|
|
93170
|
+
});
|
|
93048
93171
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
93049
93172
|
options.onReady?.(listenPort);
|
|
93050
93173
|
return {
|
|
93051
93174
|
port: listenPort,
|
|
93052
93175
|
baseUrl: baseUrl2,
|
|
93053
|
-
walletAddress: error.wallet,
|
|
93176
|
+
walletAddress: error.existing.wallet,
|
|
93054
93177
|
authMode,
|
|
93055
|
-
...
|
|
93178
|
+
...ourApiKeyLabel ? { apiKeyLabel: ourApiKeyLabel } : {},
|
|
93056
93179
|
balanceMonitor,
|
|
93057
93180
|
close: async () => {
|
|
93058
93181
|
}
|
|
@@ -93517,6 +93640,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93517
93640
|
size: imageSize,
|
|
93518
93641
|
n: 1
|
|
93519
93642
|
});
|
|
93643
|
+
publishDispatchCost(estimateImageCost(imageModel, imageSize, 1));
|
|
93520
93644
|
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
93521
93645
|
method: "POST",
|
|
93522
93646
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -93798,6 +93922,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93798
93922
|
size: img2imgSize,
|
|
93799
93923
|
n: 1
|
|
93800
93924
|
});
|
|
93925
|
+
publishDispatchCost(estimateImageCost(img2imgModel, img2imgSize, 1));
|
|
93801
93926
|
const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
93802
93927
|
method: "POST",
|
|
93803
93928
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -94627,6 +94752,8 @@ data: [DONE]
|
|
|
94627
94752
|
throw abortError();
|
|
94628
94753
|
}
|
|
94629
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);
|
|
94630
94757
|
const perAttemptTimeoutMs = timeoutForModel(tryModel);
|
|
94631
94758
|
const modelController = new AbortController();
|
|
94632
94759
|
const modelTimeoutId = setTimeout(() => modelController.abort(), perAttemptTimeoutMs);
|
|
@@ -94726,6 +94853,7 @@ data: [DONE]
|
|
|
94726
94853
|
);
|
|
94727
94854
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
94728
94855
|
if (!globalController.signal.aborted) {
|
|
94856
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94729
94857
|
const retryController = new AbortController();
|
|
94730
94858
|
const retryTimeoutId = setTimeout(
|
|
94731
94859
|
() => retryController.abort(),
|
|
@@ -94788,6 +94916,7 @@ data: [DONE]
|
|
|
94788
94916
|
);
|
|
94789
94917
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
94790
94918
|
if (!globalController.signal.aborted) {
|
|
94919
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94791
94920
|
const retryController = new AbortController();
|
|
94792
94921
|
const retryTimeoutId = setTimeout(
|
|
94793
94922
|
() => retryController.abort(),
|
|
@@ -228566,6 +228695,19 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
228566
228695
|
const cfg = config ?? {};
|
|
228567
228696
|
return cfg.tools?.web?.search?.enabled === false;
|
|
228568
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
|
+
}
|
|
228569
228711
|
function injectModelsConfig(logger48, options = {}) {
|
|
228570
228712
|
const configDir = join18(homedir15(), ".openclaw");
|
|
228571
228713
|
const configPath = join18(configDir, "openclaw.json");
|
|
@@ -228729,9 +228871,9 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
228729
228871
|
const legacyEntry = config.plugins?.entries?.clawrouter;
|
|
228730
228872
|
const legacyEntryConfig = legacyEntry?.config;
|
|
228731
228873
|
const legacyBlockRunInstall = Boolean(
|
|
228732
|
-
legacyEntry && ["walletKey", "routing"].some(
|
|
228874
|
+
legacyEntry && (["walletKey", "routing"].some(
|
|
228733
228875
|
(key2) => key2 in legacyEntry || (legacyEntryConfig ? key2 in legacyEntryConfig : false)
|
|
228734
|
-
)
|
|
228876
|
+
) || legacyPackageIsBlockRun())
|
|
228735
228877
|
);
|
|
228736
228878
|
if (legacyBlockRunInstall && prepareBlockRunPluginConfig(config, { legacyBlockRunInstall })) {
|
|
228737
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
|
|
|
@@ -91361,6 +91458,10 @@ function settledMediaCostUsd(upstream, body, x402AmountUsd, estimate) {
|
|
|
91361
91458
|
if (typeof x402AmountUsd === "number" && x402AmountUsd > 0) return x402AmountUsd;
|
|
91362
91459
|
return estimate;
|
|
91363
91460
|
}
|
|
91461
|
+
function publishDispatchCost(usd) {
|
|
91462
|
+
const store = paymentStore.getStore();
|
|
91463
|
+
if (store) store.estimatedUsd = Number.isFinite(usd) && usd > 0 ? usd : 0;
|
|
91464
|
+
}
|
|
91364
91465
|
function noteRemainingCredit(response) {
|
|
91365
91466
|
const remaining = gatewayRemainingCreditUsd(response);
|
|
91366
91467
|
if (remaining === void 0) return;
|
|
@@ -91410,6 +91511,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91410
91511
|
res.on("close", () => {
|
|
91411
91512
|
if (!res.writableEnded) clientAbort.abort();
|
|
91412
91513
|
});
|
|
91514
|
+
publishDispatchCost(0);
|
|
91413
91515
|
const upstream = await payFetch(upstreamUrl, {
|
|
91414
91516
|
method: req.method ?? "POST",
|
|
91415
91517
|
headers,
|
|
@@ -91519,18 +91621,49 @@ function warnIfSpendLimitsUnenforced(spendControl) {
|
|
|
91519
91621
|
let configured;
|
|
91520
91622
|
try {
|
|
91521
91623
|
const limits = (spendControl ?? new SpendControl()).getStatus().limits;
|
|
91522
|
-
configured =
|
|
91624
|
+
configured = POLICY_LISTS.filter((list) => (limits[list]?.length ?? 0) > 0);
|
|
91523
91625
|
} catch {
|
|
91524
91626
|
return;
|
|
91525
91627
|
}
|
|
91526
91628
|
if (configured.length === 0) return;
|
|
91527
91629
|
console.warn(
|
|
91528
|
-
`[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.`
|
|
91529
91631
|
);
|
|
91530
91632
|
console.warn(
|
|
91531
|
-
`[ClawRouter] Your
|
|
91633
|
+
`[ClawRouter] Your perRequest/hourly/daily/session limits DO apply here and are enforced per call.`
|
|
91532
91634
|
);
|
|
91533
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
|
+
}
|
|
91534
91667
|
async function startProxy(options) {
|
|
91535
91668
|
const upstreamProxy = await applyUpstreamProxy(options.upstreamProxy);
|
|
91536
91669
|
if (upstreamProxy) {
|
|
@@ -91571,28 +91704,23 @@ async function startProxy(options) {
|
|
|
91571
91704
|
void loadGatewayCatalog(apiBase, apiKey);
|
|
91572
91705
|
const listenPort = options.port ?? getProxyPort();
|
|
91573
91706
|
const existingProxy = options.allowExistingProxy === false ? void 0 : await checkExistingProxy(listenPort);
|
|
91707
|
+
const ourApiKeyLabel = apiKey ? maskApiKey(apiKey) : void 0;
|
|
91574
91708
|
if (existingProxy) {
|
|
91575
91709
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
91576
|
-
|
|
91577
|
-
|
|
91578
|
-
|
|
91579
|
-
|
|
91580
|
-
|
|
91710
|
+
assertExistingProxyBillsUs(existingProxy, {
|
|
91711
|
+
listenPort,
|
|
91712
|
+
authMode,
|
|
91713
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
91714
|
+
paymentChain
|
|
91715
|
+
});
|
|
91581
91716
|
if (authMode === "api-key") {
|
|
91582
|
-
const runningLabel = existingProxy.apiKeyLabel;
|
|
91583
|
-
const ourLabel = maskApiKey(apiKey);
|
|
91584
|
-
if (runningLabel !== ourLabel) {
|
|
91585
|
-
throw new Error(
|
|
91586
|
-
`Existing proxy on port ${listenPort} is billing ${runningLabel ?? "an unidentified BlockRun account"}, but this process is configured for ${ourLabel}. Reusing it would charge the other account. Stop that proxy first, or use a different port.`
|
|
91587
|
-
);
|
|
91588
|
-
}
|
|
91589
91717
|
options.onReady?.(listenPort);
|
|
91590
91718
|
return {
|
|
91591
91719
|
port: listenPort,
|
|
91592
91720
|
baseUrl: baseUrl2,
|
|
91593
91721
|
walletAddress: "",
|
|
91594
91722
|
authMode,
|
|
91595
|
-
apiKeyLabel:
|
|
91723
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
91596
91724
|
balanceMonitor: new ApiKeyBalanceMonitor(),
|
|
91597
91725
|
// No-op: we didn't start this proxy, so we shouldn't close it
|
|
91598
91726
|
close: async () => {
|
|
@@ -91605,20 +91733,6 @@ async function startProxy(options) {
|
|
|
91605
91733
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
91606
91734
|
);
|
|
91607
91735
|
}
|
|
91608
|
-
if (existingProxy.paymentChain) {
|
|
91609
|
-
if (existingProxy.paymentChain !== paymentChain) {
|
|
91610
|
-
throw new Error(
|
|
91611
|
-
`Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
|
|
91612
|
-
);
|
|
91613
|
-
}
|
|
91614
|
-
} else if (paymentChain !== "base") {
|
|
91615
|
-
console.warn(
|
|
91616
|
-
`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`
|
|
91617
|
-
);
|
|
91618
|
-
throw new Error(
|
|
91619
|
-
`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.`
|
|
91620
|
-
);
|
|
91621
|
-
}
|
|
91622
91736
|
let reuseSolanaAddress;
|
|
91623
91737
|
if (solanaPrivateKeyBytes) {
|
|
91624
91738
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
@@ -91688,7 +91802,16 @@ async function startProxy(options) {
|
|
|
91688
91802
|
if (store) store.amountUsd = amountUsd;
|
|
91689
91803
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91690
91804
|
});
|
|
91691
|
-
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, {
|
|
91692
91815
|
skipPreAuth: paymentChain === "solana",
|
|
91693
91816
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91694
91817
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -92071,6 +92194,7 @@ async function startProxy(options) {
|
|
|
92071
92194
|
} catch {
|
|
92072
92195
|
}
|
|
92073
92196
|
try {
|
|
92197
|
+
publishDispatchCost(imgCost);
|
|
92074
92198
|
const upstream = await payFetch(`${apiBase}/v1/images/generations`, {
|
|
92075
92199
|
method: "POST",
|
|
92076
92200
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92269,6 +92393,7 @@ async function startProxy(options) {
|
|
|
92269
92393
|
return;
|
|
92270
92394
|
}
|
|
92271
92395
|
try {
|
|
92396
|
+
publishDispatchCost(img2imgCost);
|
|
92272
92397
|
let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
92273
92398
|
method: "POST",
|
|
92274
92399
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92372,6 +92497,7 @@ async function startProxy(options) {
|
|
|
92372
92497
|
} catch {
|
|
92373
92498
|
}
|
|
92374
92499
|
try {
|
|
92500
|
+
publishDispatchCost(0.15);
|
|
92375
92501
|
let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
|
|
92376
92502
|
method: "POST",
|
|
92377
92503
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92471,6 +92597,7 @@ async function startProxy(options) {
|
|
|
92471
92597
|
} catch {
|
|
92472
92598
|
}
|
|
92473
92599
|
try {
|
|
92600
|
+
publishDispatchCost(estimateVideoCost(videoModel, videoDuration, videoHasImageInput));
|
|
92474
92601
|
const submitResp = await payFetch(`${apiBase}/v1/videos/generations`, {
|
|
92475
92602
|
method: "POST",
|
|
92476
92603
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -92755,11 +92882,7 @@ async function startProxy(options) {
|
|
|
92755
92882
|
const existingProxy2 = await checkExistingProxy(listenPort);
|
|
92756
92883
|
if (existingProxy2) {
|
|
92757
92884
|
console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
|
|
92758
|
-
rejectAttempt({
|
|
92759
|
-
code: "REUSE_EXISTING",
|
|
92760
|
-
wallet: existingProxy2.wallet,
|
|
92761
|
-
existingChain: existingProxy2.paymentChain
|
|
92762
|
-
});
|
|
92885
|
+
rejectAttempt({ code: "REUSE_EXISTING", existing: existingProxy2 });
|
|
92763
92886
|
return;
|
|
92764
92887
|
}
|
|
92765
92888
|
if (attempt < PORT_RETRY_ATTEMPTS) {
|
|
@@ -92791,21 +92914,21 @@ async function startProxy(options) {
|
|
|
92791
92914
|
break;
|
|
92792
92915
|
} catch (err) {
|
|
92793
92916
|
const error = err;
|
|
92794
|
-
if (error.code === "REUSE_EXISTING" && error.
|
|
92795
|
-
|
|
92796
|
-
|
|
92797
|
-
|
|
92798
|
-
|
|
92799
|
-
|
|
92800
|
-
}
|
|
92917
|
+
if (error.code === "REUSE_EXISTING" && error.existing) {
|
|
92918
|
+
assertExistingProxyBillsUs(error.existing, {
|
|
92919
|
+
listenPort,
|
|
92920
|
+
authMode,
|
|
92921
|
+
apiKeyLabel: ourApiKeyLabel,
|
|
92922
|
+
paymentChain
|
|
92923
|
+
});
|
|
92801
92924
|
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
92802
92925
|
options.onReady?.(listenPort);
|
|
92803
92926
|
return {
|
|
92804
92927
|
port: listenPort,
|
|
92805
92928
|
baseUrl: baseUrl2,
|
|
92806
|
-
walletAddress: error.wallet,
|
|
92929
|
+
walletAddress: error.existing.wallet,
|
|
92807
92930
|
authMode,
|
|
92808
|
-
...
|
|
92931
|
+
...ourApiKeyLabel ? { apiKeyLabel: ourApiKeyLabel } : {},
|
|
92809
92932
|
balanceMonitor,
|
|
92810
92933
|
close: async () => {
|
|
92811
92934
|
}
|
|
@@ -93270,6 +93393,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93270
93393
|
size: imageSize,
|
|
93271
93394
|
n: 1
|
|
93272
93395
|
});
|
|
93396
|
+
publishDispatchCost(estimateImageCost(imageModel, imageSize, 1));
|
|
93273
93397
|
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
93274
93398
|
method: "POST",
|
|
93275
93399
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -93551,6 +93675,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93551
93675
|
size: img2imgSize,
|
|
93552
93676
|
n: 1
|
|
93553
93677
|
});
|
|
93678
|
+
publishDispatchCost(estimateImageCost(img2imgModel, img2imgSize, 1));
|
|
93554
93679
|
const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
93555
93680
|
method: "POST",
|
|
93556
93681
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
@@ -94380,6 +94505,8 @@ data: [DONE]
|
|
|
94380
94505
|
throw abortError();
|
|
94381
94506
|
}
|
|
94382
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);
|
|
94383
94510
|
const perAttemptTimeoutMs = timeoutForModel(tryModel);
|
|
94384
94511
|
const modelController = new AbortController();
|
|
94385
94512
|
const modelTimeoutId = setTimeout(() => modelController.abort(), perAttemptTimeoutMs);
|
|
@@ -94479,6 +94606,7 @@ data: [DONE]
|
|
|
94479
94606
|
);
|
|
94480
94607
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
94481
94608
|
if (!globalController.signal.aborted) {
|
|
94609
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94482
94610
|
const retryController = new AbortController();
|
|
94483
94611
|
const retryTimeoutId = setTimeout(
|
|
94484
94612
|
() => retryController.abort(),
|
|
@@ -94541,6 +94669,7 @@ data: [DONE]
|
|
|
94541
94669
|
);
|
|
94542
94670
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
94543
94671
|
if (!globalController.signal.aborted) {
|
|
94672
|
+
publishDispatchCost(attemptEst ? Number(attemptEst) / 1e6 : 0);
|
|
94544
94673
|
const retryController = new AbortController();
|
|
94545
94674
|
const retryTimeoutId = setTimeout(
|
|
94546
94675
|
() => retryController.abort(),
|
|
@@ -228050,6 +228179,19 @@ function isBlockrunWebSearchDisabled(config) {
|
|
|
228050
228179
|
const cfg = config ?? {};
|
|
228051
228180
|
return cfg.tools?.web?.search?.enabled === false;
|
|
228052
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
|
+
}
|
|
228053
228195
|
function injectModelsConfig(logger48, options = {}) {
|
|
228054
228196
|
const configDir = join17(homedir14(), ".openclaw");
|
|
228055
228197
|
const configPath = join17(configDir, "openclaw.json");
|
|
@@ -228213,9 +228355,9 @@ function injectModelsConfig(logger48, options = {}) {
|
|
|
228213
228355
|
const legacyEntry = config.plugins?.entries?.clawrouter;
|
|
228214
228356
|
const legacyEntryConfig = legacyEntry?.config;
|
|
228215
228357
|
const legacyBlockRunInstall = Boolean(
|
|
228216
|
-
legacyEntry && ["walletKey", "routing"].some(
|
|
228358
|
+
legacyEntry && (["walletKey", "routing"].some(
|
|
228217
228359
|
(key2) => key2 in legacyEntry || (legacyEntryConfig ? key2 in legacyEntryConfig : false)
|
|
228218
|
-
)
|
|
228360
|
+
) || legacyPackageIsBlockRun())
|
|
228219
228361
|
);
|
|
228220
228362
|
if (legacyBlockRunInstall && prepareBlockRunPluginConfig(config, { legacyBlockRunInstall })) {
|
|
228221
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",
|