jarvis-ai-assistant 0.1.0__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.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/.jarvis +1 -0
- jarvis/__init__.py +3 -0
- jarvis/__pycache__/agent.cpython-313.pyc +0 -0
- jarvis/__pycache__/models.cpython-313.pyc +0 -0
- jarvis/__pycache__/tools.cpython-313.pyc +0 -0
- jarvis/__pycache__/utils.cpython-313.pyc +0 -0
- jarvis/agent.py +100 -0
- jarvis/main.py +161 -0
- jarvis/models.py +112 -0
- jarvis/tools/__init__.py +22 -0
- jarvis/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/base.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/file_ops.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/python_script.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/rag.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/search.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/shell.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_confirmation.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_interaction.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/webpage.cpython-313.pyc +0 -0
- jarvis/tools/base.py +155 -0
- jarvis/tools/file_ops.py +106 -0
- jarvis/tools/python_script.py +150 -0
- jarvis/tools/rag.py +154 -0
- jarvis/tools/search.py +48 -0
- jarvis/tools/shell.py +67 -0
- jarvis/tools/user_confirmation.py +58 -0
- jarvis/tools/user_interaction.py +86 -0
- jarvis/tools/webpage.py +90 -0
- jarvis/utils.py +105 -0
- jarvis_ai_assistant-0.1.0.dist-info/METADATA +125 -0
- jarvis_ai_assistant-0.1.0.dist-info/RECORD +35 -0
- jarvis_ai_assistant-0.1.0.dist-info/WHEEL +5 -0
- jarvis_ai_assistant-0.1.0.dist-info/entry_points.txt +2 -0
- jarvis_ai_assistant-0.1.0.dist-info/top_level.txt +1 -0
jarvis/tools/file_ops.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from ..utils import PrettyOutput, OutputType
|
|
5
|
+
|
|
6
|
+
class FileOperationTool:
|
|
7
|
+
name = "file_operation"
|
|
8
|
+
description = "Execute file operations (read/write/append/exists)"
|
|
9
|
+
parameters = {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"operation": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"enum": ["read", "write", "append", "exists"],
|
|
15
|
+
"description": "Type of file operation to perform"
|
|
16
|
+
},
|
|
17
|
+
"filepath": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Absolute or relative path to the file"
|
|
20
|
+
},
|
|
21
|
+
"content": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Content to write (required for write/append operations)",
|
|
24
|
+
"default": ""
|
|
25
|
+
},
|
|
26
|
+
"encoding": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "File encoding (default: utf-8)",
|
|
29
|
+
"default": "utf-8"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"required": ["operation", "filepath"]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
36
|
+
"""执行文件操作"""
|
|
37
|
+
try:
|
|
38
|
+
operation = args["operation"]
|
|
39
|
+
filepath = args["filepath"]
|
|
40
|
+
encoding = args.get("encoding", "utf-8")
|
|
41
|
+
|
|
42
|
+
# 记录操作和完整路径
|
|
43
|
+
abs_path = os.path.abspath(filepath)
|
|
44
|
+
PrettyOutput.print(f"文件操作: {operation} - {abs_path}", OutputType.INFO)
|
|
45
|
+
|
|
46
|
+
if operation == "exists":
|
|
47
|
+
exists = os.path.exists(filepath)
|
|
48
|
+
return {
|
|
49
|
+
"success": True,
|
|
50
|
+
"stdout": str(exists),
|
|
51
|
+
"stderr": ""
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
elif operation == "read":
|
|
55
|
+
if not os.path.exists(filepath):
|
|
56
|
+
return {
|
|
57
|
+
"success": False,
|
|
58
|
+
"error": f"文件不存在: {filepath}"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# 检查文件大小
|
|
62
|
+
if os.path.getsize(filepath) > 10 * 1024 * 1024: # 10MB
|
|
63
|
+
return {
|
|
64
|
+
"success": False,
|
|
65
|
+
"error": "文件过大 (>10MB)"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
with open(filepath, 'r', encoding=encoding) as f:
|
|
69
|
+
content = f.read()
|
|
70
|
+
return {
|
|
71
|
+
"success": True,
|
|
72
|
+
"stdout": content,
|
|
73
|
+
"stderr": ""
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
elif operation in ["write", "append"]:
|
|
77
|
+
if not args.get("content"):
|
|
78
|
+
return {
|
|
79
|
+
"success": False,
|
|
80
|
+
"error": "写入/追加操作需要提供content参数"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# 创建目录(如果不存在)
|
|
84
|
+
os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True)
|
|
85
|
+
|
|
86
|
+
mode = 'a' if operation == "append" else 'w'
|
|
87
|
+
with open(filepath, mode, encoding=encoding) as f:
|
|
88
|
+
f.write(args["content"])
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"success": True,
|
|
92
|
+
"stdout": f"成功{operation}内容到 {filepath}",
|
|
93
|
+
"stderr": ""
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
else:
|
|
97
|
+
return {
|
|
98
|
+
"success": False,
|
|
99
|
+
"error": f"未知操作: {operation}"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
return {
|
|
104
|
+
"success": False,
|
|
105
|
+
"error": f"文件操作失败: {str(e)}"
|
|
106
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import pkgutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
from ..utils import PrettyOutput, OutputType
|
|
9
|
+
|
|
10
|
+
class PythonScript:
|
|
11
|
+
"""Python脚本管理类"""
|
|
12
|
+
SCRIPTS_DIR = "/tmp/ai_scripts"
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def init_scripts_dir(cls):
|
|
16
|
+
"""初始化脚本目录"""
|
|
17
|
+
Path(cls.SCRIPTS_DIR).mkdir(parents=True, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def generate_script_path(cls, name: Optional[str] = None) -> str:
|
|
21
|
+
"""生成脚本文件路径"""
|
|
22
|
+
if name:
|
|
23
|
+
safe_name = "".join(c for c in name if c.isalnum() or c in "._- ")
|
|
24
|
+
filename = f"{int(time.time())}_{safe_name}.py"
|
|
25
|
+
else:
|
|
26
|
+
filename = f"{int(time.time())}_script.py"
|
|
27
|
+
return str(Path(cls.SCRIPTS_DIR) / filename)
|
|
28
|
+
|
|
29
|
+
class PythonScriptTool:
|
|
30
|
+
name = "execute_python"
|
|
31
|
+
description = """Execute Python code and return the results.
|
|
32
|
+
Notes:
|
|
33
|
+
1. Use print() to output results
|
|
34
|
+
2. Automatic dependency management
|
|
35
|
+
3. Code saved to temporary file
|
|
36
|
+
"""
|
|
37
|
+
parameters = {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"properties": {
|
|
40
|
+
"code": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "Python code to execute"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"type": "array",
|
|
46
|
+
"items": {"type": "string"},
|
|
47
|
+
"description": "Python package dependencies",
|
|
48
|
+
"default": []
|
|
49
|
+
},
|
|
50
|
+
"name": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Script name",
|
|
53
|
+
"default": ""
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"required": ["code"]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _is_builtin_package(package_name: str) -> bool:
|
|
61
|
+
package_name = package_name.split("==")[0].strip()
|
|
62
|
+
if hasattr(sys.modules, package_name) or package_name in sys.stdlib_module_names:
|
|
63
|
+
return True
|
|
64
|
+
try:
|
|
65
|
+
return pkgutil.find_spec(package_name) is not None
|
|
66
|
+
except Exception:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
70
|
+
"""执行Python代码"""
|
|
71
|
+
try:
|
|
72
|
+
code = args["code"]
|
|
73
|
+
dependencies = args.get("dependencies", [])
|
|
74
|
+
script_name = args.get("name", "")
|
|
75
|
+
|
|
76
|
+
# 初始化脚本目录
|
|
77
|
+
PythonScript.init_scripts_dir()
|
|
78
|
+
|
|
79
|
+
# 生成脚本路径
|
|
80
|
+
script_path = PythonScript.generate_script_path(script_name)
|
|
81
|
+
|
|
82
|
+
# 安装依赖
|
|
83
|
+
missing_deps = []
|
|
84
|
+
for dep in dependencies:
|
|
85
|
+
if not self._is_builtin_package(dep):
|
|
86
|
+
missing_deps.append(dep)
|
|
87
|
+
|
|
88
|
+
if missing_deps:
|
|
89
|
+
PrettyOutput.print("正在安装依赖...", OutputType.INFO)
|
|
90
|
+
try:
|
|
91
|
+
subprocess.run(
|
|
92
|
+
[sys.executable, "-m", "pip", "install"] + missing_deps,
|
|
93
|
+
check=True,
|
|
94
|
+
capture_output=True,
|
|
95
|
+
text=True
|
|
96
|
+
)
|
|
97
|
+
PrettyOutput.print("依赖安装完成", OutputType.INFO)
|
|
98
|
+
except subprocess.CalledProcessError as e:
|
|
99
|
+
return {
|
|
100
|
+
"success": False,
|
|
101
|
+
"error": f"依赖安装失败: {e.stderr}"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# 写入脚本文件
|
|
105
|
+
with open(script_path, 'w', encoding='utf-8') as f:
|
|
106
|
+
f.write(code)
|
|
107
|
+
|
|
108
|
+
# 执行脚本
|
|
109
|
+
PrettyOutput.print(f"执行脚本: {script_path}", OutputType.INFO)
|
|
110
|
+
result = subprocess.run(
|
|
111
|
+
[sys.executable, script_path],
|
|
112
|
+
capture_output=True,
|
|
113
|
+
text=True
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# 构建输出
|
|
117
|
+
output = []
|
|
118
|
+
|
|
119
|
+
# 添加脚本信息
|
|
120
|
+
output.append(f"脚本路径: {script_path}")
|
|
121
|
+
if dependencies:
|
|
122
|
+
output.append(f"依赖项: {', '.join(dependencies)}")
|
|
123
|
+
output.append("")
|
|
124
|
+
|
|
125
|
+
# 添加执行结果
|
|
126
|
+
if result.stdout:
|
|
127
|
+
output.append("输出:")
|
|
128
|
+
output.append(result.stdout)
|
|
129
|
+
|
|
130
|
+
# 添加错误信息(如果有)
|
|
131
|
+
if result.stderr:
|
|
132
|
+
output.append("错误:")
|
|
133
|
+
output.append(result.stderr)
|
|
134
|
+
|
|
135
|
+
# 添加返回码
|
|
136
|
+
output.append(f"返回码: {result.returncode}")
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
"success": result.returncode == 0,
|
|
140
|
+
"stdout": "\n".join(output),
|
|
141
|
+
"stderr": result.stderr,
|
|
142
|
+
"return_code": result.returncode,
|
|
143
|
+
"script_path": script_path
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
return {
|
|
148
|
+
"success": False,
|
|
149
|
+
"error": f"执行Python代码失败: {str(e)}"
|
|
150
|
+
}
|
jarvis/tools/rag.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from typing import Dict, Any, List
|
|
2
|
+
import chromadb
|
|
3
|
+
from chromadb.utils import embedding_functions
|
|
4
|
+
import hashlib
|
|
5
|
+
import re
|
|
6
|
+
from ..utils import PrettyOutput, OutputType
|
|
7
|
+
|
|
8
|
+
class RAGTool:
|
|
9
|
+
name = "rag_query"
|
|
10
|
+
description = """Execute RAG queries on documents.
|
|
11
|
+
Features:
|
|
12
|
+
1. Auto-creates document embeddings
|
|
13
|
+
2. Returns relevant passages
|
|
14
|
+
3. Maintains embedding database
|
|
15
|
+
"""
|
|
16
|
+
parameters = {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"files": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {"type": "string"},
|
|
22
|
+
"description": "Files to process"
|
|
23
|
+
},
|
|
24
|
+
"query": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Query text"
|
|
27
|
+
},
|
|
28
|
+
"num_passages": {
|
|
29
|
+
"type": "integer",
|
|
30
|
+
"description": "Number of passages",
|
|
31
|
+
"default": 3
|
|
32
|
+
},
|
|
33
|
+
"chunk_size": {
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"description": "Chunk size",
|
|
36
|
+
"default": 500
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"required": ["files", "query"]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def _get_document_hash(self, content: str) -> str:
|
|
43
|
+
return hashlib.md5(content.encode()).hexdigest()
|
|
44
|
+
|
|
45
|
+
def _chunk_text(self, text: str, chunk_size: int = 500) -> List[str]:
|
|
46
|
+
"""将文本分割成适当大小的块"""
|
|
47
|
+
# 按句子分割
|
|
48
|
+
sentences = re.split(r'(?<=[.!?])\s+', text)
|
|
49
|
+
chunks = []
|
|
50
|
+
current_chunk = []
|
|
51
|
+
current_length = 0
|
|
52
|
+
|
|
53
|
+
for sentence in sentences:
|
|
54
|
+
sentence_length = len(sentence)
|
|
55
|
+
if current_length + sentence_length > chunk_size and current_chunk:
|
|
56
|
+
chunks.append(" ".join(current_chunk))
|
|
57
|
+
current_chunk = []
|
|
58
|
+
current_length = 0
|
|
59
|
+
current_chunk.append(sentence)
|
|
60
|
+
current_length += sentence_length
|
|
61
|
+
|
|
62
|
+
if current_chunk:
|
|
63
|
+
chunks.append(" ".join(current_chunk))
|
|
64
|
+
|
|
65
|
+
return chunks
|
|
66
|
+
|
|
67
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
68
|
+
"""执行RAG查询"""
|
|
69
|
+
try:
|
|
70
|
+
files = args["files"]
|
|
71
|
+
query = args["query"]
|
|
72
|
+
num_passages = args.get("num_passages", 3)
|
|
73
|
+
chunk_size = args.get("chunk_size", 500)
|
|
74
|
+
|
|
75
|
+
# 初始化ChromaDB
|
|
76
|
+
chroma_client = chromadb.PersistentClient(path="./data/chromadb")
|
|
77
|
+
|
|
78
|
+
# 使用sentence-transformers作为嵌入模型
|
|
79
|
+
embedding_function = embedding_functions.SentenceTransformerEmbeddingFunction(
|
|
80
|
+
model_name="all-MiniLM-L6-v2"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# 获取或创建集合
|
|
84
|
+
collection = chroma_client.get_or_create_collection(
|
|
85
|
+
name="document_store",
|
|
86
|
+
embedding_function=embedding_function
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# 处理每个文件
|
|
90
|
+
for file_path in files:
|
|
91
|
+
try:
|
|
92
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
93
|
+
content = f.read()
|
|
94
|
+
|
|
95
|
+
# 计算文档哈希值
|
|
96
|
+
doc_hash = self._get_document_hash(content)
|
|
97
|
+
|
|
98
|
+
# 检查文档是否已经存在且未更改
|
|
99
|
+
existing_ids = collection.get(
|
|
100
|
+
where={"doc_hash": doc_hash}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if not existing_ids["ids"]:
|
|
104
|
+
# 分块处理文档
|
|
105
|
+
chunks = self._chunk_text(content, chunk_size)
|
|
106
|
+
|
|
107
|
+
# 为每个块生成唯一ID
|
|
108
|
+
chunk_ids = [f"{doc_hash}_{i}" for i in range(len(chunks))]
|
|
109
|
+
|
|
110
|
+
# 添加到数据库
|
|
111
|
+
collection.add(
|
|
112
|
+
documents=chunks,
|
|
113
|
+
ids=chunk_ids,
|
|
114
|
+
metadatas=[{
|
|
115
|
+
"file_path": file_path,
|
|
116
|
+
"doc_hash": doc_hash,
|
|
117
|
+
"chunk_index": i
|
|
118
|
+
} for i in range(len(chunks))]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
PrettyOutput.print(f"已添加文档: {file_path}", OutputType.INFO)
|
|
122
|
+
else:
|
|
123
|
+
PrettyOutput.print(f"文档已存在且未更改: {file_path}", OutputType.INFO)
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
PrettyOutput.print(f"处理文件 {file_path} 时出错: {str(e)}", OutputType.ERROR)
|
|
127
|
+
|
|
128
|
+
# 执行查询
|
|
129
|
+
results = collection.query(
|
|
130
|
+
query_texts=[query],
|
|
131
|
+
n_results=num_passages
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# 格式化输出
|
|
135
|
+
output = [f"查询: {query}\n"]
|
|
136
|
+
output.append(f"找到 {len(results['documents'][0])} 个相关段落:\n")
|
|
137
|
+
|
|
138
|
+
for i, (doc, metadata) in enumerate(zip(results['documents'][0], results['metadatas'][0]), 1):
|
|
139
|
+
output.append(f"\n段落 {i}:")
|
|
140
|
+
output.append(f"来源: {metadata['file_path']}")
|
|
141
|
+
output.append(f"相关内容:\n{doc}\n")
|
|
142
|
+
output.append("-" * 50)
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
"success": True,
|
|
146
|
+
"stdout": "\n".join(output),
|
|
147
|
+
"stderr": ""
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
return {
|
|
152
|
+
"success": False,
|
|
153
|
+
"error": f"RAG查询失败: {str(e)}"
|
|
154
|
+
}
|
jarvis/tools/search.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from duckduckgo_search import DDGS
|
|
3
|
+
from ..utils import PrettyOutput, OutputType
|
|
4
|
+
|
|
5
|
+
class SearchTool:
|
|
6
|
+
name = "search"
|
|
7
|
+
description = "Search for information using DuckDuckGo search engine"
|
|
8
|
+
parameters = {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"query": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Search query string"
|
|
14
|
+
},
|
|
15
|
+
"max_results": {
|
|
16
|
+
"type": "integer",
|
|
17
|
+
"description": "Maximum number of results to return",
|
|
18
|
+
"default": 5
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"required": ["query"]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
25
|
+
"""使用DuckDuckGo进行搜索"""
|
|
26
|
+
try:
|
|
27
|
+
# 打印搜索查询
|
|
28
|
+
PrettyOutput.print(f"搜索查询: {args['query']}", OutputType.INFO)
|
|
29
|
+
|
|
30
|
+
# 获取搜索结果
|
|
31
|
+
with DDGS() as ddgs:
|
|
32
|
+
results = ddgs.text(
|
|
33
|
+
keywords=args["query"],
|
|
34
|
+
max_results=args.get("max_results", 5)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"success": True,
|
|
40
|
+
"stdout": results,
|
|
41
|
+
"stderr": ""
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
return {
|
|
46
|
+
"success": False,
|
|
47
|
+
"error": f"搜索失败: {str(e)}"
|
|
48
|
+
}
|
jarvis/tools/shell.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
import subprocess
|
|
3
|
+
from ..utils import PrettyOutput, OutputType
|
|
4
|
+
|
|
5
|
+
class ShellTool:
|
|
6
|
+
name = "execute_shell"
|
|
7
|
+
description = "Execute shell commands and return the results"
|
|
8
|
+
parameters = {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"command": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Shell command to execute"
|
|
14
|
+
},
|
|
15
|
+
"timeout": {
|
|
16
|
+
"type": "integer",
|
|
17
|
+
"description": "Command execution timeout in seconds",
|
|
18
|
+
"default": 30
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"required": ["command"]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
25
|
+
"""执行shell命令"""
|
|
26
|
+
try:
|
|
27
|
+
# 获取参数
|
|
28
|
+
command = args["command"]
|
|
29
|
+
timeout = args.get("timeout", 30)
|
|
30
|
+
|
|
31
|
+
# 执行命令
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
command,
|
|
34
|
+
shell=True,
|
|
35
|
+
capture_output=True,
|
|
36
|
+
text=True,
|
|
37
|
+
timeout=timeout
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# 构建输出
|
|
41
|
+
output = []
|
|
42
|
+
|
|
43
|
+
# 添加命令信息
|
|
44
|
+
PrettyOutput.print(f"执行命令: {command}", OutputType.INFO)
|
|
45
|
+
output.append(f"命令: {command}")
|
|
46
|
+
output.append("")
|
|
47
|
+
|
|
48
|
+
# 添加输出
|
|
49
|
+
if result.stdout:
|
|
50
|
+
output.append(result.stdout)
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
"success": True,
|
|
54
|
+
"stdout": "\n".join(output),
|
|
55
|
+
"stderr": result.stderr,
|
|
56
|
+
"return_code": result.returncode
|
|
57
|
+
}
|
|
58
|
+
except subprocess.TimeoutExpired:
|
|
59
|
+
return {
|
|
60
|
+
"success": False,
|
|
61
|
+
"error": f"命令执行超时 (>{timeout}秒)"
|
|
62
|
+
}
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return {
|
|
65
|
+
"success": False,
|
|
66
|
+
"error": str(e)
|
|
67
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from ..utils import PrettyOutput, OutputType
|
|
3
|
+
|
|
4
|
+
class UserConfirmationTool:
|
|
5
|
+
name = "ask_user_confirmation"
|
|
6
|
+
description = "Request confirmation from user, returns yes/no"
|
|
7
|
+
parameters = {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"question": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Question to ask for confirmation"
|
|
13
|
+
},
|
|
14
|
+
"details": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Additional details or context",
|
|
17
|
+
"default": ""
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": ["question"]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
24
|
+
"""获取用户确认"""
|
|
25
|
+
try:
|
|
26
|
+
question = args["question"]
|
|
27
|
+
details = args.get("details", "")
|
|
28
|
+
|
|
29
|
+
# 打印详细信息
|
|
30
|
+
if details:
|
|
31
|
+
PrettyOutput.print(details, OutputType.INFO)
|
|
32
|
+
PrettyOutput.print("", OutputType.INFO) # 空行
|
|
33
|
+
|
|
34
|
+
# 打印问题
|
|
35
|
+
PrettyOutput.print(f"{question} (y/n)", OutputType.INFO)
|
|
36
|
+
|
|
37
|
+
while True:
|
|
38
|
+
response = input(">>> ").strip().lower()
|
|
39
|
+
if response in ['y', 'yes']:
|
|
40
|
+
result = True
|
|
41
|
+
break
|
|
42
|
+
elif response in ['n', 'no']:
|
|
43
|
+
result = False
|
|
44
|
+
break
|
|
45
|
+
else:
|
|
46
|
+
PrettyOutput.print("请输入 y/yes 或 n/no", OutputType.ERROR)
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"success": True,
|
|
50
|
+
"stdout": str(result),
|
|
51
|
+
"stderr": ""
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
return {
|
|
56
|
+
"success": False,
|
|
57
|
+
"error": f"获取用户确认失败: {str(e)}"
|
|
58
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Dict, Any, List
|
|
2
|
+
from ..utils import PrettyOutput, OutputType, get_multiline_input
|
|
3
|
+
|
|
4
|
+
class UserInteractionTool:
|
|
5
|
+
name = "ask_user"
|
|
6
|
+
description = "Ask user for information, supports option selection and multiline input"
|
|
7
|
+
parameters = {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"question": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Question to ask the user"
|
|
13
|
+
},
|
|
14
|
+
"options": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"items": {"type": "string"},
|
|
17
|
+
"description": "List of options for user to choose from",
|
|
18
|
+
"default": []
|
|
19
|
+
},
|
|
20
|
+
"multiline": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"description": "Allow multiline input",
|
|
23
|
+
"default": False
|
|
24
|
+
},
|
|
25
|
+
"description": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Additional description or context",
|
|
28
|
+
"default": ""
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": ["question"]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
35
|
+
"""获取用户输入"""
|
|
36
|
+
try:
|
|
37
|
+
question = args["question"]
|
|
38
|
+
options = args.get("options", [])
|
|
39
|
+
multiline = args.get("multiline", False)
|
|
40
|
+
description = args.get("description", "")
|
|
41
|
+
|
|
42
|
+
# 打印问题描述
|
|
43
|
+
if description:
|
|
44
|
+
PrettyOutput.print(description, OutputType.INFO)
|
|
45
|
+
|
|
46
|
+
# 如果有选项,显示选项列表
|
|
47
|
+
if options:
|
|
48
|
+
PrettyOutput.print("\n可选项:", OutputType.INFO)
|
|
49
|
+
for i, option in enumerate(options, 1):
|
|
50
|
+
PrettyOutput.print(f"[{i}] {option}", OutputType.INFO)
|
|
51
|
+
|
|
52
|
+
while True:
|
|
53
|
+
try:
|
|
54
|
+
choice = input("\n请选择 (输入数字): ").strip()
|
|
55
|
+
if not choice:
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
choice = int(choice)
|
|
59
|
+
if 1 <= choice <= len(options):
|
|
60
|
+
response = options[choice - 1]
|
|
61
|
+
break
|
|
62
|
+
else:
|
|
63
|
+
PrettyOutput.print("无效选择,请重试", OutputType.ERROR)
|
|
64
|
+
except ValueError:
|
|
65
|
+
PrettyOutput.print("请输入有效数字", OutputType.ERROR)
|
|
66
|
+
|
|
67
|
+
# 多行输入
|
|
68
|
+
elif multiline:
|
|
69
|
+
response = get_multiline_input(question)
|
|
70
|
+
|
|
71
|
+
# 单行输入
|
|
72
|
+
else:
|
|
73
|
+
PrettyOutput.print(question, OutputType.INFO)
|
|
74
|
+
response = input(">>> ").strip()
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"success": True,
|
|
78
|
+
"stdout": response,
|
|
79
|
+
"stderr": ""
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
return {
|
|
84
|
+
"success": False,
|
|
85
|
+
"error": f"获取用户输入失败: {str(e)}"
|
|
86
|
+
}
|