agentmux_cef\browser_api/types.rs
1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Request/response types for the browser DOM API.
5
6use serde::{Deserialize, Serialize};
7
8// ── Generic response envelope ───────────────────────────────────────────
9
10/// Every `/agentmux/browser/*` response is one of these two shapes.
11/// Matches `SPEC_BROWSER_DOM_API.md` §5.1.
12#[derive(Debug, Serialize)]
13#[serde(untagged)]
14pub enum ApiResponse<T> {
15 Ok { ok: bool, data: T },
16 Err { ok: bool, error: String },
17}
18
19impl<T> ApiResponse<T> {
20 pub fn ok(data: T) -> Self {
21 ApiResponse::Ok { ok: true, data }
22 }
23 pub fn err(msg: impl Into<String>) -> Self {
24 ApiResponse::Err {
25 ok: false,
26 error: msg.into(),
27 }
28 }
29}
30
31// ── browser.query ───────────────────────────────────────────────────────
32
33#[derive(Debug, Deserialize)]
34pub struct QueryReq {
35 pub block_id: String,
36 pub selector: String,
37 #[serde(default)]
38 pub limit: Option<u32>,
39}
40
41#[derive(Debug, Serialize)]
42pub struct QueryData {
43 pub matches: Vec<Element>,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
47pub struct Element {
48 /// Unique CSS path the backend-injected helper computed for this node.
49 /// Stable enough to target the same element in a follow-up call,
50 /// provided the DOM hasn't been restructured in between.
51 pub selector: String,
52 pub tag: String,
53 /// First ~500 chars of textContent — full text would balloon responses.
54 pub text: String,
55 pub attrs: serde_json::Map<String, serde_json::Value>,
56 pub rect: Rect,
57 pub focused: bool,
58}
59
60#[derive(Debug, Serialize, Deserialize)]
61pub struct Rect {
62 pub x: f64,
63 pub y: f64,
64 pub width: f64,
65 pub height: f64,
66}
67
68// ── browser.focus_info ──────────────────────────────────────────────────
69
70#[derive(Debug, Deserialize)]
71pub struct FocusInfoReq {
72 pub block_id: String,
73}
74
75#[derive(Debug, Serialize)]
76pub struct FocusInfoData {
77 /// null when `document.activeElement` is null or the `<body>`
78 /// (the default resting state with no focused control).
79 pub focused: Option<Element>,
80}
81
82// ── browser.eval ────────────────────────────────────────────────────────
83
84#[derive(Debug, Deserialize)]
85pub struct EvalReq {
86 pub block_id: String,
87 pub script: String,
88 /// If true and the script returns a Promise, wait for it to
89 /// resolve before returning. Maps to CDP `Runtime.evaluate`
90 /// `awaitPromise`.
91 #[serde(default)]
92 pub await_promise: bool,
93}
94
95#[derive(Debug, Serialize)]
96pub struct EvalData {
97 /// The serialized JS return value, whatever shape the script
98 /// produced. `null` if the script returned `undefined` or threw.
99 pub result: serde_json::Value,
100 /// CDP's type tag: "object" | "string" | "number" | "boolean" |
101 /// "undefined" | "function" | "symbol" | "bigint". Kept so
102 /// callers can distinguish `null`-the-value from `null`-the-failure.
103 #[serde(rename = "type")]
104 pub type_: String,
105 /// Populated when the script threw. The message + stack, when
106 /// available. `result` is null in this case.
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub exception: Option<String>,
109}
110
111// ── browser.screenshot ──────────────────────────────────────────────────
112
113#[derive(Debug, Deserialize)]
114pub struct ScreenshotReq {
115 pub block_id: String,
116}
117
118#[derive(Debug, Serialize)]
119pub struct ScreenshotData {
120 /// Base64-encoded PNG bytes — same format CDP's
121 /// `Page.captureScreenshot` returns.
122 pub png_base64: String,
123}
124
125// ── browser.click_element ───────────────────────────────────────────────
126
127#[derive(Debug, Deserialize)]
128pub struct ClickElementReq {
129 pub block_id: String,
130 pub selector: String,
131}
132
133// ── browser.focus_element ───────────────────────────────────────────────
134
135#[derive(Debug, Deserialize)]
136pub struct FocusElementReq {
137 pub block_id: String,
138 pub selector: String,
139}
140
141// ── browser.dispatch_key ────────────────────────────────────────────────
142
143#[derive(Debug, Deserialize)]
144pub struct DispatchKeyReq {
145 pub block_id: String,
146 /// Optional CSS selector: focus this element before dispatching
147 /// the key event(s). If absent, the key lands on whatever has
148 /// focus currently.
149 #[serde(default)]
150 pub selector: Option<String>,
151 /// Send this text as `Input.insertText` (atomic, preserves IME
152 /// and autocomplete behaviour). Mutually exclusive with `key`.
153 #[serde(default)]
154 pub text: Option<String>,
155 /// Send this named key as a `keyDown`+`keyUp` pair. Supported:
156 /// `Enter`, `Tab`, `Escape`, `Backspace`, `ArrowUp`, `ArrowDown`,
157 /// `ArrowLeft`, `ArrowRight`, `Space`. Unknown keys → error.
158 #[serde(default)]
159 pub key: Option<String>,
160}
161
162// ── browser.navigate ────────────────────────────────────────────────────
163
164#[derive(Debug, Deserialize)]
165pub struct NavigateReq {
166 pub block_id: String,
167 pub url: String,
168}
169
170// ── browser.back / .forward / .reload ───────────────────────────────────
171//
172// Share a single request shape — all three only need the target block id.
173// These exist so agents driving a browser pane during dev / tests can walk
174// its history without a human clicking the toolbar. Also useful for the
175// agent workflow where a tool says "open this URL, try to click X, if not
176// found go back and try Y."
177
178#[derive(Debug, Deserialize)]
179pub struct HistoryReq {
180 pub block_id: String,
181 /// Reload only: skip the http cache and force a network refetch.
182 /// Ignored by `back` / `forward`. Defaults to false.
183 #[serde(default)]
184 pub ignore_cache: bool,
185}
186
187// ── Generic "ok:true" success body for write endpoints ──────────────────
188
189#[derive(Debug, Serialize)]
190pub struct AckData {
191 pub ok: bool,
192}
193
194impl AckData {
195 pub fn new() -> Self {
196 Self { ok: true }
197 }
198}