From 76abdfdd0b7c35a00c9e016871312295bc98e077 Mon Sep 17 00:00:00 2001 From: Maxwell Du <60411452+maxduu@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:34:00 -0400 Subject: [PATCH] fix: remove token sub_type inspection; accept run_as_me from caller The tool factory previously inferred RunAsMe by inspecting the token's sub_type claim (== "user"), which was misclassifying tokens and preventing RunAsMe from propagating to child process / API-workflow tool invocations. create_tools_from_resources now takes an explicit run_as_me: bool parameter. Callers resolve it from UiPathRuntimeContext.conversational_run_as_me (populated from fpsProperties.conversationalService.runAsMe on the harness side). Depends on: - UiPath/uipath-runtime-python#148 (adds the context field) - UiPath/uipath-agents-python (threads the value from context to caller) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../agent/tools/tool_factory.py | 29 ++--------- tests/agent/tools/test_tool_factory.py | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/uipath_langchain/agent/tools/tool_factory.py b/src/uipath_langchain/agent/tools/tool_factory.py index f6a7fb4b7..801241357 100644 --- a/src/uipath_langchain/agent/tools/tool_factory.py +++ b/src/uipath_langchain/agent/tools/tool_factory.py @@ -31,34 +31,13 @@ logger = getLogger(__name__) -def _is_user_token() -> bool: - """Check if the current token is a user token (sub_type == 'user').""" - try: - from uipath._cli._utils._common import get_claim_from_token - - sub_type = get_claim_from_token("sub_type") - logger.info("Token sub_type=%r", sub_type) - return sub_type == "user" - except Exception as e: - logger.info("Token sub_type check failed: %s", e) - return False - - async def create_tools_from_resources( - agent: LowCodeAgentDefinition, llm: BaseChatModel + agent: LowCodeAgentDefinition, + llm: BaseChatModel, + run_as_me: bool = False, ) -> list[BaseTool]: - tools: list[BaseTool] = [] - is_user = _is_user_token() - run_as_me = agent.is_conversational and is_user - logger.info( - "RunAsMe decision: is_conversational=%s, is_user_token=%s, run_as_me=%s", - agent.is_conversational, - is_user, - run_as_me, - ) - - logger.info("Creating tools for agent '%s' from resources", agent.name) + logger.info("Creating tools for agent '%s' (run_as_me=%s)", agent.name, run_as_me) for resource in agent.resources: if not resource.is_enabled: diff --git a/tests/agent/tools/test_tool_factory.py b/tests/agent/tools/test_tool_factory.py index 051d495f6..5c2458f0a 100644 --- a/tests/agent/tools/test_tool_factory.py +++ b/tests/agent/tools/test_tool_factory.py @@ -405,3 +405,53 @@ async def test_function_resource_routes_through_process_tool_path( function_resource, run_as_me=False ) assert tool is not None + + async def test_run_as_me_propagates_to_process_tool( + self, process_resource, mock_uipath_sdk + ): + """run_as_me passed into create_tools_from_resources is forwarded to create_process_tool.""" + process_resource.is_enabled = True + agent = LowCodeAgentDefinition( + input_schema={"type": "object", "properties": {}}, + output_schema={"type": "object", "properties": {}}, + messages=[], + settings=Mock(spec=AgentSettings), + resources=[process_resource], + ) + mock_llm = AsyncMock(spec=BaseChatModel) + with patch( + "uipath_langchain.agent.tools.tool_factory.create_process_tool" + ) as mock_create_process_tool: + mock_create_process_tool.return_value = MagicMock( + spec=BaseUiPathStructuredTool + ) + await create_tools_from_resources(agent, mock_llm, run_as_me=True) + + mock_create_process_tool.assert_called_once_with( + process_resource, run_as_me=True + ) + + async def test_run_as_me_defaults_false_when_not_provided( + self, process_resource, mock_uipath_sdk + ): + """run_as_me defaults to False when omitted (non-conversational agents).""" + process_resource.is_enabled = True + agent = LowCodeAgentDefinition( + input_schema={"type": "object", "properties": {}}, + output_schema={"type": "object", "properties": {}}, + messages=[], + settings=Mock(spec=AgentSettings), + resources=[process_resource], + ) + mock_llm = AsyncMock(spec=BaseChatModel) + with patch( + "uipath_langchain.agent.tools.tool_factory.create_process_tool" + ) as mock_create_process_tool: + mock_create_process_tool.return_value = MagicMock( + spec=BaseUiPathStructuredTool + ) + await create_tools_from_resources(agent, mock_llm) + + mock_create_process_tool.assert_called_once_with( + process_resource, run_as_me=False + )