Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
33ae035
fix: enable tool execution when enable_tools=true
aaronlippold Dec 28, 2025
5c15d9e
test: add unit tests for tool execution and fix integration test skip…
aaronlippold Dec 28, 2025
cd28bf1
feat: add Anthropic Messages API compatible endpoint (/v1/messages)
aaronlippold Dec 28, 2025
d8c6f59
feat: add CLAUDE_AUTH_METHOD env var and styled landing page
aaronlippold Dec 29, 2025
fc7e3f5
feat: improve landing page with light/dark mode, clickable endpoints,…
aaronlippold Dec 29, 2025
1534a1f
feat: add accordion endpoints with live JSON preview and syntax highl…
aaronlippold Dec 29, 2025
4733a23
fix: improve JSON syntax highlighting contrast in light mode
aaronlippold Dec 29, 2025
c702271
feat: use Shiki for VS Code-quality JSON syntax highlighting
aaronlippold Dec 29, 2025
14b19e4
fix: use Shiki for Quick Start curl example highlighting
aaronlippold Dec 29, 2025
11a7385
fix: increase code block font size and line height for readability
aaronlippold Dec 29, 2025
04a1eda
fix: make quickstart curl command single-line for easy copy-paste
aaronlippold Dec 29, 2025
983d394
feat: add copy button to quickstart command
aaronlippold Dec 29, 2025
3e4da6a
fix: add fallback copy method for clipboard support
aaronlippold Dec 29, 2025
169c155
fix: properly escape quickstartText string for JavaScript
aaronlippold Dec 29, 2025
414d1a5
test: add tests for /version endpoint and landing page
aaronlippold Dec 29, 2025
562cfac
chore: add pytest-cov for coverage reporting
aaronlippold Dec 29, 2025
83d5ace
test: add comprehensive unit tests for core modules
aaronlippold Dec 29, 2025
4109476
test: add comprehensive unit tests for tool_manager, mcp_client, clau…
aaronlippold Dec 29, 2025
4674c25
feat: migrate landing page from Tailwind CSS to PicoCSS
aaronlippold Dec 29, 2025
1d60cdd
ci: add GitHub Actions workflow for tests and linting
aaronlippold Dec 29, 2025
395bdfa
style: apply black formatting
aaronlippold Dec 29, 2025
91fa388
ci: remove Claude Code Review workflow (token issues)
aaronlippold Dec 29, 2025
be53393
Revert: restore claude-code-review.yml
aaronlippold Dec 29, 2025
d87d6c4
feat: implement Claude Code review recommendations
aaronlippold Dec 29, 2025
b20904a
fix: add nosec comments for intentional 0.0.0.0 binding
aaronlippold Dec 29, 2025
7b8f770
docs: document CLAUDE_WRAPPER_HOST and MAX_REQUEST_SIZE env vars
aaronlippold Dec 29, 2025
01fd35f
docs: add usage examples to constants.py docstring
aaronlippold Dec 29, 2025
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
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
# Claude CLI Configuration
CLAUDE_CLI_PATH=claude

# Authentication Method (optional - explicit selection)
# Set this to override auto-detection. Values: cli, api_key, bedrock, vertex
# If not set, auto-detects based on available env vars (ANTHROPIC_API_KEY, etc.)
# CLAUDE_AUTH_METHOD=cli

# API Configuration
# If API_KEY is not set, server will prompt for interactive API key protection on startup
# Leave commented out to enable interactive prompt, or uncomment to use a fixed API key
# API_KEY=your-optional-api-key-here

# Server Configuration
PORT=8000
# Host binding address - use 127.0.0.1 for local-only access, 0.0.0.0 for all interfaces
# CLAUDE_WRAPPER_HOST=0.0.0.0
# Maximum request body size in bytes (default: 10MB)
# MAX_REQUEST_SIZE=10485760

# Timeout Configuration (milliseconds)
MAX_TIMEOUT=600000
Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}

- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root

- name: Install project
run: poetry install --no-interaction

- name: Run linting
run: poetry run black --check src tests

- name: Type checking
run: poetry run mypy src --ignore-missing-imports
continue-on-error: true

- name: Security scan
run: poetry run bandit -r src/ -ll -x tests

- name: Dependency vulnerability scan
run: poetry run safety check || true
continue-on-error: true

- name: Run tests
run: poetry run pytest tests/ -v --cov=src --cov-report=xml --cov-report=term-missing

