From abb6e82cb6b19651742e5ea01778fc86e1cde9c4 Mon Sep 17 00:00:00 2001 From: Valentina Bojan Date: Mon, 22 Jun 2026 17:47:06 +0300 Subject: [PATCH 1/7] feat(guardrails): send execution source and job key headers Guardrail validation calls now carry x-uipath-agenthub-config (execution source) and x-uipath-jobkey headers for licensing/metering correlation, mirroring the licensing flow. The source is derived from the executing CLI command (run -> runtime, debug/dev -> playground, eval -> eval) and exposed via the UIPATH_EXECUTION_SOURCE env var, so it works for both coded and low-code agents since both run through the uipath CLI. Co-Authored-By: Claude Opus 4.8 --- .../src/uipath/platform/common/_config.py | 12 +++ .../uipath/platform/common/_job_context.py | 14 ++- .../src/uipath/platform/common/constants.py | 1 + .../guardrails/_guardrails_service.py | 12 ++- .../tests/services/test_guardrails_service.py | 98 +++++++++++++++++++ .../uipath/_cli/_utils/_execution_source.py | 45 +++++++++ packages/uipath/src/uipath/_cli/cli_debug.py | 4 + packages/uipath/src/uipath/_cli/cli_dev.py | 4 + packages/uipath/src/uipath/_cli/cli_eval.py | 4 + packages/uipath/src/uipath/_cli/cli_run.py | 4 + 10 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 packages/uipath/src/uipath/_cli/_utils/_execution_source.py diff --git a/packages/uipath-platform/src/uipath/platform/common/_config.py b/packages/uipath-platform/src/uipath/platform/common/_config.py index 40db82214..a4dcfa6ab 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_config.py +++ b/packages/uipath-platform/src/uipath/platform/common/_config.py @@ -155,6 +155,18 @@ def job_key(self) -> str | None: return os.getenv(ENV_JOB_KEY, None) + @property + def execution_source(self) -> str | None: + """Execution source (AgentHub config) identifying the run context. + + Set by the ``uipath`` CLI from the executing command (run/debug/eval), + so it is available for both coded and low-code agents. Sent on + guardrail calls via the ``x-uipath-agenthub-config`` header. + """ + from uipath.platform.common.constants import ENV_EXECUTION_SOURCE + + return os.getenv(ENV_EXECUTION_SOURCE, None) + @property def has_legacy_eval_folder(self) -> bool: from uipath.platform.common.constants import LEGACY_EVAL_FOLDER diff --git a/packages/uipath-platform/src/uipath/platform/common/_job_context.py b/packages/uipath-platform/src/uipath/platform/common/_job_context.py index ccbd99f22..bca398443 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_job_context.py +++ b/packages/uipath-platform/src/uipath/platform/common/_job_context.py @@ -1,5 +1,5 @@ from ._config import UiPathConfig -from .constants import HEADER_JOB_KEY +from .constants import HEADER_AGENTHUB_CONFIG, HEADER_JOB_KEY def header_job_key() -> dict[str, str]: @@ -11,3 +11,15 @@ def header_job_key() -> dict[str, str]: if not job_key: return {} return {HEADER_JOB_KEY: job_key} + + +def header_execution_source() -> dict[str, str]: + """Return the x-uipath-agenthub-config header for the execution source. + + Returns an empty dict when ``UiPathConfig.execution_source`` is unset or + empty. + """ + source = UiPathConfig.execution_source + if not source: + return {} + return {HEADER_AGENTHUB_CONFIG: source} diff --git a/packages/uipath-platform/src/uipath/platform/common/constants.py b/packages/uipath-platform/src/uipath/platform/common/constants.py index 304ef64a6..f6ba483bf 100644 --- a/packages/uipath-platform/src/uipath/platform/common/constants.py +++ b/packages/uipath-platform/src/uipath/platform/common/constants.py @@ -26,6 +26,7 @@ ENV_UIPATH_TRACE_ID = "UIPATH_TRACE_ID" ENV_UIPATH_PROCESS_VERSION = "UIPATH_PROCESS_VERSION" ENV_UIPATH_CONFIG_PATH = "UIPATH_CONFIG_PATH" +ENV_EXECUTION_SOURCE = "UIPATH_EXECUTION_SOURCE" # Headers HEADER_FOLDER_KEY = "x-uipath-folderkey" diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py index 424b35dad..c1139490a 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py @@ -12,6 +12,7 @@ from ..common._base_service import BaseService from ..common._config import UiPathApiConfig from ..common._execution_context import UiPathExecutionContext +from ..common._job_context import header_execution_source, header_job_key from ..common._models import Endpoint, RequestSpec from ..errors import EnrichedException from .guardrails import BuiltInValidatorGuardrail @@ -122,9 +123,16 @@ def evaluate_guardrail( endpoint=Endpoint("/agentsruntime_/api/execution/guardrails/validate"), json=payload, ) - # Include trace context headers for server-side span correlation + # Include trace context headers for server-side span correlation, plus + # the execution source (x-uipath-agenthub-config) and job key headers + # for licensing/metering correlation. trace_headers = build_trace_context_headers() - request_headers = {**(spec.headers or {}), **trace_headers} + request_headers = { + **(spec.headers or {}), + **trace_headers, + **header_execution_source(), + **header_job_key(), + } span_id = None try: response = self.request( diff --git a/packages/uipath-platform/tests/services/test_guardrails_service.py b/packages/uipath-platform/tests/services/test_guardrails_service.py index dd20d5646..9f4d29bed 100644 --- a/packages/uipath-platform/tests/services/test_guardrails_service.py +++ b/packages/uipath-platform/tests/services/test_guardrails_service.py @@ -353,6 +353,104 @@ def capture_request(request): # header merging works even when no active span exists) assert "content-type" in headers + def test_evaluate_guardrail_sends_source_and_job_key_headers( + self, + httpx_mock: HTTPXMock, + service: GuardrailsService, + base_url: str, + org: str, + tenant: str, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """Outgoing request includes execution source and job key headers.""" + monkeypatch.setenv("UIPATH_EXECUTION_SOURCE", "runtime") + monkeypatch.setenv("UIPATH_JOB_KEY", "job-123") + + captured_request = None + + def capture_request(request): + nonlocal captured_request + captured_request = request + return httpx.Response( + status_code=200, + json={"result": "PASSED", "details": "OK"}, + ) + + httpx_mock.add_callback( + method="POST", + url=f"{base_url}{org}{tenant}/agentsruntime_/api/execution/guardrails/validate", + callback=capture_request, + ) + + pii_guardrail = BuiltInValidatorGuardrail( + id="test-id", + name="PII guardrail", + description="Test", + enabled_for_evals=True, + selector=GuardrailSelector( + scopes=[GuardrailScope.TOOL], match_names=["tool1"] + ), + guardrail_type="builtInValidator", + validator_type="pii_detection", + validator_parameters=[], + ) + + service.evaluate_guardrail("test input", pii_guardrail) + + assert captured_request is not None + headers = dict(captured_request.headers) + assert headers.get("x-uipath-agenthub-config") == "runtime" + assert headers.get("x-uipath-jobkey") == "job-123" + + def test_evaluate_guardrail_omits_source_and_job_key_when_unset( + self, + httpx_mock: HTTPXMock, + service: GuardrailsService, + base_url: str, + org: str, + tenant: str, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """Source/job key headers are absent when their env vars are unset.""" + monkeypatch.delenv("UIPATH_EXECUTION_SOURCE", raising=False) + monkeypatch.delenv("UIPATH_JOB_KEY", raising=False) + + captured_request = None + + def capture_request(request): + nonlocal captured_request + captured_request = request + return httpx.Response( + status_code=200, + json={"result": "PASSED", "details": "OK"}, + ) + + httpx_mock.add_callback( + method="POST", + url=f"{base_url}{org}{tenant}/agentsruntime_/api/execution/guardrails/validate", + callback=capture_request, + ) + + pii_guardrail = BuiltInValidatorGuardrail( + id="test-id", + name="PII guardrail", + description="Test", + enabled_for_evals=True, + selector=GuardrailSelector( + scopes=[GuardrailScope.TOOL], match_names=["tool1"] + ), + guardrail_type="builtInValidator", + validator_type="pii_detection", + validator_parameters=[], + ) + + service.evaluate_guardrail("test input", pii_guardrail) + + assert captured_request is not None + headers = dict(captured_request.headers) + assert "x-uipath-agenthub-config" not in headers + assert "x-uipath-jobkey" not in headers + def test_evaluate_guardrail_extracts_span_id_from_traceparent( self, httpx_mock: HTTPXMock, diff --git a/packages/uipath/src/uipath/_cli/_utils/_execution_source.py b/packages/uipath/src/uipath/_cli/_utils/_execution_source.py new file mode 100644 index 000000000..9b0bc299a --- /dev/null +++ b/packages/uipath/src/uipath/_cli/_utils/_execution_source.py @@ -0,0 +1,45 @@ +"""Execution source resolution for the CLI. + +Maps the executing CLI command to an AgentHub config ("source") and exposes it +to the SDK via the ``UIPATH_EXECUTION_SOURCE`` environment variable. Because +this runs in the same process that later executes the agent, downstream +services (e.g. guardrails) can read it from config and send it as the +``x-uipath-agenthub-config`` header. This covers both coded and low-code +agents, since both are launched through these CLI commands. +""" + +import os + +from uipath.platform.common import UiPathConfig +from uipath.platform.common.constants import ENV_EXECUTION_SOURCE + +# Execution source values, matching the low-code runtime's AgentExecutionType +# (run -> runtime, debug/dev -> playground, eval -> eval). +_RUNTIME_SOURCE = "runtime" +_PLAYGROUND_SOURCE = "playground" +_EVAL_SOURCE = "eval" + +_COMMAND_TO_SOURCE: dict[str, str] = { + "run": _RUNTIME_SOURCE, + "debug": _PLAYGROUND_SOURCE, + "dev": _PLAYGROUND_SOURCE, + "eval": _EVAL_SOURCE, +} + + +def set_execution_source(command: str) -> None: + """Set ``UIPATH_EXECUTION_SOURCE`` from the executing CLI command. + + No-op for commands that do not execute an agent. A debug-rooted job + (e.g. Maestro solution debug invoking an agent) is treated as a + playground run, mirroring the low-code runtime. + + Args: + command: The CLI command name (e.g. "run", "debug", "eval"). + """ + source = _COMMAND_TO_SOURCE.get(command) + if source is None: + return + if source == _RUNTIME_SOURCE and UiPathConfig.is_rooted_to_debug_job: + source = _PLAYGROUND_SOURCE + os.environ[ENV_EXECUTION_SOURCE] = source diff --git a/packages/uipath/src/uipath/_cli/cli_debug.py b/packages/uipath/src/uipath/_cli/cli_debug.py index 92b8ea454..2e7f117e4 100644 --- a/packages/uipath/src/uipath/_cli/cli_debug.py +++ b/packages/uipath/src/uipath/_cli/cli_debug.py @@ -87,6 +87,10 @@ def debug( attach: str | None, ) -> None: """Debug the project.""" + from ._utils._execution_source import set_execution_source + + set_execution_source("debug") + input_file = file or input_file # Setup debugging if requested if not setup_debugging(debug, debug_port): diff --git a/packages/uipath/src/uipath/_cli/cli_dev.py b/packages/uipath/src/uipath/_cli/cli_dev.py index 62740dc4b..998b8bbc4 100644 --- a/packages/uipath/src/uipath/_cli/cli_dev.py +++ b/packages/uipath/src/uipath/_cli/cli_dev.py @@ -52,6 +52,10 @@ def dev(interface: str, debug: bool, debug_port: int) -> None: INTERFACE: Choose 'terminal' for console interface (default) or 'web' for browser-based interface. """ + from ._utils._execution_source import set_execution_source + + set_execution_source("dev") + try: _check_dev_dependency(interface) except ImportError as e: diff --git a/packages/uipath/src/uipath/_cli/cli_eval.py b/packages/uipath/src/uipath/_cli/cli_eval.py index e101717d6..fcfd031cd 100644 --- a/packages/uipath/src/uipath/_cli/cli_eval.py +++ b/packages/uipath/src/uipath/_cli/cli_eval.py @@ -262,6 +262,10 @@ def eval( input_overrides: Input field overrides mapping (direct field override with deep merge) resume: Resume execution from a previous suspended state """ + from ._utils._execution_source import set_execution_source + + set_execution_source("eval") + set_llm_concurrency(max_llm_concurrency) should_register_progress_reporter = setup_reporting_prereq(no_report) diff --git a/packages/uipath/src/uipath/_cli/cli_run.py b/packages/uipath/src/uipath/_cli/cli_run.py index 48f42018b..c71937f74 100644 --- a/packages/uipath/src/uipath/_cli/cli_run.py +++ b/packages/uipath/src/uipath/_cli/cli_run.py @@ -125,6 +125,10 @@ def run( simulation: str | None, ) -> None: """Execute the project.""" + from ._utils._execution_source import set_execution_source + + set_execution_source("run") + input_file = file or input_file # Setup debugging if requested From dd0bf50069a4eb94402554c329b65a3e1fb269a4 Mon Sep 17 00:00:00 2001 From: Valentina Bojan Date: Mon, 22 Jun 2026 17:49:51 +0300 Subject: [PATCH 2/7] chore: bump uipath to 2.11.7 and uipath-platform to 0.1.72 Also drops the "(AgentHub config)" note from the execution_source docstring. Co-Authored-By: Claude Opus 4.8 --- packages/uipath-platform/pyproject.toml | 2 +- packages/uipath-platform/src/uipath/platform/common/_config.py | 2 +- packages/uipath/pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 37d6fcdaa..6d9516a9d 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.1.71" +version = "0.1.72" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/src/uipath/platform/common/_config.py b/packages/uipath-platform/src/uipath/platform/common/_config.py index a4dcfa6ab..61bd2cf70 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_config.py +++ b/packages/uipath-platform/src/uipath/platform/common/_config.py @@ -157,7 +157,7 @@ def job_key(self) -> str | None: @property def execution_source(self) -> str | None: - """Execution source (AgentHub config) identifying the run context. + """Execution source identifying the run context. Set by the ``uipath`` CLI from the executing command (run/debug/eval), so it is available for both coded and low-code agents. Sent on diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index fd088202e..581f9a3d2 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.11.6" +version = "2.11.7" 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" From 64b255e28e5cf14f80a0a0620ea64fdf23cebbdf Mon Sep 17 00:00:00 2001 From: Valentina Bojan Date: Mon, 22 Jun 2026 17:50:40 +0300 Subject: [PATCH 3/7] docs: trim execution_source docstring Co-Authored-By: Claude Opus 4.8 --- .../uipath-platform/src/uipath/platform/common/_config.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/common/_config.py b/packages/uipath-platform/src/uipath/platform/common/_config.py index 61bd2cf70..759308f2b 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_config.py +++ b/packages/uipath-platform/src/uipath/platform/common/_config.py @@ -159,9 +159,7 @@ def job_key(self) -> str | None: def execution_source(self) -> str | None: """Execution source identifying the run context. - Set by the ``uipath`` CLI from the executing command (run/debug/eval), - so it is available for both coded and low-code agents. Sent on - guardrail calls via the ``x-uipath-agenthub-config`` header. + Set by the ``uipath`` CLI from the executing command (run/debug/eval). """ from uipath.platform.common.constants import ENV_EXECUTION_SOURCE From b31a5809d2c86c0645ffba2bd995aeccef3fb251 Mon Sep 17 00:00:00 2001 From: Valentina Bojan Date: Mon, 22 Jun 2026 17:55:29 +0300 Subject: [PATCH 4/7] feat(guardrails): send execution source on x-uipath-execution-source Use a dedicated x-uipath-execution-source header instead of reusing x-uipath-agenthub-config. Co-Authored-By: Claude Opus 4.8 --- .../src/uipath/platform/common/_job_context.py | 6 +++--- .../uipath-platform/src/uipath/platform/common/constants.py | 1 + .../src/uipath/platform/guardrails/_guardrails_service.py | 2 +- .../tests/services/test_guardrails_service.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/common/_job_context.py b/packages/uipath-platform/src/uipath/platform/common/_job_context.py index bca398443..55bcdd78d 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_job_context.py +++ b/packages/uipath-platform/src/uipath/platform/common/_job_context.py @@ -1,5 +1,5 @@ from ._config import UiPathConfig -from .constants import HEADER_AGENTHUB_CONFIG, HEADER_JOB_KEY +from .constants import HEADER_EXECUTION_SOURCE, HEADER_JOB_KEY def header_job_key() -> dict[str, str]: @@ -14,7 +14,7 @@ def header_job_key() -> dict[str, str]: def header_execution_source() -> dict[str, str]: - """Return the x-uipath-agenthub-config header for the execution source. + """Return the x-uipath-execution-source header for the execution source. Returns an empty dict when ``UiPathConfig.execution_source`` is unset or empty. @@ -22,4 +22,4 @@ def header_execution_source() -> dict[str, str]: source = UiPathConfig.execution_source if not source: return {} - return {HEADER_AGENTHUB_CONFIG: source} + return {HEADER_EXECUTION_SOURCE: source} diff --git a/packages/uipath-platform/src/uipath/platform/common/constants.py b/packages/uipath-platform/src/uipath/platform/common/constants.py index f6ba483bf..02629b860 100644 --- a/packages/uipath-platform/src/uipath/platform/common/constants.py +++ b/packages/uipath-platform/src/uipath/platform/common/constants.py @@ -40,6 +40,7 @@ HEADER_PROCESS_KEY = "x-uipath-processkey" HEADER_TRACE_ID = "x-uipath-traceid" HEADER_AGENTHUB_CONFIG = "x-uipath-agenthub-config" +HEADER_EXECUTION_SOURCE = "x-uipath-execution-source" HEADER_LLMGATEWAY_BYO_CONNECTION_ID = "x-uipath-llmgateway-byoisconnectionid" HEADER_SW_LOCK_KEY = "x-uipath-sw-lockkey" HEADER_LICENSING_CONTEXT = "x-uipath-licensing-context" diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py index c1139490a..fa7f556af 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py @@ -124,7 +124,7 @@ def evaluate_guardrail( json=payload, ) # Include trace context headers for server-side span correlation, plus - # the execution source (x-uipath-agenthub-config) and job key headers + # the execution source (x-uipath-execution-source) and job key headers # for licensing/metering correlation. trace_headers = build_trace_context_headers() request_headers = { diff --git a/packages/uipath-platform/tests/services/test_guardrails_service.py b/packages/uipath-platform/tests/services/test_guardrails_service.py index 9f4d29bed..46fadf432 100644 --- a/packages/uipath-platform/tests/services/test_guardrails_service.py +++ b/packages/uipath-platform/tests/services/test_guardrails_service.py @@ -399,7 +399,7 @@ def capture_request(request): assert captured_request is not None headers = dict(captured_request.headers) - assert headers.get("x-uipath-agenthub-config") == "runtime" + assert headers.get("x-uipath-execution-source") == "runtime" assert headers.get("x-uipath-jobkey") == "job-123" def test_evaluate_guardrail_omits_source_and_job_key_when_unset( @@ -448,7 +448,7 @@ def capture_request(request): assert captured_request is not None headers = dict(captured_request.headers) - assert "x-uipath-agenthub-config" not in headers + assert "x-uipath-execution-source" not in headers assert "x-uipath-jobkey" not in headers def test_evaluate_guardrail_extracts_span_id_from_traceparent( From c94cf5c14d9175e407dad8dff2577a819b86eafc Mon Sep 17 00:00:00 2001 From: Valentina Bojan Date: Mon, 22 Jun 2026 17:58:14 +0300 Subject: [PATCH 5/7] feat(guardrails): rename source header to x-uipath-guardrails-source Co-Authored-By: Claude Opus 4.8 --- .../src/uipath/platform/common/_job_context.py | 6 +++--- .../uipath-platform/src/uipath/platform/common/constants.py | 2 +- .../src/uipath/platform/guardrails/_guardrails_service.py | 2 +- .../tests/services/test_guardrails_service.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/common/_job_context.py b/packages/uipath-platform/src/uipath/platform/common/_job_context.py index 55bcdd78d..29b5fbc50 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_job_context.py +++ b/packages/uipath-platform/src/uipath/platform/common/_job_context.py @@ -1,5 +1,5 @@ from ._config import UiPathConfig -from .constants import HEADER_EXECUTION_SOURCE, HEADER_JOB_KEY +from .constants import HEADER_GUARDRAILS_SOURCE, HEADER_JOB_KEY def header_job_key() -> dict[str, str]: @@ -14,7 +14,7 @@ def header_job_key() -> dict[str, str]: def header_execution_source() -> dict[str, str]: - """Return the x-uipath-execution-source header for the execution source. + """Return the x-uipath-guardrails-source header for the execution source. Returns an empty dict when ``UiPathConfig.execution_source`` is unset or empty. @@ -22,4 +22,4 @@ def header_execution_source() -> dict[str, str]: source = UiPathConfig.execution_source if not source: return {} - return {HEADER_EXECUTION_SOURCE: source} + return {HEADER_GUARDRAILS_SOURCE: source} diff --git a/packages/uipath-platform/src/uipath/platform/common/constants.py b/packages/uipath-platform/src/uipath/platform/common/constants.py index 02629b860..a4e069a1c 100644 --- a/packages/uipath-platform/src/uipath/platform/common/constants.py +++ b/packages/uipath-platform/src/uipath/platform/common/constants.py @@ -40,7 +40,7 @@ HEADER_PROCESS_KEY = "x-uipath-processkey" HEADER_TRACE_ID = "x-uipath-traceid" HEADER_AGENTHUB_CONFIG = "x-uipath-agenthub-config" -HEADER_EXECUTION_SOURCE = "x-uipath-execution-source" +HEADER_GUARDRAILS_SOURCE = "x-uipath-guardrails-source" HEADER_LLMGATEWAY_BYO_CONNECTION_ID = "x-uipath-llmgateway-byoisconnectionid" HEADER_SW_LOCK_KEY = "x-uipath-sw-lockkey" HEADER_LICENSING_CONTEXT = "x-uipath-licensing-context" diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py index fa7f556af..028102595 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py @@ -124,7 +124,7 @@ def evaluate_guardrail( json=payload, ) # Include trace context headers for server-side span correlation, plus - # the execution source (x-uipath-execution-source) and job key headers + # the execution source (x-uipath-guardrails-source) and job key headers # for licensing/metering correlation. trace_headers = build_trace_context_headers() request_headers = { diff --git a/packages/uipath-platform/tests/services/test_guardrails_service.py b/packages/uipath-platform/tests/services/test_guardrails_service.py index 46fadf432..3fa347813 100644 --- a/packages/uipath-platform/tests/services/test_guardrails_service.py +++ b/packages/uipath-platform/tests/services/test_guardrails_service.py @@ -399,7 +399,7 @@ def capture_request(request): assert captured_request is not None headers = dict(captured_request.headers) - assert headers.get("x-uipath-execution-source") == "runtime" + assert headers.get("x-uipath-guardrails-source") == "runtime" assert headers.get("x-uipath-jobkey") == "job-123" def test_evaluate_guardrail_omits_source_and_job_key_when_unset( @@ -448,7 +448,7 @@ def capture_request(request): assert captured_request is not None headers = dict(captured_request.headers) - assert "x-uipath-execution-source" not in headers + assert "x-uipath-guardrails-source" not in headers assert "x-uipath-jobkey" not in headers def test_evaluate_guardrail_extracts_span_id_from_traceparent( From 987d2e44a722534c4c70a9f7e70bf222892816d6 Mon Sep 17 00:00:00 2001 From: Valentina Bojan Date: Mon, 22 Jun 2026 18:02:13 +0300 Subject: [PATCH 6/7] refactor: drop debug-rooted reclassification from execution source Mirror get_execution_type's plain command->type mapping; the playground reclassification was a licensing-only concern. Co-Authored-By: Claude Opus 4.8 --- .../uipath/src/uipath/_cli/_utils/_execution_source.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/uipath/src/uipath/_cli/_utils/_execution_source.py b/packages/uipath/src/uipath/_cli/_utils/_execution_source.py index 9b0bc299a..74380fb26 100644 --- a/packages/uipath/src/uipath/_cli/_utils/_execution_source.py +++ b/packages/uipath/src/uipath/_cli/_utils/_execution_source.py @@ -10,7 +10,6 @@ import os -from uipath.platform.common import UiPathConfig from uipath.platform.common.constants import ENV_EXECUTION_SOURCE # Execution source values, matching the low-code runtime's AgentExecutionType @@ -30,9 +29,7 @@ def set_execution_source(command: str) -> None: """Set ``UIPATH_EXECUTION_SOURCE`` from the executing CLI command. - No-op for commands that do not execute an agent. A debug-rooted job - (e.g. Maestro solution debug invoking an agent) is treated as a - playground run, mirroring the low-code runtime. + No-op for commands that do not execute an agent. Args: command: The CLI command name (e.g. "run", "debug", "eval"). @@ -40,6 +37,4 @@ def set_execution_source(command: str) -> None: source = _COMMAND_TO_SOURCE.get(command) if source is None: return - if source == _RUNTIME_SOURCE and UiPathConfig.is_rooted_to_debug_job: - source = _PLAYGROUND_SOURCE os.environ[ENV_EXECUTION_SOURCE] = source From 80b34ca49f8d94516e2262dbb48d96964871e0bb Mon Sep 17 00:00:00 2001 From: Valentina Bojan Date: Mon, 22 Jun 2026 18:08:21 +0300 Subject: [PATCH 7/7] fix(ci): raise uipath-platform pin, sync lockfiles, add coverage - Raise uipath -> uipath-platform pin to >=0.1.72 (dependency-bump check) - Regenerate uv.lock files for the version bumps (--locked sync) - Add unit tests for set_execution_source and header_execution_source Co-Authored-By: Claude Opus 4.8 --- .../tests/common/test_job_context.py | 30 ++++++++++++++-- packages/uipath-platform/uv.lock | 2 +- packages/uipath/pyproject.toml | 2 +- .../tests/cli/unit/test_execution_source.py | 34 +++++++++++++++++++ packages/uipath/uv.lock | 4 +-- 5 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 packages/uipath/tests/cli/unit/test_execution_source.py diff --git a/packages/uipath-platform/tests/common/test_job_context.py b/packages/uipath-platform/tests/common/test_job_context.py index 6a4e1a97d..afcc49784 100644 --- a/packages/uipath-platform/tests/common/test_job_context.py +++ b/packages/uipath-platform/tests/common/test_job_context.py @@ -1,7 +1,15 @@ import pytest -from uipath.platform.common._job_context import header_job_key -from uipath.platform.common.constants import ENV_JOB_KEY, HEADER_JOB_KEY +from uipath.platform.common._job_context import ( + header_execution_source, + header_job_key, +) +from uipath.platform.common.constants import ( + ENV_EXECUTION_SOURCE, + ENV_JOB_KEY, + HEADER_GUARDRAILS_SOURCE, + HEADER_JOB_KEY, +) def test_returns_header_when_env_var_set(monkeypatch: pytest.MonkeyPatch) -> None: @@ -20,3 +28,21 @@ def test_returns_empty_when_env_var_blank(monkeypatch: pytest.MonkeyPatch) -> No monkeypatch.setenv(ENV_JOB_KEY, "") assert header_job_key() == {} + + +def test_execution_source_header_when_set(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv(ENV_EXECUTION_SOURCE, "runtime") + + assert header_execution_source() == {HEADER_GUARDRAILS_SOURCE: "runtime"} + + +def test_execution_source_empty_when_unset(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv(ENV_EXECUTION_SOURCE, raising=False) + + assert header_execution_source() == {} + + +def test_execution_source_empty_when_blank(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv(ENV_EXECUTION_SOURCE, "") + + assert header_execution_source() == {} diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index df0dc786a..159a3e71b 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.71" +version = "0.1.72" source = { editable = "." } dependencies = [ { name = "httpx" }, diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 581f9a3d2..d6c151b2c 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.11" dependencies = [ "uipath-core>=0.5.17, <0.6.0", "uipath-runtime>=0.11.0, <0.12.0", - "uipath-platform>=0.1.68, <0.2.0", + "uipath-platform>=0.1.72, <0.2.0", "click>=8.3.1", "httpx>=0.28.1", "pyjwt>=2.10.1", diff --git a/packages/uipath/tests/cli/unit/test_execution_source.py b/packages/uipath/tests/cli/unit/test_execution_source.py new file mode 100644 index 000000000..529655a90 --- /dev/null +++ b/packages/uipath/tests/cli/unit/test_execution_source.py @@ -0,0 +1,34 @@ +import os + +import pytest + +from uipath._cli._utils._execution_source import set_execution_source + + +@pytest.mark.parametrize( + "command,expected", + [ + ("run", "runtime"), + ("debug", "playground"), + ("dev", "playground"), + ("eval", "eval"), + ], +) +def test_set_execution_source_maps_command( + command: str, expected: str, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.delenv("UIPATH_EXECUTION_SOURCE", raising=False) + + set_execution_source(command) + + assert os.environ["UIPATH_EXECUTION_SOURCE"] == expected + + +def test_set_execution_source_noop_for_unmapped_command( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("UIPATH_EXECUTION_SOURCE", raising=False) + + set_execution_source("pack") + + assert "UIPATH_EXECUTION_SOURCE" not in os.environ diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index db1345c98..2ce2978e1 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2552,7 +2552,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.11.6" +version = "2.11.7" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, @@ -2691,7 +2691,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.71" +version = "0.1.72" source = { editable = "../uipath-platform" } dependencies = [ { name = "httpx" },