claude_agent_sdk/subagents/
mod.rs

1//! Subagent system for Claude Agent SDK
2//!
3//! This module provides functionality for creating and managing subagents,
4//! which are specialized Claude instances with specific capabilities and instructions.
5
6mod types;
7
8pub use types::{
9    DelegationStrategy, Subagent, SubagentCall, SubagentConfig, SubagentError,
10    SubagentOutput,
11};
12
13/// Subagent executor for managing and executing subagents
14///
15/// # Example
16///
17/// ```no_run
18/// use claude_agent_sdk::subagents::{SubagentExecutor, Subagent, DelegationStrategy};
19///
20/// #[tokio::main]
21/// async fn example() -> Result<(), Box<dyn std::error::Error>> {
22///     let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
23///
24///     // Register a subagent
25///     let subagent = Subagent {
26///         name: "code-reviewer".to_string(),
27///         description: "Expert code reviewer".to_string(),
28///         instructions: "Review code for bugs and best practices".to_string(),
29///         allowed_tools: vec!["Read".to_string(), "Grep".to_string()],
30///         max_turns: Some(5),
31///         model: Some("claude-sonnet-4".to_string()),
32///     };
33///
34///     executor.register(subagent)?;
35///
36///     // Execute the subagent
37///     let output = executor.execute("code-reviewer", "Review this file").await?;
38///     println!("Output: {:?}", output);
39///
40///     Ok(())
41/// }
42/// ```
43pub struct SubagentExecutor {
44    subagents: std::collections::HashMap<String, Subagent>,
45    strategy: DelegationStrategy,
46}
47
48impl SubagentExecutor {
49    /// Create a new subagent executor
50    ///
51    /// # Arguments
52    ///
53    /// * `strategy` - The delegation strategy to use
54    ///
55    /// # Example
56    ///
57    /// ```no_run
58    /// # use claude_agent_sdk::subagents::{SubagentExecutor, DelegationStrategy};
59    /// let executor = SubagentExecutor::new(DelegationStrategy::Auto);
60    /// ```
61    pub fn new(strategy: DelegationStrategy) -> Self {
62        Self {
63            subagents: std::collections::HashMap::new(),
64            strategy,
65        }
66    }
67
68    /// Register a subagent
69    ///
70    /// # Arguments
71    ///
72    /// * `subagent` - The subagent to register
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if a subagent with the same name already exists
77    ///
78    /// # Example
79    ///
80    /// ```no_run
81    /// # use claude_agent_sdk::subagents::{SubagentExecutor, Subagent, DelegationStrategy};
82    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
83    /// # let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
84    /// let subagent = Subagent {
85    ///     name: "my-agent".to_string(),
86    ///     description: "Description".to_string(),
87    ///     instructions: "Instructions".to_string(),
88    ///     allowed_tools: vec![],
89    ///     max_turns: Some(5),
90    ///     model: None,
91    /// };
92    /// executor.register(subagent)?;
93    /// # Ok(())
94    /// # }
95    /// ```
96    pub fn register(&mut self, subagent: Subagent) -> Result<(), SubagentError> {
97        if self.subagents.contains_key(&subagent.name) {
98            return Err(SubagentError::AlreadyExists(subagent.name));
99        }
100        self.subagents.insert(subagent.name.clone(), subagent);
101        Ok(())
102    }
103
104    /// Execute a subagent by name
105    ///
106    /// # Arguments
107    ///
108    /// * `name` - The name of the subagent to execute
109    /// * `input` - The input to provide to the subagent
110    ///
111    /// # Errors
112    ///
113    /// Returns an error if the subagent is not found or execution fails
114    ///
115    /// # Example
116    ///
117    /// ```no_run
118    /// # use claude_agent_sdk::subagents::{SubagentExecutor, DelegationStrategy};
119    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
120    /// # let executor = SubagentExecutor::new(DelegationStrategy::Auto);
121    /// # // ... register subagent ...
122    /// let output = executor.execute("my-agent", "Hello").await?;
123    /// # Ok(())
124    /// # }
125    /// ```
126    pub async fn execute(
127        &self,
128        name: &str,
129        input: &str,
130    ) -> Result<SubagentOutput, SubagentError> {
131        let subagent = self
132            .subagents
133            .get(name)
134            .ok_or_else(|| SubagentError::NotFound(name.to_string()))?;
135
136        // Build system prompt from description and instructions
137        let system_prompt = format!(
138            "{}\n\nInstructions:\n{}",
139            subagent.description, subagent.instructions
140        );
141
142        // Build ClaudeAgentOptions using match to handle conditional fields
143        let options = match (&subagent.model, subagent.max_turns) {
144            (Some(model), Some(max_turns)) => {
145                crate::types::config::ClaudeAgentOptions::builder()
146                    .system_prompt(crate::types::config::SystemPrompt::Text(
147                        system_prompt,
148                    ))
149                    .allowed_tools(subagent.allowed_tools.clone())
150                    .model(model.clone())
151                    .max_turns(max_turns)
152                    .build()
153            }
154            (Some(model), None) => {
155                crate::types::config::ClaudeAgentOptions::builder()
156                    .system_prompt(crate::types::config::SystemPrompt::Text(
157                        system_prompt,
158                    ))
159                    .allowed_tools(subagent.allowed_tools.clone())
160                    .model(model.clone())
161                    .build()
162            }
163            (None, Some(max_turns)) => {
164                crate::types::config::ClaudeAgentOptions::builder()
165                    .system_prompt(crate::types::config::SystemPrompt::Text(
166                        system_prompt,
167                    ))
168                    .allowed_tools(subagent.allowed_tools.clone())
169                    .max_turns(max_turns)
170                    .build()
171            }
172            (None, None) => {
173                crate::types::config::ClaudeAgentOptions::builder()
174                    .system_prompt(crate::types::config::SystemPrompt::Text(
175                        system_prompt,
176                    ))
177                    .allowed_tools(subagent.allowed_tools.clone())
178                    .build()
179            }
180        };
181
182        // Execute query
183        let messages = crate::query::query(input, Some(options))
184            .await
185            .map_err(|e| SubagentError::ExecutionFailed(format!("Query failed: {}", e)))?;
186
187        // Convert messages to JSON values for SubagentOutput
188        let json_messages = messages
189            .into_iter()
190            .map(|msg| serde_json::to_value(msg).map_err(|e| {
191                SubagentError::ExecutionFailed(format!("Failed to serialize message: {}", e))
192            }))
193            .collect::<Result<Vec<_>, _>>()?;
194
195        Ok(SubagentOutput {
196            subagent_name: name.to_string(),
197            messages: json_messages,
198        })
199    }
200
201    /// Get all registered subagent names
202    ///
203    /// # Returns
204    ///
205    /// A vector of subagent names
206    ///
207    /// # Example
208    ///
209    /// ```no_run
210    /// # use claude_agent_sdk::subagents::{SubagentExecutor, DelegationStrategy};
211    /// # let executor = SubagentExecutor::new(DelegationStrategy::Auto);
212    /// let names = executor.list_subagents();
213    /// println!("Available subagents: {:?}", names);
214    /// ```
215    pub fn list_subagents(&self) -> Vec<String> {
216        self.subagents.keys().cloned().collect()
217    }
218
219    /// Check if a subagent exists
220    ///
221    /// # Arguments
222    ///
223    /// * `name` - The name of the subagent to check
224    ///
225    /// # Returns
226    ///
227    /// `true` if the subagent exists, `false` otherwise
228    ///
229    /// # Example
230    ///
231    /// ```no_run
232    /// # use claude_agent_sdk::subagents::{SubagentExecutor, DelegationStrategy};
233    /// # let executor = SubagentExecutor::new(DelegationStrategy::Auto);
234    /// if executor.has_subagent("my-agent") {
235    ///     println!("Agent exists");
236    /// }
237    /// ```
238    pub fn has_subagent(&self, name: &str) -> bool {
239        self.subagents.contains_key(name)
240    }
241
242    /// Get the delegation strategy
243    ///
244    /// # Returns
245    ///
246    /// The current delegation strategy
247    pub fn strategy(&self) -> &DelegationStrategy {
248        &self.strategy
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn test_executor_creation() {
258        let executor = SubagentExecutor::new(DelegationStrategy::Auto);
259        assert!(matches!(executor.strategy(), &DelegationStrategy::Auto));
260    }
261
262    #[test]
263    fn test_register_subagent() {
264        let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
265
266        let subagent = Subagent {
267            name: "test-agent".to_string(),
268            description: "Test agent".to_string(),
269            instructions: "Test instructions".to_string(),
270            allowed_tools: vec![],
271            max_turns: Some(5),
272            model: None,
273        };
274
275        assert!(executor.register(subagent).is_ok());
276        assert!(executor.has_subagent("test-agent"));
277    }
278
279    #[test]
280    fn test_register_duplicate_fails() {
281        let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
282
283        let subagent = Subagent {
284            name: "test-agent".to_string(),
285            description: "Test agent".to_string(),
286            instructions: "Test instructions".to_string(),
287            allowed_tools: vec![],
288            max_turns: Some(5),
289            model: None,
290        };
291
292        assert!(executor.register(subagent.clone()).is_ok());
293        assert!(matches!(
294            executor.register(subagent),
295            Err(SubagentError::AlreadyExists(_))
296        ));
297    }
298
299    #[test]
300    fn test_list_subagents() {
301        let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
302
303        let subagent1 = Subagent {
304            name: "agent1".to_string(),
305            description: "Agent 1".to_string(),
306            instructions: "Instructions 1".to_string(),
307            allowed_tools: vec![],
308            max_turns: Some(5),
309            model: None,
310        };
311
312        let subagent2 = Subagent {
313            name: "agent2".to_string(),
314            description: "Agent 2".to_string(),
315            instructions: "Instructions 2".to_string(),
316            allowed_tools: vec![],
317            max_turns: Some(10),
318            model: Some("claude-sonnet-4".to_string()),
319        };
320
321        executor.register(subagent1).unwrap();
322        executor.register(subagent2).unwrap();
323
324        let names = executor.list_subagents();
325        assert_eq!(names.len(), 2);
326        assert!(names.contains(&"agent1".to_string()));
327        assert!(names.contains(&"agent2".to_string()));
328    }
329
330    #[test]
331    fn test_execute_not_found() {
332        let executor = SubagentExecutor::new(DelegationStrategy::Auto);
333
334        let result = tokio::runtime::Runtime::new()
335            .unwrap()
336            .block_on(executor.execute("nonexistent", "input"));
337
338        assert!(matches!(result, Err(SubagentError::NotFound(_))));
339    }
340}