Skip to content

Commit a927b7e

Browse files
feat: add IS resume triggers (#1580)
1 parent 79756aa commit a927b7e

12 files changed

Lines changed: 446 additions & 7 deletions

File tree

packages/uipath-core/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-core"
3-
version = "0.5.14"
3+
version = "0.5.15"
44
description = "UiPath Core abstractions"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-core/src/uipath/core/triggers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
"UiPathResumeTrigger",
55
"UiPathResumeTriggerType",
66
"UiPathApiTrigger",
7+
"UiPathIntegrationTrigger",
78
"UiPathResumeTriggerName",
89
]
910

1011
from uipath.core.triggers.trigger import (
1112
UiPathApiTrigger,
13+
UiPathIntegrationTrigger,
1214
UiPathResumeTrigger,
1315
UiPathResumeTriggerName,
1416
UiPathResumeTriggerType,

packages/uipath-core/src/uipath/core/triggers/trigger.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,25 @@ class UiPathApiTrigger(BaseModel):
5353
model_config = ConfigDict(validate_by_name=True)
5454

5555

56+
class UiPathIntegrationTrigger(BaseModel):
57+
"""Integration Services (Inbox) resume trigger request.
58+
59+
Mirrors Orchestrator's `IntegrationResumeDto`: the configuration needed to
60+
register a remote event trigger through the Connections service and
61+
correlate the eventual payload back to the suspended job via `inbox_id`.
62+
"""
63+
64+
connector: str = Field(alias="connector")
65+
connection_id: str = Field(alias="connectionId")
66+
operation: str = Field(alias="operation")
67+
object_name: str = Field(alias="objectName")
68+
filter_expression: str | None = Field(default=None, alias="filterExpression")
69+
parameters: dict[str, str] | None = Field(default=None, alias="parameters")
70+
inbox_id: str = Field(alias="inboxId")
71+
72+
model_config = ConfigDict(validate_by_name=True)
73+
74+
5675
class UiPathResumeTrigger(BaseModel):
5776
"""Information needed to resume execution."""
5877

@@ -65,6 +84,9 @@ class UiPathResumeTrigger(BaseModel):
6584
)
6685
item_key: str | None = Field(default=None, alias="itemKey")
6786
api_resume: UiPathApiTrigger | None = Field(default=None, alias="apiResume")
87+
integration_resume: UiPathIntegrationTrigger | None = Field(
88+
default=None, alias="integrationResume"
89+
)
6890
folder_path: str | None = Field(default=None, alias="folderPath")
6991
folder_key: str | None = Field(default=None, alias="folderKey")
7092
payload: Any | None = Field(default=None, alias="interruptObject", exclude=True)

packages/uipath-core/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/uipath-platform/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.1.39"
3+
version = "0.1.40"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/common/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
WaitEphemeralIndex,
4949
WaitEphemeralIndexRaw,
5050
WaitEscalation,
51+
WaitIntegrationEvent,
5152
WaitJob,
5253
WaitJobRaw,
5354
WaitSystemAgent,
@@ -89,6 +90,7 @@
8990
"WaitEphemeralIndexRaw",
9091
"DocumentExtractionValidation",
9192
"WaitDocumentExtractionValidation",
93+
"WaitIntegrationEvent",
9294
"RequestSpec",
9395
"Endpoint",
9496
"UiPathUrl",

packages/uipath-platform/src/uipath/platform/common/interrupt_models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,23 @@ class WaitDocumentExtractionValidation(BaseModel):
259259

260260
extraction_validation: StartExtractionValidationResponse
261261
task_url: str | None = None
262+
263+
264+
class WaitIntegrationEvent(BaseModel):
265+
"""Model representing a wait on an Integration Services event.
266+
267+
Used to suspend a job until a remote event (e.g. Slack message, Teams reply)
268+
is delivered by Integration Services. The SDK resolves `connection_name`
269+
(scoped to `connection_folder_path` when provided) to the underlying
270+
connection id and generates a fresh `inbox_id` when the trigger is created;
271+
the rest of the fields describe which remote event to subscribe to via
272+
the Connections service.
273+
"""
274+
275+
connector: str
276+
connection_name: str
277+
connection_folder_path: str | None = None
278+
operation: str
279+
object_name: str
280+
filter_expression: str | None = None
281+
parameters: dict[str, str] | None = None

