1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, thiserror::Error)]
7pub enum MuxError {
8 #[error("Session creation failed: {0}")]
9 SessionCreationFailed(String),
10
11 #[error("Session not found: {0}")]
12 SessionNotFound(String),
13
14 #[error("Pane not found: {0}")]
15 PaneNotFound(String),
16
17 #[error("Command execution failed: {0}")]
18 CommandFailed(String),
19
20 #[error("Backend not available: {0}")]
21 BackendUnavailable(String),
22
23 #[error("Operation not supported by backend: {0}")]
24 NotSupported(String),
25
26 #[error("Invalid state: {0}")]
27 InvalidState(String),
28
29 #[error("Connection error: {0}")]
30 ConnectionError(String),
31
32 #[error("Parse error: {0}")]
33 ParseError(String),
34
35 #[error("IO error: {0}")]
36 IoError(#[from] std::io::Error),
37
38 #[error("Serialization error: {0}")]
39 SerializationError(#[from] serde_json::Error),
40
41 #[error("Other error: {0}")]
42 Other(String),
43}
44
45impl MuxError {
46 pub fn with_context<S: Into<String>>(self, context: S) -> Self {
48 match self {
49 MuxError::Other(msg) => MuxError::Other(format!("{}: {}", context.into(), msg)),
50 _ => self,
51 }
52 }
53
54 pub fn session_creation_failed<S: Into<String>>(msg: S) -> Self {
56 MuxError::SessionCreationFailed(msg.into())
57 }
58
59 pub fn session_not_found<S: Into<String>>(session_id: S) -> Self {
61 MuxError::SessionNotFound(session_id.into())
62 }
63
64 pub fn pane_not_found<S: Into<String>>(pane_id: S) -> Self {
66 MuxError::PaneNotFound(pane_id.into())
67 }
68
69 pub fn command_failed<S: Into<String>>(msg: S) -> Self {
71 MuxError::CommandFailed(msg.into())
72 }
73
74 pub fn backend_unavailable<S: Into<String>>(msg: S) -> Self {
76 MuxError::BackendUnavailable(msg.into())
77 }
78
79 pub fn not_supported<S: Into<String>>(operation: S) -> Self {
81 MuxError::NotSupported(operation.into())
82 }
83
84 pub fn invalid_state<S: Into<String>>(msg: S) -> Self {
86 MuxError::InvalidState(msg.into())
87 }
88
89 pub fn connection_error<S: Into<String>>(msg: S) -> Self {
91 MuxError::ConnectionError(msg.into())
92 }
93
94 pub fn parse_error<S: Into<String>>(msg: S) -> Self {
96 MuxError::ParseError(msg.into())
97 }
98
99 pub fn other<S: Into<String>>(msg: S) -> Self {
101 MuxError::Other(msg.into())
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Session {
108 pub id: String,
109 pub name: String,
110 pub created_at: DateTime<Utc>,
111 pub window_count: usize,
112 pub attached: bool,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct Pane {
118 pub id: String,
119 pub session_id: String,
120 pub index: u32,
121 pub title: String,
122 pub active: bool,
123 pub size: PaneSize,
124}
125
126#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
128pub struct PaneSize {
129 pub width: u32,
130 pub height: u32,
131}
132
133#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
135pub enum SplitType {
136 Horizontal,
137 Vertical,
138 None,
139}
140
141#[async_trait]
143pub trait MuxBackend: Send + Sync {
144 async fn create_session(&self, name: &str) -> Result<String, MuxError>;
146
147 async fn create_pane(&self, session_id: &str, split: SplitType) -> Result<String, MuxError>;
149
150 async fn send_keys(&self, pane_id: &str, keys: &str) -> Result<(), MuxError>;
152
153 async fn capture_pane(&self, pane_id: &str) -> Result<String, MuxError>;
155
156 async fn list_sessions(&self) -> Result<Vec<Session>, MuxError>;
158
159 async fn kill_session(&self, session_id: &str) -> Result<(), MuxError>;
161
162 async fn kill_pane(&self, pane_id: &str) -> Result<(), MuxError>;
164
165 async fn resize_pane(&self, pane_id: &str, size: PaneSize) -> Result<(), MuxError>;
167
168 async fn select_pane(&self, pane_id: &str) -> Result<(), MuxError>;
170
171 async fn list_panes(&self, session_id: &str) -> Result<Vec<Pane>, MuxError>;
173
174 async fn attach_session(&self, session_id: &str) -> Result<(), MuxError>;
176
177 async fn detach_session(&self, session_id: &str) -> Result<(), MuxError>;
179}
180
181#[async_trait]
183pub trait MuxBackendExt: MuxBackend {
184 async fn set_resource_limits(
186 &self,
187 _pane_id: &str,
188 _limits: ResourceLimits,
189 ) -> Result<(), MuxError> {
190 Err(MuxError::NotSupported(
191 "Resource limits not supported by this backend".to_string(),
192 ))
193 }
194
195 async fn subscribe_events(&self, _pane_id: &str) -> Result<EventStream, MuxError> {
197 Err(MuxError::NotSupported(
198 "Event subscription not supported by this backend".to_string(),
199 ))
200 }
201
202 async fn get_metrics(&self, _pane_id: &str) -> Result<PaneMetrics, MuxError> {
204 Err(MuxError::NotSupported(
205 "Metrics not supported by this backend".to_string(),
206 ))
207 }
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct ResourceLimits {
213 pub cpu_percent: Option<f32>,
214 pub memory_mb: Option<u64>,
215 pub io_bandwidth_mb: Option<u64>,
216}
217
218pub type EventStream = tokio::sync::mpsc::Receiver<PaneEvent>;
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub enum PaneEvent {
224 Output(String),
225 Resize(PaneSize),
226 Exit(i32),
227 Error(String),
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct PaneMetrics {
233 pub cpu_usage: f32,
234 pub memory_usage_mb: u64,
235 pub io_read_bytes: u64,
236 pub io_write_bytes: u64,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
241#[serde(tag = "type")]
242pub enum MuxUIEvent {
243 PaneOutput {
245 pane_id: String,
246 data: String,
247 timestamp: DateTime<Utc>,
248 },
249 PaneExit {
251 pane_id: String,
252 exit_code: Option<i32>,
253 timestamp: DateTime<Utc>,
254 },
255 SessionCreated {
257 session_id: String,
258 name: String,
259 timestamp: DateTime<Utc>,
260 },
261 PaneCreated {
263 pane_id: String,
264 session_id: String,
265 timestamp: DateTime<Utc>,
266 },
267 MuxError {
269 error: String,
270 context: Option<String>,
271 timestamp: DateTime<Utc>,
272 },
273}