diff --git a/.env.example b/.env.example index b9aab986..4e79813d 100644 --- a/.env.example +++ b/.env.example @@ -26,8 +26,13 @@ ANTHROPIC_API_KEY=your-api-key-here # OPENROUTER_API_KEY=sk-or-your-openrouter-key # ROUTER_DEFAULT=openrouter,google/gemini-3-flash-preview +# --- Z.AI (GLM models) --- +# ZAI_API_KEY=your-zai-api-key +# ROUTER_DEFAULT=zai,glm-5 + # ============================================================================= # Available Models # ============================================================================= # OpenAI: gpt-5.2, gpt-5-mini # OpenRouter: google/gemini-3-flash-preview +# Z.AI: glm-5, glm-4.7, glm-4.7-flash diff --git a/README.md b/README.md index 89e2bfab..cd4216b1 100644 --- a/README.md +++ b/README.md @@ -274,9 +274,13 @@ Shannon can experimentally route requests through alternative AI providers using OPENAI_API_KEY=sk-... # OR OPENROUTER_API_KEY=sk-or-... +# OR +ZAI_API_KEY=your-zai-api-key # Set default model: ROUTER_DEFAULT=openai,gpt-5.2 # provider,model format +# OR +# ROUTER_DEFAULT=zai,glm-5 ``` 2. Run with `ROUTER=true`: @@ -291,6 +295,7 @@ ROUTER_DEFAULT=openai,gpt-5.2 # provider,model format |----------|--------| | OpenAI | gpt-5.2, gpt-5-mini | | OpenRouter | google/gemini-3-flash-preview | +| Z.AI | glm-5, glm-4.7, glm-4.7-flash | #### Disclaimer diff --git a/configs/router-config.json b/configs/router-config.json index cf57b1e9..6dec9171 100644 --- a/configs/router-config.json +++ b/configs/router-config.json @@ -10,9 +10,20 @@ "name": "openai", "api_base_url": "https://api.openai.com/v1/chat/completions", "api_key": "$OPENAI_API_KEY", - "models": ["gpt-5.2", "gpt-5-mini"], + "models": [ + "gpt-5.2", + "gpt-5-mini", + "gpt-4o" + ], "transformer": { - "use": [["maxcompletiontokens", { "max_completion_tokens": 16384 }]] + "use": [ + [ + "maxcompletiontokens", + { + "max_completion_tokens": 16384 + } + ] + ] } }, { @@ -23,11 +34,33 @@ "google/gemini-3-flash-preview" ], "transformer": { - "use": ["openrouter"] + "use": [ + "openrouter" + ] + } + }, + { + "name": "zai", + "api_base_url": "https://api.z.ai/api/coding/paas/v4/chat/completions", + "api_key": "$ZAI_API_KEY", + "models": [ + "glm-5", + "glm-4.7", + "glm-4.7-flash" + ], + "transformer": { + "use": [ + [ + "maxcompletiontokens", + { + "max_completion_tokens": 16384 + } + ] + ] } } ], "Router": { "default": "$ROUTER_DEFAULT" } -} +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e54ba1ff..e97c5175 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,6 +60,7 @@ services: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} - OPENAI_API_KEY=${OPENAI_API_KEY:-} - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} + - ZAI_API_KEY=${ZAI_API_KEY:-} - ROUTER_DEFAULT=${ROUTER_DEFAULT:-openai,gpt-4o} healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:3456/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"] diff --git a/shannon b/shannon index d141ddca..6c5e3d6a 100755 --- a/shannon +++ b/shannon @@ -136,12 +136,12 @@ cmd_start() { # Check for API key (router mode can use alternative provider API keys) if [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then - if [ "$ROUTER" = "true" ] && { [ -n "$OPENAI_API_KEY" ] || [ -n "$OPENROUTER_API_KEY" ]; }; then + if [ "$ROUTER" = "true" ] && { [ -n "$OPENAI_API_KEY" ] || [ -n "$OPENROUTER_API_KEY" ] || [ -n "$ZAI_API_KEY" ]; }; then # Router mode with alternative provider - set a placeholder for SDK init export ANTHROPIC_API_KEY="router-mode" else echo "ERROR: Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN in .env" - echo " (or use ROUTER=true with OPENAI_API_KEY or OPENROUTER_API_KEY)" + echo " (or use ROUTER=true with OPENAI_API_KEY, OPENROUTER_API_KEY or ZAI_API_KEY)" exit 1 fi fi @@ -182,8 +182,8 @@ cmd_start() { echo "Starting claude-code-router..." # Check for provider API keys - if [ -z "$OPENAI_API_KEY" ] && [ -z "$OPENROUTER_API_KEY" ]; then - echo "WARNING: No provider API key set (OPENAI_API_KEY or OPENROUTER_API_KEY). Router may not work." + if [ -z "$OPENAI_API_KEY" ] && [ -z "$OPENROUTER_API_KEY" ] && [ -z "$ZAI_API_KEY" ]; then + echo "WARNING: No provider API key set (OPENAI_API_KEY, OPENROUTER_API_KEY or ZAI_API_KEY). Router may not work." fi # Start router with profile diff --git a/src/ai/claude-executor.ts b/src/ai/claude-executor.ts index ceab2d67..6ce19b74 100644 --- a/src/ai/claude-executor.ts +++ b/src/ai/claude-executor.ts @@ -229,6 +229,16 @@ export async function runClaudePrompt( if (process.env.CLAUDE_CODE_OAUTH_TOKEN) { sdkEnv.CLAUDE_CODE_OAUTH_TOKEN = process.env.CLAUDE_CODE_OAUTH_TOKEN; } + // Router mode: forward base URL, auth token, and model config to SDK subprocess + if (process.env.ANTHROPIC_BASE_URL) { + sdkEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL; + } + if (process.env.ANTHROPIC_AUTH_TOKEN) { + sdkEnv.ANTHROPIC_AUTH_TOKEN = process.env.ANTHROPIC_AUTH_TOKEN; + } + if (process.env.ROUTER_DEFAULT) { + sdkEnv.ROUTER_DEFAULT = process.env.ROUTER_DEFAULT; + } const options = { model: 'claude-sonnet-4-5-20250929', @@ -472,7 +482,7 @@ export async function runClaudePromptWithRetry( await commitGitSuccess(sourceDir, description); console.log(chalk.green.bold(`${description} completed successfully on attempt ${attempt}/${maxRetries}`)); return result; - // Validation failure is retryable - agent might succeed on retry with cleaner workspace + // Validation failure is retryable - agent might succeed on retry with cleaner workspace } else { console.log(chalk.yellow(`${description} completed but output validation failed`));