alita-sdk 0.3.257__py3-none-any.whl → 0.3.562__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 +155 -0
  6. alita_sdk/cli/agent_loader.py +215 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3601 -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/toolkit.py +327 -0
  23. alita_sdk/cli/toolkit_loader.py +85 -0
  24. alita_sdk/cli/tools/__init__.py +43 -0
  25. alita_sdk/cli/tools/approval.py +224 -0
  26. alita_sdk/cli/tools/filesystem.py +1751 -0
  27. alita_sdk/cli/tools/planning.py +389 -0
  28. alita_sdk/cli/tools/terminal.py +414 -0
  29. alita_sdk/community/__init__.py +72 -12
  30. alita_sdk/community/inventory/__init__.py +236 -0
  31. alita_sdk/community/inventory/config.py +257 -0
  32. alita_sdk/community/inventory/enrichment.py +2137 -0
  33. alita_sdk/community/inventory/extractors.py +1469 -0
  34. alita_sdk/community/inventory/ingestion.py +3172 -0
  35. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  36. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  37. alita_sdk/community/inventory/parsers/base.py +295 -0
  38. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  39. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  40. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  41. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  42. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  43. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  44. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  45. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  46. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  47. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  48. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  49. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  50. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  51. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  52. alita_sdk/community/inventory/patterns/loader.py +348 -0
  53. alita_sdk/community/inventory/patterns/registry.py +198 -0
  54. alita_sdk/community/inventory/presets.py +535 -0
  55. alita_sdk/community/inventory/retrieval.py +1403 -0
  56. alita_sdk/community/inventory/toolkit.py +173 -0
  57. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  58. alita_sdk/community/inventory/visualize.py +1370 -0
  59. alita_sdk/configurations/__init__.py +11 -0
  60. alita_sdk/configurations/ado.py +148 -2
  61. alita_sdk/configurations/azure_search.py +1 -1
  62. alita_sdk/configurations/bigquery.py +1 -1
  63. alita_sdk/configurations/bitbucket.py +94 -2
  64. alita_sdk/configurations/browser.py +18 -0
  65. alita_sdk/configurations/carrier.py +19 -0
  66. alita_sdk/configurations/confluence.py +130 -1
  67. alita_sdk/configurations/delta_lake.py +1 -1
  68. alita_sdk/configurations/figma.py +76 -5
  69. alita_sdk/configurations/github.py +65 -1
  70. alita_sdk/configurations/gitlab.py +81 -0
  71. alita_sdk/configurations/google_places.py +17 -0
  72. alita_sdk/configurations/jira.py +103 -0
  73. alita_sdk/configurations/openapi.py +111 -0
  74. alita_sdk/configurations/postman.py +1 -1
  75. alita_sdk/configurations/qtest.py +72 -3
  76. alita_sdk/configurations/report_portal.py +115 -0
  77. alita_sdk/configurations/salesforce.py +19 -0
  78. alita_sdk/configurations/service_now.py +1 -12
  79. alita_sdk/configurations/sharepoint.py +167 -0
  80. alita_sdk/configurations/sonar.py +18 -0
  81. alita_sdk/configurations/sql.py +20 -0
  82. alita_sdk/configurations/testio.py +101 -0
  83. alita_sdk/configurations/testrail.py +88 -0
  84. alita_sdk/configurations/xray.py +94 -1
  85. alita_sdk/configurations/zephyr_enterprise.py +94 -1
  86. alita_sdk/configurations/zephyr_essential.py +95 -0
  87. alita_sdk/runtime/clients/artifact.py +21 -4
  88. alita_sdk/runtime/clients/client.py +458 -67
  89. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  90. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  91. alita_sdk/runtime/clients/sandbox_client.py +352 -0
  92. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  93. alita_sdk/runtime/langchain/assistant.py +183 -43
  94. alita_sdk/runtime/langchain/constants.py +647 -1
  95. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  96. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
  97. alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
  100. alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
  101. alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
  102. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
  103. alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
  104. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
  105. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
  106. alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
  107. alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
  108. alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
  109. alita_sdk/runtime/langchain/langraph_agent.py +407 -92
  110. alita_sdk/runtime/langchain/utils.py +102 -8
  111. alita_sdk/runtime/llms/preloaded.py +2 -6
  112. alita_sdk/runtime/models/mcp_models.py +61 -0
  113. alita_sdk/runtime/skills/__init__.py +91 -0
  114. alita_sdk/runtime/skills/callbacks.py +498 -0
  115. alita_sdk/runtime/skills/discovery.py +540 -0
  116. alita_sdk/runtime/skills/executor.py +610 -0
  117. alita_sdk/runtime/skills/input_builder.py +371 -0
  118. alita_sdk/runtime/skills/models.py +330 -0
  119. alita_sdk/runtime/skills/registry.py +355 -0
  120. alita_sdk/runtime/skills/skill_runner.py +330 -0
  121. alita_sdk/runtime/toolkits/__init__.py +28 -0
  122. alita_sdk/runtime/toolkits/application.py +14 -4
  123. alita_sdk/runtime/toolkits/artifact.py +24 -9
  124. alita_sdk/runtime/toolkits/datasource.py +13 -6
  125. alita_sdk/runtime/toolkits/mcp.py +780 -0
  126. alita_sdk/runtime/toolkits/planning.py +178 -0
  127. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  128. alita_sdk/runtime/toolkits/subgraph.py +11 -6
  129. alita_sdk/runtime/toolkits/tools.py +314 -70
  130. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  131. alita_sdk/runtime/tools/__init__.py +24 -0
  132. alita_sdk/runtime/tools/application.py +16 -4
  133. alita_sdk/runtime/tools/artifact.py +367 -33
  134. alita_sdk/runtime/tools/data_analysis.py +183 -0
  135. alita_sdk/runtime/tools/function.py +100 -4
  136. alita_sdk/runtime/tools/graph.py +81 -0
  137. alita_sdk/runtime/tools/image_generation.py +218 -0
  138. alita_sdk/runtime/tools/llm.py +1013 -177
  139. alita_sdk/runtime/tools/loop.py +3 -1
  140. alita_sdk/runtime/tools/loop_output.py +3 -1
  141. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  142. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  143. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  144. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  145. alita_sdk/runtime/tools/planning/models.py +246 -0
  146. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  147. alita_sdk/runtime/tools/router.py +2 -1
  148. alita_sdk/runtime/tools/sandbox.py +375 -0
  149. alita_sdk/runtime/tools/skill_router.py +776 -0
  150. alita_sdk/runtime/tools/tool.py +3 -1
  151. alita_sdk/runtime/tools/vectorstore.py +69 -65
  152. alita_sdk/runtime/tools/vectorstore_base.py +163 -90
  153. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  154. alita_sdk/runtime/utils/mcp_client.py +492 -0
  155. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  156. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  157. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  158. alita_sdk/runtime/utils/streamlit.py +41 -14
  159. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  160. alita_sdk/runtime/utils/utils.py +48 -0
  161. alita_sdk/tools/__init__.py +135 -37
  162. alita_sdk/tools/ado/__init__.py +2 -2
  163. alita_sdk/tools/ado/repos/__init__.py +15 -19
  164. alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
  165. alita_sdk/tools/ado/test_plan/__init__.py +26 -8
  166. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
  167. alita_sdk/tools/ado/wiki/__init__.py +27 -12
  168. alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
  169. alita_sdk/tools/ado/work_item/__init__.py +27 -12
  170. alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
  171. alita_sdk/tools/advanced_jira_mining/__init__.py +12 -8
  172. alita_sdk/tools/aws/delta_lake/__init__.py +14 -11
  173. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  174. alita_sdk/tools/azure_ai/search/__init__.py +13 -8
  175. alita_sdk/tools/base/tool.py +5 -1
  176. alita_sdk/tools/base_indexer_toolkit.py +454 -110
  177. alita_sdk/tools/bitbucket/__init__.py +27 -19
  178. alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
  179. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
  180. alita_sdk/tools/browser/__init__.py +41 -16
  181. alita_sdk/tools/browser/crawler.py +3 -1
  182. alita_sdk/tools/browser/utils.py +15 -6
  183. alita_sdk/tools/carrier/__init__.py +18 -17
  184. alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
  185. alita_sdk/tools/carrier/excel_reporter.py +8 -4
  186. alita_sdk/tools/chunkers/__init__.py +3 -1
  187. alita_sdk/tools/chunkers/code/codeparser.py +1 -1
  188. alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
  189. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  190. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  191. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  192. alita_sdk/tools/cloud/aws/__init__.py +11 -7
  193. alita_sdk/tools/cloud/azure/__init__.py +11 -7
  194. alita_sdk/tools/cloud/gcp/__init__.py +11 -7
  195. alita_sdk/tools/cloud/k8s/__init__.py +11 -7
  196. alita_sdk/tools/code/linter/__init__.py +9 -8
  197. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  198. alita_sdk/tools/code/sonar/__init__.py +20 -13
  199. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  200. alita_sdk/tools/confluence/__init__.py +21 -14
  201. alita_sdk/tools/confluence/api_wrapper.py +197 -58
  202. alita_sdk/tools/confluence/loader.py +14 -2
  203. alita_sdk/tools/custom_open_api/__init__.py +11 -5
  204. alita_sdk/tools/elastic/__init__.py +10 -8
  205. alita_sdk/tools/elitea_base.py +546 -64
  206. alita_sdk/tools/figma/__init__.py +11 -8
  207. alita_sdk/tools/figma/api_wrapper.py +352 -153
  208. alita_sdk/tools/github/__init__.py +17 -17
  209. alita_sdk/tools/github/api_wrapper.py +9 -26
  210. alita_sdk/tools/github/github_client.py +81 -12
  211. alita_sdk/tools/github/schemas.py +2 -1
  212. alita_sdk/tools/github/tool.py +5 -1
  213. alita_sdk/tools/gitlab/__init__.py +18 -13
  214. alita_sdk/tools/gitlab/api_wrapper.py +224 -80
  215. alita_sdk/tools/gitlab_org/__init__.py +13 -10
  216. alita_sdk/tools/google/bigquery/__init__.py +13 -13
  217. alita_sdk/tools/google/bigquery/tool.py +5 -1
  218. alita_sdk/tools/google_places/__init__.py +20 -11
  219. alita_sdk/tools/jira/__init__.py +21 -11
  220. alita_sdk/tools/jira/api_wrapper.py +315 -168
  221. alita_sdk/tools/keycloak/__init__.py +10 -8
  222. alita_sdk/tools/localgit/__init__.py +8 -3
  223. alita_sdk/tools/localgit/local_git.py +62 -54
  224. alita_sdk/tools/localgit/tool.py +5 -1
  225. alita_sdk/tools/memory/__init__.py +38 -14
  226. alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
  227. alita_sdk/tools/ocr/__init__.py +10 -8
  228. alita_sdk/tools/openapi/__init__.py +281 -108
  229. alita_sdk/tools/openapi/api_wrapper.py +883 -0
  230. alita_sdk/tools/openapi/tool.py +20 -0
  231. alita_sdk/tools/pandas/__init__.py +18 -11
  232. alita_sdk/tools/pandas/api_wrapper.py +40 -45
  233. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  234. alita_sdk/tools/postman/__init__.py +10 -11
  235. alita_sdk/tools/postman/api_wrapper.py +19 -8
  236. alita_sdk/tools/postman/postman_analysis.py +8 -1
  237. alita_sdk/tools/pptx/__init__.py +10 -10
  238. alita_sdk/tools/qtest/__init__.py +21 -14
  239. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  240. alita_sdk/tools/rally/__init__.py +12 -10
  241. alita_sdk/tools/report_portal/__init__.py +22 -16
  242. alita_sdk/tools/salesforce/__init__.py +21 -16
  243. alita_sdk/tools/servicenow/__init__.py +20 -16
  244. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  245. alita_sdk/tools/sharepoint/__init__.py +16 -14
  246. alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
  247. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  248. alita_sdk/tools/sharepoint/utils.py +8 -2
  249. alita_sdk/tools/slack/__init__.py +11 -7
  250. alita_sdk/tools/sql/__init__.py +21 -19
  251. alita_sdk/tools/sql/api_wrapper.py +71 -23
  252. alita_sdk/tools/testio/__init__.py +20 -13
  253. alita_sdk/tools/testrail/__init__.py +12 -11
  254. alita_sdk/tools/testrail/api_wrapper.py +214 -46
  255. alita_sdk/tools/utils/__init__.py +28 -4
  256. alita_sdk/tools/utils/content_parser.py +182 -62
  257. alita_sdk/tools/utils/text_operations.py +254 -0
  258. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
  259. alita_sdk/tools/xray/__init__.py +17 -14
  260. alita_sdk/tools/xray/api_wrapper.py +58 -113
  261. alita_sdk/tools/yagmail/__init__.py +8 -3
  262. alita_sdk/tools/zephyr/__init__.py +11 -7
  263. alita_sdk/tools/zephyr_enterprise/__init__.py +15 -9
  264. alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
  265. alita_sdk/tools/zephyr_essential/__init__.py +15 -10
  266. alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
  267. alita_sdk/tools/zephyr_essential/client.py +6 -4
  268. alita_sdk/tools/zephyr_scale/__init__.py +12 -8
  269. alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
  270. alita_sdk/tools/zephyr_squad/__init__.py +11 -7
  271. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/METADATA +184 -37
  272. alita_sdk-0.3.562.dist-info/RECORD +450 -0
  273. alita_sdk-0.3.562.dist-info/entry_points.txt +2 -0
  274. alita_sdk/tools/bitbucket/tools.py +0 -304
  275. alita_sdk-0.3.257.dist-info/RECORD +0 -343
  276. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/WHEEL +0 -0
  277. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/licenses/LICENSE +0 -0
  278. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,17 @@
