browsr-types 0.4.0

Shared data models and schemas for Browsr browser automation flows.
Documentation
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CommandPoint {
    pub x: f64,
    pub y: f64,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum CommandType {
    Action,
    Extraction,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PageContentKind {
    Markdown,
    Html,
    Json,
}

impl Default for PageContentKind {
    fn default() -> Self {
        PageContentKind::Markdown
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "command", rename_all = "snake_case", content = "data")]
pub enum Commands {
    ToggleClickOverlay {
        enabled: bool,
    },
    ToggleBoundingBoxes {
        enabled: bool,
        selector: Option<String>,
        limit: Option<usize>,
        include_html: Option<bool>,
    },
    // Navigation commands
    NavigateTo {
        url: String,
    },
    Refresh,
    WaitForNavigation {
        timeout_ms: Option<u64>,
    },
    WaitForElement {
        selector: String,
        timeout_ms: Option<u64>,
        visible_only: Option<bool>,
    },

    // Element interaction commands
    Click {
        selector: String,
    },
    ClickAt {
        x: f64,
        y: f64,
    },
    Clear {
        selector: String,
    },
    PressKey {
        selector: String,
        key: String,
    },

    // Content extraction
    GetContent {
        #[serde(default, skip_serializing_if = "Option::is_none")]
        selector: Option<String>,
        #[serde(default)]
        kind: Option<PageContentKind>,
    },
    GetText {
        selector: String,
    },
    GetAttribute {
        selector: String,
        attribute: String,
    },

    // Page information
    GetTitle,
    ExtractStructuredContent {
        query: String,
        /// JSON schema for structured extraction - can be a string or a JSON object
        #[serde(default, skip_serializing_if = "Option::is_none")]
        schema: Option<serde_json::Value>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        max_chars: Option<usize>,
    },

    // JavaScript execution
    Evaluate {
        expression: String,
    },
    GetBoundingBoxes {
        selector: String,
        limit: Option<usize>,
        include_html: Option<bool>,
    },
    InspectElement {
        selector: String,
    },

    // Mouse and keyboard
    /// Scroll to absolute coordinates. Both x and y are optional.
    /// If x is omitted, horizontal scroll position is unchanged.
    /// If y is omitted, vertical scroll position is unchanged.
    ScrollTo {
        #[serde(default)]
        x: Option<f64>,
        #[serde(default)]
        y: Option<f64>,
    },
    /// Move mouse to coordinates with optional human-like interpolation.
    /// If `steps` is provided, the mouse will move through intermediate points
    /// with small delays to simulate human movement.
    MoveMouseTo {
        x: f64,
        y: f64,
        /// Number of intermediate steps for human-like movement. Default is 1 (instant).
        /// Higher values (10-25) create smoother, more human-like paths.
        steps: Option<u32>,
    },
    Drag {
        from: CommandPoint,
        to: CommandPoint,
        modifiers: Option<i64>,
    },
    ScrollIntoView {
        selector: String,
    },

    // Screenshot and capture
    Screenshot {
        full_page: Option<bool>,
        path: Option<String>,
    },

    // Element-focused interactions
    ClickAdvanced {
        selector: String,
        button: Option<String>,
        click_count: Option<u8>,
        modifiers: Option<i64>,
    },
    TypeText {
        selector: String,
        text: String,
        clear: Option<bool>,
    },
    Hover {
        selector: String,
    },
    Focus {
        selector: String,
    },
    Check {
        selector: String,
    },
    SelectOption {
        selector: String,
        values: Vec<String>,
    },
    DragTo {
        selector: String,
        target_selector: Option<String>,
        source_position: Option<CommandPoint>,
        target_position: Option<CommandPoint>,
        modifiers: Option<i64>,
    },
    EvaluateOnElement {
        selector: String,
        expression: String,
    },
    GetElementBoundingBox {
        selector: String,
    },
    ElementScreenshot {
        selector: String,
        format: Option<String>,
        quality: Option<u8>,
    },
    GetBasicInfo {
        selector: String,
    },

    // Cookie commands
    /// Set a single cookie. For cookies with a domain, you may need to navigate to a matching URL first.
    SetCookie {
        name: String,
        value: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        domain: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        path: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        url: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        secure: Option<bool>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        http_only: Option<bool>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        same_site: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        expires: Option<f64>,
    },
    /// Set multiple cookies at once from a semicolon-separated string (e.g., "name1=value1; name2=value2")
    SetCookies {
        /// Semicolon-separated cookie string
        cookies: String,
        /// Domain to apply to all cookies
        domain: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        path: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        secure: Option<bool>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        http_only: Option<bool>,
    },
    /// Get all cookies for the current page
    GetCookies,
    /// Delete a cookie by name
    DeleteCookie {
        name: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        domain: Option<String>,
    },

    // LocalStorage commands (operates on current page's domain)
    /// Get all localStorage items for the current page
    GetLocalStorage,
    /// Set localStorage items on the current page
    SetLocalStorage {
        /// Key-value pairs to set
        items: Vec<LocalStorageItem>,
    },
    /// Clear all localStorage for the current page
    ClearLocalStorage,

    // SessionStorage commands (operates on current page's domain)
    /// Get all sessionStorage items for the current page
    GetSessionStorage,
    /// Set sessionStorage items on the current page
    SetSessionStorage {
        /// Key-value pairs to set
        items: Vec<LocalStorageItem>,
    },
    /// Clear all sessionStorage for the current page
    ClearSessionStorage,

    /// Capture complete browser state (cookies, localStorage, sessionStorage, current URL)
    CaptureState,
}

/// A single localStorage or sessionStorage item
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct LocalStorageItem {
    pub key: String,
    pub value: String,
}

#[derive(serde::Deserialize)]
pub struct StepPayload {
    pub commands: Vec<Commands>,
    pub thinking: Option<String>,
    pub evaluation_previous_goal: Option<String>,
    pub memory: Option<String>,
    pub next_goal: Option<String>,
}

impl Commands {
    pub fn command_type(&self) -> CommandType {
        match self {
            Commands::GetContent { .. }
            | Commands::GetText { .. }
            | Commands::GetAttribute { .. }
            | Commands::GetTitle
            | Commands::ExtractStructuredContent { .. }
            | Commands::Screenshot { .. }
            | Commands::ElementScreenshot { .. }
            | Commands::GetElementBoundingBox { .. }
            | Commands::Evaluate { .. }
            | Commands::EvaluateOnElement { .. }
            | Commands::GetBoundingBoxes { .. }
            | Commands::InspectElement { .. }
            | Commands::GetBasicInfo { .. }
            | Commands::GetCookies
            | Commands::GetLocalStorage
            | Commands::GetSessionStorage
            | Commands::CaptureState => CommandType::Extraction,
            _ => CommandType::Action,
        }
    }
}