Size: 3.7 KB Modified: 1/07/2026 10:23 PM
using System;
using System.Text;
using System.Text.RegularExpressions;

namespace KaiChat.Services;

public static class ToolResultTextSanitizer
{
    private static readonly Regex ToolResultHeaderRegex = new(
        @"(?im)^[ \t]*(?:Tool execution results?\s*\(round\s+\d+\)|PRIVATE TOOL OUTPUT[^\r\n]*)\s*:?[ \t]*(?:\r?\n|$)",
        RegexOptions.Compiled);

    public static string RemoveEchoedToolResultBlocks(string? text)
    {
        if (string.IsNullOrEmpty(text))
            return text ?? "";

        var output = new StringBuilder(text.Length);
        var searchIndex = 0;

        while (searchIndex < text.Length)
        {
            var header = ToolResultHeaderRegex.Match(text, searchIndex);
            if (!header.Success)
            {
                output.Append(text, searchIndex, text.Length - searchIndex);
                break;
            }

            output.Append(text, searchIndex, header.Index - searchIndex);
            searchIndex = FindBlockEnd(text, header);
        }

        return NormalizeVisibleText(output.ToString());
    }

    private static int FindBlockEnd(string text, Match header)
    {
        var bodyStart = header.Index + header.Length;
        var nextHeader = ToolResultHeaderRegex.Match(text, bodyStart);

        if (header.Value.Contains("PRIVATE TOOL OUTPUT", StringComparison.OrdinalIgnoreCase))
        {
            var privateEnd = IndexOfIgnoreCase(text, "END PRIVATE TOOL OUTPUT", bodyStart);
            if (privateEnd >= 0 && (!nextHeader.Success || privateEnd < nextHeader.Index))
                return AdvancePastFollowingWhitespace(text, privateEnd + "END PRIVATE TOOL OUTPUT".Length);
        }

        var instructionStart = IndexOfIgnoreCase(text, "Use this tool result before answering Raymond.", bodyStart);
        if (instructionStart >= 0 && (!nextHeader.Success || instructionStart < nextHeader.Index))
            return AdvancePastFollowingWhitespace(text, FindInstructionEnd(text, instructionStart));

        return nextHeader.Success ? nextHeader.Index : text.Length;
    }

    private static int FindInstructionEnd(string text, int instructionStart)
    {
        string[] endPhrases =
        [
            "Do not keep sampling alternatives just because more files are available. If a tool call was skipped as a repeat, use the earlier result or answer directly instead of trying the same call again.",
            "If this was skipped as a repeat, use the earlier result or answer directly instead of trying the same call again.",
            "If you cannot fix it, explain the problem clearly to Raymond."
        ];

        var bestEnd = -1;
        foreach (var phrase in endPhrases)
        {
            var index = IndexOfIgnoreCase(text, phrase, instructionStart);
            if (index >= 0)
                bestEnd = Math.Max(bestEnd, index + phrase.Length);
        }

        if (bestEnd >= 0)
            return bestEnd;

        var paragraphEnd = text.IndexOf("\n\n", instructionStart, StringComparison.Ordinal);
        return paragraphEnd >= 0 ? paragraphEnd : text.Length;
    }

    private static int AdvancePastFollowingWhitespace(string text, int index)
    {
        while (index < text.Length && char.IsWhiteSpace(text[index]))
            index++;
        return index;
    }

    private static int IndexOfIgnoreCase(string text, string value, int startIndex)
    {
        return text.IndexOf(value, startIndex, StringComparison.OrdinalIgnoreCase);
    }

    private static string NormalizeVisibleText(string text)
    {
        var normalized = Regex.Replace(text, @"[ \t]+(\r?\n)", "$1");
        normalized = Regex.Replace(normalized, @"(\r?\n){3,}", Environment.NewLine + Environment.NewLine);
        return normalized.Trim();
    }
}
Offline