@@ -0,0 +1,24 @@
+package ansiext
+
+import (
+	"strings"
+
+	"github.com/charmbracelet/x/ansi"
+)
+
+// Escape replaces control characters with their Unicode Control Picture
+// representations to ensure they are displayed correctly in the UI.
+func Escape(content string) string {
+	var sb strings.Builder
+	for _, r := range content {
+		switch {
+		case r >= 0 && r <= 0x1f: // Control characters 0x00-0x1F
+			sb.WriteRune('\u2400' + r)
+		case r == ansi.DEL:
+			sb.WriteRune('\u2421')
+		default:
+			sb.WriteRune(r)
+		}
+	}
+	return sb.String()
+}
  
  
  
    
    @@ -6,6 +6,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/charmbracelet/crush/internal/ansiext"
 	"github.com/charmbracelet/crush/internal/fsext"
 	"github.com/charmbracelet/crush/internal/llm/agent"
 	"github.com/charmbracelet/crush/internal/llm/tools"
@@ -702,7 +703,7 @@ func renderPlainContent(v *toolCallCmp, content string) string {
 		if i >= responseContextHeight {
 			break
 		}
-		ln = escapeContent(ln)
+		ln = ansiext.Escape(ln)
 		ln = " " + ln // left padding
 		if len(ln) > width {
 			ln = v.fit(ln, width)
@@ -740,7 +741,7 @@ func renderCodeContent(v *toolCallCmp, path, content string, offset int) string
 
 	lines := strings.Split(truncated, "\n")
 	for i, ln := range lines {
-		lines[i] = escapeContent(ln)
+		lines[i] = ansiext.Escape(ln)
 	}
 
 	highlighted, _ := highlight.SyntaxHighlight(strings.Join(lines, "\n"), path, t.BgBase)
@@ -816,20 +817,3 @@ func prettifyToolName(name string) string {
 		return name
 	}
 }
-
-// escapeContent replaces control characters with their Unicode Control Picture
-// representations to ensure they are displayed correctly in the UI.
-func escapeContent(content string) string {
-	var sb strings.Builder
-	for _, r := range content {
-		switch {
-		case r >= 0 && r <= 0x1f: // Control characters 0x00-0x1F
-			sb.WriteRune('\u2400' + r)
-		case r == ansi.DEL:
-			sb.WriteRune('\u2421')
-		default:
-			sb.WriteRune(r)
-		}
-	}
-	return sb.String()
-}