feat: confirmToolCall support — startToolCall with requireConfirmation + ToolRunnableCallable [JAR-9208]#703
Open
JoshParkSJ wants to merge 20 commits intomainfrom
Open
feat: confirmToolCall support — startToolCall with requireConfirmation + ToolRunnableCallable [JAR-9208]#703JoshParkSJ wants to merge 20 commits intomainfrom
JoshParkSJ wants to merge 20 commits intomainfrom
Conversation
maxduu
approved these changes
Mar 18, 2026
50d6207 to
d67cb25
Compare
f129206 to
5700f6b
Compare
3 tasks
3f4919c to
ce08e95
Compare
ce08e95 to
da450a3
Compare
Three fixes for coded agent tool confirmation: 1. hitl.py: @requires_approval now stamps REQUIRE_CONVERSATIONAL_CONFIRMATION metadata so the runtime can discover confirmation tools. 2. runtime.py: _get_tool_confirmation_info handles both graph shapes — UiPathToolNode (low-code, bound.tool) and LangGraph ToolNode (create_agent, bound.tools_by_name). 3. messages.py: seed chunk accumulator with an empty AIMessageChunk instead of the incoming message. Prevents self-merge on the first streaming chunk which doubled tool names (e.g. search_websearch_web) and broke the confirmation set lookup.
10e34fd to
1eed1e2
Compare
1eed1e2 to
f655a29
Compare
…ify graph crawling - Add ToolRunnableCallable subclass that explicitly preserves the BaseTool reference through wrap_tools_with_error_handling (replaces ad-hoc setattr) - Simplify _get_tool_confirmation_info: drop tools_by_name branch, inline logic - Add integration test that verifies confirmation discovery through the wrapper - Rename TestConfirmationToolDeferral → TestToolCallConfirmation
58b14a4 to
0b9d52a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed?
Moves tool confirmation from the old interrupt-based flow to the new
confirmToolCallevent onstartToolCall.https://uipath.atlassian.net/browse/JAR-9208
How tool confirmation discovery works
_wrap_tool_error_handlingwraps confirmation tools withConversationalToolRunnableCallable(preserves.toolreference to the underlyingBaseTool)_get_tool_confirmation_infowalks the compiled graph nodes, findsConversationalToolRunnableCallablenodes, reads their.tool.metadata, builds{tool_name: input_schema}dictAIMessage, checks the dict, emitsstartToolCallwithrequireConfirmation: trueandinputSchemaThe key constraint: CAS needs
requireConfirmationon thestartToolCallevent itself so it can render a confirmation modal instead of a spinner. This means the runtime must know which tools need confirmation before any tool node executes.Changes
hitl.py— simplified HITL approval flow:@durable_interruptinask_confirmation()now emits a plain dict instead of the oldUiPathConversationToolCallConfirmationValueinterrupt modelconfirmToolCallshape directly:{"approved": bool, "input": ...}(no more dual-payload parsing for the legacy{type, value}wrapper)@requires_approvaldecorator now setsREQUIRE_CONVERSATIONAL_CONFIRMATIONmetadata on the tool, making coded agents discoverable by the runtimeget_confirmation_schema()extracted tohitl.py— single place for "does this tool need confirmation?" logic, used by bothruntime.py(graph crawling) andtool_node.py(graph build)tool_node.py—ConversationalToolRunnableCallable:wrap_tools_with_error_handlingreplaces each tool node with aRunnableCallable, which loses the originalBaseToolreferenceREQUIRE_CONVERSATIONAL_CONFIRMATIONmetadata, it usesConversationalToolRunnableCallableinstead — a typed subclass that preserves.toolRunnableCallable(no overhead)messages.py— fix duplicate tool names in streaming + startToolCall always emitted:Bug fix:
AIMessageChunkaccumulation was doubling tool names (e.g."search_web"→"search_websearch_web"). Root cause: when the first streamed chunk arrives, it's assigned toself.current_message. If that chunk containstool_call_chunks, the accumulation line (self.current_message + message) adds the chunk to itself, and LangChain'smerge_listsconcatenates string fields. The fix skips accumulation whenself.current_message is message(identity check).Why this only surfaced now: OpenAI (
ChatOpenAI) populatestool_call_chunkswith the tool name on the very first streamed chunk. Claude (ChatAnthropic/UiPathChat) sends an empty first chunk and delivers tool call data in subsequent chunks — so the self-accumulation path was never triggered. Verified empirically with all four combinations:create_agent)startToolCallis now always emitted for confirmation tools (previously skipped). CAS readsrequireConfirmationandinputSchemafrom this event to render the confirmation UImap_tool_call_to_tool_call_start_event()acceptsrequire_confirmationandinput_schemakwargsruntime.py— confirmation metadata discovery:_get_tool_confirmation_info()walks compiled graph nodes at init to build the{tool_name: input_schema}lookup.tool) and low-code agents (multiple tools per node via.tools_by_name)Tests
test_tool_confirmation_discovery.py) verifies that_get_tool_confirmation_infodiscovers confirmation tools throughConversationalToolRunnableCallablewrappers — guards against silent regressions from LangGraph internal changes or new wrapping layersrequireConfirmationflag onstartToolCalleventsWhy rip-and-replace?
Zero production usage confirmed via App Insights telemetry (90 days, both CAS and Python agents). The
@requires_approvaldecorator ships but no deployed agent exercises it. Deployment is coordinated: CAS first, then Python. See PR descriptions on companion PRs for full analysis.Companion PRs: