alita-sdk 0.3.351__py3-none-any.whl → 0.3.499__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 (206) 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 +1256 -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 +64 -8
  30. alita_sdk/community/inventory/__init__.py +224 -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/visualize.py +1370 -0
  58. alita_sdk/configurations/bitbucket.py +94 -2
  59. alita_sdk/configurations/confluence.py +96 -1
  60. alita_sdk/configurations/gitlab.py +79 -0
  61. alita_sdk/configurations/jira.py +103 -0
  62. alita_sdk/configurations/testrail.py +88 -0
  63. alita_sdk/configurations/xray.py +93 -0
  64. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  65. alita_sdk/configurations/zephyr_essential.py +75 -0
  66. alita_sdk/runtime/clients/artifact.py +1 -1
  67. alita_sdk/runtime/clients/client.py +214 -42
  68. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  69. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  70. alita_sdk/runtime/clients/sandbox_client.py +373 -0
  71. alita_sdk/runtime/langchain/assistant.py +118 -30
  72. alita_sdk/runtime/langchain/constants.py +8 -1
  73. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  74. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  75. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -1
  76. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +41 -12
  77. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -1
  78. alita_sdk/runtime/langchain/document_loaders/constants.py +116 -99
  79. alita_sdk/runtime/langchain/interfaces/llm_processor.py +2 -2
  80. alita_sdk/runtime/langchain/langraph_agent.py +307 -71
  81. alita_sdk/runtime/langchain/utils.py +48 -8
  82. alita_sdk/runtime/llms/preloaded.py +2 -6
  83. alita_sdk/runtime/models/mcp_models.py +61 -0
  84. alita_sdk/runtime/toolkits/__init__.py +26 -0
  85. alita_sdk/runtime/toolkits/application.py +9 -2
  86. alita_sdk/runtime/toolkits/artifact.py +18 -6
  87. alita_sdk/runtime/toolkits/datasource.py +13 -6
  88. alita_sdk/runtime/toolkits/mcp.py +780 -0
  89. alita_sdk/runtime/toolkits/planning.py +178 -0
  90. alita_sdk/runtime/toolkits/tools.py +205 -55
  91. alita_sdk/runtime/toolkits/vectorstore.py +9 -4
  92. alita_sdk/runtime/tools/__init__.py +11 -3
  93. alita_sdk/runtime/tools/application.py +7 -0
  94. alita_sdk/runtime/tools/artifact.py +225 -12
  95. alita_sdk/runtime/tools/function.py +95 -5
  96. alita_sdk/runtime/tools/graph.py +10 -4
  97. alita_sdk/runtime/tools/image_generation.py +212 -0
  98. alita_sdk/runtime/tools/llm.py +494 -102
  99. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  100. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  101. alita_sdk/runtime/tools/mcp_server_tool.py +4 -4
  102. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  103. alita_sdk/runtime/tools/planning/models.py +246 -0
  104. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  105. alita_sdk/runtime/tools/router.py +2 -1
  106. alita_sdk/runtime/tools/sandbox.py +180 -79
  107. alita_sdk/runtime/tools/vectorstore.py +22 -21
  108. alita_sdk/runtime/tools/vectorstore_base.py +125 -52
  109. alita_sdk/runtime/utils/AlitaCallback.py +106 -20
  110. alita_sdk/runtime/utils/mcp_client.py +465 -0
  111. alita_sdk/runtime/utils/mcp_oauth.py +244 -0
  112. alita_sdk/runtime/utils/mcp_sse_client.py +405 -0
  113. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  114. alita_sdk/runtime/utils/streamlit.py +40 -13
  115. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  116. alita_sdk/runtime/utils/utils.py +12 -0
  117. alita_sdk/tools/__init__.py +77 -33
  118. alita_sdk/tools/ado/repos/__init__.py +7 -6
  119. alita_sdk/tools/ado/repos/repos_wrapper.py +11 -11
  120. alita_sdk/tools/ado/test_plan/__init__.py +7 -7
  121. alita_sdk/tools/ado/wiki/__init__.py +7 -11
  122. alita_sdk/tools/ado/wiki/ado_wrapper.py +89 -15
  123. alita_sdk/tools/ado/work_item/__init__.py +7 -11
  124. alita_sdk/tools/ado/work_item/ado_wrapper.py +17 -8
  125. alita_sdk/tools/advanced_jira_mining/__init__.py +8 -7
  126. alita_sdk/tools/aws/delta_lake/__init__.py +11 -9
  127. alita_sdk/tools/azure_ai/search/__init__.py +7 -6
  128. alita_sdk/tools/base_indexer_toolkit.py +345 -70
  129. alita_sdk/tools/bitbucket/__init__.py +9 -8
  130. alita_sdk/tools/bitbucket/api_wrapper.py +50 -6
  131. alita_sdk/tools/browser/__init__.py +4 -4
  132. alita_sdk/tools/carrier/__init__.py +4 -6
  133. alita_sdk/tools/chunkers/__init__.py +3 -1
  134. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  135. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  136. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  137. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  138. alita_sdk/tools/cloud/aws/__init__.py +7 -6
  139. alita_sdk/tools/cloud/azure/__init__.py +7 -6
  140. alita_sdk/tools/cloud/gcp/__init__.py +7 -6
  141. alita_sdk/tools/cloud/k8s/__init__.py +7 -6
  142. alita_sdk/tools/code/linter/__init__.py +7 -7
  143. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  144. alita_sdk/tools/code/sonar/__init__.py +8 -7
  145. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  146. alita_sdk/tools/confluence/__init__.py +9 -8
  147. alita_sdk/tools/confluence/api_wrapper.py +171 -75
  148. alita_sdk/tools/confluence/loader.py +10 -0
  149. alita_sdk/tools/custom_open_api/__init__.py +9 -4
  150. alita_sdk/tools/elastic/__init__.py +8 -7
  151. alita_sdk/tools/elitea_base.py +492 -52
  152. alita_sdk/tools/figma/__init__.py +7 -7
  153. alita_sdk/tools/figma/api_wrapper.py +2 -1
  154. alita_sdk/tools/github/__init__.py +9 -9
  155. alita_sdk/tools/github/api_wrapper.py +9 -26
  156. alita_sdk/tools/github/github_client.py +62 -2
  157. alita_sdk/tools/gitlab/__init__.py +8 -8
  158. alita_sdk/tools/gitlab/api_wrapper.py +135 -33
  159. alita_sdk/tools/gitlab_org/__init__.py +7 -8
  160. alita_sdk/tools/google/bigquery/__init__.py +11 -12
  161. alita_sdk/tools/google_places/__init__.py +8 -7
  162. alita_sdk/tools/jira/__init__.py +9 -7
  163. alita_sdk/tools/jira/api_wrapper.py +100 -52
  164. alita_sdk/tools/keycloak/__init__.py +8 -7
  165. alita_sdk/tools/localgit/local_git.py +56 -54
  166. alita_sdk/tools/memory/__init__.py +1 -1
  167. alita_sdk/tools/non_code_indexer_toolkit.py +3 -2
  168. alita_sdk/tools/ocr/__init__.py +8 -7
  169. alita_sdk/tools/openapi/__init__.py +10 -1
  170. alita_sdk/tools/pandas/__init__.py +8 -7
  171. alita_sdk/tools/postman/__init__.py +7 -8
  172. alita_sdk/tools/postman/api_wrapper.py +19 -8
  173. alita_sdk/tools/postman/postman_analysis.py +8 -1
  174. alita_sdk/tools/pptx/__init__.py +8 -9
  175. alita_sdk/tools/qtest/__init__.py +16 -11
  176. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  177. alita_sdk/tools/rally/__init__.py +7 -8
  178. alita_sdk/tools/report_portal/__init__.py +9 -7
  179. alita_sdk/tools/salesforce/__init__.py +7 -7
  180. alita_sdk/tools/servicenow/__init__.py +10 -10
  181. alita_sdk/tools/sharepoint/__init__.py +7 -6
  182. alita_sdk/tools/sharepoint/api_wrapper.py +127 -36
  183. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  184. alita_sdk/tools/sharepoint/utils.py +8 -2
  185. alita_sdk/tools/slack/__init__.py +7 -6
  186. alita_sdk/tools/sql/__init__.py +8 -7
  187. alita_sdk/tools/sql/api_wrapper.py +71 -23
  188. alita_sdk/tools/testio/__init__.py +7 -6
  189. alita_sdk/tools/testrail/__init__.py +8 -9
  190. alita_sdk/tools/utils/__init__.py +26 -4
  191. alita_sdk/tools/utils/content_parser.py +88 -60
  192. alita_sdk/tools/utils/text_operations.py +254 -0
  193. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +76 -26
  194. alita_sdk/tools/xray/__init__.py +9 -7
  195. alita_sdk/tools/zephyr/__init__.py +7 -6
  196. alita_sdk/tools/zephyr_enterprise/__init__.py +8 -6
  197. alita_sdk/tools/zephyr_essential/__init__.py +7 -6
  198. alita_sdk/tools/zephyr_essential/api_wrapper.py +12 -13
  199. alita_sdk/tools/zephyr_scale/__init__.py +7 -6
  200. alita_sdk/tools/zephyr_squad/__init__.py +7 -6
  201. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/METADATA +147 -2
  202. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/RECORD +206 -130
  203. alita_sdk-0.3.499.dist-info/entry_points.txt +2 -0
  204. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/WHEEL +0 -0
  205. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/licenses/LICENSE +0 -0
  206. {alita_sdk-0.3.351.dist-info → alita_sdk-0.3.499.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field, create_model
6
6
  from ..base.tool import BaseAction
7
7
  from .api_wrapper import FigmaApiWrapper, GLOBAL_LIMIT
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.figma import FigmaConfiguration
11
11
  from ...configurations.pgvector import PgVectorConfiguration
12
12
 
@@ -36,7 +36,6 @@ def get_tools(tool):
36
36
 
37
37
  class FigmaToolkit(BaseToolkit):
38
38
  tools: List[BaseTool] = []
39
- toolkit_max_length: int = 0
40
39
 
41
40
  @staticmethod
42
41
  def toolkit_config_schema() -> BaseModel:
@@ -44,7 +43,6 @@ class FigmaToolkit(BaseToolkit):
44
43
  x["name"]: x["args_schema"].schema()
45
44
  for x in FigmaApiWrapper.model_construct().get_available_tools()
46
45
  }
