1use std::sync::Arc;
12
13use crate::llm::message::ContentBlock;
14use crate::permissions::{PermissionChecker, PermissionDecision};
15
16use super::{Tool, ToolContext, ToolResult};
17
18#[derive(Debug, Clone)]
20pub struct PendingToolCall {
21 pub id: String,
22 pub name: String,
23 pub input: serde_json::Value,
24}
25
26#[derive(Debug)]
28pub struct ToolCallResult {
29 pub tool_use_id: String,
30 pub tool_name: String,
31 pub result: ToolResult,
32}
33
34impl ToolCallResult {
35 pub fn to_content_block(&self) -> ContentBlock {
37 ContentBlock::ToolResult {
38 tool_use_id: self.tool_use_id.clone(),
39 content: self.result.content.clone(),
40 is_error: self.result.is_error,
41 extra_content: vec![],
42 }
43 }
44}
45
46pub fn extract_tool_calls(content: &[ContentBlock]) -> Vec<PendingToolCall> {
48 content
49 .iter()
50 .filter_map(|block| {
51 if let ContentBlock::ToolUse { id, name, input } = block {
52 Some(PendingToolCall {
53 id: id.clone(),
54 name: name.clone(),
55 input: input.clone(),
56 })
57 } else {
58 None
59 }
60 })
61 .collect()
62}
63
64pub async fn execute_tool_calls(
69 calls: &[PendingToolCall],
70 tools: &[Arc<dyn Tool>],
71 ctx: &ToolContext,
72 permission_checker: &PermissionChecker,
73) -> Vec<ToolCallResult> {
74 let mut results = Vec::with_capacity(calls.len());
76
77 let mut i = 0;
79 while i < calls.len() {
80 let call = &calls[i];
81 let tool = tools.iter().find(|t| t.name() == call.name);
82
83 match tool {
84 None => {
85 results.push(ToolCallResult {
86 tool_use_id: call.id.clone(),
87 tool_name: call.name.clone(),
88 result: ToolResult::error(format!("Tool '{}' not found", call.name)),
89 });
90 i += 1;
91 }
92 Some(tool) => {
93 if tool.is_concurrency_safe() {
94 let batch_start = i;
96 while i < calls.len() {
97 let t = tools.iter().find(|t| t.name() == calls[i].name);
98 if t.is_some_and(|t| t.is_concurrency_safe()) {
99 i += 1;
100 } else {
101 break;
102 }
103 }
104
105 let batch = &calls[batch_start..i];
107 let mut handles = Vec::new();
108
109 for call in batch {
110 let tool = tools
111 .iter()
112 .find(|t| t.name() == call.name)
113 .unwrap()
114 .clone();
115 let call = call.clone();
116 let ctx_cwd = ctx.cwd.clone();
117 let ctx_cancel = ctx.cancel.clone();
118 let ctx_verbose = ctx.verbose;
119 let perm_checker = ctx.permission_checker.clone();
120
121 let ctx_plan_mode = ctx.plan_mode;
122 let ctx_file_cache = ctx.file_cache.clone();
123 handles.push(tokio::spawn(async move {
125 execute_single_tool(
126 &call,
127 &*tool,
128 &ToolContext {
129 cwd: ctx_cwd,
130 cancel: ctx_cancel,
131 permission_checker: perm_checker.clone(),
132 verbose: ctx_verbose,
133 plan_mode: ctx_plan_mode,
134 file_cache: ctx_file_cache,
135 denial_tracker: None,
136 task_manager: None,
137 session_allows: None,
138 permission_prompter: None,
139 },
140 &perm_checker,
141 )
142 .await
143 }));
144 }
145
146 for handle in handles {
147 match handle.await {
148 Ok(result) => results.push(result),
149 Err(e) => {
150 results.push(ToolCallResult {
151 tool_use_id: String::new(),
152 tool_name: String::new(),
153 result: ToolResult::error(format!("Task join error: {e}")),
154 });
155 }
156 }
157 }
158 } else {
159 let result = execute_single_tool(call, &**tool, ctx, permission_checker).await;
161 results.push(result);
162 i += 1;
163 }
164 }
165 }
166 }
167
168 results
169}
170
171async fn execute_single_tool(
173 call: &PendingToolCall,
174 tool: &dyn Tool,
175 ctx: &ToolContext,
176 permission_checker: &PermissionChecker,
177) -> ToolCallResult {
178 if ctx.plan_mode && !tool.is_read_only() {
180 return ToolCallResult {
181 tool_use_id: call.id.clone(),
182 tool_name: call.name.clone(),
183 result: ToolResult::error(
184 "Plan mode active: only read-only tools are allowed. \
185 Use ExitPlanMode to enable mutations."
186 .to_string(),
187 ),
188 };
189 }
190
191 let decision = tool
193 .check_permissions(&call.input, permission_checker)
194 .await;
195 match decision {
196 PermissionDecision::Allow => {}
197 PermissionDecision::Deny(reason) => {
198 if let Some(ref tracker) = ctx.denial_tracker {
199 tracker
200 .lock()
201 .await
202 .record(&call.name, &call.id, &reason, &call.input);
203 }
204 return ToolCallResult {
205 tool_use_id: call.id.clone(),
206 tool_name: call.name.clone(),
207 result: ToolResult::error(format!("Permission denied: {reason}")),
208 };
209 }
210 PermissionDecision::Ask(prompt) => {
211 if let Some(ref allows) = ctx.session_allows
213 && allows.lock().await.contains(call.name.as_str())
214 {
215 } else {
217 let description = format!("{}: {}", call.name, prompt);
219 let input_preview = serde_json::to_string_pretty(&call.input).ok();
220
221 let response = if let Some(ref prompter) = ctx.permission_prompter {
222 prompter.ask(&call.name, &description, input_preview.as_deref())
223 } else {
224 super::PermissionResponse::AllowOnce
226 };
227
228 match response {
229 super::PermissionResponse::AllowOnce => {
230 }
232 super::PermissionResponse::AllowSession => {
233 if let Some(ref allows) = ctx.session_allows {
235 allows.lock().await.insert(call.name.clone());
236 }
237 }
238 super::PermissionResponse::Deny => {
239 if let Some(ref tracker) = ctx.denial_tracker {
240 tracker.lock().await.record(
241 &call.name,
242 &call.id,
243 "user denied",
244 &call.input,
245 );
246 }
247 return ToolCallResult {
248 tool_use_id: call.id.clone(),
249 tool_name: call.name.clone(),
250 result: ToolResult::error("Permission denied by user".to_string()),
251 };
252 }
253 }
254 } }
256 }
257
258 if let Err(msg) = tool.validate_input(&call.input) {
260 return ToolCallResult {
261 tool_use_id: call.id.clone(),
262 tool_name: call.name.clone(),
263 result: ToolResult::error(format!("Invalid input: {msg}")),
264 };
265 }
266
267 match tool.call(call.input.clone(), ctx).await {
269 Ok(mut result) => {
270 result.content = crate::services::output_store::persist_if_large(
272 &result.content,
273 tool.name(),
274 &call.id,
275 );
276
277 let max = tool.max_result_size_chars();
279 if result.content.len() > max {
280 result.content.truncate(max);
281 result.content.push_str("\n\n(output truncated)");
282 }
283 ToolCallResult {
284 tool_use_id: call.id.clone(),
285 tool_name: call.name.clone(),
286 result,
287 }
288 }
289 Err(e) => ToolCallResult {
290 tool_use_id: call.id.clone(),
291 tool_name: call.name.clone(),
292 result: ToolResult::error(e.to_string()),
293 },
294 }
295}