← All Pull Requests

0.2.2: Antigravity tokens per conversation #37

Merged Released in 0.2.2 on 18 Oct 2026 opened by John Lauer
Merges agy-per-conversation-0.2.2 → main

Base d0f860685c26b38c7e2dbb8eadefa0475df87f59 (Publish 0.2.1). Antigravity token counters per conversation; the run report counts only the conversation whose counters grew most over the run, so other Antigravity projects on the machine do not inflate it. Tests: workspace all pass; preflight no problems.

Diff Skip to comments

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:

Comments

No comments yet.

Log in to comment.