subagent_tool.rs

  1use acp_thread::SUBAGENT_SESSION_ID_META_KEY;
  2use agent_client_protocol as acp;
  3use anyhow::{Result, anyhow};
  4use gpui::{App, SharedString, Task, WeakEntity};
  5use language_model::LanguageModelToolResultContent;
  6use schemars::JsonSchema;
  7use serde::{Deserialize, Serialize};
  8use std::sync::Arc;
  9use std::{rc::Rc, time::Duration};
 10
 11use crate::{AgentTool, Thread, ThreadEnvironment, ToolCallEventStream};
 12
 13/// Spawns an agent to perform a delegated task.
 14///
 15/// Use this tool when you want to do any of the following:
 16/// - Perform an investigation where all you need to know is the outcome, not the research that led to that outcome.
 17/// - Complete a self-contained task where you need to know if it succeeded or failed (and how), but none of its intermediate output.
 18/// - Run multiple tasks in parallel that would take significantly longer to run sequentially.
 19///
 20/// You control what the agent does by providing a prompt describing what the agent should do. The agent has access to the same tools you do.
 21///
 22/// You will receive the agent's final message.
 23///
 24/// Note:
 25/// - Agents cannot use tools you don't have access to.
 26/// - If spawning multiple agents that might write to the filesystem, provide guidance on how to avoid conflicts (e.g. assign each to different directories)
 27#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
 28pub struct SubagentToolInput {
 29    /// Short label displayed in the UI while the agent runs (e.g., "Researching alternatives")
 30    pub label: String,
 31    /// The prompt that tells the agent what task to perform. Be specific about what you want the agent to accomplish.
 32    pub prompt: String,
 33    /// Optional: Maximum runtime in seconds. No timeout by default.
 34    #[serde(default)]
 35    pub timeout: Option<u64>,
 36}
 37
 38#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
 39pub struct SubagentToolOutput {
 40    pub session_id: acp::SessionId,
 41    pub output: String,
 42}
 43
 44impl From<SubagentToolOutput> for LanguageModelToolResultContent {
 45    fn from(output: SubagentToolOutput) -> Self {
 46        serde_json::to_string(&output)
 47            .expect("Failed to serialize SubagentToolOutput")
 48            .into()
 49    }
 50}
 51
 52/// Tool that spawns a subagent thread to work on a task.
 53pub struct SubagentTool {
 54    parent_thread: WeakEntity<Thread>,
 55    environment: Rc<dyn ThreadEnvironment>,
 56}
 57
 58impl SubagentTool {
 59    pub fn new(parent_thread: WeakEntity<Thread>, environment: Rc<dyn ThreadEnvironment>) -> Self {
 60        Self {
 61            parent_thread,
 62            environment,
 63        }
 64    }
 65}
 66
 67impl AgentTool for SubagentTool {
 68    type Input = SubagentToolInput;
 69    type Output = SubagentToolOutput;
 70
 71    const NAME: &'static str = "subagent";
 72
 73    fn kind() -> acp::ToolKind {
 74        acp::ToolKind::Other
 75    }
 76
 77    fn initial_title(
 78        &self,
 79        input: Result<Self::Input, serde_json::Value>,
 80        _cx: &mut App,
 81    ) -> SharedString {
 82        input
 83            .map(|i| i.label.into())
 84            .unwrap_or_else(|_| "Subagent".into())
 85    }
 86
 87    fn run(
 88        self: Arc<Self>,
 89        input: Self::Input,
 90        event_stream: ToolCallEventStream,
 91        cx: &mut App,
 92    ) -> Task<Result<SubagentToolOutput>> {
 93        let Some(parent_thread_entity) = self.parent_thread.upgrade() else {
 94            return Task::ready(Err(anyhow!("Parent thread no longer exists")));
 95        };
 96
 97        let subagent = match self.environment.create_subagent(
 98            parent_thread_entity,
 99            input.label,
100            input.prompt,
101            input.timeout.map(|secs| Duration::from_secs(secs)),
102            cx,
103        ) {
104            Ok(subagent) => subagent,
105            Err(err) => return Task::ready(Err(err)),
106        };
107
108        let subagent_session_id = subagent.id();
109
110        event_stream.subagent_spawned(subagent_session_id.clone());
111        let meta = acp::Meta::from_iter([(
112            SUBAGENT_SESSION_ID_META_KEY.into(),
113            subagent_session_id.to_string().into(),
114        )]);
115        event_stream.update_fields_with_meta(acp::ToolCallUpdateFields::new(), Some(meta));
116
117        cx.spawn(async move |cx| {
118            let output = subagent.wait_for_output(cx).await?;
119            Ok(SubagentToolOutput {
120                session_id: subagent_session_id,
121                output,
122            })
123        })
124    }
125
126    fn replay(
127        &self,
128        _input: Self::Input,
129        output: Self::Output,
130        event_stream: ToolCallEventStream,
131        _cx: &mut App,
132    ) -> Result<()> {
133        event_stream.subagent_spawned(output.session_id.clone());
134        let meta = acp::Meta::from_iter([(
135            SUBAGENT_SESSION_ID_META_KEY.into(),
136            output.session_id.to_string().into(),
137        )]);
138        event_stream.update_fields_with_meta(acp::ToolCallUpdateFields::new(), Some(meta));
139        Ok(())
140    }
141}