1pub mod auto_memory;
31pub mod capability;
32pub mod code;
33mod code_prompts;
34pub mod contracts;
35pub mod custom_agents;
36pub mod driver;
37pub mod guard;
38pub mod hooks;
39pub mod instructions;
40pub mod manifest;
41#[cfg(feature = "agents-mcp")]
42pub mod mcp_json;
43pub mod memory;
44pub mod org_policy;
45pub mod permission;
46pub mod phase;
47pub mod pool;
48pub mod repl;
49mod repl_directives;
50mod repl_display;
51pub mod result;
52pub mod runtime;
53mod runtime_helpers;
54pub mod session;
55pub mod settings;
56pub mod signing;
57pub mod skill;
58pub mod status_line;
59pub mod task_tool;
60pub mod tool;
61pub mod tui;
62pub mod worktree;
63
64pub use capability::{capability_matches, Capability};
66pub use guard::{LoopGuard, LoopVerdict};
67pub use manifest::{AgentManifest, AutoPullError, ModelConfig, ResourceQuota};
68pub use memory::InMemorySubstrate;
69pub use phase::LoopPhase;
70pub use pool::{AgentId, AgentMessage, AgentPool, MessageRouter, SpawnConfig, ToolBuilder};
71pub use result::{AgentError, AgentLoopResult, DriverError, StopReason, TokenUsage};
72
73use driver::{LlmDriver, StreamEvent};
74use memory::MemorySubstrate;
75use tokio::sync::mpsc;
76use tool::ToolRegistry;
77
78pub struct AgentBuilder<'a> {
89 manifest: &'a AgentManifest,
90 driver: Option<&'a dyn LlmDriver>,
91 tools: ToolRegistry,
92 memory: Option<&'a dyn MemorySubstrate>,
93 stream_tx: Option<mpsc::Sender<StreamEvent>>,
94}
95
96impl<'a> AgentBuilder<'a> {
97 pub fn new(manifest: &'a AgentManifest) -> Self {
99 Self { manifest, driver: None, tools: ToolRegistry::new(), memory: None, stream_tx: None }
100 }
101
102 #[must_use]
104 pub fn driver(mut self, driver: &'a dyn LlmDriver) -> Self {
105 self.driver = Some(driver);
106 self
107 }
108
109 #[must_use]
111 pub fn tool(mut self, tool: Box<dyn tool::Tool>) -> Self {
112 self.tools.register(tool);
113 self
114 }
115
116 #[must_use]
118 pub fn memory(mut self, memory: &'a dyn MemorySubstrate) -> Self {
119 self.memory = Some(memory);
120 self
121 }
122
123 #[must_use]
125 pub fn stream(mut self, tx: mpsc::Sender<StreamEvent>) -> Self {
126 self.stream_tx = Some(tx);
127 self
128 }
129
130 pub async fn run(self, query: &str) -> Result<AgentLoopResult, AgentError> {
134 let driver = self
135 .driver
136 .ok_or_else(|| AgentError::ManifestError("no LLM driver configured".into()))?;
137
138 let default_memory = InMemorySubstrate::new();
139 let memory = self.memory.unwrap_or(&default_memory);
140
141 runtime::run_agent_loop(self.manifest, query, driver, &self.tools, memory, self.stream_tx)
142 .await
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use driver::mock::MockDriver;
150
151 #[tokio::test]
152 async fn test_builder_minimal() {
153 let manifest = AgentManifest::default();
154 let driver = MockDriver::single_response("built!");
155
156 let result = AgentBuilder::new(&manifest)
157 .driver(&driver)
158 .run("hello")
159 .await
160 .expect("builder run failed");
161
162 assert_eq!(result.text, "built!");
163 }
164
165 #[tokio::test]
166 async fn test_builder_no_driver_errors() {
167 let manifest = AgentManifest::default();
168
169 let err = AgentBuilder::new(&manifest).run("hello").await.unwrap_err();
170
171 assert!(matches!(err, AgentError::ManifestError(_)), "expected ManifestError, got: {err}");
172 }
173
174 #[tokio::test]
175 async fn test_builder_with_memory() {
176 let manifest = AgentManifest::default();
177 let driver = MockDriver::single_response("remembered");
178 let memory = InMemorySubstrate::new();
179
180 let result = AgentBuilder::new(&manifest)
181 .driver(&driver)
182 .memory(&memory)
183 .run("test")
184 .await
185 .expect("builder run failed");
186
187 assert_eq!(result.text, "remembered");
188 }
189
190 #[tokio::test]
191 async fn test_builder_with_stream() {
192 let manifest = AgentManifest::default();
193 let driver = MockDriver::single_response("streamed");
194 let (tx, mut rx) = mpsc::channel(32);
195
196 let result = AgentBuilder::new(&manifest)
197 .driver(&driver)
198 .stream(tx)
199 .run("test")
200 .await
201 .expect("builder run failed");
202
203 assert_eq!(result.text, "streamed");
204
205 let mut got_events = false;
206 while let Ok(_event) = rx.try_recv() {
207 got_events = true;
208 }
209 assert!(got_events, "expected stream events");
210 }
211
212 #[tokio::test]
213 async fn test_builder_with_tool() {
214 use crate::agent::driver::ToolDefinition;
215 use crate::agent::tool::ToolResult as TResult;
216
217 struct DummyTool;
218
219 #[async_trait::async_trait]
220 impl tool::Tool for DummyTool {
221 fn name(&self) -> &'static str {
222 "dummy"
223 }
224 fn definition(&self) -> ToolDefinition {
225 ToolDefinition {
226 name: "dummy".into(),
227 description: "Dummy tool".into(),
228 input_schema: serde_json::json!(
229 {"type": "object"}
230 ),
231 }
232 }
233 async fn execute(&self, _input: serde_json::Value) -> TResult {
234 TResult::success("dummy result")
235 }
236 fn required_capability(&self) -> capability::Capability {
237 capability::Capability::Memory
238 }
239 }
240
241 let manifest = AgentManifest::default();
242 let driver = MockDriver::single_response("with tool");
243
244 let result = AgentBuilder::new(&manifest)
245 .driver(&driver)
246 .tool(Box::new(DummyTool))
247 .run("test")
248 .await
249 .expect("builder run with tool failed");
250
251 assert_eq!(result.text, "with tool");
252 }
253}