pub mod agent;
mod browser;
pub mod browser_setup;
pub mod research;
pub mod kromekover;
mod manager;
pub mod page_enhancer;
pub mod page_extractor;
mod tools;
mod utils;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default = "default_temperature")]
pub temperature: f64,
#[serde(default = "default_max_tokens")]
pub max_tokens: u64,
#[serde(default = "default_max_steps")]
pub max_steps: usize,
#[serde(default = "default_search_engine")]
pub search_engine: String,
#[serde(default)]
pub browser: BrowserConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserConfig {
#[serde(default = "default_headless")]
pub headless: bool,
#[serde(default = "default_disable_security")]
pub disable_security: bool,
#[serde(default)]
pub window: WindowConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowConfig {
#[serde(default = "default_window_width")]
pub width: u32,
#[serde(default = "default_window_height")]
pub height: u32,
}
fn default_temperature() -> f64 {
0.7
}
fn default_max_tokens() -> u64 {
2048
}
fn default_max_steps() -> usize {
10
}
fn default_search_engine() -> String {
"google".to_string()
}
fn default_headless() -> bool {
true
}
fn default_disable_security() -> bool {
false }
fn default_window_width() -> u32 {
1280
}
fn default_window_height() -> u32 {
720
}
impl Default for Config {
fn default() -> Self {
Self {
temperature: default_temperature(),
max_tokens: default_max_tokens(),
max_steps: default_max_steps(),
search_engine: default_search_engine(),
browser: BrowserConfig::default(),
}
}
}
impl Default for BrowserConfig {
fn default() -> Self {
Self {
headless: default_headless(),
disable_security: default_disable_security(),
window: WindowConfig::default(),
}
}
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
width: default_window_width(),
height: default_window_height(),
}
}
}
pub fn load_yaml_config() -> anyhow::Result<Config> {
let config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config.yaml");
if config_path.exists() {
let contents = fs::read_to_string(&config_path)?;
let config: Config = serde_yaml::from_str(&contents)?;
Ok(config)
} else {
Ok(Config::default())
}
}
pub use browser::{
BrowserContext, BrowserError, BrowserResult, BrowserWrapper, download_managed_browser,
find_browser_executable, launch_browser,
};
pub use manager::BrowserManager;
pub use tools::{
BrowserAgentTool, BrowserClickTool, BrowserExtractTextTool, BrowserNavigateTool,
BrowserResearchTool, BrowserScreenshotTool, BrowserScrollTool, BrowserTypeTextTool,
};
struct BrowserManagerWrapper(std::sync::Arc<crate::BrowserManager>);
impl kodegen_server_http::ShutdownHook for BrowserManagerWrapper {
fn shutdown(&self) -> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>> + Send + '_>> {
let manager = self.0.clone();
Box::pin(async move {
manager.shutdown().await
})
}
}
pub async fn start_server(
addr: std::net::SocketAddr,
tls_cert: Option<std::path::PathBuf>,
tls_key: Option<std::path::PathBuf>,
) -> anyhow::Result<kodegen_server_http::ServerHandle> {
let listener = tokio::net::TcpListener::bind(addr).await
.map_err(|e| anyhow::anyhow!("Failed to bind to {}: {}", addr, e))?;
let tls_config = match (tls_cert, tls_key) {
(Some(cert), Some(key)) => Some((cert, key)),
_ => None,
};
start_server_with_listener(listener, tls_config).await
}
pub async fn start_server_with_listener(
listener: tokio::net::TcpListener,
tls_config: Option<(std::path::PathBuf, std::path::PathBuf)>,
) -> anyhow::Result<kodegen_server_http::ServerHandle> {
use kodegen_server_http::{ServerBuilder, Managers, RouterSet, register_tool};
use rmcp::handler::server::router::{prompt::PromptRouter, tool::ToolRouter};
let actual_port = listener.local_addr()?.port();
let mut builder = ServerBuilder::new()
.category(kodegen_config::CATEGORY_BROWSER)
.register_tools(move || async move {
let mut tool_router = ToolRouter::new();
let mut prompt_router = PromptRouter::new();
let managers = Managers::new();
let server_url = format!("http://127.0.0.1:{}/mcp", actual_port);
let browser_manager = crate::BrowserManager::global();
managers.register(BrowserManagerWrapper(browser_manager.clone())).await;
(tool_router, prompt_router) = register_tool(
tool_router,
prompt_router,
crate::BrowserNavigateTool::new(browser_manager.clone()),
);
(tool_router, prompt_router) = register_tool(
tool_router,
prompt_router,
crate::BrowserClickTool::new(browser_manager.clone()),
);
(tool_router, prompt_router) = register_tool(
tool_router,
prompt_router,
crate::BrowserTypeTextTool::new(browser_manager.clone()),
);
(tool_router, prompt_router) = register_tool(
tool_router,
prompt_router,
crate::BrowserScreenshotTool::new(browser_manager.clone()),
);
(tool_router, prompt_router) = register_tool(
tool_router,
prompt_router,
crate::BrowserExtractTextTool::new(browser_manager.clone()),
);
(tool_router, prompt_router) = register_tool(
tool_router,
prompt_router,
crate::BrowserScrollTool::new(browser_manager.clone()),
);
(tool_router, prompt_router) = register_tool(
tool_router,
prompt_router,
crate::BrowserAgentTool::new(browser_manager.clone(), server_url.clone()),
);
(tool_router, prompt_router) = register_tool(
tool_router,
prompt_router,
crate::BrowserResearchTool::new(browser_manager.clone()),
);
Ok(RouterSet::new(tool_router, prompt_router, managers))
})
.with_listener(listener);
if let Some((cert, key)) = tls_config {
builder = builder.with_tls_config(cert, key);
}
builder.serve().await
}