mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-29 09:37:37 +08:00
fix(codex): fix 404 errors and connection timeout with custom base_url (#760)
* fix(proxy): fix Codex 404 errors with custom base_url prefixes - handlers.rs:268: Remove hardcoded /v1 prefix in Codex forwarding - codex.rs:140: Only add /v1 for origin-only base_urls, dedupe /v1/v1 - stream_check.rs:364: Try /responses first, fallback to /v1/responses - provider.rs:427: Don't force /v1 for custom prefix base_urls Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(codex): always add /v1 for custom prefix base_urls Changed logic to always add /v1 prefix unless base_url already ends with /v1. This fixes 504 timeout errors with relay services that expect /v1 in the path. - Most relay services follow OpenAI standard format: /v1/responses - Users can opt-out by adding /v1 to their base_url configuration - Updated test case to reflect new behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(proxy): allow system proxy on localhost with different ports - Only bypass system proxy if it points to CC Switch's own port (15721) - Allow other localhost proxies (e.g., Clash on 7890) to be used - Add INFO level logging for request URLs to aid debugging This fixes connection timeout issues when using local proxy tools. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(codex): don't add /v1 for custom prefix base_urls Reverted logic to not add /v1 for base_urls with custom prefixes. Many relay services use custom paths without /v1. - Pure origin (e.g., https://api.openai.com) → adds /v1 - With /v1 (e.g., https://api.openai.com/v1) → no change - Custom prefix (e.g., https://example.com/openai) → no /v1 This fixes 404 errors with relay services that don't use /v1 in their paths. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(proxy): use dynamic port for system proxy detection Instead of hardcoding port 15721, now uses the actual configured listen_port from proxy settings. - Added set_proxy_port() to update the port when proxy server starts - Added get_proxy_port() to retrieve current port for detection - Updated server.rs to call set_proxy_port() on startup - Updated tests to reflect new behavior This allows users to change the proxy port in settings without breaking the system proxy detection logic. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(proxy): change default proxy port from 15721 to 5000 Update the default fallback port in get_proxy_port() from 15721 to 5000 to match the project's standard default port configuration. Also updated test cases to use port 5000 consistently. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(proxy): revert default port back to 15721 Revert the default fallback port in get_proxy_port() from 5000 back to 15721 to align with the project's updated default port configuration. Also updated test cases to use port 15721 consistently. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: ozbombor <ozbombor@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -373,11 +373,15 @@ impl StreamCheckService {
|
||||
timeout: std::time::Duration,
|
||||
) -> Result<(u16, String), AppError> {
|
||||
let base = base_url.trim_end_matches('/');
|
||||
// Codex CLI 使用 /v1/responses 端点 (OpenAI Responses API)
|
||||
let url = if base.ends_with("/v1") {
|
||||
format!("{base}/responses")
|
||||
// Codex CLI 的 base_url 语义:base_url 是 API base(可能已包含 /v1 或其他自定义前缀),
|
||||
// Responses 端点为 `/responses`。
|
||||
//
|
||||
// 兼容:如果 base_url 配成纯 origin(如 https://api.openai.com),则需要补 `/v1`。
|
||||
// 优先尝试 `{base}/responses`,若 404 再回退 `{base}/v1/responses`。
|
||||
let urls = if base.ends_with("/v1") {
|
||||
vec![format!("{base}/responses")]
|
||||
} else {
|
||||
format!("{base}/v1/responses")
|
||||
vec![format!("{base}/responses"), format!("{base}/v1/responses")]
|
||||
};
|
||||
|
||||
// 解析模型名和推理等级 (支持 model@level 或 model#level 格式)
|
||||
@@ -399,40 +403,50 @@ impl StreamCheckService {
|
||||
body["reasoning"] = json!({ "effort": effort });
|
||||
}
|
||||
|
||||
// 严格按照 Codex CLI 请求格式设置 headers
|
||||
let response = client
|
||||
.post(&url)
|
||||
.header("authorization", format!("Bearer {}", auth.api_key))
|
||||
.header("content-type", "application/json")
|
||||
.header("accept", "text/event-stream")
|
||||
.header("accept-encoding", "identity")
|
||||
.header(
|
||||
"user-agent",
|
||||
format!("codex_cli_rs/0.80.0 ({os_name} 15.7.2; {arch_name}) Terminal"),
|
||||
)
|
||||
.header("originator", "codex_cli_rs")
|
||||
.timeout(timeout)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(Self::map_request_error)?;
|
||||
for (i, url) in urls.iter().enumerate() {
|
||||
// 严格按照 Codex CLI 请求格式设置 headers
|
||||
let response = client
|
||||
.post(url)
|
||||
.header("authorization", format!("Bearer {}", auth.api_key))
|
||||
.header("content-type", "application/json")
|
||||
.header("accept", "text/event-stream")
|
||||
.header("accept-encoding", "identity")
|
||||
.header(
|
||||
"user-agent",
|
||||
format!("codex_cli_rs/0.80.0 ({os_name} 15.7.2; {arch_name}) Terminal"),
|
||||
)
|
||||
.header("originator", "codex_cli_rs")
|
||||
.timeout(timeout)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(Self::map_request_error)?;
|
||||
|
||||
let status = response.status().as_u16();
|
||||
let status = response.status().as_u16();
|
||||
|
||||
if !response.status().is_success() {
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
return Err(AppError::Message(format!("HTTP {status}: {error_text}")));
|
||||
}
|
||||
|
||||
let mut stream = response.bytes_stream();
|
||||
if let Some(chunk) = stream.next().await {
|
||||
match chunk {
|
||||
Ok(_) => Ok((status, model.to_string())),
|
||||
Err(e) => Err(AppError::Message(format!("Stream read failed: {e}"))),
|
||||
if !response.status().is_success() {
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
// 回退策略:仅当首选 URL 返回 404 时尝试下一个
|
||||
if i == 0 && status == 404 && urls.len() > 1 {
|
||||
continue;
|
||||
}
|
||||
return Err(AppError::Message(format!("HTTP {status}: {error_text}")));
|
||||
}
|
||||
} else {
|
||||
Err(AppError::Message("No response data received".to_string()))
|
||||
|
||||
let mut stream = response.bytes_stream();
|
||||
if let Some(chunk) = stream.next().await {
|
||||
match chunk {
|
||||
Ok(_) => return Ok((status, actual_model)),
|
||||
Err(e) => return Err(AppError::Message(format!("Stream read failed: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
return Err(AppError::Message("No response data received".to_string()));
|
||||
}
|
||||
|
||||
Err(AppError::Message(
|
||||
"No valid Codex responses endpoint found".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Gemini 流式检查
|
||||
|
||||
Reference in New Issue
Block a user