@@ -42,13 +42,15 @@ enum GeminiCliMessage {
4242 id : String ,
4343 #[ serde( deserialize_with = "deserialize_utc_timestamp" ) ]
4444 timestamp : DateTime < Utc > ,
45- content : String ,
45+ #[ serde( default ) ]
46+ content : Option < GeminiCliContent > ,
4647 } ,
4748 Gemini {
4849 id : String ,
4950 #[ serde( deserialize_with = "deserialize_utc_timestamp" ) ]
5051 timestamp : DateTime < Utc > ,
51- content : String ,
52+ #[ serde( default ) ]
53+ content : Option < GeminiCliContent > ,
5254 model : String ,
5355 #[ serde( default ) ]
5456 thoughts : Vec < simd_json:: OwnedValue > ,
@@ -60,22 +62,68 @@ enum GeminiCliMessage {
6062 id : String ,
6163 #[ serde( deserialize_with = "deserialize_utc_timestamp" ) ]
6264 timestamp : DateTime < Utc > ,
63- content : String ,
65+ #[ serde( default ) ]
66+ content : Option < GeminiCliContent > ,
6467 } ,
6568 Error {
6669 id : String ,
6770 #[ serde( deserialize_with = "deserialize_utc_timestamp" ) ]
6871 timestamp : DateTime < Utc > ,
69- content : String ,
72+ #[ serde( default ) ]
73+ content : Option < GeminiCliContent > ,
7074 } ,
7175 Info {
7276 id : String ,
7377 #[ serde( deserialize_with = "deserialize_utc_timestamp" ) ]
7478 timestamp : DateTime < Utc > ,
75- content : String ,
79+ #[ serde( default ) ]
80+ content : Option < GeminiCliContent > ,
7681 } ,
7782}
7883
84+ /// A single `Part` from Gemini CLI's multi-modal content. Only `text` is
85+ /// consumed by splitrail; any other fields (e.g. `inlineData`, `fileData`,
86+ /// `functionCall`) are silently ignored by serde's default behaviour, which
87+ /// is what we want — the schema is still evolving upstream and we don't want
88+ /// unrecognised part kinds to abort parsing.
89+ #[ derive( Debug , Clone , Serialize , Deserialize , Default ) ]
90+ struct GeminiCliPart {
91+ #[ serde( default ) ]
92+ text : Option < String > ,
93+ }
94+
95+ /// The `content` field on a Gemini CLI message. Mirrors `PartListUnion` from
96+ /// `@google/genai`: it can be a plain string (legacy format), a single `Part`
97+ /// object, or an array of `Part` objects (current format, since Gemini CLI
98+ /// changed its schema — see issue #137).
99+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
100+ #[ serde( untagged) ]
101+ enum GeminiCliContent {
102+ Text ( String ) ,
103+ Part ( GeminiCliPart ) ,
104+ Parts ( Vec < GeminiCliPart > ) ,
105+ }
106+
107+ impl GeminiCliContent {
108+ /// Extract a concatenated plain-text representation of the content,
109+ /// ignoring non-text parts (images, function calls, etc.).
110+ fn as_text ( & self ) -> String {
111+ match self {
112+ GeminiCliContent :: Text ( s) => s. clone ( ) ,
113+ GeminiCliContent :: Part ( p) => p. text . clone ( ) . unwrap_or_default ( ) ,
114+ GeminiCliContent :: Parts ( ps) => {
115+ let mut out = String :: new ( ) ;
116+ for p in ps {
117+ if let Some ( t) = & p. text {
118+ out. push_str ( t) ;
119+ }
120+ }
121+ out
122+ }
123+ }
124+ }
125+ }
126+
79127#[ derive( Debug , Clone , Serialize , Deserialize ) ]
80128struct GeminiCliTokens {
81129 #[ serde( default ) ]
@@ -216,15 +264,20 @@ fn parse_json_session_file(file_path: &Path) -> Result<Vec<ConversationMessage>>
216264 timestamp,
217265 content,
218266 } => {
219- if fallback_session_name. is_none ( ) && !content. is_empty ( ) {
220- let text_str = content;
221- let truncated = if text_str. chars ( ) . count ( ) > 50 {
222- let chars: String = text_str. chars ( ) . take ( 50 ) . collect ( ) ;
223- format ! ( "{}..." , chars)
224- } else {
225- text_str
226- } ;
227- fallback_session_name = Some ( truncated) ;
267+ if fallback_session_name. is_none ( ) {
268+ let text_str = content
269+ . as_ref ( )
270+ . map ( GeminiCliContent :: as_text)
271+ . unwrap_or_default ( ) ;
272+ if !text_str. is_empty ( ) {
273+ let truncated = if text_str. chars ( ) . count ( ) > 50 {
274+ let chars: String = text_str. chars ( ) . take ( 50 ) . collect ( ) ;
275+ format ! ( "{chars}..." )
276+ } else {
277+ text_str
278+ } ;
279+ fallback_session_name = Some ( truncated) ;
280+ }
228281 }
229282
230283 entries. push ( ConversationMessage {
0 commit comments