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
@@ -7,13 +7,14 @@ 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
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
13
14
 
14
15
  name = "github"
15
16
 
16
- def _get_toolkit(tool) -> BaseToolkit:
17
+ def get_toolkit(tool) -> BaseToolkit:
17
18
  return AlitaGitHubToolkit().get_toolkit(
18
19
  selected_tools=tool['settings'].get('selected_tools', []),
19
20
  github_base_url=tool['settings'].get('base_url', ''),
@@ -31,21 +32,16 @@ def _get_toolkit(tool) -> BaseToolkit:
31
32
  toolkit_name=tool.get('toolkit_name')
32
33
  )
33
34
 
34
- def get_toolkit():
35
- return AlitaGitHubToolkit.toolkit_config_schema()
36
-
37
35
  def get_tools(tool):
38
- return _get_toolkit(tool).get_tools()
36
+ return get_toolkit(tool).get_tools()
39
37
 
40
38
  class AlitaGitHubToolkit(BaseToolkit):
41
39
  tools: List[BaseTool] = []
42
- toolkit_max_length: int = 0
43
40
 
44
41
  @staticmethod
45
42
  def toolkit_config_schema() -> BaseModel:
46
43
  selected_tools = {x['name']: x['args_schema'].schema() for x in
47
44
  AlitaGitHubAPIWrapper.model_construct().get_available_tools()}
48
- AlitaGitHubToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
49
45
  return create_model(
50
46
  name,
51
47
  __config__=ConfigDict(
@@ -62,8 +58,7 @@ class AlitaGitHubToolkit(BaseToolkit):
62
58
  json_schema_extra={'configuration_types': ['github']})),
63
59
  pgvector_configuration=(Optional[PgVectorConfiguration], Field(description="PgVector configuration", default=None,
64
60
  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})),
61
+ repository=(str, Field(description="Github repository")),
67
62
  active_branch=(Optional[str], Field(description="Active branch", default="main")),
68
63
  base_branch=(Optional[str], Field(description="Github Base branch", default="main")),
69
64
  # embedder settings
@@ -87,18 +82,22 @@ class AlitaGitHubToolkit(BaseToolkit):
87
82
  github_api_wrapper = AlitaGitHubAPIWrapper(**wrapper_payload)
88
83
  available_tools: List[Dict] = github_api_wrapper.get_available_tools()
89
84
  tools = []
