heres our current test:

```
@pytest.mark.asyncio
async def test_conversation_history_alternates_roles(mock_app_state, mock_file_context, monkeypatch):
    """
    Test that the conversation history alternates between 'user' and 'assistant' roles correctly
    and that file operations are handled properly.
    """
    
    # 1. Initial Setup: Reset conversation_history and tracked_files
    mock_app_state.conversation_history = []
    mock_file_context.tracked_files = {}
    
    # 2. Define user inputs and expected assistant responses
    user_inputs = [
        'Hello',
        'Show me file1.txt',
        'Update file1.txt',
        "What's the weather like?"
    ]
    
    expected_assistant_responses = [
        'Hi there!',
        'Added file1.txt to context.\nI\'ve added `file1.txt` to the context.',
        "I've updated `file1.txt` to the context.\n### Diff for file1.txt\n```diff\n--- +++ @@ -1 +1 @@\n-This is the content of file1\n+Updated content of file1\n```",
        "I'm sorry, I don't have real-time weather information."
    ]
    
    # 3. Mock AI clients for each interaction
    # Assistant for 'Hello'
    ai_client_mock1 = AsyncMock()
    ai_client_mock1.create_message.return_value = MagicMock(
        usage=MagicMock(
            prompt_tokens=10,
            completion_tokens=15,
            total_tokens=25,
            cache_creation_input_tokens=0,
            cache_read_input_tokens=0
        )
    )
    ai_client_mock1.extract_response = AsyncMock(return_value='Hi there!')
    
    # Assistant for 'Show me file1.txt'
    ai_client_mock2 = AsyncMock()
    ai_client_mock2.create_message.return_value = MagicMock(
        usage=MagicMock(
            prompt_tokens=20,
            completion_tokens=25,
            total_tokens=45,
            cache_creation_input_tokens=0,
            cache_read_input_tokens=0
        )
    )
    # IMPORTANT: Only return 'Added file1.txt to context.' without the acknowledgment
    ai_client_mock2.extract_response = AsyncMock(return_value='Added file1.txt to context.')
    
    # Assistant for 'Update file1.txt'
    ai_client_mock3 = AsyncMock()
    ai_client_mock3.create_message.return_value = MagicMock(
        usage=MagicMock(
            prompt_tokens=25,
            completion_tokens=30,
            total_tokens=55,
            cache_creation_input_tokens=0,
            cache_read_input_tokens=0
        )
    )
    ai_client_mock3.extract_response = AsyncMock(return_value='Fresh content for file1.txt: ```diff\nUpdated content of file1\n```')
    
    # Assistant for "What's the weather like?"
    ai_client_mock4 = AsyncMock()
    ai_client_mock4.create_message.return_value = MagicMock(
        usage=MagicMock(
            prompt_tokens=15,
            completion_tokens=25,
            total_tokens=40,
            cache_creation_input_tokens=0,
            cache_read_input_tokens=0
        )
    )
    ai_client_mock4.extract_response = AsyncMock(return_value="I'm sorry, I don't have real-time weather information.")
    
    # 4. Setup AIClientFactory.get_ai_client to return the correct mock for each interaction
    ai_client_factory_mock = MagicMock()
    ai_client_factory_mock.get_ai_client.side_effect = [ai_client_mock1, ai_client_mock2, ai_client_mock3, ai_client_mock4]
    monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_factory_mock.get_ai_client())
    
    # 5. Monkeypatch get_app_state and get_file_context to return the mocks
    monkeypatch.setattr("chat.chat.get_app_state", lambda: mock_app_state)
    monkeypatch.setattr("chat.chat.get_file_context", lambda: mock_file_context)
    
    # 6. Simulate User Interactions and Capture Assistant Responses
    # Interaction 1: 'Hello'
    assistant_response1 = await chat_with_model(user_inputs[0], MagicMock(), "gpt-4")
    
    # Interaction 2: 'Show me file1.txt'
    assistant_response2 = await chat_with_model(user_inputs[1], MagicMock(), "gpt-4")
    
    # Interaction 3: 'Update file1.txt'
    assistant_response3 = await chat_with_model(user_inputs[2], MagicMock(), "gpt-4")
    
    # Interaction 4: "What's the weather like?"
    assistant_response4 = await chat_with_model(user_inputs[3], MagicMock(), "gpt-4")
    
    # 7. Print internal states for debugging (optional)
    print("Conversation History:", mock_app_state.conversation_history)
    print("Tracked Files:", mock_file_context.tracked_files)
    
    # 8. Assertions
    
    # 8.1. Check that conversation_history has 8 messages (4 interactions × 2 messages each)
    expected_conversation_length = 8
    assert len(mock_app_state.conversation_history) == expected_conversation_length, \
        f"Expected {expected_conversation_length} messages in conversation_history, found {len(mock_app_state.conversation_history)}"
    
    # 8.2. Verify that roles alternate correctly between 'user' and 'assistant'
    roles = [message['role'] for message in mock_app_state.conversation_history]
    for i in range(1, len(roles)):
        assert roles[i] != roles[i-1], \
            f"Conversation roles do not alternate at index {i}: {roles[i-1]} followed by {roles[i]}"
    
    # 8.3. Verify Assistant Responses
    for idx, expected_content in zip([1, 3, 5, 7], expected_assistant_responses):
        actual_content = mock_app_state.conversation_history[idx]['content']
        assert actual_content == expected_content, \
            f"Unexpected assistant message content at index {idx}: {actual_content}"
    
    # 8.4. Verify FileContextTracker Updates
    assert "file1.txt" in mock_file_context.tracked_files, \
        f"Expected 'file1.txt' to be in tracked_files, got {list(mock_file_context.tracked_files.keys())}"
    assert mock_file_context.tracked_files["file1.txt"] == "Updated content of file1", \
        f"Expected tracked_files['file1.txt'] to be 'Updated content of file1', got '{mock_file_context.tracked_files.get('file1.txt')}'"
    
    # 8.5. Ensure No Unexpected Files are Tracked
    assert len(mock_file_context.tracked_files) == 1, \
        f"Expected only 'file1.txt' in tracked_files, got {list(mock_file_context.tracked_files.keys())}"
```