47
- FigmaToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
48
46
  return create_model(
49
47
  name,
50
48
  global_limit=(Optional[int], Field(description="Global limit", default=GLOBAL_LIMIT)),
@@ -66,7 +64,6 @@ class FigmaToolkit(BaseToolkit):
66
64
  "metadata": {
67
65
  "label": "Figma",
68
66
  "icon_url": "figma-icon.svg",
69
- "max_length": FigmaToolkit.toolkit_max_length,
70
67
  "categories": ["other"],
71
68
  "extra_categories": ["figma", "design", "ui/ux", "prototyping", "collaboration"],
72
69
  }
@@ -85,18 +82,21 @@ class FigmaToolkit(BaseToolkit):
85
82
  **(kwargs.get('pgvector_configuration') or {}),
86
83
  }
87
84
  figma_api_wrapper = FigmaApiWrapper(**wrapper_payload)
88
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
89
85
  available_tools = figma_api_wrapper.get_available_tools()
90
86
  tools = []
91
87
  for tool in available_tools:
92
88
  if selected_tools:
93
89
  if tool["name"] not in selected_tools:
94
90
  continue
91
+ description = tool["description"]
92
+ if toolkit_name:
93
+ description = f"Toolkit: {toolkit_name}\n{description}"
94
+ description = description[:1000]
95
95
  tools.append(
96
96
  BaseAction(
97
97
  api_wrapper=figma_api_wrapper,
98
- name=prefix + tool["name"],
99
- description=tool["description"],
98
+ name=tool["name"],
99
+ description=description,
100
100
  args_schema=tool["args_schema"],
101
101
  )
102
102
  )
