1#![allow(dead_code)]
3use std::collections::HashMap;
4use std::sync::Arc;
5use tokio::fs;
6
7use super::agent_tool_utils::{
8 AgentToolResult, extract_partial_result, finalize_agent_tool, resolve_agent_tools,
9};
10use super::load_agents_dir::AgentDefinition;
11
12pub struct ToolContext {
14 pub available_tools: Vec<String>,
15 pub mcp_clients: Vec<String>,
16 pub commands: Vec<(String, String)>, pub agent_definitions: Vec<AgentDefinition>,
18 pub main_loop_model: String,
19 pub custom_system_prompt: Option<String>,
20 pub append_system_prompt: Option<String>,
21 pub tool_use_id: Option<String>,
22}
23
24pub struct AgentOverrides {
26 pub user_context: Option<HashMap<String, String>>,
27 pub system_context: Option<HashMap<String, String>>,
28 pub system_prompt: Option<String>,
29 pub agent_id: Option<String>,
30}
31
32pub struct RunAgentResult {
34 pub messages: Vec<serde_json::Value>,
35 pub result: AgentToolResult,
36}
37
38pub fn resolve_agent_model(
40 agent_model: Option<&str>,
41 main_loop_model: &str,
42 override_model: Option<&str>,
43) -> String {
44 if let Some(m) = override_model {
45 return m.to_string();
46 }
47 if let Some(m) = agent_model {
48 if m == "inherit" {
49 return main_loop_model.to_string();
50 }
51 return m.to_string();
52 }
53 main_loop_model.to_string()
54}
55
56pub fn filter_incomplete_tool_calls(messages: &[serde_json::Value]) -> Vec<serde_json::Value> {
58 let mut tool_use_ids_with_results = std::collections::HashSet::new();
60
61 for message in messages {
62 if message.get("type").and_then(|t| t.as_str()) == Some("user") {
63 if let Some(content) = message.get("message").and_then(|m| m.get("content")) {
64 if let Some(arr) = content.as_array() {
65 for block in arr {
66 if block.get("type").and_then(|t| t.as_str()) == Some("tool_result") {
67 if let Some(id) = block.get("tool_use_id").and_then(|v| v.as_str()) {
68 tool_use_ids_with_results.insert(id.to_string());
69 }
70 }
71 }
72 }
73 }
74 }
75 }
76
77 messages
79 .iter()
80 .filter(|message| {
81 if message.get("type").and_then(|t| t.as_str()) != Some("assistant") {
82 return true;
83 }
84 if let Some(content) = message.get("message").and_then(|m| m.get("content")) {
85 if let Some(arr) = content.as_array() {
86 let has_incomplete = arr.iter().any(|block| {
87 block.get("type").and_then(|t| t.as_str()) == Some("tool_use")
88 && block
89 .get("id")
90 .and_then(|v| v.as_str())
91 .is_some_and(|id| !tool_use_ids_with_results.contains(id))
92 });
93 return !has_incomplete;
94 }
95 }
96 true
97 })
98 .cloned()
99 .collect()
100}
101
102pub struct RunAgentParams {
104 pub agent_definition: AgentDefinition,
105 pub prompt_messages: Vec<serde_json::Value>,
106 pub tool_context: ToolContext,
107 pub is_async: bool,
108 pub override_params: Option<AgentOverrides>,
109 pub model: Option<String>,
110 pub max_turns: Option<usize>,
111 pub fork_context_messages: Option<Vec<serde_json::Value>>,
112 pub allowed_tools: Option<Vec<String>>,
113 pub worktree_path: Option<String>,
114 pub description: Option<String>,
115}
116
117pub async fn run_agent(params: RunAgentParams) -> Result<RunAgentResult, String> {
121 let start_time = std::time::SystemTime::now()
122 .duration_since(std::time::UNIX_EPOCH)
123 .unwrap_or_default()
124 .as_millis() as u64;
125
126 let resolved_model = resolve_agent_model(
127 params.agent_definition.model.as_deref(),
128 ¶ms.tool_context.main_loop_model,
129 params.model.as_deref(),
130 );
131
132 let agent_id = params
133 .override_params
134 .as_ref()
135 .and_then(|o| o.agent_id.clone())
136 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
137
138 let context_messages = params
140 .fork_context_messages
141 .as_ref()
142 .map(|msgs| filter_incomplete_tool_calls(msgs))
143 .unwrap_or_default();
144
145 let mut initial_messages: Vec<serde_json::Value> = context_messages;
146 initial_messages.extend(params.prompt_messages);
147
148 let resolved = resolve_agent_tools(
150 ¶ms.agent_definition,
151 ¶ms.tool_context.available_tools,
152 params.is_async,
153 );
154
155 let _agent_system_prompt = params
157 .override_params
158 .as_ref()
159 .and_then(|o| o.system_prompt.clone())
160 .unwrap_or_else(|| params.agent_definition.system_prompt());
161
162 log::debug!(
173 "Running agent '{}' (type: {}, model: {}, async: {})",
174 agent_id,
175 params.agent_definition.agent_type,
176 resolved_model,
177 params.is_async
178 );
179
180 let _ = write_agent_metadata(
182 &agent_id,
183 ¶ms.agent_definition,
184 ¶ms.worktree_path,
185 ¶ms.description,
186 )
187 .await;
188
189 let result = AgentToolResult {
191 agent_id: agent_id.clone(),
192 agent_type: Some(params.agent_definition.agent_type.clone()),
193 content: "Agent completed".to_string(),
194 total_tool_use_count: 0,
195 total_duration_ms: 0,
196 total_tokens: 0,
197 usage: super::agent_tool_utils::TokenUsage::default(),
198 };
199
200 Ok(RunAgentResult {
201 messages: initial_messages,
202 result,
203 })
204}
205
206async fn write_agent_metadata(
208 agent_id: &str,
209 agent_definition: &AgentDefinition,
210 worktree_path: &Option<String>,
211 description: &Option<String>,
212) -> std::io::Result<()> {
213 let metadata_dir = std::env::current_dir()?
214 .join(".claude")
215 .join("subagents")
216 .join(agent_id);
217 fs::create_dir_all(&metadata_dir).await?;
218
219 let meta = serde_json::json!({
220 "agentType": agent_definition.agent_type,
221 "worktreePath": worktree_path,
222 "description": description,
223 });
224
225 fs::write(
226 metadata_dir.join("metadata.json"),
227 serde_json::to_string_pretty(&meta)?,
228 )
229 .await
230}
231
232pub fn cleanup_agent(agent_id: &str) {
234 log::debug!("Cleaning up agent: {}", agent_id);
242}
243
244pub fn extract_agent_summary(messages: &[serde_json::Value]) -> String {
246 for msg in messages.iter().rev() {
248 if msg.get("type").and_then(|t| t.as_str()) != Some("assistant") {
249 continue;
250 }
251 if let Some(content) = msg.get("message").and_then(|m| m.get("content")) {
252 if let Some(arr) = content.as_array() {
253 let text = super::agent_tool_utils::extract_text_content(arr, "\n");
254 if !text.is_empty() {
255 if text.len() > 500 {
257 return format!("{}...", &text[..497]);
258 }
259 return text;
260 }
261 }
262 }
263 }
264
265 extract_partial_result(messages).unwrap_or_else(|| "Agent completed".to_string())
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 fn make_agent_def() -> AgentDefinition {
274 AgentDefinition {
275 agent_type: "test".to_string(),
276 when_to_use: "test".to_string(),
277 tools: vec!["*".to_string()],
278 disallowed_tools: vec![],
279 source: "built-in".to_string(),
280 base_dir: "built-in".to_string(),
281 get_system_prompt: Arc::new(|| String::new()),
282 model: None,
283 max_turns: None,
284 permission_mode: None,
285 effort: None,
286 color: None,
287 mcp_servers: vec![],
288 hooks: None,
289 skills: vec![],
290 background: false,
291 initial_prompt: None,
292 memory: None,
293 isolation: None,
294 required_mcp_servers: vec![],
295 omit_claude_md: false,
296 critical_system_reminder_experimental: None,
297 }
298 }
299
300 #[test]
301 fn test_resolve_agent_model_override() {
302 assert_eq!(
303 resolve_agent_model(Some("haiku"), "sonnet", Some("opus")),
304 "opus"
305 );
306 }
307
308 #[test]
309 fn test_resolve_agent_model_inherit() {
310 assert_eq!(
311 resolve_agent_model(Some("inherit"), "sonnet", None),
312 "sonnet"
313 );
314 }
315
316 #[test]
317 fn test_filter_incomplete_tool_calls_keeps_complete() {
318 let messages = vec![
319 serde_json::json!({
320 "type": "assistant",
321 "message": {
322 "content": [{"type": "tool_use", "id": "1", "name": "Bash"}]
323 }
324 }),
325 serde_json::json!({
326 "type": "user",
327 "message": {
328 "content": [{"type": "tool_result", "tool_use_id": "1", "content": "done"}]
329 }
330 }),
331 ];
332 let filtered = filter_incomplete_tool_calls(&messages);
333 assert_eq!(filtered.len(), 2);
334 }
335
336 #[test]
337 fn test_filter_incomplete_tool_calls_removes_incomplete() {
338 let messages = vec![serde_json::json!({
339 "type": "assistant",
340 "message": {
341 "content": [{"type": "tool_use", "id": "1", "name": "Bash"}]
342 }
343 })];
344 let filtered = filter_incomplete_tool_calls(&messages);
345 assert_eq!(filtered.len(), 0);
346 }
347
348 #[test]
349 fn test_extract_agent_summary_from_messages() {
350 let messages = vec![serde_json::json!({
351 "type": "assistant",
352 "message": {
353 "content": [{"type": "text", "text": "Task completed successfully"}]
354 }
355 })];
356 let summary = extract_agent_summary(&messages);
357 assert_eq!(summary, "Task completed successfully");
358 }
359}