fix(reasoning): wrap multiline italic text per line for Telegram

Telegram and other markdown parsers don't support underscore markdown
spanning multiple lines. The reasoning block now wraps each line
individually with underscores, so multiline reasoning text renders
correctly in italics.

Fixes #2060

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zerone0x 2026-01-26 14:02:42 +08:00
parent 6859e1e6a6
commit d5a0909825
2 changed files with 34 additions and 3 deletions

View File

@ -1,6 +1,6 @@
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import { extractAssistantText } from "./pi-embedded-utils.js";
import { extractAssistantText, formatReasoningMessage } from "./pi-embedded-utils.js";
describe("extractAssistantText", () => {
it("strips Minimax tool invocation XML from text", () => {
@ -508,3 +508,28 @@ File contents here`,
expect(result).toBe("StartMiddleEnd");
});
});
describe("formatReasoningMessage", () => {
it("returns empty string for empty input", () => {
expect(formatReasoningMessage("")).toBe("");
expect(formatReasoningMessage(" ")).toBe("");
});
it("wraps single line in italics", () => {
expect(formatReasoningMessage("Because it helps")).toBe("Reasoning:\n_Because it helps_");
});
it("wraps each line individually for multiline text", () => {
const input = "line one\nline two\nline three";
expect(formatReasoningMessage(input)).toBe("Reasoning:\n_line one_\n_line two_\n_line three_");
});
it("preserves empty lines without wrapping them", () => {
const input = "line one\n\nline three";
expect(formatReasoningMessage(input)).toBe("Reasoning:\n_line one_\n\n_line three_");
});
it("trims leading and trailing whitespace from input", () => {
expect(formatReasoningMessage(" text ")).toBe("Reasoning:\n_text_");
});
});

View File

@ -209,9 +209,15 @@ export function extractAssistantThinking(msg: AssistantMessage): string {
export function formatReasoningMessage(text: string): string {
const trimmed = text.trim();
if (!trimmed) return "";
// Show reasoning in italics (cursive) for markdown-friendly surfaces (Discord, etc.).
// Show reasoning in italics (cursive) for markdown-friendly surfaces.
// Wrap each line individually because many markdown parsers (Telegram, etc.)
// don't support underscores spanning multiple lines.
// Keep the plain "Reasoning:" prefix so existing parsing/detection keeps working.
return `Reasoning:\n_${trimmed}_`;
const italicLines = trimmed
.split("\n")
.map((line) => (line ? `_${line}_` : ""))
.join("\n");
return `Reasoning:\n${italicLines}`;
}
type ThinkTaggedSplitBlock =