alita-sdk 0.3.462__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 (261) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +258 -0
  3. alita_sdk/cli/agent_executor.py +15 -3
  4. alita_sdk/cli/agent_loader.py +56 -8
  5. alita_sdk/cli/agent_ui.py +93 -31
  6. alita_sdk/cli/agents.py +2274 -230
  7. alita_sdk/cli/callbacks.py +96 -25
  8. alita_sdk/cli/cli.py +10 -1
  9. alita_sdk/cli/config.py +162 -9
  10. alita_sdk/cli/context/__init__.py +30 -0
  11. alita_sdk/cli/context/cleanup.py +198 -0
  12. alita_sdk/cli/context/manager.py +731 -0
  13. alita_sdk/cli/context/message.py +285 -0
  14. alita_sdk/cli/context/strategies.py +289 -0
  15. alita_sdk/cli/context/token_estimation.py +127 -0
  16. alita_sdk/cli/input_handler.py +419 -0
  17. alita_sdk/cli/inventory.py +1073 -0
  18. alita_sdk/cli/testcases/__init__.py +94 -0
  19. alita_sdk/cli/testcases/data_generation.py +119 -0
  20. alita_sdk/cli/testcases/discovery.py +96 -0
  21. alita_sdk/cli/testcases/executor.py +84 -0
  22. alita_sdk/cli/testcases/logger.py +85 -0
  23. alita_sdk/cli/testcases/parser.py +172 -0
  24. alita_sdk/cli/testcases/prompts.py +91 -0
  25. alita_sdk/cli/testcases/reporting.py +125 -0
  26. alita_sdk/cli/testcases/setup.py +108 -0
  27. alita_sdk/cli/testcases/test_runner.py +282 -0
  28. alita_sdk/cli/testcases/utils.py +39 -0
  29. alita_sdk/cli/testcases/validation.py +90 -0
  30. alita_sdk/cli/testcases/workflow.py +196 -0
  31. alita_sdk/cli/toolkit.py +14 -17
  32. alita_sdk/cli/toolkit_loader.py +35 -5
  33. alita_sdk/cli/tools/__init__.py +36 -2
  34. alita_sdk/cli/tools/approval.py +224 -0
  35. alita_sdk/cli/tools/filesystem.py +910 -64
  36. alita_sdk/cli/tools/planning.py +389 -0
  37. alita_sdk/cli/tools/terminal.py +414 -0
  38. alita_sdk/community/__init__.py +72 -12
  39. alita_sdk/community/inventory/__init__.py +236 -0
  40. alita_sdk/community/inventory/config.py +257 -0
  41. alita_sdk/community/inventory/enrichment.py +2137 -0
  42. alita_sdk/community/inventory/extractors.py +1469 -0
  43. alita_sdk/community/inventory/ingestion.py +3172 -0
  44. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  45. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  46. alita_sdk/community/inventory/parsers/base.py +295 -0
  47. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  48. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  49. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  50. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  51. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  52. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  53. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  54. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  55. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  56. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  57. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  58. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  59. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  60. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  61. alita_sdk/community/inventory/patterns/loader.py +348 -0
  62. alita_sdk/community/inventory/patterns/registry.py +198 -0
  63. alita_sdk/community/inventory/presets.py +535 -0
  64. alita_sdk/community/inventory/retrieval.py +1403 -0
  65. alita_sdk/community/inventory/toolkit.py +173 -0
  66. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  67. alita_sdk/community/inventory/visualize.py +1370 -0
  68. alita_sdk/configurations/__init__.py +1 -1
  69. alita_sdk/configurations/ado.py +141 -20
  70. alita_sdk/configurations/bitbucket.py +0 -3
  71. alita_sdk/configurations/confluence.py +76 -42
  72. alita_sdk/configurations/figma.py +76 -0
  73. alita_sdk/configurations/gitlab.py +17 -5
  74. alita_sdk/configurations/openapi.py +329 -0
  75. alita_sdk/configurations/qtest.py +72 -1
  76. alita_sdk/configurations/report_portal.py +96 -0
  77. alita_sdk/configurations/sharepoint.py +148 -0
  78. alita_sdk/configurations/testio.py +83 -0
  79. alita_sdk/runtime/clients/artifact.py +3 -3
  80. alita_sdk/runtime/clients/client.py +353 -48
  81. alita_sdk/runtime/clients/sandbox_client.py +0 -21
  82. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  83. alita_sdk/runtime/langchain/assistant.py +123 -26
  84. alita_sdk/runtime/langchain/constants.py +642 -1
  85. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  86. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  87. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
  88. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  89. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  90. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  91. alita_sdk/runtime/langchain/langraph_agent.py +279 -73
  92. alita_sdk/runtime/langchain/utils.py +82 -15
  93. alita_sdk/runtime/llms/preloaded.py +2 -6
  94. alita_sdk/runtime/skills/__init__.py +91 -0
  95. alita_sdk/runtime/skills/callbacks.py +498 -0
  96. alita_sdk/runtime/skills/discovery.py +540 -0
  97. alita_sdk/runtime/skills/executor.py +610 -0
  98. alita_sdk/runtime/skills/input_builder.py +371 -0
  99. alita_sdk/runtime/skills/models.py +330 -0
  100. alita_sdk/runtime/skills/registry.py +355 -0
  101. alita_sdk/runtime/skills/skill_runner.py +330 -0
  102. alita_sdk/runtime/toolkits/__init__.py +7 -0
  103. alita_sdk/runtime/toolkits/application.py +21 -9
  104. alita_sdk/runtime/toolkits/artifact.py +15 -5
  105. alita_sdk/runtime/toolkits/datasource.py +13 -6
  106. alita_sdk/runtime/toolkits/mcp.py +139 -251
  107. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  108. alita_sdk/runtime/toolkits/planning.py +178 -0
  109. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  110. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  111. alita_sdk/runtime/toolkits/tools.py +238 -32
  112. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  113. alita_sdk/runtime/tools/__init__.py +3 -1
  114. alita_sdk/runtime/tools/application.py +20 -6
  115. alita_sdk/runtime/tools/artifact.py +511 -28
  116. alita_sdk/runtime/tools/data_analysis.py +183 -0
  117. alita_sdk/runtime/tools/function.py +43 -15
  118. alita_sdk/runtime/tools/image_generation.py +50 -44
  119. alita_sdk/runtime/tools/llm.py +852 -67
  120. alita_sdk/runtime/tools/loop.py +3 -1
  121. alita_sdk/runtime/tools/loop_output.py +3 -1
  122. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  123. alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
  124. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  125. alita_sdk/runtime/tools/planning/models.py +246 -0
  126. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  127. alita_sdk/runtime/tools/router.py +2 -4
  128. alita_sdk/runtime/tools/sandbox.py +9 -6
  129. alita_sdk/runtime/tools/skill_router.py +776 -0
  130. alita_sdk/runtime/tools/tool.py +3 -1
  131. alita_sdk/runtime/tools/vectorstore.py +7 -2
  132. alita_sdk/runtime/tools/vectorstore_base.py +51 -11
  133. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  134. alita_sdk/runtime/utils/constants.py +5 -1
  135. alita_sdk/runtime/utils/mcp_client.py +492 -0
  136. alita_sdk/runtime/utils/mcp_oauth.py +202 -5
  137. alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
  138. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  139. alita_sdk/runtime/utils/serialization.py +155 -0
  140. alita_sdk/runtime/utils/streamlit.py +6 -10
  141. alita_sdk/runtime/utils/toolkit_utils.py +16 -5
  142. alita_sdk/runtime/utils/utils.py +36 -0
  143. alita_sdk/tools/__init__.py +113 -29
  144. alita_sdk/tools/ado/repos/__init__.py +51 -33
  145. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  146. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  147. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  148. alita_sdk/tools/ado/utils.py +1 -18
  149. alita_sdk/tools/ado/wiki/__init__.py +25 -8
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  151. alita_sdk/tools/ado/work_item/__init__.py +26 -9
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  155. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  156. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  157. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  158. alita_sdk/tools/base/tool.py +5 -1
  159. alita_sdk/tools/base_indexer_toolkit.py +170 -45
  160. alita_sdk/tools/bitbucket/__init__.py +17 -12
  161. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  162. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  163. alita_sdk/tools/browser/__init__.py +5 -4
  164. alita_sdk/tools/carrier/__init__.py +5 -6
  165. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  166. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  167. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  168. alita_sdk/tools/chunkers/__init__.py +3 -1
  169. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  170. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  171. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  172. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  173. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  174. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  175. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  176. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  177. alita_sdk/tools/code/linter/__init__.py +10 -8
  178. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  179. alita_sdk/tools/code/sonar/__init__.py +10 -7
  180. alita_sdk/tools/code_indexer_toolkit.py +73 -23
  181. alita_sdk/tools/confluence/__init__.py +21 -15
  182. alita_sdk/tools/confluence/api_wrapper.py +78 -23
  183. alita_sdk/tools/confluence/loader.py +4 -2
  184. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  185. alita_sdk/tools/elastic/__init__.py +11 -8
  186. alita_sdk/tools/elitea_base.py +493 -30
  187. alita_sdk/tools/figma/__init__.py +58 -11
  188. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  189. alita_sdk/tools/figma/figma_client.py +73 -0
  190. alita_sdk/tools/figma/toon_tools.py +2748 -0
  191. alita_sdk/tools/github/__init__.py +13 -14
  192. alita_sdk/tools/github/github_client.py +224 -100
  193. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  194. alita_sdk/tools/github/schemas.py +14 -5
  195. alita_sdk/tools/github/tool.py +5 -1
  196. alita_sdk/tools/github/tool_prompts.py +9 -22
  197. alita_sdk/tools/gitlab/__init__.py +15 -11
  198. alita_sdk/tools/gitlab/api_wrapper.py +207 -41
  199. alita_sdk/tools/gitlab_org/__init__.py +10 -8
  200. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  201. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  202. alita_sdk/tools/google/bigquery/tool.py +5 -1
  203. alita_sdk/tools/google_places/__init__.py +10 -8
  204. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  205. alita_sdk/tools/jira/__init__.py +17 -11
  206. alita_sdk/tools/jira/api_wrapper.py +91 -40
  207. alita_sdk/tools/keycloak/__init__.py +11 -8
  208. alita_sdk/tools/localgit/__init__.py +9 -3
  209. alita_sdk/tools/localgit/local_git.py +62 -54
  210. alita_sdk/tools/localgit/tool.py +5 -1
  211. alita_sdk/tools/memory/__init__.py +11 -3
  212. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  213. alita_sdk/tools/ocr/__init__.py +11 -8
  214. alita_sdk/tools/openapi/__init__.py +490 -114
  215. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  216. alita_sdk/tools/openapi/tool.py +20 -0
  217. alita_sdk/tools/pandas/__init__.py +20 -12
  218. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  219. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  220. alita_sdk/tools/postman/__init__.py +11 -11
  221. alita_sdk/tools/pptx/__init__.py +10 -9
  222. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  223. alita_sdk/tools/qtest/__init__.py +30 -10
  224. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  225. alita_sdk/tools/rally/__init__.py +10 -8
  226. alita_sdk/tools/rally/api_wrapper.py +1 -1
  227. alita_sdk/tools/report_portal/__init__.py +12 -9
  228. alita_sdk/tools/salesforce/__init__.py +10 -9
  229. alita_sdk/tools/servicenow/__init__.py +17 -14
  230. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  231. alita_sdk/tools/sharepoint/__init__.py +10 -8
  232. alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
  233. alita_sdk/tools/slack/__init__.py +10 -8
  234. alita_sdk/tools/slack/api_wrapper.py +2 -2
  235. alita_sdk/tools/sql/__init__.py +11 -9
  236. alita_sdk/tools/testio/__init__.py +10 -8
  237. alita_sdk/tools/testrail/__init__.py +11 -8
  238. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  239. alita_sdk/tools/utils/__init__.py +9 -4
  240. alita_sdk/tools/utils/content_parser.py +77 -3
  241. alita_sdk/tools/utils/text_operations.py +410 -0
  242. alita_sdk/tools/utils/tool_prompts.py +79 -0
  243. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  244. alita_sdk/tools/xray/__init__.py +12 -9
  245. alita_sdk/tools/yagmail/__init__.py +9 -3
  246. alita_sdk/tools/zephyr/__init__.py +9 -7
  247. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
  248. alita_sdk/tools/zephyr_essential/__init__.py +10 -8
  249. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  250. alita_sdk/tools/zephyr_essential/client.py +2 -2
  251. alita_sdk/tools/zephyr_scale/__init__.py +11 -9
  252. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  253. alita_sdk/tools/zephyr_squad/__init__.py +10 -8
  254. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
  255. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  256. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  257. alita_sdk-0.3.462.dist-info/RECORD +0 -384
  258. alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
  259. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  260. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  261. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,90 @@
