Skip to main content

copilot_sdk/
error.rs

1// Copyright (c) 2026 Elias Bachaalany
2// SPDX-License-Identifier: MIT
3
4//! Error types for the Copilot SDK.
5
6use std::time::Duration;
7use thiserror::Error;
8
9/// Main error type for the Copilot SDK.
10#[derive(Debug, Error)]
11pub enum CopilotError {
12    /// Transport/IO error
13    #[error("Transport error: {0}")]
14    Transport(#[from] std::io::Error),
15
16    /// Connection was closed unexpectedly
17    #[error("Connection closed")]
18    ConnectionClosed,
19
20    /// Client is not connected
21    #[error("Not connected")]
22    NotConnected,
23
24    /// JSON-RPC error from server
25    #[error("JSON-RPC error {code}: {message}")]
26    JsonRpc {
27        code: i32,
28        message: String,
29        data: Option<serde_json::Value>,
30    },
31
32    /// Protocol version mismatch
33    #[error("Protocol version mismatch: expected {expected}, got {actual}")]
34    ProtocolMismatch { expected: u32, actual: u32 },
35
36    /// Protocol error (framing, invalid messages, etc.)
37    #[error("Protocol error: {0}")]
38    Protocol(String),
39
40    /// JSON serialization/deserialization error
41    #[error("JSON error: {0}")]
42    Json(#[from] serde_json::Error),
43
44    /// Request timed out
45    #[error("Request timed out after {0:?}")]
46    Timeout(Duration),
47
48    /// Session not found
49    #[error("Session not found: {0}")]
50    SessionNotFound(String),
51
52    /// Session was already destroyed
53    #[error("Session already destroyed")]
54    SessionDestroyed,
55
56    /// Invalid configuration
57    #[error("Invalid configuration: {0}")]
58    InvalidConfig(String),
59
60    /// Failed to start CLI process
61    #[error("Failed to start CLI: {0}")]
62    ProcessStart(std::io::Error),
63
64    /// CLI process exited unexpectedly
65    #[error("CLI exited unexpectedly with code {0:?}")]
66    ProcessExit(Option<i32>),
67
68    /// Port detection failed in TCP mode
69    #[error("Failed to detect CLI server port")]
70    PortDetectionFailed,
71
72    /// Client is shutting down
73    #[error("Client is shutting down")]
74    Shutdown,
75
76    /// Tool not found
77    #[error("Tool not found: {0}")]
78    ToolNotFound(String),
79
80    /// Tool execution error
81    #[error("Tool execution error: {0}")]
82    ToolError(String),
83
84    /// Permission denied
85    #[error("Permission denied: {0}")]
86    PermissionDenied(String),
87
88    /// Channel send error (internal)
89    #[error("Internal channel error")]
90    ChannelError,
91}
92
93/// Result type alias for Copilot SDK operations.
94pub type Result<T> = std::result::Result<T, CopilotError>;
95
96impl CopilotError {
97    /// Create a JSON-RPC error from components.
98    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    /// Create an invalid config error.
111    pub fn invalid_config(msg: impl Into<String>) -> Self {
112        Self::InvalidConfig(msg.into())
113    }
114
115    /// Returns true if this error indicates the connection is no longer usable.
116    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
127// Convert oneshot channel errors to our error type
128impl 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}