diff --git a/crates/g3-core/src/correct_filter_json.rs b/crates/g3-core/src/correct_filter_json.rs index a927d93..fd4f7aa 100644 --- a/crates/g3-core/src/correct_filter_json.rs +++ b/crates/g3-core/src/correct_filter_json.rs @@ -14,6 +14,7 @@ thread_local! { } #[derive(Debug, Clone)] +#[allow(dead_code)] struct CorrectJsonToolState { suppression_mode: bool, brace_depth: i32, @@ -22,7 +23,8 @@ struct CorrectJsonToolState { } impl CorrectJsonToolState { - fn new() -> Self { + #[allow(dead_code)] +fn new() -> Self { Self { suppression_mode: false, brace_depth: 0, @@ -31,7 +33,8 @@ impl CorrectJsonToolState { } } - fn reset(&mut self) { + #[allow(dead_code)] +fn reset(&mut self) { self.suppression_mode = false; self.brace_depth = 0; self.buffer.clear(); @@ -40,6 +43,7 @@ impl CorrectJsonToolState { } // Correct implementation according to specification +#[allow(dead_code)] pub fn correct_filter_json_tool_calls(content: &str) -> String { CORRECT_JSON_TOOL_STATE.with(|state| { let mut state = state.borrow_mut(); @@ -132,6 +136,7 @@ pub fn correct_filter_json_tool_calls(content: &str) -> String { // Helper function to extract content with JSON tool call filtered out // Returns everything except the JSON between the first '{' and last '}' (inclusive) +#[allow(dead_code)] fn extract_content_without_json(full_content: &str, json_start: usize) -> String { // Find the end of the JSON using proper brace counting with string handling let mut brace_depth = 0; @@ -174,6 +179,7 @@ fn extract_content_without_json(full_content: &str, json_start: usize) -> String } // Reset function for testing +#[allow(dead_code)] pub fn reset_correct_json_tool_state() { CORRECT_JSON_TOOL_STATE.with(|state| { let mut state = state.borrow_mut(); diff --git a/crates/g3-core/src/final_filter_json.rs b/crates/g3-core/src/final_filter_json.rs index eb7df19..0239ece 100644 --- a/crates/g3-core/src/final_filter_json.rs +++ b/crates/g3-core/src/final_filter_json.rs @@ -181,6 +181,7 @@ fn extract_final_content(full_content: &str, json_start: usize) -> String { } // Reset function for testing +#[allow(dead_code)] pub fn reset_final_json_tool_state() { FINAL_JSON_TOOL_STATE.with(|state| { let mut state = state.borrow_mut(); diff --git a/crates/g3-core/src/fixed_filter_json.rs b/crates/g3-core/src/fixed_filter_json.rs index b9d3f13..3352964 100644 --- a/crates/g3-core/src/fixed_filter_json.rs +++ b/crates/g3-core/src/fixed_filter_json.rs @@ -14,6 +14,7 @@ thread_local! { } #[derive(Debug, Clone)] +#[allow(dead_code)] struct FixedJsonToolState { suppression_mode: bool, brace_depth: i32, @@ -23,7 +24,8 @@ struct FixedJsonToolState { } impl FixedJsonToolState { - fn new() -> Self { + #[allow(dead_code)] +fn new() -> Self { Self { suppression_mode: false, brace_depth: 0, @@ -33,7 +35,8 @@ impl FixedJsonToolState { } } - fn reset(&mut self) { + #[allow(dead_code)] +fn reset(&mut self) { self.suppression_mode = false; self.brace_depth = 0; self.buffer.clear(); @@ -43,6 +46,7 @@ impl FixedJsonToolState { } // FINAL CORRECTED implementation according to specification +#[allow(dead_code)] pub fn fixed_filter_json_tool_calls(content: &str) -> String { if content.is_empty() { return String::new(); @@ -162,6 +166,7 @@ pub fn fixed_filter_json_tool_calls(content: &str) -> String { // Helper function to extract content with JSON tool call filtered out // Returns everything except the JSON between the first '{' and last '}' (inclusive) +#[allow(dead_code)] fn extract_fixed_content(full_content: &str, json_start: usize) -> String { // Find the end of the JSON using proper brace counting with string handling let mut brace_depth = 0; @@ -204,6 +209,7 @@ fn extract_fixed_content(full_content: &str, json_start: usize) -> String { } // Reset function for testing +#[allow(dead_code)] pub fn reset_fixed_json_tool_state() { FIXED_JSON_TOOL_STATE.with(|state| { let mut state = state.borrow_mut(); diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 4f60ae8..742f9be 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -32,6 +32,7 @@ use anyhow::Result; use g3_config::Config; use g3_execution::CodeExecutor; use g3_providers::{CompletionRequest, Message, MessageRole, ProviderRegistry, Tool}; +#[allow(unused_imports)] use regex::Regex; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -755,31 +756,31 @@ Do not explain what you're going to do - just do it by calling the tools. When you need to execute a tool, write ONLY the JSON tool call on a new line: -{\"tool\": \"tool_name\", \"args\": {\"param\": \"value\"}} +{\"tool\": \"tool_name\", \"args\": {\"param\": \"value\"} The tool will execute immediately and you'll receive the result (success or error) to continue with. # Available Tools - **shell**: Execute shell commands - - Format: {\"tool\": \"shell\", \"args\": {\"command\": \"your_command_here\"}} - - Example: {\"tool\": \"shell\", \"args\": {\"command\": \"ls ~/Downloads\"}} + - Format: {\"tool\": \"shell\", \"args\": {\"command\": \"your_command_here\"} + - Example: {\"tool\": \"shell\", \"args\": {\"command\": \"ls ~/Downloads\"} - **read_file**: Read the contents of a file (supports partial reads via start/end) - - Format: {\"tool\": \"read_file\", \"args\": {\"file_path\": \"path/to/file\", \"start\": 0, \"end\": 100}} - - Example: {\"tool\": \"read_file\", \"args\": {\"file_path\": \"src/main.rs\"}} - - Example (partial): {\"tool\": \"read_file\", \"args\": {\"file_path\": \"large.log\", \"start\": 0, \"end\": 1000}} + - Format: {\"tool\": \"read_file\", \"args\": {\"file_path\": \"path/to/file\", \"start\": 0, \"end\": 100} + - Example: {\"tool\": \"read_file\", \"args\": {\"file_path\": \"src/main.rs\"} + - Example (partial): {\"tool\": \"read_file\", \"args\": {\"file_path\": \"large.log\", \"start\": 0, \"end\": 1000} - **write_file**: Write content to a file (creates or overwrites) - - Format: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"path/to/file\", \"content\": \"file content\"}} - - Example: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"src/lib.rs\", \"content\": \"pub fn hello() {}\"}} + - Format: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"path/to/file\", \"content\": \"file content\"} + - Example: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"src/lib.rs\", \"content\": \"pub fn hello() {}\"} - **str_replace**: Replace text in a file using a diff - - Format: {\"tool\": \"str_replace\", \"args\": {\"file_path\": \"path/to/file\", \"diff\": \"--- old\\n-old text\\n+++ new\\n+new text\"}} - - Example: {\"tool\": \"str_replace\", \"args\": {\"file_path\": \"src/main.rs\", \"diff\": \"--- old\\n-old_code();\\n+++ new\\n+new_code();\"}} + - Format: {\"tool\": \"str_replace\", \"args\": {\"file_path\": \"path/to/file\", \"diff\": \"--- old\\n-old text\\n+++ new\\n+new text\"} + - Example: {\"tool\": \"str_replace\", \"args\": {\"file_path\": \"src/main.rs\", \"diff\": \"--- old\\n-old_code();\\n+++ new\\n+new_code();\"} - **final_output**: Signal task completion with a detailed summary of work done in markdown format - - Format: {\"tool\": \"final_output\", \"args\": {\"summary\": \"what_was_accomplished\"}} + - Format: {\"tool\": \"final_output\", \"args\": {\"summary\": \"what_was_accomplished\"} # Instructions @@ -1507,7 +1508,17 @@ The tool will execute immediately and you'll receive the result (success or erro } let exec_start = Instant::now(); - let tool_result = self.execute_tool(&tool_call).await?; + // Add 8-minute timeout for tool execution + let tool_result = match tokio::time::timeout( + Duration::from_secs(8 * 60), // 8 minutes + self.execute_tool(&tool_call) + ).await { + Ok(result) => result?, + Err(_) => { + warn!("Tool call {} timed out after 8 minutes", tool_call.tool); + format!("❌ Tool execution timed out after 8 minutes") + } + }; let exec_duration = exec_start.elapsed(); total_execution_time += exec_duration; @@ -2380,167 +2391,11 @@ The tool will execute immediately and you'll receive the result (success or erro } } -use std::cell::RefCell; - -// Thread-local state for tracking JSON tool call suppression -thread_local! { - static JSON_TOOL_STATE: RefCell = RefCell::new(JsonToolState::new()); -} - -#[derive(Debug, Clone)] -struct JsonToolState { - suppression_mode: bool, - brace_depth: i32, - buffer: String, -} - -impl JsonToolState { - fn new() -> Self { - Self { - suppression_mode: false, - brace_depth: 0, - buffer: String::new(), - } - } - - fn reset(&mut self) { - self.suppression_mode = false; - self.brace_depth = 0; - self.buffer.clear(); - } -} - -// Helper function to filter JSON tool calls from display content +// Helper function to filter JSON tool calls from display content (unused) +#[allow(dead_code)] fn filter_json_tool_calls(content: &str) -> String { - JSON_TOOL_STATE.with(|state| { - let mut state = state.borrow_mut(); - - // If we're already in suppression mode, continue tracking - if state.suppression_mode { - // Add content to buffer for tracking - state.buffer.push_str(content); - - // Count braces to track JSON nesting depth - for ch in content.chars() { - match ch { - '{' => state.brace_depth += 1, - '}' => { - state.brace_depth -= 1; - // Exit suppression mode when we've closed all braces - if state.brace_depth <= 0 { - debug!("Exiting JSON tool suppression mode - completed JSON object"); - state.reset(); - // Check if there's any content after the JSON - if let Some(close_pos) = content.rfind('}') { - if close_pos + 1 < content.len() { - // Return any content after the JSON - return content[close_pos + 1..].to_string(); - } - } - } - } - _ => {} - } - } - // While in suppression mode, return empty string - return String::new(); - } - - // Check if content contains any JSON tool call patterns - let patterns = [ - r#"{"tool":"#, - r#"{"tool"#, // Partial pattern - r#"{"too"#, // Even more partial - r#"{"to"#, // Very partial - r#"{"t"#, // Extremely partial - r#"{ "tool":"#, - r#"{"tool" :"#, - r#"{ "tool" :"#, - r#"{"tool": "#, // Pattern with space after colon - r#"{ "tool": "#, // Pattern with spaces - ]; - - // Check if any pattern is found in the content - for pattern in &patterns { - if let Some(pos) = content.find(pattern) { - debug!("Detected JSON tool call pattern '{}' at position {} - entering suppression mode", pattern, pos); - // Found a tool call pattern - enter suppression mode - state.suppression_mode = true; - state.brace_depth = 0; - state.buffer.clear(); - state.buffer.push_str(&content[pos..]); - - // Count braces in the remaining content after the pattern - for ch in content[pos..].chars() { - match ch { - '{' => state.brace_depth += 1, - '}' => { - state.brace_depth -= 1; - if state.brace_depth <= 0 { - debug!("JSON tool call completed in same chunk - exiting suppression mode"); - state.reset(); - break; - } - } - _ => {} - } - } - - // Return any content before the JSON tool call - if pos > 0 { - return content[..pos].to_string(); - } else { - return String::new(); - } - } - } - - // Check for partial JSON patterns that might be split across chunks - let trimmed = content.trim(); - - // Special case: single character chunks that might be part of a JSON tool call - if content.len() <= 3 && state.buffer.len() < 20 { - // Accumulate small chunks to check for patterns - state.buffer.push_str(content); - if state.buffer.contains(r#"{"tool"#) || state.buffer.contains(r#"{ "tool"#) { - state.suppression_mode = true; - state.brace_depth = state.buffer.chars().filter(|&c| c == '{').count() as i32; - return String::new(); - } - } - - // Check if this looks like the start of a JSON tool call (larger chunks) - let pattern = Regex::new(r#"\s*\{\s*"tool"\s*:"#).unwrap(); - if pattern.is_match(trimmed) { - // This might be the start of a JSON tool call - // Enter suppression mode preemptively - debug!("Detected potential JSON tool call start - entering suppression mode"); - state.suppression_mode = true; - state.brace_depth = 0; - state.buffer.clear(); - state.buffer.push_str(content); - - // Count braces - for ch in content.chars() { - match ch { - '{' => state.brace_depth += 1, - '}' => { - state.brace_depth -= 1; - if state.brace_depth <= 0 { - state.reset(); - break; - } - } - _ => {} - } - } - - return String::new(); - } - - // No JSON tool call detected, return content as-is - content.to_string() - }) + // This function is no longer used - replaced by final_filter_json::final_filter_json_tool_calls + content.to_string() } // Apply unified diff to an input string with optional [start, end) bounds @@ -2813,7 +2668,7 @@ fn shell_escape_command(command: &str) -> String { fn fix_nested_quotes_in_shell_command(json_str: &str) -> String { let mut _result = String::new(); let _chars = json_str.chars().peekable(); - // Example: {"tool": "shell", "args": {"command": "python -c 'import os; print("hello")'"}} + // Example: {"tool": "shell", "args": {"command": "python -c 'import os; print("hello")'"} // Look for the pattern: "command": " if let Some(command_start) = json_str.find(r#""command": ""#) { diff --git a/crates/g3-core/src/new_filter_json.rs b/crates/g3-core/src/new_filter_json.rs index e8741b9..34af55f 100644 --- a/crates/g3-core/src/new_filter_json.rs +++ b/crates/g3-core/src/new_filter_json.rs @@ -11,6 +11,7 @@ thread_local! { } #[derive(Debug, Clone)] +#[allow(dead_code)] struct NewJsonToolState { suppression_mode: bool, brace_depth: i32, @@ -19,7 +20,8 @@ struct NewJsonToolState { } impl NewJsonToolState { - fn new() -> Self { + #[allow(dead_code)] +fn new() -> Self { Self { suppression_mode: false, brace_depth: 0, @@ -28,7 +30,8 @@ impl NewJsonToolState { } } - fn reset(&mut self) { + #[allow(dead_code)] +fn reset(&mut self) { self.suppression_mode = false; self.brace_depth = 0; self.accumulated_content.clear(); @@ -41,6 +44,7 @@ impl NewJsonToolState { // 2. Enter suppression mode and use brace counting to find complete JSON // 3. Only elide JSON content between first '{' and last '}' (inclusive) // 4. Return everything else as the final filtered string +#[allow(dead_code)] pub fn new_filter_json_tool_calls(content: &str) -> String { NEW_JSON_TOOL_STATE.with(|state| { let mut state = state.borrow_mut(); @@ -136,6 +140,7 @@ pub fn new_filter_json_tool_calls(content: &str) -> String { // Helper function to extract content with JSON tool call filtered out // Returns everything except the JSON between the first '{' and last '}' (inclusive) +#[allow(dead_code)] fn extract_filtered_content(full_content: &str, json_start: usize) -> String { // Find the end of the JSON using proper brace counting let mut brace_depth = 0; @@ -178,6 +183,7 @@ fn extract_filtered_content(full_content: &str, json_start: usize) -> String { } // Reset function for testing +#[allow(dead_code)] pub fn reset_new_json_tool_state() { NEW_JSON_TOOL_STATE.with(|state| { let mut state = state.borrow_mut();