From 682e9158572b61fe40f06dc3e699187a0928ab37 Mon Sep 17 00:00:00 2001 From: dst1213 Date: Fri, 8 May 2026 20:46:48 +0800 Subject: [PATCH] fix: retry quota errors returned as HTTP 400 --- crates/tui/src/client.rs | 4 ---- crates/tui/src/llm_client/mod.rs | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index 5f2c80fa..376ac940 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -654,10 +654,6 @@ impl DeepSeekClient { if status.is_success() { return Ok(response); } - let retryable = status.as_u16() == 429 || status.is_server_error(); - if !retryable { - return Ok(response); - } let retry_after = extract_retry_after(response.headers()); let body = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await; Err(LlmError::from_http_response_with_retry_after( diff --git a/crates/tui/src/llm_client/mod.rs b/crates/tui/src/llm_client/mod.rs index 009f701b..91cdf05c 100644 --- a/crates/tui/src/llm_client/mod.rs +++ b/crates/tui/src/llm_client/mod.rs @@ -202,7 +202,16 @@ impl LlmError { 400 => { // Classify 400 errors by examining the response body let body_lower = body.to_lowercase(); - if body_lower.contains("context_length") + if body_lower.contains("insufficientquota") + || body_lower.contains("insufficient_quota") + || body_lower.contains("exceeded your current quota") + || body_lower.contains("quota exceeded") + { + LlmError::RateLimited { + message: body.to_string(), + retry_after: None, + } + } else if body_lower.contains("context_length") || body_lower.contains("token") || body_lower.contains("too long") || body_lower.contains("maximum") @@ -846,6 +855,14 @@ mod tests { let err = LlmError::from_http_response(400, "context_length_exceeded"); assert!(matches!(err, LlmError::ContextLengthError(_))); + // Some OpenAI-compatible gateways return quota/rate-limit errors as HTTP 400. + let err = LlmError::from_http_response( + 400, + r#"{"error":{"code":"insufficientquota","message":"You exceeded your current quota"}}"#, + ); + assert!(matches!(err, LlmError::RateLimited { .. })); + assert!(err.is_retryable()); + // Content policy let err = LlmError::from_http_response(400, "content_policy_violation"); assert!(matches!(err, LlmError::ContentPolicyError(_)));