diff --git a/internal/aiagent/worker.go b/internal/aiagent/worker.go index 2422d3a9..6a58dbfb 100644 --- a/internal/aiagent/worker.go +++ b/internal/aiagent/worker.go @@ -661,7 +661,7 @@ func (m *Manager) messageText(msg cmodels.Message) string { var trimmed string if msg.ContentType == cmodels.ContentTypeHTML { // Render full with the same link-keeping options as trimmed, else the prefix diffing below never matches. - if t := stringutil.HTML2TextWithLinks(msg.Content); t != "" { + if t := stringutil.HTML2TextMarkdownLinks(msg.Content); t != "" { full = t } trimmed = stringutil.HTML2TextNoQuotes(msg.Content) diff --git a/internal/conversation/models/models.go b/internal/conversation/models/models.go index 8e6c35c8..9b9ec370 100644 --- a/internal/conversation/models/models.go +++ b/internal/conversation/models/models.go @@ -568,7 +568,7 @@ func Transcript(msgs []Message, max int) string { } text := strings.TrimSpace(msg.TextContent) if msg.ContentType == ContentTypeHTML { - if t := stringutil.HTML2TextWithLinks(msg.Content); t != "" { + if t := stringutil.HTML2TextMarkdownLinks(msg.Content); t != "" { text = t } } diff --git a/internal/conversation/models/models_test.go b/internal/conversation/models/models_test.go index 97df683a..cea5be9e 100644 --- a/internal/conversation/models/models_test.go +++ b/internal/conversation/models/models_test.go @@ -33,7 +33,7 @@ func TestTranscript(t *testing.T) { } got := Transcript(msgs, 50) - want := "Customer: My payment on this page ( https://example.com/pay ) failed.\n" + + want := "Customer: My payment on [this page](https://example.com/pay) failed.\n" + "Agent: Looking into it.\n" + "Customer: Any update?\n" if got != want { diff --git a/internal/stringutil/emailquote.go b/internal/stringutil/emailquote.go index d3eb79b1..9b95fcda 100644 --- a/internal/stringutil/emailquote.go +++ b/internal/stringutil/emailquote.go @@ -25,14 +25,14 @@ var ( func HTML2TextNoQuotes(htmlContent string) string { doc, err := html.Parse(strings.NewReader(htmlContent)) if err != nil { - return TrimPlainTextQuotes(HTML2TextWithLinks(htmlContent)) + return TrimPlainTextQuotes(HTML2TextMarkdownLinks(htmlContent)) } pruneQuotedNodes(doc) var b strings.Builder if err := html.Render(&b, doc); err != nil { - return TrimPlainTextQuotes(HTML2TextWithLinks(htmlContent)) + return TrimPlainTextQuotes(HTML2TextMarkdownLinks(htmlContent)) } - return TrimPlainTextQuotes(HTML2TextWithLinks(b.String())) + return TrimPlainTextQuotes(HTML2TextMarkdownLinks(b.String())) } // TrimPlainTextQuotes strips a trailing quoted-reply block (">" lines, "On ... wrote:" and "Original Message" markers) from plain text. diff --git a/internal/stringutil/emailquote_test.go b/internal/stringutil/emailquote_test.go index 1d329f77..ed3fe714 100644 --- a/internal/stringutil/emailquote_test.go +++ b/internal/stringutil/emailquote_test.go @@ -41,7 +41,7 @@ func TestHTML2TextNoQuotes(t *testing.T) { { name: "protonmail", html: `
Hello!
See the guide for steps.
`, - want: "See the guide ( https://example.com/guide ) for steps.", + want: "See [the guide](https://example.com/guide) for steps.", }, { name: "link text equal to url not duplicated", @@ -318,10 +318,30 @@ func TestHTML2TextWithLinks(t *testing.T) { html: `No links here.
`, want: "No links here.", }, + { + name: "nested markup inside the anchor flattens to link text", + html: ``, + want: "[Pay now](https://example.com/x)", + }, + { + name: "brackets in link text are escaped", + html: ``, + want: `[Docs \[beta\]](https://example.com)`, + }, + { + name: "url with parentheses is wrapped in angle brackets", + html: ``, + want: "[See](Top of page.
`, + want: "Top of page.", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := HTML2TextWithLinks(tt.html); got != tt.want { + if got := HTML2TextMarkdownLinks(tt.html); got != tt.want { t.Errorf("got %q, want %q", got, tt.want) } })