Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions cecli/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1573,9 +1573,14 @@ async def generate(self, user_message, preproc):

try:
if self.enable_context_compaction:
self.compact_context_completed = False
await self.compact_context_if_needed()
self.compact_context_completed = True
# Skip compaction if the user wants to clear or exit
# Compacting is wasteful since /clear will clear everything
# and /exit will exit the application
stripped = user_message.strip()
if stripped not in ("/clear", "/exit", "/quit"):
self.compact_context_completed = False
await self.compact_context_if_needed()
self.compact_context_completed = True

self.run_one_completed = False
await self.run_one(user_message, preproc)
Expand Down
105 changes: 105 additions & 0 deletions tests/commands/test_compaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from unittest.mock import AsyncMock, MagicMock

import pytest

# It's better to patch the Coder class where it's used if possible,
# but for this test, we will instantiate it and mock its methods.
from cecli.coders.base_coder import Coder
from cecli.io import InputOutput


@pytest.fixture
def mock_io():
"""Fixture for a mocked InputOutput object."""
return MagicMock(spec=InputOutput)


@pytest.fixture
def mock_model():
"""Fixture for a mocked model object."""
model = MagicMock()
model.info = {"max_input_tokens": 10000}
# Mock the name attribute that is used in Coder.create
model.name = "mock_model"
model.edit_format = "wholefile"
return model


@pytest.mark.asyncio
async def test_generate_skips_compaction_for_clear_command(mock_io, mock_model):
"""
Verify that compact_context_if_needed is NOT called for the /clear command.
"""
# Arrange
coder = await Coder.create(main_model=mock_model, io=mock_io, edit_format="wholefile")
coder.enable_context_compaction = True
coder.compact_context_if_needed = AsyncMock()
coder.run_one = AsyncMock()
user_message = "/clear"

# Act
await coder.generate(user_message, preproc=True)

# Assert
coder.compact_context_if_needed.assert_not_called()
coder.run_one.assert_called_once_with(user_message, True)


@pytest.mark.asyncio
async def test_generate_skips_compaction_for_exit_command(mock_io, mock_model):
"""
Verify that compact_context_if_needed is NOT called for the /exit command.
"""
# Arrange
coder = await Coder.create(main_model=mock_model, io=mock_io, edit_format="wholefile")
coder.enable_context_compaction = True
coder.compact_context_if_needed = AsyncMock()
coder.run_one = AsyncMock()
user_message = "/exit"

# Act
await coder.generate(user_message, preproc=True)

# Assert
coder.compact_context_if_needed.assert_not_called()
coder.run_one.assert_called_once_with(user_message, True)


@pytest.mark.asyncio
async def test_generate_skips_compaction_for_quit_command(mock_io, mock_model):
"""
Verify that compact_context_if_needed is NOT called for the /quit command.
"""
# Arrange
coder = await Coder.create(main_model=mock_model, io=mock_io, edit_format="wholefile")
coder.enable_context_compaction = True
coder.compact_context_if_needed = AsyncMock()
coder.run_one = AsyncMock()
user_message = "/quit"

# Act
await coder.generate(user_message, preproc=True)

# Assert
coder.compact_context_if_needed.assert_not_called()
coder.run_one.assert_called_once_with(user_message, True)


@pytest.mark.asyncio
async def test_generate_runs_compaction_for_regular_message(mock_io, mock_model):
"""
Verify that compact_context_if_needed IS called for a regular message.
"""
# Arrange
coder = await Coder.create(main_model=mock_model, io=mock_io, edit_format="wholefile")
coder.enable_context_compaction = True
coder.compact_context_if_needed = AsyncMock()
coder.run_one = AsyncMock()
user_message = "This is a regular message"

# Act
await coder.generate(user_message, preproc=True)

# Assert
coder.compact_context_if_needed.assert_called_once()
coder.run_one.assert_called_once_with(user_message, True)
Loading