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,20 +1,19 @@
1
1
  import json
2
2
  import logging
3
- from typing import Dict, List, Optional, Union, Any, Generator
3
+ from typing import Dict, List, Literal, Optional, Union, Any, Generator
4
4
 
5
5
  import pandas as pd
6
+ from langchain_core.documents import Document
6
7
  from langchain_core.tools import ToolException
7
8
  from openai import BadRequestError
8
9
  from pydantic import SecretStr, create_model, model_validator
9
10
  from pydantic.fields import Field, PrivateAttr
10
11
  from testrail_api import StatusCodeError, TestRailAPI
11
12
 
12
- from ..chunkers.code.constants import get_file_extension
13
- from ..elitea_base import BaseVectorStoreToolApiWrapper, extend_with_vector_tools
14
- from langchain_core.documents import Document
15
-
13
+ from ..chunkers.code.constants import get_file_extension, image_extensions
14
+ from ..non_code_indexer_toolkit import NonCodeIndexerToolkit
15
+ from ..utils.available_tools_decorator import extend_with_parent_available_tools
16
16
  from ...runtime.utils.utils import IndexerKeywords
17
- from ..utils.content_parser import parse_file_content
18
17
 
19
18
  try:
20
19
  from alita_sdk.runtime.langchain.interfaces.llm_processor import get_embeddings
@@ -118,6 +117,13 @@ getCases = create_model(
118
117
  description="A list of case field keys to include in the data output. If None, defaults to ['title', 'id'].",
119
118
  ),
120
119
  ),
120
+ suite_id=(Optional[str],
121
+ Field(
122
+ default=None,
123
+ description="[Optional] Suite id for test cases extraction in case "
124
+ "project is in multiple suite mode (setting 3)",
125
+ ),
126
+ ),
121
127
  )
122
128
 
123
129
  getCasesByFilter = create_model(
@@ -292,6 +298,18 @@ updateCase = create_model(
292
298
  ),
293
299
  )
294
300
 
