Research – Token Counting Logic
Date: 2026-02-01 Phase: Research
Structure
src/tunacode/utils/messaging/(adapter + token_counter)src/tunacode/types/canonical.py(canonical message + usage types)src/tunacode/core/state.py(session token count computation + persistence)src/tunacode/core/types/state_structures.py(ConversationState.total_tokens)src/tunacode/core/agents/main.py(token count refresh after message changes)src/tunacode/core/agents/resume/(prune/summary/filter using heuristic tokens)src/tunacode/core/agents/agent_components/orchestrator/(provider usage tokens)src/tunacode/ui/app.py(resource bar + response panel token display)src/tunacode/ui/commands/__init__.py(clear command preserves total_tokens)
Key Files
src/tunacode/utils/messaging/token_counter.py:12-129defines heuristic constants (CHARS_PER_TOKEN=4),MessageTokenCacheEntry,MessageTokenCache,estimate_tokens,estimate_tokens_from_length,get_message_text_length.MessageTokenCache.rebuild_total()(line 34) iteratesmessages, usesid(message)as the cache key, calls_get_or_update, sums token counts, then_prune._get_or_update()(line 52) callsget_message_text_length()thenestimate_tokens_from_length()._prune()(line 65) removes cache entries not in active ids.estimate_tokens()(line 73) useslen(text)thenestimate_tokens_from_length().get_message_text_length()(line 99) canonicalizes and computes part length.
src/tunacode/utils/messaging/adapter.py:57-205defines_get_parts()andto_canonical()used bytoken_counterto normalize messages intoCanonicalMessage.src/tunacode/types/canonical.py:104-133definesCanonicalMessageandget_text_content();UsageMetricsat lines 184-214;NormalizedUsageat lines 260-289.src/tunacode/core/state.py:65-75SessionState.update_token_count()computesconversation.total_tokensviaMessageTokenCache.rebuild_total.adjust_token_count()is defined at lines 72-75. Session persistence storestotal_tokensat lines 273 and loads at lines 321-322.src/tunacode/core/types/state_structures.py:24-33ConversationState.total_tokensandmax_tokensfields.src/tunacode/core/agents/main.py:222,303,350,583,590,594,598callsession.update_token_count()after message list changes and cleanup.src/tunacode/core/agents/resume/prune.py:100-224usesestimate_tokensto compute token counts for tool return parts and pruning thresholds.src/tunacode/core/agents/resume/summary.py:97-201usesestimate_tokensto compute message totals for compaction and summarytoken_count.src/tunacode/core/agents/resume/filter.py:52-76prepare_history()combines summary filtering and pruning and returnstokens_reclaimed.src/tunacode/core/agents/agent_components/orchestrator/usage_tracker.py:15-53update_usage()reads provider usage vianormalize_request_usage()and updatesUsageMetrics.src/tunacode/core/agents/agent_components/orchestrator/orchestrator.py:217-258callsupdate_usage()onmodel_response.src/tunacode/ui/app.py:217-236usessession.usage.last_call_usage.completion_tokensfor response metadata;src/tunacode/ui/app.py:365-376usesconversation.total_tokensfor the resource bar.src/tunacode/ui/commands/__init__.py:77-105clear command preservesconversation.total_tokensand resets usage metrics.
Patterns Found
- Conversation token total (heuristic):
SessionState.update_token_count()→MessageTokenCache.rebuild_total()→_get_or_update()→get_message_text_length()→_canonicalize_message()→to_canonical()→_canonical_text_length(); thenestimate_tokens_from_length()divides character length byCHARS_PER_TOKEN.- Cache keys are
id(message)and_prune()removes entries not in the current message list.
- Heuristic token estimates in resume/compaction:
estimate_tokens()is called inresume/prune.py(tool-output pruning) andresume/summary.py(summary threshold + summary token_count).
- Provider usage tokens:
orchestrator.update_usage()readsmodel_response.usage, normalizes it vianormalize_request_usage(), and updatessession.usage.last_call_usageandsession.usage.session_total_usage.
- Display:
- Resource bar reads
conversation.total_tokens(heuristic count). - Response panels read
session.usage.last_call_usage.completion_tokens(provider usage).
- Resource bar reads
Dependencies
core/state.pyimportsMessageTokenCachefromtunacode.utils.messaging.utils/messaging/token_counter.pyimportsCanonicalMessageandto_canonical.core/agents/resume/prune.pyandcore/agents/resume/summary.pyimportestimate_tokens.core/agents/agent_components/orchestrator/usage_tracker.pyimportsUsageMetricsandnormalize_request_usagefromtypes/canonical.py.- UI modules read
conversation.total_tokensandsession.usage.last_call_usage.
Symbol Index
tunacode.utils.messaging.token_counterexportsMessageTokenCache,estimate_tokens,get_message_text_length(utils/messaging/__init__.py:16-20).tunacode.core.agents.resumeexportsprune_old_tool_outputs,filter_compacted,prepare_history,SummaryMessage,should_compact,generate_summary(core/agents/resume/__init__.py:15-41).tunacode.core.agents.agent_components.orchestrator.usage_trackerexportsupdate_usage(usage_tracker.py:15).tunacode.types.canonicalexportsCanonicalMessage,UsageMetrics,NormalizedUsage,normalize_request_usage(types/canonical.py:104-319).