From 82aae32fe71f26da4a9354e14767a1015a63ed4e 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) + } +} +