1
+ """
2
+ Validation utilities for test execution.
3
+
4
+ Handles JSON extraction, fallback results, and diagnostics.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ from pathlib import Path
10
+ from typing import Dict, Any
11
+ from rich.console import Console
12
+
13
+ logger = logging.getLogger(__name__)
14
+ console = Console()
15
+
16
+
17
+ def extract_json_from_text(text: str) -> dict:
18
+ """Extract JSON object from text using brace counting."""
19
+ start_idx = text.find('{')
20
+ if start_idx == -1:
21
+ raise ValueError("No JSON found in text")
22
+
23
+ brace_count = 0
24
+ end_idx = -1
25
+ for i, char in enumerate(text[start_idx:], start=start_idx):
26
+ if char == '{':
27
+ brace_count += 1
28
+ elif char == '}':
29
+ brace_count -= 1
30
+ if brace_count == 0:
31
+ end_idx = i + 1
32
+ break
33
+
34
+ if end_idx == -1:
35
+ raise ValueError("Could not find matching closing brace")
36
+
37
+ return json.loads(text[start_idx:end_idx])
38
+
39
+
40
+ def create_fallback_result_for_test(test_case: Dict[str, Any], test_file: Path, reason: str = 'Validation failed') -> Dict[str, Any]:
41
+ """Create a fallback result for a single test case with detailed step information.
42
+
43
+ Args:
44
+ test_case: Parsed test case data
45
+ test_file: Path to test case file
46
+ reason: Reason for fallback
47
+
48
+ Returns:
49
+ Fallback test result dict with step details
50
+ """
51
+ fallback_steps = []
52
+ for step_info in test_case.get('steps', []):
53
+ fallback_steps.append({
54
+ 'step_number': step_info['number'],
55
+ 'title': step_info['title'],
56
+ 'passed': False,
57
+ 'details': reason
58
+ })
59
+
60
+ return {
61
+ 'title': test_case['name'],
62
+ 'passed': False,
63
+ 'file': test_file.name,
64
+ 'step_results': fallback_steps,
65
+ 'validation_error': reason
66
+ }
67
+
68
+
69
+ def print_validation_diagnostics(validation_output: str) -> None:
70
+ """Print diagnostic information for validation output.
71
+
72
+ Args:
73
+ validation_output: The validation output to diagnose
74
+ """
75
+ console.print(f"\n[bold red]🔍 Diagnostic Information:[/bold red]")
76
+ console.print(f"[dim]Output length: {len(validation_output)} characters[/dim]")
77
+
78
+ # Check for key JSON elements
79
+ has_json = '{' in validation_output and '}' in validation_output
80
+ has_fields = 'test_number' in validation_output and 'steps' in validation_output
81
+
82
+ console.print(f"[dim]Has JSON structure: {has_json}[/dim]")
83
+ console.print(f"[dim]Has required fields: {has_fields}[/dim]")
84
+
85
+ # Show relevant excerpt
86
+ if len(validation_output) > 400:
87
+ console.print(f"\n[red]First 200 chars:[/red] [dim]{validation_output[:200]}[/dim]")
88
+ console.print(f"[red]Last 200 chars:[/red] [dim]{validation_output[-200:]}[/dim]")
89
+ else:
90
+ console.print(f"\n[red]Full output:[/red] [dim]{validation_output}[/dim]")
@@ -0,0 +1,196 @@
1
+ """
2
+ Main workflow orchestration for test case execution.
3
+
4
+ Coordinates the entire test execution flow from parsing to reporting.
5
+ """
6
+
7
+ import logging
8
+ import uuid
9
+ from pathlib import Path
10
+ from typing import List, Dict, Any, Optional, Tuple
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def parse_all_test_cases(
16
+ test_case_files_list: List[Path],
17
+ master_log
18
+ ) -> List[Dict[str, Any]]:
19
+ """Parse all test case files.
20
+
21
+ Args:
22
+ test_case_files_list: List of test case files to parse
23
+ master_log: Log capture instance
24
+
25
+ Returns:
26
+ List of parsed test case dicts with 'file' and 'data' keys
27
+ """
28
+ from .parser import parse_test_case
29
+
30
+ parsed_test_cases = []
31
+ for test_file in test_case_files_list:
32
+ try:
33
+ test_case = parse_test_case(str(test_file))
34
+ parsed_test_cases.append({
35
+ 'file': test_file,
36
+ 'data': test_case
37
+ })
38
+ except Exception as e:
39
+ master_log.print(f"[yellow]⚠ Warning: Failed to parse {test_file.name}: {e}[/yellow]")
40
+ logger.debug(f"Parse error for {test_file.name}: {e}", exc_info=True)
41
+
42
+ return parsed_test_cases
43
+
44
+
45
+ def filter_test_cases_needing_data_gen(
46
+ parsed_test_cases: List[Dict[str, Any]]
47
+ ) -> List[Dict[str, Any]]:
48
+ """Filter test cases that need data generation.
49
+
50
+ Args:
51
+ parsed_test_cases: All parsed test cases
52
+
53
+ Returns:
54
+ Filtered list of test cases that require data generation
55
+ """
56
+ return [
57
+ tc for tc in parsed_test_cases
58
+ if tc['data'].get('generate_test_data', True)
59
+ ]
60
+
61
+
62
+ def execute_all_test_cases(
63
+ parsed_test_cases: List[Dict[str, Any]],
64
+ bulk_gen_chat_history: List[Dict[str, str]],
65
+ test_cases_path: Path,
66
+ agent_def: Dict[str, Any],
67
+ validator_def: Optional[Dict[str, Any]],
68
+ client,
69
+ config,
70
+ model: Optional[str],
71
+ temperature: Optional[float],
72
+ max_tokens: Optional[int],
73
+ work_dir: str,
74
+ master_log,
75
+ setup_executor_func,
76
+ verbose: bool = True,
77
+ debug: bool = False,
78
+ ) -> List[Dict[str, Any]]:
79
+ """Execute all test cases and return results.
80
+
81
+ Args:
82
+ parsed_test_cases: List of parsed test cases
83
+ bulk_gen_chat_history: Chat history from data generation
84
+ test_cases_path: Path to test cases directory
85
+ agent_def: Test runner agent definition
86
+ validator_def: Validator agent definition (optional)
87
+ client: API client
88
+ config: CLI configuration
89
+ model: Model override
90
+ temperature: Temperature override
91
+ max_tokens: Max tokens override
92
+ work_dir: Working directory
93
+ master_log: Log capture instance
94
+ setup_executor_func: Function to setup executor
95
+
96
+ Returns:
97
+ List of test result dicts
98
+ """
99
+ from .parser import resolve_toolkit_config_path
100
+ from .utils import extract_toolkit_name
101
+ from .executor import cleanup_executor_cache
102
+ from .test_runner import execute_single_test_case, validate_single_test_case
103
+ from .validation import create_fallback_result_for_test
104
+
105
+ if not parsed_test_cases:
106
+ master_log.print("[yellow]No test cases to execute[/yellow]")
107
+ return []
108
+
109
+ master_log.print(f"\n[bold yellow]📋 Executing test cases sequentially...[/bold yellow]\n")
110
+
111
+ # Show data generation context availability
112
+ if bulk_gen_chat_history:
113
+ master_log.print(f"[dim]✓ Data generation history available ({len(bulk_gen_chat_history)} messages) - shared with all test cases[/dim]\n")
114
+ else:
115
+ master_log.print(f"[dim]ℹ No data generation history (skipped or disabled)[/dim]\n")
116
+
117
+ # Executor caches
118
+ executor_cache = {}
119
+ validation_executor_cache = {}
120
+
121
+ # Execute each test case sequentially
122
+ test_results = []
123
+ total_tests = len(parsed_test_cases)
124
+
125
+ for idx, tc_info in enumerate(parsed_test_cases, 1):
126
+ test_case = tc_info['data']
127
+ test_file = tc_info['file']
128
+ test_name = test_case['name']
129
+
130
+ try:
131
+ # Resolve toolkit config path
132
+ toolkit_config_path = resolve_toolkit_config_path(
133
+ test_case.get('config_path', ''),
134
+ test_file,
135
+ test_cases_path
136
+ )
137
+
138
+ # Use cache key
139
+ cache_key = toolkit_config_path if toolkit_config_path else '__no_config__'
140
+
141
+ # Execute single test case
142
+ execution_output = execute_single_test_case(
143
+ tc_info, idx, total_tests, bulk_gen_chat_history, test_cases_path,
144
+ executor_cache, client, agent_def, config, model, temperature,
145
+ max_tokens, work_dir, master_log, setup_executor_func,
146
+ verbose=verbose,
147
+ debug=debug,
148
+ )
149
+
150
+ if not execution_output:
151
+ # Create fallback result for failed execution
152
+ test_results.append({
153
+ 'title': test_name,
154
+ 'passed': False,
155
+ 'file': test_file.name,
156
+ 'step_results': []
157
+ })
158
+ continue
159
+
160
+ # Append execution to history for validation
161
+ from .prompts import build_single_test_execution_prompt
162
+ validation_chat_history = bulk_gen_chat_history + [
163
+ {"role": "user", "content": build_single_test_execution_prompt(tc_info, idx)},
164
+ {"role": "assistant", "content": execution_output}
165
+ ]
166
+
167
+ # Validate test case
168
+ test_result = validate_single_test_case(
169
+ tc_info, idx, execution_output, validation_chat_history,
170
+ validation_executor_cache, cache_key, client, validator_def,
171
+ agent_def, toolkit_config_path, config, model, temperature,
172
+ max_tokens, work_dir, master_log, setup_executor_func,
173
+ verbose=verbose,
174
+ debug=debug,
175
+ )
176
+
177
+ test_results.append(test_result)
178
+
179
+ except Exception as e:
180
+ logger.debug(f"Test execution failed for {test_name}: {e}", exc_info=True)
181
+ master_log.print(f"[red]✗ Test execution failed: {e}[/red]")
182
+
183
+ # Create fallback result
184
+ fallback_result = create_fallback_result_for_test(
185
+ test_case,
186
+ test_file,
187
+ f'Test execution failed: {str(e)}'
188
+ )
189
+ test_results.append(fallback_result)
190
+ master_log.print()
191
+
192
+ # Cleanup executor caches
193
+ cleanup_executor_cache(executor_cache, "executor")
194
+ cleanup_executor_cache(validation_executor_cache, "validation executor")
195
+
196
+ return test_results
alita_sdk/cli/toolkit.py CHANGED
@@ -10,6 +10,7 @@ import logging
10
10
  from typing import Optional, Dict, Any
11
11
 
12
12
  from .cli import get_client
13
+ from .toolkit_loader import load_toolkit_config
13
14
 
14
15
  logger = logging.getLogger(__name__)
15
16
 
@@ -140,24 +141,15 @@ def toolkit_schema(ctx, toolkit_name: str):
140
141
  @click.pass_context
141
142
  def toolkit_test(ctx, toolkit_type: str, tool: str, config_file, params,
142
143
  param, llm_model: str, temperature: float, max_tokens: int):
143
- """
144
- Test a specific tool from a toolkit.
144
+ """Test a specific tool from a toolkit.
145
145
 
146
146
  TOOLKIT_TYPE: Type of toolkit (e.g., 'jira', 'github', 'confluence')
147
147
 
148
+ \b
148
149
  Examples:
149
-
150
- # Test with config and params from files
151
- alita-cli toolkit test jira --tool get_issue \\
152
- --config jira-config.json --params params.json
153
-
154
- # Test with inline parameters
155
- alita-cli toolkit test jira --tool get_issue \\
156
- --config jira-config.json --param issue_key=PROJ-123
157
-
158
- # Test with JSON output for scripting
159
- alita-cli --output json toolkit test github --tool get_issue \\
160
- --config github-config.json --param owner=user --param repo=myrepo
150
+ alita toolkit test jira --tool get_issue --config jira.json --params params.json
151
+ alita toolkit test jira --tool get_issue --config jira.json --param issue_key=PROJ-123
152
+ alita -o json toolkit test github --tool get_issue --config github.json
161
153
  """
