Fold one transcript record into the aggregate.
(&mut self, json: &serde_json::Value)
| 195 | impl SessionAggregate { |
| 196 | /// Fold one transcript record into the aggregate. |
| 197 | pub fn observe(&mut self, json: &serde_json::Value) { |
| 198 | let get_str = |key: &str| json.get(key).and_then(|v| v.as_str()).map(String::from); |
| 199 | |
| 200 | if let Some(ts) = get_str("timestamp") { |
| 201 | // ISO 8601 timestamps compare correctly as strings |
| 202 | if self |
| 203 | .first_timestamp |
| 204 | .as_deref() |
| 205 | .is_none_or(|f| ts.as_str() < f) |
| 206 | { |
| 207 | self.first_timestamp = Some(ts.clone()); |
| 208 | } |
| 209 | if self |
| 210 | .last_timestamp |
| 211 | .as_deref() |
| 212 | .is_none_or(|l| ts.as_str() > l) |
| 213 | { |
| 214 | self.last_timestamp = Some(ts); |
| 215 | } |
| 216 | } |
| 217 | if self.cwd.is_none() { |
| 218 | self.cwd = get_str("cwd"); |
| 219 | } |
| 220 | if self.git_branch.is_none() { |
| 221 | self.git_branch = get_str("gitBranch"); |
| 222 | } |
| 223 | if let Some(version) = get_str("version") { |
| 224 | self.version = Some(version); // last seen wins |
| 225 | } |
| 226 | |
| 227 | match json.get("type").and_then(|t| t.as_str()) { |
| 228 | Some("user") => self.user_message_count += 1, |
| 229 | Some("assistant") => { |
| 230 | self.assistant_message_count += 1; |
| 231 | if let Some(usage) = json.get("message").and_then(|m| m.get("usage")) { |
| 232 | let tok = |key: &str| { |
| 233 | usage |
| 234 | .get(key) |
| 235 | .and_then(serde_json::Value::as_i64) |
| 236 | .unwrap_or(0) |
| 237 | }; |
| 238 | self.total_input_tokens += tok("input_tokens"); |
| 239 | self.total_output_tokens += tok("output_tokens"); |
| 240 | self.total_cache_read_input_tokens += tok("cache_read_input_tokens"); |
| 241 | self.total_cache_creation_input_tokens += tok("cache_creation_input_tokens"); |
| 242 | } |
| 243 | } |
| 244 | Some("ai-title") => { |
| 245 | if let Some(title) = get_str("aiTitle") { |
| 246 | self.title = Some(title); |
| 247 | } |
| 248 | } |
| 249 | Some("pr-link") => { |
| 250 | if let Some(url) = get_str("prUrl") { |
| 251 | self.pr_url = Some(url); |
| 252 | } |
| 253 | if let Some(n) = json.get("prNumber").and_then(serde_json::Value::as_i64) { |
| 254 | self.pr_number = Some(n); |
no outgoing calls
no test coverage detected