mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-31 19:22:15 +08:00
fix(proxy): remove hardcoded ?beta=true injection, passthrough client query params
- handlers.rs: add `uri` extractor to capture client's full path+query - claude.rs: remove ?beta=true injection from build_url(), update tests - stream_check.rs: remove ?beta=true from health check URL The proxy no longer injects ?beta=true into API URLs. Instead, query parameters sent by the Claude Code client are transparently passed through. This fixes duplicate ?beta=true issues and aligns with upstream behavior where the client already sends this parameter.
This commit is contained in:
@@ -56,12 +56,19 @@ pub async fn get_status(State(state): State<ProxyState>) -> Result<Json<ProxySta
|
|||||||
/// - 现在 OpenRouter 已推出 Claude Code 兼容接口,默认不再启用该转换(逻辑保留以备回退)
|
/// - 现在 OpenRouter 已推出 Claude Code 兼容接口,默认不再启用该转换(逻辑保留以备回退)
|
||||||
pub async fn handle_messages(
|
pub async fn handle_messages(
|
||||||
State(state): State<ProxyState>,
|
State(state): State<ProxyState>,
|
||||||
|
uri: axum::http::Uri,
|
||||||
headers: axum::http::HeaderMap,
|
headers: axum::http::HeaderMap,
|
||||||
Json(body): Json<Value>,
|
Json(body): Json<Value>,
|
||||||
) -> Result<axum::response::Response, ProxyError> {
|
) -> Result<axum::response::Response, ProxyError> {
|
||||||
let mut ctx =
|
let mut ctx =
|
||||||
RequestContext::new(&state, &body, &headers, AppType::Claude, "Claude", "claude").await?;
|
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
|
let is_stream = body
|
||||||
.get("stream")
|
.get("stream")
|
||||||
.and_then(|s| s.as_bool())
|
.and_then(|s| s.as_bool())
|
||||||
@@ -72,7 +79,7 @@ pub async fn handle_messages(
|
|||||||
let result = match forwarder
|
let result = match forwarder
|
||||||
.forward_with_retry(
|
.forward_with_retry(
|
||||||
&AppType::Claude,
|
&AppType::Claude,
|
||||||
"/v1/messages",
|
endpoint,
|
||||||
body.clone(),
|
body.clone(),
|
||||||
headers,
|
headers,
|
||||||
ctx.get_providers(),
|
ctx.get_providers(),
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ impl ProviderAdapter for ClaudeAdapter {
|
|||||||
//
|
//
|
||||||
// 现在 OpenRouter 已推出 Claude Code 兼容接口,因此默认直接透传 endpoint。
|
// 现在 OpenRouter 已推出 Claude Code 兼容接口,因此默认直接透传 endpoint。
|
||||||
// 如需回退旧逻辑,可在 forwarder 中根据 needs_transform 改写 endpoint。
|
// 如需回退旧逻辑,可在 forwarder 中根据 needs_transform 改写 endpoint。
|
||||||
|
//
|
||||||
|
// ?beta=true 不再由代理注入,而是由客户端自身发送并透传。
|
||||||
|
|
||||||
let mut base = format!(
|
let mut base = format!(
|
||||||
"{}/{}",
|
"{}/{}",
|
||||||
@@ -263,19 +265,7 @@ impl ProviderAdapter for ClaudeAdapter {
|
|||||||
base = base.replace("/v1/v1", "/v1");
|
base = base.replace("/v1/v1", "/v1");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为 Claude 原生 /v1/messages 端点添加 ?beta=true 参数
|
base
|
||||||
// 这是某些上游服务(如 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_auth_headers(&self, request: RequestBuilder, auth: &AuthInfo) -> RequestBuilder {
|
fn add_auth_headers(&self, request: RequestBuilder, auth: &AuthInfo) -> RequestBuilder {
|
||||||
@@ -487,23 +477,21 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_build_url_anthropic() {
|
fn test_build_url_anthropic() {
|
||||||
let adapter = ClaudeAdapter::new();
|
let adapter = ClaudeAdapter::new();
|
||||||
// /v1/messages 端点会自动添加 ?beta=true 参数
|
// ?beta=true 不再由代理注入,而是由客户端自身发送并透传
|
||||||
let url = adapter.build_url("https://api.anthropic.com", "/v1/messages");
|
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]
|
#[test]
|
||||||
fn test_build_url_openrouter() {
|
fn test_build_url_openrouter() {
|
||||||
let adapter = ClaudeAdapter::new();
|
let adapter = ClaudeAdapter::new();
|
||||||
// /v1/messages 端点会自动添加 ?beta=true 参数
|
|
||||||
let url = adapter.build_url("https://openrouter.ai/api", "/v1/messages");
|
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]
|
#[test]
|
||||||
fn test_build_url_no_beta_for_other_endpoints() {
|
fn test_build_url_no_beta_for_other_endpoints() {
|
||||||
let adapter = ClaudeAdapter::new();
|
let adapter = ClaudeAdapter::new();
|
||||||
// 非 /v1/messages 端点不添加 ?beta=true
|
|
||||||
let url = adapter.build_url("https://api.anthropic.com", "/v1/complete");
|
let url = adapter.build_url("https://api.anthropic.com", "/v1/complete");
|
||||||
assert_eq!(url, "https://api.anthropic.com/v1/complete");
|
assert_eq!(url, "https://api.anthropic.com/v1/complete");
|
||||||
}
|
}
|
||||||
@@ -511,16 +499,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_build_url_preserve_existing_query() {
|
fn test_build_url_preserve_existing_query() {
|
||||||
let adapter = ClaudeAdapter::new();
|
let adapter = ClaudeAdapter::new();
|
||||||
// 已有查询参数时不重复添加
|
// 客户端携带的 query 参数原样透传
|
||||||
let url = adapter.build_url("https://api.anthropic.com", "/v1/messages?foo=bar");
|
let url = adapter.build_url("https://api.anthropic.com", "/v1/messages?beta=true");
|
||||||
assert_eq!(url, "https://api.anthropic.com/v1/messages?foo=bar");
|
assert_eq!(url, "https://api.anthropic.com/v1/messages?beta=true");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_build_url_no_beta_for_openai_chat_completions() {
|
fn test_build_url_no_beta_for_openai_chat_completions() {
|
||||||
let adapter = ClaudeAdapter::new();
|
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");
|
let url = adapter.build_url("https://integrate.api.nvidia.com", "/v1/chat/completions");
|
||||||
assert_eq!(url, "https://integrate.api.nvidia.com/v1/chat/completions");
|
assert_eq!(url, "https://integrate.api.nvidia.com/v1/chat/completions");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,11 +293,11 @@ impl StreamCheckService {
|
|||||||
timeout: std::time::Duration,
|
timeout: std::time::Duration,
|
||||||
) -> Result<(u16, String), AppError> {
|
) -> Result<(u16, String), AppError> {
|
||||||
let base = base_url.trim_end_matches('/');
|
let base = base_url.trim_end_matches('/');
|
||||||
// URL 必须包含 ?beta=true 参数(某些中转服务依赖此参数验证请求来源)
|
// 健康检查不注入 ?beta=true,依赖 anthropic-beta header 传递身份验证信息
|
||||||
let url = if base.ends_with("/v1") {
|
let url = if base.ends_with("/v1") {
|
||||||
format!("{base}/messages?beta=true")
|
format!("{base}/messages")
|
||||||
} else {
|
} else {
|
||||||
format!("{base}/v1/messages?beta=true")
|
format!("{base}/v1/messages")
|
||||||
};
|
};
|
||||||
|
|
||||||
let body = json!({
|
let body = json!({
|
||||||
|
|||||||
Reference in New Issue
Block a user