From 216b8abac6676cf67c1f0dfdec2fcfcf4bf7ad28 Mon Sep 17 00:00:00 2001 From: viettranx Date: Mon, 30 Mar 2026 11:45:33 +0700 Subject: [PATCH] fix: Telegram @mention linking + conditional media read prompts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Convert @mentions to in HTML formatter (Telegram Bot API does NOT auto-link @username in parse_mode: HTML) - Protect @mentions from italic regex mangling underscored usernames - Change read_image/read_audio/read_video system prompt from "REQUIRED" to conditional — model only reads media when relevant to user's question - Annotate history media tags so model distinguishes group context images from current-turn images --- internal/agent/systemprompt.go | 10 +++++----- internal/channels/telegram/format.go | 22 ++++++++++++++++++++++ internal/channels/telegram/handlers.go | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/internal/agent/systemprompt.go b/internal/agent/systemprompt.go index 3665138e..ae428edd 100644 --- a/internal/agent/systemprompt.go +++ b/internal/agent/systemprompt.go @@ -98,9 +98,9 @@ var coreToolSummaries = map[string]string{ "session_status": "Show session status (model, tokens, compaction count)", "sessions_history": "Fetch message history for a session", "sessions_send": "Send a message into another session", - "read_image": "REQUIRED when you see tags — call this tool with the path attribute to analyze the image. You CAN see images through this tool. Never say you cannot see images", - "read_audio": "REQUIRED when you see tags — call this tool to transcribe or analyze audio content", - "read_video": "REQUIRED when you see tags — call this tool to analyze video content", + "read_image": "Analyze images when the user asks about them or when understanding the image is needed to answer. Call with the path attribute from tags. You CAN see images through this tool. Never say you cannot see images", + "read_audio": "Analyze audio when the user asks about it or references audio content. Call with the media_id from tags. You CAN hear audio through this tool", + "read_video": "Analyze video when the user asks about it or references video content. Call with the media_id from tags. You CAN see video through this tool", "create_video": "Generate videos from text descriptions using AI", "read_document": "Analyze documents (PDF, DOCX, etc.) attached to the conversation. Call this when you see tags. If this tool fails, use a relevant skill instead (e.g. pdf skill with exec tool). The path attribute in is a directly accessible file in your workspace — use it directly, no need to copy", "create_image": "Generate images from text descriptions using AI", @@ -389,9 +389,9 @@ func buildToolingSection(toolNames []string, hasSandbox bool, shellDenyGroups ma "When users send images, videos, audio, or documents, you see tags like:", ` `, ` , , `, - "You MUST use the corresponding read_* tool (with the path or media_id) to analyze them.", + "Use the corresponding read_* tool (with the path or media_id) to analyze them when the user asks about them or when understanding the media is needed to answer.", "You have full vision/audio/video capabilities through these tools.", - "NEVER say you cannot see images or files — always use the tools.", + "NEVER say you cannot see images or files — always use the tools when relevant.", ) } diff --git a/internal/channels/telegram/format.go b/internal/channels/telegram/format.go index 65ad4803..556894ba 100644 --- a/internal/channels/telegram/format.go +++ b/internal/channels/telegram/format.go @@ -77,6 +77,21 @@ func markdownToTelegramHTML(text string) string { text = regexp.MustCompile(`\*\*(.+?)\*\*`).ReplaceAllString(text, "$1") text = regexp.MustCompile(`__(.+?)__`).ReplaceAllString(text, "$1") + // Protect @mentions from italic conversion and convert to clickable Telegram links. + // In HTML parse_mode, Telegram does NOT auto-link @username — we must use tags. + // Uses (^|\W) prefix to avoid matching emails like user@domain.com. + var mentionPlaceholders []string + reMention := regexp.MustCompile(`(^|\W)(@\w+)`) + text = reMention.ReplaceAllStringFunc(text, func(s string) string { + match := reMention.FindStringSubmatch(s) + if len(match) < 3 { + return s + } + idx := len(mentionPlaceholders) + mentionPlaceholders = append(mentionPlaceholders, match[2]) + return match[1] + fmt.Sprintf("\x00MN%d\x00", idx) + }) + // Italic reItalic := regexp.MustCompile(`_([^_]+)_`) text = reItalic.ReplaceAllStringFunc(text, func(s string) string { @@ -90,6 +105,13 @@ func markdownToTelegramHTML(text string) string { // Strikethrough text = regexp.MustCompile(`~~(.+?)~~`).ReplaceAllString(text, "$1") + // Restore @mentions as clickable Telegram links + for i, mention := range mentionPlaceholders { + username := strings.TrimPrefix(mention, "@") + linked := fmt.Sprintf(`%s`, username, mention) + text = strings.ReplaceAll(text, fmt.Sprintf("\x00MN%d\x00", i), linked) + } + // List items text = regexp.MustCompile(`(?m)^[-*]\s+`).ReplaceAllString(text, "• ") diff --git a/internal/channels/telegram/handlers.go b/internal/channels/telegram/handlers.go index b098e8a8..6dca1d90 100644 --- a/internal/channels/telegram/handlers.go +++ b/internal/channels/telegram/handlers.go @@ -482,7 +482,7 @@ func (c *Channel) handleMessage(ctx context.Context, update telego.Update) { } if len(histMedia) > 0 { histTags := buildMediaTags(histMedia) - annotated = histTags + "\n\n" + annotated + annotated = "[Media from recent group messages — only analyze if user asks about them]\n" + histTags + "\n[/Media]\n\n" + annotated } for _, e := range histErrors { slog.Warn("telegram: history media download failed",