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
2 changes: 1 addition & 1 deletion packages/uipath-platform/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-platform"
version = "0.1.74"
version = "0.1.75"
description = "HTTP client library for programmatic access to UiPath Platform"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from ..common._endpoints_manager import EndpointManager
from ..common._execution_context import UiPathExecutionContext
from ..common._models import Endpoint
from ..common.constants import HEADER_AGENTHUB_CONFIG
from .llm_gateway import (
ChatCompletion,
SpecificToolChoice,
Expand Down Expand Up @@ -59,7 +60,7 @@ def _build_llm_headers(
"X-UiPath-LlmGateway-RequestingFeature": requesting_feature,
}
if agenthub_config:
headers["X-UiPath-AgentHub-Config"] = agenthub_config
headers[HEADER_AGENTHUB_CONFIG] = agenthub_config
if action_id:
headers["X-UiPath-LlmGateway-ActionId"] = action_id
return headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import os

from ._config import UiPathConfig
from .constants import HEADER_INTERNAL_ACCOUNT_ID, HEADER_INTERNAL_TENANT_ID


def resolve_service_url(endpoint_path: str) -> str | None:
Expand Down Expand Up @@ -57,8 +58,8 @@ def inject_routing_headers(headers: dict[str, str]) -> None:
"""
tenant_id = UiPathConfig.tenant_id
if tenant_id:
headers["X-UiPath-Internal-TenantId"] = tenant_id
headers[HEADER_INTERNAL_TENANT_ID] = tenant_id

organization_id = UiPathConfig.organization_id
if organization_id:
headers["X-UiPath-Internal-AccountId"] = organization_id
headers[HEADER_INTERNAL_ACCOUNT_ID] = organization_id
29 changes: 19 additions & 10 deletions packages/uipath-platform/src/uipath/platform/common/_span_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
from pydantic import BaseModel, ConfigDict, Field
from uipath.core.serialization import serialize_json

from .constants import (
ENV_FOLDER_KEY,
ENV_JOB_KEY,
ENV_ORGANIZATION_ID,
ENV_PROCESS_KEY,
ENV_TENANT_ID,
ENV_UIPATH_PROCESS_UUID,
ENV_UIPATH_PROCESS_VERSION,
ENV_UIPATH_TRACE_ID,
)

logger = logging.getLogger(__name__)

# SourceEnum.CodedAgents = 10 (default for Python SDK / coded agents)
Expand Down Expand Up @@ -110,25 +121,23 @@ class UiPathSpan:
created_at: str = field(default_factory=lambda: datetime.now().isoformat() + "Z")
updated_at: str = field(default_factory=lambda: datetime.now().isoformat() + "Z")
organization_id: Optional[str] = field(
default_factory=lambda: env.get("UIPATH_ORGANIZATION_ID", "")
)
tenant_id: Optional[str] = field(
default_factory=lambda: env.get("UIPATH_TENANT_ID", "")
default_factory=lambda: env.get(ENV_ORGANIZATION_ID, "")
)
tenant_id: Optional[str] = field(default_factory=lambda: env.get(ENV_TENANT_ID, ""))
expiry_time_utc: Optional[str] = None
folder_key: Optional[str] = field(
default_factory=lambda: env.get("UIPATH_FOLDER_KEY", "")
default_factory=lambda: env.get(ENV_FOLDER_KEY, "")
)
source: int = DEFAULT_SOURCE
span_type: str = "Coded Agents"
process_key: Optional[str] = field(
default_factory=lambda: env.get("UIPATH_PROCESS_UUID")
default_factory=lambda: env.get(ENV_UIPATH_PROCESS_UUID)
)
reference_id: Optional[str] = field(
default_factory=lambda: env.get("TRACE_REFERENCE_ID")
)

job_key: Optional[str] = field(default_factory=lambda: env.get("UIPATH_JOB_KEY"))
job_key: Optional[str] = field(default_factory=lambda: env.get(ENV_JOB_KEY))

# Top-level fields for internal tracing schema
execution_type: Optional[int] = None
Expand Down Expand Up @@ -243,7 +252,7 @@ def otel_span_to_uipath_span(
span_id = format(span_context.span_id, "016x")

# Override trace_id if custom or env var provided (supports both UUID and hex format)
trace_id_override = custom_trace_id or os.environ.get("UIPATH_TRACE_ID")
trace_id_override = custom_trace_id or os.environ.get(ENV_UIPATH_TRACE_ID)
if trace_id_override:
trace_id = _SpanUtils.normalize_trace_id(trace_id_override)

Expand Down Expand Up @@ -322,8 +331,8 @@ def otel_span_to_uipath_span(

# Add process context attributes from environment variables
for env_key, attr_key in (
("UIPATH_PROCESS_KEY", "agentName"),
("UIPATH_PROCESS_VERSION", "agentVersion"),
(ENV_PROCESS_KEY, "agentName"),
(ENV_UIPATH_PROCESS_VERSION, "agentVersion"),
):
value = env.get(env_key)
if value:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
inject_routing_headers,
resolve_service_url,
)
from uipath.platform.common.constants import (
HEADER_INTERNAL_ACCOUNT_ID,
HEADER_INTERNAL_TENANT_ID,
)


class TestResolveServiceUrl:
Expand Down Expand Up @@ -68,16 +72,16 @@ def test_injects_tenant_and_org(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("UIPATH_ORGANIZATION_ID", "org-456")
headers: dict[str, str] = {}
inject_routing_headers(headers)
assert headers["X-UiPath-Internal-TenantId"] == "tenant-123"
assert headers["X-UiPath-Internal-AccountId"] == "org-456"
assert headers[HEADER_INTERNAL_TENANT_ID] == "tenant-123"
assert headers[HEADER_INTERNAL_ACCOUNT_ID] == "org-456"

def test_skips_missing_env_vars(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("UIPATH_TENANT_ID", raising=False)
monkeypatch.delenv("UIPATH_ORGANIZATION_ID", raising=False)
headers: dict[str, str] = {}
inject_routing_headers(headers)
assert "X-UiPath-Internal-TenantId" not in headers
assert "X-UiPath-Internal-AccountId" not in headers
assert HEADER_INTERNAL_TENANT_ID not in headers
assert HEADER_INTERNAL_ACCOUNT_ID not in headers

def test_does_not_overwrite_existing_headers(
self, monkeypatch: pytest.MonkeyPatch
Expand All @@ -87,4 +91,4 @@ def test_does_not_overwrite_existing_headers(
headers: dict[str, str] = {"X-Custom": "keep-me"}
inject_routing_headers(headers)
assert headers["X-Custom"] == "keep-me"
assert headers["X-UiPath-Internal-TenantId"] == "tenant-123"
assert headers[HEADER_INTERNAL_TENANT_ID] == "tenant-123"
4 changes: 2 additions & 2 deletions packages/uipath-platform/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/uipath/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = "uipath"
version = "2.11.11"
version = "2.11.12"
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
dependencies = [
"uipath-core>=0.5.21, <0.6.0",
"uipath-runtime>=0.11.0, <0.12.0",
"uipath-platform>=0.1.74, <0.2.0",
"uipath-platform>=0.1.75, <0.2.0",
"click>=8.3.1",
"httpx>=0.28.1",
"pyjwt>=2.10.1",
Expand Down
28 changes: 17 additions & 11 deletions packages/uipath/src/uipath/_cli/_auth/_auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
from uipath._cli._utils._console import ConsoleLogger
from uipath._utils._auth import update_env_file
from uipath.platform.common import ExternalApplicationService, TokenData
from uipath.platform.common.constants import (
ENV_BASE_URL,
ENV_ORGANIZATION_ID,
ENV_TENANT_ID,
ENV_UIPATH_ACCESS_TOKEN,
)

from ._utils import update_auth_file

Expand Down Expand Up @@ -61,17 +67,17 @@ async def _authenticate_client_credentials(self):
)

env_vars = {
"UIPATH_ACCESS_TOKEN": token_data.access_token,
"UIPATH_URL": external_app_service._base_url,
"UIPATH_ORGANIZATION_ID": get_parsed_token_data(token_data).get("prt_id"),
ENV_UIPATH_ACCESS_TOKEN: token_data.access_token,
ENV_BASE_URL: external_app_service._base_url,
ENV_ORGANIZATION_ID: get_parsed_token_data(token_data).get("prt_id"),
}

if tenant_name:
self._tenant = tenant_name
auth_session = AuthSession(self._domain)
auth_session.update_token_data(token_data)
tenant_info = await auth_session.resolve_tenant_info(self._tenant)
env_vars["UIPATH_TENANT_ID"] = tenant_info["tenant_id"]
env_vars[ENV_TENANT_ID] = tenant_info["tenant_id"]
else:
self._console.warning("Could not extract tenant from --base-url.")
update_env_file(env_vars)
Expand All @@ -90,10 +96,10 @@ async def _authenticate_authorization_code(self) -> None:

update_env_file(
{
"UIPATH_ACCESS_TOKEN": token_data.access_token,
"UIPATH_URL": uipath_url,
"UIPATH_TENANT_ID": tenant_info["tenant_id"],
"UIPATH_ORGANIZATION_ID": tenant_info["organization_id"],
ENV_UIPATH_ACCESS_TOKEN: token_data.access_token,
ENV_BASE_URL: uipath_url,
ENV_TENANT_ID: tenant_info["tenant_id"],
ENV_ORGANIZATION_ID: tenant_info["organization_id"],
}
)

Expand All @@ -110,9 +116,9 @@ async def _authenticate_authorization_code(self) -> None:

async def _can_reuse_existing_token(self, auth_session: AuthSession) -> bool:
if (
os.getenv("UIPATH_URL")
and os.getenv("UIPATH_TENANT_ID")
and os.getenv("UIPATH_ORGANIZATION_ID")
os.getenv(ENV_BASE_URL)
and os.getenv(ENV_TENANT_ID)
and os.getenv(ENV_ORGANIZATION_ID)
):
try:
await auth_session.ensure_valid_token()
Expand Down
3 changes: 2 additions & 1 deletion packages/uipath/src/uipath/_cli/_auth/_auth_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import click

from uipath.platform.common import TokenData
from uipath.platform.common.constants import ENV_UIPATH_ACCESS_TOKEN
from uipath.platform.identity import IdentityService
from uipath.platform.portal import (
PortalService as PlatformPortalService,
Expand Down Expand Up @@ -95,7 +96,7 @@ async def ensure_valid_token(self):
def finalize(token_data: TokenData):
self.update_token_data(token_data)
update_auth_file(token_data)
update_env_file({"UIPATH_ACCESS_TOKEN": token_data.access_token})
update_env_file({ENV_UIPATH_ACCESS_TOKEN: token_data.access_token})

if exp is not None and float(exp) > time.time():
finalize(auth_data)
Expand Down
3 changes: 2 additions & 1 deletion packages/uipath/src/uipath/_cli/_auth/_url_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Tuple
from urllib.parse import urlparse

from ...platform.common.constants import ENV_BASE_URL
from .._utils._console import ConsoleLogger

console = ConsoleLogger()
Expand All @@ -26,7 +27,7 @@ def resolve_domain(base_url: str | None, environment: str | None) -> str:
return domain

if environment is None:
uipath_url = os.getenv("UIPATH_URL")
uipath_url = os.getenv(ENV_BASE_URL)
if uipath_url:
parsed = urlparse(uipath_url)
if parsed.scheme and parsed.netloc:
Expand Down
20 changes: 14 additions & 6 deletions packages/uipath/src/uipath/_cli/_chat/_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
UiPathConversationToolCallEvent,
)
from uipath.core.triggers import UiPathResumeTrigger
from uipath.platform.common.constants import (
ENV_BASE_URL,
ENV_ORGANIZATION_ID,
ENV_TENANT_ID,
ENV_UIPATH_ACCESS_TOKEN,
HEADER_INTERNAL_ACCOUNT_ID,
HEADER_INTERNAL_TENANT_ID,
)
from uipath.runtime.chat import UiPathChatProtocol
from uipath.runtime.context import UiPathRuntimeContext

Expand Down Expand Up @@ -503,7 +511,7 @@ def get_chat_bridge(
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")
base_url = os.environ.get(ENV_BASE_URL)
if not base_url:
raise RuntimeError(
"UIPATH_URL environment variable required for conversational mode"
Expand All @@ -528,11 +536,11 @@ def get_chat_bridge(

# Build headers from context
headers = {
"Authorization": f"Bearer {os.environ.get('UIPATH_ACCESS_TOKEN', '')}",
"X-UiPath-Internal-TenantId": context.tenant_id
or os.environ.get("UIPATH_TENANT_ID", ""),
"X-UiPath-Internal-AccountId": context.org_id
or os.environ.get("UIPATH_ORGANIZATION_ID", ""),
"Authorization": f"Bearer {os.environ.get(ENV_UIPATH_ACCESS_TOKEN, '')}",
HEADER_INTERNAL_TENANT_ID: context.tenant_id
or os.environ.get(ENV_TENANT_ID, ""),
HEADER_INTERNAL_ACCOUNT_ID: context.org_id
or os.environ.get(ENV_ORGANIZATION_ID, ""),
"X-UiPath-ConversationId": context.conversation_id,
}

Expand Down
20 changes: 14 additions & 6 deletions packages/uipath/src/uipath/_cli/_chat/_voice_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
UiPathVoiceToolCallRequest,
UiPathVoiceToolCallResult,
)
from uipath.platform.common.constants import (
ENV_BASE_URL,
ENV_ORGANIZATION_ID,
ENV_TENANT_ID,
ENV_UIPATH_ACCESS_TOKEN,
HEADER_INTERNAL_ACCOUNT_ID,
HEADER_INTERNAL_TENANT_ID,
)
from uipath.runtime.context import UiPathRuntimeContext

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -229,7 +237,7 @@ def get_voice_bridge(
f"CAS_WEBSOCKET_HOST is set. Using websocket_url '{url}{socketio_path}'."
)
else:
base_url = os.environ.get("UIPATH_URL")
base_url = os.environ.get(ENV_BASE_URL)
if not base_url:
raise RuntimeError(
"UIPATH_URL environment variable required for conversational mode"
Expand All @@ -241,11 +249,11 @@ def get_voice_bridge(
socketio_path = "autopilotforeveryone_/websocket_/socket.io"

headers = {
"Authorization": f"Bearer {os.environ.get('UIPATH_ACCESS_TOKEN', '')}",
"X-UiPath-Internal-TenantId": context.tenant_id
or os.environ.get("UIPATH_TENANT_ID", ""),
"X-UiPath-Internal-AccountId": context.org_id
or os.environ.get("UIPATH_ORGANIZATION_ID", ""),
"Authorization": f"Bearer {os.environ.get(ENV_UIPATH_ACCESS_TOKEN, '')}",
HEADER_INTERNAL_TENANT_ID: context.tenant_id
or os.environ.get(ENV_TENANT_ID, ""),
HEADER_INTERNAL_ACCOUNT_ID: context.org_id
or os.environ.get(ENV_ORGANIZATION_ID, ""),
"X-UiPath-ConversationId": context.conversation_id,
}

Expand Down
Loading
Loading