fix: stop duplicating text in Gemini Web streamed responses (#7163) (#7198)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-14 21:21:55 -03:00
committed by GitHub
parent 3a92236d7a
commit 01476e6e6a
3 changed files with 17 additions and 8 deletions

View File

@@ -0,0 +1 @@
- fix(sse): stop duplicating text in Gemini Web streamed responses (#7163)

View File

@@ -112,12 +112,15 @@ function parseCookies(raw: string): Array<{ name: string; value: string }> {
* [["wrb.fr", null, "<JSON string>"]]
*
* The JSON string contains nested array: inner[4][0][1] = ["text chunks"].
* We concatenate text from every wrb.fr line because Gemini can split one
* assistant answer across multiple StreamGenerate chunks.
* Each wrb.fr line is a CUMULATIVE snapshot of the whole answer generated so
* far (not an independent delta), so we keep only the text from the LAST
* frame that yields non-empty text instead of concatenating every frame —
* concatenating would reproduce the same growing text with each snapshot
* (see #7163).
*/
export function parseStreamResponse(raw: string): string {
const lines = raw.split("\n");
const textChunks: string[] = [];
let lastText = "";
for (const rawLine of lines) {
const line = rawLine.trim();
@@ -133,12 +136,12 @@ export function parseStreamResponse(raw: string): string {
const responseArray = inner?.[4]?.[0]?.[1];
if (!Array.isArray(responseArray)) continue;
const text = responseArray.filter((c: unknown) => typeof c === "string").join("");
if (text) textChunks.push(text);
if (text) lastText = text;
} catch {
// Skip unparseable lines
}
}
return textChunks.join("");
return lastText;
}
function readCredentialString(value: unknown): string {

View File

@@ -266,15 +266,20 @@ test("#2832: GeminiWebExecutor catch block sanitizes Playwright launch errors (i
// ─── StreamGenerate parsing ─────────────────────────────────────────────────
test("parseStreamResponse concatenates Gemini Web text from multiple wrb.fr chunks", () => {
test("parseStreamResponse keeps only the final cumulative StreamGenerate snapshot (no duplication) — regression for #7163", () => {
const makeChunk = (text: string) => {
const inner = new Array(80).fill(null);
inner[4] = [[null, [text]]];
return `[["wrb.fr", null, ${JSON.stringify(JSON.stringify(inner))}]]`;
};
const raw = `)]}'\n10\n${makeChunk("First ")}\n5\n${makeChunk("chunk")}`;
assert.equal(parseStreamResponse(raw), "First chunk");
// Gemini's StreamGenerate frames are CUMULATIVE snapshots: each later frame
// repeats the full answer generated so far, not just the new characters.
const frame1 = "Hello!";
const frame2 = "Hello! How can I";
const frame3 = "Hello! How can I help you out today?";
const raw = `)]}'\n10\n${makeChunk(frame1)}\n5\n${makeChunk(frame2)}\n5\n${makeChunk(frame3)}`;
assert.equal(parseStreamResponse(raw), frame3);
});
test("parseStreamResponse ignores wrb.fr lines whose first entry is not an array", () => {