Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/uipath/_cli/_chat/_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_SYNTHETIC_USER_ID,
)
from uipath.core.chat import (
UiPathConversationEvent,
UiPathConversationExchangeEndEvent,
Expand Down Expand Up @@ -401,13 +405,25 @@ 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)
synthetic_user_id = os.environ.get(ENV_SYNTHETIC_USER_ID)

# 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 "",
"syntheticUserId": synthetic_user_id 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"):
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}'."
Expand All @@ -420,13 +436,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,
)
Expand Down
1 change: 1 addition & 0 deletions src/uipath/_utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
20 changes: 20 additions & 0 deletions tests/cli/chat/test_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,26 @@ 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_synthetic_user_id_when_set(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""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_SYNTHETIC_USER_ID", "synthetic-user-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 "syntheticUserId=synthetic-user-guid-789" in bridge.websocket_url
assert "jobKey" not in bridge.websocket_url
assert "robotKey" not in bridge.websocket_url


class TestGetChatBridge:
"""Tests for get_chat_bridge factory function."""
Expand Down