@@ -345,7 +345,8 @@ class FigmaApiWrapper(NonCodeIndexerToolkit):
345
345
  text_nodes[node['id']] = self.get_texts_recursive(node)
346
346
  # process image nodes
347
347
  if image_nodes:
348
- images = self._client.get_file_images(file_key, image_nodes).images or {}
348
+ file_images = self._client.get_file_images(file_key, image_nodes)
349
+ images = self._client.get_file_images(file_key, image_nodes).images or {} if file_images else {}
349
350
  total_images = len(images)
350
351
  if total_images == 0:
351
352
  logging.info(f"No images found for file {file_key}.")
@@ -7,7 +7,7 @@ from .api_wrapper import AlitaGitHubAPIWrapper
7
7
  from .tool import GitHubAction
8
8
  from ..elitea_base import filter_missconfigured_index_tools
9
9
 
10
- from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
10
+ from ..utils import clean_string, get_max_toolkit_length
11
11
  from ...configurations.github import GithubConfiguration
12
12
  from ...configurations.pgvector import PgVectorConfiguration
13
13
 
@@ -39,13 +39,11 @@ def get_tools(tool):
39
39
 
40
40
  class AlitaGitHubToolkit(BaseToolkit):
41
41
  tools: List[BaseTool] = []
42
- toolkit_max_length: int = 0
43
42
 
44
43
  @staticmethod
45
44
  def toolkit_config_schema() -> BaseModel:
46
45
  selected_tools = {x['name']: x['args_schema'].schema() for x in
47
46
  AlitaGitHubAPIWrapper.model_construct().get_available_tools()}
48
- AlitaGitHubToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
49
47
  return create_model(
50
48
  name,
51
49
  __config__=ConfigDict(
@@ -62,8 +60,7 @@ class AlitaGitHubToolkit(BaseToolkit):
62
60
  json_schema_extra={'configuration_types': ['github']})),
63
61
  pgvector_configuration=(Optional[PgVectorConfiguration], Field(description="PgVector configuration", default=None,
64
62
  json_schema_extra={'configuration_types': ['pgvector']})),
65
- repository=(str, Field(description="Github repository", json_schema_extra={'toolkit_name': True,
66
- 'max_toolkit_length': AlitaGitHubToolkit.toolkit_max_length})),
63
+ repository=(str, Field(description="Github repository")),
67
64
  active_branch=(Optional[str], Field(description="Active branch", default="main")),
68
65
  base_branch=(Optional[str], Field(description="Github Base branch", default="main")),
69
66
  # embedder settings
@@ -87,17 +84,20 @@ class AlitaGitHubToolkit(BaseToolkit):
87
84
  github_api_wrapper = AlitaGitHubAPIWrapper(**wrapper_payload)
88
85
  available_tools: List[Dict] = github_api_wrapper.get_available_tools()
