This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Vandamme Proxy is a FastAPI-based proxy server that converts Claude API requests to OpenAI-compatible API calls. It enables Claude Code CLI to work with various LLM providers (OpenAI, Azure OpenAI, Ollama, and any OpenAI-compatible API).
IMPORTANT: Always use Makefile targets for standard operations. The Makefile provides standardized commands that align with CI/CD pipelines and encode project-specific best practices.
# Quick start (recommended) - sets up everything
make dev-env-init
# Or setup development environment only
make dev-env-setup
# Verify installation succeeded
make check-install
# Using UV directly (if needed)
uv sync --extra cli# Using the vdm CLI (recommended)
vdm server start
# With bridge (auto-starts agent-cli-to-api for cursor/codex/gemini/claude)
vdm server start --bridge cursor
# Direct execution
python start_proxy.py
# Or with Docker
docker compose up -dThe test suite follows a strictly enforced three-tier categorization:
| Category | Location | Dependencies | API Calls | Default |
|---|---|---|---|---|
| unit | tests/unit/ |
None (RESPX mocked) | None | Yes |
| integration | tests/integration/ |
Running local server | Only localhost | Yes |
| external | tests/external/ |
Real external APIs | Real HTTP | No (opt-in) |
# Run default tests (unit + integration, no API costs)
make test
# Run unit tests only (fast, all mocked)
make test-unit
# Run integration tests (requires server, localhost only)
make test-integration
# Run external tests (requires API keys + opt-in)
ALLOW_EXTERNAL_TESTS=1 make test-external
# External test one-shot mode (auto-enables opt-in)
make test-external-oneshot
# External test lenient mode (skips if keys missing)
ALLOW_EXTERNAL_TESTS=1 EXTERNAL_TESTS_SKIP_MISSING=1 make test-external
# Run ALL tests including external (full validation)
make test-all
# Quick tests without coverage
make test-quick
# Test configuration and connectivity
vdm test connection
vdm test models
vdm health upstream
vdm config validate
# Debug model resolution
vdm debug model-resolution <model-name> # Trace resolution pipeline
vdm debug model-resolution <model-name> --json # JSON output for piping to jqExternal Test Opt-In:
ALLOW_EXTERNAL_TESTS=1- Required to run external tests (prevents accidental API charges)EXTERNAL_TESTS_SKIP_MISSING=1- Skip tests when their required API keys are missing
Deprecated:
make test-e2e- Usemake test-externalinstead
The project uses RESPX for elegant HTTP API mocking:
import pytest
import httpx
from tests.fixtures.mock_http import (
openai_chat_completion,
mock_openai_api,
)
@pytest.mark.unit
@pytest.mark.asyncio
async def test_chat(mock_openai_api, openai_chat_completion):
"""Test chat completion with mocked OpenAI API."""
# Mock the OpenAI endpoint
mock_openai_api.post("/v1/chat/completions").mock(
return_value=httpx.Response(200, json=openai_chat_completion)
)
# Your test code using the proxy
# The HTTP call is intercepted and returns the mocked responseKey fixtures available in tests/fixtures/mock_http.py:
mock_openai_api- RESPX mock for OpenAI endpointsmock_anthropic_api- RESPX mock for Anthropic endpointsopenai_chat_completion- Standard chat responseopenai_chat_completion_with_tool- Function calling responseopenai_streaming_chunks- Streaming SSE eventsanthropic_message_response- Anthropic message formatanthropic_streaming_events- Anthropic SSE events
Benefits:
- ✅ Zero API costs for regular development
- ✅ 10-100x faster test execution
- ✅ Works offline, no network dependencies
- ✅ Deterministic, reproducible tests
- ✅ Mock at HTTP layer (not SDK objects)
The project uses Codecov for coverage tracking and reporting:
# Run tests with coverage (local)
make coverage
# View HTML coverage report
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # LinuxCoverage Configuration:
codecov.yml- Codecov platform settings (thresholds, ignores)pyproject.toml- pytest-cov settings (source dirs, omit patterns)- Project target: 80% coverage
- Patch target: 75% coverage
CI/CD:
- Coverage reports uploaded to Codecov on every PR
- PR comments show coverage diff and missing lines
- GitHub Actions use codecov-action@v5
- Upload happens only for Python 3.12 matrix job
# Format code (ruff format + ruff check --fix with type transformations)
make format
# Lint check only (doesn't modify files)
make lint
# Type checking
make type-check
# Run all static checks (format + lint + typecheck, NO tests)
make sanitize
# Quick check (format + lint only, skip type-check)
make quick-check
# Fast validation (quick-check + quick tests)
make validate
# Pre-commit checks (format + all checks)
make pre-commit
# Security checks
make security-check# Setup development environment (editable, includes CLI)
make dev-env-setup
# Initialize complete development environment (recommended for first-time setup)
make dev-env-init
# Verify that vdm CLI is installed correctly
make check-install
# Run development server with hot reload
make dev
# Check proxy server health
make health
# Clean temporary files and caches
make clean
# Show all available targets
make help-
Request/Response Flow:
src/api/endpoints.py- FastAPI endpoints (/v1/messages,/v1/messages/count_tokens,/v1/models,/v1/aliases,/health,/test-connection)src/conversion/request_converter.py- Converts Claude API format to OpenAI formatsrc/conversion/response_converter.py- Converts OpenAI responses back to Claude formatsrc/core/client.py- OpenAI API client with retry logic and connection poolingsrc/core/anthropic_client.py- Anthropic-compatible API client for direct passthroughsrc/core/provider_manager.py- Multi-provider management with format selectionsrc/core/model_manager.py- Model name resolution with alias supportsrc/core/alias_manager.py- Model alias management with case-insensitive substring matching
-
Dual-Mode Operation:
- OpenAI Mode: Converts Claude requests to OpenAI format, processes, converts back
- Anthropic Mode: Direct passthrough for Anthropic-compatible APIs without conversion
- Mode is automatically selected based on provider's
api_formatconfiguration
-
Middleware System:
- Elegant chain-of-responsibility pattern for request/response processing
src/middleware/base.py- Base middleware interface and MiddlewareChainsrc/middleware/thought_signature.py- Google Gemini thought signature persistencesrc/api/middleware_integration.py- Integration layer for API endpoints- Middleware operates transparently on both streaming and non-streaming responses
- Activated per-provider based on configuration (e.g.,
GEMINI_THOUGHT_SIGNATURES_ENABLED)
-
Provider Management:
- Support for multiple LLM providers (OpenAI, Anthropic, Azure, Google Gemini, custom endpoints)
- Each provider can be configured as
api_format=openaiorapi_format=anthropic - Provider selection via model prefix:
provider:model_name(e.g.,anthropic:claude-3-sonnet) - Falls back to Default Target if no prefix specified
- Providers auto-discovered from environment variables (
{PROVIDER}_API_KEY) - Special defaults: OpenAI and Poe providers have default BASE_URLs if not specified
-
Authentication & Security:
- Proxy Authentication: Optional client API key validation at the proxy via
PROXY_API_KEYenvironment variable- This controls access TO the proxy itself, not to external providers
- If
PROXY_API_KEYis set, clients must provide this exact key to use the proxy - If not set, the proxy accepts all requests (open access)
- Provider Authentication: Each provider has its own API key (e.g.,
OPENAI_API_KEY,ANTHROPIC_API_KEYfor provider)- These are separate from proxy authentication
- Used to authenticate with the actual LLM providers
- Multi-API Key Support: Configure multiple keys per provider with automatic round-robin rotation
- Automatic Failover: Keys rotate on authentication failures (401/403/429)
- Thread-Safe Operation: Process-global rotation state with asyncio locks
- Proxy Authentication: Optional client API key validation at the proxy via
-
Configuration:
src/core/config.py- Central configuration managementsrc/core/provider_config.py- Per-provider configuration managementsrc/config/defaults.toml- Default Target configurations and fallback model aliasessrc/core/alias_config.py- TOML-based configuration loader for hierarchical alias system- Environment variables loaded from
.envfile viapython-dotenv - Custom headers support via
CUSTOM_HEADER_*environment variables (auto-converted to HTTP headers) - Configuration hierarchy: Environment vars > ./vandamme-config.toml > ~/.config/vandamme-proxy/vandamme-config.toml > defaults.toml
-
Data Models:
src/models/claude.py- Pydantic models for Claude API formatsrc/models/openai.py- Pydantic models for OpenAI API format
The converter handles:
- System messages: Converts Claude's system parameter to OpenAI system role messages
- User/Assistant messages: Direct role mapping with content transformation
- Tool use: Converts Claude's tool_use blocks to OpenAI function calling format
- Tool results: Converts Claude's tool_result blocks to OpenAI tool messages
- Images: Converts base64-encoded images in content blocks
- Streaming: Full Server-Sent Events (SSE) support with cancellation handling
The proxy passes Claude model names through unchanged unless there is a configured alias that matches the model.
Environment variables prefixed with CUSTOM_HEADER_ are automatically converted to HTTP headers:
CUSTOM_HEADER_ACCEPT→ACCEPTheaderCUSTOM_HEADER_X_API_KEY→X-API-KEYheader- Underscores in env var names become hyphens in header names
start_proxy.py- Entry point script (legacy, use vdm CLI instead)src/main.py- FastAPI app initializationsrc/cli/main.py- Main CLI entry point for vdm commandsrc/cli/commands/- CLI command implementationssrc/api/endpoints.py- Main API endpointssrc/core/config.py- Configuration management (83 lines)src/core/alias_manager.py- Model alias management with case-insensitive substring matchingsrc/core/alias_config.py- TOML configuration loader for hierarchical alias systemsrc/config/defaults.toml- Default Target configurations and fallback aliasessrc/conversion/request_converter.py- Claude→OpenAI request conversionsrc/conversion/response_converter.py- OpenAI→Claude response conversion
Required (at least one provider):
{PROVIDER}_API_KEY- API key(s) for any configured provider (e.g.,POE_API_KEY,AZURE_API_KEY)- Supports single key:
OPENAI_API_KEY=sk-... - Supports multiple keys:
OPENAI_API_KEY="sk-key1 sk-key2 sk-key3"(round-robin rotation)
- Supports single key:
Provider Configuration:
{PROVIDER}_API_FORMAT- API format: "openai" (default) or "anthropic"{PROVIDER}_BASE_URL- Base URL for the providerVDM_DEFAULT_TARGET- Default Target to use (overrides defaults.toml)
Model Aliases:
{PROVIDER}_ALIAS_{NAME}- Provider-specific model alias (e.g.,POE_ALIAS_HAIKU=gpt-4o-mini)- Takes precedence over TOML configuration files
Vandamme Proxy supports OAuth 2.0 authentication for providers like ChatGPT, allowing you to use your ChatGPT Plus/Pro subscription instead of API key billing.
OAuth Configuration:
{PROVIDER}_AUTH_MODE- Authentication mode: "api_key" (default), "passthrough", or "oauth"- Alternatively, use
{PROVIDER}_API_KEY=!OAUTHsentinel value
OAuth CLI Commands:
vdm oauth login <provider>- Authenticate with a provider using OAuthvdm oauth status <provider>- Check OAuth authentication statusvdm oauth logout <provider>- Remove stored OAuth tokens
Token Storage:
- OAuth tokens are stored in
~/.vandamme/oauth/{provider}/auth.json - File permissions are set to 0600 (read/write for owner only)
- Tokens are automatically refreshed when expired
Examples:
# OpenAI provider (default format) - single key
OPENAI_API_KEY=sk-...
# OpenAI provider with multiple keys for load balancing and failover
OPENAI_API_KEY="sk-key1 sk-key2 sk-key3"
# Anthropic provider (direct passthrough)
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_BASE_URL=https://api.anthropic.com
# Multiple Anthropic keys with automatic rotation on failures
ANTHROPIC_API_KEY="sk-ant-primary sk-ant-secondary sk-ant-backup"
ANTHROPIC_API_FORMAT=anthropic
# AWS Bedrock (Anthropic-compatible)
BEDROCK_API_KEY=...
BEDROCK_BASE_URL=https://bedrock-runtime.us-east-1.amazonaws.com
BEDROCK_API_FORMAT=anthropic
# Azure OpenAI
AZURE_API_KEY=...
AZURE_BASE_URL=https://your-resource.openai.azure.com
AZURE_API_FORMAT=openai
AZURE_API_VERSION=2024-02-15-preview
# Model Aliases (override TOML defaults)
POE_ALIAS_HAIKU=my-custom-haiku-model
OPENAI_ALIAS_FAST=gpt-4o
# ChatGPT provider with OAuth authentication
CHATGPT_AUTH_MODE=oauth
CHATGPT_BASE_URL=https://api.openai.com/v1
# Cursor Agent provider (via agent-cli-to-api bridge)
# Option A: Use --bridge flag (recommended) — auto-starts bridge and sets env vars
vdm server start --bridge cursor
# Option B: Manual setup — set env vars yourself
CURSOR_API_KEY=bridge # Dummy key to trigger auto-discovery
VDM_DEFAULT_TARGET=cursor # Set cursor as default providerSecurity (Proxy Authentication):
ANTHROPIC_API_KEY- Optional proxy authentication key- If set, clients must provide this exact key to access the proxy
- This is NOT related to any external provider's API key
- This controls access TO the proxy, not access to provider APIs
- Example: Set this to require a specific API key from Claude Code CLI users
API Configuration:
OPENAI_BASE_URL- API base URL (default: https://api.openai.com/v1)AZURE_API_VERSION- For Azure OpenAI deployments
Server Settings:
HOST- Server host (default: 0.0.0.0)PORT- Server port (default: 8082)LOG_LEVEL- Logging level (default: INFO)
Performance:
MAX_TOKENS_LIMIT- Maximum tokens (default: 4096)MIN_TOKENS_LIMIT- Minimum tokens (default: 100)REQUEST_TIMEOUT- Request timeout in seconds for non-streaming requests- Precedence: env var → provider TOML →
[defaults]→ error - Set to 0 to disable timeout (no wait limit)
- Default in
[defaults]section: 90
- Precedence: env var → provider TOML →
STREAMING_READ_TIMEOUT_SECONDS- Read timeout for streaming SSE requests (default: None/disabled)- Set to a high value (e.g., 600) to allow long-running streaming responses
- If unset, streaming reads have no timeout (recommended for SSE)
STREAMING_CONNECT_TIMEOUT_SECONDS- Connect timeout for streaming requests (default: 30)MAX_RETRIES- Retry attempts- Precedence: env var → provider TOML →
[defaults]→ error - Set to 0 to disable retries (fail immediately)
- Default in
[defaults]section: 2
- Precedence: env var → provider TOML →
Middleware Configuration:
GEMINI_THOUGHT_SIGNATURES_ENABLED- Enable thought signature middleware for Google Gemini (default: true)THOUGHT_SIGNATURE_MAX_CACHE_SIZE- Maximum cache entries (default: 10000)THOUGHT_SIGNATURE_CACHE_TTL- Cache TTL in seconds (default: 3600)THOUGHT_SIGNATURE_CLEANUP_INTERVAL- Cleanup interval in seconds (default: 300)
# Start proxy
vdm server start
# Use Claude Code with proxy (if ANTHROPIC_API_KEY not set in proxy)
ANTHROPIC_BASE_URL=http://localhost:8082 ANTHROPIC_API_KEY= claude
# Use Claude Code with proxy (if ANTHROPIC_API_KEY is set in proxy)
ANTHROPIC_BASE_URL=http://localhost:8082 ANTHROPIC_API_KEY="exact-matching-key" claudeFor production deployments with high availability:
# Configure multiple keys per provider
export OPENAI_API_KEY="sk-prod-key1 sk-prod-key2 sk-backup"
export ANTHROPIC_API_KEY="sk-ant-primary sk-ant-secondary"
export POE_API_KEY="poe-key-1 poe-key-2 poe-key-3"
# Keys automatically rotate in round-robin order
# Failed keys (401/403/429) are skipped with immediate failover
# Start with high availability
vdm server start# Enable debug logging to see key rotation
LOG_LEVEL=DEBUG vdm server start
# Logs show:
# - API key hashes (first 8 characters)
# - Which key was used for each request
# - When rotation occurs
# - Authentication failure details# Configure for direct Anthropic API access
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_BASE_URL=https://api.anthropic.com
ANTHROPIC_API_FORMAT=anthropic
VDM_DEFAULT_TARGET=anthropic# Configure for AWS Bedrock with Claude models
BEDROCK_API_KEY=your-aws-key
BEDROCK_BASE_URL=https://bedrock-runtime.us-east-1.amazonaws.com
BEDROCK_API_FORMAT=anthropic
VDM_DEFAULT_TARGET=bedrock
# Use with specific model
ANTHROPIC_BASE_URL=http://localhost:8082 claude --model bedrock:anthropic.claude-3-sonnet-20240229-v1:0# Configure for Google Vertex AI (Anthropic models)
VERTEX_API_KEY=your-vertex-key
VERTEX_BASE_URL=https://generativelanguage.googleapis.com/v1beta
VERTEX_API_FORMAT=anthropic
VDM_DEFAULT_TARGET=vertexOAuth authentication allows you to use your ChatGPT Plus/Pro subscription instead of purchasing API keys.
# 1. Configure ChatGPT provider for OAuth
export CHATGPT_AUTH_MODE=oauth
export CHATGPT_BASE_URL=https://api.openai.com/v1
export VDM_DEFAULT_TARGET=chatgpt
# 2. Authenticate with ChatGPT (opens browser)
vdm oauth login chatgpt
# 3. Check authentication status
vdm oauth status chatgpt
# 4. Start the proxy
vdm server start
# 5. Use with Claude Code CLI
ANTHROPIC_BASE_URL=http://localhost:8082 claude "Hello, world!"OAuth vs API Key Authentication:
| Feature | OAuth | API Key |
|---|---|---|
| Billing | ChatGPT subscription | Per-usage billing |
| Setup | Browser auth flow | Copy/paste key |
| Token refresh | Automatic | Manual |
| Rate limits | Subscription tier | API tier |
| Use case | Personal/development | Production |
Troubleshooting OAuth:
# If authentication fails, clear tokens and retry
vdm oauth logout chatgpt
vdm oauth login chatgpt
# Check stored tokens location
ls -la ~/.vandamme/oauth/chatgpt/
# Verify file permissions (should be 0600)
stat ~/.vandamme/oauth/chatgpt/auth.jsonThe Cursor Agent provider exposes your Cursor subscription-backed models
through vandamme-proxy via the agent-cli-to-api bridge. No code changes
needed — it works as a standard OpenAI provider.
Warning: This uses the
cursor-agentCLI session tokens. Review Cursor's Terms of Service before using in production. The bridge is a third-party project and may break if Cursor changes their backend.
Prerequisites:
- Install the Cursor agent CLI:
curl https://cursor.com/install -fsS | bash - Authenticate:
agent login - Install the bridge:
uv tool install git+https://github.com/leeguooooo/agent-cli-to-api
Setup (one command with --bridge):
# The --bridge flag auto-starts the bridge and sets CURSOR_API_KEY
vdm server start --bridge cursorSetup (manual, if you prefer separate terminals):
# 1. Start the bridge in a separate terminal
agent-cli-to-api cursor-agent --host 127.0.0.1 --port 8766
# 2. Set env vars for the proxy
export CURSOR_API_KEY="bridge" # Dummy key to trigger auto-discovery
export VDM_DEFAULT_TARGET=cursor
# 3. Start the proxy
vdm server startUsage with Claude Code:
# Streaming (primary path — non-streaming times out)
ANTHROPIC_BASE_URL=http://localhost:8082 ANTHROPIC_API_KEY=bridge claude --model cursor:autoKnown Limitations:
- Streaming only — non-streaming requests time out (cursor-agent backend buffers output)
- Model names — use
auto(default routing) or cursor-agent model names
Model aliases provide flexible model selection with case-insensitive substring matching and intelligent fallbacks.
For the complete model resolution pipeline (how bare names, profiles, and aliases interact), see Model Resolution Guide.
-
Environment Variables (highest priority):
# Provider-specific aliases POE_ALIAS_HAIKU=gpt-4o-mini OPENAI_ALIAS_FAST=gpt-4o-mini ANTHROPIC_ALIAS_CHAT=claude-3-5-sonnet-20241022 -
TOML Configuration Files (fallback defaults):
./vandamme-config.toml- Project-specific overrides~/.config/vandamme-proxy/vandamme-config.toml- User preferencessrc/config/defaults.toml- Built-in package defaults
# vandamme-config.toml example # Provider-specific aliases (override defaults.aliases) [poe.aliases] haiku = "my-custom-haiku" sonnet = "my-preferred-sonnet" # Global fallback aliases (used when provider has no alias) [defaults.aliases] common = "openai:gpt-4o" haiku = "fallback-haiku-model"
Precedence order:
{PROVIDER}_ALIAS_{NAME} env → [provider.aliases] → [defaults.aliases] → not found
New syntax (recommended):
[profiles.main]
timeout = 100
[profiles.main.aliases]
haiku = "zai:haiku"Legacy syntax (deprecated, will be removed):
["#main"]
timeout = 100
["#main".aliases]
haiku = "zai:haiku"Migration notes:
- Both syntaxes work during the deprecation period
- New syntax takes precedence when both exist for the same profile
- Legacy syntax emits a deprecation warning in logs
- Migrate your configurations to
[profiles.name]syntax
The proxy automatically provides sensible defaults for common model names:
| Alias | Poe Provider | OpenAI Provider | Anthropic Provider |
|---|---|---|---|
| haiku | gpt-5.1-mini | gpt-5.1-mini | claude-3-5-haiku-20241022 |
| sonnet | gpt-5.1-codex-mini | gpt-5.1-codex | claude-3-5-sonnet-20241022 |
| opus | gpt-5.1-codex-max | gpt-5.2 | claude-3-opus-20240229 |
The proxy provides a top-models feature to answer “what models should I use now?”:
- Fetches curated recommendations from OpenRouter
- Caches results locally for performance
- Provides suggested aliases (
top,top-cheap,top-longctx) - Exposes recommendations via API and CLI
# View curated models (API)
curl "http://localhost:8082/top-models?limit=5"
# View with CLI (Rich table + suggested aliases)
vdm models top
# See suggested aliases alongside your configured ones
curl http://localhost:8082/v1/aliases | jq '.suggested'
# Force refresh bypassing cache
vdm models top --refresh
curl "http://localhost:8082/top-models?refresh=true"- API:
GET /top-models(proxy metadata, not under/v1) - CLI:
vdm models top [--limit N] [--refresh] [--provider X] [--json] - Suggested aliases appear as non-mutating overlay in
/v1/aliases
See Top Models Documentation for full details.
# List all configured aliases (including fallbacks)
curl http://localhost:8082/v1/aliases
# Use aliases in requests
curl -X POST http://localhost:8082/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "haiku",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hello!"}]
}'
# Substring matching works too
curl -X POST http://localhost:8082/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "my-custom-haiku-model",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hello!"}]
}'# Configure aliases (provider-specific)
export POE_ALIAS_HAIKU=gpt-4o-mini
export OPENAI_ALIAS_FAST=gpt-4o-mini
# Use aliases with Claude Code
ANTHROPIC_BASE_URL=http://localhost:8082 claude --model haiku "Quick response"
ANTHROPIC_BASE_URL=http://localhost:8082 claude --model fast "Process this quickly"
# Or rely on fallback defaults (no config needed!)
export POE_API_KEY=your-key
ANTHROPIC_BASE_URL=http://localhost:8082 claude --model sonnet "Uses gpt-5.1-codex-mini fallback"You can specify which provider to use per request:
-
Default Target: Uses the configured
VDM_DEFAULT_TARGET# Uses Default Target claude --model claude-3-5-sonnet-20241022 -
Provider Prefix: Specify provider in model name
# Use specific provider claude --model anthropic:claude-3-5-sonnet-20241022 claude --model openai:gpt-4o claude --model bedrock:anthropic.claude-3-sonnet-20240229-v1:0 -
Environment Override: Override Default Target temporarily
# Temporarily use different provider VDM_DEFAULT_TARGET=anthropic claude
When a profile name matches a provider name (case-insensitive), the profile takes precedence. This is intentional behavior that allows custom provider overrides.
How it works:
- Use
[profiles.name]syntax to define profiles - Profile resolution happens BEFORE provider resolution
- Request
name:modelchecks ifnameis a profile first
Example:
# vandamme-config.toml
[profiles.openai]
timeout = 120
max-retries = 5
[profiles.openai.aliases]
haiku = "anthropic:claude-3-5-haiku-20241022" # Override to use AnthropicWith this profile:
openai:haiku→ uses profile's alias →anthropic:claude-3-5-haiku-20241022- Direct
openaiprovider access still works viaprovider:openai:modelsyntax if needed
Detection: The proxy logs a message when profile names collide with provider names (INFO level).
# Health check
curl http://localhost:8082/health
# Test OpenAI connectivity
curl http://localhost:8082/test-connection
# Test message endpoint
curl -X POST http://localhost:8082/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: your-key" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hello!"}]
}'- Set
LOG_LEVEL=DEBUGto see detailed request/response conversions and middleware operations - The proxy also logs a hint in debug mode when a model alias was resolved, pointing to the
vdm debug model-resolutioncommand for a full trace
The vdm debug model-resolution command traces every phase of the model resolution pipeline:
# Trace how a model name resolves through profiles, aliases, and providers
vdm debug model-resolution haiku
# JSON output for scripting
vdm debug model-resolution "openai:gpt-4o" --json | jq '.final_provider'
# Debug a profile alias
vdm debug model-resolution "top:haiku"The command shows each resolution phase (profile prefix detection, default profile, profile alias lookup, AliasManager resolution, provider prefix parsing) with input, output, and the reason for each match or skip.
- HTTP client noise (OpenAI/httpx/httpcore request traces) is intentionally downgraded to DEBUG; raise the global log level to DEBUG if you need to inspect raw HTTP calls
- Check
src/core/logging.pyfor logging configuration - Request/response conversion is logged in
request_converter.py - Middleware chain execution logged in
src/middleware/base.py - Thought signature operations logged in
src/middleware/thought_signature.py
The proxy implements elegant error handling for streaming responses to prevent "response already started" errors:
For Streaming Requests (SSE):
- Upstream errors (timeouts, HTTP errors) are converted to SSE error events instead of raising HTTPException
- Clients receive a structured error payload in the stream:
{"error": {"message": "...", "type": "upstream_timeout", "code": "read_timeout", "suggestion": "..."}} - The stream then terminates with
data: [DONE]\n\n - Warning-level logs include request_id, provider, and upstream details
For Non-Streaming Requests:
- Timeout errors are mapped to HTTP 504 Gateway Timeout
- Other errors preserve their original HTTP status codes
Streaming Timeout Configuration:
STREAMING_READ_TIMEOUT_SECONDScontrols how long to wait for SSE data- Recommended: Leave unset (None) for unlimited read timeout on streaming
- Set explicitly if you want to enforce a timeout on long-running streams
STREAMING_CONNECT_TIMEOUT_SECONDS(default: 30s) bounds initial connection time
This design ensures that even when upstream timeouts occur during streaming:
- Server logs show clean warning messages (not RuntimeError stack traces)
- Clients receive a proper error event in the SSE stream
- Metrics are finalized correctly via
with_streaming_error_handling
- The proxy uses async/await throughout for high concurrency
- Connection pooling is managed by OpenAI/Anthropic clients
- Streaming responses support client disconnection/cancellation
- Token counting endpoint uses character-based estimation (4 chars ≈ 1 token)
- Error responses are classified and converted to Claude API format
- Middleware system is transparent to both streaming and non-streaming flows
- Google Gemini thought signatures are automatically handled when enabled (required for multi-turn function calling)