this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: restore auth-adapter middleware for LiteLLM auth

LiteLLM's os.environ/ substitution doesn't work in extra_headers,
causing "Authentication required" errors. The auth-adapter middleware
translates Bearer tokens to x-api-key headers reliably.

Changes:
- Restore src/auth-adapter.ts for header translation
- Route LiteLLM through auth-adapter -> anthropic-proxy
- Add volume for anthropic-proxy session persistence
- Simplify litellm-config.yaml (use api_key, not extra_headers)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice f2e769fe 103ac23a

+137 -50
+65 -37
docker-compose.yml
··· 9 9 context: . 10 10 dockerfile: Dockerfile.anthropic-proxy 11 11 ports: 12 - - "4001:4001" 12 + - '4001:4001' 13 13 environment: 14 14 - PORT=4001 15 15 - SESSION_SECRET=${ANTHROPIC_PROXY_SESSION_SECRET} ··· 17 17 - REDIRECT_URI=https://console.anthropic.com/oauth/code/callback 18 18 - OAUTH_BASE_URL=https://claude.ai 19 19 - API_BASE_URL=https://api.anthropic.com/v1 20 + volumes: 21 + - anthropic-proxy-data:/root/.local/share/anthropic-proxy 20 22 networks: 21 23 - assistant-net 22 24 restart: unless-stopped 23 25 healthcheck: 24 - test: ["CMD", "curl", "-f", "http://localhost:4001/health"] 26 + test: ['CMD', 'curl', '-f', 'http://localhost:4001/health'] 25 27 interval: 30s 26 28 timeout: 10s 27 29 retries: 3 28 30 29 - # LiteLLM: OpenAI-compatible API that proxies to anthropic-proxy 30 - litellm: 31 - image: ghcr.io/berriai/litellm:main-latest 31 + # Auth adapter: translates Bearer tokens to x-api-key headers 32 + # LiteLLM's os.environ/ substitution doesn't work in extra_headers 33 + auth-adapter: 34 + image: oven/bun:latest 35 + working_dir: /app 36 + command: ['bun', 'run', 'src/auth-adapter.ts'] 32 37 ports: 33 - - "4000:4000" 38 + - '4002:4002' 34 39 environment: 35 - - ANTHROPIC_PROXY_SESSION_ID=${ANTHROPIC_PROXY_SESSION_ID} 40 + - ANTHROPIC_PROXY_INTERNAL_URL=http://anthropic-proxy:4001 41 + - AUTH_ADAPTER_PORT=4002 36 42 volumes: 37 - - ./litellm-config.yaml:/app/config.yaml:ro 38 - command: ["--config", "/app/config.yaml", "--port", "4000"] 43 + - ./src/auth-adapter.ts:/app/src/auth-adapter.ts:ro 39 44 networks: 40 45 - assistant-net 41 46 restart: unless-stopped ··· 43 48 anthropic-proxy: 44 49 condition: service_healthy 45 50 healthcheck: 46 - test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:4000/health')"] 51 + test: ['CMD', 'bun', '-e', "fetch('http://localhost:4002/health').then(r => process.exit(r.ok ? 0 : 1))"] 47 52 interval: 30s 48 53 timeout: 10s 49 54 retries: 3 50 55 51 - letta: 52 - image: letta/letta:latest 56 + # LiteLLM: OpenAI-compatible API that proxies to auth-adapter -> anthropic-proxy 57 + litellm: 58 + image: ghcr.io/berriai/litellm:main-latest 53 59 ports: 54 - - "8283:8283" 60 + - '4000:4000' 55 61 environment: 56 - # Use LiteLLM as OpenAI-compatible endpoint for Claude models 57 - - OPENAI_API_BASE=http://litellm:4000 58 - # Session ID is passed via LiteLLM extra_headers to anthropic-proxy 59 - - OPENAI_API_KEY=${ANTHROPIC_PROXY_SESSION_ID} 62 + - ANTHROPIC_PROXY_SESSION_ID=${ANTHROPIC_PROXY_SESSION_ID} 60 63 volumes: 61 - - letta-data:/var/lib/postgresql/data 64 + - ./litellm-config.yaml:/app/config.yaml:ro 65 + command: ['--config', '/app/config.yaml', '--port', '4000'] 62 66 networks: 63 67 - assistant-net 64 68 restart: unless-stopped 65 69 depends_on: 66 - litellm: 70 + auth-adapter: 67 71 condition: service_healthy 68 72 healthcheck: 69 - test: ["CMD", "curl", "-f", "http://localhost:8283/v1/health"] 73 + test: ['CMD', 'python', '-c', "import urllib.request; urllib.request.urlopen('http://localhost:4000/health')"] 70 74 interval: 30s 71 75 timeout: 10s 72 76 retries: 3 73 77 74 - app: 75 - build: . 78 + letta: 79 + image: letta/letta:latest 76 80 ports: 77 - - "3000:3000" 81 + - '8283:8283' 78 82 environment: 79 - - PORT=3000 80 - - LETTA_BASE_URL=http://letta:8283 81 - - LITELLM_URL=http://litellm:4000 82 - - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} 83 - - TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL} 84 - - TELEGRAM_WEBHOOK_SECRET_TOKEN=${TELEGRAM_WEBHOOK_SECRET_TOKEN} 85 - - ANTHROPIC_PROXY_URL=http://anthropic-proxy:4001/v1 86 - - ANTHROPIC_PROXY_SESSION_ID=${ANTHROPIC_PROXY_SESSION_ID} 87 - - OPENAI_API_KEY=${OPENAI_API_KEY} 88 - # Tool webhook URL - Letta calls back to app via Docker network 89 - - TOOL_WEBHOOK_URL=http://app:3000 83 + # Use LiteLLM as OpenAI-compatible endpoint for Claude models 84 + - OPENAI_API_BASE=http://litellm:4000 85 + # Session ID is passed via LiteLLM extra_headers to anthropic-proxy 86 + - OPENAI_API_KEY=${ANTHROPIC_PROXY_SESSION_ID} 90 87 volumes: 91 - - ./data:/app/data 88 + - letta-data:/var/lib/postgresql/data 92 89 networks: 93 90 - assistant-net 94 91 restart: unless-stopped 95 92 depends_on: 96 - letta: 93 + litellm: 97 94 condition: service_healthy 98 95 healthcheck: 99 - test: ["CMD", "curl", "-f", "http://localhost:3000/health"] 96 + test: ['CMD', 'curl', '-f', 'http://localhost:8283/v1/health'] 100 97 interval: 30s 101 98 timeout: 10s 102 99 retries: 3 103 100 101 + # app: 102 + # build: . 103 + # ports: 104 + # - '3000:3000' 105 + # environment: 106 + # - PORT=3000 107 + # - LETTA_BASE_URL=http://letta:8283 108 + # - LITELLM_URL=http://litellm:4000 109 + # - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} 110 + # - TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL} 111 + # - TELEGRAM_WEBHOOK_SECRET_TOKEN=${TELEGRAM_WEBHOOK_SECRET_TOKEN} 112 + # - ANTHROPIC_PROXY_URL=http://anthropic-proxy:4001/v1 113 + # - ANTHROPIC_PROXY_SESSION_ID=${ANTHROPIC_PROXY_SESSION_ID} 114 + # - OPENAI_API_KEY=${OPENAI_API_KEY} 115 + # # Tool webhook URL - Letta calls back to app via Docker network 116 + # - TOOL_WEBHOOK_URL=http://app:3000 117 + # volumes: 118 + # - ./data:/app/data 119 + # networks: 120 + # - assistant-net 121 + # restart: unless-stopped 122 + # depends_on: 123 + # letta: 124 + # condition: service_healthy 125 + # healthcheck: 126 + # test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] 127 + # interval: 30s 128 + # timeout: 10s 129 + # retries: 3 130 + 104 131 volumes: 105 132 letta-data: 133 + anthropic-proxy-data:
+5 -13
litellm-config.yaml
··· 1 1 model_list: 2 2 # Claude Opus 4.5 - main conversational agent 3 - # Connects directly to anthropic-proxy with custom headers 4 - # Note: os.environ/ works in extra_headers per LiteLLM docs ("ANY value") 5 - # and source code (_check_for_os_environ_vars recursively processes dicts) 3 + # Routes through auth-adapter -> anthropic-proxy for OAuth-based auth 6 4 - model_name: claude-opus-4-5-20251101 7 5 litellm_params: 8 6 model: anthropic/claude-opus-4-5-20251101 9 - api_base: http://anthropic-proxy:4001 10 - api_key: "unused" 11 - extra_headers: 12 - x-api-key: os.environ/ANTHROPIC_PROXY_SESSION_ID 13 - Accept-Encoding: identity 7 + api_base: http://auth-adapter:4002 8 + api_key: os.environ/ANTHROPIC_PROXY_SESSION_ID 14 9 15 10 # Claude Haiku 4.5 - fast detection/classification 16 11 # Used for overwhelm, brain dump, and self-bullying detection 17 12 - model_name: claude-haiku-4-5-20251001 18 13 litellm_params: 19 14 model: anthropic/claude-haiku-4-5-20251001 20 - api_base: http://anthropic-proxy:4001 21 - api_key: "unused" 22 - extra_headers: 23 - x-api-key: os.environ/ANTHROPIC_PROXY_SESSION_ID 24 - Accept-Encoding: identity 15 + api_base: http://auth-adapter:4002 16 + api_key: os.environ/ANTHROPIC_PROXY_SESSION_ID 25 17 26 18 litellm_settings: 27 19 drop_params: true
+67
src/auth-adapter.ts
··· 1 + /** 2 + * Auth adapter proxy for anthropic-proxy 3 + * 4 + * Translates Authorization: Bearer <token> to x-api-key: <token> 5 + * This allows Letta (which uses OpenAI-style auth) to communicate 6 + * with anthropic-proxy (which expects x-api-key header). 7 + * 8 + * Run: bun src/auth-adapter.ts 9 + */ 10 + 11 + const PROXY_TARGET = process.env['ANTHROPIC_PROXY_INTERNAL_URL'] ?? 'http://localhost:4001'; 12 + const PORT = Number(process.env['AUTH_ADAPTER_PORT'] ?? '4002'); 13 + 14 + console.log(`Auth adapter starting on port ${PORT.toString()}`); 15 + console.log(`Proxying to: ${PROXY_TARGET}`); 16 + 17 + Bun.serve({ 18 + port: PORT, 19 + async fetch(req) { 20 + const url = new URL(req.url); 21 + const targetUrl = `${PROXY_TARGET}${url.pathname}${url.search}`; 22 + 23 + console.log(`[${req.method}] ${url.pathname}`); 24 + 25 + // Clone headers and translate auth 26 + const headers = new Headers(req.headers); 27 + 28 + // Extract Bearer token and convert to x-api-key 29 + // Also handle case where LiteLLM sends x-api-key directly (for Anthropic provider) 30 + const authHeader = headers.get('Authorization') ?? ''; 31 + if (authHeader.startsWith('Bearer ')) { 32 + const token = authHeader.slice(7); 33 + headers.set('x-api-key', token); 34 + headers.delete('Authorization'); 35 + } 36 + // If x-api-key is already set (by LiteLLM's Anthropic provider), it passes through 37 + 38 + // Request uncompressed responses (Letta can't handle gzip) 39 + headers.set('Accept-Encoding', 'identity'); 40 + 41 + // Forward the request 42 + try { 43 + const response = await fetch(targetUrl, { 44 + method: req.method, 45 + headers, 46 + body: req.method !== 'GET' && req.method !== 'HEAD' ? req.body : undefined, 47 + }); 48 + 49 + console.log(` Response: ${response.status.toString()}`); 50 + 51 + // Return the response 52 + return new Response(response.body, { 53 + status: response.status, 54 + statusText: response.statusText, 55 + headers: response.headers, 56 + }); 57 + } catch (error) { 58 + console.error('Proxy error:', error); 59 + return new Response(JSON.stringify({ error: 'Proxy error' }), { 60 + status: 502, 61 + headers: { 'Content-Type': 'application/json' }, 62 + }); 63 + } 64 + }, 65 + }); 66 + 67 + console.log(`Auth adapter listening on http://localhost:${PORT.toString()}`);