Skip to main content

aimux_protocol/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// User-chosen session name (e.g. "work", "agents").
4pub type SessionId = String;
5
6/// Zero-based window index within a session.
7pub type WindowId = u32;
8
9/// Globally unique pane identifier in "%N" format (e.g. "%0", "%7").
10pub type PaneId = String;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SessionInfo {
14    pub name: SessionId,
15    pub windows: Vec<WindowInfo>,
16    pub created_at: u64, // Unix timestamp
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct WindowInfo {
21    pub id: WindowId,
22    pub panes: Vec<PaneInfo>,
23    pub active_pane: PaneId,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct PaneInfo {
28    pub id: PaneId,
29    pub pid: u32,
30    pub running: bool,
31    pub exit_code: Option<i32>,
32    pub size: (u16, u16), // (cols, rows)
33    pub title: String,
34    #[serde(default)]
35    pub marks: Vec<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct MarkInfo {
40    pub mark: String,
41    pub pane_id: PaneId,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct CursorPos {
46    pub row: u16,
47    pub col: u16,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ProcessInfo {
52    pub pid: u32,
53    pub running: bool,
54    pub exit_code: Option<i32>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub enum ScrollbackRange {
59    Last(usize),
60    All,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct CaptureResult {
65    pub pane_id: PaneId,
66    pub lines: Vec<String>,
67    pub scrollback_lines: Vec<String>,
68    pub cursor: CursorPos,
69    pub size: (u16, u16), // (cols, rows)
70    pub process: ProcessInfo,
71    pub title: String,
72}
73
74/// Wire-format cell for screen snapshots.
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
76pub struct ScreenCell {
77    pub ch: char,
78    pub attrs: ScreenCellAttrs,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
82pub struct ScreenCellAttrs {
83    pub bold: bool,
84    pub dim: bool,
85    pub italic: bool,
86    pub underline: bool,
87    pub fg: ScreenColor,
88    pub bg: ScreenColor,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
92pub enum ScreenColor {
93    #[default]
94    Default,
95    Indexed(u8),
96    Rgb(u8, u8, u8),
97}
98
99/// Position and size of a pane in the terminal grid.
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
101pub struct PaneLayout {
102    pub pane_id: PaneId,
103    pub x: u16,
104    pub y: u16,
105    pub width: u16,
106    pub height: u16,
107    pub is_active: bool,
108    pub is_zoomed: bool,
109}
110
111/// Summary info for a window, used in status bar rendering.
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
113pub struct WindowBarInfo {
114    pub id: WindowId,
115    pub title: String,
116    pub is_active: bool,
117}
118
119/// Full layout description for the current window.
120#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
121pub struct LayoutInfo {
122    pub session: SessionId,
123    pub window_id: WindowId,
124    pub panes: Vec<PaneLayout>,
125    pub terminal_cols: u16,
126    pub terminal_rows: u16,
127    pub windows: Vec<WindowBarInfo>,
128}
129
130/// Events streamed to subscribers via NDJSON.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132#[serde(tag = "event")]
133pub enum PaneEvent {
134    #[serde(rename = "output")]
135    Output { pane: PaneId, data: String },
136    #[serde(rename = "exit")]
137    Exit { pane: PaneId, code: i32 },
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ServerStatusInfo {
142    pub version: String,
143    pub uptime_secs: u64,
144    pub sessions: u32,
145    pub windows: u32,
146    pub panes: u32,
147    pub pid: u32,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct LayoutListEntry {
152    pub name: String,
153    pub source: LayoutSource,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub enum CaptureFilter {
158    LastCommand { strip_prompt: bool },
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
162pub enum LayoutSource {
163    Config,
164    Saved,
165    Preset,
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn pane_event_output_json_format() {
174        let event = PaneEvent::Output {
175            pane: "%0".into(),
176            data: "hello world".into(),
177        };
178        let json = serde_json::to_string(&event).unwrap();
179        assert!(json.contains("\"event\":\"output\""));
180        assert!(json.contains("\"pane\":\"%0\""));
181        assert!(json.contains("\"data\":\"hello world\""));
182
183        // Roundtrip
184        let parsed: PaneEvent = serde_json::from_str(&json).unwrap();
185        match parsed {
186            PaneEvent::Output { pane, data } => {
187                assert_eq!(pane, "%0");
188                assert_eq!(data, "hello world");
189            }
190            _ => panic!("wrong variant"),
191        }
192    }
193
194    #[test]
195    fn pane_event_exit_json_format() {
196        let event = PaneEvent::Exit {
197            pane: "%1".into(),
198            code: 0,
199        };
200        let json = serde_json::to_string(&event).unwrap();
201        assert!(json.contains("\"event\":\"exit\""));
202        assert!(json.contains("\"pane\":\"%1\""));
203        assert!(json.contains("\"code\":0"));
204
205        let parsed: PaneEvent = serde_json::from_str(&json).unwrap();
206        match parsed {
207            PaneEvent::Exit { pane, code } => {
208                assert_eq!(pane, "%1");
209                assert_eq!(code, 0);
210            }
211            _ => panic!("wrong variant"),
212        }
213    }
214
215    #[test]
216    fn pane_event_msgpack_roundtrip() {
217        let events = vec![
218            PaneEvent::Output {
219                pane: "%0".into(),
220                data: "test data".into(),
221            },
222            PaneEvent::Exit {
223                pane: "%2".into(),
224                code: 1,
225            },
226        ];
227        for event in events {
228            let bytes = rmp_serde::to_vec(&event).unwrap();
229            let decoded: PaneEvent = rmp_serde::from_slice(&bytes).unwrap();
230            assert_eq!(format!("{:?}", event), format!("{:?}", decoded));
231        }
232    }
233}