Skip to main content

oxios_kernel/kernel_handle/
browser_api.rs

1//! Browser API — browser backend facade.
2//!
3//! When the `browser` feature is enabled, lazily initializes an
4//! `Arc<oxibrowser_core::Browser>` on first use from an async context.
5//! This avoids the `block_on` panic that occurs when constructing
6//! inside an existing tokio runtime.
7
8#[cfg(feature = "browser")]
9use std::sync::Arc;
10
11/// Browser management system calls.
12///
13/// Wraps the embedded OxiBrowser engine. `BrowserTool` borrows the `Browser`
14/// via `browser()` for agent tool calls.
15///
16/// Initialization is **lazy**: the browser engine is only created on the
17/// first call to [`browser()`](Self::browser) from an async context.
18/// [`from_config()`](Self::from_config) and [`Default::default()`] are
19/// cheap and never panic.
20#[cfg(feature = "browser")]
21pub struct BrowserApi {
22    inner: tokio::sync::OnceCell<Arc<oxibrowser_core::Browser>>,
23    config: Option<oxibrowser_core::BrowserConfig>,
24}
25
26#[cfg(feature = "browser")]
27impl Clone for BrowserApi {
28    fn clone(&self) -> Self {
29        Self {
30            inner: self.inner.clone(),
31            config: self.config.clone(),
32        }
33    }
34}
35
36#[cfg(feature = "browser")]
37impl BrowserApi {
38    /// Create a new BrowserApi that will lazily initialize a Browser from config.
39    ///
40    /// This is cheap — no I/O or runtime calls happen here.
41    pub fn from_config(config: &oxibrowser_core::BrowserConfig) -> Self {
42        Self {
43            inner: tokio::sync::OnceCell::new(),
44            config: Some(config.clone()),
45        }
46    }
47
48    /// Create a new BrowserApi from an already-initialized Browser.
49    pub fn new(browser: Arc<oxibrowser_core::Browser>) -> Self {
50        let cell = tokio::sync::OnceCell::new();
51        // SAFETY: cell was just created, it's empty.
52        match cell.set(browser) {
53            Ok(()) => {}
54            Err(_) => unreachable!("OnceCell was just created"),
55        }
56        Self {
57            inner: cell,
58            config: None,
59        }
60    }
61
62    /// Get the browser engine, initializing it lazily if needed.
63    ///
64    /// Must be called from an async context (tokio runtime active).
65    /// Returns `Err` if no config was provided and the browser was not
66    /// pre-initialized.
67    pub async fn browser(&self) -> anyhow::Result<&Arc<oxibrowser_core::Browser>> {
68        self.inner
69            .get_or_try_init(|| async {
70                let config = self.config.as_ref().ok_or_else(|| {
71                    anyhow::anyhow!("BrowserApi has no config and was not pre-initialized")
72                })?;
73                let browser = oxibrowser_core::Browser::new(config.clone())
74                    .await
75                    .map_err(|e| anyhow::anyhow!("Failed to initialize browser engine: {}", e))?;
76                Ok(Arc::new(browser))
77            })
78            .await
79    }
80
81    /// Shut down the browser engine (if it was initialized).
82    pub async fn shutdown(&self) -> anyhow::Result<()> {
83        if let Some(browser) = self.inner.get() {
84            browser.close().await?;
85        }
86        Ok(())
87    }
88}
89
90/// Default — creates an uninitialized BrowserApi.
91///
92/// This is used by `from_subsystems` and when `browser.enabled = false`.
93/// It will return an error on [`browser()`](Self::browser) if no config
94/// is ever set, but will **not panic**.
95#[cfg(feature = "browser")]
96impl Default for BrowserApi {
97    fn default() -> Self {
98        Self {
99            inner: tokio::sync::OnceCell::new(),
100            config: None,
101        }
102    }
103}
104
105/// Zero-sized browser placeholder when the `browser` feature is disabled.
106#[cfg(not(feature = "browser"))]
107pub struct BrowserApi;
108
109#[cfg(not(feature = "browser"))]
110impl Default for BrowserApi {
111    fn default() -> Self {
112        BrowserApi
113    }
114}