@blockrun/clawrouter 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +291 -217
- package/dist/index.d.ts +216 -3
- package/dist/index.js +658 -84
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/dist/index.js
CHANGED
|
@@ -95,40 +95,260 @@ var envKeyAuth = {
|
|
|
95
95
|
// src/models.ts
|
|
96
96
|
var BLOCKRUN_MODELS = [
|
|
97
97
|
// Smart routing meta-model — proxy replaces with actual model
|
|
98
|
-
{
|
|
98
|
+
{
|
|
99
|
+
id: "blockrun/auto",
|
|
100
|
+
name: "BlockRun Smart Router",
|
|
101
|
+
inputPrice: 0,
|
|
102
|
+
outputPrice: 0,
|
|
103
|
+
contextWindow: 105e4,
|
|
104
|
+
maxOutput: 128e3
|
|
105
|
+
},
|
|
99
106
|
// OpenAI GPT-5 Family
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
{
|
|
108
|
+
id: "openai/gpt-5.2",
|
|
109
|
+
name: "GPT-5.2",
|
|
110
|
+
inputPrice: 1.75,
|
|
111
|
+
outputPrice: 14,
|
|
112
|
+
contextWindow: 4e5,
|
|
113
|
+
maxOutput: 128e3,
|
|
114
|
+
reasoning: true,
|
|
115
|
+
vision: true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "openai/gpt-5-mini",
|
|
119
|
+
name: "GPT-5 Mini",
|
|
120
|
+
inputPrice: 0.25,
|
|
121
|
+
outputPrice: 2,
|
|
122
|
+
contextWindow: 2e5,
|
|
123
|
+
maxOutput: 65536
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "openai/gpt-5-nano",
|
|
127
|
+
name: "GPT-5 Nano",
|
|
128
|
+
inputPrice: 0.05,
|
|
129
|
+
outputPrice: 0.4,
|
|
130
|
+
contextWindow: 128e3,
|
|
131
|
+
maxOutput: 32768
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "openai/gpt-5.2-pro",
|
|
135
|
+
name: "GPT-5.2 Pro",
|
|
136
|
+
inputPrice: 21,
|
|
137
|
+
outputPrice: 168,
|
|
138
|
+
contextWindow: 4e5,
|
|
139
|
+
maxOutput: 128e3,
|
|
140
|
+
reasoning: true
|
|
141
|
+
},
|
|
104
142
|
// OpenAI GPT-4 Family
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
143
|
+
{
|
|
144
|
+
id: "openai/gpt-4.1",
|
|
145
|
+
name: "GPT-4.1",
|
|
146
|
+
inputPrice: 2,
|
|
147
|
+
outputPrice: 8,
|
|
148
|
+
contextWindow: 128e3,
|
|
149
|
+
maxOutput: 16384,
|
|
150
|
+
vision: true
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "openai/gpt-4.1-mini",
|
|
154
|
+
name: "GPT-4.1 Mini",
|
|
155
|
+
inputPrice: 0.4,
|
|
156
|
+
outputPrice: 1.6,
|
|
157
|
+
contextWindow: 128e3,
|
|
158
|
+
maxOutput: 16384
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "openai/gpt-4.1-nano",
|
|
162
|
+
name: "GPT-4.1 Nano",
|
|
163
|
+
inputPrice: 0.1,
|
|
164
|
+
outputPrice: 0.4,
|
|
165
|
+
contextWindow: 128e3,
|
|
166
|
+
maxOutput: 16384
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "openai/gpt-4o",
|
|
170
|
+
name: "GPT-4o",
|
|
171
|
+
inputPrice: 2.5,
|
|
172
|
+
outputPrice: 10,
|
|
173
|
+
contextWindow: 128e3,
|
|
174
|
+
maxOutput: 16384,
|
|
175
|
+
vision: true
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "openai/gpt-4o-mini",
|
|
179
|
+
name: "GPT-4o Mini",
|
|
180
|
+
inputPrice: 0.15,
|
|
181
|
+
outputPrice: 0.6,
|
|
182
|
+
contextWindow: 128e3,
|
|
183
|
+
maxOutput: 16384
|
|
184
|
+
},
|
|
110
185
|
// OpenAI O-series (Reasoning)
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
186
|
+
{
|
|
187
|
+
id: "openai/o1",
|
|
188
|
+
name: "o1",
|
|
189
|
+
inputPrice: 15,
|
|
190
|
+
outputPrice: 60,
|
|
191
|
+
contextWindow: 2e5,
|
|
192
|
+
maxOutput: 1e5,
|
|
193
|
+
reasoning: true
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "openai/o1-mini",
|
|
197
|
+
name: "o1-mini",
|
|
198
|
+
inputPrice: 1.1,
|
|
199
|
+
outputPrice: 4.4,
|
|
200
|
+
contextWindow: 128e3,
|
|
201
|
+
maxOutput: 65536,
|
|
202
|
+
reasoning: true
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "openai/o3",
|
|
206
|
+
name: "o3",
|
|
207
|
+
inputPrice: 2,
|
|
208
|
+
outputPrice: 8,
|
|
209
|
+
contextWindow: 2e5,
|
|
210
|
+
maxOutput: 1e5,
|
|
211
|
+
reasoning: true
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "openai/o3-mini",
|
|
215
|
+
name: "o3-mini",
|
|
216
|
+
inputPrice: 1.1,
|
|
217
|
+
outputPrice: 4.4,
|
|
218
|
+
contextWindow: 128e3,
|
|
219
|
+
maxOutput: 65536,
|
|
220
|
+
reasoning: true
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "openai/o4-mini",
|
|
224
|
+
name: "o4-mini",
|
|
225
|
+
inputPrice: 1.1,
|
|
226
|
+
outputPrice: 4.4,
|
|
227
|
+
contextWindow: 128e3,
|
|
228
|
+
maxOutput: 65536,
|
|
229
|
+
reasoning: true
|
|
230
|
+
},
|
|
116
231
|
// Anthropic
|
|
117
|
-
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
232
|
+
{
|
|
233
|
+
id: "anthropic/claude-haiku-4.5",
|
|
234
|
+
name: "Claude Haiku 4.5",
|
|
235
|
+
inputPrice: 1,
|
|
236
|
+
outputPrice: 5,
|
|
237
|
+
contextWindow: 2e5,
|
|
238
|
+
maxOutput: 8192
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "anthropic/claude-sonnet-4",
|
|
242
|
+
name: "Claude Sonnet 4",
|
|
243
|
+
inputPrice: 3,
|
|
244
|
+
outputPrice: 15,
|
|
245
|
+
contextWindow: 2e5,
|
|
246
|
+
maxOutput: 64e3,
|
|
247
|
+
reasoning: true
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "anthropic/claude-opus-4",
|
|
251
|
+
name: "Claude Opus 4",
|
|
252
|
+
inputPrice: 15,
|
|
253
|
+
outputPrice: 75,
|
|
254
|
+
contextWindow: 2e5,
|
|
255
|
+
maxOutput: 32e3,
|
|
256
|
+
reasoning: true
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "anthropic/claude-opus-4.5",
|
|
260
|
+
name: "Claude Opus 4.5",
|
|
261
|
+
inputPrice: 5,
|
|
262
|
+
outputPrice: 25,
|
|
263
|
+
contextWindow: 2e5,
|
|
264
|
+
maxOutput: 32e3,
|
|
265
|
+
reasoning: true
|
|
266
|
+
},
|
|
121
267
|
// Google
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
268
|
+
{
|
|
269
|
+
id: "google/gemini-3-pro-preview",
|
|
270
|
+
name: "Gemini 3 Pro Preview",
|
|
271
|
+
inputPrice: 2,
|
|
272
|
+
outputPrice: 12,
|
|
273
|
+
contextWindow: 105e4,
|
|
274
|
+
maxOutput: 65536,
|
|
275
|
+
reasoning: true,
|
|
276
|
+
vision: true
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "google/gemini-2.5-pro",
|
|
280
|
+
name: "Gemini 2.5 Pro",
|
|
281
|
+
inputPrice: 1.25,
|
|
282
|
+
outputPrice: 10,
|
|
283
|
+
contextWindow: 105e4,
|
|
284
|
+
maxOutput: 65536,
|
|
285
|
+
reasoning: true,
|
|
286
|
+
vision: true
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "google/gemini-2.5-flash",
|
|
290
|
+
name: "Gemini 2.5 Flash",
|
|
291
|
+
inputPrice: 0.15,
|
|
292
|
+
outputPrice: 0.6,
|
|
293
|
+
contextWindow: 1e6,
|
|
294
|
+
maxOutput: 65536
|
|
295
|
+
},
|
|
125
296
|
// DeepSeek
|
|
126
|
-
{
|
|
127
|
-
|
|
297
|
+
{
|
|
298
|
+
id: "deepseek/deepseek-chat",
|
|
299
|
+
name: "DeepSeek V3.2 Chat",
|
|
300
|
+
inputPrice: 0.28,
|
|
301
|
+
outputPrice: 0.42,
|
|
302
|
+
contextWindow: 128e3,
|
|
303
|
+
maxOutput: 8192
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: "deepseek/deepseek-reasoner",
|
|
307
|
+
name: "DeepSeek V3.2 Reasoner",
|
|
308
|
+
inputPrice: 0.28,
|
|
309
|
+
outputPrice: 0.42,
|
|
310
|
+
contextWindow: 128e3,
|
|
311
|
+
maxOutput: 8192,
|
|
312
|
+
reasoning: true
|
|
313
|
+
},
|
|
314
|
+
// Moonshot / Kimi
|
|
315
|
+
{
|
|
316
|
+
id: "moonshot/kimi-k2.5",
|
|
317
|
+
name: "Kimi K2.5",
|
|
318
|
+
inputPrice: 0.5,
|
|
319
|
+
outputPrice: 2.4,
|
|
320
|
+
contextWindow: 262144,
|
|
321
|
+
maxOutput: 8192,
|
|
322
|
+
reasoning: true,
|
|
323
|
+
vision: true
|
|
324
|
+
},
|
|
128
325
|
// xAI / Grok
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
326
|
+
{
|
|
327
|
+
id: "xai/grok-3",
|
|
328
|
+
name: "Grok 3",
|
|
329
|
+
inputPrice: 3,
|
|
330
|
+
outputPrice: 15,
|
|
331
|
+
contextWindow: 131072,
|
|
332
|
+
maxOutput: 16384,
|
|
333
|
+
reasoning: true
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "xai/grok-3-fast",
|
|
337
|
+
name: "Grok 3 Fast",
|
|
338
|
+
inputPrice: 5,
|
|
339
|
+
outputPrice: 25,
|
|
340
|
+
contextWindow: 131072,
|
|
341
|
+
maxOutput: 16384,
|
|
342
|
+
reasoning: true
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: "xai/grok-3-mini",
|
|
346
|
+
name: "Grok 3 Mini",
|
|
347
|
+
inputPrice: 0.3,
|
|
348
|
+
outputPrice: 0.5,
|
|
349
|
+
contextWindow: 131072,
|
|
350
|
+
maxOutput: 16384
|
|
351
|
+
}
|
|
132
352
|
];
|
|
133
353
|
function toOpenClawModel(m) {
|
|
134
354
|
return {
|
|
@@ -393,10 +613,18 @@ function scoreTokenCount(estimatedTokens, thresholds) {
|
|
|
393
613
|
function scoreKeywordMatch(text, keywords, name, signalLabel, thresholds, scores) {
|
|
394
614
|
const matches = keywords.filter((kw) => text.includes(kw.toLowerCase()));
|
|
395
615
|
if (matches.length >= thresholds.high) {
|
|
396
|
-
return {
|
|
616
|
+
return {
|
|
617
|
+
name,
|
|
618
|
+
score: scores.high,
|
|
619
|
+
signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})`
|
|
620
|
+
};
|
|
397
621
|
}
|
|
398
622
|
if (matches.length >= thresholds.low) {
|
|
399
|
-
return {
|
|
623
|
+
return {
|
|
624
|
+
name,
|
|
625
|
+
score: scores.low,
|
|
626
|
+
signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})`
|
|
627
|
+
};
|
|
400
628
|
}
|
|
401
629
|
return { name, score: scores.none, signal: null };
|
|
402
630
|
}
|
|
@@ -519,9 +747,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
519
747
|
const w = weights[d.name] ?? 0;
|
|
520
748
|
weightedScore += d.score * w;
|
|
521
749
|
}
|
|
522
|
-
const reasoningMatches = config.reasoningKeywords.filter(
|
|
523
|
-
(kw) => text.includes(kw.toLowerCase())
|
|
524
|
-
);
|
|
750
|
+
const reasoningMatches = config.reasoningKeywords.filter((kw) => text.includes(kw.toLowerCase()));
|
|
525
751
|
if (reasoningMatches.length >= 2) {
|
|
526
752
|
const confidence2 = calibrateConfidence(
|
|
527
753
|
Math.max(weightedScore, 0.3),
|
|
@@ -543,10 +769,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
543
769
|
distanceFromBoundary = simpleMedium - weightedScore;
|
|
544
770
|
} else if (weightedScore < mediumComplex) {
|
|
545
771
|
tier = "MEDIUM";
|
|
546
|
-
distanceFromBoundary = Math.min(
|
|
547
|
-
weightedScore - simpleMedium,
|
|
548
|
-
mediumComplex - weightedScore
|
|
549
|
-
);
|
|
772
|
+
distanceFromBoundary = Math.min(weightedScore - simpleMedium, mediumComplex - weightedScore);
|
|
550
773
|
} else if (weightedScore < complexReasoning) {
|
|
551
774
|
tier = "COMPLEX";
|
|
552
775
|
distanceFromBoundary = Math.min(
|
|
@@ -651,15 +874,7 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
651
874
|
"database",
|
|
652
875
|
"infrastructure"
|
|
653
876
|
],
|
|
654
|
-
creativeKeywords: [
|
|
655
|
-
"story",
|
|
656
|
-
"poem",
|
|
657
|
-
"compose",
|
|
658
|
-
"brainstorm",
|
|
659
|
-
"creative",
|
|
660
|
-
"imagine",
|
|
661
|
-
"write a"
|
|
662
|
-
],
|
|
877
|
+
creativeKeywords: ["story", "poem", "compose", "brainstorm", "creative", "imagine", "write a"],
|
|
663
878
|
// New dimension keyword lists
|
|
664
879
|
imperativeVerbs: [
|
|
665
880
|
"build",
|
|
@@ -802,15 +1017,10 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
802
1017
|
);
|
|
803
1018
|
}
|
|
804
1019
|
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
805
|
-
const ruleResult = classifyByRules(
|
|
806
|
-
prompt,
|
|
807
|
-
systemPrompt,
|
|
808
|
-
estimatedTokens,
|
|
809
|
-
config.scoring
|
|
810
|
-
);
|
|
1020
|
+
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
811
1021
|
let tier;
|
|
812
1022
|
let confidence;
|
|
813
|
-
|
|
1023
|
+
const method = "rules";
|
|
814
1024
|
let reasoning = `score=${ruleResult.score} | ${ruleResult.signals.join(", ")}`;
|
|
815
1025
|
if (ruleResult.tier !== null) {
|
|
816
1026
|
tier = ruleResult.tier;
|
|
@@ -891,14 +1101,16 @@ var RequestDeduplicator = class {
|
|
|
891
1101
|
const entry = this.inflight.get(key);
|
|
892
1102
|
if (!entry) return void 0;
|
|
893
1103
|
const promise = new Promise((resolve) => {
|
|
894
|
-
entry.waiters.push(
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1104
|
+
entry.waiters.push(
|
|
1105
|
+
new Promise((r) => {
|
|
1106
|
+
const orig = entry.resolve;
|
|
1107
|
+
entry.resolve = (result) => {
|
|
1108
|
+
orig(result);
|
|
1109
|
+
resolve(result);
|
|
1110
|
+
r(result);
|
|
1111
|
+
};
|
|
1112
|
+
})
|
|
1113
|
+
);
|
|
902
1114
|
});
|
|
903
1115
|
return promise;
|
|
904
1116
|
}
|
|
@@ -937,11 +1149,183 @@ var RequestDeduplicator = class {
|
|
|
937
1149
|
}
|
|
938
1150
|
};
|
|
939
1151
|
|
|
1152
|
+
// src/balance.ts
|
|
1153
|
+
import { createPublicClient, http, erc20Abi } from "viem";
|
|
1154
|
+
import { base } from "viem/chains";
|
|
1155
|
+
|
|
1156
|
+
// src/errors.ts
|
|
1157
|
+
var InsufficientFundsError = class extends Error {
|
|
1158
|
+
code = "INSUFFICIENT_FUNDS";
|
|
1159
|
+
currentBalanceUSD;
|
|
1160
|
+
requiredUSD;
|
|
1161
|
+
walletAddress;
|
|
1162
|
+
constructor(opts) {
|
|
1163
|
+
super(
|
|
1164
|
+
`Insufficient USDC balance. Current: ${opts.currentBalanceUSD}, Required: ${opts.requiredUSD}. Fund wallet: ${opts.walletAddress}`
|
|
1165
|
+
);
|
|
1166
|
+
this.name = "InsufficientFundsError";
|
|
1167
|
+
this.currentBalanceUSD = opts.currentBalanceUSD;
|
|
1168
|
+
this.requiredUSD = opts.requiredUSD;
|
|
1169
|
+
this.walletAddress = opts.walletAddress;
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
var EmptyWalletError = class extends Error {
|
|
1173
|
+
code = "EMPTY_WALLET";
|
|
1174
|
+
walletAddress;
|
|
1175
|
+
constructor(walletAddress) {
|
|
1176
|
+
super(`No USDC balance. Fund wallet to use ClawRouter: ${walletAddress}`);
|
|
1177
|
+
this.name = "EmptyWalletError";
|
|
1178
|
+
this.walletAddress = walletAddress;
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
function isInsufficientFundsError(error) {
|
|
1182
|
+
return error instanceof Error && error.code === "INSUFFICIENT_FUNDS";
|
|
1183
|
+
}
|
|
1184
|
+
function isEmptyWalletError(error) {
|
|
1185
|
+
return error instanceof Error && error.code === "EMPTY_WALLET";
|
|
1186
|
+
}
|
|
1187
|
+
function isBalanceError(error) {
|
|
1188
|
+
return isInsufficientFundsError(error) || isEmptyWalletError(error);
|
|
1189
|
+
}
|
|
1190
|
+
var RpcError = class extends Error {
|
|
1191
|
+
code = "RPC_ERROR";
|
|
1192
|
+
originalError;
|
|
1193
|
+
constructor(message, originalError) {
|
|
1194
|
+
super(`RPC error: ${message}. Check network connectivity.`);
|
|
1195
|
+
this.name = "RpcError";
|
|
1196
|
+
this.originalError = originalError;
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
function isRpcError(error) {
|
|
1200
|
+
return error instanceof Error && error.code === "RPC_ERROR";
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/balance.ts
|
|
1204
|
+
var USDC_BASE2 = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
1205
|
+
var CACHE_TTL_MS = 3e4;
|
|
1206
|
+
var BALANCE_THRESHOLDS = {
|
|
1207
|
+
/** Low balance warning threshold: $1.00 */
|
|
1208
|
+
LOW_BALANCE_MICROS: 1000000n,
|
|
1209
|
+
/** Effectively zero threshold: $0.0001 (covers dust/rounding) */
|
|
1210
|
+
ZERO_THRESHOLD: 100n
|
|
1211
|
+
};
|
|
1212
|
+
var BalanceMonitor = class {
|
|
1213
|
+
client;
|
|
1214
|
+
walletAddress;
|
|
1215
|
+
/** Cached balance (null = not yet fetched) */
|
|
1216
|
+
cachedBalance = null;
|
|
1217
|
+
/** Timestamp when cache was last updated */
|
|
1218
|
+
cachedAt = 0;
|
|
1219
|
+
constructor(walletAddress) {
|
|
1220
|
+
this.walletAddress = walletAddress;
|
|
1221
|
+
this.client = createPublicClient({
|
|
1222
|
+
chain: base,
|
|
1223
|
+
transport: http()
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Check current USDC balance.
|
|
1228
|
+
* Uses cache if valid, otherwise fetches from RPC.
|
|
1229
|
+
*/
|
|
1230
|
+
async checkBalance() {
|
|
1231
|
+
const now = Date.now();
|
|
1232
|
+
if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS) {
|
|
1233
|
+
return this.buildInfo(this.cachedBalance);
|
|
1234
|
+
}
|
|
1235
|
+
const balance = await this.fetchBalance();
|
|
1236
|
+
this.cachedBalance = balance;
|
|
1237
|
+
this.cachedAt = now;
|
|
1238
|
+
return this.buildInfo(balance);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Check if balance is sufficient for an estimated cost.
|
|
1242
|
+
*
|
|
1243
|
+
* @param estimatedCostMicros - Estimated cost in USDC smallest unit (6 decimals)
|
|
1244
|
+
*/
|
|
1245
|
+
async checkSufficient(estimatedCostMicros) {
|
|
1246
|
+
const info = await this.checkBalance();
|
|
1247
|
+
if (info.balance >= estimatedCostMicros) {
|
|
1248
|
+
return { sufficient: true, info };
|
|
1249
|
+
}
|
|
1250
|
+
const shortfall = estimatedCostMicros - info.balance;
|
|
1251
|
+
return {
|
|
1252
|
+
sufficient: false,
|
|
1253
|
+
info,
|
|
1254
|
+
shortfall: this.formatUSDC(shortfall)
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Optimistically deduct estimated cost from cached balance.
|
|
1259
|
+
* Call this after a successful payment to keep cache accurate.
|
|
1260
|
+
*
|
|
1261
|
+
* @param amountMicros - Amount to deduct in USDC smallest unit
|
|
1262
|
+
*/
|
|
1263
|
+
deductEstimated(amountMicros) {
|
|
1264
|
+
if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
|
|
1265
|
+
this.cachedBalance -= amountMicros;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Invalidate cache, forcing next checkBalance() to fetch from RPC.
|
|
1270
|
+
* Call this after a payment failure to get accurate balance.
|
|
1271
|
+
*/
|
|
1272
|
+
invalidate() {
|
|
1273
|
+
this.cachedBalance = null;
|
|
1274
|
+
this.cachedAt = 0;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Force refresh balance from RPC (ignores cache).
|
|
1278
|
+
*/
|
|
1279
|
+
async refresh() {
|
|
1280
|
+
this.invalidate();
|
|
1281
|
+
return this.checkBalance();
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Format USDC amount (in micros) as "$X.XX".
|
|
1285
|
+
*/
|
|
1286
|
+
formatUSDC(amountMicros) {
|
|
1287
|
+
const dollars = Number(amountMicros) / 1e6;
|
|
1288
|
+
return `$${dollars.toFixed(2)}`;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Get the wallet address being monitored.
|
|
1292
|
+
*/
|
|
1293
|
+
getWalletAddress() {
|
|
1294
|
+
return this.walletAddress;
|
|
1295
|
+
}
|
|
1296
|
+
/** Fetch balance from RPC */
|
|
1297
|
+
async fetchBalance() {
|
|
1298
|
+
try {
|
|
1299
|
+
const balance = await this.client.readContract({
|
|
1300
|
+
address: USDC_BASE2,
|
|
1301
|
+
abi: erc20Abi,
|
|
1302
|
+
functionName: "balanceOf",
|
|
1303
|
+
args: [this.walletAddress]
|
|
1304
|
+
});
|
|
1305
|
+
return balance;
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
throw new RpcError(error instanceof Error ? error.message : "Unknown error", error);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
/** Build BalanceInfo from raw balance */
|
|
1311
|
+
buildInfo(balance) {
|
|
1312
|
+
return {
|
|
1313
|
+
balance,
|
|
1314
|
+
balanceUSD: this.formatUSDC(balance),
|
|
1315
|
+
isLow: balance < BALANCE_THRESHOLDS.LOW_BALANCE_MICROS,
|
|
1316
|
+
isEmpty: balance < BALANCE_THRESHOLDS.ZERO_THRESHOLD,
|
|
1317
|
+
walletAddress: this.walletAddress
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
|
|
940
1322
|
// src/proxy.ts
|
|
941
1323
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
942
1324
|
var AUTO_MODEL = "blockrun/auto";
|
|
943
|
-
var USER_AGENT = "clawrouter/0.3.
|
|
1325
|
+
var USER_AGENT = "clawrouter/0.3.4";
|
|
944
1326
|
var HEARTBEAT_INTERVAL_MS = 2e3;
|
|
1327
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 18e4;
|
|
1328
|
+
var DEFAULT_PORT = 8402;
|
|
945
1329
|
function buildModelPricing() {
|
|
946
1330
|
const map = /* @__PURE__ */ new Map();
|
|
947
1331
|
for (const m of BLOCKRUN_MODELS) {
|
|
@@ -973,7 +1357,8 @@ function estimateAmount(modelId, bodyLength, maxTokens) {
|
|
|
973
1357
|
async function startProxy(options) {
|
|
974
1358
|
const apiBase = options.apiBase ?? BLOCKRUN_API;
|
|
975
1359
|
const account = privateKeyToAccount3(options.walletKey);
|
|
976
|
-
const { fetch: payFetch
|
|
1360
|
+
const { fetch: payFetch } = createPaymentFetch(options.walletKey);
|
|
1361
|
+
const balanceMonitor = new BalanceMonitor(account.address);
|
|
977
1362
|
const routingConfig = mergeRoutingConfig(options.routingConfig);
|
|
978
1363
|
const modelPricing = buildModelPricing();
|
|
979
1364
|
const routerOpts = {
|
|
@@ -982,9 +1367,25 @@ async function startProxy(options) {
|
|
|
982
1367
|
};
|
|
983
1368
|
const deduplicator = new RequestDeduplicator();
|
|
984
1369
|
const server = createServer(async (req, res) => {
|
|
985
|
-
if (req.url === "/health") {
|
|
1370
|
+
if (req.url === "/health" || req.url?.startsWith("/health?")) {
|
|
1371
|
+
const url = new URL(req.url, "http://localhost");
|
|
1372
|
+
const full = url.searchParams.get("full") === "true";
|
|
1373
|
+
const response = {
|
|
1374
|
+
status: "ok",
|
|
1375
|
+
wallet: account.address
|
|
1376
|
+
};
|
|
1377
|
+
if (full) {
|
|
1378
|
+
try {
|
|
1379
|
+
const balanceInfo = await balanceMonitor.checkBalance();
|
|
1380
|
+
response.balance = balanceInfo.balanceUSD;
|
|
1381
|
+
response.isLow = balanceInfo.isLow;
|
|
1382
|
+
response.isEmpty = balanceInfo.isEmpty;
|
|
1383
|
+
} catch {
|
|
1384
|
+
response.balanceError = "Could not fetch balance";
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
986
1387
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
987
|
-
res.end(JSON.stringify(
|
|
1388
|
+
res.end(JSON.stringify(response));
|
|
988
1389
|
return;
|
|
989
1390
|
}
|
|
990
1391
|
if (!req.url?.startsWith("/v1")) {
|
|
@@ -993,25 +1394,38 @@ async function startProxy(options) {
|
|
|
993
1394
|
return;
|
|
994
1395
|
}
|
|
995
1396
|
try {
|
|
996
|
-
await proxyRequest(
|
|
1397
|
+
await proxyRequest(
|
|
1398
|
+
req,
|
|
1399
|
+
res,
|
|
1400
|
+
apiBase,
|
|
1401
|
+
payFetch,
|
|
1402
|
+
options,
|
|
1403
|
+
routerOpts,
|
|
1404
|
+
deduplicator,
|
|
1405
|
+
balanceMonitor
|
|
1406
|
+
);
|
|
997
1407
|
} catch (err) {
|
|
998
1408
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
999
1409
|
options.onError?.(error);
|
|
1000
1410
|
if (!res.headersSent) {
|
|
1001
1411
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
1002
|
-
res.end(
|
|
1003
|
-
|
|
1004
|
-
|
|
1412
|
+
res.end(
|
|
1413
|
+
JSON.stringify({
|
|
1414
|
+
error: { message: `Proxy error: ${error.message}`, type: "proxy_error" }
|
|
1415
|
+
})
|
|
1416
|
+
);
|
|
1005
1417
|
} else if (!res.writableEnded) {
|
|
1006
|
-
res.write(
|
|
1418
|
+
res.write(
|
|
1419
|
+
`data: ${JSON.stringify({ error: { message: error.message, type: "proxy_error" } })}
|
|
1007
1420
|
|
|
1008
|
-
`
|
|
1421
|
+
`
|
|
1422
|
+
);
|
|
1009
1423
|
res.write("data: [DONE]\n\n");
|
|
1010
1424
|
res.end();
|
|
1011
1425
|
}
|
|
1012
1426
|
}
|
|
1013
1427
|
});
|
|
1014
|
-
const listenPort = options.port ??
|
|
1428
|
+
const listenPort = options.port ?? DEFAULT_PORT;
|
|
1015
1429
|
return new Promise((resolve, reject) => {
|
|
1016
1430
|
server.on("error", reject);
|
|
1017
1431
|
server.listen(listenPort, "127.0.0.1", () => {
|
|
@@ -1022,6 +1436,8 @@ async function startProxy(options) {
|
|
|
1022
1436
|
resolve({
|
|
1023
1437
|
port,
|
|
1024
1438
|
baseUrl,
|
|
1439
|
+
walletAddress: account.address,
|
|
1440
|
+
balanceMonitor,
|
|
1025
1441
|
close: () => new Promise((res, rej) => {
|
|
1026
1442
|
server.close((err) => err ? rej(err) : res());
|
|
1027
1443
|
})
|
|
@@ -1029,7 +1445,7 @@ async function startProxy(options) {
|
|
|
1029
1445
|
});
|
|
1030
1446
|
});
|
|
1031
1447
|
}
|
|
1032
|
-
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator) {
|
|
1448
|
+
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor) {
|
|
1033
1449
|
const startTime = Date.now();
|
|
1034
1450
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
1035
1451
|
const bodyChunks = [];
|
|
@@ -1086,13 +1502,51 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1086
1502
|
return;
|
|
1087
1503
|
}
|
|
1088
1504
|
deduplicator.markInflight(dedupKey);
|
|
1505
|
+
let estimatedCostMicros;
|
|
1506
|
+
if (modelId) {
|
|
1507
|
+
const estimated = estimateAmount(modelId, body.length, maxTokens);
|
|
1508
|
+
if (estimated) {
|
|
1509
|
+
estimatedCostMicros = BigInt(estimated);
|
|
1510
|
+
const sufficiency = await balanceMonitor.checkSufficient(estimatedCostMicros);
|
|
1511
|
+
if (sufficiency.info.isEmpty) {
|
|
1512
|
+
deduplicator.removeInflight(dedupKey);
|
|
1513
|
+
const error = new EmptyWalletError(sufficiency.info.walletAddress);
|
|
1514
|
+
options.onInsufficientFunds?.({
|
|
1515
|
+
balanceUSD: sufficiency.info.balanceUSD,
|
|
1516
|
+
requiredUSD: balanceMonitor.formatUSDC(estimatedCostMicros),
|
|
1517
|
+
walletAddress: sufficiency.info.walletAddress
|
|
1518
|
+
});
|
|
1519
|
+
throw error;
|
|
1520
|
+
}
|
|
1521
|
+
if (!sufficiency.sufficient) {
|
|
1522
|
+
deduplicator.removeInflight(dedupKey);
|
|
1523
|
+
const error = new InsufficientFundsError({
|
|
1524
|
+
currentBalanceUSD: sufficiency.info.balanceUSD,
|
|
1525
|
+
requiredUSD: balanceMonitor.formatUSDC(estimatedCostMicros),
|
|
1526
|
+
walletAddress: sufficiency.info.walletAddress
|
|
1527
|
+
});
|
|
1528
|
+
options.onInsufficientFunds?.({
|
|
1529
|
+
balanceUSD: sufficiency.info.balanceUSD,
|
|
1530
|
+
requiredUSD: balanceMonitor.formatUSDC(estimatedCostMicros),
|
|
1531
|
+
walletAddress: sufficiency.info.walletAddress
|
|
1532
|
+
});
|
|
1533
|
+
throw error;
|
|
1534
|
+
}
|
|
1535
|
+
if (sufficiency.info.isLow) {
|
|
1536
|
+
options.onLowBalance?.({
|
|
1537
|
+
balanceUSD: sufficiency.info.balanceUSD,
|
|
1538
|
+
walletAddress: sufficiency.info.walletAddress
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1089
1543
|
let heartbeatInterval;
|
|
1090
1544
|
let headersSentEarly = false;
|
|
1091
1545
|
if (isStreaming) {
|
|
1092
1546
|
res.writeHead(200, {
|
|
1093
1547
|
"content-type": "text/event-stream",
|
|
1094
1548
|
"cache-control": "no-cache",
|
|
1095
|
-
|
|
1549
|
+
connection: "keep-alive"
|
|
1096
1550
|
});
|
|
1097
1551
|
headersSentEarly = true;
|
|
1098
1552
|
res.write(": heartbeat\n\n");
|
|
@@ -1104,7 +1558,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1104
1558
|
}
|
|
1105
1559
|
const headers = {};
|
|
1106
1560
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
1107
|
-
if (key === "host" || key === "connection" || key === "transfer-encoding" || key === "content-length")
|
|
1561
|
+
if (key === "host" || key === "connection" || key === "transfer-encoding" || key === "content-length")
|
|
1562
|
+
continue;
|
|
1108
1563
|
if (typeof value === "string") {
|
|
1109
1564
|
headers[key] = value;
|
|
1110
1565
|
}
|
|
@@ -1114,18 +1569,34 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1114
1569
|
}
|
|
1115
1570
|
headers["user-agent"] = USER_AGENT;
|
|
1116
1571
|
let preAuth;
|
|
1117
|
-
if (
|
|
1118
|
-
|
|
1119
|
-
if (estimated) {
|
|
1120
|
-
preAuth = { estimatedAmount: estimated };
|
|
1121
|
-
}
|
|
1572
|
+
if (estimatedCostMicros !== void 0) {
|
|
1573
|
+
preAuth = { estimatedAmount: estimatedCostMicros.toString() };
|
|
1122
1574
|
}
|
|
1575
|
+
let completed = false;
|
|
1576
|
+
res.on("close", () => {
|
|
1577
|
+
if (heartbeatInterval) {
|
|
1578
|
+
clearInterval(heartbeatInterval);
|
|
1579
|
+
heartbeatInterval = void 0;
|
|
1580
|
+
}
|
|
1581
|
+
if (!completed) {
|
|
1582
|
+
deduplicator.removeInflight(dedupKey);
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1586
|
+
const controller = new AbortController();
|
|
1587
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
1123
1588
|
try {
|
|
1124
|
-
const upstream = await payFetch(
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1589
|
+
const upstream = await payFetch(
|
|
1590
|
+
upstreamUrl,
|
|
1591
|
+
{
|
|
1592
|
+
method: req.method ?? "POST",
|
|
1593
|
+
headers,
|
|
1594
|
+
body: body.length > 0 ? body : void 0,
|
|
1595
|
+
signal: controller.signal
|
|
1596
|
+
},
|
|
1597
|
+
preAuth
|
|
1598
|
+
);
|
|
1599
|
+
clearTimeout(timeoutId);
|
|
1129
1600
|
if (heartbeatInterval) {
|
|
1130
1601
|
clearInterval(heartbeatInterval);
|
|
1131
1602
|
heartbeatInterval = void 0;
|
|
@@ -1198,11 +1669,21 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1198
1669
|
completedAt: Date.now()
|
|
1199
1670
|
});
|
|
1200
1671
|
}
|
|
1672
|
+
if (estimatedCostMicros !== void 0) {
|
|
1673
|
+
balanceMonitor.deductEstimated(estimatedCostMicros);
|
|
1674
|
+
}
|
|
1675
|
+
completed = true;
|
|
1201
1676
|
} catch (err) {
|
|
1677
|
+
clearTimeout(timeoutId);
|
|
1202
1678
|
if (heartbeatInterval) {
|
|
1203
1679
|
clearInterval(heartbeatInterval);
|
|
1680
|
+
heartbeatInterval = void 0;
|
|
1204
1681
|
}
|
|
1205
1682
|
deduplicator.removeInflight(dedupKey);
|
|
1683
|
+
balanceMonitor.invalidate();
|
|
1684
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1685
|
+
throw new Error(`Request timed out after ${timeoutMs}ms`);
|
|
1686
|
+
}
|
|
1206
1687
|
throw err;
|
|
1207
1688
|
}
|
|
1208
1689
|
if (routingDecision) {
|
|
@@ -1217,6 +1698,62 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1217
1698
|
}
|
|
1218
1699
|
}
|
|
1219
1700
|
|
|
1701
|
+
// src/retry.ts
|
|
1702
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
1703
|
+
maxRetries: 2,
|
|
1704
|
+
baseDelayMs: 500,
|
|
1705
|
+
retryableCodes: [429, 502, 503, 504]
|
|
1706
|
+
};
|
|
1707
|
+
function sleep(ms) {
|
|
1708
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1709
|
+
}
|
|
1710
|
+
async function fetchWithRetry(fetchFn, url, init, config) {
|
|
1711
|
+
const cfg = {
|
|
1712
|
+
...DEFAULT_RETRY_CONFIG,
|
|
1713
|
+
...config
|
|
1714
|
+
};
|
|
1715
|
+
let lastError;
|
|
1716
|
+
let lastResponse;
|
|
1717
|
+
for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
|
|
1718
|
+
try {
|
|
1719
|
+
const response = await fetchFn(url, init);
|
|
1720
|
+
if (!cfg.retryableCodes.includes(response.status)) {
|
|
1721
|
+
return response;
|
|
1722
|
+
}
|
|
1723
|
+
lastResponse = response;
|
|
1724
|
+
const retryAfter = response.headers.get("retry-after");
|
|
1725
|
+
let delay;
|
|
1726
|
+
if (retryAfter) {
|
|
1727
|
+
const seconds = parseInt(retryAfter, 10);
|
|
1728
|
+
delay = isNaN(seconds) ? cfg.baseDelayMs * Math.pow(2, attempt) : seconds * 1e3;
|
|
1729
|
+
} else {
|
|
1730
|
+
delay = cfg.baseDelayMs * Math.pow(2, attempt);
|
|
1731
|
+
}
|
|
1732
|
+
if (attempt < cfg.maxRetries) {
|
|
1733
|
+
await sleep(delay);
|
|
1734
|
+
}
|
|
1735
|
+
} catch (err) {
|
|
1736
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1737
|
+
if (attempt < cfg.maxRetries) {
|
|
1738
|
+
const delay = cfg.baseDelayMs * Math.pow(2, attempt);
|
|
1739
|
+
await sleep(delay);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (lastResponse) {
|
|
1744
|
+
return lastResponse;
|
|
1745
|
+
}
|
|
1746
|
+
throw lastError ?? new Error("Max retries exceeded");
|
|
1747
|
+
}
|
|
1748
|
+
function isRetryable(errorOrResponse, config) {
|
|
1749
|
+
const retryableCodes = config?.retryableCodes ?? DEFAULT_RETRY_CONFIG.retryableCodes;
|
|
1750
|
+
if (errorOrResponse instanceof Response) {
|
|
1751
|
+
return retryableCodes.includes(errorOrResponse.status);
|
|
1752
|
+
}
|
|
1753
|
+
const message = errorOrResponse.message.toLowerCase();
|
|
1754
|
+
return message.includes("network") || message.includes("timeout") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("socket hang up");
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1220
1757
|
// src/index.ts
|
|
1221
1758
|
async function startProxyInBackground(api) {
|
|
1222
1759
|
const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
|
|
@@ -1228,6 +1765,23 @@ async function startProxyInBackground(api) {
|
|
|
1228
1765
|
} else {
|
|
1229
1766
|
api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);
|
|
1230
1767
|
}
|
|
1768
|
+
const startupMonitor = new BalanceMonitor(address);
|
|
1769
|
+
try {
|
|
1770
|
+
const startupBalance = await startupMonitor.checkBalance();
|
|
1771
|
+
if (startupBalance.isEmpty) {
|
|
1772
|
+
api.logger.warn(`[!] No USDC balance. Fund wallet to use ClawRouter: ${address}`);
|
|
1773
|
+
} else if (startupBalance.isLow) {
|
|
1774
|
+
api.logger.warn(
|
|
1775
|
+
`[!] Low balance: ${startupBalance.balanceUSD} remaining. Fund wallet: ${address}`
|
|
1776
|
+
);
|
|
1777
|
+
} else {
|
|
1778
|
+
api.logger.info(`Wallet balance: ${startupBalance.balanceUSD}`);
|
|
1779
|
+
}
|
|
1780
|
+
} catch (err) {
|
|
1781
|
+
api.logger.warn(
|
|
1782
|
+
`Could not check wallet balance: ${err instanceof Error ? err.message : String(err)}`
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1231
1785
|
const routingConfig = api.pluginConfig?.routing;
|
|
1232
1786
|
const proxy = await startProxy({
|
|
1233
1787
|
walletKey,
|
|
@@ -1242,6 +1796,14 @@ async function startProxyInBackground(api) {
|
|
|
1242
1796
|
const cost = decision.costEstimate.toFixed(4);
|
|
1243
1797
|
const saved = (decision.savings * 100).toFixed(0);
|
|
1244
1798
|
api.logger.info(`${decision.model} $${cost} (saved ${saved}%)`);
|
|
1799
|
+
},
|
|
1800
|
+
onLowBalance: (info) => {
|
|
1801
|
+
api.logger.warn(`[!] Low balance: ${info.balanceUSD}. Fund wallet: ${info.walletAddress}`);
|
|
1802
|
+
},
|
|
1803
|
+
onInsufficientFunds: (info) => {
|
|
1804
|
+
api.logger.error(
|
|
1805
|
+
`[!] Insufficient funds. Balance: ${info.balanceUSD}, Needed: ${info.requiredUSD}. Fund wallet: ${info.walletAddress}`
|
|
1806
|
+
);
|
|
1245
1807
|
}
|
|
1246
1808
|
});
|
|
1247
1809
|
setActiveProxy(proxy);
|
|
@@ -1264,15 +1826,27 @@ var plugin = {
|
|
|
1264
1826
|
};
|
|
1265
1827
|
var index_default = plugin;
|
|
1266
1828
|
export {
|
|
1829
|
+
BALANCE_THRESHOLDS,
|
|
1267
1830
|
BLOCKRUN_MODELS,
|
|
1831
|
+
BalanceMonitor,
|
|
1832
|
+
DEFAULT_RETRY_CONFIG,
|
|
1268
1833
|
DEFAULT_ROUTING_CONFIG,
|
|
1834
|
+
EmptyWalletError,
|
|
1835
|
+
InsufficientFundsError,
|
|
1269
1836
|
OPENCLAW_MODELS,
|
|
1270
1837
|
PaymentCache,
|
|
1271
1838
|
RequestDeduplicator,
|
|
1839
|
+
RpcError,
|
|
1272
1840
|
blockrunProvider,
|
|
1273
1841
|
buildProviderModels,
|
|
1274
1842
|
createPaymentFetch,
|
|
1275
1843
|
index_default as default,
|
|
1844
|
+
fetchWithRetry,
|
|
1845
|
+
isBalanceError,
|
|
1846
|
+
isEmptyWalletError,
|
|
1847
|
+
isInsufficientFundsError,
|
|
1848
|
+
isRetryable,
|
|
1849
|
+
isRpcError,
|
|
1276
1850
|
logUsage,
|
|
1277
1851
|
route,
|
|
1278
1852
|
startProxy
|