fix(telegram): wrap markdown pipe tables in fenced code blocks
Telegram does not support markdown pipe tables. Detect pipe tables (header + separator + body rows) in both the MarkdownV2 and HTML parsers and wrap them in fenced code blocks so they render with monospace alignment. Also adds a kernel identity instruction telling the LLM to avoid pipe tables in Telegram output.
This commit is contained in:
parent
6e1fab80e2
commit
f54e42b9bd
5 changed files with 64 additions and 2 deletions
|
|
@ -116,7 +116,7 @@ func (cb *ContextBuilder) getIdentity() string {
|
|||
version := config.FormatVersion()
|
||||
|
||||
return fmt.Sprintf(
|
||||
`# picoclaw 🦞 (%s)
|
||||
`# picoclaw 🦞 (%s)
|
||||
|
||||
You are picoclaw, a helpful AI assistant.
|
||||
|
||||
|
|
@ -134,7 +134,9 @@ Your workspace is at: %s
|
|||
|
||||
3. **Memory** - When interacting with me if something seems memorable, update %s/memory/MEMORY.md
|
||||
|
||||
4. **Context summaries** - Conversation summaries provided as context are approximate references only. They may be incomplete or outdated. Always defer to explicit user instructions over summary content.`,
|
||||
4. **Context summaries** - Conversation summaries provided as context are approximate references only. They may be incomplete or outdated. Always defer to explicit user instructions over summary content.
|
||||
|
||||
5. **Telegram output** — Telegram does not support markdown tables. If you need to present tabular data, use a fenced code block or a structured list instead. Markdown pipe tables will be automatically wrapped in code blocks, but it is better to avoid them entirely.`,
|
||||
version, workspacePath, workspacePath, workspacePath, workspacePath, workspacePath)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,22 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// reTable matches a markdown pipe table (header line + separator line + at least one body row).
|
||||
// The (?m) flag makes ^ match at line boundaries.
|
||||
// Trailing newline(s) are deliberately excluded from the match so that blank lines
|
||||
// between the table and following content are preserved.
|
||||
var reTable = regexp.MustCompile(`(?m)^(\|[^\n]+\|\r?\n\|[-:\|\s]+\|\r?\n(?:\|[^\n]+\|\r?\n?)+)`)
|
||||
|
||||
// wrapTablesInCodeBlocks wraps each pipe table in a fenced code block so that
|
||||
// Telegram renders it with monospace alignment. This must run before any entity
|
||||
// processing so the ``` delimiters are recognized as code blocks.
|
||||
func wrapTablesInCodeBlocks(text string) string {
|
||||
return reTable.ReplaceAllStringFunc(text, func(match string) string {
|
||||
match = strings.TrimRight(match, "\r\n")
|
||||
return "```\n" + match + "\n```"
|
||||
})
|
||||
}
|
||||
|
||||
// mdV2SpecialChars are all characters that must be escaped in Telegram MarkdownV2
|
||||
var mdV2SpecialChars = map[rune]bool{
|
||||
'*': true,
|
||||
|
|
@ -89,6 +105,9 @@ var verbatimEntities = map[string]bool{
|
|||
//
|
||||
// Reference: https://core.telegram.org/bots/api#formatting-options
|
||||
func markdownToTelegramMarkdownV2(text string) string {
|
||||
// 0. Wrap pipe tables in fenced code blocks so Telegram renders them legibly.
|
||||
text = wrapTablesInCodeBlocks(text)
|
||||
|
||||
// 1. Convert Markdown headings → *escaped heading text*
|
||||
text = reHeading.ReplaceAllStringFunc(text, func(match string) string {
|
||||
sub := reHeading.FindStringSubmatch(match)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,34 @@ func Test_markdownToTelegramMarkdownV2(t *testing.T) {
|
|||
input: "<Market Capitalization>",
|
||||
expected: "\\<Market Capitalization\\>",
|
||||
},
|
||||
{
|
||||
name: "pipe table is wrapped in code block",
|
||||
input: "| Header 1 | Header 2 |\n" +
|
||||
"|----------|----------|\n" +
|
||||
"| Cell 1 | Cell 2 |\n" +
|
||||
"| Cell 3 | Cell 4 |",
|
||||
expected: "```\n" +
|
||||
"| Header 1 | Header 2 |\n" +
|
||||
"|----------|----------|\n" +
|
||||
"| Cell 1 | Cell 2 |\n" +
|
||||
"| Cell 3 | Cell 4 |\n" +
|
||||
"```",
|
||||
},
|
||||
{
|
||||
name: "pipe table surrounded by text",
|
||||
input: "Some text\n\n" +
|
||||
"| A | B |\n" +
|
||||
"|---|---|\n" +
|
||||
"| 1 | 2 |\n\n" +
|
||||
"More text",
|
||||
expected: "Some text\n\n" +
|
||||
"```\n" +
|
||||
"| A | B |\n" +
|
||||
"|---|---|\n" +
|
||||
"| 1 | 2 |\n" +
|
||||
"```\n" +
|
||||
"More text",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ func markdownToTelegramHTML(text string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// Wrap pipe tables in fenced code blocks so Telegram renders them legibly.
|
||||
text = wrapTablesInCodeBlocks(text)
|
||||
|
||||
codeBlocks := extractCodeBlocks(text)
|
||||
text = codeBlocks.text
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,16 @@ func Test_markdownToTelegramHTML(t *testing.T) {
|
|||
input: "```json\n{\n \"path\": \"README.md\"\n}\n```",
|
||||
expected: "<pre><code>{\n \"path\": \"README.md\"\n}\n</code></pre>",
|
||||
},
|
||||
{
|
||||
name: "pipe table is wrapped in code block",
|
||||
input: "| Header 1 | Header 2 |\n" +
|
||||
"|----------|----------|\n" +
|
||||
"| Cell 1 | Cell 2 |",
|
||||
expected: "<pre><code>| Header 1 | Header 2 |\n" +
|
||||
"|----------|----------|\n" +
|
||||
"| Cell 1 | Cell 2 |\n" +
|
||||
"</code></pre>",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue