alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__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/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +15 -3
- alita_sdk/cli/agent_loader.py +56 -8
- alita_sdk/cli/agent_ui.py +93 -31
- alita_sdk/cli/agents.py +2274 -230
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +162 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +36 -2
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +910 -64
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +17 -5
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +353 -48
- alita_sdk/runtime/clients/sandbox_client.py +0 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +123 -26
- alita_sdk/runtime/langchain/constants.py +642 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +279 -73
- alita_sdk/runtime/langchain/utils.py +82 -15
- alita_sdk/runtime/llms/preloaded.py +2 -6
- 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 +7 -0
- alita_sdk/runtime/toolkits/application.py +21 -9
- alita_sdk/runtime/toolkits/artifact.py +15 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +139 -251
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +238 -32
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/application.py +20 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +43 -15
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +852 -67
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- 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 +51 -11
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +202 -5
- alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +16 -5
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +113 -29
- alita_sdk/tools/ado/repos/__init__.py +51 -33
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- 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 +25 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -9
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +170 -45
- alita_sdk/tools/bitbucket/__init__.py +17 -12
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +10 -7
- alita_sdk/tools/code_indexer_toolkit.py +73 -23
- alita_sdk/tools/confluence/__init__.py +21 -15
- alita_sdk/tools/confluence/api_wrapper.py +78 -23
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +13 -14
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +15 -11
- alita_sdk/tools/gitlab/api_wrapper.py +207 -41
- alita_sdk/tools/gitlab_org/__init__.py +10 -8
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +10 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -11
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +11 -3
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +490 -114
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- 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 +11 -11
- alita_sdk/tools/pptx/__init__.py +10 -9
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +30 -10
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +10 -8
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -9
- alita_sdk/tools/salesforce/__init__.py +10 -9
- alita_sdk/tools/servicenow/__init__.py +17 -14
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
- alita_sdk/tools/slack/__init__.py +10 -8
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +11 -9
- alita_sdk/tools/testio/__init__.py +10 -8
- alita_sdk/tools/testrail/__init__.py +11 -8
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +77 -3
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
- alita_sdk/tools/xray/__init__.py +12 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +9 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
- alita_sdk/tools/zephyr_essential/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -9
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -8
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.462.dist-info/RECORD +0 -384
- alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -2,12 +2,16 @@
|
|
|
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
|
|
|
8
9
|
from ..code_indexer_toolkit import CodeIndexerToolkit
|
|
9
10
|
from ..utils.available_tools_decorator import extend_with_parent_available_tools
|
|
11
|
+
from ..elitea_base import extend_with_file_operations, BaseCodeToolApiWrapper
|
|
10
12
|
from ..utils.content_parser import parse_file_content
|
|
13
|
+
from .utils import get_position
|
|
14
|
+
from ..utils.tool_prompts import EDIT_FILE_DESCRIPTION, UPDATE_FILE_PROMPT_WITH_PATH
|
|
11
15
|
|
|
12
16
|
AppendFileModel = create_model(
|
|
13
17
|
"AppendFileModel",
|
|
@@ -19,7 +23,7 @@ DeleteFileModel = create_model(
|
|
|
19
23
|
"DeleteFileModel",
|
|
20
24
|
file_path=(str, Field(description="The path of the file")),
|
|
21
25
|
branch=(str, Field(description="The branch to delete the file from")),
|
|
22
|
-
commit_message=(str, Field(default=None, description="Commit message for deleting the file. Optional.")),
|
|
26
|
+
commit_message=(Optional[str], Field(default=None, description="Commit message for deleting the file. Optional.")),
|
|
23
27
|
)
|
|
24
28
|
CreateFileModel = create_model(
|
|
25
29
|
"CreateFileModel",
|
|
@@ -34,7 +38,7 @@ ReadFileModel = create_model(
|
|
|
34
38
|
)
|
|
35
39
|
UpdateFileModel = create_model(
|
|
36
40
|
"UpdateFileModel",
|
|
37
|
-
file_query=(str, Field(description=
|
|
41
|
+
file_query=(str, Field(description=UPDATE_FILE_PROMPT_WITH_PATH)),
|
|
38
42
|
branch=(str, Field(description="The branch to update the file in")),
|
|
39
43
|
)
|
|
40
44
|
CommentOnIssueModel = create_model(
|
|
@@ -51,6 +55,11 @@ CreatePullRequestModel = create_model(
|
|
|
51
55
|
pr_body=(str, Field(description="The body of the pull request")),
|
|
52
56
|
branch=(str, Field(description="The branch to create the pull request from")),
|
|
53
57
|
)
|
|
58
|
+
CommentOnPRModel = create_model(
|
|
59
|
+
"CommentOnPRModel",
|
|
60
|
+
pr_number=(int, Field(description="The number of the pull request/merge request")),
|
|
61
|
+
comment=(str, Field(description="The comment text to add")),
|
|
62
|
+
)
|
|
54
63
|
|
|
55
64
|
CreateBranchModel = create_model(
|
|
56
65
|
"CreateBranchModel",
|
|
@@ -58,7 +67,7 @@ CreateBranchModel = create_model(
|
|
|
58
67
|
)
|
|
59
68
|
ListBranchesInRepoModel = create_model(
|
|
60
69
|
"ListBranchesInRepoModel",
|
|
61
|
-
limit=(Optional[int], Field(default=20, description="Maximum number of branches to return. If not provided, all branches will be returned.")),
|
|
70
|
+
limit=(Optional[int], Field(default=20, description="Maximum number of branches to return. If not provided, all branches will be returned.", gt=0)),
|
|
62
71
|
branch_wildcard=(Optional[str], Field(default=None, description="Wildcard pattern to filter branches by name. If not provided, all branches will be returned."))
|
|
63
72
|
|
|
64
73
|
)
|
|
@@ -89,9 +98,9 @@ GetPRChangesModel = create_model(
|
|
|
89
98
|
CreatePRChangeCommentModel = create_model(
|
|
90
99
|
"CreatePRChangeCommentModel",
|
|
91
100
|
pr_number=(int, Field(description="GitLab Merge Request (Pull Request) number")),
|
|
92
|
-
file_path=(str, Field(description="File path of the changed file")),
|
|
93
|
-
line_number=(int, Field(description="Line
|
|
94
|
-
comment=(str, Field(description="Comment content")),
|
|
101
|
+
file_path=(str, Field(description="File path of the changed file as shown in the diff")),
|
|
102
|
+
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.")),
|
|
103
|
+
comment=(str, Field(description="Comment content to add to the specific line")),
|
|
95
104
|
)
|
|
96
105
|
GetCommitsModel = create_model(
|
|
97
106
|
"GetCommitsModel",
|
|
@@ -109,6 +118,12 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
109
118
|
branch: Optional[str] = 'main'
|
|
110
119
|
_git: Any = PrivateAttr()
|
|
111
120
|
_active_branch: Any = PrivateAttr()
|
|
121
|
+
|
|
122
|
+
# Import file operation methods from BaseCodeToolApiWrapper
|
|
123
|
+
read_file_chunk = BaseCodeToolApiWrapper.read_file_chunk
|
|
124
|
+
read_multiple_files = BaseCodeToolApiWrapper.read_multiple_files
|
|
125
|
+
search_file = BaseCodeToolApiWrapper.search_file
|
|
126
|
+
edit_file = BaseCodeToolApiWrapper.edit_file
|
|
112
127
|
|
|
113
128
|
@staticmethod
|
|
114
129
|
def _sanitize_url(url: str) -> str:
|
|
@@ -215,8 +230,22 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
215
230
|
except Exception as e:
|
|
216
231
|
return f"Unable to get commit hash for {file_path} due to error:\n{e}"
|
|
217
232
|
|
|
218
|
-
def _read_file(self, file_path: str, branch: str):
|
|
219
|
-
|
|
233
|
+
def _read_file(self, file_path: str, branch: str, **kwargs):
|
|
234
|
+
"""
|
|
235
|
+
Read a file from specified branch with optional partial read support.
|
|
236
|
+
|
|
237
|
+
Parameters:
|
|
238
|
+
file_path: the file path
|
|
239
|
+
branch: the branch to read the file from
|
|
240
|
+
**kwargs: Additional parameters (offset, limit, head, tail) - currently ignored,
|
|
241
|
+
partial read handled client-side by base class methods
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
File content as string
|
|
245
|
+
"""
|
|
246
|
+
# Default to active branch if branch is None, consistent with other methods
|
|
247
|
+
branch = branch if branch else self._active_branch
|
|
248
|
+
return str(self.read_file(file_path, branch))
|
|
220
249
|
|
|
221
250
|
def create_branch(self, branch_name: str) -> str:
|
|
222
251
|
try:
|
|
@@ -303,7 +332,30 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
303
332
|
except Exception as e:
|
|
304
333
|
return "Unable to make comment due to error:\n" + str(e)
|
|
305
334
|
|
|
335
|
+
def comment_on_pr(self, pr_number: int, comment: str) -> str:
|
|
336
|
+
"""
|
|
337
|
+
Add a comment to a pull request (merge request) in GitLab.
|
|
338
|
+
|
|
339
|
+
This method adds a general comment to the entire merge request,
|
|
340
|
+
not tied to specific code lines or file changes.
|
|
341
|
+
|
|
342
|
+
Parameters:
|
|
343
|
+
pr_number: GitLab Merge Request (Pull Request) number
|
|
344
|
+
comment: Comment text to add
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Success message or error description
|
|
348
|
+
"""
|
|
349
|
+
try:
|
|
350
|
+
mr = self.repo_instance.mergerequests.get(pr_number)
|
|
351
|
+
mr.notes.create({"body": comment})
|
|
352
|
+
return "Commented on merge request " + str(pr_number)
|
|
353
|
+
except Exception as e:
|
|
354
|
+
return "Unable to make comment due to error:\n" + str(e)
|
|
355
|
+
|
|
306
356
|
def create_file(self, file_path: str, file_contents: str, branch: str) -> str:
|
|
357
|
+
# Default to active branch if branch is None
|
|
358
|
+
branch = branch if branch else self._active_branch
|
|
307
359
|
try:
|
|
308
360
|
self.set_active_branch(branch)
|
|
309
361
|
self.repo_instance.files.get(file_path, branch)
|
|
@@ -320,50 +372,126 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
320
372
|
return "Created file " + file_path
|
|
321
373
|
|
|
322
374
|
def read_file(self, file_path: str, branch: str) -> str:
|
|
375
|
+
# Default to active branch if branch is None
|
|
376
|
+
branch = branch if branch else self._active_branch
|
|
323
377
|
self.set_active_branch(branch)
|
|
324
378
|
file = self.repo_instance.files.get(file_path, branch)
|
|
325
379
|
return parse_file_content(file_name=file_path,
|
|
326
380
|
file_content=file.decode(),
|
|
327
381
|
llm=self.llm)
|
|
382
|
+
|
|
383
|
+
def _write_file(
|
|
384
|
+
self,
|
|
385
|
+
file_path: str,
|
|
386
|
+
content: str,
|
|
387
|
+
branch: str = None,
|
|
388
|
+
commit_message: str = None
|
|
389
|
+
) -> str:
|
|
390
|
+
"""
|
|
391
|
+
Write content to a file (create or update).
|
|
392
|
+
|
|
393
|
+
Parameters:
|
|
394
|
+
file_path: Path to the file
|
|
395
|
+
content: New file content
|
|
396
|
+
branch: Branch name (uses active branch if None)
|
|
397
|
+
commit_message: Commit message
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
Success message
|
|
401
|
+
"""
|
|
402
|
+
try:
|
|
403
|
+
branch = branch or self._active_branch
|
|
404
|
+
|
|
405
|
+
if branch == self.branch:
|
|
406
|
+
raise ToolException(
|
|
407
|
+
f"Cannot commit directly to the {self.branch} branch. "
|
|
408
|
+
"Please create a new branch and try again."
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
self.set_active_branch(branch)
|
|
412
|
+
|
|
413
|
+
# Check if file exists
|
|
414
|
+
try:
|
|
415
|
+
self.repo_instance.files.get(file_path, branch)
|
|
416
|
+
# File exists, update it
|
|
417
|
+
commit = {
|
|
418
|
+
"branch": branch,
|
|
419
|
+
"commit_message": commit_message or f"Update {file_path}",
|
|
420
|
+
"actions": [
|
|
421
|
+
{
|
|
422
|
+
"action": "update",
|
|
423
|
+
"file_path": file_path,
|
|
424
|
+
"content": content,
|
|
425
|
+
}
|
|
426
|
+
],
|
|
427
|
+
}
|
|
428
|
+
self.repo_instance.commits.create(commit)
|
|
429
|
+
return f"Updated file {file_path}"
|
|
430
|
+
except:
|
|
431
|
+
# File doesn't exist, create it
|
|
432
|
+
data = {
|
|
433
|
+
"branch": branch,
|
|
434
|
+
"commit_message": commit_message or f"Create {file_path}",
|
|
435
|
+
"file_path": file_path,
|
|
436
|
+
"content": content,
|
|
437
|
+
}
|
|
438
|
+
self.repo_instance.files.create(data)
|
|
439
|
+
return f"Created file {file_path}"
|
|
440
|
+
except Exception as e:
|
|
441
|
+
raise ToolException(f"Unable to write file {file_path}: {str(e)}")
|
|
328
442
|
|
|
329
443
|
def update_file(self, file_query: str, branch: str) -> str:
|
|
444
|
+
"""
|
|
445
|
+
Update file using edit_file functionality.
|
|
446
|
+
|
|
447
|
+
This method now delegates to edit_file which uses OLD/NEW markers.
|
|
448
|
+
For backwards compatibility, it extracts the file_path from the query.
|
|
449
|
+
|
|
450
|
+
Expected format:
|
|
451
|
+
file_path
|
|
452
|
+
OLD <<<<
|
|
453
|
+
old content
|
|
454
|
+
>>>> OLD
|
|
455
|
+
NEW <<<<
|
|
456
|
+
new content
|
|
457
|
+
>>>> NEW
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
file_query: File path on first line, followed by OLD/NEW markers
|
|
461
|
+
branch: Branch to update the file in
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
Success or error message
|
|
465
|
+
"""
|
|
330
466
|
if branch == self.branch:
|
|
331
467
|
return (
|
|
332
|
-
"You're attempting to commit
|
|
468
|
+
"You're attempting to commit directly "
|
|
333
469
|
f"to the {self.branch} branch, which is protected. "
|
|
334
470
|
"Please create a new branch and try again."
|
|
335
471
|
)
|
|
336
472
|
try:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if file_content == updated_file_content:
|
|
473
|
+
# Split into lines and find first non-empty line for file_path
|
|
474
|
+
lines = file_query.split("\n")
|
|
475
|
+
first_non_empty_idx = None
|
|
476
|
+
for i, line in enumerate(lines):
|
|
477
|
+
if line.strip():
|
|
478
|
+
first_non_empty_idx = i
|
|
479
|
+
break
|
|
480
|
+
|
|
481
|
+
if first_non_empty_idx is None:
|
|
347
482
|
return (
|
|
348
|
-
"
|
|
349
|
-
"
|
|
350
|
-
"the current file contents."
|
|
483
|
+
"Invalid file_query format. Expected first non-empty line to be the file path "
|
|
484
|
+
"followed by OLD/NEW blocks."
|
|
351
485
|
)
|
|
352
486
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
"content": updated_file_content,
|
|
361
|
-
}
|
|
362
|
-
],
|
|
363
|
-
}
|
|
487
|
+
file_path = lines[first_non_empty_idx].strip()
|
|
488
|
+
# Keep all lines after file_path line (preserving empty lines)
|
|
489
|
+
edit_content = "\n".join(lines[first_non_empty_idx + 1:])
|
|
490
|
+
|
|
491
|
+
# Delegate to edit_file method with appropriate commit message
|
|
492
|
+
commit_message = f"Update {file_path}"
|
|
493
|
+
return self.edit_file(file_path, edit_content, branch, commit_message)
|
|
364
494
|
|
|
365
|
-
self.repo_instance.commits.create(commit)
|
|
366
|
-
return "Updated file " + file_path
|
|
367
495
|
except Exception as e:
|
|
368
496
|
return "Unable to update file due to error:\n" + str(e)
|
|
369
497
|
|
|
@@ -415,10 +543,41 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
415
543
|
return res
|
|
416
544
|
|
|
417
545
|
def create_pr_change_comment(self, pr_number: int, file_path: str, line_number: int, comment: str) -> str:
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
546
|
+
"""
|
|
547
|
+
Create a comment on a specific line in a pull request (merge request) change in GitLab.
|
|
548
|
+
|
|
549
|
+
This method adds an inline comment to a specific line in the diff of a merge request.
|
|
550
|
+
The line_number parameter refers to the line index in the diff output (0-based),
|
|
551
|
+
not the line number in the original file.
|
|
552
|
+
|
|
553
|
+
**Important**: Use get_pr_changes first to see the diff and identify the correct
|
|
554
|
+
line index for commenting.
|
|
555
|
+
|
|
556
|
+
Parameters:
|
|
557
|
+
pr_number: GitLab Merge Request number
|
|
558
|
+
file_path: Path to the file being commented on (as shown in the diff)
|
|
559
|
+
line_number: Line index from the diff (0-based index)
|
|
560
|
+
comment: Comment text to add
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Success message or error description
|
|
564
|
+
"""
|
|
565
|
+
try:
|
|
566
|
+
mr = self.repo_instance.mergerequests.get(pr_number)
|
|
567
|
+
except GitlabGetError as e:
|
|
568
|
+
if e.response_code == 404:
|
|
569
|
+
raise ToolException(f"Merge request number {pr_number} wasn't found: {e}")
|
|
570
|
+
raise ToolException(f"Error retrieving merge request {pr_number}: {e}")
|
|
571
|
+
|
|
572
|
+
try:
|
|
573
|
+
# Calculate proper position with SHA references and line mappings
|
|
574
|
+
position = get_position(file_path=file_path, line_number=line_number, mr=mr)
|
|
575
|
+
|
|
576
|
+
# Create discussion with the comment
|
|
577
|
+
mr.discussions.create({"body": comment, "position": position})
|
|
578
|
+
return f"Comment added successfully to line {line_number} in {file_path} on MR #{pr_number}"
|
|
579
|
+
except Exception as e:
|
|
580
|
+
raise ToolException(f"Failed to create comment on MR #{pr_number}: {e}")
|
|
422
581
|
|
|
423
582
|
def get_commits(self, sha: Optional[str] = None, path: Optional[str] = None, since: Optional[str] = None, until: Optional[str] = None, author: Optional[str] = None):
|
|
424
583
|
params = {}
|
|
@@ -445,6 +604,7 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
445
604
|
]
|
|
446
605
|
|
|
447
606
|
@extend_with_parent_available_tools
|
|
607
|
+
@extend_with_file_operations
|
|
448
608
|
def get_available_tools(self):
|
|
449
609
|
return [
|
|
450
610
|
{
|
|
@@ -495,6 +655,12 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
495
655
|
"description": self.comment_on_issue.__doc__ or "Comment on an issue in the repository.",
|
|
496
656
|
"args_schema": CommentOnIssueModel,
|
|
497
657
|
},
|
|
658
|
+
{
|
|
659
|
+
"name": "comment_on_pr",
|
|
660
|
+
"ref": self.comment_on_pr,
|
|
661
|
+
"description": self.comment_on_pr.__doc__ or "Comment on a pull request (merge request) in the repository.",
|
|
662
|
+
"args_schema": CommentOnPRModel,
|
|
663
|
+
},
|
|
498
664
|
{
|
|
499
665
|
"name": "create_file",
|
|
500
666
|
"ref": self.create_file,
|
|
@@ -510,7 +676,7 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
510
676
|
{
|
|
511
677
|
"name": "update_file",
|
|
512
678
|
"ref": self.update_file,
|
|
513
|
-
"description":
|
|
679
|
+
"description": EDIT_FILE_DESCRIPTION,
|
|
514
680
|
"args_schema": UpdateFileModel,
|
|
515
681
|
},
|
|
516
682
|
{
|
|
@@ -540,7 +706,7 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
|
|
|
540
706
|
{
|
|
541
707
|
"name": "create_pr_change_comment",
|
|
542
708
|
"ref": self.create_pr_change_comment,
|
|
543
|
-
"description": "Create
|
|
709
|
+
"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.",
|
|
544
710
|
"args_schema": CreatePRChangeCommentModel,
|
|
545
711
|
},
|
|
546
712
|
{
|
|
@@ -6,8 +6,9 @@ from ..base.tool import BaseAction
|
|
|
6
6
|
from pydantic import create_model, BaseModel, ConfigDict, Field, SecretStr
|
|
7
7
|
|
|
8
8
|
from ..elitea_base import filter_missconfigured_index_tools
|
|
9
|
-
from ..utils import clean_string,
|
|
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
|
|
|
@@ -22,12 +23,10 @@ def get_tools(tool):
|
|
|
22
23
|
|
|
23
24
|
class AlitaGitlabSpaceToolkit(BaseToolkit):
|
|
24
25
|
tools: List[BaseTool] = []
|
|
25
|
-
toolkit_max_length: int = 0
|
|
26
26
|
|
|
27
27
|
@staticmethod
|
|
28
28
|
def toolkit_config_schema() -> BaseModel:
|
|
29
29
|
selected_tools = {x['name']: x['args_schema'].schema() for x in GitLabWorkspaceAPIWrapper.model_construct().get_available_tools()}
|
|
30
|
-
AlitaGitlabSpaceToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
|
|
31
30
|
return create_model(
|
|
32
31
|
name,
|
|
33
32
|
gitlab_configuration=(GitlabConfiguration, Field(description="GitLab configuration",
|
|
@@ -44,7 +43,6 @@ class AlitaGitlabSpaceToolkit(BaseToolkit):
|
|
|
44
43
|
'metadata': {
|
|
45
44
|
"label": "GitLab Org",
|
|
46
45
|
"icon_url": None,
|
|
47
|
-
"max_length": AlitaGitlabSpaceToolkit.toolkit_max_length,
|
|
48
46
|
"categories": ["code repositories"],
|
|
49
47
|
"extra_categories": ["gitlab", "git", "repository", "code", "version control"],
|
|
50
48
|
}
|
|
@@ -62,18 +60,22 @@ class AlitaGitlabSpaceToolkit(BaseToolkit):
|
|
|
62
60
|
**kwargs['gitlab_configuration'],
|
|
63
61
|
}
|
|
64
62
|
gitlab_wrapper = GitLabWorkspaceAPIWrapper(**wrapper_payload)
|
|
65
|
-
prefix = clean_string(toolkit_name, AlitaGitlabSpaceToolkit.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
|
|
66
63
|
available_tools = gitlab_wrapper.get_available_tools()
|
|
67
64
|
tools = []
|
|
68
65
|
for tool in available_tools:
|
|
69
66
|
if selected_tools:
|
|
70
67
|
if tool["name"] not in selected_tools:
|
|
71
68
|
continue
|
|
69
|
+
description = tool["description"]
|
|
70
|
+
if toolkit_name:
|
|
71
|
+
description = f"Toolkit: {toolkit_name}\n{description}"
|
|
72
|
+
description = description[:1000]
|
|
72
73
|
tools.append(BaseAction(
|
|
73
74
|
api_wrapper=gitlab_wrapper,
|
|
74
|
-
name=
|
|
75
|
-
description=
|
|
76
|
-
args_schema=tool["args_schema"]
|
|
75
|
+
name=tool['name'],
|
|
76
|
+
description=description,
|
|
77
|
+
args_schema=tool["args_schema"],
|
|
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"]}
|
|
77
79
|
))
|
|
78
80
|
return cls(tools=tools)
|
|
79
81
|
|
|
@@ -8,8 +8,9 @@ from langchain_core.tools import ToolException
|
|
|
8
8
|
from pydantic import model_validator, PrivateAttr, create_model, SecretStr
|
|
9
9
|
from pydantic.fields import Field
|
|
10
10
|
|
|
11
|
-
from ..elitea_base import BaseToolApiWrapper
|
|
11
|
+
from ..elitea_base import BaseToolApiWrapper, BaseCodeToolApiWrapper
|
|
12
12
|
from ..gitlab.utils import get_diff_w_position, get_position
|
|
13
|
+
from ..utils.tool_prompts import EDIT_FILE_DESCRIPTION, UPDATE_FILE_PROMPT_NO_PATH
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
@@ -24,7 +25,7 @@ GitLabCreateBranch = create_model(
|
|
|
24
25
|
GitLabListBranches = create_model(
|
|
25
26
|
"GitLabListBranchesModel",
|
|
26
27
|
repository=(Optional[str], Field(description="Name of the repository", default=None)),
|
|
27
|
-
limit=(Optional[int], Field(description="Maximum number of branches to return. If not provided, all branches will be returned.", default=20)),
|
|
28
|
+
limit=(Optional[int], Field(description="Maximum number of branches to return. If not provided, all branches will be returned.", default=20, gt=0)),
|
|
28
29
|
branch_wildcard=(Optional[str], Field(description="Wildcard pattern to filter branches by name. If not provided, all branches will be returned.", default=None))
|
|
29
30
|
)
|
|
30
31
|
|
|
@@ -75,7 +76,7 @@ GitLabReadFile = create_model(
|
|
|
75
76
|
GitLabUpdateFile = create_model(
|
|
76
77
|
"GitLabUpdateFile",
|
|
77
78
|
file_path=(str, Field(description="Path of the file to update")),
|
|
78
|
-
update_query=(str, Field(description=
|
|
79
|
+
update_query=(str, Field(description=UPDATE_FILE_PROMPT_NO_PATH)),
|
|
79
80
|
repository=(Optional[str], Field(description="Name of the repository", default=None)),
|
|
80
81
|
branch=(str, Field(description=branch_description))
|
|
81
82
|
)
|
|
@@ -159,6 +160,9 @@ class GitLabWorkspaceAPIWrapper(BaseToolApiWrapper):
|
|
|
159
160
|
repo_instances: Dict[str, Any] = {}
|
|
160
161
|
_active_branch: Optional[str] = PrivateAttr(default='main')
|
|
161
162
|
|
|
163
|
+
# Reuse common file helpers from BaseCodeToolApiWrapper where applicable
|
|
164
|
+
edit_file = BaseCodeToolApiWrapper.edit_file
|
|
165
|
+
|
|
162
166
|
class Config:
|
|
163
167
|
arbitrary_types_allowed = True
|
|
164
168
|
|
|
@@ -371,51 +375,76 @@ class GitLabWorkspaceAPIWrapper(BaseToolApiWrapper):
|
|
|
371
375
|
except Exception as e:
|
|
372
376
|
return ToolException(e)
|
|
373
377
|
|
|
374
|
-
def
|
|
375
|
-
"""
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
378
|
+
def _read_file(self, file_path: str, branch: str, **kwargs) -> str:
|
|
379
|
+
"""
|
|
380
|
+
Internal read_file used by BaseCodeToolApiWrapper.edit_file.
|
|
381
|
+
Delegates to the public `read_file` implementation which supports an optional repository argument.
|
|
382
|
+
The repository may be passed via kwargs or provided earlier through `update_file` which sets
|
|
383
|
+
a temporary attribute `_tmp_repository_for_edit`.
|
|
384
|
+
"""
|
|
385
|
+
# Repository from temporary context, then None
|
|
386
|
+
repository = getattr(self, "_tmp_repository_for_edit", None)
|
|
387
|
+
try:
|
|
388
|
+
# Public read_file signature: read_file(file_path, branch, repository=None)
|
|
389
|
+
return self.read_file(file_path, branch, repository)
|
|
390
|
+
except Exception as e:
|
|
391
|
+
raise ToolException(f"Can't extract file content (`{file_path}`) due to error:\n{str(e)}")
|
|
392
|
+
|
|
393
|
+
def _write_file(self, file_path: str, content: str, branch: str = None, commit_message: str = None) -> str:
|
|
394
|
+
"""
|
|
395
|
+
Write content to a file (update only) in the specified GitLab repository.
|
|
396
|
+
|
|
397
|
+
This implementation follows the same commit flow as the previous `update_file`:
|
|
398
|
+
it does not attempt to create the file when it is missing — it will always
|
|
399
|
+
create a commit with a single `update` action. If the file does not exist on
|
|
400
|
+
the target branch, the underlying GitLab API will typically return an error.
|
|
389
401
|
"""
|
|
390
402
|
try:
|
|
403
|
+
branch = branch if branch else (self._active_branch if self._active_branch else self.branch)
|
|
404
|
+
# pick repository from temporary edit context
|
|
405
|
+
repository = getattr(self, "_tmp_repository_for_edit", None)
|
|
391
406
|
repo_instance = self._get_repo(repository)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
for old, new in self.extract_old_new_pairs(update_query):
|
|
395
|
-
if not old.strip():
|
|
396
|
-
continue
|
|
397
|
-
updated_file_content = updated_file_content.replace(old, new)
|
|
398
|
-
if file_content == updated_file_content:
|
|
399
|
-
return (
|
|
400
|
-
"File content was not updated because old content was not found or empty."
|
|
401
|
-
"It may be helpful to use the read_file action to get "
|
|
402
|
-
"the current file contents."
|
|
403
|
-
)
|
|
407
|
+
|
|
408
|
+
# Always perform an 'update' action commit (do not create file when missing)
|
|
404
409
|
commit = {
|
|
405
410
|
"branch": branch,
|
|
406
|
-
"commit_message": "Update "
|
|
411
|
+
"commit_message": commit_message or f"Update {file_path}",
|
|
407
412
|
"actions": [
|
|
408
413
|
{
|
|
409
414
|
"action": "update",
|
|
410
415
|
"file_path": file_path,
|
|
411
|
-
"content":
|
|
416
|
+
"content": content,
|
|
412
417
|
}
|
|
413
418
|
],
|
|
414
419
|
}
|
|
415
420
|
repo_instance.commits.create(commit)
|
|
416
|
-
return "Updated file "
|
|
421
|
+
return f"Updated file {file_path}"
|
|
422
|
+
except ToolException:
|
|
423
|
+
raise
|
|
424
|
+
except Exception as e:
|
|
425
|
+
return ToolException(f"Unable to write file due to error: {str(e)}")
|
|
426
|
+
|
|
427
|
+
def update_file(self, file_path: str, update_query: str, branch: str, repository: Optional[str] = None) -> str:
|
|
428
|
+
"""Updates a file with new content using OLD/NEW markers by delegating to `edit_file`.
|
|
429
|
+
|
|
430
|
+
The method sets a temporary repository context so that `edit_file`'s internal
|
|
431
|
+
calls to `_read_file` and `_write_file` operate on the requested repository.
|
|
432
|
+
"""
|
|
433
|
+
# Set temporary repository context used by _read_file/_write_file
|
|
434
|
+
self._tmp_repository_for_edit = repository
|
|
435
|
+
try:
|
|
436
|
+
commit_message = f"Update {file_path}"
|
|
437
|
+
return self.edit_file(file_path=file_path, file_query=update_query, branch=branch, commit_message=commit_message)
|
|
438
|
+
except ToolException as e:
|
|
439
|
+
return str(e)
|
|
417
440
|
except Exception as e:
|
|
418
441
|
return ToolException(f"Unable to update file due to error: {str(e)}")
|
|
442
|
+
finally:
|
|
443
|
+
# Clear temporary context
|
|
444
|
+
try:
|
|
445
|
+
delattr(self, "_tmp_repository_for_edit")
|
|
446
|
+
except Exception:
|
|
447
|
+
self._tmp_repository_for_edit = None
|
|
419
448
|
|
|
420
449
|
def delete_file(self, file_path: str, branch: str, repository: Optional[str] = None) -> str:
|
|
421
450
|
"""Deletes a file from the repo."""
|
|
@@ -428,36 +457,6 @@ class GitLabWorkspaceAPIWrapper(BaseToolApiWrapper):
|
|
|
428
457
|
except Exception as e:
|
|
429
458
|
return ToolException(f"Unable to delete file due to error: {str(e)}")
|
|
430
459
|
|
|
431
|
-
def extract_old_new_pairs(self, file_query):
|
|
432
|
-
"""Extract old and new content pairs from the file query."""
|
|
433
|
-
code_lines = file_query.split("\n")
|
|
434
|
-
old_contents = []
|
|
435
|
-
new_contents = []
|
|
436
|
-
in_old_section = False
|
|
437
|
-
in_new_section = False
|
|
438
|
-
current_section_content = []
|
|
439
|
-
for line in code_lines:
|
|
440
|
-
if "OLD <<<" in line:
|
|
441
|
-
in_old_section = True
|
|
442
|
-
current_section_content = []
|
|
443
|
-
continue
|
|
444
|
-
if ">>>> OLD" in line:
|
|
445
|
-
in_old_section = False
|
|
446
|
-
old_contents.append("\n".join(current_section_content).strip())
|
|
447
|
-
current_section_content = []
|
|
448
|
-
continue
|
|
449
|
-
if "NEW <<<" in line:
|
|
450
|
-
in_new_section = True
|
|
451
|
-
current_section_content = []
|
|
452
|
-
continue
|
|
453
|
-
if ">>>> NEW" in line:
|
|
454
|
-
in_new_section = False
|
|
455
|
-
new_contents.append("\n".join(current_section_content).strip())
|
|
456
|
-
current_section_content = []
|
|
457
|
-
continue
|
|
458
|
-
if in_old_section or in_new_section:
|
|
459
|
-
current_section_content.append(line)
|
|
460
|
-
return list(zip(old_contents, new_contents))
|
|
461
460
|
|
|
462
461
|
def append_file(self, file_path: str, content: str, branch: str, repository: Optional[str] = None) -> str:
|
|
463
462
|
"""
|
|
@@ -641,7 +640,7 @@ class GitLabWorkspaceAPIWrapper(BaseToolApiWrapper):
|
|
|
641
640
|
},
|
|
642
641
|
{
|
|
643
642
|
"name": "update_file",
|
|
644
|
-
"description":
|
|
643
|
+
"description": EDIT_FILE_DESCRIPTION,
|
|
645
644
|
"args_schema": GitLabUpdateFile,
|
|
646
645
|
"ref": self.update_file,
|
|
647
646
|
},
|