diff --git a/src/agent_chat_cli/components/chat_history.py b/src/agent_chat_cli/components/chat_history.py index 0f0885c..1543811 100644 --- a/src/agent_chat_cli/components/chat_history.py +++ b/src/agent_chat_cli/components/chat_history.py @@ -4,7 +4,7 @@ from agent_chat_cli.components.messages import ( AgentMessage, Message, - MessageType, + RoleType, SystemMessage, ToolMessage, UserMessage, @@ -20,22 +20,22 @@ def _create_message_widget( self, message: Message ) -> SystemMessage | UserMessage | AgentMessage | ToolMessage: match message.type: - case MessageType.SYSTEM: + case RoleType.SYSTEM: system_widget = SystemMessage() system_widget.message = message.content return system_widget - case MessageType.USER: + case RoleType.USER: user_widget = UserMessage() user_widget.message = message.content return user_widget - case MessageType.AGENT: + case RoleType.AGENT: agent_widget = AgentMessage() agent_widget.message = message.content return agent_widget - case MessageType.TOOL: + case RoleType.TOOL: tool_widget = ToolMessage() if message.metadata and "tool_name" in message.metadata: diff --git a/src/agent_chat_cli/components/messages.py b/src/agent_chat_cli/components/messages.py index 4c6697f..d87280b 100644 --- a/src/agent_chat_cli/components/messages.py +++ b/src/agent_chat_cli/components/messages.py @@ -10,7 +10,7 @@ from agent_chat_cli.utils import get_tool_info, format_tool_input -class MessageType(Enum): +class RoleType(Enum): SYSTEM = "system" USER = "user" AGENT = "agent" @@ -19,26 +19,26 @@ class MessageType(Enum): @dataclass class Message: - type: MessageType + type: RoleType content: str metadata: dict[str, Any] | None = None @classmethod def system(cls, content: str) -> "Message": - return cls(type=MessageType.SYSTEM, content=content) + return cls(type=RoleType.SYSTEM, content=content) @classmethod def user(cls, content: str) -> "Message": - return cls(type=MessageType.USER, content=content) + return cls(type=RoleType.USER, content=content) @classmethod def agent(cls, content: str) -> "Message": - return cls(type=MessageType.AGENT, content=content) + return cls(type=RoleType.AGENT, content=content) @classmethod def tool(cls, tool_name: str, content: str) -> "Message": return cls( - type=MessageType.TOOL, content=content, metadata={"tool_name": tool_name} + type=RoleType.TOOL, content=content, metadata={"tool_name": tool_name} ) diff --git a/src/agent_chat_cli/core/actions.py b/src/agent_chat_cli/core/actions.py index e0cfc57..353bc73 100644 --- a/src/agent_chat_cli/core/actions.py +++ b/src/agent_chat_cli/core/actions.py @@ -2,7 +2,7 @@ from agent_chat_cli.utils.enums import ControlCommand from agent_chat_cli.components.chat_history import ChatHistory -from agent_chat_cli.components.messages import Message, MessageType +from agent_chat_cli.components.messages import RoleType from agent_chat_cli.components.tool_permission_prompt import ToolPermissionPrompt from agent_chat_cli.utils.logger import log_json @@ -17,34 +17,15 @@ def __init__(self, app: "AgentChatCLIApp") -> None: def quit(self) -> None: self.app.exit() - async def add_message_to_chat_history( - self, type: MessageType, content: str - ) -> None: - match type: - case MessageType.USER: - message = Message.user(content) - case MessageType.SYSTEM: - message = Message.system(content) - case MessageType.AGENT: - message = Message.agent(content) - case _: - raise ValueError(f"Unsupported message type: {type}") - - chat_history = self.app.query_one(ChatHistory) - chat_history.add_message(message) - async def submit_user_message(self, message: str) -> None: - chat_history = self.app.query_one(ChatHistory) - chat_history.add_message(Message.user(message)) - self.app.ui_state.start_thinking() - await self.app.ui_state.scroll_to_bottom() + await self.app.renderer.add_message(RoleType.USER, message) await self._query(message) async def post_system_message(self, message: str) -> None: - await self.add_message_to_chat_history(MessageType.SYSTEM, message) + await self.app.renderer.add_message(RoleType.SYSTEM, message) - async def render_message(self, message) -> None: - await self.app.renderer.render_message(message) + async def handle_app_event(self, event) -> None: + await self.app.renderer.handle_app_event(event) async def interrupt(self) -> None: permission_prompt = self.app.query_one(ToolPermissionPrompt) diff --git a/src/agent_chat_cli/core/agent_loop.py b/src/agent_chat_cli/core/agent_loop.py index 96d79a2..80cfc27 100644 --- a/src/agent_chat_cli/core/agent_loop.py +++ b/src/agent_chat_cli/core/agent_loop.py @@ -24,7 +24,7 @@ get_available_servers, get_sdk_config, ) -from agent_chat_cli.utils.enums import AgentMessageType, ContentType, ControlCommand +from agent_chat_cli.utils.enums import AppEventType, ContentType, ControlCommand from agent_chat_cli.utils.logger import log_json from agent_chat_cli.utils.mcp_server_status import MCPServerStatus @@ -33,8 +33,8 @@ @dataclass -class AgentMessage: - type: AgentMessageType +class AppEvent: + type: AppEventType data: Any @@ -93,8 +93,8 @@ async def start(self) -> None: await self._handle_message(message) - await self.app.actions.render_message( - AgentMessage(type=AgentMessageType.RESULT, data=None) + await self.app.actions.handle_app_event( + AppEvent(type=AppEventType.RESULT, data=None) ) async def _initialize_client(self, mcp_servers: dict) -> None: @@ -115,7 +115,7 @@ async def _handle_message(self, message: Message) -> None: if isinstance(message, SystemMessage): log_json(message.data) - if message.subtype == AgentMessageType.INIT.value and message.data.get( + if message.subtype == AppEventType.INIT.value and message.data.get( "session_id" ): # When initializing the chat, we store the session_id for later @@ -136,9 +136,9 @@ async def _handle_message(self, message: Message) -> None: text_chunk = delta.get("text", "") if text_chunk: - await self.app.actions.render_message( - AgentMessage( - type=AgentMessageType.STREAM_EVENT, + await self.app.actions.handle_app_event( + AppEvent( + type=AppEventType.STREAM_EVENT, data={"text": text_chunk}, ) ) @@ -164,9 +164,9 @@ async def _handle_message(self, message: Message) -> None: ) # Finally, post the agent assistant response - await self.app.actions.render_message( - AgentMessage( - type=AgentMessageType.ASSISTANT, + await self.app.actions.handle_app_event( + AppEvent( + type=AppEventType.ASSISTANT, data={"content": content}, ) ) @@ -181,9 +181,9 @@ async def _can_use_tool( # Handle permission request queue sequentially async with self.permission_lock: - await self.app.actions.render_message( - AgentMessage( - type=AgentMessageType.TOOL_PERMISSION_REQUEST, + await self.app.actions.handle_app_event( + AppEvent( + type=AppEventType.TOOL_PERMISSION_REQUEST, data={ "tool_name": tool_name, "tool_input": tool_input, diff --git a/src/agent_chat_cli/core/renderer.py b/src/agent_chat_cli/core/renderer.py index a1bb44a..07f27de 100644 --- a/src/agent_chat_cli/core/renderer.py +++ b/src/agent_chat_cli/core/renderer.py @@ -6,11 +6,12 @@ from agent_chat_cli.components.chat_history import ChatHistory from agent_chat_cli.components.messages import ( AgentMessage as AgentMessageWidget, - MessageType, + Message, + RoleType, ToolMessage, ) -from agent_chat_cli.core.agent_loop import AgentMessage -from agent_chat_cli.utils.enums import AgentMessageType, ContentType +from agent_chat_cli.core.agent_loop import AppEvent +from agent_chat_cli.utils.enums import AppEventType, ContentType from agent_chat_cli.utils.logger import log_json if TYPE_CHECKING: @@ -32,31 +33,48 @@ def __init__(self, app: "AgentChatCLIApp") -> None: self.app = app self._stream = StreamBuffer() - async def render_message(self, message: AgentMessage) -> None: - match message.type: - case AgentMessageType.STREAM_EVENT: - await self._render_stream_event(message) + async def handle_app_event(self, event: AppEvent) -> None: + match event.type: + case AppEventType.STREAM_EVENT: + await self._render_stream_event(event) - case AgentMessageType.ASSISTANT: - await self._render_assistant_message(message) + case AppEventType.ASSISTANT: + await self._render_assistant_message(event) - case AgentMessageType.SYSTEM: - await self._render_system_message(message) + case AppEventType.SYSTEM: + await self._render_system_message(event) - case AgentMessageType.USER: - await self._render_user_message(message) + case AppEventType.USER: + await self._render_user_message(event) - case AgentMessageType.TOOL_PERMISSION_REQUEST: - await self._render_tool_permission_request(message) + case AppEventType.TOOL_PERMISSION_REQUEST: + await self._render_tool_permission_request(event) - case AgentMessageType.RESULT: + case AppEventType.RESULT: await self._on_complete() - if message.type is not AgentMessageType.RESULT: + if event.type is not AppEventType.RESULT: await self.app.ui_state.scroll_to_bottom() - async def _render_stream_event(self, message: AgentMessage) -> None: - text_chunk = message.data.get("text", "") + async def add_message(self, type: RoleType, content: str) -> None: + match type: + case RoleType.USER: + message = Message.user(content) + case RoleType.SYSTEM: + message = Message.system(content) + case RoleType.AGENT: + message = Message.agent(content) + case _: + raise ValueError(f"Unsupported message type: {type}") + + chat_history = self.app.query_one(ChatHistory) + chat_history.add_message(message) + + self.app.ui_state.start_thinking() + await self.app.ui_state.scroll_to_bottom() + + async def _render_stream_event(self, event: AppEvent) -> None: + text_chunk = event.data.get("text", "") if not text_chunk: return @@ -77,8 +95,8 @@ async def _render_stream_event(self, message: AgentMessage) -> None: markdown = self._stream.widget.query_one(Markdown) markdown.update(self._stream.text) - async def _render_assistant_message(self, message: AgentMessage) -> None: - content_blocks = message.data.get("content", []) + async def _render_assistant_message(self, event: AppEvent) -> None: + content_blocks = event.data.get("content", []) chat_history = self.app.query_one(ChatHistory) for block in content_blocks: @@ -97,35 +115,27 @@ async def _render_assistant_message(self, message: AgentMessage) -> None: await chat_history.mount(tool_msg) - async def _render_system_message(self, message: AgentMessage) -> None: - system_content = ( - message.data if isinstance(message.data, str) else str(message.data) - ) + async def _render_system_message(self, event: AppEvent) -> None: + system_content = event.data if isinstance(event.data, str) else str(event.data) - await self.app.actions.add_message_to_chat_history( - MessageType.SYSTEM, system_content - ) + await self.add_message(RoleType.SYSTEM, system_content) - async def _render_user_message(self, message: AgentMessage) -> None: - user_content = ( - message.data if isinstance(message.data, str) else str(message.data) - ) + async def _render_user_message(self, event: AppEvent) -> None: + user_content = event.data if isinstance(event.data, str) else str(event.data) - await self.app.actions.add_message_to_chat_history( - MessageType.USER, user_content - ) + await self.add_message(RoleType.USER, user_content) - async def _render_tool_permission_request(self, message: AgentMessage) -> None: + async def _render_tool_permission_request(self, event: AppEvent) -> None: log_json( { "event": "showing_permission_prompt", - "tool_name": message.data.get("tool_name", ""), + "tool_name": event.data.get("tool_name", ""), } ) self.app.ui_state.show_permission_prompt( - tool_name=message.data.get("tool_name", ""), - tool_input=message.data.get("tool_input", {}), + tool_name=event.data.get("tool_name", ""), + tool_input=event.data.get("tool_input", {}), ) async def _on_complete(self) -> None: diff --git a/src/agent_chat_cli/docs/architecture.md b/src/agent_chat_cli/docs/architecture.md index 52702fb..1072a2c 100644 --- a/src/agent_chat_cli/docs/architecture.md +++ b/src/agent_chat_cli/docs/architecture.md @@ -24,7 +24,7 @@ Manages the conversation loop with Claude SDK: - Maintains async queue for user queries - Handles streaming responses - Parses SDK messages into structured AgentMessage objects -- Emits AgentMessageType events (STREAM_EVENT, ASSISTANT, RESULT) +- Emits AppEventType events (STREAM_EVENT, ASSISTANT, RESULT) - Manages session persistence via session_id - Implements `_can_use_tool` callback for interactive tool permission requests - Uses `permission_lock` (asyncio.Lock) to serialize parallel permission requests @@ -82,9 +82,9 @@ Claude SDK (all enabled servers pre-connected at startup) ↓ AgentLoop._handle_message ↓ -AgentMessage (typed message) → Actions.render_message +AppEvent (typed message) → Actions.handle_app_event ↓ -Match on AgentMessageType: +Match on AppEventType: - STREAM_EVENT → Update streaming message widget - ASSISTANT → Mount tool use widgets - SYSTEM → Display system notification @@ -109,7 +109,7 @@ AgentLoop internals: ### Enums (`utils/enums.py`) -**AgentMessageType**: Agent communication events +**AppEventType**: Agent communication events - ASSISTANT: Assistant message with content blocks - STREAM_EVENT: Streaming text chunk - RESULT: Response complete @@ -126,16 +126,16 @@ AgentLoop internals: - EXIT: User command to quit application - CLEAR: User command to start new conversation -**MessageType** (`components/messages.py`): UI message types +**RoleType** (`components/messages.py`): UI message role types - SYSTEM, USER, AGENT, TOOL ### Data Classes -**AgentMessage** (`utils/agent_loop.py`): Structured message from agent loop +**AppEvent** (`utils/agent_loop.py`): Structured message from agent loop ```python @dataclass -class AgentMessage: - type: AgentMessageType +class AppEvent: + type: AppEventType data: Any ``` @@ -143,7 +143,7 @@ class AgentMessage: ```python @dataclass class Message: - type: MessageType + type: RoleType content: str metadata: dict[str, Any] | None = None ``` @@ -180,7 +180,7 @@ Tool Execution Request (from Claude SDK) ↓ AgentLoop._can_use_tool (callback with permission_lock acquired) ↓ -Emit SYSTEM AgentMessage with tool_permission_request data +Emit SYSTEM AppEvent with tool_permission_request data ↓ Renderer._handle_tool_permission_request shows permission prompt ↓ @@ -304,14 +304,14 @@ SDK reconnects to previous session with full history ### Agent Response Flow 1. AgentLoop receives SDK message -2. Parse into AgentMessage with AgentMessageType -3. Actions.render_message → Renderer (match/case on type) +2. Parse into AppEvent with AppEventType +3. Actions.handle_app_event → Renderer (match/case on type) 4. Update UI components based on type 5. Scroll to bottom ## Notes -- Two distinct MessageType enums exist for different purposes (UI vs Agent events) +- Two distinct type enums exist: RoleType for UI message roles (USER, AGENT, SYSTEM) and AppEventType for agent events (STREAM_EVENT, ASSISTANT, etc.) - Renderer manages stateful streaming via StreamBuffer - Config loading combines multiple prompts into final system_prompt - Tool names follow format: `mcp__servername__toolname` diff --git a/src/agent_chat_cli/utils/__init__.py b/src/agent_chat_cli/utils/__init__.py index 7a26ab8..9015b14 100644 --- a/src/agent_chat_cli/utils/__init__.py +++ b/src/agent_chat_cli/utils/__init__.py @@ -1,11 +1,11 @@ """Utility modules for Agent Chat CLI.""" -from agent_chat_cli.utils.enums import AgentMessageType, ContentType +from agent_chat_cli.utils.enums import AppEventType, ContentType from agent_chat_cli.utils.tool_info import ToolInfo, get_tool_info from agent_chat_cli.utils.format_tool_input import format_tool_input __all__ = [ - "AgentMessageType", + "AppEventType", "ContentType", "ToolInfo", "get_tool_info", diff --git a/src/agent_chat_cli/utils/enums.py b/src/agent_chat_cli/utils/enums.py index b638e20..f8cd799 100644 --- a/src/agent_chat_cli/utils/enums.py +++ b/src/agent_chat_cli/utils/enums.py @@ -1,7 +1,7 @@ from enum import Enum -class AgentMessageType(Enum): +class AppEventType(Enum): ASSISTANT = "assistant" INIT = "init" RESULT = "result" diff --git a/tests/components/test_messages.py b/tests/components/test_messages.py index 79d78de..3f8625d 100644 --- a/tests/components/test_messages.py +++ b/tests/components/test_messages.py @@ -1,39 +1,39 @@ -from agent_chat_cli.components.messages import Message, MessageType +from agent_chat_cli.components.messages import Message, RoleType class TestMessage: def test_system_creates_system_message(self): msg = Message.system("System alert") - assert msg.type == MessageType.SYSTEM + assert msg.type == RoleType.SYSTEM assert msg.content == "System alert" assert msg.metadata is None def test_user_creates_user_message(self): msg = Message.user("Hello there") - assert msg.type == MessageType.USER + assert msg.type == RoleType.USER assert msg.content == "Hello there" assert msg.metadata is None def test_agent_creates_agent_message(self): msg = Message.agent("I can help with that.") - assert msg.type == MessageType.AGENT + assert msg.type == RoleType.AGENT assert msg.content == "I can help with that." assert msg.metadata is None def test_tool_creates_tool_message_with_metadata(self): msg = Message.tool("read_file", '{"path": "/tmp/test.txt"}') - assert msg.type == MessageType.TOOL + assert msg.type == RoleType.TOOL assert msg.content == '{"path": "/tmp/test.txt"}' assert msg.metadata == {"tool_name": "read_file"} -class TestMessageType: +class TestRoleType: def test_all_types_have_values(self): - assert MessageType.SYSTEM.value == "system" - assert MessageType.USER.value == "user" - assert MessageType.AGENT.value == "agent" - assert MessageType.TOOL.value == "tool" + assert RoleType.SYSTEM.value == "system" + assert RoleType.USER.value == "user" + assert RoleType.AGENT.value == "agent" + assert RoleType.TOOL.value == "tool" diff --git a/tests/core/test_actions.py b/tests/core/test_actions.py index f34ad90..6762cfb 100644 --- a/tests/core/test_actions.py +++ b/tests/core/test_actions.py @@ -4,7 +4,7 @@ from agent_chat_cli.app import AgentChatCLIApp from agent_chat_cli.components.chat_history import ChatHistory from agent_chat_cli.components.messages import ( - MessageType, + RoleType, SystemMessage, UserMessage, AgentMessage, @@ -42,7 +42,7 @@ async def test_adds_user_message(self, mock_agent_loop, mock_config): async with app.run_test(): chat_history = app.query_one(ChatHistory) - await app.actions.add_message_to_chat_history(MessageType.USER, "Hello") + await app.renderer.add_message(RoleType.USER, "Hello") widgets = chat_history.query(UserMessage) assert len(widgets) == 1 @@ -53,9 +53,7 @@ async def test_adds_system_message(self, mock_agent_loop, mock_config): async with app.run_test(): chat_history = app.query_one(ChatHistory) - await app.actions.add_message_to_chat_history( - MessageType.SYSTEM, "System alert" - ) + await app.renderer.add_message(RoleType.SYSTEM, "System alert") widgets = chat_history.query(SystemMessage) assert len(widgets) == 1 @@ -66,9 +64,7 @@ async def test_adds_agent_message(self, mock_agent_loop, mock_config): async with app.run_test(): chat_history = app.query_one(ChatHistory) - await app.actions.add_message_to_chat_history( - MessageType.AGENT, "I can help" - ) + await app.renderer.add_message(RoleType.AGENT, "I can help") widgets = chat_history.query(AgentMessage) assert len(widgets) == 1 @@ -78,9 +74,7 @@ async def test_raises_for_unsupported_type(self, mock_agent_loop, mock_config): app = AgentChatCLIApp() async with app.run_test(): with pytest.raises(ValueError, match="Unsupported message type"): - await app.actions.add_message_to_chat_history( - MessageType.TOOL, "tool content" - ) + await app.renderer.add_message(RoleType.TOOL, "tool content") class TestActionsPostSystemMessage: diff --git a/tests/core/test_agent_loop.py b/tests/core/test_agent_loop.py index e8d3c4a..0ece27b 100644 --- a/tests/core/test_agent_loop.py +++ b/tests/core/test_agent_loop.py @@ -12,7 +12,7 @@ ) from agent_chat_cli.core.agent_loop import AgentLoop -from agent_chat_cli.utils.enums import AgentMessageType, ContentType, ControlCommand +from agent_chat_cli.utils.enums import AppEventType, ContentType, ControlCommand from agent_chat_cli.utils.mcp_server_status import MCPServerStatus @@ -21,7 +21,7 @@ def mock_app(): app = MagicMock() app.ui_state = MagicMock() app.actions = MagicMock() - app.actions.render_message = AsyncMock() + app.actions.handle_app_event = AsyncMock() app.actions.post_system_message = AsyncMock() return app @@ -111,7 +111,7 @@ async def test_stores_session_id_from_init_message(self, mock_app, mock_config): agent_loop.session_id = None message = MagicMock(spec=SystemMessage) - message.subtype = AgentMessageType.INIT.value + message.subtype = AppEventType.INIT.value message.data = { "session_id": "new-session-456", "mcp_servers": [], @@ -127,7 +127,7 @@ async def test_updates_mcp_server_status_from_init_message( agent_loop = AgentLoop(app=mock_app) message = MagicMock(spec=SystemMessage) - message.subtype = AgentMessageType.INIT.value + message.subtype = AppEventType.INIT.value message.data = { "session_id": "session-123", "mcp_servers": [{"name": "filesystem", "status": "connected"}], @@ -156,9 +156,9 @@ async def test_handles_text_delta_stream_event(self, mock_app, mock_config): await agent_loop._handle_message(message) - mock_app.actions.render_message.assert_called_once() - call_arg = mock_app.actions.render_message.call_args[0][0] - assert call_arg.type == AgentMessageType.STREAM_EVENT + mock_app.actions.handle_app_event.assert_called_once() + call_arg = mock_app.actions.handle_app_event.call_args[0][0] + assert call_arg.type == AppEventType.STREAM_EVENT assert call_arg.data == {"text": "Hello world"} async def test_ignores_empty_text_delta(self, mock_app, mock_config): @@ -178,7 +178,7 @@ async def test_ignores_empty_text_delta(self, mock_app, mock_config): await agent_loop._handle_message(message) - mock_app.actions.render_message.assert_not_called() + mock_app.actions.handle_app_event.assert_not_called() class TestHandleMessageAssistantMessage: @@ -193,9 +193,9 @@ async def test_handles_text_block(self, mock_app, mock_config): await agent_loop._handle_message(message) - mock_app.actions.render_message.assert_called_once() - call_arg = mock_app.actions.render_message.call_args[0][0] - assert call_arg.type == AgentMessageType.ASSISTANT + mock_app.actions.handle_app_event.assert_called_once() + call_arg = mock_app.actions.handle_app_event.call_args[0][0] + assert call_arg.type == AppEventType.ASSISTANT assert call_arg.data["content"][0]["type"] == ContentType.TEXT.value assert call_arg.data["content"][0]["text"] == "Assistant response" @@ -212,9 +212,9 @@ async def test_handles_tool_use_block(self, mock_app, mock_config): await agent_loop._handle_message(message) - mock_app.actions.render_message.assert_called_once() - call_arg = mock_app.actions.render_message.call_args[0][0] - assert call_arg.type == AgentMessageType.ASSISTANT + mock_app.actions.handle_app_event.assert_called_once() + call_arg = mock_app.actions.handle_app_event.call_args[0][0] + assert call_arg.type == AppEventType.ASSISTANT assert call_arg.data["content"][0]["type"] == ContentType.TOOL_USE.value assert call_arg.data["content"][0]["name"] == "read_file" @@ -285,7 +285,7 @@ async def test_posts_permission_request_message(self, mock_app, mock_config): _context=MagicMock(), ) - mock_app.actions.render_message.assert_called_once() - call_arg = mock_app.actions.render_message.call_args[0][0] - assert call_arg.type == AgentMessageType.TOOL_PERMISSION_REQUEST + mock_app.actions.handle_app_event.assert_called_once() + call_arg = mock_app.actions.handle_app_event.call_args[0][0] + assert call_arg.type == AppEventType.TOOL_PERMISSION_REQUEST assert call_arg.data["tool_name"] == "write_file" diff --git a/tests/core/test_renderer.py b/tests/core/test_renderer.py index 71556bd..b5d461b 100644 --- a/tests/core/test_renderer.py +++ b/tests/core/test_renderer.py @@ -2,8 +2,8 @@ from unittest.mock import AsyncMock, MagicMock, patch from agent_chat_cli.app import AgentChatCLIApp -from agent_chat_cli.core.agent_loop import AgentMessage -from agent_chat_cli.utils.enums import AgentMessageType, ContentType +from agent_chat_cli.core.agent_loop import AppEvent +from agent_chat_cli.utils.enums import AppEventType, ContentType @pytest.fixture @@ -28,25 +28,23 @@ class TestRendererRenderMessage: async def test_handles_stream_event(self, mock_agent_loop, mock_config): app = AgentChatCLIApp() async with app.run_test(): - message = AgentMessage( - type=AgentMessageType.STREAM_EVENT, + message = AppEvent( + type=AppEventType.STREAM_EVENT, data={"text": "Hello"}, ) - await app.renderer.render_message(message) + await app.renderer.handle_app_event(message) assert app.renderer._stream.text == "Hello" async def test_accumulates_stream_chunks(self, mock_agent_loop, mock_config): app = AgentChatCLIApp() async with app.run_test(): - await app.renderer.render_message( - AgentMessage( - type=AgentMessageType.STREAM_EVENT, data={"text": "Hello "} - ) + await app.renderer.handle_app_event( + AppEvent(type=AppEventType.STREAM_EVENT, data={"text": "Hello "}) ) - await app.renderer.render_message( - AgentMessage(type=AgentMessageType.STREAM_EVENT, data={"text": "world"}) + await app.renderer.handle_app_event( + AppEvent(type=AppEventType.STREAM_EVENT, data={"text": "world"}) ) assert app.renderer._stream.text == "Hello world" @@ -54,12 +52,12 @@ async def test_accumulates_stream_chunks(self, mock_agent_loop, mock_config): async def test_handles_tool_permission_request(self, mock_agent_loop, mock_config): app = AgentChatCLIApp() async with app.run_test(): - message = AgentMessage( - type=AgentMessageType.TOOL_PERMISSION_REQUEST, + message = AppEvent( + type=AppEventType.TOOL_PERMISSION_REQUEST, data={"tool_name": "read_file", "tool_input": {"path": "/tmp"}}, ) - await app.renderer.render_message(message) + await app.renderer.handle_app_event(message) from agent_chat_cli.components.tool_permission_prompt import ( ToolPermissionPrompt, @@ -72,12 +70,12 @@ async def test_handles_result_resets_state(self, mock_agent_loop, mock_config): app = AgentChatCLIApp() async with app.run_test(): app.ui_state.start_thinking() - await app.renderer.render_message( - AgentMessage(type=AgentMessageType.STREAM_EVENT, data={"text": "test"}) + await app.renderer.handle_app_event( + AppEvent(type=AppEventType.STREAM_EVENT, data={"text": "test"}) ) - await app.renderer.render_message( - AgentMessage(type=AgentMessageType.RESULT, data=None) + await app.renderer.handle_app_event( + AppEvent(type=AppEventType.RESULT, data=None) ) assert app.renderer._stream.widget is None @@ -86,8 +84,8 @@ async def test_handles_result_resets_state(self, mock_agent_loop, mock_config): async def test_handles_assistant_with_tool_use(self, mock_agent_loop, mock_config): app = AgentChatCLIApp() async with app.run_test(): - message = AgentMessage( - type=AgentMessageType.ASSISTANT, + message = AppEvent( + type=AppEventType.ASSISTANT, data={ "content": [ { @@ -99,15 +97,15 @@ async def test_handles_assistant_with_tool_use(self, mock_agent_loop, mock_confi }, ) - await app.renderer.render_message(message) + await app.renderer.handle_app_event(message) assert app.renderer._stream.widget is None async def test_ignores_empty_stream_chunks(self, mock_agent_loop, mock_config): app = AgentChatCLIApp() async with app.run_test(): - await app.renderer.render_message( - AgentMessage(type=AgentMessageType.STREAM_EVENT, data={"text": ""}) + await app.renderer.handle_app_event( + AppEvent(type=AppEventType.STREAM_EVENT, data={"text": ""}) ) assert app.renderer._stream.text == "" diff --git a/tests/utils/test_enums.py b/tests/utils/test_enums.py index 85791fc..6a92fa8 100644 --- a/tests/utils/test_enums.py +++ b/tests/utils/test_enums.py @@ -1,22 +1,20 @@ from agent_chat_cli.utils.enums import ( - AgentMessageType, + AppEventType, ContentType, ControlCommand, Key, ) -class TestAgentMessageType: +class TestAppEventType: def test_all_message_types_have_values(self): - assert AgentMessageType.ASSISTANT.value == "assistant" - assert AgentMessageType.INIT.value == "init" - assert AgentMessageType.RESULT.value == "result" - assert AgentMessageType.STREAM_EVENT.value == "stream_event" - assert AgentMessageType.SYSTEM.value == "system" - assert ( - AgentMessageType.TOOL_PERMISSION_REQUEST.value == "tool_permission_request" - ) - assert AgentMessageType.USER.value == "user" + assert AppEventType.ASSISTANT.value == "assistant" + assert AppEventType.INIT.value == "init" + assert AppEventType.RESULT.value == "result" + assert AppEventType.STREAM_EVENT.value == "stream_event" + assert AppEventType.SYSTEM.value == "system" + assert AppEventType.TOOL_PERMISSION_REQUEST.value == "tool_permission_request" + assert AppEventType.USER.value == "user" class TestContentType: