mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
1 Commits
openhands/
...
openhands/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16ed83082f |
@@ -1,5 +1,6 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events.event_filter import EventFilter
|
||||
@@ -10,11 +11,15 @@ from openhands.server.session.conversation import ServerConversation
|
||||
from openhands.server.shared import conversation_manager
|
||||
from openhands.server.utils import get_conversation
|
||||
|
||||
app = APIRouter(prefix='/api/conversations/{conversation_id}', dependencies=get_dependencies())
|
||||
app = APIRouter(
|
||||
prefix='/api/conversations/{conversation_id}', dependencies=get_dependencies()
|
||||
)
|
||||
|
||||
|
||||
@app.get('/config')
|
||||
async def get_remote_runtime_config(conversation: ServerConversation = Depends(get_conversation)) -> JSONResponse:
|
||||
async def get_remote_runtime_config(
|
||||
conversation: ServerConversation = Depends(get_conversation),
|
||||
) -> JSONResponse:
|
||||
"""Retrieve the runtime configuration.
|
||||
|
||||
Currently, this is the session ID and runtime ID (if available).
|
||||
@@ -31,7 +36,9 @@ async def get_remote_runtime_config(conversation: ServerConversation = Depends(g
|
||||
|
||||
|
||||
@app.get('/vscode-url')
|
||||
async def get_vscode_url(conversation: ServerConversation = Depends(get_conversation)) -> JSONResponse:
|
||||
async def get_vscode_url(
|
||||
conversation: ServerConversation = Depends(get_conversation),
|
||||
) -> JSONResponse:
|
||||
"""Get the VSCode URL.
|
||||
|
||||
This endpoint allows getting the VSCode URL.
|
||||
@@ -61,7 +68,9 @@ async def get_vscode_url(conversation: ServerConversation = Depends(get_conversa
|
||||
|
||||
|
||||
@app.get('/web-hosts')
|
||||
async def get_hosts(conversation: ServerConversation = Depends(get_conversation)) -> JSONResponse:
|
||||
async def get_hosts(
|
||||
conversation: ServerConversation = Depends(get_conversation),
|
||||
) -> JSONResponse:
|
||||
"""Get the hosts used by the runtime.
|
||||
|
||||
This endpoint allows getting the hosts used by the runtime.
|
||||
@@ -143,7 +152,76 @@ async def search_events(
|
||||
|
||||
|
||||
@app.post('/events')
|
||||
async def add_event(request: Request, conversation: ServerConversation = Depends(get_conversation)):
|
||||
async def add_event(
|
||||
request: Request, conversation: ServerConversation = Depends(get_conversation)
|
||||
):
|
||||
data = request.json()
|
||||
await conversation_manager.send_to_event_stream(conversation.sid, data)
|
||||
return JSONResponse({'success': True})
|
||||
|
||||
|
||||
class MicroagentResponse(BaseModel):
|
||||
"""Response model for microagents endpoint."""
|
||||
|
||||
name: str
|
||||
type: str
|
||||
content: str
|
||||
triggers: list[str] = []
|
||||
|
||||
|
||||
@app.get('/microagents')
|
||||
async def get_microagents(
|
||||
conversation: ServerConversation = Depends(get_conversation),
|
||||
) -> JSONResponse:
|
||||
"""Get all microagents associated with the conversation.
|
||||
|
||||
This endpoint returns all repository and knowledge microagents that are loaded for the conversation.
|
||||
|
||||
Returns:
|
||||
JSONResponse: A JSON response containing the list of microagents.
|
||||
"""
|
||||
try:
|
||||
# Get the agent session for this conversation
|
||||
agent_session = conversation_manager.get_agent_session(conversation.sid)
|
||||
|
||||
if not agent_session:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
content={'error': 'Agent session not found for this conversation'},
|
||||
)
|
||||
|
||||
# Access the memory to get the microagents
|
||||
memory = agent_session.memory
|
||||
|
||||
# Prepare the response
|
||||
microagents = []
|
||||
|
||||
# Add repo microagents
|
||||
for name, agent in memory.repo_microagents.items():
|
||||
microagents.append(
|
||||
MicroagentResponse(
|
||||
name=name, type='repo', content=agent.content, triggers=[]
|
||||
)
|
||||
)
|
||||
|
||||
# Add knowledge microagents
|
||||
for name, agent in memory.knowledge_microagents.items():
|
||||
microagents.append(
|
||||
MicroagentResponse(
|
||||
name=name,
|
||||
type='knowledge',
|
||||
content=agent.content,
|
||||
triggers=agent.triggers,
|
||||
)
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={'microagents': [m.dict() for m in microagents]},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Error getting microagents: {e}')
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={'error': f'Error getting microagents: {e}'},
|
||||
)
|
||||
|
||||
136
tests/unit/test_conversation_routes.py
Normal file
136
tests/unit/test_conversation_routes.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from openhands.microagent.microagent import KnowledgeMicroagent, RepoMicroagent
|
||||
from openhands.microagent.types import MicroagentMetadata, MicroagentType
|
||||
from openhands.server.routes.conversation import get_microagents
|
||||
from openhands.server.session.conversation import ServerConversation
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_microagents():
|
||||
"""Test the get_microagents function directly."""
|
||||
# Create mock microagents
|
||||
repo_microagent = RepoMicroagent(
|
||||
name='test_repo',
|
||||
content='This is a test repo microagent',
|
||||
metadata=MicroagentMetadata(
|
||||
name='test_repo', type=MicroagentType.REPO_KNOWLEDGE
|
||||
),
|
||||
source='test_source',
|
||||
type=MicroagentType.REPO_KNOWLEDGE,
|
||||
)
|
||||
|
||||
knowledge_microagent = KnowledgeMicroagent(
|
||||
name='test_knowledge',
|
||||
content='This is a test knowledge microagent',
|
||||
metadata=MicroagentMetadata(
|
||||
name='test_knowledge',
|
||||
type=MicroagentType.KNOWLEDGE,
|
||||
triggers=['test', 'knowledge'],
|
||||
),
|
||||
source='test_source',
|
||||
type=MicroagentType.KNOWLEDGE,
|
||||
)
|
||||
|
||||
# Mock the agent session and memory
|
||||
mock_memory = MagicMock()
|
||||
mock_memory.repo_microagents = {'test_repo': repo_microagent}
|
||||
mock_memory.knowledge_microagents = {'test_knowledge': knowledge_microagent}
|
||||
|
||||
mock_agent_session = MagicMock()
|
||||
mock_agent_session.memory = mock_memory
|
||||
|
||||
# Create a mock ServerConversation
|
||||
mock_conversation = MagicMock(spec=ServerConversation)
|
||||
mock_conversation.sid = 'test_sid'
|
||||
|
||||
# Mock the conversation manager
|
||||
with patch(
|
||||
'openhands.server.routes.conversation.conversation_manager'
|
||||
) as mock_manager:
|
||||
# Set up the mocks
|
||||
mock_manager.get_agent_session.return_value = mock_agent_session
|
||||
|
||||
# Call the function directly
|
||||
response = await get_microagents(conversation=mock_conversation)
|
||||
|
||||
# Verify the response
|
||||
assert isinstance(response, JSONResponse)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Parse the JSON content
|
||||
content = json.loads(response.body)
|
||||
assert 'microagents' in content
|
||||
assert len(content['microagents']) == 2
|
||||
|
||||
# Check repo microagent
|
||||
repo_agent = next(m for m in content['microagents'] if m['name'] == 'test_repo')
|
||||
assert repo_agent['type'] == 'repo'
|
||||
assert repo_agent['content'] == 'This is a test repo microagent'
|
||||
assert repo_agent['triggers'] == []
|
||||
|
||||
# Check knowledge microagent
|
||||
knowledge_agent = next(
|
||||
m for m in content['microagents'] if m['name'] == 'test_knowledge'
|
||||
)
|
||||
assert knowledge_agent['type'] == 'knowledge'
|
||||
assert knowledge_agent['content'] == 'This is a test knowledge microagent'
|
||||
assert knowledge_agent['triggers'] == ['test', 'knowledge']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_microagents_no_agent_session():
|
||||
"""Test the get_microagents function when no agent session is found."""
|
||||
# Create a mock ServerConversation
|
||||
mock_conversation = MagicMock(spec=ServerConversation)
|
||||
mock_conversation.sid = 'test_sid'
|
||||
|
||||
# Mock the conversation manager
|
||||
with patch(
|
||||
'openhands.server.routes.conversation.conversation_manager'
|
||||
) as mock_manager:
|
||||
# Set up the mocks
|
||||
mock_manager.get_agent_session.return_value = None
|
||||
|
||||
# Call the function directly
|
||||
response = await get_microagents(conversation=mock_conversation)
|
||||
|
||||
# Verify the response
|
||||
assert isinstance(response, JSONResponse)
|
||||
assert response.status_code == 404
|
||||
|
||||
# Parse the JSON content
|
||||
content = json.loads(response.body)
|
||||
assert 'error' in content
|
||||
assert 'Agent session not found' in content['error']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_microagents_exception():
|
||||
"""Test the get_microagents function when an exception occurs."""
|
||||
# Create a mock ServerConversation
|
||||
mock_conversation = MagicMock(spec=ServerConversation)
|
||||
mock_conversation.sid = 'test_sid'
|
||||
|
||||
# Mock the conversation manager
|
||||
with patch(
|
||||
'openhands.server.routes.conversation.conversation_manager'
|
||||
) as mock_manager:
|
||||
# Set up the mocks to raise an exception
|
||||
mock_manager.get_agent_session.side_effect = Exception('Test exception')
|
||||
|
||||
# Call the function directly
|
||||
response = await get_microagents(conversation=mock_conversation)
|
||||
|
||||
# Verify the response
|
||||
assert isinstance(response, JSONResponse)
|
||||
assert response.status_code == 500
|
||||
|
||||
# Parse the JSON content
|
||||
content = json.loads(response.body)
|
||||
assert 'error' in content
|
||||
assert 'Test exception' in content['error']
|
||||
Reference in New Issue
Block a user