301
+ getSuites = create_model(
302
+ "getSuites",
303
+ project_id=(str, Field(description="Project id")),
304
+ output_format=(
305
+ str,
306
+ Field(
307
+ default="json",
308
+ description="Desired output format. Supported values: 'json', 'csv', 'markdown'. Defaults to 'json'.",
309
+ ),
310
+ ),
311
+ )
312
+
295
313
  SUPPORTED_KEYS = {
296
314
  "id", "title", "section_id", "template_id", "type_id", "priority_id", "milestone_id",
297
315
  "refs", "created_by", "created_on", "updated_by", "updated_on", "estimate",
@@ -301,7 +319,7 @@ SUPPORTED_KEYS = {
301
319
  }
302
320
 
303
321
 
304
- class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
322
+ class TestrailAPIWrapper(NonCodeIndexerToolkit):
305
323
  url: str
306
324
  password: Optional[SecretStr] = None,
307
325
  email: Optional[str] = None,
@@ -322,7 +340,76 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
322
340
  password = values.get("password")
323
341
  email = values.get("email")
324
342
  cls._client = TestRailAPI(url, email, password)
325
- return values
343
+ return super().validate_toolkit(values)
344
+
345
+ def _is_suite_id_required(self, project_id: str) -> bool:
346
+ """
347
+ Returns True if project requires suite_id (multiple suite or baselines mode), otherwise False.
348
+ Args:
349
+ project_id: The TestRail project ID to check
350
+ """
351
+ try:
352
+ project = self._client.projects.get_project(project_id=project_id)
353
+ # 1 for single suite mode, 2 for single suite + baselines, 3 for multiple suites
354
+ suite_mode = project.get('suite_mode', 1)
355
+ return suite_mode == 2 or suite_mode == 3
356
+ except StatusCodeError:
357
+ return False
358
+
359
+ def _fetch_cases_with_suite_handling(
360
+ self,
361
+ project_id: str,
362
+ suite_id: Optional[str] = None,
363
+ **api_params
364
+ ) -> List[Dict]:
365
+ """
366
+ Unified method to fetch test cases with proper TestRail suite mode handling.
367
+
368
+ Args:
369
+ project_id: The TestRail project ID
370
+ suite_id: Optional suite ID to filter by
371
+ **api_params: Additional parameters to pass to the get_cases API call
372
+
373
+ Returns:
374
+ List of test case dictionaries
375
+ """
376
+ def _extract_cases_from_response(response):
377
+ """Extract cases from API response, supporting both old and new testrail_api versions."""
378
+ return response.get('cases', []) if isinstance(response, dict) else response
379
+
380
+ suite_required = self._is_suite_id_required(project_id=project_id)
381
+ all_cases = []
382
+
383
+ if suite_required:
384
+ # Suite modes 2 & 3: Require suite_id parameter
385
+ if suite_id:
386
+ response = self._client.cases.get_cases(
387
+ project_id=project_id, suite_id=int(suite_id), **api_params
388
+ )
389
+ cases_from_suite = _extract_cases_from_response(response)
390
+ all_cases.extend(cases_from_suite)
391
+ else:
392
+ suites = self._get_raw_suites(project_id)
393
+ suite_ids = [suite['id'] for suite in suites if 'id' in suite]
394
+ for current_suite_id in suite_ids:
395
+ try:
396
+ response = self._client.cases.get_cases(
397
+ project_id=project_id, suite_id=int(current_suite_id), **api_params
398
+ )
399
+ cases_from_suite = _extract_cases_from_response(response)
400
+ all_cases.extend(cases_from_suite)
401
+ except StatusCodeError:
402
+ continue
403
+ else:
404
+ # Suite mode 1: Can fetch all cases directly without suite_id
405
+ try:
406
+ response = self._client.cases.get_cases(project_id=project_id, **api_params)
407
+ cases_from_project = _extract_cases_from_response(response)
408
+ all_cases.extend(cases_from_project)
409
+ except StatusCodeError as e:
410
+ logger.warning(f"Unable to fetch cases at project level: {e}")
411
+
412
+ return all_cases
326
413
 
327
414
  def add_cases(self, add_test_cases_data: str):
328
415
  """Adds new test cases into Testrail per defined parameters.
@@ -390,7 +477,8 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
390
477
  return f"Extracted test case:\n{str(extracted_case)}"
391
478
 
392
479
  def get_cases(
393
- self, project_id: str, output_format: str = "json", keys: Optional[List[str]] = None
480
+ self, project_id: str, output_format: str = "json", keys: Optional[List[str]] = None,
481
+ suite_id: Optional[str] = None
394
482
  ) -> Union[str, ToolException]:
395
483
  """
396
484
  Extracts a list of test cases in the specified format: `json`, `csv`, or `markdown`.
@@ -411,10 +499,10 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
411
499
  invalid_keys = [key for key in keys if key not in SUPPORTED_KEYS]
412
500
 
413
501
  try:
414
- extracted_cases = self._client.cases.get_cases(project_id=project_id)
415
- cases = extracted_cases.get("cases")
502
+ # Use unified suite handling method
503
+ cases = self._fetch_cases_with_suite_handling(project_id=project_id, suite_id=suite_id)
416
504
 
417
- if cases is None:
505
+ if not cases:
418
506
  return ToolException("No test cases found in the extracted data.")
419
507
 
420
508
  extracted_cases_data = [
@@ -467,24 +555,36 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
467
555
  "json_case_arguments must be a JSON string or dictionary."
468
556
  )
469
557
  self._log_tool_event(message=f"Extract test cases per filter {params}", tool_name='get_cases_by_filter')
470
- extracted_cases = self._client.cases.get_cases(
471
- project_id=project_id, **params
558
+
559
+ # Extract suite_id from params for unified handling
560
+ suite_id_in_params = params.pop('suite_id', None)
561
+ suite_id = str(suite_id_in_params) if suite_id_in_params else None
562
+
563
+ # Use unified suite handling method with remaining filter parameters
564
+ cases = self._fetch_cases_with_suite_handling(
565
+ project_id=project_id,
566
+ suite_id=suite_id,
567
+ **params
472
568
  )
473
- self._log_tool_event(message=f"Test cases were extracted", tool_name='get_cases_by_filter')
474
- # support old versions of testrail_api
475
- cases = extracted_cases.get("cases") if isinstance(extracted_cases, dict) else extracted_cases
476
569
 
477
- if cases is None:
570
+ self._log_tool_event(message="Test cases were extracted", tool_name='get_cases_by_filter')
571
+
572
+ if not cases:
478
573
  return ToolException("No test cases found in the extracted data.")
479
574
 
480
575
  if keys is None:
481
576
  return self._to_markup(cases, output_format)
482
577
 
483
- extracted_cases_data = [
484
- {key: case.get(key, "N/A") for key in keys} for case in cases
485
- ]
578
+ extracted_cases_data = []
579
+ for case in cases:
580
+ case_dict = {}
581
+ for key in keys:
582
+ if key in case:
583
+ case_dict[key] = case[key]
584
+ if case_dict:
585
+ extracted_cases_data.append(case_dict)
486
586
 
487
- if extracted_cases_data is None:
587
+ if not extracted_cases_data:
488
588
  return ToolException("No valid test case data found to format.")
489
589
 
490
590
  result = self._to_markup(extracted_cases_data, output_format)
@@ -532,47 +632,86 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
532
632
  return (
533
633
  f"Test case #{case_id} has been updated at '{updated_case['updated_on']}')"
534
634
  )
635
+
636
+ def _get_raw_suites(self, project_id: str) -> List[Dict]:
637
+ """
638
+ Internal helper to get raw suite data from TestRail API.
639
+ Handles both old and new testrail_api response formats.
640
+ """
641
+ suites_response = self._client.suites.get_suites(project_id=project_id)
642
+ if isinstance(suites_response, dict) and 'suites' in suites_response:
643
+ return suites_response['suites']
644
+ else:
645
+ return suites_response if isinstance(suites_response, list) else []
646
+
647
+ def get_suites(self, project_id: str, output_format: str = "json",) -> Union[str, ToolException]:
648
+ """Extracts a list of test suites for a given project from Testrail"""
649
+ try:
650
+ suites = self._get_raw_suites(project_id)
651
+
652
+ if not suites:
653
+ return ToolException("No test suites found for the specified project.")
654
+
655
+ suite_dicts = []
656
+ for suite in suites:
657
+ if isinstance(suite, dict):
658
+ suite_dict = {}
659
+ for field in ["id", "name", "description", "project_id", "is_baseline",
660
+ "completed_on", "url", "is_master", "is_completed"]:
661
+ if field in suite:
662
+ suite_dict[field] = suite[field]
663
+ suite_dicts.append(suite_dict)
664
+ else:
665
+ suite_dicts.append({"suite": str(suite)})
666
+ return self._to_markup(suite_dicts, output_format)
667
+ except StatusCodeError as e:
668
+ return ToolException(f"Unable to extract test suites: {e}")
535
669
 
536
670
  def _base_loader(self, project_id: str,
537
671
  suite_id: Optional[str] = None,
538
672
  section_id: Optional[int] = None,
539
673
  title_keyword: Optional[str] = None,
674
+ chunking_tool: str = None,
540
675
  **kwargs: Any
541
676
  ) -> Generator[Document, None, None]:
542
677
  self._include_attachments = kwargs.get('include_attachments', False)
543
678
  self._skip_attachment_extensions = kwargs.get('skip_attachment_extensions', [])
544
679
 
545
680
  try:
546
- if suite_id:
547
- resp = self._client.cases.get_cases(project_id=project_id, suite_id=int(suite_id))
548
- cases = resp.get('cases', [])
549
- else:
550
- resp = self._client.cases.get_cases(project_id=project_id)
551
- cases = resp.get('cases', [])
681
+ # Use unified suite handling method
682
+ cases = self._fetch_cases_with_suite_handling(project_id=project_id, suite_id=suite_id)
552
683
  except StatusCodeError as e:
553
684
  raise ToolException(f"Unable to extract test cases: {e}")
554
- # Apply filters
685
+
686
+ # Apply filters
555
687
  if section_id is not None:
556
688
  cases = [case for case in cases if case.get('section_id') == section_id]
557
689
  if title_keyword is not None:
558
690
  cases = [case for case in cases if title_keyword.lower() in case.get('title', '').lower()]
559
691
 
560
692
  for case in cases:
561
- yield Document(page_content=json.dumps(case), metadata={
693
+ metadata = {
562
694
  'project_id': project_id,
563
695
  'title': case.get('title', ''),
564
696
  'suite_id': suite_id or case.get('suite_id', ''),
565
697
  'id': str(case.get('id', '')),
566
698
  IndexerKeywords.UPDATED_ON.value: case.get('updated_on') or -1,
567
699
  'labels': [lbl['title'] for lbl in case.get('labels', [])],
568
- 'type': case.get('type_id') or -1,
700
+ 'type': "testrail_test_case",
569
701
  'priority': case.get('priority_id') or -1,
570
702
  'milestone': case.get('milestone_id') or -1,
571
703
  'estimate': case.get('estimate') or '',
572
704
  'automation_type': case.get('custom_automation_type') or -1,
573
705
  'section_id': case.get('section_id') or -1,
574
706
  'entity_type': 'test_case',
575
- })
707
+ }
708
+ if chunking_tool:
709
+ # content is in metadata for chunking tool post-processing
710
+ metadata[IndexerKeywords.CONTENT_IN_BYTES.value] = json.dumps(case).encode("utf-8")
711
+ page_content = ""
712
+ else:
713
+ page_content = json.dumps(case)
714
+ yield Document(page_content=page_content, metadata=metadata)
576
715
 
577
716
  def _process_document(self, document: Document) -> Generator[Document, None, None]:
578
717
  """
@@ -592,37 +731,56 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
592
731
  return
593
732
 
594
733
  # get base data from the document required to extract attachments and other metadata
595
- base_data = json.loads(document.page_content)
734
+ base_data = document.metadata
596
735
  case_id = base_data.get("id")
597
736
 
598
737
  # get a list of attachments for the case
599
- attachments = self._client.attachments.get_attachments_for_case_bulk(case_id=case_id)
738
+ attachments_response = self._client.attachments.get_attachments_for_case(case_id=case_id)
739
+
740
+ # Extract attachments from response - handle both old and new API response formats
741
+ if isinstance(attachments_response, dict) and 'attachments' in attachments_response:
742
+ attachments = attachments_response['attachments']
743
+ else:
744
+ attachments = attachments_response if isinstance(attachments_response, list) else []
600
745
 
601
746
  # process each attachment to extract its content
602
747
  for attachment in attachments:
603
- if get_file_extension(attachment['filename']) in self._skip_attachment_extensions:
748
+ attachment_name = attachment.get('filename') or attachment.get('name')
749
+ attachment['filename'] = attachment_name
750
+
751
+ # Handle filetype: use existing field if present, otherwise extract from filename
752
+ if 'filetype' not in attachment or not attachment['filetype']:
753
+ file_extension = get_file_extension(attachment_name)
754
+ attachment['filetype'] = file_extension.lstrip('.')
755
+
756
+ # Handle is_image: use existing field if present, otherwise check file extension
757
+ if 'is_image' not in attachment:
758
+ file_extension = get_file_extension(attachment_name)
759
+ attachment['is_image'] = file_extension in image_extensions
760
+
761
+ if get_file_extension(attachment_name) in self._skip_attachment_extensions:
604
762
  logger.info(f"Skipping attachment {attachment['filename']} with unsupported extension.")
605
763
  continue
606
764
 
607
765
  attachment_id = f"attach_{attachment['id']}"
608
- # add attachment id to metadata of parent
609
- document.metadata.setdefault(IndexerKeywords.DEPENDENT_DOCS.value, []).append(attachment_id)
610
- # TODO: pass it to chunkers
611
- yield Document(page_content=self._process_attachment(attachment),
766
+ file_name, content_bytes = self._process_attachment(attachment)
767
+ yield Document(page_content="",
612
768
  metadata={
613
769
  'project_id': base_data.get('project_id', ''),
770
+ 'updated_on': base_data.get('updated_on', ''),
614
771
  'id': str(attachment_id),
615
- IndexerKeywords.PARENT.value: str(case_id),
616
772
  'filename': attachment['filename'],
617
773
  'filetype': attachment['filetype'],
618
774
  'created_on': attachment['created_on'],
619
775
  'entity_type': 'test_case_attachment',
620
776
  'is_image': attachment['is_image'],
777
+ IndexerKeywords.CONTENT_FILE_NAME.value: file_name,
778
+ IndexerKeywords.CONTENT_IN_BYTES.value: content_bytes
621
779
  })
622
780
  except json.JSONDecodeError as e:
623
781
  raise ToolException(f"Failed to decode JSON from document: {e}")
624
782
 
625
- def _process_attachment(self, attachment: Dict[str, Any]) -> str:
783
+ def _process_attachment(self, attachment: Dict[str, Any]) -> tuple[Any, bytes]:
626
784
  """
627
785
  Processes an attachment to extract its content.
628
786
 
@@ -633,18 +791,21 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
633
791
  str: string description of the attachment.
634
792
  """
635
793
 
636
- page_content = "This filetype is not supported."
637
794
  if attachment['filetype'] == 'txt' :
638
- page_content = self._client.get(endpoint=f"get_attachment/{attachment['id']}")
795
+ response = self._client.get(endpoint=f"get_attachment/{attachment['id']}")
796
+ if hasattr(response, "content"):
797
+ return attachment['filename'], response.content
798
+ elif isinstance(response, str):
799
+ return attachment['filename'], response.encode("utf-8")
639
800
  else:
640
801
  try:
641
802
  attachment_path = self._client.attachments.get_attachment(attachment_id=attachment['id'], path=f"./{attachment['filename']}")
642
- page_content = parse_file_content(file_name=attachment['filename'], file_content=attachment_path.read_bytes(), llm=self.llm, is_capture_image=True)
803
+ return attachment['filename'], attachment_path.read_bytes()
643
804
  except BadRequestError as ai_e:
644
805
  logger.error(f"Unable to parse page's content with type: {attachment['filetype']} due to AI service issues: {ai_e}")
645
806
  except Exception as e:
646
807
  logger.error(f"Unable to parse page's content with type: {attachment['filetype']}: {e}")
647
- return page_content
808
+ return attachment['filename'], b"This filetype is not supported."
648
809
 
649
810
  def _index_tool_params(self):
650
811
  return {
@@ -658,6 +819,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
658
819
  'skip_attachment_extensions': (Optional[List[str]], Field(
659
820
  description="List of file extensions to skip when processing attachments: i.e. ['.png', '.jpg']",
660
821
  default=[])),
822
+ 'chunking_tool':(Literal['json', ''], Field(description="Name of chunking tool", default='json'))
661
823
  }
662
824
 
663
825
  def _to_markup(self, data: List[Dict], output_format: str) -> str:
@@ -687,7 +849,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
687
849
  if output_format == "markdown":
688
850
  return df.to_markdown(index=False)
689
851
 
690
- @extend_with_vector_tools
852
+ @extend_with_parent_available_tools
691
853
  def get_available_tools(self):
692
854
  tools = [
693
855
  {
@@ -725,6 +887,12 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
725
887
  "ref": self.update_case,
726
888
  "description": self.update_case.__doc__,
727
889
  "args_schema": updateCase,
890
+ },
891
+ {
892
+ "name": "get_suites",
893
+ "ref": self.get_suites,
894
+ "description": self.get_suites.__doc__,
895
+ "args_schema": getSuites,
728
896
  }
729
897
  ]
730
898
  return tools
@@ -7,6 +7,8 @@ import requests
7
7
  from pydantic import create_model, Field
8
8
 
9
9
 
10
+ # DEPRECATED: Tool names no longer use prefixes
11
+ # Kept for backward compatibility only
10
12
  TOOLKIT_SPLITTER = "___"
11
13
  TOOL_NAME_LIMIT = 64
12
14
 
@@ -22,10 +24,13 @@ def clean_string(s: str, max_length: int = 0):
22
24
 
23
25
 
24
26
  def get_max_toolkit_length(selected_tools: Any):
25
- """Calculates the maximum length of the toolkit name based on the selected tools per toolkit."""
26
-
27
- longest_tool_name_length = max(len(tool_name) for tool_name in selected_tools.keys())
28
- return TOOL_NAME_LIMIT - longest_tool_name_length - len(TOOLKIT_SPLITTER)
27
+ """DEPRECATED: Calculates the maximum length of the toolkit name.
28
+
29
+ This function is deprecated as tool names no longer use prefixes.
30
+ Returns a fixed value for backward compatibility.
31
+ """
32
+ # Return a reasonable default since we no longer use prefixes
33
+ return 50
29
34
 
30
35
 
31
36
  def parse_list(list_str: str = None) -> List[str]:
@@ -55,6 +60,8 @@ def parse_type(type_str):
55
60
  """Parse a type string into an actual Python type."""
56
61
  try:
57
62
  # Evaluate the type string using builtins and imported modules
63
+ if type_str == 'number':
64
+ type_str = 'int'
58
65
  return eval(type_str, {**vars(builtins), **globals()})
59
66
  except Exception as e:
60
67
  print(f"Error parsing type: {e}")
@@ -95,3 +102,20 @@ def check_connection_response(check_fun):
95
102
  else:
96
103
  return f"Service Unreachable: return code {response.status_code}"
97
104
  return _wrapper
105
+
106
+
107
+ def make_json_serializable(obj):
108
+ if isinstance(obj, BaseModel):
109
+ return obj.model_dump()
110
+ if isinstance(obj, dict):
111
+ return {k: make_json_serializable(v) for k, v in obj.items()}
112
+ if isinstance(obj, list):
113
+ return [make_json_serializable(i) for i in obj]
114
+ if isinstance(obj, bool):
115
+ return bool(obj)
116
+ if isinstance(obj, (str, int, float)) or obj is None:
117
+ return obj
118
+ # Fallback: handle objects that look like booleans but were not caught above
119
+ if str(obj) in ("True", "False"):
120
+ return str(obj) == "True"
121
+ return str(obj)