support docker

This commit is contained in:
musistudio
2025-12-25 23:00:24 +08:00
parent 6a20b2021d
commit cd7454d7fb
25 changed files with 541 additions and 206 deletions

View File

@@ -1,8 +1,9 @@
{
"name": "@musistudio/claude-code-router-server",
"version": "1.0.73",
"name": "@CCR/server",
"version": "2.0.0",
"description": "Server for Claude Code Router",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "node ../../scripts/build-server.js",
"dev": "ts-node src/index.ts"
@@ -16,7 +17,7 @@
"author": "musistudio",
"license": "MIT",
"dependencies": {
"@musistudio/claude-code-router-shared": "workspace:*",
"@CCR/shared": "workspace:*",
"@fastify/static": "^8.2.0",
"@musistudio/llms": "^1.0.51",
"dotenv": "^16.4.7",

View File

@@ -6,16 +6,17 @@ import { initConfig, initDir } from "./utils";
import { createServer } from "./server";
import { router } from "./utils/router";
import { apiKeyAuth } from "./middleware/auth";
import { PID_FILE, CONFIG_FILE, HOME_DIR } from "@musistudio/claude-code-router-shared";
import { CONFIG_FILE, HOME_DIR } from "@CCR/shared";
import { createStream } from 'rotating-file-stream';
import { sessionUsageCache } from "./utils/cache";
import {SSEParserTransform} from "./utils/SSEParser.transform";
import {SSESerializerTransform} from "./utils/SSESerializer.transform";
import {rewriteStream} from "./utils/rewriteStream";
import JSON5 from "json5";
import { IAgent } from "./agents/type";
import { IAgent, ITool } from "./agents/type";
import agentsManager from "./agents";
import { EventEmitter } from "node:events";
import {spawn} from "child_process";
const event = new EventEmitter()
@@ -44,14 +45,7 @@ interface RunOptions {
logger?: any;
}
async function run(options: RunOptions = {}) {
// Check if service is already running
const isRunning = existsSync(PID_FILE);
if (isRunning) {
console.log("✅ Service is already running in the background.");
return;
}
async function getServer(options: RunOptions = {}) {
await initializeClaudeConfig();
await initDir();
const config = await initConfig();
@@ -63,13 +57,10 @@ async function run(options: RunOptions = {}) {
let HOST = config.HOST || "127.0.0.1";
if (hasProviders) {
// When providers are configured, require both HOST and APIKEY
if (!config.HOST || !config.APIKEY) {
console.error("❌ Both HOST and APIKEY must be configured when Providers are set.");
console.error(" Please add HOST and APIKEY to your config file.");
process.exit(1);
}
HOST = config.HOST;
if (!config.APIKEY) {
HOST = "127.0.0.1";
}
} else {
// When no providers are configured, listen on 0.0.0.0 without authentication
HOST = "0.0.0.0";
@@ -78,35 +69,6 @@ async function run(options: RunOptions = {}) {
const port = config.PORT || 3456;
// Save the PID of the background process
writeFileSync(PID_FILE, process.pid.toString());
// Handle SIGINT (Ctrl+C) to clean up PID file
process.on("SIGINT", () => {
console.log("Received SIGINT, cleaning up...");
if (existsSync(PID_FILE)) {
try {
unlinkSync(PID_FILE);
} catch (e) {
// Ignore cleanup errors
}
}
process.exit(0);
});
// Handle SIGTERM to clean up PID file
process.on("SIGTERM", () => {
if (existsSync(PID_FILE)) {
try {
const fs = require('fs');
fs.unlinkSync(PID_FILE);
} catch (e) {
// Ignore cleanup errors
}
}
process.exit(0);
});
// Use port from environment variable if set (for background process)
const servicePort = process.env.SERVICE_PORT
? parseInt(process.env.SERVICE_PORT)
@@ -159,7 +121,7 @@ async function run(options: RunOptions = {}) {
}
}
const server = createServer({
const serverInstance = createServer({
jsonPath: CONFIG_FILE,
initialConfig: {
// ...config,
@@ -175,16 +137,8 @@ async function run(options: RunOptions = {}) {
logger: loggerConfig,
});
// Add global error handlers to prevent the service from crashing
process.on("uncaughtException", (err) => {
server.logger.error("Uncaught exception:", err);
});
process.on("unhandledRejection", (reason, promise) => {
server.logger.error("Unhandled rejection at:", promise, "reason:", reason);
});
// Add async preHandler hook for authentication
server.addHook("preHandler", async (req: any, reply: any) => {
serverInstance.addHook("preHandler", async (req: any, reply: any) => {
return new Promise<void>((resolve, reject) => {
const done = (err?: Error) => {
if (err) reject(err);
@@ -194,7 +148,7 @@ async function run(options: RunOptions = {}) {
apiKeyAuth(config)(req, reply, done).catch(reject);
});
});
server.addHook("preHandler", async (req: any, reply: any) => {
serverInstance.addHook("preHandler", async (req: any, reply: any) => {
if (req.url.startsWith("/v1/messages") && !req.url.startsWith("/v1/messages/count_tokens")) {
const useAgents = []
@@ -231,10 +185,10 @@ async function run(options: RunOptions = {}) {
});
}
});
server.addHook("onError", async (request: any, reply: any, error: any) => {
serverInstance.addHook("onError", async (request: any, reply: any, error: any) => {
event.emit('onError', request, reply, error);
})
server.addHook("onSend", (req: any, reply: any, payload: any, done: any) => {
serverInstance.addHook("onSend", (req: any, reply: any, payload: any, done: any) => {
if (req.sessionId && req.url.startsWith("/v1/messages") && !req.url.startsWith("/v1/messages/count_tokens")) {
if (payload instanceof ReadableStream) {
if (req.agents) {
@@ -409,16 +363,39 @@ async function run(options: RunOptions = {}) {
}
done(null, payload)
});
server.addHook("onSend", async (req: any, reply: any, payload: any) => {
serverInstance.addHook("onSend", async (req: any, reply: any, payload: any) => {
event.emit('onSend', req, reply, payload);
return payload;
})
});
// Add global error handlers to prevent the service from crashing
process.on("uncaughtException", (err) => {
serverInstance.logger.error("Uncaught exception:", err);
});
server.start();
process.on("unhandledRejection", (reason, promise) => {
serverInstance.logger.error("Unhandled rejection at:", promise, "reason:", reason);
});
return serverInstance;
}
export { run };
async function run() {
const server = await getServer();
server.app.post("/api/restart", async () => {
setTimeout(async () => {
process.exit(0);
}, 100);
return { success: true, message: "Service restart initiated" }
});
await server.start();
}
export { getServer };
export type { RunOptions };
export type { IAgent, ITool } from "./agents/type";
export { initDir, initConfig, readConfigFile, writeConfigFile, backupConfigFile } from "./utils";
// 如果是直接运行此文件,则启动服务
if (require.main === module) {

View File

@@ -5,6 +5,7 @@ import fastifyStatic from "@fastify/static";
import { readdirSync, statSync, readFileSync, writeFileSync, existsSync } from "fs";
import { homedir } from "os";
import {calculateTokenCount} from "./utils/router";
import { fork, spawn } from "child_process";
export const createServer = (config: any): any => {
const server = new Server(config);
@@ -46,20 +47,6 @@ export const createServer = (config: any): any => {
return { success: true, message: "Config saved successfully" };
});
// Add endpoint to restart the service with access control
server.app.post("/api/restart", async (req: any, reply: any) => {
reply.send({ success: true, message: "Service restart initiated" });
// Restart the service after a short delay to allow response to be sent
setTimeout(() => {
const { spawn } = require("child_process");
spawn(process.execPath, [process.argv[1], "restart"], {
detached: true,
stdio: "ignore",
});
}, 1000);
});
// Register static file serving with caching
server.app.register(fastifyStatic, {
root: join(__dirname, "..", "dist"),

View File

@@ -7,7 +7,7 @@ import {
DEFAULT_CONFIG,
HOME_DIR,
PLUGINS_DIR,
} from "@musistudio/claude-code-router-shared";
} from "@CCR/shared";
// Function to interpolate environment variables in config values
const interpolateEnvVars = (obj: any): any => {

View File

@@ -3,7 +3,7 @@ import { sessionUsageCache, Usage } from "./cache";
import { readFile, access } from "fs/promises";
import { opendir, stat } from "fs/promises";
import { join } from "path";
import { CLAUDE_PROJECTS_DIR, HOME_DIR } from "@musistudio/claude-code-router-shared";
import { CLAUDE_PROJECTS_DIR, HOME_DIR } from "@CCR/shared";
import { LRUCache } from "lru-cache";
// Types from @anthropic-ai/sdk