fix(tui): preserve streamed text when final payload regresses (#15452) (#15573)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: e4a5e3c8a6
Co-authored-by: TsekaLuk <79151285+TsekaLuk@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
This commit is contained in:
Tseka Luk
2026-02-14 02:12:59 +08:00
committed by GitHub
parent be18f5f0f0
commit 5cd9e210fa
3 changed files with 188 additions and 2 deletions

View File

@@ -89,4 +89,109 @@ describe("TuiStreamAssembler", () => {
expect(second).toBeNull();
});
it("keeps richer streamed text when final payload drops earlier blocks", () => {
const assembler = new TuiStreamAssembler();
assembler.ingestDelta(
"run-5",
{
role: "assistant",
content: [
{ type: "text", text: "Before tool call" },
{ type: "tool_use", name: "search" },
{ type: "text", text: "After tool call" },
],
},
false,
);
const finalText = assembler.finalize(
"run-5",
{
role: "assistant",
content: [
{ type: "tool_use", name: "search" },
{ type: "text", text: "After tool call" },
],
},
false,
);
expect(finalText).toBe("Before tool call\nAfter tool call");
});
it("keeps non-empty final text for plain text prefix/suffix updates", () => {
const assembler = new TuiStreamAssembler();
assembler.ingestDelta(
"run-5b",
{
role: "assistant",
content: [
{ type: "text", text: "Draft line 1" },
{ type: "text", text: "Draft line 2" },
],
},
false,
);
const finalText = assembler.finalize(
"run-5b",
{
role: "assistant",
content: [{ type: "text", text: "Draft line 1" }],
},
false,
);
expect(finalText).toBe("Draft line 1");
});
it("accepts richer final payload when it extends streamed text", () => {
const assembler = new TuiStreamAssembler();
assembler.ingestDelta(
"run-6",
{
role: "assistant",
content: [{ type: "text", text: "Before tool call" }],
},
false,
);
const finalText = assembler.finalize(
"run-6",
{
role: "assistant",
content: [
{ type: "text", text: "Before tool call" },
{ type: "text", text: "After tool call" },
],
},
false,
);
expect(finalText).toBe("Before tool call\nAfter tool call");
});
it("prefers non-empty final payload when it is not a dropped block regression", () => {
const assembler = new TuiStreamAssembler();
assembler.ingestDelta(
"run-7",
{
role: "assistant",
content: [{ type: "text", text: "NOT OK" }],
},
false,
);
const finalText = assembler.finalize(
"run-7",
{
role: "assistant",
content: [{ type: "text", text: "OK" }],
},
false,
);
expect(finalText).toBe("OK");
});
});