fix(tui): strip <final> tags in TUI display

Add <final> 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 <think>...</think> and <final>...</final> tags.
The TUI was only stripping <think> tags, causing <final> 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 <noreply@anthropic.com>
This commit is contained in:
Kyle Kim 2026-01-25 02:42:15 +09:00 committed by Peter Steinberger
parent 15a9c21203
commit e2f3823c58
2 changed files with 20 additions and 3 deletions

View File

@ -21,5 +21,22 @@ describe("stripThinkingTags", () => {
it("returns original text when no tags exist", () => {
expect(stripThinkingTags("Hello")).toBe("Hello");
});
it("strips <final>…</final> segments", () => {
const input = "<final>\n\nHello there\n\n</final>";
expect(stripThinkingTags(input)).toBe("Hello there\n\n");
});
it("strips mixed <think> and <final> tags", () => {
const input = "<think>reasoning</think>\n\n<final>Hello</final>";
expect(stripThinkingTags(input)).toBe("Hello");
});
it("handles incomplete <final tag gracefully", () => {
// When streaming splits mid-tag, we may see "<final" without closing ">"
// This should not crash and should handle gracefully
expect(stripThinkingTags("<final\nHello")).toBe("<final\nHello");
expect(stripThinkingTags("Hello</final>")).toBe("Hello");
});
});

View File

@ -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;