Gemini CLI Chat Record Parsing#
JSONL File Structure#
Gemini CLI writes session history as a JSONL event log in ~/.gemini/tmp/. Each file is replayed in order to reconstruct conversation state. The record types are :
- Line 0 — metadata header:
{ sessionId, projectHash, startTime, lastUpdated, kind }. Thekindfield is"main"for a primary session or"subagent"for a delegated session. Subagent sessions also carry aparentSessionIdlink . - Message records:
{ id, timestamp, type, content, tokens?, model?, toolCalls?, thoughts? }wheretypeis"user","gemini","info", etc. $setcheckpoints: Merge metadata into the session; if$set.messagesis present it replaces all messages (the first user message of a session usually lives only here, written byupdateMessagesFromHistory()).$rewindTorecords: Remove the named message and all subsequent ones.
The main parser entry point is parseGeminiSession() in src/sources/gemini.ts, which delegates replay to replay().
Duplicate-Message-ID Replay (Map.set In-Place Replacement)#
Gemini re-appends a turn with the same id whenever usage tokens or tool calls arrive after its initial text record (recordMessageTokens() / recordToolCalls() in the CLI). This means a turn can appear 2–3 times in the file .
The replay loop uses a Map<string, ReplayedRecord> and calls Map.set on every encountered id . Because Map preserves insertion order and set on an existing key updates the value without changing position, the final iteration order matches the original turn sequence .
ThoughtSummary Shape#
Thoughts parsed from a "gemini" message are { subject, description, timestamp } objects — not { text } . thoughtText() joins trimmed subject and description with "\n\n" and falls back to a plain text field if both are absent. Each thought becomes a "thinking" block in Decant's normalized output.
Token Field Semantics and Cost Normalization#
Gemini reports token counts in a non-standard layout. usageFrom() in src/sources/gemini.ts normalizes them:
| Gemini field | Maps to Decant field | Notes |
|---|---|---|
promptTokenCount | input (after subtraction) | Includes cached content; Decant stores input = promptTokenCount − cachedContentTokenCount |
candidatesTokenCount | output (after addition) | Does not include thoughts; Decant stores output = candidatesTokenCount + thoughtsTokenCount |
cachedContentTokenCount | cacheRead | Billed separately as a cache read |
thoughtsTokenCount | reasoning | Outside candidates; must be added to output to satisfy Decant's reasoning-within-output contract |
This normalization satisfies three constraints simultaneously: accurate cost (cache reads priced differently), accurate context-window occupancy (input = true prompt tokens), and the reasoning-within-output contract required by Decant's downstream cost math .
$rewindTo Behavior#
rewind() in src/sources/gemini.ts implements two cases:
- Target not found → the entire
Mapis cleared (messages.clear()), matching Gemini CLI's own loader behavior . - Target found → the target message and all messages inserted after it are deleted by iterating
Map.keys()in order .
Built-in Tool Name Mapping#
Gemini CLI's built-in tools use snake_case names. They are mapped across three files:
src/buckets.ts — Activity Buckets#
toolBucket() assigns each tool to an activity category:
| Tool | Bucket |
|---|---|
run_shell_command | "code" (write) or "context" (read-only, e.g. cat, grep) |
write_file, replace | "code" |
write_todos | "planning" |
read_file | "context" |
src/distill.ts — Command Decoding#
decodeCommand() extracts the shell command string from a tool call input. For run_shell_command it reads the "command" key ; for exec_command / shell / local_shell it reads "cmd" . File tools (write_file, replace) are not handled here — they go through fileOp() separately.
src/enrich.ts — File References#
The GEMINI_FILE_TOOLS table maps file tool names to their input parameter key and operation type:
| Tool | key | operation |
|---|---|---|
read_file | "file_path" | "read" |
replace | "file_path" | "edit" |
write_file | "file_path" | "write" |
These are used by keyedRef() to extract file path references from Gemini tool call inputs when session.toolType === "gemini" .