elizaos-plugin-browser 2.0.0

Browser automation plugin for elizaOS - enables AI agents to browse websites, interact with elements, and extract data
Documentation
use crate::types::ErrorCode;
use thiserror::Error;

#[derive(Error, Debug)]
pub struct BrowserError {
    pub code: ErrorCode,
    pub message: String,
    pub user_message: String,
    pub recoverable: bool,
    pub details: Option<serde_json::Value>,
}

impl std::fmt::Display for BrowserError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[{:?}] {}", self.code, self.message)
    }
}

impl BrowserError {
    pub fn new(
        message: impl Into<String>,
        code: ErrorCode,
        user_message: impl Into<String>,
        recoverable: bool,
    ) -> Self {
        Self {
            code,
            message: message.into(),
            user_message: user_message.into(),
            recoverable,
            details: None,
        }
    }

    pub fn with_details(mut self, details: serde_json::Value) -> Self {
        self.details = Some(details);
        self
    }
}

pub fn service_not_available() -> BrowserError {
    BrowserError::new(
        "Browser service is not available",
        ErrorCode::ServiceNotAvailable,
        "The browser automation service is not available. Please ensure the plugin is properly configured.",
        false,
    )
}

pub fn session_error(message: impl Into<String>) -> BrowserError {
    BrowserError::new(
        message,
        ErrorCode::SessionError,
        "There was an error with the browser session. Please try again.",
        true,
    )
}

pub fn navigation_error(url: &str, original_error: Option<&str>) -> BrowserError {
    let message = match original_error {
        Some(err) => format!("Failed to navigate to {}: {}", url, err),
        None => format!("Failed to navigate to {}", url),
    };

    BrowserError::new(
        message,
        ErrorCode::NavigationError,
        "I couldn't navigate to the requested page. Please check the URL and try again.",
        true,
    )
    .with_details(serde_json::json!({
        "url": url,
        "original_error": original_error,
    }))
}

pub fn action_error(action: &str, target: &str, original_error: Option<&str>) -> BrowserError {
    let message = match original_error {
        Some(err) => format!("Failed to {} on {}: {}", action, target, err),
        None => format!("Failed to {} on {}", action, target),
    };

    BrowserError::new(
        message,
        ErrorCode::ActionError,
        format!("I couldn't {} on the requested element. Please check if the element exists and try again.", action),
        true,
    )
    .with_details(serde_json::json!({
        "action": action,
        "target": target,
        "original_error": original_error,
    }))
}

pub fn security_error(message: impl Into<String>) -> BrowserError {
    BrowserError::new(
        message,
        ErrorCode::SecurityError,
        "This action was blocked for security reasons.",
        false,
    )
}

pub fn captcha_error(message: impl Into<String>) -> BrowserError {
    BrowserError::new(
        message,
        ErrorCode::CaptchaError,
        "Failed to solve the CAPTCHA. Please try again.",
        true,
    )
}

pub fn timeout_error(operation: &str, timeout_ms: u64) -> BrowserError {
    BrowserError::new(
        format!("{} timed out after {}ms", operation, timeout_ms),
        ErrorCode::TimeoutError,
        "The operation timed out. Please try again.",
        true,
    )
    .with_details(serde_json::json!({
        "operation": operation,
        "timeout_ms": timeout_ms,
    }))
}

pub fn no_url_found() -> BrowserError {
    BrowserError::new(
        "No URL found in message",
        ErrorCode::NoUrlFound,
        "I couldn't find a URL in your request. Please provide a valid URL to navigate to.",
        false,
    )
}

pub fn handle_browser_error<F>(error: &BrowserError, callback: Option<F>, action: Option<&str>)
where
    F: FnOnce(&str, bool),
{
    tracing::error!("Browser error [{}]: {}", error.code as u8, error.message);

    if let Some(cb) = callback {
        let message = match action {
            Some(a) => format!(
                "I encountered an error while trying to {}. Please try again.",
                a
            ),
            None => error.user_message.clone(),
        };
        cb(&message, true);
    }
}