feat: add claude-code-router support for multi-model testing

- Add ROUTER=true flag to route requests through claude-code-router
- Add router service to docker-compose with profile-based activation
- Support OpenAI (gpt-4o) and Google Gemini (gemini-2.5-pro) as alternatives
- Add router-config.json with provider configuration template
- Update .env.example with provider API key options
- Document router mode limitations (cost tracking shows $0)
This commit is contained in:
ajmallesh
2026-01-15 14:14:37 -08:00
parent 378ed824ad
commit df06dadaff
5 changed files with 138 additions and 6 deletions

View File

@@ -1,8 +1,33 @@
# Shannon Environment Configuration
# Copy this file to .env and fill in your credentials
# Anthropic API Key (required - choose one)
# Recommended output token configuration for larger tool outputs
CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000
# =============================================================================
# OPTION 1: Direct Anthropic (default, no router)
# =============================================================================
ANTHROPIC_API_KEY=your-api-key-here
# OR use OAuth token instead
# CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
# =============================================================================
# OPTION 2: Router Mode (use alternative providers)
# =============================================================================
# Enable router mode by running: ./shannon start ... ROUTER=true
# Then configure ONE of the providers below:
# --- OpenAI ---
# OPENAI_API_KEY=sk-your-openai-key
# ROUTER_DEFAULT=openai,gpt-4o
# --- Google Gemini ---
GEMINI_API_KEY=your-gemini-key
ROUTER_DEFAULT=gemini,gemini-2.5-pro
# =============================================================================
# Available Models
# =============================================================================
# OpenAI: gpt-4o, gpt-4o-mini
# Gemini: gemini-2.5-pro, gemini-2.5-flash

View File

@@ -50,6 +50,7 @@ CONFIG=<file> YAML configuration file for authentication and testing pa
OUTPUT=<path> Custom output directory for session folder (default: ./audit-logs/)
PIPELINE_TESTING=true Use minimal prompts and fast retry intervals (10s instead of 5min)
REBUILD=true Force Docker rebuild with --no-cache (use when code changes aren't picked up)
ROUTER=true Route requests through claude-code-router for multi-model support (see limitations below)
```
### Generate TOTP for Authentication
@@ -284,6 +285,16 @@ Missing tools can be skipped using `PIPELINE_TESTING=true` mode during developme
- `subfinder` - Subdomain discovery
- `whatweb` - Web technology detection
### Router Mode Limitations
When using `ROUTER=true` to route requests through claude-code-router (e.g., to use OpenAI models):
**Cost tracking shows $0.00**: The Claude Agent SDK expects `total_cost_usd` in the result message, which is Anthropic-specific. OpenAI's API returns token counts in `usage` but not a cost field, and the router doesn't translate this. This is a known limitation of the router, not a Shannon bug.
**Workarounds:**
- Accept $0 costs when using router mode (recommended for dev/testing)
- Use Anthropic directly for production runs where cost tracking matters
- Use external tools like `ccusage` for post-hoc token analysis
### Diagnostic & Utility Scripts
```bash
# View Temporal workflow history

View File

@@ -0,0 +1,31 @@
{
"HOST": "0.0.0.0",
"APIKEY": "shannon-router-key",
"LOG": true,
"LOG_LEVEL": "info",
"NON_INTERACTIVE_MODE": true,
"API_TIMEOUT_MS": 600000,
"Providers": [
{
"name": "openai",
"api_base_url": "https://api.openai.com/v1/chat/completions",
"api_key": "$OPENAI_API_KEY",
"models": ["gpt-4o", "gpt-4o-mini"],
"transformer": {
"use": [["maxtoken", { "max_tokens": 16384 }]]
}
},
{
"name": "gemini",
"api_base_url": "https://generativelanguage.googleapis.com/v1beta/models/",
"api_key": "$GEMINI_API_KEY",
"models": ["gemini-2.5-pro", "gemini-2.5-flash"],
"transformer": {
"use": ["gemini"]
}
}
],
"Router": {
"default": "$ROUTER_DEFAULT"
}
}

View File

@@ -20,6 +20,8 @@ services:
environment:
- TEMPORAL_ADDRESS=temporal:7233
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
- ANTHROPIC_BASE_URL=${ANTHROPIC_BASE_URL:-} # Optional: route through claude-code-router
- ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN:-} # Auth token for router
- CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-}
- CLAUDE_CODE_MAX_OUTPUT_TOKENS=${CLAUDE_CODE_MAX_OUTPUT_TOKENS:-64000}
depends_on:
@@ -36,5 +38,33 @@ services:
security_opt:
- seccomp:unconfined
# Optional: claude-code-router for multi-model support
# Start with: ROUTER=true ./shannon start ...
router:
image: node:20-slim
profiles: ["router"] # Only starts when explicitly requested
command: >
sh -c "apt-get update && apt-get install -y gettext-base &&
npm install -g @musistudio/claude-code-router &&
mkdir -p /root/.claude-code-router &&
envsubst < /config/router-config.json > /root/.claude-code-router/config.json &&
ccr start"
ports:
- "3456:3456"
volumes:
- ./configs/router-config.json:/config/router-config.json:ro
environment:
- HOST=0.0.0.0
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- GEMINI_API_KEY=${GEMINI_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))"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
volumes:
temporal-data:

