@@ -7,7 +7,6 @@ use std::sync::Arc;
77use std:: time:: { Duration , Instant } ;
88
99use async_trait:: async_trait;
10- use cortex_common:: DEFAULT_BATCH_TIMEOUT_SECS ;
1110use futures:: future:: join_all;
1211use serde:: { Deserialize , Serialize } ;
1312use serde_json:: { Value , json} ;
@@ -21,6 +20,10 @@ use crate::tools::spec::{ToolDefinition, ToolHandler, ToolResult};
2120/// Maximum number of tools that can be executed in a batch.
2221pub const MAX_BATCH_SIZE : usize = 10 ;
2322
23+ /// Default timeout for individual tool execution in seconds.
24+ /// This prevents a single tool from consuming the entire batch timeout.
25+ pub const DEFAULT_TOOL_TIMEOUT_SECS : u64 = 60 ;
26+
2427/// Tools that cannot be called within a batch (prevent recursion).
2528/// Note: Task is now allowed to enable parallel task execution.
2629pub const DISALLOWED_TOOLS : & [ & str ] = & [ "Batch" , "batch" , "Agent" , "agent" ] ;
@@ -43,6 +46,10 @@ pub struct BatchToolArgs {
4346 /// Optional timeout in seconds for the entire batch (default: 300s).
4447 #[ serde( default ) ]
4548 pub timeout_secs : Option < u64 > ,
49+ /// Optional timeout in seconds for individual tools (default: 60s).
50+ /// This prevents a single tool from consuming the entire batch timeout.
51+ #[ serde( default ) ]
52+ pub tool_timeout_secs : Option < u64 > ,
4653}
4754
4855/// Result of a single tool call within the batch.
@@ -156,7 +163,7 @@ impl BatchToolHandler {
156163 & self ,
157164 calls : Vec < BatchToolCall > ,
158165 context : & ToolContext ,
159- timeout_duration : Duration ,
166+ tool_timeout : Duration ,
160167 ) -> BatchResult {
161168 let start_time = Instant :: now ( ) ;
162169 let results = Arc :: new ( Mutex :: new ( Vec :: with_capacity ( calls. len ( ) ) ) ) ;
@@ -174,9 +181,9 @@ impl BatchToolHandler {
174181 async move {
175182 let call_start = Instant :: now ( ) ;
176183
177- // Execute with per-call timeout (use batch timeout for each call)
184+ // Execute with per-tool timeout to prevent single tools from blocking others
178185 let execution_result = timeout (
179- timeout_duration ,
186+ tool_timeout ,
180187 executor. execute_tool ( & call. tool , call. arguments , & ctx) ,
181188 )
182189 . await ;
@@ -200,19 +207,23 @@ impl BatchToolHandler {
200207 duration_ms,
201208 } ,
202209 Ok ( Err ( e) ) => BatchCallResult {
203- tool : tool_name,
210+ tool : tool_name. clone ( ) ,
204211 index,
205212 success : false ,
206213 result : None ,
207214 error : Some ( format ! ( "Execution error: {}" , e) ) ,
208215 duration_ms,
209216 } ,
210217 Err ( _) => BatchCallResult {
211- tool : tool_name,
218+ tool : tool_name. clone ( ) ,
212219 index,
213220 success : false ,
214221 result : None ,
215- error : Some ( format ! ( "Timeout after {}s" , timeout_duration. as_secs( ) ) ) ,
222+ error : Some ( format ! (
223+ "Tool '{}' timed out after {}s" ,
224+ tool_name,
225+ tool_timeout. as_secs( )
226+ ) ) ,
216227 duration_ms,
217228 } ,
218229 } ;
@@ -326,13 +337,13 @@ impl ToolHandler for BatchToolHandler {
326337 // Validate calls
327338 self . validate_calls ( & args. calls ) ?;
328339
329- // Determine timeout
330- let timeout_secs = args. timeout_secs . unwrap_or ( DEFAULT_BATCH_TIMEOUT_SECS ) ;
331- let timeout_duration = Duration :: from_secs ( timeout_secs ) ;
340+ // Determine per-tool timeout (prevents single tool from blocking others)
341+ let tool_timeout_secs = args. tool_timeout_secs . unwrap_or ( DEFAULT_TOOL_TIMEOUT_SECS ) ;
342+ let tool_timeout = Duration :: from_secs ( tool_timeout_secs ) ;
332343
333344 // Execute all tools in parallel
334345 let batch_result = self
335- . execute_parallel ( args. calls , context, timeout_duration )
346+ . execute_parallel ( args. calls , context, tool_timeout )
336347 . await ;
337348
338349 // Format output
@@ -382,6 +393,12 @@ pub fn batch_tool_definition() -> ToolDefinition {
382393 "description" : "Optional timeout in seconds for the entire batch execution (default: 300)" ,
383394 "minimum" : 1 ,
384395 "maximum" : 600
396+ } ,
397+ "tool_timeout_secs" : {
398+ "type" : "integer" ,
399+ "description" : "Optional timeout in seconds for individual tool execution (default: 60). Prevents a single tool from consuming the entire batch timeout." ,
400+ "minimum" : 1 ,
401+ "maximum" : 300
385402 }
386403 }
387404 } ) ,
@@ -407,6 +424,7 @@ pub async fn execute_batch(
407424 } )
408425 . collect ( ) ,
409426 timeout_secs : None ,
427+ tool_timeout_secs : None ,
410428 } ;
411429
412430 let arguments = serde_json:: to_value ( args)
0 commit comments