opendev_repl/
tool_executor.rs1use std::collections::HashMap;
6use std::time::Instant;
7
8use futures::future::join_all;
9use serde_json::Value;
10use tracing::{debug, info, warn};
11
12use opendev_tools_core::parallel::{ParallelPolicy, ToolCall as PolicyToolCall};
13use opendev_tools_core::{ToolContext, ToolRegistry, ToolResult};
14
15use crate::error::ReplError;
16
17#[derive(Debug, Clone)]
19pub struct ToolExecutionResult {
20 pub tool_name: String,
22 pub success: bool,
24 pub output: Option<String>,
26 pub error: Option<String>,
28 pub duration_ms: u64,
30}
31
32pub struct ToolExecutor {
34 execution_count: u64,
36}
37
38impl ToolExecutor {
39 pub fn new() -> Self {
41 Self { execution_count: 0 }
42 }
43
44 pub async fn execute(
49 &mut self,
50 tool_call: &Value,
51 registry: &ToolRegistry,
52 context: &ToolContext,
53 ) -> Result<ToolExecutionResult, ReplError> {
54 let tool_name = tool_call["function"]["name"]
55 .as_str()
56 .unwrap_or("unknown")
57 .to_string();
58
59 self.execution_count += 1;
60 info!(
61 tool = %tool_name,
62 execution_num = self.execution_count,
63 "Executing tool"
64 );
65
66 let start = Instant::now();
67
68 let args_value = &tool_call["function"]["arguments"];
70 let args: HashMap<String, Value> = if let Some(args_str) = args_value.as_str() {
71 serde_json::from_str(args_str).unwrap_or_default()
72 } else if let Some(obj) = args_value.as_object() {
73 obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
74 } else {
75 HashMap::new()
76 };
77
78 let result: ToolResult = registry.execute(&tool_name, args, context).await;
80 let duration_ms = start.elapsed().as_millis() as u64;
81
82 if result.success {
83 debug!(
84 tool = %tool_name,
85 duration_ms,
86 "Tool execution succeeded"
87 );
88 } else {
89 warn!(
90 tool = %tool_name,
91 error = ?result.error,
92 duration_ms,
93 "Tool execution failed"
94 );
95 }
96
97 Ok(ToolExecutionResult {
98 tool_name,
99 success: result.success,
100 output: result.output,
101 error: result.error,
102 duration_ms,
103 })
104 }
105
106 pub async fn execute_batch(
112 &mut self,
113 tool_calls: &[Value],
114 registry: &ToolRegistry,
115 context: &ToolContext,
116 ) -> Vec<Result<ToolExecutionResult, ReplError>> {
117 if tool_calls.is_empty() {
118 return vec![];
119 }
120
121 let policy_calls: Vec<PolicyToolCall> = tool_calls
123 .iter()
124 .map(|tc| {
125 let name = tc["function"]["name"]
126 .as_str()
127 .unwrap_or("unknown")
128 .to_string();
129 let arguments = tc["function"]["arguments"].clone();
130 let args_val = if let Some(s) = arguments.as_str() {
131 serde_json::from_str(s).unwrap_or(Value::Object(Default::default()))
132 } else {
133 arguments
134 };
135 PolicyToolCall::new(name, args_val)
136 })
137 .collect();
138
139 let groups = ParallelPolicy::partition(&policy_calls);
140
141 let mut results: Vec<Option<Result<ToolExecutionResult, ReplError>>> =
143 (0..tool_calls.len()).map(|_| None).collect();
144
145 for group in &groups {
146 if group.len() == 1 {
147 let idx = group[0];
149 self.execution_count += 1;
150 let res = self
151 .execute_single(&tool_calls[idx], registry, context)
152 .await;
153 results[idx] = Some(res);
154 } else {
155 let futs: Vec<_> = group
157 .iter()
158 .map(|&idx| {
159 let tc = &tool_calls[idx];
160 Self::execute_standalone(tc, registry, context)
161 })
162 .collect();
163
164 let group_results = join_all(futs).await;
165 for (&idx, res) in group.iter().zip(group_results) {
166 self.execution_count += 1;
167 results[idx] = Some(res);
168 }
169 }
170 }
171
172 results
174 .into_iter()
175 .enumerate()
176 .map(|(i, opt)| {
177 opt.unwrap_or_else(|| {
178 Ok(ToolExecutionResult {
179 tool_name: format!("tool_{}", i),
180 success: false,
181 output: None,
182 error: Some("tool was not scheduled for execution".to_string()),
183 duration_ms: 0,
184 })
185 })
186 })
187 .collect()
188 }
189
190 async fn execute_single(
192 &mut self,
193 tool_call: &Value,
194 registry: &ToolRegistry,
195 context: &ToolContext,
196 ) -> Result<ToolExecutionResult, ReplError> {
197 self.execute(tool_call, registry, context).await
198 }
199
200 async fn execute_standalone(
202 tool_call: &Value,
203 registry: &ToolRegistry,
204 context: &ToolContext,
205 ) -> Result<ToolExecutionResult, ReplError> {
206 let tool_name = tool_call["function"]["name"]
207 .as_str()
208 .unwrap_or("unknown")
209 .to_string();
210
211 let start = Instant::now();
212
213 let args_value = &tool_call["function"]["arguments"];
214 let args: HashMap<String, Value> = if let Some(args_str) = args_value.as_str() {
215 serde_json::from_str(args_str).unwrap_or_default()
216 } else if let Some(obj) = args_value.as_object() {
217 obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
218 } else {
219 HashMap::new()
220 };
221
222 let result: ToolResult = registry.execute(&tool_name, args, context).await;
223 let duration_ms = start.elapsed().as_millis() as u64;
224
225 if result.success {
226 debug!(tool = %tool_name, duration_ms, "Tool execution succeeded (parallel)");
227 } else {
228 warn!(tool = %tool_name, error = ?result.error, duration_ms, "Tool execution failed (parallel)");
229 }
230
231 Ok(ToolExecutionResult {
232 tool_name,
233 success: result.success,
234 output: result.output,
235 error: result.error,
236 duration_ms,
237 })
238 }
239
240 pub fn format_result(result: &ToolExecutionResult) -> String {
242 if result.success {
243 format!(
244 " {} ({}ms)\n{}",
245 result.tool_name,
246 result.duration_ms,
247 result.output.as_deref().unwrap_or("")
248 )
249 } else {
250 format!(
251 " {} FAILED ({}ms)\n Error: {}",
252 result.tool_name,
253 result.duration_ms,
254 result.error.as_deref().unwrap_or("unknown error")
255 )
256 }
257 }
258}
259
260impl Default for ToolExecutor {
261 fn default() -> Self {
262 Self::new()
263 }
264}
265
266#[cfg(test)]
267#[path = "tool_executor_tests.rs"]
268mod tests;