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
157impl fmt::Display for SessionErrorKind {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match self {
160 SessionErrorKind::NotFound(id) => write!(f, "session not found: {id}"),
161 SessionErrorKind::AgentError => write!(f, "agent error"),
162 SessionErrorKind::Timeout(d) => write!(f, "timed out after {d:?}"),
163 SessionErrorKind::SendWhileWaiting => {
164 write!(f, "cannot send while send_and_wait is in flight")
165 }
166 SessionErrorKind::EventLoopClosed => {
167 write!(f, "event loop closed before session reached idle")
168 }
169 SessionErrorKind::ElicitationNotSupported => write!(
170 f,
171 "elicitation not supported by host \
172 \u{2014} check session.capabilities().ui.elicitation first"
173 ),
174 SessionErrorKind::SessionFsProviderRequired => write!(
175 f,
176 "session was created on a client with session_fs configured \
177 but no SessionFsProvider was supplied"
178 ),
179 SessionErrorKind::InvalidSessionFsConfig => {
180 write!(f, "invalid SessionFsConfig")
181 }
182 SessionErrorKind::SessionIdMismatch {
183 requested,
184 returned,
185 } => write!(
186 f,
187 "CLI returned session ID {returned} after SDK registered {requested}"
188 ),
189 }
190 }
191}
192
193#[derive(Clone, Debug, PartialEq, Eq)]
197#[non_exhaustive]
198pub enum ErrorKind {
199 Protocol(ProtocolErrorKind),
201 Rpc {
203 code: i32,
205 },
206 Session(SessionErrorKind),
208 Io,
210 Json,
212 BinaryNotFound {
214 name: String,
216 hint: Option<String>,
218 },
219 InvalidConfig,
221 GitHubTokenProvider,
223}
224
225impl fmt::Display for ErrorKind {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 match self {
228 ErrorKind::Protocol(k) => write!(f, "{k}"),
229 ErrorKind::Rpc { code } => write!(f, "RPC error {code}"),
230 ErrorKind::Session(k) => write!(f, "{k}"),
231 ErrorKind::Io => write!(f, "I/O error"),
232 ErrorKind::Json => write!(f, "JSON error"),
233 ErrorKind::BinaryNotFound {
234 name,
235 hint: Some(h),
236 } => {
237 write!(f, "binary not found: {name} ({h})")
238 }
239 ErrorKind::BinaryNotFound { name, hint: None } => {
240 write!(f, "binary not found: {name}")
241 }
242 ErrorKind::InvalidConfig => write!(f, "invalid configuration"),
243 ErrorKind::GitHubTokenProvider => write!(f, "GitHub token provider error"),
244 }
245 }
246}
247
248pub struct Error {
250 repr: Repr<ErrorKind>,
251 backtrace: Option<Box<Backtrace>>,
254}
255
256impl Error {
257 pub(crate) fn new<E>(kind: ErrorKind, error: E) -> Self
259 where
260 E: Into<Box<dyn std::error::Error + Send + Sync>>,
261 {
262 Self {
263 repr: Repr::Custom(Custom {
264 kind,
265 error: error.into(),
266 }),
267 backtrace: capture_backtrace(),
268 }
269 }
270
271 pub fn kind(&self) -> &ErrorKind {
273 match &self.repr {
274 Repr::Simple(kind)
275 | Repr::SimpleMessage(kind, ..)
276 | Repr::Custom(Custom { kind, .. }) => kind,
277 }
278 }
279
280 pub fn message(&self) -> Option<&str> {
282 match &self.repr {
283 Repr::SimpleMessage(_, message) => Some(message.borrow()),
284 _ => None,
285 }
286 }
287
288 #[must_use]
290 pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
291 where
292 C: Into<Cow<'static, str>>,
293 {
294 Self {
295 repr: Repr::SimpleMessage(kind, message.into()),
296 backtrace: capture_backtrace(),
297 }
298 }
299
300 pub fn is_transport_failure(&self) -> bool {
304 matches!(self.kind(), ErrorKind::Io)
305 || matches!(
306 self.kind(),
307 ErrorKind::Protocol(ProtocolErrorKind::RequestCancelled)
308 )
309 }
310
311 pub fn rpc_code(&self) -> Option<i32> {
313 match self.kind() {
314 ErrorKind::Rpc { code } => Some(*code),
315 _ => None,
316 }
317 }
318}
319
320impl fmt::Display for Error {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 match &self.repr {
323 Repr::Simple(kind) => write!(f, "{kind}"),
324 Repr::SimpleMessage(kind, message) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
325 write!(f, "{kind}: {message}")
326 }
327 Repr::SimpleMessage(_, message) => write!(f, "{message}"),
328 Repr::Custom(Custom { kind, error }) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
329 write!(f, "{kind}: {error}")
330 }
331 Repr::Custom(Custom { error, .. }) => write!(f, "{error}"),
332 }
333 }
334}
335
336impl fmt::Debug for Error {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 let mut dbg = f.debug_struct("Error");
339 dbg.field("context", &self.repr);
340 if let Some(backtrace) = &self.backtrace {
341 return dbg.field("backtrace", backtrace).finish();
342 }
343 dbg.finish_non_exhaustive()
344 }
345}
346
347impl std::error::Error for Error {
348 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
349 match &self.repr {
350 Repr::Custom(Custom { error, .. }) => Some(&**error),
351 _ => None,
352 }
353 }
354}
355
356impl From<ErrorKind> for Error {
357 fn from(kind: ErrorKind) -> Self {
358 Self {
359 repr: Repr::Simple(kind),
360 backtrace: capture_backtrace(),
361 }
362 }
363}
364
365impl From<ProtocolErrorKind> for Error {
366 fn from(kind: ProtocolErrorKind) -> Self {
367 Self::from(ErrorKind::Protocol(kind))
368 }
369}
370
371impl From<SessionErrorKind> for Error {
372 fn from(kind: SessionErrorKind) -> Self {
373 Self::from(ErrorKind::Session(kind))
374 }
375}
376
377impl From<std::io::Error> for Error {
378 fn from(error: std::io::Error) -> Self {
379 Self::new(ErrorKind::Io, error)
380 }
381}
382
383impl From<serde_json::Error> for Error {
384 fn from(error: serde_json::Error) -> Self {
385 Self::new(ErrorKind::Json, error)
386 }
387}
388
389#[inline(always)]
390fn capture_backtrace() -> Option<Box<Backtrace>> {
391 let backtrace = Backtrace::capture();
392 if backtrace.status() == BacktraceStatus::Captured {
393 Some(Box::new(backtrace))
394 } else {
395 None
396 }
397}
398
399#[derive(Debug)]
411pub struct StopErrors(pub(crate) Vec<Error>);
412
413impl StopErrors {
414 pub fn errors(&self) -> &[Error] {
417 &self.0
418 }
419
420 pub fn into_errors(self) -> Vec<Error> {
422 self.0
423 }
424}
425
426impl fmt::Display for StopErrors {
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 match self.0.as_slice() {
429 [] => write!(f, "stop completed with no errors"),
430 [only] => write!(f, "stop failed: {only}"),
431 [first, rest @ ..] => write!(
432 f,
433 "stop failed with {n} errors; first: {first}",
434 n = 1 + rest.len(),
435 ),
436 }
437 }
438}
439
440impl std::error::Error for StopErrors {
441 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
442 self.0
443 .first()
444 .map(|e| e as &(dyn std::error::Error + 'static))
445 }
446}