alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +156 -0
- alita_sdk/cli/agent_loader.py +245 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3113 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- 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/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -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 +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- 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 +94 -2
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +91 -0
- alita_sdk/configurations/jira.py +103 -0
- 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/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +388 -46
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +8 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +157 -39
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- 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 +10 -4
- 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 +40 -19
- alita_sdk/runtime/langchain/langraph_agent.py +405 -84
- alita_sdk/runtime/langchain/utils.py +106 -7
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- 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 +31 -0
- alita_sdk/runtime/toolkits/application.py +29 -10
- alita_sdk/runtime/toolkits/artifact.py +20 -11
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +783 -0
- 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 +356 -69
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +10 -3
- alita_sdk/runtime/tools/application.py +27 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +67 -35
- alita_sdk/runtime/tools/graph.py +10 -4
- alita_sdk/runtime/tools/image_generation.py +148 -46
- alita_sdk/runtime/tools/llm.py +1003 -128
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
- 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 +65 -48
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +9 -3
- alita_sdk/runtime/tools/vectorstore_base.py +70 -14
- 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 +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +40 -13
- alita_sdk/runtime/utils/toolkit_utils.py +30 -9
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +134 -35
- alita_sdk/tools/ado/repos/__init__.py +51 -32
- 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 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -13
- alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
- 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 +271 -84
- alita_sdk/tools/bitbucket/__init__.py +17 -11
- 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/sematic/proposal_chunker.py +1 -1
- 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 +11 -8
- alita_sdk/tools/code_indexer_toolkit.py +82 -22
- alita_sdk/tools/confluence/__init__.py +22 -16
- alita_sdk/tools/confluence/api_wrapper.py +107 -30
- alita_sdk/tools/confluence/loader.py +14 -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 +14 -15
- 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 +16 -11
- alita_sdk/tools/gitlab/api_wrapper.py +218 -48
- alita_sdk/tools/gitlab_org/__init__.py +10 -9
- 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 +11 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -10
- alita_sdk/tools/jira/api_wrapper.py +92 -41
- 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 +12 -4
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +491 -106
- 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 +10 -9
- alita_sdk/tools/pptx/__init__.py +11 -10
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +31 -11
- alita_sdk/tools/qtest/api_wrapper.py +2135 -86
- alita_sdk/tools/rally/__init__.py +10 -9
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -8
- alita_sdk/tools/salesforce/__init__.py +10 -8
- alita_sdk/tools/servicenow/__init__.py +17 -15
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -7
- alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +10 -7
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +12 -9
- alita_sdk/tools/testio/__init__.py +10 -7
- alita_sdk/tools/testrail/__init__.py +11 -10
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +103 -18
- 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 +30 -13
- alita_sdk/tools/xray/__init__.py +13 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +10 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
- alita_sdk/tools/zephyr_essential/__init__.py +10 -7
- 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 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -7
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
- 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.379.dist-info/RECORD +0 -360
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Output formatting utilities for Alita CLI.
|
|
3
|
+
|
|
4
|
+
Provides text and JSON formatters for displaying toolkit test results,
|
|
5
|
+
agent responses, and other CLI outputs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OutputFormatter:
|
|
14
|
+
"""Base class for output formatters."""
|
|
15
|
+
|
|
16
|
+
def format_toolkit_result(self, result: Dict[str, Any]) -> str:
|
|
17
|
+
"""Format toolkit test result."""
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
def format_error(self, error: str) -> str:
|
|
21
|
+
"""Format error message."""
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
def format_toolkit_list(self, toolkits: List[Dict[str, Any]]) -> str:
|
|
25
|
+
"""Format list of available toolkits."""
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TextFormatter(OutputFormatter):
|
|
30
|
+
"""Human-readable text formatter."""
|
|
31
|
+
|
|
32
|
+
def format_toolkit_result(self, result: Dict[str, Any]) -> str:
|
|
33
|
+
"""Format toolkit test result as text."""
|
|
34
|
+
if not result.get('success', False):
|
|
35
|
+
return self.format_error(result.get('error', 'Unknown error'))
|
|
36
|
+
|
|
37
|
+
lines = [
|
|
38
|
+
"\n✓ Tool executed successfully\n",
|
|
39
|
+
f"Tool: {result.get('tool_name', 'unknown')}",
|
|
40
|
+
f"Toolkit: {result.get('toolkit_config', {}).get('type', 'unknown')}",
|
|
41
|
+
f"LLM Model: {result.get('llm_model', 'N/A')}",
|
|
42
|
+
f"Execution time: {result.get('execution_time_seconds', 0):.3f}s",
|
|
43
|
+
"",
|
|
44
|
+
"Result:",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Format result based on type
|
|
48
|
+
tool_result = result.get('result')
|
|
49
|
+
if isinstance(tool_result, str):
|
|
50
|
+
lines.append(f" {tool_result}")
|
|
51
|
+
elif isinstance(tool_result, dict):
|
|
52
|
+
for key, value in tool_result.items():
|
|
53
|
+
lines.append(f" {key}: {value}")
|
|
54
|
+
else:
|
|
55
|
+
lines.append(f" {str(tool_result)}")
|
|
56
|
+
|
|
57
|
+
# Add events if present
|
|
58
|
+
events = result.get('events_dispatched', [])
|
|
59
|
+
if events:
|
|
60
|
+
lines.extend([
|
|
61
|
+
"",
|
|
62
|
+
f"Events dispatched: {len(events)}"
|
|
63
|
+
])
|
|
64
|
+
for event in events[:5]: # Limit to first 5 events
|
|
65
|
+
event_data = event.get('data', {})
|
|
66
|
+
message = event_data.get('message', str(event_data))
|
|
67
|
+
lines.append(f" - {event.get('name', 'event')}: {message}")
|
|
68
|
+
|
|
69
|
+
if len(events) > 5:
|
|
70
|
+
lines.append(f" ... and {len(events) - 5} more events")
|
|
71
|
+
|
|
72
|
+
return "\n".join(lines)
|
|
73
|
+
|
|
74
|
+
def format_error(self, error: str) -> str:
|
|
75
|
+
"""Format error message as text."""
|
|
76
|
+
return f"\n✗ Error: {error}\n"
|
|
77
|
+
|
|
78
|
+
def format_toolkit_list(self, toolkits: List[Dict[str, Any]]) -> str:
|
|
79
|
+
"""Format list of available toolkits as text."""
|
|
80
|
+
lines = ["\nAvailable toolkits:\n"]
|
|
81
|
+
|
|
82
|
+
for toolkit in sorted(toolkits, key=lambda x: x.get('name', '')):
|
|
83
|
+
name = toolkit.get('name', 'unknown')
|
|
84
|
+
class_name = toolkit.get('class_name', '')
|
|
85
|
+
lines.append(f" - {name}" + (f" ({class_name})" if class_name else ""))
|
|
86
|
+
|
|
87
|
+
lines.append(f"\nTotal: {len(toolkits)} toolkits")
|
|
88
|
+
return "\n".join(lines)
|
|
89
|
+
|
|
90
|
+
def format_toolkit_schema(self, toolkit_name: str, schema: Dict[str, Any]) -> str:
|
|
91
|
+
"""Format toolkit schema as text."""
|
|
92
|
+
lines = [
|
|
93
|
+
f"\n{toolkit_name.title()} Toolkit Configuration Schema:\n",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
properties = schema.get('properties', {})
|
|
97
|
+
required = schema.get('required', [])
|
|
98
|
+
|
|
99
|
+
for field_name, field_schema in properties.items():
|
|
100
|
+
field_type = field_schema.get('type', 'any')
|
|
101
|
+
description = field_schema.get('description', '')
|
|
102
|
+
is_required = field_name in required
|
|
103
|
+
default = field_schema.get('default')
|
|
104
|
+
|
|
105
|
+
req_text = "required" if is_required else "optional"
|
|
106
|
+
lines.append(f" - {field_name} ({req_text}): {description}")
|
|
107
|
+
lines.append(f" Type: {field_type}")
|
|
108
|
+
|
|
109
|
+
if default is not None:
|
|
110
|
+
lines.append(f" Default: {default}")
|
|
111
|
+
|
|
112
|
+
# Show enum values if present
|
|
113
|
+
if 'enum' in field_schema:
|
|
114
|
+
lines.append(f" Options: {', '.join(map(str, field_schema['enum']))}")
|
|
115
|
+
|
|
116
|
+
# Handle nested objects
|
|
117
|
+
if field_type == 'object' and 'properties' in field_schema:
|
|
118
|
+
lines.append(f" Fields:")
|
|
119
|
+
for nested_name, nested_schema in field_schema['properties'].items():
|
|
120
|
+
nested_desc = nested_schema.get('description', '')
|
|
121
|
+
lines.append(f" - {nested_name}: {nested_desc}")
|
|
122
|
+
|
|
123
|
+
lines.append("")
|
|
124
|
+
|
|
125
|
+
return "\n".join(lines)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class JSONFormatter(OutputFormatter):
|
|
129
|
+
"""JSON formatter for scripting and automation."""
|
|
130
|
+
|
|
131
|
+
def __init__(self, pretty: bool = True):
|
|
132
|
+
"""
|
|
133
|
+
Initialize JSON formatter.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
pretty: If True, format JSON with indentation
|
|
137
|
+
"""
|
|
138
|
+
self.pretty = pretty
|
|
139
|
+
|
|
140
|
+
def _dump(self, data: Any) -> str:
|
|
141
|
+
"""Dump data as JSON."""
|
|
142
|
+
if self.pretty:
|
|
143
|
+
return json.dumps(data, indent=2, default=str)
|
|
144
|
+
return json.dumps(data, default=str)
|
|
145
|
+
|
|
146
|
+
def format_toolkit_result(self, result: Dict[str, Any]) -> str:
|
|
147
|
+
"""Format toolkit test result as JSON."""
|
|
148
|
+
return self._dump(result)
|
|
149
|
+
|
|
150
|
+
def format_error(self, error: str) -> str:
|
|
151
|
+
"""Format error message as JSON."""
|
|
152
|
+
return self._dump({'success': False, 'error': error})
|
|
153
|
+
|
|
154
|
+
def format_toolkit_list(self, toolkits: List[Dict[str, Any]]) -> str:
|
|
155
|
+
"""Format list of available toolkits as JSON."""
|
|
156
|
+
return self._dump({
|
|
157
|
+
'toolkits': toolkits,
|
|
158
|
+
'total': len(toolkits)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
def format_toolkit_schema(self, toolkit_name: str, schema: Dict[str, Any]) -> str:
|
|
162
|
+
"""Format toolkit schema as JSON."""
|
|
163
|
+
return self._dump({
|
|
164
|
+
'toolkit': toolkit_name,
|
|
165
|
+
'schema': schema
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_formatter(output_format: str = 'text', pretty: bool = True) -> OutputFormatter:
|
|
170
|
+
"""
|
|
171
|
+
Get output formatter by name.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
output_format: Format type ('text' or 'json')
|
|
175
|
+
pretty: For JSON formatter, whether to pretty-print
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
OutputFormatter instance
|
|
179
|
+
"""
|
|
180
|
+
if output_format == 'json':
|
|
181
|
+
return JSONFormatter(pretty=pretty)
|
|
182
|
+
return TextFormatter()
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced input handler with readline support.
|
|
3
|
+
|
|
4
|
+
Provides tab completion for commands, cursor movement, and input history.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import readline
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Optional, Any, Callable
|
|
11
|
+
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
# Available commands for autocompletion
|
|
18
|
+
CHAT_COMMANDS = [
|
|
19
|
+
'/help',
|
|
20
|
+
'/clear',
|
|
21
|
+
'/history',
|
|
22
|
+
'/save',
|
|
23
|
+
'/agent',
|
|
24
|
+
'/model',
|
|
25
|
+
'/mode',
|
|
26
|
+
'/mode always',
|
|
27
|
+
'/mode auto',
|
|
28
|
+
'/mode yolo',
|
|
29
|
+
'/dir',
|
|
30
|
+
'/dir add',
|
|
31
|
+
'/dir rm',
|
|
32
|
+
'/dir remove',
|
|
33
|
+
'/inventory',
|
|
34
|
+
'/session',
|
|
35
|
+
'/session list',
|
|
36
|
+
'/session resume',
|
|
37
|
+
'/add_mcp',
|
|
38
|
+
'/add_toolkit',
|
|
39
|
+
'/rm_mcp',
|
|
40
|
+
'/rm_toolkit',
|
|
41
|
+
'/reload',
|
|
42
|
+
'exit',
|
|
43
|
+
'quit',
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Callback to get dynamic toolkit names for completion
|
|
48
|
+
_toolkit_names_callback: Optional[Callable[[], List[str]]] = None
|
|
49
|
+
|
|
50
|
+
# Callback to get inventory .json files for completion
|
|
51
|
+
_inventory_files_callback: Optional[Callable[[], List[str]]] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def set_toolkit_names_callback(callback: Callable[[], List[str]]):
|
|
55
|
+
"""
|
|
56
|
+
Set a callback function that returns available toolkit names.
|
|
57
|
+
|
|
58
|
+
This allows the input handler to provide dynamic completions
|
|
59
|
+
for /add_toolkit without having a direct dependency on config.
|
|
60
|
+
"""
|
|
61
|
+
global _toolkit_names_callback
|
|
62
|
+
_toolkit_names_callback = callback
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def set_inventory_files_callback(callback: Callable[[], List[str]]):
|
|
66
|
+
"""
|
|
67
|
+
Set a callback function that returns available inventory .json files.
|
|
68
|
+
|
|
69
|
+
This allows the input handler to provide dynamic completions
|
|
70
|
+
for /inventory without having a direct dependency on agents.py.
|
|
71
|
+
"""
|
|
72
|
+
global _inventory_files_callback
|
|
73
|
+
_inventory_files_callback = callback
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_toolkit_names_for_completion() -> List[str]:
|
|
77
|
+
"""Get toolkit names for tab completion."""
|
|
78
|
+
global _toolkit_names_callback
|
|
79
|
+
if _toolkit_names_callback:
|
|
80
|
+
try:
|
|
81
|
+
return _toolkit_names_callback()
|
|
82
|
+
except Exception:
|
|
83
|
+
return []
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_inventory_files_for_completion() -> List[str]:
|
|
88
|
+
"""Get inventory .json files for tab completion."""
|
|
89
|
+
global _inventory_files_callback
|
|
90
|
+
if _inventory_files_callback:
|
|
91
|
+
try:
|
|
92
|
+
return _inventory_files_callback()
|
|
93
|
+
except Exception:
|
|
94
|
+
return []
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ChatInputHandler:
|
|
99
|
+
"""
|
|
100
|
+
Enhanced input handler with readline support for chat sessions.
|
|
101
|
+
|
|
102
|
+
Features:
|
|
103
|
+
- Tab completion for slash commands
|
|
104
|
+
- Arrow key navigation through input history
|
|
105
|
+
- Cursor movement with left/right arrows
|
|
106
|
+
- Ctrl+A (start of line), Ctrl+E (end of line)
|
|
107
|
+
- Persistent command history within session
|
|
108
|
+
- Material UI-style input prompt
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(self):
|
|
112
|
+
self._setup_readline()
|
|
113
|
+
self._input_history: List[str] = []
|
|
114
|
+
|
|
115
|
+
def _setup_readline(self):
|
|
116
|
+
"""Configure readline for enhanced input."""
|
|
117
|
+
# Set up tab completion
|
|
118
|
+
readline.set_completer(self._completer)
|
|
119
|
+
|
|
120
|
+
# Detect if we're using libedit (macOS) or GNU readline (Linux)
|
|
121
|
+
# libedit uses different syntax for parse_and_bind
|
|
122
|
+
if 'libedit' in readline.__doc__:
|
|
123
|
+
# macOS libedit syntax
|
|
124
|
+
readline.parse_and_bind('bind ^I rl_complete')
|
|
125
|
+
else:
|
|
126
|
+
# GNU readline syntax
|
|
127
|
+
readline.parse_and_bind('tab: complete')
|
|
128
|
+
|
|
129
|
+
# Enable emacs-style keybindings (Ctrl+A, Ctrl+E, etc.)
|
|
130
|
+
# This is usually the default on macOS/Linux
|
|
131
|
+
try:
|
|
132
|
+
if 'libedit' not in readline.__doc__:
|
|
133
|
+
readline.parse_and_bind('set editing-mode emacs')
|
|
134
|
+
except Exception:
|
|
135
|
+
pass # Some systems might not support this
|
|
136
|
+
|
|
137
|
+
# Set completion display style (GNU readline only)
|
|
138
|
+
try:
|
|
139
|
+
if 'libedit' not in readline.__doc__:
|
|
140
|
+
readline.parse_and_bind('set show-all-if-ambiguous on')
|
|
141
|
+
readline.parse_and_bind('set completion-ignore-case on')
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
# Set delimiters for completion (space and common punctuation)
|
|
146
|
+
readline.set_completer_delims(' \t\n;')
|
|
147
|
+
|
|
148
|
+
def _completer(self, text: str, state: int) -> Optional[str]:
|
|
149
|
+
"""
|
|
150
|
+
Readline completer function for slash commands.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
text: The current text being completed
|
|
154
|
+
state: The state of completion (0 for first match, 1 for second, etc.)
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
The next matching command or None
|
|
158
|
+
"""
|
|
159
|
+
# Get the full line buffer
|
|
160
|
+
line = readline.get_line_buffer()
|
|
161
|
+
|
|
162
|
+
# Only complete at the start of the line or after whitespace
|
|
163
|
+
if line and not line.startswith('/') and text != line:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
matches = []
|
|
167
|
+
|
|
168
|
+
# Handle /add_toolkit <name> completion
|
|
169
|
+
if line.startswith('/add_toolkit '):
|
|
170
|
+
# Get partial toolkit name being typed
|
|
171
|
+
toolkit_prefix = text.lower()
|
|
172
|
+
toolkit_names = get_toolkit_names_for_completion()
|
|
173
|
+
matches = [f'/add_toolkit {name}' for name in toolkit_names
|
|
174
|
+
if name.lower().startswith(toolkit_prefix) or toolkit_prefix == '']
|
|
175
|
+
# Also match just the toolkit name if text doesn't start with /
|
|
176
|
+
if not text.startswith('/'):
|
|
177
|
+
matches = [name for name in toolkit_names if name.lower().startswith(toolkit_prefix)]
|
|
178
|
+
# Handle /inventory <path> completion
|
|
179
|
+
elif line.startswith('/inventory '):
|
|
180
|
+
# Get partial path being typed
|
|
181
|
+
path_prefix = text.lower()
|
|
182
|
+
inventory_files = get_inventory_files_for_completion()
|
|
183
|
+
matches = [f'/inventory {path}' for path in inventory_files
|
|
184
|
+
if path.lower().startswith(path_prefix) or path_prefix == '']
|
|
185
|
+
# Also match just the path if text doesn't start with /
|
|
186
|
+
if not text.startswith('/'):
|
|
187
|
+
matches = [path for path in inventory_files if path.lower().startswith(path_prefix)]
|
|
188
|
+
# Find matching commands
|
|
189
|
+
elif text.startswith('/'):
|
|
190
|
+
matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith(text)]
|
|
191
|
+
elif text == '' and line == '':
|
|
192
|
+
# Show all commands on empty tab
|
|
193
|
+
matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith('/')]
|
|
194
|
+
else:
|
|
195
|
+
matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith(text)]
|
|
196
|
+
|
|
197
|
+
# Return the match at the given state
|
|
198
|
+
if state < len(matches):
|
|
199
|
+
return matches[state]
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def get_input(self, prompt: str = "") -> str:
|
|
203
|
+
"""
|
|
204
|
+
Get user input with enhanced readline features.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
prompt: The prompt to display (note: for rich console, prompt is printed separately)
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
The user's input string
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
user_input = input(prompt)
|
|
214
|
+
|
|
215
|
+
# Add non-empty, non-duplicate inputs to history
|
|
216
|
+
if user_input.strip() and (not self._input_history or
|
|
217
|
+
self._input_history[-1] != user_input):
|
|
218
|
+
self._input_history.append(user_input)
|
|
219
|
+
readline.add_history(user_input)
|
|
220
|
+
|
|
221
|
+
return user_input
|
|
222
|
+
except (KeyboardInterrupt, EOFError):
|
|
223
|
+
raise
|
|
224
|
+
|
|
225
|
+
def clear_history(self):
|
|
226
|
+
"""Clear the input history."""
|
|
227
|
+
self._input_history.clear()
|
|
228
|
+
readline.clear_history()
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def history(self) -> List[str]:
|
|
232
|
+
"""Get the current input history."""
|
|
233
|
+
return self._input_history.copy()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# Global instance for use across the CLI
|
|
237
|
+
_input_handler: Optional[ChatInputHandler] = None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def get_input_handler() -> ChatInputHandler:
|
|
241
|
+
"""Get or create the global input handler instance."""
|
|
242
|
+
global _input_handler
|
|
243
|
+
if _input_handler is None:
|
|
244
|
+
_input_handler = ChatInputHandler()
|
|
245
|
+
return _input_handler
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def chat_input(prompt: str = "") -> str:
|
|
249
|
+
"""
|
|
250
|
+
Convenience function for getting chat input with enhanced features.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
prompt: The prompt to display
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
The user's input string
|
|
257
|
+
"""
|
|
258
|
+
return get_input_handler().get_input(prompt)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def styled_input(context_info: Optional[Dict[str, Any]] = None) -> str:
|
|
262
|
+
"""
|
|
263
|
+
Get user input with a styled bordered prompt that works correctly with readline.
|
|
264
|
+
|
|
265
|
+
The prompt is passed directly to input() so readline can properly
|
|
266
|
+
handle cursor positioning and history navigation.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
context_info: Optional context info dict with keys:
|
|
270
|
+
- used_tokens: Current tokens in context
|
|
271
|
+
- max_tokens: Maximum allowed tokens
|
|
272
|
+
- fill_ratio: Context fill ratio (0.0-1.0)
|
|
273
|
+
- pruned_count: Number of pruned messages
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
The user's input string
|
|
277
|
+
"""
|
|
278
|
+
# Get terminal width for the border
|
|
279
|
+
try:
|
|
280
|
+
width = console.width - 2
|
|
281
|
+
except Exception:
|
|
282
|
+
width = 78
|
|
283
|
+
|
|
284
|
+
# Build context indicator if provided
|
|
285
|
+
context_indicator = ""
|
|
286
|
+
if context_info and context_info.get('max_tokens', 0) > 0:
|
|
287
|
+
context_indicator = _format_context_indicator(
|
|
288
|
+
context_info.get('used_tokens', 0),
|
|
289
|
+
context_info.get('max_tokens', 8000),
|
|
290
|
+
context_info.get('fill_ratio', 0.0),
|
|
291
|
+
context_info.get('pruned_count', 0),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Print the complete box frame first, then move cursor up to input line
|
|
295
|
+
console.print()
|
|
296
|
+
console.print(f"[dim]╭{'─' * width}╮[/dim]")
|
|
297
|
+
console.print(f"[dim]│[/dim]{' ' * width}[dim]│[/dim]")
|
|
298
|
+
|
|
299
|
+
# Bottom border with context indicator on the right
|
|
300
|
+
if context_indicator:
|
|
301
|
+
indicator_len = len(_strip_ansi(context_indicator))
|
|
302
|
+
padding = width - indicator_len - 1
|
|
303
|
+
console.print(f"[dim]╰{'─' * padding}[/dim]{context_indicator}[dim]─╯[/dim]")
|
|
304
|
+
else:
|
|
305
|
+
console.print(f"[dim]╰{'─' * width}╯[/dim]")
|
|
306
|
+
|
|
307
|
+
# Move cursor up 2 lines and to position after "│ > "
|
|
308
|
+
# \033[2A = move up 2 lines, \033[4C = move right 4 columns
|
|
309
|
+
prompt = "\033[2A\033[2C > "
|
|
310
|
+
|
|
311
|
+
user_input = get_input_handler().get_input(prompt)
|
|
312
|
+
|
|
313
|
+
# Move cursor down to after the box
|
|
314
|
+
console.print()
|
|
315
|
+
|
|
316
|
+
return user_input
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _format_context_indicator(
|
|
320
|
+
used_tokens: int,
|
|
321
|
+
max_tokens: int,
|
|
322
|
+
fill_ratio: float,
|
|
323
|
+
pruned_count: int = 0
|
|
324
|
+
) -> str:
|
|
325
|
+
"""
|
|
326
|
+
Format context usage indicator for display.
|
|
327
|
+
|
|
328
|
+
Shows: [1234/8000 ██████░░░░ 15%]
|
|
329
|
+
Color coded: green <70%, yellow 70-90%, red >90%
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
used_tokens: Current tokens in context
|
|
333
|
+
max_tokens: Maximum allowed tokens
|
|
334
|
+
fill_ratio: Context fill ratio (0.0-1.0)
|
|
335
|
+
pruned_count: Number of pruned messages
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Formatted indicator string with ANSI colors
|
|
339
|
+
"""
|
|
340
|
+
# Determine color based on fill ratio
|
|
341
|
+
if fill_ratio < 0.7:
|
|
342
|
+
color = "green"
|
|
343
|
+
elif fill_ratio < 0.9:
|
|
344
|
+
color = "yellow"
|
|
345
|
+
else:
|
|
346
|
+
color = "red"
|
|
347
|
+
|
|
348
|
+
# Build progress bar (10 chars)
|
|
349
|
+
bar_width = 10
|
|
350
|
+
filled = int(fill_ratio * bar_width)
|
|
351
|
+
empty = bar_width - filled
|
|
352
|
+
bar = "█" * filled + "░" * empty
|
|
353
|
+
|
|
354
|
+
# Format percentage
|
|
355
|
+
percent = int(fill_ratio * 100)
|
|
356
|
+
|
|
357
|
+
# Build indicator
|
|
358
|
+
indicator = f"[{color}]{used_tokens}/{max_tokens} {bar} {percent}%[/{color}]"
|
|
359
|
+
|
|
360
|
+
# Add pruned count if any
|
|
361
|
+
if pruned_count > 0:
|
|
362
|
+
indicator += f" [dim]({pruned_count} pruned)[/dim]"
|
|
363
|
+
|
|
364
|
+
return indicator
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _strip_ansi(text: str) -> str:
|
|
368
|
+
"""Strip ANSI escape codes and Rich markup from text for length calculation."""
|
|
369
|
+
import re
|
|
370
|
+
# Remove Rich markup tags like [green], [/green], [dim], etc.
|
|
371
|
+
text = re.sub(r'\[/?[^\]]+\]', '', text)
|
|
372
|
+
# Remove ANSI escape codes
|
|
373
|
+
text = re.sub(r'\x1b\[[0-9;]*m', '', text)
|
|
374
|
+
return text
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def styled_selection_input(prompt_text: str = "Select") -> str:
|
|
378
|
+
"""
|
|
379
|
+
Get user selection input with a styled bordered prompt.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
prompt_text: The prompt text to show (e.g., "Select model number")
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
The user's input string
|
|
386
|
+
"""
|
|
387
|
+
# Get terminal width for the border
|
|
388
|
+
try:
|
|
389
|
+
width = console.width - 2
|
|
390
|
+
except Exception:
|
|
391
|
+
width = 78
|
|
392
|
+
|
|
393
|
+
# Print the complete box frame first, then move cursor up to input line
|
|
394
|
+
console.print()
|
|
395
|
+
console.print(f"[dim]╭{'─' * width}╮[/dim]")
|
|
396
|
+
console.print(f"[dim]│[/dim]{' ' * width}[dim]│[/dim]")
|
|
397
|
+
console.print(f"[dim]╰{'─' * width}╯[/dim]")
|
|
398
|
+
|
|
399
|
+
# Move cursor up 2 lines and to position after "│"
|
|
400
|
+
# \033[2A = move up 2 lines, \033[2C = move right 2 columns
|
|
401
|
+
prompt = f"\033[2A\033[2C {prompt_text}: "
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
user_input = input(prompt)
|
|
405
|
+
except (KeyboardInterrupt, EOFError):
|
|
406
|
+
console.print()
|
|
407
|
+
raise
|
|
408
|
+
|
|
409
|
+
# Move cursor down to after the box
|
|
410
|
+
console.print()
|
|
411
|
+
|
|
412
|
+
return user_input.strip()
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def print_input_prompt():
|
|
416
|
+
"""Print a clean, modern input prompt."""
|
|
417
|
+
# Simple clean prompt with > indicator
|
|
418
|
+
console.print() # Empty line for spacing
|
|
419
|
+
console.print("[bold cyan]>[/bold cyan] ", end="")
|