1
1
  # api_wrapper.py
2
- from typing import Any, Dict, List, Optional
3
2
  import fnmatch
4
- from alita_sdk.tools.elitea_base import BaseCodeToolApiWrapper
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from gitlab import GitlabGetError
6
+ from langchain_core.tools import ToolException
5
7
  from pydantic import create_model, Field, model_validator, SecretStr, PrivateAttr
6
8
 
9
+ from ..code_indexer_toolkit import CodeIndexerToolkit
10
+ from ..utils.available_tools_decorator import extend_with_parent_available_tools
11
+ from ..elitea_base import extend_with_file_operations, BaseCodeToolApiWrapper
12
+ from ..utils.content_parser import parse_file_content
13
+ from .utils import get_position
14
+
7
15
  AppendFileModel = create_model(
8
16
  "AppendFileModel",
9
17
  file_path=(str, Field(description="The path of the file")),
@@ -29,7 +37,7 @@ ReadFileModel = create_model(
29
37
  )
30
38
  UpdateFileModel = create_model(
31
39
  "UpdateFileModel",
32
- 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")),
33
41
  branch=(str, Field(description="The branch to update the file in")),
34
42
  )
35
43
  CommentOnIssueModel = create_model(
@@ -84,9 +92,9 @@ GetPRChangesModel = create_model(
84
92
  CreatePRChangeCommentModel = create_model(
85
93
  "CreatePRChangeCommentModel",
86
94
  pr_number=(int, Field(description="GitLab Merge Request (Pull Request) number")),
87
- file_path=(str, Field(description="File path of the changed file")),
88
- line_number=(int, Field(description="Line number from the diff for a changed file")),
89
- comment=(str, Field(description="Comment content")),
95
+ file_path=(str, Field(description="File path of the changed file as shown in the diff")),
96
+ 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.")),
97
+ comment=(str, Field(description="Comment content to add to the specific line")),
90
98
  )
91
99
  GetCommitsModel = create_model(
92
100
  "GetCommitsModel",
@@ -97,53 +105,67 @@ GetCommitsModel = create_model(
97
105
  author=(Optional[str], Field(description="Author name", default=None)),
98
106
  )
99
107
 
100
- class GitLabAPIWrapper(BaseCodeToolApiWrapper):
108
+ class GitLabAPIWrapper(CodeIndexerToolkit):
101
109
  url: str
102
110
  repository: str
103
111
  private_token: SecretStr
104
112
  branch: Optional[str] = 'main'
105
113
  _git: Any = PrivateAttr()
106
- _repo_instance: Any = PrivateAttr()
107
114
  _active_branch: Any = PrivateAttr()
108
-
109
- llm: Optional[Any] = None
110
- # Alita instance
111
- alita: Optional[Any] = None
112
-
113
- # Vector store configuration
114
- connection_string: Optional[SecretStr] = None
115
- collection_name: Optional[str] = None
116
- doctype: Optional[str] = 'code'
117
- embedding_model: Optional[str] = "HuggingFaceEmbeddings"
118
- embedding_model_params: Optional[Dict[str, Any]] = {"model_name": "sentence-transformers/all-MiniLM-L6-v2"}
119
- vectorstore_type: Optional[str] = "PGVector"
115
+
116
+ # Import file operation methods from BaseCodeToolApiWrapper
117
+ read_file_chunk = BaseCodeToolApiWrapper.read_file_chunk
118
+ read_multiple_files = BaseCodeToolApiWrapper.read_multiple_files
119
+ search_file = BaseCodeToolApiWrapper.search_file
120
+ edit_file = BaseCodeToolApiWrapper.edit_file
121
+
122
+ @staticmethod
123
+ def _sanitize_url(url: str) -> str:
124
+ """Remove trailing slash from URL if present."""
125
+ return url.rstrip('/') if url else url
120
126
 
121
127
  @model_validator(mode='before')
122
128
  @classmethod
123
- def validate_toolkit(cls, values: Dict) -> Dict:
129
+ def validate_toolkit_before(cls, values: Dict) -> Dict:
130
+ return super().validate_toolkit(values)
131
+
132
+ @model_validator(mode='after')
133
+ def validate_toolkit(self):
124
134
  try:
125
- import gitlab
135
+ import gitlab
126
136
  except ImportError:
127
137
  raise ImportError(
128
138
  "python-gitlab is not installed. "
129
139
  "Please install it with `pip install python-gitlab`"
130
140
  )
131
-
141
+ self.repository = self._sanitize_url(self.repository)
132
142
  g = gitlab.Gitlab(
133
- url=values['url'],
134
- private_token=values['private_token'],
143
+ url=self._sanitize_url(self.url),
144
+ private_token=self.private_token.get_secret_value(),
135
145
  keep_base_url=True,
136
146
  )
137
147
 
138
148
  g.auth()
139
- cls._repo_instance = g.projects.get(values.get('repository'))
140
- cls._git = g
141
- cls._active_branch = values.get('branch')
142
- return values
149
+ self._git = g
150
+ self._active_branch = self.branch
151
+ return self
152
+
153
+ @property
154
+ def repo_instance(self):
155
+ if not hasattr(self, "_repo_instance") or self._repo_instance is None:
156
+ try:
157
+ if self._git and self.repository:
158
+ self._repo_instance = self._git.projects.get(self.repository)
159
+ else:
160
+ self._repo_instance = None
161
+ except Exception as e:
162
+ # Only raise when accessed, not during initialization
163
+ raise ToolException(e)
164
+ return self._repo_instance
143
165
 
144
166
  def set_active_branch(self, branch_name: str) -> str:
145
167
  self._active_branch = branch_name
146
- self._repo_instance.default_branch = branch_name
168
+ self.repo_instance.default_branch = branch_name
147
169
  return f"Active branch set to {branch_name}"
148
170
 
149
171
  def list_branches_in_repo(self, limit: Optional[int] = 20, branch_wildcard: Optional[str] = None) -> List[str]:
@@ -158,7 +180,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
158
180
  List[str]: List containing names of branches
159
181
  """
160
182
  try:
161
- branches = self._repo_instance.branches.list(get_all=True)
183
+ branches = self.repo_instance.branches.list(get_all=True)
162
184
 
163
185
  if branch_wildcard:
164
186
  branches = [branch for branch in branches if fnmatch.fnmatch(branch.name, branch_wildcard)]
@@ -185,7 +207,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
185
207
 
186
208
  def _get_all_files(self, path: str = None, recursive: bool = True, branch: str = None):
187
209
  branch = branch if branch else self._active_branch
188
- return self._repo_instance.repository_tree(path=path, ref=branch, recursive=recursive, all=True)
210
+ return self.repo_instance.repository_tree(path=path, ref=branch, recursive=recursive, all=True)
189
211
 
190
212
  # overrided for indexer
191
213
  def _get_files(self, path: str = None, recursive: bool = True, branch: str = None):
@@ -197,17 +219,31 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
197
219
  Get the commit hash of a file in a specific branch.
198
220
  """
199
221
  try:
200
- file = self._repo_instance.files.get(file_path, branch)
222
+ file = self.repo_instance.files.get(file_path, branch)
201
223
  return file.commit_id
202
224
  except Exception as e:
203
225
  return f"Unable to get commit hash for {file_path} due to error:\n{e}"
204
226
 
205
- def _read_file(self, file_path: str, branch: str):
227
+ def _read_file(self, file_path: str, branch: str, **kwargs):
228
+ """
229
+ Read a file from specified branch with optional partial read support.
230
+
231
+ Parameters:
232
+ file_path: the file path
233
+ branch: the branch to read the file from
234
+ **kwargs: Additional parameters (offset, limit, head, tail) - currently ignored,
235
+ partial read handled client-side by base class methods
236
+
237
+ Returns:
238
+ File content as string
239
+ """
240
+ # Default to active branch if branch is None, consistent with other methods
241
+ branch = branch if branch else self._active_branch
206
242
  return self.read_file(file_path, branch)
207
243
 
208
244
  def create_branch(self, branch_name: str) -> str:
209
245
  try:
210
- self._repo_instance.branches.create(
246
+ self.repo_instance.branches.create(
211
247
  {
212
248
  'branch': branch_name,
213
249
  'ref': self._active_branch,
@@ -230,7 +266,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
230
266
  return parsed
231
267
 
232
268
  def get_issues(self) -> str:
233
- issues = self._repo_instance.issues.list(state="opened")
269
+ issues = self.repo_instance.issues.list(state="opened")
234
270
  if len(issues) > 0:
235
271
  parsed_issues = self.parse_issues(issues)
236
272
  parsed_issues_str = (
@@ -241,7 +277,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
241
277
  return "No open issues available"
242
278
 
243
279
  def get_issue(self, issue_number: int) -> Dict[str, Any]:
244
- issue = self._repo_instance.issues.get(issue_number)
280
+ issue = self.repo_instance.issues.get(issue_number)
245
281
  page = 0
246
282
  comments: List[dict] = []
247
283
  while len(comments) <= 10:
@@ -267,7 +303,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
267
303
  commits are already in the {self.branch} branch"""
268
304
  else:
269
305
  try:
270
- pr = self._repo_instance.mergerequests.create(
306
+ pr = self.repo_instance.mergerequests.create(
271
307
  {
272
308
  "source_branch": branch,
273
309
  "target_branch": self.branch,
@@ -284,16 +320,18 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
284
320
  issue_number = int(comment_query.split("\n\n")[0])
285
321
  comment = comment_query[len(str(issue_number)) + 2 :]
286
322
  try:
287
- issue = self._repo_instance.issues.get(issue_number)
323
+ issue = self.repo_instance.issues.get(issue_number)
288
324
  issue.notes.create({"body": comment})
289
325
  return "Commented on issue " + str(issue_number)
290
326
  except Exception as e:
291
327
  return "Unable to make comment due to error:\n" + str(e)
292
328
 
293
329
  def create_file(self, file_path: str, file_contents: str, branch: str) -> str:
330
+ # Default to active branch if branch is None
331
+ branch = branch if branch else self._active_branch
294
332
  try:
295
333
  self.set_active_branch(branch)
296
- self._repo_instance.files.get(file_path, branch)
334
+ self.repo_instance.files.get(file_path, branch)
297
335
  return f"File already exists at {file_path}. Use update_file instead"
298
336
  except Exception:
299
337
  data = {
@@ -302,53 +340,126 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
302
340
  "file_path": file_path,
303
341
  "content": file_contents,
304
342
  }
305
- self._repo_instance.files.create(data)
343
+ self.repo_instance.files.create(data)
306
344
 
307
345
  return "Created file " + file_path
308
346
 
309
347
  def read_file(self, file_path: str, branch: str) -> str:
348
+ # Default to active branch if branch is None
349
+ branch = branch if branch else self._active_branch
310
350
  self.set_active_branch(branch)
311
- file = self._repo_instance.files.get(file_path, branch)
312
- return file.decode().decode("utf-8")
351
+ file = self.repo_instance.files.get(file_path, branch)
352
+ return parse_file_content(file_name=file_path,
353
+ file_content=file.decode(),
354
+ llm=self.llm)
355
+
356
+ def _write_file(
357
+ self,
358
+ file_path: str,
359
+ content: str,
360
+ branch: str = None,
361
+ commit_message: str = None
362
+ ) -> str:
363
+ """
364
+ Write content to a file (create or update).
365
+
366
+ Parameters:
367
+ file_path: Path to the file
368
+ content: New file content
369
+ branch: Branch name (uses active branch if None)
370
+ commit_message: Commit message
371
+
372
+ Returns:
373
+ Success message
374
+ """
375
+ try:
376
+ branch = branch or self._active_branch
377
+
378
+ if branch == self.branch:
379
+ raise ToolException(
380
+ f"Cannot commit directly to the {self.branch} branch. "
381
+ "Please create a new branch and try again."
382
+ )
383
+
384
+ self.set_active_branch(branch)
385
+
386
+ # Check if file exists
387
+ try:
388
+ self.repo_instance.files.get(file_path, branch)
389
+ # File exists, update it
390
+ commit = {
391
+ "branch": branch,
392
+ "commit_message": commit_message or f"Update {file_path}",
393
+ "actions": [
394
+ {
395
+ "action": "update",
396
+ "file_path": file_path,
397
+ "content": content,
398
+ }
399
+ ],
400
+ }
401
+ self.repo_instance.commits.create(commit)
402
+ return f"Updated file {file_path}"
403
+ except:
404
+ # File doesn't exist, create it
405
+ data = {
406
+ "branch": branch,
407
+ "commit_message": commit_message or f"Create {file_path}",
408
+ "file_path": file_path,
409
+ "content": content,
410
+ }
411
+ self.repo_instance.files.create(data)
412
+ return f"Created file {file_path}"
413
+ except Exception as e:
414
+ raise ToolException(f"Unable to write file {file_path}: {str(e)}")
313
415
 
314
416
  def update_file(self, file_query: str, branch: str) -> str:
417
+ """
418
+ Update file using edit_file functionality.
419
+
420
+ This method now delegates to edit_file which uses OLD/NEW markers.
421
+ For backwards compatibility, it extracts the file_path from the query.
422
+
423
+ Expected format:
424
+ file_path
425
+ OLD <<<<
426
+ old content
427
+ >>>> OLD
428
+ NEW <<<<
429
+ new content
430
+ >>>> NEW
431
+
432
+ Args:
433
+ file_query: File path on first line, followed by OLD/NEW markers
434
+ branch: Branch to update the file in
435
+
436
+ Returns:
437
+ Success or error message
438
+ """
315
439
  if branch == self.branch:
316
440
  return (
317
- "You're attempting to commit to the directly"
441
+ "You're attempting to commit directly "
318
442
  f"to the {self.branch} branch, which is protected. "
319
443
  "Please create a new branch and try again."
320
444
  )
321
445
  try:
322
- file_path: str = file_query.split("\n")[0]
323
- self.set_active_branch(branch)
324
- file_content = self.read_file(file_path, branch)
325
- updated_file_content = file_content
326
- for old, new in self.extract_old_new_pairs(file_query):
327
- if not old.strip():
328
- continue
329
- updated_file_content = updated_file_content.replace(old, new)
330
-
331
- if file_content == updated_file_content:
446
+ # Extract file path from first line
447
+ lines = file_query.split("\n", 1)
448
+ if len(lines) < 2:
332
449
  return (
333
- "File content was not updated because old content was not found or empty."
334
- "It may be helpful to use the read_file action to get "
335
- "the current file contents."
450
+ "Invalid file_query format. Expected:\n"
451
+ "file_path\n"
452
+ "OLD <<<< old content >>>> OLD\n"
453
+ "NEW <<<< new content >>>> NEW"
336
454
  )
337
455
 
338
- commit = {
339
- "branch": branch,
340
- "commit_message": "Create " + file_path,
341
- "actions": [
342
- {
343
- "action": "update",
344
- "file_path": file_path,
345
- "content": updated_file_content,
346
- }
347
- ],
348
- }
456
+ file_path = lines[0].strip()
457
+ edit_content = lines[1] if len(lines) > 1 else ""
458
+
459
+ # Delegate to edit_file method with appropriate commit message
460
+ commit_message = f"Update {file_path}"
461
+ return self.edit_file(file_path, edit_content, branch, commit_message)
349
462
 
350
- self._repo_instance.commits.create(commit)
351
- return "Updated file " + file_path
352
463
  except Exception as e:
353
464
  return "Unable to update file due to error:\n" + str(e)
354
465
 
@@ -377,7 +488,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
377
488
  ],
378
489
  }
379
490
 
380
- self._repo_instance.commits.create(commit)
491
+ self.repo_instance.commits.create(commit)
381
492
  return "Updated file " + file_path
382
493
  except Exception as e:
383
494
  return "Unable to update file due to error:\n" + str(e)
@@ -387,23 +498,54 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
387
498
  self.set_active_branch(branch)
388
499
  if not commit_message:
389
500
  commit_message = f"Delete {file_path}"
390
- self._repo_instance.files.delete(file_path, branch, commit_message)
501
+ self.repo_instance.files.delete(file_path, branch, commit_message)
391
502
  return f"Deleted file {file_path}"
392
503
  except Exception as e:
393
504
  return f"Unable to delete file due to error:\n{e}"
394
505
 
395
506
  def get_pr_changes(self, pr_number: int) -> str:
396
- mr = self._repo_instance.mergerequests.get(pr_number)
507
+ mr = self.repo_instance.mergerequests.get(pr_number)
397
508
  res = f"title: {mr.title}\ndescription: {mr.description}\n\n"
398
509
  for change in mr.changes()["changes"]:
399
510
  res += f"diff --git a/{change['old_path']} b/{change['new_path']}\n{change['diff']}\n"
400
511
  return res
401
512
 
402
513
  def create_pr_change_comment(self, pr_number: int, file_path: str, line_number: int, comment: str) -> str:
403
- mr = self._repo_instance.mergerequests.get(pr_number)
404
- position = {"position_type": "text", "new_path": file_path, "new_line": line_number}
405
- mr.discussions.create({"body": comment, "position": position})
406
- return "Comment added"
514
+ """
515
+ Create a comment on a specific line in a pull request (merge request) change in GitLab.
516
+
517
+ This method adds an inline comment to a specific line in the diff of a merge request.
518
+ The line_number parameter refers to the line index in the diff output (0-based),
519
+ not the line number in the original file.
520
+
521
+ **Important**: Use get_pr_changes first to see the diff and identify the correct
522
+ line index for commenting.
523
+
524
+ Parameters:
525
+ pr_number: GitLab Merge Request number
526
+ file_path: Path to the file being commented on (as shown in the diff)
527
+ line_number: Line index from the diff (0-based index)
528
+ comment: Comment text to add
529
+
530
+ Returns:
531
+ Success message or error description
532
+ """
533
+ try:
534
+ mr = self.repo_instance.mergerequests.get(pr_number)
535
+ except GitlabGetError as e:
536
+ if e.response_code == 404:
537
+ raise ToolException(f"Merge request number {pr_number} wasn't found: {e}")
538
+ raise ToolException(f"Error retrieving merge request {pr_number}: {e}")
539
+
540
+ try:
541
+ # Calculate proper position with SHA references and line mappings
542
+ position = get_position(file_path=file_path, line_number=line_number, mr=mr)
543
+
544
+ # Create discussion with the comment
545
+ mr.discussions.create({"body": comment, "position": position})
546
+ return f"Comment added successfully to line {line_number} in {file_path} on MR #{pr_number}"
547
+ except Exception as e:
548
+ raise ToolException(f"Failed to create comment on MR #{pr_number}: {e}")
407
549
 
408
550
  def get_commits(self, sha: Optional[str] = None, path: Optional[str] = None, since: Optional[str] = None, until: Optional[str] = None, author: Optional[str] = None):
409
551
  params = {}
@@ -417,7 +559,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
417
559
  params["until"] = until
418
560
  if author:
419
561
  params["author"] = author
420
- commits = self._repo_instance.commits.list(**params)
562
+ commits = self.repo_instance.commits.list(**params)
421
563
  return [
422
564
  {
423
565
  "sha": commit.id,
@@ -429,6 +571,8 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
429
571
  for commit in commits
430
572
  ]
431
573
 
574
+ @extend_with_parent_available_tools
575
+ @extend_with_file_operations
432
576
  def get_available_tools(self):
433
577
  return [
434
578
  {
@@ -524,7 +668,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
524
668
  {
525
669
  "name": "create_pr_change_comment",
526
670
  "ref": self.create_pr_change_comment,
527
- "description": "Create a comment on a pull request change.",
671
+ "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.",
528
672
  "args_schema": CreatePRChangeCommentModel,
529
673
  },
530
674
  {
@@ -533,4 +677,4 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
533
677
  "description": "Retrieve a list of commits from the repository.",
534
678
  "args_schema": GetCommitsModel,
535
679
  }
536
- ] + self._get_vector_search_tools()
680
+ ]
@@ -4,7 +4,9 @@ from langchain_core.tools import BaseToolkit
4
4
  from langchain_core.tools import BaseTool
5
5
  from ..base.tool import BaseAction
6
6
  from pydantic import create_model, BaseModel, ConfigDict, Field, SecretStr
7
- from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
7
+
8
+ from ..elitea_base import filter_missconfigured_index_tools
9
+ from ..utils import clean_string, get_max_toolkit_length
8
10
  from ...configurations.gitlab import GitlabConfiguration
9
11
 
10
12
  name = "gitlab_org"
@@ -20,17 +22,13 @@ def get_tools(tool):
20
22
 
21
23
  class AlitaGitlabSpaceToolkit(BaseToolkit):
22
24
  tools: List[BaseTool] = []
23
- toolkit_max_length: int = 0
24
25
 
25
26
  @staticmethod
26
27
  def toolkit_config_schema() -> BaseModel:
27
28
  selected_tools = {x['name']: x['args_schema'].schema() for x in GitLabWorkspaceAPIWrapper.model_construct().get_available_tools()}
28
- AlitaGitlabSpaceToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
29
29
  return create_model(
30
30
  name,
31
- name=(str, Field(description="Toolkit name", json_schema_extra={'toolkit_name': True,
32
- 'max_toolkit_length': AlitaGitlabSpaceToolkit.toolkit_max_length})),
33
- gitlab_configuration=(Optional[GitlabConfiguration], Field(description="GitLab configuration",
31
+ gitlab_configuration=(GitlabConfiguration, Field(description="GitLab configuration",
34
32
  json_schema_extra={
35
33
  'configuration_types': ['gitlab']})),
36
34
  repositories=(str, Field(
@@ -51,6 +49,7 @@ class AlitaGitlabSpaceToolkit(BaseToolkit):
51
49
  )
52
50
 
53
51
  @classmethod
52
+ @filter_missconfigured_index_tools
54
53
  def get_toolkit(cls, selected_tools: list[str] | None = None, toolkit_name: Optional[str] = None, **kwargs):
55
54
  if selected_tools is None:
56
55
  selected_tools = []
@@ -60,18 +59,22 @@ class AlitaGitlabSpaceToolkit(BaseToolkit):
60
59
  **kwargs['gitlab_configuration'],
61
60
  }
62
61
  gitlab_wrapper = GitLabWorkspaceAPIWrapper(**wrapper_payload)
63
- prefix = clean_string(toolkit_name, AlitaGitlabSpaceToolkit.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
64
62
  available_tools = gitlab_wrapper.get_available_tools()
65
63
  tools = []
66
64
  for tool in available_tools:
67
65
  if selected_tools:
68
66
  if tool["name"] not in selected_tools:
69
67
  continue
68
+ description = tool["description"]
69
+ if toolkit_name:
70
+ description = f"Toolkit: {toolkit_name}\n{description}"
71
+ description = description[:1000]
70
72
  tools.append(BaseAction(
71
73
  api_wrapper=gitlab_wrapper,
72
- name=prefix + tool['name'],
73
- description=tool["description"],
74
- args_schema=tool["args_schema"]
74
+ name=tool['name'],
75
+ description=description,
76
+ args_schema=tool["args_schema"],
77
+ metadata={"toolkit_name": toolkit_name} if toolkit_name else {}
75
78
  ))
76
79
  return cls(tools=tools)
77
80
 
@@ -5,7 +5,7 @@ from langchain_core.tools import BaseTool, BaseToolkit
5
5
  from pydantic import BaseModel, Field, computed_field, field_validator
6
6
 
7
7
  from ....configurations.bigquery import BigQueryConfiguration
8
- from ...utils import TOOLKIT_SPLITTER, clean_string, get_max_toolkit_length
8
+ from ...utils import clean_string, get_max_toolkit_length
9
9
  from .api_wrapper import BigQueryApiWrapper
10
10
  from .tool import BigQueryAction
11
11
 
@@ -22,11 +22,6 @@ def get_available_tools() -> dict[str, dict]:
22
22
  return available_tools
23
23
 
24
24
 
25
- toolkit_max_length = lru_cache(maxsize=1)(
26
- lambda: get_max_toolkit_length(get_available_tools())
27
- )
28
-
29
-
30
25
  class BigQueryToolkitConfig(BaseModel):
31
26
  class Config:
32
27
  title = name
@@ -46,7 +41,7 @@ class BigQueryToolkitConfig(BaseModel):
46
41
  }
47
42
  }
48
43
 
49
- bigquery_configuration: Optional[BigQueryConfiguration] = Field(
44
+ bigquery_configuration: BigQueryConfiguration = Field(
50
45
  description="BigQuery configuration", json_schema_extra={"configuration_types": ["bigquery"]}
51
46
  )
52
47
  selected_tools: List[str] = Field(
@@ -86,9 +81,10 @@ class BigQueryToolkit(BaseToolkit):
86
81
 
87
82
  @computed_field
88
83
  @property
89
- def tool_prefix(self) -> str:
84
+ def toolkit_context(self) -> str:
85
+ """Returns toolkit context for descriptions (max 1000 chars)."""
90
86
  return (
91
- clean_string(self.toolkit_name, toolkit_max_length()) + TOOLKIT_SPLITTER
87
+ f" [Toolkit: {clean_string(self.toolkit_name, 0)}]"
92
88
  if self.toolkit_name
93
89
  else ""
94
90
  )
@@ -122,14 +118,18 @@ class BigQueryToolkit(BaseToolkit):
122
118
  selected_tools = set(selected_tools)
123
119
  for t in instance.available_tools:
124
120
  if t["name"] in selected_tools:
121
+ description = t["description"]
122
+ if toolkit_name:
123
+ description = f"Toolkit: {toolkit_name}\n{description}"
124
+ description = f"Project: {getattr(instance.api_wrapper, 'project', '')}\n{description}"
125
+ description = description[:1000]
125
126
  instance.tools.append(
126
127
  BigQueryAction(
127
128
  api_wrapper=instance.api_wrapper,
128
- name=instance.tool_prefix + t["name"],
129
- # set unique description for declared tools to differentiate the same methods for different toolkits
130
- description=f"Project: {getattr(instance.api_wrapper, 'project', '')}\n"
131
- + t["description"],
129
+ name=t["name"],
130
+ description=description,
132
131
  args_schema=t["args_schema"],
132
+ metadata={"toolkit_name": toolkit_name} if toolkit_name else {}
133
133
  )
134
134
  )
135
135
  return instance
@@ -29,6 +29,10 @@ class BigQueryAction(BaseTool):
29
29
  ) -> str:
30
30
  """Use the GitHub API to run an operation."""
31
31
  try:
32
- return self.api_wrapper.run(self.mode, *args, **kwargs)
32
+ # Strip numeric suffix added for deduplication (_2, _3, etc.)
33
+ # to get the original tool name that exists in the wrapper
34
+ import re
35
+ mode = re.sub(r'_\d+$', '', self.mode) if self.mode else self.mode
36
+ return self.api_wrapper.run(mode, *args, **kwargs)
33
37
  except Exception as e:
34
38
  return f"Error: {format_exc()}"