89
86
  tools = []
90
- prefix = clean_string(toolkit_name, AlitaGitHubToolkit.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
91
87
  for tool in available_tools:
92
88
  if selected_tools:
93
89
  if tool["name"] not in selected_tools:
94
90
  continue
91
+ description = tool["description"]
92
+ if toolkit_name:
93
+ description = f"Toolkit: {toolkit_name}\n{description}"
94
+ description = f"Repository: {github_api_wrapper.github_repository}\n{description}"
95
+ description = description[:1000]
95
96
  tools.append(GitHubAction(
96
97
  api_wrapper=github_api_wrapper,
97
- name=prefix + tool["name"],
98
+ name=tool["name"],
98
99
  mode=tool["mode"],
99
- # set unique description for declared tools to differentiate the same methods for different toolkits
100
- description=f"Repository: {github_api_wrapper.github_repository}\n" + tool["description"],
100
+ description=description,
101
101
  args_schema=tool["args_schema"]
102
102
  ))
103
103
  return cls(tools=tools)
@@ -1,9 +1,7 @@
1
- from typing import Any, Dict, List, Optional, Union, Tuple
2
1
  import logging
3
- import traceback
4
- import json
5
- import re
6
- from pydantic import BaseModel, model_validator, Field, SecretStr
2
+ from typing import Any, Dict, Optional
3
+
4
+ from pydantic import model_validator, Field, SecretStr
7
5
 
8
6
  from .github_client import GitHubClient
9
7
  from .graphql_client_wrapper import GraphQLClientWrapper
@@ -11,28 +9,17 @@ from .schemas import (
11
9
  GitHubAuthConfig,
12
10
  GitHubRepoConfig
13
11
  )
14
-
15
- from ..elitea_base import BaseCodeToolApiWrapper
16
-
17
- from langchain_core.callbacks import dispatch_custom_event
12
+ from ..code_indexer_toolkit import CodeIndexerToolkit
13
+ from ..utils.available_tools_decorator import extend_with_parent_available_tools
18
14
 
19
15
  logger = logging.getLogger(__name__)
20
16
 
21
17
  # Import prompts for tools
22
- from .tool_prompts import (
23
- UPDATE_FILE_PROMPT,
24
- CREATE_ISSUE_PROMPT,
25
- UPDATE_ISSUE_PROMPT,
26
- CREATE_ISSUE_ON_PROJECT_PROMPT,
27
- UPDATE_ISSUE_ON_PROJECT_PROMPT
28
- )
29
18
 
30
19
  # Create schema models for the new indexing functionality
31
- from pydantic import create_model
32
- from typing import Literal
33
20
 
34
21
 
35
- class AlitaGitHubAPIWrapper(BaseCodeToolApiWrapper):
22
+ class AlitaGitHubAPIWrapper(CodeIndexerToolkit):
36
23
  """
37
24
  Wrapper for GitHub API that integrates both REST and GraphQL functionality.
38
25
  """
@@ -117,7 +104,7 @@ class AlitaGitHubAPIWrapper(BaseCodeToolApiWrapper):
117
104
  if "llm" not in values:
118
105
  values["llm"] = None
119
106
 
120
- return values
107
+ return super().validate_toolkit(values)
121
108
 
122
109
  # Expose GitHub REST client methods directly via property
123
110
  @property
@@ -131,7 +118,7 @@ class AlitaGitHubAPIWrapper(BaseCodeToolApiWrapper):
131
118
  """Access to GitHub GraphQL client methods"""
132
119
  return self.graphql_client_instance
133
120
 
134
-
121
+ @extend_with_parent_available_tools
135
122
  def get_available_tools(self):
136
123
  # this is horrible, I need to think on something better
137
124
  if not self.github_client_instance:
@@ -142,12 +129,8 @@ class AlitaGitHubAPIWrapper(BaseCodeToolApiWrapper):
142
129
  graphql_tools = GraphQLClientWrapper.model_construct().get_available_tools()
143
130
  else:
144
131
  graphql_tools = self.graphql_client_instance.get_available_tools()
145
-
146
- # Add vector search tools from base class (includes index_data + search tools)
147
- vector_search_tools = self._get_vector_search_tools()
148
132
 
149
- tools = github_tools + graphql_tools + vector_search_tools
150
- return tools
133
+ return github_tools + graphql_tools
151
134
 
152
135
  def _get_files(self, path: str = "", branch: str = None):
153
136
  """Get list of files from GitHub repository."""
@@ -11,6 +11,7 @@ from github import Auth, Github, GithubIntegration, Repository
11
11
  from github.Consts import DEFAULT_BASE_URL
12
12
  from langchain_core.tools import ToolException
13
13
 
14
+ from ..elitea_base import extend_with_file_operations
14
15
  from .schemas import (
15
16
  GitHubAuthConfig,
16
17
  GitHubRepoConfig,
@@ -1414,13 +1415,16 @@ class GitHubClient(BaseModel):
1414
1415
  except Exception as e:
1415
1416
  return f"File not found `{file_path}` on branch `{branch}`. Error: {str(e)}"
1416
1417
 
1417
- def _read_file(self, file_path: str, branch: str, repo_name: Optional[str] = None) -> str:
1418
+ def _read_file(self, file_path: str, branch: str, repo_name: Optional[str] = None, **kwargs) -> str:
1418
1419
  """
1419
- Read a file from specified branch
1420
+ Read a file from specified branch with optional partial read support.
1421
+
1420
1422
  Parameters:
1421
1423
  file_path(str): the file path
1422
1424
  branch(str): the branch to read the file from
1423
1425
  repo_name (Optional[str]): Name of the repository in format 'owner/repo'
1426
+ **kwargs: Additional parameters (offset, limit, head, tail) - currently ignored,
1427
+ partial read handled client-side by base class methods
1424
1428
 
1425
1429
  Returns:
1426
1430
  str: The file decoded as a string, or an error message if not found
@@ -1445,6 +1449,61 @@ class GitHubClient(BaseModel):
1445
1449
  str: The file contents as a string
1446
1450
  """
1447
1451
  return self._read_file(file_path, branch if branch else self.active_branch, repo_name)
1452
+
1453
+ def _write_file(
1454
+ self,
1455
+ file_path: str,
1456
+ content: str,
1457
+ branch: str = None,
1458
+ commit_message: str = None,
1459
+ repo_name: Optional[str] = None
1460
+ ) -> str:
1461
+ """
1462
+ Write content to a file (create or update).
1463
+
1464
+ Parameters:
1465
+ file_path: Path to the file
1466
+ content: New file content
1467
+ branch: Branch name (uses active branch if None)
1468
+ commit_message: Commit message
1469
+ repo_name: Name of the repository in format 'owner/repo'
1470
+
1471
+ Returns:
1472
+ Success message
1473
+ """
1474
+ try:
1475
+ repo = self.github_api.get_repo(repo_name) if repo_name else self.github_repo_instance
1476
+ branch = branch or self.active_branch
1477
+
1478
+ if branch == self.github_base_branch:
1479
+ raise ToolException(
1480
+ f"Cannot commit directly to the {self.github_base_branch} branch. "
1481
+ "Please create a new branch and try again."
1482
+ )
1483
+
1484
+ # Check if file exists
1485
+ try:
1486
+ existing_file = repo.get_contents(file_path, ref=branch)
1487
+ # File exists, update it
1488
+ repo.update_file(
1489
+ path=file_path,
1490
+ message=commit_message or f"Update {file_path}",
1491
+ content=content,
1492
+ branch=branch,
1493
+ sha=existing_file.sha,
1494
+ )
1495
+ return f"Updated file {file_path}"
1496
+ except:
1497
+ # File doesn't exist, create it
1498
+ repo.create_file(
1499
+ path=file_path,
1500
+ message=commit_message or f"Create {file_path}",
1501
+ content=content,
1502
+ branch=branch,
1503
+ )
1504
+ return f"Created file {file_path}"
1505
+ except Exception as e:
1506
+ raise ToolException(f"Unable to write file {file_path}: {str(e)}")
1448
1507
 
1449
1508
  def loader(self,
1450
1509
  branch: Optional[str] = None,
@@ -1877,6 +1936,7 @@ class GitHubClient(BaseModel):
1877
1936
  import traceback
1878
1937
  return f"API call failed: {traceback.format_exc()}"
1879
1938
 
1939
+ @extend_with_file_operations
1880
1940
  def get_available_tools(self) -> List[Dict[str, Any]]:
1881
1941
  return [
1882
1942
  {
@@ -8,7 +8,7 @@ from pydantic.fields import Field
8
8
 
9
9
  from .api_wrapper import GitLabAPIWrapper
10
10
  from ..elitea_base import filter_missconfigured_index_tools
11
- from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
11
+ from ..utils import clean_string, get_max_toolkit_length
12
12
  from ...configurations.gitlab import GitlabConfiguration
13
13
  from ...configurations.pgvector import PgVectorConfiguration
14
14
 
@@ -34,16 +34,14 @@ def get_tools(tool):
34
34
 
35
35
  class AlitaGitlabToolkit(BaseToolkit):
36
36
  tools: List[BaseTool] = []
37
- toolkit_max_length: int = 0
38
37
 
39
38
  @staticmethod
40
39
  def toolkit_config_schema() -> BaseModel:
41
40
  selected_tools = {x['name']: x['args_schema'].schema() for x in
42
41
  GitLabAPIWrapper.model_construct().get_available_tools()}
43
- AlitaGitlabToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
44
42
  return create_model(
45
43
  name,
46
- repository=(str, Field(description="GitLab repository", json_schema_extra={'toolkit_name': True, 'max_toolkit_length': AlitaGitlabToolkit.toolkit_max_length})),
44
+ repository=(str, Field(description="GitLab repository")),
47
45
  gitlab_configuration=(GitlabConfiguration, Field(description="GitLab configuration", json_schema_extra={'configuration_types': ['gitlab']})),
48
46
  branch=(str, Field(description="Main branch", default="main")),
49
47
  # indexer settings
@@ -76,18 +74,20 @@ class AlitaGitlabToolkit(BaseToolkit):
76
74
  **(kwargs.get('embedding_configuration') or {}),
77
75
  }
78
76
  gitlab_api_wrapper = GitLabAPIWrapper(**wrapper_payload)
79
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
80
77
  available_tools: List[Dict] = gitlab_api_wrapper.get_available_tools()
81
78
  tools = []
82
79
  for tool in available_tools:
83
80
  if selected_tools:
84
81
  if tool["name"] not in selected_tools:
85
82
  continue
86
-
83
+ description = tool["description"] + f"\nrepo: {gitlab_api_wrapper.repository}"
84
+ if toolkit_name:
85
+ description = f"{description}\nToolkit: {toolkit_name}"
86
+ description = description[:1000]
87
87
  tools.append(BaseAction(
88
88
  api_wrapper=gitlab_api_wrapper,
89
- name=prefix + tool["name"],
90
- description=tool["description"] + f"\nrepo: {gitlab_api_wrapper.repository}",
89
+ name=tool["name"],
90
+ description=description,
91
91
  args_schema=tool["args_schema"]
92
92
  ))
93
93
  return cls(tools=tools)
@@ -1,9 +1,15 @@
1
1
  # api_wrapper.py
2
- from typing import Any, Dict, List, Optional
3
2
  import fnmatch
4
- from ...tools.elitea_base import BaseCodeToolApiWrapper
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from langchain_core.tools import ToolException
5
6
  from pydantic import create_model, Field, model_validator, SecretStr, PrivateAttr
6
7
 
8
+ from ..code_indexer_toolkit import CodeIndexerToolkit
9
+ from ..utils.available_tools_decorator import extend_with_parent_available_tools
10
+ from ..elitea_base import extend_with_file_operations
11
+ from ..utils.content_parser import parse_file_content
12
+
7
13
  AppendFileModel = create_model(
8
14
  "AppendFileModel",
9
15
  file_path=(str, Field(description="The path of the file")),
@@ -97,18 +103,26 @@ GetCommitsModel = create_model(
97
103
  author=(Optional[str], Field(description="Author name", default=None)),
98
104
  )
99
105
 
100
- class GitLabAPIWrapper(BaseCodeToolApiWrapper):
106
+ class GitLabAPIWrapper(CodeIndexerToolkit):
101
107
  url: str
102
108
  repository: str
103
109
  private_token: SecretStr
104
110
  branch: Optional[str] = 'main'
105
111
  _git: Any = PrivateAttr()
106
- _repo_instance: Any = PrivateAttr()
107
112
  _active_branch: Any = PrivateAttr()
108
113
 
114
+ @staticmethod
115
+ def _sanitize_url(url: str) -> str:
116
+ """Remove trailing slash from URL if present."""
117
+ return url.rstrip('/') if url else url
118
+
109
119
  @model_validator(mode='before')
110
120
  @classmethod
111
- def validate_toolkit(cls, values: Dict) -> Dict:
121
+ def validate_toolkit_before(cls, values: Dict) -> Dict:
122
+ return super().validate_toolkit(values)
123
+
124
+ @model_validator(mode='after')
125
+ def validate_toolkit(self):
112
126
  try:
113
127
  import gitlab
114
128
  except ImportError:
@@ -116,22 +130,34 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
116
130
  "python-gitlab is not installed. "
117
131
  "Please install it with `pip install python-gitlab`"
118
132
  )
119
-
133
+ self.repository = self._sanitize_url(self.repository)
120
134
  g = gitlab.Gitlab(
121
- url=values['url'],
122
- private_token=values['private_token'],
135
+ url=self._sanitize_url(self.url),
136
+ private_token=self.private_token.get_secret_value(),
123
137
  keep_base_url=True,
124
138
  )
125
139
 
126
140
  g.auth()
127
- cls._repo_instance = g.projects.get(values.get('repository'))
128
- cls._git = g
129
- cls._active_branch = values.get('branch')
130
- return values
141
+ self._git = g
142
+ self._active_branch = self.branch
143
+ return self
144
+
145
+ @property
146
+ def repo_instance(self):
147
+ if not hasattr(self, "_repo_instance") or self._repo_instance is None:
148
+ try:
149
+ if self._git and self.repository:
150
+ self._repo_instance = self._git.projects.get(self.repository)
151
+ else:
152
+ self._repo_instance = None
153
+ except Exception as e:
154
+ # Only raise when accessed, not during initialization
155
+ raise ToolException(e)
156
+ return self._repo_instance
131
157
 
132
158
  def set_active_branch(self, branch_name: str) -> str:
133
159
  self._active_branch = branch_name
134
- self._repo_instance.default_branch = branch_name
160
+ self.repo_instance.default_branch = branch_name
135
161
  return f"Active branch set to {branch_name}"
136
162
 
137
163
  def list_branches_in_repo(self, limit: Optional[int] = 20, branch_wildcard: Optional[str] = None) -> List[str]:
@@ -146,7 +172,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
146
172
  List[str]: List containing names of branches
147
173
  """
148
174
  try:
149
- branches = self._repo_instance.branches.list(get_all=True)
175
+ branches = self.repo_instance.branches.list(get_all=True)
150
176
 
151
177
  if branch_wildcard:
152
178
  branches = [branch for branch in branches if fnmatch.fnmatch(branch.name, branch_wildcard)]
@@ -173,7 +199,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
173
199
 
174
200
  def _get_all_files(self, path: str = None, recursive: bool = True, branch: str = None):
175
201
  branch = branch if branch else self._active_branch
176
- return self._repo_instance.repository_tree(path=path, ref=branch, recursive=recursive, all=True)
202
+ return self.repo_instance.repository_tree(path=path, ref=branch, recursive=recursive, all=True)
177
203
 
178
204
  # overrided for indexer
179
205
  def _get_files(self, path: str = None, recursive: bool = True, branch: str = None):
@@ -185,17 +211,29 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
185
211
  Get the commit hash of a file in a specific branch.
186
212
  """
187
213
  try:
188
- file = self._repo_instance.files.get(file_path, branch)
214
+ file = self.repo_instance.files.get(file_path, branch)
189
215
  return file.commit_id
190
216
  except Exception as e:
191
217
  return f"Unable to get commit hash for {file_path} due to error:\n{e}"
192
218
 
193
- def _read_file(self, file_path: str, branch: str):
219
+ def _read_file(self, file_path: str, branch: str, **kwargs):
220
+ """
221
+ Read a file from specified branch with optional partial read support.
222
+
223
+ Parameters:
224
+ file_path: the file path
225
+ branch: the branch to read the file from
226
+ **kwargs: Additional parameters (offset, limit, head, tail) - currently ignored,
227
+ partial read handled client-side by base class methods
228
+
229
+ Returns:
230
+ File content as string
231
+ """
194
232
  return self.read_file(file_path, branch)
195
233
 
196
234
  def create_branch(self, branch_name: str) -> str:
197
235
  try:
198
- self._repo_instance.branches.create(
236
+ self.repo_instance.branches.create(
199
237
  {
200
238
  'branch': branch_name,
201
239
  'ref': self._active_branch,
@@ -218,7 +256,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
218
256
  return parsed
219
257
 
220
258
  def get_issues(self) -> str:
221
- issues = self._repo_instance.issues.list(state="opened")
259
+ issues = self.repo_instance.issues.list(state="opened")
222
260
  if len(issues) > 0:
223
261
  parsed_issues = self.parse_issues(issues)
224
262
  parsed_issues_str = (
@@ -229,7 +267,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
229
267
  return "No open issues available"
230
268
 
231
269
  def get_issue(self, issue_number: int) -> Dict[str, Any]:
232
- issue = self._repo_instance.issues.get(issue_number)
270
+ issue = self.repo_instance.issues.get(issue_number)
233
271
  page = 0
234
272
  comments: List[dict] = []
235
273
  while len(comments) <= 10:
@@ -255,7 +293,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
255
293
  commits are already in the {self.branch} branch"""
256
294
  else:
257
295
  try:
258
- pr = self._repo_instance.mergerequests.create(
296
+ pr = self.repo_instance.mergerequests.create(
259
297
  {
260
298
  "source_branch": branch,
261
299
  "target_branch": self.branch,
@@ -272,7 +310,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
272
310
  issue_number = int(comment_query.split("\n\n")[0])
273
311
  comment = comment_query[len(str(issue_number)) + 2 :]
274
312
  try:
275
- issue = self._repo_instance.issues.get(issue_number)
313
+ issue = self.repo_instance.issues.get(issue_number)
276
314
  issue.notes.create({"body": comment})
277
315
  return "Commented on issue " + str(issue_number)
278
316
  except Exception as e:
@@ -281,7 +319,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
281
319
  def create_file(self, file_path: str, file_contents: str, branch: str) -> str:
282
320
  try:
283
321
  self.set_active_branch(branch)
284
- self._repo_instance.files.get(file_path, branch)
322
+ self.repo_instance.files.get(file_path, branch)
285
323
  return f"File already exists at {file_path}. Use update_file instead"
286
324
  except Exception:
287
325
  data = {
@@ -290,14 +328,76 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
290
328
  "file_path": file_path,
291
329
  "content": file_contents,
292
330
  }
293
- self._repo_instance.files.create(data)
331
+ self.repo_instance.files.create(data)
294
332
 
295
333
  return "Created file " + file_path
296
334
 
297
335
  def read_file(self, file_path: str, branch: str) -> str:
298
336
  self.set_active_branch(branch)
299
- file = self._repo_instance.files.get(file_path, branch)
300
- return file.decode().decode("utf-8")
337
+ file = self.repo_instance.files.get(file_path, branch)
338
+ return parse_file_content(file_name=file_path,
339
+ file_content=file.decode(),
340
+ llm=self.llm)
341
+
342
+ def _write_file(
343
+ self,
344
+ file_path: str,
345
+ content: str,
346
+ branch: str = None,
347
+ commit_message: str = None
348
+ ) -> str:
349
+ """
350
+ Write content to a file (create or update).
351
+
352
+ Parameters:
353
+ file_path: Path to the file
354
+ content: New file content
355
+ branch: Branch name (uses active branch if None)
356
+ commit_message: Commit message
357
+
358
+ Returns:
359
+ Success message
360
+ """
361
+ try:
362
+ branch = branch or self._active_branch
363
+
364
+ if branch == self.branch:
365
+ raise ToolException(
366
+ f"Cannot commit directly to the {self.branch} branch. "
367
+ "Please create a new branch and try again."
368
+ )
369
+
370
+ self.set_active_branch(branch)
371
+
372
+ # Check if file exists
373
+ try:
374
+ self.repo_instance.files.get(file_path, branch)
375
+ # File exists, update it
376
+ commit = {
377
+ "branch": branch,
378
+ "commit_message": commit_message or f"Update {file_path}",
379
+ "actions": [
380
+ {
381
+ "action": "update",
382
+ "file_path": file_path,
383
+ "content": content,
384
+ }
385
+ ],
386
+ }
387
+ self.repo_instance.commits.create(commit)
388
+ return f"Updated file {file_path}"
389
+ except:
390
+ # File doesn't exist, create it
391
+ data = {
392
+ "branch": branch,
393
+ "commit_message": commit_message or f"Create {file_path}",
394
+ "file_path": file_path,
395
+ "content": content,
396
+ }
397
+ self.repo_instance.files.create(data)
398
+ return f"Created file {file_path}"
399
+ except Exception as e:
400
+ raise ToolException(f"Unable to write file {file_path}: {str(e)}")
301
401
 
302
402
  def update_file(self, file_query: str, branch: str) -> str:
303
403
  if branch == self.branch:
@@ -335,7 +435,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
335
435
  ],
336
436
  }
337
437
 
338
- self._repo_instance.commits.create(commit)
438
+ self.repo_instance.commits.create(commit)
339
439
  return "Updated file " + file_path
340
440
  except Exception as e:
341
441
  return "Unable to update file due to error:\n" + str(e)
@@ -365,7 +465,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
365
465
  ],
366
466
  }
367
467
 
368
- self._repo_instance.commits.create(commit)
468
+ self.repo_instance.commits.create(commit)
369
469
  return "Updated file " + file_path
370
470
  except Exception as e:
371
471
  return "Unable to update file due to error:\n" + str(e)
@@ -375,20 +475,20 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
375
475
  self.set_active_branch(branch)
376
476
  if not commit_message:
377
477
  commit_message = f"Delete {file_path}"
378
- self._repo_instance.files.delete(file_path, branch, commit_message)
478
+ self.repo_instance.files.delete(file_path, branch, commit_message)
379
479
  return f"Deleted file {file_path}"
380
480
  except Exception as e:
381
481
  return f"Unable to delete file due to error:\n{e}"
382
482
 
383
483
  def get_pr_changes(self, pr_number: int) -> str:
384
- mr = self._repo_instance.mergerequests.get(pr_number)
484
+ mr = self.repo_instance.mergerequests.get(pr_number)
385
485
  res = f"title: {mr.title}\ndescription: {mr.description}\n\n"
386
486
  for change in mr.changes()["changes"]:
387
487
  res += f"diff --git a/{change['old_path']} b/{change['new_path']}\n{change['diff']}\n"
388
488
  return res
389
489
 
390
490
  def create_pr_change_comment(self, pr_number: int, file_path: str, line_number: int, comment: str) -> str:
391
- mr = self._repo_instance.mergerequests.get(pr_number)
491
+ mr = self.repo_instance.mergerequests.get(pr_number)
392
492
  position = {"position_type": "text", "new_path": file_path, "new_line": line_number}
393
493
  mr.discussions.create({"body": comment, "position": position})
394
494
  return "Comment added"
@@ -405,7 +505,7 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
405
505
  params["until"] = until
406
506
  if author:
407
507
  params["author"] = author
408
- commits = self._repo_instance.commits.list(**params)
508
+ commits = self.repo_instance.commits.list(**params)
409
509
  return [
410
510
  {
411
511
  "sha": commit.id,
@@ -417,6 +517,8 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
417
517
  for commit in commits
418
518
  ]
419
519
 
520
+ @extend_with_parent_available_tools
521
+ @extend_with_file_operations
420
522
  def get_available_tools(self):
421
523
  return [
422
524
  {
@@ -521,4 +623,4 @@ class GitLabAPIWrapper(BaseCodeToolApiWrapper):
521
623
  "description": "Retrieve a list of commits from the repository.",
522
624
  "args_schema": GetCommitsModel,
523
625
  }
524
- ] + self._get_vector_search_tools()
626
+ ]