use serde::{Deserialize, Serialize};
use thiserror::Error;
use ts_rs::TS;
#[derive(Debug, Clone, Serialize, Deserialize, TS, Error)]
#[ts(export)]
pub enum AiMuxError {
#[error("provider error: {0}")]
Provider(String),
#[error("HTTP error: {0}")]
Http(String),
#[error("JSON error: {0}")]
Json(String),
#[error("stream error: {0}")]
Stream(String),
#[error("tool error: {0}")]
Tool(String),
#[error("invalid argument: {0}")]
InvalidArgument(String),
#[error("invalid prompt: {0}")]
InvalidPrompt(String),
#[error("rate limited: retry after {retry_after_ms}ms")]
RateLimited { retry_after_ms: u64 },
#[error("authentication failed: {0}")]
Auth(String),
#[error("token expired: {0}")]
TokenExpired(String),
#[error("model not found: {0}")]
ModelNotFound(String),
#[error("unsupported functionality: {0}")]
Unsupported(String),
#[error("no such model: {0}")]
NoSuchModel(String),
#[error("unknown provider: {0}")]
UnknownProvider(String),
#[error("API call error: {0}")]
ApiCall(String),
#[error("request timed out: {0}")]
Timeout(String),
#[error("request aborted")]
Aborted,
#[error("{0}")]
Other(String),
}
impl From<serde_json::Error> for AiMuxError {
fn from(e: serde_json::Error) -> Self {
AiMuxError::Json(e.to_string())
}
}
impl AiMuxError {
pub fn is_retryable(&self) -> bool {
matches!(
self,
AiMuxError::RateLimited { .. } | AiMuxError::Http(_) | AiMuxError::ApiCall(_)
)
}
pub fn retry_after_hint(&self) -> Option<i64> {
match self {
AiMuxError::RateLimited { retry_after_ms } => Some(*retry_after_ms as i64),
_ => None,
}
}
pub fn error_type(&self) -> &'static str {
match self {
AiMuxError::Provider(_) => "Provider",
AiMuxError::Http(_) => "Http",
AiMuxError::Json(_) => "Json",
AiMuxError::Stream(_) => "Stream",
AiMuxError::Tool(_) => "Tool",
AiMuxError::InvalidArgument(_) => "InvalidArgument",
AiMuxError::InvalidPrompt(_) => "InvalidPrompt",
AiMuxError::RateLimited { .. } => "RateLimited",
AiMuxError::Auth(_) => "Auth",
AiMuxError::TokenExpired(_) => "TokenExpired",
AiMuxError::ModelNotFound(_) => "ModelNotFound",
AiMuxError::Unsupported(_) => "Unsupported",
AiMuxError::NoSuchModel(_) => "NoSuchModel",
AiMuxError::UnknownProvider(_) => "UnknownProvider",
AiMuxError::ApiCall(_) => "ApiCall",
AiMuxError::Timeout(_) => "Timeout",
AiMuxError::Aborted => "Aborted",
AiMuxError::Other(_) => "Other",
}
}
pub fn status_code(&self) -> Option<u16> {
let message = match self {
AiMuxError::Provider(m)
| AiMuxError::Http(m)
| AiMuxError::Auth(m)
| AiMuxError::ApiCall(m)
| AiMuxError::ModelNotFound(m) => m,
_ => return None,
};
let rest = message.strip_prefix("HTTP ")?;
let code_str: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
code_str.parse().ok()
}
}