uipath 2.0.63__tar.gz → 2.0.65__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of uipath might be problematic. Click here for more details.

Files changed (194) hide show
  1. {uipath-2.0.63 → uipath-2.0.65}/PKG-INFO +1 -1
  2. {uipath-2.0.63 → uipath-2.0.65}/pyproject.toml +1 -1
  3. uipath-2.0.65/src/uipath/_cli/_runtime/_escalation.py +235 -0
  4. uipath-2.0.65/src/uipath/_cli/_runtime/_hitl.py +257 -0
  5. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_utils/_common.py +26 -0
  6. uipath-2.0.65/src/uipath/_cli/_utils/_tracing.py +52 -0
  7. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/jobs_service.py +53 -0
  8. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/tracing/_traced.py +66 -6
  9. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/conftest.py +3 -1
  10. uipath-2.0.65/tests/cli/test_hitl.py +314 -0
  11. uipath-2.0.65/tests/cli/test_utils.py +119 -0
  12. {uipath-2.0.63 → uipath-2.0.65}/uv.lock +1697 -1698
  13. {uipath-2.0.63 → uipath-2.0.65}/.cursorrules +0 -0
  14. {uipath-2.0.63 → uipath-2.0.65}/.editorconfig +0 -0
  15. {uipath-2.0.63 → uipath-2.0.65}/.gitattributes +0 -0
  16. {uipath-2.0.63 → uipath-2.0.65}/.github/workflows/cd.yml +0 -0
  17. {uipath-2.0.63 → uipath-2.0.65}/.github/workflows/ci.yml +0 -0
  18. {uipath-2.0.63 → uipath-2.0.65}/.github/workflows/commitlint.yml +0 -0
  19. {uipath-2.0.63 → uipath-2.0.65}/.github/workflows/lint.yml +0 -0
  20. {uipath-2.0.63 → uipath-2.0.65}/.github/workflows/publish-dev.yml +0 -0
  21. {uipath-2.0.63 → uipath-2.0.65}/.github/workflows/publish-docs.yml +0 -0
  22. {uipath-2.0.63 → uipath-2.0.65}/.github/workflows/slack.yml +0 -0
  23. {uipath-2.0.63 → uipath-2.0.65}/.github/workflows/test.yml +0 -0
  24. {uipath-2.0.63 → uipath-2.0.65}/.gitignore +0 -0
  25. {uipath-2.0.63 → uipath-2.0.65}/.pre-commit-config.yaml +0 -0
  26. {uipath-2.0.63 → uipath-2.0.65}/.python-version +0 -0
  27. {uipath-2.0.63 → uipath-2.0.65}/.vscode/extensions.json +0 -0
  28. {uipath-2.0.63 → uipath-2.0.65}/.vscode/settings.json +0 -0
  29. {uipath-2.0.63 → uipath-2.0.65}/CONTRIBUTING.md +0 -0
  30. {uipath-2.0.63 → uipath-2.0.65}/LICENSE +0 -0
  31. {uipath-2.0.63 → uipath-2.0.65}/README.md +0 -0
  32. {uipath-2.0.63 → uipath-2.0.65}/docs/CONTRIBUTING.md +0 -0
  33. {uipath-2.0.63 → uipath-2.0.65}/docs/FAQ.md +0 -0
  34. {uipath-2.0.63 → uipath-2.0.65}/docs/assets/env-preparation-failed-dark.png +0 -0
  35. {uipath-2.0.63 → uipath-2.0.65}/docs/assets/env-preparation-failed-light.png +0 -0
  36. {uipath-2.0.63 → uipath-2.0.65}/docs/assets/favicon.png +0 -0
  37. {uipath-2.0.63 → uipath-2.0.65}/docs/assets/logo-dark.svg +0 -0
  38. {uipath-2.0.63 → uipath-2.0.65}/docs/assets/logo-light.svg +0 -0
  39. {uipath-2.0.63 → uipath-2.0.65}/docs/cli/index.md +0 -0
  40. {uipath-2.0.63 → uipath-2.0.65}/docs/core/actions.md +0 -0
  41. {uipath-2.0.63 → uipath-2.0.65}/docs/core/assets/cloud_env_var_dark.gif +0 -0
  42. {uipath-2.0.63 → uipath-2.0.65}/docs/core/assets/cloud_env_var_light.gif +0 -0
  43. {uipath-2.0.63 → uipath-2.0.65}/docs/core/assets/cloud_env_var_secret_dark.png +0 -0
  44. {uipath-2.0.63 → uipath-2.0.65}/docs/core/assets/cloud_env_var_secret_light.png +0 -0
  45. {uipath-2.0.63 → uipath-2.0.65}/docs/core/assets/copy_path_dark.png +0 -0
  46. {uipath-2.0.63 → uipath-2.0.65}/docs/core/assets/copy_path_light.png +0 -0
  47. {uipath-2.0.63 → uipath-2.0.65}/docs/core/assets.md +0 -0
  48. {uipath-2.0.63 → uipath-2.0.65}/docs/core/attachments.md +0 -0
  49. {uipath-2.0.63 → uipath-2.0.65}/docs/core/buckets.md +0 -0
  50. {uipath-2.0.63 → uipath-2.0.65}/docs/core/connections.md +0 -0
  51. {uipath-2.0.63 → uipath-2.0.65}/docs/core/context_grounding.md +0 -0
  52. {uipath-2.0.63 → uipath-2.0.65}/docs/core/environment_variables.md +0 -0
  53. {uipath-2.0.63 → uipath-2.0.65}/docs/core/getting_started.md +0 -0
  54. {uipath-2.0.63 → uipath-2.0.65}/docs/core/jobs.md +0 -0
  55. {uipath-2.0.63 → uipath-2.0.65}/docs/core/processes.md +0 -0
  56. {uipath-2.0.63 → uipath-2.0.65}/docs/core/queues.md +0 -0
  57. {uipath-2.0.63 → uipath-2.0.65}/docs/core/traced.md +0 -0
  58. {uipath-2.0.63 → uipath-2.0.65}/docs/hooks.py +0 -0
  59. {uipath-2.0.63 → uipath-2.0.65}/docs/javascripts/extra.js +0 -0
  60. {uipath-2.0.63 → uipath-2.0.65}/docs/langchain/chat_models.md +0 -0
  61. {uipath-2.0.63 → uipath-2.0.65}/docs/langchain/context_grounding.md +0 -0
  62. {uipath-2.0.63 → uipath-2.0.65}/docs/langchain/human_in_the_loop.md +0 -0
  63. {uipath-2.0.63 → uipath-2.0.65}/docs/overrides/main.html +0 -0
  64. {uipath-2.0.63 → uipath-2.0.65}/docs/overrides/partials/actions.html +0 -0
  65. {uipath-2.0.63 → uipath-2.0.65}/docs/overrides/partials/logo.html +0 -0
  66. {uipath-2.0.63 → uipath-2.0.65}/docs/release_policy.md +0 -0
  67. {uipath-2.0.63 → uipath-2.0.65}/docs/stylesheets/extra.css +0 -0
  68. {uipath-2.0.63 → uipath-2.0.65}/justfile +0 -0
  69. {uipath-2.0.63 → uipath-2.0.65}/mkdocs.yml +0 -0
  70. {uipath-2.0.63 → uipath-2.0.65}/py.typed +0 -0
  71. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/__init__.py +0 -0
  72. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/README.md +0 -0
  73. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/__init__.py +0 -0
  74. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/_auth_server.py +0 -0
  75. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/_models.py +0 -0
  76. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
  77. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/_portal_service.py +0 -0
  78. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/_utils.py +0 -0
  79. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/auth_config.json +0 -0
  80. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/index.html +0 -0
  81. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/localhost.crt +0 -0
  82. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_auth/localhost.key +0 -0
  83. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_runtime/_contracts.py +0 -0
  84. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_runtime/_logging.py +0 -0
  85. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_runtime/_runtime.py +0 -0
  86. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
  87. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_templates/.rels.template +0 -0
  88. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
  89. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_templates/main.py.template +0 -0
  90. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
  91. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_utils/_console.py +0 -0
  92. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_utils/_constants.py +0 -0
  93. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_utils/_folders.py +0 -0
  94. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_utils/_input_args.py +0 -0
  95. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
  96. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/_utils/_processes.py +0 -0
  97. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/cli_auth.py +0 -0
  98. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/cli_deploy.py +0 -0
  99. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/cli_init.py +0 -0
  100. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/cli_invoke.py +0 -0
  101. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/cli_new.py +0 -0
  102. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/cli_pack.py +0 -0
  103. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/cli_publish.py +0 -0
  104. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/cli_run.py +0 -0
  105. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/middlewares.py +0 -0
  106. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_cli/spinner.py +0 -0
  107. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_config.py +0 -0
  108. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_execution_context.py +0 -0
  109. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_folder_context.py +0 -0
  110. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/__init__.py +0 -0
  111. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/_base_service.py +0 -0
  112. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/actions_service.py +0 -0
  113. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/api_client.py +0 -0
  114. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/assets_service.py +0 -0
  115. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/attachments_service.py +0 -0
  116. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/buckets_service.py +0 -0
  117. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/connections_service.py +0 -0
  118. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/context_grounding_service.py +0 -0
  119. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/folder_service.py +0 -0
  120. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/llm_gateway_service.py +0 -0
  121. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/processes_service.py +0 -0
  122. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_services/queues_service.py +0 -0
  123. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_uipath.py +0 -0
  124. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/__init__.py +0 -0
  125. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/_endpoint.py +0 -0
  126. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/_infer_bindings.py +0 -0
  127. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/_logs.py +0 -0
  128. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/_read_overwrites.py +0 -0
  129. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/_request_override.py +0 -0
  130. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/_request_spec.py +0 -0
  131. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/_url.py +0 -0
  132. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/_user_agent.py +0 -0
  133. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/_utils/constants.py +0 -0
  134. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/__init__.py +0 -0
  135. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/action_schema.py +0 -0
  136. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/actions.py +0 -0
  137. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/assets.py +0 -0
  138. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/attachment.py +0 -0
  139. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/buckets.py +0 -0
  140. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/connections.py +0 -0
  141. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/context_grounding.py +0 -0
  142. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/context_grounding_index.py +0 -0
  143. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/errors.py +0 -0
  144. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/exceptions.py +0 -0
  145. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/interrupt_models.py +0 -0
  146. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/job.py +0 -0
  147. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/llm_gateway.py +0 -0
  148. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/processes.py +0 -0
  149. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/models/queues.py +0 -0
  150. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/py.typed +0 -0
  151. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/telemetry/__init__.py +0 -0
  152. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/telemetry/_constants.py +0 -0
  153. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/telemetry/_track.py +0 -0
  154. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/tracing/__init__.py +0 -0
  155. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/tracing/_otel_exporters.py +0 -0
  156. {uipath-2.0.63 → uipath-2.0.65}/src/uipath/tracing/_utils.py +0 -0
  157. {uipath-2.0.63 → uipath-2.0.65}/tests/__init__.py +0 -0
  158. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/mocks/bindings_script.py +0 -0
  159. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/mocks/pyproject.toml +0 -0
  160. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/mocks/simple_script.py +0 -0
  161. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/mocks/uipath-mock.json +0 -0
  162. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/mocks/uipath-simple-script-mock.json +0 -0
  163. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/test_init.py +0 -0
  164. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/test_invoke.py +0 -0
  165. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/test_new.py +0 -0
  166. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/test_pack.py +0 -0
  167. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/test_publish.py +0 -0
  168. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/test_run.py +0 -0
  169. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/utils/project_details.py +0 -0
  170. {uipath-2.0.63 → uipath-2.0.65}/tests/cli/utils/uipath_json.py +0 -0
  171. {uipath-2.0.63 → uipath-2.0.65}/tests/conftest.py +0 -0
  172. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/conftest.py +0 -0
  173. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_actions_service.py +0 -0
  174. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_api_client.py +0 -0
  175. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_assets_service.py +0 -0
  176. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_attachments_service.py +0 -0
  177. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_base_service.py +0 -0
  178. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_buckets_service.py +0 -0
  179. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_connections_service.py +0 -0
  180. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_context_grounding_service.py +0 -0
  181. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_folder_service.py +0 -0
  182. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_jobs_service.py +0 -0
  183. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_llm_integration.py +0 -0
  184. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_llm_service.py +0 -0
  185. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_processes_service.py +0 -0
  186. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_queues_service.py +0 -0
  187. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
  188. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/test_bindings_inference.py +0 -0
  189. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/test_config.py +0 -0
  190. {uipath-2.0.63 → uipath-2.0.65}/tests/sdk/test_overwrites.py +0 -0
  191. {uipath-2.0.63 → uipath-2.0.65}/tests/tracing/test_otel_exporters.py +0 -0
  192. {uipath-2.0.63 → uipath-2.0.65}/tests/tracing/test_span_utils.py +0 -0
  193. {uipath-2.0.63 → uipath-2.0.65}/tests/tracing/test_traced.py +0 -0
  194. {uipath-2.0.63 → uipath-2.0.65}/tests/tracing/test_tracing_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath
