diff --git a/src-tauri/src/proxy/handlers.rs b/src-tauri/src/proxy/handlers.rs index b16bf2715..96076470d 100644 --- a/src-tauri/src/proxy/handlers.rs +++ b/src-tauri/src/proxy/handlers.rs @@ -1005,19 +1005,39 @@ fn codex_proxy_error_json( return body; }; - let cause = error_obj - .get("message") - .and_then(|value| value.as_str()) - .map(ToString::to_string) - .filter(|message| !message.trim().is_empty()) - .unwrap_or_else(|| get_error_message(error)); - - let status_fragment = upstream_status - .map(|status| format!("; upstream_status: HTTP {status}")) - .unwrap_or_default(); - let message = format!( - "CC Switch local proxy failed while handling Codex endpoint {endpoint}. Provider: {provider_name}; model: {request_model}{status_fragment}; cause: {cause}" - ); + let message = if upstream_status == Some(413) { + // 413 来自上游渠道商的网关(典型是 nginx 的 client_max_body_size),不是 CC + // Switch 本地代理的限制(本地 DefaultBodyLimit 已放到 200MB)。上游响应体往往是 + // 一整段 nginx HTML,对用户毫无价值,这里替换成明确指向上游 + 可操作的指引, + // 避免「以为是 CC Switch 封装了 nginx / 是本地代理的锅」这种反复出现的误解。 + format!( + concat!( + "Upstream provider rejected the request with HTTP 413 (Payload Too Large). ", + "The request body exceeds the upstream gateway's size limit; this is the ", + "provider's server-side limit, not a CC Switch limit. ", + "Provider: {provider}; model: {model}; endpoint: {endpoint}. ", + "To recover, shrink the request: run /compact, remove large pasted logs or ", + "inline images, or ask the provider to raise its request body limit ", + "(e.g. nginx client_max_body_size)." + ), + provider = provider_name, + model = request_model, + endpoint = endpoint, + ) + } else { + let cause = error_obj + .get("message") + .and_then(|value| value.as_str()) + .map(ToString::to_string) + .filter(|message| !message.trim().is_empty()) + .unwrap_or_else(|| get_error_message(error)); + let status_fragment = upstream_status + .map(|status| format!("; upstream_status: HTTP {status}")) + .unwrap_or_default(); + format!( + "CC Switch local proxy failed while handling Codex endpoint {endpoint}. Provider: {provider_name}; model: {request_model}{status_fragment}; cause: {cause}" + ) + }; error_obj.insert( "message".to_string(), @@ -1477,4 +1497,36 @@ data: {\"type\":\"response.output_item.done\",\"item\":{\"type\":\"message\"}}\n assert_eq!(body["error"]["code"], 2013); assert_eq!(body["error"]["upstream_status"], 502); } + + #[test] + fn codex_proxy_413_points_to_upstream_not_local_proxy() { + // 模拟上游渠道商 nginx 因 client_max_body_size 返回的 413 HTML 页面 + // (见 issue #666:长上下文 / 大图 / 大日志撞上游体积上限) + let error = ProxyError::UpstreamError { + status: 413, + body: Some( + "\r\n413 Request Entity Too Large\r\n\ + \r\n

413 Request Entity Too Large

\r\n\ +
nginx/1.29.6
\r\n\r\n" + .to_string(), + ), + }; + let body = codex_proxy_error_json("HCAI", "gpt-5.5", "/responses", &error); + + let message = body["error"]["message"].as_str().unwrap(); + // 不再误导成「本地代理失败」 + assert!(!message.contains("CC Switch local proxy failed")); + // 明确指向上游 + 体积超限 + 可操作指引 + assert!(message.contains("413")); + assert!(message.to_lowercase().contains("upstream")); + assert!(message.contains("/compact")); + // 关键:不把整段 nginx HTML 回显给用户 + assert!(!message.contains("")); + assert!(!message.contains("nginx/1.29.6")); + // 结构化字段仍然保留,便于程序化消费 / UI 呈现 + assert_eq!(body["error"]["upstream_status"], 413); + assert_eq!(body["error"]["provider"], "HCAI"); + assert_eq!(body["error"]["model"], "gpt-5.5"); + assert_eq!(body["error"]["endpoint"], "/responses"); + } }