From 48b2250d01c58a8523ee562dd92fe4040bd5b83e Mon Sep 17 00:00:00 2001 From: Dushyant Pathak Date: Thu, 3 Sep 2026 13:37:50 +0530 Subject: [PATCH] feat(action-center): allow custom taskSourceMetadata keys on CreateTask/CreateEscalation HITL tasks created via interrupt(CreateEscalation(...)) have no way to carry caller-supplied context (e.g. a conversational-agent id) in taskSource.taskSourceMetadata today; it is built entirely from UiPathConfig state with no extension point. Add an optional task_source_metadata dict to CreateTask (inherited by CreateEscalation) and TasksService.create/create_async, threaded through _create_spec and merged into taskSourceMetadata (as custom_metadata) on top of the built-in InstanceId/FolderKey/JobKey/ProcessKey keys. Additive only; no change for existing callers. --- packages/uipath-platform/pyproject.toml | 2 +- .../platform/action_center/_tasks_service.py | 29 ++++++++- .../platform/common/interrupt_models.py | 1 + .../platform/resume_triggers/_protocol.py | 1 + .../tests/services/test_actions_service.py | 62 +++++++++++++++++++ .../tests/services/test_hitl.py | 1 + packages/uipath-platform/uv.lock | 2 +- packages/uipath/uv.lock | 4 +- 8 files changed, 96 insertions(+), 6 deletions(-) diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index fe8dc8a9c..2f1b6a7c8 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.2.23" +version = "0.2.24" 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/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index bcb8d72e4..dc5a4bc93 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -55,6 +55,7 @@ def _create_spec( labels: Optional[List[str]] = None, is_actionable_message_enabled: Optional[bool] = None, actionable_message_metadata: Optional[Dict[str, Any]] = None, + task_source_metadata: Optional[Dict[str, Any]] = None, source_name: str = "Agent", is_debug: bool = False, ) -> RequestSpec: @@ -147,7 +148,12 @@ def _create_spec( _apply_priority_labels_and_actionable_toggle( json_payload, priority, labels, is_actionable_message_enabled ) - _apply_task_source(json_payload, source_name, is_debug=is_debug) + _apply_task_source( + json_payload, + source_name, + is_debug=is_debug, + custom_metadata=task_source_metadata, + ) return RequestSpec( method="POST", @@ -185,12 +191,20 @@ def _apply_priority_labels_and_actionable_toggle( def _apply_task_source( - payload: Dict[str, Any], source_name: str, is_debug: bool = False + payload: Dict[str, Any], + source_name: str, + is_debug: bool = False, + custom_metadata: Optional[Dict[str, Any]] = None, ) -> None: """Populate ``payload["taskSource"]`` when UiPathConfig has project_id + trace_id. Shared between AppTask and QuickForm spec builders — the taskSource block is identical for both task types. + + ``custom_metadata`` is merged into ``taskSourceMetadata`` on top of the built-in + InstanceId/FolderKey/JobKey/ProcessKey keys, so a caller-supplied key (e.g. a + conversational-agent id a HITL task should carry) wins on collision rather than + being silently dropped. """ project_id = UiPathConfig.project_id trace_id = UiPathConfig.trace_id @@ -204,6 +218,7 @@ def _apply_task_source( "FolderKey": UiPathConfig.folder_key, "JobKey": UiPathConfig.job_key, "ProcessKey": UiPathConfig.process_uuid, + **(custom_metadata or {}), }, "jobId": UiPathConfig.job_key, } @@ -488,6 +503,7 @@ async def create_async( labels: Optional[List[str]] = None, is_actionable_message_enabled: Optional[bool] = None, actionable_message_metadata: Optional[Dict[str, Any]] = None, + task_source_metadata: Optional[Dict[str, Any]] = None, source_name: str = "Agent", ) -> Task: """Creates a new action asynchronously. @@ -507,6 +523,9 @@ async def create_async( labels: Optional list of labels for the task is_actionable_message_enabled: Optional boolean indicating whether actionable notifications are enabled for this task actionable_message_metadata: Optional metadata for the action + task_source_metadata: Optional extra keys merged into taskSource.taskSourceMetadata, + on top of the built-in InstanceId/FolderKey/JobKey/ProcessKey (e.g. a + conversational-agent id a HITL task should carry downstream) source_name: The name of the source that created the task. Defaults to 'Agent'. Returns: @@ -540,6 +559,7 @@ async def create_async( labels=labels, is_actionable_message_enabled=is_actionable_message_enabled, actionable_message_metadata=actionable_message_metadata, + task_source_metadata=task_source_metadata, source_name=source_name, is_debug=is_debug, ) @@ -582,6 +602,7 @@ def create( labels: Optional[List[str]] = None, is_actionable_message_enabled: Optional[bool] = None, actionable_message_metadata: Optional[Dict[str, Any]] = None, + task_source_metadata: Optional[Dict[str, Any]] = None, source_name: str = "Agent", ) -> Task: """Creates a new task synchronously. @@ -601,6 +622,9 @@ def create( labels: Optional list of labels for the task is_actionable_message_enabled: Optional boolean indicating whether actionable notifications are enabled for this task actionable_message_metadata: Optional metadata for the action + task_source_metadata: Optional extra keys merged into taskSource.taskSourceMetadata, + on top of the built-in InstanceId/FolderKey/JobKey/ProcessKey (e.g. a + conversational-agent id a HITL task should carry downstream) source_name: The name of the source that created the task. Defaults to 'Agent'. Returns: @@ -634,6 +658,7 @@ def create( labels=labels, is_actionable_message_enabled=is_actionable_message_enabled, actionable_message_metadata=actionable_message_metadata, + task_source_metadata=task_source_metadata, source_name=source_name, is_debug=is_debug, ) diff --git a/packages/uipath-platform/src/uipath/platform/common/interrupt_models.py b/packages/uipath-platform/src/uipath/platform/common/interrupt_models.py index a0533fc53..185148829 100644 --- a/packages/uipath-platform/src/uipath/platform/common/interrupt_models.py +++ b/packages/uipath-platform/src/uipath/platform/common/interrupt_models.py @@ -73,6 +73,7 @@ class CreateTask(BaseModel): labels: list[str] | None = None is_actionable_message_enabled: bool | None = None actionable_message_metadata: dict[str, Any] | None = None + task_source_metadata: dict[str, Any] | None = None source_name: str = "Agent" diff --git a/packages/uipath-platform/src/uipath/platform/resume_triggers/_protocol.py b/packages/uipath-platform/src/uipath/platform/resume_triggers/_protocol.py index 9721baa5c..02578221e 100644 --- a/packages/uipath-platform/src/uipath/platform/resume_triggers/_protocol.py +++ b/packages/uipath-platform/src/uipath/platform/resume_triggers/_protocol.py @@ -679,6 +679,7 @@ async def _handle_task_trigger( labels=value.labels, is_actionable_message_enabled=value.is_actionable_message_enabled, actionable_message_metadata=value.actionable_message_metadata, + task_source_metadata=value.task_source_metadata, source_name=value.source_name, ) if not action: diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index de9c97b46..051d32ba6 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -1024,3 +1024,65 @@ def test_create_skips_jit_when_not_a_studio_project( assert _requested_app_schemas(httpx_mock) assert _posted_body(httpx_mock, create_task_url)["folderPath"] == "Shared/Apps" + + +def test_create_merges_task_source_metadata( + httpx_mock: HTTPXMock, + service: TasksService, + create_task_url: str, + jit_debug_env: None, +) -> None: + _mock_create_task(httpx_mock, create_task_url) + + service.create( + title="Test Action", + app_key="test-app-key", + data={"test": "data"}, + task_source_metadata={"ConversationalAgentId": "agent-1"}, + ) + + task_source_metadata = _posted_body(httpx_mock, create_task_url)["taskSource"][ + "taskSourceMetadata" + ] + assert task_source_metadata["ConversationalAgentId"] == "agent-1" + # The built-in correlation keys are still populated alongside the extra one. + assert task_source_metadata["InstanceId"] == "trace-1" + + +async def test_create_async_merges_task_source_metadata( + httpx_mock: HTTPXMock, + service: TasksService, + create_task_url: str, + jit_debug_env: None, +) -> None: + _mock_create_task(httpx_mock, create_task_url) + + await service.create_async( + title="Test Action", + app_key="test-app-key", + data={"test": "data"}, + task_source_metadata={"ConversationalAgentId": "agent-1"}, + ) + + task_source_metadata = _posted_body(httpx_mock, create_task_url)["taskSource"][ + "taskSourceMetadata" + ] + assert task_source_metadata["ConversationalAgentId"] == "agent-1" + assert task_source_metadata["InstanceId"] == "trace-1" + + +def test_create_omits_extra_task_source_metadata_when_unset( + httpx_mock: HTTPXMock, + service: TasksService, + create_task_url: str, + jit_debug_env: None, +) -> None: + _mock_create_task(httpx_mock, create_task_url) + + service.create(title="Test Action", app_key="test-app-key", data={"test": "data"}) + + task_source_metadata = _posted_body(httpx_mock, create_task_url)["taskSource"][ + "taskSourceMetadata" + ] + assert "ConversationalAgentId" not in task_source_metadata + assert task_source_metadata["InstanceId"] == "trace-1" diff --git a/packages/uipath-platform/tests/services/test_hitl.py b/packages/uipath-platform/tests/services/test_hitl.py index 96b0ed185..5699125a9 100644 --- a/packages/uipath-platform/tests/services/test_hitl.py +++ b/packages/uipath-platform/tests/services/test_hitl.py @@ -1278,6 +1278,7 @@ async def test_create_resume_trigger_create_task( labels=None, is_actionable_message_enabled=None, actionable_message_metadata=None, + task_source_metadata=None, source_name="Agent", ) diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index 2172f797c..931b1cd38 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.2.23" +version = "0.2.24" source = { editable = "." } dependencies = [ { name = "anyio" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index bf9895bf0..3b0d2aabd 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.11" [options] -exclude-newer = "2026-08-25T00:14:27.5403279Z" +exclude-newer = "2026-09-01T08:30:36.118554Z" exclude-newer-span = "P2D" [options.exclude-newer-package] @@ -2762,7 +2762,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.23" +version = "0.2.24" source = { editable = "../uipath-platform" } dependencies = [ { name = "anyio" },