use std::sync::Arc;
use crate::error::KovaError;
use crate::mcp::McpClient;
use crate::mcp::tool::McpTool;
use crate::memory::MemoryStore;
use crate::memory::in_memory::InMemoryStore;
use crate::models::InferenceConfig;
use crate::provider::LlmProvider;
use crate::streaming::StreamingHandler;
use crate::tool::Tool;
use crate::tool::ToolLifecycleHook;
use crate::tool::approval::ToolApprovalHandler;
use crate::tool::registry::ToolRegistry;
use super::Agent;
pub struct AgentBuilder {
provider: Option<Arc<dyn LlmProvider>>,
tools: Vec<Arc<dyn Tool>>,
tool_registry: ToolRegistry,
registry_explicitly_set: bool,
memory: Option<Arc<dyn MemoryStore>>,
system_prompt: Option<String>,
max_iterations: usize,
max_concurrent_tools: usize,
inference_config: InferenceConfig,
streaming_handler: Option<Arc<dyn StreamingHandler>>,
approval_handler: Option<Arc<dyn ToolApprovalHandler>>,
lifecycle_hook: Option<Arc<dyn ToolLifecycleHook>>,
}
impl AgentBuilder {
pub fn new() -> Self {
Self {
provider: None,
tools: Vec::new(),
tool_registry: ToolRegistry::new(),
registry_explicitly_set: false,
memory: None,
system_prompt: None,
max_iterations: 10,
max_concurrent_tools: 10,
inference_config: InferenceConfig::default(),
streaming_handler: None,
approval_handler: None,
lifecycle_hook: None,
}
}
pub fn provider(mut self, provider: Arc<dyn LlmProvider>) -> Self {
self.provider = Some(provider);
self
}
pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = Some(prompt.into());
self
}
pub fn max_iterations(mut self, n: usize) -> Self {
self.max_iterations = n;
self
}
pub fn max_concurrent_tools(mut self, n: usize) -> Self {
self.max_concurrent_tools = n;
self
}
pub fn inference_config(mut self, config: InferenceConfig) -> Self {
self.inference_config = config;
self
}
pub fn tool(mut self, tool: Arc<dyn Tool>) -> Self {
self.tools.push(tool);
self
}
pub fn tool_registry(mut self, registry: ToolRegistry) -> Self {
self.tool_registry = registry;
self.registry_explicitly_set = true;
self
}
pub fn memory(mut self, memory: Arc<dyn MemoryStore>) -> Self {
self.memory = Some(memory);
self
}
pub fn streaming_handler(mut self, handler: Arc<dyn StreamingHandler>) -> Self {
self.streaming_handler = Some(handler);
self
}
pub async fn mcp_client(
mut self,
client: Arc<McpClient>,
server_name: &str,
) -> Result<Self, KovaError> {
let tool_defs = client.tools_list().await?;
for def in tool_defs {
self.tools.push(Arc::new(McpTool::new(
def,
Arc::clone(&client),
server_name,
)));
}
Ok(self)
}
pub fn with_approval_handler(mut self, handler: Arc<dyn ToolApprovalHandler>) -> Self {
self.approval_handler = Some(handler);
self
}
pub fn with_lifecycle_hook(mut self, hook: Arc<dyn ToolLifecycleHook>) -> Self {
self.lifecycle_hook = Some(hook);
self
}
pub fn build(self) -> Result<Agent, KovaError> {
let provider = self
.provider
.ok_or_else(|| KovaError::Build("LlmProvider is required".into()))?;
if self.registry_explicitly_set && !self.tools.is_empty() {
return Err(KovaError::Build(
"Use either .tool() or .tool_registry(), not both".into(),
));
}
let tool_registry = if !self.tools.is_empty() {
ToolRegistry::from_tools(self.tools)
} else {
self.tool_registry
};
let memory = self
.memory
.unwrap_or_else(|| Arc::new(InMemoryStore::new()));
Ok(Agent {
provider,
tool_registry,
memory,
system_prompt: self.system_prompt,
max_iterations: self.max_iterations,
max_concurrent_tools: self.max_concurrent_tools,
inference_config: self.inference_config,
streaming_handler: self.streaming_handler,
approval_handler: self.approval_handler,
lifecycle_hook: self.lifecycle_hook,
})
}
}
impl Default for AgentBuilder {
fn default() -> Self {
Self::new()
}
}