feat(cli-run): add streaming delta, think block rendering, and rich tool headers

Adds message.part.delta event handling for real-time streaming output,
reasoning/think block display with in-place updates, per-agent profile
colors, padded text output, and semantic tool headers with icons.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2026-02-17 23:33:06 +09:00
parent 4bb8fa4a7f
commit eaf315a8d7
10 changed files with 945 additions and 73 deletions
+104
View File
@@ -0,0 +1,104 @@
import pc from "picocolors"
export function renderAgentHeader(
agent: string | null,
model: string | null,
variant: string | null,
agentColorsByName: Record<string, string>,
): void {
if (!agent && !model) return
const agentLabel = agent
? pc.bold(colorizeWithProfileColor(agent, agentColorsByName[agent]))
: ""
const modelBase = model ?? ""
const variantSuffix = variant ? ` (${variant})` : ""
const modelLabel = model ? pc.dim(`${modelBase}${variantSuffix}`) : ""
if (modelLabel) {
process.stdout.write(` ${modelLabel} \n`)
}
if (agentLabel) {
process.stdout.write(` ${pc.dim("└─")} ${agentLabel} \n`)
}
}
export function openThinkBlock(): void {
return
}
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
}
export function writePaddedText(
text: string,
atLineStart: boolean,
): { output: string; atLineStart: boolean } {
const isGitHubActions = process.env.GITHUB_ACTIONS === "true"
if (isGitHubActions) {
return { output: text, atLineStart: text.endsWith("\n") }
}
let output = ""
let lineStart = atLineStart
for (let i = 0; i < text.length; i++) {
const ch = text[i]
if (lineStart) {
output += " "
lineStart = false
}
if (ch === "\n") {
output += " \n"
lineStart = true
continue
}
output += ch
}
return { output, atLineStart: lineStart }
}
function colorizeWithProfileColor(text: string, hexColor?: string): string {
if (!hexColor) return pc.magenta(text)
const rgb = parseHexColor(hexColor)
if (!rgb) return pc.magenta(text)
const [r, g, b] = rgb
return `\u001b[38;2;${r};${g};${b}m${text}\u001b[39m`
}
function parseHexColor(hexColor: string): [number, number, number] | null {
const cleaned = hexColor.trim()
const match = cleaned.match(/^#?([A-Fa-f0-9]{6})$/)
if (!match) return null
const hex = match[1]
const r = Number.parseInt(hex.slice(0, 2), 16)
const g = Number.parseInt(hex.slice(2, 4), 16)
const b = Number.parseInt(hex.slice(4, 6), 16)
return [r, g, b]
}