Skip to main content

tauri_agent_plugin/
models.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4#[derive(Clone, Debug, Default, Deserialize, Serialize)]
5#[serde(rename_all = "camelCase")]
6pub struct Config {
7    /// Allows the inline debugger server to bind a local socket in release builds.
8    #[serde(default)]
9    pub allow_release_socket: bool,
10    /// Allows the inline debugger server to bind a non-loopback host. Off by
11    /// default: the debugger is unauthenticated code-exec surface and must stay
12    /// local unless explicitly opted in.
13    #[serde(default)]
14    pub allow_non_loopback: bool,
15    #[serde(default)]
16    pub inline_server: InlineServerConfig,
17    /// Advertise a human-facing VNC/noVNC visual surface in the endpoint
18    /// registry. The plugin only publishes the location; the surrounding
19    /// harness runs the actual VNC server. Requires the inline server so the
20    /// endpoint registry is published.
21    #[serde(default)]
22    pub vnc: Option<crate::endpoint::VncEndpoint>,
23}
24
25#[derive(Clone, Debug, Deserialize, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub struct InlineServerConfig {
28    #[serde(default)]
29    pub enabled: bool,
30    #[serde(default = "default_inline_server_host")]
31    pub host: String,
32    #[serde(default)]
33    pub port: u16,
34    #[serde(default = "default_publish_endpoint")]
35    pub publish_endpoint: bool,
36}
37
38impl Default for InlineServerConfig {
39    fn default() -> Self {
40        Self {
41            enabled: false,
42            host: default_inline_server_host(),
43            port: 0,
44            publish_endpoint: default_publish_endpoint(),
45        }
46    }
47}
48
49fn default_inline_server_host() -> String {
50    "127.0.0.1".into()
51}
52
53fn default_publish_endpoint() -> bool {
54    true
55}
56
57#[derive(Clone, Debug, Default, Deserialize, Serialize)]
58#[serde(rename_all = "camelCase")]
59pub struct AgentAttachRequest {
60    pub app: Option<String>,
61    pub window: Option<String>,
62}
63
64#[derive(Clone, Debug, Deserialize, Serialize)]
65#[serde(rename_all = "camelCase")]
66pub struct AgentAttachResponse {
67    pub attached: bool,
68    pub windows: Vec<WindowInfo>,
69}
70
71#[derive(Clone, Debug, Default, Deserialize, Serialize)]
72#[serde(rename_all = "camelCase")]
73pub struct AgentWindowRequest {
74    pub window: Option<String>,
75    pub action: Option<WindowAction>,
76    pub x: Option<i32>,
77    pub y: Option<i32>,
78    pub width: Option<u32>,
79    pub height: Option<u32>,
80}
81
82#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub enum WindowAction {
85    Get,
86    Focus,
87    Show,
88    Hide,
89    Minimize,
90    Unminimize,
91    Maximize,
92    Unmaximize,
93    SetSize,
94    SetPosition,
95}
96
97#[derive(Clone, Debug, Default, Deserialize, Serialize)]
98#[serde(rename_all = "camelCase")]
99pub struct AgentSnapshotRequest {
100    pub window: Option<String>,
101    pub scope: Option<String>,
102    pub mode: Option<SnapshotMode>,
103}
104
105#[derive(Clone, Debug, Default, Deserialize, Serialize)]
106#[serde(rename_all = "camelCase")]
107pub enum SnapshotMode {
108    #[default]
109    Compact,
110    Verbose,
111}
112
113#[derive(Clone, Debug, Deserialize, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct AgentActionRequest {
116    pub window: Option<String>,
117    #[serde(rename = "ref")]
118    pub ref_id: Option<String>,
119    pub action: AgentAction,
120    pub value: Option<String>,
121    pub modifiers: Option<Vec<KeyModifier>>,
122}
123
124#[derive(Clone, Debug, Default, Deserialize, Serialize)]
125#[serde(rename_all = "camelCase")]
126pub struct AgentFindRequest {
127    pub window: Option<String>,
128    pub scope: Option<String>,
129    pub role: Option<String>,
130    pub name: Option<String>,
131    pub text: Option<String>,
132    pub limit: Option<u64>,
133}
134
135#[derive(Clone, Debug, Deserialize, Serialize)]
136#[serde(rename_all = "camelCase")]
137pub struct AgentInspectRequest {
138    pub window: Option<String>,
139    #[serde(rename = "ref")]
140    pub ref_id: String,
141}
142
143#[derive(Clone, Debug, Deserialize, Serialize)]
144#[serde(rename_all = "camelCase")]
145pub struct AgentInspectResponse {
146    #[serde(rename = "ref")]
147    pub ref_id: String,
148    pub role: String,
149    pub name: String,
150    pub tag_name: String,
151    pub text: String,
152    pub value: Option<String>,
153    pub attributes: BTreeMap<String, String>,
154    pub states: Vec<String>,
155}
156
157#[derive(Clone, Debug, Deserialize, Serialize)]
158#[serde(rename_all = "camelCase")]
159pub struct AgentFindResponse {
160    pub matches: Vec<AgentInspectResponse>,
161}
162
163#[derive(Clone, Debug, Deserialize, Serialize)]
164#[serde(rename_all = "camelCase")]
165pub struct AgentEvalRequest {
166    pub window: Option<String>,
167    pub code: String,
168}
169
170#[derive(Clone, Debug, Deserialize, Serialize)]
171#[serde(rename_all = "camelCase")]
172pub struct AgentSelectRequest {
173    pub window: Option<String>,
174    #[serde(rename = "ref")]
175    pub ref_id: String,
176    pub value: Option<String>,
177}
178
179#[derive(Clone, Debug, Deserialize, Serialize)]
180#[serde(rename_all = "camelCase")]
181pub struct AgentTypeRequest {
182    pub window: Option<String>,
183    #[serde(rename = "ref")]
184    pub ref_id: String,
185    pub text: String,
186}
187
188#[derive(Clone, Debug, Deserialize, Serialize)]
189#[serde(rename_all = "camelCase")]
190pub struct AgentCheckRequest {
191    pub window: Option<String>,
192    #[serde(rename = "ref")]
193    pub ref_id: String,
194    pub checked: Option<bool>,
195}
196
197#[derive(Clone, Debug, Deserialize, Serialize)]
198#[serde(rename_all = "camelCase")]
199pub struct AgentUploadFile {
200    pub name: String,
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub r#type: Option<String>,
203    #[serde(default, skip_serializing_if = "Option::is_none")]
204    pub text: Option<String>,
205}
206
207#[derive(Clone, Debug, Deserialize, Serialize)]
208#[serde(rename_all = "camelCase")]
209pub struct AgentUploadRequest {
210    pub window: Option<String>,
211    #[serde(rename = "ref")]
212    pub ref_id: String,
213    pub files: Vec<AgentUploadFile>,
214}
215
216#[derive(Clone, Debug, Deserialize, Serialize)]
217#[serde(rename_all = "camelCase")]
218pub struct AgentDialogRequest {
219    pub window: Option<String>,
220    #[serde(default, skip_serializing_if = "Option::is_none")]
221    pub action: Option<String>,
222    #[serde(default, skip_serializing_if = "Option::is_none")]
223    pub accept: Option<bool>,
224    #[serde(default, skip_serializing_if = "Option::is_none")]
225    pub prompt_text: Option<String>,
226}
227
228#[derive(Clone, Debug, Deserialize, Serialize)]
229#[serde(rename_all = "camelCase")]
230pub struct AgentHoverRequest {
231    pub window: Option<String>,
232    #[serde(rename = "ref")]
233    pub ref_id: String,
234}
235
236#[derive(Clone, Debug, Deserialize, Serialize)]
237#[serde(rename_all = "camelCase")]
238pub struct AgentFocusRequest {
239    pub window: Option<String>,
240    #[serde(rename = "ref")]
241    pub ref_id: String,
242}
243
244#[derive(Clone, Debug, Deserialize, Serialize)]
245#[serde(rename_all = "camelCase")]
246pub struct AgentBlurRequest {
247    pub window: Option<String>,
248    #[serde(rename = "ref")]
249    pub ref_id: String,
250}
251
252#[derive(Clone, Debug, Deserialize, Serialize)]
253#[serde(rename_all = "camelCase")]
254pub struct AgentScrollRequest {
255    pub window: Option<String>,
256    #[serde(rename = "ref")]
257    pub ref_id: String,
258    pub x: Option<f64>,
259    pub y: Option<f64>,
260}
261
262#[derive(Clone, Debug, Deserialize, Serialize)]
263#[serde(rename_all = "camelCase")]
264pub struct AgentDragRequest {
265    pub window: Option<String>,
266    #[serde(rename = "ref")]
267    pub ref_id: String,
268    pub to_ref: Option<String>,
269}
270
271#[derive(Clone, Debug, Deserialize, Serialize)]
272#[serde(rename_all = "camelCase")]
273pub enum AgentAction {
274    Click,
275    Fill,
276    Press,
277}
278
279#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
280#[serde(rename_all = "PascalCase")]
281pub enum KeyModifier {
282    Alt,
283    Control,
284    Meta,
285    Shift,
286}
287
288#[derive(Clone, Debug, Default, Deserialize, Serialize)]
289#[serde(rename_all = "camelCase")]
290pub struct AgentScreenshotRequest {
291    pub window: Option<String>,
292    pub path: Option<String>,
293    pub backend: Option<ScreenshotBackend>,
294    #[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
295    pub ref_id: Option<String>,
296}
297
298#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
299#[serde(rename_all = "camelCase")]
300pub enum ScreenshotBackend {
301    Dom,
302    Native,
303    Auto,
304}
305
306#[derive(Clone, Debug, Default, Deserialize, Serialize)]
307#[serde(rename_all = "camelCase")]
308pub struct AgentLogRequest {
309    pub window: Option<String>,
310    pub follow: Option<bool>,
311    pub clear: Option<bool>,
312}
313
314#[derive(Clone, Debug, Deserialize, Serialize)]
315#[serde(rename_all = "camelCase")]
316pub struct AgentLogEntry {
317    pub level: String,
318    pub message: String,
319    pub timestamp: String,
320    pub window: Option<String>,
321}
322
323#[derive(Clone, Debug, Default, Deserialize, Serialize)]
324#[serde(rename_all = "camelCase")]
325pub struct AgentEventsRequest {
326    pub window: Option<String>,
327    pub follow: Option<bool>,
328    pub clear: Option<bool>,
329}
330
331#[derive(Clone, Debug, Default, Deserialize, Serialize)]
332#[serde(rename_all = "camelCase")]
333pub struct AgentNetworkRequest {
334    pub window: Option<String>,
335    pub follow: Option<bool>,
336    pub clear: Option<bool>,
337}
338
339#[derive(Clone, Debug, Deserialize, Serialize)]
340#[serde(rename_all = "camelCase")]
341pub struct AgentNetworkEntry {
342    pub id: String,
343    #[serde(rename = "type")]
344    pub entry_type: String,
345    pub method: String,
346    pub url: String,
347    pub status: Option<u16>,
348    pub ok: Option<bool>,
349    pub started_at: String,
350    pub ended_at: Option<String>,
351    pub duration_ms: Option<f64>,
352    pub request_body_size: Option<u64>,
353    pub response_body_size: Option<u64>,
354    pub error: Option<String>,
355    pub window: Option<String>,
356}
357
358#[derive(Clone, Debug, Default, Deserialize, Serialize)]
359#[serde(rename_all = "camelCase")]
360pub struct AgentIpcRequest {
361    pub window: Option<String>,
362    pub follow: Option<bool>,
363    pub clear: Option<bool>,
364}
365
366#[derive(Clone, Debug, Deserialize, Serialize)]
367#[serde(rename_all = "camelCase")]
368pub struct AgentIpcEntry {
369    pub id: String,
370    pub command: String,
371    pub started_at: String,
372    pub ended_at: Option<String>,
373    pub duration_ms: Option<f64>,
374    pub ok: Option<bool>,
375    pub error: Option<String>,
376    pub window: Option<String>,
377}
378
379#[derive(Clone, Debug, Default, Deserialize, Serialize)]
380#[serde(rename_all = "camelCase")]
381pub struct AgentStorageRequest {
382    pub window: Option<String>,
383    pub area: Option<StorageArea>,
384    pub action: Option<StorageAction>,
385    pub key: Option<String>,
386    pub value: Option<String>,
387}
388
389#[derive(Clone, Debug, Default, Deserialize, Serialize)]
390#[serde(rename_all = "camelCase")]
391pub enum StorageArea {
392    #[default]
393    Local,
394    Session,
395}
396
397#[derive(Clone, Debug, Default, Deserialize, Serialize)]
398#[serde(rename_all = "camelCase")]
399pub enum StorageAction {
400    #[default]
401    Get,
402    Set,
403    Remove,
404    Clear,
405}
406
407#[derive(Clone, Debug, Deserialize, Serialize)]
408#[serde(rename_all = "camelCase")]
409pub struct AgentStorageEntry {
410    pub area: StorageArea,
411    pub key: String,
412    pub value: String,
413}
414
415#[derive(Clone, Debug, Deserialize, Serialize)]
416#[serde(rename_all = "camelCase")]
417pub struct AgentStorageResponse {
418    pub area: StorageArea,
419    pub entries: Vec<AgentStorageEntry>,
420}
421
422#[derive(Clone, Debug, Default, Deserialize, Serialize)]
423#[serde(rename_all = "camelCase")]
424pub struct AgentCookiesRequest {
425    pub window: Option<String>,
426    pub action: Option<CookieAction>,
427    pub name: Option<String>,
428    pub value: Option<String>,
429}
430
431#[derive(Clone, Debug, Default, Deserialize, Serialize)]
432#[serde(rename_all = "camelCase")]
433pub enum CookieAction {
434    #[default]
435    Get,
436    Set,
437    Remove,
438    Clear,
439}
440
441#[derive(Clone, Debug, Deserialize, Serialize)]
442#[serde(rename_all = "camelCase")]
443pub struct AgentCookieEntry {
444    pub name: String,
445    pub value: String,
446}
447
448#[derive(Clone, Debug, Deserialize, Serialize)]
449#[serde(rename_all = "camelCase")]
450pub struct AgentCookiesResponse {
451    pub entries: Vec<AgentCookieEntry>,
452}
453
454#[derive(Clone, Debug, Default, Deserialize, Serialize)]
455#[serde(rename_all = "camelCase")]
456pub struct AgentLocationRequest {
457    pub window: Option<String>,
458    pub action: Option<LocationAction>,
459    pub url: Option<String>,
460}
461
462#[derive(Clone, Debug, Default, Deserialize, Serialize)]
463#[serde(rename_all = "camelCase")]
464pub enum LocationAction {
465    #[default]
466    Get,
467    Push,
468    Replace,
469    Reload,
470    Back,
471    Forward,
472}
473
474#[derive(Clone, Debug, Deserialize, Serialize)]
475#[serde(rename_all = "camelCase")]
476pub struct AgentLocationResponse {
477    pub href: String,
478    pub origin: String,
479    pub pathname: String,
480    pub search: String,
481    pub hash: String,
482}
483
484#[derive(Clone, Debug, Deserialize, Serialize)]
485#[serde(rename_all = "camelCase")]
486pub struct AgentEventEntry {
487    pub kind: String,
488    pub timestamp: String,
489    pub window: Option<String>,
490    pub detail: Option<serde_json::Value>,
491}
492
493#[derive(Clone, Debug, Deserialize, Serialize)]
494#[serde(rename_all = "camelCase")]
495pub struct AgentWaitRequest {
496    pub window: Option<String>,
497    pub text: Option<String>,
498    pub scope: Option<String>,
499    pub role: Option<String>,
500    pub name: Option<String>,
501    pub timeout_ms: Option<u64>,
502    pub state: Option<String>,
503    #[serde(rename = "fn", default, skip_serializing_if = "Option::is_none")]
504    pub wait_fn: Option<String>,
505    #[serde(default, skip_serializing_if = "Option::is_none")]
506    pub network_idle: Option<bool>,
507    #[serde(default, skip_serializing_if = "Option::is_none")]
508    pub idle_ms: Option<u64>,
509}
510
511#[derive(Clone, Debug, Deserialize, Serialize)]
512#[serde(rename_all = "camelCase")]
513pub struct AgentWaitResponse {
514    pub matched: bool,
515    pub text: String,
516    #[serde(rename = "match", skip_serializing_if = "Option::is_none")]
517    pub match_entry: Option<AgentInspectResponse>,
518}
519
520#[derive(Clone, Debug, Default, Deserialize, Serialize)]
521#[serde(rename_all = "camelCase")]
522pub struct AgentExpectRequest {
523    pub window: Option<String>,
524    pub scope: Option<String>,
525    pub role: Option<String>,
526    pub name: Option<String>,
527    pub text: Option<String>,
528    pub present: Option<bool>,
529    pub value: Option<String>,
530    pub has_state: Option<String>,
531}
532
533#[derive(Clone, Debug, Deserialize, Serialize)]
534#[serde(rename_all = "camelCase")]
535pub struct AgentExpectResponse {
536    pub ok: bool,
537    #[serde(rename = "match", skip_serializing_if = "Option::is_none")]
538    pub match_entry: Option<AgentInspectResponse>,
539}
540
541#[derive(Clone, Debug, Default, Deserialize, Serialize)]
542#[serde(rename_all = "camelCase")]
543pub struct AgentStateRequest {
544    pub window: Option<String>,
545    pub key: Option<String>,
546}
547
548#[derive(Clone, Debug, Default, Deserialize, Serialize)]
549#[serde(rename_all = "camelCase")]
550pub struct AgentRecordRequest {
551    pub window: Option<String>,
552    pub action: Option<RecordAction>,
553}
554
555#[derive(Clone, Debug, Default, Deserialize, Serialize)]
556#[serde(rename_all = "camelCase")]
557pub struct AgentStreamRequest {
558    pub window: Option<String>,
559    pub since: Option<u64>,
560    pub timeout_ms: Option<u64>,
561}
562
563#[derive(Clone, Debug, Deserialize, Serialize)]
564#[serde(rename_all = "camelCase")]
565pub struct AgentStreamFrame {
566    pub seq: u64,
567    pub added: Vec<String>,
568    pub removed: Vec<String>,
569}
570
571#[derive(Clone, Debug, Deserialize, Serialize)]
572#[serde(rename_all = "camelCase")]
573pub struct AgentStreamResponse {
574    pub frames: Vec<AgentStreamFrame>,
575    pub cursor: u64,
576    pub snapshot: String,
577    pub dropped: bool,
578}
579
580#[derive(Clone, Debug, Default, Deserialize, Serialize)]
581#[serde(rename_all = "camelCase")]
582pub enum RecordAction {
583    Start,
584    Stop,
585    #[default]
586    Get,
587    Clear,
588}
589
590#[derive(Clone, Debug, Deserialize, Serialize)]
591#[serde(rename_all = "camelCase")]
592pub struct AgentRecordEntry {
593    pub method: String,
594    pub params: Option<serde_json::Value>,
595    pub timestamp: String,
596}
597
598#[derive(Clone, Debug, Deserialize, Serialize)]
599#[serde(rename_all = "camelCase")]
600pub struct AgentRecordResponse {
601    pub recording: bool,
602    pub entries: Vec<AgentRecordEntry>,
603}
604
605#[derive(Clone, Debug, Deserialize, Serialize)]
606#[serde(rename_all = "camelCase")]
607pub struct WindowInfo {
608    pub label: String,
609    pub title: Option<String>,
610    pub focused: bool,
611    pub visible: bool,
612    #[serde(skip_serializing_if = "Option::is_none")]
613    pub minimized: Option<bool>,
614    #[serde(skip_serializing_if = "Option::is_none")]
615    pub maximized: Option<bool>,
616    #[serde(skip_serializing_if = "Option::is_none")]
617    pub scale_factor: Option<f64>,
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub inner_bounds: Option<WindowBounds>,
620    #[serde(skip_serializing_if = "Option::is_none")]
621    pub outer_bounds: Option<WindowBounds>,
622}
623
624#[derive(Clone, Debug, Deserialize, Serialize)]
625#[serde(rename_all = "camelCase")]
626pub struct WindowBounds {
627    pub x: i32,
628    pub y: i32,
629    pub width: u32,
630    pub height: u32,
631}
632
633#[cfg(test)]
634mod tests {
635    use super::*;
636
637    #[test]
638    fn serializes_headless_debugger_models_with_camel_case_fields() {
639        let config = Config::default();
640        assert!(!config.inline_server.enabled);
641        assert_eq!(config.inline_server.host, "127.0.0.1");
642        assert_eq!(config.inline_server.port, 0);
643        assert!(config.inline_server.publish_endpoint);
644
645        let parsed: Config = serde_json::from_value(serde_json::json!({
646            "inlineServer": {
647                "enabled": true,
648                "port": 45127
649            }
650        }))
651        .unwrap();
652        assert!(parsed.inline_server.enabled);
653        assert_eq!(parsed.inline_server.host, "127.0.0.1");
654        assert_eq!(parsed.inline_server.port, 45127);
655
656        let window = WindowInfo {
657            label: "main".into(),
658            title: Some("Fixture".into()),
659            focused: true,
660            visible: true,
661            minimized: Some(false),
662            maximized: Some(false),
663            scale_factor: Some(2.0),
664            inner_bounds: Some(WindowBounds {
665                x: 10,
666                y: 20,
667                width: 800,
668                height: 600,
669            }),
670            outer_bounds: Some(WindowBounds {
671                x: 4,
672                y: 12,
673                width: 824,
674                height: 648,
675            }),
676        };
677        assert_eq!(
678            serde_json::to_value(window).unwrap(),
679            serde_json::json!({
680                "label": "main",
681                "title": "Fixture",
682                "focused": true,
683                "visible": true,
684                "minimized": false,
685                "maximized": false,
686                "scaleFactor": 2.0,
687                "innerBounds": {"x": 10, "y": 20, "width": 800, "height": 600},
688                "outerBounds": {"x": 4, "y": 12, "width": 824, "height": 648}
689            })
690        );
691
692        let window_request = AgentWindowRequest {
693            window: Some("main".into()),
694            action: Some(WindowAction::SetSize),
695            x: None,
696            y: None,
697            width: Some(800),
698            height: Some(600),
699        };
700        assert_eq!(
701            serde_json::to_value(window_request).unwrap(),
702            serde_json::json!({
703                "window": "main",
704                "action": "setSize",
705                "x": null,
706                "y": null,
707                "width": 800,
708                "height": 600
709            })
710        );
711
712        let attach = AgentAttachRequest {
713            app: Some("ducktape".into()),
714            window: Some("main".into()),
715        };
716        assert_eq!(
717            serde_json::to_value(attach).unwrap(),
718            serde_json::json!({"app": "ducktape", "window": "main"})
719        );
720
721        let wait = AgentWaitRequest {
722            window: Some("main".into()),
723            text: Some("Registered".into()),
724            scope: None,
725            role: None,
726            name: None,
727            timeout_ms: Some(250),
728            state: None,
729            wait_fn: None,
730            network_idle: None,
731            idle_ms: None,
732        };
733        assert_eq!(
734            serde_json::to_value(wait).unwrap(),
735            serde_json::json!({
736                "window": "main",
737                "text": "Registered",
738                "scope": null,
739                "role": null,
740                "name": null,
741                "timeoutMs": 250,
742                "state": null
743            })
744        );
745
746        let semantic_wait = AgentWaitRequest {
747            window: Some("main".into()),
748            text: None,
749            scope: Some("main".into()),
750            role: Some("button".into()),
751            name: Some("Forge".into()),
752            timeout_ms: Some(250),
753            state: Some("absent".into()),
754            wait_fn: None,
755            network_idle: None,
756            idle_ms: None,
757        };
758        assert_eq!(
759            serde_json::to_value(semantic_wait).unwrap(),
760            serde_json::json!({
761                "window": "main",
762                "text": null,
763                "scope": "main",
764                "role": "button",
765                "name": "Forge",
766                "timeoutMs": 250,
767                "state": "absent"
768            })
769        );
770
771        let find = AgentFindRequest {
772            window: Some("main".into()),
773            scope: Some("main".into()),
774            role: Some("button".into()),
775            name: Some("Forge".into()),
776            text: None,
777            limit: Some(1),
778        };
779        assert_eq!(
780            serde_json::to_value(find).unwrap(),
781            serde_json::json!({
782                "window": "main",
783                "scope": "main",
784                "role": "button",
785                "name": "Forge",
786                "text": null,
787                "limit": 1
788            })
789        );
790
791        let find_response = AgentFindResponse {
792            matches: vec![AgentInspectResponse {
793                ref_id: "@1".into(),
794                role: "button".into(),
795                name: "Forge".into(),
796                tag_name: "button".into(),
797                text: "Forge".into(),
798                value: None,
799                attributes: BTreeMap::new(),
800                states: Vec::new(),
801            }],
802        };
803        assert_eq!(
804            serde_json::to_value(find_response).unwrap(),
805            serde_json::json!({
806                "matches": [{
807                    "ref": "@1",
808                    "role": "button",
809                    "name": "Forge",
810                    "tagName": "button",
811                    "text": "Forge",
812                    "value": null,
813                    "attributes": {},
814                    "states": []
815                }]
816            })
817        );
818
819        let semantic_wait_response = AgentWaitResponse {
820            matched: true,
821            text: "Forge".into(),
822            match_entry: Some(AgentInspectResponse {
823                ref_id: "@1".into(),
824                role: "button".into(),
825                name: "Forge".into(),
826                tag_name: "button".into(),
827                text: "Forge".into(),
828                value: None,
829                attributes: BTreeMap::new(),
830                states: Vec::new(),
831            }),
832        };
833        assert_eq!(
834            serde_json::to_value(semantic_wait_response).unwrap(),
835            serde_json::json!({
836                "matched": true,
837                "text": "Forge",
838                "match": {
839                    "ref": "@1",
840                    "role": "button",
841                    "name": "Forge",
842                    "tagName": "button",
843                    "text": "Forge",
844                    "value": null,
845                    "attributes": {},
846                    "states": []
847                }
848            })
849        );
850
851        let inspect = AgentInspectRequest {
852            window: Some("main".into()),
853            ref_id: "@4".into(),
854        };
855        assert_eq!(
856            serde_json::to_value(inspect).unwrap(),
857            serde_json::json!({"window": "main", "ref": "@4"})
858        );
859
860        let eval = AgentEvalRequest {
861            window: Some("main".into()),
862            code: "document.title".into(),
863        };
864        assert_eq!(
865            serde_json::to_value(eval).unwrap(),
866            serde_json::json!({"window": "main", "code": "document.title"})
867        );
868
869        let select = AgentSelectRequest {
870            window: Some("main".into()),
871            ref_id: "@4".into(),
872            value: Some("remote".into()),
873        };
874        assert_eq!(
875            serde_json::to_value(select).unwrap(),
876            serde_json::json!({"window": "main", "ref": "@4", "value": "remote"})
877        );
878
879        let check = AgentCheckRequest {
880            window: Some("main".into()),
881            ref_id: "@6".into(),
882            checked: Some(true),
883        };
884        assert_eq!(
885            serde_json::to_value(check).unwrap(),
886            serde_json::json!({"window": "main", "ref": "@6", "checked": true})
887        );
888
889        let hover = AgentHoverRequest {
890            window: Some("main".into()),
891            ref_id: "@1".into(),
892        };
893        assert_eq!(
894            serde_json::to_value(hover).unwrap(),
895            serde_json::json!({"window": "main", "ref": "@1"})
896        );
897
898        let focus = AgentFocusRequest {
899            window: Some("main".into()),
900            ref_id: "@2".into(),
901        };
902        assert_eq!(
903            serde_json::to_value(focus).unwrap(),
904            serde_json::json!({"window": "main", "ref": "@2"})
905        );
906
907        let blur = AgentBlurRequest {
908            window: Some("main".into()),
909            ref_id: "@2".into(),
910        };
911        assert_eq!(
912            serde_json::to_value(blur).unwrap(),
913            serde_json::json!({"window": "main", "ref": "@2"})
914        );
915
916        let scroll = AgentScrollRequest {
917            window: Some("main".into()),
918            ref_id: "@7".into(),
919            x: Some(3.0),
920            y: Some(12.0),
921        };
922        assert_eq!(
923            serde_json::to_value(scroll).unwrap(),
924            serde_json::json!({"window": "main", "ref": "@7", "x": 3.0, "y": 12.0})
925        );
926
927        let drag = AgentDragRequest {
928            window: Some("main".into()),
929            ref_id: "@1".into(),
930            to_ref: Some("@8".into()),
931        };
932        assert_eq!(
933            serde_json::to_value(drag).unwrap(),
934            serde_json::json!({"window": "main", "ref": "@1", "toRef": "@8"})
935        );
936
937        let record = AgentRecordRequest {
938            window: None,
939            action: Some(RecordAction::Start),
940        };
941        assert_eq!(
942            serde_json::to_value(record).unwrap(),
943            serde_json::json!({"window": null, "action": "start"})
944        );
945
946        let logs = AgentLogRequest {
947            window: Some("main".into()),
948            follow: Some(true),
949            clear: Some(true),
950        };
951        assert_eq!(
952            serde_json::to_value(logs).unwrap(),
953            serde_json::json!({"window": "main", "follow": true, "clear": true})
954        );
955
956        let events = AgentEventsRequest {
957            window: Some("main".into()),
958            follow: Some(true),
959            clear: Some(true),
960        };
961        assert_eq!(
962            serde_json::to_value(events).unwrap(),
963            serde_json::json!({"window": "main", "follow": true, "clear": true})
964        );
965
966        let network = AgentNetworkRequest {
967            window: Some("main".into()),
968            follow: Some(true),
969            clear: Some(true),
970        };
971        assert_eq!(
972            serde_json::to_value(network).unwrap(),
973            serde_json::json!({"window": "main", "follow": true, "clear": true})
974        );
975
976        let network_entry = AgentNetworkEntry {
977            id: "fetch-1".into(),
978            entry_type: "fetch".into(),
979            method: "POST".into(),
980            url: "https://example.test/api/agents".into(),
981            status: Some(201),
982            ok: Some(true),
983            started_at: "2026-07-07T00:00:00.000Z".into(),
984            ended_at: Some("2026-07-07T00:00:00.050Z".into()),
985            duration_ms: Some(50.0),
986            request_body_size: Some(8),
987            response_body_size: Some(11),
988            error: None,
989            window: Some("main".into()),
990        };
991        assert_eq!(
992            serde_json::to_value(network_entry).unwrap(),
993            serde_json::json!({
994                "id": "fetch-1",
995                "type": "fetch",
996                "method": "POST",
997                "url": "https://example.test/api/agents",
998                "status": 201,
999                "ok": true,
1000                "startedAt": "2026-07-07T00:00:00.000Z",
1001                "endedAt": "2026-07-07T00:00:00.050Z",
1002                "durationMs": 50.0,
1003                "requestBodySize": 8,
1004                "responseBodySize": 11,
1005                "error": null,
1006                "window": "main"
1007            })
1008        );
1009
1010        let storage = AgentStorageRequest {
1011            window: Some("main".into()),
1012            area: Some(StorageArea::Session),
1013            action: Some(StorageAction::Set),
1014            key: Some("agent.route".into()),
1015            value: Some("/agents".into()),
1016        };
1017        assert_eq!(
1018            serde_json::to_value(storage).unwrap(),
1019            serde_json::json!({
1020                "window": "main",
1021                "area": "session",
1022                "action": "set",
1023                "key": "agent.route",
1024                "value": "/agents"
1025            })
1026        );
1027
1028        let storage_response = AgentStorageResponse {
1029            area: StorageArea::Session,
1030            entries: vec![AgentStorageEntry {
1031                area: StorageArea::Session,
1032                key: "agent.route".into(),
1033                value: "/agents".into(),
1034            }],
1035        };
1036        assert_eq!(
1037            serde_json::to_value(storage_response).unwrap(),
1038            serde_json::json!({
1039                "area": "session",
1040                "entries": [{
1041                    "area": "session",
1042                    "key": "agent.route",
1043                    "value": "/agents"
1044                }]
1045            })
1046        );
1047
1048        let cookies = AgentCookiesRequest {
1049            window: Some("main".into()),
1050            action: Some(CookieAction::Set),
1051            name: Some("agent.cookie".into()),
1052            value: Some("ready".into()),
1053        };
1054        assert_eq!(
1055            serde_json::to_value(cookies).unwrap(),
1056            serde_json::json!({
1057                "window": "main",
1058                "action": "set",
1059                "name": "agent.cookie",
1060                "value": "ready"
1061            })
1062        );
1063
1064        let cookies_response = AgentCookiesResponse {
1065            entries: vec![AgentCookieEntry {
1066                name: "agent.cookie".into(),
1067                value: "ready".into(),
1068            }],
1069        };
1070        assert_eq!(
1071            serde_json::to_value(cookies_response).unwrap(),
1072            serde_json::json!({
1073                "entries": [{
1074                    "name": "agent.cookie",
1075                    "value": "ready"
1076                }]
1077            })
1078        );
1079
1080        let location = AgentLocationRequest {
1081            window: Some("main".into()),
1082            action: Some(LocationAction::Push),
1083            url: Some("/agents?view=debug#roster".into()),
1084        };
1085        assert_eq!(
1086            serde_json::to_value(location).unwrap(),
1087            serde_json::json!({
1088                "window": "main",
1089                "action": "push",
1090                "url": "/agents?view=debug#roster"
1091            })
1092        );
1093
1094        let location_response = AgentLocationResponse {
1095            href: "tauri-agent://static/agents?view=debug#roster".into(),
1096            origin: "null".into(),
1097            pathname: "/agents".into(),
1098            search: "?view=debug".into(),
1099            hash: "#roster".into(),
1100        };
1101        assert_eq!(
1102            serde_json::to_value(location_response).unwrap(),
1103            serde_json::json!({
1104                "href": "tauri-agent://static/agents?view=debug#roster",
1105                "origin": "null",
1106                "pathname": "/agents",
1107                "search": "?view=debug",
1108                "hash": "#roster"
1109            })
1110        );
1111
1112        let screenshot = AgentScreenshotRequest {
1113            window: Some("main".into()),
1114            path: Some("/tmp/app.png".into()),
1115            backend: Some(ScreenshotBackend::Native),
1116            ref_id: None,
1117        };
1118        assert_eq!(
1119            serde_json::to_value(screenshot).unwrap(),
1120            serde_json::json!({
1121                "window": "main",
1122                "path": "/tmp/app.png",
1123                "backend": "native"
1124            })
1125        );
1126
1127        let press = AgentActionRequest {
1128            window: Some("main".into()),
1129            ref_id: Some("@2".into()),
1130            action: AgentAction::Press,
1131            value: Some("Enter".into()),
1132            modifiers: Some(vec![KeyModifier::Meta, KeyModifier::Shift]),
1133        };
1134        assert_eq!(
1135            serde_json::to_value(press).unwrap(),
1136            serde_json::json!({
1137                "window": "main",
1138                "ref": "@2",
1139                "action": "press",
1140                "value": "Enter",
1141                "modifiers": ["Meta", "Shift"]
1142            })
1143        );
1144    }
1145}