packages/uipath-platform/src/uipath/platform/orchestrator/_jobs_service.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,52 @@ def _retrieve_api_payload_spec(
674674
},
675675
)
676676

677+
def retrieve_inbox_payload(self, inbox_id: str) -> Any:
678+
"""Fetch payload data for Integration Services (Inbox) triggers.
679+
680+
Unlike `retrieve_api_payload`, this returns the response body as-is.
681+
Orchestrator's `GET /JobTriggers/GetPayload/{inboxId}` returns the
682+
stored payload directly without an envelope.
683+
684+
Args:
685+
inbox_id: The Id of the inbox to fetch the payload for.
686+
687+
Returns:
688+
The stored payload.
689+
"""
690+
spec = self._retrieve_api_payload_spec(inbox_id=inbox_id)
691+
692+
response = self.request(
693+
spec.method,
694+
url=spec.endpoint,
695+
headers=spec.headers,
696+
)
697+
698+
return response.json()
699+
700+
async def retrieve_inbox_payload_async(self, inbox_id: str) -> Any:
701+
"""Asynchronously fetch payload data for Integration Services (Inbox) triggers.
702+
703+
Unlike `retrieve_api_payload_async`, this returns the response body
704+
as-is. Orchestrator's `GET /JobTriggers/GetPayload/{inboxId}` returns
705+
the stored payload directly without an envelope.
706+
707+
Args:
708+
inbox_id: The Id of the inbox to fetch the payload for.
709+
710+
Returns:
711+
The stored payload.
712+
"""
713+
spec = self._retrieve_api_payload_spec(inbox_id=inbox_id)
714+
715+
response = await self.request_async(
716+
spec.method,
717+
url=spec.endpoint,
718+
headers=spec.headers,
719+
)
720+
721+
return response.json()
722+
677723
def _extract_first_inbox_id(self, response: Any) -> str:
678724
if len(response["value"]) > 0:
679725
return response["value"][0]["ItemKey"]

packages/uipath-platform/src/uipath/platform/resume_triggers/_protocol.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from uipath.core.serialization import serialize_object
1414
from uipath.core.triggers import (
1515
UiPathApiTrigger,
16+
UiPathIntegrationTrigger,
1617
UiPathResumeTrigger,
1718
UiPathResumeTriggerName,
1819
UiPathResumeTriggerType,
@@ -43,11 +44,13 @@
4344
WaitEphemeralIndex,
4445
WaitEphemeralIndexRaw,
4546
WaitEscalation,
47+
WaitIntegrationEvent,
4648
WaitJob,
4749
WaitJobRaw,
4850
WaitSystemAgent,
4951
WaitTask,
5052
)
53+
from uipath.platform.connections import EventArguments
5154
from uipath.platform.context_grounding import DeepRagStatus, IndexStatus
5255
from uipath.platform.context_grounding.context_grounding_index import (
5356
ContextGroundingIndex,
@@ -401,6 +404,23 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None:
401404
f"Error fetching API trigger payload for inbox {trigger.api_resume.inbox_id}: {str(e)}",
402405
) from e
403406

