From 87f95b2af1ef6cc5d90d959c61e87d267e5e7cf7 Mon Sep 17 00:00:00 2001 From: Josh Park <50765702+JoshParkSJ@users.noreply.github.com> Date: Sat, 11 Apr 2026 23:37:22 -0400 Subject: [PATCH 1/2] fix: remove interrupt --- pyproject.toml | 2 +- src/uipath/runtime/chat/protocol.py | 14 +------------- src/uipath/runtime/chat/runtime.py | 2 -- tests/test_chat.py | 17 ----------------- uv.lock | 8 ++++---- 5 files changed, 6 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index efc3d55..7d1eb4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-runtime" -version = "0.10.0" +version = "0.10.1" description = "Runtime abstractions and interfaces for building agents and automation scripts in the UiPath ecosystem" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/src/uipath/runtime/chat/protocol.py b/src/uipath/runtime/chat/protocol.py index cc86957..f85dc83 100644 --- a/src/uipath/runtime/chat/protocol.py +++ b/src/uipath/runtime/chat/protocol.py @@ -5,7 +5,6 @@ from uipath.core.chat import ( UiPathConversationMessageEvent, ) -from uipath.core.triggers import UiPathResumeTrigger class UiPathChatProtocol(Protocol): @@ -32,17 +31,6 @@ async def emit_message_event( """ ... - async def emit_interrupt_event( - self, - resume_trigger: UiPathResumeTrigger, - ) -> None: - """Wrap and send an interrupt event. - - Args: - resume_trigger: UiPathResumeTrigger to wrap and send - """ - ... - async def emit_exchange_end_event(self) -> None: """Send an exchange end event.""" ... @@ -52,5 +40,5 @@ async def emit_exchange_error_event(self, error: Exception) -> None: ... async def wait_for_resume(self) -> dict[str, Any]: - """Wait for the interrupt_end event to be received.""" + """Wait for a confirmToolCall event to be received.""" ... diff --git a/src/uipath/runtime/chat/runtime.py b/src/uipath/runtime/chat/runtime.py index 07753a2..cf285c7 100644 --- a/src/uipath/runtime/chat/runtime.py +++ b/src/uipath/runtime/chat/runtime.py @@ -102,8 +102,6 @@ async def stream( resume_map: dict[str, Any] = {} for trigger in api_triggers: - await self.chat_bridge.emit_interrupt_event(trigger) - resume_data = ( await self.chat_bridge.wait_for_resume() ) diff --git a/tests/test_chat.py b/tests/test_chat.py index 8110ab8..e7497bc 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -33,7 +33,6 @@ def make_chat_bridge_mock() -> UiPathChatProtocol: bridge_mock.connect = AsyncMock() bridge_mock.disconnect = AsyncMock() bridge_mock.emit_message_event = AsyncMock() - bridge_mock.emit_interrupt_event = AsyncMock() bridge_mock.wait_for_resume = AsyncMock() return cast(UiPathChatProtocol, bridge_mock) @@ -331,7 +330,6 @@ async def test_chat_runtime_handles_api_trigger_suspension(): cast(AsyncMock, bridge.connect).assert_awaited_once() cast(AsyncMock, bridge.disconnect).assert_awaited_once() - cast(AsyncMock, bridge.emit_interrupt_event).assert_awaited_once() cast(AsyncMock, bridge.wait_for_resume).assert_awaited_once() # Message events emitted (one before suspend, one after resume) @@ -563,15 +561,8 @@ async def test_chat_runtime_handles_multiple_api_triggers(): assert resume_input["api-call"] == {"approved": True} # Bridge should have been called 3 times (once per trigger) - assert cast(AsyncMock, bridge.emit_interrupt_event).await_count == 3 assert cast(AsyncMock, bridge.wait_for_resume).await_count == 3 - # Verify each emit_interrupt_event received a trigger - emit_calls = cast(AsyncMock, bridge.emit_interrupt_event).await_args_list - assert emit_calls[0][0][0].interrupt_id == "email-confirm" - assert emit_calls[1][0][0].interrupt_id == "file-delete" - assert emit_calls[2][0][0].interrupt_id == "api-call" - @pytest.mark.asyncio async def test_chat_runtime_filters_non_api_triggers(): @@ -603,12 +594,4 @@ async def test_chat_runtime_filters_non_api_triggers(): assert result.triggers[0].trigger_type == UiPathResumeTriggerType.QUEUE_ITEM # Bridge should have been called only 2 times (for 2 API triggers) - assert cast(AsyncMock, bridge.emit_interrupt_event).await_count == 2 assert cast(AsyncMock, bridge.wait_for_resume).await_count == 2 - - # Verify only API triggers were emitted - emit_calls = cast(AsyncMock, bridge.emit_interrupt_event).await_args_list - assert emit_calls[0][0][0].interrupt_id == "email-confirm" - assert emit_calls[0][0][0].trigger_type == UiPathResumeTriggerType.API - assert emit_calls[1][0][0].interrupt_id == "file-delete" - assert emit_calls[1][0][0].trigger_type == UiPathResumeTriggerType.API diff --git a/uv.lock b/uv.lock index f514162..dea324b 100644 --- a/uv.lock +++ b/uv.lock @@ -991,21 +991,21 @@ wheels = [ [[package]] name = "uipath-core" -version = "0.5.2" +version = "0.5.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/17/d3/7b3cd984ca5530892a2dac945408d50f64f3f21c1fa6c5a68d9625d685f0/uipath_core-0.5.2.tar.gz", hash = "sha256:a7fd2d5c6d49117bea060c162cd380ae59fe5f2ed74bb0e4e508d51cd57e9de1", size = 119081, upload-time = "2026-02-23T10:16:48.567Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/f2/9b6d5eb0a4e5b2a4a80c777bc4b7a22efb8e343ef48766ed3ab73d6b11eb/uipath_core-0.5.11.tar.gz", hash = "sha256:9ed987360e7439f53b07e4d10d2381cacc80443f43f3fcf7f721d46ac3320c95", size = 117024, upload-time = "2026-04-06T14:31:38.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/24/3b65d78f8028b4bf49f76ed9fc08257c0941bf64fc597caf69e2c0605584/uipath_core-0.5.2-py3-none-any.whl", hash = "sha256:7a675074e2c6b2ec00995051d1c9850f6f70543cce3161b6a6b8c2911dc16ee0", size = 42844, upload-time = "2026-02-23T10:16:47.271Z" }, + { url = "https://files.pythonhosted.org/packages/68/bd/f28d89dcaec4ea040efb17ad9f8229675f293cff5f0a310351003d7f92b7/uipath_core-0.5.11-py3-none-any.whl", hash = "sha256:8c107dd9597d20c4ca7d8e770e703d36b0e21a8a1d80e13d585f125eb64d54bf", size = 43283, upload-time = "2026-04-06T14:31:36.908Z" }, ] [[package]] name = "uipath-runtime" -version = "0.10.0" +version = "0.10.1" source = { editable = "." } dependencies = [ { name = "uipath-core" }, From 05c596c8b7ea6bb2d9a4704a9acb488fd625ea29 Mon Sep 17 00:00:00 2001 From: Josh Park <50765702+JoshParkSJ@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:21:59 -0400 Subject: [PATCH 2/2] fix: rename to wait for tool confirmation --- src/uipath/runtime/chat/protocol.py | 2 +- src/uipath/runtime/chat/runtime.py | 4 +--- tests/test_chat.py | 18 +++++++++--------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/uipath/runtime/chat/protocol.py b/src/uipath/runtime/chat/protocol.py index f85dc83..9fcf379 100644 --- a/src/uipath/runtime/chat/protocol.py +++ b/src/uipath/runtime/chat/protocol.py @@ -39,6 +39,6 @@ async def emit_exchange_error_event(self, error: Exception) -> None: """Emit an exchange error event.""" ... - async def wait_for_resume(self) -> dict[str, Any]: + async def wait_for_tool_confirmation(self) -> dict[str, Any]: """Wait for a confirmToolCall event to be received.""" ... diff --git a/src/uipath/runtime/chat/runtime.py b/src/uipath/runtime/chat/runtime.py index cf285c7..2d3d68e 100644 --- a/src/uipath/runtime/chat/runtime.py +++ b/src/uipath/runtime/chat/runtime.py @@ -102,9 +102,7 @@ async def stream( resume_map: dict[str, Any] = {} for trigger in api_triggers: - resume_data = ( - await self.chat_bridge.wait_for_resume() - ) + resume_data = await self.chat_bridge.wait_for_tool_confirmation() assert trigger.interrupt_id is not None, ( "Trigger interrupt_id cannot be None" diff --git a/tests/test_chat.py b/tests/test_chat.py index e7497bc..da095d5 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -33,7 +33,7 @@ def make_chat_bridge_mock() -> UiPathChatProtocol: bridge_mock.connect = AsyncMock() bridge_mock.disconnect = AsyncMock() bridge_mock.emit_message_event = AsyncMock() - bridge_mock.wait_for_resume = AsyncMock() + bridge_mock.wait_for_tool_confirmation = AsyncMock() return cast(UiPathChatProtocol, bridge_mock) @@ -308,7 +308,7 @@ async def test_chat_runtime_handles_api_trigger_suspension(): runtime_impl = SuspendingMockRuntime(suspend_at_message=0) bridge = make_chat_bridge_mock() - cast(AsyncMock, bridge.wait_for_resume).return_value = {"approved": True} + cast(AsyncMock, bridge.wait_for_tool_confirmation).return_value = {"approved": True} chat_runtime = UiPathChatRuntime( delegate=runtime_impl, @@ -330,7 +330,7 @@ async def test_chat_runtime_handles_api_trigger_suspension(): cast(AsyncMock, bridge.connect).assert_awaited_once() cast(AsyncMock, bridge.disconnect).assert_awaited_once() - cast(AsyncMock, bridge.wait_for_resume).assert_awaited_once() + cast(AsyncMock, bridge.wait_for_tool_confirmation).assert_awaited_once() # Message events emitted (one before suspend, one after resume) assert cast(AsyncMock, bridge.emit_message_event).await_count == 2 @@ -343,8 +343,8 @@ async def test_chat_runtime_yields_events_during_suspension_flow(): runtime_impl = SuspendingMockRuntime(suspend_at_message=0) bridge = make_chat_bridge_mock() - # wait_for_resume returns approval data - cast(AsyncMock, bridge.wait_for_resume).return_value = {"approved": True} + # wait_for_tool_confirmation returns approval data + cast(AsyncMock, bridge.wait_for_tool_confirmation).return_value = {"approved": True} chat_runtime = UiPathChatRuntime( delegate=runtime_impl, @@ -531,7 +531,7 @@ async def test_chat_runtime_handles_multiple_api_triggers(): bridge = make_chat_bridge_mock() # Bridge returns approval for each trigger - cast(AsyncMock, bridge.wait_for_resume).side_effect = [ + cast(AsyncMock, bridge.wait_for_tool_confirmation).side_effect = [ {"approved": True}, # email-confirm {"approved": True}, # file-delete {"approved": True}, # api-call @@ -561,7 +561,7 @@ async def test_chat_runtime_handles_multiple_api_triggers(): assert resume_input["api-call"] == {"approved": True} # Bridge should have been called 3 times (once per trigger) - assert cast(AsyncMock, bridge.wait_for_resume).await_count == 3 + assert cast(AsyncMock, bridge.wait_for_tool_confirmation).await_count == 3 @pytest.mark.asyncio @@ -572,7 +572,7 @@ async def test_chat_runtime_filters_non_api_triggers(): bridge = make_chat_bridge_mock() # Bridge returns approval for API triggers only - cast(AsyncMock, bridge.wait_for_resume).side_effect = [ + cast(AsyncMock, bridge.wait_for_tool_confirmation).side_effect = [ {"approved": True}, # email-confirm {"approved": True}, # file-delete ] @@ -594,4 +594,4 @@ async def test_chat_runtime_filters_non_api_triggers(): assert result.triggers[0].trigger_type == UiPathResumeTriggerType.QUEUE_ITEM # Bridge should have been called only 2 times (for 2 API triggers) - assert cast(AsyncMock, bridge.wait_for_resume).await_count == 2 + assert cast(AsyncMock, bridge.wait_for_tool_confirmation).await_count == 2