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}