use rucora_core::{
error::ToolError,
tool::{Tool, ToolCategory},
};
use async_trait::async_trait;
use serde_json::{Value, json};
pub struct BrowserOpenTool;
impl BrowserOpenTool {
pub fn new() -> Self {
Self
}
fn validate_url(&self, url: &str) -> Result<String, ToolError> {
let url = url.trim();
if url.is_empty() {
return Err(ToolError::Message("URL 不能为空".to_string()));
}
if url.chars().any(char::is_whitespace) {
return Err(ToolError::Message("URL 不能包含空白字符".to_string()));
}
if !url.starts_with("https://") {
return Err(ToolError::Message("只允许 https:// URL".to_string()));
}
let host = url
.trim_start_matches("https://")
.split('/')
.next()
.unwrap_or("");
if host.starts_with("localhost")
|| host.starts_with("127.")
|| host.starts_with("192.168.")
|| host.starts_with("10.")
|| host.starts_with("172.")
{
return Err(ToolError::Message(format!(
"阻止访问本地/私有主机: {host}"
)));
}
Ok(url.to_string())
}
}
impl Default for BrowserOpenTool {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Tool for BrowserOpenTool {
fn name(&self) -> &str {
"browser_open"
}
fn description(&self) -> Option<&str> {
Some("在系统默认浏览器中打开 HTTPS URL")
}
fn categories(&self) -> &'static [ToolCategory] {
&[ToolCategory::Browser]
}
fn input_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "要打开的 HTTPS URL"
}
},
"required": ["url"]
})
}
async fn call(&self, input: Value) -> Result<Value, ToolError> {
let url = input
.get("url")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::Message("缺少必需的 'url' 字段".to_string()))?;
let validated_url = self.validate_url(url)?;
#[cfg(target_os = "windows")]
{
tokio::process::Command::new("cmd")
.args(["/C", "start", "", &validated_url])
.spawn()
.map_err(|e| ToolError::Message(format!("打开浏览器失败: {e}")))?;
}
#[cfg(target_os = "macos")]
{
tokio::process::Command::new("open")
.arg(&validated_url)
.spawn()
.map_err(|e| ToolError::Message(format!("打开浏览器失败: {}", e)))?;
}
#[cfg(target_os = "linux")]
{
tokio::process::Command::new("xdg-open")
.arg(&validated_url)
.spawn()
.map_err(|e| ToolError::Message(format!("打开浏览器失败: {}", e)))?;
}
Ok(json!({
"success": true,
"url": validated_url,
"message": "已在系统浏览器中打开 URL"
}))
}
}