diff --git a/README.md b/README.md
index 505e096..0d723bc 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ I am seeking funding support for this project to better sustain its development.

+
+
## ✨ Features
- **Model Routing**: Route requests to different models based on your needs (e.g., background tasks, thinking, long context).
@@ -572,6 +574,8 @@ A huge thank you to all our sponsors for their generous support!
- @\*更
- @\*.
- @F\*t
-
+- @\*政
+- @\*铭
+- @\*叶
(If your name is masked, please contact me via my homepage email to update it with your GitHub username.)
diff --git a/README_zh.md b/README_zh.md
index 3c31fba..43f37cf 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -10,6 +10,9 @@

+
+
+
## ✨ 功能
- **模型路由**: 根据您的需求将请求路由到不同的模型(例如,后台任务、思考、长上下文)。
@@ -541,6 +544,9 @@ jobs:
- @\*更
- @\*.
- @F\*t
+- @\*政
+- @\*铭
+- @\*叶
(如果您的名字被屏蔽,请通过我的主页电子邮件与我联系,以便使用您的 GitHub 用户名进行更新。)
diff --git a/blog/images/roadmap.svg b/blog/images/roadmap.svg
new file mode 100644
index 0000000..0403023
--- /dev/null
+++ b/blog/images/roadmap.svg
@@ -0,0 +1,67 @@
+
diff --git a/package.json b/package.json
index 653b565..ca2a631 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@musistudio/claude-code-router",
- "version": "1.0.47",
+ "version": "1.0.48",
"description": "Use Claude Code without an Anthropics account and route it to another LLM provider",
"bin": {
"ccr": "./dist/cli.js"
@@ -22,6 +22,7 @@
"@fastify/static": "^8.2.0",
"@musistudio/llms": "^1.0.32",
"dotenv": "^16.4.7",
+ "find-process": "^2.0.0",
"json5": "^2.2.3",
"openurl": "^1.1.1",
"rotating-file-stream": "^3.2.7",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 25d739b..1792678 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,9 @@ importers:
dotenv:
specifier: ^16.4.7
version: 16.6.1
+ find-process:
+ specifier: ^2.0.0
+ version: 2.0.0
json5:
specifier: ^2.2.3
version: 2.2.3
@@ -338,6 +341,10 @@ packages:
buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -345,6 +352,10 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ commander@12.1.0:
+ resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
+ engines: {node: '>=18'}
+
content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
@@ -460,6 +471,10 @@ packages:
resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==}
engines: {node: '>=20'}
+ find-process@2.0.0:
+ resolution: {integrity: sha512-YUBQnteWGASJoEVVsOXy6XtKAY2O1FCsWnnvQ8y0YwgY1rZiKeVptnFvMu6RSELZAJOGklqseTnUGGs5D0bKmg==}
+ hasBin: true
+
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@@ -524,6 +539,10 @@ packages:
resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==}
engines: {node: '>=18'}
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
@@ -609,6 +628,10 @@ packages:
light-my-request@6.6.0:
resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==}
+ loglevel@1.9.2:
+ resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==}
+ engines: {node: '>= 0.6.0'}
+
lru-cache@11.1.0:
resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==}
engines: {node: 20 || >=22}
@@ -865,6 +888,10 @@ packages:
resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==}
engines: {node: '>=0.10.0'}
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
@@ -1188,12 +1215,19 @@ snapshots:
buffer-equal-constant-time@1.0.1: {}
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
+ commander@12.1.0: {}
+
content-disposition@0.5.4:
dependencies:
safe-buffer: 5.2.1
@@ -1351,6 +1385,12 @@ snapshots:
fast-querystring: 1.1.2
safe-regex2: 5.0.0
+ find-process@2.0.0:
+ dependencies:
+ chalk: 4.1.2
+ commander: 12.1.0
+ loglevel: 1.9.2
+
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@@ -1458,6 +1498,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ has-flag@4.0.0: {}
+
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
@@ -1538,6 +1580,8 @@ snapshots:
process-warning: 4.0.1
set-cookie-parser: 2.7.1
+ loglevel@1.9.2: {}
+
lru-cache@11.1.0: {}
merge2@1.4.1: {}
@@ -1741,6 +1785,10 @@ snapshots:
strip-eof@1.0.0: {}
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
supports-preserve-symlinks-flag@1.0.0: {}
thread-stream@3.1.0:
diff --git a/src/cli.ts b/src/cli.ts
index b8c0f9c..7e9692f 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -45,7 +45,8 @@ async function waitForService(
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
- if (isServiceRunning()) {
+ const isRunning = await isServiceRunning()
+ if (isRunning) {
// Wait for an additional short period to ensure service is fully ready
await new Promise((resolve) => setTimeout(resolve, 500));
return true;
@@ -56,6 +57,7 @@ async function waitForService(
}
async function main() {
+ const isRunning = await isServiceRunning()
switch (command) {
case "start":
run();
@@ -95,7 +97,7 @@ async function main() {
inputData += chunk;
}
});
-
+
process.stdin.on("end", async () => {
try {
const input: StatusLineInput = JSON.parse(inputData);
@@ -108,7 +110,7 @@ async function main() {
});
break;
case "code":
- if (!isServiceRunning()) {
+ if (!isRunning) {
console.log("Service not running, starting service...");
const cliPath = join(__dirname, "cli.js");
const startProcess = spawn("node", [cliPath, "start"], {
@@ -153,7 +155,7 @@ async function main() {
break;
case "ui":
// Check if service is running
- if (!isServiceRunning()) {
+ if (!isRunning) {
console.log("Service not running, starting service...");
const cliPath = join(__dirname, "cli.js");
const startProcess = spawn("node", [cliPath, "start"], {
diff --git a/src/index.ts b/src/index.ts
index e2adb51..c10534a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -51,7 +51,8 @@ interface RunOptions {
async function run(options: RunOptions = {}) {
// Check if service is already running
- if (isServiceRunning()) {
+ const isRunning = await isServiceRunning()
+ if (isRunning) {
console.log("✅ Service is already running in the background.");
return;
}
@@ -244,7 +245,6 @@ async function run(options: RunOptions = {}) {
req,
config
});
- console.log('result', toolResult)
toolMessages.push({
"tool_use_id": currentToolId,
"type": "tool_result",
@@ -295,14 +295,12 @@ async function run(options: RunOptions = {}) {
// 检查流是否仍然可写
if (!controller.desiredSize) {
- console.log('Stream backpressure detected');
break;
}
controller.enqueue(value)
}catch (readError: any) {
if (readError.name === 'AbortError' || readError.code === 'ERR_STREAM_PREMATURE_CLOSE') {
- console.log('Stream reading aborted due to client disconnect');
abortController.abort(); // 中止所有相关操作
break;
}
@@ -318,7 +316,6 @@ async function run(options: RunOptions = {}) {
// 处理流提前关闭的错误
if (error.code === 'ERR_STREAM_PREMATURE_CLOSE') {
- console.log('Stream prematurely closed, aborting operations');
abortController.abort();
return undefined;
}
@@ -349,7 +346,7 @@ async function run(options: RunOptions = {}) {
}
} catch (readError: any) {
if (readError.name === 'AbortError' || readError.code === 'ERR_STREAM_PREMATURE_CLOSE') {
- console.log('Background read stream closed prematurely');
+ console.error('Background read stream closed prematurely');
} else {
console.error('Error in background stream reading:', readError);
}
@@ -362,13 +359,15 @@ async function run(options: RunOptions = {}) {
}
sessionUsageCache.put(req.sessionId, payload.usage);
}
- if (typeof payload ==='object' && payload.error) {
- return done(payload.error, null)
+ if (typeof payload ==='object') {
+ if (payload.error) {
+ return done(payload.error, null)
+ }
+ return done(payload)
}
done(null, payload)
});
server.addHook("onSend", async (req, reply, payload) => {
- console.log('主应用onSend')
event.emit('onSend', req, reply, payload);
return payload;
})
diff --git a/src/utils/close.ts b/src/utils/close.ts
index 6c1f973..27de6ab 100644
--- a/src/utils/close.ts
+++ b/src/utils/close.ts
@@ -5,8 +5,9 @@ import { join } from 'path';
export async function closeService() {
const PID_FILE = join(HOME_DIR, '.claude-code-router.pid');
-
- if (!isServiceRunning()) {
+ const isRunning = await isServiceRunning()
+
+ if (!isRunning) {
console.log("No service is currently running.");
return;
}
diff --git a/src/utils/processCheck.ts b/src/utils/processCheck.ts
index 16d864f..1c9eed3 100644
--- a/src/utils/processCheck.ts
+++ b/src/utils/processCheck.ts
@@ -1,6 +1,16 @@
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { PID_FILE, REFERENCE_COUNT_FILE } from '../constants';
import { readConfigFile } from '.';
+import find from 'find-process';
+
+export async function isProcessRunning(pid: number): Promise {
+ try {
+ const processes = await find('pid', pid);
+ return processes.length > 0;
+ } catch (error) {
+ return false;
+ }
+}
export function incrementReferenceCount() {
let count = 0;
@@ -27,15 +37,14 @@ export function getReferenceCount(): number {
return parseInt(readFileSync(REFERENCE_COUNT_FILE, 'utf-8')) || 0;
}
-export function isServiceRunning(): boolean {
+export async function isServiceRunning(): Promise {
if (!existsSync(PID_FILE)) {
return false;
}
try {
const pid = parseInt(readFileSync(PID_FILE, 'utf-8'));
- process.kill(pid, 0);
- return true;
+ return await isProcessRunning(pid);
} catch (e) {
// Process not running, clean up pid file
cleanupPidFile();
@@ -62,7 +71,7 @@ export function getServicePid(): number | null {
if (!existsSync(PID_FILE)) {
return null;
}
-
+
try {
const pid = parseInt(readFileSync(PID_FILE, 'utf-8'));
return isNaN(pid) ? null : pid;
@@ -73,10 +82,10 @@ export function getServicePid(): number | null {
export async function getServiceInfo() {
const pid = getServicePid();
- const running = isServiceRunning();
+ const running = await isServiceRunning();
const config = await readConfigFile();
const port = config.PORT || 3456;
-
+
return {
running,
pid,