conversation.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. from fastapi import APIRouter, HTTPException, Request, status
  2. from fastapi.responses import JSONResponse
  3. from openhands.core.logger import openhands_logger as logger
  4. from openhands.runtime.base import Runtime
  5. app = APIRouter(prefix='/api')
  6. @app.get('/conversation')
  7. async def get_remote_runtime_config(request: Request):
  8. """Retrieve the runtime configuration.
  9. Currently, this is the session ID and runtime ID (if available).
  10. """
  11. runtime = request.state.conversation.runtime
  12. runtime_id = runtime.runtime_id if hasattr(runtime, 'runtime_id') else None
  13. session_id = runtime.sid if hasattr(runtime, 'sid') else None
  14. return JSONResponse(
  15. content={
  16. 'runtime_id': runtime_id,
  17. 'session_id': session_id,
  18. }
  19. )
  20. @app.get('/vscode-url')
  21. async def get_vscode_url(request: Request):
  22. """Get the VSCode URL.
  23. This endpoint allows getting the VSCode URL.
  24. Args:
  25. request (Request): The incoming FastAPI request object.
  26. Returns:
  27. JSONResponse: A JSON response indicating the success of the operation.
  28. """
  29. try:
  30. runtime: Runtime = request.state.conversation.runtime
  31. logger.debug(f'Runtime type: {type(runtime)}')
  32. logger.debug(f'Runtime VSCode URL: {runtime.vscode_url}')
  33. return JSONResponse(status_code=200, content={'vscode_url': runtime.vscode_url})
  34. except Exception as e:
  35. logger.error(f'Error getting VSCode URL: {e}', exc_info=True)
  36. return JSONResponse(
  37. status_code=500,
  38. content={
  39. 'vscode_url': None,
  40. 'error': f'Error getting VSCode URL: {e}',
  41. },
  42. )
  43. @app.get('/events/search')
  44. async def search_events(
  45. request: Request,
  46. query: str | None = None,
  47. start_id: int = 0,
  48. limit: int = 20,
  49. event_type: str | None = None,
  50. source: str | None = None,
  51. start_date: str | None = None,
  52. end_date: str | None = None,
  53. ):
  54. """Search through the event stream with filtering and pagination.
  55. Args:
  56. request (Request): The incoming request object
  57. query (str, optional): Text to search for in event content
  58. start_id (int): Starting ID in the event stream. Defaults to 0
  59. limit (int): Maximum number of events to return. Must be between 1 and 100. Defaults to 20
  60. event_type (str, optional): Filter by event type (e.g., "FileReadAction")
  61. source (str, optional): Filter by event source
  62. start_date (str, optional): Filter events after this date (ISO format)
  63. end_date (str, optional): Filter events before this date (ISO format)
  64. Returns:
  65. dict: Dictionary containing:
  66. - events: List of matching events
  67. - has_more: Whether there are more matching events after this batch
  68. Raises:
  69. HTTPException: If conversation is not found
  70. ValueError: If limit is less than 1 or greater than 100
  71. """
  72. if not request.state.conversation:
  73. raise HTTPException(
  74. status_code=status.HTTP_404_NOT_FOUND, detail='Conversation not found'
  75. )
  76. # Get matching events from the stream
  77. event_stream = request.state.conversation.event_stream
  78. matching_events = event_stream.get_matching_events(
  79. query=query,
  80. event_type=event_type,
  81. source=source,
  82. start_date=start_date,
  83. end_date=end_date,
  84. start_id=start_id,
  85. limit=limit + 1, # Get one extra to check if there are more
  86. )
  87. # Check if there are more events
  88. has_more = len(matching_events) > limit
  89. if has_more:
  90. matching_events = matching_events[:limit] # Remove the extra event
  91. return {
  92. 'events': matching_events,
  93. 'has_more': has_more,
  94. }