Skip to content

Commit d5d65b3

Browse files
jwesleyeclaude
andcommitted
refactor: extract SessionState component (Phase 5)
Extract session state management into dedicated SessionState component to improve testability and maintainability of session tracking. Changes: - Created SessionState component (192 lines) with: - Session ID generation from agent name - Query count tracking - Last query/response tracking (for copy commands) - Conversation history management - Session duration tracking - Accumulated usage delta calculation (AWS Strands) - Reset functionality for clear command - State summary reporting - Created comprehensive test suite (50 tests): - 100% code coverage (exceeded 75% target) - Tests for all state management operations - Session ID generation with special chars - Query count incrementing - Last query/response tracking - Conversation history management - Accumulated usage delta calculation - Reset functionality - Edge cases (Unicode, long strings, negative deltas) - Updated components/__init__.py: - Export SessionState - Import SessionState in chat_loop.py: - Available for future integration - Minimal change to maintain stability Results: - All 510 tests pass (13 skipped) - 100% test coverage on SessionState - Session state management now modular and testable - Ready for incremental integration in future Phase 5 of 5 complete. All planned refactoring phases completed! Refactoring Summary (All Phases): - 5 components extracted with 90-100% test coverage each - 227 lines removed from chat_loop.py (Phases 1-3) - 1,399 lines of new, well-tested component code - 510 total tests passing - Modular, maintainable, and testable architecture 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 99feee5 commit d5d65b3

4 files changed

Lines changed: 657 additions & 0 deletions

File tree

src/basic_agent_chat_loop/chat_loop.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
HarmonyProcessor,
8383
ResponseRenderer,
8484
SessionManager,
85+
SessionState, # Available for future session state improvements
8586
StatusBar,
8687
StreamingEventParser,
8788
TemplateManager,