3
- Version: 2.0.63
3
+ Version: 2.0.65
4
4
  Summary: Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools.
5
5
  Project-URL: Homepage, https://uipath.com
6
6
  Project-URL: Repository, https://github.com/UiPath/uipath-python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uipath"
3
- version = "2.0.63"
3
+ version = "2.0.65"
4
4
  description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
5
5
  readme = { file = "README.md", content-type = "text/markdown" }
6
6
  requires-python = ">=3.10"
@@ -0,0 +1,235 @@
1
+ import json
2
+ import logging
3
+ from pathlib import Path
4
+ from typing import Any, Dict, Optional, Union
5
+
6
+ from uipath import UiPath
7
+ from uipath.models.actions import Action
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class Escalation:
13
+ """Class to handle default escalation."""
14
+
15
+ def __init__(self, config_path: Union[str, Path] = "uipath.json"):
16
+ """Initialize the escalation with a config file path.
17
+
18
+ Args:
19
+ config_path: Path to the configuration file (string or Path object)
20
+ """
21
+ self.config_path = Path(config_path)
22
+ self._config = None
23
+ self._enabled = False
24
+ self._load_config()
25
+
26
+ def _load_config(self) -> None:
27
+ """Load and validate the default escalation from the config file.
28
+
29
+ If the 'defaultEscalation' section exists, validates required fields.
30
+ Raises error if required fields are missing.
31
+ """
32
+ try:
33
+ config_data = json.loads(self.config_path.read_text(encoding="utf-8"))
34
+ escalation_config = config_data.get("defaultEscalation")
35
+
36
+ if escalation_config:
37
+ required_fields = {"request", "title"}
38
+ missing_fields = [
39
+ field for field in required_fields if field not in escalation_config
40
+ ]
41
+
42
+ if not any(key in escalation_config for key in ("appName", "appKey")):
43
+ missing_fields.append("appName or appKey")
44
+
45
+ if missing_fields:
46
+ raise ValueError(
47
+ f"Missing required fields in configuration: {', '.join(missing_fields)}"
48
+ )
49
+
50
+ self._config = escalation_config
51
+ self._enabled = True
52
+ logger.debug("Escalation configuration loaded successfully")
53
+ else:
54
+ self._enabled = False
55
+
56
+ except FileNotFoundError:
57
+ logger.debug(f"Config file not found: {self.config_path}")
58
+ self._enabled = False
59
+
60
+ except json.JSONDecodeError:
61
+ logger.warning(
62
+ f"Failed to parse config file {self.config_path}: Invalid JSON"
63
+ )
64
+ self._enabled = False
65
+
66
+ except ValueError as e:
67
+ logger.error(str(e))
68
+ raise
69
+
70
+ except Exception as e:
71
+ logger.error(f"Unexpected error loading config {self.config_path}: {e}")
72
+ self._enabled = False
73
+
74
+ @property
75
+ def enabled(self) -> bool:
76
+ """Check if escalation is enabled.
77
+
78
+ Returns:
79
+ True if configuration is valid and loaded
80
+ """
81
+ return self._enabled
82
+
83
+ def prepare_data(self, value: Any) -> Dict[str, Any]:
84
+ """Prepare action data by replacing $VALUE placeholders with the provided value.
85
+
86
+ Args:
87
+ value: The value to substitute into the template
88
+
89
+ Returns:
90
+ Prepared data dictionary with substitutions applied
91
+ """
92
+ if not self.enabled or not self._config:
93
+ return {}
94
+
95
+ template = self._config.get("request", {})
96
+
97
+ if isinstance(value, str):
98
+ try:
99
+ value_obj = json.loads(value)
100
+ except json.JSONDecodeError:
101
+ value_obj = value
102
+ else:
103
+ value_obj = value
104
+
105
+ return self._substitute_values(template, value_obj)
106
+
107
+ def _substitute_values(
108
+ self, template: Dict[str, Any], value: Any
109
+ ) -> Dict[str, Any]:
110
+ """Replace template placeholders with actual values.
111
+
112
+ Args:
113
+ template: Template dictionary containing placeholders
114
+ value: Values to substitute into the template
115
+
116
+ Returns:
117
+ Template with values substituted
118
+ """
119
+
120
+ def process_value(template_value):
121
+ if isinstance(template_value, dict):
122
+ return {k: process_value(v) for k, v in template_value.items()}
123
+ elif isinstance(template_value, list):
124
+ return [process_value(item) for item in template_value]
125
+ elif isinstance(template_value, str):
126
+ if template_value == "$VALUE":
127
+ return value
128
+ elif template_value.startswith("$VALUE."):
129
+ return self._resolve_value_path(template_value, value)
130
+
131
+ return template_value
132
+
133
+ return process_value(template)
134
+
135
+ def _resolve_value_path(self, path_expr: str, value: Any) -> Any:
136
+ """Resolve a dot-notation path expression against a value.
137
+
138
+ Args:
139
+ path_expr: Path expression (e.g. "$VALUE.user.name")
140
+ value: Value object to extract data from
141
+
142
+ Returns:
143
+ Extracted value or None if path doesn't exist
144
+ """
145
+ path_parts = path_expr.replace("$VALUE.", "").split(".")
146
+ current = value
147
+
148
+ for part in path_parts:
149
+ if not isinstance(current, dict) or part not in current:
150
+ return None
151
+ current = current.get(part)
152
+
153
+ return current
154
+
155
+ def extract_response_value(self, action_data: Dict[str, Any]) -> Any:
156
+ if not self._config:
157
+ return ""
158
+
159
+ response_template = self._config.get("response")
160
+ if not response_template:
161
+ return ""
162
+
163
+ for key, template_value in response_template.items():
164
+ if key in action_data:
165
+ extracted_value = None
166
+
167
+ if template_value == "$VALUE":
168
+ extracted_value = action_data[key]
169
+ elif isinstance(template_value, str) and template_value.startswith(
170
+ "$VALUE."
171
+ ):
172
+ path_parts = template_value.replace("$VALUE.", "").split(".")
173
+ current = action_data[key]
174
+
175
+ valid_path = True
176
+ for part in path_parts:
177
+ if not isinstance(current, dict) or part not in current:
178
+ valid_path = False
179
+ break
180
+ current = current.get(part)
181
+
182
+ if valid_path:
183
+ extracted_value = current
184
+
185
+ if extracted_value is not None:
186
+ if isinstance(extracted_value, str):
187
+ if extracted_value.lower() == "true":
188
+ return True
189
+ elif extracted_value.lower() == "false":
190
+ return False
191
+
192
+ try:
193
+ if "." in extracted_value:
194
+ return float(extracted_value)
195
+ else:
196
+ return int(extracted_value)
197
+ except ValueError:
198
+ pass
199
+
200
+ return extracted_value
201
+
202
+ return action_data
203
+
204
+ async def create(self, value: Any) -> Optional[Action]:
205
+ """Create an escalation Action with the prepared data.
206
+
207
+ Args:
208
+ value: The dynamic value to be substituted into the template
209
+
210
+ Returns:
211
+ The created Action object or None if creation fails
212
+ """
213
+ if not self.enabled or not self._config:
214
+ return None
215
+
216
+ action_data = self.prepare_data(value)
217
+
218
+ if not action_data:
219
+ logger.warning("Action creation skipped: empty data after preparation")
220
+ return None
221
+
222
+ try:
223
+ uipath = UiPath()
224
+ action = uipath.actions.create(
225
+ title=self._config.get("title", "Default escalation"),
226
+ app_name=self._config.get("appName"),
227
+ app_key=self._config.get("appKey"),
228
+ app_version=self._config.get("appVersion", 1),
229
+ data=action_data,
230
+ )
231
+ logger.info(f"Action created successfully: {action.key}")
232
+ return action
233
+ except Exception as e:
234
+ logger.error(f"Error creating action: {e}")
235
+ return None
@@ -0,0 +1,257 @@
1
+ import json
2
+ import uuid
3
+ from dataclasses import dataclass
4
+ from functools import cached_property
5
+ from typing import Any, Optional
6
+
7
+ from uipath import UiPath
8
+ from uipath.models import CreateAction, InvokeProcess, WaitAction, WaitJob
9
+
10
+ from .._runtime._contracts import (
11
+ UiPathApiTrigger,
12
+ UiPathErrorCategory,
13
+ UiPathResumeTrigger,
14
+ UiPathResumeTriggerType,
15
+ UiPathRuntimeError,
16
+ UiPathRuntimeStatus,
17
+ )
18
+ from .._utils._common import serialize_object
19
+ from ._escalation import Escalation
20
+
21
+
22
+ def _try_convert_to_json_format(value: str | None) -> str | None:
23
+ """Attempts to parse a string as JSON and returns the parsed object or original string.
24
+
25
+ Args:
26
+ value: The string value to attempt JSON parsing on.
27
+
28
+ Returns:
29
+ The parsed JSON object if successful, otherwise the original string value.
30
+ """
31
+ try:
32
+ if not value:
33
+ return None
34
+ return json.loads(value)
35
+ except json.decoder.JSONDecodeError:
36
+ return value
37
+
38
+
39
+ class HitlReader:
40
+ """Handles reading and retrieving Human-In-The-Loop (HITL) data from UiPath services."""
41
+
42
+ @classmethod
43
+ async def read(cls, resume_trigger: UiPathResumeTrigger) -> Optional[str]:
44
+ """Reads data from a UiPath resume trigger based on its type.
45
+
46
+ This method handles different types of resume triggers (ACTION, JOB, API) and retrieves
47
+ the appropriate data from UiPath services. For actions, it retrieves action data with
48
+ optional escalation processing. For jobs, it retrieves job output and validates success.
49
+ For API triggers, it retrieves the API payload.
50
+
51
+ Args:
52
+ resume_trigger: The UiPath resume trigger containing the trigger type and metadata.
53
+
54
+ Returns:
55
+ The retrieved data as a string, or None if no data is available.
56
+
57
+ Raises:
58
+ UiPathRuntimeError: If the job failed, API connection failed, trigger type is unknown,
59
+ or HITL feedback retrieval failed.
60
+ """
61
+ uipath = UiPath()
62
+ default_escalation = Escalation()
63
+ match resume_trigger.trigger_type:
64
+ case UiPathResumeTriggerType.ACTION:
65
+ if resume_trigger.item_key:
66
+ action = await uipath.actions.retrieve_async(
67
+ resume_trigger.item_key,
68
+ app_folder_key=resume_trigger.folder_key,
69
+ app_folder_path=resume_trigger.folder_path,
70
+ )
71
+
72
+ if default_escalation.enabled:
73
+ return default_escalation.extract_response_value(action.data)
74
+
75
+ return action.data
76
+
77
+ case UiPathResumeTriggerType.JOB:
78
+ if resume_trigger.item_key:
79
+ job = await uipath.jobs.retrieve_async(
80
+ resume_trigger.item_key,
81
+ folder_key=resume_trigger.folder_key,
82
+ folder_path=resume_trigger.folder_path,
83
+ )
84
+ if (
85
+ job.state
86
+ and not job.state.lower()
87
+ == UiPathRuntimeStatus.SUCCESSFUL.value.lower()
88
+ ):
89
+ raise UiPathRuntimeError(
90
+ "INVOKED_PROCESS_FAILURE",
91
+ "Invoked process did not finish successfully.",
92
+ _try_convert_to_json_format(str(job.job_error or job.info))
93
+ or "Job error unavailable.",
94
+ )
95
+ return _try_convert_to_json_format(job.output_arguments)
96
+
97
+ case UiPathResumeTriggerType.API:
98
+ if resume_trigger.api_resume and resume_trigger.api_resume.inbox_id:
99
+ try:
100
+ return await uipath.jobs.retrieve_api_payload_async(
101
+ resume_trigger.api_resume.inbox_id
102
+ )
103
+ except Exception as e:
104
+ raise UiPathRuntimeError(
105
+ "API_CONNECTION_ERROR",
106
+ "Failed to get trigger payload",
107
+ f"Error fetching API trigger payload for inbox {resume_trigger.api_resume.inbox_id}: {str(e)}",
108
+ UiPathErrorCategory.SYSTEM,
109
+ ) from e
110
+ case _:
111
+ raise UiPathRuntimeError(
112
+ "UNKNOWN_TRIGGER_TYPE",
113
+ "Unexpected trigger type received",
114
+ f"Trigger type :{type(resume_trigger.trigger_type)} is invalid",
115
+ UiPathErrorCategory.USER,
116
+ )
117
+
118
+ raise UiPathRuntimeError(
119
+ "HITL_FEEDBACK_FAILURE",
120
+ "Failed to receive payload from HITL action",
121
+ detail="Failed to receive payload from HITL action",
122
+ category=UiPathErrorCategory.SYSTEM,
123
+ )
124
+
125
+
126
+ @dataclass
127
+ class HitlProcessor:
128
+ """Processes events in a Human-(Robot/Agent)-In-The-Loop scenario.
129
+
130
+ This class handles the creation and processing of HITL resume triggers for different
131
+ types of UiPath operations including actions, jobs, and API calls. It determines the
132
+ appropriate trigger type based on the input value and creates the corresponding
133
+ resume trigger with proper configuration.
134
+
135
+ Attributes:
136
+ value: The input value to be processed, can be various UiPath model types or strings.
137
+ """
138
+
139
+ value: Any
140
+
141
+ @cached_property
142
+ def type(self) -> UiPathResumeTriggerType:
143
+ """Determines the resume trigger type based on the input value.
144
+
145
+ Analyzes the input value type and returns the corresponding UiPath resume trigger type.
146
+ Actions (CreateAction, WaitAction) map to ACTION type, jobs (InvokeProcess, WaitJob)
147
+ map to JOB type, and all other values default to API type.
148
+
149
+ Returns:
150
+ The appropriate UiPathResumeTriggerType based on the input value type.
151
+ """
152
+ if isinstance(self.value, CreateAction) or isinstance(self.value, WaitAction):
153
+ return UiPathResumeTriggerType.ACTION
154
+ if isinstance(self.value, InvokeProcess) or isinstance(self.value, WaitJob):
155
+ return UiPathResumeTriggerType.JOB
156
+ # default to API trigger
157
+ return UiPathResumeTriggerType.API
158
+
159
+ async def create_resume_trigger(self) -> UiPathResumeTrigger:
160
+ """Creates a UiPath resume trigger based on the input value and its type.
161
+
162
+ This method processes the input value and creates an appropriate resume trigger
163
+ for HITL scenarios. It handles different input types:
164
+ - Actions: Creates or references UiPath actions with folder information
165
+ - Jobs: Invokes processes or references existing jobs with folder information
166
+ - API: Creates API triggers with generated inbox IDs
167
+ - String with escalation: Creates escalated actions
168
+
169
+ Returns:
170
+ A configured UiPathResumeTrigger ready for HITL processing.
171
+
172
+ Raises:
173
+ UiPathRuntimeError: If action/job creation fails, escalation fails, or an
174
+ unknown model type is encountered.
175
+ Exception: If any underlying UiPath service calls fail.
176
+ """
177
+ uipath = UiPath()
178
+ default_escalation = Escalation()
179
+
180
+ try:
181
+ hitl_input = self.value
182
+ resume_trigger = UiPathResumeTrigger(
183
+ trigger_type=self.type, payload=serialize_object(hitl_input)
184
+ )
185
+
186
+ # check for default escalation config
187
+ if default_escalation.enabled and isinstance(hitl_input, str):
188
+ resume_trigger.trigger_type = UiPathResumeTriggerType.ACTION
189
+ action = await default_escalation.create(hitl_input)
190
+ if not action:
191
+ raise Exception("Failed to create default escalation")
192
+ resume_trigger.item_key = action.key
193
+ return resume_trigger
194
+
195
+ match self.type:
196
+ case UiPathResumeTriggerType.ACTION:
197
+ resume_trigger.folder_path = hitl_input.app_folder_path
198
+ resume_trigger.folder_key = hitl_input.app_folder_key
199
+ if isinstance(hitl_input, WaitAction):
200
+ resume_trigger.item_key = hitl_input.action.key
201
+ elif isinstance(hitl_input, CreateAction):
202
+ action = await uipath.actions.create_async(
203
+ title=hitl_input.title,
204
+ app_name=hitl_input.app_name if hitl_input.app_name else "",
205
+ app_folder_path=hitl_input.app_folder_path
206
+ if hitl_input.app_folder_path
207
+ else "",
208
+ app_folder_key=hitl_input.app_folder_key
209
+ if hitl_input.app_folder_key
210
+ else "",
211
+ app_key=hitl_input.app_key if hitl_input.app_key else "",
212
+ app_version=hitl_input.app_version
213
+ if hitl_input.app_version
214
+ else 1,
215
+ assignee=hitl_input.assignee if hitl_input.assignee else "",
216
+ data=hitl_input.data,
217
+ )
218
+ if not action:
219
+ raise Exception("Failed to create action")
220
+ resume_trigger.item_key = action.key
221
+
222
+ case UiPathResumeTriggerType.JOB:
223
+ resume_trigger.folder_path = hitl_input.process_folder_path
224
+ resume_trigger.folder_key = hitl_input.process_folder_key
225
+ if isinstance(hitl_input, WaitJob):
226
+ resume_trigger.item_key = hitl_input.job.key
227
+ elif isinstance(hitl_input, InvokeProcess):
228
+ job = await uipath.processes.invoke_async(
229
+ name=hitl_input.name,
230
+ input_arguments=hitl_input.input_arguments,
231
+ folder_path=hitl_input.process_folder_path,
232
+ folder_key=hitl_input.process_folder_key,
233
+ )
234
+ if not job:
235
+ raise Exception("Failed to invoke process")
236
+ resume_trigger.item_key = job.key
237
+
238
+ case UiPathResumeTriggerType.API:
239
+ resume_trigger.api_resume = UiPathApiTrigger(
240
+ inbox_id=str(uuid.uuid4()), request=serialize_object(hitl_input)
241
+ )
242
+ case _:
243
+ raise UiPathRuntimeError(
244
+ "UNKNOWN_HITL_MODEL",
245
+ "Unexpected model received",
246
+ f"{type(hitl_input)} is not a valid Human(Robot/Agent)-In-The-Loop model",
247
+ UiPathErrorCategory.USER,
248
+ )
249
+ except Exception as e:
250
+ raise UiPathRuntimeError(
251
+ "HITL_ACTION_CREATION_FAILED",
252
+ "Failed to create HITL action",
253
+ f"{str(e)}",
254
+ UiPathErrorCategory.SYSTEM,
255
+ ) from e
256
+
257
+ return resume_trigger
@@ -43,3 +43,29 @@ def get_env_vars(spinner: Optional[Spinner] = None) -> list[str | None]:
43
43
  click.get_current_context().exit(1)
