diff --git a/packages/adapters/gemini-local/src/server/parse.test.ts b/packages/adapters/gemini-local/src/server/parse.test.ts new file mode 100644 index 00000000..e50f7795 --- /dev/null +++ b/packages/adapters/gemini-local/src/server/parse.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from "vitest"; +import { parseGeminiJsonl } from "./parse.js"; + +describe("parseGeminiJsonl", () => { + it("collects assistant text from message events with string content", () => { + const stdout = [ + '{"type":"init","session_id":"session-1"}', + '{"type":"message","role":"user","content":"Respond with hello."}', + '{"type":"message","role":"assistant","content":"hello","delta":true}', + '{"type":"result","status":"success"}', + ].join("\n"); + + const parsed = parseGeminiJsonl(stdout); + + expect(parsed.sessionId).toBe("session-1"); + expect(parsed.summary).toBe("hello"); + expect(parsed.errorMessage).toBeNull(); + }); + + it("collects assistant text from message events with structured object content", () => { + const stdout = [ + '{"type":"init","session_id":"session-2"}', + '{"type":"message","role":"assistant","content":{"content":[{"type":"text","text":"first part"},{"type":"text","text":"second part"}]}}', + '{"type":"result","status":"success"}', + ].join("\n"); + + const parsed = parseGeminiJsonl(stdout); + + expect(parsed.sessionId).toBe("session-2"); + expect(parsed.summary).toBe("first part\n\nsecond part"); + expect(parsed.errorMessage).toBeNull(); + }); + + it("ignores non-assistant message events", () => { + const stdout = [ + '{"type":"message","role":"user","content":"hidden user input"}', + '{"type":"message","role":"system","content":"hidden system note"}', + '{"type":"message","role":"assistant","content":"visible response"}', + '{"type":"result","status":"success"}', + ].join("\n"); + + const parsed = parseGeminiJsonl(stdout); + + expect(parsed.summary).toBe("visible response"); + }); +}); diff --git a/packages/adapters/gemini-local/src/server/parse.ts b/packages/adapters/gemini-local/src/server/parse.ts index a9d3df9e..423e4db4 100644 --- a/packages/adapters/gemini-local/src/server/parse.ts +++ b/packages/adapters/gemini-local/src/server/parse.ts @@ -121,6 +121,19 @@ export function parseGeminiJsonl(stdout: string) { continue; } + if (type === "message") { + const role = asString(event.role, "").trim().toLowerCase(); + if (role === "assistant") { + // Mirror the assistant-event handling above: collect every assistant + // message including deltas. Gemini CLI emits these as discrete final + // messages (one per assistant turn), not as cumulative streaming + // tokens, so collecting all of them produces the expected concatenated + // turn-by-turn summary rather than duplicated text. + messages.push(...collectMessageText(event.content)); + } + continue; + } + if (type === "result") { resultEvent = event; accumulateUsage(usage, event.usage ?? event.usageMetadata);