mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
269 lines
8.9 KiB
Python
269 lines
8.9 KiB
Python
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
|
|
from openhands.events.event_store import EventStore
|
|
from openhands.events.serialization.event import event_to_dict
|
|
from openhands.memory.memory import Memory
|
|
from openhands.microagent.types import InputMetadata
|
|
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, file_store
|
|
from openhands.server.user_auth import get_user_id
|
|
from openhands.server.utils import get_conversation, get_conversation_metadata
|
|
from openhands.storage.data_models.conversation_metadata import ConversationMetadata
|
|
|
|
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(
|
|
conversation_id: str,
|
|
start_id: int = 0,
|
|
end_id: int | None = None,
|
|
reverse: bool = False,
|
|
filter: EventFilter | None = None,
|
|
limit: int = 20,
|
|
metadata: ConversationMetadata = Depends(get_conversation_metadata),
|
|
user_id: str | None = Depends(get_user_id),
|
|
):
|
|
"""Search through the event stream with filtering and pagination.
|
|
Args:
|
|
conversation_id: The conversation ID
|
|
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
|
|
metadata: Conversation metadata (injected by dependency)
|
|
user_id: User ID (injected by dependency)
|
|
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 or access is denied
|
|
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'
|
|
)
|
|
|
|
# Create an event store to access the events directly
|
|
event_store = EventStore(
|
|
sid=conversation_id,
|
|
file_store=file_store,
|
|
user_id=user_id,
|
|
)
|
|
|
|
# Get matching events from the store
|
|
events = list(
|
|
event_store.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 = await request.json()
|
|
await conversation_manager.send_event_to_conversation(conversation.sid, data)
|
|
return JSONResponse({'success': True})
|
|
|
|
|
|
class MicroagentResponse(BaseModel):
|
|
"""Response model for microagents endpoint."""
|
|
|
|
name: str
|
|
type: str
|
|
content: str
|
|
triggers: list[str] = []
|
|
inputs: list[InputMetadata] = []
|
|
tools: 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: Memory | None = agent_session.memory
|
|
if memory is None:
|
|
return JSONResponse(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
content={
|
|
'error': 'Memory is not yet initialized for this conversation'
|
|
},
|
|
)
|
|
|
|
# Prepare the response
|
|
microagents = []
|
|
|
|
# Add repo microagents
|
|
for name, r_agent in memory.repo_microagents.items():
|
|
microagents.append(
|
|
MicroagentResponse(
|
|
name=name,
|
|
type='repo',
|
|
content=r_agent.content,
|
|
triggers=[],
|
|
inputs=r_agent.metadata.inputs,
|
|
tools=[
|
|
server.name
|
|
for server in r_agent.metadata.mcp_tools.stdio_servers
|
|
]
|
|
if r_agent.metadata.mcp_tools
|
|
else [],
|
|
)
|
|
)
|
|
|
|
# Add knowledge microagents
|
|
for name, k_agent in memory.knowledge_microagents.items():
|
|
microagents.append(
|
|
MicroagentResponse(
|
|
name=name,
|
|
type='knowledge',
|
|
content=k_agent.content,
|
|
triggers=k_agent.triggers,
|
|
inputs=k_agent.metadata.inputs,
|
|
tools=[
|
|
server.name
|
|
for server in k_agent.metadata.mcp_tools.stdio_servers
|
|
]
|
|
if k_agent.metadata.mcp_tools
|
|
else [],
|
|
)
|
|
)
|
|
|
|
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}'},
|
|
)
|