use thiserror::Error;
#[derive(Error, Debug)]
pub enum CCSwarmError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
SerdeJson(#[from] serde_json::Error),
#[error("Configuration error: {message}")]
Configuration {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Agent error [{agent_id}]: {message}")]
Agent {
agent_id: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Session error [{session_id}]: {message}")]
Session {
session_id: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Task error [{task_id}]: {message}")]
Task {
task_id: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Network error: {message}")]
Network {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Orchestrator error: {message}")]
Orchestrator {
message: String,
task_id: Option<String>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Git error: {message}")]
Git {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Template error: {message}")]
Template {
message: String,
template_name: Option<String>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Extension error [{extension_id}]: {message}")]
Extension {
extension_id: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Resource error: {message}")]
Resource {
message: String,
resource_type: Option<String>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Authentication error: {message}")]
Auth {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("{message}")]
UserError {
message: String,
suggestion: Option<String>,
},
#[error("{message}")]
Other {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
impl From<String> for CCSwarmError {
fn from(error: String) -> Self {
Self::Other {
message: error,
source: None,
}
}
}
impl From<&str> for CCSwarmError {
fn from(error: &str) -> Self {
Self::Other {
message: error.to_string(),
source: None,
}
}
}
#[cfg(feature = "claude-acp")]
impl From<crate::acp_claude::ACPError> for CCSwarmError {
fn from(error: crate::acp_claude::ACPError) -> Self {
Self::Network {
message: error.to_string(),
source: None,
}
}
}
pub type Result<T> = std::result::Result<T, CCSwarmError>;
pub trait ErrorContext<T> {
fn with_context<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> String;
fn with_context_and_source<F, E>(self, f: F, source: E) -> Result<T>
where
F: FnOnce() -> String,
E: std::error::Error + Send + Sync + 'static;
}
impl<T, E> ErrorContext<T> for std::result::Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn with_context<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> String,
{
self.map_err(|e| CCSwarmError::Other {
message: f(),
source: Some(Box::new(e)),
})
}
fn with_context_and_source<F, S>(self, f: F, source: S) -> Result<T>
where
F: FnOnce() -> String,
S: std::error::Error + Send + Sync + 'static,
{
self.map_err(|_| CCSwarmError::Other {
message: f(),
source: Some(Box::new(source)),
})
}
}
impl CCSwarmError {
pub fn config<S: Into<String>>(message: S) -> Self {
Self::Configuration {
message: message.into(),
source: None,
}
}
pub fn agent<S: Into<String>, I: Into<String>>(agent_id: I, message: S) -> Self {
Self::Agent {
agent_id: agent_id.into(),
message: message.into(),
source: None,
}
}
pub fn session<S: Into<String>, I: Into<String>>(session_id: I, message: S) -> Self {
Self::Session {
session_id: session_id.into(),
message: message.into(),
source: None,
}
}
pub fn orchestrator<S: Into<String>>(message: S, task_id: Option<String>) -> Self {
Self::Orchestrator {
message: message.into(),
task_id,
source: None,
}
}
pub fn task<S: Into<String>, I: Into<String>>(task_id: I, message: S) -> Self {
Self::Task {
task_id: task_id.into(),
message: message.into(),
source: None,
}
}
pub fn network<S: Into<String>>(message: S) -> Self {
Self::Network {
message: message.into(),
source: None,
}
}
pub fn git<S: Into<String>>(message: S) -> Self {
Self::Git {
message: message.into(),
source: None,
}
}
pub fn template<S: Into<String>>(message: S) -> Self {
Self::Template {
message: message.into(),
template_name: None,
source: None,
}
}
pub fn template_with_name<S: Into<String>, N: Into<String>>(
message: S,
template_name: N,
) -> Self {
Self::Template {
message: message.into(),
template_name: Some(template_name.into()),
source: None,
}
}
pub fn extension<S: Into<String>, I: Into<String>>(extension_id: I, message: S) -> Self {
Self::Extension {
extension_id: extension_id.into(),
message: message.into(),
source: None,
}
}
pub fn resource<S: Into<String>>(message: S) -> Self {
Self::Resource {
message: message.into(),
resource_type: None,
source: None,
}
}
pub fn resource_with_type<S: Into<String>, T: Into<String>>(
message: S,
resource_type: T,
) -> Self {
Self::Resource {
message: message.into(),
resource_type: Some(resource_type.into()),
source: None,
}
}
pub fn auth<S: Into<String>>(message: S) -> Self {
Self::Auth {
message: message.into(),
source: None,
}
}
pub fn user_error<S: Into<String>>(message: S) -> Self {
Self::UserError {
message: message.into(),
suggestion: None,
}
}
pub fn user_error_with_suggestion<S: Into<String>, T: Into<String>>(
message: S,
suggestion: T,
) -> Self {
Self::UserError {
message: message.into(),
suggestion: Some(suggestion.into()),
}
}
pub fn with_source<E>(mut self, source: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
match &mut self {
Self::Configuration { source: s, .. }
| Self::Agent { source: s, .. }
| Self::Session { source: s, .. }
| Self::Task { source: s, .. }
| Self::Network { source: s, .. }
| Self::Git { source: s, .. }
| Self::Template { source: s, .. }
| Self::Extension { source: s, .. }
| Self::Resource { source: s, .. }
| Self::Auth { source: s, .. }
| Self::Other { source: s, .. } => {
*s = Some(Box::new(source));
}
_ => {}
}
self
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::Network { .. } | Self::Io(_) | Self::Task { .. } | Self::Resource { .. }
)
}
pub fn should_retry(&self) -> bool {
match self {
Self::Network { .. } | Self::Resource { .. } => true,
Self::Io(io_err) => {
matches!(
io_err.kind(),
std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::BrokenPipe
| std::io::ErrorKind::TimedOut
| std::io::ErrorKind::Interrupted
| std::io::ErrorKind::WouldBlock
)
}
_ => false,
}
}
pub fn suggested_retry_delay(&self) -> std::time::Duration {
match self {
Self::Network { .. } => std::time::Duration::from_secs(1),
Self::Resource { .. } => std::time::Duration::from_secs(2),
Self::Io(_) => std::time::Duration::from_millis(500),
_ => std::time::Duration::from_secs(1),
}
}
pub fn max_retries(&self) -> u32 {
if !self.should_retry() {
return 0;
}
match self {
Self::Network { .. } => 3,
Self::Resource { .. } => 5,
Self::Io(_) => 2,
_ => 0,
}
}
pub fn severity(&self) -> ErrorSeverity {
match self {
Self::Auth { .. } | Self::Configuration { .. } => ErrorSeverity::Critical,
Self::Agent { .. }
| Self::Session { .. }
| Self::Extension { .. }
| Self::Orchestrator { .. } => ErrorSeverity::High,
Self::Task { .. } | Self::Git { .. } | Self::Template { .. } => ErrorSeverity::Medium,
Self::Network { .. } | Self::Resource { .. } => ErrorSeverity::Low,
Self::Io(_) | Self::SerdeJson(_) => ErrorSeverity::Medium,
Self::UserError { .. } => ErrorSeverity::Info,
Self::Other { .. } => ErrorSeverity::Medium,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ErrorSeverity {
Info,
Low,
Medium,
High,
Critical,
}
impl std::fmt::Display for ErrorSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Info => write!(f, "INFO"),
Self::Low => write!(f, "LOW"),
Self::Medium => write!(f, "MEDIUM"),
Self::High => write!(f, "HIGH"),
Self::Critical => write!(f, "CRITICAL"),
}
}
}