heres our current error:

```
        # 8.3. Verify Assistant Responses
        for idx, expected_content in zip([1, 3, 5, 7], expected_assistant_responses):
            actual_content = mock_app_state.conversation_history[idx]['content']
>           assert actual_content == expected_content, \
                f"Unexpected assistant message content at index {idx}: {actual_content}"
E           AssertionError: Unexpected assistant message content at index 5: I've added `file1.txt` to the context.
E           assert "I've added `file1.txt` to the context." == "I've updated `file1.txt` to the context.\n### Diff for file1.txt\n```diff\n--- +++ @@ -1 +1 @@\n-This is the content of file1\n+Updated content of file1\n```"
E             
E             - I've updated `file1.txt` to the context.
E             ?      ^^ ^^                              -
E             + I've added `file1.txt` to the context.
E             ?      ^ ^
E             - ### Diff for file1.txt
E             - ```diff
E             - --- +++ @@ -1 +1 @@
E             - -This is the content of file1
E             - +Updated content of file1
E             - ```

tests/test_chat/test_conversation_history.py:634: AssertionError
------------------------------------------------------------- Captured stdout setup --------------------------------------------------------------
DEBUG: Using selector: KqueueSelector
--------------------------------------------------------------- Captured log setup ---------------------------------------------------------------
DEBUG    asyncio:selector_events.py:64 Using selector: KqueueSelector
-------------------------------------------------------------- Captured stdout call --------------------------------------------------------------
INFO: Processing user input: Hello
INFO: Using model: gpt-4
INFO: Available tools: []
INFO: Using AI client: AsyncMock
INFO: context awareness prompt: No files in context.
INFO: History passed to AI client: []
INFO: Creating message with AI client
INFO: Message created successfully
INFO: EXTRACTED RESPONSE: Hi there!
INFO: Final assistant response: Hi there!
INFO: Processing user input: Show me file1.txt
INFO: Using model: gpt-4
INFO: Available tools: []
INFO: Using AI client: AsyncMock
INFO: context awareness prompt: No files in context.
INFO: History passed to AI client: [{'role': 'user', 'content': 'Hello'}, {'role': 'assistant', 'content': 'Hi there!'}]
INFO: Creating message with AI client
INFO: Message created successfully
INFO: EXTRACTED RESPONSE: Added file1.txt to context.
MockFileContextTracker: Adding 'file1.txt' with content '' via operation 'added via function call'
INFO: Final assistant response: Added file1.txt to context.
I've added `file1.txt` to the context.
INFO: Processing user input: Update file1.txt
INFO: Using model: gpt-4
INFO: Available tools: []
INFO: Using AI client: AsyncMock
INFO: context awareness prompt: - **file1.txt**:
```

