use std::sync::mpsc::Receiver;
use crate::agent::{RunHandle, ToolExecutionHook, ToolPermissionHook};
use crate::app::AgentEvent;
use crate::providers::ProviderMessage;
use crate::tools::AgentRunConfig;
use thndrs_agent::CancelToken;
#[derive(Debug)]
pub struct HarnessHandle {
pub events: Receiver<AgentEvent>,
pub cancel: CancelToken,
}
impl HarnessHandle {
pub fn request_cancel(&self) {
self.cancel.cancel();
}
}
#[derive(Debug)]
pub struct HarnessTurn {
handle: RunHandle,
}
impl HarnessTurn {
pub fn new(handle: RunHandle) -> Self {
Self { handle }
}
pub fn provider_with_steering(
config: AgentRunConfig, messages: Vec<ProviderMessage>, expects_write: bool, steering: Receiver<String>,
) -> Self {
Self::new(RunHandle::provider_with_steering(
config,
messages,
expects_write,
steering,
))
}
pub fn provider_with_steering_and_permissions(
config: AgentRunConfig, messages: Vec<ProviderMessage>, expects_write: bool, steering: Receiver<String>,
permission_hook: ToolPermissionHook,
) -> Self {
Self::new(
RunHandle::provider_with_steering(config, messages, expects_write, steering)
.with_permission_hook(permission_hook),
)
}
pub fn provider_with_steering_permissions_and_execution(
config: AgentRunConfig, messages: Vec<ProviderMessage>, expects_write: bool, steering: Receiver<String>,
permission_hook: ToolPermissionHook, execution_hook: ToolExecutionHook,
) -> Self {
Self::new(
RunHandle::provider_with_steering(config, messages, expects_write, steering)
.with_permission_hook(permission_hook)
.with_execution_hook(execution_hook),
)
}
pub fn start(self) -> HarnessHandle {
let cancel = self.handle.cancel.clone();
let events = self.handle.spawn();
HarnessHandle { events, cancel }
}
#[cfg(test)]
pub fn fake(config: AgentRunConfig, prompt: String) -> Self {
Self::new(RunHandle::fake(config, prompt))
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use crate::cli::WebSearchMode;
use crate::tools::AgentRunConfig;
#[test]
fn fake_turn_starts_and_finishes_without_app() {
let config = AgentRunConfig::new(
PathBuf::from("."),
String::from("fake-agent"),
WebSearchMode::DuckDuckGo,
);
let handle = HarnessTurn::fake(config, String::new()).start();
let mut events = Vec::new();
while let Ok(event) = handle.events.recv() {
events.push(event);
}
assert_eq!(events.first(), Some(&AgentEvent::Started));
assert_eq!(events.last(), Some(&AgentEvent::Finished));
assert!(
events
.iter()
.any(|event| matches!(event, AgentEvent::ReasoningDelta(_)))
);
assert!(
events
.iter()
.any(|event| matches!(event, AgentEvent::AssistantDelta(_)))
);
assert!(
events
.iter()
.any(|event| matches!(event, AgentEvent::ToolStarted { .. }))
);
assert!(
events
.iter()
.any(|event| matches!(event, AgentEvent::ToolFinished { .. }))
);
}
}