From ce84bc60b2f0f7e30c948a93eb54e2316f015d45 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sat, 23 May 2026 18:07:41 -0500 Subject: [PATCH] fix(tui): surface streaming reasoning titles --- .../opencode/src/cli/cmd/tui/context/thinking.ts | 8 ++++---- packages/opencode/test/cli/tui/thinking.test.ts | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/thinking.ts b/packages/opencode/src/cli/cmd/tui/context/thinking.ts index 2af7866c0a..b9d8e182ef 100644 --- a/packages/opencode/src/cli/cmd/tui/context/thinking.ts +++ b/packages/opencode/src/cli/cmd/tui/context/thinking.ts @@ -6,12 +6,12 @@ export type ThinkingMode = "show" | "hide" const MODES: readonly ThinkingMode[] = ["show", "hide"] as const // OpenAI's Responses API surfaces reasoning summaries that start with a bolded -// title block: "**Inspecting PR workflow**\n\n". Treat that first block -// as disclosure metadata so the TUI can style its header independently from -// the remaining markdown body. +// title block: "**Inspecting PR workflow**\n\n". Treat that first block, +// or a complete title still awaiting its body while streaming, as disclosure +// metadata so the TUI can style its header independently from the markdown body. export function reasoningSummary(text: string) { const content = text.trim() - const match = content.match(/^\*\*([^*\n]+)\*\*\r?\n\r?\n/) + const match = content.match(/^\*\*([^*\n]+)\*\*(?:\r?\n\r?\n|$)/) if (!match) return { title: null, body: content } return { title: match[1].trim(), body: content.slice(match[0].length).trim() } } diff --git a/packages/opencode/test/cli/tui/thinking.test.ts b/packages/opencode/test/cli/tui/thinking.test.ts index 24bcccc6e0..2c0f0ef53d 100644 --- a/packages/opencode/test/cli/tui/thinking.test.ts +++ b/packages/opencode/test/cli/tui/thinking.test.ts @@ -9,6 +9,20 @@ describe("reasoningSummary", () => { }) }) + test("extracts a completed title before its streamed body arrives", () => { + expect(reasoningSummary("**Continuing Quality Review**")).toEqual({ + title: "Continuing Quality Review", + body: "", + }) + }) + + test("does not consume ordinary leading bold content", () => { + expect(reasoningSummary("**Important:** keep this in the body.")).toEqual({ + title: null, + body: "**Important:** keep this in the body.", + }) + }) + test("leaves content without a leading title in its body", () => { expect(reasoningSummary("Details only.")).toEqual({ title: null, body: "Details only." }) })