| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
CCRelay is a VS Code extension — with optional Electron and Tauri desktop apps — that bundles a local API proxy so you can seamlessly switch between AI providers (Anthropic, OpenAI, Gemini, etc.) without losing conversation context. Designed for Claude Code, Claude Cowork, and OpenAI Codex.
Website: https://ccrelay.inflab.org
Proxy & Routing
Client Integrations
Operations
Desktop & UI
External web search
Relaying uses the provider baseUrl hostname. The rows below are upstream endpoints we have validated when you add them as a provider. Vendors may offer Anthropic APIs, OpenAI-compatible APIs, or both — but your client protocol and the upstream protocol are often not the same. When they differ, CCRelay applies generic protocol conversion first, then hostname-specific alignment where we maintain it. When the wire looks the same on both sides, tooling still differs by vendor (for example Web Search Server Tools, strict Chat schemas, or Responses-only tools).
Hosts not listed get generic conversion only (no extra platform layer). Listed hosts get generic conversion plus platform rules for tools, messages, responses, and request URL/body quirks. The last column is where Web Search Server Tools are supported for that vendor; it does not depend on how you reach the relay.
Example — Azure OpenAI: Upstream Web Search Server Tools exist only on the Responses API (hence “Responses API only” in the Web Search Server Tools column). You can still point clients at CCRelay using the OpenAI Chat Completions surface. After you set Azure OpenAI as the provider baseUrl, Chat-shaped calls that include Web Search Server Tools are rewritten in the conversion layer into upstream Responses requests so search keeps working—you do not need the client to call /v1/responses itself.
| Provider (target host) | Anthropic /v1/messages | OpenAI /chat/completions | OpenAI /v1/responses | Web Search Server Tools |
|---|---|---|---|---|
| Z.ai GLM (api.z.ai, open.bigmodel.cn) | Supported | Supported | Not supported | Anthropic endpoint only |
| Xiaomi MiMo (api.xiaomimimo.com) | Supported | Supported | Not supported | Chat only |
| MiniMax (api.minimax.io, api.minimaxi.com) | Supported | Supported | Not supported | Not supported |
| Google Gemini (OpenAI-compatible, generativelanguage.googleapis.com) | Not supported | Supported | Not supported | Not supported |
| Azure OpenAI (*.cognitiveservices.azure.com) | Not supported | Supported | Supported | Responses API only |
| Other hosts | Varies | Varies | Varies | Generic conversion only |
Screenshots (Claude Code through CCRelay)
git clone https://github.com/inflaborg/ccrelay.git
cd ccrelay
npm install
npm run build
npm run package # produces dists/ccrelay-vscode-*.vsixnpm install
npm run compile # or npm run watch
# Press F5 in VS Code to launch Extension Development HostAn optional Electron desktop app (packages/desktop) runs the same core as the VS Code extension:
A lightweight Tauri desktop app (packages/desktop-tauri) runs the same core as the VS Code extension and Electron app:
npm install
npm run tauri:dev # Builds web UI + Node sidecar, then runs Tauri dev
npm run tauri:pack:mac # Production macOS installer
npm run tauri:pack:win # Production Windows installernpm run tauri:build (run automatically before pack/dev) bundles the sidecar JavaScript, copies the native SQLite module, and places a Node binary next to the Tauri external sidecar slot. Use Node.js 22 when building from source (matches CI).
Edit ~/.ccrelay/config.yaml (auto-created on first launch):
providers:
glm:
name: "Z.AI-GLM-5"
baseUrl: "https://api.z.ai/api/anthropic"
mode: "inject"
apiKey: "${GLM_API_KEY}"
modelMap:
- pattern: "claude-opus-*"
model: "glm-5"
- pattern: "claude-sonnet-*"
model: "glm-5"
- pattern: "claude-haiku-*"
model: "glm-4.7"
enabled: true
defaultProvider: "glm"Add to ~/.claude/settings.json:
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "ccrelay_apikey_placehold_do_not_need_to_setup_here",
"ANTHROPIC_BASE_URL": "http://localhost:7575/anthropic",
"API_TIMEOUT_MS": "3000000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1
}
}Optional per-tier model names — only needed if you want to override Claude Code's defaults:
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "ccrelay_apikey_placehold_do_not_need_to_setup_here",
"ANTHROPIC_BASE_URL": "http://localhost:7575/anthropic",
"API_TIMEOUT_MS": "3000000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1,
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-7",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5"
}
}You can also set these from the Web dashboard: Client configuration tab.
CCRelay exposes both Anthropic and OpenAI compatible routes on the same port (default 7575). Use URL prefixes to pick the right protocol:
| Client | Protocol | Base URL |
|---|---|---|
| Claude Code | Anthropic | http://127.0.0.1:7575/anthropic |
| Claude Cowork | Anthropic | http://127.0.0.1:7575/anthropic |
| Codex | OpenAI | http://127.0.0.1:7575/openai |
Legacy /v1/... paths still work when pointed at http://127.0.0.1:7575 directly.
See Quick Start for the recommended ~/.claude/settings.json config.
Quick test (current shell only):
export ANTHROPIC_BASE_URL=http://127.0.0.1:7575/anthropic
claudeSet the app's Anthropic Base URL to http://127.0.0.1:7575/anthropic. Switch providers via the CCRelay extension or config.yaml.
Create or edit ~/.codex/config.toml (or use Client configuration → Codex → Apply in the dashboard):
model = "gpt-5.4-mini"
model_provider = "ccrelay"
model_catalog_json = "ccrelay-model-catalog.json"
[model_providers.ccrelay]
name = "CCRelay"
base_url = "http://localhost:7575/openai"Apply writes ~/.codex/ccrelay-model-catalog.json from the active provider’s custom models (or exact modelMap entries) so Codex /model can list them. Set model to one of those ids. Restart Codex after Apply or a provider switch so the catalog reloads.
When multiple VS Code windows are open:
Logging: request logs are persisted only by the Leader. Followers proxy log API calls to the Leader; if the Leader is unreachable, those calls return 503.
IPC lock (~/.ccrelay/ccrelay-lock.sock on Unix/macOS, named pipe on Windows) coordinates Leader election across VS Code and the desktop app.
| Mode | Auth behavior | Use case |
|---|---|---|
| passthrough | Preserves original auth headers | Official Claude API with OAuth |
| inject | Replaces auth with provider API key | Third-party providers (GLM, OpenRouter, etc.) |
Map Claude model names to provider-specific models with wildcard support:
modelMap:
- pattern: "claude-opus-*"
model: "glm-5"
- pattern: "claude-sonnet-*"
model: "glm-4.7"Vision model mapping — separate mapping for multimodal requests:
vlModelMap:
- pattern: "claude-*"
model: "vision-model"modelMap applies only to request bodies (model field). GET /models responses are not rewritten.
Starting from Claude Desktop 1.7196.0, the client rejects model IDs that contain third-party keywords such as qwen, glm, kimi, deepseek, etc. If you use third-party upstream models, map them to claude- prefixed aliases for Cowork only.
The alias must be claude- followed by a single token without additional hyphens (e.g. claude-a1b2c3d4, not claude-my-model), because multi-hyphen names are parsed as Anthropic model versions.
Canonical alias ids (Wizard, Cowork helper, Smart Routing) use claude-{8 hex}: SHA1(providerId:protocol:upstreamModelId) truncated to 8 hex digits, prefixed with smartRouting.aliasPrefix (default claude-). The same upstream model on different providers or protocols gets a different alias.
Custom model list (customModelsList): each line is realModelId;displayName;alias (or realModelId;;alias when display equals the real id). The real id is what upstream expects; alias is the Cowork-safe id.
Cowork: In Claude Desktop, add a custom request header x-ccrelay-model-alias with any value (for example 1). With this header, GET /models and GET /models/{id} return alias as the wire id. Without the header, the same list returns real model ids (for other clients).
Model mapping (modelMap): auto-generated entries include alias rules, identity rules (realId → realId), Claude family wildcards (claude-haiku-* / claude-sonnet-* / claude-opus-*), and default claude-* / gpt-* catch-alls. Identity rules ensure clients sending real model ids are not misrouted by wildcards.
Example -- two GLM models; Cowork uses aliases via the header above:
glm:
name: "GLM"
baseUrl: "https://api.z.ai/api/paas/v4"
providerType: "openai_chat"
mode: "inject"
apiKey: "${GLM_API_KEY}"
useCustomModelsList: true
customModelsList:
- "glm-5.1;GLM 5.1;claude-363a702b"
- "glm-4.7;GLM 4.7;claude-02a1bc84"
modelMap:
- { pattern: "claude-363a702b", model: "glm-5.1" }
- { pattern: "glm-5.1", model: "glm-5.1" }
- { pattern: "claude-02a1bc84", model: "glm-4.7" }
- { pattern: "glm-4.7", model: "glm-4.7" }
- { pattern: "claude-haiku-*", model: "glm-5.1" }
- { pattern: "claude-sonnet-*", model: "glm-5.1" }
- { pattern: "claude-opus-*", model: "glm-5.1" }
- { pattern: "claude-*", model: "glm-5.1" }
- { pattern: "gpt-*", model: "glm-5.1" }With this configuration:
The built-in wizard and Cowork quick-fill helper generate realId;displayName;claude-{hash} lines and matching modelMap entries using the canonical hash above. Add x-ccrelay-model-alias in Claude Desktop for Cowork; omit it elsewhere. Use Rebuild model map in the provider editor to fully rebuild modelMap from customModelsList after manual edits (Claude family wildcard targets are kept when those models still exist; other custom wildcard rules such as gpt-*-mini are not preserved).
Use Quick fill custom models to enter upstream model IDs and display names, then optionally map claude-haiku-* / claude-sonnet-* / claude-opus-* to a model in the list (defaults to the first). The custom model list and model map are generated automatically.
In Claude Desktop's Configure third-party inference panel, add x-ccrelay-model-alias to Gateway extra headers so that the model list returns aliases instead of real IDs.
CCRelay accepts three inbound protocols and converts when the upstream provider speaks a different wire:
| Inbound path | Client protocol |
|---|---|
| /v1/messages, /anthropic/v1/messages | Anthropic Messages |
| /v1/chat/completions, /openai/chat/completions | OpenAI Chat Completions |
| /v1/responses | OpenAI Responses API |
| /v1/models, /openai/models | OpenAI models list |
| /anthropic/v1/models | Anthropic models list |
Conversion rules:
Streaming limitations:
Example: OpenAI-compatible provider (Gemini)
gemini:
name: "Gemini"
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai"
providerType: "openai"
mode: "inject"
apiKey: "${GEMINI_API_KEY}"
modelMap:
- pattern: "claude-*"
model: "gemini-2.5-pro"Built-in web dashboard accessible via Command Palette → CCRelay: Open Dashboard (VS Code) or tray menu → Open Dashboard (desktop app).
Note: The dashboard is not accessible by directly opening http://127.0.0.1:7575/ccrelay/ in a browser. Access is restricted to requests originating from within the VS Code extension or the desktop app, which include an internal header. Open the dashboard via the extension command or the desktop tray menu instead.
Web UI
Desktop app
CCRelay uses ~/.ccrelay/config.yaml (auto-created on first launch). On startup the bundled defaults are merged with your file — your values always win, missing keys are filled from defaults. List sections (routing.forward, routing.block, concurrency.routes) merge by identity key, with your rows first and new defaults appended. Omit a list to inherit full defaults; set [] for intentionally empty.
YAML config supports both camelCase and snake_case keys.
| Setting | Default | Description |
|---|---|---|
| server.port | 7575 | Proxy server port |
| server.host | 127.0.0.1 | Bind address |
| server.autoStart | true | Auto-start server on extension load |
| server.locale | "" | Web UI language ("en" or "zh"). First visit shows a picker if unset. |
| Setting | Default | Description |
|---|---|---|
| defaultProvider | official | Default provider ID |
| providers | {...} | Provider map (see below) |
Each provider supports:
| Field | Default | Description |
|---|---|---|
| name | — | Display name |
| baseUrl | — | API base URL |
| mode | "passthrough" | passthrough (keep auth) or inject (replace auth) |
| providerType | "anthropic" | "anthropic", "openai" (full passthrough), or "openai_chat" (Chat Completions only) |
| openaiCompat | — | Optional. Set azure_openai for Azure Chat shaping on custom domains (official *.cognitiveservices.azure.com is auto-detected) |
| apiKey | — | API key for inject mode. Supports ${ENV_VAR}. |
| authHeader | "authorization" | Auth header name |
| modelMap | — | Model name mappings ([{pattern, model}], wildcards supported) |
| vlModelMap | — | Vision model mappings (for multimodal requests) |
| headers | — | Custom request headers |
| enabled | true | Enable/disable |
Enable Smart Routing on the Providers tab (top card). It aggregates all enabled providers' model lists and routes each request to the matching provider by model id. Smart Routing and the single fallback provider are mutually exclusive: when Smart Routing is active, provider cards are not marked in use; select a provider card and Apply to use it and disable Smart Routing.
Use the Smart Routing tab for settings (alias prefix, bare model id fallback, exclude list, custom routing rules, aggregated model table).
| Setting | Default | Description |
|---|---|---|
| smartRouting.enabled | false | Enable on the Providers tab. Aggregate provider models and route by <providerId>:<modelId> |
| smartRouting.aliasPrefix | "claude-" | Prefix for canonical alias ids (claude-{8 hex}) when client sends x-ccrelay-model-alias |
| smartRouting.exclude | — | Wildcard list of public model ids left out of /v1/models and not routed. Requests that would use an excluded id are rejected |
| smartRouting.include | — | When set, only matching public ids are exposed (mutually exclusive with exclude) |
| smartRouting.modelsCache.ttlSeconds | 600 | Upstream models list cache TTL for non-custom providers |
| smartRouting.bareModelFallback.mode | first-match | When client sends a bare model id (no prefix), match first provider in YAML order or reject |
| smartRouting.modelRules | — | Custom rules (pattern, provider, model) matched before the catalog; not exposed in /v1/models |
| Setting | Default | Description |
|---|---|---|
| configVersion | "0.2.5" | Config schema version. Older configs auto-upgraded on startup. |
| routing.forward | [{path, provider}] | Forward rules — first match wins. provider: "auto" = current provider. Unmatched → 404. |
| routing.block | [{path, response, code, condition?}] | Block rules — return custom response. Optional condition.providers (allowlist) and condition.providerNot (exclusion list). |
| Setting | Default | Description |
|---|---|---|
| concurrency.enabled | true | Enable request queue |
| concurrency.maxWorkers | 3 | Max concurrent requests |
| concurrency.maxQueueSize | 100 | Max queued requests (0 = unlimited) |
| concurrency.requestTimeout | 0 | Queue timeout in seconds (0 = unlimited) |
| concurrency.routes | [] | Per-route queue config (by pattern) |
| Setting | Default | Description |
|---|---|---|
| logging.storeBodies | true | Store request/response bodies in the Logs tab. Token and performance metrics are recorded separately |
| logging.database.type | "sqlite" | "sqlite" or "postgres" |
logging.enabled is deprecated. If storeBodies is unset, enabled is used as a fallback.
SQLite:
| Setting | Default | Description |
|---|---|---|
| logging.database.path | "" | DB file path (empty = ~/.ccrelay/logs.db) |
| logging.database.sqlite3_executable | "" | Path to sqlite3 binary (empty = resolve from PATH) |
If sqlite3 cannot be resolved, the proxy runs without log persistence (warning in logs).
PostgreSQL:
| Setting | Default | Description |
|---|---|---|
| logging.database.host | localhost | Server host |
| logging.database.port | 5432 | Server port |
| logging.database.name | ccrelay | Database name |
| logging.database.user | "" | Username |
| logging.database.password | "" | Password (supports ${ENV_VAR}) |
| logging.database.ssl | false | Enable SSL |
Optional local handling of Anthropic-style web search (server tool) requests for selected providers. CCRelay can run live retrieval through Tavily or Parallel, then return a synthesized assistant response for that turn so the upstream chat model does not need to implement the tool itself.
| Setting | Description |
|---|---|
| webSearch.enabled | Master switch (true / false). When omitted, non-empty providers means on. |
| webSearch.providers | Provider IDs (keys under providers:) assigned to web search (kept when disabled). |
| webSearch.defaultSearchBackend | Optional: tavily or parallel (defaults when not inferred per request). |
| Setting | Description |
|---|---|
| webSearch.tavily.apiKey | Tavily API key. Supports ${ENV_VAR}. |
| webSearch.tavily.searchDepth | basic or advanced (optional). |
| webSearch.tavily.maxResults | Number of results, 1–10 (optional). |
| Setting | Description |
|---|---|
| webSearch.parallel.apiKey | Parallel API key. Supports ${ENV_VAR}. |
| webSearch.parallel.mode | turbo, basic, or advanced (optional; default basic). |
| webSearch.parallel.maxResults | Number of results, 1–10 (optional). |
| webSearch.parallel.publishedAfter | Optional RFC 3339 date (YYYY-MM-DD) for freshness filter. |
| webSearch.parallel.location | Geo target: any ISO 3166-1 alpha-2 code (e.g. us, cn, gb); omit for auto. Unsupported codes may be ignored by Parallel. |
| webSearch.parallel.includeDomains | Optional allow-list of domains (YAML array). |
| webSearch.parallel.excludeDomains | Optional block-list of domains (YAML array). |
| webSearch.parallel.liveFetch | Optional: enable live fetch when cache is stale (higher latency). |
| webSearch.parallel.maxCharsPerResult | Optional excerpt size limit per result URL. |
You may use the top-level key web_search instead of webSearch (same nested shape).
webSearch:
tavily:
apiKey: "${TAVILY_API_KEY}"
searchDepth: basic
maxResults: 5
defaultSearchBackend: tavily
enabled: true
providers:
- my-providerSet enabled: false to turn off web search without clearing the providers preset list.
Edit the same fields from the dashboard Capabilities tab.
configVersion: "0.2.5"
server:
port: 7575
host: "127.0.0.1"
autoStart: true
providers:
official:
name: "Claude Official"
baseUrl: "https://api.anthropic.com"
mode: "passthrough"
providerType: "anthropic"
enabled: true
glm:
name: "Z.AI-GLM-5"
baseUrl: "https://api.z.ai/api/anthropic"
mode: "inject"
apiKey: "${GLM_API_KEY}"
modelMap:
- pattern: "claude-opus-*"
model: "glm-5"
- pattern: "claude-sonnet-*"
model: "glm-5"
- pattern: "claude-haiku-*"
model: "glm-4.7"
enabled: true
gemini:
name: "Gemini"
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai"
providerType: "openai"
mode: "inject"
apiKey: "${GEMINI_API_KEY}"
modelMap:
- pattern: "claude-*"
model: "gemini-2.5-pro"
enabled: true
defaultProvider: "official"
routing:
forward:
- path: "/v1/messages"
provider: "auto"
- path: "/v1/chat/completions"
provider: "auto"
- path: "/v1/responses"
provider: "auto"
- path: "/v1/models"
provider: "auto"
- path: "/v1/messages/count_tokens"
provider: "auto"
block:
- path: "/api/event_logging/*"
response: ""
code: 200
- path: "/v1/messages/count_tokens"
response: '{"input_tokens": 0}'
code: 200
concurrency:
enabled: true
maxWorkers: 3
maxQueueSize: 100
requestTimeout: 0
logging:
storeBodies: true
database:
type: "sqlite"
path: ""Management endpoints at /ccrelay/:
| Endpoint | Method | Description |
|---|---|---|
| /ccrelay/api/status | GET | Proxy status |
| /ccrelay/api/providers | GET | List providers |
| /ccrelay/api/switch/{id} | GET | Switch to provider |
| /ccrelay/api/switch | POST | Switch provider (JSON body) |
| /ccrelay/api/providers/export | POST | Export providers by ID |
| /ccrelay/api/providers/import | POST | Import providers (merge by ID) |
| /ccrelay/api/queue | GET | Queue statistics |
| /ccrelay/api/logs | GET | Request logs |
| /ccrelay/api/config | GET, PATCH | Read/write config sections |
| /ccrelay/ws | WebSocket | Follower sync |
| /ccrelay/ | GET | Web UI dashboard |
All other requests are proxied to the current provider.
| Command | ID | Description |
|---|---|---|
| CCRelay: Show Menu | ccrelay.showMenu | Show main menu |
| CCRelay: Switch Provider | ccrelay.switchProvider | Provider picker |
| CCRelay: Start Server | ccrelay.startServer | Start server |
| CCRelay: Stop Server | ccrelay.stopServer | Stop server |
| CCRelay: Open Settings | ccrelay.openSettings | Extension settings |
| CCRelay: Show Logs | ccrelay.showLogs | Output logs |
| CCRelay: Clear Logs | ccrelay.clearLogs | Clear output logs |
| CCRelay: Open Dashboard | ccrelay.openWebUI | Web dashboard |
npm run compile # Type-check
npm run watch # Watch & recompile
npm run lint # Lint
npm run format # Format
npm run test # Unit tests
npm run test:integration
npm run test:all
npm run test:coverage
npm run package # Build VSIX
npm run build:dev # Dev build
npm run build:prod # Prod build
# Electron desktop app
npm run desktop:start
npm run desktop:pack:mac
npm run desktop:pack:win
# Tauri desktop app
npm run tauri:dev
npm run tauri:pack:mac
npm run tauri:pack:winccrelay/ ├── packages/ │ ├── core/ # Shared runtime (proxy, config, converters) │ ├── vscode/ # VS Code extension │ ├── desktop/ # Electron desktop app │ └── desktop-tauri/ # Tauri desktop app ├── web/ # Web UI (React + Vite) ├── tests/ # Vitest unit + integration ├── scripts/ # Build & packaging helpers └── dists/ # Packaged .vsix
| File | Location | Description |
|---|---|---|
| Config | ~/.ccrelay/config.yaml | Main config (auto-created) |
| State | ~/.ccrelay/state.json | Active provider ID |
| IPC lock | ~/.ccrelay/ccrelay-lock.sock (Unix) / named pipe (Win) | Leader election |
| Log DB | ~/.ccrelay/logs.db | Request logs (Leader only) |
Issues and Pull Requests are welcome!
This project is 100% AI-generated code. Special thanks to:
Copyright (c) 2026 inflab.org
| Back | FazBrowse Home | New Git URL |