From 0be115013ec6ae1f4a9de17d1a4a9898df3560b4 Mon Sep 17 00:00:00 2001 From: Christin <164907691+scottcmg@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:48:09 -0800 Subject: [PATCH 1/3] feat(chat): add keys to bridge --- src/uipath/_cli/_chat/_bridge.py | 26 ++++++++++++++++++++------ tests/cli/chat/test_bridge.py | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/uipath/_cli/_chat/_bridge.py b/src/uipath/_cli/_chat/_bridge.py index a454afa17..f1ae369c2 100644 --- a/src/uipath/_cli/_chat/_bridge.py +++ b/src/uipath/_cli/_chat/_bridge.py @@ -6,8 +6,12 @@ import os import uuid from typing import Any -from urllib.parse import urlparse +from urllib.parse import urlencode, urlparse +from uipath._utils.constants import ( + ENV_FOLDER_KEY, + ENV_JOB_KEY, +) from uipath.core.chat import ( UiPathConversationEvent, UiPathConversationExchangeEndEvent, @@ -389,7 +393,6 @@ def get_chat_bridge( assert context.conversation_id is not None, "conversation_id must be set in context" assert context.exchange_id is not None, "exchange_id must be set in context" - # Extract host from UIPATH_URL base_url = os.environ.get("UIPATH_URL") if not base_url: raise RuntimeError( @@ -401,9 +404,20 @@ def get_chat_bridge( raise RuntimeError(f"Invalid UIPATH_URL format: {base_url}") host = parsed.netloc + conversation_id = context.conversation_id + folder_key = os.environ.get(ENV_FOLDER_KEY) + job_key = os.environ.get(ENV_JOB_KEY) + + # Build query params for CAS for use verifying the requesting agent should have access when runAsMe=False + query_params: dict[str, str] = { + "conversationId": conversation_id, + "folderKey": folder_key or "", + "jobKey": job_key or "", + } + query_params = {k: v for k, v in query_params.items() if v} + query_string = urlencode(query_params) - # Construct WebSocket URL for CAS - websocket_url = f"wss://{host}?conversationId={context.conversation_id}" + websocket_url = f"wss://{host}?{query_string}" websocket_path = "autopilotforeveryone_/websocket_/socket.io" if os.environ.get("CAS_WEBSOCKET_HOST"): @@ -420,13 +434,13 @@ def get_chat_bridge( or os.environ.get("UIPATH_TENANT_ID", ""), "X-UiPath-Internal-AccountId": f"{context.org_id}" or os.environ.get("UIPATH_ORGANIZATION_ID", ""), - "X-UiPath-ConversationId": context.conversation_id, + "X-UiPath-ConversationId": conversation_id, } return SocketIOChatBridge( websocket_url=websocket_url, websocket_path=websocket_path, - conversation_id=context.conversation_id, + conversation_id=conversation_id, exchange_id=context.exchange_id, headers=headers, ) diff --git a/tests/cli/chat/test_bridge.py b/tests/cli/chat/test_bridge.py index 2da4f31ad..6b124abf4 100644 --- a/tests/cli/chat/test_bridge.py +++ b/tests/cli/chat/test_bridge.py @@ -165,6 +165,25 @@ def test_get_chat_bridge_includes_conversation_id_in_url( assert "conversationId=my-conversation-id" in bridge.websocket_url + def test_get_chat_bridge_includes_folder_key_job_key_when_set( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + """folderKey and jobKey are included in URL when env vars are set (CAS contract).""" + monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com/org/tenant") + monkeypatch.setenv("UIPATH_ACCESS_TOKEN", "test-token") + monkeypatch.setenv("UIPATH_FOLDER_KEY", "folder-guid-456") + monkeypatch.setenv("UIPATH_JOB_KEY", "job-guid-789") + monkeypatch.delenv("CAS_WEBSOCKET_HOST", raising=False) + + context = MockRuntimeContext(conversation_id="conv-id") + + bridge = cast(SocketIOChatBridge, get_chat_bridge(cast(Any, context))) + + assert "conversationId=conv-id" in bridge.websocket_url + assert "folderKey=folder-guid-456" in bridge.websocket_url + assert "jobKey=job-guid-789" in bridge.websocket_url + assert "robotKey" not in bridge.websocket_url + class TestGetChatBridge: """Tests for get_chat_bridge factory function.""" From 98964ba6b1da0017aa0ab679d1c5d7f7da2dc708 Mon Sep 17 00:00:00 2001 From: Christin <164907691+scottcmg@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:15:50 -0800 Subject: [PATCH 2/3] restore comments --- src/uipath/_cli/_chat/_bridge.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/uipath/_cli/_chat/_bridge.py b/src/uipath/_cli/_chat/_bridge.py index f1ae369c2..19d64b719 100644 --- a/src/uipath/_cli/_chat/_bridge.py +++ b/src/uipath/_cli/_chat/_bridge.py @@ -393,6 +393,7 @@ def get_chat_bridge( assert context.conversation_id is not None, "conversation_id must be set in context" assert context.exchange_id is not None, "exchange_id must be set in context" + # Extract host from UIPATH_URL base_url = os.environ.get("UIPATH_URL") if not base_url: raise RuntimeError( @@ -417,6 +418,7 @@ def get_chat_bridge( query_params = {k: v for k, v in query_params.items() if v} query_string = urlencode(query_params) + # Construct WebSocket URL for CAS websocket_url = f"wss://{host}?{query_string}" websocket_path = "autopilotforeveryone_/websocket_/socket.io" From 08a5fe25b24f21ea552e834d93f4253b0c7611ad Mon Sep 17 00:00:00 2001 From: Christin <164907691+scottcmg@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:34:22 -0700 Subject: [PATCH 3/3] change to synthetic id --- src/uipath/_cli/_chat/_bridge.py | 10 +++++----- src/uipath/_utils/constants.py | 1 + tests/cli/chat/test_bridge.py | 9 +++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/uipath/_cli/_chat/_bridge.py b/src/uipath/_cli/_chat/_bridge.py index 19d64b719..576cfca36 100644 --- a/src/uipath/_cli/_chat/_bridge.py +++ b/src/uipath/_cli/_chat/_bridge.py @@ -10,7 +10,7 @@ from uipath._utils.constants import ( ENV_FOLDER_KEY, - ENV_JOB_KEY, + ENV_SYNTHETIC_USER_ID, ) from uipath.core.chat import ( UiPathConversationEvent, @@ -407,13 +407,13 @@ def get_chat_bridge( host = parsed.netloc conversation_id = context.conversation_id folder_key = os.environ.get(ENV_FOLDER_KEY) - job_key = os.environ.get(ENV_JOB_KEY) + synthetic_user_id = os.environ.get(ENV_SYNTHETIC_USER_ID) - # Build query params for CAS for use verifying the requesting agent should have access when runAsMe=False + # Build query params for CAS: conversationId, folderKey, syntheticUserId (RunAsMe=false validation per CAS contract) query_params: dict[str, str] = { "conversationId": conversation_id, "folderKey": folder_key or "", - "jobKey": job_key or "", + "syntheticUserId": synthetic_user_id or "", } query_params = {k: v for k, v in query_params.items() if v} query_string = urlencode(query_params) @@ -423,7 +423,7 @@ def get_chat_bridge( websocket_path = "autopilotforeveryone_/websocket_/socket.io" if os.environ.get("CAS_WEBSOCKET_HOST"): - websocket_url = f"ws://{os.environ.get('CAS_WEBSOCKET_HOST')}?conversationId={context.conversation_id}" + websocket_url = f"ws://{os.environ.get('CAS_WEBSOCKET_HOST')}?{query_string}" websocket_path = "/socket.io" logger.warning( f"CAS_WEBSOCKET_HOST is set. Using websocket_url '{websocket_url}{websocket_path}'." diff --git a/src/uipath/_utils/constants.py b/src/uipath/_utils/constants.py index bb0f08aba..18918492c 100644 --- a/src/uipath/_utils/constants.py +++ b/src/uipath/_utils/constants.py @@ -7,6 +7,7 @@ ENV_FOLDER_KEY = "UIPATH_FOLDER_KEY" ENV_FOLDER_PATH = "UIPATH_FOLDER_PATH" ENV_JOB_KEY = "UIPATH_JOB_KEY" +ENV_SYNTHETIC_USER_ID = "UIPATH_SYNTHETIC_USER_ID" ENV_JOB_ID = "UIPATH_JOB_ID" ENV_ROBOT_KEY = "UIPATH_ROBOT_KEY" ENV_TENANT_ID = "UIPATH_TENANT_ID" diff --git a/tests/cli/chat/test_bridge.py b/tests/cli/chat/test_bridge.py index 6b124abf4..50cacd45b 100644 --- a/tests/cli/chat/test_bridge.py +++ b/tests/cli/chat/test_bridge.py @@ -165,14 +165,14 @@ def test_get_chat_bridge_includes_conversation_id_in_url( assert "conversationId=my-conversation-id" in bridge.websocket_url - def test_get_chat_bridge_includes_folder_key_job_key_when_set( + def test_get_chat_bridge_includes_folder_key_synthetic_user_id_when_set( self, monkeypatch: pytest.MonkeyPatch ) -> None: - """folderKey and jobKey are included in URL when env vars are set (CAS contract).""" + """folderKey and syntheticUserId are included in URL when env vars are set (CAS RunAsMe=false contract).""" monkeypatch.setenv("UIPATH_URL", "https://cloud.uipath.com/org/tenant") monkeypatch.setenv("UIPATH_ACCESS_TOKEN", "test-token") monkeypatch.setenv("UIPATH_FOLDER_KEY", "folder-guid-456") - monkeypatch.setenv("UIPATH_JOB_KEY", "job-guid-789") + monkeypatch.setenv("UIPATH_SYNTHETIC_USER_ID", "synthetic-user-guid-789") monkeypatch.delenv("CAS_WEBSOCKET_HOST", raising=False) context = MockRuntimeContext(conversation_id="conv-id") @@ -181,7 +181,8 @@ def test_get_chat_bridge_includes_folder_key_job_key_when_set( assert "conversationId=conv-id" in bridge.websocket_url assert "folderKey=folder-guid-456" in bridge.websocket_url - assert "jobKey=job-guid-789" in bridge.websocket_url + assert "syntheticUserId=synthetic-user-guid-789" in bridge.websocket_url + assert "jobKey" not in bridge.websocket_url assert "robotKey" not in bridge.websocket_url