Skip to content

EricDarkly/claude-code-ld-proxy

Repository files navigation

Claude Code × LaunchDarkly Proxy

A transparent proxy that sits between Claude Code and the Anthropic API. It adds LaunchDarkly AI Config capabilities as a sidecar: model selection, metrics tracking, and automated response quality scoring. All without changing how Claude Code behaves.

How it works

Claude Code  →  proxy (localhost:9911)  →  Anthropic API
                     │
                     ├─ evaluates LD AI Config (in-memory, <1ms)
                     ├─ injects system prompt from the config
                     ├─ creates a run tracker
                     ├─ streams response through unchanged
                     ├─ parses SSE to extract tokens / stop reason / tools
                     ├─ records metrics to LD Monitoring dashboard
                     └─ fires attached judges asynchronously

Everything Claude Code sends is forwarded byte-for-byte — streaming, tool use, thinking blocks, betas, and every content type pass through unchanged. LaunchDarkly is layered on without touching the response.

What gets tracked automatically

Metric LD event key
Duration $ld:ai:duration:total
Input tokens (incl. cache) $ld:ai:tokens:input
Output tokens $ld:ai:tokens:output
Total tokens $ld:ai:tokens:total
Successful generation $ld:ai:generation:success
Failed generation $ld:ai:generation:error
Tool calls (one per tool) $ld:ai:tool_call
Judge scores configured metric key

Prerequisites

  • Python 3.13+
  • uv (recommended) or pip
  • A LaunchDarkly account with AI Configs enabled
  • An Anthropic API key (passed through from Claude Code)

LaunchDarkly setup

1. Create an AI Config

In the LaunchDarkly dashboard, go to AI Configs and create a new config.

  • Key: use model-selector or set LD_AI_CONFIG_KEY to match whatever key you choose
  • Messages: add a system role message with any instructions you want injected into every Claude Code request
  • Enable the config for your target environment

The proxy evaluates this config on every request. When disabled or when no system messages are present, requests are forwarded unchanged.

2. Attach judges (optional)

Judges are separate AI Configs with mode: judge that score each response on a 0–1 scale. Attach them to your AI Config in the LaunchDarkly UI under Judges.

Each judge:

  • Receives the full conversation (system prompt + messages) and Claude's response text
  • Returns a numeric score emitted under its configured Evaluation Metric Key
  • Runs asynchronously after the response is returned to Claude Code (no added latency)
  • Respects its configured sampling rate — set this below 1.0 to control cost

Judges can use any provider supported by the LD AI SDK (Anthropic, OpenAI). Use a separate API key or provider from Claude Code's own key to avoid competing for the same rate limits.

Setup

git clone <repo-url>
cd claude-code-ld-proxy

# Install dependencies
uv sync

# Configure environment
cp .env.example .env
# Edit .env and fill in LD_SDK_KEY and LD_AI_CONFIG_KEY

Running

uv run main.py

Then point Claude Code at the proxy instead of Anthropic directly:

ANTHROPIC_BASE_URL=http://localhost:9911 claude

Or set it permanently in your shell profile:

export ANTHROPIC_BASE_URL=http://localhost:9911

Per-user targeting

To pass a user identity for LaunchDarkly targeting rules, include the x-ld-user-key header in your requests. Claude Code doesn't set this by default, so it falls back to claude-code-local.

Configuration

Variable Required Default Description
LD_SDK_KEY Yes LaunchDarkly server-side SDK key
LD_AI_CONFIG_KEY Yes model-selector Key of the AI Config to evaluate
ANTHROPIC_API_KEY Yes* Forwarded from Claude Code automatically
OPENAI_API_KEY No Required if any judges use provider: openai
PORT No 9911 Port the proxy listens on
PROXY_UPSTREAM No https://api.anthropic.com Upstream API base URL
LOG_LEVEL No INFO DEBUG | INFO | WARNING | ERROR

*ANTHROPIC_API_KEY is forwarded automatically from the x-api-key header that Claude Code sends. You only need it in .env if you want the proxy itself to make API calls (e.g. for judges using Anthropic as the judge provider).

Logs

Each request produces two lines (three if judges are attached):

INFO     server.proxy              ▶  model=claude-opus-4-8  user=claude-code-local  stream=yes
INFO     pipeline.tracking         ◀  1243ms  in=4521  out=312  cached=801  stop=end_turn  tools=read_file,bash
INFO     pipeline.tracking         ⚖  judge=quality-judge  score=0.87  success=yes

Errors show the Anthropic error type and message:

WARNING  server.proxy              upstream 429  path=v1/messages  type=rate_limit_error  message=...

Project structure

main.py                      # Entrypoint — runs uvicorn
core/
  settings.py                # Env config and shared constants
launchdarkly/
  client.py                  # LD client init and AI Config evaluation
pipeline/
  interceptor.py             # Request mutation: injects system prompt, starts tracker
  tracking.py                # Response parsing: records metrics, runs judges
server/
  proxy.py                   # FastAPI app and catch-all proxy route

Extending

The two entry points for customization are both in pipeline/interceptor.py:

intercept(body, user_key) — runs before the request is forwarded. Mutate body here to change the model, inject additional context, or add tools. The body dict has model, system, messages, tools, and stream.

append_system(body, extra) — convenience helper that appends text to the system prompt regardless of whether it's a string or a content-block list.

Judge scoring and metrics recording are fully automatic once judges are attached to the AI Config in the LaunchDarkly UI — no code changes needed.

Health check

GET http://localhost:9911/health
→ {"status": "ok", "launchdarkly": true}

About

Transparent proxy for Claude Code that layers LaunchDarkly AI Config model selection, metrics, and judge-based quality scoring on top of the Anthropic API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages