use std::sync::Arc;
use agent_contracts::runtime::RuntimeView;
use agent_contracts::tool::{ToolExecutor, ToolSpecView};
use agent_types::tool::{FinalToolCall, RawToolOutcome, ToolExecutionError, ToolExecutorOutput};
use async_trait::async_trait;
use subagent::SpawnSubagentRequest;
use crate::r#impl::ToolRuntimeServices;
use super::input::SpawnSubagentInput;
use super::output::SpawnSubagentOutput;
use super::spec::SpawnSubagentToolSpec;
use super::validation;
pub struct SpawnSubagentExecutor {
spec: Arc<SpawnSubagentToolSpec>,
services: ToolRuntimeServices,
}
impl SpawnSubagentExecutor {
pub fn new(spec: Arc<SpawnSubagentToolSpec>, services: ToolRuntimeServices) -> Self {
Self { spec, services }
}
}
#[async_trait]
impl ToolExecutor for SpawnSubagentExecutor {
fn spec(&self) -> &dyn ToolSpecView {
self.spec.as_ref()
}
async fn invoke(
&self,
call: &FinalToolCall,
runtime: &dyn RuntimeView,
) -> Result<ToolExecutorOutput, ToolExecutionError> {
let input: SpawnSubagentInput =
serde_json::from_value(call.input.clone()).map_err(|error| {
ToolExecutionError::ExecutionFailed {
message: format!("Failed to parse input: {}", error),
}
})?;
let validation_result = validation::validate_input(&input);
if !validation_result.result {
let message = validation_result
.message
.unwrap_or_else(|| "Validation failed".to_string());
let error_code = validation_result.error_code.unwrap_or(0);
return Ok(ToolExecutorOutput::Completed {
raw_outcome: RawToolOutcome::Error {
message: format!("[error_code={}] {}", error_code, message),
},
});
}
let subagent_control = self.services.subagent_control.clone().ok_or_else(|| {
ToolExecutionError::ExecutionFailed {
message: "subagent control is not configured".to_string(),
}
})?;
let session_id = runtime
.agent_context()
.metadata()
.session_id
.clone()
.ok_or_else(|| ToolExecutionError::ExecutionFailed {
message: "spawn_subagent requires a session_id in runtime metadata".to_string(),
})?;
let parent_agent_id =
agent_types::common::ids::AgentId(runtime.agent_context().metadata().agent_id.clone());
let (description, task_goal, task_context) = if let Some(role_id) = &input.subagent_role_id
{
let role_config = self.services.subagent_roles.get(role_id);
if let Some(config) = role_config {
let goal = config.prompt.clone().unwrap_or(input.task_goal.clone());
let context = if config.description.is_empty() {
input.task_context.clone()
} else {
format!("{}\n\n{}", config.description, input.task_context)
};
(input.description.clone(), goal, context)
} else {
(
input.description.clone(),
input.task_goal.clone(),
input.task_context.clone(),
)
}
} else {
(
input.description.clone(),
input.task_goal.clone(),
input.task_context.clone(),
)
};
let result = subagent_control
.spawn(SpawnSubagentRequest {
session_id,
parent_agent_id,
description,
task_goal,
task_context,
output_schema: input.output_schema,
subagent_role_id: input.subagent_role_id,
predefined_prompt: None,
max_turns: Some(input.max_turns.unwrap_or(20)),
})
.await
.map_err(|error| ToolExecutionError::ExecutionFailed {
message: error.to_string(),
})?;
let output = serde_json::to_string(&SpawnSubagentOutput {
agent_id: result.agent_id,
})
.map_err(|error| ToolExecutionError::ExecutionFailed {
message: format!("Failed to serialize spawn_subagent output: {}", error),
})?;
Ok(ToolExecutorOutput::Completed {
raw_outcome: RawToolOutcome::Success { output },
})
}
}