subagent_tool.rs

  1use acp_thread::SUBAGENT_SESSION_ID_META_KEY;
  2use agent_client_protocol as acp;
  3use anyhow::Result;
  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)]
 39#[serde(untagged)]
 40pub enum SubagentToolOutput {
 41    Success {
 42        session_id: acp::SessionId,
 43        output: String,
 44    },
 45    Error {
 46        error: String,
 47    },
 48}
 49
 50impl From<SubagentToolOutput> for LanguageModelToolResultContent {
 51    fn from(output: SubagentToolOutput) -> Self {
 52        match output {
 53            output @ SubagentToolOutput::Success { .. } => serde_json::to_string(&output)
 54                .unwrap_or_else(|e| format!("Failed to serialize subagent output: {e}"))
 55                .into(),
 56            SubagentToolOutput::Error { error } => error.into(),
 57        }
 58    }
 59}
 60
 61/// Tool that spawns a subagent thread to work on a task.
 62pub struct SubagentTool {
 63    parent_thread: WeakEntity<Thread>,
 64    environment: Rc<dyn ThreadEnvironment>,
 65}
 66
 67impl SubagentTool {
 68    pub fn new(parent_thread: WeakEntity<Thread>, environment: Rc<dyn ThreadEnvironment>) -> Self {
 69        Self {
 70            parent_thread,
 71            environment,
 72        }
 73    }
 74}
 75
 76impl AgentTool for SubagentTool {
 77    type Input = SubagentToolInput;
 78    type Output = SubagentToolOutput;
 79
 80    const NAME: &'static str = "subagent";
 81
 82    fn kind() -> acp::ToolKind {
 83        acp::ToolKind::Other
 84    }
 85
 86    fn initial_title(
 87        &self,
 88        input: Result<Self::Input, serde_json::Value>,
 89        _cx: &mut App,
 90    ) -> SharedString {
 91        input
 92            .map(|i| i.label.into())
 93            .unwrap_or_else(|_| "Subagent".into())
 94    }
 95
 96    fn run(
 97        self: Arc<Self>,
 98        input: Self::Input,
 99        event_stream: ToolCallEventStream,
100        cx: &mut App,
101    ) -> Task<Result<Self::Output, Self::Output>> {
102        let Some(parent_thread_entity) = self.parent_thread.upgrade() else {
103            return Task::ready(Err(SubagentToolOutput::Error {
104                error: "Parent thread no longer exists".to_string(),
105            }));
106        };
107
108        let subagent = match self.environment.create_subagent(
109            parent_thread_entity,
110            input.label,
111            input.prompt,
112            input.timeout.map(|secs| Duration::from_secs(secs)),
113            cx,
114        ) {
115            Ok(subagent) => subagent,
116            Err(err) => {
117                return Task::ready(Err(SubagentToolOutput::Error {
118                    error: err.to_string(),
119                }));
120            }
121        };
122
123        let subagent_session_id = subagent.id();
124
125        event_stream.subagent_spawned(subagent_session_id.clone());
126        let meta = acp::Meta::from_iter([(
127            SUBAGENT_SESSION_ID_META_KEY.into(),
128            subagent_session_id.to_string().into(),
129        )]);
130        event_stream.update_fields_with_meta(acp::ToolCallUpdateFields::new(), Some(meta));
131
132        cx.spawn(async move |cx| {
133            let output =
134                subagent
135                    .wait_for_output(cx)
136                    .await
137                    .map_err(|e| SubagentToolOutput::Error {
138                        error: e.to_string(),
139                    })?;
140            Ok(SubagentToolOutput::Success {
141                session_id: subagent_session_id,
142                output,
143            })
144        })
145    }
146
147    fn replay(
148        &self,
149        _input: Self::Input,
150        output: Self::Output,
151        event_stream: ToolCallEventStream,
152        _cx: &mut App,
153    ) -> Result<()> {
154        match output {
155            SubagentToolOutput::Success { session_id, .. } => {
156                event_stream.subagent_spawned(session_id.clone());
157                let meta = acp::Meta::from_iter([(
158                    SUBAGENT_SESSION_ID_META_KEY.into(),
159                    session_id.to_string().into(),
160                )]);
161                event_stream.update_fields_with_meta(acp::ToolCallUpdateFields::new(), Some(meta));
162            }
163            SubagentToolOutput::Error { .. } => {}
164        }
165        Ok(())
166    }
167}