Skip to main content

codetether_agent/tui/app/smart_switch/
error_detection.rs

1//! Provider error classification and normalization.
2
3/// Normalizes provider aliases (zhipuai/z-ai -> zai).
4pub fn normalize_provider_alias(provider_name: &str) -> &str {
5    if provider_name.eq_ignore_ascii_case("zhipuai") || provider_name.eq_ignore_ascii_case("z-ai") {
6        "zai"
7    } else {
8        provider_name
9    }
10}
11
12/// Normalizes a model reference for use as a key in the attempted models set.
13pub fn smart_switch_model_key(model_ref: &str) -> String {
14    model_ref.trim().to_ascii_lowercase()
15}
16
17/// Classifies provider errors as retryable (rate limit, timeout, 5xx) vs permanent.
18pub fn is_retryable_provider_error(err: &str) -> bool {
19    let normalized = err.to_ascii_lowercase();
20    let transient_status_codes = [
21        " 429 ", " 500 ", " 502 ", " 503 ", " 504 ", " 520 ", " 521 ", " 522 ", " 523 ", " 524 ",
22        " 525 ", " 526 ", " 529 ", " 598 ", " 599 ", " 798 ",
23    ];
24    let non_retryable_status_codes = [" 400 ", " 401 ", " 403 ", " 404 ", " 422 "];
25    let markers = [
26        "429",
27        "rate limit",
28        "too many requests",
29        "quota exceeded",
30        "service unavailable",
31        "temporarily unavailable",
32        "bad gateway",
33        "gateway timeout",
34        "timed out",
35        "timeout",
36        "connection reset",
37        "connection refused",
38        "network error",
39        "unknown api error",
40        "api_error",
41        "unknown error",
42        "protocol error code 469",
43        "no text payload",
44        "context length exceeded",
45        "maximum context length",
46    ];
47
48    if non_retryable_status_codes
49        .iter()
50        .any(|c| normalized.contains(c))
51    {
52        return false;
53    }
54
55    markers.iter().any(|m| normalized.contains(m))
56        || transient_status_codes
57            .iter()
58            .any(|c| normalized.contains(c))
59}