Skip to main content

antigravity_codes/
error.rs

1//! Error types for the harness client.
2
3use std::path::PathBuf;
4
5/// Anything that can go wrong driving a localharness process.
6#[derive(Debug, thiserror::Error)]
7#[non_exhaustive]
8pub enum Error {
9    /// The `localharness` binary could not be located.
10    ///
11    /// It ships only inside the platform wheels published to PyPI, so there is
12    /// no package manager that will put it on `PATH` for you — see
13    /// [`crate::process::find_harness`].
14    #[error(
15        "could not find the localharness binary; set ANTIGRAVITY_HARNESS_PATH, put \
16         `localharness` on PATH, or install the google-antigravity wheel"
17    )]
18    HarnessNotFound,
19
20    /// The configured binary path does not exist or is not executable.
21    #[error("localharness at {path} is not usable: {source}")]
22    HarnessNotExecutable {
23        /// The path that was tried.
24        path: PathBuf,
25        /// The underlying filesystem error.
26        source: std::io::Error,
27    },
28
29    /// The harness exited before completing the stdio handshake.
30    ///
31    /// `stderr` is included because the harness reports configuration problems
32    /// there and then exits, rather than replying on the wire.
33    #[error("localharness exited during handshake: {stderr}")]
34    HandshakeFailed {
35        /// Whatever the process wrote to stderr before dying.
36        stderr: String,
37    },
38
39    /// The handshake reply could not be parsed.
40    #[error("malformed handshake reply: {0}")]
41    Handshake(#[from] crate::handshake::DecodeError),
42
43    /// The WebSocket never accepted a connection on the port the harness
44    /// reported.
45    #[error("could not connect to the harness WebSocket on port {port} after {attempts} attempts")]
46    WebSocketUnreachable {
47        /// The port from `OutputConfig`.
48        port: u16,
49        /// How many connection attempts were made.
50        attempts: u32,
51    },
52
53    /// The WebSocket closed or failed mid-session.
54    ///
55    /// Boxed because `tungstenite::Error` is by far the largest thing this enum
56    /// carries, and every fallible call in the crate returns this type.
57    #[error("websocket error: {0}")]
58    #[cfg(feature = "async-client")]
59    WebSocket(Box<tokio_tungstenite::tungstenite::Error>),
60
61    /// A frame arrived that this crate could not decode.
62    ///
63    /// The raw text is retained: unlike an unknown enum value or an unknown
64    /// `oneof` arm — both of which decode fine by design — this means the frame
65    /// is structurally unexpected, and the payload is what makes it reportable.
66    #[error("could not decode frame: {source}")]
67    Decode {
68        /// The serde failure.
69        source: serde_json::Error,
70        /// The frame exactly as received.
71        raw: String,
72    },
73
74    /// The session ended while a caller was waiting on it.
75    #[error("the harness session has closed")]
76    SessionClosed,
77
78    /// The agent stopped without completing the turn.
79    ///
80    /// Raised after the turn's steps have been delivered, so whatever the agent
81    /// managed to say before failing has already been seen.
82    #[error("the turn failed: {message}")]
83    Turn {
84        /// What the harness reported on the trajectory.
85        message: String,
86    },
87
88    /// Underlying process or socket I/O failed.
89    #[error("io error: {0}")]
90    Io(#[from] std::io::Error),
91}
92
93/// Convenience alias for fallible harness operations.
94pub type Result<T> = std::result::Result<T, Error>;
95
96#[cfg(feature = "async-client")]
97impl From<tokio_tungstenite::tungstenite::Error> for Error {
98    fn from(e: tokio_tungstenite::tungstenite::Error) -> Self {
99        Self::WebSocket(Box::new(e))
100    }
101}
102
103#[cfg(feature = "async-client")]
104impl Error {
105    /// Builds a [`Error::Decode`] that keeps the offending frame.
106    pub(crate) fn decode(source: serde_json::Error, raw: impl Into<String>) -> Self {
107        Self::Decode {
108            source,
109            raw: raw.into(),
110        }
111    }
112}