alita-sdk 0.3.379__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.
Files changed (278) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent/__init__.py +5 -0
  4. alita_sdk/cli/agent/default.py +258 -0
  5. alita_sdk/cli/agent_executor.py +156 -0
  6. alita_sdk/cli/agent_loader.py +245 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3113 -0
  9. alita_sdk/cli/callbacks.py +647 -0
  10. alita_sdk/cli/cli.py +168 -0
  11. alita_sdk/cli/config.py +306 -0
  12. alita_sdk/cli/context/__init__.py +30 -0
  13. alita_sdk/cli/context/cleanup.py +198 -0
  14. alita_sdk/cli/context/manager.py +731 -0
  15. alita_sdk/cli/context/message.py +285 -0
  16. alita_sdk/cli/context/strategies.py +289 -0
  17. alita_sdk/cli/context/token_estimation.py +127 -0
  18. alita_sdk/cli/formatting.py +182 -0
  19. alita_sdk/cli/input_handler.py +419 -0
  20. alita_sdk/cli/inventory.py +1073 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/testcases/__init__.py +94 -0
  23. alita_sdk/cli/testcases/data_generation.py +119 -0
  24. alita_sdk/cli/testcases/discovery.py +96 -0
  25. alita_sdk/cli/testcases/executor.py +84 -0
  26. alita_sdk/cli/testcases/logger.py +85 -0
  27. alita_sdk/cli/testcases/parser.py +172 -0
  28. alita_sdk/cli/testcases/prompts.py +91 -0
  29. alita_sdk/cli/testcases/reporting.py +125 -0
  30. alita_sdk/cli/testcases/setup.py +108 -0
  31. alita_sdk/cli/testcases/test_runner.py +282 -0
  32. alita_sdk/cli/testcases/utils.py +39 -0
  33. alita_sdk/cli/testcases/validation.py +90 -0
  34. alita_sdk/cli/testcases/workflow.py +196 -0
  35. alita_sdk/cli/toolkit.py +327 -0
  36. alita_sdk/cli/toolkit_loader.py +85 -0
  37. alita_sdk/cli/tools/__init__.py +43 -0
  38. alita_sdk/cli/tools/approval.py +224 -0
  39. alita_sdk/cli/tools/filesystem.py +1751 -0
  40. alita_sdk/cli/tools/planning.py +389 -0
  41. alita_sdk/cli/tools/terminal.py +414 -0
  42. alita_sdk/community/__init__.py +72 -12
  43. alita_sdk/community/inventory/__init__.py +236 -0
  44. alita_sdk/community/inventory/config.py +257 -0
  45. alita_sdk/community/inventory/enrichment.py +2137 -0
  46. alita_sdk/community/inventory/extractors.py +1469 -0
  47. alita_sdk/community/inventory/ingestion.py +3172 -0
  48. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  49. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  50. alita_sdk/community/inventory/parsers/base.py +295 -0
  51. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  52. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  53. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  54. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  55. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  56. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  57. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  58. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  59. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  60. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  61. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  62. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  63. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  64. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  65. alita_sdk/community/inventory/patterns/loader.py +348 -0
  66. alita_sdk/community/inventory/patterns/registry.py +198 -0
  67. alita_sdk/community/inventory/presets.py +535 -0
  68. alita_sdk/community/inventory/retrieval.py +1403 -0
  69. alita_sdk/community/inventory/toolkit.py +173 -0
  70. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  71. alita_sdk/community/inventory/visualize.py +1370 -0
  72. alita_sdk/configurations/__init__.py +1 -1
  73. alita_sdk/configurations/ado.py +141 -20
  74. alita_sdk/configurations/bitbucket.py +94 -2
  75. alita_sdk/configurations/confluence.py +130 -1
  76. alita_sdk/configurations/figma.py +76 -0
  77. alita_sdk/configurations/gitlab.py +91 -0
  78. alita_sdk/configurations/jira.py +103 -0
  79. alita_sdk/configurations/openapi.py +329 -0
  80. alita_sdk/configurations/qtest.py +72 -1
  81. alita_sdk/configurations/report_portal.py +96 -0
  82. alita_sdk/configurations/sharepoint.py +148 -0
  83. alita_sdk/configurations/testio.py +83 -0
  84. alita_sdk/configurations/testrail.py +88 -0
  85. alita_sdk/configurations/xray.py +93 -0
  86. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  87. alita_sdk/configurations/zephyr_essential.py +75 -0
  88. alita_sdk/runtime/clients/artifact.py +3 -3
  89. alita_sdk/runtime/clients/client.py +388 -46
  90. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  91. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  92. alita_sdk/runtime/clients/sandbox_client.py +8 -21
  93. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  94. alita_sdk/runtime/langchain/assistant.py +157 -39
  95. alita_sdk/runtime/langchain/constants.py +647 -1
  96. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  97. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
  100. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  101. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  102. alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
  103. alita_sdk/runtime/langchain/langraph_agent.py +405 -84
  104. alita_sdk/runtime/langchain/utils.py +106 -7
  105. alita_sdk/runtime/llms/preloaded.py +2 -6
  106. alita_sdk/runtime/models/mcp_models.py +61 -0
  107. alita_sdk/runtime/skills/__init__.py +91 -0
  108. alita_sdk/runtime/skills/callbacks.py +498 -0
  109. alita_sdk/runtime/skills/discovery.py +540 -0
  110. alita_sdk/runtime/skills/executor.py +610 -0
  111. alita_sdk/runtime/skills/input_builder.py +371 -0
  112. alita_sdk/runtime/skills/models.py +330 -0
  113. alita_sdk/runtime/skills/registry.py +355 -0
  114. alita_sdk/runtime/skills/skill_runner.py +330 -0
  115. alita_sdk/runtime/toolkits/__init__.py +31 -0
  116. alita_sdk/runtime/toolkits/application.py +29 -10
  117. alita_sdk/runtime/toolkits/artifact.py +20 -11
  118. alita_sdk/runtime/toolkits/datasource.py +13 -6
  119. alita_sdk/runtime/toolkits/mcp.py +783 -0
  120. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  121. alita_sdk/runtime/toolkits/planning.py +178 -0
  122. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  123. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  124. alita_sdk/runtime/toolkits/tools.py +356 -69
  125. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  126. alita_sdk/runtime/tools/__init__.py +10 -3
  127. alita_sdk/runtime/tools/application.py +27 -6
  128. alita_sdk/runtime/tools/artifact.py +511 -28
  129. alita_sdk/runtime/tools/data_analysis.py +183 -0
  130. alita_sdk/runtime/tools/function.py +67 -35
  131. alita_sdk/runtime/tools/graph.py +10 -4
  132. alita_sdk/runtime/tools/image_generation.py +148 -46
  133. alita_sdk/runtime/tools/llm.py +1003 -128
  134. alita_sdk/runtime/tools/loop.py +3 -1
  135. alita_sdk/runtime/tools/loop_output.py +3 -1
  136. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  137. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  138. alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
  139. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  140. alita_sdk/runtime/tools/planning/models.py +246 -0
  141. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  142. alita_sdk/runtime/tools/router.py +2 -4
  143. alita_sdk/runtime/tools/sandbox.py +65 -48
  144. alita_sdk/runtime/tools/skill_router.py +776 -0
  145. alita_sdk/runtime/tools/tool.py +3 -1
  146. alita_sdk/runtime/tools/vectorstore.py +9 -3
  147. alita_sdk/runtime/tools/vectorstore_base.py +70 -14
  148. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  149. alita_sdk/runtime/utils/constants.py +5 -1
  150. alita_sdk/runtime/utils/mcp_client.py +492 -0
  151. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  152. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  153. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  154. alita_sdk/runtime/utils/serialization.py +155 -0
  155. alita_sdk/runtime/utils/streamlit.py +40 -13
  156. alita_sdk/runtime/utils/toolkit_utils.py +30 -9
  157. alita_sdk/runtime/utils/utils.py +36 -0
  158. alita_sdk/tools/__init__.py +134 -35
  159. alita_sdk/tools/ado/repos/__init__.py +51 -32
  160. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  161. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  162. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  163. alita_sdk/tools/ado/utils.py +1 -18
  164. alita_sdk/tools/ado/wiki/__init__.py +25 -12
  165. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  166. alita_sdk/tools/ado/work_item/__init__.py +26 -13
  167. alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
  168. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  169. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  170. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  171. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  172. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  173. alita_sdk/tools/base/tool.py +5 -1
  174. alita_sdk/tools/base_indexer_toolkit.py +271 -84
  175. alita_sdk/tools/bitbucket/__init__.py +17 -11
  176. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  177. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  178. alita_sdk/tools/browser/__init__.py +5 -4
  179. alita_sdk/tools/carrier/__init__.py +5 -6
  180. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  181. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  182. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  183. alita_sdk/tools/chunkers/__init__.py +3 -1
  184. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  185. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  186. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  187. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  188. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  189. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  190. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  191. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  192. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  193. alita_sdk/tools/code/linter/__init__.py +10 -8
  194. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  195. alita_sdk/tools/code/sonar/__init__.py +11 -8
  196. alita_sdk/tools/code_indexer_toolkit.py +82 -22
  197. alita_sdk/tools/confluence/__init__.py +22 -16
  198. alita_sdk/tools/confluence/api_wrapper.py +107 -30
  199. alita_sdk/tools/confluence/loader.py +14 -2
  200. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  201. alita_sdk/tools/elastic/__init__.py +11 -8
  202. alita_sdk/tools/elitea_base.py +493 -30
  203. alita_sdk/tools/figma/__init__.py +58 -11
  204. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  205. alita_sdk/tools/figma/figma_client.py +73 -0
  206. alita_sdk/tools/figma/toon_tools.py +2748 -0
  207. alita_sdk/tools/github/__init__.py +14 -15
  208. alita_sdk/tools/github/github_client.py +224 -100
  209. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  210. alita_sdk/tools/github/schemas.py +14 -5
  211. alita_sdk/tools/github/tool.py +5 -1
  212. alita_sdk/tools/github/tool_prompts.py +9 -22
  213. alita_sdk/tools/gitlab/__init__.py +16 -11
  214. alita_sdk/tools/gitlab/api_wrapper.py +218 -48
  215. alita_sdk/tools/gitlab_org/__init__.py +10 -9
  216. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  217. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  218. alita_sdk/tools/google/bigquery/tool.py +5 -1
  219. alita_sdk/tools/google_places/__init__.py +11 -8
  220. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  221. alita_sdk/tools/jira/__init__.py +17 -10
  222. alita_sdk/tools/jira/api_wrapper.py +92 -41
  223. alita_sdk/tools/keycloak/__init__.py +11 -8
  224. alita_sdk/tools/localgit/__init__.py +9 -3
  225. alita_sdk/tools/localgit/local_git.py +62 -54
  226. alita_sdk/tools/localgit/tool.py +5 -1
  227. alita_sdk/tools/memory/__init__.py +12 -4
  228. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  229. alita_sdk/tools/ocr/__init__.py +11 -8
  230. alita_sdk/tools/openapi/__init__.py +491 -106
  231. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  232. alita_sdk/tools/openapi/tool.py +20 -0
  233. alita_sdk/tools/pandas/__init__.py +20 -12
  234. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  235. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  236. alita_sdk/tools/postman/__init__.py +10 -9
  237. alita_sdk/tools/pptx/__init__.py +11 -10
  238. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  239. alita_sdk/tools/qtest/__init__.py +31 -11
  240. alita_sdk/tools/qtest/api_wrapper.py +2135 -86
  241. alita_sdk/tools/rally/__init__.py +10 -9
  242. alita_sdk/tools/rally/api_wrapper.py +1 -1
  243. alita_sdk/tools/report_portal/__init__.py +12 -8
  244. alita_sdk/tools/salesforce/__init__.py +10 -8
  245. alita_sdk/tools/servicenow/__init__.py +17 -15
  246. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  247. alita_sdk/tools/sharepoint/__init__.py +10 -7
  248. alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
  249. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  250. alita_sdk/tools/sharepoint/utils.py +8 -2
  251. alita_sdk/tools/slack/__init__.py +10 -7
  252. alita_sdk/tools/slack/api_wrapper.py +2 -2
  253. alita_sdk/tools/sql/__init__.py +12 -9
  254. alita_sdk/tools/testio/__init__.py +10 -7
  255. alita_sdk/tools/testrail/__init__.py +11 -10
  256. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  257. alita_sdk/tools/utils/__init__.py +9 -4
  258. alita_sdk/tools/utils/content_parser.py +103 -18
  259. alita_sdk/tools/utils/text_operations.py +410 -0
  260. alita_sdk/tools/utils/tool_prompts.py +79 -0
  261. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
  262. alita_sdk/tools/xray/__init__.py +13 -9
  263. alita_sdk/tools/yagmail/__init__.py +9 -3
  264. alita_sdk/tools/zephyr/__init__.py +10 -7
  265. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
  266. alita_sdk/tools/zephyr_essential/__init__.py +10 -7
  267. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  268. alita_sdk/tools/zephyr_essential/client.py +2 -2
  269. alita_sdk/tools/zephyr_scale/__init__.py +11 -8
  270. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  271. alita_sdk/tools/zephyr_squad/__init__.py +10 -7
  272. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
  273. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  274. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  275. alita_sdk-0.3.379.dist-info/RECORD +0 -360
  276. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  277. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  278. {alita_sdk-0.3.379.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="The file query string")),
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 number from the diff for a changed file")),
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:
@@ -117,7 +132,11 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
117
132
 
