ea7f53fd7d
## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - Each agent uses an adapter that drives a CLI (Claude, Gemini, Codex, etc.) > - The Gemini adapter parses a JSONL transcript stream the CLI emits to learn what the model said > - Gemini CLI v0.38 changed the transcript shape: assistant text now comes through `type=message` with `role`/`content` and terminal status comes through `type=status` / `type=stats` > - The existing parser was written against the older `type=assistant` / `type=result` shape, so post-v0.38 outputs left the parsed summary empty and downgraded the SSH hello probe to "unexpected output" > - This pull request updates every Gemini consumer (server parser, UI parser, CLI formatter) to accept the v0.38 shape while keeping the legacy shape working > - The benefit is the Gemini adapter handles current upstream output without losing backward compatibility, with explicit test coverage for both shapes ## What Changed - `packages/adapters/gemini-local/src/server/parse.ts` recognizes `type=message` events with role/content and stops downgrading them - `packages/adapters/gemini-local/src/ui/parse-stdout.ts` mirrors the parser changes for the live UI transcript - `packages/adapters/gemini-local/src/cli/format-event.ts` formats the new event shape correctly for CLI output - `parse.test.ts` and `parse-stdout.test.ts` add v0.38 coverage; `gemini-local-adapter.test.ts` and `execute.remote.test.ts` switch happy-path fixtures to the current real wire format and keep dedicated tests for the older schema ## Verification - `pnpm vitest run --no-coverage --project @paperclipai/adapter-gemini-local` — full suite passes including new v0.38 cases and preserved legacy cases - `pnpm typecheck` clean ## Risks Low risk — additive event handling. Legacy event shape path is preserved with its own tests, so existing fixtures continue to parse identically. ## Model Used Claude Opus 4.7 (1M context) ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots — N/A (no UI) - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { parseGeminiStdoutLine } from "./parse-stdout.js";
|
|
|
|
const ts = "2026-05-04T05:43:45.198Z";
|
|
|
|
describe("parseGeminiStdoutLine", () => {
|
|
it("renders v0.38 message+role:assistant as an assistant transcript entry", () => {
|
|
const line = JSON.stringify({
|
|
type: "message",
|
|
role: "assistant",
|
|
content: "hello.",
|
|
delta: true,
|
|
});
|
|
const entries = parseGeminiStdoutLine(line, ts);
|
|
expect(entries).toEqual([{ kind: "assistant", ts, text: "hello." }]);
|
|
});
|
|
|
|
it("renders v0.38 message+role:user as a user transcript entry", () => {
|
|
const line = JSON.stringify({
|
|
type: "message",
|
|
role: "user",
|
|
content: "Respond with hello.",
|
|
});
|
|
const entries = parseGeminiStdoutLine(line, ts);
|
|
expect(entries).toEqual([{ kind: "user", ts, text: "Respond with hello." }]);
|
|
});
|
|
|
|
it("preserves the legacy claude-style assistant event handler", () => {
|
|
const line = JSON.stringify({
|
|
type: "assistant",
|
|
message: { content: [{ type: "output_text", text: "legacy hello" }] },
|
|
});
|
|
const entries = parseGeminiStdoutLine(line, ts);
|
|
expect(entries).toEqual([{ kind: "assistant", ts, text: "legacy hello" }]);
|
|
});
|
|
|
|
it("reads token usage from v0.38 result.stats", () => {
|
|
const line = JSON.stringify({
|
|
type: "result",
|
|
status: "success",
|
|
stats: {
|
|
total_tokens: 9468,
|
|
input_tokens: 9095,
|
|
output_tokens: 29,
|
|
cached: 8132,
|
|
},
|
|
});
|
|
const [entry] = parseGeminiStdoutLine(line, ts);
|
|
expect(entry).toMatchObject({
|
|
kind: "result",
|
|
inputTokens: 9095,
|
|
outputTokens: 29,
|
|
cachedTokens: 8132,
|
|
isError: false,
|
|
subtype: "success",
|
|
});
|
|
});
|
|
|
|
it("flags v0.38 result.status=error as an error", () => {
|
|
const line = JSON.stringify({
|
|
type: "result",
|
|
status: "error",
|
|
error: "boom",
|
|
});
|
|
const [entry] = parseGeminiStdoutLine(line, ts);
|
|
expect(entry).toMatchObject({ kind: "result", isError: true, errors: ["boom"] });
|
|
});
|
|
|
|
it("ignores message events without an actionable role", () => {
|
|
const line = JSON.stringify({ type: "message", role: "system", content: "ignored" });
|
|
expect(parseGeminiStdoutLine(line, ts)).toEqual([]);
|
|
});
|
|
});
|