Skip to content

feat(tokens): per-turn token tracking and StreamUsageSummary event#219

Open
torrmal wants to merge 4 commits into
stagingfrom
feat/cost-management
Open

feat(tokens): per-turn token tracking and StreamUsageSummary event#219
torrmal wants to merge 4 commits into
stagingfrom
feat/cost-management

Conversation

@torrmal

@torrmal torrmal commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds StreamUsageSummary dataclass to provider.py — carries input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens for a full turn
  • In session.py, accumulates token counts across every LLM call in a turn (main loop + error-fallback path), logs them at INFO level, and yields a StreamUsageSummary after the turn loop completes
  • Pre-merges turn_stream() signature additions from PR send trace data #218 (trace_tags / trace_metadata / TraceContext kwargs) to avoid a conflict when that lands

Log line emitted per turn

turn_tokens session=<id> turn=N in=X out=Y cache_read=0 cache_write=0

Cache fields will be non-zero once the Anthropic prompt-caching work (stashed on this branch) is wired in.

Test plan

  • Send a message; confirm turn_tokens log line appears with non-zero in/out
  • Multi-tool turn: confirm tokens are summed across all LLM calls, not just the last

🤖 Generated with Claude Code

torrmal and others added 4 commits June 25, 2026 17:47
Add StreamUsageSummary event type to provider.py. In session.py, accumulate
input/output/cache tokens across every LLM call in a turn, log them at INFO
level, and yield a StreamUsageSummary after the turn loop completes.

Also pre-merge turn_stream() signature additions from PR #218 (trace_tags /
trace_metadata / TraceContext kwargs) to avoid a conflict when that PR lands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…k artifacts

The ~5,200-token backend template (FastAPI canonical template, 7-step
workflow, deployment rules) was unconditionally included in every system
prompt — even for simple chat turns, data analysis, and any session with
no backend artifacts.

prompt_builder.py: add include_backend_prompt: bool = False to build().
session.py: add _has_fullstack_artifacts() which scans the workspace
ArtifactStore for any fullstack-stateless-app / fullstack-stateful-app
artifact. Pass the result as include_backend_prompt on every prompt build.

Because _build_system_prompt() is called per-LLM-call (not per-turn),
the prompt flips to include the template on the very next call after
create_artifact() registers a fullstack artifact — no turns are lost.
Sessions with no fullstack artifacts save ~5,200 tokens per turn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntent detection

_has_fullstack_artifacts() only checked existing workspace artifacts, missing
the case where a user asks to build a backend app from scratch in a fresh
session — the model would enter the first LLM call without the FastAPI
template and deployment rules.

Replace with _needs_backend_prompt(user_message) which fires on any of:
1. Workspace already contains a fullstack-stateless/stateful artifact.
2. Current user message contains backend/app-building intent keywords.
3. Any prior user turn in conversation history matched (2) — covers
   multi-turn sessions where the original request was several turns ago.

_message_suggests_backend() is a pure keyword heuristic (no LLM call):
strong signals (fastapi, flask, backend, fullstack, etc.) trigger alone;
weaker signals (app, server, service, ...) require a co-occurring build verb.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant