From d5a09098258b88905617adabcfd815ca1e9976cf Mon Sep 17 00:00:00 2001 From: zerone0x Date: Mon, 26 Jan 2026 14:02:42 +0800 Subject: [PATCH] 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 --- src/agents/pi-embedded-utils.test.ts | 27 ++++++++++++++++++++++++++- src/agents/pi-embedded-utils.ts | 10 ++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/agents/pi-embedded-utils.test.ts b/src/agents/pi-embedded-utils.test.ts index c765a4d3a..5e90079a6 100644 --- a/src/agents/pi-embedded-utils.test.ts +++ b/src/agents/pi-embedded-utils.test.ts @@ -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_"); + }); +}); diff --git a/src/agents/pi-embedded-utils.ts b/src/agents/pi-embedded-utils.ts index 89a9df805..69be08971 100644 --- a/src/agents/pi-embedded-utils.ts +++ b/src/agents/pi-embedded-utils.ts @@ -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 =