From 30e52a4bf7567936e4ba5593482cfca7f53e7dc0 Mon Sep 17 00:00:00 2001 From: VascoSch92 Date: Wed, 27 May 2026 11:35:06 +0200 Subject: [PATCH] fix(sdk): override RemoteConversation.interrupt() to POST /interrupt RemoteConversation did not override interrupt(), so it inherited BaseConversation.interrupt(), whose default falls back to pause(). Remote callers invoking interrupt() therefore got pause semantics (wait for the in-flight LLM call) instead of an immediate cancel. The server already exposes POST /conversations/{id}/interrupt, so override interrupt() to POST to it, mirroring pause(). --- .../conversation/impl/remote_conversation.py | 7 +++++ .../remote/test_remote_conversation.py | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py b/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py index 4b40f357c9..a7328af06e 100644 --- a/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py +++ b/openhands-sdk/openhands/sdk/conversation/impl/remote_conversation.py @@ -1230,6 +1230,13 @@ def pause(self) -> None: f"{self._conversation_action_base_path}/{self._id}/pause", ) + def interrupt(self) -> None: + _send_request( + self._client, + "POST", + f"{self._conversation_action_base_path}/{self._id}/interrupt", + ) + def update_secrets(self, secrets: Mapping[str, SecretValue]) -> None: from openhands.sdk.secret.secrets import SecretSource diff --git a/tests/sdk/conversation/remote/test_remote_conversation.py b/tests/sdk/conversation/remote/test_remote_conversation.py index 4a4de21c81..53d14f7a6f 100644 --- a/tests/sdk/conversation/remote/test_remote_conversation.py +++ b/tests/sdk/conversation/remote/test_remote_conversation.py @@ -1085,6 +1085,32 @@ def test_remote_conversation_pause(self, mock_ws_client): ] assert len(request_calls) >= 1, "Should have made a POST call to pause endpoint" + @patch( + "openhands.sdk.conversation.impl.remote_conversation.WebSocketCallbackClient" + ) + def test_remote_conversation_interrupt(self, mock_ws_client): + """interrupt() must POST to /interrupt, not degrade to /pause.""" + conversation_id = str(uuid.uuid4()) + mock_client_instance = self.setup_mock_client(conversation_id=conversation_id) + + mock_ws_instance = Mock() + mock_ws_client.return_value = mock_ws_instance + + conversation = RemoteConversation(agent=self.agent, workspace=self.workspace) + conversation.interrupt() + + posts = [ + call[0][1] + for call in mock_client_instance.request.call_args_list + if call[0][0] == "POST" + ] + assert any( + f"/api/conversations/{conversation_id}/interrupt" in url for url in posts + ), "Should have made a POST call to interrupt endpoint" + assert not any( + f"/api/conversations/{conversation_id}/pause" in url for url in posts + ), "interrupt() must not degrade to the pause endpoint" + @patch( "openhands.sdk.conversation.impl.remote_conversation.WebSocketCallbackClient" )