can you look at my current error, im trying to run my test file to check our updates:

```
import pytest
from chat.chat import chat_with_model, process_file_updates, detect_file_updates, calculate_diff, remove_full_content_from_response
from app_state import get_app_state
from efc.efc_cache import get_file_context
from unittest.mock import AsyncMock, MagicMock
import os


@pytest.fixture
def mock_app_state():
    class MockAppState:
        def __init__(self):
            self.active_project = "/path/to/active_project"
            self.conversation_history = []
            self.file_histories = {}
            self.token_usage = {}

        def get_active_project_tree_string(self):
            return "Mocked project tree"

    return MockAppState()


@pytest.fixture
def mock_file_context():
    class MockFileContextTracker:
        def __init__(self):
            self.tracked_files = {}

        def generate_context_awareness_prompt(self, app_state) -> str:
            if not self.tracked_files:
                return "No files in context."
            context = ""
            for relative_path in self.tracked_files:
                content = self.get_file_content(relative_path)
                context += f"- **{relative_path}**:\n```\n{content}\n```\n"
            return context

        def get_file_content(self, relative_path: str) -> str:
            return self.tracked_files.get(relative_path, "")

        def add_or_update_file(self, relative_path: str, content: str, operation: str) -> None:
            self.tracked_files[relative_path] = content

    return MockFileContextTracker()



def test_detect_file_updates():
    assistant_message = "Fresh content for file1.txt: ```diff\n+ Added line\n- Removed line\n```"
    updates = detect_file_updates(assistant_message)
    assert updates == {"file1.txt": "+ Added line\n- Removed line"}


def test_calculate_diff():
    old_content = "Line1\nLine2\nLine3"
    new_content = "Line1\nLine2 modified\nLine3"
    diff = calculate_diff(old_content, new_content)
    expected_diff = """--- 
+++ 
@@ -1,3 +1,3 @@
 Line1
-Line2
+Line2 modified
 Line3"""
    assert diff == expected_diff


def test_remove_full_content_from_response():
    assistant_message = "Some message. Fresh content for file1.txt: ```diff\n+ Added line\n- Removed line\n``` More text."
    cleaned_message = remove_full_content_from_response(assistant_message, 'file1.txt')
    assert cleaned_message == "Some message. More text."


@pytest.mark.asyncio
async def test_initial_file_addition(mock_app_state, mock_file_context):
    user_input = "Create file1.txt"
    
    # Mock AI client response for file addition
    ai_client_mock = AsyncMock()
    ai_client_mock.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value="Added /path/to/active_project/file1.txt to context.")
    )
    monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_mock)
    
    assistant_response = await chat_with_model(user_input, MagicMock(), "gpt-4")
    
    # Check that a notification message was appended
    assert len(mock_app_state.conversation_history) == 2
    assert mock_app_state.conversation_history[1]['role'] == 'assistant'
    assert mock_app_state.conversation_history[1]['content'] == 'Added /path/to/active_project/file1.txt to context.'
    
    # Check that FileContextTracker was updated
    assert mock_file_context.tracked_files["file1.txt"] == "This is the content of file1"


@pytest.mark.asyncio
async def test_file_update_with_diff(mock_app_state, mock_file_context):
    # Initial addition
    mock_app_state.conversation_history = [
        {'role': 'user', 'content': 'Create file1.txt'},
        {'role': 'assistant', 'content': 'Added /path/to/active_project/file1.txt to context.'}
    ]
    mock_file_context.tracked_files["file1.txt"] = "This is the content of file1"
    
    user_input = "Update file1.txt"
    
    # Mock AI client response for file update
    new_content = "This is the updated content of file1"
    ai_client_mock = AsyncMock()
    ai_client_mock.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value=f"Fresh content for /path/to/active_project/file1.txt: ```diff\n+ This is the updated content of file1\n```")
    )
    monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_mock)
    
    assistant_response = await chat_with_model(user_input, MagicMock(), "gpt-4")
    
    # Check that a diff message was appended
    assert len(mock_app_state.conversation_history) == 4
    assert mock_app_state.conversation_history[3]['role'] == 'assistant'
    assert "### Diff for /path/to/active_project/file1.txt" in mock_app_state.conversation_history[3]['content']
    
    # Check that FileContextTracker was updated
    assert mock_file_context.tracked_files["file1.txt"] == new_content


@pytest.mark.asyncio
async def test_multiple_file_operations(mock_app_state, mock_file_context):
    # Initial additions
    mock_app_state.conversation_history = [
        {'role': 'user', 'content': 'Create file1.txt'},
        {'role': 'assistant', 'content': 'Added /path/to/active_project/file1.txt to context.'},
        {'role': 'user', 'content': 'Create file2.txt'},
        {'role': 'assistant', 'content': 'Added /path/to/active_project/file2.txt to context.'}
    ]
    mock_file_context.tracked_files["file1.txt"] = "This is the content of file1"
    mock_file_context.tracked_files["file2.txt"] = "This is the content of file2"
    
    user_input1 = "Update file1.txt"
    user_input2 = "Update file2.txt"
    
    # Mock AI client response for file1.txt update
    new_content1 = "This is the updated content of file1"
    ai_client_mock1 = AsyncMock()
    ai_client_mock1.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value=f"Fresh content for /path/to/active_project/file1.txt: ```diff\n+ This is the updated content of file1\n```")
    )
    
    # Mock AI client response for file2.txt update
    new_content2 = "This is the updated content of file2"
    ai_client_mock2 = AsyncMock()
    ai_client_mock2.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value=f"Fresh content for /path/to/active_project/file2.txt: ```diff\n+ This is the updated content of file2\n```")
    )
    
    ai_client_factory_mock = MagicMock()
    ai_client_factory_mock.get_ai_client.side_effect = [ai_client_mock1, ai_client_mock2]
    monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_factory_mock.get_ai_client())
    
    # Update file1.txt
    assistant_response1 = await chat_with_model(user_input1, MagicMock(), "gpt-4")
    
    # Update file2.txt
    assistant_response2 = await chat_with_model(user_input2, MagicMock(), "gpt-4")
    
    # Check that diffs for both files were appended
    assert len(mock_app_state.conversation_history) == 6
    assert "### Diff for /path/to/active_project/file1.txt" in mock_app_state.conversation_history[3]['content']
    assert "### Diff for /path/to/active_project/file2.txt" in mock_app_state.conversation_history[5]['content']
    
    # Check that FileContextTracker was updated
    assert mock_file_context.tracked_files["file1.txt"] == new_content1
    assert mock_file_context.tracked_files["file2.txt"] == new_content2


@pytest.mark.asyncio
async def test_conversation_history_alternates_roles(mock_app_state, mock_file_context):
    history = [
        {'role': 'user', 'content': 'Hello'},
        {'role': 'assistant', 'content': 'Hi there!'},
        {'role': 'user', 'content': 'Show me file1.txt'},
        {'role': 'assistant', 'content': 'Added /path/to/active_project/file1.txt to context.'},
        {'role': 'user', 'content': 'Update file1.txt'},
        {'role': 'assistant', 'content': '### Diff for /path/to/active_project/file1.txt\n```diff\n+ Updated content of file1\n```'},
        {'role': 'user', 'content': "What's the weather like?"},
        {'role': 'assistant', 'content': "I'm sorry, I don't have real-time weather information."},
    ]
    
    mock_app_state.conversation_history = history
    mock_file_context.tracked_files["file1.txt"] = "Updated content of file1"
    
    user_input = "Update file1.txt again"
    
    # Mock AI client response for file1.txt update
    new_content = "This is the second update of file1"
    ai_client_mock = AsyncMock()
    ai_client_mock.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value=f"Fresh content for /path/to/active_project/file1.txt: ```diff\n+ This is the second update of file1\n```")
    )
    monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_mock)
    
    assistant_response = await chat_with_model(user_input, MagicMock(), "gpt-4")
    
    # Check role alternation
    assert len(mock_app_state.conversation_history) == 8 + 2  # Original 8 + 2 new messages
    assert mock_app_state.conversation_history[-2]['role'] == 'assistant'
    assert mock_app_state.conversation_history[-2]['content'].startswith("### Diff for /path/to/active_project/file1.txt")
    assert mock_app_state.conversation_history[-1]['role'] == 'assistant'
    assert mock_app_state.conversation_history[-1]['content'] == "I'm sorry, I don't have real-time weather information."


@pytest.mark.asyncio
async def test_non_file_related_messages_preserved(mock_app_state, mock_file_context):
    history = [
        {'role': 'user', 'content': 'Hello'},
        {'role': 'assistant', 'content': 'Hi there!'},
        {'role': 'user', 'content': "What's the weather like?"},
        {'role': 'assistant', 'content': "I'm sorry, I don't have real-time weather information."},
    ]
    
    mock_app_state.conversation_history = history
    
    user_input = "Update file1.txt"
    
    # Mock AI client response for file1.txt update (file does not exist)
    ai_client_mock = AsyncMock()
    ai_client_mock.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value=f"Fresh content for /path/to/active_project/file1.txt: ```diff\n+ New content of file1\n```")
    )
    monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_mock)
    
    assistant_response = await chat_with_model(user_input, MagicMock(), "gpt-4")
    
    # Check that non-file messages are preserved and new diff is added
    assert len(mock_app_state.conversation_history) == 4 + 2  # Original 4 + 2 new messages
    assert '### Diff for /path/to/active_project/file1.txt' in mock_app_state.conversation_history[-2]['content']
    assert 'Fresh content for /path/to/active_project/file1.txt: ```diff\n+ New content of file1\n```' not in mock_app_state.conversation_history[-2]['content']
    assert mock_app_state.conversation_history[-1]['role'] == 'assistant'
    assert mock_app_state.conversation_history[-1]['content'] == "I'm sorry, I don't have real-time weather information."


def test_remove_full_content_from_response():
    assistant_message = "Some message. Fresh content for file1.txt: ```diff\n+ Added line\n- Removed line\n``` More text."
    cleaned_message = remove_full_content_from_response(assistant_message, 'file1.txt')
    assert cleaned_message == "Some message. More text."


@pytest.mark.asyncio
async def test_file_addition_and_update_sequence(mock_app_state, mock_file_context):
    # Sequence: Create file, update file, update file again
    user_input_create = "Create file1.txt"
    user_input_update1 = "Update file1.txt"
    user_input_update2 = "Update file1.txt again"
    
    # Mock AI client responses
    ai_client_mock_create = AsyncMock()
    ai_client_mock_create.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value="Added /path/to/active_project/file1.txt to context.")
    )
    
    ai_client_mock_update1 = AsyncMock()
    ai_client_mock_update1.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value=f"Fresh content for /path/to/active_project/file1.txt: ```diff\n+ Updated line in file1.txt\n```")
    )
    
    ai_client_mock_update2 = AsyncMock()
    ai_client_mock_update2.create_message.return_value = MagicMock(
        usage=None,
        extract_response=MagicMock(return_value=f"Fresh content for /path/to/active_project/file1.txt: ```diff\n+ Second update in file1.txt\n```")
    )
    
    ai_client_factory_mock = MagicMock()
    ai_client_factory_mock.get_ai_client.side_effect = [ai_client_mock_create, ai_client_mock_update1, ai_client_mock_update2]
    monkeypatch.setattr("chat.chat.AIClientFactory.get_ai_client", lambda x: ai_client_factory_mock.get_ai_client())
    
    # Create file1.txt
    assistant_response_create = await chat_with_model(user_input_create, MagicMock(), "gpt-4")
    assert len(mock_app_state.conversation_history) == 2
    assert mock_app_state.conversation_history[1]['content'] == 'Added /path/to/active_project/file1.txt to context.'
    assert mock_file_context.tracked_files["file1.txt"] == "Added /path/to/active_project/file1.txt to context."
    
    # Update file1.txt first time
    assistant_response_update1 = await chat_with_model(user_input_update1, MagicMock(), "gpt-4")
    assert len(mock_app_state.conversation_history) == 4
    assert "### Diff for /path/to/active_project/file1.txt" in mock_app_state.conversation_history[3]['content']
    assert mock_file_context.tracked_files["file1.txt"] == "Updated line in file1.txt"
    
    # Update file1.txt second time
    assistant_response_update2 = await chat_with_model(user_input_update2, MagicMock(), "gpt-4")
    assert len(mock_app_state.conversation_history) == 6
    assert "### Diff for /path/to/active_project/file1.txt" in mock_app_state.conversation_history[5]['content']
    assert mock_file_context.tracked_files["file1.txt"] == "Second update in file1.txt"
```

