app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
← All Pull Requests
0.2.2: Antigravity tokens per conversation #37
Merges
agy-per-conversation-0.2.2 → mainBase 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
Log in to comment.
No comments yet.