#[derive(Debug, thiserror::Error)]
pub enum OrchestrationFailure {
#[error("scheduler error: {0}")]
SchedulerInit(String),
#[error("config verification error: {0}")]
VerifyConfig(String),
#[error("planner error: {0}")]
PlannerError(String),
#[error("retry reset error: {0}")]
RetryReset(String),
#[error("{0}")]
Generic(String),
}
#[derive(Debug, thiserror::Error)]
pub enum SkillOperationFailure {
#[error("invalid skill name: {0}")]
InvalidName(String),
#[error("skill directory not found: {0}")]
DirectoryNotFound(String),
#[error("{0}")]
Generic(String),
}
#[derive(Debug, thiserror::Error)]
pub enum AgentError {
#[error(transparent)]
Llm(#[from] zeph_llm::LlmError),
#[error(transparent)]
Channel(#[from] crate::channel::ChannelError),
#[error(transparent)]
Memory(#[from] zeph_memory::MemoryError),
#[error(transparent)]
Skill(#[from] zeph_skills::SkillError),
#[error(transparent)]
Tool(#[from] zeph_tools::executor::ToolError),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("blocking task failed: {0}")]
SpawnBlocking(#[from] tokio::task::JoinError),
#[error("agent shut down")]
Shutdown,
#[error("context exhausted: {0}")]
ContextExhausted(String),
#[error("tool timed out: {tool_name}")]
ToolTimeout { tool_name: zeph_common::ToolName },
#[error("schema validation failed: {0}")]
SchemaValidation(String),
#[error("orchestration error: {0}")]
OrchestrationError(#[from] OrchestrationFailure),
#[error("unknown command: {0}")]
UnknownCommand(String),
#[error("skill error: {0}")]
SkillOperation(#[from] SkillOperationFailure),
#[error("context error: {0}")]
ContextError(String),
}
impl AgentError {
#[must_use]
pub fn is_context_length_error(&self) -> bool {
if let Self::Llm(e) = self {
return e.is_context_length_error();
}
false
}
#[must_use]
pub fn is_beta_header_rejected(&self) -> bool {
if let Self::Llm(e) = self {
return e.is_beta_header_rejected();
}
false
}
#[must_use]
pub fn is_no_providers(&self) -> bool {
matches!(self, Self::Llm(zeph_llm::LlmError::NoProviders))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn agent_error_detects_context_length_from_llm() {
let e = AgentError::Llm(zeph_llm::LlmError::ContextLengthExceeded);
assert!(e.is_context_length_error());
}
#[test]
fn agent_error_detects_context_length_from_typed_variant() {
let e = AgentError::Llm(zeph_llm::LlmError::ContextLengthExceeded);
assert!(e.is_context_length_error());
}
#[test]
fn agent_error_other_with_context_message_not_detected() {
let e = AgentError::Llm(zeph_llm::LlmError::Other("context length exceeded".into()));
assert!(!e.is_context_length_error());
}
#[test]
fn agent_error_non_llm_variant_not_detected() {
let e = AgentError::ContextError("something went wrong".into());
assert!(!e.is_context_length_error());
}
#[test]
fn shutdown_variant_display() {
let e = AgentError::Shutdown;
assert_eq!(e.to_string(), "agent shut down");
}
#[test]
fn context_exhausted_variant_display() {
let e = AgentError::ContextExhausted("no space left".into());
assert!(e.to_string().contains("no space left"));
}
#[test]
fn tool_timeout_variant_display() {
let e = AgentError::ToolTimeout {
tool_name: "bash".into(),
};
assert!(e.to_string().contains("bash"));
}
#[test]
fn schema_validation_variant_display() {
let e = AgentError::SchemaValidation("missing field".into());
assert!(e.to_string().contains("missing field"));
}
#[test]
fn agent_error_detects_beta_header_rejected() {
let e = AgentError::Llm(zeph_llm::LlmError::BetaHeaderRejected {
header: "compact-2026-01-12".into(),
});
assert!(e.is_beta_header_rejected());
}
#[test]
fn agent_error_non_llm_variant_not_beta_rejected() {
let e = AgentError::ContextError("something went wrong".into());
assert!(!e.is_beta_header_rejected());
}
#[test]
fn agent_error_detects_no_providers() {
let e = AgentError::Llm(zeph_llm::LlmError::NoProviders);
assert!(e.is_no_providers());
}
#[test]
fn agent_error_non_no_providers_returns_false() {
let e = AgentError::ContextError("other".into());
assert!(!e.is_no_providers());
}
#[test]
fn orchestration_error_display() {
let e =
AgentError::OrchestrationError(OrchestrationFailure::Generic("planner failed".into()));
assert!(e.to_string().contains("planner failed"));
}
#[test]
fn orchestration_failure_variants_display() {
assert!(
OrchestrationFailure::SchedulerInit("dag error".into())
.to_string()
.contains("dag error")
);
assert!(
OrchestrationFailure::VerifyConfig("bad config".into())
.to_string()
.contains("bad config")
);
assert!(
OrchestrationFailure::PlannerError("plan failed".into())
.to_string()
.contains("plan failed")
);
assert!(
OrchestrationFailure::RetryReset("reset failed".into())
.to_string()
.contains("reset failed")
);
}
#[test]
fn unknown_command_display() {
let e = AgentError::UnknownCommand("/foo".into());
assert!(e.to_string().contains("/foo"));
}
#[test]
fn skill_operation_display() {
let e =
AgentError::SkillOperation(SkillOperationFailure::DirectoryNotFound("my-skill".into()));
assert!(e.to_string().contains("my-skill"));
}
#[test]
fn skill_operation_failure_variants_display() {
assert!(
SkillOperationFailure::InvalidName("bad/name".into())
.to_string()
.contains("bad/name")
);
assert!(
SkillOperationFailure::DirectoryNotFound("foo".into())
.to_string()
.contains("foo")
);
}
}