Merge dev/3.0 into main for v3.0.0
@@ -1,85 +0,0 @@
|
||||
name: Build and Publish Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE: musistudio/claude-code-router
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
else
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Docker image version: $VERSION"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
pnpm build
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.DOCKER_IMAGE }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}},value=${{ steps.version.outputs.version }}
|
||||
type=semver,pattern={{major}}.{{minor}},value=${{ steps.version.outputs.version }}
|
||||
type=raw,value=latest
|
||||
type=sha
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./packages/server/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Image digest
|
||||
run: echo "Image pushed with digest ${{ steps.meta.outputs.digest }}"
|
||||
@@ -1,55 +0,0 @@
|
||||
name: Deploy Docs to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/workflows/docs.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./docs
|
||||
run: npm install
|
||||
|
||||
- name: Build Docusaurus
|
||||
working-directory: ./docs
|
||||
run: npm run build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./docs/build
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -0,0 +1,115 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
macos:
|
||||
name: macOS
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Verify tag version
|
||||
shell: bash
|
||||
run: |
|
||||
tag_version="${GITHUB_REF_NAME#v}"
|
||||
package_version="$(node -p "require('./package.json').version")"
|
||||
if [[ "$tag_version" != "$package_version" ]]; then
|
||||
echo "Tag v$tag_version does not match package.json version $package_version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and publish ad-hoc macOS artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
npm run build:assets
|
||||
npx electron-builder --config build/electron-builder.local.cjs --mac --publish always
|
||||
|
||||
windows:
|
||||
name: Windows
|
||||
needs: macos
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Verify tag version
|
||||
shell: bash
|
||||
run: |
|
||||
tag_version="${GITHUB_REF_NAME#v}"
|
||||
package_version="$(node -p "require('./package.json').version")"
|
||||
if [[ "$tag_version" != "$package_version" ]]; then
|
||||
echo "Tag v$tag_version does not match package.json version $package_version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and publish Windows artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
npm run build:assets
|
||||
npx electron-builder --win --publish always
|
||||
|
||||
linux:
|
||||
name: Linux
|
||||
needs: macos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Verify tag version
|
||||
shell: bash
|
||||
run: |
|
||||
tag_version="${GITHUB_REF_NAME#v}"
|
||||
package_version="$(node -p "require('./package.json').version")"
|
||||
if [[ "$tag_version" != "$package_version" ]]; then
|
||||
echo "Tag v$tag_version does not match package.json version $package_version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and publish Linux artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
npm run build:assets
|
||||
npx electron-builder --linux AppImage --publish always
|
||||
@@ -1,12 +1,16 @@
|
||||
node_modules
|
||||
.env
|
||||
log.txt
|
||||
.idea
|
||||
dist
|
||||
.DS_Store
|
||||
.vscode
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
# Documentation build output
|
||||
docs/build
|
||||
docs/.docusaurus
|
||||
node_modules
|
||||
dist
|
||||
release
|
||||
*.log
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.idea
|
||||
.claude
|
||||
.bot-gateway-state
|
||||
.agent-data
|
||||
tmp
|
||||
release-local
|
||||
logs
|
||||
.opencat
|
||||
@@ -1,19 +0,0 @@
|
||||
src
|
||||
node_modules
|
||||
.claude
|
||||
CLAUDE.md
|
||||
screenshoots
|
||||
.DS_Store
|
||||
.vscode
|
||||
.idea
|
||||
.env
|
||||
.blog
|
||||
docs
|
||||
.log
|
||||
blog
|
||||
config.json
|
||||
ui
|
||||
scripts
|
||||
packages
|
||||
custom-router.example.js
|
||||
examples
|
||||
@@ -1,248 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Claude Code Router is a tool that routes Claude Code requests to different LLM providers. It uses a Monorepo architecture with four main packages:
|
||||
|
||||
- **cli** (`@musistudio/claude-code-router`): Command-line tool providing the `ccr` command
|
||||
- **server** (`@CCR/server`): Core server handling API routing and transformations
|
||||
- **shared** (`@CCR/shared`): Shared constants, utilities, and preset management
|
||||
- **ui** (`@CCR/ui`): Web management interface (React + Vite)
|
||||
|
||||
## Build Commands
|
||||
|
||||
### Build all packages
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Build individual packages
|
||||
```bash
|
||||
pnpm build:cli # Build CLI
|
||||
pnpm build:server # Build Server
|
||||
pnpm build:ui # Build UI
|
||||
```
|
||||
|
||||
### Development mode
|
||||
```bash
|
||||
pnpm dev:cli # Develop CLI (ts-node)
|
||||
pnpm dev:server # Develop Server (ts-node)
|
||||
pnpm dev:ui # Develop UI (Vite)
|
||||
```
|
||||
|
||||
### Publish
|
||||
```bash
|
||||
pnpm release # Build and publish all packages
|
||||
```
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### 1. Routing System (packages/server/src/utils/router.ts)
|
||||
|
||||
The routing logic determines which model a request should be sent to:
|
||||
|
||||
- **Default routing**: Uses `Router.default` configuration
|
||||
- **Project-level routing**: Checks `~/.claude/projects/<project-id>/claude-code-router.json`
|
||||
- **Custom routing**: Loads custom JavaScript router function via `CUSTOM_ROUTER_PATH`
|
||||
- **Built-in scenario routing**:
|
||||
- `background`: Background tasks (typically lightweight models)
|
||||
- `think`: Thinking-intensive tasks (Plan Mode)
|
||||
- `longContext`: Long context (exceeds `longContextThreshold` tokens)
|
||||
- `webSearch`: Web search tasks
|
||||
- `image`: Image-related tasks
|
||||
|
||||
Token calculation uses `tiktoken` (cl100k_base) to estimate request size.
|
||||
|
||||
### 2. Transformer System
|
||||
|
||||
The project uses the `@musistudio/llms` package (external dependency) to handle request/response transformations. Transformers adapt to different provider API differences:
|
||||
|
||||
- Built-in transformers: `anthropic`, `deepseek`, `gemini`, `openrouter`, `groq`, `maxtoken`, `tooluse`, `reasoning`, `enhancetool`, etc.
|
||||
- Custom transformers: Load external plugins via `transformers` array in `config.json`
|
||||
|
||||
Transformer configuration supports:
|
||||
- Global application (provider level)
|
||||
- Model-specific application
|
||||
- Option passing (e.g., `max_tokens` parameter for `maxtoken`)
|
||||
|
||||
### 3. Agent System (packages/server/src/agents/)
|
||||
|
||||
Agents are pluggable feature modules that can:
|
||||
- Detect whether to handle a request (`shouldHandle`)
|
||||
- Modify requests (`reqHandler`)
|
||||
- Provide custom tools (`tools`)
|
||||
|
||||
Built-in agents:
|
||||
- **imageAgent**: Handles image-related tasks
|
||||
|
||||
Agent tool call flow:
|
||||
1. Detect and mark agents in `preHandler` hook
|
||||
2. Add agent tools to the request
|
||||
3. Intercept tool call events in `onSend` hook
|
||||
4. Execute agent tool and initiate new LLM request
|
||||
5. Stream results back
|
||||
|
||||
### 4. SSE Stream Processing
|
||||
|
||||
The server uses custom Transform streams to handle Server-Sent Events:
|
||||
- `SSEParserTransform`: Parses SSE text stream into event objects
|
||||
- `SSESerializerTransform`: Serializes event objects into SSE text stream
|
||||
- `rewriteStream`: Intercepts and modifies stream data (for agent tool calls)
|
||||
|
||||
### 5. Configuration Management
|
||||
|
||||
Configuration file location: `~/.claude-code-router/config.json`
|
||||
|
||||
Key features:
|
||||
- Supports environment variable interpolation (`$VAR_NAME` or `${VAR_NAME}`)
|
||||
- JSON5 format (supports comments)
|
||||
- Automatic backups (keeps last 3 backups)
|
||||
- Hot reload requires service restart (`ccr restart`)
|
||||
|
||||
Configuration validation:
|
||||
- If `Providers` are configured, both `HOST` and `APIKEY` must be set
|
||||
- Otherwise listens on `0.0.0.0` without authentication
|
||||
|
||||
### 6. Logging System
|
||||
|
||||
Two separate logging systems:
|
||||
|
||||
**Server-level logs** (pino):
|
||||
- Location: `~/.claude-code-router/logs/ccr-*.log`
|
||||
- Content: HTTP requests, API calls, server events
|
||||
- Configuration: `LOG_LEVEL` (fatal/error/warn/info/debug/trace)
|
||||
|
||||
**Application-level logs**:
|
||||
- Location: `~/.claude-code-router/claude-code-router.log`
|
||||
- Content: Routing decisions, business logic events
|
||||
|
||||
## CLI Commands
|
||||
|
||||
```bash
|
||||
ccr start # Start server
|
||||
ccr stop # Stop server
|
||||
ccr restart # Restart server
|
||||
ccr status # Show status
|
||||
ccr code # Execute claude command
|
||||
ccr model # Interactive model selection and configuration
|
||||
ccr preset # Manage presets (export, install, list, info, delete)
|
||||
ccr activate # Output shell environment variables (for integration)
|
||||
ccr ui # Open Web UI
|
||||
ccr statusline # Integrated statusline (reads JSON from stdin)
|
||||
```
|
||||
|
||||
### Preset Commands
|
||||
|
||||
```bash
|
||||
ccr preset export <name> # Export current configuration as a preset
|
||||
ccr preset install <source> # Install a preset from file, URL, or name
|
||||
ccr preset list # List all installed presets
|
||||
ccr preset info <name> # Show preset information
|
||||
ccr preset delete <name> # Delete a preset
|
||||
```
|
||||
|
||||
## Subagent Routing
|
||||
|
||||
Use special tags in subagent prompts to specify models:
|
||||
```
|
||||
<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>
|
||||
Please help me analyze this code...
|
||||
```
|
||||
|
||||
## Preset System
|
||||
|
||||
The preset system allows users to save, share, and reuse configurations easily.
|
||||
|
||||
### Preset Structure
|
||||
|
||||
Presets are stored in `~/.claude-code-router/presets/<preset-name>/manifest.json`
|
||||
|
||||
Each preset contains:
|
||||
- **Metadata**: name, version, description, author, keywords, etc.
|
||||
- **Configuration**: Providers, Router, transformers, and other settings
|
||||
- **Dynamic Schema** (optional): Input fields for collecting required information during installation
|
||||
- **Required Inputs** (optional): Fields that need to be filled during installation (e.g., API keys)
|
||||
|
||||
### Core Functions
|
||||
|
||||
Located in `packages/shared/src/preset/`:
|
||||
|
||||
- **export.ts**: Export current configuration as a preset directory
|
||||
- `exportPreset(presetName, config, options)`: Creates preset directory with manifest.json
|
||||
- Automatically sanitizes sensitive data (api_key fields become `{{field}}` placeholders)
|
||||
|
||||
- **install.ts**: Install and manage presets
|
||||
- `installPreset(preset, config, options)`: Install preset to config
|
||||
- `loadPreset(source)`: Load preset from directory
|
||||
- `listPresets()`: List all installed presets
|
||||
- `isPresetInstalled(presetName)`: Check if preset is installed
|
||||
- `validatePreset(preset)`: Validate preset structure
|
||||
|
||||
- **merge.ts**: Merge preset configuration with existing config
|
||||
- Handles conflicts using different strategies (ask, overwrite, merge, skip)
|
||||
|
||||
- **sensitiveFields.ts**: Identify and sanitize sensitive fields
|
||||
- Detects api_key, password, secret fields automatically
|
||||
- Replaces sensitive values with environment variable placeholders
|
||||
|
||||
### Preset File Format
|
||||
|
||||
**manifest.json** (in preset directory):
|
||||
```json
|
||||
{
|
||||
"name": "my-preset",
|
||||
"version": "1.0.0",
|
||||
"description": "My configuration",
|
||||
"author": "Author Name",
|
||||
"keywords": ["openai", "production"],
|
||||
"Providers": [...],
|
||||
"Router": {...},
|
||||
"schema": [
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "OpenAI API Key",
|
||||
"prompt": "Enter your OpenAI API key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### CLI Integration
|
||||
|
||||
The CLI layer (`packages/cli/src/utils/preset/`) handles:
|
||||
- User interaction and prompts
|
||||
- File operations
|
||||
- Display formatting
|
||||
|
||||
Key files:
|
||||
- `commands.ts`: Command handlers for `ccr preset` subcommands
|
||||
- `export.ts`: CLI wrapper for export functionality
|
||||
- `install.ts`: CLI wrapper for install functionality
|
||||
|
||||
## Dependencies
|
||||
|
||||
```
|
||||
cli → server → shared
|
||||
server → @musistudio/llms (core routing and transformation logic)
|
||||
ui (standalone frontend application)
|
||||
```
|
||||
|
||||
## Development Notes
|
||||
|
||||
1. **Node.js version**: Requires >= 18.0.0
|
||||
2. **Package manager**: Uses pnpm (monorepo depends on workspace protocol)
|
||||
3. **TypeScript**: All packages use TypeScript, but UI package is ESM module
|
||||
4. **Build tools**:
|
||||
- cli/server/shared: esbuild
|
||||
- ui: Vite + TypeScript
|
||||
5. **@musistudio/llms**: This is an external dependency package providing the core server framework and transformer functionality, type definitions in `packages/server/src/types.d.ts`
|
||||
6. **Code comments**: All comments in code MUST be written in English
|
||||
7. **Documentation**: When implementing new features, add documentation to the docs project instead of creating standalone md files
|
||||
|
||||
## Configuration Example Locations
|
||||
|
||||
- Main configuration example: Complete example in README.md
|
||||
- Custom router example: `custom-router.example.js`
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 musistudio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,586 +1,186 @@
|
||||

|
||||
<h1 align="center">Claude Code Router Desktop</h1>
|
||||
|
||||
[](README_zh.md)
|
||||
[](https://discord.gg/rdftVMaUcS)
|
||||
[](https://github.com/musistudio/claude-code-router/blob/main/LICENSE)
|
||||
<p align="center">
|
||||
<a href="README_zh.md"><img alt="Chinese README" src="https://img.shields.io/badge/%F0%9F%87%A8%F0%9F%87%B3-%E4%B8%AD%E6%96%87%E7%89%88-ff0000?style=flat" /></a>
|
||||
<a href="https://discord.gg/rdftVMaUcS"><img alt="Discord" src="https://img.shields.io/badge/Discord-%235865F2.svg?&logo=discord&logoColor=white" /></a>
|
||||
<a href="https://github.com/musistudio/claude-code-router/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/musistudio/claude-code-router" /></a>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<p align="center">
|
||||
<img src="blog/images/sponsors/glm-en.jpg" alt="GLM CODING PLAN sponsor" />
|
||||
</p>
|
||||
|
||||

|
||||
> This project is sponsored by Z.ai, supporting us with their GLM CODING PLAN.
|
||||
> **Sponsored by Z.ai.** This project is sponsored by Z.ai, supporting us with their GLM CODING PLAN.
|
||||
|
||||
> GLM CODING PLAN is a subscription service designed for AI coding, starting at just $10/month. It provides access to their flagship GLM-4.7 & (GLM-5 Only Available for Pro Users)model across 10+ popular AI coding tools (Claude Code, Cline, Roo Code, etc.), offering developers top-tier, fast, and stable coding experiences.
|
||||
> GLM CODING PLAN is a subscription service designed for AI coding, starting at just $10/month. It provides access to their flagship GLM-4.7 & (GLM-5 Only Available for Pro Users) model across 10+ popular AI coding tools (Claude Code, Cline, Roo Code, etc.), offering developers top-tier, fast, and stable coding experiences.
|
||||
|
||||
> Get 10% OFF GLM CODING PLAN:https://z.ai/subscribe?ic=8JVLJQFSKB
|
||||
> Get 10% OFF GLM CODING PLAN: https://z.ai/subscribe?ic=8JVLJQFSKB
|
||||
|
||||
> [Progressive Disclosure of Agent Tools from the Perspective of CLI Tool Style](/blog/en/progressive-disclosure-of-agent-tools-from-the-perspective-of-cli-tool-style.md)
|
||||
Claude Code Router Desktop is a local gateway and desktop control panel for routing agent requests from Claude Code, Codex, ZCode, and compatible clients to the model provider you actually want to use.
|
||||
|
||||
> A powerful tool to route Claude Code requests to different models and customize any request.
|
||||
CCR runs on your machine, keeps provider configuration in your local config directory, and exposes a local gateway at `http://127.0.0.1:3456`.
|
||||
|
||||

|
||||
## Why Use CCR
|
||||
|
||||
## ✨ Features
|
||||
- Use one local endpoint for multiple agent tools instead of configuring every client separately.
|
||||
- Route different workloads to different models, such as fast background work, reasoning tasks, long-context requests, image tasks, or web-search-capable models.
|
||||
- Mix providers without changing your workflow. CCR supports OpenAI-compatible APIs, Anthropic Messages, Gemini Generate Content, OpenRouter, DeepSeek, SiliconFlow, Moonshot, Mistral, Z.AI, Bailian, and custom providers.
|
||||
- Control cost and reliability with fallback routing, API key rotation, usage statistics, and request logs.
|
||||
- Manage everything from a desktop UI instead of editing JSON by hand.
|
||||
- Extend the gateway with plugins, proxy routes, local HTTP backends, and provider deeplinks.
|
||||
|
||||
- **Model Routing**: Route requests to different models based on your needs (e.g., background tasks, thinking, long context).
|
||||
- **Multi-Provider Support**: Supports various model providers like OpenRouter, DeepSeek, Ollama, Gemini, Volcengine, and SiliconFlow.
|
||||
- **Request/Response Transformation**: Customize requests and responses for different providers using transformers.
|
||||
- **Dynamic Model Switching**: Switch models on-the-fly within Claude Code using the `/model` command.
|
||||
- **CLI Model Management**: Manage models and providers directly from the terminal with `ccr model`.
|
||||
- **GitHub Actions Integration**: Trigger Claude Code tasks in your GitHub workflows.
|
||||
- **Plugin System**: Extend functionality with custom transformers.
|
||||
## Features
|
||||
|
||||
## 🚀 Getting Started
|
||||
- **Desktop dashboard**: start or stop the local gateway, inspect usage, configure the tray window, and manage runtime settings.
|
||||
- **Provider management**: add provider presets or custom endpoints, test connectivity, manage credentials, and monitor supported account balances where available.
|
||||
- **Routing rules**: set default, background, thinking, long-context, image, web-search, subagent, model-prefix, and conditional routing rules.
|
||||
- **Agent profiles**: configure Claude Code, Codex, and ZCode profiles that point to the CCR gateway.
|
||||
- **Gateway compatibility**: translate client requests through the local CCR wrapper and the core gateway runtime.
|
||||
- **Proxy mode**: capture supported API traffic through a local proxy with optional system proxy integration and network capture.
|
||||
- **Plugins**: install or load wrapper plugins, including routes for Claude Design and Cursor Proxy style integrations.
|
||||
- **Virtual models**: expose aliases or composed model profiles for clients that expect a specific model name.
|
||||
- **Provider deeplinks**: import provider configuration through `ccr://provider?...` links after user confirmation.
|
||||
|
||||
### 1. Installation
|
||||
## Download And Install
|
||||
|
||||
First, ensure you have [Claude Code](https://docs.anthropic.com/en/docs/claude-code/quickstart) installed:
|
||||
1. Open the [GitHub Releases page](https://github.com/musistudio/claude-code-router/releases).
|
||||
2. Download the package for your platform:
|
||||
- macOS: `Claude Code Router_<version>.dmg` or `.zip`
|
||||
- Windows: `Claude Code Router_<version>.exe`
|
||||
- Linux: `Claude Code Router_<version>.AppImage`
|
||||
3. Install and launch **Claude Code Router**.
|
||||
4. On first launch, CCR creates its local configuration:
|
||||
- macOS/Linux: `~/.claude-code-router/config.json`
|
||||
- Windows: `%APPDATA%\Claude Code Router\config.json`
|
||||
|
||||
```shell
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
CCR starts two local services when the gateway is enabled:
|
||||
|
||||
- CCR wrapper gateway: `http://127.0.0.1:3456`
|
||||
- Core gateway runtime: `http://127.0.0.1:3457`
|
||||
|
||||
## Quick Start
|
||||
|
||||
CCR can be configured entirely from the desktop UI. Use this setup order for a clean first run.
|
||||
|
||||
### 1. Add a provider
|
||||
|
||||
Open **Providers**, click **Add Provider**, then choose a built-in preset or create a custom provider. Fill in the provider name, endpoint, protocol, API key, and model list in the form. Use the connectivity check when available, then save the provider.
|
||||
|
||||
### 2. Configure routing
|
||||
|
||||
Open **Routing** and select which provider/model should handle the default route. Then fill optional routes for background work, thinking requests, long-context requests, image tasks, and web search if you want different models for those scenarios.
|
||||
|
||||
Use **Add Routing Rule** when you need more control, such as model-prefix routing, subagent routing, request conditions, or fallback behavior.
|
||||
|
||||
### 3. Start the gateway
|
||||
|
||||
Open **Server** and click **Start**. Enable auto start if you want CCR to start the local gateway whenever the desktop app opens.
|
||||
|
||||
### 4. Connect your agent tool
|
||||
|
||||
Open **Profiles** and choose the client you want to use. Configure the Claude Code, Codex, or ZCode profile from the form, select the target model, and apply the profile. For app-based profiles, use the profile action button to open the target app through CCR.
|
||||
|
||||
### 5. Monitor and adjust
|
||||
|
||||
Use **Dashboard** for usage and provider health, the tray window for quick token and account status, **Network Logs** for debugging provider behavior, and **Extensions** for plugin configuration.
|
||||
|
||||
## Provider Deeplink
|
||||
|
||||
Provider websites can open CCR and import a model provider with a custom protocol link:
|
||||
|
||||
```text
|
||||
ccr://provider?name=Example%20AI&base_url=https%3A%2F%2Fapi.example.com%2Fv1&api_key=sk-example&models=example-chat%2Cexample-coder&protocol=openai_chat_completions
|
||||
```
|
||||
|
||||
Then, install Claude Code Router:
|
||||
Supported query parameters:
|
||||
|
||||
```shell
|
||||
npm install -g @musistudio/claude-code-router
|
||||
```
|
||||
- `name`: display name for the provider.
|
||||
- `base_url`: provider API base URL. Aliases: `baseUrl`, `api_base_url`, `url`, `endpoint`.
|
||||
- `api_key`: optional provider API key. Aliases: `apiKey`, `apikey`, `key`, `token`.
|
||||
- `models`: comma-separated or newline-separated model list. You can also repeat `model=...`.
|
||||
- `protocol`: one of `openai_chat_completions`, `openai_responses`, `anthropic_messages`, or `gemini_generate_content`.
|
||||
|
||||
### 2. Configuration
|
||||
For larger payloads, pass `payload` as URL-encoded JSON or base64url JSON with the same fields. CCR always opens a confirmation dialog before writing a provider imported from an external link.
|
||||
|
||||
Create and configure your `~/.claude-code-router/config.json` file. For more details, you can refer to `config.example.json`.
|
||||
## Plugins
|
||||
|
||||
The `config.json` file has several key sections:
|
||||
CCR has two plugin layers:
|
||||
|
||||
- **`PROXY_URL`** (optional): You can set a proxy for API requests, for example: `"PROXY_URL": "http://127.0.0.1:7890"`.
|
||||
- **`LOG`** (optional): You can enable logging by setting it to `true`. When set to `false`, no log files will be created. Default is `true`.
|
||||
- **`LOG_LEVEL`** (optional): Set the logging level. Available options are: `"fatal"`, `"error"`, `"warn"`, `"info"`, `"debug"`, `"trace"`. Default is `"debug"`.
|
||||
- **Logging Systems**: The Claude Code Router uses two separate logging systems:
|
||||
- **Server-level logs**: HTTP requests, API calls, and server events are logged using pino in the `~/.claude-code-router/logs/` directory with filenames like `ccr-*.log`
|
||||
- **Application-level logs**: Routing decisions and business logic events are logged in `~/.claude-code-router/claude-code-router.log`
|
||||
- **`APIKEY`** (optional): You can set a secret key to authenticate requests. When set, clients must provide this key in the `Authorization` header (e.g., `Bearer your-secret-key`) or the `x-api-key` header. Example: `"APIKEY": "your-secret-key"`.
|
||||
- **`HOST`** (optional): You can set the host address for the server. If `APIKEY` is not set, the host will be forced to `127.0.0.1` for security reasons to prevent unauthorized access. Example: `"HOST": "0.0.0.0"`.
|
||||
- **`NON_INTERACTIVE_MODE`** (optional): When set to `true`, enables compatibility with non-interactive environments like GitHub Actions, Docker containers, or other CI/CD systems. This sets appropriate environment variables (`CI=true`, `FORCE_COLOR=0`, etc.) and configures stdin handling to prevent the process from hanging in automated environments. Example: `"NON_INTERACTIVE_MODE": true`.
|
||||
- Core gateway plugins: use `providerPlugins` and `virtualModelProfiles`; these are passed through to the core gateway.
|
||||
- Wrapper plugins: use top-level `plugins` to extend the Electron wrapper, register local HTTP backends, add gateway routes, and route proxy-mode traffic to plugin backends.
|
||||
|
||||
- **`Providers`**: Used to configure different model providers.
|
||||
- **`Router`**: Used to set up routing rules. `default` specifies the default model, which will be used for all requests if no other route is configured.
|
||||
- **`API_TIMEOUT_MS`**: Specifies the timeout for API calls in milliseconds.
|
||||
|
||||
#### Environment Variable Interpolation
|
||||
|
||||
Claude Code Router supports environment variable interpolation for secure API key management. You can reference environment variables in your `config.json` using either `$VAR_NAME` or `${VAR_NAME}` syntax:
|
||||
Example wrapper plugin route:
|
||||
|
||||
```json
|
||||
{
|
||||
"OPENAI_API_KEY": "$OPENAI_API_KEY",
|
||||
"GEMINI_API_KEY": "${GEMINI_API_KEY}",
|
||||
"Providers": [
|
||||
"plugins": [
|
||||
{
|
||||
"name": "openai",
|
||||
"api_base_url": "https://api.openai.com/v1/chat/completions",
|
||||
"api_key": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-5", "gpt-5-mini"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to keep sensitive API keys in environment variables instead of hardcoding them in configuration files. The interpolation works recursively through nested objects and arrays.
|
||||
|
||||
Here is a comprehensive example:
|
||||
|
||||
```json
|
||||
{
|
||||
"APIKEY": "your-secret-key",
|
||||
"PROXY_URL": "http://127.0.0.1:7890",
|
||||
"LOG": true,
|
||||
"API_TIMEOUT_MS": 600000,
|
||||
"NON_INTERACTIVE_MODE": false,
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openrouter",
|
||||
"api_base_url": "https://openrouter.ai/api/v1/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": [
|
||||
"google/gemini-2.5-pro-preview",
|
||||
"anthropic/claude-sonnet-4",
|
||||
"anthropic/claude-3.5-sonnet",
|
||||
"anthropic/claude-3.7-sonnet:thinking"
|
||||
],
|
||||
"transformer": {
|
||||
"use": ["openrouter"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["deepseek-chat", "deepseek-reasoner"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"],
|
||||
"deepseek-chat": {
|
||||
"use": ["tooluse"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ollama",
|
||||
"api_base_url": "http://localhost:11434/v1/chat/completions",
|
||||
"api_key": "ollama",
|
||||
"models": ["qwen2.5-coder:latest"]
|
||||
},
|
||||
{
|
||||
"name": "gemini",
|
||||
"api_base_url": "https://generativelanguage.googleapis.com/v1beta/models/",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["gemini-2.5-flash", "gemini-2.5-pro"],
|
||||
"transformer": {
|
||||
"use": ["gemini"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "volcengine",
|
||||
"api_base_url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["deepseek-v3-250324", "deepseek-r1-250528"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "modelscope",
|
||||
"api_base_url": "https://api-inference.modelscope.cn/v1/chat/completions",
|
||||
"api_key": "",
|
||||
"models": ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507"],
|
||||
"transformer": {
|
||||
"use": [
|
||||
[
|
||||
"maxtoken",
|
||||
{
|
||||
"max_tokens": 65536
|
||||
}
|
||||
],
|
||||
"enhancetool"
|
||||
],
|
||||
"Qwen/Qwen3-235B-A22B-Thinking-2507": {
|
||||
"use": ["reasoning"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dashscope",
|
||||
"api_base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"api_key": "",
|
||||
"models": ["qwen3-coder-plus"],
|
||||
"transformer": {
|
||||
"use": [
|
||||
[
|
||||
"maxtoken",
|
||||
{
|
||||
"max_tokens": 65536
|
||||
}
|
||||
],
|
||||
"enhancetool"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aihubmix",
|
||||
"api_base_url": "https://aihubmix.com/v1/chat/completions",
|
||||
"api_key": "sk-",
|
||||
"models": [
|
||||
"Z/glm-4.5",
|
||||
"claude-opus-4-20250514",
|
||||
"gemini-2.5-pro"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat",
|
||||
"background": "ollama,qwen2.5-coder:latest",
|
||||
"think": "deepseek,deepseek-reasoner",
|
||||
"longContext": "openrouter,google/gemini-2.5-pro-preview",
|
||||
"longContextThreshold": 60000,
|
||||
"webSearch": "gemini,gemini-2.5-flash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Running Claude Code with the Router
|
||||
|
||||
Start Claude Code using the router:
|
||||
|
||||
```shell
|
||||
ccr code
|
||||
```
|
||||
|
||||
> **Note**: After modifying the configuration file, you need to restart the service for the changes to take effect:
|
||||
>
|
||||
> ```shell
|
||||
> ccr restart
|
||||
> ```
|
||||
|
||||
### 4. UI Mode
|
||||
|
||||
For a more intuitive experience, you can use the UI mode to manage your configuration:
|
||||
|
||||
```shell
|
||||
ccr ui
|
||||
```
|
||||
|
||||
This will open a web-based interface where you can easily view and edit your `config.json` file.
|
||||
|
||||

|
||||
|
||||
### 5. CLI Model Management
|
||||
|
||||
For users who prefer terminal-based workflows, you can use the interactive CLI model selector:
|
||||
|
||||
```shell
|
||||
ccr model
|
||||
```
|
||||

|
||||
|
||||
This command provides an interactive interface to:
|
||||
|
||||
- View current configuration:
|
||||
- See all configured models (default, background, think, longContext, webSearch, image)
|
||||
- Switch models: Quickly change which model is used for each router type
|
||||
- Add new models: Add models to existing providers
|
||||
- Create new providers: Set up complete provider configurations including:
|
||||
- Provider name and API endpoint
|
||||
- API key
|
||||
- Available models
|
||||
- Transformer configuration with support for:
|
||||
- Multiple transformers (openrouter, deepseek, gemini, etc.)
|
||||
- Transformer options (e.g., maxtoken with custom limits)
|
||||
- Provider-specific routing (e.g., OpenRouter provider preferences)
|
||||
|
||||
The CLI tool validates all inputs and provides helpful prompts to guide you through the configuration process, making it easy to manage complex setups without editing JSON files manually.
|
||||
|
||||
### 6. Presets Management
|
||||
|
||||
Presets allow you to save, share, and reuse configurations easily. You can export your current configuration as a preset and install presets from files or URLs.
|
||||
|
||||
```shell
|
||||
# Export current configuration as a preset
|
||||
ccr preset export my-preset
|
||||
|
||||
# Export with metadata
|
||||
ccr preset export my-preset --description "My OpenAI config" --author "Your Name" --tags "openai,production"
|
||||
|
||||
# Install a preset from local directory
|
||||
ccr preset install /path/to/preset
|
||||
|
||||
# List all installed presets
|
||||
ccr preset list
|
||||
|
||||
# Show preset information
|
||||
ccr preset info my-preset
|
||||
|
||||
# Delete a preset
|
||||
ccr preset delete my-preset
|
||||
```
|
||||
|
||||
**Preset Features:**
|
||||
- **Export**: Save your current configuration as a preset directory (with manifest.json)
|
||||
- **Install**: Install presets from local directories
|
||||
- **Sensitive Data Handling**: API keys and other sensitive data are automatically sanitized during export (marked as `{{field}}` placeholders)
|
||||
- **Dynamic Configuration**: Presets can include input schemas for collecting required information during installation
|
||||
- **Version Control**: Each preset includes version metadata for tracking updates
|
||||
|
||||
**Preset File Structure:**
|
||||
```
|
||||
~/.claude-code-router/presets/
|
||||
├── my-preset/
|
||||
│ └── manifest.json # Contains configuration and metadata
|
||||
```
|
||||
|
||||
### 7. Activate Command (Environment Variables Setup)
|
||||
|
||||
The `activate` command allows you to set up environment variables globally in your shell, enabling you to use the `claude` command directly or integrate Claude Code Router with applications built using the Agent SDK.
|
||||
|
||||
To activate the environment variables, run:
|
||||
|
||||
```shell
|
||||
eval "$(ccr activate)"
|
||||
```
|
||||
|
||||
This command outputs the necessary environment variables in shell-friendly format, which are then set in your current shell session. After activation, you can:
|
||||
|
||||
- **Use `claude` command directly**: Run `claude` commands without needing to use `ccr code`. The `claude` command will automatically route requests through Claude Code Router.
|
||||
- **Integrate with Agent SDK applications**: Applications built with the Anthropic Agent SDK will automatically use the configured router and models.
|
||||
|
||||
The `activate` command sets the following environment variables:
|
||||
|
||||
- `ANTHROPIC_AUTH_TOKEN`: API key from your configuration
|
||||
- `ANTHROPIC_BASE_URL`: The local router endpoint (default: `http://127.0.0.1:3456`)
|
||||
- `NO_PROXY`: Set to `127.0.0.1` to prevent proxy interference
|
||||
- `DISABLE_TELEMETRY`: Disables telemetry
|
||||
- `DISABLE_COST_WARNINGS`: Disables cost warnings
|
||||
- `API_TIMEOUT_MS`: API timeout from your configuration
|
||||
|
||||
> **Note**: Make sure the Claude Code Router service is running (`ccr start`) before using the activated environment variables. The environment variables are only valid for the current shell session. To make them persistent, you can add `eval "$(ccr activate)"` to your shell configuration file (e.g., `~/.zshrc` or `~/.bashrc`).
|
||||
|
||||
#### Providers
|
||||
|
||||
The `Providers` array is where you define the different model providers you want to use. Each provider object requires:
|
||||
|
||||
- `name`: A unique name for the provider.
|
||||
- `api_base_url`: The full API endpoint for chat completions.
|
||||
- `api_key`: Your API key for the provider.
|
||||
- `models`: A list of model names available from this provider.
|
||||
- `transformer` (optional): Specifies transformers to process requests and responses.
|
||||
|
||||
#### Transformers
|
||||
|
||||
Transformers allow you to modify the request and response payloads to ensure compatibility with different provider APIs.
|
||||
|
||||
- **Global Transformer**: Apply a transformer to all models from a provider. In this example, the `openrouter` transformer is applied to all models under the `openrouter` provider.
|
||||
```json
|
||||
{
|
||||
"name": "openrouter",
|
||||
"api_base_url": "https://openrouter.ai/api/v1/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": [
|
||||
"google/gemini-2.5-pro-preview",
|
||||
"anthropic/claude-sonnet-4",
|
||||
"anthropic/claude-3.5-sonnet"
|
||||
],
|
||||
"transformer": { "use": ["openrouter"] }
|
||||
}
|
||||
```
|
||||
- **Model-Specific Transformer**: Apply a transformer to a specific model. In this example, the `deepseek` transformer is applied to all models, and an additional `tooluse` transformer is applied only to the `deepseek-chat` model.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["deepseek-chat", "deepseek-reasoner"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"],
|
||||
"deepseek-chat": { "use": ["tooluse"] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Passing Options to a Transformer**: Some transformers, like `maxtoken`, accept options. To pass options, use a nested array where the first element is the transformer name and the second is an options object.
|
||||
```json
|
||||
{
|
||||
"name": "siliconflow",
|
||||
"api_base_url": "https://api.siliconflow.cn/v1/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["moonshotai/Kimi-K2-Instruct"],
|
||||
"transformer": {
|
||||
"use": [
|
||||
[
|
||||
"maxtoken",
|
||||
"id": "local-admin-api",
|
||||
"enabled": true,
|
||||
"proxy": {
|
||||
"routes": [
|
||||
{
|
||||
"max_tokens": 16384
|
||||
"id": "admin-api",
|
||||
"host": "api.example.com",
|
||||
"paths": ["/v1/admin"],
|
||||
"upstream": "http://127.0.0.1:4510",
|
||||
"stripPathPrefix": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Available Built-in Transformers:**
|
||||
|
||||
- `Anthropic`:If you use only the `Anthropic` transformer, it will preserve the original request and response parameters(you can use it to connect directly to an Anthropic endpoint).
|
||||
- `deepseek`: Adapts requests/responses for DeepSeek API.
|
||||
- `gemini`: Adapts requests/responses for Gemini API.
|
||||
- `openrouter`: Adapts requests/responses for OpenRouter API. It can also accept a `provider` routing parameter to specify which underlying providers OpenRouter should use. For more details, refer to the [OpenRouter documentation](https://openrouter.ai/docs/features/provider-routing). See an example below:
|
||||
```json
|
||||
"transformer": {
|
||||
"use": ["openrouter"],
|
||||
"moonshotai/kimi-k2": {
|
||||
"use": [
|
||||
[
|
||||
"openrouter",
|
||||
{
|
||||
"provider": {
|
||||
"only": ["moonshotai/fp8"]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
- `groq`: Adapts requests/responses for groq API.
|
||||
- `maxtoken`: Sets a specific `max_tokens` value.
|
||||
- `tooluse`: Optimizes tool usage for certain models via `tool_choice`.
|
||||
- `gemini-cli` (experimental): Unofficial support for Gemini via Gemini CLI [gemini-cli.js](https://gist.github.com/musistudio/1c13a65f35916a7ab690649d3df8d1cd).
|
||||
- `reasoning`: Used to process the `reasoning_content` field.
|
||||
- `sampling`: Used to process sampling information fields such as `temperature`, `top_p`, `top_k`, and `repetition_penalty`.
|
||||
- `enhancetool`: Adds a layer of error tolerance to the tool call parameters returned by the LLM (this will cause the tool call information to no longer be streamed).
|
||||
- `cleancache`: Clears the `cache_control` field from requests.
|
||||
- `vertex-gemini`: Handles the Gemini API using Vertex authentication.
|
||||
- `chutes-glm` Unofficial support for GLM 4.5 model via Chutes [chutes-glm-transformer.js](https://gist.github.com/vitobotta/2be3f33722e05e8d4f9d2b0138b8c863).
|
||||
- `qwen-cli` (experimental): Unofficial support for qwen3-coder-plus model via Qwen CLI [qwen-cli.js](https://gist.github.com/musistudio/f5a67841ced39912fd99e42200d5ca8b).
|
||||
- `rovo-cli` (experimental): Unofficial support for gpt-5 via Atlassian Rovo Dev CLI [rovo-cli.js](https://gist.github.com/SaseQ/c2a20a38b11276537ec5332d1f7a5e53).
|
||||
|
||||
**Custom Transformers:**
|
||||
|
||||
You can also create your own transformers and load them via the `transformers` field in `config.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"path": "/User/xxx/.claude-code-router/plugins/gemini-cli.js",
|
||||
"options": {
|
||||
"project": "xxx"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Router
|
||||
Plugin modules export a function or object with `setup(ctx)`. The context supports:
|
||||
|
||||
The `Router` object defines which model to use for different scenarios:
|
||||
- `ctx.registerGatewayRoute({ method, path, auth, handler })`
|
||||
- `ctx.registerHttpBackend({ id, host, port, handler })`
|
||||
- `ctx.registerProxyRoute({ host, paths, upstream, stripPathPrefix, rewritePathPrefix, headers })`
|
||||
- `ctx.openSqliteStore({ filename, migrate })`
|
||||
- `ctx.registerCoreGatewayProviderPlugin(plugin)`
|
||||
- `ctx.registerCoreGatewayVirtualModelProfile(profile)`
|
||||
|
||||
- `default`: The default model for general tasks.
|
||||
- `background`: A model for background tasks. This can be a smaller, local model to save costs.
|
||||
- `think`: A model for reasoning-heavy tasks, like Plan Mode.
|
||||
- `longContext`: A model for handling long contexts (e.g., > 60K tokens).
|
||||
- `longContextThreshold` (optional): The token count threshold for triggering the long context model. Defaults to 60000 if not specified.
|
||||
- `webSearch`: Used for handling web search tasks and this requires the model itself to support the feature. If you're using openrouter, you need to add the `:online` suffix after the model name.
|
||||
- `image` (beta): Used for handling image-related tasks (supported by CCR’s built-in agent). If the model does not support tool calling, you need to set the `config.forceUseImageAgent` property to `true`.
|
||||
Local plugin examples are available in [examples/plugins](examples/plugins).
|
||||
|
||||
- You can also switch models dynamically in Claude Code with the `/model` command:
|
||||
`/model provider_name,model_name`
|
||||
Example: `/model openrouter,anthropic/claude-3.5-sonnet`
|
||||
## Development
|
||||
|
||||
#### Custom Router
|
||||
|
||||
For more advanced routing logic, you can specify a custom router script via the `CUSTOM_ROUTER_PATH` in your `config.json`. This allows you to implement complex routing rules beyond the default scenarios.
|
||||
|
||||
In your `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"CUSTOM_ROUTER_PATH": "/User/xxx/.claude-code-router/custom-router.js"
|
||||
}
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
npm run typecheck
|
||||
npm run build:assets
|
||||
npm run build:app:mac
|
||||
npm run build:app:win
|
||||
```
|
||||
|
||||
The custom router file must be a JavaScript module that exports an `async` function. This function receives the request object and the config object as arguments and should return the provider and model name as a string (e.g., `"provider_name,model_name"`), or `null` to fall back to the default router.
|
||||
`npm run build:assets` compiles the Electron main process and renderer assets into `dist/`.
|
||||
|
||||
Here is an example of a `custom-router.js` based on `custom-router.example.js`:
|
||||
`npm run build` packages the app for the current platform and writes installer artifacts to `release/`.
|
||||
|
||||
```javascript
|
||||
// /User/xxx/.claude-code-router/custom-router.js
|
||||
`npm run build:app:mac` and `npm run build:app:win` package platform-specific app artifacts. Linux AppImage packaging is configured in `electron-builder.json`.
|
||||
|
||||
/**
|
||||
* A custom router function to determine which model to use based on the request.
|
||||
*
|
||||
* @param {object} req - The request object from Claude Code, containing the request body.
|
||||
* @param {object} config - The application's config object.
|
||||
* @returns {Promise<string|null>} - A promise that resolves to the "provider,model_name" string, or null to use the default router.
|
||||
*/
|
||||
module.exports = async function router(req, config) {
|
||||
const userMessage = req.body.messages.find((m) => m.role === "user")?.content;
|
||||
`npm run build:app:mac` creates a local macOS test package in `release-local/` using ad-hoc signing. It is useful with a free Apple Account or Apple Development certificate, but it is not suitable for public distribution because downloaded copies will not pass Gatekeeper notarization checks.
|
||||
|
||||
if (userMessage && userMessage.includes("explain this code")) {
|
||||
// Use a powerful model for code explanation
|
||||
return "openrouter,anthropic/claude-3.5-sonnet";
|
||||
}
|
||||
macOS release builds are signed and notarized for distribution. Before running `npm run build:app:mac:release`, the build machine must have a `Developer ID Application` certificate available through the keychain or `CSC_LINK`/`CSC_KEY_PASSWORD`, full Xcode selected with `xcode-select`, and one notarization credential set:
|
||||
|
||||
// Fallback to the default router configuration
|
||||
return null;
|
||||
};
|
||||
```
|
||||
- `APPLE_API_KEY`, `APPLE_API_KEY_ID`, and `APPLE_API_ISSUER`
|
||||
- `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, and `APPLE_TEAM_ID`
|
||||
- `APPLE_KEYCHAIN_PROFILE`, optionally with `APPLE_KEYCHAIN`
|
||||
|
||||
##### Subagent Routing
|
||||
The macOS packaging hook validates codesigning, the stapled notarization ticket, and Gatekeeper assessment before writing distributable artifacts.
|
||||
|
||||
For routing within subagents, you must specify a particular provider and model by including `<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>` at the **beginning** of the subagent's prompt. This allows you to direct specific subagent tasks to designated models.
|
||||
Packaged builds check GitHub Releases for updates through `electron-updater`. For local update feed testing, set `CCR_UPDATE_FEED_URL` to a generic electron-updater feed URL before starting the app. `CCR_UPDATE_ALLOW_PRERELEASE=1` enables prerelease updates.
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
<CCR-SUBAGENT-MODEL>openrouter,anthropic/claude-3.5-sonnet</CCR-SUBAGENT-MODEL>
|
||||
Please help me analyze this code snippet for potential optimizations...
|
||||
```
|
||||
|
||||
## Status Line (Beta)
|
||||
To better monitor the status of claude-code-router at runtime, version v1.0.40 includes a built-in statusline tool, which you can enable in the UI.
|
||||

|
||||
|
||||
The effect is as follows:
|
||||

|
||||
|
||||
## 🤖 GitHub Actions
|
||||
|
||||
Integrate Claude Code Router into your CI/CD pipeline. After setting up [Claude Code Actions](https://docs.anthropic.com/en/docs/claude-code/github-actions), modify your `.github/workflows/claude.yaml` to use the router:
|
||||
|
||||
```yaml
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
# ... other triggers
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
# ... other conditions
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
mkdir -p $HOME/.claude-code-router
|
||||
cat << 'EOF' > $HOME/.claude-code-router/config.json
|
||||
{
|
||||
"log": true,
|
||||
"NON_INTERACTIVE_MODE": true,
|
||||
"OPENAI_API_KEY": "${{ secrets.OPENAI_API_KEY }}",
|
||||
"OPENAI_BASE_URL": "https://api.deepseek.com",
|
||||
"OPENAI_MODEL": "deepseek-chat"
|
||||
}
|
||||
EOF
|
||||
shell: bash
|
||||
|
||||
- name: Start Claude Code Router
|
||||
run: |
|
||||
nohup ~/.bun/bin/bunx @musistudio/claude-code-router@1.0.8 start &
|
||||
shell: bash
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@beta
|
||||
env:
|
||||
ANTHROPIC_BASE_URL: http://localhost:3456
|
||||
with:
|
||||
anthropic_api_key: "any-string-is-ok"
|
||||
```
|
||||
|
||||
> **Note**: When running in GitHub Actions or other automation environments, make sure to set `"NON_INTERACTIVE_MODE": true` in your configuration to prevent the process from hanging due to stdin handling issues.
|
||||
|
||||
This setup allows for interesting automations, like running tasks during off-peak hours to reduce API costs.
|
||||
|
||||
## 📝 Further Reading
|
||||
## Further Reading
|
||||
|
||||
- [Project Motivation and How It Works](blog/en/project-motivation-and-how-it-works.md)
|
||||
- [Maybe We Can Do More with the Router](blog/en/maybe-we-can-do-more-with-the-route.md)
|
||||
- [GLM-4.6 Supports Reasoning and Interleaved Thinking](blog/en/glm-4.6-supports-reasoning.md)
|
||||
|
||||
## ❤️ Support & Sponsoring
|
||||
## Support & Sponsoring
|
||||
|
||||
If you find this project helpful, please consider sponsoring its development. Your support is greatly appreciated!
|
||||
If you find this project helpful, please consider sponsoring its development. Your support is greatly appreciated.
|
||||
|
||||
[](https://ko-fi.com/F1F31GN2GM)
|
||||
|
||||
@@ -595,13 +195,10 @@ If you find this project helpful, please consider sponsoring its development. Yo
|
||||
|
||||
### Our Sponsors
|
||||
|
||||
A huge thank you to all our sponsors for their generous support!
|
||||
|
||||
A huge thank you to all our sponsors for their generous support.
|
||||
|
||||
- [AIHubmix](https://aihubmix.com/)
|
||||
- [BurnCloud](https://ai.burncloud.com)
|
||||
- [302.AI](https://share.302.ai/ZGVF9w)
|
||||
- [Z智谱](https://www.bigmodel.cn/claude-code?ic=FPF9IVAGFJ)
|
||||
- @Simon Leischnig
|
||||
- [@duanshuaimin](https://github.com/duanshuaimin)
|
||||
- [@vrgitadmin](https://github.com/vrgitadmin)
|
||||
@@ -639,7 +236,7 @@ A huge thank you to all our sponsors for their generous support!
|
||||
- [@congzhangzh](https://github.com/congzhangzh)
|
||||
- @\*\_
|
||||
- @Z\*m
|
||||
- @*鑫
|
||||
- @\*鑫
|
||||
- @c\*y
|
||||
- @\*昕
|
||||
- [@witsice](https://github.com/witsice)
|
||||
@@ -684,23 +281,5 @@ A huge thank you to all our sponsors for their generous support!
|
||||
- @\*\*飞
|
||||
- @\*\*驰
|
||||
- @x\*g
|
||||
- @\*\*东
|
||||
- @\*落
|
||||
- @哆\*k
|
||||
- @\*涛
|
||||
- [@苗大](https://github.com/WitMiao)
|
||||
- @\*呢
|
||||
- @\d*u
|
||||
- @crizcraig
|
||||
- s\*s
|
||||
- \*火
|
||||
- \*勤
|
||||
- \*\*锟
|
||||
- \*涛
|
||||
- \*\*明
|
||||
- \*知
|
||||
- \*语
|
||||
- \*瓜
|
||||
|
||||
|
||||
(If your name is masked, please contact me via my homepage email to update it with your GitHub username.)
|
||||
|
||||
@@ -1,555 +1,186 @@
|
||||

|
||||
<h1 align="center">Claude Code Router Desktop</h1>
|
||||
|
||||
[](README.md)
|
||||
[](https://discord.gg/rdftVMaUcS)
|
||||
[](https://github.com/musistudio/claude-code-router/blob/main/LICENSE)
|
||||
<p align="center">
|
||||
<a href="README.md"><img alt="English README" src="https://img.shields.io/badge/%F0%9F%87%AC%F0%9F%87%A7-English-000aff?style=flat" /></a>
|
||||
<a href="https://discord.gg/rdftVMaUcS"><img alt="Discord" src="https://img.shields.io/badge/Discord-%235865F2.svg?&logo=discord&logoColor=white" /></a>
|
||||
<a href="https://github.com/musistudio/claude-code-router/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/musistudio/claude-code-router" /></a>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<p align="center">
|
||||
<img src="blog/images/sponsors/glm-zh.jpg" alt="GLM CODING PLAN 赞助" />
|
||||
</p>
|
||||
|
||||

|
||||
> 本项目由 Z智谱 提供赞助, 他们通过 GLM CODING PLAN 对本项目提供技术支持。
|
||||
> GLM CODING PLAN 是专为AI编码打造的订阅套餐,每月最低仅需20元,即可在十余款主流AI编码工具如 Claude Code、Cline、Roo Code 中畅享智谱旗舰模型GLM-4.7(受限于算力,目前仅限Pro用户开放),为开发者提供顶尖的编码体验。
|
||||
> 智谱AI为本产品提供了特别优惠,使用以下链接购买可以享受九折优惠:https://www.bigmodel.cn/claude-code?ic=RRVJPB5SII
|
||||
> **Z智谱赞助支持。** 本项目由 Z智谱提供赞助,他们通过 GLM CODING PLAN 对本项目提供技术支持。
|
||||
|
||||
> [从CLI工具风格看工具渐进式披露](/blog/zh/从CLI工具风格看工具渐进式披露.md)
|
||||
> GLM CODING PLAN 是专为 AI 编码打造的订阅套餐,每月最低仅需 20 元,即可在十余款主流 AI 编码工具如 Claude Code、Cline、Roo Code 中畅享智谱旗舰模型 GLM-4.7(受限于算力,目前仅限 Pro 用户开放),为开发者提供顶尖的编码体验。
|
||||
|
||||
> 一款强大的工具,可将 Claude Code 请求路由到不同的模型,并自定义任何请求。
|
||||
> 智谱 AI 为本产品提供了特别优惠,使用以下链接购买可以享受九折优惠:https://www.bigmodel.cn/claude-code?ic=RRVJPB5SII
|
||||
|
||||

|
||||
Claude Code Router Desktop 是一个本地网关和桌面控制台,用来把 Claude Code、Codex、ZCode 以及兼容客户端的 Agent 请求路由到你真正想使用的模型服务。
|
||||
|
||||
CCR 在你的本机运行,Provider 配置保存在本地配置目录,并默认暴露本地网关地址:`http://127.0.0.1:3456`。
|
||||
|
||||
## ✨ 功能
|
||||
## 为什么使用 CCR
|
||||
|
||||
- **模型路由**: 根据您的需求将请求路由到不同的模型(例如,后台任务、思考、长上下文)。
|
||||
- **多提供商支持**: 支持 OpenRouter、DeepSeek、Ollama、Gemini、Volcengine 和 SiliconFlow 等各种模型提供商。
|
||||
- **请求/响应转换**: 使用转换器为不同的提供商自定义请求和响应。
|
||||
- **动态模型切换**: 在 Claude Code 中使用 `/model` 命令动态切换模型。
|
||||
- **GitHub Actions 集成**: 在您的 GitHub 工作流程中触发 Claude Code 任务。
|
||||
- **插件系统**: 使用自定义转换器扩展功能。
|
||||
- 用一个本地入口连接多个 Agent 工具,不需要在每个客户端里重复配置 Provider。
|
||||
- 不同任务使用不同模型,例如后台任务、推理任务、长上下文、图片任务或支持联网搜索的模型。
|
||||
- 在不改变工作流的情况下混用不同 Provider。CCR 支持 OpenAI 兼容 API、Anthropic Messages、Gemini Generate Content、OpenRouter、DeepSeek、SiliconFlow、Moonshot、Mistral、Z.AI、百炼以及自定义 Provider。
|
||||
- 通过 fallback 路由、API Key 轮换、用量统计和请求日志来控制成本和可靠性。
|
||||
- 使用桌面 UI 管理配置,减少手写 JSON。
|
||||
- 通过插件、代理路由、本地 HTTP 后端和 Provider deeplink 扩展网关能力。
|
||||
|
||||
## 🚀 快速入门
|
||||
## 功能和特性
|
||||
|
||||
### 1. 安装
|
||||
- **桌面控制台**:启动或停止本地网关,查看用量,配置托盘窗口和运行时设置。
|
||||
- **Provider 管理**:添加预设或自定义端点,检测连通性,管理凭据,并在可用时查看账号余额。
|
||||
- **路由规则**:配置默认、后台、thinking、长上下文、图片、Web Search、Subagent、模型前缀和条件路由。
|
||||
- **Agent Profiles**:为 Claude Code、Codex 和 ZCode 配置指向 CCR 网关的 Profile。
|
||||
- **网关兼容层**:通过本地 CCR wrapper 和 core gateway runtime 转换客户端请求。
|
||||
- **代理模式**:通过本地代理捕获支持的 API 流量,可选系统代理和网络捕获。
|
||||
- **插件系统**:安装或加载 wrapper 插件,包括 Claude Design、Cursor Proxy 这类集成路由。
|
||||
- **虚拟模型**:为客户端暴露模型别名或组合模型配置,适配固定模型名场景。
|
||||
- **Provider Deeplink**:通过 `ccr://provider?...` 链接导入 Provider 配置,写入前会弹出确认。
|
||||
|
||||
首先,请确保您已安装 [Claude Code](https://docs.anthropic.com/en/docs/claude-code/quickstart):
|
||||
## 下载和安装
|
||||
|
||||
```shell
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
1. 打开 [GitHub Releases 页面](https://github.com/musistudio/claude-code-router/releases)。
|
||||
2. 按系统下载对应安装包:
|
||||
- macOS:`Claude Code Router_<version>.dmg` 或 `.zip`
|
||||
- Windows:`Claude Code Router_<version>.exe`
|
||||
- Linux:`Claude Code Router_<version>.AppImage`
|
||||
3. 安装并启动 **Claude Code Router**。
|
||||
4. 首次启动后,CCR 会创建本地配置:
|
||||
- macOS/Linux:`~/.claude-code-router/config.json`
|
||||
- Windows:`%APPDATA%\Claude Code Router\config.json`
|
||||
|
||||
启用网关后,CCR 会启动两个本地服务:
|
||||
|
||||
- CCR wrapper gateway:`http://127.0.0.1:3456`
|
||||
- Core gateway runtime:`http://127.0.0.1:3457`
|
||||
|
||||
## 快速开始
|
||||
|
||||
CCR 可以完全通过桌面 UI 完成配置。首次使用建议按下面顺序操作。
|
||||
|
||||
### 1. 添加 Provider
|
||||
|
||||
打开 **Providers**,点击 **Add Provider**,选择内置预设或创建自定义 Provider。按表单填写 Provider 名称、端点、协议、API Key 和模型列表。可用时先运行连通性检测,然后保存 Provider。
|
||||
|
||||
### 2. 设置路由
|
||||
|
||||
打开 **Routing**,先选择默认路由要使用的 provider/model。然后根据需要设置后台任务、Thinking、长上下文、图片任务和 Web Search 等场景的专用模型。
|
||||
|
||||
如果需要更细粒度控制,使用 **Add Routing Rule** 添加模型前缀、Subagent、请求条件或 fallback 规则。
|
||||
|
||||
### 3. 启动网关
|
||||
|
||||
打开 **Server**,点击 **Start** 启动本地网关。如果希望每次打开桌面应用时自动启动网关,可以启用 auto start。
|
||||
|
||||
### 4. 连接 Agent 工具
|
||||
|
||||
打开 **Profiles**,选择要使用的客户端。通过表单配置 Claude Code、Codex 或 ZCode Profile,选择目标模型并应用配置。对于 App 类型的 Profile,可以使用页面里的操作按钮通过 CCR 打开目标应用。
|
||||
|
||||
### 5. 日常查看和调整
|
||||
|
||||
使用 **Dashboard** 查看用量和 Provider 状态,使用托盘窗口快速查看 Token 和账号状态,使用 **Network Logs** 调试 Provider 行为,使用 **Extensions** 配置插件。
|
||||
|
||||
## Provider Deeplink
|
||||
|
||||
Provider 网站可以通过自定义协议打开 CCR 并导入模型服务配置:
|
||||
|
||||
```text
|
||||
ccr://provider?name=Example%20AI&base_url=https%3A%2F%2Fapi.example.com%2Fv1&api_key=sk-example&models=example-chat%2Cexample-coder&protocol=openai_chat_completions
|
||||
```
|
||||
|
||||
然后,安装 Claude Code Router:
|
||||
支持的 query 参数:
|
||||
|
||||
```shell
|
||||
npm install -g @musistudio/claude-code-router
|
||||
```
|
||||
- `name`:Provider 展示名称。
|
||||
- `base_url`:Provider API Base URL。别名:`baseUrl`、`api_base_url`、`url`、`endpoint`。
|
||||
- `api_key`:可选 Provider API Key。别名:`apiKey`、`apikey`、`key`、`token`。
|
||||
- `models`:逗号或换行分隔的模型列表,也可以重复传入 `model=...`。
|
||||
- `protocol`:`openai_chat_completions`、`openai_responses`、`anthropic_messages` 或 `gemini_generate_content`。
|
||||
|
||||
### 2. 配置
|
||||
更大的 payload 可以通过 URL 编码 JSON 或 base64url JSON 传入 `payload` 字段。CCR 在写入外部链接导入的 Provider 前,总会弹出确认窗口。
|
||||
|
||||
创建并配置您的 `~/.claude-code-router/config.json` 文件。有关更多详细信息,您可以参考 `config.example.json`。
|
||||
## 插件
|
||||
|
||||
`config.json` 文件有几个关键部分:
|
||||
- **`PROXY_URL`** (可选): 您可以为 API 请求设置代理,例如:`"PROXY_URL": "http://127.0.0.1:7890"`。
|
||||
- **`LOG`** (可选): 您可以通过将其设置为 `true` 来启用日志记录。当设置为 `false` 时,将不会创建日志文件。默认值为 `true`。
|
||||
- **`LOG_LEVEL`** (可选): 设置日志级别。可用选项包括:`"fatal"`、`"error"`、`"warn"`、`"info"`、`"debug"`、`"trace"`。默认值为 `"debug"`。
|
||||
- **日志系统**: Claude Code Router 使用两个独立的日志系统:
|
||||
- **服务器级别日志**: HTTP 请求、API 调用和服务器事件使用 pino 记录在 `~/.claude-code-router/logs/` 目录中,文件名类似于 `ccr-*.log`
|
||||
- **应用程序级别日志**: 路由决策和业务逻辑事件记录在 `~/.claude-code-router/claude-code-router.log` 文件中
|
||||
- **`APIKEY`** (可选): 您可以设置一个密钥来进行身份验证。设置后,客户端请求必须在 `Authorization` 请求头 (例如, `Bearer your-secret-key`) 或 `x-api-key` 请求头中提供此密钥。例如:`"APIKEY": "your-secret-key"`。
|
||||
- **`HOST`** (可选): 您可以设置服务的主机地址。如果未设置 `APIKEY`,出于安全考虑,主机地址将强制设置为 `127.0.0.1`,以防止未经授权的访问。例如:`"HOST": "0.0.0.0"`。
|
||||
- **`NON_INTERACTIVE_MODE`** (可选): 当设置为 `true` 时,启用与非交互式环境(如 GitHub Actions、Docker 容器或其他 CI/CD 系统)的兼容性。这会设置适当的环境变量(`CI=true`、`FORCE_COLOR=0` 等)并配置 stdin 处理,以防止进程在自动化环境中挂起。例如:`"NON_INTERACTIVE_MODE": true`。
|
||||
- **`Providers`**: 用于配置不同的模型提供商。
|
||||
- **`Router`**: 用于设置路由规则。`default` 指定默认模型,如果未配置其他路由,则该模型将用于所有请求。
|
||||
- **`API_TIMEOUT_MS`**: API 请求超时时间,单位为毫秒。
|
||||
CCR 有两层插件:
|
||||
|
||||
这是一个综合示例:
|
||||
- Core gateway plugins:使用 `providerPlugins` 和 `virtualModelProfiles`,会透传给 core gateway。
|
||||
- Wrapper plugins:使用顶层 `plugins` 扩展 Electron wrapper,注册本地 HTTP 后端、添加 gateway route,或把代理模式流量路由到插件后端。
|
||||
|
||||
Wrapper plugin route 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"APIKEY": "your-secret-key",
|
||||
"PROXY_URL": "http://127.0.0.1:7890",
|
||||
"LOG": true,
|
||||
"API_TIMEOUT_MS": 600000,
|
||||
"NON_INTERACTIVE_MODE": false,
|
||||
"Providers": [
|
||||
"plugins": [
|
||||
{
|
||||
"name": "openrouter",
|
||||
"api_base_url": "https://openrouter.ai/api/v1/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": [
|
||||
"google/gemini-2.5-pro-preview",
|
||||
"anthropic/claude-sonnet-4",
|
||||
"anthropic/claude-3.5-sonnet",
|
||||
"anthropic/claude-3.7-sonnet:thinking"
|
||||
],
|
||||
"transformer": {
|
||||
"use": ["openrouter"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["deepseek-chat", "deepseek-reasoner"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"],
|
||||
"deepseek-chat": {
|
||||
"use": ["tooluse"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ollama",
|
||||
"api_base_url": "http://localhost:11434/v1/chat/completions",
|
||||
"api_key": "ollama",
|
||||
"models": ["qwen2.5-coder:latest"]
|
||||
},
|
||||
{
|
||||
"name": "gemini",
|
||||
"api_base_url": "https://generativelanguage.googleapis.com/v1beta/models/",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["gemini-2.5-flash", "gemini-2.5-pro"],
|
||||
"transformer": {
|
||||
"use": ["gemini"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "volcengine",
|
||||
"api_base_url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["deepseek-v3-250324", "deepseek-r1-250528"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "modelscope",
|
||||
"api_base_url": "https://api-inference.modelscope.cn/v1/chat/completions",
|
||||
"api_key": "",
|
||||
"models": ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507"],
|
||||
"transformer": {
|
||||
"use": [
|
||||
[
|
||||
"maxtoken",
|
||||
{
|
||||
"max_tokens": 65536
|
||||
}
|
||||
],
|
||||
"enhancetool"
|
||||
],
|
||||
"Qwen/Qwen3-235B-A22B-Thinking-2507": {
|
||||
"use": ["reasoning"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dashscope",
|
||||
"api_base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"api_key": "",
|
||||
"models": ["qwen3-coder-plus"],
|
||||
"transformer": {
|
||||
"use": [
|
||||
[
|
||||
"maxtoken",
|
||||
{
|
||||
"max_tokens": 65536
|
||||
}
|
||||
],
|
||||
"enhancetool"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aihubmix",
|
||||
"api_base_url": "https://aihubmix.com/v1/chat/completions",
|
||||
"api_key": "sk-",
|
||||
"models": [
|
||||
"Z/glm-4.5",
|
||||
"claude-opus-4-20250514",
|
||||
"gemini-2.5-pro"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat",
|
||||
"background": "ollama,qwen2.5-coder:latest",
|
||||
"think": "deepseek,deepseek-reasoner",
|
||||
"longContext": "openrouter,google/gemini-2.5-pro-preview",
|
||||
"longContextThreshold": 60000,
|
||||
"webSearch": "gemini,gemini-2.5-flash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 3. 使用 Router 运行 Claude Code
|
||||
|
||||
使用 router 启动 Claude Code:
|
||||
|
||||
```shell
|
||||
ccr code
|
||||
```
|
||||
|
||||
> **注意**: 修改配置文件后,需要重启服务使配置生效:
|
||||
> ```shell
|
||||
> ccr restart
|
||||
> ```
|
||||
|
||||
### 4. UI 模式
|
||||
|
||||
为了获得更直观的体验,您可以使用 UI 模式来管理您的配置:
|
||||
|
||||
```shell
|
||||
ccr ui
|
||||
```
|
||||
|
||||
这将打开一个基于 Web 的界面,您可以在其中轻松查看和编辑您的 `config.json` 文件。
|
||||
|
||||

|
||||
|
||||
### 5. CLI 模型管理
|
||||
|
||||
对于偏好终端工作流的用户,可以使用交互式 CLI 模型选择器:
|
||||
|
||||
```shell
|
||||
ccr model
|
||||
```
|
||||
|
||||
该命令提供交互式界面来:
|
||||
|
||||
- 查看当前配置
|
||||
- 查看所有配置的模型(default、background、think、longContext、webSearch、image)
|
||||
- 切换模型:快速更改每个路由器类型使用的模型
|
||||
- 添加新模型:向现有提供商添加模型
|
||||
- 创建新提供商:设置完整的提供商配置,包括:
|
||||
- 提供商名称和 API 端点
|
||||
- API 密钥
|
||||
- 可用模型
|
||||
- Transformer 配置,支持:
|
||||
- 多个转换器(openrouter、deepseek、gemini 等)
|
||||
- Transformer 选项(例如,带自定义限制的 maxtoken)
|
||||
- 特定于提供商的路由(例如,OpenRouter 提供商偏好)
|
||||
|
||||
CLI 工具验证所有输入并提供有用的提示来引导您完成配置过程,使管理复杂的设置变得容易,无需手动编辑 JSON 文件。
|
||||
|
||||
### 6. 预设管理
|
||||
|
||||
预设允许您轻松保存、共享和重用配置。您可以将当前配置导出为预设,并从文件或 URL 安装预设。
|
||||
|
||||
```shell
|
||||
# 将当前配置导出为预设
|
||||
ccr preset export my-preset
|
||||
|
||||
# 使用元数据导出
|
||||
ccr preset export my-preset --description "我的 OpenAI 配置" --author "您的名字" --tags "openai,生产环境"
|
||||
|
||||
# 从本地目录安装预设
|
||||
ccr preset install /path/to/preset
|
||||
|
||||
# 列出所有已安装的预设
|
||||
ccr preset list
|
||||
|
||||
# 显示预设信息
|
||||
ccr preset info my-preset
|
||||
|
||||
# 删除预设
|
||||
ccr preset delete my-preset
|
||||
```
|
||||
|
||||
**预设功能:**
|
||||
- **导出**:将当前配置保存为预设目录(包含 manifest.json)
|
||||
- **安装**:从本地目录安装预设
|
||||
- **敏感数据处理**:导出期间自动清理 API 密钥和其他敏感数据(标记为 `{{field}}` 占位符)
|
||||
- **动态配置**:预设可以包含输入架构,用于在安装期间收集所需信息
|
||||
- **版本控制**:每个预设包含版本元数据,用于跟踪更新
|
||||
|
||||
**预设文件结构:**
|
||||
```
|
||||
~/.claude-code-router/presets/
|
||||
├── my-preset/
|
||||
│ └── manifest.json # 包含配置和元数据
|
||||
```
|
||||
|
||||
### 7. Activate 命令(环境变量设置)
|
||||
|
||||
`activate` 命令允许您在 shell 中全局设置环境变量,使您能够直接使用 `claude` 命令或将 Claude Code Router 与使用 Agent SDK 构建的应用程序集成。
|
||||
|
||||
要激活环境变量,请运行:
|
||||
|
||||
```shell
|
||||
eval "$(ccr activate)"
|
||||
```
|
||||
|
||||
此命令会以 shell 友好的格式输出必要的环境变量,这些变量将在当前的 shell 会话中设置。激活后,您可以:
|
||||
|
||||
- **直接使用 `claude` 命令**:无需使用 `ccr code` 即可运行 `claude` 命令。`claude` 命令将自动通过 Claude Code Router 路由请求。
|
||||
- **与 Agent SDK 应用程序集成**:使用 Anthropic Agent SDK 构建的应用程序将自动使用配置的路由器和模型。
|
||||
|
||||
`activate` 命令设置以下环境变量:
|
||||
|
||||
- `ANTHROPIC_AUTH_TOKEN`: 来自配置的 API 密钥
|
||||
- `ANTHROPIC_BASE_URL`: 本地路由器端点(默认:`http://127.0.0.1:3456`)
|
||||
- `NO_PROXY`: 设置为 `127.0.0.1` 以防止代理干扰
|
||||
- `DISABLE_TELEMETRY`: 禁用遥测
|
||||
- `DISABLE_COST_WARNINGS`: 禁用成本警告
|
||||
- `API_TIMEOUT_MS`: 来自配置的 API 超时时间
|
||||
|
||||
> **注意**:在使用激活的环境变量之前,请确保 Claude Code Router 服务正在运行(`ccr start`)。环境变量仅在当前 shell 会话中有效。要使其持久化,您可以将 `eval "$(ccr activate)"` 添加到您的 shell 配置文件(例如 `~/.zshrc` 或 `~/.bashrc`)中。
|
||||
|
||||
#### Providers
|
||||
|
||||
`Providers` 数组是您定义要使用的不同模型提供商的地方。每个提供商对象都需要:
|
||||
|
||||
- `name`: 提供商的唯一名称。
|
||||
- `api_base_url`: 聊天补全的完整 API 端点。
|
||||
- `api_key`: 您提供商的 API 密钥。
|
||||
- `models`: 此提供商可用的模型名称列表。
|
||||
- `transformer` (可选): 指定用于处理请求和响应的转换器。
|
||||
|
||||
#### Transformers
|
||||
|
||||
Transformers 允许您修改请求和响应负载,以确保与不同提供商 API 的兼容性。
|
||||
|
||||
- **全局 Transformer**: 将转换器应用于提供商的所有模型。在此示例中,`openrouter` 转换器将应用于 `openrouter` 提供商下的所有模型。
|
||||
```json
|
||||
{
|
||||
"name": "openrouter",
|
||||
"api_base_url": "https://openrouter.ai/api/v1/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": [
|
||||
"google/gemini-2.5-pro-preview",
|
||||
"anthropic/claude-sonnet-4",
|
||||
"anthropic/claude-3.5-sonnet"
|
||||
],
|
||||
"transformer": { "use": ["openrouter"] }
|
||||
}
|
||||
```
|
||||
- **特定于模型的 Transformer**: 将转换器应用于特定模型。在此示例中,`deepseek` 转换器应用于所有模型,而额外的 `tooluse` 转换器仅应用于 `deepseek-chat` 模型。
|
||||
```json
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["deepseek-chat", "deepseek-reasoner"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"],
|
||||
"deepseek-chat": { "use": ["tooluse"] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **向 Transformer 传递选项**: 某些转换器(如 `maxtoken`)接受选项。要传递选项,请使用嵌套数组,其中第一个元素是转换器名称,第二个元素是选项对象。
|
||||
```json
|
||||
{
|
||||
"name": "siliconflow",
|
||||
"api_base_url": "https://api.siliconflow.cn/v1/chat/completions",
|
||||
"api_key": "sk-xxx",
|
||||
"models": ["moonshotai/Kimi-K2-Instruct"],
|
||||
"transformer": {
|
||||
"use": [
|
||||
[
|
||||
"maxtoken",
|
||||
{
|
||||
"max_tokens": 16384
|
||||
}
|
||||
]
|
||||
"id": "local-admin-api",
|
||||
"enabled": true,
|
||||
"proxy": {
|
||||
"routes": [
|
||||
{
|
||||
"id": "admin-api",
|
||||
"host": "api.example.com",
|
||||
"paths": ["/v1/admin"],
|
||||
"upstream": "http://127.0.0.1:4510",
|
||||
"stripPathPrefix": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**可用的内置 Transformer:**
|
||||
|
||||
- `Anthropic`: 如果你只使用这一个转换器,则会直接透传请求和响应(你可以用它来接入其他支持Anthropic端点的服务商)。
|
||||
- `deepseek`: 适配 DeepSeek API 的请求/响应。
|
||||
- `gemini`: 适配 Gemini API 的请求/响应。
|
||||
- `openrouter`: 适配 OpenRouter API 的请求/响应。它还可以接受一个 `provider` 路由参数,以指定 OpenRouter 应使用哪些底层提供商。有关更多详细信息,请参阅 [OpenRouter 文档](https://openrouter.ai/docs/features/provider-routing)。请参阅下面的示例:
|
||||
```json
|
||||
"transformer": {
|
||||
"use": ["openrouter"],
|
||||
"moonshotai/kimi-k2": {
|
||||
"use": [
|
||||
[
|
||||
"openrouter",
|
||||
{
|
||||
"provider": {
|
||||
"only": ["moonshotai/fp8"]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
- `groq`: 适配 groq API 的请求/响应
|
||||
- `maxtoken`: 设置特定的 `max_tokens` 值。
|
||||
- `tooluse`: 优化某些模型的工具使用(通过`tool_choice`参数)。
|
||||
- `gemini-cli` (实验性): 通过 Gemini CLI [gemini-cli.js](https://gist.github.com/musistudio/1c13a65f35916a7ab690649d3df8d1cd) 对 Gemini 的非官方支持。
|
||||
- `reasoning`: 用于处理 `reasoning_content` 字段。
|
||||
- `sampling`: 用于处理采样信息字段,如 `temperature`、`top_p`、`top_k` 和 `repetition_penalty`。
|
||||
- `enhancetool`: 对 LLM 返回的工具调用参数增加一层容错处理(这会导致不再流式返回工具调用信息)。
|
||||
- `cleancache`: 清除请求中的 `cache_control` 字段。
|
||||
- `vertex-gemini`: 处理使用 vertex 鉴权的 gemini api。
|
||||
- `qwen-cli` (实验性): 通过 Qwen CLI [qwen-cli.js](https://gist.github.com/musistudio/f5a67841ced39912fd99e42200d5ca8b) 对 qwen3-coder-plus 的非官方支持。
|
||||
- `rovo-cli` (experimental): 通过 Atlassian Rovo Dev CLI [rovo-cli.js](https://gist.github.com/SaseQ/c2a20a38b11276537ec5332d1f7a5e53) 对 GPT-5 的非官方支持。
|
||||
|
||||
**自定义 Transformer:**
|
||||
|
||||
您还可以创建自己的转换器,并通过 `config.json` 中的 `transformers` 字段加载它们。
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"path": "/User/xxx/.claude-code-router/plugins/gemini-cli.js",
|
||||
"options": {
|
||||
"project": "xxx"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Router
|
||||
插件模块需要导出函数或包含 `setup(ctx)` 的对象。上下文支持:
|
||||
|
||||
`Router` 对象定义了在不同场景下使用哪个模型:
|
||||
- `ctx.registerGatewayRoute({ method, path, auth, handler })`
|
||||
- `ctx.registerHttpBackend({ id, host, port, handler })`
|
||||
- `ctx.registerProxyRoute({ host, paths, upstream, stripPathPrefix, rewritePathPrefix, headers })`
|
||||
- `ctx.openSqliteStore({ filename, migrate })`
|
||||
- `ctx.registerCoreGatewayProviderPlugin(plugin)`
|
||||
- `ctx.registerCoreGatewayVirtualModelProfile(profile)`
|
||||
|
||||
- `default`: 用于常规任务的默认模型。
|
||||
- `background`: 用于后台任务的模型。这可以是一个较小的本地模型以节省成本。
|
||||
- `think`: 用于推理密集型任务(如计划模式)的模型。
|
||||
- `longContext`: 用于处理长上下文(例如,> 60K 令牌)的模型。
|
||||
- `longContextThreshold` (可选): 触发长上下文模型的令牌数阈值。如果未指定,默认为 60000。
|
||||
- `webSearch`: 用于处理网络搜索任务,需要模型本身支持。如果使用`openrouter`需要在模型后面加上`:online`后缀。
|
||||
- `image`(测试版): 用于处理图片类任务(采用CCR内置的agent支持),如果该模型不支持工具调用,需要将`config.forceUseImageAgent`属性设置为`true`。
|
||||
本地插件示例见 [examples/plugins](examples/plugins)。
|
||||
|
||||
您还可以使用 `/model` 命令在 Claude Code 中动态切换模型:
|
||||
`/model provider_name,model_name`
|
||||
示例: `/model openrouter,anthropic/claude-3.5-sonnet`
|
||||
## 开发
|
||||
|
||||
#### 自定义路由器
|
||||
|
||||
对于更高级的路由逻辑,您可以在 `config.json` 中通过 `CUSTOM_ROUTER_PATH` 字段指定一个自定义路由器脚本。这允许您实现超出默认场景的复杂路由规则。
|
||||
|
||||
在您的 `config.json` 中配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"CUSTOM_ROUTER_PATH": "/User/xxx/.claude-code-router/custom-router.js"
|
||||
}
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
npm run typecheck
|
||||
npm run build:assets
|
||||
npm run build:app:mac
|
||||
npm run build:app:win
|
||||
```
|
||||
|
||||
自定义路由器文件必须是一个导出 `async` 函数的 JavaScript 模块。该函数接收请求对象和配置对象作为参数,并应返回提供商和模型名称的字符串(例如 `"provider_name,model_name"`),如果返回 `null` 则回退到默认路由。
|
||||
`npm run build:assets` 会把 Electron main process 和 renderer assets 编译到 `dist/`。
|
||||
|
||||
这是一个基于 `custom-router.example.js` 的 `custom-router.js` 示例:
|
||||
`npm run build` 会为当前平台打包应用,并把安装包写入 `release/`。
|
||||
|
||||
```javascript
|
||||
// /User/xxx/.claude-code-router/custom-router.js
|
||||
`npm run build:app:mac` 和 `npm run build:app:win` 会分别打包对应平台的应用产物。Linux AppImage 打包配置在 `electron-builder.json` 中。
|
||||
|
||||
/**
|
||||
* 一个自定义路由函数,用于根据请求确定使用哪个模型。
|
||||
*
|
||||
* @param {object} req - 来自 Claude Code 的请求对象,包含请求体。
|
||||
* @param {object} config - 应用程序的配置对象。
|
||||
* @returns {Promise<string|null>} - 一个解析为 "provider,model_name" 字符串的 Promise,如果返回 null,则使用默认路由。
|
||||
*/
|
||||
module.exports = async function router(req, config) {
|
||||
const userMessage = req.body.messages.find(m => m.role === 'user')?.content;
|
||||
`npm run build:app:mac` 会在 `release-local/` 生成本地测试用 macOS 包,使用 ad-hoc 签名。它适合免费 Apple Account 或只有 Apple Development 证书的本机测试,但不适合公开分发,因为用户下载后仍无法通过 Gatekeeper 公证检查。
|
||||
|
||||
if (userMessage && userMessage.includes('解释这段代码')) {
|
||||
// 为代码解释任务使用更强大的模型
|
||||
return 'openrouter,anthropic/claude-3.5-sonnet';
|
||||
}
|
||||
macOS 发布包会使用 Developer ID 签名并提交 Apple 公证。运行 `npm run build:app:mac:release` 前,打包机器必须具备:可用的 `Developer ID Application` 证书(在 keychain 中,或通过 `CSC_LINK`/`CSC_KEY_PASSWORD` 提供)、已通过 `xcode-select` 选择完整 Xcode,以及下面任意一组公证凭据:
|
||||
|
||||
// 回退到默认的路由配置
|
||||
return null;
|
||||
};
|
||||
```
|
||||
- `APPLE_API_KEY`、`APPLE_API_KEY_ID`、`APPLE_API_ISSUER`
|
||||
- `APPLE_ID`、`APPLE_APP_SPECIFIC_PASSWORD`、`APPLE_TEAM_ID`
|
||||
- `APPLE_KEYCHAIN_PROFILE`,可选 `APPLE_KEYCHAIN`
|
||||
|
||||
##### 子代理路由
|
||||
macOS 打包 hook 会在产物生成前验证代码签名、公证票据 stapling 和 Gatekeeper 评估,避免发布未公证的安装包。
|
||||
|
||||
对于子代理内的路由,您必须在子代理提示词的**开头**包含 `<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>` 来指定特定的提供商和模型。这样可以将特定的子代理任务定向到指定的模型。
|
||||
打包后的应用会通过 `electron-updater` 检查 GitHub Releases。测试本地更新源时,可以在启动应用前设置 `CCR_UPDATE_FEED_URL` 为 generic electron-updater feed URL。`CCR_UPDATE_ALLOW_PRERELEASE=1` 可以启用 prerelease 更新。
|
||||
|
||||
**示例:**
|
||||
## 深入阅读
|
||||
|
||||
```
|
||||
<CCR-SUBAGENT-MODEL>openrouter,anthropic/claude-3.5-sonnet</CCR-SUBAGENT-MODEL>
|
||||
请帮我分析这段代码是否存在潜在的优化空间...
|
||||
```
|
||||
- [项目动机和工作原理](blog/zh/项目初衷及原理.md)
|
||||
- [也许我们可以用路由器做更多事情](blog/zh/或许我们能在Router中做更多事情.md)
|
||||
|
||||
## Status Line (Beta)
|
||||
为了在运行时更好的查看claude-code-router的状态,claude-code-router在v1.0.40内置了一个statusline工具,你可以在UI中启用它,
|
||||

|
||||
## 支持与赞助
|
||||
|
||||
效果如下:
|
||||

|
||||
|
||||
## 🤖 GitHub Actions
|
||||
|
||||
将 Claude Code Router 集成到您的 CI/CD 管道中。在设置 [Claude Code Actions](https://docs.anthropic.com/en/docs/claude-code/github-actions) 后,修改您的 `.github/workflows/claude.yaml` 以使用路由器:
|
||||
|
||||
```yaml
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
# ... other triggers
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
# ... other conditions
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
mkdir -p $HOME/.claude-code-router
|
||||
cat << 'EOF' > $HOME/.claude-code-router/config.json
|
||||
{
|
||||
"log": true,
|
||||
"NON_INTERACTIVE_MODE": true,
|
||||
"OPENAI_API_KEY": "${{ secrets.OPENAI_API_KEY }}",
|
||||
"OPENAI_BASE_URL": "https://api.deepseek.com",
|
||||
"OPENAI_MODEL": "deepseek-chat"
|
||||
}
|
||||
EOF
|
||||
shell: bash
|
||||
|
||||
- name: Start Claude Code Router
|
||||
run: |
|
||||
nohup ~/.bun/bin/bunx @musistudio/claude-code-router@1.0.8 start &
|
||||
shell: bash
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@beta
|
||||
env:
|
||||
ANTHROPIC_BASE_URL: http://localhost:3456
|
||||
with:
|
||||
anthropic_api_key: "any-string-is-ok"
|
||||
```
|
||||
|
||||
这种设置可以实现有趣的自动化,例如在非高峰时段运行任务以降低 API 成本。
|
||||
|
||||
## 📝 深入阅读
|
||||
|
||||
- [项目动机和工作原理](blog/zh/项目初衷及原理.md)
|
||||
- [也许我们可以用路由器做更多事情](blog/zh/或许我们能在Router中做更多事情.md)
|
||||
|
||||
## ❤️ 支持与赞助
|
||||
|
||||
如果您觉得这个项目有帮助,请考虑赞助它的开发。非常感谢您的支持!
|
||||
如果你觉得这个项目有帮助,欢迎赞助项目开发。非常感谢你的支持。
|
||||
|
||||
[](https://ko-fi.com/F1F31GN2GM)
|
||||
|
||||
@@ -564,57 +195,55 @@ jobs:
|
||||
|
||||
### 我们的赞助商
|
||||
|
||||
非常感谢所有赞助商的慷慨支持!
|
||||
非常感谢所有赞助商的慷慨支持。
|
||||
|
||||
- [AIHubmix](https://aihubmix.com/)
|
||||
- [BurnCloud](https://ai.burncloud.com)
|
||||
- [302.AI](https://share.302.ai/ZGVF9w)
|
||||
- [Z智谱](https://www.bigmodel.cn/claude-code?ic=FPF9IVAGFJ)
|
||||
- @Simon Leischnig
|
||||
- [@duanshuaimin](https://github.com/duanshuaimin)
|
||||
- [@vrgitadmin](https://github.com/vrgitadmin)
|
||||
- @*o
|
||||
- @\*o
|
||||
- [@ceilwoo](https://github.com/ceilwoo)
|
||||
- @*说
|
||||
- @*更
|
||||
- @K*g
|
||||
- @R*R
|
||||
- @\*说
|
||||
- @\*更
|
||||
- @K\*g
|
||||
- @R\*R
|
||||
- [@bobleer](https://github.com/bobleer)
|
||||
- @*苗
|
||||
- @*划
|
||||
- @\*苗
|
||||
- @\*划
|
||||
- [@Clarence-pan](https://github.com/Clarence-pan)
|
||||
- [@carter003](https://github.com/carter003)
|
||||
- @S*r
|
||||
- @*晖
|
||||
- @*敏
|
||||
- @Z*z
|
||||
- @*然
|
||||
- @S\*r
|
||||
- @\*晖
|
||||
- @\*敏
|
||||
- @Z\*z
|
||||
- @\*然
|
||||
- [@cluic](https://github.com/cluic)
|
||||
- @*苗
|
||||
- @\*苗
|
||||
- [@PromptExpert](https://github.com/PromptExpert)
|
||||
- @*应
|
||||
- @\*应
|
||||
- [@yusnake](https://github.com/yusnake)
|
||||
- @*飞
|
||||
- @董*
|
||||
- @*汀
|
||||
- @*涯
|
||||
- @*:-)
|
||||
- @**磊
|
||||
- @*琢
|
||||
- @*成
|
||||
- @Z*o
|
||||
- @\*飞
|
||||
- @董\*
|
||||
- @\*汀
|
||||
- @\*涯
|
||||
- @\*:-)
|
||||
- @\*\*磊
|
||||
- @\*琢
|
||||
- @\*成
|
||||
- @Z\*o
|
||||
- @\*琨
|
||||
- [@congzhangzh](https://github.com/congzhangzh)
|
||||
- @*_
|
||||
- @\*\_
|
||||
- @Z\*m
|
||||
- @*鑫
|
||||
- @\*鑫
|
||||
- @c\*y
|
||||
- @\*昕
|
||||
- [@witsice](https://github.com/witsice)
|
||||
- @b\*g
|
||||
- @\*亿
|
||||
- @\*辉
|
||||
- @JACK
|
||||
- @JACK
|
||||
- @\*光
|
||||
- @W\*l
|
||||
- [@kesku](https://github.com/kesku)
|
||||
@@ -652,26 +281,5 @@ jobs:
|
||||
- @\*\*飞
|
||||
- @\*\*驰
|
||||
- @x\*g
|
||||
- @\*\*东
|
||||
- @\*落
|
||||
- @哆\*k
|
||||
- @\*涛
|
||||
- [@苗大](https://github.com/WitMiao)
|
||||
- @\*呢
|
||||
- @\d*u
|
||||
- @crizcraig
|
||||
- s\*s
|
||||
- \*火
|
||||
- \*勤
|
||||
- \*\*锟
|
||||
- \*涛
|
||||
- \*\*明
|
||||
- \*知
|
||||
- \*语
|
||||
- \*瓜
|
||||
|
||||
(如果您的名字被屏蔽,请通过我的主页电子邮件与我联系,以便使用您的 GitHub 用户名进行更新。)
|
||||
|
||||
|
||||
## 交流群
|
||||
<img src="/blog/images/wechat_group.jpg" width="200" alt="wechat_group" />
|
||||
(如果你的名字被打码,请通过我的主页邮箱联系我更新为 GitHub 用户名。)
|
||||
|
||||
|
After Width: | Height: | Size: 992 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 88 KiB |
@@ -1,88 +0,0 @@
|
||||
# GLM-4.6 Supports Reasoning and Interleaved Thinking
|
||||
|
||||
## Enabling Reasoning in Claude Code with GLM-4.6
|
||||
|
||||
Starting from version 4.5, GLM has supported Claude Code. I’ve been following its progress closely, and many users have reported that reasoning could not be enabled within Claude Code. Recently, thanks to sponsorship from Zhipu, I decided to investigate this issue in depth. According to the [official documentation](https://docs.z.ai/api-reference/llm/chat-completion), the`/chat/completions` endpoint has reasoning enabled by default, but the model itself decides whether to think:
|
||||
|
||||
```
|
||||
thinking.type enum<string> default:enabled
|
||||
|
||||
Whether to enable the chain of thought(When enabled, GLM-4.6, GLM-4.5 and others will automatically determine whether to think, while GLM-4.5V will think compulsorily), default: enabled
|
||||
|
||||
Available options: enabled, disabled
|
||||
```
|
||||
|
||||
However, within Claude Code, its heavy system prompt interference disrupts GLM’s internal reasoning judgment, causing the model to rarely think.
|
||||
Therefore, we need to explicitly guide the model to believe reasoning is required. Since claude-code-router functions as a proxy, the only feasible approach is modifying prompts or parameters.
|
||||
|
||||
Initially, I tried completely removing Claude Code’s system prompt — and indeed, the model started reasoning — but that broke Claude Code’s workflow.
|
||||
So instead, I used prompt injection to clearly instruct the model to think step by step.
|
||||
|
||||
|
||||
```javascript
|
||||
// transformer.ts
|
||||
import { UnifiedChatRequest } from "../types/llm";
|
||||
import { Transformer } from "../types/transformer";
|
||||
|
||||
export class ForceReasoningTransformer implements Transformer {
|
||||
name = "forcereasoning";
|
||||
|
||||
async transformRequestIn(
|
||||
request: UnifiedChatRequest
|
||||
): Promise<UnifiedChatRequest> {
|
||||
const systemMessage = request.messages.find(
|
||||
(item) => item.role === "system"
|
||||
);
|
||||
if (Array.isArray(systemMessage?.content)) {
|
||||
systemMessage.content.push({
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
});
|
||||
}
|
||||
const lastMessage = request.messages[request.messages.length - 1];
|
||||
if (lastMessage.role === "user" && Array.isArray(lastMessage.content)) {
|
||||
lastMessage.content.push({
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
});
|
||||
}
|
||||
if (lastMessage.role === "tool") {
|
||||
request.messages.push({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return request;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Why use <reasoning_content> instead of the <think> tag? Two reasons:
|
||||
|
||||
1. Using the <think> tag doesn’t effectively trigger reasoning — likely because the model was trained on data where <think> had special behavior.
|
||||
|
||||
2. If we use <think>, the reasoning output is split into a separate field, which directly relates to the chain-of-thought feedback problem discussed below.
|
||||
|
||||
## Chain-of-Thought Feedback
|
||||
Recently, Minimax released `Minimax-m2`, along with [an article](https://www.minimaxi.com/news/why-is-interleaved-thinking-important-for-m2) explaining interleaved thinking.
|
||||
While the idea isn’t entirely new, it’s a good opportunity to analyze it.
|
||||
|
||||
Why do we need to interleaved thinking?
|
||||
Minimax’s article mentions that the Chat Completion API does not support passing reasoning content between requests.
|
||||
We know ChatGPT was the first to support reasoning, but OpenAI initially didn’t expose the chain of thought to users.
|
||||
Therefore, the Chat Completion API didn’t need to support it. Even the CoT field was first introduced by DeepSeek.
|
||||
|
||||
Do we really need explicit CoT fields? What happens if we don’t have them? Will it affect reasoning?
|
||||
By inspecting [sglang’s source code](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/parser/reasoning_parser.py), we can see that reasoning content is naturally emitted in messages with specific markers.
|
||||
If we don’t split it out, the next-round conversation will naturally include it.
|
||||
Thus, the only reason we need interleaved thinking is because we separated the reasoning content from the normal messages.
|
||||
|
||||
With fewer than 40 lines of code above, I implemented a simple exploration of enabling reasoning and chain-of-thought feedback for GLM-4.5/4.6.
|
||||
(It’s only simple because I haven’t implemented parsing logic yet — you could easily modify the transformer to split reasoning output on response and merge it back on request, improving Claude Code’s frontend display compatibility.)
|
||||
|
||||
If you have better ideas, feel free to reach out — I’d love to discuss further.
|
||||
|
Before Width: | Height: | Size: 2.3 MiB |
@@ -1,83 +0,0 @@
|
||||
# GLM-4.6支持思考及思维链回传
|
||||
|
||||
## GLM-4.6在cluade code中启用思考
|
||||
GLM从4.5开始就对claude code进行了支持,我之前也一直在关注,很多用户反映在claude code中无法启用思考,刚好最近收到了来自智谱的赞助,就着手进行研究。
|
||||
|
||||
首先根据[官方文档](https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E5%AF%B9%E8%AF%9D%E8%A1%A5%E5%85%A8),我们发现`/chat/completions`端点是默认启用思考的,但是是由模型判断是否需要进行思考
|
||||
|
||||
```
|
||||
thinking object
|
||||
仅 GLM-4.5 及以上模型支持此参数配置. 控制大模型是否开启思维链。
|
||||
|
||||
thinking.type enum<string> default:enabled
|
||||
是否开启思维链(当开启后 GLM-4.6 GLM-4.5 为模型自动判断是否思考,GLM-4.5V 为强制思考), 默认: enabled.
|
||||
|
||||
Available options: enabled, disabled
|
||||
```
|
||||
|
||||
在claude code本身大量的提示词干扰下,会严重阻碍GLM模型本身的判断机制,导致模型很少进行思考。所以我们需要对模型进行引导,让模型认为需要进行思考。但是`claude-code-router`作为proxy,能做的只能是修改提示词/参数。
|
||||
|
||||
在最开始,我尝试直接删除claude code的系统提示词,模型确实进行了思考,但是这样就无法驱动claude code。所以我们需要进行提示词注入,明确告知模型需要进行思考。
|
||||
|
||||
```javascript
|
||||
// transformer.ts
|
||||
import { UnifiedChatRequest } from "../types/llm";
|
||||
import { Transformer } from "../types/transformer";
|
||||
|
||||
export class ForceReasoningTransformer implements Transformer {
|
||||
name = "forcereasoning";
|
||||
|
||||
async transformRequestIn(
|
||||
request: UnifiedChatRequest
|
||||
): Promise<UnifiedChatRequest> {
|
||||
const systemMessage = request.messages.find(
|
||||
(item) => item.role === "system"
|
||||
);
|
||||
if (Array.isArray(systemMessage?.content)) {
|
||||
systemMessage.content.push({
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
});
|
||||
}
|
||||
const lastMessage = request.messages[request.messages.length - 1];
|
||||
if (lastMessage.role === "user" && Array.isArray(lastMessage.content)) {
|
||||
lastMessage.content.push({
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
});
|
||||
}
|
||||
if (lastMessage.role === "tool") {
|
||||
request.messages.push({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return request;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
至于为什么让模型将思考内容放入reasoning_content标签而不是think标签有两个原因:
|
||||
1. 直接使用think标签不能很好的激活思考,猜测是训练模型时以think标签作为数据集进行训练。
|
||||
2. 如果使用think标签,模型的推理内容会被拆分到单独的字段,这就涉及到我们接下来要说的思维链回传问题。
|
||||
|
||||
|
||||
## 思维链回传
|
||||
|
||||
近期Minimax发布了Minimax-m2,与此同时,他们还发布了一篇[文章](https://www.minimaxi.com/news/why-is-interleaved-thinking-important-for-m2)介绍思维链回传。但是太阳底下无新鲜事,刚好借此来剖析一下。
|
||||
1. 我们首先来看一下为什么需要回传思维链?
|
||||
Minimax在文章中说的是Chat Completion API不支持在后续请求中传递推理内容。我们知道ChatGPT是最先支持推理的,但是OpenAI最初没有开放思维链给用户,所以对于Chat Completion API来讲并不需要支持思维链相关的东西。就连CoT的字段也是DeepSeek率先在Chat Completion API中加入的。
|
||||
|
||||
2. 我们真的需要这些字段吗?
|
||||
如果没有这些字段会怎么样?会影响到模型的思考吗?可以查看一下[sglang的源码](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/parser/reasoning_parser.py)发现思维链的信息原本就会在消息中按照特定的标记进行输出,假如我们不对其进行拆分,正常情况下在下轮对话中会自然包含这些信息。所以需要思维链回传的原因就是我们对模型的思维链内容进行拆分。
|
||||
|
||||
我用上面不到40行的代码完成了对GLM-4.5/6支持思考以及思维链回传的简单探索(单纯是因为没时间做拆分,完全可以在transformer中响应时先做拆分,请求时再进行合并,这样对cc前端的展示适配会更好),如果你有什么更好的想法也欢迎与我联系。
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { buildBrowserRenderer, buildMain, buildRenderer, buildStyles, buildTrayRenderer, cleanDist, copyAppAssets, copyBrowserRendererHtml, copyMarketplacePlugins, copyModelCatalog, copyRendererHtml, copyTrayRendererHtml } from "./esbuild.config.mjs";
|
||||
|
||||
const mode = process.argv.includes("--dev") ? "development" : "production";
|
||||
|
||||
cleanDist();
|
||||
copyAppAssets();
|
||||
copyMarketplacePlugins();
|
||||
copyModelCatalog();
|
||||
copyBrowserRendererHtml();
|
||||
copyRendererHtml();
|
||||
copyTrayRendererHtml();
|
||||
|
||||
await Promise.all([
|
||||
buildMain({ mode }),
|
||||
buildBrowserRenderer({ mode }),
|
||||
buildRenderer({ mode }),
|
||||
buildTrayRenderer({ mode }),
|
||||
buildStyles({ minify: mode === "production" })
|
||||
]);
|
||||
|
||||
console.log(`Built Electron app assets in ${mode} mode.`);
|
||||
@@ -0,0 +1,336 @@
|
||||
import electron from "electron";
|
||||
import esbuild from "esbuild";
|
||||
import { createHash } from "node:crypto";
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync, readdirSync, readFileSync, statSync, watch } from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
binPath,
|
||||
buildStyles,
|
||||
cleanDist,
|
||||
browserRendererHtmlInput,
|
||||
copyAppAssets,
|
||||
copyBrowserRendererHtml,
|
||||
copyMarketplacePlugins,
|
||||
copyModelCatalog,
|
||||
copyRendererHtml,
|
||||
copyTrayRendererHtml,
|
||||
createBrowserRendererBuildOptions,
|
||||
createMainBuildOptions,
|
||||
createRendererBuildOptions,
|
||||
createTrayRendererBuildOptions,
|
||||
cssInput,
|
||||
cssOutput,
|
||||
appAssetsInput,
|
||||
modelCatalogInput,
|
||||
projectRoot,
|
||||
rendererHtmlInput,
|
||||
trayRendererHtmlInput,
|
||||
watchPlugin
|
||||
} from "./esbuild.config.mjs";
|
||||
|
||||
let electronProcess = null;
|
||||
let restartTimer = null;
|
||||
let pendingRestartReasons = [];
|
||||
const watchSignatures = new Map();
|
||||
let shuttingDown = false;
|
||||
const restartDelayMs = 160;
|
||||
const ignoredSignatureEntries = new Set([".DS_Store"]);
|
||||
const ready = {
|
||||
browser: false,
|
||||
main: false,
|
||||
renderer: false,
|
||||
tray: false
|
||||
};
|
||||
|
||||
function logDev(message) {
|
||||
console.log(`[dev] ${new Date().toISOString()} ${message}`);
|
||||
}
|
||||
|
||||
function relativePath(file) {
|
||||
return path.relative(projectRoot, file) || ".";
|
||||
}
|
||||
|
||||
function readyState() {
|
||||
return Object.entries(ready)
|
||||
.map(([name, value]) => `${name}:${value ? "ready" : "pending"}`)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function describeWatchEvent(label, watchedPath, eventType, filename, isDirectory = false) {
|
||||
const changedPath = filename
|
||||
? path.join(isDirectory ? watchedPath : path.dirname(watchedPath), String(filename))
|
||||
: watchedPath;
|
||||
return `${label} ${eventType} ${relativePath(changedPath)}`;
|
||||
}
|
||||
|
||||
function contentSignature(targetPath) {
|
||||
try {
|
||||
return readContentSignature(targetPath);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
key: `error:${message}`,
|
||||
summary: `signature-error=${message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function readContentSignature(targetPath) {
|
||||
if (!existsSync(targetPath)) {
|
||||
return {
|
||||
key: "missing",
|
||||
summary: "missing"
|
||||
};
|
||||
}
|
||||
|
||||
const stats = statSync(targetPath);
|
||||
if (stats.isDirectory()) {
|
||||
return directorySignature(targetPath);
|
||||
}
|
||||
|
||||
const content = readFileSync(targetPath);
|
||||
const hash = createHash("sha1").update(content).digest("hex").slice(0, 12);
|
||||
return {
|
||||
key: `file:${hash}`,
|
||||
summary: `size=${stats.size} mtime=${stats.mtime.toISOString()} ctime=${stats.ctime.toISOString()} sha1=${hash}`
|
||||
};
|
||||
}
|
||||
|
||||
function directorySignature(targetPath) {
|
||||
const files = listDirectoryFiles(targetPath);
|
||||
const hash = createHash("sha1");
|
||||
let newestMtimeMs = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const absolutePath = path.join(targetPath, file);
|
||||
const stats = statSync(absolutePath);
|
||||
newestMtimeMs = Math.max(newestMtimeMs, stats.mtimeMs);
|
||||
hash.update(file);
|
||||
hash.update("\0");
|
||||
hash.update(readFileSync(absolutePath));
|
||||
hash.update("\0");
|
||||
}
|
||||
|
||||
const digest = hash.digest("hex").slice(0, 12);
|
||||
const newestMtime = newestMtimeMs > 0 ? new Date(newestMtimeMs).toISOString() : "none";
|
||||
return {
|
||||
key: `dir:${digest}`,
|
||||
summary: `files=${files.length} newestMtime=${newestMtime} sha1=${digest}`
|
||||
};
|
||||
}
|
||||
|
||||
function listDirectoryFiles(targetPath, basePath = targetPath) {
|
||||
const entries = readdirSync(targetPath, { withFileTypes: true })
|
||||
.filter((entry) => !ignoredSignatureEntries.has(entry.name))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
const files = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const absolutePath = path.join(targetPath, entry.name);
|
||||
const relative = path.relative(basePath, absolutePath);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...listDirectoryFiles(absolutePath, basePath));
|
||||
} else if (entry.isFile()) {
|
||||
files.push(relative);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function rememberWatchSignature(label, targetPath) {
|
||||
const signature = contentSignature(targetPath);
|
||||
watchSignatures.set(label, signature.key);
|
||||
logDev(`watch baseline: ${label} ${relativePath(targetPath)}; ${signature.summary}`);
|
||||
}
|
||||
|
||||
function handleWatchedInput(label, watchedPath, eventType, filename, options, onChange) {
|
||||
const reason = describeWatchEvent(label, watchedPath, eventType, filename, options?.isDirectory);
|
||||
const signature = contentSignature(watchedPath);
|
||||
const previousSignature = watchSignatures.get(label);
|
||||
const changed = previousSignature !== signature.key;
|
||||
watchSignatures.set(label, signature.key);
|
||||
logDev(`watch event: ${reason}; ${signature.summary}; content=${changed ? "changed" : "unchanged"}`);
|
||||
|
||||
if (!changed) {
|
||||
logDev(`restart skipped: ${reason} (content unchanged)`);
|
||||
return;
|
||||
}
|
||||
|
||||
onChange();
|
||||
scheduleRestart(reason);
|
||||
}
|
||||
|
||||
function markReady(name, reason = `${name} esbuild completed`) {
|
||||
if (name === "browser" || name === "main" || name === "renderer" || name === "tray") {
|
||||
ready[name] = true;
|
||||
}
|
||||
logDev(`build ready: ${reason}; ${readyState()}`);
|
||||
if (ready.browser && ready.main && ready.renderer && ready.tray) {
|
||||
scheduleRestart(reason);
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleRestart(reason = "unknown trigger") {
|
||||
if (shuttingDown) {
|
||||
logDev(`restart ignored during shutdown: ${reason}`);
|
||||
return;
|
||||
}
|
||||
pendingRestartReasons.push(reason);
|
||||
if (restartTimer) {
|
||||
clearTimeout(restartTimer);
|
||||
logDev(`restart rescheduled in ${restartDelayMs}ms: ${reason}`);
|
||||
} else {
|
||||
logDev(`restart scheduled in ${restartDelayMs}ms: ${reason}`);
|
||||
}
|
||||
restartTimer = setTimeout(restartElectron, restartDelayMs);
|
||||
}
|
||||
|
||||
function restartElectron() {
|
||||
const reasons = Array.from(new Set(pendingRestartReasons));
|
||||
pendingRestartReasons = [];
|
||||
restartTimer = null;
|
||||
|
||||
if (electronProcess) {
|
||||
logDev(`stopping Electron pid=${electronProcess.pid ?? "unknown"}`);
|
||||
electronProcess.kill();
|
||||
electronProcess = null;
|
||||
}
|
||||
|
||||
logDev(`starting Electron; reasons=${reasons.join(" | ") || "initial start"}`);
|
||||
const child = spawn(electron, ["."], {
|
||||
cwd: projectRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: "development"
|
||||
},
|
||||
stdio: "inherit"
|
||||
});
|
||||
electronProcess = child;
|
||||
logDev(`Electron started pid=${child.pid ?? "unknown"}`);
|
||||
child.on("exit", (code, signal) => {
|
||||
logDev(`Electron exited pid=${child.pid ?? "unknown"} code=${code ?? "null"} signal=${signal ?? "null"}`);
|
||||
if (electronProcess === child) {
|
||||
electronProcess = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logDev("starting dev build");
|
||||
cleanDist();
|
||||
copyAppAssets();
|
||||
copyMarketplacePlugins();
|
||||
copyModelCatalog();
|
||||
copyBrowserRendererHtml();
|
||||
copyRendererHtml();
|
||||
copyTrayRendererHtml();
|
||||
await buildStyles({ minify: false });
|
||||
|
||||
const tailwindProcess = spawn(binPath("tailwindcss"), ["-i", cssInput, "-o", cssOutput, "--watch"], {
|
||||
cwd: projectRoot,
|
||||
stdio: "inherit",
|
||||
shell: process.platform === "win32"
|
||||
});
|
||||
logDev(`Tailwind watcher started pid=${tailwindProcess.pid ?? "unknown"} input=${relativePath(cssInput)} output=${relativePath(cssOutput)}`);
|
||||
tailwindProcess.on("exit", (code, signal) => {
|
||||
logDev(`Tailwind watcher exited code=${code ?? "null"} signal=${signal ?? "null"}`);
|
||||
});
|
||||
|
||||
rememberWatchSignature("home html", rendererHtmlInput);
|
||||
rememberWatchSignature("browser html", browserRendererHtmlInput);
|
||||
rememberWatchSignature("tray html", trayRendererHtmlInput);
|
||||
rememberWatchSignature("app assets", appAssetsInput);
|
||||
if (existsSync(modelCatalogInput)) {
|
||||
rememberWatchSignature("model catalog", modelCatalogInput);
|
||||
}
|
||||
|
||||
const htmlWatcher = watch(rendererHtmlInput, { persistent: true }, (eventType, filename) => {
|
||||
handleWatchedInput("home html", rendererHtmlInput, eventType, filename, undefined, copyRendererHtml);
|
||||
});
|
||||
|
||||
const browserHtmlWatcher = watch(browserRendererHtmlInput, { persistent: true }, (eventType, filename) => {
|
||||
handleWatchedInput("browser html", browserRendererHtmlInput, eventType, filename, undefined, copyBrowserRendererHtml);
|
||||
});
|
||||
|
||||
const trayHtmlWatcher = watch(trayRendererHtmlInput, { persistent: true }, (eventType, filename) => {
|
||||
handleWatchedInput("tray html", trayRendererHtmlInput, eventType, filename, undefined, copyTrayRendererHtml);
|
||||
});
|
||||
|
||||
const appAssetsWatcher = watch(appAssetsInput, { persistent: true }, (eventType, filename) => {
|
||||
handleWatchedInput("app assets", appAssetsInput, eventType, filename, { isDirectory: true }, copyAppAssets);
|
||||
});
|
||||
|
||||
const modelCatalogWatcher = existsSync(modelCatalogInput)
|
||||
? watch(modelCatalogInput, { persistent: true }, (eventType, filename) => {
|
||||
handleWatchedInput("model catalog", modelCatalogInput, eventType, filename, undefined, copyModelCatalog);
|
||||
})
|
||||
: { close: () => undefined };
|
||||
|
||||
const mainContext = await esbuild.context(
|
||||
createMainBuildOptions({
|
||||
mode: "development",
|
||||
plugins: [watchPlugin("main", (name) => markReady(name))]
|
||||
})
|
||||
);
|
||||
|
||||
const rendererContext = await esbuild.context(
|
||||
createRendererBuildOptions({
|
||||
mode: "development",
|
||||
plugins: [
|
||||
watchPlugin("renderer", (name) => {
|
||||
copyRendererHtml();
|
||||
markReady(name);
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
const trayRendererContext = await esbuild.context(
|
||||
createTrayRendererBuildOptions({
|
||||
mode: "development",
|
||||
plugins: [
|
||||
watchPlugin("tray", (name) => {
|
||||
copyTrayRendererHtml();
|
||||
markReady(name);
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
const browserRendererContext = await esbuild.context(
|
||||
createBrowserRendererBuildOptions({
|
||||
mode: "development",
|
||||
plugins: [
|
||||
watchPlugin("browser", (name) => {
|
||||
copyBrowserRendererHtml();
|
||||
markReady(name);
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all([mainContext.watch(), rendererContext.watch(), trayRendererContext.watch(), browserRendererContext.watch()]);
|
||||
logDev("watchers are active");
|
||||
|
||||
async function shutdown() {
|
||||
logDev("shutting down dev build");
|
||||
shuttingDown = true;
|
||||
if (restartTimer) {
|
||||
clearTimeout(restartTimer);
|
||||
}
|
||||
if (electronProcess) {
|
||||
electronProcess.kill();
|
||||
}
|
||||
tailwindProcess.kill();
|
||||
htmlWatcher.close();
|
||||
browserHtmlWatcher.close();
|
||||
trayHtmlWatcher.close();
|
||||
appAssetsWatcher.close();
|
||||
modelCatalogWatcher.close();
|
||||
await Promise.all([mainContext.dispose(), rendererContext.dispose(), trayRendererContext.dispose(), browserRendererContext.dispose()]);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.on("SIGINT", shutdown);
|
||||
process.on("SIGTERM", shutdown);
|
||||
@@ -0,0 +1,19 @@
|
||||
const baseConfig = require("../electron-builder.json");
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
directories: {
|
||||
...baseConfig.directories,
|
||||
output: "release-local"
|
||||
},
|
||||
mac: {
|
||||
...baseConfig.mac,
|
||||
identity: "-",
|
||||
notarize: false,
|
||||
forceCodeSigning: false
|
||||
}
|
||||
};
|
||||
|
||||
delete config.afterSign;
|
||||
|
||||
module.exports = config;
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,275 @@
|
||||
import esbuild from "esbuild";
|
||||
import { spawn } from "node:child_process";
|
||||
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { builtinModules } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export const projectRoot = path.resolve(__dirname, "..");
|
||||
export const distDir = path.join(projectRoot, "dist");
|
||||
export const mainOutDir = path.join(distDir, "main");
|
||||
export const rendererOutDir = path.join(distDir, "renderer");
|
||||
export const appAssetsDir = path.join(distDir, "assets");
|
||||
export const rendererAssetsDir = path.join(rendererOutDir, "assets");
|
||||
export const marketplacePluginsDir = path.join(distDir, "marketplace", "plugins");
|
||||
export const appAssetsInput = path.join(projectRoot, "assets");
|
||||
export const modelCatalogInput = path.join(projectRoot, "models.json");
|
||||
export const modelCatalogOutput = path.join(distDir, "models.json");
|
||||
export const rendererRoot = path.join(projectRoot, "src", "renderer");
|
||||
export const rendererHtmlInput = path.join(rendererRoot, "pages", "home", "index.html");
|
||||
export const rendererHtmlOutput = path.join(rendererOutDir, "pages", "home", "index.html");
|
||||
export const browserRendererHtmlInput = path.join(rendererRoot, "pages", "browser", "index.html");
|
||||
export const browserRendererHtmlOutput = path.join(rendererOutDir, "pages", "browser", "index.html");
|
||||
export const trayRendererHtmlInput = path.join(rendererRoot, "pages", "tray", "index.html");
|
||||
export const trayRendererHtmlOutput = path.join(rendererOutDir, "pages", "tray", "index.html");
|
||||
export const cssInput = path.join(rendererRoot, "styles", "globals.css");
|
||||
export const cssOutput = path.join(rendererAssetsDir, "main.css");
|
||||
|
||||
const nodeExternals = [
|
||||
"electron",
|
||||
"better-sqlite3",
|
||||
...builtinModules,
|
||||
...builtinModules.map((moduleName) => `node:${moduleName}`)
|
||||
];
|
||||
|
||||
export function cleanDist() {
|
||||
rmSync(distDir, { force: true, recursive: true });
|
||||
ensureDist();
|
||||
}
|
||||
|
||||
export function ensureDist() {
|
||||
mkdirSync(mainOutDir, { recursive: true });
|
||||
mkdirSync(appAssetsDir, { recursive: true });
|
||||
mkdirSync(marketplacePluginsDir, { recursive: true });
|
||||
mkdirSync(rendererAssetsDir, { recursive: true });
|
||||
mkdirSync(path.dirname(rendererHtmlOutput), { recursive: true });
|
||||
mkdirSync(path.dirname(browserRendererHtmlOutput), { recursive: true });
|
||||
mkdirSync(path.dirname(trayRendererHtmlOutput), { recursive: true });
|
||||
}
|
||||
|
||||
export function copyAppAssets() {
|
||||
ensureDist();
|
||||
if (existsSync(appAssetsInput)) {
|
||||
cpSync(appAssetsInput, appAssetsDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function copyModelCatalog() {
|
||||
ensureDist();
|
||||
if (existsSync(modelCatalogInput)) {
|
||||
cpSync(modelCatalogInput, modelCatalogOutput);
|
||||
}
|
||||
}
|
||||
|
||||
export function copyRendererHtml() {
|
||||
copyRendererPageHtml(rendererHtmlInput, rendererHtmlOutput, "main.js");
|
||||
}
|
||||
|
||||
export function copyTrayRendererHtml() {
|
||||
copyRendererPageHtml(trayRendererHtmlInput, trayRendererHtmlOutput, "tray.js");
|
||||
}
|
||||
|
||||
export function copyBrowserRendererHtml() {
|
||||
copyRendererPageHtml(browserRendererHtmlInput, browserRendererHtmlOutput, "browser.js");
|
||||
}
|
||||
|
||||
export function copyMarketplacePlugins() {
|
||||
ensureDist();
|
||||
for (const filename of ["claude-design-plugin.cjs", "cursor-proxy-plugin.cjs"]) {
|
||||
const source = path.join(projectRoot, "examples", "plugins", filename);
|
||||
const target = path.join(marketplacePluginsDir, filename);
|
||||
if (existsSync(source)) {
|
||||
cpSync(source, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyRendererPageHtml(input, output, scriptName) {
|
||||
ensureDist();
|
||||
const source = readFileSync(input, "utf8");
|
||||
const styleTag = ' <link rel="stylesheet" href="../../assets/main.css" />';
|
||||
const scriptTag = ` <script type="module" src="../../assets/${scriptName}"></script>`;
|
||||
let html = source.includes('<script type="module" src="./main.tsx"></script>')
|
||||
? source.replace(' <script type="module" src="./main.tsx"></script>', scriptTag)
|
||||
: source.replace("</body>", `${scriptTag}\n </body>`);
|
||||
|
||||
if (!html.includes('href="../../assets/main.css"')) {
|
||||
html = html.replace("</head>", `${styleTag}\n </head>`);
|
||||
}
|
||||
|
||||
writeFileSync(output, html, "utf8");
|
||||
}
|
||||
|
||||
export function createMainBuildOptions({ mode = "production", plugins = [] } = {}) {
|
||||
return {
|
||||
absWorkingDir: projectRoot,
|
||||
bundle: true,
|
||||
entryNames: "[name]",
|
||||
entryPoints: [
|
||||
path.join(projectRoot, "src", "main", "main.ts"),
|
||||
path.join(projectRoot, "src", "main", "browser-preload.ts"),
|
||||
path.join(projectRoot, "src", "main", "cli.ts"),
|
||||
path.join(projectRoot, "src", "server", "mcp", "fusion-vision-mcp.ts"),
|
||||
path.join(projectRoot, "src", "main", "preload.ts")
|
||||
],
|
||||
external: nodeExternals,
|
||||
format: "cjs",
|
||||
legalComments: "none",
|
||||
logLevel: "info",
|
||||
minify: mode === "production",
|
||||
outdir: mainOutDir,
|
||||
platform: "node",
|
||||
plugins,
|
||||
sourcemap: mode !== "production",
|
||||
target: "node22"
|
||||
};
|
||||
}
|
||||
|
||||
export function createRendererBuildOptions({ mode = "production", plugins = [] } = {}) {
|
||||
return {
|
||||
absWorkingDir: projectRoot,
|
||||
assetNames: "assets/[name]-[hash]",
|
||||
bundle: true,
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify(mode)
|
||||
},
|
||||
entryPoints: [path.join(rendererRoot, "pages", "home", "main.tsx")],
|
||||
format: "esm",
|
||||
jsx: "automatic",
|
||||
legalComments: "none",
|
||||
loader: {
|
||||
".gif": "file",
|
||||
".ico": "file",
|
||||
".jpg": "file",
|
||||
".jpeg": "file",
|
||||
".png": "file",
|
||||
".svg": "file",
|
||||
".webp": "file"
|
||||
},
|
||||
logLevel: "info",
|
||||
minify: mode === "production",
|
||||
outfile: path.join(rendererAssetsDir, "main.js"),
|
||||
platform: "browser",
|
||||
plugins: [rendererAliasPlugin(), ...plugins],
|
||||
publicPath: "../../assets",
|
||||
sourcemap: mode !== "production",
|
||||
target: "chrome120"
|
||||
};
|
||||
}
|
||||
|
||||
export function createTrayRendererBuildOptions({ mode = "production", plugins = [] } = {}) {
|
||||
return {
|
||||
...createRendererBuildOptions({ mode, plugins }),
|
||||
entryPoints: [path.join(rendererRoot, "pages", "tray", "main.tsx")],
|
||||
outfile: path.join(rendererAssetsDir, "tray.js")
|
||||
};
|
||||
}
|
||||
|
||||
export function createBrowserRendererBuildOptions({ mode = "production", plugins = [] } = {}) {
|
||||
return {
|
||||
...createRendererBuildOptions({ mode, plugins }),
|
||||
entryPoints: [path.join(rendererRoot, "pages", "browser", "main.tsx")],
|
||||
outfile: path.join(rendererAssetsDir, "browser.js")
|
||||
};
|
||||
}
|
||||
|
||||
export function watchPlugin(name, onEnd) {
|
||||
return {
|
||||
name: `${name}-watch`,
|
||||
setup(build) {
|
||||
build.onEnd((result) => {
|
||||
if (result.errors.length === 0) {
|
||||
onEnd(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildMain(options = {}) {
|
||||
await esbuild.build(createMainBuildOptions(options));
|
||||
}
|
||||
|
||||
export async function buildRenderer(options = {}) {
|
||||
await esbuild.build(createRendererBuildOptions(options));
|
||||
}
|
||||
|
||||
export async function buildTrayRenderer(options = {}) {
|
||||
await esbuild.build(createTrayRendererBuildOptions(options));
|
||||
}
|
||||
|
||||
export async function buildBrowserRenderer(options = {}) {
|
||||
await esbuild.build(createBrowserRendererBuildOptions(options));
|
||||
}
|
||||
|
||||
export async function buildStyles({ minify = false } = {}) {
|
||||
ensureDist();
|
||||
const args = ["-i", cssInput, "-o", cssOutput];
|
||||
if (minify) {
|
||||
args.push("--minify");
|
||||
}
|
||||
await runCommand(binPath("tailwindcss"), args);
|
||||
}
|
||||
|
||||
export function binPath(name) {
|
||||
const extension = process.platform === "win32" ? ".cmd" : "";
|
||||
return path.join(projectRoot, "node_modules", ".bin", `${name}${extension}`);
|
||||
}
|
||||
|
||||
export function runCommand(command, args, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
cwd: projectRoot,
|
||||
stdio: "inherit",
|
||||
shell: process.platform === "win32",
|
||||
...options
|
||||
});
|
||||
|
||||
child.on("error", reject);
|
||||
child.on("exit", (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
reject(new Error(`${path.basename(command)} exited with code ${code}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function rendererAliasPlugin() {
|
||||
return {
|
||||
name: "renderer-alias",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^@\// }, (args) => {
|
||||
return { path: resolveRendererImport(args.path.slice(2)) };
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function resolveRendererImport(importPath) {
|
||||
const basePath = path.resolve(rendererRoot, importPath);
|
||||
const candidates = [
|
||||
basePath,
|
||||
`${basePath}.tsx`,
|
||||
`${basePath}.ts`,
|
||||
`${basePath}.jsx`,
|
||||
`${basePath}.js`,
|
||||
`${basePath}.json`,
|
||||
`${basePath}.css`,
|
||||
path.join(basePath, "index.tsx"),
|
||||
path.join(basePath, "index.ts"),
|
||||
path.join(basePath, "index.jsx"),
|
||||
path.join(basePath, "index.js")
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return basePath;
|
||||
}
|
||||
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 761 KiB |
@@ -0,0 +1,72 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const errors = [];
|
||||
|
||||
function reportAndExit() {
|
||||
if (errors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error("macOS release preflight failed:");
|
||||
for (const error of errors) {
|
||||
console.error(`- ${error}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function run(command, args) {
|
||||
try {
|
||||
return execFileSync(command, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
||||
} catch (error) {
|
||||
const stderr = error.stderr?.toString().trim();
|
||||
const stdout = error.stdout?.toString().trim();
|
||||
const details = stderr || stdout || error.message;
|
||||
errors.push(`cannot run ${command} ${args.join(" ")}: ${details}`);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function hasCompleteNotaryCredentials() {
|
||||
const hasAppPassword =
|
||||
Boolean(process.env.APPLE_ID) &&
|
||||
Boolean(process.env.APPLE_APP_SPECIFIC_PASSWORD) &&
|
||||
Boolean(process.env.APPLE_TEAM_ID);
|
||||
|
||||
const hasApiKey =
|
||||
Boolean(process.env.APPLE_API_KEY) &&
|
||||
Boolean(process.env.APPLE_API_KEY_ID) &&
|
||||
Boolean(process.env.APPLE_API_ISSUER);
|
||||
|
||||
const hasKeychainProfile = Boolean(process.env.APPLE_KEYCHAIN_PROFILE);
|
||||
|
||||
return hasAppPassword || hasApiKey || hasKeychainProfile;
|
||||
}
|
||||
|
||||
if (process.platform !== "darwin") {
|
||||
errors.push("macOS release builds must run on macOS.");
|
||||
reportAndExit();
|
||||
}
|
||||
|
||||
const developerDir = run("xcode-select", ["-p"]).trim();
|
||||
if (developerDir.endsWith("/CommandLineTools")) {
|
||||
errors.push("notarization stapling requires full Xcode. Install Xcode and run `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`.");
|
||||
}
|
||||
|
||||
run("xcrun", ["notarytool", "--version"]);
|
||||
run("xcrun", ["-f", "stapler"]);
|
||||
|
||||
if (!process.env.CSC_LINK) {
|
||||
const identities = run("security", ["find-identity", "-v", "-p", "codesigning"]);
|
||||
if (!/"Developer ID Application: .+ \([A-Z0-9]+\)"/.test(identities)) {
|
||||
errors.push("no Developer ID Application signing identity was found in the keychain. Install a Developer ID Application certificate or provide CSC_LINK/CSC_KEY_PASSWORD.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasCompleteNotaryCredentials()) {
|
||||
errors.push(
|
||||
"missing notarization credentials. Set APPLE_API_KEY + APPLE_API_KEY_ID + APPLE_API_ISSUER, or APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD + APPLE_TEAM_ID, or APPLE_KEYCHAIN_PROFILE."
|
||||
);
|
||||
}
|
||||
|
||||
reportAndExit();
|
||||
console.log("macOS release preflight passed.");
|
||||
@@ -0,0 +1,24 @@
|
||||
const { execFileSync } = require("node:child_process");
|
||||
const path = require("node:path");
|
||||
|
||||
function run(command, args) {
|
||||
execFileSync(command, args, { stdio: "inherit" });
|
||||
}
|
||||
|
||||
module.exports = async function verifyMacosNotarization(context) {
|
||||
if (context.electronPlatformName !== "darwin") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.CCR_SKIP_MAC_NOTARIZATION_VERIFY === "1") {
|
||||
console.warn("Skipping macOS notarization verification because CCR_SKIP_MAC_NOTARIZATION_VERIFY=1.");
|
||||
return;
|
||||
}
|
||||
|
||||
const productFilename = context.packager.appInfo.productFilename;
|
||||
const appPath = path.join(context.appOutDir, `${productFilename}.app`);
|
||||
|
||||
run("codesign", ["--verify", "--deep", "--strict", "--verbose=2", appPath]);
|
||||
run("xcrun", ["stapler", "validate", appPath]);
|
||||
run("spctl", ["--assess", "--type", "execute", "--verbose=4", appPath]);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
module.exports = async function verifyUpdateMetadata(buildResult) {
|
||||
const artifactPaths = Array.isArray(buildResult?.artifactPaths) ? buildResult.artifactPaths : [];
|
||||
const outputDirs = new Set(artifactPaths.map((artifactPath) => path.dirname(artifactPath)));
|
||||
const metadataFiles = artifactPaths.filter((artifactPath) => /^latest(?:-[a-z]+)?\.ya?ml$/i.test(path.basename(artifactPath)));
|
||||
|
||||
for (const outputDir of outputDirs) {
|
||||
for (const filename of fs.readdirSync(outputDir)) {
|
||||
const candidate = path.join(outputDir, filename);
|
||||
if (/^latest(?:-[a-z]+)?\.ya?ml$/i.test(filename) && !metadataFiles.includes(candidate)) {
|
||||
metadataFiles.push(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
for (const metadataFile of metadataFiles) {
|
||||
const metadataDir = path.dirname(metadataFile);
|
||||
const metadata = fs.readFileSync(metadataFile, "utf8");
|
||||
for (const artifact of readReferencedArtifacts(metadata)) {
|
||||
if (isRemoteUrl(artifact)) {
|
||||
continue;
|
||||
}
|
||||
const artifactPath = path.resolve(metadataDir, artifact);
|
||||
if (!fs.existsSync(artifactPath)) {
|
||||
errors.push(`${path.relative(process.cwd(), metadataFile)} references missing artifact ${artifact}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(`Update metadata validation failed:\n${errors.map((error) => `- ${error}`).join("\n")}`);
|
||||
}
|
||||
};
|
||||
|
||||
function readReferencedArtifacts(metadata) {
|
||||
const values = new Set();
|
||||
for (const line of metadata.split(/\r?\n/)) {
|
||||
const match = line.match(/^\s*(?:path|url):\s*(.+?)\s*$/);
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
const value = match[1].replace(/^['"]|['"]$/g, "").trim();
|
||||
if (value) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
return [...values];
|
||||
}
|
||||
|
||||
function isRemoteUrl(value) {
|
||||
return /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/index.css",
|
||||
"css": "src/renderer/styles/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
@@ -14,8 +14,7 @@
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
"lib": "@/lib"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = async function router(req, config) {
|
||||
return "deepseek,deepseek-chat";
|
||||
};
|
||||
@@ -1,29 +1,5 @@
|
||||
# Docusaurus build output
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Docusaurus generated files
|
||||
.docusaurus/
|
||||
|
||||
# Node modules
|
||||
node_modules/
|
||||
|
||||
# Environment variables
|
||||
dist/
|
||||
.astro/
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
*.swp
|
||||
*.swo
|
||||
.env.*
|
||||
|
||||
@@ -1,104 +1,23 @@
|
||||
# Claude Code Router Documentation
|
||||
# Claude Code Router Docs
|
||||
|
||||
This directory contains the documentation website built with [Docusaurus](https://docusaurus.io/).
|
||||
Astro-powered documentation site for Claude Code Router.
|
||||
|
||||
## Development
|
||||
## Commands
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
pnpm install
|
||||
```sh
|
||||
npm install
|
||||
npm run dev
|
||||
npm run build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### Start Development Server
|
||||
The local development server runs from this `docs` directory.
|
||||
|
||||
```bash
|
||||
# From docs directory
|
||||
pnpm start
|
||||
## Content
|
||||
|
||||
# Or from root directory
|
||||
pnpm dev:docs
|
||||
```
|
||||
Docs pages are authored in Markdown:
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) to view the documentation.
|
||||
- Chinese: `src/content/docs/zh/index.md`
|
||||
- English: `src/content/docs/en/index.md`
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
# From docs directory
|
||||
pnpm build
|
||||
|
||||
# Or from root directory
|
||||
pnpm build:docs
|
||||
```
|
||||
|
||||
The built files will be in the `build/` directory.
|
||||
|
||||
## Serve Built Files
|
||||
|
||||
```bash
|
||||
# From docs directory
|
||||
pnpm serve
|
||||
|
||||
# Or from root directory
|
||||
pnpm serve:docs
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── docs/ # Markdown documentation files
|
||||
│ ├── intro.md # Introduction page
|
||||
│ ├── installation.md
|
||||
│ ├── config/ # Configuration docs
|
||||
│ ├── advanced/ # Advanced topics
|
||||
│ └── cli/ # CLI reference
|
||||
├── src/ # React components and pages
|
||||
│ ├── components/ # Custom React components
|
||||
│ ├── pages/ # Additional pages
|
||||
│ ├── css/ # Custom CSS
|
||||
│ └── theme/ # Docusaurus theme customization
|
||||
├── static/ # Static assets (images, etc.)
|
||||
├── i18n/ # Internationalization files
|
||||
├── docusaurus.config.ts # Docusaurus configuration
|
||||
└── sidebars.ts # Documentation sidebar structure
|
||||
```
|
||||
|
||||
## Adding Documentation
|
||||
|
||||
### Adding New Docs
|
||||
|
||||
Create a new Markdown file in the `docs/` directory and add it to `sidebars.ts`.
|
||||
|
||||
### Adding New Pages
|
||||
|
||||
Add React components to `src/pages/`.
|
||||
|
||||
### Customizing Styles
|
||||
|
||||
Edit `src/css/custom.css`.
|
||||
|
||||
## Internationalization
|
||||
|
||||
Documentation supports both English and Chinese.
|
||||
|
||||
- English: `docs/` and `src/`
|
||||
- Chinese: `i18n/zh/docusaurus-plugin-content-docs/current/`
|
||||
|
||||
To add Chinese translations:
|
||||
|
||||
1. Create corresponding files in `i18n/zh/docusaurus-plugin-content-docs/current/`
|
||||
2. Translate the content
|
||||
|
||||
## Deployment
|
||||
|
||||
The documentation can be deployed to:
|
||||
|
||||
- GitHub Pages
|
||||
- Netlify
|
||||
- Vercel
|
||||
- Any static hosting service
|
||||
|
||||
See [Docusaurus deployment docs](https://docusaurus.io/docs/deployment) for details.
|
||||
Frontmatter provides the page title, eyebrow, and lead text. Markdown headings generate the right-side table of contents, and fenced code blocks are compiled with Shiki highlighting.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
|
||||
export default defineConfig({
|
||||
output: "static",
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: "github-light",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
title: Project Motivation and Principles
|
||||
date: 2025-02-25
|
||||
tags: [claude-code, reverse-engineering, tutorial]
|
||||
---
|
||||
|
||||
# Project Motivation and Principles
|
||||
|
||||
As early as the day after Claude Code was released (2025-02-25), I began and completed a reverse engineering attempt of the project. At that time, using Claude Code required registering for an Anthropic account, applying for a waitlist, and waiting for approval. However, due to well-known reasons, Anthropic blocks users from mainland China, making it impossible for me to use the service through normal means. Based on known information, I discovered the following:
|
||||
|
||||
1. Claude Code is installed via npm, so it's very likely developed with Node.js.
|
||||
2. Node.js offers various debugging methods: simple `console.log` usage, launching with `--inspect` to hook into Chrome DevTools, or even debugging obfuscated code using `d8`.
|
||||
|
||||
My goal was to use Claude Code without an Anthropic account. I didn't need the full source code—just a way to intercept and reroute requests made by Claude Code to Anthropic's models to my own custom endpoint. So I started the reverse engineering process:
|
||||
|
||||
1. First, install Claude Code:
|
||||
```bash
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
```
|
||||
|
||||
2. After installation, the project is located at `~/.nvm/versions/node/v20.10.0/lib/node_modules/@anthropic-ai/claude-code`(this may vary depending on your Node version manager and version).
|
||||
|
||||
3. Open the package.json to analyze the entry point:
|
||||
```package.json
|
||||
{
|
||||
"name": "@anthropic-ai/claude-code",
|
||||
"version": "1.0.24",
|
||||
"main": "sdk.mjs",
|
||||
"types": "sdk.d.ts",
|
||||
"bin": {
|
||||
"claude": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "Boris Cherny <boris@anthropic.com>",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"description": "Use Claude, Anthropic's AI assistant, right from your terminal. Claude can understand your codebase, edit files, run terminal commands, and handle entire workflows for you.",
|
||||
"homepage": "https://github.com/anthropics/claude-code",
|
||||
"bugs": {
|
||||
"url": "https://github.com/anthropics/claude-code/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node -e \"if (!process.env.AUTHORIZED) { console.error('ERROR: Direct publishing is not allowed.\\nPlease use the publish-external.sh script to publish this package.'); process.exit(1); }\"",
|
||||
"preinstall": "node scripts/preinstall.js"
|
||||
},
|
||||
"dependencies": {},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "^0.33.5",
|
||||
"@img/sharp-darwin-x64": "^0.33.5",
|
||||
"@img/sharp-linux-arm": "^0.33.5",
|
||||
"@img/sharp-linux-arm64": "^0.33.5",
|
||||
"@img/sharp-linux-x64": "^0.33.5",
|
||||
"@img/sharp-win32-x64": "^0.33.5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The key entry is `"claude": "cli.js"`. Opening cli.js, you'll see the code is minified and obfuscated. But using WebStorm's `Format File` feature, you can reformat it for better readability:
|
||||

|
||||
|
||||
Now you can begin understanding Claude Code's internal logic and prompt structure by reading the code. To dig deeper, you can insert console.log statements or launch in debug mode with Chrome DevTools using:
|
||||
|
||||
```bash
|
||||
NODE_OPTIONS="--inspect-brk=9229" claude
|
||||
```
|
||||
|
||||
This command starts Claude Code in debug mode and opens port 9229. Visit chrome://inspect/ in Chrome and click inspect to begin debugging:
|
||||

|
||||

|
||||
|
||||
By searching for the keyword api.anthropic.com, you can easily locate where Claude Code makes its API calls. From the surrounding code, it's clear that baseURL can be overridden with the `ANTHROPIC_BASE_URL` environment variable, and `apiKey` and `authToken` can be configured similarly:
|
||||

|
||||
|
||||
So far, we've discovered some key information:
|
||||
|
||||
1. Environment variables can override Claude Code's `baseURL` and `apiKey`.
|
||||
|
||||
2. Claude Code adheres to the Anthropic API specification.
|
||||
|
||||
Therefore, we need:
|
||||
1. A service to convert OpenAI API-compatible requests into Anthropic API format.
|
||||
|
||||
2. Set the environment variables before launching Claude Code to redirect requests to this service.
|
||||
|
||||
Thus, `claude-code-router` was born. This project uses `Express.js` to implement the `/v1/messages` endpoint. It leverages middlewares to transform request/response formats and supports request rewriting (useful for prompt tuning per model).
|
||||
|
||||
Back in February, the full DeepSeek model series had poor support for Function Calling, so I initially used `qwen-max`. It worked well—but without KV cache support, it consumed a large number of tokens and couldn't provide the native `Claude Code` experience.
|
||||
|
||||
So I experimented with a Router-based mode using a lightweight model to dispatch tasks. The architecture included four roles: `router`, `tool`, `think`, and `coder`. Each request passed through a free lightweight model that would decide whether the task involved reasoning, coding, or tool usage. Reasoning and coding tasks looped until a tool was invoked to apply changes. However, the lightweight model lacked the capability to route tasks accurately, and architectural issues prevented it from effectively driving Claude Code.
|
||||
|
||||
Everything changed at the end of May when the official Claude Code was launched, and `DeepSeek-R1` model (released 2025-05-28) added Function Call support. I redesigned the system. With the help of AI pair programming, I fixed earlier request/response transformation issues—especially the handling of models that return JSON instead of Function Call outputs.
|
||||
|
||||
This time, I used the `DeepSeek-V3` model. It performed better than expected: supporting most tool calls, handling task decomposition and stepwise planning, and—most importantly—costing less than one-tenth the price of Claude 3.5 Sonnet.
|
||||
|
||||
The official Claude Code organizes agents differently from the beta version, so I restructured my Router mode to include four roles: the default model, `background`, `think`, and `longContext`.
|
||||
|
||||
- The default model handles general tasks and acts as a fallback.
|
||||
|
||||
- The `background` model manages lightweight background tasks. According to Anthropic, Claude Haiku 3.5 is often used here, so I routed this to a local `ollama` service.
|
||||
|
||||
- The `think` model is responsible for reasoning and planning mode tasks. I use `DeepSeek-R1` here, though it doesn't support cost control, so `Think` and `UltraThink` behave identically.
|
||||
|
||||
- The `longContext` model handles long-context scenarios. The router uses `tiktoken` to calculate token lengths in real time, and if the context exceeds 32K, it switches to this model to compensate for DeepSeek's long-context limitations.
|
||||
|
||||
This describes the evolution and reasoning behind the project. By cleverly overriding environment variables, we can forward and modify requests without altering Claude Code's source—allowing us to benefit from official updates while using our own models and custom prompts.
|
||||
|
||||
This project offers a practical approach to running Claude Code under Anthropic's regional restrictions, balancing `cost`, `performance`, and `customizability`. That said, the official `Max Plan` still offers the best experience if available.
|
||||
@@ -1,94 +0,0 @@
|
||||
---
|
||||
title: GLM-4.6 Supports Reasoning and Interleaved Thinking
|
||||
date: 2025-11-18
|
||||
tags: [glm, reasoning, chain-of-thought]
|
||||
---
|
||||
|
||||
# GLM-4.6 Supports Reasoning and Interleaved Thinking
|
||||
|
||||
## Enabling Reasoning in Claude Code with GLM-4.6
|
||||
|
||||
Starting from version 4.5, GLM has supported Claude Code. I've been following its progress closely, and many users have reported that reasoning could not be enabled within Claude Code. Recently, thanks to sponsorship from Zhipu, I decided to investigate this issue in depth. According to the [official documentation](https://docs.z.ai/api-reference/llm/chat-completion), the`/chat/completions` endpoint has reasoning enabled by default, but the model itself decides whether to think:
|
||||
|
||||
```
|
||||
thinking.type enum<string> default:enabled
|
||||
|
||||
Whether to enable the chain of thought(When enabled, GLM-4.6, GLM-4.5 and others will automatically determine whether to think, while GLM-4.5V will think compulsorily), default: enabled
|
||||
|
||||
Available options: enabled, disabled
|
||||
```
|
||||
|
||||
However, within Claude Code, its heavy system prompt interference disrupts GLM's internal reasoning judgment, causing the model to rarely think.
|
||||
Therefore, we need to explicitly guide the model to believe reasoning is required. Since claude-code-router functions as a proxy, the only feasible approach is modifying prompts or parameters.
|
||||
|
||||
Initially, I tried completely removing Claude Code's system prompt — and indeed, the model started reasoning — but that broke Claude Code's workflow.
|
||||
So instead, I used prompt injection to clearly instruct the model to think step by step.
|
||||
|
||||
|
||||
```javascript
|
||||
// transformer.ts
|
||||
import { UnifiedChatRequest } from "../types/llm";
|
||||
import { Transformer } from "../types/transformer";
|
||||
|
||||
export class ForceReasoningTransformer implements Transformer {
|
||||
name = "forcereasoning";
|
||||
|
||||
async transformRequestIn(
|
||||
request: UnifiedChatRequest
|
||||
): Promise<UnifiedChatRequest> {
|
||||
const systemMessage = request.messages.find(
|
||||
(item) => item.role === "system"
|
||||
);
|
||||
if (Array.isArray(systemMessage?.content)) {
|
||||
systemMessage.content.push({
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
});
|
||||
}
|
||||
const lastMessage = request.messages[request.messages.length - 1];
|
||||
if (lastMessage.role === "user" && Array.isArray(lastMessage.content)) {
|
||||
lastMessage.content.push({
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
});
|
||||
}
|
||||
if (lastMessage.role === "tool") {
|
||||
request.messages.push({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model.\nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly.\nNever skip your chain of thought.\nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return request;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Why use `<reasoning_content>` instead of the `<think>` tag? Two reasons:
|
||||
|
||||
1. Using the `<think>` tag doesn't effectively trigger reasoning — likely because the model was trained on data where `<think>` had special behavior.
|
||||
|
||||
2. If we use `<think>`, the reasoning output is split into a separate field, which directly relates to the chain-of-thought feedback problem discussed below.
|
||||
|
||||
## Chain-of-Thought Feedback
|
||||
Recently, Minimax released `Minimax-m2`, along with [an article](https://www.minimaxi.com/news/why-is-interleaved-thinking-important-for-m2) explaining interleaved thinking.
|
||||
While the idea isn't entirely new, it's a good opportunity to analyze it.
|
||||
|
||||
Why do we need to interleaved thinking?
|
||||
Minimax's article mentions that the Chat Completion API does not support passing reasoning content between requests.
|
||||
We know ChatGPT was the first to support reasoning, but OpenAI initially didn't expose the chain of thought to users.
|
||||
Therefore, the Chat Completion API didn't need to support it. Even the CoT field was first introduced by DeepSeek.
|
||||
|
||||
Do we really need explicit CoT fields? What happens if we don't have them? Will it affect reasoning?
|
||||
By inspecting [sglang's source code](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/parser/reasoning_parser.py), we can see that reasoning content is naturally emitted in messages with specific markers.
|
||||
If we don't split it out, the next-round conversation will naturally include it.
|
||||
Thus, the only reason we need interleaved thinking is because we separated the reasoning content from the normal messages.
|
||||
|
||||
With fewer than 40 lines of code above, I implemented a simple exploration of enabling reasoning and chain-of-thought feedback for GLM-4.5/4.6.
|
||||
(It's only simple because I haven't implemented parsing logic yet — you could easily modify the transformer to split reasoning output on response and merge it back on request, improving Claude Code's frontend display compatibility.)
|
||||
|
||||
If you have better ideas, feel free to reach out — I'd love to discuss further.
|
||||
@@ -1,111 +0,0 @@
|
||||
---
|
||||
title: Maybe We Can Do More with the Router
|
||||
date: 2025-11-18
|
||||
tags: [router, transformer, deepseek]
|
||||
---
|
||||
|
||||
# Maybe We Can Do More with the Router
|
||||
|
||||
Since the release of `claude-code-router`, I've received a lot of user feedback, and quite a few issues are still open. Most of them are related to support for different providers and the lack of tool usage from the deepseek model.
|
||||
|
||||
Originally, I created this project for personal use, mainly to access claude code at a lower cost. So, multi-provider support wasn't part of the initial design. But during troubleshooting, I discovered that even though most providers claim to be compatible with the OpenAI-style `/chat/completions` interface, there are many subtle differences. For example:
|
||||
|
||||
1. When Gemini's tool parameter type is string, the `format` field only supports `date` and `date-time`, and there's no tool call ID.
|
||||
|
||||
2. OpenRouter requires `cache_control` for caching.
|
||||
|
||||
3. The official DeepSeek API has a `max_output` of 8192, but Volcano Engine's limit is even higher.
|
||||
|
||||
Aside from these, smaller providers often have quirks in their parameter handling. So I decided to create a new project, [musistudio/llms](https://github.com/musistudio/llms), to deal with these compatibility issues. It uses the OpenAI format as a base and introduces a generic Transformer interface for transforming both requests and responses.
|
||||
|
||||
Once a `Transformer` is implemented for each provider, it becomes possible to mix-and-match requests between them. For example, I implemented bidirectional conversion between Anthropic and OpenAI formats in `AnthropicTransformer`, which listens to the `/v1/messages` endpoint. Similarly, `GeminiTransformer` handles Gemini <-> OpenAI format conversions and listens to `/v1beta/models/:modelAndAction`.
|
||||
|
||||
When both requests and responses are transformed into a common format, they can interoperate seamlessly:
|
||||
|
||||
```
|
||||
AnthropicRequest -> AnthropicTransformer -> OpenAIRequest -> GeminiTransformer -> GeminiRequest -> GeminiServer
|
||||
```
|
||||
|
||||
```
|
||||
GeminiResponse -> GeminiTransformer -> OpenAIResponse -> AnthropicTransformer -> AnthropicResponse
|
||||
```
|
||||
|
||||
Using a middleware layer to smooth out differences may introduce some performance overhead, but the main goal here is to enable `claude-code-router` to support multiple providers.
|
||||
|
||||
As for the issue of DeepSeek's lackluster tool usage — I found that it stems from poor instruction adherence in long conversations. Initially, the model actively calls tools, but after several rounds, it starts responding with plain text instead. My first workaround was injecting a system prompt to remind the model to use tools proactively. But in long contexts, the model tends to forget this instruction.
|
||||
|
||||
After reading the DeepSeek documentation, I noticed it supports the `tool_choice` parameter, which can be set to `"required"` to force the model to use at least one tool. I tested this by enabling the parameter, and it significantly improved the model's tool usage. We can remove the setting when it's no longer necessary. With the help of the `Transformer` interface in [musistudio/llms](https://github.com/musistudio/llms), we can modify the request before it's sent and adjust the response after it's received.
|
||||
|
||||
Inspired by the Plan Mode in `claude code`, I implemented a similar Tool Mode for DeepSeek:
|
||||
|
||||
```typescript
|
||||
export class TooluseTransformer implements Transformer {
|
||||
name = "tooluse";
|
||||
|
||||
transformRequestIn(request: UnifiedChatRequest): UnifiedChatRequest {
|
||||
if (request.tools?.length) {
|
||||
request.messages.push({
|
||||
role: "system",
|
||||
content: `<system-reminder>Tool mode is active. The user expects you to proactively execute the most suitable tool to help complete the task.
|
||||
Before invoking a tool, you must carefully evaluate whether it matches the current task. If no available tool is appropriate for the task, you MUST call the \`ExitTool\` to exit tool mode — this is the only valid way to terminate tool mode.
|
||||
Always prioritize completing the user's task effectively and efficiently by using tools whenever appropriate.</system-reminder>`,
|
||||
});
|
||||
request.tool_choice = "required";
|
||||
request.tools.unshift({
|
||||
type: "function",
|
||||
function: {
|
||||
name: "ExitTool",
|
||||
description: `Use this tool when you are in tool mode and have completed the task. This is the only valid way to exit tool mode.
|
||||
IMPORTANT: Before using this tool, ensure that none of the available tools are applicable to the current task. You must evaluate all available options — only if no suitable tool can help you complete the task should you use ExitTool to terminate tool mode.
|
||||
Examples:
|
||||
1. Task: "Use a tool to summarize this document" — Do not use ExitTool if a summarization tool is available.
|
||||
2. Task: "What's the weather today?" — If no tool is available to answer, use ExitTool after reasoning that none can fulfill the task.`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
response: {
|
||||
type: "string",
|
||||
description:
|
||||
"Your response will be forwarded to the user exactly as returned — the tool will not modify or post-process it in any way.",
|
||||
},
|
||||
},
|
||||
required: ["response"],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
async transformResponseOut(response: Response): Promise<Response> {
|
||||
if (response.headers.get("Content-Type")?.includes("application/json")) {
|
||||
const jsonResponse = await response.json();
|
||||
if (
|
||||
jsonResponse?.choices[0]?.message.tool_calls?.length &&
|
||||
jsonResponse?.choices[0]?.message.tool_calls[0]?.function?.name ===
|
||||
"ExitTool"
|
||||
) {
|
||||
const toolArguments = JSON.parse(toolCall.function.arguments || "{}");
|
||||
jsonResponse.choices[0].message.content = toolArguments.response || "";
|
||||
delete jsonResponse.choices[0].message.tool_calls;
|
||||
}
|
||||
|
||||
// Handle non-streaming response if needed
|
||||
return new Response(JSON.stringify(jsonResponse), {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
});
|
||||
} else if (response.headers.get("Content-Type")?.includes("stream")) {
|
||||
// ...
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This transformer ensures the model calls at least one tool. If no tools are appropriate or the task is finished, it can exit using `ExitTool`. Since this relies on the `tool_choice` parameter, it only works with models that support it.
|
||||
|
||||
In practice, this approach noticeably improves tool usage for DeepSeek. The tradeoff is that sometimes the model may invoke irrelevant or unnecessary tools, which could increase latency and token usage.
|
||||
|
||||
This update is just a small experiment — adding an `"agent"` to the router. Maybe there are more interesting things we can explore from here.
|
||||
@@ -1,108 +0,0 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# ccr model
|
||||
|
||||
Interactive model selection and configuration.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
ccr model [command]
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### Select Model
|
||||
|
||||
Interactively select a model:
|
||||
|
||||
```bash
|
||||
ccr model
|
||||
```
|
||||
|
||||
This will display an interactive menu with available providers and models.
|
||||
|
||||
### Set Default Model
|
||||
|
||||
Set the default model directly:
|
||||
|
||||
```bash
|
||||
ccr model set <provider>,<model>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
ccr model set deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### List Models
|
||||
|
||||
List all configured models:
|
||||
|
||||
```bash
|
||||
ccr model list
|
||||
```
|
||||
|
||||
### Add Model
|
||||
|
||||
Add a new model to configuration:
|
||||
|
||||
```bash
|
||||
ccr model add <provider>,<model>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
ccr model add groq,llama-3.3-70b-versatile
|
||||
```
|
||||
|
||||
### Remove Model
|
||||
|
||||
Remove a model from configuration:
|
||||
|
||||
```bash
|
||||
ccr model remove <provider>,<model>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Interactive selection
|
||||
|
||||
```bash
|
||||
$ ccr model
|
||||
|
||||
? Select a provider: deepseek
|
||||
? Select a model: deepseek-chat
|
||||
|
||||
Default model set to: deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### Direct configuration
|
||||
|
||||
```bash
|
||||
ccr model set deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### View current configuration
|
||||
|
||||
```bash
|
||||
ccr model list
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Configured Models:
|
||||
deepseek,deepseek-chat (default)
|
||||
groq,llama-3.3-70b-versatile
|
||||
gemini,gemini-1.5-pro
|
||||
```
|
||||
|
||||
## Related Commands
|
||||
|
||||
- [ccr start](/docs/cli/start) - Start the server
|
||||
- [ccr config](/docs/cli/other-commands#ccr-config) - Edit configuration
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Other Commands
|
||||
|
||||
Additional CLI commands for managing Claude Code Router.
|
||||
|
||||
## ccr stop
|
||||
|
||||
Stop the running server.
|
||||
|
||||
```bash
|
||||
ccr stop
|
||||
```
|
||||
|
||||
## ccr restart
|
||||
|
||||
Restart the server.
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
## ccr code
|
||||
|
||||
Execute a claude command through the router.
|
||||
|
||||
```bash
|
||||
ccr code [args...]
|
||||
```
|
||||
|
||||
## ccr ui
|
||||
|
||||
Open the Web UI in your browser.
|
||||
|
||||
```bash
|
||||
ccr ui
|
||||
```
|
||||
|
||||
## ccr activate
|
||||
|
||||
Output shell environment variables for integration with external tools.
|
||||
|
||||
```bash
|
||||
ccr activate
|
||||
```
|
||||
|
||||
## Global Options
|
||||
|
||||
These options can be used with any command:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-h, --help` | Show help |
|
||||
| `-v, --version` | Show version number |
|
||||
| `--config <path>` | Path to configuration file |
|
||||
| `--verbose` | Enable verbose output |
|
||||
|
||||
## Examples
|
||||
|
||||
### Stop the server
|
||||
|
||||
```bash
|
||||
ccr stop
|
||||
```
|
||||
|
||||
### Restart with custom config
|
||||
|
||||
```bash
|
||||
ccr restart --config /path/to/config.json
|
||||
```
|
||||
|
||||
### Open Web UI
|
||||
|
||||
```bash
|
||||
ccr ui
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Getting Started](/docs/intro) - Introduction to Claude Code Router
|
||||
- [Configuration](/docs/config/basic) - Configuration guide
|
||||
@@ -1,254 +0,0 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# ccr preset
|
||||
|
||||
Manage presets - configuration templates that can be shared and reused.
|
||||
|
||||
## Overview
|
||||
|
||||
Presets allow you to:
|
||||
- Save your current configuration as a reusable template
|
||||
- Share configurations with others
|
||||
- Install pre-configured setups from the community
|
||||
- Switch between different configurations easily
|
||||
|
||||
## Commands
|
||||
|
||||
### export
|
||||
|
||||
Export your current configuration as a preset.
|
||||
|
||||
```bash
|
||||
ccr preset export <name> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--output <path>` - Custom output directory path
|
||||
- `--description <text>` - Preset description
|
||||
- `--author <name>` - Preset author
|
||||
- `--tags <tags>` - Comma-separated keywords
|
||||
- `--include-sensitive` - Include API keys and sensitive data (not recommended)
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
ccr preset export my-config --description "My production setup" --author "Your Name"
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. Reads current configuration from `~/.claude-code-router/config.json`
|
||||
2. Prompts for description, author, and keywords (if not provided)
|
||||
3. Sanitizes sensitive fields (API keys become placeholders)
|
||||
4. Creates preset directory at `~/.claude-code-router/presets/<name>/`
|
||||
5. Generates `manifest.json` with configuration and metadata
|
||||
|
||||
### install
|
||||
|
||||
Install a preset from a local directory.
|
||||
|
||||
```bash
|
||||
ccr preset install <source>
|
||||
```
|
||||
|
||||
**Sources:**
|
||||
- Local directory path: `/path/to/preset-directory`
|
||||
- Preset name (for reconfiguring an already installed preset): `preset-name`
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Install from directory
|
||||
ccr preset install ./my-preset
|
||||
|
||||
# Reconfigure an installed preset
|
||||
ccr preset install my-preset
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. Reads `manifest.json` from the preset directory
|
||||
2. Validates the preset structure
|
||||
3. If the preset has a `schema`, prompts for required values (API keys, etc.)
|
||||
4. Copies preset to `~/.claude-code-router/presets/<name>/`
|
||||
5. Saves user inputs in `manifest.json`
|
||||
|
||||
**Note:** URL installation is not currently supported. Download the preset directory first.
|
||||
|
||||
### list
|
||||
|
||||
List all installed presets.
|
||||
|
||||
```bash
|
||||
ccr preset list
|
||||
```
|
||||
|
||||
**Example output:**
|
||||
```
|
||||
Available presets:
|
||||
|
||||
• my-config (v1.0.0)
|
||||
My production setup
|
||||
by Your Name
|
||||
|
||||
• openai-setup
|
||||
Basic OpenAI configuration
|
||||
```
|
||||
|
||||
### info
|
||||
|
||||
Show detailed information about a preset.
|
||||
|
||||
```bash
|
||||
ccr preset info <name>
|
||||
```
|
||||
|
||||
**Shows:**
|
||||
- Version, description, author, keywords
|
||||
- Configuration summary (Providers, Router rules)
|
||||
- Required inputs (if any)
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
ccr preset info my-config
|
||||
```
|
||||
|
||||
### delete / rm / remove
|
||||
|
||||
Delete an installed preset.
|
||||
|
||||
```bash
|
||||
ccr preset delete <name>
|
||||
ccr preset rm <name>
|
||||
ccr preset remove <name>
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
ccr preset delete my-config
|
||||
```
|
||||
|
||||
## Preset Structure
|
||||
|
||||
A preset is a directory containing a `manifest.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-preset",
|
||||
"version": "1.0.0",
|
||||
"description": "My configuration",
|
||||
"author": "Author Name",
|
||||
"keywords": ["openai", "production"],
|
||||
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"api_base_url": "https://api.openai.com/v1/chat/completions",
|
||||
"api_key": "{{apiKey}}",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
},
|
||||
|
||||
"schema": [
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "OpenAI API Key",
|
||||
"prompt": "Enter your OpenAI API key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Schema System
|
||||
|
||||
The `schema` field defines inputs that users must provide during installation:
|
||||
|
||||
**Field types:**
|
||||
- `password` - Hidden input (for API keys)
|
||||
- `input` - Text input
|
||||
- `select` - Single selection from options
|
||||
- `multiselect` - Multiple selection
|
||||
- `confirm` - Yes/No confirmation
|
||||
- `editor` - Multi-line text
|
||||
- `number` - Numeric input
|
||||
|
||||
**Dynamic options:**
|
||||
```json
|
||||
{
|
||||
"id": "provider",
|
||||
"type": "select",
|
||||
"label": "Select Provider",
|
||||
"options": {
|
||||
"type": "providers"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Conditional fields:**
|
||||
```json
|
||||
{
|
||||
"id": "model",
|
||||
"type": "select",
|
||||
"label": "Select Model",
|
||||
"when": {
|
||||
"field": "provider",
|
||||
"operator": "exists"
|
||||
},
|
||||
"options": {
|
||||
"type": "models",
|
||||
"providerField": "#{selectedProvider}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sharing Presets
|
||||
|
||||
To share a preset:
|
||||
|
||||
1. **Export your configuration:**
|
||||
```bash
|
||||
ccr preset export my-preset
|
||||
```
|
||||
|
||||
2. **Share the directory:**
|
||||
```bash
|
||||
~/.claude-code-router/presets/my-preset/
|
||||
```
|
||||
|
||||
3. **Distribution methods:**
|
||||
- Upload to GitHub repository
|
||||
- Create a GitHub Gist
|
||||
- Share as a zip file
|
||||
- Publish on npm (future feature)
|
||||
|
||||
4. **Users install with:**
|
||||
```bash
|
||||
ccr preset install /path/to/my-preset
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Automatic Sanitization
|
||||
|
||||
By default, `export` sanitizes sensitive fields:
|
||||
- Fields named `api_key`, `apikey`, `password`, `secret` are replaced with `{{fieldName}}` placeholders
|
||||
- These placeholders become required inputs in the schema
|
||||
- Users are prompted to provide their own values during installation
|
||||
|
||||
### Include Sensitive Data
|
||||
|
||||
To include actual values (not recommended):
|
||||
```bash
|
||||
ccr preset export my-preset --include-sensitive
|
||||
```
|
||||
|
||||
**Warning:** Never share presets containing sensitive data!
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Configuration Guide](/docs/cli/config/basic) - Basic configuration
|
||||
- [Project-Level Configuration](/docs/cli/config/project-level) - Project-specific settings
|
||||
- [Presets](/docs/presets/intro) - Advanced preset topics
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# ccr start
|
||||
|
||||
Start the Claude Code Router server.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
ccr start [options]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Alias | Description |
|
||||
|--------|-------|-------------|
|
||||
| `--port <number>` | `-p` | Port to listen on (default: 8080) |
|
||||
| `--config <path>` | `-c` | Path to configuration file |
|
||||
| `--daemon` | `-d` | Run as daemon (background process) |
|
||||
| `--log-level <level>` | `-l` | Log level (fatal/error/warn/info/debug/trace) |
|
||||
|
||||
## Examples
|
||||
|
||||
### Start with default settings
|
||||
|
||||
```bash
|
||||
ccr start
|
||||
```
|
||||
|
||||
### Start on custom port
|
||||
|
||||
```bash
|
||||
ccr start --port 3000
|
||||
```
|
||||
|
||||
### Start with custom config
|
||||
|
||||
```bash
|
||||
ccr start --config /path/to/config.json
|
||||
```
|
||||
|
||||
### Start as daemon
|
||||
|
||||
```bash
|
||||
ccr start --daemon
|
||||
```
|
||||
|
||||
### Start with debug logging
|
||||
|
||||
```bash
|
||||
ccr start --log-level debug
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
You can also configure the server using environment variables:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `PORT` | Port to listen on |
|
||||
| `CONFIG_PATH` | Path to configuration file |
|
||||
| `LOG_LEVEL` | Logging level |
|
||||
| `CUSTOM_ROUTER_PATH` | Path to custom router function |
|
||||
| `HOST` | Host to bind to (default: 0.0.0.0) |
|
||||
|
||||
## Output
|
||||
|
||||
When started successfully, you'll see:
|
||||
|
||||
```
|
||||
Claude Code Router is running on http://localhost:8080
|
||||
API endpoint: http://localhost:8080/v1
|
||||
```
|
||||
|
||||
## Related Commands
|
||||
|
||||
- [ccr stop](/docs/cli/other-commands#ccr-stop) - Stop the server
|
||||
- [ccr restart](/docs/cli/other-commands#ccr-restart) - Restart the server
|
||||
- [ccr status](/docs/cli/other-commands#ccr-status) - Check server status
|
||||
@@ -1,62 +0,0 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# ccr status
|
||||
|
||||
Show the current status of the Claude Code Router server.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
ccr status
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
### Running Server
|
||||
|
||||
When the server is running:
|
||||
|
||||
```
|
||||
Claude Code Router Status: Running
|
||||
Version: 2.0.0
|
||||
PID: 12345
|
||||
Port: 8080
|
||||
Uptime: 2h 34m
|
||||
Configuration: /home/user/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
### Stopped Server
|
||||
|
||||
When the server is not running:
|
||||
|
||||
```
|
||||
Claude Code Router Status: Stopped
|
||||
```
|
||||
|
||||
## Exit Codes
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 0 | Server is running |
|
||||
| 1 | Server is stopped |
|
||||
| 2 | Error checking status |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
$ ccr status
|
||||
|
||||
Claude Code Router Status: Running
|
||||
Version: 2.0.0
|
||||
PID: 12345
|
||||
Port: 8080
|
||||
Uptime: 2h 34m
|
||||
```
|
||||
|
||||
## Related Commands
|
||||
|
||||
- [ccr start](/docs/cli/start) - Start the server
|
||||
- [ccr stop](/docs/cli/other-commands#ccr-stop) - Stop the server
|
||||
- [ccr restart](/docs/cli/other-commands#ccr-restart) - Restart the server
|
||||
@@ -1,400 +0,0 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# ccr statusline
|
||||
|
||||
Display a customizable status bar showing real-time information about your Claude Code session, including workspace, Git branch, model, token usage, and more.
|
||||
|
||||
## Overview
|
||||
|
||||
The `ccr statusline` command reads JSON data from stdin and renders a beautifully formatted status bar in your terminal. It's designed to integrate with Claude Code's hook system to display real-time session information.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
ccr statusline
|
||||
```
|
||||
|
||||
The command expects JSON data via stdin, typically piped from a Claude Code hook:
|
||||
|
||||
```bash
|
||||
echo '{"hook_event_name":"...","session_id":"...","..."}' | ccr statusline
|
||||
```
|
||||
|
||||
### Hook Integration
|
||||
|
||||
Configure in your Claude Code settings:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"postResponse": {
|
||||
"command": "ccr statusline",
|
||||
"input": "json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Available Themes
|
||||
|
||||
### Default Theme
|
||||
|
||||
A clean, minimal theme with Nerd Font icons and colored text:
|
||||
|
||||
```
|
||||
my-project main claude-3-5-sonnet-20241022 ↑ 12.3k ↓ 5.2k
|
||||
```
|
||||
|
||||
### Powerline Theme
|
||||
|
||||
A vim-powerline inspired style with colored backgrounds and arrow separators:
|
||||
|
||||
```
|
||||
my-project main claude-3-5-sonnet-20241022 ↑ 12.3k ↓ 5.2k
|
||||
```
|
||||
|
||||
Activate by setting `currentStyle: "powerline"` in your config.
|
||||
|
||||
### Simple Theme
|
||||
|
||||
Fallback theme without icons for terminals that don't support Nerd Fonts:
|
||||
|
||||
```
|
||||
my-project main claude-3-5-sonnet-20241022 ↑ 12.3k ↓ 5.2k
|
||||
```
|
||||
|
||||
Automatically used when `USE_SIMPLE_ICONS=true` or on unsupported terminals.
|
||||
|
||||
## Available Modules
|
||||
|
||||
Status line modules display different types of information:
|
||||
|
||||
| Module | Description | Variables |
|
||||
|--------|-------------|-----------|
|
||||
| **workDir** | Current working directory name | `{{workDirName}}` |
|
||||
| **gitBranch** | Current Git branch | `{{gitBranch}}` |
|
||||
| **model** | Model being used | `{{model}}` |
|
||||
| **usage** | Token usage (input/output) | `{{inputTokens}}`, `{{outputTokens}}` |
|
||||
| **context** | Context window usage | `{{contextPercent}}`, `{{contextWindowSize}}` |
|
||||
| **speed** | Token processing speed | `{{tokenSpeed}}`, `{{isStreaming}}` |
|
||||
| **cost** | API cost | `{{cost}}` |
|
||||
| **duration** | Session duration | `{{duration}}` |
|
||||
| **lines** | Code changes | `{{linesAdded}}`, `{{linesRemoved}}` |
|
||||
| **script** | Custom script output | Dynamic |
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure statusline in `~/.claude-code-router/config.json`:
|
||||
|
||||
### Default Style Example
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"currentStyle": "default",
|
||||
"default": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "workDir",
|
||||
"icon": "",
|
||||
"text": "{{workDirName}}",
|
||||
"color": "bright_blue"
|
||||
},
|
||||
{
|
||||
"type": "gitBranch",
|
||||
"icon": "",
|
||||
"text": "{{gitBranch}}",
|
||||
"color": "bright_magenta"
|
||||
},
|
||||
{
|
||||
"type": "model",
|
||||
"icon": "",
|
||||
"text": "{{model}}",
|
||||
"color": "bright_cyan"
|
||||
},
|
||||
{
|
||||
"type": "usage",
|
||||
"icon": "↑",
|
||||
"text": "{{inputTokens}}",
|
||||
"color": "bright_green"
|
||||
},
|
||||
{
|
||||
"type": "usage",
|
||||
"icon": "↓",
|
||||
"text": "{{outputTokens}}",
|
||||
"color": "bright_yellow"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Powerline Style Example
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"currentStyle": "powerline",
|
||||
"powerline": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "workDir",
|
||||
"icon": "",
|
||||
"text": "{{workDirName}}",
|
||||
"color": "white",
|
||||
"background": "bg_bright_blue"
|
||||
},
|
||||
{
|
||||
"type": "gitBranch",
|
||||
"icon": "",
|
||||
"text": "{{gitBranch}}",
|
||||
"color": "white",
|
||||
"background": "bg_bright_magenta"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Full Featured Example
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"currentStyle": "default",
|
||||
"default": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "workDir",
|
||||
"icon": "",
|
||||
"text": "{{workDirName}}",
|
||||
"color": "bright_blue"
|
||||
},
|
||||
{
|
||||
"type": "gitBranch",
|
||||
"icon": "",
|
||||
"text": "{{gitBranch}}",
|
||||
"color": "bright_magenta"
|
||||
},
|
||||
{
|
||||
"type": "model",
|
||||
"icon": "",
|
||||
"text": "{{model}}",
|
||||
"color": "bright_cyan"
|
||||
},
|
||||
{
|
||||
"type": "context",
|
||||
"icon": "🪟",
|
||||
"text": "{{contextPercent}}% / {{contextWindowSize}}",
|
||||
"color": "bright_green"
|
||||
},
|
||||
{
|
||||
"type": "speed",
|
||||
"icon": "⚡",
|
||||
"text": "{{tokenSpeed}} t/s {{isStreaming}}",
|
||||
"color": "bright_yellow"
|
||||
},
|
||||
{
|
||||
"type": "cost",
|
||||
"icon": "💰",
|
||||
"text": "{{cost}}",
|
||||
"color": "bright_magenta"
|
||||
},
|
||||
{
|
||||
"type": "duration",
|
||||
"icon": "⏱️",
|
||||
"text": "{{duration}}",
|
||||
"color": "bright_white"
|
||||
},
|
||||
{
|
||||
"type": "lines",
|
||||
"icon": "📝",
|
||||
"text": "+{{linesAdded}}/-{{linesRemoved}}",
|
||||
"color": "bright_cyan"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Scripts
|
||||
|
||||
You can create custom modules by executing scripts:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "script",
|
||||
"icon": "🔧",
|
||||
"scriptPath": "/path/to/script.js",
|
||||
"options": {
|
||||
"customOption": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Script format (CommonJS):
|
||||
|
||||
```javascript
|
||||
// my-status-module.js
|
||||
module.exports = function(variables, options) {
|
||||
// Access variables like model, gitBranch, etc.
|
||||
// Access options from configuration
|
||||
return `Custom: ${variables.model}`;
|
||||
};
|
||||
|
||||
// Or async
|
||||
module.exports = async function(variables, options) {
|
||||
const data = await fetchSomeData();
|
||||
return data;
|
||||
};
|
||||
```
|
||||
|
||||
## Color Options
|
||||
|
||||
### Standard Colors
|
||||
|
||||
- `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`
|
||||
- `bright_black`, `bright_red`, `bright_green`, `bright_yellow`, `bright_blue`, `bright_magenta`, `bright_cyan`, `bright_white`
|
||||
|
||||
### Background Colors
|
||||
|
||||
Prefix with `bg_`: `bg_blue`, `bg_bright_red`, etc.
|
||||
|
||||
### Hexadecimal Colors
|
||||
|
||||
Use 24-bit TrueColor with hex codes:
|
||||
|
||||
```json
|
||||
{
|
||||
"color": "#FF5733",
|
||||
"background": "bg_#1E90FF"
|
||||
}
|
||||
```
|
||||
|
||||
## Available Variables
|
||||
|
||||
All variables are accessible in module text using `{{variableName}}`:
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{workDirName}}` | Current directory name | `my-project` |
|
||||
| `{{gitBranch}}` | Git branch name | `main` |
|
||||
| `{{model}}` | Model name | `claude-3-5-sonnet-20241022` |
|
||||
| `{{inputTokens}}` | Input tokens (formatted) | `12.3k` |
|
||||
| `{{outputTokens}}` | Output tokens (formatted) | `5.2k` |
|
||||
| `{{tokenSpeed}}` | Tokens per second | `45` |
|
||||
| `{{isStreaming}}` | Streaming status | `streaming` or empty |
|
||||
| `{{contextPercent}}` | Context usage percentage | `45` |
|
||||
| `{{contextWindowSize}}` | Total context window | `200k` |
|
||||
| `{{cost}}` | Total cost | `$0.15` |
|
||||
| `{{duration}}` | Session duration | `2m34s` |
|
||||
| `{{linesAdded}}` | Lines added | `150` |
|
||||
| `{{linesRemoved}}` | Lines removed | `25` |
|
||||
| `{{sessionId}}` | Session ID (first 8 chars) | `a1b2c3d4` |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Control behavior with environment variables:
|
||||
|
||||
| Variable | Values | Description |
|
||||
|----------|--------|-------------|
|
||||
| `USE_SIMPLE_ICONS` | `true`/`false` | Force simple theme without icons |
|
||||
| `NERD_FONT` | Any value | Auto-detect Nerd Font support |
|
||||
|
||||
## Examples
|
||||
|
||||
### Minimal Status Line
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"default": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "model",
|
||||
"text": "{{model}}"
|
||||
},
|
||||
{
|
||||
"type": "usage",
|
||||
"text": "↑{{inputTokens}} ↓{{outputTokens}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output: `claude-3-5-sonnet-20241022 ↑12.3k ↓5.2k`
|
||||
|
||||
### Developer Productivity Focus
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"default": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "gitBranch",
|
||||
"icon": "",
|
||||
"text": "{{gitBranch}}",
|
||||
"color": "bright_magenta"
|
||||
},
|
||||
{
|
||||
"type": "lines",
|
||||
"icon": "📝",
|
||||
"text": "+{{linesAdded}}/-{{linesRemoved}}",
|
||||
"color": "bright_cyan"
|
||||
},
|
||||
{
|
||||
"type": "duration",
|
||||
"icon": "⏱️",
|
||||
"text": "{{duration}}",
|
||||
"color": "bright_white"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output: ` feature/auth 📝 +150/-25 ⏱️ 2m34s`
|
||||
|
||||
## Preset Integration
|
||||
|
||||
Statusline themes can be included in presets. When you install a preset with statusline configuration, it will automatically apply when you activate that preset.
|
||||
|
||||
See [Presets](/docs/presets/intro) for more information.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Icons Not Displaying
|
||||
|
||||
Set `USE_SIMPLE_ICONS=true` in your environment:
|
||||
|
||||
```bash
|
||||
export USE_SIMPLE_ICONS=true
|
||||
```
|
||||
|
||||
### Colors Not Working
|
||||
|
||||
Ensure your terminal supports TrueColor (24-bit color):
|
||||
|
||||
```bash
|
||||
export COLORTERM=truecolor
|
||||
```
|
||||
|
||||
### Git Branch Not Showing
|
||||
|
||||
Ensure you're in a Git repository and have the `git` command installed.
|
||||
|
||||
## Related Commands
|
||||
|
||||
- [ccr status](/docs/cli/commands/status) - Check server status
|
||||
- [ccr preset](/docs/cli/commands/preset) - Manage presets with statusline themes
|
||||
@@ -1,221 +0,0 @@
|
||||
---
|
||||
title: Basic Configuration
|
||||
---
|
||||
|
||||
# Basic Configuration
|
||||
|
||||
CLI uses the same configuration file as Server: `~/.claude-code-router/config.json`
|
||||
|
||||
## Configuration Methods
|
||||
|
||||
You can configure Claude Code Router in two ways:
|
||||
|
||||
### Option 1: Edit Configuration File Directly
|
||||
|
||||
Edit `~/.claude-code-router/config.json` with your favorite editor:
|
||||
|
||||
```bash
|
||||
nano ~/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
### Option 2: Use Web UI
|
||||
|
||||
Open the web interface and configure visually:
|
||||
|
||||
```bash
|
||||
ccr ui
|
||||
```
|
||||
|
||||
## Restart After Configuration Changes
|
||||
|
||||
After modifying the configuration file or making changes through the Web UI, you must restart the service:
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
Or restart directly through the Web UI.
|
||||
|
||||
## Configuration File Location
|
||||
|
||||
```bash
|
||||
~/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
## Minimal Configuration Example
|
||||
|
||||
```json5
|
||||
{
|
||||
// API key (optional, used to protect service)
|
||||
"APIKEY": "your-api-key-here",
|
||||
|
||||
// LLM providers
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
|
||||
// Default routing
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Configuration supports environment variable interpolation:
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"apiKey": "$OPENAI_API_KEY" // Read from environment variable
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Set in `.bashrc` or `.zshrc`:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
```
|
||||
|
||||
## Common Configuration Options
|
||||
|
||||
### HOST and PORT
|
||||
|
||||
```json5
|
||||
{
|
||||
"HOST": "127.0.0.1", // Listen address
|
||||
"PORT": 3456 // Listen port
|
||||
}
|
||||
```
|
||||
|
||||
### Logging Configuration
|
||||
|
||||
```json5
|
||||
{
|
||||
"LOG": true, // Enable logging
|
||||
"LOG_LEVEL": "info" // Log level
|
||||
}
|
||||
```
|
||||
|
||||
### Routing Configuration
|
||||
|
||||
```json5
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-4",
|
||||
"background": "openai,gpt-3.5-turbo",
|
||||
"think": "openai,gpt-4",
|
||||
"longContext": "anthropic,claude-3-opus"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Validation
|
||||
|
||||
Configuration file is automatically validated. Common errors:
|
||||
|
||||
- **Missing Providers**: Must configure at least one provider
|
||||
- **Missing API Key**: If Providers are configured, must provide API Key
|
||||
- **Model doesn't exist**: Ensure model is in provider's models list
|
||||
|
||||
## Configuration Backup
|
||||
|
||||
Configuration is automatically backed up on each update:
|
||||
|
||||
```
|
||||
~/.claude-code-router/config.backup.{timestamp}.json
|
||||
```
|
||||
|
||||
## Apply Configuration Changes
|
||||
|
||||
After modifying the configuration file or making changes through the Web UI, restart the service:
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
Or restart directly through the Web UI by clicking the "Save and Restart" button.
|
||||
|
||||
## View Current Configuration
|
||||
|
||||
```bash
|
||||
# View via API
|
||||
curl http://localhost:3456/api/config
|
||||
|
||||
# Or view configuration file
|
||||
cat ~/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
## Example Configurations
|
||||
|
||||
### OpenAI
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Anthropic
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"baseUrl": "https://api.anthropic.com/v1",
|
||||
"apiKey": "$ANTHROPIC_API_KEY",
|
||||
"models": ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-5-sonnet-20241022"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Providers
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
},
|
||||
{
|
||||
"name": "anthropic",
|
||||
"baseUrl": "https://api.anthropic.com/v1",
|
||||
"apiKey": "$ANTHROPIC_API_KEY",
|
||||
"models": ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4",
|
||||
"think": "anthropic,claude-3-5-sonnet-20241022",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,217 +0,0 @@
|
||||
---
|
||||
title: Project-Level Configuration
|
||||
---
|
||||
|
||||
# Project-Level Configuration
|
||||
|
||||
In addition to global configuration, `ccr` also supports setting different routing rules for specific projects.
|
||||
|
||||
## Project Configuration File
|
||||
|
||||
Project configuration file is located at:
|
||||
|
||||
```
|
||||
~/.claude/projects/<project-id>/claude-code-router.json
|
||||
```
|
||||
|
||||
Where `<project-id>` is the unique identifier of the Claude Code project.
|
||||
|
||||
## Project Configuration Structure
|
||||
|
||||
```json5
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-4",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Finding Project ID
|
||||
|
||||
### Method 1: Using CLI
|
||||
|
||||
```bash
|
||||
# Run in project directory
|
||||
ccr status
|
||||
```
|
||||
|
||||
Output will show current project ID:
|
||||
|
||||
```
|
||||
Project: my-project (abc123def456)
|
||||
```
|
||||
|
||||
### Method 2: Check Claude Code Configuration
|
||||
|
||||
```bash
|
||||
cat ~/.claude.json
|
||||
```
|
||||
|
||||
Find your project ID:
|
||||
|
||||
```json
|
||||
{
|
||||
"projects": {
|
||||
"abc123def456": {
|
||||
"path": "/path/to/your/project",
|
||||
"name": "my-project"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Project Configuration
|
||||
|
||||
### Manual Creation
|
||||
|
||||
```bash
|
||||
# Create project configuration directory
|
||||
mkdir -p ~/.claude/projects/abc123def456
|
||||
|
||||
# Create configuration file
|
||||
cat > ~/.claude/projects/abc123def456/claude-code-router.json << 'EOF'
|
||||
{
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-5-sonnet-20241022",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Using ccr model Command
|
||||
|
||||
```bash
|
||||
# Run in project directory
|
||||
cd /path/to/your/project
|
||||
ccr model --project
|
||||
```
|
||||
|
||||
## Configuration Priority
|
||||
|
||||
Routing configuration priority (from high to low):
|
||||
|
||||
1. **Custom routing function** (`CUSTOM_ROUTER_PATH`)
|
||||
2. **Project-level configuration** (`~/.claude/projects/<id>/claude-code-router.json`)
|
||||
3. **Global configuration** (`~/.claude-code-router/config.json`)
|
||||
4. **Built-in routing rules**
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Scenario 1: Different Projects Use Different Models
|
||||
|
||||
```json5
|
||||
// Web project uses GPT-4
|
||||
~/.claude/projects/web-project-id/claude-code-router.json:
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
|
||||
// AI project uses Claude
|
||||
~/.claude/projects/ai-project-id/claude-code-router.json:
|
||||
{
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-5-sonnet-20241022"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scenario 2: Test Projects Use Low-Cost Models
|
||||
|
||||
```json5
|
||||
~/.claude/projects/test-project-id/claude-code-router.json:
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-3.5-turbo",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scenario 3: Long Context Projects
|
||||
|
||||
```json5
|
||||
~/.claude/projects/long-context-project-id/claude-code-router.json:
|
||||
{
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-opus-20240229",
|
||||
"longContext": "anthropic,claude-3-opus-20240229"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verify Project Configuration
|
||||
|
||||
```bash
|
||||
# View routing used by current project
|
||||
ccr status
|
||||
|
||||
# Check logs to confirm routing decisions
|
||||
tail -f ~/.claude-code-router/claude-code-router.log
|
||||
```
|
||||
|
||||
## Delete Project Configuration
|
||||
|
||||
```bash
|
||||
rm ~/.claude/projects/<project-id>/claude-code-router.json
|
||||
```
|
||||
|
||||
After deletion, falls back to global configuration.
|
||||
|
||||
## Complete Example
|
||||
|
||||
Assume you have two projects:
|
||||
|
||||
### Global Configuration (`~/.claude-code-router/config.json`)
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
},
|
||||
{
|
||||
"name": "anthropic",
|
||||
"baseUrl": "https://api.anthropic.com/v1",
|
||||
"apiKey": "$ANTHROPIC_API_KEY",
|
||||
"models": ["claude-3-5-sonnet-20241022"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Web Project Configuration
|
||||
|
||||
```json5
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AI Project Configuration
|
||||
|
||||
```json5
|
||||
{
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-5-sonnet-20241022",
|
||||
"think": "anthropic,claude-3-5-sonnet-20241022"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way:
|
||||
- Web project uses GPT-4
|
||||
- AI project uses Claude
|
||||
- All projects' background tasks use GPT-3.5-turbo (inherit global configuration)
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
Install Claude Code Router globally using your preferred package manager.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js**: >= 18.0.0
|
||||
- **pnpm**: >= 8.0.0 (if using pnpm)
|
||||
- An API key from your preferred LLM provider
|
||||
|
||||
## Install via npm
|
||||
|
||||
```bash
|
||||
npm install -g @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
## Install via pnpm
|
||||
|
||||
```bash
|
||||
pnpm add -g @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
## Install via Yarn
|
||||
|
||||
```bash
|
||||
yarn global add @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
## Verify Installation
|
||||
|
||||
After installation, verify that `ccr` is available:
|
||||
|
||||
```bash
|
||||
ccr --version
|
||||
```
|
||||
|
||||
You should see the version number displayed.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once installed, proceed to [Quick Start](/docs/quick-start) to configure and start using the router.
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
title: CLI Introduction
|
||||
---
|
||||
|
||||
# CLI Introduction
|
||||
|
||||
Claude Code Router CLI (`ccr`) is a command-line tool for managing and controlling the Claude Code Router service.
|
||||
|
||||
## Feature Overview
|
||||
|
||||
`ccr` provides the following functionality:
|
||||
|
||||
- **Service Management**: Start, stop, restart service
|
||||
- **Configuration Management**: Interactive model selection configuration
|
||||
- **Status Viewing**: View service running status
|
||||
- **Code Execution**: Directly execute `claude` command
|
||||
- **Environment Integration**: Output environment variables for shell integration
|
||||
- **Web UI**: Open Web management interface
|
||||
- **Status Bar**: Display customizable session status with `ccr statusline`
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install -g @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Configuration
|
||||
|
||||
Before using Claude Code Router, you need to configure your providers. You can either:
|
||||
|
||||
1. **Edit configuration file directly**: Edit `~/.claude-code-router/config.json` manually
|
||||
2. **Use Web UI**: Run `ccr ui` to open the web interface and configure visually
|
||||
|
||||
After making configuration changes, restart the service:
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
Or restart directly through the Web UI.
|
||||
|
||||
### Start Claude Code
|
||||
|
||||
Once configured, you can start Claude Code with:
|
||||
|
||||
```bash
|
||||
ccr code
|
||||
```
|
||||
|
||||
This will launch Claude Code and route your requests through the configured provider.
|
||||
|
||||
### Service Management
|
||||
|
||||
```bash
|
||||
ccr start # Start the router service
|
||||
ccr status # View service status
|
||||
ccr stop # Stop the router service
|
||||
ccr restart # Restart the router service
|
||||
```
|
||||
|
||||
### Web UI
|
||||
|
||||
```bash
|
||||
ccr ui # Open Web management interface
|
||||
```
|
||||
|
||||
## Configuration File
|
||||
|
||||
`ccr` uses the configuration file at `~/.claude-code-router/config.json`
|
||||
|
||||
Configure once, and both CLI and Server will use it.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Installation Guide](/docs/cli/installation) - Detailed installation instructions
|
||||
- [Quick Start](/docs/cli/quick-start) - Get started in 5 minutes
|
||||
- [Command Reference](/docs/category/cli-commands) - Complete command list
|
||||
- [Status Line](/docs/cli/commands/statusline) - Customize your status bar
|
||||
- [Configuration Guide](/docs/category/cli-config) - Configuration file details
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Quick Start
|
||||
|
||||
Get up and running with Claude Code Router in 5 minutes.
|
||||
|
||||
## 1. Configure the Router
|
||||
|
||||
Before using Claude Code Router, you need to configure your LLM providers. You can either:
|
||||
|
||||
### Option A: Edit Configuration File Directly
|
||||
|
||||
Edit `~/.claude-code-router/config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": 8080,
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"api_base_url": "https://api.openai.com/v1/chat/completions",
|
||||
"api_key": "your-api-key-here",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option B: Use Web UI
|
||||
|
||||
```bash
|
||||
ccr ui
|
||||
```
|
||||
|
||||
This will open the web interface where you can configure providers visually.
|
||||
|
||||
## 2. Start the Router
|
||||
|
||||
```bash
|
||||
ccr start
|
||||
```
|
||||
|
||||
The router will start on `http://localhost:8080` by default.
|
||||
|
||||
## 3. Use Claude Code
|
||||
|
||||
Now you can use Claude Code normally:
|
||||
|
||||
```bash
|
||||
ccr code
|
||||
```
|
||||
|
||||
Your requests will be routed through Claude Code Router to your configured provider.
|
||||
|
||||
## Restart After Configuration Changes
|
||||
|
||||
If you modify the configuration file or make changes through the Web UI, restart the service:
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
Or restart directly through the Web UI.
|
||||
|
||||
## What's Next?
|
||||
|
||||
- [Basic Configuration](/docs/cli/config/basic) - Learn about configuration options
|
||||
- [Routing](/docs/cli/config/routing) - Configure smart routing rules
|
||||
- [CLI Commands](/docs/category/cli-commands) - Explore all CLI commands
|
||||
@@ -1,647 +0,0 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Presets
|
||||
|
||||
Use predefined configurations for quick setup.
|
||||
|
||||
## What are Presets?
|
||||
|
||||
Presets are pre-configured settings that include provider configurations, routing rules, and transformers optimized for specific use cases.
|
||||
|
||||
## Using Presets
|
||||
|
||||
### CLI Mode (Command Line)
|
||||
|
||||
CLI mode is suitable for developers who prefer command-line operations.
|
||||
|
||||
#### Installing Presets
|
||||
|
||||
**Install from local directory:**
|
||||
|
||||
```bash
|
||||
ccr preset install /path/to/preset-directory
|
||||
```
|
||||
|
||||
**Reconfigure an installed preset:**
|
||||
|
||||
```bash
|
||||
ccr preset install my-preset
|
||||
```
|
||||
|
||||
#### Using Presets
|
||||
|
||||
After installing a preset, you can use the preset name to start Claude Code:
|
||||
|
||||
```bash
|
||||
# Start with a specific preset
|
||||
ccr my-preset "your prompt"
|
||||
```
|
||||
|
||||
The preset will:
|
||||
- Automatically load pre-configured Providers
|
||||
- Apply preset routing rules
|
||||
- Use transformers configured in the preset
|
||||
|
||||
#### List All Presets
|
||||
|
||||
```bash
|
||||
ccr preset list
|
||||
```
|
||||
|
||||
This will display all installed presets with their names, versions, and descriptions.
|
||||
|
||||
#### View Preset Information
|
||||
|
||||
```bash
|
||||
ccr preset info my-preset
|
||||
```
|
||||
|
||||
#### Delete Preset
|
||||
|
||||
```bash
|
||||
ccr preset delete my-preset
|
||||
```
|
||||
|
||||
### Web UI Mode
|
||||
|
||||
Web UI provides a more friendly visual interface with additional installation methods.
|
||||
|
||||
#### Access Web UI
|
||||
|
||||
```bash
|
||||
ccr ui
|
||||
```
|
||||
|
||||
Then open `http://localhost:3000` in your browser.
|
||||
|
||||
#### Install from GitHub Repository
|
||||
|
||||
1. Click the "Preset Market" button
|
||||
2. Select the preset you want to install from the list
|
||||
3. Click the "Install" button
|
||||
|
||||
#### Reconfigure Preset
|
||||
|
||||
1. Click the "View Details" icon next to the preset
|
||||
2. Modify configuration items in the detail page
|
||||
3. Click "Apply" to save configuration
|
||||
|
||||
#### Manage Presets
|
||||
|
||||
- **View**: Click the info icon on the right side of the preset
|
||||
- **Delete**: Click the delete icon on the right side of the preset
|
||||
|
||||
## Creating Custom Presets
|
||||
|
||||
### Preset Directory Structure
|
||||
|
||||
Presets are stored as directories with the following structure:
|
||||
|
||||
```
|
||||
~/.claude-code-router/presets/<preset-name>/
|
||||
├── manifest.json # Required: Preset configuration file
|
||||
├── transformers/ # Optional: Custom transformers
|
||||
│ └── custom-transformer.js
|
||||
├── scripts/ # Optional: Custom scripts
|
||||
│ └── status.js
|
||||
└── README.md # Optional: Documentation
|
||||
```
|
||||
|
||||
### Dynamic Configuration System
|
||||
|
||||
CCR introduces a powerful dynamic configuration system that supports:
|
||||
|
||||
- **Multiple Input Types**: Selectors, multi-select, confirm boxes, text input, number input, etc.
|
||||
- **Conditional Logic**: Dynamically show/hide configuration fields based on user input
|
||||
- **Variable References**: Configuration fields can reference each other
|
||||
- **Dynamic Options**: Option lists can be dynamically generated from preset configuration or user input
|
||||
|
||||
#### Schema Field Types
|
||||
|
||||
| Type | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| `password` | Password input (hidden) | API Key |
|
||||
| `input` | Single-line text input | Base URL |
|
||||
| `number` | Number input | Max tokens |
|
||||
| `select` | Single-select dropdown | Choose Provider |
|
||||
| `multiselect` | Multi-select | Enable features |
|
||||
| `confirm` | Confirmation box | Use proxy |
|
||||
| `editor` | Multi-line text editor | Custom config |
|
||||
|
||||
#### Condition Operators
|
||||
|
||||
| Operator | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `eq` | Equals | `{"field": "provider", "operator": "eq", "value": "openai"}` |
|
||||
| `ne` | Not equals | `{"field": "advanced", "operator": "ne", "value": true}` |
|
||||
| `in` | In (array) | `{"field": "feature", "operator": "in", "value": ["a", "b"]}` |
|
||||
| `nin` | Not in (array) | `{"field": "type", "operator": "nin", "value": ["x", "y"]}` |
|
||||
| `exists` | Field exists | `{"field": "apiKey", "operator": "exists"}` |
|
||||
| `gt/lt/gte/lte` | Greater/less than (or equal) | For number comparisons |
|
||||
|
||||
#### Dynamic Options Types
|
||||
|
||||
##### static - Static Options
|
||||
```json
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{"label": "Option 1", "value": "value1"},
|
||||
{"label": "Option 2", "value": "value2"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### providers - Extract from Providers Configuration
|
||||
```json
|
||||
"options": {
|
||||
"type": "providers"
|
||||
}
|
||||
```
|
||||
Automatically extracts names from the `Providers` array as options.
|
||||
|
||||
##### models - Extract from Specified Provider's Models
|
||||
```json
|
||||
"options": {
|
||||
"type": "models",
|
||||
"providerField": "{{selectedProvider}}"
|
||||
}
|
||||
```
|
||||
Dynamically displays models based on the user-selected provider.
|
||||
|
||||
#### Template Variables
|
||||
|
||||
Use `{{variableName}}` syntax to reference user input in the template:
|
||||
|
||||
```json
|
||||
"template": {
|
||||
"Providers": [
|
||||
{
|
||||
"name": "{{providerName}}",
|
||||
"api_key": "{{apiKey}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Configuration Mappings
|
||||
|
||||
For complex configuration needs, use `configMappings` to precisely control value placement:
|
||||
|
||||
```json
|
||||
"configMappings": [
|
||||
{
|
||||
"target": "Providers[0].api_key",
|
||||
"value": "{{apiKey}}"
|
||||
},
|
||||
{
|
||||
"target": "PROXY_URL",
|
||||
"value": "{{proxyUrl}}",
|
||||
"when": {
|
||||
"field": "useProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Complete Example
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "multi-provider-example",
|
||||
"version": "1.0.0",
|
||||
"description": "Multi-provider configuration example - Switch between OpenAI and DeepSeek",
|
||||
"author": "CCR Team",
|
||||
"keywords": ["openai", "deepseek", "multi-provider"],
|
||||
"ccrVersion": "2.0.0",
|
||||
"schema": [
|
||||
{
|
||||
"id": "primaryProvider",
|
||||
"type": "select",
|
||||
"label": "Primary Provider",
|
||||
"prompt": "Select your primary LLM provider",
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{
|
||||
"label": "OpenAI",
|
||||
"value": "openai",
|
||||
"description": "Use OpenAI's GPT models"
|
||||
},
|
||||
{
|
||||
"label": "DeepSeek",
|
||||
"value": "deepseek",
|
||||
"description": "Use DeepSeek's cost-effective models"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": true,
|
||||
"defaultValue": "openai"
|
||||
},
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "API Key",
|
||||
"prompt": "Enter your API Key",
|
||||
"placeholder": "sk-...",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "defaultModel",
|
||||
"type": "select",
|
||||
"label": "Default Model",
|
||||
"prompt": "Select the default model to use",
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{"label": "GPT-4o", "value": "gpt-4o"},
|
||||
{"label": "GPT-4o-mini", "value": "gpt-4o-mini"}
|
||||
]
|
||||
},
|
||||
"required": true,
|
||||
"defaultValue": "gpt-4o",
|
||||
"when": {
|
||||
"field": "primaryProvider",
|
||||
"operator": "eq",
|
||||
"value": "openai"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "enableProxy",
|
||||
"type": "confirm",
|
||||
"label": "Enable Proxy",
|
||||
"prompt": "Access API through a proxy?",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "proxyUrl",
|
||||
"type": "input",
|
||||
"label": "Proxy URL",
|
||||
"prompt": "Enter proxy server address",
|
||||
"placeholder": "http://127.0.0.1:7890",
|
||||
"required": true,
|
||||
"when": {
|
||||
"field": "enableProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"template": {
|
||||
"Providers": [
|
||||
{
|
||||
"name": "{{primaryProvider}}",
|
||||
"api_base_url": "https://api.openai.com/v1/chat/completions",
|
||||
"api_key": "{{apiKey}}",
|
||||
"models": ["{{defaultModel}}"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "{{primaryProvider}},{{defaultModel}}"
|
||||
},
|
||||
"PROXY_URL": "{{proxyUrl}}"
|
||||
},
|
||||
"configMappings": [
|
||||
{
|
||||
"target": "PROXY_URL",
|
||||
"value": "{{proxyUrl}}",
|
||||
"when": {
|
||||
"field": "enableProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### manifest.json Complete Field Reference
|
||||
|
||||
`manifest.json` is the core configuration file of a preset, using JSON5 format (comments supported).
|
||||
|
||||
#### 1. Metadata Fields
|
||||
|
||||
These fields describe basic information about the preset:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | ✓ | Preset name (unique identifier) |
|
||||
| `version` | string | ✓ | Version number (follows semver) |
|
||||
| `description` | string | - | Preset description |
|
||||
| `author` | string | - | Author information |
|
||||
| `homepage` | string | - | Project homepage URL |
|
||||
| `repository` | string | - | Source repository URL |
|
||||
| `license` | string | - | License type |
|
||||
| `keywords` | string[] | - | Keyword tags |
|
||||
| `ccrVersion` | string | - | Compatible CCR version |
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-preset",
|
||||
"version": "1.0.0",
|
||||
"description": "My custom preset",
|
||||
"author": "Your Name",
|
||||
"homepage": "https://github.com/yourname/ccr-presets",
|
||||
"repository": "https://github.com/yourname/ccr-presets.git",
|
||||
"license": "MIT",
|
||||
"keywords": ["openai", "production"],
|
||||
"ccrVersion": "2.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Configuration Fields
|
||||
|
||||
These fields are directly merged into CCR's configuration. All fields supported in `config.json` can be used here:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Providers` | array | Provider configuration array |
|
||||
| `Router` | object | Routing configuration |
|
||||
| `transformers` | array | Transformer configuration |
|
||||
| `StatusLine` | object | Status bar configuration |
|
||||
| `NON_INTERACTIVE_MODE` | boolean | Enable non-interactive mode (for CI/CD) |
|
||||
|
||||
**CLI-Only Fields** (these fields only work in CLI mode and are not used by the server):
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `noServer` | boolean | Skip local server startup and use provider's API directly |
|
||||
| `claudeCodeSettings` | object | Claude Code specific settings (env, statusLine, etc.) |
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"api_base_url": "https://api.openai.com/v1/chat/completions",
|
||||
"api_key": "${OPENAI_API_KEY}",
|
||||
"models": ["gpt-4o", "gpt-4o-mini"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4o",
|
||||
"background": "openai,gpt-4o-mini"
|
||||
},
|
||||
"PORT": 8080
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Dynamic Configuration System Fields
|
||||
|
||||
These fields are used to create interactive configuration templates:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `schema` | array | Configuration input form definition |
|
||||
| `template` | object | Configuration template (with variable references) |
|
||||
| `configMappings` | array | Configuration mapping rules |
|
||||
| `userValues` | object | User-filled values (used at runtime) |
|
||||
|
||||
**Schema Field Types:**
|
||||
|
||||
| Type | Description | Use Case |
|
||||
|------|-------------|----------|
|
||||
| `password` | Password input (hidden) | API Key |
|
||||
| `input` | Single-line text input | URL |
|
||||
| `number` | Number input | Port number |
|
||||
| `select` | Single-select dropdown | Select Provider |
|
||||
| `multiselect` | Multi-select | Enable features |
|
||||
| `confirm` | Confirmation box | Enable/disable |
|
||||
| `editor` | Multi-line text editor | Custom config |
|
||||
|
||||
Dynamic configuration example:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": [
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "API Key",
|
||||
"prompt": "Enter your API Key",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "provider",
|
||||
"type": "select",
|
||||
"label": "Provider",
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{"label": "OpenAI", "value": "openai"},
|
||||
{"label": "DeepSeek", "value": "deepseek"}
|
||||
]
|
||||
},
|
||||
"defaultValue": "openai"
|
||||
}
|
||||
],
|
||||
"template": {
|
||||
"Providers": [
|
||||
{
|
||||
"name": "#{provider}",
|
||||
"api_key": "#{apiKey}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Creating Preset Examples
|
||||
|
||||
#### Example 1: Simple Preset (No Dynamic Configuration)
|
||||
|
||||
```bash
|
||||
# Create preset directory
|
||||
mkdir -p ~/.claude-code-router/presets/simple-openai
|
||||
|
||||
# Create manifest.json
|
||||
cat > ~/.claude-code-router/presets/simple-openai/manifest.json << 'EOF'
|
||||
{
|
||||
"name": "simple-openai",
|
||||
"version": "1.0.0",
|
||||
"description": "Simple OpenAI configuration",
|
||||
"author": "Your Name",
|
||||
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"api_base_url": "https://api.openai.com/v1/chat/completions",
|
||||
"api_key": "${OPENAI_API_KEY}",
|
||||
"models": ["gpt-4o", "gpt-4o-mini"]
|
||||
}
|
||||
],
|
||||
|
||||
"Router": {
|
||||
"default": "openai,gpt-4o",
|
||||
"background": "openai,gpt-4o-mini"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Configure preset (input API Key)
|
||||
ccr preset install simple-openai
|
||||
|
||||
# Use preset
|
||||
ccr simple-openai "your prompt"
|
||||
```
|
||||
|
||||
#### Example 2: Advanced Preset (Dynamic Configuration)
|
||||
|
||||
```bash
|
||||
# Create preset directory
|
||||
mkdir -p ~/.claude-code-router/presets/advanced-config
|
||||
|
||||
# Create manifest.json
|
||||
cat > ~/.claude-code-router/presets/advanced-config/manifest.json << 'EOF'
|
||||
{
|
||||
"name": "advanced-config",
|
||||
"version": "1.0.0",
|
||||
"description": "Advanced configuration with multi-provider support",
|
||||
"author": "Your Name",
|
||||
"keywords": ["openai", "deepseek", "multi-provider"],
|
||||
|
||||
"schema": [
|
||||
{
|
||||
"id": "provider",
|
||||
"type": "select",
|
||||
"label": "Select Provider",
|
||||
"prompt": "Choose your primary LLM provider",
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{
|
||||
"label": "OpenAI",
|
||||
"value": "openai",
|
||||
"description": "Use OpenAI's GPT models"
|
||||
},
|
||||
{
|
||||
"label": "DeepSeek",
|
||||
"value": "deepseek",
|
||||
"description": "Use DeepSeek's cost-effective models"
|
||||
}
|
||||
]
|
||||
},
|
||||
"defaultValue": "openai",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "API Key",
|
||||
"prompt": "Enter your API Key",
|
||||
"placeholder": "sk-...",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "enableProxy",
|
||||
"type": "confirm",
|
||||
"label": "Enable Proxy",
|
||||
"prompt": "Access API through a proxy?",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "proxyUrl",
|
||||
"type": "input",
|
||||
"label": "Proxy URL",
|
||||
"prompt": "Enter proxy server address",
|
||||
"placeholder": "http://127.0.0.1:7890",
|
||||
"required": true,
|
||||
"when": {
|
||||
"field": "enableProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"template": {
|
||||
"Providers": [
|
||||
{
|
||||
"name": "#{provider}",
|
||||
"api_base_url": "#{provider === 'openai' ? 'https://api.openai.com/v1/chat/completions' : 'https://api.deepseek.com/v1/chat/completions'}",
|
||||
"api_key": "#{apiKey}",
|
||||
"models": ["gpt-4o", "gpt-4o-mini"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "#{provider},gpt-4o",
|
||||
"background": "#{provider},gpt-4o-mini"
|
||||
}
|
||||
},
|
||||
|
||||
"configMappings": [
|
||||
{
|
||||
"target": "PROXY_URL",
|
||||
"value": "#{proxyUrl}",
|
||||
"when": {
|
||||
"field": "enableProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Configure preset (will prompt for input)
|
||||
ccr preset install advanced-config
|
||||
|
||||
# Use preset
|
||||
ccr advanced-config "your prompt"
|
||||
```
|
||||
|
||||
### Export Current Configuration as Preset
|
||||
|
||||
If you have already configured CCR, you can export the current configuration:
|
||||
|
||||
```bash
|
||||
# Export current configuration
|
||||
ccr preset export my-exported-preset
|
||||
```
|
||||
|
||||
Export will automatically:
|
||||
- Identify sensitive fields (like `api_key`) and replace with environment variable placeholders
|
||||
- Generate `schema` for collecting user input
|
||||
- Generate `template` and `configMappings`
|
||||
|
||||
Optional flags:
|
||||
|
||||
```bash
|
||||
ccr preset export my-exported-preset \
|
||||
--description "Exported configuration" \
|
||||
--author "Your Name" \
|
||||
--tags "production,openai"
|
||||
```
|
||||
|
||||
## Preset File Location
|
||||
|
||||
Presets are stored in:
|
||||
|
||||
```
|
||||
~/.claude-code-router/presets/
|
||||
```
|
||||
|
||||
Each preset is a directory containing a `manifest.json` file.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Dynamic Configuration**: Use the schema system for configuration items that require user input
|
||||
2. **Provide Defaults**: Set reasonable defaults for optional fields
|
||||
3. **Conditional Display**: Use `when` conditions to avoid unnecessary inputs
|
||||
4. **Clear Labels**: Provide clear `label` and `prompt` for each field
|
||||
5. **Validate Input**: Use `validator` to ensure input validity
|
||||
6. **Version Control**: Keep commonly used presets in version control
|
||||
7. **Document**: Add descriptions and version info for custom presets
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [CLI Reference](/docs/cli/start) - Complete CLI command reference
|
||||
- [Configuration](/docs/config/basic) - Detailed configuration guide
|
||||
@@ -1,126 +0,0 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Custom Router
|
||||
|
||||
Write your own routing logic in JavaScript.
|
||||
|
||||
## Creating a Custom Router
|
||||
|
||||
Create a JavaScript file that exports a routing function:
|
||||
|
||||
```javascript
|
||||
// custom-router.js
|
||||
module.exports = function(config, context) {
|
||||
const { scenario, projectId, tokenCount, request } = context;
|
||||
|
||||
// Your custom logic here
|
||||
if (scenario === 'background') {
|
||||
return 'groq,llama-3.3-70b-versatile';
|
||||
}
|
||||
|
||||
if (tokenCount > 100000) {
|
||||
return 'gemini,gemini-1.5-pro';
|
||||
}
|
||||
|
||||
// Check request content
|
||||
if (request && request.system && request.system.includes('code')) {
|
||||
return 'deepseek,deepseek-coder';
|
||||
}
|
||||
|
||||
// Default
|
||||
return 'deepseek,deepseek-chat';
|
||||
};
|
||||
```
|
||||
|
||||
## Context Object
|
||||
|
||||
The router function receives a context object with:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `scenario` | string | Detected scenario (background, think, webSearch, image, etc.) |
|
||||
| `projectId` | string | Project ID from Claude Code |
|
||||
| `tokenCount` | number | Estimated token count of the request |
|
||||
| `request` | object | Full request object |
|
||||
|
||||
## Configuration
|
||||
|
||||
Set the environment variable to use your custom router:
|
||||
|
||||
```bash
|
||||
export CUSTOM_ROUTER_PATH="/path/to/custom-router.js"
|
||||
```
|
||||
|
||||
Or set it in your shell configuration:
|
||||
|
||||
```bash
|
||||
# ~/.bashrc or ~/.zshrc
|
||||
export CUSTOM_ROUTER_PATH="/path/to/custom-router.js"
|
||||
```
|
||||
|
||||
## Return Format
|
||||
|
||||
The router function should return a string in the format:
|
||||
|
||||
```
|
||||
{provider-name},{model-name}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
If your router function throws an error or returns an invalid format, the router will fall back to the default routing configuration.
|
||||
|
||||
## Example: Time-Based Routing
|
||||
|
||||
```javascript
|
||||
module.exports = function(config, context) {
|
||||
const hour = new Date().getHours();
|
||||
|
||||
// Use faster models during work hours
|
||||
if (hour >= 9 && hour <= 18) {
|
||||
return 'groq,llama-3.3-70b-versatile';
|
||||
}
|
||||
|
||||
// Use more capable models outside work hours
|
||||
return 'deepseek,deepseek-chat';
|
||||
};
|
||||
```
|
||||
|
||||
## Example: Cost Optimization
|
||||
|
||||
```javascript
|
||||
module.exports = function(config, context) {
|
||||
const { tokenCount } = context;
|
||||
|
||||
// Use cheaper models for large requests
|
||||
if (tokenCount > 50000) {
|
||||
return 'groq,llama-3.3-70b-versatile';
|
||||
}
|
||||
|
||||
// Use default for smaller requests
|
||||
return 'deepseek,deepseek-chat';
|
||||
};
|
||||
```
|
||||
|
||||
## Testing Your Router
|
||||
|
||||
Test your custom router by checking the logs:
|
||||
|
||||
```bash
|
||||
tail -f ~/.claude-code-router/claude-code-router.log
|
||||
```
|
||||
|
||||
Look for routing decisions to see which model is being selected.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Agents](/docs/advanced/agents) - Extend functionality with agents
|
||||
- [Presets](/docs/advanced/presets) - Use predefined configurations
|
||||
@@ -1,224 +0,0 @@
|
||||
---
|
||||
title: Configuration API
|
||||
---
|
||||
|
||||
# Configuration API
|
||||
|
||||
## GET /api/config
|
||||
|
||||
Get current server configuration.
|
||||
|
||||
### Request Example
|
||||
|
||||
```bash
|
||||
curl http://localhost:3456/api/config \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### Response Example
|
||||
|
||||
```json
|
||||
{
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": 3456,
|
||||
"APIKEY": "sk-xxxxx",
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "sk-...",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
},
|
||||
"transformers": [
|
||||
"anthropic"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## POST /api/config
|
||||
|
||||
Update server configuration. Old configuration is automatically backed up before updating.
|
||||
|
||||
### Request Example
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/api/config \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": 3456,
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Configuration Object Structure
|
||||
|
||||
#### Basic Configuration
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `HOST` | string | No | Listen address (default 127.0.0.1) |
|
||||
| `PORT` | integer | No | Listen port (default 3456) |
|
||||
| `APIKEY` | string | No | API key |
|
||||
| `LOG` | boolean | No | Enable logging (default true) |
|
||||
| `LOG_LEVEL` | string | No | Log level (debug/info/warn/error) |
|
||||
|
||||
#### Providers Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "provider-name",
|
||||
"baseUrl": "https://api.example.com/v1",
|
||||
"apiKey": "your-api-key",
|
||||
"models": ["model-1", "model-2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | string | Yes | Provider name |
|
||||
| `baseUrl` | string | Yes | API base URL |
|
||||
| `apiKey` | string | Yes | API key |
|
||||
| `models` | array | Yes | List of supported models |
|
||||
|
||||
#### Router Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "provider,model",
|
||||
"longContextThreshold": 100000,
|
||||
"routes": {
|
||||
"background": "lightweight-model",
|
||||
"think": "powerful-model",
|
||||
"longContext": "long-context-model",
|
||||
"webSearch": "search-model",
|
||||
"image": "vision-model"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Transformers Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"provider": "provider-name",
|
||||
"models": ["model-1"],
|
||||
"options": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Response Example
|
||||
|
||||
Success:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Config saved successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Backup
|
||||
|
||||
Every time configuration is updated, old configuration is automatically backed up to:
|
||||
|
||||
```
|
||||
~/.claude-code-router/config.backup.{timestamp}.json
|
||||
```
|
||||
|
||||
Keeps the last 3 backups.
|
||||
|
||||
## GET /api/transformers
|
||||
|
||||
Get list of all transformers loaded by the server.
|
||||
|
||||
### Request Example
|
||||
|
||||
```bash
|
||||
curl http://localhost:3456/api/transformers \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### Response Example
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"endpoint": null
|
||||
},
|
||||
{
|
||||
"name": "openai",
|
||||
"endpoint": null
|
||||
},
|
||||
{
|
||||
"name": "gemini",
|
||||
"endpoint": "https://generativelanguage.googleapis.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Transformer List
|
||||
|
||||
Built-in transformers:
|
||||
|
||||
- `anthropic` - Anthropic Claude format
|
||||
- `openai` - OpenAI format
|
||||
- `deepseek` - DeepSeek format
|
||||
- `gemini` - Google Gemini format
|
||||
- `openrouter` - OpenRouter format
|
||||
- `groq` - Groq format
|
||||
- `maxtoken` - Adjust max_tokens parameter
|
||||
- `tooluse` - Tool use conversion
|
||||
- `reasoning` - Reasoning mode conversion
|
||||
- `enhancetool` - Enhance tool functionality
|
||||
|
||||
## Environment Variable Interpolation
|
||||
|
||||
Configuration supports environment variable interpolation:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"apiKey": "$OPENAI_API_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Or use `${VAR_NAME}` format:
|
||||
|
||||
```json
|
||||
{
|
||||
"baseUrl": "${API_BASE_URL}"
|
||||
}
|
||||
```
|
||||
@@ -1,170 +0,0 @@
|
||||
---
|
||||
title: Logs API
|
||||
---
|
||||
|
||||
# Logs API
|
||||
|
||||
## GET /api/logs/files
|
||||
|
||||
Get list of all available log files.
|
||||
|
||||
### Request Example
|
||||
|
||||
```bash
|
||||
curl http://localhost:3456/api/logs/files \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### Response Example
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "ccr-20241226143022.log",
|
||||
"path": "/home/user/.claude-code-router/logs/ccr-20241226143022.log",
|
||||
"size": 1024000,
|
||||
"lastModified": "2024-12-26T14:30:22.000Z"
|
||||
},
|
||||
{
|
||||
"name": "ccr-20241226143021.log",
|
||||
"path": "/home/user/.claude-code-router/logs/ccr-20241226143021.log",
|
||||
"size": 980000,
|
||||
"lastModified": "2024-12-26T14:30:21.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Field Description
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | string | File name |
|
||||
| `path` | string | Complete file path |
|
||||
| `size` | integer | File size (bytes) |
|
||||
| `lastModified` | string | Last modification time (ISO 8601) |
|
||||
|
||||
Files are sorted by modification time in descending order.
|
||||
|
||||
## GET /api/logs
|
||||
|
||||
Get content of specified log file.
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `file` | string | No | Log file path (default uses app.log) |
|
||||
|
||||
### Request Example (Get Default Log)
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3456/api/logs" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### Request Example (Get Specific File)
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3456/api/logs?file=/home/user/.claude-code-router/logs/ccr-20241226143022.log" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### Response Example
|
||||
|
||||
```json
|
||||
[
|
||||
"{\"level\":30,\"time\":1703550622000,\"pid\":12345,\"hostname\":\"server\",\"msg\":\"Incoming request\",\"req\":{\"id\":1,\"method\":\"POST\",\"url\":\"/v1/messages\",\"remoteAddress\":\"127.0.0.1\"}}",
|
||||
"{\"level\":30,\"time\":1703550622500,\"pid\":12345,\"hostname\":\"server\",\"msg\":\"Request completed\",\"res\":{\"statusCode\":200,\"responseTime\":500}}",
|
||||
"..."
|
||||
]
|
||||
```
|
||||
|
||||
Returns an array of log lines, each line is a JSON string.
|
||||
|
||||
### Log Format
|
||||
|
||||
Logs use Pino format:
|
||||
|
||||
```json
|
||||
{
|
||||
"level": 30,
|
||||
"time": 1703550622000,
|
||||
"pid": 12345,
|
||||
"hostname": "server",
|
||||
"msg": "Incoming request",
|
||||
"req": {
|
||||
"id": 1,
|
||||
"method": "POST",
|
||||
"url": "/v1/messages",
|
||||
"remoteAddress": "127.0.0.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Log Levels
|
||||
|
||||
| Level | Value | Description |
|
||||
|-------|-------|-------------|
|
||||
| `trace` | 10 | Most verbose logs |
|
||||
| `debug` | 20 | Debug information |
|
||||
| `info` | 30 | General information |
|
||||
| `warn` | 40 | Warning information |
|
||||
| `error` | 50 | Error information |
|
||||
| `fatal` | 60 | Fatal error |
|
||||
|
||||
## DELETE /api/logs
|
||||
|
||||
Clear content of specified log file.
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `file` | string | No | Log file path (default uses app.log) |
|
||||
|
||||
### Request Example (Clear Default Log)
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:3456/api/logs" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### Request Example (Clear Specific File)
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:3456/api/logs?file=/home/user/.claude-code-router/logs/ccr-20241226143022.log" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### Response Example
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Logs cleared successfully"
|
||||
}
|
||||
```
|
||||
|
||||
## Log Locations
|
||||
|
||||
### Server Logs
|
||||
|
||||
Location: `~/.claude-code-router/logs/`
|
||||
|
||||
File naming: `ccr-{YYYYMMDD}{HH}{MM}{SS}.log`
|
||||
|
||||
Content: HTTP requests, API calls, server events
|
||||
|
||||
### Application Logs
|
||||
|
||||
Location: `~/.claude-code-router/claude-code-router.log`
|
||||
|
||||
Content: Routing decisions, business logic events
|
||||
|
||||
## Log Rotation
|
||||
|
||||
Server logs use rotating-file-stream for automatic rotation:
|
||||
|
||||
- **maxFiles**: 3 - Keep last 3 log files
|
||||
- **interval**: 1d - Rotate daily
|
||||
- **maxSize**: 50M - Maximum 50MB per file
|
||||
@@ -1,224 +0,0 @@
|
||||
---
|
||||
title: Messages API
|
||||
---
|
||||
|
||||
# Messages API
|
||||
|
||||
## POST /v1/messages
|
||||
|
||||
Send messages to LLM, compatible with Anthropic Claude API format.
|
||||
|
||||
### Request Format
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/v1/messages \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"max_tokens": 1024,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello, Claude!"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### Request Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `model` | string | Yes | Model name (will be routed to actual provider) |
|
||||
| `messages` | array | Yes | Array of messages |
|
||||
| `max_tokens` | integer | Yes | Maximum tokens to generate |
|
||||
| `system` | string | No | System prompt |
|
||||
| `tools` | array | No | List of available tools |
|
||||
| `stream` | boolean | No | Whether to use streaming response (default false) |
|
||||
| `temperature` | number | No | Temperature parameter (0-1) |
|
||||
|
||||
### Message Object Format
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "user|assistant",
|
||||
"content": "string | array"
|
||||
}
|
||||
```
|
||||
|
||||
### Response Format (Non-streaming)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "msg_xxx",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Hello! How can I help you today?"
|
||||
}
|
||||
],
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"stop_reason": "end_turn",
|
||||
"usage": {
|
||||
"input_tokens": 10,
|
||||
"output_tokens": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming Response
|
||||
|
||||
Set `stream: true` to enable streaming response:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"max_tokens": 1024,
|
||||
"messages": [...],
|
||||
"stream": true
|
||||
}
|
||||
```
|
||||
|
||||
Streaming response event types:
|
||||
|
||||
- `message_start` - Message start
|
||||
- `content_block_start` - Content block start
|
||||
- `content_block_delta` - Content increment
|
||||
- `content_block_stop` - Content block end
|
||||
- `message_delta` - Message metadata (usage)
|
||||
- `message_stop` - Message end
|
||||
|
||||
### Tool Use
|
||||
|
||||
Supports function calling (Tool Use):
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"max_tokens": 1024,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What's the weather like?"
|
||||
}
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"description": "Get the current weather",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "City name"
|
||||
}
|
||||
},
|
||||
"required": ["location"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Multimodal Support
|
||||
|
||||
Supports image input:
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "image",
|
||||
"source": {
|
||||
"type": "base64",
|
||||
"media_type": "image/png",
|
||||
"data": "iVBORw0KGgo..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Describe this image"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## POST /v1/messages/count_tokens
|
||||
|
||||
Count tokens in messages.
|
||||
|
||||
### Request Format
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/v1/messages/count_tokens \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello!"
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"system": "You are a helpful assistant."
|
||||
}'
|
||||
```
|
||||
|
||||
### Request Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `model` | string | Yes | Model name |
|
||||
| `messages` | array | Yes | Array of messages |
|
||||
| `tools` | array | No | List of tools |
|
||||
| `system` | string | No | System prompt |
|
||||
|
||||
### Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"input_tokens": 42
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
### 400 Bad Request
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"type": "invalid_request_error",
|
||||
"message": "messages is required"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 401 Unauthorized
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"type": "authentication_error",
|
||||
"message": "Invalid API key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 500 Internal Server Error
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"type": "api_error",
|
||||
"message": "Failed to connect to provider"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,92 +0,0 @@
|
||||
---
|
||||
title: API Overview
|
||||
---
|
||||
|
||||
# API Overview
|
||||
|
||||
Claude Code Router Server provides a complete HTTP API with support for:
|
||||
|
||||
- **Messages API**: Message interface compatible with Anthropic Claude API
|
||||
- **Configuration API**: Read and update server configuration
|
||||
- **Logs API**: View and manage service logs
|
||||
- **Tools API**: Calculate token counts
|
||||
|
||||
## Basic Information
|
||||
|
||||
**Base URL**: `http://localhost:3456`
|
||||
|
||||
**Authentication**: API Key (via `x-api-key` header)
|
||||
|
||||
```bash
|
||||
curl -H "x-api-key: your-api-key" http://localhost:3456/api/config
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Messages
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/v1/messages` | POST | Send message (compatible with Anthropic API) |
|
||||
| `/v1/messages/count_tokens` | POST | Count tokens in messages |
|
||||
|
||||
### Configuration Management
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/api/config` | GET | Get current configuration |
|
||||
| `/api/config` | POST | Update configuration |
|
||||
| `/api/transformers` | GET | Get list of available transformers |
|
||||
|
||||
### Log Management
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/api/logs/files` | GET | Get list of log files |
|
||||
| `/api/logs` | GET | Get log content |
|
||||
| `/api/logs` | DELETE | Clear logs |
|
||||
|
||||
### Service Management
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/api/restart` | POST | Restart service |
|
||||
| `/ui` | GET | Web management interface |
|
||||
| `/ui/` | GET | Web management interface (redirect) |
|
||||
|
||||
## Authentication
|
||||
|
||||
### API Key Authentication
|
||||
|
||||
Add API Key in request header:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/v1/messages \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '...'
|
||||
```
|
||||
|
||||
## Streaming Responses
|
||||
|
||||
The Messages API supports streaming responses (Server-Sent Events):
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/v1/messages \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{"stream": true, ...}'
|
||||
```
|
||||
|
||||
Streaming response format:
|
||||
|
||||
```
|
||||
event: message_start
|
||||
data: {"type":"message_start","message":{...}}
|
||||
|
||||
event: content_block_delta
|
||||
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hello"}}
|
||||
|
||||
event: message_stop
|
||||
data: {"type":"message_stop"}
|
||||
```
|
||||
@@ -1,146 +0,0 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Basic Configuration
|
||||
|
||||
Learn how to configure Claude Code Router to suit your needs.
|
||||
|
||||
## Configuration File Location
|
||||
|
||||
The configuration file is located at:
|
||||
|
||||
```
|
||||
~/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
## Configuration Structure
|
||||
|
||||
### Providers
|
||||
|
||||
Configure LLM providers to route requests to:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"NAME": "deepseek",
|
||||
"HOST": "https://api.deepseek.com",
|
||||
"APIKEY": "your-api-key",
|
||||
"MODELS": ["deepseek-chat", "deepseek-coder"]
|
||||
},
|
||||
{
|
||||
"NAME": "groq",
|
||||
"HOST": "https://api.groq.com/openai/v1",
|
||||
"APIKEY": "your-groq-api-key",
|
||||
"MODELS": ["llama-3.3-70b-versatile"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Router
|
||||
|
||||
Configure which model to use by default:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Format: `{provider-name},{model-name}`
|
||||
|
||||
### Transformers
|
||||
|
||||
Apply transformations to requests/responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"providers": ["deepseek", "groq"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Use environment variables in your configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"NAME": "deepseek",
|
||||
"HOST": "https://api.deepseek.com",
|
||||
"APIKEY": "$DEEPSEEK_API_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Both `$VAR_NAME` and `${VAR_NAME}` syntax are supported.
|
||||
|
||||
## Complete Example
|
||||
|
||||
```json
|
||||
{
|
||||
"port": 8080,
|
||||
"Providers": [
|
||||
{
|
||||
"NAME": "deepseek",
|
||||
"HOST": "https://api.deepseek.com",
|
||||
"APIKEY": "$DEEPSEEK_API_KEY",
|
||||
"MODELS": ["deepseek-chat", "deepseek-coder"],
|
||||
"transformers": ["anthropic"]
|
||||
},
|
||||
{
|
||||
"NAME": "groq",
|
||||
"HOST": "https://api.groq.com/openai/v1",
|
||||
"APIKEY": "$GROQ_API_KEY",
|
||||
"MODELS": ["llama-3.3-70b-versatile"],
|
||||
"transformers": ["anthropic"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat",
|
||||
"longContextThreshold": 100000,
|
||||
"background": "groq,llama-3.3-70b-versatile"
|
||||
},
|
||||
"transformers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"providers": ["deepseek", "groq"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Editing Configuration
|
||||
|
||||
Use the CLI to edit the configuration:
|
||||
|
||||
```bash
|
||||
ccr config edit
|
||||
```
|
||||
|
||||
This will open the configuration file in your default editor.
|
||||
|
||||
## Reloading Configuration
|
||||
|
||||
After editing the configuration, restart the router:
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Providers Configuration](/docs/config/providers) - Detailed provider configuration
|
||||
- [Routing Configuration](/docs/config/routing) - Configure routing rules
|
||||
- [Transformers](/docs/config/transformers) - Apply transformations
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Providers Configuration
|
||||
|
||||
Detailed guide for configuring LLM providers.
|
||||
|
||||
## Supported Providers
|
||||
|
||||
### DeepSeek
|
||||
|
||||
```json
|
||||
{
|
||||
"NAME": "deepseek",
|
||||
"HOST": "https://api.deepseek.com",
|
||||
"APIKEY": "your-api-key",
|
||||
"MODELS": ["deepseek-chat", "deepseek-coder"],
|
||||
"transformers": ["anthropic"]
|
||||
}
|
||||
```
|
||||
|
||||
### Groq
|
||||
|
||||
```json
|
||||
{
|
||||
"NAME": "groq",
|
||||
"HOST": "https://api.groq.com/openai/v1",
|
||||
"APIKEY": "your-api-key",
|
||||
"MODELS": ["llama-3.3-70b-versatile"],
|
||||
"transformers": ["anthropic"]
|
||||
}
|
||||
```
|
||||
|
||||
### Gemini
|
||||
|
||||
```json
|
||||
{
|
||||
"NAME": "gemini",
|
||||
"HOST": "https://generativelanguage.googleapis.com/v1beta",
|
||||
"APIKEY": "your-api-key",
|
||||
"MODELS": ["gemini-1.5-pro"],
|
||||
"transformers": ["anthropic"]
|
||||
}
|
||||
```
|
||||
|
||||
### OpenRouter
|
||||
|
||||
```json
|
||||
{
|
||||
"NAME": "openrouter",
|
||||
"HOST": "https://openrouter.ai/api/v1",
|
||||
"APIKEY": "your-api-key",
|
||||
"MODELS": ["anthropic/claude-3.5-sonnet"],
|
||||
"transformers": ["anthropic"]
|
||||
}
|
||||
```
|
||||
|
||||
## Provider Configuration Options
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `NAME` | string | Yes | Unique provider identifier |
|
||||
| `HOST` | string | Yes | API base URL |
|
||||
| `APIKEY` | string | Yes | API authentication key |
|
||||
| `MODELS` | string[] | No | List of available models |
|
||||
| `transformers` | string[] | No | List of transformers to apply |
|
||||
|
||||
## Model Selection
|
||||
|
||||
When selecting a model in routing, use the format:
|
||||
|
||||
```
|
||||
{provider-name},{model-name}
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Routing Configuration](/docs/config/routing) - Configure how requests are routed
|
||||
- [Transformers](/docs/config/transformers) - Apply transformations to requests
|
||||
@@ -1,257 +0,0 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Routing Configuration
|
||||
|
||||
Configure how requests are routed to different models.
|
||||
|
||||
## Default Routing
|
||||
|
||||
Set the default model for all requests:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Built-in Scenarios
|
||||
|
||||
### Background Tasks
|
||||
|
||||
Route background tasks to a lightweight model:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"background": "groq,llama-3.3-70b-versatile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Thinking Mode (Plan Mode)
|
||||
|
||||
Route thinking-intensive tasks to a more capable model:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"think": "deepseek,deepseek-chat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Long Context
|
||||
|
||||
Route requests with long context:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"longContextThreshold": 100000,
|
||||
"longContext": "gemini,gemini-1.5-pro"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Web Search
|
||||
|
||||
Route web search tasks:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"webSearch": "deepseek,deepseek-chat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Image Tasks
|
||||
|
||||
Route image-related tasks:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"image": "gemini,gemini-1.5-pro"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fallback
|
||||
|
||||
When a request fails, you can configure a list of backup models. The system will try each model in sequence until one succeeds:
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat",
|
||||
"background": "ollama,qwen2.5-coder:latest",
|
||||
"think": "deepseek,deepseek-reasoner",
|
||||
"longContext": "openrouter,google/gemini-2.5-pro-preview",
|
||||
"longContextThreshold": 60000,
|
||||
"webSearch": "gemini,gemini-2.5-flash"
|
||||
},
|
||||
"fallback": {
|
||||
"default": [
|
||||
"aihubmix,Z/glm-4.5",
|
||||
"openrouter,anthropic/claude-sonnet-4"
|
||||
],
|
||||
"background": [
|
||||
"ollama,qwen2.5-coder:latest"
|
||||
],
|
||||
"think": [
|
||||
"openrouter,anthropic/claude-3.7-sonnet:thinking"
|
||||
],
|
||||
"longContext": [
|
||||
"modelscope,Qwen/Qwen3-Coder-480B-A35B-Instruct"
|
||||
],
|
||||
"webSearch": [
|
||||
"openrouter,anthropic/claude-sonnet-4"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Trigger**: When a model request fails for a routing scenario (HTTP error response)
|
||||
2. **Auto-switch**: The system automatically checks the fallback configuration for that scenario
|
||||
3. **Sequential retry**: Tries each backup model in order
|
||||
4. **Success**: Once a model responds successfully, returns immediately
|
||||
5. **All failed**: If all backup models fail, returns the original error
|
||||
|
||||
### Configuration Details
|
||||
|
||||
- **Format**: Each backup model format is `provider,model`
|
||||
- **Validation**: Backup models must exist in the `Providers` configuration
|
||||
- **Flexibility**: Different scenarios can have different fallback lists
|
||||
- **Optional**: If a scenario doesn't need fallback, omit it or use an empty array
|
||||
|
||||
### Use Cases
|
||||
|
||||
#### Scenario 1: Primary Model Quota Exhausted
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "openrouter,anthropic/claude-sonnet-4"
|
||||
},
|
||||
"fallback": {
|
||||
"default": [
|
||||
"deepseek,deepseek-chat",
|
||||
"aihubmix,Z/glm-4.5"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Automatically switches to backup models when the primary model quota is exhausted.
|
||||
|
||||
#### Scenario 2: Service Reliability
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"background": "volcengine,deepseek-v3-250324"
|
||||
},
|
||||
"fallback": {
|
||||
"background": [
|
||||
"modelscope,Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
"dashscope,qwen3-coder-plus"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Automatically switches to other providers when the primary service fails.
|
||||
|
||||
### Log Monitoring
|
||||
|
||||
The system logs detailed fallback process:
|
||||
|
||||
```
|
||||
[warn] Request failed for default, trying 2 fallback models
|
||||
[info] Trying fallback model: aihubmix,Z/glm-4.5
|
||||
[warn] Fallback model aihubmix,Z/glm-4.5 failed: API rate limit exceeded
|
||||
[info] Trying fallback model: openrouter,anthropic/claude-sonnet-4
|
||||
[info] Fallback model openrouter,anthropic/claude-sonnet-4 succeeded
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
|
||||
1. **Cost consideration**: Backup models may incur different costs, configure appropriately
|
||||
2. **Performance differences**: Different models may have varying response speeds and quality
|
||||
3. **Quota management**: Ensure backup models have sufficient quotas
|
||||
4. **Testing**: Regularly test the availability of backup models
|
||||
|
||||
## Project-Level Routing
|
||||
|
||||
Configure routing per project in `~/.claude/projects/<project-id>/claude-code-router.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "groq,llama-3.3-70b-versatile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Project-level configuration takes precedence over global configuration.
|
||||
|
||||
## Custom Router
|
||||
|
||||
Create a custom JavaScript router function:
|
||||
|
||||
1. Create a router file (e.g., `custom-router.js`):
|
||||
|
||||
```javascript
|
||||
module.exports = function(config, context) {
|
||||
// Analyze the request context
|
||||
const { scenario, projectId, tokenCount } = context;
|
||||
|
||||
// Custom routing logic
|
||||
if (scenario === 'background') {
|
||||
return 'groq,llama-3.3-70b-versatile';
|
||||
}
|
||||
|
||||
if (tokenCount > 100000) {
|
||||
return 'gemini,gemini-1.5-pro';
|
||||
}
|
||||
|
||||
// Default
|
||||
return 'deepseek,deepseek-chat';
|
||||
};
|
||||
```
|
||||
|
||||
2. Set the `CUSTOM_ROUTER_PATH` environment variable:
|
||||
|
||||
```bash
|
||||
export CUSTOM_ROUTER_PATH="/path/to/custom-router.js"
|
||||
```
|
||||
|
||||
## Token Counting
|
||||
|
||||
The router uses `tiktoken` (cl100k_base) to estimate request token count. This is used for:
|
||||
|
||||
- Determining if a request exceeds `longContextThreshold`
|
||||
- Custom routing logic based on token count
|
||||
|
||||
## Subagent Routing
|
||||
|
||||
Specify models for subagents using special tags:
|
||||
|
||||
```
|
||||
<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>
|
||||
Please help me analyze this code...
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Transformers](/docs/config/transformers) - Apply transformations to requests
|
||||
- [Custom Router](/docs/advanced/custom-router) - Advanced custom routing
|
||||
@@ -1,790 +0,0 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Transformers
|
||||
|
||||
Transformers are the core mechanism for adapting API differences between LLM providers. They convert requests and responses between different formats, handle authentication, and manage provider-specific features.
|
||||
|
||||
## Understanding Transformers
|
||||
|
||||
### What is a Transformer?
|
||||
|
||||
A transformer is a plugin that:
|
||||
- **Transforms requests** from the unified format to provider-specific format
|
||||
- **Transforms responses** from provider format back to unified format
|
||||
- **Handles authentication** for provider APIs
|
||||
- **Modifies requests** to add or adjust parameters
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Incoming Request│ (Anthropic format from Claude Code)
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ transformRequestOut │ ← Parse incoming request to unified format
|
||||
└────────┬────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ UnifiedChatRequest │
|
||||
└────────┬────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ transformRequestIn (optional) │ ← Modify unified request before sending
|
||||
└────────┬────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Provider API Call │
|
||||
└────────┬────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ transformResponseIn (optional) │ ← Convert provider response to unified format
|
||||
└────────┬────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ transformResponseOut (optional)│ ← Convert unified response to Anthropic format
|
||||
└────────┬────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Outgoing Response│ (Anthropic format to Claude Code)
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Transformer Interface
|
||||
|
||||
All transformers implement the following interface:
|
||||
|
||||
```typescript
|
||||
interface Transformer {
|
||||
// Convert unified request to provider-specific format
|
||||
transformRequestIn?: (
|
||||
request: UnifiedChatRequest,
|
||||
provider: LLMProvider,
|
||||
context: TransformerContext
|
||||
) => Promise<Record<string, any>>;
|
||||
|
||||
// Convert provider request to unified format
|
||||
transformRequestOut?: (
|
||||
request: any,
|
||||
context: TransformerContext
|
||||
) => Promise<UnifiedChatRequest>;
|
||||
|
||||
// Convert provider response to unified format
|
||||
transformResponseIn?: (
|
||||
response: Response,
|
||||
context?: TransformerContext
|
||||
) => Promise<Response>;
|
||||
|
||||
// Convert unified response to provider format
|
||||
transformResponseOut?: (
|
||||
response: Response,
|
||||
context: TransformerContext
|
||||
) => Promise<Response>;
|
||||
|
||||
// Custom endpoint path (optional)
|
||||
endPoint?: string;
|
||||
|
||||
// Transformer name (for custom transformers)
|
||||
name?: string;
|
||||
|
||||
// Custom authentication handler (optional)
|
||||
auth?: (
|
||||
request: any,
|
||||
provider: LLMProvider,
|
||||
context: TransformerContext
|
||||
) => Promise<any>;
|
||||
|
||||
// Logger instance (auto-injected)
|
||||
logger?: any;
|
||||
}
|
||||
```
|
||||
|
||||
### Key Types
|
||||
|
||||
#### UnifiedChatRequest
|
||||
|
||||
```typescript
|
||||
interface UnifiedChatRequest {
|
||||
messages: UnifiedMessage[];
|
||||
model: string;
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
stream?: boolean;
|
||||
tools?: UnifiedTool[];
|
||||
tool_choice?: any;
|
||||
reasoning?: {
|
||||
effort?: ThinkLevel; // "none" | "low" | "medium" | "high"
|
||||
max_tokens?: number;
|
||||
enabled?: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### UnifiedMessage
|
||||
|
||||
```typescript
|
||||
interface UnifiedMessage {
|
||||
role: "user" | "assistant" | "system" | "tool";
|
||||
content: string | null | MessageContent[];
|
||||
tool_calls?: Array<{
|
||||
id: string;
|
||||
type: "function";
|
||||
function: {
|
||||
name: string;
|
||||
arguments: string;
|
||||
};
|
||||
}>;
|
||||
tool_call_id?: string;
|
||||
thinking?: {
|
||||
content: string;
|
||||
signature?: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Built-in Transformers
|
||||
|
||||
### anthropic
|
||||
|
||||
Transforms requests to be compatible with Anthropic-style APIs:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"providers": ["deepseek", "groq"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Converts Anthropic message format to/from OpenAI format
|
||||
- Handles tool calls and tool results
|
||||
- Supports thinking/reasoning content blocks
|
||||
- Manages streaming responses
|
||||
|
||||
### deepseek
|
||||
|
||||
Specialized transformer for DeepSeek API:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "deepseek",
|
||||
"providers": ["deepseek"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- DeepSeek-specific reasoning format
|
||||
- Handles `reasoning_content` in responses
|
||||
- Supports thinking budget tokens
|
||||
|
||||
### gemini
|
||||
|
||||
Transformer for Google Gemini API:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "gemini",
|
||||
"providers": ["gemini"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### maxtoken
|
||||
|
||||
Limits max_tokens in requests:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "maxtoken",
|
||||
"options": {
|
||||
"max_tokens": 8192
|
||||
},
|
||||
"models": ["deepseek,deepseek-chat"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### customparams
|
||||
|
||||
Injects custom parameters into requests:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "customparams",
|
||||
"options": {
|
||||
"include_reasoning": true,
|
||||
"custom_header": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Custom Transformers
|
||||
|
||||
### Simple Transformer: Modifying Requests
|
||||
|
||||
The simplest transformers just modify the request before it's sent to the provider.
|
||||
|
||||
**Example: Add a custom header to all requests**
|
||||
|
||||
```javascript
|
||||
// custom-header-transformer.js
|
||||
module.exports = class CustomHeaderTransformer {
|
||||
name = 'custom-header';
|
||||
|
||||
constructor(options) {
|
||||
this.headerName = options?.headerName || 'X-Custom-Header';
|
||||
this.headerValue = options?.headerValue || 'default-value';
|
||||
}
|
||||
|
||||
async transformRequestIn(request, provider, context) {
|
||||
// Add custom header (will be used by auth method)
|
||||
request._customHeaders = {
|
||||
[this.headerName]: this.headerValue
|
||||
};
|
||||
return request;
|
||||
}
|
||||
|
||||
async auth(request, provider) {
|
||||
const headers = {
|
||||
'authorization': `Bearer ${provider.apiKey}`,
|
||||
...request._customHeaders
|
||||
};
|
||||
return {
|
||||
body: request,
|
||||
config: { headers }
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Usage in config:**
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "custom-header",
|
||||
"path": "/path/to/custom-header-transformer.js",
|
||||
"options": {
|
||||
"headerName": "X-My-Header",
|
||||
"headerValue": "my-value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Intermediate Transformer: Request/Response Conversion
|
||||
|
||||
This example shows how to convert between different API formats.
|
||||
|
||||
**Example: Mock API format transformer**
|
||||
|
||||
```javascript
|
||||
// mockapi-transformer.js
|
||||
module.exports = class MockAPITransformer {
|
||||
name = 'mockapi';
|
||||
endPoint = '/v1/chat'; // Custom endpoint
|
||||
|
||||
// Convert from MockAPI format to unified format
|
||||
async transformRequestOut(request, context) {
|
||||
const messages = request.conversation.map(msg => ({
|
||||
role: msg.sender,
|
||||
content: msg.text
|
||||
}));
|
||||
|
||||
return {
|
||||
messages,
|
||||
model: request.model_id,
|
||||
max_tokens: request.max_tokens,
|
||||
temperature: request.temp
|
||||
};
|
||||
}
|
||||
|
||||
// Convert from unified format to MockAPI format
|
||||
async transformRequestIn(request, provider, context) {
|
||||
return {
|
||||
model_id: request.model,
|
||||
conversation: request.messages.map(msg => ({
|
||||
sender: msg.role,
|
||||
text: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
|
||||
})),
|
||||
max_tokens: request.max_tokens || 4096,
|
||||
temp: request.temperature || 0.7
|
||||
};
|
||||
}
|
||||
|
||||
// Convert MockAPI response to unified format
|
||||
async transformResponseIn(response, context) {
|
||||
const data = await response.json();
|
||||
|
||||
const unifiedResponse = {
|
||||
id: data.request_id,
|
||||
object: 'chat.completion',
|
||||
created: data.timestamp,
|
||||
model: data.model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: data.reply.text
|
||||
},
|
||||
finish_reason: data.stop_reason
|
||||
}],
|
||||
usage: {
|
||||
prompt_tokens: data.tokens.input,
|
||||
completion_tokens: data.tokens.output,
|
||||
total_tokens: data.tokens.input + data.tokens.output
|
||||
}
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(unifiedResponse), {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Advanced Transformer: Streaming Response Processing
|
||||
|
||||
This example shows how to handle streaming responses.
|
||||
|
||||
**Example: Add custom metadata to streaming responses**
|
||||
|
||||
```javascript
|
||||
// streaming-metadata-transformer.js
|
||||
module.exports = class StreamingMetadataTransformer {
|
||||
name = 'streaming-metadata';
|
||||
|
||||
constructor(options) {
|
||||
this.metadata = options?.metadata || {};
|
||||
this.logger = null; // Will be injected by the system
|
||||
}
|
||||
|
||||
async transformResponseOut(response, context) {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
|
||||
// Handle streaming response
|
||||
if (contentType?.includes('text/event-stream')) {
|
||||
return this.transformStream(response, context);
|
||||
}
|
||||
|
||||
// Handle non-streaming response
|
||||
return response;
|
||||
}
|
||||
|
||||
async transformStream(response, context) {
|
||||
const decoder = new TextDecoder();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
const transformedStream = new ReadableStream({
|
||||
start: async (controller) => {
|
||||
const reader = response.body.getReader();
|
||||
let buffer = '';
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim() || !line.startsWith('data: ')) {
|
||||
controller.enqueue(encoder.encode(line + '\n'));
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = line.slice(6).trim();
|
||||
if (data === '[DONE]') {
|
||||
controller.enqueue(encoder.encode(line + '\n'));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const chunk = JSON.parse(data);
|
||||
|
||||
// Add custom metadata
|
||||
if (chunk.choices && chunk.choices[0]) {
|
||||
chunk.choices[0].metadata = this.metadata;
|
||||
}
|
||||
|
||||
// Log for debugging
|
||||
this.logger?.debug({
|
||||
chunk,
|
||||
context: context.req.id
|
||||
}, 'Transformed streaming chunk');
|
||||
|
||||
const modifiedLine = `data: ${JSON.stringify(chunk)}\n\n`;
|
||||
controller.enqueue(encoder.encode(modifiedLine));
|
||||
} catch (parseError) {
|
||||
// If parsing fails, pass through original line
|
||||
controller.enqueue(encoder.encode(line + '\n'));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger?.error({ error }, 'Stream transformation error');
|
||||
controller.error(error);
|
||||
} finally {
|
||||
controller.close();
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new Response(transformedStream, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Real-World Example: Reasoning Content Transformer
|
||||
|
||||
This is based on the actual `reasoning.transformer.ts` from the codebase.
|
||||
|
||||
```typescript
|
||||
// reasoning-transformer.ts
|
||||
import { Transformer, TransformerOptions } from "@musistudio/llms";
|
||||
|
||||
export class ReasoningTransformer implements Transformer {
|
||||
static TransformerName = "reasoning";
|
||||
enable: boolean;
|
||||
|
||||
constructor(private readonly options?: TransformerOptions) {
|
||||
this.enable = this.options?.enable ?? true;
|
||||
}
|
||||
|
||||
// Transform request to add reasoning parameters
|
||||
async transformRequestIn(request: UnifiedChatRequest): Promise<UnifiedChatRequest> {
|
||||
if (!this.enable) {
|
||||
request.thinking = {
|
||||
type: "disabled",
|
||||
budget_tokens: -1,
|
||||
};
|
||||
request.enable_thinking = false;
|
||||
return request;
|
||||
}
|
||||
|
||||
if (request.reasoning) {
|
||||
request.thinking = {
|
||||
type: "enabled",
|
||||
budget_tokens: request.reasoning.max_tokens,
|
||||
};
|
||||
request.enable_thinking = true;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
// Transform response to convert reasoning_content to thinking format
|
||||
async transformResponseOut(response: Response): Promise<Response> {
|
||||
if (!this.enable) return response;
|
||||
|
||||
const contentType = response.headers.get("Content-Type");
|
||||
|
||||
// Handle non-streaming response
|
||||
if (contentType?.includes("application/json")) {
|
||||
const jsonResponse = await response.json();
|
||||
if (jsonResponse.choices[0]?.message.reasoning_content) {
|
||||
jsonResponse.thinking = {
|
||||
content: jsonResponse.choices[0].message.reasoning_content
|
||||
};
|
||||
}
|
||||
return new Response(JSON.stringify(jsonResponse), {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle streaming response
|
||||
if (contentType?.includes("stream")) {
|
||||
// [Streaming transformation code here]
|
||||
// See the full implementation in the codebase
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Transformer Registration
|
||||
|
||||
### Method 1: Static Name (Class-based)
|
||||
|
||||
Use this when creating a transformer in TypeScript/ES6:
|
||||
|
||||
```typescript
|
||||
export class MyTransformer implements Transformer {
|
||||
static TransformerName = "my-transformer";
|
||||
|
||||
async transformRequestIn(request: UnifiedChatRequest): Promise<any> {
|
||||
// Transformation logic
|
||||
return request;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Method 2: Instance Name (Instance-based)
|
||||
|
||||
Use this for JavaScript transformers:
|
||||
|
||||
```javascript
|
||||
module.exports = class MyTransformer {
|
||||
constructor(options) {
|
||||
this.name = 'my-transformer';
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
async transformRequestIn(request, provider, context) {
|
||||
// Transformation logic
|
||||
return request;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Applying Transformers
|
||||
|
||||
### Global Application (Provider Level)
|
||||
|
||||
Apply to all requests for a provider:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"NAME": "deepseek",
|
||||
"HOST": "https://api.deepseek.com",
|
||||
"APIKEY": "your-api-key",
|
||||
"transformers": ["anthropic"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Model-Specific Application
|
||||
|
||||
Apply to specific models only:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "maxtoken",
|
||||
"options": {
|
||||
"max_tokens": 8192
|
||||
},
|
||||
"models": ["deepseek,deepseek-chat"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note: The model format is `provider,model` (e.g., `deepseek,deepseek-chat`).
|
||||
|
||||
### Global Transformers (All Providers)
|
||||
|
||||
Apply transformers to all providers:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "custom-logger",
|
||||
"path": "/path/to/custom-logger.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Passing Options
|
||||
|
||||
Some transformers accept configuration options:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "maxtoken",
|
||||
"options": {
|
||||
"max_tokens": 8192
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "customparams",
|
||||
"options": {
|
||||
"custom_param_1": "value1",
|
||||
"custom_param_2": 42
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Immutability
|
||||
|
||||
Always create new objects rather than mutating existing ones:
|
||||
|
||||
```javascript
|
||||
// Bad
|
||||
async transformRequestIn(request) {
|
||||
request.max_tokens = 4096;
|
||||
return request;
|
||||
}
|
||||
|
||||
// Good
|
||||
async transformRequestIn(request) {
|
||||
return {
|
||||
...request,
|
||||
max_tokens: request.max_tokens || 4096
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Error Handling
|
||||
|
||||
Always handle errors gracefully:
|
||||
|
||||
```javascript
|
||||
async transformResponseIn(response) {
|
||||
try {
|
||||
const data = await response.json();
|
||||
// Process data
|
||||
return new Response(JSON.stringify(processedData), {
|
||||
status: response.status,
|
||||
headers: response.headers
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger?.error({ error }, 'Transformation failed');
|
||||
// Return original response if transformation fails
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Logging
|
||||
|
||||
Use the injected logger for debugging:
|
||||
|
||||
```javascript
|
||||
async transformRequestIn(request, provider, context) {
|
||||
this.logger?.debug({
|
||||
model: request.model,
|
||||
provider: provider.name
|
||||
}, 'Transforming request');
|
||||
|
||||
// Your transformation logic
|
||||
|
||||
return modifiedRequest;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Stream Handling
|
||||
|
||||
When handling streams, always:
|
||||
- Use a buffer to handle incomplete chunks
|
||||
- Properly release the reader lock
|
||||
- Handle errors in the stream
|
||||
- Close the controller when done
|
||||
|
||||
```javascript
|
||||
const transformedStream = new ReadableStream({
|
||||
start: async (controller) => {
|
||||
const reader = response.body.getReader();
|
||||
let buffer = '';
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
// Process stream...
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(error);
|
||||
} finally {
|
||||
controller.close();
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Context Usage
|
||||
|
||||
The `context` parameter contains useful information:
|
||||
|
||||
```javascript
|
||||
async transformRequestIn(request, provider, context) {
|
||||
// Access request ID
|
||||
const requestId = context.req.id;
|
||||
|
||||
// Access original request
|
||||
const originalRequest = context.req.original;
|
||||
|
||||
// Your transformation logic
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Your Transformer
|
||||
|
||||
### Manual Testing
|
||||
|
||||
1. Add your transformer to the config
|
||||
2. Start the server: `ccr restart`
|
||||
3. Check logs: `tail -f ~/.claude-code-router/logs/ccr-*.log`
|
||||
4. Make a test request
|
||||
5. Verify the output
|
||||
|
||||
### Debug Tips
|
||||
|
||||
- Add logging to track transformation steps
|
||||
- Test with both streaming and non-streaming requests
|
||||
- Verify error handling with invalid inputs
|
||||
- Check that original responses are returned on error
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Advanced Topics](/docs/server/advanced/custom-router) - Advanced routing customization
|
||||
- [Agents](/docs/server/advanced/agents) - Extending with agents
|
||||
- [Core Package](/docs/server/intro) - Learn about @musistudio/llms
|
||||
@@ -1,186 +0,0 @@
|
||||
---
|
||||
title: Server Deployment
|
||||
---
|
||||
|
||||
# Server Deployment
|
||||
|
||||
Claude Code Router Server supports multiple deployment methods, from local development to production environments.
|
||||
|
||||
## Docker Deployment (Recommended)
|
||||
|
||||
### Using Docker Hub Image
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name claude-code-router \
|
||||
-p 3456:3456 \
|
||||
-v ~/.claude-code-router:/app/.claude-code-router \
|
||||
musistudio/claude-code-router:latest
|
||||
```
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
Create `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
claude-code-router:
|
||||
image: musistudio/claude-code-router:latest
|
||||
container_name: claude-code-router
|
||||
ports:
|
||||
- "3456:3456"
|
||||
volumes:
|
||||
- ./config:/app/.claude-code-router
|
||||
environment:
|
||||
- LOG_LEVEL=info
|
||||
- HOST=0.0.0.0
|
||||
- PORT=3456
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Start the service:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Custom Build
|
||||
|
||||
Build Docker image from source:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/musistudio/claude-code-router.git
|
||||
cd claude-code-router
|
||||
docker build -t claude-code-router:latest .
|
||||
```
|
||||
|
||||
## Configuration File Mounting
|
||||
|
||||
Mount configuration file into container:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name claude-code-router \
|
||||
-p 3456:3456 \
|
||||
-v $(pwd)/config.json:/app/.claude-code-router/config.json \
|
||||
musistudio/claude-code-router:latest
|
||||
```
|
||||
|
||||
Configuration file example:
|
||||
|
||||
```json5
|
||||
{
|
||||
// Server configuration
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": 3456,
|
||||
"APIKEY": "your-api-key-here",
|
||||
|
||||
// Logging configuration
|
||||
"LOG": true,
|
||||
"LOG_LEVEL": "info",
|
||||
|
||||
// LLM provider configuration
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
|
||||
// Routing configuration
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Override configuration through environment variables:
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HOST` | Listen address | `127.0.0.1` |
|
||||
| `PORT` | Listen port | `3456` |
|
||||
| `APIKEY` | API key | - |
|
||||
| `LOG_LEVEL` | Log level | `debug` |
|
||||
| `LOG` | Enable logging | `true` |
|
||||
|
||||
## Production Recommendations
|
||||
|
||||
### 1. Use Reverse Proxy
|
||||
|
||||
Use Nginx as reverse proxy:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3456;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Configure HTTPS
|
||||
|
||||
Use Let's Encrypt to obtain free certificate:
|
||||
|
||||
```bash
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
### 3. Log Management
|
||||
|
||||
Configure log rotation and persistence:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
claude-code-router:
|
||||
image: musistudio/claude-code-router:latest
|
||||
volumes:
|
||||
- ./logs:/app/.claude-code-router/logs
|
||||
environment:
|
||||
- LOG_LEVEL=warn
|
||||
```
|
||||
|
||||
### 4. Health Check
|
||||
|
||||
Configure Docker health check:
|
||||
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3456/api/config"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
```
|
||||
|
||||
## Access Web UI
|
||||
|
||||
After deployment is complete, access the Web UI:
|
||||
|
||||
```
|
||||
http://localhost:3456/ui/
|
||||
```
|
||||
|
||||
Through the Web UI you can:
|
||||
- View and manage configuration
|
||||
- Monitor logs
|
||||
- Check service status
|
||||
|
||||
## Secondary Development
|
||||
|
||||
If you need to develop based on CCR Server, please see [API Reference](/docs/category/api).
|
||||
@@ -1,167 +0,0 @@
|
||||
---
|
||||
title: Server Introduction
|
||||
---
|
||||
|
||||
# Server Introduction
|
||||
|
||||
Claude Code Router Server is a core service component responsible for routing Claude Code API requests to different LLM providers. It provides a complete HTTP API with support for:
|
||||
|
||||
- **API Request Routing**: Convert Anthropic-format requests to various provider API formats
|
||||
- **Authentication & Authorization**: Support API Key authentication
|
||||
- **Configuration Management**: Dynamic configuration of providers, routing rules, and transformers
|
||||
- **Web UI**: Built-in management interface
|
||||
- **Logging System**: Complete request logging
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────────────────────┐ ┌──────────────┐
|
||||
│ Claude Code │────▶│ CCR Server │────▶│ LLM Provider │
|
||||
│ Client │ │ ┌─────────────────────┐ │ │ (OpenAI/ │
|
||||
└─────────────┘ │ │ @musistudio/llms │ │ │ Gemini/etc)│
|
||||
│ │ (Core Package) │ │ └──────────────┘
|
||||
│ │ - Request Transform │ │
|
||||
│ │ - Response Transform │ │
|
||||
│ │ - Auth Handling │ │
|
||||
│ └─────────────────────┘ │
|
||||
│ │
|
||||
│ - Routing Logic │
|
||||
│ - Agent System │
|
||||
│ - Configuration │
|
||||
└─────────────────────────────┘
|
||||
│
|
||||
├─ Web UI
|
||||
├─ Config API
|
||||
└─ Logs API
|
||||
```
|
||||
|
||||
## Core Package: @musistudio/llms
|
||||
|
||||
The server is built on top of **@musistudio/llms**, a universal LLM API transformation library that provides the core request/response transformation capabilities.
|
||||
|
||||
### What is @musistudio/llms?
|
||||
|
||||
`@musistudio/llms` is a standalone npm package (`@musistudio/llms`) that handles:
|
||||
|
||||
- **API Format Conversion**: Transforms between different LLM provider APIs (Anthropic, OpenAI, Gemini, etc.)
|
||||
- **Request/Response Transformation**: Converts requests and responses to a unified format
|
||||
- **Authentication Handling**: Manages different authentication methods across providers
|
||||
- **Streaming Support**: Handles streaming responses from different providers
|
||||
- **Transformer System**: Provides an extensible architecture for adding new providers
|
||||
|
||||
### Key Concepts
|
||||
|
||||
#### 1. Unified Request/Response Format
|
||||
|
||||
The core package defines a unified format (`UnifiedChatRequest`, `UnifiedChatResponse`) that abstracts away provider-specific differences:
|
||||
|
||||
```typescript
|
||||
interface UnifiedChatRequest {
|
||||
messages: UnifiedMessage[];
|
||||
model: string;
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
stream?: boolean;
|
||||
tools?: UnifiedTool[];
|
||||
tool_choice?: any;
|
||||
reasoning?: {
|
||||
effort?: ThinkLevel;
|
||||
max_tokens?: number;
|
||||
enabled?: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Transformer Interface
|
||||
|
||||
All transformers implement a common interface:
|
||||
|
||||
```typescript
|
||||
interface Transformer {
|
||||
transformRequestIn?: (request: UnifiedChatRequest, provider: LLMProvider, context: TransformerContext) => Promise<any>;
|
||||
transformRequestOut?: (request: any, context: TransformerContext) => Promise<UnifiedChatRequest>;
|
||||
transformResponseIn?: (response: Response, context?: TransformerContext) => Promise<Response>;
|
||||
transformResponseOut?: (response: Response, context: TransformerContext) => Promise<Response>;
|
||||
endPoint?: string;
|
||||
name?: string;
|
||||
auth?: (request: any, provider: LLMProvider, context: TransformerContext) => Promise<any>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Built-in Transformers
|
||||
|
||||
The core package includes transformers for:
|
||||
- **anthropic**: Anthropic API format
|
||||
- **openai**: OpenAI API format
|
||||
- **gemini**: Google Gemini API format
|
||||
- **deepseek**: DeepSeek API format
|
||||
- **groq**: Groq API format
|
||||
- **openrouter**: OpenRouter API format
|
||||
- And more...
|
||||
|
||||
### Integration with CCR Server
|
||||
|
||||
The CCR server integrates `@musistudio/llms` through:
|
||||
|
||||
1. **Transformer Service** (`packages/core/src/services/transformer.ts`): Manages transformer registration and instantiation
|
||||
2. **Provider Configuration**: Maps provider configs to core package's LLMProvider interface
|
||||
3. **Request Pipeline**: Applies transformers in sequence during request processing
|
||||
4. **Custom Transformers**: Supports loading external transformer plugins
|
||||
|
||||
### Version and Updates
|
||||
|
||||
The current version of `@musistudio/llms` is `1.0.51`. It's published as an independent npm package and can be used standalone or as part of CCR Server.
|
||||
|
||||
## Core Features
|
||||
|
||||
### 1. Request Routing
|
||||
- Token-count-based intelligent routing
|
||||
- Project-level routing configuration
|
||||
- Custom routing functions
|
||||
- Scenario-based routing (background, think, longContext, etc.)
|
||||
|
||||
### 2. Request Transformation
|
||||
- Supports API format conversion for multiple LLM providers
|
||||
- Built-in transformers: Anthropic, DeepSeek, Gemini, OpenRouter, Groq, etc.
|
||||
- Extensible transformer system
|
||||
|
||||
### 3. Agent System
|
||||
- Plugin-based Agent architecture
|
||||
- Built-in image processing Agent
|
||||
- Custom Agent support
|
||||
|
||||
### 4. Configuration Management
|
||||
- JSON5 format configuration file
|
||||
- Environment variable interpolation
|
||||
- Hot configuration reload (requires service restart)
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Scenario 1: Personal Local Service
|
||||
Run the service locally for personal Claude Code use:
|
||||
|
||||
```bash
|
||||
ccr start
|
||||
```
|
||||
|
||||
### Scenario 2: Team Shared Service
|
||||
Deploy using Docker to provide shared service for team members:
|
||||
|
||||
```bash
|
||||
docker run -d -p 3456:3456 musistudio/claude-code-router
|
||||
```
|
||||
|
||||
### Scenario 3: Secondary Development
|
||||
Build custom applications based on exposed APIs:
|
||||
|
||||
```bash
|
||||
GET /api/config
|
||||
POST /v1/messages
|
||||
GET /api/logs
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Docker Deployment Guide](/docs/server/deployment) - Learn how to deploy the service
|
||||
- [API Reference](/docs/category/api) - View complete API documentation
|
||||
- [Configuration Guide](/docs/category/server-config) - Understand server configuration options
|
||||
@@ -1,95 +0,0 @@
|
||||
import type { Config } from '@docusaurus/types';
|
||||
import type * as Preset from '@docusaurus/preset-classic';
|
||||
import { themes as prismThemes } from 'prism-react-renderer';
|
||||
|
||||
const config: Config = {
|
||||
title: 'Claude Code Router',
|
||||
tagline: 'Use Claude Code without an Anthropics account and route it to another LLM provider',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
url: 'https://musistudio.github.io',
|
||||
baseUrl: '/claude-code-router/',
|
||||
|
||||
organizationName: 'musistudio',
|
||||
projectName: 'claude-code-router',
|
||||
|
||||
onBrokenLinks: 'warn',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
onBrokenAnchors: 'warn',
|
||||
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'zh-CN'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
{
|
||||
docs: {
|
||||
sidebarPath: './sidebars.ts',
|
||||
editUrl:
|
||||
'https://github.com/musistudio/claude-code-router/tree/main/docs',
|
||||
},
|
||||
blog: {
|
||||
showReadingTime: true,
|
||||
editUrl:
|
||||
'https://github.com/musistudio/claude-code-router/tree/main/docs',
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
} satisfies Preset.Options,
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
// Disable dark mode
|
||||
colorMode: {
|
||||
defaultMode: 'light',
|
||||
disableSwitch: true,
|
||||
respectPrefersColorScheme: false,
|
||||
},
|
||||
|
||||
image: 'img/docusaurus-social-card.jpg',
|
||||
navbar: {
|
||||
title: 'Claude Code Router',
|
||||
logo: {
|
||||
alt: 'Claude Code Router Logo',
|
||||
src: 'img/ccr.svg',
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'tutorialSidebar',
|
||||
position: 'left',
|
||||
label: 'Documentation',
|
||||
},
|
||||
{ to: '/blog', label: 'Blog', position: 'left' },
|
||||
{
|
||||
type: 'localeDropdown',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/musistudio/claude-code-router',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'light',
|
||||
links: [],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Claude Code Router. Built with Docusaurus.`,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
additionalLanguages: ['bash', 'typescript', 'javascript', 'json'],
|
||||
},
|
||||
} satisfies Preset.ThemeConfig,
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,329 +0,0 @@
|
||||
{
|
||||
"theme.ErrorPageContent.title": {
|
||||
"message": "This page crashed.",
|
||||
"description": "The title of the fallback page when the page crashed"
|
||||
},
|
||||
"theme.BackToTopButton.buttonAriaLabel": {
|
||||
"message": "Scroll back to top",
|
||||
"description": "The ARIA label for the back to top button"
|
||||
},
|
||||
"theme.blog.archive.title": {
|
||||
"message": "Archive",
|
||||
"description": "The page & hero title of the blog archive page"
|
||||
},
|
||||
"theme.blog.archive.description": {
|
||||
"message": "Archive",
|
||||
"description": "The page & hero description of the blog archive page"
|
||||
},
|
||||
"theme.blog.paginator.navAriaLabel": {
|
||||
"message": "Blog list page navigation",
|
||||
"description": "The ARIA label for the blog pagination"
|
||||
},
|
||||
"theme.blog.paginator.newerEntries": {
|
||||
"message": "Newer entries",
|
||||
"description": "The label used to navigate to the newer blog posts page (previous page)"
|
||||
},
|
||||
"theme.blog.paginator.olderEntries": {
|
||||
"message": "Older entries",
|
||||
"description": "The label used to navigate to the older blog posts page (next page)"
|
||||
},
|
||||
"theme.blog.post.paginator.navAriaLabel": {
|
||||
"message": "Blog post page navigation",
|
||||
"description": "The ARIA label for the blog posts pagination"
|
||||
},
|
||||
"theme.blog.post.paginator.newerPost": {
|
||||
"message": "Newer post",
|
||||
"description": "The blog post button label to navigate to the newer/previous post"
|
||||
},
|
||||
"theme.blog.post.paginator.olderPost": {
|
||||
"message": "Older post",
|
||||
"description": "The blog post button label to navigate to the older/next post"
|
||||
},
|
||||
"theme.tags.tagsPageLink": {
|
||||
"message": "View all tags",
|
||||
"description": "The label of the link targeting the tag list page"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel.mode.system": {
|
||||
"message": "system mode",
|
||||
"description": "The name for the system color mode"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel.mode.light": {
|
||||
"message": "light mode",
|
||||
"description": "The name for the light color mode"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel.mode.dark": {
|
||||
"message": "dark mode",
|
||||
"description": "The name for the dark color mode"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel": {
|
||||
"message": "Switch between dark and light mode (currently {mode})",
|
||||
"description": "The ARIA label for the color mode toggle"
|
||||
},
|
||||
"theme.docs.breadcrumbs.navAriaLabel": {
|
||||
"message": "Breadcrumbs",
|
||||
"description": "The ARIA label for the breadcrumbs"
|
||||
},
|
||||
"theme.docs.DocCard.categoryDescription.plurals": {
|
||||
"message": "1 item|{count} items",
|
||||
"description": "The default description for a category card in the generated index about how many items this category includes"
|
||||
},
|
||||
"theme.docs.paginator.navAriaLabel": {
|
||||
"message": "Docs pages",
|
||||
"description": "The ARIA label for the docs pagination"
|
||||
},
|
||||
"theme.docs.paginator.previous": {
|
||||
"message": "Previous",
|
||||
"description": "The label used to navigate to the previous doc"
|
||||
},
|
||||
"theme.docs.paginator.next": {
|
||||
"message": "Next",
|
||||
"description": "The label used to navigate to the next doc"
|
||||
},
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": {
|
||||
"message": "One doc tagged|{count} docs tagged",
|
||||
"description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.docs.tagDocListPageTitle": {
|
||||
"message": "{nDocsTagged} with \"{tagName}\"",
|
||||
"description": "The title of the page for a docs tag"
|
||||
},
|
||||
"theme.docs.versionBadge.label": {
|
||||
"message": "Version: {versionLabel}"
|
||||
},
|
||||
"theme.docs.versions.unreleasedVersionLabel": {
|
||||
"message": "This is unreleased documentation for {siteTitle} {versionLabel} version.",
|
||||
"description": "The label used to tell the user that he's browsing an unreleased doc version"
|
||||
},
|
||||
"theme.docs.versions.unmaintainedVersionLabel": {
|
||||
"message": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.",
|
||||
"description": "The label used to tell the user that he's browsing an unmaintained doc version"
|
||||
},
|
||||
"theme.docs.versions.latestVersionSuggestionLabel": {
|
||||
"message": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).",
|
||||
"description": "The label used to tell the user to check the latest version"
|
||||
},
|
||||
"theme.docs.versions.latestVersionLinkLabel": {
|
||||
"message": "latest version",
|
||||
"description": "The label used for the latest version suggestion link label"
|
||||
},
|
||||
"theme.common.editThisPage": {
|
||||
"message": "Edit this page",
|
||||
"description": "The link label to edit the current page"
|
||||
},
|
||||
"theme.common.headingLinkTitle": {
|
||||
"message": "Direct link to {heading}",
|
||||
"description": "Title for link to heading"
|
||||
},
|
||||
"theme.lastUpdated.atDate": {
|
||||
"message": " on {date}",
|
||||
"description": "The words used to describe on which date a page has been last updated"
|
||||
},
|
||||
"theme.lastUpdated.byUser": {
|
||||
"message": " by {user}",
|
||||
"description": "The words used to describe by who the page has been last updated"
|
||||
},
|
||||
"theme.lastUpdated.lastUpdatedAtBy": {
|
||||
"message": "Last updated{atDate}{byUser}",
|
||||
"description": "The sentence used to display when a page has been last updated, and by who"
|
||||
},
|
||||
"theme.NotFound.title": {
|
||||
"message": "Page Not Found",
|
||||
"description": "The title of the 404 page"
|
||||
},
|
||||
"theme.navbar.mobileVersionsDropdown.label": {
|
||||
"message": "Versions",
|
||||
"description": "The label for the navbar versions dropdown on mobile view"
|
||||
},
|
||||
"theme.tags.tagsListLabel": {
|
||||
"message": "Tags:",
|
||||
"description": "The label alongside a tag list"
|
||||
},
|
||||
"theme.AnnouncementBar.closeButtonAriaLabel": {
|
||||
"message": "Close",
|
||||
"description": "The ARIA label for close button of announcement bar"
|
||||
},
|
||||
"theme.admonition.caution": {
|
||||
"message": "caution",
|
||||
"description": "The default label used for the Caution admonition (:::caution)"
|
||||
},
|
||||
"theme.admonition.danger": {
|
||||
"message": "danger",
|
||||
"description": "The default label used for the Danger admonition (:::danger)"
|
||||
},
|
||||
"theme.admonition.info": {
|
||||
"message": "info",
|
||||
"description": "The default label used for the Info admonition (:::info)"
|
||||
},
|
||||
"theme.admonition.note": {
|
||||
"message": "note",
|
||||
"description": "The default label used for the Note admonition (:::note)"
|
||||
},
|
||||
"theme.admonition.tip": {
|
||||
"message": "tip",
|
||||
"description": "The default label used for the Tip admonition (:::tip)"
|
||||
},
|
||||
"theme.admonition.warning": {
|
||||
"message": "warning",
|
||||
"description": "The default label used for the Warning admonition (:::warning)"
|
||||
},
|
||||
"theme.blog.sidebar.navAriaLabel": {
|
||||
"message": "Blog recent posts navigation",
|
||||
"description": "The ARIA label for recent posts in the blog sidebar"
|
||||
},
|
||||
"theme.DocSidebarItem.expandCategoryAriaLabel": {
|
||||
"message": "Expand sidebar category '{label}'",
|
||||
"description": "The ARIA label to expand the sidebar category"
|
||||
},
|
||||
"theme.DocSidebarItem.collapseCategoryAriaLabel": {
|
||||
"message": "Collapse sidebar category '{label}'",
|
||||
"description": "The ARIA label to collapse the sidebar category"
|
||||
},
|
||||
"theme.IconExternalLink.ariaLabel": {
|
||||
"message": "(opens in new tab)",
|
||||
"description": "The ARIA label for the external link icon"
|
||||
},
|
||||
"theme.NavBar.navAriaLabel": {
|
||||
"message": "Main",
|
||||
"description": "The ARIA label for the main navigation"
|
||||
},
|
||||
"theme.NotFound.p1": {
|
||||
"message": "We could not find what you were looking for.",
|
||||
"description": "The first paragraph of the 404 page"
|
||||
},
|
||||
"theme.NotFound.p2": {
|
||||
"message": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.",
|
||||
"description": "The 2nd paragraph of the 404 page"
|
||||
},
|
||||
"theme.navbar.mobileLanguageDropdown.label": {
|
||||
"message": "Languages",
|
||||
"description": "The label for the mobile language switcher dropdown"
|
||||
},
|
||||
"theme.TOCCollapsible.toggleButtonLabel": {
|
||||
"message": "On this page",
|
||||
"description": "The label used by the button on the collapsible TOC component"
|
||||
},
|
||||
"theme.blog.post.readMore": {
|
||||
"message": "Read more",
|
||||
"description": "The label used in blog post item excerpts to link to full blog posts"
|
||||
},
|
||||
"theme.blog.post.readMoreLabel": {
|
||||
"message": "Read more about {title}",
|
||||
"description": "The ARIA label for the link to full blog posts from excerpts"
|
||||
},
|
||||
"theme.blog.post.readingTime.plurals": {
|
||||
"message": "One min read|{readingTime} min read",
|
||||
"description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.CodeBlock.copy": {
|
||||
"message": "Copy",
|
||||
"description": "The copy button label on code blocks"
|
||||
},
|
||||
"theme.CodeBlock.copied": {
|
||||
"message": "Copied",
|
||||
"description": "The copied button label on code blocks"
|
||||
},
|
||||
"theme.CodeBlock.copyButtonAriaLabel": {
|
||||
"message": "Copy code to clipboard",
|
||||
"description": "The ARIA label for copy code blocks button"
|
||||
},
|
||||
"theme.CodeBlock.wordWrapToggle": {
|
||||
"message": "Toggle word wrap",
|
||||
"description": "The title attribute for toggle word wrapping button of code block lines"
|
||||
},
|
||||
"theme.docs.breadcrumbs.home": {
|
||||
"message": "Home page",
|
||||
"description": "The ARIA label for the home page in the breadcrumbs"
|
||||
},
|
||||
"theme.docs.sidebar.navAriaLabel": {
|
||||
"message": "Docs sidebar",
|
||||
"description": "The ARIA label for the sidebar navigation"
|
||||
},
|
||||
"theme.docs.sidebar.collapseButtonTitle": {
|
||||
"message": "Collapse sidebar",
|
||||
"description": "The title attribute for collapse button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.collapseButtonAriaLabel": {
|
||||
"message": "Collapse sidebar",
|
||||
"description": "The title attribute for collapse button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.closeSidebarButtonAriaLabel": {
|
||||
"message": "Close navigation bar",
|
||||
"description": "The ARIA label for close button of mobile sidebar"
|
||||
},
|
||||
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": {
|
||||
"message": "← Back to main menu",
|
||||
"description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"
|
||||
},
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": {
|
||||
"message": "Toggle navigation bar",
|
||||
"description": "The ARIA label for hamburger menu button of mobile navigation"
|
||||
},
|
||||
"theme.navbar.mobileDropdown.collapseButton.expandAriaLabel": {
|
||||
"message": "Expand the dropdown",
|
||||
"description": "The ARIA label of the button to expand the mobile dropdown navbar item"
|
||||
},
|
||||
"theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": {
|
||||
"message": "Collapse the dropdown",
|
||||
"description": "The ARIA label of the button to collapse the mobile dropdown navbar item"
|
||||
},
|
||||
"theme.docs.sidebar.expandButtonTitle": {
|
||||
"message": "Expand sidebar",
|
||||
"description": "The ARIA label and title attribute for expand button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": {
|
||||
"message": "Expand sidebar",
|
||||
"description": "The ARIA label and title attribute for expand button of doc sidebar"
|
||||
},
|
||||
"theme.blog.post.plurals": {
|
||||
"message": "One post|{count} posts",
|
||||
"description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.blog.tagTitle": {
|
||||
"message": "{nPosts} tagged with \"{tagName}\"",
|
||||
"description": "The title of the page for a blog tag"
|
||||
},
|
||||
"theme.blog.author.pageTitle": {
|
||||
"message": "{authorName} - {nPosts}",
|
||||
"description": "The title of the page for a blog author"
|
||||
},
|
||||
"theme.blog.authorsList.pageTitle": {
|
||||
"message": "Authors",
|
||||
"description": "The title of the authors page"
|
||||
},
|
||||
"theme.blog.authorsList.viewAll": {
|
||||
"message": "View all authors",
|
||||
"description": "The label of the link targeting the blog authors page"
|
||||
},
|
||||
"theme.blog.author.noPosts": {
|
||||
"message": "This author has not written any posts yet.",
|
||||
"description": "The text for authors with 0 blog post"
|
||||
},
|
||||
"theme.contentVisibility.unlistedBanner.title": {
|
||||
"message": "Unlisted page",
|
||||
"description": "The unlisted content banner title"
|
||||
},
|
||||
"theme.contentVisibility.unlistedBanner.message": {
|
||||
"message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||
"description": "The unlisted content banner message"
|
||||
},
|
||||
"theme.contentVisibility.draftBanner.title": {
|
||||
"message": "Draft page",
|
||||
"description": "The draft content banner title"
|
||||
},
|
||||
"theme.contentVisibility.draftBanner.message": {
|
||||
"message": "This page is a draft. It will only be visible in dev and be excluded from the production build.",
|
||||
"description": "The draft content banner message"
|
||||
},
|
||||
"theme.ErrorPageContent.tryAgain": {
|
||||
"message": "Try again",
|
||||
"description": "The label of the button to try again rendering when the React error boundary captures an error"
|
||||
},
|
||||
"theme.common.skipToMainContent": {
|
||||
"message": "Skip to main content",
|
||||
"description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation"
|
||||
},
|
||||
"theme.tags.tagsPageTitle": {
|
||||
"message": "Tags",
|
||||
"description": "The title of the tag list page"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"title": {
|
||||
"message": "Blog",
|
||||
"description": "The title for the blog used in SEO"
|
||||
},
|
||||
"description": {
|
||||
"message": "Blog",
|
||||
"description": "The description for the blog used in SEO"
|
||||
},
|
||||
"sidebar.title": {
|
||||
"message": "Recent posts",
|
||||
"description": "The label for the left sidebar"
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
{
|
||||
"version.label": {
|
||||
"message": "Next",
|
||||
"description": "The label for version current"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server": {
|
||||
"message": "Server",
|
||||
"description": "The label for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server.link.generated-index.title": {
|
||||
"message": "Claude Code Router Server",
|
||||
"description": "The generated-index page title for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server.link.generated-index.description": {
|
||||
"message": "Deploy and manage Claude Code Router server",
|
||||
"description": "The generated-index page description for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference": {
|
||||
"message": "API Reference",
|
||||
"description": "The label for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference.link.generated-index.title": {
|
||||
"message": "API Reference",
|
||||
"description": "The generated-index page title for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference.link.generated-index.description": {
|
||||
"message": "Server API documentation",
|
||||
"description": "The generated-index page description for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.server-configuration-category": {
|
||||
"message": "Configuration",
|
||||
"description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.server-configuration-category.link.generated-index.title": {
|
||||
"message": "Server Configuration",
|
||||
"description": "The generated-index page title for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.server-configuration-category.link.generated-index.description": {
|
||||
"message": "Server configuration guide",
|
||||
"description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced": {
|
||||
"message": "Advanced",
|
||||
"description": "The label for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced.link.generated-index.title": {
|
||||
"message": "Advanced Topics",
|
||||
"description": "The generated-index page title for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced.link.generated-index.description": {
|
||||
"message": "Advanced features and customization",
|
||||
"description": "The generated-index page description for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI": {
|
||||
"message": "CLI",
|
||||
"description": "The label for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI.link.generated-index.title": {
|
||||
"message": "Claude Code Router CLI",
|
||||
"description": "The generated-index page title for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI.link.generated-index.description": {
|
||||
"message": "Command-line tool usage guide",
|
||||
"description": "The generated-index page description for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands": {
|
||||
"message": "Commands",
|
||||
"description": "The label for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands.link.generated-index.title": {
|
||||
"message": "CLI Commands",
|
||||
"description": "The generated-index page title for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands.link.generated-index.description": {
|
||||
"message": "Complete command reference",
|
||||
"description": "The generated-index page description for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.cli-configuration-category": {
|
||||
"message": "Configuration",
|
||||
"description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.cli-configuration-category.link.generated-index.title": {
|
||||
"message": "CLI Configuration",
|
||||
"description": "The generated-index page title for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.cli-configuration-category.link.generated-index.description": {
|
||||
"message": "CLI configuration guide",
|
||||
"description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"link.title.Docs": {
|
||||
"message": "Docs",
|
||||
"description": "The title of the footer links column with title=Docs in the footer"
|
||||
},
|
||||
"link.title.Community": {
|
||||
"message": "Community",
|
||||
"description": "The title of the footer links column with title=Community in the footer"
|
||||
},
|
||||
"link.title.More": {
|
||||
"message": "More",
|
||||
"description": "The title of the footer links column with title=More in the footer"
|
||||
},
|
||||
"link.item.label.Tutorial": {
|
||||
"message": "Tutorial",
|
||||
"description": "The label of footer link with label=Tutorial linking to /docs/intro"
|
||||
},
|
||||
"link.item.label.GitHub": {
|
||||
"message": "GitHub",
|
||||
"description": "The label of footer link with label=GitHub linking to https://github.com/musistudio/claude-code-router"
|
||||
},
|
||||
"link.item.label.Blog": {
|
||||
"message": "Blog",
|
||||
"description": "The label of footer link with label=Blog linking to /blog"
|
||||
},
|
||||
"copyright": {
|
||||
"message": "Copyright © 2026 Claude Code Router. Built with Docusaurus.",
|
||||
"description": "The footer copyright"
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"title": {
|
||||
"message": "Claude Code Router",
|
||||
"description": "The title in the navbar"
|
||||
},
|
||||
"logo.alt": {
|
||||
"message": "Claude Code Router Logo",
|
||||
"description": "The alt text of navbar logo"
|
||||
},
|
||||
"item.label.Documentation": {
|
||||
"message": "Documentation",
|
||||
"description": "Navbar item with label Documentation"
|
||||
},
|
||||
"item.label.Blog": {
|
||||
"message": "Blog",
|
||||
"description": "Navbar item with label Blog"
|
||||
},
|
||||
"item.label.GitHub": {
|
||||
"message": "GitHub",
|
||||
"description": "Navbar item with label GitHub"
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
{
|
||||
"theme.ErrorPageContent.title": {
|
||||
"message": "页面已崩溃。",
|
||||
"description": "The title of the fallback page when the page crashed"
|
||||
},
|
||||
"theme.BackToTopButton.buttonAriaLabel": {
|
||||
"message": "回到顶部",
|
||||
"description": "The ARIA label for the back to top button"
|
||||
},
|
||||
"theme.blog.archive.title": {
|
||||
"message": "历史博文",
|
||||
"description": "The page & hero title of the blog archive page"
|
||||
},
|
||||
"theme.blog.archive.description": {
|
||||
"message": "历史博文",
|
||||
"description": "The page & hero description of the blog archive page"
|
||||
},
|
||||
"theme.blog.paginator.navAriaLabel": {
|
||||
"message": "博文列表分页导航",
|
||||
"description": "The ARIA label for the blog pagination"
|
||||
},
|
||||
"theme.blog.paginator.newerEntries": {
|
||||
"message": "较新的博文",
|
||||
"description": "The label used to navigate to the newer blog posts page (previous page)"
|
||||
},
|
||||
"theme.blog.paginator.olderEntries": {
|
||||
"message": "较旧的博文",
|
||||
"description": "The label used to navigate to the older blog posts page (next page)"
|
||||
},
|
||||
"theme.blog.post.paginator.navAriaLabel": {
|
||||
"message": "博文分页导航",
|
||||
"description": "The ARIA label for the blog posts pagination"
|
||||
},
|
||||
"theme.blog.post.paginator.newerPost": {
|
||||
"message": "较新一篇",
|
||||
"description": "The blog post button label to navigate to the newer/previous post"
|
||||
},
|
||||
"theme.blog.post.paginator.olderPost": {
|
||||
"message": "较旧一篇",
|
||||
"description": "The blog post button label to navigate to the older/next post"
|
||||
},
|
||||
"theme.tags.tagsPageLink": {
|
||||
"message": "查看所有标签",
|
||||
"description": "The label of the link targeting the tag list page"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel.mode.system": {
|
||||
"message": "system mode",
|
||||
"description": "The name for the system color mode"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel.mode.light": {
|
||||
"message": "浅色模式",
|
||||
"description": "The name for the light color mode"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel.mode.dark": {
|
||||
"message": "暗黑模式",
|
||||
"description": "The name for the dark color mode"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel": {
|
||||
"message": "切换浅色/暗黑模式(当前为{mode})",
|
||||
"description": "The ARIA label for the color mode toggle"
|
||||
},
|
||||
"theme.docs.breadcrumbs.navAriaLabel": {
|
||||
"message": "页面路径",
|
||||
"description": "The ARIA label for the breadcrumbs"
|
||||
},
|
||||
"theme.docs.DocCard.categoryDescription.plurals": {
|
||||
"message": "{count} 个项目",
|
||||
"description": "The default description for a category card in the generated index about how many items this category includes"
|
||||
},
|
||||
"theme.docs.paginator.navAriaLabel": {
|
||||
"message": "文件选项卡",
|
||||
"description": "The ARIA label for the docs pagination"
|
||||
},
|
||||
"theme.docs.paginator.previous": {
|
||||
"message": "上一页",
|
||||
"description": "The label used to navigate to the previous doc"
|
||||
},
|
||||
"theme.docs.paginator.next": {
|
||||
"message": "下一页",
|
||||
"description": "The label used to navigate to the next doc"
|
||||
},
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": {
|
||||
"message": "{count} 篇文档带有标签",
|
||||
"description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.docs.tagDocListPageTitle": {
|
||||
"message": "{nDocsTagged}「{tagName}」",
|
||||
"description": "The title of the page for a docs tag"
|
||||
},
|
||||
"theme.docs.versionBadge.label": {
|
||||
"message": "版本:{versionLabel}"
|
||||
},
|
||||
"theme.docs.versions.unreleasedVersionLabel": {
|
||||
"message": "此为 {siteTitle} {versionLabel} 版尚未发行的文档。",
|
||||
"description": "The label used to tell the user that he's browsing an unreleased doc version"
|
||||
},
|
||||
"theme.docs.versions.unmaintainedVersionLabel": {
|
||||
"message": "此为 {siteTitle} {versionLabel} 版的文档,现已不再积极维护。",
|
||||
"description": "The label used to tell the user that he's browsing an unmaintained doc version"
|
||||
},
|
||||
"theme.docs.versions.latestVersionSuggestionLabel": {
|
||||
"message": "最新的文档请参阅 {latestVersionLink} ({versionLabel})。",
|
||||
"description": "The label used to tell the user to check the latest version"
|
||||
},
|
||||
"theme.docs.versions.latestVersionLinkLabel": {
|
||||
"message": "最新版本",
|
||||
"description": "The label used for the latest version suggestion link label"
|
||||
},
|
||||
"theme.common.headingLinkTitle": {
|
||||
"message": "{heading}的直接链接",
|
||||
"description": "Title for link to heading"
|
||||
},
|
||||
"theme.common.editThisPage": {
|
||||
"message": "编辑此页",
|
||||
"description": "The link label to edit the current page"
|
||||
},
|
||||
"theme.lastUpdated.atDate": {
|
||||
"message": "于 {date} ",
|
||||
"description": "The words used to describe on which date a page has been last updated"
|
||||
},
|
||||
"theme.lastUpdated.byUser": {
|
||||
"message": "由 {user} ",
|
||||
"description": "The words used to describe by who the page has been last updated"
|
||||
},
|
||||
"theme.lastUpdated.lastUpdatedAtBy": {
|
||||
"message": "最后{byUser}{atDate}更新",
|
||||
"description": "The sentence used to display when a page has been last updated, and by who"
|
||||
},
|
||||
"theme.NotFound.title": {
|
||||
"message": "找不到页面",
|
||||
"description": "The title of the 404 page"
|
||||
},
|
||||
"theme.navbar.mobileVersionsDropdown.label": {
|
||||
"message": "选择版本",
|
||||
"description": "The label for the navbar versions dropdown on mobile view"
|
||||
},
|
||||
"theme.tags.tagsListLabel": {
|
||||
"message": "标签:",
|
||||
"description": "The label alongside a tag list"
|
||||
},
|
||||
"theme.AnnouncementBar.closeButtonAriaLabel": {
|
||||
"message": "关闭",
|
||||
"description": "The ARIA label for close button of announcement bar"
|
||||
},
|
||||
"theme.admonition.caution": {
|
||||
"message": "警告",
|
||||
"description": "The default label used for the Caution admonition (:::caution)"
|
||||
},
|
||||
"theme.admonition.danger": {
|
||||
"message": "危险",
|
||||
"description": "The default label used for the Danger admonition (:::danger)"
|
||||
},
|
||||
"theme.admonition.info": {
|
||||
"message": "信息",
|
||||
"description": "The default label used for the Info admonition (:::info)"
|
||||
},
|
||||
"theme.admonition.note": {
|
||||
"message": "备注",
|
||||
"description": "The default label used for the Note admonition (:::note)"
|
||||
},
|
||||
"theme.admonition.tip": {
|
||||
"message": "提示",
|
||||
"description": "The default label used for the Tip admonition (:::tip)"
|
||||
},
|
||||
"theme.admonition.warning": {
|
||||
"message": "注意",
|
||||
"description": "The default label used for the Warning admonition (:::warning)"
|
||||
},
|
||||
"theme.blog.sidebar.navAriaLabel": {
|
||||
"message": "最近博文导航",
|
||||
"description": "The ARIA label for recent posts in the blog sidebar"
|
||||
},
|
||||
"theme.DocSidebarItem.expandCategoryAriaLabel": {
|
||||
"message": "展开侧边栏分类 '{label}'",
|
||||
"description": "The ARIA label to expand the sidebar category"
|
||||
},
|
||||
"theme.DocSidebarItem.collapseCategoryAriaLabel": {
|
||||
"message": "折叠侧边栏分类 '{label}'",
|
||||
"description": "The ARIA label to collapse the sidebar category"
|
||||
},
|
||||
"theme.IconExternalLink.ariaLabel": {
|
||||
"message": "(opens in new tab)",
|
||||
"description": "The ARIA label for the external link icon"
|
||||
},
|
||||
"theme.NavBar.navAriaLabel": {
|
||||
"message": "主导航",
|
||||
"description": "The ARIA label for the main navigation"
|
||||
},
|
||||
"theme.NotFound.p1": {
|
||||
"message": "我们找不到您要找的页面。",
|
||||
"description": "The first paragraph of the 404 page"
|
||||
},
|
||||
"theme.NotFound.p2": {
|
||||
"message": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。",
|
||||
"description": "The 2nd paragraph of the 404 page"
|
||||
},
|
||||
"theme.navbar.mobileLanguageDropdown.label": {
|
||||
"message": "选择语言",
|
||||
"description": "The label for the mobile language switcher dropdown"
|
||||
},
|
||||
"theme.TOCCollapsible.toggleButtonLabel": {
|
||||
"message": "本页总览",
|
||||
"description": "The label used by the button on the collapsible TOC component"
|
||||
},
|
||||
"theme.blog.post.readMore": {
|
||||
"message": "阅读更多",
|
||||
"description": "The label used in blog post item excerpts to link to full blog posts"
|
||||
},
|
||||
"theme.blog.post.readMoreLabel": {
|
||||
"message": "阅读 {title} 的全文",
|
||||
"description": "The ARIA label for the link to full blog posts from excerpts"
|
||||
},
|
||||
"theme.CodeBlock.copy": {
|
||||
"message": "复制",
|
||||
"description": "The copy button label on code blocks"
|
||||
},
|
||||
"theme.CodeBlock.copied": {
|
||||
"message": "复制成功",
|
||||
"description": "The copied button label on code blocks"
|
||||
},
|
||||
"theme.CodeBlock.copyButtonAriaLabel": {
|
||||
"message": "复制代码到剪贴板",
|
||||
"description": "The ARIA label for copy code blocks button"
|
||||
},
|
||||
"theme.CodeBlock.wordWrapToggle": {
|
||||
"message": "切换自动换行",
|
||||
"description": "The title attribute for toggle word wrapping button of code block lines"
|
||||
},
|
||||
"theme.blog.post.readingTime.plurals": {
|
||||
"message": "阅读需 {readingTime} 分钟",
|
||||
"description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.docs.breadcrumbs.home": {
|
||||
"message": "主页面",
|
||||
"description": "The ARIA label for the home page in the breadcrumbs"
|
||||
},
|
||||
"theme.docs.sidebar.navAriaLabel": {
|
||||
"message": "文档侧边栏",
|
||||
"description": "The ARIA label for the sidebar navigation"
|
||||
},
|
||||
"theme.docs.sidebar.collapseButtonTitle": {
|
||||
"message": "收起侧边栏",
|
||||
"description": "The title attribute for collapse button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.collapseButtonAriaLabel": {
|
||||
"message": "收起侧边栏",
|
||||
"description": "The title attribute for collapse button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.closeSidebarButtonAriaLabel": {
|
||||
"message": "关闭导航栏",
|
||||
"description": "The ARIA label for close button of mobile sidebar"
|
||||
},
|
||||
"theme.navbar.mobileDropdown.collapseButton.expandAriaLabel": {
|
||||
"message": "Expand the dropdown",
|
||||
"description": "The ARIA label of the button to expand the mobile dropdown navbar item"
|
||||
},
|
||||
"theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": {
|
||||
"message": "Collapse the dropdown",
|
||||
"description": "The ARIA label of the button to collapse the mobile dropdown navbar item"
|
||||
},
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": {
|
||||
"message": "切换导航栏",
|
||||
"description": "The ARIA label for hamburger menu button of mobile navigation"
|
||||
},
|
||||
"theme.docs.sidebar.expandButtonTitle": {
|
||||
"message": "展开侧边栏",
|
||||
"description": "The ARIA label and title attribute for expand button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": {
|
||||
"message": "展开侧边栏",
|
||||
"description": "The ARIA label and title attribute for expand button of doc sidebar"
|
||||
},
|
||||
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": {
|
||||
"message": "← 回到主菜单",
|
||||
"description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"
|
||||
},
|
||||
"theme.blog.post.plurals": {
|
||||
"message": "{count} 篇博文",
|
||||
"description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.blog.tagTitle": {
|
||||
"message": "{nPosts} 含有标签「{tagName}」",
|
||||
"description": "The title of the page for a blog tag"
|
||||
},
|
||||
"theme.blog.author.pageTitle": {
|
||||
"message": "{authorName} - {nPosts}",
|
||||
"description": "The title of the page for a blog author"
|
||||
},
|
||||
"theme.blog.authorsList.pageTitle": {
|
||||
"message": "作者",
|
||||
"description": "The title of the authors page"
|
||||
},
|
||||
"theme.blog.authorsList.viewAll": {
|
||||
"message": "查看所有作者",
|
||||
"description": "The label of the link targeting the blog authors page"
|
||||
},
|
||||
"theme.blog.author.noPosts": {
|
||||
"message": "该作者尚未撰写任何文章。",
|
||||
"description": "The text for authors with 0 blog post"
|
||||
},
|
||||
"theme.contentVisibility.unlistedBanner.title": {
|
||||
"message": "未列出页",
|
||||
"description": "The unlisted content banner title"
|
||||
},
|
||||
"theme.contentVisibility.unlistedBanner.message": {
|
||||
"message": "此页面未列出。搜索引擎不会对其索引,只有拥有直接链接的用户才能访问。",
|
||||
"description": "The unlisted content banner message"
|
||||
},
|
||||
"theme.contentVisibility.draftBanner.title": {
|
||||
"message": "草稿页",
|
||||
"description": "The draft content banner title"
|
||||
},
|
||||
"theme.contentVisibility.draftBanner.message": {
|
||||
"message": "此页面是草稿,仅在开发环境中可见,不会包含在正式版本中。",
|
||||
"description": "The draft content banner message"
|
||||
},
|
||||
"theme.ErrorPageContent.tryAgain": {
|
||||
"message": "重试",
|
||||
"description": "The label of the button to try again rendering when the React error boundary captures an error"
|
||||
},
|
||||
"theme.common.skipToMainContent": {
|
||||
"message": "跳到主要内容",
|
||||
"description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation"
|
||||
},
|
||||
"theme.tags.tagsPageTitle": {
|
||||
"message": "标签",
|
||||
"description": "The title of the tag list page"
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
---
|
||||
title: 项目初衷及原理
|
||||
date: 2025-02-25
|
||||
tags: [claude-code, 逆向工程, 教程]
|
||||
---
|
||||
|
||||
# 项目初衷及原理
|
||||
|
||||
早在 Claude Code 发布的第二天(2025-02-25),我就尝试并完成了对该项目的逆向。当时要使用 Claude Code 你需要注册一个 Anthropic 账号,然后申请 waitlist,等待通过后才能使用。但是因为众所周知的原因,Anthropic 屏蔽了中国区的用户,所以通过正常手段我无法使用,通过已知的信息,我发现:
|
||||
|
||||
1. Claude Code 使用 npm 进行安装,所以很大可能其使用 Node.js 进行开发。
|
||||
2. Node.js 调试手段众多,可以简单使用`console.log`获取想要的信息,也可以使用`--inspect`将其接入`Chrome Devtools`,甚至你可以使用`d8`去调试某些加密混淆的代码。
|
||||
|
||||
由于我的目标是让我在没有 Anthropic 账号的情况下使用`Claude Code`,我并不需要获得完整的源代码,只需要将`Claude Code`请求 Anthropic 模型时将其转发到我自定义的接口即可。接下来我就开启了我的逆向过程:
|
||||
|
||||
1. 首先安装`Claude Code`
|
||||
|
||||
```bash
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
```
|
||||
|
||||
2. 安装后该项目被放在了`~/.nvm/versions/node/v20.10.0/lib/node_modules/@anthropic-ai/claude-code`中,因为我使用了`nvm`作为我的 node 版本控制器,当前使用`node-v20.10.0`,所以该路径会因人而异。
|
||||
3. 找到项目路径之后可通过 package.json 分析包入口,内容如下:
|
||||
|
||||
```package.json
|
||||
{
|
||||
"name": "@anthropic-ai/claude-code",
|
||||
"version": "1.0.24",
|
||||
"main": "sdk.mjs",
|
||||
"types": "sdk.d.ts",
|
||||
"bin": {
|
||||
"claude": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "Boris Cherny <boris@anthropic.com>",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"description": "Use Claude, Anthropic's AI assistant, right from your terminal. Claude can understand your codebase, edit files, run terminal commands, and handle entire workflows for you.",
|
||||
"homepage": "https://github.com/anthropics/claude-code",
|
||||
"bugs": {
|
||||
"url": "https://github.com/anthropics/claude-code/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node -e \"if (!process.env.AUTHORIZED) { console.error('ERROR: Direct publishing is not allowed.\\nPlease use the publish-external.sh script to publish this package.'); process.exit(1); }\"",
|
||||
"preinstall": "node scripts/preinstall.js"
|
||||
},
|
||||
"dependencies": {},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "^0.33.5",
|
||||
"@img/sharp-darwin-x64": "^0.33.5",
|
||||
"@img/sharp-linux-arm": "^0.33.5",
|
||||
"@img/sharp-linux-arm64": "^0.33.5",
|
||||
"@img/sharp-linux-x64": "^0.33.5",
|
||||
"@img/sharp-win32-x64": "^0.33.5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中`"claude": "cli.js"`就是我们要找的入口,打开 cli.js,发现代码被压缩混淆过了。没关系,借助`webstorm`的`Formate File`功能可以重新格式化,让代码变得稍微好看一点。就像这样:
|
||||

|
||||
|
||||
现在,你可以通过阅读部分代码来了解`Claude Code`的内容工具原理与提示词。你也可以在关键地方使用`console.log`来获得更多信息,当然,也可以使用`Chrome Devtools`来进行断点调试,使用以下命令启动`Claude Code`:
|
||||
|
||||
```bash
|
||||
NODE_OPTIONS="--inspect-brk=9229" claude
|
||||
```
|
||||
|
||||
该命令会以调试模式启动`Claude Code`,并将调试的端口设置为`9229`。这时候通过 Chrome 访问`chrome://inspect/`即可看到当前的`Claude Code`进程,点击`inspect`即可进行调试。
|
||||

|
||||

|
||||
|
||||
通过搜索关键字符`api.anthropic.com`很容易能找到`Claude Code`用来发请求的地方,根据上下文的查看,很容易发现这里的`baseURL`可以通过环境变量`ANTHROPIC_BASE_URL`进行覆盖,`apiKey`和`authToken`也同理。
|
||||

|
||||
|
||||
到目前为止,我们获得关键信息:
|
||||
|
||||
1. 可以使用环境变量覆盖`Claude Code`的`BaseURL`和`apiKey`的配置
|
||||
|
||||
2. `Claude Code`使用[Anthropic API](https://docs.anthropic.com/en/api/overview)的规范
|
||||
|
||||
所以我们需要:
|
||||
|
||||
1. 实现一个服务用来将`OpenAI API`的规范转换成`Anthropic API`格式。
|
||||
|
||||
2. 启动`Claude Code`之前写入环境变量将`baseURL`指向到该服务。
|
||||
|
||||
于是,`claude-code-router`就诞生了,该项目使用`Express.js`作为 HTTP 服务,实现`/v1/messages`端点,使用`middlewares`处理请求/响应的格式转换以及请求重写功能(可以用来重写 Claude Code 的提示词以针对单个模型进行调优)。
|
||||
在 2 月份由于`DeepSeek`全系列模型对`Function Call`的支持不佳导致无法直接使用`DeepSeek`模型,所以在当时我选择了`qwen-max`模型,一切表现的都很好,但是`qwen-max`不支持`KV Cache`,意味着我要消耗大量的 token,但是却无法获取`Claude Code`原生的体验。
|
||||
所以我又尝试了`Router`模式,即使用一个小模型对任务进行分发,一共分为四个模型:`router`、`tool`、`think`和`coder`,所有的请求先经过一个免费的小模型,由小模型去判断应该是进行思考还是编码还是调用工具,再进行任务的分发,如果是思考和编码任务将会进行循环调用,直到最终使用工具写入或修改文件。但是实践下来发现免费的小模型不足以很好的完成任务的分发,再加上整个 Agnet 的设计存在缺陷,导致并不能很好的驱动`Claude Code`。
|
||||
直到 5 月底,`Claude Code`被正式推出,这时`DeepSeek`全系列模型(R1 于 05-28)均支持`Function Call`,我开始重新设计该项目。在与 AI 的结对编程中我修复了之前的请求和响应转换问题,在某些场景下模型输出 JSON 响应而不是`Function Call`。这次直接使用`DeepSeek-v3`模型,它工作的比我想象中要好:能完成绝大多数工具调用,还支持用步骤规划解决任务,最关键的是`DeepSeek`的价格不到`claude Sonnet 3.5`的十分之一。正式发布的`Claude Code`对 Agent 的组织也不同于测试版,于是在分析了`Claude Code`的请求调用之后,我重新组织了`Router`模式:现在它还是四个模型:默认模型、`background`、`think`和`longContext`。
|
||||
|
||||
- 默认模型作为最终的兜底和日常处理
|
||||
|
||||
- `background`是用来处理一些后台任务,据 Anthropic 官方说主要用`Claude Haiku 3.5`模型去处理一些小任务,如俳句生成和对话摘要,于是我将其路由到了本地的`ollama`服务。
|
||||
|
||||
- `think`模型用于让`Claude Code`进行思考或者在`Plan Mode`下使用,这里我使用的是`DeepSeek-R1`,由于其不支持推理成本控制,所以`Think`和`UltraThink`是一样的逻辑。
|
||||
|
||||
- `longContext`是用于处理长下上文的场景,该项目会对每次请求使用tiktoken实时计算上下文长度,如果上下文大于32K则使用该模型,旨在弥补`DeepSeek`在长上下文处理不佳的情况。
|
||||
|
||||
以上就是该项目的发展历程以及我的一些思考,通过巧妙的使用环境变量覆盖的手段在不修改`Claude Code`源码的情况下完成请求的转发和修改,这就使得在可以得到 Anthropic 更新的同时使用自己的模型,自定义自己的提示词。该项目只是在 Anthropic 封禁中国区用户的情况下使用`Claude Code`并且达到成本和性能平衡的一种手段。如果可以的话,还是官方的Max Plan体验最好。
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
title: GLM-4.6支持思考及思维链回传
|
||||
date: 2025-11-18
|
||||
tags: [glm, 思考, 思维链]
|
||||
---
|
||||
|
||||
# GLM-4.6支持思考及思维链回传
|
||||
|
||||
## GLM-4.6在cluade code中启用思考
|
||||
GLM从4.5开始就对claude code进行了支持,我之前也一直在关注,很多用户反映在claude code中无法启用思考,刚好最近收到了来自智谱的赞助,就着手进行研究。
|
||||
|
||||
首先根据[官方文档](https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E5%AF%B9%E8%AF%9D%E8%A1%A5%E5%85%A8),我们发现`/chat/completions`端点是默认启用思考的,但是是由模型判断是否需要进行思考
|
||||
|
||||
```
|
||||
thinking object
|
||||
仅 GLM-4.5 及以上模型支持此参数配置. 控制大模型是否开启思维链。
|
||||
|
||||
thinking.type enum<string> default:enabled
|
||||
是否开启思维链(当开启后 GLM-4.6 GLM-4.5 为模型自动判断是否思考,GLM-4.5V 为强制思考), 默认: enabled.
|
||||
|
||||
Available options: enabled, disabled
|
||||
```
|
||||
|
||||
在claude code本身大量的提示词干扰下,会严重阻碍GLM模型本身的判断机制,导致模型很少进行思考。所以我们需要对模型进行引导,让模型认为需要进行思考。但是`claude-code-router`作为proxy,能做的只能是修改提示词/参数。
|
||||
|
||||
在最开始,我尝试直接删除claude code的系统提示词,模型确实进行了思考,但是这样就无法驱动claude code。所以我们需要进行提示词注入,明确告知模型需要进行思考。
|
||||
|
||||
```javascript
|
||||
// transformer.ts
|
||||
import { UnifiedChatRequest } from "../types/llm";
|
||||
import { Transformer } from "../types/transformer";
|
||||
|
||||
export class ForceReasoningTransformer implements Transformer {
|
||||
name = "forcereasoning";
|
||||
|
||||
async transformRequestIn(
|
||||
request: UnifiedChatRequest
|
||||
): Promise<UnifiedChatRequest> {
|
||||
const systemMessage = request.messages.find(
|
||||
(item) => item.role === "system"
|
||||
);
|
||||
if (Array.isArray(systemMessage?.content)) {
|
||||
systemMessage.content.push({
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
});
|
||||
}
|
||||
const lastMessage = request.messages[request.messages.length - 1];
|
||||
if (lastMessage.role === "user" && Array.isArray(lastMessage.content)) {
|
||||
lastMessage.content.push({
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
});
|
||||
}
|
||||
if (lastMessage.role === "tool") {
|
||||
request.messages.push({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "You are an expert reasoning model. \nAlways think step by step before answering. Even if the problem seems simple, always write down your reasoning process explicitly. \nNever skip your chain of thought. \nUse the following output format:\n<reasoning_content>(Write your full detailed thinking here.)</reasoning_content>\n\nWrite your final conclusion here.",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return request;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
至于为什么让模型将思考内容放入reasoning_content标签而不是think标签有两个原因:
|
||||
1. 直接使用think标签不能很好的激活思考,猜测是训练模型时以think标签作为数据集进行训练。
|
||||
2. 如果使用think标签,模型的推理内容会被拆分到单独的字段,这就涉及到我们接下来要说的思维链回传问题。
|
||||
|
||||
|
||||
## 思维链回传
|
||||
|
||||
近期Minimax发布了Minimax-m2,与此同时,他们还发布了一篇[文章](https://www.minimaxi.com/news/why-is-interleaved-thinking-important-for-m2)介绍思维链回传。但是太阳底下无新鲜事,刚好借此来剖析一下。
|
||||
1. 我们首先来看一下为什么需要回传思维链?
|
||||
Minimax在文章中说的是Chat Completion API不支持在后续请求中传递推理内容。我们知道ChatGPT是最先支持推理的,但是OpenAI最初没有开放思维链给用户,所以对于Chat Completion API来讲并不需要支持思维链相关的东西。就连CoT的字段也是DeepSeek率先在Chat Completion API中加入的。
|
||||
|
||||
2. 我们真的需要这些字段吗?
|
||||
如果没有这些字段会怎么样?会影响到模型的思考吗?可以查看一下[sglang的源码](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/parser/reasoning_parser.py)发现思维链的信息原本就会在消息中按照特定的标记进行输出,假如我们不对其进行拆分,正常情况下在下轮对话中会自然包含这些信息。所以需要思维链回传的原因就是我们对模型的思维链内容进行拆分。
|
||||
|
||||
我用上面不到40行的代码完成了对GLM-4.5/6支持思考以及思维链回传的简单探索(单纯是因为没时间做拆分,完全可以在transformer中响应时先做拆分,请求时再进行合并,这样对cc前端的展示适配会更好),如果你有什么更好的想法也欢迎与我联系。
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
---
|
||||
title: 或许我们能在 Router 中做更多事情
|
||||
date: 2025-11-18
|
||||
tags: [router, transformer, deepseek]
|
||||
---
|
||||
|
||||
# 或许我们能在 Router 中做更多事情
|
||||
|
||||
自从`claude-code-router`发布以来,我收到了很多用户的反馈,至今还有不少的 issues 未处理。其中大多都是关于不同的供应商的支持和`deepseek`模型调用工具不积极的问题。
|
||||
之前开发这个项目主要是为了我自己能以较低成本使用上`claude code`,所以一开始的设计并没有考虑到多供应商的情况。在实际的排查问题中,我发现尽管市面上所有的供应商几乎都宣称兼容`OpenAI`格式调用,即通过`/chat/compeletions`接口调用,但是其中的细节差异非常多。例如:
|
||||
|
||||
1. Gemini 的工具参数类型是 string 时,`format`参数只支持`date`和`date-time`,并且没有工具调用 ID。
|
||||
|
||||
2. OpenRouter 需要使用`cache_control`进行缓存。
|
||||
|
||||
3. DeepSeek 官方 API 的 `max_output` 为 8192,而火山引擎的会更大。
|
||||
|
||||
除了这些问题之外,还有一些其他的小的供应商,他们或多或少参数都有点问题。于是,我打算开发一个新的项目[musistudio/llms](https://github.com/musistudio/llms)来处理这种不同服务商的兼容问题。该项目使用 OpenAI 格式为基础的通用格式,提供了一个`Transformer`接口,该接口用于处理转换请求和响应。当我们给不同的服务商都实现了`Transformer`后,我们可以实现不同服务商的混合调用。比如我在`AnthropicTransformer`中实现了`Anthropic` <-> `OpenAI`格式的互相转换,并监听了`/v1/messages`端点,在`GeminiTransformer`中实现了`Gemini` <-> `OpenAI`格式的互相转换,并监听了`/v1beta/models/:modelAndAction`端点,当他们的请求和响应都被转换成一个通用格式的时候,就可以实现他们的互相调用。
|
||||
|
||||
```
|
||||
AnthropicRequest -> AnthropicTransformer -> OpenAIRequest -> GeminiTransformer -> GeminiRequest -> GeminiServer
|
||||
```
|
||||
|
||||
```
|
||||
GeminiReseponse -> GeminiTransformer -> OpenAIResponse -> AnthropicTransformer -> AnthropicResponse
|
||||
```
|
||||
|
||||
虽然使用中间层抹平差异可能会带来一些性能问题,但是该项目最初的目的是为了让`claude-code-router`支持不同的供应商。
|
||||
|
||||
至于`deepseek`模型调用工具不积极的问题,我发现这是由于`deepseek`在长上下文中的指令遵循不佳导致的。现象就是刚开始模型会主动调用工具,但是在经过几轮对话后模型只会返回文本。一开始的解决方案是通过注入一个系统提示词告知模型需要积极去使用工具以解决用户的问题,但是后面测试发现在长上下文中模型会遗忘该指令。
|
||||
查看`deepseek`文档后发现模型支持`tool_choice`参数,可以强制让模型最少调用 1 个工具,我尝试将该值设置为`required`,发现模型调用工具的积极性大大增加,现在我们只需要在合适的时候取消这个参数即可。借助[musistudio/llms](https://github.com/musistudio/llms)的`Transformer`可以让我们在发送请求前和收到响应后做点什么,所以我参考`claude code`的`Plan Mode`,实现了一个使用与`deepseek`的`Tool Mode`
|
||||
|
||||
```typescript
|
||||
export class TooluseTransformer implements Transformer {
|
||||
name = "tooluse";
|
||||
|
||||
transformRequestIn(request: UnifiedChatRequest): UnifiedChatRequest {
|
||||
if (request.tools?.length) {
|
||||
request.messages.push({
|
||||
role: "system",
|
||||
content: `<system-reminder>Tool mode is active. The user expects you to proactively execute the most suitable tool to help complete the task.
|
||||
Before invoking a tool, you must carefully evaluate whether it matches the current task. If no available tool is appropriate for the task, you MUST call the \`ExitTool\` to exit tool mode — this is the only valid way to terminate tool mode.
|
||||
Always prioritize completing the user's task effectively and efficiently by using tools whenever appropriate.</system-reminder>`,
|
||||
});
|
||||
request.tool_choice = "required";
|
||||
request.tools.unshift({
|
||||
type: "function",
|
||||
function: {
|
||||
name: "ExitTool",
|
||||
description: `Use this tool when you are in tool mode and have completed the task. This is the only valid way to exit tool mode.
|
||||
IMPORTANT: Before using this tool, ensure that none of the available tools are applicable to the current task. You must evaluate all available options — only if no suitable tool can help you complete the task should you use ExitTool to terminate tool mode.
|
||||
Examples:
|
||||
1. Task: "Use a tool to summarize this document" — Do not use ExitTool if a summarization tool is available.
|
||||
2. Task: "What's the weather today?" — If no tool is available to answer, use ExitTool after reasoning that none can fulfill the task.`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
response: {
|
||||
type: "string",
|
||||
description:
|
||||
"Your response will be forwarded to the user exactly as returned — the tool will not modify or post-process it in any way.",
|
||||
},
|
||||
},
|
||||
required: ["response"],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
async transformResponseOut(response: Response): Promise<Response> {
|
||||
if (response.headers.get("Content-Type")?.includes("application/json")) {
|
||||
const jsonResponse = await response.json();
|
||||
if (
|
||||
jsonResponse?.choices[0]?.message.tool_calls?.length &&
|
||||
jsonResponse?.choices[0]?.message.tool_calls[0]?.function?.name ===
|
||||
"ExitTool"
|
||||
) {
|
||||
const toolArguments = JSON.parse(toolCall.function.arguments || "{}");
|
||||
jsonResponse.choices[0].message.content = toolArguments.response || "";
|
||||
delete jsonResponse.choices[0].message.tool_calls;
|
||||
}
|
||||
|
||||
// Handle non-streaming response if needed
|
||||
return new Response(JSON.stringify(jsonResponse), {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
});
|
||||
} else if (response.headers.get("Content-Type")?.includes("stream")) {
|
||||
// ...
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该工具将始终让模型至少调用一个工具,如果没有合适的工具或者任务已完成可以调用`ExitTool`来退出工具模式,因为是依靠`tool_choice`参数实现的,所以仅适用于支持该参数的模型。经过测试,该工具能显著增加`deepseek`的工具调用次数,弊端是可能会有跟任务无关或者没有必要的工具调用导致增加任务执行事件和消耗的 `token` 数。
|
||||
|
||||
这次更新仅仅是在 Router 中实现一个`agent`的一次小探索,或许还能做更多其他有趣的事也说不定...
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"title": {
|
||||
"message": "Blog",
|
||||
"description": "The title for the blog used in SEO"
|
||||
},
|
||||
"description": {
|
||||
"message": "Blog",
|
||||
"description": "The description for the blog used in SEO"
|
||||
},
|
||||
"sidebar.title": {
|
||||
"message": "Recent posts",
|
||||
"description": "The label for the left sidebar"
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
---
|
||||
id: advanced/custom-router
|
||||
title: 自定义路由器
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 自定义路由器
|
||||
|
||||
使用 JavaScript 编写自己的路由逻辑。
|
||||
|
||||
## 创建自定义路由器
|
||||
|
||||
创建一个导出路由函数的 JavaScript 文件:
|
||||
|
||||
```javascript
|
||||
// custom-router.js
|
||||
module.exports = async function(req, config) {
|
||||
// 获取用户消息
|
||||
const userMessage = req.body.messages.find(m => m.role === 'user')?.content;
|
||||
|
||||
// 自定义逻辑
|
||||
if (userMessage && userMessage.includes('解释代码')) {
|
||||
return 'openrouter,anthropic/claude-3.5-sonnet';
|
||||
}
|
||||
|
||||
// 返回 null 以使用默认路由
|
||||
return null;
|
||||
};
|
||||
```
|
||||
|
||||
## 参数说明
|
||||
|
||||
路由函数接收以下参数:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `req` | object | 来自 Claude Code 的请求对象,包含请求体 |
|
||||
| `config` | object | 应用程序的配置对象 |
|
||||
|
||||
## 配置
|
||||
|
||||
在 `config.json` 中设置 `CUSTOM_ROUTER_PATH` 以使用您的自定义路由器:
|
||||
|
||||
```json
|
||||
{
|
||||
"CUSTOM_ROUTER_PATH": "/path/to/custom-router.js"
|
||||
}
|
||||
```
|
||||
|
||||
## 返回格式
|
||||
|
||||
路由函数应返回以下格式的字符串:
|
||||
|
||||
```
|
||||
{provider-name},{model-name}
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```
|
||||
deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
如果返回 `null`,则回退到默认路由配置。
|
||||
|
||||
## 错误处理
|
||||
|
||||
如果路由函数抛出错误或返回无效格式,路由器将回退到默认路由配置。
|
||||
|
||||
## 示例:基于时间的路由
|
||||
|
||||
```javascript
|
||||
module.exports = async function(req, config) {
|
||||
const hour = new Date().getHours();
|
||||
|
||||
// 工作时间使用更快的模型
|
||||
if (hour >= 9 && hour <= 18) {
|
||||
return 'groq,llama-3.3-70b-versatile';
|
||||
}
|
||||
|
||||
// 非工作时间使用更强大的模型
|
||||
return 'deepseek,deepseek-chat';
|
||||
};
|
||||
```
|
||||
|
||||
## 示例:成本优化
|
||||
|
||||
```javascript
|
||||
module.exports = async function(req, config) {
|
||||
const userMessage = req.body.messages.find(m => m.role === 'user')?.content;
|
||||
|
||||
// 简单任务使用较便宜的模型
|
||||
if (userMessage && userMessage.length < 100) {
|
||||
return 'groq,llama-3.3-70b-versatile';
|
||||
}
|
||||
|
||||
// 复杂任务使用默认模型
|
||||
return null;
|
||||
};
|
||||
```
|
||||
|
||||
## 示例:任务类型路由
|
||||
|
||||
```javascript
|
||||
module.exports = async function(req, config) {
|
||||
const userMessage = req.body.messages.find(m => m.role === 'user')?.content;
|
||||
|
||||
if (!userMessage) return null;
|
||||
|
||||
// 代码相关任务
|
||||
if (userMessage.includes('代码') || userMessage.includes('code')) {
|
||||
return 'deepseek,deepseek-coder';
|
||||
}
|
||||
|
||||
// 解释任务
|
||||
if (userMessage.includes('解释') || userMessage.includes('explain')) {
|
||||
return 'openrouter,anthropic/claude-3.5-sonnet';
|
||||
}
|
||||
|
||||
// 默认
|
||||
return null;
|
||||
};
|
||||
```
|
||||
|
||||
## 测试您的路由器
|
||||
|
||||
通过检查日志来测试您的自定义路由器:
|
||||
|
||||
```bash
|
||||
tail -f ~/.claude-code-router/claude-code-router.log
|
||||
```
|
||||
|
||||
查找路由决策以查看正在选择哪个模型。
|
||||
|
||||
## 子代理路由
|
||||
|
||||
对于子代理内的路由,您必须在子代理提示词的**开头**包含 `<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>` 来指定特定的提供商和模型。
|
||||
|
||||
**示例:**
|
||||
|
||||
```
|
||||
<CCR-SUBAGENT-MODEL>openrouter,anthropic/claude-3.5-sonnet</CCR-SUBAGENT-MODEL>
|
||||
请帮我分析这段代码是否存在潜在的优化空间...
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [Agent](/zh/docs/advanced/agents) - 使用 Agent 扩展功能
|
||||
- [预设](/zh/docs/advanced/presets) - 使用预定义配置
|
||||
@@ -1,673 +0,0 @@
|
||||
---
|
||||
id: advanced/presets
|
||||
title: 预设配置
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# 预设配置
|
||||
|
||||
使用预定义配置进行快速设置。
|
||||
|
||||
## 什么是预设?
|
||||
|
||||
预设是预配置的设置,包括针对特定用例优化的提供商配置、路由规则和转换器。
|
||||
|
||||
## 使用预设
|
||||
|
||||
### CLI 方式(命令行)
|
||||
|
||||
CLI 方式适合开发者通过命令行快速操作。
|
||||
|
||||
#### 安装预设
|
||||
|
||||
**从本地目录安装:**
|
||||
|
||||
```bash
|
||||
ccr preset install /path/to/preset-directory
|
||||
```
|
||||
|
||||
**重新配置已安装的预设:**
|
||||
|
||||
```bash
|
||||
ccr preset install my-preset
|
||||
```
|
||||
|
||||
:::note 注意
|
||||
CLI 方式**不支持**从 URL 直接安装预设。如需从 GitHub 安装,请先克隆到本地或使用 Web UI。
|
||||
:::
|
||||
|
||||
#### 使用预设
|
||||
|
||||
安装预设后,可以使用预设名称启动 Claude Code:
|
||||
|
||||
```bash
|
||||
# 使用指定预设启动
|
||||
ccr my-preset "your prompt"
|
||||
|
||||
# 后台任务使用预设
|
||||
ccr my-preset --background "your prompt"
|
||||
```
|
||||
|
||||
预设会:
|
||||
- 自动加载预配置的 Provider
|
||||
- 应用预设的路由规则
|
||||
- 使用预设中配置的 transformer
|
||||
|
||||
#### 列出所有预设
|
||||
|
||||
```bash
|
||||
ccr preset list
|
||||
```
|
||||
|
||||
此命令将显示所有已安装的预设及其名称、版本和描述。
|
||||
|
||||
#### 查看预设信息
|
||||
|
||||
```bash
|
||||
ccr preset info my-preset
|
||||
```
|
||||
|
||||
#### 删除预设
|
||||
|
||||
```bash
|
||||
ccr preset delete my-preset
|
||||
```
|
||||
|
||||
### Web UI 方式
|
||||
|
||||
Web UI 提供更友好的可视化界面,支持更多安装方式。
|
||||
|
||||
#### 访问 Web UI
|
||||
|
||||
```bash
|
||||
ccr ui
|
||||
```
|
||||
|
||||
然后在浏览器中打开 `http://localhost:3000`
|
||||
|
||||
#### 从 GitHub 仓库安装
|
||||
|
||||
1. 点击"预设商城"按钮
|
||||
2. 在预设列表中选择要安装的预设
|
||||
3. 点击"安装"按钮
|
||||
|
||||
或手动输入 GitHub 仓库地址:
|
||||
|
||||
```
|
||||
格式:https://github.com/username/repo
|
||||
示例:https://github.com/example/ccr-presets
|
||||
```
|
||||
|
||||
#### 重新配置预设
|
||||
|
||||
1. 在预设列表中点击"查看详情"按钮
|
||||
2. 在详情页面中修改配置项
|
||||
3. 点击"应用"保存配置
|
||||
|
||||
#### 管理预设
|
||||
|
||||
- **查看**:点击预设右侧的信息图标
|
||||
- **删除**:点击预设右侧的删除图标
|
||||
|
||||
## 创建自定义预设
|
||||
|
||||
### 预设目录结构
|
||||
|
||||
预设以目录形式存储,每个预设包含以下结构:
|
||||
|
||||
```
|
||||
~/.claude-code-router/presets/<preset-name>/
|
||||
├── manifest.json # 必填:预设配置文件
|
||||
├── transformers/ # 可选:自定义转换器
|
||||
│ └── custom-transformer.js
|
||||
├── scripts/ # 可选:自定义脚本
|
||||
│ └── status.js
|
||||
└── README.md # 可选:说明文档
|
||||
```
|
||||
|
||||
### 动态配置系统
|
||||
|
||||
CCR 引入了强大的动态配置系统,支持:
|
||||
|
||||
- **多种输入类型**:选择器、多选、确认框、文本输入、数字输入等
|
||||
- **条件逻辑**:根据用户输入动态显示/隐藏配置项
|
||||
- **变量引用**:配置项之间可以互相引用
|
||||
- **动态选项**:选项列表可以从预设配置或用户输入中动态生成
|
||||
|
||||
#### Schema 字段类型
|
||||
|
||||
| 类型 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `password` | 密码输入(隐藏显示) | API Key |
|
||||
| `input` | 单行文本输入 | Base URL |
|
||||
| `number` | 数字输入 | 最大Token数 |
|
||||
| `select` | 单选下拉框 | 选择Provider |
|
||||
| `multiselect` | 多选框 | 启用功能 |
|
||||
| `confirm` | 确认框 | 是否使用代理 |
|
||||
| `editor` | 多行文本编辑器 | 自定义配置 |
|
||||
|
||||
#### 条件运算符
|
||||
|
||||
| 运算符 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| `eq` | 等于 | `{"field": "provider", "operator": "eq", "value": "openai"}` |
|
||||
| `ne` | 不等于 | `{"field": "advanced", "operator": "ne", "value": true}` |
|
||||
| `in` | 包含于 | `{"field": "feature", "operator": "in", "value": ["a", "b"]}` |
|
||||
| `nin` | 不包含于 | `{"field": "type", "operator": "nin", "value": ["x", "y"]}` |
|
||||
| `exists` | 字段存在 | `{"field": "apiKey", "operator": "exists"}` |
|
||||
| `gt/lt/gte/lte` | 大于/小于/大于等于/小于等于 | 用于数字比较 |
|
||||
|
||||
#### 动态选项类型
|
||||
|
||||
##### static - 静态选项
|
||||
```json
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{"label": "选项1", "value": "value1"},
|
||||
{"label": "选项2", "value": "value2"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### providers - 从 Providers 配置提取
|
||||
```json
|
||||
"options": {
|
||||
"type": "providers"
|
||||
}
|
||||
```
|
||||
自动从 `Providers` 数组中提取 name 作为选项。
|
||||
|
||||
##### models - 从指定 Provider 的 models 提取
|
||||
```json
|
||||
"options": {
|
||||
"type": "models",
|
||||
"providerField": "{{selectedProvider}}"
|
||||
}
|
||||
```
|
||||
根据用户选择的 Provider,动态显示该 Provider 的 models。
|
||||
|
||||
#### 模板变量
|
||||
|
||||
使用 `{{变量名}}` 语法在 template 中引用用户输入:
|
||||
|
||||
```json
|
||||
"template": {
|
||||
"Providers": [
|
||||
{
|
||||
"name": "{{providerName}}",
|
||||
"api_key": "{{apiKey}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 配置映射
|
||||
|
||||
对于复杂的配置需求,使用 `configMappings` 精确控制值的位置:
|
||||
|
||||
```json
|
||||
"configMappings": [
|
||||
{
|
||||
"target": "Providers[0].api_key",
|
||||
"value": "{{apiKey}}"
|
||||
},
|
||||
{
|
||||
"target": "PROXY_URL",
|
||||
"value": "{{proxyUrl}}",
|
||||
"when": {
|
||||
"field": "useProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### 完整示例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "multi-provider-example",
|
||||
"version": "1.0.0",
|
||||
"description": "多Provider配置示例 - 支持OpenAI和DeepSeek切换",
|
||||
"author": "CCR Team",
|
||||
"keywords": ["openai", "deepseek", "multi-provider"],
|
||||
"ccrVersion": "2.0.0",
|
||||
"schema": [
|
||||
{
|
||||
"id": "primaryProvider",
|
||||
"type": "select",
|
||||
"label": "主要Provider",
|
||||
"prompt": "选择您主要使用的LLM提供商",
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{
|
||||
"label": "OpenAI",
|
||||
"value": "openai",
|
||||
"description": "使用OpenAI的GPT模型"
|
||||
},
|
||||
{
|
||||
"label": "DeepSeek",
|
||||
"value": "deepseek",
|
||||
"description": "使用DeepSeek的高性价比模型"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": true,
|
||||
"defaultValue": "openai"
|
||||
},
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "API Key",
|
||||
"prompt": "请输入您的API Key",
|
||||
"placeholder": "sk-...",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "defaultModel",
|
||||
"type": "select",
|
||||
"label": "默认模型",
|
||||
"prompt": "选择默认使用的模型",
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{"label": "GPT-4o", "value": "gpt-4o"},
|
||||
{"label": "GPT-4o-mini", "value": "gpt-4o-mini"}
|
||||
]
|
||||
},
|
||||
"required": true,
|
||||
"defaultValue": "gpt-4o",
|
||||
"when": {
|
||||
"field": "primaryProvider",
|
||||
"operator": "eq",
|
||||
"value": "openai"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "enableProxy",
|
||||
"type": "confirm",
|
||||
"label": "启用代理",
|
||||
"prompt": "是否通过代理访问API?",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "proxyUrl",
|
||||
"type": "input",
|
||||
"label": "代理地址",
|
||||
"prompt": "输入代理服务器地址",
|
||||
"placeholder": "http://127.0.0.1:7890",
|
||||
"required": true,
|
||||
"when": {
|
||||
"field": "enableProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"template": {
|
||||
"Providers": [
|
||||
{
|
||||
"name": "{{primaryProvider}}",
|
||||
"api_base_url": "https://api.openai.com/v1",
|
||||
"api_key": "{{apiKey}}",
|
||||
"models": ["{{defaultModel}}"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "{{primaryProvider}}/{{defaultModel}}"
|
||||
},
|
||||
"PROXY_URL": "{{proxyUrl}}"
|
||||
},
|
||||
"configMappings": [
|
||||
{
|
||||
"target": "PROXY_URL",
|
||||
"value": "{{proxyUrl}}",
|
||||
"when": {
|
||||
"field": "enableProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### manifest.json 完整字段说明
|
||||
|
||||
`manifest.json` 是预设的核心配置文件,使用 JSON5 格式(支持注释)。
|
||||
|
||||
#### 1. 元数据字段(Metadata)
|
||||
|
||||
这些字段用于描述预设的基本信息:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `name` | string | ✓ | 预设名称(唯一标识符) |
|
||||
| `version` | string | ✓ | 版本号(遵循 semver 规范) |
|
||||
| `description` | string | - | 预设描述 |
|
||||
| `author` | string | - | 作者信息 |
|
||||
| `homepage` | string | - | 项目主页 URL |
|
||||
| `repository` | string | - | 源代码仓库 URL |
|
||||
| `license` | string | - | 许可证类型 |
|
||||
| `keywords` | string[] | - | 关键词标签 |
|
||||
| `ccrVersion` | string | - | 兼容的 CCR 版本 |
|
||||
| `source` | string | - | 预设来源 URL |
|
||||
| `sourceType` | string | - | 来源类型(`local`/`gist`/`registry`) |
|
||||
| `checksum` | string | - | 内容校验和(SHA256) |
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-preset",
|
||||
"version": "1.0.0",
|
||||
"description": "我的自定义预设",
|
||||
"author": "Your Name",
|
||||
"homepage": "https://github.com/yourname/ccr-presets",
|
||||
"repository": "https://github.com/yourname/ccr-presets.git",
|
||||
"license": "MIT",
|
||||
"keywords": ["openai", "production"],
|
||||
"ccrVersion": "2.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 配置字段(Configuration)
|
||||
|
||||
这些字段会直接合并到 CCR 的配置中,所有 `config.json` 支持的字段都可以在这里使用:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `Providers` | array | Provider 配置数组 |
|
||||
| `Router` | object | 路由配置 |
|
||||
| `transformers` | array | 转换器配置 |
|
||||
| `StatusLine` | object | 状态栏配置 |
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"api_base_url": "https://api.openai.com/v1",
|
||||
"api_key": "${OPENAI_API_KEY}",
|
||||
"models": ["gpt-4o", "gpt-4o-mini"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai/gpt-4o",
|
||||
"background": "openai/gpt-4o-mini"
|
||||
},
|
||||
"PORT": 8080
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 动态配置系统字段
|
||||
|
||||
这些字段用于创建可交互的配置模板:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `schema` | array | 配置输入表单定义 |
|
||||
| `template` | object | 配置模板(使用变量引用) |
|
||||
| `configMappings` | array | 配置映射规则 |
|
||||
| `userValues` | object | 用户填写的值(运行时使用) |
|
||||
| `requiredInputs` | array | 必填输入项列表(自动生成) |
|
||||
|
||||
**schema 字段类型:**
|
||||
|
||||
| 类型 | 说明 | 使用场景 |
|
||||
|------|------|----------|
|
||||
| `password` | 密码输入(隐藏) | API Key |
|
||||
| `input` | 单行文本输入 | URL |
|
||||
| `number` | 数字输入 | 端口号 |
|
||||
| `select` | 单选下拉框 | 选择 Provider |
|
||||
| `multiselect` | 多选框 | 启用功能 |
|
||||
| `confirm` | 确认框 | 是否启用 |
|
||||
| `editor` | 多行文本编辑器 | 自定义配置 |
|
||||
|
||||
动态配置示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": [
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "API Key",
|
||||
"prompt": "请输入您的 API Key",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "provider",
|
||||
"type": "select",
|
||||
"label": "Provider",
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{"label": "OpenAI", "value": "openai"},
|
||||
{"label": "DeepSeek", "value": "deepseek"}
|
||||
]
|
||||
},
|
||||
"defaultValue": "openai"
|
||||
}
|
||||
],
|
||||
"template": {
|
||||
"Providers": [
|
||||
{
|
||||
"name": "#{provider}",
|
||||
"api_key": "#{apiKey}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 创建预设示例
|
||||
|
||||
#### 示例 1:简单预设(无动态配置)
|
||||
|
||||
```bash
|
||||
# 创建预设目录
|
||||
mkdir -p ~/.claude-code-router/presets/simple-openai
|
||||
|
||||
# 创建 manifest.json
|
||||
cat > ~/.claude-code-router/presets/simple-openai/manifest.json << 'EOF'
|
||||
{
|
||||
"name": "simple-openai",
|
||||
"version": "1.0.0",
|
||||
"description": "简单的 OpenAI 配置",
|
||||
"author": "Your Name",
|
||||
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"api_base_url": "https://api.openai.com/v1",
|
||||
"api_key": "${OPENAI_API_KEY}",
|
||||
"models": ["gpt-4o", "gpt-4o-mini"]
|
||||
}
|
||||
],
|
||||
|
||||
"Router": {
|
||||
"default": "openai/gpt-4o",
|
||||
"background": "openai/gpt-4o-mini"
|
||||
},
|
||||
|
||||
"requiredInputs": [
|
||||
{
|
||||
"id": "Providers[0].api_key",
|
||||
"prompt": "Enter OpenAI API Key",
|
||||
"placeholder": "OPENAI_API_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# 配置预设(输入 API Key)
|
||||
ccr preset install simple-openai
|
||||
|
||||
# 使用预设
|
||||
ccr simple-openai "your prompt"
|
||||
```
|
||||
|
||||
#### 示例 2:高级预设(动态配置)
|
||||
|
||||
```bash
|
||||
# 创建预设目录
|
||||
mkdir -p ~/.claude-code-router/presets/advanced-config
|
||||
|
||||
# 创建 manifest.json
|
||||
cat > ~/.claude-code-router/presets/advanced-config/manifest.json << 'EOF'
|
||||
{
|
||||
"name": "advanced-config",
|
||||
"version": "1.0.0",
|
||||
"description": "支持多 Provider 选择的高级配置",
|
||||
"author": "Your Name",
|
||||
"keywords": ["openai", "deepseek", "multi-provider"],
|
||||
|
||||
"schema": [
|
||||
{
|
||||
"id": "provider",
|
||||
"type": "select",
|
||||
"label": "选择 Provider",
|
||||
"prompt": "选择您主要使用的 LLM 提供商",
|
||||
"options": {
|
||||
"type": "static",
|
||||
"options": [
|
||||
{
|
||||
"label": "OpenAI",
|
||||
"value": "openai",
|
||||
"description": "使用 OpenAI 的 GPT 模型"
|
||||
},
|
||||
{
|
||||
"label": "DeepSeek",
|
||||
"value": "deepseek",
|
||||
"description": "使用 DeepSeek 的高性价比模型"
|
||||
}
|
||||
]
|
||||
},
|
||||
"defaultValue": "openai",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "API Key",
|
||||
"prompt": "请输入您的 API Key",
|
||||
"placeholder": "sk-...",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "enableProxy",
|
||||
"type": "confirm",
|
||||
"label": "启用代理",
|
||||
"prompt": "是否通过代理访问 API?",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "proxyUrl",
|
||||
"type": "input",
|
||||
"label": "代理地址",
|
||||
"prompt": "输入代理服务器地址",
|
||||
"placeholder": "http://127.0.0.1:7890",
|
||||
"required": true,
|
||||
"when": {
|
||||
"field": "enableProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"template": {
|
||||
"Providers": [
|
||||
{
|
||||
"name": "#{provider}",
|
||||
"api_base_url": "#{provider === 'openai' ? 'https://api.openai.com/v1' : 'https://api.deepseek.com'}",
|
||||
"api_key": "#{apiKey}",
|
||||
"models": ["gpt-4o", "gpt-4o-mini"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "#{provider}/gpt-4o",
|
||||
"background": "#{provider}/gpt-4o-mini"
|
||||
}
|
||||
},
|
||||
|
||||
"configMappings": [
|
||||
{
|
||||
"target": "PROXY_URL",
|
||||
"value": "#{proxyUrl}",
|
||||
"when": {
|
||||
"field": "enableProxy",
|
||||
"operator": "eq",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# 配置预设(会提示输入)
|
||||
ccr preset install advanced-config
|
||||
|
||||
# 使用预设
|
||||
ccr advanced-config "your prompt"
|
||||
```
|
||||
|
||||
### 导出当前配置为预设
|
||||
|
||||
如果您已经配置好了 CCR,可以导出当前配置:
|
||||
|
||||
```bash
|
||||
# 导出当前配置
|
||||
ccr preset export my-exported-preset
|
||||
```
|
||||
|
||||
导出时会自动:
|
||||
- 识别敏感字段(如 `api_key`)并替换为环境变量占位符
|
||||
- 生成 `schema` 用于收集用户输入
|
||||
- 生成 `template` 和 `configMappings`
|
||||
|
||||
可选项:
|
||||
|
||||
```bash
|
||||
ccr preset export my-exported-preset \
|
||||
--description "导出的配置" \
|
||||
--author "Your Name" \
|
||||
--tags "production,openai"
|
||||
```
|
||||
|
||||
:::tip 分享预设
|
||||
导出的预设目录可以直接分享给他人。接收者可以:
|
||||
- **CLI 方式**:将目录放到 `~/.claude-code-router/presets/`,然后运行 `ccr preset install 预设名`
|
||||
- **Web UI 方式**:将目录上传到 GitHub,然后通过仓库 URL 安装
|
||||
:::
|
||||
|
||||
## 预设文件位置
|
||||
|
||||
预设保存在:
|
||||
|
||||
```
|
||||
~/.claude-code-router/presets/
|
||||
```
|
||||
|
||||
每个预设都是一个目录,包含 `manifest.json` 文件。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用动态配置**:为需要用户输入的配置项使用schema系统
|
||||
2. **提供默认值**:为非必填项提供合理的默认值
|
||||
3. **条件显示**:使用when条件避免不必要的输入
|
||||
4. **清晰的标签**:为每个字段提供清晰的label和prompt
|
||||
5. **验证输入**:使用validator确保输入的有效性
|
||||
6. **版本控制**:将常用预设保存在版本控制中
|
||||
7. **文档化**:为自定义预设添加描述和版本信息
|
||||
|
||||
## 下一步
|
||||
|
||||
- [CLI 参考](/zh/docs/cli/start) - 完整的 CLI 命令参考
|
||||
- [配置](/zh/docs/config/basic) - 详细配置指南
|
||||
@@ -1,254 +0,0 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# ccr preset
|
||||
|
||||
管理预设(Presets)——可共享和重用的配置模板。
|
||||
|
||||
## 概述
|
||||
|
||||
预设功能让您可以:
|
||||
- 将当前配置保存为可重用的模板
|
||||
- 与他人分享配置
|
||||
- 安装社区提供的预配置方案
|
||||
- 在不同配置之间轻松切换
|
||||
|
||||
## 命令
|
||||
|
||||
### export
|
||||
|
||||
将当前配置导出为预设。
|
||||
|
||||
```bash
|
||||
ccr preset export <名称> [选项]
|
||||
```
|
||||
|
||||
**选项:**
|
||||
- `--output <路径>` - 自定义输出目录路径
|
||||
- `--description <文本>` - 预设描述
|
||||
- `--author <名称>` - 预设作者
|
||||
- `--tags <标签>` - 逗号分隔的关键字
|
||||
- `--include-sensitive` - 包含 API 密钥等敏感数据(不推荐)
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
ccr preset export my-config --description "我的生产环境配置" --author "您的名字"
|
||||
```
|
||||
|
||||
**执行过程:**
|
||||
1. 读取 `~/.claude-code-router/config.json` 中的当前配置
|
||||
2. 提示输入描述、作者和关键字(如未通过命令行提供)
|
||||
3. 自动清理敏感字段(API 密钥变为占位符)
|
||||
4. 在 `~/.claude-code-router/presets/<名称>/` 创建预设目录
|
||||
5. 生成包含配置和元数据的 `manifest.json`
|
||||
|
||||
### install
|
||||
|
||||
从本地目录安装预设。
|
||||
|
||||
```bash
|
||||
ccr preset install <来源>
|
||||
```
|
||||
|
||||
**来源:**
|
||||
- 本地目录路径:`/path/to/preset-directory`
|
||||
- 预设名称(用于重新配置已安装的预设):`preset-name`
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
# 从目录安装
|
||||
ccr preset install ./my-preset
|
||||
|
||||
# 重新配置已安装的预设
|
||||
ccr preset install my-preset
|
||||
```
|
||||
|
||||
**执行过程:**
|
||||
1. 从预设目录读取 `manifest.json`
|
||||
2. 验证预设结构
|
||||
3. 如果预设包含 `schema`,提示输入必需的值(API 密钥等)
|
||||
4. 将预设复制到 `~/.claude-code-router/presets/<名称>/`
|
||||
5. 在 `manifest.json` 中保存用户输入
|
||||
|
||||
**注意:** 目前不支持从 URL 安装。请先下载预设目录。
|
||||
|
||||
### list
|
||||
|
||||
列出所有已安装的预设。
|
||||
|
||||
```bash
|
||||
ccr preset list
|
||||
```
|
||||
|
||||
**示例输出:**
|
||||
```
|
||||
Available presets:
|
||||
|
||||
• my-config (v1.0.0)
|
||||
My production setup
|
||||
by Your Name
|
||||
|
||||
• openai-setup
|
||||
Basic OpenAI configuration
|
||||
```
|
||||
|
||||
### info
|
||||
|
||||
显示预设的详细信息。
|
||||
|
||||
```bash
|
||||
ccr preset info <名称>
|
||||
```
|
||||
|
||||
**显示内容:**
|
||||
- 版本、描述、作者、关键字
|
||||
- 配置摘要(Providers、Router 规则)
|
||||
- 必需输入(如果有)
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
ccr preset info my-config
|
||||
```
|
||||
|
||||
### delete / rm / remove
|
||||
|
||||
删除已安装的预设。
|
||||
|
||||
```bash
|
||||
ccr preset delete <名称>
|
||||
ccr preset rm <名称>
|
||||
ccr preset remove <名称>
|
||||
```
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
ccr preset delete my-config
|
||||
```
|
||||
|
||||
## 预设结构
|
||||
|
||||
预设是一个包含 `manifest.json` 文件的目录:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-preset",
|
||||
"version": "1.0.0",
|
||||
"description": "我的配置",
|
||||
"author": "作者姓名",
|
||||
"keywords": ["openai", "production"],
|
||||
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"api_base_url": "https://api.openai.com/v1",
|
||||
"api_key": "{{apiKey}}",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
|
||||
"Router": {
|
||||
"default": "openai:gpt-4"
|
||||
},
|
||||
|
||||
"schema": [
|
||||
{
|
||||
"id": "apiKey",
|
||||
"type": "password",
|
||||
"label": "OpenAI API 密钥",
|
||||
"prompt": "请输入您的 OpenAI API 密钥"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Schema 系统
|
||||
|
||||
`schema` 字段定义用户在安装时必须提供的输入:
|
||||
|
||||
**字段类型:**
|
||||
- `password` - 隐藏输入(用于 API 密钥)
|
||||
- `input` - 文本输入
|
||||
- `select` - 单选下拉框
|
||||
- `multiselect` - 多选下拉框
|
||||
- `confirm` - 是/否确认
|
||||
- `editor` - 多行文本编辑器
|
||||
- `number` - 数字输入
|
||||
|
||||
**动态选项:**
|
||||
```json
|
||||
{
|
||||
"id": "provider",
|
||||
"type": "select",
|
||||
"label": "选择提供商",
|
||||
"options": {
|
||||
"type": "providers"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**条件显示:**
|
||||
```json
|
||||
{
|
||||
"id": "model",
|
||||
"type": "select",
|
||||
"label": "选择模型",
|
||||
"when": {
|
||||
"field": "provider",
|
||||
"operator": "exists"
|
||||
},
|
||||
"options": {
|
||||
"type": "models",
|
||||
"providerField": "#{selectedProvider}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 分享预设
|
||||
|
||||
分享预设的步骤:
|
||||
|
||||
1. **导出配置:**
|
||||
```bash
|
||||
ccr preset export my-preset
|
||||
```
|
||||
|
||||
2. **分享目录:**
|
||||
```bash
|
||||
~/.claude-code-router/presets/my-preset/
|
||||
```
|
||||
|
||||
3. **分发方式:**
|
||||
- 上传到 GitHub 仓库
|
||||
- 创建 GitHub Gist
|
||||
- 打包为 zip 文件分享
|
||||
- 发布到 npm(未来功能)
|
||||
|
||||
4. **用户安装:**
|
||||
```bash
|
||||
ccr preset install /path/to/my-preset
|
||||
```
|
||||
|
||||
## 安全性
|
||||
|
||||
### 自动清理
|
||||
|
||||
默认情况下,`export` 会清理敏感字段:
|
||||
- 名为 `api_key`、`apikey`、`password`、`secret` 的字段会被替换为 `{{字段名}}` 占位符
|
||||
- 这些占位符会成为 schema 中的必需输入
|
||||
- 用户在安装时会被提示提供自己的值
|
||||
|
||||
### 包含敏感数据
|
||||
|
||||
要包含实际值(不推荐):
|
||||
```bash
|
||||
ccr preset export my-preset --include-sensitive
|
||||
```
|
||||
|
||||
**警告:** 永远不要分享包含敏感数据的预设!
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [配置指南](/zh/docs/cli/config/basic) - 基础配置
|
||||
- [项目级配置](/zh/docs/cli/config/project-level) - 项目特定设置
|
||||
- [服务器:预设](/zh/docs/server/advanced/presets) - 高级预设主题
|
||||
@@ -1,402 +0,0 @@
|
||||
---
|
||||
id: cli/commands/statusline
|
||||
title: ccr statusline
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# ccr statusline
|
||||
|
||||
显示可自定义的状态栏,实时展示 Claude Code 会话信息,包括工作区、Git 分支、模型、token 使用情况等。
|
||||
|
||||
## 概述
|
||||
|
||||
`ccr statusline` 命令从 stdin 读取 JSON 数据,并在终端中渲染格式精美的状态栏。它设计用于与 Claude Code 的 hook 系统集成,以显示实时会话信息。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
ccr statusline
|
||||
```
|
||||
|
||||
该命令期望通过 stdin 接收 JSON 数据,通常通过管道从 Claude Code hook 传递:
|
||||
|
||||
```bash
|
||||
echo '{"hook_event_name":"...","session_id":"...","..."}' | ccr statusline
|
||||
```
|
||||
|
||||
### Hook 集成
|
||||
|
||||
在您的 Claude Code 设置中配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"postResponse": {
|
||||
"command": "ccr statusline",
|
||||
"input": "json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 可用主题
|
||||
|
||||
### 默认主题
|
||||
|
||||
简洁优雅的主题,使用 Nerd Font 图标和彩色文本:
|
||||
|
||||
```
|
||||
my-project main claude-3-5-sonnet-20241022 ↑ 12.3k ↓ 5.2k
|
||||
```
|
||||
|
||||
### Powerline 主题
|
||||
|
||||
vim-powerline 风格,带背景色和箭头分隔符:
|
||||
|
||||
```
|
||||
my-project main claude-3-5-sonnet-20241022 ↑ 12.3k ↓ 5.2k
|
||||
```
|
||||
|
||||
通过在配置中设置 `currentStyle: "powerline"` 激活。
|
||||
|
||||
### 简单主题
|
||||
|
||||
回退主题,不带图标,适用于不支持 Nerd Font 的终端:
|
||||
|
||||
```
|
||||
my-project main claude-3-5-sonnet-20241022 ↑ 12.3k ↓ 5.2k
|
||||
```
|
||||
|
||||
当 `USE_SIMPLE_ICONS=true` 或在不支持的终端上自动使用。
|
||||
|
||||
## 可用模块
|
||||
|
||||
状态栏模块显示不同类型的信息:
|
||||
|
||||
| 模块 | 说明 | 变量 |
|
||||
|------|------|------|
|
||||
| **workDir** | 当前工作目录名称 | `{{workDirName}}` |
|
||||
| **gitBranch** | 当前 Git 分支 | `{{gitBranch}}` |
|
||||
| **model** | 使用的模型 | `{{model}}` |
|
||||
| **usage** | Token 使用情况(输入/输出) | `{{inputTokens}}`, `{{outputTokens}}` |
|
||||
| **context** | 上下文窗口使用情况 | `{{contextPercent}}`, `{{contextWindowSize}}` |
|
||||
| **speed** | Token 处理速度 | `{{tokenSpeed}}`, `{{isStreaming}}` |
|
||||
| **cost** | API 成本 | `{{cost}}` |
|
||||
| **duration** | 会话持续时间 | `{{duration}}` |
|
||||
| **lines** | 代码变更 | `{{linesAdded}}`, `{{linesRemoved}}` |
|
||||
| **script** | 自定义脚本输出 | 动态 |
|
||||
|
||||
## 配置
|
||||
|
||||
在 `~/.claude-code-router/config.json` 中配置 statusline:
|
||||
|
||||
### 默认样式示例
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"currentStyle": "default",
|
||||
"default": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "workDir",
|
||||
"icon": "",
|
||||
"text": "{{workDirName}}",
|
||||
"color": "bright_blue"
|
||||
},
|
||||
{
|
||||
"type": "gitBranch",
|
||||
"icon": "",
|
||||
"text": "{{gitBranch}}",
|
||||
"color": "bright_magenta"
|
||||
},
|
||||
{
|
||||
"type": "model",
|
||||
"icon": "",
|
||||
"text": "{{model}}",
|
||||
"color": "bright_cyan"
|
||||
},
|
||||
{
|
||||
"type": "usage",
|
||||
"icon": "↑",
|
||||
"text": "{{inputTokens}}",
|
||||
"color": "bright_green"
|
||||
},
|
||||
{
|
||||
"type": "usage",
|
||||
"icon": "↓",
|
||||
"text": "{{outputTokens}}",
|
||||
"color": "bright_yellow"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Powerline 样式示例
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"currentStyle": "powerline",
|
||||
"powerline": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "workDir",
|
||||
"icon": "",
|
||||
"text": "{{workDirName}}",
|
||||
"color": "white",
|
||||
"background": "bg_bright_blue"
|
||||
},
|
||||
{
|
||||
"type": "gitBranch",
|
||||
"icon": "",
|
||||
"text": "{{gitBranch}}",
|
||||
"color": "white",
|
||||
"background": "bg_bright_magenta"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 完整功能示例
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"currentStyle": "default",
|
||||
"default": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "workDir",
|
||||
"icon": "",
|
||||
"text": "{{workDirName}}",
|
||||
"color": "bright_blue"
|
||||
},
|
||||
{
|
||||
"type": "gitBranch",
|
||||
"icon": "",
|
||||
"text": "{{gitBranch}}",
|
||||
"color": "bright_magenta"
|
||||
},
|
||||
{
|
||||
"type": "model",
|
||||
"icon": "",
|
||||
"text": "{{model}}",
|
||||
"color": "bright_cyan"
|
||||
},
|
||||
{
|
||||
"type": "context",
|
||||
"icon": "🪟",
|
||||
"text": "{{contextPercent}}% / {{contextWindowSize}}",
|
||||
"color": "bright_green"
|
||||
},
|
||||
{
|
||||
"type": "speed",
|
||||
"icon": "⚡",
|
||||
"text": "{{tokenSpeed}} t/s {{isStreaming}}",
|
||||
"color": "bright_yellow"
|
||||
},
|
||||
{
|
||||
"type": "cost",
|
||||
"icon": "💰",
|
||||
"text": "{{cost}}",
|
||||
"color": "bright_magenta"
|
||||
},
|
||||
{
|
||||
"type": "duration",
|
||||
"icon": "⏱️",
|
||||
"text": "{{duration}}",
|
||||
"color": "bright_white"
|
||||
},
|
||||
{
|
||||
"type": "lines",
|
||||
"icon": "📝",
|
||||
"text": "+{{linesAdded}}/-{{linesRemoved}}",
|
||||
"color": "bright_cyan"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义脚本
|
||||
|
||||
您可以通过执行脚本创建自定义模块:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "script",
|
||||
"icon": "🔧",
|
||||
"scriptPath": "/path/to/script.js",
|
||||
"options": {
|
||||
"customOption": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
脚本格式(CommonJS):
|
||||
|
||||
```javascript
|
||||
// my-status-module.js
|
||||
module.exports = function(variables, options) {
|
||||
// 访问变量如 model、gitBranch 等
|
||||
// 从配置中访问选项
|
||||
return `Custom: ${variables.model}`;
|
||||
};
|
||||
|
||||
// 或异步
|
||||
module.exports = async function(variables, options) {
|
||||
const data = await fetchSomeData();
|
||||
return data;
|
||||
};
|
||||
```
|
||||
|
||||
## 颜色选项
|
||||
|
||||
### 标准颜色
|
||||
|
||||
- `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`
|
||||
- `bright_black`, `bright_red`, `bright_green`, `bright_yellow`, `bright_blue`, `bright_magenta`, `bright_cyan`, `bright_white`
|
||||
|
||||
### 背景颜色
|
||||
|
||||
添加前缀 `bg_`:`bg_blue`, `bg_bright_red` 等。
|
||||
|
||||
### 十六进制颜色
|
||||
|
||||
使用 24 位 TrueColor 和十六进制代码:
|
||||
|
||||
```json
|
||||
{
|
||||
"color": "#FF5733",
|
||||
"background": "bg_#1E90FF"
|
||||
}
|
||||
```
|
||||
|
||||
## 可用变量
|
||||
|
||||
所有变量都可以在模块文本中使用 `{{variableName}}` 访问:
|
||||
|
||||
| 变量 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `{{workDirName}}` | 当前目录名称 | `my-project` |
|
||||
| `{{gitBranch}}` | Git 分支名称 | `main` |
|
||||
| `{{model}}` | 模型名称 | `claude-3-5-sonnet-20241022` |
|
||||
| `{{inputTokens}}` | 输入 tokens(格式化) | `12.3k` |
|
||||
| `{{outputTokens}}` | 输出 tokens(格式化) | `5.2k` |
|
||||
| `{{tokenSpeed}}` | 每秒 tokens 数 | `45` |
|
||||
| `{{isStreaming}}` | 流式传输状态 | `streaming` 或空 |
|
||||
| `{{contextPercent}}` | 上下文使用百分比 | `45` |
|
||||
| `{{contextWindowSize}}` | 总上下文窗口 | `200k` |
|
||||
| `{{cost}}` | 总成本 | `$0.15` |
|
||||
| `{{duration}}` | 会话持续时间 | `2m34s` |
|
||||
| `{{linesAdded}}` | 添加的行数 | `150` |
|
||||
| `{{linesRemoved}}` | 删除的行数 | `25` |
|
||||
| `{{sessionId}}` | 会话 ID(前 8 个字符) | `a1b2c3d4` |
|
||||
|
||||
## 环境变量
|
||||
|
||||
使用环境变量控制行为:
|
||||
|
||||
| 变量 | 值 | 说明 |
|
||||
|------|------|------|
|
||||
| `USE_SIMPLE_ICONS` | `true`/`false` | 强制使用不带图标的简单主题 |
|
||||
| `NERD_FONT` | 任意值 | 自动检测 Nerd Font 支持 |
|
||||
|
||||
## 示例
|
||||
|
||||
### 极简状态栏
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"default": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "model",
|
||||
"text": "{{model}}"
|
||||
},
|
||||
{
|
||||
"type": "usage",
|
||||
"text": "↑{{inputTokens}} ↓{{outputTokens}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出:`claude-3-5-sonnet-20241022 ↑12.3k ↓5.2k`
|
||||
|
||||
### 开发者生产力重点
|
||||
|
||||
```json
|
||||
{
|
||||
"StatusLine": {
|
||||
"default": {
|
||||
"modules": [
|
||||
{
|
||||
"type": "gitBranch",
|
||||
"icon": "",
|
||||
"text": "{{gitBranch}}",
|
||||
"color": "bright_magenta"
|
||||
},
|
||||
{
|
||||
"type": "lines",
|
||||
"icon": "📝",
|
||||
"text": "+{{linesAdded}}/-{{linesRemoved}}",
|
||||
"color": "bright_cyan"
|
||||
},
|
||||
{
|
||||
"type": "duration",
|
||||
"icon": "⏱️",
|
||||
"text": "{{duration}}",
|
||||
"color": "bright_white"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出:` feature/auth 📝 +150/-25 ⏱️ 2m34s`
|
||||
|
||||
## Preset 集成
|
||||
|
||||
Statusline 主题可以包含在 presets 中。当您安装带有 statusline 配置的 preset 时,激活该 preset 时会自动应用。
|
||||
|
||||
查看 [Presets](/docs/server/advanced/presets) 了解更多信息。
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 图标不显示
|
||||
|
||||
在环境中设置 `USE_SIMPLE_ICONS=true`:
|
||||
|
||||
```bash
|
||||
export USE_SIMPLE_ICONS=true
|
||||
```
|
||||
|
||||
### 颜色不工作
|
||||
|
||||
确保您的终端支持 TrueColor(24 位颜色):
|
||||
|
||||
```bash
|
||||
export COLORTERM=truecolor
|
||||
```
|
||||
|
||||
### Git 分支不显示
|
||||
|
||||
确保您在 Git 仓库中并安装了 `git` 命令。
|
||||
|
||||
## 相关命令
|
||||
|
||||
- [ccr status](/docs/cli/commands/status) - 检查服务状态
|
||||
- [ccr preset](/docs/cli/commands/preset) - 管理带 statusline 主题的 presets
|
||||
@@ -1,208 +0,0 @@
|
||||
# CLI 基础配置
|
||||
|
||||
CLI 使用与 Server 相同的配置文件:`~/.claude-code-router/config.json`
|
||||
|
||||
## 配置文件位置
|
||||
|
||||
```bash
|
||||
~/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
## 快速配置
|
||||
|
||||
使用交互式命令配置:
|
||||
|
||||
```bash
|
||||
ccr model
|
||||
```
|
||||
|
||||
这将引导你完成:
|
||||
1. 选择 LLM 提供商
|
||||
2. 配置 API Key
|
||||
3. 选择模型
|
||||
4. 设置路由规则
|
||||
|
||||
## 手动配置
|
||||
|
||||
### 编辑配置文件
|
||||
|
||||
```bash
|
||||
# 打开配置文件
|
||||
nano ~/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
### 最小配置示例
|
||||
|
||||
```json5
|
||||
{
|
||||
// API 密钥(可选,用于保护服务)
|
||||
"APIKEY": "your-api-key-here",
|
||||
|
||||
// LLM 提供商
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
|
||||
// 默认路由
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
配置支持环境变量插值:
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"apiKey": "$OPENAI_API_KEY" // 从环境变量读取
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
在 `.bashrc` 或 `.zshrc` 中设置:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
```
|
||||
|
||||
## 常用配置项
|
||||
|
||||
### HOST 和 PORT
|
||||
|
||||
```json5
|
||||
{
|
||||
"HOST": "127.0.0.1", // 监听地址
|
||||
"PORT": 3456 // 监听端口
|
||||
}
|
||||
```
|
||||
|
||||
### 日志配置
|
||||
|
||||
```json5
|
||||
{
|
||||
"LOG": true, // 启用日志
|
||||
"LOG_LEVEL": "info" // 日志级别
|
||||
}
|
||||
```
|
||||
|
||||
### 路由配置
|
||||
|
||||
```json5
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-4",
|
||||
"background": "openai,gpt-3.5-turbo",
|
||||
"think": "openai,gpt-4",
|
||||
"longContext": "anthropic,claude-3-opus"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 配置验证
|
||||
|
||||
配置文件会自动验证。常见错误:
|
||||
|
||||
- **缺少 Providers**:必须至少配置一个提供商
|
||||
- **API Key 缺失**:如果配置了 Providers,必须提供 API Key
|
||||
- **模型不存在**:确保模型在提供商的 models 列表中
|
||||
|
||||
## 配置备份
|
||||
|
||||
每次更新配置时会自动备份:
|
||||
|
||||
```
|
||||
~/.claude-code-router/config.backup.{timestamp}.json
|
||||
```
|
||||
|
||||
## 重新加载配置
|
||||
|
||||
修改配置后需要重启服务:
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
## 查看当前配置
|
||||
|
||||
```bash
|
||||
# 通过 API 查看
|
||||
curl http://localhost:3456/api/config
|
||||
|
||||
# 或查看配置文件
|
||||
cat ~/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
## 示例配置
|
||||
|
||||
### OpenAI
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Anthropic
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"baseUrl": "https://api.anthropic.com/v1",
|
||||
"apiKey": "$ANTHROPIC_API_KEY",
|
||||
"models": ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-5-sonnet-20241022"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 多提供商
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
},
|
||||
{
|
||||
"name": "anthropic",
|
||||
"baseUrl": "https://api.anthropic.com/v1",
|
||||
"apiKey": "$ANTHROPIC_API_KEY",
|
||||
"models": ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4",
|
||||
"think": "anthropic,claude-3-5-sonnet-20241022",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,213 +0,0 @@
|
||||
# 项目级配置
|
||||
|
||||
除了全局配置,`ccr` 还支持为特定项目设置不同的路由规则。
|
||||
|
||||
## 项目配置文件
|
||||
|
||||
项目配置文件位于:
|
||||
|
||||
```
|
||||
~/.claude/projects/<project-id>/claude-code-router.json
|
||||
```
|
||||
|
||||
其中 `<project-id>` 是 Claude Code 项目的唯一标识符。
|
||||
|
||||
## 项目配置结构
|
||||
|
||||
```json5
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-4",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 查找项目 ID
|
||||
|
||||
### 方法一:使用 CLI
|
||||
|
||||
```bash
|
||||
# 在项目目录中运行
|
||||
ccr status
|
||||
```
|
||||
|
||||
输出会显示当前项目 ID:
|
||||
|
||||
```
|
||||
Project: my-project (abc123def456)
|
||||
```
|
||||
|
||||
### 方法二:查看 Claude Code 配置
|
||||
|
||||
```bash
|
||||
cat ~/.claude.json
|
||||
```
|
||||
|
||||
找到你的项目 ID:
|
||||
|
||||
```json
|
||||
{
|
||||
"projects": {
|
||||
"abc123def456": {
|
||||
"path": "/path/to/your/project",
|
||||
"name": "my-project"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 创建项目配置
|
||||
|
||||
### 手动创建
|
||||
|
||||
```bash
|
||||
# 创建项目配置目录
|
||||
mkdir -p ~/.claude/projects/abc123def456
|
||||
|
||||
# 创建配置文件
|
||||
cat > ~/.claude/projects/abc123def456/claude-code-router.json << 'EOF'
|
||||
{
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-5-sonnet-20241022",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### 使用 ccr model 命令
|
||||
|
||||
```bash
|
||||
# 在项目目录中运行
|
||||
cd /path/to/your/project
|
||||
ccr model --project
|
||||
```
|
||||
|
||||
## 配置优先级
|
||||
|
||||
路由配置的优先级(从高到低):
|
||||
|
||||
1. **自定义路由函数** (`CUSTOM_ROUTER_PATH`)
|
||||
2. **项目级配置** (`~/.claude/projects/<id>/claude-code-router.json`)
|
||||
3. **全局配置** (`~/.claude-code-router/config.json`)
|
||||
4. **内置路由规则**
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景一:不同项目使用不同模型
|
||||
|
||||
```json5
|
||||
// Web 项目使用 GPT-4
|
||||
~/.claude/projects/web-project-id/claude-code-router.json:
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
|
||||
// AI 项目使用 Claude
|
||||
~/.claude/projects/ai-project-id/claude-code-router.json:
|
||||
{
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-5-sonnet-20241022"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 场景二:测试项目使用低成本模型
|
||||
|
||||
```json5
|
||||
~/.claude/projects/test-project-id/claude-code-router.json:
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-3.5-turbo",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 场景三:长上下文项目
|
||||
|
||||
```json5
|
||||
~/.claude/projects/long-context-project-id/claude-code-router.json:
|
||||
{
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-opus-20240229",
|
||||
"longContext": "anthropic,claude-3-opus-20240229"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 验证项目配置
|
||||
|
||||
```bash
|
||||
# 查看当前项目使用的路由
|
||||
ccr status
|
||||
|
||||
# 查看日志确认路由决策
|
||||
tail -f ~/.claude-code-router/claude-code-router.log
|
||||
```
|
||||
|
||||
## 删除项目配置
|
||||
|
||||
```bash
|
||||
rm ~/.claude/projects/<project-id>/claude-code-router.json
|
||||
```
|
||||
|
||||
删除后会回退到全局配置。
|
||||
|
||||
## 完整示例
|
||||
|
||||
假设你有两个项目:
|
||||
|
||||
### 全局配置(`~/.claude-code-router/config.json`)
|
||||
|
||||
```json5
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
},
|
||||
{
|
||||
"name": "anthropic",
|
||||
"baseUrl": "https://api.anthropic.com/v1",
|
||||
"apiKey": "$ANTHROPIC_API_KEY",
|
||||
"models": ["claude-3-5-sonnet-20241022"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4",
|
||||
"background": "openai,gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Web 项目配置
|
||||
|
||||
```json5
|
||||
{
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AI 项目配置
|
||||
|
||||
```json5
|
||||
{
|
||||
"Router": {
|
||||
"default": "anthropic,claude-3-5-sonnet-20241022",
|
||||
"think": "anthropic,claude-3-5-sonnet-20241022"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样:
|
||||
- Web 项目使用 GPT-4
|
||||
- AI 项目使用 Claude
|
||||
- 所有项目的后台任务使用 GPT-3.5-turbo(继承全局配置)
|
||||
@@ -1,84 +0,0 @@
|
||||
# CLI 简介
|
||||
|
||||
Claude Code Router CLI (`ccr`) 是一个命令行工具,用于管理和控制 Claude Code Router 服务。
|
||||
|
||||
## 功能概述
|
||||
|
||||
`ccr` 提供以下功能:
|
||||
|
||||
- **服务管理**:启动、停止、重启服务
|
||||
- **配置管理**:交互式配置模型选择
|
||||
- **状态查看**:查看服务运行状态
|
||||
- **代码执行**:直接执行 `claude` 命令
|
||||
- **环境集成**:输出环境变量用于 shell 集成
|
||||
- **Web UI**:打开 Web 管理界面
|
||||
- **状态栏**:使用 `ccr statusline` 显示自定义会话状态
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
npm install -g @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
或使用项目别名:
|
||||
|
||||
```bash
|
||||
npm install -g claude-code-router
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 启动服务
|
||||
|
||||
```bash
|
||||
ccr start
|
||||
```
|
||||
|
||||
### 查看状态
|
||||
|
||||
```bash
|
||||
ccr status
|
||||
```
|
||||
|
||||
### 停止服务
|
||||
|
||||
```bash
|
||||
ccr stop
|
||||
```
|
||||
|
||||
### 查看模型
|
||||
|
||||
```bash
|
||||
ccr model
|
||||
```
|
||||
|
||||
## 与 Claude Code 集成
|
||||
|
||||
`ccr` 可以与 Claude Code 无缝集成,将请求路由到你选择的 LLM 提供商。
|
||||
|
||||
### 方式一:设置 API 地址
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_BASE_URL="http://localhost:3456/v1"
|
||||
export ANTHROPIC_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
### 方式二:使用 activate 命令
|
||||
|
||||
```bash
|
||||
eval "$(ccr activate)"
|
||||
```
|
||||
|
||||
## 配置文件
|
||||
|
||||
`ccr` 使用与 Server 相同的配置文件:`~/.claude-code-router/config.json`
|
||||
|
||||
配置一次,CLI 和 Server 都会使用。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [安装指南](/docs/cli/installation) - 详细安装说明
|
||||
- [快速开始](/docs/cli/quick-start) - 5 分钟上手
|
||||
- [命令参考](/docs/category/cli-commands) - 完整命令列表
|
||||
- [状态栏](/docs/cli/commands/statusline) - 自定义状态栏
|
||||
- [配置说明](/docs/category/cli-config) - 配置文件详解
|
||||
@@ -1,128 +0,0 @@
|
||||
---
|
||||
id: cli/model
|
||||
title: ccr model
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# ccr model
|
||||
|
||||
交互式模型选择和配置。
|
||||
|
||||
## 用法
|
||||
|
||||
```bash
|
||||
ccr model [命令]
|
||||
```
|
||||
|
||||
## 命令
|
||||
|
||||
### 选择模型
|
||||
|
||||
交互式选择模型:
|
||||
|
||||
```bash
|
||||
ccr model
|
||||
```
|
||||
|
||||
这将显示一个包含可用提供商和模型的交互式菜单。
|
||||
|
||||
### 设置默认模型
|
||||
|
||||
直接设置默认模型:
|
||||
|
||||
```bash
|
||||
ccr model set <provider>,<model>
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
ccr model set deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### 列出模型
|
||||
|
||||
列出所有配置的模型:
|
||||
|
||||
```bash
|
||||
ccr model list
|
||||
```
|
||||
|
||||
### 添加模型
|
||||
|
||||
添加新模型到配置:
|
||||
|
||||
```bash
|
||||
ccr model add <provider>,<model>
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
ccr model add groq,llama-3.3-70b-versatile
|
||||
```
|
||||
|
||||
### 删除模型
|
||||
|
||||
从配置中删除模型:
|
||||
|
||||
```bash
|
||||
ccr model remove <provider>,<model>
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
### 交互式选择
|
||||
|
||||
```bash
|
||||
$ ccr model
|
||||
|
||||
? 选择一个提供商: deepseek
|
||||
? 选择一个模型: deepseek-chat
|
||||
|
||||
默认模型设置为: deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### 直接配置
|
||||
|
||||
```bash
|
||||
ccr model set deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### 查看当前配置
|
||||
|
||||
```bash
|
||||
ccr model list
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
已配置的模型:
|
||||
deepseek,deepseek-chat (默认)
|
||||
groq,llama-3.3-70b-versatile
|
||||
gemini,gemini-2.5-pro
|
||||
```
|
||||
|
||||
## 交互式功能
|
||||
|
||||
`ccr model` 命令提供以下功能:
|
||||
|
||||
1. **查看当前配置**:查看所有已配置的模型和路由器设置
|
||||
2. **切换模型**:快速更改每个路由器类型使用的模型
|
||||
3. **添加新模型**:向现有提供商添加模型
|
||||
4. **创建新提供商**:设置完整的提供商配置,包括:
|
||||
- 提供商名称和 API 端点
|
||||
- API 密钥
|
||||
- 可用模型
|
||||
- 转换器配置,支持:
|
||||
- 多个转换器(openrouter、deepseek、gemini 等)
|
||||
- 转换器选项(例如,带自定义限制的 maxtoken)
|
||||
- 提供商特定路由(例如,OpenRouter 提供商偏好)
|
||||
|
||||
CLI 工具会验证所有输入并提供有用的提示来引导您完成配置过程,使管理复杂设置变得容易,无需手动编辑 JSON 文件。
|
||||
|
||||
## 相关命令
|
||||
|
||||
- [ccr start](/zh/docs/cli/start) - 启动服务器
|
||||
- [ccr config](/zh/docs/cli/other-commands#ccr-config) - 编辑配置
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
id: cli/other-commands
|
||||
title: 其他命令
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# 其他命令
|
||||
|
||||
管理 Claude Code Router 的其他 CLI 命令。
|
||||
|
||||
## ccr stop
|
||||
|
||||
停止运行中的服务器。
|
||||
|
||||
```bash
|
||||
ccr stop
|
||||
```
|
||||
|
||||
## ccr restart
|
||||
|
||||
重启服务器。
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
## ccr code
|
||||
|
||||
通过路由器执行 claude 命令。
|
||||
|
||||
```bash
|
||||
ccr code [参数...]
|
||||
```
|
||||
|
||||
## ccr ui
|
||||
|
||||
在浏览器中打开 Web UI。
|
||||
|
||||
```bash
|
||||
ccr ui
|
||||
```
|
||||
|
||||
## ccr activate
|
||||
|
||||
输出用于与外部工具集成的 shell 环境变量。
|
||||
|
||||
```bash
|
||||
ccr activate
|
||||
```
|
||||
|
||||
## 全局选项
|
||||
|
||||
这些选项可用于任何命令:
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `-h, --help` | 显示帮助 |
|
||||
| `-v, --version` | 显示版本号 |
|
||||
| `--config <路径>` | 配置文件路径 |
|
||||
| `--verbose` | 启用详细输出 |
|
||||
|
||||
## 示例
|
||||
|
||||
### 停止服务器
|
||||
|
||||
```bash
|
||||
ccr stop
|
||||
```
|
||||
|
||||
### 使用自定义配置重启
|
||||
|
||||
```bash
|
||||
ccr restart --config /path/to/config.json
|
||||
```
|
||||
|
||||
### 打开 Web UI
|
||||
|
||||
```bash
|
||||
ccr ui
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [入门](/zh/docs/intro) - Claude Code Router 简介
|
||||
- [配置](/zh/docs/config/basic) - 配置指南
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
id: cli/start
|
||||
title: ccr start
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# ccr start
|
||||
|
||||
启动 Claude Code Router 服务器。
|
||||
|
||||
## 用法
|
||||
|
||||
```bash
|
||||
ccr start [选项]
|
||||
```
|
||||
|
||||
## 选项
|
||||
|
||||
| 选项 | 别名 | 说明 |
|
||||
|------|------|------|
|
||||
| `--port <number>` | `-p` | 监听端口号(默认:3456) |
|
||||
| `--config <path>` | `-c` | 配置文件路径 |
|
||||
| `--daemon` | `-d` | 作为守护进程运行(后台进程) |
|
||||
| `--log-level <level>` | `-l` | 日志级别(fatal/error/warn/info/debug/trace) |
|
||||
|
||||
## 示例
|
||||
|
||||
### 使用默认设置启动
|
||||
|
||||
```bash
|
||||
ccr start
|
||||
```
|
||||
|
||||
### 在自定义端口启动
|
||||
|
||||
```bash
|
||||
ccr start --port 3000
|
||||
```
|
||||
|
||||
### 使用自定义配置启动
|
||||
|
||||
```bash
|
||||
ccr start --config /path/to/config.json
|
||||
```
|
||||
|
||||
### 作为守护进程启动
|
||||
|
||||
```bash
|
||||
ccr start --daemon
|
||||
```
|
||||
|
||||
### 启用调试日志
|
||||
|
||||
```bash
|
||||
ccr start --log-level debug
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
您也可以使用环境变量配置服务器:
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `PORT` | 监听端口号 |
|
||||
| `CONFIG_PATH` | 配置文件路径 |
|
||||
| `LOG_LEVEL` | 日志级别 |
|
||||
| `CUSTOM_ROUTER_PATH` | 自定义路由器函数路径 |
|
||||
| `HOST` | 绑定主机地址(默认:0.0.0.0) |
|
||||
|
||||
## 输出
|
||||
|
||||
启动成功后,您将看到:
|
||||
|
||||
```
|
||||
Claude Code Router is running on http://localhost:3456
|
||||
API endpoint: http://localhost:3456/v1
|
||||
```
|
||||
|
||||
## 相关命令
|
||||
|
||||
- [ccr stop](/zh/docs/cli/other-commands#ccr-stop) - 停止服务器
|
||||
- [ccr restart](/zh/docs/cli/other-commands#ccr-restart) - 重启服务器
|
||||
- [ccr status](/zh/docs/cli/other-commands#ccr-status) - 检查服务器状态
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
id: cli/status
|
||||
title: ccr status
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# ccr status
|
||||
|
||||
显示 Claude Code Router 服务器的当前状态。
|
||||
|
||||
## 用法
|
||||
|
||||
```bash
|
||||
ccr status
|
||||
```
|
||||
|
||||
## 输出
|
||||
|
||||
### 运行中的服务器
|
||||
|
||||
当服务器正在运行时:
|
||||
|
||||
```
|
||||
Claude Code Router 状态: 运行中
|
||||
版本: 2.0.0
|
||||
PID: 12345
|
||||
端口: 3456
|
||||
运行时间: 2小时34分钟
|
||||
配置: /home/user/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
### 已停止的服务器
|
||||
|
||||
当服务器未运行时:
|
||||
|
||||
```
|
||||
Claude Code Router 状态: 已停止
|
||||
```
|
||||
|
||||
## 退出代码
|
||||
|
||||
| 代码 | 说明 |
|
||||
|------|------|
|
||||
| 0 | 服务器正在运行 |
|
||||
| 1 | 服务器已停止 |
|
||||
| 2 | 检查状态时出错 |
|
||||
|
||||
## 示例
|
||||
|
||||
```bash
|
||||
$ ccr status
|
||||
|
||||
Claude Code Router 状态: 运行中
|
||||
版本: 2.0.0
|
||||
PID: 12345
|
||||
端口: 3456
|
||||
运行时间: 2小时34分钟
|
||||
```
|
||||
|
||||
## 相关命令
|
||||
|
||||
- [ccr start](/zh/docs/cli/start) - 启动服务器
|
||||
- [ccr stop](/zh/docs/cli/other-commands#ccr-stop) - 停止服务器
|
||||
- [ccr restart](/zh/docs/cli/other-commands#ccr-restart) - 重启服务器
|
||||
@@ -1,161 +0,0 @@
|
||||
---
|
||||
id: config/basic
|
||||
title: 基础配置
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 基础配置
|
||||
|
||||
学习如何配置 Claude Code Router 以满足您的需求。
|
||||
|
||||
## 配置文件位置
|
||||
|
||||
配置文件位于:
|
||||
|
||||
```
|
||||
~/.claude-code-router/config.json
|
||||
```
|
||||
|
||||
## 配置结构
|
||||
|
||||
### Providers(提供商)
|
||||
|
||||
配置 LLM 提供商以将请求路由到:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "your-api-key",
|
||||
"models": ["deepseek-chat", "deepseek-coder"]
|
||||
},
|
||||
{
|
||||
"name": "groq",
|
||||
"api_base_url": "https://api.groq.com/openai/v1/chat/completions",
|
||||
"api_key": "your-groq-api-key",
|
||||
"models": ["llama-3.3-70b-versatile"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Router(路由器)
|
||||
|
||||
配置默认使用的模型:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
格式:`{provider-name},{model-name}`
|
||||
|
||||
### Transformers(转换器)
|
||||
|
||||
对请求/响应应用转换:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"path": "/path/to/custom-transformer.js",
|
||||
"options": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
|
||||
在配置中使用环境变量:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "$DEEPSEEK_API_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
同时支持 `$VAR_NAME` 和 `${VAR_NAME}` 语法。
|
||||
|
||||
## 完整示例
|
||||
|
||||
```json
|
||||
{
|
||||
"PORT": 8080,
|
||||
"APIKEY": "your-secret-key",
|
||||
"PROXY_URL": "http://127.0.0.1:7890",
|
||||
"LOG": true,
|
||||
"LOG_LEVEL": "debug",
|
||||
"API_TIMEOUT_MS": 600000,
|
||||
"Providers": [
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "$DEEPSEEK_API_KEY",
|
||||
"models": ["deepseek-chat", "deepseek-coder"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "groq",
|
||||
"api_base_url": "https://api.groq.com/openai/v1/chat/completions",
|
||||
"api_key": "$GROQ_API_KEY",
|
||||
"models": ["llama-3.3-70b-versatile"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat",
|
||||
"longContextThreshold": 100000,
|
||||
"background": "groq,llama-3.3-70b-versatile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 编辑配置
|
||||
|
||||
使用 CLI 编辑配置:
|
||||
|
||||
```bash
|
||||
ccr config edit
|
||||
```
|
||||
|
||||
这将在您的默认编辑器中打开配置文件。
|
||||
|
||||
## 重新加载配置
|
||||
|
||||
编辑配置后,重启路由器:
|
||||
|
||||
```bash
|
||||
ccr restart
|
||||
```
|
||||
|
||||
## 配置选项说明
|
||||
|
||||
- **PORT**: 服务器端口号(默认:3456)
|
||||
- **APIKEY**: API 密钥,用于身份验证
|
||||
- **HOST**: 服务器监听地址(默认:127.0.0.1,如果配置了 Providers 且没有设置 APIKEY,则强制为 127.0.0.1)
|
||||
- **PROXY_URL**: 代理服务器地址
|
||||
- **LOG**: 是否启用日志(默认:true)
|
||||
- **LOG_LEVEL**: 日志级别(fatal/error/warn/info/debug/trace)
|
||||
- **API_TIMEOUT_MS**: API 请求超时时间(毫秒)
|
||||
- **NON_INTERACTIVE_MODE**: 非交互模式(用于 CI/CD 环境)
|
||||
|
||||
## 下一步
|
||||
|
||||
- [提供商配置](/zh/docs/config/providers) - 详细的提供商配置
|
||||
- [路由配置](/zh/docs/config/routing) - 配置路由规则
|
||||
- [转换器](/zh/docs/config/transformers) - 应用转换
|
||||
@@ -1,213 +0,0 @@
|
||||
---
|
||||
id: config/providers
|
||||
title: 提供商配置
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# 提供商配置
|
||||
|
||||
配置 LLM 提供商的详细指南。
|
||||
|
||||
## 支持的提供商
|
||||
|
||||
### DeepSeek
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "your-api-key",
|
||||
"models": ["deepseek-chat", "deepseek-coder", "deepseek-reasoner"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Groq
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "groq",
|
||||
"api_base_url": "https://api.groq.com/openai/v1/chat/completions",
|
||||
"api_key": "your-api-key",
|
||||
"models": ["llama-3.3-70b-versatile"]
|
||||
}
|
||||
```
|
||||
|
||||
### Gemini
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "gemini",
|
||||
"api_base_url": "https://generativelanguage.googleapis.com/v1beta/models/",
|
||||
"api_key": "your-api-key",
|
||||
"models": ["gemini-2.5-flash", "gemini-2.5-pro"],
|
||||
"transformer": {
|
||||
"use": ["gemini"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### OpenRouter
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "openrouter",
|
||||
"api_base_url": "https://openrouter.ai/api/v1/chat/completions",
|
||||
"api_key": "your-api-key",
|
||||
"models": [
|
||||
"anthropic/claude-3.5-sonnet",
|
||||
"google/gemini-2.5-pro-preview"
|
||||
],
|
||||
"transformer": {
|
||||
"use": ["openrouter"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Ollama(本地模型)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ollama",
|
||||
"api_base_url": "http://localhost:11434/v1/chat/completions",
|
||||
"api_key": "ollama",
|
||||
"models": ["qwen2.5-coder:latest"]
|
||||
}
|
||||
```
|
||||
|
||||
### 火山引擎
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "volcengine",
|
||||
"api_base_url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
|
||||
"api_key": "your-api-key",
|
||||
"models": ["deepseek-v3-250324", "deepseek-r1-250528"],
|
||||
"transformer": {
|
||||
"use": ["deepseek"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ModelScope
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "modelscope",
|
||||
"api_base_url": "https://api-inference.modelscope.cn/v1/chat/completions",
|
||||
"api_key": "",
|
||||
"models": [
|
||||
"Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
"Qwen/Qwen3-235B-A22B-Thinking-2507"
|
||||
],
|
||||
"transformer": {
|
||||
"use": [
|
||||
["maxtoken", { "max_tokens": 65536 }],
|
||||
"enhancetool"
|
||||
],
|
||||
"Qwen/Qwen3-235B-A22B-Thinking-2507": {
|
||||
"use": ["reasoning"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DashScope(阿里云)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "dashscope",
|
||||
"api_base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
"api_key": "your-api-key",
|
||||
"models": ["qwen3-coder-plus"],
|
||||
"transformer": {
|
||||
"use": [
|
||||
["maxtoken", { "max_tokens": 65536 }],
|
||||
"enhancetool"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 提供商配置选项
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `name` | string | 是 | 提供商的唯一标识符 |
|
||||
| `api_base_url` | string | 是 | API 基础 URL |
|
||||
| `api_key` | string | 是 | API 认证密钥 |
|
||||
| `models` | string[] | 否 | 可用模型列表 |
|
||||
| `transformer` | object | 否 | 应用的转换器配置 |
|
||||
|
||||
## 模型选择
|
||||
|
||||
在路由中选择模型时,使用以下格式:
|
||||
|
||||
```
|
||||
{provider-name},{model-name}
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```
|
||||
deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
## 使用环境变量
|
||||
|
||||
您可以在配置中使用环境变量来保护 API 密钥:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "$DEEPSEEK_API_KEY",
|
||||
"models": ["deepseek-chat"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
支持 `$VAR_NAME` 和 `${VAR_NAME}` 两种语法。
|
||||
|
||||
## 转换器配置
|
||||
|
||||
转换器用于适配不同提供商的 API 差异。您可以在提供商级别或模型级别配置转换器:
|
||||
|
||||
### 提供商级别转换器
|
||||
|
||||
应用于提供商的所有模型:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "openrouter",
|
||||
"transformer": {
|
||||
"use": ["openrouter"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 模型级别转换器
|
||||
|
||||
应用于特定模型:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "deepseek",
|
||||
"transformer": {
|
||||
"use": ["deepseek"],
|
||||
"deepseek-chat": {
|
||||
"use": ["tooluse"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [路由配置](/zh/docs/config/routing) - 配置请求如何路由
|
||||
- [转换器](/zh/docs/config/transformers) - 对请求应用转换
|
||||
@@ -1,165 +0,0 @@
|
||||
---
|
||||
id: config/routing
|
||||
title: 路由配置
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# 路由配置
|
||||
|
||||
配置如何将请求路由到不同的模型。
|
||||
|
||||
## 默认路由
|
||||
|
||||
为所有请求设置默认模型:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 内置场景
|
||||
|
||||
### 后台任务
|
||||
|
||||
将后台任务路由到轻量级模型:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"background": "groq,llama-3.3-70b-versatile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 思考模式(计划模式)
|
||||
|
||||
将思考密集型任务路由到更强大的模型:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"think": "deepseek,deepseek-reasoner"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 长上下文
|
||||
|
||||
路由长上下文请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"longContextThreshold": 100000,
|
||||
"longContext": "gemini,gemini-2.5-pro"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 网络搜索
|
||||
|
||||
路由网络搜索任务:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"webSearch": "gemini,gemini-2.5-flash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 图像任务
|
||||
|
||||
路由图像相关任务:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"image": "gemini,gemini-2.5-pro"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 项目级路由
|
||||
|
||||
在 `~/.claude/projects/<project-id>/claude-code-router.json` 中为每个项目配置路由:
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "groq,llama-3.3-70b-versatile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
项目级配置优先于全局配置。
|
||||
|
||||
## 自定义路由器
|
||||
|
||||
创建自定义 JavaScript 路由器函数:
|
||||
|
||||
1. 创建路由器文件(例如 `custom-router.js`):
|
||||
|
||||
```javascript
|
||||
module.exports = async function(req, config) {
|
||||
// 分析请求上下文
|
||||
const userMessage = req.body.messages.find(m => m.role === 'user')?.content;
|
||||
|
||||
// 自定义路由逻辑
|
||||
if (userMessage && userMessage.includes('解释代码')) {
|
||||
return 'openrouter,anthropic/claude-3.5-sonnet';
|
||||
}
|
||||
|
||||
// 返回 null 以使用默认路由
|
||||
return null;
|
||||
};
|
||||
```
|
||||
|
||||
2. 在 `config.json` 中设置 `CUSTOM_ROUTER_PATH`:
|
||||
|
||||
```json
|
||||
{
|
||||
"CUSTOM_ROUTER_PATH": "/path/to/custom-router.js"
|
||||
}
|
||||
```
|
||||
|
||||
## Token 计数
|
||||
|
||||
路由器使用 `tiktoken` (cl100k_base) 来估算请求 token 数量。这用于:
|
||||
|
||||
- 确定请求是否超过 `longContextThreshold`
|
||||
- 基于 token 数量的自定义路由逻辑
|
||||
|
||||
## 子代理路由
|
||||
|
||||
使用特殊标签为子代理指定模型:
|
||||
|
||||
```
|
||||
<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>
|
||||
请帮我分析这段代码...
|
||||
```
|
||||
|
||||
## 动态模型切换
|
||||
|
||||
在 Claude Code 中使用 `/model` 命令动态切换模型:
|
||||
|
||||
```
|
||||
/model provider_name,model_name
|
||||
```
|
||||
|
||||
示例:`/model openrouter,anthropic/claude-3.5-sonnet`
|
||||
|
||||
## 路由优先级
|
||||
|
||||
1. 项目级配置
|
||||
2. 自定义路由器
|
||||
3. 内置场景路由
|
||||
4. 默认路由
|
||||
|
||||
## 下一步
|
||||
|
||||
- [转换器](/zh/docs/config/transformers) - 对请求应用转换
|
||||
- [自定义路由器](/zh/docs/advanced/custom-router) - 高级自定义路由
|
||||
@@ -1,283 +0,0 @@
|
||||
---
|
||||
id: config/transformers
|
||||
title: 转换器
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# 转换器
|
||||
|
||||
转换器用于适配不同提供商之间的 API 差异。
|
||||
|
||||
## 内置转换器
|
||||
|
||||
### anthropic
|
||||
|
||||
将请求转换为兼容 Anthropic 风格的 API:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["anthropic"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果只使用这一个转换器,它将直接透传请求和响应(您可以用来接入其他支持 Anthropic 端点的服务商)。
|
||||
|
||||
### deepseek
|
||||
|
||||
专门用于 DeepSeek API 的转换器:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["deepseek"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### gemini
|
||||
|
||||
用于 Google Gemini API 的转换器:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["gemini"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### groq
|
||||
|
||||
用于 Groq API 的转换器:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["groq"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### openrouter
|
||||
|
||||
用于 OpenRouter API 的转换器:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["openrouter"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
OpenRouter 转换器还支持 `provider` 路由参数,以指定 OpenRouter 应使用哪些底层提供商:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["openrouter"],
|
||||
"moonshotai/kimi-k2": {
|
||||
"use": [
|
||||
["openrouter", {
|
||||
"provider": {
|
||||
"only": ["moonshotai/fp8"]
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### maxtoken
|
||||
|
||||
设置特定的 `max_tokens` 值:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": [
|
||||
["maxtoken", { "max_tokens": 65536 }]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### tooluse
|
||||
|
||||
通过 `tool_choice` 参数优化某些模型的工具使用:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["tooluse"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### reasoning
|
||||
|
||||
用于处理 `reasoning_content` 字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["reasoning"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### sampling
|
||||
|
||||
用于处理采样信息字段,如 `temperature`、`top_p`、`top_k` 和 `repetition_penalty`:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["sampling"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### enhancetool
|
||||
|
||||
对 LLM 返回的工具调用参数增加一层容错处理(注意:这会导致不再流式返回工具调用信息):
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["enhancetool"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### cleancache
|
||||
|
||||
清除请求中的 `cache_control` 字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["cleancache"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### vertex-gemini
|
||||
|
||||
处理使用 Vertex 鉴权的 Gemini API:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": ["vertex-gemini"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 应用转换器
|
||||
|
||||
### 全局应用
|
||||
|
||||
应用于提供商的所有请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "your-api-key",
|
||||
"transformer": {
|
||||
"use": ["deepseek"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 模型特定应用
|
||||
|
||||
应用于特定模型:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "deepseek",
|
||||
"transformer": {
|
||||
"use": ["deepseek"],
|
||||
"deepseek-chat": {
|
||||
"use": ["tooluse"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 传递选项
|
||||
|
||||
某些转换器接受选项:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformer": {
|
||||
"use": [
|
||||
["maxtoken", { "max_tokens": 8192 }]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义转换器
|
||||
|
||||
创建自定义转换器插件:
|
||||
|
||||
1. 创建转换器文件:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
name: 'my-transformer',
|
||||
transformRequest: async (req, config) => {
|
||||
// 修改请求
|
||||
return req;
|
||||
},
|
||||
transformResponse: async (res, config) => {
|
||||
// 修改响应
|
||||
return res;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
2. 在配置中加载:
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"path": "/path/to/transformer.js",
|
||||
"options": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 实验性转换器
|
||||
|
||||
### gemini-cli(实验性)
|
||||
|
||||
通过 Gemini CLI 对 Gemini 的非官方支持。
|
||||
|
||||
### qwen-cli(实验性)
|
||||
|
||||
通过 Qwen CLI 对 qwen3-coder-plus 的非官方支持。
|
||||
|
||||
### rovo-cli(实验性)
|
||||
|
||||
通过 Atlassian Rovo Dev CLI 对 GPT-5 的非官方支持。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [高级主题](/zh/docs/advanced/custom-router) - 高级路由自定义
|
||||
- [Agent](/zh/docs/advanced/agents) - 使用 Agent 扩展功能
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"version.label": {
|
||||
"message": "Next",
|
||||
"description": "The label for version current"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server": {
|
||||
"message": "服务器",
|
||||
"description": "The label for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server.link.generated-index.title": {
|
||||
"message": "Claude Code Router 服务器",
|
||||
"description": "The generated-index page title for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server.link.generated-index.description": {
|
||||
"message": "部署和管理 Claude Code Router 服务器",
|
||||
"description": "The generated-index page description for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference": {
|
||||
"message": "API 参考",
|
||||
"description": "The label for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference.link.generated-index.title": {
|
||||
"message": "API 参考",
|
||||
"description": "The generated-index page title for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference.link.generated-index.description": {
|
||||
"message": "服务器 API 接口文档",
|
||||
"description": "The generated-index page description for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Configuration": {
|
||||
"message": "配置",
|
||||
"description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Configuration.link.generated-index.title": {
|
||||
"message": "服务器配置",
|
||||
"description": "The generated-index page title for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Configuration.link.generated-index.description": {
|
||||
"message": "服务器配置说明",
|
||||
"description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced": {
|
||||
"message": "高级",
|
||||
"description": "The label for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced.link.generated-index.title": {
|
||||
"message": "高级主题",
|
||||
"description": "The generated-index page title for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced.link.generated-index.description": {
|
||||
"message": "高级功能和自定义",
|
||||
"description": "The generated-index page description for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI": {
|
||||
"message": "CLI",
|
||||
"description": "The label for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI.link.generated-index.title": {
|
||||
"message": "Claude Code Router CLI",
|
||||
"description": "The generated-index page title for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI.link.generated-index.description": {
|
||||
"message": "命令行工具使用指南",
|
||||
"description": "The generated-index page description for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands": {
|
||||
"message": "命令",
|
||||
"description": "The label for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands.link.generated-index.title": {
|
||||
"message": "CLI 命令",
|
||||
"description": "The generated-index page title for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands.link.generated-index.description": {
|
||||
"message": "完整的命令参考",
|
||||
"description": "The generated-index page description for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
id: installation
|
||||
title: 安装
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# 安装
|
||||
|
||||
使用您喜欢的包管理器全局安装 Claude Code Router。
|
||||
|
||||
## 前置要求
|
||||
|
||||
- **Node.js**: >= 18.0.0
|
||||
- **pnpm**: >= 8.0.0(如果使用 pnpm)
|
||||
- 来自您偏好的 LLM 提供商的 API 密钥
|
||||
|
||||
## 通过 npm 安装
|
||||
|
||||
```bash
|
||||
npm install -g @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
## 通过 pnpm 安装
|
||||
|
||||
```bash
|
||||
pnpm add -g @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
## 通过 Yarn 安装
|
||||
|
||||
```bash
|
||||
yarn global add @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
## 验证安装
|
||||
|
||||
安装完成后,验证 `ccr` 命令是否可用:
|
||||
|
||||
```bash
|
||||
ccr --version
|
||||
```
|
||||
|
||||
您应该看到版本号显示。
|
||||
|
||||
## 下一步
|
||||
|
||||
安装完成后,前往 [快速开始](/zh/docs/quick-start) 了解如何配置和使用路由器。
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
id: intro
|
||||
title: 欢迎使用 Claude Code Router
|
||||
sidebar_position: 1
|
||||
slug: /
|
||||
---
|
||||
|
||||
# 欢迎使用 Claude Code Router
|
||||
|
||||
[](https://www.npmjs.com/package/@musistudio/claude-code-router)
|
||||

|
||||

|
||||
|
||||
**Claude Code Router** 是一个强大的工具,允许你在没有 Anthropic 账户的情况下使用 [Claude Code](https://claude.ai/code),并将请求路由到其他 LLM 提供商。
|
||||
|
||||
## 特性
|
||||
|
||||
- **多提供商支持**: 路由到 DeepSeek、Gemini、Groq、OpenRouter 等
|
||||
- **智能路由**: 内置不同任务类型的场景(后台、思考、网络搜索、图像)
|
||||
- **项目级配置**: 每个项目自定义路由
|
||||
- **自定义路由函数**: 编写 JavaScript 定义自己的路由逻辑
|
||||
- **转换器系统**: 无缝适配不同提供商之间的 API 差异
|
||||
- **代理系统**: 可扩展的插件架构,实现自定义功能
|
||||
- **Web UI**: 内置管理界面,方便配置
|
||||
- **CLI 集成**: 与现有的 Claude Code 工作流无缝集成
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
npm install -g @musistudio/claude-code-router
|
||||
# 或
|
||||
pnpm add -g @musistudio/claude-code-router
|
||||
# 或
|
||||
yarn global add @musistudio/claude-code-router
|
||||
```
|
||||
|
||||
### 基本使用
|
||||
|
||||
```bash
|
||||
# 启动路由器服务器
|
||||
ccr start
|
||||
|
||||
# 配置 Claude Code 使用路由器
|
||||
export ANTHROPIC_API_URL="http://localhost:8080/v1"
|
||||
export ANTHROPIC_API_KEY="your-api-key"
|
||||
|
||||
# 现在可以正常使用 Claude Code!
|
||||
claude code
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [安装指南](/docs/installation) - 详细安装说明
|
||||
- [快速开始](/docs/quick-start) - 5 分钟入门
|
||||
- [配置](/docs/config/basic) - 了解如何配置路由器
|
||||
- [CLI 参考](/docs/cli/start) - 完整的 CLI 命令参考
|
||||
|
||||
## 架构
|
||||
|
||||
Claude Code Router 由四个主要组件组成:
|
||||
|
||||
- **CLI** (`@musistudio/claude-code-router`): 提供 `ccr` 命令的命令行工具
|
||||
- **Server** (`@CCR/server`): 处理 API 路由和转换的核心服务器
|
||||
- **Shared** (`@CCR/shared`): 共享常量和工具
|
||||
- **UI** (`@CCR/ui`): Web 管理界面(React + Vite)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT © [musistudio](https://github.com/musistudio)
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
id: quick-start
|
||||
title: 快速开始
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# 快速开始
|
||||
|
||||
5 分钟内启动并运行 Claude Code Router。
|
||||
|
||||
## 1. 启动路由器
|
||||
|
||||
```bash
|
||||
ccr start
|
||||
```
|
||||
|
||||
路由器默认将在 `http://localhost:8080` 上启动。
|
||||
|
||||
## 2. 配置环境变量
|
||||
|
||||
在您的 shell 中设置以下环境变量:
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_API_URL="http://localhost:8080/v1"
|
||||
export ANTHROPIC_API_KEY="your-provider-api-key"
|
||||
```
|
||||
|
||||
或者使用 `ccr activate` 命令获取环境变量:
|
||||
|
||||
```bash
|
||||
eval "$(ccr activate)"
|
||||
```
|
||||
|
||||
## 3. 使用 Claude Code
|
||||
|
||||
现在您可以正常使用 Claude Code:
|
||||
|
||||
```bash
|
||||
claude code
|
||||
```
|
||||
|
||||
您的请求将通过 Claude Code Router 路由到您配置的提供商。
|
||||
|
||||
## 4. 配置提供商(可选)
|
||||
|
||||
要配置多个提供商或自定义路由,使用:
|
||||
|
||||
```bash
|
||||
ccr model
|
||||
```
|
||||
|
||||
这将打开一个交互式菜单来选择和配置模型。
|
||||
|
||||
或者直接编辑配置文件:
|
||||
|
||||
```bash
|
||||
# 在默认编辑器中打开配置
|
||||
ccr config edit
|
||||
```
|
||||
|
||||
配置文件示例 (`~/.claude-code-router/config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "deepseek",
|
||||
"api_base_url": "https://api.deepseek.com/chat/completions",
|
||||
"api_key": "your-deepseek-api-key",
|
||||
"models": ["deepseek-chat", "deepseek-coder"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "deepseek,deepseek-chat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [基础配置](/zh/docs/config/basic) - 了解配置选项
|
||||
- [路由配置](/zh/docs/config/routing) - 配置智能路由规则
|
||||
- [CLI 命令](/zh/docs/cli/start) - 探索所有 CLI 命令
|
||||
@@ -1,220 +0,0 @@
|
||||
# 配置 API
|
||||
|
||||
## GET /api/config
|
||||
|
||||
获取当前服务器配置。
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl http://localhost:3456/api/config \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": 3456,
|
||||
"APIKEY": "sk-xxxxx",
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "sk-...",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
},
|
||||
"transformers": [
|
||||
"anthropic"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## POST /api/config
|
||||
|
||||
更新服务器配置。更新后会自动备份旧配置。
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/api/config \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": 3456,
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4"]
|
||||
}
|
||||
],
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### 配置对象结构
|
||||
|
||||
#### 基础配置
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `HOST` | string | 否 | 监听地址(默认 127.0.0.1) |
|
||||
| `PORT` | integer | 否 | 监听端口(默认 3456) |
|
||||
| `APIKEY` | string | 否 | API 密钥 |
|
||||
| `LOG` | boolean | 否 | 是否启用日志(默认 true) |
|
||||
| `LOG_LEVEL` | string | 否 | 日志级别(debug/info/warn/error) |
|
||||
|
||||
#### Providers 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"name": "provider-name",
|
||||
"baseUrl": "https://api.example.com/v1",
|
||||
"apiKey": "your-api-key",
|
||||
"models": ["model-1", "model-2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `name` | string | 是 | 提供商名称 |
|
||||
| `baseUrl` | string | 是 | API 基础 URL |
|
||||
| `apiKey` | string | 是 | API 密钥 |
|
||||
| `models` | array | 是 | 支持的模型列表 |
|
||||
|
||||
#### Router 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"Router": {
|
||||
"default": "provider,model",
|
||||
"longContextThreshold": 100000,
|
||||
"routes": {
|
||||
"background": "lightweight-model",
|
||||
"think": "powerful-model",
|
||||
"longContext": "long-context-model",
|
||||
"webSearch": "search-model",
|
||||
"image": "vision-model"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Transformers 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"provider": "provider-name",
|
||||
"models": ["model-1"],
|
||||
"options": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
成功:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Config saved successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### 配置备份
|
||||
|
||||
每次更新配置时,旧配置会自动备份到:
|
||||
|
||||
```
|
||||
~/.claude-code-router/config.backup.{timestamp}.json
|
||||
```
|
||||
|
||||
保留最近 3 个备份。
|
||||
|
||||
## GET /api/transformers
|
||||
|
||||
获取服务器加载的所有转换器列表。
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl http://localhost:3456/api/transformers \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"transformers": [
|
||||
{
|
||||
"name": "anthropic",
|
||||
"endpoint": null
|
||||
},
|
||||
{
|
||||
"name": "openai",
|
||||
"endpoint": null
|
||||
},
|
||||
{
|
||||
"name": "gemini",
|
||||
"endpoint": "https://generativelanguage.googleapis.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 转换器列表
|
||||
|
||||
内置转换器:
|
||||
|
||||
- `anthropic` - Anthropic Claude 格式
|
||||
- `openai` - OpenAI 格式
|
||||
- `deepseek` - DeepSeek 格式
|
||||
- `gemini` - Google Gemini 格式
|
||||
- `openrouter` - OpenRouter 格式
|
||||
- `groq` - Groq 格式
|
||||
- `maxtoken` - 调整 max_tokens 参数
|
||||
- `tooluse` - 工具使用转换
|
||||
- `reasoning` - 推理模式转换
|
||||
- `enhancetool` - 增强工具功能
|
||||
|
||||
## 环境变量插值
|
||||
|
||||
配置支持环境变量插值:
|
||||
|
||||
```json
|
||||
{
|
||||
"Providers": [
|
||||
{
|
||||
"apiKey": "$OPENAI_API_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
或使用 `${VAR_NAME}` 格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"baseUrl": "${API_BASE_URL}"
|
||||
}
|
||||
```
|
||||
@@ -1,166 +0,0 @@
|
||||
# 日志 API
|
||||
|
||||
## GET /api/logs/files
|
||||
|
||||
获取所有可用的日志文件列表。
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl http://localhost:3456/api/logs/files \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "ccr-20241226143022.log",
|
||||
"path": "/home/user/.claude-code-router/logs/ccr-20241226143022.log",
|
||||
"size": 1024000,
|
||||
"lastModified": "2024-12-26T14:30:22.000Z"
|
||||
},
|
||||
{
|
||||
"name": "ccr-20241226143021.log",
|
||||
"path": "/home/user/.claude-code-router/logs/ccr-20241226143021.log",
|
||||
"size": 980000,
|
||||
"lastModified": "2024-12-26T14:30:21.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `name` | string | 文件名 |
|
||||
| `path` | string | 完整文件路径 |
|
||||
| `size` | integer | 文件大小(字节) |
|
||||
| `lastModified` | string | 最后修改时间(ISO 8601) |
|
||||
|
||||
文件按修改时间倒序排列。
|
||||
|
||||
## GET /api/logs
|
||||
|
||||
获取指定日志文件的内容。
|
||||
|
||||
### 查询参数
|
||||
|
||||
| 参数 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `file` | string | 否 | 日志文件路径(默认使用 app.log) |
|
||||
|
||||
### 请求示例(获取默认日志)
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3456/api/logs" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### 请求示例(获取指定文件)
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3456/api/logs?file=/home/user/.claude-code-router/logs/ccr-20241226143022.log" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
[
|
||||
"{\"level\":30,\"time\":1703550622000,\"pid\":12345,\"hostname\":\"server\",\"msg\":\"Incoming request\",\"req\":{\"id\":1,\"method\":\"POST\",\"url\":\"/v1/messages\",\"remoteAddress\":\"127.0.0.1\"}}",
|
||||
"{\"level\":30,\"time\":1703550622500,\"pid\":12345,\"hostname\":\"server\",\"msg\":\"Request completed\",\"res\":{\"statusCode\":200,\"responseTime\":500}}",
|
||||
"..."
|
||||
]
|
||||
```
|
||||
|
||||
返回的是日志行数组,每行是一个 JSON 字符串。
|
||||
|
||||
### 日志格式
|
||||
|
||||
日志使用 Pino 格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"level": 30,
|
||||
"time": 1703550622000,
|
||||
"pid": 12345,
|
||||
"hostname": "server",
|
||||
"msg": "Incoming request",
|
||||
"req": {
|
||||
"id": 1,
|
||||
"method": "POST",
|
||||
"url": "/v1/messages",
|
||||
"remoteAddress": "127.0.0.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 日志级别
|
||||
|
||||
| 级别 | 值 | 说明 |
|
||||
|------|------|------|
|
||||
| `trace` | 10 | 最详细的日志 |
|
||||
| `debug` | 20 | 调试信息 |
|
||||
| `info` | 30 | 一般信息 |
|
||||
| `warn` | 40 | 警告信息 |
|
||||
| `error` | 50 | 错误信息 |
|
||||
| `fatal` | 60 | 致命错误 |
|
||||
|
||||
## DELETE /api/logs
|
||||
|
||||
清除指定日志文件的内容。
|
||||
|
||||
### 查询参数
|
||||
|
||||
| 参数 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `file` | string | 否 | 日志文件路径(默认使用 app.log) |
|
||||
|
||||
### 请求示例(清除默认日志)
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:3456/api/logs" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### 请求示例(清除指定文件)
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:3456/api/logs?file=/home/user/.claude-code-router/logs/ccr-20241226143022.log" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Logs cleared successfully"
|
||||
}
|
||||
```
|
||||
|
||||
## 日志位置
|
||||
|
||||
### 服务器日志
|
||||
|
||||
位置:`~/.claude-code-router/logs/`
|
||||
|
||||
文件命名:`ccr-{YYYYMMDD}{HH}{MM}{SS}.log`
|
||||
|
||||
内容:HTTP 请求、API 调用、服务器事件
|
||||
|
||||
### 应用日志
|
||||
|
||||
位置:`~/.claude-code-router/claude-code-router.log`
|
||||
|
||||
内容:路由决策、业务逻辑事件
|
||||
|
||||
## 日志轮转
|
||||
|
||||
服务器日志使用 rotating-file-stream 自动轮转:
|
||||
|
||||
- **maxFiles**: 3 - 保留最近 3 个日志文件
|
||||
- **interval**: 1d - 每天轮转
|
||||
- **maxSize**: 50M - 单个文件最大 50MB
|
||||
@@ -1,220 +0,0 @@
|
||||
# 消息 API
|
||||
|
||||
## POST /v1/messages
|
||||
|
||||
发送消息到 LLM,兼容 Anthropic Claude API 格式。
|
||||
|
||||
### 请求格式
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/v1/messages \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"max_tokens": 1024,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello, Claude!"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `model` | string | 是 | 模型名称(会被路由到实际提供商) |
|
||||
| `messages` | array | 是 | 消息数组 |
|
||||
| `max_tokens` | integer | 是 | 最大生成 Token 数 |
|
||||
| `system` | string | 否 | 系统提示词 |
|
||||
| `tools` | array | 否 | 可用工具列表 |
|
||||
| `stream` | boolean | 否 | 是否使用流式响应(默认 false) |
|
||||
| `temperature` | number | 否 | 温度参数(0-1) |
|
||||
|
||||
### 消息对象格式
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "user|assistant",
|
||||
"content": "string | array"
|
||||
}
|
||||
```
|
||||
|
||||
### 响应格式(非流式)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "msg_xxx",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Hello! How can I help you today?"
|
||||
}
|
||||
],
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"stop_reason": "end_turn",
|
||||
"usage": {
|
||||
"input_tokens": 10,
|
||||
"output_tokens": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 流式响应
|
||||
|
||||
设置 `stream: true` 启用流式响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"max_tokens": 1024,
|
||||
"messages": [...],
|
||||
"stream": true
|
||||
}
|
||||
```
|
||||
|
||||
流式响应事件类型:
|
||||
|
||||
- `message_start` - 消息开始
|
||||
- `content_block_start` - 内容块开始
|
||||
- `content_block_delta` - 内容增量
|
||||
- `content_block_stop` - 内容块结束
|
||||
- `message_delta` - 消息元数据(usage)
|
||||
- `message_stop` - 消息结束
|
||||
|
||||
### 工具使用
|
||||
|
||||
支持函数调用(Tool Use):
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"max_tokens": 1024,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What's the weather like?"
|
||||
}
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"description": "Get the current weather",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "City name"
|
||||
}
|
||||
},
|
||||
"required": ["location"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 多模态支持
|
||||
|
||||
支持图片输入:
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "image",
|
||||
"source": {
|
||||
"type": "base64",
|
||||
"media_type": "image/png",
|
||||
"data": "iVBORw0KGgo..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Describe this image"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## POST /v1/messages/count_tokens
|
||||
|
||||
计算消息的 Token 数量。
|
||||
|
||||
### 请求格式
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/v1/messages/count_tokens \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"model": "claude-3-5-sonnet-20241022",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello!"
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"system": "You are a helpful assistant."
|
||||
}'
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `model` | string | 是 | 模型名称 |
|
||||
| `messages` | array | 是 | 消息数组 |
|
||||
| `tools` | array | 否 | 工具列表 |
|
||||
| `system` | string | 否 | 系统提示词 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"input_tokens": 42
|
||||
}
|
||||
```
|
||||
|
||||
## 错误响应
|
||||
|
||||
### 400 Bad Request
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"type": "invalid_request_error",
|
||||
"message": "messages is required"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 401 Unauthorized
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"type": "authentication_error",
|
||||
"message": "Invalid API key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 500 Internal Server Error
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"type": "api_error",
|
||||
"message": "Failed to connect to provider"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,88 +0,0 @@
|
||||
# API 概览
|
||||
|
||||
Claude Code Router Server 提供了完整的 HTTP API,支持:
|
||||
|
||||
- **消息 API**:兼容 Anthropic Claude API 的消息接口
|
||||
- **配置 API**:读取和更新服务器配置
|
||||
- **日志 API**:查看和管理服务日志
|
||||
- **工具 API**:计算 Token 数量
|
||||
|
||||
## 基础信息
|
||||
|
||||
**Base URL**: `http://localhost:3456`
|
||||
|
||||
**认证方式**: API Key(通过 `x-api-key` 请求头)
|
||||
|
||||
```bash
|
||||
curl -H "x-api-key: your-api-key" http://localhost:3456/api/config
|
||||
```
|
||||
|
||||
## API 端点列表
|
||||
|
||||
### 消息相关
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/v1/messages` | POST | 发送消息(兼容 Anthropic API) |
|
||||
| `/v1/messages/count_tokens` | POST | 计算消息的 Token 数量 |
|
||||
|
||||
### 配置管理
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/config` | GET | 获取当前配置 |
|
||||
| `/api/config` | POST | 更新配置 |
|
||||
| `/api/transformers` | GET | 获取可用的转换器列表 |
|
||||
|
||||
### 日志管理
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/logs/files` | GET | 获取日志文件列表 |
|
||||
| `/api/logs` | GET | 获取日志内容 |
|
||||
| `/api/logs` | DELETE | 清除日志 |
|
||||
|
||||
### 服务管理
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/restart` | POST | 重启服务 |
|
||||
| `/ui` | GET | Web 管理界面 |
|
||||
| `/ui/` | GET | Web 管理界面(重定向) |
|
||||
|
||||
## 认证
|
||||
|
||||
### API Key 认证
|
||||
|
||||
在请求头中添加 API Key:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/v1/messages \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '...'
|
||||
```
|
||||
|
||||
## 流式响应
|
||||
|
||||
消息 API 支持流式响应(Server-Sent Events):
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3456/v1/messages \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{"stream": true, ...}'
|
||||
```
|
||||
|
||||
流式响应格式:
|
||||
|
||||
```
|
||||
event: message_start
|
||||
data: {"type":"message_start","message":{...}}
|
||||
|
||||
event: content_block_delta
|
||||
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hello"}}
|
||||
|
||||
event: message_stop
|
||||
data: {"type":"message_stop"}
|
||||
```
|
||||
@@ -1,182 +0,0 @@
|
||||
# Server 部署
|
||||
|
||||
Claude Code Router Server 支持多种部署方式,从本地开发到生产环境。
|
||||
|
||||
## Docker 部署(推荐)
|
||||
|
||||
### 使用 Docker Hub 镜像
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name claude-code-router \
|
||||
-p 3456:3456 \
|
||||
-v ~/.claude-code-router:/app/.claude-code-router \
|
||||
musistudio/claude-code-router:latest
|
||||
```
|
||||
|
||||
### 使用 Docker Compose
|
||||
|
||||
创建 `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
claude-code-router:
|
||||
image: musistudio/claude-code-router:latest
|
||||
container_name: claude-code-router
|
||||
ports:
|
||||
- "3456:3456"
|
||||
volumes:
|
||||
- ./config:/app/.claude-code-router
|
||||
environment:
|
||||
- LOG_LEVEL=info
|
||||
- HOST=0.0.0.0
|
||||
- PORT=3456
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
启动服务:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 自定义构建
|
||||
|
||||
从源码构建 Docker 镜像:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/musistudio/claude-code-router.git
|
||||
cd claude-code-router
|
||||
docker build -t claude-code-router:latest .
|
||||
```
|
||||
|
||||
## 配置文件挂载
|
||||
|
||||
将配置文件挂载到容器中:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name claude-code-router \
|
||||
-p 3456:3456 \
|
||||
-v $(pwd)/config.json:/app/.claude-code-router/config.json \
|
||||
musistudio/claude-code-router:latest
|
||||
```
|
||||
|
||||
配置文件示例:
|
||||
|
||||
```json5
|
||||
{
|
||||
// 服务器配置
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": 3456,
|
||||
"APIKEY": "your-api-key-here",
|
||||
|
||||
// 日志配置
|
||||
"LOG": true,
|
||||
"LOG_LEVEL": "info",
|
||||
|
||||
// LLM 提供商配置
|
||||
"Providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "$OPENAI_API_KEY",
|
||||
"models": ["gpt-4", "gpt-3.5-turbo"]
|
||||
}
|
||||
],
|
||||
|
||||
// 路由配置
|
||||
"Router": {
|
||||
"default": "openai,gpt-4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
支持通过环境变量覆盖配置:
|
||||
|
||||
| 变量名 | 说明 | 默认值 |
|
||||
|--------|------|--------|
|
||||
| `HOST` | 监听地址 | `127.0.0.1` |
|
||||
| `PORT` | 监听端口 | `3456` |
|
||||
| `APIKEY` | API 密钥 | - |
|
||||
| `LOG_LEVEL` | 日志级别 | `debug` |
|
||||
| `LOG` | 是否启用日志 | `true` |
|
||||
|
||||
## 生产环境建议
|
||||
|
||||
### 1. 使用反向代理
|
||||
|
||||
使用 Nginx 作为反向代理:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3456;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 配置 HTTPS
|
||||
|
||||
使用 Let's Encrypt 获取免费证书:
|
||||
|
||||
```bash
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
### 3. 日志管理
|
||||
|
||||
配置日志轮转和持久化:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
claude-code-router:
|
||||
image: musistudio/claude-code-router:latest
|
||||
volumes:
|
||||
- ./logs:/app/.claude-code-router/logs
|
||||
environment:
|
||||
- LOG_LEVEL=warn
|
||||
```
|
||||
|
||||
### 4. 健康检查
|
||||
|
||||
配置 Docker 健康检查:
|
||||
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3456/api/config"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
```
|
||||
|
||||
## 访问 Web UI
|
||||
|
||||
部署完成后,访问 Web UI:
|
||||
|
||||
```
|
||||
http://localhost:3456/ui/
|
||||
```
|
||||
|
||||
通过 Web UI 可以:
|
||||
- 查看和管理配置
|
||||
- 监控日志
|
||||
- 查看服务状态
|
||||
|
||||
## 二次开发
|
||||
|
||||
如果需要基于 CCR Server 进行二次开发,请查看 [API 参考](/docs/category/api)。
|
||||
@@ -1,77 +0,0 @@
|
||||
# Server 简介
|
||||
|
||||
Claude Code Router Server 是一个核心服务组件,负责将 Claude Code 的 API 请求路由到不同的 LLM 提供商。它提供了完整的 HTTP API,支持:
|
||||
|
||||
- **API 请求路由**:将 Anthropic 格式的请求转换为各种提供商的 API 格式
|
||||
- **认证与授权**:支持 API Key 认证
|
||||
- **配置管理**:动态配置提供商、路由规则和转换器
|
||||
- **Web UI**:内置管理界面
|
||||
- **日志系统**:完整的请求日志记录
|
||||
|
||||
## 架构概述
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
|
||||
│ Claude Code │────▶│ CCR Server │────▶│ LLM Provider │
|
||||
│ Client │ │ (Router + │ │ (OpenAI/ │
|
||||
└─────────────┘ │ Transformer) │ │ Gemini/etc)│
|
||||
└──────────────────┘ └──────────────┘
|
||||
│
|
||||
├─ Web UI
|
||||
├─ Config API
|
||||
└─ Logs API
|
||||
```
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 请求路由
|
||||
- 基于 Token 数量的智能路由
|
||||
- 项目级路由配置
|
||||
- 自定义路由函数
|
||||
- 场景化路由(background、think、longContext 等)
|
||||
|
||||
### 2. 请求转换
|
||||
- 支持多种 LLM 提供商的 API 格式转换
|
||||
- 内置转换器:Anthropic、DeepSeek、Gemini、OpenRouter、Groq 等
|
||||
- 可扩展的转换器系统
|
||||
|
||||
### 3. Agent 系统
|
||||
- 插件式的 Agent 架构
|
||||
- 内置图片处理 Agent
|
||||
- 自定义 Agent 支持
|
||||
|
||||
### 4. 配置管理
|
||||
- JSON5 格式配置文件
|
||||
- 环境变量插值
|
||||
- 配置热更新(需重启服务)
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景一:个人本地服务
|
||||
在本地运行服务,供个人 Claude Code 使用:
|
||||
|
||||
```bash
|
||||
ccr start
|
||||
```
|
||||
|
||||
### 场景二:团队共享服务
|
||||
使用 Docker 部署,为团队成员提供共享服务:
|
||||
|
||||
```bash
|
||||
docker run -d -p 3456:3456 musistudio/claude-code-router
|
||||
```
|
||||
|
||||
### 场景三:二次开发
|
||||
基于暴露的 API 构建自定义应用:
|
||||
|
||||
```bash
|
||||
GET /api/config
|
||||
POST /v1/messages
|
||||
GET /api/logs
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [Docker 部署指南](/docs/server/deployment) - 学习如何部署服务
|
||||
- [API 参考](/docs/category/api) - 查看完整的 API 文档
|
||||
- [配置说明](/docs/category/server-config) - 了解服务器配置选项
|
||||
@@ -1,90 +0,0 @@
|
||||
{
|
||||
"version.label": {
|
||||
"message": "Next",
|
||||
"description": "The label for version current"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server": {
|
||||
"message": "服务器",
|
||||
"description": "The label for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server.link.generated-index.title": {
|
||||
"message": "Claude Code Router 服务器",
|
||||
"description": "The generated-index page title for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Server.link.generated-index.description": {
|
||||
"message": "部署和管理 Claude Code Router 服务器",
|
||||
"description": "The generated-index page description for category 'Server' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference": {
|
||||
"message": "API 参考",
|
||||
"description": "The label for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference.link.generated-index.title": {
|
||||
"message": "API 参考",
|
||||
"description": "The generated-index page title for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.API Reference.link.generated-index.description": {
|
||||
"message": "服务器 API 接口文档",
|
||||
"description": "The generated-index page description for category 'API Reference' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.server-configuration-category": {
|
||||
"message": "配置",
|
||||
"description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.server-configuration-category.link.generated-index.title": {
|
||||
"message": "服务器配置",
|
||||
"description": "The generated-index page title for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.server-configuration-category.link.generated-index.description": {
|
||||
"message": "服务器配置说明",
|
||||
"description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced": {
|
||||
"message": "高级",
|
||||
"description": "The label for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced.link.generated-index.title": {
|
||||
"message": "高级主题",
|
||||
"description": "The generated-index page title for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Advanced.link.generated-index.description": {
|
||||
"message": "高级功能和自定义",
|
||||
"description": "The generated-index page description for category 'Advanced' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI": {
|
||||
"message": "CLI",
|
||||
"description": "The label for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI.link.generated-index.title": {
|
||||
"message": "Claude Code Router CLI",
|
||||
"description": "The generated-index page title for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.CLI.link.generated-index.description": {
|
||||
"message": "命令行工具使用指南",
|
||||
"description": "The generated-index page description for category 'CLI' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands": {
|
||||
"message": "命令",
|
||||
"description": "The label for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands.link.generated-index.title": {
|
||||
"message": "CLI 命令",
|
||||
"description": "The generated-index page title for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.Commands.link.generated-index.description": {
|
||||
"message": "完整的命令参考",
|
||||
"description": "The generated-index page description for category 'Commands' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.cli-configuration-category": {
|
||||
"message": "配置",
|
||||
"description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.cli-configuration-category.link.generated-index.title": {
|
||||
"message": "CLI 配置",
|
||||
"description": "The generated-index page title for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
},
|
||||
"sidebar.tutorialSidebar.category.cli-configuration-category.link.generated-index.description": {
|
||||
"message": "CLI 配置指南",
|
||||
"description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'"
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
---
|
||||
title: ccr model
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# ccr model
|
||||
|
||||
交互式模型选择和配置。
|
||||
|
||||
## 用法
|
||||
|
||||
```bash
|
||||
ccr model [命令]
|
||||
```
|
||||
|
||||
## 命令
|
||||
|
||||
### 选择模型
|
||||
|
||||
交互式选择模型:
|
||||
|
||||
```bash
|
||||
ccr model
|
||||
```
|
||||
|
||||
这将显示一个包含可用提供商和模型的交互式菜单。
|
||||
|
||||
### 设置默认模型
|
||||
|
||||
直接设置默认模型:
|
||||
|
||||
```bash
|
||||
ccr model set <provider>,<model>
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
ccr model set deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### 列出模型
|
||||
|
||||
列出所有配置的模型:
|
||||
|
||||
```bash
|
||||
ccr model list
|
||||
```
|
||||
|
||||
### 添加模型
|
||||
|
||||
添加新模型到配置:
|
||||
|
||||
```bash
|
||||
ccr model add <provider>,<model>
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
ccr model add groq,llama-3.3-70b-versatile
|
||||
```
|
||||
|
||||
### 删除模型
|
||||
|
||||
从配置中删除模型:
|
||||
|
||||
```bash
|
||||
ccr model remove <provider>,<model>
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
### 交互式选择
|
||||
|
||||
```bash
|
||||
$ ccr model
|
||||
|
||||
? 选择一个提供商: deepseek
|
||||
? 选择一个模型: deepseek-chat
|
||||
|
||||
默认模型设置为: deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### 直接配置
|
||||
|
||||
```bash
|
||||
ccr model set deepseek,deepseek-chat
|
||||
```
|
||||
|
||||
### 查看当前配置
|
||||
|
||||
```bash
|
||||
ccr model list
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
已配置的模型:
|
||||
deepseek,deepseek-chat (默认)
|
||||
groq,llama-3.3-70b-versatile
|
||||
gemini,gemini-2.5-pro
|
||||
```
|
||||
|
||||
## 交互式功能
|
||||
|
||||
`ccr model` 命令提供以下功能:
|
||||
|
||||
1. **查看当前配置**:查看所有已配置的模型和路由器设置
|
||||
2. **切换模型**:快速更改每个路由器类型使用的模型
|
||||
3. **添加新模型**:向现有提供商添加模型
|
||||
4. **创建新提供商**:设置完整的提供商配置,包括:
|
||||
- 提供商名称和 API 端点
|
||||
- API 密钥
|
||||
- 可用模型
|
||||
- 转换器配置,支持:
|
||||
- 多个转换器(openrouter、deepseek、gemini 等)
|
||||
- 转换器选项(例如,带自定义限制的 maxtoken)
|
||||
- 提供商特定路由(例如,OpenRouter 提供商偏好)
|
||||
|
||||
CLI 工具会验证所有输入并提供有用的提示来引导您完成配置过程,使管理复杂设置变得容易,无需手动编辑 JSON 文件。
|
||||
|
||||
## 相关命令
|
||||
|
||||
- [ccr start](/zh/docs/cli/start) - 启动服务器
|
||||
- [ccr config](/zh/docs/cli/other-commands#ccr-config) - 编辑配置
|
||||