1use serde::Serialize;
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
8#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
9pub enum ErrorCode {
10 MissingRequiredField,
12 InvalidFieldValue,
13 InvalidState,
14
15 AgentNotFound,
17 TaskNotFound,
18 FileNotFound,
19 AttachmentNotFound,
20
21 AlreadyClaimed,
23 AlreadyExists,
24 DependencyCycle,
25 TagMismatch,
26 NotOwner,
27 DependencyNotSatisfied,
28
29 DatabaseError,
31 InternalError,
32 UnknownTool,
33}
34
35#[derive(Debug, Serialize)]
37pub struct ToolError {
38 pub code: ErrorCode,
39 pub message: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub field: Option<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub details: Option<String>,
44}
45
46impl ToolError {
47 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
48 Self {
49 code,
50 message: message.into(),
51 field: None,
52 details: None,
53 }
54 }
55
56 pub fn with_field(mut self, field: impl Into<String>) -> Self {
57 self.field = Some(field.into());
58 self
59 }
60
61 pub fn with_details(mut self, details: impl Into<String>) -> Self {
62 self.details = Some(details.into());
63 self
64 }
65
66 pub fn missing_field(field: &str) -> Self {
69 Self::new(
70 ErrorCode::MissingRequiredField,
71 format!("{} is required", field),
72 )
73 .with_field(field)
74 }
75
76 pub fn invalid_value(field: &str, reason: &str) -> Self {
77 Self::new(ErrorCode::InvalidFieldValue, reason).with_field(field)
78 }
79
80 pub fn agent_not_found(agent_id: &str) -> Self {
81 Self::new(
82 ErrorCode::AgentNotFound,
83 format!("Agent not found: {}", agent_id),
84 )
85 }
86
87 pub fn task_not_found(task_id: &str) -> Self {
88 Self::new(
89 ErrorCode::TaskNotFound,
90 format!("Task not found: {}", task_id),
91 )
92 }
93
94 pub fn already_claimed(task_id: &str, owner: &str) -> Self {
95 Self::new(
96 ErrorCode::AlreadyClaimed,
97 format!("Task {} already claimed by {}", task_id, owner),
98 )
99 }
100
101 pub fn not_owner(task_id: &str, agent_id: &str) -> Self {
102 Self::new(
103 ErrorCode::NotOwner,
104 format!("Agent {} does not own task {}", agent_id, task_id),
105 )
106 }
107
108 pub fn dependency_cycle(blocker: &str, blocked: &str) -> Self {
109 Self::new(
110 ErrorCode::DependencyCycle,
111 format!(
112 "Adding dependency {} -> {} would create a cycle",
113 blocker, blocked
114 ),
115 )
116 }
117
118 pub fn tag_mismatch(missing: &str) -> Self {
119 Self::new(
120 ErrorCode::TagMismatch,
121 format!("Agent missing required tag(s): {}", missing),
122 )
123 }
124
125 pub fn deps_not_satisfied(blockers: &[String]) -> Self {
126 Self::new(
127 ErrorCode::DependencyNotSatisfied,
128 format!("Task blocked by: {}", blockers.join(", ")),
129 )
130 }
131
132 pub fn database(err: impl fmt::Display) -> Self {
133 Self::new(ErrorCode::DatabaseError, err.to_string())
134 }
135
136 pub fn internal(err: impl fmt::Display) -> Self {
137 Self::new(ErrorCode::InternalError, err.to_string())
138 }
139
140 pub fn unknown_tool(name: &str) -> Self {
141 Self::new(ErrorCode::UnknownTool, format!("Unknown tool: {}", name))
142 }
143}
144
145impl fmt::Display for ToolError {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}", self.message)
148 }
149}
150
151impl std::error::Error for ToolError {}
152
153impl From<anyhow::Error> for ToolError {
155 fn from(err: anyhow::Error) -> Self {
156 match err.downcast::<ToolError>() {
158 Ok(tool_err) => tool_err,
159 Err(err) => ToolError::internal(err),
160 }
161 }
162}
163
164
165pub type ToolResult<T> = std::result::Result<T, ToolError>;