# pyright: reportIncompatibleMethodOverride=false from __future__ import annotations import logging from abc import ABC, abstractmethod from datetime import datetime from typing import TYPE_CHECKING, Literal from uuid import UUID, uuid4 from pydantic import Field from openhands.agent_server.utils import utc_now from openhands.app_server.event_callback.event_callback_result_models import ( EventCallbackResult, EventCallbackResultStatus, ) from openhands.sdk import Event from openhands.sdk.utils.models import ( DiscriminatedUnionMixin, OpenHandsModel, get_known_concrete_subclasses, ) _logger = logging.getLogger(__name__) if TYPE_CHECKING: EventKind = str else: EventKind = Literal[tuple(c.__name__ for c in get_known_concrete_subclasses(Event))] class EventCallbackProcessor(DiscriminatedUnionMixin, ABC): @abstractmethod async def __call__( self, conversation_id: UUID, callback: EventCallback, event: Event, ) -> EventCallbackResult: """Process an event.""" class LoggingCallbackProcessor(EventCallbackProcessor): """Example implementation which logs callbacks.""" async def __call__( self, conversation_id: UUID, callback: EventCallback, event: Event, ) -> EventCallbackResult: _logger.info(f'Callback {callback.id} Invoked for event {event}') return EventCallbackResult( status=EventCallbackResultStatus.SUCCESS, event_callback_id=callback.id, event_id=event.id, conversation_id=conversation_id, ) class CreateEventCallbackRequest(OpenHandsModel): conversation_id: UUID | None = Field( default=None, description=( 'Optional filter on the conversation to which this callback applies' ), ) processor: EventCallbackProcessor event_kind: EventKind | None = Field( default=None, description=( 'Optional filter on the type of events to which this callback applies' ), ) class EventCallback(CreateEventCallbackRequest): id: UUID = Field(default_factory=uuid4) created_at: datetime = Field(default_factory=utc_now) class EventCallbackPage(OpenHandsModel): items: list[EventCallback] next_page_id: str | None = None