From 9b1c659f077657834c0e7b4de69af6ebc9339933 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Wed, 24 Dec 2025 13:53:19 +0800 Subject: [PATCH] feat(proxy): add openrouter_compat_mode for optional format conversion Add configurable OpenRouter compatibility mode that enables Anthropic to OpenAI format conversion. When enabled, rewrites endpoint to /v1/chat/completions and transforms request/response formats. Defaults to enabled for OpenRouter. --- src-tauri/src/proxy/forwarder.rs | 13 +++++++-- src-tauri/src/proxy/providers/claude.rs | 37 +++++++++++++++++++------ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index 60b1db9a2..334182f92 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -430,12 +430,19 @@ impl RequestForwarder { let base_url = adapter.extract_base_url(provider)?; log::info!("[{}] base_url: {}", adapter.name(), base_url); - // 使用适配器构建 URL - let url = adapter.build_url(&base_url, endpoint); - // 检查是否需要格式转换 let needs_transform = adapter.needs_transform(provider); + let effective_endpoint = + if needs_transform && adapter.name() == "Claude" && endpoint == "/v1/messages" { + "/v1/chat/completions" + } else { + endpoint + }; + + // 使用适配器构建 URL + let url = adapter.build_url(&base_url, effective_endpoint); + // 记录原始请求 JSON log::info!( "[{}] ====== 请求开始 ======\n>>> 原始请求 JSON:\n{}", diff --git a/src-tauri/src/proxy/providers/claude.rs b/src-tauri/src/proxy/providers/claude.rs index ba6254252..a0dfd87e7 100644 --- a/src-tauri/src/proxy/providers/claude.rs +++ b/src-tauri/src/proxy/providers/claude.rs @@ -48,6 +48,24 @@ impl ClaudeAdapter { false } + /// 检测 OpenRouter 是否启用兼容模式 + fn is_openrouter_compat_enabled(&self, provider: &Provider) -> bool { + if !self.is_openrouter(provider) { + return false; + } + + let raw = provider.settings_config.get("openrouter_compat_mode"); + match raw { + Some(serde_json::Value::Bool(enabled)) => *enabled, + Some(serde_json::Value::Number(num)) => num.as_i64().unwrap_or(0) != 0, + Some(serde_json::Value::String(value)) => { + let normalized = value.trim().to_lowercase(); + normalized == "true" || normalized == "1" + } + _ => true, + } + } + /// 检测是否为仅 Bearer 认证模式 fn is_bearer_only_mode(&self, provider: &Provider) -> bool { // 检查 settings_config 中的 auth_mode @@ -197,11 +215,7 @@ impl ProviderAdapter for ClaudeAdapter { // 映射到 `/v1/chat/completions`,并做 Anthropic ↔ OpenAI 的格式转换。 // // 现在 OpenRouter 已推出 Claude Code 兼容接口,因此默认直接透传 endpoint。 - // 如需回退旧逻辑,可恢复下面这段分支: - // - // if base_url.contains("openrouter.ai") { - // return format!("{}/v1/chat/completions", base_url.trim_end_matches('/')); - // } + // 如需回退旧逻辑,可在 forwarder 中根据 needs_transform 改写 endpoint。 format!( "{}/{}", @@ -235,8 +249,7 @@ impl ProviderAdapter for ClaudeAdapter { // Anthropic ↔ OpenAI 的格式转换。 // // 如果未来需要回退到旧的 OpenAI Chat Completions 方案,可恢复下面这行: - // self.is_openrouter(_provider) - false + self.is_openrouter_compat_enabled(_provider) } fn transform_request( @@ -430,6 +443,14 @@ mod tests { "ANTHROPIC_BASE_URL": "https://openrouter.ai/api" } })); - assert!(!adapter.needs_transform(&openrouter_provider)); + assert!(adapter.needs_transform(&openrouter_provider)); + + let openrouter_disabled = create_provider(json!({ + "env": { + "ANTHROPIC_BASE_URL": "https://openrouter.ai/api" + }, + "openrouter_compat_mode": false + })); + assert!(!adapter.needs_transform(&openrouter_disabled)); } }