refactor(backend): update get_microagent_management_conversations API to support V1 (#11313)

Co-authored-by: Tim O'Farrell <tofarr@gmail.com>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
This commit is contained in:
Hiep Le 2025-11-04 17:44:44 +07:00 committed by GitHub
parent 2fc8ab2601
commit fa431fb956
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 421 additions and 113 deletions

View File

@ -1104,47 +1104,154 @@ def add_experiment_config_for_conversation(
return False
@app.get('/microagent-management/conversations')
async def get_microagent_management_conversations(
selected_repository: str,
page_id: str | None = None,
limit: int = 20,
conversation_store: ConversationStore = Depends(get_conversation_store),
provider_tokens: PROVIDER_TOKEN_TYPE = Depends(get_provider_tokens),
) -> ConversationInfoResultSet:
"""Get conversations for the microagent management page with pagination support.
This endpoint returns conversations with conversation_trigger = 'microagent_management'
and only includes conversations with active PRs. Pagination is supported.
def _parse_combined_page_id(page_id: str | None) -> tuple[str | None, str | None]:
"""Parse combined page_id to extract separate V0 and V1 page_ids.
Args:
page_id: Optional page ID for pagination
limit: Maximum number of results per page (default: 20)
selected_repository: Optional repository filter to limit results to a specific repository
conversation_store: Conversation store dependency
provider_tokens: Provider tokens for checking PR status
"""
conversation_metadata_result_set = await conversation_store.search(page_id, limit)
page_id: Combined page_id (base64-encoded JSON) or legacy V0 page_id
# Apply age filter first using common function
filtered_results = _filter_conversations_by_age(
conversation_metadata_result_set.results, config.conversation_max_age_seconds
Returns:
Tuple of (v0_page_id, v1_page_id)
"""
v0_page_id = None
v1_page_id = None
if page_id:
try:
# Try to parse as JSON first
page_data = json.loads(base64.b64decode(page_id))
v0_page_id = page_data.get('v0')
v1_page_id = page_data.get('v1')
except (json.JSONDecodeError, TypeError, Exception):
# Fallback: treat as v0 page_id for backward compatibility
# This catches base64 decode errors and any other parsing issues
v0_page_id = page_id
return v0_page_id, v1_page_id
async def _fetch_v1_conversations_safe(
app_conversation_service: AppConversationService,
v1_page_id: str | None,
limit: int,
) -> tuple[list[ConversationInfo], str | None]:
"""Safely fetch V1 conversations with error handling.
Args:
app_conversation_service: App conversation service for V1
v1_page_id: Page ID for V1 pagination
limit: Maximum number of results
Returns:
Tuple of (v1_conversations, v1_next_page_id)
"""
v1_conversations = []
v1_next_page_id = None
try:
age_filter_date = None
if config.conversation_max_age_seconds:
age_filter_date = datetime.now(timezone.utc) - timedelta(
seconds=config.conversation_max_age_seconds
)
app_conversation_page = await app_conversation_service.search_app_conversations(
page_id=v1_page_id,
limit=limit,
created_at__gte=age_filter_date,
)
v1_conversations = [
_to_conversation_info(app_conv) for app_conv in app_conversation_page.items
]
v1_next_page_id = app_conversation_page.next_page_id
except Exception as e:
# V1 system might not be available or initialized yet
logger.debug(f'V1 conversation service not available: {str(e)}')
return v1_conversations, v1_next_page_id
async def _process_v0_conversations(
conversation_metadata_result_set,
) -> list[ConversationInfo]:
"""Process V0 conversations with age filtering and agent loop info.
Args:
conversation_metadata_result_set: Result set from V0 conversation store
Returns:
List of processed ConversationInfo objects
"""
# Apply age filter to V0 conversations
v0_filtered_results = _filter_conversations_by_age(
conversation_metadata_result_set.results,
config.conversation_max_age_seconds,
)
# Check if the last PR is active (not closed/merged)
provider_handler = ProviderHandler(provider_tokens)
v0_conversation_ids = set(
conversation.conversation_id for conversation in v0_filtered_results
)
# Apply additional filters
final_filtered_results = []
for conversation in filtered_results:
# Get agent loop info for V0 conversations
await conversation_manager.get_connections(filter_to_sids=v0_conversation_ids)
v0_agent_loop_info = await conversation_manager.get_agent_loop_info(
filter_to_sids=v0_conversation_ids
)
v0_agent_loop_info_by_conversation_id = {
info.conversation_id: info for info in v0_agent_loop_info
}
# Convert to ConversationInfo objects
v0_conversations = await wait_all(
_get_conversation_info(
conversation=conversation,
num_connections=sum(
1
for conversation_id in v0_agent_loop_info_by_conversation_id.values()
if conversation_id == conversation.conversation_id
),
agent_loop_info=v0_agent_loop_info_by_conversation_id.get(
conversation.conversation_id
),
)
for conversation in v0_filtered_results
)
return v0_conversations
async def _apply_microagent_filters(
conversations: list[ConversationInfo],
selected_repository: str,
provider_handler: ProviderHandler,
) -> list[ConversationInfo]:
"""Apply microagent management specific filters to conversations.
Filters conversations by:
- Trigger type (MICROAGENT_MANAGEMENT)
- Repository match
- PR status (only open PRs)
Args:
conversations: List of conversations to filter
selected_repository: Repository to filter by
provider_handler: Handler for checking PR status
Returns:
Filtered list of conversations
"""
filtered = []
for conversation in conversations:
# Only include microagent_management conversations
if conversation.trigger != ConversationTrigger.MICROAGENT_MANAGEMENT:
continue
# Apply repository filter if specified
# Apply repository filter
if conversation.selected_repository != selected_repository:
continue
# Check if PR is still open
if (
conversation.pr_number
and len(conversation.pr_number) > 0
@ -1159,12 +1266,101 @@ async def get_microagent_management_conversations(
# Skip this conversation if the PR is closed/merged
continue
final_filtered_results.append(conversation)
filtered.append(conversation)
return await _build_conversation_result_set(
final_filtered_results, conversation_metadata_result_set.next_page_id
return filtered
def _create_combined_page_id(
v0_next_page_id: str | None, v1_next_page_id: str | None
) -> str | None:
"""Create a combined page_id from V0 and V1 page_ids.
Args:
v0_next_page_id: Next page ID for V0 conversations
v1_next_page_id: Next page ID for V1 conversations
Returns:
Base64-encoded JSON combining both page_ids, or None if no next pages
"""
if not v0_next_page_id and not v1_next_page_id:
return None
next_page_data = {
'v0': v0_next_page_id,
'v1': v1_next_page_id,
}
return base64.b64encode(json.dumps(next_page_data).encode()).decode()
@app.get('/microagent-management/conversations')
async def get_microagent_management_conversations(
selected_repository: str,
page_id: str | None = None,
limit: int = 20,
conversation_store: ConversationStore = Depends(get_conversation_store),
provider_tokens: PROVIDER_TOKEN_TYPE = Depends(get_provider_tokens),
app_conversation_service: AppConversationService = app_conversation_service_dependency,
) -> ConversationInfoResultSet:
"""Get conversations for the microagent management page with pagination support.
This endpoint returns conversations with conversation_trigger = 'microagent_management'
and only includes conversations with active PRs. Pagination is supported.
Args:
page_id: Optional page ID for pagination
limit: Maximum number of results per page (default: 20)
selected_repository: Repository filter to limit results to a specific repository
conversation_store: Conversation store dependency
provider_tokens: Provider tokens for checking PR status
app_conversation_service: App conversation service for V1 conversations
Returns:
ConversationInfoResultSet with filtered and paginated results
"""
# Parse page_id to extract V0 and V1 components
v0_page_id, v1_page_id = _parse_combined_page_id(page_id)
# Fetch V0 conversations
conversation_metadata_result_set = await conversation_store.search(
v0_page_id, limit
)
# Fetch V1 conversations (with graceful error handling)
v1_conversations, v1_next_page_id = await _fetch_v1_conversations_safe(
app_conversation_service, v1_page_id, limit
)
# Process V0 conversations
v0_conversations = await _process_v0_conversations(conversation_metadata_result_set)
# Apply microagent-specific filters
provider_handler = ProviderHandler(provider_tokens)
v0_filtered = await _apply_microagent_filters(
v0_conversations, selected_repository, provider_handler
)
v1_filtered = await _apply_microagent_filters(
v1_conversations, selected_repository, provider_handler
)
# Combine and sort results
all_conversations = v0_filtered + v1_filtered
all_conversations.sort(
key=lambda x: x.created_at or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
)
# Limit to requested number of results
final_results = all_conversations[:limit]
# Create combined page_id for pagination
next_page_id = _create_combined_page_id(
conversation_metadata_result_set.next_page_id, v1_next_page_id
)
return ConversationInfoResultSet(results=final_results, next_page_id=next_page_id)
def _to_conversation_info(app_conversation: AppConversation) -> ConversationInfo:
"""Convert a V1 AppConversation into an old style ConversationInfo"""

View File

@ -1,8 +1,13 @@
import base64
import json
from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from openhands.app_server.app_conversation.app_conversation_service import (
AppConversationService,
)
from openhands.integrations.provider import ProviderHandler
from openhands.server.data_models.conversation_info_result_set import (
ConversationInfoResultSet,
@ -17,6 +22,54 @@ from openhands.storage.data_models.conversation_metadata import (
)
def _create_mock_app_conversation_service():
"""Create a mock AppConversationService that returns empty V1 results."""
mock_service = MagicMock(spec=AppConversationService)
mock_service.search_app_conversations = AsyncMock(
return_value=MagicMock(items=[], next_page_id=None)
)
return mock_service
def _decode_combined_page_id(page_id: str | None) -> dict:
"""Decode a combined page_id to get v0 and v1 components."""
if not page_id:
return {'v0': None, 'v1': None}
try:
return json.loads(base64.b64decode(page_id))
except Exception:
# Legacy format - just v0
return {'v0': page_id, 'v1': None}
async def _mock_wait_all(coros):
"""Mock implementation of wait_all that properly awaits coroutines."""
results = []
for coro in coros:
if hasattr(coro, '__await__'):
results.append(await coro)
else:
results.append(coro)
return results
def _setup_common_mocks():
"""Set up common mocks used by all tests."""
return {
'config': patch('openhands.server.routes.manage_conversations.config'),
'conversation_manager': patch(
'openhands.server.routes.manage_conversations.conversation_manager'
),
'wait_all': patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
'provider_handler': patch(
'openhands.server.routes.manage_conversations.ProviderHandler'
),
}
@pytest.mark.asyncio
async def test_get_microagent_management_conversations_success():
"""Test successful retrieval of microagent management conversations."""
@ -64,24 +117,30 @@ async def test_get_microagent_management_conversations_success():
mock_provider_handler = MagicMock(spec=ProviderHandler)
mock_provider_handler.is_pr_open = AsyncMock(return_value=True)
# Mock app conversation service
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch(
'openhands.server.routes.manage_conversations.ProviderHandler',
return_value=mock_provider_handler,
),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function
mock_build_result.return_value = ConversationInfoResultSet(
results=[], next_page_id='next_page_456'
)
# Mock config
mock_config.conversation_max_age_seconds = 86400 # 24 hours
# Mock conversation manager
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function with correct parameter order
result = await get_microagent_management_conversations(
selected_repository=selected_repository,
@ -89,11 +148,16 @@ async def test_get_microagent_management_conversations_success():
limit=limit,
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify the result
assert isinstance(result, ConversationInfoResultSet)
assert result.next_page_id == 'next_page_456'
# Decode the combined page_id to verify v0 component
decoded_page_id = _decode_combined_page_id(result.next_page_id)
assert decoded_page_id['v0'] == 'next_page_456'
assert decoded_page_id['v1'] is None
# Verify conversation store was called correctly
mock_conversation_store.search.assert_called_once_with(page_id, limit)
@ -114,26 +178,31 @@ async def test_get_microagent_management_conversations_no_results():
# Mock provider tokens
mock_provider_tokens = {'github': 'token_123'}
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch('openhands.server.routes.manage_conversations.ProviderHandler'),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function
mock_build_result.return_value = ConversationInfoResultSet(
results=[], next_page_id=None
)
# Mock config
mock_config.conversation_max_age_seconds = 86400
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function with required selected_repository parameter
result = await get_microagent_management_conversations(
selected_repository='owner/repo',
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify the result
@ -184,29 +253,34 @@ async def test_get_microagent_management_conversations_filter_by_repository():
mock_provider_handler = MagicMock(spec=ProviderHandler)
mock_provider_handler.is_pr_open = AsyncMock(return_value=True)
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch(
'openhands.server.routes.manage_conversations.ProviderHandler',
return_value=mock_provider_handler,
),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function - only repo1 should be included
mock_build_result.return_value = ConversationInfoResultSet(
results=[mock_conversations[0]], next_page_id=None
)
# Mock config
mock_config.conversation_max_age_seconds = 86400
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function with repository filter
result = await get_microagent_management_conversations(
selected_repository='owner/repo1',
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify only conversations from the specified repository are returned
@ -257,29 +331,34 @@ async def test_get_microagent_management_conversations_filter_by_trigger():
mock_provider_handler = MagicMock(spec=ProviderHandler)
mock_provider_handler.is_pr_open = AsyncMock(return_value=True)
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch(
'openhands.server.routes.manage_conversations.ProviderHandler',
return_value=mock_provider_handler,
),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function - only microagent_management should be included
mock_build_result.return_value = ConversationInfoResultSet(
results=[mock_conversations[0]], next_page_id=None
)
# Mock config
mock_config.conversation_max_age_seconds = 86400
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function
result = await get_microagent_management_conversations(
selected_repository='owner/repo',
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify only microagent_management conversations are returned
@ -330,29 +409,34 @@ async def test_get_microagent_management_conversations_filter_inactive_pr():
mock_provider_handler = MagicMock(spec=ProviderHandler)
mock_provider_handler.is_pr_open = AsyncMock(side_effect=[True, False])
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch(
'openhands.server.routes.manage_conversations.ProviderHandler',
return_value=mock_provider_handler,
),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function - only active PR should be included
mock_build_result.return_value = ConversationInfoResultSet(
results=[mock_conversations[0]], next_page_id=None
)
# Mock config
mock_config.conversation_max_age_seconds = 86400
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function
result = await get_microagent_management_conversations(
selected_repository='owner/repo',
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify only conversations with active PRs are returned
@ -393,29 +477,34 @@ async def test_get_microagent_management_conversations_no_pr_number():
# Mock provider handler
mock_provider_handler = MagicMock(spec=ProviderHandler)
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch(
'openhands.server.routes.manage_conversations.ProviderHandler',
return_value=mock_provider_handler,
),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function
mock_build_result.return_value = ConversationInfoResultSet(
results=mock_conversations, next_page_id=None
)
# Mock config
mock_config.conversation_max_age_seconds = 86400
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function
result = await get_microagent_management_conversations(
selected_repository='owner/repo',
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify conversation without PR number is included
@ -456,29 +545,34 @@ async def test_get_microagent_management_conversations_no_repository():
# Mock provider handler
mock_provider_handler = MagicMock(spec=ProviderHandler)
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch(
'openhands.server.routes.manage_conversations.ProviderHandler',
return_value=mock_provider_handler,
),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function - conversation should be filtered out due to repository mismatch
mock_build_result.return_value = ConversationInfoResultSet(
results=[], next_page_id=None
)
# Mock config
mock_config.conversation_max_age_seconds = 86400
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function
result = await get_microagent_management_conversations(
selected_repository='owner/repo',
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify conversation without repository is filtered out
@ -532,29 +626,34 @@ async def test_get_microagent_management_conversations_age_filter():
mock_provider_handler = MagicMock(spec=ProviderHandler)
mock_provider_handler.is_pr_open = AsyncMock(return_value=True)
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch(
'openhands.server.routes.manage_conversations.ProviderHandler',
return_value=mock_provider_handler,
),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function - only recent conversation should be included
mock_build_result.return_value = ConversationInfoResultSet(
results=[recent_conversation], next_page_id=None
)
# Mock config with short max age
mock_config.conversation_max_age_seconds = 3600 # 1 hour
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function
result = await get_microagent_management_conversations(
selected_repository='owner/repo',
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify only recent conversation is returned
@ -574,21 +673,25 @@ async def test_get_microagent_management_conversations_pagination():
# Mock provider tokens
mock_provider_tokens = {'github': 'token_123'}
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch('openhands.server.routes.manage_conversations.ProviderHandler'),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function
mock_build_result.return_value = ConversationInfoResultSet(
results=[], next_page_id='next_page_789'
)
# Mock config
mock_config.conversation_max_age_seconds = 86400
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function with pagination parameters
result = await get_microagent_management_conversations(
selected_repository='owner/repo',
@ -596,11 +699,15 @@ async def test_get_microagent_management_conversations_pagination():
limit=5,
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify pagination parameters were passed correctly
mock_conversation_store.search.assert_called_once_with('test_page', 5)
assert result.next_page_id == 'next_page_789'
# Decode and verify the next_page_id
decoded_page_id = _decode_combined_page_id(result.next_page_id)
assert decoded_page_id['v0'] == 'next_page_789'
@pytest.mark.asyncio
@ -615,26 +722,31 @@ async def test_get_microagent_management_conversations_default_parameters():
# Mock provider tokens
mock_provider_tokens = {'github': 'token_123'}
mock_app_conversation_service = _create_mock_app_conversation_service()
with (
patch('openhands.server.routes.manage_conversations.ProviderHandler'),
patch(
'openhands.server.routes.manage_conversations._build_conversation_result_set'
) as mock_build_result,
patch('openhands.server.routes.manage_conversations.config') as mock_config,
patch(
'openhands.server.routes.manage_conversations.conversation_manager'
) as mock_conv_mgr,
patch(
'openhands.server.routes.manage_conversations.wait_all',
side_effect=_mock_wait_all,
),
):
# Mock the build result function
mock_build_result.return_value = ConversationInfoResultSet(
results=[], next_page_id=None
)
# Mock config
mock_config.conversation_max_age_seconds = 86400
mock_conv_mgr.get_connections = AsyncMock(return_value=[])
mock_conv_mgr.get_agent_loop_info = AsyncMock(return_value=[])
# Call the function without parameters (selected_repository is required)
result = await get_microagent_management_conversations(
selected_repository='owner/repo',
conversation_store=mock_conversation_store,
provider_tokens=mock_provider_tokens,
app_conversation_service=mock_app_conversation_service,
)
# Verify default values were used