use futures::future::BoxFuture;
use crate::agent::subagent::executor::SubagentExecutor;
use crate::error::ToolError;
use crate::tools::{Tool, ToolParameters, ToolResult};
use echo_core::agent::CancellationToken;
use serde_json::{Value, json};
use std::sync::Arc;
use tracing::{debug, info, warn};
pub struct AgentDispatchTool {
executor: Arc<SubagentExecutor>,
parent_agent: String,
cancel: CancellationToken,
}
impl AgentDispatchTool {
pub fn new(
executor: Arc<SubagentExecutor>,
parent_agent: impl Into<String>,
cancel: CancellationToken,
) -> Self {
Self {
executor,
parent_agent: parent_agent.into(),
cancel,
}
}
}
impl Tool for AgentDispatchTool {
fn name(&self) -> &str {
"agent_tool"
}
fn description(&self) -> &str {
"Dispatch a task to a specialized SubAgent for execution. As the orchestrator, prefer using this tool to delegate computation, data fetching, etc. to professional SubAgents rather than answering directly."
}
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": {
"agent_name": {
"type": "string",
"description": "SubAgent name"
},
"task": {
"type": "string",
"description": "Specific task description to assign to the SubAgent, should include necessary context"
},
"mode": {
"type": "string",
"enum": ["sync", "fork", "teammate"],
"description": "Execution mode: sync - synchronous wait (default), fork - independent with inherited context, teammate - parallel collaboration"
}
},
"required": ["agent_name", "task"]
})
}
fn execute(
&self,
parameters: ToolParameters,
) -> BoxFuture<'_, crate::error::Result<ToolResult>> {
let executor = self.executor.clone();
let parent_agent = self.parent_agent.clone();
let cancel = self.cancel.clone();
Box::pin(async move {
let agent_name = parameters
.get("agent_name")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::MissingParameter("agent_name".to_string()))?;
let task = parameters
.get("task")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::MissingParameter("task".to_string()))?;
let mode_override =
parameters
.get("mode")
.and_then(|v| v.as_str())
.and_then(|m| match m {
"sync" => Some(crate::agent::subagent::ExecutionMode::Sync),
"fork" => Some(crate::agent::subagent::ExecutionMode::Fork),
"teammate" => Some(crate::agent::subagent::ExecutionMode::Teammate),
_ => None,
});
info!(
target_agent = %agent_name,
task = %task,
mode = ?mode_override,
"Dispatching task to subagent via SubagentExecutor"
);
let req = crate::agent::subagent::DispatchRequest {
agent_name: agent_name.to_string(),
task: task.to_string(),
mode_override,
cancel,
parent_agent: parent_agent.clone(),
parent_context: None, delegate_depth: 0,
};
match executor.dispatch(req).await {
Ok(result) => {
info!(target_agent = %agent_name, "Subagent completed successfully");
debug!(target_agent = %agent_name, output = %result.output, "Subagent result");
Ok(ToolResult::success(result.output))
}
Err(e) => {
warn!(target_agent = %agent_name, error = %e, "Subagent execution failed");
Ok(ToolResult::error(format!(
"SubAgent '{}' execution failed: {}",
agent_name, e
)))
}
}
})
}
}