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 {}