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