diff --git a/src-tauri/src/proxy/handlers.rs b/src-tauri/src/proxy/handlers.rs index 644ef353c..ec97efbc8 100644 --- a/src-tauri/src/proxy/handlers.rs +++ b/src-tauri/src/proxy/handlers.rs @@ -56,12 +56,19 @@ pub async fn get_status(State(state): State) -> Result, + uri: axum::http::Uri, headers: axum::http::HeaderMap, Json(body): Json, ) -> Result { let mut ctx = RequestContext::new(&state, &body, &headers, AppType::Claude, "Claude", "claude").await?; + // 提取完整路径+query(如 "/v1/messages?beta=true"),透传客户端 URI + let endpoint = uri + .path_and_query() + .map(|pq| pq.as_str()) + .unwrap_or(uri.path()); + let is_stream = body .get("stream") .and_then(|s| s.as_bool()) @@ -72,7 +79,7 @@ pub async fn handle_messages( let result = match forwarder .forward_with_retry( &AppType::Claude, - "/v1/messages", + endpoint, body.clone(), headers, ctx.get_providers(), diff --git a/src-tauri/src/proxy/providers/claude.rs b/src-tauri/src/proxy/providers/claude.rs index 82f365d3b..a64f0c980 100644 --- a/src-tauri/src/proxy/providers/claude.rs +++ b/src-tauri/src/proxy/providers/claude.rs @@ -251,6 +251,8 @@ impl ProviderAdapter for ClaudeAdapter { // // 现在 OpenRouter 已推出 Claude Code 兼容接口,因此默认直接透传 endpoint。 // 如需回退旧逻辑,可在 forwarder 中根据 needs_transform 改写 endpoint。 + // + // ?beta=true 不再由代理注入,而是由客户端自身发送并透传。 let mut base = format!( "{}/{}", @@ -263,19 +265,7 @@ impl ProviderAdapter for ClaudeAdapter { base = base.replace("/v1/v1", "/v1"); } - // 为 Claude 原生 /v1/messages 端点添加 ?beta=true 参数 - // 这是某些上游服务(如 DuckCoding)验证请求来源的关键参数 - // 注意:不要为 OpenAI Chat Completions (/v1/chat/completions) 添加此参数 - // 当 apiFormat="openai_chat" 时,请求会转发到 /v1/chat/completions, - // 但该端点是 OpenAI 标准,不支持 ?beta=true 参数 - if endpoint.contains("/v1/messages") - && !endpoint.contains("/v1/chat/completions") - && !endpoint.contains('?') - { - format!("{base}?beta=true") - } else { - base - } + base } fn add_auth_headers(&self, request: RequestBuilder, auth: &AuthInfo) -> RequestBuilder { @@ -487,23 +477,21 @@ mod tests { #[test] fn test_build_url_anthropic() { let adapter = ClaudeAdapter::new(); - // /v1/messages 端点会自动添加 ?beta=true 参数 + // ?beta=true 不再由代理注入,而是由客户端自身发送并透传 let url = adapter.build_url("https://api.anthropic.com", "/v1/messages"); - assert_eq!(url, "https://api.anthropic.com/v1/messages?beta=true"); + assert_eq!(url, "https://api.anthropic.com/v1/messages"); } #[test] fn test_build_url_openrouter() { let adapter = ClaudeAdapter::new(); - // /v1/messages 端点会自动添加 ?beta=true 参数 let url = adapter.build_url("https://openrouter.ai/api", "/v1/messages"); - assert_eq!(url, "https://openrouter.ai/api/v1/messages?beta=true"); + assert_eq!(url, "https://openrouter.ai/api/v1/messages"); } #[test] fn test_build_url_no_beta_for_other_endpoints() { let adapter = ClaudeAdapter::new(); - // 非 /v1/messages 端点不添加 ?beta=true let url = adapter.build_url("https://api.anthropic.com", "/v1/complete"); assert_eq!(url, "https://api.anthropic.com/v1/complete"); } @@ -511,16 +499,14 @@ mod tests { #[test] fn test_build_url_preserve_existing_query() { let adapter = ClaudeAdapter::new(); - // 已有查询参数时不重复添加 - let url = adapter.build_url("https://api.anthropic.com", "/v1/messages?foo=bar"); - assert_eq!(url, "https://api.anthropic.com/v1/messages?foo=bar"); + // 客户端携带的 query 参数原样透传 + let url = adapter.build_url("https://api.anthropic.com", "/v1/messages?beta=true"); + assert_eq!(url, "https://api.anthropic.com/v1/messages?beta=true"); } #[test] fn test_build_url_no_beta_for_openai_chat_completions() { let adapter = ClaudeAdapter::new(); - // OpenAI Chat Completions 端点不添加 ?beta=true - // 这是 Nvidia 等 apiFormat="openai_chat" 供应商使用的端点 let url = adapter.build_url("https://integrate.api.nvidia.com", "/v1/chat/completions"); assert_eq!(url, "https://integrate.api.nvidia.com/v1/chat/completions"); } diff --git a/src-tauri/src/services/stream_check.rs b/src-tauri/src/services/stream_check.rs index b5bf88887..6a906662e 100644 --- a/src-tauri/src/services/stream_check.rs +++ b/src-tauri/src/services/stream_check.rs @@ -293,11 +293,11 @@ impl StreamCheckService { timeout: std::time::Duration, ) -> Result<(u16, String), AppError> { let base = base_url.trim_end_matches('/'); - // URL 必须包含 ?beta=true 参数(某些中转服务依赖此参数验证请求来源) + // 健康检查不注入 ?beta=true,依赖 anthropic-beta header 传递身份验证信息 let url = if base.ends_with("/v1") { - format!("{base}/messages?beta=true") + format!("{base}/messages") } else { - format!("{base}/v1/messages?beta=true") + format!("{base}/v1/messages") }; let body = json!({