1use std::backtrace::{Backtrace, BacktraceStatus};
4use std::borrow::{Borrow, Cow};
5use std::fmt;
6use std::time::Duration;
7
8use crate::types::SessionId;
9
10pub type Result<T> = std::result::Result<T, Error>;
12
13#[derive(Debug)]
21pub(crate) enum Repr<T: fmt::Debug> {
22 Simple(T),
23 SimpleMessage(T, Cow<'static, str>),
24 Custom(Custom<T>),
25 }
27
28#[derive(Debug)]
30pub(crate) struct Custom<T: fmt::Debug> {
31 pub(crate) kind: T,
32 pub(crate) error: Box<dyn std::error::Error + Send + Sync>,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq)]
39#[non_exhaustive]
40pub enum ProtocolErrorKind {
41 MissingContentLength,
43
44 InvalidContentLength(String),
46
47 RequestCancelled,
49
50 CliStartupTimeout,
52
53 CliStartupFailed,
55
56 VersionMismatch {
58 server: u32,
60 min: u32,
62 max: u32,
64 },
65
66 InvalidProtocolVersion {
68 server: i64,
70 },
71
72 VersionChanged {
74 previous: u32,
76 current: u32,
78 },
79}
80
81impl fmt::Display for ProtocolErrorKind {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 ProtocolErrorKind::MissingContentLength => {
85 write!(f, "missing Content-Length header")
86 }
87 ProtocolErrorKind::InvalidContentLength(v) => {
88 write!(f, "invalid Content-Length value: \"{v}\"")
89 }
90 ProtocolErrorKind::RequestCancelled => write!(f, "request cancelled"),
91 ProtocolErrorKind::CliStartupTimeout => {
92 write!(f, "timed out waiting for CLI to report listening port")
93 }
94 ProtocolErrorKind::CliStartupFailed => {
95 write!(f, "CLI exited before reporting listening port")
96 }
97 ProtocolErrorKind::VersionMismatch { server, min, max } => {
98 write!(
99 f,
100 "version mismatch: server={server}, supported={min}\u{2013}{max}"
101 )
102 }
103 ProtocolErrorKind::InvalidProtocolVersion { server } => {
104 write!(f, "invalid protocol version: server={server}")
105 }
106 ProtocolErrorKind::VersionChanged { previous, current } => {
107 write!(f, "version changed: was {previous}, now {current}")
108 }
109 }
110 }
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
117#[non_exhaustive]
118pub enum SessionErrorKind {
119 NotFound(SessionId),
121
122 AgentError,
124
125 Timeout(Duration),
127
128 SendWhileWaiting,
130
131 EventLoopClosed,
133
134 ElicitationNotSupported,
137
138 SessionFsProviderRequired,
143
144 InvalidSessionFsConfig,
147
148 SessionIdMismatch {
150 requested: SessionId,
152 returned: SessionId,
154 },
155
156 DetachFailed,
158}
159
160impl fmt::Display for SessionErrorKind {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 match self {
163 SessionErrorKind::NotFound(id) => write!(f, "session not found: {id}"),
164 SessionErrorKind::AgentError => write!(f, "agent error"),
165 SessionErrorKind::Timeout(d) => write!(f, "timed out after {d:?}"),
166 SessionErrorKind::SendWhileWaiting => {
167 write!(f, "cannot send while send_and_wait is in flight")
168 }
169 SessionErrorKind::EventLoopClosed => {
170 write!(f, "event loop closed before session reached idle")
171 }
172 SessionErrorKind::ElicitationNotSupported => write!(
173 f,
174 "elicitation not supported by host \
175 \u{2014} check session.capabilities().ui.elicitation first"
176 ),
177 SessionErrorKind::SessionFsProviderRequired => write!(
178 f,
179 "session was created on a client with session_fs configured \
180 but no SessionFsProvider was supplied"
181 ),
182 SessionErrorKind::InvalidSessionFsConfig => {
183 write!(f, "invalid SessionFsConfig")
184 }
185 SessionErrorKind::SessionIdMismatch {
186 requested,
187 returned,
188 } => write!(
189 f,
190 "CLI returned session ID {returned} after SDK registered {requested}"
191 ),
192 SessionErrorKind::DetachFailed => write!(f, "failed to detach session"),
193 }
194 }
195}
196
197#[derive(Clone, Debug, PartialEq, Eq)]
201#[non_exhaustive]
202pub enum ErrorKind {
203 Protocol(ProtocolErrorKind),
205 Rpc {
207 code: i32,
209 },
210 Session(SessionErrorKind),
212 Io,
214 Json,
216 BinaryNotFound {
218 name: String,
220 hint: Option<String>,
222 },
223 InvalidConfig,
225 GitHubTokenProvider,
227}
228
229impl fmt::Display for ErrorKind {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 match self {
232 ErrorKind::Protocol(k) => write!(f, "{k}"),
233 ErrorKind::Rpc { code } => write!(f, "RPC error {code}"),
234 ErrorKind::Session(k) => write!(f, "{k}"),
235 ErrorKind::Io => write!(f, "I/O error"),
236 ErrorKind::Json => write!(f, "JSON error"),
237 ErrorKind::BinaryNotFound {
238 name,
239 hint: Some(h),
240 } => {
241 write!(f, "binary not found: {name} ({h})")
242 }
243 ErrorKind::BinaryNotFound { name, hint: None } => {
244 write!(f, "binary not found: {name}")
245 }
246 ErrorKind::InvalidConfig => write!(f, "invalid configuration"),
247 ErrorKind::GitHubTokenProvider => write!(f, "GitHub token provider error"),
248 }
249 }
250}
251
252pub struct Error {
254 repr: Repr<ErrorKind>,
255 backtrace: Option<Box<Backtrace>>,
258}
259
260impl Error {
261 pub(crate) fn new<E>(kind: ErrorKind, error: E) -> Self
263 where
264 E: Into<Box<dyn std::error::Error + Send + Sync>>,
265 {
266 Self {
267 repr: Repr::Custom(Custom {
268 kind,
269 error: error.into(),
270 }),
271 backtrace: capture_backtrace(),
272 }
273 }
274
275 pub fn kind(&self) -> &ErrorKind {
277 match &self.repr {
278 Repr::Simple(kind)
279 | Repr::SimpleMessage(kind, ..)
280 | Repr::Custom(Custom { kind, .. }) => kind,
281 }
282 }
283
284 pub fn message(&self) -> Option<&str> {
286 match &self.repr {
287 Repr::SimpleMessage(_, message) => Some(message.borrow()),
288 _ => None,
289 }
290 }
291
292 #[must_use]
294 pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
295 where
296 C: Into<Cow<'static, str>>,
297 {
298 Self {
299 repr: Repr::SimpleMessage(kind, message.into()),
300 backtrace: capture_backtrace(),
301 }
302 }
303
304 pub fn is_transport_failure(&self) -> bool {
308 matches!(self.kind(), ErrorKind::Io)
309 || matches!(
310 self.kind(),
311 ErrorKind::Protocol(ProtocolErrorKind::RequestCancelled)
312 )
313 }
314
315 pub fn rpc_code(&self) -> Option<i32> {
317 match self.kind() {
318 ErrorKind::Rpc { code } => Some(*code),
319 _ => None,
320 }
321 }
322}
323
324impl fmt::Display for Error {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 match &self.repr {
327 Repr::Simple(kind) => write!(f, "{kind}"),
328 Repr::SimpleMessage(kind, message) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
329 write!(f, "{kind}: {message}")
330 }
331 Repr::SimpleMessage(_, message) => write!(f, "{message}"),
332 Repr::Custom(Custom { kind, error }) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
333 write!(f, "{kind}: {error}")
334 }
335 Repr::Custom(Custom { error, .. }) => write!(f, "{error}"),
336 }
337 }
338}
339
340impl fmt::Debug for Error {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 let mut dbg = f.debug_struct("Error");
343 dbg.field("context", &self.repr);
344 if let Some(backtrace) = &self.backtrace {
345 return dbg.field("backtrace", backtrace).finish();
346 }
347 dbg.finish_non_exhaustive()
348 }
349}
350
351impl std::error::Error for Error {
352 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
353 match &self.repr {
354 Repr::Custom(Custom { error, .. }) => Some(&**error),
355 _ => None,
356 }
357 }
358}
359
360impl From<ErrorKind> for Error {
361 fn from(kind: ErrorKind) -> Self {
362 Self {
363 repr: Repr::Simple(kind),
364 backtrace: capture_backtrace(),
365 }
366 }
367}
368
369impl From<ProtocolErrorKind> for Error {
370 fn from(kind: ProtocolErrorKind) -> Self {
371 Self::from(ErrorKind::Protocol(kind))
372 }
373}
374
375impl From<SessionErrorKind> for Error {
376 fn from(kind: SessionErrorKind) -> Self {
377 Self::from(ErrorKind::Session(kind))
378 }
379}
380
381impl From<std::io::Error> for Error {
382 fn from(error: std::io::Error) -> Self {
383 Self::new(ErrorKind::Io, error)
384 }
385}
386
387impl From<serde_json::Error> for Error {
388 fn from(error: serde_json::Error) -> Self {
389 Self::new(ErrorKind::Json, error)
390 }
391}
392
393#[inline(always)]
394fn capture_backtrace() -> Option<Box<Backtrace>> {
395 let backtrace = Backtrace::capture();
396 if backtrace.status() == BacktraceStatus::Captured {
397 Some(Box::new(backtrace))
398 } else {
399 None
400 }
401}
402
403#[derive(Debug)]
415pub struct StopErrors(pub(crate) Vec<Error>);
416
417impl StopErrors {
418 pub fn errors(&self) -> &[Error] {
421 &self.0
422 }
423
424 pub fn into_errors(self) -> Vec<Error> {
426 self.0
427 }
428}
429
430impl fmt::Display for StopErrors {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 match self.0.as_slice() {
433 [] => write!(f, "stop completed with no errors"),
434 [only] => write!(f, "stop failed: {only}"),
435 [first, rest @ ..] => write!(
436 f,
437 "stop failed with {n} errors; first: {first}",
438 n = 1 + rest.len(),
439 ),
440 }
441 }
442}
443
444impl std::error::Error for StopErrors {
445 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
446 self.0
447 .first()
448 .map(|e| e as &(dyn std::error::Error + 'static))
449 }
450}