1use std::backtrace::{Backtrace, BacktraceStatus};
4use std::borrow::{Borrow, Cow};
5use std::fmt;
6use std::time::Duration;
7
8use serde_json::Value;
9
10use crate::types::SessionId;
11
12pub type Result<T> = std::result::Result<T, Error>;
14
15#[derive(Debug)]
23pub(crate) enum Repr<T: fmt::Debug> {
24 Simple(T),
25 SimpleMessage(T, Cow<'static, str>),
26 Custom(Custom<T>),
27 }
29
30#[derive(Debug)]
32pub(crate) struct Custom<T: fmt::Debug> {
33 pub(crate) kind: T,
34 pub(crate) error: Box<dyn std::error::Error + Send + Sync>,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum ProtocolErrorKind {
43 MissingContentLength,
45
46 InvalidContentLength(String),
48
49 RequestCancelled,
51
52 CliStartupTimeout,
54
55 CliStartupFailed,
57
58 VersionMismatch {
60 server: u32,
62 min: u32,
64 max: u32,
66 },
67
68 InvalidProtocolVersion {
70 server: i64,
72 },
73
74 VersionChanged {
76 previous: u32,
78 current: u32,
80 },
81}
82
83impl fmt::Display for ProtocolErrorKind {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 ProtocolErrorKind::MissingContentLength => {
87 write!(f, "missing Content-Length header")
88 }
89 ProtocolErrorKind::InvalidContentLength(v) => {
90 write!(f, "invalid Content-Length value: \"{v}\"")
91 }
92 ProtocolErrorKind::RequestCancelled => write!(f, "request cancelled"),
93 ProtocolErrorKind::CliStartupTimeout => {
94 write!(f, "timed out waiting for CLI to report listening port")
95 }
96 ProtocolErrorKind::CliStartupFailed => {
97 write!(f, "CLI exited before reporting listening port")
98 }
99 ProtocolErrorKind::VersionMismatch { server, min, max } => {
100 write!(
101 f,
102 "version mismatch: server={server}, supported={min}\u{2013}{max}"
103 )
104 }
105 ProtocolErrorKind::InvalidProtocolVersion { server } => {
106 write!(f, "invalid protocol version: server={server}")
107 }
108 ProtocolErrorKind::VersionChanged { previous, current } => {
109 write!(f, "version changed: was {previous}, now {current}")
110 }
111 }
112 }
113}
114
115#[derive(Clone, Debug, PartialEq, Eq)]
119#[non_exhaustive]
120pub enum SessionErrorKind {
121 NotFound(SessionId),
123
124 AgentError,
126
127 Timeout(Duration),
129
130 SendWhileWaiting,
132
133 EventLoopClosed,
135
136 ElicitationNotSupported,
139
140 SessionFsProviderRequired,
145
146 InvalidSessionFsConfig,
149
150 SessionIdMismatch {
152 requested: SessionId,
154 returned: SessionId,
156 },
157
158 DetachFailed,
160}
161
162impl fmt::Display for SessionErrorKind {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 match self {
165 SessionErrorKind::NotFound(id) => write!(f, "session not found: {id}"),
166 SessionErrorKind::AgentError => write!(f, "agent error"),
167 SessionErrorKind::Timeout(d) => write!(f, "timed out after {d:?}"),
168 SessionErrorKind::SendWhileWaiting => {
169 write!(f, "cannot send while send_and_wait is in flight")
170 }
171 SessionErrorKind::EventLoopClosed => {
172 write!(f, "event loop closed before session reached idle")
173 }
174 SessionErrorKind::ElicitationNotSupported => write!(
175 f,
176 "elicitation not supported by host \
177 \u{2014} check session.capabilities().ui.elicitation first"
178 ),
179 SessionErrorKind::SessionFsProviderRequired => write!(
180 f,
181 "session was created on a client with session_fs configured \
182 but no SessionFsProvider was supplied"
183 ),
184 SessionErrorKind::InvalidSessionFsConfig => {
185 write!(f, "invalid SessionFsConfig")
186 }
187 SessionErrorKind::SessionIdMismatch {
188 requested,
189 returned,
190 } => write!(
191 f,
192 "CLI returned session ID {returned} after SDK registered {requested}"
193 ),
194 SessionErrorKind::DetachFailed => write!(f, "failed to detach session"),
195 }
196 }
197}
198
199#[derive(Clone, Debug, PartialEq, Eq)]
203#[non_exhaustive]
204pub enum ErrorKind {
205 Protocol(ProtocolErrorKind),
207 Rpc {
209 code: i32,
211 },
212 Session(SessionErrorKind),
214 Io,
216 Json,
218 BinaryNotFound {
220 name: String,
222 hint: Option<String>,
224 },
225 InvalidConfig,
227 GitHubTokenProvider,
229}
230
231impl fmt::Display for ErrorKind {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 match self {
234 ErrorKind::Protocol(k) => write!(f, "{k}"),
235 ErrorKind::Rpc { code } => write!(f, "RPC error {code}"),
236 ErrorKind::Session(k) => write!(f, "{k}"),
237 ErrorKind::Io => write!(f, "I/O error"),
238 ErrorKind::Json => write!(f, "JSON error"),
239 ErrorKind::BinaryNotFound {
240 name,
241 hint: Some(h),
242 } => {
243 write!(f, "binary not found: {name} ({h})")
244 }
245 ErrorKind::BinaryNotFound { name, hint: None } => {
246 write!(f, "binary not found: {name}")
247 }
248 ErrorKind::InvalidConfig => write!(f, "invalid configuration"),
249 ErrorKind::GitHubTokenProvider => write!(f, "GitHub token provider error"),
250 }
251 }
252}
253
254pub struct Error {
256 repr: Repr<ErrorKind>,
257 rpc_data: Option<Box<Value>>,
258 backtrace: Option<Box<Backtrace>>,
261}
262
263impl Error {
264 pub(crate) fn new<E>(kind: ErrorKind, error: E) -> Self
266 where
267 E: Into<Box<dyn std::error::Error + Send + Sync>>,
268 {
269 Self {
270 repr: Repr::Custom(Custom {
271 kind,
272 error: error.into(),
273 }),
274 rpc_data: None,
275 backtrace: capture_backtrace(),
276 }
277 }
278
279 pub fn kind(&self) -> &ErrorKind {
281 match &self.repr {
282 Repr::Simple(kind)
283 | Repr::SimpleMessage(kind, ..)
284 | Repr::Custom(Custom { kind, .. }) => kind,
285 }
286 }
287
288 pub fn message(&self) -> Option<&str> {
290 match &self.repr {
291 Repr::SimpleMessage(_, message) => Some(message.borrow()),
292 _ => None,
293 }
294 }
295
296 #[must_use]
298 pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
299 where
300 C: Into<Cow<'static, str>>,
301 {
302 Self {
303 repr: Repr::SimpleMessage(kind, message.into()),
304 rpc_data: None,
305 backtrace: capture_backtrace(),
306 }
307 }
308
309 pub(crate) fn from_rpc<C>(code: i32, message: C, data: Option<Value>) -> Self
310 where
311 C: Into<Cow<'static, str>>,
312 {
313 Self {
314 repr: Repr::SimpleMessage(ErrorKind::Rpc { code }, message.into()),
315 rpc_data: data.map(Box::new),
316 backtrace: capture_backtrace(),
317 }
318 }
319
320 pub fn is_transport_failure(&self) -> bool {
324 matches!(self.kind(), ErrorKind::Io)
325 || matches!(
326 self.kind(),
327 ErrorKind::Protocol(ProtocolErrorKind::RequestCancelled)
328 )
329 }
330
331 pub fn rpc_code(&self) -> Option<i32> {
333 match self.kind() {
334 ErrorKind::Rpc { code } => Some(*code),
335 _ => None,
336 }
337 }
338
339 pub fn rpc_data(&self) -> Option<&Value> {
345 self.rpc_data.as_deref()
346 }
347}
348
349impl fmt::Display for Error {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 match &self.repr {
352 Repr::Simple(kind) => write!(f, "{kind}"),
353 Repr::SimpleMessage(kind, message) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
354 write!(f, "{kind}: {message}")
355 }
356 Repr::SimpleMessage(_, message) => write!(f, "{message}"),
357 Repr::Custom(Custom { kind, error }) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
358 write!(f, "{kind}: {error}")
359 }
360 Repr::Custom(Custom { error, .. }) => write!(f, "{error}"),
361 }
362 }
363}
364
365impl fmt::Debug for Error {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 let mut dbg = f.debug_struct("Error");
368 dbg.field("context", &self.repr);
369 if let Some(backtrace) = &self.backtrace {
370 return dbg.field("backtrace", backtrace).finish();
371 }
372 dbg.finish_non_exhaustive()
373 }
374}
375
376impl std::error::Error for Error {
377 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
378 match &self.repr {
379 Repr::Custom(Custom { error, .. }) => Some(&**error),
380 _ => None,
381 }
382 }
383}
384
385impl From<ErrorKind> for Error {
386 fn from(kind: ErrorKind) -> Self {
387 Self {
388 repr: Repr::Simple(kind),
389 rpc_data: None,
390 backtrace: capture_backtrace(),
391 }
392 }
393}
394
395impl From<ProtocolErrorKind> for Error {
396 fn from(kind: ProtocolErrorKind) -> Self {
397 Self::from(ErrorKind::Protocol(kind))
398 }
399}
400
401impl From<SessionErrorKind> for Error {
402 fn from(kind: SessionErrorKind) -> Self {
403 Self::from(ErrorKind::Session(kind))
404 }
405}
406
407impl From<std::io::Error> for Error {
408 fn from(error: std::io::Error) -> Self {
409 Self::new(ErrorKind::Io, error)
410 }
411}
412
413impl From<serde_json::Error> for Error {
414 fn from(error: serde_json::Error) -> Self {
415 Self::new(ErrorKind::Json, error)
416 }
417}
418
419#[inline(always)]
420fn capture_backtrace() -> Option<Box<Backtrace>> {
421 let backtrace = Backtrace::capture();
422 if backtrace.status() == BacktraceStatus::Captured {
423 Some(Box::new(backtrace))
424 } else {
425 None
426 }
427}
428
429#[derive(Debug)]
441pub struct StopErrors(pub(crate) Vec<Error>);
442
443impl StopErrors {
444 pub fn errors(&self) -> &[Error] {
447 &self.0
448 }
449
450 pub fn into_errors(self) -> Vec<Error> {
452 self.0
453 }
454}
455
456impl fmt::Display for StopErrors {
457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458 match self.0.as_slice() {
459 [] => write!(f, "stop completed with no errors"),
460 [only] => write!(f, "stop failed: {only}"),
461 [first, rest @ ..] => write!(
462 f,
463 "stop failed with {n} errors; first: {first}",
464 n = 1 + rest.len(),
465 ),
466 }
467 }
468}
469
470impl std::error::Error for StopErrors {
471 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
472 self.0
473 .first()
474 .map(|e| e as &(dyn std::error::Error + 'static))
475 }
476}