diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index 3f6ead5..70aae0a 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -72,24 +72,28 @@ fn encode_image_to_base64(path: &str) -> Result<(String, String), std::io::Error /// Truncate tool result if it exceeds MAX_TOOL_RESULT_CHARS. /// Preserves the end of the output as it often contains the conclusion/useful result. fn truncate_tool_result(output: &str) -> String { - if output.len() <= MAX_TOOL_RESULT_CHARS { + let char_count = output.chars().count(); + if char_count <= MAX_TOOL_RESULT_CHARS { return output.to_string(); } - let truncated_start_len = output.len().saturating_sub(TRUNCATION_SUFFIX_LEN); + let truncated_start_len = char_count.saturating_sub(TRUNCATION_SUFFIX_LEN); if truncated_start_len > MAX_TOOL_RESULT_CHARS { // Even after removing suffix, still too long - take from beginning + let head_len = MAX_TOOL_RESULT_CHARS - 100; + let head: String = output.chars().take(head_len).collect(); format!( "{}...\n\n[Output truncated - {} characters removed]", - &output[..MAX_TOOL_RESULT_CHARS - 100], - output.len() - MAX_TOOL_RESULT_CHARS + 100 + head, + char_count - MAX_TOOL_RESULT_CHARS + 100 ) } else { // Keep most of the end which usually contains the useful result + let tail: String = output.chars().skip(truncated_start_len).collect(); format!( "...\n\n[Output truncated - {} characters removed]\n\n{}", truncated_start_len, - &output[truncated_start_len..] + tail ) } } @@ -1036,6 +1040,16 @@ mod tests { assert_eq!(provider_message.tool_calls.as_ref().unwrap()[0].name, "calculator"); } + #[test] + fn test_truncate_tool_result_handles_utf8_char_boundaries() { + let input = "范".repeat(MAX_TOOL_RESULT_CHARS + 500); + + let output = truncate_tool_result(&input); + + assert!(output.contains("Output truncated")); + assert!(output.is_char_boundary(output.len())); + } + #[test] fn test_did_successfully_write_memory_only_accepts_successful_put_or_update() { let tool_call = ToolCall {