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.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +15 -3
- alita_sdk/cli/agent_loader.py +56 -8
- alita_sdk/cli/agent_ui.py +93 -31
- alita_sdk/cli/agents.py +2274 -230
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +162 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +36 -2
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +910 -64
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +17 -5
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +353 -48
- alita_sdk/runtime/clients/sandbox_client.py +0 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +123 -26
- alita_sdk/runtime/langchain/constants.py +642 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +279 -73
- alita_sdk/runtime/langchain/utils.py +82 -15
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +7 -0
- alita_sdk/runtime/toolkits/application.py +21 -9
- alita_sdk/runtime/toolkits/artifact.py +15 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +139 -251
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +238 -32
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/application.py +20 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +43 -15
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +852 -67
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +9 -6
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +51 -11
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +202 -5
- alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +16 -5
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +113 -29
- alita_sdk/tools/ado/repos/__init__.py +51 -33
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -9
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +170 -45
- alita_sdk/tools/bitbucket/__init__.py +17 -12
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +10 -7
- alita_sdk/tools/code_indexer_toolkit.py +73 -23
- alita_sdk/tools/confluence/__init__.py +21 -15
- alita_sdk/tools/confluence/api_wrapper.py +78 -23
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +13 -14
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +15 -11
- alita_sdk/tools/gitlab/api_wrapper.py +207 -41
- alita_sdk/tools/gitlab_org/__init__.py +10 -8
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +10 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -11
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +11 -3
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +490 -114
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +11 -11
- alita_sdk/tools/pptx/__init__.py +10 -9
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +30 -10
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +10 -8
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -9
- alita_sdk/tools/salesforce/__init__.py +10 -9
- alita_sdk/tools/servicenow/__init__.py +17 -14
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
- alita_sdk/tools/slack/__init__.py +10 -8
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +11 -9
- alita_sdk/tools/testio/__init__.py +10 -8
- alita_sdk/tools/testrail/__init__.py +11 -8
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +77 -3
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
- alita_sdk/tools/xray/__init__.py +12 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +9 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
- alita_sdk/tools/zephyr_essential/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -9
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -8
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.462.dist-info/RECORD +0 -384
- alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
151
|
-
|
|
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 =
|
|
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 =
|
|
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
|
alita_sdk/cli/toolkit_loader.py
CHANGED
|
@@ -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
|
|
52
|
-
|
|
53
|
-
|
|
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
|
alita_sdk/cli/tools/__init__.py
CHANGED
|
@@ -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__ = [
|
|
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)
|