agent_tui/ipc/
params.rs

1//! Shared RPC parameter types for CLI and daemon.
2//!
3//! These types define the contract between CLI and daemon. Both sides use the same
4//! types, ensuring field names stay in sync and eliminating magic string mismatches.
5//!
6//! # Design
7//!
8//! - CLI serializes these types to JSON for RPC requests
9//! - Daemon deserializes JSON to these types, then converts to domain types
10//! - Field names are the single source of truth (enforced at compile time)
11
12use serde::{Deserialize, Serialize};
13
14/// Parameters for the `spawn` RPC method.
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16pub struct SpawnParams {
17    #[serde(default)]
18    pub command: String,
19    #[serde(default, skip_serializing_if = "Vec::is_empty")]
20    pub args: Vec<String>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub cwd: Option<String>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub session: Option<String>,
25    #[serde(default = "default_cols")]
26    pub cols: u16,
27    #[serde(default = "default_rows")]
28    pub rows: u16,
29}
30
31fn default_cols() -> u16 {
32    80
33}
34fn default_rows() -> u16 {
35    24
36}
37
38/// Parameters for the `snapshot` RPC method.
39#[derive(Debug, Clone, Default, Serialize, Deserialize)]
40pub struct SnapshotParams {
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub session: Option<String>,
43    #[serde(default)]
44    pub include_elements: bool,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub region: Option<String>,
47    #[serde(default)]
48    pub strip_ansi: bool,
49    #[serde(default)]
50    pub include_cursor: bool,
51}
52
53/// Parameters for the `accessibility_snapshot` RPC method.
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
55pub struct AccessibilitySnapshotParams {
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub session: Option<String>,
58    #[serde(default)]
59    pub interactive: bool,
60}
61
62/// Parameters for element reference actions (click, focus, clear, etc.).
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ElementRefParams {
65    #[serde(rename = "ref")]
66    pub element_ref: String,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub session: Option<String>,
69}
70
71/// Parameters for the `fill` RPC method.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct FillParams {
74    #[serde(rename = "ref")]
75    pub element_ref: String,
76    pub value: String,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub session: Option<String>,
79}
80
81/// Parameters for key-related RPC methods (keystroke, keydown, keyup).
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct KeyParams {
84    pub key: String,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub session: Option<String>,
87}
88
89/// Parameters for the `type` RPC method.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct TypeParams {
92    pub text: String,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub session: Option<String>,
95}
96
97/// Parameters for the `wait` RPC method.
98#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct WaitParams {
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub session: Option<String>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub text: Option<String>,
104    #[serde(default = "default_timeout_ms")]
105    pub timeout_ms: u64,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub condition: Option<String>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub target: Option<String>,
110}
111
112fn default_timeout_ms() -> u64 {
113    30000
114}
115
116/// Parameters for the `find` RPC method.
117#[derive(Debug, Clone, Default, Serialize, Deserialize)]
118pub struct FindParams {
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub session: Option<String>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub role: Option<String>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub name: Option<String>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub text: Option<String>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub placeholder: Option<String>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub focused: Option<bool>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub nth: Option<usize>,
133    #[serde(default)]
134    pub exact: bool,
135}
136
137/// Parameters for the `resize` RPC method.
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ResizeParams {
140    pub cols: u16,
141    pub rows: u16,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub session: Option<String>,
144}
145
146/// Parameters for session-only RPC methods (kill, restart, sessions, etc.).
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148pub struct SessionParams {
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub session: Option<String>,
151}
152
153/// Parameters for the `select` RPC method.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct SelectParams {
156    #[serde(rename = "ref")]
157    pub element_ref: String,
158    pub option: String,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub session: Option<String>,
161}
162
163/// Parameters for the `multiselect` RPC method.
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct MultiselectParams {
166    #[serde(rename = "ref")]
167    pub element_ref: String,
168    pub options: Vec<String>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub session: Option<String>,
171}
172
173/// Parameters for the `scroll` RPC method.
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct ScrollParams {
176    pub direction: String,
177    #[serde(default = "default_scroll_amount")]
178    pub amount: u16,
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub session: Option<String>,
181}
182
183fn default_scroll_amount() -> u16 {
184    1
185}
186
187/// Parameters for the `count` RPC method.
188#[derive(Debug, Clone, Default, Serialize, Deserialize)]
189pub struct CountParams {
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub session: Option<String>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub role: Option<String>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub name: Option<String>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub text: Option<String>,
198}
199
200/// Parameters for the `toggle` RPC method.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct ToggleParams {
203    #[serde(rename = "ref")]
204    pub element_ref: String,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub state: Option<bool>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub session: Option<String>,
209}
210
211/// Parameters for the `record_stop` RPC method.
212#[derive(Debug, Clone, Default, Serialize, Deserialize)]
213pub struct RecordStopParams {
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub session: Option<String>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub format: Option<String>,
218}
219
220/// Parameters for the `trace` RPC method.
221#[derive(Debug, Clone, Default, Serialize, Deserialize)]
222pub struct TraceParams {
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub session: Option<String>,
225    #[serde(default)]
226    pub start: bool,
227    #[serde(default)]
228    pub stop: bool,
229    #[serde(default = "default_trace_count")]
230    pub count: usize,
231}
232
233fn default_trace_count() -> usize {
234    100
235}
236
237/// Parameters for the `console` RPC method.
238#[derive(Debug, Clone, Default, Serialize, Deserialize)]
239pub struct ConsoleParams {
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub session: Option<String>,
242    #[serde(default = "default_console_count")]
243    pub count: usize,
244    #[serde(default)]
245    pub clear: bool,
246}
247
248fn default_console_count() -> usize {
249    50
250}
251
252/// Parameters for the `errors` RPC method.
253#[derive(Debug, Clone, Default, Serialize, Deserialize)]
254pub struct ErrorsParams {
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub session: Option<String>,
257    #[serde(default = "default_errors_count")]
258    pub count: usize,
259    #[serde(default)]
260    pub clear: bool,
261}
262
263fn default_errors_count() -> usize {
264    10
265}
266
267/// Parameters for PTY read operations.
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct PtyReadParams {
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub session: Option<String>,
272    #[serde(default = "default_max_bytes")]
273    pub max_bytes: usize,
274}
275
276fn default_max_bytes() -> usize {
277    4096
278}
279
280/// Parameters for PTY write operations.
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct PtyWriteParams {
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub session: Option<String>,
285    pub data: String,
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn test_snapshot_params_serialization() {
294        let params = SnapshotParams {
295            session: Some("test-session".to_string()),
296            include_elements: true,
297            include_cursor: true,
298            ..Default::default()
299        };
300
301        let json = serde_json::to_value(&params).unwrap();
302        assert_eq!(json["session"], "test-session");
303        assert_eq!(json["include_elements"], true);
304        assert_eq!(json["include_cursor"], true);
305    }
306
307    #[test]
308    fn test_snapshot_params_deserialization() {
309        let json = serde_json::json!({
310            "session": "abc123",
311            "include_elements": true,
312            "include_cursor": true
313        });
314
315        let params: SnapshotParams = serde_json::from_value(json).unwrap();
316        assert_eq!(params.session, Some("abc123".to_string()));
317        assert!(params.include_elements);
318        assert!(params.include_cursor);
319    }
320
321    #[test]
322    fn test_snapshot_params_defaults() {
323        let json = serde_json::json!({});
324        let params: SnapshotParams = serde_json::from_value(json).unwrap();
325
326        assert_eq!(params.session, None);
327        assert!(!params.include_elements);
328        assert!(!params.include_cursor);
329        assert!(!params.strip_ansi);
330        assert_eq!(params.region, None);
331    }
332
333    #[test]
334    fn test_element_ref_params_rename() {
335        let params = ElementRefParams {
336            element_ref: "@btn1".to_string(),
337            session: None,
338        };
339
340        let json = serde_json::to_value(&params).unwrap();
341        // Should serialize as "ref", not "element_ref"
342        assert_eq!(json["ref"], "@btn1");
343        assert!(json.get("element_ref").is_none());
344    }
345
346    #[test]
347    fn test_element_ref_params_deserialization() {
348        let json = serde_json::json!({
349            "ref": "@inp1",
350            "session": "sess-1"
351        });
352
353        let params: ElementRefParams = serde_json::from_value(json).unwrap();
354        assert_eq!(params.element_ref, "@inp1");
355        assert_eq!(params.session, Some("sess-1".to_string()));
356    }
357
358    #[test]
359    fn test_spawn_params_defaults() {
360        let json = serde_json::json!({});
361        let params: SpawnParams = serde_json::from_value(json).unwrap();
362
363        assert_eq!(params.command, "");
364        assert!(params.args.is_empty());
365        assert_eq!(params.cols, 80);
366        assert_eq!(params.rows, 24);
367    }
368
369    #[test]
370    fn test_wait_params_defaults() {
371        let json = serde_json::json!({});
372        let params: WaitParams = serde_json::from_value(json).unwrap();
373
374        assert_eq!(params.timeout_ms, 30000);
375        assert_eq!(params.text, None);
376        assert_eq!(params.condition, None);
377    }
378}