use std::sync::Arc;
use machi_tools::{SharedTool, ToolRegistry};
use machi_types::{ErrorCode, MachiError};
use crate::definition::{AgentDefinition, CompletionRequirement, Instructions};
use crate::instance::Agent;
#[derive(Default)]
pub struct AgentBuilder {
definition: Option<AgentDefinition>,
tools: Vec<SharedTool>,
}
impl std::fmt::Debug for AgentBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AgentBuilder")
.field("definition", &self.definition)
.field("tools", &self.tools.len())
.finish()
}
}
impl AgentBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_definition(definition: AgentDefinition) -> Self {
Self {
definition: Some(definition),
tools: Vec::new(),
}
}
#[must_use]
pub fn named(name: impl Into<String>) -> Self {
Self {
definition: Some(AgentDefinition::new(name)),
tools: Vec::new(),
}
}
#[must_use]
pub fn instructions(mut self, instructions: impl Into<Instructions>) -> Self {
if let Some(def) = &mut self.definition {
def.instructions = instructions.into();
}
self
}
#[must_use]
pub fn model(mut self, model: impl Into<String>) -> Self {
if let Some(def) = &mut self.definition {
def.model = model.into();
}
self
}
#[must_use]
pub fn description(mut self, description: impl Into<String>) -> Self {
if let Some(def) = &mut self.definition {
def.description = description.into();
}
self
}
#[must_use]
pub fn max_steps(mut self, max_steps: usize) -> Self {
if let Some(def) = &mut self.definition {
def.max_steps = max_steps;
}
self
}
#[must_use]
pub fn tools(mut self, tools: Vec<SharedTool>) -> Self {
self.tools = tools;
self
}
#[must_use]
pub fn completion(mut self, requirement: CompletionRequirement) -> Self {
if let Some(def) = &mut self.definition {
def.completion = Some(requirement);
}
self
}
#[must_use]
pub fn output_schema(mut self, schema: serde_json::Value) -> Self {
if let Some(def) = &mut self.definition {
def.output_schema = Some(schema);
}
self
}
pub fn build(self) -> Result<Agent, MachiError> {
let definition = self.definition.ok_or_else(|| {
MachiError::new(ErrorCode::AgentBuild, "agent definition is required")
})?;
definition.validate()?;
let filtered: Vec<SharedTool> = self
.tools
.into_iter()
.filter(|t| definition.tools.admits(t.name()))
.collect();
let system_prompt = definition.instructions.resolve();
let tools = Arc::new(ToolRegistry::from_tools(filtered));
Ok(Agent::new(definition, system_prompt, tools))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_empty_name() {
let err = AgentBuilder::named(" ").build().expect_err("empty");
assert_eq!(err.code(), ErrorCode::AgentInvalidDefinition);
}
#[test]
fn builds_minimal() {
let agent = AgentBuilder::named("assistant")
.instructions("You are helpful.")
.model("mock")
.build()
.expect("build");
assert_eq!(agent.name(), "assistant");
assert_eq!(agent.system_prompt(), "You are helpful.");
}
#[test]
fn allowed_tools_filtered_at_build() {
use std::sync::Arc;
use machi_tools::CalcTool;
let mut def = AgentDefinition::new("x");
def.tools = crate::definition::ToolPolicy::Allowlist(vec!["calc".into()]);
def.model = "m".into();
let agent_empty = AgentBuilder::from_definition({
let mut d = def.clone();
d.tools = crate::definition::ToolPolicy::Allowlist(vec!["other".into()]);
d
})
.tools(vec![Arc::new(CalcTool)])
.build()
.expect("build");
assert!(agent_empty.tools().is_empty());
let agent = AgentBuilder::from_definition(def)
.tools(vec![Arc::new(CalcTool)])
.build()
.expect("build");
assert_eq!(agent.tools().names(), vec!["calc".to_owned()]);
}
}