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