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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ SMALL_MODEL="gpt-4o-mini"
HOST="0.0.0.0"
PORT="8082"
LOG_LEVEL="INFO"
SSL_VERIFY=false
# DEBUG, INFO, WARNING, ERROR, CRITICAL

# Optional: Performance settings
Expand Down Expand Up @@ -48,4 +49,4 @@ MAX_RETRIES="2"
# SMALL_MODEL="llama3.1:8b"

# Note: If you get "unsupported_country_region_territory" errors,
# consider using Azure OpenAI or a local model setup instead.
# consider using Azure OpenAI or a local model setup instead.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ The application automatically loads environment variables from a `.env` file in
- `MAX_TOKENS_LIMIT` - Token limit (default: `4096`)
- `REQUEST_TIMEOUT` - Request timeout in seconds (default: `90`)

**SSL Configuration:**

- `SSL_VERIFY` - Enable/disable SSL certificate verification (default: `true`)
- `CA_BUNDLE_PATH` - Path to custom CA bundle for SSL verification

### Model Mapping

The proxy maps Claude model requests to your configured models:
Expand Down Expand Up @@ -159,6 +164,22 @@ response = httpx.post(
)
```

## SSL Certificate Issues

If you encounter SSL certificate errors like `CERTIFICATE_VERIFY_FAILED`, you have two options:

1. **Disable SSL verification** (not recommended for production):
```bash
SSL_VERIFY=false python start_proxy.py
```

2. **Use a custom CA bundle**:
```bash
CA_BUNDLE_PATH=/path/to/your/certificate.pem python start_proxy.py
```

Note: Disabling SSL verification makes your connection less secure and should only be used in trusted environments or for testing purposes.

## Integration with Claude Code

This proxy is designed to work seamlessly with Claude Code CLI:
Expand Down
18 changes: 18 additions & 0 deletions src/api/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ async def test_connection():
)


@router.get("/ssl-config")
async def ssl_config():
"""SSL configuration endpoint"""
import os
ssl_verify = os.environ.get("SSL_VERIFY", "true")
ca_bundle_path = os.environ.get("CA_BUNDLE_PATH")

return {
"ssl_verify": ssl_verify,
"ca_bundle_path": ca_bundle_path,
"ssl_verify_bool": ssl_verify.lower() == "true"
}


@router.get("/")


@router.get("/")
async def root():
"""Root endpoint"""
Expand All @@ -226,5 +243,6 @@ async def root():
"count_tokens": "/v1/messages/count_tokens",
"health": "/health",
"test_connection": "/test-connection",
"ssl_config": "/ssl-config",
},
}
31 changes: 29 additions & 2 deletions src/core/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import asyncio
import json
import os
import ssl
from fastapi import HTTPException
from typing import Optional, AsyncGenerator, Dict, Any
from openai import AsyncOpenAI, AsyncAzureOpenAI
Expand All @@ -13,19 +15,44 @@ def __init__(self, api_key: str, base_url: str, timeout: int = 90, api_version:
self.api_key = api_key
self.base_url = base_url

# Check for SSL configuration environment variables
ssl_verify = os.environ.get("SSL_VERIFY", "true").lower() == "true"
ca_bundle_path = os.environ.get("CA_BUNDLE_PATH")

# Configure SSL context if needed
http_client = None
if not ssl_verify or ca_bundle_path:
import httpx

# Create custom SSL context
ssl_context = ssl.create_default_context()

if not ssl_verify:
# Disable SSL verification
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
elif ca_bundle_path:
# Use custom CA bundle
ssl_context.load_verify_locations(cafile=ca_bundle_path)

# Create custom HTTP client with our SSL context
http_client = httpx.AsyncClient(verify=ssl_context)

# Detect if using Azure and instantiate the appropriate client
if api_version:
self.client = AsyncAzureOpenAI(
api_key=api_key,
azure_endpoint=base_url,
api_version=api_version,
timeout=timeout
timeout=timeout,
http_client=http_client
)
else:
self.client = AsyncOpenAI(
api_key=api_key,
base_url=base_url,
timeout=timeout
timeout=timeout,
http_client=http_client
)
self.active_requests: Dict[str, asyncio.Event] = {}

Expand Down
11 changes: 11 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def main():
print(f" MAX_TOKENS_LIMIT - Token limit (default: 4096)")
print(f" MIN_TOKENS_LIMIT - Minimum token limit (default: 100)")
print(f" REQUEST_TIMEOUT - Request timeout in seconds (default: 90)")
print(f" SSL_VERIFY - Enable/disable SSL certificate verification (default: true)")
print(f" CA_BUNDLE_PATH - Path to custom CA bundle for SSL verification")
print("")
print("Model mapping:")
print(f" Claude haiku models -> {config.small_model}")
Expand All @@ -50,6 +52,15 @@ def main():
print(f" Request Timeout: {config.request_timeout}s")
print(f" Server: {config.host}:{config.port}")
print(f" Client API Key Validation: {'Enabled' if config.anthropic_api_key else 'Disabled'}")

# SSL configuration
import os
ssl_verify = os.environ.get("SSL_VERIFY", "true")
ca_bundle_path = os.environ.get("CA_BUNDLE_PATH")
if ssl_verify.lower() == "false":
print(" SSL Verification: Disabled")
elif ca_bundle_path:
print(f" CA Bundle Path: {ca_bundle_path}")
print("")

# Parse log level - extract just the first word to handle comments
Expand Down
54 changes: 54 additions & 0 deletions test_ssl_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Test SSL configuration options."""

import os
import sys

# Add src to Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))

from src.core.client import OpenAIClient

def test_ssl_config():
"""Test SSL configuration options."""
print("Testing SSL configuration...")

# Test with SSL verification disabled
os.environ["SSL_VERIFY"] = "false"

try:
client = OpenAIClient(
api_key="test-key",
base_url="https://api.openai.com/v1",
timeout=30
)
print("✓ Successfully created client with SSL verification disabled")
except Exception as e:
print(f"✗ Failed to create client with SSL verification disabled: {e}")

# Test with custom CA bundle (this will fail if the file doesn't exist, but we're just testing instantiation)
os.environ["SSL_VERIFY"] = "true"
os.environ["CA_BUNDLE_PATH"] = "/tmp/fake-ca-bundle.pem"

try:
client = OpenAIClient(
api_key="test-key",
base_url="https://api.openai.com/v1",
timeout=30
)
print("✓ Successfully created client with custom CA bundle path")
except Exception as e:
# This is expected to fail since the file doesn't exist, but the client should be created
if "No such file" in str(e) or "No such process" in str(e):
print("✓ Successfully created client with custom CA bundle path (file not found is expected)")
else:
print(f"✗ Unexpected error with custom CA bundle: {e}")

# Clean up environment variables
if "SSL_VERIFY" in os.environ:
del os.environ["SSL_VERIFY"]
if "CA_BUNDLE_PATH" in os.environ:
del os.environ["CA_BUNDLE_PATH"]

if __name__ == "__main__":
test_ssl_config()