pub mod capability;
pub mod code;
mod code_prompts;
pub mod contracts;
pub mod driver;
pub mod guard;
pub mod manifest;
pub mod memory;
pub mod phase;
pub mod pool;
pub mod repl;
mod repl_display;
pub mod result;
pub mod runtime;
mod runtime_helpers;
pub mod session;
pub mod signing;
pub mod tool;
pub mod tui;
pub use capability::{capability_matches, Capability};
pub use guard::{LoopGuard, LoopVerdict};
pub use manifest::{AgentManifest, AutoPullError, ModelConfig, ResourceQuota};
pub use memory::InMemorySubstrate;
pub use phase::LoopPhase;
pub use pool::{AgentId, AgentMessage, AgentPool, MessageRouter, SpawnConfig, ToolBuilder};
pub use result::{AgentError, AgentLoopResult, DriverError, StopReason, TokenUsage};
use driver::{LlmDriver, StreamEvent};
use memory::MemorySubstrate;
use tokio::sync::mpsc;
use tool::ToolRegistry;
pub struct AgentBuilder<'a> {
manifest: &'a AgentManifest,
driver: Option<&'a dyn LlmDriver>,
tools: ToolRegistry,
memory: Option<&'a dyn MemorySubstrate>,
stream_tx: Option<mpsc::Sender<StreamEvent>>,
}
impl<'a> AgentBuilder<'a> {
pub fn new(manifest: &'a AgentManifest) -> Self {
Self { manifest, driver: None, tools: ToolRegistry::new(), memory: None, stream_tx: None }
}
#[must_use]
pub fn driver(mut self, driver: &'a dyn LlmDriver) -> Self {
self.driver = Some(driver);
self
}
#[must_use]
pub fn tool(mut self, tool: Box<dyn tool::Tool>) -> Self {
self.tools.register(tool);
self
}
#[must_use]
pub fn memory(mut self, memory: &'a dyn MemorySubstrate) -> Self {
self.memory = Some(memory);
self
}
#[must_use]
pub fn stream(mut self, tx: mpsc::Sender<StreamEvent>) -> Self {
self.stream_tx = Some(tx);
self
}
pub async fn run(self, query: &str) -> Result<AgentLoopResult, AgentError> {
let driver = self
.driver
.ok_or_else(|| AgentError::ManifestError("no LLM driver configured".into()))?;
let default_memory = InMemorySubstrate::new();
let memory = self.memory.unwrap_or(&default_memory);
runtime::run_agent_loop(self.manifest, query, driver, &self.tools, memory, self.stream_tx)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use driver::mock::MockDriver;
#[tokio::test]
async fn test_builder_minimal() {
let manifest = AgentManifest::default();
let driver = MockDriver::single_response("built!");
let result = AgentBuilder::new(&manifest)
.driver(&driver)
.run("hello")
.await
.expect("builder run failed");
assert_eq!(result.text, "built!");
}
#[tokio::test]
async fn test_builder_no_driver_errors() {
let manifest = AgentManifest::default();
let err = AgentBuilder::new(&manifest).run("hello").await.unwrap_err();
assert!(matches!(err, AgentError::ManifestError(_)), "expected ManifestError, got: {err}");
}
#[tokio::test]
async fn test_builder_with_memory() {
let manifest = AgentManifest::default();
let driver = MockDriver::single_response("remembered");
let memory = InMemorySubstrate::new();
let result = AgentBuilder::new(&manifest)
.driver(&driver)
.memory(&memory)
.run("test")
.await
.expect("builder run failed");
assert_eq!(result.text, "remembered");
}
#[tokio::test]
async fn test_builder_with_stream() {
let manifest = AgentManifest::default();
let driver = MockDriver::single_response("streamed");
let (tx, mut rx) = mpsc::channel(32);
let result = AgentBuilder::new(&manifest)
.driver(&driver)
.stream(tx)
.run("test")
.await
.expect("builder run failed");
assert_eq!(result.text, "streamed");
let mut got_events = false;
while let Ok(_event) = rx.try_recv() {
got_events = true;
}
assert!(got_events, "expected stream events");
}
#[tokio::test]
async fn test_builder_with_tool() {
use crate::agent::driver::ToolDefinition;
use crate::agent::tool::ToolResult as TResult;
struct DummyTool;
#[async_trait::async_trait]
impl tool::Tool for DummyTool {
fn name(&self) -> &'static str {
"dummy"
}
fn definition(&self) -> ToolDefinition {
ToolDefinition {
name: "dummy".into(),
description: "Dummy tool".into(),
input_schema: serde_json::json!(
{"type": "object"}
),
}
}
async fn execute(&self, _input: serde_json::Value) -> TResult {
TResult::success("dummy result")
}
fn required_capability(&self) -> capability::Capability {
capability::Capability::Memory
}
}
let manifest = AgentManifest::default();
let driver = MockDriver::single_response("with tool");
let result = AgentBuilder::new(&manifest)
.driver(&driver)
.tool(Box::new(DummyTool))
.run("test")
.await
.expect("builder run with tool failed");
assert_eq!(result.text, "with tool");
}
}