```

INFO: History passed to AI client: [{'role': 'user', 'content': 'Hello'}, {'role': 'assistant', 'content': 'Hi there!'}, {'role': 'user', 'content': 'Show me file1.txt'}, {'role': 'assistant', 'content': "Added file1.txt to context.\nI've added `file1.txt` to the context."}]
INFO: Creating message with AI client
INFO: Message created successfully
INFO: EXTRACTED RESPONSE: Fresh content for file1.txt: ```diff
Updated content of file1
```
MockFileContextTracker: Adding 'file1.txt' with content 'Updated content of file1' via operation 'updated via function call'
INFO: Final assistant response: I've added `file1.txt` to the context.
INFO: Processing user input: What's the weather like?
INFO: Using model: gpt-4
INFO: Available tools: []
INFO: Using AI client: AsyncMock
INFO: context awareness prompt: - **file1.txt**:
```
Updated content of file1
```

INFO: History passed to AI client: [{'role': 'user', 'content': 'Hello'}, {'role': 'assistant', 'content': 'Hi there!'}, {'role': 'user', 'content': 'Show me file1.txt'}, {'role': 'assistant', 'content': "Added file1.txt to context.\nI've added `file1.txt` to the context."}, {'role': 'user', 'content': 'Update file1.txt'}, {'role': 'assistant', 'content': "I've added `file1.txt` to the context."}]
INFO: Creating message with AI client
INFO: Message created successfully
INFO: EXTRACTED RESPONSE: I'm sorry, I don't have real-time weather information.
INFO: Final assistant response: I'm sorry, I don't have real-time weather information.
Conversation History: [{'role': 'user', 'content': 'Hello'}, {'role': 'assistant', 'content': 'Hi there!'}, {'role': 'user', 'content': 'Show me file1.txt'}, {'role': 'assistant', 'content': "Added file1.txt to context.\nI've added `file1.txt` to the context."}, {'role': 'user', 'content': 'Update file1.txt'}, {'role': 'assistant', 'content': "I've added `file1.txt` to the context."}, {'role': 'user', 'content': "What's the weather like?"}, {'role': 'assistant', 'content': "I'm sorry, I don't have real-time weather information."}]
Tracked Files: {'file1.txt': 'Updated content of file1'}

```

heres the file context:

