OpenHands / openhands /server /routes /conversation.py
Backup-bdg's picture
Upload 964 files
51ff9e5 verified
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import JSONResponse
from openhands.core.logger import openhands_logger as logger
from openhands.events.event_filter import EventFilter
from openhands.events.serialization.event import event_to_dict
from openhands.runtime.base import Runtime
from openhands.server.dependencies import get_dependencies
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.get('/config')
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).
"""
runtime = conversation.runtime
runtime_id = runtime.runtime_id if hasattr(runtime, 'runtime_id') else None
session_id = runtime.sid if hasattr(runtime, 'sid') else None
return JSONResponse(
content={
'runtime_id': runtime_id,
'session_id': session_id,
}
)
@app.get('/vscode-url')
async def get_vscode_url(conversation: ServerConversation = Depends(get_conversation)) -> JSONResponse:
"""Get the VSCode URL.
This endpoint allows getting the VSCode URL.
Args:
request (Request): The incoming FastAPI request object.
Returns:
JSONResponse: A JSON response indicating the success of the operation.
"""
try:
runtime: Runtime = conversation.runtime
logger.debug(f'Runtime type: {type(runtime)}')
logger.debug(f'Runtime VSCode URL: {runtime.vscode_url}')
return JSONResponse(
status_code=status.HTTP_200_OK, content={'vscode_url': runtime.vscode_url}
)
except Exception as e:
logger.error(f'Error getting VSCode URL: {e}')
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
'vscode_url': None,
'error': f'Error getting VSCode URL: {e}',
},
)
@app.get('/web-hosts')
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.
Args:
request (Request): The incoming FastAPI request object.
Returns:
JSONResponse: A JSON response indicating the success of the operation.
"""
try:
runtime: Runtime = conversation.runtime
logger.debug(f'Runtime type: {type(runtime)}')
logger.debug(f'Runtime hosts: {runtime.web_hosts}')
return JSONResponse(status_code=200, content={'hosts': runtime.web_hosts})
except Exception as e:
logger.error(f'Error getting runtime hosts: {e}')
return JSONResponse(
status_code=500,
content={
'hosts': None,
'error': f'Error getting runtime hosts: {e}',
},
)
@app.get('/events')
async def search_events(
start_id: int = 0,
end_id: int | None = None,
reverse: bool = False,
filter: EventFilter | None = None,
limit: int = 20,
conversation: ServerConversation = Depends(get_conversation),
):
"""Search through the event stream with filtering and pagination.
Args:
request: The incoming request object
start_id: Starting ID in the event stream. Defaults to 0
end_id: Ending ID in the event stream
reverse: Whether to retrieve events in reverse order. Defaults to False.
filter: Filter for events
limit: Maximum number of events to return. Must be between 1 and 100. Defaults to 20
Returns:
dict: Dictionary containing:
- events: List of matching events
- has_more: Whether there are more matching events after this batch
Raises:
HTTPException: If conversation is not found
ValueError: If limit is less than 1 or greater than 100
"""
if limit < 0 or limit > 100:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid limit'
)
# Get matching events from the stream
event_stream = conversation.event_stream
events = list(
event_stream.search_events(
start_id=start_id,
end_id=end_id,
reverse=reverse,
filter=filter,
limit=limit + 1,
)
)
# Check if there are more events
has_more = len(events) > limit
if has_more:
events = events[:limit] # Remove the extra event
events_json = [event_to_dict(event) for event in events]
return {
'events': events_json,
'has_more': has_more,
}
@app.post('/events')
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})