1use std::sync::Arc;
4
5use machi_tools::{SharedTool, ToolRegistry};
6use machi_types::{ErrorCode, MachiError};
7
8use crate::definition::{AgentDefinition, CompletionRequirement, Instructions};
9use crate::instance::Agent;
10
11#[derive(Default)]
13pub struct AgentBuilder {
14 definition: Option<AgentDefinition>,
15 tools: Vec<SharedTool>,
16}
17
18impl std::fmt::Debug for AgentBuilder {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 f.debug_struct("AgentBuilder")
21 .field("definition", &self.definition)
22 .field("tools", &self.tools.len())
23 .finish()
24 }
25}
26
27impl AgentBuilder {
28 #[must_use]
30 pub fn new() -> Self {
31 Self::default()
32 }
33
34 #[must_use]
36 pub fn from_definition(definition: AgentDefinition) -> Self {
37 Self {
38 definition: Some(definition),
39 tools: Vec::new(),
40 }
41 }
42
43 #[must_use]
45 pub fn named(name: impl Into<String>) -> Self {
46 Self {
47 definition: Some(AgentDefinition::new(name)),
48 tools: Vec::new(),
49 }
50 }
51
52 #[must_use]
54 pub fn instructions(mut self, instructions: impl Into<Instructions>) -> Self {
55 if let Some(def) = &mut self.definition {
56 def.instructions = instructions.into();
57 }
58 self
59 }
60
61 #[must_use]
63 pub fn model(mut self, model: impl Into<String>) -> Self {
64 if let Some(def) = &mut self.definition {
65 def.model = model.into();
66 }
67 self
68 }
69
70 #[must_use]
72 pub fn description(mut self, description: impl Into<String>) -> Self {
73 if let Some(def) = &mut self.definition {
74 def.description = description.into();
75 }
76 self
77 }
78
79 #[must_use]
81 pub fn max_steps(mut self, max_steps: usize) -> Self {
82 if let Some(def) = &mut self.definition {
83 def.max_steps = max_steps;
84 }
85 self
86 }
87
88 #[must_use]
90 pub fn tools(mut self, tools: Vec<SharedTool>) -> Self {
91 self.tools = tools;
92 self
93 }
94
95 #[must_use]
97 pub fn completion(mut self, requirement: CompletionRequirement) -> Self {
98 if let Some(def) = &mut self.definition {
99 def.completion = Some(requirement);
100 }
101 self
102 }
103
104 #[must_use]
106 pub fn output_schema(mut self, schema: serde_json::Value) -> Self {
107 if let Some(def) = &mut self.definition {
108 def.output_schema = Some(schema);
109 }
110 self
111 }
112
113 pub fn build(self) -> Result<Agent, MachiError> {
119 let definition = self.definition.ok_or_else(|| {
120 MachiError::new(ErrorCode::AgentBuild, "agent definition is required")
121 })?;
122 definition.validate()?;
123
124 let filtered: Vec<SharedTool> = self
126 .tools
127 .into_iter()
128 .filter(|t| definition.tools.admits(t.name()))
129 .collect();
130
131 let system_prompt = definition.instructions.resolve();
132 let tools = Arc::new(ToolRegistry::from_tools(filtered));
133 Ok(Agent::new(definition, system_prompt, tools))
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn rejects_empty_name() {
143 let err = AgentBuilder::named(" ").build().expect_err("empty");
144 assert_eq!(err.code(), ErrorCode::AgentInvalidDefinition);
145 }
146
147 #[test]
148 fn builds_minimal() {
149 let agent = AgentBuilder::named("assistant")
150 .instructions("You are helpful.")
151 .model("mock")
152 .build()
153 .expect("build");
154 assert_eq!(agent.name(), "assistant");
155 assert_eq!(agent.system_prompt(), "You are helpful.");
156 }
157
158 #[test]
159 fn allowed_tools_filtered_at_build() {
160 use std::sync::Arc;
161
162 use machi_tools::CalcTool;
163
164 let mut def = AgentDefinition::new("x");
165 def.tools = crate::definition::ToolPolicy::Allowlist(vec!["calc".into()]);
166 def.model = "m".into();
167 let agent_empty = AgentBuilder::from_definition({
170 let mut d = def.clone();
171 d.tools = crate::definition::ToolPolicy::Allowlist(vec!["other".into()]);
172 d
173 })
174 .tools(vec![Arc::new(CalcTool)])
175 .build()
176 .expect("build");
177 assert!(agent_empty.tools().is_empty());
178
179 let agent = AgentBuilder::from_definition(def)
180 .tools(vec![Arc::new(CalcTool)])
181 .build()
182 .expect("build");
183 assert_eq!(agent.tools().names(), vec!["calc".to_owned()]);
184 }
185}