_________________________________________ ERROR collecting tests/test_chat/test_conversation_history.py __________________________________________
tests/test_chat/test_conversation_history.py:190: in <module>
    from chat.chat import chat_with_model, process_file_updates, detect_file_updates, calculate_diff, remove_full_content_from_response
chat/chat.py:375: in <module>
    def detect_file_updates(assistant_message: str) -> Dict[str, Optional[str]]:
E   NameError: name 'Optional' is not defined
============================================================ short test summary info =============================================================
ERROR tests/test_chat/test_conversation_history.py - NameError: name 'Optional' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!

chat.py
```
import logging
import json
import os
import re
import difflib
from typing import List, Dict, Any, Tuple
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 in assistant_response
    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
    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)

    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 is not None:
                # Existing file: calculate diff
                diff = calculate_diff(existing_content, new_content)
                if diff.strip():
                    # Format and append diff as an assistant message
                    diff_message = f"### Diff for {relative_path}\n```diff\n{diff}\n```"
                    app_state.conversation_history.append({"role": "assistant", "content": diff_message})
                else:
                    # No changes detected
                    notification_message = f"No changes detected in {relative_path}."
                    app_state.conversation_history.append({"role": "assistant", "content": notification_message})
            else:
                # First instance: insert simple notification
                notification_message = f"I've added `{relative_path}` to the context."
                app_state.conversation_history.append({"role": "assistant", "content": notification_message})

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

            # Remove the processed content from assistant_response
            assistant_response = remove_full_content_from_response(assistant_response, relative_path)
        else:
            # Handle cases where new_content is None
            logging.warning(f"No content provided for {relative_path}.")
            continue

    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.
    """
    # Remove the section containing the full file content
    pattern = rf"Fresh content for {re.escape(relative_path)}:.*?```.*?\n[\s\S]*?```"
    cleaned_message = re.sub(pattern, "", assistant_message, flags=re.MULTILINE)
    return cleaned_message.strip()


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
```