Merge branch 'main' into bugfix/fix-code-args-handling

This commit is contained in:
musi
2025-08-06 20:53:24 +08:00
committed by GitHub
7 changed files with 41 additions and 2065 deletions

View File

@@ -41,6 +41,7 @@ The `config.json` file has several key sections:
- **`LOG`** (optional): You can enable logging by setting it to `true`. The log file will be located at `$HOME/.claude-code-router.log`.
- **`APIKEY`** (optional): You can set a secret key to authenticate requests. When set, clients must provide this key in the `Authorization` header (e.g., `Bearer your-secret-key`) or the `x-api-key` header. Example: `"APIKEY": "your-secret-key"`.
- **`HOST`** (optional): You can set the host address for the server. If `APIKEY` is not set, the host will be forced to `127.0.0.1` for security reasons to prevent unauthorized access. Example: `"HOST": "0.0.0.0"`.
- **`NON_INTERACTIVE_MODE`** (optional): When set to `true`, enables compatibility with non-interactive environments like GitHub Actions, Docker containers, or other CI/CD systems. This sets appropriate environment variables (`CI=true`, `FORCE_COLOR=0`, etc.) and configures stdin handling to prevent the process from hanging in automated environments. Example: `"NON_INTERACTIVE_MODE": true`.
- **`Providers`**: Used to configure different model providers.
- **`Router`**: Used to set up routing rules. `default` specifies the default model, which will be used for all requests if no other route is configured.
@@ -54,6 +55,7 @@ Here is a comprehensive example:
"PROXY_URL": "http://127.0.0.1:7890",
"LOG": true,
"API_TIMEOUT_MS": 600000,
"NON_INTERACTIVE_MODE": false,
"Providers": [
{
"name": "openrouter",
@@ -407,6 +409,7 @@ jobs:
cat << 'EOF' > $HOME/.claude-code-router/config.json
{
"log": true,
"NON_INTERACTIVE_MODE": true,
"OPENAI_API_KEY": "${{ secrets.OPENAI_API_KEY }}",
"OPENAI_BASE_URL": "https://api.deepseek.com",
"OPENAI_MODEL": "deepseek-chat"
@@ -428,6 +431,8 @@ jobs:
anthropic_api_key: "any-string-is-ok"
```
> **Note**: When running in GitHub Actions or other automation environments, make sure to set `"NON_INTERACTIVE_MODE": true` in your configuration to prevent the process from hanging due to stdin handling issues.
This setup allows for interesting automations, like running tasks during off-peak hours to reduce API costs.
## 📝 Further Reading

View File

@@ -38,6 +38,7 @@ npm install -g @musistudio/claude-code-router
- **`LOG`** (可选): 您可以通过将其设置为 `true` 来启用日志记录。日志文件将位于 `$HOME/.claude-code-router.log`
- **`APIKEY`** (可选): 您可以设置一个密钥来进行身份验证。设置后,客户端请求必须在 `Authorization` 请求头 (例如, `Bearer your-secret-key`) 或 `x-api-key` 请求头中提供此密钥。例如:`"APIKEY": "your-secret-key"`
- **`HOST`** (可选): 您可以设置服务的主机地址。如果未设置 `APIKEY`,出于安全考虑,主机地址将强制设置为 `127.0.0.1`,以防止未经授权的访问。例如:`"HOST": "0.0.0.0"`
- **`NON_INTERACTIVE_MODE`** (可选): 当设置为 `true` 时,启用与非交互式环境(如 GitHub Actions、Docker 容器或其他 CI/CD 系统)的兼容性。这会设置适当的环境变量(`CI=true``FORCE_COLOR=0` 等)并配置 stdin 处理,以防止进程在自动化环境中挂起。例如:`"NON_INTERACTIVE_MODE": true`
- **`Providers`**: 用于配置不同的模型提供商。
- **`Router`**: 用于设置路由规则。`default` 指定默认模型,如果未配置其他路由,则该模型将用于所有请求。
- **`API_TIMEOUT_MS`**: API 请求超时时间,单位为毫秒。
@@ -50,6 +51,7 @@ npm install -g @musistudio/claude-code-router
"PROXY_URL": "http://127.0.0.1:7890",
"LOG": true,
"API_TIMEOUT_MS": 600000,
"NON_INTERACTIVE_MODE": false,
"Providers": [
{
"name": "openrouter",
@@ -402,6 +404,7 @@ jobs:
cat << 'EOF' > $HOME/.claude-code-router/config.json
{
"log": true,
"NON_INTERACTIVE_MODE": true,
"OPENAI_API_KEY": "${{ secrets.OPENAI_API_KEY }}",
"OPENAI_BASE_URL": "https://api.deepseek.com",
"OPENAI_MODEL": "deepseek-chat"

View File

@@ -114,5 +114,6 @@
},
"APIKEY": "your-secret-key",
"HOST": "0.0.0.0",
"API_TIMEOUT_MS": 600000
"API_TIMEOUT_MS": 600000,
"NON_INTERACTIVE_MODE": false
}

2052
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@ export const createServer = (config: any): Server => {
// Restart the service after a short delay to allow response to be sent
setTimeout(() => {
const { spawn } = require("child_process");
spawn("ccr", ["restart"], { detached: true, stdio: "ignore" });
spawn(process.execPath, [process.argv[1], "restart"], { detached: true, stdio: "ignore" });
}, 1000);
});

View File

@@ -1,21 +1,29 @@
import { spawn } from "child_process";
import {
incrementReferenceCount,
decrementReferenceCount,
} from "./processCheck";
import { closeService } from "./close";
import { spawn, type StdioOptions } from "child_process";
import { readConfigFile } from ".";
import { closeService } from "./close";
import {
decrementReferenceCount,
incrementReferenceCount,
} from "./processCheck";
export async function executeCodeCommand(args: string[] = []) {
// Set environment variables
const config = await readConfigFile();
const env = {
const env: Record<string, string> = {
...process.env,
ANTHROPIC_AUTH_TOKEN: "test",
ANTHROPIC_BASE_URL: `http://127.0.0.1:${config.PORT || 3456}`,
API_TIMEOUT_MS: String(config.API_TIMEOUT_MS ?? 600000), // Default to 10 minutes if not set
};
// Non-interactive mode for automation environments
if (config.NON_INTERACTIVE_MODE) {
env.CI = "true";
env.FORCE_COLOR = "0";
env.NODE_NO_READLINE = "1";
env.TERM = "dumb";
}
// Set ANTHROPIC_SMALL_FAST_MODEL if it exists in config
if (config?.ANTHROPIC_SMALL_FAST_MODEL) {
env.ANTHROPIC_SMALL_FAST_MODEL = config.ANTHROPIC_SMALL_FAST_MODEL;
@@ -35,13 +43,23 @@ export async function executeCodeCommand(args: string[] = []) {
// Properly join arguments to preserve spaces in quotes
// Wrap each argument in double quotes to preserve single and double quotes inside arguments
const joinedArgs = args.length > 0 ? args.map(arg => `"${arg.replace(/\"/g, '\\"')}"`).join(" ") : "";
// 🔥 CONFIG-DRIVEN: stdio configuration based on environment
const stdioConfig: StdioOptions = config.NON_INTERACTIVE_MODE
? ["pipe", "inherit", "inherit"] // Pipe stdin for non-interactive
: "inherit"; // Default inherited behavior
const claudeProcess = spawn(claudePath + (joinedArgs ? ` ${joinedArgs}` : ""), [], {
env,
stdio: "inherit",
stdio: stdioConfig,
shell: true,
});
// Close stdin for non-interactive mode
if (config.NON_INTERACTIVE_MODE) {
claudeProcess.stdin?.end();
}
claudeProcess.on("error", (error) => {
console.error("Failed to start claude command:", error.message);
console.log(

View File

@@ -174,5 +174,6 @@
"think": "gemini-cli,gemini-2.5-pro",
"longContext": "gemini-cli,gemini-2.5-pro",
"webSearch": "gemini-cli,gemini-2.5-flash"
}
}
},
"NON_INTERACTIVE_MODE": false
}