diff --git a/openhands/server/data_models/conversation_info.py b/openhands/server/data_models/conversation_info.py index 1fe5262672..26be799c27 100644 --- a/openhands/server/data_models/conversation_info.py +++ b/openhands/server/data_models/conversation_info.py @@ -18,4 +18,5 @@ class ConversationInfo: status: ConversationStatus = ConversationStatus.STOPPED selected_repository: str | None = None trigger: ConversationTrigger | None = None + num_connections: int = 0 created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) diff --git a/openhands/server/routes/files.py b/openhands/server/routes/files.py index 97800e7551..bb75631d0f 100644 --- a/openhands/server/routes/files.py +++ b/openhands/server/routes/files.py @@ -308,38 +308,9 @@ async def get_cwd( workspace_mount_path_in_sandbox: str, ) -> str: metadata = await conversation_store.get_metadata(conversation_id) - is_running = await conversation_manager.is_agent_loop_running(conversation_id) - conversation_info = await _get_conversation_info(metadata, is_running) - cwd = workspace_mount_path_in_sandbox - if conversation_info and conversation_info.selected_repository: - repo_dir = conversation_info.selected_repository.split('/')[-1] + if metadata and metadata.selected_repository: + repo_dir = metadata.selected_repository.split('/')[-1] cwd = os.path.join(cwd, repo_dir) return cwd - - -async def _get_conversation_info( - conversation: ConversationMetadata, - is_running: bool, -) -> ConversationInfo | None: - try: - title = conversation.title - if not title: - title = f'Conversation {conversation.conversation_id[:5]}' - return ConversationInfo( - conversation_id=conversation.conversation_id, - title=title, - last_updated_at=conversation.last_updated_at, - created_at=conversation.created_at, - selected_repository=conversation.selected_repository, - status=ConversationStatus.RUNNING - if is_running - else ConversationStatus.STOPPED, - ) - except Exception as e: - logger.error( - f'Error loading conversation {conversation.conversation_id}: {str(e)}', - extra={'session_id': conversation.conversation_id}, - ) - return None diff --git a/openhands/server/routes/manage_conversations.py b/openhands/server/routes/manage_conversations.py index f08e7efc47..35620416eb 100644 --- a/openhands/server/routes/manage_conversations.py +++ b/openhands/server/routes/manage_conversations.py @@ -267,13 +267,18 @@ async def search_conversations( conversation.conversation_id for conversation in filtered_results ) running_conversations = await conversation_manager.get_running_agent_loops( - user_id, set(conversation_ids) + user_id, conversation_ids ) + connection_ids_to_conversation_ids = await conversation_manager.get_connections(filter_to_sids=conversation_ids) result = ConversationInfoResultSet( results=await wait_all( _get_conversation_info( conversation=conversation, is_running=conversation.conversation_id in running_conversations, + num_connections=sum( + 1 for conversation_id in connection_ids_to_conversation_ids.values() + if conversation_id == conversation.conversation_id + ) ) for conversation in filtered_results ), @@ -290,7 +295,8 @@ async def get_conversation( try: metadata = await conversation_store.get_metadata(conversation_id) is_running = await conversation_manager.is_agent_loop_running(conversation_id) - conversation_info = await _get_conversation_info(metadata, is_running) + num_connections = len(await conversation_manager.get_connections(filter_to_sids={conversation_id})) + conversation_info = await _get_conversation_info(metadata, is_running, num_connections) return conversation_info except FileNotFoundError: return None @@ -318,6 +324,7 @@ async def delete_conversation( async def _get_conversation_info( conversation: ConversationMetadata, is_running: bool, + num_connections: int ) -> ConversationInfo | None: try: title = conversation.title @@ -333,6 +340,7 @@ async def _get_conversation_info( status=( ConversationStatus.RUNNING if is_running else ConversationStatus.STOPPED ), + num_connections=num_connections ) except Exception as e: logger.error( diff --git a/tests/unit/test_conversation.py b/tests/unit/test_conversation.py index 3539338e8f..5276020619 100644 --- a/tests/unit/test_conversation.py +++ b/tests/unit/test_conversation.py @@ -110,7 +110,11 @@ async def test_search_conversations(): async def mock_get_running_agent_loops(*args, **kwargs): return set() + async def mock_get_connections(*args, **kwargs): + return {} + mock_manager.get_running_agent_loops = mock_get_running_agent_loops + mock_manager.get_connections = mock_get_connections with patch( 'openhands.server.routes.manage_conversations.datetime' ) as mock_datetime: @@ -162,6 +166,7 @@ async def test_search_conversations(): ), status=ConversationStatus.STOPPED, selected_repository='foobar', + num_connections=0, ) ] ) @@ -190,6 +195,7 @@ async def test_get_conversation(): 'openhands.server.routes.manage_conversations.conversation_manager' ) as mock_manager: mock_manager.is_agent_loop_running = AsyncMock(return_value=False) + mock_manager.get_connections = AsyncMock(return_value={}) conversation = await get_conversation( 'some_conversation_id', conversation_store=mock_store @@ -202,6 +208,7 @@ async def test_get_conversation(): last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00+00:00'), status=ConversationStatus.STOPPED, selected_repository='foobar', + num_connections=0, ) assert conversation == expected @@ -420,6 +427,7 @@ async def test_delete_conversation(): 'openhands.server.routes.manage_conversations.conversation_manager' ) as mock_manager: mock_manager.is_agent_loop_running = AsyncMock(return_value=False) + mock_manager.get_connections = AsyncMock(return_value={}) # Mock the runtime class with patch(