1#[derive(Debug, thiserror::Error)]
11pub enum OrchestrationFailure {
12 #[error("scheduler error: {0}")]
14 SchedulerInit(String),
15
16 #[error("config verification error: {0}")]
18 VerifyConfig(String),
19
20 #[error("planner error: {0}")]
22 PlannerError(String),
23
24 #[error("retry reset error: {0}")]
26 RetryReset(String),
27
28 #[error("{0}")]
30 Generic(String),
31}
32
33#[derive(Debug, thiserror::Error)]
37pub enum SkillOperationFailure {
38 #[error("invalid skill name: {0}")]
40 InvalidName(String),
41
42 #[error("skill directory not found: {0}")]
44 DirectoryNotFound(String),
45
46 #[error("{0}")]
48 Generic(String),
49}
50
51#[derive(Debug, thiserror::Error)]
58pub enum AgentError {
59 #[error(transparent)]
60 Llm(#[from] zeph_llm::LlmError),
61
62 #[error(transparent)]
63 Channel(#[from] crate::channel::ChannelError),
64
65 #[error(transparent)]
66 Memory(#[from] zeph_memory::MemoryError),
67
68 #[error(transparent)]
69 Skill(#[from] zeph_skills::SkillError),
70
71 #[error(transparent)]
72 Tool(#[from] zeph_tools::executor::ToolError),
73
74 #[error("I/O error: {0}")]
75 Io(#[from] std::io::Error),
76
77 #[error("blocking task failed: {0}")]
79 SpawnBlocking(#[from] tokio::task::JoinError),
80
81 #[error("agent shut down")]
83 Shutdown,
84
85 #[error("context exhausted: {0}")]
87 ContextExhausted(String),
88
89 #[error("tool timed out: {tool_name}")]
91 ToolTimeout { tool_name: zeph_common::ToolName },
92
93 #[error("schema validation failed: {0}")]
95 SchemaValidation(String),
96
97 #[error("orchestration error: {0}")]
99 OrchestrationError(#[from] OrchestrationFailure),
100
101 #[error("unknown command: {0}")]
103 UnknownCommand(String),
104
105 #[error("skill error: {0}")]
107 SkillOperation(#[from] SkillOperationFailure),
108
109 #[error("context error: {0}")]
111 ContextError(String),
112
113 #[error("database error: {0}")]
115 Db(String),
116}
117
118impl AgentError {
119 #[must_use]
121 pub fn is_context_length_error(&self) -> bool {
122 if let Self::Llm(e) = self {
123 return e.is_context_length_error();
124 }
125 false
126 }
127
128 #[must_use]
130 pub fn is_beta_header_rejected(&self) -> bool {
131 if let Self::Llm(e) = self {
132 return e.is_beta_header_rejected();
133 }
134 false
135 }
136
137 #[must_use]
139 pub fn is_no_providers(&self) -> bool {
140 matches!(self, Self::Llm(zeph_llm::LlmError::NoProviders))
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn agent_error_detects_context_length_from_llm() {
150 let e = AgentError::Llm(zeph_llm::LlmError::ContextLengthExceeded);
151 assert!(e.is_context_length_error());
152 }
153
154 #[test]
155 fn agent_error_detects_context_length_from_typed_variant() {
156 let e = AgentError::Llm(zeph_llm::LlmError::ContextLengthExceeded);
158 assert!(e.is_context_length_error());
159 }
160
161 #[test]
162 fn agent_error_other_with_context_message_not_detected() {
163 let e = AgentError::Llm(zeph_llm::LlmError::Other("context length exceeded".into()));
166 assert!(!e.is_context_length_error());
167 }
168
169 #[test]
170 fn agent_error_non_llm_variant_not_detected() {
171 let e = AgentError::ContextError("something went wrong".into());
172 assert!(!e.is_context_length_error());
173 }
174
175 #[test]
176 fn shutdown_variant_display() {
177 let e = AgentError::Shutdown;
178 assert_eq!(e.to_string(), "agent shut down");
179 }
180
181 #[test]
182 fn context_exhausted_variant_display() {
183 let e = AgentError::ContextExhausted("no space left".into());
184 assert!(e.to_string().contains("no space left"));
185 }
186
187 #[test]
188 fn tool_timeout_variant_display() {
189 let e = AgentError::ToolTimeout {
190 tool_name: "bash".into(),
191 };
192 assert!(e.to_string().contains("bash"));
193 }
194
195 #[test]
196 fn schema_validation_variant_display() {
197 let e = AgentError::SchemaValidation("missing field".into());
198 assert!(e.to_string().contains("missing field"));
199 }
200
201 #[test]
202 fn agent_error_detects_beta_header_rejected() {
203 let e = AgentError::Llm(zeph_llm::LlmError::BetaHeaderRejected {
204 header: "compact-2026-01-12".into(),
205 });
206 assert!(e.is_beta_header_rejected());
207 }
208
209 #[test]
210 fn agent_error_non_llm_variant_not_beta_rejected() {
211 let e = AgentError::ContextError("something went wrong".into());
212 assert!(!e.is_beta_header_rejected());
213 }
214
215 #[test]
216 fn agent_error_detects_no_providers() {
217 let e = AgentError::Llm(zeph_llm::LlmError::NoProviders);
218 assert!(e.is_no_providers());
219 }
220
221 #[test]
222 fn agent_error_non_no_providers_returns_false() {
223 let e = AgentError::ContextError("other".into());
224 assert!(!e.is_no_providers());
225 }
226
227 #[test]
228 fn orchestration_error_display() {
229 let e =
230 AgentError::OrchestrationError(OrchestrationFailure::Generic("planner failed".into()));
231 assert!(e.to_string().contains("planner failed"));
232 }
233
234 #[test]
235 fn orchestration_failure_variants_display() {
236 assert!(
237 OrchestrationFailure::SchedulerInit("dag error".into())
238 .to_string()
239 .contains("dag error")
240 );
241 assert!(
242 OrchestrationFailure::VerifyConfig("bad config".into())
243 .to_string()
244 .contains("bad config")
245 );
246 assert!(
247 OrchestrationFailure::PlannerError("plan failed".into())
248 .to_string()
249 .contains("plan failed")
250 );
251 assert!(
252 OrchestrationFailure::RetryReset("reset failed".into())
253 .to_string()
254 .contains("reset failed")
255 );
256 }
257
258 #[test]
259 fn unknown_command_display() {
260 let e = AgentError::UnknownCommand("/foo".into());
261 assert!(e.to_string().contains("/foo"));
262 }
263
264 #[test]
265 fn skill_operation_display() {
266 let e =
267 AgentError::SkillOperation(SkillOperationFailure::DirectoryNotFound("my-skill".into()));
268 assert!(e.to_string().contains("my-skill"));
269 }
270
271 #[test]
272 fn skill_operation_failure_variants_display() {
273 assert!(
274 SkillOperationFailure::InvalidName("bad/name".into())
275 .to_string()
276 .contains("bad/name")
277 );
278 assert!(
279 SkillOperationFailure::DirectoryNotFound("foo".into())
280 .to_string()
281 .contains("foo")
282 );
283 }
284}