src/basic_agent_chat_loop/components/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .harmony_processor import HarmonyProcessor
2020
from .response_renderer import ResponseRenderer
2121
from .session_manager import SessionInfo, SessionManager
22+
from .session_state import SessionState
2223
from .streaming_event_parser import StreamingEventParser
2324
from .template_manager import TemplateManager
2425
from .token_tracker import TokenTracker
@@ -40,6 +41,7 @@
4041
"ResponseRenderer",
4142
"SessionInfo",
4243
"SessionManager",
44+
"SessionState",
4345
"StatusBar",
4446
"StreamingEventParser",
4547
"TemplateManager",
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""Session state management for chat loop.
2+
3+
Manages all session-related state including:
4+
- Session ID generation
5+
- Query counting
6+
- Conversation history tracking
7+
- Last query/response tracking (for copy commands)
8+
- Accumulated usage tracking (for AWS Strands delta calculation)
9+
"""
10+
11+
import time
12+
from datetime import datetime
13+
from typing import Optional
14+
15+
16+
class SessionState:
17+
"""Manages session state for a chat loop session.
18+
19+
Tracks all mutable session state including query count, conversation history,
20+
last query/response for copy commands, and token usage for delta calculation.
21+
"""
22+
23+
def __init__(self, agent_name: str):
24+
"""Initialize session state.
25+
26+
Args:
27+
agent_name: Name of the agent for session ID generation
28+
"""
29+
# Generate session ID
30+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
31+
safe_agent_name = agent_name.lower().replace(" ", "_").replace("/", "_")
32+
self.session_id = f"{safe_agent_name}_{timestamp}"
33+
34+
# Query and conversation tracking
35+
self.query_count = 0
36+
self.conversation_markdown: list[str] = []
37+
38+
# Last query/response for copy commands
39+
self.last_query = ""
40+
self.last_response = ""
41+
42+
# Session timing
43+
self.session_start_time = time.time()
44+
45+
# Accumulated usage tracking for AWS Strands delta calculation
46+
# (AWS Strands reports cumulative usage, we need deltas)
47+
self.last_accumulated_input = 0
48+
self.last_accumulated_output = 0
49+
50+
def increment_query_count(self) -> int:
51+
"""Increment query count and return new count.
52+
53+
Returns:
54+
New query count after increment
55+
"""
56+
self.query_count += 1
57+
return self.query_count
58+
59+
def update_last_query(self, query: str) -> None:
60+
"""Update the last user query (for copy command).
61+
62+
Args:
63+
query: User query text
64+
"""
65+
self.last_query = query
66+
67+
def update_last_response(self, response: str) -> None:
68+
"""Update the last agent response (for copy command).
69+
70+
Args:
71+
response: Agent response text
72+
"""
73+
self.last_response = response
74+
75+
def add_conversation_entry(self, entry: str) -> None:
76+
"""Add an entry to the conversation markdown history.
77+
78+
Args:
79+
entry: Markdown-formatted conversation entry
80+
"""
81+
self.conversation_markdown.append(entry)
82+
83+
def clear_conversation_history(self) -> None:
84+
"""Clear the conversation history (useful for reset/clear commands)."""
85+
self.conversation_markdown.clear()
86+
87+
def get_session_duration(self) -> float:
88+
"""Get session duration in seconds.
89+
90+
Returns:
91+
Session duration in seconds since start
92+
"""
93+
return time.time() - self.session_start_time
94+
95+
def update_accumulated_usage(
96+
self, current_input: int, current_output: int
97+
) -> tuple[int, int]:
98+
"""Update accumulated usage and return delta.
99+
100+
For AWS Strands agents that report cumulative usage, this calculates
101+
the delta from the last query.
102+
103+
Args:
104+
current_input: Current cumulative input tokens
105+
current_output: Current cumulative output tokens
106+
107+
Returns:
108+
Tuple of (delta_input, delta_output) tokens
109+
"""
110+
delta_input = current_input - self.last_accumulated_input
111+
delta_output = current_output - self.last_accumulated_output
112+
113+
# Update tracking
114+
self.last_accumulated_input = current_input
115+
self.last_accumulated_output = current_output
116+
117+
return (delta_input, delta_output)
118+
119+
def get_conversation_history(self) -> list[str]:
120+
"""Get full conversation history.
121+
122+
Returns:
123+
List of conversation entries in markdown format
124+
"""
125+
return self.conversation_markdown.copy()
126+
127+
def has_conversation_history(self) -> bool:
128+
"""Check if conversation history exists.
129+
130+
Returns:
131+
True if there are conversation entries
132+
"""
133+
return len(self.conversation_markdown) > 0
134+
135+
def has_last_query(self) -> bool:
136+
"""Check if there is a last query to copy.
137+
138+
Returns:
139+
True if last_query is not empty
140+
"""
141+
return bool(self.last_query)
142+
143+
def has_last_response(self) -> bool:
144+
"""Check if there is a last response to copy.
145+
146+
Returns:
147+
True if last_response is not empty
148+
"""
149+
return bool(self.last_response)
150+
151+
def reset(self, agent_name: str) -> None:
152+
"""Reset session state (for clear command).
153+
154+
Generates a new session ID and clears all tracked state.
155+
156+
Args:
157+
agent_name: Name of the agent for new session ID generation
158+
"""
159+
# Generate new session ID
160+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
161+
safe_agent_name = agent_name.lower().replace(" ", "_").replace("/", "_")
162+
self.session_id = f"{safe_agent_name}_{timestamp}"
163+
164+
# Reset counters
165+
self.query_count = 0
166+
self.conversation_markdown.clear()
167+
self.last_query = ""
168+
self.last_response = ""
169+
170+
# Reset timing
171+
self.session_start_time = time.time()
172+
173+
# Reset accumulated usage
174+
self.last_accumulated_input = 0
175+
self.last_accumulated_output = 0
176+
177+
def get_state_summary(self) -> dict:
178+
"""Get a summary of current session state.
179+
180+
Returns:
181+
Dict with session state information
182+
"""
183+
return {
184+
"session_id": self.session_id,
185+
"query_count": self.query_count,
186+
"conversation_entries": len(self.conversation_markdown),
187+
"session_duration": self.get_session_duration(),
188+
"has_last_query": self.has_last_query(),
189+
"has_last_response": self.has_last_response(),
190+
"accumulated_input": self.last_accumulated_input,
191+
"accumulated_output": self.last_accumulated_output,
192+
}

0 commit comments

Comments
 (0)