90
- prefix = clean_string(toolkit_name, AlitaGitHubToolkit.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
91
85
  for tool in available_tools:
92
86
  if selected_tools:
93
87
  if tool["name"] not in selected_tools:
94
88
  continue
89
+ description = tool["description"]
90
+ if toolkit_name:
91
+ description = f"Toolkit: {toolkit_name}\n{description}"
92
+ description = f"Repository: {github_api_wrapper.github_repository}\n{description}"
93
+ description = description[:1000]
95
94
  tools.append(GitHubAction(
96
95
  api_wrapper=github_api_wrapper,
97
- name=prefix + tool["name"],
96
+ name=tool["name"],
98
97
  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"],
101
- args_schema=tool["args_schema"]
98
+ description=description,
99
+ args_schema=tool["args_schema"],
100
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
102
101
  ))
103
102
  return cls(tools=tools)
104
103
 
@@ -11,10 +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 .schemas import (
15
- GitHubAuthConfig,
16
- GitHubRepoConfig,
17
- )
14
+ from ..elitea_base import extend_with_file_operations, BaseCodeToolApiWrapper
18
15
 
19
16
  from .schemas import (
20
17
  GitHubAuthConfig,
@@ -22,6 +19,7 @@ from .schemas import (
22
19
  NoInput,
23
20
  BranchName,
24
21
  CreateBranchName,
22
+ DeleteBranchName,
25
23
  DirectoryPath,
26
24
  ReadFile,
27
25
  UpdateFile,
@@ -48,9 +46,9 @@ from .schemas import (
48
46
  # Import prompts for tools
49
47
  from .tool_prompts import (
50
48
  CREATE_FILE_PROMPT,
51
- UPDATE_FILE_PROMPT,
52
49
  CREATE_ISSUE_PROMPT,
53
50
  UPDATE_ISSUE_PROMPT,
51
+ DELETE_BRANCH_PROMPT,
54
52
  )
55
53
 
56
54
  from langchain_community.tools.github.prompt import (
@@ -71,6 +69,8 @@ from langchain_community.tools.github.prompt import (
71
69
  CREATE_PULL_REQUEST_PROMPT
72
70
  )
73
71
 
72
+ from ..utils.tool_prompts import EDIT_FILE_DESCRIPTION
73
+
74
74
 
75
75
  class GitHubClient(BaseModel):
76
76
  """Client for interacting with the GitHub REST API."""
@@ -94,6 +94,12 @@ class GitHubClient(BaseModel):
94
94
 
95
95
  # Alita instance
96
96
  alita: Optional[Any] = Field(default=None, exclude=True)
97
+
98
+ # Import file operation methods from BaseCodeToolApiWrapper
99
+ read_file_chunk = BaseCodeToolApiWrapper.read_file_chunk
100
+ read_multiple_files = BaseCodeToolApiWrapper.read_multiple_files
101
+ search_file = BaseCodeToolApiWrapper.search_file
102
+ edit_file = BaseCodeToolApiWrapper.edit_file
97
103
 
98
104
  @property
99
105
  def github_repo_instance(self) -> Optional[Repository.Repository]:
@@ -408,26 +414,53 @@ class GitHubClient(BaseModel):
408
414
  Returns:
409
415
  str: A detailed diff comparison between the two commits or an error message.
410
416
  """
417
+ def safe_author_info(commit_obj):
418
+ """Safely extract author info from a commit object, handling None values."""
419
+ author = commit_obj.commit.author
420
+ if author:
421
+ return {
422
+ "name": author.name or "Unknown",
423
+ "date": author.date.isoformat() if author.date else None
424
+ }
425
+ # Fallback to GitHub user info if git author is not available
426
+ elif commit_obj.author:
427
+ return {
428
+ "name": commit_obj.author.login,
429
+ "date": None
430
+ }
431
+ return {"name": "Unknown", "date": None}
432
+
411
433
  try:
412
434
  # Get the repository
413
435
  repo = self.github_api.get_repo(repo_name) if repo_name else self.github_repo_instance
414
-
436
+
415
437
  # Get the comparison between the two commits
416
438
  comparison = repo.compare(base_sha, head_sha)
417
-
439
+
440
+ # Get head commit - the GitHub Compare API doesn't return head_commit,
441
+ # so we get it from the commits list or fetch it directly
442
+ if comparison.commits:
443
+ head_commit_obj = comparison.commits[-1]
444
+ else:
445
+ # For identical commits or edge cases, fetch head commit directly
446
+ head_commit_obj = repo.get_commit(head_sha)
447
+
448
+ base_author = safe_author_info(comparison.base_commit)
449
+ head_author = safe_author_info(head_commit_obj)
450
+
418
451
  # Extract comparison information
419
452
  diff_info = {
420
453
  "base_commit": {
421
454
  "sha": comparison.base_commit.sha,
422
455
  "message": comparison.base_commit.commit.message,
423
- "author": comparison.base_commit.commit.author.name,
424
- "date": comparison.base_commit.commit.author.date.isoformat()
456
+ "author": base_author["name"],
457
+ "date": base_author["date"]
425
458
  },
426
459
  "head_commit": {
427
- "sha": comparison.head_commit.sha,
428
- "message": comparison.head_commit.commit.message,
429
- "author": comparison.head_commit.commit.author.name,
430
- "date": comparison.head_commit.commit.author.date.isoformat()
460
+ "sha": head_commit_obj.sha,
461
+ "message": head_commit_obj.commit.message,
462
+ "author": head_author["name"],
463
+ "date": head_author["date"]
431
464
  },
432
465
  "status": comparison.status, # ahead, behind, identical, or diverged
433
466
  "ahead_by": comparison.ahead_by,
@@ -436,14 +469,15 @@ class GitHubClient(BaseModel):
436
469
  "commits": [],
437
470
  "files": []
438
471
  }
439
-
472
+
440
473
  # Get commits in the comparison
441
474
  for commit in comparison.commits:
475
+ author_info = safe_author_info(commit)
442
476
  commit_info = {
443
477
  "sha": commit.sha,
444
478
  "message": commit.commit.message,
445
- "author": commit.commit.author.name,
446
- "date": commit.commit.author.date.isoformat(),
479
+ "author": author_info["name"],
480
+ "date": author_info["date"],
447
481
  "url": commit.html_url
448
482
  }
449
483
  diff_info["commits"].append(commit_info)
@@ -1048,6 +1082,73 @@ class GitHubClient(BaseModel):
1048
1082
  except Exception as e:
1049
1083
  return f"Failed to create branch: {str(e)}"
1050
1084
 
1085
+ def delete_branch(self, branch_name: str, force: bool = False) -> str:
1086
+ """
1087
+ Delete a branch from the GitHub repository.
1088
+
1089
+ Protected branches that cannot be deleted:
1090
+ - 'main' and 'master' branches are always protected
1091
+ - The configured base branch (github_base_branch) is protected
1092
+ - The currently active branch is protected (unless force=True)
1093
+
1094
+ Parameters:
1095
+ branch_name (str): Name of the branch to delete
1096
+ force (bool): If True, allows deletion of the current active branch
1097
+
1098
+ Returns:
1099
+ str: A success or error message.
1100
+ """
1101
+ from github import GithubException
1102
+
1103
+ try:
1104
+ # Protected branch names that should never be deleted
1105
+ protected_branches = {'main', 'master'}
1106
+
1107
+ # Add base branch to protected list
1108
+ if self.github_base_branch:
1109
+ protected_branches.add(self.github_base_branch)
1110
+
1111
+ # Check if trying to delete a protected branch
1112
+ if branch_name.lower() in {b.lower() for b in protected_branches}:
1113
+ return (
1114
+ f"Cannot delete branch '{branch_name}': "
1115
+ f"It is a protected branch (main, master, or base branch). "
1116
+ f"Protected branches: {', '.join(sorted(protected_branches))}"
1117
+ )
1118
+
1119
+ # Check if trying to delete the active branch without force
1120
+ if branch_name == self.active_branch and not force:
1121
+ return (
1122
+ f"Cannot delete branch '{branch_name}': "
1123
+ f"It is currently the active branch. "
1124
+ f"Use force=True to delete it anyway, or switch to a different branch first."
1125
+ )
1126
+
1127
+ # Delete the branch
1128
+ repo = self.github_repo_instance
1129
+ ref = repo.get_git_ref(f"heads/{branch_name}")
1130
+ ref.delete()
1131
+
1132
+ # If we deleted the active branch, reset to base branch
1133
+ if branch_name == self.active_branch:
1134
+ self.active_branch = self.github_base_branch
1135
+ return (
1136
+ f"Branch '{branch_name}' deleted successfully. "
1137
+ f"Active branch has been reset to '{self.github_base_branch}'."
1138
+ )
1139
+
1140
+ return f"Branch '{branch_name}' deleted successfully."
1141
+
1142
+ except GithubException as e:
1143
+ if e.status == 422:
1144
+ return f"Cannot delete branch '{branch_name}': {e.data.get('message', str(e))}"
1145
+ elif e.status == 404:
1146
+ return f"Branch '{branch_name}' not found in the repository."
1147
+ else:
1148
+ return f"Failed to delete branch '{branch_name}': {str(e)}"
1149
+ except Exception as e:
1150
+ return f"Failed to delete branch '{branch_name}': {str(e)}"
1151
+
1051
1152
  def create_file(self, file_path: str, file_contents: str, repo_name: Optional[str] = None) -> str:
1052
1153
  """
1053
1154
  Creates a new file on the GitHub repo
@@ -1087,65 +1188,12 @@ class GitHubClient(BaseModel):
1087
1188
  except Exception as e:
1088
1189
  return f"Unable to create file due to error:\n{str(e)}"
1089
1190
 
1090
- def extract_old_new_pairs(self, file_query):
1091
- # Split the file content by lines
1092
- code_lines = file_query.split("\n")
1093
-
1094
- # Initialize lists to hold the contents of OLD and NEW sections
1095
- old_contents = []
1096
- new_contents = []
1097
-
1098
- # Initialize variables to track whether the current line is within an OLD or NEW section
1099
- in_old_section = False
1100
- in_new_section = False
1101
-
1102
- # Temporary storage for the current section's content
1103
- current_section_content = []
1104
-
1105
- # Iterate through each line in the file content
1106
- for line in code_lines:
1107
- # Check for OLD section start
1108
- if "OLD <<<" in line:
1109
- in_old_section = True
1110
- current_section_content = [] # Reset current section content
1111
- continue # Skip the line with the marker
1112
-
1113
- # Check for OLD section end
1114
- if ">>>> OLD" in line:
1115
- in_old_section = False
1116
- old_contents.append("\n".join(current_section_content).strip()) # Add the captured content
1117
- current_section_content = [] # Reset current section content
1118
- continue # Skip the line with the marker
1119
-
1120
- # Check for NEW section start
1121
- if "NEW <<<" in line:
1122
- in_new_section = True
1123
- current_section_content = [] # Reset current section content
1124
- continue # Skip the line with the marker
1125
-
1126
- # Check for NEW section end
1127
- if ">>>> NEW" in line:
1128
- in_new_section = False
1129
- new_contents.append("\n".join(current_section_content).strip()) # Add the captured content
1130
- current_section_content = [] # Reset current section content
1131
- continue # Skip the line with the marker
1132
-
1133
- # If currently in an OLD or NEW section, add the line to the current section content
1134
- if in_old_section or in_new_section:
1135
- current_section_content.append(line)
1136
-
1137
- # Pair the OLD and NEW contents
1138
- paired_contents = list(zip(old_contents, new_contents))
1139
-
1140
- return paired_contents
1141
-
1142
1191
  def update_file(self, file_query: str, repo_name: Optional[str] = None, commit_message: Optional[str] = None) -> str:
1143
- """
1144
- Updates a file with new content.
1192
+ """Updates a file with new content using OLD/NEW markers and edit_file.
1193
+
1145
1194
  Parameters:
1146
- file_query(str): Contains the file path and the file contents.
1147
- The old file contents is wrapped in OLD <<<< and >>>> OLD
1148
- The new file contents is wrapped in NEW <<<< and >>>> NEW
1195
+ file_query(str): Contains the file path on the first line and the file contents
1196
+ wrapped in OLD <<<< and >>>> OLD / NEW <<<< and >>>> NEW markers.
1149
1197
  For example:
1150
1198
  /test/hello.txt
1151
1199
  OLD <<<<
@@ -1154,13 +1202,13 @@ class GitHubClient(BaseModel):
1154
1202
  NEW <<<<
1155
1203
  Hello Mars!
1156
1204
  >>>> NEW
1157
- repo_name (Optional[str]): Name of the repository in format 'owner/repo'
1205
+ repo_name (Optional[str]): Name of the repository in format 'owner/repo'. Currently
1206
+ not used by edit_file and must refer to the initialized repository.
1158
1207
 
1159
1208
  Returns:
1160
1209
  A success or failure message
1161
1210
  """
1162
1211
  try:
1163
- repo = self.github_api.get_repo(repo_name) if repo_name else self.github_repo_instance
1164
1212
  branch = self.active_branch
1165
1213
 
1166
1214
  if branch == self.github_base_branch:
@@ -1169,29 +1217,36 @@ class GitHubClient(BaseModel):
1169
1217
  "which is protected. Please create a new branch and try again."
1170
1218
  )
1171
1219
 
1172
- file_path: str = file_query.split("\n")[0]
1173
-
1174
- file_content = self._read_file(file_path, branch, repo_name)
1175
- updated_file_content = file_content
1176
- for old, new in self.extract_old_new_pairs(file_query):
1177
- if not old.strip():
1178
- continue
1179
- updated_file_content = updated_file_content.replace(old, new)
1180
-
1181
- if file_content == updated_file_content:
1220
+ # Split into lines and find first non-empty line for file_path
1221
+ lines = file_query.split("\n")
1222
+ first_non_empty_idx = None
1223
+ for i, line in enumerate(lines):
1224
+ if line.strip():
1225
+ first_non_empty_idx = i
1226
+ break
1227
+
1228
+ if first_non_empty_idx is None:
1182
1229
  return (
1183
- "File content was not updated because old content was not found or empty. "
1184
- "It may be helpful to use the read_file action to get the current file contents."
1230
+ "Invalid file_query format. Expected first non-empty line to be the file path "
1231
+ "followed by OLD/NEW blocks."
1185
1232
  )
1186
1233
 
1187
- repo.update_file(
1188
- path=file_path,
1189
- message=commit_message if commit_message else f"Update {file_path}",
1190
- content=updated_file_content,
1191
- branch=branch,
1192
- sha=repo.get_contents(file_path, ref=branch).sha,
1193
- )
1194
- return f"Updated file {file_path}"
1234
+ file_path = lines[first_non_empty_idx].strip()
1235
+ # Keep all lines after file_path line (preserving empty lines)
1236
+ edit_content = "\n".join(lines[first_non_empty_idx + 1:])
1237
+
1238
+ # Set temporary repo override for internal helpers
1239
+ self._tmp_repo_for_edit = repo_name
1240
+ try:
1241
+ return self.edit_file(
1242
+ file_path=file_path,
1243
+ file_query=edit_content,
1244
+ branch=branch,
1245
+ commit_message=commit_message or f"Update {file_path}",
1246
+ )
1247
+ finally:
1248
+ if hasattr(self, "_tmp_repo_for_edit"):
1249
+ delattr(self, "_tmp_repo_for_edit")
1195
1250
  except Exception as e:
1196
1251
  return f"Unable to update file due to error:\n{str(e)}"
1197
1252
 
@@ -1414,23 +1469,27 @@ class GitHubClient(BaseModel):
1414
1469
  except Exception as e:
1415
1470
  return f"File not found `{file_path}` on branch `{branch}`. Error: {str(e)}"
1416
1471
 
1417
- def _read_file(self, file_path: str, branch: str, repo_name: Optional[str] = None) -> str:
1472
+ def _read_file(self, file_path: str, branch: str, repo_name: Optional[str] = None, **kwargs) -> str:
1418
1473
  """
1419
- Read a file from specified branch
1474
+ Read a file from specified branch with optional partial read support.
1475
+
1420
1476
  Parameters:
1421
1477
  file_path(str): the file path
1422
1478
  branch(str): the branch to read the file from
1423
1479
  repo_name (Optional[str]): Name of the repository in format 'owner/repo'
1480
+ **kwargs: Additional parameters (offset, limit, head, tail) - currently ignored,
1481
+ partial read handled client-side by base class methods
1424
1482
 
1425
1483
  Returns:
1426
1484
  str: The file decoded as a string, or an error message if not found
1427
1485
  """
1428
1486
  try:
1429
- repo = self.github_api.get_repo(repo_name) if repo_name else self.github_repo_instance
1487
+ # Prefer temporary repo set by update_file, then explicit repo_name
1488
+ effective_repo = getattr(self, "_tmp_repo_for_edit", None) or repo_name
1489
+ repo = self.github_api.get_repo(effective_repo) if effective_repo else self.github_repo_instance
1430
1490
  file = repo.get_contents(file_path, ref=branch)
1431
1491
  return file.decoded_content.decode("utf-8")
1432
1492
  except Exception as e:
1433
- from traceback import format_exc
1434
1493
  return f"File not found `{file_path}` on branch `{branch}`. Error: {str(e)}"
1435
1494
 
1436
1495
  def read_file(self, file_path: str, branch: Optional[str] = None, repo_name: Optional[str] = None) -> str:
@@ -1445,6 +1504,63 @@ class GitHubClient(BaseModel):
1445
1504
  str: The file contents as a string
1446
1505
  """
1447
1506
  return self._read_file(file_path, branch if branch else self.active_branch, repo_name)
1507
+
1508
+ def _write_file(
1509
+ self,
1510
+ file_path: str,
1511
+ content: str,
1512
+ branch: str = None,
1513
+ commit_message: str = None,
1514
+ repo_name: Optional[str] = None
1515
+ ) -> str:
1516
+ """
1517
+ Write content to a file (create or update).
1518
+
1519
+ Parameters:
1520
+ file_path: Path to the file
1521
+ content: New file content
1522
+ branch: Branch name (uses active branch if None)
1523
+ commit_message: Commit message
1524
+ repo_name: Name of the repository in format 'owner/repo'
1525
+
1526
+ Returns:
1527
+ Success message
1528
+ """
1529
+ try:
1530
+ # Prefer temporary repo set by update_file, then explicit repo_name
1531
+ effective_repo = getattr(self, "_tmp_repo_for_edit", None) or repo_name
1532
+ repo = self.github_api.get_repo(effective_repo) if effective_repo else self.github_repo_instance
1533
+ branch = branch or self.active_branch
1534
+
1535
+ if branch == self.github_base_branch:
1536
+ raise ToolException(
1537
+ f"Cannot commit directly to the {self.github_base_branch} branch. "
1538
+ "Please create a new branch and try again."
1539
+ )
1540
+
1541
+ # Check if file exists
1542
+ try:
1543
+ existing_file = repo.get_contents(file_path, ref=branch)
1544
+ # File exists, update it
1545
+ repo.update_file(
1546
+ path=file_path,
1547
+ message=commit_message or f"Update {file_path}",
1548
+ content=content,
1549
+ branch=branch,
1550
+ sha=existing_file.sha,
1551
+ )
1552
+ return f"Updated file {file_path}"
1553
+ except:
1554
+ # File doesn't exist, create it
1555
+ repo.create_file(
1556
+ path=file_path,
1557
+ message=commit_message or f"Create {file_path}",
1558
+ content=content,
1559
+ branch=branch,
1560
+ )
1561
+ return f"Created file {file_path}"
1562
+ except Exception as e:
1563
+ raise ToolException(f"Unable to write file {file_path}: {str(e)}")
1448
1564
 
1449
1565
  def loader(self,
1450
1566
  branch: Optional[str] = None,
@@ -1877,6 +1993,7 @@ class GitHubClient(BaseModel):
1877
1993
  import traceback
1878
1994
  return f"API call failed: {traceback.format_exc()}"
1879
1995
 
1996
+ @extend_with_file_operations
1880
1997
  def get_available_tools(self) -> List[Dict[str, Any]]:
1881
1998
  return [
1882
1999
  {
@@ -1946,7 +2063,7 @@ class GitHubClient(BaseModel):
1946
2063
  "ref": self.update_file,
1947
2064
  "name": "update_file",
1948
2065
  "mode": "update_file",
1949
- "description": UPDATE_FILE_PROMPT,
2066
+ "description": EDIT_FILE_DESCRIPTION,
1950
2067
  "args_schema": UpdateFile,
1951
2068
  },
1952
2069
  {
@@ -1991,6 +2108,13 @@ class GitHubClient(BaseModel):
1991
2108
  "description": CREATE_BRANCH_PROMPT,
1992
2109
  "args_schema": CreateBranchName,
1993
2110
  },
2111
+ {
2112
+ "ref": self.delete_branch,
2113
+ "name": "delete_branch",
2114
+ "mode": "delete_branch",
2115
+ "description": DELETE_BRANCH_PROMPT,
2116
+ "args_schema": DeleteBranchName,
2117
+ },
1994
2118
  {
1995
2119
  "ref": self.get_files_from_directory,
1996
2120
  "name": "get_files_from_directory",