Skip to content

feat: Add model routing configuration support#1980

Open
papperrollinggery wants to merge 1 commit intofarion1231:mainfrom
papperrollinggery:feat/model-routing
Open

feat: Add model routing configuration support#1980
papperrollinggery wants to merge 1 commit intofarion1231:mainfrom
papperrollinggery:feat/model-routing

Conversation

@papperrollinggery
Copy link
Copy Markdown

Summary / 概述

This PR adds Model Routing feature to cc-switch, allowing a single Provider to route different Claude models to different backends based on the requested model name.

本 PR 为 cc-switch 添加了模型路由功能,允许单个 Provider 根据请求的模型名称自动路由到不同的后端服务。

Problem / 问题

Before (之前) After (之后)
Single apiFormat per Provider Multiple routing rules per Provider
All models routed to same backend Model-specific routing
Multiple Providers needed for different formats One Provider handles all

Example / 示例:

// Before: Only one apiFormat
{ "meta": { "apiFormat": "anthropic" } }

// After: Multiple routes per Provider
{ "meta": {
    "apiFormat": "anthropic",
    "modelRoutingConfig": {
      "enabled": true,
      "routes": [
        { "sourceModel": "Claude Opus", "target": { "apiFormat": "openai_chat", "modelName": "gemini-2.5-pro" } },
        { "sourceModel": "Claude Sonnet", "target": { "apiFormat": "openai_chat", "modelName": "gemini-2.5-flash" } }
      ]
    }
  }
}

Related Issue / 关联 Issue

Fixes #


Technical Details / 技术细节

Architecture / 架构

Claude Code → cc-switch → NewAPI (localhost:3000) → AI Platforms
                      ↓ [routing by model]
              Claude Opus   → gemini-2.5-pro (OpenAI Chat)
              Claude Sonnet → gemini-2.5-flash (OpenAI Chat)
              Claude Haiku  → MiniMax-Text-01 (Anthropic)

Routing Flow / 路由流程

  1. Extract model field from request body (case-insensitive)
  2. Look up matching sourceModel in modelRoutingConfig.routes
  3. Override base_url, api_format, and model_name based on target config
  4. Fall back to default config if no route matches

Files Changed / 修改的文件

Backend (Rust) - 后端:

  • src-tauri/src/provider.rs - Added ModelRoutingConfig, ModelRoute, RouteTarget, RouteFallback structs
  • src-tauri/src/proxy/providers/claude.rs - Added get_claude_api_format_with_model(), get_routed_base_url()
  • src-tauri/src/proxy/forwarder.rs - Integrated routing in 3 locations (lines 764, 870, 1383)
  • src-tauri/src/proxy/providers/mod.rs - Exported new functions

Frontend (TypeScript) - 前端:

  • src/components/providers/forms/ModelRoutingConfigPanel.tsx - NEW - UI panel component (426 lines)
  • src/components/providers/forms/hooks/useModelRoutingConfig.ts - NEW - State management hook (90 lines)
  • src/components/providers/forms/ProviderAdvancedConfig.tsx - Integrated ModelRoutingConfigPanel
  • src/components/providers/forms/ProviderForm.tsx - State and persistence management

i18n Files / 国际化

  • src/i18n/locales/zh.json - 中文翻译 (+18 lines)
  • src/i18n/locales/en.json - English translation (+18 lines)

Testing / 测试

Build Verification / 构建验证

Test Command Status
TypeScript check pnpm typecheck ✅ Pass
Format check pnpm format:check ✅ Pass
Rust check cargo check ✅ Pass
Rust clippy cargo clippy ✅ Pass
Production build pnpm tauri build ✅ Pass

Manual Testing / 手动测试

  1. Open cc-switch → Add/Edit Provider
  2. Set Base URL to NewAPI endpoint (e.g., http://localhost:3000)
  3. Expand "Advanced Configuration"
  4. Enable "Model Routing Configuration"
  5. Add routing rules:
    • Source Model: Claude Opus → Target: gemini-2.5-pro (OpenAI Chat)
    • Source Model: Claude Sonnet → Target: gemini-2.5-flash (OpenAI Chat)
    • Source Model: Claude Haiku → Target: MiniMax-Text-01 (Anthropic)
  6. Save and test with different Claude models

Edge Cases / 边界情况

Case Expected Behavior
No matching route Use fallback.apiFormat if configured
No fallback configured Use default Provider apiFormat
Routing disabled Use default Provider behavior
Case mismatch Case-insensitive matching (e.g., "Claude Opus" = "claude opus")

Breaking Changes / 破坏性变更

None / 无 - This feature is additive and backward-compatible.

Existing Providers without modelRoutingConfig work unchanged. The feature only activates when modelRoutingConfig.enabled = true.


Checklist / 检查清单

  • pnpm typecheck passes
  • pnpm format:check passes
  • cargo clippy passes
  • Updated i18n files
  • Backend compiles successfully
  • Frontend compiles successfully
  • Application builds and runs

Commit / 提交

feat(provider): add model routing for single Provider

- Support routing different Claude models to different backends
- Add ModelRoutingConfig data structures to provider.rs
- Implement get_claude_api_format_with_model() and get_routed_base_url()
- Add ModelRoutingConfigPanel UI component
- Add useModelRoutingConfig state management hook
- Integrate routing logic in forwarder.rs
- Update zh.json and en.json translations

Notes / 备注

  1. API Key Management: This implementation assumes NewAPI handles API key injection. Direct routing to external APIs requires additional key management.

  2. Claude Adapter Only: Current implementation is for Claude adapter only. Other adapters (Gemini, MiniMax, etc.) can be extended similarly.

  3. Security: All routing happens on the client side. Ensure the NewAPI endpoint is trusted and properly secured.

- Support routing different Claude models to different backends
- Add ModelRoutingConfig data structures to provider.rs
- Implement get_claude_api_format_with_model() and get_routed_base_url()
- Add ModelRoutingConfigPanel UI component (426 lines)
- Add useModelRoutingConfig state management hook (90 lines)
- Integrate routing logic in forwarder.rs
- Update zh.json and en.json translations

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@papperrollinggery papperrollinggery changed the title feat(provider): add model routing for single Provider feat: Add model routing configuration support Apr 9, 2026
@farion1231
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78b08fa38e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if let Some(route) = routing_config.routes.iter().find(|r| {
r.source_model.eq_ignore_ascii_case(model)
}) {
return Box::leak(route.target.api_format.clone().into_boxed_str());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop leaking strings when selecting routed API format

get_claude_api_format_with_model now allocates and leaks memory on every matched route by returning Box::leak(...into_boxed_str()). Because this path runs per request, a busy long-lived proxy process will accumulate unreclaimed heap memory over time. This should return a non-leaking owned value (or map to known static literals) instead of manufacturing 'static strings at runtime.

Useful? React with 👍 / 👎.

Comment on lines +871 to +875
base_url = crate::proxy::providers::get_routed_base_url(
provider,
original_model,
&base_url,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rewrite request model when a routing rule matches

The routing integration in forward only replaces base_url (and separately API format), but it never updates the outgoing request model to RouteTarget.model_name. As a result, routes intended to map Claude model IDs to different backend model IDs still send the original Claude model upstream, which can cause wrong-model execution or upstream rejection for non-Claude backends. The matched route’s target model needs to be applied to the request body before sending.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants