browsing 0.1.7

Browser automation: navigate, click, extract, screenshot. Standalone browser control via CDP.
Documentation
//! Extract action handler (page content extraction)

use crate::agent::views::ActionResult;
use crate::error::{BrowsingError, Result};
use crate::tools::views::ActionModel;
use crate::traits::BrowserClient;
use tracing::info;

/// Execute extract action: get page content as structured data.
pub async fn handle_extract(
    action: ActionModel,
    browser_session: &mut dyn BrowserClient,
) -> Result<ActionResult> {
    let query = action
        .params
        .get("query")
        .and_then(|v| v.as_str())
        .ok_or_else(|| BrowsingError::Tool("Missing 'query' parameter".to_string()))?;

    let start_from_char = action
        .params
        .get("start_from_char")
        .and_then(|v| v.as_u64())
        .unwrap_or(0) as usize;

    let current_url = browser_session
        .get_current_url()
        .await
        .unwrap_or_else(|_| "unknown".to_string());

    let page = browser_session.get_page()?;
    let content_script = r#"
        (function() {
            const body = document.body || document.documentElement;
            return body.innerText || body.textContent || '';
        })()
    "#;

    let content = page.evaluate(content_script).await.unwrap_or_default();
    let content_str = content.as_str();

    let final_content = if start_from_char > 0 && start_from_char < content_str.len() {
        &content_str[start_from_char..]
    } else {
        content_str
    };

    let max_chars = 100_000;
    let truncated = final_content.len() > max_chars;
    let final_content = if truncated {
        &final_content[..max_chars]
    } else {
        final_content
    };

    let extracted_content = format!(
        "<url>\n{}\n</url>\n<query>\n{}\n</query>\n<result>\n{}\n</result>",
        current_url,
        query,
        if truncated {
            format!(
                "{}... (truncated)",
                &final_content[..1000.min(final_content.len())]
            )
        } else {
            final_content.to_string()
        }
    );

    info!("📄 Extracted content for query: {}", query);
    Ok(ActionResult {
        extracted_content: Some(extracted_content.clone()),
        long_term_memory: Some(format!(
            "Extracted content for query: {query} ({} chars)",
            extracted_content.len()
        )),
        ..Default::default()
    })
}