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 #[serde(default)]
9 pub allow_release_socket: bool,
10 #[serde(default)]
14 pub allow_non_loopback: bool,
15 #[serde(default)]
16 pub inline_server: InlineServerConfig,
17 #[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}