use anyhow::{bail, Result};
use std::collections::HashMap;
use std::sync::Arc;
use crate::parser::ToolCall;
use super::claude::ClaudeAdapter;
use super::mapping::ToolNameMapping;
use super::traits::{Agent, ExecutionConfig};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AgentType {
#[default]
Claude,
}
impl AgentType {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"claude" | "claude-code" => Some(AgentType::Claude),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
AgentType::Claude => "claude",
}
}
}
#[derive(Debug)]
pub struct NormalizedResult {
pub tool_calls: Vec<ToolCall>,
pub agent_name: String,
}
#[derive(Debug)]
pub struct ExecutionOutput {
pub result: NormalizedResult,
pub session_log_path: Option<std::path::PathBuf>,
pub stdout: Option<String>,
}
pub struct AgentHarness {
agents: HashMap<AgentType, Arc<dyn Agent>>,
default_agent: AgentType,
}
impl AgentHarness {
pub fn new() -> Self {
let mut agents: HashMap<AgentType, Arc<dyn Agent>> = HashMap::new();
agents.insert(AgentType::Claude, Arc::new(ClaudeAdapter::new()));
Self {
agents,
default_agent: AgentType::Claude,
}
}
pub fn execute(
&self,
agent_type: Option<AgentType>,
prompt: &str,
config: ExecutionConfig,
) -> Result<ExecutionOutput> {
let agent_type = agent_type.unwrap_or(self.default_agent);
let agent = self
.agents
.get(&agent_type)
.ok_or_else(|| anyhow::anyhow!("Agent not registered: {:?}", agent_type))?;
if !agent.is_available() {
bail!(
"Agent '{}' is not available on this system",
agent.name()
);
}
let raw_result = agent.execute(prompt, &config)?;
let raw_tool_calls = agent.parse_session(&raw_result)?;
let normalized_calls = self.normalize_tool_calls(&raw_tool_calls, agent.tool_mapping());
Ok(ExecutionOutput {
result: NormalizedResult {
tool_calls: normalized_calls,
agent_name: agent.name().to_string(),
},
session_log_path: raw_result.session_log_path,
stdout: raw_result.stdout,
})
}
fn normalize_tool_calls(
&self,
calls: &[ToolCall],
mapping: &ToolNameMapping,
) -> Vec<ToolCall> {
calls
.iter()
.map(|call| ToolCall {
name: mapping.to_canonical(&call.name),
params: call.params.clone(),
timestamp: call.timestamp,
})
.collect()
}
pub fn get_agent(&self, agent_type: AgentType) -> Option<&Arc<dyn Agent>> {
self.agents.get(&agent_type)
}
pub fn registered_agents(&self) -> Vec<&'static str> {
self.agents.values().map(|a| a.name()).collect()
}
}
impl Default for AgentHarness {
fn default() -> Self {
Self::new()
}
}