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}