diff --git a/agent/insights.py b/agent/insights.py index 70907b4f3d57..803787cd341d 100644 --- a/agent/insights.py +++ b/agent/insights.py @@ -762,6 +762,9 @@ def format_terminal(self, report: Dict) -> str: lines.append(f" Sessions: {o['total_sessions']:<12} Messages: {o['total_messages']:,}") lines.append(f" Tool calls: {o['total_tool_calls']:<12,} User messages: {o['user_messages']:,}") lines.append(f" Input tokens: {o['total_input_tokens']:<12,} Output tokens: {o['total_output_tokens']:,}") + cache_total = (o.get('total_cache_read_tokens') or 0) + (o.get('total_cache_write_tokens') or 0) + if cache_total: + lines.append(f" Cache tokens: {cache_total:<12,} (read: {o.get('total_cache_read_tokens') or 0:,} / write: {o.get('total_cache_write_tokens') or 0:,})") lines.append(f" Total tokens: {o['total_tokens']:,}") if o["total_hours"] > 0: lines.append(f" Active time: ~{_format_duration(o['total_hours'] * 3600):<11} Avg session: ~{_format_duration(o['avg_session_duration'])}") @@ -877,7 +880,9 @@ def format_gateway(self, report: Dict) -> str: # Overview lines.append(f"**Sessions:** {o['total_sessions']} | **Messages:** {o['total_messages']:,} | **Tool calls:** {o['total_tool_calls']:,}") - lines.append(f"**Tokens:** {o['total_tokens']:,} (in: {o['total_input_tokens']:,} / out: {o['total_output_tokens']:,})") + cache_total = (o.get('total_cache_read_tokens') or 0) + (o.get('total_cache_write_tokens') or 0) + cache_part = f" / cache: {cache_total:,}" if cache_total else "" + lines.append(f"**Tokens:** {o['total_tokens']:,} (in: {o['total_input_tokens']:,} / out: {o['total_output_tokens']:,}{cache_part})") if o["total_hours"] > 0: lines.append(f"**Active time:** ~{_format_duration(o['total_hours'] * 3600)} | **Avg session:** ~{_format_duration(o['avg_session_duration'])}") lines.append("") diff --git a/tests/agent/test_insights.py b/tests/agent/test_insights.py index 2740daf0962f..6802495114d2 100644 --- a/tests/agent/test_insights.py +++ b/tests/agent/test_insights.py @@ -533,6 +533,77 @@ def test_gateway_format_shows_models(self, populated_db): assert "sessions" in text +# ========================================================================= +# Cache token display (issue #18615) +# ========================================================================= + +class TestCacheTokenDisplay: + def _make_db_with_cache(self, db): + """Helper: create a session with significant cache tokens.""" + db.create_session(session_id="s_cache", source="cli", model="claude-sonnet") + db.update_token_counts( + "s_cache", + input_tokens=2000, + output_tokens=800, + cache_read_tokens=50000, + cache_write_tokens=10000, + ) + db._conn.commit() + return db + + def test_terminal_format_shows_cache_when_present(self, db): + """Terminal format must include cache token line when cache > 0.""" + self._make_db_with_cache(db) + engine = InsightsEngine(db) + report = engine.generate(days=30) + text = engine.format_terminal(report) + + assert "Cache tokens" in text + assert "50,000" in text or "60,000" in text # read or total + + def test_gateway_format_shows_cache_when_present(self, db): + """Gateway format must include cache in token breakdown when cache > 0.""" + self._make_db_with_cache(db) + engine = InsightsEngine(db) + report = engine.generate(days=30) + text = engine.format_gateway(report) + + assert "cache:" in text.lower() + + def test_terminal_format_hides_cache_when_zero(self, db): + """Terminal format must NOT show cache line when all cache tokens are 0.""" + db.create_session(session_id="s_nocache", source="cli", model="gpt-4o") + db.update_token_counts("s_nocache", input_tokens=1000, output_tokens=500) + db._conn.commit() + + engine = InsightsEngine(db) + report = engine.generate(days=30) + text = engine.format_terminal(report) + + assert "Cache tokens" not in text + + def test_gateway_format_hides_cache_when_zero(self, db): + """Gateway format must NOT mention cache when all cache tokens are 0.""" + db.create_session(session_id="s_nocache", source="cli", model="gpt-4o") + db.update_token_counts("s_nocache", input_tokens=1000, output_tokens=500) + db._conn.commit() + + engine = InsightsEngine(db) + report = engine.generate(days=30) + text = engine.format_gateway(report) + + assert "cache" not in text.lower() + + def test_total_tokens_include_cache(self, db): + """total_tokens must equal input + output + cache_read + cache_write.""" + self._make_db_with_cache(db) + engine = InsightsEngine(db) + report = engine.generate(days=30) + o = report["overview"] + + assert o["total_tokens"] == 2000 + 800 + 50000 + 10000 + + # ========================================================================= # Edge cases # =========================================================================