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:
parent
0bffdc441e
commit
04e95d7e27
@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user