use std::time::Duration;
use thiserror::Error;
use crate::id::ProviderId;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Provider(#[from] ProviderError),
#[error(transparent)]
Tool(#[from] ToolError),
#[error(transparent)]
Context(#[from] ContextError),
#[error(transparent)]
Storage(#[from] StorageError),
#[error("configuration error: {0}")]
Config(String),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ProviderError {
#[error("authentication failed for provider `{provider}`")]
Authentication {
provider: ProviderId,
},
#[error("provider `{provider}` rejected request: {message}")]
BadRequest {
provider: ProviderId,
message: String,
},
#[error("provider `{provider}` rate limited request")]
RateLimited {
provider: ProviderId,
retry_after: Option<Duration>,
},
#[error("provider `{provider}` timed out")]
Timeout {
provider: ProviderId,
},
#[error("provider `{provider}` is overloaded")]
Overloaded {
provider: ProviderId,
},
#[error("provider `{provider}` does not support feature `{feature}`")]
Unsupported {
provider: ProviderId,
feature: String,
},
#[error("transport error for provider `{provider}`: {source}")]
Transport {
provider: ProviderId,
#[source]
source: TransportError,
},
#[error("failed to decode response from provider `{provider}`: {message}")]
Decode {
provider: ProviderId,
message: String,
},
#[error("provider `{provider}` returned error: {message}")]
Provider {
provider: ProviderId,
status: Option<u16>,
message: String,
},
}
impl ProviderError {
#[must_use]
pub const fn is_retryable(&self) -> bool {
matches!(
self,
Self::RateLimited { .. }
| Self::Timeout { .. }
| Self::Overloaded { .. }
| Self::Transport { .. }
)
}
#[must_use]
pub fn is_context_overflow(&self) -> bool {
let message = match self {
Self::BadRequest { message, .. } | Self::Provider { message, .. } => message.as_str(),
_ => return false,
};
message.contains("context_length_exceeded")
|| message.contains("maximum context length")
|| message.contains("too many tokens")
|| message.contains("reduce the length")
|| message.contains("token limit")
|| message.contains("prompt is too long")
|| message.contains("input length exceeds")
|| message.contains("context window")
}
}
#[derive(Debug)]
pub struct TransportError(Box<dyn std::error::Error + Send + Sync>);
impl TransportError {
#[must_use]
pub fn new(error: impl std::error::Error + Send + Sync + 'static) -> Self {
Self(Box::new(error))
}
}
impl std::fmt::Display for TransportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for TransportError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.0.as_ref())
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ToolError {
#[error("tool `{name}` is not registered")]
NotFound {
name: String,
},
#[error("tool `{name}` failed: {message}")]
Execution {
name: String,
message: String,
},
#[error("tool `{name}` received invalid arguments: {message}")]
InvalidArguments {
name: String,
message: String,
},
#[error("tool `{name}` is not implemented")]
NotImplemented {
name: String,
},
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ContextError {
#[error("context adapter `{adapter}` failed: {message}")]
AdapterFailed {
adapter: String,
message: String,
},
#[error("invalid context input: {message}")]
InvalidInput {
message: String,
},
#[error("context adapter `{adapter}` is not registered")]
AdapterNotFound {
adapter: String,
},
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum StorageError {
#[error("storage entity not found: `{id}`")]
NotFound {
id: String,
},
#[error("storage connection failed ({backend}): {message}")]
ConnectionFailed {
backend: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("storage serialization failed: {message}")]
SerializationFailed {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("storage backend error ({backend}): {message}")]
BackendError {
backend: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("storage migration failed ({backend}): {message}")]
MigrationFailed {
backend: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("data corruption in `{field}`: {message}")]
DataCorruption {
field: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}