{"id":44616286,"url":"https://github.com/inflaborg/ccrelay","last_synced_at":"2026-05-13T08:10:06.772Z","repository":{"id":338419593,"uuid":"1154404492","full_name":"inflaborg/ccrelay","owner":"inflaborg","description":"CCRelay, a Claude Code Router \u0026 Gateway. Switch underlying LLM providers (Anthropic, OpenAI) on the fly.","archived":false,"fork":false,"pushed_at":"2026-05-09T14:47:12.000Z","size":4470,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-09T16:09:28.411Z","etag":null,"topics":["anthropic","ccrelay","claude-code","claude-code-router","claude-code-switcher","gemini","glm","llm","llm-gateway","middleware","openai","vscode","vscode-extension"],"latest_commit_sha":null,"homepage":"https://ccrelay.inflab.org","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inflaborg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-10T10:53:46.000Z","updated_at":"2026-05-09T14:40:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/inflaborg/ccrelay","commit_stats":null,"previous_names":["inflaborg/ccrelay"],"tags_count":48,"template":false,"template_full_name":null,"purl":"pkg:github/inflaborg/ccrelay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inflaborg%2Fccrelay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inflaborg%2Fccrelay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inflaborg%2Fccrelay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inflaborg%2Fccrelay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inflaborg","download_url":"https://codeload.github.com/inflaborg/ccrelay/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inflaborg%2Fccrelay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32868103,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-10T13:40:02.631Z","status":"ssl_error","status_checked_at":"2026-05-10T13:40:02.145Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anthropic","ccrelay","claude-code","claude-code-router","claude-code-switcher","gemini","glm","llm","llm-gateway","middleware","openai","vscode","vscode-extension"],"created_at":"2026-02-14T13:05:32.640Z","updated_at":"2026-05-13T08:10:06.765Z","avatar_url":"https://github.com/inflaborg.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CCRelay\n\n[![VSCode Extension](https://img.shields.io/badge/VSCode-Extension-blue)](https://code.visualstudio.com/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n**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**.\n\n**Website**: [https://ccrelay.inflab.org](https://ccrelay.inflab.org)\n\n**[中文文档](./README_CN.md)**\n\n---\n\n## Table of Contents\n\n- [Core Features](#core-features)\n- [Verified upstreams (by host)](#verified-upstreams-by-host)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Desktop App (Electron)](#desktop-app-electron)\n- [Desktop App (Tauri)](#desktop-app-tauri)\n- [Quick Start](#quick-start)\n- [Client Integrations](#client-integrations)\n- [Usage Guide](#usage-guide)\n  - [Multi-Instance Mode](#multi-instance-mode)\n  - [Provider Modes](#provider-modes)\n  - [Model Mapping](#model-mapping)\n  - [Claude Desktop / Cowork Model ID Restrictions](#claude-desktop--cowork-model-id-restrictions)\n  - [OpenAI Format Conversion](#openai-format-conversion)\n  - [Web UI Dashboard](#web-ui-dashboard)\n- [External web search (Tavily)](#external-web-search-tavily)\n- [Configuration](#configuration)\n- [API Endpoints](#api-endpoints)\n- [Commands](#commands)\n- [Development](#development)\n- [File Locations](#file-locations)\n- [TODO](#todo)\n- [License](#license)\n\n---\n\n## Core Features\n\n**Proxy \u0026 Routing**\n\n- Built-in HTTP proxy (default `http://127.0.0.1:7575`) with path-based routing — forward to a provider, block with a custom response, or return 404\n- Multi-protocol: accepts **Anthropic**, **OpenAI Chat Completions**, and **OpenAI Responses API** (`/v1/responses`) on the same port\n- Automatic cross-protocol conversion when client and upstream wire formats differ\n- URL prefixes `/openai/...` and `/anthropic/v1/...` let different clients target the right protocol explicitly\n\n**Client Integrations**\n\n- First-class support for **Claude Code** (`ANTHROPIC_BASE_URL`), **Claude Cowork**, and **OpenAI Codex** (`~/.codex/config.toml`)\n- Web dashboard **Client configuration** tab writes the right env vars for you\n\n**Operations**\n\n- Multi-instance coordination (Leader/Follower) across VS Code windows and the desktop app\n- Config hot-reload — edits to `config.yaml` are picked up automatically\n- Optional request/response logging (SQLite or PostgreSQL) with a built-in log viewer, token tracking, and performance metrics (TTFB, output TPS, P50/P90 latency)\n- Concurrency control with per-route queue limits\n\n**Desktop \u0026 UI**\n\n- Optional Electron or Tauri desktop app — run CCRelay without VS Code\n- Web dashboard with provider management, settings, and i18n (English + Chinese)\n- Provider import/export as JSON\n\n**External web search**\n\n- Optional **Tavily**-backed handling of Anthropic-style **web search** tool traffic for provider IDs you allowlist; configure in `config.yaml` or the dashboard **Capabilities** tab\n\n### Verified upstreams (by host)\n\nRelaying 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).\n\n**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.\n\n**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.\n\n| Provider (target host)                                                     | Anthropic `/v1/messages` | OpenAI `/chat/completions` | OpenAI `/v1/responses` | Web Search Server Tools |\n| -------------------------------------------------------------------------- | ------------------------ | -------------------------- | ---------------------- | ----------------------- |\n| **Z.ai GLM** (`api.z.ai`, `open.bigmodel.cn`)                              | Supported                | Supported                  | Not supported          | Supported               |\n| **Xiaomi MiMo** (`api.xiaomimimo.com`)                                     | Supported                | Supported                  | Not supported          | Chat only               |\n| **MiniMax** (`api.minimax.io`, `api.minimaxi.com`)                         | Supported                | Supported                  | Not supported          | Not supported           |\n| **Google Gemini** (OpenAI-compatible, `generativelanguage.googleapis.com`) | Not supported            | Supported                  | Not supported          | Not supported           |\n| **Azure OpenAI** (`*.cognitiveservices.azure.com`)                         | Not supported            | Supported                  | Supported              | Responses API only      |\n| _Other hosts_                                                              | _Varies_                 | _Varies_                   | _Varies_               | Generic conversion only |\n\n**Screenshots (Claude Code through CCRelay)**\n\n![Claude Code — GLM Web Search Server Tools](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-claude-glm-web-search.webp)\n\n![Claude Code — Xiaomi MiMo Web Search Server Tools](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-claude-xiaomi-mimo-web-search.webp)\n\n---\n\n## Requirements\n\n- VS Code 1.80.0 or higher\n- Node.js (for development)\n\n---\n\n## Installation\n\n### Install from VSIX\n\n1. Download the latest `.vsix` from [Releases](https://github.com/inflaborg/ccrelay/releases)\n2. In VS Code: `Cmd+Shift+P` (macOS) or `Ctrl+Shift+P` → `Extensions: Install from VSIX...`\n3. Select the downloaded file\n\n### Build from Source\n\n```bash\ngit clone https://github.com/inflaborg/ccrelay.git\ncd ccrelay\nnpm install\nnpm run build\nnpm run package        # produces dists/ccrelay-vscode-*.vsix\n```\n\n### Development Mode\n\n```bash\nnpm install\nnpm run compile        # or npm run watch\n# Press F5 in VS Code to launch Extension Development Host\n```\n\n---\n\n## Desktop App (Electron)\n\nAn optional Electron desktop app (`packages/desktop`) runs the same core as the VS Code extension:\n\n- Shares `~/.ccrelay/` config, state, and Leader election with the extension\n- Tray menu → **Open Dashboard** loads the web UI in an app window\n- Download from [GitHub Releases](https://github.com/inflaborg/ccrelay/releases):\n  - **macOS**: `CCRelay-\u003cversion\u003e-darwin-arm64.dmg` or `-darwin-x64.dmg`\n  - **Windows**: `CCRelay-\u003cversion\u003e-win32-x64.exe` or `-win32-arm64.exe`\n\n### macOS: First Launch\n\nRelease builds are not Apple-notarized. If Gatekeeper blocks the app:\n\n```bash\nxattr -cr /path/to/CCRelay.app\n```\n\nOr **Control-click** the app → **Open** the first time.\n\n---\n\n## Desktop App (Tauri)\n\nA lightweight Tauri desktop app (`packages/desktop-tauri`) runs the same core as the VS Code extension and Electron app:\n\n- Shares `~/.ccrelay/` config, state, and Leader election with all other instances\n- Uses a sidecar architecture: Rust shell manages a Node.js server process\n- Tray menu with Start/Stop Server and Open Dashboard\n- Download from [GitHub Releases](https://github.com/inflaborg/ccrelay/releases):\n  - Installer names follow the Electron desktop pattern (`CCRelay-\u003cversion\u003e-\u003cplatform\u003e-\u003carch\u003e.\u003cext\u003e`) with **`tauri`** added after the version (for example `CCRelay-0.2.1-tauri-darwin-arm64.dmg`, `CCRelay-0.2.1-tauri-win32-x64.exe`). Windows ships **NSIS `.exe`** only (no MSI).\n\n### Development\n\n```bash\nnpm install\nnpm run tauri:dev         # Dev mode with hot reload\nnpm run tauri:pack:mac    # Build macOS app\nnpm run tauri:pack:win    # Build Windows app\n```\n\n---\n\n## Quick Start\n\n### 1. Add a provider\n\nEdit `~/.ccrelay/config.yaml` (auto-created on first launch):\n\n```yaml\nproviders:\n  glm:\n    name: \"Z.AI-GLM-5\"\n    baseUrl: \"https://api.z.ai/api/anthropic\"\n    mode: \"inject\"\n    apiKey: \"${GLM_API_KEY}\"\n    modelMap:\n      - pattern: \"claude-opus-*\"\n        model: \"glm-5\"\n      - pattern: \"claude-sonnet-*\"\n        model: \"glm-5\"\n      - pattern: \"claude-haiku-*\"\n        model: \"glm-4.7\"\n    enabled: true\n\ndefaultProvider: \"glm\"\n```\n\n### 2. Point Claude Code at CCRelay\n\nAdd to `~/.claude/settings.json`:\n\n```json\n{\n  \"env\": {\n    \"ANTHROPIC_AUTH_TOKEN\": \"ccrelay_apikey_placehold_do_not_need_to_setup_here\",\n    \"ANTHROPIC_BASE_URL\": \"http://localhost:7575/anthropic\",\n    \"API_TIMEOUT_MS\": \"3000000\",\n    \"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC\": 1\n  }\n}\n```\n\nOptional per-tier model names — only needed if you want to override Claude Code's defaults:\n\n```json\n{\n  \"env\": {\n    \"ANTHROPIC_AUTH_TOKEN\": \"ccrelay_apikey_placehold_do_not_need_to_setup_here\",\n    \"ANTHROPIC_BASE_URL\": \"http://localhost:7575/anthropic\",\n    \"API_TIMEOUT_MS\": \"3000000\",\n    \"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC\": 1,\n    \"ANTHROPIC_DEFAULT_OPUS_MODEL\": \"claude-opus-4-7\",\n    \"ANTHROPIC_DEFAULT_SONNET_MODEL\": \"claude-sonnet-4-6\",\n    \"ANTHROPIC_DEFAULT_HAIKU_MODEL\": \"claude-haiku-4-5\"\n  }\n}\n```\n\nYou can also set these from the Web dashboard: **Client configuration** tab.\n\n### 3. Switch providers\n\n- Click the CCRelay icon in the VS Code status bar\n- Or Command Palette: `CCRelay: Switch Provider`\n\n---\n\n## Client Integrations\n\nCCRelay exposes both **Anthropic** and **OpenAI** compatible routes on the same port (default **7575**). Use URL prefixes to pick the right protocol:\n\n| Client            | Protocol  | Base URL                          |\n| ----------------- | --------- | --------------------------------- |\n| **Claude Code**   | Anthropic | `http://127.0.0.1:7575/anthropic` |\n| **Claude Cowork** | Anthropic | `http://127.0.0.1:7575/anthropic` |\n| **Codex**         | OpenAI    | `http://127.0.0.1:7575/openai`    |\n\nLegacy `/v1/...` paths still work when pointed at `http://127.0.0.1:7575` directly.\n\n### Claude Code\n\nSee [Quick Start](#quick-start) for the recommended `~/.claude/settings.json` config.\n\nQuick test (current shell only):\n\n```bash\nexport ANTHROPIC_BASE_URL=http://127.0.0.1:7575/anthropic\nclaude\n```\n\n### Claude Cowork\n\nSet the app's **Anthropic Base URL** to `http://127.0.0.1:7575/anthropic`. Switch providers via the CCRelay extension or `config.yaml`.\n\n### Codex\n\nCreate or edit `~/.codex/config.toml`:\n\n```toml\nmodel = \"gpt-5.4-mini\"\nmodel_provider = \"ccrelay\"\n\n[model_providers.ccrelay]\nname = \"CCRelay\"\nbase_url = \"http://localhost:7575/openai\"\n```\n\nAdjust `model` to one your CCRelay provider can route (via `modelMap`).\n\n---\n\n## Usage Guide\n\n### Multi-Instance Mode\n\nWhen multiple VS Code windows are open:\n\n- One instance becomes the **Leader** and runs the HTTP server; others are **Followers**\n- Leader broadcasts provider changes to Followers via WebSocket\n- If the Leader exits, a Follower takes over automatically\n- Status bar shows role: `$(broadcast)` = Leader, `$(radio-tower)` = Follower\n\n**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.\n\n**IPC lock** (`~/.ccrelay/ccrelay-lock.sock` on Unix/macOS, named pipe on Windows) coordinates Leader election across VS Code and the desktop app.\n\n### Provider Modes\n\n| Mode          | Auth behavior                       | Use case                                      |\n| ------------- | ----------------------------------- | --------------------------------------------- |\n| `passthrough` | Preserves original auth headers     | Official Claude API with OAuth                |\n| `inject`      | Replaces auth with provider API key | Third-party providers (GLM, OpenRouter, etc.) |\n\n### Model Mapping\n\nMap Claude model names to provider-specific models with wildcard support:\n\n```yaml\nmodelMap:\n  - pattern: \"claude-opus-*\"\n    model: \"glm-5\"\n  - pattern: \"claude-sonnet-*\"\n    model: \"glm-4.7\"\n```\n\n**Vision model mapping** — separate mapping for multimodal requests:\n\n```yaml\nvlModelMap:\n  - pattern: \"claude-*\"\n    model: \"vision-model\"\n```\n\n`modelMap` applies only to request bodies (`model` field). `GET /models` responses are not rewritten.\n\n### Claude Desktop / Cowork Model ID Restrictions\n\nStarting 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.\n\nThe alias must be `claude-` followed by a single token **without additional hyphens** (e.g. `claude-a1`, not `claude-my-model`), because multi-hyphen names are parsed as Anthropic model versions.\n\n**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.\n\n**Cowork**: In Claude Desktop, add a custom request header `x-ccrelay-model-alias` (any value except `false` / `0` / `no`). 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).\n\n**Model mapping** (`modelMap`): map each alias to the real upstream model id. Place specific rules before wildcard `claude-*` / `gpt-*` catch-alls.\n\n**Example** -- two GLM models; Cowork uses aliases via the header above:\n\n```yaml\nglm:\n  name: \"GLM\"\n  baseUrl: \"https://api.z.ai/api/paas/v4\"\n  providerType: \"openai_chat\"\n  mode: \"inject\"\n  apiKey: \"${GLM_API_KEY}\"\n  useCustomModelsList: true\n  customModelsList:\n    - \"glm-5.1;GLM 5.1;claude-a1\"\n    - \"glm-4.7;GLM 4.7;claude-a2\"\n  modelMap:\n    - { pattern: \"claude-a1\", model: \"glm-5.1\" }\n    - { pattern: \"claude-a2\", model: \"glm-4.7\" }\n    - { pattern: \"claude-*\", model: \"glm-5.1\" }\n    - { pattern: \"gpt-*\", model: \"glm-5.1\" }\n```\n\nWith this configuration:\n\n- **Without** `x-ccrelay-model-alias`: `GET /models` returns `glm-5.1` and `glm-4.7` (with display names when they differ from the id).\n- **With** `x-ccrelay-model-alias`: `GET /models` returns `claude-a1` / `claude-a2` as ids; Cowork selects those; CCRelay maps them to real upstream ids via `modelMap`.\n- The `claude-*` and `gpt-*` wildcards catch any other model names the client may send and route them to the first model.\n\nThe built-in wizard writes `realId;displayName;claude-{hash}` lines and matching `modelMap` entries. Add `x-ccrelay-model-alias` in Claude Desktop for Cowork; omit it elsewhere.\n\n#### Custom model list configuration UI\n\n![Custom model list](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/provider-custom-model-1.webp)\n\nUse **Quick fill custom models** to enter upstream model IDs and display names in a structured form; the custom model list and model map are generated automatically.\n\n![Quick fill custom models](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/provider-custom-model-2.webp)\n\n#### Enabling alias in Claude Cowork\n\nIn 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.\n\n![Cowork gateway extra headers](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/cowork-ccrelay-model-alias.webp)\n\n### OpenAI Format Conversion\n\nCCRelay accepts three inbound protocols and converts when the upstream provider speaks a different wire:\n\n| Inbound path                                       | Client protocol         |\n| -------------------------------------------------- | ----------------------- |\n| `/v1/messages`, `/anthropic/v1/messages`           | Anthropic Messages      |\n| `/v1/chat/completions`, `/openai/chat/completions` | OpenAI Chat Completions |\n| `/v1/responses`                                    | OpenAI Responses API    |\n| `/v1/models`, `/openai/models`                     | OpenAI models list      |\n| `/anthropic/v1/models`                             | Anthropic models list   |\n\n**Conversion rules**:\n\n- Same family on both sides (e.g. Chat + `openai` provider) → passthrough (model mapping and auth still apply)\n- Cross-family → request/response body conversion via Chat Completions hub\n- `GET /models` → list format converted when entry path and `providerType` disagree; upstream errors forwarded as-is\n\n**Streaming limitations**:\n\n- Cross-protocol paths force `stream: false` for conversion. If the client sends `stream: true`, CCRelay synthesizes a minimal SSE envelope so the client SDK can finish; model output arrives in the final payload, not token-by-token.\n- Same-family streaming passes through normally.\n\n**Example: OpenAI-compatible provider (Gemini)**\n\n```yaml\ngemini:\n  name: \"Gemini\"\n  baseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\"\n  providerType: \"openai\"\n  mode: \"inject\"\n  apiKey: \"${GEMINI_API_KEY}\"\n  modelMap:\n    - pattern: \"claude-*\"\n      model: \"gemini-2.5-pro\"\n```\n\n### Web UI Dashboard\n\nBuilt-in web dashboard accessible via Command Palette → `CCRelay: Open Dashboard` (VS Code) or tray menu → **Open Dashboard** (desktop app).\n\n- **Dashboard** — server status, current provider, token usage, performance metrics (TTFB, P50/P90 latency, output TPS) with time range selector\n- **Providers** — view, switch, duplicate, import/export providers\n- **Capabilities** — optional **Tavily** web search: API key, search depth, max results, and which providers answer web search locally\n- **Logs** — request/response log viewer with token columns, TTFB, output TPS, and model mapping display (hidden when logging is disabled)\n- **Settings** — manage YAML config in the UI; routing and concurrency hot-reload on save, server and logging changes require a restart\n- **Client configuration** — write Claude Code env vars and Codex config from the UI\n\n\u003e **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.\n\n**Web UI**\n\n![Client configuration](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-ccrelay-setup-1.webp)\n\n![Configure default models](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-ccrelay-setup-2.webp)\n\n![Capabilities — Tavily web search](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-capabilities-websearch-tavily.webp)\n\n![Request Logs](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-ccrelay-1.webp)\n\n![Log Details](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-ccrelay-3.webp)\n\n**Desktop app**\n\n![Desktop — Dashboard](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-desktop-1.webp)\n\n![Desktop — Provider list](https://raw.githubusercontent.com/inflaborg/ccrelay/main/docs/screenshot-desktop-2.webp)\n\n---\n\n## Configuration\n\nCCRelay 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.\n\n\u003e YAML config supports both `camelCase` and `snake_case` keys.\n\n### Server\n\n| Setting            | Default     | Description                                                              |\n| ------------------ | ----------- | ------------------------------------------------------------------------ |\n| `server.port`      | `7575`      | Proxy server port                                                        |\n| `server.host`      | `127.0.0.1` | Bind address                                                             |\n| `server.autoStart` | `true`      | Auto-start server on extension load                                      |\n| `server.locale`    | `\"\"`        | Web UI language (`\"en\"` or `\"zh\"`). First visit shows a picker if unset. |\n\n### Providers\n\n| Setting           | Default    | Description              |\n| ----------------- | ---------- | ------------------------ |\n| `defaultProvider` | `official` | Default provider ID      |\n| `providers`       | `{...}`    | Provider map (see below) |\n\nEach provider supports:\n\n| Field          | Default           | Description                                                                              |\n| -------------- | ----------------- | ---------------------------------------------------------------------------------------- |\n| `name`         | —                 | Display name                                                                             |\n| `baseUrl`      | —                 | API base URL                                                                             |\n| `mode`         | `\"passthrough\"`   | `passthrough` (keep auth) or `inject` (replace auth)                                     |\n| `providerType` | `\"anthropic\"`     | `\"anthropic\"`, `\"openai\"` (full passthrough), or `\"openai_chat\"` (Chat Completions only) |\n| `apiKey`       | —                 | API key for inject mode. Supports `${ENV_VAR}`.                                          |\n| `authHeader`   | `\"authorization\"` | Auth header name                                                                         |\n| `modelMap`     | —                 | Model name mappings (`[{pattern, model}]`, wildcards supported)                          |\n| `vlModelMap`   | —                 | Vision model mappings (for multimodal requests)                                          |\n| `headers`      | —                 | Custom request headers                                                                   |\n| `enabled`      | `true`            | Enable/disable                                                                           |\n\n### Routing\n\n| Setting           | Default                                | Description                                                                                                                    |\n| ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |\n| `configVersion`   | `\"0.2.0\"`                              | Config schema version. Legacy configs auto-migrated.                                                                           |\n| `routing.forward` | `[{path, provider}]`                   | Forward rules — first match wins. `provider: \"auto\"` = current provider. Unmatched → 404.                                      |\n| `routing.block`   | `[{path, response, code, condition?}]` | Block rules — return custom response. Optional `condition.providers` (allowlist) and `condition.providerNot` (exclusion list). |\n\n### Concurrency\n\n| Setting                      | Default | Description                              |\n| ---------------------------- | ------- | ---------------------------------------- |\n| `concurrency.enabled`        | `true`  | Enable request queue                     |\n| `concurrency.maxWorkers`     | `3`     | Max concurrent requests                  |\n| `concurrency.maxQueueSize`   | `100`   | Max queued requests (0 = unlimited)      |\n| `concurrency.requestTimeout` | `60`    | Queue timeout in seconds (0 = unlimited) |\n| `concurrency.routes`         | `[]`    | Per-route queue config (by `pattern`)    |\n\n### Logging\n\n| Setting                 | Default    | Description                |\n| ----------------------- | ---------- | -------------------------- |\n| `logging.enabled`       | `false`    | Enable request logging     |\n| `logging.database.type` | `\"sqlite\"` | `\"sqlite\"` or `\"postgres\"` |\n\n**SQLite:**\n\n| Setting                               | Default | Description                                            |\n| ------------------------------------- | ------- | ------------------------------------------------------ |\n| `logging.database.path`               | `\"\"`    | DB file path (empty = `~/.ccrelay/logs.db`)            |\n| `logging.database.sqlite3_executable` | `\"\"`    | Path to `sqlite3` binary (empty = resolve from `PATH`) |\n\nIf `sqlite3` cannot be resolved, the proxy runs without log persistence (warning in logs).\n\n**PostgreSQL:**\n\n| Setting                     | Default     | Description                      |\n| --------------------------- | ----------- | -------------------------------- |\n| `logging.database.host`     | `localhost` | Server host                      |\n| `logging.database.port`     | `5432`      | Server port                      |\n| `logging.database.name`     | `ccrelay`   | Database name                    |\n| `logging.database.user`     | `\"\"`        | Username                         |\n| `logging.database.password` | `\"\"`        | Password (supports `${ENV_VAR}`) |\n| `logging.database.ssl`      | `false`     | Enable SSL                       |\n\n### External web search (Tavily)\n\nOptional **local handling** of Anthropic-style **web search** (server tool) requests for selected providers. When configured, CCRelay runs live retrieval through the **[Tavily](https://tavily.com/)** Search API and returns a synthesized assistant response for that turn, so the upstream model does not need to implement the tool itself.\n\n| Setting                        | Description                                                   |\n| ------------------------------ | ------------------------------------------------------------- |\n| `webSearch.tavily.apiKey`      | Tavily API key. Supports `${ENV_VAR}`.                        |\n| `webSearch.tavily.searchDepth` | `basic` or `advanced` (optional).                             |\n| `webSearch.tavily.maxResults`  | Number of results, 1–10 (optional).                           |\n| `webSearch.providers`          | Provider IDs (keys under `providers:`) that use this feature. |\n\nYou may use the top-level key `web_search` instead of `webSearch` (same nested shape).\n\n```yaml\nwebSearch:\n  tavily:\n    apiKey: \"${TAVILY_API_KEY}\"\n    searchDepth: basic\n    maxResults: 5\n  providers:\n    - glm\n```\n\nEdit the same fields from the dashboard **Capabilities** tab.\n\n### Full Example\n\n```yaml\nconfigVersion: \"0.2.0\"\n\nserver:\n  port: 7575\n  host: \"127.0.0.1\"\n  autoStart: true\n\nproviders:\n  official:\n    name: \"Claude Official\"\n    baseUrl: \"https://api.anthropic.com\"\n    mode: \"passthrough\"\n    providerType: \"anthropic\"\n    enabled: true\n\n  glm:\n    name: \"Z.AI-GLM-5\"\n    baseUrl: \"https://api.z.ai/api/anthropic\"\n    mode: \"inject\"\n    apiKey: \"${GLM_API_KEY}\"\n    modelMap:\n      - pattern: \"claude-opus-*\"\n        model: \"glm-5\"\n      - pattern: \"claude-sonnet-*\"\n        model: \"glm-5\"\n      - pattern: \"claude-haiku-*\"\n        model: \"glm-4.7\"\n    enabled: true\n\n  gemini:\n    name: \"Gemini\"\n    baseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\"\n    providerType: \"openai\"\n    mode: \"inject\"\n    apiKey: \"${GEMINI_API_KEY}\"\n    modelMap:\n      - pattern: \"claude-*\"\n        model: \"gemini-2.5-pro\"\n    enabled: true\n\ndefaultProvider: \"official\"\n\nrouting:\n  forward:\n    - path: \"/v1/messages\"\n      provider: \"auto\"\n    - path: \"/v1/chat/completions\"\n      provider: \"auto\"\n    - path: \"/v1/responses\"\n      provider: \"auto\"\n    - path: \"/v1/models\"\n      provider: \"auto\"\n    - path: \"/v1/messages/count_tokens\"\n      provider: \"auto\"\n  block:\n    - path: \"/api/event_logging/*\"\n      response: \"\"\n      code: 200\n    - path: \"/v1/messages/count_tokens\"\n      response: '{\"input_tokens\": 0}'\n      code: 200\n\nconcurrency:\n  enabled: true\n  maxWorkers: 3\n  maxQueueSize: 100\n  requestTimeout: 60\n\nlogging:\n  enabled: true\n  database:\n    type: \"sqlite\"\n    path: \"\"\n```\n\n---\n\n## API Endpoints\n\nManagement endpoints at `/ccrelay/`:\n\n| Endpoint                        | Method     | Description                    |\n| ------------------------------- | ---------- | ------------------------------ |\n| `/ccrelay/api/status`           | GET        | Proxy status                   |\n| `/ccrelay/api/providers`        | GET        | List providers                 |\n| `/ccrelay/api/switch/{id}`      | GET        | Switch to provider             |\n| `/ccrelay/api/switch`           | POST       | Switch provider (JSON body)    |\n| `/ccrelay/api/providers/export` | POST       | Export providers by ID         |\n| `/ccrelay/api/providers/import` | POST       | Import providers (merge by ID) |\n| `/ccrelay/api/queue`            | GET        | Queue statistics               |\n| `/ccrelay/api/logs`             | GET        | Request logs                   |\n| `/ccrelay/api/config`           | GET, PATCH | Read/write config sections     |\n| `/ccrelay/ws`                   | WebSocket  | Follower sync                  |\n| `/ccrelay/`                     | GET        | Web UI dashboard               |\n\nAll other requests are proxied to the current provider.\n\n---\n\n## Commands\n\n| Command                  | ID                       | Description        |\n| ------------------------ | ------------------------ | ------------------ |\n| CCRelay: Show Menu       | `ccrelay.showMenu`       | Show main menu     |\n| CCRelay: Switch Provider | `ccrelay.switchProvider` | Provider picker    |\n| CCRelay: Start Server    | `ccrelay.startServer`    | Start server       |\n| CCRelay: Stop Server     | `ccrelay.stopServer`     | Stop server        |\n| CCRelay: Open Settings   | `ccrelay.openSettings`   | Extension settings |\n| CCRelay: Show Logs       | `ccrelay.showLogs`       | Output logs        |\n| CCRelay: Clear Logs      | `ccrelay.clearLogs`      | Clear output logs  |\n| CCRelay: Open Dashboard  | `ccrelay.openWebUI`      | Web dashboard      |\n\n---\n\n## Development\n\n```bash\nnpm run compile        # Type-check\nnpm run watch          # Watch \u0026 recompile\nnpm run lint           # Lint\nnpm run format         # Format\nnpm run test           # Unit tests\nnpm run test:integration\nnpm run test:all\nnpm run test:coverage\nnpm run package        # Build VSIX\nnpm run build:dev      # Dev build\nnpm run build:prod     # Prod build\n\n# Electron desktop app\nnpm run desktop:start\nnpm run desktop:pack:mac\nnpm run desktop:pack:win\n\n# Tauri desktop app\nnpm run tauri:dev\nnpm run tauri:pack:mac\nnpm run tauri:pack:win\n```\n\n### Project Structure\n\n```\nccrelay/\n├── packages/\n│   ├── core/              # Shared runtime (proxy, config, converters)\n│   ├── vscode/            # VS Code extension\n│   ├── desktop/           # Electron desktop app\n│   └── desktop-tauri/     # Tauri desktop app\n├── web/                   # Web UI (React + Vite)\n├── tests/                 # Vitest unit + integration\n├── scripts/               # Build \u0026 packaging helpers\n└── dists/                 # Packaged .vsix\n```\n\n---\n\n## File Locations\n\n| File     | Location                                                 | Description                |\n| -------- | -------------------------------------------------------- | -------------------------- |\n| Config   | `~/.ccrelay/config.yaml`                                 | Main config (auto-created) |\n| State    | `~/.ccrelay/state.json`                                  | Active provider ID         |\n| IPC lock | `~/.ccrelay/ccrelay-lock.sock` (Unix) / named pipe (Win) | Leader election            |\n| Log DB   | `~/.ccrelay/logs.db`                                     | Request logs (Leader only) |\n\n---\n\n## TODO\n\n- macOS: Apple Developer ID signing + notarization in CI to remove Gatekeeper prompts\n- Re-enable DMG packaging once signing works\n\n---\n\n## Contributing\n\nIssues and Pull Requests are welcome!\n\n---\n\n## Acknowledgments\n\nThis project is **100% AI-generated code**. Special thanks to:\n\n- **[Cursor](https://cursor.com)** and **[Claude Code](https://claude.ai/code)** — AI coding assistants\n- **[GLM](https://z.ai/model-api)** and **[Xiaomi MiMo](https://platform.xiaomimimo.com/token-plan)** — model APIs used as development backends\n\n---\n\n## License\n\n[MIT License](LICENSE)\n\nCopyright (c) 2026 [inflab.org](https://inflab.org)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finflaborg%2Fccrelay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finflaborg%2Fccrelay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finflaborg%2Fccrelay/lists"}