refactor(cli-run): stream reasoning text instead of summarized thinking line

Replace the single-line "Thinking: <summary>" rendering with direct streaming
of reasoning tokens via writePaddedText. Removes maybePrintThinkingLine and
renderThinkingLine in favor of incremental output with dim styling.

🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim 2026-02-18 01:14:01 +09:00
parent 0bffdc441e
commit 04e95d7e27
4 changed files with 22 additions and 45 deletions

View File

@ -20,7 +20,6 @@ import {
closeThinkBlock, closeThinkBlock,
openThinkBlock, openThinkBlock,
renderAgentHeader, renderAgentHeader,
renderThinkingLine,
writePaddedText, writePaddedText,
} from "./output-renderer" } from "./output-renderer"
@ -111,10 +110,15 @@ export function handleMessagePartUpdated(ctx: RunContext, payload: EventPayload,
if (part.type === "reasoning") { if (part.type === "reasoning") {
ensureThinkBlockOpen(state) ensureThinkBlockOpen(state)
const reasoningText = part.text ?? state.lastReasoningText const reasoningText = part.text ?? ""
maybePrintThinkingLine(state, reasoningText) const newText = reasoningText.slice(state.lastReasoningText.length)
if (newText) {
const padded = writePaddedText(newText, state.thinkingAtLineStart)
process.stdout.write(pc.dim(padded.output))
state.thinkingAtLineStart = padded.atLineStart
state.hasReceivedMeaningfulWork = true
}
state.lastReasoningText = reasoningText state.lastReasoningText = reasoningText
state.hasReceivedMeaningfulWork = true
return return
} }
@ -157,9 +161,10 @@ export function handleMessagePartDelta(ctx: RunContext, payload: EventPayload, s
if (partType === "reasoning") { if (partType === "reasoning") {
ensureThinkBlockOpen(state) ensureThinkBlockOpen(state)
const nextReasoningText = `${state.lastReasoningText}${delta}` const padded = writePaddedText(delta, state.thinkingAtLineStart)
maybePrintThinkingLine(state, nextReasoningText) process.stdout.write(pc.dim(padded.output))
state.lastReasoningText = nextReasoningText state.thinkingAtLineStart = padded.atLineStart
state.lastReasoningText += delta
state.hasReceivedMeaningfulWork = true state.hasReceivedMeaningfulWork = true
return return
} }
@ -226,6 +231,7 @@ export function handleMessageUpdated(ctx: RunContext, payload: EventPayload, sta
state.hasPrintedThinkingLine = false state.hasPrintedThinkingLine = false
state.lastThinkingSummary = "" state.lastThinkingSummary = ""
state.textAtLineStart = true state.textAtLineStart = true
state.thinkingAtLineStart = false
closeThinkBlockIfNeeded(state) closeThinkBlockIfNeeded(state)
const agent = props?.info?.agent ?? null const agent = props?.info?.agent ?? null
@ -298,6 +304,7 @@ function ensureThinkBlockOpen(state: EventState): void {
openThinkBlock() openThinkBlock()
state.inThinkBlock = true state.inThinkBlock = true
state.hasPrintedThinkingLine = false state.hasPrintedThinkingLine = false
state.thinkingAtLineStart = false
} }
function closeThinkBlockIfNeeded(state: EventState): void { function closeThinkBlockIfNeeded(state: EventState): void {
@ -306,19 +313,5 @@ function closeThinkBlockIfNeeded(state: EventState): void {
state.inThinkBlock = false state.inThinkBlock = false
state.lastThinkingLineWidth = 0 state.lastThinkingLineWidth = 0
state.lastThinkingSummary = "" state.lastThinkingSummary = ""
} state.thinkingAtLineStart = false
function maybePrintThinkingLine(state: EventState, text: string): void {
const normalized = text.replace(/\s+/g, " ").trim()
if (!normalized) return
const summary = normalized
if (summary === state.lastThinkingSummary) return
state.lastThinkingLineWidth = renderThinkingLine(
summary,
state.lastThinkingLineWidth,
)
state.lastThinkingSummary = summary
state.hasPrintedThinkingLine = true
} }

View File

@ -35,6 +35,8 @@ export interface EventState {
lastThinkingSummary: string lastThinkingSummary: string
/** Whether text stream is currently at line start (for padding) */ /** Whether text stream is currently at line start (for padding) */
textAtLineStart: boolean textAtLineStart: boolean
/** Whether reasoning stream is currently at line start (for padding) */
thinkingAtLineStart: boolean
} }
export function createEventState(): EventState { export function createEventState(): EventState {
@ -60,5 +62,6 @@ export function createEventState(): EventState {
messageRoleById: {}, messageRoleById: {},
lastThinkingSummary: "", lastThinkingSummary: "",
textAtLineStart: true, textAtLineStart: true,
thinkingAtLineStart: false,
} }
} }

View File

@ -215,8 +215,7 @@ describe("message.part.delta handling", () => {
//#then //#then
const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("") const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("")
expect(rendered).toContain("\r") expect(rendered).toContain("┃ Thinking: Composing final summary")
expect(rendered).toContain("Thinking: Composing final summary")
expect(rendered).toContain("in Korean with specifics.") expect(rendered).toContain("in Korean with specifics.")
if (previous !== undefined) process.env.GITHUB_ACTIONS = previous if (previous !== undefined) process.env.GITHUB_ACTIONS = previous
@ -282,7 +281,7 @@ describe("message.part.delta handling", () => {
//#then //#then
const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("") const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("")
const renderCount = rendered.split("Thinking:").length - 1 const renderCount = rendered.split("Thinking:").length - 1
expect(renderCount).toBe(2) expect(renderCount).toBe(1)
if (previous !== undefined) process.env.GITHUB_ACTIONS = previous if (previous !== undefined) process.env.GITHUB_ACTIONS = previous
stdoutSpy.mockRestore() stdoutSpy.mockRestore()

View File

@ -25,29 +25,11 @@ export function renderAgentHeader(
} }
export function openThinkBlock(): void { export function openThinkBlock(): void {
return process.stdout.write("\n ┃ Thinking: ")
} }
export function closeThinkBlock(): void { export function closeThinkBlock(): void {
process.stdout.write("\n") process.stdout.write(" \n")
}
export function renderThinkingLine(
summary: string,
previousWidth: number,
): number {
const line = ` ┃ Thinking: ${summary} `
const isGitHubActions = process.env.GITHUB_ACTIONS === "true"
if (isGitHubActions) {
process.stdout.write(`${pc.dim(line)}\n`)
return line.length
}
const minPadding = 6
const clearPadding = Math.max(previousWidth - line.length + minPadding, minPadding)
process.stdout.write(`\r${pc.dim(line)}${" ".repeat(clearPadding)}`)
return line.length
} }
export function writePaddedText( export function writePaddedText(