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:
@@ -199,13 +199,14 @@ function getJoinedChunks(chunks: readonly string[]): string {
|
|||||||
*/
|
*/
|
||||||
function cleanAgentOutput(output: string | null): string | null {
|
function cleanAgentOutput(output: string | null): string | null {
|
||||||
if (!output) return null
|
if (!output) return null
|
||||||
const trimmed = output.trim()
|
let text = output.trim()
|
||||||
if (!trimmed) return null
|
if (!text) return null
|
||||||
|
|
||||||
|
// Step 1: Unwrap JSON containers (no recursion — single-level unwrap)
|
||||||
// JSON array of content blocks: [{"type":"text","text":"..."},...]
|
// JSON array of content blocks: [{"type":"text","text":"..."},...]
|
||||||
if (trimmed.startsWith("[")) {
|
if (text.startsWith("[")) {
|
||||||
try {
|
try {
|
||||||
const arr = JSON.parse(trimmed)
|
const arr = JSON.parse(text)
|
||||||
if (Array.isArray(arr)) {
|
if (Array.isArray(arr)) {
|
||||||
const texts: string[] = []
|
const texts: string[] = []
|
||||||
for (const item of arr) {
|
for (const item of arr) {
|
||||||
@@ -217,37 +218,43 @@ function cleanAgentOutput(output: string | null): string | null {
|
|||||||
texts.push(item.text)
|
texts.push(item.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (texts.length > 0) return texts.join("\n")
|
if (texts.length > 0) text = texts.join("\n")
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* not valid JSON */
|
/* not valid JSON */
|
||||||
}
|
}
|
||||||
}
|
} else if (text.startsWith("{")) {
|
||||||
|
// JSON object with common result fields
|
||||||
// JSON object with common result fields
|
|
||||||
if (trimmed.startsWith("{")) {
|
|
||||||
try {
|
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"]) {
|
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 {
|
} catch {
|
||||||
/* not valid JSON */
|
/* not valid JSON */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// <task_result> XML wrapper (OpenCode)
|
// Step 2: Strip leading session / task_id lines that some agents prepend
|
||||||
const tagStart = trimmed.indexOf("<task_result>")
|
// 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) {
|
if (tagStart !== -1) {
|
||||||
const contentStart = tagStart + "<task_result>".length
|
const contentStart = tagStart + "<task_result>".length
|
||||||
const contentEnd = trimmed.indexOf("</task_result>", contentStart)
|
const contentEnd = text.indexOf("</task_result>", contentStart)
|
||||||
const extracted = trimmed
|
const extracted = text
|
||||||
.substring(contentStart, contentEnd === -1 ? undefined : contentEnd)
|
.substring(contentStart, contentEnd === -1 ? undefined : contentEnd)
|
||||||
.trim()
|
.trim()
|
||||||
if (extracted) return extracted
|
if (extracted) return extracted
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStreamingTurnsFromLiveMessage(
|
function buildStreamingTurnsFromLiveMessage(
|
||||||
@@ -441,7 +448,9 @@ function buildStreamingTurnsFromLiveMessage(
|
|||||||
currentBlocks.push({
|
currentBlocks.push({
|
||||||
type: "tool_result",
|
type: "tool_result",
|
||||||
tool_use_id: block.info.tool_call_id,
|
tool_use_id: block.info.tool_call_id,
|
||||||
output_preview: resolvedOutput ?? null,
|
output_preview: isAgent
|
||||||
|
? cleanAgentOutput(resolvedOutput)
|
||||||
|
: (resolvedOutput ?? null),
|
||||||
is_error: false,
|
is_error: false,
|
||||||
...(agentStats ? { agent_stats: agentStats } : {}),
|
...(agentStats ? { agent_stats: agentStats } : {}),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user