Skip to main content

feagi_agent/
feagi_agent_error.rs

1//! Unified error types for the FEAGI agent (client and server).
2
3use feagi_io::FeagiNetworkError;
4use feagi_structures::FeagiDataError;
5use std::error::Error;
6use std::fmt::{Display, Formatter};
7
8/// Errors that can occur in FEAGI agent operations (both client and server).
9#[derive(Debug, Clone)]
10pub enum FeagiAgentError {
11    /// Unable to initialize/start (typically server-side)
12    InitFail(String),
13    /// Failed to connect
14    ConnectionFailed(String),
15    /// Authentication failed (invalid credentials, expired token, etc.)
16    AuthenticationFailed(String),
17    /// Cannot understand what the remote endpoint sent
18    UnableToDecodeReceivedData(String),
19    /// Failed to send data to the remote endpoint
20    UnableToSendData(String),
21    /// Something went wrong with the server network socket and it should be restarted
22    SocketFailure(String),
23    /// Other/uncategorized error
24    Other(String),
25}
26
27impl Display for FeagiAgentError {
28    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29        match self {
30            FeagiAgentError::InitFail(msg) => {
31                write!(f, "FeagiAgentError: Init failed: {}", msg)
32            }
33            FeagiAgentError::ConnectionFailed(msg) => {
34                write!(f, "FeagiAgentError: Connection failed: {}", msg)
35            }
36            FeagiAgentError::AuthenticationFailed(msg) => {
37                write!(f, "FeagiAgentError: Authentication failed: {}", msg)
38            }
39            FeagiAgentError::UnableToDecodeReceivedData(msg) => {
40                write!(
41                    f,
42                    "FeagiAgentError: Unable to decode received data: {}",
43                    msg
44                )
45            }
46            FeagiAgentError::UnableToSendData(msg) => {
47                write!(f, "FeagiAgentError: Unable to send data: {}", msg)
48            }
49            FeagiAgentError::SocketFailure(msg) => {
50                write!(f, "FeagiAgentError: Socket failure: {}", msg)
51            }
52            FeagiAgentError::Other(msg) => {
53                write!(f, "FeagiAgentError: {}", msg)
54            }
55        }
56    }
57}
58
59impl Error for FeagiAgentError {
60    fn source(&self) -> Option<&(dyn Error + 'static)> {
61        None
62    }
63}
64
65impl From<FeagiDataError> for FeagiAgentError {
66    fn from(err: FeagiDataError) -> Self {
67        match err {
68            FeagiDataError::DeserializationError(msg) => {
69                FeagiAgentError::UnableToDecodeReceivedData(msg)
70            }
71            FeagiDataError::SerializationError(msg) => FeagiAgentError::UnableToSendData(msg),
72            FeagiDataError::BadParameters(msg) => {
73                FeagiAgentError::Other(format!("Bad parameters: {}", msg))
74            }
75            FeagiDataError::NeuronError(msg) => {
76                FeagiAgentError::Other(format!("Neuron error: {}", msg))
77            }
78            FeagiDataError::InternalError(msg) => {
79                FeagiAgentError::Other(format!("Internal error: {}", msg))
80            }
81            FeagiDataError::ResourceLockedWhileRunning(msg) => {
82                FeagiAgentError::Other(format!("Resource locked: {}", msg))
83            }
84            FeagiDataError::ConstError(msg) => {
85                FeagiAgentError::Other(format!("Const error: {}", msg))
86            }
87            FeagiDataError::NotImplemented => FeagiAgentError::Other("Not implemented".to_string()),
88        }
89    }
90}
91
92/// Returns true when `err` indicates a non-blocking transport send would block (e.g. ZMQ `EAGAIN`
93/// surfaced as `FeagiNetworkError::SendFailed` with `"Socket would block"`).
94///
95/// Used by callers and telemetry to avoid treating transient backpressure like a session failure.
96pub fn is_transient_zmq_send_would_block(err: &FeagiAgentError) -> bool {
97    match err {
98        FeagiAgentError::UnableToSendData(msg) | FeagiAgentError::SocketFailure(msg) => {
99            msg.contains("Socket would block")
100        }
101        _ => err.to_string().contains("Socket would block"),
102    }
103}
104
105/// String-based check for error messages already formatted (e.g. `anyhow` chains).
106pub fn is_transient_zmq_send_message(message: &str) -> bool {
107    message.contains("Socket would block")
108}
109
110impl From<FeagiNetworkError> for FeagiAgentError {
111    fn from(err: FeagiNetworkError) -> Self {
112        match err {
113            FeagiNetworkError::CannotBind(msg) => {
114                FeagiAgentError::InitFail(format!("Cannot bind: {}", msg))
115            }
116            FeagiNetworkError::CannotUnbind(msg) => {
117                FeagiAgentError::SocketFailure(format!("Cannot unbind: {}", msg))
118            }
119            FeagiNetworkError::CannotConnect(msg) => {
120                FeagiAgentError::ConnectionFailed(format!("Cannot connect: {}", msg))
121            }
122            FeagiNetworkError::CannotDisconnect(msg) => {
123                FeagiAgentError::SocketFailure(format!("Cannot disconnect: {}", msg))
124            }
125            FeagiNetworkError::SendFailed(msg) => FeagiAgentError::UnableToSendData(msg),
126            FeagiNetworkError::ReceiveFailed(msg) => {
127                FeagiAgentError::UnableToDecodeReceivedData(format!("Receive failed: {}", msg))
128            }
129            FeagiNetworkError::InvalidSocketProperties(msg) => {
130                FeagiAgentError::InitFail(format!("Invalid socket properties: {}", msg))
131            }
132            FeagiNetworkError::SocketCreationFailed(msg) => {
133                FeagiAgentError::SocketFailure(format!("Socket creation failed: {}", msg))
134            }
135            FeagiNetworkError::GeneralFailure(msg) => {
136                FeagiAgentError::Other(format!("General failure: {}", msg))
137            }
138        }
139    }
140}
141
142#[cfg(test)]
143mod transient_send_tests {
144    use super::*;
145
146    #[test]
147    fn detects_would_block_in_unable_to_send() {
148        let e = FeagiAgentError::UnableToSendData("Socket would block".to_string());
149        assert!(is_transient_zmq_send_would_block(&e));
150    }
151
152    #[test]
153    fn ignores_other_unable_to_send() {
154        let e =
155            FeagiAgentError::UnableToSendData("Cannot send to inactive sensory socket".to_string());
156        assert!(!is_transient_zmq_send_would_block(&e));
157    }
158
159    #[test]
160    fn message_helper_matches() {
161        assert!(is_transient_zmq_send_message(
162            "FeagiAgentError: Unable to send data: Socket would block"
163        ));
164        assert!(!is_transient_zmq_send_message("connection reset"));
165    }
166}