118
133
  @model_validator(mode='before')
119
134
  @classmethod
120
- def validate_toolkit(cls, values: Dict) -> Dict:
135
+ def validate_toolkit_before(cls, values: Dict) -> Dict:
136
+ return super().validate_toolkit(values)
137
+
138
+ @model_validator(mode='after')
139
+ def validate_toolkit(self):
121
140
  try:
122
141
  import gitlab
123
142
  except ImportError:
@@ -125,17 +144,17 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
125
144
  "python-gitlab is not installed. "
126
145
  "Please install it with `pip install python-gitlab`"
127
146
  )
128
- values['repository'] = cls._sanitize_url(values['repository'])
147
+ self.repository = self._sanitize_url(self.repository)
129
148
  g = gitlab.Gitlab(
130
- url=cls._sanitize_url(values['url']),
131
- private_token=values['private_token'],
149
+ url=self._sanitize_url(self.url),
150
+ private_token=self.private_token.get_secret_value(),
132
151
  keep_base_url=True,
133
152
  )
134
153
 
135
154
  g.auth()
136
- cls._git = g
137
- cls._active_branch = values.get('branch')
138
- return super().validate_toolkit(values)
155
+ self._git = g
156
+ self._active_branch = self.branch
157
+ return self
139
158
 
140
159
  @property
