//! Local usage log (~/.local/share/codecraft/usage.jsonl) for /costs. use crate::session::LocalSession; use codecraft_core::types::{Usage, now_ms}; use serde_json::{Value, json}; use std::io::Write; fn path() -> std::path::PathBuf { crate::conf::data_dir().join("usage.jsonl") } pub fn record(session: &LocalSession, model: &str, u: &Usage, cost: f64) { let line = json!({"ts": now_ms(), "session": session.id, "model": model, "in": u.input_tokens, "out": u.output_tokens, "cache_read": u.cache_read_tokens, "cache_write": u.cache_write_tokens, "cost": cost}); let _ = std::fs::create_dir_all(crate::conf::data_dir()); if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(path()) { let _ = writeln!(f, "{line}"); } } /// (requests, input, output, cost) per model since `since` (ms). pub fn summary(since: i64) -> Vec<(String, u64, u64, u64, f64)> { let text = std::fs::read_to_string(path()).unwrap_or_default(); let mut map: std::collections::BTreeMap = Default::default(); for line in text.lines() { let Ok(v) = serde_json::from_str::(line) else { continue }; if v["ts"].as_i64().unwrap_or(0) < since { continue; } let e = map.entry(v["model"].as_str().unwrap_or("?").to_string()).or_default(); e.0 += 1; e.1 += v["in"].as_u64().unwrap_or(0) + v["cache_read"].as_u64().unwrap_or(0) + v["cache_write"].as_u64().unwrap_or(0); e.2 += v["out"].as_u64().unwrap_or(0); e.3 += v["cost"].as_f64().unwrap_or(0.0); } map.into_iter().map(|(k, (n, i, o, c))| (k, n, i, o, c)).collect() }