1use std::fmt;
4
5pub type LlmResult<T> = Result<T, LlmError>;
7
8#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum LlmError {
12 #[error("invalid request: {0}")]
14 InvalidRequest(String),
15
16 #[error("authentication failed: {0}")]
18 Auth(String),
19
20 #[error("rate limited{}", .retry_after.map(|d| format!(" (retry after {}s)", d.as_secs())).unwrap_or_default())]
22 RateLimited {
23 retry_after: Option<std::time::Duration>,
25 message: String,
27 },
28
29 #[error("provider error (status {status}): {message}")]
31 Provider {
32 status: u16,
34 message: String,
36 },
37
38 #[error("transport error: {0}")]
40 Transport(String),
41
42 #[error("request timed out")]
44 Timeout,
45
46 #[error("serialization error: {0}")]
48 Serialization(String),
49
50 #[error("stream error: {0}")]
52 Stream(String),
53
54 #[error("budget exceeded: {0}")]
56 BudgetExceeded(String),
57
58 #[error("tool `{tool}` failed: {message}")]
60 Tool {
61 tool: String,
63 message: String,
65 },
66
67 #[error("unsupported: {0}")]
69 Unsupported(String),
70
71 #[error("all providers failed ({} attempted)", .0.len())]
73 AllProvidersFailed(Vec<LlmError>),
74
75 #[error("{0}")]
77 Other(String),
78}
79
80impl LlmError {
81 pub fn is_retryable(&self) -> bool {
83 match self {
84 LlmError::Timeout
85 | LlmError::Transport(_)
86 | LlmError::Stream(_)
87 | LlmError::RateLimited { .. } => true,
88 LlmError::Provider { status, .. } => *status >= 500 || *status == 408,
89 _ => false,
90 }
91 }
92
93 pub fn invalid(msg: impl fmt::Display) -> Self {
95 LlmError::InvalidRequest(msg.to_string())
96 }
97
98 pub fn serde(msg: impl fmt::Display) -> Self {
100 LlmError::Serialization(msg.to_string())
101 }
102}
103
104impl From<serde_json::Error> for LlmError {
105 fn from(e: serde_json::Error) -> Self {
106 LlmError::Serialization(e.to_string())
107 }
108}