diff --git a/src-tauri/src/proxy/handlers.rs b/src-tauri/src/proxy/handlers.rs index 029671df..faeefec0 100644 --- a/src-tauri/src/proxy/handlers.rs +++ b/src-tauri/src/proxy/handlers.rs @@ -5,7 +5,7 @@ //! 重构后的结构: //! - 通用逻辑提取到 `handler_context` 和 `response_processor` 模块 //! - 各 handler 只保留独特的业务逻辑 -//! - Claude 的格式转换逻辑保留在此文件(独有功能) +//! - Claude 的格式转换逻辑保留在此文件(用于 OpenRouter 旧接口回退) use super::{ error_mapper::{get_error_message, map_proxy_error_to_status}, @@ -54,8 +54,8 @@ pub async fn get_status(State(state): State) -> Result, headers: axum::http::HeaderMap, @@ -112,7 +112,7 @@ pub async fn handle_messages( /// Claude 格式转换处理(独有逻辑) /// -/// 处理 OpenRouter 等需要格式转换的中转服务 +/// 处理 OpenRouter 旧 OpenAI 兼容接口的回退方案(当前默认不启用) async fn handle_claude_transform( response: reqwest::Response, ctx: &RequestContext, diff --git a/src-tauri/src/proxy/providers/adapter.rs b/src-tauri/src/proxy/providers/adapter.rs index c0372e03..6393cf62 100644 --- a/src-tauri/src/proxy/providers/adapter.rs +++ b/src-tauri/src/proxy/providers/adapter.rs @@ -87,7 +87,7 @@ pub trait ProviderAdapter: Send + Sync { /// 是否需要格式转换 /// /// 默认返回 `false`(透传模式)。 - /// 仅当供应商需要格式转换时(如 Claude + OpenRouter)才返回 `true`。 + /// 仅当供应商需要格式转换时(如 Claude + OpenRouter 旧 OpenAI 兼容接口)才返回 `true`。 /// /// # Arguments /// * `provider` - Provider 配置 diff --git a/src-tauri/src/proxy/providers/claude.rs b/src-tauri/src/proxy/providers/claude.rs index 57ca328c..ff7a5592 100644 --- a/src-tauri/src/proxy/providers/claude.rs +++ b/src-tauri/src/proxy/providers/claude.rs @@ -5,7 +5,7 @@ //! ## 认证模式 //! - **Claude**: Anthropic 官方 API (x-api-key + anthropic-version) //! - **ClaudeAuth**: 中转服务 (仅 Bearer 认证,无 x-api-key) -//! - **OpenRouter**: 需要 Anthropic ↔ OpenAI 格式转换 +//! - **OpenRouter**: 已支持 Claude Code 兼容接口,默认透传(保留旧转换逻辑备用) use super::{AuthInfo, AuthStrategy, ProviderAdapter, ProviderType}; use crate::provider::Provider; @@ -28,10 +28,8 @@ impl ClaudeAdapter { /// - Claude: 默认 Anthropic 官方 pub fn provider_type(&self, provider: &Provider) -> ProviderType { // 检测 OpenRouter - if let Ok(base_url) = self.extract_base_url(provider) { - if base_url.contains("openrouter.ai") { - return ProviderType::OpenRouter; - } + if self.is_openrouter(provider) { + return ProviderType::OpenRouter; } // 检测 ClaudeAuth (仅 Bearer 认证) @@ -194,12 +192,17 @@ impl ProviderAdapter for ClaudeAdapter { } fn build_url(&self, base_url: &str, endpoint: &str) -> String { - // OpenRouter 使用 /v1/chat/completions - if base_url.contains("openrouter.ai") { - return format!("{}/v1/chat/completions", base_url.trim_end_matches('/')); - } + // NOTE: + // 过去 OpenRouter 只有 OpenAI Chat Completions 兼容接口,需要把 Claude 的 `/v1/messages` + // 映射到 `/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('/')); + // } - // Anthropic 直连 format!( "{}/{}", base_url.trim_end_matches('/'), @@ -215,19 +218,25 @@ impl ProviderAdapter for ClaudeAdapter { .header("x-api-key", &auth.api_key) .header("anthropic-version", "2023-06-01"), // ClaudeAuth 中转服务: 仅 Bearer,无 x-api-key - AuthStrategy::ClaudeAuth => { - request.header("Authorization", format!("Bearer {}", auth.api_key)) - } + AuthStrategy::ClaudeAuth => request + .header("Authorization", format!("Bearer {}", auth.api_key)) + .header("anthropic-version", "2023-06-01"), // OpenRouter: Bearer - AuthStrategy::Bearer => { - request.header("Authorization", format!("Bearer {}", auth.api_key)) - } + AuthStrategy::Bearer => request + .header("Authorization", format!("Bearer {}", auth.api_key)) + .header("anthropic-version", "2023-06-01"), _ => request, } } - fn needs_transform(&self, provider: &Provider) -> bool { - self.is_openrouter(provider) + fn needs_transform(&self, _provider: &Provider) -> bool { + // NOTE: + // OpenRouter 已推出 Claude Code 兼容接口(可直接处理 `/v1/messages`),默认不再启用 + // Anthropic ↔ OpenAI 的格式转换。 + // + // 如果未来需要回退到旧的 OpenAI Chat Completions 方案,可恢复下面这行: + // self.is_openrouter(_provider) + false } fn transform_request( @@ -401,7 +410,7 @@ mod tests { fn test_build_url_openrouter() { let adapter = ClaudeAdapter::new(); let url = adapter.build_url("https://openrouter.ai/api", "/v1/messages"); - assert_eq!(url, "https://openrouter.ai/api/v1/chat/completions"); + assert_eq!(url, "https://openrouter.ai/api/v1/messages"); } #[test] @@ -420,6 +429,6 @@ mod tests { "ANTHROPIC_BASE_URL": "https://openrouter.ai/api" } })); - assert!(adapter.needs_transform(&openrouter_provider)); + assert!(!adapter.needs_transform(&openrouter_provider)); } } diff --git a/src-tauri/src/proxy/providers/mod.rs b/src-tauri/src/proxy/providers/mod.rs index f604f60a..ee59c007 100644 --- a/src-tauri/src/proxy/providers/mod.rs +++ b/src-tauri/src/proxy/providers/mod.rs @@ -48,17 +48,21 @@ pub enum ProviderType { Gemini, /// Google Gemini CLI (OAuth Bearer) GeminiCli, - /// OpenRouter (需要 Anthropic ↔ OpenAI 格式转换) + /// OpenRouter(已支持 Claude Code 兼容接口,默认透传;保留旧转换逻辑备用) OpenRouter, } impl ProviderType { /// 是否需要格式转换 /// - /// OpenRouter 需要将 Anthropic 格式转换为 OpenAI 格式 + /// 过去 OpenRouter 需要将 Anthropic 格式转换为 OpenAI 格式; + /// 现在默认关闭转换(因为 OpenRouter 已支持 Claude Code 兼容接口)。 #[allow(dead_code)] pub fn needs_transform(&self) -> bool { - matches!(self, ProviderType::OpenRouter) + match self { + ProviderType::OpenRouter => false, + _ => false, + } } /// 获取默认端点 @@ -215,7 +219,7 @@ mod tests { assert!(!ProviderType::Codex.needs_transform()); assert!(!ProviderType::Gemini.needs_transform()); assert!(!ProviderType::GeminiCli.needs_transform()); - assert!(ProviderType::OpenRouter.needs_transform()); + assert!(!ProviderType::OpenRouter.needs_transform()); } #[test]