1use crate::constants::{TEAMMATE_LOG_RESULT_MAX_CHARS, TEAMMATE_PROMPT_PREVIEW_MAX_CHARS};
7use crate::llm::LlmError;
8
9#[derive(Debug)]
11pub enum ChatError {
12 ApiAuth(String),
15 ApiRateLimit {
17 message: String,
18 retry_after_secs: Option<u64>,
19 },
20 ApiBadRequest(String),
22 ApiServerError { status: u16, message: String },
24
25 NetworkTimeout(String),
28 NetworkError(String),
30
31 StreamInterrupted(String),
34 StreamDeserialize(String),
36
37 RequestBuild(String),
40
41 HookAborted,
44
45 RuntimeFailed(String),
48 AgentPanic(String),
50
51 AbnormalFinish(String),
54 Other(String),
56}
57
58impl ChatError {
59 pub fn display_message(&self) -> String {
61 match self {
62 Self::ApiAuth(_) => "API 认证失败,请检查 API Key".to_string(),
63 Self::ApiRateLimit {
64 retry_after_secs, ..
65 } => {
66 if let Some(secs) = retry_after_secs {
67 format!("请求过于频繁,请在 {} 秒后重试", secs)
68 } else {
69 "请求过于频繁,请稍后重试".to_string()
70 }
71 }
72 Self::ApiBadRequest(msg) => {
73 format!("请求参数错误: {}", truncate(&sanitize_html(msg), 150))
74 }
75 Self::ApiServerError { status, .. } => {
76 format!("{} (HTTP {})", http_status_label(*status), status)
77 }
78 Self::NetworkTimeout(_) => "网络连接超时,请检查网络后重试".to_string(),
79 Self::NetworkError(_) => "网络连接失败,请检查网络设置".to_string(),
80 Self::StreamInterrupted(msg) => {
81 format!(
82 "流式响应中断: {}",
83 truncate(&sanitize_html(msg), TEAMMATE_PROMPT_PREVIEW_MAX_CHARS)
84 )
85 }
86 Self::StreamDeserialize(msg) => {
87 format!(
88 "响应解析失败: {}",
89 truncate(&sanitize_html(msg), TEAMMATE_PROMPT_PREVIEW_MAX_CHARS)
90 )
91 }
92 Self::RequestBuild(msg) => {
93 format!("构建请求失败: {}", truncate(msg, 150))
94 }
95 Self::HookAborted => "请求被 hook 中止".to_string(),
96 Self::RuntimeFailed(msg) => {
97 format!(
98 "运行时错误: {}",
99 truncate(msg, TEAMMATE_PROMPT_PREVIEW_MAX_CHARS)
100 )
101 }
102 Self::AgentPanic(msg) => {
103 format!(
104 "Agent 异常: {}",
105 truncate(msg, TEAMMATE_PROMPT_PREVIEW_MAX_CHARS)
106 )
107 }
108 Self::AbnormalFinish(reason) => {
109 format!("API 返回异常: finish_reason={}", truncate(reason, 80))
110 }
111 Self::Other(msg) => truncate(&sanitize_html(msg), TEAMMATE_LOG_RESULT_MAX_CHARS),
112 }
113 }
114}
115
116impl std::fmt::Display for ChatError {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 match self {
119 Self::ApiAuth(msg) => write!(f, "API 认证失败: {}", msg),
120 Self::ApiRateLimit { message, .. } => write!(f, "API 请求过于频繁: {}", message),
121 Self::ApiBadRequest(msg) => write!(f, "API 请求参数错误: {}", msg),
122 Self::ApiServerError { status, message } => {
123 write!(f, "API 服务端错误 (HTTP {}): {}", status, message)
124 }
125 Self::NetworkTimeout(msg) => write!(f, "网络连接超时: {}", msg),
126 Self::NetworkError(msg) => write!(f, "网络错误: {}", msg),
127 Self::StreamInterrupted(msg) => write!(f, "流式响应中断: {}", msg),
128 Self::StreamDeserialize(msg) => write!(f, "流式反序列化失败: {}", msg),
129 Self::RequestBuild(msg) => write!(f, "构建请求失败: {}", msg),
130 Self::HookAborted => write!(f, "LLM 请求被 hook 中止"),
131 Self::RuntimeFailed(msg) => write!(f, "运行时错误: {}", msg),
132 Self::AgentPanic(msg) => write!(f, "Agent 异常: {}", msg),
133 Self::AbnormalFinish(reason) => write!(f, "API 返回异常: finish_reason={}", reason),
134 Self::Other(msg) => write!(f, "{}", msg),
135 }
136 }
137}
138
139impl std::error::Error for ChatError {}
140
141impl From<LlmError> for ChatError {
144 fn from(e: LlmError) -> Self {
145 match e {
146 LlmError::Http(re) => ChatError::from(re),
147 LlmError::Api { status, body } => {
148 if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&body) {
150 let error_obj = parsed.get("error").unwrap_or(&parsed);
151 let code = error_obj.get("code").and_then(|v| {
152 v.as_str()
153 .map(|s| s.to_string())
154 .or_else(|| v.as_i64().map(|n| n.to_string()))
155 });
156 let message = error_obj
157 .get("message")
158 .and_then(|v| v.as_str())
159 .unwrap_or(&body);
160 ChatError::from_api_error(code.as_deref(), message)
161 } else {
162 ChatError::from_http_status(status, sanitize_html(&body))
163 }
164 }
165 LlmError::Deserialize(msg) => ChatError::StreamDeserialize(truncate(&msg, 500)),
166 LlmError::StreamInterrupted(msg) => ChatError::StreamInterrupted(msg),
167 LlmError::RequestBuild(msg) => ChatError::RequestBuild(msg),
168 }
169 }
170}
171
172impl From<reqwest::Error> for ChatError {
175 fn from(e: reqwest::Error) -> Self {
176 if e.is_timeout() {
177 ChatError::NetworkTimeout(e.to_string())
178 } else if let Some(status) = e.status() {
179 ChatError::from_http_status(status.as_u16(), e.to_string())
180 } else {
181 ChatError::NetworkError(e.to_string())
182 }
183 }
184}
185
186impl ChatError {
187 pub(crate) fn from_http_status(status: u16, message: String) -> Self {
189 match status {
190 401 | 403 => ChatError::ApiAuth(message),
191 429 => ChatError::ApiRateLimit {
192 message,
193 retry_after_secs: None,
194 },
195 400 => ChatError::ApiBadRequest(message),
196 500..=599 => ChatError::ApiServerError {
197 status,
198 message: sanitize_html(&message),
199 },
200 _ => ChatError::Other(format!("HTTP {}: {}", status, message)),
201 }
202 }
203
204 fn from_api_error(code: Option<&str>, message: &str) -> Self {
206 match code {
207 Some("rate_limit_exceeded") => ChatError::ApiRateLimit {
208 message: message.to_string(),
209 retry_after_secs: None,
210 },
211 Some("invalid_api_key") | Some("authentication_required") => {
212 ChatError::ApiAuth(message.to_string())
213 }
214 Some("invalid_request_error") => ChatError::ApiBadRequest(message.to_string()),
215 Some("1305") => ChatError::ApiRateLimit {
216 message: message.to_string(),
217 retry_after_secs: None,
218 },
219 Some(code) if code.contains("rpm_exceeded") || code.contains("tpm_exceeded") => {
220 ChatError::ApiRateLimit {
221 message: message.to_string(),
222 retry_after_secs: None,
223 }
224 }
225 _ => {
226 let msg_lower = message.to_lowercase();
227 if msg_lower.contains("api key")
228 || msg_lower.contains("unauthorized")
229 || msg_lower.contains("authentication")
230 {
231 ChatError::ApiAuth(message.to_string())
232 } else if msg_lower.contains("rate limit")
233 || msg_lower.contains("too many requests")
234 || msg_lower.contains("访问量过大")
235 || msg_lower.contains("请稍后再试")
236 || msg_lower.contains("过载")
237 || msg_lower.contains("overloaded")
238 || msg_lower.contains("too busy")
239 || msg_lower.contains("速率限制")
240 || msg_lower.contains("网络错误")
241 || msg_lower.contains("quota exceeded")
242 || msg_lower.contains("concurrency limit")
243 || msg_lower.contains("请求频率")
244 || msg_lower.contains("busy")
245 || msg_lower.contains("rpm_exceeded")
246 || msg_lower.contains("rpm exceeded")
247 || msg_lower.contains("tpm_exceeded")
248 || msg_lower.contains("tpm exceeded")
249 || msg_lower.contains("channel:rpm")
250 || msg_lower.contains("channel:tpm")
251 {
252 ChatError::ApiRateLimit {
253 message: message.to_string(),
254 retry_after_secs: None,
255 }
256 } else if msg_lower.contains("invalid") || msg_lower.contains("bad request") {
257 ChatError::ApiBadRequest(message.to_string())
258 } else {
259 ChatError::Other(sanitize_html(message))
260 }
261 }
262 }
263 }
264}
265
266fn sanitize_html(input: &str) -> String {
270 let mut result = String::with_capacity(input.len());
271 let mut in_tag = false;
272 for ch in input.chars() {
273 match ch {
274 '<' => in_tag = true,
275 '>' => {
276 in_tag = false;
277 if !result.is_empty() && !result.ends_with(char::is_whitespace) {
279 result.push(' ');
280 }
281 }
282 _ if !in_tag => result.push(ch),
283 _ => {}
284 }
285 }
286 let collapsed: String = result.split_whitespace().collect::<Vec<_>>().join(" ");
288 collapsed
289}
290
291fn truncate(s: &str, max_len: usize) -> String {
293 if s.len() <= max_len {
294 return s.to_string();
295 }
296 let mut end = max_len;
298 while !s.is_char_boundary(end) && end > 0 {
299 end -= 1;
300 }
301 format!("{}...", &s[..end])
302}
303
304fn http_status_label(status: u16) -> &'static str {
306 match status {
307 500 => "服务端内部错误",
308 502 => "网关错误",
309 503 => "服务暂不可用",
310 504 => "网关超时",
311 529 => "服务过载",
312 _ => "服务端错误",
313 }
314}