1use std::time::Duration;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
11pub enum CopilotError {
12 #[error("Transport error: {0}")]
14 Transport(#[from] std::io::Error),
15
16 #[error("Connection closed")]
18 ConnectionClosed,
19
20 #[error("Not connected")]
22 NotConnected,
23
24 #[error("JSON-RPC error {code}: {message}")]
26 JsonRpc {
27 code: i32,
28 message: String,
29 data: Option<serde_json::Value>,
30 },
31
32 #[error("Protocol version mismatch: expected {expected}, got {actual}")]
34 ProtocolMismatch { expected: u32, actual: u32 },
35
36 #[error("Protocol error: {0}")]
38 Protocol(String),
39
40 #[error("JSON error: {0}")]
42 Json(#[from] serde_json::Error),
43
44 #[error("Request timed out after {0:?}")]
46 Timeout(Duration),
47
48 #[error("Session not found: {0}")]
50 SessionNotFound(String),
51
52 #[error("Session already destroyed")]
54 SessionDestroyed,
55
56 #[error("Invalid configuration: {0}")]
58 InvalidConfig(String),
59
60 #[error("Failed to start CLI: {0}")]
62 ProcessStart(std::io::Error),
63
64 #[error("CLI exited unexpectedly with code {0:?}")]
66 ProcessExit(Option<i32>),
67
68 #[error("Failed to detect CLI server port")]
70 PortDetectionFailed,
71
72 #[error("Client is shutting down")]
74 Shutdown,
75
76 #[error("Tool not found: {0}")]
78 ToolNotFound(String),
79
80 #[error("Tool execution error: {0}")]
82 ToolError(String),
83
84 #[error("Permission denied: {0}")]
86 PermissionDenied(String),
87
88 #[error("Internal channel error")]
90 ChannelError,
91}
92
93pub type Result<T> = std::result::Result<T, CopilotError>;
95
96impl CopilotError {
97 pub fn json_rpc(
99 code: i32,
100 message: impl Into<String>,
101 data: Option<serde_json::Value>,
102 ) -> Self {
103 Self::JsonRpc {
104 code,
105 message: message.into(),
106 data,
107 }
108 }
109
110 pub fn invalid_config(msg: impl Into<String>) -> Self {
112 Self::InvalidConfig(msg.into())
113 }
114
115 pub fn is_fatal(&self) -> bool {
117 matches!(
118 self,
119 CopilotError::ConnectionClosed
120 | CopilotError::ProcessExit(_)
121 | CopilotError::Shutdown
122 | CopilotError::ProtocolMismatch { .. }
123 )
124 }
125}
126
127impl From<tokio::sync::oneshot::error::RecvError> for CopilotError {
129 fn from(_: tokio::sync::oneshot::error::RecvError) -> Self {
130 CopilotError::ConnectionClosed
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_error_display() {
140 let err = CopilotError::ProtocolMismatch {
141 expected: 1,
142 actual: 2,
143 };
144 assert_eq!(
145 err.to_string(),
146 "Protocol version mismatch: expected 1, got 2"
147 );
148 }
149
150 #[test]
151 fn test_is_fatal() {
152 assert!(CopilotError::ConnectionClosed.is_fatal());
153 assert!(CopilotError::Shutdown.is_fatal());
154 assert!(!CopilotError::Timeout(Duration::from_secs(30)).is_fatal());
155 }
156}