this repo has no description
0
fork

Configure Feed

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

refactor: remove auth-adapter middleware (#1)

* refactor: remove auth-adapter middleware

LiteLLM can handle header translation natively using extra_headers.
This eliminates the need for a separate middleware service.

- Configure LiteLLM to pass x-api-key and Accept-Encoding headers
- Remove auth-adapter service from docker-compose
- Update docs to reflect simplified architecture

* revert: restore auth-adapter middleware

Address bugbot review concern about os.environ/ syntax in
extra_headers not being reliably processed by LiteLLM.

The auth-adapter middleware is a proven approach for:
- Translating Bearer tokens to x-api-key headers
- Adding Accept-Encoding: identity to prevent gzip issues

While LiteLLM docs claim os.environ/ works for "ANY value",
there are reported issues with nested dictionary values.
Keeping auth-adapter ensures reliable header translation.

* refactor: remove auth-adapter middleware

Use LiteLLM's native extra_headers with os.environ/ substitution.

Investigation confirmed this is safe:
- LiteLLM docs: os.environ/ works for "ANY value on config.yaml"
- Source code: _check_for_os_environ_vars recursively processes dicts
- extra_headers values are processed like any other config value

This eliminates a separate Docker service while maintaining the same
header translation (Bearer -> x-api-key, Accept-Encoding: identity).

---------

Co-authored-by: Claude <noreply@anthropic.com>

authored by

Alice
Claude
and committed by
GitHub
64beb117 94554865

+12 -104
+1 -6
README.md
··· 10 10 Letta (port 8283) - AI agent framework 11 11 ↓ OpenAI-compatible API 12 12 LiteLLM (port 4000) - API translation layer 13 - ↓ Anthropic API format 14 - auth-adapter (port 4002) - Header translation 15 - 13 + ↓ Anthropic API format (x-api-key via extra_headers) 16 14 anthropic-proxy (port 4001) - OAuth session management 17 15 18 16 Anthropic API (Claude Opus 4.5) ··· 21 19 - **Bun**: Runtime and HTTP server for Telegram bot 22 20 - **Letta**: AI agent framework with persistent memory 23 21 - **LiteLLM**: Translates OpenAI-compatible requests to Anthropic format 24 - - **auth-adapter**: Middleware for header translation (Bearer → x-api-key) 25 22 - **anthropic-proxy**: OAuth proxy for Anthropic API access 26 23 - **SQLite**: Local storage for items, wins, and context 27 24 ··· 59 56 60 57 This starts: 61 58 - `anthropic-proxy` on port 4001 - OAuth proxy for Anthropic API 62 - - `auth-adapter` on port 4002 - Header translation middleware 63 59 - `litellm` on port 4000 - OpenAI-compatible API proxy 64 60 - `letta` on port 8283 - AI agent framework 65 61 ··· 202 198 │ ├── config.ts # Environment configuration 203 199 │ ├── health.ts # Health check endpoints 204 200 │ ├── letta.ts # Letta client bootstrap 205 - │ ├── auth-adapter.ts # Header translation middleware 206 201 │ ├── db/ 207 202 │ │ ├── index.ts # Database initialization 208 203 │ │ ├── schema.ts # Drizzle ORM schema
+3 -27
docker-compose.yml
··· 26 26 timeout: 10s 27 27 retries: 3 28 28 29 - # Auth adapter: adds Accept-Encoding: identity to prevent gzip responses 30 - auth-adapter: 31 - image: oven/bun:latest 32 - working_dir: /app 33 - command: ["bun", "run", "src/auth-adapter.ts"] 34 - ports: 35 - - "4002:4002" 36 - environment: 37 - - ANTHROPIC_PROXY_INTERNAL_URL=http://anthropic-proxy:4001 38 - - AUTH_ADAPTER_PORT=4002 39 - volumes: 40 - - ./src/auth-adapter.ts:/app/src/auth-adapter.ts:ro 41 - networks: 42 - - assistant-net 43 - restart: unless-stopped 44 - depends_on: 45 - anthropic-proxy: 46 - condition: service_healthy 47 - healthcheck: 48 - test: ["CMD", "bun", "-e", "fetch('http://localhost:4002/health').then(r => process.exit(r.ok ? 0 : 1))"] 49 - interval: 30s 50 - timeout: 10s 51 - retries: 3 52 - 53 - # LiteLLM: OpenAI-compatible API that proxies to auth-adapter -> anthropic-proxy 29 + # LiteLLM: OpenAI-compatible API that proxies to anthropic-proxy 54 30 litellm: 55 31 image: ghcr.io/berriai/litellm:main-latest 56 32 ports: ··· 64 40 - assistant-net 65 41 restart: unless-stopped 66 42 depends_on: 67 - auth-adapter: 43 + anthropic-proxy: 68 44 condition: service_healthy 69 45 healthcheck: 70 46 test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:4000/health')"] ··· 79 55 environment: 80 56 # Use LiteLLM as OpenAI-compatible endpoint for Claude models 81 57 - OPENAI_API_BASE=http://litellm:4000 82 - # Session ID is passed as API key to LiteLLM -> auth-adapter -> anthropic-proxy 58 + # Session ID is passed via LiteLLM extra_headers to anthropic-proxy 83 59 - OPENAI_API_KEY=${ANTHROPIC_PROXY_SESSION_ID} 84 60 volumes: 85 61 - letta-data:/var/lib/postgresql/data
+8 -3
litellm-config.yaml
··· 1 1 model_list: 2 2 # Claude Opus 4.5 - the only model for this project 3 - # Routes through auth-adapter -> anthropic-proxy for OAuth-based auth 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) 4 6 - model_name: claude-opus-4-5-20251101 5 7 litellm_params: 6 8 model: anthropic/claude-opus-4-5-20251101 7 - api_base: http://auth-adapter:4002 8 - api_key: os.environ/ANTHROPIC_PROXY_SESSION_ID 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 9 14 10 15 litellm_settings: 11 16 drop_params: true
-68
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} -> ${targetUrl}`); 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 - const authHeader = headers.get('Authorization') ?? ''; 30 - if (authHeader.startsWith('Bearer ')) { 31 - const token = authHeader.slice(7); 32 - headers.set('x-api-key', token); 33 - headers.delete('Authorization'); 34 - console.log(` Auth: Bearer -> x-api-key (token: ${token.slice(0, 8)}...)`); 35 - } else { 36 - console.log(` Auth: None or non-Bearer`); 37 - } 38 - 39 - // Request uncompressed responses (Letta can't handle gzip) 40 - headers.set('Accept-Encoding', 'identity'); 41 - 42 - // Forward the request 43 - try { 44 - const response = await fetch(targetUrl, { 45 - method: req.method, 46 - headers, 47 - body: req.method !== 'GET' && req.method !== 'HEAD' ? req.body : undefined, 48 - }); 49 - 50 - console.log(` Response: ${response.status.toString()}`); 51 - 52 - // Return the response 53 - return new Response(response.body, { 54 - status: response.status, 55 - statusText: response.statusText, 56 - headers: response.headers, 57 - }); 58 - } catch (error) { 59 - console.error('Proxy error:', error); 60 - return new Response(JSON.stringify({ error: 'Proxy error' }), { 61 - status: 502, 62 - headers: { 'Content-Type': 'application/json' }, 63 - }); 64 - } 65 - }, 66 - }); 67 - 68 - console.log(`Auth adapter listening on http://localhost:${PORT.toString()}`);