From 135df948d8f5b9b25ff7d02cf5844e2df82f938b Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Thu, 17 Sep 2026 03:46:12 +0530 Subject: [PATCH] fix(agents): emit a single delta when copilot/qwen/bob repeat a payload across fields --- next/src/lib/agents/__tests__/argv.test.ts | 61 ++++++++++++++++++++++ next/src/lib/agents/argv.ts | 29 +++++++--- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/next/src/lib/agents/__tests__/argv.test.ts b/next/src/lib/agents/__tests__/argv.test.ts index 449277d9..6130c647 100644 --- a/next/src/lib/agents/__tests__/argv.test.ts +++ b/next/src/lib/agents/__tests__/argv.test.ts @@ -163,6 +163,39 @@ describe("parseLine opencode", () => { }); }); +describe("parseLine copilot", () => { + it("emits a single delta when response and text repeat one payload", () => { + const line = JSON.stringify({ + response: "Hello", + text: "Hello", + }); + + expect(parseLine("copilot", line)).toEqual([ + { + kind: "delta", + text: "Hello", + }, + ]); + }); +}); + +describe("parseLine qwen", () => { + it("emits a single delta when text, content, and message repeat one payload", () => { + const line = JSON.stringify({ + text: "Hello", + content: "Hello", + message: "Hello", + }); + + expect(parseLine("qwen", line)).toEqual([ + { + kind: "delta", + text: "Hello", + }, + ]); + }); +}); + describe("parseLine bob", () => { it("extracts text from stream-json output", () => { const line = JSON.stringify({ @@ -203,6 +236,34 @@ describe("parseLine bob", () => { ]); }); + it("emits a single delta when text, content, and message repeat one payload", () => { + // Some CLIs echo the turn payload under several keys at once. Emitting + // one delta per key appends the same HTML two or three times. + const line = JSON.stringify({ + text: "Hello", + content: "Hello", + message: "Hello", + }); + + expect(parseLine("bob", line)).toEqual([ + { + kind: "delta", + text: "Hello", + }, + ]); + }); + + it("falls back to the next populated field when the first is empty", () => { + const line = JSON.stringify({ text: "", content: "Hi" }); + + expect(parseLine("bob", line)).toEqual([ + { + kind: "delta", + text: "Hi", + }, + ]); + }); + it("handles final answer after thinking when --hide-intermediary-output is used", () => { // When --hide-intermediary-output is enabled, Bob only emits the final answer. // This test verifies that the parser correctly handles the final completion. diff --git a/next/src/lib/agents/argv.ts b/next/src/lib/agents/argv.ts index f92b6b59..380fafd6 100644 --- a/next/src/lib/agents/argv.ts +++ b/next/src/lib/agents/argv.ts @@ -372,8 +372,13 @@ function parseLineWithState(agent: string, line: string, state: ParseState): Age } if (agent === "copilot") { - if (typeof obj.response === "string") out.push({ kind: "delta", text: obj.response }); - if (typeof obj.text === "string") out.push({ kind: "delta", text: obj.text }); + // Copilot can echo the turn payload under both `response` and `text`. + // Emit a single delta (first populated field wins), like the opencode + // single-pick below — otherwise the same HTML is appended twice. + const text = [obj.response, obj.text].find( + (value): value is string => typeof value === "string" && value.length > 0, + ); + if (text) out.push({ kind: "delta", text }); } if (agent === "opencode") { @@ -417,15 +422,23 @@ function parseLineWithState(agent: string, line: string, state: ParseState): Age } if (agent === "qwen") { - if (typeof obj.text === "string") out.push({ kind: "delta", text: obj.text }); - if (typeof obj.content === "string") out.push({ kind: "delta", text: obj.content }); - if (typeof obj.message === "string") out.push({ kind: "delta", text: obj.message }); + // Qwen can echo the turn payload under `text`, `content`, and `message` + // at once. Emit a single delta (first populated field wins), like the + // opencode single-pick above — otherwise the same HTML is appended up to + // three times. + const text = [obj.text, obj.content, obj.message].find( + (value): value is string => typeof value === "string" && value.length > 0, + ); + if (text) out.push({ kind: "delta", text }); } if (agent === "bob") { - if (typeof obj.text === "string") out.push({ kind: "delta", text: obj.text }); - if (typeof obj.content === "string") out.push({ kind: "delta", text: obj.content }); - if (typeof obj.message === "string") out.push({ kind: "delta", text: obj.message }); + // Same single-pick as qwen: Bob repeats the completion across `text`, + // `content`, and `message` on some stream-json lines. + const text = [obj.text, obj.content, obj.message].find( + (value): value is string => typeof value === "string" && value.length > 0, + ); + if (text) out.push({ kind: "delta", text }); } if (agent === "qoder") {