orchflow_mux/
backend.rs

1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5/// Error types for multiplexer operations
6#[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    /// Helper to create errors with context
47    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    /// Create a session creation failed error
55    pub fn session_creation_failed<S: Into<String>>(msg: S) -> Self {
56        MuxError::SessionCreationFailed(msg.into())
57    }
58
59    /// Create a session not found error
60    pub fn session_not_found<S: Into<String>>(session_id: S) -> Self {
61        MuxError::SessionNotFound(session_id.into())
62    }
63
64    /// Create a pane not found error
65    pub fn pane_not_found<S: Into<String>>(pane_id: S) -> Self {
66        MuxError::PaneNotFound(pane_id.into())
67    }
68
69    /// Create a command failed error
70    pub fn command_failed<S: Into<String>>(msg: S) -> Self {
71        MuxError::CommandFailed(msg.into())
72    }
73
74    /// Create a backend unavailable error
75    pub fn backend_unavailable<S: Into<String>>(msg: S) -> Self {
76        MuxError::BackendUnavailable(msg.into())
77    }
78
79    /// Create a not supported error
80    pub fn not_supported<S: Into<String>>(operation: S) -> Self {
81        MuxError::NotSupported(operation.into())
82    }
83
84    /// Create an invalid state error
85    pub fn invalid_state<S: Into<String>>(msg: S) -> Self {
86        MuxError::InvalidState(msg.into())
87    }
88
89    /// Create a connection error
90    pub fn connection_error<S: Into<String>>(msg: S) -> Self {
91        MuxError::ConnectionError(msg.into())
92    }
93
94    /// Create a parse error
95    pub fn parse_error<S: Into<String>>(msg: S) -> Self {
96        MuxError::ParseError(msg.into())
97    }
98
99    /// Create an other error
100    pub fn other<S: Into<String>>(msg: S) -> Self {
101        MuxError::Other(msg.into())
102    }
103}
104
105/// Represents a terminal multiplexer session
106#[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/// Represents a pane within a session
116#[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/// Pane dimensions
127#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
128pub struct PaneSize {
129    pub width: u32,
130    pub height: u32,
131}
132
133/// How to split a pane
134#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
135pub enum SplitType {
136    Horizontal,
137    Vertical,
138    None,
139}
140
141/// The core trait that all multiplexer backends must implement
142#[async_trait]
143pub trait MuxBackend: Send + Sync {
144    /// Create a new session with the given name
145    async fn create_session(&self, name: &str) -> Result<String, MuxError>;
146
147    /// Create a new pane in the specified session
148    async fn create_pane(&self, session_id: &str, split: SplitType) -> Result<String, MuxError>;
149
150    /// Send keystrokes to a specific pane
151    async fn send_keys(&self, pane_id: &str, keys: &str) -> Result<(), MuxError>;
152
153    /// Capture the current contents of a pane
154    async fn capture_pane(&self, pane_id: &str) -> Result<String, MuxError>;
155
156    /// List all sessions
157    async fn list_sessions(&self) -> Result<Vec<Session>, MuxError>;
158
159    /// Kill a session and all its panes
160    async fn kill_session(&self, session_id: &str) -> Result<(), MuxError>;
161
162    /// Kill a specific pane
163    async fn kill_pane(&self, pane_id: &str) -> Result<(), MuxError>;
164
165    /// Resize a pane
166    async fn resize_pane(&self, pane_id: &str, size: PaneSize) -> Result<(), MuxError>;
167
168    /// Select (focus) a pane
169    async fn select_pane(&self, pane_id: &str) -> Result<(), MuxError>;
170
171    /// List all panes in a session
172    async fn list_panes(&self, session_id: &str) -> Result<Vec<Pane>, MuxError>;
173
174    /// Attach to a session
175    async fn attach_session(&self, session_id: &str) -> Result<(), MuxError>;
176
177    /// Detach from a session
178    async fn detach_session(&self, session_id: &str) -> Result<(), MuxError>;
179}
180
181/// Optional extended capabilities that some backends may support
182#[async_trait]
183pub trait MuxBackendExt: MuxBackend {
184    /// Set resource limits for a pane (muxd only)
185    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    /// Subscribe to pane events (muxd only)
196    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    /// Get performance metrics for a pane (muxd only)
203    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/// Resource limits for a pane (muxd feature)
211#[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
218/// Event stream for real-time pane updates (muxd feature)
219pub type EventStream = tokio::sync::mpsc::Receiver<PaneEvent>;
220
221/// Events that can occur in a pane
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub enum PaneEvent {
224    Output(String),
225    Resize(PaneSize),
226    Exit(i32),
227    Error(String),
228}
229
230/// Performance metrics for a pane (muxd feature)
231#[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/// Events emitted to the UI for mux operations
240#[derive(Debug, Clone, Serialize, Deserialize)]
241#[serde(tag = "type")]
242pub enum MuxUIEvent {
243    /// Pane produced output
244    PaneOutput {
245        pane_id: String,
246        data: String,
247        timestamp: DateTime<Utc>,
248    },
249    /// Pane process exited
250    PaneExit {
251        pane_id: String,
252        exit_code: Option<i32>,
253        timestamp: DateTime<Utc>,
254    },
255    /// Session created
256    SessionCreated {
257        session_id: String,
258        name: String,
259        timestamp: DateTime<Utc>,
260    },
261    /// Pane created
262    PaneCreated {
263        pane_id: String,
264        session_id: String,
265        timestamp: DateTime<Utc>,
266    },
267    /// Error occurred
268    MuxError {
269        error: String,
270        context: Option<String>,
271        timestamp: DateTime<Utc>,
272    },
273}