Skip to main content

ag_agent/app_server/
error.rs

1use crate::app_server_transport::AppServerTransportError;
2
3/// Typed error returned by app-server infrastructure operations.
4///
5/// Wraps transport failures, prompt rendering issues, lock errors, and
6/// provider-specific failures so callers can distinguish error categories
7/// without parsing opaque strings.
8#[derive(Debug, thiserror::Error)]
9pub enum AppServerError {
10    /// The user interrupted an in-flight app-server turn.
11    #[error("{0}")]
12    InterruptedByUser(String),
13
14    /// The session registry mutex is poisoned.
15    #[error("Failed to lock {provider} app-server session map")]
16    LockPoisoned {
17        /// Provider label for diagnostics.
18        provider: &'static str,
19    },
20
21    /// A prompt template or protocol instruction rendering failed.
22    #[error("{0}")]
23    PromptRender(String),
24
25    /// A provider-specific runtime startup or turn execution failure.
26    #[error("{0}")]
27    Provider(String),
28
29    /// Both the initial attempt and one retry after restart failed.
30    #[error(
31        "{provider} app-server failed, then retry failed after restart: first error: \
32         {first_error}; retry error: {retry_error}"
33    )]
34    RetryExhausted {
35        /// Provider label for diagnostics.
36        provider: &'static str,
37        /// Error message from the first failed attempt.
38        first_error: String,
39        /// Error message from the retry attempt after restart.
40        retry_error: String,
41    },
42
43    /// An stdio transport or process communication failure.
44    #[error(transparent)]
45    Transport(#[from] AppServerTransportError),
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn lock_poisoned_display_includes_provider_name() {
54        // Arrange
55        let error = AppServerError::LockPoisoned { provider: "Codex" };
56
57        // Act
58        let display = error.to_string();
59
60        // Assert
61        assert_eq!(display, "Failed to lock Codex app-server session map");
62    }
63
64    #[test]
65    fn interrupted_by_user_display_shows_message() {
66        // Arrange
67        let error = AppServerError::InterruptedByUser("[Stopped]".to_string());
68
69        // Act / Assert
70        assert_eq!(error.to_string(), "[Stopped]");
71    }
72
73    #[test]
74    fn prompt_render_display_shows_message() {
75        // Arrange
76        let error = AppServerError::PromptRender("template syntax error".to_string());
77
78        // Act / Assert
79        assert_eq!(error.to_string(), "template syntax error");
80    }
81
82    #[test]
83    fn provider_display_shows_message() {
84        // Arrange
85        let error = AppServerError::Provider("runtime crashed".to_string());
86
87        // Act / Assert
88        assert_eq!(error.to_string(), "runtime crashed");
89    }
90
91    #[test]
92    fn retry_exhausted_display_includes_both_errors() {
93        // Arrange
94        let error = AppServerError::RetryExhausted {
95            provider: "Codex",
96            first_error: "connection reset".to_string(),
97            retry_error: "timeout".to_string(),
98        };
99
100        // Act
101        let display = error.to_string();
102
103        // Assert
104        assert_eq!(
105            display,
106            "Codex app-server failed, then retry failed after restart: first error: connection \
107             reset; retry error: timeout"
108        );
109    }
110
111    #[test]
112    fn transport_display_delegates_to_inner_error() {
113        // Arrange
114        let error = AppServerError::Transport(AppServerTransportError::ProcessTerminated);
115
116        // Act / Assert
117        assert_eq!(
118            error.to_string(),
119            "App-server terminated before sending expected response"
120        );
121    }
122
123    #[test]
124    fn transport_from_conversion_wraps_transport_error() {
125        // Arrange
126        let transport_error = AppServerTransportError::Timeout {
127            response_id: "init-1".to_string(),
128            timeout_seconds: 300,
129        };
130
131        // Act
132        let error: AppServerError = transport_error.into();
133
134        // Assert
135        assert!(matches!(error, AppServerError::Transport(_)));
136    }
137}