9.9 KiB
OpenAI-Compatible Provider Routing
CCS can route Claude Code traffic through a local Anthropic-compatible proxy when your API profile points at an OpenAI-compatible chat completions endpoint.
This is useful for providers such as:
- Hugging Face Inference Providers
- OpenRouter
- Ollama
- llama.cpp servers
- OpenAI-compatible self-hosted gateways
Related Project: claude-code-router
claude-code-router is the main external reference that informed this CCS work. Their Anthropic/OpenAI transformer design helped shape the routing approach here.
When to use CCR:
- you want a standalone router without CCS profile integration
- you do not need CCS account/runtime management around the request flow
When to use CCS:
- you already use CCS API profiles or runtime bridges
- you want the proxy flow available through
ccs <profile>andccs proxy ... - you want the routing behavior documented and tested inside the CCS workflow
What CCS Does
When you launch a compatible settings profile with the Claude target, CCS now:
- Starts a local proxy on
127.0.0.1using the resolved local port for that profile - Accepts Anthropic
/v1/messagestraffic from Claude Code - Translates requests into OpenAI chat-completions format
- Forwards them to your configured upstream provider
- Translates streaming responses back into Anthropic SSE
You do not need to rewrite your profile by hand each time.
Quick Start
Create or reuse an API profile that points at an OpenAI-compatible endpoint:
ccs api create --preset hf
Then you can use the profile directly:
ccs hf
CCS detects that the profile is OpenAI-compatible and auto-routes Claude Code through the local proxy.
Manual Proxy Lifecycle
If you want to manage the proxy explicitly:
ccs proxy start hf
eval "$(ccs proxy activate)"
ccs proxy status
ccs proxy stop
Useful variants:
ccs proxy start hf --host 127.0.0.1
ccs proxy start hf --port 3460
ccs proxy activate hf
ccs proxy activate --fish
ccs proxy status hf
ccs proxy stop hf
Port selection precedence is:
- CLI
--portfor an exact one-off pin proxy.profile_ports[profile]for an exact per-profile pinproxy.portfor a shared preferred starting port- adaptive per-profile fallback when nothing is pinned
Legacy shared proxy.port: 3456 values are treated as unset so older configs
move onto the adaptive path instead of staying on the hot legacy default. If
you need an exact 3456 binding now, pin it via --port or proxy.profile_ports.
ccs proxy activate now prints the full local runtime contract:
ANTHROPIC_BASE_URLANTHROPIC_AUTH_TOKENANTHROPIC_MODELplus tier defaults when presentDISABLE_TELEMETRYDISABLE_COST_WARNINGSAPI_TIMEOUT_MSNO_PROXY
Multiple Active Proxy Profiles
CCS now stores OpenAI-compatible proxy state per profile instead of treating the runtime as a singleton.
- Different compatible profiles can run at the same time on separate local ports
ccs proxy activatewithout a profile stays convenient when only one proxy is running- When multiple proxies are running, pass the profile explicitly to
activate,status, orstop statusandactivatealways reflect the actual running port instead of an assumed default
If you want to pin or guide ports explicitly, configure them in ~/.ccs/config.yaml:
proxy:
port: 45000
profile_ports:
hf: 3460
openai: 3461
Request-Time Routing
The proxy is no longer limited to the startup profile's default model.
Supported request-time selectors:
profile:modelExample:deepseek:deepseek-reasonerprofileExample:openrouter- plain model ids
Example:
deepseek-chat
Plain model ids use exact string equality against the configured profile model
slots (model, opusModel, sonnetModel, haikuModel). CCS does not apply
fuzzy matching or prefix matching here. If no exact match is found, the request
stays on the active profile with the requested model id unchanged.
Routing behavior:
profile:modelwins immediately.- Scenario routing may override the active profile when configured.
- Plain model ids are matched against the configured OpenAI-compatible profiles before falling back to the active profile.
This means a Claude session launched through one compatible profile can still request another compatible profile/model when the proxy can resolve it safely.
Scenario Routing
Scenario routing is now supported through proxy.routing in your CCS config.
Example ~/.ccs/config.yaml:
proxy:
routing:
default: "deepseek:deepseek-chat"
background: "ollama:qwen2.5-coder:0.5b"
think: "deepseek:deepseek-reasoner"
longContext: "openrouter:google/gemini-2.5-pro"
longContextThreshold: 60000
webSearch: "openrouter:perplexity/sonar-pro"
Current scenario detection:
background: requested model containshaikuthink: Anthropicthinkingis enabledlongContext: estimated request tokens exceedlongContextThresholdwebSearch: tool list includesweb_searchdefault: fallback selector when the above do not apply
Routing decisions are logged through CCS structured logs.
longContextThreshold uses an intentionally approximate token estimate based on
message characters, tool payload size, and a chars / 4 heuristic. Tune the
threshold conservatively if your routing decision needs a sharper cutoff near
the boundary.
How Profile Detection Works
CCS keeps these profiles in the normal API/settings-profile flow.
Anthropic-compatible endpoints such as:
https://api.anthropic.comhttps://api.z.ai/api/anthropichttps://api.deepseek.com/anthropic
continue to launch directly.
OpenAI-compatible endpoints such as:
https://router.huggingface.co/v1https://api.openai.com/v1http://localhost:11434
are routed through the local proxy for Claude-target launches.
Provider Setup
DeepSeek
Use a settings profile whose env looks like:
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/v1",
"ANTHROPIC_AUTH_TOKEN": "sk-...",
"ANTHROPIC_MODEL": "deepseek-chat",
"CCS_DROID_PROVIDER": "generic-chat-completion-api"
}
}
Typical override target:
deepseek:deepseek-reasoner
OpenRouter
{
"env": {
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api/v1",
"ANTHROPIC_AUTH_TOKEN": "sk-or-...",
"ANTHROPIC_MODEL": "openai/gpt-4.1-mini",
"CCS_DROID_PROVIDER": "generic-chat-completion-api"
}
}
Useful when you want:
- model fan-out behind one provider profile
- long-context or web-search scenario targets
Ollama / Local Gateways
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:11434",
"ANTHROPIC_AUTH_TOKEN": "ollama",
"ANTHROPIC_MODEL": "qwen3-coder",
"CCS_DROID_PROVIDER": "generic-chat-completion-api"
}
}
For self-signed HTTPS gateways, add CCS_OPENAI_PROXY_INSECURE=1.
DashScope / Qwen Compatible Mode
DashScope's compatible endpoint works even when older settings files still carry a stale Anthropic-style provider hint:
{
"env": {
"ANTHROPIC_BASE_URL": "https://dashscope-us.aliyuncs.com/compatible-mode/v1",
"ANTHROPIC_AUTH_TOKEN": "sk-...",
"ANTHROPIC_MODEL": "qwen3.6-plus",
"CCS_DROID_PROVIDER": "anthropic"
}
}
CCS now infers the OpenAI-compatible route from the base URL and does not let that stale provider hint block proxy routing.
Self-Signed TLS
If your upstream gateway uses a self-signed or privately issued certificate, set this in the profile settings JSON:
{
"env": {
"CCS_OPENAI_PROXY_INSECURE": "1"
}
}
That flag is respected by both:
ccs <profile>auto-routingccs proxy start <profile>
Supported Runtime Paths
ccs <profile>with Claude target: auto-starts the local proxy when neededccs proxy start <profile>: starts the proxy explicitlyGET /: proxy info and bound profile detailsGET /health: proxy liveness checkGET /v1/models: local view of the configured model mappingPOST /v1/messages: Anthropic-compatible request entrypoint
Troubleshooting
Missing or invalid local proxy token
- Re-run
eval "$(ccs proxy activate)" - Check
ccs proxy statusand confirm the expected profile is running
Self-signed or private CA upstream
- Add
CCS_OPENAI_PROXY_INSECURE=1to the profile settings - Restart the proxy after changing the setting
Need to pin or verify the local port
- Check the active binding with
ccs proxy status hf - Pin a one-off port with
ccs proxy start hf --port 3460 - Reserve a stable profile port with
proxy.profile_ports - Re-run
ccs proxy activate hfafter changing the port
Provider returns 429 or empty upstream output
- CCS now preserves upstream rate-limit errors and retry headers
- Empty or malformed provider JSON is returned as Anthropic-style
api_error
Requests route to the wrong model/profile
- Use an explicit selector such as
profile:model - Review
proxy.routingif scenario routing is enabled - Check CCS structured logs in
~/.ccs/logs/current.jsonlfor routing decisions
Validation
The shipped coverage includes:
- unit tests for OpenAI-compatible profile detection
- unit tests for Anthropic -> OpenAI request translation
- unit tests for request-time profile/model routing and scenario routing
- unit tests for multi-line SSE parsing
- integration tests for
/v1/messagesrequest/response translation - integration tests for rate limits, empty upstream responses, timeout handling, thinking/tool-call chunk streaming, and request-time routing
- integration tests for daemon lifecycle and
/health//v1/models - e2e tests for
ccs proxylifecycle - e2e tests for
ccs <profile>auto-routing through a mock upstream
Focused verification command:
bun test tests/e2e/proxy-command.e2e.test.ts tests/integration/proxy/request-routing.test.ts --coverage
Pre-merge gate:
bun run validate