diff --git a/pyproject.toml b/pyproject.toml index 326970586..b401d21e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.6.27" +version = "2.6.28" 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" diff --git a/src/uipath/tracing/_utils.py b/src/uipath/tracing/_utils.py index 3df356d05..d051d6752 100644 --- a/src/uipath/tracing/_utils.py +++ b/src/uipath/tracing/_utils.py @@ -15,6 +15,9 @@ logger = logging.getLogger(__name__) +# SourceEnum.Robots = 4 (default for Python SDK / coded agents) +DEFAULT_SOURCE = 4 + @dataclass class UiPathSpan: @@ -43,7 +46,7 @@ class UiPathSpan: folder_key: Optional[str] = field( default_factory=lambda: env.get("UIPATH_FOLDER_KEY", "") ) - source: Optional[int] = None + source: int = DEFAULT_SOURCE span_type: str = "Coded Agents" process_key: Optional[str] = field( default_factory=lambda: env.get("UIPATH_PROCESS_UUID") @@ -248,7 +251,10 @@ def otel_span_to_uipath_span( execution_type = attributes_dict.get("executionType") agent_version = attributes_dict.get("agentVersion") reference_id = attributes_dict.get("referenceId") - source = attributes_dict.get("source") + + # Source: override via uipath.source attribute, else DEFAULT_SOURCE + uipath_source = attributes_dict.get("uipath.source") + source = uipath_source if isinstance(uipath_source, int) else DEFAULT_SOURCE # Create UiPathSpan from OpenTelemetry span start_time = datetime.fromtimestamp( diff --git a/tests/tracing/test_span_utils.py b/tests/tracing/test_span_utils.py index 0da577bca..54a5e4948 100644 --- a/tests/tracing/test_span_utils.py +++ b/tests/tracing/test_span_utils.py @@ -320,3 +320,69 @@ def test_uipath_span_missing_execution_type_and_agent_version(self): assert span_dict["ExecutionType"] is None assert span_dict["AgentVersion"] is None + + @patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"}) + def test_uipath_span_source_defaults_to_robots(self): + """Test that Source defaults to 4 (Robots) and ignores attributes.source.""" + mock_span = Mock(spec=OTelSpan) + + trace_id = 0x123456789ABCDEF0123456789ABCDEF0 + span_id = 0x0123456789ABCDEF + mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False) + mock_span.get_span_context.return_value = mock_context + + mock_span.name = "test-span" + mock_span.parent = None + mock_span.status.status_code = StatusCode.OK + # source in attributes should NOT override top-level Source + mock_span.attributes = {"source": "runtime"} + mock_span.events = [] + mock_span.links = [] + + current_time_ns = int(datetime.now().timestamp() * 1e9) + mock_span.start_time = current_time_ns + mock_span.end_time = current_time_ns + 1000000 + + uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span) + span_dict = uipath_span.to_dict() + + # Top-level Source should be 4 (Robots), string "runtime" is ignored + assert uipath_span.source == 4 + assert span_dict["Source"] == 4 + + # attributes.source string should still be in Attributes JSON + attrs = json.loads(span_dict["Attributes"]) + assert attrs["source"] == "runtime" + + @patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"}) + def test_uipath_span_source_override_with_uipath_source(self): + """Test that uipath.source attribute overrides default (for low-code agents).""" + mock_span = Mock(spec=OTelSpan) + + trace_id = 0x123456789ABCDEF0123456789ABCDEF0 + span_id = 0x0123456789ABCDEF + mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False) + mock_span.get_span_context.return_value = mock_context + + mock_span.name = "test-span" + mock_span.parent = None + mock_span.status.status_code = StatusCode.OK + # uipath.source=1 (Agents) overrides default of 4 (Robots) + mock_span.attributes = {"uipath.source": 1, "source": "runtime"} + mock_span.events = [] + mock_span.links = [] + + current_time_ns = int(datetime.now().timestamp() * 1e9) + mock_span.start_time = current_time_ns + mock_span.end_time = current_time_ns + 1000000 + + uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span) + span_dict = uipath_span.to_dict() + + # uipath.source overrides - low-code agents use 1 (Agents) + assert uipath_span.source == 1 + assert span_dict["Source"] == 1 + + # String source still in Attributes JSON + attrs = json.loads(span_dict["Attributes"]) + assert attrs["source"] == "runtime" diff --git a/uv.lock b/uv.lock index 993b17e28..e09e0210d 100644 --- a/uv.lock +++ b/uv.lock @@ -2491,7 +2491,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.6.27" +version = "2.6.28" source = { editable = "." } dependencies = [ { name = "applicationinsights" },