45
shannon
View File

@@ -35,6 +35,7 @@ Options for 'start':
CONFIG=<path> Configuration file (YAML)
OUTPUT=<path> Output directory for reports (default: ./audit-logs/)
PIPELINE_TESTING=true Use minimal prompts for fast testing
ROUTER=true Route requests through claude-code-router (multi-model support)
Options for 'stop':
CLEAN=true Remove all data including volumes
@@ -63,6 +64,7 @@ parse_args() {
CLEAN=*) CLEAN="${arg#CLEAN=}" ;;
PIPELINE_TESTING=*) PIPELINE_TESTING="${arg#PIPELINE_TESTING=}" ;;
REBUILD=*) REBUILD="${arg#REBUILD=}" ;;
ROUTER=*) ROUTER="${arg#ROUTER=}" ;;
esac
done
}
@@ -121,10 +123,16 @@ cmd_start() {
exit 1
fi
# Check for API key
# Check for API key (router mode can use OPENAI_API_KEY or GEMINI_API_KEY instead)
if [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
echo "ERROR: Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN in .env"
exit 1
if [ "$ROUTER" = "true" ] && { [ -n "$OPENAI_API_KEY" ] || [ -n "$GEMINI_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 GEMINI_API_KEY)"
exit 1
fi
fi
# Determine container path for REPO
@@ -149,6 +157,33 @@ cmd_start() {
export OUTPUT_DIR="$OUTPUT"
fi
# Handle ROUTER flag - start claude-code-router for multi-model support
if [ "$ROUTER" = "true" ]; then
# Check if router is already running
if docker compose -f "$COMPOSE_FILE" --profile router ps router 2>/dev/null | grep -q "running"; then
echo "Router already running, skipping startup..."
else
echo "Starting claude-code-router..."
# Check for OpenAI API key
if [ -z "$OPENAI_API_KEY" ] && [ -z "$GEMINI_API_KEY" ]; then
echo "WARNING: Neither OPENAI_API_KEY nor GEMINI_API_KEY set. Router may not work."
fi
# Start router with profile
docker compose -f "$COMPOSE_FILE" --profile router up -d router
# Give router a few seconds to start (health check disabled for now - TODO: debug later)
echo "Waiting for router to start..."
sleep 5
fi
# Set ANTHROPIC_BASE_URL to route through router
export ANTHROPIC_BASE_URL="http://router:3456"
# Set auth token to match router's APIKEY
export ANTHROPIC_AUTH_TOKEN="shannon-router-key"
fi
# Ensure containers are running (starts them if needed)
ensure_containers
@@ -226,9 +261,9 @@ cmd_stop() {
parse_args "$@"
if [ "$CLEAN" = "true" ]; then
docker compose -f "$COMPOSE_FILE" down -v
docker compose -f "$COMPOSE_FILE" --profile router down -v
else
docker compose -f "$COMPOSE_FILE" down
docker compose -f "$COMPOSE_FILE" --profile router down
fi
}