Skip to main content

netget/events/
errors.rs

1//! Action execution error types
2//!
3//! Defines error types for action execution that can trigger LLM retries
4
5use std::fmt;
6
7/// Errors that can occur during action execution
8#[derive(Debug)]
9pub enum ActionExecutionError {
10    /// Port is already in use - LLM should choose different port
11    PortConflict {
12        port: u16,
13        protocol: String,
14        underlying_error: String,
15    },
16
17    /// Privilege requirements not met - usually not retryable
18    /// (should be caught by pre-flight checks, but included for completeness)
19    PrivilegeDenied {
20        requirement: String,
21        message: String,
22    },
23
24    /// Fatal error that should not be retried
25    Fatal(anyhow::Error),
26
27    /// SQL execution error - retryable with error context
28    #[cfg(feature = "sqlite")]
29    SqlError {
30        database_id: u32,
31        query: String,
32        error: String,
33    },
34
35    /// Documentation required before opening server/client
36    /// This error includes the documentation content to show the LLM
37    DocumentationRequired {
38        action_type: String, // "open_server" or "open_client"
39        protocol: String,
40        documentation: String,
41        original_action: serde_json::Value,
42    },
43
44    /// Invalid action parameters - LLM should correct the parameters
45    InvalidActionParameters {
46        action_type: String,
47        parameter_name: String,
48        error_message: String,
49        original_action: serde_json::Value,
50    },
51}
52
53impl fmt::Display for ActionExecutionError {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::PortConflict {
57                port,
58                protocol,
59                underlying_error,
60            } => {
61                write!(
62                    f,
63                    "Port {} conflict for {} server: {}",
64                    port, protocol, underlying_error
65                )
66            }
67            Self::PrivilegeDenied {
68                requirement,
69                message,
70            } => {
71                write!(f, "Privilege denied ({}): {}", requirement, message)
72            }
73            Self::Fatal(e) => write!(f, "Fatal error: {}", e),
74            #[cfg(feature = "sqlite")]
75            Self::SqlError {
76                database_id, error, ..
77            } => {
78                write!(f, "SQL error in database {}: {}", database_id, error)
79            }
80            Self::DocumentationRequired {
81                action_type,
82                protocol,
83                ..
84            } => {
85                write!(
86                    f,
87                    "Documentation required for {} with protocol {}",
88                    action_type, protocol
89                )
90            }
91            Self::InvalidActionParameters {
92                action_type,
93                parameter_name,
94                error_message,
95                ..
96            } => {
97                write!(
98                    f,
99                    "Invalid parameter '{}' for {}: {}",
100                    parameter_name, action_type, error_message
101                )
102            }
103        }
104    }
105}
106
107impl std::error::Error for ActionExecutionError {}
108
109impl ActionExecutionError {
110    /// Check if this error is retryable
111    pub fn is_retryable(&self) -> bool {
112        #[allow(clippy::match_like_matches_macro)]
113        match self {
114            Self::PortConflict { .. } => true,
115            #[cfg(feature = "sqlite")]
116            Self::SqlError { .. } => true,
117            Self::DocumentationRequired { .. } => true,
118            Self::InvalidActionParameters { .. } => true,
119            _ => false,
120        }
121    }
122
123    /// Build a correction message for the LLM
124    pub fn build_correction_message(&self) -> String {
125        match self {
126            Self::PortConflict { port, protocol, .. } => {
127                format!(
128                    r#"Your previous action failed during execution.
129
130Error: Port {} is already in use
131
132The port you specified for the {} server is already bound by another process or service.
133Please choose a different port number.
134
135Suggestions:
136- Try port {} (original + 1000)
137- Try port {} (original + 10000)
138- Try port {} (common alternative)
139- Or choose any available port number
140
141Please provide a corrected open_server action with a different port."#,
142                    port,
143                    protocol,
144                    port + 1000,
145                    port + 10000,
146                    if *port < 1024 { port + 8000 } else { port + 1 }
147                )
148            }
149            Self::PrivilegeDenied {
150                requirement,
151                message,
152            } => {
153                format!(
154                    r#"Your previous action failed during execution.
155
156Error: Insufficient privileges
157
158Requirement: {}
159Message: {}
160
161This error typically cannot be resolved by changing parameters. Please inform the user that elevated privileges are required."#,
162                    requirement, message
163                )
164            }
165            Self::Fatal(e) => {
166                format!(
167                    r#"Your previous action failed during execution.
168
169Error: {}
170
171This error cannot be automatically resolved. Please inform the user of the error."#,
172                    e
173                )
174            }
175            #[cfg(feature = "sqlite")]
176            Self::SqlError {
177                database_id,
178                query,
179                error,
180            } => {
181                format!(
182                    r#"Your previous SQL query failed during execution.
183
184Database ID: {}
185Query: {}
186Error: {}
187
188The SQL query syntax or logic had an error. Please review the error message and provide a corrected execute_sql action.
189
190Common issues:
191- Table or column names don't exist (check schema with list_databases)
192- SQL syntax errors (check SQL statement structure)
193- Type mismatches or constraint violations
194- Missing WHERE clause or incorrect conditions
195
196Please provide a corrected execute_sql action with valid SQL."#,
197                    database_id, query, error
198                )
199            }
200            Self::DocumentationRequired {
201                action_type,
202                protocol,
203                documentation,
204                ..
205            } => {
206                format!(
207                    r#"Before executing {} for protocol '{}', you must first read the documentation.
208
209Here is the documentation for '{}':
210
211{}
212
213Now that you have read the documentation, please confirm your {} action by providing it again.
214If you need to modify the action based on the documentation, please do so.
215
216IMPORTANT: You must provide the action again - it will not be automatically retried."#,
217                    action_type, protocol, protocol, documentation, action_type
218                )
219            }
220            Self::InvalidActionParameters {
221                action_type,
222                parameter_name,
223                error_message,
224                original_action,
225            } => {
226                format!(
227                    r#"Your previous action failed validation.
228
229Action: {}
230Parameter: {}
231Error: {}
232
233Original action you provided:
234{}
235
236Please provide a corrected {} action with the proper parameters.
237
238CRITICAL: Make sure all required fields are included with valid values."#,
239                    action_type,
240                    parameter_name,
241                    error_message,
242                    serde_json::to_string_pretty(original_action)
243                        .unwrap_or_else(|_| format!("{:?}", original_action)),
244                    action_type
245                )
246            }
247        }
248    }
249}
250
251impl From<anyhow::Error> for ActionExecutionError {
252    fn from(err: anyhow::Error) -> Self {
253        // By default, treat as fatal error
254        Self::Fatal(err)
255    }
256}