uipath 2.1.40__py3-none-any.whl → 2.1.42__py3-none-any.whl
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.
- uipath/_cli/_auth/_auth_server.py +2 -2
- uipath/_cli/_auth/_auth_service.py +151 -0
- uipath/_cli/_auth/_client_credentials.py +21 -22
- uipath/_cli/_auth/_oidc_utils.py +49 -44
- uipath/_cli/_auth/_portal_service.py +23 -22
- uipath/_cli/_auth/_url_utils.py +15 -5
- uipath/_cli/_dev/_terminal/__init__.py +50 -10
- uipath/_cli/_dev/_terminal/_components/_chat.py +23 -5
- uipath/_cli/_dev/_terminal/_components/_details.py +10 -2
- uipath/_cli/_dev/_terminal/_components/_new.py +16 -3
- uipath/_cli/_dev/_terminal/_models/_execution.py +14 -5
- uipath/_cli/_dev/_terminal/_styles/terminal.tcss +9 -0
- uipath/_cli/_runtime/_contracts.py +3 -1
- uipath/_cli/cli_auth.py +18 -151
- uipath/_cli/cli_invoke.py +20 -0
- uipath/_cli/middlewares.py +1 -0
- {uipath-2.1.40.dist-info → uipath-2.1.42.dist-info}/METADATA +1 -1
- {uipath-2.1.40.dist-info → uipath-2.1.42.dist-info}/RECORD +21 -20
- {uipath-2.1.40.dist-info → uipath-2.1.42.dist-info}/WHEEL +0 -0
- {uipath-2.1.40.dist-info → uipath-2.1.42.dist-info}/entry_points.txt +0 -0
- {uipath-2.1.40.dist-info → uipath-2.1.42.dist-info}/licenses/LICENSE +0 -0
@@ -7,7 +7,7 @@ import threading
|
|
7
7
|
import time
|
8
8
|
from typing import Optional
|
9
9
|
|
10
|
-
from ._oidc_utils import
|
10
|
+
from ._oidc_utils import OidcUtils
|
11
11
|
|
12
12
|
# Server port
|
13
13
|
PORT = 6234
|
@@ -74,7 +74,7 @@ def make_request_handler_class(state, code_verifier, token_callback, domain):
|
|
74
74
|
content = f.read()
|
75
75
|
|
76
76
|
# Get the redirect URI from auth config
|
77
|
-
auth_config = get_auth_config()
|
77
|
+
auth_config = OidcUtils.get_auth_config()
|
78
78
|
redirect_uri = auth_config["redirect_uri"]
|
79
79
|
|
80
80
|
content = content.replace("__PY_REPLACE_EXPECTED_STATE__", state)
|
@@ -0,0 +1,151 @@
|
|
1
|
+
import asyncio
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import webbrowser
|
5
|
+
from socket import AF_INET, SOCK_STREAM, error, socket
|
6
|
+
from typing import Optional
|
7
|
+
from urllib.parse import urlparse
|
8
|
+
|
9
|
+
from uipath._cli._auth._auth_server import HTTPServer
|
10
|
+
from uipath._cli._auth._client_credentials import ClientCredentialsService
|
11
|
+
from uipath._cli._auth._oidc_utils import OidcUtils
|
12
|
+
from uipath._cli._auth._portal_service import PortalService, select_tenant
|
13
|
+
from uipath._cli._auth._url_utils import set_force_flag
|
14
|
+
from uipath._cli._auth._utils import update_auth_file, update_env_file
|
15
|
+
from uipath._cli._utils._console import ConsoleLogger
|
16
|
+
|
17
|
+
|
18
|
+
class AuthService:
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
environment: str,
|
22
|
+
*,
|
23
|
+
force: bool,
|
24
|
+
client_id: Optional[str],
|
25
|
+
client_secret: Optional[str],
|
26
|
+
base_url: Optional[str],
|
27
|
+
scope: Optional[str],
|
28
|
+
):
|
29
|
+
self._force = force
|
30
|
+
self._console = ConsoleLogger()
|
31
|
+
self._domain = self._get_domain(environment)
|
32
|
+
self._client_id = client_id
|
33
|
+
self._client_secret = client_secret
|
34
|
+
self._base_url = base_url
|
35
|
+
self._scope = scope
|
36
|
+
set_force_flag(self._force)
|
37
|
+
|
38
|
+
def _get_domain(self, environment: str) -> str:
|
39
|
+
# only search env var if not force authentication
|
40
|
+
if not self._force:
|
41
|
+
uipath_url = os.getenv("UIPATH_URL")
|
42
|
+
if uipath_url and environment == "cloud": # "cloud" is the default
|
43
|
+
parsed_url = urlparse(uipath_url)
|
44
|
+
if parsed_url.scheme and parsed_url.netloc:
|
45
|
+
environment = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
46
|
+
else:
|
47
|
+
self._console.error(
|
48
|
+
f"Malformed UIPATH_URL: '{uipath_url}'. Please ensure it includes both scheme and netloc (e.g., 'https://cloud.uipath.com')."
|
49
|
+
)
|
50
|
+
return environment
|
51
|
+
|
52
|
+
def authenticate(self) -> None:
|
53
|
+
if self._client_id and self._client_secret:
|
54
|
+
self._authenticate_client_credentials()
|
55
|
+
return
|
56
|
+
|
57
|
+
self._authenticate_authorization_code()
|
58
|
+
|
59
|
+
def _authenticate_client_credentials(self) -> None:
|
60
|
+
if not self._base_url:
|
61
|
+
self._console.error(
|
62
|
+
"--base-url is required when using client credentials authentication."
|
63
|
+
)
|
64
|
+
return
|
65
|
+
self._console.hint("Using client credentials authentication.")
|
66
|
+
credentials_service = ClientCredentialsService(self._base_url)
|
67
|
+
credentials_service.authenticate(
|
68
|
+
self._client_id, # type: ignore
|
69
|
+
self._client_secret, # type: ignore
|
70
|
+
self._scope,
|
71
|
+
)
|
72
|
+
|
73
|
+
def _authenticate_authorization_code(self) -> None:
|
74
|
+
with PortalService(self._domain) as portal_service:
|
75
|
+
if not self._force:
|
76
|
+
# use existing env vars
|
77
|
+
if (
|
78
|
+
os.getenv("UIPATH_URL")
|
79
|
+
and os.getenv("UIPATH_TENANT_ID")
|
80
|
+
and os.getenv("UIPATH_ORGANIZATION_ID")
|
81
|
+
):
|
82
|
+
try:
|
83
|
+
portal_service.ensure_valid_token()
|
84
|
+
return
|
85
|
+
except Exception:
|
86
|
+
self._console.error(
|
87
|
+
"Authentication token is invalid. Please reauthenticate using the '--force' flag.",
|
88
|
+
)
|
89
|
+
auth_url, code_verifier, state = OidcUtils.get_auth_url(self._domain)
|
90
|
+
webbrowser.open(auth_url, 1)
|
91
|
+
auth_config = OidcUtils.get_auth_config()
|
92
|
+
|
93
|
+
self._console.link(
|
94
|
+
"If a browser window did not open, please open the following URL in your browser:",
|
95
|
+
auth_url,
|
96
|
+
)
|
97
|
+
server = HTTPServer(port=auth_config["port"])
|
98
|
+
token_data = asyncio.run(server.start(state, code_verifier, self._domain))
|
99
|
+
|
100
|
+
if not token_data:
|
101
|
+
self._console.error(
|
102
|
+
"Authentication failed. Please try again.",
|
103
|
+
)
|
104
|
+
|
105
|
+
portal_service.update_token_data(token_data)
|
106
|
+
update_auth_file(token_data)
|
107
|
+
access_token = token_data["access_token"]
|
108
|
+
update_env_file({"UIPATH_ACCESS_TOKEN": access_token})
|
109
|
+
|
110
|
+
tenants_and_organizations = portal_service.get_tenants_and_organizations()
|
111
|
+
base_url = select_tenant(self._domain, tenants_and_organizations)
|
112
|
+
try:
|
113
|
+
portal_service.post_auth(base_url)
|
114
|
+
except Exception:
|
115
|
+
self._console.error(
|
116
|
+
"Could not prepare the environment. Please try again.",
|
117
|
+
)
|
118
|
+
|
119
|
+
def set_port(self):
|
120
|
+
def is_port_in_use(target_port: int) -> bool:
|
121
|
+
with socket(AF_INET, SOCK_STREAM) as s:
|
122
|
+
try:
|
123
|
+
s.bind(("localhost", target_port))
|
124
|
+
s.close()
|
125
|
+
return False
|
126
|
+
except error:
|
127
|
+
return True
|
128
|
+
|
129
|
+
auth_config = OidcUtils.get_auth_config()
|
130
|
+
port = int(auth_config.get("port", 8104))
|
131
|
+
port_option_one = int(auth_config.get("portOptionOne", 8104)) # type: ignore
|
132
|
+
port_option_two = int(auth_config.get("portOptionTwo", 8055)) # type: ignore
|
133
|
+
port_option_three = int(auth_config.get("portOptionThree", 42042)) # type: ignore
|
134
|
+
if is_port_in_use(port):
|
135
|
+
if is_port_in_use(port_option_one):
|
136
|
+
if is_port_in_use(port_option_two):
|
137
|
+
if is_port_in_use(port_option_three):
|
138
|
+
self._console.error(
|
139
|
+
"All configured ports are in use. Please close applications using ports or configure different ports."
|
140
|
+
)
|
141
|
+
else:
|
142
|
+
port = port_option_three
|
143
|
+
else:
|
144
|
+
port = port_option_two
|
145
|
+
else:
|
146
|
+
port = port_option_one
|
147
|
+
auth_config["port"] = port
|
148
|
+
with open(
|
149
|
+
os.path.join(os.path.dirname(__file__), "..", "auth_config.json"), "w"
|
150
|
+
) as f:
|
151
|
+
json.dump(auth_config, f)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Optional, cast
|
2
2
|
from urllib.parse import urlparse
|
3
3
|
|
4
4
|
import httpx
|
@@ -14,12 +14,13 @@ console = ConsoleLogger()
|
|
14
14
|
class ClientCredentialsService:
|
15
15
|
"""Service for client credentials authentication flow."""
|
16
16
|
|
17
|
-
def __init__(self,
|
18
|
-
self.
|
17
|
+
def __init__(self, base_url: str):
|
18
|
+
self._base_url = base_url
|
19
|
+
self._domain = self._extract_domain_from_base_url(base_url)
|
19
20
|
|
20
21
|
def get_token_url(self) -> str:
|
21
22
|
"""Get the token URL for the specified domain."""
|
22
|
-
match self.
|
23
|
+
match self._domain:
|
23
24
|
case "alpha":
|
24
25
|
return "https://alpha.uipath.com/identity_/connect/token"
|
25
26
|
case "staging":
|
@@ -39,7 +40,7 @@ class ClientCredentialsService:
|
|
39
40
|
"""
|
40
41
|
return hostname == domain or hostname.endswith(f".{domain}")
|
41
42
|
|
42
|
-
def
|
43
|
+
def _extract_domain_from_base_url(self, base_url: str) -> str:
|
43
44
|
"""Extract domain from base URL.
|
44
45
|
|
45
46
|
Args:
|
@@ -71,7 +72,7 @@ class ClientCredentialsService:
|
|
71
72
|
|
72
73
|
def authenticate(
|
73
74
|
self, client_id: str, client_secret: str, scope: Optional[str] = "OR.Execution"
|
74
|
-
) ->
|
75
|
+
) -> None:
|
75
76
|
"""Authenticate using client credentials flow.
|
76
77
|
|
77
78
|
Args:
|
@@ -97,37 +98,35 @@ class ClientCredentialsService:
|
|
97
98
|
match response.status_code:
|
98
99
|
case 200:
|
99
100
|
token_data = response.json()
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
101
|
+
token_data = cast(
|
102
|
+
TokenData,
|
103
|
+
{
|
104
|
+
"access_token": token_data["access_token"],
|
105
|
+
"token_type": token_data.get("token_type", "Bearer"),
|
106
|
+
"expires_in": token_data.get("expires_in", 3600),
|
107
|
+
"scope": token_data.get("scope", scope),
|
108
|
+
"refresh_token": "",
|
109
|
+
"id_token": "",
|
110
|
+
},
|
111
|
+
)
|
112
|
+
self._setup_environment(token_data)
|
109
113
|
case 400:
|
110
114
|
console.error(
|
111
115
|
"Invalid client credentials or request parameters."
|
112
116
|
)
|
113
|
-
return None
|
114
117
|
case 401:
|
115
118
|
console.error("Unauthorized: Invalid client credentials.")
|
116
|
-
return None
|
117
119
|
case _:
|
118
120
|
console.error(
|
119
121
|
f"Authentication failed: {response.status_code} - {response.text}"
|
120
122
|
)
|
121
|
-
return None
|
122
123
|
|
123
124
|
except httpx.RequestError as e:
|
124
125
|
console.error(f"Network error during authentication: {e}")
|
125
|
-
return None
|
126
126
|
except Exception as e:
|
127
127
|
console.error(f"Unexpected error during authentication: {e}")
|
128
|
-
return None
|
129
128
|
|
130
|
-
def
|
129
|
+
def _setup_environment(self, token_data: TokenData):
|
131
130
|
"""Setup environment variables for client credentials authentication.
|
132
131
|
|
133
132
|
Args:
|
@@ -138,7 +137,7 @@ class ClientCredentialsService:
|
|
138
137
|
|
139
138
|
env_vars = {
|
140
139
|
"UIPATH_ACCESS_TOKEN": token_data["access_token"],
|
141
|
-
"UIPATH_URL":
|
140
|
+
"UIPATH_URL": self._base_url,
|
142
141
|
"UIPATH_ORGANIZATION_ID": parsed_access_token.get("prt_id", ""),
|
143
142
|
"UIPATH_TENANT_ID": "",
|
144
143
|
}
|
uipath/_cli/_auth/_oidc_utils.py
CHANGED
@@ -24,47 +24,52 @@ def get_state_param() -> str:
|
|
24
24
|
return base64.urlsafe_b64encode(os.urandom(32)).decode("utf-8").rstrip("=")
|
25
25
|
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
"
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
27
|
+
class OidcUtils:
|
28
|
+
@classmethod
|
29
|
+
def get_auth_config(cls) -> AuthConfig:
|
30
|
+
with open(
|
31
|
+
os.path.join(os.path.dirname(__file__), "auth_config.json"), "r"
|
32
|
+
) as f:
|
33
|
+
auth_config = json.load(f)
|
34
|
+
|
35
|
+
port = auth_config.get("port", 8104)
|
36
|
+
|
37
|
+
redirect_uri = auth_config["redirect_uri"].replace(
|
38
|
+
"__PY_REPLACE_PORT__", str(port)
|
39
|
+
)
|
40
|
+
|
41
|
+
return AuthConfig(
|
42
|
+
client_id=auth_config["client_id"],
|
43
|
+
redirect_uri=redirect_uri,
|
44
|
+
scope=auth_config["scope"],
|
45
|
+
port=port,
|
46
|
+
)
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def get_auth_url(cls, domain: str) -> tuple[str, str, str]:
|
50
|
+
"""Get the authorization URL for OAuth2 PKCE flow.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
domain (str): The UiPath domain to authenticate against (e.g. 'alpha', 'cloud')
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
tuple[str, str]: A tuple containing:
|
57
|
+
- The authorization URL with query parameters
|
58
|
+
- The code verifier for PKCE flow
|
59
|
+
"""
|
60
|
+
code_verifier, code_challenge = generate_code_verifier_and_challenge()
|
61
|
+
auth_config = cls.get_auth_config()
|
62
|
+
state = get_state_param()
|
63
|
+
query_params = {
|
64
|
+
"client_id": auth_config["client_id"],
|
65
|
+
"redirect_uri": auth_config["redirect_uri"],
|
66
|
+
"response_type": "code",
|
67
|
+
"scope": auth_config["scope"],
|
68
|
+
"state": state,
|
69
|
+
"code_challenge": code_challenge,
|
70
|
+
"code_challenge_method": "S256",
|
71
|
+
}
|
72
|
+
|
73
|
+
query_string = urlencode(query_params)
|
74
|
+
url = build_service_url(domain, f"/identity_/connect/authorize?{query_string}")
|
75
|
+
return url, code_verifier, state
|
@@ -8,7 +8,7 @@ import httpx
|
|
8
8
|
from ..._utils._ssl_context import get_httpx_client_kwargs
|
9
9
|
from .._utils._console import ConsoleLogger
|
10
10
|
from ._models import TenantsAndOrganizationInfoResponse, TokenData
|
11
|
-
from ._oidc_utils import
|
11
|
+
from ._oidc_utils import OidcUtils
|
12
12
|
from ._url_utils import build_service_url, get_base_url
|
13
13
|
from ._utils import (
|
14
14
|
get_auth_data,
|
@@ -102,7 +102,7 @@ class PortalService:
|
|
102
102
|
raise RuntimeError("HTTP client is not initialized")
|
103
103
|
|
104
104
|
url = build_service_url(self.domain, "/identity_/connect/token")
|
105
|
-
client_id = get_auth_config().get("client_id")
|
105
|
+
client_id = OidcUtils.get_auth_config().get("client_id")
|
106
106
|
|
107
107
|
data = {
|
108
108
|
"grant_type": "refresh_token",
|
@@ -136,32 +136,33 @@ class PortalService:
|
|
136
136
|
auth_data = get_auth_data()
|
137
137
|
claims = get_parsed_token_data(auth_data)
|
138
138
|
exp = claims.get("exp")
|
139
|
-
|
140
139
|
if exp is not None and float(exp) > time.time():
|
141
140
|
if not os.getenv("UIPATH_URL"):
|
142
141
|
tenants_and_organizations = self.get_tenants_and_organizations()
|
143
142
|
select_tenant(
|
144
143
|
self.domain if self.domain else "alpha", tenants_and_organizations
|
145
144
|
)
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
145
|
+
updated_env_contents = {
|
146
|
+
"UIPATH_ACCESS_TOKEN": auth_data["access_token"],
|
147
|
+
}
|
148
|
+
else:
|
149
|
+
refresh_token = auth_data.get("refresh_token")
|
150
|
+
if refresh_token is None:
|
151
|
+
raise Exception("Refresh token not found")
|
152
|
+
token_data = self.post_refresh_token_request(refresh_token)
|
153
|
+
update_auth_file(token_data)
|
154
|
+
|
155
|
+
self.access_token = token_data["access_token"]
|
156
|
+
self.prt_id = claims.get("prt_id")
|
157
|
+
|
158
|
+
updated_env_contents = {
|
159
|
+
"UIPATH_ACCESS_TOKEN": token_data["access_token"],
|
160
|
+
}
|
161
|
+
if not os.getenv("UIPATH_URL"):
|
162
|
+
tenants_and_organizations = self.get_tenants_and_organizations()
|
163
|
+
select_tenant(
|
164
|
+
self.domain if self.domain else "alpha", tenants_and_organizations
|
165
|
+
)
|
165
166
|
|
166
167
|
update_env_file(updated_env_contents)
|
167
168
|
|
uipath/_cli/_auth/_url_utils.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import os
|
2
2
|
from urllib.parse import urlparse
|
3
3
|
|
4
|
+
ignore_env_var = False
|
5
|
+
|
4
6
|
|
5
7
|
def get_base_url(domain: str) -> str:
|
6
8
|
"""Get the base URL for UiPath services.
|
@@ -11,11 +13,14 @@ def get_base_url(domain: str) -> str:
|
|
11
13
|
Returns:
|
12
14
|
The base URL to use for UiPath services
|
13
15
|
"""
|
14
|
-
|
15
|
-
|
16
|
-
if
|
17
|
-
|
18
|
-
|
16
|
+
global ignore_env_var
|
17
|
+
|
18
|
+
if not ignore_env_var:
|
19
|
+
# If UIPATH_URL is set and domain is 'cloud' (default), use the base from UIPATH_URL
|
20
|
+
uipath_url = os.getenv("UIPATH_URL")
|
21
|
+
if uipath_url and domain == "cloud":
|
22
|
+
parsed_url = urlparse(uipath_url)
|
23
|
+
return f"{parsed_url.scheme}://{parsed_url.netloc}"
|
19
24
|
|
20
25
|
# If domain is already a full URL, use it directly
|
21
26
|
if domain.startswith("http"):
|
@@ -25,6 +30,11 @@ def get_base_url(domain: str) -> str:
|
|
25
30
|
return f"https://{domain if domain else 'cloud'}.uipath.com"
|
26
31
|
|
27
32
|
|
33
|
+
def set_force_flag(force: bool):
|
34
|
+
global ignore_env_var
|
35
|
+
ignore_env_var = force
|
36
|
+
|
37
|
+
|
28
38
|
def build_service_url(domain: str, path: str) -> str:
|
29
39
|
"""Build a service URL by combining the base URL with a path.
|
30
40
|
|
@@ -4,7 +4,7 @@ import traceback
|
|
4
4
|
from datetime import datetime
|
5
5
|
from os import environ as env
|
6
6
|
from pathlib import Path
|
7
|
-
from typing import Any, Dict
|
7
|
+
from typing import Any, Dict, cast
|
8
8
|
from uuid import uuid4
|
9
9
|
|
10
10
|
import pyperclip # type: ignore[import-untyped]
|
@@ -15,7 +15,12 @@ from textual.binding import Binding
|
|
15
15
|
from textual.containers import Container, Horizontal
|
16
16
|
from textual.widgets import Button, Footer, Input, ListView, RichLog
|
17
17
|
|
18
|
-
from uipath.agent.conversation import
|
18
|
+
from uipath.agent.conversation import (
|
19
|
+
UiPathConversationContentPart,
|
20
|
+
UiPathConversationEvent,
|
21
|
+
UiPathConversationMessage,
|
22
|
+
UiPathInlineValue,
|
23
|
+
)
|
19
24
|
|
20
25
|
from ..._runtime._contracts import (
|
21
26
|
UiPathErrorContract,
|
@@ -116,7 +121,8 @@ class UiPathDevTerminal(App[Any]):
|
|
116
121
|
|
117
122
|
details_panel = self.query_one("#details-panel", RunDetailsPanel)
|
118
123
|
if details_panel and details_panel.current_run:
|
119
|
-
|
124
|
+
status = details_panel.current_run.status
|
125
|
+
if status == "running":
|
120
126
|
self.app.notify(
|
121
127
|
"Wait for agent response...", timeout=1.5, severity="warning"
|
122
128
|
)
|
@@ -128,7 +134,22 @@ class UiPathDevTerminal(App[Any]):
|
|
128
134
|
),
|
129
135
|
details_panel.current_run.id,
|
130
136
|
)
|
131
|
-
details_panel.current_run.
|
137
|
+
if details_panel.current_run.status == "suspended":
|
138
|
+
details_panel.current_run.resume_data = user_text
|
139
|
+
else:
|
140
|
+
details_panel.current_run.input_data = UiPathConversationMessage(
|
141
|
+
message_id=str(uuid4()),
|
142
|
+
created_at=datetime.now().isoformat(),
|
143
|
+
updated_at=datetime.now().isoformat(),
|
144
|
+
content_parts=[
|
145
|
+
UiPathConversationContentPart(
|
146
|
+
content_part_id=str(uuid4()),
|
147
|
+
mime_type="text/plain",
|
148
|
+
data=UiPathInlineValue(inline=user_text),
|
149
|
+
)
|
150
|
+
],
|
151
|
+
role="user",
|
152
|
+
)
|
132
153
|
asyncio.create_task(self._execute_runtime(details_panel.current_run))
|
133
154
|
event.input.clear()
|
134
155
|
|
@@ -147,7 +168,7 @@ class UiPathDevTerminal(App[Any]):
|
|
147
168
|
async def action_execute_run(self) -> None:
|
148
169
|
"""Execute a new run with UiPath runtime."""
|
149
170
|
new_run_panel = self.query_one("#new-run-panel", NewRunPanel)
|
150
|
-
entrypoint, input_data = new_run_panel.get_input_values()
|
171
|
+
entrypoint, input_data, conversational = new_run_panel.get_input_values()
|
151
172
|
|
152
173
|
if not entrypoint:
|
153
174
|
return
|
@@ -158,7 +179,7 @@ class UiPathDevTerminal(App[Any]):
|
|
158
179
|
except json.JSONDecodeError:
|
159
180
|
return
|
160
181
|
|
161
|
-
run = ExecutionRun(entrypoint, input)
|
182
|
+
run = ExecutionRun(entrypoint, input, conversational)
|
162
183
|
|
163
184
|
self.runs[run.id] = run
|
164
185
|
|
@@ -166,7 +187,10 @@ class UiPathDevTerminal(App[Any]):
|
|
166
187
|
|
167
188
|
self._show_run_details(run)
|
168
189
|
|
169
|
-
|
190
|
+
if not run.conversational:
|
191
|
+
asyncio.create_task(self._execute_runtime(run))
|
192
|
+
else:
|
193
|
+
self._focus_chat_input()
|
170
194
|
|
171
195
|
async def action_clear_history(self) -> None:
|
172
196
|
"""Clear run history."""
|
@@ -191,6 +215,7 @@ class UiPathDevTerminal(App[Any]):
|
|
191
215
|
entrypoint=run.entrypoint,
|
192
216
|
trace_id=str(uuid4()),
|
193
217
|
execution_id=run.id,
|
218
|
+
is_conversational=run.conversational,
|
194
219
|
logs_min_level=env.get("LOG_LEVEL", "INFO"),
|
195
220
|
log_handler=RunContextLogHandler(
|
196
221
|
run_id=run.id, callback=self._handle_log_message
|
@@ -201,11 +226,16 @@ class UiPathDevTerminal(App[Any]):
|
|
201
226
|
)
|
202
227
|
|
203
228
|
if run.status == "suspended":
|
204
|
-
context.resume = True
|
205
229
|
context.input_json = run.resume_data
|
230
|
+
context.resume = True
|
206
231
|
self._add_info_log(run, f"Resuming execution: {run.entrypoint}")
|
207
232
|
else:
|
208
|
-
|
233
|
+
if run.conversational:
|
234
|
+
context.input_message = cast(
|
235
|
+
UiPathConversationMessage, run.input_data
|
236
|
+
)
|
237
|
+
else:
|
238
|
+
context.input_json = run.input_data
|
209
239
|
self._add_info_log(run, f"Starting execution: {run.entrypoint}")
|
210
240
|
|
211
241
|
run.status = "running"
|
@@ -214,7 +244,10 @@ class UiPathDevTerminal(App[Any]):
|
|
214
244
|
result = await self.runtime_factory.execute_in_root_span(context)
|
215
245
|
|
216
246
|
if result is not None:
|
217
|
-
if
|
247
|
+
if (
|
248
|
+
result.status == UiPathRuntimeStatus.SUSPENDED.value
|
249
|
+
and result.resume
|
250
|
+
):
|
218
251
|
run.status = "suspended"
|
219
252
|
else:
|
220
253
|
run.output_data = result.output
|
@@ -254,6 +287,13 @@ class UiPathDevTerminal(App[Any]):
|
|
254
287
|
# Populate the details panel with run data
|
255
288
|
details_panel.update_run(run)
|
256
289
|
|
290
|
+
def _focus_chat_input(self):
|
291
|
+
"""Focus the chat input box."""
|
292
|
+
details_panel = self.query_one("#details-panel", RunDetailsPanel)
|
293
|
+
details_panel.switch_tab("chat-tab")
|
294
|
+
chat_input = details_panel.query_one("#chat-input", Input)
|
295
|
+
chat_input.focus()
|
296
|
+
|
257
297
|
def _add_run_in_history(self, run: ExecutionRun):
|
258
298
|
"""Add run to history panel."""
|
259
299
|
history_panel = self.query_one("#history-panel", RunHistoryPanel)
|
@@ -1,10 +1,11 @@
|
|
1
1
|
import time
|
2
|
-
from typing import Dict, List, Union
|
2
|
+
from typing import Dict, List, Optional, Union
|
3
3
|
|
4
4
|
from textual.app import ComposeResult
|
5
5
|
from textual.containers import Container, Vertical, VerticalScroll
|
6
6
|
from textual.widgets import Input, Markdown
|
7
7
|
|
8
|
+
from uipath._cli._dev._terminal._models._execution import ExecutionRun
|
8
9
|
from uipath.agent.conversation import (
|
9
10
|
UiPathConversationEvent,
|
10
11
|
UiPathConversationMessage,
|
@@ -44,8 +45,22 @@ class ChatPanel(Container):
|
|
44
45
|
id="chat-input",
|
45
46
|
)
|
46
47
|
|
48
|
+
def update_messages(self, run: ExecutionRun) -> None:
|
49
|
+
"""Update the chat panel with messages from the given execution run."""
|
50
|
+
chat_view = self.query_one("#chat-view")
|
51
|
+
chat_view.remove_children()
|
52
|
+
self._chat_widgets.clear()
|
53
|
+
self._last_update_time.clear()
|
54
|
+
|
55
|
+
for chat_msg in run.messages:
|
56
|
+
self.add_chat_message(None, chat_msg, auto_scroll=False)
|
57
|
+
chat_view.scroll_end(animate=False)
|
58
|
+
|
47
59
|
def add_chat_message(
|
48
|
-
self,
|
60
|
+
self,
|
61
|
+
event: Optional[UiPathConversationEvent],
|
62
|
+
chat_msg: UiPathConversationMessage,
|
63
|
+
auto_scroll: bool = True,
|
49
64
|
) -> None:
|
50
65
|
"""Add or update a chat message bubble."""
|
51
66
|
chat_view = self.query_one("#chat-view")
|
@@ -90,17 +105,20 @@ class ChatPanel(Container):
|
|
90
105
|
|
91
106
|
if existing:
|
92
107
|
should_update = (
|
93
|
-
event
|
108
|
+
event
|
109
|
+
and event.exchange
|
94
110
|
and event.exchange.message
|
95
111
|
and event.exchange.message.end is not None
|
96
112
|
)
|
97
113
|
if should_update or now - last_update > 0.15:
|
98
114
|
existing.update(content)
|
99
115
|
self._last_update_time[chat_msg.message_id] = now
|
100
|
-
|
116
|
+
if auto_scroll:
|
117
|
+
chat_view.scroll_end(animate=False)
|
101
118
|
else:
|
102
119
|
widget_instance = widget_cls(content)
|
103
120
|
chat_view.mount(widget_instance)
|
104
121
|
self._chat_widgets[chat_msg.message_id] = widget_instance
|
105
122
|
self._last_update_time[chat_msg.message_id] = now
|
106
|
-
|
123
|
+
if auto_scroll:
|
124
|
+
chat_view.scroll_end(animate=False)
|
@@ -155,7 +155,11 @@ class RunDetailsPanel(Container):
|
|
155
155
|
|
156
156
|
def _update_chat_tab(self, run: ExecutionRun) -> None:
|
157
157
|
chat_input = self.query_one("#chat-input", Input)
|
158
|
-
chat_input.disabled =
|
158
|
+
chat_input.disabled = (
|
159
|
+
run.status == "completed" or run.status == "failed"
|
160
|
+
) and not run.conversational
|
161
|
+
chat_panel = self.query_one("#chat-panel", ChatPanel)
|
162
|
+
chat_panel.update_messages(run)
|
159
163
|
|
160
164
|
def _flatten_values(self, value: object, prefix: str = "") -> list[str]:
|
161
165
|
"""Flatten nested dict/list structures into dot-notation paths."""
|
@@ -288,7 +292,11 @@ class RunDetailsPanel(Container):
|
|
288
292
|
def _rebuild_spans_tree(self):
|
289
293
|
"""Rebuild the spans tree from current run's traces."""
|
290
294
|
spans_tree = self.query_one("#spans-tree", Tree)
|
291
|
-
spans_tree.
|
295
|
+
if spans_tree is None or spans_tree.root is None:
|
296
|
+
return
|
297
|
+
|
298
|
+
spans_tree.root.remove_children()
|
299
|
+
|
292
300
|
# Only clear the node mapping since we're rebuilding the tree structure
|
293
301
|
self.span_tree_nodes.clear()
|
294
302
|
|
@@ -5,7 +5,7 @@ from typing import Any, Dict, Tuple, cast
|
|
5
5
|
from textual.app import ComposeResult
|
6
6
|
from textual.containers import Container, Horizontal, Vertical
|
7
7
|
from textual.reactive import reactive
|
8
|
-
from textual.widgets import Button, Select, TabbedContent, TabPane, TextArea
|
8
|
+
from textual.widgets import Button, Checkbox, Select, TabbedContent, TabPane, TextArea
|
9
9
|
|
10
10
|
from ._json_input import JsonInput
|
11
11
|
|
@@ -76,6 +76,12 @@ class NewRunPanel(Container):
|
|
76
76
|
allow_blank=False,
|
77
77
|
)
|
78
78
|
|
79
|
+
yield Checkbox(
|
80
|
+
"chat mode",
|
81
|
+
value=False,
|
82
|
+
id="conversational-toggle",
|
83
|
+
)
|
84
|
+
|
79
85
|
yield JsonInput(
|
80
86
|
text=self.initial_input,
|
81
87
|
language="json",
|
@@ -108,9 +114,16 @@ class NewRunPanel(Container):
|
|
108
114
|
mock_json_from_schema(ep.get("input", {})), indent=2
|
109
115
|
)
|
110
116
|
|
111
|
-
def
|
117
|
+
async def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
|
118
|
+
"""Hide JSON input if conversational is enabled."""
|
119
|
+
if event.checkbox.id == "conversational-toggle":
|
120
|
+
json_input = self.query_one("#json-input", TextArea)
|
121
|
+
json_input.display = not event.value
|
122
|
+
|
123
|
+
def get_input_values(self) -> Tuple[str, str, bool]:
|
112
124
|
json_input = self.query_one("#json-input", TextArea)
|
113
|
-
|
125
|
+
conversational = self.query_one("#conversational-toggle", Checkbox).value
|
126
|
+
return self.selected_entrypoint, json_input.text.strip(), conversational
|
114
127
|
|
115
128
|
def reset_form(self):
|
116
129
|
"""Reset selection and JSON input to defaults."""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
from datetime import datetime
|
3
|
-
from typing import Any, Dict, List, Optional
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
4
4
|
from uuid import uuid4
|
5
5
|
|
6
6
|
from rich.text import Text
|
@@ -15,15 +15,21 @@ from ._messages import LogMessage, TraceMessage
|
|
15
15
|
class ExecutionRun:
|
16
16
|
"""Represents a single execution run."""
|
17
17
|
|
18
|
-
def __init__(
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
entrypoint: str,
|
21
|
+
input_data: Union[Dict[str, Any], UiPathConversationMessage],
|
22
|
+
conversational: bool = False,
|
23
|
+
):
|
19
24
|
self.id = str(uuid4())[:8]
|
20
25
|
self.entrypoint = entrypoint
|
21
26
|
self.input_data = input_data
|
22
|
-
self.
|
27
|
+
self.conversational = conversational
|
28
|
+
self.resume_data: Optional[Any] = None
|
23
29
|
self.output_data: Optional[Dict[str, Any]] = None
|
24
30
|
self.start_time = datetime.now()
|
25
31
|
self.end_time: Optional[datetime] = None
|
26
|
-
self.status = "
|
32
|
+
self.status = "pending" # pending, running, completed, failed, suspended
|
27
33
|
self.traces: List[TraceMessage] = []
|
28
34
|
self.logs: List[LogMessage] = []
|
29
35
|
self.error: Optional[UiPathErrorContract] = None
|
@@ -34,13 +40,15 @@ class ExecutionRun:
|
|
34
40
|
if self.end_time:
|
35
41
|
delta = self.end_time - self.start_time
|
36
42
|
return f"{delta.total_seconds():.1f}s"
|
37
|
-
|
43
|
+
elif self.start_time:
|
38
44
|
delta = datetime.now() - self.start_time
|
39
45
|
return f"{delta.total_seconds():.1f}s"
|
46
|
+
return "0.0s"
|
40
47
|
|
41
48
|
@property
|
42
49
|
def display_name(self) -> Text:
|
43
50
|
status_colors = {
|
51
|
+
"pending": "grey50",
|
44
52
|
"running": "yellow",
|
45
53
|
"suspended": "cyan",
|
46
54
|
"completed": "green",
|
@@ -48,6 +56,7 @@ class ExecutionRun:
|
|
48
56
|
}
|
49
57
|
|
50
58
|
status_icon = {
|
59
|
+
"pending": "●",
|
51
60
|
"running": "▶",
|
52
61
|
"suspended": "⏸",
|
53
62
|
"completed": "✔",
|
@@ -27,6 +27,11 @@ Screen {
|
|
27
27
|
color: #e0e0e0;
|
28
28
|
}
|
29
29
|
|
30
|
+
.run-pending {
|
31
|
+
border-left: solid grey;
|
32
|
+
color: #e0e0e0;
|
33
|
+
}
|
34
|
+
|
30
35
|
.run-completed {
|
31
36
|
border-left: solid #00ff88;
|
32
37
|
color: #e0e0e0;
|
@@ -250,3 +255,7 @@ Response, Tool {
|
|
250
255
|
dock: bottom;
|
251
256
|
margin: 1;
|
252
257
|
}
|
258
|
+
|
259
|
+
Checkbox{
|
260
|
+
margin-top: 1;
|
261
|
+
}
|
@@ -22,7 +22,7 @@ from opentelemetry.sdk.trace.export import (
|
|
22
22
|
from opentelemetry.trace import Tracer
|
23
23
|
from pydantic import BaseModel, Field
|
24
24
|
|
25
|
-
from uipath.agent.conversation import UiPathConversationEvent
|
25
|
+
from uipath.agent.conversation import UiPathConversationEvent, UiPathConversationMessage
|
26
26
|
from uipath.tracing import TracingManager
|
27
27
|
|
28
28
|
from ._logging import LogsInterceptor
|
@@ -167,6 +167,7 @@ class UiPathRuntimeContext(BaseModel):
|
|
167
167
|
entrypoint: Optional[str] = None
|
168
168
|
input: Optional[str] = None
|
169
169
|
input_json: Optional[Any] = None
|
170
|
+
input_message: Optional[UiPathConversationMessage] = None
|
170
171
|
job_id: Optional[str] = None
|
171
172
|
execution_id: Optional[str] = None
|
172
173
|
trace_id: Optional[str] = None
|
@@ -186,6 +187,7 @@ class UiPathRuntimeContext(BaseModel):
|
|
186
187
|
is_eval_run: bool = False
|
187
188
|
log_handler: Optional[logging.Handler] = None
|
188
189
|
chat_handler: Optional[UiPathConversationHandler] = None
|
190
|
+
is_conversational: Optional[bool] = None
|
189
191
|
|
190
192
|
model_config = {"arbitrary_types_allowed": True}
|
191
193
|
|
uipath/_cli/cli_auth.py
CHANGED
@@ -1,61 +1,15 @@
|
|
1
|
-
import asyncio
|
2
|
-
import json
|
3
|
-
import os
|
4
|
-
import socket
|
5
|
-
import webbrowser
|
6
1
|
from typing import Optional
|
7
|
-
from urllib.parse import urlparse
|
8
2
|
|
9
3
|
import click
|
10
4
|
|
11
5
|
from ..telemetry import track
|
12
|
-
from ._auth.
|
13
|
-
from ._auth._client_credentials import ClientCredentialsService
|
14
|
-
from ._auth._oidc_utils import get_auth_config, get_auth_url
|
15
|
-
from ._auth._portal_service import PortalService, select_tenant
|
16
|
-
from ._auth._utils import update_auth_file, update_env_file
|
6
|
+
from ._auth._auth_service import AuthService
|
17
7
|
from ._utils._common import environment_options
|
18
8
|
from ._utils._console import ConsoleLogger
|
19
9
|
|
20
10
|
console = ConsoleLogger()
|
21
11
|
|
22
12
|
|
23
|
-
def is_port_in_use(port: int) -> bool:
|
24
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
25
|
-
try:
|
26
|
-
s.bind(("localhost", port))
|
27
|
-
s.close()
|
28
|
-
return False
|
29
|
-
except socket.error:
|
30
|
-
return True
|
31
|
-
|
32
|
-
|
33
|
-
def set_port():
|
34
|
-
auth_config = get_auth_config()
|
35
|
-
port = int(auth_config.get("port", 8104))
|
36
|
-
port_option_one = int(auth_config.get("portOptionOne", 8104)) # type: ignore
|
37
|
-
port_option_two = int(auth_config.get("portOptionTwo", 8055)) # type: ignore
|
38
|
-
port_option_three = int(auth_config.get("portOptionThree", 42042)) # type: ignore
|
39
|
-
if is_port_in_use(port):
|
40
|
-
if is_port_in_use(port_option_one):
|
41
|
-
if is_port_in_use(port_option_two):
|
42
|
-
if is_port_in_use(port_option_three):
|
43
|
-
console.error(
|
44
|
-
"All configured ports are in use. Please close applications using ports or configure different ports."
|
45
|
-
)
|
46
|
-
else:
|
47
|
-
port = port_option_three
|
48
|
-
else:
|
49
|
-
port = port_option_two
|
50
|
-
else:
|
51
|
-
port = port_option_one
|
52
|
-
auth_config["port"] = port
|
53
|
-
with open(
|
54
|
-
os.path.join(os.path.dirname(__file__), "..", "auth_config.json"), "w"
|
55
|
-
) as f:
|
56
|
-
json.dump(auth_config, f)
|
57
|
-
|
58
|
-
|
59
13
|
@click.command()
|
60
14
|
@environment_options
|
61
15
|
@click.option(
|
@@ -63,6 +17,7 @@ def set_port():
|
|
63
17
|
"--force",
|
64
18
|
is_flag=True,
|
65
19
|
required=False,
|
20
|
+
default=False,
|
66
21
|
help="Force new token",
|
67
22
|
)
|
68
23
|
@click.option(
|
@@ -89,7 +44,7 @@ def set_port():
|
|
89
44
|
@track
|
90
45
|
def auth(
|
91
46
|
domain,
|
92
|
-
force:
|
47
|
+
force: bool = False,
|
93
48
|
client_id: Optional[str] = None,
|
94
49
|
client_secret: Optional[str] = None,
|
95
50
|
base_url: Optional[str] = None,
|
@@ -108,109 +63,21 @@ def auth(
|
|
108
63
|
- Set REQUESTS_CA_BUNDLE to specify a custom CA bundle for SSL verification
|
109
64
|
- Set UIPATH_DISABLE_SSL_VERIFY to disable SSL verification (not recommended)
|
110
65
|
"""
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
66
|
+
auth_service = AuthService(
|
67
|
+
domain,
|
68
|
+
force=force,
|
69
|
+
client_id=client_id,
|
70
|
+
client_secret=client_secret,
|
71
|
+
base_url=base_url,
|
72
|
+
scope=scope,
|
73
|
+
)
|
74
|
+
with console.spinner("Authenticating with UiPath ..."):
|
75
|
+
try:
|
76
|
+
auth_service.authenticate()
|
77
|
+
console.success(
|
78
|
+
"Authentication successful.",
|
119
79
|
)
|
120
|
-
|
121
|
-
|
122
|
-
# Check if client credentials are provided for unattended authentication
|
123
|
-
if client_id and client_secret:
|
124
|
-
if not base_url:
|
80
|
+
except KeyboardInterrupt:
|
125
81
|
console.error(
|
126
|
-
"
|
127
|
-
)
|
128
|
-
return
|
129
|
-
|
130
|
-
with console.spinner("Authenticating with client credentials ..."):
|
131
|
-
credentials_service = ClientCredentialsService(domain)
|
132
|
-
|
133
|
-
# If base_url is provided, extract domain from it to override the CLI domain parameter
|
134
|
-
if base_url:
|
135
|
-
extracted_domain = credentials_service.extract_domain_from_base_url(
|
136
|
-
base_url
|
137
|
-
)
|
138
|
-
credentials_service.domain = extracted_domain
|
139
|
-
|
140
|
-
token_data = credentials_service.authenticate(
|
141
|
-
client_id, client_secret, scope
|
142
|
-
)
|
143
|
-
|
144
|
-
if token_data:
|
145
|
-
credentials_service.setup_environment(token_data, base_url)
|
146
|
-
console.success(
|
147
|
-
"Client credentials authentication successful.",
|
148
|
-
)
|
149
|
-
else:
|
150
|
-
console.error(
|
151
|
-
"Client credentials authentication failed. Please check your credentials.",
|
152
|
-
)
|
153
|
-
return
|
154
|
-
|
155
|
-
# Interactive authentication flow (existing logic)
|
156
|
-
with console.spinner("Authenticating with UiPath ..."):
|
157
|
-
with PortalService(domain) as portal_service:
|
158
|
-
if not force:
|
159
|
-
if (
|
160
|
-
os.getenv("UIPATH_URL")
|
161
|
-
and os.getenv("UIPATH_TENANT_ID")
|
162
|
-
and os.getenv("UIPATH_ORGANIZATION_ID")
|
163
|
-
):
|
164
|
-
try:
|
165
|
-
portal_service.ensure_valid_token()
|
166
|
-
console.success(
|
167
|
-
"Authentication successful.",
|
168
|
-
)
|
169
|
-
return
|
170
|
-
except Exception:
|
171
|
-
console.info(
|
172
|
-
"Authentication token is invalid. Please reauthenticate.",
|
173
|
-
)
|
174
|
-
|
175
|
-
auth_url, code_verifier, state = get_auth_url(domain)
|
176
|
-
|
177
|
-
webbrowser.open(auth_url, 1)
|
178
|
-
auth_config = get_auth_config()
|
179
|
-
|
180
|
-
console.link(
|
181
|
-
"If a browser window did not open, please open the following URL in your browser:",
|
182
|
-
auth_url,
|
82
|
+
"Authentication cancelled by user.",
|
183
83
|
)
|
184
|
-
|
185
|
-
try:
|
186
|
-
server = HTTPServer(port=auth_config["port"])
|
187
|
-
token_data = asyncio.run(server.start(state, code_verifier, domain))
|
188
|
-
|
189
|
-
if not token_data:
|
190
|
-
console.error(
|
191
|
-
"Authentication failed. Please try again.",
|
192
|
-
)
|
193
|
-
return
|
194
|
-
|
195
|
-
portal_service.update_token_data(token_data)
|
196
|
-
update_auth_file(token_data)
|
197
|
-
access_token = token_data["access_token"]
|
198
|
-
update_env_file({"UIPATH_ACCESS_TOKEN": access_token})
|
199
|
-
|
200
|
-
tenants_and_organizations = (
|
201
|
-
portal_service.get_tenants_and_organizations()
|
202
|
-
)
|
203
|
-
base_url = select_tenant(domain, tenants_and_organizations)
|
204
|
-
try:
|
205
|
-
portal_service.post_auth(base_url)
|
206
|
-
console.success(
|
207
|
-
"Authentication successful.",
|
208
|
-
)
|
209
|
-
except Exception:
|
210
|
-
console.error(
|
211
|
-
"Could not prepare the environment. Please try again.",
|
212
|
-
)
|
213
|
-
except KeyboardInterrupt:
|
214
|
-
console.error(
|
215
|
-
"Authentication cancelled by user.",
|
216
|
-
)
|
uipath/_cli/cli_invoke.py
CHANGED
@@ -18,6 +18,7 @@ from ..telemetry import track
|
|
18
18
|
from ._utils._common import get_env_vars
|
19
19
|
from ._utils._folders import get_personal_workspace_info
|
20
20
|
from ._utils._processes import get_release_info
|
21
|
+
from .middlewares import Middlewares
|
21
22
|
|
22
23
|
logger = logging.getLogger(__name__)
|
23
24
|
console = ConsoleLogger()
|
@@ -87,6 +88,25 @@ def invoke(
|
|
87
88
|
"x-uipath-organizationunitid": str(personal_workspace_folder_id),
|
88
89
|
}
|
89
90
|
|
91
|
+
context = {
|
92
|
+
"url": url,
|
93
|
+
"payload": payload,
|
94
|
+
"headers": headers,
|
95
|
+
}
|
96
|
+
|
97
|
+
result = Middlewares.next("invoke", context)
|
98
|
+
|
99
|
+
if result.error_message:
|
100
|
+
console.error(
|
101
|
+
result.error_message, include_traceback=result.should_include_stacktrace
|
102
|
+
)
|
103
|
+
|
104
|
+
if result.info_message:
|
105
|
+
console.info(result.info_message)
|
106
|
+
|
107
|
+
if not result.should_continue:
|
108
|
+
return
|
109
|
+
|
90
110
|
with httpx.Client(**get_httpx_client_kwargs()) as client:
|
91
111
|
response = client.post(url, json=payload, headers=headers)
|
92
112
|
|
uipath/_cli/middlewares.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: uipath
|
3
|
-
Version: 2.1.
|
3
|
+
Version: 2.1.42
|
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
|
@@ -6,40 +6,41 @@ uipath/_uipath.py,sha256=lDsF2rBurqxm24DlRan25z9SU9t9b2RkAGvoI645QSw,4314
|
|
6
6
|
uipath/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
uipath/_cli/README.md,sha256=GLtCfbeIKZKNnGTCsfSVqRQ27V1btT1i2bSAyW_xZl4,474
|
8
8
|
uipath/_cli/__init__.py,sha256=kf4GINkunFGMZkTk2Z4f1Q3-OsxpNnV6u_9BsBt1i0E,2229
|
9
|
-
uipath/_cli/cli_auth.py,sha256=
|
9
|
+
uipath/_cli/cli_auth.py,sha256=i3ykLlCg68xgPXHHaa0agHwGFIiLiTLzOiF6Su8XaEo,2436
|
10
10
|
uipath/_cli/cli_deploy.py,sha256=KPCmQ0c_NYD5JofSDao5r6QYxHshVCRxlWDVnQvlp5w,645
|
11
11
|
uipath/_cli/cli_dev.py,sha256=JRzXrAUM_sj6FCVG-VveYADTwR8yQ330SgYs3LgbvJc,2104
|
12
12
|
uipath/_cli/cli_eval.py,sha256=Hze4PwW4smivmSZg_eGDHr3pZ6LHxX5MkTJuXB2xpxs,3598
|
13
13
|
uipath/_cli/cli_init.py,sha256=ls577uNm2zWccknIhtVFS3ah2ds0QSy2_TgMp6z7Wt4,6049
|
14
|
-
uipath/_cli/cli_invoke.py,sha256=
|
14
|
+
uipath/_cli/cli_invoke.py,sha256=4jyhqcy7tPrpxvaUhW-9gut6ddsCGMdJJcpOXXmIe8g,4348
|
15
15
|
uipath/_cli/cli_new.py,sha256=9378NYUBc9j-qKVXV7oja-jahfJhXBg8zKVyaon7ctY,2102
|
16
16
|
uipath/_cli/cli_pack.py,sha256=NmwZTfwZ2fURiHyiX1BM0juAtBOjPB1Jmcpu-rD7p-4,11025
|
17
17
|
uipath/_cli/cli_publish.py,sha256=FmBCdeh4zFaESOLfzTTPxGcOwUtsQ_WkvF_fjHEdU8s,6448
|
18
18
|
uipath/_cli/cli_pull.py,sha256=vwS0KMX6O2L6RaPy8tw_qzXe4dC7kf_G6nbLm0I62eI,6831
|
19
19
|
uipath/_cli/cli_push.py,sha256=-j-gDIbT8GyU2SybLQqFl5L8KI9nu3CDijVtltDgX20,3132
|
20
20
|
uipath/_cli/cli_run.py,sha256=pM4FoR0mbykIK2O68auN_NX1LckNsMLJLBtW8MqgLxo,7777
|
21
|
-
uipath/_cli/middlewares.py,sha256=
|
21
|
+
uipath/_cli/middlewares.py,sha256=vgvzIHHpaDxUxHV8cbBv9_PFbq4E4Gq-iJTLvrlyIfw,4956
|
22
22
|
uipath/_cli/spinner.py,sha256=bS-U_HA5yne11ejUERu7CQoXmWdabUD2bm62EfEdV8M,1107
|
23
|
-
uipath/_cli/_auth/_auth_server.py,sha256=
|
24
|
-
uipath/_cli/_auth/
|
23
|
+
uipath/_cli/_auth/_auth_server.py,sha256=22km0F1NFNXgyLbvtAx3ssiQlVGHroLdtDCWEqiCiMg,7106
|
24
|
+
uipath/_cli/_auth/_auth_service.py,sha256=tX8YuHlgUn2qUDQ_hrbabs7kMTD3K1u3EgqGYj92KRM,6106
|
25
|
+
uipath/_cli/_auth/_client_credentials.py,sha256=VIyzgnGHSZxJXG8kSFIcCH0n2K2zep2SYcb5q1nBFcs,5408
|
25
26
|
uipath/_cli/_auth/_models.py,sha256=sYMCfvmprIqnZxStlD_Dxx2bcxgn0Ri4D7uwemwkcNg,948
|
26
|
-
uipath/_cli/_auth/_oidc_utils.py,sha256=
|
27
|
-
uipath/_cli/_auth/_portal_service.py,sha256
|
28
|
-
uipath/_cli/_auth/_url_utils.py,sha256=
|
27
|
+
uipath/_cli/_auth/_oidc_utils.py,sha256=j9VCXai_dM9PF1WtoWEsXETvrWUjuTgU-dCJ3oRudyg,2394
|
28
|
+
uipath/_cli/_auth/_portal_service.py,sha256=MwsqEs505kMCiPCfjcelgflzvg1V3MqRNNVaOismaDc,8272
|
29
|
+
uipath/_cli/_auth/_url_utils.py,sha256=Vr-eYbwW_ltmwkULNEAbn6LNsMHnT4uT1n_1zlqD4Ss,1503
|
29
30
|
uipath/_cli/_auth/_utils.py,sha256=9nb76xe5XmDZ0TAncp-_1SKqL6FdwRi9eS3C2noN1lY,1591
|
30
31
|
uipath/_cli/_auth/auth_config.json,sha256=UnAhdum8phjuZaZKE5KLp0IcPCbIltDEU1M_G8gVbos,443
|
31
32
|
uipath/_cli/_auth/index.html,sha256=uGK0CDTP8Rys_p4O_Pbd2x4tz0frKNVcumjrXnal5Nc,22814
|
32
33
|
uipath/_cli/_auth/localhost.crt,sha256=oGl9oLLOiouHubAt39B4zEfylFvKEtbtr_43SIliXJc,1226
|
33
34
|
uipath/_cli/_auth/localhost.key,sha256=X31VYXD8scZtmGA837dGX5l6G-LXHLo5ItWJhZXaz3c,1679
|
34
|
-
uipath/_cli/_dev/_terminal/__init__.py,sha256=
|
35
|
-
uipath/_cli/_dev/_terminal/_components/_chat.py,sha256=
|
36
|
-
uipath/_cli/_dev/_terminal/_components/_details.py,sha256=
|
35
|
+
uipath/_cli/_dev/_terminal/__init__.py,sha256=35nz6kTZyWSM9pP-FsXq8VXo6r1iM8rRUtqOqYahFLc,13271
|
36
|
+
uipath/_cli/_dev/_terminal/_components/_chat.py,sha256=NLRoy49QScHiI-q0FGykkaU8ajv1d23fx7issSALcFA,4119
|
37
|
+
uipath/_cli/_dev/_terminal/_components/_details.py,sha256=FbLYtJ56gqHV6CIrpzO_n9Sk_YNg4nzRKTSsbj-DBPQ,17257
|
37
38
|
uipath/_cli/_dev/_terminal/_components/_history.py,sha256=dcT9tohEwpUaLGi7VWu5d-mDIF45UxFzN2Yvdf5N-eM,2691
|
38
39
|
uipath/_cli/_dev/_terminal/_components/_json_input.py,sha256=MPkaeiA5KfkwJZKuNJ02hQksVtluZlmJv9nLRRAWYQI,592
|
39
|
-
uipath/_cli/_dev/_terminal/_components/_new.py,sha256=
|
40
|
-
uipath/_cli/_dev/_terminal/_models/_execution.py,sha256=
|
40
|
+
uipath/_cli/_dev/_terminal/_components/_new.py,sha256=paA8oRhP5mphpf3RHV0gx7_CYdN5e6158tv_XVQifdE,5219
|
41
|
+
uipath/_cli/_dev/_terminal/_models/_execution.py,sha256=gPcxtwWR9eO929VaieOdI1e77clceKLoKA0FYayuCFQ,2869
|
41
42
|
uipath/_cli/_dev/_terminal/_models/_messages.py,sha256=p66MHUi_SS30CQWXtiwydybMKBQrtZLXNfNUD6TdK1w,1832
|
42
|
-
uipath/_cli/_dev/_terminal/_styles/terminal.tcss,sha256=
|
43
|
+
uipath/_cli/_dev/_terminal/_styles/terminal.tcss,sha256=ktVpKwXIXw2VZp8KIZD6fO9i9NTGvts_icCTxMdzEiY,3240
|
43
44
|
uipath/_cli/_dev/_terminal/_utils/_chat.py,sha256=YUZxYVdmEManwHDuZsczJT1dWIYE1dVBgABlurwMFcE,8493
|
44
45
|
uipath/_cli/_dev/_terminal/_utils/_exporter.py,sha256=oI6D_eMwrh_2aqDYUh4GrJg8VLGrLYhDahR-_o0uJns,4144
|
45
46
|
uipath/_cli/_dev/_terminal/_utils/_logger.py,sha256=jeNShEED27cNIHTe_NNx-2kUiXpSLTmi0onM6tVkqRM,888
|
@@ -57,7 +58,7 @@ uipath/_cli/_evals/_models/__init__.py,sha256=Ewjp3u2YeTH2MmzY9LWf7EIbAoIf_nW9fM
|
|
57
58
|
uipath/_cli/_evals/_models/_evaluation_set.py,sha256=tVHykSget-G3sOCs9bSchMYUTpFqzXVlYYbY8L9SI0c,1518
|
58
59
|
uipath/_cli/_evals/_models/_evaluators.py,sha256=l57NEVyYmzSKuoIXuGkE94Br01hAMg35fiS2MlTkaQM,2115
|
59
60
|
uipath/_cli/_push/sw_file_handler.py,sha256=AX4TKM-q6CNGw3JyBW02M8ktPZuFMcAU9LN3Ii0Q2QI,18202
|
60
|
-
uipath/_cli/_runtime/_contracts.py,sha256=
|
61
|
+
uipath/_cli/_runtime/_contracts.py,sha256=5seaUN5nurtimNiCH3c0PH9IcGiF-v1WNxJR-bQN7fs,21380
|
61
62
|
uipath/_cli/_runtime/_escalation.py,sha256=x3vI98qsfRA-fL_tNkRVTFXioM5Gv2w0GFcXJJ5eQtg,7981
|
62
63
|
uipath/_cli/_runtime/_hitl.py,sha256=VKbM021nVg1HEDnTfucSLJ0LsDn83CKyUtVzofS2qTU,11369
|
63
64
|
uipath/_cli/_runtime/_logging.py,sha256=MGklGKPjYKjs7J5Jy9eplA9zCDsdtEbkZdCbTwgut_4,8311
|
@@ -139,8 +140,8 @@ uipath/tracing/_traced.py,sha256=qeVDrds2OUnpdUIA0RhtF0kg2dlAZhyC1RRkI-qivTM,185
|
|
139
140
|
uipath/tracing/_utils.py,sha256=wJRELaPu69iY0AhV432Dk5QYf_N_ViRU4kAUG1BI1ew,10384
|
140
141
|
uipath/utils/__init__.py,sha256=VD-KXFpF_oWexFg6zyiWMkxl2HM4hYJMIUDZ1UEtGx0,105
|
141
142
|
uipath/utils/_endpoints_manager.py,sha256=iRTl5Q0XAm_YgcnMcJOXtj-8052sr6jpWuPNz6CgT0Q,8408
|
142
|
-
uipath-2.1.
|
143
|
-
uipath-2.1.
|
144
|
-
uipath-2.1.
|
145
|
-
uipath-2.1.
|
146
|
-
uipath-2.1.
|
143
|
+
uipath-2.1.42.dist-info/METADATA,sha256=nv3-hB1EOq2ErNpPkxUUBfjJ5y1BWtpKJ57orbyEXho,6482
|
144
|
+
uipath-2.1.42.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
145
|
+
uipath-2.1.42.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
|
146
|
+
uipath-2.1.42.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
|
147
|
+
uipath-2.1.42.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|