Skip to main content

megacode_agent_sdk/
error.rs

1//! The one error type (spec slice-ar R8.6.1).
2
3use std::fmt;
4
5/// What went wrong with a session.
6#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum Error {
9    /// No harness binary could be found or obtained (R12).
10    BinaryUnavailable(String),
11    /// The session did not start: no `init` in time, or one this SDK cannot
12    /// speak to (R6.2). The text names what arrived.
13    Handshake(String),
14    /// The session ended without being asked to.
15    SessionEnded {
16        /// What the backend could say about the end, when it has anything.
17        diagnostics: Option<Diagnostics>,
18    },
19    /// A line from the session that is not JSON, or misses a field its type
20    /// requires (R6.3).
21    Protocol(String),
22    /// The session refused a command, or the options it was started with:
23    /// the protocol's error `code` and its `message`.
24    Refused {
25        /// The protocol's error code (`no_turn`, `bad_argument`, …).
26        code: String,
27        /// The session's own explanation.
28        message: String,
29    },
30    /// A turn is still running: one turn at a time, until its `result`.
31    TurnInProgress,
32    /// The program left more than 10,000 events of a turn unread; the
33    /// session was ended rather than stop reading (R8.5.5).
34    SlowConsumer,
35    /// The command does not fit the session's 8 MiB line limit (R8.5.2).
36    TooLarge,
37}
38
39/// What is known about a session that ended unexpectedly. Every field is
40/// optional: a backend fills what it has.
41#[derive(Debug, Clone, Default, PartialEq, Eq)]
42#[non_exhaustive]
43pub struct Diagnostics {
44    /// The exit code, when the session exited with one.
45    pub exit_code: Option<i32>,
46    /// The signal that ended the session, when one did.
47    pub signal: Option<i32>,
48    /// The last lines (at most 200) of the session's error output.
49    pub stderr_tail: Vec<String>,
50}
51
52impl fmt::Display for Error {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Error::BinaryUnavailable(why) => write!(f, "no harness binary: {why}"),
56            Error::Handshake(why) => write!(f, "the session did not start: {why}"),
57            Error::SessionEnded { diagnostics } => {
58                f.write_str("the session ended unexpectedly")?;
59                if let Some(d) = diagnostics {
60                    if let Some(code) = d.exit_code {
61                        write!(f, " (exit {code})")?;
62                    }
63                    if let Some(signal) = d.signal {
64                        write!(f, " (signal {signal})")?;
65                    }
66                    if let Some(last) = d.stderr_tail.last() {
67                        write!(f, ": {last}")?;
68                    }
69                }
70                Ok(())
71            }
72            Error::Protocol(why) => write!(f, "protocol error: {why}"),
73            Error::Refused { code, message } => write!(f, "refused ({code}): {message}"),
74            Error::TurnInProgress => f.write_str("a turn is still running"),
75            Error::SlowConsumer => f.write_str(
76                "the session was ended: more than 10,000 events of a turn were left unread",
77            ),
78            Error::TooLarge => f.write_str("the command is over the session's 8 MiB line limit"),
79        }
80    }
81}
82
83impl std::error::Error for Error {}