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 Summary,
10 Html,
11 RawHtml,
12 Screenshot,
13 Links,
14 Json,
15 Images,
16 Branding,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
20#[serde(rename_all = "camelCase")]
21pub struct JsonExtractionOptions {
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub prompt: Option<String>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub schema: Option<Value>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
29#[serde(rename_all = "camelCase")]
30pub struct ScrapeAction {
31 #[serde(rename = "type")]
32 pub action_type: String,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub selector: Option<String>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub text: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub milliseconds: Option<u64>,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub expression: Option<String>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
44#[serde(rename_all = "camelCase")]
45pub struct ScrapeApiRequest {
46 pub url: String,
47 #[serde(default = "default_scrape_formats")]
48 pub formats: Vec<ScrapeFormat>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub wait_for: Option<u64>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub actions: Option<Vec<ScrapeAction>>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub json_options: Option<JsonExtractionOptions>,
55 #[serde(default = "default_true")]
56 pub only_main_content: bool,
57 #[serde(default = "default_true")]
58 pub remove_base64_images: bool,
59}
60
61fn default_scrape_formats() -> Vec<ScrapeFormat> {
62 vec![ScrapeFormat::Markdown]
63}
64
65fn default_true() -> bool {
66 true
67}
68
69impl ScrapeApiRequest {
70 pub fn new(url: impl Into<String>) -> Self {
71 Self {
72 url: url.into(),
73 formats: vec![ScrapeFormat::Markdown],
74 wait_for: None,
75 actions: None,
76 json_options: None,
77 only_main_content: true,
78 remove_base64_images: true,
79 }
80 }
81
82 pub fn with_formats(mut self, formats: Vec<ScrapeFormat>) -> Self {
83 self.formats = formats;
84 self
85 }
86
87 pub fn with_wait(mut self, ms: u64) -> Self {
88 self.wait_for = Some(ms);
89 self
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
94#[serde(rename_all = "camelCase")]
95pub struct PageMetadata {
96 pub title: Option<String>,
97 pub description: Option<String>,
98 #[serde(rename = "sourceURL")]
99 pub source_url: String,
100 pub status_code: Option<u16>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
104pub struct ExtractedLink {
105 pub href: String,
106 #[serde(default)]
107 pub text: String,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
111pub struct ExtractedImage {
112 pub src: String,
113 #[serde(default)]
114 pub alt: Option<String>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
118pub struct BrandingInfo {
119 #[serde(default)]
120 pub colors: Option<Vec<String>>,
121 #[serde(default)]
122 pub fonts: Option<Vec<String>>,
123 #[serde(default)]
124 pub logo: Option<String>,
125 #[serde(default)]
126 pub favicon: Option<String>,
127 #[serde(default)]
128 pub name: Option<String>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
132#[serde(rename_all = "camelCase")]
133pub struct ScrapeData {
134 #[serde(default)]
135 pub markdown: Option<String>,
136 #[serde(default)]
137 pub summary: Option<String>,
138 #[serde(default)]
139 pub html: Option<String>,
140 #[serde(default)]
141 pub raw_html: Option<String>,
142 #[serde(default)]
143 pub screenshot: Option<String>,
144 #[serde(default)]
145 pub links: Option<Vec<ExtractedLink>>,
146 #[serde(default)]
147 pub json: Option<Value>,
148 #[serde(default)]
149 pub images: Option<Vec<ExtractedImage>>,
150 #[serde(default)]
151 pub branding: Option<BrandingInfo>,
152 pub metadata: PageMetadata,
153 #[serde(default)]
154 pub warning: Option<String>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
158pub struct ScrapeApiResponse {
159 pub success: bool,
160 pub data: ScrapeData,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
164#[serde(rename_all = "camelCase")]
165pub struct CrawlApiRequest {
166 pub url: String,
167 #[serde(default = "default_crawl_limit")]
168 pub limit: usize,
169 #[serde(default = "default_crawl_depth")]
170 pub max_depth: usize,
171 #[serde(default = "default_scrape_formats")]
172 pub formats: Vec<ScrapeFormat>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub wait_for: Option<u64>,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub include_paths: Option<Vec<String>>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub exclude_paths: Option<Vec<String>>,
179 #[serde(default = "default_true")]
180 pub only_main_content: bool,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub json_options: Option<JsonExtractionOptions>,
183}
184
185fn default_crawl_limit() -> usize {
186 10
187}
188
189fn default_crawl_depth() -> usize {
190 2
191}
192
193impl CrawlApiRequest {
194 pub fn new(url: impl Into<String>) -> Self {
195 Self {
196 url: url.into(),
197 limit: 10,
198 max_depth: 2,
199 formats: vec![ScrapeFormat::Markdown],
200 wait_for: None,
201 include_paths: None,
202 exclude_paths: None,
203 only_main_content: true,
204 json_options: None,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
210pub struct CrawlApiResponse {
211 pub success: bool,
212 pub total: usize,
213 pub completed: usize,
214 pub data: Vec<ScrapeData>,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
218pub struct SessionCreated {
219 pub session_id: String,
220 #[serde(default)]
221 pub sse_url: Option<String>,
222 #[serde(default)]
223 pub frame_url: Option<String>,
224 #[serde(default)]
225 pub frame_token: Option<String>,
226}
227
228impl SessionCreated {
229 pub fn build_sse_url(&self, base_url: &str, width: Option<u32>, height: Option<u32>) -> String {
230 let mut url = self
231 .sse_url
232 .clone()
233 .unwrap_or_else(|| format!("{}/stream/sse?session_id={}", base_url, self.session_id));
234
235 if let Some(ref token) = self.frame_token {
236 let sep = if url.contains('?') { "&" } else { "?" };
237 url = format!("{}{}token={}", url, sep, token);
238 }
239 if let Some(w) = width {
240 let sep = if url.contains('?') { "&" } else { "?" };
241 url = format!("{}{}width={}", url, sep, w);
242 }
243 if let Some(h) = height {
244 url = format!("{}&height={}", url, h);
245 }
246 url
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
251pub struct ObserveOptions {
252 pub use_image: Option<bool>,
253 pub full_page: Option<bool>,
254 pub wait_ms: Option<u64>,
255 pub include_content: Option<bool>,
256}
257
258impl Default for ObserveOptions {
259 fn default() -> Self {
260 Self {
261 use_image: Some(true),
262 full_page: None,
263 wait_ms: None,
264 include_content: Some(true),
265 }
266 }
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
270pub struct RelayEvent {
271 pub ts: i64,
272 pub session_id: String,
273 pub category: String,
274 pub method: Option<String>,
275 pub level: Option<String>,
276 pub summary: Option<String>,
277 pub payload: Value,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
281pub struct RelayEventsResponse {
282 pub session_id: String,
283 pub count: usize,
284 pub events: Vec<RelayEvent>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
288pub struct RelaySessionInfo {
289 pub session_id: String,
290 pub connected: bool,
291 pub connected_at: i64,
292 #[serde(default)]
293 pub last_activity: Option<i64>,
294 #[serde(default)]
295 pub idle_secs: Option<i64>,
296 #[serde(default)]
297 pub user_email: Option<String>,
298 #[serde(default)]
299 pub tab_url: Option<String>,
300 #[serde(default)]
301 pub tab_title: Option<String>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
305pub struct RelaySessionListResponse {
306 pub sessions: Vec<RelaySessionInfo>,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
310#[serde(rename_all = "camelCase")]
311pub struct ShellCreateSessionRequest {
312 #[serde(default, skip_serializing_if = "Option::is_none")]
313 pub image: Option<String>,
314 #[serde(default, skip_serializing_if = "Option::is_none")]
315 pub language: Option<String>,
316 #[serde(default, skip_serializing_if = "Option::is_none")]
317 pub timeout_secs: Option<u32>,
318 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub working_dir: Option<String>,
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
323#[serde(rename_all = "camelCase")]
324pub struct ShellCreateSessionResponse {
325 pub session_id: String,
326 pub status: String,
327 #[serde(default)]
328 pub worker_id: Option<String>,
329 #[serde(default)]
330 pub language: Option<String>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
334#[serde(rename_all = "camelCase")]
335pub struct ShellSessionListItem {
336 pub session_id: String,
337 pub status: String,
338 pub image: String,
339 pub created_at: String,
340 pub last_activity: String,
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
344pub struct ShellSessionListResponse {
345 pub sessions: Vec<ShellSessionListItem>,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
349pub struct ShellTerminateResponse {
350 pub session_id: String,
351 pub status: String,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
355pub struct ShellExecRequest {
356 pub session_id: String,
357 pub command: String,
358 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub timeout_secs: Option<u32>,
360 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub working_dir: Option<String>,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
365pub struct ShellExecResult {
366 #[serde(default)]
367 pub stdout: String,
368 #[serde(default)]
369 pub stderr: String,
370 #[serde(default)]
371 pub exit_code: Option<i32>,
372 #[serde(default)]
373 pub duration_ms: Option<u64>,
374 #[serde(default)]
375 pub timed_out: bool,
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
379pub struct ShellExecResponse {
380 pub session_id: String,
381 #[serde(flatten)]
382 pub result: ShellExecResult,
383}