fix(ui): clean streaming Agent result output by unwrapping JSON and stripping task_id prefix

Refactor cleanAgentOutput to a sequential non-recursive pipeline: unwrap
JSON containers first, then strip task_id session lines, then extract
<task_result> content. Also apply cleaning to in-progress agent output
during streaming, not just completed results.
This commit is contained in:
xintaofei
2026-04-17 09:12:54 +08:00
parent 4f41a217c4
commit 73a910bb62

View File

@@ -199,13 +199,14 @@ function getJoinedChunks(chunks: readonly string[]): string {
*/
function cleanAgentOutput(output: string | null): string | null {
if (!output) return null
const trimmed = output.trim()
if (!trimmed) return null
let text = output.trim()
if (!text) return null
// Step 1: Unwrap JSON containers (no recursion — single-level unwrap)
// JSON array of content blocks: [{"type":"text","text":"..."},...]
if (trimmed.startsWith("[")) {
if (text.startsWith("[")) {
try {
const arr = JSON.parse(trimmed)
const arr = JSON.parse(text)
if (Array.isArray(arr)) {
const texts: string[] = []
for (const item of arr) {
@@ -217,37 +218,43 @@ function cleanAgentOutput(output: string | null): string | null {
texts.push(item.text)
}
}
if (texts.length > 0) return texts.join("\n")
if (texts.length > 0) text = texts.join("\n")
}
} catch {
/* not valid JSON */
}
}
} else if (text.startsWith("{")) {
// JSON object with common result fields
if (trimmed.startsWith("{")) {
try {
const obj = JSON.parse(trimmed) as Record<string, unknown>
const obj = JSON.parse(text) as Record<string, unknown>
for (const key of ["result", "output", "text", "content", "completed"]) {
if (typeof obj[key] === "string") return obj[key] as string
if (typeof obj[key] === "string") {
text = (obj[key] as string).trim()
break
}
}
} catch {
/* not valid JSON */
}
}
// <task_result> XML wrapper (OpenCode)
const tagStart = trimmed.indexOf("<task_result>")
// Step 2: Strip leading session / task_id lines that some agents prepend
// before the actual result (e.g. "task_id: ses_xxx (for resuming ...)").
text = text.replace(/^task_id:\s*\S+[^\n]*\n+/, "").trim()
if (!text) return null
// Step 3: Extract from <task_result> XML wrapper (OpenCode)
const tagStart = text.indexOf("<task_result>")
if (tagStart !== -1) {
const contentStart = tagStart + "<task_result>".length
const contentEnd = trimmed.indexOf("</task_result>", contentStart)
const extracted = trimmed
const contentEnd = text.indexOf("</task_result>", contentStart)
const extracted = text
.substring(contentStart, contentEnd === -1 ? undefined : contentEnd)
.trim()
if (extracted) return extracted
}
return output
return text
}
function buildStreamingTurnsFromLiveMessage(
@@ -441,7 +448,9 @@ function buildStreamingTurnsFromLiveMessage(
currentBlocks.push({
type: "tool_result",
tool_use_id: block.info.tool_call_id,
output_preview: resolvedOutput ?? null,
output_preview: isAgent
? cleanAgentOutput(resolvedOutput)
: (resolvedOutput ?? null),
is_error: false,
...(agentStats ? { agent_stats: agentStats } : {}),
})