44
44
 
45
45
  return [base_url, token]
46
+
47
+
48
+ def serialize_object(obj):
49
+ """Recursively serializes an object and all its nested components."""
50
+ # Handle Pydantic models
51
+ if hasattr(obj, "model_dump"):
52
+ return serialize_object(obj.model_dump(by_alias=True))
53
+ elif hasattr(obj, "dict"):
54
+ return serialize_object(obj.dict())
55
+ elif hasattr(obj, "to_dict"):
56
+ return serialize_object(obj.to_dict())
57
+ # Handle dictionaries
58
+ elif isinstance(obj, dict):
59
+ return {k: serialize_object(v) for k, v in obj.items()}
60
+ # Handle lists
61
+ elif isinstance(obj, list):
62
+ return [serialize_object(item) for item in obj]
63
+ # Handle other iterable objects (convert to dict first)
64
+ elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes)):
65
+ try:
66
+ return serialize_object(dict(obj))
67
+ except (TypeError, ValueError):
68
+ return obj
69
+ # Return primitive types as is
70
+ else:
71
+ return obj
@@ -0,0 +1,52 @@
1
+ import datetime
2
+ import logging
3
+ from zoneinfo import ZoneInfo
4
+
5
+
6
+ class IgnoreSpecificUrl(logging.Filter):
7
+ def __init__(self, url_to_ignore):
8
+ super().__init__()
9
+ self.url_to_ignore = url_to_ignore
10
+
11
+ def filter(self, record):
12
+ try:
13
+ if record.msg == 'HTTP Request: %s %s "%s %d %s"':
14
+ # Ignore the log if the URL matches the one we want to ignore
15
+ method = record.args[0]
16
+ url = record.args[1]
17
+
18
+ if method == "POST" and url.path.endswith(self.url_to_ignore):
19
+ # Check if the URL contains the specific path we want to ignore
20
+ return True
21
+ return False
22
+
23
+ except Exception:
24
+ return False
25
+
26
+
27
+ def setup_tracer_httpx_logging(url: str):
28
+ # Create a custom logger for httpx
29
+ # Add the custom filter to the root logger
30
+ logging.getLogger("httpx").addFilter(IgnoreSpecificUrl(url))
31
+
32
+
33
+ def simple_serialize_defaults(obj):
34
+ if hasattr(obj, "model_dump"):
35
+ return obj.model_dump(exclude_none=True, mode="json")
36
+ if hasattr(obj, "dict"):
37
+ return obj.dict()
38
+ if hasattr(obj, "to_dict"):
39
+ return obj.to_dict()
40
+
41
+ if isinstance(obj, (set, tuple)):
42
+ if hasattr(obj, "_asdict") and callable(obj._asdict):
43
+ return obj._asdict()
44
+ return list(obj)
45
+
46
+ if isinstance(obj, datetime.datetime):
47
+ return obj.isoformat()
48
+
49
+ if isinstance(obj, (datetime.timezone, ZoneInfo)):
50
+ return obj.tzname(None)
51
+
52
+ return str(obj)
@@ -272,6 +272,59 @@ class JobsService(FolderContext, BaseService):
272
272
  response = response.json()
