use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum ScrapeFormat {
Markdown,
Html,
Screenshot,
Structured,
Agent,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct JsonExtractionOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prompt: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schema: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScrapeAction {
#[serde(rename = "type")]
pub action_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selector: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub milliseconds: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expression: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScrapeApiRequest {
pub url: String,
#[serde(default = "default_scrape_formats")]
pub formats: Vec<ScrapeFormat>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub wait_for: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actions: Option<Vec<ScrapeAction>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub json_options: Option<JsonExtractionOptions>,
#[serde(default = "default_true")]
pub only_main_content: bool,
#[serde(default = "default_true")]
pub remove_base64_images: bool,
}
fn default_scrape_formats() -> Vec<ScrapeFormat> {
vec![ScrapeFormat::Markdown]
}
fn default_true() -> bool {
true
}
impl ScrapeApiRequest {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
formats: vec![ScrapeFormat::Markdown],
wait_for: None,
actions: None,
json_options: None,
only_main_content: true,
remove_base64_images: true,
}
}
pub fn with_formats(mut self, formats: Vec<ScrapeFormat>) -> Self {
self.formats = formats;
self
}
pub fn with_wait(mut self, ms: u64) -> Self {
self.wait_for = Some(ms);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
pub struct PageMetadata {
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default, rename = "sourceURL")]
pub source_url: String,
#[serde(default)]
pub status_code: Option<u16>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
pub struct HtmlResult {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub full: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub selectors: Vec<HtmlSelectorResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct HtmlSelectorResult {
pub selector: String,
pub html: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
pub struct ScrapeData {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub markdown: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub html: Option<HtmlResult>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub screenshot: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub structured: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<Value>,
#[serde(default)]
pub metadata: PageMetadata,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub warning: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ScrapeApiResponse {
pub success: bool,
pub data: ScrapeData,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct CrawlApiRequest {
pub url: String,
#[serde(default = "default_crawl_limit")]
pub limit: usize,
#[serde(default = "default_crawl_depth")]
pub max_depth: usize,
#[serde(default = "default_scrape_formats")]
pub formats: Vec<ScrapeFormat>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub wait_for: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_paths: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exclude_paths: Option<Vec<String>>,
#[serde(default = "default_true")]
pub only_main_content: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub json_options: Option<JsonExtractionOptions>,
}
fn default_crawl_limit() -> usize {
10
}
fn default_crawl_depth() -> usize {
2
}
impl CrawlApiRequest {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
limit: 10,
max_depth: 2,
formats: vec![ScrapeFormat::Markdown],
wait_for: None,
include_paths: None,
exclude_paths: None,
only_main_content: true,
json_options: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CrawlApiResponse {
pub success: bool,
pub total: usize,
pub completed: usize,
pub data: Vec<ScrapeData>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SessionCreated {
pub session_id: String,
#[serde(default)]
pub sse_url: Option<String>,
#[serde(default)]
pub frame_url: Option<String>,
#[serde(default)]
pub frame_token: Option<String>,
}
impl SessionCreated {
pub fn build_sse_url(&self, base_url: &str, width: Option<u32>, height: Option<u32>) -> String {
let mut url = self
.sse_url
.clone()
.unwrap_or_else(|| format!("{}/stream/sse?session_id={}", base_url, self.session_id));
if let Some(ref token) = self.frame_token {
let sep = if url.contains('?') { "&" } else { "?" };
url = format!("{}{}token={}", url, sep, token);
}
if let Some(w) = width {
let sep = if url.contains('?') { "&" } else { "?" };
url = format!("{}{}width={}", url, sep, w);
}
if let Some(h) = height {
url = format!("{}&height={}", url, h);
}
url
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ObserveOptions {
pub use_image: Option<bool>,
pub full_page: Option<bool>,
pub wait_ms: Option<u64>,
pub include_content: Option<bool>,
}
impl Default for ObserveOptions {
fn default() -> Self {
Self {
use_image: Some(true),
full_page: None,
wait_ms: None,
include_content: Some(true),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct RelayEvent {
pub ts: i64,
pub session_id: String,
pub category: String,
pub method: Option<String>,
pub level: Option<String>,
pub summary: Option<String>,
pub payload: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct RelayEventsResponse {
pub session_id: String,
pub count: usize,
pub events: Vec<RelayEvent>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SessionType {
Browser,
Shell,
Relay,
}
impl Default for SessionType {
fn default() -> Self {
Self::Browser
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SessionDetail {
pub session_id: String,
#[serde(default)]
pub session_type: SessionType,
#[serde(default)]
pub worker_id: Option<String>,
#[serde(default)]
pub user_email: Option<String>,
#[serde(default)]
pub last_activity: Option<i64>,
#[serde(default)]
pub idle_secs: Option<i64>,
#[serde(default)]
pub connected: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SessionListResponse {
pub sessions: Vec<String>,
#[serde(default)]
pub session_details: Vec<SessionDetail>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct RelaySessionInfo {
pub session_id: String,
pub connected: bool,
pub connected_at: i64,
#[serde(default)]
pub last_activity: Option<i64>,
#[serde(default)]
pub idle_secs: Option<i64>,
#[serde(default)]
pub user_email: Option<String>,
#[serde(default)]
pub tab_url: Option<String>,
#[serde(default)]
pub tab_title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct RelaySessionListResponse {
pub sessions: Vec<RelaySessionInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "level", rename_all = "snake_case")]
pub enum NetworkAccess {
None,
Trusted,
AllowedDomains { domains: Vec<String> },
Full,
}
impl Default for NetworkAccess {
fn default() -> Self { NetworkAccess::Trusted }
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct MountPoint {
pub storage_path: String,
pub container_path: String,
#[serde(default = "default_mount_max_size_mb")]
pub max_size_mb: u32,
}
fn default_mount_max_size_mb() -> u32 { 500 }
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct ShellCreateSessionRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub environment_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub memory_mb: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disk_mb: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cpu_cores: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout_secs: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub working_dir: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_mounts: Option<Vec<MountPoint>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_mounts: Option<Vec<MountPoint>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolDefinition>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tools_endpoint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_timeout_secs: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub setup_script: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_paths: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub network_access: Option<NetworkAccess>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub env_vars: Option<std::collections::HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ShellCreateSessionResponse {
pub session_id: String,
pub status: String,
#[serde(default)]
pub worker_id: Option<String>,
#[serde(default)]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub webhook_secret: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tools_injected: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ShellListItem {
pub shell_id: String,
pub status: String,
pub image: String,
pub created_at: String,
pub last_activity: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ShellListResponse {
pub shells: Vec<ShellListItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ShellTerminateResponse {
pub session_id: String,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ShellExecRequest {
pub session_id: String,
pub command: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout_secs: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub working_dir: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ShellExecResult {
#[serde(default)]
pub stdout: String,
#[serde(default)]
pub stderr: String,
#[serde(default)]
pub exit_code: Option<i32>,
#[serde(default)]
pub duration_ms: Option<u64>,
#[serde(default)]
pub timed_out: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ShellExecResponse {
pub session_id: String,
#[serde(flatten)]
pub result: ShellExecResult,
}