use agent_diva_tools::{Tool, ToolRegistry};
use agent_diva_core::security::{SecurityConfig, SecurityPolicy};
use agent_diva_core::cron::CronService;
#[cfg(feature = "files")]
use agent_diva_files::FileManager;
use std::sync::Arc;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct BuiltInToolsConfig {
pub filesystem: bool,
pub shell: bool,
pub web: bool,
pub spawn: bool,
pub cron: bool,
pub mcp: bool,
pub attachment: bool,
}
impl Default for BuiltInToolsConfig {
fn default() -> Self {
Self {
filesystem: true,
shell: true,
web: true,
spawn: true,
cron: false, mcp: true,
attachment: true,
}
}
}
impl BuiltInToolsConfig {
pub fn minimal() -> Self {
Self {
filesystem: true,
shell: false,
web: false,
spawn: false,
cron: false,
mcp: false,
attachment: false,
}
}
pub fn none() -> Self {
Self {
filesystem: false,
shell: false,
web: false,
spawn: false,
cron: false,
mcp: false,
attachment: false,
}
}
pub fn all() -> Self {
Self {
filesystem: true,
shell: true,
web: true,
spawn: true,
cron: true,
mcp: true,
attachment: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ShellToolConfig {
pub timeout_secs: u64,
pub working_dir: Option<PathBuf>,
pub restrict_to_workspace: bool,
}
impl Default for ShellToolConfig {
fn default() -> Self {
Self {
timeout_secs: 60,
working_dir: None,
restrict_to_workspace: true,
}
}
}
#[derive(Debug, Clone)]
pub struct WebToolConfig {
pub search_enabled: bool,
pub fetch_enabled: bool,
pub search_provider: String,
pub search_api_key: Option<String>,
pub max_results: u32,
}
impl Default for WebToolConfig {
fn default() -> Self {
Self {
search_enabled: true,
fetch_enabled: true,
search_provider: "bocha".to_string(),
search_api_key: None,
max_results: 5,
}
}
}
pub struct ToolAssembly {
workspace: PathBuf,
builtin_config: BuiltInToolsConfig,
shell_config: ShellToolConfig,
web_config: WebToolConfig,
security_config: SecurityConfig,
custom_tools: Vec<Arc<dyn Tool>>,
mcp_servers: std::collections::HashMap<String, agent_diva_core::config::MCPServerConfig>,
cron_service: Option<Arc<CronService>>,
subagent_spawner: Option<Arc<dyn SubagentSpawner>>,
#[cfg(feature = "files")]
file_manager: Option<Arc<FileManager>>,
}
#[async_trait::async_trait]
pub trait SubagentSpawner: Send + Sync {
async fn spawn(
&self,
task: String,
label: Option<String>,
channel: String,
chat_id: String,
) -> Result<String, agent_diva_tools::ToolError>;
}
impl ToolAssembly {
pub fn new(workspace: PathBuf) -> Self {
Self {
workspace,
builtin_config: BuiltInToolsConfig::default(),
shell_config: ShellToolConfig::default(),
web_config: WebToolConfig::default(),
security_config: SecurityConfig::default(),
custom_tools: Vec::new(),
mcp_servers: std::collections::HashMap::new(),
cron_service: None,
subagent_spawner: None,
#[cfg(feature = "files")]
file_manager: None,
}
}
#[cfg(feature = "files")]
pub fn with_file_manager(mut self, manager: Arc<FileManager>) -> Self {
self.file_manager = Some(manager);
self
}
pub fn builtin(mut self, config: BuiltInToolsConfig) -> Self {
self.builtin_config = config;
self
}
pub fn filesystem(mut self, enabled: bool) -> Self {
self.builtin_config.filesystem = enabled;
self
}
pub fn shell(mut self, enabled: bool) -> Self {
self.builtin_config.shell = enabled;
self
}
pub fn shell_config(mut self, config: ShellToolConfig) -> Self {
self.shell_config = config;
self.builtin_config.shell = true;
self
}
pub fn web(mut self, enabled: bool) -> Self {
self.builtin_config.web = enabled;
self
}
pub fn web_config(mut self, config: WebToolConfig) -> Self {
self.web_config = config;
self.builtin_config.web = true;
self
}
pub fn spawn(mut self, enabled: bool) -> Self {
self.builtin_config.spawn = enabled;
self
}
pub fn with_subagent_spawner(mut self, spawner: Arc<dyn SubagentSpawner>) -> Self {
self.subagent_spawner = Some(spawner);
self.builtin_config.spawn = true;
self
}
pub fn cron(mut self, enabled: bool) -> Self {
self.builtin_config.cron = enabled;
self
}
pub fn with_cron_service(mut self, service: Arc<CronService>) -> Self {
self.cron_service = Some(service);
self.builtin_config.cron = true;
self
}
pub fn mcp(mut self, enabled: bool) -> Self {
self.builtin_config.mcp = enabled;
self
}
pub fn add_mcp_server(mut self, name: String, config: agent_diva_core::config::MCPServerConfig) -> Self {
self.mcp_servers.insert(name, config);
self.builtin_config.mcp = true;
self
}
pub fn mcp_servers(mut self, servers: std::collections::HashMap<String, agent_diva_core::config::MCPServerConfig>) -> Self {
self.mcp_servers = servers;
self
}
pub fn attachment(mut self, enabled: bool) -> Self {
self.builtin_config.attachment = enabled;
self
}
pub fn security(mut self, config: SecurityConfig) -> Self {
self.security_config = config;
self
}
pub fn restrict_to_workspace(mut self, restrict: bool) -> Self {
self.security_config.workspace_only = restrict;
self.shell_config.restrict_to_workspace = restrict;
self
}
pub fn with_tool(mut self, tool: Arc<dyn Tool>) -> Self {
self.custom_tools.push(tool);
self
}
pub fn with_tools(mut self, tools: Vec<Arc<dyn Tool>>) -> Self {
self.custom_tools.extend(tools);
self
}
pub fn build(self) -> ToolRegistry {
let mut registry = ToolRegistry::new();
if self.builtin_config.filesystem {
let security = Arc::new(SecurityPolicy::with_config(
self.workspace.clone(),
self.security_config.clone(),
));
registry.register(Arc::new(agent_diva_tools::ReadFileTool::new(security.clone())));
registry.register(Arc::new(agent_diva_tools::WriteFileTool::new(security.clone())));
registry.register(Arc::new(agent_diva_tools::EditFileTool::new(security.clone())));
registry.register(Arc::new(agent_diva_tools::ListDirTool::new(security)));
}
if self.builtin_config.shell {
registry.register(Arc::new(agent_diva_tools::ExecTool::with_config(
self.shell_config.timeout_secs,
self.shell_config.working_dir.clone().or(Some(self.workspace.clone())),
self.shell_config.restrict_to_workspace,
)));
}
if self.builtin_config.web {
if self.web_config.search_enabled {
registry.register(Arc::new(agent_diva_tools::WebSearchTool::new(
self.web_config.search_api_key.clone(),
)));
}
if self.web_config.fetch_enabled {
registry.register(Arc::new(agent_diva_tools::WebFetchTool::new()));
}
}
if self.builtin_config.spawn {
if let Some(spawner) = self.subagent_spawner {
registry.register(Arc::new(agent_diva_tools::SpawnTool::new(
move |task, label, channel, chat_id| {
let spawner = spawner.clone();
async move {
spawner.spawn(task, label, channel, chat_id).await
}
},
)));
}
}
if self.builtin_config.cron {
if let Some(cron_service) = self.cron_service {
registry.register(Arc::new(agent_diva_tools::CronTool::new(cron_service)));
}
}
if self.builtin_config.mcp && !self.mcp_servers.is_empty() {
for mcp_tool in agent_diva_tools::load_mcp_tools_sync(&self.mcp_servers) {
registry.register(mcp_tool);
}
}
#[cfg(feature = "files")]
if self.builtin_config.attachment {
if let Some(fm) = self.file_manager.clone() {
registry.register(Arc::new(agent_diva_tools::ReadAttachmentTool::new(fm)));
}
}
for tool in self.custom_tools {
registry.register(tool);
}
registry
}
pub fn build_for_agent_loop(self) -> (ToolRegistry, AgentLoopToolConfig) {
let builtin_config = self.builtin_config.clone();
let shell_config = self.shell_config.clone();
let web_config = self.web_config.clone();
let security_config = self.security_config.clone();
let mcp_servers = self.mcp_servers.clone();
let cron_service = self.cron_service.clone();
let subagent_spawner = self.subagent_spawner.clone();
let registry = self.build();
let loop_config = AgentLoopToolConfig {
builtin_config,
shell_config,
web_config,
security_config,
mcp_servers,
cron_service,
subagent_spawner,
};
(registry, loop_config)
}
}
#[derive(Clone)]
pub struct AgentLoopToolConfig {
pub builtin_config: BuiltInToolsConfig,
pub shell_config: ShellToolConfig,
pub web_config: WebToolConfig,
pub security_config: SecurityConfig,
pub mcp_servers: std::collections::HashMap<String, agent_diva_core::config::MCPServerConfig>,
pub cron_service: Option<Arc<CronService>>,
pub subagent_spawner: Option<Arc<dyn SubagentSpawner>>,
}
impl Default for AgentLoopToolConfig {
fn default() -> Self {
Self {
builtin_config: BuiltInToolsConfig::default(),
shell_config: ShellToolConfig::default(),
web_config: WebToolConfig::default(),
security_config: SecurityConfig::default(),
mcp_servers: std::collections::HashMap::new(),
cron_service: None,
subagent_spawner: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_assembly_minimal() {
let registry = ToolAssembly::new(PathBuf::from("/tmp/test"))
.builtin(BuiltInToolsConfig::minimal())
.build();
assert!(registry.has("read_file"));
assert!(registry.has("write_file"));
assert!(!registry.has("exec"));
assert!(!registry.has("web_search"));
}
#[test]
fn test_tool_assembly_none() {
let registry = ToolAssembly::new(PathBuf::from("/tmp/test"))
.builtin(BuiltInToolsConfig::none())
.build();
assert!(registry.is_empty());
}
#[test]
fn test_tool_assembly_default() {
let registry = ToolAssembly::new(PathBuf::from("/tmp/test"))
.build();
assert!(registry.has("read_file"));
assert!(registry.has("exec"));
assert!(registry.has("web_search"));
assert!(!registry.has("spawn"));
assert!(!registry.has("cron")); }
#[test]
fn test_tool_assembly_custom_config() {
let registry = ToolAssembly::new(PathBuf::from("/tmp/test"))
.filesystem(true)
.shell(false)
.web(true)
.spawn(false)
.build();
assert!(registry.has("read_file"));
assert!(!registry.has("exec"));
assert!(registry.has("web_search"));
assert!(!registry.has("spawn"));
}
}