alita-sdk 0.3.412__py3-none-any.whl → 0.3.413__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.
- alita_sdk/configurations/bitbucket.py +95 -0
- alita_sdk/configurations/confluence.py +96 -1
- alita_sdk/configurations/gitlab.py +79 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/langchain/constants.py +2 -0
- alita_sdk/runtime/langchain/langraph_agent.py +2 -10
- alita_sdk/runtime/langchain/utils.py +4 -3
- alita_sdk/runtime/tools/function.py +2 -2
- alita_sdk/runtime/tools/llm.py +5 -0
- alita_sdk/tools/sharepoint/api_wrapper.py +26 -17
- alita_sdk/tools/sharepoint/utils.py +8 -2
- {alita_sdk-0.3.412.dist-info → alita_sdk-0.3.413.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.412.dist-info → alita_sdk-0.3.413.dist-info}/RECORD +20 -20
- {alita_sdk-0.3.412.dist-info → alita_sdk-0.3.413.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.412.dist-info → alita_sdk-0.3.413.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.412.dist-info → alita_sdk-0.3.413.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
from atlassian import Bitbucket
|
|
3
4
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
|
4
5
|
|
|
5
6
|
|
|
@@ -30,3 +31,97 @@ class BitbucketConfiguration(BaseModel):
|
|
|
30
31
|
url: str = Field(description="Bitbucket URL")
|
|
31
32
|
username: str = Field(description="Bitbucket Username")
|
|
32
33
|
password: SecretStr = Field(description="Bitbucket Password/App Password")
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def check_connection(settings: dict) -> str | None:
|
|
37
|
+
"""
|
|
38
|
+
Check the connection to Bitbucket.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
settings: Dictionary containing Bitbucket configuration
|
|
42
|
+
- url: Bitbucket instance URL (required)
|
|
43
|
+
- username: Bitbucket username (required)
|
|
44
|
+
- password: Password or App Password (required)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
None if connection successful, error message string if failed
|
|
48
|
+
"""
|
|
49
|
+
import requests
|
|
50
|
+
from requests.auth import HTTPBasicAuth
|
|
51
|
+
|
|
52
|
+
# Validate url
|
|
53
|
+
url = settings.get("url", "").strip()
|
|
54
|
+
if not url:
|
|
55
|
+
return "Bitbucket URL is required"
|
|
56
|
+
|
|
57
|
+
# Normalize URL - remove trailing slashes
|
|
58
|
+
url = url.rstrip("/")
|
|
59
|
+
|
|
60
|
+
# Basic URL validation
|
|
61
|
+
if not url.startswith(("http://", "https://")):
|
|
62
|
+
return "Bitbucket URL must start with http:// or https://"
|
|
63
|
+
|
|
64
|
+
# Validate username
|
|
65
|
+
username = settings.get("username", "").strip()
|
|
66
|
+
if not username:
|
|
67
|
+
return "Bitbucket username is required"
|
|
68
|
+
|
|
69
|
+
# Validate password
|
|
70
|
+
password = settings.get("password")
|
|
71
|
+
if not password:
|
|
72
|
+
return "Bitbucket password is required"
|
|
73
|
+
|
|
74
|
+
# Extract password value if it's a SecretStr
|
|
75
|
+
password_value = password.get_secret_value() if hasattr(password, 'get_secret_value') else password
|
|
76
|
+
|
|
77
|
+
if not password_value or not str(password_value).strip():
|
|
78
|
+
return "Bitbucket password cannot be empty"
|
|
79
|
+
|
|
80
|
+
# Detect if this is Bitbucket Cloud or Server/Data Center
|
|
81
|
+
is_cloud = "bitbucket.org" in url.lower() or "api.bitbucket.org" in url.lower()
|
|
82
|
+
is_correct_bitbucket_domain = "bitbucket" in url.lower()
|
|
83
|
+
|
|
84
|
+
if is_cloud:
|
|
85
|
+
# Bitbucket Cloud: Use API v2.0
|
|
86
|
+
# Endpoint: /2.0/user - returns current authenticated user
|
|
87
|
+
test_url = f"{url}/2.0/user"
|
|
88
|
+
else:
|
|
89
|
+
# Bitbucket Server/Data Center: Use API v1.0
|
|
90
|
+
# Endpoint: /rest/api/1.0/users/{username}
|
|
91
|
+
test_url = f"{url}/rest/api/1.0/users/{username}"
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
response = requests.get(
|
|
95
|
+
test_url,
|
|
96
|
+
auth=HTTPBasicAuth(username, str(password_value).strip()),
|
|
97
|
+
timeout=10
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Check response status
|
|
101
|
+
if response.status_code == 200:
|
|
102
|
+
# Successfully connected and authenticated
|
|
103
|
+
return None
|
|
104
|
+
elif response.status_code == 401:
|
|
105
|
+
return "Authentication failed: invalid username or password"
|
|
106
|
+
elif response.status_code == 403:
|
|
107
|
+
return "Access forbidden: check user permissions"
|
|
108
|
+
elif response.status_code == 404:
|
|
109
|
+
if not is_correct_bitbucket_domain:
|
|
110
|
+
return f"Url you provided is incorrect. Please provide correct server or cloud bitbucket url."
|
|
111
|
+
if is_cloud:
|
|
112
|
+
return "Bitbucket API endpoint not found: please provide the correct bitbucket cloud URL"
|
|
113
|
+
else:
|
|
114
|
+
return "Bitbucket API endpoint not found: please provide the correct bitbucket server URL"
|
|
115
|
+
else:
|
|
116
|
+
return f"Bitbucket API returned status code {response.status_code}"
|
|
117
|
+
|
|
118
|
+
except requests.exceptions.SSLError as e:
|
|
119
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
120
|
+
except requests.exceptions.ConnectionError:
|
|
121
|
+
return f"Cannot connect to Bitbucket at {url if not is_cloud else 'api.bitbucket.org'}: connection refused"
|
|
122
|
+
except requests.exceptions.Timeout:
|
|
123
|
+
return f"Connection to Bitbucket at {url if not is_cloud else 'api.bitbucket.org'} timed out"
|
|
124
|
+
except requests.exceptions.RequestException as e:
|
|
125
|
+
return f"Error connecting to Bitbucket: {str(e)}"
|
|
126
|
+
except Exception as e:
|
|
127
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -34,4 +34,99 @@ class ConfluenceConfiguration(BaseModel):
|
|
|
34
34
|
base_url: str = Field(description="Confluence URL")
|
|
35
35
|
username: Optional[str] = Field(description="Confluence Username", default=None)
|
|
36
36
|
api_key: Optional[SecretStr] = Field(description="Confluence API Key", default=None)
|
|
37
|
-
token: Optional[SecretStr] = Field(description="Confluence Token", default=None)
|
|
37
|
+
token: Optional[SecretStr] = Field(description="Confluence Token", default=None)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def check_connection(settings: dict) -> str | None:
|
|
41
|
+
"""
|
|
42
|
+
Check the connection to Confluence.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
settings: Dictionary containing Confluence configuration
|
|
46
|
+
- base_url: Confluence instance URL (required)
|
|
47
|
+
- username: Username for Basic Auth (optional)
|
|
48
|
+
- api_key: API key/password for Basic Auth (optional)
|
|
49
|
+
- token: Bearer token for authentication (optional)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
None if connection successful, error message string if failed
|
|
53
|
+
"""
|
|
54
|
+
import requests
|
|
55
|
+
from requests.auth import HTTPBasicAuth
|
|
56
|
+
|
|
57
|
+
# Validate base_url
|
|
58
|
+
base_url = settings.get("base_url", "").strip()
|
|
59
|
+
if not base_url:
|
|
60
|
+
return "Confluence URL is required"
|
|
61
|
+
|
|
62
|
+
# Normalize URL - remove trailing slashes
|
|
63
|
+
base_url = base_url.rstrip("/")
|
|
64
|
+
|
|
65
|
+
# Basic URL validation
|
|
66
|
+
if not base_url.startswith(("http://", "https://")):
|
|
67
|
+
return "Confluence URL must start with http:// or https://"
|
|
68
|
+
|
|
69
|
+
# Check authentication credentials
|
|
70
|
+
username = settings.get("username")
|
|
71
|
+
api_key = settings.get("api_key")
|
|
72
|
+
token = settings.get("token")
|
|
73
|
+
|
|
74
|
+
# Validate authentication - at least one method must be provided
|
|
75
|
+
has_basic_auth = bool(username and api_key)
|
|
76
|
+
has_token = bool(token and str(token).strip())
|
|
77
|
+
|
|
78
|
+
# Determine authentication method
|
|
79
|
+
auth_headers = {}
|
|
80
|
+
auth = None
|
|
81
|
+
|
|
82
|
+
if has_token:
|
|
83
|
+
# Bearer token authentication
|
|
84
|
+
token_value = token.get_secret_value() if hasattr(token, 'get_secret_value') else token
|
|
85
|
+
auth_headers["Authorization"] = f"Bearer {token_value}"
|
|
86
|
+
elif has_basic_auth:
|
|
87
|
+
# Basic authentication
|
|
88
|
+
api_key_value = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else api_key
|
|
89
|
+
auth = HTTPBasicAuth(username, api_key_value)
|
|
90
|
+
else:
|
|
91
|
+
return "Authentication required: provide either token or both username and api_key"
|
|
92
|
+
|
|
93
|
+
# Test connection using /rest/api/user/current endpoint
|
|
94
|
+
# This endpoint returns current user info and validates authentication
|
|
95
|
+
test_url = f"{base_url}/rest/api/user/current"
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
response = requests.get(
|
|
99
|
+
test_url,
|
|
100
|
+
auth=auth,
|
|
101
|
+
headers=auth_headers,
|
|
102
|
+
timeout=10
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Check response status
|
|
106
|
+
if response.status_code == 200:
|
|
107
|
+
# Successfully connected and authenticated
|
|
108
|
+
return None
|
|
109
|
+
elif response.status_code == 401:
|
|
110
|
+
# Authentication failed
|
|
111
|
+
if has_token:
|
|
112
|
+
return "Authentication failed: Invalid token"
|
|
113
|
+
else:
|
|
114
|
+
return "Authentication failed: Invalid username or API key"
|
|
115
|
+
elif response.status_code == 403:
|
|
116
|
+
return """Access forbidden: check permissions and verify the credentials you provided.
|
|
117
|
+
Most probably you provided incorrect credentials (user name and api key or token)"""
|
|
118
|
+
elif response.status_code == 404:
|
|
119
|
+
return "Confluence API endpoint not found: verify the Confluence URL"
|
|
120
|
+
else:
|
|
121
|
+
return f"Confluence API returned status code {response.status_code}"
|
|
122
|
+
|
|
123
|
+
except requests.exceptions.SSLError as e:
|
|
124
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
125
|
+
except requests.exceptions.ConnectionError:
|
|
126
|
+
return f"Cannot connect to Confluence at {base_url}: connection refused"
|
|
127
|
+
except requests.exceptions.Timeout:
|
|
128
|
+
return f"Connection to Confluence at {base_url} timed out"
|
|
129
|
+
except requests.exceptions.RequestException as e:
|
|
130
|
+
return f"Error connecting to Confluence: {str(e)}"
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -29,3 +29,82 @@ class GitlabConfiguration(BaseModel):
|
|
|
29
29
|
)
|
|
30
30
|
url: str = Field(description="GitLab URL")
|
|
31
31
|
private_token: SecretStr = Field(description="GitLab private token")
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def check_connection(settings: dict) -> str | None:
|
|
35
|
+
"""
|
|
36
|
+
Check the connection to GitLab.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
settings: Dictionary containing GitLab configuration
|
|
40
|
+
- url: GitLab instance URL (required)
|
|
41
|
+
- private_token: GitLab private token for authentication (required)
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
None if connection successful, error message string if failed
|
|
45
|
+
"""
|
|
46
|
+
import requests
|
|
47
|
+
|
|
48
|
+
# Validate url
|
|
49
|
+
url = settings.get("url", "").strip()
|
|
50
|
+
if not url:
|
|
51
|
+
return "GitLab URL is required"
|
|
52
|
+
|
|
53
|
+
# Normalize URL - remove trailing slashes
|
|
54
|
+
url = url.rstrip("/")
|
|
55
|
+
|
|
56
|
+
# Basic URL validation
|
|
57
|
+
if not url.startswith(("http://", "https://")):
|
|
58
|
+
return "GitLab URL must start with http:// or https://"
|
|
59
|
+
|
|
60
|
+
# Validate private_token
|
|
61
|
+
private_token = settings.get("private_token")
|
|
62
|
+
if not private_token:
|
|
63
|
+
return "GitLab private token is required"
|
|
64
|
+
|
|
65
|
+
# Extract token value if it's a SecretStr
|
|
66
|
+
token_value = private_token.get_secret_value() if hasattr(private_token, 'get_secret_value') else private_token
|
|
67
|
+
|
|
68
|
+
if not token_value or not str(token_value).strip():
|
|
69
|
+
return "GitLab private token cannot be empty"
|
|
70
|
+
|
|
71
|
+
# Test connection using /api/v4/user endpoint
|
|
72
|
+
# This endpoint returns current authenticated user info
|
|
73
|
+
test_url = f"{url}/api/v4/user"
|
|
74
|
+
|
|
75
|
+
# GitLab supports both PRIVATE-TOKEN header and Authorization Bearer
|
|
76
|
+
# Using PRIVATE-TOKEN is GitLab-specific and more explicit
|
|
77
|
+
headers = {
|
|
78
|
+
"PRIVATE-TOKEN": str(token_value).strip()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
response = requests.get(
|
|
83
|
+
test_url,
|
|
84
|
+
headers=headers,
|
|
85
|
+
timeout=10
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Check response status
|
|
89
|
+
if response.status_code == 200:
|
|
90
|
+
# Successfully connected and authenticated
|
|
91
|
+
return None
|
|
92
|
+
elif response.status_code == 401:
|
|
93
|
+
return "Authentication failed: invalid private token"
|
|
94
|
+
elif response.status_code == 403:
|
|
95
|
+
return "Access forbidden: token lacks required permissions"
|
|
96
|
+
elif response.status_code == 404:
|
|
97
|
+
return "GitLab API endpoint not found: verify the GitLab URL"
|
|
98
|
+
else:
|
|
99
|
+
return f"GitLab API returned status code {response.status_code}"
|
|
100
|
+
|
|
101
|
+
except requests.exceptions.SSLError as e:
|
|
102
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
103
|
+
except requests.exceptions.ConnectionError:
|
|
104
|
+
return f"Cannot connect to GitLab at {url}: connection refused"
|
|
105
|
+
except requests.exceptions.Timeout:
|
|
106
|
+
return f"Connection to GitLab at {url} timed out"
|
|
107
|
+
except requests.exceptions.RequestException as e:
|
|
108
|
+
return f"Error connecting to GitLab: {str(e)}"
|
|
109
|
+
except Exception as e:
|
|
110
|
+
return f"Unexpected error: {str(e)}"
|
alita_sdk/configurations/jira.py
CHANGED
|
@@ -35,3 +35,106 @@ class JiraConfiguration(BaseModel):
|
|
|
35
35
|
username: Optional[str] = Field(description="Jira Username", default=None)
|
|
36
36
|
api_key: Optional[SecretStr] = Field(description="Jira API Key", default=None)
|
|
37
37
|
token: Optional[SecretStr] = Field(description="Jira Token", default=None)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def check_connection(settings: dict) -> str | None:
|
|
41
|
+
"""
|
|
42
|
+
Check Jira connection using provided settings.
|
|
43
|
+
Returns None if connection is successful, error message otherwise.
|
|
44
|
+
|
|
45
|
+
Tests authentication by calling the /rest/api/latest/myself endpoint,
|
|
46
|
+
which returns information about the currently authenticated user.
|
|
47
|
+
"""
|
|
48
|
+
import requests
|
|
49
|
+
from requests.auth import HTTPBasicAuth
|
|
50
|
+
|
|
51
|
+
# Extract and validate settings
|
|
52
|
+
base_url = settings.get('base_url', '').rstrip('/')
|
|
53
|
+
username = settings.get('username')
|
|
54
|
+
api_key = settings.get('api_key')
|
|
55
|
+
token = settings.get('token')
|
|
56
|
+
|
|
57
|
+
# Validate base URL
|
|
58
|
+
if not base_url:
|
|
59
|
+
return "Base URL is required"
|
|
60
|
+
|
|
61
|
+
if not base_url.startswith(('http://', 'https://')):
|
|
62
|
+
return "Base URL must start with http:// or https://"
|
|
63
|
+
|
|
64
|
+
# Validate authentication - at least one method must be provided
|
|
65
|
+
has_basic_auth = bool(username and api_key)
|
|
66
|
+
has_token = bool(token and str(token).strip())
|
|
67
|
+
|
|
68
|
+
if not (has_basic_auth or has_token):
|
|
69
|
+
return "Authentication required: Provide either username + API key, or bearer token"
|
|
70
|
+
|
|
71
|
+
# Setup authentication headers
|
|
72
|
+
headers = {'Accept': 'application/json'}
|
|
73
|
+
auth = None
|
|
74
|
+
|
|
75
|
+
if has_token:
|
|
76
|
+
# Bearer token authentication
|
|
77
|
+
token_value = token.get_secret_value() if hasattr(token, 'get_secret_value') else token
|
|
78
|
+
headers['Authorization'] = f'Bearer {token_value}'
|
|
79
|
+
elif has_basic_auth:
|
|
80
|
+
# Basic authentication
|
|
81
|
+
api_key_value = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else api_key
|
|
82
|
+
auth = HTTPBasicAuth(username, api_key_value)
|
|
83
|
+
|
|
84
|
+
# Build API endpoint - using 'latest' for version independence
|
|
85
|
+
api_endpoint = f"{base_url}/rest/api/latest/myself"
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Make authenticated request to verify credentials
|
|
89
|
+
response = requests.get(
|
|
90
|
+
api_endpoint,
|
|
91
|
+
headers=headers,
|
|
92
|
+
auth=auth,
|
|
93
|
+
timeout=10
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Handle different response codes
|
|
97
|
+
if response.status_code == 200:
|
|
98
|
+
return None # Success - credentials are valid
|
|
99
|
+
|
|
100
|
+
elif response.status_code == 401:
|
|
101
|
+
# Authentication failed
|
|
102
|
+
if has_token:
|
|
103
|
+
return "Authentication failed: Invalid bearer token"
|
|
104
|
+
else:
|
|
105
|
+
return "Authentication failed: Invalid username or API key"
|
|
106
|
+
|
|
107
|
+
elif response.status_code == 403:
|
|
108
|
+
# Authenticated but insufficient permissions
|
|
109
|
+
return "Access forbidden: Your account has insufficient permissions to access Jira API"
|
|
110
|
+
|
|
111
|
+
elif response.status_code == 404:
|
|
112
|
+
# API endpoint not found - likely wrong URL
|
|
113
|
+
return "Jira API endpoint not found: Verify your base URL (e.g., 'https://yourinstance.atlassian.net')"
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
# Other HTTP errors - try to extract Jira error messages
|
|
117
|
+
error_detail = ""
|
|
118
|
+
try:
|
|
119
|
+
error_json = response.json()
|
|
120
|
+
if 'errorMessages' in error_json and error_json['errorMessages']:
|
|
121
|
+
error_detail = ": " + ", ".join(error_json['errorMessages'])
|
|
122
|
+
elif 'message' in error_json:
|
|
123
|
+
error_detail = f": {error_json['message']}"
|
|
124
|
+
except:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
return f"Connection failed with status {response.status_code}{error_detail}"
|
|
128
|
+
|
|
129
|
+
except requests.exceptions.SSLError:
|
|
130
|
+
return "SSL certificate verification failed: Check your Jira URL or network settings"
|
|
131
|
+
except requests.exceptions.ConnectionError:
|
|
132
|
+
return "Connection error: Unable to reach Jira server - check URL and network connectivity"
|
|
133
|
+
except requests.exceptions.Timeout:
|
|
134
|
+
return "Connection timeout: Jira server did not respond within 10 seconds"
|
|
135
|
+
except requests.exceptions.MissingSchema:
|
|
136
|
+
return "Invalid URL format: URL must include protocol (http:// or https://)"
|
|
137
|
+
except requests.exceptions.InvalidURL:
|
|
138
|
+
return "Invalid URL format: Please check your Jira base URL"
|
|
139
|
+
except Exception as e:
|
|
140
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -19,3 +19,91 @@ class TestRailConfiguration(BaseModel):
|
|
|
19
19
|
url: str = Field(description="Testrail URL")
|
|
20
20
|
email: str = Field(description="TestRail Email")
|
|
21
21
|
password: SecretStr = Field(description="TestRail Password")
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def check_connection(settings: dict) -> str | None:
|
|
25
|
+
"""
|
|
26
|
+
Check the connection to TestRail.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
settings: Dictionary containing TestRail configuration
|
|
30
|
+
- url: TestRail instance URL (required)
|
|
31
|
+
- email: User email for authentication (required)
|
|
32
|
+
- password: Password or API key for authentication (required)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
None if connection successful, error message string if failed
|
|
36
|
+
"""
|
|
37
|
+
import requests
|
|
38
|
+
from requests.auth import HTTPBasicAuth
|
|
39
|
+
|
|
40
|
+
# Validate url
|
|
41
|
+
url = settings.get("url", "").strip()
|
|
42
|
+
if not url:
|
|
43
|
+
return "TestRail URL is required"
|
|
44
|
+
|
|
45
|
+
# Normalize URL - remove trailing slashes
|
|
46
|
+
url = url.rstrip("/")
|
|
47
|
+
|
|
48
|
+
# Basic URL validation
|
|
49
|
+
if not url.startswith(("http://", "https://")):
|
|
50
|
+
return "TestRail URL must start with http:// or https://"
|
|
51
|
+
|
|
52
|
+
# Validate email
|
|
53
|
+
email = settings.get("email", "").strip()
|
|
54
|
+
if not email:
|
|
55
|
+
return "TestRail email is required"
|
|
56
|
+
|
|
57
|
+
# Validate password
|
|
58
|
+
password = settings.get("password")
|
|
59
|
+
if not password:
|
|
60
|
+
return "TestRail password is required"
|
|
61
|
+
|
|
62
|
+
# Extract password value if it's a SecretStr
|
|
63
|
+
password_value = password.get_secret_value() if hasattr(password, 'get_secret_value') else password
|
|
64
|
+
|
|
65
|
+
if not password_value or not password_value.strip():
|
|
66
|
+
return "TestRail password cannot be empty"
|
|
67
|
+
|
|
68
|
+
# Test connection using /index.php?/api/v2/get_user_by_email endpoint
|
|
69
|
+
# This endpoint returns user info and validates authentication
|
|
70
|
+
test_url = f"{url}/index.php?/api/v2/get_user_by_email&email={email}"
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
response = requests.get(
|
|
74
|
+
test_url,
|
|
75
|
+
auth=HTTPBasicAuth(email, password_value),
|
|
76
|
+
timeout=10
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Check response status
|
|
80
|
+
if response.status_code == 200:
|
|
81
|
+
# Successfully connected and authenticated
|
|
82
|
+
return None
|
|
83
|
+
elif response.status_code == 401:
|
|
84
|
+
return "Authentication failed: invalid email or password"
|
|
85
|
+
elif response.status_code == 403:
|
|
86
|
+
return "Access forbidden: check user permissions"
|
|
87
|
+
elif response.status_code == 404:
|
|
88
|
+
return "TestRail API endpoint not found: verify the TestRail URL"
|
|
89
|
+
elif response.status_code == 400:
|
|
90
|
+
# Could be invalid email format or other bad request
|
|
91
|
+
try:
|
|
92
|
+
error_data = response.json()
|
|
93
|
+
error_msg = error_data.get("error", "Bad request")
|
|
94
|
+
return f"Bad request: {error_msg}"
|
|
95
|
+
except:
|
|
96
|
+
return "Bad request: check email format and URL"
|
|
97
|
+
else:
|
|
98
|
+
return f"TestRail API returned status code {response.status_code}"
|
|
99
|
+
|
|
100
|
+
except requests.exceptions.SSLError as e:
|
|
101
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
102
|
+
except requests.exceptions.ConnectionError:
|
|
103
|
+
return f"Cannot connect to TestRail at {url}: connection refused"
|
|
104
|
+
except requests.exceptions.Timeout:
|
|
105
|
+
return f"Connection to TestRail at {url} timed out"
|
|
106
|
+
except requests.exceptions.RequestException as e:
|
|
107
|
+
return f"Error connecting to TestRail: {str(e)}"
|
|
108
|
+
except Exception as e:
|
|
109
|
+
return f"Unexpected error: {str(e)}"
|
alita_sdk/configurations/xray.py
CHANGED
|
@@ -30,3 +30,96 @@ class XrayConfiguration(BaseModel):
|
|
|
30
30
|
base_url: str = Field(description="Xray URL")
|
|
31
31
|
client_id: Optional[str] = Field(description="Client ID")
|
|
32
32
|
client_secret: Optional[SecretStr] = Field(description="Client secret")
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def check_connection(settings: dict) -> str | None:
|
|
36
|
+
"""
|
|
37
|
+
Check the connection to Xray Cloud.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
settings: Dictionary containing Xray configuration
|
|
41
|
+
- base_url: Xray Cloud URL (required)
|
|
42
|
+
- client_id: OAuth2 Client ID (required)
|
|
43
|
+
- client_secret: OAuth2 Client Secret (required)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
None if connection successful, error message string if failed
|
|
47
|
+
"""
|
|
48
|
+
import requests
|
|
49
|
+
|
|
50
|
+
# Validate base_url
|
|
51
|
+
base_url = settings.get("base_url", "").strip()
|
|
52
|
+
if not base_url:
|
|
53
|
+
return "Xray URL is required"
|
|
54
|
+
|
|
55
|
+
# Normalize URL - remove trailing slashes
|
|
56
|
+
base_url = base_url.rstrip("/")
|
|
57
|
+
|
|
58
|
+
# Basic URL validation
|
|
59
|
+
if not base_url.startswith(("http://", "https://")):
|
|
60
|
+
return "Xray URL must start with http:// or https://"
|
|
61
|
+
|
|
62
|
+
# Validate client_id
|
|
63
|
+
client_id = settings.get("client_id", "").strip() if settings.get("client_id") else ""
|
|
64
|
+
if not client_id:
|
|
65
|
+
return "Xray client ID is required"
|
|
66
|
+
|
|
67
|
+
# Validate client_secret
|
|
68
|
+
client_secret = settings.get("client_secret")
|
|
69
|
+
if not client_secret:
|
|
70
|
+
return "Xray client secret is required"
|
|
71
|
+
|
|
72
|
+
# Extract client_secret value if it's a SecretStr
|
|
73
|
+
client_secret_value = client_secret.get_secret_value() if hasattr(client_secret, 'get_secret_value') else client_secret
|
|
74
|
+
|
|
75
|
+
if not client_secret_value or not str(client_secret_value).strip():
|
|
76
|
+
return "Xray client secret cannot be empty"
|
|
77
|
+
|
|
78
|
+
# Test connection using /api/v2/authenticate endpoint
|
|
79
|
+
# This is the OAuth2 token generation endpoint for Xray Cloud
|
|
80
|
+
auth_url = f"{base_url}/api/v2/authenticate"
|
|
81
|
+
|
|
82
|
+
auth_payload = {
|
|
83
|
+
"client_id": client_id,
|
|
84
|
+
"client_secret": str(client_secret_value).strip()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
response = requests.post(
|
|
89
|
+
auth_url,
|
|
90
|
+
json=auth_payload,
|
|
91
|
+
headers={"Content-Type": "application/json"},
|
|
92
|
+
timeout=10
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Check response status
|
|
96
|
+
if response.status_code == 200:
|
|
97
|
+
# Successfully authenticated and got token
|
|
98
|
+
return None
|
|
99
|
+
elif response.status_code == 401:
|
|
100
|
+
return "Authentication failed: invalid client ID or secret"
|
|
101
|
+
elif response.status_code == 403:
|
|
102
|
+
return "Access forbidden: check client credentials"
|
|
103
|
+
elif response.status_code == 400:
|
|
104
|
+
# Bad request - could be invalid format
|
|
105
|
+
try:
|
|
106
|
+
error_data = response.json()
|
|
107
|
+
error_msg = error_data.get("error", "Bad request")
|
|
108
|
+
return f"Bad request: {error_msg}"
|
|
109
|
+
except:
|
|
110
|
+
return "Bad request: check client ID and secret format"
|
|
111
|
+
elif response.status_code == 404:
|
|
112
|
+
return "Xray API endpoint not found: verify the Xray URL"
|
|
113
|
+
else:
|
|
114
|
+
return f"Xray API returned status code {response.status_code}"
|
|
115
|
+
|
|
116
|
+
except requests.exceptions.SSLError as e:
|
|
117
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
118
|
+
except requests.exceptions.ConnectionError:
|
|
119
|
+
return f"Cannot connect to Xray at {base_url}: connection refused"
|
|
120
|
+
except requests.exceptions.Timeout:
|
|
121
|
+
return f"Connection to Xray at {base_url} timed out"
|
|
122
|
+
except requests.exceptions.RequestException as e:
|
|
123
|
+
return f"Error connecting to Xray: {str(e)}"
|
|
124
|
+
except Exception as e:
|
|
125
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -18,3 +18,96 @@ class ZephyrEnterpriseConfiguration(BaseModel):
|
|
|
18
18
|
)
|
|
19
19
|
base_url: str = Field(description="Zephyr base URL")
|
|
20
20
|
token: Optional[SecretStr] = Field(description="API token")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def check_connection(settings: dict) -> str | None:
|
|
24
|
+
"""
|
|
25
|
+
Check the connection to Zephyr Enterprise.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
settings: Dictionary containing Zephyr Enterprise configuration
|
|
29
|
+
- base_url: Zephyr Enterprise instance URL (required)
|
|
30
|
+
- token: API token for authentication (optional, anonymous access possible)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
None if connection successful, error message string if failed
|
|
34
|
+
"""
|
|
35
|
+
import requests
|
|
36
|
+
|
|
37
|
+
# Validate base_url
|
|
38
|
+
base_url = settings.get("base_url", "").strip()
|
|
39
|
+
if not base_url:
|
|
40
|
+
return "Zephyr Enterprise URL is required"
|
|
41
|
+
|
|
42
|
+
# Normalize URL - remove trailing slashes
|
|
43
|
+
base_url = base_url.rstrip("/")
|
|
44
|
+
|
|
45
|
+
# Basic URL validation
|
|
46
|
+
if not base_url.startswith(("http://", "https://")):
|
|
47
|
+
return "Zephyr Enterprise URL must start with http:// or https://"
|
|
48
|
+
|
|
49
|
+
# Get token (optional)
|
|
50
|
+
token = settings.get("token")
|
|
51
|
+
|
|
52
|
+
# Prepare headers
|
|
53
|
+
headers = {}
|
|
54
|
+
has_token = False
|
|
55
|
+
if token:
|
|
56
|
+
# Extract token value if it's a SecretStr
|
|
57
|
+
token_value = token.get_secret_value() if hasattr(token, 'get_secret_value') else token
|
|
58
|
+
if token_value and str(token_value).strip():
|
|
59
|
+
headers["Authorization"] = f"Bearer {str(token_value).strip()}"
|
|
60
|
+
has_token = True
|
|
61
|
+
|
|
62
|
+
# Use different endpoints based on whether authentication is provided
|
|
63
|
+
# Note: /healthcheck may allow anonymous access, so we use authenticated endpoints when token is provided
|
|
64
|
+
if has_token:
|
|
65
|
+
# Test with an endpoint that requires authentication: /flex/services/rest/latest/project
|
|
66
|
+
# This endpoint lists projects and requires proper authentication
|
|
67
|
+
test_url = f"{base_url}/flex/services/rest/latest/user/current"
|
|
68
|
+
else:
|
|
69
|
+
# Without token, test basic connectivity with healthcheck
|
|
70
|
+
test_url = f"{base_url}/flex/services/rest/latest/healthcheck"
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
response = requests.get(
|
|
74
|
+
test_url,
|
|
75
|
+
headers=headers,
|
|
76
|
+
timeout=10
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Check response status
|
|
80
|
+
if response.status_code == 200:
|
|
81
|
+
# Successfully connected
|
|
82
|
+
return None
|
|
83
|
+
elif response.status_code == 401:
|
|
84
|
+
if has_token:
|
|
85
|
+
return "Authentication failed: invalid API token"
|
|
86
|
+
else:
|
|
87
|
+
return "Authentication required: provide API token"
|
|
88
|
+
elif response.status_code == 403:
|
|
89
|
+
return "Access forbidden: check token permissions"
|
|
90
|
+
elif response.status_code == 404:
|
|
91
|
+
# If user endpoint not found, try healthcheck as fallback
|
|
92
|
+
if has_token:
|
|
93
|
+
try:
|
|
94
|
+
fallback_url = f"{base_url}/flex/services/rest/latest/healthcheck"
|
|
95
|
+
fallback_response = requests.get(fallback_url, headers=headers, timeout=10)
|
|
96
|
+
if fallback_response.status_code == 200:
|
|
97
|
+
return None
|
|
98
|
+
except:
|
|
99
|
+
pass
|
|
100
|
+
return "Zephyr Enterprise API endpoint not found: verify the Zephyr URL"
|
|
101
|
+
else:
|
|
102
|
+
return f"Zephyr Enterprise API returned status code {response.status_code}"
|
|
103
|
+
|
|
104
|
+
except requests.exceptions.SSLError as e:
|
|
105
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
106
|
+
except requests.exceptions.ConnectionError:
|
|
107
|
+
return f"Cannot connect to Zephyr Enterprise at {base_url}: connection refused"
|
|
108
|
+
except requests.exceptions.Timeout:
|
|
109
|
+
return f"Connection to Zephyr Enterprise at {base_url} timed out"
|
|
110
|
+
except requests.exceptions.RequestException as e:
|
|
111
|
+
return f"Error connecting to Zephyr Enterprise: {str(e)}"
|
|
112
|
+
except Exception as e:
|
|
113
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -18,3 +18,78 @@ class ZephyrEssentialConfiguration(BaseModel):
|
|
|
18
18
|
)
|
|
19
19
|
base_url: Optional[str] = Field(description="Zephyr Essential API Base URL", default=None)
|
|
20
20
|
token: SecretStr = Field(description="Zephyr Essential API Token")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def check_connection(settings: dict) -> str | None:
|
|
24
|
+
"""
|
|
25
|
+
Check the connection to Zephyr Essential (Zephyr Scale).
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
settings: Dictionary containing Zephyr Essential configuration
|
|
29
|
+
- base_url: Zephyr Essential API Base URL (optional, defaults to Zephyr Scale Cloud API)
|
|
30
|
+
- token: Zephyr Essential API Token (required)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
None if connection successful, error message string if failed
|
|
34
|
+
"""
|
|
35
|
+
import requests
|
|
36
|
+
|
|
37
|
+
# Get base_url or use default
|
|
38
|
+
base_url = settings.get("base_url")
|
|
39
|
+
if base_url:
|
|
40
|
+
base_url = base_url.strip().rstrip("/")
|
|
41
|
+
# Validate URL format if provided
|
|
42
|
+
if not base_url.startswith(("http://", "https://")):
|
|
43
|
+
return "Zephyr Essential URL must start with http:// or https://"
|
|
44
|
+
else:
|
|
45
|
+
# Default to Zephyr Scale Cloud API
|
|
46
|
+
base_url = "https://api.zephyrscale.smartbear.com/v2"
|
|
47
|
+
|
|
48
|
+
# Validate token
|
|
49
|
+
token = settings.get("token")
|
|
50
|
+
if not token:
|
|
51
|
+
return "Zephyr Essential API token is required"
|
|
52
|
+
|
|
53
|
+
# Extract token value if it's a SecretStr
|
|
54
|
+
token_value = token.get_secret_value() if hasattr(token, 'get_secret_value') else token
|
|
55
|
+
|
|
56
|
+
if not token_value or not str(token_value).strip():
|
|
57
|
+
return "Zephyr Essential API token cannot be empty"
|
|
58
|
+
|
|
59
|
+
# Test connection using /projects endpoint (requires authentication)
|
|
60
|
+
test_url = f"{base_url}/projects"
|
|
61
|
+
|
|
62
|
+
headers = {
|
|
63
|
+
"Authorization": f"Bearer {str(token_value).strip()}"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
response = requests.get(
|
|
68
|
+
test_url,
|
|
69
|
+
headers=headers,
|
|
70
|
+
timeout=10
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Check response status
|
|
74
|
+
if response.status_code == 200:
|
|
75
|
+
# Successfully connected and authenticated
|
|
76
|
+
return None
|
|
77
|
+
elif response.status_code == 401:
|
|
78
|
+
return "Authentication failed: invalid API token"
|
|
79
|
+
elif response.status_code == 403:
|
|
80
|
+
return "Access forbidden: token lacks required permissions"
|
|
81
|
+
elif response.status_code == 404:
|
|
82
|
+
return "Zephyr Essential API endpoint not found: verify the API URL"
|
|
83
|
+
else:
|
|
84
|
+
return f"Zephyr Essential API returned status code {response.status_code}"
|
|
85
|
+
|
|
86
|
+
except requests.exceptions.SSLError as e:
|
|
87
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
88
|
+
except requests.exceptions.ConnectionError:
|
|
89
|
+
return f"Cannot connect to Zephyr Essential at {base_url}: connection refused"
|
|
90
|
+
except requests.exceptions.Timeout:
|
|
91
|
+
return f"Connection to Zephyr Essential at {base_url} timed out"
|
|
92
|
+
except requests.exceptions.RequestException as e:
|
|
93
|
+
return f"Error connecting to Zephyr Essential: {str(e)}"
|
|
94
|
+
except Exception as e:
|
|
95
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -467,10 +467,11 @@ def create_graph(
|
|
|
467
467
|
input_params = node.get('input', ['messages'])
|
|
468
468
|
input_mapping = node.get('input_mapping',
|
|
469
469
|
{'messages': {'type': 'variable', 'value': 'messages'}})
|
|
470
|
+
output_vars = node.get('output', [])
|
|
470
471
|
lg_builder.add_node(node_id, FunctionTool(
|
|
471
472
|
client=client, tool=tool,
|
|
472
473
|
name=node_id, return_type='str',
|
|
473
|
-
output_variables=
|
|
474
|
+
output_variables=output_vars + ['messages'] if 'messages' not in output_vars else output_vars,
|
|
474
475
|
input_variables=input_params,
|
|
475
476
|
input_mapping= input_mapping
|
|
476
477
|
))
|
|
@@ -500,15 +501,6 @@ def create_graph(
|
|
|
500
501
|
structured_output=node.get('structured_output', False),
|
|
501
502
|
task=node.get('task')
|
|
502
503
|
))
|
|
503
|
-
# TODO: decide on struct output for agent nodes
|
|
504
|
-
# elif node_type == 'agent':
|
|
505
|
-
# lg_builder.add_node(node_id, AgentNode(
|
|
506
|
-
# client=client, tool=tool,
|
|
507
|
-
# name=node['id'], return_type='dict',
|
|
508
|
-
# output_variables=node.get('output', []),
|
|
509
|
-
# input_variables=node.get('input', ['messages']),
|
|
510
|
-
# task=node.get('task')
|
|
511
|
-
# ))
|
|
512
504
|
elif node_type == 'loop':
|
|
513
505
|
lg_builder.add_node(node_id, LoopNode(
|
|
514
506
|
client=client, tool=tool,
|
|
@@ -5,8 +5,9 @@ import re
|
|
|
5
5
|
from pydantic import create_model, Field
|
|
6
6
|
from typing import Tuple, TypedDict, Any, Optional, Annotated
|
|
7
7
|
from langchain_core.messages import AnyMessage
|
|
8
|
-
from
|
|
9
|
-
|
|
8
|
+
from langgraph.graph import add_messages
|
|
9
|
+
|
|
10
|
+
from ...runtime.langchain.constants import ELITEA_RS
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
@@ -130,7 +131,7 @@ def parse_type(type_str):
|
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
def create_state(data: Optional[dict] = None):
|
|
133
|
-
state_dict = {'input': str, 'router_output': str} # Always include router_output
|
|
134
|
+
state_dict = {'input': str, 'router_output': str, ELITEA_RS: str} # Always include router_output
|
|
134
135
|
types_dict = {}
|
|
135
136
|
if not data:
|
|
136
137
|
data = {'messages': 'list[str]'}
|
|
@@ -120,8 +120,8 @@ class FunctionTool(BaseTool):
|
|
|
120
120
|
return {
|
|
121
121
|
"messages": [{
|
|
122
122
|
"role": "assistant",
|
|
123
|
-
"content": dumps(tool_result) if not isinstance(tool_result, ToolException)
|
|
124
|
-
|
|
123
|
+
"content": dumps(tool_result) if not isinstance(tool_result, ToolException)
|
|
124
|
+
else str(tool_result)
|
|
125
125
|
}]
|
|
126
126
|
}
|
|
127
127
|
else:
|
alita_sdk/runtime/tools/llm.py
CHANGED
|
@@ -7,6 +7,7 @@ from langchain_core.runnables import RunnableConfig
|
|
|
7
7
|
from langchain_core.tools import BaseTool, ToolException
|
|
8
8
|
from pydantic import Field
|
|
9
9
|
|
|
10
|
+
from ..langchain.constants import ELITEA_RS
|
|
10
11
|
from ..langchain.utils import create_pydantic_model, propagate_the_input_mapping
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
@@ -122,6 +123,8 @@ class LLMNode(BaseTool):
|
|
|
122
123
|
}
|
|
123
124
|
for key, value in (self.structured_output_dict or {}).items()
|
|
124
125
|
}
|
|
126
|
+
# Add default output field for proper response to user
|
|
127
|
+
struct_params['elitea_response'] = {'description': 'final output to user', 'type': 'str'}
|
|
125
128
|
struct_model = create_pydantic_model(f"LLMOutput", struct_params)
|
|
126
129
|
completion = llm_client.invoke(messages, config=config)
|
|
127
130
|
if hasattr(completion, 'tool_calls') and completion.tool_calls:
|
|
@@ -137,6 +140,8 @@ class LLMNode(BaseTool):
|
|
|
137
140
|
# Ensure messages are properly formatted
|
|
138
141
|
if result.get('messages') and isinstance(result['messages'], list):
|
|
139
142
|
result['messages'] = [{'role': 'assistant', 'content': '\n'.join(result['messages'])}]
|
|
143
|
+
else:
|
|
144
|
+
result['messages'] = messages + [AIMessage(content=result.get(ELITEA_RS, ''))]
|
|
140
145
|
|
|
141
146
|
return result
|
|
142
147
|
else:
|
|
@@ -8,6 +8,7 @@ from office365.runtime.auth.client_credential import ClientCredential
|
|
|
8
8
|
from office365.sharepoint.client_context import ClientContext
|
|
9
9
|
from pydantic import Field, PrivateAttr, create_model, model_validator, SecretStr
|
|
10
10
|
|
|
11
|
+
from .utils import decode_sharepoint_string
|
|
11
12
|
from ..non_code_indexer_toolkit import NonCodeIndexerToolkit
|
|
12
13
|
from ..utils.content_parser import parse_file_content
|
|
13
14
|
from ...runtime.utils.utils import IndexerKeywords
|
|
@@ -105,26 +106,34 @@ class SharepointApiWrapper(NonCodeIndexerToolkit):
|
|
|
105
106
|
def get_files_list(self, folder_name: str = None, limit_files: int = 100):
|
|
106
107
|
""" If folder name is specified, lists all files in this folder under Shared Documents path. If folder name is empty, lists all files under root catalog (Shared Documents). Number of files is limited by limit_files (default is 100)."""
|
|
107
108
|
try:
|
|
109
|
+
# exclude default system libraries like 'Form Templates', 'Site Assets', 'Style Library'
|
|
110
|
+
all_libraries = self._client.web.lists.filter("BaseTemplate eq 101 and Title ne 'Form Templates' and Title ne 'Site Assets' and Title ne 'Style Library'").get().execute_query()
|
|
108
111
|
result = []
|
|
109
112
|
if not limit_files:
|
|
110
113
|
limit_files = 100
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
#
|
|
115
|
+
for lib in all_libraries:
|
|
116
|
+
library_type = decode_sharepoint_string(lib.properties["EntityTypeName"])
|
|
117
|
+
target_folder_url = f"{library_type}/{folder_name}" if folder_name else library_type
|
|
118
|
+
files = (self._client.web.get_folder_by_server_relative_path(target_folder_url)
|
|
119
|
+
.get_files(True)
|
|
120
|
+
.execute_query())
|
|
121
|
+
#
|
|
122
|
+
for file in files:
|
|
123
|
+
if f"{library_type}/Forms" in file.properties['ServerRelativeUrl']:
|
|
124
|
+
# skip files from system folder "Forms"
|
|
125
|
+
continue
|
|
126
|
+
if len(result) >= limit_files:
|
|
127
|
+
break
|
|
128
|
+
temp_props = {
|
|
129
|
+
'Name': file.properties['Name'],
|
|
130
|
+
'Path': file.properties['ServerRelativeUrl'],
|
|
131
|
+
'Created': file.properties['TimeCreated'],
|
|
132
|
+
'Modified': file.properties['TimeLastModified'],
|
|
133
|
+
'Link': file.properties['LinkingUrl'],
|
|
134
|
+
'id': file.properties['UniqueId']
|
|
135
|
+
}
|
|
136
|
+
result.append(temp_props)
|
|
128
137
|
return result if result else ToolException("Can not get files or folder is empty. Please, double check folder name and read permissions.")
|
|
129
138
|
except Exception as e:
|
|
130
139
|
# attempt to get via graph api
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import re
|
|
2
2
|
from io import BytesIO
|
|
3
|
+
from docx import Document
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
def read_docx_from_bytes(file_content):
|
|
5
7
|
"""Read and return content from a .docx file using a byte stream."""
|
|
@@ -11,4 +13,8 @@ def read_docx_from_bytes(file_content):
|
|
|
11
13
|
return '\n'.join(text)
|
|
12
14
|
except Exception as e:
|
|
13
15
|
print(f"Error reading .docx from bytes: {e}")
|
|
14
|
-
return ""
|
|
16
|
+
return ""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def decode_sharepoint_string(s):
|
|
20
|
+
return re.sub(r'_x([0-9A-Fa-f]{4})_', lambda m: chr(int(m.group(1), 16)), s)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alita_sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.413
|
|
4
4
|
Summary: SDK for building langchain agents using resources from Alita
|
|
5
5
|
Author-email: Artem Rozumenko <artyom.rozumenko@gmail.com>, Mikalai Biazruchka <mikalai_biazruchka@epam.com>, Roman Mitusov <roman_mitusov@epam.com>, Ivan Krakhmaliuk <lifedj27@gmail.com>, Artem Dubrovskiy <ad13box@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -5,17 +5,17 @@ alita_sdk/configurations/__init__.py,sha256=3_MlzyzAi1dog6MFQD_ICOZvaPfXf2UfhSZw
|
|
|
5
5
|
alita_sdk/configurations/ado.py,sha256=Djw1_8QmssETVpjpk2LeFN--Wg5-D1_L1NdwH8dtbGM,1274
|
|
6
6
|
alita_sdk/configurations/azure_search.py,sha256=WDBc38uSmRBA_ypezb04wob2qUMqcw275FI39SWvrPo,808
|
|
7
7
|
alita_sdk/configurations/bigquery.py,sha256=kuJ9AUAHynnyM4wkaQbiW3x5VmrJkjR0RGmsyAec194,917
|
|
8
|
-
alita_sdk/configurations/bitbucket.py,sha256=
|
|
8
|
+
alita_sdk/configurations/bitbucket.py,sha256=I7LrgLxI28F9Dge5h5gdf9Fp6rdHdl7daocVaZVebfk,5249
|
|
9
9
|
alita_sdk/configurations/browser.py,sha256=2ZMUrqk8M9oZi3HX4ouGp9b-qdYgpGN-hVin--zgzpc,670
|
|
10
10
|
alita_sdk/configurations/carrier.py,sha256=QPgXyNXki8ECQAwBB1I64vsltV5patZUnZrd5m8obyY,693
|
|
11
|
-
alita_sdk/configurations/confluence.py,sha256=
|
|
11
|
+
alita_sdk/configurations/confluence.py,sha256=bGGw8R_JtcH1m3NCIuE-W52GDQXELgB9JoxrNmjj974,5469
|
|
12
12
|
alita_sdk/configurations/delta_lake.py,sha256=sCNDClaO6-b1Qv5WDxOMHjQUrHnr6S6EPQuS9o6q8Z8,1130
|
|
13
13
|
alita_sdk/configurations/embedding.py,sha256=8GSC8Feh8CH7bT_6cQhNqlS6raE91S2YRAtb2N9bUA8,552
|
|
14
14
|
alita_sdk/configurations/figma.py,sha256=91P-R5hqTwpXQvuAcUMXztzqvSvmtPkguR88Zw830Og,1025
|
|
15
15
|
alita_sdk/configurations/github.py,sha256=6F7P9BPu9zE54q8m8IPjIct8cEX3WODYzZvn9WtxOXI,6283
|
|
16
|
-
alita_sdk/configurations/gitlab.py,sha256=
|
|
16
|
+
alita_sdk/configurations/gitlab.py,sha256=ZDe3uGYp7Or01YxIPAHUfvNQNw5jbQHpHakx3XJRO4M,4177
|
|
17
17
|
alita_sdk/configurations/google_places.py,sha256=jXhlPXywkDhHNrgB4KoVWogf3CVi1bY3wFLRzDqMr-E,589
|
|
18
|
-
alita_sdk/configurations/jira.py,sha256=
|
|
18
|
+
alita_sdk/configurations/jira.py,sha256=isZPChfvzVNaoWLcVq4jWVDUcDGszieWdLg8gqxAU84,5914
|
|
19
19
|
alita_sdk/configurations/pgvector.py,sha256=P-Q07ocIg4CXN_7hUBDM6r9gN62XS1N2jyP79tM9Tig,500
|
|
20
20
|
alita_sdk/configurations/postman.py,sha256=A4swgV_0eEJ3LXYWzUUIDD5zFu9rS_vndqp1xd4YHnM,1114
|
|
21
21
|
alita_sdk/configurations/qtest.py,sha256=i-09LUFDqcBKmzSiKd8C3NEKcRDAH6wwKsajC_TbBnM,642
|
|
@@ -28,11 +28,11 @@ alita_sdk/configurations/slack.py,sha256=ppwfV7YMpkq-qU6YREK7EH8VmYBZ0EN_9WIwz3E
|
|
|
28
28
|
alita_sdk/configurations/sonar.py,sha256=b5Mh-s8C6Wts0KF0VX6AN8KmdXwVSu1UXJOf2AVGQbs,676
|
|
29
29
|
alita_sdk/configurations/sql.py,sha256=0n2q2Tmc19_xUol6EN1LO6H1tZgJuxm7PvK3bFklGnM,754
|
|
30
30
|
alita_sdk/configurations/testio.py,sha256=yoGWTuwcjFoBxbsxHLGVbcQBBZAFDQwuVcTijyUrW_U,612
|
|
31
|
-
alita_sdk/configurations/testrail.py,sha256=
|
|
32
|
-
alita_sdk/configurations/xray.py,sha256=
|
|
31
|
+
alita_sdk/configurations/testrail.py,sha256=URm7nT3inwdwnD-_BVMlL5QSIPbKwtZGfxoeaXyyAm8,4355
|
|
32
|
+
alita_sdk/configurations/xray.py,sha256=mWic9HMJlYaxMWu0Hs6wpBvRAcJTUnrXMJjAgSzSinM,4968
|
|
33
33
|
alita_sdk/configurations/zephyr.py,sha256=ndqGYFy5OFxjoXB7DzC71rd5W6qGBGAlKMWoqT8TuNk,1653
|
|
34
|
-
alita_sdk/configurations/zephyr_enterprise.py,sha256=
|
|
35
|
-
alita_sdk/configurations/zephyr_essential.py,sha256=
|
|
34
|
+
alita_sdk/configurations/zephyr_enterprise.py,sha256=x2ZNvR2tYJySzEmBY3fovUe7B-_QawCMZPWl8GzmfkU,4830
|
|
35
|
+
alita_sdk/configurations/zephyr_essential.py,sha256=TiZedsBlfIDroflipvoqxjJeEWPonQzeT7e1bYns98s,3869
|
|
36
36
|
alita_sdk/runtime/__init__.py,sha256=4W0UF-nl3QF2bvET5lnah4o24CoTwSoKXhuN0YnwvEE,828
|
|
37
37
|
alita_sdk/runtime/clients/__init__.py,sha256=BdehU5GBztN1Qi1Wul0cqlU46FxUfMnI6Vq2Zd_oq1M,296
|
|
38
38
|
alita_sdk/runtime/clients/artifact.py,sha256=b7hVuGRROt6qUcT11uAZqzJqslzmlgW-Y6oGsiwNmjI,4029
|
|
@@ -43,13 +43,13 @@ alita_sdk/runtime/clients/sandbox_client.py,sha256=OhEasE0MxBBDw4o76xkxVCpNpr3xJ
|
|
|
43
43
|
alita_sdk/runtime/langchain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
44
|
alita_sdk/runtime/langchain/assistant.py,sha256=qKoEjbGuUnX-OZDHmSaK3plb1jON9unzEwAjxBT9DY8,16044
|
|
45
45
|
alita_sdk/runtime/langchain/chat_message_template.py,sha256=kPz8W2BG6IMyITFDA5oeb5BxVRkHEVZhuiGl4MBZKdc,2176
|
|
46
|
-
alita_sdk/runtime/langchain/constants.py,sha256=
|
|
46
|
+
alita_sdk/runtime/langchain/constants.py,sha256=DhxYZgIVcyP7V9MnHQy5kD0N4Do4YxntnQbH1Pmw4Dg,3349
|
|
47
47
|
alita_sdk/runtime/langchain/indexer.py,sha256=0ENHy5EOhThnAiYFc7QAsaTNp9rr8hDV_hTK8ahbatk,37592
|
|
48
|
-
alita_sdk/runtime/langchain/langraph_agent.py,sha256=
|
|
48
|
+
alita_sdk/runtime/langchain/langraph_agent.py,sha256=9ezh0iDTQwyE4PBH96k9g31Mz2Y1Rap1iw3mj7laQBQ,49731
|
|
49
49
|
alita_sdk/runtime/langchain/mixedAgentParser.py,sha256=M256lvtsL3YtYflBCEp-rWKrKtcY1dJIyRGVv7KW9ME,2611
|
|
50
50
|
alita_sdk/runtime/langchain/mixedAgentRenderes.py,sha256=asBtKqm88QhZRILditjYICwFVKF5KfO38hu2O-WrSWE,5964
|
|
51
51
|
alita_sdk/runtime/langchain/store_manager.py,sha256=i8Fl11IXJhrBXq1F1ukEVln57B1IBe-tqSUvfUmBV4A,2218
|
|
52
|
-
alita_sdk/runtime/langchain/utils.py,sha256=
|
|
52
|
+
alita_sdk/runtime/langchain/utils.py,sha256=GxqG1xhdUd6BC01ZZFkdc_0Q27LNgc-mr6t_1LD7GRU,7579
|
|
53
53
|
alita_sdk/runtime/langchain/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
54
|
alita_sdk/runtime/langchain/agents/xml_chat.py,sha256=Mx7PK5T97_GrFCwHHZ3JZP42S7MwtUzV0W-_8j6Amt8,6212
|
|
55
55
|
alita_sdk/runtime/langchain/document_loaders/AlitaBDDScenariosLoader.py,sha256=4kFU1ijrM1Jw7cywQv8mUiBHlE6w-uqfzSZP4hUV5P4,3771
|
|
@@ -110,11 +110,11 @@ alita_sdk/runtime/tools/application.py,sha256=RCGe-mRfj8372gTFkEX2xBvcYhw7IKdU1t
|
|
|
110
110
|
alita_sdk/runtime/tools/artifact.py,sha256=u3szFwZqguHrPZ3tZJ7S_TiZl7cxlT3oHYd6zbdpRDE,13842
|
|
111
111
|
alita_sdk/runtime/tools/datasource.py,sha256=pvbaSfI-ThQQnjHG-QhYNSTYRnZB0rYtZFpjCfpzxYI,2443
|
|
112
112
|
alita_sdk/runtime/tools/echo.py,sha256=spw9eCweXzixJqHnZofHE1yWiSUa04L4VKycf3KCEaM,486
|
|
113
|
-
alita_sdk/runtime/tools/function.py,sha256=
|
|
113
|
+
alita_sdk/runtime/tools/function.py,sha256=SjqNdw4x02jiqc7rA5ERp69TDUIChZM-VNmpu31ZajA,6139
|
|
114
114
|
alita_sdk/runtime/tools/graph.py,sha256=7jImBBSEdP5Mjnn2keOiyUwdGDFhEXLUrgUiugO3mgA,3503
|
|
115
115
|
alita_sdk/runtime/tools/image_generation.py,sha256=Kls9D_ke_SK7xmVr7I9SlQcAEBJc86gf66haN0qIj9k,7469
|
|
116
116
|
alita_sdk/runtime/tools/indexer_tool.py,sha256=whSLPevB4WD6dhh2JDXEivDmTvbjiMV1MrPl9cz5eLA,4375
|
|
117
|
-
alita_sdk/runtime/tools/llm.py,sha256=
|
|
117
|
+
alita_sdk/runtime/tools/llm.py,sha256=Oq0IH1mVEgtkw-8p5y-_xTCz-OcWtfvtR5xl19Anrhc,15875
|
|
118
118
|
alita_sdk/runtime/tools/loop.py,sha256=uds0WhZvwMxDVFI6MZHrcmMle637cQfBNg682iLxoJA,8335
|
|
119
119
|
alita_sdk/runtime/tools/loop_output.py,sha256=U4hO9PCQgWlXwOq6jdmCGbegtAxGAPXObSxZQ3z38uk,8069
|
|
120
120
|
alita_sdk/runtime/tools/mcp_server_tool.py,sha256=MhLxZJ44LYrB_0GrojmkyqKoDRaqIHkEQAsg718ipog,4277
|
|
@@ -317,9 +317,9 @@ alita_sdk/tools/servicenow/__init__.py,sha256=ziEt2juPrGFyB98ZXbGf25v6gZo4UJTHsz
|
|
|
317
317
|
alita_sdk/tools/servicenow/api_wrapper.py,sha256=WpH-bBLGFdhehs4g-K-WAkNuaD1CSrwsDpdgB3RG53s,6120
|
|
318
318
|
alita_sdk/tools/servicenow/servicenow_client.py,sha256=Rdqfu-ll-qbnclMzChLZBsfXRDzgoX_FdeI2WLApWxc,3269
|
|
319
319
|
alita_sdk/tools/sharepoint/__init__.py,sha256=5z2iSmm-0kbHKf70wN6OOgS4Px7tOzwkIpHXz0Vrbj4,4045
|
|
320
|
-
alita_sdk/tools/sharepoint/api_wrapper.py,sha256=
|
|
320
|
+
alita_sdk/tools/sharepoint/api_wrapper.py,sha256=QcOtgc8L10YLdSK8LwACV_vRBF7T-u7T9-W05TbuAQI,15409
|
|
321
321
|
alita_sdk/tools/sharepoint/authorization_helper.py,sha256=QvxWFBjYZfhI1h_KkSrDbRh8D5BlFX8xWDLmlIoO4mo,9569
|
|
322
|
-
alita_sdk/tools/sharepoint/utils.py,sha256=
|
|
322
|
+
alita_sdk/tools/sharepoint/utils.py,sha256=CO1PNRC5CpQaVo2Gdenj_jqm2bReSqUT92Bk5s37d8M,573
|
|
323
323
|
alita_sdk/tools/slack/__init__.py,sha256=YiPAoRc6y6uVpfHl0K1Qi-flcyPlWFIMVcVbhicGWXY,3990
|
|
324
324
|
alita_sdk/tools/slack/api_wrapper.py,sha256=5VrV7iSGno8ZcDzEHdGPNhInhtODGPPvAzoZ9W9iQWE,14009
|
|
325
325
|
alita_sdk/tools/sql/__init__.py,sha256=F3eewPEKqVh3gZdNTUvcoFPOgG9Mn11qKoadtCmhQ4Q,3484
|
|
@@ -353,8 +353,8 @@ alita_sdk/tools/zephyr_scale/api_wrapper.py,sha256=kT0TbmMvuKhDUZc0i7KO18O38JM9S
|
|
|
353
353
|
alita_sdk/tools/zephyr_squad/__init__.py,sha256=0ne8XLJEQSLOWfzd2HdnqOYmQlUliKHbBED5kW_Vias,2895
|
|
354
354
|
alita_sdk/tools/zephyr_squad/api_wrapper.py,sha256=kmw_xol8YIYFplBLWTqP_VKPRhL_1ItDD0_vXTe_UuI,14906
|
|
355
355
|
alita_sdk/tools/zephyr_squad/zephyr_squad_cloud_client.py,sha256=R371waHsms4sllHCbijKYs90C-9Yu0sSR3N4SUfQOgU,5066
|
|
356
|
-
alita_sdk-0.3.
|
|
357
|
-
alita_sdk-0.3.
|
|
358
|
-
alita_sdk-0.3.
|
|
359
|
-
alita_sdk-0.3.
|
|
360
|
-
alita_sdk-0.3.
|
|
356
|
+
alita_sdk-0.3.413.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
357
|
+
alita_sdk-0.3.413.dist-info/METADATA,sha256=8HeeW3AsixJWjgBQKoTQOwBtaQ9LO3w3h_plko-DG3E,19071
|
|
358
|
+
alita_sdk-0.3.413.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
359
|
+
alita_sdk-0.3.413.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
|
|
360
|
+
alita_sdk-0.3.413.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|