mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Add conversation age limit configuration (#6763)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
42f1fc92fa
commit
52723061b1
@ -76,6 +76,7 @@ class AppConfig(BaseModel):
|
||||
file_uploads_allowed_extensions: list[str] = Field(default_factory=lambda: ['.*'])
|
||||
runloop_api_key: SecretStr | None = Field(default=None)
|
||||
cli_multiline_input: bool = Field(default=False)
|
||||
conversation_max_age_seconds: int = Field(default=864000) # 10 days in seconds
|
||||
|
||||
defaults_dict: ClassVar[dict] = {}
|
||||
|
||||
|
||||
@ -130,8 +130,9 @@ async def _create_new_conversation(
|
||||
@app.post('/conversations')
|
||||
async def new_conversation(request: Request, data: InitSessionRequest):
|
||||
"""Initialize a new session or join an existing one.
|
||||
|
||||
After successful initialization, the client should connect to the WebSocket
|
||||
using the returned conversation ID
|
||||
using the returned conversation ID.
|
||||
"""
|
||||
logger.info('Initializing new conversation')
|
||||
user_id = get_user_id(request)
|
||||
@ -188,10 +189,19 @@ async def search_conversations(
|
||||
config, get_user_id(request)
|
||||
)
|
||||
conversation_metadata_result_set = await conversation_store.search(page_id, limit)
|
||||
|
||||
# Filter out conversations older than max_age
|
||||
now = datetime.now(timezone.utc)
|
||||
max_age = config.conversation_max_age_seconds
|
||||
filtered_results = [
|
||||
conversation for conversation in conversation_metadata_result_set.results
|
||||
if hasattr(conversation, 'created_at') and
|
||||
(now - conversation.created_at.replace(tzinfo=timezone.utc)).total_seconds() <= max_age
|
||||
]
|
||||
|
||||
conversation_ids = set(
|
||||
conversation.conversation_id
|
||||
for conversation in conversation_metadata_result_set.results
|
||||
if hasattr(conversation, 'created_at')
|
||||
for conversation in filtered_results
|
||||
)
|
||||
running_conversations = await conversation_manager.get_running_agent_loops(
|
||||
get_user_id(request), set(conversation_ids)
|
||||
@ -202,7 +212,7 @@ async def search_conversations(
|
||||
conversation=conversation,
|
||||
is_running=conversation.conversation_id in running_conversations,
|
||||
)
|
||||
for conversation in conversation_metadata_result_set.results
|
||||
for conversation in filtered_results
|
||||
),
|
||||
next_page_id=conversation_metadata_result_set.next_page_id,
|
||||
)
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import json
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from openhands.runtime.impl.docker.docker_runtime import DockerRuntime
|
||||
from openhands.server.routes.manage_conversations import (
|
||||
delete_conversation,
|
||||
get_conversation,
|
||||
@ -30,8 +31,8 @@ def _patch_store():
|
||||
'selected_repository': 'foobar',
|
||||
'conversation_id': 'some_conversation_id',
|
||||
'github_user_id': '12345',
|
||||
'created_at': '2025-01-01T00:00:00',
|
||||
'last_updated_at': '2025-01-01T00:01:00',
|
||||
'created_at': '2025-01-01T00:00:00+00:00',
|
||||
'last_updated_at': '2025-01-01T00:01:00+00:00',
|
||||
}
|
||||
),
|
||||
)
|
||||
@ -49,22 +50,46 @@ def _patch_store():
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_conversations():
|
||||
with _patch_store():
|
||||
result_set = await search_conversations(
|
||||
MagicMock(state=MagicMock(github_token=''))
|
||||
)
|
||||
expected = ConversationInfoResultSet(
|
||||
results=[
|
||||
ConversationInfo(
|
||||
conversation_id='some_conversation_id',
|
||||
title='Some Conversation',
|
||||
created_at=datetime.fromisoformat('2025-01-01T00:00:00'),
|
||||
last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00'),
|
||||
status=ConversationStatus.STOPPED,
|
||||
selected_repository='foobar',
|
||||
)
|
||||
]
|
||||
)
|
||||
assert result_set == expected
|
||||
with patch(
|
||||
'openhands.server.routes.manage_conversations.config'
|
||||
) as mock_config:
|
||||
mock_config.conversation_max_age_seconds = 864000 # 10 days
|
||||
with patch(
|
||||
'openhands.server.routes.manage_conversations.conversation_manager'
|
||||
) as mock_manager:
|
||||
|
||||
async def mock_get_running_agent_loops(*args, **kwargs):
|
||||
return set()
|
||||
|
||||
mock_manager.get_running_agent_loops = mock_get_running_agent_loops
|
||||
with patch(
|
||||
'openhands.server.routes.manage_conversations.datetime'
|
||||
) as mock_datetime:
|
||||
mock_datetime.now.return_value = datetime.fromisoformat(
|
||||
'2025-01-01T00:00:00+00:00'
|
||||
)
|
||||
mock_datetime.fromisoformat = datetime.fromisoformat
|
||||
mock_datetime.timezone = timezone
|
||||
result_set = await search_conversations(
|
||||
MagicMock(state=MagicMock(github_token=''))
|
||||
)
|
||||
expected = ConversationInfoResultSet(
|
||||
results=[
|
||||
ConversationInfo(
|
||||
conversation_id='some_conversation_id',
|
||||
title='Some Conversation',
|
||||
created_at=datetime.fromisoformat(
|
||||
'2025-01-01T00:00:00+00:00'
|
||||
),
|
||||
last_updated_at=datetime.fromisoformat(
|
||||
'2025-01-01T00:01:00+00:00'
|
||||
),
|
||||
status=ConversationStatus.STOPPED,
|
||||
selected_repository='foobar',
|
||||
)
|
||||
]
|
||||
)
|
||||
assert result_set == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -76,8 +101,8 @@ async def test_get_conversation():
|
||||
expected = ConversationInfo(
|
||||
conversation_id='some_conversation_id',
|
||||
title='Some Conversation',
|
||||
created_at=datetime.fromisoformat('2025-01-01T00:00:00'),
|
||||
last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00'),
|
||||
created_at=datetime.fromisoformat('2025-01-01T00:00:00+00:00'),
|
||||
last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00+00:00'),
|
||||
status=ConversationStatus.STOPPED,
|
||||
selected_repository='foobar',
|
||||
)
|
||||
@ -109,8 +134,8 @@ async def test_update_conversation():
|
||||
expected = ConversationInfo(
|
||||
conversation_id='some_conversation_id',
|
||||
title='New Title',
|
||||
created_at=datetime.fromisoformat('2025-01-01T00:00:00'),
|
||||
last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00'),
|
||||
created_at=datetime.fromisoformat('2025-01-01T00:00:00+00:00'),
|
||||
last_updated_at=datetime.fromisoformat('2025-01-01T00:01:00+00:00'),
|
||||
status=ConversationStatus.STOPPED,
|
||||
selected_repository='foobar',
|
||||
)
|
||||
@ -120,11 +145,12 @@ async def test_update_conversation():
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_conversation():
|
||||
with _patch_store():
|
||||
await delete_conversation(
|
||||
'some_conversation_id',
|
||||
MagicMock(state=MagicMock(github_token='')),
|
||||
)
|
||||
conversation = await get_conversation(
|
||||
'some_conversation_id', MagicMock(state=MagicMock(github_token=''))
|
||||
)
|
||||
assert conversation is None
|
||||
with patch.object(DockerRuntime, 'delete', return_value=None):
|
||||
await delete_conversation(
|
||||
'some_conversation_id',
|
||||
MagicMock(state=MagicMock(github_token='')),
|
||||
)
|
||||
conversation = await get_conversation(
|
||||
'some_conversation_id', MagicMock(state=MagicMock(github_token=''))
|
||||
)
|
||||
assert conversation is None
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user