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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
351pub struct ToolDefinition {
352 pub name: String,
353 pub description: String,
354 pub parameters: Value,
355}
356
357#[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#[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}