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

View File

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

View File

@@ -215,8 +215,7 @@ describe("message.part.delta handling", () => {
//#then
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.")
if (previous !== undefined) process.env.GITHUB_ACTIONS = previous
@@ -282,7 +281,7 @@ describe("message.part.delta handling", () => {
//#then
const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("")
const renderCount = rendered.split("Thinking:").length - 1
expect(renderCount).toBe(2)
expect(renderCount).toBe(1)
if (previous !== undefined) process.env.GITHUB_ACTIONS = previous
stdoutSpy.mockRestore()

View File

@@ -25,29 +25,11 @@ export function renderAgentHeader(
}
export function openThinkBlock(): void {
return
process.stdout.write("\n ┃ Thinking: ")
}
export function closeThinkBlock(): void {
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
process.stdout.write(" \n")
}
export function writePaddedText(