neuromance_client/
error.rs

1//! Error types for the client library.
2
3use std::time::Duration;
4
5use serde::Deserialize;
6use thiserror::Error;
7
8/// Error response from the API.
9///
10/// Wraps the detailed error information returned by LLM providers.
11#[derive(Debug, Deserialize)]
12pub struct ErrorResponse {
13    /// The error detail object from the API.
14    pub error: ErrorDetail,
15}
16
17/// Detailed error information from the API.
18///
19/// Contains the specific error message returned by the provider.
20#[derive(Debug, Deserialize)]
21pub struct ErrorDetail {
22    /// The error message text describing what went wrong.
23    pub message: String,
24}
25
26/// Errors that can occur when interacting with LLM APIs.
27///
28/// This enum covers all error conditions from network failures to API-specific
29/// errors like rate limiting and content filtering.
30#[derive(Debug, Error)]
31#[non_exhaustive]
32pub enum ClientError {
33    /// Network or HTTP request failure.
34    ///
35    /// Indicates issues like DNS resolution, connection failures, or socket errors.
36    /// These errors are typically retryable.
37    #[error("Network error: {0}")]
38    NetworkError(#[from] reqwest::Error),
39
40    /// Middleware layer error.
41    ///
42    /// Errors from request/response middleware such as retry logic or logging.
43    #[error("Middleware error: {0}")]
44    MiddlewareError(#[from] reqwest_middleware::Error),
45
46    /// JSON serialization or deserialization error.
47    ///
48    /// Occurs when request/response JSON cannot be properly encoded or decoded.
49    #[error("Serialization error: {0}")]
50    SerializationError(#[from] serde_json::Error),
51
52    /// API authentication failure (HTTP 401).
53    ///
54    /// The API key is missing, invalid, or revoked. Check your credentials.
55    #[error("Authentication error: {0}")]
56    AuthenticationError(String),
57
58    /// use reqwest_eventsource::{Error as EventSourceError};
59    #[error("EventSource error: {0}")]
60    EventSourceError(#[from] reqwest_eventsource::Error),
61
62    /// Rate limit exceeded (HTTP 429).
63    ///
64    /// Too many requests sent in a given time period. Wait and retry.
65    #[error("Rate limit exceeded: {retry_after:?}")]
66    RateLimitError {
67        /// Suggested wait time before retrying, if provided by the API.
68        retry_after: Option<Duration>,
69    },
70
71    /// Model-specific error from the API.
72    ///
73    /// The model encountered an error during generation.
74    #[error("Model error: {0}")]
75    ModelError(String),
76
77    /// Client configuration issue.
78    ///
79    /// Invalid base URL, missing required fields, or incompatible settings.
80    #[error("Configuration error: {0}")]
81    ConfigurationError(String),
82
83    /// Request timeout.
84    ///
85    /// The request took longer than the configured timeout. Consider increasing
86    /// the timeout or reducing request complexity.
87    #[error("Timeout error")]
88    TimeoutError,
89
90    /// Malformed request.
91    ///
92    /// The request structure is invalid or missing required parameters.
93    #[error("Invalid request: {0}")]
94    InvalidRequest(String),
95
96    /// Unexpected or malformed API response.
97    ///
98    /// The API returned data that doesn't match the expected format.
99    #[error("Invalid response: {0}")]
100    InvalidResponse(String),
101
102    /// Tools requested but not supported by this model.
103    ///
104    /// The model or provider doesn't support function calling.
105    #[error("Tool execution not supported")]
106    ToolsNotSupported,
107
108    /// Streaming requested but not supported.
109    ///
110    /// The model or provider doesn't support streaming responses.
111    #[error("Streaming not supported")]
112    StreamingNotSupported,
113
114    /// Token limit exceeded for this model.
115    ///
116    /// The input plus requested output exceeds the model's context window.
117    #[error("Context length exceeded: {current_tokens} > {max_tokens}")]
118    ContextLengthExceeded {
119        /// Current number of tokens in the request.
120        current_tokens: usize,
121        /// Maximum tokens allowed by the model.
122        max_tokens: usize,
123    },
124
125    /// Content blocked by safety filter.
126    ///
127    /// The content violates the provider's usage policies.
128    #[error("Content filtered: {reason}")]
129    ContentFiltered {
130        /// Reason for filtering (e.g., "violence", "hate_speech").
131        reason: String,
132    },
133
134    /// API service unavailable (5xx errors).
135    ///
136    /// The provider's servers are experiencing issues. Retry with backoff.
137    #[error("Service unavailable: {0}")]
138    ServiceUnavailable(String),
139
140    /// Temperature parameter out of valid range.
141    ///
142    /// Temperature must be between 0.0 and 2.0.
143    #[error("Temperature must be between 0.0 & 2.0")]
144    InvalidTemperature,
145
146    /// top_p parameter out of valid range.
147    ///
148    /// top_p must be between 0.0 and 1.0.
149    #[error("TopP must be between 0.0 & 1.0")]
150    InvalidTopP,
151
152    /// frequency_penalty parameter out of valid range.
153    ///
154    /// frequency_penalty must be between -2.0 and 2.0.
155    #[error("FrequencyPenalty must be between -2.0 & 2.0")]
156    InvalidFrequencyPenalty,
157}
158
159impl ClientError {
160    /// Check if this error is potentially retryable.
161    ///
162    /// Returns `true` for network errors, timeouts, rate limits, and service unavailable errors.
163    pub fn is_retryable(&self) -> bool {
164        matches!(
165            self,
166            Self::NetworkError(_)
167                | Self::MiddlewareError(_)
168                | Self::TimeoutError
169                | Self::RateLimitError { .. }
170                | Self::ServiceUnavailable(_)
171        )
172    }
173
174    /// Check if this is an authentication error.
175    pub fn is_authentication_error(&self) -> bool {
176        matches!(self, Self::AuthenticationError(_))
177    }
178
179    /// Check if this is a rate limit error.
180    pub fn is_rate_limit_error(&self) -> bool {
181        matches!(self, Self::RateLimitError { .. })
182    }
183
184    /// Get the retry-after duration if this is a rate limit error.
185    ///
186    /// Returns the suggested wait time before retrying the request.
187    pub fn retry_after(&self) -> Option<Duration> {
188        match self {
189            Self::RateLimitError { retry_after } => *retry_after,
190            _ => None,
191        }
192    }
193}