feat: add client side tools to bridge and new cas events#1609
feat: add client side tools to bridge and new cas events#1609norman-le wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds support for “client-side tools” in agent resource modeling and extends the chat event protocol/bridge to handle additional tool-call lifecycle events (including an executingToolCall signal and new CAS event handling paths).
Changes:
- Add
ClientSideas a newAgentToolTypeand introduceAgentClientSideToolResourceConfig, including normalization support. - Extend core chat tool-call event models with
executingToolCalland additional start-event fields (isClientSideTool,outputSchema). - Update the CLI Socket.IO chat bridge to parse
endToolCallas a resume signal and add an emitter forexecutingToolCall.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| packages/uipath/src/uipath/agent/models/agent.py | Adds a new agent tool type + resource config union member and normalizes incoming tool type strings for client-side tools. |
| packages/uipath/src/uipath/_cli/_chat/_bridge.py | Adds support for new CAS tool-call event shapes and introduces an executingToolCall emitter based on resume triggers. |
| packages/uipath-core/src/uipath/core/chat/tool.py | Extends the chat protocol models with executingToolCall and new start-event fields to represent client-side tools. |
| packages/uipath-core/src/uipath/core/chat/init.py | Exports the newly added UiPathConversationExecutingToolCallEvent. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| tool_call_id = request.get("tool_call_id") | ||
| tool_name = request.get("tool_name") | ||
| tool_input = request.get("input") | ||
|
|
||
| if not tool_call_id or not tool_name: | ||
| logger.info( | ||
| f"emit_executing_tool_call_event: missing tool_call_id or tool_name, skipping. tool_call_id={tool_call_id}, tool_name={tool_name}" | ||
| ) | ||
| return | ||
|
|
||
| executing_event = UiPathConversationMessageEvent( | ||
| message_id=self._current_message_id, | ||
| tool_call=UiPathConversationToolCallEvent( | ||
| tool_call_id=tool_call_id, | ||
| executing=UiPathConversationExecutingToolCallEvent( | ||
| tool_name=tool_name, | ||
| input=tool_input, | ||
| ), |
| self._tool_confirmation_event = asyncio.Event() | ||
| self._tool_confirmation_value: ( | ||
| UiPathConversationToolCallConfirmationEvent | None | ||
| UiPathConversationToolCallConfirmationEvent | ||
| | UiPathConversationToolCallEndEvent | ||
| | None | ||
| ) = None |
| class AgentClientSideToolResourceConfig(BaseAgentToolResourceConfig): | ||
| """Resource config for client-side tools executed by the client SDK.""" | ||
|
|
||
| type: Literal[AgentToolType.CLIENT_SIDE] = AgentToolType.CLIENT_SIDE | ||
| properties: BaseResourceProperties = Field(default_factory=BaseResourceProperties) | ||
| output_schema: Optional[Dict[str, Any]] = Field(None, alias="outputSchema") | ||
| arguments: Optional[Dict[str, Any]] = Field(default_factory=dict) | ||
|
|
| class AgentClientSideToolResourceConfig(BaseAgentToolResourceConfig): | ||
| """Resource config for client-side tools executed by the client SDK.""" | ||
|
|
||
| type: Literal[AgentToolType.CLIENT_SIDE] = AgentToolType.CLIENT_SIDE |
There was a problem hiding this comment.
Can we add an example maybe in agentinterfaces what an example client side tool would look like?
|
| async def emit_interrupt_event(self, resume_trigger: UiPathResumeTrigger): | ||
| """No-op. | ||
|
|
||
| Tool confirmation — the only interrupt pattern CAS uses today — is | ||
| handled end-to-end via ``startToolCall`` with ``requireConfirmation: | ||
| true`` paired with ``wait_for_resume()``. This is deliberately | ||
| simpler than the old interrupt-based flow: CAS needs | ||
| ``requireConfirmation`` on the tool call event itself to render the | ||
| confirmation UI, so a parallel ``startInterrupt`` event would be | ||
| redundant. | ||
|
|
||
| The only hypothetical reason to put work here is a generic, | ||
| non-tool-call agent interrupt (e.g. a coded agent calling | ||
| ``interrupt("do you want to continue?")``). Nothing uses that today | ||
| and it's not a near-term requirement — the method is kept for | ||
| generic flexibility. | ||
| """Emit an executingToolCall event if the trigger is marked with is_execution_phase. |
There was a problem hiding this comment.
@JoshParkSJ Could you check this - I thought emit_interrupt_event should be kept a no-op? And maybe a separate function to emit conversational tool-call-executing events? Since we don't know if every emit_interrupt_event would be a tool-call, right
There was a problem hiding this comment.
I think it was a no-op since Josh moved it to use the requireConfirmation property instead, and Pufu didn't want to remove the emit interrupt event (for cases like this where we might need it).
I had another PR to modify the uipath-runtime-python but he told me to use the existing one as it was essentially emitting an interrupt on a trigger. But let me know if im misunderstanding that @JoshParkSJ



Videos in CAS localhost (Agent Builder changes already published, but this will be the debug experience as well when the sdk is upgraded):
Without tool confirmation for client side tool:
Screen.Recording.2026-05-13.at.9.32.26.PM.mov
With tool confirmation for client side tool:
Screen.Recording.2026-05-13.at.9.27.56.PM.mov
URT Eval:

Related to changes in other PRs:
CAS: https://github.com/UiPath/AgentInterfaces/pull/949
uipath-langchain-python (tool definition): UiPath/uipath-langchain-python#819
uipath-python (bridge changes): #1609
uipath-agents-python (skipping for evals, so it can be mocked): https://github.com/UiPath/uipath-agents-python/pull/485
Another Agents PR fixing some issues with debug: https://github.com/UiPath/Agents/pull/5250