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, Serialize, Deserialize, JsonSchema)]
270pub struct RelaySessionInfo {
271 pub session_id: String,
272 pub connected: bool,
273 pub connected_at: i64,
274 #[serde(default)]
275 pub last_activity: Option<i64>,
276 #[serde(default)]
277 pub idle_secs: Option<i64>,
278 #[serde(default)]
279 pub user_email: Option<String>,
280 #[serde(default)]
281 pub tab_url: Option<String>,
282 #[serde(default)]
283 pub tab_title: Option<String>,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
287pub struct RelaySessionListResponse {
288 pub sessions: Vec<RelaySessionInfo>,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
295#[serde(tag = "level", rename_all = "snake_case")]
296pub enum NetworkAccess {
297 None,
298 Trusted,
299 AllowedDomains { domains: Vec<String> },
300 Full,
301}
302
303impl Default for NetworkAccess {
304 fn default() -> Self { NetworkAccess::Trusted }
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
309pub struct ToolDefinition {
310 pub name: String,
311 pub description: String,
312 pub parameters: Value,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
317pub struct MountPoint {
318 pub storage_path: String,
319 pub container_path: String,
320 #[serde(default = "default_mount_max_size_mb")]
321 pub max_size_mb: u32,
322}
323
324fn default_mount_max_size_mb() -> u32 { 500 }
325
326#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
329pub struct ShellCreateSessionRequest {
330 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub environment_id: Option<String>,
332 #[serde(default, skip_serializing_if = "Option::is_none")]
333 pub image: Option<String>,
334 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub memory_mb: Option<u32>,
336 #[serde(default, skip_serializing_if = "Option::is_none")]
337 pub disk_mb: Option<u32>,
338 #[serde(default, skip_serializing_if = "Option::is_none")]
339 pub cpu_cores: Option<f32>,
340 #[serde(default, skip_serializing_if = "Option::is_none")]
341 pub language: Option<String>,
342 #[serde(default, skip_serializing_if = "Option::is_none")]
343 pub timeout_secs: Option<u32>,
344 #[serde(default, skip_serializing_if = "Option::is_none")]
345 pub working_dir: Option<String>,
346 #[serde(default, skip_serializing_if = "Option::is_none")]
347 pub input_mounts: Option<Vec<MountPoint>>,
348 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub output_mounts: Option<Vec<MountPoint>>,
350 #[serde(default, skip_serializing_if = "Option::is_none")]
351 pub tools: Option<Vec<ToolDefinition>>,
352 #[serde(default, skip_serializing_if = "Option::is_none")]
353 pub tools_endpoint: Option<String>,
354 #[serde(default, skip_serializing_if = "Option::is_none")]
355 pub tool_timeout_secs: Option<u32>,
356 #[serde(default, skip_serializing_if = "Option::is_none")]
357 pub setup_script: Option<String>,
358 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub cache_paths: Option<Vec<String>>,
360 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub network_access: Option<NetworkAccess>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub env_vars: Option<std::collections::HashMap<String, String>>,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
367pub struct ShellCreateSessionResponse {
368 pub session_id: String,
369 pub status: String,
370 #[serde(default)]
371 pub worker_id: Option<String>,
372 #[serde(default)]
373 pub language: Option<String>,
374 #[serde(default, skip_serializing_if = "Option::is_none")]
375 pub webhook_secret: Option<String>,
376 #[serde(default, skip_serializing_if = "Option::is_none")]
377 pub tools_injected: Option<Vec<String>>,
378}
379
380#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
381pub struct ShellSessionListItem {
382 pub session_id: String,
383 pub status: String,
384 pub image: String,
385 pub created_at: String,
386 pub last_activity: String,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
390pub struct ShellSessionListResponse {
391 pub sessions: Vec<ShellSessionListItem>,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
395pub struct ShellTerminateResponse {
396 pub session_id: String,
397 pub status: String,
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
401pub struct ShellExecRequest {
402 pub session_id: String,
403 pub command: String,
404 #[serde(default, skip_serializing_if = "Option::is_none")]
405 pub timeout_secs: Option<u32>,
406 #[serde(default, skip_serializing_if = "Option::is_none")]
407 pub working_dir: Option<String>,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
411pub struct ShellExecResult {
412 #[serde(default)]
413 pub stdout: String,
414 #[serde(default)]
415 pub stderr: String,
416 #[serde(default)]
417 pub exit_code: Option<i32>,
418 #[serde(default)]
419 pub duration_ms: Option<u64>,
420 #[serde(default)]
421 pub timed_out: bool,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
425pub struct ShellExecResponse {
426 pub session_id: String,
427 #[serde(flatten)]
428 pub result: ShellExecResult,
429}