Skip to main content

browsr_types/
client_api.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
6#[serde(rename_all = "camelCase")]
7pub enum ScrapeFormat {
8    Markdown,
9    Html,
10    Screenshot,
11    Structured,
12    Agent,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
16#[serde(rename_all = "camelCase")]
17pub struct JsonExtractionOptions {
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub prompt: Option<String>,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub schema: Option<Value>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
25#[serde(rename_all = "camelCase")]
26pub struct ScrapeAction {
27    #[serde(rename = "type")]
28    pub action_type: String,
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub selector: Option<String>,
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub text: Option<String>,
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub milliseconds: Option<u64>,
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub expression: Option<String>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
40#[serde(rename_all = "camelCase")]
41pub struct ScrapeApiRequest {
42    pub url: String,
43    #[serde(default = "default_scrape_formats")]
44    pub formats: Vec<ScrapeFormat>,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub wait_for: Option<u64>,
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub actions: Option<Vec<ScrapeAction>>,
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub json_options: Option<JsonExtractionOptions>,
51    #[serde(default = "default_true")]
52    pub only_main_content: bool,
53    #[serde(default = "default_true")]
54    pub remove_base64_images: bool,
55}
56
57fn default_scrape_formats() -> Vec<ScrapeFormat> {
58    vec![ScrapeFormat::Markdown]
59}
60
61fn default_true() -> bool {
62    true
63}
64
65impl ScrapeApiRequest {
66    pub fn new(url: impl Into<String>) -> Self {
67        Self {
68            url: url.into(),
69            formats: vec![ScrapeFormat::Markdown],
70            wait_for: None,
71            actions: None,
72            json_options: None,
73            only_main_content: true,
74            remove_base64_images: true,
75        }
76    }
77
78    pub fn with_formats(mut self, formats: Vec<ScrapeFormat>) -> Self {
79        self.formats = formats;
80        self
81    }
82
83    pub fn with_wait(mut self, ms: u64) -> Self {
84        self.wait_for = Some(ms);
85        self
86    }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
90#[serde(rename_all = "camelCase")]
91pub struct PageMetadata {
92    #[serde(default)]
93    pub title: Option<String>,
94    #[serde(default)]
95    pub description: Option<String>,
96    #[serde(default, rename = "sourceURL")]
97    pub source_url: String,
98    #[serde(default)]
99    pub status_code: Option<u16>,
100}
101
102/// HTML output from /v1/scrape.
103#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
104#[serde(rename_all = "camelCase")]
105pub struct HtmlResult {
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub full: Option<String>,
108    #[serde(default, skip_serializing_if = "Vec::is_empty")]
109    pub selectors: Vec<HtmlSelectorResult>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
113#[serde(rename_all = "camelCase")]
114pub struct HtmlSelectorResult {
115    pub selector: String,
116    pub html: String,
117}
118
119/// Scraped data matching browsr-cloud /v1/scrape response.
120#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
121#[serde(rename_all = "camelCase")]
122pub struct ScrapeData {
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub markdown: Option<String>,
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub html: Option<HtmlResult>,
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub screenshot: Option<String>,
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub structured: Option<Value>,
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub agent: Option<Value>,
133    #[serde(default)]
134    pub metadata: PageMetadata,
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub warning: Option<String>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
140pub struct ScrapeApiResponse {
141    pub success: bool,
142    pub data: ScrapeData,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
146#[serde(rename_all = "camelCase")]
147pub struct CrawlApiRequest {
148    pub url: String,
149    #[serde(default = "default_crawl_limit")]
150    pub limit: usize,
151    #[serde(default = "default_crawl_depth")]
152    pub max_depth: usize,
153    #[serde(default = "default_scrape_formats")]
154    pub formats: Vec<ScrapeFormat>,
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub wait_for: Option<u64>,
157    #[serde(default, skip_serializing_if = "Option::is_none")]
158    pub include_paths: Option<Vec<String>>,
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub exclude_paths: Option<Vec<String>>,
161    #[serde(default = "default_true")]
162    pub only_main_content: bool,
163    #[serde(default, skip_serializing_if = "Option::is_none")]
164    pub json_options: Option<JsonExtractionOptions>,
165}
166
167fn default_crawl_limit() -> usize {
168    10
169}
170
171fn default_crawl_depth() -> usize {
172    2
173}
174
175impl CrawlApiRequest {
176    pub fn new(url: impl Into<String>) -> Self {
177        Self {
178            url: url.into(),
179            limit: 10,
180            max_depth: 2,
181            formats: vec![ScrapeFormat::Markdown],
182            wait_for: None,
183            include_paths: None,
184            exclude_paths: None,
185            only_main_content: true,
186            json_options: None,
187        }
188    }
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
192pub struct CrawlApiResponse {
193    pub success: bool,
194    pub total: usize,
195    pub completed: usize,
196    pub data: Vec<ScrapeData>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
200pub struct SessionCreated {
201    pub session_id: String,
202    #[serde(default)]
203    pub sse_url: Option<String>,
204    #[serde(default)]
205    pub frame_url: Option<String>,
206    #[serde(default)]
207    pub frame_token: Option<String>,
208}
209
210impl SessionCreated {
211    pub fn build_sse_url(&self, base_url: &str, width: Option<u32>, height: Option<u32>) -> String {
212        let mut url = self
213            .sse_url
214            .clone()
215            .unwrap_or_else(|| format!("{}/stream/sse?session_id={}", base_url, self.session_id));
216
217        if let Some(ref token) = self.frame_token {
218            let sep = if url.contains('?') { "&" } else { "?" };
219            url = format!("{}{}token={}", url, sep, token);
220        }
221        if let Some(w) = width {
222            let sep = if url.contains('?') { "&" } else { "?" };
223            url = format!("{}{}width={}", url, sep, w);
224        }
225        if let Some(h) = height {
226            url = format!("{}&height={}", url, h);
227        }
228        url
229    }
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
233pub struct ObserveOptions {
234    pub use_image: Option<bool>,
235    pub full_page: Option<bool>,
236    pub wait_ms: Option<u64>,
237    pub include_content: Option<bool>,
238}
239
240impl Default for ObserveOptions {
241    fn default() -> Self {
242        Self {
243            use_image: Some(true),
244            full_page: None,
245            wait_ms: None,
246            include_content: Some(true),
247        }
248    }
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
252pub struct RelayEvent {
253    pub ts: i64,
254    pub session_id: String,
255    pub category: String,
256    pub method: Option<String>,
257    pub level: Option<String>,
258    pub summary: Option<String>,
259    pub payload: Value,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
263pub struct RelayEventsResponse {
264    pub session_id: String,
265    pub count: usize,
266    pub events: Vec<RelayEvent>,
267}
268
269// ── Session types (shared between server, client, CLI) ──────────────────
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
272#[serde(rename_all = "snake_case")]
273pub enum SessionType {
274    Browser,
275    Shell,
276    Relay,
277}
278
279impl Default for SessionType {
280    fn default() -> Self {
281        Self::Browser
282    }
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
286pub struct SessionDetail {
287    pub session_id: String,
288    #[serde(default)]
289    pub session_type: SessionType,
290    #[serde(default)]
291    pub worker_id: Option<String>,
292    #[serde(default)]
293    pub user_email: Option<String>,
294    #[serde(default)]
295    pub last_activity: Option<i64>,
296    #[serde(default)]
297    pub idle_secs: Option<i64>,
298    #[serde(default)]
299    pub connected: Option<bool>,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
303pub struct SessionListResponse {
304    pub sessions: Vec<String>,
305    #[serde(default)]
306    pub session_details: Vec<SessionDetail>,
307}
308
309// ── Relay session types ─────────────────────────────────────────────────
310
311#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
312pub struct RelaySessionInfo {
313    pub session_id: String,
314    pub connected: bool,
315    pub connected_at: i64,
316    #[serde(default)]
317    pub last_activity: Option<i64>,
318    #[serde(default)]
319    pub idle_secs: Option<i64>,
320    #[serde(default)]
321    pub user_email: Option<String>,
322    #[serde(default)]
323    pub tab_url: Option<String>,
324    #[serde(default)]
325    pub tab_title: Option<String>,
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
329pub struct RelaySessionListResponse {
330    pub sessions: Vec<RelaySessionInfo>,
331}
332
333// ── Shell shared types ────────────────────────────────────────────────────
334
335/// Network access policy for shell session containers.
336#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
337#[serde(tag = "level", rename_all = "snake_case")]
338pub enum NetworkAccess {
339    None,
340    Trusted,
341    AllowedDomains { domains: Vec<String> },
342    Full,
343}
344
345impl Default for NetworkAccess {
346    fn default() -> Self { NetworkAccess::Trusted }
347}
348
349/// Declarative description of an external tool function.
350#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
351pub struct ToolDefinition {
352    pub name: String,
353    pub description: String,
354    pub parameters: Value,
355}
356
357/// Mount point for input/output data volumes.
358#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
359pub struct MountPoint {
360    pub storage_path: String,
361    pub container_path: String,
362    #[serde(default = "default_mount_max_size_mb")]
363    pub max_size_mb: u32,
364}
365
366fn default_mount_max_size_mb() -> u32 { 500 }
367
368// ── Shell session request/response ───────────────────────────────────────
369
370#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
371pub struct ShellCreateSessionRequest {
372    #[serde(default, skip_serializing_if = "Option::is_none")]
373    pub environment_id: Option<String>,
374    #[serde(default, skip_serializing_if = "Option::is_none")]
375    pub image: Option<String>,
376    #[serde(default, skip_serializing_if = "Option::is_none")]
377    pub memory_mb: Option<u32>,
378    #[serde(default, skip_serializing_if = "Option::is_none")]
379    pub disk_mb: Option<u32>,
380    #[serde(default, skip_serializing_if = "Option::is_none")]
381    pub cpu_cores: Option<f32>,
382    #[serde(default, skip_serializing_if = "Option::is_none")]
383    pub language: Option<String>,
384    #[serde(default, skip_serializing_if = "Option::is_none")]
385    pub timeout_secs: Option<u32>,
386    #[serde(default, skip_serializing_if = "Option::is_none")]
387    pub working_dir: Option<String>,
388    #[serde(default, skip_serializing_if = "Option::is_none")]
389    pub input_mounts: Option<Vec<MountPoint>>,
390    #[serde(default, skip_serializing_if = "Option::is_none")]
391    pub output_mounts: Option<Vec<MountPoint>>,
392    #[serde(default, skip_serializing_if = "Option::is_none")]
393    pub tools: Option<Vec<ToolDefinition>>,
394    #[serde(default, skip_serializing_if = "Option::is_none")]
395    pub tools_endpoint: Option<String>,
396    #[serde(default, skip_serializing_if = "Option::is_none")]
397    pub tool_timeout_secs: Option<u32>,
398    #[serde(default, skip_serializing_if = "Option::is_none")]
399    pub setup_script: Option<String>,
400    #[serde(default, skip_serializing_if = "Option::is_none")]
401    pub cache_paths: Option<Vec<String>>,
402    #[serde(default, skip_serializing_if = "Option::is_none")]
403    pub network_access: Option<NetworkAccess>,
404    #[serde(default, skip_serializing_if = "Option::is_none")]
405    pub env_vars: Option<std::collections::HashMap<String, String>>,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
409pub struct ShellCreateSessionResponse {
410    pub session_id: String,
411    pub status: String,
412    #[serde(default)]
413    pub worker_id: Option<String>,
414    #[serde(default)]
415    pub language: Option<String>,
416    #[serde(default, skip_serializing_if = "Option::is_none")]
417    pub webhook_secret: Option<String>,
418    #[serde(default, skip_serializing_if = "Option::is_none")]
419    pub tools_injected: Option<Vec<String>>,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
423pub struct ShellListItem {
424    pub shell_id: String,
425    pub status: String,
426    pub image: String,
427    pub created_at: String,
428    pub last_activity: String,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
432pub struct ShellListResponse {
433    pub shells: Vec<ShellListItem>,
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
437pub struct ShellTerminateResponse {
438    pub session_id: String,
439    pub status: String,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
443pub struct ShellExecRequest {
444    pub session_id: String,
445    pub command: String,
446    #[serde(default, skip_serializing_if = "Option::is_none")]
447    pub timeout_secs: Option<u32>,
448    #[serde(default, skip_serializing_if = "Option::is_none")]
449    pub working_dir: Option<String>,
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
453pub struct ShellExecResult {
454    #[serde(default)]
455    pub stdout: String,
456    #[serde(default)]
457    pub stderr: String,
458    #[serde(default)]
459    pub exit_code: Option<i32>,
460    #[serde(default)]
461    pub duration_ms: Option<u64>,
462    #[serde(default)]
463    pub timed_out: bool,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
467pub struct ShellExecResponse {
468    pub session_id: String,
469    #[serde(flatten)]
470    pub result: ShellExecResult,
471}