diff --git a/src/basic/mod.rs b/src/basic/mod.rs index 3b29251a3..def0abb8e 100644 --- a/src/basic/mod.rs +++ b/src/basic/mod.rs @@ -1410,6 +1410,9 @@ impl ScriptService { /// Transforms: SELECT var ... CASE "value" ... END SELECT /// Into: if var == "value" { ... } else if var == "value2" { ... } /// Note: We use if-else instead of match because 'match' is a reserved keyword in Rhai + /// + /// IMPORTANT: This function strips 'let ' keywords from assignment statements inside CASE blocks + /// to avoid creating local variables that shadow outer scope variables. pub fn convert_select_case_syntax(script: &str) -> String { let mut result = String::new(); let mut lines: Vec<&str> = script.lines().collect(); @@ -1417,6 +1420,20 @@ impl ScriptService { log::info!("[TOOL] Converting SELECT/CASE syntax to if-else chains"); + // Helper function to strip 'let ' from the beginning of a line + // This is needed because convert_if_then_syntax adds 'let' to all assignments, + // but inside CASE blocks we want to modify outer variables, not create new ones + fn strip_let_from_assignment(line: &str) -> String { + let trimmed = line.trim(); + let trimmed_lower = trimmed.to_lowercase(); + if trimmed_lower.starts_with("let ") && trimmed.contains('=') { + // This is a 'let' assignment - strip the 'let ' keyword + trimmed[4..].trim().to_string() + } else { + trimmed.to_string() + } + } + while i < lines.len() { let trimmed = lines[i].trim(); let upper = trimmed.to_uppercase(); @@ -1450,9 +1467,11 @@ impl ScriptService { if in_case { for body_line in ¤t_case_body { result.push_str(" "); - result.push_str(body_line); + // Strip 'let ' from assignments to avoid creating local variables + let processed_line = strip_let_from_assignment(body_line); + result.push_str(&processed_line); // Add semicolon if line doesn't have one - if !body_line.ends_with(';') && !body_line.ends_with('{') && !body_line.ends_with('}') { + if !processed_line.ends_with(';') && !processed_line.ends_with('{') && !processed_line.ends_with('}') { result.push(';'); } result.push('\n'); @@ -1471,9 +1490,11 @@ impl ScriptService { if in_case { for body_line in ¤t_case_body { result.push_str(" "); - result.push_str(body_line); + // Strip 'let ' from assignments to avoid creating local variables + let processed_line = strip_let_from_assignment(body_line); + result.push_str(&processed_line); // Add semicolon if line doesn't have one - if !body_line.ends_with(';') && !body_line.ends_with('{') && !body_line.ends_with('}') { + if !processed_line.ends_with(';') && !processed_line.ends_with('{') && !processed_line.ends_with('}') { result.push(';'); } result.push('\n'); @@ -1490,9 +1511,11 @@ impl ScriptService { if in_case { for body_line in ¤t_case_body { result.push_str(" "); - result.push_str(body_line); + // Strip 'let ' from assignments to avoid creating local variables + let processed_line = strip_let_from_assignment(body_line); + result.push_str(&processed_line); // Add semicolon if line doesn't have one - if !body_line.ends_with(';') && !body_line.ends_with('{') && !body_line.ends_with('}') { + if !processed_line.ends_with(';') && !processed_line.ends_with('{') && !processed_line.ends_with('}') { result.push(';'); } result.push('\n'); diff --git a/src/core/bot/mod.rs b/src/core/bot/mod.rs index 263756bf9..e0024a2ed 100644 --- a/src/core/bot/mod.rs +++ b/src/core/bot/mod.rs @@ -654,6 +654,7 @@ impl BotOrchestrator { let mut in_analysis = false; let mut tool_call_buffer = String::new(); // Accumulate potential tool call JSON chunks let mut accumulating_tool_call = false; // Track if we're currently accumulating a tool call + let mut tool_was_executed = false; // Track if a tool was executed to avoid duplicate final message let handler = llm_models::get_handler(&model); info!("[STREAM_START] Entering stream processing loop for model: {}", model); @@ -835,6 +836,7 @@ impl BotOrchestrator { // Clear the tool_call_buffer since we found and executed a tool call tool_call_buffer.clear(); accumulating_tool_call = false; // Reset accumulation flag + tool_was_executed = true; // Mark that a tool was executed // Continue to next chunk continue; } @@ -1004,12 +1006,16 @@ impl BotOrchestrator { #[cfg(not(feature = "chat"))] let suggestions: Vec = Vec::new(); + // When a tool was executed, the content was already sent as streaming chunks + // (pre-tool text + tool result). Sending full_response again would duplicate it. + let final_content = if tool_was_executed { String::new() } else { full_response }; + let final_response = BotResponse { bot_id: message.bot_id, user_id: message.user_id, session_id: message.session_id, channel: message.channel, - content: full_response, + content: final_content, message_type: MessageType::BOT_RESPONSE, stream_token: None, is_complete: true,