273
273
  return self._extract_first_inbox_id(response)
274
274
 
275
+ def retrieve_api_payload(self, inbox_id: str) -> Any:
276
+ """Fetch payload data for API triggers.
277
+
278
+ Args:
279
+ inbox_id: The Id of the inbox to fetch the payload for.
280
+
281
+ Returns:
282
+ The value field from the API response payload.
283
+ """
284
+ spec = self._retrieve_api_payload_spec(inbox_id=inbox_id)
285
+
286
+ response = self.request(
287
+ spec.method,
288
+ url=spec.endpoint,
289
+ headers=spec.headers,
290
+ )
291
+
292
+ data = response.json()
293
+ return data.get("payload")
294
+
295
+ async def retrieve_api_payload_async(self, inbox_id: str) -> Any:
296
+ """Asynchronously fetch payload data for API triggers.
297
+
298
+ Args:
299
+ inbox_id: The Id of the inbox to fetch the payload for.
300
+
301
+ Returns:
302
+ The value field from the API response payload.
303
+ """
304
+ spec = self._retrieve_api_payload_spec(inbox_id=inbox_id)
305
+
306
+ response = await self.request_async(
307
+ spec.method,
308
+ url=spec.endpoint,
309
+ headers=spec.headers,
310
+ )
311
+
312
+ data = response.json()
313
+ return data.get("payload")
314
+
315
+ def _retrieve_api_payload_spec(
316
+ self,
317
+ *,
318
+ inbox_id: str,
319
+ ) -> RequestSpec:
320
+ return RequestSpec(
321
+ method="GET",
322
+ endpoint=Endpoint(f"/orchestrator_/api/JobTriggers/GetPayload/{inbox_id}"),
323
+ headers={
324
+ **self.folder_headers,
325
+ },
326
+ )
327
+
275
328
  def _extract_first_inbox_id(self, response: Any) -> str:
276
329
  if len(response["value"]) > 0:
277
330
  return response["value"][0]["ItemKey"]