This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@~/.claude/standards-python-extended.md
CRITICAL: The project name is ccproxy (lowercase). Do NOT refer to the project as "CCProxy". The PascalCase form is used exclusively for class names (e.g., CCProxyHandler, CCProxyConfig).
ccproxy is a command-line tool that intercepts and routes Claude Code's requests to different LLM providers via a LiteLLM proxy server. It enables intelligent request routing based on token count, model type, tool usage, or custom rules.
# Run all tests with coverage
uv run pytest
# Run specific test file
uv run pytest tests/test_classifier.py
# Run tests matching pattern
uv run pytest -k "test_token_count"
# Run with verbose output
uv run pytest -v# Format code with ruff
uv run ruff format .
# Check linting issues
uv run ruff check .
# Fix linting issues automatically
uv run ruff check --fix .
# Type checking with mypy
uv run mypy src/ccproxy# Install with dev dependencies
uv sync --dev
# Install as a tool globally
uv tool install .
# Run the module directly
uv run python -m ccproxy# Install configuration files
ccproxy install [--force]
# Start/stop proxy server
ccproxy start [--detach]
ccproxy stop
ccproxy restart [--detach]
# View logs and status
ccproxy logs [-f] [-n LINES]
ccproxy status [--json]
# Run command with proxy environment
ccproxy run <command> [args...]The codebase follows a modular architecture with clear separation of concerns:
Request → CCProxyHandler → Hook Pipeline → Response
↓
RequestClassifier (rule evaluation)
↓
ModelRouter (model lookup)
- CCProxyHandler (
handler.py) - LiteLLM CustomLogger that intercepts all requests - RequestClassifier (
classifier.py) - Evaluates rules in order (first match wins) - ModelRouter (
router.py) - Maps rule names to actual model configurations - Hook Pipeline - Sequential execution of configured hooks with error isolation
- handler.py: Main entry point as a LiteLLM CustomLogger. Orchestrates the classification and routing process via
async_pre_call_hook(). - classifier.py: Rule-based classification system that evaluates rules in order to determine routing.
- rules.py: Defines
ClassificationRuleabstract base class and built-in rules:ThinkingRule- Matches requests with "thinking" fieldMatchModelRule- Matches by model name substringMatchToolRule- Matches by tool name in requestTokenCountRule- Evaluates based on token count threshold
- router.py: Manages model configurations from LiteLLM proxy server. Lazy-loads models on first request.
- config.py: Configuration management using Pydantic with multi-level discovery (env var → LiteLLM runtime → ~/.ccproxy/).
- hooks.py: Built-in hooks that process requests. Hooks support optional params via
hook:+params:YAML format (seeHookConfigclass in config.py):rule_evaluator- Evaluates rules and stores routing decisionmodel_router- Routes to appropriate modelforward_oauth- Forwards OAuth tokens to provider APIsextract_session_id- Extracts session identifierscapture_headers- Captures HTTP headers with sensitive redaction (supportsheadersparam)forward_apikey- Forwards x-api-key header
- cli.py: Tyro-based CLI interface (~900 lines) for managing the proxy server.
- utils.py: Template discovery and debug utilities (
dt(),dv(),d(),p()).
Rules are evaluated in the order configured in ccproxy.yaml. Each rule:
- Inherits from
ClassificationRuleabstract base class - Implements
evaluate(request: dict, config: CCProxyConfig) -> bool - Returns the first matching rule's name as the routing label
# Example rule configuration in ccproxy.yaml
rules:
- name: thinking_model
rule: ccproxy.rules.ThinkingRule
- name: haiku_requests
rule: ccproxy.rules.MatchModelRule
params:
- model_name: "haiku"
- name: large_context
rule: ccproxy.rules.TokenCountRule
params:
- threshold: 60000Custom rules can be created by implementing the ClassificationRule interface and specifying the Python import path in the configuration.
~/.ccproxy/config.yaml- LiteLLM proxy configuration with model definitions~/.ccproxy/ccproxy.yaml- ccproxy-specific configuration (rules, hooks, debug settings, handler path)~/.ccproxy/ccproxy.py- Auto-generated handler file (created onccproxy startbased onhandlerconfig)
Config Discovery Precedence:
CCPROXY_CONFIG_DIRenvironment variable- LiteLLM proxy runtime directory (auto-detected)
~/.ccproxy/(default fallback)
The test suite uses pytest with comprehensive fixtures (18 test files, 90% coverage minimum):
mock_proxy_serverfixture for mocking LiteLLM proxycleanupfixture ensures singleton instances are cleared between tests- Tests organized to mirror source structure (
test_<module>.py) - Parametrized tests for rule evaluation scenarios
- Integration tests verify end-to-end behavior
- Singleton patterns:
CCProxyConfigandModelRouteruse thread-safe singletons. Useclear_config_instance()andclear_router()to reset state in tests. - Token counting: Uses tiktoken with fallback to character-based estimation for non-OpenAI models.
- OAuth token forwarding: Handled specially for Claude CLI requests. Supports custom User-Agent per provider.
- Request metadata: Stored by
litellm_call_idwith 60-second TTL auto-cleanup (LiteLLM doesn't preserve custom metadata). - Hook error isolation: Errors in one hook don't block others from executing.
- Lazy model loading: Models loaded from LiteLLM proxy on first request, not at startup.
Key dependencies include:
- litellm[proxy] - Core proxy functionality
- pydantic/pydantic-settings - Configuration and validation
- tyro - CLI interface generation
- tiktoken - Token counting
- anthropic - Anthropic API client
- rich - Terminal output formatting
- langfuse - Observability integration
- prisma - Database ORM
- structlog - Structured logging
ccproxy must be installed with litellm in the same environment so that LiteLLM can import the ccproxy handler:
# Install in editable mode with litellm bundled
uv tool install --editable . --with 'litellm[proxy]' --forceWith editable mode, source changes are reflected immediately. Just restart the proxy:
# Restart proxy to regenerate handler and pick up changes
ccproxy stop
ccproxy start --detach
# Verify
ccproxy status
# Run tests
uv run pytestLiteLLM imports ccproxy.handler:CCProxyHandler at runtime from the auto-generated ~/.ccproxy/ccproxy.py file. Both must be in the same Python environment:
uv tool install ccproxy→ isolated envuv tool install litellm→ different isolated env
Solution: Install together so they share the same environment.
The handler file is automatically regenerated on every ccproxy start based on the handler configuration in ccproxy.yaml.