From ddc4723849cde491c74cb48b990799892cdd4e1a Mon Sep 17 00:00:00 2001
From: dj-oyu <68707227+dj-oyu@users.noreply.github.com>
Date: Fri, 20 Feb 2026 00:14:09 +0900
Subject: [PATCH] telegram: strip think blocks and split long outbound messages
---
pkg/channels/telegram.go | 55 +++++++++++++++++++++++++++--------
pkg/channels/telegram_test.go | 22 ++++++++++++++
2 files changed, 65 insertions(+), 12 deletions(-)
create mode 100644 pkg/channels/telegram_test.go
diff --git a/pkg/channels/telegram.go b/pkg/channels/telegram.go
index 24b82b557..b318ceb56 100644
--- a/pkg/channels/telegram.go
+++ b/pkg/channels/telegram.go
@@ -45,6 +45,10 @@ func (c *thinkingCancel) Cancel() {
}
}
+const telegramMaxMessageChars = 3900
+
+var thinkBlockPattern = regexp.MustCompile(`(?is).*?`)
+
func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChannel, error) {
var opts []telego.BotOption
telegramCfg := cfg.Channels.Telegram
@@ -157,35 +161,62 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
c.stopThinking.Delete(msg.ChatID)
}
- htmlContent := markdownToTelegramHTML(msg.Content)
+ cleanContent := sanitizeTelegramOutgoingContent(msg.Content)
+ chunks := utils.SplitMessage(cleanContent, telegramMaxMessageChars)
+ if len(chunks) == 0 {
+ chunks = []string{cleanContent}
+ }
// Try to edit placeholder
+ firstChunkSent := false
if pID, ok := c.placeholders.Load(msg.ChatID); ok {
c.placeholders.Delete(msg.ChatID)
- editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), htmlContent)
+ editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), markdownToTelegramHTML(chunks[0]))
editMsg.ParseMode = telego.ModeHTML
if _, err = c.bot.EditMessageText(ctx, editMsg); err == nil {
- return nil
+ firstChunkSent = true
}
// Fallback to new message if edit fails
}
- tgMsg := tu.Message(tu.ID(chatID), htmlContent)
- tgMsg.ParseMode = telego.ModeHTML
+ sendChunk := func(text string) error {
+ tgMsg := tu.Message(tu.ID(chatID), markdownToTelegramHTML(text))
+ tgMsg.ParseMode = telego.ModeHTML
+ if _, sendErr := c.bot.SendMessage(ctx, tgMsg); sendErr != nil {
+ logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]interface{}{
+ "error": sendErr.Error(),
+ })
+ fallbackMsg := tu.Message(tu.ID(chatID), text)
+ fallbackMsg.ParseMode = ""
+ _, sendErr = c.bot.SendMessage(ctx, fallbackMsg)
+ return sendErr
+ }
+ return nil
+ }
- if _, err = c.bot.SendMessage(ctx, tgMsg); err != nil {
- logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]interface{}{
- "error": err.Error(),
- })
- tgMsg.ParseMode = ""
- _, err = c.bot.SendMessage(ctx, tgMsg)
- return err
+ startIdx := 0
+ if firstChunkSent {
+ startIdx = 1
+ }
+ for i := startIdx; i < len(chunks); i++ {
+ if err = sendChunk(chunks[i]); err != nil {
+ return err
+ }
}
return nil
}
+func sanitizeTelegramOutgoingContent(content string) string {
+ cleaned := thinkBlockPattern.ReplaceAllString(content, "")
+ cleaned = strings.TrimSpace(cleaned)
+ if cleaned == "" {
+ return "(empty response)"
+ }
+ return cleaned
+}
+
func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Message) error {
if message == nil {
return fmt.Errorf("message is nil")
diff --git a/pkg/channels/telegram_test.go b/pkg/channels/telegram_test.go
new file mode 100644
index 000000000..7c6f9fd16
--- /dev/null
+++ b/pkg/channels/telegram_test.go
@@ -0,0 +1,22 @@
+package channels
+
+import "testing"
+
+func TestSanitizeTelegramOutgoingContent_RemovesThinkBlock(t *testing.T) {
+ in := "\nsecret reasoning\n\n\nユーザー向け本文"
+ got := sanitizeTelegramOutgoingContent(in)
+ want := "ユーザー向け本文"
+ if got != want {
+ t.Fatalf("sanitizeTelegramOutgoingContent() = %q, want %q", got, want)
+ }
+}
+
+func TestSanitizeTelegramOutgoingContent_EmptyAfterThink(t *testing.T) {
+ in := "only reasoning"
+ got := sanitizeTelegramOutgoingContent(in)
+ want := "(empty response)"
+ if got != want {
+ t.Fatalf("sanitizeTelegramOutgoingContent() = %q, want %q", got, want)
+ }
+}
+