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}