diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py index 9163d2511..34165a774 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py @@ -411,6 +411,23 @@ def __call(func: RouteHandler[StateT]) -> RouteHandler[StateT]: return __call + def end_of_conversation( + self, + *, + auth_handlers: list[str] | None = None, + **kwargs, + ) -> _RouteDecorator[RouteHandler[StateT]]: + """ + Register a new end-of-conversation activity event listener as either a decorator or a method. + + :param auth_handlers: Optional list of authorization handler IDs for the route. + :type auth_handlers: Optional[list[str]] + :param kwargs: Additional route configuration passed to :meth:`microsoft_agents.hosting.core.AgentApplication.add_route`. + """ + return self.activity( + ActivityTypes.end_of_conversation, auth_handlers=auth_handlers, **kwargs + ) + def message( self, select: str | re.Pattern[str] | list[str | re.Pattern[str]], diff --git a/tests/hosting_core/app/test_agent_application_routes.py b/tests/hosting_core/app/test_agent_application_routes.py index fcb6548db..412300001 100644 --- a/tests/hosting_core/app/test_agent_application_routes.py +++ b/tests/hosting_core/app/test_agent_application_routes.py @@ -137,6 +137,54 @@ async def handler(context: TurnContext, state: TurnState): assert result is handler +class TestEndOfConversationRoute: + def setup_method(self): + self.app = _make_app() + self.called = False + self.received_code: str | None = None + + @pytest.mark.asyncio + async def test_routes_end_of_conversation_activity(self): + @self.app.end_of_conversation() + async def handler(context: TurnContext, state: TurnState): + self.called = True + self.received_code = context.activity.code + + await self.app._on_activity( + _make_context( + _make_activity( + type=ActivityTypes.end_of_conversation, + code="completedSuccessfully", + ) + ), + TurnState(), + ) + + assert self.called + assert self.received_code == "completedSuccessfully" + + @pytest.mark.asyncio + async def test_does_not_match_other_activity_types(self): + @self.app.end_of_conversation() + async def handler(context: TurnContext, state: TurnState): + self.called = True + + await self.app._on_activity( + _make_context(_make_activity(type=ActivityTypes.message)), + TurnState(), + ) + + assert not self.called + + def test_decorator_returns_original_handler(self): + async def handler(context: TurnContext, state: TurnState): + pass + + result = self.app.end_of_conversation()(handler) + + assert result is handler + + class TestMessageRoute: def setup_method(self): self.app = _make_app()