141
160
  def repo_instance(self):
@@ -211,8 +230,22 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
211
230
  except Exception as e:
212
231
  return f"Unable to get commit hash for {file_path} due to error:\n{e}"
213
232
 
214
- def _read_file(self, file_path: str, branch: str):
215
- return self.read_file(file_path, branch)
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))
216
249
 
217
250
  def create_branch(self, branch_name: str) -> str:
218
251
  try:
@@ -299,7 +332,30 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
299
332
  except Exception as e:
300
333
  return "Unable to make comment due to error:\n" + str(e)
301
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
+
302
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
303
359
  try:
304
360
  self.set_active_branch(branch)
305
361
  self.repo_instance.files.get(file_path, branch)
@@ -316,50 +372,126 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
316
372
  return "Created file " + file_path
317
373
 
318
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
319
377
  self.set_active_branch(branch)
320
378
  file = self.repo_instance.files.get(file_path, branch)
321
379
  return parse_file_content(file_name=file_path,
322
380
  file_content=file.decode(),
323
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)}")
324
442
 
325
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
+ """
326
466
  if branch == self.branch:
327
467
  return (
328
- "You're attempting to commit to the directly"
468
+ "You're attempting to commit directly "
329
469
  f"to the {self.branch} branch, which is protected. "
330
470
  "Please create a new branch and try again."
331
471
  )
332
472
  try:
333
- file_path: str = file_query.split("\n")[0]
334
- self.set_active_branch(branch)
335
- file_content = self.read_file(file_path, branch)
336
- updated_file_content = file_content
337
- for old, new in self.extract_old_new_pairs(file_query):
338
- if not old.strip():
339
- continue
340
- updated_file_content = updated_file_content.replace(old, new)
341
-
342
- 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:
343
482
  return (
344
- "File content was not updated because old content was not found or empty."
345
- "It may be helpful to use the read_file action to get "
346
- "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."
347
485
  )
348
486
 
349
- commit = {
350
- "branch": branch,
351
- "commit_message": "Create " + file_path,
352
- "actions": [
353
- {
354
- "action": "update",
355
- "file_path": file_path,
356
- "content": updated_file_content,
357
- }
358
- ],
359
- }
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)
360
494
 
361
- self.repo_instance.commits.create(commit)
362
- return "Updated file " + file_path
363
495
  except Exception as e:
364
496
  return "Unable to update file due to error:\n" + str(e)
365
497
 
@@ -411,10 +543,41 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
411
543
  return res
412
544
 
413
545
  def create_pr_change_comment(self, pr_number: int, file_path: str, line_number: int, comment: str) -> str:
414
- mr = self.repo_instance.mergerequests.get(pr_number)
415
- position = {"position_type": "text", "new_path": file_path, "new_line": line_number}
416
- mr.discussions.create({"body": comment, "position": position})
417
- return "Comment added"
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}")
418
581
 
419
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):
420
583
  params = {}
@@ -441,6 +604,7 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
441
604
  ]
442
605
 
443
606
  @extend_with_parent_available_tools
607
+ @extend_with_file_operations
444
608
  def get_available_tools(self):
445
609
  return [
446
610
  {
@@ -491,6 +655,12 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
491
655
  "description": self.comment_on_issue.__doc__ or "Comment on an issue in the repository.",
492
656
  "args_schema": CommentOnIssueModel,
493
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
+ },
494
664
  {
495
665
  "name": "create_file",
496
666
  "ref": self.create_file,
@@ -506,7 +676,7 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
506
676
  {
507
677
  "name": "update_file",
508
678
  "ref": self.update_file,
509
- "description": self.update_file.__doc__ or "Update the contents of a file in the repository.",
679
+ "description": EDIT_FILE_DESCRIPTION,
510
680
  "args_schema": UpdateFileModel,
511
681
  },
512
682
  {
@@ -536,7 +706,7 @@ class GitLabAPIWrapper(CodeIndexerToolkit):
536
706
  {
537
707
  "name": "create_pr_change_comment",
538
708
  "ref": self.create_pr_change_comment,
539
- "description": "Create a comment on a pull request change.",
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.",
540
710
  "args_schema": CreatePRChangeCommentModel,
541
711
  },
542
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, TOOLKIT_SPLITTER, get_max_toolkit_length
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,16 +23,12 @@ 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
- name=(str, Field(description="Toolkit name", json_schema_extra={'toolkit_name': True,
34
- 'max_toolkit_length': AlitaGitlabSpaceToolkit.toolkit_max_length})),
35
32
  gitlab_configuration=(GitlabConfiguration, Field(description="GitLab configuration",
36
33
  json_schema_extra={
37
34
  'configuration_types': ['gitlab']})),
@@ -63,18 +60,22 @@ class AlitaGitlabSpaceToolkit(BaseToolkit):
63
60
  **kwargs['gitlab_configuration'],
64
61
  }
65
62
  gitlab_wrapper = GitLabWorkspaceAPIWrapper(**wrapper_payload)
66
- prefix = clean_string(toolkit_name, AlitaGitlabSpaceToolkit.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
67
63
  available_tools = gitlab_wrapper.get_available_tools()
68
64
  tools = []
69
65
  for tool in available_tools:
70
66
  if selected_tools:
71
67
  if tool["name"] not in selected_tools:
72
68
  continue
69
+ description = tool["description"]
70
+ if toolkit_name:
71
+ description = f"Toolkit: {toolkit_name}\n{description}"
72
+ description = description[:1000]
73
73
  tools.append(BaseAction(
74
74
  api_wrapper=gitlab_wrapper,
75
- name=prefix + tool['name'],
76
- description=tool["description"],
77
- 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"]}
78
79
  ))
79
80
  return cls(tools=tools)
80
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="File path followed by the old and new contents")),
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 update_file(self, file_path: str, update_query: str, branch: str, repository: Optional[str] = None) -> str:
375
- """Updates a file with new content.
376
- Parameters:
377
- branch (str): The name of the branch where update the file.
378
- update_query(str): Contains file contents.
379
- The old file contents is wrapped in OLD <<<< and >>>> OLD
380
- The new file contents is wrapped in NEW <<<< and >>>> NEW
381
- For example:
382
- /test/hello.txt
383
- OLD <<<<
384
- Hello Earth!
385
- >>>> OLD
386
- NEW <<<<
387
- Hello Mars!
388
- >>>> NEW
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
- file_content = self.read_file(file_path, branch, repository)
393
- updated_file_content = file_content
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 " + file_path,
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": updated_file_content,
416
+ "content": content,
412
417
  }
413
418
  ],
414
419
  }
415
420
  repo_instance.commits.create(commit)
416
- return "Updated file " + file_path
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": self.update_file.__doc__ or "Updates a file in the GitLab repository.",
643
+ "description": EDIT_FILE_DESCRIPTION,
645
644
  "args_schema": GitLabUpdateFile,
646
645
  "ref": self.update_file,
647
646
  },