162
154
  formatter = ctx.obj['formatter']
163
155
  client = get_client(ctx)
@@ -166,9 +158,15 @@ def toolkit_test(ctx, toolkit_type: str, tool: str, config_file, params,
166
158
  # Load toolkit configuration
167
159
  toolkit_config = {}
168
160
  if config_file:
169
- toolkit_config = json.load(config_file)
161
+ toolkit_config = load_toolkit_config(config_file.name)
170
162
  logger.debug(f"Loaded toolkit config from {config_file.name}")
171
163
 
164
+ # Add the tool to selected_tools in the config
165
+ if 'selected_tools' not in toolkit_config:
166
+ toolkit_config['selected_tools'] = []
167
+ if tool not in toolkit_config['selected_tools']:
168
+ toolkit_config['selected_tools'].append(tool)
169
+
172
170
  # Load tool parameters
173
171
  tool_params = {}
174
172
  if params:
@@ -204,7 +202,6 @@ def toolkit_test(ctx, toolkit_type: str, tool: str, config_file, params,
204
202
  llm_config = {
205
203
  'temperature': temperature,
206
204
  'max_tokens': max_tokens,
207
- 'top_p': 1.0
208
205
  }
209
206
 
210
207
  # Execute test
@@ -254,7 +251,7 @@ def toolkit_tools(ctx, toolkit_type: str, config_file):
254
251
  # Load toolkit configuration if provided
255
252
  toolkit_config = {}
256
253
  if config_file:
257
- toolkit_config = json.load(config_file)
254
+ toolkit_config = load_toolkit_config(config_file.name)
258
255
 
259
256
  # Import and instantiate toolkit
260
257
  from alita_sdk.tools import AVAILABLE_TOOLS
@@ -12,6 +12,20 @@ from typing import Dict, Any, List
12
12
  from .config import substitute_env_vars
13
13
 
14
14
 
15
+ # All available tools in the inventory toolkit
16
+ INVENTORY_TOOLS = [
17
+ "search_graph",
18
+ "get_entity",
19
+ "get_entity_content",
20
+ "impact_analysis",
21
+ "get_related_entities",
22
+ "get_stats",
23
+ "get_citations",
24
+ "list_entities_by_type",
25
+ "list_entities_by_layer",
26
+ ]
27
+
28
+
15
29
  def load_toolkit_config(file_path: str) -> Dict[str, Any]:
16
30
  """Load toolkit configuration from JSON or YAML file with env var substitution."""
17
31
  path = Path(file_path)
@@ -33,7 +47,15 @@ def load_toolkit_config(file_path: str) -> Dict[str, Any]:
33
47
 
34
48
 
35
49
  def load_toolkit_configs(agent_def: Dict[str, Any], toolkit_config_paths: tuple) -> List[Dict[str, Any]]:
36
- """Load all toolkit configurations from agent definition and CLI options."""
50
+ """Load all toolkit configurations from agent definition and CLI options.
51
+
52
+ Args:
53
+ agent_def: Agent definition dictionary
54
+ toolkit_config_paths: Tuple of file paths or dict configs from CLI options
55
+
56
+ Returns:
57
+ List of toolkit configuration dictionaries
58
+ """
37
59
  toolkit_configs = []
38
60
 
39
61
  # Load from agent definition if present
@@ -45,11 +67,19 @@ def load_toolkit_configs(agent_def: Dict[str, Any], toolkit_config_paths: tuple)
45
67
  toolkit_configs.append(config)
46
68
  elif 'config' in tk_config:
47
69
  toolkit_configs.append(tk_config['config'])
70
+ elif 'type' in tk_config:
71
+ # Direct toolkit config (e.g., {'type': 'inventory', ...})
72
+ toolkit_configs.append(tk_config)
48
73
 
49
- # Load from CLI options
74
+ # Load from CLI options - can be file paths (str) or dict configs
50
75
  if toolkit_config_paths:
51
- for config_path in toolkit_config_paths:
52
- config = load_toolkit_config(config_path)
53
- toolkit_configs.append(config)
76
+ for config_item in toolkit_config_paths:
77
+ if isinstance(config_item, dict):
78
+ # Direct config dict (e.g., from /inventory command)
79
+ toolkit_configs.append(config_item)
80
+ elif isinstance(config_item, str):
81
+ # File path
82
+ config = load_toolkit_config(config_item)
83
+ toolkit_configs.append(config)
54
84
 
55
85
  return toolkit_configs
@@ -4,6 +4,40 @@ CLI tools package.
4
4
  Contains specialized tools for CLI agents.
5
5
  """
6
6
 
7
- from .filesystem import get_filesystem_tools
7
+ from .filesystem import get_filesystem_tools, FilesystemApiWrapper
8
+ from .terminal import get_terminal_tools, create_default_blocked_patterns_file
9
+ from .planning import (
10
+ get_planning_tools,
11
+ PlanState,
12
+ list_sessions,
13
+ generate_session_id,
14
+ create_session_memory,
15
+ save_session_metadata,
16
+ load_session_metadata,
17
+ update_session_metadata,
18
+ get_session_dir,
19
+ to_portable_path,
20
+ from_portable_path,
21
+ )
22
+ from .approval import create_approval_wrapper, ApprovalToolWrapper, prompt_approval
8
23
 
9
- __all__ = ['get_filesystem_tools']
24
+ __all__ = [
25
+ 'get_filesystem_tools',
26
+ 'FilesystemApiWrapper',
27
+ 'get_terminal_tools',
28
+ 'create_default_blocked_patterns_file',
29
+ 'get_planning_tools',
30
+ 'PlanState',
31
+ 'list_sessions',
32
+ 'generate_session_id',
33
+ 'create_session_memory',
34
+ 'save_session_metadata',
35
+ 'load_session_metadata',
36
+ 'update_session_metadata',
37
+ 'get_session_dir',
38
+ 'to_portable_path',
39
+ 'from_portable_path',
40
+ 'create_approval_wrapper',
41
+ 'ApprovalToolWrapper',
42
+ 'prompt_approval',
43
+ ]
@@ -0,0 +1,224 @@
1
+ """
2
+ Tool approval wrapper for CLI.
3
+
4
+ Wraps tools to require user approval before execution based on approval mode.
5
+ Modes:
6
+ - 'always': Always require approval for each tool call
7
+ - 'auto': No approval required (automatic execution)
8
+ - 'yolo': No approval and no safety checks (use with caution)
9
+ """
10
+
11
+ import functools
12
+ from typing import Any, Callable, Dict, List, Optional, Set
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.table import Table
16
+ from rich import box
17
+ from rich.text import Text
18
+ import json
19
+
20
+ from langchain_core.tools import BaseTool, StructuredTool, Tool
21
+
22
+ console = Console()
23
+
24
+ # Tools that always require approval in 'always' mode (dangerous built-in operations)
25
+ # These are the built-in CLI tools that modify the filesystem or execute commands
26
+ DANGEROUS_TOOLS = {
27
+ 'terminal_run_command', # Shell command execution
28
+ 'write_file',
29
+ 'create_file',
30
+ 'delete_file',
31
+ 'move_file',
32
+ 'copy_file',
33
+ 'create_directory',
34
+ }
35
+
36
+ # Note: Tools NOT in DANGEROUS_TOOLS are auto-approved, including:
37
+ # - Read-only filesystem tools (read_file, list_directory, etc.)
38
+ # - User-added toolkit tools (via --toolkit_config or /add_toolkit)
39
+ # - MCP server tools (via --mcp or /add_mcp)
40
+ #
41
+ # The assumption is that users explicitly add toolkits they trust.
42
+ # Only built-in tools that can modify files or run commands require approval.
43
+
44
+
45
+ def prompt_approval(tool_name: str, tool_args: Dict[str, Any], approval_mode: str) -> bool:
46
+ """
47
+ Prompt user for approval before executing a tool.
48
+
49
+ Args:
50
+ tool_name: Name of the tool to execute
51
+ tool_args: Arguments to pass to the tool
52
+ approval_mode: Current approval mode ('always', 'auto', 'yolo')
53
+
54
+ Returns:
55
+ True if approved, False if rejected
56
+ """
57
+ # Auto mode - always approve
58
+ if approval_mode == 'auto':
59
+ return True
60
+
61
+ # Yolo mode - always approve, no questions asked
62
+ if approval_mode == 'yolo':
63
+ return True
64
+
65
+ # Always mode - only prompt for dangerous built-in tools
66
+ # User-added toolkits and MCP tools are auto-approved (user explicitly added them)
67
+ if tool_name not in DANGEROUS_TOOLS:
68
+ return True
69
+
70
+ # Create approval prompt panel for dangerous tools
71
+ console.print()
72
+
73
+ # Build args display
74
+ args_content = []
75
+ for key, value in tool_args.items():
76
+ if isinstance(value, str) and len(value) > 100:
77
+ display_value = value[:100] + "..."
78
+ elif isinstance(value, (dict, list)):
79
+ try:
80
+ formatted = json.dumps(value, indent=2)
81
+ if len(formatted) > 200:
82
+ formatted = formatted[:200] + "..."
83
+ display_value = formatted
84
+ except:
85
+ display_value = str(value)[:100]
86
+ else:
87
+ display_value = str(value)
88
+ args_content.append(f" [cyan]{key}[/cyan]: {display_value}")
89
+
90
+ args_text = "\n".join(args_content) if args_content else " (no arguments)"
91
+
92
+ # All tools reaching here are dangerous (in DANGEROUS_TOOLS)
93
+ icon = "⚠️"
94
+ border_style = "yellow"
95
+ title_style = "bold yellow"
96
+
97
+ panel = Panel(
98
+ Text.from_markup(f"[bold]{tool_name}[/bold]\n\n[dim]Arguments:[/dim]\n{args_text}"),
99
+ title=f"[{title_style}]{icon} Approve Tool Call?[/{title_style}]",
100
+ title_align="left",
101
+ subtitle="[dim][y]es / [n]o / [a]uto-approve / [q]uit[/dim]",
102
+ subtitle_align="right",
103
+ border_style=border_style,
104
+ box=box.ROUNDED,
105
+ padding=(1, 2),
106
+ )
107
+ console.print(panel)
108
+
109
+ # Get user input
110
+ while True:
111
+ try:
112
+ response = input("→ ").strip().lower()
113
+ except (KeyboardInterrupt, EOFError):
114
+ console.print("\n[yellow]Cancelled[/yellow]")
115
+ return False
116
+
117
+ if response in ('y', 'yes', ''):
118
+ console.print("[green]✓ Approved[/green]")
119
+ return True
120
+ elif response in ('n', 'no'):
121
+ console.print("[red]✗ Rejected[/red]")
122
+ return False
123
+ elif response in ('a', 'auto'):
124
+ console.print("[cyan]→ Switching to auto mode for this session[/cyan]")
125
+ # Signal to switch to auto mode
126
+ return 'switch_auto'
127
+ elif response in ('q', 'quit'):
128
+ console.print("[yellow]Quitting...[/yellow]")
129
+ raise KeyboardInterrupt()
130
+ else:
131
+ console.print("[dim]Please enter y/n/a/q[/dim]")
132
+
133
+
134
+ class ApprovalToolWrapper:
135
+ """
136
+ Wrapper that adds approval prompts to tools based on approval mode.
137
+
138
+ This wrapper intercepts tool calls and prompts for user approval
139
+ before executing dangerous operations.
140
+ """
141
+
142
+ def __init__(self, approval_mode_ref: List[str]):
143
+ """
144
+ Initialize the approval wrapper.
145
+
146
+ Args:
147
+ approval_mode_ref: A mutable list containing the current approval mode.
148
+ Using a list allows the mode to be changed externally.
149
+ access as approval_mode_ref[0]
150
+ """
151
+ self.approval_mode_ref = approval_mode_ref
152
+
153
+ @property
154
+ def approval_mode(self) -> str:
155
+ """Get current approval mode."""
156
+ return self.approval_mode_ref[0] if self.approval_mode_ref else 'always'
157
+
158
+ def wrap_tool(self, tool: BaseTool) -> BaseTool:
159
+ """
160
+ Wrap a tool to add approval prompts.
161
+
162
+ Args:
163
+ tool: The tool to wrap
164
+
165
+ Returns:
166
+ Wrapped tool with approval logic
167
+ """
168
+ original_func = tool.func if hasattr(tool, 'func') else tool._run
169
+ tool_name = tool.name
170
+ wrapper_self = self
171
+
172
+ @functools.wraps(original_func)
173
+ def wrapped_func(*args, **kwargs):
174
+ # Get approval
175
+ approval = prompt_approval(tool_name, kwargs, wrapper_self.approval_mode)
176
+
177
+ if approval == 'switch_auto':
178
+ # Switch to auto mode
179
+ wrapper_self.approval_mode_ref[0] = 'auto'
180
+ console.print("[cyan]Mode switched to 'auto' - tools will auto-approve[/cyan]")
181
+ elif not approval:
182
+ return f"Tool execution rejected by user"
183
+
184
+ # Execute the tool
185
+ return original_func(*args, **kwargs)
186
+
187
+ # Create new tool with wrapped function
188
+ if isinstance(tool, StructuredTool):
189
+ return StructuredTool(
190
+ name=tool.name,
191
+ description=tool.description,
192
+ func=wrapped_func,
193
+ args_schema=tool.args_schema,
194
+ return_direct=tool.return_direct,
195
+ )
196
+ else:
197
+ # Clone the tool with wrapped function
198
+ tool.func = wrapped_func
199
+ return tool
200
+
201
+ def wrap_tools(self, tools: List[BaseTool]) -> List[BaseTool]:
202
+ """
203
+ Wrap multiple tools with approval logic.
204
+
205
+ Args:
206
+ tools: List of tools to wrap
207
+
208
+ Returns:
209
+ List of wrapped tools
210
+ """
211
+ return [self.wrap_tool(tool) for tool in tools]
212
+
213
+
214
+ def create_approval_wrapper(approval_mode_ref: List[str]) -> ApprovalToolWrapper:
215
+ """
216
+ Create an approval wrapper with a reference to the current mode.
217
+
218
+ Args:
219
+ approval_mode_ref: Mutable list containing current approval mode [0]
220
+
221
+ Returns:
222
+ ApprovalToolWrapper instance
223
+ """
224
+ return ApprovalToolWrapper(approval_mode_ref)