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