use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ProviderKind {
OpenAI,
Anthropic,
OpenRouter,
}
impl std::fmt::Display for ProviderKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProviderKind::OpenAI => write!(f, "openai"),
ProviderKind::Anthropic => write!(f, "anthropic"),
ProviderKind::OpenRouter => write!(f, "openrouter"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ToolArgumentIssue {
pub path: String,
pub schema_path: String,
pub message: String,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("authentication error for {provider}: {message}")]
Auth {
provider: ProviderKind,
message: String,
},
#[error("API request failed for {provider}: {message}")]
Request {
provider: ProviderKind,
message: String,
},
#[error("rate limit exceeded for {provider}: {message}")]
RateLimit {
provider: ProviderKind,
message: String,
},
#[error("invalid request: {0}")]
InvalidRequest(String),
#[error("model not available: {model} for provider {provider}")]
ModelNotAvailable {
provider: ProviderKind,
model: String,
},
#[error("provider {0} is not configured")]
ProviderNotConfigured(ProviderKind),
#[error("provider {0} feature is not enabled — enable the '{0}' feature in Cargo.toml")]
ProviderNotEnabled(ProviderKind),
#[error("content filtered by {provider}: {reason}")]
ContentFiltered {
provider: ProviderKind,
reason: String,
},
#[error("configuration error: {0}")]
Config(String),
#[error("serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("stream error: {0}")]
Stream(String),
#[error("request timed out for {provider}")]
Timeout {
provider: ProviderKind,
},
#[error("tool calling is not supported for provider {provider}")]
ToolProviderUnsupported {
provider: ProviderKind,
},
#[error("invalid arguments for tool '{name}': {message}")]
ToolArguments {
name: String,
message: String,
issues: Vec<ToolArgumentIssue>,
},
#[error("tool not found: {name}")]
ToolNotFound {
name: String,
},
#[error("tool execution exceeded the maximum number of rounds ({max_rounds})")]
ToolLoopLimitExceeded {
max_rounds: usize,
},
#[error("structured output validation failed for {provider} model {model}: {message}")]
StructuredOutput {
provider: ProviderKind,
model: String,
message: String,
},
}
impl Error {
pub fn is_retryable(&self) -> bool {
matches!(
self,
Error::RateLimit { .. } | Error::Timeout { .. } | Error::Http(_)
)
}
pub fn is_auth_error(&self) -> bool {
matches!(self, Error::Auth { .. })
}
pub fn is_rate_limit(&self) -> bool {
matches!(self, Error::RateLimit { .. })
}
pub fn kind_str(&self) -> &'static str {
match self {
Error::Auth { .. } => "auth",
Error::Request { .. } => "request",
Error::RateLimit { .. } => "rate_limit",
Error::InvalidRequest(_) => "invalid_request",
Error::ModelNotAvailable { .. } => "model_not_available",
Error::ProviderNotConfigured(_) => "provider_not_configured",
Error::ProviderNotEnabled(_) => "provider_not_enabled",
Error::ContentFiltered { .. } => "content_filtered",
Error::Config(_) => "config",
Error::Serialization(_) => "serialization",
Error::Http(_) => "http",
Error::Stream(_) => "stream",
Error::Timeout { .. } => "timeout",
Error::ToolProviderUnsupported { .. } => "tool_provider_unsupported",
Error::ToolArguments { .. } => "tool_arguments",
Error::ToolNotFound { .. } => "tool_not_found",
Error::ToolLoopLimitExceeded { .. } => "tool_loop_limit_exceeded",
Error::StructuredOutput { .. } => "structured_output",
}
}
pub fn provider(&self) -> Option<ProviderKind> {
match self {
Error::Auth { provider, .. }
| Error::Request { provider, .. }
| Error::RateLimit { provider, .. }
| Error::ModelNotAvailable { provider, .. }
| Error::ProviderNotConfigured(provider)
| Error::ProviderNotEnabled(provider)
| Error::ContentFiltered { provider, .. }
| Error::Timeout { provider }
| Error::ToolProviderUnsupported { provider }
| Error::StructuredOutput { provider, .. } => Some(*provider),
_ => None,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;