407+
case UiPathResumeTriggerType.INBOX:
408+
if trigger.integration_resume and trigger.integration_resume.inbox_id:
409+
try:
410+
inbox_payload = await uipath.jobs.retrieve_inbox_payload_async(
411+
trigger.integration_resume.inbox_id
412+
)
413+
event_args = EventArguments.model_validate(inbox_payload)
414+
return await uipath.connections.retrieve_event_payload_async(
415+
event_args
416+
)
417+
except Exception as e:
418+
raise UiPathFaultedTriggerError(
419+
ErrorCategory.SYSTEM,
420+
f"Failed to get trigger payload"
421+
f"Error fetching Inbox trigger payload for inbox {trigger.integration_resume.inbox_id}: {str(e)}",
422+
) from e
423+
404424
case _:
405425
raise UiPathFaultedTriggerError(
406426
ErrorCategory.SYSTEM,
@@ -461,6 +481,9 @@ async def create_trigger(self, suspend_value: Any) -> UiPathResumeTrigger:
461481
case UiPathResumeTriggerType.API:
462482
self._handle_api_trigger(suspend_value, resume_trigger)
463483

484+
case UiPathResumeTriggerType.INBOX:
485+
await self._handle_inbox_trigger(suspend_value, resume_trigger)
486+
464487
case UiPathResumeTriggerType.DEEP_RAG:
465488
await self._handle_deep_rag_job_trigger(
466489
suspend_value, resume_trigger
@@ -545,6 +568,8 @@ def _determine_trigger_type(self, value: Any) -> UiPathResumeTriggerType:
545568
value, (DocumentExtractionValidation, WaitDocumentExtractionValidation)
546569
):
547570
return UiPathResumeTriggerType.IXP_VS_ESCALATION
571+
if isinstance(value, WaitIntegrationEvent):
572+
return UiPathResumeTriggerType.INBOX
548573
# default to API trigger
549574
return UiPathResumeTriggerType.API
550575

@@ -579,6 +604,8 @@ def _determine_trigger_name(self, value: Any) -> UiPathResumeTriggerName:
579604
return UiPathResumeTriggerName.BATCH_RAG
580605
if isinstance(value, (DocumentExtraction, WaitDocumentExtraction)):
581606
return UiPathResumeTriggerName.EXTRACTION
607+
if isinstance(value, WaitIntegrationEvent):
608+
return UiPathResumeTriggerName.INBOX
582609
# default to API trigger
583610
return UiPathResumeTriggerName.API
584611

@@ -901,6 +928,57 @@ def _handle_api_trigger(
901928
inbox_id=str(uuid.uuid4()), request=serialize_object(value)
902929
)
903930

931+
async def _handle_inbox_trigger(
932+
self, value: WaitIntegrationEvent, resume_trigger: UiPathResumeTrigger
933+
) -> None:
934+
"""Handle Inbox-type resume triggers.
935+
936+
Resolves `connection_name` (scoped to `connection_folder_path` when
937+
provided) to a connection id via the Connections service, populates
938+
`integration_resume` with the Integration Services configuration plus a
939+
freshly generated `inbox_id`. The Connections-service registration is
940+
performed server-side by Orchestrator's `CreateResumeTriggerTaskHandler`
941+
once the job suspends.
942+
943+
Args:
944+
value: The suspend value (WaitIntegrationEvent)
945+
resume_trigger: The resume trigger to populate
946+
947+
Raises:
948+
Exception: If no connection matches `connection_name`, or if more
949+
than one exact match is found.
950+
"""
951+
uipath = UiPath()
952+
connections = await uipath.connections.list_async(
953+
name=value.connection_name,
954+
folder_path=value.connection_folder_path,
955+
connector_key=value.connector,
956+
)
957+
connection = next(
958+
(c for c in connections if c.name == value.connection_name), None
959+
)
960+
if connection is None:
961+
raise Exception(
962+
f"No connection named '{value.connection_name}' "
963+
f"for connector '{value.connector}' found"
964+
+ (
965+
f" in folder '{value.connection_folder_path}'"
966+
if value.connection_folder_path
967+
else ""
968+
)
969+
)
970+
assert connection.id is not None
971+
972+
resume_trigger.integration_resume = UiPathIntegrationTrigger(
973+
connector=value.connector,
974+
connection_id=connection.id,
975+
operation=value.operation,
976+
object_name=value.object_name,
977+
filter_expression=value.filter_expression,
978+
parameters=value.parameters,
979+
inbox_id=str(uuid.uuid4()),
980+
)
981+
904982

905983
class UiPathResumeTriggerHandler:
906984
"""Combined handler for creating and reading resume triggers.

0 commit comments

Comments
 (0)