@blockrun/clawrouter 0.6.9 → 0.8.0
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.d.ts +1 -0
- package/dist/cli.js +3416 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +233 -131
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -2324,6 +2324,8 @@ var DEFAULT_PORT = 8402;
|
|
|
2324
2324
|
var MAX_FALLBACK_ATTEMPTS = 3;
|
|
2325
2325
|
var HEALTH_CHECK_TIMEOUT_MS = 2e3;
|
|
2326
2326
|
var RATE_LIMIT_COOLDOWN_MS = 6e4;
|
|
2327
|
+
var PORT_RETRY_ATTEMPTS = 5;
|
|
2328
|
+
var PORT_RETRY_DELAY_MS = 1e3;
|
|
2327
2329
|
var rateLimitedModels = /* @__PURE__ */ new Map();
|
|
2328
2330
|
function isRateLimited(modelId) {
|
|
2329
2331
|
const hitTime = rateLimitedModels.get(modelId);
|
|
@@ -2613,7 +2615,7 @@ async function startProxy(options) {
|
|
|
2613
2615
|
if (existingWallet) {
|
|
2614
2616
|
const account2 = privateKeyToAccount2(options.walletKey);
|
|
2615
2617
|
const balanceMonitor2 = new BalanceMonitor(account2.address);
|
|
2616
|
-
const
|
|
2618
|
+
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
2617
2619
|
if (existingWallet !== account2.address) {
|
|
2618
2620
|
console.warn(
|
|
2619
2621
|
`[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingWallet}, but current config uses ${account2.address}. Reusing existing proxy.`
|
|
@@ -2622,7 +2624,7 @@ async function startProxy(options) {
|
|
|
2622
2624
|
options.onReady?.(listenPort);
|
|
2623
2625
|
return {
|
|
2624
2626
|
port: listenPort,
|
|
2625
|
-
baseUrl,
|
|
2627
|
+
baseUrl: baseUrl2,
|
|
2626
2628
|
walletAddress: existingWallet,
|
|
2627
2629
|
balanceMonitor: balanceMonitor2,
|
|
2628
2630
|
close: async () => {
|
|
@@ -2748,80 +2750,123 @@ async function startProxy(options) {
|
|
|
2748
2750
|
}
|
|
2749
2751
|
}
|
|
2750
2752
|
});
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2753
|
+
const tryListen = (attempt) => {
|
|
2754
|
+
return new Promise((resolveAttempt, rejectAttempt) => {
|
|
2755
|
+
const onError = async (err) => {
|
|
2756
|
+
server.removeListener("error", onError);
|
|
2757
|
+
if (err.code === "EADDRINUSE") {
|
|
2758
|
+
const existingWallet2 = await checkExistingProxy(listenPort);
|
|
2759
|
+
if (existingWallet2) {
|
|
2760
|
+
console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
|
|
2761
|
+
rejectAttempt({ code: "REUSE_EXISTING", wallet: existingWallet2 });
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
if (attempt < PORT_RETRY_ATTEMPTS) {
|
|
2765
|
+
console.log(
|
|
2766
|
+
`[ClawRouter] Port ${listenPort} in TIME_WAIT, retrying in ${PORT_RETRY_DELAY_MS}ms (attempt ${attempt}/${PORT_RETRY_ATTEMPTS})`
|
|
2767
|
+
);
|
|
2768
|
+
rejectAttempt({ code: "RETRY", attempt });
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
console.error(
|
|
2772
|
+
`[ClawRouter] Port ${listenPort} still in use after ${PORT_RETRY_ATTEMPTS} attempts`
|
|
2773
|
+
);
|
|
2774
|
+
rejectAttempt(err);
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
rejectAttempt(err);
|
|
2778
|
+
};
|
|
2779
|
+
server.once("error", onError);
|
|
2780
|
+
server.listen(listenPort, "127.0.0.1", () => {
|
|
2781
|
+
server.removeListener("error", onError);
|
|
2782
|
+
resolveAttempt();
|
|
2783
|
+
});
|
|
2784
|
+
});
|
|
2785
|
+
};
|
|
2786
|
+
let lastError;
|
|
2787
|
+
for (let attempt = 1; attempt <= PORT_RETRY_ATTEMPTS; attempt++) {
|
|
2788
|
+
try {
|
|
2789
|
+
await tryListen(attempt);
|
|
2790
|
+
break;
|
|
2791
|
+
} catch (err) {
|
|
2792
|
+
const error = err;
|
|
2793
|
+
if (error.code === "REUSE_EXISTING" && error.wallet) {
|
|
2794
|
+
const baseUrl2 = `http://127.0.0.1:${listenPort}`;
|
|
2755
2795
|
options.onReady?.(listenPort);
|
|
2756
|
-
|
|
2796
|
+
return {
|
|
2757
2797
|
port: listenPort,
|
|
2758
|
-
baseUrl,
|
|
2759
|
-
walletAddress:
|
|
2798
|
+
baseUrl: baseUrl2,
|
|
2799
|
+
walletAddress: error.wallet,
|
|
2760
2800
|
balanceMonitor,
|
|
2761
2801
|
close: async () => {
|
|
2762
2802
|
}
|
|
2763
|
-
}
|
|
2764
|
-
return;
|
|
2803
|
+
};
|
|
2765
2804
|
}
|
|
2766
|
-
|
|
2805
|
+
if (error.code === "RETRY") {
|
|
2806
|
+
await new Promise((r) => setTimeout(r, PORT_RETRY_DELAY_MS));
|
|
2807
|
+
continue;
|
|
2808
|
+
}
|
|
2809
|
+
lastError = err;
|
|
2810
|
+
break;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
if (lastError) {
|
|
2814
|
+
throw lastError;
|
|
2815
|
+
}
|
|
2816
|
+
const addr = server.address();
|
|
2817
|
+
const port = addr.port;
|
|
2818
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
2819
|
+
options.onReady?.(port);
|
|
2820
|
+
server.on("error", (err) => {
|
|
2821
|
+
console.error(`[ClawRouter] Server runtime error: ${err.message}`);
|
|
2822
|
+
options.onError?.(err);
|
|
2823
|
+
});
|
|
2824
|
+
server.on("clientError", (err, socket) => {
|
|
2825
|
+
console.error(`[ClawRouter] Client error: ${err.message}`);
|
|
2826
|
+
if (socket.writable && !socket.destroyed) {
|
|
2827
|
+
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
server.on("connection", (socket) => {
|
|
2831
|
+
connections.add(socket);
|
|
2832
|
+
socket.setTimeout(3e5);
|
|
2833
|
+
socket.on("timeout", () => {
|
|
2834
|
+
console.error(`[ClawRouter] Socket timeout, destroying connection`);
|
|
2835
|
+
socket.destroy();
|
|
2767
2836
|
});
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
options.onError?.(err);
|
|
2776
|
-
});
|
|
2777
|
-
server.on("clientError", (err, socket) => {
|
|
2778
|
-
console.error(`[ClawRouter] Client error: ${err.message}`);
|
|
2779
|
-
if (socket.writable && !socket.destroyed) {
|
|
2780
|
-
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
2781
|
-
}
|
|
2782
|
-
});
|
|
2783
|
-
server.on("connection", (socket) => {
|
|
2784
|
-
connections.add(socket);
|
|
2785
|
-
socket.setTimeout(3e5);
|
|
2786
|
-
socket.on("timeout", () => {
|
|
2787
|
-
console.error(`[ClawRouter] Socket timeout, destroying connection`);
|
|
2788
|
-
socket.destroy();
|
|
2789
|
-
});
|
|
2790
|
-
socket.on("end", () => {
|
|
2791
|
-
});
|
|
2792
|
-
socket.on("error", (err) => {
|
|
2793
|
-
console.error(`[ClawRouter] Socket error: ${err.message}`);
|
|
2794
|
-
});
|
|
2795
|
-
socket.on("close", () => {
|
|
2796
|
-
connections.delete(socket);
|
|
2797
|
-
});
|
|
2798
|
-
});
|
|
2799
|
-
resolve({
|
|
2800
|
-
port,
|
|
2801
|
-
baseUrl,
|
|
2802
|
-
walletAddress: account.address,
|
|
2803
|
-
balanceMonitor,
|
|
2804
|
-
close: () => new Promise((res, rej) => {
|
|
2805
|
-
const timeout = setTimeout(() => {
|
|
2806
|
-
rej(new Error("[ClawRouter] Close timeout after 4s"));
|
|
2807
|
-
}, 4e3);
|
|
2808
|
-
sessionStore.close();
|
|
2809
|
-
for (const socket of connections) {
|
|
2810
|
-
socket.destroy();
|
|
2811
|
-
}
|
|
2812
|
-
connections.clear();
|
|
2813
|
-
server.close((err) => {
|
|
2814
|
-
clearTimeout(timeout);
|
|
2815
|
-
if (err) {
|
|
2816
|
-
rej(err);
|
|
2817
|
-
} else {
|
|
2818
|
-
res();
|
|
2819
|
-
}
|
|
2820
|
-
});
|
|
2821
|
-
})
|
|
2822
|
-
});
|
|
2837
|
+
socket.on("end", () => {
|
|
2838
|
+
});
|
|
2839
|
+
socket.on("error", (err) => {
|
|
2840
|
+
console.error(`[ClawRouter] Socket error: ${err.message}`);
|
|
2841
|
+
});
|
|
2842
|
+
socket.on("close", () => {
|
|
2843
|
+
connections.delete(socket);
|
|
2823
2844
|
});
|
|
2824
2845
|
});
|
|
2846
|
+
return {
|
|
2847
|
+
port,
|
|
2848
|
+
baseUrl,
|
|
2849
|
+
walletAddress: account.address,
|
|
2850
|
+
balanceMonitor,
|
|
2851
|
+
close: () => new Promise((res, rej) => {
|
|
2852
|
+
const timeout = setTimeout(() => {
|
|
2853
|
+
rej(new Error("[ClawRouter] Close timeout after 4s"));
|
|
2854
|
+
}, 4e3);
|
|
2855
|
+
sessionStore.close();
|
|
2856
|
+
for (const socket of connections) {
|
|
2857
|
+
socket.destroy();
|
|
2858
|
+
}
|
|
2859
|
+
connections.clear();
|
|
2860
|
+
server.close((err) => {
|
|
2861
|
+
clearTimeout(timeout);
|
|
2862
|
+
if (err) {
|
|
2863
|
+
rej(err);
|
|
2864
|
+
} else {
|
|
2865
|
+
res();
|
|
2866
|
+
}
|
|
2867
|
+
});
|
|
2868
|
+
})
|
|
2869
|
+
};
|
|
2825
2870
|
}
|
|
2826
2871
|
async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxTokens, payFetch, balanceMonitor, signal) {
|
|
2827
2872
|
let requestBody = body;
|
|
@@ -3457,80 +3502,137 @@ function isCompletionMode() {
|
|
|
3457
3502
|
return args.some((arg, i) => arg === "completion" && i >= 1 && i <= 3);
|
|
3458
3503
|
}
|
|
3459
3504
|
function injectModelsConfig(logger) {
|
|
3460
|
-
const
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3505
|
+
const configDir = join5(homedir4(), ".openclaw");
|
|
3506
|
+
const configPath = join5(configDir, "openclaw.json");
|
|
3507
|
+
let config = {};
|
|
3508
|
+
let needsWrite = false;
|
|
3509
|
+
if (!existsSync(configDir)) {
|
|
3510
|
+
try {
|
|
3511
|
+
mkdirSync(configDir, { recursive: true });
|
|
3512
|
+
logger.info("Created OpenClaw config directory");
|
|
3513
|
+
} catch (err) {
|
|
3514
|
+
logger.info(`Failed to create config dir: ${err instanceof Error ? err.message : String(err)}`);
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3464
3517
|
}
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
if (!config.models.providers.blockrun) {
|
|
3473
|
-
config.models.providers.blockrun = {
|
|
3474
|
-
baseUrl: expectedBaseUrl,
|
|
3475
|
-
api: "openai-completions",
|
|
3476
|
-
// apiKey is required by pi-coding-agent's ModelRegistry for providers with models.
|
|
3477
|
-
// We use a placeholder since the proxy handles real x402 auth internally.
|
|
3478
|
-
apiKey: "x402-proxy-handles-auth",
|
|
3479
|
-
models: OPENCLAW_MODELS
|
|
3480
|
-
};
|
|
3481
|
-
needsWrite = true;
|
|
3482
|
-
} else {
|
|
3483
|
-
if (config.models.providers.blockrun.baseUrl !== expectedBaseUrl) {
|
|
3484
|
-
config.models.providers.blockrun.baseUrl = expectedBaseUrl;
|
|
3485
|
-
needsWrite = true;
|
|
3486
|
-
}
|
|
3487
|
-
if (!config.models.providers.blockrun.apiKey) {
|
|
3488
|
-
config.models.providers.blockrun.apiKey = "x402-proxy-handles-auth";
|
|
3489
|
-
needsWrite = true;
|
|
3490
|
-
}
|
|
3491
|
-
const currentModels = config.models.providers.blockrun.models;
|
|
3492
|
-
if (!currentModels || currentModels.length !== OPENCLAW_MODELS.length) {
|
|
3493
|
-
config.models.providers.blockrun.models = OPENCLAW_MODELS;
|
|
3518
|
+
if (existsSync(configPath)) {
|
|
3519
|
+
try {
|
|
3520
|
+
const content = readFileSync(configPath, "utf-8").trim();
|
|
3521
|
+
if (content) {
|
|
3522
|
+
config = JSON.parse(content);
|
|
3523
|
+
} else {
|
|
3524
|
+
logger.info("OpenClaw config is empty, initializing");
|
|
3494
3525
|
needsWrite = true;
|
|
3495
3526
|
}
|
|
3527
|
+
} catch (err) {
|
|
3528
|
+
logger.info(`Failed to parse config (will recreate): ${err instanceof Error ? err.message : String(err)}`);
|
|
3529
|
+
config = {};
|
|
3530
|
+
needsWrite = true;
|
|
3496
3531
|
}
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3532
|
+
} else {
|
|
3533
|
+
logger.info("OpenClaw config not found, creating");
|
|
3534
|
+
needsWrite = true;
|
|
3535
|
+
}
|
|
3536
|
+
if (!config.models) {
|
|
3537
|
+
config.models = {};
|
|
3538
|
+
needsWrite = true;
|
|
3539
|
+
}
|
|
3540
|
+
const models = config.models;
|
|
3541
|
+
if (!models.providers) {
|
|
3542
|
+
models.providers = {};
|
|
3543
|
+
needsWrite = true;
|
|
3544
|
+
}
|
|
3545
|
+
const proxyPort = getProxyPort();
|
|
3546
|
+
const expectedBaseUrl = `http://127.0.0.1:${proxyPort}/v1`;
|
|
3547
|
+
const providers = models.providers;
|
|
3548
|
+
if (!providers.blockrun) {
|
|
3549
|
+
providers.blockrun = {
|
|
3550
|
+
baseUrl: expectedBaseUrl,
|
|
3551
|
+
api: "openai-completions",
|
|
3552
|
+
// apiKey is required by pi-coding-agent's ModelRegistry for providers with models.
|
|
3553
|
+
// We use a placeholder since the proxy handles real x402 auth internally.
|
|
3554
|
+
apiKey: "x402-proxy-handles-auth",
|
|
3555
|
+
models: OPENCLAW_MODELS
|
|
3556
|
+
};
|
|
3557
|
+
logger.info("Injected BlockRun provider config");
|
|
3558
|
+
needsWrite = true;
|
|
3559
|
+
} else {
|
|
3560
|
+
const blockrun = providers.blockrun;
|
|
3561
|
+
let fixed = false;
|
|
3562
|
+
if (!blockrun.baseUrl || blockrun.baseUrl !== expectedBaseUrl) {
|
|
3563
|
+
blockrun.baseUrl = expectedBaseUrl;
|
|
3564
|
+
fixed = true;
|
|
3565
|
+
}
|
|
3566
|
+
if (!blockrun.api) {
|
|
3567
|
+
blockrun.api = "openai-completions";
|
|
3568
|
+
fixed = true;
|
|
3569
|
+
}
|
|
3570
|
+
if (!blockrun.apiKey) {
|
|
3571
|
+
blockrun.apiKey = "x402-proxy-handles-auth";
|
|
3572
|
+
fixed = true;
|
|
3573
|
+
}
|
|
3574
|
+
const currentModels = blockrun.models;
|
|
3575
|
+
if (!currentModels || !Array.isArray(currentModels) || currentModels.length !== OPENCLAW_MODELS.length) {
|
|
3576
|
+
blockrun.models = OPENCLAW_MODELS;
|
|
3577
|
+
fixed = true;
|
|
3578
|
+
}
|
|
3579
|
+
if (fixed) {
|
|
3580
|
+
logger.info("Fixed incomplete BlockRun provider config");
|
|
3502
3581
|
needsWrite = true;
|
|
3503
3582
|
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3583
|
+
}
|
|
3584
|
+
if (!config.agents) {
|
|
3585
|
+
config.agents = {};
|
|
3586
|
+
needsWrite = true;
|
|
3587
|
+
}
|
|
3588
|
+
const agents = config.agents;
|
|
3589
|
+
if (!agents.defaults) {
|
|
3590
|
+
agents.defaults = {};
|
|
3591
|
+
needsWrite = true;
|
|
3592
|
+
}
|
|
3593
|
+
const defaults = agents.defaults;
|
|
3594
|
+
if (!defaults.model) {
|
|
3595
|
+
defaults.model = {};
|
|
3596
|
+
needsWrite = true;
|
|
3597
|
+
}
|
|
3598
|
+
const model = defaults.model;
|
|
3599
|
+
if (model.primary !== "blockrun/auto") {
|
|
3600
|
+
model.primary = "blockrun/auto";
|
|
3601
|
+
needsWrite = true;
|
|
3602
|
+
}
|
|
3603
|
+
const KEY_MODEL_ALIASES = [
|
|
3604
|
+
{ id: "auto", alias: "auto" },
|
|
3605
|
+
{ id: "free", alias: "free" },
|
|
3606
|
+
{ id: "sonnet", alias: "sonnet" },
|
|
3607
|
+
{ id: "opus", alias: "opus" },
|
|
3608
|
+
{ id: "haiku", alias: "haiku" },
|
|
3609
|
+
{ id: "grok", alias: "grok" },
|
|
3610
|
+
{ id: "deepseek", alias: "deepseek" },
|
|
3611
|
+
{ id: "kimi", alias: "kimi" },
|
|
3612
|
+
{ id: "gemini", alias: "gemini" },
|
|
3613
|
+
{ id: "flash", alias: "flash" },
|
|
3614
|
+
{ id: "gpt", alias: "gpt" },
|
|
3615
|
+
{ id: "reasoner", alias: "reasoner" }
|
|
3616
|
+
];
|
|
3617
|
+
if (!defaults.models) {
|
|
3618
|
+
defaults.models = {};
|
|
3619
|
+
needsWrite = true;
|
|
3620
|
+
}
|
|
3621
|
+
const allowlist = defaults.models;
|
|
3622
|
+
for (const m of KEY_MODEL_ALIASES) {
|
|
3623
|
+
const fullId = `blockrun/${m.id}`;
|
|
3624
|
+
if (!allowlist[fullId]) {
|
|
3625
|
+
allowlist[fullId] = { alias: m.alias };
|
|
3626
|
+
needsWrite = true;
|
|
3528
3627
|
}
|
|
3529
|
-
|
|
3628
|
+
}
|
|
3629
|
+
if (needsWrite) {
|
|
3630
|
+
try {
|
|
3530
3631
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3531
3632
|
logger.info("Smart routing enabled (blockrun/auto)");
|
|
3633
|
+
} catch (err) {
|
|
3634
|
+
logger.info(`Failed to write config: ${err instanceof Error ? err.message : String(err)}`);
|
|
3532
3635
|
}
|
|
3533
|
-
} catch {
|
|
3534
3636
|
}
|
|
3535
3637
|
}
|
|
3536
3638
|
function injectAuthProfile(logger) {
|