obol_core/transcript/
gemini.rs1use crate::error::ObolError;
8use crate::model::{MessageUsage, Provider};
9use serde_json::Value;
10
11pub fn parse(bytes: &[u8]) -> Result<Vec<MessageUsage>, ObolError> {
12 let text = std::str::from_utf8(bytes).map_err(|e| ObolError::MalformedTranscript {
13 line: 0,
14 msg: e.to_string(),
15 })?;
16 let mut latest: Option<Value> = None;
19 for line in text.lines() {
20 let line = line.trim();
21 if line.is_empty() {
22 continue;
23 }
24 let v: Value = match serde_json::from_str(line) {
25 Ok(v) => v,
26 Err(_) => continue,
27 };
28 let msgs = v.pointer("/$set/messages").or_else(|| v.get("messages"));
29 if let Some(m) = msgs {
30 if m.as_array().is_some_and(|a| !a.is_empty()) {
31 latest = Some(m.clone());
32 }
33 }
34 }
35 let mut out = Vec::new();
36 let msgs = match latest {
37 Some(Value::Array(a)) => a,
38 _ => return Ok(out),
39 };
40 for msg in &msgs {
41 if msg.get("type").and_then(Value::as_str) != Some("gemini") {
42 continue;
43 }
44 let tok = match msg.get("tokens") {
45 Some(t) if t.is_object() => t,
46 _ => continue,
47 };
48 let g = |k: &str| tok.get(k).and_then(Value::as_u64).unwrap_or(0);
49 let input = g("input");
50 let cached = g("cached");
51 let output = g("output") + g("thoughts");
52 out.push(MessageUsage {
53 model: msg
54 .get("model")
55 .and_then(Value::as_str)
56 .unwrap_or("")
57 .to_string(),
58 provider: Provider::Other("google".into()),
59 namespace: "litellm".into(),
60 input_uncached: input,
61 cache_read: cached,
62 cache_write_5m: 0,
63 cache_write_1h: 0,
64 output,
65 request_input_tokens: input + cached,
66 service_tier: None,
67 });
68 }
69 Ok(out)
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn reads_latest_snapshot_and_folds_thoughts_into_output() {
78 let u = parse(include_bytes!("../../tests/fixtures/gemini-mini.jsonl")).unwrap();
79 assert_eq!(u.len(), 1, "{u:?}");
80 assert_eq!(u[0].model, "gemini-3-flash-preview");
81 assert_eq!(u[0].input_uncached, 9431);
82 assert_eq!(u[0].cache_read, 0);
83 assert_eq!(u[0].output, 106); assert_eq!(u[0].request_input_tokens, 9431);
85 }
86}