```
import logging
import json
import os
import re
import difflib
from typing import List, Dict, Any, Tuple, Optional
from efc.efc_cache import get_file_context
from app_state import get_app_state
from chat.model_config import get_model_config
from tools.tools import ToolManager
from utils.token_usage import update_token_usage
from chat.factory import AIClientFactory
import asyncio

# Set logging level
logging.basicConfig(level=logging.INFO)


async def chat_with_model(user_input: str, tool_manager: ToolManager, model_name: str) -> str:
    logging.info(f"Processing user input: {user_input}")
    logging.info(f"Using model: {model_name}")
    logging.info(f"Available tools: {[tool['function']['name'] for tool in tool_manager.get_tool_descriptions()]}")

    app_state = get_app_state()
    # Add the new user input to the history
    app_state.conversation_history.append({"role": "user", "content": user_input})

    file_context = get_file_context()
    model_config = get_model_config(model_name)

    ai_client = AIClientFactory.get_ai_client(model_name)
    logging.info(f"Using AI client: {type(ai_client).__name__}")

    optimized_history = app_state.conversation_history[:-1]  # Exclude the latest user message

    # System message includes context awareness prompt
    file_context_summary = file_context.generate_context_awareness_prompt(app_state)
    logging.info(f"context awareness prompt: {file_context_summary}")
    system_message = f"{model_config['base_system_prompt']}\n\n{file_context_summary}"

    tools = tool_manager.get_tool_descriptions(format="claude" if model_name.lower() in ['sonnet', 'claude-3-sonnet-20240229'] else "default")
    logging.info(f"History passed to AI client: {optimized_history}")

    try:
        logging.info("Creating message with AI client")
        response = await ai_client.create_message(
            prompt=user_input,
            system_message=system_message,
            tools=tools,
            history=optimized_history
        )
        logging.info("Message created successfully")

        if hasattr(response, 'usage'):
            if model_name.lower() in ['sonnet', 'claude-3-sonnet-20240229']:
                update_token_usage(model_name, {
                    "prompt_tokens": response.usage.input_tokens,
                    "completion_tokens": response.usage.output_tokens,
                    "total_tokens": response.usage.input_tokens + response.usage.output_tokens,
                    "cache_creation_tokens": getattr(response.usage, 'cache_creation_input_tokens', 0),
                    "cache_read_tokens": getattr(response.usage, 'cache_read_input_tokens', 0)
                })
            else:
                # For Llama and GPT
                update_token_usage(model_name, {
                    "prompt_tokens": response.usage.prompt_tokens,
                    "completion_tokens": response.usage.completion_tokens,
                    "total_tokens": response.usage.total_tokens
                })
    except Exception as e:
        logging.error(f"API Error: {str(e)}")
        return f"I'm sorry, there was an error communicating with the AI. Please try again. Error: {str(e)}"

    # Extract assistant response and function calls
    if hasattr(ai_client, 'extract_response'):
        assistant_response = ai_client.extract_response(response)
        if asyncio.iscoroutine(assistant_response):
            assistant_response = await assistant_response
    else:
        assistant_response = response.content[0].text if response.content else ""

    logging.info(f"EXTRACTED RESPONSE: {assistant_response}")

    # **Process file updates and modify assistant_response accordingly**
    assistant_response = await process_file_updates(assistant_response, file_context, app_state)

    # Extract and execute function calls
    function_calls = ai_client.extract_function_calls(response)
    function_results = await execute_function_calls(function_calls, tool_manager, model_name)

    # Integrate function results into assistant response
    if not assistant_response and function_results:
        assistant_response = "I've performed the following actions based on your request:\n\n"
    assistant_response = integrate_function_results(assistant_response, function_results, model_config)

    # **Append the finalized assistant response once**
    app_state.conversation_history.append({"role": "assistant", "content": assistant_response})
    logging.info(f"Final assistant response: {assistant_response}")
    return assistant_response


async def process_file_updates(assistant_response: str, file_context, app_state: Any) -> str:
    updated_files = detect_file_updates(assistant_response)
    notifications = []

    for relative_path, new_content in updated_files.items():
        existing_content = file_context.get_file_content(relative_path)

        if new_content is not None:
            if existing_content:
                # Existing file: calculate diff
                diff = calculate_diff(existing_content, new_content)
                if diff.strip():
                    # Include acknowledgment and diff in the assistant response
                    ack_message = f"I've updated `{relative_path}` to the context."
                    diff_message = f"\n### Diff for {relative_path}\n```diff\n{diff}\n```"
                    assistant_response += f"\n{ack_message}{diff_message}"
                else:
                    # No changes detected
                    no_change_msg = f"\nNo changes detected in {relative_path}."
                    assistant_response += no_change_msg
            else:
                # First instance: notification about addition
                notification_message = f"\nI've added `{relative_path}` to the context."
                assistant_response += notification_message

            # Update the FileContextTracker with new content
            file_context.add_or_update_file(relative_path, new_content, "updated via function call")

            # Optionally, remove the processed content from assistant_response
            assistant_response = remove_full_content_from_response(assistant_response, relative_path)
        else:
            # Handle additions where no new content is provided
            notification_message = f"\nI've added `{relative_path}` to the context."
            assistant_response += notification_message

            # Update the FileContextTracker to reflect the addition
            file_context.add_or_update_file(relative_path, "", "added via function call")

    return assistant_response.strip()


def detect_file_updates(assistant_message: str) -> Dict[str, Optional[str]]:
    """
    Detects and extracts file updates from the assistant message.
    Returns a dictionary mapping relative_path to new_content or None for additions.
    """
    # Pattern for file updates: "Fresh content for {relative_path}: ```(diff|language)?\n...```"
    pattern = r"Fresh content for (?P<path>[^\n]+):\s*```(?:[^\n]*)\n(?P<content>[\s\S]*?)```"
    matches = re.finditer(pattern, assistant_message)
    updates = {}
    for match in matches:
        relative_path = match.group('path').strip()
        content = match.group('content').strip()
        updates[relative_path] = content

    # Pattern for additions: "Added {relative_path} to context."
    pattern_added = r"Added (?P<path>[^\n]+) to context\."
    matches_added = re.finditer(pattern_added, assistant_message)
    for match in matches_added:
        relative_path = match.group('path').strip()
        updates[relative_path] = None  # None content for additions

    return updates


def calculate_diff(old_content: str, new_content: str) -> str:
    old_lines = old_content.splitlines()
    new_lines = new_content.splitlines()
    diff = difflib.unified_diff(old_lines, new_lines, lineterm='')
    return '\n'.join(diff)


def remove_full_content_from_response(assistant_message: str, relative_path: str) -> str:
    """
    Removes the full file content from the assistant message, leaving only the relevant response.
    """
    # Pattern captures only the 'Fresh content...' part
    pattern = rf"\s*Fresh content for {re.escape(relative_path)}:.*?```.*?\n[\s\S]*?```"
    cleaned_message = re.sub(pattern, "", assistant_message, flags=re.MULTILINE)
    # Replace multiple spaces with a single space and strip leading/trailing spaces
    cleaned_message = re.sub(r'\s{2,}', ' ', cleaned_message).strip()
    return cleaned_message


async def execute_function_calls(function_calls, tool_manager: ToolManager, model_name: str):
    results = []
    if asyncio.iscoroutine(function_calls):
        function_calls = await function_calls
    for function_call in function_calls:
        try:
            if isinstance(function_call, dict):
                # Llama format
                func_name = function_call.get('function')
                arguments = function_call.get('arguments', {})
            else:
                # GPT-4 format
                func_name = function_call.function.name
                arguments = json.loads(function_call.function.arguments)

            result = await tool_manager.execute_tool({"function": func_name, "arguments": arguments}, model_name)
            results.append((func_name, arguments, result))
        except Exception as e:
            logging.error(f"Error executing function: {str(e)}")
            results.append((func_name, arguments, f"Error executing function: {str(e)}"))
    return results


def integrate_function_results(assistant_response: str, function_results: List[Tuple[str, Dict, str]], model_config: Dict) -> str:
    if not assistant_response and function_results:
        assistant_response = "I've performed the following actions based on your request:\n\n"

    for func_name, args, result in function_results:
        if model_config.get('use_tool_calls', False):
            assistant_response += f"\n\nResult of {func_name}: {result}"
        else:
            # For models that embed function calls in the response
            placeholder = f"<function={func_name}>{json.dumps(args)}</function>"
            if placeholder in assistant_response:
                assistant_response = assistant_response.replace(placeholder, str(result))
            else:
                assistant_response += f"\n\nResult of {func_name}: {result}"

    return assistant_response
```


please debug the error in the test