Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand All @@ -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

Expand Down
41 changes: 37 additions & 4 deletions configs/router-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
]
}
},
{
Expand All @@ -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"
}
}
}
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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))"]
Expand Down
8 changes: 4 additions & 4 deletions shannon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/ai/claude-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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`));

Expand Down