- name: Upload coverage to Codecov
if: matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
fail_ci_if_error: false
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,9 @@ Env vars override defaults and can be set at runtime with `-e` flags or in `dock

- **Core Server Settings**:
- `PORT=9000`: Changes the internal listening port (default: 8000; update port mapping accordingly).
- `CLAUDE_WRAPPER_HOST=127.0.0.1`: Sets the host binding address (default: 0.0.0.0 for all interfaces; use 127.0.0.1 for local-only access).
- `MAX_TIMEOUT=600`: Sets the request timeout in seconds (default: 300; increase for complex Claude queries).
- `MAX_REQUEST_SIZE=10485760`: Maximum request body size in bytes (default: 10MB; increase for large payloads).
- `CLAUDE_CWD=/path/to/workspace`: Sets Claude Code's working directory (default: isolated temp directory for security).

- **Authentication and Providers**:
Expand Down
1,040 changes: 1,034 additions & 6 deletions poetry.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ pytest = "^8.0.0"
pytest-asyncio = "^0.23.0"
requests = "^2.32.0"
openai = "^1.0.0"
pytest-cov = "^7.0.0"
mypy = "^1.14.0"
bandit = "^1.8.0"
safety = "^3.2.0"
hypothesis = "^6.122.0"

[build-system]
requires = ["poetry-core"]
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Claude Code OpenAI Wrapper - A FastAPI-based OpenAI-compatible API for Claude Code."""

__version__ = "1.0.0"
__version__ = "2.1.0"
29 changes: 28 additions & 1 deletion src/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,34 @@ def get_api_key(self):
return self.env_api_key

def _detect_auth_method(self) -> str:
"""Detect which Claude Code authentication method is configured."""
"""Detect which Claude Code authentication method is configured.

Priority:
1. Explicit CLAUDE_AUTH_METHOD env var (cli, api_key, bedrock, vertex)
2. Legacy env vars (CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX)
3. Auto-detect based on ANTHROPIC_API_KEY presence
4. Default to claude_cli
"""
# Check for explicit auth method first
explicit_method = os.getenv("CLAUDE_AUTH_METHOD", "").lower()
if explicit_method:
method_map = {
"cli": "claude_cli",
"claude_cli": "claude_cli",
"api_key": "anthropic",
"anthropic": "anthropic",
"bedrock": "bedrock",
"vertex": "vertex",
}
if explicit_method in method_map:
logger.info(f"Using explicit auth method: {method_map[explicit_method]}")
return method_map[explicit_method]
else:
logger.warning(
f"Unknown CLAUDE_AUTH_METHOD '{explicit_method}', falling back to auto-detect"
)

# Fall back to legacy env vars and auto-detection
if os.getenv("CLAUDE_CODE_USE_BEDROCK") == "1":
return "bedrock"
elif os.getenv("CLAUDE_CODE_USE_VERTEX") == "1":
Expand Down
27 changes: 22 additions & 5 deletions src/claude_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ async def run_completion(
disallowed_tools: Optional[List[str]] = None,
session_id: Optional[str] = None,
continue_session: bool = False,
permission_mode: Optional[str] = None,
) -> AsyncGenerator[Dict[str, Any], None]:
"""Run Claude Agent using the Python SDK and yield response chunks."""

Expand Down Expand Up @@ -136,6 +137,10 @@ async def run_completion(
if disallowed_tools:
options.disallowed_tools = disallowed_tools

# Set permission mode (needed for tool execution in API context)
if permission_mode:
options.permission_mode = permission_mode

# Handle session continuity
if continue_session:
options.continue_session = True
Expand Down Expand Up @@ -188,7 +193,18 @@ async def run_completion(
}

def parse_claude_message(self, messages: List[Dict[str, Any]]) -> Optional[str]:
"""Extract the assistant message from Claude Agent SDK messages."""
"""Extract the assistant message from Claude Agent SDK messages.

Prioritizes ResultMessage.result for multi-turn conversations,
falls back to last AssistantMessage content.
"""
# First, check for ResultMessage with 'result' field (multi-turn completion)
for message in messages:
if message.get("subtype") == "success" and "result" in message:
return message["result"]

# Collect all text from AssistantMessages (take the last one with text)
last_text = None
for message in messages:
# Look for AssistantMessage type (new SDK format)
if "content" in message and isinstance(message["content"], list):
Expand All @@ -203,7 +219,7 @@ def parse_claude_message(self, messages: List[Dict[str, Any]]) -> Optional[str]:
text_parts.append(block)

if text_parts:
return "\n".join(text_parts)
last_text = "\n".join(text_parts)

# Fallback: look for old format
elif message.get("type") == "assistant" and "message" in message:
Expand All @@ -216,11 +232,12 @@ def parse_claude_message(self, messages: List[Dict[str, Any]]) -> Optional[str]:
for block in content:
if isinstance(block, dict) and block.get("type") == "text":
text_parts.append(block.get("text", ""))
return "\n".join(text_parts) if text_parts else None
if text_parts:
last_text = "\n".join(text_parts)
elif isinstance(content, str):
return content
last_text = content

return None
return last_text

def extract_metadata(self, messages: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Extract metadata like costs, tokens, and session info from SDK messages."""
Expand Down
23 changes: 22 additions & 1 deletion src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,33 @@
Constants and configuration for Claude Code OpenAI Wrapper.

Single source of truth for tool names, models, and other configuration values.

Usage Examples:
# Check if a model is supported
from src.constants import CLAUDE_MODELS
if model_name in CLAUDE_MODELS:
# proceed with request

# Get default allowed tools
from src.constants import DEFAULT_ALLOWED_TOOLS
options = {"allowed_tools": DEFAULT_ALLOWED_TOOLS}

# Use rate limits in FastAPI
from src.constants import RATE_LIMIT_CHAT
@limiter.limit(f"{RATE_LIMIT_CHAT}/minute")
async def chat_endpoint(): ...

Note:
- Tool configurations are managed by ToolManager (see tool_manager.py)
- Model validation uses graceful degradation (warns but allows unknown models)
- Rate limits can be overridden via environment variables
"""

import os

# Claude Agent SDK Tool Names
# These are the built-in tools available in the Claude Agent SDK
# See: https://docs.anthropic.com/en/docs/claude-code/sdk
CLAUDE_TOOLS = [
"Task", # Launch agents for complex tasks
"Bash", # Execute bash commands
Expand Down Expand Up @@ -49,7 +70,7 @@
# NOTE: Claude Agent SDK only supports Claude 4+ models, not Claude 3.x
CLAUDE_MODELS = [
# Claude 4.5 Family (Latest - Fall 2025) - RECOMMENDED
"claude-opus-4-5-20250929", # Latest Opus 4.5 - Most capable
"claude-opus-4-5-20250929", # Latest Opus 4.5 - Most capable
"claude-sonnet-4-5-20250929", # Recommended - best coding model
"claude-haiku-4-5-20251001", # Fast & cheap
# Claude 4.1
Expand Down
Loading
Loading