Enrich Codex proxy forwarding-error responses with context

When forward_with_retry fails for Codex endpoints (/chat/completions, /responses, /responses/compact), the handlers previously returned the bare ProxyError, surfacing a terse body to the client. Build a richer JSON error instead: the message embeds provider, model, endpoint and upstream status, and the error object carries those as structured fields alongside a stable cc_switch_* code.

Align map_proxy_error_to_status with ProxyError::into_response so the manually built responses use the canonical status codes (AuthError->401, Config/InvalidRequest->400, TransformError->422, StreamIdleTimeout->504, AlreadyRunning->409, NotRunning->503); previously these forwarder-level errors collapsed to 500.
This commit is contained in:
Jason
2026-05-30 21:42:16 +08:00
parent 0e6f2b395f
commit f4e2c28a2b
2 changed files with 251 additions and 8 deletions
+41 -4
View File
@@ -1,6 +1,6 @@
//! 错误类型到 HTTP 状态码的映射
//!
//! 将 ProxyError 映射到合适的 HTTP 状态码,用于日志记录
//! 将 ProxyError 映射到合适的 HTTP 状态码,用于日志记录和手动构建错误响应
use super::ProxyError;
@@ -12,14 +12,21 @@ use super::ProxyError;
/// - 连接失败:502 Bad Gateway
/// - 无可用 Provider503 Service Unavailable
/// - 重试耗尽:503 Service Unavailable
/// - 认证错误:401 Unauthorized
/// - 配置/请求错误:400 Bad Request
/// - 转换错误:422 Unprocessable Entity
/// - 其他错误:500 Internal Server Error
pub fn map_proxy_error_to_status(error: &ProxyError) -> u16 {
match error {
// 服务状态错误:与 IntoResponse 保持一致
ProxyError::AlreadyRunning => 409,
ProxyError::NotRunning => 503,
// 上游错误:使用实际状态码
ProxyError::UpstreamError { status, .. } => *status,
// 超时错误:504 Gateway Timeout
ProxyError::Timeout(_) => 504,
ProxyError::Timeout(_) | ProxyError::StreamIdleTimeout(_) => 504,
// 转发失败/连接失败:502 Bad Gateway
ProxyError::ForwardFailed(_) => 502,
@@ -39,11 +46,17 @@ pub fn map_proxy_error_to_status(error: &ProxyError) -> u16 {
// Provider 不健康:503 Service Unavailable
ProxyError::ProviderUnhealthy(_) => 503,
// 配置错误/无效请求:400 Bad Request
ProxyError::ConfigError(_) | ProxyError::InvalidRequest(_) => 400,
// 认证错误:401 Unauthorized
ProxyError::AuthError(_) => 401,
// 数据库错误:500 Internal Server Error
ProxyError::DatabaseError(_) => 500,
// 转换错误:500 Internal Server Error
ProxyError::TransformError(_) => 500,
// 转换错误:422 Unprocessable Entity
ProxyError::TransformError(_) => 422,
// 其他未知错误:500 Internal Server Error
_ => 500,
@@ -104,6 +117,30 @@ mod tests {
assert_eq!(map_proxy_error_to_status(&error), 503);
}
#[test]
fn test_map_status_matches_proxy_error_response_semantics() {
assert_eq!(
map_proxy_error_to_status(&ProxyError::AuthError("bad token".to_string())),
401
);
assert_eq!(
map_proxy_error_to_status(&ProxyError::ConfigError("bad config".to_string())),
400
);
assert_eq!(
map_proxy_error_to_status(&ProxyError::InvalidRequest("bad request".to_string())),
400
);
assert_eq!(
map_proxy_error_to_status(&ProxyError::TransformError("bad transform".to_string())),
422
);
assert_eq!(
map_proxy_error_to_status(&ProxyError::StreamIdleTimeout(30)),
504
);
}
#[test]
fn test_get_error_message() {
let error = ProxyError::UpstreamError {