use tokio::sync::oneshot;
use haki_core::{AgentLoop, NoopToolExecutor, Session};
use haki_config::Config;
use haki_llm::LlmProvider;
use crate::definition::AgentDef;
pub struct AgentHandle {
pub def: AgentDef,
result_rx: oneshot::Receiver<anyhow::Result<String>>,
}
impl AgentHandle {
pub async fn await_result(self) -> anyhow::Result<String> {
self.result_rx.await.map_err(|_| anyhow::anyhow!("Agent task dropped"))?
}
}
pub struct AgentSpawner {
config: Config,
}
impl AgentSpawner {
pub fn new(config: Config) -> Self {
Self { config }
}
pub fn spawn(&self, def: AgentDef, prompt: String) -> anyhow::Result<AgentHandle> {
let (tx, rx) = oneshot::channel();
let model = def
.model_override
.clone()
.unwrap_or_else(|| self.config.model.clone());
let mut cfg = self.config.clone();
cfg.model = model;
cfg.system_prompt = Some(def.role.system_prompt().to_string());
let provider = LlmProvider::from_config(&cfg.provider)?;
tokio::spawn(async move {
let mut session = Session::new(cfg);
let agent = AgentLoop::new(provider, Box::new(NoopToolExecutor), 4096);
let result = agent.run_turn(&mut session, &prompt).await;
let _ = tx.send(result);
});
Ok(AgentHandle { def, result_rx: rx })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::definition::AgentRole;
use haki_config::Config;
#[test]
fn spawner_builds_without_panic() {
let _ = AgentSpawner::new(Config::default());
}
#[test]
fn spawn_fails_gracefully_with_bad_provider() {
let mut cfg = Config::default();
cfg.provider.name = "nonexistent-xyz".into();
cfg.provider.api_key = Some("fake".into());
let spawner = AgentSpawner::new(cfg);
let def = AgentDef::new("test", AgentRole::Planner);
assert!(spawner.spawn(def, "hello".into()).is_err());
}
}