From 75f0a782a4068ae55a8e04a73192427ebe0326a4 Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Fri, 30 Jan 2026 16:45:52 -0800 Subject: [PATCH 1/7] fix: use fixed source=4 (Robots) for UiPathSpan instead of reading from attributes Co-Authored-By: Claude Opus 4.5 --- pyproject.toml | 2 +- src/uipath/tracing/_utils.py | 4 +--- uv.lock | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) 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..e28375943 100644 --- a/src/uipath/tracing/_utils.py +++ b/src/uipath/tracing/_utils.py @@ -43,7 +43,7 @@ class UiPathSpan: folder_key: Optional[str] = field( default_factory=lambda: env.get("UIPATH_FOLDER_KEY", "") ) - source: Optional[int] = None + source: int = 4 # SourceEnum.Robots for Python SDK span_type: str = "Coded Agents" process_key: Optional[str] = field( default_factory=lambda: env.get("UIPATH_PROCESS_UUID") @@ -248,7 +248,6 @@ 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") # Create UiPathSpan from OpenTelemetry span start_time = datetime.fromtimestamp( @@ -278,7 +277,6 @@ def otel_span_to_uipath_span( execution_type=execution_type, agent_version=agent_version, reference_id=reference_id, - source=source, ) @staticmethod 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" }, From 792e10239c965940b4ccd5ce85f47c26c148eb0d Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Fri, 30 Jan 2026 16:54:30 -0800 Subject: [PATCH 2/7] test: add unit test for source field defaulting to 4 (Robots) Co-Authored-By: Claude Opus 4.5 --- tests/tracing/test_span_utils.py | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/tracing/test_span_utils.py b/tests/tracing/test_span_utils.py index 0da577bca..e0e24275f 100644 --- a/tests/tracing/test_span_utils.py +++ b/tests/tracing/test_span_utils.py @@ -320,3 +320,36 @@ 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), not from attributes + 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" From 4f143f08d63990c123b71234583ddc5b250a322b Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Sun, 1 Feb 2026 19:22:15 -0800 Subject: [PATCH 3/7] fix: allow integer source override from attributes for low-code agents - Default source=4 (Robots) for coded agents - Integer source in attributes (e.g., source=1 for Agents) overrides default - String values like "runtime" are ignored for top-level Source Addresses review comment from @JosephMar Related: UiPath/uipath-agents-python#196 Co-Authored-By: Claude Opus 4.5 --- src/uipath/tracing/_utils.py | 7 +++++++ tests/tracing/test_span_utils.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/uipath/tracing/_utils.py b/src/uipath/tracing/_utils.py index e28375943..7209f33bd 100644 --- a/src/uipath/tracing/_utils.py +++ b/src/uipath/tracing/_utils.py @@ -249,6 +249,12 @@ def otel_span_to_uipath_span( agent_version = attributes_dict.get("agentVersion") reference_id = attributes_dict.get("referenceId") + # Source: default is 4 (Robots) for coded agents, but allow integer override + # from attributes for low-code agents (source=1). String values like "runtime" + # are ignored here and pass through in attributes JSON. + source_attr = attributes_dict.get("source") + source = source_attr if isinstance(source_attr, int) else None + # Create UiPathSpan from OpenTelemetry span start_time = datetime.fromtimestamp( (otel_span.start_time or 0) / 1e9 @@ -277,6 +283,7 @@ def otel_span_to_uipath_span( execution_type=execution_type, agent_version=agent_version, reference_id=reference_id, + **({"source": source} if source is not None else {}), ) @staticmethod diff --git a/tests/tracing/test_span_utils.py b/tests/tracing/test_span_utils.py index e0e24275f..fb9f336ab 100644 --- a/tests/tracing/test_span_utils.py +++ b/tests/tracing/test_span_utils.py @@ -346,10 +346,39 @@ def test_uipath_span_source_defaults_to_robots(self): uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span) span_dict = uipath_span.to_dict() - # Top-level Source should be 4 (Robots), not from attributes + # 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_integer(self): + """Test that integer source from attributes 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 + # Integer source=1 (Agents) should override default of 4 (Robots) + mock_span.attributes = {"source": 1} + 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() + + # Integer source should override - low-code agents use 1 (Agents) + assert uipath_span.source == 1 + assert span_dict["Source"] == 1 From 38a0cd1ad197c0c00d63534d3550da14b058ca30 Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Sun, 1 Feb 2026 19:33:11 -0800 Subject: [PATCH 4/7] fix: use topLevelSource attribute for Source override - Default source=4 (Robots) for coded agents - topLevelSource attribute (int) overrides top-level Source - source attribute (string) passes through in Attributes JSON Co-Authored-By: Claude Opus 4.5 --- src/uipath/tracing/_utils.py | 10 +++++----- tests/tracing/test_span_utils.py | 14 +++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/uipath/tracing/_utils.py b/src/uipath/tracing/_utils.py index 7209f33bd..4f107e1cd 100644 --- a/src/uipath/tracing/_utils.py +++ b/src/uipath/tracing/_utils.py @@ -249,11 +249,11 @@ def otel_span_to_uipath_span( agent_version = attributes_dict.get("agentVersion") reference_id = attributes_dict.get("referenceId") - # Source: default is 4 (Robots) for coded agents, but allow integer override - # from attributes for low-code agents (source=1). String values like "runtime" - # are ignored here and pass through in attributes JSON. - source_attr = attributes_dict.get("source") - source = source_attr if isinstance(source_attr, int) else None + # Source: default is 4 (Robots) for coded agents. + # Low-code agents can override via `topLevelSource` attribute (e.g., topLevelSource=1). + # String `source` ("runtime", "playground") passes through in Attributes JSON. + top_level_source = attributes_dict.get("topLevelSource") + source = top_level_source if isinstance(top_level_source, int) else None # 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 fb9f336ab..782a3e18f 100644 --- a/tests/tracing/test_span_utils.py +++ b/tests/tracing/test_span_utils.py @@ -355,8 +355,8 @@ def test_uipath_span_source_defaults_to_robots(self): assert attrs["source"] == "runtime" @patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"}) - def test_uipath_span_source_override_with_integer(self): - """Test that integer source from attributes overrides default (for low-code agents).""" + def test_uipath_span_source_override_with_topLevelSource(self): + """Test that topLevelSource attribute overrides default (for low-code agents).""" mock_span = Mock(spec=OTelSpan) trace_id = 0x123456789ABCDEF0123456789ABCDEF0 @@ -367,8 +367,8 @@ def test_uipath_span_source_override_with_integer(self): mock_span.name = "test-span" mock_span.parent = None mock_span.status.status_code = StatusCode.OK - # Integer source=1 (Agents) should override default of 4 (Robots) - mock_span.attributes = {"source": 1} + # topLevelSource=1 (Agents) overrides default of 4 (Robots) + mock_span.attributes = {"topLevelSource": 1, "source": "runtime"} mock_span.events = [] mock_span.links = [] @@ -379,6 +379,10 @@ def test_uipath_span_source_override_with_integer(self): uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span) span_dict = uipath_span.to_dict() - # Integer source should override - low-code agents use 1 (Agents) + # topLevelSource 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" From edaef93c678f82b651990b3e6851bdb92be16eb0 Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Sun, 1 Feb 2026 19:37:10 -0800 Subject: [PATCH 5/7] chore: simplify comment --- src/uipath/tracing/_utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/uipath/tracing/_utils.py b/src/uipath/tracing/_utils.py index 4f107e1cd..7578a9542 100644 --- a/src/uipath/tracing/_utils.py +++ b/src/uipath/tracing/_utils.py @@ -249,9 +249,7 @@ def otel_span_to_uipath_span( agent_version = attributes_dict.get("agentVersion") reference_id = attributes_dict.get("referenceId") - # Source: default is 4 (Robots) for coded agents. - # Low-code agents can override via `topLevelSource` attribute (e.g., topLevelSource=1). - # String `source` ("runtime", "playground") passes through in Attributes JSON. + # Source: default 4 (Robots), override via topLevelSource attribute top_level_source = attributes_dict.get("topLevelSource") source = top_level_source if isinstance(top_level_source, int) else None From 12d099ea9b2cf85d0109fbd5470e21565e7f7d7b Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Mon, 2 Feb 2026 12:04:59 -0800 Subject: [PATCH 6/7] fix: use uipath.source attribute for Source override Co-Authored-By: Claude Opus 4.5 --- src/uipath/tracing/_utils.py | 6 +++--- tests/tracing/test_span_utils.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/uipath/tracing/_utils.py b/src/uipath/tracing/_utils.py index 7578a9542..c51a9e784 100644 --- a/src/uipath/tracing/_utils.py +++ b/src/uipath/tracing/_utils.py @@ -249,9 +249,9 @@ def otel_span_to_uipath_span( agent_version = attributes_dict.get("agentVersion") reference_id = attributes_dict.get("referenceId") - # Source: default 4 (Robots), override via topLevelSource attribute - top_level_source = attributes_dict.get("topLevelSource") - source = top_level_source if isinstance(top_level_source, int) else None + # Source: default 4 (Robots), override via uipath.source attribute + uipath_source = attributes_dict.get("uipath.source") + source = uipath_source if isinstance(uipath_source, int) else None # 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 782a3e18f..54a5e4948 100644 --- a/tests/tracing/test_span_utils.py +++ b/tests/tracing/test_span_utils.py @@ -355,8 +355,8 @@ def test_uipath_span_source_defaults_to_robots(self): assert attrs["source"] == "runtime" @patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"}) - def test_uipath_span_source_override_with_topLevelSource(self): - """Test that topLevelSource attribute overrides default (for low-code agents).""" + 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 @@ -367,8 +367,8 @@ def test_uipath_span_source_override_with_topLevelSource(self): mock_span.name = "test-span" mock_span.parent = None mock_span.status.status_code = StatusCode.OK - # topLevelSource=1 (Agents) overrides default of 4 (Robots) - mock_span.attributes = {"topLevelSource": 1, "source": "runtime"} + # uipath.source=1 (Agents) overrides default of 4 (Robots) + mock_span.attributes = {"uipath.source": 1, "source": "runtime"} mock_span.events = [] mock_span.links = [] @@ -379,7 +379,7 @@ def test_uipath_span_source_override_with_topLevelSource(self): uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span) span_dict = uipath_span.to_dict() - # topLevelSource overrides - low-code agents use 1 (Agents) + # uipath.source overrides - low-code agents use 1 (Agents) assert uipath_span.source == 1 assert span_dict["Source"] == 1 From 809a7f2c4f9d3ba50280dd5bb2f467ec4799f6b0 Mon Sep 17 00:00:00 2001 From: Sakshar Thakkar Date: Mon, 2 Feb 2026 12:10:59 -0800 Subject: [PATCH 7/7] refactor: use DEFAULT_SOURCE constant for source field Co-Authored-By: Claude Opus 4.5 --- src/uipath/tracing/_utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/uipath/tracing/_utils.py b/src/uipath/tracing/_utils.py index c51a9e784..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: int = 4 # SourceEnum.Robots for Python SDK + source: int = DEFAULT_SOURCE span_type: str = "Coded Agents" process_key: Optional[str] = field( default_factory=lambda: env.get("UIPATH_PROCESS_UUID") @@ -249,9 +252,9 @@ def otel_span_to_uipath_span( agent_version = attributes_dict.get("agentVersion") reference_id = attributes_dict.get("referenceId") - # Source: default 4 (Robots), override via uipath.source attribute + # 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 None + source = uipath_source if isinstance(uipath_source, int) else DEFAULT_SOURCE # Create UiPathSpan from OpenTelemetry span start_time = datetime.fromtimestamp( @@ -281,7 +284,7 @@ def otel_span_to_uipath_span( execution_type=execution_type, agent_version=agent_version, reference_id=reference_id, - **({"source": source} if source is not None else {}), + source=source, ) @staticmethod