mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
Add sandbox_id field to conversation endpoints (#13044)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -79,6 +79,7 @@ export interface Conversation {
|
||||
conversation_version?: "V0" | "V1";
|
||||
sub_conversation_ids?: string[];
|
||||
public?: boolean;
|
||||
sandbox_id?: string | null;
|
||||
}
|
||||
|
||||
export interface ResultSet<T> {
|
||||
|
||||
@@ -38,3 +38,4 @@ class ConversationInfo:
|
||||
conversation_version: str = 'V0'
|
||||
sub_conversation_ids: list[str] = field(default_factory=list)
|
||||
public: bool | None = None
|
||||
sandbox_id: str | None = None
|
||||
|
||||
@@ -766,6 +766,7 @@ async def _get_conversation_info(
|
||||
url=agent_loop_info.url if agent_loop_info else None,
|
||||
session_api_key=getattr(agent_loop_info, 'session_api_key', None),
|
||||
pr_number=conversation.pr_number,
|
||||
sandbox_id=None, # V0 conversations don't have sandbox_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
@@ -1582,4 +1583,5 @@ def _to_conversation_info(app_conversation: AppConversation) -> ConversationInfo
|
||||
sub_id.hex for sub_id in app_conversation.sub_conversation_ids
|
||||
],
|
||||
public=app_conversation.public,
|
||||
sandbox_id=app_conversation.sandbox_id,
|
||||
)
|
||||
|
||||
@@ -24,8 +24,12 @@ from openhands.app_server.app_conversation.app_conversation_service import (
|
||||
AppConversationService,
|
||||
)
|
||||
from openhands.app_server.sandbox.sandbox_models import SandboxStatus
|
||||
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
|
||||
from openhands.microagent.microagent import KnowledgeMicroagent, RepoMicroagent
|
||||
from openhands.microagent.types import MicroagentMetadata, MicroagentType
|
||||
from openhands.runtime.runtime_status import RuntimeStatus
|
||||
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
||||
from openhands.server.data_models.agent_loop_info import AgentLoopInfo
|
||||
from openhands.server.data_models.conversation_info import ConversationStatus
|
||||
from openhands.server.data_models.conversation_info_result_set import (
|
||||
ConversationInfoResultSet,
|
||||
@@ -38,6 +42,8 @@ from openhands.server.routes.conversation import (
|
||||
from openhands.server.routes.manage_conversations import (
|
||||
_RESUME_GRACE_PERIOD,
|
||||
UpdateConversationRequest,
|
||||
_get_conversation_info,
|
||||
_to_conversation_info,
|
||||
get_conversation,
|
||||
search_conversations,
|
||||
update_conversation,
|
||||
@@ -54,8 +60,6 @@ from openhands.storage.data_models.conversation_metadata import (
|
||||
async def test_get_microagents():
|
||||
"""Test the get_microagents function directly."""
|
||||
# Create mock microagents
|
||||
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
|
||||
|
||||
repo_microagent = RepoMicroagent(
|
||||
name='test_repo',
|
||||
content='This is a test repo microagent',
|
||||
@@ -1152,8 +1156,6 @@ async def test_add_message_empty_message():
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_sub_conversation_with_planning_agent():
|
||||
"""Test creating a sub-conversation from a parent conversation with planning agent."""
|
||||
from uuid import uuid4
|
||||
|
||||
parent_conversation_id = uuid4()
|
||||
user_id = 'test_user_456'
|
||||
sandbox_id = 'test_sandbox_123'
|
||||
@@ -1513,8 +1515,6 @@ async def test_get_conversation_resume_status_handling(
|
||||
should_call_server,
|
||||
):
|
||||
"""Test get_conversation handles resume status correctly for various scenarios."""
|
||||
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
||||
|
||||
conversation_id = uuid4()
|
||||
|
||||
# Convert string execution_status to enum if provided
|
||||
@@ -1564,3 +1564,125 @@ async def test_get_conversation_resume_status_handling(
|
||||
)
|
||||
else:
|
||||
mock_httpx_client.get.assert_not_called()
|
||||
|
||||
|
||||
# Tests for _to_conversation_info and _get_conversation_info sandbox_id mapping
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_to_conversation_info_maps_sandbox_id():
|
||||
"""Test that _to_conversation_info correctly maps sandbox_id from V1 AppConversation."""
|
||||
conversation_id = uuid4()
|
||||
test_sandbox_id = 'test-sandbox-123'
|
||||
|
||||
# Create a mock V1 AppConversation with sandbox_id
|
||||
mock_app_conversation = AppConversation(
|
||||
id=conversation_id,
|
||||
created_by_user_id='test_user',
|
||||
sandbox_id=test_sandbox_id,
|
||||
sandbox_status=SandboxStatus.RUNNING,
|
||||
execution_status=ConversationExecutionStatus.RUNNING,
|
||||
conversation_url='https://sandbox.example.com/conversation',
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
title='Test Conversation',
|
||||
selected_repository='test/repo',
|
||||
selected_branch='main',
|
||||
git_provider='github',
|
||||
trigger=ConversationTrigger.GUI,
|
||||
sub_conversation_ids=[],
|
||||
public=False,
|
||||
)
|
||||
|
||||
result = _to_conversation_info(mock_app_conversation)
|
||||
|
||||
assert result is not None
|
||||
assert result.conversation_id == conversation_id.hex
|
||||
assert result.sandbox_id == test_sandbox_id
|
||||
assert result.conversation_version == 'V1'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_conversation_info_v0_has_null_sandbox_id():
|
||||
"""Test that _get_conversation_info returns null sandbox_id for V0 conversations."""
|
||||
conversation_id = 'test-conversation-v0'
|
||||
|
||||
# Create mock V0 conversation metadata (no sandbox_id field)
|
||||
mock_metadata = ConversationMetadata(
|
||||
conversation_id=conversation_id,
|
||||
user_id='test_user',
|
||||
title='V0 Test Conversation',
|
||||
selected_repository=None,
|
||||
last_updated_at=datetime.now(timezone.utc),
|
||||
created_at=datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
# Create mock agent_loop_info
|
||||
mock_agent_loop_info = AgentLoopInfo(
|
||||
conversation_id=conversation_id,
|
||||
runtime_status=RuntimeStatus.READY,
|
||||
status=ConversationStatus.STOPPED,
|
||||
event_store=None,
|
||||
url=None,
|
||||
session_api_key=None,
|
||||
)
|
||||
|
||||
result = await _get_conversation_info(
|
||||
conversation=mock_metadata,
|
||||
num_connections=0,
|
||||
agent_loop_info=mock_agent_loop_info,
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.sandbox_id is None
|
||||
assert result.conversation_version == 'V0'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_conversation_info_returns_all_fields():
|
||||
"""Test that _get_conversation_info returns all expected fields for V0 conversations."""
|
||||
conversation_id = 'test-conversation-full'
|
||||
|
||||
# Create mock V0 conversation metadata
|
||||
mock_metadata = ConversationMetadata(
|
||||
conversation_id=conversation_id,
|
||||
user_id='test_user',
|
||||
title='Full Test Conversation',
|
||||
selected_repository='test/repo',
|
||||
selected_branch='main',
|
||||
git_provider='github',
|
||||
trigger=ConversationTrigger.GUI,
|
||||
last_updated_at=datetime.now(timezone.utc),
|
||||
created_at=datetime.now(timezone.utc),
|
||||
pr_number=[123],
|
||||
)
|
||||
|
||||
# Create mock agent_loop_info
|
||||
mock_agent_loop_info = AgentLoopInfo(
|
||||
conversation_id=conversation_id,
|
||||
runtime_status=RuntimeStatus.READY,
|
||||
status=ConversationStatus.STOPPED,
|
||||
event_store=None,
|
||||
url='https://example.com/conversation',
|
||||
session_api_key='test-key',
|
||||
)
|
||||
|
||||
result = await _get_conversation_info(
|
||||
conversation=mock_metadata,
|
||||
num_connections=5,
|
||||
agent_loop_info=mock_agent_loop_info,
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.conversation_id == conversation_id
|
||||
assert result.title == 'Full Test Conversation'
|
||||
assert result.selected_repository == 'test/repo'
|
||||
assert result.selected_branch == 'main'
|
||||
assert result.git_provider == 'github'
|
||||
assert result.trigger == ConversationTrigger.GUI
|
||||
assert result.num_connections == 5
|
||||
assert result.url == 'https://example.com/conversation'
|
||||
assert result.session_api_key == 'test-key'
|
||||
assert result.pr_number == [123]
|
||||
assert result.sandbox_id is None # V0 should have null sandbox_id
|
||||
assert result.conversation_version == 'V0'
|
||||
|
||||
Reference in New Issue
Block a user