alita-sdk 0.3.532__py3-none-any.whl → 0.3.602__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.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/cli/agent_executor.py +2 -1
- alita_sdk/cli/agent_loader.py +34 -4
- alita_sdk/cli/agents.py +433 -203
- alita_sdk/community/__init__.py +8 -4
- alita_sdk/configurations/__init__.py +1 -0
- alita_sdk/configurations/openapi.py +323 -0
- alita_sdk/runtime/clients/client.py +165 -7
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +61 -11
- alita_sdk/runtime/langchain/constants.py +419 -171
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -2
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/langraph_agent.py +108 -23
- alita_sdk/runtime/langchain/utils.py +76 -14
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +5 -0
- alita_sdk/runtime/toolkits/artifact.py +2 -1
- alita_sdk/runtime/toolkits/mcp.py +6 -3
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/tools.py +139 -10
- alita_sdk/runtime/toolkits/vectorstore.py +1 -1
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/artifact.py +15 -0
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/llm.py +260 -73
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_server_tool.py +6 -3
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +9 -6
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +7 -2
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +1 -1
- alita_sdk/runtime/utils/mcp_sse_client.py +1 -1
- alita_sdk/runtime/utils/toolkit_utils.py +2 -0
- alita_sdk/tools/__init__.py +44 -2
- alita_sdk/tools/ado/repos/__init__.py +26 -8
- alita_sdk/tools/ado/repos/repos_wrapper.py +78 -52
- alita_sdk/tools/ado/test_plan/__init__.py +3 -2
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +2 -1
- alita_sdk/tools/ado/wiki/ado_wrapper.py +23 -1
- alita_sdk/tools/ado/work_item/__init__.py +3 -2
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- alita_sdk/tools/advanced_jira_mining/__init__.py +2 -1
- alita_sdk/tools/aws/delta_lake/__init__.py +2 -1
- alita_sdk/tools/azure_ai/search/__init__.py +2 -1
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base_indexer_toolkit.py +51 -30
- alita_sdk/tools/bitbucket/__init__.py +2 -1
- alita_sdk/tools/bitbucket/api_wrapper.py +1 -1
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +3 -3
- alita_sdk/tools/browser/__init__.py +1 -1
- alita_sdk/tools/carrier/__init__.py +1 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/cloud/aws/__init__.py +2 -1
- alita_sdk/tools/cloud/azure/__init__.py +2 -1
- alita_sdk/tools/cloud/gcp/__init__.py +2 -1
- alita_sdk/tools/cloud/k8s/__init__.py +2 -1
- alita_sdk/tools/code/linter/__init__.py +2 -1
- alita_sdk/tools/code/sonar/__init__.py +2 -1
- alita_sdk/tools/code_indexer_toolkit.py +19 -2
- alita_sdk/tools/confluence/__init__.py +7 -6
- alita_sdk/tools/confluence/api_wrapper.py +7 -8
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/custom_open_api/__init__.py +2 -1
- alita_sdk/tools/elastic/__init__.py +2 -1
- alita_sdk/tools/elitea_base.py +28 -9
- alita_sdk/tools/figma/__init__.py +52 -6
- alita_sdk/tools/figma/api_wrapper.py +1158 -123
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +2 -1
- alita_sdk/tools/github/github_client.py +56 -92
- alita_sdk/tools/github/schemas.py +4 -4
- alita_sdk/tools/gitlab/__init__.py +2 -1
- alita_sdk/tools/gitlab/api_wrapper.py +118 -38
- alita_sdk/tools/gitlab_org/__init__.py +2 -1
- alita_sdk/tools/gitlab_org/api_wrapper.py +60 -62
- alita_sdk/tools/google/bigquery/__init__.py +2 -1
- alita_sdk/tools/google_places/__init__.py +2 -1
- alita_sdk/tools/jira/__init__.py +2 -1
- alita_sdk/tools/keycloak/__init__.py +2 -1
- alita_sdk/tools/localgit/__init__.py +2 -1
- alita_sdk/tools/memory/__init__.py +1 -1
- alita_sdk/tools/ocr/__init__.py +2 -1
- alita_sdk/tools/openapi/__init__.py +490 -118
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +11 -5
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +2 -1
- alita_sdk/tools/pptx/__init__.py +2 -1
- alita_sdk/tools/qtest/__init__.py +21 -2
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +2 -1
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +2 -1
- alita_sdk/tools/salesforce/__init__.py +2 -1
- alita_sdk/tools/servicenow/__init__.py +11 -10
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +2 -1
- alita_sdk/tools/sharepoint/api_wrapper.py +2 -2
- alita_sdk/tools/slack/__init__.py +3 -2
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +3 -2
- alita_sdk/tools/testio/__init__.py +2 -1
- alita_sdk/tools/testrail/__init__.py +2 -1
- alita_sdk/tools/utils/content_parser.py +77 -3
- alita_sdk/tools/utils/text_operations.py +163 -71
- alita_sdk/tools/xray/__init__.py +3 -2
- alita_sdk/tools/yagmail/__init__.py +2 -1
- alita_sdk/tools/zephyr/__init__.py +2 -1
- alita_sdk/tools/zephyr_enterprise/__init__.py +2 -1
- alita_sdk/tools/zephyr_essential/__init__.py +2 -1
- alita_sdk/tools/zephyr_scale/__init__.py +3 -2
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +2 -1
- {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/METADATA +7 -6
- {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/RECORD +137 -119
- {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/entry_points.txt +0 -0
- {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/top_level.txt +0 -0
|
@@ -10,6 +10,7 @@ from ..elitea_base import filter_missconfigured_index_tools
|
|
|
10
10
|
from ..utils import clean_string, get_max_toolkit_length
|
|
11
11
|
from ...configurations.github import GithubConfiguration
|
|
12
12
|
from ...configurations.pgvector import PgVectorConfiguration
|
|
13
|
+
from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
|
|
13
14
|
|
|
14
15
|
name = "github"
|
|
15
16
|
|
|
@@ -96,7 +97,7 @@ class AlitaGitHubToolkit(BaseToolkit):
|
|
|
96
97
|
mode=tool["mode"],
|
|
97
98
|
description=description,
|
|
98
99
|
args_schema=tool["args_schema"],
|
|
99
|
-
metadata={
|
|
100
|
+
metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
|
|
100
101
|
))
|
|
101
102
|
return cls(tools=tools)
|
|
102
103
|
|
|
@@ -12,10 +12,6 @@ from github.Consts import DEFAULT_BASE_URL
|
|
|
12
12
|
from langchain_core.tools import ToolException
|
|
13
13
|
|
|
14
14
|
from ..elitea_base import extend_with_file_operations, BaseCodeToolApiWrapper
|
|
15
|
-
from .schemas import (
|
|
16
|
-
GitHubAuthConfig,
|
|
17
|
-
GitHubRepoConfig,
|
|
18
|
-
)
|
|
19
15
|
|
|
20
16
|
from .schemas import (
|
|
21
17
|
GitHubAuthConfig,
|
|
@@ -415,26 +411,53 @@ class GitHubClient(BaseModel):
|
|
|
415
411
|
Returns:
|
|
416
412
|
str: A detailed diff comparison between the two commits or an error message.
|
|
417
413
|
"""
|
|
414
|
+
def safe_author_info(commit_obj):
|
|
415
|
+
"""Safely extract author info from a commit object, handling None values."""
|
|
416
|
+
author = commit_obj.commit.author
|
|
417
|
+
if author:
|
|
418
|
+
return {
|
|
419
|
+
"name": author.name or "Unknown",
|
|
420
|
+
"date": author.date.isoformat() if author.date else None
|
|
421
|
+
}
|
|
422
|
+
# Fallback to GitHub user info if git author is not available
|
|
423
|
+
elif commit_obj.author:
|
|
424
|
+
return {
|
|
425
|
+
"name": commit_obj.author.login,
|
|
426
|
+
"date": None
|
|
427
|
+
}
|
|
428
|
+
return {"name": "Unknown", "date": None}
|
|
429
|
+
|
|
418
430
|
try:
|
|
419
431
|
# Get the repository
|
|
420
432
|
repo = self.github_api.get_repo(repo_name) if repo_name else self.github_repo_instance
|
|
421
|
-
|
|
433
|
+
|
|
422
434
|
# Get the comparison between the two commits
|
|
423
435
|
comparison = repo.compare(base_sha, head_sha)
|
|
424
|
-
|
|
436
|
+
|
|
437
|
+
# Get head commit - the GitHub Compare API doesn't return head_commit,
|
|
438
|
+
# so we get it from the commits list or fetch it directly
|
|
439
|
+
if comparison.commits:
|
|
440
|
+
head_commit_obj = comparison.commits[-1]
|
|
441
|
+
else:
|
|
442
|
+
# For identical commits or edge cases, fetch head commit directly
|
|
443
|
+
head_commit_obj = repo.get_commit(head_sha)
|
|
444
|
+
|
|
445
|
+
base_author = safe_author_info(comparison.base_commit)
|
|
446
|
+
head_author = safe_author_info(head_commit_obj)
|
|
447
|
+
|
|
425
448
|
# Extract comparison information
|
|
426
449
|
diff_info = {
|
|
427
450
|
"base_commit": {
|
|
428
451
|
"sha": comparison.base_commit.sha,
|
|
429
452
|
"message": comparison.base_commit.commit.message,
|
|
430
|
-
"author":
|
|
431
|
-
"date":
|
|
453
|
+
"author": base_author["name"],
|
|
454
|
+
"date": base_author["date"]
|
|
432
455
|
},
|
|
433
456
|
"head_commit": {
|
|
434
|
-
"sha":
|
|
435
|
-
"message":
|
|
436
|
-
"author":
|
|
437
|
-
"date":
|
|
457
|
+
"sha": head_commit_obj.sha,
|
|
458
|
+
"message": head_commit_obj.commit.message,
|
|
459
|
+
"author": head_author["name"],
|
|
460
|
+
"date": head_author["date"]
|
|
438
461
|
},
|
|
439
462
|
"status": comparison.status, # ahead, behind, identical, or diverged
|
|
440
463
|
"ahead_by": comparison.ahead_by,
|
|
@@ -443,14 +466,15 @@ class GitHubClient(BaseModel):
|
|
|
443
466
|
"commits": [],
|
|
444
467
|
"files": []
|
|
445
468
|
}
|
|
446
|
-
|
|
469
|
+
|
|
447
470
|
# Get commits in the comparison
|
|
448
471
|
for commit in comparison.commits:
|
|
472
|
+
author_info = safe_author_info(commit)
|
|
449
473
|
commit_info = {
|
|
450
474
|
"sha": commit.sha,
|
|
451
475
|
"message": commit.commit.message,
|
|
452
|
-
"author":
|
|
453
|
-
"date":
|
|
476
|
+
"author": author_info["name"],
|
|
477
|
+
"date": author_info["date"],
|
|
454
478
|
"url": commit.html_url
|
|
455
479
|
}
|
|
456
480
|
diff_info["commits"].append(commit_info)
|
|
@@ -1094,65 +1118,12 @@ class GitHubClient(BaseModel):
|
|
|
1094
1118
|
except Exception as e:
|
|
1095
1119
|
return f"Unable to create file due to error:\n{str(e)}"
|
|
1096
1120
|
|
|
1097
|
-
def extract_old_new_pairs(self, file_query):
|
|
1098
|
-
# Split the file content by lines
|
|
1099
|
-
code_lines = file_query.split("\n")
|
|
1100
|
-
|
|
1101
|
-
# Initialize lists to hold the contents of OLD and NEW sections
|
|
1102
|
-
old_contents = []
|
|
1103
|
-
new_contents = []
|
|
1104
|
-
|
|
1105
|
-
# Initialize variables to track whether the current line is within an OLD or NEW section
|
|
1106
|
-
in_old_section = False
|
|
1107
|
-
in_new_section = False
|
|
1108
|
-
|
|
1109
|
-
# Temporary storage for the current section's content
|
|
1110
|
-
current_section_content = []
|
|
1111
|
-
|
|
1112
|
-
# Iterate through each line in the file content
|
|
1113
|
-
for line in code_lines:
|
|
1114
|
-
# Check for OLD section start
|
|
1115
|
-
if "OLD <<<" in line:
|
|
1116
|
-
in_old_section = True
|
|
1117
|
-
current_section_content = [] # Reset current section content
|
|
1118
|
-
continue # Skip the line with the marker
|
|
1119
|
-
|
|
1120
|
-
# Check for OLD section end
|
|
1121
|
-
if ">>>> OLD" in line:
|
|
1122
|
-
in_old_section = False
|
|
1123
|
-
old_contents.append("\n".join(current_section_content).strip()) # Add the captured content
|
|
1124
|
-
current_section_content = [] # Reset current section content
|
|
1125
|
-
continue # Skip the line with the marker
|
|
1126
|
-
|
|
1127
|
-
# Check for NEW section start
|
|
1128
|
-
if "NEW <<<" in line:
|
|
1129
|
-
in_new_section = True
|
|
1130
|
-
current_section_content = [] # Reset current section content
|
|
1131
|
-
continue # Skip the line with the marker
|
|
1132
|
-
|
|
1133
|
-
# Check for NEW section end
|
|
1134
|
-
if ">>>> NEW" in line:
|
|
1135
|
-
in_new_section = False
|
|
1136
|
-
new_contents.append("\n".join(current_section_content).strip()) # Add the captured content
|
|
1137
|
-
current_section_content = [] # Reset current section content
|
|
1138
|
-
continue # Skip the line with the marker
|
|
1139
|
-
|
|
1140
|
-
# If currently in an OLD or NEW section, add the line to the current section content
|
|
1141
|
-
if in_old_section or in_new_section:
|
|
1142
|
-
current_section_content.append(line)
|
|
1143
|
-
|
|
1144
|
-
# Pair the OLD and NEW contents
|
|
1145
|
-
paired_contents = list(zip(old_contents, new_contents))
|
|
1146
|
-
|
|
1147
|
-
return paired_contents
|
|
1148
|
-
|
|
1149
1121
|
def update_file(self, file_query: str, repo_name: Optional[str] = None, commit_message: Optional[str] = None) -> str:
|
|
1150
|
-
"""
|
|
1151
|
-
|
|
1122
|
+
"""Updates a file with new content using OLD/NEW markers and edit_file.
|
|
1123
|
+
|
|
1152
1124
|
Parameters:
|
|
1153
|
-
file_query(str): Contains the file path and the file contents
|
|
1154
|
-
|
|
1155
|
-
The new file contents is wrapped in NEW <<<< and >>>> NEW
|
|
1125
|
+
file_query(str): Contains the file path on the first line and the file contents
|
|
1126
|
+
wrapped in OLD <<<< and >>>> OLD / NEW <<<< and >>>> NEW markers.
|
|
1156
1127
|
For example:
|
|
1157
1128
|
/test/hello.txt
|
|
1158
1129
|
OLD <<<<
|
|
@@ -1161,13 +1132,13 @@ class GitHubClient(BaseModel):
|
|
|
1161
1132
|
NEW <<<<
|
|
1162
1133
|
Hello Mars!
|
|
1163
1134
|
>>>> NEW
|
|
1164
|
-
repo_name (Optional[str]): Name of the repository in format 'owner/repo'
|
|
1135
|
+
repo_name (Optional[str]): Name of the repository in format 'owner/repo'. Currently
|
|
1136
|
+
not used by edit_file and must refer to the initialized repository.
|
|
1165
1137
|
|
|
1166
1138
|
Returns:
|
|
1167
1139
|
A success or failure message
|
|
1168
1140
|
"""
|
|
1169
1141
|
try:
|
|
1170
|
-
repo = self.github_api.get_repo(repo_name) if repo_name else self.github_repo_instance
|
|
1171
1142
|
branch = self.active_branch
|
|
1172
1143
|
|
|
1173
1144
|
if branch == self.github_base_branch:
|
|
@@ -1176,29 +1147,22 @@ class GitHubClient(BaseModel):
|
|
|
1176
1147
|
"which is protected. Please create a new branch and try again."
|
|
1177
1148
|
)
|
|
1178
1149
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
file_content = self._read_file(file_path, branch, repo_name)
|
|
1182
|
-
updated_file_content = file_content
|
|
1183
|
-
for old, new in self.extract_old_new_pairs(file_query):
|
|
1184
|
-
if not old.strip():
|
|
1185
|
-
continue
|
|
1186
|
-
updated_file_content = updated_file_content.replace(old, new)
|
|
1187
|
-
|
|
1188
|
-
if file_content == updated_file_content:
|
|
1150
|
+
if "\n" not in file_query:
|
|
1189
1151
|
return (
|
|
1190
|
-
"
|
|
1191
|
-
"
|
|
1152
|
+
"Invalid file_query format. Expected first line to be the file path "
|
|
1153
|
+
"followed by OLD/NEW blocks."
|
|
1192
1154
|
)
|
|
1193
1155
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1156
|
+
file_path, edit_content = file_query.split("\n", 1)
|
|
1157
|
+
file_path = file_path.strip()
|
|
1158
|
+
|
|
1159
|
+
# Delegate to shared edit_file implementation
|
|
1160
|
+
return self.edit_file(
|
|
1161
|
+
file_path=file_path,
|
|
1162
|
+
file_query=edit_content,
|
|
1198
1163
|
branch=branch,
|
|
1199
|
-
|
|
1164
|
+
commit_message=commit_message or f"Update {file_path}",
|
|
1200
1165
|
)
|
|
1201
|
-
return f"Updated file {file_path}"
|
|
1202
1166
|
except Exception as e:
|
|
1203
1167
|
return f"Unable to update file due to error:\n{str(e)}"
|
|
1204
1168
|
|
|
@@ -92,7 +92,7 @@ SearchIssues = create_model(
|
|
|
92
92
|
"SearchIssues",
|
|
93
93
|
search_query=(str, Field(description="Keywords or query for searching issues and PRs in Github")),
|
|
94
94
|
repo_name=(Optional[str], Field(description="Name of the repository to search issues in", default=None)),
|
|
95
|
-
max_count=(Optional[int], Field(description="Maximum number of issues to return", default=30))
|
|
95
|
+
max_count=(Optional[int], Field(description="Maximum number of issues to return", default=30, gt=0))
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
CreateIssue = create_model(
|
|
@@ -152,7 +152,7 @@ GetCommits = create_model(
|
|
|
152
152
|
since=(Optional[str], Field(description="Only commits after this date will be returned (ISO format)", default=None)),
|
|
153
153
|
until=(Optional[str], Field(description="Only commits before this date will be returned (ISO format)", default=None)),
|
|
154
154
|
author=(Optional[str], Field(description="The author of the commits", default=None)),
|
|
155
|
-
max_count=(Optional[int], Field(description="Maximum number of commits to return (default: 30)", default=30))
|
|
155
|
+
max_count=(Optional[int], Field(description="Maximum number of commits to return (default: 30)", default=30, gt=0))
|
|
156
156
|
)
|
|
157
157
|
|
|
158
158
|
GetCommitChanges = create_model(
|
|
@@ -213,7 +213,7 @@ ListProjectIssues = create_model(
|
|
|
213
213
|
"ListProjectIssues",
|
|
214
214
|
board_repo=(str, Field(description="The organization and repository for the board (project). Example: 'org-name/repo-name'")),
|
|
215
215
|
project_number=(int, Field(description="The project number as shown in the project URL")),
|
|
216
|
-
items_count=(Optional[int], Field(description="Maximum number of items to retrieve", default=100))
|
|
216
|
+
items_count=(Optional[int], Field(description="Maximum number of items to retrieve", default=100, gt=0))
|
|
217
217
|
)
|
|
218
218
|
|
|
219
219
|
SearchProjectIssues = create_model(
|
|
@@ -221,7 +221,7 @@ SearchProjectIssues = create_model(
|
|
|
221
221
|
board_repo=(str, Field(description="The organization and repository for the board (project). Example: 'org-name/repo-name'")),
|
|
222
222
|
project_number=(int, Field(description="The project number as shown in the project URL")),
|
|
223
223
|
search_query=(str, Field(description="Search query for filtering issues. Examples: 'status:In Progress', 'release:v1.0'")),
|
|
224
|
-
items_count=(Optional[int], Field(description="Maximum number of items to retrieve", default=100))
|
|
224
|
+
items_count=(Optional[int], Field(description="Maximum number of items to retrieve", default=100, gt=0))
|
|
225
225
|
)
|
|
226
226
|
|
|
227
227
|
ListProjectViews = create_model(
|
|
@@ -11,6 +11,7 @@ from ..elitea_base import filter_missconfigured_index_tools
|
|
|
11
11
|
from ..utils import clean_string, get_max_toolkit_length
|
|
12
12
|
from ...configurations.gitlab import GitlabConfiguration
|
|
13
13
|
from ...configurations.pgvector import PgVectorConfiguration
|
|
14
|
+
from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
|
|
14
15
|
|
|
15
16
|
name = "gitlab"
|
|
16
17
|
|
|
@@ -92,7 +93,7 @@ class AlitaGitlabToolkit(BaseToolkit):
|
|
|
92
93
|
name=tool["name"],
|
|
93
94
|
description=description,
|
|
94
95
|
args_schema=tool["args_schema"],
|
|
95
|
-
metadata={
|
|
96
|
+
metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
|
|
96
97
|
))
|
|
97
98
|
return cls(tools=tools)
|
|
98
99
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import fnmatch
|
|
3
3
|
from typing import Any, Dict, List, Optional
|
|
4
4
|
|
|
5
|
+
from gitlab import GitlabGetError
|
|
5
6
|
from langchain_core.tools import ToolException
|
|
6
7
|
from pydantic import create_model, Field, model_validator, SecretStr, PrivateAttr
|
|
7
8
|
|
|
@@ -9,6 +10,7 @@ from ..code_indexer_toolkit import CodeIndexerToolkit
|
|
|
9
10
|
from ..utils.available_tools_decorator import extend_with_parent_available_tools
|
|
10
11
|
from ..elitea_base import extend_with_file_operations, BaseCodeToolApiWrapper
|
|
11
12
|
from ..utils.content_parser import parse_file_content
|
|
13
|
+
from .utils import get_position
|
|
12
14
|
|
|
13
15
|
AppendFileModel = create_model(
|
|
14
16
|
"AppendFileModel",
|
|
@@ -35,7 +37,7 @@ ReadFileModel = create_model(
|
|
|
35
37
|
)
|
|
36
38
|
UpdateFileModel = create_model(
|
|
37
39
|
"UpdateFileModel",
|
|
38
|
-
file_query=(str, Field(description="The file query string")),
|
|
40
|
+
file_query=(str, Field(description="The file query string containing file path on first line, followed by OLD/NEW markers. Format: file_path\\nOLD <<<< old content >>>> OLD\\nNEW <<<< new content >>>> NEW")),
|
|
39
41
|
branch=(str, Field(description="The branch to update the file in")),
|
|
40
42
|
)
|
|
41
43
|
CommentOnIssueModel = create_model(
|
|
@@ -52,6 +54,11 @@ CreatePullRequestModel = create_model(
|
|
|
52
54
|
pr_body=(str, Field(description="The body of the pull request")),
|
|
53
55
|
branch=(str, Field(description="The branch to create the pull request from")),
|
|
54
56
|
)
|
|
57
|
+
CommentOnPRModel = create_model(
|
|
58
|
+
"CommentOnPRModel",
|
|
59
|
+
pr_number=(int, Field(description="The number of the pull request/merge request")),
|
|
60
|
+
comment=(str, Field(description="The comment text to add")),
|
|
61
|
+
)
|
|
55
62
|
|
|
56
63
|
CreateBranchModel = create_model(
|
|
57
64
|
"CreateBranchModel",
|
|
@@ -59,7 +66,7 @@ CreateBranchModel = create_model(
|
|
|
59
66
|
)
|
|
60
67
|
ListBranchesInRepoModel = create_model(
|
|
61
68
|
"ListBranchesInRepoModel",
|
|
62
|
-
limit=(Optional[int], Field(default=20, description="Maximum number of branches to return. If not provided, all branches will be returned.")),
|
|
69
|
+
limit=(Optional[int], Field(default=20, description="Maximum number of branches to return. If not provided, all branches will be returned.", gt=0)),
|
|
63
70
|
branch_wildcard=(Optional[str], Field(default=None, description="Wildcard pattern to filter branches by name. If not provided, all branches will be returned."))
|
|
64
71
|
|
|
65
72
|
)
|
|
@@ -90,9 +97,9 @@ GetPRChangesModel = create_model(
|
|
|
90
97
|
CreatePRChangeCommentModel = create_model(
|
|
91
98
|
"CreatePRChangeCommentModel",
|
|
92
99
|
pr_number=(int, Field(description="GitLab Merge Request (Pull Request) number")),
|
|
93
|
-
file_path=(str, Field(description="File path of the changed file")),
|
|
94
|
-
line_number=(int, Field(description="Line
|
|
95
|
-
comment=(str, Field(description="Comment content")),
|
|
100
|
+
file_path=(str, Field(description="File path of the changed file as shown in the diff")),
|
|
101
|
+
line_number=(int, Field(description="Line index (0-based) from the diff output. Use get_pr_changes first to see the diff and identify the correct line index to comment on.")),
|
|
102
|
+
comment=(str, Field(description="Comment content to add to the specific line")),
|
|
96
103
|
)
|
|
97
104
|
GetCommitsModel = create_model(
|
|
98
105
|
"GetCommitsModel",
|
|
@@ -235,7 +242,9 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
235
242
|
Returns:
|
|
236
243
|
File content as string
|
|
237
244
|
"""
|
|
238
|
-
|
|
245
|
+
# Default to active branch if branch is None, consistent with other methods
|
|
246
|
+
branch = branch if branch else self._active_branch
|
|
247
|
+
return str(self.read_file(file_path, branch))
|
|
239
248
|
|
|
240
249
|
def create_branch(self, branch_name: str) -> str:
|
|
241
250
|
try:
|
|
@@ -322,7 +331,30 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
322
331
|
except Exception as e:
|
|
323
332
|
return "Unable to make comment due to error:\n" + str(e)
|
|
324
333
|
|
|
334
|
+
def comment_on_pr(self, pr_number: int, comment: str) -> str:
|
|
335
|
+
"""
|
|
336
|
+
Add a comment to a pull request (merge request) in GitLab.
|
|
337
|
+
|
|
338
|
+
This method adds a general comment to the entire merge request,
|
|
339
|
+
not tied to specific code lines or file changes.
|
|
340
|
+
|
|
341
|
+
Parameters:
|
|
342
|
+
pr_number: GitLab Merge Request (Pull Request) number
|
|
343
|
+
comment: Comment text to add
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Success message or error description
|
|
347
|
+
"""
|
|
348
|
+
try:
|
|
349
|
+
mr = self.repo_instance.mergerequests.get(pr_number)
|
|
350
|
+
mr.notes.create({"body": comment})
|
|
351
|
+
return "Commented on merge request " + str(pr_number)
|
|
352
|
+
except Exception as e:
|
|
353
|
+
return "Unable to make comment due to error:\n" + str(e)
|
|
354
|
+
|
|
325
355
|
def create_file(self, file_path: str, file_contents: str, branch: str) -> str:
|
|
356
|
+
# Default to active branch if branch is None
|
|
357
|
+
branch = branch if branch else self._active_branch
|
|
326
358
|
try:
|
|
327
359
|
self.set_active_branch(branch)
|
|
328
360
|
self.repo_instance.files.get(file_path, branch)
|
|
@@ -339,6 +371,8 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
339
371
|
return "Created file " + file_path
|
|
340
372
|
|
|
341
373
|
def read_file(self, file_path: str, branch: str) -> str:
|
|
374
|
+
# Default to active branch if branch is None
|
|
375
|
+
branch = branch if branch else self._active_branch
|
|
342
376
|
self.set_active_branch(branch)
|
|
343
377
|
file = self.repo_instance.files.get(file_path, branch)
|
|
344
378
|
return parse_file_content(file_name=file_path,
|
|
@@ -406,43 +440,52 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
406
440
|
raise ToolException(f"Unable to write file {file_path}: {str(e)}")
|
|
407
441
|
|
|
408
442
|
def update_file(self, file_query: str, branch: str) -> str:
|
|
443
|
+
"""
|
|
444
|
+
Update file using edit_file functionality.
|
|
445
|
+
|
|
446
|
+
This method now delegates to edit_file which uses OLD/NEW markers.
|
|
447
|
+
For backwards compatibility, it extracts the file_path from the query.
|
|
448
|
+
|
|
449
|
+
Expected format:
|
|
450
|
+
file_path
|
|
451
|
+
OLD <<<<
|
|
452
|
+
old content
|
|
453
|
+
>>>> OLD
|
|
454
|
+
NEW <<<<
|
|
455
|
+
new content
|
|
456
|
+
>>>> NEW
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
file_query: File path on first line, followed by OLD/NEW markers
|
|
460
|
+
branch: Branch to update the file in
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
Success or error message
|
|
464
|
+
"""
|
|
409
465
|
if branch == self.branch:
|
|
410
466
|
return (
|
|
411
|
-
"You're attempting to commit
|
|
467
|
+
"You're attempting to commit directly "
|
|
412
468
|
f"to the {self.branch} branch, which is protected. "
|
|
413
469
|
"Please create a new branch and try again."
|
|
414
470
|
)
|
|
415
471
|
try:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
updated_file_content = file_content
|
|
420
|
-
for old, new in self.extract_old_new_pairs(file_query):
|
|
421
|
-
if not old.strip():
|
|
422
|
-
continue
|
|
423
|
-
updated_file_content = updated_file_content.replace(old, new)
|
|
424
|
-
|
|
425
|
-
if file_content == updated_file_content:
|
|
472
|
+
# Extract file path from first line
|
|
473
|
+
lines = file_query.split("\n", 1)
|
|
474
|
+
if len(lines) < 2:
|
|
426
475
|
return (
|
|
427
|
-
"
|
|
428
|
-
"
|
|
429
|
-
"
|
|
476
|
+
"Invalid file_query format. Expected:\n"
|
|
477
|
+
"file_path\n"
|
|
478
|
+
"OLD <<<< old content >>>> OLD\n"
|
|
479
|
+
"NEW <<<< new content >>>> NEW"
|
|
430
480
|
)
|
|
431
481
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
"file_path": file_path,
|
|
439
|
-
"content": updated_file_content,
|
|
440
|
-
}
|
|
441
|
-
],
|
|
442
|
-
}
|
|
482
|
+
file_path = lines[0].strip()
|
|
483
|
+
edit_content = lines[1] if len(lines) > 1 else ""
|
|
484
|
+
|
|
485
|
+
# Delegate to edit_file method with appropriate commit message
|
|
486
|
+
commit_message = f"Update {file_path}"
|
|
487
|
+
return self.edit_file(file_path, edit_content, branch, commit_message)
|
|
443
488
|
|
|
444
|
-
self.repo_instance.commits.create(commit)
|
|
445
|
-
return "Updated file " + file_path
|
|
446
489
|
except Exception as e:
|
|
447
490
|
return "Unable to update file due to error:\n" + str(e)
|
|
448
491
|
|
|
@@ -494,10 +537,41 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
494
537
|
return res
|
|
495
538
|
|
|
496
539
|
def create_pr_change_comment(self, pr_number: int, file_path: str, line_number: int, comment: str) -> str:
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
540
|
+
"""
|
|
541
|
+
Create a comment on a specific line in a pull request (merge request) change in GitLab.
|
|
542
|
+
|
|
543
|
+
This method adds an inline comment to a specific line in the diff of a merge request.
|
|
544
|
+
The line_number parameter refers to the line index in the diff output (0-based),
|
|
545
|
+
not the line number in the original file.
|
|
546
|
+
|
|
547
|
+
**Important**: Use get_pr_changes first to see the diff and identify the correct
|
|
548
|
+
line index for commenting.
|
|
549
|
+
|
|
550
|
+
Parameters:
|
|
551
|
+
pr_number: GitLab Merge Request number
|
|
552
|
+
file_path: Path to the file being commented on (as shown in the diff)
|
|
553
|
+
line_number: Line index from the diff (0-based index)
|
|
554
|
+
comment: Comment text to add
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
Success message or error description
|
|
558
|
+
"""
|
|
559
|
+
try:
|
|
560
|
+
mr = self.repo_instance.mergerequests.get(pr_number)
|
|
561
|
+
except GitlabGetError as e:
|
|
562
|
+
if e.response_code == 404:
|
|
563
|
+
raise ToolException(f"Merge request number {pr_number} wasn't found: {e}")
|
|
564
|
+
raise ToolException(f"Error retrieving merge request {pr_number}: {e}")
|
|
565
|
+
|
|
566
|
+
try:
|
|
567
|
+
# Calculate proper position with SHA references and line mappings
|
|
568
|
+
position = get_position(file_path=file_path, line_number=line_number, mr=mr)
|
|
569
|
+
|
|
570
|
+
# Create discussion with the comment
|
|
571
|
+
mr.discussions.create({"body": comment, "position": position})
|
|
572
|
+
return f"Comment added successfully to line {line_number} in {file_path} on MR #{pr_number}"
|
|
573
|
+
except Exception as e:
|
|
574
|
+
raise ToolException(f"Failed to create comment on MR #{pr_number}: {e}")
|
|
501
575
|
|
|
502
576
|
def get_commits(self, sha: Optional[str] = None, path: Optional[str] = None, since: Optional[str] = None, until: Optional[str] = None, author: Optional[str] = None):
|
|
503
577
|
params = {}
|
|
@@ -575,6 +649,12 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
575
649
|
"description": self.comment_on_issue.__doc__ or "Comment on an issue in the repository.",
|
|
576
650
|
"args_schema": CommentOnIssueModel,
|
|
577
651
|
},
|
|
652
|
+
{
|
|
653
|
+
"name": "comment_on_pr",
|
|
654
|
+
"ref": self.comment_on_pr,
|
|
655
|
+
"description": self.comment_on_pr.__doc__ or "Comment on a pull request (merge request) in the repository.",
|
|
656
|
+
"args_schema": CommentOnPRModel,
|
|
657
|
+
},
|
|
578
658
|
{
|
|
579
659
|
"name": "create_file",
|
|
580
660
|
"ref": self.create_file,
|
|
@@ -620,7 +700,7 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
620
700
|
{
|
|
621
701
|
"name": "create_pr_change_comment",
|
|
622
702
|
"ref": self.create_pr_change_comment,
|
|
623
|
-
"description": "Create
|
|
703
|
+
"description": self.create_pr_change_comment.__doc__ or "Create an inline comment on a specific line in a pull request change. Use get_pr_changes first to see the diff and identify the line index for commenting. The line_number is a 0-based index from the diff output, not the file line number.",
|
|
624
704
|
"args_schema": CreatePRChangeCommentModel,
|
|
625
705
|
},
|
|
626
706
|
{
|
|
@@ -8,6 +8,7 @@ from pydantic import create_model, BaseModel, ConfigDict, Field, SecretStr
|
|
|
8
8
|
from ..elitea_base import filter_missconfigured_index_tools
|
|
9
9
|
from ..utils import clean_string, get_max_toolkit_length
|
|
10
10
|
from ...configurations.gitlab import GitlabConfiguration
|
|
11
|
+
from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
|
|
11
12
|
|
|
12
13
|
name = "gitlab_org"
|
|
13
14
|
|
|
@@ -74,7 +75,7 @@ class AlitaGitlabSpaceToolkit(BaseToolkit):
|
|
|
74
75
|
name=tool['name'],
|
|
75
76
|
description=description,
|
|
76
77
|
args_schema=tool["args_schema"],
|
|
77
|
-
metadata={
|
|
78
|
+
metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
|
|
78
79
|
))
|
|
79
80
|
return cls(tools=tools)
|
|
80
81
|
|