use std::error::Error;
use async_trait::async_trait;
use serde_json::{json, Value};
use crate::{
agent::state::Command,
tools::{Tool, ToolResult, ToolRuntime},
};
pub struct HandoffTool {
name: String,
description: String,
}
impl HandoffTool {
pub fn new() -> Self {
Self {
name: "handoff".to_string(),
description: "Transfer control to another specialized agent. Use this when the current agent cannot handle the request and another agent would be better suited.".to_string(),
}
}
pub fn with_name_and_description(name: String, description: String) -> Self {
Self { name, description }
}
}
impl Default for HandoffTool {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Tool for HandoffTool {
fn name(&self) -> String {
self.name.clone()
}
fn description(&self) -> String {
self.description.clone()
}
fn parameters(&self) -> Value {
serde_json::json!({
"type": "object",
"properties": {
"agent_name": {
"type": "string",
"description": "The name of the agent to hand off control to"
},
"message": {
"type": "string",
"description": "Optional message to pass to the target agent"
}
},
"required": ["agent_name"]
})
}
async fn run(&self, _input: Value) -> Result<String, crate::error::ToolError> {
Err(crate::error::ToolError::ConfigurationError(
"HandoffTool requires runtime. Use run_with_runtime instead.".to_string(),
))
}
async fn run_with_runtime(
&self,
input: Value,
runtime: &ToolRuntime,
) -> Result<ToolResult, Box<dyn Error>> {
let agent_name = if let Some(name_val) = input.get("agent_name") {
if name_val.is_string() {
name_val.as_str().unwrap().to_string()
} else {
return Err("agent_name must be a string".into());
}
} else {
return Err("agent_name is required".into());
};
let message = input
.get("message")
.and_then(|m| m.as_str())
.map(|s| s.to_string());
let mut state = runtime.state().await;
state.set_active_agent(agent_name.clone());
if let Some(msg) = message {
state.set_field("handoff_message".to_string(), json!(msg));
}
let mut fields = std::collections::HashMap::new();
fields.insert("active_agent".to_string(), json!(agent_name));
if let Some(msg) = input.get("message") {
fields.insert("handoff_message".to_string(), msg.clone());
}
let command = Command::UpdateState { fields };
Ok(ToolResult::with_command(
format!("Handing off control to agent: {}", agent_name),
command,
))
}
fn requires_runtime(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handoff_tool_creation() {
let tool = HandoffTool::new();
assert_eq!(tool.name(), "handoff");
assert!(tool.requires_runtime());
}
#[test]
fn test_handoff_tool_custom() {
let tool = HandoffTool::with_name_and_description(
"transfer".to_string(),
"Transfer control".to_string(),
);
assert_eq!(tool.name(), "transfer");
assert_eq!(tool.description(), "Transfer control");
}
}