From 71457fa10074b3b7cd9e87a96a82bf0b786f442c Mon Sep 17 00:00:00 2001 From: Ganghyun Kim <58307870+kyleok@users.noreply.github.com> Date: Sun, 25 Jan 2026 04:52:34 +0900 Subject: [PATCH] fix(tui): strip tags in TUI display (#1613) Add tag handling to stripThinkingTags() to prevent reasoning-tag provider responses from leaking incomplete tags during streaming. When using providers like google-antigravity/*, ollama, or minimax, the model wraps responses in ... and ... tags. The TUI was only stripping tags, causing to leak through and display as the response ~50% of the time. This is a defense-in-depth fix for the TUI layer. Fixes: #1561 Co-authored-by: Claude Code --- ui/src/ui/format.test.ts | 17 +++++++++++++++++ ui/src/ui/format.ts | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ui/src/ui/format.test.ts b/ui/src/ui/format.test.ts index dc785481c..d7acecebb 100644 --- a/ui/src/ui/format.test.ts +++ b/ui/src/ui/format.test.ts @@ -21,5 +21,22 @@ describe("stripThinkingTags", () => { it("returns original text when no tags exist", () => { expect(stripThinkingTags("Hello")).toBe("Hello"); }); + + it("strips segments", () => { + const input = "\n\nHello there\n\n"; + expect(stripThinkingTags(input)).toBe("Hello there\n\n"); + }); + + it("strips mixed and tags", () => { + const input = "reasoning\n\nHello"; + expect(stripThinkingTags(input)).toBe("Hello"); + }); + + it("handles incomplete { + // When streaming splits mid-tag, we may see "" + // This should not crash and should handle gracefully + expect(stripThinkingTags("")).toBe("Hello"); + }); }); diff --git a/ui/src/ui/format.ts b/ui/src/ui/format.ts index 8e52af89a..e8f4e4991 100644 --- a/ui/src/ui/format.ts +++ b/ui/src/ui/format.ts @@ -67,9 +67,9 @@ export function parseList(input: string): string[] { .filter((v) => v.length > 0); } -const THINKING_TAG_RE = /<\s*\/?\s*think(?:ing)?\s*>/gi; -const THINKING_OPEN_RE = /<\s*think(?:ing)?\s*>/i; -const THINKING_CLOSE_RE = /<\s*\/\s*think(?:ing)?\s*>/i; +const THINKING_TAG_RE = /<\s*\/?\s*(?:think(?:ing)?|final)\s*>/gi; +const THINKING_OPEN_RE = /<\s*(?:think(?:ing)?|final)\s*>/i; +const THINKING_CLOSE_RE = /<\s*\/\s*(?:think(?:ing)?|final)\s*>/i; export function stripThinkingTags(value: string): string { if (!value) return value;