1#[derive(Debug, thiserror::Error)]
5pub enum AgentError {
6 #[error(transparent)]
7 Llm(#[from] zeph_llm::LlmError),
8
9 #[error(transparent)]
10 Channel(#[from] crate::channel::ChannelError),
11
12 #[error(transparent)]
13 Memory(#[from] zeph_memory::MemoryError),
14
15 #[error(transparent)]
16 Skill(#[from] zeph_skills::SkillError),
17
18 #[error(transparent)]
19 Tool(#[from] zeph_tools::executor::ToolError),
20
21 #[error("I/O error: {0}")]
22 Io(#[from] std::io::Error),
23
24 #[error("agent shut down")]
26 Shutdown,
27
28 #[error("context exhausted: {0}")]
30 ContextExhausted(String),
31
32 #[error("tool timed out: {tool_name}")]
34 ToolTimeout { tool_name: zeph_common::ToolName },
35
36 #[error("schema validation failed: {0}")]
38 SchemaValidation(String),
39
40 #[error("{0}")]
41 Other(String),
42}
43
44impl AgentError {
45 #[must_use]
47 pub fn is_context_length_error(&self) -> bool {
48 if let Self::Llm(e) = self {
49 return e.is_context_length_error();
50 }
51 false
52 }
53
54 #[must_use]
56 pub fn is_beta_header_rejected(&self) -> bool {
57 if let Self::Llm(e) = self {
58 return e.is_beta_header_rejected();
59 }
60 false
61 }
62
63 #[must_use]
65 pub fn is_no_providers(&self) -> bool {
66 matches!(self, Self::Llm(zeph_llm::LlmError::NoProviders))
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn agent_error_detects_context_length_from_llm() {
76 let e = AgentError::Llm(zeph_llm::LlmError::ContextLengthExceeded);
77 assert!(e.is_context_length_error());
78 }
79
80 #[test]
81 fn agent_error_detects_context_length_from_other_message() {
82 let e = AgentError::Llm(zeph_llm::LlmError::Other("context length exceeded".into()));
83 assert!(e.is_context_length_error());
84 }
85
86 #[test]
87 fn agent_error_non_llm_variant_not_detected() {
88 let e = AgentError::Other("something went wrong".into());
89 assert!(!e.is_context_length_error());
90 }
91
92 #[test]
93 fn shutdown_variant_display() {
94 let e = AgentError::Shutdown;
95 assert_eq!(e.to_string(), "agent shut down");
96 }
97
98 #[test]
99 fn context_exhausted_variant_display() {
100 let e = AgentError::ContextExhausted("no space left".into());
101 assert!(e.to_string().contains("no space left"));
102 }
103
104 #[test]
105 fn tool_timeout_variant_display() {
106 let e = AgentError::ToolTimeout {
107 tool_name: "bash".into(),
108 };
109 assert!(e.to_string().contains("bash"));
110 }
111
112 #[test]
113 fn schema_validation_variant_display() {
114 let e = AgentError::SchemaValidation("missing field".into());
115 assert!(e.to_string().contains("missing field"));
116 }
117
118 #[test]
119 fn agent_error_detects_beta_header_rejected() {
120 let e = AgentError::Llm(zeph_llm::LlmError::BetaHeaderRejected {
121 header: "compact-2026-01-12".into(),
122 });
123 assert!(e.is_beta_header_rejected());
124 }
125
126 #[test]
127 fn agent_error_non_llm_variant_not_beta_rejected() {
128 let e = AgentError::Other("something went wrong".into());
129 assert!(!e.is_beta_header_rejected());
130 }
131
132 #[test]
133 fn agent_error_detects_no_providers() {
134 let e = AgentError::Llm(zeph_llm::LlmError::NoProviders);
135 assert!(e.is_no_providers());
136 }
137
138 #[test]
139 fn agent_error_non_no_providers_returns_false() {
140 let e = AgentError::Other("other".into());
141 assert!(!e.is_no_providers());
142 }
143}