Class: Agent::Sessions::Adapters::Gemini

Inherits:
Base
  • Object
show all
Defined in:
lib/agent/sessions/adapters/gemini.rb

Overview

Gemini CLI. Verified against a real store on this machine (2026-08-24): 9 project directories, 12 chat files, 121 records.

The store is keyed by an opaque project hash, not by an encoded path: ~/.gemini/tmp//chats/session--.json, with a sibling logs.json holding a flat prompt log. Nothing anywhere under the store names a working directory — grepping every JSON file in it for cwd, workspace, projectPath, rootPath and directory found zero — which is why project resolution below depends on a separate map file rather than on decoding the hash.

Constant Summary collapse

FILENAME =

session-2025-11-29T20-08-b20947ab.json. The trailing hex is NOT a session id: two files in the real store share d4abc9ce while being different sessions, so the whole basename is the id — unique, stable, and derivable without opening the file, which is what Layer 2 is for. The agent's own sessionId lives inside the document and reaches a caller through the reader.

/\Asession-(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-\h+\.json\z/

Constants inherited from Base

Base::FIDELITIES

Constants included from Enumeration

Enumeration::MAX_LINE_BYTES

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#base_dir, fidelity_value, homedir_config, #initialize, #locate, #retention, #retention_source, store_configs, #verify

Methods included from Enumeration

#bytes_for, #session_id_from, #sessions, #sessions_for_project, #updated_at_for

Constructor Details

This class inherits a constructor from Agent::Sessions::Adapters::Base

Class Method Details

.reader_classObject



23
# File 'lib/agent/sessions/adapters/gemini.rb', line 23

def self.reader_class = Readers::Gemini

Instance Method Details

#encode_project(dir) ⇒ Object



94
95
96
# File 'lib/agent/sessions/adapters/gemini.rb', line 94

def encode_project(dir)
  project_map.key(dir) || super
end

#project_dir_name(path) ⇒ Object

/tmp//chats/.json — two levels up from the file, not one, so Base's default (the immediate parent) would answer "chats" for every session.


90
91
92
# File 'lib/agent/sessions/adapters/gemini.rb', line 90

def project_dir_name(path)
  File.basename(File.dirname(File.dirname(path)))
end

#project_path_for(path) ⇒ Object

The store groups sessions under a hash of the project directory that this gem cannot reverse: it is not a plain SHA-256 of the path (tested directly against real directories), and the store records the path nowhere else. ~/.gemini/projects.json is the map Gemini itself keeps — when it exists, this reads it; when it does not, nil is the honest answer and projects reports nothing rather than inventing a name from the hash.



82
83
84
85
# File 'lib/agent/sessions/adapters/gemini.rb', line 82

def project_path_for(path)
  hash = project_dir_name(path)
  hash && project_map[hash]
end

#project_pathsObject



98
99
100
101
102
103
# File 'lib/agent/sessions/adapters/gemini.rb', line 98

def project_paths
  return [] unless primary_layer.exists?

  Dir.glob(File.join(escape_glob(primary_layer.path), "*", "chats", "session-*.json"))
     .filter_map { |path| project_path_for(path) }.uniq.sort
end

#started_at_for(path, stat) ⇒ Object

UTC, unlike Codex and pi, whose rollout filenames use the local clock. Verified rather than assumed: four real filenames match their own document's startTime to the minute when read as UTC (12-42 against 2025-12-12T12:42:50.033Z), and would be three hours out as local time on the machine this was written on.

Minute precision only — the document's startTime carries seconds, but reading it would cost a parse per session, and Layer 2 is stat-only.



65
66
67
68
69
70
71
72
73
# File 'lib/agent/sessions/adapters/gemini.rb', line 65

def started_at_for(path, stat)
  parts = FILENAME.match(File.basename(path))&.captures or return super

  begin
    Time.utc(*parts.map(&:to_i))
  rescue ArgumentError # digits that do not form a real date
    super
  end
end

#warningsObject

The delta-log variant: tokentelemetry's parser of this same store handles chats written as JSONL with a header line and $set deltas. No such file exists here (zero .jsonl anywhere under the store), so this adapter reads the verified .json spelling only — and says so where a user with the other spelling will see it, rather than silently enumerating nothing for half their sessions.



39
40
41
42
43
44
45
46
47
# File 'lib/agent/sessions/adapters/gemini.rb', line 39

def warnings
  list = super
  jsonl = Dir.glob(File.join(escape_glob(primary_layer.path), "*", "chats", "*.jsonl"))
  if jsonl.any?
    list << "#{jsonl.size} chat file(s) use the JSONL delta format, which this adapter does " \
            "not read yet; those sessions are not enumerated"
  end
  list
end