← Commit history

Merge PR #258: 0.2.2: Antigravity tokens per conversation (agy-per-conversation-0.2.2 -> main) merge

John Lauer ·3dc1764a05 · ·parents d0f8606, f32d1cb

Showing changes introduced by the merge (against its first parent).

3 files changed +15−3
crates/adom-aiflow/src/main.rs+7−1
@@ -692,6 +692,12 @@ fn agy_report(r: &Run) -> Option<Value> {     let snaps: Vec<Value> = r.data["usage"].as_array().cloned().unwrap_or_default().into_iter().filter(|s| s["agyTokens"].is_object()).collect();     if snaps.len() < 2 { return None; }     let keys = ["calls", "freshIn", "cachedIn", "out"];+    // the run's own conversation: the one whose counters grew most over the run (the AI calls aiflow+    // from it); other Antigravity conversations on the machine (another project) are left out+    let (first, last) = (&snaps[0]["agyTokens"], &snaps[snaps.len() - 1]["agyTokens"]);+    let conv: Option<String> = last["byConversation"].as_object().and_then(|m| m.iter().map(|(id, c)| (id.clone(), c["calls"].as_i64().unwrap_or(0) - first["byConversation"][id]["calls"].as_i64().unwrap_or(0))).filter(|(_, d)| *d > 0).max_by_key(|(_, d)| *d).map(|(id, _)| id));+    let pick = |s: &Value| -> Value { match &conv { Some(id) => { let c = &s["byConversation"][id]; if c.is_object() { c.clone() } else { json!({"calls": 0, "freshIn": 0, "cachedIn": 0, "out": 0}) } } None => s.clone() } };+    let snaps: Vec<Value> = snaps.into_iter().map(|mut s| { s["agyTokens"] = pick(&s["agyTokens"]); s }).collect();     let diff = |a: &Value, b: &Value| -> Value { let mut m = serde_json::Map::new(); for k in keys { m.insert(k.into(), json!(b[k].as_i64().unwrap_or(0).saturating_sub(a[k].as_i64().unwrap_or(0)).max(0))); } Value::Object(m) };     let mut steps: Vec<Value> = Vec::new();     for w in snaps.windows(2) {@@ -706,7 +712,7 @@ fn agy_report(r: &Run) -> Option<Value> {     let week = usage_delta(r).into_iter().find(|x| x["provider"] == "antigravity" && x["label"] == "week").and_then(|x| x["usedPct"].as_f64());     let month: f64 = std::env::var("AIFLOW_PLAN_USD_MONTH").ok().and_then(|v| v.parse().ok()).unwrap_or(199.99);     let usd = week.map(|p| (p / 100.0 * month * 12.0 / 52.0 * 100.0).round() / 100.0);-    Some(json!({"total": total, "steps": steps, "weekPct": week, "planUsdMonth": month, "usd": usd}))+    Some(json!({"total": total, "steps": steps, "weekPct": week, "planUsdMonth": month, "usd": usd, "conversation": conv})) }  /// First and last usage per provider and label: what the run cost in plan percent.
docs/release-0.2.2.mdadded+3
@@ -0,0 +1,3 @@+# adom-aiflow 0.2.2++Antigravity token counts are kept per conversation; the run report and `deliver` count only the run's own conversation (the one whose counters grew most during the run), so another project using Antigravity on the same machine does not inflate the run's tokens. The weekly-limit percent is still per account, as Google reports it.
tools/agy-usage.py+5−2
@@ -5,7 +5,8 @@   tokens: cumulative model-call counters summed over every conversation in           ~/.gemini/antigravity/conversations/*.db (gen_metadata field 1.4:           .2 fresh input, .3 output, .5 cached input), plus per-model totals.-Counters only grow, so the difference between two snapshots is what ran in between.+Counters only grow, so the difference between two snapshots is what ran in between; byConversation+lets a report keep only the run's own conversation when other Antigravity work shares the machine. Undocumented formats (decoded 2026-10-16/17); anything unreadable is skipped, never fatal.""" import glob, json, os, re, sqlite3, urllib.request @@ -32,8 +33,9 @@ def fields(b):   def tokens():-    tot = {"calls": 0, "freshIn": 0, "cachedIn": 0, "out": 0, "byModel": {}}+    tot = {"calls": 0, "freshIn": 0, "cachedIn": 0, "out": 0, "byModel": {}, "byConversation": {}}     for db in glob.glob(os.path.expanduser("~/.gemini/antigravity/conversations/*.db")):+        conv = tot["byConversation"].setdefault(os.path.basename(db)[:-3], {"calls": 0, "freshIn": 0, "cachedIn": 0, "out": 0})         try:             con = sqlite3.connect(f"file:{db}?mode=ro", uri=True, timeout=2)             rows = con.execute("select data from gen_metadata").fetchall()@@ -53,6 +55,7 @@ def tokens():                         if f2 == 4 and t2 == 2:                             d = {a: c for a, b2, c in fields(x) if b2 == 0}                             tot["calls"] += 1; tot["freshIn"] += d.get(2, 0); tot["out"] += d.get(3, 0); tot["cachedIn"] += d.get(5, 0)+                            conv["calls"] += 1; conv["freshIn"] += d.get(2, 0); conv["out"] += d.get(3, 0); conv["cachedIn"] += d.get(5, 0)                             m = tot["byModel"].setdefault(model or "unknown", {"calls": 0, "freshIn": 0, "cachedIn": 0, "out": 0})                             m["calls"] += 1; m["freshIn"] += d.get(2, 0); m["out"] += d.get(3, 0); m["cachedIn"] += d.get(5, 0)             except Exception: